diff --git a/.cargo/config.toml b/.cargo/config.toml index 1b8ffe1a1c827fcf5fcf65b6c24c0439e2f3dc8c..8573f582e258b38b5225013d1fa5c65964eb64bf 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -9,3 +9,8 @@ rustdocflags = [ CC_x86_64_unknown_linux_musl = { value = ".cargo/musl-gcc", force = true, relative = true } CXX_x86_64_unknown_linux_musl = { value = ".cargo/musl-g++", force = true, relative = true } CARGO_WORKSPACE_ROOT_DIR = { value = "", relative = true } +SQLX_OFFLINE = "true" + +[net] +retry = 5 +# git-fetch-with-cli = true # commented because there is a risk that a runner can be banned by github diff --git a/.config/lychee.toml b/.config/lychee.toml index b1f08de33340999d52114ed8c6b5fcd6604662fa..58f8d068d9d13d270c19445fa6983c605b0febb4 100644 --- a/.config/lychee.toml +++ b/.config/lychee.toml @@ -28,7 +28,7 @@ exclude = [ "http://visitme/", "https://visitme/", - # TODO <https://github.com/paritytech/polkadot-sdk/issues/134> + # TODO meta issue: <https://github.com/paritytech/polkadot-sdk/issues/134> "https://docs.substrate.io/main-docs/build/custom-rpc/#public-rpcs", "https://docs.substrate.io/rustdocs/latest/sp_api/macro.decl_runtime_apis.html", "https://github.com/ipfs/js-ipfs-bitswap/blob/", @@ -50,8 +50,10 @@ exclude = [ "https://w3f.github.io/parachain-implementers-guide/runtime/session_info.html", # Behind a captcha (code 403): + "https://chainlist.org/chain/*", "https://iohk.io/en/blog/posts/2023/11/03/partner-chains-are-coming-to-cardano/", "https://www.reddit.com/r/rust/comments/3spfh1/does_collect_allocate_more_than_once_while/", + # 403 rate limited: "https://etherscan.io/block/11090290", "https://subscan.io/", diff --git a/.config/taplo.toml b/.config/taplo.toml index 7cbc1b075125ad237f16d5d7dd33b0de7089ac38..4b8afc74a52ead13766e1af769ca2af6b31835d9 100644 --- a/.config/taplo.toml +++ b/.config/taplo.toml @@ -40,3 +40,10 @@ keys = ["workspace.dependencies"] [rule.formatting] reorder_keys = true + +[[rule]] +include = ["**/Cargo.toml"] +keys = ["build-dependencies", "dependencies", "dev-dependencies"] + +[rule.formatting] +reorder_keys = true diff --git a/.github/env b/.github/env index bb61e1f4cd99f7a3f585bb04d8961ee6fb0ffb2f..730c37f1db80beb0cf79f56140dd740c572a1353 100644 --- a/.github/env +++ b/.github/env @@ -1 +1 @@ -IMAGE="docker.io/paritytech/ci-unified:bullseye-1.81.0-2024-09-11-v202409111034" +IMAGE="docker.io/paritytech/ci-unified:bullseye-1.81.0-2024-11-19-v202411281558" diff --git a/.github/scripts/cmd/cmd.py b/.github/scripts/cmd/cmd.py index 9da05cac17b93d9064d564527e6affed57892c8f..e65f10a97b4e8ee56d73dd8fa34613bdaa927a2e 100755 --- a/.github/scripts/cmd/cmd.py +++ b/.github/scripts/cmd/cmd.py @@ -45,20 +45,20 @@ BENCH """ bench_example = '''**Examples**: - Runs all benchmarks + 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 fails fast on first failed benchmark %(prog)s --runtime westend --fail-fast - - Does not output anything and cleans up the previous bot's & author command triggering comments in PR + + 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) +parser_bench = subparsers.add_parser('bench', aliases=['bench-omni'], help='Runs benchmarks (frame omni bencher)', epilog=bench_example, formatter_class=argparse.RawDescriptionHelpFormatter) for arg, config in common_args.items(): parser_bench.add_argument(arg, **config) @@ -67,6 +67,7 @@ parser_bench.add_argument('--runtime', help='Runtime(s) space separated', choice parser_bench.add_argument('--pallet', help='Pallet(s) space separated', nargs='*', default=[]) parser_bench.add_argument('--fail-fast', help='Fail fast on first failed benchmark', action='store_true') + """ FMT """ @@ -98,12 +99,12 @@ def main(): print(f'args: {args}') - if args.command == 'bench': + if args.command == 'bench' or args.command == 'bench-omni': runtime_pallets_map = {} failed_benchmarks = {} successful_benchmarks = {} - profile = "release" + profile = "production" print(f'Provided runtimes: {args.runtime}') # convert to mapped dict @@ -111,13 +112,36 @@ def main(): runtimesMatrix = {x['name']: x for x in runtimesMatrix} print(f'Filtered out runtimes: {runtimesMatrix}') + compile_bencher = os.system(f"cargo install --path substrate/utils/frame/omni-bencher --locked --profile {profile}") + if compile_bencher != 0: + print_and_log('⌠Failed to compile frame-omni-bencher') + sys.exit(1) + # 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['bench_features']}") + build_command = f"forklift cargo build -p {runtime['package']} --profile {profile} --features={runtime['bench_features']}" + print(f'-- building "{runtime["name"]}" with `{build_command}`') + build_status = os.system(build_command) + if build_status != 0: + print_and_log(f'⌠Failed to build {runtime["name"]}') + if args.fail_fast: + sys.exit(1) + else: + continue + 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} {runtime['bench_flags']}").read() + list_command = f"frame-omni-bencher v1 benchmark pallet " \ + f"--no-csv-header " \ + f"--no-storage-info " \ + f"--no-min-squares " \ + f"--no-median-slopes " \ + f"--all " \ + f"--list " \ + f"--runtime={wasm_file} " \ + f"{runtime['bench_flags']}" + print(f'-- running: {list_command}') + output = os.popen(list_command).read() raw_pallets = output.strip().split('\n') all_pallets = set() @@ -179,12 +203,15 @@ def main(): # TODO: we can remove once all pallets in dev runtime are migrated to polkadot-sdk-frame try: uses_polkadot_sdk_frame = "true" in os.popen(f"cargo metadata --locked --format-version 1 --no-deps | jq -r '.packages[] | select(.name == \"{pallet.replace('_', '-')}\") | .dependencies | any(.name == \"polkadot-sdk-frame\")'").read() + print(f'uses_polkadot_sdk_frame: {uses_polkadot_sdk_frame}') # Empty output from the previous os.popen command except StopIteration: + print(f'Error: {pallet} not found in dev runtime') uses_polkadot_sdk_frame = False template = config['template'] if uses_polkadot_sdk_frame and re.match(r"frame-(:?umbrella-)?weight-template\.hbs", os.path.normpath(template).split(os.path.sep)[-1]): template = "substrate/.maintain/frame-umbrella-weight-template.hbs" + print(f'template: {template}') else: default_path = f"./{config['path']}/src/weights" xcm_path = f"./{config['path']}/src/weights/xcm" diff --git a/.github/scripts/cmd/test_cmd.py b/.github/scripts/cmd/test_cmd.py index 7b29fbfe90d82f76256c7090d340f62e2223a2a4..68998b9899090ae76444426f8298ba4093700789 100644 --- a/.github/scripts/cmd/test_cmd.py +++ b/.github/scripts/cmd/test_cmd.py @@ -47,7 +47,7 @@ mock_runtimes_matrix = [ def get_mock_bench_output(runtime, pallets, output_path, header, bench_flags, template = None): return f"frame-omni-bencher v1 benchmark pallet --extrinsic=* " \ - f"--runtime=target/release/wbuild/{runtime}-runtime/{runtime.replace('-', '_')}_runtime.wasm " \ + f"--runtime=target/production/wbuild/{runtime}-runtime/{runtime.replace('-', '_')}_runtime.wasm " \ f"--pallet={pallets} --header={header} " \ f"--output={output_path} " \ f"--wasm-execution=compiled " \ @@ -93,7 +93,7 @@ class TestCmd(unittest.TestCase): def test_bench_command_normal_execution_all_runtimes(self): self.mock_parse_args.return_value = (argparse.Namespace( - command='bench', + command='bench-omni', runtime=list(map(lambda x: x['name'], mock_runtimes_matrix)), pallet=['pallet_balances'], fail_fast=True, @@ -117,10 +117,10 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p kitchensink-runtime --profile release --features=runtime-benchmarks"), - call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), - call("forklift cargo build -p rococo-runtime --profile release --features=runtime-benchmarks"), - call("forklift cargo build -p asset-hub-westend-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p kitchensink-runtime --profile production --features=runtime-benchmarks"), + call("forklift cargo build -p westend-runtime --profile production --features=runtime-benchmarks"), + call("forklift cargo build -p rococo-runtime --profile production --features=runtime-benchmarks"), + call("forklift cargo build -p asset-hub-westend-runtime --profile production --features=runtime-benchmarks"), call(get_mock_bench_output( runtime='kitchensink', @@ -150,7 +150,7 @@ class TestCmd(unittest.TestCase): def test_bench_command_normal_execution(self): self.mock_parse_args.return_value = (argparse.Namespace( - command='bench', + command='bench-omni', runtime=['westend'], pallet=['pallet_balances', 'pallet_staking'], fail_fast=True, @@ -170,7 +170,7 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p westend-runtime --profile production --features=runtime-benchmarks"), # Westend runtime calls call(get_mock_bench_output( @@ -193,7 +193,7 @@ class TestCmd(unittest.TestCase): def test_bench_command_normal_execution_xcm(self): self.mock_parse_args.return_value = (argparse.Namespace( - command='bench', + command='bench-omni', runtime=['westend'], pallet=['pallet_xcm_benchmarks::generic'], fail_fast=True, @@ -213,7 +213,7 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p westend-runtime --profile production --features=runtime-benchmarks"), # Westend runtime calls call(get_mock_bench_output( @@ -229,7 +229,7 @@ class TestCmd(unittest.TestCase): def test_bench_command_two_runtimes_two_pallets(self): self.mock_parse_args.return_value = (argparse.Namespace( - command='bench', + command='bench-omni', runtime=['westend', 'rococo'], pallet=['pallet_balances', 'pallet_staking'], fail_fast=True, @@ -250,8 +250,8 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), - call("forklift cargo build -p rococo-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p westend-runtime --profile production --features=runtime-benchmarks"), + call("forklift cargo build -p rococo-runtime --profile production --features=runtime-benchmarks"), # Westend runtime calls call(get_mock_bench_output( runtime='westend', @@ -287,7 +287,7 @@ class TestCmd(unittest.TestCase): def test_bench_command_one_dev_runtime(self): self.mock_parse_args.return_value = (argparse.Namespace( - command='bench', + command='bench-omni', runtime=['dev'], pallet=['pallet_balances'], fail_fast=True, @@ -309,7 +309,7 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p kitchensink-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p kitchensink-runtime --profile production --features=runtime-benchmarks"), # Westend runtime calls call(get_mock_bench_output( runtime='kitchensink', @@ -324,7 +324,7 @@ class TestCmd(unittest.TestCase): def test_bench_command_one_cumulus_runtime(self): self.mock_parse_args.return_value = (argparse.Namespace( - command='bench', + command='bench-omni', runtime=['asset-hub-westend'], pallet=['pallet_assets'], fail_fast=True, @@ -344,7 +344,7 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p asset-hub-westend-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p asset-hub-westend-runtime --profile production --features=runtime-benchmarks"), # Asset-hub-westend runtime calls call(get_mock_bench_output( runtime='asset-hub-westend', @@ -359,7 +359,7 @@ class TestCmd(unittest.TestCase): def test_bench_command_one_cumulus_runtime_xcm(self): self.mock_parse_args.return_value = (argparse.Namespace( - command='bench', + command='bench-omni', runtime=['asset-hub-westend'], pallet=['pallet_xcm_benchmarks::generic', 'pallet_assets'], fail_fast=True, @@ -379,7 +379,7 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p asset-hub-westend-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p asset-hub-westend-runtime --profile production --features=runtime-benchmarks"), # Asset-hub-westend runtime calls call(get_mock_bench_output( runtime='asset-hub-westend', diff --git a/.github/scripts/common/lib.sh b/.github/scripts/common/lib.sh index e3dd6224f29b2d7c4a0a1300e844ba45a4e7ed98..c9be21e45dcb2d5d8e3aca0987e53a25798cf2f5 100755 --- a/.github/scripts/common/lib.sh +++ b/.github/scripts/common/lib.sh @@ -237,24 +237,52 @@ fetch_release_artifacts() { popd > /dev/null } -# Fetch the release artifacts like binary and signatures from S3. Assumes the ENV are set: +# Fetch deb package from S3. Assumes the ENV are set: # - RELEASE_ID # - GITHUB_TOKEN # - REPO in the form paritytech/polkadot -fetch_release_artifacts_from_s3() { +fetch_debian_package_from_s3() { BINARY=$1 echo "Version : $VERSION" echo "Repo : $REPO" echo "Binary : $BINARY" + echo "Tag : $RELEASE_TAG" OUTPUT_DIR=${OUTPUT_DIR:-"./release-artifacts/${BINARY}"} echo "OUTPUT_DIR : $OUTPUT_DIR" URL_BASE=$(get_s3_url_base $BINARY) echo "URL_BASE=$URL_BASE" - URL_BINARY=$URL_BASE/$VERSION/$BINARY - URL_SHA=$URL_BASE/$VERSION/$BINARY.sha256 - URL_ASC=$URL_BASE/$VERSION/$BINARY.asc + URL=$URL_BASE/$RELEASE_TAG/x86_64-unknown-linux-gnu/${BINARY}_${VERSION}_amd64.deb + + mkdir -p "$OUTPUT_DIR" + pushd "$OUTPUT_DIR" > /dev/null + + echo "Fetching deb package..." + + echo "Fetching %s" "$URL" + curl --progress-bar -LO "$URL" || echo "Missing $URL" + + pwd + ls -al --color + popd > /dev/null + +} + +# Fetch the release artifacts like binary and signatures from S3. Assumes the ENV are set: +# inputs: binary (polkadot), target(aarch64-apple-darwin) +fetch_release_artifacts_from_s3() { + BINARY=$1 + TARGET=$2 + OUTPUT_DIR=${OUTPUT_DIR:-"./release-artifacts/${TARGET}/${BINARY}"} + echo "OUTPUT_DIR : $OUTPUT_DIR" + + URL_BASE=$(get_s3_url_base $BINARY) + echo "URL_BASE=$URL_BASE" + + URL_BINARY=$URL_BASE/$VERSION/$TARGET/$BINARY + URL_SHA=$URL_BASE/$VERSION/$TARGET/$BINARY.sha256 + URL_ASC=$URL_BASE/$VERSION/$TARGET/$BINARY.asc # Fetch artifacts mkdir -p "$OUTPUT_DIR" @@ -269,7 +297,6 @@ fetch_release_artifacts_from_s3() { pwd ls -al --color popd > /dev/null - } # Pass the name of the binary as input, it will @@ -277,15 +304,26 @@ fetch_release_artifacts_from_s3() { function get_s3_url_base() { name=$1 case $name in - polkadot | polkadot-execute-worker | polkadot-prepare-worker | staking-miner) + polkadot | polkadot-execute-worker | polkadot-prepare-worker ) printf "https://releases.parity.io/polkadot" ;; - polkadot-parachain) - printf "https://releases.parity.io/cumulus" + polkadot-parachain) + printf "https://releases.parity.io/polkadot-parachain" + ;; + + polkadot-omni-node) + printf "https://releases.parity.io/polkadot-omni-node" ;; - *) + chain-spec-builder) + printf "https://releases.parity.io/chain-spec-builder" + ;; + + frame-omni-bencher) + printf "https://releases.parity.io/frame-omni-bencher" + ;; + *) printf "UNSUPPORTED BINARY $name" exit 1 ;; @@ -468,3 +506,16 @@ validate_stable_tag() { exit 1 fi } + +# Prepare docker stable tag form the polkadot stable tag +# input: tag (polkaodot-stableYYMM(-X) or polkadot-stableYYMM(-X)-rcX) +# output: stableYYMM(-X) or stableYYMM(-X)-rcX +prepare_docker_stable_tag() { + tag="$1" + if [[ "$tag" =~ stable[0-9]{4}(-[0-9]+)?(-rc[0-9]+)? ]]; then + echo "${BASH_REMATCH[0]}" + else + echo "Tag is invalid: $tag" + exit 1 + fi +} diff --git a/.github/scripts/generate-prdoc.py b/.github/scripts/generate-prdoc.py index 780fa0012976681adf1d2f75480ccc4b50f4ae4b..43e8437a0c960b547cf88c0c9fe70ef602944ec7 100644 --- a/.github/scripts/generate-prdoc.py +++ b/.github/scripts/generate-prdoc.py @@ -36,6 +36,21 @@ def from_pr_number(n, audience, bump, force): create_prdoc(n, audience, pr.title, pr.body, patch, bump, force) +def translate_audience(audience): + aliases = { + 'runtime_dev': 'Runtime Dev', + 'runtime_user': 'Runtime Operator', + 'node_dev': 'Node Dev', + 'node_user': 'Node User', + } + + if audience in aliases: + to = aliases[audience] + print(f"Translated audience '{audience}' to '{to}'") + audience = to + + return audience + def create_prdoc(pr, audience, title, description, patch, bump, force): path = f"prdoc/pr_{pr}.prdoc" @@ -49,6 +64,7 @@ def create_prdoc(pr, audience, title, description, patch, bump, force): print(f"No preexisting PrDoc for PR {pr}") prdoc = { "title": title, "doc": [{}], "crates": [] } + audience = translate_audience(audience) prdoc["doc"][0]["audience"] = audience prdoc["doc"][0]["description"] = description @@ -70,10 +86,10 @@ def create_prdoc(pr, audience, title, description, patch, bump, force): if p == '/': exit(1) p = os.path.dirname(p) - + with open(os.path.join(p, "Cargo.toml")) as f: manifest = toml.load(f) - + if not "package" in manifest: continue @@ -117,7 +133,7 @@ def setup_parser(parser=None, pr_required=True): parser = argparse.ArgumentParser() parser.add_argument("--pr", type=int, required=pr_required, help="The PR number to generate the PrDoc for.") parser.add_argument("--audience", type=str, nargs='*', choices=allowed_audiences, default=["todo"], help="The audience of whom the changes may concern. Example: --audience runtime_dev node_dev") - parser.add_argument("--bump", type=str, default="major", choices=["patch", "minor", "major", "silent", "ignore", "no_change"], help="A default bump level for all crates. Example: --bump patch") + parser.add_argument("--bump", type=str, default="major", choices=["patch", "minor", "major", "silent", "ignore", "none"], help="A default bump level for all crates. Example: --bump patch") parser.add_argument("--force", action="store_true", help="Whether to overwrite any existing PrDoc.") return parser diff --git a/.github/scripts/release/distributions b/.github/scripts/release/distributions new file mode 100644 index 0000000000000000000000000000000000000000..a430ec76c6ba2d6698ce0b82830ee594e03f69fa --- /dev/null +++ b/.github/scripts/release/distributions @@ -0,0 +1,39 @@ +Origin: Parity +Label: Parity +Codename: release +Architectures: amd64 +Components: main +Description: Apt repository for software made by Parity Technologies Ltd. +SignWith: 90BD75EBBB8E95CB3DA6078F94A4029AB4B35DAE + +Origin: Parity +Label: Parity Staging +Codename: staging +Architectures: amd64 +Components: main +Description: Staging distribution for Parity Technologies Ltd. packages +SignWith: 90BD75EBBB8E95CB3DA6078F94A4029AB4B35DAE + +Origin: Parity +Label: Parity stable2407 +Codename: stable2407 +Architectures: amd64 +Components: main +Description: Apt repository for software made by Parity Technologies Ltd. +SignWith: 90BD75EBBB8E95CB3DA6078F94A4029AB4B35DAE + +Origin: Parity +Label: Parity stable2409 +Codename: stable2409 +Architectures: amd64 +Components: main +Description: Apt repository for software made by Parity Technologies Ltd. +SignWith: 90BD75EBBB8E95CB3DA6078F94A4029AB4B35DAE + +Origin: Parity +Label: Parity stable2412 +Codename: stable2412 +Architectures: amd64 +Components: main +Description: Apt repository for software made by Parity Technologies Ltd. +SignWith: 90BD75EBBB8E95CB3DA6078F94A4029AB4B35DAE diff --git a/.github/scripts/release/release_lib.sh b/.github/scripts/release/release_lib.sh index 8b9254ec3f290dada1906cf9c09bc3e93cde3229..984709f2ea0315064f5069b5592428e1d618fc38 100644 --- a/.github/scripts/release/release_lib.sh +++ b/.github/scripts/release/release_lib.sh @@ -129,13 +129,69 @@ upload_s3_release() { echo "Working on version: $version " echo "Working on platform: $target " + URL_BASE=$(get_s3_url_base $product) + echo "Current content, should be empty on new uploads:" - aws s3 ls "s3://releases.parity.io/${product}/${version}/${target}" --recursive --human-readable --summarize || true + aws s3 ls "s3://${URL_BASE}/${version}/${target}" --recursive --human-readable --summarize || true echo "Content to be uploaded:" - artifacts="artifacts/$product/" + artifacts="release-artifacts/$target/$product/" ls "$artifacts" - aws s3 sync --acl public-read "$artifacts" "s3://releases.parity.io/${product}/${version}/${target}" + aws s3 sync --acl public-read "$artifacts" "s3://${URL_BASE}/${version}/${target}" echo "Uploaded files:" - aws s3 ls "s3://releases.parity.io/${product}/${version}/${target}" --recursive --human-readable --summarize - echo "✅ The release should be at https://releases.parity.io/${product}/${version}/${target}" + aws s3 ls "s3://${URL_BASE}/${version}/${target}" --recursive --human-readable --summarize + echo "✅ The release should be at https://${URL_BASE}/${version}/${target}" +} + +# Upload runtimes artifacts to s3 release bucket +# +# input: version (stable release tage.g. polkadot-stable2412 or polkadot-stable2412-rc1) +# output: none +upload_s3_runtimes_release_artifacts() { + alias aws='podman run --rm -it docker.io/paritytech/awscli -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_BUCKET aws' + + version=$1 + + echo "Working on version: $version " + + echo "Current content, should be empty on new uploads:" + aws s3 ls "s3://releases.parity.io/polkadot/runtimes/${version}/" --recursive --human-readable --summarize || true + echo "Content to be uploaded:" + artifacts="artifacts/runtimes/" + ls "$artifacts" + aws s3 sync --acl public-read "$artifacts" "s3://releases.parity.io/polkadot/runtimes/${version}/" + echo "Uploaded files:" + aws s3 ls "s3://releases.parity.io/polkadot/runtimes/${version}/" --recursive --human-readable --summarize + echo "✅ The release should be at https://releases.parity.io/polkadot/runtimes/${version}" +} + + +# Pass the name of the binary as input, it will +# return the s3 base url +function get_s3_url_base() { + name=$1 + case $name in + polkadot | polkadot-execute-worker | polkadot-prepare-worker ) + printf "releases.parity.io/polkadot" + ;; + + polkadot-parachain) + printf "releases.parity.io/polkadot-parachain" + ;; + + polkadot-omni-node) + printf "releases.parity.io/polkadot-omni-node" + ;; + + chain-spec-builder) + printf "releases.parity.io/chain-spec-builder" + ;; + + frame-omni-bencher) + printf "releases.parity.io/frame-omni-bencher" + ;; + *) + printf "UNSUPPORTED BINARY $name" + exit 1 + ;; + esac } diff --git a/.github/workflows/bench-all-runtimes.yml b/.github/workflows/bench-all-runtimes.yml new file mode 100644 index 0000000000000000000000000000000000000000..fa36a6c249776e152c1df82884f864b1ba62a9cc --- /dev/null +++ b/.github/workflows/bench-all-runtimes.yml @@ -0,0 +1,214 @@ +name: Bench all runtimes + +on: + # schedule: + # - cron: '0 1 * * 0' # weekly on Sunday night 01:00 UTC + workflow_dispatch: + inputs: + draft: + type: boolean + default: false + description: "Whether to create a draft PR" + +permissions: # allow the action to create a PR + contents: write + issues: write + pull-requests: write + actions: read + +jobs: + preflight: + uses: ./.github/workflows/reusable-preflight.yml + + runtime-matrix: + runs-on: ubuntu-latest + needs: [preflight] + timeout-minutes: 30 + outputs: + runtime: ${{ steps.runtime.outputs.runtime }} + branch: ${{ steps.branch.outputs.branch }} + date: ${{ steps.branch.outputs.date }} + container: + image: ${{ needs.preflight.outputs.IMAGE }} + name: Extract runtimes from matrix + steps: + - uses: actions/checkout@v4 + with: + ref: master + + - name: Extract runtimes + id: runtime + run: | + RUNTIMES=$(jq '[.[] | select(.package != null)]' .github/workflows/runtimes-matrix.json) + + RUNTIMES=$(echo $RUNTIMES | jq -c .) + echo "runtime=$RUNTIMES" + echo "runtime=$RUNTIMES" >> $GITHUB_OUTPUT + + - name: Create branch + id: branch + run: | + DATE=$(date +'%Y-%m-%d-%s') + BRANCH="update-weights-weekly-$DATE" + # Fixes "detected dubious ownership" error in the ci + git config --global --add safe.directory $GITHUB_WORKSPACE + + git checkout -b $BRANCH + git push --set-upstream origin $BRANCH + + echo "date=$DATE" >> $GITHUB_OUTPUT + echo "branch=$BRANCH" >> $GITHUB_OUTPUT + + run-frame-omni-bencher: + needs: [preflight, runtime-matrix] + runs-on: ${{ needs.preflight.outputs.RUNNER_WEIGHTS }} + # 24 hours per runtime. + # Max it takes 14hr for westend to recalculate, but due to limited runners, + # sometimes it can take longer. + timeout-minutes: 1440 + strategy: + fail-fast: false # keep running other workflows even if one fails, to see the logs of all possible failures + matrix: + runtime: ${{ fromJSON(needs.runtime-matrix.outputs.runtime) }} + container: + image: ${{ needs.preflight.outputs.IMAGE }} + env: + PACKAGE_NAME: ${{ matrix.runtime.package }} + FLAGS: ${{ matrix.runtime.bench_flags }} + RUST_LOG: "frame_omni_bencher=info,polkadot_sdk_frame=info" + steps: + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ needs.runtime-matrix.outputs.branch }} # checkout always from the initially created branch to avoid conflicts + + - name: script + id: required + run: | + git --version + # Fixes "detected dubious ownership" error in the ci + git config --global --add safe.directory $GITHUB_WORKSPACE + git remote -v + python3 -m pip install -r .github/scripts/generate-prdoc.requirements.txt + python3 .github/scripts/cmd/cmd.py bench --runtime ${{ matrix.runtime.name }} + git add . + git status + + if [ -f /tmp/cmd/command_output.log ]; then + CMD_OUTPUT=$(cat /tmp/cmd/command_output.log) + # export to summary to display in the PR + echo "$CMD_OUTPUT" >> $GITHUB_STEP_SUMMARY + # should be multiline, otherwise it captures the first line only + echo 'cmd_output<<EOF' >> $GITHUB_OUTPUT + echo "$CMD_OUTPUT" >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT + fi + + # Create patch that includes both modifications and new files + git add -A + git diff --staged > diff-${{ matrix.runtime.name }}.patch -U0 + git reset + + - name: Upload diff + uses: actions/upload-artifact@v4 + with: + name: diff-${{ matrix.runtime.name }} + path: diff-${{ matrix.runtime.name }}.patch + + apply-diff-commit: + runs-on: ubuntu-latest + needs: [runtime-matrix, run-frame-omni-bencher] + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ needs.runtime-matrix.outputs.branch }} + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: patches + + # needs to be able to trigger CI + - uses: actions/create-github-app-token@v1 + id: generate_token + with: + app-id: ${{ secrets.CMD_BOT_APP_ID }} + private-key: ${{ secrets.CMD_BOT_APP_KEY }} + + - name: Apply diff and create PR + env: + GH_TOKEN: ${{ steps.generate_token.outputs.token }} + BRANCH: ${{ needs.runtime-matrix.outputs.branch }} + DATE: ${{ needs.runtime-matrix.outputs.date }} + run: | + git --version + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + git status + + # Apply all patches + for file in patches/diff-*/diff-*.patch; do + if [ -f "$file" ] && [ -s "$file" ]; then + echo "Applying $file" + # using --3way and --ours for conflicts resolution. Requires git 2.47+ + git apply "$file" --unidiff-zero --allow-empty --3way --ours || echo "Failed to apply $file" + else + echo "Skipping empty or non-existent patch file: $file" + fi + done + + rm -rf patches + + # Get release tags from 1 and 3 months ago + ONE_MONTH_AGO=$(date -d "1 month ago" +%Y-%m-%d) + THREE_MONTHS_AGO=$(date -d "3 months ago" +%Y-%m-%d) + + # Get tags with their dates + ONE_MONTH_INFO=$(git for-each-ref --sort=-creatordate --format '%(refname:short)|%(creatordate:iso-strict-local)' 'refs/tags/polkadot-v*' | awk -v date="$ONE_MONTH_AGO" -F'|' '$2 <= date {print $0; exit}') + THREE_MONTHS_INFO=$(git for-each-ref --sort=-creatordate --format '%(refname:short)|%(creatordate:iso-strict-local)' 'refs/tags/polkadot-v*' | awk -v date="$THREE_MONTHS_AGO" -F'|' '$2 <= date {print $0; exit}') + + # Split into tag and date + ONE_MONTH_TAG=$(echo "$ONE_MONTH_INFO" | cut -d'|' -f1) + ONE_MONTH_DATE=$(echo "$ONE_MONTH_INFO" | cut -d'|' -f2 | cut -d'T' -f1) + THREE_MONTHS_TAG=$(echo "$THREE_MONTHS_INFO" | cut -d'|' -f1) + THREE_MONTHS_DATE=$(echo "$THREE_MONTHS_INFO" | cut -d'|' -f2 | cut -d'T' -f1) + + # Base URL for Subweight comparisons + BASE_URL="https://weights.tasty.limo/compare?repo=polkadot-sdk&threshold=5&path_pattern=.%2F**%2Fweights%2F**%2F*.rs%2C.%2F**%2Fweights.rs&method=asymptotic&ignore_errors=true&unit=time" + + # Generate comparison links + MASTER_LINK="${BASE_URL}&old=master&new=${BRANCH}" + ONE_MONTH_LINK="${BASE_URL}&old=${ONE_MONTH_TAG}&new=${BRANCH}" + THREE_MONTHS_LINK="${BASE_URL}&old=${THREE_MONTHS_TAG}&new=${BRANCH}" + + # Create PR body with all links in a temporary file + cat > /tmp/pr_body.md << EOF + Auto-update of all weights for ${DATE}. + + Subweight results: + - [now vs master](${MASTER_LINK}) + - [now vs ${ONE_MONTH_TAG} (${ONE_MONTH_DATE})](${ONE_MONTH_LINK}) + - [now vs ${THREE_MONTHS_TAG} (${THREE_MONTHS_DATE})](${THREE_MONTHS_LINK}) + EOF + + git add . + git commit -m "Update all weights weekly for $DATE" + git push --set-upstream origin "$BRANCH" + + MAYBE_DRAFT=${{ inputs.draft && '--draft' || '' }} + + PR_TITLE="Auto-update of all weights for $DATE" + gh pr create \ + --title "$PR_TITLE" \ + --head "$BRANCH" \ + --base "master" \ + --reviewer paritytech/ci \ + --reviewer paritytech/release-engineering \ + $MAYBE_DRAFT \ + --label "R0-silent" \ + --body "$(cat /tmp/pr_body.md)" \ No newline at end of file diff --git a/.github/workflows/benchmarks-networking.yml b/.github/workflows/benchmarks-networking.yml new file mode 100644 index 0000000000000000000000000000000000000000..8f4246c79548169e9fab03cd61719f9e04f2cea1 --- /dev/null +++ b/.github/workflows/benchmarks-networking.yml @@ -0,0 +1,111 @@ +name: Networking Benchmarks + +on: + push: + branches: + - master + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + preflight: + uses: ./.github/workflows/reusable-preflight.yml + + build: + timeout-minutes: 50 + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER_BENCHMARK }} + container: + image: ${{ needs.preflight.outputs.IMAGE }} + strategy: + fail-fast: false + matrix: + features: + [ + { bench: "notifications_protocol" }, + { bench: "request_response_protocol" }, + ] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Run Benchmarks + id: run-benchmarks + run: | + mkdir -p ./charts + forklift cargo bench -p sc-network --bench ${{ matrix.features.bench }} -- --output-format bencher | grep "^test" | tee ./charts/${{ matrix.features.bench }}.txt || echo "Benchmarks failed" + ls -lsa ./charts + + - name: Upload artifacts + uses: actions/upload-artifact@v4.3.6 + with: + name: ${{ matrix.features.bench }}-${{ github.sha }} + path: ./charts + + publish-benchmarks: + timeout-minutes: 60 + needs: [build] + if: github.ref == 'refs/heads/master' + environment: subsystem-benchmarks + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: gh-pages + fetch-depth: 0 + + - run: git checkout master -- + + - name: Download artifacts + uses: actions/download-artifact@v4.1.8 + with: + name: notifications_protocol-${{ github.sha }} + path: ./charts + + - name: Download artifacts + uses: actions/download-artifact@v4.1.8 + with: + name: request_response_protocol-${{ github.sha }} + path: ./charts + + - name: Setup git + run: | + # Fixes "detected dubious ownership" error in the ci + git config --global --add safe.directory '*' + ls -lsR ./charts + + - 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: Generate ${{ env.BENCH }} + env: + BENCH: notifications_protocol + uses: benchmark-action/github-action-benchmark@v1 + with: + tool: "cargo" + name: ${{ env.BENCH }} + output-file-path: ./charts/${{ env.BENCH }}.txt + benchmark-data-dir-path: ./bench/${{ env.BENCH }} + github-token: ${{ steps.app-token.outputs.token }} + auto-push: true + + - name: Generate ${{ env.BENCH }} + env: + BENCH: request_response_protocol + uses: benchmark-action/github-action-benchmark@v1 + with: + tool: "cargo" + name: ${{ env.BENCH }} + output-file-path: ./charts/${{ env.BENCH }}.txt + benchmark-data-dir-path: ./bench/${{ env.BENCH }} + github-token: ${{ steps.app-token.outputs.token }} + auto-push: true diff --git a/.github/workflows/subsystem-benchmarks.yml b/.github/workflows/benchmarks-subsystem.yml similarity index 100% rename from .github/workflows/subsystem-benchmarks.yml rename to .github/workflows/benchmarks-subsystem.yml diff --git a/.github/workflows/build-misc.yml b/.github/workflows/build-misc.yml index a9b433a94b64ceef1c308f9d38b04e4ea381edd2..e1ef29f305d0f1e97866d84a83a960a04734b76c 100644 --- a/.github/workflows/build-misc.yml +++ b/.github/workflows/build-misc.yml @@ -20,7 +20,7 @@ jobs: uses: ./.github/workflows/reusable-preflight.yml build-runtimes-polkavm: - timeout-minutes: 20 + timeout-minutes: 60 needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER }} container: @@ -38,13 +38,37 @@ jobs: env: SUBSTRATE_RUNTIME_TARGET: riscv id: required + run: forklift cargo check -p minimal-template-runtime -p westend-runtime -p rococo-runtime -p polkadot-test-runtime + - name: Stop all workflows if failed + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} + uses: ./.github/actions/workflow-stopper + with: + app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} + app-key: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_KEY }} + + # As part of our test fixtures we build the revive-uapi crate always with the `unstable-hostfn` feature. + # To make sure that it won't break for users downstream which are not setting this feature + # It doesn't need to produce working code so we just use a similar enough RISC-V target + check-revive-stable-uapi-polkavm: + timeout-minutes: 30 + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} + container: + image: ${{ needs.preflight.outputs.IMAGE }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check Rust run: | - forklift cargo check -p minimal-template-runtime - forklift cargo check -p westend-runtime - forklift cargo check -p rococo-runtime - forklift cargo check -p polkadot-test-runtime + rustup show + rustup +nightly show + + - name: Build + id: required + run: forklift cargo +nightly check -p pallet-revive-uapi --no-default-features --target riscv64imac-unknown-none-elf -Zbuild-std=core - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} @@ -73,7 +97,7 @@ jobs: cd ./substrate/bin/utils/subkey forklift cargo build --locked --release - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} diff --git a/.github/workflows/build-publish-eth-rpc.yml b/.github/workflows/build-publish-eth-rpc.yml index 3aa1624096dfb848bd59a806946b02e70b13bf95..a98b3881a14534c8d316f2dcc8b4bae7e05ce707 100644 --- a/.github/workflows/build-publish-eth-rpc.yml +++ b/.github/workflows/build-publish-eth-rpc.yml @@ -12,7 +12,8 @@ concurrency: cancel-in-progress: true env: - IMAGE_NAME: "docker.io/paritypr/eth-rpc" + ETH_RPC_IMAGE_NAME: "docker.io/paritypr/eth-rpc" + ETH_INDEXER_IMAGE_NAME: "docker.io/paritypr/eth-indexer" jobs: set-variables: @@ -34,7 +35,7 @@ jobs: echo "set VERSION=${VERSION}" build_docker: - name: Build docker image + name: Build docker images runs-on: parity-large needs: [set-variables] env: @@ -43,17 +44,26 @@ jobs: - name: Check out the repo uses: actions/checkout@v4 - - name: Build Docker image + - name: Build eth-rpc Docker image uses: docker/build-push-action@v6 with: context: . - file: ./substrate/frame/revive/rpc/Dockerfile + file: ./substrate/frame/revive/rpc/dockerfiles/eth-rpc/Dockerfile push: false tags: | - ${{ env.IMAGE_NAME }}:${{ env.VERSION }} + ${{ env.ETH_RPC_IMAGE_NAME }}:${{ env.VERSION }} + + - name: Build eth-indexer Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile + push: false + tags: | + ${{ env.ETH_INDEXER_IMAGE_NAME }}:${{ env.VERSION }} build_push_docker: - name: Build and push docker image + name: Build and push docker images runs-on: parity-large if: github.ref == 'refs/heads/master' needs: [set-variables] @@ -69,11 +79,20 @@ jobs: username: ${{ secrets.PARITYPR_DOCKERHUB_USERNAME }} password: ${{ secrets.PARITYPR_DOCKERHUB_PASSWORD }} - - name: Build Docker image + - name: Build eth-rpc Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./substrate/frame/revive/rpc/dockerfiles/eth-rpc/Dockerfile + push: true + tags: | + ${{ env.ETH_RPC_IMAGE_NAME }}:${{ env.VERSION }} + + - name: Build eth-indexer Docker image uses: docker/build-push-action@v6 with: context: . - file: ./substrate/frame/revive/rpc/Dockerfile + file: ./substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile push: true tags: | - ${{ env.IMAGE_NAME }}:${{ env.VERSION }} + ${{ env.ETH_INDEXER_IMAGE_NAME }}:${{ env.VERSION }} diff --git a/.github/workflows/build-publish-images.yml b/.github/workflows/build-publish-images.yml index 874b5d37469cde33f2b92f8cec0a4d61c57b7ffb..deb3b3df5ff2f6d0c13e038cb6291e958a9c879a 100644 --- a/.github/workflows/build-publish-images.yml +++ b/.github/workflows/build-publish-images.yml @@ -53,7 +53,7 @@ jobs: - name: pack artifacts run: | mkdir -p ./artifacts - VERSION="${{ needs.preflight.outputs.SOURCE_REF_NAME }}" # will be tag or branch name + VERSION="${{ needs.preflight.outputs.SOURCE_REF_SLUG }}" # will be tag or branch name mv ./target/testnet/polkadot ./artifacts/. mv ./target/testnet/polkadot-prepare-worker ./artifacts/. mv ./target/testnet/polkadot-execute-worker ./artifacts/. @@ -62,7 +62,7 @@ jobs: sha256sum polkadot | tee polkadot.sha256 shasum -c polkadot.sha256 cd ../ - EXTRATAG="${{ needs.preflight.outputs.SOURCE_REF_NAME }}-${COMMIT_SHA}" + EXTRATAG="${{ needs.preflight.outputs.SOURCE_REF_SLUG }}-${COMMIT_SHA}" echo "Polkadot version = ${VERSION} (EXTRATAG = ${EXTRATAG})" echo -n ${VERSION} > ./artifacts/VERSION echo -n ${EXTRATAG} > ./artifacts/EXTRATAG @@ -77,7 +77,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} path: artifacts.tar retention-days: 1 @@ -103,7 +103,7 @@ jobs: mkdir -p ./artifacts mv ./target/release/polkadot-parachain ./artifacts/. echo "___The VERSION is either a tag name or the curent branch if triggered not by a tag___" - echo ${{ needs.preflight.outputs.SOURCE_REF_NAME }} | tee ./artifacts/VERSION + echo ${{ needs.preflight.outputs.SOURCE_REF_SLUG }} | tee ./artifacts/VERSION - name: tar run: tar -cvf artifacts.tar artifacts @@ -111,7 +111,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} path: artifacts.tar retention-days: 1 @@ -147,7 +147,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} path: artifacts.tar retention-days: 1 @@ -172,8 +172,8 @@ jobs: mkdir -p ./artifacts mv ./target/testnet/adder-collator ./artifacts/. mv ./target/testnet/undying-collator ./artifacts/. - echo -n "${{ needs.preflight.outputs.SOURCE_REF_NAME }}" > ./artifacts/VERSION - echo -n "${{ needs.preflight.outputs.SOURCE_REF_NAME }}-${COMMIT_SHA}" > ./artifacts/EXTRATAG + echo -n "${{ needs.preflight.outputs.SOURCE_REF_SLUG }}" > ./artifacts/VERSION + echo -n "${{ needs.preflight.outputs.SOURCE_REF_SLUG }}-${COMMIT_SHA}" > ./artifacts/EXTRATAG echo "adder-collator version = $(cat ./artifacts/VERSION) (EXTRATAG = $(cat ./artifacts/EXTRATAG))" echo "undying-collator version = $(cat ./artifacts/VERSION) (EXTRATAG = $(cat ./artifacts/EXTRATAG))" cp -r ./docker/* ./artifacts @@ -184,7 +184,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} path: artifacts.tar retention-days: 1 @@ -209,8 +209,8 @@ jobs: mv ./target/testnet/malus ./artifacts/. mv ./target/testnet/polkadot-execute-worker ./artifacts/. mv ./target/testnet/polkadot-prepare-worker ./artifacts/. - echo -n "${{ needs.preflight.outputs.SOURCE_REF_NAME }}" > ./artifacts/VERSION - echo -n "${{ needs.preflight.outputs.SOURCE_REF_NAME }}-${COMMIT_SHA}" > ./artifacts/EXTRATAG + echo -n "${{ needs.preflight.outputs.SOURCE_REF_SLUG }}" > ./artifacts/VERSION + echo -n "${{ needs.preflight.outputs.SOURCE_REF_SLUG }}-${COMMIT_SHA}" > ./artifacts/EXTRATAG echo "polkadot-test-malus = $(cat ./artifacts/VERSION) (EXTRATAG = $(cat ./artifacts/EXTRATAG))" cp -r ./docker/* ./artifacts @@ -220,7 +220,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} path: artifacts.tar retention-days: 1 @@ -246,6 +246,7 @@ jobs: WASM_BUILD_NO_COLOR=1 forklift cargo build --locked --release -p staging-node-cli ls -la target/release/ - name: pack artifacts + shell: bash run: | mv target/release/substrate-node ./artifacts/substrate/substrate echo -n "Substrate version = " @@ -264,7 +265,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} path: artifacts.tar retention-days: 1 @@ -294,7 +295,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} path: artifacts.tar retention-days: 1 @@ -313,7 +314,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-test-parachain-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: build-test-parachain-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: tar -xvf artifacts.tar @@ -337,7 +338,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-linux-stable-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: build-linux-stable-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: tar -xvf artifacts.tar @@ -361,7 +362,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-test-collators-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: build-test-collators-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: tar -xvf artifacts.tar @@ -385,7 +386,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-malus-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: build-malus-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: tar -xvf artifacts.tar @@ -409,7 +410,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-linux-substrate-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: build-linux-substrate-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: tar -xvf artifacts.tar @@ -441,7 +442,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-linux-stable-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: build-linux-stable-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: | tar -xvf artifacts.tar @@ -449,7 +450,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-linux-stable-cumulus-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: build-linux-stable-cumulus-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: | tar -xvf artifacts.tar @@ -457,7 +458,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: prepare-bridges-zombienet-artifacts-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: prepare-bridges-zombienet-artifacts-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: | tar -xvf artifacts.tar @@ -482,7 +483,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-linux-stable-cumulus-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: build-linux-stable-cumulus-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: tar -xvf artifacts.tar diff --git a/.github/workflows/check-frame-omni-bencher.yml b/.github/workflows/check-frame-omni-bencher.yml index b47c9d49feafe5f7608ea431d8d16d126326e61a..bc0ff82b677414d82b9e5724f3fa0b46612436dc 100644 --- a/.github/workflows/check-frame-omni-bencher.yml +++ b/.github/workflows/check-frame-omni-bencher.yml @@ -41,7 +41,7 @@ jobs: forklift cargo build --locked --quiet --release -p asset-hub-westend-runtime --features runtime-benchmarks forklift cargo run --locked --release -p frame-omni-bencher --quiet -- v1 benchmark pallet --runtime target/release/wbuild/asset-hub-westend-runtime/asset_hub_westend_runtime.compact.compressed.wasm --all --steps 2 --repeat 1 --quiet - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} @@ -99,7 +99,7 @@ jobs: echo "Running command: $cmd" eval "$cmd" - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index dd9d3eaf824fc9550d2851622a9705843ac8d04f..cea6b9a8636a6f1f3492942a5e8b7c18c4b10a64 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -33,7 +33,7 @@ jobs: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.0 (22. Sep 2023) - name: Lychee link checker - uses: lycheeverse/lychee-action@7cd0af4c74a61395d455af97419279d86aafaede # for v1.9.1 (10. Jan 2024) + uses: lycheeverse/lychee-action@f81112d0d2814ded911bd23e3beaa9dda9093915 # for v1.9.1 (10. Jan 2024) with: args: >- --config .config/lychee.toml diff --git a/.github/workflows/check-runtime-migration.yml b/.github/workflows/check-runtime-migration.yml index 9866ae18b98ac7dac962ec823873988c6e1cffad..e935f1cb44981e07f06cfeb74811fec29e9f41a3 100644 --- a/.github/workflows/check-runtime-migration.yml +++ b/.github/workflows/check-runtime-migration.yml @@ -16,6 +16,8 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true +permissions: {} + jobs: preflight: uses: ./.github/workflows/reusable-preflight.yml diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml index 78602410cdf6570b1cd9b58eb08fa392a0f52c41..df1a1c8be6038c3cbd996d1fa0dceb46aeb87b07 100644 --- a/.github/workflows/check-semver.yml +++ b/.github/workflows/check-semver.yml @@ -2,7 +2,7 @@ name: Check semver on: pull_request: - types: [opened, synchronize, reopened, ready_for_review] + types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] workflow_dispatch: merge_group: @@ -11,7 +11,7 @@ concurrency: cancel-in-progress: true env: - TOOLCHAIN: nightly-2024-06-01 + TOOLCHAIN: nightly-2024-11-19 jobs: preflight: @@ -62,22 +62,36 @@ jobs: echo "PRDOC_EXTRA_ARGS=--max-bump minor" >> $GITHUB_ENV + - name: Echo Skip + if: ${{ contains(github.event.pull_request.labels.*.name, 'R0-silent') }} + run: echo "Skipping this PR because it is labeled as R0-silent." + - name: Rust Cache + if: ${{ !contains(github.event.pull_request.labels.*.name, 'R0-silent') }} uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 with: cache-on-failure: true - name: Rust compilation prerequisites + if: ${{ !contains(github.event.pull_request.labels.*.name, 'R0-silent') }} run: | rustup default $TOOLCHAIN + rustup target add wasm32-unknown-unknown --toolchain $TOOLCHAIN rustup component add rust-src --toolchain $TOOLCHAIN - name: install parity-publish + if: ${{ !contains(github.event.pull_request.labels.*.name, 'R0-silent') }} # Set the target dir to cache the build. - run: CARGO_TARGET_DIR=./target/ cargo install parity-publish@0.8.0 --locked -q + run: CARGO_TARGET_DIR=./target/ cargo install parity-publish@0.10.4 --locked -q - name: check semver + if: ${{ !contains(github.event.pull_request.labels.*.name, 'R0-silent') }} run: | + if [ -z "$PR" ]; then + echo "Skipping master/merge queue" + exit 0 + fi + export CARGO_TARGET_DIR=target export RUSTFLAGS='-A warnings -A missing_docs' export SKIP_WASM_BUILD=1 diff --git a/.github/workflows/checks-quick.yml b/.github/workflows/checks-quick.yml index 4fcaf80c83fc569a525c220b7c68f60212914752..1a8813833deff8cccb412d832a38de470e0dc933 100644 --- a/.github/workflows/checks-quick.yml +++ b/.github/workflows/checks-quick.yml @@ -30,7 +30,7 @@ jobs: id: required run: cargo +nightly fmt --all -- --check - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} @@ -97,7 +97,6 @@ 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 . @@ -139,7 +138,7 @@ jobs: # Fixes "detected dubious ownership" error in the ci git config --global --add safe.directory '*' python3 scripts/generate-umbrella.py --sdk . --version 0.1.0 - cargo +nightly fmt --all + cargo +nightly fmt -p polkadot-sdk if [ -n "$(git status --porcelain)" ]; then cat <<EOF diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index c240504fa1e7f1a3d486233a961ea14ac7b2f917..02428711811f957e35de0cf27af8ba51f9c40547 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -36,7 +36,7 @@ jobs: cargo clippy --all-targets --locked --workspace --quiet cargo clippy --all-targets --all-features --locked --workspace --quiet - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} @@ -62,7 +62,7 @@ jobs: # experimental code may rely on try-runtime and vice-versa forklift cargo check --locked --all --features try-runtime,experimental --quiet - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} @@ -91,7 +91,7 @@ jobs: ./check-features-variants.sh cd - - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index 525ab0c0fc2300c2b74f3ef16756853446275650..44a9a9f0611934d9ed59ba12cb8a98f571859c49 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -5,7 +5,7 @@ on: types: [created] permissions: # allow the action to comment on the PR - contents: write + contents: read issues: write pull-requests: write actions: read @@ -19,10 +19,10 @@ jobs: steps: - name: Generate token id: generate_token - uses: tibdex/github-app-token@v2.1.0 + uses: actions/create-github-app-token@v1 with: - app_id: ${{ secrets.CMD_BOT_APP_ID }} - private_key: ${{ secrets.CMD_BOT_APP_KEY }} + 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 @@ -55,38 +55,9 @@ jobs: 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' }} + if: ${{ startsWith(github.event.comment.body, '/cmd') }} runs-on: ubuntu-latest steps: - name: Add reaction to triggered comment @@ -102,12 +73,11 @@ jobs: }) 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 + if: ${{ startsWith(github.event.comment.body, '/cmd') && contains(github.event.comment.body, '--clean') }} with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -139,25 +109,72 @@ jobs: } } }) - 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' }} + + get-pr-info: + if: ${{ startsWith(github.event.comment.body, '/cmd') }} runs-on: ubuntu-latest + outputs: + CMD: ${{ steps.get-comment.outputs.group2 }} + pr-branch: ${{ steps.get-pr.outputs.pr_branch }} + repo: ${{ steps.get-pr.outputs.repo }} steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Get command uses: actions-ecosystem/action-regex-match@v2 - id: get-pr-comment + id: get-comment with: text: ${{ github.event.comment.body }} regex: "^(\\/cmd )([-\\/\\s\\w.=:]+)$" # see explanation in docs/contributor/commands-readme.md#examples + + # Get PR branch name, because the issue_comment event does not contain the PR branch name + - name: Check if the issue is a PR + id: check-pr + run: | + if [ -n "${{ github.event.issue.pull_request.url }}" ]; then + echo "This is a pull request comment" + else + echo "This is not a pull request comment" + exit 1 + fi + + - name: Get PR Branch Name and Repo + if: steps.check-pr.outcome == 'success' + id: get-pr + uses: actions/github-script@v7 + with: + script: | + const pr = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + }); + const prBranch = pr.data.head.ref; + const repo = pr.data.head.repo.full_name; + console.log(prBranch, repo) + core.setOutput('pr_branch', prBranch); + core.setOutput('repo', repo); + + - name: Use PR Branch Name and Repo + env: + PR_BRANCH: ${{ steps.get-pr.outputs.pr_branch }} + REPO: ${{ steps.get-pr.outputs.repo }} + CMD: ${{ steps.get-comment.outputs.group2 }} + run: | + echo "The PR branch is $PR_BRANCH" + echo "The repository is $REPO" + echo "The CMD is $CMD" + + help: + needs: [clean, get-pr-info] + if: ${{ startsWith(github.event.comment.body, '/cmd') && contains(github.event.comment.body, '--help') }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 - name: Save output of help id: help env: - CMD: ${{ steps.get-pr-comment.outputs.group2 }} # to avoid "" around the command + CMD: ${{ needs.get-pr-info.outputs.CMD }} # to avoid "" around the command run: | python3 -m pip install -r .github/scripts/generate-prdoc.requirements.txt echo 'help<<EOF' >> $GITHUB_OUTPUT @@ -209,9 +226,11 @@ jobs: }) 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' }} + needs: [clean, get-pr-info] + if: ${{ startsWith(github.event.comment.body, '/cmd') && !contains(github.event.comment.body, '--help') }} runs-on: ubuntu-latest + env: + CMD: ${{ needs.get-pr-info.outputs.CMD }} outputs: IMAGE: ${{ steps.set-image.outputs.IMAGE }} RUNNER: ${{ steps.set-image.outputs.RUNNER }} @@ -221,90 +240,39 @@ jobs: - id: set-image run: | - BODY=$(echo "${{ github.event.comment.body }}" | xargs) + BODY=$(echo "$CMD" | xargs) # remove whitespace 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 + IMAGE=$IMAGE_OVERRIDE + echo "IMAGE=$IMAGE" >> $GITHUB_OUTPUT fi - if [[ $BODY == "/cmd bench"* ]]; then + if [[ $BODY == "bench"* ]]; then echo "RUNNER=parity-weights" >> $GITHUB_OUTPUT - elif [[ $BODY == "/cmd update-ui"* ]]; then + elif [[ $BODY == "update-ui"* ]]; then echo "RUNNER=parity-large" >> $GITHUB_OUTPUT else echo "RUNNER=ubuntu-latest" >> $GITHUB_OUTPUT fi - - # Get PR branch name, because the issue_comment event does not contain the PR branch name - get-pr-branch: - needs: [set-image] - runs-on: ubuntu-latest - outputs: - pr-branch: ${{ steps.get-pr.outputs.pr_branch }} - repo: ${{ steps.get-pr.outputs.repo }} - steps: - - name: Check if the issue is a PR - id: check-pr - run: | - if [ -n "${{ github.event.issue.pull_request.url }}" ]; then - echo "This is a pull request comment" - else - echo "This is not a pull request comment" - exit 1 - fi - - - name: Get PR Branch Name and Repo - if: steps.check-pr.outcome == 'success' - id: get-pr - uses: actions/github-script@v7 - with: - script: | - const pr = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - }); - const prBranch = pr.data.head.ref; - const repo = pr.data.head.repo.full_name; - console.log(prBranch, repo) - core.setOutput('pr_branch', prBranch); - core.setOutput('repo', repo); - - - name: Use PR Branch Name and Repo + - name: Print outputs run: | - echo "The PR branch is ${{ steps.get-pr.outputs.pr_branch }}" - echo "The repository is ${{ steps.get-pr.outputs.repo }}" + echo "RUNNER=${{ steps.set-image.outputs.RUNNER }}" + echo "IMAGE=${{ steps.set-image.outputs.IMAGE }}" - cmd: - needs: [set-image, get-pr-branch] + before-cmd: + needs: [set-image, get-pr-info] + runs-on: ubuntu-latest env: JOB_NAME: "cmd" - runs-on: ${{ needs.set-image.outputs.RUNNER }} - timeout-minutes: 4320 # 72 hours -> 3 days; as it could take a long time to run all the runtimes/pallets - container: - image: ${{ needs.set-image.outputs.IMAGE }} + CMD: ${{ needs.get-pr-info.outputs.CMD }} + PR_BRANCH: ${{ needs.get-pr-info.outputs.pr-branch }} + outputs: + job_url: ${{ steps.build-link.outputs.job_url }} + run_url: ${{ steps.build-link.outputs.run_url }} 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 - - # In order to run prdoc without specifying the PR number, we need to add the PR number as an argument automatically - - name: Prepare PR Number argument - id: pr-arg - run: | - CMD="${{ steps.get-pr-comment.outputs.group2 }}" - if echo "$CMD" | grep -q "prdoc" && ! echo "$CMD" | grep -qE "\-\-pr[[:space:]=][0-9]+"; then - echo "arg=--pr ${{ github.event.issue.number }}" >> $GITHUB_OUTPUT - else - echo "arg=" >> $GITHUB_OUTPUT - fi - - name: Build workflow link if: ${{ !contains(github.event.comment.body, '--quiet') }} id: build-link @@ -327,45 +295,85 @@ jobs: - name: Comment PR (Start) # No need to comment on prdoc start or if --quiet - if: ${{ !contains(github.event.comment.body, '--quiet') && !contains(github.event.comment.body, 'prdoc') }} + if: ${{ !contains(github.event.comment.body, '--quiet') && !startsWith(needs.get-pr-info.outputs.CMD, 'prdoc') && !startsWith(needs.get-pr-info.outputs.CMD, 'fmt')}} uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | let job_url = ${{ steps.build-link.outputs.job_url }} - + let cmd = process.env.CMD; 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})` + body: `Command "${cmd}" has started 🚀 [See logs here](${job_url})` }) + cmd: + needs: [before-cmd, set-image, get-pr-info, is-org-member] + env: + CMD: ${{ needs.get-pr-info.outputs.CMD }} + PR_BRANCH: ${{ needs.get-pr-info.outputs.pr-branch }} + runs-on: ${{ needs.set-image.outputs.RUNNER }} + container: + image: ${{ needs.set-image.outputs.IMAGE }} + timeout-minutes: 1440 # 24 hours per runtime + # lowerdown permissions to separate permissions context for executable parts by contributors + permissions: + contents: read + pull-requests: none + actions: none + issues: none + outputs: + cmd_output: ${{ steps.cmd.outputs.cmd_output }} + subweight: ${{ steps.subweight.outputs.result }} + steps: - name: Checkout uses: actions/checkout@v4 with: - repository: ${{ needs.get-pr-branch.outputs.repo }} - ref: ${{ needs.get-pr-branch.outputs.pr-branch }} + repository: ${{ needs.get-pr-info.outputs.repo }} + ref: ${{ needs.get-pr-info.outputs.pr-branch }} - - name: Install dependencies for bench - if: startsWith(steps.get-pr-comment.outputs.group2, 'bench') + # In order to run prdoc without specifying the PR number, we need to add the PR number as an argument automatically + - name: Prepare PR Number argument + id: pr-arg run: | - cargo install subweight --locked - cargo install --path substrate/utils/frame/omni-bencher --locked + CMD="${{ needs.get-pr-info.outputs.CMD }}" + if echo "$CMD" | grep -q "prdoc" && ! echo "$CMD" | grep -qE "\-\-pr[[:space:]=][0-9]+"; then + echo "arg=--pr ${{ github.event.issue.number }}" >> $GITHUB_OUTPUT + else + echo "arg=" >> $GITHUB_OUTPUT + fi - name: Run cmd id: cmd env: - CMD: ${{ steps.get-pr-comment.outputs.group2 }} # to avoid "" around the command PR_ARG: ${{ steps.pr-arg.outputs.arg }} + IS_ORG_MEMBER: ${{ needs.is-org-member.outputs.member }} run: | echo "Running command: '$CMD $PR_ARG' 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 -m pip install -r .github/scripts/generate-prdoc.requirements.txt - python3 .github/scripts/cmd/cmd.py $CMD $PR_ARG + echo "IS_ORG_MEMBER: $IS_ORG_MEMBER" + + git config --global --add safe.directory $GITHUB_WORKSPACE + git config user.name "cmd[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + + # if the user is not an org member, we need to use the bot's path from master to avoid unwanted modifications + if [ "$IS_ORG_MEMBER" = "true" ]; then + # safe to run commands from current branch + BOT_PATH=.github + else + # going to run commands from master + TMP_DIR=/tmp/polkadot-sdk + git clone --depth 1 --branch master https://github.com/paritytech/polkadot-sdk $TMP_DIR + BOT_PATH=$TMP_DIR/.github + fi + + # install deps and run a command from master + python3 -m pip install -r $BOT_PATH/scripts/generate-prdoc.requirements.txt + python3 $BOT_PATH/scripts/cmd/cmd.py $CMD $PR_ARG git status git diff @@ -379,6 +387,11 @@ jobs: echo 'EOF' >> $GITHUB_OUTPUT fi + git add -A + git diff HEAD > /tmp/cmd/command_diff.patch -U0 + git commit -m "tmp cmd: $CMD" || true + # without push, as we're saving the diff to an artifact and subweight will compare the local branch with the remote branch + - name: Upload command output if: ${{ always() }} uses: actions/upload-artifact@v4 @@ -386,29 +399,25 @@ jobs: name: command-output path: /tmp/cmd/command_output.log - - 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" + - name: Upload command diff + uses: actions/upload-artifact@v4 + with: + name: command-diff + path: /tmp/cmd/command_diff.patch - 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 pull --rebase origin ${{ needs.get-pr-branch.outputs.pr-branch }} - - git push origin ${{ needs.get-pr-branch.outputs.pr-branch }} - else - echo "Nothing to commit"; - fi + - name: Install subweight for bench + if: startsWith(needs.get-pr-info.outputs.CMD, 'bench') + run: cargo install subweight - - name: Run Subweight + - name: Run Subweight for bench id: subweight - if: startsWith(steps.get-pr-comment.outputs.group2, 'bench') + if: startsWith(needs.get-pr-info.outputs.CMD, 'bench') shell: bash run: | git fetch + git remote -v + echo $(git log -n 2 --oneline) + result=$(subweight compare commits \ --path-pattern "./**/weights/**/*.rs,./**/weights.rs" \ --method asymptotic \ @@ -416,7 +425,7 @@ jobs: --no-color \ --change added changed \ --ignore-errors \ - refs/remotes/origin/master refs/heads/${{ needs.get-pr-branch.outputs.pr-branch }}) + refs/remotes/origin/master $PR_BRANCH) # Save the multiline result to the output { @@ -425,19 +434,86 @@ jobs: echo "EOF" } >> $GITHUB_OUTPUT + after-cmd: + needs: [cmd, get-pr-info, before-cmd] + env: + CMD: ${{ needs.get-pr-info.outputs.CMD }} + PR_BRANCH: ${{ needs.get-pr-info.outputs.pr-branch }} + runs-on: ubuntu-latest + steps: + # needs to be able to trigger CI, as default token does not retrigger + - uses: actions/create-github-app-token@v1 + id: generate_token + with: + app-id: ${{ secrets.CMD_BOT_APP_ID }} + private-key: ${{ secrets.CMD_BOT_APP_KEY }} + + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ steps.generate_token.outputs.token }} + repository: ${{ needs.get-pr-info.outputs.repo }} + ref: ${{ needs.get-pr-info.outputs.pr-branch }} + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + name: command-diff + path: command-diff + + - name: Apply & Commit changes + run: | + ls -lsa . + + git config --global --add safe.directory $GITHUB_WORKSPACE + git config user.name "cmd[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --global pull.rebase false + + echo "Applying $file" + git apply "command-diff/command_diff.patch" --unidiff-zero --allow-empty + + rm -rf command-diff + + git status + + if [ -n "$(git status --porcelain)" ]; then + + git remote -v + + push_changes() { + git push origin "HEAD:$PR_BRANCH" + } + + git add . + git restore --staged Cargo.lock # ignore changes in Cargo.lock + git commit -m "Update from ${{ github.actor }} running command '$CMD'" || true + + # Attempt to push changes + if ! push_changes; then + echo "Push failed, trying to rebase..." + git pull --rebase origin $PR_BRANCH + # After successful rebase, try pushing again + push_changes + fi + else + echo "Nothing to commit"; + fi + - name: Comment PR (End) # No need to comment on prdoc success or --quiet - if: ${{ !failure() && !contains(github.event.comment.body, '--quiet') && !contains(github.event.comment.body, 'prdoc') }} + if: ${{ needs.cmd.result == 'success' && !contains(github.event.comment.body, '--quiet') && !startsWith(needs.get-pr-info.outputs.CMD, 'prdoc') && !startsWith(needs.get-pr-info.outputs.CMD, 'fmt') }} uses: actions/github-script@v7 env: - SUBWEIGHT: "${{ steps.subweight.outputs.result }}" - CMD_OUTPUT: "${{ steps.cmd.outputs.cmd_output }}" + SUBWEIGHT: "${{ needs.cmd.outputs.subweight }}" + CMD_OUTPUT: "${{ needs.cmd.outputs.cmd_output }}" with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - let runUrl = ${{ steps.build-link.outputs.run_url }} - let subweight = process.env.SUBWEIGHT; - let cmdOutput = process.env.CMD_OUTPUT; + let runUrl = ${{ needs.before-cmd.outputs.run_url }} + let subweight = process.env.SUBWEIGHT || ''; + let cmdOutput = process.env.CMD_OUTPUT || ''; + let cmd = process.env.CMD; console.log(cmdOutput); let subweightCollapsed = subweight.trim() !== '' @@ -452,34 +528,41 @@ jobs: 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}${cmdOutputCollapsed}` + body: `Command "${cmd}" has finished ✅ [See logs here](${runUrl})${subweightCollapsed}${cmdOutputCollapsed}` }) + finish: + needs: [get-pr-info, before-cmd, after-cmd, cmd] + if: ${{ always() }} + runs-on: ubuntu-latest + env: + CMD_OUTPUT: "${{ needs.cmd.outputs.cmd_output }}" + CMD: ${{ needs.get-pr-info.outputs.CMD }} + steps: - name: Comment PR (Failure) - if: ${{ failure() && !contains(github.event.comment.body, '--quiet') }} + if: ${{ needs.cmd.result == 'failure' || needs.after-cmd.result == 'failure' }} uses: actions/github-script@v7 - env: - CMD_OUTPUT: "${{ steps.cmd.outputs.cmd_output }}" with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - let jobUrl = ${{ steps.build-link.outputs.job_url }} + let jobUrl = ${{ needs.before-cmd.outputs.job_url }} let cmdOutput = process.env.CMD_OUTPUT; - - let cmdOutputCollapsed = cmdOutput.trim() !== '' - ? `<details>\n\n<summary>Command output:</summary>\n\n${cmdOutput}\n\n</details>` - : ''; + let cmd = process.env.CMD; + let cmdOutputCollapsed = ''; + if (cmdOutput && cmdOutput.trim() !== '') { + cmdOutputCollapsed = `<details>\n\n<summary>Command output:</summary>\n\n${cmdOutput}\n\n</details>` + } 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})${cmdOutputCollapsed}` + body: `Command "${cmd}" has failed âŒ! [See logs here](${jobUrl})${cmdOutputCollapsed}` }) - name: Add 😕 reaction on failure + if: ${{ needs.cmd.result == 'failure' || needs.after-cmd.result == 'failure' }} uses: actions/github-script@v7 - if: ${{ failure() }} with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -491,8 +574,8 @@ jobs: }) - name: Add 👠reaction on success + if: ${{ needs.cmd.result == 'success' && needs.after-cmd.result == 'success' }} uses: actions/github-script@v7 - if: ${{ !failure() }} with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/command-backport.yml b/.github/workflows/command-backport.yml index 8f23bcd75f0176e73932d506b387ad00e7e78ca8..8a017a434525a7289ca4a57879b0e11d39092807 100644 --- a/.github/workflows/command-backport.yml +++ b/.github/workflows/command-backport.yml @@ -40,7 +40,7 @@ jobs: uses: korthout/backport-action@v3 id: backport with: - target_branches: stable2407 stable2409 + target_branches: stable2407 stable2409 stable2412 merge_commits: skip github_token: ${{ steps.generate_token.outputs.token }} pull_description: | @@ -86,7 +86,7 @@ jobs: const reviewer = '${{ github.event.pull_request.user.login }}'; for (const pullNumber of pullNumbers) { - await github.pulls.createReviewRequest({ + await github.pulls.requestReviewers({ owner: context.repo.owner, repo: context.repo.repo, pull_number: parseInt(pullNumber), diff --git a/.github/workflows/command-inform.yml b/.github/workflows/command-inform.yml index 97346395319362b0455bcbcfbb490fa23e6b3b07..3431eadf70608d2a7465f57718689f4acfe96952 100644 --- a/.github/workflows/command-inform.yml +++ b/.github/workflows/command-inform.yml @@ -8,7 +8,7 @@ jobs: comment: runs-on: ubuntu-latest # Temporary disable the bot until the new command bot works properly - if: github.event.issue.pull_request && startsWith(github.event.comment.body, 'bot ') && false # disabled for now, until tested + 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 @@ -18,5 +18,5 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: 'We have migrated the command bot to GHA<br/><br/>Please, see the new usage instructions <a href="https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/commands-readme.md">here</a>. Soon the old commands will be disabled.' - }) \ No newline at end of file + body: 'We have migrated the command bot to GHA<br/><br/>Please, see the new usage instructions <a href="https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/commands-readme.md">here</a> or <a href="https://forum.parity.io/t/streamlining-weight-generation-and-more-the-new-cmd-bot/2411">here</a>. Soon the old commands will be disabled.' + }) diff --git a/.github/workflows/command-prdoc.yml b/.github/workflows/command-prdoc.yml index 7022e8e0e0067cb2dee68ef4c009d59b22ab9360..71dbcfbd22893ac0cd94387e7311eae471751dbf 100644 --- a/.github/workflows/command-prdoc.yml +++ b/.github/workflows/command-prdoc.yml @@ -14,7 +14,7 @@ on: required: true options: - "TODO" - - "no_change" + - "none" - "patch" - "minor" - "major" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index cc84e7f9ad3b96f6bfa3ef976cb797e3e3824a49..b7c70c9e6d66f2dbef81c8f5aa2253b3b075820f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -29,7 +29,7 @@ jobs: env: RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} @@ -69,7 +69,7 @@ jobs: retention-days: 1 if-no-files-found: error - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} diff --git a/.github/workflows/misc-sync-templates.yml b/.github/workflows/misc-sync-templates.yml index 7ff0705fe249aa9899add3baad1b148091b432ff..71f49b62fbbead4a2a95da776e1fbd4337b20666 100644 --- a/.github/workflows/misc-sync-templates.yml +++ b/.github/workflows/misc-sync-templates.yml @@ -21,9 +21,76 @@ on: stable_release_branch: description: 'Stable release branch, e.g. stable2407' required: true + debug: + description: Enable runner debug logging + required: false + default: false + patch: + description: 'Patch number of the stable release we want to sync with' + required: false + default: "" jobs: + prepare-chain-spec-artifacts: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - template: minimal + package_name: 'minimal-template-runtime' + runtime_path: './templates/minimal/runtime' + runtime_wasm_path: minimal-template-runtime/minimal_template_runtime.compact.compressed.wasm + relay_chain: 'dev' + - template: parachain + package_name: 'parachain-template-runtime' + runtime_path: './templates/parachain/runtime' + runtime_wasm_path: parachain-template-runtime/parachain_template_runtime.compact.compressed.wasm + relay_chain: 'rococo-local' + steps: + - uses: actions/checkout@v4 + with: + ref: "${{ github.event.inputs.stable_release_branch }}" + + - name: Setup build environment + run: | + sudo apt-get update + sudo apt-get install -y protobuf-compiler + cargo install --git https://github.com/chevdor/srtool-cli --locked + cargo install --path substrate/bin/utils/chain-spec-builder --locked + srtool pull + + - name: Build runtime and generate chain spec + run: | + # Prepare directories + sudo mkdir -p ${{ matrix.runtime_path }}/target + sudo chmod -R 777 ${{ matrix.runtime_path }}/target + + # Build runtime + srtool build --package ${{ matrix.package_name }} --runtime-dir ${{ matrix.runtime_path }} --root + + # Generate chain spec + # Note that para-id is set to 1000 for both minimal/parachain templates. + # `parachain-runtime` is hardcoded to use this parachain id. + # `minimal` template isn't using it, but when started with Omni Node, this para id is required (any number can do it, so setting it to 1000 for convenience). + chain-spec-builder -c dev_chain_spec.json create \ + --relay-chain "${{ matrix.relay_chain }}" \ + --para-id 1000 \ + --runtime "${{ matrix.runtime_path }}/target/srtool/release/wbuild/${{ matrix.runtime_wasm_path }}" \ + named-preset development + + - name: Prepare upload directory + run: | + mkdir -p artifacts-${{ matrix.template }} + cp dev_chain_spec.json artifacts-${{ matrix.template }}/dev_chain_spec.json + + - name: Upload template directory + uses: actions/upload-artifact@v4 + with: + name: artifacts-${{ matrix.template }} + path: artifacts-${{ matrix.template }}/dev_chain_spec.json + sync-templates: + needs: prepare-chain-spec-artifacts runs-on: ubuntu-latest environment: master strategy: @@ -44,6 +111,12 @@ jobs: with: path: polkadot-sdk ref: "${{ github.event.inputs.stable_release_branch }}" + - name: Download template artifacts + uses: actions/download-artifact@v4 + with: + name: artifacts-${{ matrix.template }} + path: templates/${{ matrix.template }}/ + if: matrix.template != 'solochain' - name: Generate a token for the template repository id: app_token uses: actions/create-github-app-token@v1.9.3 @@ -76,6 +149,10 @@ jobs: working-directory: polkadot-sdk/templates/${{ matrix.template }}/ - name: Create a new workspace Cargo.toml run: | + # This replaces the existing Cargo.toml for parachain-template, + # corresponding to the `parachain-template-docs` crate, so no need + # to delete that `Cargo.toml` after copying the `polkadot-sdk/templates/parachain/*` + # to the `polkadot-sdk-parachain-template` repo. cat << EOF > Cargo.toml [workspace.package] license = "MIT-0" @@ -86,7 +163,7 @@ jobs: EOF [ ${{ matrix.template }} != "solochain" ] && echo "# Leave out the node compilation from regular template usage." \ - && echo "\"default-members\" = [\"pallets/template\", \"runtime\"]" >> Cargo.toml + && echo "default-members = [\"pallets/template\", \"runtime\"]" >> Cargo.toml [ ${{ matrix.template }} == "solochain" ] && echo "# The node isn't yet replaceable by Omni Node." cat << EOF >> Cargo.toml members = [ @@ -115,8 +192,9 @@ jobs: toml set templates/${{ matrix.template }}/Cargo.toml 'workspace.package.edition' "$(toml get --raw Cargo.toml 'workspace.package.edition')" > Cargo.temp mv Cargo.temp ./templates/${{ matrix.template }}/Cargo.toml working-directory: polkadot-sdk + - name: Print the result Cargo.tomls for debugging - if: runner.debug == '1' + if: ${{ github.event.inputs.debug }} run: find . -type f -name 'Cargo.toml' -exec cat {} \; working-directory: polkadot-sdk/templates/${{ matrix.template }}/ @@ -126,9 +204,21 @@ jobs: - name: Copy over the new changes run: | cp -r polkadot-sdk/templates/${{ matrix.template }}/* "${{ env.template-path }}/" + - name: Remove unnecessary files from parachain template + if: ${{ matrix.template == 'parachain' }} + run: | + rm -f "${{ env.template-path }}/README.docify.md" + rm -f "${{ env.template-path }}/src/lib.rs" - name: Run psvm on monorepo workspace dependencies - run: psvm -o -v ${{ github.event.inputs.stable_release_branch }} -p ./Cargo.toml + run: | + patch_input="${{ github.event.inputs.patch }}" + if [[ -n "$patch_input" ]]; then + patch="-$patch_input" + else + patch="" + fi + psvm -o -v "${{ github.event.inputs.stable_release_branch }}$patch" -p ./Cargo.toml working-directory: polkadot-sdk/ - name: Copy over required workspace dependencies run: | @@ -142,6 +232,12 @@ jobs: done; working-directory: "${{ env.template-path }}" + - name: Print the result Cargo.tomls for debugging after copying required workspace dependencies + if: ${{ github.event.inputs.debug }} + run: find . -type f -name 'Cargo.toml' -exec cat {} \; + working-directory: polkadot-sdk/templates/${{ matrix.template }}/ + + # 3. Verify the build. Push the changes or create a PR. # We've run into out-of-disk error when compiling in the next step, so we free up some space this way. diff --git a/.github/workflows/publish-check-compile.yml b/.github/workflows/publish-check-compile.yml new file mode 100644 index 0000000000000000000000000000000000000000..f20909106a82098d79ab8e47b03bf5b4076b9380 --- /dev/null +++ b/.github/workflows/publish-check-compile.yml @@ -0,0 +1,50 @@ +name: Check publish build + +on: + push: + branches: + - master + pull_request: + 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: + preflight: + uses: ./.github/workflows/reusable-preflight.yml + + check-publish-compile: + timeout-minutes: 90 + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} + container: + image: ${{ needs.preflight.outputs.IMAGE }} + steps: + - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 + + - name: Rust Cache + uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7 + with: + cache-on-failure: true + + - name: install parity-publish + run: | + rustup override set 1.82.0 + cargo install parity-publish@0.10.4 --locked -q + + - name: parity-publish update plan + run: parity-publish --color always plan --skip-check --prdoc prdoc/ + + - name: parity-publish apply plan + run: parity-publish --color always apply --registry + + - name: parity-publish check compile + run: | + packages="$(parity-publish apply --print)" + + if [ -n "$packages" ]; then + cargo --color always check $(printf -- '-p %s ' $packages) + fi diff --git a/.github/workflows/publish-check-crates.yml b/.github/workflows/publish-check-crates.yml index 3fad3b64147422bb842ab40985d224458f283f34..c1b13243ba193c7c141782dc263c577c5f0ec159 100644 --- a/.github/workflows/publish-check-crates.yml +++ b/.github/workflows/publish-check-crates.yml @@ -19,12 +19,12 @@ jobs: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Rust Cache - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 + uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7 with: cache-on-failure: true - name: install parity-publish - run: cargo install parity-publish@0.8.0 --locked -q + run: cargo install parity-publish@0.10.4 --locked -q - name: parity-publish check run: parity-publish --color always check --allow-unpublished diff --git a/.github/workflows/publish-claim-crates.yml b/.github/workflows/publish-claim-crates.yml index 37bf06bb82d86e82f33108ecb1da73083e09794d..804baf9ff06cfc4c32c19fbe3a70be81c8f07b46 100644 --- a/.github/workflows/publish-claim-crates.yml +++ b/.github/workflows/publish-claim-crates.yml @@ -13,12 +13,12 @@ jobs: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Rust Cache - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 + uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7 with: cache-on-failure: true - name: install parity-publish - run: cargo install parity-publish@0.8.0 --locked -q + run: cargo install parity-publish@0.10.4 --locked -q - name: parity-publish claim env: diff --git a/.github/workflows/release-branchoff-stable.yml b/.github/workflows/release-10_branchoff-stable.yml similarity index 91% rename from .github/workflows/release-branchoff-stable.yml rename to .github/workflows/release-10_branchoff-stable.yml index adce1b261b71f7f7ee6795403c4651cfab889f23..cfe135ac7299e9e0bc53eda6f2c333d58f994bdd 100644 --- a/.github/workflows/release-branchoff-stable.yml +++ b/.github/workflows/release-10_branchoff-stable.yml @@ -92,8 +92,11 @@ jobs: . ./.github/scripts/release/release_lib.sh NODE_VERSION="${{ needs.prepare-tooling.outputs.node_version }}" - set_version "\(NODE_VERSION[^=]*= \)\".*\"" $NODE_VERSION "polkadot/node/primitives/src/lib.rs" + NODE_VERSION_PATTERN="\(NODE_VERSION[^=]*= \)\".*\"" + set_version $NODE_VERSION_PATTERN $NODE_VERSION "polkadot/node/primitives/src/lib.rs" commit_with_message "Bump node version to $NODE_VERSION in polkadot-cli" + set_version $NODE_VERSION_PATTERN $NODE_VERSION "cumulus/polkadot-omni-node/lib/src/nodes/mod.rs" + commit_with_message "Bump node version to $NODE_VERSION in polkadot-omni-node-lib" SPEC_VERSION=$(get_spec_version $NODE_VERSION) runtimes_list=$(get_filtered_runtimes_list) diff --git a/.github/workflows/release-10_rc-automation.yml b/.github/workflows/release-11_rc-automation.yml similarity index 100% rename from .github/workflows/release-10_rc-automation.yml rename to .github/workflows/release-11_rc-automation.yml diff --git a/.github/workflows/release-build-rc.yml b/.github/workflows/release-20_build-rc.yml similarity index 62% rename from .github/workflows/release-build-rc.yml rename to .github/workflows/release-20_build-rc.yml index a43c2b282a8d32355ddf28c8f56d8dbaac72342d..d4c7055c37c589b34d2696a85988adf750b5fa14 100644 --- a/.github/workflows/release-build-rc.yml +++ b/.github/workflows/release-20_build-rc.yml @@ -11,10 +11,12 @@ on: - polkadot - polkadot-parachain - polkadot-omni-node + - frame-omni-bencher + - chain-spec-builder - all release_tag: - description: Tag matching the actual release candidate with the format stableYYMM-rcX or stableYYMM + description: Tag matching the actual release candidate with the format polkadot-stableYYMM(-X)-rcX or polkadot-stableYYMM(-X) type: string jobs: @@ -106,6 +108,50 @@ jobs: attestations: write contents: read + build-frame-omni-bencher-binary: + needs: [validate-inputs] + if: ${{ inputs.binary == 'frame-omni-bencher' || inputs.binary == 'all' }} + uses: "./.github/workflows/release-reusable-rc-buid.yml" + with: + binary: '["frame-omni-bencher"]' + package: "frame-omni-bencher" + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: x86_64-unknown-linux-gnu + secrets: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + permissions: + id-token: write + attestations: write + contents: read + + build-chain-spec-builder-binary: + needs: [validate-inputs] + if: ${{ inputs.binary == 'chain-spec-builder' || inputs.binary == 'all' }} + uses: "./.github/workflows/release-reusable-rc-buid.yml" + with: + binary: '["chain-spec-builder"]' + package: staging-chain-spec-builder + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: x86_64-unknown-linux-gnu + secrets: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + permissions: + id-token: write + attestations: write + contents: read + build-polkadot-macos-binary: needs: [validate-inputs] if: ${{ inputs.binary == 'polkadot' || inputs.binary == 'all' }} @@ -134,7 +180,7 @@ jobs: uses: "./.github/workflows/release-reusable-rc-buid.yml" with: binary: '["polkadot-parachain"]' - package: "polkadot-parachain-bin" + package: polkadot-parachain-bin release_tag: ${{ needs.validate-inputs.outputs.release_tag }} target: aarch64-apple-darwin secrets: @@ -156,7 +202,51 @@ jobs: uses: "./.github/workflows/release-reusable-rc-buid.yml" with: binary: '["polkadot-omni-node"]' - package: "polkadot-omni-node" + package: polkadot-omni-node + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: aarch64-apple-darwin + secrets: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + permissions: + id-token: write + attestations: write + contents: read + + build-frame-omni-bencher-macos-binary: + needs: [validate-inputs] + if: ${{ inputs.binary == 'frame-omni-bencher' || inputs.binary == 'all' }} + uses: "./.github/workflows/release-reusable-rc-buid.yml" + with: + binary: '["frame-omni-bencher"]' + package: frame-omni-bencher + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: aarch64-apple-darwin + secrets: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + permissions: + id-token: write + attestations: write + contents: read + + build-chain-spec-builder-macos-binary: + needs: [validate-inputs] + if: ${{ inputs.binary == 'chain-spec-builder' || inputs.binary == 'all' }} + uses: "./.github/workflows/release-reusable-rc-buid.yml" + with: + binary: '["chain-spec-builder"]' + package: staging-chain-spec-builder release_tag: ${{ needs.validate-inputs.outputs.release_tag }} target: aarch64-apple-darwin secrets: diff --git a/.github/workflows/release-30_publish_release_draft.yml b/.github/workflows/release-30_publish_release_draft.yml index 4364b4f80457b726f6bbea35ddd32b5cccfcb931..78ceea91f1005062d2a019f22b93f09b7f6bd35b 100644 --- a/.github/workflows/release-30_publish_release_draft.yml +++ b/.github/workflows/release-30_publish_release_draft.yml @@ -1,19 +1,46 @@ name: Release - Publish draft -on: - push: - tags: - # Catches v1.2.3 and v1.2.3-rc1 - - v[0-9]+.[0-9]+.[0-9]+* - # - polkadot-stable[0-9]+* Activate when the release process from release org is setteled +# This workflow runs in paritytech-release and creates full release draft with: +# - release notes +# - info about the runtimes +# - attached artifacts: +# - runtimes +# - binaries +# - signatures +on: workflow_dispatch: inputs: - version: - description: Current release/rc version + release_tag: + description: Tag matching the actual release candidate with the format polkadot-stableYYMM(-X)-rcX or polkadot-stableYYMM(-X) + required: true + type: string jobs: + check-synchronization: + uses: paritytech-release/sync-workflows/.github/workflows/check-syncronization.yml@main + + validate-inputs: + needs: [ check-synchronization ] + if: ${{ needs.check-synchronization.outputs.checks_passed }} == 'true' + runs-on: ubuntu-latest + outputs: + release_tag: ${{ steps.validate_inputs.outputs.release_tag }} + + steps: + - name: Checkout sources + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Validate inputs + id: validate_inputs + run: | + . ./.github/scripts/common/lib.sh + + RELEASE_TAG=$(validate_stable_tag ${{ inputs.release_tag }}) + echo "release_tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT + get-rust-versions: + needs: [ validate-inputs ] runs-on: ubuntu-latest outputs: rustc-stable: ${{ steps.get-rust-versions.outputs.stable }} @@ -24,47 +51,28 @@ jobs: echo "stable=$RUST_STABLE_VERSION" >> $GITHUB_OUTPUT build-runtimes: + needs: [ validate-inputs ] uses: "./.github/workflows/release-srtool.yml" with: excluded_runtimes: "asset-hub-rococo bridge-hub-rococo contracts-rococo coretime-rococo people-rococo rococo rococo-parachain substrate-test bp cumulus-test kitchensink minimal-template parachain-template penpal polkadot-test seedling shell frame-try sp solochain-template polkadot-sdk-docs-first" build_opts: "--features on-chain-release-build" - - build-binaries: - runs-on: ubuntu-latest - strategy: - matrix: - # Tuples of [package, binary-name] - binary: [ [frame-omni-bencher, frame-omni-bencher], [staging-chain-spec-builder, chain-spec-builder] ] - steps: - - name: Checkout sources - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.0.0 - - - name: Install protobuf-compiler - run: | - sudo apt update - sudo apt install -y protobuf-compiler - - - name: Build ${{ matrix.binary[1] }} binary - run: | - cargo build --locked --profile=production -p ${{ matrix.binary[0] }} --bin ${{ matrix.binary[1] }} - target/production/${{ matrix.binary[1] }} --version - - - name: Upload ${{ matrix.binary[1] }} binary - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 - with: - name: ${{ matrix.binary[1] }} - path: target/production/${{ matrix.binary[1] }} - + profile: production + permissions: + id-token: write + attestations: write + contents: read publish-release-draft: runs-on: ubuntu-latest - needs: [ get-rust-versions, build-runtimes ] + environment: release + needs: [ validate-inputs, get-rust-versions, build-runtimes ] outputs: release_url: ${{ steps.create-release.outputs.html_url }} asset_upload_url: ${{ steps.create-release.outputs.upload_url }} + steps: - name: Checkout - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.0.0 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Download artifacts uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 @@ -87,20 +95,21 @@ jobs: GLUTTON_WESTEND_DIGEST: ${{ github.workspace}}/glutton-westend-runtime/glutton-westend-srtool-digest.json PEOPLE_WESTEND_DIGEST: ${{ github.workspace}}/people-westend-runtime/people-westend-srtool-digest.json WESTEND_DIGEST: ${{ github.workspace}}/westend-runtime/westend-srtool-digest.json + RELEASE_TAG: ${{ needs.validate-inputs.outputs.release_tag }} shell: bash run: | . ./.github/scripts/common/lib.sh export REF1=$(get_latest_release_tag) - if [[ -z "${{ inputs.version }}" ]]; then + if [[ -z "$RELEASE_TAG" ]]; then export REF2="${{ github.ref_name }}" echo "REF2: ${REF2}" else - export REF2="${{ inputs.version }}" + export REF2="$RELEASE_TAG" echo "REF2: ${REF2}" fi echo "REL_TAG=$REF2" >> $GITHUB_ENV - export VERSION=$(echo "$REF2" | sed -E 's/.*(stable[0-9]+).*$/\1/') + export VERSION=$(echo "$REF2" | sed -E 's/.*(stable[0-9]{4}(-[0-9]+)?).*$/\1/') ./scripts/release/build-changelogs.sh @@ -112,19 +121,29 @@ jobs: scripts/release/context.json **/*-srtool-digest.json + - name: Generate content write token for the release automation + id: generate_write_token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.POLKADOT_SDK_RELEASE_RW_APP_ID }} + private-key: ${{ secrets.POLKADOT_SDK_RELEASE_RW_APP_KEY }} + owner: paritytech + repositories: polkadot-sdk + - name: Create draft release id: create-release - uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e # v1.1.4 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ env.REL_TAG }} - release_name: Polkadot ${{ env.REL_TAG }} - body_path: ${{ github.workspace}}/scripts/release/RELEASE_DRAFT.md - draft: true + GITHUB_TOKEN: ${{ steps.generate_write_token.outputs.token }} + run: | + gh release create ${{ env.REL_TAG }} \ + --repo paritytech/polkadot-sdk \ + --draft \ + --title "Polkadot ${{ env.REL_TAG }}" \ + --notes-file ${{ github.workspace}}/scripts/release/RELEASE_DRAFT.md publish-runtimes: - needs: [ build-runtimes, publish-release-draft ] + needs: [ validate-inputs, build-runtimes, publish-release-draft ] + environment: release continue-on-error: true runs-on: ubuntu-latest strategy: @@ -132,7 +151,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.0.0 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Download artifacts uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 @@ -144,44 +163,83 @@ jobs: >>$GITHUB_ENV echo ASSET=$(find ${{ matrix.chain }}-runtime -name '*.compact.compressed.wasm') >>$GITHUB_ENV echo SPEC=$(<${JSON} jq -r .runtimes.compact.subwasm.core_version.specVersion) + - name: Generate content write token for the release automation + id: generate_write_token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.POLKADOT_SDK_RELEASE_RW_APP_ID }} + private-key: ${{ secrets.POLKADOT_SDK_RELEASE_RW_APP_KEY }} + owner: paritytech + repositories: polkadot-sdk + - name: Upload compressed ${{ matrix.chain }} v${{ env.SPEC }} wasm - if: ${{ matrix.chain != 'rococo-parachain' }} - uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 #v1.0.2 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.publish-release-draft.outputs.asset_upload_url }} - asset_path: ${{ env.ASSET }} - asset_name: ${{ matrix.chain }}_runtime-v${{ env.SPEC }}.compact.compressed.wasm - asset_content_type: application/wasm + GITHUB_TOKEN: ${{ steps.generate_write_token.outputs.token }} + run: | + gh release upload ${{ needs.validate-inputs.outputs.release_tag }} \ + --repo paritytech/polkadot-sdk \ + '${{ env.ASSET }}#${{ matrix.chain }}_runtime-v${{ env.SPEC }}.compact.compressed.wasm' - publish-binaries: - needs: [ publish-release-draft, build-binaries ] + publish-release-artifacts: + needs: [ validate-inputs, publish-release-draft ] + environment: release continue-on-error: true runs-on: ubuntu-latest strategy: matrix: - binary: [frame-omni-bencher, chain-spec-builder] + binary: [ polkadot, polkadot-execute-worker, polkadot-prepare-worker, polkadot-parachain, polkadot-omni-node, frame-omni-bencher, chain-spec-builder ] + target: [ x86_64-unknown-linux-gnu, aarch64-apple-darwin ] steps: - - name: Download artifacts - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + - name: Checkout sources + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Fetch binaries from s3 based on version + run: | + . ./.github/scripts/common/lib.sh + + VERSION="${{ needs.validate-inputs.outputs.release_tag }}" + fetch_release_artifacts_from_s3 ${{ matrix.binary }} ${{ matrix.target }} + + - name: Rename aarch64-apple-darwin binaries + if: ${{ matrix.target == 'aarch64-apple-darwin' }} + working-directory: ${{ github.workspace}}/release-artifacts/${{ matrix.target }}/${{ matrix.binary }} + run: | + mv ${{ matrix.binary }} ${{ matrix.binary }}-aarch64-apple-darwin + mv ${{ matrix.binary }}.asc ${{ matrix.binary }}-aarch64-apple-darwin.asc + mv ${{ matrix.binary }}.sha256 ${{ matrix.binary }}-aarch64-apple-darwin.sha256 + + - name: Generate content write token for the release automation + id: generate_write_token + uses: actions/create-github-app-token@v1 with: - name: ${{ matrix.binary }} + app-id: ${{ vars.POLKADOT_SDK_RELEASE_RW_APP_ID }} + private-key: ${{ secrets.POLKADOT_SDK_RELEASE_RW_APP_KEY }} + owner: paritytech + repositories: polkadot-sdk - - name: Upload ${{ matrix.binary }} binary - uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 #v1.0.2 + - name: Upload ${{ matrix.binary }} binary to release draft env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.publish-release-draft.outputs.asset_upload_url }} - asset_path: ${{ github.workspace}}/${{ matrix.binary }} - asset_name: ${{ matrix.binary }} - asset_content_type: application/octet-stream + GITHUB_TOKEN: ${{ steps.generate_write_token.outputs.token }} + working-directory: ${{ github.workspace}}/release-artifacts/${{ matrix.target }}/${{ matrix.binary }} + run: | + if [[ ${{ matrix.target }} == "aarch64-apple-darwin" ]]; then + gh release upload ${{ needs.validate-inputs.outputs.release_tag }} \ + --repo paritytech/polkadot-sdk \ + ${{ matrix.binary }}-aarch64-apple-darwin \ + ${{ matrix.binary }}-aarch64-apple-darwin.asc \ + ${{ matrix.binary }}-aarch64-apple-darwin.sha256 + else + gh release upload ${{ needs.validate-inputs.outputs.release_tag }} \ + --repo paritytech/polkadot-sdk \ + ${{ matrix.binary }} \ + ${{ matrix.binary }}.asc \ + ${{ matrix.binary }}.sha256 + fi post_to_matrix: runs-on: ubuntu-latest - needs: publish-release-draft + needs: [ validate-inputs, publish-release-draft ] environment: release strategy: matrix: @@ -197,5 +255,5 @@ jobs: access_token: ${{ secrets.RELEASENOTES_MATRIX_V2_ACCESS_TOKEN }} server: m.parity.io message: | - **New version of polkadot tagged**: ${{ github.ref_name }}<br/> - Draft release created: ${{ needs.publish-release-draft.outputs.release_url }} + **New version of polkadot tagged**: ${{ needs.validate-inputs.outputs.release_tag }}<br/> + And release draft is release created in [polkadot-sdk repo](https://github.com/paritytech/polkadot-sdk/releases) diff --git a/.github/workflows/release-31_promote-rc-to-final.yml b/.github/workflows/release-31_promote-rc-to-final.yml new file mode 100644 index 0000000000000000000000000000000000000000..6aa9d4bddd1d5492cd76bd60162df6ae481bd161 --- /dev/null +++ b/.github/workflows/release-31_promote-rc-to-final.yml @@ -0,0 +1,125 @@ +name: Release - Promote RC to final candidate on S3 + +on: + workflow_dispatch: + inputs: + binary: + description: Binary to be build for the release + default: all + type: choice + options: + - polkadot + - polkadot-parachain + - polkadot-omni-node + - frame-omni-bencher + - chain-spec-builder + - all + release_tag: + description: Tag matching the actual release candidate with the format polkadot-stableYYMM(-X)-rcX + type: string + + +jobs: + + check-synchronization: + uses: paritytech-release/sync-workflows/.github/workflows/check-syncronization.yml@main + + validate-inputs: + needs: [ check-synchronization ] + if: ${{ needs.check-synchronization.outputs.checks_passed }} == 'true' + runs-on: ubuntu-latest + outputs: + release_tag: ${{ steps.validate_inputs.outputs.release_tag }} + final_tag: ${{ steps.validate_inputs.outputs.final_tag }} + + steps: + - name: Checkout sources + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Validate inputs + id: validate_inputs + run: | + . ./.github/scripts/common/lib.sh + + RELEASE_TAG=$(validate_stable_tag ${{ inputs.release_tag }}) + echo "release_tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT + + promote-polkadot-rc-to-final: + if: ${{ inputs.binary == 'polkadot' || inputs.binary == 'all' }} + needs: [ validate-inputs ] + uses: ./.github/workflows/release-reusable-promote-to-final.yml + strategy: + matrix: + target: [ x86_64-unknown-linux-gnu, aarch64-apple-darwin ] + with: + package: polkadot + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: ${{ matrix.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + promote-polkadot-parachain-rc-to-final: + if: ${{ inputs.binary == 'polkadot-parachain' || inputs.binary == 'all' }} + needs: [ validate-inputs ] + uses: ./.github/workflows/release-reusable-promote-to-final.yml + strategy: + matrix: + target: [ x86_64-unknown-linux-gnu, aarch64-apple-darwin ] + with: + package: polkadot-parachain + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: ${{ matrix.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + promote-polkadot-omni-node-rc-to-final: + if: ${{ inputs.binary == 'polkadot-omni-node' || inputs.binary == 'all' }} + needs: [ validate-inputs ] + uses: ./.github/workflows/release-reusable-promote-to-final.yml + strategy: + matrix: + target: [ x86_64-unknown-linux-gnu, aarch64-apple-darwin ] + with: + package: polkadot-omni-node + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: ${{ matrix.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + promote-frame-omni-bencher-rc-to-final: + if: ${{ inputs.binary == 'frame-omni-bencher' || inputs.binary == 'all' }} + needs: [ validate-inputs ] + uses: ./.github/workflows/release-reusable-promote-to-final.yml + strategy: + matrix: + target: [ x86_64-unknown-linux-gnu, aarch64-apple-darwin ] + with: + package: frame-omni-bencher + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: ${{ matrix.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + promote-chain-spec-builder-rc-to-final: + if: ${{ inputs.binary == 'chain-spec-builder' || inputs.binary == 'all' }} + needs: [ validate-inputs ] + uses: ./.github/workflows/release-reusable-promote-to-final.yml + strategy: + matrix: + target: [ x86_64-unknown-linux-gnu, aarch64-apple-darwin ] + with: + package: chain-spec-builder + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: ${{ matrix.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/release-40_publish-deb-package.yml b/.github/workflows/release-40_publish-deb-package.yml new file mode 100644 index 0000000000000000000000000000000000000000..3c5411ab16f0281d638ca3c1981606b422fdc8ae --- /dev/null +++ b/.github/workflows/release-40_publish-deb-package.yml @@ -0,0 +1,152 @@ +name: Release - Publish polakdot deb package + +on: + workflow_dispatch: + inputs: + tag: + description: Current final release tag in the format polakdot-stableYYMM or polkadot-stable-YYMM-X + default: polkadot-stable2412 + required: true + type: string + + distribution: + description: Distribution where to publish deb package (release, staging, stable2407, etc) + default: staging + required: true + type: string + +jobs: + check-synchronization: + uses: paritytech-release/sync-workflows/.github/workflows/check-syncronization.yml@main + + validate-inputs: + needs: [check-synchronization] + if: ${{ needs.check-synchronization.outputs.checks_passed }} == 'true' + runs-on: ubuntu-latest + outputs: + release_tag: ${{ steps.validate_inputs.outputs.release_tag }} + + steps: + - name: Checkout sources + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Validate inputs + id: validate_inputs + run: | + . ./.github/scripts/common/lib.sh + + RELEASE_TAG=$(validate_stable_tag ${{ inputs.tag }}) + echo "release_tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT + + + fetch-artifacts-from-s3: + runs-on: ubuntu-latest + needs: [validate-inputs] + env: + REPO: ${{ github.repository }} + RELEASE_TAG: ${{ needs.validate-inputs.outputs.release_tag }} + outputs: + VERSION: ${{ steps.fetch_artifacts_from_s3.outputs.VERSION }} + + steps: + - name: Checkout sources + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Fetch rc artifacts or release artifacts from s3 based on version + id: fetch_artifacts_from_s3 + run: | + . ./.github/scripts/common/lib.sh + + VERSION="$(get_polkadot_node_version_from_code)" + echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT + + fetch_debian_package_from_s3 polkadot + + - name: Upload artifacts + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: release-artifacts + path: release-artifacts/polkadot/*.deb + + publish-deb-package: + runs-on: ubuntu-latest + needs: [fetch-artifacts-from-s3] + environment: release + env: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_DEB_PATH: "s3://releases-package-repos/deb" + LOCAL_DEB_REPO_PATH: ${{ github.workspace }}/deb + VERSION: ${{ needs.fetch-artifacts-from-s3.outputs.VERSION }} + + steps: + - name: Install pgpkkms + run: | + # Install pgpkms that is used to sign built artifacts + python3 -m pip install "pgpkms @ git+https://github.com/paritytech-release/pgpkms.git@1f8555426662ac93a3849480a35449f683b1c89f" + echo "PGPKMS_REPREPRO_PATH=$(which pgpkms-reprepro)" >> $GITHUB_ENV + + - name: Install awscli + run: | + python3 -m pip install awscli + which aws + + - name: Checkout sources + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Import gpg keys + shell: bash + run: | + . ./.github/scripts/common/lib.sh + + import_gpg_keys + + - name: Download artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: release-artifacts + path: release-artifacts + + - name: Setup local deb repo + run: | + sudo apt-get install -y reprepro + which reprepro + + sed -i "s|^SignWith:.*|SignWith: ! ${PGPKMS_REPREPRO_PATH}|" ${{ github.workspace }}/.github/scripts/release/distributions + + mkdir -p ${{ github.workspace }}/deb/conf + cp ${{ github.workspace }}/.github/scripts/release/distributions ${{ github.workspace }}/deb/conf/distributions + cat ${{ github.workspace }}/deb/conf/distributions + + - name: Sync local deb repo + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + run: | + # Download the current state of the deb repo + aws s3 sync "$AWS_DEB_PATH/db" "$LOCAL_DEB_REPO_PATH/db" + aws s3 sync "$AWS_DEB_PATH/pool" "$LOCAL_DEB_REPO_PATH/pool" + aws s3 sync "$AWS_DEB_PATH/dists" "$LOCAL_DEB_REPO_PATH/dists" + + - name: Add deb package to local repo + env: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + run: | + # Add the new deb to the repo + reprepro -b "$LOCAL_DEB_REPO_PATH" includedeb "${{ inputs.distribution }}" "release-artifacts/polkadot_${VERSION}_amd64.deb" + + - name: Upload updated deb repo + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + run: | + # Upload the updated repo - dists and pool should be publicly readable + aws s3 sync "$LOCAL_DEB_REPO_PATH/pool" "$AWS_DEB_PATH/pool" --acl public-read + aws s3 sync "$LOCAL_DEB_REPO_PATH/dists" "$AWS_DEB_PATH/dists" --acl public-read + aws s3 sync "$LOCAL_DEB_REPO_PATH/db" "$AWS_DEB_PATH/db" + aws s3 sync "$LOCAL_DEB_REPO_PATH/conf" "$AWS_DEB_PATH/conf" + + # Invalidate caches to make sure latest files are served + aws cloudfront create-invalidation --distribution-id E36FKEYWDXAZYJ --paths '/deb/*' diff --git a/.github/workflows/release-50_publish-docker.yml b/.github/workflows/release-50_publish-docker.yml index 627e53bacd88ad01bef4b1b2bd49508047db166d..a3c49598d6b1619d997b103ae14a05ab15f89b8f 100644 --- a/.github/workflows/release-50_publish-docker.yml +++ b/.github/workflows/release-50_publish-docker.yml @@ -4,10 +4,6 @@ name: Release - Publish Docker Image # It builds and published releases and rc candidates. on: - #TODO: activate automated run later - # release: - # types: - # - published workflow_dispatch: inputs: image_type: @@ -30,16 +26,6 @@ on: - polkadot-parachain - chain-spec-builder - release_id: - description: | - Release ID. - You can find it using the command: - curl -s \ - -H "Authorization: Bearer ${GITHUB_TOKEN}" https://api.github.com/repos/$OWNER/$REPO/releases | \ - jq '.[] | { name: .name, id: .id }' - required: true - type: number - registry: description: Container registry required: true @@ -55,12 +41,12 @@ on: default: parity version: - description: version to build/release + description: Version of the polkadot node release in format v1.16.0 or v1.16.0-rc1 default: v0.9.18 required: true stable_tag: - description: Tag matching the actual stable release version in the format stableYYMM or stableYYMM-X for patch releases + description: Tag matching the actual stable release version in the format polkadpt-stableYYMM(-rcX) or plkadot-stableYYMM-X(-rcX) for patch releases required: true permissions: @@ -78,11 +64,15 @@ env: IMAGE_TYPE: ${{ inputs.image_type }} jobs: + check-synchronization: + uses: paritytech-release/sync-workflows/.github/workflows/check-syncronization.yml@main + validate-inputs: + needs: [check-synchronization] + if: ${{ needs.check-synchronization.outputs.checks_passed }} == 'true' 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: @@ -97,11 +87,6 @@ jobs: VERSION=$(filter_version_from_input "${{ inputs.version }}") echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT - RELEASE_ID=$(check_release_id "${{ inputs.release_id }}") - 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 @@ -114,50 +99,26 @@ jobs: - name: Checkout sources uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - #TODO: this step will be needed when automated triggering will work - #this step runs only if the workflow is triggered automatically when new release is published - # if: ${{ env.EVENT_NAME == 'release' && env.EVENT_ACTION != '' && env.EVENT_ACTION == 'published' }} - # run: | - # mkdir -p release-artifacts && cd release-artifacts - - # for f in $BINARY $BINARY.asc $BINARY.sha256; do - # URL="https://github.com/${{ github.event.repository.full_name }}/releases/download/${{ github.event.release.tag_name }}/$f" - # echo " - Fetching $f from $URL" - # wget "$URL" -O "$f" - # done - # chmod a+x $BINARY - # ls -al - - name: Fetch rc artifacts or release artifacts from s3 based on version - #this step runs only if the workflow is triggered manually - if: ${{ env.EVENT_NAME == 'workflow_dispatch' && inputs.binary != 'polkadot-omni-node' && inputs.binary != 'chain-spec-builder'}} + # if: ${{ env.EVENT_NAME == 'workflow_dispatch' && inputs.binary != 'polkadot-omni-node' && inputs.binary != 'chain-spec-builder'}} run: | . ./.github/scripts/common/lib.sh - VERSION="${{ needs.validate-inputs.outputs.VERSION }}" + VERSION="${{ needs.validate-inputs.outputs.stable_tag }}" if [[ ${{ inputs.binary }} == 'polkadot' ]]; then bins=(polkadot polkadot-prepare-worker polkadot-execute-worker) for bin in "${bins[@]}"; do - fetch_release_artifacts_from_s3 $bin + fetch_release_artifacts_from_s3 $bin x86_64-unknown-linux-gnu done else - fetch_release_artifacts_from_s3 $BINARY + fetch_release_artifacts_from_s3 $BINARY x86_64-unknown-linux-gnu fi - - name: Fetch polkadot-omni-node/chain-spec-builder rc artifacts or release artifacts based on release id - #this step runs only if the workflow is triggered manually and only for chain-spec-builder - if: ${{ env.EVENT_NAME == 'workflow_dispatch' && (inputs.binary == 'polkadot-omni-node' || inputs.binary == 'chain-spec-builder') }} - run: | - . ./.github/scripts/common/lib.sh - - RELEASE_ID="${{ needs.validate-inputs.outputs.RELEASE_ID }}" - fetch_release_artifacts - - name: Upload artifacts uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: release-artifacts - path: release-artifacts/${{ env.BINARY }}/**/* + path: release-artifacts/x86_64-unknown-linux-gnu/${{ env.BINARY }}/**/* build-container: # this job will be triggered for the polkadot-parachain rc and release or polkadot rc image build if: ${{ inputs.binary == 'polkadot-omni-node' || inputs.binary == 'polkadot-parachain' || inputs.binary == 'chain-spec-builder' || inputs.image_type == 'rc' }} @@ -173,7 +134,7 @@ jobs: uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - name: Check sha256 ${{ env.BINARY }} - if: ${{ inputs.binary == 'polkadot-parachain' || inputs.binary == 'polkadot' }} + # if: ${{ inputs.binary == 'polkadot-parachain' || inputs.binary == 'polkadot' }} working-directory: release-artifacts run: | . ../.github/scripts/common/lib.sh @@ -182,7 +143,7 @@ jobs: check_sha256 $BINARY && echo "OK" || echo "ERR" - name: Check GPG ${{ env.BINARY }} - if: ${{ inputs.binary == 'polkadot-parachain' || inputs.binary == 'polkadot' }} + # if: ${{ inputs.binary == 'polkadot-parachain' || inputs.binary == 'polkadot' }} working-directory: release-artifacts run: | . ../.github/scripts/common/lib.sh @@ -190,35 +151,29 @@ jobs: check_gpg $BINARY - name: Fetch rc commit and tag + working-directory: release-artifacts if: ${{ env.IMAGE_TYPE == 'rc' }} id: fetch_rc_refs + shell: bash run: | - . ./.github/scripts/common/lib.sh - - echo "release=${{ needs.validate-inputs.outputs.stable_tag }}" >> $GITHUB_OUTPUT + . ../.github/scripts/common/lib.sh commit=$(git rev-parse --short HEAD) && \ echo "commit=${commit}" >> $GITHUB_OUTPUT - - echo "tag=${{ needs.validate-inputs.outputs.version }}" >> $GITHUB_OUTPUT + echo "release=$(echo ${{ needs.validate-inputs.outputs.version }})" >> $GITHUB_OUTPUT + echo "tag=$(prepare_docker_stable_tag ${{ needs.validate-inputs.outputs.stable_tag }})" >> $GITHUB_OUTPUT - name: Fetch release tags working-directory: release-artifacts if: ${{ env.IMAGE_TYPE == 'release'}} id: fetch_release_refs + shell: bash run: | - chmod a+rx $BINARY - - 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 + . ../.github/scripts/common/lib.sh echo "tag=latest" >> $GITHUB_OUTPUT - echo "release=${release}" >> $GITHUB_OUTPUT - echo "stable=${{ needs.validate-inputs.outputs.stable_tag }}" >> $GITHUB_OUTPUT + echo "release=$(echo ${{ needs.validate-inputs.outputs.version }})" >> $GITHUB_OUTPUT + echo "stable=$(prepare_docker_stable_tag ${{ needs.validate-inputs.outputs.stable_tag }})" >> $GITHUB_OUTPUT - name: Build Injected Container image for polkadot rc if: ${{ env.BINARY == 'polkadot' }} @@ -342,8 +297,10 @@ jobs: - name: Fetch values id: fetch-data run: | + . ./.github/scripts/common/lib.sh date=$(date -u '+%Y-%m-%dT%H:%M:%SZ') echo "date=$date" >> $GITHUB_OUTPUT + echo "stable=$(prepare_docker_stable_tag ${{ needs.validate-inputs.outputs.stable_tag }})" >> $GITHUB_OUTPUT - name: Build and push id: docker_build @@ -354,7 +311,7 @@ jobs: # TODO: The owner should be used below but buildx does not resolve the VARs # TODO: It would be good to get rid of this GHA that we don't really need. tags: | - parity/polkadot:${{ needs.validate-inputs.outputs.stable_tag }} + parity/polkadot:${{ steps.fetch-data.outputs.stable }} parity/polkadot:latest parity/polkadot:${{ needs.fetch-latest-debian-package-version.outputs.polkadot_container_tag }} build-args: | diff --git a/.github/workflows/release-reusable-promote-to-final.yml b/.github/workflows/release-reusable-promote-to-final.yml new file mode 100644 index 0000000000000000000000000000000000000000..ed4a80a01e82625666a9584e043d4c9da363b765 --- /dev/null +++ b/.github/workflows/release-reusable-promote-to-final.yml @@ -0,0 +1,83 @@ +name: Promote rc to final + +on: + workflow_call: + inputs: + package: + description: Package to be promoted + required: true + type: string + + release_tag: + description: Tag matching the actual release candidate with the format polkadot-stableYYMM(-X)-rcX taht will be changed to final in form of polkadot-stableYYMM(-X) + required: true + type: string + + target: + description: Target triple for which the artifacts are being uploaded (e.g aarch64-apple-darwin) + required: true + type: string + + secrets: + AWS_DEFAULT_REGION: + required: true + AWS_RELEASE_ACCESS_KEY_ID: + required: true + AWS_RELEASE_SECRET_ACCESS_KEY: + required: true + +jobs: + + promote-release-artifacts: + environment: release + runs-on: ubuntu-latest + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + + steps: + - name: Checkout sources + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Prepare final tag + id: prepare_final_tag + shell: bash + run: | + tag="$(echo ${{ inputs.release_tag }} | sed 's/-rc[0-9]*$//')" + echo $tag + echo "FINAL_TAG=${tag}" >> $GITHUB_OUTPUT + + - name: Fetch binaries from s3 based on version + run: | + . ./.github/scripts/common/lib.sh + + VERSION="${{ inputs.release_tag }}" + if [[ ${{ inputs.package }} == 'polkadot' ]]; then + packages=(polkadot polkadot-prepare-worker polkadot-execute-worker) + for package in "${packages[@]}"; do + fetch_release_artifacts_from_s3 $package ${{ inputs.target }} + done + else + fetch_release_artifacts_from_s3 ${{ inputs.package }} ${{ inputs.target }} + fi + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Upload ${{ inputs.package }} ${{ inputs.target }} artifacts to s3 + run: | + . ./.github/scripts/release/release_lib.sh + + if [[ ${{ inputs.package }} == 'polkadot' ]]; then + packages=(polkadot polkadot-prepare-worker polkadot-execute-worker) + for package in "${packages[@]}"; do + upload_s3_release $package ${{ steps.prepare_final_tag.outputs.final_tag }} ${{ inputs.target }} + done + else + upload_s3_release ${{ inputs.package }} ${{ steps.prepare_final_tag.outputs.final_tag }} ${{ inputs.target }} + fi diff --git a/.github/workflows/release-reusable-rc-buid.yml b/.github/workflows/release-reusable-rc-buid.yml index 7e31a4744b5941378ae30101e7cfb8a435b82ab8..b79f7fa617506b1f9223becff4686f55c669d079 100644 --- a/.github/workflows/release-reusable-rc-buid.yml +++ b/.github/workflows/release-reusable-rc-buid.yml @@ -104,7 +104,7 @@ jobs: ./.github/scripts/release/build-linux-release.sh ${{ matrix.binaries }} ${{ inputs.package }} - name: Generate artifact attestation - uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 + uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 with: subject-path: /artifacts/${{ matrix.binaries }}/${{ matrix.binaries }} @@ -133,7 +133,7 @@ jobs: - name: Upload ${{ matrix.binaries }} artifacts uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: - name: ${{ matrix.binaries }} + name: ${{ matrix.binaries }}_${{ inputs.target }} path: /artifacts/${{ matrix.binaries }} build-macos-rc: @@ -149,7 +149,6 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} - SKIP_WASM_BUILD: 1 steps: - name: Checkout sources uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 @@ -220,7 +219,7 @@ jobs: ./.github/scripts/release/build-macos-release.sh ${{ matrix.binaries }} ${{ inputs.package }} - name: Generate artifact attestation - uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 + uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 with: subject-path: ${{ env.ARTIFACTS_PATH }}/${{ matrix.binaries }} @@ -264,9 +263,24 @@ jobs: ref: ${{ inputs.release_tag }} fetch-depth: 0 - - name: Download artifacts + - name: Download polkadot_x86_64-unknown-linux-gnu artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: polkadot_x86_64-unknown-linux-gnu + path: target/production + merge-multiple: true + + - name: Download polkadot-execute-worker_x86_64-unknown-linux-gnu artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: polkadot-execute-worker_x86_64-unknown-linux-gnu + path: target/production + merge-multiple: true + + - name: Download polkadot-prepare-worker_x86_64-unknown-linux-gnu artifacts uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: + name: polkadot-prepare-worker_x86_64-unknown-linux-gnu path: target/production merge-multiple: true @@ -278,14 +292,14 @@ jobs: . "${GITHUB_WORKSPACE}"/.github/scripts/release/build-deb.sh ${{ inputs.package }} ${VERSION} - name: Generate artifact attestation - uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 + uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 with: subject-path: target/production/*.deb - name: Upload ${{inputs.package }} artifacts uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: - name: ${{ inputs.package }} + name: ${{ inputs.package }}_${{ inputs.target }} path: target/production overwrite: true @@ -302,7 +316,6 @@ jobs: AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} - upload-polkadot-parachain-artifacts-to-s3: if: ${{ inputs.package == 'polkadot-parachain-bin' && inputs.target == 'x86_64-unknown-linux-gnu' }} needs: [build-rc] @@ -329,6 +342,32 @@ jobs: AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + upload-frame-omni-bencher-artifacts-to-s3: + if: ${{ inputs.package == 'frame-omni-bencher' && inputs.target == 'x86_64-unknown-linux-gnu' }} + needs: [build-rc] + uses: ./.github/workflows/release-reusable-s3-upload.yml + with: + package: ${{ inputs.package }} + release_tag: ${{ inputs.release_tag }} + target: ${{ inputs.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + upload-chain-spec-builder-artifacts-to-s3: + if: ${{ inputs.package == 'staging-chain-spec-builder' && inputs.target == 'x86_64-unknown-linux-gnu' }} + needs: [build-rc] + uses: ./.github/workflows/release-reusable-s3-upload.yml + with: + package: chain-spec-builder + release_tag: ${{ inputs.release_tag }} + target: ${{ inputs.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + upload-polkadot-macos-artifacts-to-s3: if: ${{ inputs.package == 'polkadot' && inputs.target == 'aarch64-apple-darwin' }} # TODO: add and use a `build-polkadot-homebrew-package` which packs all `polkadot` binaries: @@ -395,3 +434,29 @@ jobs: AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + upload-frame-omni-bencher-macos-artifacts-to-s3: + if: ${{ inputs.package == 'frame-omni-bencher' && inputs.target == 'aarch64-apple-darwin' }} + needs: [build-macos-rc] + uses: ./.github/workflows/release-reusable-s3-upload.yml + with: + package: ${{ inputs.package }} + release_tag: ${{ inputs.release_tag }} + target: ${{ inputs.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + upload-chain-spec-builder-macos-artifacts-to-s3: + if: ${{ inputs.package == 'staging-chain-spec-builder' && inputs.target == 'aarch64-apple-darwin' }} + needs: [build-macos-rc] + uses: ./.github/workflows/release-reusable-s3-upload.yml + with: + package: chain-spec-builder + release_tag: ${{ inputs.release_tag }} + target: ${{ inputs.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/release-reusable-s3-upload.yml b/.github/workflows/release-reusable-s3-upload.yml index f85466bc8c072ae9003004e892d70365412b222a..48c7e53c6c8f91f59b32ab1fefce1f95b7079994 100644 --- a/.github/workflows/release-reusable-s3-upload.yml +++ b/.github/workflows/release-reusable-s3-upload.yml @@ -9,7 +9,7 @@ on: type: string release_tag: - description: Tag matching the actual release candidate with the format stableYYMM-rcX or stableYYMM-rcX + description: Tag matching the actual release candidate with the format polkadot-stableYYMM(-X)-rcX or polkadot-stableYYMM-rcX required: true type: string @@ -40,18 +40,10 @@ jobs: uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Download amd64 artifacts - if: ${{ inputs.target == 'x86_64-unknown-linux-gnu' }} uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: - name: ${{ inputs.package }} - path: artifacts/${{ inputs.package }} - - - name: Download arm artifacts - if: ${{ inputs.target == 'aarch64-apple-darwin' }} - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - with: - name: ${{ inputs.package }}_aarch64-apple-darwin - path: artifacts/${{ inputs.package }} + name: ${{ inputs.package }}_${{ inputs.target }} + path: release-artifacts/${{ inputs.target }}/${{ inputs.package }} - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 diff --git a/.github/workflows/release-srtool.yml b/.github/workflows/release-srtool.yml index 9a29b46d2fc3290495226ec09d3320ed49a07f53..fc10496d481b9141baa69d20e0a673652ae7a293 100644 --- a/.github/workflows/release-srtool.yml +++ b/.github/workflows/release-srtool.yml @@ -1,7 +1,7 @@ name: Srtool build env: - SUBWASM_VERSION: 0.20.0 + SUBWASM_VERSION: 0.21.0 TOML_CLI_VERSION: 0.2.4 on: @@ -11,14 +11,16 @@ on: type: string build_opts: type: string + profile: + type: string outputs: published_runtimes: value: ${{ jobs.find-runtimes.outputs.runtime }} - schedule: - - cron: "00 02 * * 1" # 2AM weekly on monday - - workflow_dispatch: +permissions: + id-token: write + attestations: write + contents: read jobs: find-runtimes: @@ -75,6 +77,7 @@ jobs: with: chain: ${{ matrix.chain }} runtime_dir: ${{ matrix.runtime_dir }} + profile: ${{ inputs.profile }} - name: Summary run: | @@ -83,6 +86,11 @@ jobs: echo "Compact Runtime: ${{ steps.srtool_build.outputs.wasm }}" echo "Compressed Runtime: ${{ steps.srtool_build.outputs.wasm_compressed }}" + - name: Generate artifact attestation + uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 + with: + subject-path: ${{ steps.srtool_build.outputs.wasm }} + # We now get extra information thanks to subwasm - name: Install subwasm run: | diff --git a/.github/workflows/reusable-preflight.yml b/.github/workflows/reusable-preflight.yml index e1799adddcaf6435601894876c25034625827577..8487ab107d7c2ff5b080b39a209e04cc718ba1f9 100644 --- a/.github/workflows/reusable-preflight.yml +++ b/.github/workflows/reusable-preflight.yml @@ -203,6 +203,7 @@ jobs: echo $( substrate-contracts-node --version | awk 'NF' ) estuary --version cargo-contract --version + taplo --version - name: Info forklift run: forklift version diff --git a/.github/workflows/review-bot.yml b/.github/workflows/review-bot.yml index 3dd5b1114813dbb2e151319293ade7ce44f4aae9..27c6162a0fc20cddbde29324a9626f3f1e8b7973 100644 --- a/.github/workflows/review-bot.yml +++ b/.github/workflows/review-bot.yml @@ -29,7 +29,7 @@ jobs: with: artifact-name: pr_number - name: "Evaluates PR reviews and assigns reviewers" - uses: paritytech/review-bot@v2.6.0 + uses: paritytech/review-bot@v2.7.0 with: repo-token: ${{ steps.app_token.outputs.token }} team-token: ${{ steps.app_token.outputs.token }} diff --git a/.github/workflows/runtimes-matrix.json b/.github/workflows/runtimes-matrix.json index f991db55b86db45e07ec436b28896ebb38158601..747b2bb4ac8fb7c4e083b6f1c7915f4cb8399895 100644 --- a/.github/workflows/runtimes-matrix.json +++ b/.github/workflows/runtimes-matrix.json @@ -27,9 +27,9 @@ "path": "polkadot/runtime/rococo", "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", - "uri": "wss://try-runtime-rococo.polkadot.io:443", "bench_features": "runtime-benchmarks", "bench_flags": "", + "uri": "wss://try-runtime-rococo.polkadot.io:443", "is_relay": true }, { @@ -84,7 +84,8 @@ "template": "cumulus/templates/xcm-bench-template.hbs", "bench_features": "runtime-benchmarks", "bench_flags": "", - "uri": "wss://westend-collectives-rpc.polkadot.io:443" + "uri": "wss://westend-collectives-rpc.polkadot.io:443", + "is_relay": false }, { "name": "contracts-rococo", @@ -122,7 +123,7 @@ { "name": "glutton-westend", "package": "glutton-westend-runtime", - "path": "cumulus/parachains/runtimes/gluttons/glutton-westend", + "path": "cumulus/parachains/runtimes/glutton/glutton-westend", "header": "cumulus/file_header.txt", "template": "cumulus/templates/xcm-bench-template.hbs", "bench_features": "runtime-benchmarks", diff --git a/.github/workflows/tests-linux-stable-coverage.yml b/.github/workflows/tests-linux-stable-coverage.yml index c5af6bcae77faf03283f9fa262147328b4c9baa7..61e01cda442840c244b7c374d16826db2d50af1d 100644 --- a/.github/workflows/tests-linux-stable-coverage.yml +++ b/.github/workflows/tests-linux-stable-coverage.yml @@ -102,7 +102,7 @@ jobs: merge-multiple: true - run: ls -al reports/ - name: Upload to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true diff --git a/.github/workflows/tests-linux-stable.yml b/.github/workflows/tests-linux-stable.yml index b9d0605b2495214ff671884a3bcb1cab94afaf1e..3f8dc4fe1240a078aef7ca498bc1e8667f13dda2 100644 --- a/.github/workflows/tests-linux-stable.yml +++ b/.github/workflows/tests-linux-stable.yml @@ -37,7 +37,7 @@ jobs: id: required run: WASM_BUILD_NO_COLOR=1 forklift cargo test -p staging-node-cli --release --locked -- --ignored - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} @@ -63,7 +63,7 @@ jobs: id: required run: forklift cargo nextest run --workspace --features runtime-benchmarks benchmark --locked --cargo-profile testnet --cargo-quiet - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} @@ -113,7 +113,7 @@ jobs: if: ${{ matrix.partition == '1/3' }} run: forklift cargo nextest run -p sp-api-test --features enable-staging-api --cargo-quiet - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} @@ -155,7 +155,7 @@ jobs: --filter-expr " !test(/all_security_features_work/) - test(/nonexistent_cache_dir/)" \ --partition count:${{ matrix.partition }} \ - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index cca32650b106056527297d594cd118ceeece74ca..decd88f2e84cfb5cf7519046f0018e6119c03315 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -165,12 +165,14 @@ jobs: - name: Download artifact (master run) uses: actions/download-artifact@v4.1.8 + continue-on-error: true with: name: cargo-check-benches-master-${{ github.sha }} path: ./artifacts/master - name: Download artifact (current run) uses: actions/download-artifact@v4.1.8 + continue-on-error: true with: name: cargo-check-benches-current-${{ github.sha }} path: ./artifacts/current @@ -183,6 +185,12 @@ jobs: exit 0 fi + # fail if no artifacts + if [ ! -d ./artifacts/master ] || [ ! -d ./artifacts/current ]; then + echo "No artifacts found" + exit 1 + fi + docker run --rm \ -v $PWD/artifacts/master:/artifacts/master \ -v $PWD/artifacts/current:/artifacts/current \ diff --git a/.github/workflows/zombienet-reusable-preflight.yml b/.github/workflows/zombienet-reusable-preflight.yml new file mode 100644 index 0000000000000000000000000000000000000000..8e938567d8118ad6d68eb91108281fd20e72b26f --- /dev/null +++ b/.github/workflows/zombienet-reusable-preflight.yml @@ -0,0 +1,145 @@ +# Reusable workflow to set various useful variables +# and to perform checks and generate conditions for other workflows. +# Currently it checks if any Rust (build-related) file is changed +# and if the current (caller) workflow file is changed. +# Example: +# +# jobs: +# preflight: +# uses: ./.github/workflows/reusable-preflight.yml +# some-job: +# needs: changes +# if: ${{ needs.preflight.outputs.changes_rust }} +# ....... + +name: Zombienet Preflight + +on: + workflow_call: + # Map the workflow outputs to job outputs + outputs: + changes_substrate: + value: ${{ jobs.preflight.outputs.changes_substrate }} + + ZOMBIENET_IMAGE: + value: ${{ jobs.preflight.outputs.ZOMBIENET_IMAGE }} + description: "ZOMBIENET CI image" + + ZOMBIENET_RUNNER: + value: ${{ jobs.preflight.outputs.ZOMBIENET_RUNNER }} + description: | + Main runner for zombienet tests. + + DOCKER_IMAGES_VERSION: + value: ${{ jobs.preflight.outputs.DOCKER_IMAGES_VERSION }} + description: | + Version for temp docker images. + + # Global vars (from global preflight) + SOURCE_REF_SLUG: + value: ${{ jobs.global_preflight.outputs.SOURCE_REF_SLUG }} + + # Zombie vars + PUSHGATEWAY_URL: + value: ${{ jobs.preflight.outputs.PUSHGATEWAY_URL }} + description: "Gateway (url) to push metrics related to test." + DEBUG: + value: ${{ jobs.preflight.outputs.DEBUG }} + description: "Debug value to zombienet v1 tests." + ZOMBIE_PROVIDER: + value: ${{ jobs.preflight.outputs.ZOMBIE_PROVIDER }} + description: "Provider to use in zombienet-sdk tests." + RUST_LOG: + value: ${{ jobs.preflight.outputs.RUST_LOG }} + description: "Log value to use in zombinet-sdk tests." + RUN_IN_CI: + value: ${{ jobs.preflight.outputs.RUN_IN_CI }} + description: "Internal flag to make zombienet aware of the env." + + KUBERNETES_CPU_REQUEST: + value: ${{ jobs.preflight.outputs.KUBERNETES_CPU_REQUEST }} + description: "Base cpu (request) for pod runner." + + KUBERNETES_MEMORY_REQUEST: + value: ${{ jobs.preflight.outputs.KUBERNETES_MEMORY_REQUEST }} + description: "Base memory (request) for pod runner." + +jobs: + global_preflight: + uses: ./.github/workflows/reusable-preflight.yml + + # + # + # + preflight: + runs-on: ubuntu-latest + outputs: + changes_substrate: ${{ steps.set_changes.outputs.substrate_any_changed || steps.set_changes.outputs.currentWorkflow_any_changed }} + + ZOMBIENET_IMAGE: ${{ steps.set_vars.outputs.ZOMBIENET_IMAGE }} + ZOMBIENET_RUNNER: ${{ steps.set_vars.outputs.ZOMBIENET_RUNNER }} + + DOCKER_IMAGES_VERSION: ${{ steps.set_images_version.outputs.ZOMBIENET_RUNNER }} + + # common vars + PUSHGATEWAY_URL: ${{ steps.set_vars.outputs.PUSHGATEWAY_URL }} + DEBUG: ${{ steps.set_vars.outputs.DEBUG }} + ZOMBIE_PROVIDER: ${{ steps.set_vars.outputs.ZOMBIE_PROVIDER }} + RUST_LOG: ${{ steps.set_vars.outputs.RUST_LOG }} + RUN_IN_CI: ${{ steps.set_vars.outputs.RUN_IN_CI }} + KUBERNETES_CPU_REQUEST: ${{ steps.set_vars.outputs.KUBERNETES_CPU_REQUEST }} + KUBERNETES_MEMORY_REQUEST: ${{ steps.set_vars.outputs.KUBERNETES_MEMORY_REQUEST }} + + steps: + + - uses: actions/checkout@v4 + + # + # Set changes + # + - name: Current file + id: current_file + shell: bash + run: | + echo "currentWorkflowFile=$(echo ${{ github.workflow_ref }} | sed -nE "s/.*(\.github\/workflows\/[a-zA-Z0-9_-]*\.y[a]?ml)@refs.*/\1/p")" >> $GITHUB_OUTPUT + echo "currentActionDir=$(echo ${{ github.action_path }} | sed -nE "s/.*(\.github\/actions\/[a-zA-Z0-9_-]*)/\1/p")" >> $GITHUB_OUTPUT + + - name: Set changes + id: set_changes + uses: tj-actions/changed-files@v45 + with: + files_yaml: | + substrate: + - 'substrate/**/*' + currentWorkflow: + - '${{ steps.current_file.outputs.currentWorkflowFile }}' + - '.github/workflows/zombienet-reusable-preflight.yml' + - '.github/zombienet-env' + + + # + # Set environment vars (including runner/image) + # + - name: Set vars + id: set_vars + shell: bash + run: cat .github/env >> $GITHUB_OUTPUT + + + # + # + # + - name: Set docker images version + id: set_images_version + shell: bash + run: | + export BRANCH_NAME=${{ github.head_ref || github.ref_name }} + export DOCKER_IMAGES_VERSION=${BRANCH_NAME/\//-} + if [[ ${{ github.event_name }} == "merge_group" ]]; then export DOCKER_IMAGES_VERSION="${GITHUB_SHA::8}"; fi + echo "DOCKER_IMAGES_VERSION=${DOCKER_IMAGES_VERSION}" >> $GITHUB_OUTPUT + + - name: log + shell: bash + run: | + echo "workflow file: ${{ steps.current_file.outputs.currentWorkflowFile }}" + echo "Modified: ${{ steps.set_changes.outputs.modified_keys }}" \ No newline at end of file diff --git a/.github/workflows/zombienet_substrate.yml b/.github/workflows/zombienet_substrate.yml new file mode 100644 index 0000000000000000000000000000000000000000..823679d67d5c0adc204a2fc15360fdab561a4c17 --- /dev/null +++ b/.github/workflows/zombienet_substrate.yml @@ -0,0 +1,45 @@ +name: Zombienet Substrate + +on: + workflow_run: + workflows: [Build and push images] + types: [completed] + merge_group: + workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + preflight: + uses: ./.github/workflows/zombienet-reusable-preflight.yml + + zombienet-substrate-0000-block-building: + needs: [preflight] + # only run if we have changes in ./substrate directory and the build workflow already finish with success status. + if: ${{ needs.preflight.outputs.changes_substrate && github.event.workflow_run.conclusion == 'success' }} + runs-on: ${{ needs.preflight.outputs.ZOMBIENET_RUNNER }} + timeout-minutes: 60 + container: + image: ${{ needs.preflight.outputs.ZOMBIENET_IMAGE }} + env: + FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1 + LOCAL_DIR: "./substrate/zombienet" + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4.1.8 + with: + name: build-linux-substrate-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + + - name: script + run: | + DEBUG=${{ needs.preflight.outputs.DEBUG }} zombie -p native ${LOCAL_DIR}/0000-block-building/block-building.zndsl + + - name: upload logs + uses: actions/upload-artifact@v4 + with: + name: zombienet-logs-scale-net + path: | + /tmp/zombie*/logs/* diff --git a/.github/zombienet-env b/.github/zombienet-env new file mode 100644 index 0000000000000000000000000000000000000000..e6da1a49c4bb38602bdc9d7a21623ed136dee61b --- /dev/null +++ b/.github/zombienet-env @@ -0,0 +1,9 @@ + ZOMBIENET_IMAGE="docker.io/paritytech/zombienet:v1.3.116" + ZOMBIE_RUNNER="zombienet-arc-runner" + PUSHGATEWAY_URL="http://zombienet-prometheus-pushgateway.managed-monitoring:9091/metrics/job/zombie-metrics" + DEBUG="zombie,zombie::network-node,zombie::kube::client::logs" + ZOMBIE_PROVIDER="k8s" + RUST_LOG="info,zombienet_orchestrator=debug" + RUN_IN_CI="1" + KUBERNETES_CPU_REQUEST="512m" + KUBERNETES_MEMORY_REQUEST="1Gi" diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f508404f1efa137eaecb70a05c093d1e0d53d750..42a7e87bda433988a7d7dbad4e455dcd3cc80471 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,7 +22,7 @@ workflow: variables: # CI_IMAGE: !reference [ .ci-unified, variables, CI_IMAGE ] - CI_IMAGE: "docker.io/paritytech/ci-unified:bullseye-1.81.0-2024-09-11-v202409111034" + CI_IMAGE: "docker.io/paritytech/ci-unified:bullseye-1.81.0-2024-11-19-v202411281558" # BUILDAH_IMAGE is defined in group variables BUILDAH_COMMAND: "buildah --storage-driver overlay2" RELENG_SCRIPTS_BRANCH: "master" diff --git a/.gitlab/pipeline/zombienet.yml b/.gitlab/pipeline/zombienet.yml index 08bfed2e24ce903cb265be034bce6b51040d193b..c48bca8af48be43c34a5976ea23d6aa8d3ccf761 100644 --- a/.gitlab/pipeline/zombienet.yml +++ b/.gitlab/pipeline/zombienet.yml @@ -1,7 +1,7 @@ .zombienet-refs: extends: .build-refs variables: - ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.116" + ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.119" PUSHGATEWAY_URL: "http://zombienet-prometheus-pushgateway.managed-monitoring:9091/metrics/job/zombie-metrics" DEBUG: "zombie,zombie::network-node,zombie::kube::client::logs" ZOMBIE_PROVIDER: "k8s" diff --git a/.gitlab/pipeline/zombienet/parachain-template.yml b/.gitlab/pipeline/zombienet/parachain-template.yml index 896ba7913be7a1352ac3d89df56a3521e6d9c913..d5c1b6558b39dcaa055578b210a06db60c25320b 100644 --- a/.gitlab/pipeline/zombienet/parachain-template.yml +++ b/.gitlab/pipeline/zombienet/parachain-template.yml @@ -43,4 +43,4 @@ zombienet-parachain-template-smoke: - ls -ltr $(pwd)/artifacts - cargo test -p template-zombienet-tests --features zombienet --tests minimal_template_block_production_test - cargo test -p template-zombienet-tests --features zombienet --tests parachain_template_block_production_test - # - cargo test -p template-zombienet-tests --features zombienet --tests solochain_template_block_production_test + - cargo test -p template-zombienet-tests --features zombienet --tests solochain_template_block_production_test diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index 3dab49a118e5bb03543a5beeafddd198cab8586a..4d8d4947daa5ca627c146779e91769e4b104dc5d 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -63,6 +63,8 @@ LOCAL_SDK_TEST: "/builds/parity/mirrors/polkadot-sdk/polkadot/zombienet-sdk-tests" FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1 RUN_IN_CONTAINER: "1" + # don't retry sdk tests + NEXTEST_RETRIES: 0 artifacts: name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" when: always @@ -158,38 +160,6 @@ zombienet-polkadot-functional-0010-validator-disabling: --local-dir="${LOCAL_DIR}/functional" --test="0010-validator-disabling.zndsl" -.zombienet-polkadot-functional-0011-async-backing-6-seconds-rate: - extends: - - .zombienet-polkadot-common - script: - - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh - --local-dir="${LOCAL_DIR}/functional" - --test="0011-async-backing-6-seconds-rate.zndsl" - -zombienet-polkadot-elastic-scaling-0001-basic-3cores-6s-blocks: - extends: - - .zombienet-polkadot-common - variables: - FORCED_INFRA_INSTANCE: "spot-iops" - before_script: - - !reference [ .zombienet-polkadot-common, before_script ] - - cp --remove-destination ${LOCAL_DIR}/assign-core.js ${LOCAL_DIR}/elastic_scaling - script: - - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh - --local-dir="${LOCAL_DIR}/elastic_scaling" - --test="0001-basic-3cores-6s-blocks.zndsl" - -zombienet-polkadot-elastic-scaling-0002-elastic-scaling-doesnt-break-parachains: - extends: - - .zombienet-polkadot-common - before_script: - - !reference [ .zombienet-polkadot-common, before_script ] - - cp --remove-destination ${LOCAL_DIR}/assign-core.js ${LOCAL_DIR}/elastic_scaling - script: - - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh - --local-dir="${LOCAL_DIR}/elastic_scaling" - --test="0002-elastic-scaling-doesnt-break-parachains.zndsl" - .zombienet-polkadot-functional-0012-spam-statement-distribution-requests: extends: - .zombienet-polkadot-common @@ -233,15 +203,18 @@ zombienet-polkadot-functional-0015-coretime-shared-core: --local-dir="${LOCAL_DIR}/functional" --test="0016-approval-voting-parallel.zndsl" -zombienet-polkadot-functional-0017-sync-backing: +zombienet-polkadot-functional-0018-shared-core-idle-parachain: extends: - .zombienet-polkadot-common + before_script: + - !reference [ .zombienet-polkadot-common, before_script ] + - cp --remove-destination ${LOCAL_DIR}/assign-core.js ${LOCAL_DIR}/functional script: - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh --local-dir="${LOCAL_DIR}/functional" - --test="0017-sync-backing.zndsl" + --test="0018-shared-core-idle-parachain.zndsl" -zombienet-polkadot-functional-0018-shared-core-idle-parachain: +zombienet-polkadot-functional-0019-coretime-collation-fetching-fairness: extends: - .zombienet-polkadot-common before_script: @@ -250,7 +223,7 @@ zombienet-polkadot-functional-0018-shared-core-idle-parachain: script: - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh --local-dir="${LOCAL_DIR}/functional" - --test="0018-shared-core-idle-parachain.zndsl" + --test="0019-coretime-collation-fetching-fairness.zndsl" zombienet-polkadot-smoke-0001-parachains-smoke-test: extends: @@ -372,6 +345,8 @@ zombienet-polkadot-malus-0001-dispute-valid: --local-dir="${LOCAL_DIR}/integrationtests" --test="0001-dispute-valid-block.zndsl" +# sdk tests + .zombienet-polkadot-coretime-revenue: extends: - .zombienet-polkadot-common @@ -386,3 +361,122 @@ zombienet-polkadot-malus-0001-dispute-valid: - unset NEXTEST_FAILURE_OUTPUT - unset NEXTEST_SUCCESS_OUTPUT - cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- smoke::coretime_revenue::coretime_revenue_test + +zombienet-polkadot-elastic-scaling-slot-based-3cores: + extends: + - .zombienet-polkadot-common + needs: + - job: build-polkadot-zombienet-tests + artifacts: true + before_script: + - !reference [ ".zombienet-polkadot-common", "before_script" ] + - export POLKADOT_IMAGE="${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + - export CUMULUS_IMAGE="docker.io/paritypr/test-parachain:${PIPELINE_IMAGE_TAG}" + - export X_INFRA_INSTANCE=spot # use spot by default + script: + # we want to use `--no-capture` in zombienet tests. + - unset NEXTEST_FAILURE_OUTPUT + - unset NEXTEST_SUCCESS_OUTPUT + - cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- elastic_scaling::slot_based_3cores::slot_based_3cores_test + +zombienet-polkadot-elastic-scaling-slot-based-12cores: + extends: + - .zombienet-polkadot-common + needs: + - job: build-polkadot-zombienet-tests + artifacts: true + before_script: + - !reference [ ".zombienet-polkadot-common", "before_script" ] + - export POLKADOT_IMAGE="${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + - export CUMULUS_IMAGE="docker.io/paritypr/test-parachain:${PIPELINE_IMAGE_TAG}" + - export X_INFRA_INSTANCE=spot # use spot by default + script: + # we want to use `--no-capture` in zombienet tests. + - unset NEXTEST_FAILURE_OUTPUT + - unset NEXTEST_SUCCESS_OUTPUT + - cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- elastic_scaling::slot_based_12cores::slot_based_12cores_test + +zombienet-polkadot-elastic-scaling-doesnt-break-parachains: + extends: + - .zombienet-polkadot-common + needs: + - job: build-polkadot-zombienet-tests + artifacts: true + before_script: + - !reference [ ".zombienet-polkadot-common", "before_script" ] + - export POLKADOT_IMAGE="${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + - export X_INFRA_INSTANCE=spot # use spot by default + variables: + KUBERNETES_CPU_REQUEST: "1" + script: + # we want to use `--no-capture` in zombienet tests. + - unset NEXTEST_FAILURE_OUTPUT + - unset NEXTEST_SUCCESS_OUTPUT + - RUST_LOG=info,zombienet_=trace cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- elastic_scaling::doesnt_break_parachains::doesnt_break_parachains_test + +zombienet-polkadot-elastic-scaling-basic-3cores: + extends: + - .zombienet-polkadot-common + needs: + - job: build-polkadot-zombienet-tests + artifacts: true + before_script: + - !reference [ ".zombienet-polkadot-common", "before_script" ] + - export POLKADOT_IMAGE="${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + - export CUMULUS_IMAGE="${COL_IMAGE}" + - export X_INFRA_INSTANCE=spot # use spot by default + script: + # we want to use `--no-capture` in zombienet tests. + - unset NEXTEST_FAILURE_OUTPUT + - unset NEXTEST_SUCCESS_OUTPUT + - cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- elastic_scaling::basic_3cores::basic_3cores_test + +zombienet-polkadot-functional-sync-backing: + extends: + - .zombienet-polkadot-common + needs: + - job: build-polkadot-zombienet-tests + artifacts: true + before_script: + - !reference [ ".zombienet-polkadot-common", "before_script" ] + - export POLKADOT_IMAGE="${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + # Hardcoded to an old polkadot-parachain image, pre async backing. + - export CUMULUS_IMAGE="docker.io/paritypr/polkadot-parachain-debug:master-99623e62" + - export X_INFRA_INSTANCE=spot # use spot by default + script: + # we want to use `--no-capture` in zombienet tests. + - unset NEXTEST_FAILURE_OUTPUT + - unset NEXTEST_SUCCESS_OUTPUT + - cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- functional::sync_backing::sync_backing_test + +zombienet-polkadot-functional-async-backing-6-seconds-rate: + extends: + - .zombienet-polkadot-common + needs: + - job: build-polkadot-zombienet-tests + artifacts: true + before_script: + - !reference [ ".zombienet-polkadot-common", "before_script" ] + - export POLKADOT_IMAGE="${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + - export X_INFRA_INSTANCE=spot # use spot by default + script: + # we want to use `--no-capture` in zombienet tests. + - unset NEXTEST_FAILURE_OUTPUT + - unset NEXTEST_SUCCESS_OUTPUT + - cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- functional::async_backing_6_seconds_rate::async_backing_6_seconds_rate_test + +zombienet-polkadot-functional-duplicate-collations: + extends: + - .zombienet-polkadot-common + needs: + - job: build-polkadot-zombienet-tests + artifacts: true + before_script: + - !reference [ ".zombienet-polkadot-common", "before_script" ] + - export POLKADOT_IMAGE="${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + - export X_INFRA_INSTANCE=spot # use spot by default + script: + # we want to use `--no-capture` in zombienet tests. + - unset NEXTEST_FAILURE_OUTPUT + - unset NEXTEST_SUCCESS_OUTPUT + - cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- functional::duplicate_collations::duplicate_collations_test diff --git a/Cargo.lock b/Cargo.lock index 330c2563d976191d9a12e1d0c725ba0088a39c2b..57c70150a20725e002140ed290b3b1987d01cf76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,48 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "alloy-core" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c618bd382f0bc2ac26a7e4bfae01c9b015ca8f21b37ca40059ae35a7e62b3dc6" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives 0.8.15", + "alloy-rlp", + "alloy-sol-types 0.8.15", +] + +[[package]] +name = "alloy-dyn-abi" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41056bde53ae10ffbbf11618efbe1e0290859e5eab0fe9ef82ebdb62f12a866f" +dependencies = [ + "alloy-json-abi", + "alloy-primitives 0.8.15", + "alloy-sol-type-parser", + "alloy-sol-types 0.8.15", + "const-hex", + "itoa", + "serde", + "serde_json", + "winnow 0.6.18", +] + +[[package]] +name = "alloy-json-abi" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c357da577dfb56998d01f574d81ad7a1958d248740a7981b205d69d65a7da404" +dependencies = [ + "alloy-primitives 0.8.15", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + [[package]] name = "alloy-primitives" version = "0.4.2" @@ -145,6 +187,34 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "alloy-primitives" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6259a506ab13e1d658796c31e6e39d2e2ee89243bcc505ddc613b35732e0a430" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more 1.0.0", + "foldhash", + "hashbrown 0.15.2", + "hex-literal", + "indexmap 2.7.0", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand", + "ruint", + "rustc-hash 2.0.0", + "serde", + "sha3 0.10.8", + "tiny-keccak", +] + [[package]] name = "alloy-rlp" version = "0.3.3" @@ -169,18 +239,88 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.87", - "syn-solidity", + "syn-solidity 0.4.2", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9d64f851d95619233f74b310f12bcf16e0cbc27ee3762b6115c14a84809280a" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error2", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf7ed1574b699f48bf17caab4e6e54c6d12bc3c006ab33d58b1e227c1c3559f" +dependencies = [ + "alloy-sol-macro-input", + "const-hex", + "heck 0.5.0", + "indexmap 2.7.0", + "proc-macro-error2", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", + "syn-solidity 0.8.15", "tiny-keccak", ] +[[package]] +name = "alloy-sol-macro-input" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c02997ccef5f34f9c099277d4145f183b422938ed5322dc57a089fe9b9ad9ee" +dependencies = [ + "const-hex", + "dunce", + "heck 0.5.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", + "syn-solidity 0.8.15", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce13ff37285b0870d0a0746992a4ae48efaf34b766ae4c2640fa15e5305f8e73" +dependencies = [ + "serde", + "winnow 0.6.18", +] + [[package]] name = "alloy-sol-types" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98d7107bed88e8f09f0ddcc3335622d87bfb6821f3e0c7473329fb1cfad5e015" dependencies = [ - "alloy-primitives", - "alloy-sol-macro", + "alloy-primitives 0.4.2", + "alloy-sol-macro 0.4.2", + "const-hex", + "serde", +] + +[[package]] +name = "alloy-sol-types" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1174cafd6c6d810711b4e00383037bdb458efc4fe3dbafafa16567e0320c54d8" +dependencies = [ + "alloy-json-abi", + "alloy-primitives 0.8.15", + "alloy-sol-macro 0.8.15", "const-hex", "serde", ] @@ -701,30 +841,14 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" -[[package]] -name = "asn1-rs" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" -dependencies = [ - "asn1-rs-derive 0.4.0", - "asn1-rs-impl 0.1.0", - "displaydoc", - "nom", - "num-traits", - "rusticata-macros", - "thiserror", - "time", -] - [[package]] name = "asn1-rs" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ad1373757efa0f70ec53939aabc7152e1591cb485208052993070ac8d2429d" dependencies = [ - "asn1-rs-derive 0.5.0", - "asn1-rs-impl 0.2.0", + "asn1-rs-derive", + "asn1-rs-impl", "displaydoc", "nom", "num-traits", @@ -733,18 +857,6 @@ dependencies = [ "time", ] -[[package]] -name = "asn1-rs-derive" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" -dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", - "syn 1.0.109", - "synstructure 0.12.6", -] - [[package]] name = "asn1-rs-derive" version = "0.5.0" @@ -757,17 +869,6 @@ dependencies = [ "synstructure 0.13.1", ] -[[package]] -name = "asn1-rs-impl" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" -dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", - "syn 1.0.109", -] - [[package]] name = "asn1-rs-impl" version = "0.2.0" @@ -809,6 +910,7 @@ dependencies = [ "cumulus-primitives-core 0.7.0", "emulated-integration-tests-common", "frame-support 28.0.0", + "pallet-asset-rewards", "parachains-common 7.0.0", "rococo-emulated-chain", "sp-core 28.0.0", @@ -827,6 +929,7 @@ dependencies = [ "emulated-integration-tests-common", "frame-support 28.0.0", "pallet-asset-conversion 10.0.0", + "pallet-asset-rewards", "pallet-assets 29.1.0", "pallet-balances 28.0.0", "pallet-message-queue 31.0.0", @@ -858,11 +961,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -877,6 +980,7 @@ dependencies = [ "pallet-asset-conversion 10.0.0", "pallet-asset-conversion-ops 0.1.0", "pallet-asset-conversion-tx-payment 10.0.0", + "pallet-asset-rewards", "pallet-assets 29.1.0", "pallet-assets-freezer 0.1.0", "pallet-aura 27.0.0", @@ -887,7 +991,6 @@ dependencies = [ "pallet-multisig 28.0.0", "pallet-nft-fractionalization 10.0.0", "pallet-nfts 22.0.0", - "pallet-nfts-runtime-api 14.0.0", "pallet-proxy 28.0.0", "pallet-session 28.0.0", "pallet-timestamp 27.0.0", @@ -899,6 +1002,7 @@ dependencies = [ "pallet-xcm-benchmarks 7.0.0", "pallet-xcm-bridge-hub-router 0.5.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", @@ -961,6 +1065,7 @@ dependencies = [ "frame-support 28.0.0", "frame-system 28.0.0", "pallet-asset-conversion 10.0.0", + "pallet-asset-rewards", "pallet-asset-tx-payment 28.0.0", "pallet-assets 29.1.0", "pallet-balances 28.0.0", @@ -993,11 +1098,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -1012,6 +1117,7 @@ dependencies = [ "pallet-asset-conversion 10.0.0", "pallet-asset-conversion-ops 0.1.0", "pallet-asset-conversion-tx-payment 10.0.0", + "pallet-asset-rewards", "pallet-assets 29.1.0", "pallet-assets-freezer 0.1.0", "pallet-aura 27.0.0", @@ -1019,6 +1125,7 @@ dependencies = [ "pallet-balances 28.0.0", "pallet-collator-selection 9.0.0", "pallet-message-queue 31.0.0", + "pallet-migrations 1.0.0", "pallet-multisig 28.0.0", "pallet-nft-fractionalization 10.0.0", "pallet-nfts 22.0.0", @@ -1036,6 +1143,7 @@ dependencies = [ "pallet-xcm-benchmarks 7.0.0", "pallet-xcm-bridge-hub-router 0.5.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", @@ -1077,6 +1185,7 @@ dependencies = [ "frame-support 28.0.0", "frame-system 28.0.0", "hex-literal", + "pallet-asset-conversion 10.0.0", "pallet-assets 29.1.0", "pallet-balances 28.0.0", "pallet-collator-selection 9.0.0", @@ -1094,6 +1203,7 @@ dependencies = [ "staging-xcm-builder 7.0.0", "staging-xcm-executor 7.0.0", "substrate-wasm-builder 17.0.0", + "xcm-runtime-apis 0.1.0", ] [[package]] @@ -1291,7 +1401,7 @@ dependencies = [ "futures-lite 2.3.0", "parking", "polling 3.4.0", - "rustix 0.38.25", + "rustix 0.38.42", "slab", "tracing", "windows-sys 0.52.0", @@ -1373,7 +1483,7 @@ dependencies = [ "cfg-if", "event-listener 5.3.1", "futures-lite 2.3.0", - "rustix 0.38.25", + "rustix 0.38.42", "tracing", ] @@ -1389,7 +1499,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.25", + "rustix 0.38.42", "signal-hook-registry", "slab", "windows-sys 0.52.0", @@ -1474,6 +1584,28 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "asynchronous-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic-take" version = "1.1.0" @@ -1762,6 +1894,9 @@ name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] [[package]] name = "bitvec" @@ -1934,6 +2069,8 @@ dependencies = [ "frame-support 28.0.0", "parity-scale-codec", "scale-info", + "sp-core 28.0.0", + "staging-xcm 7.0.0", ] [[package]] @@ -1944,6 +2081,8 @@ dependencies = [ "frame-support 28.0.0", "parity-scale-codec", "scale-info", + "sp-core 28.0.0", + "staging-xcm 7.0.0", ] [[package]] @@ -2450,6 +2589,8 @@ dependencies = [ "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", ] [[package]] @@ -2500,7 +2641,6 @@ dependencies = [ "pallet-bridge-messages 0.7.0", "pallet-message-queue 31.0.0", "pallet-xcm 7.0.0", - "pallet-xcm-bridge-hub 0.2.0", "parachains-common 7.0.0", "parity-scale-codec", "rococo-system-emulated-network", @@ -2525,7 +2665,6 @@ version = "0.5.0" dependencies = [ "bp-asset-hub-rococo", "bp-asset-hub-westend", - "bp-bridge-hub-polkadot", "bp-bridge-hub-rococo", "bp-bridge-hub-westend", "bp-header-chain 0.7.0", @@ -2537,17 +2676,18 @@ dependencies = [ "bp-rococo", "bp-runtime 0.7.0", "bp-westend", + "bp-xcm-bridge-hub-router 0.6.0", "bridge-hub-common 0.1.0", "bridge-hub-test-utils 0.7.0", "bridge-runtime-common 0.7.0", "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -2578,6 +2718,7 @@ dependencies = [ "pallet-xcm-benchmarks 7.0.0", "pallet-xcm-bridge-hub 0.2.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", @@ -2632,8 +2773,6 @@ dependencies = [ "bp-relayers 0.7.0", "bp-runtime 0.7.0", "bp-test-utils 0.7.0", - "bp-xcm-bridge-hub 0.2.0", - "bridge-runtime-common 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "frame-support 28.0.0", @@ -2656,6 +2795,7 @@ dependencies = [ "sp-io 30.0.0", "sp-keyring 31.0.0", "sp-runtime 31.0.1", + "sp-std 14.0.0", "sp-tracing 16.0.0", "staging-xcm 7.0.0", "staging-xcm-builder 7.0.0", @@ -2739,7 +2879,6 @@ dependencies = [ "pallet-bridge-messages 0.7.0", "pallet-message-queue 31.0.0", "pallet-xcm 7.0.0", - "pallet-xcm-bridge-hub 0.2.0", "parachains-common 7.0.0", "parity-scale-codec", "rococo-westend-system-emulated-network", @@ -2774,17 +2913,18 @@ dependencies = [ "bp-rococo", "bp-runtime 0.7.0", "bp-westend", + "bp-xcm-bridge-hub-router 0.6.0", "bridge-hub-common 0.1.0", "bridge-hub-test-utils 0.7.0", "bridge-runtime-common 0.7.0", "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -2815,6 +2955,7 @@ dependencies = [ "pallet-xcm-benchmarks 7.0.0", "pallet-xcm-bridge-hub 0.2.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", @@ -2868,7 +3009,6 @@ dependencies = [ "bp-relayers 0.7.0", "bp-runtime 0.7.0", "bp-test-utils 0.7.0", - "bp-xcm-bridge-hub 0.2.0", "frame-support 28.0.0", "frame-system 28.0.0", "log", @@ -2989,6 +3129,9 @@ name = "bytes" version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +dependencies = [ + "serde", +] [[package]] name = "bzip2-sys" @@ -3121,6 +3264,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chacha" version = "0.3.0" @@ -3159,6 +3308,7 @@ dependencies = [ name = "chain-spec-guide-runtime" version = "0.0.0" dependencies = [ + "cmd_lib", "docify", "frame-support 28.0.0", "pallet-balances 28.0.0", @@ -3509,11 +3659,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -3549,7 +3699,9 @@ dependencies = [ "pallet-treasury 27.0.0", "pallet-utility 28.0.0", "pallet-xcm 7.0.0", + "pallet-xcm-benchmarks 7.0.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", @@ -3733,21 +3885,11 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - [[package]] name = "const-hex" -version = "1.10.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5104de16b218eddf8e34ffe2f86f74bfa4e61e95a1b89732fccf6325efd0557" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" dependencies = [ "cfg-if", "cpufeatures", @@ -3815,11 +3957,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -3958,11 +4100,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -3991,6 +4133,7 @@ dependencies = [ "pallet-xcm 7.0.0", "pallet-xcm-benchmarks 7.0.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", @@ -4058,11 +4201,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -4090,6 +4233,7 @@ dependencies = [ "pallet-xcm 7.0.0", "pallet-xcm-benchmarks 7.0.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", @@ -4252,6 +4396,21 @@ dependencies = [ "wasmtime-types", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.3.2" @@ -4469,6 +4628,8 @@ dependencies = [ "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", "cumulus-relay-chain-interface", + "cumulus-test-client", + "cumulus-test-relay-sproof-builder 0.7.0", "futures", "parity-scale-codec", "parking_lot 0.12.3", @@ -4493,10 +4654,12 @@ dependencies = [ "sp-consensus-aura 0.32.0", "sp-core 28.0.0", "sp-inherents 26.0.0", + "sp-keyring 31.0.0", "sp-keystore 0.34.0", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", "sp-timestamp 26.0.0", + "sp-trie 29.0.0", "substrate-prometheus-endpoint", "tokio", "tracing", @@ -4589,7 +4752,6 @@ dependencies = [ "polkadot-parachain-primitives 6.0.0", "polkadot-primitives 7.0.0", "polkadot-test-client", - "portpicker", "rstest", "sc-cli", "sc-client-api", @@ -4602,7 +4764,6 @@ dependencies = [ "sp-runtime 31.0.1", "sp-state-machine 0.35.0", "sp-version 29.0.0", - "substrate-test-utils", "tokio", "tracing", "url", @@ -4646,7 +4807,6 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-overseer", "polkadot-primitives 7.0.0", - "portpicker", "rand", "rstest", "sc-cli", @@ -4660,7 +4820,6 @@ dependencies = [ "sp-runtime 31.0.1", "sp-tracing 16.0.0", "sp-version 29.0.0", - "substrate-test-utils", "tokio", "tracing", ] @@ -4680,6 +4839,7 @@ dependencies = [ "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "futures", + "futures-timer", "polkadot-primitives 7.0.0", "sc-client-api", "sc-consensus", @@ -4706,6 +4866,8 @@ name = "cumulus-pallet-aura-ext" version = "0.7.0" dependencies = [ "cumulus-pallet-parachain-system 0.7.0", + "cumulus-primitives-core 0.7.0", + "cumulus-test-relay-sproof-builder 0.7.0", "frame-support 28.0.0", "frame-system 28.0.0", "pallet-aura 27.0.0", @@ -4714,7 +4876,10 @@ dependencies = [ "scale-info", "sp-application-crypto 30.0.0", "sp-consensus-aura 0.32.0", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-runtime 31.0.1", + "sp-version 29.0.0", ] [[package]] @@ -4795,7 +4960,6 @@ dependencies = [ "pallet-message-queue 31.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", - "polkadot-runtime-common 7.0.0", "polkadot-runtime-parachains 7.0.0", "rand", "sc-client-api", @@ -4934,6 +5098,25 @@ dependencies = [ "sp-runtime 39.0.2", ] +[[package]] +name = "cumulus-pallet-weight-reclaim" +version = "1.0.0" +dependencies = [ + "cumulus-primitives-proof-size-hostfunction 0.2.0", + "cumulus-primitives-storage-weight-reclaim 1.0.0", + "derive-where", + "docify", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "log", + "parity-scale-codec", + "scale-info", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-trie 29.0.0", +] + [[package]] name = "cumulus-pallet-xcm" version = "0.7.0" @@ -5266,7 +5449,6 @@ dependencies = [ "async-trait", "cumulus-primitives-core 0.7.0", "cumulus-relay-chain-interface", - "cumulus-test-service", "futures", "futures-timer", "polkadot-cli", @@ -5384,10 +5566,10 @@ dependencies = [ name = "cumulus-test-client" version = "0.1.0" dependencies = [ + "cumulus-pallet-weight-reclaim", "cumulus-primitives-core 0.7.0", "cumulus-primitives-parachain-inherent 0.7.0", "cumulus-primitives-proof-size-hostfunction 0.2.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-test-relay-sproof-builder 0.7.0", "cumulus-test-runtime", "cumulus-test-service", @@ -5449,9 +5631,9 @@ version = "0.1.0" dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", + "cumulus-pallet-weight-reclaim", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "frame-executive 28.0.0", "frame-support 28.0.0", "frame-system 28.0.0", @@ -5459,7 +5641,6 @@ dependencies = [ "pallet-aura 27.0.0", "pallet-authorship 28.0.0", "pallet-balances 28.0.0", - "pallet-collator-selection 9.0.0", "pallet-glutton 14.0.0", "pallet-message-queue 31.0.0", "pallet-session 28.0.0", @@ -5498,13 +5679,12 @@ dependencies = [ "cumulus-client-consensus-aura", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", - "cumulus-client-consensus-relay-chain", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-pallet-parachain-system 0.7.0", + "cumulus-pallet-weight-reclaim", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", @@ -5517,7 +5697,6 @@ dependencies = [ "jsonrpsee", "pallet-timestamp 27.0.0", "pallet-transaction-payment 28.0.0", - "parachains-common 7.0.0", "parity-scale-codec", "polkadot-cli", "polkadot-node-subsystem", @@ -5525,7 +5704,6 @@ dependencies = [ "polkadot-primitives 7.0.0", "polkadot-service", "polkadot-test-service", - "portpicker", "prometheus", "rand", "sc-basic-authorship", @@ -5561,7 +5739,6 @@ dependencies = [ "sp-timestamp 26.0.0", "sp-tracing 16.0.0", "substrate-test-client", - "substrate-test-utils", "tempfile", "tokio", "tracing", @@ -5746,9 +5923,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "data-encoding-macro" @@ -5786,30 +5963,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] [[package]] name = "der-parser" -version = "8.2.0" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" dependencies = [ - "asn1-rs 0.5.2", - "displaydoc", - "nom", - "num-bigint", - "num-traits", - "rusticata-macros", -] - -[[package]] -name = "der-parser" -version = "9.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" -dependencies = [ - "asn1-rs 0.6.1", + "asn1-rs", "displaydoc", "nom", "num-bigint", @@ -5965,6 +6129,15 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-sys" version = "0.4.1" @@ -6059,7 +6232,7 @@ dependencies = [ "regex", "syn 2.0.87", "termcolor", - "toml 0.8.12", + "toml 0.8.19", "walkdir", ] @@ -6072,6 +6245,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "downcast" version = "0.11.0" @@ -6197,6 +6376,9 @@ name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] [[package]] name = "elliptic-curve" @@ -6225,7 +6407,6 @@ dependencies = [ "asset-test-utils 7.0.0", "bp-messages 0.7.0", "bp-xcm-bridge-hub 0.2.0", - "bridge-runtime-common 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-core 0.7.0", @@ -6268,18 +6449,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "enum-as-inner" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" -dependencies = [ - "heck 0.4.1", - "proc-macro2 1.0.86", - "quote 1.0.37", - "syn 1.0.109", -] - [[package]] name = "enum-as-inner" version = "0.6.0" @@ -6417,23 +6586,23 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.2" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ - "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "etcetera" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ - "cc", - "libc", + "cfg-if", + "home", + "windows-sys 0.48.0", ] [[package]] @@ -6630,9 +6799,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fastrlp" @@ -6662,7 +6831,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb42427514b063d97ce21d5199f36c0c307d981434a6be32582bc79fe5bd2303" dependencies = [ "expander", - "indexmap 2.2.3", + "indexmap 2.7.0", "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", @@ -6847,12 +7016,29 @@ dependencies = [ "num-traits", ] +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -6997,6 +7183,7 @@ dependencies = [ "sc-client-db", "sc-executor 0.32.0", "sc-executor-common 0.29.0", + "sc-runtime-utilities", "sc-service", "sc-sysinfo", "serde", @@ -7214,6 +7401,18 @@ dependencies = [ "serde", ] +[[package]] +name = "frame-metadata" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daaf440c68eb2c3d88e5760fe8c7af3f9fee9181fab6c2f2c4e7cc48dcc40bb8" +dependencies = [ + "cfg-if", + "parity-scale-codec", + "scale-info", + "serde", +] + [[package]] name = "frame-metadata-hash-extension" version = "0.1.0" @@ -7221,7 +7420,7 @@ dependencies = [ "array-bytes", "const-hex", "docify", - "frame-metadata 16.0.0", + "frame-metadata 18.0.0", "frame-support 28.0.0", "frame-system 28.0.0", "log", @@ -7306,7 +7505,7 @@ dependencies = [ "bitflags 1.3.2", "docify", "environmental", - "frame-metadata 16.0.0", + "frame-metadata 18.0.0", "frame-support-procedural 23.0.0", "frame-system 28.0.0", "impl-trait-for-tuples", @@ -7400,7 +7599,7 @@ dependencies = [ "macro_magic", "parity-scale-codec", "pretty_assertions", - "proc-macro-warning 1.0.0", + "proc-macro-warning", "proc-macro2 1.0.86", "quote 1.0.37", "regex", @@ -7427,7 +7626,7 @@ dependencies = [ "frame-support-procedural-tools 13.0.0", "itertools 0.11.0", "macro_magic", - "proc-macro-warning 1.0.0", + "proc-macro-warning", "proc-macro2 1.0.86", "quote 1.0.37", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -7484,7 +7683,7 @@ version = "3.0.0" dependencies = [ "frame-benchmarking 28.0.0", "frame-executive 28.0.0", - "frame-metadata 16.0.0", + "frame-metadata 18.0.0", "frame-support 28.0.0", "frame-support-test-pallet", "frame-system 28.0.0", @@ -7555,7 +7754,6 @@ dependencies = [ "sp-externalities 0.25.0", "sp-io 30.0.0", "sp-runtime 31.0.1", - "sp-std 14.0.0", "sp-version 29.0.0", "sp-weights 27.0.0", "substrate-test-runtime-client", @@ -7677,7 +7875,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29f9df8a11882c4e3335eb2d18a0137c505d9ca927470b0cac9c6f0ae07d28f7" dependencies = [ - "rustix 0.38.25", + "rustix 0.38.42", "windows-sys 0.48.0", ] @@ -7710,9 +7908,9 @@ dependencies = [ [[package]] name = "futures-bounded" -version = "0.1.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b07bbbe7d7e78809544c6f718d875627addc73a7c3582447abc052cd3dc67e0" +checksum = "91f328e7fb845fc832912fb6a34f40cf6d1888c92f974d1893a54e97b5ff542e" dependencies = [ "futures-timer", "futures-util", @@ -7746,6 +7944,17 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot 0.12.3", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -7773,7 +7982,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.1.0", + "fastrand 2.3.0", "futures-core", "futures-io", "parking", @@ -7793,12 +8002,13 @@ dependencies = [ [[package]] name = "futures-rustls" -version = "0.24.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd3cf68c183738046838e300353e4716c674dc5e56890de4826801a6622a28" +checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" dependencies = [ "futures-io", - "rustls 0.21.7", + "rustls 0.23.18", + "rustls-pki-types", ] [[package]] @@ -7820,7 +8030,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ "gloo-timers", - "send_wrapper 0.4.0", + "send_wrapper", ] [[package]] @@ -8099,7 +8309,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.9", - "indexmap 2.2.3", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -8118,7 +8328,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.2.3", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -8189,6 +8399,16 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", + "serde", +] + [[package]] name = "hashlink" version = "0.8.4" @@ -8198,6 +8418,15 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "heck" version = "0.3.3" @@ -8239,6 +8468,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hex-conservative" @@ -8270,7 +8502,7 @@ dependencies = [ "async-trait", "cfg-if", "data-encoding", - "enum-as-inner 0.6.0", + "enum-as-inner", "futures-channel", "futures-io", "futures-util", @@ -8278,6 +8510,7 @@ dependencies = [ "ipnet", "once_cell", "rand", + "socket2 0.5.7", "thiserror", "tinyvec", "tokio", @@ -8287,9 +8520,9 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28757f23aa75c98f254cf0405e6d8c25b831b32921b050a66692427679b1f243" +checksum = "0a2e2aba9c389ce5267d31cf1e4dace82390ae276b0b364ea55630b1fa1b44b4" dependencies = [ "cfg-if", "futures-util", @@ -8529,7 +8762,7 @@ dependencies = [ "hyper 1.3.1", "hyper-util", "log", - "rustls 0.23.14", + "rustls 0.23.18", "rustls-native-certs 0.8.0", "rustls-pki-types", "tokio", @@ -8612,17 +8845,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.4.0" @@ -8816,12 +9038,13 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.3" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.2", + "serde", ] [[package]] @@ -8896,7 +9119,7 @@ dependencies = [ "socket2 0.5.7", "widestring", "windows-sys 0.48.0", - "winreg 0.50.0", + "winreg", ] [[package]] @@ -8935,7 +9158,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.9", - "rustix 0.38.25", + "rustix 0.38.42", "windows-sys 0.48.0", ] @@ -9144,7 +9367,7 @@ dependencies = [ "http 1.1.0", "jsonrpsee-core", "pin-project", - "rustls 0.23.14", + "rustls 0.23.18", "rustls-pki-types", "rustls-platform-verifier", "soketto 0.8.0", @@ -9197,7 +9420,7 @@ dependencies = [ "hyper-util", "jsonrpsee-core", "jsonrpsee-types", - "rustls 0.23.14", + "rustls 0.23.18", "rustls-platform-verifier", "serde", "serde_json", @@ -9321,6 +9544,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "keccak-hash" version = "0.11.0" @@ -9526,6 +9759,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin 0.9.8", +] [[package]] name = "lazycell" @@ -9541,9 +9777,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libflate" @@ -9604,16 +9840,15 @@ dependencies = [ [[package]] name = "libp2p" -version = "0.52.4" +version = "0.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94495eb319a85b70a68b85e2389a95bb3555c71c49025b78c691a854a7e6464" +checksum = "bbbe80f9c7e00526cd6b838075b9c171919404a4732cb2fa8ece0a093223bfc4" dependencies = [ "bytes", "either", "futures", "futures-timer", "getrandom", - "instant", "libp2p-allow-block-list", "libp2p-connection-limits", "libp2p-core", @@ -9630,7 +9865,6 @@ dependencies = [ "libp2p-swarm", "libp2p-tcp", "libp2p-upnp", - "libp2p-wasm-ext", "libp2p-websocket", "libp2p-yamux", "multiaddr 0.18.1", @@ -9641,9 +9875,9 @@ dependencies = [ [[package]] name = "libp2p-allow-block-list" -version = "0.2.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55b46558c5c0bf99d3e2a1a38fd54ff5476ca66dd1737b12466a1824dd219311" +checksum = "d1027ccf8d70320ed77e984f273bc8ce952f623762cb9bf2d126df73caef8041" dependencies = [ "libp2p-core", "libp2p-identity", @@ -9653,9 +9887,9 @@ dependencies = [ [[package]] name = "libp2p-connection-limits" -version = "0.2.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5107ad45cb20b2f6c3628c7b6014b996fcb13a88053f4569c872c6e30abf58" +checksum = "8d003540ee8baef0d254f7b6bfd79bac3ddf774662ca0abf69186d517ef82ad8" dependencies = [ "libp2p-core", "libp2p-identity", @@ -9665,17 +9899,15 @@ dependencies = [ [[package]] name = "libp2p-core" -version = "0.40.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd44289ab25e4c9230d9246c475a22241e301b23e8f4061d3bdef304a1a99713" +checksum = "a61f26c83ed111104cd820fe9bc3aaabbac5f1652a1d213ed6e900b7918a1298" dependencies = [ "either", "fnv", "futures", "futures-timer", - "instant", "libp2p-identity", - "log", "multiaddr 0.18.1", "multihash 0.19.1", "multistream-select", @@ -9687,33 +9919,35 @@ dependencies = [ "rw-stream-sink", "smallvec", "thiserror", - "unsigned-varint 0.7.2", + "tracing", + "unsigned-varint 0.8.0", "void", + "web-time", ] [[package]] name = "libp2p-dns" -version = "0.40.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6a18db73084b4da2871438f6239fef35190b05023de7656e877c18a00541a3b" +checksum = "97f37f30d5c7275db282ecd86e54f29dd2176bd3ac656f06abf43bedb21eb8bd" dependencies = [ "async-trait", "futures", + "hickory-resolver", "libp2p-core", "libp2p-identity", - "log", "parking_lot 0.12.3", "smallvec", - "trust-dns-resolver", + "tracing", ] [[package]] name = "libp2p-identify" -version = "0.43.1" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a96638a0a176bec0a4bcaebc1afa8cf909b114477209d7456ade52c61cd9cd" +checksum = "1711b004a273be4f30202778856368683bd9a83c4c7dcc8f848847606831a4e3" dependencies = [ - "asynchronous-codec", + "asynchronous-codec 0.7.0", "either", "futures", "futures-bounded", @@ -9721,12 +9955,12 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-swarm", - "log", "lru 0.12.3", "quick-protobuf 0.8.1", "quick-protobuf-codec", "smallvec", "thiserror", + "tracing", "void", ] @@ -9750,83 +9984,84 @@ dependencies = [ [[package]] name = "libp2p-kad" -version = "0.44.6" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ea178dabba6dde6ffc260a8e0452ccdc8f79becf544946692fff9d412fc29d" +checksum = "ced237d0bd84bbebb7c2cad4c073160dacb4fe40534963c32ed6d4c6bb7702a3" dependencies = [ "arrayvec 0.7.4", - "asynchronous-codec", + "asynchronous-codec 0.7.0", "bytes", "either", "fnv", "futures", + "futures-bounded", "futures-timer", - "instant", "libp2p-core", "libp2p-identity", "libp2p-swarm", - "log", "quick-protobuf 0.8.1", "quick-protobuf-codec", "rand", "sha2 0.10.8", "smallvec", "thiserror", + "tracing", "uint 0.9.5", - "unsigned-varint 0.7.2", "void", + "web-time", ] [[package]] name = "libp2p-mdns" -version = "0.44.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a2567c305232f5ef54185e9604579a894fd0674819402bb0ac0246da82f52a" +checksum = "14b8546b6644032565eb29046b42744aee1e9f261ed99671b2c93fb140dba417" dependencies = [ "data-encoding", "futures", + "hickory-proto", "if-watch", "libp2p-core", "libp2p-identity", "libp2p-swarm", - "log", "rand", "smallvec", "socket2 0.5.7", "tokio", - "trust-dns-proto 0.22.0", + "tracing", "void", ] [[package]] name = "libp2p-metrics" -version = "0.13.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239ba7d28f8d0b5d77760dc6619c05c7e88e74ec8fbbe97f856f20a56745e620" +checksum = "77ebafa94a717c8442d8db8d3ae5d1c6a15e30f2d347e0cd31d057ca72e42566" dependencies = [ - "instant", + "futures", "libp2p-core", "libp2p-identify", "libp2p-identity", "libp2p-kad", "libp2p-ping", "libp2p-swarm", - "once_cell", + "pin-project", "prometheus-client", + "web-time", ] [[package]] name = "libp2p-noise" -version = "0.43.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2eeec39ad3ad0677551907dd304b2f13f17208ccebe333bef194076cd2e8921" +checksum = "36b137cb1ae86ee39f8e5d6245a296518912014eaa87427d24e6ff58cfc1b28c" dependencies = [ + "asynchronous-codec 0.7.0", "bytes", "curve25519-dalek 4.1.3", "futures", "libp2p-core", "libp2p-identity", - "log", "multiaddr 0.18.1", "multihash 0.19.1", "once_cell", @@ -9836,33 +10071,34 @@ dependencies = [ "snow", "static_assertions", "thiserror", + "tracing", "x25519-dalek", "zeroize", ] [[package]] name = "libp2p-ping" -version = "0.43.1" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e702d75cd0827dfa15f8fd92d15b9932abe38d10d21f47c50438c71dd1b5dae3" +checksum = "005a34420359223b974ee344457095f027e51346e992d1e0dcd35173f4cdd422" dependencies = [ "either", "futures", "futures-timer", - "instant", "libp2p-core", "libp2p-identity", "libp2p-swarm", - "log", "rand", + "tracing", "void", + "web-time", ] [[package]] name = "libp2p-quic" -version = "0.9.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "130d451d83f21b81eb7b35b360bc7972aeafb15177784adc56528db082e6b927" +checksum = "46352ac5cd040c70e88e7ff8257a2ae2f891a4076abad2c439584a31c15fd24e" dependencies = [ "bytes", "futures", @@ -9871,66 +10107,68 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-tls", - "log", "parking_lot 0.12.3", - "quinn 0.10.2", + "quinn", "rand", - "ring 0.16.20", - "rustls 0.21.7", + "ring 0.17.8", + "rustls 0.23.18", "socket2 0.5.7", "thiserror", "tokio", + "tracing", ] [[package]] name = "libp2p-request-response" -version = "0.25.3" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e3b4d67870478db72bac87bfc260ee6641d0734e0e3e275798f089c3fecfd4" +checksum = "1356c9e376a94a75ae830c42cdaea3d4fe1290ba409a22c809033d1b7dcab0a6" dependencies = [ "async-trait", "futures", - "instant", + "futures-bounded", + "futures-timer", "libp2p-core", "libp2p-identity", "libp2p-swarm", - "log", "rand", "smallvec", + "tracing", "void", + "web-time", ] [[package]] name = "libp2p-swarm" -version = "0.43.7" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "580189e0074af847df90e75ef54f3f30059aedda37ea5a1659e8b9fca05c0141" +checksum = "d7dd6741793d2c1fb2088f67f82cf07261f25272ebe3c0b0c311e0c6b50e851a" dependencies = [ "either", "fnv", "futures", "futures-timer", - "instant", "libp2p-core", "libp2p-identity", "libp2p-swarm-derive", - "log", + "lru 0.12.3", "multistream-select", "once_cell", "rand", "smallvec", "tokio", + "tracing", "void", + "web-time", ] [[package]] name = "libp2p-swarm-derive" -version = "0.33.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4d5ec2a3df00c7836d7696c136274c9c59705bac69133253696a6c932cd1d74" +checksum = "206e0aa0ebe004d778d79fb0966aa0de996c19894e2c0605ba2f8524dd4443d8" dependencies = [ - "heck 0.4.1", - "proc-macro-warning 0.4.2", + "heck 0.5.0", "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.87", @@ -9938,9 +10176,9 @@ dependencies = [ [[package]] name = "libp2p-tcp" -version = "0.40.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b558dd40d1bcd1aaaed9de898e9ec6a436019ecc2420dd0016e712fbb61c5508" +checksum = "ad964f312c59dcfcac840acd8c555de8403e295d39edf96f5240048b5fcaa314" dependencies = [ "futures", "futures-timer", @@ -9948,92 +10186,80 @@ dependencies = [ "libc", "libp2p-core", "libp2p-identity", - "log", "socket2 0.5.7", "tokio", + "tracing", ] [[package]] name = "libp2p-tls" -version = "0.2.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8218d1d5482b122ccae396bbf38abdcb283ecc96fa54760e1dfd251f0546ac61" +checksum = "47b23dddc2b9c355f73c1e36eb0c3ae86f7dc964a3715f0731cfad352db4d847" dependencies = [ "futures", "futures-rustls", "libp2p-core", "libp2p-identity", - "rcgen", - "ring 0.16.20", - "rustls 0.21.7", + "rcgen 0.11.3", + "ring 0.17.8", + "rustls 0.23.18", "rustls-webpki 0.101.4", "thiserror", - "x509-parser 0.15.1", + "x509-parser", "yasna", ] [[package]] name = "libp2p-upnp" -version = "0.1.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82775a47b34f10f787ad3e2a22e2c1541e6ebef4fe9f28f3ac553921554c94c1" +checksum = "01bf2d1b772bd3abca049214a3304615e6a36fa6ffc742bdd1ba774486200b8f" dependencies = [ "futures", "futures-timer", "igd-next", "libp2p-core", "libp2p-swarm", - "log", "tokio", + "tracing", "void", ] -[[package]] -name = "libp2p-wasm-ext" -version = "0.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e5d8e3a9e07da0ef5b55a9f26c009c8fb3c725d492d8bb4b431715786eea79c" -dependencies = [ - "futures", - "js-sys", - "libp2p-core", - "send_wrapper 0.6.0", - "wasm-bindgen", - "wasm-bindgen-futures", -] - [[package]] name = "libp2p-websocket" -version = "0.42.2" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004ee9c4a4631435169aee6aad2f62e3984dc031c43b6d29731e8e82a016c538" +checksum = "888b2ff2e5d8dcef97283daab35ad1043d18952b65e05279eecbe02af4c6e347" dependencies = [ "either", "futures", "futures-rustls", "libp2p-core", "libp2p-identity", - "log", "parking_lot 0.12.3", "pin-project-lite", "rw-stream-sink", "soketto 0.8.0", "thiserror", + "tracing", "url", "webpki-roots 0.25.2", ] [[package]] name = "libp2p-yamux" -version = "0.44.1" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eedcb62824c4300efb9cfd4e2a6edaf3ca097b9e68b36dabe45a44469fd6a85" +checksum = "788b61c80789dba9760d8c669a5bedb642c8267555c803fabd8396e4ca5c5882" dependencies = [ + "either", "futures", "libp2p-core", - "log", "thiserror", - "yamux", + "tracing", + "yamux 0.12.1", + "yamux 0.13.3", ] [[package]] @@ -10099,6 +10325,17 @@ dependencies = [ "libsecp256k1-core", ] +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libz-sys" version = "1.1.12" @@ -10158,9 +10395,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lioness" @@ -10194,9 +10431,9 @@ dependencies = [ [[package]] name = "litep2p" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b67484b8ac41e1cfdf012f65fa81e88c2ef5f8a7d6dec0e2678c2d06dc04530" +checksum = "6ca6ee50a125dc4fc4e9a3ae3640010796d1d07bc517a0ac715fdf0b24a0b6ac" dependencies = [ "async-trait", "bs58", @@ -10207,7 +10444,7 @@ dependencies = [ "futures-timer", "hex-literal", "hickory-resolver", - "indexmap 2.2.3", + "indexmap 2.7.0", "libc", "mockall 0.13.0", "multiaddr 0.17.1", @@ -10219,7 +10456,7 @@ dependencies = [ "prost 0.12.6", "prost-build", "rand", - "rcgen", + "rcgen 0.10.0", "ring 0.16.20", "rustls 0.20.9", "serde", @@ -10239,7 +10476,7 @@ dependencies = [ "unsigned-varint 0.8.0", "url", "x25519-dalek", - "x509-parser 0.16.0", + "x509-parser", "yasna", "zeroize", ] @@ -10432,12 +10669,6 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "matrixmultiply" version = "0.3.7" @@ -10448,6 +10679,16 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + [[package]] name = "memchr" version = "2.7.4" @@ -10510,13 +10751,13 @@ dependencies = [ [[package]] name = "merkleized-metadata" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f313fcff1d2a4bcaa2deeaa00bf7530d77d5f7bd0467a117dde2e29a75a7a17a" +checksum = "38c592efaf1b3250df14c8f3c2d952233f0302bb81d3586db2f303666c1cd607" dependencies = [ "array-bytes", "blake3", - "frame-metadata 16.0.0", + "frame-metadata 18.0.0", "parity-scale-codec", "scale-decode 0.13.1", "scale-info", @@ -10623,7 +10864,7 @@ dependencies = [ "c2-chacha", "curve25519-dalek 4.1.3", "either", - "hashlink", + "hashlink 0.8.4", "lioness", "log", "parking_lot 0.12.3", @@ -11036,36 +11277,24 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "static_assertions", -] - -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags 2.6.0", - "cfg-if", - "libc", ] [[package]] name = "nix" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.6.0", "cfg-if", - "cfg_aliases", + "cfg_aliases 0.2.1", "libc", ] @@ -11306,6 +11535,23 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + [[package]] name = "num-complex" version = "0.4.4" @@ -11440,22 +11686,13 @@ dependencies = [ "memchr", ] -[[package]] -name = "oid-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" -dependencies = [ - "asn1-rs 0.5.2", -] - [[package]] name = "oid-registry" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c958dd45046245b9c3c2547369bb634eb461670b2e7e0de552905801a648d1d" dependencies = [ - "asn1-rs 0.6.1", + "asn1-rs", ] [[package]] @@ -11556,7 +11793,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7b1d40dd8f367db3c65bec8d3dd47d4a604ee8874480738f93191bddab4e0e0" dependencies = [ "expander", - "indexmap 2.2.3", + "indexmap 2.7.0", "itertools 0.11.0", "petgraph", "proc-macro-crate 3.1.0", @@ -11784,6 +12021,27 @@ dependencies = [ "sp-runtime 39.0.2", ] +[[package]] +name = "pallet-asset-rewards" +version = "0.1.0" +dependencies = [ + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-assets 29.1.0", + "pallet-assets-freezer 0.1.0", + "pallet-balances 28.0.0", + "parity-scale-codec", + "primitive-types 0.13.1", + "scale-info", + "sp-api 26.0.0", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-std 14.0.0", +] + [[package]] name = "pallet-asset-tx-payment" version = "28.0.0" @@ -11861,17 +12119,12 @@ dependencies = [ name = "pallet-assets-freezer" version = "0.1.0" dependencies = [ - "frame-benchmarking 28.0.0", - "frame-support 28.0.0", - "frame-system 28.0.0", "log", "pallet-assets 29.1.0", "pallet-balances 28.0.0", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", ] [[package]] @@ -11894,14 +12147,10 @@ dependencies = [ name = "pallet-atomic-swap" version = "28.0.0" dependencies = [ - "frame-support 28.0.0", - "frame-system 28.0.0", "pallet-balances 28.0.0", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", ] [[package]] @@ -12129,7 +12378,6 @@ dependencies = [ "pallet-staking 28.0.0", "sp-core 28.0.0", "sp-runtime 31.0.1", - "sp-std 14.0.0", "sp-storage 19.0.0", "sp-tracing 16.0.0", ] @@ -12702,13 +12950,11 @@ dependencies = [ "frame-system 28.0.0", "impl-trait-for-tuples", "log", - "pallet-assets 29.1.0", "pallet-balances 28.0.0", "pallet-contracts-fixtures", "pallet-contracts-proc-macro 18.0.0", "pallet-contracts-uapi 5.0.0", "pallet-insecure-randomness-collective-flip 16.0.0", - "pallet-message-queue 31.0.0", "pallet-proxy 28.0.0", "pallet-timestamp 27.0.0", "pallet-utility 28.0.0", @@ -12725,7 +12971,6 @@ dependencies = [ "sp-io 30.0.0", "sp-keystore 0.34.0", "sp-runtime 31.0.1", - "sp-std 14.0.0", "sp-tracing 16.0.0", "staging-xcm 7.0.0", "staging-xcm-builder 7.0.0", @@ -12776,7 +13021,7 @@ dependencies = [ "parity-wasm", "sp-runtime 31.0.1", "tempfile", - "toml 0.8.12", + "toml 0.8.19", "twox-hash", ] @@ -12784,7 +13029,6 @@ dependencies = [ name = "pallet-contracts-mock-network" version = "3.0.0" dependencies = [ - "assert_matches", "frame-support 28.0.0", "frame-system 28.0.0", "pallet-assets 29.1.0", @@ -12793,17 +13037,13 @@ dependencies = [ "pallet-contracts-fixtures", "pallet-contracts-proc-macro 18.0.0", "pallet-contracts-uapi 5.0.0", - "pallet-insecure-randomness-collective-flip 16.0.0", "pallet-message-queue 31.0.0", - "pallet-proxy 28.0.0", "pallet-timestamp 27.0.0", - "pallet-utility 28.0.0", "pallet-xcm 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-primitives 7.0.0", "polkadot-runtime-parachains 7.0.0", - "pretty_assertions", "scale-info", "sp-api 26.0.0", "sp-core 28.0.0", @@ -12905,7 +13145,6 @@ dependencies = [ "frame-support 28.0.0", "frame-system 28.0.0", "pallet-balances 28.0.0", - "pallet-scheduler 29.0.0", "parity-scale-codec", "scale-info", "serde", @@ -13000,7 +13239,6 @@ dependencies = [ "sp-runtime 31.0.1", "sp-staking 26.0.0", "sp-tracing 16.0.0", - "substrate-test-utils", ] [[package]] @@ -13097,6 +13335,7 @@ dependencies = [ "log", "pallet-bags-list 27.0.0", "pallet-balances 28.0.0", + "pallet-delegated-staking 1.0.0", "pallet-election-provider-multi-phase 27.0.0", "pallet-nomination-pools 25.0.0", "pallet-session 28.0.0", @@ -13110,7 +13349,6 @@ dependencies = [ "sp-npos-elections 26.0.0", "sp-runtime 31.0.1", "sp-staking 26.0.0", - "sp-std 14.0.0", "sp-tracing 16.0.0", ] @@ -13363,6 +13601,24 @@ dependencies = [ "sp-runtime 31.0.1", ] +[[package]] +name = "pallet-example-view-functions" +version = "1.0.0" +dependencies = [ + "frame-benchmarking 28.0.0", + "frame-metadata 18.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "log", + "parity-scale-codec", + "pretty_assertions", + "scale-info", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-metadata-ir 0.6.0", + "sp-runtime 31.0.1", +] + [[package]] name = "pallet-examples" version = "4.0.0-dev" @@ -13377,6 +13633,7 @@ dependencies = [ "pallet-example-single-block-migrations", "pallet-example-split", "pallet-example-tasks", + "pallet-example-view-functions", ] [[package]] @@ -13400,7 +13657,6 @@ dependencies = [ "sp-runtime 31.0.1", "sp-staking 26.0.0", "sp-tracing 16.0.0", - "substrate-test-utils", ] [[package]] @@ -13431,7 +13687,6 @@ dependencies = [ "frame-support 28.0.0", "frame-system 28.0.0", "log", - "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", "sp-core 28.0.0", @@ -13597,7 +13852,6 @@ dependencies = [ "scale-info", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-keyring 31.0.0", "sp-runtime 31.0.1", ] @@ -13622,14 +13876,10 @@ dependencies = [ name = "pallet-insecure-randomness-collective-flip" version = "16.0.0" dependencies = [ - "frame-support 28.0.0", - "frame-system 28.0.0", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "safe-mix", "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", ] [[package]] @@ -13764,6 +14014,7 @@ dependencies = [ "impl-trait-for-tuples", "log", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "pretty_assertions", "scale-info", "sp-api 26.0.0", @@ -13806,18 +14057,13 @@ dependencies = [ name = "pallet-mixnet" version = "0.4.0" dependencies = [ - "frame-benchmarking 28.0.0", - "frame-support 28.0.0", - "frame-system 28.0.0", "log", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "scale-info", "serde", "sp-application-crypto 30.0.0", - "sp-arithmetic 23.0.0", - "sp-io 30.0.0", "sp-mixnet 0.4.0", - "sp-runtime 31.0.1", ] [[package]] @@ -13844,18 +14090,12 @@ dependencies = [ name = "pallet-mmr" version = "27.0.0" dependencies = [ - "array-bytes", - "frame-benchmarking 28.0.0", - "frame-support 28.0.0", - "frame-system 28.0.0", "itertools 0.11.0", "log", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", "sp-mmr-primitives 26.0.0", - "sp-runtime 31.0.1", "sp-tracing 16.0.0", ] @@ -13920,7 +14160,6 @@ dependencies = [ "sp-core 28.0.0", "sp-io 30.0.0", "sp-runtime 31.0.1", - "sp-std 14.0.0", ] [[package]] @@ -13980,7 +14219,6 @@ dependencies = [ name = "pallet-nfts-runtime-api" version = "14.0.0" dependencies = [ - "pallet-nfts 22.0.0", "parity-scale-codec", "sp-api 26.0.0", ] @@ -14032,14 +14270,10 @@ dependencies = [ name = "pallet-node-authorization" version = "28.0.0" dependencies = [ - "frame-support 28.0.0", - "frame-system 28.0.0", "log", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", ] [[package]] @@ -14195,31 +14429,6 @@ dependencies = [ "sp-io 30.0.0", "sp-runtime 31.0.1", "sp-staking 26.0.0", - "sp-std 14.0.0", - "sp-tracing 16.0.0", -] - -[[package]] -name = "pallet-nomination-pools-test-transfer-stake" -version = "1.0.0" -dependencies = [ - "frame-election-provider-support 28.0.0", - "frame-support 28.0.0", - "frame-system 28.0.0", - "log", - "pallet-bags-list 27.0.0", - "pallet-balances 28.0.0", - "pallet-nomination-pools 25.0.0", - "pallet-staking 28.0.0", - "pallet-staking-reward-curve", - "pallet-timestamp 27.0.0", - "parity-scale-codec", - "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", - "sp-staking 26.0.0", - "sp-std 14.0.0", "sp-tracing 16.0.0", ] @@ -14230,7 +14439,6 @@ dependencies = [ "frame-support 28.0.0", "frame-system 28.0.0", "log", - "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", "serde", @@ -14312,7 +14520,6 @@ name = "pallet-paged-list" version = "0.6.0" dependencies = [ "docify", - "frame-benchmarking 28.0.0", "frame-support 28.0.0", "frame-system 28.0.0", "parity-scale-codec", @@ -14346,10 +14553,9 @@ name = "pallet-paged-list-fuzzer" version = "0.1.0" dependencies = [ "arbitrary", - "frame-support 28.0.0", "honggfuzz", "pallet-paged-list 0.6.0", - "sp-io 30.0.0", + "polkadot-sdk-frame 0.1.0", ] [[package]] @@ -14600,9 +14806,9 @@ version = "0.1.0" dependencies = [ "array-bytes", "assert_matches", - "bitflags 1.3.2", "derive_more 0.99.17", "environmental", + "ethabi-decode 2.0.0", "ethereum-types 0.15.1", "frame-benchmarking 28.0.0", "frame-support 28.0.0", @@ -14610,11 +14816,8 @@ dependencies = [ "hex", "hex-literal", "impl-trait-for-tuples", - "jsonrpsee", "log", - "pallet-assets 29.1.0", "pallet-balances 28.0.0", - "pallet-message-queue 31.0.0", "pallet-proxy 28.0.0", "pallet-revive-fixtures 0.1.0", "pallet-revive-proc-macro 0.1.0", @@ -14624,7 +14827,7 @@ dependencies = [ "pallet-utility 28.0.0", "parity-scale-codec", "paste", - "polkavm 0.13.0", + "polkavm 0.19.0", "pretty_assertions", "rlp 0.6.1", "scale-info", @@ -14633,13 +14836,14 @@ dependencies = [ "serde_json", "sp-api 26.0.0", "sp-arithmetic 23.0.0", + "sp-consensus-aura 0.32.0", + "sp-consensus-babe 0.32.0", + "sp-consensus-slots 0.32.0", "sp-core 28.0.0", "sp-io 30.0.0", "sp-keystore 0.34.0", "sp-runtime 31.0.1", - "sp-std 14.0.0", "sp-tracing 16.0.0", - "sp-weights 27.0.0", "staging-xcm 7.0.0", "staging-xcm-builder 7.0.0", "subxt-signer", @@ -14686,7 +14890,6 @@ dependencies = [ "ethabi", "futures", "hex", - "hex-literal", "jsonrpsee", "log", "pallet-revive 0.1.0", @@ -14697,13 +14900,11 @@ dependencies = [ "sc-rpc", "sc-rpc-api", "sc-service", - "scale-info", - "secp256k1 0.28.2", - "serde_json", + "sp-arithmetic 23.0.0", "sp-core 28.0.0", "sp-crypto-hashing 0.1.0", - "sp-runtime 31.0.1", "sp-weights 27.0.0", + "sqlx", "static_init", "substrate-cli-test-utils", "substrate-prometheus-endpoint", @@ -14718,15 +14919,10 @@ name = "pallet-revive-fixtures" version = "0.1.0" dependencies = [ "anyhow", - "frame-system 28.0.0", - "log", - "parity-wasm", - "polkavm-linker 0.14.0", + "polkavm-linker 0.19.0", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-runtime 31.0.1", - "tempfile", - "toml 0.8.12", + "toml 0.8.19", ] [[package]] @@ -14741,7 +14937,7 @@ dependencies = [ "polkavm-linker 0.10.0", "sp-runtime 39.0.2", "tempfile", - "toml 0.8.12", + "toml 0.8.19", ] [[package]] @@ -14754,13 +14950,10 @@ dependencies = [ "pallet-assets 29.1.0", "pallet-balances 28.0.0", "pallet-message-queue 31.0.0", - "pallet-proxy 28.0.0", "pallet-revive 0.1.0", "pallet-revive-fixtures 0.1.0", - "pallet-revive-proc-macro 0.1.0", "pallet-revive-uapi 0.1.0", "pallet-timestamp 27.0.0", - "pallet-utility 28.0.0", "pallet-xcm 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", @@ -14768,10 +14961,8 @@ dependencies = [ "polkadot-runtime-parachains 7.0.0", "pretty_assertions", "scale-info", - "sp-api 26.0.0", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-keystore 0.34.0", "sp-runtime 31.0.1", "sp-tracing 16.0.0", "staging-xcm 7.0.0", @@ -14840,9 +15031,10 @@ name = "pallet-revive-uapi" version = "0.1.0" dependencies = [ "bitflags 1.3.2", + "pallet-revive-proc-macro 0.1.0", "parity-scale-codec", "paste", - "polkavm-derive 0.14.0", + "polkavm-derive 0.19.0", "scale-info", ] @@ -14877,7 +15069,6 @@ dependencies = [ "sp-io 30.0.0", "sp-runtime 31.0.1", "sp-staking 26.0.0", - "sp-std 14.0.0", ] [[package]] @@ -14966,17 +15157,11 @@ dependencies = [ name = "pallet-salary" version = "13.0.0" dependencies = [ - "frame-benchmarking 28.0.0", - "frame-support 28.0.0", - "frame-system 28.0.0", "log", "pallet-ranked-collective 28.0.0", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "scale-info", - "sp-arithmetic 23.0.0", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", ] [[package]] @@ -15723,7 +15908,6 @@ dependencies = [ "sp-core 28.0.0", "sp-io 30.0.0", "sp-runtime 31.0.1", - "sp-std 14.0.0", ] [[package]] @@ -15782,10 +15966,6 @@ dependencies = [ "frame-benchmarking 28.0.0", "frame-support 28.0.0", "frame-system 28.0.0", - "pallet-balances 28.0.0", - "pallet-collective 28.0.0", - "pallet-root-testing 4.0.0", - "pallet-timestamp 27.0.0", "parity-scale-codec", "scale-info", "sp-core 28.0.0", @@ -15956,6 +16136,7 @@ dependencies = [ "bp-messages 0.7.0", "bp-runtime 0.7.0", "bp-xcm-bridge-hub 0.2.0", + "bp-xcm-bridge-hub-router 0.6.0", "frame-support 28.0.0", "frame-system 28.0.0", "log", @@ -16036,6 +16217,13 @@ dependencies = [ "staging-xcm-builder 17.0.1", ] +[[package]] +name = "parachain-template" +version = "0.0.0" +dependencies = [ + "docify", +] + [[package]] name = "parachain-template-node" version = "0.0.0" @@ -16164,6 +16352,7 @@ dependencies = [ "pallet-session 28.0.0", "pallet-timestamp 27.0.0", "pallet-xcm 7.0.0", + "parachains-common 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "sp-consensus-aura 0.32.0", @@ -16175,6 +16364,7 @@ dependencies = [ "staging-xcm 7.0.0", "staging-xcm-executor 7.0.0", "substrate-wasm-builder 17.0.0", + "xcm-runtime-apis 0.1.0", ] [[package]] @@ -16422,6 +16612,15 @@ dependencies = [ "serde", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "penpal-emulated-chain" version = "0.0.0" @@ -16540,11 +16739,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "enumflags2", "frame-benchmarking 28.0.0", @@ -16573,6 +16772,7 @@ dependencies = [ "pallet-xcm 7.0.0", "pallet-xcm-benchmarks 7.0.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", @@ -16630,6 +16830,7 @@ dependencies = [ "sp-runtime 31.0.1", "staging-xcm 7.0.0", "staging-xcm-executor 7.0.0", + "westend-runtime", "westend-runtime-constants 7.0.0", "westend-system-emulated-network", ] @@ -16641,11 +16842,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "enumflags2", "frame-benchmarking 28.0.0", @@ -16674,6 +16875,7 @@ dependencies = [ "pallet-xcm 7.0.0", "pallet-xcm-benchmarks 7.0.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", @@ -16758,7 +16960,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.2.3", + "indexmap 2.7.0", ] [[package]] @@ -16793,6 +16995,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -16809,12 +17022,6 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" -[[package]] -name = "platforms" -version = "3.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4c7666f2019727f9e8e14bf14456e99c707d780922869f1ba473eee101fa49" - [[package]] name = "plotters" version = "0.3.5" @@ -16849,7 +17056,7 @@ version = "6.0.0" dependencies = [ "assert_cmd", "color-eyre", - "nix 0.28.0", + "nix 0.29.0", "polkadot-cli", "polkadot-core-primitives 7.0.0", "polkadot-node-core-pvf", @@ -16994,6 +17201,16 @@ dependencies = [ "itertools 0.10.5", ] +[[package]] +name = "polkadot-ckb-merkle-mountain-range" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "221c71b432b38e494a0fdedb5f720e4cb974edf03a0af09e5b2238dbac7e6947" +dependencies = [ + "cfg-if", + "itertools 0.10.5", +] + [[package]] name = "polkadot-cli" version = "7.0.0" @@ -17087,7 +17304,7 @@ dependencies = [ "fatality", "futures", "futures-timer", - "indexmap 2.2.3", + "indexmap 2.7.0", "parity-scale-codec", "polkadot-erasure-coding", "polkadot-node-network-protocol", @@ -17598,7 +17815,7 @@ dependencies = [ "futures", "landlock", "libc", - "nix 0.28.0", + "nix 0.29.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-primitives 7.0.0", @@ -17623,7 +17840,7 @@ dependencies = [ "cfg-if", "cpu-time", "libc", - "nix 0.28.0", + "nix 0.29.0", "parity-scale-codec", "polkadot-node-core-pvf-common", "polkadot-node-primitives", @@ -17641,7 +17858,7 @@ dependencies = [ "cfg-if", "criterion", "libc", - "nix 0.28.0", + "nix 0.29.0", "parity-scale-codec", "polkadot-node-core-pvf-common", "polkadot-node-primitives", @@ -17682,7 +17899,6 @@ dependencies = [ name = "polkadot-node-metrics" version = "7.0.0" dependencies = [ - "assert_cmd", "bs58", "futures", "futures-timer", @@ -17700,7 +17916,6 @@ dependencies = [ "sc-tracing", "sp-keyring 31.0.0", "substrate-prometheus-endpoint", - "substrate-test-utils", "tempfile", "tokio", "tracing-gum", @@ -17883,6 +18098,7 @@ dependencies = [ "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", "cumulus-relay-chain-interface", + "cumulus-test-runtime", "docify", "frame-benchmarking 28.0.0", "frame-benchmarking-cli", @@ -17893,7 +18109,7 @@ dependencies = [ "futures-timer", "jsonrpsee", "log", - "nix 0.28.0", + "nix 0.29.0", "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc", "pallet-transaction-payment-rpc-runtime-api 28.0.0", @@ -17910,23 +18126,31 @@ dependencies = [ "sc-consensus-manual-seal", "sc-executor 0.32.0", "sc-network", + "sc-offchain", "sc-rpc", + "sc-runtime-utilities", "sc-service", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", + "sc-transaction-pool-api", + "scale-info", "serde", "serde_json", "sp-api 26.0.0", "sp-block-builder 26.0.0", + "sp-consensus", "sp-consensus-aura 0.32.0", "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-genesis-builder 0.8.0", "sp-inherents 26.0.0", "sp-keystore 0.34.0", + "sp-offchain 26.0.0", "sp-runtime 31.0.1", "sp-session 27.0.0", + "sp-storage 19.0.0", "sp-timestamp 26.0.0", "sp-transaction-pool 26.0.0", "sp-version 29.0.0", @@ -17934,6 +18158,7 @@ dependencies = [ "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-state-trie-migration-rpc", + "subxt-metadata", "tokio", "wait-timeout", ] @@ -17974,7 +18199,6 @@ dependencies = [ "bridge-hub-westend-runtime", "collectives-westend-runtime", "color-eyre", - "contracts-rococo-runtime", "coretime-rococo-runtime", "coretime-westend-runtime", "cumulus-primitives-core 0.7.0", @@ -18314,7 +18538,6 @@ dependencies = [ "pallet-session 28.0.0", "pallet-staking 28.0.0", "pallet-timestamp 27.0.0", - "pallet-vesting 28.0.0", "parity-scale-codec", "polkadot-core-primitives 7.0.0", "polkadot-parachain-primitives 6.0.0", @@ -18433,6 +18656,7 @@ dependencies = [ "cumulus-pallet-parachain-system-proc-macro 0.6.0", "cumulus-pallet-session-benchmarking 9.0.0", "cumulus-pallet-solo-to-para 0.7.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-ping 0.7.0", @@ -18474,6 +18698,7 @@ dependencies = [ "pallet-asset-conversion-ops 0.1.0", "pallet-asset-conversion-tx-payment 10.0.0", "pallet-asset-rate 7.0.0", + "pallet-asset-rewards", "pallet-asset-tx-payment 28.0.0", "pallet-assets 29.1.0", "pallet-assets-freezer 0.1.0", @@ -18542,7 +18767,6 @@ dependencies = [ "pallet-remark 28.0.0", "pallet-revive 0.1.0", "pallet-revive-eth-rpc", - "pallet-revive-fixtures 0.1.0", "pallet-revive-mock-network 0.1.0", "pallet-revive-proc-macro 0.1.0", "pallet-revive-uapi 0.1.0", @@ -18672,6 +18896,7 @@ dependencies = [ "sc-rpc-api", "sc-rpc-server", "sc-rpc-spec-v2", + "sc-runtime-utilities", "sc-service", "sc-state-db", "sc-statement-store", @@ -19017,11 +19242,12 @@ version = "0.0.1" dependencies = [ "assert_cmd", "chain-spec-guide-runtime", + "cmd_lib", "cumulus-client-service", "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", + "cumulus-pallet-weight-reclaim", "cumulus-primitives-proof-size-hostfunction 0.2.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "docify", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -19096,6 +19322,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", "sp-std 14.0.0", + "sp-storage 19.0.0", "sp-tracing 16.0.0", "sp-version 29.0.0", "sp-weights 27.0.0", @@ -19334,7 +19561,7 @@ dependencies = [ "fatality", "futures", "futures-timer", - "indexmap 2.2.3", + "indexmap 2.7.0", "parity-scale-codec", "polkadot-node-network-protocol", "polkadot-node-primitives", @@ -19562,7 +19789,6 @@ dependencies = [ "staging-xcm-executor 7.0.0", "substrate-wasm-builder 17.0.0", "test-runtime-constants", - "tiny-keccak", ] [[package]] @@ -19610,7 +19836,6 @@ dependencies = [ "sp-runtime 31.0.1", "sp-state-machine 0.35.0", "substrate-test-client", - "substrate-test-utils", "tempfile", "test-runtime-constants", "tokio", @@ -19635,6 +19860,7 @@ dependencies = [ "env_logger 0.11.3", "log", "parity-scale-codec", + "polkadot-primitives 7.0.0", "serde", "serde_json", "substrate-build-script-utils", @@ -19673,15 +19899,28 @@ dependencies = [ [[package]] name = "polkavm" -version = "0.13.0" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd044ab1d3b11567ab6b98ca71259a992b4034220d5972988a0e96518e5d343d" +dependencies = [ + "libc", + "log", + "polkavm-assembler 0.18.0", + "polkavm-common 0.18.0", + "polkavm-linux-raw 0.18.0", +] + +[[package]] +name = "polkavm" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e79a14b15ed38cb5b9a1e38d02e933f19e3d180ae5b325fed606c5e5b9177e" +checksum = "8379bb48ff026aa8ae0645ea45f27920bfd21c82b2e82ed914224bb233d59f83" dependencies = [ "libc", "log", - "polkavm-assembler 0.13.0", - "polkavm-common 0.13.0", - "polkavm-linux-raw 0.13.0", + "polkavm-assembler 0.19.0", + "polkavm-common 0.19.0", + "polkavm-linux-raw 0.19.0", ] [[package]] @@ -19704,18 +19943,21 @@ dependencies = [ [[package]] name = "polkavm-assembler" -version = "0.13.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e8da55465000feb0a61bbf556ed03024db58f3420eca37721fc726b3b2136bf" +checksum = "eaad38dc420bfed79e6f731471c973ce5ff5e47ab403e63cf40358fef8a6368f" dependencies = [ "log", ] [[package]] -name = "polkavm-common" -version = "0.8.0" +name = "polkavm-assembler" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c99f7eee94e7be43ba37eef65ad0ee8cbaf89b7c00001c3f6d2be985cb1817" +checksum = "57513b596cf0bafb052dab48e9c168f473c35f7522e17f70cc9f96603012d9b7" +dependencies = [ + "log", +] [[package]] name = "polkavm-common" @@ -19738,27 +19980,22 @@ dependencies = [ [[package]] name = "polkavm-common" -version = "0.13.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084b4339aae7dfdaaa5aa7d634110afd95970e0737b6fb2a0cb10db8b56b753c" +checksum = "31ff33982a807d8567645d4784b9b5d7ab87bcb494f534a57cadd9012688e102" dependencies = [ "log", - "polkavm-assembler 0.13.0", + "polkavm-assembler 0.18.0", ] [[package]] name = "polkavm-common" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711952a783e9c5ad407cdacb1ed147f36d37c5d43417c1091d86456d2999417b" - -[[package]] -name = "polkavm-derive" -version = "0.8.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79fa916f7962348bd1bb1a65a83401675e6fc86c51a0fdbcf92a3108e58e6125" +checksum = "a972bd305ba8cbf0de79951d6d49d2abfad47c277596be5a2c6a0924a163abbd" dependencies = [ - "polkavm-derive-impl-macro 0.8.0", + "log", + "polkavm-assembler 0.19.0", ] [[package]] @@ -19781,23 +20018,20 @@ dependencies = [ [[package]] name = "polkavm-derive" -version = "0.14.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4832a0aebf6cefc988bb7b2d74ea8c86c983164672e2fc96300f356a1babfc1" +checksum = "c2eb703f3b6404c13228402e98a5eae063fd16b8f58afe334073ec105ee4117e" dependencies = [ - "polkavm-derive-impl-macro 0.14.0", + "polkavm-derive-impl-macro 0.18.0", ] [[package]] -name = "polkavm-derive-impl" -version = "0.8.0" +name = "polkavm-derive" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c10b2654a8a10a83c260bfb93e97b262cf0017494ab94a65d389e0eda6de6c9c" +checksum = "d8d866972a7532d82d05c26b4516563660dd6676d7ab9e64e681d8ef0e29255c" dependencies = [ - "polkavm-common 0.8.0", - "proc-macro2 1.0.86", - "quote 1.0.37", - "syn 2.0.87", + "polkavm-derive-impl-macro 0.19.0", ] [[package]] @@ -19826,23 +20060,25 @@ dependencies = [ [[package]] name = "polkavm-derive-impl" -version = "0.14.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e339fc7c11310fe5adf711d9342278ac44a75c9784947937cce12bd4f30842f2" +checksum = "12d2840cc62a0550156b1676fed8392271ddf2fab4a00661db56231424674624" dependencies = [ - "polkavm-common 0.14.0", + "polkavm-common 0.18.0", "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.87", ] [[package]] -name = "polkavm-derive-impl-macro" -version = "0.8.0" +name = "polkavm-derive-impl" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" +checksum = "5cffca9d51b21153395a192b65698457687bc51daa41026629895542ccaa65c2" dependencies = [ - "polkavm-derive-impl 0.8.0", + "polkavm-common 0.19.0", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 2.0.87", ] @@ -19868,11 +20104,21 @@ dependencies = [ [[package]] name = "polkavm-derive-impl-macro" -version = "0.14.0" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c16669ddc7433e34c1007d31080b80901e3e8e523cb9d4b441c3910cf9294b" +dependencies = [ + "polkavm-derive-impl 0.18.0", + "syn 2.0.87", +] + +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b569754b15060d03000c09e3bf11509d527f60b75d79b4c30c3625b5071d9702" +checksum = "cc0dc0cf2e8f4d30874131eccfa36bdabd4a52cfb79c15f8630508abaf06a2a6" dependencies = [ - "polkavm-derive-impl 0.14.0", + "polkavm-derive-impl 0.19.0", "syn 2.0.87", ] @@ -19908,15 +20154,32 @@ dependencies = [ [[package]] name = "polkavm-linker" -version = "0.14.0" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9bfe793b094d9ea5c99b7c43ba46e277b0f8f48f4bbfdbabf8d3ebf701a4bd3" +dependencies = [ + "dirs", + "gimli 0.31.1", + "hashbrown 0.14.5", + "log", + "object 0.36.1", + "polkavm-common 0.18.0", + "regalloc2 0.9.3", + "rustc-demangle", +] + +[[package]] +name = "polkavm-linker" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0959ac3b0f4fd5caf5c245c637705f19493efe83dba31a83bbba928b93b0116a" +checksum = "caec2308f1328b5a667da45322c04fad7ff97ad8b36817d18c7635ea4dd6c6f4" dependencies = [ + "dirs", "gimli 0.31.1", "hashbrown 0.14.5", "log", "object 0.36.1", - "polkavm-common 0.14.0", + "polkavm-common 0.19.0", "regalloc2 0.9.3", "rustc-demangle", ] @@ -19935,9 +20198,15 @@ checksum = "26e45fa59c7e1bb12ef5289080601e9ec9b31435f6e32800a5c90c132453d126" [[package]] name = "polkavm-linux-raw" -version = "0.13.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686c4dd9c9c16cc22565b51bdbb269792318d0fd2e6b966b5f6c788534cad0e9" +checksum = "23eff02c070c70f31878a3d915e88a914ecf3e153741e2fb572dde28cce20fde" + +[[package]] +name = "polkavm-linux-raw" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136ae072ab6fa38e584a06d12b1b216cff19f54d5cd202a8f8c5ec2e92e7e4bb" [[package]] name = "polling" @@ -19964,7 +20233,7 @@ dependencies = [ "cfg-if", "concurrent-queue", "pin-project-lite", - "rustix 0.38.25", + "rustix 0.38.42", "tracing", "windows-sys 0.52.0", ] @@ -20024,7 +20293,7 @@ dependencies = [ "findshlibs", "libc", "log", - "nix 0.26.2", + "nix 0.26.4", "once_cell", "parking_lot 0.12.3", "smallvec", @@ -20231,17 +20500,6 @@ version = "0.5.20+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" -[[package]] -name = "proc-macro-warning" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" -dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", - "syn 2.0.87", -] - [[package]] name = "proc-macro-warning" version = "1.0.0" @@ -20283,7 +20541,7 @@ dependencies = [ "hex", "lazy_static", "procfs-core", - "rustix 0.38.25", + "rustix 0.38.42", ] [[package]] @@ -20313,9 +20571,9 @@ dependencies = [ [[package]] name = "prometheus-client" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c99afa9a01501019ac3a14d71d9f94050346f55ca471ce90c799a15c58f61e2" +checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca" dependencies = [ "dtoa", "itoa", @@ -20486,7 +20744,7 @@ dependencies = [ "log", "names", "prost 0.11.9", - "reqwest 0.11.20", + "reqwest 0.11.27", "thiserror", "url", "winapi", @@ -20546,15 +20804,15 @@ dependencies = [ [[package]] name = "quick-protobuf-codec" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ededb1cd78531627244d51dd0c7139fbe736c7d57af0092a76f0ffb2f56e98" +checksum = "15a0580ab32b169745d7a39db2ba969226ca16738931be152a3209b409de2474" dependencies = [ - "asynchronous-codec", + "asynchronous-codec 0.7.0", "bytes", "quick-protobuf 0.8.1", "thiserror", - "unsigned-varint 0.7.2", + "unsigned-varint 0.8.0", ] [[package]] @@ -20579,24 +20837,6 @@ dependencies = [ "rand", ] -[[package]] -name = "quinn" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75" -dependencies = [ - "bytes", - "futures-io", - "pin-project-lite", - "quinn-proto 0.10.6", - "quinn-udp 0.4.1", - "rustc-hash 1.1.0", - "rustls 0.21.7", - "thiserror", - "tokio", - "tracing", -] - [[package]] name = "quinn" version = "0.11.5" @@ -20604,34 +20844,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" dependencies = [ "bytes", + "futures-io", "pin-project-lite", - "quinn-proto 0.11.8", - "quinn-udp 0.5.4", + "quinn-proto", + "quinn-udp", "rustc-hash 2.0.0", - "rustls 0.23.14", + "rustls 0.23.18", "socket2 0.5.7", "thiserror", "tokio", "tracing", ] -[[package]] -name = "quinn-proto" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a" -dependencies = [ - "bytes", - "rand", - "ring 0.16.20", - "rustc-hash 1.1.0", - "rustls 0.21.7", - "slab", - "thiserror", - "tinyvec", - "tracing", -] - [[package]] name = "quinn-proto" version = "0.11.8" @@ -20640,28 +20864,15 @@ checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ "bytes", "rand", - "ring 0.17.7", + "ring 0.17.8", "rustc-hash 2.0.0", - "rustls 0.23.14", + "rustls 0.23.18", "slab", "thiserror", "tinyvec", "tracing", ] -[[package]] -name = "quinn-udp" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" -dependencies = [ - "bytes", - "libc", - "socket2 0.5.7", - "tracing", - "windows-sys 0.48.0", -] - [[package]] name = "quinn-udp" version = "0.5.4" @@ -20708,6 +20919,7 @@ dependencies = [ "libc", "rand_chacha", "rand_core 0.6.4", + "serde", ] [[package]] @@ -20830,6 +21042,18 @@ dependencies = [ "yasna", ] +[[package]] +name = "rcgen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c4f3084aa3bc7dfbba4eff4fab2a54db4324965d8872ab933565e6fbd83bc6" +dependencies = [ + "pem 3.0.4", + "ring 0.16.20", + "time", + "yasna", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -20850,11 +21074,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] @@ -20933,7 +21157,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.8", "regex-syntax 0.8.5", ] @@ -20954,9 +21178,9 @@ checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -21030,7 +21254,6 @@ dependencies = [ "async-trait", "backoff", "bp-runtime 0.7.0", - "console", "futures", "isahc", "jsonpath_lib", @@ -21064,9 +21287,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64 0.21.7", "bytes", @@ -21092,6 +21315,8 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls 0.24.1", @@ -21101,14 +21326,14 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "webpki-roots 0.25.2", - "winreg 0.50.0", + "winreg", ] [[package]] name = "reqwest" -version = "0.12.5" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64 0.22.1", "bytes", @@ -21128,14 +21353,14 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "quinn 0.11.5", - "rustls 0.23.14", + "quinn", + "rustls 0.23.18", "rustls-pemfile 2.0.0", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.1", "tokio", "tokio-rustls 0.26.0", "tower-service", @@ -21144,7 +21369,7 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "webpki-roots 0.26.3", - "winreg 0.52.0", + "windows-registry", ] [[package]] @@ -21201,16 +21426,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -21281,12 +21507,12 @@ version = "0.6.0" dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-ping 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -21352,14 +21578,12 @@ dependencies = [ "pallet-beefy-mmr 28.0.0", "pallet-bounties 27.0.0", "pallet-child-bounties 27.0.0", - "pallet-collective 28.0.0", "pallet-conviction-voting 28.0.0", "pallet-democracy 28.0.0", "pallet-elections-phragmen 29.0.0", "pallet-grandpa 28.0.0", "pallet-identity 29.0.0", "pallet-indices 28.0.0", - "pallet-membership 28.0.0", "pallet-message-queue 31.0.0", "pallet-migrations 1.0.0", "pallet-mmr 27.0.0", @@ -21396,7 +21620,6 @@ dependencies = [ "polkadot-runtime-parachains 7.0.0", "rococo-runtime-constants 7.0.0", "scale-info", - "separator", "serde", "serde_derive", "serde_json", @@ -21428,7 +21651,6 @@ dependencies = [ "staging-xcm-executor 7.0.0", "static_assertions", "substrate-wasm-builder 17.0.0", - "tiny-keccak", "tokio", "xcm-runtime-apis 0.1.0", ] @@ -21509,6 +21731,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "rsa" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af6c4b23d99685a1408194da11270ef8e9809aff951cc70ec9b17350b087e474" +dependencies = [ + "const-oid", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle 2.5.0", + "zeroize", +] + [[package]] name = "rstest" version = "0.18.2" @@ -21683,15 +21925,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.25" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys 0.4.11", - "windows-sys 0.48.0", + "linux-raw-sys 0.4.14", + "windows-sys 0.59.0", ] [[package]] @@ -21724,7 +21966,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", - "ring 0.17.7", + "ring 0.17.8", "rustls-pki-types", "rustls-webpki 0.102.8", "subtle 2.5.0", @@ -21733,13 +21975,13 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.14" +version = "0.23.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" +checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" dependencies = [ "log", "once_cell", - "ring 0.17.7", + "ring 0.17.8", "rustls-pki-types", "rustls-webpki 0.102.8", "subtle 2.5.0", @@ -21805,9 +22047,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustls-platform-verifier" @@ -21820,7 +22062,7 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.14", + "rustls 0.23.18", "rustls-native-certs 0.7.0", "rustls-platform-verifier-android", "rustls-webpki 0.102.8", @@ -21852,7 +22094,7 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "rustls-pki-types", "untrusted 0.9.0", ] @@ -22163,7 +22405,6 @@ dependencies = [ "sp-state-machine 0.35.0", "sp-statement-store 10.0.0", "sp-storage 19.0.0", - "sp-test-primitives", "sp-trie 29.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime", @@ -22186,7 +22427,6 @@ dependencies = [ "parity-db", "parity-scale-codec", "parking_lot 0.12.3", - "quickcheck", "rand", "sc-client-api", "sc-state-db", @@ -22663,7 +22903,7 @@ dependencies = [ name = "sc-executor-common" version = "0.29.0" dependencies = [ - "polkavm 0.9.3", + "polkavm 0.18.0", "sc-allocator 23.0.0", "sp-maybe-compressed-blob 11.0.0", "sp-wasm-interface 20.0.0", @@ -22704,7 +22944,7 @@ name = "sc-executor-polkavm" version = "0.29.0" dependencies = [ "log", - "polkavm 0.9.3", + "polkavm 0.18.0", "sc-executor-common 0.29.0", "sp-wasm-interface 20.0.0", ] @@ -22861,7 +23101,7 @@ dependencies = [ "assert_matches", "async-channel 1.9.0", "async-trait", - "asynchronous-codec", + "asynchronous-codec 0.6.2", "bytes", "cid 0.9.0", "criterion", @@ -22923,16 +23163,10 @@ dependencies = [ name = "sc-network-common" version = "0.33.0" dependencies = [ - "async-trait", "bitflags 1.3.2", "futures", - "libp2p-identity", "parity-scale-codec", "prost-build", - "sc-consensus", - "sc-network-types", - "sp-consensus", - "sp-consensus-grandpa 13.0.0", "sp-runtime 31.0.1", "tempfile", ] @@ -23127,7 +23361,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "rand", - "rustls 0.23.14", + "rustls 0.23.18", "sc-block-builder", "sc-client-api", "sc-client-db", @@ -23251,6 +23485,7 @@ dependencies = [ "futures", "futures-util", "hex", + "itertools 0.11.0", "jsonrpsee", "log", "parity-scale-codec", @@ -23296,6 +23531,25 @@ dependencies = [ "substrate-wasm-builder 17.0.0", ] +[[package]] +name = "sc-runtime-utilities" +version = "0.1.0" +dependencies = [ + "cumulus-primitives-proof-size-hostfunction 0.2.0", + "cumulus-test-runtime", + "parity-scale-codec", + "sc-executor 0.32.0", + "sc-executor-common 0.29.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-state-machine 0.35.0", + "sp-version 29.0.0", + "sp-wasm-interface 20.0.0", + "subxt", + "thiserror", +] + [[package]] name = "sc-service" version = "0.35.0" @@ -23475,7 +23729,6 @@ dependencies = [ "sp-crypto-hashing 0.1.0", "sp-io 30.0.0", "sp-runtime 31.0.1", - "sp-std 14.0.0", ] [[package]] @@ -23489,7 +23742,6 @@ dependencies = [ "parking_lot 0.12.3", "pin-project", "rand", - "sc-network", "sc-utils", "serde", "serde_json", @@ -23546,7 +23798,7 @@ dependencies = [ "criterion", "futures", "futures-timer", - "indexmap 2.2.3", + "indexmap 2.7.0", "itertools 0.11.0", "linked-hash-map", "log", @@ -23572,6 +23824,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", + "tracing", ] [[package]] @@ -23686,9 +23939,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.11.5" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa7ffc1c0ef49b0452c6e2986abf2b07743320641ffd5fc63d552458e3b779b" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" dependencies = [ "bitvec", "cfg-if", @@ -23700,9 +23953,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.11.5" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46385cc24172cf615450267463f937c10072516359b3ff1cb24228a4a08bf951" +checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", @@ -23832,12 +24085,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -24034,18 +24281,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" - -[[package]] -name = "separator" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" - [[package]] name = "serde" version = "1.0.214" @@ -24120,7 +24355,7 @@ version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.7.0", "itoa", "memchr", "ryu", @@ -24154,7 +24389,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.7.0", "itoa", "ryu", "serde", @@ -24241,6 +24476,16 @@ dependencies = [ "keccak", ] +[[package]] +name = "sha3-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +dependencies = [ + "cc", + "cfg-if", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -24392,6 +24637,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "smol" @@ -24633,7 +24881,7 @@ dependencies = [ "chacha20poly1305", "curve25519-dalek 4.1.3", "rand_core 0.6.4", - "ring 0.17.7", + "ring 0.17.8", "rustc_version 0.4.0", "sha2 0.10.8", "subtle 2.5.0", @@ -24761,7 +25009,6 @@ dependencies = [ "sp-io 30.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", - "wasm-bindgen-test", ] [[package]] @@ -24874,7 +25121,6 @@ dependencies = [ "snowbridge-pallet-ethereum-client-fixtures 0.9.0", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-keyring 31.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", "static_assertions", @@ -24933,8 +25179,7 @@ dependencies = [ name = "snowbridge-pallet-inbound-queue" version = "0.2.0" dependencies = [ - "alloy-primitives", - "alloy-sol-types", + "alloy-core", "frame-benchmarking 28.0.0", "frame-support 28.0.0", "frame-system 28.0.0", @@ -24964,8 +25209,8 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2e6a9d00e60e3744e6b6f0c21fea6694b9c6401ac40e41340a96e561dcf1935" dependencies = [ - "alloy-primitives", - "alloy-sol-types", + "alloy-primitives 0.4.2", + "alloy-sol-types 0.4.2", "frame-benchmarking 38.0.0", "frame-support 38.0.0", "frame-system 38.0.0", @@ -25028,7 +25273,6 @@ dependencies = [ "sp-arithmetic 23.0.0", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-keyring 31.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -25075,7 +25319,6 @@ dependencies = [ "snowbridge-pallet-outbound-queue 0.2.0", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-keyring 31.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm 7.0.0", @@ -25519,6 +25762,7 @@ dependencies = [ "sp-api 26.0.0", "sp-consensus", "sp-core 28.0.0", + "sp-metadata-ir 0.6.0", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", "sp-tracing 16.0.0", @@ -25984,9 +26228,9 @@ dependencies = [ [[package]] name = "sp-core" -version = "31.0.0" +version = "32.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d7a0fd8f16dcc3761198fc83be12872f823b37b749bc72a3a6a1f702509366" +checksum = "bb2dac7e47c7ddbb61efe196d5cce99f6ea88926c961fa39909bfeae46fc5a7b" dependencies = [ "array-bytes", "bitflags 1.3.2", @@ -26017,59 +26261,12 @@ dependencies = [ "serde", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-debug-derive 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-externalities 0.27.0", - "sp-runtime-interface 26.0.0", + "sp-externalities 0.28.0", + "sp-runtime-interface 27.0.0", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-storage 20.0.0", + "sp-storage 21.0.0", "ss58-registry", - "substrate-bip39 0.5.0", - "thiserror", - "tracing", - "w3f-bls", - "zeroize", -] - -[[package]] -name = "sp-core" -version = "32.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2dac7e47c7ddbb61efe196d5cce99f6ea88926c961fa39909bfeae46fc5a7b" -dependencies = [ - "array-bytes", - "bitflags 1.3.2", - "blake2 0.10.6", - "bounded-collections", - "bs58", - "dyn-clonable", - "ed25519-zebra 3.1.0", - "futures", - "hash-db", - "hash256-std-hasher", - "impl-serde 0.4.0", - "itertools 0.10.5", - "k256", - "libsecp256k1", - "log", - "merlin", - "parity-bip39", - "parity-scale-codec", - "parking_lot 0.12.3", - "paste", - "primitive-types 0.12.2", - "rand", - "scale-info", - "schnorrkel 0.11.4", - "secp256k1 0.28.2", - "secrecy 0.8.0", - "serde", - "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-debug-derive 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-externalities 0.28.0", - "sp-runtime-interface 27.0.0", - "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-storage 21.0.0", - "ss58-registry", - "substrate-bip39 0.6.0", + "substrate-bip39 0.6.0", "thiserror", "tracing", "w3f-bls", @@ -26205,7 +26402,7 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" version = "0.4.1" -source = "git+https://github.com/paritytech/polkadot-sdk#838a534da874cf6071fba1df07643c6c5b033ae0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -26369,18 +26566,6 @@ dependencies = [ "sp-storage 19.0.0", ] -[[package]] -name = "sp-externalities" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d6a4572eadd4a63cff92509a210bf425501a0c5e76574b30a366ac77653787" -dependencies = [ - "environmental", - "parity-scale-codec", - "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-storage 20.0.0", -] - [[package]] name = "sp-externalities" version = "0.28.0" @@ -26464,7 +26649,7 @@ dependencies = [ "libsecp256k1", "log", "parity-scale-codec", - "polkavm-derive 0.9.1", + "polkavm-derive 0.18.0", "rustversion", "secp256k1 0.28.2", "sp-core 28.0.0", @@ -26650,7 +26835,7 @@ dependencies = [ name = "sp-metadata-ir" version = "0.6.0" dependencies = [ - "frame-metadata 16.0.0", + "frame-metadata 18.0.0", "parity-scale-codec", "scale-info", ] @@ -26695,7 +26880,7 @@ dependencies = [ "array-bytes", "log", "parity-scale-codec", - "polkadot-ckb-merkle-mountain-range", + "polkadot-ckb-merkle-mountain-range 0.8.1", "scale-info", "serde", "sp-api 26.0.0", @@ -26713,7 +26898,7 @@ checksum = "9a12dd76e368f1e48144a84b4735218b712f84b3f976970e2f25a29b30440e10" dependencies = [ "log", "parity-scale-codec", - "polkadot-ckb-merkle-mountain-range", + "polkadot-ckb-merkle-mountain-range 0.7.0", "scale-info", "serde", "sp-api 34.0.0", @@ -26948,7 +27133,7 @@ dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", - "polkavm-derive 0.9.1", + "polkavm-derive 0.18.0", "primitive-types 0.13.1", "rustversion", "sp-core 28.0.0", @@ -26965,26 +27150,6 @@ dependencies = [ "trybuild", ] -[[package]] -name = "sp-runtime-interface" -version = "26.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a675ea4858333d4d755899ed5ed780174aa34fec15953428d516af5452295" -dependencies = [ - "bytes", - "impl-trait-for-tuples", - "parity-scale-codec", - "polkavm-derive 0.8.0", - "primitive-types 0.12.2", - "sp-externalities 0.27.0", - "sp-runtime-interface-proc-macro 18.0.0", - "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-storage 20.0.0", - "sp-tracing 16.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-wasm-interface 20.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "static_assertions", -] - [[package]] name = "sp-runtime-interface" version = "27.0.0" @@ -27028,7 +27193,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#838a534da874cf6071fba1df07643c6c5b033ae0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" dependencies = [ "Inflector", "proc-macro-crate 1.3.1", @@ -27342,20 +27507,6 @@ dependencies = [ "sp-debug-derive 14.0.0", ] -[[package]] -name = "sp-storage" -version = "20.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dba5791cb3978e95daf99dad919ecb3ec35565604e88cd38d805d9d4981e8bd" -dependencies = [ - "impl-serde 0.4.0", - "parity-scale-codec", - "ref-cast", - "serde", - "sp-debug-derive 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "sp-storage" version = "21.0.0" @@ -27427,19 +27578,6 @@ dependencies = [ "tracing-subscriber 0.3.18", ] -[[package]] -name = "sp-tracing" -version = "16.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0351810b9d074df71c4514c5228ed05c250607cba131c1c9d1526760ab69c05c" -dependencies = [ - "parity-scale-codec", - "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tracing", - "tracing-core", - "tracing-subscriber 0.2.25", -] - [[package]] name = "sp-tracing" version = "17.0.1" @@ -27653,7 +27791,7 @@ name = "sp-version-proc-macro" version = "13.0.0" dependencies = [ "parity-scale-codec", - "proc-macro-warning 1.0.0", + "proc-macro-warning", "proc-macro2 1.0.86", "quote 1.0.37", "sp-version 29.0.0", @@ -27696,20 +27834,6 @@ dependencies = [ "wasmtime", ] -[[package]] -name = "sp-wasm-interface" -version = "20.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef97172c42eb4c6c26506f325f48463e9bc29b2034a587f1b9e48c751229bee" -dependencies = [ - "anyhow", - "impl-trait-for-tuples", - "log", - "parity-scale-codec", - "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmtime", -] - [[package]] name = "sp-wasm-interface" version = "21.0.1" @@ -27763,6 +27887,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "spinners" @@ -27785,6 +27912,210 @@ dependencies = [ "der", ] +[[package]] +name = "sqlformat" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" +dependencies = [ + "atoi", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener 5.3.1", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.14.5", + "hashlink 0.9.1", + "hex", + "indexmap 2.7.0", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2 0.10.8", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.37", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.87", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" +dependencies = [ + "dotenvy", + "either", + "heck 0.5.0", + "hex", + "once_cell", + "proc-macro2 1.0.86", + "quote 1.0.37", + "serde", + "serde_json", + "sha2 0.10.8", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.87", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.6.0", + "byteorder", + "bytes", + "crc", + "digest 0.10.7", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array 0.14.7", + "hex", + "hkdf", + "hmac 0.12.1", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2 0.10.8", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.6.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac 0.12.1", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2 0.10.8", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "tracing", + "url", +] + [[package]] name = "ss58-registry" version = "1.43.0" @@ -27857,12 +28188,11 @@ dependencies = [ "jsonrpsee", "kitchensink-runtime", "log", - "nix 0.28.0", + "nix 0.29.0", "node-primitives", "node-rpc", "node-testing", "parity-scale-codec", - "platforms", "polkadot-sdk 0.1.0", "pretty_assertions", "rand", @@ -27936,7 +28266,7 @@ version = "7.0.0" dependencies = [ "array-bytes", "bounded-collections", - "derivative", + "derive-where", "environmental", "frame-support 28.0.0", "hex", @@ -27977,7 +28307,6 @@ dependencies = [ name = "staging-xcm-builder" version = "7.0.0" dependencies = [ - "assert_matches", "frame-support 28.0.0", "frame-system 28.0.0", "impl-trait-for-tuples", @@ -28080,7 +28409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a2a1c578e98c1c16fc3b8ec1328f7659a500737d7a0c6d625e73e830ff9c1f6" dependencies = [ "bitflags 1.3.2", - "cfg_aliases", + "cfg_aliases 0.1.1", "libc", "parking_lot 0.11.2", "parking_lot_core 0.8.6", @@ -28094,7 +28423,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" dependencies = [ - "cfg_aliases", + "cfg_aliases 0.1.1", "memchr", "proc-macro2 1.0.86", "quote 1.0.37", @@ -28112,6 +28441,17 @@ dependencies = [ "serde", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.8.0" @@ -28249,19 +28589,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "substrate-bip39" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b564c293e6194e8b222e52436bcb99f60de72043c7f845cf6c4406db4df121" -dependencies = [ - "hmac 0.12.1", - "pbkdf2", - "schnorrkel 0.11.4", - "sha2 0.10.8", - "zeroize", -] - [[package]] name = "substrate-bip39" version = "0.6.0" @@ -28285,7 +28612,7 @@ version = "0.1.0" dependencies = [ "assert_cmd", "futures", - "nix 0.28.0", + "nix 0.29.0", "node-primitives", "regex", "sc-cli", @@ -28469,7 +28796,6 @@ dependencies = [ "sc-client-db", "sc-consensus", "sc-executor 0.32.0", - "sc-offchain", "sc-service", "serde", "serde_json", @@ -28571,12 +28897,6 @@ dependencies = [ [[package]] name = "substrate-test-utils" version = "4.0.0-dev" -dependencies = [ - "futures", - "sc-service", - "tokio", - "trybuild", -] [[package]] name = "substrate-wasm-builder" @@ -28587,12 +28907,12 @@ dependencies = [ "cargo_metadata", "console", "filetime", - "frame-metadata 16.0.0", + "frame-metadata 18.0.0", "jobserver", "merkleized-metadata", "parity-scale-codec", "parity-wasm", - "polkavm-linker 0.9.2", + "polkavm-linker 0.18.0", "sc-executor 0.32.0", "shlex", "sp-core 28.0.0", @@ -28602,7 +28922,7 @@ dependencies = [ "sp-version 29.0.0", "strum 0.26.3", "tempfile", - "toml 0.8.12", + "toml 0.8.19", "walkdir", "wasm-opt", ] @@ -28623,7 +28943,7 @@ dependencies = [ "sp-maybe-compressed-blob 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "strum 0.26.3", "tempfile", - "toml 0.8.12", + "toml 0.8.19", "walkdir", "wasm-opt", ] @@ -28658,7 +28978,7 @@ dependencies = [ "log", "num-format", "rand", - "reqwest 0.12.5", + "reqwest 0.12.9", "scale-info", "semver 1.0.18", "serde", @@ -28979,11 +29299,32 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "syn-solidity" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219389c1ebe89f8333df8bdfb871f6631c552ff399c23cac02480b6088aad8f0" +dependencies = [ + "paste", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -29069,15 +29410,15 @@ checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" [[package]] name = "tempfile" -version = "3.8.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", - "fastrand 2.1.0", - "redox_syscall 0.4.1", - "rustix 0.38.25", - "windows-sys 0.48.0", + "fastrand 2.3.0", + "once_cell", + "rustix 0.38.42", + "windows-sys 0.59.0", ] [[package]] @@ -29106,7 +29447,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.38.25", + "rustix 0.38.42", "windows-sys 0.48.0", ] @@ -29171,7 +29512,6 @@ dependencies = [ "sc-service", "sp-core 28.0.0", "sp-keyring 31.0.0", - "substrate-test-utils", "test-parachain-adder", "tokio", ] @@ -29192,6 +29532,7 @@ dependencies = [ "log", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", + "polkadot-primitives 7.0.0", "sp-io 30.0.0", "substrate-wasm-builder 17.0.0", "tiny-keccak", @@ -29207,6 +29548,7 @@ dependencies = [ "log", "parity-scale-codec", "polkadot-cli", + "polkadot-erasure-coding", "polkadot-node-core-pvf", "polkadot-node-primitives", "polkadot-node-subsystem", @@ -29215,10 +29557,10 @@ dependencies = [ "polkadot-service", "polkadot-test-service", "sc-cli", + "sc-client-api", "sc-service", "sp-core 28.0.0", "sp-keyring 31.0.0", - "substrate-test-utils", "test-parachain-undying", "tokio", ] @@ -29528,7 +29870,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.14", + "rustls 0.23.18", "rustls-pki-types", "tokio", ] @@ -29599,33 +29941,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.8" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.19.15", -] - -[[package]] -name = "toml" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.12", + "toml_edit 0.22.22", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -29636,9 +29966,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.3", - "serde", - "serde_spanned", + "indexmap 2.7.0", "toml_datetime", "winnow 0.5.15", ] @@ -29649,18 +29977,18 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.7.0", "toml_datetime", "winnow 0.5.15", ] [[package]] name = "toml_edit" -version = "0.22.12" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", @@ -29920,78 +30248,6 @@ dependencies = [ "keccak-hasher", ] -[[package]] -name = "trust-dns-proto" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner 0.5.1", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.2.3", - "ipnet", - "lazy_static", - "rand", - "smallvec", - "socket2 0.4.9", - "thiserror", - "tinyvec", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "trust-dns-proto" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner 0.6.0", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.4.0", - "ipnet", - "once_cell", - "rand", - "smallvec", - "thiserror", - "tinyvec", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "trust-dns-resolver" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6" -dependencies = [ - "cfg-if", - "futures-util", - "ipconfig", - "lru-cache", - "once_cell", - "parking_lot 0.12.3", - "rand", - "resolv-conf", - "smallvec", - "thiserror", - "tokio", - "tracing", - "trust-dns-proto 0.23.2", -] - [[package]] name = "try-lock" version = "0.2.4" @@ -30143,6 +30399,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + [[package]] name = "unicode-segmentation" version = "1.11.0" @@ -30167,6 +30429,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "universal-hash" version = "0.5.1" @@ -30201,7 +30469,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" dependencies = [ - "asynchronous-codec", + "asynchronous-codec 0.6.2", "bytes", "futures-io", "futures-util", @@ -30239,7 +30507,7 @@ dependencies = [ "flate2", "log", "once_cell", - "rustls 0.23.14", + "rustls 0.23.18", "rustls-pki-types", "serde", "serde_json", @@ -30410,6 +30678,12 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.95" @@ -30479,30 +30753,6 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" -[[package]] -name = "wasm-bindgen-test" -version = "0.3.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" -dependencies = [ - "console_error_panic_hook", - "js-sys", - "scoped-tls", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-bindgen-test-macro", -] - -[[package]] -name = "wasm-bindgen-test-macro" -version = "0.3.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" -dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", -] - [[package]] name = "wasm-encoder" version = "0.31.1" @@ -30950,7 +31200,7 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "untrusted 0.9.0", ] @@ -31016,10 +31266,8 @@ dependencies = [ "pallet-balances 28.0.0", "pallet-beefy 28.0.0", "pallet-beefy-mmr 28.0.0", - "pallet-collective 28.0.0", "pallet-conviction-voting 28.0.0", "pallet-delegated-staking 1.0.0", - "pallet-democracy 28.0.0", "pallet-election-provider-multi-phase 27.0.0", "pallet-election-provider-support-benchmarking 27.0.0", "pallet-elections-phragmen 29.0.0", @@ -31149,6 +31397,16 @@ dependencies = [ "westend-emulated-chain", ] +[[package]] +name = "whoami" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +dependencies = [ + "redox_syscall 0.5.8", + "wasite", +] + [[package]] name = "wide" version = "0.7.11" @@ -31243,6 +31501,36 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -31485,16 +31773,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wyz" version = "0.5.1" @@ -31516,35 +31794,18 @@ dependencies = [ "zeroize", ] -[[package]] -name = "x509-parser" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da" -dependencies = [ - "asn1-rs 0.5.2", - "data-encoding", - "der-parser 8.2.0", - "lazy_static", - "nom", - "oid-registry 0.6.1", - "rusticata-macros", - "thiserror", - "time", -] - [[package]] name = "x509-parser" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" dependencies = [ - "asn1-rs 0.6.1", + "asn1-rs", "data-encoding", - "der-parser 9.0.0", + "der-parser", "lazy_static", "nom", - "oid-registry 0.7.0", + "oid-registry", "rusticata-macros", "thiserror", "time", @@ -31590,7 +31851,6 @@ version = "0.5.0" dependencies = [ "array-bytes", "cumulus-pallet-parachain-system 0.7.0", - "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-core 0.7.0", "cumulus-primitives-parachain-inherent 0.7.0", "cumulus-test-relay-sproof-builder 0.7.0", @@ -31622,10 +31882,13 @@ name = "xcm-executor-integration-tests" version = "1.0.0" dependencies = [ "frame-support 28.0.0", + "frame-system 28.0.0", "futures", + "pallet-sudo 28.0.0", "pallet-transaction-payment 28.0.0", "pallet-xcm 7.0.0", "parity-scale-codec", + "polkadot-runtime-parachains 7.0.0", "polkadot-test-client", "polkadot-test-runtime", "polkadot-test-service", @@ -31644,6 +31907,7 @@ name = "xcm-procedural" version = "7.0.0" dependencies = [ "Inflector", + "frame-support 28.0.0", "proc-macro2 1.0.86", "quote 1.0.37", "staging-xcm 7.0.0", @@ -31830,6 +32094,22 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "yamux" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31b5e376a8b012bee9c423acdbb835fc34d45001cfa3106236a624e4b738028" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot 0.12.3", + "pin-project", + "rand", + "static_assertions", + "web-time", +] + [[package]] name = "yansi" version = "0.5.1" @@ -31897,7 +32177,7 @@ version = "1.0.0" dependencies = [ "futures-util", "parity-scale-codec", - "reqwest 0.11.20", + "reqwest 0.12.9", "serde", "serde_json", "thiserror", @@ -31909,29 +32189,29 @@ dependencies = [ [[package]] name = "zombienet-configuration" -version = "0.2.15" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d7a8cc4f8e8bb3f40757b62d3b054da5c95f43321c775eb321edc89d431583e" +checksum = "5ced2fca1322821431f03d06dcf2ea74d3a7369760b6c587b372de6eada3ce43" dependencies = [ "anyhow", "lazy_static", "multiaddr 0.18.1", "regex", - "reqwest 0.11.20", + "reqwest 0.11.27", "serde", "serde_json", "thiserror", "tokio", - "toml 0.7.8", + "toml 0.8.19", "url", "zombienet-support", ] [[package]] name = "zombienet-orchestrator" -version = "0.2.15" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d32fa87851f41443a78971bd7110274f9a66d139ac834de159adc08f90cf8e3" +checksum = "86ecd17133c3129547b6472591b5e58d4aee1fc63c965a3418fd56d33a8a4e82" dependencies = [ "anyhow", "async-trait", @@ -31943,11 +32223,11 @@ dependencies = [ "multiaddr 0.18.1", "rand", "regex", - "reqwest 0.11.20", + "reqwest 0.11.27", "serde", "serde_json", "sha2 0.10.8", - "sp-core 31.0.0", + "sp-core 34.0.0", "subxt", "subxt-signer", "thiserror", @@ -31962,9 +32242,9 @@ dependencies = [ [[package]] name = "zombienet-prom-metrics-parser" -version = "0.2.15" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9acb9c94bc7c2c83f8eb8e26ed403f757af1632f22b89394d8876412ede990ca" +checksum = "23702db0819a050c8a0130a769b105695137020a64207b4597aa021f06924552" dependencies = [ "pest", "pest_derive", @@ -31973,9 +32253,9 @@ dependencies = [ [[package]] name = "zombienet-provider" -version = "0.2.15" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc8f3f71d4d974fc4a2262fa9293c2eedc423540378bd7c1dc1b66cc95d1d1af" +checksum = "83e903843c62cd811e7730ccc618dcd14444d20e8aadfcd7d7561c7b47d8f984" dependencies = [ "anyhow", "async-trait", @@ -31984,9 +32264,9 @@ dependencies = [ "hex", "k8s-openapi", "kube", - "nix 0.27.1", + "nix 0.29.0", "regex", - "reqwest 0.11.20", + "reqwest 0.11.27", "serde", "serde_json", "serde_yaml", @@ -32004,14 +32284,15 @@ dependencies = [ [[package]] name = "zombienet-sdk" -version = "0.2.15" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dbfddce7a6100cdc930b93301f1b6381e6577ecc013d6802258ea6902a2bebd" +checksum = "e457b12c8fdc7003c12dd56855da09812ac11dd232e4ec01acccb2899fe05e44" dependencies = [ "async-trait", "futures", "lazy_static", "subxt", + "subxt-signer", "tokio", "zombienet-configuration", "zombienet-orchestrator", @@ -32021,17 +32302,17 @@ dependencies = [ [[package]] name = "zombienet-support" -version = "0.2.15" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d20567c52b4fd46b600cda254dedb6a6dc30cabf512de91e4f6f78f0f7f4644b" +checksum = "43547d65b19a92cf0ee44380239d82ef345e7d26f7b04b9e0ecf48496af6346b" dependencies = [ "anyhow", "async-trait", "futures", - "nix 0.27.1", + "nix 0.29.0", "rand", "regex", - "reqwest 0.11.20", + "reqwest 0.11.27", "thiserror", "tokio", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 533ea4c9e8780592862c5634c7a1acf07fa98ae8..0d415fe4fdbd46d86394d73060197e12119b16e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,6 +83,7 @@ members = [ "cumulus/pallets/parachain-system/proc-macro", "cumulus/pallets/session-benchmarking", "cumulus/pallets/solo-to-para", + "cumulus/pallets/weight-reclaim", "cumulus/pallets/xcm", "cumulus/pallets/xcmp-queue", "cumulus/parachains/common", @@ -293,6 +294,7 @@ members = [ "substrate/client/rpc-api", "substrate/client/rpc-servers", "substrate/client/rpc-spec-v2", + "substrate/client/runtime-utilities", "substrate/client/service", "substrate/client/service/test", "substrate/client/state-db", @@ -313,6 +315,7 @@ members = [ "substrate/frame/asset-conversion", "substrate/frame/asset-conversion/ops", "substrate/frame/asset-rate", + "substrate/frame/asset-rewards", "substrate/frame/assets", "substrate/frame/assets-freezer", "substrate/frame/atomic-swap", @@ -360,6 +363,7 @@ members = [ "substrate/frame/examples/single-block-migrations", "substrate/frame/examples/split", "substrate/frame/examples/tasks", + "substrate/frame/examples/view-functions", "substrate/frame/executive", "substrate/frame/fast-unstake", "substrate/frame/glutton", @@ -386,7 +390,6 @@ members = [ "substrate/frame/nomination-pools/fuzzer", "substrate/frame/nomination-pools/runtime-api", "substrate/frame/nomination-pools/test-delegate-stake", - "substrate/frame/nomination-pools/test-transfer-stake", "substrate/frame/offences", "substrate/frame/offences/benchmarking", "substrate/frame/paged-list", @@ -536,6 +539,7 @@ members = [ "templates/minimal/node", "templates/minimal/pallets/template", "templates/minimal/runtime", + "templates/parachain", "templates/parachain/node", "templates/parachain/pallets/template", "templates/parachain/runtime", @@ -594,8 +598,7 @@ zero-prefixed-literal = { level = "allow", priority = 2 } # 00_1000_0 Inflector = { version = "0.11.4" } aes-gcm = { version = "0.10" } ahash = { version = "0.8.2" } -alloy-primitives = { version = "0.4.2", default-features = false } -alloy-sol-types = { version = "0.4.2", default-features = false } +alloy-core = { version = "0.8.15", default-features = false } always-assert = { version = "0.1" } anyhow = { version = "1.0.81", default-features = false } approx = { version = "0.5.1" } @@ -717,6 +720,7 @@ cumulus-pallet-parachain-system = { path = "cumulus/pallets/parachain-system", d cumulus-pallet-parachain-system-proc-macro = { path = "cumulus/pallets/parachain-system/proc-macro", default-features = false } cumulus-pallet-session-benchmarking = { path = "cumulus/pallets/session-benchmarking", default-features = false } cumulus-pallet-solo-to-para = { path = "cumulus/pallets/solo-to-para", default-features = false } +cumulus-pallet-weight-reclaim = { path = "cumulus/pallets/weight-reclaim", default-features = false } cumulus-pallet-xcm = { path = "cumulus/pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "cumulus/pallets/xcmp-queue", default-features = false } cumulus-ping = { path = "cumulus/parachains/pallets/ping", default-features = false } @@ -736,8 +740,8 @@ cumulus-test-relay-sproof-builder = { path = "cumulus/test/relay-sproof-builder" cumulus-test-runtime = { path = "cumulus/test/runtime" } cumulus-test-service = { path = "cumulus/test/service" } curve25519-dalek = { version = "4.1.3" } -derivative = { version = "2.2.0", default-features = false } derive-syn-parse = { version = "0.2.0" } +derive-where = { version = "1.2.7" } derive_more = { version = "0.99.17", default-features = false } digest = { version = "0.10.3", default-features = false } directories = { version = "5.0.1" } @@ -779,7 +783,7 @@ frame-benchmarking-pallet-pov = { default-features = false, path = "substrate/fr frame-election-provider-solution-type = { path = "substrate/frame/election-provider-support/solution-type", default-features = false } frame-election-provider-support = { path = "substrate/frame/election-provider-support", default-features = false } frame-executive = { path = "substrate/frame/executive", default-features = false } -frame-metadata = { version = "16.0.0", default-features = false } +frame-metadata = { version = "18.0.0", default-features = false } frame-metadata-hash-extension = { path = "substrate/frame/metadata-hash-extension", default-features = false } frame-support = { path = "substrate/frame/support", default-features = false } frame-support-procedural = { path = "substrate/frame/support/procedural", default-features = false } @@ -841,20 +845,20 @@ kvdb-shared-tests = { version = "0.11.0" } landlock = { version = "0.3.0" } libc = { version = "0.2.155" } libfuzzer-sys = { version = "0.4" } -libp2p = { version = "0.52.4" } +libp2p = { version = "0.54.1" } 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" } linregress = { version = "0.5.1" } lite-json = { version = "0.2.0", default-features = false } -litep2p = { version = "0.8.1", features = ["websocket"] } +litep2p = { version = "0.9.0", features = ["websocket"] } log = { version = "0.4.22", default-features = false } macro_magic = { version = "0.5.1" } maplit = { version = "1.0.2" } memmap2 = { version = "0.9.3" } memory-db = { version = "0.32.0", default-features = false } -merkleized-metadata = { version = "0.1.0" } +merkleized-metadata = { version = "0.2.0" } merlin = { version = "3.0", default-features = false } messages-relay = { path = "bridges/relays/messages" } metered = { version = "0.6.1", default-features = false, package = "prioritized-metered-channel" } @@ -871,7 +875,7 @@ multihash = { version = "0.19.1", default-features = false } multihash-codetable = { version = "0.1.1" } multistream-select = { version = "0.13.0" } names = { version = "0.14.0", default-features = false } -nix = { version = "0.28.0" } +nix = { version = "0.29.0" } node-cli = { path = "substrate/bin/node/cli", package = "staging-node-cli" } node-inspect = { path = "substrate/bin/node/inspect", default-features = false, package = "staging-node-inspect" } node-primitives = { path = "substrate/bin/node/primitives", default-features = false } @@ -891,6 +895,7 @@ pallet-asset-conversion = { path = "substrate/frame/asset-conversion", default-f pallet-asset-conversion-ops = { path = "substrate/frame/asset-conversion/ops", default-features = false } pallet-asset-conversion-tx-payment = { path = "substrate/frame/transaction-payment/asset-conversion-tx-payment", default-features = false } pallet-asset-rate = { path = "substrate/frame/asset-rate", default-features = false } +pallet-asset-rewards = { path = "substrate/frame/asset-rewards", default-features = false } pallet-asset-tx-payment = { path = "substrate/frame/transaction-payment/asset-tx-payment", default-features = false } pallet-assets = { path = "substrate/frame/assets", default-features = false } pallet-assets-freezer = { path = "substrate/frame/assets-freezer", default-features = false } @@ -937,6 +942,7 @@ pallet-example-offchain-worker = { path = "substrate/frame/examples/offchain-wor pallet-example-single-block-migrations = { path = "substrate/frame/examples/single-block-migrations", default-features = false } pallet-example-split = { path = "substrate/frame/examples/split", default-features = false } pallet-example-tasks = { path = "substrate/frame/examples/tasks", default-features = false } +pallet-example-view-functions = { path = "substrate/frame/examples/view-functions", default-features = false } pallet-examples = { path = "substrate/frame/examples" } pallet-fast-unstake = { path = "substrate/frame/fast-unstake", default-features = false } pallet-glutton = { path = "substrate/frame/glutton", default-features = false } @@ -1032,7 +1038,6 @@ people-rococo-runtime = { path = "cumulus/parachains/runtimes/people/people-roco people-westend-emulated-chain = { path = "cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend" } people-westend-runtime = { path = "cumulus/parachains/runtimes/people/people-westend" } pin-project = { version = "1.1.3" } -platforms = { version = "3.4" } polkadot-approval-distribution = { path = "polkadot/node/network/approval-distribution", default-features = false } polkadot-availability-bitfield-distribution = { path = "polkadot/node/network/bitfield-distribution", default-features = false } polkadot-availability-distribution = { path = "polkadot/node/network/availability-distribution", default-features = false } @@ -1089,9 +1094,9 @@ 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 = { version = "0.9.3", default-features = false } -polkavm-derive = "0.9.1" -polkavm-linker = "0.9.2" +polkavm = { version = "0.18.0", default-features = false } +polkavm-derive = "0.18.0" +polkavm-linker = "0.18.0" portpicker = { version = "0.1.1" } pretty_assertions = { version = "1.3.0" } primitive-types = { version = "0.13.1", default-features = false, features = [ @@ -1123,7 +1128,7 @@ regex = { version = "1.10.2" } relay-substrate-client = { path = "bridges/relays/client-substrate" } relay-utils = { path = "bridges/relays/utils" } remote-externalities = { path = "substrate/utils/frame/remote-externalities", default-features = false, package = "frame-remote-externalities" } -reqwest = { version = "0.11", default-features = false } +reqwest = { version = "0.12.9", default-features = false } rlp = { version = "0.6.1", default-features = false } rococo-emulated-chain = { path = "cumulus/parachains/integration-tests/emulated/chains/relays/rococo" } rococo-parachain-runtime = { path = "cumulus/parachains/runtimes/testing/rococo-parachain" } @@ -1136,7 +1141,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 } -rustls = { version = "0.23.14", default-features = false, features = ["logging", "ring", "std", "tls12"] } +rustls = { version = "0.23.18", default-features = false, features = ["logging", "ring", "std", "tls12"] } rustversion = { version = "1.0.17" } rusty-fork = { version = "0.3.0", default-features = false } safe-mix = { version = "1.0", default-features = false } @@ -1184,6 +1189,7 @@ sc-rpc-api = { path = "substrate/client/rpc-api", default-features = false } sc-rpc-server = { path = "substrate/client/rpc-servers", default-features = false } sc-rpc-spec-v2 = { path = "substrate/client/rpc-spec-v2", default-features = false } sc-runtime-test = { path = "substrate/client/executor/runtime-test" } +sc-runtime-utilities = { path = "substrate/client/runtime-utilities", default-features = true } sc-service = { path = "substrate/client/service", default-features = false } sc-service-test = { path = "substrate/client/service/test" } sc-state-db = { path = "substrate/client/state-db", default-features = false } @@ -1197,14 +1203,13 @@ sc-tracing-proc-macro = { path = "substrate/client/tracing/proc-macro", default- sc-transaction-pool = { path = "substrate/client/transaction-pool", default-features = false } sc-transaction-pool-api = { path = "substrate/client/transaction-pool/api", default-features = false } sc-utils = { path = "substrate/client/utils", default-features = false } -scale-info = { version = "2.11.1", default-features = false } +scale-info = { version = "2.11.6", default-features = false } schemars = { version = "0.8.13", default-features = false } schnellru = { version = "0.2.3" } schnorrkel = { version = "0.11.4", default-features = false } seccompiler = { version = "0.4.0" } secp256k1 = { version = "0.28.0", default-features = false } secrecy = { version = "0.8.0", default-features = false } -separator = { version = "0.4.1" } serde = { version = "1.0.214", default-features = false } serde-big-array = { version = "0.3.2" } serde_derive = { version = "1.0.117" } @@ -1317,6 +1322,7 @@ substrate-test-runtime-transaction-pool = { path = "substrate/test-utils/runtime substrate-test-utils = { path = "substrate/test-utils" } substrate-wasm-builder = { path = "substrate/utils/wasm-builder", default-features = false } subxt = { version = "0.38", default-features = false } +subxt-metadata = { version = "0.38.0", default-features = false } subxt-signer = { version = "0.38" } syn = { version = "2.0.87" } sysinfo = { version = "0.30" } @@ -1366,7 +1372,6 @@ void = { version = "1.0.2" } w3f-bls = { version = "0.1.3", default-features = false } wait-timeout = { version = "0.2" } walkdir = { version = "2.5.0" } -wasm-bindgen-test = { version = "0.3.19" } wasm-instrument = { version = "0.4", default-features = false } wasm-opt = { version = "0.116" } wasm-timer = { version = "0.2.5" } @@ -1387,7 +1392,7 @@ xcm-procedural = { path = "polkadot/xcm/procedural", default-features = false } xcm-runtime-apis = { path = "polkadot/xcm/xcm-runtime-apis", default-features = false } xcm-simulator = { path = "polkadot/xcm/xcm-simulator", default-features = false } zeroize = { version = "1.7.0", default-features = false } -zombienet-sdk = { version = "0.2.15" } +zombienet-sdk = { version = "0.2.20" } zstd = { version = "0.12.4", default-features = false } [profile.release] diff --git a/README.md b/README.md index 6c0dfbb2e7e4255efb7bd925789bbc6d5fd7862a..24352cc28a1a98bed2719583ecc0d2a37ae12399 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,9 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/paritytec <!-- markdownlint-disable-next-line MD013 -->   -The Polkadot SDK is released every three months as a `stableYYMMDD` release. They are supported for +The Polkadot SDK is released every three months as a `stableYYMM` release. They are supported for one year with patches. See the next upcoming versions in the [Release -Registry](https://github.com/paritytech/release-registry/). +Registry](https://github.com/paritytech/release-registry/) and more docs in [RELEASE.md](./docs/RELEASE.md). You can use [`psvm`](https://github.com/paritytech/psvm) to update all dependencies to a specific version without needing to manually select the correct version for each crate. diff --git a/bridges/SECURITY.md b/bridges/SECURITY.md index 9f215c88765474e6b211882296c8cf190f216780..ea19eca42cc35c844ef38ded1820a5accaaac827 100644 --- a/bridges/SECURITY.md +++ b/bridges/SECURITY.md @@ -13,6 +13,6 @@ If you think that your report might be eligible for the Bug Bounty Program, plea Please check up-to-date [Parity Bug Bounty Program rules](https://www.parity.io/bug-bounty) to find out the information about our Bug Bounty Program. -**Warning**: This is an unified SECURITY.md file for Paritytech GitHub Organization. The presence of this file does not +**Warning**: This is a unified SECURITY.md file for Paritytech GitHub Organization. The presence of this file does not mean that this repository is covered by the Bug Bounty program. Please always check the Bug Bounty Program scope for information. diff --git a/bridges/bin/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml index 37b56140c289e2a82d8e3e2d1566df420185bbde..b5ec37a24a8d473b2632e62a5bf10ad3ecadb212 100644 --- a/bridges/bin/runtime-common/Cargo.toml +++ b/bridges/bin/runtime-common/Cargo.toml @@ -24,7 +24,6 @@ bp-parachains = { workspace = true } bp-polkadot-core = { workspace = true } bp-relayers = { workspace = true } bp-runtime = { workspace = true } -bp-xcm-bridge-hub = { workspace = true } pallet-bridge-grandpa = { workspace = true } pallet-bridge-messages = { workspace = true } pallet-bridge-parachains = { workspace = true } @@ -63,7 +62,6 @@ std = [ "bp-relayers/std", "bp-runtime/std", "bp-test-utils/std", - "bp-xcm-bridge-hub/std", "codec/std", "frame-support/std", "frame-system/std", @@ -99,6 +97,7 @@ runtime-benchmarks = [ "pallet-utility/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "sp-trie", + "xcm/runtime-benchmarks", ] integrity-test = ["static_assertions"] test-helpers = ["bp-runtime/test-helpers", "sp-trie"] diff --git a/bridges/bin/runtime-common/src/integrity.rs b/bridges/bin/runtime-common/src/integrity.rs index 535f1a26e5e8989e59b36e65d1c1948111c81b33..0fc377090cfe18a4579b522eabeebd4b194b8478 100644 --- a/bridges/bin/runtime-common/src/integrity.rs +++ b/bridges/bin/runtime-common/src/integrity.rs @@ -21,16 +21,15 @@ use bp_header_chain::ChainWithGrandpa; use bp_messages::{ChainWithMessages, InboundLaneData, MessageNonce}; -use bp_runtime::Chain; +use bp_runtime::{AccountIdOf, Chain}; use codec::Encode; use frame_support::{storage::generator::StorageValue, traits::Get, weights::Weight}; use frame_system::limits; -use pallet_bridge_messages::WeightInfoExt as _; +use pallet_bridge_messages::{ThisChainOf, WeightInfoExt as _}; // Re-export to avoid include all dependencies everywhere. #[doc(hidden)] pub mod __private { - pub use bp_xcm_bridge_hub; pub use static_assertions; } @@ -66,9 +65,9 @@ macro_rules! assert_bridge_messages_pallet_types( with_bridged_chain_messages_instance: $i:path, this_chain: $this:path, bridged_chain: $bridged:path, + expected_payload_type: $payload:path, ) => { { - use $crate::integrity::__private::bp_xcm_bridge_hub::XcmAsPlainPayload; use $crate::integrity::__private::static_assertions::assert_type_eq_all; use bp_messages::ChainWithMessages; use bp_runtime::Chain; @@ -81,8 +80,8 @@ macro_rules! assert_bridge_messages_pallet_types( assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::ThisChain, $this); assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::BridgedChain, $bridged); - assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::OutboundPayload, XcmAsPlainPayload); - assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::InboundPayload, XcmAsPlainPayload); + assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::OutboundPayload, $payload); + assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::InboundPayload, $payload); } } ); @@ -97,6 +96,7 @@ macro_rules! assert_complete_bridge_types( with_bridged_chain_messages_instance: $mi:path, this_chain: $this:path, bridged_chain: $bridged:path, + expected_payload_type: $payload:path, ) => { $crate::assert_chain_types!(runtime: $r, this_chain: $this); $crate::assert_bridge_messages_pallet_types!( @@ -104,6 +104,7 @@ macro_rules! assert_complete_bridge_types( with_bridged_chain_messages_instance: $mi, this_chain: $this, bridged_chain: $bridged, + expected_payload_type: $payload, ); } ); @@ -364,8 +365,11 @@ pub fn check_message_lane_weights< ); // check that weights allow us to receive delivery confirmations - let max_incoming_inbound_lane_data_proof_size = - InboundLaneData::<()>::encoded_size_hint_u32(this_chain_max_unrewarded_relayers as _); + let max_incoming_inbound_lane_data_proof_size = InboundLaneData::< + AccountIdOf<ThisChainOf<T, MessagesPalletInstance>>, + >::encoded_size_hint_u32( + this_chain_max_unrewarded_relayers as _ + ); pallet_bridge_messages::ensure_able_to_receive_confirmation::<Weights<T, MessagesPalletInstance>>( C::max_extrinsic_size(), C::max_extrinsic_weight(), diff --git a/bridges/chains/chain-asset-hub-rococo/Cargo.toml b/bridges/chains/chain-asset-hub-rococo/Cargo.toml index 363a869048aae4d875d68a8ca46e30756cbc799f..4eb93ab52bc91813753692f03bdadc071b36b2e8 100644 --- a/bridges/chains/chain-asset-hub-rococo/Cargo.toml +++ b/bridges/chains/chain-asset-hub-rococo/Cargo.toml @@ -19,10 +19,14 @@ scale-info = { features = ["derive"], workspace = true } # Substrate Dependencies frame-support = { workspace = true } +sp-core = { workspace = true } # Bridge Dependencies bp-xcm-bridge-hub-router = { workspace = true } +# Polkadot dependencies +xcm = { workspace = true } + [features] default = ["std"] std = [ @@ -30,4 +34,6 @@ std = [ "codec/std", "frame-support/std", "scale-info/std", + "sp-core/std", + "xcm/std", ] diff --git a/bridges/chains/chain-asset-hub-rococo/src/lib.rs b/bridges/chains/chain-asset-hub-rococo/src/lib.rs index de2e9ae856d1f8756f0a2a6b9cae3da3e265e76e..4ff7b391acd050c081e75e7b65a43f375452ca6d 100644 --- a/bridges/chains/chain-asset-hub-rococo/src/lib.rs +++ b/bridges/chains/chain-asset-hub-rococo/src/lib.rs @@ -18,10 +18,13 @@ #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + use codec::{Decode, Encode}; use scale_info::TypeInfo; pub use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall; +use xcm::latest::prelude::*; /// `AssetHubRococo` Runtime `Call` enum. /// @@ -44,5 +47,27 @@ frame_support::parameter_types! { pub const XcmBridgeHubRouterTransactCallMaxWeight: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(200_000_000, 6144); } +/// Builds an (un)congestion XCM program with the `report_bridge_status` call for +/// `ToWestendXcmRouter`. +pub fn build_congestion_message<RuntimeCall>( + bridge_id: sp_core::H256, + is_congested: bool, +) -> alloc::vec::Vec<Instruction<RuntimeCall>> { + alloc::vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::Xcm, + fallback_max_weight: Some(XcmBridgeHubRouterTransactCallMaxWeight::get()), + call: Call::ToWestendXcmRouter(XcmBridgeHubRouterCall::report_bridge_status { + bridge_id, + is_congested, + }) + .encode() + .into(), + }, + ExpectTransactStatus(MaybeErrorCode::Success), + ] +} + /// Identifier of AssetHubRococo in the Rococo relay chain. pub const ASSET_HUB_ROCOCO_PARACHAIN_ID: u32 = 1000; diff --git a/bridges/chains/chain-asset-hub-westend/Cargo.toml b/bridges/chains/chain-asset-hub-westend/Cargo.toml index 430d9b6116cfc7fba648b96b1de6ef379f3e38f3..22071399f4d18f30d0f4173129dcbfc48aa95c8d 100644 --- a/bridges/chains/chain-asset-hub-westend/Cargo.toml +++ b/bridges/chains/chain-asset-hub-westend/Cargo.toml @@ -19,10 +19,14 @@ scale-info = { features = ["derive"], workspace = true } # Substrate Dependencies frame-support = { workspace = true } +sp-core = { workspace = true } # Bridge Dependencies bp-xcm-bridge-hub-router = { workspace = true } +# Polkadot dependencies +xcm = { workspace = true } + [features] default = ["std"] std = [ @@ -30,4 +34,6 @@ std = [ "codec/std", "frame-support/std", "scale-info/std", + "sp-core/std", + "xcm/std", ] diff --git a/bridges/chains/chain-asset-hub-westend/src/lib.rs b/bridges/chains/chain-asset-hub-westend/src/lib.rs index 9de1c88098942cdf7bd0684462a95ac3de412490..9d245e08f7cc83768e7f334bf8d60f2c242aeb62 100644 --- a/bridges/chains/chain-asset-hub-westend/src/lib.rs +++ b/bridges/chains/chain-asset-hub-westend/src/lib.rs @@ -18,10 +18,13 @@ #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + use codec::{Decode, Encode}; use scale_info::TypeInfo; pub use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall; +use xcm::latest::prelude::*; /// `AssetHubWestend` Runtime `Call` enum. /// @@ -44,5 +47,27 @@ frame_support::parameter_types! { pub const XcmBridgeHubRouterTransactCallMaxWeight: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(200_000_000, 6144); } +/// Builds an (un)congestion XCM program with the `report_bridge_status` call for +/// `ToRococoXcmRouter`. +pub fn build_congestion_message<RuntimeCall>( + bridge_id: sp_core::H256, + is_congested: bool, +) -> alloc::vec::Vec<Instruction<RuntimeCall>> { + alloc::vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::Xcm, + fallback_max_weight: Some(XcmBridgeHubRouterTransactCallMaxWeight::get()), + call: Call::ToRococoXcmRouter(XcmBridgeHubRouterCall::report_bridge_status { + bridge_id, + is_congested, + }) + .encode() + .into(), + }, + ExpectTransactStatus(MaybeErrorCode::Success), + ] +} + /// Identifier of AssetHubWestend in the Westend relay chain. pub const ASSET_HUB_WESTEND_PARACHAIN_ID: u32 = 1000; diff --git a/bridges/chains/chain-bridge-hub-cumulus/Cargo.toml b/bridges/chains/chain-bridge-hub-cumulus/Cargo.toml index 99ba721991ee90b04e708beb37dcca2f0aa5c96c..b9eb1d2d69c1290e3518766c167e6b827723158d 100644 --- a/bridges/chains/chain-bridge-hub-cumulus/Cargo.toml +++ b/bridges/chains/chain-bridge-hub-cumulus/Cargo.toml @@ -16,14 +16,14 @@ workspace = true [dependencies] # Bridge Dependencies -bp-polkadot-core = { workspace = true } bp-messages = { workspace = true } +bp-polkadot-core = { workspace = true } bp-runtime = { workspace = true } # Substrate Based Dependencies -frame-system = { workspace = true } frame-support = { workspace = true } +frame-system = { workspace = true } sp-api = { workspace = true } sp-std = { workspace = true } diff --git a/bridges/chains/chain-bridge-hub-kusama/Cargo.toml b/bridges/chains/chain-bridge-hub-kusama/Cargo.toml index 39f7b44daa5543b83108761d7acf5f72d5a8458f..136832d0199dca7e6cbad5ed298358c04c51468a 100644 --- a/bridges/chains/chain-bridge-hub-kusama/Cargo.toml +++ b/bridges/chains/chain-bridge-hub-kusama/Cargo.toml @@ -17,8 +17,8 @@ workspace = true # Bridge Dependencies bp-bridge-hub-cumulus = { workspace = true } -bp-runtime = { workspace = true } bp-messages = { workspace = true } +bp-runtime = { workspace = true } # Substrate Based Dependencies diff --git a/bridges/chains/chain-bridge-hub-polkadot/Cargo.toml b/bridges/chains/chain-bridge-hub-polkadot/Cargo.toml index 3b0ac96e7cd367cd373cdaedcbeaf299af7debe0..04ce144b790609f5fbbd10b3be15470b0419e568 100644 --- a/bridges/chains/chain-bridge-hub-polkadot/Cargo.toml +++ b/bridges/chains/chain-bridge-hub-polkadot/Cargo.toml @@ -18,8 +18,8 @@ workspace = true # Bridge Dependencies bp-bridge-hub-cumulus = { workspace = true } -bp-runtime = { workspace = true } bp-messages = { workspace = true } +bp-runtime = { workspace = true } # Substrate Based Dependencies diff --git a/bridges/chains/chain-bridge-hub-rococo/Cargo.toml b/bridges/chains/chain-bridge-hub-rococo/Cargo.toml index 23fbd9a2742f7ccf137bad53372e6e80d62a65c0..08a704add2b75b78d0f027a30d78657ff022cc22 100644 --- a/bridges/chains/chain-bridge-hub-rococo/Cargo.toml +++ b/bridges/chains/chain-bridge-hub-rococo/Cargo.toml @@ -18,8 +18,8 @@ codec = { features = ["derive"], workspace = true } # Bridge Dependencies bp-bridge-hub-cumulus = { workspace = true } -bp-runtime = { workspace = true } bp-messages = { workspace = true } +bp-runtime = { workspace = true } bp-xcm-bridge-hub = { workspace = true } # Substrate Based Dependencies diff --git a/bridges/chains/chain-bridge-hub-westend/Cargo.toml b/bridges/chains/chain-bridge-hub-westend/Cargo.toml index 61357e6aa6c848c32614524cf8a755511bad81d3..35932371d0a9887ceffdf26b5678b9f54ffddb69 100644 --- a/bridges/chains/chain-bridge-hub-westend/Cargo.toml +++ b/bridges/chains/chain-bridge-hub-westend/Cargo.toml @@ -18,8 +18,8 @@ codec = { features = ["derive"], workspace = true } # Bridge Dependencies bp-bridge-hub-cumulus = { workspace = true } -bp-runtime = { workspace = true } bp-messages = { workspace = true } +bp-runtime = { workspace = true } bp-xcm-bridge-hub = { workspace = true } # Substrate Based Dependencies diff --git a/bridges/chains/chain-polkadot-bulletin/src/lib.rs b/bridges/chains/chain-polkadot-bulletin/src/lib.rs index c5c18beb2cadc81ae6b7502ef0b194cdee4be535..070bc7b0ba3d3804a3982d81296b7d7d72b06a62 100644 --- a/bridges/chains/chain-polkadot-bulletin/src/lib.rs +++ b/bridges/chains/chain-polkadot-bulletin/src/lib.rs @@ -225,4 +225,4 @@ impl ChainWithMessages for PolkadotBulletin { } decl_bridge_finality_runtime_apis!(polkadot_bulletin, grandpa); -decl_bridge_messages_runtime_apis!(polkadot_bulletin, bp_messages::HashedLaneId); +decl_bridge_messages_runtime_apis!(polkadot_bulletin, bp_messages::LegacyLaneId); diff --git a/bridges/modules/beefy/Cargo.toml b/bridges/modules/beefy/Cargo.toml index cffc62d290828f032c5c57f27982e7f60f9b94ef..adbf79e28b5ace48e989b75649199551c2d96995 100644 --- a/bridges/modules/beefy/Cargo.toml +++ b/bridges/modules/beefy/Cargo.toml @@ -31,13 +31,13 @@ sp-runtime = { workspace = true } sp-std = { workspace = true } [dev-dependencies] -sp-consensus-beefy = { workspace = true, default-features = true } +bp-test-utils = { workspace = true, default-features = true } mmr-lib = { workspace = true } pallet-beefy-mmr = { workspace = true, default-features = true } pallet-mmr = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } +sp-consensus-beefy = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -bp-test-utils = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/modules/grandpa/Cargo.toml b/bridges/modules/grandpa/Cargo.toml index 6d1419ae5b030733ad9fb38a6a459ab7ce34f99f..fdca48ac6f079de441742f217dd3bde717b7686b 100644 --- a/bridges/modules/grandpa/Cargo.toml +++ b/bridges/modules/grandpa/Cargo.toml @@ -19,8 +19,8 @@ scale-info = { features = ["derive"], workspace = true } # Bridge Dependencies -bp-runtime = { workspace = true } bp-header-chain = { workspace = true } +bp-runtime = { workspace = true } # Substrate Dependencies diff --git a/bridges/modules/messages/Cargo.toml b/bridges/modules/messages/Cargo.toml index 9df318587e3895ee8d040a586f4d70770fc64f0f..6248c9e65e1678fe9141366003155a59996b46c5 100644 --- a/bridges/modules/messages/Cargo.toml +++ b/bridges/modules/messages/Cargo.toml @@ -33,8 +33,8 @@ bp-runtime = { features = ["test-helpers"], workspace = true } bp-test-utils = { workspace = true } pallet-balances = { workspace = true } pallet-bridge-grandpa = { workspace = true } -sp-io = { workspace = true } sp-core = { workspace = true } +sp-io = { workspace = true } [features] default = ["std"] diff --git a/bridges/modules/messages/README.md b/bridges/modules/messages/README.md index a78c8680249851b86fe09c42d66d36914ca8dd80..7d9a23b4ba1457c19e41543ff53031c686c66eeb 100644 --- a/bridges/modules/messages/README.md +++ b/bridges/modules/messages/README.md @@ -13,7 +13,7 @@ module and the final goal is to hand message to the message dispatch mechanism. ## Overview -Message lane is an unidirectional channel, where messages are sent from source chain to the target chain. At the same +Message lane is a unidirectional channel, where messages are sent from source chain to the target chain. At the same time, a single instance of messages module supports both outbound lanes and inbound lanes. So the chain where the module is deployed (this chain), may act as a source chain for outbound messages (heading to a bridged chain) and as a target chain for inbound messages (coming from a bridged chain). diff --git a/bridges/modules/messages/src/lib.rs b/bridges/modules/messages/src/lib.rs index af14257db99c1cf0882f4f1a5a94329b194d1977..61763186cb02167a7e7bfc3d9fc1059a24728c5d 100644 --- a/bridges/modules/messages/src/lib.rs +++ b/bridges/modules/messages/src/lib.rs @@ -230,8 +230,8 @@ pub mod pallet { // why do we need to know the weight of this (`receive_messages_proof`) call? Because // we may want to return some funds for not-dispatching (or partially dispatching) some // messages to the call origin (relayer). And this is done by returning actual weight - // from the call. But we only know dispatch weight of every messages. So to refund - // relayer because we have not dispatched Message, we need to: + // from the call. But we only know dispatch weight of every message. So to refund + // relayer because we have not dispatched message, we need to: // // ActualWeight = DeclaredWeight - Message.DispatchWeight // diff --git a/bridges/modules/messages/src/tests/mock.rs b/bridges/modules/messages/src/tests/mock.rs index 2935ebd69610f6138c18c22d2c548e2f670487a2..8eebdf3a50817dd11936f2f3958272e4d0781c90 100644 --- a/bridges/modules/messages/src/tests/mock.rs +++ b/bridges/modules/messages/src/tests/mock.rs @@ -461,9 +461,12 @@ pub fn inbound_unrewarded_relayers_state(lane: TestLaneIdType) -> UnrewardedRela /// Return test externalities to use in tests. pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap(); - pallet_balances::GenesisConfig::<TestRuntime> { balances: vec![(ENDOWED_ACCOUNT, 1_000_000)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig::<TestRuntime> { + balances: vec![(ENDOWED_ACCOUNT, 1_000_000)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); sp_io::TestExternalities::new(t) } diff --git a/bridges/modules/relayers/Cargo.toml b/bridges/modules/relayers/Cargo.toml index 04e7b52ed86c4dbeebc2f6b26e7ddb54ebba155a..97ed61a9004e8be23334960fcebc8c8fc8a387a3 100644 --- a/bridges/modules/relayers/Cargo.toml +++ b/bridges/modules/relayers/Cargo.toml @@ -34,15 +34,15 @@ sp-runtime = { workspace = true } sp-std = { workspace = true } [dev-dependencies] -bp-runtime = { workspace = true } -pallet-balances = { workspace = true, default-features = true } -sp-io = { workspace = true } -sp-runtime = { workspace = true } bp-parachains = { workspace = true } bp-polkadot-core = { workspace = true } +bp-runtime = { workspace = true } bp-test-utils = { workspace = true } +pallet-balances = { workspace = true, default-features = true } pallet-utility = { workspace = true } sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } [features] default = ["std"] diff --git a/bridges/modules/relayers/src/extension/mod.rs b/bridges/modules/relayers/src/extension/mod.rs index 34d280d26d6ef018b9da03af77356d64954fceec..d562ed9bcd0e87ed5bb910454ca57b0112fe25a9 100644 --- a/bridges/modules/relayers/src/extension/mod.rs +++ b/bridges/modules/relayers/src/extension/mod.rs @@ -129,7 +129,7 @@ pub struct BridgeRelayersTransactionExtension<Runtime, Config, LaneId>( impl<R, C, LaneId> BridgeRelayersTransactionExtension<R, C, LaneId> where Self: 'static + Send + Sync, - R: RelayersConfig<LaneId = LaneId> + R: RelayersConfig<C::BridgeRelayersPalletInstance, LaneId = LaneId> + BridgeMessagesConfig<C::BridgeMessagesPalletInstance, LaneId = LaneId> + TransactionPaymentConfig, C: ExtensionConfig<Runtime = R, LaneId = LaneId>, @@ -250,7 +250,7 @@ where // let's also replace the weight of slashing relayer with the weight of rewarding relayer if call_info.is_receive_messages_proof_call() { post_info_weight = post_info_weight.saturating_sub( - <R as RelayersConfig>::WeightInfo::extra_weight_of_successful_receive_messages_proof_call(), + <R as RelayersConfig<C::BridgeRelayersPalletInstance>>::WeightInfo::extra_weight_of_successful_receive_messages_proof_call(), ); } @@ -278,7 +278,7 @@ impl<R, C, LaneId> TransactionExtension<R::RuntimeCall> for BridgeRelayersTransactionExtension<R, C, LaneId> where Self: 'static + Send + Sync, - R: RelayersConfig<LaneId = LaneId> + R: RelayersConfig<C::BridgeRelayersPalletInstance, LaneId = LaneId> + BridgeMessagesConfig<C::BridgeMessagesPalletInstance, LaneId = LaneId> + TransactionPaymentConfig, C: ExtensionConfig<Runtime = R, LaneId = LaneId>, @@ -326,7 +326,9 @@ where }; // we only boost priority if relayer has staked required balance - if !RelayersPallet::<R>::is_registration_active(&data.relayer) { + if !RelayersPallet::<R, C::BridgeRelayersPalletInstance>::is_registration_active( + &data.relayer, + ) { return Ok((Default::default(), Some(data), origin)) } @@ -382,7 +384,11 @@ where match call_result { RelayerAccountAction::None => (), RelayerAccountAction::Reward(relayer, reward_account, reward) => { - RelayersPallet::<R>::register_relayer_reward(reward_account, &relayer, reward); + RelayersPallet::<R, C::BridgeRelayersPalletInstance>::register_relayer_reward( + reward_account, + &relayer, + reward, + ); log::trace!( target: LOG_TARGET, @@ -394,7 +400,7 @@ where ); }, RelayerAccountAction::Slash(relayer, slash_account) => - RelayersPallet::<R>::slash_and_deregister( + RelayersPallet::<R, C::BridgeRelayersPalletInstance>::slash_and_deregister( &relayer, ExplicitOrAccountParams::Params(slash_account), ), diff --git a/bridges/modules/xcm-bridge-hub-router/Cargo.toml b/bridges/modules/xcm-bridge-hub-router/Cargo.toml index 55824f6a7fe7bf89a872fbef503a574c02c7f9ae..b0286938f36dac1ddb8871510413ed6dccbcbb2e 100644 --- a/bridges/modules/xcm-bridge-hub-router/Cargo.toml +++ b/bridges/modules/xcm-bridge-hub-router/Cargo.toml @@ -56,6 +56,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs b/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs index 3c4a10f82e7dff12ae7d8f7b300bc5d66839073c..ff06a1e3c8c5ac67aa7b36d4606721062700f1ea 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs @@ -18,9 +18,9 @@ #![cfg(feature = "runtime-benchmarks")] -use crate::{DeliveryFeeFactor, MINIMAL_DELIVERY_FEE_FACTOR}; +use crate::{Bridge, BridgeState, Call, MINIMAL_DELIVERY_FEE_FACTOR}; use frame_benchmarking::{benchmarks_instance_pallet, BenchmarkError}; -use frame_support::traits::{Get, Hooks}; +use frame_support::traits::{EnsureOrigin, Get, Hooks, UnfilteredDispatchable}; use sp_runtime::traits::Zero; use xcm::prelude::*; @@ -45,16 +45,35 @@ pub trait Config<I: 'static>: crate::Config<I> { benchmarks_instance_pallet! { on_initialize_when_non_congested { - DeliveryFeeFactor::<T, I>::put(MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR); + Bridge::<T, I>::put(BridgeState { + is_congested: false, + delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR, + }); }: { crate::Pallet::<T, I>::on_initialize(Zero::zero()) } on_initialize_when_congested { - DeliveryFeeFactor::<T, I>::put(MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR); + Bridge::<T, I>::put(BridgeState { + is_congested: false, + delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR, + }); let _ = T::ensure_bridged_target_destination()?; T::make_congested(); }: { crate::Pallet::<T, I>::on_initialize(Zero::zero()) } + + report_bridge_status { + Bridge::<T, I>::put(BridgeState::default()); + + let origin: T::RuntimeOrigin = T::BridgeHubOrigin::try_successful_origin().expect("expected valid BridgeHubOrigin"); + let bridge_id = Default::default(); + let is_congested = true; + + let call = Call::<T, I>::report_bridge_status { bridge_id, is_congested }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert!(Bridge::<T, I>::get().is_congested); + } } diff --git a/bridges/modules/xcm-bridge-hub-router/src/lib.rs b/bridges/modules/xcm-bridge-hub-router/src/lib.rs index fe8f5a2efdfb8ad2f166941a841def915bc103b0..7361696faba71fd68c5d2b9fb4982a229ec48fd0 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/lib.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/lib.rs @@ -30,9 +30,10 @@ #![cfg_attr(not(feature = "std"), no_std)] -pub use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; +pub use bp_xcm_bridge_hub_router::{BridgeState, XcmChannelStatusProvider}; use codec::Encode; use frame_support::traits::Get; +use sp_core::H256; use sp_runtime::{FixedPointNumber, FixedU128, Saturating}; use sp_std::vec::Vec; use xcm::prelude::*; @@ -98,6 +99,8 @@ pub mod pallet { /// Checks the XCM version for the destination. type DestinationVersion: GetVersion; + /// Origin of the sibling bridge hub that is allowed to report bridge status. + type BridgeHubOrigin: EnsureOrigin<Self::RuntimeOrigin>; /// Actual message sender (`HRMP` or `DMP`) to the sibling bridge hub location. type ToBridgeHubSender: SendXcm; /// Local XCM channel manager. @@ -120,95 +123,112 @@ pub mod pallet { return T::WeightInfo::on_initialize_when_congested() } + // if bridge has reported congestion, we don't change anything + let mut bridge = Self::bridge(); + if bridge.is_congested { + return T::WeightInfo::on_initialize_when_congested() + } + // if we can't decrease the delivery fee factor anymore, we don't change anything - let mut delivery_fee_factor = Self::delivery_fee_factor(); - if delivery_fee_factor == MINIMAL_DELIVERY_FEE_FACTOR { + if bridge.delivery_fee_factor == MINIMAL_DELIVERY_FEE_FACTOR { return T::WeightInfo::on_initialize_when_congested() } - let previous_factor = delivery_fee_factor; - delivery_fee_factor = - MINIMAL_DELIVERY_FEE_FACTOR.max(delivery_fee_factor / EXPONENTIAL_FEE_BASE); + let previous_factor = bridge.delivery_fee_factor; + bridge.delivery_fee_factor = + MINIMAL_DELIVERY_FEE_FACTOR.max(bridge.delivery_fee_factor / EXPONENTIAL_FEE_BASE); + log::info!( target: LOG_TARGET, "Bridge channel is uncongested. Decreased fee factor from {} to {}", previous_factor, - delivery_fee_factor, + bridge.delivery_fee_factor, ); Self::deposit_event(Event::DeliveryFeeFactorDecreased { - new_value: delivery_fee_factor, + new_value: bridge.delivery_fee_factor, }); - DeliveryFeeFactor::<T, I>::put(delivery_fee_factor); + Bridge::<T, I>::put(bridge); T::WeightInfo::on_initialize_when_non_congested() } } - /// Initialization value for the delivery fee factor. - #[pallet::type_value] - pub fn InitialFactor() -> FixedU128 { - MINIMAL_DELIVERY_FEE_FACTOR + #[pallet::call] + impl<T: Config<I>, I: 'static> Pallet<T, I> { + /// Notification about congested bridge queue. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::report_bridge_status())] + pub fn report_bridge_status( + origin: OriginFor<T>, + // this argument is not currently used, but to ease future migration, we'll keep it + // here + bridge_id: H256, + is_congested: bool, + ) -> DispatchResult { + let _ = T::BridgeHubOrigin::ensure_origin(origin)?; + + log::info!( + target: LOG_TARGET, + "Received bridge status from {:?}: congested = {}", + bridge_id, + is_congested, + ); + + Bridge::<T, I>::mutate(|bridge| { + bridge.is_congested = is_congested; + }); + Ok(()) + } } - /// The number to multiply the base delivery fee by. + /// Bridge that we are using. /// - /// This factor is shared by all bridges, served by this pallet. For example, if this - /// chain (`Config::UniversalLocation`) opens two bridges ( - /// `X2(GlobalConsensus(Config::BridgedNetworkId::get()), Parachain(1000))` and - /// `X2(GlobalConsensus(Config::BridgedNetworkId::get()), Parachain(2000))`), then they - /// both will be sharing the same fee factor. This is because both bridges are sharing - /// the same local XCM channel with the child/sibling bridge hub, which we are using - /// to detect congestion: - /// - /// ```nocompile - /// ThisChain --- Local XCM channel --> Sibling Bridge Hub ------ - /// | | - /// | | - /// | | - /// Lane1 Lane2 - /// | | - /// | | - /// | | - /// \ / | - /// Parachain1 <-- Local XCM channel --- Remote Bridge Hub <------ - /// | - /// | - /// Parachain1 <-- Local XCM channel --------- - /// ``` - /// - /// If at least one of other channels is congested, the local XCM channel with sibling - /// bridge hub eventually becomes congested too. And we have no means to detect - which - /// bridge exactly causes the congestion. So the best solution here is not to make - /// any differences between all bridges, started by this chain. + /// **bridges-v1** assumptions: all outbound messages through this router are using single lane + /// and to single remote consensus. If there is some other remote consensus that uses the same + /// bridge hub, the separate pallet instance shall be used, In `v2` we'll have all required + /// primitives (lane-id aka bridge-id, derived from XCM locations) to support multiple bridges + /// by the same pallet instance. #[pallet::storage] - #[pallet::getter(fn delivery_fee_factor)] - pub type DeliveryFeeFactor<T: Config<I>, I: 'static = ()> = - StorageValue<_, FixedU128, ValueQuery, InitialFactor>; + #[pallet::getter(fn bridge)] + pub type Bridge<T: Config<I>, I: 'static = ()> = StorageValue<_, BridgeState, ValueQuery>; impl<T: Config<I>, I: 'static> Pallet<T, I> { /// Called when new message is sent (queued to local outbound XCM queue) over the bridge. pub(crate) fn on_message_sent_to_bridge(message_size: u32) { - // if outbound channel is not congested, do nothing - if !T::LocalXcmChannelManager::is_congested(&T::SiblingBridgeHubLocation::get()) { - return - } + log::trace!( + target: LOG_TARGET, + "on_message_sent_to_bridge - message_size: {message_size:?}", + ); + let _ = Bridge::<T, I>::try_mutate(|bridge| { + let is_channel_with_bridge_hub_congested = + T::LocalXcmChannelManager::is_congested(&T::SiblingBridgeHubLocation::get()); + let is_bridge_congested = bridge.is_congested; + + // if outbound queue is not congested AND bridge has not reported congestion, do + // nothing + if !is_channel_with_bridge_hub_congested && !is_bridge_congested { + return Err(()) + } + + // ok - we need to increase the fee factor, let's do that + let message_size_factor = FixedU128::from_u32(message_size.saturating_div(1024)) + .saturating_mul(MESSAGE_SIZE_FEE_BASE); + let total_factor = EXPONENTIAL_FEE_BASE.saturating_add(message_size_factor); + let previous_factor = bridge.delivery_fee_factor; + bridge.delivery_fee_factor = + bridge.delivery_fee_factor.saturating_mul(total_factor); - // ok - we need to increase the fee factor, let's do that - let message_size_factor = FixedU128::from_u32(message_size.saturating_div(1024)) - .saturating_mul(MESSAGE_SIZE_FEE_BASE); - let total_factor = EXPONENTIAL_FEE_BASE.saturating_add(message_size_factor); - DeliveryFeeFactor::<T, I>::mutate(|f| { - let previous_factor = *f; - *f = f.saturating_mul(total_factor); log::info!( target: LOG_TARGET, "Bridge channel is congested. Increased fee factor from {} to {}", previous_factor, - f, + bridge.delivery_fee_factor, ); - Self::deposit_event(Event::DeliveryFeeFactorIncreased { new_value: *f }); - *f + Self::deposit_event(Event::DeliveryFeeFactorIncreased { + new_value: bridge.delivery_fee_factor, + }); + Ok(()) }); } } @@ -310,9 +330,9 @@ impl<T: Config<I>, I: 'static> ExporterFor for Pallet<T, I> { let message_size = message.encoded_size(); let message_fee = (message_size as u128).saturating_mul(T::ByteFee::get()); let fee_sum = base_fee.saturating_add(message_fee); - - let fee_factor = Self::delivery_fee_factor(); + let fee_factor = Self::bridge().delivery_fee_factor; let fee = fee_factor.saturating_mul_int(fee_sum); + let fee = if fee > 0 { Some((T::FeeAsset::get(), fee).into()) } else { None }; log::info!( @@ -427,24 +447,47 @@ mod tests { use frame_system::{EventRecord, Phase}; use sp_runtime::traits::One; + fn congested_bridge(delivery_fee_factor: FixedU128) -> BridgeState { + BridgeState { is_congested: true, delivery_fee_factor } + } + + fn uncongested_bridge(delivery_fee_factor: FixedU128) -> BridgeState { + BridgeState { is_congested: false, delivery_fee_factor } + } + #[test] fn initial_fee_factor_is_one() { run_test(|| { - assert_eq!(DeliveryFeeFactor::<TestRuntime, ()>::get(), MINIMAL_DELIVERY_FEE_FACTOR); + assert_eq!( + Bridge::<TestRuntime, ()>::get(), + uncongested_bridge(MINIMAL_DELIVERY_FEE_FACTOR), + ); }) } #[test] fn fee_factor_is_not_decreased_from_on_initialize_when_xcm_channel_is_congested() { run_test(|| { - DeliveryFeeFactor::<TestRuntime, ()>::put(FixedU128::from_rational(125, 100)); + Bridge::<TestRuntime, ()>::put(uncongested_bridge(FixedU128::from_rational(125, 100))); TestLocalXcmChannelManager::make_congested(&SiblingBridgeHubLocation::get()); // it should not decrease, because queue is congested - let old_delivery_fee_factor = XcmBridgeHubRouter::delivery_fee_factor(); + let old_delivery = XcmBridgeHubRouter::bridge(); XcmBridgeHubRouter::on_initialize(One::one()); - assert_eq!(XcmBridgeHubRouter::delivery_fee_factor(), old_delivery_fee_factor); + assert_eq!(XcmBridgeHubRouter::bridge(), old_delivery); + assert_eq!(System::events(), vec![]); + }) + } + + #[test] + fn fee_factor_is_not_decreased_from_on_initialize_when_bridge_has_reported_congestion() { + run_test(|| { + Bridge::<TestRuntime, ()>::put(congested_bridge(FixedU128::from_rational(125, 100))); + // it should not decrease, because bridge congested + let old_bridge = XcmBridgeHubRouter::bridge(); + XcmBridgeHubRouter::on_initialize(One::one()); + assert_eq!(XcmBridgeHubRouter::bridge(), old_bridge); assert_eq!(System::events(), vec![]); }) } @@ -453,16 +496,19 @@ mod tests { fn fee_factor_is_decreased_from_on_initialize_when_xcm_channel_is_uncongested() { run_test(|| { let initial_fee_factor = FixedU128::from_rational(125, 100); - DeliveryFeeFactor::<TestRuntime, ()>::put(initial_fee_factor); + Bridge::<TestRuntime, ()>::put(uncongested_bridge(initial_fee_factor)); - // it shold eventually decreased to one - while XcmBridgeHubRouter::delivery_fee_factor() > MINIMAL_DELIVERY_FEE_FACTOR { + // it should eventually decrease to one + while XcmBridgeHubRouter::bridge().delivery_fee_factor > MINIMAL_DELIVERY_FEE_FACTOR { XcmBridgeHubRouter::on_initialize(One::one()); } - // verify that it doesn't decreases anymore + // verify that it doesn't decrease anymore XcmBridgeHubRouter::on_initialize(One::one()); - assert_eq!(XcmBridgeHubRouter::delivery_fee_factor(), MINIMAL_DELIVERY_FEE_FACTOR); + assert_eq!( + XcmBridgeHubRouter::bridge(), + uncongested_bridge(MINIMAL_DELIVERY_FEE_FACTOR) + ); // check emitted event let first_system_event = System::events().first().cloned(); @@ -582,7 +628,7 @@ mod tests { // but when factor is larger than one, it increases the fee, so it becomes: // `(BASE_FEE + BYTE_FEE * msg_size) * F + HRMP_FEE` let factor = FixedU128::from_rational(125, 100); - DeliveryFeeFactor::<TestRuntime, ()>::put(factor); + Bridge::<TestRuntime, ()>::put(uncongested_bridge(factor)); let expected_fee = (FixedU128::saturating_from_integer(BASE_FEE + BYTE_FEE * (msg_size as u128)) * factor) @@ -598,7 +644,7 @@ mod tests { #[test] fn sent_message_doesnt_increase_factor_if_queue_is_uncongested() { run_test(|| { - let old_delivery_fee_factor = XcmBridgeHubRouter::delivery_fee_factor(); + let old_bridge = XcmBridgeHubRouter::bridge(); assert_eq!( send_xcm::<XcmBridgeHubRouter>( Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]), @@ -609,7 +655,7 @@ mod tests { ); assert!(TestToBridgeHubSender::is_message_sent()); - assert_eq!(old_delivery_fee_factor, XcmBridgeHubRouter::delivery_fee_factor()); + assert_eq!(old_bridge, XcmBridgeHubRouter::bridge()); assert_eq!(System::events(), vec![]); }); @@ -620,7 +666,39 @@ mod tests { run_test(|| { TestLocalXcmChannelManager::make_congested(&SiblingBridgeHubLocation::get()); - let old_delivery_fee_factor = XcmBridgeHubRouter::delivery_fee_factor(); + let old_bridge = XcmBridgeHubRouter::bridge(); + assert_ok!(send_xcm::<XcmBridgeHubRouter>( + Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]), + vec![ClearOrigin].into(), + ) + .map(drop)); + + assert!(TestToBridgeHubSender::is_message_sent()); + assert!( + old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor + ); + + // check emitted event + let first_system_event = System::events().first().cloned(); + assert!(matches!( + first_system_event, + Some(EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::XcmBridgeHubRouter( + Event::DeliveryFeeFactorIncreased { .. } + ), + .. + }) + )); + }); + } + + #[test] + fn sent_message_increases_factor_if_bridge_has_reported_congestion() { + run_test(|| { + Bridge::<TestRuntime, ()>::put(congested_bridge(MINIMAL_DELIVERY_FEE_FACTOR)); + + let old_bridge = XcmBridgeHubRouter::bridge(); assert_ok!(send_xcm::<XcmBridgeHubRouter>( Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]), vec![ClearOrigin].into(), @@ -628,7 +706,9 @@ mod tests { .map(drop)); assert!(TestToBridgeHubSender::is_message_sent()); - assert!(old_delivery_fee_factor < XcmBridgeHubRouter::delivery_fee_factor()); + assert!( + old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor + ); // check emitted event let first_system_event = System::events().first().cloned(); diff --git a/bridges/modules/xcm-bridge-hub-router/src/mock.rs b/bridges/modules/xcm-bridge-hub-router/src/mock.rs index 095572883920fce371536d8575df77b514a4b148..ac642e108c2ae615397d046a6be9a7dd25f5621c 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/mock.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/mock.rs @@ -80,6 +80,7 @@ impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime { type DestinationVersion = LatestOrNoneForLocationVersionChecker<Equals<UnknownXcmVersionForRoutableLocation>>; + type BridgeHubOrigin = frame_system::EnsureRoot<u64>; type ToBridgeHubSender = TestToBridgeHubSender; type LocalXcmChannelManager = TestLocalXcmChannelManager; diff --git a/bridges/modules/xcm-bridge-hub-router/src/weights.rs b/bridges/modules/xcm-bridge-hub-router/src/weights.rs index d9a0426fecaf8de6858b785222a50d0e4291f0af..8f5012c9de26beb1626775961a95ff32210b35d7 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/weights.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/weights.rs @@ -52,6 +52,7 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn on_initialize_when_non_congested() -> Weight; fn on_initialize_when_congested() -> Weight; + fn report_bridge_status() -> Weight; } /// Weights for `pallet_xcm_bridge_hub_router` that are generated using one of the Bridge testnets. @@ -85,6 +86,19 @@ impl<T: frame_system::Config> WeightInfo for BridgeWeight<T> { // Minimum execution time: 4_239 nanoseconds. Weight::from_parts(4_383_000, 3547).saturating_add(T::DbWeight::get().reads(1_u64)) } + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + fn report_bridge_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `53` + // Estimated: `1502` + // Minimum execution time: 10_427 nanoseconds. + Weight::from_parts(10_682_000, 1502) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } } // For backwards compatibility and tests @@ -120,4 +134,17 @@ impl WeightInfo for () { // Minimum execution time: 4_239 nanoseconds. Weight::from_parts(4_383_000, 3547).saturating_add(RocksDbWeight::get().reads(1_u64)) } + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + fn report_bridge_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `53` + // Estimated: `1502` + // Minimum execution time: 10_427 nanoseconds. + Weight::from_parts(10_682_000, 1502) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } } diff --git a/bridges/modules/xcm-bridge-hub/Cargo.toml b/bridges/modules/xcm-bridge-hub/Cargo.toml index fe58b910a94ef99547bed8f2956a6084aea43e2d..b5e3658744436ca6d226617eb30044f782069d26 100644 --- a/bridges/modules/xcm-bridge-hub/Cargo.toml +++ b/bridges/modules/xcm-bridge-hub/Cargo.toml @@ -34,12 +34,13 @@ xcm-builder = { workspace = true } xcm-executor = { workspace = true } [dev-dependencies] -pallet-balances = { workspace = true } -sp-io = { workspace = true } -bp-runtime = { workspace = true } bp-header-chain = { workspace = true } +bp-runtime = { workspace = true } +bp-xcm-bridge-hub-router = { workspace = true } +pallet-balances = { workspace = true } pallet-xcm-bridge-hub-router = { workspace = true } polkadot-parachain-primitives = { workspace = true } +sp-io = { workspace = true } [features] default = ["std"] @@ -47,6 +48,7 @@ std = [ "bp-header-chain/std", "bp-messages/std", "bp-runtime/std", + "bp-xcm-bridge-hub-router/std", "bp-xcm-bridge-hub/std", "codec/std", "frame-support/std", @@ -75,6 +77,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/bridges/modules/xcm-bridge-hub/src/exporter.rs b/bridges/modules/xcm-bridge-hub/src/exporter.rs index 5afb9f36bc9414bde920eec3d3e84bc7487f711d..93b6093b42af5ab42794f92bf1a7683d32e60317 100644 --- a/bridges/modules/xcm-bridge-hub/src/exporter.rs +++ b/bridges/modules/xcm-bridge-hub/src/exporter.rs @@ -364,7 +364,7 @@ mod tests { use bp_runtime::RangeInclusiveExt; use bp_xcm_bridge_hub::{Bridge, BridgeLocations, BridgeState}; - use frame_support::assert_ok; + use frame_support::{assert_ok, traits::EnsureOrigin}; use pallet_bridge_messages::InboundLaneStorage; use xcm_builder::{NetworkExportTable, UnpaidRemoteExporter}; use xcm_executor::traits::{export_xcm, ConvertLocation}; @@ -381,9 +381,8 @@ mod tests { BridgedUniversalDestination::get() } - fn open_lane() -> (BridgeLocations, TestLaneIdType) { + fn open_lane(origin: RuntimeOrigin) -> (BridgeLocations, TestLaneIdType) { // open expected outbound lane - let origin = OpenBridgeOrigin::sibling_parachain_origin(); let with = bridged_asset_hub_universal_location(); let locations = XcmOverBridge::bridge_locations_from_origin(origin, Box::new(with.into())).unwrap(); @@ -439,7 +438,7 @@ mod tests { } fn open_lane_and_send_regular_message() -> (BridgeId, TestLaneIdType) { - let (locations, lane_id) = open_lane(); + let (locations, lane_id) = open_lane(OpenBridgeOrigin::sibling_parachain_origin()); // now let's try to enqueue message using our `ExportXcm` implementation export_xcm::<XcmOverBridge>( @@ -473,7 +472,7 @@ mod tests { fn exporter_does_not_suspend_the_bridge_if_outbound_bridge_queue_is_not_congested() { run_test(|| { let (bridge_id, _) = open_lane_and_send_regular_message(); - assert!(!TestLocalXcmChannelManager::is_bridge_suspened()); + assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); }); } @@ -490,7 +489,7 @@ mod tests { } open_lane_and_send_regular_message(); - assert!(!TestLocalXcmChannelManager::is_bridge_suspened()); + assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id)); }); } @@ -502,11 +501,11 @@ mod tests { open_lane_and_send_regular_message(); } - assert!(!TestLocalXcmChannelManager::is_bridge_suspened()); + assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); open_lane_and_send_regular_message(); - assert!(TestLocalXcmChannelManager::is_bridge_suspened()); + assert!(TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended); }); } @@ -523,7 +522,7 @@ mod tests { OUTBOUND_LANE_UNCONGESTED_THRESHOLD + 1, ); - assert!(!TestLocalXcmChannelManager::is_bridge_resumed()); + assert!(!TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended); }); } @@ -537,7 +536,7 @@ mod tests { OUTBOUND_LANE_UNCONGESTED_THRESHOLD, ); - assert!(!TestLocalXcmChannelManager::is_bridge_resumed()); + assert!(!TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); }); } @@ -554,7 +553,7 @@ mod tests { OUTBOUND_LANE_UNCONGESTED_THRESHOLD, ); - assert!(TestLocalXcmChannelManager::is_bridge_resumed()); + assert!(TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); }); } @@ -648,7 +647,10 @@ mod tests { let dest = Location::new(2, BridgedUniversalDestination::get()); // open bridge - let (_, expected_lane_id) = open_lane(); + let origin = OpenBridgeOrigin::sibling_parachain_origin(); + let origin_as_location = + OpenBridgeOriginOf::<TestRuntime, ()>::try_origin(origin.clone()).unwrap(); + let (_, expected_lane_id) = open_lane(origin); // check before - no messages assert_eq!( @@ -662,18 +664,24 @@ mod tests { ); // send `ExportMessage(message)` by `UnpaidRemoteExporter`. - TestExportXcmWithXcmOverBridge::set_origin_for_execute(SiblingLocation::get()); + ExecuteXcmOverSendXcm::set_origin_for_execute(origin_as_location); assert_ok!(send_xcm::< UnpaidRemoteExporter< NetworkExportTable<BridgeTable>, - TestExportXcmWithXcmOverBridge, + ExecuteXcmOverSendXcm, UniversalLocation, >, >(dest.clone(), Xcm::<()>::default())); + // we need to set `UniversalLocation` for `sibling_parachain_origin` for + // `XcmOverBridgeWrappedWithExportMessageRouterInstance`. + ExportMessageOriginUniversalLocation::set(Some(SiblingUniversalLocation::get())); // send `ExportMessage(message)` by `pallet_xcm_bridge_hub_router`. - TestExportXcmWithXcmOverBridge::set_origin_for_execute(SiblingLocation::get()); - assert_ok!(send_xcm::<XcmOverBridgeRouter>(dest.clone(), Xcm::<()>::default())); + ExecuteXcmOverSendXcm::set_origin_for_execute(SiblingLocation::get()); + assert_ok!(send_xcm::<XcmOverBridgeWrappedWithExportMessageRouter>( + dest.clone(), + Xcm::<()>::default() + )); // check after - a message ready to be relayed assert_eq!( @@ -765,7 +773,7 @@ mod tests { ); // ok - let _ = open_lane(); + let _ = open_lane(OpenBridgeOrigin::sibling_parachain_origin()); let mut dest_wrapper = Some(bridged_relative_destination()); assert_ok!(XcmOverBridge::validate( BridgedRelayNetwork::get(), @@ -780,4 +788,77 @@ mod tests { assert_eq!(None, dest_wrapper); }); } + + #[test] + fn congestion_with_pallet_xcm_bridge_hub_router_works() { + run_test(|| { + // valid routable destination + let dest = Location::new(2, BridgedUniversalDestination::get()); + + fn router_bridge_state() -> pallet_xcm_bridge_hub_router::BridgeState { + pallet_xcm_bridge_hub_router::Bridge::< + TestRuntime, + XcmOverBridgeWrappedWithExportMessageRouterInstance, + >::get() + } + + // open two bridges + let origin = OpenBridgeOrigin::sibling_parachain_origin(); + let origin_as_location = + OpenBridgeOriginOf::<TestRuntime, ()>::try_origin(origin.clone()).unwrap(); + let (bridge_1, expected_lane_id_1) = open_lane(origin); + + // we need to set `UniversalLocation` for `sibling_parachain_origin` for + // `XcmOverBridgeWrappedWithExportMessageRouterInstance`. + ExportMessageOriginUniversalLocation::set(Some(SiblingUniversalLocation::get())); + + // check before + // bridges are opened + assert_eq!( + XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state, + BridgeState::Opened + ); + + // the router is uncongested + assert!(!router_bridge_state().is_congested); + assert!(!TestLocalXcmChannelManager::is_bridge_suspended(bridge_1.bridge_id())); + assert!(!TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id())); + + // make bridges congested with sending too much messages + for _ in 1..(OUTBOUND_LANE_CONGESTED_THRESHOLD + 2) { + // send `ExportMessage(message)` by `pallet_xcm_bridge_hub_router`. + ExecuteXcmOverSendXcm::set_origin_for_execute(origin_as_location.clone()); + assert_ok!(send_xcm::<XcmOverBridgeWrappedWithExportMessageRouter>( + dest.clone(), + Xcm::<()>::default() + )); + } + + // checks after + // bridges are suspended + assert_eq!( + XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state, + BridgeState::Suspended, + ); + // the router is congested + assert!(router_bridge_state().is_congested); + assert!(TestLocalXcmChannelManager::is_bridge_suspended(bridge_1.bridge_id())); + assert!(!TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id())); + + // make bridges uncongested to trigger resume signal + XcmOverBridge::on_bridge_messages_delivered( + expected_lane_id_1, + OUTBOUND_LANE_UNCONGESTED_THRESHOLD, + ); + + // bridge is again opened + assert_eq!( + XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state, + BridgeState::Opened + ); + // the router is uncongested + assert!(!router_bridge_state().is_congested); + assert!(TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id())); + }) + } } diff --git a/bridges/modules/xcm-bridge-hub/src/lib.rs b/bridges/modules/xcm-bridge-hub/src/lib.rs index 1b2536598a202892052d395284d88c682448c801..1633e99d7f303abe0e16c12a42d7e98be4fe23ff 100644 --- a/bridges/modules/xcm-bridge-hub/src/lib.rs +++ b/bridges/modules/xcm-bridge-hub/src/lib.rs @@ -145,8 +145,10 @@ use bp_messages::{LaneState, MessageNonce}; use bp_runtime::{AccountIdOf, BalanceOf, RangeInclusiveExt}; -pub use bp_xcm_bridge_hub::{Bridge, BridgeId, BridgeState}; -use bp_xcm_bridge_hub::{BridgeLocations, BridgeLocationsError, LocalXcmChannelManager}; +use bp_xcm_bridge_hub::BridgeLocationsError; +pub use bp_xcm_bridge_hub::{ + Bridge, BridgeId, BridgeLocations, BridgeState, LocalXcmChannelManager, +}; use frame_support::{traits::fungible::MutateHold, DefaultNoBound}; use frame_system::Config as SystemConfig; use pallet_bridge_messages::{Config as BridgeMessagesConfig, LanesManagerError}; diff --git a/bridges/modules/xcm-bridge-hub/src/mock.rs b/bridges/modules/xcm-bridge-hub/src/mock.rs index 9f06b99ef6d56c20afa298291e6764e5a422fd10..d186507dab1792960ba225fa9d9f16d9ead4f667 100644 --- a/bridges/modules/xcm-bridge-hub/src/mock.rs +++ b/bridges/modules/xcm-bridge-hub/src/mock.rs @@ -24,10 +24,10 @@ use bp_messages::{ }; use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, HashOf}; use bp_xcm_bridge_hub::{BridgeId, LocalXcmChannelManager}; -use codec::Encode; +use codec::{Decode, Encode}; use frame_support::{ assert_ok, derive_impl, parameter_types, - traits::{EnsureOrigin, Equals, Everything, OriginTrait}, + traits::{EnsureOrigin, Equals, Everything, Get, OriginTrait}, weights::RuntimeDbWeight, }; use polkadot_parachain_primitives::primitives::Sibling; @@ -44,7 +44,7 @@ use xcm_builder::{ InspectMessageQueues, NetworkExportTable, NetworkExportTableItem, ParentIsPreset, SiblingParachainConvertsVia, }; -use xcm_executor::XcmExecutor; +use xcm_executor::{traits::ConvertOrigin, XcmExecutor}; pub type AccountId = AccountId32; pub type Balance = u64; @@ -63,7 +63,7 @@ frame_support::construct_runtime! { Balances: pallet_balances::{Pallet, Event<T>}, Messages: pallet_bridge_messages::{Pallet, Call, Event<T>}, XcmOverBridge: pallet_xcm_bridge_hub::{Pallet, Call, HoldReason, Event<T>}, - XcmOverBridgeRouter: pallet_xcm_bridge_hub_router, + XcmOverBridgeWrappedWithExportMessageRouter: pallet_xcm_bridge_hub_router = 57, } } @@ -208,17 +208,27 @@ impl pallet_xcm_bridge_hub::Config for TestRuntime { type BlobDispatcher = TestBlobDispatcher; } -impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime { +/// A router instance simulates a scenario where the router is deployed on a different chain than +/// the `MessageExporter`. This means that the router sends an `ExportMessage`. +pub type XcmOverBridgeWrappedWithExportMessageRouterInstance = (); +impl pallet_xcm_bridge_hub_router::Config<XcmOverBridgeWrappedWithExportMessageRouterInstance> + for TestRuntime +{ type RuntimeEvent = RuntimeEvent; type WeightInfo = (); - type UniversalLocation = UniversalLocation; + type UniversalLocation = ExportMessageOriginUniversalLocation; type SiblingBridgeHubLocation = BridgeHubLocation; type BridgedNetworkId = BridgedRelayNetwork; type Bridges = NetworkExportTable<BridgeTable>; type DestinationVersion = AlwaysLatest; - type ToBridgeHubSender = TestExportXcmWithXcmOverBridge; + // We convert to root `here` location with `BridgeHubLocationXcmOriginAsRoot` + type BridgeHubOrigin = frame_system::EnsureRoot<AccountId>; + // **Note**: The crucial part is that `ExportMessage` is processed by `XcmExecutor`, which + // calls the `ExportXcm` implementation of `pallet_xcm_bridge_hub` as the + // `MessageExporter`. + type ToBridgeHubSender = ExecuteXcmOverSendXcm; type LocalXcmChannelManager = TestLocalXcmChannelManager; type ByteFee = ConstU128<0>; @@ -230,7 +240,7 @@ impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = (); type AssetTransactor = (); - type OriginConverter = (); + type OriginConverter = BridgeHubLocationXcmOriginAsRoot<RuntimeOrigin>; type IsReserve = (); type IsTeleporter = (); type UniversalLocation = UniversalLocation; @@ -270,8 +280,8 @@ thread_local! { /// /// Note: The crucial part is that `ExportMessage` is processed by `XcmExecutor`, which calls the /// `ExportXcm` implementation of `pallet_xcm_bridge_hub` as `MessageExporter`. -pub struct TestExportXcmWithXcmOverBridge; -impl SendXcm for TestExportXcmWithXcmOverBridge { +pub struct ExecuteXcmOverSendXcm; +impl SendXcm for ExecuteXcmOverSendXcm { type Ticket = Xcm<()>; fn validate( @@ -298,7 +308,7 @@ impl SendXcm for TestExportXcmWithXcmOverBridge { Ok(hash) } } -impl InspectMessageQueues for TestExportXcmWithXcmOverBridge { +impl InspectMessageQueues for ExecuteXcmOverSendXcm { fn clear_messages() { todo!() } @@ -307,12 +317,51 @@ impl InspectMessageQueues for TestExportXcmWithXcmOverBridge { todo!() } } -impl TestExportXcmWithXcmOverBridge { +impl ExecuteXcmOverSendXcm { pub fn set_origin_for_execute(origin: Location) { EXECUTE_XCM_ORIGIN.with(|o| *o.borrow_mut() = Some(origin)); } } +/// A dynamic way to set different universal location for the origin which sends `ExportMessage`. +pub struct ExportMessageOriginUniversalLocation; +impl ExportMessageOriginUniversalLocation { + pub(crate) fn set(universal_location: Option<InteriorLocation>) { + EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION.with(|o| *o.borrow_mut() = universal_location); + } +} +impl Get<InteriorLocation> for ExportMessageOriginUniversalLocation { + fn get() -> InteriorLocation { + EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION.with(|o| { + o.borrow() + .clone() + .expect("`EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION` is not set!") + }) + } +} +thread_local! { + pub static EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION: RefCell<Option<InteriorLocation>> = RefCell::new(None); +} + +pub struct BridgeHubLocationXcmOriginAsRoot<RuntimeOrigin>( + sp_std::marker::PhantomData<RuntimeOrigin>, +); +impl<RuntimeOrigin: OriginTrait> ConvertOrigin<RuntimeOrigin> + for BridgeHubLocationXcmOriginAsRoot<RuntimeOrigin> +{ + fn convert_origin( + origin: impl Into<Location>, + kind: OriginKind, + ) -> Result<RuntimeOrigin, Location> { + let origin = origin.into(); + if kind == OriginKind::Xcm && origin.eq(&BridgeHubLocation::get()) { + Ok(RuntimeOrigin::root()) + } else { + Err(origin) + } + } +} + /// Type for specifying how a `Location` can be converted into an `AccountId`. This is used /// when determining ownership of accounts for asset transacting and when attempting to use XCM /// `Transact` in order to determine the dispatch Origin. @@ -396,6 +445,9 @@ impl EnsureOrigin<RuntimeOrigin> for OpenBridgeOrigin { } } +pub(crate) type OpenBridgeOriginOf<T, I> = + <T as pallet_xcm_bridge_hub::Config<I>>::OpenBridgeOrigin; + pub struct TestLocalXcmChannelManager; impl TestLocalXcmChannelManager { @@ -403,30 +455,82 @@ impl TestLocalXcmChannelManager { frame_support::storage::unhashed::put(b"TestLocalXcmChannelManager.Congested", &true); } - pub fn is_bridge_suspened() -> bool { - frame_support::storage::unhashed::get_or_default(b"TestLocalXcmChannelManager.Suspended") + fn suspended_key(bridge: &BridgeId) -> Vec<u8> { + [b"TestLocalXcmChannelManager.Suspended", bridge.encode().as_slice()].concat() + } + fn resumed_key(bridge: &BridgeId) -> Vec<u8> { + [b"TestLocalXcmChannelManager.Resumed", bridge.encode().as_slice()].concat() + } + + pub fn is_bridge_suspended(bridge: &BridgeId) -> bool { + frame_support::storage::unhashed::get_or_default(&Self::suspended_key(bridge)) } - pub fn is_bridge_resumed() -> bool { - frame_support::storage::unhashed::get_or_default(b"TestLocalXcmChannelManager.Resumed") + pub fn is_bridge_resumed(bridge: &BridgeId) -> bool { + frame_support::storage::unhashed::get_or_default(&Self::resumed_key(bridge)) + } + + fn build_congestion_message(bridge: &BridgeId, is_congested: bool) -> Vec<Instruction<()>> { + use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall; + #[allow(clippy::large_enum_variant)] + #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, scale_info::TypeInfo)] + enum Call { + #[codec(index = 57)] + XcmOverBridgeWrappedWithExportMessageRouter(XcmBridgeHubRouterCall), + } + + sp_std::vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::Xcm, + fallback_max_weight: None, + call: Call::XcmOverBridgeWrappedWithExportMessageRouter( + XcmBridgeHubRouterCall::report_bridge_status { + bridge_id: bridge.inner(), + is_congested, + } + ) + .encode() + .into(), + }, + ExpectTransactStatus(MaybeErrorCode::Success), + ] + } + + fn report_bridge_status( + local_origin: &Location, + bridge: &BridgeId, + is_congested: bool, + key: Vec<u8>, + ) -> Result<(), SendError> { + // send as BridgeHub would send to sibling chain + ExecuteXcmOverSendXcm::set_origin_for_execute(BridgeHubLocation::get()); + let result = send_xcm::<ExecuteXcmOverSendXcm>( + local_origin.clone(), + Self::build_congestion_message(&bridge, is_congested).into(), + ); + + if result.is_ok() { + frame_support::storage::unhashed::put(&key, &true); + } + + result.map(|_| ()) } } impl LocalXcmChannelManager for TestLocalXcmChannelManager { - type Error = (); + type Error = SendError; fn is_congested(_with: &Location) -> bool { frame_support::storage::unhashed::get_or_default(b"TestLocalXcmChannelManager.Congested") } - fn suspend_bridge(_local_origin: &Location, _bridge: BridgeId) -> Result<(), Self::Error> { - frame_support::storage::unhashed::put(b"TestLocalXcmChannelManager.Suspended", &true); - Ok(()) + fn suspend_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> { + Self::report_bridge_status(local_origin, &bridge, true, Self::suspended_key(&bridge)) } - fn resume_bridge(_local_origin: &Location, _bridge: BridgeId) -> Result<(), Self::Error> { - frame_support::storage::unhashed::put(b"TestLocalXcmChannelManager.Resumed", &true); - Ok(()) + fn resume_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> { + Self::report_bridge_status(local_origin, &bridge, false, Self::resumed_key(&bridge)) } } diff --git a/bridges/primitives/beefy/Cargo.toml b/bridges/primitives/beefy/Cargo.toml index 404acaff30af252f6e5c52d9b28e8ccc72d542ce..b32cf1e407eba927e1800965f34d8bffbbfc1af7 100644 --- a/bridges/primitives/beefy/Cargo.toml +++ b/bridges/primitives/beefy/Cargo.toml @@ -23,10 +23,10 @@ bp-runtime = { workspace = true } # Substrate Dependencies binary-merkle-tree = { workspace = true } -sp-consensus-beefy = { workspace = true } frame-support = { workspace = true } pallet-beefy-mmr = { workspace = true } pallet-mmr = { workspace = true } +sp-consensus-beefy = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } diff --git a/bridges/primitives/header-chain/Cargo.toml b/bridges/primitives/header-chain/Cargo.toml index 081bda479495f5bbd4599b4230d45f5c4e3c5e85..b17dcb2f7491c799439492efc23d7ef7014f09e7 100644 --- a/bridges/primitives/header-chain/Cargo.toml +++ b/bridges/primitives/header-chain/Cargo.toml @@ -23,8 +23,8 @@ bp-runtime = { workspace = true } # Substrate Dependencies frame-support = { workspace = true } -sp-core = { features = ["serde"], workspace = true } sp-consensus-grandpa = { features = ["serde"], workspace = true } +sp-core = { features = ["serde"], workspace = true } sp-runtime = { features = ["serde"], workspace = true } sp-std = { workspace = true } diff --git a/bridges/primitives/messages/Cargo.toml b/bridges/primitives/messages/Cargo.toml index 87c8cbe881803522888fd73497e80d9fd2cb2e08..dd1bd083371f6e80695e2f83af6be629b490a303 100644 --- a/bridges/primitives/messages/Cargo.toml +++ b/bridges/primitives/messages/Cargo.toml @@ -16,19 +16,19 @@ scale-info = { features = ["bit-vec", "derive"], workspace = true } serde = { features = ["alloc", "derive"], workspace = true } # Bridge dependencies -bp-runtime = { workspace = true } bp-header-chain = { workspace = true } +bp-runtime = { workspace = true } # Substrate Dependencies frame-support = { workspace = true } sp-core = { workspace = true } -sp-std = { workspace = true } sp-io = { workspace = true } +sp-std = { workspace = true } [dev-dependencies] +bp-runtime = { workspace = true } hex = { workspace = true, default-features = true } hex-literal = { workspace = true, default-features = true } -bp-runtime = { workspace = true } [features] default = ["std"] diff --git a/bridges/primitives/relayers/Cargo.toml b/bridges/primitives/relayers/Cargo.toml index 34be38bed4ac67fec9f0b5a1e1c1a8b0ca70e6d5..9219bae1e13182ec4e8f458d0f946a2a63537d71 100644 --- a/bridges/primitives/relayers/Cargo.toml +++ b/bridges/primitives/relayers/Cargo.toml @@ -21,8 +21,8 @@ bp-parachains = { workspace = true } bp-runtime = { workspace = true } # Substrate Dependencies -frame-system = { workspace = true } frame-support = { workspace = true } +frame-system = { workspace = true } pallet-utility = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } diff --git a/bridges/primitives/xcm-bridge-hub-router/Cargo.toml b/bridges/primitives/xcm-bridge-hub-router/Cargo.toml index ba0c51152bd2fa6fd6e03b942cd4ce1fb93b8fd9..b8a21ec35024dd286ab69fd0df01d9bb247f9533 100644 --- a/bridges/primitives/xcm-bridge-hub-router/Cargo.toml +++ b/bridges/primitives/xcm-bridge-hub-router/Cargo.toml @@ -15,8 +15,8 @@ codec = { features = ["bit-vec", "derive"], workspace = true } scale-info = { features = ["bit-vec", "derive"], workspace = true } # Substrate Dependencies -sp-runtime = { workspace = true } sp-core = { workspace = true } +sp-runtime = { workspace = true } # Polkadot Dependencies xcm = { workspace = true } diff --git a/bridges/primitives/xcm-bridge-hub/Cargo.toml b/bridges/primitives/xcm-bridge-hub/Cargo.toml index 79201a8756f9a61f1f5bc93aa2f631e9ad7fa441..800e2a3da3a3db1421c40ea959ef129902bf96d2 100644 --- a/bridges/primitives/xcm-bridge-hub/Cargo.toml +++ b/bridges/primitives/xcm-bridge-hub/Cargo.toml @@ -20,10 +20,10 @@ bp-messages = { workspace = true } bp-runtime = { workspace = true } # Substrate Dependencies -sp-std = { workspace = true } -sp-io = { workspace = true } -sp-core = { workspace = true } frame-support = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } # Polkadot Dependencies xcm = { workspace = true } diff --git a/bridges/primitives/xcm-bridge-hub/src/lib.rs b/bridges/primitives/xcm-bridge-hub/src/lib.rs index 63beb1bc30410c4ba84334953b0e302d4e2e1406..471cf402c34fbdc6d2b47070ce19736d10669884 100644 --- a/bridges/primitives/xcm-bridge-hub/src/lib.rs +++ b/bridges/primitives/xcm-bridge-hub/src/lib.rs @@ -87,6 +87,11 @@ impl BridgeId { .into(), ) } + + /// Access the inner representation. + pub fn inner(&self) -> H256 { + self.0 + } } impl core::fmt::Debug for BridgeId { diff --git a/bridges/relays/client-substrate/Cargo.toml b/bridges/relays/client-substrate/Cargo.toml index 6065c23773e3f6808ee0a56f07f0ef0325378fce..6a59688b2d8c1807df3882d7dfb242a63c98423b 100644 --- a/bridges/relays/client-substrate/Cargo.toml +++ b/bridges/relays/client-substrate/Cargo.toml @@ -18,16 +18,16 @@ futures = { workspace = true } jsonrpsee = { features = ["macros", "ws-client"], workspace = true } log = { workspace = true } num-traits = { workspace = true, default-features = true } +quick_cache = { workspace = true } rand = { workspace = true, default-features = true } -serde_json = { workspace = true } scale-info = { features = [ "derive", ], workspace = true, default-features = true } +serde_json = { workspace = true } +thiserror = { workspace = true } tokio = { features = [ "rt-multi-thread", ], workspace = true, default-features = true } -thiserror = { workspace = true } -quick_cache = { workspace = true } # Bridge dependencies diff --git a/bridges/relays/lib-substrate-relay/Cargo.toml b/bridges/relays/lib-substrate-relay/Cargo.toml index b0f93e5b5485f24b230b9b2868c6301b6ed64181..b418a2a3abb8a167cf7316478dd57f7c99e21b71 100644 --- a/bridges/relays/lib-substrate-relay/Cargo.toml +++ b/bridges/relays/lib-substrate-relay/Cargo.toml @@ -32,29 +32,29 @@ bp-relayers = { workspace = true, default-features = true } equivocation-detector = { workspace = true } finality-relay = { workspace = true } -parachains-relay = { workspace = true } -relay-utils = { workspace = true } messages-relay = { workspace = true } +parachains-relay = { workspace = true } relay-substrate-client = { workspace = true } +relay-utils = { workspace = true } pallet-bridge-grandpa = { workspace = true, default-features = true } pallet-bridge-messages = { workspace = true, default-features = true } pallet-bridge-parachains = { workspace = true, default-features = true } -bp-runtime = { workspace = true, default-features = true } bp-messages = { workspace = true, default-features = true } +bp-runtime = { workspace = true, default-features = true } # Substrate Dependencies frame-support = { workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-grandpa = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } sp-consensus-grandpa = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-trie = { workspace = true } [dev-dependencies] -scale-info = { features = ["derive"], workspace = true } pallet-transaction-payment = { workspace = true, default-features = true } relay-substrate-client = { features = ["test-helpers"], workspace = true } +scale-info = { features = ["derive"], workspace = true } diff --git a/bridges/relays/lib-substrate-relay/src/error.rs b/bridges/relays/lib-substrate-relay/src/error.rs index 2ebd9130f3912ba4c0552860a2259b222220e8f8..3a62f30838c7588317234409a6107b7d1d6cacf1 100644 --- a/bridges/relays/lib-substrate-relay/src/error.rs +++ b/bridges/relays/lib-substrate-relay/src/error.rs @@ -47,7 +47,7 @@ pub enum Error<Hash: Debug + MaybeDisplay, HeaderNumber: Debug + MaybeDisplay> { #[error("Failed to guess initial {0} GRANDPA authorities set id: checked all possible ids in range [0; {1}]")] GuessInitialAuthorities(&'static str, HeaderNumber), /// Failed to retrieve GRANDPA authorities at the given header from the source chain. - #[error("Failed to retrive {0} GRANDPA authorities set at header {1}: {2:?}")] + #[error("Failed to retrieve {0} GRANDPA authorities set at header {1}: {2:?}")] RetrieveAuthorities(&'static str, Hash, client::Error), /// Failed to decode GRANDPA authorities at the given header of the source chain. #[error("Failed to decode {0} GRANDPA authorities set at header {1}: {2:?}")] diff --git a/bridges/relays/messages/src/message_lane_loop.rs b/bridges/relays/messages/src/message_lane_loop.rs index 36de637f04c437450e9054dd5088c3da5eb329cd..cdc94b9fae493cc9701449a1485308476d5c758f 100644 --- a/bridges/relays/messages/src/message_lane_loop.rs +++ b/bridges/relays/messages/src/message_lane_loop.rs @@ -1041,7 +1041,7 @@ pub(crate) mod tests { #[test] fn message_lane_loop_is_able_to_recover_from_unsuccessful_transaction() { // with this configuration, both source and target clients will mine their transactions, but - // their corresponding nonce won't be udpated => reconnect will happen + // their corresponding nonce won't be updated => reconnect will happen let (exit_sender, exit_receiver) = unbounded(); let result = run_loop_test( Arc::new(Mutex::new(TestClientData { diff --git a/bridges/relays/utils/Cargo.toml b/bridges/relays/utils/Cargo.toml index 4c25566607dcdb574dce7f36183867fee0d29742..6d28789daaec26e321e038026dfe5ebeb8bbe74c 100644 --- a/bridges/relays/utils/Cargo.toml +++ b/bridges/relays/utils/Cargo.toml @@ -15,19 +15,18 @@ anyhow = { workspace = true, default-features = true } async-std = { workspace = true } async-trait = { workspace = true } backoff = { workspace = true } -console = { workspace = true } -isahc = { workspace = true } -sp-tracing = { workspace = true, default-features = true } futures = { workspace = true } +isahc = { workspace = true } jsonpath_lib = { workspace = true } log = { workspace = true } num-traits = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } sysinfo = { workspace = true } +thiserror = { workspace = true } time = { features = ["formatting", "local-offset", "std"], workspace = true } tokio = { features = ["rt"], workspace = true, default-features = true } -thiserror = { workspace = true } # Bridge dependencies @@ -35,5 +34,5 @@ bp-runtime = { workspace = true, default-features = true } # Substrate dependencies -sp-runtime = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } diff --git a/bridges/relays/utils/src/initialize.rs b/bridges/relays/utils/src/initialize.rs index 564ed1f0e5cc831e27df6dbb973583752ad4374f..deb9b9d059d51fa87ac9f1d3d0afbbd0991a2e6c 100644 --- a/bridges/relays/utils/src/initialize.rs +++ b/bridges/relays/utils/src/initialize.rs @@ -52,9 +52,10 @@ pub fn initialize_logger(with_timestamp: bool) { format, ); - let env_filter = EnvFilter::from_default_env() - .add_directive(Level::WARN.into()) - .add_directive("bridge=info".parse().expect("static filter string is valid")); + let env_filter = EnvFilter::builder() + .with_default_directive(Level::WARN.into()) + .with_default_directive("bridge=info".parse().expect("static filter string is valid")) + .from_env_lossy(); let builder = SubscriberBuilder::default().with_env_filter(env_filter); diff --git a/bridges/snowbridge/pallets/ethereum-client/Cargo.toml b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml index 262d9a7f380ded22b3a280b791e2525b3a75400c..87b4c66d77535e4452acafb63a2ff5cd402890ac 100644 --- a/bridges/snowbridge/pallets/ethereum-client/Cargo.toml +++ b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml @@ -15,37 +15,36 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { optional = true, workspace = true, default-features = true } -serde_json = { optional = true, workspace = true, default-features = true } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } hex-literal = { optional = true, workspace = true, default-features = true } log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } +serde_json = { optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } sp-core = { workspace = true } -sp-std = { workspace = true } -sp-runtime = { workspace = true } sp-io = { optional = true, workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +pallet-timestamp = { optional = true, workspace = true } +snowbridge-beacon-primitives = { workspace = true } snowbridge-core = { workspace = true } snowbridge-ethereum = { workspace = true } snowbridge-pallet-ethereum-client-fixtures = { optional = true, workspace = true } -snowbridge-beacon-primitives = { workspace = true } static_assertions = { workspace = true } -pallet-timestamp = { optional = true, workspace = true } [dev-dependencies] -rand = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } hex-literal = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } +rand = { workspace = true, default-features = true } +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } snowbridge-pallet-ethereum-client-fixtures = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -serde = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/ethereum-client/fixtures/Cargo.toml b/bridges/snowbridge/pallets/ethereum-client/fixtures/Cargo.toml index 87f0cf9a551308d58e5eb080a9057f844d2543af..74bfe580ec369c1a9e83fa355969fa18e71538ec 100644 --- a/bridges/snowbridge/pallets/ethereum-client/fixtures/Cargo.toml +++ b/bridges/snowbridge/pallets/ethereum-client/fixtures/Cargo.toml @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] hex-literal = { workspace = true, default-features = true } +snowbridge-beacon-primitives = { workspace = true } +snowbridge-core = { workspace = true } sp-core = { workspace = true } sp-std = { workspace = true } -snowbridge-core = { workspace = true } -snowbridge-beacon-primitives = { workspace = true } [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/inbound-queue/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml index 1b08bb39b4346a76e58c0a695b4c71126bd40510..5d4e8ad676628301317bc1c2440c23ae9d5b6259 100644 --- a/bridges/snowbridge/pallets/inbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml @@ -15,42 +15,40 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { optional = true, workspace = true, default-features = true } +alloy-core = { workspace = true, features = ["sol-types"] } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } hex-literal = { optional = true, workspace = true, default-features = true } log = { workspace = true } -alloy-primitives = { features = ["rlp"], workspace = true } -alloy-sol-types = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-balances = { workspace = true } sp-core = { workspace = true } -sp-std = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } +sp-std = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } -snowbridge-core = { workspace = true } -snowbridge-router-primitives = { workspace = true } snowbridge-beacon-primitives = { workspace = true } +snowbridge-core = { workspace = true } snowbridge-pallet-inbound-queue-fixtures = { optional = true, workspace = true } +snowbridge-router-primitives = { workspace = true } [dev-dependencies] frame-benchmarking = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -snowbridge-pallet-ethereum-client = { workspace = true, default-features = true } hex-literal = { workspace = true, default-features = true } +snowbridge-pallet-ethereum-client = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } [features] default = ["std"] std = [ - "alloy-primitives/std", - "alloy-sol-types/std", + "alloy-core/std", "codec/std", "frame-benchmarking/std", "frame-support/std", @@ -83,6 +81,7 @@ runtime-benchmarks = [ "snowbridge-router-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/bridges/snowbridge/pallets/inbound-queue/fixtures/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue/fixtures/Cargo.toml index 6162a17728b61e55da68af95bba5800d6e0e47cc..c698dbbf1003f4a3f4e8efff24e471f142d0cf4b 100644 --- a/bridges/snowbridge/pallets/inbound-queue/fixtures/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue/fixtures/Cargo.toml @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] hex-literal = { workspace = true, default-features = true } +snowbridge-beacon-primitives = { workspace = true } +snowbridge-core = { workspace = true } sp-core = { workspace = true } sp-std = { workspace = true } -snowbridge-core = { workspace = true } -snowbridge-beacon-primitives = { workspace = true } [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/inbound-queue/fixtures/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/lib.rs index 00adcdfa186adc755273b9753d833a6bdbec5b56..cb4232376c6fc98c21485aee7e3f5980cf8f941d 100644 --- a/bridges/snowbridge/pallets/inbound-queue/fixtures/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/lib.rs @@ -3,5 +3,6 @@ #![cfg_attr(not(feature = "std"), no_std)] pub mod register_token; +pub mod send_native_eth; pub mod send_token; pub mod send_token_to_penpal; diff --git a/bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_native_eth.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_native_eth.rs new file mode 100755 index 0000000000000000000000000000000000000000..d3e8d76e6b395081869b85e4b5d1541665a1259c --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_native_eth.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com> +// Generated, do not edit! +// See ethereum client README.md for instructions to generate + +use hex_literal::hex; +use snowbridge_beacon_primitives::{ + types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader, +}; +use snowbridge_core::inbound::{InboundQueueFixture, Log, Message, Proof}; +use sp_core::U256; +use sp_std::vec; + +pub fn make_send_native_eth_message() -> InboundQueueFixture { + InboundQueueFixture { + message: Message { + event_log: Log { + address: hex!("87d1f7fdfee7f651fabc8bfcb6e086c278b77a7d").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005f00a736aa0000000000010000000000000000000000000000000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000e8764817000000000000000000000000").into(), + }, + proof: Proof { + receipt_proof: (vec![ + hex!("17cd4d05dde30703008a4f213205923630cff8e6bc9d5d95a52716bfb5551fd7").to_vec(), + ], vec![ + hex!("f903b4822080b903ae02f903aa018301a7fcb9010000000000000000000000000020000000000000000000004000000000000000000400000000000000000000001000000000000000000000000000000000000000000000000000000001080000000000000000000000000000000000000000080000000000020000000000000000000800010100000000000000000000000000000000000200000000000000000000000000001000000040080008000000000000000000040000000021000000002000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000200000000000000f9029ff9015d9487d1f7fdfee7f651fabc8bfcb6e086c278b77a7df884a024c5d2de620c6e25186ae16f6919eba93b6e2c1a33857cc419d9f3a00d6967e9a00000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000000000000000000000000000000000000000003e8b8c000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000208eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48f9013c9487d1f7fdfee7f651fabc8bfcb6e086c278b77a7df863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a05f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0b8c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005f00a736aa0000000000010000000000000000000000000000000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000e8764817000000000000000000000000").to_vec(), + ]), + execution_proof: ExecutionProof { + header: BeaconHeader { + slot: 246, + proposer_index: 7, + parent_root: hex!("4faaac5d2fa0b8884fe1175c7cac1c92aac9eba5a20b4302edb98a56428c5974").into(), + state_root: hex!("882c13f1d56df781e3444a78cae565bfa1c89822c86cdb0daea71f5351231580").into(), + body_root: hex!("c47eb72204b1ca567396dacef8b0214027eb7f0789330b55166085d1f9cb4c65").into(), + }, + ancestry_proof: Some(AncestryProof { + header_branch: vec![ + hex!("38e2454bc93c4cfafcea772b8531e4802bbd2561366620699096dd4e591bc488").into(), + hex!("3d7389fb144ccaeca8b8e1667ce1d1538dfceb50bf1e49c4b368a223f051fda3").into(), + hex!("0d49c9c24137ad4d86ebca2f36a159573a68b5d5d60e317776c77cc8b6093034").into(), + hex!("0fadc6735bcdc2793a5039a806fbf39984c39374ed4d272c1147e1c23df88983").into(), + hex!("3a058ad4b169eebb4c754c8488d41e56a7a0e5f8b55b5ec67452a8d326585c69").into(), + hex!("de200426caa9bc03f8e0033b4ef4df1db6501924b5c10fb7867e76db942b903c").into(), + hex!("48b578632bc40eebb517501f179ffdd06d762c03e9383df16fc651eeddd18806").into(), + hex!("98d9d6904b2a6a285db4c4ae59a07100cd38ec4d9fb7a16a10fe83ec99e6ba1d").into(), + hex!("1b2bbae6e684864b714654a60778664e63ba6c3c9bed8074ec1a0380fe5042e6").into(), + hex!("eb907a888eadf5a7e2bd0a3a5a9369e409c7aa688bd4cde758d5b608c6c82785").into(), + hex!("ffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b").into(), + hex!("6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220").into(), + hex!("b7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f").into(), + ], + finalized_block_root: hex!("440615588532ce496a93d189cb0ef1df7cf67d529faee0fd03213ce26ea115e5").into(), + }), + execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader { + parent_hash: hex!("a8c89213b7d7d2ac76462d89e6a7384374db905b657ad803d3c86f88f86c39df").into(), + fee_recipient: hex!("0000000000000000000000000000000000000000").into(), + state_root: hex!("a1e8175213a6a43da17fae65109245867cbc60e3ada16b8ac28c6b208761c772").into(), + receipts_root: hex!("17cd4d05dde30703008a4f213205923630cff8e6bc9d5d95a52716bfb5551fd7").into(), + logs_bloom: hex!("00000000000000000000000020000000000000000000004000000000000000000400000000000000000000001000000000000000000000000000000000000000000000000000000001080000000000000000000000000000000000000000080000000000020000000000000000000800010100000000000000000000000000000000000200000000000000000000000000001000000040080008000000000000000000040000000021000000002000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000200000000000000").into(), + prev_randao: hex!("b9b26dc14ea8c57d069fde0c94ad31c2558365c3986a0c06558470f8c02e62ce").into(), + block_number: 246, + gas_limit: 62908420, + gas_used: 108540, + timestamp: 1734718384, + extra_data: hex!("d983010e08846765746888676f312e32322e358664617277696e").into(), + base_fee_per_gas: U256::from(7u64), + block_hash: hex!("878195e2ea83c74d475363d03d41a7fbfc4026d6e5bcffb713928253984a64a7").into(), + transactions_root: hex!("909139b3137666b4551b629ce6d9fb7e5e6f6def8a48d078448ec6600fe63c7f").into(), + withdrawals_root: hex!("792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535").into(), + blob_gas_used: 0, + excess_blob_gas: 0, + }), + execution_branch: vec![ + hex!("5d78e26ea639df17c2194ff925f782b9522009d58cfc60e3d34ba79a19f8faf1").into(), + hex!("b46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb").into(), + hex!("db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71").into(), + hex!("3d84b2809a36450186e5169995a5e3cab55d751aee90fd8456b33d871ccaa463").into(), + ], + } + }, + }, + finalized_header: BeaconHeader { + slot: 608, + proposer_index: 3, + parent_root: hex!("f10c2349530dbd339a72886270e2e304bb68155af68c918c850acd9ab341350f").into(), + state_root: hex!("6df0ef4cbb4986a84ff0763727402b88636e6b5535022cd3ad6967b8dd799402").into(), + body_root: hex!("f66fc1c022f07f91c777ad5c464625fc0b43d3e7a45650567dce60011210f574").into(), + }, + block_roots_root: hex!("1c0dbf54db070770f5e573b72afe0aac2b0e3cf312107d1cd73bf64d7a2ed90c").into(), + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue/src/envelope.rs b/bridges/snowbridge/pallets/inbound-queue/src/envelope.rs index 31a8992442d83b789849bdd99bedef31109ecc5a..d213c8aad6481d335f5a83d625c3df69df980a73 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/envelope.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/envelope.rs @@ -5,8 +5,7 @@ use snowbridge_core::{inbound::Log, ChannelId}; use sp_core::{RuntimeDebug, H160, H256}; use sp_std::prelude::*; -use alloy_primitives::B256; -use alloy_sol_types::{sol, SolEvent}; +use alloy_core::{primitives::B256, sol, sol_types::SolEvent}; sol! { event OutboundMessageAccepted(bytes32 indexed channel_id, uint64 nonce, bytes32 indexed message_id, bytes payload); @@ -36,7 +35,7 @@ impl TryFrom<&Log> for Envelope { fn try_from(log: &Log) -> Result<Self, Self::Error> { let topics: Vec<B256> = log.topics.iter().map(|x| B256::from_slice(x.as_ref())).collect(); - let event = OutboundMessageAccepted::decode_log(topics, &log.data, true) + let event = OutboundMessageAccepted::decode_raw_log(topics, &log.data, true) .map_err(|_| EnvelopeDecodeError)?; Ok(Self { @@ -44,7 +43,7 @@ impl TryFrom<&Log> for Envelope { channel_id: ChannelId::from(event.channel_id.as_ref()), nonce: event.nonce, message_id: H256::from(event.message_id.as_ref()), - payload: event.payload, + payload: event.payload.into(), }) } } diff --git a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs index 675d4b6915937ff7129dcbb5c8264b155edd31f2..eed0656e9ca7fb9835a0992564f3e564df79ed62 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs @@ -248,20 +248,6 @@ impl inbound_queue::Config for Test { type AssetTransactor = SuccessfulTransactor; } -pub fn last_events(n: usize) -> Vec<RuntimeEvent> { - frame_system::Pallet::<Test>::events() - .into_iter() - .rev() - .take(n) - .rev() - .map(|e| e.event) - .collect() -} - -pub fn expect_events(e: Vec<RuntimeEvent>) { - assert_eq!(last_events(e.len()), e); -} - pub fn setup() { System::set_block_number(1); Balances::mint_into( diff --git a/bridges/snowbridge/pallets/inbound-queue/src/test.rs b/bridges/snowbridge/pallets/inbound-queue/src/test.rs index 76d0b98e9eb4632f50b72821642f8b57cb189832..aa99d63b4bf990b0ac65bec24fc7668b6a027379 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/test.rs @@ -5,11 +5,11 @@ use super::*; use frame_support::{assert_noop, assert_ok}; use hex_literal::hex; use snowbridge_core::{inbound::Proof, ChannelId}; -use sp_keyring::AccountKeyring as Keyring; +use sp_keyring::Sr25519Keyring as Keyring; use sp_runtime::DispatchError; use sp_std::convert::From; -use crate::{Error, Event as InboundQueueEvent}; +use crate::Error; use crate::mock::*; @@ -35,17 +35,16 @@ fn test_submit_happy_path() { assert_eq!(Balances::balance(&channel_sovereign), initial_fund); assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - expect_events(vec![InboundQueueEvent::MessageReceived { - channel_id: hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539") - .into(), - nonce: 1, - message_id: [ - 11, 25, 133, 51, 23, 68, 111, 211, 132, 94, 254, 17, 194, 252, 198, 233, 10, 193, - 156, 93, 72, 140, 65, 69, 79, 155, 154, 28, 141, 166, 171, 255, - ], - fee_burned: 110000000000, - } - .into()]); + + let events = frame_system::Pallet::<Test>::events(); + assert!( + events.iter().any(|event| matches!( + event.event, + RuntimeEvent::InboundQueue(Event::MessageReceived { nonce, ..}) + if nonce == 1 + )), + "no event emit." + ); let delivery_cost = InboundQueue::calculate_delivery_cost(message.encode().len() as u32); assert!( diff --git a/bridges/snowbridge/pallets/outbound-queue/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml index 78546e258daa30e966ddef1ed48c35cebcc17d65..e343d4c684ab9feaf3e6901c4a56edd2e2dbf439 100644 --- a/bridges/snowbridge/pallets/outbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml @@ -15,28 +15,27 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { features = ["alloc", "derive"], workspace = true } codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } +serde = { features = ["alloc", "derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +sp-arithmetic = { workspace = true } sp-core = { workspace = true } -sp-std = { workspace = true } -sp-runtime = { workspace = true } sp-io = { workspace = true } -sp-arithmetic = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } bridge-hub-common = { workspace = true } +ethabi = { workspace = true } snowbridge-core = { features = ["serde"], workspace = true } snowbridge-outbound-queue-merkle-tree = { workspace = true } -ethabi = { workspace = true } [dev-dependencies] pallet-message-queue = { workspace = true } -sp-keyring = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml index 16241428df80871fae21ade130bf0f72c7f2f1ba..2a0616b4f954f58701d1dbb9edc4a4f5699be1c9 100644 --- a/bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml @@ -22,9 +22,9 @@ sp-core = { workspace = true } sp-runtime = { workspace = true } [dev-dependencies] -hex-literal = { workspace = true, default-features = true } -hex = { workspace = true, default-features = true } array-bytes = { workspace = true, default-features = true } +hex = { workspace = true, default-features = true } +hex-literal = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } diff --git a/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml index d35bdde5a81e7a80a228efff19c3c1de0eceefef..18f7dde22c93cdafd37b4c6263a5fd1172e4b171 100644 --- a/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml @@ -16,11 +16,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -sp-std = { workspace = true } -sp-api = { workspace = true } frame-support = { workspace = true } -snowbridge-outbound-queue-merkle-tree = { workspace = true } snowbridge-core = { workspace = true } +snowbridge-outbound-queue-merkle-tree = { workspace = true } +sp-api = { workspace = true } +sp-std = { workspace = true } [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/system/Cargo.toml b/bridges/snowbridge/pallets/system/Cargo.toml index f1e749afb9977c440ba7bbfa55c8a8acbc8c0cda..c695b1034f6981e38d11947f6df998976130ec7b 100644 --- a/bridges/snowbridge/pallets/system/Cargo.toml +++ b/bridges/snowbridge/pallets/system/Cargo.toml @@ -18,16 +18,16 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } -sp-std = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } +sp-std = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } @@ -38,9 +38,8 @@ snowbridge-core = { workspace = true } hex = { workspace = true, default-features = true } hex-literal = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } pallet-message-queue = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } snowbridge-pallet-outbound-queue = { workspace = true, default-features = true } [features] @@ -71,6 +70,7 @@ runtime-benchmarks = [ "snowbridge-pallet-outbound-queue/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/bridges/snowbridge/pallets/system/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/system/runtime-api/Cargo.toml index 7c524dd2edadb6132be50616f7e6855ad463c8b4..fc377b460d33c49e7aadceb36ed1cb90b434f58c 100644 --- a/bridges/snowbridge/pallets/system/runtime-api/Cargo.toml +++ b/bridges/snowbridge/pallets/system/runtime-api/Cargo.toml @@ -18,10 +18,10 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -sp-std = { workspace = true } +snowbridge-core = { workspace = true } sp-api = { workspace = true } +sp-std = { workspace = true } xcm = { workspace = true } -snowbridge-core = { workspace = true } [features] default = ["std"] diff --git a/bridges/snowbridge/primitives/beacon/Cargo.toml b/bridges/snowbridge/primitives/beacon/Cargo.toml index 9ced99fbf3fdddd8f64877606f957da14c70f608..bf5d6838f7bb23979960f6e67f4f205bc09d434b 100644 --- a/bridges/snowbridge/primitives/beacon/Cargo.toml +++ b/bridges/snowbridge/primitives/beacon/Cargo.toml @@ -12,24 +12,24 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { optional = true, features = ["derive"], workspace = true, default-features = true } -hex = { workspace = true } codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } +hex = { workspace = true } rlp = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } frame-support = { workspace = true } -sp-runtime = { workspace = true } sp-core = { workspace = true } -sp-std = { workspace = true } sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +byte-slice-cast = { workspace = true } ssz_rs = { workspace = true } ssz_rs_derive = { workspace = true } -byte-slice-cast = { workspace = true } -snowbridge-ethereum = { workspace = true } milagro-bls = { workspace = true } +snowbridge-ethereum = { workspace = true } [dev-dependencies] hex-literal = { workspace = true, default-features = true } diff --git a/bridges/snowbridge/primitives/core/Cargo.toml b/bridges/snowbridge/primitives/core/Cargo.toml index fa37c795b2d1e0871109cb393d6ef546e282cc99..514579400acac6b4ea78adb4befdbb0aba000675 100644 --- a/bridges/snowbridge/primitives/core/Cargo.toml +++ b/bridges/snowbridge/primitives/core/Cargo.toml @@ -12,10 +12,10 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { optional = true, features = ["alloc", "derive"], workspace = true } codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } hex-literal = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, features = ["alloc", "derive"], workspace = true } polkadot-parachain-primitives = { workspace = true } xcm = { workspace = true } @@ -23,11 +23,11 @@ xcm-builder = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +sp-arithmetic = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } -sp-io = { workspace = true } -sp-core = { workspace = true } -sp-arithmetic = { workspace = true } snowbridge-beacon-primitives = { workspace = true } @@ -64,4 +64,5 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/bridges/snowbridge/primitives/core/src/location.rs b/bridges/snowbridge/primitives/core/src/location.rs index f49a245c4126f410994390908a81b6d55e613b15..eb5ac66d46db7192d249141dbe7104fce8d619b0 100644 --- a/bridges/snowbridge/primitives/core/src/location.rs +++ b/bridges/snowbridge/primitives/core/src/location.rs @@ -206,7 +206,7 @@ mod tests { for token in token_locations { assert!( TokenIdOf::convert_location(&token).is_some(), - "Valid token = {token:?} yeilds no TokenId." + "Valid token = {token:?} yields no TokenId." ); } @@ -220,7 +220,7 @@ mod tests { for token in non_token_locations { assert!( TokenIdOf::convert_location(&token).is_none(), - "Invalid token = {token:?} yeilds a TokenId." + "Invalid token = {token:?} yields a TokenId." ); } } diff --git a/bridges/snowbridge/primitives/ethereum/Cargo.toml b/bridges/snowbridge/primitives/ethereum/Cargo.toml index 764ce90b8139d936d16e38a2f337c05004427675..5c249354a53a6ee14cc1729df667cbcc91e6cd5e 100644 --- a/bridges/snowbridge/primitives/ethereum/Cargo.toml +++ b/bridges/snowbridge/primitives/ethereum/Cargo.toml @@ -12,24 +12,23 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { optional = true, features = ["derive"], workspace = true, default-features = true } -serde-big-array = { optional = true, features = ["const-generics"], workspace = true } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } ethbloom = { workspace = true } ethereum-types = { features = ["codec", "rlp", "serialize"], workspace = true } hex-literal = { workspace = true } parity-bytes = { workspace = true } rlp = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +serde-big-array = { optional = true, features = ["const-generics"], workspace = true } sp-io = { workspace = true } -sp-std = { workspace = true } sp-runtime = { workspace = true } +sp-std = { workspace = true } ethabi = { workspace = true } [dev-dependencies] -wasm-bindgen-test = { workspace = true } rand = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } diff --git a/bridges/snowbridge/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml index ee8d481cec12ae07d107c0463b778345fcfcecee..e44cca077ef323f2f7617914dd3625b0170081de 100644 --- a/bridges/snowbridge/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -13,8 +13,8 @@ workspace = true [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } sp-core = { workspace = true } @@ -51,4 +51,5 @@ runtime-benchmarks = [ "snowbridge-core/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/bridges/snowbridge/primitives/router/src/inbound/mock.rs b/bridges/snowbridge/primitives/router/src/inbound/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..537853b324f633990b1c52c4eccef3a224755a30 --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/inbound/mock.rs @@ -0,0 +1,48 @@ +use crate::inbound::{MessageToXcm, TokenId}; +use frame_support::parameter_types; +use sp_runtime::{ + traits::{IdentifyAccount, MaybeEquivalence, Verify}, + MultiSignature, +}; +use xcm::{latest::WESTEND_GENESIS_HASH, prelude::*}; + +pub const CHAIN_ID: u64 = 11155111; +pub const NETWORK: NetworkId = Ethereum { chain_id: CHAIN_ID }; + +parameter_types! { + pub EthereumNetwork: NetworkId = NETWORK; + + pub const CreateAssetCall: [u8;2] = [53, 0]; + pub const CreateAssetExecutionFee: u128 = 2_000_000_000; + pub const CreateAssetDeposit: u128 = 100_000_000_000; + pub const SendTokenExecutionFee: u128 = 1_000_000_000; + pub const InboundQueuePalletInstance: u8 = 80; + pub UniversalLocation: InteriorLocation = + [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1002)].into(); + pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),Parachain(1000)]); +} + +type Signature = MultiSignature; +type AccountId = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId; +type Balance = u128; + +pub(crate) struct MockTokenIdConvert; +impl MaybeEquivalence<TokenId, Location> for MockTokenIdConvert { + fn convert(_id: &TokenId) -> Option<Location> { + Some(Location::parent()) + } + fn convert_back(_loc: &Location) -> Option<TokenId> { + None + } +} + +pub(crate) type MessageConverter = MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + MockTokenIdConvert, + UniversalLocation, + AssetHubFromEthereum, +>; diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index e03560f66e244b3d939ae3088e82866662082fa6..1c210afb1f7403d84dd8fc2f0acf35cb3063a171 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -2,6 +2,8 @@ // SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com> //! Converts messages from Ethereum to XCM messages +#[cfg(test)] +mod mock; #[cfg(test)] mod tests; @@ -279,6 +281,7 @@ where // Call create_asset on foreign assets pallet. Transact { origin_kind: OriginKind::Xcm, + fallback_max_weight: Some(Weight::from_parts(400_000_000, 8_000)), call: ( create_call_index, asset_id, @@ -357,7 +360,9 @@ where }])), // Perform a deposit reserve to send to destination chain. DepositReserveAsset { - assets: Definite(vec![dest_para_fee_asset.clone(), asset].into()), + // Send over assets and unspent fees, XCM delivery fee will be charged from + // here. + assets: Wild(AllCounted(2)), dest: Location::new(1, [Parachain(dest_para_id)]), xcm: vec![ // Buy execution on target. @@ -391,10 +396,16 @@ where // Convert ERC20 token address to a location that can be understood by Assets Hub. fn convert_token_address(network: NetworkId, token: H160) -> Location { - Location::new( - 2, - [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], - ) + // If the token is `0x0000000000000000000000000000000000000000` then return the location of + // native Ether. + if token == H160([0; 20]) { + Location::new(2, [GlobalConsensus(network)]) + } else { + Location::new( + 2, + [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], + ) + } } /// Constructs an XCM message destined for AssetHub that withdraws assets from the sovereign diff --git a/bridges/snowbridge/primitives/router/src/inbound/tests.rs b/bridges/snowbridge/primitives/router/src/inbound/tests.rs index 786aa594f653eec5e160a0a15ed4df638da8e728..11d7928602c6ecb07b1cebe0086a8fa52643eb78 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/tests.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/tests.rs @@ -1,21 +1,12 @@ use super::EthereumLocationsConverterFor; -use crate::inbound::CallIndex; -use frame_support::{assert_ok, parameter_types}; +use crate::inbound::{ + mock::*, Command, ConvertMessage, Destination, MessageV1, VersionedMessage, H160, +}; +use frame_support::assert_ok; use hex_literal::hex; use xcm::prelude::*; use xcm_executor::traits::ConvertLocation; -const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; - -parameter_types! { - pub EthereumNetwork: NetworkId = NETWORK; - - pub const CreateAssetCall: CallIndex = [1, 1]; - pub const CreateAssetExecutionFee: u128 = 123; - pub const CreateAssetDeposit: u128 = 891; - pub const SendTokenExecutionFee: u128 = 592; -} - #[test] fn test_ethereum_network_converts_successfully() { let expected_account: [u8; 32] = @@ -81,3 +72,74 @@ fn test_reanchor_all_assets() { assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); } } + +#[test] +fn test_convert_send_token_with_weth() { + const WETH: H160 = H160([0xff; 20]); + const AMOUNT: u128 = 1_000_000; + const FEE: u128 = 1_000; + const ACCOUNT_ID: [u8; 32] = [0xBA; 32]; + const MESSAGE: VersionedMessage = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH, + destination: Destination::AccountId32 { id: ACCOUNT_ID }, + amount: AMOUNT, + fee: FEE, + }, + }); + let result = MessageConverter::convert([1; 32].into(), MESSAGE); + assert_ok!(&result); + let (xcm, fee) = result.unwrap(); + assert_eq!(FEE, fee); + + let expected_assets = ReserveAssetDeposited( + vec![Asset { + id: AssetId(Location { + parents: 2, + interior: Junctions::X2( + [GlobalConsensus(NETWORK), AccountKey20 { network: None, key: WETH.into() }] + .into(), + ), + }), + fun: Fungible(AMOUNT), + }] + .into(), + ); + let actual_assets = xcm.into_iter().find(|x| matches!(x, ReserveAssetDeposited(..))); + assert_eq!(actual_assets, Some(expected_assets)) +} + +#[test] +fn test_convert_send_token_with_eth() { + const ETH: H160 = H160([0x00; 20]); + const AMOUNT: u128 = 1_000_000; + const FEE: u128 = 1_000; + const ACCOUNT_ID: [u8; 32] = [0xBA; 32]; + const MESSAGE: VersionedMessage = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: ETH, + destination: Destination::AccountId32 { id: ACCOUNT_ID }, + amount: AMOUNT, + fee: FEE, + }, + }); + let result = MessageConverter::convert([1; 32].into(), MESSAGE); + assert_ok!(&result); + let (xcm, fee) = result.unwrap(); + assert_eq!(FEE, fee); + + let expected_assets = ReserveAssetDeposited( + vec![Asset { + id: AssetId(Location { + parents: 2, + interior: Junctions::X1([GlobalConsensus(NETWORK)].into()), + }), + fun: Fungible(AMOUNT), + }] + .into(), + ); + let actual_assets = xcm.into_iter().find(|x| matches!(x, ReserveAssetDeposited(..))); + assert_eq!(actual_assets, Some(expected_assets)) +} diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs index 3b5dbdb77c89227d053548375b0c30b47792cea9..622c408070150392c00f80c30c58ad238cefbebd 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -289,8 +289,13 @@ where let (token, amount) = match reserve_asset { Asset { id: AssetId(inner_location), fun: Fungible(amount) } => match inner_location.unpack() { + // Get the ERC20 contract address of the token. (0, [AccountKey20 { network, key }]) if self.network_matches(network) => Some((H160(*key), *amount)), + // If there is no ERC20 contract address in the location then signal to the + // gateway that is a native Ether transfer by using + // `0x0000000000000000000000000000000000000000` as the token address. + (0, []) => Some((H160([0; 20]), *amount)), _ => None, }, _ => None, diff --git a/bridges/snowbridge/primitives/router/src/outbound/tests.rs b/bridges/snowbridge/primitives/router/src/outbound/tests.rs index 44f81ce31b3a8f4761a68fd5ca2496a5d79320bf..2a60f9f3e0eab8569a5a1300b03d423243e7789a 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/tests.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/tests.rs @@ -515,6 +515,46 @@ fn xcm_converter_convert_with_wildcard_all_asset_filter_succeeds() { assert_eq!(result, Ok((expected_payload, [0; 32]))); } +#[test] +fn xcm_converter_convert_with_native_eth_succeeds() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + // The asset is `{ parents: 0, interior: X1(Here) }` relative to ethereum. + let assets: Assets = vec![Asset { id: AssetId([].into()), fun: Fungible(1000) }].into(); + let filter: AssetFilter = Wild(All); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + + let mut converter = + XcmConverter::<MockTokenIdConvert, ()>::new(&message, network, Default::default()); + + // The token address that is expected to be sent should be + // `0x0000000000000000000000000000000000000000`. The solidity will + // interpret this as a transfer of ETH. + let expected_payload = Command::AgentExecute { + agent_id: Default::default(), + command: AgentExecuteCommand::TransferToken { + token: H160([0; 20]), + recipient: beneficiary_address.into(), + amount: 1000, + }, + }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); +} + #[test] fn xcm_converter_convert_with_fees_less_than_reserve_yields_success() { let network = BridgedNetwork::get(); diff --git a/bridges/snowbridge/runtime/runtime-common/Cargo.toml b/bridges/snowbridge/runtime/runtime-common/Cargo.toml index d47cb3cb7101fb54d7bdde854ff73e628555e86b..23cd0adf1226d0353bbb7ec2e9ab028afc61cebb 100644 --- a/bridges/snowbridge/runtime/runtime-common/Cargo.toml +++ b/bridges/snowbridge/runtime/runtime-common/Cargo.toml @@ -12,11 +12,11 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -log = { workspace = true } codec = { workspace = true } frame-support = { workspace = true } -sp-std = { workspace = true } +log = { workspace = true } sp-arithmetic = { workspace = true } +sp-std = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } @@ -43,4 +43,5 @@ runtime-benchmarks = [ "snowbridge-core/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/bridges/snowbridge/runtime/test-common/Cargo.toml b/bridges/snowbridge/runtime/test-common/Cargo.toml index 6f8e586bf5ff12e7d870df95ffc6a0f02461357b..184a0ff2329f00b2de39ba0a74d682aea3c49fb7 100644 --- a/bridges/snowbridge/runtime/test-common/Cargo.toml +++ b/bridges/snowbridge/runtime/test-common/Cargo.toml @@ -6,6 +6,8 @@ authors = ["Snowfork <contact@snowfork.com>"] edition.workspace = true license = "Apache-2.0" categories = ["cryptography::cryptocurrencies"] +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -17,8 +19,8 @@ codec = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-balances = { workspace = true } -pallet-session = { workspace = true } pallet-message-queue = { workspace = true } +pallet-session = { workspace = true } pallet-timestamp = { workspace = true } pallet-utility = { workspace = true } sp-core = { workspace = true } @@ -90,5 +92,6 @@ runtime-benchmarks = [ "snowbridge-pallet-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] fast-runtime = [] diff --git a/bridges/snowbridge/runtime/test-common/src/lib.rs b/bridges/snowbridge/runtime/test-common/src/lib.rs index dca5062ab31094d0ab9a7ea060fce58c13f9bb98..5441dd822caca197496d37d76bc785896c0e4149 100644 --- a/bridges/snowbridge/runtime/test-common/src/lib.rs +++ b/bridges/snowbridge/runtime/test-common/src/lib.rs @@ -13,7 +13,7 @@ use parachains_runtimes_test_utils::{ use snowbridge_core::{ChannelId, ParaId}; use snowbridge_pallet_ethereum_client_fixtures::*; use sp_core::{Get, H160, U256}; -use sp_keyring::AccountKeyring::*; +use sp_keyring::Sr25519Keyring::*; use sp_runtime::{traits::Header, AccountId32, DigestItem, SaturatedConversion, Saturating}; use xcm::latest::prelude::*; use xcm_executor::XcmExecutor; @@ -431,7 +431,7 @@ pub fn ethereum_extrinsic<Runtime>( collator_session_key: CollatorSessionKeys<Runtime>, runtime_para_id: u32, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, <Runtime as frame_system::Config>::RuntimeCall, ) -> sp_runtime::DispatchOutcome, ) where @@ -567,7 +567,7 @@ pub fn ethereum_to_polkadot_message_extrinsics_work<Runtime>( collator_session_key: CollatorSessionKeys<Runtime>, runtime_para_id: u32, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, <Runtime as frame_system::Config>::RuntimeCall, ) -> sp_runtime::DispatchOutcome, ) where diff --git a/cumulus/README.md b/cumulus/README.md index 7e145ad7b4abaf1aea3ecaf18236d231d2599b5d..400f9481c3fbeedd944416ff2a885a2a5867c416 100644 --- a/cumulus/README.md +++ b/cumulus/README.md @@ -4,7 +4,7 @@ This repository contains both the Cumulus SDK and also specific chains implemented on top of this SDK. -If you only want to run a **Polkadot Parachain Node**, check out our [container section](./docs/contributor/container.md). +If you only want to run a **Polkadot Parachain Node**, check out our [container section](../docs/contributor/container.md). ## Cumulus SDK @@ -34,7 +34,7 @@ A Polkadot [collator](https://wiki.polkadot.network/docs/en/learn-collator) for `polkadot-parachain` binary (previously called `polkadot-collator`). You may run `polkadot-parachain` locally after building it or using one of the container option described -[here](./docs/contributor/container.md). +[here](../docs/contributor/container.md). ### Relay Chain Interaction To operate a parachain node, a connection to the corresponding relay chain is necessary. This can be achieved in one of @@ -60,7 +60,7 @@ polkadot-parachain \ ``` #### External Relay Chain Node -An external relay chain node is connected via WebsSocket RPC by using the `--relay-chain-rpc-urls` command line +An external relay chain node is connected via WebSocket RPC by using the `--relay-chain-rpc-urls` command line argument. This option accepts one or more space-separated WebSocket URLs to a full relay chain node. By default, only the first URL will be used, with the rest as a backup in case the connection to the first node is lost. diff --git a/cumulus/bin/pov-validator/Cargo.toml b/cumulus/bin/pov-validator/Cargo.toml index 9be92960ad772217635c796f0f7962384aaebfe8..d7af29a6bcb25949b5cf0861735fd97e1cf1bb3d 100644 --- a/cumulus/bin/pov-validator/Cargo.toml +++ b/cumulus/bin/pov-validator/Cargo.toml @@ -9,18 +9,18 @@ homepage.workspace = true description = "A tool for validating PoVs locally" [dependencies] -codec.workspace = true +anyhow.workspace = true clap = { workspace = true, features = ["derive"] } -sc-executor.workspace = true -sp-io.workspace = true -sp-core.workspace = true -sp-maybe-compressed-blob.workspace = true +codec.workspace = true polkadot-node-primitives.workspace = true polkadot-parachain-primitives.workspace = true polkadot-primitives.workspace = true -anyhow.workspace = true -tracing.workspace = true +sc-executor.workspace = true +sp-core.workspace = true +sp-io.workspace = true +sp-maybe-compressed-blob.workspace = true tracing-subscriber.workspace = true +tracing.workspace = true [lints] workspace = true diff --git a/cumulus/client/cli/Cargo.toml b/cumulus/client/cli/Cargo.toml index 9b6f6b73960b416c481b43f053477c70e55b8495..bdc0236e368faeb24b6dc5e95aebe9e48c06a292 100644 --- a/cumulus/client/cli/Cargo.toml +++ b/cumulus/client/cli/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Parachain node CLI utilities." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -15,10 +17,10 @@ codec = { workspace = true, default-features = true } url = { workspace = true } # Substrate +sc-chain-spec = { workspace = true, default-features = true } sc-cli = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } -sc-chain-spec = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } diff --git a/cumulus/client/collator/Cargo.toml b/cumulus/client/collator/Cargo.toml index 6ebde0c2c653b8279ead203bdabeafd3ab8292e1..ff591c2d6e3ac210d21adaa59127f853befc2bf5 100644 --- a/cumulus/client/collator/Cargo.toml +++ b/cumulus/client/collator/Cargo.toml @@ -5,20 +5,22 @@ authors.workspace = true edition.workspace = true description = "Common node-side functionality and glue code to collate parachain blocks." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] -parking_lot = { workspace = true, default-features = true } codec = { features = ["derive"], workspace = true, default-features = true } futures = { workspace = true } +parking_lot = { workspace = true, default-features = true } tracing = { workspace = true, default-features = true } # Substrate sc-client-api = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } @@ -46,5 +48,5 @@ polkadot-node-subsystem-test-helpers = { workspace = true } # Cumulus cumulus-test-client = { workspace = true } -cumulus-test-runtime = { workspace = true } cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } +cumulus-test-runtime = { workspace = true } diff --git a/cumulus/client/consensus/aura/Cargo.toml b/cumulus/client/consensus/aura/Cargo.toml index 0bb2de6bb9b8f47baa76b6c288533cb24acb4ea3..8637133a5f5cb0de115dcb9c21a19240f4a910b8 100644 --- a/cumulus/client/consensus/aura/Cargo.toml +++ b/cumulus/client/consensus/aura/Cargo.toml @@ -5,6 +5,8 @@ version = "0.7.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -14,18 +16,19 @@ async-trait = { workspace = true } codec = { features = ["derive"], workspace = true, default-features = true } futures = { workspace = true } parking_lot = { workspace = true } -tracing = { workspace = true, default-features = true } schnellru = { workspace = true } tokio = { workspace = true, features = ["macros"] } +tracing = { workspace = true, default-features = true } # Substrate +prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-consensus-aura = { workspace = true, default-features = true } sc-consensus-babe = { workspace = true, default-features = true } sc-consensus-slots = { workspace = true, default-features = true } -sc-utils = { workspace = true, default-features = true } sc-telemetry = { workspace = true, default-features = true } +sc-utils = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-application-crypto = { workspace = true, default-features = true } sp-block-builder = { workspace = true, default-features = true } @@ -36,25 +39,30 @@ sp-core = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-timestamp = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } +sp-timestamp = { workspace = true, default-features = true } +sp-trie = { workspace = true, default-features = true } # Cumulus +cumulus-client-collator = { workspace = true, default-features = true } cumulus-client-consensus-common = { workspace = true, default-features = true } -cumulus-relay-chain-interface = { workspace = true, default-features = true } cumulus-client-consensus-proposer = { workspace = true, default-features = true } cumulus-client-parachain-inherent = { workspace = true, default-features = true } cumulus-primitives-aura = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } -cumulus-client-collator = { workspace = true, default-features = true } +cumulus-relay-chain-interface = { workspace = true, default-features = true } # Polkadot -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } + +[dev-dependencies] +cumulus-test-client = { workspace = true } +cumulus-test-relay-sproof-builder = { workspace = true } +sp-keyring = { workspace = true } [features] # Allows collator to use full PoV size for block building diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs index 2dbcf5eb58e96b42fa988380cb3ebe6a1654edee..7723de5a576a2bd87fe46687b2cf33aba2a931bc 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -336,6 +336,7 @@ where ); Some(super::can_build_upon::<_, _, P>( slot_now, + relay_slot, timestamp, block_hash, included_block, diff --git a/cumulus/client/consensus/aura/src/collators/mod.rs b/cumulus/client/consensus/aura/src/collators/mod.rs index 89070607fbaba9e919a9dd3e924ddb84f64261de..4c191c7d8a1a0882905fd77ec1ab0af90b213413 100644 --- a/cumulus/client/consensus/aura/src/collators/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/mod.rs @@ -22,19 +22,18 @@ use crate::collator::SlotClaim; use codec::Codec; -use cumulus_client_consensus_common::{ - self as consensus_common, load_abridged_host_configuration, ParentSearchParams, -}; +use cumulus_client_consensus_common::{self as consensus_common, ParentSearchParams}; use cumulus_primitives_aura::{AuraUnincludedSegmentApi, Slot}; use cumulus_primitives_core::{relay_chain::Hash as ParaHash, BlockT, ClaimQueueOffset}; use cumulus_relay_chain_interface::RelayChainInterface; +use polkadot_node_subsystem::messages::RuntimeApiRequest; use polkadot_node_subsystem_util::runtime::ClaimQueueSnapshot; use polkadot_primitives::{ - AsyncBackingParams, CoreIndex, Hash as RelayHash, Id as ParaId, OccupiedCoreAssumption, - ValidationCodeHash, + CoreIndex, Hash as RelayHash, Id as ParaId, OccupiedCoreAssumption, ValidationCodeHash, + DEFAULT_SCHEDULING_LOOKAHEAD, }; use sc_consensus_aura::{standalone as aura_internal, AuraApi}; -use sp_api::ProvideRuntimeApi; +use sp_api::{ApiExt, ProvideRuntimeApi, RuntimeApiInfo}; use sp_core::Pair; use sp_keystore::KeystorePtr; use sp_timestamp::Timestamp; @@ -44,12 +43,13 @@ pub mod lookahead; pub mod slot_based; // This is an arbitrary value which is likely guaranteed to exceed any reasonable -// limit, as it would correspond to 10 non-included blocks. +// limit, as it would correspond to 30 non-included blocks. // // Since we only search for parent blocks which have already been imported, // we can guarantee that all imported blocks respect the unincluded segment -// rules specified by the parachain's runtime and thus will never be too deep. -const PARENT_SEARCH_DEPTH: usize = 10; +// rules specified by the parachain's runtime and thus will never be too deep. This is just an extra +// sanity check. +const PARENT_SEARCH_DEPTH: usize = 30; /// Check the `local_validation_code_hash` against the validation code hash in the relay chain /// state. @@ -101,26 +101,43 @@ async fn check_validation_code_or_log( } } -/// Reads async backing parameters from the relay chain storage at the given relay parent. -async fn async_backing_params( +/// Fetch scheduling lookahead at given relay parent. +async fn scheduling_lookahead( relay_parent: RelayHash, relay_client: &impl RelayChainInterface, -) -> Option<AsyncBackingParams> { - match load_abridged_host_configuration(relay_parent, relay_client).await { - Ok(Some(config)) => Some(config.async_backing_params), - Ok(None) => { +) -> Option<u32> { + let runtime_api_version = relay_client + .version(relay_parent) + .await + .map_err(|e| { tracing::error!( - target: crate::LOG_TARGET, - "Active config is missing in relay chain storage", - ); - None - }, + target: super::LOG_TARGET, + error = ?e, + "Failed to fetch relay chain runtime version.", + ) + }) + .ok()?; + + let parachain_host_runtime_api_version = runtime_api_version + .api_version( + &<dyn polkadot_primitives::runtime_api::ParachainHost<polkadot_primitives::Block>>::ID, + ) + .unwrap_or_default(); + + if parachain_host_runtime_api_version < + RuntimeApiRequest::SCHEDULING_LOOKAHEAD_RUNTIME_REQUIREMENT + { + return None + } + + match relay_client.scheduling_lookahead(relay_parent).await { + Ok(scheduling_lookahead) => Some(scheduling_lookahead), Err(err) => { tracing::error!( target: crate::LOG_TARGET, ?err, ?relay_parent, - "Failed to read active config from relay chain client", + "Failed to fetch scheduling lookahead from relay chain", ); None }, @@ -160,7 +177,8 @@ async fn cores_scheduled_for_para( // Checks if we own the slot at the given block and whether there // is space in the unincluded segment. async fn can_build_upon<Block: BlockT, Client, P>( - slot: Slot, + para_slot: Slot, + relay_slot: Slot, timestamp: Timestamp, parent_hash: Block::Hash, included_block: Block::Hash, @@ -169,25 +187,35 @@ async fn can_build_upon<Block: BlockT, Client, P>( ) -> Option<SlotClaim<P::Public>> where Client: ProvideRuntimeApi<Block>, - Client::Api: AuraApi<Block, P::Public> + AuraUnincludedSegmentApi<Block>, + Client::Api: AuraApi<Block, P::Public> + AuraUnincludedSegmentApi<Block> + ApiExt<Block>, P: Pair, P::Public: Codec, P::Signature: Codec, { let runtime_api = client.runtime_api(); let authorities = runtime_api.authorities(parent_hash).ok()?; - let author_pub = aura_internal::claim_slot::<P>(slot, &authorities, keystore).await?; + let author_pub = aura_internal::claim_slot::<P>(para_slot, &authorities, keystore).await?; - // Here we lean on the property that building on an empty unincluded segment must always - // be legal. Skipping the runtime API query here allows us to seamlessly run this - // collator against chains which have not yet upgraded their runtime. - if parent_hash != included_block && - !runtime_api.can_build_upon(parent_hash, included_block, slot).ok()? - { - return None + // This function is typically called when we want to build block N. At that point, the + // unincluded segment in the runtime is unaware of the hash of block N-1. If the unincluded + // segment in the runtime is full, but block N-1 is the included block, the unincluded segment + // should have length 0 and we can build. Since the hash is not available to the runtime + // however, we need this extra check here. + if parent_hash == included_block { + return Some(SlotClaim::unchecked::<P>(author_pub, para_slot, timestamp)); } - Some(SlotClaim::unchecked::<P>(author_pub, slot, timestamp)) + let api_version = runtime_api + .api_version::<dyn AuraUnincludedSegmentApi<Block>>(parent_hash) + .ok() + .flatten()?; + + let slot = if api_version > 1 { relay_slot } else { para_slot }; + + runtime_api + .can_build_upon(parent_hash, included_block, slot) + .ok()? + .then(|| SlotClaim::unchecked::<P>(author_pub, para_slot, timestamp)) } /// Use [`cumulus_client_consensus_common::find_potential_parents`] to find parachain blocks that @@ -205,9 +233,10 @@ where let parent_search_params = ParentSearchParams { relay_parent, para_id, - ancestry_lookback: crate::collators::async_backing_params(relay_parent, relay_client) + ancestry_lookback: scheduling_lookahead(relay_parent, relay_client) .await - .map_or(0, |params| params.allowed_ancestry_len as usize), + .unwrap_or(DEFAULT_SCHEDULING_LOOKAHEAD) + .saturating_sub(1) as usize, max_depth: PARENT_SEARCH_DEPTH, ignore_alternative_branches: true, }; @@ -239,3 +268,116 @@ where .max_by_key(|a| a.depth) .map(|parent| (included_block, parent)) } + +#[cfg(test)] +mod tests { + use crate::collators::can_build_upon; + use codec::Encode; + use cumulus_primitives_aura::Slot; + use cumulus_primitives_core::BlockT; + use cumulus_relay_chain_interface::PHash; + use cumulus_test_client::{ + runtime::{Block, Hash}, + Client, DefaultTestClientBuilderExt, InitBlockBuilder, TestClientBuilder, + TestClientBuilderExt, + }; + use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; + use polkadot_primitives::HeadData; + use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy}; + use sp_consensus::BlockOrigin; + use sp_keystore::{Keystore, KeystorePtr}; + use sp_timestamp::Timestamp; + use std::sync::Arc; + + async fn import_block<I: BlockImport<Block>>( + importer: &I, + block: Block, + origin: BlockOrigin, + import_as_best: bool, + ) { + let (header, body) = block.deconstruct(); + + let mut block_import_params = BlockImportParams::new(origin, header); + block_import_params.fork_choice = Some(ForkChoiceStrategy::Custom(import_as_best)); + block_import_params.body = Some(body); + importer.import_block(block_import_params).await.unwrap(); + } + + fn sproof_with_parent_by_hash(client: &Client, hash: PHash) -> RelayStateSproofBuilder { + let header = client.header(hash).ok().flatten().expect("No header for parent block"); + let included = HeadData(header.encode()); + let mut builder = RelayStateSproofBuilder::default(); + builder.para_id = cumulus_test_client::runtime::PARACHAIN_ID.into(); + builder.included_para_head = Some(included); + + builder + } + async fn build_and_import_block(client: &Client, included: Hash) -> Block { + let sproof = sproof_with_parent_by_hash(client, included); + + let block_builder = client.init_block_builder(None, sproof).block_builder; + + let block = block_builder.build().unwrap().block; + + let origin = BlockOrigin::NetworkInitialSync; + import_block(client, block.clone(), origin, true).await; + block + } + + fn set_up_components() -> (Arc<Client>, KeystorePtr) { + let keystore = Arc::new(sp_keystore::testing::MemoryKeystore::new()) as Arc<_>; + for key in sp_keyring::Sr25519Keyring::iter() { + Keystore::sr25519_generate_new( + &*keystore, + sp_application_crypto::key_types::AURA, + Some(&key.to_seed()), + ) + .expect("Can insert key into MemoryKeyStore"); + } + (Arc::new(TestClientBuilder::new().build()), keystore) + } + + /// This tests a special scenario where the unincluded segment in the runtime + /// is full. We are calling `can_build_upon`, passing the last built block as the + /// included one. In the runtime we will not find the hash of the included block in the + /// unincluded segment. The `can_build_upon` runtime API would therefore return `false`, but + /// we are ensuring on the node side that we are are always able to build on the included block. + #[tokio::test] + async fn test_can_build_upon() { + let (client, keystore) = set_up_components(); + + let genesis_hash = client.chain_info().genesis_hash; + let mut last_hash = genesis_hash; + + // Fill up the unincluded segment tracker in the runtime. + while can_build_upon::<_, _, sp_consensus_aura::sr25519::AuthorityPair>( + Slot::from(u64::MAX), + Slot::from(u64::MAX), + Timestamp::default(), + last_hash, + genesis_hash, + &*client, + &keystore, + ) + .await + .is_some() + { + let block = build_and_import_block(&client, genesis_hash).await; + last_hash = block.header().hash(); + } + + // Blocks were built with the genesis hash set as included block. + // We call `can_build_upon` with the last built block as the included block. + let result = can_build_upon::<_, _, sp_consensus_aura::sr25519::AuthorityPair>( + Slot::from(u64::MAX), + Slot::from(u64::MAX), + Timestamp::default(), + last_hash, + last_hash, + &*client, + &keystore, + ) + .await; + assert!(result.is_some()); + } +} 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 42515123070468cc4c4355c30aa8a2e1bdec0e6c..48287555dea65f3ee6726728dd0ea1522aa2b586 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 @@ -23,30 +23,32 @@ use cumulus_primitives_aura::AuraUnincludedSegmentApi; use cumulus_primitives_core::{GetCoreSelectorApi, PersistedValidationData}; use cumulus_relay_chain_interface::RelayChainInterface; -use polkadot_primitives::{ - vstaging::{ClaimQueueOffset, CoreSelector, DEFAULT_CLAIM_QUEUE_OFFSET}, - BlockId, CoreIndex, Hash as RelayHash, Header as RelayHeader, Id as ParaId, - OccupiedCoreAssumption, -}; +use polkadot_primitives::{Block as RelayBlock, Id as ParaId}; use futures::prelude::*; use sc_client_api::{backend::AuxStore, BlockBackend, BlockOf, UsageProvider}; use sc_consensus::BlockImport; -use sp_api::{ApiExt, ProvideRuntimeApi}; +use sp_api::ProvideRuntimeApi; use sp_application_crypto::AppPublic; use sp_blockchain::HeaderBackend; use sp_consensus_aura::{AuraApi, Slot}; -use sp_core::{crypto::Pair, U256}; +use sp_core::crypto::Pair; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member, One}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member}; use sp_timestamp::Timestamp; -use std::{collections::BTreeSet, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use super::CollatorMessage; use crate::{ collator::{self as collator_util}, - collators::{check_validation_code_or_log, cores_scheduled_for_para}, + collators::{ + check_validation_code_or_log, + slot_based::{ + core_selector, + relay_chain_data_cache::{RelayChainData, RelayChainDataCache}, + }, + }, LOG_TARGET, }; @@ -218,7 +220,7 @@ where collator_util::Collator::<Block, P, _, _, _, _, _>::new(params) }; - let mut relay_chain_fetcher = RelayChainCachingFetcher::new(relay_client.clone(), para_id); + let mut relay_chain_data_cache = RelayChainDataCache::new(relay_client.clone(), para_id); loop { // We wait here until the next slot arrives. @@ -242,7 +244,7 @@ where // Retrieve the core selector. let (core_selector, claim_queue_offset) = - match core_selector(&*para_client, &parent).await { + match core_selector(&*para_client, parent.hash, *parent.header.number()) { Ok(core_selector) => core_selector, Err(err) => { tracing::trace!( @@ -259,7 +261,7 @@ where max_pov_size, scheduled_cores, claimed_cores, - }) = relay_chain_fetcher + }) = relay_chain_data_cache .get_mut_relay_chain_data(relay_parent, claim_queue_offset) .await else { @@ -300,8 +302,17 @@ where // on-chain data. collator.collator_service().check_block_status(parent_hash, &parent_header); + let Ok(relay_slot) = + sc_consensus_babe::find_pre_digest::<RelayBlock>(relay_parent_header) + .map(|babe_pre_digest| babe_pre_digest.slot()) + else { + tracing::error!(target: crate::LOG_TARGET, "Relay chain does not contain babe slot. This should never happen."); + continue; + }; + let slot_claim = match crate::collators::can_build_upon::<_, _, P>( para_slot.slot, + relay_slot, para_slot.timestamp, parent_hash, included_block, @@ -419,119 +430,3 @@ where } } } - -/// Contains relay chain data necessary for parachain block building. -#[derive(Clone)] -struct RelayChainData { - /// Current relay chain parent header. - pub relay_parent_header: RelayHeader, - /// The cores on which the para is scheduled at the configured claim queue offset. - pub scheduled_cores: Vec<CoreIndex>, - /// Maximum configured PoV size on the relay chain. - pub max_pov_size: u32, - /// The claimed cores at a relay parent. - pub claimed_cores: BTreeSet<CoreIndex>, -} - -/// Simple helper to fetch relay chain data and cache it based on the current relay chain best block -/// hash. -struct RelayChainCachingFetcher<RI> { - relay_client: RI, - para_id: ParaId, - last_data: Option<(RelayHash, RelayChainData)>, -} - -impl<RI> RelayChainCachingFetcher<RI> -where - RI: RelayChainInterface + Clone + 'static, -{ - pub fn new(relay_client: RI, para_id: ParaId) -> Self { - Self { relay_client, para_id, last_data: None } - } - - /// Fetch required [`RelayChainData`] from the relay chain. - /// If this data has been fetched in the past for the incoming hash, it will reuse - /// cached data. - pub async fn get_mut_relay_chain_data( - &mut self, - relay_parent: RelayHash, - claim_queue_offset: ClaimQueueOffset, - ) -> Result<&mut RelayChainData, ()> { - match &self.last_data { - Some((last_seen_hash, _)) if *last_seen_hash == relay_parent => { - tracing::trace!(target: crate::LOG_TARGET, %relay_parent, "Using cached data for relay parent."); - Ok(&mut self.last_data.as_mut().expect("last_data is Some").1) - }, - _ => { - tracing::trace!(target: crate::LOG_TARGET, %relay_parent, "Relay chain best block changed, fetching new data from relay chain."); - let data = self.update_for_relay_parent(relay_parent, claim_queue_offset).await?; - self.last_data = Some((relay_parent, data)); - Ok(&mut self.last_data.as_mut().expect("last_data was just set above").1) - }, - } - } - - /// Fetch fresh data from the relay chain for the given relay parent hash. - async fn update_for_relay_parent( - &self, - relay_parent: RelayHash, - claim_queue_offset: ClaimQueueOffset, - ) -> Result<RelayChainData, ()> { - let scheduled_cores = cores_scheduled_for_para( - relay_parent, - self.para_id, - &self.relay_client, - claim_queue_offset, - ) - .await; - - let Ok(Some(relay_parent_header)) = - self.relay_client.header(BlockId::Hash(relay_parent)).await - else { - tracing::warn!(target: crate::LOG_TARGET, "Unable to fetch latest relay chain block header."); - return Err(()) - }; - - let max_pov_size = match self - .relay_client - .persisted_validation_data(relay_parent, self.para_id, OccupiedCoreAssumption::Included) - .await - { - Ok(None) => return Err(()), - Ok(Some(pvd)) => pvd.max_pov_size, - Err(err) => { - tracing::error!(target: crate::LOG_TARGET, ?err, "Failed to gather information from relay-client"); - return Err(()) - }, - }; - - Ok(RelayChainData { - relay_parent_header, - scheduled_cores, - max_pov_size, - claimed_cores: BTreeSet::new(), - }) - } -} - -async fn core_selector<Block: BlockT, Client>( - para_client: &Client, - parent: &consensus_common::PotentialParent<Block>, -) -> Result<(CoreSelector, ClaimQueueOffset), sp_api::ApiError> -where - Client: ProvideRuntimeApi<Block> + Send + Sync, - Client::Api: GetCoreSelectorApi<Block>, -{ - let block_hash = parent.hash; - let runtime_api = para_client.runtime_api(); - - if runtime_api.has_api::<dyn GetCoreSelectorApi<Block>>(block_hash)? { - Ok(runtime_api.core_selector(block_hash)?) - } else { - let next_block_number: U256 = (*parent.header.number() + One::one()).into(); - - // If the runtime API does not support the core selector API, fallback to some default - // values. - Ok((CoreSelector(next_block_number.byte(0)), ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET))) - } -} diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/block_import.rs b/cumulus/client/consensus/aura/src/collators/slot_based/block_import.rs new file mode 100644 index 0000000000000000000000000000000000000000..9c53da6a6b7d7d63845ebbf8527944da3d8d9d73 --- /dev/null +++ b/cumulus/client/consensus/aura/src/collators/slot_based/block_import.rs @@ -0,0 +1,144 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see <http://www.gnu.org/licenses/>. + +use futures::{stream::FusedStream, StreamExt}; +use sc_consensus::{BlockImport, StateAction}; +use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; +use sp_api::{ApiExt, CallApiAt, CallContext, Core, ProvideRuntimeApi, StorageProof}; +use sp_runtime::traits::{Block as BlockT, Header as _}; +use sp_trie::proof_size_extension::ProofSizeExt; +use std::sync::Arc; + +/// Handle for receiving the block and the storage proof from the [`SlotBasedBlockImport`]. +/// +/// This handle should be passed to [`Params`](super::Params) or can also be dropped if the node is +/// not running as collator. +pub struct SlotBasedBlockImportHandle<Block> { + receiver: TracingUnboundedReceiver<(Block, StorageProof)>, +} + +impl<Block> SlotBasedBlockImportHandle<Block> { + /// Returns the next item. + /// + /// The future will never return when the internal channel is closed. + pub async fn next(&mut self) -> (Block, StorageProof) { + loop { + if self.receiver.is_terminated() { + futures::pending!() + } else if let Some(res) = self.receiver.next().await { + return res + } + } + } +} + +/// Special block import for the slot based collator. +pub struct SlotBasedBlockImport<Block, BI, Client> { + inner: BI, + client: Arc<Client>, + sender: TracingUnboundedSender<(Block, StorageProof)>, +} + +impl<Block, BI, Client> SlotBasedBlockImport<Block, BI, Client> { + /// Create a new instance. + /// + /// The returned [`SlotBasedBlockImportHandle`] needs to be passed to the + /// [`Params`](super::Params), so that this block import instance can communicate with the + /// collation task. If the node is not running as a collator, just dropping the handle is fine. + pub fn new(inner: BI, client: Arc<Client>) -> (Self, SlotBasedBlockImportHandle<Block>) { + let (sender, receiver) = tracing_unbounded("SlotBasedBlockImportChannel", 1000); + + (Self { sender, client, inner }, SlotBasedBlockImportHandle { receiver }) + } +} + +impl<Block, BI: Clone, Client> Clone for SlotBasedBlockImport<Block, BI, Client> { + fn clone(&self) -> Self { + Self { inner: self.inner.clone(), client: self.client.clone(), sender: self.sender.clone() } + } +} + +#[async_trait::async_trait] +impl<Block, BI, Client> BlockImport<Block> for SlotBasedBlockImport<Block, BI, Client> +where + Block: BlockT, + BI: BlockImport<Block> + Send + Sync, + BI::Error: Into<sp_consensus::Error>, + Client: ProvideRuntimeApi<Block> + CallApiAt<Block> + Send + Sync, + Client::StateBackend: Send, + Client::Api: Core<Block>, +{ + type Error = sp_consensus::Error; + + async fn check_block( + &self, + block: sc_consensus::BlockCheckParams<Block>, + ) -> Result<sc_consensus::ImportResult, Self::Error> { + self.inner.check_block(block).await.map_err(Into::into) + } + + async fn import_block( + &self, + mut params: sc_consensus::BlockImportParams<Block>, + ) -> Result<sc_consensus::ImportResult, Self::Error> { + // If the channel exists and it is required to execute the block, we will execute the block + // here. This is done to collect the storage proof and to prevent re-execution, we push + // downwards the state changes. `StateAction::ApplyChanges` is ignored, because it either + // means that the node produced the block itself or the block was imported via state sync. + if !self.sender.is_closed() && !matches!(params.state_action, StateAction::ApplyChanges(_)) + { + let mut runtime_api = self.client.runtime_api(); + + runtime_api.set_call_context(CallContext::Onchain); + + runtime_api.record_proof(); + let recorder = runtime_api + .proof_recorder() + .expect("Proof recording is enabled in the line above; qed."); + runtime_api.register_extension(ProofSizeExt::new(recorder)); + + let parent_hash = *params.header.parent_hash(); + + let block = Block::new(params.header.clone(), params.body.clone().unwrap_or_default()); + + runtime_api + .execute_block(parent_hash, block.clone()) + .map_err(|e| Box::new(e) as Box<_>)?; + + let storage_proof = + runtime_api.extract_proof().expect("Proof recording was enabled above; qed"); + + let state = self.client.state_at(parent_hash).map_err(|e| Box::new(e) as Box<_>)?; + let gen_storage_changes = runtime_api + .into_storage_changes(&state, parent_hash) + .map_err(sp_consensus::Error::ChainLookup)?; + + if params.header.state_root() != &gen_storage_changes.transaction_storage_root { + return Err(sp_consensus::Error::Other(Box::new( + sp_blockchain::Error::InvalidStateRoot, + ))) + } + + params.state_action = StateAction::ApplyChanges(sc_consensus::StorageChanges::Changes( + gen_storage_changes, + )); + + let _ = self.sender.unbounded_send((block, storage_proof)); + } + + self.inner.import_block(params).await.map_err(Into::into) + } +} diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/collation_task.rs b/cumulus/client/consensus/aura/src/collators/slot_based/collation_task.rs index 5b8151f6302c411469a3258135de2618fc6f5d48..abaeb8319a4028388dfb5a9d2a93067c741b7c89 100644 --- a/cumulus/client/consensus/aura/src/collators/slot_based/collation_task.rs +++ b/cumulus/client/consensus/aura/src/collators/slot_based/collation_task.rs @@ -47,6 +47,8 @@ pub struct Params<Block: BlockT, RClient, CS> { pub collator_service: CS, /// Receiver channel for communication with the block builder task. pub collator_receiver: TracingUnboundedReceiver<CollatorMessage<Block>>, + /// The handle from the special slot based block import. + pub block_import_handle: super::SlotBasedBlockImportHandle<Block>, } /// Asynchronously executes the collation task for a parachain. @@ -55,28 +57,49 @@ pub struct Params<Block: BlockT, RClient, CS> { /// collations to the relay chain. It listens for new best relay chain block notifications and /// handles collator messages. If our parachain is scheduled on a core and we have a candidate, /// the task will build a collation and send it to the relay chain. -pub async fn run_collation_task<Block, RClient, CS>(mut params: Params<Block, RClient, CS>) -where +pub async fn run_collation_task<Block, RClient, CS>( + Params { + relay_client, + collator_key, + para_id, + reinitialize, + collator_service, + mut collator_receiver, + mut block_import_handle, + }: Params<Block, RClient, CS>, +) where Block: BlockT, CS: CollatorServiceInterface<Block> + Send + Sync + 'static, RClient: RelayChainInterface + Clone + 'static, { - let Ok(mut overseer_handle) = params.relay_client.overseer_handle() else { + let Ok(mut overseer_handle) = relay_client.overseer_handle() else { tracing::error!(target: LOG_TARGET, "Failed to get overseer handle."); return }; cumulus_client_collator::initialize_collator_subsystems( &mut overseer_handle, - params.collator_key, - params.para_id, - params.reinitialize, + collator_key, + para_id, + reinitialize, ) .await; - let collator_service = params.collator_service; - while let Some(collator_message) = params.collator_receiver.next().await { - handle_collation_message(collator_message, &collator_service, &mut overseer_handle).await; + loop { + futures::select! { + collator_message = collator_receiver.next() => { + let Some(message) = collator_message else { + return; + }; + + handle_collation_message(message, &collator_service, &mut overseer_handle).await; + }, + block_import_msg = block_import_handle.next().fuse() => { + // TODO: Implement me. + // Issue: https://github.com/paritytech/polkadot-sdk/issues/6495 + let _ = block_import_msg; + } + } } } diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs b/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs index 18e63681d578cf7758e2e3dd78b422365af4a935..ab78b31fbd802f9a26ea6bbd6d461c345817610f 100644 --- a/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs @@ -35,30 +35,35 @@ use cumulus_client_collator::service::ServiceInterface as CollatorServiceInterfa use cumulus_client_consensus_common::{self as consensus_common, ParachainBlockImportMarker}; use cumulus_client_consensus_proposer::ProposerInterface; use cumulus_primitives_aura::AuraUnincludedSegmentApi; -use cumulus_primitives_core::GetCoreSelectorApi; +use cumulus_primitives_core::{ClaimQueueOffset, CoreSelector, GetCoreSelectorApi}; use cumulus_relay_chain_interface::RelayChainInterface; use futures::FutureExt; use polkadot_primitives::{ - CollatorPair, CoreIndex, Hash as RelayHash, Id as ParaId, ValidationCodeHash, + vstaging::DEFAULT_CLAIM_QUEUE_OFFSET, CollatorPair, CoreIndex, Hash as RelayHash, Id as ParaId, + ValidationCodeHash, }; use sc_client_api::{backend::AuxStore, BlockBackend, BlockOf, UsageProvider}; use sc_consensus::BlockImport; use sc_utils::mpsc::tracing_unbounded; -use sp_api::ProvideRuntimeApi; +use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_application_crypto::AppPublic; use sp_blockchain::HeaderBackend; use sp_consensus_aura::AuraApi; -use sp_core::{crypto::Pair, traits::SpawnNamed}; +use sp_core::{crypto::Pair, traits::SpawnNamed, U256}; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; -use sp_runtime::traits::{Block as BlockT, Member}; +use sp_runtime::traits::{Block as BlockT, Member, NumberFor, One}; use std::{sync::Arc, time::Duration}; +pub use block_import::{SlotBasedBlockImport, SlotBasedBlockImportHandle}; + mod block_builder_task; +mod block_import; mod collation_task; +mod relay_chain_data_cache; /// Parameters for [`run`]. -pub struct Params<BI, CIDP, Client, Backend, RClient, CHP, Proposer, CS, Spawner> { +pub struct Params<Block, BI, CIDP, Client, Backend, RClient, CHP, Proposer, CS, Spawner> { /// Inherent data providers. Only non-consensus inherent data should be provided, i.e. /// the timestamp, slot, and paras inherents should be omitted, as they are set by this /// collator. @@ -90,6 +95,8 @@ pub struct Params<BI, CIDP, Client, Backend, RClient, CHP, Proposer, CS, Spawner /// Drift slots by a fixed duration. This can be used to create more preferrable authoring /// timings. pub slot_drift: Duration, + /// The handle returned by [`SlotBasedBlockImport`]. + pub block_import_handle: SlotBasedBlockImportHandle<Block>, /// Spawner for spawning futures. pub spawner: Spawner, } @@ -111,8 +118,9 @@ pub fn run<Block, P, BI, CIDP, Client, Backend, RClient, CHP, Proposer, CS, Spaw authoring_duration, reinitialize, slot_drift, + block_import_handle, spawner, - }: Params<BI, CIDP, Client, Backend, RClient, CHP, Proposer, CS, Spawner>, + }: Params<Block, BI, CIDP, Client, Backend, RClient, CHP, Proposer, CS, Spawner>, ) where Block: BlockT, Client: ProvideRuntimeApi<Block> @@ -147,6 +155,7 @@ pub fn run<Block, P, BI, CIDP, Client, Backend, RClient, CHP, Proposer, CS, Spaw reinitialize, collator_service: collator_service.clone(), collator_receiver: rx, + block_import_handle, }; let collation_task_fut = run_collation_task::<Block, _, _>(collator_task_params); @@ -197,3 +206,26 @@ struct CollatorMessage<Block: BlockT> { /// Core index that this block should be submitted on pub core_index: CoreIndex, } + +/// Fetch the `CoreSelector` and `ClaimQueueOffset` for `parent_hash`. +fn core_selector<Block: BlockT, Client>( + para_client: &Client, + parent_hash: Block::Hash, + parent_number: NumberFor<Block>, +) -> Result<(CoreSelector, ClaimQueueOffset), sp_api::ApiError> +where + Client: ProvideRuntimeApi<Block> + Send + Sync, + Client::Api: GetCoreSelectorApi<Block>, +{ + let runtime_api = para_client.runtime_api(); + + if runtime_api.has_api::<dyn GetCoreSelectorApi<Block>>(parent_hash)? { + Ok(runtime_api.core_selector(parent_hash)?) + } else { + let next_block_number: U256 = (parent_number + One::one()).into(); + + // If the runtime API does not support the core selector API, fallback to some default + // values. + Ok((CoreSelector(next_block_number.byte(0)), ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET))) + } +} diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/relay_chain_data_cache.rs b/cumulus/client/consensus/aura/src/collators/slot_based/relay_chain_data_cache.rs new file mode 100644 index 0000000000000000000000000000000000000000..be30ec2f747da1c66b4241e99cbf1a3a580df44c --- /dev/null +++ b/cumulus/client/consensus/aura/src/collators/slot_based/relay_chain_data_cache.rs @@ -0,0 +1,127 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Utility for caching [`RelayChainData`] for different relay blocks. + +use crate::collators::cores_scheduled_for_para; +use cumulus_primitives_core::ClaimQueueOffset; +use cumulus_relay_chain_interface::RelayChainInterface; +use polkadot_primitives::{ + CoreIndex, Hash as RelayHash, Header as RelayHeader, Id as ParaId, OccupiedCoreAssumption, +}; +use sp_runtime::generic::BlockId; +use std::collections::BTreeSet; + +/// Contains relay chain data necessary for parachain block building. +#[derive(Clone)] +pub struct RelayChainData { + /// Current relay chain parent header. + pub relay_parent_header: RelayHeader, + /// The cores on which the para is scheduled at the configured claim queue offset. + pub scheduled_cores: Vec<CoreIndex>, + /// Maximum configured PoV size on the relay chain. + pub max_pov_size: u32, + /// The claimed cores at a relay parent. + pub claimed_cores: BTreeSet<CoreIndex>, +} + +/// Simple helper to fetch relay chain data and cache it based on the current relay chain best block +/// hash. +pub struct RelayChainDataCache<RI> { + relay_client: RI, + para_id: ParaId, + cached_data: schnellru::LruMap<RelayHash, RelayChainData>, +} + +impl<RI> RelayChainDataCache<RI> +where + RI: RelayChainInterface + Clone + 'static, +{ + pub fn new(relay_client: RI, para_id: ParaId) -> Self { + Self { + relay_client, + para_id, + // 50 cached relay chain blocks should be more than enough. + cached_data: schnellru::LruMap::new(schnellru::ByLength::new(50)), + } + } + + /// Fetch required [`RelayChainData`] from the relay chain. + /// If this data has been fetched in the past for the incoming hash, it will reuse + /// cached data. + pub async fn get_mut_relay_chain_data( + &mut self, + relay_parent: RelayHash, + claim_queue_offset: ClaimQueueOffset, + ) -> Result<&mut RelayChainData, ()> { + let insert_data = if self.cached_data.peek(&relay_parent).is_some() { + tracing::trace!(target: crate::LOG_TARGET, %relay_parent, "Using cached data for relay parent."); + None + } else { + tracing::trace!(target: crate::LOG_TARGET, %relay_parent, "Relay chain best block changed, fetching new data from relay chain."); + Some(self.update_for_relay_parent(relay_parent, claim_queue_offset).await?) + }; + + Ok(self + .cached_data + .get_or_insert(relay_parent, || { + insert_data.expect("`insert_data` exists if not cached yet; qed") + }) + .expect("There is space for at least one element; qed")) + } + + /// Fetch fresh data from the relay chain for the given relay parent hash. + async fn update_for_relay_parent( + &self, + relay_parent: RelayHash, + claim_queue_offset: ClaimQueueOffset, + ) -> Result<RelayChainData, ()> { + let scheduled_cores = cores_scheduled_for_para( + relay_parent, + self.para_id, + &self.relay_client, + claim_queue_offset, + ) + .await; + + let Ok(Some(relay_parent_header)) = + self.relay_client.header(BlockId::Hash(relay_parent)).await + else { + tracing::warn!(target: crate::LOG_TARGET, "Unable to fetch latest relay chain block header."); + return Err(()) + }; + + let max_pov_size = match self + .relay_client + .persisted_validation_data(relay_parent, self.para_id, OccupiedCoreAssumption::Included) + .await + { + Ok(None) => return Err(()), + Ok(Some(pvd)) => pvd.max_pov_size, + Err(err) => { + tracing::error!(target: crate::LOG_TARGET, ?err, "Failed to gather information from relay-client"); + return Err(()) + }, + }; + + Ok(RelayChainData { + relay_parent_header, + scheduled_cores, + max_pov_size, + claimed_cores: BTreeSet::new(), + }) + } +} diff --git a/cumulus/client/consensus/aura/src/equivocation_import_queue.rs b/cumulus/client/consensus/aura/src/equivocation_import_queue.rs index 68f2d37c8748863be879134d3fd0849adf5efb11..a3bc90f53c25d7964556f8ae17fb66092e343ec6 100644 --- a/cumulus/client/consensus/aura/src/equivocation_import_queue.rs +++ b/cumulus/client/consensus/aura/src/equivocation_import_queue.rs @@ -68,7 +68,8 @@ impl NaiveEquivocationDefender { } } -struct Verifier<P, Client, Block, CIDP> { +/// A parachain block import verifier that checks for equivocation limits within each slot. +pub struct Verifier<P, Client, Block, CIDP> { client: Arc<Client>, create_inherent_data_providers: CIDP, defender: Mutex<NaiveEquivocationDefender>, @@ -76,6 +77,34 @@ struct Verifier<P, Client, Block, CIDP> { _phantom: std::marker::PhantomData<fn() -> (Block, P)>, } +impl<P, Client, Block, CIDP> Verifier<P, Client, Block, CIDP> +where + P: Pair, + P::Signature: Codec, + P::Public: Codec + Debug, + Block: BlockT, + Client: ProvideRuntimeApi<Block> + Send + Sync, + <Client as ProvideRuntimeApi<Block>>::Api: BlockBuilderApi<Block> + AuraApi<Block, P::Public>, + + CIDP: CreateInherentDataProviders<Block, ()>, +{ + /// Creates a new Verifier instance for handling parachain block import verification in Aura + /// consensus. + pub fn new( + client: Arc<Client>, + inherent_data_provider: CIDP, + telemetry: Option<TelemetryHandle>, + ) -> Self { + Self { + client, + create_inherent_data_providers: inherent_data_provider, + defender: Mutex::new(NaiveEquivocationDefender::default()), + telemetry, + _phantom: std::marker::PhantomData, + } + } +} + #[async_trait::async_trait] impl<P, Client, Block, CIDP> VerifierT<Block> for Verifier<P, Client, Block, CIDP> where @@ -97,6 +126,7 @@ where // This is done for example when gap syncing and it is expected that the block after the gap // was checked/chosen properly, e.g. by warp syncing to this block using a finality proof. if block_params.state_action.skip_execution_checks() || block_params.with_state() { + block_params.fork_choice = Some(ForkChoiceStrategy::Custom(block_params.with_state())); return Ok(block_params) } diff --git a/cumulus/client/consensus/common/Cargo.toml b/cumulus/client/consensus/common/Cargo.toml index 4bc2f1d1e600e5f82faaf7cfa84a3b831cf085b7..5bc5160601e757d92b7e732833a62589f846ea62 100644 --- a/cumulus/client/consensus/common/Cargo.toml +++ b/cumulus/client/consensus/common/Cargo.toml @@ -5,6 +5,8 @@ version = "0.7.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -18,6 +20,7 @@ log = { workspace = true, default-features = true } tracing = { workspace = true, default-features = true } # Substrate +prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-consensus-babe = { workspace = true, default-features = true } @@ -29,15 +32,14 @@ sp-runtime = { workspace = true, default-features = true } sp-timestamp = { workspace = true, default-features = true } sp-trie = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } # Polkadot polkadot-primitives = { workspace = true, default-features = true } # Cumulus +cumulus-client-pov-recovery = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } cumulus-relay-chain-interface = { workspace = true, default-features = true } -cumulus-client-pov-recovery = { workspace = true, default-features = true } schnellru = { workspace = true } [dev-dependencies] diff --git a/cumulus/client/consensus/common/src/tests.rs b/cumulus/client/consensus/common/src/tests.rs index 79e620db3bfa0c82860fa5fcce28a8c5fea5b975..2eff183fc47375e7cff8633798c2d33384b2060e 100644 --- a/cumulus/client/consensus/common/src/tests.rs +++ b/cumulus/client/consensus/common/src/tests.rs @@ -284,6 +284,10 @@ impl RelayChainInterface for Relaychain { ) -> RelayChainResult<Vec<u8>> { unimplemented!("Not needed for test") } + + async fn scheduling_lookahead(&self, _: PHash) -> RelayChainResult<u32> { + unimplemented!("Not needed for test") + } } fn sproof_with_best_parent(client: &Client) -> RelayStateSproofBuilder { diff --git a/cumulus/client/consensus/proposer/Cargo.toml b/cumulus/client/consensus/proposer/Cargo.toml index bb760ae03f4df0dfae60fce06ed49e223763a8e6..e391481bc445288276a93243e9561947672129aa 100644 --- a/cumulus/client/consensus/proposer/Cargo.toml +++ b/cumulus/client/consensus/proposer/Cargo.toml @@ -5,6 +5,8 @@ version = "0.7.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/client/consensus/relay-chain/Cargo.toml b/cumulus/client/consensus/relay-chain/Cargo.toml index f3ee6fc2f7d257ff2be86c3d9a0096ea78d30be8..fdc343dc65deccb47c431da58d8c554f1a3106ac 100644 --- a/cumulus/client/consensus/relay-chain/Cargo.toml +++ b/cumulus/client/consensus/relay-chain/Cargo.toml @@ -5,6 +5,8 @@ version = "0.7.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -16,6 +18,7 @@ parking_lot = { workspace = true, default-features = true } tracing = { workspace = true, default-features = true } # Substrate +prometheus-endpoint = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-block-builder = { workspace = true, default-features = true } @@ -24,7 +27,6 @@ sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } # Cumulus cumulus-client-consensus-common = { workspace = true, default-features = true } diff --git a/cumulus/client/network/Cargo.toml b/cumulus/client/network/Cargo.toml index bc67678eedeb199d57aad6f35f5be386231b841e..3fb7eac591aaeb1ed1ae043adbd5979bb754a6c0 100644 --- a/cumulus/client/network/Cargo.toml +++ b/cumulus/client/network/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true description = "Cumulus-specific networking protocol" edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -19,28 +21,27 @@ tracing = { workspace = true, default-features = true } # Substrate sc-client-api = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } # Polkadot polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } # Cumulus cumulus-relay-chain-interface = { workspace = true, default-features = true } [dev-dependencies] -portpicker = { workspace = true } +rstest = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } url = { workspace = true } -rstest = { workspace = true } # Substrate sc-cli = { workspace = true, default-features = true } @@ -49,7 +50,6 @@ sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } # Polkadot polkadot-test-client = { workspace = true } diff --git a/cumulus/client/network/src/tests.rs b/cumulus/client/network/src/tests.rs index cccb710bf18f1122d8675ee0bfca23e2075b3d47..3bdcdaae4ef67e679b1655149383e78c8ec03c36 100644 --- a/cumulus/client/network/src/tests.rs +++ b/cumulus/client/network/src/tests.rs @@ -347,6 +347,10 @@ impl RelayChainInterface for DummyRelayChainInterface { ) -> RelayChainResult<Vec<u8>> { unimplemented!("Not needed for test") } + + async fn scheduling_lookahead(&self, _: PHash) -> RelayChainResult<u32> { + unimplemented!("Not needed for test") + } } fn make_validator_and_api() -> ( diff --git a/cumulus/client/parachain-inherent/Cargo.toml b/cumulus/client/parachain-inherent/Cargo.toml index 0d82cf64874322c2d9e6e2cb64ed742d35bc58e5..4f53e2bc1bc280a32be3e22df09cb5f1ec841fd9 100644 --- a/cumulus/client/parachain-inherent/Cargo.toml +++ b/cumulus/client/parachain-inherent/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Inherent that needs to be present in every parachain block. Contains messages and a relay chain storage-proof." license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [dependencies] async-trait = { workspace = true } diff --git a/cumulus/client/parachain-inherent/src/mock.rs b/cumulus/client/parachain-inherent/src/mock.rs index 950cba2aaa7dec9e9dc8312191722f95f820cd6d..8dbc6ace0f06cbf426ea33cdf6f832bfcb5670ed 100644 --- a/cumulus/client/parachain-inherent/src/mock.rs +++ b/cumulus/client/parachain-inherent/src/mock.rs @@ -17,20 +17,18 @@ use crate::{ParachainInherentData, INHERENT_IDENTIFIER}; use codec::Decode; use cumulus_primitives_core::{ - relay_chain, InboundDownwardMessage, InboundHrmpMessage, ParaId, PersistedValidationData, + relay_chain, + relay_chain::{Slot, UpgradeGoAhead}, + InboundDownwardMessage, InboundHrmpMessage, ParaId, PersistedValidationData, }; use cumulus_primitives_parachain_inherent::MessageQueueChain; +use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use sc_client_api::{Backend, StorageProvider}; use sp_crypto_hashing::twox_128; use sp_inherents::{InherentData, InherentDataProvider}; use sp_runtime::traits::Block; use std::collections::BTreeMap; -use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; - -/// Relay chain slot duration, in milliseconds. -pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; - /// Inherent data provider that supplies mocked validation data. /// /// This is useful when running a node that is not actually backed by any relay chain. @@ -68,10 +66,12 @@ pub struct MockValidationDataInherentDataProvider<R = ()> { pub xcm_config: MockXcmConfig, /// Inbound downward XCM messages to be injected into the block. pub raw_downward_messages: Vec<Vec<u8>>, - // Inbound Horizontal messages sorted by channel. + /// Inbound Horizontal messages sorted by channel. pub raw_horizontal_messages: Vec<(ParaId, Vec<u8>)>, - // Additional key-value pairs that should be injected. + /// Additional key-value pairs that should be injected. pub additional_key_values: Option<Vec<(Vec<u8>, Vec<u8>)>>, + /// Whether upgrade go ahead should be set. + pub upgrade_go_ahead: Option<UpgradeGoAhead>, } /// Something that can generate randomness. @@ -173,9 +173,9 @@ impl<R: Send + Sync + GenerateRandomness<u64>> InherentDataProvider // Calculate the mocked relay block based on the current para block let relay_parent_number = self.relay_offset + self.relay_blocks_per_para_block * self.current_para_block; - sproof_builder.current_slot = - ((relay_parent_number / RELAY_CHAIN_SLOT_DURATION_MILLIS) as u64).into(); + sproof_builder.current_slot = Slot::from(relay_parent_number as u64); + sproof_builder.upgrade_go_ahead = self.upgrade_go_ahead; // Process the downward messages and set up the correct head let mut downward_messages = Vec::new(); let mut dmq_mqc = MessageQueueChain::new(self.xcm_config.starting_dmq_mqc_head); diff --git a/cumulus/client/pov-recovery/Cargo.toml b/cumulus/client/pov-recovery/Cargo.toml index 3127dd26fcaa67d137ddbc867f30a76ff3faeedb..7c85318bdde3cee1ab74252cfa56fdb021d35d70 100644 --- a/cumulus/client/pov-recovery/Cargo.toml +++ b/cumulus/client/pov-recovery/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true description = "Parachain PoV recovery" edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -19,10 +21,10 @@ tracing = { workspace = true, default-features = true } # Substrate sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-maybe-compressed-blob = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } # Polkadot @@ -32,19 +34,18 @@ polkadot-overseer = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } # Cumulus +async-trait = { workspace = true } cumulus-primitives-core = { workspace = true, default-features = true } cumulus-relay-chain-interface = { workspace = true, default-features = true } -async-trait = { workspace = true } [dev-dependencies] -rstest = { workspace = true } -tokio = { features = ["macros"], workspace = true, default-features = true } -portpicker = { workspace = true } -sp-blockchain = { workspace = true, default-features = true } +assert_matches = { workspace = true } cumulus-test-client = { workspace = true } +rstest = { workspace = true } sc-utils = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -assert_matches = { workspace = true } +tokio = { features = ["macros"], workspace = true, default-features = true } # Cumulus cumulus-test-service = { workspace = true } @@ -52,4 +53,3 @@ cumulus-test-service = { workspace = true } # Substrate sc-cli = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } diff --git a/cumulus/client/pov-recovery/src/tests.rs b/cumulus/client/pov-recovery/src/tests.rs index 91b462e06bf87bf9fb264e638daa77547b8ce660..be890d01dd96721402e00d5eaa07fd763e03079c 100644 --- a/cumulus/client/pov-recovery/src/tests.rs +++ b/cumulus/client/pov-recovery/src/tests.rs @@ -503,6 +503,10 @@ impl RelayChainInterface for Relaychain { ) -> RelayChainResult<Vec<u8>> { unimplemented!("Not needed for test") } + + async fn scheduling_lookahead(&self, _: PHash) -> RelayChainResult<u32> { + unimplemented!("Not needed for test") + } } fn make_candidate_chain(candidate_number_range: Range<u32>) -> Vec<CommittedCandidateReceipt> { diff --git a/cumulus/client/relay-chain-inprocess-interface/Cargo.toml b/cumulus/client/relay-chain-inprocess-interface/Cargo.toml index 6f1b74191be79a3c90100a18df1b20b850e0f4b2..1307ec76de85ccbe2d36f2ba31b776205165fa8f 100644 --- a/cumulus/client/relay-chain-inprocess-interface/Cargo.toml +++ b/cumulus/client/relay-chain-inprocess-interface/Cargo.toml @@ -5,6 +5,8 @@ version = "0.7.0" edition.workspace = true description = "Implementation of the RelayChainInterface trait for Polkadot full-nodes." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -17,9 +19,9 @@ futures-timer = { workspace = true } # Substrate sc-cli = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } +sc-sysinfo = { workspace = true, default-features = true } sc-telemetry = { workspace = true, default-features = true } sc-tracing = { workspace = true, default-features = true } -sc-sysinfo = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } @@ -40,9 +42,6 @@ cumulus-relay-chain-interface = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } # Polkadot +metered = { features = ["futures_channel"], workspace = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-test-client = { workspace = true } -metered = { features = ["futures_channel"], workspace = true } - -# Cumulus -cumulus-test-service = { workspace = true } diff --git a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs index f29e7f3ed7c7c5eb55cbe80a2c428c112e58cd60..e5daf8ee7b5878b5dbae3401b3688f916434ebbe 100644 --- a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs +++ b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs @@ -316,6 +316,10 @@ impl RelayChainInterface for RelayChainInProcessInterface { ) -> RelayChainResult<BTreeMap<CoreIndex, VecDeque<ParaId>>> { Ok(self.full_client.runtime_api().claim_queue(hash)?) } + + async fn scheduling_lookahead(&self, hash: PHash) -> RelayChainResult<u32> { + Ok(self.full_client.runtime_api().scheduling_lookahead(hash)?) + } } pub enum BlockCheckStatus { diff --git a/cumulus/client/relay-chain-interface/Cargo.toml b/cumulus/client/relay-chain-interface/Cargo.toml index a496fab050dd7fc3cba69c8a6812c5f07b27a6d4..659d3b0f5b274a9d1ea3cfd2a574c2fa98a0cc5b 100644 --- a/cumulus/client/relay-chain-interface/Cargo.toml +++ b/cumulus/client/relay-chain-interface/Cargo.toml @@ -5,6 +5,8 @@ version = "0.7.0" edition.workspace = true description = "Common interface for different relay chain datasources." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -14,14 +16,14 @@ polkadot-overseer = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } sp-version = { workspace = true } -futures = { workspace = true } async-trait = { workspace = true } -thiserror = { workspace = true } -jsonrpsee-core = { workspace = true } codec = { workspace = true, default-features = true } +futures = { workspace = true } +jsonrpsee-core = { workspace = true } +thiserror = { workspace = true } diff --git a/cumulus/client/relay-chain-interface/src/lib.rs b/cumulus/client/relay-chain-interface/src/lib.rs index 4a49eada292ac83ff489e4a8f12d2aa60e7ef300..f1d5e013ba6a21a9f267ef05ea101fddaa973003 100644 --- a/cumulus/client/relay-chain-interface/src/lib.rs +++ b/cumulus/client/relay-chain-interface/src/lib.rs @@ -244,6 +244,9 @@ pub trait RelayChainInterface: Send + Sync { &self, relay_parent: PHash, ) -> RelayChainResult<BTreeMap<CoreIndex, VecDeque<ParaId>>>; + + /// Fetch the scheduling lookahead value. + async fn scheduling_lookahead(&self, relay_parent: PHash) -> RelayChainResult<u32>; } #[async_trait] @@ -398,6 +401,10 @@ where ) -> RelayChainResult<BTreeMap<CoreIndex, VecDeque<ParaId>>> { (**self).claim_queue(relay_parent).await } + + async fn scheduling_lookahead(&self, relay_parent: PHash) -> RelayChainResult<u32> { + (**self).scheduling_lookahead(relay_parent).await + } } /// Helper function to call an arbitrary runtime API using a `RelayChainInterface` client. diff --git a/cumulus/client/relay-chain-minimal-node/Cargo.toml b/cumulus/client/relay-chain-minimal-node/Cargo.toml index 95ecadc8bd06ec52b0089dd39143e78e1a27811a..5b1e30cea9ba116cbe4bcc1b3626641658b469d2 100644 --- a/cumulus/client/relay-chain-minimal-node/Cargo.toml +++ b/cumulus/client/relay-chain-minimal-node/Cargo.toml @@ -5,43 +5,45 @@ version = "0.7.0" edition.workspace = true description = "Minimal node implementation to be used in tandem with RPC or light-client mode." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] # polkadot deps -polkadot-primitives = { workspace = true, default-features = true } polkadot-core-primitives = { workspace = true, default-features = true } -polkadot-overseer = { workspace = true, default-features = true } -polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } +polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-overseer = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } polkadot-network-bridge = { workspace = true, default-features = true } polkadot-service = { workspace = true, default-features = true } # substrate deps +prometheus-endpoint = { workspace = true, default-features = true } sc-authority-discovery = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } sc-tracing = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } -sp-consensus-babe = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } tokio = { features = ["macros"], workspace = true, default-features = true } # cumulus deps +cumulus-primitives-core = { workspace = true, default-features = true } cumulus-relay-chain-interface = { workspace = true, default-features = true } cumulus-relay-chain-rpc-interface = { workspace = true, default-features = true } -cumulus-primitives-core = { workspace = true, default-features = true } array-bytes = { workspace = true, default-features = true } -tracing = { workspace = true, default-features = true } async-trait = { workspace = true } futures = { workspace = true } +tracing = { workspace = true, default-features = true } diff --git a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs index 1086e3a52ec013c33335cf671833a279ed6783cd..cfd5bd951333dc5436d90ad9c83033a75628debe 100644 --- a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs +++ b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs @@ -26,7 +26,9 @@ use futures::{Stream, StreamExt}; use polkadot_core_primitives::{Block, BlockNumber, Hash, Header}; use polkadot_overseer::{ChainApiBackend, RuntimeApiSubsystemClient}; use polkadot_primitives::{ - async_backing::AsyncBackingParams, slashing, vstaging::async_backing::BackingState, + async_backing::AsyncBackingParams, + slashing, + vstaging::async_backing::{BackingState, Constraints}, ApprovalVotingParams, CoreIndex, NodeFeatures, }; use sc_authority_discovery::{AuthorityDiscovery, Error as AuthorityDiscoveryError}; @@ -454,6 +456,18 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { .parachain_host_candidates_pending_availability(at, para_id) .await?) } + + async fn backing_constraints( + &self, + at: Hash, + para_id: ParaId, + ) -> Result<Option<Constraints>, ApiError> { + Ok(self.rpc_client.parachain_host_backing_constraints(at, para_id).await?) + } + + async fn scheduling_lookahead(&self, at: Hash) -> Result<u32, sp_api::ApiError> { + Ok(self.rpc_client.parachain_host_scheduling_lookahead(at).await?) + } } #[async_trait::async_trait] diff --git a/cumulus/client/relay-chain-rpc-interface/Cargo.toml b/cumulus/client/relay-chain-rpc-interface/Cargo.toml index fb4cb4ceed4ec9a6ec796fa7b00c4df353e90ad6..50b438e342370db35dcb657905b1fa5179271da1 100644 --- a/cumulus/client/relay-chain-rpc-interface/Cargo.toml +++ b/cumulus/client/relay-chain-rpc-interface/Cargo.toml @@ -5,6 +5,8 @@ version = "0.7.0" edition.workspace = true description = "Implementation of the RelayChainInterface trait that connects to a remote RPC-node." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -18,36 +20,36 @@ polkadot-overseer = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } cumulus-relay-chain-interface = { workspace = true, default-features = true } +prometheus-endpoint = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } +sc-rpc-api = { workspace = true, default-features = true } +sc-service = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-consensus-babe = { workspace = true, default-features = true } sp-authority-discovery = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } sp-storage = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sc-rpc-api = { workspace = true, default-features = true } -sc-service = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } tokio = { features = ["sync"], workspace = true, default-features = true } tokio-util = { features = ["compat"], workspace = true } +async-trait = { workspace = true } +codec = { workspace = true, default-features = true } +either = { workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } -codec = { workspace = true, default-features = true } jsonrpsee = { features = ["ws-client"], workspace = true } -tracing = { workspace = true, default-features = true } -async-trait = { workspace = true } -url = { workspace = true } -serde_json = { workspace = true, default-features = true } -serde = { workspace = true, default-features = true } +pin-project = { workspace = true } +prometheus = { workspace = true } +rand = { workspace = true, default-features = true } schnellru = { workspace = true } +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } smoldot = { default_features = false, features = ["std"], workspace = true } smoldot-light = { default_features = false, features = ["std"], workspace = true } -either = { workspace = true, default-features = true } thiserror = { workspace = true } -rand = { workspace = true, default-features = true } -pin-project = { workspace = true } -prometheus = { workspace = true } +tracing = { workspace = true, default-features = true } +url = { workspace = true } diff --git a/cumulus/client/relay-chain-rpc-interface/src/lib.rs b/cumulus/client/relay-chain-rpc-interface/src/lib.rs index 0e2f6c054c403607754e494a3ce6b595ad2bcd6b..a895d8f3e5f26239eafc40db45d85d5e86a807d7 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/lib.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/lib.rs @@ -282,4 +282,8 @@ impl RelayChainInterface for RelayChainRpcInterface { > { self.rpc_client.parachain_host_claim_queue(relay_parent).await } + + async fn scheduling_lookahead(&self, relay_parent: RelayHash) -> RelayChainResult<u32> { + self.rpc_client.parachain_host_scheduling_lookahead(relay_parent).await + } } diff --git a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs index d7785d92c73a5fe02a1872bfd7a8f27a0b844d8e..1cd9d0c11eeddbca8b02831c89016ac869479073 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs @@ -35,8 +35,8 @@ use cumulus_primitives_core::{ async_backing::AsyncBackingParams, slashing, vstaging::{ - async_backing::BackingState, CandidateEvent, - CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, + async_backing::{BackingState, Constraints}, + CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, @@ -706,6 +706,14 @@ impl RelayChainRpcClient { .await } + pub async fn parachain_host_scheduling_lookahead( + &self, + at: RelayHash, + ) -> Result<u32, RelayChainError> { + self.call_remote_runtime_function("ParachainHost_scheduling_lookahead", at, None::<()>) + .await + } + pub async fn validation_code_hash( &self, at: RelayHash, @@ -720,6 +728,15 @@ impl RelayChainRpcClient { .await } + pub async fn parachain_host_backing_constraints( + &self, + at: RelayHash, + para_id: ParaId, + ) -> Result<Option<Constraints>, RelayChainError> { + self.call_remote_runtime_function("ParachainHost_backing_constraints", at, Some(para_id)) + .await + } + fn send_register_message_to_worker( &self, message: RpcDispatcherMessage, diff --git a/cumulus/client/service/Cargo.toml b/cumulus/client/service/Cargo.toml index 8e9e41ca89dc06401c04e36b5cc0db7ffb3e36d7..c88386b985a4d7d66bed5a2069c0d95ae5118aff 100644 --- a/cumulus/client/service/Cargo.toml +++ b/cumulus/client/service/Cargo.toml @@ -5,32 +5,35 @@ authors.workspace = true edition.workspace = true description = "Common functions used to assemble the components of a parachain node." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] futures = { workspace = true } +futures-timer = { workspace = true } # Substrate sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } -sc-transaction-pool = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } +sc-network-sync = { workspace = true, default-features = true } +sc-network-transactions = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } sc-sysinfo = { workspace = true, default-features = true } sc-telemetry = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } -sc-network-sync = { workspace = true, default-features = true } +sc-transaction-pool = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } -sc-network-transactions = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-transaction-pool = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } # Polkadot polkadot-primitives = { workspace = true, default-features = true } @@ -39,10 +42,10 @@ polkadot-primitives = { workspace = true, default-features = true } cumulus-client-cli = { workspace = true, default-features = true } cumulus-client-collator = { workspace = true, default-features = true } cumulus-client-consensus-common = { workspace = true, default-features = true } -cumulus-client-pov-recovery = { workspace = true, default-features = true } cumulus-client-network = { workspace = true, default-features = true } +cumulus-client-pov-recovery = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } -cumulus-relay-chain-interface = { workspace = true, default-features = true } cumulus-relay-chain-inprocess-interface = { workspace = true, default-features = true } +cumulus-relay-chain-interface = { workspace = true, default-features = true } cumulus-relay-chain-minimal-node = { workspace = true, default-features = true } diff --git a/cumulus/docs/overview.md b/cumulus/docs/overview.md index 402c56042c4911a3ecf49e0a7cbef5d15e2a0e9b..82603257a871b2acc67418d7aafcf89d92c11554 100644 --- a/cumulus/docs/overview.md +++ b/cumulus/docs/overview.md @@ -70,7 +70,7 @@ A Parachain validator needs to validate a given PoVBlock, but without requiring the Parachain. To still make it possible to validate the Parachain block, the PoVBlock contains the witness data. The witness data is a proof that is collected while building the block. The proof will contain all trie nodes that are read during the block production. Cumulus uses the witness data to -reconstruct a partial trie and uses this a storage when executing the block. +reconstruct a partial trie and uses this as storage when executing the block. The outgoing messages are also collected at block production. These are messages from the Parachain the block is built for to other Parachains or to the relay chain itself. diff --git a/cumulus/docs/release.md b/cumulus/docs/release.md deleted file mode 100644 index 8302b7b9b7fc046920a92a97894ff16e8baca214..0000000000000000000000000000000000000000 --- a/cumulus/docs/release.md +++ /dev/null @@ -1,135 +0,0 @@ -# Releases - -## Versioning - -### Example #1 - -``` -| Polkadot | v 0. 9.22 | -| Client | v 0. 9.22 0 | -| Runtime | v 9 22 0 | => 9220 -| semver | 0. 9.22 0 | -``` - -### Example #2 - -``` -| Polkadot | v 0.10.42 | -| Client | v 0.10.42 0 | -| Runtime | v 10.42 0 | => 10420 -| semver | 0.10.42 0 | -``` - -### Example #3 - -``` -| Polkadot | v 1. 2.18 | -| Client | v 1. 2.18 0 | -| Runtime | v 1 2 18 0 | => 102180 -| semver | 1. 2.18 0 | -``` - - -This document contains information related to the releasing process and describes a few of the steps and checks that are -performed during the release process. - -## Client - -### <a name="burnin"></a>Burn In - -Ensure that Parity DevOps has run the new release on Westend and Kusama Asset Hub collators for 12h prior to publishing -the release. - -### Build Artifacts - -Add any necessary assets to the release. They should include: - -- Linux binaries - - GPG signature - - SHA256 checksum -- WASM binaries of the runtimes -- Source code - - -## Runtimes - -### Spec Version - -A new runtime release must bump the `spec_version`. This may follow a pattern with the client release (e.g. runtime -v9220 corresponds to v0.9.22). - -### Runtime version bump between RCs - -The clients need to be aware of runtime changes. However, we do not want to bump the `spec_version` for every single -release candidate. Instead, we can bump the `impl` field of the version to signal the change to the client. This applies -only to runtimes that have been deployed. - -### Old Migrations Removed - -Previous `on_runtime_upgrade` functions from old upgrades should be removed. - -### New Migrations - -Ensure that any migrations that are required due to storage or logic changes are included in the `on_runtime_upgrade` -function of the appropriate pallets. - -### Extrinsic Ordering & Storage - -Offline signing libraries depend on a consistent ordering of call indices and functions. Compare the metadata of the -current and new runtimes and ensure that the `module index, call index` tuples map to the same set of functions. It also -checks if there have been any changes in `storage`. In case of a breaking change, increase `transaction_version`. - -To verify the order has not changed, manually start the following -[Github Action](https://github.com/paritytech/polkadot-sdk/cumulus/.github/workflows/release-20_extrinsic-ordering-check-from-bin.yml). -It takes around a minute to run and will produce the report as artifact you need to manually check. - -To run it, in the _Run Workflow_ dropdown: -1. **Use workflow from**: to ignore, leave `master` as default -2. **The WebSocket url of the reference node**: - Asset Hub Polkadot: `wss://statemint-rpc.polkadot.io` - - Asset Hub Kusama: `wss://statemine-rpc.polkadot.io` - - Asset Hub Westend: `wss://westmint-rpc.polkadot.io` -3. **A url to a Linux binary for the node containing the runtime to test**: Paste the URL of the latest - release-candidate binary from the draft-release on Github. The binary has to previously be uploaded to S3 (Github url - link to the binary is constantly changing) - - E.g: https://releases.parity.io/cumulus/v0.9.270-rc3/polkadot-parachain -4. **The name of the chain under test. Usually, you would pass a local chain**: - Asset Hub Polkadot: - `asset-hub-polkadot-local` - - Asset Hub Kusama: `asset-hub-kusama-local` - - Asset Hub Westend: `asset-hub-westend-local` -5. Click **Run workflow** - -When the workflow is done, click on it and download the zip artifact, inside you'll find an `output.txt` file. The -things to look for in the output are lines like: - -- `[Identity] idx 28 -> 25 (calls 15)` - indicates the index for Identity has changed -- `[+] Society, Recovery` - indicates the new version includes 2 additional modules/pallets. -- If no indices have changed, every modules line should look something like `[Identity] idx 25 (calls 15)` - -**Note**: Adding new functions to the runtime does not constitute a breaking change as long as the indexes did not -change. - -**Note**: Extrinsic function signatures changes (adding/removing & ordering arguments) are not caught by the job, so -those changes should be reviewed "manually" - -### Benchmarks - -The Benchmarks can now be started from the CI. First find the CI pipeline from -[here](https://gitlab.parity.io/parity/mirrors/cumulus/-/pipelines?page=1&scope=all&ref=release-parachains-v9220) and -pick the latest. [Guide](https://github.com/paritytech/ci_cd/wiki/Benchmarks:-cumulus) - -### Integration Tests - -Until https://github.com/paritytech/ci_cd/issues/499 is done, tests will have to be run manually. -1. Go to https://github.com/paritytech/parachains-integration-tests and check out the release branch. E.g. -https://github.com/paritytech/parachains-integration-tests/tree/release-v9270-v0.9.27 for `release-parachains-v0.9.270` -2. Clone `release-parachains-<version>` branch from Cumulus -3. `cargo build --release` -4. Copy `./target/polkadot-parachain` to `./bin` -5. Clone `it/release-<version>-fast-sudo` from Polkadot In case the branch does not exists (it is a manual process): - cherry pick `paritytech/polkadot@791c8b8` and run: - `find . -type f -name "*.toml" -print0 | xargs -0 sed -i '' -e 's/polkadot-vX.X.X/polkadot-v<version>/g'` -6. `cargo build --release --features fast-runtime` -7. Copy `./target/polkadot` into `./bin` (in Cumulus) -8. Run the tests: - - Asset Hub Polkadot: `yarn zombienet-test -c ./examples/statemint/config.toml -t ./examples/statemint` - - Asset Hub Kusama: `yarn zombienet-test -c ./examples/statemine/config.toml -t ./examples/statemine` diff --git a/cumulus/pallets/aura-ext/Cargo.toml b/cumulus/pallets/aura-ext/Cargo.toml index c08148928b7cec891b8f80cb856714c24b04d809..82638de71aa129154af0e4abdd24767e5370c15d 100644 --- a/cumulus/pallets/aura-ext/Cargo.toml +++ b/cumulus/pallets/aura-ext/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "AURA consensus extension pallet for parachains" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -26,9 +28,15 @@ sp-runtime = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } [dev-dependencies] - # Cumulus cumulus-pallet-parachain-system = { workspace = true, default-features = true } +cumulus-primitives-core = { workspace = true, default-features = true } +cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } + +# Substrate +sp-core = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } +sp-version = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/cumulus/pallets/aura-ext/src/consensus_hook.rs b/cumulus/pallets/aura-ext/src/consensus_hook.rs index c1a8568bdd834f0e754bdad20a5a11246cc6ac31..56966aa0c8f82578dcc3da8294a4299d6a333e23 100644 --- a/cumulus/pallets/aura-ext/src/consensus_hook.rs +++ b/cumulus/pallets/aura-ext/src/consensus_hook.rs @@ -18,7 +18,6 @@ //! block velocity. //! //! The velocity `V` refers to the rate of block processing by the relay chain. - use super::{pallet, Aura}; use core::{marker::PhantomData, num::NonZeroU32}; use cumulus_pallet_parachain_system::{ @@ -54,8 +53,23 @@ where let velocity = V.max(1); let relay_chain_slot = state_proof.read_slot().expect("failed to read relay chain slot"); - let (slot, authored) = - pallet::SlotInfo::<T>::get().expect("slot info is inserted on block initialization"); + let (relay_chain_slot, authored_in_relay) = match pallet::RelaySlotInfo::<T>::get() { + Some((slot, authored)) if slot == relay_chain_slot => (slot, authored), + Some((slot, _)) if slot < relay_chain_slot => (relay_chain_slot, 0), + Some((slot, _)) => { + panic!("Slot moved backwards: stored_slot={slot:?}, relay_chain_slot={relay_chain_slot:?}") + }, + None => (relay_chain_slot, 0), + }; + + // We need to allow one additional block to be built to fill the unincluded segment. + if authored_in_relay > velocity { + panic!("authored blocks limit is reached for the slot: relay_chain_slot={relay_chain_slot:?}, authored={authored_in_relay:?}, velocity={velocity:?}"); + } + + pallet::RelaySlotInfo::<T>::put((relay_chain_slot, authored_in_relay + 1)); + + let para_slot = pallet_aura::CurrentSlot::<T>::get(); // Convert relay chain timestamp. let relay_chain_timestamp = @@ -67,19 +81,16 @@ where // Check that we are not too far in the future. Since we expect `V` parachain blocks // during the relay chain slot, we can allow for `V` parachain slots into the future. - if *slot > *para_slot_from_relay + u64::from(velocity) { + if *para_slot > *para_slot_from_relay + u64::from(velocity) { panic!( - "Parachain slot is too far in the future: parachain_slot: {:?}, derived_from_relay_slot: {:?} velocity: {:?}", - slot, + "Parachain slot is too far in the future: parachain_slot={:?}, derived_from_relay_slot={:?} velocity={:?}, relay_chain_slot={:?}", + para_slot, para_slot_from_relay, - velocity + velocity, + relay_chain_slot ); } - // We need to allow authoring multiple blocks in the same slot. - if slot != para_slot_from_relay && authored > velocity { - panic!("authored blocks limit is reached for the slot") - } let weight = T::DbWeight::get().reads(1); ( @@ -110,7 +121,7 @@ impl< /// is more recent than the included block itself. pub fn can_build_upon(included_hash: T::Hash, new_slot: Slot) -> bool { let velocity = V.max(1); - let (last_slot, authored_so_far) = match pallet::SlotInfo::<T>::get() { + let (last_slot, authored_so_far) = match pallet::RelaySlotInfo::<T>::get() { None => return true, Some(x) => x, }; @@ -123,11 +134,8 @@ impl< return false } - // TODO: This logic needs to be adjusted. - // It checks that we have not authored more than `V + 1` blocks in the slot. - // As a slot however, we take the parachain slot here. Velocity should - // be measured in relation to the relay chain slot. - // https://github.com/paritytech/polkadot-sdk/issues/3967 + // Check that we have not authored more than `V + 1` parachain blocks in the current relay + // chain slot. if last_slot == new_slot { authored_so_far < velocity + 1 } else { diff --git a/cumulus/pallets/aura-ext/src/lib.rs b/cumulus/pallets/aura-ext/src/lib.rs index dc854eb820184cbc79c5c150b1ce008c0d1955ce..19c2634ca708a063fcefbf6c7405fed0476c93a2 100644 --- a/cumulus/pallets/aura-ext/src/lib.rs +++ b/cumulus/pallets/aura-ext/src/lib.rs @@ -40,6 +40,9 @@ use sp_consensus_aura::{digests::CompatibleDigestItem, Slot}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; pub mod consensus_hook; +pub mod migration; +mod test; + pub use consensus_hook::FixedVelocityConsensusHook; type Aura<T> = pallet_aura::Pallet<T>; @@ -57,6 +60,7 @@ pub mod pallet { pub trait Config: pallet_aura::Config + frame_system::Config {} #[pallet::pallet] + #[pallet::storage_version(migration::STORAGE_VERSION)] pub struct Pallet<T>(_); #[pallet::hooks] @@ -70,20 +74,7 @@ pub mod pallet { // Fetch the authorities once to get them into the storage proof of the PoV. Authorities::<T>::get(); - let new_slot = pallet_aura::CurrentSlot::<T>::get(); - - let (new_slot, authored) = match SlotInfo::<T>::get() { - Some((slot, authored)) if slot == new_slot => (slot, authored + 1), - Some((slot, _)) if slot < new_slot => (new_slot, 1), - Some(..) => { - panic!("slot moved backwards") - }, - None => (new_slot, 1), - }; - - SlotInfo::<T>::put((new_slot, authored)); - - T::DbWeight::get().reads_writes(4, 2) + T::DbWeight::get().reads_writes(1, 0) } } @@ -99,11 +90,12 @@ pub mod pallet { ValueQuery, >; - /// Current slot paired with a number of authored blocks. + /// Current relay chain slot paired with a number of authored blocks. /// - /// Updated on each block initialization. + /// This is updated in [`FixedVelocityConsensusHook::on_state_proof`] with the current relay + /// chain slot as provided by the relay chain state proof. #[pallet::storage] - pub(crate) type SlotInfo<T: Config> = StorageValue<_, (Slot, u32), OptionQuery>; + pub(crate) type RelaySlotInfo<T: Config> = StorageValue<_, (Slot, u32), OptionQuery>; #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] diff --git a/cumulus/pallets/aura-ext/src/migration.rs b/cumulus/pallets/aura-ext/src/migration.rs new file mode 100644 index 0000000000000000000000000000000000000000..b580c19fc733cb6e513a756741fcbaba1dbe4e92 --- /dev/null +++ b/cumulus/pallets/aura-ext/src/migration.rs @@ -0,0 +1,74 @@ +// 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 <http://www.gnu.org/licenses/>. +extern crate alloc; + +use crate::{Config, Pallet}; +#[cfg(feature = "try-runtime")] +use alloc::vec::Vec; +use frame_support::{migrations::VersionedMigration, pallet_prelude::StorageVersion}; + +/// The in-code storage version. +pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + +mod v0 { + use super::*; + use frame_support::{pallet_prelude::OptionQuery, storage_alias}; + use sp_consensus_aura::Slot; + + /// Current slot paired with a number of authored blocks. + /// + /// Updated on each block initialization. + #[storage_alias] + pub(super) type SlotInfo<T: Config> = StorageValue<Pallet<T>, (Slot, u32), OptionQuery>; +} +mod v1 { + use super::*; + use frame_support::{pallet_prelude::*, traits::UncheckedOnRuntimeUpgrade}; + + pub struct UncheckedMigrationToV1<T: Config>(PhantomData<T>); + + impl<T: Config> UncheckedOnRuntimeUpgrade for UncheckedMigrationToV1<T> { + fn on_runtime_upgrade() -> Weight { + let mut weight: Weight = Weight::zero(); + weight += migrate::<T>(); + weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> { + Ok(Vec::new()) + } + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> { + ensure!(!v0::SlotInfo::<T>::exists(), "SlotInfo should not exist"); + Ok(()) + } + } + + pub fn migrate<T: Config>() -> Weight { + v0::SlotInfo::<T>::kill(); + T::DbWeight::get().writes(1) + } +} + +/// Migrate `V0` to `V1`. +pub type MigrateV0ToV1<T> = VersionedMigration< + 0, + 1, + v1::UncheckedMigrationToV1<T>, + Pallet<T>, + <T as frame_system::Config>::DbWeight, +>; diff --git a/cumulus/pallets/aura-ext/src/test.rs b/cumulus/pallets/aura-ext/src/test.rs new file mode 100644 index 0000000000000000000000000000000000000000..b0099381e682d3805363eec230b57a31fa1c7b51 --- /dev/null +++ b/cumulus/pallets/aura-ext/src/test.rs @@ -0,0 +1,338 @@ +// Copyright Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see <http://www.gnu.org/licenses/>. + +#![cfg(test)] +extern crate alloc; + +use super::*; + +use core::num::NonZeroU32; +use cumulus_pallet_parachain_system::{ + consensus_hook::ExpectParentIncluded, AnyRelayNumber, DefaultCoreSelector, ParachainSetCode, +}; +use cumulus_primitives_core::ParaId; +use frame_support::{ + derive_impl, + pallet_prelude::ConstU32, + parameter_types, + traits::{ConstBool, ConstU64, EnqueueWithOrigin}, +}; +use sp_io::TestExternalities; +use sp_version::RuntimeVersion; + +type Block = frame_system::mocking::MockBlock<Test>; + +frame_support::construct_runtime!( + pub enum Test { + System: frame_system, + ParachainSystem: cumulus_pallet_parachain_system, + Aura: pallet_aura, + AuraExt: crate, + } +); + +parameter_types! { + pub Version: RuntimeVersion = RuntimeVersion { + spec_name: "test".into(), + impl_name: "system-test".into(), + authoring_version: 1, + spec_version: 1, + impl_version: 1, + apis: sp_version::create_apis_vec!([]), + transaction_version: 1, + system_version: 1, + }; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type Version = Version; + type OnSetCode = ParachainSetCode<Test>; + type RuntimeEvent = (); +} + +impl crate::Config for Test {} + +impl pallet_aura::Config for Test { + type AuthorityId = sp_consensus_aura::sr25519::AuthorityId; + type MaxAuthorities = ConstU32<100_000>; + type DisabledValidators = (); + type AllowMultipleBlocksPerSlot = ConstBool<true>; + type SlotDuration = ConstU64<6000>; +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = (); + type WeightInfo = (); +} + +impl cumulus_pallet_parachain_system::Config for Test { + type WeightInfo = (); + type RuntimeEvent = (); + type OnSystemEvent = (); + type SelfParaId = (); + type OutboundXcmpMessageSource = (); + // Ignore all DMP messages by enqueueing them into `()`: + type DmpQueue = EnqueueWithOrigin<(), sp_core::ConstU8<0>>; + type ReservedDmpWeight = (); + type XcmpMessageHandler = (); + type ReservedXcmpWeight = (); + type CheckAssociatedRelayNumber = AnyRelayNumber; + type ConsensusHook = ExpectParentIncluded; + type SelectCore = DefaultCoreSelector<Test>; +} + +#[cfg(test)] +mod test { + use crate::test::*; + use cumulus_pallet_parachain_system::{ + Ancestor, ConsensusHook, RelayChainStateProof, UsedBandwidth, + }; + use sp_core::H256; + + fn set_ancestors() { + let mut ancestors = Vec::new(); + for i in 0..3 { + let mut ancestor = Ancestor::new_unchecked(UsedBandwidth::default(), None); + ancestor.replace_para_head_hash(H256::repeat_byte(i + 1)); + ancestors.push(ancestor); + } + cumulus_pallet_parachain_system::UnincludedSegment::<Test>::put(ancestors); + } + + pub fn new_test_ext(para_slot: u64) -> sp_io::TestExternalities { + let mut ext = TestExternalities::new_empty(); + ext.execute_with(|| { + set_ancestors(); + // Set initial parachain slot + pallet_aura::CurrentSlot::<Test>::put(Slot::from(para_slot)); + }); + ext + } + + fn set_relay_slot(slot: u64, authored: u32) { + RelaySlotInfo::<Test>::put((Slot::from(slot), authored)) + } + + fn relay_chain_state_proof(relay_slot: u64) -> RelayChainStateProof { + let mut builder = cumulus_test_relay_sproof_builder::RelayStateSproofBuilder::default(); + builder.current_slot = relay_slot.into(); + + let (hash, state_proof) = builder.into_state_root_and_proof(); + + RelayChainStateProof::new(ParaId::from(200), hash, state_proof) + .expect("Should be able to construct state proof.") + } + + fn assert_slot_info(expected_slot: u64, expected_authored: u32) { + let (slot, authored) = pallet::RelaySlotInfo::<Test>::get().unwrap(); + assert_eq!(slot, Slot::from(expected_slot), "Slot stored in RelaySlotInfo is incorrect."); + assert_eq!( + authored, expected_authored, + "Number of authored blocks stored in RelaySlotInfo is incorrect." + ); + } + + #[test] + fn test_velocity() { + type Hook = FixedVelocityConsensusHook<Test, 6000, 2, 1>; + + new_test_ext(1).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + let (_, capacity) = Hook::on_state_proof(&state_proof); + assert_eq!(capacity, NonZeroU32::new(1).unwrap().into()); + assert_slot_info(10, 1); + + let (_, capacity) = Hook::on_state_proof(&state_proof); + assert_eq!(capacity, NonZeroU32::new(1).unwrap().into()); + assert_slot_info(10, 2); + }); + } + + #[test] + #[should_panic(expected = "authored blocks limit is reached for the slot")] + fn test_exceeding_velocity_limit() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook<Test, 6000, VELOCITY, 1>; + + new_test_ext(1).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + for authored in 0..=VELOCITY + 1 { + Hook::on_state_proof(&state_proof); + assert_slot_info(10, authored + 1); + } + }); + } + + #[test] + fn test_para_slot_calculated_from_slot_duration() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook<Test, 3000, VELOCITY, 1>; + + new_test_ext(6).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + Hook::on_state_proof(&state_proof); + + let para_slot = Slot::from(7); + pallet_aura::CurrentSlot::<Test>::put(para_slot); + Hook::on_state_proof(&state_proof); + }); + } + + #[test] + fn test_velocity_at_least_one() { + // Even though this is 0, one block should always be allowed. + const VELOCITY: u32 = 0; + type Hook = FixedVelocityConsensusHook<Test, 6000, VELOCITY, 1>; + + new_test_ext(6).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + Hook::on_state_proof(&state_proof); + }); + } + + #[test] + #[should_panic( + expected = "Parachain slot is too far in the future: parachain_slot=Slot(8), derived_from_relay_slot=Slot(5) velocity=2" + )] + fn test_para_slot_calculated_from_slot_duration_2() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook<Test, 3000, VELOCITY, 1>; + + new_test_ext(8).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + let (_, _) = Hook::on_state_proof(&state_proof); + }); + } + + #[test] + fn test_velocity_resets_on_new_relay_slot() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook<Test, 6000, VELOCITY, 1>; + + new_test_ext(1).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + for authored in 0..=VELOCITY { + Hook::on_state_proof(&state_proof); + assert_slot_info(10, authored + 1); + } + + let state_proof = relay_chain_state_proof(11); + for authored in 0..=VELOCITY { + Hook::on_state_proof(&state_proof); + assert_slot_info(11, authored + 1); + } + }); + } + + #[test] + #[should_panic( + expected = "Slot moved backwards: stored_slot=Slot(10), relay_chain_slot=Slot(9)" + )] + fn test_backward_relay_slot_not_tolerated() { + type Hook = FixedVelocityConsensusHook<Test, 6000, 2, 1>; + + new_test_ext(1).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + Hook::on_state_proof(&state_proof); + assert_slot_info(10, 1); + + let state_proof = relay_chain_state_proof(9); + Hook::on_state_proof(&state_proof); + }); + } + + #[test] + #[should_panic( + expected = "Parachain slot is too far in the future: parachain_slot=Slot(13), derived_from_relay_slot=Slot(10) velocity=2" + )] + fn test_future_parachain_slot_errors() { + type Hook = FixedVelocityConsensusHook<Test, 6000, 2, 1>; + + new_test_ext(13).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + Hook::on_state_proof(&state_proof); + }); + } + + #[test] + fn test_can_build_upon_true_when_empty() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook<Test, 6000, VELOCITY, 1>; + + new_test_ext(1).execute_with(|| { + let hash = H256::repeat_byte(0x1); + assert!(Hook::can_build_upon(hash, Slot::from(1))); + }); + } + + #[test] + fn test_can_build_upon_respects_velocity() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook<Test, 6000, VELOCITY, 10>; + + new_test_ext(1).execute_with(|| { + let hash = H256::repeat_byte(0x1); + let relay_slot = Slot::from(10); + + set_relay_slot(10, VELOCITY - 1); + assert!(Hook::can_build_upon(hash, relay_slot)); + + set_relay_slot(10, VELOCITY); + assert!(Hook::can_build_upon(hash, relay_slot)); + + set_relay_slot(10, VELOCITY + 1); + // Velocity too high + assert!(!Hook::can_build_upon(hash, relay_slot)); + }); + } + + #[test] + fn test_can_build_upon_slot_can_not_decrease() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook<Test, 6000, VELOCITY, 10>; + + new_test_ext(1).execute_with(|| { + let hash = H256::repeat_byte(0x1); + + set_relay_slot(10, VELOCITY); + // Slot moves backwards + assert!(!Hook::can_build_upon(hash, Slot::from(9))); + }); + } + + #[test] + fn test_can_build_upon_unincluded_segment_size() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook<Test, 6000, VELOCITY, 2>; + + new_test_ext(1).execute_with(|| { + let relay_slot = Slot::from(10); + + set_relay_slot(10, VELOCITY); + // Size after included is two, we can not build + let hash = H256::repeat_byte(0x1); + assert!(!Hook::can_build_upon(hash, relay_slot)); + + // Size after included is one, we can build + let hash = H256::repeat_byte(0x2); + assert!(Hook::can_build_upon(hash, relay_slot)); + }); + } +} diff --git a/cumulus/pallets/collator-selection/Cargo.toml b/cumulus/pallets/collator-selection/Cargo.toml index 8d67db3daf8bb5b1284ef1dd6e7bfea7305b452f..651cceebbc6e39b2875636b64603d36a26b0f739 100644 --- a/cumulus/pallets/collator-selection/Cargo.toml +++ b/cumulus/pallets/collator-selection/Cargo.toml @@ -16,29 +16,29 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { workspace = true } codec = { features = ["derive"], workspace = true } +log = { workspace = true } rand = { features = ["std_rng"], workspace = true } scale-info = { features = ["derive"], workspace = true } -sp-runtime = { workspace = true } -sp-staking = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } pallet-session = { workspace = true } +sp-runtime = { workspace = true } +sp-staking = { workspace = true } frame-benchmarking = { optional = true, workspace = true } [dev-dependencies] +pallet-aura = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } +sp-consensus-aura = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } -sp-consensus-aura = { workspace = true, default-features = true } -pallet-aura = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/cumulus/pallets/collator-selection/src/lib.rs b/cumulus/pallets/collator-selection/src/lib.rs index 9d7e62af3c68f496de34e4f0c02ffdc5a21cf978..34c6ca8b36eab3ccbfdcd00bb2e83baa5f670f09 100644 --- a/cumulus/pallets/collator-selection/src/lib.rs +++ b/cumulus/pallets/collator-selection/src/lib.rs @@ -150,22 +150,27 @@ pub mod pallet { type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>; /// Account Identifier from which the internal Pot is generated. + #[pallet::constant] type PotId: Get<PalletId>; /// Maximum number of candidates that we should have. /// /// This does not take into account the invulnerables. + #[pallet::constant] type MaxCandidates: Get<u32>; /// Minimum number eligible collators. Should always be greater than zero. This includes /// Invulnerable collators. This ensures that there will always be one collator who can /// produce a block. + #[pallet::constant] type MinEligibleCollators: Get<u32>; /// Maximum number of invulnerables. + #[pallet::constant] type MaxInvulnerables: Get<u32>; // Will be kicked if block is not produced in threshold. + #[pallet::constant] type KickThreshold: Get<BlockNumberFor<Self>>; /// A stable ID for a validator. @@ -183,6 +188,14 @@ pub mod pallet { type WeightInfo: WeightInfo; } + #[pallet::extra_constants] + impl<T: Config> Pallet<T> { + /// Gets this pallet's derived pot account. + fn pot_account() -> T::AccountId { + Self::account_id() + } + } + /// Basic information about a collation candidate. #[derive( PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, diff --git a/cumulus/pallets/collator-selection/src/mock.rs b/cumulus/pallets/collator-selection/src/mock.rs index d13f9e9d8c44d3190cce7a168a26bad784824a0f..6a97525c4f576f2a956f21be3a89807699423205 100644 --- a/cumulus/pallets/collator-selection/src/mock.rs +++ b/cumulus/pallets/collator-selection/src/mock.rs @@ -188,7 +188,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { invulnerables, }; let session = pallet_session::GenesisConfig::<Test> { keys, ..Default::default() }; - pallet_balances::GenesisConfig::<Test> { balances } + pallet_balances::GenesisConfig::<Test> { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); // collator selection must be initialized before session. diff --git a/cumulus/pallets/dmp-queue/Cargo.toml b/cumulus/pallets/dmp-queue/Cargo.toml index 936526290d93ecd8935620c8f1f6045faadecb64..4f5bbc97bfc2df69efd42de35c3162ecf838de00 100644 --- a/cumulus/pallets/dmp-queue/Cargo.toml +++ b/cumulus/pallets/dmp-queue/Cargo.toml @@ -21,8 +21,8 @@ scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -sp-runtime = { workspace = true } sp-io = { workspace = true } +sp-runtime = { workspace = true } # Polkadot xcm = { workspace = true } @@ -56,6 +56,7 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ diff --git a/cumulus/pallets/dmp-queue/src/tests.rs b/cumulus/pallets/dmp-queue/src/tests.rs index 70d542ea2ed2abe9f09573205560dc96bf5c3011..368a1c0b436430495ffd005eb1a632dca16ab249 100644 --- a/cumulus/pallets/dmp-queue/src/tests.rs +++ b/cumulus/pallets/dmp-queue/src/tests.rs @@ -21,11 +21,7 @@ use super::{migration::*, mock::*}; use crate::*; -use frame_support::{ - pallet_prelude::*, - traits::{OnFinalize, OnIdle, OnInitialize}, - StorageNoopGuard, -}; +use frame_support::{pallet_prelude::*, traits::OnIdle, StorageNoopGuard}; #[test] fn migration_works() { @@ -183,14 +179,12 @@ fn migration_too_long_ignored() { } fn run_to_block(n: u64) { - assert!(n > System::block_number(), "Cannot go back in time"); - - while System::block_number() < n { - AllPalletsWithSystem::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - AllPalletsWithSystem::on_initialize(System::block_number()); - AllPalletsWithSystem::on_idle(System::block_number(), Weight::MAX); - } + System::run_to_block_with::<AllPalletsWithSystem>( + n, + frame_system::RunToBlockHooks::default().after_initialize(|bn| { + AllPalletsWithSystem::on_idle(bn, Weight::MAX); + }), + ); } fn assert_only_event(e: Event<Runtime>) { diff --git a/cumulus/pallets/parachain-system/Cargo.toml b/cumulus/pallets/parachain-system/Cargo.toml index 3cb0394c4b95431ca32a6e4e743a0598a54b8e37..6b6bc4fbcefe51f1cb2cc92931219ef783ea080c 100644 --- a/cumulus/pallets/parachain-system/Cargo.toml +++ b/cumulus/pallets/parachain-system/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Base pallet for cumulus-based parachains" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -15,8 +17,8 @@ codec = { features = ["derive"], workspace = true } environmental = { workspace = true } impl-trait-for-tuples = { workspace = true } log = { workspace = true } -trie-db = { workspace = true } scale-info = { features = ["derive"], workspace = true } +trie-db = { workspace = true } # Substrate frame-benchmarking = { optional = true, workspace = true } @@ -36,7 +38,6 @@ sp-version = { workspace = true } # Polkadot polkadot-parachain-primitives = { features = ["wasm-api"], workspace = true } polkadot-runtime-parachains = { workspace = true } -polkadot-runtime-common = { optional = true, workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } @@ -48,18 +49,18 @@ cumulus-primitives-proof-size-hostfunction = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } +futures = { workspace = true } hex-literal = { workspace = true, default-features = true } -trie-standardmap = { workspace = true } rand = { workspace = true, default-features = true } -futures = { workspace = true } +trie-standardmap = { workspace = true } # Substrate sc-client-api = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } +sp-consensus-slots = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } -sp-consensus-slots = { workspace = true, default-features = true } # Cumulus cumulus-test-client = { workspace = true } @@ -82,7 +83,6 @@ std = [ "log/std", "pallet-message-queue/std", "polkadot-parachain-primitives/std", - "polkadot-runtime-common/std", "polkadot-runtime-parachains/std", "scale-info/std", "sp-core/std", @@ -107,17 +107,16 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", - "polkadot-runtime-common/runtime-benchmarks", "polkadot-runtime-parachains/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "pallet-message-queue/try-runtime", - "polkadot-runtime-common?/try-runtime", "polkadot-runtime-parachains/try-runtime", "sp-runtime/try-runtime", ] diff --git a/cumulus/pallets/parachain-system/proc-macro/Cargo.toml b/cumulus/pallets/parachain-system/proc-macro/Cargo.toml index da6f0fd03efb79b03e4815433084ca8b79f6595d..d4485a400cb8383fcfb6a1b2dbb9e37330baa545 100644 --- a/cumulus/pallets/parachain-system/proc-macro/Cargo.toml +++ b/cumulus/pallets/parachain-system/proc-macro/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Proc macros provided by the parachain-system pallet" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -13,10 +15,10 @@ workspace = true proc-macro = true [dependencies] -syn = { workspace = true } +proc-macro-crate = { workspace = true } proc-macro2 = { workspace = true } quote = { workspace = true } -proc-macro-crate = { workspace = true } +syn = { workspace = true } [features] default = ["std"] diff --git a/cumulus/pallets/parachain-system/src/consensus_hook.rs b/cumulus/pallets/parachain-system/src/consensus_hook.rs index 3062396a4e7865bf4e3545b39c6ad9fa6ded6a24..6d65bdc77186f4abf5da06b16ec4c36cf27e5fbb 100644 --- a/cumulus/pallets/parachain-system/src/consensus_hook.rs +++ b/cumulus/pallets/parachain-system/src/consensus_hook.rs @@ -22,7 +22,7 @@ use core::num::NonZeroU32; use frame_support::weights::Weight; /// The possible capacity of the unincluded segment. -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] pub struct UnincludedSegmentCapacity(UnincludedSegmentCapacityInner); impl UnincludedSegmentCapacity { @@ -41,7 +41,7 @@ impl UnincludedSegmentCapacity { } } -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] pub(crate) enum UnincludedSegmentCapacityInner { ExpectParentIncluded, Value(NonZeroU32), diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 39fc8321a072ea9380e728ce19a94a064921aa82..624f91e7fdfb3c03164b1d7e6246df14266ea65e 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -45,7 +45,7 @@ use cumulus_primitives_core::{ use cumulus_primitives_parachain_inherent::{MessageQueueChain, ParachainInherentData}; use frame_support::{ defensive, - dispatch::{DispatchResult, Pays, PostDispatchInfo}, + dispatch::DispatchResult, ensure, inherent::{InherentData, InherentIdentifier, ProvideInherent}, traits::{Get, HandleMessage}, @@ -80,8 +80,7 @@ pub mod relay_state_snapshot; pub mod validate_block; use unincluded_segment::{ - Ancestor, HrmpChannelUpdate, HrmpWatermarkUpdate, OutboundBandwidthLimits, SegmentTracker, - UsedBandwidth, + HrmpChannelUpdate, HrmpWatermarkUpdate, OutboundBandwidthLimits, SegmentTracker, }; pub use consensus_hook::{ConsensusHook, ExpectParentIncluded}; @@ -109,6 +108,7 @@ pub use consensus_hook::{ConsensusHook, ExpectParentIncluded}; /// ``` pub use cumulus_pallet_parachain_system_proc_macro::register_validate_block; pub use relay_state_snapshot::{MessagingStateSnapshot, RelayChainStateProof}; +pub use unincluded_segment::{Ancestor, UsedBandwidth}; pub use pallet::*; @@ -309,8 +309,12 @@ pub mod pallet { <UpgradeRestrictionSignal<T>>::kill(); let relay_upgrade_go_ahead = <UpgradeGoAhead<T>>::take(); - let vfp = <ValidationData<T>>::get() - .expect("set_validation_data inherent needs to be present in every block!"); + let vfp = <ValidationData<T>>::get().expect( + r"Missing required set_validation_data inherent. This inherent must be + present in every block. This error typically occurs when the set_validation_data + execution failed and was rejected by the block builder. Check earlier log entries + for the specific cause of the failure.", + ); LastRelayChainBlockNumber::<T>::put(vfp.relay_parent_number); @@ -567,11 +571,12 @@ pub mod pallet { /// if the appropriate time has come. #[pallet::call_index(0)] #[pallet::weight((0, DispatchClass::Mandatory))] - // TODO: This weight should be corrected. + // TODO: This weight should be corrected. Currently the weight is registered manually in the + // call with `register_extra_weight_unchecked`. pub fn set_validation_data( origin: OriginFor<T>, data: ParachainInherentData, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { ensure_none(origin)?; assert!( !<ValidationData<T>>::exists(), @@ -692,7 +697,12 @@ pub mod pallet { vfp.relay_parent_number, )); - Ok(PostDispatchInfo { actual_weight: Some(total_weight), pays_fee: Pays::No }) + frame_system::Pallet::<T>::register_extra_weight_unchecked( + total_weight, + DispatchClass::Mandatory, + ); + + Ok(()) } #[pallet::call_index(1)] @@ -1636,7 +1646,7 @@ impl<T: Config> InspectMessageQueues for Pallet<T> { } #[cfg(feature = "runtime-benchmarks")] -impl<T: Config> polkadot_runtime_common::xcm_sender::EnsureForParachain for Pallet<T> { +impl<T: Config> polkadot_runtime_parachains::EnsureForParachain for Pallet<T> { fn ensure(para_id: ParaId) { if let ChannelStatus::Closed = Self::get_channel_status(para_id) { Self::open_outbound_hrmp_channel_for_benchmarks_or_tests(para_id) diff --git a/cumulus/pallets/session-benchmarking/Cargo.toml b/cumulus/pallets/session-benchmarking/Cargo.toml index 5af94434e0afeacec3866f8f531f6afe1f88ee64..6d77e567c9b6bfc28d596882167e89f73a477e08 100644 --- a/cumulus/pallets/session-benchmarking/Cargo.toml +++ b/cumulus/pallets/session-benchmarking/Cargo.toml @@ -17,11 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -sp-runtime = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } pallet-session = { workspace = true } +sp-runtime = { workspace = true } [features] default = ["std"] diff --git a/cumulus/pallets/session-benchmarking/src/inner.rs b/cumulus/pallets/session-benchmarking/src/inner.rs index 8d5954304878dfd5ff4c4bf912168d70e4e5e53f..6c5188921362efd9a60fa3a30693943b618379e7 100644 --- a/cumulus/pallets/session-benchmarking/src/inner.rs +++ b/cumulus/pallets/session-benchmarking/src/inner.rs @@ -14,29 +14,49 @@ // limitations under the License. //! Benchmarking setup for pallet-session. +#![cfg(feature = "runtime-benchmarks")] use alloc::{vec, vec::Vec}; use codec::Decode; -use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_benchmarking::v2::*; use frame_system::RawOrigin; use pallet_session::*; pub struct Pallet<T: Config>(pallet_session::Pallet<T>); pub trait Config: pallet_session::Config {} -benchmarks! { - set_keys { +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn set_keys() -> Result<(), BenchmarkError> { let caller: T::AccountId = whitelisted_caller(); frame_system::Pallet::<T>::inc_providers(&caller); let keys = T::Keys::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).unwrap(); - let proof: Vec<u8> = vec![0,1,2,3]; - }: _(RawOrigin::Signed(caller), keys, proof) + let proof: Vec<u8> = vec![0, 1, 2, 3]; + + #[extrinsic_call] + _(RawOrigin::Signed(caller), keys, proof); + + Ok(()) + } - purge_keys { + #[benchmark] + fn purge_keys() -> Result<(), BenchmarkError> { let caller: T::AccountId = whitelisted_caller(); frame_system::Pallet::<T>::inc_providers(&caller); let keys = T::Keys::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).unwrap(); - let proof: Vec<u8> = vec![0,1,2,3]; - let _t = pallet_session::Pallet::<T>::set_keys(RawOrigin::Signed(caller.clone()).into(), keys, proof); - }: _(RawOrigin::Signed(caller)) + let proof: Vec<u8> = vec![0, 1, 2, 3]; + let _t = pallet_session::Pallet::<T>::set_keys( + RawOrigin::Signed(caller.clone()).into(), + keys, + proof, + ); + + #[extrinsic_call] + _(RawOrigin::Signed(caller)); + + Ok(()) + } } diff --git a/cumulus/pallets/solo-to-para/Cargo.toml b/cumulus/pallets/solo-to-para/Cargo.toml index 5fd1939e93a03397dda15bae0eb638f823cbb1ce..2088361bf11a590dec293f84ba0b2a43d13c661f 100644 --- a/cumulus/pallets/solo-to-para/Cargo.toml +++ b/cumulus/pallets/solo-to-para/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Adds functionality to migrate from a Solo to a Parachain" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/pallets/weight-reclaim/Cargo.toml b/cumulus/pallets/weight-reclaim/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..d412a9b105d989156771a6a0a5fc24e0a3ac59ae --- /dev/null +++ b/cumulus/pallets/weight-reclaim/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "cumulus-pallet-weight-reclaim" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "pallet and transaction extensions for accurate proof size reclaim" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +# Substrate dependencies +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-trie = { workspace = true } + +cumulus-primitives-storage-weight-reclaim = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +# Other dependencies +codec = { features = ["derive"], workspace = true } +derive-where = { workspace = true } +docify = { workspace = true } +log = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } + +[dev-dependencies] +cumulus-primitives-proof-size-hostfunction = { workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "cumulus-primitives-proof-size-hostfunction/std", + "cumulus-primitives-storage-weight-reclaim/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-io/std", + "sp-runtime/std", + "sp-trie/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/cumulus/pallets/weight-reclaim/src/benchmarks.rs b/cumulus/pallets/weight-reclaim/src/benchmarks.rs new file mode 100644 index 0000000000000000000000000000000000000000..78bebc967d96bd91ec3f9edd878caf7314523861 --- /dev/null +++ b/cumulus/pallets/weight-reclaim/src/benchmarks.rs @@ -0,0 +1,71 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see <http://www.gnu.org/licenses/>. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_support::pallet_prelude::{DispatchClass, Pays}; +use frame_system::RawOrigin; +use sp_runtime::traits::{AsTransactionAuthorizedOrigin, DispatchTransaction}; + +#[frame_benchmarking::v2::benchmarks( + where T: Send + Sync, + <T as frame_system::Config>::RuntimeCall: + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>, + <T as frame_system::Config>::RuntimeOrigin: AsTransactionAuthorizedOrigin, +)] +mod bench { + use super::*; + use frame_benchmarking::impl_test_function; + + #[benchmark] + fn storage_weight_reclaim() { + let ext = StorageWeightReclaim::<T, ()>::new(()); + + let origin = RawOrigin::Root.into(); + let call = T::RuntimeCall::from(frame_system::Call::remark { remark: alloc::vec![] }); + + let overestimate = 10_000; + let info = DispatchInfo { + call_weight: Weight::zero().add_proof_size(overestimate), + extension_weight: Weight::zero(), + class: DispatchClass::Normal, + pays_fee: Pays::No, + }; + + let post_info = PostDispatchInfo { actual_weight: None, pays_fee: Pays::No }; + + let mut block_weight = frame_system::ConsumedWeight::default(); + block_weight.accrue(Weight::from_parts(0, overestimate), info.class); + + frame_system::BlockWeight::<T>::put(block_weight); + + #[block] + { + assert!(ext.test_run(origin, &call, &info, 0, 0, |_| Ok(post_info)).unwrap().is_ok()); + } + + let final_block_proof_size = + frame_system::BlockWeight::<T>::get().get(info.class).proof_size(); + + assert!( + final_block_proof_size < overestimate, + "The proof size measured should be less than {overestimate}" + ); + } + + impl_benchmark_test_suite!(Pallet, crate::tests::setup_test_ext_default(), crate::tests::Test); +} diff --git a/cumulus/pallets/weight-reclaim/src/lib.rs b/cumulus/pallets/weight-reclaim/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..7bbd2cf29d831942520dd680eee8ddc9da853b50 --- /dev/null +++ b/cumulus/pallets/weight-reclaim/src/lib.rs @@ -0,0 +1,306 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Pallet and transaction extensions to reclaim PoV proof size weight after an extrinsic has been +//! applied. +//! +//! This crate provides: +//! * [`StorageWeightReclaim`] transaction extension: it must wrap the whole transaction extension +//! pipeline. +//! * The pallet required for the transaction extensions weight information and benchmarks. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; +use codec::{Decode, Encode}; +use cumulus_primitives_storage_weight_reclaim::get_proof_size; +use derive_where::derive_where; +use frame_support::{ + dispatch::{DispatchInfo, PostDispatchInfo}, + pallet_prelude::Weight, + traits::Defensive, +}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{DispatchInfoOf, Dispatchable, Implication, PostDispatchInfoOf, TransactionExtension}, + transaction_validity::{TransactionSource, TransactionValidityError, ValidTransaction}, + DispatchResult, +}; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarks; +#[cfg(test)] +mod tests; +mod weights; + +pub use pallet::*; +pub use weights::WeightInfo; + +const LOG_TARGET: &'static str = "runtime::storage_reclaim_pallet"; + +/// Pallet to use alongside the transaction extension [`StorageWeightReclaim`], the pallet provides +/// weight information and benchmarks. +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + pub struct Pallet<T>(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type WeightInfo: WeightInfo; + } +} + +/// Storage weight reclaim mechanism. +/// +/// This extension must wrap all the transaction extensions: +#[doc = docify::embed!("./src/tests.rs", Tx)] +/// +/// This extension checks the size of the node-side storage proof before and after executing a given +/// extrinsic using the proof size host function. The difference between benchmarked and used weight +/// is reclaimed. +/// +/// If the benchmark was underestimating the proof size, then it is added to the block weight. +/// +/// For the time part of the weight, it does same as system `WeightReclaim` extension, it +/// calculates the unused weight using the post information and reclaim the unused weight. +/// So this extension can be used as a drop-in replacement for `WeightReclaim` extension for +/// parachains. +#[derive(Encode, Decode, TypeInfo)] +#[derive_where(Clone, Eq, PartialEq, Default; S)] +#[scale_info(skip_type_params(T))] +pub struct StorageWeightReclaim<T, S>(pub S, core::marker::PhantomData<T>); + +impl<T, S> StorageWeightReclaim<T, S> { + /// Create a new `StorageWeightReclaim` instance. + pub fn new(s: S) -> Self { + Self(s, Default::default()) + } +} + +impl<T, S> From<S> for StorageWeightReclaim<T, S> { + fn from(s: S) -> Self { + Self::new(s) + } +} + +impl<T, S: core::fmt::Debug> core::fmt::Debug for StorageWeightReclaim<T, S> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + #[cfg(feature = "std")] + let _ = write!(f, "StorageWeightReclaim<{:?}>", self.0); + + #[cfg(not(feature = "std"))] + let _ = write!(f, "StorageWeightReclaim<wasm-stripped>"); + + Ok(()) + } +} + +impl<T: Config + Send + Sync, S: TransactionExtension<T::RuntimeCall>> + TransactionExtension<T::RuntimeCall> for StorageWeightReclaim<T, S> +where + T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>, +{ + const IDENTIFIER: &'static str = "StorageWeightReclaim<Use `metadata()`!>"; + + type Implicit = S::Implicit; + + // Initial proof size and inner extension value. + type Val = (Option<u64>, S::Val); + + // Initial proof size and inner extension pre. + type Pre = (Option<u64>, S::Pre); + + fn implicit(&self) -> Result<Self::Implicit, TransactionValidityError> { + self.0.implicit() + } + + fn metadata() -> Vec<sp_runtime::traits::TransactionExtensionMetadata> { + let mut inner = S::metadata(); + inner.push(sp_runtime::traits::TransactionExtensionMetadata { + identifier: "StorageWeightReclaim", + ty: scale_info::meta_type::<()>(), + implicit: scale_info::meta_type::<()>(), + }); + inner + } + + fn weight(&self, call: &T::RuntimeCall) -> Weight { + T::WeightInfo::storage_weight_reclaim().saturating_add(self.0.weight(call)) + } + + fn validate( + &self, + origin: T::RuntimeOrigin, + call: &T::RuntimeCall, + info: &DispatchInfoOf<T::RuntimeCall>, + len: usize, + self_implicit: Self::Implicit, + inherited_implication: &impl Implication, + source: TransactionSource, + ) -> Result<(ValidTransaction, Self::Val, T::RuntimeOrigin), TransactionValidityError> { + let proof_size = get_proof_size(); + + self.0 + .validate(origin, call, info, len, self_implicit, inherited_implication, source) + .map(|(validity, val, origin)| (validity, (proof_size, val), origin)) + } + + fn prepare( + self, + val: Self::Val, + origin: &T::RuntimeOrigin, + call: &T::RuntimeCall, + info: &DispatchInfoOf<T::RuntimeCall>, + len: usize, + ) -> Result<Self::Pre, TransactionValidityError> { + let (proof_size, inner_val) = val; + self.0.prepare(inner_val, origin, call, info, len).map(|pre| (proof_size, pre)) + } + + fn post_dispatch_details( + pre: Self::Pre, + info: &DispatchInfoOf<T::RuntimeCall>, + post_info: &PostDispatchInfoOf<T::RuntimeCall>, + len: usize, + result: &DispatchResult, + ) -> Result<Weight, TransactionValidityError> { + let (proof_size_before_dispatch, inner_pre) = pre; + + let mut post_info_with_inner = *post_info; + S::post_dispatch(inner_pre, info, &mut post_info_with_inner, len, result)?; + + let inner_refund = if let (Some(before_weight), Some(after_weight)) = + (post_info.actual_weight, post_info_with_inner.actual_weight) + { + before_weight.saturating_sub(after_weight) + } else { + Weight::zero() + }; + + let Some(proof_size_before_dispatch) = proof_size_before_dispatch else { + // We have no proof size information, there is nothing we can do. + return Ok(inner_refund); + }; + + let Some(proof_size_after_dispatch) = get_proof_size().defensive_proof( + "Proof recording enabled during prepare, now disabled. This should not happen.", + ) else { + return Ok(inner_refund) + }; + + // The consumed proof size as measured by the host. + let measured_proof_size = + proof_size_after_dispatch.saturating_sub(proof_size_before_dispatch); + + // The consumed weight as benchmarked. Calculated from post info and info. + // NOTE: `calc_actual_weight` will take the minimum of `post_info` and `info` weights. + // This means any underestimation of compute time in the pre dispatch info will not be + // taken into account. + let benchmarked_actual_weight = post_info_with_inner.calc_actual_weight(info); + + let benchmarked_actual_proof_size = benchmarked_actual_weight.proof_size(); + if benchmarked_actual_proof_size < measured_proof_size { + log::error!( + target: LOG_TARGET, + "Benchmarked storage weight smaller than consumed storage weight. \ + benchmarked: {benchmarked_actual_proof_size} consumed: {measured_proof_size}" + ); + } else { + log::trace!( + target: LOG_TARGET, + "Reclaiming storage weight. benchmarked: {benchmarked_actual_proof_size}, + consumed: {measured_proof_size}" + ); + } + + let accurate_weight = benchmarked_actual_weight.set_proof_size(measured_proof_size); + + let pov_size_missing_from_node = frame_system::BlockWeight::<T>::mutate(|current_weight| { + let already_reclaimed = frame_system::ExtrinsicWeightReclaimed::<T>::get(); + current_weight.accrue(already_reclaimed, info.class); + current_weight.reduce(info.total_weight(), info.class); + current_weight.accrue(accurate_weight, 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 extrinsic_len = frame_system::AllExtrinsicsLen::<T>::get().unwrap_or(0); + let node_side_pov_size = proof_size_after_dispatch.saturating_add(extrinsic_len.into()); + let block_weight_proof_size = current_weight.total().proof_size(); + let pov_size_missing_from_node = + node_side_pov_size.saturating_sub(block_weight_proof_size); + if pov_size_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: {pov_size_missing_from_node}. Setting to \ + node-side proof size." + ); + current_weight + .accrue(Weight::from_parts(0, pov_size_missing_from_node), info.class); + } + + pov_size_missing_from_node + }); + + // The saturation will happen if the pre-dispatch weight is underestimating the proof + // size or if the node-side proof size is higher than expected. + // In this case the extrinsic proof size weight reclaimed is 0 and not a negative reclaim. + let accurate_unspent = info + .total_weight() + .saturating_sub(accurate_weight) + .saturating_sub(Weight::from_parts(0, pov_size_missing_from_node)); + frame_system::ExtrinsicWeightReclaimed::<T>::put(accurate_unspent); + + // Call have already returned their unspent amount. + // (also transaction extension prior in the pipeline, but there shouldn't be any.) + let already_unspent_in_tx_ext_pipeline = post_info.calc_unspent(info); + Ok(accurate_unspent.saturating_sub(already_unspent_in_tx_ext_pipeline)) + } + + fn bare_validate( + call: &T::RuntimeCall, + info: &DispatchInfoOf<T::RuntimeCall>, + len: usize, + ) -> frame_support::pallet_prelude::TransactionValidity { + S::bare_validate(call, info, len) + } + + fn bare_validate_and_prepare( + call: &T::RuntimeCall, + info: &DispatchInfoOf<T::RuntimeCall>, + len: usize, + ) -> Result<(), TransactionValidityError> { + S::bare_validate_and_prepare(call, info, len) + } + + fn bare_post_dispatch( + info: &DispatchInfoOf<T::RuntimeCall>, + post_info: &mut PostDispatchInfoOf<T::RuntimeCall>, + len: usize, + result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + S::bare_post_dispatch(info, post_info, len, result)?; + + frame_system::Pallet::<T>::reclaim_weight(info, post_info) + } +} diff --git a/cumulus/pallets/weight-reclaim/src/tests.rs b/cumulus/pallets/weight-reclaim/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..ce647445b33272b71929ad1ac51042f079e75c4e --- /dev/null +++ b/cumulus/pallets/weight-reclaim/src/tests.rs @@ -0,0 +1,1051 @@ +// 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 <http://www.gnu.org/licenses/>. + +#![cfg(test)] + +use super::*; +use cumulus_primitives_proof_size_hostfunction::PROOF_RECORDING_DISABLED; +use frame_support::{ + assert_ok, derive_impl, dispatch::GetDispatchInfo, pallet_prelude::DispatchClass, +}; +use sp_runtime::{ + generic, + traits::{Applyable, BlakeTwo256, DispatchTransaction, Get}, + BuildStorage, +}; +use sp_trie::proof_size_extension::ProofSizeExt; + +thread_local! { + static CHECK_WEIGHT_WEIGHT: core::cell::RefCell<Weight> = Default::default(); + static STORAGE_WEIGHT_RECLAIM_WEIGHT: core::cell::RefCell<Weight> = Default::default(); + static MOCK_EXT_WEIGHT: core::cell::RefCell<Weight> = Default::default(); + static MOCK_EXT_REFUND: core::cell::RefCell<Weight> = Default::default(); +} + +/// An extension which has some proof_size weight and some proof_size refund. +#[derive(Encode, Decode, Debug, Clone, PartialEq, Eq, scale_info::TypeInfo)] +pub struct MockExtensionWithRefund; + +impl TransactionExtension<RuntimeCall> for MockExtensionWithRefund { + const IDENTIFIER: &'static str = "mock_extension_with_refund"; + type Implicit = (); + type Val = (); + type Pre = (); + fn weight(&self, _: &RuntimeCall) -> Weight { + MOCK_EXT_WEIGHT.with_borrow(|v| *v) + } + fn post_dispatch_details( + _pre: Self::Pre, + _info: &DispatchInfoOf<RuntimeCall>, + _post_info: &PostDispatchInfoOf<RuntimeCall>, + _len: usize, + _result: &DispatchResult, + ) -> Result<Weight, TransactionValidityError> { + Ok(MOCK_EXT_REFUND.with_borrow(|v| *v)) + } + fn bare_post_dispatch( + _info: &DispatchInfoOf<RuntimeCall>, + post_info: &mut PostDispatchInfoOf<RuntimeCall>, + _len: usize, + _result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + if let Some(ref mut w) = post_info.actual_weight { + *w -= MOCK_EXT_REFUND.with_borrow(|v| *v); + } + Ok(()) + } + + sp_runtime::impl_tx_ext_default!(RuntimeCall; validate prepare); +} + +pub type Tx = + crate::StorageWeightReclaim<Test, (frame_system::CheckWeight<Test>, MockExtensionWithRefund)>; +type AccountId = u64; +type Extrinsic = generic::UncheckedExtrinsic<AccountId, RuntimeCall, (), Tx>; +type Block = generic::Block<generic::Header<AccountId, BlakeTwo256>, Extrinsic>; + +#[frame_support::runtime] +mod runtime { + #[runtime::runtime] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeFreezeReason, + RuntimeHoldReason, + RuntimeSlashReason, + RuntimeLockId, + RuntimeTask, + RuntimeViewFunction + )] + pub struct Test; + + #[runtime::pallet_index(0)] + pub type System = frame_system::Pallet<Test>; + + #[runtime::pallet_index(1)] + pub type WeightReclaim = crate::Pallet<Test>; +} + +pub struct MockWeightInfo; + +impl frame_system::ExtensionsWeightInfo for MockWeightInfo { + fn check_genesis() -> Weight { + Default::default() + } + fn check_mortality_mortal_transaction() -> Weight { + Default::default() + } + fn check_mortality_immortal_transaction() -> Weight { + Default::default() + } + fn check_non_zero_sender() -> Weight { + Default::default() + } + fn check_nonce() -> Weight { + Default::default() + } + fn check_spec_version() -> Weight { + Default::default() + } + fn check_tx_version() -> Weight { + Default::default() + } + fn check_weight() -> Weight { + CHECK_WEIGHT_WEIGHT.with_borrow(|v| *v) + } + fn weight_reclaim() -> Weight { + Default::default() + } +} + +impl frame_system::WeightInfo for MockWeightInfo { + fn remark(_b: u32) -> Weight { + Weight::from_parts(400, 0) + } + fn set_code() -> Weight { + Weight::zero() + } + fn set_storage(_i: u32) -> Weight { + Weight::zero() + } + fn kill_prefix(_p: u32) -> Weight { + Weight::zero() + } + fn kill_storage(_i: u32) -> Weight { + Weight::zero() + } + fn set_heap_pages() -> Weight { + Weight::zero() + } + fn remark_with_event(_b: u32) -> Weight { + Weight::zero() + } + fn authorize_upgrade() -> Weight { + Weight::zero() + } + fn apply_authorized_upgrade() -> Weight { + Weight::zero() + } +} + +impl crate::WeightInfo for MockWeightInfo { + fn storage_weight_reclaim() -> Weight { + STORAGE_WEIGHT_RECLAIM_WEIGHT.with_borrow(|v| *v) + } +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type AccountData = (); + type MaxConsumers = frame_support::traits::ConstU32<3>; + type ExtensionsWeightInfo = MockWeightInfo; +} + +impl crate::Config for Test { + type WeightInfo = MockWeightInfo; +} + +fn new_test_ext() -> sp_io::TestExternalities { + RuntimeGenesisConfig::default().build_storage().unwrap().into() +} + +struct TestRecorder { + return_values: Box<[usize]>, + counter: core::sync::atomic::AtomicUsize, +} + +impl TestRecorder { + fn new(values: &[usize]) -> Self { + TestRecorder { return_values: values.into(), counter: Default::default() } + } +} + +impl sp_trie::ProofSizeProvider for TestRecorder { + fn estimate_encoded_size(&self) -> usize { + let counter = self.counter.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + self.return_values[counter] + } +} + +fn setup_test_externalities(proof_values: &[usize]) -> sp_io::TestExternalities { + let mut test_ext = new_test_ext(); + let test_recorder = TestRecorder::new(proof_values); + test_ext.register_extension(ProofSizeExt::new(test_recorder)); + test_ext +} + +#[cfg(feature = "runtime-benchmarks")] +pub fn setup_test_ext_default() -> sp_io::TestExternalities { + setup_test_externalities(&[0; 32]) +} + +fn set_current_storage_weight(new_weight: u64) { + frame_system::BlockWeight::<Test>::mutate(|current_weight| { + current_weight.set(Weight::from_parts(0, new_weight), DispatchClass::Normal); + }); +} + +fn get_storage_weight() -> Weight { + *frame_system::BlockWeight::<Test>::get().get(DispatchClass::Normal) +} + +const CALL: &<Test as frame_system::Config>::RuntimeCall = + &RuntimeCall::System(frame_system::Call::set_heap_pages { pages: 0u64 }); +const ALICE_ORIGIN: frame_system::Origin<Test> = frame_system::Origin::<Test>::Signed(99); +const LEN: usize = 150; + +fn new_tx_ext() -> Tx { + Tx::new((frame_system::CheckWeight::new(), MockExtensionWithRefund)) +} + +fn new_extrinsic() -> generic::CheckedExtrinsic<AccountId, RuntimeCall, Tx> { + generic::CheckedExtrinsic { + format: generic::ExtrinsicFormat::Signed(99, new_tx_ext()), + function: RuntimeCall::System(frame_system::Call::remark { remark: vec![] }), + } +} + +#[allow(unused)] +mod doc { + type Runtime = super::Test; + use crate::StorageWeightReclaim; + + #[docify::export(Tx)] + type Tx = StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonce<Runtime>, + frame_system::CheckWeight<Runtime>, + // ... all other extensions + // No need for `frame_system::WeightReclaim` as the reclaim. + ), + >; +} + +#[test] +fn basic_refund_no_post_info() { + // The real cost will be 100 bytes of storage size + let mut test_ext = setup_test_externalities(&[0, 100]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 500 + let info = DispatchInfo { call_weight: Weight::from_parts(0, 500), ..Default::default() }; + let mut post_info = PostDispatchInfo::default(); + + let tx_ext = new_tx_ext(); + + // Check weight should add 500 + 150 (len) to weight. + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + + assert_eq!(pre.0, Some(0)); + + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight, None); + assert_eq!(get_storage_weight().proof_size(), 1250); + }); +} + +#[test] +fn basic_refund_some_post_info() { + // The real cost will be 100 bytes of storage size + let mut test_ext = setup_test_externalities(&[0, 100]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 500 + let info = DispatchInfo { call_weight: Weight::from_parts(0, 500), ..Default::default() }; + let mut post_info = PostDispatchInfo::default(); + post_info.actual_weight = Some(info.total_weight()); + + let tx_ext = new_tx_ext(); + + // Check weight should add 500 + 150 (len) to weight. + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + + assert_eq!(pre.0, Some(0)); + + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight.unwrap(), Weight::from_parts(0, 100)); + assert_eq!(get_storage_weight().proof_size(), 1250); + }); +} + +#[test] +fn does_nothing_without_extension() { + // Proof size extension not registered + let mut test_ext = new_test_ext(); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 500 + let info = DispatchInfo { call_weight: Weight::from_parts(0, 500), ..Default::default() }; + let mut post_info = PostDispatchInfo::default(); + post_info.actual_weight = Some(info.total_weight()); + + let tx_ext = new_tx_ext(); + + // Check weight should add 500 + 150 (len) to weight. + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + + assert_eq!(pre.0, None); + + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight.unwrap(), info.total_weight()); + assert_eq!(get_storage_weight().proof_size(), 1650); + }) +} + +#[test] +fn negative_refund_is_added_to_weight() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + // Benchmarked storage weight: 100 + let info = DispatchInfo { call_weight: Weight::from_parts(0, 100), ..Default::default() }; + let mut post_info = PostDispatchInfo::default(); + post_info.actual_weight = Some(info.total_weight()); + + let tx_ext = new_tx_ext(); + + // Weight added should be 100 + 150 (len) + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + + assert_eq!(pre.0, Some(100)); + + // We expect no refund + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight.unwrap(), info.total_weight()); + assert_eq!( + get_storage_weight().proof_size(), + 1100 + LEN as u64 + info.total_weight().proof_size() + ); + }) +} + +#[test] +fn test_zero_proof_size() { + let mut test_ext = setup_test_externalities(&[0, 0]); + + test_ext.execute_with(|| { + let info = DispatchInfo { call_weight: Weight::from_parts(0, 500), ..Default::default() }; + let mut post_info = PostDispatchInfo::default(); + post_info.actual_weight = Some(info.total_weight()); + + let tx_ext = new_tx_ext(); + + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + + assert_eq!(pre.0, Some(0)); + + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight.unwrap(), Weight::from_parts(0, 0)); + // Proof size should be exactly equal to extrinsic length + assert_eq!(get_storage_weight().proof_size(), LEN as u64); + }); +} + +#[test] +fn test_larger_pre_dispatch_proof_size() { + let mut test_ext = setup_test_externalities(&[300, 100]); + + test_ext.execute_with(|| { + set_current_storage_weight(1300); + + let info = DispatchInfo { call_weight: Weight::from_parts(0, 500), ..Default::default() }; + let mut post_info = PostDispatchInfo::default(); + post_info.actual_weight = Some(info.total_weight()); + + let tx_ext = new_tx_ext(); + + // Adds 500 + 150 (len) weight, total weight is 1950 + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + + assert_eq!(pre.0, Some(300)); + + // check weight: + // Refund 500 unspent weight according to `post_info`, total weight is now 1650 + // + // storage reclaim: + // Recorded proof size is negative -200, total weight is now 1450 + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight.unwrap(), Weight::from_parts(0, 0)); + assert_eq!(get_storage_weight().proof_size(), 1450); + }); +} + +#[test] +fn test_incorporates_check_weight_unspent_weight() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 300 + let info = DispatchInfo { call_weight: Weight::from_parts(100, 300), ..Default::default() }; + + // Actual weight is 50 + let mut post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 250)), + pays_fee: Default::default(), + }; + + let tx_ext = new_tx_ext(); + + // Check weight should add 300 + 150 (len) of weight + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + + assert_eq!(pre.0, Some(100)); + + // The `CheckWeight` extension will refund `actual_weight` from `PostDispatchInfo` + // we always need to call `post_dispatch` to verify that they interoperate correctly. + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight.unwrap(), Weight::from_parts(50, 350 - LEN as u64)); + // Reclaimed 100 + assert_eq!(get_storage_weight().proof_size(), 1350); + }) +} + +#[test] +fn test_incorporates_check_weight_unspent_weight_on_negative() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + // Benchmarked storage weight: 50 + let info = DispatchInfo { call_weight: Weight::from_parts(100, 50), ..Default::default() }; + + // Actual weight is 25 + let mut post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 25)), + pays_fee: Default::default(), + }; + + let tx_ext = new_tx_ext(); + + // Adds 50 + 150 (len) weight, total weight 1200 + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + assert_eq!(pre.0, Some(100)); + + // The `CheckWeight` extension will refund `actual_weight` from `PostDispatchInfo` + // CheckWeight: refunds unspent 25 weight according to `post_info`, 1175 + // + // storage reclaim: + // Adds 200 - 25 (unspent) == 175 weight, total weight 1350 + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight.unwrap(), Weight::from_parts(50, 25)); + assert_eq!(get_storage_weight().proof_size(), 1350); + }) +} + +#[test] +fn test_nothing_reclaimed() { + let mut test_ext = setup_test_externalities(&[0, 100]); + + test_ext.execute_with(|| { + set_current_storage_weight(0); + // Benchmarked storage weight: 100 + let info = DispatchInfo { call_weight: Weight::from_parts(100, 100), ..Default::default() }; + + // Actual proof size is 100 + let mut post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 100)), + pays_fee: Default::default(), + }; + + let tx_ext = new_tx_ext(); + + // Adds benchmarked weight 100 + 150 (len), total weight is now 250 + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + + // Weight should go up by 150 len + 100 proof size weight, total weight 250 + assert_eq!(get_storage_weight().proof_size(), 250); + + // Should return `setup_test_externalities` proof recorder value: 100. + assert_eq!(pre.0, Some(0)); + + // The `CheckWeight` extension will refund `actual_weight` from `PostDispatchInfo` + // we always need to call `post_dispatch` to verify that they interoperate correctly. + // Nothing to refund, unspent is 0, total weight 250 + // + // weight reclaim: + // `setup_test_externalities` proof recorder value: 200, so this means the extrinsic + // actually used 100 proof size. + // Nothing to refund or add, weight matches proof recorder + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight.unwrap(), Weight::from_parts(50, 100)); + // Check block len weight was not reclaimed: + // 100 weight + 150 extrinsic len == 250 proof size + assert_eq!(get_storage_weight().proof_size(), 250); + }) +} + +// Test for refund of calls and related proof size +#[test] +fn test_series() { + struct TestCfg { + measured_proof_size_pre_dispatch: u64, + measured_proof_size_post_dispatch: u64, + info_call_weight: Weight, + info_extension_weight: Weight, + post_info_actual_weight: Option<Weight>, + block_weight_pre_dispatch: Weight, + mock_ext_refund: Weight, + assert_post_info_weight: Option<Weight>, + assert_block_weight_post_dispatch: Weight, + } + + let base_extrinsic = <<Test as frame_system::Config>::BlockWeights as Get< + frame_system::limits::BlockWeights, + >>::get() + .per_class + .get(DispatchClass::Normal) + .base_extrinsic; + + let tests = vec![ + // Info is exact, no post info, no refund. + TestCfg { + measured_proof_size_pre_dispatch: 100, + measured_proof_size_post_dispatch: 400, + info_call_weight: Weight::from_parts(40, 100), + info_extension_weight: Weight::from_parts(60, 200), + post_info_actual_weight: None, + block_weight_pre_dispatch: Weight::from_parts(1000, 1000), + mock_ext_refund: Weight::from_parts(0, 0), + assert_post_info_weight: None, + assert_block_weight_post_dispatch: base_extrinsic + + Weight::from_parts(1100, 1300 + LEN as u64), + }, + // some tx ext refund is ignored, because post info is None. + TestCfg { + measured_proof_size_pre_dispatch: 100, + measured_proof_size_post_dispatch: 400, + info_call_weight: Weight::from_parts(40, 100), + info_extension_weight: Weight::from_parts(60, 200), + post_info_actual_weight: None, + block_weight_pre_dispatch: Weight::from_parts(1000, 1000), + mock_ext_refund: Weight::from_parts(20, 20), + assert_post_info_weight: None, + assert_block_weight_post_dispatch: base_extrinsic + + Weight::from_parts(1100, 1300 + LEN as u64), + }, + // some tx ext refund is ignored on proof size because lower than actual measure. + TestCfg { + measured_proof_size_pre_dispatch: 100, + measured_proof_size_post_dispatch: 400, + info_call_weight: Weight::from_parts(40, 100), + info_extension_weight: Weight::from_parts(60, 200), + post_info_actual_weight: Some(Weight::from_parts(100, 300)), + block_weight_pre_dispatch: Weight::from_parts(1000, 1000), + mock_ext_refund: Weight::from_parts(20, 20), + assert_post_info_weight: Some(Weight::from_parts(80, 300)), + assert_block_weight_post_dispatch: base_extrinsic + + Weight::from_parts(1080, 1300 + LEN as u64), + }, + // post info doesn't double refund the call and is missing some. + TestCfg { + measured_proof_size_pre_dispatch: 100, + measured_proof_size_post_dispatch: 350, + info_call_weight: Weight::from_parts(40, 100), + info_extension_weight: Weight::from_parts(60, 200), + post_info_actual_weight: Some(Weight::from_parts(60, 200)), + block_weight_pre_dispatch: Weight::from_parts(1000, 1000), + mock_ext_refund: Weight::from_parts(20, 20), + // 50 are missed in pov because 100 is unspent in post info but it should be only 50. + assert_post_info_weight: Some(Weight::from_parts(40, 200)), + assert_block_weight_post_dispatch: base_extrinsic + + Weight::from_parts(1040, 1250 + LEN as u64), + }, + // post info doesn't double refund the call and is accurate. + TestCfg { + measured_proof_size_pre_dispatch: 100, + measured_proof_size_post_dispatch: 250, + info_call_weight: Weight::from_parts(40, 100), + info_extension_weight: Weight::from_parts(60, 200), + post_info_actual_weight: Some(Weight::from_parts(60, 200)), + block_weight_pre_dispatch: Weight::from_parts(1000, 1000), + mock_ext_refund: Weight::from_parts(20, 20), + assert_post_info_weight: Some(Weight::from_parts(40, 150)), + assert_block_weight_post_dispatch: base_extrinsic + + Weight::from_parts(1040, 1150 + LEN as u64), + }, + // post info doesn't double refund the call and is accurate. Even if mock ext is refunding + // too much. + TestCfg { + measured_proof_size_pre_dispatch: 100, + measured_proof_size_post_dispatch: 250, + info_call_weight: Weight::from_parts(40, 100), + info_extension_weight: Weight::from_parts(60, 200), + post_info_actual_weight: Some(Weight::from_parts(60, 200)), + block_weight_pre_dispatch: Weight::from_parts(1000, 1000), + mock_ext_refund: Weight::from_parts(20, 300), + assert_post_info_weight: Some(Weight::from_parts(40, 150)), + assert_block_weight_post_dispatch: base_extrinsic + + Weight::from_parts(1040, 1150 + LEN as u64), + }, + ]; + + for (i, test) in tests.into_iter().enumerate() { + dbg!("test number: ", i); + MOCK_EXT_REFUND.with_borrow_mut(|v| *v = test.mock_ext_refund); + let mut test_ext = setup_test_externalities(&[ + test.measured_proof_size_pre_dispatch as usize, + test.measured_proof_size_post_dispatch as usize, + ]); + + test_ext.execute_with(|| { + frame_system::BlockWeight::<Test>::mutate(|current_weight| { + current_weight.set(test.block_weight_pre_dispatch, DispatchClass::Normal); + }); + // Benchmarked storage weight: 50 + let info = DispatchInfo { + call_weight: test.info_call_weight, + extension_weight: test.info_extension_weight, + ..Default::default() + }; + let mut post_info = PostDispatchInfo { + actual_weight: test.post_info_actual_weight, + pays_fee: Default::default(), + }; + let tx_ext = new_tx_ext(); + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight, test.assert_post_info_weight); + assert_eq!( + *frame_system::BlockWeight::<Test>::get().get(DispatchClass::Normal), + test.assert_block_weight_post_dispatch, + ); + }) + } +} + +#[test] +fn storage_size_reported_correctly() { + let mut test_ext = setup_test_externalities(&[1000]); + test_ext.execute_with(|| { + assert_eq!(get_proof_size(), Some(1000)); + }); + + let mut test_ext = new_test_ext(); + + let test_recorder = TestRecorder::new(&[0]); + + test_ext.register_extension(ProofSizeExt::new(test_recorder)); + + test_ext.execute_with(|| { + assert_eq!(get_proof_size(), Some(0)); + }); +} + +#[test] +fn storage_size_disabled_reported_correctly() { + let mut test_ext = setup_test_externalities(&[PROOF_RECORDING_DISABLED as usize]); + + test_ext.execute_with(|| { + assert_eq!(get_proof_size(), None); + }); +} + +#[test] +fn full_basic_refund() { + // Settings for the test: + let actual_used_proof_size = 200; + let check_weight = 100; + let storage_weight_reclaim = 100; + let mock_ext = 142; + let mock_ext_refund = 100; + + // Test execution: + CHECK_WEIGHT_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(1, check_weight)); + STORAGE_WEIGHT_RECLAIM_WEIGHT + .with_borrow_mut(|v| *v = Weight::from_parts(1, storage_weight_reclaim)); + MOCK_EXT_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(36, mock_ext)); + MOCK_EXT_REFUND.with_borrow_mut(|v| *v = Weight::from_parts(35, mock_ext_refund)); + + let initial_storage_weight = 1212u64; + + let mut test_ext = setup_test_externalities(&[ + initial_storage_weight as usize, + initial_storage_weight as usize + actual_used_proof_size, + ]); + + test_ext.execute_with(|| { + set_current_storage_weight(initial_storage_weight); + + let extrinsic = new_extrinsic(); + let call_info = extrinsic.function.get_dispatch_info(); + + let info = extrinsic.get_dispatch_info(); + let post_info = extrinsic.apply::<Test>(&info, LEN).unwrap().unwrap(); + + // Assertions: + assert_eq!( + post_info.actual_weight.unwrap().ref_time(), + call_info.call_weight.ref_time() + 3, + ); + assert_eq!( + post_info.actual_weight.unwrap().proof_size(), + // LEN is part of the base extrinsic, not the post info weight actual weight. + actual_used_proof_size as u64, + ); + assert_eq!( + get_storage_weight().proof_size(), + initial_storage_weight + actual_used_proof_size as u64 + LEN as u64 + ); + }); +} + +#[test] +fn full_accrue() { + // Settings for the test: + let actual_used_proof_size = 400; + let check_weight = 100; + let storage_weight_reclaim = 100; + let mock_ext = 142; + let mock_ext_refund = 100; + + // Test execution: + CHECK_WEIGHT_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(1, check_weight)); + STORAGE_WEIGHT_RECLAIM_WEIGHT + .with_borrow_mut(|v| *v = Weight::from_parts(1, storage_weight_reclaim)); + MOCK_EXT_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(36, mock_ext)); + MOCK_EXT_REFUND.with_borrow_mut(|v| *v = Weight::from_parts(35, mock_ext_refund)); + + let initial_storage_weight = 1212u64; + + let mut test_ext = setup_test_externalities(&[ + initial_storage_weight as usize, + initial_storage_weight as usize + actual_used_proof_size, + ]); + + test_ext.execute_with(|| { + set_current_storage_weight(initial_storage_weight); + + let extrinsic = new_extrinsic(); + let call_info = extrinsic.function.get_dispatch_info(); + + let info = extrinsic.get_dispatch_info(); + let post_info = extrinsic.apply::<Test>(&info, LEN).unwrap().unwrap(); + + // Assertions: + assert_eq!( + post_info.actual_weight.unwrap().ref_time(), + call_info.call_weight.ref_time() + 3, + ); + assert_eq!( + post_info.actual_weight.unwrap().proof_size(), + info.total_weight().proof_size(), // The post info doesn't get the accrue. + ); + assert_eq!( + get_storage_weight().proof_size(), + initial_storage_weight + actual_used_proof_size as u64 + LEN as u64 + ); + }); +} + +#[test] +fn bare_is_reclaimed() { + let mut test_ext = setup_test_externalities(&[]); + test_ext.execute_with(|| { + let info = DispatchInfo { + call_weight: Weight::from_parts(100, 100), + extension_weight: Weight::from_parts(100, 100), + class: DispatchClass::Normal, + pays_fee: Default::default(), + }; + let mut post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(100, 100)), + pays_fee: Default::default(), + }; + MOCK_EXT_REFUND.with_borrow_mut(|v| *v = Weight::from_parts(10, 10)); + + frame_system::BlockWeight::<Test>::mutate(|current_weight| { + current_weight + .set(Weight::from_parts(45, 45) + info.total_weight(), DispatchClass::Normal); + }); + + StorageWeightReclaim::<Test, MockExtensionWithRefund>::bare_post_dispatch( + &info, + &mut post_info, + 0, + &Ok(()), + ) + .expect("tx is valid"); + + assert_eq!( + *frame_system::BlockWeight::<Test>::get().get(DispatchClass::Normal), + Weight::from_parts(45 + 90, 45 + 90), + ); + }); +} + +#[test] +fn sets_to_node_storage_proof_if_higher() { + struct TestCfg { + initial_proof_size: u64, + post_dispatch_proof_size: u64, + mock_ext_proof_size: u64, + pre_dispatch_block_proof_size: u64, + assert_final_block_proof_size: u64, + } + + let tests = vec![ + // The storage proof reported by the proof recorder is higher than what is stored on + // the runtime side. + TestCfg { + initial_proof_size: 1000, + post_dispatch_proof_size: 1005, + mock_ext_proof_size: 0, + pre_dispatch_block_proof_size: 5, + // We expect that the storage weight was set to the node-side proof size (1005) + + // extrinsics length (150) + assert_final_block_proof_size: 1155, + }, + // In this second scenario the proof size on the node side is only lower + // after reclaim happened. + TestCfg { + initial_proof_size: 175, + post_dispatch_proof_size: 180, + mock_ext_proof_size: 100, + pre_dispatch_block_proof_size: 85, + // After the pre_dispatch, the BlockWeight proof size will be + // 85 (initial) + 100 (benched) + 150 (tx length) = 335 + // + // We expect that the storage weight was set to the node-side proof weight + // 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_final_block_proof_size: 330, + }, + ]; + + for test in tests { + let mut test_ext = setup_test_externalities(&[ + test.initial_proof_size as usize, + test.post_dispatch_proof_size as usize, + ]); + + CHECK_WEIGHT_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(0, 0)); + STORAGE_WEIGHT_RECLAIM_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(0, 0)); + MOCK_EXT_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(0, test.mock_ext_proof_size)); + + test_ext.execute_with(|| { + set_current_storage_weight(test.pre_dispatch_block_proof_size); + + let extrinsic = new_extrinsic(); + let call_info = extrinsic.function.get_dispatch_info(); + assert_eq!(call_info.call_weight.proof_size(), 0); + + let info = extrinsic.get_dispatch_info(); + let _post_info = extrinsic.apply::<Test>(&info, LEN).unwrap().unwrap(); + + assert_eq!(get_storage_weight().proof_size(), test.assert_final_block_proof_size); + }) + } +} + +#[test] +fn test_pov_missing_from_node_reclaim() { + // Test scenario: after dispatch the pov size from node side is less than block weight. + // Ensure `pov_size_missing_from_node` is calculated correctly, and `ExtrinsicWeightReclaimed` + // is updated correctly. + + // Proof size: + let bench_pre_dispatch_call = 220; + let bench_post_dispatch_actual = 90; + let len = 20; // Only one extrinsic in the scenario. So all extrinsics length. + let block_pre_dispatch = 100; + let missing_from_node = 50; + let node_diff = 70; + + let node_pre_dispatch = block_pre_dispatch + missing_from_node; + let node_post_dispatch = node_pre_dispatch + node_diff; + + // Initialize the test. + let mut test_ext = + setup_test_externalities(&[node_pre_dispatch as usize, node_post_dispatch as usize]); + + test_ext.execute_with(|| { + set_current_storage_weight(block_pre_dispatch); + let info = DispatchInfo { + call_weight: Weight::from_parts(0, bench_pre_dispatch_call), + extension_weight: Weight::from_parts(0, 0), + ..Default::default() + }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(0, bench_post_dispatch_actual)), + ..Default::default() + }; + + // Execute the transaction. + let tx_ext = StorageWeightReclaim::<Test, frame_system::CheckWeight<Test>>::new( + frame_system::CheckWeight::new(), + ); + tx_ext + .test_run(ALICE_ORIGIN.clone().into(), CALL, &info, len as usize, 0, |_| Ok(post_info)) + .expect("valid") + .expect("success"); + + // Assert the results. + assert_eq!( + frame_system::BlockWeight::<Test>::get().get(DispatchClass::Normal).proof_size(), + node_post_dispatch + len, + ); + assert_eq!( + frame_system::ExtrinsicWeightReclaimed::<Test>::get().proof_size(), + bench_pre_dispatch_call - node_diff - missing_from_node, + ); + }); +} + +#[test] +fn test_ref_time_weight_reclaim() { + // Test scenario: after dispatch the time weight is refunded correctly. + + // Time weight: + let bench_pre_dispatch_call = 145; + let bench_post_dispatch_actual = 104; + let bench_mock_ext_weight = 63; + let bench_mock_ext_refund = 22; + let len = 20; // Only one extrinsic in the scenario. So all extrinsics length. + let block_pre_dispatch = 121; + let node_pre_dispatch = 0; + let node_post_dispatch = 0; + + // Initialize the test. + CHECK_WEIGHT_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(0, 0)); + STORAGE_WEIGHT_RECLAIM_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(0, 0)); + MOCK_EXT_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(bench_mock_ext_weight, 0)); + MOCK_EXT_REFUND.with_borrow_mut(|v| *v = Weight::from_parts(bench_mock_ext_refund, 0)); + + let base_extrinsic = <<Test as frame_system::Config>::BlockWeights as Get< + frame_system::limits::BlockWeights, + >>::get() + .per_class + .get(DispatchClass::Normal) + .base_extrinsic; + + let mut test_ext = + setup_test_externalities(&[node_pre_dispatch as usize, node_post_dispatch as usize]); + + test_ext.execute_with(|| { + frame_system::BlockWeight::<Test>::mutate(|current_weight| { + current_weight.set(Weight::from_parts(block_pre_dispatch, 0), DispatchClass::Normal); + }); + let info = DispatchInfo { + call_weight: Weight::from_parts(bench_pre_dispatch_call, 0), + extension_weight: Weight::from_parts(bench_mock_ext_weight, 0), + ..Default::default() + }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(bench_post_dispatch_actual, 0)), + ..Default::default() + }; + + type InnerTxExt = (frame_system::CheckWeight<Test>, MockExtensionWithRefund); + // Execute the transaction. + let tx_ext = StorageWeightReclaim::<Test, InnerTxExt>::new(( + frame_system::CheckWeight::new(), + MockExtensionWithRefund, + )); + tx_ext + .test_run(ALICE_ORIGIN.clone().into(), CALL, &info, len as usize, 0, |_| Ok(post_info)) + .expect("valid transaction extension pipeline") + .expect("success"); + + // Assert the results. + assert_eq!( + frame_system::BlockWeight::<Test>::get().get(DispatchClass::Normal).ref_time(), + block_pre_dispatch + + base_extrinsic.ref_time() + + bench_post_dispatch_actual + + bench_mock_ext_weight - + bench_mock_ext_refund, + ); + assert_eq!( + frame_system::ExtrinsicWeightReclaimed::<Test>::get().ref_time(), + bench_pre_dispatch_call - bench_post_dispatch_actual + bench_mock_ext_refund, + ); + }); +} + +#[test] +fn test_metadata() { + assert_eq!( + StorageWeightReclaim::<Test, frame_system::CheckWeight<Test>>::metadata() + .iter() + .map(|m| m.identifier) + .collect::<Vec<_>>(), + vec!["CheckWeight", "StorageWeightReclaim"] + ); +} diff --git a/cumulus/pallets/weight-reclaim/src/weights.rs b/cumulus/pallets/weight-reclaim/src/weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..e651c8a783185b048f59e284ef957539a0221d2f --- /dev/null +++ b/cumulus/pallets/weight-reclaim/src/weights.rs @@ -0,0 +1,74 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `fedora`, CPU: `13th Gen Intel(R) Core(TM) i7-1360P` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// ./target/release/parachain-template-node +// benchmark +// pallet +// --pallet +// cumulus-pallet-weight-reclaim +// --chain +// dev +// --output +// cumulus/pallets/weight-reclaim/src/weights.rs +// --template +// substrate/.maintain/frame-weight-template.hbs +// --extrinsic +// * + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `cumulus_pallet_weight_reclaim`. +pub trait WeightInfo { + fn storage_weight_reclaim() -> Weight; +} + +/// Weights for `cumulus_pallet_weight_reclaim` using the Substrate node and recommended hardware. +pub struct SubstrateWeight<T>(PhantomData<T>); +impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_247_000 picoseconds. + Weight::from_parts(2_466_000, 0) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_247_000 picoseconds. + Weight::from_parts(2_466_000, 0) + } +} diff --git a/cumulus/pallets/xcm/Cargo.toml b/cumulus/pallets/xcm/Cargo.toml index 35d7a083b061d204de339407c6e26c4ef948dbb8..25938763c956a5b71e5a2f38c9eff77428f4afc9 100644 --- a/cumulus/pallets/xcm/Cargo.toml +++ b/cumulus/pallets/xcm/Cargo.toml @@ -5,6 +5,8 @@ name = "cumulus-pallet-xcm" version = "0.7.0" license = "Apache-2.0" description = "Pallet for stuff specific to parachains' usage of XCM" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -13,10 +15,10 @@ workspace = true codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } -sp-io = { workspace = true } -sp-runtime = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } xcm = { workspace = true } diff --git a/cumulus/pallets/xcmp-queue/Cargo.toml b/cumulus/pallets/xcmp-queue/Cargo.toml index 9c7470eda6da4aeaba942c25f04f4aeb13ee471e..43dfae8927d22e7c70b334efed09977fc9f58572 100644 --- a/cumulus/pallets/xcmp-queue/Cargo.toml +++ b/cumulus/pallets/xcmp-queue/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Pallet to queue outbound and inbound XCMP messages." license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -17,24 +19,24 @@ scale-info = { features = ["derive"], workspace = true } # Substrate frame-support = { workspace = true } frame-system = { workspace = true } -sp-io = { workspace = true } +pallet-message-queue = { workspace = true } sp-core = { workspace = true } +sp-io = { workspace = true } sp-runtime = { workspace = true } -pallet-message-queue = { workspace = true } # Polkadot polkadot-runtime-common = { workspace = true } polkadot-runtime-parachains = { workspace = true } xcm = { workspace = true } -xcm-executor = { workspace = true } xcm-builder = { workspace = true } +xcm-executor = { workspace = true } # Cumulus cumulus-primitives-core = { workspace = true } # Optional import for benchmarking -frame-benchmarking = { optional = true, workspace = true } bounded-collections = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } # Bridges bp-xcm-bridge-hub-router = { optional = true, workspace = true } @@ -42,9 +44,9 @@ bp-xcm-bridge-hub-router = { optional = true, workspace = true } [dev-dependencies] # Substrate -sp-core = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } frame-support = { features = ["experimental"], workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } # Cumulus cumulus-pallet-parachain-system = { workspace = true, default-features = true } @@ -85,6 +87,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-parachain-system/try-runtime", diff --git a/cumulus/pallets/xcmp-queue/src/bridging.rs b/cumulus/pallets/xcmp-queue/src/bridging.rs index 8ed11505a27a991cf2f6e2ff2097bf886fca1ddb..355691a41659d4f60d6f4dd98e752ae209077798 100644 --- a/cumulus/pallets/xcmp-queue/src/bridging.rs +++ b/cumulus/pallets/xcmp-queue/src/bridging.rs @@ -45,12 +45,11 @@ impl<Runtime: crate::Config> bp_xcm_bridge_hub_router::XcmChannelStatusProvider } } -/// Adapter implementation for `bp_xcm_bridge_hub_router::XcmChannelStatusProvider` which checks -/// only `OutboundXcmpStatus` for defined `SiblingParaId` if is suspended. +/// Adapter implementation for `bp_xcm_bridge::ChannelStatusProvider` and/or +/// `bp_xcm_bridge_hub_router::XcmChannelStatusProvider` which checks only `OutboundXcmpStatus` +/// for defined `Location` if is suspended. pub struct OutXcmpChannelStatusProvider<Runtime>(core::marker::PhantomData<Runtime>); -impl<Runtime: crate::Config> bp_xcm_bridge_hub_router::XcmChannelStatusProvider - for OutXcmpChannelStatusProvider<Runtime> -{ +impl<Runtime: crate::Config> OutXcmpChannelStatusProvider<Runtime> { fn is_congested(with: &Location) -> bool { // handle congestion only for a sibling parachain locations. let sibling_para_id: ParaId = match with.unpack() { @@ -88,6 +87,14 @@ impl<Runtime: crate::Config> bp_xcm_bridge_hub_router::XcmChannelStatusProvider } } +impl<Runtime: crate::Config> bp_xcm_bridge_hub_router::XcmChannelStatusProvider + for OutXcmpChannelStatusProvider<Runtime> +{ + fn is_congested(with: &Location) -> bool { + Self::is_congested(with) + } +} + #[cfg(feature = "runtime-benchmarks")] pub fn suspend_channel_for_benchmarks<T: crate::Config>(target: ParaId) { pallet::Pallet::<T>::suspend_channel(target) diff --git a/cumulus/parachains/chain-specs/asset-hub-kusama.json b/cumulus/parachains/chain-specs/asset-hub-kusama.json index 58b8ac01922712234ad81a56fde825026af904e2..ae4409e4f44fa8106abe4b1fb6f75f27865c6579 100644 --- a/cumulus/parachains/chain-specs/asset-hub-kusama.json +++ b/cumulus/parachains/chain-specs/asset-hub-kusama.json @@ -28,7 +28,8 @@ "/dns/mine14.rotko.net/tcp/35524/wss/p2p/12D3KooWJUFnjR2PNbsJhudwPVaWCoZy1acPGKjM2cSuGj345BBu", "/dns/asset-hub-kusama.bootnodes.polkadotters.com/tcp/30511/p2p/12D3KooWDpk7wVH7RgjErEvbvAZ2kY5VeaAwRJP5ojmn1e8b8UbU", "/dns/asset-hub-kusama.bootnodes.polkadotters.com/tcp/30513/wss/p2p/12D3KooWDpk7wVH7RgjErEvbvAZ2kY5VeaAwRJP5ojmn1e8b8UbU", - "/dns/boot-kusama-assethub.luckyfriday.io/tcp/443/wss/p2p/12D3KooWSwaeFs6FNgpgh54fdoxSDAA4nJNaPE3PAcse2GRrG7b3" + "/dns/boot-kusama-assethub.luckyfriday.io/tcp/443/wss/p2p/12D3KooWSwaeFs6FNgpgh54fdoxSDAA4nJNaPE3PAcse2GRrG7b3", + "/dns/asset-hub-kusama-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWNCg821LyWDVrAJ2mG6ScDeeBFuDPiJtLYc9jCGNCyMoq" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/asset-hub-polkadot.json b/cumulus/parachains/chain-specs/asset-hub-polkadot.json index 3e46501b00785d9d92b3bde19e5dd08e00d7e86d..62efb924c171d21e4e5cb452b9f35ef097f3c9cf 100644 --- a/cumulus/parachains/chain-specs/asset-hub-polkadot.json +++ b/cumulus/parachains/chain-specs/asset-hub-polkadot.json @@ -28,7 +28,8 @@ "/dns/mint14.rotko.net/tcp/35514/wss/p2p/12D3KooWKkzLjYF6M5eEs7nYiqEtRqY8SGVouoCwo3nCWsRnThDW", "/dns/asset-hub-polkadot.bootnodes.polkadotters.com/tcp/30508/p2p/12D3KooWKbfY9a9oywxMJKiALmt7yhrdQkjXMtvxhhDDN23vG93R", "/dns/asset-hub-polkadot.bootnodes.polkadotters.com/tcp/30510/wss/p2p/12D3KooWKbfY9a9oywxMJKiALmt7yhrdQkjXMtvxhhDDN23vG93R", - "/dns/boot-polkadot-assethub.luckyfriday.io/tcp/443/wss/p2p/12D3KooWDR9M7CjV1xdjCRbRwkFn1E7sjMaL4oYxGyDWxuLrFc2J" + "/dns/boot-polkadot-assethub.luckyfriday.io/tcp/443/wss/p2p/12D3KooWDR9M7CjV1xdjCRbRwkFn1E7sjMaL4oYxGyDWxuLrFc2J", + "/dns/asset-hub-polkadot-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWJUhizuk3crSvpyKLGycHBtnP93rwjksVueveU6x6k6RY" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/asset-hub-westend.json b/cumulus/parachains/chain-specs/asset-hub-westend.json index 42717974a0b34e31420fe08ad51195dd3a1b2c4e..67a208c2787b88e12426e6b94f18b7616e1972fa 100644 --- a/cumulus/parachains/chain-specs/asset-hub-westend.json +++ b/cumulus/parachains/chain-specs/asset-hub-westend.json @@ -29,7 +29,8 @@ "/dns/wmint14.rotko.net/tcp/34534/ws/p2p/12D3KooWE4UDXqgtTcMCyUQ8S4uvaT8VMzzTBA6NWmKuYwTacWuN", "/dns/wmint14.rotko.net/tcp/35534/wss/p2p/12D3KooWE4UDXqgtTcMCyUQ8S4uvaT8VMzzTBA6NWmKuYwTacWuN", "/dns/asset-hub-westend.bootnodes.polkadotters.com/tcp/30514/p2p/12D3KooWNFYysCqmojxqjjaTfD2VkWBNngfyUKWjcR4WFixfHNTk", - "/dns/asset-hub-westend.bootnodes.polkadotters.com/tcp/30516/wss/p2p/12D3KooWNFYysCqmojxqjjaTfD2VkWBNngfyUKWjcR4WFixfHNTk" + "/dns/asset-hub-westend.bootnodes.polkadotters.com/tcp/30516/wss/p2p/12D3KooWNFYysCqmojxqjjaTfD2VkWBNngfyUKWjcR4WFixfHNTk", + "/dns/asset-hub-westend-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWDUPyF2q8b6fVFEuwxBbRV3coAy1kzuCPU3D9TRiLnUfE" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/bridge-hub-kusama.json b/cumulus/parachains/chain-specs/bridge-hub-kusama.json index 36558b325bbf315144c69e8f6c4294bd6d5dfdff..83910965584f97f95d4338ee945cfcba57fa6710 100644 --- a/cumulus/parachains/chain-specs/bridge-hub-kusama.json +++ b/cumulus/parachains/chain-specs/bridge-hub-kusama.json @@ -28,7 +28,8 @@ "/dns/kbr13.rotko.net/tcp/35553/wss/p2p/12D3KooWAmBp54mUEYtvsk2kxNEsDbAvdUMcaghxKXgUQxmPEQ66", "/dns/bridge-hub-kusama.bootnodes.polkadotters.com/tcp/30520/p2p/12D3KooWH3pucezRRS5esoYyzZsUkKWcPSByQxEvmM819QL1HPLV", "/dns/bridge-hub-kusama.bootnodes.polkadotters.com/tcp/30522/wss/p2p/12D3KooWH3pucezRRS5esoYyzZsUkKWcPSByQxEvmM819QL1HPLV", - "/dns/boot-kusama-bridgehub.luckyfriday.io/tcp/443/wss/p2p/12D3KooWQybw6AFmAvrFfwUQnNxUpS12RovapD6oorh2mAJr4xyd" + "/dns/boot-kusama-bridgehub.luckyfriday.io/tcp/443/wss/p2p/12D3KooWQybw6AFmAvrFfwUQnNxUpS12RovapD6oorh2mAJr4xyd", + "/dns/bridge-hub-kusama-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWBE1ZhrYqMC3ECFK6qbufS9kgKuF57XpvvZU6LKsPUSnF" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/bridge-hub-polkadot.json b/cumulus/parachains/chain-specs/bridge-hub-polkadot.json index eb22e09035f34b85cecbbc923ea1e68081f14a8d..30585efaf4f11026e520a0fa10b377de01895fb2 100644 --- a/cumulus/parachains/chain-specs/bridge-hub-polkadot.json +++ b/cumulus/parachains/chain-specs/bridge-hub-polkadot.json @@ -28,7 +28,8 @@ "/dns/bridge-hub-polkadot.bootnodes.polkadotters.com/tcp/30519/wss/p2p/12D3KooWLUNE3LHPDa1WrrZaYT7ArK66CLM1bPv7kKz74UcLnQRB", "/dns/boot-polkadot-bridgehub.luckyfriday.io/tcp/443/wss/p2p/12D3KooWKf3mBXHjLbwtPqv1BdbQuwbFNcQQYxASS7iQ25264AXH", "/dns/bridge-hub-polkadot.bootnode.amforc.com/tcp/29999/wss/p2p/12D3KooWGT5E56rAHfT5dY1pMLTrpAgV72yfDtD1Y5tPCHaTsifp", - "/dns/bridge-hub-polkadot.bootnode.amforc.com/tcp/30010/p2p/12D3KooWGT5E56rAHfT5dY1pMLTrpAgV72yfDtD1Y5tPCHaTsifp" + "/dns/bridge-hub-polkadot.bootnode.amforc.com/tcp/30010/p2p/12D3KooWGT5E56rAHfT5dY1pMLTrpAgV72yfDtD1Y5tPCHaTsifp", + "/dns/bridge-hub-polkadot-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWSBpo6fYU8CUr4fwA14CKSDUSj5jSgZzQDBNL1B8Dnmaw" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/bridge-hub-westend.json b/cumulus/parachains/chain-specs/bridge-hub-westend.json index 40c7c7460c2359c855adac91a0dbf2dbec2c008b..05d679a3e23f13609020bfe62bc0a55d619a51a6 100644 --- a/cumulus/parachains/chain-specs/bridge-hub-westend.json +++ b/cumulus/parachains/chain-specs/bridge-hub-westend.json @@ -29,7 +29,8 @@ "/dns/bridge-hub-westend.bootnodes.polkadotters.com/tcp/30523/p2p/12D3KooWPkwgJofp4GeeRwNgXqkp2aFwdLkCWv3qodpBJLwK43Jj", "/dns/bridge-hub-westend.bootnodes.polkadotters.com/tcp/30525/wss/p2p/12D3KooWPkwgJofp4GeeRwNgXqkp2aFwdLkCWv3qodpBJLwK43Jj", "/dns/bridge-hub-westend.bootnode.amforc.com/tcp/29999/wss/p2p/12D3KooWDSWod2gMtHxunXot538oEMw9p42pnPrpRELdsfYyT8R6", - "/dns/bridge-hub-westend.bootnode.amforc.com/tcp/30007/p2p/12D3KooWDSWod2gMtHxunXot538oEMw9p42pnPrpRELdsfYyT8R6" + "/dns/bridge-hub-westend.bootnode.amforc.com/tcp/30007/p2p/12D3KooWDSWod2gMtHxunXot538oEMw9p42pnPrpRELdsfYyT8R6", + "/dns/bridge-hub-westend-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWJEfDZxrEKehoPbW2Mfg6rypttMXCMgMiybmapKqcByc1" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/collectives-polkadot.json b/cumulus/parachains/chain-specs/collectives-polkadot.json index 5ccccbec905326da8ca7b45013f38e1bb5da9926..458530baf336b725f7e6bbbaa70f991d291a9ee3 100644 --- a/cumulus/parachains/chain-specs/collectives-polkadot.json +++ b/cumulus/parachains/chain-specs/collectives-polkadot.json @@ -27,7 +27,8 @@ "/dns/pch16.rotko.net/tcp/35576/wss/p2p/12D3KooWKrm3XmuGzJH17Wcn4HRDGsEjLZGDgN77q3ZhwnnQP7y1", "/dns/collectives-polkadot.bootnodes.polkadotters.com/tcp/30526/p2p/12D3KooWNohUjvJtGKUa8Vhy8C1ZBB5N8JATB6e7rdLVCioeb3ff", "/dns/collectives-polkadot.bootnodes.polkadotters.com/tcp/30528/wss/p2p/12D3KooWNohUjvJtGKUa8Vhy8C1ZBB5N8JATB6e7rdLVCioeb3ff", - "/dns/boot-polkadot-collectives.luckyfriday.io/tcp/443/wss/p2p/12D3KooWCzifnPooTt4kvTnXT7FTKTymVL7xn7DURQLsS2AKpf6w" + "/dns/boot-polkadot-collectives.luckyfriday.io/tcp/443/wss/p2p/12D3KooWCzifnPooTt4kvTnXT7FTKTymVL7xn7DURQLsS2AKpf6w", + "/dns/collectives-polkadot-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWNscpobBzjPEdjbbjjKRYh9j1whYJvagRJwb9UH68zCPC" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/collectives-westend.json b/cumulus/parachains/chain-specs/collectives-westend.json index f583eddcef1f145500a68f48965cc15f8333a3a7..aa0204df1a064166117601f394f02aa31fbf021b 100644 --- a/cumulus/parachains/chain-specs/collectives-westend.json +++ b/cumulus/parachains/chain-specs/collectives-westend.json @@ -29,7 +29,8 @@ "/dns/wch13.rotko.net/tcp/34593/ws/p2p/12D3KooWPG85zhuSRoyptjLkFD4iJFistjiBmc15JgQ96B4fdXYr", "/dns/wch13.rotko.net/tcp/35593/wss/p2p/12D3KooWPG85zhuSRoyptjLkFD4iJFistjiBmc15JgQ96B4fdXYr", "/dns/collectives-westend.bootnodes.polkadotters.com/tcp/30529/p2p/12D3KooWAFkXNSBfyPduZVgfS7pj5NuVpbU8Ee5gHeF8wvos7Yqn", - "/dns/collectives-westend.bootnodes.polkadotters.com/tcp/30531/wss/p2p/12D3KooWAFkXNSBfyPduZVgfS7pj5NuVpbU8Ee5gHeF8wvos7Yqn" + "/dns/collectives-westend.bootnodes.polkadotters.com/tcp/30531/wss/p2p/12D3KooWAFkXNSBfyPduZVgfS7pj5NuVpbU8Ee5gHeF8wvos7Yqn", + "/dns/collectives-westend-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWFH7UZnWESzuRSgrLvNSfALjtpr9PmG7QGyRNCizWEHcd" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/coretime-kusama.json b/cumulus/parachains/chain-specs/coretime-kusama.json index 3e4ffae403bdb7118fe94befc32e15f497e71fcc..8352588a1e4bfae8c9fc535258ca60b1580f1bc3 100644 --- a/cumulus/parachains/chain-specs/coretime-kusama.json +++ b/cumulus/parachains/chain-specs/coretime-kusama.json @@ -26,7 +26,8 @@ "/dns/coretime-kusama-bootnode.radiumblock.com/tcp/30333/p2p/12D3KooWFzW9AgxNfkVNCepVByS7URDCRDAA5p3XzBLVptqZvWoL", "/dns/coretime-kusama-bootnode.radiumblock.com/tcp/30336/wss/p2p/12D3KooWFzW9AgxNfkVNCepVByS7URDCRDAA5p3XzBLVptqZvWoL", "/dns/coretime-kusama.bootnode.amforc.com/tcp/29999/wss/p2p/12D3KooWPrgxrrumrANp6Bp2SMEwMQHPHDbPzA1HbcrakZrbFi5P", - "/dns/coretime-kusama.bootnode.amforc.com/tcp/30013/p2p/12D3KooWPrgxrrumrANp6Bp2SMEwMQHPHDbPzA1HbcrakZrbFi5P" + "/dns/coretime-kusama.bootnode.amforc.com/tcp/30013/p2p/12D3KooWPrgxrrumrANp6Bp2SMEwMQHPHDbPzA1HbcrakZrbFi5P", + "/dns/coretime-kusama-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWMPc6jEjzFLRCK7QgbcNh3gvxCzGvDKhU4F66QWf2kZmq" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/coretime-polkadot.json b/cumulus/parachains/chain-specs/coretime-polkadot.json index e4f947d2afc953eeb6c4607c8b17ae8ecfc9e01b..7c12ee155b41a28dd71fa304572bd0964bec519d 100644 --- a/cumulus/parachains/chain-specs/coretime-polkadot.json +++ b/cumulus/parachains/chain-specs/coretime-polkadot.json @@ -12,7 +12,8 @@ "/dns/coretime-polkadot-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWGpmytHjdthrkKgkXDZyKm9ABtJ2PtGk9NStJDG4pChy9", "/dns/coretime-polkadot-boot-ng.dwellir.com/tcp/30361/p2p/12D3KooWGpmytHjdthrkKgkXDZyKm9ABtJ2PtGk9NStJDG4pChy9", "/dns/coretime-polkadot-bootnode.radiumblock.com/tcp/30333/p2p/12D3KooWFsQphSqvqjVyKcEdR1D7LPcXHqjmy6ASuJrTr5isk9JU", - "/dns/coretime-polkadot-bootnode.radiumblock.com/tcp/30336/wss/p2p/12D3KooWFsQphSqvqjVyKcEdR1D7LPcXHqjmy6ASuJrTr5isk9JU" + "/dns/coretime-polkadot-bootnode.radiumblock.com/tcp/30336/wss/p2p/12D3KooWFsQphSqvqjVyKcEdR1D7LPcXHqjmy6ASuJrTr5isk9JU", + "/dns/coretime-polkadot-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWFG9WQQTf3MX3YQypZjJtoJM5zCQgJcqYdxxTStsbhZGU" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/coretime-westend.json b/cumulus/parachains/chain-specs/coretime-westend.json index 42f67526c29ac5bcf94fd3741a577c68667fc226..de6923bd76697ff750a0fc5b03c3b2641cb58269 100644 --- a/cumulus/parachains/chain-specs/coretime-westend.json +++ b/cumulus/parachains/chain-specs/coretime-westend.json @@ -30,7 +30,8 @@ "/dns/coretime-westend.bootnodes.polkadotters.com/tcp/30358/wss/p2p/12D3KooWDc9T2vQ8rHvX7hAt9eLWktD9Q89NDTcLm5STkuNbzUGf", "/dns/coretime-westend.bootnodes.polkadotters.com/tcp/30356/p2p/12D3KooWDc9T2vQ8rHvX7hAt9eLWktD9Q89NDTcLm5STkuNbzUGf", "/dns/coretime-westend.bootnode.amforc.com/tcp/29999/wss/p2p/12D3KooWG9a9H9An96E3kgXL1sirHta117iuacJXnJRaUywkMiSd", - "/dns/coretime-westend.bootnode.amforc.com/tcp/30013/p2p/12D3KooWG9a9H9An96E3kgXL1sirHta117iuacJXnJRaUywkMiSd" + "/dns/coretime-westend.bootnode.amforc.com/tcp/30013/p2p/12D3KooWG9a9H9An96E3kgXL1sirHta117iuacJXnJRaUywkMiSd", + "/dns/coretime-westend-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWCFNzjaiq45ZpW2qStmQdG5w7ZHrmi3RWUeG8cV2pPc2Y" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/people-kusama.json b/cumulus/parachains/chain-specs/people-kusama.json index 300b9fcfb183c262ce1fc2279d6ca7bf457bc1db..701e6e7dc1ec67aafed69718806ef9aef10ec33b 100644 --- a/cumulus/parachains/chain-specs/people-kusama.json +++ b/cumulus/parachains/chain-specs/people-kusama.json @@ -28,7 +28,8 @@ "/dns/ibp-boot-kusama-people.luckyfriday.io/tcp/30342/p2p/12D3KooWM4bRafMH2StfBEQtyj5cMWfGLYbuikCZmvKv9m1MQVPn", "/dns/ibp-boot-kusama-people.luckyfriday.io/tcp/443/wss/p2p/12D3KooWM4bRafMH2StfBEQtyj5cMWfGLYbuikCZmvKv9m1MQVPn", "/dns4/people-kusama.boot.stake.plus/tcp/30332/wss/p2p/12D3KooWRuKr3ogzXwD8zE2CTWenGdy8vSfViAjYMwGiwvFCsz8n", - "/dns/people-kusama.boot.stake.plus/tcp/31332/wss/p2p/12D3KooWFkDKdFxBJFyj9zumuJ4Mmctec2GqdYHcKYq8MTVe8dxf" + "/dns/people-kusama.boot.stake.plus/tcp/31332/wss/p2p/12D3KooWFkDKdFxBJFyj9zumuJ4Mmctec2GqdYHcKYq8MTVe8dxf", + "/dns/people-kusama-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWN32MmhPgZN8e1Dmc8DzEUKsfC2hga3Lqekko4VWvrbhq" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/people-polkadot.json b/cumulus/parachains/chain-specs/people-polkadot.json index 083c0fbf44a4ac6272dca80b80982532c976334b..ff8d57b9284d47b466b891d16d5f43e6cb89dd31 100644 --- a/cumulus/parachains/chain-specs/people-polkadot.json +++ b/cumulus/parachains/chain-specs/people-polkadot.json @@ -8,7 +8,8 @@ "/dns/polkadot-people-connect-0.polkadot.io/tcp/443/wss/p2p/12D3KooWP7BoJ7nAF9QnsreN8Eft1yHNUhvhxFiQyKFEUePi9mu3", "/dns/polkadot-people-connect-1.polkadot.io/tcp/443/wss/p2p/12D3KooWSSfWY3fTGJvGkuNUNBSNVCdLLNJnwkZSNQt7GCRYXu4o", "/dns/people-polkadot-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWKMYu1L28TkDf1ooMW8D8PHcztLnjV3bausH9eiVTRUYN", - "/dns/people-polkadot-boot-ng.dwellir.com/tcp/30346/p2p/12D3KooWKMYu1L28TkDf1ooMW8D8PHcztLnjV3bausH9eiVTRUYN" + "/dns/people-polkadot-boot-ng.dwellir.com/tcp/30346/p2p/12D3KooWKMYu1L28TkDf1ooMW8D8PHcztLnjV3bausH9eiVTRUYN", + "/dns/people-polkadot-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWDf2aLDKHQyLkDzdEGs6exNzWWw62s2EK9g1wrujJzRZt" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/people-westend.json b/cumulus/parachains/chain-specs/people-westend.json index ac24b2e6435922e67e2cb07d66b90b2bac2fa5b8..e52d7b299e1d1e88d6d38124bf6de09861ec12d7 100644 --- a/cumulus/parachains/chain-specs/people-westend.json +++ b/cumulus/parachains/chain-specs/people-westend.json @@ -28,7 +28,8 @@ "/dns/wppl16.rotko.net/tcp/33766/p2p/12D3KooWHwUXBUo2WRMUBwPLC2ttVbnEk1KvDyESYAeKcNoCn7WS", "/dns/wppl16.rotko.net/tcp/35766/wss/p2p/12D3KooWHwUXBUo2WRMUBwPLC2ttVbnEk1KvDyESYAeKcNoCn7WS", "/dns/people-westend-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWBdCpCabhgBpLn67LWcXE2JJCCTMhuJHrfDNiTiCCr3KX", - "/dns/people-westend-boot-ng.dwellir.com/tcp/30355/p2p/12D3KooWBdCpCabhgBpLn67LWcXE2JJCCTMhuJHrfDNiTiCCr3KX" + "/dns/people-westend-boot-ng.dwellir.com/tcp/30355/p2p/12D3KooWBdCpCabhgBpLn67LWcXE2JJCCTMhuJHrfDNiTiCCr3KX", + "/dns/people-westend-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWJzL4R3kq9Ms88gsV6bS9zGT8DHySdqwau5SHNqTzToNM" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/common/Cargo.toml b/cumulus/parachains/common/Cargo.toml index 6d436bdf799a4f7682ced2098f4926ec9bc50379..6c52c3201c71909f5238d183728ea468329cf784 100644 --- a/cumulus/parachains/common/Cargo.toml +++ b/cumulus/parachains/common/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Logic which is common to all parachain runtimes" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -37,9 +39,9 @@ xcm = { workspace = true } xcm-executor = { workspace = true } # Cumulus -pallet-collator-selection = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-utility = { workspace = true } +pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } [dev-dependencies] @@ -90,4 +92,5 @@ runtime-benchmarks = [ "polkadot-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml index 25796e7d64b4575163a51de1a548b7d1987f88de..c6a8baeff3b37b147c82b21bfcf1668a90d48e1d 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml @@ -13,15 +13,16 @@ workspace = true [dependencies] # Substrate +frame-support = { workspace = true } +pallet-asset-rewards = { workspace = true } sp-core = { workspace = true } sp-keyring = { workspace = true } -frame-support = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } +asset-hub-rococo-runtime = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true } emulated-integration-tests-common = { workspace = true } -asset-hub-rococo-runtime = { workspace = true, default-features = true } +parachains-common = { workspace = true, default-features = true } rococo-emulated-chain = { workspace = true } testnet-parachains-constants = { features = ["rococo"], workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs index 3ffb9a704b4649c481d0537cd3eacfc0034c4365..4a10a1e10c7330800f1a45e621f250c8f4b841f4 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs @@ -42,6 +42,7 @@ pub fn genesis() -> Storage { .cloned() .map(|k| (k, ED * 4096 * 4096)) .collect(), + ..Default::default() }, parachain_info: asset_hub_rococo_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml index 8e423ebbf9c27d19cdf317542b82eb5ccd53bc30..c67b94d0db73e243c867f3f4754d7b1fb3be422e 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml @@ -13,17 +13,17 @@ workspace = true [dependencies] # Substrate +frame-support = { workspace = true } sp-core = { workspace = true } sp-keyring = { workspace = true } -frame-support = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } +asset-hub-westend-runtime = { workspace = true } cumulus-primitives-core = { workspace = true } emulated-integration-tests-common = { workspace = true } -asset-hub-westend-runtime = { workspace = true } -westend-emulated-chain = { workspace = true, default-features = true } +parachains-common = { workspace = true, default-features = true } testnet-parachains-constants = { features = ["westend"], workspace = true, default-features = true } +westend-emulated-chain = { workspace = true, default-features = true } # Polkadot xcm = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs index ef7997322da732e7fe55648c6f8c64e470cb7e82..0473686081e7e70ba15a81a21b00277a99f6a0c7 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs @@ -39,6 +39,7 @@ pub fn genesis() -> Storage { system: asset_hub_westend_runtime::SystemConfig::default(), balances: asset_hub_westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: asset_hub_westend_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml index 231265085edad1f411c842b8ef28debaa4d07842..8b16d8ac27ae3d65d400fc715c5dbb8931cafecf 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml @@ -13,9 +13,9 @@ workspace = true [dependencies] # Substrate +frame-support = { workspace = true } sp-core = { workspace = true } sp-keyring = { workspace = true } -frame-support = { workspace = true } # Polkadot Dependencies xcm = { workspace = true } @@ -24,8 +24,8 @@ xcm = { workspace = true } bp-messages = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } -emulated-integration-tests-common = { workspace = true } -bridge-hub-rococo-runtime = { workspace = true, default-features = true } bridge-hub-common = { workspace = true } +bridge-hub-rococo-runtime = { workspace = true, default-features = true } +emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } testnet-parachains-constants = { features = ["rococo"], workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs index 575017f88bb590159fe7df9f7e6d5e3ea096f269..62b2e4eed9e7310a2e58d0de3ed8d7470c7ac1cc 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs @@ -33,6 +33,7 @@ pub fn genesis() -> Storage { system: bridge_hub_rococo_runtime::SystemConfig::default(), balances: bridge_hub_rococo_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: bridge_hub_rococo_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/lib.rs index 5ef0993f70a1ce33daa68ec23b474716e6bee956..43398eb8bd48095f08eb5fd075f1c9b941b784c1 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/lib.rs @@ -16,7 +16,8 @@ pub mod genesis; pub use bridge_hub_rococo_runtime::{ - xcm_config::XcmConfig as BridgeHubRococoXcmConfig, EthereumBeaconClient, EthereumInboundQueue, + self as bridge_hub_rococo_runtime, xcm_config::XcmConfig as BridgeHubRococoXcmConfig, + EthereumBeaconClient, EthereumInboundQueue, ExistentialDeposit as BridgeHubRococoExistentialDeposit, RuntimeOrigin as BridgeHubRococoRuntimeOrigin, }; diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml index 8292e132809c446d0cf1c50ece00699a48f58b14..292b5bd3e43420b26bf29704990d3f6f1fbf0a7e 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml @@ -13,9 +13,9 @@ workspace = true [dependencies] # Substrate +frame-support = { workspace = true } sp-core = { workspace = true } sp-keyring = { workspace = true } -frame-support = { workspace = true } # Polkadot Dependencies xcm = { workspace = true } @@ -24,8 +24,8 @@ xcm = { workspace = true } bp-messages = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } -emulated-integration-tests-common = { workspace = true } -bridge-hub-westend-runtime = { workspace = true, default-features = true } bridge-hub-common = { workspace = true } +bridge-hub-westend-runtime = { workspace = true, default-features = true } +emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } testnet-parachains-constants = { features = ["westend"], workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs index eb4623084f85ed016716efbeb655b1147f57e020..5286110bcab9a77db8af491f98049d58167c5bb4 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs @@ -33,6 +33,7 @@ pub fn genesis() -> Storage { system: bridge_hub_westend_runtime::SystemConfig::default(), balances: bridge_hub_westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: bridge_hub_westend_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs index b548e3b7e64c3932cfcc654abdf52308ea120306..1b6f796518871835a04ac397ad8859f1a1ec7f8f 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs @@ -18,6 +18,7 @@ pub mod genesis; pub use bridge_hub_westend_runtime::{ self, xcm_config::XcmConfig as BridgeHubWestendXcmConfig, ExistentialDeposit as BridgeHubWestendExistentialDeposit, + RuntimeOrigin as BridgeHubWestendRuntimeOrigin, }; // Substrate @@ -47,6 +48,8 @@ decl_test_parachains! { PolkadotXcm: bridge_hub_westend_runtime::PolkadotXcm, Balances: bridge_hub_westend_runtime::Balances, EthereumSystem: bridge_hub_westend_runtime::EthereumSystem, + EthereumInboundQueue: bridge_hub_westend_runtime::EthereumInboundQueue, + EthereumOutboundQueue: bridge_hub_westend_runtime::EthereumOutboundQueue, } }, } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/Cargo.toml index 87dfd73ab05bab063bedeebae9feb03bcc4af46f..55e3ad6743edd8212f604302cd0a2ed0389aae29 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/Cargo.toml @@ -13,12 +13,12 @@ workspace = true [dependencies] # Substrate -sp-core = { workspace = true } frame-support = { workspace = true } +sp-core = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } +collectives-westend-runtime = { workspace = true } cumulus-primitives-core = { workspace = true } emulated-integration-tests-common = { workspace = true } -collectives-westend-runtime = { workspace = true } +parachains-common = { workspace = true, default-features = true } testnet-parachains-constants = { features = ["westend"], workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/genesis.rs index d4ef184ea392de3bcd936b889aa030e032ad120c..51e065a4ae55d5cc7fc3d17e8afc1aab69ff5251 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/genesis.rs @@ -30,6 +30,7 @@ pub fn genesis() -> Storage { system: collectives_westend_runtime::SystemConfig::default(), balances: collectives_westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: collectives_westend_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/Cargo.toml index 94d43c5eee2f444fdbfd7004a9436217c9291c42..8f12dc675199ce23c282c64802dfd2af2ec472dc 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/Cargo.toml @@ -13,12 +13,12 @@ workspace = true [dependencies] # Substrate -sp-core = { workspace = true } frame-support = { workspace = true } +sp-core = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } -cumulus-primitives-core = { workspace = true } coretime-rococo-runtime = { workspace = true, default-features = true } +cumulus-primitives-core = { workspace = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } testnet-parachains-constants = { features = ["rococo"], workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/src/genesis.rs index e0f035c368e3966f1d2fc08b7d0bf2597fadda01..f2035c8654d08112afaf3041e3ac988a200e2300 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/src/genesis.rs @@ -30,6 +30,7 @@ pub fn genesis() -> Storage { system: coretime_rococo_runtime::SystemConfig::default(), balances: coretime_rococo_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: coretime_rococo_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/Cargo.toml index 2640c27d016b36a18d913d2305ef1d1cfb57b91e..fad1000ac66cfe849fd6ed80a51f7f5496b0ca03 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/Cargo.toml @@ -13,12 +13,12 @@ workspace = true [dependencies] # Substrate -sp-core = { workspace = true } frame-support = { workspace = true } +sp-core = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } -cumulus-primitives-core = { workspace = true } coretime-westend-runtime = { workspace = true, default-features = true } +cumulus-primitives-core = { workspace = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } testnet-parachains-constants = { features = ["westend"], workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/src/genesis.rs index 239ad3760c1120b5bfdf04f93e442bf1a50f2976..29894222eff7dbfad4be1bd3797efd9eed0340b2 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/src/genesis.rs @@ -30,6 +30,7 @@ pub fn genesis() -> Storage { system: coretime_westend_runtime::SystemConfig::default(), balances: coretime_westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: coretime_westend_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/Cargo.toml index 1549d6a2ab6ba1de05d9233ff7bc29951501a43d..c98e8629e31d52cf568f65df45bbfdefbb98134e 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/Cargo.toml @@ -10,12 +10,12 @@ publish = false [dependencies] # Substrate -sp-core = { workspace = true } frame-support = { workspace = true } +sp-core = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } people-rococo-runtime = { workspace = true } testnet-parachains-constants = { features = ["rococo"], workspace = true, default-features = true } 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 36a701d24c27e9cda44a06d033332e7f03177d88..9772a64d23b343bc75a3581a217decd18047fa10 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 @@ -31,6 +31,7 @@ pub fn genesis() -> Storage { system: people_rococo_runtime::SystemConfig::default(), balances: people_rococo_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: people_rococo_runtime::ParachainInfoConfig { parachain_id: ParaId::from(PARA_ID), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/Cargo.toml index 9c5ac0bca9de7ae2f201aba958b8220b9a24a013..598ba5488f85498bb36c4e2314769ceced4f89be 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/Cargo.toml @@ -10,12 +10,12 @@ publish = false [dependencies] # Substrate -sp-core = { workspace = true } frame-support = { workspace = true } +sp-core = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } people-westend-runtime = { workspace = true } testnet-parachains-constants = { features = ["westend"], workspace = true, default-features = true } 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 942ec1b31d2b46f3b3ec9c253a1c6ee521119b4d..377babc59f65f4c91fe6366a9fde7a32262813ee 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 @@ -31,6 +31,7 @@ pub fn genesis() -> Storage { system: people_westend_runtime::SystemConfig::default(), balances: people_westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: people_westend_runtime::ParachainInfoConfig { parachain_id: ParaId::from(PARA_ID), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/Cargo.toml index 743cd7dc54a2800a215e70cc204251975534e1c1..7e92e3bf94481c8b6e50f190d76771c8876643a7 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/Cargo.toml @@ -13,15 +13,15 @@ workspace = true [dependencies] # Substrate +frame-support = { workspace = true } sp-core = { workspace = true } sp-keyring = { workspace = true } -frame-support = { workspace = true } # Polkadot xcm = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } penpal-runtime = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs index 63510d233d2c48d3af107b899a2aa18cb5057710..e514d0cb7477348ef323bcd9f2803d66a34c8057 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs @@ -40,6 +40,7 @@ pub fn genesis(para_id: u32) -> Storage { system: penpal_runtime::SystemConfig::default(), balances: penpal_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: penpal_runtime::ParachainInfoConfig { parachain_id: para_id.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml index 6db1263df8c73821c65882022b7e7afc413eb0d2..ccf3854e67d88e32d4d27c1edc3f77c1ee41465d 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml @@ -13,18 +13,18 @@ workspace = true [dependencies] # Substrate -sp-core = { workspace = true } -sp-keyring = { workspace = true } +sc-consensus-grandpa = { workspace = true } sp-authority-discovery = { workspace = true } sp-consensus-babe = { workspace = true } sp-consensus-beefy = { workspace = true, default-features = true } -sc-consensus-grandpa = { workspace = true } +sp-core = { workspace = true } +sp-keyring = { workspace = true } # Polkadot polkadot-primitives = { workspace = true } -rococo-runtime-constants = { workspace = true } rococo-runtime = { workspace = true } +rococo-runtime-constants = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs index 3d8b5b1a500f26f72d25ecc6d6ad1ff2805ff181..db9fe19dbdd73ead1a71f1d71ad355972082a00c 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs @@ -57,6 +57,7 @@ pub fn genesis() -> Storage { system: rococo_runtime::SystemConfig::default(), balances: rococo_runtime::BalancesConfig { balances: accounts::init_balances().iter().map(|k| (k.clone(), ENDOWMENT)).collect(), + ..Default::default() }, session: rococo_runtime::SessionConfig { keys: validators::initial_authorities() diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs index bd637a5f7965bca47b171283bb37473b009bdbc1..240c0931ae5af3d1e9e28dcebece18278772d1ad 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs @@ -25,7 +25,7 @@ use emulated_integration_tests_common::{ // Rococo declaration decl_test_relay_chains! { - #[api_version(11)] + #[api_version(12)] pub struct Rococo { genesis = genesis::genesis(), on_init = (), diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml index de285d9885a2f78fdea1a27ea8b8d9dc3840d300..9b980d7d39cc2df4bf8154920b981bdcd5547598 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml @@ -13,21 +13,21 @@ workspace = true [dependencies] # Substrate -sp-core = { workspace = true } -sp-runtime = { workspace = true } +pallet-staking = { workspace = true } +sc-consensus-grandpa = { workspace = true } sp-authority-discovery = { workspace = true } sp-consensus-babe = { workspace = true } sp-consensus-beefy = { workspace = true, default-features = true } -sc-consensus-grandpa = { workspace = true } -pallet-staking = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } # Polkadot polkadot-primitives = { workspace = true } -westend-runtime-constants = { workspace = true } westend-runtime = { workspace = true } +westend-runtime-constants = { workspace = true } xcm = { workspace = true } xcm-runtime-apis = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs index f8d43cf4648dbc3bf3bc7f01fbbdfd5e4cc187a8..2f02ca5f1932f9345e26037279f529e514e54f69 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs @@ -58,6 +58,7 @@ pub fn genesis() -> Storage { system: westend_runtime::SystemConfig::default(), balances: westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ENDOWMENT)).collect(), + ..Default::default() }, session: westend_runtime::SessionConfig { keys: validators::initial_authorities() diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs index ce9fafcd5bda8bd815d197b7e4a1ee70f6eb426c..729bb3ad63d161e0f64f3455601bc11a0c50016a 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs @@ -25,7 +25,7 @@ use emulated_integration_tests_common::{ // Westend declaration decl_test_relay_chains! { - #[api_version(11)] + #[api_version(12)] pub struct Westend { genesis = genesis::genesis(), on_init = (), diff --git a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml index 23edaf6bfe655e560f045c15955d5265e95ae977..4bd45ef1a87c6af88e8f615a7d45f72588f0e7b7 100644 --- a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" description = "Common resources for integration testing with xcm-emulator" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -14,36 +16,35 @@ codec = { workspace = true } paste = { workspace = true, default-features = true } # Substrate -sp-consensus-beefy = { workspace = true, default-features = true } -sc-consensus-grandpa = { workspace = true, default-features = true } -sp-authority-discovery = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } frame-support = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-consensus-babe = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } pallet-assets = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-message-queue = { workspace = true, default-features = true } +sc-consensus-grandpa = { workspace = true, default-features = true } +sp-authority-discovery = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } +sp-consensus-beefy = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } # Polkadot -polkadot-primitives = { workspace = true, default-features = true } +pallet-xcm = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } polkadot-runtime-parachains = { workspace = true, default-features = true } xcm = { workspace = true, default-features = true } -pallet-xcm = { workspace = true, default-features = true } # Cumulus -parachains-common = { workspace = true, default-features = true } +asset-test-utils = { workspace = true, default-features = true } +cumulus-pallet-parachain-system = { workspace = true, default-features = true } +cumulus-pallet-xcmp-queue = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } +parachains-common = { workspace = true, default-features = true } xcm-emulator = { workspace = true, default-features = true } -cumulus-pallet-xcmp-queue = { workspace = true, default-features = true } -cumulus-pallet-parachain-system = { workspace = true, default-features = true } -asset-test-utils = { workspace = true, default-features = true } # Bridges bp-messages = { workspace = true, default-features = true } bp-xcm-bridge-hub = { workspace = true, default-features = true } pallet-bridge-messages = { workspace = true, default-features = true } pallet-xcm-bridge-hub = { workspace = true, default-features = true } -bridge-runtime-common = { workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/common/src/impls.rs b/cumulus/parachains/integration-tests/emulated/common/src/impls.rs index c0d42cf2758e9431c87e4187eb42b9ab703f7f54..9dad323aa19c72a03d2ed5229b114fcb9d7c0d1b 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/impls.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/impls.rs @@ -370,6 +370,8 @@ macro_rules! impl_send_transact_helpers_for_relay_chain { let destination: $crate::impls::Location = <Self as RelayChain>::child_location_of(recipient); let xcm = $crate::impls::xcm_transact_unpaid_execution(call, $crate::impls::OriginKind::Superuser); + $crate::impls::dmp::Pallet::<<Self as $crate::impls::Chain>::Runtime>::make_parachain_reachable(recipient); + // Send XCM `Transact` $crate::impls::assert_ok!(<Self as [<$chain RelayPallet>]>::XcmPallet::send( root_origin, diff --git a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs index e2757f8b9a35b7ba07067ac708fe37babdd16af4..f5466a63f1f5b9b7018465268c15e2175d976a3c 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs @@ -58,6 +58,8 @@ pub const USDT_ID: u32 = 1984; pub const PENPAL_A_ID: u32 = 2000; pub const PENPAL_B_ID: u32 = 2001; +pub const ASSET_HUB_ROCOCO_ID: u32 = 1000; +pub const ASSET_HUB_WESTEND_ID: u32 = 1000; pub const ASSETS_PALLET_ID: u8 = 50; parameter_types! { diff --git a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs index b776cafb2545e8e25b7e137fbf448ee35293a66a..983ac626177ee60ed14ea4cae75a8096b2191361 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs @@ -23,6 +23,7 @@ pub use pallet_message_queue; pub use pallet_xcm; // Polkadot +pub use polkadot_runtime_parachains::dmp::Pallet as Dmp; pub use xcm::{ prelude::{ AccountId32, All, Asset, AssetId, BuyExecution, DepositAsset, ExpectTransactStatus, @@ -156,6 +157,8 @@ macro_rules! test_relay_is_trusted_teleporter { // Send XCM message from Relay <$sender_relay>::execute_with(|| { + $crate::macros::Dmp::<<$sender_relay as $crate::macros::Chain>::Runtime>::make_parachain_reachable(<$receiver_para>::para_id()); + assert_ok!(<$sender_relay as [<$sender_relay Pallet>]>::XcmPallet::limited_teleport_assets( origin.clone(), bx!(para_destination.clone().into()), @@ -641,9 +644,8 @@ macro_rules! test_dry_run_transfer_across_pk_bridge { let transfer_amount = 10_000_000_000_000u128; let initial_balance = transfer_amount * 10; - // Bridge setup. + // AssetHub setup. $sender_asset_hub::force_xcm_version($destination, XCM_VERSION); - open_bridge_between_asset_hub_rococo_and_asset_hub_westend(); <$sender_asset_hub as TestExt>::execute_with(|| { type Runtime = <$sender_asset_hub as Chain>::Runtime; 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 9125c976525eea932800088017d5245a1b3b64f0..380f4983ad9874ae258dcdd5846cf7c1366bd7cd 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs @@ -31,7 +31,7 @@ pub fn xcm_transact_paid_execution( VersionedXcm::from(Xcm(vec![ WithdrawAsset(fees.clone().into()), BuyExecution { fees, weight_limit }, - Transact { origin_kind, call }, + Transact { origin_kind, call, fallback_max_weight: None }, RefundSurplus, DepositAsset { assets: All.into(), @@ -53,7 +53,7 @@ pub fn xcm_transact_unpaid_execution( VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit, check_origin }, - Transact { origin_kind, call }, + Transact { origin_kind, call, fallback_max_weight: None }, ])) } diff --git a/cumulus/parachains/integration-tests/emulated/networks/rococo-system/Cargo.toml b/cumulus/parachains/integration-tests/emulated/networks/rococo-system/Cargo.toml index 864f3c6edd7e3f91566666c65c509d2494009d4d..2f8889e4816263beb8dcbb60173aced583fb338d 100644 --- a/cumulus/parachains/integration-tests/emulated/networks/rococo-system/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/networks/rococo-system/Cargo.toml @@ -12,10 +12,10 @@ workspace = true [dependencies] # Cumulus -emulated-integration-tests-common = { workspace = true } -rococo-emulated-chain = { workspace = true } asset-hub-rococo-emulated-chain = { workspace = true } bridge-hub-rococo-emulated-chain = { workspace = true } -people-rococo-emulated-chain = { workspace = true } -penpal-emulated-chain = { workspace = true } coretime-rococo-emulated-chain = { workspace = true } +emulated-integration-tests-common = { workspace = true } +penpal-emulated-chain = { workspace = true } +people-rococo-emulated-chain = { workspace = true } +rococo-emulated-chain = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/Cargo.toml b/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/Cargo.toml index cd0cb272b7f5ea9bea09835b7cf4612ad9c377be..1b789b21c7dfdfe409aad71e288e915259748f8e 100644 --- a/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/Cargo.toml @@ -12,11 +12,11 @@ workspace = true [dependencies] # Cumulus -emulated-integration-tests-common = { workspace = true } -rococo-emulated-chain = { workspace = true } -westend-emulated-chain = { workspace = true, default-features = true } asset-hub-rococo-emulated-chain = { workspace = true } asset-hub-westend-emulated-chain = { workspace = true } bridge-hub-rococo-emulated-chain = { workspace = true } bridge-hub-westend-emulated-chain = { workspace = true } +emulated-integration-tests-common = { workspace = true } penpal-emulated-chain = { workspace = true } +rococo-emulated-chain = { workspace = true } +westend-emulated-chain = { workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/networks/westend-system/Cargo.toml b/cumulus/parachains/integration-tests/emulated/networks/westend-system/Cargo.toml index cec2e3733b2a0fbe83608f75981e5c288c70ea7f..50e75a6bdd746d45119af7f75e36ee7da28a8343 100644 --- a/cumulus/parachains/integration-tests/emulated/networks/westend-system/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/networks/westend-system/Cargo.toml @@ -12,11 +12,11 @@ workspace = true [dependencies] # Cumulus -emulated-integration-tests-common = { workspace = true } -westend-emulated-chain = { workspace = true } asset-hub-westend-emulated-chain = { workspace = true } bridge-hub-westend-emulated-chain = { workspace = true } collectives-westend-emulated-chain = { workspace = true } +coretime-westend-emulated-chain = { workspace = true } +emulated-integration-tests-common = { workspace = true } penpal-emulated-chain = { workspace = true } people-westend-emulated-chain = { workspace = true } -coretime-westend-emulated-chain = { workspace = true } +westend-emulated-chain = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml index 3d40db6b03ab5e172a9885bde1325bf614c34b19..b53edb39c73b59555994a279f122af6c234a4ce0 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml @@ -11,31 +11,32 @@ publish = false workspace = true [dependencies] -codec = { workspace = true } assert_matches = { workspace = true } +codec = { workspace = true } # Substrate -sp-runtime = { workspace = true } -sp-core = { workspace = true } frame-support = { workspace = true } -pallet-balances = { workspace = true } -pallet-assets = { workspace = true } pallet-asset-conversion = { workspace = true } +pallet-asset-rewards = { workspace = true } +pallet-assets = { workspace = true } +pallet-balances = { workspace = true } pallet-message-queue = { workspace = true } pallet-treasury = { workspace = true } pallet-utility = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } # Polkadot -xcm = { workspace = true } pallet-xcm = { workspace = true } -xcm-executor = { workspace = true } -xcm-runtime-apis = { workspace = true, default-features = true } polkadot-runtime-common = { workspace = true, default-features = true } rococo-runtime-constants = { workspace = true, default-features = true } +xcm = { workspace = true } +xcm-executor = { workspace = true } +xcm-runtime-apis = { workspace = true, default-features = true } # Cumulus asset-test-utils = { workspace = true, default-features = true } cumulus-pallet-parachain-system = { workspace = true } -parachains-common = { workspace = true, default-features = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } rococo-system-emulated-network = { workspace = true } 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 f3a1b3f5bfa282b21aba49bbe79a81780508f975..513ca278a319e49e326ae0725d62cbb1324f48bf 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 @@ -76,10 +76,11 @@ mod imports { genesis::ED as ROCOCO_ED, rococo_runtime::{ governance as rococo_governance, + governance::pallet_custom_origins::Origin::Treasurer, xcm_config::{ UniversalLocation as RococoUniversalLocation, XcmConfig as RococoXcmConfig, }, - OriginCaller as RococoOriginCaller, + Dmp, OriginCaller as RococoOriginCaller, }, RococoRelayPallet as RococoPallet, }, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs index baec7d20f4156657134ba8963adc61208c29266f..fb95c361f089188a5bb35e6f7daf9f49c868136b 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs @@ -13,6 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use rococo_system_emulated_network::rococo_emulated_chain::rococo_runtime::Dmp; + use super::reserve_transfer::*; use crate::{ imports::*, @@ -777,6 +779,8 @@ fn transfer_native_asset_from_relay_to_para_through_asset_hub() { xcm: xcm_on_final_dest, }]); + Dmp::make_parachain_reachable(AssetHubRococo::para_id()); + // First leg is a teleport, from there a local-reserve-transfer to final dest <Rococo as RococoPallet>::XcmPallet::transfer_assets_using_type_and_then( t.signed_origin, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs index 88fa379c4072b97e867f3eccb68bb23b0eeedff2..75714acb07cd9e2b126bd4238e6c0377c8561e8f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs @@ -16,6 +16,7 @@ mod claim_assets; mod hybrid_transfers; mod reserve_transfer; +mod reward_pool; mod send; mod set_xcm_versions; mod swap; 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 698ef2c9e792887800a77a9cc5a61503be819a4d..407a581afeb97c0ff9b09666d75f793557d32035 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 @@ -14,6 +14,7 @@ // limitations under the License. use crate::imports::*; +use rococo_system_emulated_network::rococo_emulated_chain::rococo_runtime::Dmp; use sp_core::{crypto::get_public_from_string_or_panic, sr25519}; fn relay_to_para_sender_assertions(t: RelayToParaTest) { @@ -115,7 +116,7 @@ pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { assert_expected_events!( AssetHubRococo, vec![ - // Transport fees are paid + // Delivery fees are paid RuntimeEvent::PolkadotXcm(pallet_xcm::Event::FeesPaid { .. }) => {}, ] ); @@ -274,7 +275,7 @@ fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) { t.args.dest.clone() ), }, - // Transport fees are paid + // Delivery fees are paid RuntimeEvent::PolkadotXcm( pallet_xcm::Event::FeesPaid { .. } ) => {}, @@ -305,7 +306,7 @@ fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) { owner: *owner == t.sender.account_id, balance: *balance == t.args.amount, }, - // Transport fees are paid + // Delivery fees are paid RuntimeEvent::PolkadotXcm( pallet_xcm::Event::FeesPaid { .. } ) => {}, @@ -487,6 +488,11 @@ pub fn para_to_para_through_hop_receiver_assertions<Hop: Clone>(t: Test<PenpalA, } fn relay_to_para_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult { + let Junction::Parachain(para_id) = *t.args.dest.chain_location().last().unwrap() else { + unimplemented!("Destination is not a parachain?") + }; + + Dmp::make_parachain_reachable(para_id); <Rococo as RococoPallet>::XcmPallet::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), @@ -546,6 +552,13 @@ fn para_to_system_para_reserve_transfer_assets(t: ParaToSystemParaTest) -> Dispa fn para_to_para_through_relay_limited_reserve_transfer_assets( t: ParaToParaThroughRelayTest, ) -> DispatchResult { + let Junction::Parachain(para_id) = *t.args.dest.chain_location().last().unwrap() else { + unimplemented!("Destination is not a parachain?") + }; + + Rococo::ext_wrapper(|| { + Dmp::make_parachain_reachable(para_id); + }); <PenpalA as PenpalAPallet>::PolkadotXcm::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs new file mode 100644 index 0000000000000000000000000000000000000000..2f3ee536a7b9948e17d6be9ccf2033cce625b580 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs @@ -0,0 +1,114 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::imports::*; +use codec::Encode; +use frame_support::{assert_ok, sp_runtime::traits::Dispatchable, traits::schedule::DispatchTime}; +use xcm_executor::traits::ConvertLocation; + +#[test] +fn treasury_creates_asset_reward_pool() { + AssetHubRococo::execute_with(|| { + type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent; + type Balances = <AssetHubRococo as AssetHubRococoPallet>::Balances; + + let treasurer = + Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]); + let treasurer_account = + ahr_xcm_config::LocationToAccountId::convert_location(&treasurer).unwrap(); + + assert_ok!(Balances::force_set_balance( + <AssetHubRococo as Chain>::RuntimeOrigin::root(), + treasurer_account.clone().into(), + ASSET_HUB_ROCOCO_ED * 100_000, + )); + + let events = AssetHubRococo::events(); + match events.iter().last() { + Some(RuntimeEvent::Balances(pallet_balances::Event::BalanceSet { who, .. })) => + assert_eq!(*who, treasurer_account), + _ => panic!("Expected Balances::BalanceSet event"), + } + }); + + Rococo::execute_with(|| { + type AssetHubRococoRuntimeCall = <AssetHubRococo as Chain>::RuntimeCall; + type AssetHubRococoRuntime = <AssetHubRococo as Chain>::Runtime; + type RococoRuntimeCall = <Rococo as Chain>::RuntimeCall; + type RococoRuntime = <Rococo as Chain>::Runtime; + type RococoRuntimeEvent = <Rococo as Chain>::RuntimeEvent; + type RococoRuntimeOrigin = <Rococo as Chain>::RuntimeOrigin; + + Dmp::make_parachain_reachable(AssetHubRococo::para_id()); + + let staked_asset_id = bx!(RelayLocation::get()); + let reward_asset_id = bx!(RelayLocation::get()); + + let reward_rate_per_block = 1_000_000_000; + let lifetime = 1_000_000_000; + let admin = None; + + let create_pool_call = + RococoRuntimeCall::XcmPallet(pallet_xcm::Call::<RococoRuntime>::send { + dest: bx!(VersionedLocation::V4( + xcm::v4::Junction::Parachain(AssetHubRococo::para_id().into()).into() + )), + message: bx!(VersionedXcm::V5(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::SovereignAccount, + fallback_max_weight: None, + call: AssetHubRococoRuntimeCall::AssetRewards( + pallet_asset_rewards::Call::<AssetHubRococoRuntime>::create_pool { + staked_asset_id, + reward_asset_id, + reward_rate_per_block, + expiry: DispatchTime::After(lifetime), + admin + } + ) + .encode() + .into(), + } + ]))), + }); + + let treasury_origin: RococoRuntimeOrigin = Treasurer.into(); + assert_ok!(create_pool_call.dispatch(treasury_origin)); + + assert_expected_events!( + Rococo, + vec![ + RococoRuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + AssetHubRococo::execute_with(|| { + type Runtime = <AssetHubRococo as Chain>::Runtime; + type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent; + + assert_eq!(1, pallet_asset_rewards::Pools::<Runtime>::iter().count()); + + let events = AssetHubRococo::events(); + match events.iter().last() { + Some(RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { + success: true, + .. + })) => (), + _ => panic!("Expected MessageQueue::Processed event"), + } + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/treasury.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/treasury.rs index 69111d38bcac3b8d7278be81170d8158ba0bf4ea..8648c8ce9311d05430468b01510f8f88514d5e08 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/treasury.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/treasury.rs @@ -29,6 +29,7 @@ use frame_support::{ use parachains_common::AccountId; use polkadot_runtime_common::impls::VersionedLocatableAsset; use rococo_runtime_constants::currency::GRAND; +use rococo_system_emulated_network::rococo_emulated_chain::rococo_runtime::Dmp; use xcm_executor::traits::ConvertLocation; // Fund Treasury account on Asset Hub from Treasury account on Relay Chain with ROCs. @@ -64,6 +65,7 @@ fn spend_roc_on_asset_hub() { treasury_balance * 2, )); + Dmp::make_parachain_reachable(1000); let native_asset = Location::here(); let asset_hub_location: Location = [Parachain(1000)].into(); let treasury_location: Location = (Parent, PalletInstance(18)).into(); @@ -199,6 +201,8 @@ fn create_and_claim_treasury_spend_in_usdt() { // create a conversion rate from `asset_kind` to the native currency. assert_ok!(AssetRate::create(root.clone(), Box::new(asset_kind.clone()), 2.into())); + Dmp::make_parachain_reachable(1000); + // create and approve a treasury spend. assert_ok!(Treasury::spend( root, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml index 7117124b1d1f9a9c1f8fb279897501720589658f..ef68a53c3b18bd629ca2feda51bb7b76defb081e 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml @@ -11,36 +11,37 @@ publish = false workspace = true [dependencies] -codec = { workspace = true } assert_matches = { workspace = true } +codec = { workspace = true } # Substrate -sp-runtime = { workspace = true } -sp-core = { workspace = true } frame-metadata-hash-extension = { workspace = true, default-features = true } frame-support = { workspace = true } frame-system = { workspace = true } -pallet-balances = { workspace = true } -pallet-assets = { workspace = true } pallet-asset-conversion = { workspace = true } -pallet-treasury = { workspace = true } +pallet-asset-rewards = { workspace = true } +pallet-asset-tx-payment = { workspace = true } +pallet-assets = { workspace = true } +pallet-balances = { workspace = true } pallet-message-queue = { workspace = true } pallet-transaction-payment = { workspace = true } -pallet-asset-tx-payment = { workspace = true } +pallet-treasury = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } # Polkadot +pallet-xcm = { workspace = true } polkadot-runtime-common = { workspace = true, default-features = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } -pallet-xcm = { workspace = true } xcm-runtime-apis = { workspace = true } # Cumulus -assets-common = { workspace = true } -parachains-common = { workspace = true, default-features = true } asset-test-utils = { workspace = true, default-features = true } -cumulus-pallet-xcmp-queue = { workspace = true } +assets-common = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } +cumulus-pallet-xcmp-queue = { workspace = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } westend-system-emulated-network = { workspace = true } 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 3cca99fbfe5cfaee1f4254742b4a27f870984d16..68dc87250f76bdcd458166069cf4e4b78f61e94f 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 @@ -79,8 +79,12 @@ mod imports { }, westend_emulated_chain::{ genesis::ED as WESTEND_ED, - westend_runtime::xcm_config::{ - UniversalLocation as WestendUniversalLocation, XcmConfig as WestendXcmConfig, + westend_runtime::{ + governance::pallet_custom_origins::Origin::Treasurer, + xcm_config::{ + UniversalLocation as WestendUniversalLocation, XcmConfig as WestendXcmConfig, + }, + Dmp, }, WestendRelayPallet as WestendPallet, }, @@ -106,6 +110,7 @@ mod imports { pub type ParaToParaThroughRelayTest = Test<PenpalA, PenpalB, Westend>; pub type ParaToParaThroughAHTest = Test<PenpalA, PenpalB, AssetHubWestend>; pub type RelayToParaThroughAHTest = Test<Westend, PenpalA, AssetHubWestend>; + pub type PenpalToRelayThroughAHTest = Test<PenpalA, Westend, AssetHubWestend>; } #[cfg(test)] diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs index a0fc82fba6ef5ddc59921f2fa744d47c81dd834e..91ebdda16828a6fa6039c73d64dc226da9be1521 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs @@ -13,6 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use westend_system_emulated_network::westend_emulated_chain::westend_runtime::Dmp; + use super::reserve_transfer::*; use crate::{ imports::*, @@ -658,13 +660,13 @@ fn bidirectional_teleport_foreign_asset_between_para_and_asset_hub_using_explici } // =============================================================== -// ===== Transfer - Native Asset - Relay->AssetHub->Parachain ==== +// ====== Transfer - Native Asset - Relay->AssetHub->Penpal ====== // =============================================================== -/// Transfers of native asset Relay to Parachain (using AssetHub reserve). Parachains want to avoid +/// Transfers of native asset Relay to Penpal (using AssetHub reserve). Parachains want to avoid /// managing SAs on all system chains, thus want all their DOT-in-reserve to be held in their /// Sovereign Account on Asset Hub. #[test] -fn transfer_native_asset_from_relay_to_para_through_asset_hub() { +fn transfer_native_asset_from_relay_to_penpal_through_asset_hub() { // Init values for Relay let destination = Westend::child_location_of(PenpalA::para_id()); let sender = WestendSender::get(); @@ -778,6 +780,8 @@ fn transfer_native_asset_from_relay_to_para_through_asset_hub() { xcm: xcm_on_final_dest, }]); + Dmp::make_parachain_reachable(AssetHubWestend::para_id()); + // First leg is a teleport, from there a local-reserve-transfer to final dest <Westend as WestendPallet>::XcmPallet::transfer_assets_using_type_and_then( t.signed_origin, @@ -820,6 +824,137 @@ fn transfer_native_asset_from_relay_to_para_through_asset_hub() { assert!(receiver_assets_after < receiver_assets_before + amount_to_send); } +// =============================================================== +// ===== Transfer - Native Asset - Penpal->AssetHub->Relay ======= +// =============================================================== +/// Transfers of native asset Penpal to Relay (using AssetHub reserve). Parachains want to avoid +/// managing SAs on all system chains, thus want all their DOT-in-reserve to be held in their +/// Sovereign Account on Asset Hub. +#[test] +fn transfer_native_asset_from_penpal_to_relay_through_asset_hub() { + // Init values for Penpal + let destination = RelayLocation::get(); + let sender = PenpalASender::get(); + let amount_to_send: Balance = WESTEND_ED * 100; + + // Init values for Penpal + let relay_native_asset_location = RelayLocation::get(); + let receiver = WestendReceiver::get(); + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination.clone(), + receiver.clone(), + amount_to_send, + (Parent, amount_to_send).into(), + None, + 0, + ), + }; + let mut test = PenpalToRelayThroughAHTest::new(test_args); + + let sov_penpal_on_ah = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalA::para_id()), + ); + // fund Penpal's sender account + PenpalA::mint_foreign_asset( + <PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()), + relay_native_asset_location.clone(), + sender.clone(), + amount_to_send * 2, + ); + // fund Penpal's SA on AssetHub with the assets held in reserve + AssetHubWestend::fund_accounts(vec![(sov_penpal_on_ah.clone().into(), amount_to_send * 2)]); + + // prefund Relay checking account so we accept teleport "back" from AssetHub + let check_account = + Westend::execute_with(|| <Westend as WestendPallet>::XcmPallet::check_account()); + Westend::fund_accounts(vec![(check_account, amount_to_send)]); + + // Query initial balances + let sender_balance_before = PenpalA::execute_with(|| { + type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets; + <ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender) + }); + let sov_penpal_on_ah_before = AssetHubWestend::execute_with(|| { + <AssetHubWestend as AssetHubWestendPallet>::Balances::free_balance(sov_penpal_on_ah.clone()) + }); + let receiver_balance_before = Westend::execute_with(|| { + <Westend as WestendPallet>::Balances::free_balance(receiver.clone()) + }); + + fn transfer_assets_dispatchable(t: PenpalToRelayThroughAHTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let asset_hub_location = PenpalA::sibling_location_of(AssetHubWestend::para_id()); + let context = PenpalUniversalLocation::get(); + + // reanchor fees to the view of destination (Westend Relay) + let mut remote_fees = fee.clone().reanchored(&t.args.dest, &context).unwrap(); + if let Fungible(ref mut amount) = remote_fees.fun { + // we already spent some fees along the way, just use half of what we started with + *amount = *amount / 2; + } + let xcm_on_final_dest = Xcm::<()>(vec![ + BuyExecution { fees: remote_fees, weight_limit: t.args.weight_limit.clone() }, + DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }, + ]); + + // reanchor final dest (Westend Relay) to the view of hop (Asset Hub) + let mut dest = t.args.dest.clone(); + dest.reanchor(&asset_hub_location, &context).unwrap(); + // on Asset Hub + let xcm_on_hop = Xcm::<()>(vec![InitiateTeleport { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + dest, + xcm: xcm_on_final_dest, + }]); + + // First leg is a reserve-withdraw, from there a teleport to final dest + <PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type_and_then( + t.signed_origin, + bx!(asset_hub_location.into()), + bx!(t.args.assets.into()), + bx!(TransferType::DestinationReserve), + bx!(fee.id.into()), + bx!(TransferType::DestinationReserve), + bx!(VersionedXcm::from(xcm_on_hop)), + t.args.weight_limit, + ) + } + test.set_dispatchable::<PenpalA>(transfer_assets_dispatchable); + test.assert(); + + // Query final balances + let sender_balance_after = PenpalA::execute_with(|| { + type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets; + <ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender) + }); + let sov_penpal_on_ah_after = AssetHubWestend::execute_with(|| { + <AssetHubWestend as AssetHubWestendPallet>::Balances::free_balance(sov_penpal_on_ah.clone()) + }); + let receiver_balance_after = Westend::execute_with(|| { + <Westend as WestendPallet>::Balances::free_balance(receiver.clone()) + }); + + // Sender's asset balance is reduced by amount sent plus delivery fees + assert!(sender_balance_after < sender_balance_before - amount_to_send); + // SA on AH balance is decreased by `amount_to_send` + assert_eq!(sov_penpal_on_ah_after, sov_penpal_on_ah_before - amount_to_send); + // Receiver's balance is increased + assert!(receiver_balance_after > receiver_balance_before); + // Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(receiver_balance_after < receiver_balance_before + amount_to_send); +} + // ============================================================================================== // ==== Bidirectional Transfer - Native + Teleportable Foreign Assets - Parachain<->AssetHub ==== // ============================================================================================== @@ -839,7 +974,7 @@ fn bidirectional_transfer_multiple_assets_between_penpal_and_asset_hub() { // xcm to be executed at dest let xcm_on_dest = Xcm(vec![ // since this is the last hop, we don't need to further use any assets previously - // reserved for fees (there are no further hops to cover transport fees for); we + // reserved for fees (there are no further hops to cover delivery fees for); we // RefundSurplus to get back any unspent fees RefundSurplus, DepositAsset { assets: Wild(All), beneficiary: t.args.beneficiary }, @@ -875,7 +1010,7 @@ fn bidirectional_transfer_multiple_assets_between_penpal_and_asset_hub() { // xcm to be executed at dest let xcm_on_dest = Xcm(vec![ // since this is the last hop, we don't need to further use any assets previously - // reserved for fees (there are no further hops to cover transport fees for); we + // reserved for fees (there are no further hops to cover delivery fees for); we // RefundSurplus to get back any unspent fees RefundSurplus, DepositAsset { assets: Wild(All), beneficiary: t.args.beneficiary }, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs index 0dfe7a85f4c2a08ecb7d7701cabe64d0045a13f2..576c44fc542fdc5fd26365078d96ac7ee4796515 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs @@ -17,6 +17,7 @@ mod claim_assets; mod fellowship_treasury; mod hybrid_transfers; mod reserve_transfer; +mod reward_pool; mod send; mod set_asset_claimer; mod set_xcm_versions; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index 558eab13e5c7cd945d350ed9b9e6ee88e6ea29a2..dc36fed42932c5ed219c87c4f20bc86e111443bd 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -15,6 +15,7 @@ use crate::{create_pool_with_wnd_on, foreign_balance_on, imports::*}; use sp_core::{crypto::get_public_from_string_or_panic, sr25519}; +use westend_system_emulated_network::westend_emulated_chain::westend_runtime::Dmp; fn relay_to_para_sender_assertions(t: RelayToParaTest) { type RuntimeEvent = <Westend as Chain>::RuntimeEvent; @@ -115,7 +116,7 @@ pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { assert_expected_events!( AssetHubWestend, vec![ - // Transport fees are paid + // Delivery fees are paid RuntimeEvent::PolkadotXcm(pallet_xcm::Event::FeesPaid { .. }) => {}, ] ); @@ -274,7 +275,7 @@ fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) { t.args.dest.clone() ), }, - // Transport fees are paid + // Delivery fees are paid RuntimeEvent::PolkadotXcm( pallet_xcm::Event::FeesPaid { .. } ) => {}, @@ -305,7 +306,7 @@ fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) { owner: *owner == t.sender.account_id, balance: *balance == t.args.amount, }, - // Transport fees are paid + // Delivery fees are paid RuntimeEvent::PolkadotXcm( pallet_xcm::Event::FeesPaid { .. } ) => {}, @@ -487,6 +488,11 @@ pub fn para_to_para_through_hop_receiver_assertions<Hop: Clone>(t: Test<PenpalA, } fn relay_to_para_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult { + let Junction::Parachain(para_id) = *t.args.dest.chain_location().last().unwrap() else { + unimplemented!("Destination is not a parachain?") + }; + + Dmp::make_parachain_reachable(para_id); <Westend as WestendPallet>::XcmPallet::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), @@ -533,6 +539,13 @@ fn para_to_system_para_reserve_transfer_assets(t: ParaToSystemParaTest) -> Dispa fn para_to_para_through_relay_limited_reserve_transfer_assets( t: ParaToParaThroughRelayTest, ) -> DispatchResult { + let Junction::Parachain(para_id) = *t.args.dest.chain_location().last().unwrap() else { + unimplemented!("Destination is not a parachain?") + }; + + Westend::ext_wrapper(|| { + Dmp::make_parachain_reachable(para_id); + }); <PenpalA as PenpalAPallet>::PolkadotXcm::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs new file mode 100644 index 0000000000000000000000000000000000000000..4df51abcacebf4ff0102b5d50271ca9cad7e14e6 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs @@ -0,0 +1,113 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::imports::*; +use codec::Encode; +use frame_support::{assert_ok, sp_runtime::traits::Dispatchable, traits::schedule::DispatchTime}; +use xcm_executor::traits::ConvertLocation; + +#[test] +fn treasury_creates_asset_reward_pool() { + AssetHubWestend::execute_with(|| { + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + type Balances = <AssetHubWestend as AssetHubWestendPallet>::Balances; + + let treasurer = + Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]); + let treasurer_account = + ahw_xcm_config::LocationToAccountId::convert_location(&treasurer).unwrap(); + + assert_ok!(Balances::force_set_balance( + <AssetHubWestend as Chain>::RuntimeOrigin::root(), + treasurer_account.clone().into(), + ASSET_HUB_WESTEND_ED * 100_000, + )); + + let events = AssetHubWestend::events(); + match events.iter().last() { + Some(RuntimeEvent::Balances(pallet_balances::Event::BalanceSet { who, .. })) => + assert_eq!(*who, treasurer_account), + _ => panic!("Expected Balances::BalanceSet event"), + } + }); + Westend::execute_with(|| { + type AssetHubWestendRuntimeCall = <AssetHubWestend as Chain>::RuntimeCall; + type AssetHubWestendRuntime = <AssetHubWestend as Chain>::Runtime; + type WestendRuntimeCall = <Westend as Chain>::RuntimeCall; + type WestendRuntime = <Westend as Chain>::Runtime; + type WestendRuntimeEvent = <Westend as Chain>::RuntimeEvent; + type WestendRuntimeOrigin = <Westend as Chain>::RuntimeOrigin; + + Dmp::make_parachain_reachable(AssetHubWestend::para_id()); + + let staked_asset_id = bx!(RelayLocation::get()); + let reward_asset_id = bx!(RelayLocation::get()); + + let reward_rate_per_block = 1_000_000_000; + let lifetime = 1_000_000_000; + let admin = None; + + let create_pool_call = + WestendRuntimeCall::XcmPallet(pallet_xcm::Call::<WestendRuntime>::send { + dest: bx!(VersionedLocation::V4( + xcm::v4::Junction::Parachain(AssetHubWestend::para_id().into()).into() + )), + message: bx!(VersionedXcm::V5(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::SovereignAccount, + fallback_max_weight: None, + call: AssetHubWestendRuntimeCall::AssetRewards( + pallet_asset_rewards::Call::<AssetHubWestendRuntime>::create_pool { + staked_asset_id, + reward_asset_id, + reward_rate_per_block, + expiry: DispatchTime::After(lifetime), + admin + } + ) + .encode() + .into(), + } + ]))), + }); + + let treasury_origin: WestendRuntimeOrigin = Treasurer.into(); + assert_ok!(create_pool_call.dispatch(treasury_origin)); + + assert_expected_events!( + Westend, + vec![ + WestendRuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + AssetHubWestend::execute_with(|| { + type Runtime = <AssetHubWestend as Chain>::Runtime; + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + + assert_eq!(1, pallet_asset_rewards::Pools::<Runtime>::iter().count()); + + let events = AssetHubWestend::events(); + match events.iter().last() { + Some(RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { + success: true, + .. + })) => (), + _ => panic!("Expected MessageQueue::Processed event"), + } + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/set_asset_claimer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/set_asset_claimer.rs index 544b05360521e50b2f99c4b47ab7282eb91187bd..bc00106b47c149468fbc53811b22fe7d4def5e2d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/set_asset_claimer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/set_asset_claimer.rs @@ -44,7 +44,7 @@ fn test_set_asset_claimer_within_a_chain() { type RuntimeCall = <AssetHubWestend as Chain>::RuntimeCall; let asset_trap_xcm = Xcm::<RuntimeCall>::builder_unsafe() - .set_asset_claimer(bob_location.clone()) + .set_hints(vec![AssetClaimer { location: bob_location.clone() }]) .withdraw_asset(assets.clone()) .clear_origin() .build(); @@ -116,7 +116,7 @@ fn test_set_asset_claimer_between_the_chains() { let assets: Assets = (Parent, trap_amount).into(); type RuntimeCall = <BridgeHubWestend as Chain>::RuntimeCall; let trap_xcm = Xcm::<RuntimeCall>::builder_unsafe() - .set_asset_claimer(alice_bh_sibling.clone()) + .set_hints(vec![AssetClaimer { location: alice_bh_sibling.clone() }]) .withdraw_asset(assets.clone()) .clear_origin() .build(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs index 3c53cfb261be4c2c0d09d0080e29e7df1ebdd284..7e881a332a537207e518da0da9bf94c301a1ac38 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs @@ -43,10 +43,10 @@ fn transfer_and_transact_in_same_xcm( // xcm to be executed at dest let xcm_on_dest = Xcm(vec![ - Transact { origin_kind: OriginKind::Xcm, call }, + Transact { origin_kind: OriginKind::Xcm, call, fallback_max_weight: None }, ExpectTransactStatus(MaybeErrorCode::Success), // since this is the last hop, we don't need to further use any assets previously - // reserved for fees (there are no further hops to cover transport fees for); we + // reserved for fees (there are no further hops to cover delivery fees for); we // RefundSurplus to get back any unspent fees RefundSurplus, DepositAsset { assets: Wild(All), beneficiary }, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/treasury.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/treasury.rs index c303e6411d33b7a4485aa0132855d0237a26eb1b..3b53557fc05c614046c548e8b01a97b971dcd45b 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/treasury.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/treasury.rs @@ -20,6 +20,7 @@ use emulated_integration_tests_common::{ }; use frame_support::traits::fungibles::{Inspect, Mutate}; use polkadot_runtime_common::impls::VersionedLocatableAsset; +use westend_system_emulated_network::westend_emulated_chain::westend_runtime::Dmp; use xcm_executor::traits::ConvertLocation; #[test] @@ -58,6 +59,8 @@ fn create_and_claim_treasury_spend() { // create a conversion rate from `asset_kind` to the native currency. assert_ok!(AssetRate::create(root.clone(), Box::new(asset_kind.clone()), 2.into())); + Dmp::make_parachain_reachable(1000); + // create and approve a treasury spend. assert_ok!(Treasury::spend( root, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml index 9f6fe78a33eebfccbe387df6a86336cb0ed1737b..35ceffe4c6953a84165987a330a1d806b248e33f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml @@ -12,27 +12,26 @@ workspace = true [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } hex-literal = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } # Substrate -sp-core = { workspace = true } frame-support = { workspace = true } -pallet-assets = { workspace = true } pallet-asset-conversion = { workspace = true } +pallet-assets = { workspace = true } pallet-balances = { workspace = true } pallet-message-queue = { workspace = true, default-features = true } +sp-core = { workspace = true } sp-runtime = { workspace = true } # Polkadot -xcm = { workspace = true } pallet-xcm = { workspace = true } +xcm = { workspace = true } xcm-executor = { workspace = true } xcm-runtime-apis = { workspace = true } # Bridges pallet-bridge-messages = { workspace = true } -pallet-xcm-bridge-hub = { workspace = true } # Cumulus cumulus-pallet-xcmp-queue = { workspace = true } @@ -44,7 +43,7 @@ testnet-parachains-constants = { features = ["rococo", "westend"], workspace = t # Snowbridge snowbridge-core = { workspace = true } -snowbridge-router-primitives = { workspace = true } -snowbridge-pallet-system = { workspace = true } -snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-pallet-inbound-queue-fixtures = { workspace = true, default-features = true } +snowbridge-pallet-outbound-queue = { workspace = true } +snowbridge-pallet-system = { workspace = true } +snowbridge-router-primitives = { workspace = true } 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 54bc395c86f094b22350d5940c29b0b2a32cd36f..f84d42cb29f8ecfe5821b5814a9f4dac34ab0246 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 @@ -50,6 +50,7 @@ mod imports { AssetHubWestendParaPallet as AssetHubWestendPallet, }, bridge_hub_rococo_emulated_chain::{ + bridge_hub_rococo_runtime::bridge_to_ethereum_config::EthereumGatewayAddress, genesis::ED as BRIDGE_HUB_ROCOCO_ED, BridgeHubRococoExistentialDeposit, BridgeHubRococoParaPallet as BridgeHubRococoPallet, BridgeHubRococoRuntimeOrigin, BridgeHubRococoXcmConfig, EthereumBeaconClient, EthereumInboundQueue, 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 33ab1e70b97b95b8369457b7e72bd0bf2d205f07..d1fe94962f184846e6b5b7f1d8efbf01b7c69c66 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 @@ -16,7 +16,7 @@ use crate::tests::*; fn send_assets_over_bridge<F: FnOnce()>(send_fn: F) { - // fund the AHR's SA on BHR for paying bridge transport fees + // fund the AHR's SA on BHR for paying bridge delivery fees BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128); // set XCM versions @@ -25,9 +25,6 @@ fn send_assets_over_bridge<F: FnOnce()>(send_fn: F) { AssetHubRococo::force_xcm_version(asset_hub_westend_location(), XCM_VERSION); BridgeHubRococo::force_xcm_version(bridge_hub_westend_location(), XCM_VERSION); - // open bridge - open_bridge_between_asset_hub_rococo_and_asset_hub_westend(); - // send message over bridge send_fn(); 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 8aff877559616ccb24b2f3de1a3a00492404d907..265002897ac5fead8b9b47e8fc0a7ff962572e6f 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 @@ -51,9 +51,6 @@ pub(crate) fn bridged_roc_at_ah_westend() -> Location { } // WND and wWND -pub(crate) fn wnd_at_ah_westend() -> Location { - Parent.into() -} pub(crate) fn bridged_wnd_at_ah_rococo() -> Location { Location::new(2, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))]) } @@ -240,43 +237,3 @@ pub(crate) fn assert_bridge_hub_westend_message_received() { ); }) } - -pub(crate) fn open_bridge_between_asset_hub_rococo_and_asset_hub_westend() { - use testnet_parachains_constants::{ - rococo::currency::UNITS as ROC, westend::currency::UNITS as WND, - }; - - // open AHR -> AHW - BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), ROC * 5); - AssetHubRococo::open_bridge( - AssetHubRococo::sibling_location_of(BridgeHubRococo::para_id()), - [ - GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), - Parachain(AssetHubWestend::para_id().into()), - ] - .into(), - Some(( - (roc_at_ah_rococo(), ROC * 1).into(), - BridgeHubRococo::sovereign_account_id_of(BridgeHubRococo::sibling_location_of( - AssetHubRococo::para_id(), - )), - )), - ); - - // open AHW -> AHR - BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), WND * 5); - AssetHubWestend::open_bridge( - AssetHubWestend::sibling_location_of(BridgeHubWestend::para_id()), - [ - GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH)), - Parachain(AssetHubRococo::para_id().into()), - ] - .into(), - Some(( - (wnd_at_ah_westend(), WND * 1).into(), - BridgeHubWestend::sovereign_account_id_of(BridgeHubWestend::sibling_location_of( - AssetHubWestend::para_id(), - )), - )), - ); -} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/register_bridged_assets.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/register_bridged_assets.rs index 1ae3a1b15805b5b9507da7bce3a32c278a6ac6a9..70e7a7a3ddd36f2e18da935cf0b62db874d57a53 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/register_bridged_assets.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/register_bridged_assets.rs @@ -58,7 +58,7 @@ fn register_rococo_asset_on_wah_from_rah() { let destination = asset_hub_westend_location(); - // fund the RAH's SA on RBH for paying bridge transport fees + // fund the RAH's SA on RBH for paying bridge delivery fees BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128); // set XCM versions 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 931a3128f826997086eed4bb224e0c33edeef78b..799af037869754d70ddd14a97026429526816ea2 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs @@ -13,6 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use rococo_system_emulated_network::rococo_emulated_chain::rococo_runtime::Dmp; + use crate::tests::*; #[test] @@ -38,6 +40,8 @@ fn send_xcm_from_rococo_relay_to_westend_asset_hub_should_fail_on_not_applicable // Rococo Global Consensus // Send XCM message from Relay Chain to Bridge Hub source Parachain Rococo::execute_with(|| { + Dmp::make_parachain_reachable(BridgeHubRococo::para_id()); + assert_ok!(<Rococo as RococoPallet>::XcmPallet::send( sudo_origin, bx!(destination), @@ -65,14 +69,11 @@ fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() { let native_token = Location::parent(); let amount = ASSET_HUB_ROCOCO_ED * 1_000; - // fund the AHR's SA on BHR for paying bridge transport fees + // fund the AHR's SA on BHR for paying bridge delivery fees BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128); // fund sender AssetHubRococo::fund_accounts(vec![(AssetHubRococoSender::get().into(), amount * 10)]); - // open bridge - open_bridge_between_asset_hub_rococo_and_asset_hub_westend(); - // Initially set only default version on all runtimes let newer_xcm_version = xcm::prelude::XCM_VERSION; let older_xcm_version = newer_xcm_version - 1; 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 d59553574c26a4f56628bb2fb139a184089a9d4c..7f242bab5a9dafee282a8003cf92f46ee6897b0c 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 @@ -20,8 +20,8 @@ use hex_literal::hex; use rococo_westend_system_emulated_network::BridgeHubRococoParaSender as BridgeHubRococoSender; use snowbridge_core::{inbound::InboundQueueFixture, outbound::OperatingMode}; use snowbridge_pallet_inbound_queue_fixtures::{ - register_token::make_register_token_message, send_token::make_send_token_message, - send_token_to_penpal::make_send_token_to_penpal_message, + register_token::make_register_token_message, send_native_eth::make_send_native_eth_message, + send_token::make_send_token_message, send_token_to_penpal::make_send_token_to_penpal_message, }; use snowbridge_pallet_system; use snowbridge_router_primitives::inbound::{ @@ -84,7 +84,11 @@ fn create_agent() { let remote_xcm = VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, DescendOrigin(Parachain(origin_para).into()), - Transact { origin_kind: OriginKind::Xcm, call: create_agent_call.encode().into() }, + Transact { + origin_kind: OriginKind::Xcm, + call: create_agent_call.encode().into(), + fallback_max_weight: None, + }, ])); // Rococo Global Consensus @@ -138,7 +142,11 @@ fn create_channel() { let create_agent_xcm = VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, DescendOrigin(Parachain(origin_para).into()), - Transact { origin_kind: OriginKind::Xcm, call: create_agent_call.encode().into() }, + Transact { + origin_kind: OriginKind::Xcm, + call: create_agent_call.encode().into(), + fallback_max_weight: None, + }, ])); let create_channel_call = @@ -147,7 +155,11 @@ fn create_channel() { let create_channel_xcm = VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, DescendOrigin(Parachain(origin_para).into()), - Transact { origin_kind: OriginKind::Xcm, call: create_channel_call.encode().into() }, + Transact { + origin_kind: OriginKind::Xcm, + call: create_channel_call.encode().into(), + fallback_max_weight: None, + }, ])); // Rococo Global Consensus @@ -226,7 +238,7 @@ fn register_weth_token_from_ethereum_to_asset_hub() { /// Tests the registering of a token as an asset on AssetHub, and then subsequently sending /// a token from Ethereum to AssetHub. #[test] -fn send_token_from_ethereum_to_asset_hub() { +fn send_weth_token_from_ethereum_to_asset_hub() { BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id().into(), INITIAL_FUND); // Fund ethereum sovereign on AssetHub @@ -266,7 +278,7 @@ fn send_token_from_ethereum_to_asset_hub() { /// Tests sending a token to a 3rd party parachain, called PenPal. The token reserve is /// still located on AssetHub. #[test] -fn send_token_from_ethereum_to_penpal() { +fn send_weth_from_ethereum_to_penpal() { let asset_hub_sovereign = BridgeHubRococo::sovereign_account_id_of(Location::new( 1, [Parachain(AssetHubRococo::para_id().into())], @@ -486,8 +498,8 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { assert!( events.iter().any(|event| matches!( event, - RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) - if *who == TREASURY_ACCOUNT.into() && *amount == 16903333 + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount: _amount }) + if *who == TREASURY_ACCOUNT.into() )), "Snowbridge sovereign takes local fee." ); @@ -495,8 +507,178 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { assert!( events.iter().any(|event| matches!( event, - RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) - if *who == assethub_sovereign && *amount == 2680000000000, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount: _amount }) + if *who == assethub_sovereign + )), + "AssetHub sovereign takes remote fee." + ); + }); +} + +/// Tests the full cycle of eth transfers: +/// - sending a token to AssetHub +/// - returning the token to Ethereum +#[test] +fn send_eth_asset_from_asset_hub_to_ethereum_and_back() { + let ethereum_network: NetworkId = EthereumNetwork::get().into(); + let origin_location = (Parent, Parent, ethereum_network).into(); + + use ahr_xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee; + let assethub_location = BridgeHubRococo::sibling_location_of(AssetHubRococo::para_id()); + let assethub_sovereign = BridgeHubRococo::sovereign_account_id_of(assethub_location); + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::<AccountId>::convert_location(&origin_location).unwrap(); + + AssetHubRococo::force_default_xcm_version(Some(XCM_VERSION)); + BridgeHubRococo::force_default_xcm_version(Some(XCM_VERSION)); + AssetHubRococo::force_xcm_version(origin_location.clone(), XCM_VERSION); + + BridgeHubRococo::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); + AssetHubRococo::fund_accounts(vec![ + (AssetHubRococoReceiver::get(), INITIAL_FUND), + (ethereum_sovereign.clone(), INITIAL_FUND), + ]); + + // Register ETH + AssetHubRococo::execute_with(|| { + type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent; + type RuntimeOrigin = <AssetHubRococo as Chain>::RuntimeOrigin; + assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::ForeignAssets::force_create( + RuntimeOrigin::root(), + origin_location.clone(), + ethereum_sovereign.into(), + true, + 1000, + )); + + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::ForceCreated { .. }) => {}, + ] + ); + }); + const ETH_AMOUNT: u128 = 1_000_000_000_000_000_000; + + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = <BridgeHubRococo as Chain>::RuntimeEvent; + type RuntimeOrigin = <BridgeHubRococo as Chain>::RuntimeOrigin; + + // Set the gateway. This is needed because new fixtures use a different gateway address. + assert_ok!(<BridgeHubRococo as Chain>::System::set_storage( + RuntimeOrigin::root(), + vec![( + EthereumGatewayAddress::key().to_vec(), + sp_core::H160(hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d")).encode(), + )], + )); + + // Construct SendToken message and sent to inbound queue + assert_ok!(send_inbound_message(make_send_native_eth_message())); + + // Check that the send token message was sent using xcm + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent; + type RuntimeOrigin = <AssetHubRococo as Chain>::RuntimeOrigin; + + let _issued_event = RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { + asset_id: origin_location.clone(), + owner: AssetHubRococoReceiver::get().into(), + amount: ETH_AMOUNT, + }); + // Check that AssetHub has issued the foreign asset + assert_expected_events!( + AssetHubRococo, + vec![ + _issued_event => {}, + ] + ); + let assets = + vec![Asset { id: AssetId(origin_location.clone()), fun: Fungible(ETH_AMOUNT) }]; + let multi_assets = VersionedAssets::from(Assets::from(assets)); + + let destination = origin_location.clone().into(); + + let beneficiary = VersionedLocation::from(Location::new( + 0, + [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], + )); + + let free_balance_before = <AssetHubRococo as AssetHubRococoPallet>::Balances::free_balance( + AssetHubRococoReceiver::get(), + ); + // Send the Weth back to Ethereum + <AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::limited_reserve_transfer_assets( + RuntimeOrigin::signed(AssetHubRococoReceiver::get()), + Box::new(destination), + Box::new(beneficiary), + Box::new(multi_assets), + 0, + Unlimited, + ) + .unwrap(); + + let _burned_event = RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned { + asset_id: origin_location.clone(), + owner: AssetHubRococoReceiver::get().into(), + balance: ETH_AMOUNT, + }); + // Check that AssetHub has issued the foreign asset + let _destination = origin_location.clone(); + assert_expected_events!( + AssetHubRococo, + vec![ + _burned_event => {}, + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { + destination: _destination, .. + }) => {}, + ] + ); + + let free_balance_after = <AssetHubRococo as AssetHubRococoPallet>::Balances::free_balance( + AssetHubRococoReceiver::get(), + ); + // Assert at least DefaultBridgeHubEthereumBaseFee charged from the sender + let free_balance_diff = free_balance_before - free_balance_after; + assert!(free_balance_diff > DefaultBridgeHubEthereumBaseFee::get()); + }); + + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = <BridgeHubRococo as Chain>::RuntimeEvent; + // Check that the transfer token back to Ethereum message was queue in the Ethereum + // Outbound Queue + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageAccepted {..}) => {}, + RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued {..}) => {}, + ] + ); + + let events = BridgeHubRococo::events(); + // Check that the local fee was credited to the Snowbridge sovereign account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount: _amount }) + if *who == TREASURY_ACCOUNT.into() + )), + "Snowbridge sovereign takes local fee." + ); + // Check that the remote fee was credited to the AssetHub sovereign account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount: _amount }) + if *who == assethub_sovereign )), "AssetHub sovereign takes remote fee." ); @@ -553,7 +735,7 @@ fn register_weth_token_in_asset_hub_fail_for_insufficient_fee() { }); } -fn send_token_from_ethereum_to_asset_hub_with_fee(account_id: [u8; 32], fee: u128) { +fn send_weth_from_ethereum_to_asset_hub_with_fee(account_id: [u8; 32], fee: u128) { let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); let weth_asset_location: Location = Location::new(2, [ethereum_network_v5.into(), AccountKey20 { network: None, key: WETH }]); @@ -611,8 +793,8 @@ fn send_token_from_ethereum_to_asset_hub_with_fee(account_id: [u8; 32], fee: u12 } #[test] -fn send_token_from_ethereum_to_existent_account_on_asset_hub() { - send_token_from_ethereum_to_asset_hub_with_fee(AssetHubRococoSender::get().into(), XCM_FEE); +fn send_weth_from_ethereum_to_existent_account_on_asset_hub() { + send_weth_from_ethereum_to_asset_hub_with_fee(AssetHubRococoSender::get().into(), XCM_FEE); AssetHubRococo::execute_with(|| { type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent; @@ -628,8 +810,8 @@ fn send_token_from_ethereum_to_existent_account_on_asset_hub() { } #[test] -fn send_token_from_ethereum_to_non_existent_account_on_asset_hub() { - send_token_from_ethereum_to_asset_hub_with_fee([1; 32], XCM_FEE); +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub() { + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], XCM_FEE); AssetHubRococo::execute_with(|| { type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent; @@ -645,8 +827,8 @@ fn send_token_from_ethereum_to_non_existent_account_on_asset_hub() { } #[test] -fn send_token_from_ethereum_to_non_existent_account_on_asset_hub_with_insufficient_fee() { - send_token_from_ethereum_to_asset_hub_with_fee([1; 32], INSUFFICIENT_XCM_FEE); +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub_with_insufficient_fee() { + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], INSUFFICIENT_XCM_FEE); AssetHubRococo::execute_with(|| { type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent; @@ -663,10 +845,10 @@ 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( +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub_with_sufficient_fee_but_do_not_satisfy_ed( ) { // 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); + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], 30_000_000); AssetHubRococo::execute_with(|| { type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml index b87f25ac0f014125bc5807fa8732dfb8ee67067a..f718e7e77f597723c2a53dac3552bb103bab96d9 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml @@ -11,43 +11,42 @@ publish = false workspace = true [dependencies] -hex-literal = { workspace = true, default-features = true } codec = { workspace = true } +hex-literal = { workspace = true, default-features = true } log = { workspace = true } scale-info = { workspace = true } # Substrate frame-support = { workspace = true } -pallet-assets = { workspace = true } pallet-asset-conversion = { workspace = true } +pallet-assets = { workspace = true } pallet-balances = { workspace = true } pallet-message-queue = { workspace = true, default-features = true } sp-core = { workspace = true } sp-runtime = { workspace = true } # Polkadot -xcm = { workspace = true } pallet-xcm = { workspace = true } +xcm = { workspace = true } xcm-executor = { workspace = true } xcm-runtime-apis = { workspace = true } # Bridges pallet-bridge-messages = { workspace = true } -pallet-xcm-bridge-hub = { workspace = true } # Cumulus +asset-hub-westend-runtime = { workspace = true } +bridge-hub-westend-runtime = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } emulated-integration-tests-common = { workspace = true } parachains-common = { workspace = true, default-features = true } rococo-westend-system-emulated-network = { workspace = true } testnet-parachains-constants = { features = ["rococo", "westend"], workspace = true, default-features = true } -asset-hub-westend-runtime = { workspace = true } -bridge-hub-westend-runtime = { workspace = true } # Snowbridge snowbridge-core = { workspace = true } -snowbridge-router-primitives = { workspace = true } -snowbridge-pallet-system = { workspace = true } -snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-pallet-inbound-queue = { workspace = true } snowbridge-pallet-inbound-queue-fixtures = { workspace = true } +snowbridge-pallet-outbound-queue = { workspace = true } +snowbridge-pallet-system = { workspace = true } +snowbridge-router-primitives = { workspace = true } 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 501ddb84d42591a0735932399600cf63e0c1c140..3d4d4f58e3b54f2baf8fc9dcf5683e9b3be4532f 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 @@ -51,9 +51,11 @@ mod imports { }, bridge_hub_westend_emulated_chain::{ genesis::ED as BRIDGE_HUB_WESTEND_ED, BridgeHubWestendExistentialDeposit, - BridgeHubWestendParaPallet as BridgeHubWestendPallet, BridgeHubWestendXcmConfig, + BridgeHubWestendParaPallet as BridgeHubWestendPallet, BridgeHubWestendRuntimeOrigin, + BridgeHubWestendXcmConfig, }, penpal_emulated_chain::{ + self, penpal_runtime::xcm_config::{ CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub, LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub, 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 ab09517339db71c3eeff70ebec15bcf8218f02f4..a73c1280b406a7154c05707a4dfe53946ac9c1c9 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 @@ -17,7 +17,7 @@ use crate::{create_pool_with_native_on, tests::*}; use xcm::latest::AssetTransferFilter; fn send_assets_over_bridge<F: FnOnce()>(send_fn: F) { - // fund the AHW's SA on BHW for paying bridge transport fees + // fund the AHW's SA on BHW for paying bridge delivery fees BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), 10_000_000_000_000u128); // set XCM versions @@ -26,9 +26,6 @@ fn send_assets_over_bridge<F: FnOnce()>(send_fn: F) { AssetHubWestend::force_xcm_version(asset_hub_rococo_location(), XCM_VERSION); BridgeHubWestend::force_xcm_version(bridge_hub_rococo_location(), XCM_VERSION); - // open bridge - open_bridge_between_asset_hub_rococo_and_asset_hub_westend(); - // send message over bridge send_fn(); @@ -592,7 +589,7 @@ fn do_send_pens_and_wnds_from_penpal_westend_via_ahw_to_asset_hub_rococo( // XCM to be executed at dest (Rococo Asset Hub) let xcm_on_dest = Xcm(vec![ // since this is the last hop, we don't need to further use any assets previously - // reserved for fees (there are no further hops to cover transport fees for); we + // reserved for fees (there are no further hops to cover delivery fees for); we // RefundSurplus to get back any unspent fees RefundSurplus, // deposit everything to final beneficiary 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 6c1cdb98e8b2a69b38431d1119d554069e362e4a..676b2862e66783618eeb6d8f0c90d66cbd657936 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 @@ -52,9 +52,6 @@ pub(crate) fn bridged_wnd_at_ah_rococo() -> Location { } // ROC and wROC -pub(crate) fn roc_at_ah_rococo() -> Location { - Parent.into() -} pub(crate) fn bridged_roc_at_ah_westend() -> Location { Location::new(2, [GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH))]) } @@ -250,43 +247,3 @@ pub(crate) fn assert_bridge_hub_rococo_message_received() { ); }) } - -pub(crate) fn open_bridge_between_asset_hub_rococo_and_asset_hub_westend() { - use testnet_parachains_constants::{ - rococo::currency::UNITS as ROC, westend::currency::UNITS as WND, - }; - - // open AHR -> AHW - BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), ROC * 5); - AssetHubRococo::open_bridge( - AssetHubRococo::sibling_location_of(BridgeHubRococo::para_id()), - [ - GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), - Parachain(AssetHubWestend::para_id().into()), - ] - .into(), - Some(( - (roc_at_ah_rococo(), ROC * 1).into(), - BridgeHubRococo::sovereign_account_id_of(BridgeHubRococo::sibling_location_of( - AssetHubRococo::para_id(), - )), - )), - ); - - // open AHW -> AHR - BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), WND * 5); - AssetHubWestend::open_bridge( - AssetHubWestend::sibling_location_of(BridgeHubWestend::para_id()), - [ - GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH)), - Parachain(AssetHubRococo::para_id().into()), - ] - .into(), - Some(( - (wnd_at_ah_westend(), WND * 1).into(), - BridgeHubWestend::sovereign_account_id_of(BridgeHubWestend::sibling_location_of( - AssetHubWestend::para_id(), - )), - )), - ); -} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/register_bridged_assets.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/register_bridged_assets.rs index 424f1e55956bda9cc5f87428540895abbdad3554..952fc35e670345e9257fe1addbe483f3545fc482 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/register_bridged_assets.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/register_bridged_assets.rs @@ -82,7 +82,7 @@ fn register_asset_on_rah_from_wah(bridged_asset_at_rah: Location) { let destination = asset_hub_rococo_location(); - // fund the WAH's SA on WBH for paying bridge transport fees + // fund the WAH's SA on WBH for paying bridge delivery fees BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), 10_000_000_000_000u128); // set XCM versions diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs index 787d7dc842cb27b33dd771c62b43f15361611ad6..e655f06a0f01c7d7189a0c6a905eb76821442917 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs @@ -13,6 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use rococo_westend_system_emulated_network::westend_emulated_chain::westend_runtime::Dmp; + use crate::tests::*; #[test] @@ -38,6 +40,8 @@ fn send_xcm_from_westend_relay_to_rococo_asset_hub_should_fail_on_not_applicable // Westend Global Consensus // Send XCM message from Relay Chain to Bridge Hub source Parachain Westend::execute_with(|| { + Dmp::make_parachain_reachable(BridgeHubWestend::para_id()); + assert_ok!(<Westend as WestendPallet>::XcmPallet::send( sudo_origin, bx!(destination), @@ -65,14 +69,11 @@ fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() { let native_token = Location::parent(); let amount = ASSET_HUB_WESTEND_ED * 1_000; - // fund the AHR's SA on BHR for paying bridge transport fees + // fund the AHR's SA on BHR for paying bridge delivery fees BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), 10_000_000_000_000u128); // fund sender AssetHubWestend::fund_accounts(vec![(AssetHubWestendSender::get().into(), amount * 10)]); - // open bridge - open_bridge_between_asset_hub_rococo_and_asset_hub_westend(); - // Initially set only default version on all runtimes let newer_xcm_version = xcm::prelude::XCM_VERSION; let older_xcm_version = newer_xcm_version - 1; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs index ffa60a4f52e746668da4f76a882d5e46789ffa63..6789aae83ffe4bc4e48feaa3c1db353286cd1260 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -12,15 +12,18 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use crate::imports::*; +use crate::{imports::*, tests::penpal_emulated_chain::penpal_runtime}; use asset_hub_westend_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee; -use bridge_hub_westend_runtime::EthereumInboundQueue; +use bridge_hub_westend_runtime::{ + bridge_to_ethereum_config::EthereumGatewayAddress, EthereumBeaconClient, EthereumInboundQueue, +}; use codec::{Decode, Encode}; -use emulated_integration_tests_common::RESERVABLE_ASSET_ID; +use emulated_integration_tests_common::{PENPAL_B_ID, RESERVABLE_ASSET_ID}; use frame_support::pallet_prelude::TypeInfo; use hex_literal::hex; use rococo_westend_system_emulated_network::asset_hub_westend_emulated_chain::genesis::AssetHubWestendAssetOwner; -use snowbridge_core::{outbound::OperatingMode, AssetMetadata, TokenIdOf}; +use snowbridge_core::{inbound::InboundQueueFixture, AssetMetadata, TokenIdOf}; +use snowbridge_pallet_inbound_queue_fixtures::send_native_eth::make_send_native_eth_message; use snowbridge_router_primitives::inbound::{ Command, Destination, EthereumLocationsConverterFor, MessageV1, VersionedMessage, }; @@ -28,19 +31,20 @@ use sp_core::H256; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; use xcm_executor::traits::ConvertLocation; -const INITIAL_FUND: u128 = 5_000_000_000_000; pub const CHAIN_ID: u64 = 11155111; pub const WETH: [u8; 20] = hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"); +const INITIAL_FUND: u128 = 5_000_000_000_000; const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); const XCM_FEE: u128 = 100_000_000_000; +const INSUFFICIENT_XCM_FEE: u128 = 1000; const TOKEN_AMOUNT: u128 = 100_000_000_000; +const TREASURY_ACCOUNT: [u8; 32] = + hex!("6d6f646c70792f74727372790000000000000000000000000000000000000000"); #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] pub enum ControlCall { #[codec(index = 3)] CreateAgent, - #[codec(index = 4)] - CreateChannel { mode: OperatingMode }, } #[allow(clippy::large_enum_variant)] @@ -50,6 +54,75 @@ pub enum SnowbridgeControl { Control(ControlCall), } +pub fn send_inbound_message(fixture: InboundQueueFixture) -> DispatchResult { + EthereumBeaconClient::store_finalized_header( + fixture.finalized_header, + fixture.block_roots_root, + ) + .unwrap(); + EthereumInboundQueue::submit( + BridgeHubWestendRuntimeOrigin::signed(BridgeHubWestendSender::get()), + fixture.message, + ) +} + +/// Create an agent on Ethereum. An agent is a representation of an entity in the Polkadot +/// ecosystem (like a parachain) on Ethereum. +#[test] +#[ignore] +fn create_agent() { + let origin_para: u32 = 1001; + // Fund the origin parachain sovereign account so that it can pay execution fees. + BridgeHubWestend::fund_para_sovereign(origin_para.into(), INITIAL_FUND); + + let sudo_origin = <Westend as Chain>::RuntimeOrigin::root(); + let destination = Westend::child_location_of(BridgeHubWestend::para_id()).into(); + + let create_agent_call = SnowbridgeControl::Control(ControlCall::CreateAgent {}); + // Construct XCM to create an agent for para 1001 + let remote_xcm = VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + DescendOrigin(Parachain(origin_para).into()), + Transact { + origin_kind: OriginKind::Xcm, + call: create_agent_call.encode().into(), + fallback_max_weight: None, + }, + ])); + + // Westend Global Consensus + // Send XCM message from Relay Chain to Bridge Hub source Parachain + Westend::execute_with(|| { + assert_ok!(<Westend as WestendPallet>::XcmPallet::send( + sudo_origin, + bx!(destination), + bx!(remote_xcm), + )); + + type RuntimeEvent = <Westend as Chain>::RuntimeEvent; + // Check that the Transact message was sent + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent; + // Check that a message was sent to Ethereum to create the agent + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent { + .. + }) => {}, + ] + ); + }); +} + /// Tests the registering of a token as an asset on AssetHub. #[test] fn register_weth_token_from_ethereum_to_asset_hub() { @@ -82,6 +155,566 @@ fn register_weth_token_from_ethereum_to_asset_hub() { }); } +/// Tests the registering of a token as an asset on AssetHub, and then subsequently sending +/// a token from Ethereum to AssetHub. +#[test] +fn send_weth_token_from_ethereum_to_asset_hub() { + let ethereum_network: NetworkId = EthereumNetwork::get().into(); + let origin_location = Location::new(2, ethereum_network); + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::<AccountId>::convert_location(&origin_location).unwrap(); + + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); + + // Fund ethereum sovereign on AssetHub + AssetHubWestend::fund_accounts(vec![ + (AssetHubWestendReceiver::get(), INITIAL_FUND), + (ethereum_sovereign, INITIAL_FUND), + ]); + + // Register the token + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent; + + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::RegisterToken { token: WETH.into(), fee: XCM_FEE }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {},] + ); + }); + + // Send the token + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent; + + type EthereumInboundQueue = + <BridgeHubWestend as BridgeHubWestendPallet>::EthereumInboundQueue; + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::AccountId32 { id: AssetHubWestendSender::get().into() }, + amount: 1_000_000, + fee: XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + assert_ok!(EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into())); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +/// Tests sending a token to a 3rd party parachain, called PenPal. The token reserve is +/// still located on AssetHub. +#[test] +fn send_weth_from_ethereum_to_penpal() { + let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubWestend::para_id().into())], + )); + // Fund AssetHub sovereign account so it can pay execution fees for the asset transfer + BridgeHubWestend::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); + + // Fund PenPal receiver (covering ED) + let native_id: Location = Parent.into(); + let receiver: AccountId = [ + 28, 189, 45, 67, 83, 10, 68, 112, 90, 208, 136, 175, 49, 62, 24, 248, 11, 83, 239, 22, 179, + 97, 119, 205, 75, 119, 184, 70, 242, 165, 240, 124, + ] + .into(); + PenpalB::mint_foreign_asset( + <PenpalB as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()), + native_id, + receiver, + penpal_runtime::EXISTENTIAL_DEPOSIT, + ); + + PenpalB::execute_with(|| { + assert_ok!(<PenpalB as Chain>::System::set_storage( + <PenpalB as Chain>::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]).encode(), + )], + )); + }); + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + + // The Weth asset location, identified by the contract address on Ethereum + let weth_asset_location: Location = + (Parent, Parent, ethereum_network_v5, AccountKey20 { network: None, key: WETH }).into(); + + let origin_location = (Parent, Parent, ethereum_network_v5).into(); + + // Fund ethereum sovereign on AssetHub + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::<AccountId>::convert_location(&origin_location).unwrap(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + + // Create asset on the Penpal parachain. + PenpalB::execute_with(|| { + assert_ok!(<PenpalB as PenpalBPallet>::ForeignAssets::force_create( + <PenpalB as Chain>::RuntimeOrigin::root(), + weth_asset_location.clone(), + asset_hub_sovereign.into(), + false, + 1000, + )); + + assert!(<PenpalB as PenpalBPallet>::ForeignAssets::asset_exists(weth_asset_location)); + }); + + // Register the token + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent; + + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::RegisterToken { token: WETH.into(), fee: XCM_FEE }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {},] + ); + }); + + // Send the token + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent; + + type EthereumInboundQueue = + <BridgeHubWestend as BridgeHubWestendPallet>::EthereumInboundQueue; + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::ForeignAccountId32 { + para_id: PENPAL_B_ID, + id: PenpalBReceiver::get().into(), + fee: XCM_FEE, + }, + amount: 1_000_000, + fee: XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + assert_ok!(EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into())); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + // Check that the assets were issued on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + PenpalB::execute_with(|| { + type RuntimeEvent = <PenpalB as Chain>::RuntimeEvent; + // Check that the assets were issued on PenPal + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +/// Tests the full cycle of eth transfers: +/// - sending a token to AssetHub +/// - returning the token to Ethereum +#[test] +fn send_eth_asset_from_asset_hub_to_ethereum_and_back() { + let ethereum_network: NetworkId = EthereumNetwork::get().into(); + let origin_location = (Parent, Parent, ethereum_network).into(); + + use asset_hub_westend_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee; + let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::<AccountId>::convert_location(&origin_location).unwrap(); + + AssetHubWestend::force_default_xcm_version(Some(XCM_VERSION)); + BridgeHubWestend::force_default_xcm_version(Some(XCM_VERSION)); + AssetHubWestend::force_xcm_version(origin_location.clone(), XCM_VERSION); + + BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); + AssetHubWestend::fund_accounts(vec![ + (AssetHubWestendReceiver::get(), INITIAL_FUND), + (ethereum_sovereign.clone(), INITIAL_FUND), + ]); + + // Register ETH + AssetHubWestend::execute_with(|| { + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + type RuntimeOrigin = <AssetHubWestend as Chain>::RuntimeOrigin; + assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::ForeignAssets::force_create( + RuntimeOrigin::root(), + origin_location.clone(), + ethereum_sovereign.into(), + true, + 1000, + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::ForceCreated { .. }) => {}, + ] + ); + }); + const ETH_AMOUNT: u128 = 1_000_000_000_000_000_000; + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent; + type RuntimeOrigin = <BridgeHubWestend as Chain>::RuntimeOrigin; + + // Set the gateway. This is needed because new fixtures use a different gateway address. + assert_ok!(<BridgeHubWestend as Chain>::System::set_storage( + RuntimeOrigin::root(), + vec![( + EthereumGatewayAddress::key().to_vec(), + sp_core::H160(hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d")).encode(), + )], + )); + + // Construct SendToken message and sent to inbound queue + assert_ok!(send_inbound_message(make_send_native_eth_message())); + + // Check that the send token message was sent using xcm + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + type RuntimeOrigin = <AssetHubWestend as Chain>::RuntimeOrigin; + + let _issued_event = RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { + asset_id: origin_location.clone(), + owner: AssetHubWestendReceiver::get().into(), + amount: ETH_AMOUNT, + }); + // Check that AssetHub has issued the foreign asset + assert_expected_events!( + AssetHubWestend, + vec![ + _issued_event => {}, + ] + ); + let assets = + vec![Asset { id: AssetId(origin_location.clone()), fun: Fungible(ETH_AMOUNT) }]; + let multi_assets = VersionedAssets::from(Assets::from(assets)); + + let destination = origin_location.clone().into(); + + let beneficiary = VersionedLocation::from(Location::new( + 0, + [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], + )); + + let free_balance_before = + <AssetHubWestend as AssetHubWestendPallet>::Balances::free_balance( + AssetHubWestendReceiver::get(), + ); + // Send the Weth back to Ethereum + <AssetHubWestend as AssetHubWestendPallet>::PolkadotXcm::limited_reserve_transfer_assets( + RuntimeOrigin::signed(AssetHubWestendReceiver::get()), + Box::new(destination), + Box::new(beneficiary), + Box::new(multi_assets), + 0, + Unlimited, + ) + .unwrap(); + + let _burned_event = RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned { + asset_id: origin_location.clone(), + owner: AssetHubWestendReceiver::get().into(), + balance: ETH_AMOUNT, + }); + // Check that AssetHub has issued the foreign asset + let _destination = origin_location.clone(); + assert_expected_events!( + AssetHubWestend, + vec![ + _burned_event => {}, + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { + destination: _destination, .. + }) => {}, + ] + ); + + let free_balance_after = <AssetHubWestend as AssetHubWestendPallet>::Balances::free_balance( + AssetHubWestendReceiver::get(), + ); + // Assert at least DefaultBridgeHubEthereumBaseFee charged from the sender + let free_balance_diff = free_balance_before - free_balance_after; + assert!(free_balance_diff > DefaultBridgeHubEthereumBaseFee::get()); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent; + // Check that the transfer token back to Ethereum message was queue in the Ethereum + // Outbound Queue + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageAccepted {..}) => {}, + RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued {..}) => {}, + ] + ); + + let events = BridgeHubWestend::events(); + // Check that the local fee was credited to the Snowbridge sovereign account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount: _ }) + if *who == TREASURY_ACCOUNT.into() + )), + "Snowbridge sovereign takes local fee." + ); + // Check that the remote fee was credited to the AssetHub sovereign account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount: _ }) + if *who == assethub_sovereign + )), + "AssetHub sovereign takes remote fee." + ); + }); +} + +#[test] +fn register_weth_token_in_asset_hub_fail_for_insufficient_fee() { + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent; + type EthereumInboundQueue = + <BridgeHubWestend as BridgeHubWestendPallet>::EthereumInboundQueue; + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::RegisterToken { + token: WETH.into(), + // Insufficient fee which should trigger the trap + fee: INSUFFICIENT_XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success:false, .. }) => {}, + ] + ); + }); +} + +fn send_weth_from_ethereum_to_asset_hub_with_fee(account_id: [u8; 32], fee: u128) { + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + let weth_asset_location: Location = + Location::new(2, [ethereum_network_v5.into(), AccountKey20 { network: None, key: WETH }]); + // Fund asset hub sovereign on bridge hub + let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubWestend::para_id().into())], + )); + BridgeHubWestend::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); + + // Register WETH + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = <AssetHubWestend as Chain>::RuntimeOrigin; + + assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_asset_location.clone().try_into().unwrap(), + asset_hub_sovereign.into(), + false, + 1, + )); + + assert!(<AssetHubWestend as AssetHubWestendPallet>::ForeignAssets::asset_exists( + weth_asset_location.clone().try_into().unwrap(), + )); + }); + + // Send WETH to an existent account on asset hub + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent; + + type EthereumInboundQueue = + <BridgeHubWestend as BridgeHubWestendPallet>::EthereumInboundQueue; + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::AccountId32 { id: account_id }, + amount: 1_000_000, + fee, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + assert_ok!(EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into())); + + // Check that the message was sent + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_weth_from_ethereum_to_existent_account_on_asset_hub() { + send_weth_from_ethereum_to_asset_hub_with_fee(AssetHubWestendSender::get().into(), XCM_FEE); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub() { + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], XCM_FEE); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub_with_insufficient_fee() { + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], INSUFFICIENT_XCM_FEE); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + + // Check that the message was not processed successfully due to insufficient fee + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success:false, .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub_with_sufficient_fee_but_do_not_satisfy_ed( +) { + // On AH the xcm fee is 26_789_690 and the ED is 3_300_000 + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], 30_000_000); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + + // Check that the message was not processed successfully due to insufficient ED + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success:false, .. }) => {}, + ] + ); + }); +} + /// Tests the registering of a token as an asset on AssetHub, and then subsequently sending /// a token from Ethereum to AssetHub. #[test] @@ -269,8 +902,8 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { assert!( events.iter().any(|event| matches!( event, - RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) - if *who == TreasuryAccount::get().into() && *amount == 5071000000 + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount: _amount }) + if *who == TreasuryAccount::get().into() )), "Snowbridge sovereign takes local fee." ); @@ -278,8 +911,8 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { assert!( events.iter().any(|event| matches!( event, - RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) - if *who == assethub_sovereign && *amount == 2680000000000, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount: _amount }) + if *who == assethub_sovereign )), "AssetHub sovereign takes remote fee." ); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/transact.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/transact.rs index db42704dae614304cfc74f044cf60cf8fd7c5bef..f6a3c53c4bf57c7edf48942efdd90f30d8de0aef 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/transact.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/transact.rs @@ -49,10 +49,10 @@ fn transfer_and_transact_in_same_xcm( // xcm to be executed at dest let xcm_on_dest = Xcm(vec![ - Transact { origin_kind: OriginKind::Xcm, call }, + Transact { origin_kind: OriginKind::Xcm, call, fallback_max_weight: None }, ExpectTransactStatus(MaybeErrorCode::Success), // since this is the last hop, we don't need to further use any assets previously - // reserved for fees (there are no further hops to cover transport fees for); we + // reserved for fees (there are no further hops to cover delivery fees for); we // RefundSurplus to get back any unspent fees RefundSurplus, DepositAsset { assets: Wild(All), beneficiary }, 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 c4d281b75a77b32a9772ade66e2dbb0837cc8fcf..1d4e93d40da454a30f12c9f645010b2121560b9e 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 @@ -11,31 +11,31 @@ publish = false workspace = true [dependencies] -codec = { workspace = true } assert_matches = { workspace = true } +codec = { workspace = true } # Substrate -sp-runtime = { workspace = true } frame-support = { workspace = true } -pallet-balances = { workspace = true } pallet-asset-rate = { workspace = true } pallet-assets = { workspace = true } -pallet-treasury = { workspace = true } +pallet-balances = { workspace = true } pallet-message-queue = { workspace = true } +pallet-treasury = { workspace = true } pallet-utility = { workspace = true } pallet-whitelist = { workspace = true } +sp-runtime = { workspace = true } # Polkadot +pallet-xcm = { workspace = true } polkadot-runtime-common = { workspace = true, default-features = true } +westend-runtime-constants = { workspace = true, default-features = true } xcm = { workspace = true } xcm-executor = { workspace = true } -pallet-xcm = { workspace = true } -westend-runtime-constants = { workspace = true, default-features = true } # Cumulus -parachains-common = { workspace = true, default-features = true } -testnet-parachains-constants = { features = ["westend"], workspace = true, default-features = true } -cumulus-pallet-xcmp-queue = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } +cumulus-pallet-xcmp-queue = { workspace = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } +testnet-parachains-constants = { features = ["westend"], workspace = true, default-features = true } westend-system-emulated-network = { workspace = 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 index 80b82e0c446f79d633b725806ecad27a69c2edf8..802fed1e681dac4841cd3a116caf90d96d73895b 100644 --- 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 @@ -41,6 +41,7 @@ fn fellows_whitelist_call() { ) .encode() .into(), + fallback_max_weight: None } ]))), }); diff --git a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship_treasury.rs b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship_treasury.rs index 8418e3da3bba0731a56c6b9dfbeb378d089adf28..ed7c9bafc607f0f39bad26ff6f939fb5c97ee243 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship_treasury.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship_treasury.rs @@ -20,6 +20,7 @@ use frame_support::{ }; use polkadot_runtime_common::impls::VersionedLocatableAsset; use westend_runtime_constants::currency::UNITS; +use westend_system_emulated_network::westend_emulated_chain::westend_runtime::Dmp; use xcm_executor::traits::ConvertLocation; // Fund Fellowship Treasury from Westend Treasury and spend from Fellowship Treasury. @@ -57,6 +58,8 @@ fn fellowship_treasury_spend() { treasury_balance * 2, )); + Dmp::make_parachain_reachable(1000); + let native_asset = Location::here(); let asset_hub_location: Location = [Parachain(1000)].into(); let treasury_location: Location = (Parent, PalletInstance(37)).into(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/Cargo.toml index 28d9da0993ff6cfbb3c535fc998eb634e90ed00d..61397b1b8d40786e32b36eb9fabda962db19a2c8 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/Cargo.toml @@ -13,8 +13,8 @@ publish = false frame-support = { workspace = true } pallet-balances = { workspace = true } pallet-broker = { workspace = true, default-features = true } -pallet-message-queue = { workspace = true } pallet-identity = { workspace = true } +pallet-message-queue = { workspace = true } sp-runtime = { workspace = true } # Polkadot diff --git a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/tests/coretime_interface.rs b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/tests/coretime_interface.rs index 9915b1753ef6d2af0e5d4c49fa7b166ee68c959b..554025e1ecfed070a60bfc44c76b5a06ebc9818e 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/tests/coretime_interface.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/tests/coretime_interface.rs @@ -17,6 +17,7 @@ use crate::imports::*; use frame_support::traits::OnInitialize; use pallet_broker::{ConfigRecord, Configuration, CoreAssignment, CoreMask, ScheduleItem}; use rococo_runtime_constants::system_parachain::coretime::TIMESLICE_PERIOD; +use rococo_system_emulated_network::rococo_emulated_chain::rococo_runtime::Dmp; use sp_runtime::Perbill; #[test] @@ -34,6 +35,10 @@ fn transact_hardcoded_weights_are_sane() { type CoretimeEvent = <CoretimeRococo as Chain>::RuntimeEvent; type RelayEvent = <Rococo as Chain>::RuntimeEvent; + Rococo::execute_with(|| { + Dmp::make_parachain_reachable(CoretimeRococo::para_id()); + }); + // Reserve a workload, configure broker and start sales. CoretimeRococo::execute_with(|| { // Hooks don't run in emulated tests - workaround as we need `on_initialize` to tick things diff --git a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/Cargo.toml index d57e7926b0ec1fa6f73ed40260d8eb5976be0cd3..9f0eadf136501b7a76020f81b47f37205ace63e7 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/Cargo.toml @@ -13,8 +13,8 @@ publish = false frame-support = { workspace = true } pallet-balances = { workspace = true } pallet-broker = { workspace = true, default-features = true } -pallet-message-queue = { workspace = true } pallet-identity = { workspace = true } +pallet-message-queue = { workspace = true } sp-runtime = { workspace = true } # Polkadot diff --git a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/tests/coretime_interface.rs b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/tests/coretime_interface.rs index 00530f80b958d25f3af36ea17a63059d239831e7..900994b1afc1f62f40b55aad3f7a9c694c2219a9 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/tests/coretime_interface.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/tests/coretime_interface.rs @@ -18,6 +18,7 @@ use frame_support::traits::OnInitialize; use pallet_broker::{ConfigRecord, Configuration, CoreAssignment, CoreMask, ScheduleItem}; use sp_runtime::Perbill; use westend_runtime_constants::system_parachain::coretime::TIMESLICE_PERIOD; +use westend_system_emulated_network::westend_emulated_chain::westend_runtime::Dmp; #[test] fn transact_hardcoded_weights_are_sane() { @@ -34,6 +35,10 @@ fn transact_hardcoded_weights_are_sane() { type CoretimeEvent = <CoretimeWestend as Chain>::RuntimeEvent; type RelayEvent = <Westend as Chain>::RuntimeEvent; + Westend::execute_with(|| { + Dmp::make_parachain_reachable(CoretimeWestend::para_id()); + }); + // Reserve a workload, configure broker and start sales. CoretimeWestend::execute_with(|| { // Hooks don't run in emulated tests - workaround as we need `on_initialize` to tick things diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/Cargo.toml index 011be93ecac73f06a73616802d19a0e871cd8b30..8b12897ef018b9016835857aa5bc75e2306f9133 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/Cargo.toml @@ -13,8 +13,8 @@ codec = { workspace = true } # Substrate frame-support = { workspace = true } pallet-balances = { workspace = true } -pallet-message-queue = { workspace = true } pallet-identity = { workspace = true } +pallet-message-queue = { workspace = true } sp-runtime = { workspace = true } # Polkadot 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 aa6eebc5458f4a1f149b8d5981f18d9e646e7339..e069c1f617836040f151b7ffede7df519e734998 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 @@ -13,13 +13,14 @@ codec = { workspace = true } # Substrate frame-support = { workspace = true } pallet-balances = { workspace = true } -pallet-message-queue = { workspace = true } pallet-identity = { workspace = true } +pallet-message-queue = { workspace = true } pallet-xcm = { workspace = true } sp-runtime = { workspace = true } # Polkadot polkadot-runtime-common = { workspace = true, default-features = true } +westend-runtime = { workspace = true } westend-runtime-constants = { workspace = true, default-features = true } xcm = { workspace = true } xcm-executor = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs new file mode 100644 index 0000000000000000000000000000000000000000..3b1779e40b60a0f407d6001d3ffd6c4723ae349a --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs @@ -0,0 +1,550 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::imports::*; +use frame_support::traits::ProcessMessageError; + +use codec::Encode; +use frame_support::sp_runtime::traits::Dispatchable; +use parachains_common::AccountId; +use people_westend_runtime::people::IdentityInfo; +use westend_runtime::{ + governance::pallet_custom_origins::Origin::GeneralAdmin as GeneralAdminOrigin, Dmp, +}; +use westend_system_emulated_network::people_westend_emulated_chain::people_westend_runtime; + +use pallet_identity::Data; + +use emulated_integration_tests_common::accounts::{ALICE, BOB}; + +#[test] +fn relay_commands_add_registrar() { + let (origin_kind, origin) = (OriginKind::Superuser, <Westend as Chain>::RuntimeOrigin::root()); + + let registrar: AccountId = [1; 32].into(); + Westend::execute_with(|| { + type Runtime = <Westend as Chain>::Runtime; + type RuntimeCall = <Westend as Chain>::RuntimeCall; + type RuntimeEvent = <Westend as Chain>::RuntimeEvent; + type PeopleCall = <PeopleWestend as Chain>::RuntimeCall; + type PeopleRuntime = <PeopleWestend as Chain>::Runtime; + + Dmp::make_parachain_reachable(1004); + + let add_registrar_call = + PeopleCall::Identity(pallet_identity::Call::<PeopleRuntime>::add_registrar { + account: registrar.into(), + }); + + let xcm_message = RuntimeCall::XcmPallet(pallet_xcm::Call::<Runtime>::send { + dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind, + call: add_registrar_call.encode().into(), + fallback_max_weight: None + } + ]))), + }); + + assert_ok!(xcm_message.dispatch(origin)); + + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + PeopleWestend::execute_with(|| { + type RuntimeEvent = <PeopleWestend as Chain>::RuntimeEvent; + + assert_expected_events!( + PeopleWestend, + vec![ + RuntimeEvent::Identity(pallet_identity::Event::RegistrarAdded { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true, .. }) => {}, + ] + ); + }); +} + +#[test] +fn relay_commands_add_registrar_wrong_origin() { + let people_westend_alice = PeopleWestend::account_id_of(ALICE); + + let origins = vec![ + ( + OriginKind::SovereignAccount, + <Westend as Chain>::RuntimeOrigin::signed(people_westend_alice), + ), + (OriginKind::Xcm, GeneralAdminOrigin.into()), + ]; + + let mut signed_origin = true; + + for (origin_kind, origin) in origins { + let registrar: AccountId = [1; 32].into(); + Westend::execute_with(|| { + type Runtime = <Westend as Chain>::Runtime; + type RuntimeCall = <Westend as Chain>::RuntimeCall; + type RuntimeEvent = <Westend as Chain>::RuntimeEvent; + type PeopleCall = <PeopleWestend as Chain>::RuntimeCall; + type PeopleRuntime = <PeopleWestend as Chain>::Runtime; + + Dmp::make_parachain_reachable(1004); + + let add_registrar_call = + PeopleCall::Identity(pallet_identity::Call::<PeopleRuntime>::add_registrar { + account: registrar.into(), + }); + + let xcm_message = RuntimeCall::XcmPallet(pallet_xcm::Call::<Runtime>::send { + dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind, + call: add_registrar_call.encode().into(), + fallback_max_weight: None + } + ]))), + }); + + assert_ok!(xcm_message.dispatch(origin)); + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + PeopleWestend::execute_with(|| { + type RuntimeEvent = <PeopleWestend as Chain>::RuntimeEvent; + + if signed_origin { + assert_expected_events!( + PeopleWestend, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::ProcessingFailed { error: ProcessMessageError::Unsupported, .. }) => {}, + ] + ); + } else { + assert_expected_events!( + PeopleWestend, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true, .. }) => {}, + ] + ); + } + }); + + signed_origin = false; + } +} + +#[test] +fn relay_commands_kill_identity() { + // To kill an identity, first one must be set + PeopleWestend::execute_with(|| { + type PeopleRuntime = <PeopleWestend as Chain>::Runtime; + type PeopleRuntimeEvent = <PeopleWestend as Chain>::RuntimeEvent; + + let people_westend_alice = + <PeopleWestend as Chain>::RuntimeOrigin::signed(PeopleWestend::account_id_of(ALICE)); + + let identity_info = IdentityInfo { + email: Data::Raw(b"test@test.io".to_vec().try_into().unwrap()), + ..Default::default() + }; + let identity: Box<<PeopleRuntime as pallet_identity::Config>::IdentityInformation> = + Box::new(identity_info); + + assert_ok!(<PeopleWestend as PeopleWestendPallet>::Identity::set_identity( + people_westend_alice, + identity + )); + + assert_expected_events!( + PeopleWestend, + vec![ + PeopleRuntimeEvent::Identity(pallet_identity::Event::IdentitySet { .. }) => {}, + ] + ); + }); + + let (origin_kind, origin) = (OriginKind::Superuser, <Westend as Chain>::RuntimeOrigin::root()); + + Westend::execute_with(|| { + type Runtime = <Westend as Chain>::Runtime; + type RuntimeCall = <Westend as Chain>::RuntimeCall; + type PeopleCall = <PeopleWestend as Chain>::RuntimeCall; + type RuntimeEvent = <Westend as Chain>::RuntimeEvent; + type PeopleRuntime = <PeopleWestend as Chain>::Runtime; + + Dmp::make_parachain_reachable(1004); + + let kill_identity_call = + PeopleCall::Identity(pallet_identity::Call::<PeopleRuntime>::kill_identity { + target: people_westend_runtime::MultiAddress::Id(PeopleWestend::account_id_of( + ALICE, + )), + }); + + let xcm_message = RuntimeCall::XcmPallet(pallet_xcm::Call::<Runtime>::send { + dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind, + call: kill_identity_call.encode().into(), + fallback_max_weight: None + } + ]))), + }); + + assert_ok!(xcm_message.dispatch(origin)); + + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + PeopleWestend::execute_with(|| { + type RuntimeEvent = <PeopleWestend as Chain>::RuntimeEvent; + + assert_expected_events!( + PeopleWestend, + vec![ + RuntimeEvent::Identity(pallet_identity::Event::IdentityKilled { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true, .. }) => {}, + ] + ); + }); +} + +#[test] +fn relay_commands_kill_identity_wrong_origin() { + let people_westend_alice = PeopleWestend::account_id_of(BOB); + + let origins = vec![ + ( + OriginKind::SovereignAccount, + <Westend as Chain>::RuntimeOrigin::signed(people_westend_alice), + ), + (OriginKind::Xcm, GeneralAdminOrigin.into()), + ]; + + for (origin_kind, origin) in origins { + Westend::execute_with(|| { + type Runtime = <Westend as Chain>::Runtime; + type RuntimeCall = <Westend as Chain>::RuntimeCall; + type PeopleCall = <PeopleWestend as Chain>::RuntimeCall; + type RuntimeEvent = <Westend as Chain>::RuntimeEvent; + type PeopleRuntime = <PeopleWestend as Chain>::Runtime; + + Dmp::make_parachain_reachable(1004); + + let kill_identity_call = + PeopleCall::Identity(pallet_identity::Call::<PeopleRuntime>::kill_identity { + target: people_westend_runtime::MultiAddress::Id(PeopleWestend::account_id_of( + ALICE, + )), + }); + + let xcm_message = RuntimeCall::XcmPallet(pallet_xcm::Call::<Runtime>::send { + dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind, + call: kill_identity_call.encode().into(), + fallback_max_weight: None + } + ]))), + }); + + assert_ok!(xcm_message.dispatch(origin)); + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + PeopleWestend::execute_with(|| { + assert_expected_events!(PeopleWestend, vec![]); + }); + } +} + +#[test] +fn relay_commands_add_remove_username_authority() { + let people_westend_alice = PeopleWestend::account_id_of(ALICE); + let people_westend_bob = PeopleWestend::account_id_of(BOB); + + let (origin_kind, origin, usr) = + (OriginKind::Superuser, <Westend as Chain>::RuntimeOrigin::root(), "rootusername"); + + // First, add a username authority. + Westend::execute_with(|| { + type Runtime = <Westend as Chain>::Runtime; + type RuntimeCall = <Westend as Chain>::RuntimeCall; + type RuntimeEvent = <Westend as Chain>::RuntimeEvent; + type PeopleCall = <PeopleWestend as Chain>::RuntimeCall; + type PeopleRuntime = <PeopleWestend as Chain>::Runtime; + + Dmp::make_parachain_reachable(1004); + + let add_username_authority = + PeopleCall::Identity(pallet_identity::Call::<PeopleRuntime>::add_username_authority { + authority: people_westend_runtime::MultiAddress::Id(people_westend_alice.clone()), + suffix: b"suffix1".into(), + allocation: 10, + }); + + let add_authority_xcm_msg = RuntimeCall::XcmPallet(pallet_xcm::Call::<Runtime>::send { + dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind, + call: add_username_authority.encode().into(), + fallback_max_weight: None + } + ]))), + }); + + assert_ok!(add_authority_xcm_msg.dispatch(origin.clone())); + + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + // Check events system-parachain-side + PeopleWestend::execute_with(|| { + type RuntimeEvent = <PeopleWestend as Chain>::RuntimeEvent; + + assert_expected_events!( + PeopleWestend, + vec![ + RuntimeEvent::Identity(pallet_identity::Event::AuthorityAdded { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true, .. }) => {}, + ] + ); + }); + + // Now, use the previously added username authority to concede a username to an account. + PeopleWestend::execute_with(|| { + type PeopleRuntimeEvent = <PeopleWestend as Chain>::RuntimeEvent; + let full_username = [usr.to_owned(), ".suffix1".to_owned()].concat().into_bytes(); + + assert_ok!(<PeopleWestend as PeopleWestendPallet>::Identity::set_username_for( + <PeopleWestend as Chain>::RuntimeOrigin::signed(people_westend_alice.clone()), + people_westend_runtime::MultiAddress::Id(people_westend_bob.clone()), + full_username, + None, + true + )); + + assert_expected_events!( + PeopleWestend, + vec![ + PeopleRuntimeEvent::Identity(pallet_identity::Event::UsernameQueued { .. }) => {}, + ] + ); + }); + + // Accept the given username + PeopleWestend::execute_with(|| { + type PeopleRuntimeEvent = <PeopleWestend as Chain>::RuntimeEvent; + let full_username = [usr.to_owned(), ".suffix1".to_owned()].concat().into_bytes(); + + assert_ok!(<PeopleWestend as PeopleWestendPallet>::Identity::accept_username( + <PeopleWestend as Chain>::RuntimeOrigin::signed(people_westend_bob.clone()), + full_username.try_into().unwrap(), + )); + + assert_expected_events!( + PeopleWestend, + vec![ + PeopleRuntimeEvent::Identity(pallet_identity::Event::UsernameSet { .. }) => {}, + ] + ); + }); + + // Now, remove the username authority with another privileged XCM call. + Westend::execute_with(|| { + type Runtime = <Westend as Chain>::Runtime; + type RuntimeCall = <Westend as Chain>::RuntimeCall; + type RuntimeEvent = <Westend as Chain>::RuntimeEvent; + type PeopleCall = <PeopleWestend as Chain>::RuntimeCall; + type PeopleRuntime = <PeopleWestend as Chain>::Runtime; + + Dmp::make_parachain_reachable(1004); + + let remove_username_authority = PeopleCall::Identity(pallet_identity::Call::< + PeopleRuntime, + >::remove_username_authority { + authority: people_westend_runtime::MultiAddress::Id(people_westend_alice.clone()), + suffix: b"suffix1".into(), + }); + + let remove_authority_xcm_msg = RuntimeCall::XcmPallet(pallet_xcm::Call::<Runtime>::send { + dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind, + call: remove_username_authority.encode().into(), + fallback_max_weight: None + } + ]))), + }); + + assert_ok!(remove_authority_xcm_msg.dispatch(origin)); + + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + // Final event check. + PeopleWestend::execute_with(|| { + type RuntimeEvent = <PeopleWestend as Chain>::RuntimeEvent; + + assert_expected_events!( + PeopleWestend, + vec![ + RuntimeEvent::Identity(pallet_identity::Event::AuthorityRemoved { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true, .. }) => {}, + ] + ); + }); +} + +#[test] +fn relay_commands_add_remove_username_authority_wrong_origin() { + let people_westend_alice = PeopleWestend::account_id_of(ALICE); + + let origins = vec![ + ( + OriginKind::SovereignAccount, + <Westend as Chain>::RuntimeOrigin::signed(people_westend_alice.clone()), + ), + (OriginKind::Xcm, GeneralAdminOrigin.into()), + ]; + + for (origin_kind, origin) in origins { + Westend::execute_with(|| { + type Runtime = <Westend as Chain>::Runtime; + type RuntimeCall = <Westend as Chain>::RuntimeCall; + type RuntimeEvent = <Westend as Chain>::RuntimeEvent; + type PeopleCall = <PeopleWestend as Chain>::RuntimeCall; + type PeopleRuntime = <PeopleWestend as Chain>::Runtime; + + Dmp::make_parachain_reachable(1004); + + let add_username_authority = PeopleCall::Identity(pallet_identity::Call::< + PeopleRuntime, + >::add_username_authority { + authority: people_westend_runtime::MultiAddress::Id(people_westend_alice.clone()), + suffix: b"suffix1".into(), + allocation: 10, + }); + + let add_authority_xcm_msg = RuntimeCall::XcmPallet(pallet_xcm::Call::<Runtime>::send { + dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind, + call: add_username_authority.encode().into(), + fallback_max_weight: None + } + ]))), + }); + + assert_ok!(add_authority_xcm_msg.dispatch(origin.clone())); + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + // Check events system-parachain-side + PeopleWestend::execute_with(|| { + assert_expected_events!(PeopleWestend, vec![]); + }); + + Westend::execute_with(|| { + type Runtime = <Westend as Chain>::Runtime; + type RuntimeCall = <Westend as Chain>::RuntimeCall; + type RuntimeEvent = <Westend as Chain>::RuntimeEvent; + type PeopleCall = <PeopleWestend as Chain>::RuntimeCall; + type PeopleRuntime = <PeopleWestend as Chain>::Runtime; + + let remove_username_authority = PeopleCall::Identity(pallet_identity::Call::< + PeopleRuntime, + >::remove_username_authority { + authority: people_westend_runtime::MultiAddress::Id(people_westend_alice.clone()), + suffix: b"suffix1".into(), + }); + + Dmp::make_parachain_reachable(1004); + + let remove_authority_xcm_msg = + RuntimeCall::XcmPallet(pallet_xcm::Call::<Runtime>::send { + dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::SovereignAccount, + call: remove_username_authority.encode().into(), + fallback_max_weight: None, + } + ]))), + }); + + assert_ok!(remove_authority_xcm_msg.dispatch(origin)); + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + PeopleWestend::execute_with(|| { + assert_expected_events!(PeopleWestend, vec![]); + }); + } +} 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 08749b295dc21def8feb2c2318bcbed2b258ba9e..b9ad9e3db467fa06b9f7da5dfe73d2e8a6155c2d 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,4 +14,5 @@ // limitations under the License. mod claim_assets; +mod governance; mod teleport; diff --git a/cumulus/parachains/pallets/collective-content/Cargo.toml b/cumulus/parachains/pallets/collective-content/Cargo.toml index c52021f67e36233eb46c2fe703c9c6cef963ab76..09301bd738f3abd6b3cfa71854b1dbc754e9c0de 100644 --- a/cumulus/parachains/pallets/collective-content/Cargo.toml +++ b/cumulus/parachains/pallets/collective-content/Cargo.toml @@ -5,6 +5,8 @@ authors = ["Parity Technologies <admin@parity.io>"] edition.workspace = true description = "Managed content" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/pallets/parachain-info/Cargo.toml b/cumulus/parachains/pallets/parachain-info/Cargo.toml index e0bed23c4f8c0931d1b5b13c36a696378f62fd83..604441c65f29541787bb9a5a56f30cfb02015f1a 100644 --- a/cumulus/parachains/pallets/parachain-info/Cargo.toml +++ b/cumulus/parachains/pallets/parachain-info/Cargo.toml @@ -5,6 +5,8 @@ name = "staging-parachain-info" version = "0.7.0" license = "Apache-2.0" description = "Pallet to store the parachain ID" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/pallets/ping/Cargo.toml b/cumulus/parachains/pallets/ping/Cargo.toml index 51fc384a4f1408ba7ad9c05f9d172146290d4cd5..248b5d7202fa44ab5fd26748fc2444aa95e006ae 100644 --- a/cumulus/parachains/pallets/ping/Cargo.toml +++ b/cumulus/parachains/pallets/ping/Cargo.toml @@ -5,6 +5,8 @@ name = "cumulus-ping" version = "0.7.0" license = "Apache-2.0" description = "Ping Pallet for Cumulus XCM/UMP testing." +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -13,14 +15,14 @@ workspace = true codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } -sp-runtime = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +sp-runtime = { workspace = true } xcm = { workspace = true } -cumulus-primitives-core = { workspace = true } cumulus-pallet-xcm = { workspace = true } +cumulus-primitives-core = { workspace = true } [features] default = ["std"] diff --git a/cumulus/parachains/pallets/ping/src/lib.rs b/cumulus/parachains/pallets/ping/src/lib.rs index 2cf32c891fc0b88838c1ce0ce99998f0f0fdf2f4..b6423a81db3c9550288ab988d3a0558b94f14c8e 100644 --- a/cumulus/parachains/pallets/ping/src/lib.rs +++ b/cumulus/parachains/pallets/ping/src/lib.rs @@ -114,6 +114,7 @@ pub mod pallet { }) .encode() .into(), + fallback_max_weight: None, }]), ) { Ok((hash, cost)) => { @@ -214,6 +215,7 @@ pub mod pallet { }) .encode() .into(), + fallback_max_weight: None, }]), ) { Ok((hash, cost)) => diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index 42adaba7a27c50f1e3157c6b9a70d662e627ae90..3da8aa9b6cfea9de7a0f7a3d685294f5b7fe5a94 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Rococo variant of Asset Hub parachain runtime" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -25,10 +27,11 @@ frame-system = { workspace = true } frame-system-benchmarking = { optional = true, workspace = true } frame-system-rpc-runtime-api = { workspace = true } frame-try-runtime = { optional = true, workspace = true } +pallet-asset-conversion = { workspace = true } +pallet-asset-conversion-ops = { workspace = true } pallet-asset-conversion-tx-payment = { workspace = true } +pallet-asset-rewards = { workspace = true } pallet-assets = { workspace = true } -pallet-asset-conversion-ops = { workspace = true } -pallet-asset-conversion = { workspace = true } pallet-assets-freezer = { workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } @@ -37,7 +40,6 @@ pallet-message-queue = { workspace = true } pallet-multisig = { workspace = true } pallet-nft-fractionalization = { workspace = true } pallet-nfts = { workspace = true } -pallet-nfts-runtime-api = { workspace = true } pallet-proxy = { workspace = true } pallet-session = { workspace = true } pallet-timestamp = { workspace = true } @@ -49,9 +51,9 @@ sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } -sp-keyring = { workspace = true } -sp-inherents = { workspace = true } sp-genesis-builder = { workspace = true } +sp-inherents = { workspace = true } +sp-keyring = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } @@ -59,46 +61,48 @@ sp-storage = { workspace = true } sp-transaction-pool = { workspace = true } sp-version = { workspace = true } sp-weights = { workspace = true } + # num-traits feature needed for dex integer sq root: primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } # Polkadot -rococo-runtime-constants = { workspace = true } pallet-xcm = { workspace = true } pallet-xcm-benchmarks = { optional = true, workspace = true } polkadot-parachain-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } +rococo-runtime-constants = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } xcm-runtime-apis = { workspace = true } # Cumulus +assets-common = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { features = ["bridging"], workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-utility = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } parachains-common = { workspace = true } testnet-parachains-constants = { features = ["rococo"], workspace = true } -assets-common = { workspace = true } # Bridges -pallet-xcm-bridge-hub-router = { workspace = true } bp-asset-hub-rococo = { workspace = true } bp-asset-hub-westend = { workspace = true } bp-bridge-hub-rococo = { workspace = true } bp-bridge-hub-westend = { workspace = true } +pallet-xcm-bridge-hub-router = { workspace = true } snowbridge-router-primitives = { workspace = true } [dev-dependencies] asset-test-utils = { workspace = true, default-features = true } +parachains-runtimes-test-utils = { workspace = true, default-features = true } [build-dependencies] substrate-wasm-builder = { optional = true, workspace = true, default-features = true } @@ -109,6 +113,7 @@ runtime-benchmarks = [ "assets-common/runtime-benchmarks", "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -119,6 +124,7 @@ runtime-benchmarks = [ "pallet-asset-conversion-ops/runtime-benchmarks", "pallet-asset-conversion-tx-payment/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", + "pallet-asset-rewards/runtime-benchmarks", "pallet-assets-freezer/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", @@ -143,10 +149,12 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", @@ -156,6 +164,7 @@ try-runtime = [ "pallet-asset-conversion-ops/try-runtime", "pallet-asset-conversion-tx-payment/try-runtime", "pallet-asset-conversion/try-runtime", + "pallet-asset-rewards/try-runtime", "pallet-assets-freezer/try-runtime", "pallet-assets/try-runtime", "pallet-aura/try-runtime", @@ -188,11 +197,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", @@ -206,6 +215,7 @@ std = [ "pallet-asset-conversion-ops/std", "pallet-asset-conversion-tx-payment/std", "pallet-asset-conversion/std", + "pallet-asset-rewards/std", "pallet-assets-freezer/std", "pallet-assets/std", "pallet-aura/std", @@ -215,7 +225,6 @@ std = [ "pallet-message-queue/std", "pallet-multisig/std", "pallet-nft-fractionalization/std", - "pallet-nfts-runtime-api/std", "pallet-nfts/std", "pallet-proxy/std", "pallet-session/std", 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 bc48c2d805fdb177fc19e32c2af85a0782d9111d..43b7bf0ba1184e095c2f53eeefceb2b88b154a9a 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -35,7 +35,7 @@ use assets_common::{ foreign_creators::ForeignCreators, local_and_foreign_assets::{LocalFromLeft, TargetFromLeft}, matching::{FromNetwork, FromSiblingParachain}, - AssetIdForTrustBackedAssetsConvert, + AssetIdForPoolAssets, AssetIdForPoolAssetsConvert, AssetIdForTrustBackedAssetsConvert, }; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector}; @@ -61,8 +61,9 @@ use frame_support::{ genesis_builder_helper::{build_state, get_preset}, ord_parameter_types, parameter_types, traits::{ - fungible, fungibles, tokens::imbalance::ResolveAssetTo, AsEnsureOriginWithArg, ConstBool, - ConstU128, ConstU32, ConstU64, ConstU8, EitherOfDiverse, InstanceFilter, TransformOrigin, + fungible, fungible::HoldConsideration, fungibles, tokens::imbalance::ResolveAssetTo, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, + ConstantStoragePrice, EitherOfDiverse, Equals, InstanceFilter, TransformOrigin, }, weights::{ConstantMultiplier, Weight, WeightToFee as _}, BoundedVec, PalletId, @@ -83,8 +84,8 @@ use sp_runtime::{Perbill, RuntimeDebug}; use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*}; use xcm_config::{ ForeignAssetsConvertedConcreteId, GovernanceLocation, LocationToAccountId, - PoolAssetsConvertedConcreteId, TokenLocation, TrustBackedAssetsConvertedConcreteId, - TrustBackedAssetsPalletLocation, + PoolAssetsConvertedConcreteId, PoolAssetsPalletLocation, TokenLocation, + TrustBackedAssetsConvertedConcreteId, TrustBackedAssetsPalletLocation, }; #[cfg(test)] @@ -110,6 +111,9 @@ use xcm_runtime_apis::{ fees::Error as XcmPaymentApiError, }; +#[cfg(feature = "runtime-benchmarks")] +use frame_support::traits::PalletInfoAccess; + use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; impl_opaque_keys! { @@ -123,7 +127,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("statemine"), impl_name: alloc::borrow::Cow::Borrowed("statemine"), authoring_version: 1, - spec_version: 1_016_002, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, @@ -181,6 +185,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo<Runtime>; +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -212,8 +220,8 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; - type FreezeIdentifier = (); - type MaxFreezes = ConstU32<0>; + type FreezeIdentifier = RuntimeFreezeReason; + type MaxFreezes = ConstU32<50>; type DoneSlashHandler = (); } @@ -297,7 +305,7 @@ impl pallet_assets::Config<PoolAssetsInstance> for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; type RemoveItemsLimit = ConstU32<1000>; - type AssetId = u32; + type AssetId = AssetIdForPoolAssets; type AssetIdParameter = u32; type Currency = Balances; type CreateOrigin = @@ -338,8 +346,21 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< AccountId, >; -/// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`. -pub type NativeAndAssets = fungible::UnionOf< +/// Union fungibles implementation for `AssetsFreezer` and `ForeignAssetsFreezer`. +pub type LocalAndForeignAssetsFreezer = fungibles::UnionOf< + AssetsFreezer, + ForeignAssetsFreezer, + LocalFromLeft< + AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocation, xcm::v5::Location>, + AssetIdForTrustBackedAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`LocalAndForeignAssets`] and [`Balances`]. +pub type NativeAndNonPoolAssets = fungible::UnionOf< Balances, LocalAndForeignAssets, TargetFromLeft<TokenLocation, xcm::v5::Location>, @@ -347,6 +368,45 @@ pub type NativeAndAssets = fungible::UnionOf< AccountId, >; +/// Union fungibles implementation for [`LocalAndForeignAssetsFreezer`] and [`Balances`]. +pub type NativeAndNonPoolAssetsFreezer = fungible::UnionOf< + Balances, + LocalAndForeignAssetsFreezer, + TargetFromLeft<TokenLocation, xcm::v5::Location>, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`PoolAssets`] and [`NativeAndNonPoolAssets`]. +/// +/// NOTE: Should be kept updated to include ALL balances and assets in the runtime. +pub type NativeAndAllAssets = fungibles::UnionOf< + PoolAssets, + NativeAndNonPoolAssets, + LocalFromLeft< + AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation, xcm::v5::Location>, + AssetIdForPoolAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`PoolAssetsFreezer`] and [`NativeAndNonPoolAssetsFreezer`]. +/// +/// NOTE: Should be kept updated to include ALL balances and assets in the runtime. +pub type NativeAndAllAssetsFreezer = fungibles::UnionOf< + PoolAssetsFreezer, + NativeAndNonPoolAssetsFreezer, + LocalFromLeft< + AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation, xcm::v5::Location>, + AssetIdForPoolAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter< AssetConversionPalletId, (xcm::v5::Location, xcm::v5::Location), @@ -357,7 +417,7 @@ impl pallet_asset_conversion::Config for Runtime { type Balance = Balance; type HigherPrecisionBalance = sp_core::U256; type AssetKind = xcm::v5::Location; - type Assets = NativeAndAssets; + type Assets = NativeAndNonPoolAssets; type PoolId = (Self::AssetKind, Self::AssetKind); type PoolLocator = pallet_asset_conversion::WithFirstAsset< TokenLocation, @@ -818,9 +878,9 @@ impl pallet_asset_conversion_tx_payment::Config for Runtime { type AssetId = xcm::v5::Location; type OnChargeAssetTransaction = SwapAssetAdapter< TokenLocation, - NativeAndAssets, + NativeAndNonPoolAssets, AssetConversion, - ResolveAssetTo<StakingPot, NativeAndAssets>, + ResolveAssetTo<StakingPot, NativeAndNonPoolAssets>, >; type WeightInfo = weights::pallet_asset_conversion_tx_payment::WeightInfo<Runtime>; #[cfg(feature = "runtime-benchmarks")] @@ -936,6 +996,10 @@ impl pallet_xcm_bridge_hub_router::Config<ToWestendXcmRouterInstance> for Runtim type Bridges = xcm_config::bridging::NetworkExportTable; type DestinationVersion = PolkadotXcm; + type BridgeHubOrigin = frame_support::traits::EitherOfDiverse< + EnsureRoot<AccountId>, + EnsureXcm<Equals<Self::SiblingBridgeHubLocation>>, + >; type ToBridgeHubSender = XcmpQueue; type LocalXcmChannelManager = cumulus_pallet_xcmp_queue::bridging::InAndOutXcmpChannelStatusProvider<Runtime>; @@ -944,6 +1008,55 @@ impl pallet_xcm_bridge_hub_router::Config<ToWestendXcmRouterInstance> for Runtim type FeeAsset = xcm_config::bridging::XcmBridgeHubRouterFeeAssetId; } +#[cfg(feature = "runtime-benchmarks")] +pub struct PalletAssetRewardsBenchmarkHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_asset_rewards::benchmarking::BenchmarkHelper<xcm::v5::Location> + for PalletAssetRewardsBenchmarkHelper +{ + fn staked_asset() -> Location { + Location::new( + 0, + [PalletInstance(<Assets as PalletInfoAccess>::index() as u8), GeneralIndex(100)], + ) + } + fn reward_asset() -> Location { + Location::new( + 0, + [PalletInstance(<Assets as PalletInfoAccess>::index() as u8), GeneralIndex(101)], + ) + } +} + +parameter_types! { + pub const AssetRewardsPalletId: PalletId = PalletId(*b"py/astrd"); + pub const RewardsPoolCreationHoldReason: RuntimeHoldReason = + RuntimeHoldReason::AssetRewards(pallet_asset_rewards::HoldReason::PoolCreation); + // 1 item, 135 bytes into the storage on pool creation. + pub const StakePoolCreationDeposit: Balance = deposit(1, 135); +} + +impl pallet_asset_rewards::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PalletId = AssetRewardsPalletId; + type Balance = Balance; + type Assets = NativeAndAllAssets; + type AssetsFreezer = NativeAndAllAssetsFreezer; + type AssetId = xcm::v5::Location; + type CreatePoolOrigin = EnsureSigned<AccountId>; + type RuntimeFreezeReason = RuntimeFreezeReason; + type Consideration = HoldConsideration< + AccountId, + Balances, + RewardsPoolCreationHoldReason, + ConstantStoragePrice<StakePoolCreationDeposit, Balance>, + >; + type WeightInfo = weights::pallet_asset_rewards::WeightInfo<Runtime>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = PalletAssetRewardsBenchmarkHelper; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime @@ -953,6 +1066,7 @@ construct_runtime!( ParachainSystem: cumulus_pallet_parachain_system = 1, Timestamp: pallet_timestamp = 3, ParachainInfo: parachain_info = 4, + WeightReclaim: cumulus_pallet_weight_reclaim = 5, // Monetary stuff. Balances: pallet_balances = 10, @@ -988,10 +1102,13 @@ construct_runtime!( NftFractionalization: pallet_nft_fractionalization = 54, PoolAssets: pallet_assets::<Instance3> = 55, AssetConversion: pallet_asset_conversion = 56, + AssetsFreezer: pallet_assets_freezer::<Instance1> = 57, ForeignAssetsFreezer: pallet_assets_freezer::<Instance2> = 58, PoolAssetsFreezer: pallet_assets_freezer::<Instance3> = 59, + AssetRewards: pallet_asset_rewards = 60, + // TODO: the pallet instance should be removed once all pools have migrated // to the new account IDs. AssetConversionMigration: pallet_asset_conversion_ops = 200, @@ -1007,18 +1124,20 @@ pub type SignedBlock = generic::SignedBlock<Block>; /// BlockId type as expected by this runtime. pub type BlockId = generic::BlockId<Block>; /// The extension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender<Runtime>, - frame_system::CheckSpecVersion<Runtime>, - frame_system::CheckTxVersion<Runtime>, - frame_system::CheckGenesis<Runtime>, - frame_system::CheckEra<Runtime>, - frame_system::CheckNonce<Runtime>, - frame_system::CheckWeight<Runtime>, - pallet_asset_conversion_tx_payment::ChargeAssetTxPayment<Runtime>, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim<Runtime>, - frame_metadata_hash_extension::CheckMetadataHash<Runtime>, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckTxVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckEra<Runtime>, + frame_system::CheckNonce<Runtime>, + frame_system::CheckWeight<Runtime>, + pallet_asset_conversion_tx_payment::ChargeAssetTxPayment<Runtime>, + frame_metadata_hash_extension::CheckMetadataHash<Runtime>, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, TxExtension>; @@ -1038,6 +1157,7 @@ pub type Migrations = ( >, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion<Runtime>, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1<Runtime>, ); parameter_types! { @@ -1180,6 +1300,7 @@ mod benches { [pallet_assets, Foreign] [pallet_assets, Pool] [pallet_asset_conversion, AssetConversion] + [pallet_asset_rewards, AssetRewards] [pallet_asset_conversion_tx_payment, AssetTxPayment] [pallet_balances, Balances] [pallet_message_queue, MessageQueue] @@ -1202,6 +1323,7 @@ mod benches { // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); } @@ -1415,37 +1537,31 @@ impl_runtime_apis! { // We accept the native token to pay fees. let mut acceptable_assets = vec![AssetId(native_token.clone())]; // We also accept all assets in a pool with the native token. - let assets_in_pool_with_native = assets_common::get_assets_in_pool_with::< - Runtime, - xcm::v5::Location - >(&native_token).map_err(|()| XcmPaymentApiError::VersionedConversionFailed)?.into_iter(); - acceptable_assets.extend(assets_in_pool_with_native); + acceptable_assets.extend( + assets_common::PoolAdapter::<Runtime>::get_assets_in_pool_with(native_token) + .map_err(|()| XcmPaymentApiError::VersionedConversionFailed)? + ); PolkadotXcm::query_acceptable_payment_assets(xcm_version, acceptable_assets) } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> { let native_asset = xcm_config::TokenLocation::get(); let fee_in_native = WeightToFee::weight_to_fee(&weight); - match asset.try_as::<AssetId>() { + let latest_asset_id: Result<AssetId, ()> = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == native_asset => { // for native token Ok(fee_in_native) }, Ok(asset_id) => { - let assets_in_pool_with_this_asset: Vec<_> = assets_common::get_assets_in_pool_with::< - Runtime, - xcm::v5::Location - >(&asset_id.0).map_err(|()| XcmPaymentApiError::VersionedConversionFailed)?; - if assets_in_pool_with_this_asset - .into_iter() - .map(|asset_id| asset_id.0) - .any(|location| location == native_asset) { - pallet_asset_conversion::Pallet::<Runtime>::quote_price_tokens_for_exact_tokens( - asset_id.clone().0, + // Try to get current price of `asset_id` in `native_asset`. + if let Ok(Some(swapped_in_native)) = assets_common::PoolAdapter::<Runtime>::quote_price_tokens_for_exact_tokens( + asset_id.0.clone(), native_asset, fee_in_native, true, // We include the fee. - ).ok_or(XcmPaymentApiError::AssetNotFound) + ) { + Ok(swapped_in_native) } else { log::trace!(target: "xcm::xcm_runtime_apis", "query_weight_to_asset_fee - unhandled asset_id: {asset_id:?}!"); Err(XcmPaymentApiError::AssetNotFound) @@ -1495,6 +1611,12 @@ impl_runtime_apis! { } } + impl pallet_asset_rewards::AssetRewards<Block, Balance> for Runtime { + fn pool_creation_cost() -> Balance { + StakePoolCreationDeposit::get() + } + } + impl cumulus_primitives_core::GetCoreSelectorApi<Block> for Runtime { fn core_selector() -> (CoreSelector, ClaimQueueOffset) { ParachainSystem::core_selector() @@ -1855,20 +1977,8 @@ impl_runtime_apis! { type ToWestend = XcmBridgeHubRouterBench<Runtime, ToWestendXcmRouterInstance>; - let whitelist: Vec<TrackedStorageKey> = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - //TODO: use from relay_well_known_keys::ACTIVE_CONFIG - hex_literal::hex!("06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec<TrackedStorageKey> = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::<BenchmarkBatch>::new(); let params = (&config, &whitelist); diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 0000000000000000000000000000000000000000..c8f9bb7cd56c3c2d56224aa8043e3817901c18c4 --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=asset-hub-rococo-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo<T>(PhantomData<T>); +impl<T: frame_system::Config> cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo<T> { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 7_301_000 picoseconds. + Weight::from_parts(7_536_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/frame_system_extensions.rs index 182410f20fffe19f74f887fa7858c7072d855172..a5c9fea3cdf53f3daacd3fb1a223056729d01fcf 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/frame_system_extensions.rs @@ -16,28 +16,29 @@ //! Autogenerated weights for `frame_system_extensions` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-21, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-10-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `gleipnir`, CPU: `AMD Ryzen 9 7900X 12-Core Processor` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `697235d969a1`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 // Executed Command: -// ./target/release/polkadot-parachain +// frame-omni-bencher +// v1 // benchmark // pallet -// --wasm-execution=compiled +// --extrinsic=* +// --runtime=target/release/wbuild/asset-hub-rococo-runtime/asset_hub_rococo_runtime.wasm // --pallet=frame_system_extensions +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 // --no-storage-info -// --no-median-slopes // --no-min-squares -// --extrinsic=* -// --steps=2 -// --repeat=2 -// --json -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/ -// --chain=asset-hub-rococo-dev +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,8 +57,8 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `54` // Estimated: `3509` - // Minimum execution time: 3_637_000 picoseconds. - Weight::from_parts(6_382_000, 0) + // Minimum execution time: 8_313_000 picoseconds. + Weight::from_parts(8_528_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -67,8 +68,8 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `92` // Estimated: `3509` - // Minimum execution time: 5_841_000 picoseconds. - Weight::from_parts(8_776_000, 0) + // Minimum execution time: 12_527_000 picoseconds. + Weight::from_parts(13_006_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -78,8 +79,8 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `92` // Estimated: `3509` - // Minimum execution time: 5_841_000 picoseconds. - Weight::from_parts(8_776_000, 0) + // Minimum execution time: 12_380_000 picoseconds. + Weight::from_parts(12_922_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -87,44 +88,64 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 561_000 picoseconds. - Weight::from_parts(2_705_000, 0) + // Minimum execution time: 782_000 picoseconds. + Weight::from_parts(855_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn check_nonce() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_316_000 picoseconds. - Weight::from_parts(5_771_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `101` + // Estimated: `3593` + // Minimum execution time: 11_743_000 picoseconds. + Weight::from_parts(12_067_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } fn check_spec_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 511_000 picoseconds. - Weight::from_parts(2_575_000, 0) + // Minimum execution time: 644_000 picoseconds. + Weight::from_parts(697_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn check_tx_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 501_000 picoseconds. - Weight::from_parts(2_595_000, 0) + // Minimum execution time: 605_000 picoseconds. + Weight::from_parts(700_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `System::BlockWeight` (r:1 w:1) /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn check_weight() -> Weight { // Proof Size summary in bytes: // Measured: `24` // Estimated: `1533` - // Minimum execution time: 3_687_000 picoseconds. - Weight::from_parts(6_192_000, 0) + // Minimum execution time: 9_796_000 picoseconds. + Weight::from_parts(10_365_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1533` + // Minimum execution time: 4_855_000 picoseconds. + Weight::from_parts(5_050_000, 0) .saturating_add(Weight::from_parts(0, 1533)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs index 33f111009ed0fb3e4be1e5277ed3d3e960a0dd09..6893766ac72d2113f993e54d3b28e5e136acac3d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs @@ -16,6 +16,7 @@ pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; @@ -23,6 +24,7 @@ pub mod frame_system_extensions; pub mod pallet_asset_conversion; pub mod pallet_asset_conversion_ops; pub mod pallet_asset_conversion_tx_payment; +pub mod pallet_asset_rewards; pub mod pallet_assets_foreign; pub mod pallet_assets_local; pub mod pallet_assets_pool; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs new file mode 100644 index 0000000000000000000000000000000000000000..218c93c51035037e5d325dc5b3d31897e164f0ec --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs @@ -0,0 +1,217 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `pallet_asset_rewards` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_asset_rewards +// --chain=asset-hub-rococo-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_asset_rewards`. +pub struct WeightInfo<T>(PhantomData<T>); +impl<T: frame_system::Config> pallet_asset_rewards::WeightInfo for WeightInfo<T> { + /// Storage: `Assets::Asset` (r:2 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::NextPoolId` (r:1 w:1) + /// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:0 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::Pools` (r:0 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `358` + // Estimated: `6360` + // Minimum execution time: 65_882_000 picoseconds. + Weight::from_parts(67_073_000, 0) + .saturating_add(Weight::from_parts(0, 6360)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `872` + // Estimated: `4809` + // Minimum execution time: 56_950_000 picoseconds. + Weight::from_parts(58_088_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn unstake() -> Weight { + // Proof Size summary in bytes: + // Measured: `872` + // Estimated: `4809` + // Minimum execution time: 59_509_000 picoseconds. + Weight::from_parts(61_064_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn harvest_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1072` + // Estimated: `6208` + // Minimum execution time: 80_685_000 picoseconds. + Weight::from_parts(83_505_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_reward_rate_per_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 17_032_000 picoseconds. + Weight::from_parts(17_628_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_admin() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 15_290_000 picoseconds. + Weight::from_parts(16_212_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_expiry_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 17_721_000 picoseconds. + Weight::from_parts(18_603_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn deposit_reward_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `747` + // Estimated: `6208` + // Minimum execution time: 67_754_000 picoseconds. + Weight::from_parts(69_428_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:0) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:1 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:0 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + fn cleanup_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `1105` + // Estimated: `6208` + // Minimum execution time: 127_524_000 picoseconds. + Weight::from_parts(130_238_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(10)) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_multisig.rs index cf9c523f6571f48b37a300295d162960d82528bd..1192478c90ac483d6b82c4a2704d4b1ba5903fbc 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_multisig.rs @@ -16,28 +16,28 @@ //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --extrinsic=* // --chain=asset-hub-rococo-dev -// --wasm-execution=compiled // --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-rococo/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_714_000 picoseconds. - Weight::from_parts(14_440_231, 0) + // Minimum execution time: 16_059_000 picoseconds. + Weight::from_parts(17_033_878, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 5 - .saturating_add(Weight::from_parts(598, 0).saturating_mul(z.into())) + // Standard Error: 8 + .saturating_add(Weight::from_parts(489, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -67,15 +67,15 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `262 + s * (2 ±0)` + // Measured: `295 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 44_768_000 picoseconds. - Weight::from_parts(33_662_218, 0) + // Minimum execution time: 46_128_000 picoseconds. + Weight::from_parts(33_704_180, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_633 - .saturating_add(Weight::from_parts(128_927, 0).saturating_mul(s.into())) - // Standard Error: 16 - .saturating_add(Weight::from_parts(1_543, 0).saturating_mul(z.into())) + // Standard Error: 1_456 + .saturating_add(Weight::from_parts(147_148, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_037, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -85,15 +85,15 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 29_745_000 picoseconds. - Weight::from_parts(20_559_891, 0) + // Minimum execution time: 32_218_000 picoseconds. + Weight::from_parts(21_320_145, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 914 - .saturating_add(Weight::from_parts(103_601, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_504, 0).saturating_mul(z.into())) + // Standard Error: 1_922 + .saturating_add(Weight::from_parts(131_349, 0).saturating_mul(s.into())) + // Standard Error: 18 + .saturating_add(Weight::from_parts(1_829, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -105,60 +105,63 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `385 + s * (33 ±0)` + // Measured: `418 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 51_506_000 picoseconds. - Weight::from_parts(36_510_777, 0) + // Minimum execution time: 53_641_000 picoseconds. + Weight::from_parts(32_057_363, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 2_183 - .saturating_add(Weight::from_parts(183_764, 0).saturating_mul(s.into())) - // Standard Error: 21 - .saturating_add(Weight::from_parts(1_653, 0).saturating_mul(z.into())) + // Standard Error: 2_897 + .saturating_add(Weight::from_parts(254_035, 0).saturating_mul(s.into())) + // Standard Error: 28 + .saturating_add(Weight::from_parts(2_432, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `295 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 31_072_000 picoseconds. - Weight::from_parts(32_408_621, 0) + // Minimum execution time: 30_302_000 picoseconds. + Weight::from_parts(33_367_363, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 913 - .saturating_add(Weight::from_parts(121_410, 0).saturating_mul(s.into())) + // Standard Error: 1_389 + .saturating_add(Weight::from_parts(150_845, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 18_301_000 picoseconds. - Weight::from_parts(18_223_547, 0) + // Minimum execution time: 17_008_000 picoseconds. + Weight::from_parts(18_452_875, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 747 - .saturating_add(Weight::from_parts(114_584, 0).saturating_mul(s.into())) + // Standard Error: 949 + .saturating_add(Weight::from_parts(130_051, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `482 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 32_107_000 picoseconds. - Weight::from_parts(33_674_827, 0) + // Minimum execution time: 30_645_000 picoseconds. + Weight::from_parts(33_864_517, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_220 - .saturating_add(Weight::from_parts(122_011, 0).saturating_mul(s.into())) + // Standard Error: 1_511 + .saturating_add(Weight::from_parts(138_628, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs index 51b6543bae82ba496998706ab6c2aaf6e0ff604b..8506125d41331c190323adce4fa51d6581e725fe 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `55b2c3410882`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=asset-hub-rococo-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=asset-hub-rococo-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -64,14 +66,16 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 22_136_000 picoseconds. - Weight::from_parts(22_518_000, 0) + // Minimum execution time: 28_401_000 picoseconds. + Weight::from_parts(29_326_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) @@ -90,18 +94,20 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 92_277_000 picoseconds. - Weight::from_parts(94_843_000, 0) + // Minimum execution time: 109_686_000 picoseconds. + Weight::from_parts(114_057_000, 0) .saturating_add(Weight::from_parts(0, 3610)) - .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().reads(9)) .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::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`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: `XcmpQueue::DeliveryFeeFactor` (r:1 w:0) - /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: Some(28), added: 2503, 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) @@ -111,25 +117,29 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Storage: `ParachainSystem::RelevantMessagingState` (r:1 w:0) /// Proof: `ParachainSystem::RelevantMessagingState` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) - /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: Some(1282), added: 1777, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) - /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: Some(105506), added: 107981, mode: `MaxEncodedLen`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: // Measured: `400` // Estimated: `6196` - // Minimum execution time: 120_110_000 picoseconds. - Weight::from_parts(122_968_000, 0) + // Minimum execution time: 137_693_000 picoseconds. + Weight::from_parts(142_244_000, 0) .saturating_add(Weight::from_parts(0, 6196)) - .saturating_add(T::DbWeight::get().reads(9)) + .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`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Assets::Asset` (r:1 w:1) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) /// Storage: `Assets::Account` (r:2 w:2) /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) @@ -146,23 +156,24 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `496` + // Measured: `537` // Estimated: `6208` - // Minimum execution time: 143_116_000 picoseconds. - Weight::from_parts(147_355_000, 0) + // Minimum execution time: 178_291_000 picoseconds. + Weight::from_parts(185_648_000, 0) .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().reads(14)) .saturating_add(T::DbWeight::get().writes(7)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `103` + // Estimated: `1588` + // Minimum execution time: 14_014_000 picoseconds. + Weight::from_parts(14_522_000, 0) + .saturating_add(Weight::from_parts(0, 1588)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -170,8 +181,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_517_000 picoseconds. - Weight::from_parts(6_756_000, 0) + // Minimum execution time: 7_195_000 picoseconds. + Weight::from_parts(7_440_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -181,8 +192,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_894_000 picoseconds. - Weight::from_parts(2_024_000, 0) + // Minimum execution time: 2_278_000 picoseconds. + Weight::from_parts(2_488_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -208,8 +219,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 27_314_000 picoseconds. - Weight::from_parts(28_787_000, 0) + // Minimum execution time: 35_095_000 picoseconds. + Weight::from_parts(36_347_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -234,8 +245,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `363` // Estimated: `3828` - // Minimum execution time: 29_840_000 picoseconds. - Weight::from_parts(30_589_000, 0) + // Minimum execution time: 38_106_000 picoseconds. + Weight::from_parts(38_959_000, 0) .saturating_add(Weight::from_parts(0, 3828)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -246,45 +257,45 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_893_000 picoseconds. - Weight::from_parts(2_017_000, 0) + // Minimum execution time: 2_307_000 picoseconds. + Weight::from_parts(2_478_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `159` - // Estimated: `13524` - // Minimum execution time: 19_211_000 picoseconds. - Weight::from_parts(19_552_000, 0) - .saturating_add(Weight::from_parts(0, 13524)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15999` + // Minimum execution time: 25_238_000 picoseconds. + Weight::from_parts(25_910_000, 0) + .saturating_add(Weight::from_parts(0, 15999)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `163` - // Estimated: `13528` - // Minimum execution time: 19_177_000 picoseconds. - Weight::from_parts(19_704_000, 0) - .saturating_add(Weight::from_parts(0, 13528)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `16003` + // Minimum execution time: 25_626_000 picoseconds. + Weight::from_parts(26_147_000, 0) + .saturating_add(Weight::from_parts(0, 16003)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `173` - // Estimated: `16013` - // Minimum execution time: 20_449_000 picoseconds. - Weight::from_parts(21_075_000, 0) - .saturating_add(Weight::from_parts(0, 16013)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18488` + // Minimum execution time: 28_528_000 picoseconds. + Weight::from_parts(28_882_000, 0) + .saturating_add(Weight::from_parts(0, 18488)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -304,36 +315,36 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `212` // Estimated: `6152` - // Minimum execution time: 26_578_000 picoseconds. - Weight::from_parts(27_545_000, 0) + // Minimum execution time: 33_042_000 picoseconds. + Weight::from_parts(34_444_000, 0) .saturating_add(Weight::from_parts(0, 6152)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `206` - // Estimated: `11096` - // Minimum execution time: 11_646_000 picoseconds. - Weight::from_parts(11_944_000, 0) - .saturating_add(Weight::from_parts(0, 11096)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `176` + // Estimated: `13541` + // Minimum execution time: 18_218_000 picoseconds. + Weight::from_parts(18_622_000, 0) + .saturating_add(Weight::from_parts(0, 13541)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `170` - // Estimated: `13535` - // Minimum execution time: 19_301_000 picoseconds. - Weight::from_parts(19_664_000, 0) - .saturating_add(Weight::from_parts(0, 13535)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `16010` + // Minimum execution time: 25_838_000 picoseconds. + Weight::from_parts(26_276_000, 0) + .saturating_add(Weight::from_parts(0, 16010)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -350,11 +361,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: // Measured: `212` - // Estimated: `13577` - // Minimum execution time: 35_715_000 picoseconds. - Weight::from_parts(36_915_000, 0) - .saturating_add(Weight::from_parts(0, 13577)) - .saturating_add(T::DbWeight::get().reads(11)) + // Estimated: `16052` + // Minimum execution time: 46_196_000 picoseconds. + Weight::from_parts(47_859_000, 0) + .saturating_add(Weight::from_parts(0, 16052)) + .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -365,8 +376,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `103` // Estimated: `1588` - // Minimum execution time: 4_871_000 picoseconds. - Weight::from_parts(5_066_000, 0) + // Minimum execution time: 7_068_000 picoseconds. + Weight::from_parts(7_442_000, 0) .saturating_add(Weight::from_parts(0, 1588)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -377,22 +388,24 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `7740` // Estimated: `11205` - // Minimum execution time: 25_150_000 picoseconds. - Weight::from_parts(26_119_000, 0) + // Minimum execution time: 31_497_000 picoseconds. + Weight::from_parts(31_975_000, 0) .saturating_add(Weight::from_parts(0, 11205)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `160` // Estimated: `3625` - // Minimum execution time: 38_248_000 picoseconds. - Weight::from_parts(39_122_000, 0) + // Minimum execution time: 44_534_000 picoseconds. + Weight::from_parts(46_175_000, 0) .saturating_add(Weight::from_parts(0, 3625)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs index 00ecf239428f03c9b967b975bdc5b0835ccbf259..9a75428ada8b1c5b6295982c92dfa42058117cd9 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm_bridge_hub_router` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-08-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-07, 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` +//! HOSTNAME: `runner-acd6uxux-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: @@ -52,14 +52,14 @@ impl<T: frame_system::Config> pallet_xcm_bridge_hub_router::WeightInfo for Weigh /// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: Some(4002), added: 4497, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:0) /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: Some(1282), added: 1777, mode: `MaxEncodedLen`) - /// Storage: `ToWestendXcmRouter::DeliveryFeeFactor` (r:1 w:1) - /// Proof: `ToWestendXcmRouter::DeliveryFeeFactor` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `ToWestendXcmRouter::Bridge` (r:1 w:1) + /// Proof: `ToWestendXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) fn on_initialize_when_non_congested() -> Weight { // Proof Size summary in bytes: - // Measured: `153` + // Measured: `154` // Estimated: `5487` - // Minimum execution time: 12_993_000 picoseconds. - Weight::from_parts(13_428_000, 0) + // Minimum execution time: 13_884_000 picoseconds. + Weight::from_parts(14_312_000, 0) .saturating_add(Weight::from_parts(0, 5487)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -72,9 +72,21 @@ impl<T: frame_system::Config> pallet_xcm_bridge_hub_router::WeightInfo for Weigh // Proof Size summary in bytes: // Measured: `144` // Estimated: `5487` - // Minimum execution time: 6_305_000 picoseconds. - Weight::from_parts(6_536_000, 0) + // Minimum execution time: 6_909_000 picoseconds. + Weight::from_parts(7_115_000, 0) .saturating_add(Weight::from_parts(0, 5487)) .saturating_add(T::DbWeight::get().reads(2)) } + /// Storage: `ToWestendXcmRouter::Bridge` (r:1 w:1) + /// Proof: `ToWestendXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) + fn report_bridge_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `150` + // Estimated: `1502` + // Minimum execution time: 12_394_000 picoseconds. + Weight::from_parts(12_883_000, 0) + .saturating_add(Weight::from_parts(0, 1502)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/mod.rs index 025c39bcee0785a810f13774f43dc7cbca2bb0b9..ccf473484cad0c0398ac07cb85af3274b87ab43d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/mod.rs @@ -22,6 +22,7 @@ use alloc::vec::Vec; use frame_support::weights::Weight; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::{ latest::{prelude::*, AssetTransferFilter}, DoubleEncoded, @@ -84,7 +85,11 @@ impl<Call> XcmWeightInfo<Call> for AssetHubRococoXcmWeight<Call> { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::<Runtime>::transfer_reserve_asset()) } - fn transact(_origin_type: &OriginKind, _call: &DoubleEncoded<Call>) -> Weight { + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option<Weight>, + _call: &DoubleEncoded<Call>, + ) -> Weight { XcmGeneric::<Runtime>::transact() } fn hrmp_new_channel_open_request( @@ -172,8 +177,16 @@ impl<Call> XcmWeightInfo<Call> for AssetHubRococoXcmWeight<Call> { fn clear_error() -> Weight { XcmGeneric::<Runtime>::clear_error() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::<Runtime>::set_asset_claimer() + fn set_hints(hints: &BoundedVec<Hint, HintNumVariants>) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::<Runtime>::asset_claimer()); + }, + } + } + weight } fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::<Runtime>::claim_asset() diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index b69c136b29d98779f4150915c5e8b03b8d0d33e0..d48debef94c88eaf10dd76c92f501e1e856f3868 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -87,7 +87,7 @@ impl<T: frame_system::Config> WeightInfo<T> { // Minimum execution time: 5_803_000 picoseconds. Weight::from_parts(5_983_000, 0) } - pub fn set_asset_claimer() -> Weight { + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs index 08b2f520c4b9ac1abb39f9d185c821dec7241d0f..0c6ff5e4bfddc6c236fe1ec4a5a5f425027f41f5 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 @@ -76,6 +76,10 @@ parameter_types! { pub TrustBackedAssetsPalletLocation: Location = PalletInstance(TrustBackedAssetsPalletIndex::get()).into(); pub TrustBackedAssetsPalletIndex: u8 = <Assets as PalletInfoAccess>::index() as u8; + pub TrustBackedAssetsPalletLocationV3: xcm::v3::Location = + xcm::v3::Junction::PalletInstance(<Assets as PalletInfoAccess>::index() as u8).into(); + pub PoolAssetsPalletLocationV3: xcm::v3::Location = + xcm::v3::Junction::PalletInstance(<PoolAssets as PalletInfoAccess>::index() as u8).into(); pub ForeignAssetsPalletLocation: Location = PalletInstance(<ForeignAssets as PalletInfoAccess>::index() as u8).into(); pub PoolAssetsPalletLocation: Location = @@ -336,7 +340,7 @@ pub type TrustedTeleporters = ( /// asset and the asset required for fee payment. pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< crate::AssetConversion, - crate::NativeAndAssets, + crate::NativeAndNonPoolAssets, ( TrustBackedAssetsAsLocation<TrustBackedAssetsPalletLocation, Balance, xcm::v5::Location>, ForeignAssetsConvertedConcreteId, @@ -387,7 +391,7 @@ impl xcm_executor::Config for XcmConfig { TokenLocation, crate::AssetConversion, WeightToFee, - crate::NativeAndAssets, + crate::NativeAndNonPoolAssets, ( TrustBackedAssetsAsLocation< TrustBackedAssetsPalletLocation, @@ -396,7 +400,7 @@ impl xcm_executor::Config for XcmConfig { >, ForeignAssetsConvertedConcreteId, ), - ResolveAssetTo<StakingPot, crate::NativeAndAssets>, + ResolveAssetTo<StakingPot, crate::NativeAndNonPoolAssets>, AccountId, >, // This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated 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 5da8b45417a3baafdccf3eab73ddb3f5224a2fb2..144934ecd4abd637481ed19cacc3fce07a89ea77 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs @@ -24,10 +24,11 @@ use asset_hub_rococo_runtime::{ ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, LocationToAccountId, StakingPot, TokenLocation, TrustBackedAssetsPalletLocation, XcmConfig, }, - AllPalletsWithoutSystem, AssetConversion, AssetDeposit, Assets, Balances, CollatorSelection, - ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, - MetadataDepositPerByte, ParachainSystem, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, - SessionKeys, TrustBackedAssetsInstance, XcmpQueue, + AllPalletsWithoutSystem, AssetConversion, AssetDeposit, Assets, Balances, Block, + CollatorSelection, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, + MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, Runtime, RuntimeCall, + RuntimeEvent, RuntimeOrigin, SessionKeys, ToWestendXcmRouterInstance, + TrustBackedAssetsInstance, XcmpQueue, }; use asset_test_utils::{ test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, @@ -1242,6 +1243,58 @@ mod asset_hub_rococo_tests { ) } + #[test] + fn report_bridge_status_from_xcm_bridge_router_for_westend_works() { + asset_test_utils::test_cases_over_bridge::report_bridge_status_from_xcm_bridge_router_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + LocationToAccountId, + ToWestendXcmRouterInstance, + >( + collator_session_keys(), + bridging_to_asset_hub_westend, + || bp_asset_hub_rococo::build_congestion_message(Default::default(), true).into(), + || bp_asset_hub_rococo::build_congestion_message(Default::default(), false).into(), + ) + } + + #[test] + fn test_report_bridge_status_call_compatibility() { + // if this test fails, make sure `bp_asset_hub_rococo` has valid encoding + assert_eq!( + RuntimeCall::ToWestendXcmRouter( + pallet_xcm_bridge_hub_router::Call::report_bridge_status { + bridge_id: Default::default(), + is_congested: true, + } + ) + .encode(), + bp_asset_hub_rococo::Call::ToWestendXcmRouter( + bp_asset_hub_rococo::XcmBridgeHubRouterCall::report_bridge_status { + bridge_id: Default::default(), + is_congested: true, + } + ) + .encode() + ); + } + + #[test] + fn check_sane_weight_report_bridge_status_for_westend() { + use pallet_xcm_bridge_hub_router::WeightInfo; + let actual = <Runtime as pallet_xcm_bridge_hub_router::Config< + ToWestendXcmRouterInstance, + >>::WeightInfo::report_bridge_status(); + let max_weight = bp_asset_hub_rococo::XcmBridgeHubRouterTransactCallMaxWeight::get(); + assert!( + actual.all_lte(max_weight), + "max_weight: {:?} should be adjusted to actual {:?}", + max_weight, + actual + ); + } + #[test] fn reserve_transfer_native_asset_to_non_teleport_para_works() { asset_test_utils::test_cases::reserve_transfer_native_asset_to_non_teleport_para_works::< @@ -1471,3 +1524,19 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); + asset_test_utils::test_cases::xcm_payment_api_with_pools_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index d5eaa43ab83449254962b295d2b6a4a413257582..f7fb858de62e87c4048ea0cd8f987a9aba550d2e 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Westend variant of Asset Hub parachain runtime" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -25,34 +27,36 @@ frame-system = { workspace = true } frame-system-benchmarking = { optional = true, workspace = true } frame-system-rpc-runtime-api = { workspace = true } frame-try-runtime = { optional = true, workspace = true } +pallet-asset-conversion = { workspace = true } pallet-asset-conversion-ops = { workspace = true } pallet-asset-conversion-tx-payment = { workspace = true } +pallet-asset-rewards = { workspace = true } pallet-assets = { workspace = true } -pallet-asset-conversion = { workspace = true } pallet-assets-freezer = { workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } +pallet-migrations = { workspace = true } pallet-multisig = { workspace = true } pallet-nft-fractionalization = { workspace = true } pallet-nfts = { workspace = true } pallet-nfts-runtime-api = { workspace = true } pallet-proxy = { workspace = true } +pallet-revive = { workspace = true } pallet-session = { workspace = true } pallet-state-trie-migration = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-uniques = { workspace = true } -pallet-revive = { workspace = true } pallet-utility = { workspace = true } sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } -sp-keyring = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } +sp-keyring = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } @@ -60,6 +64,7 @@ sp-std = { workspace = true } sp-storage = { workspace = true } sp-transaction-pool = { workspace = true } sp-version = { workspace = true } + # num-traits feature needed for dex integer sq root: primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } @@ -75,32 +80,33 @@ xcm-executor = { workspace = true } xcm-runtime-apis = { workspace = true } # Cumulus +assets-common = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } -pallet-message-queue = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { features = ["bridging"], workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-utility = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } pallet-collator-selection = { workspace = true } +pallet-message-queue = { workspace = true } parachain-info = { workspace = true } parachains-common = { workspace = true } testnet-parachains-constants = { features = ["westend"], workspace = true } -assets-common = { workspace = true } # Bridges -pallet-xcm-bridge-hub-router = { workspace = true } bp-asset-hub-rococo = { workspace = true } bp-asset-hub-westend = { workspace = true } bp-bridge-hub-rococo = { workspace = true } bp-bridge-hub-westend = { workspace = true } +pallet-xcm-bridge-hub-router = { workspace = true } snowbridge-router-primitives = { workspace = true } [dev-dependencies] asset-test-utils = { workspace = true, default-features = true } +parachains-runtimes-test-utils = { workspace = true, default-features = true } [build-dependencies] substrate-wasm-builder = { optional = true, workspace = true, default-features = true } @@ -111,6 +117,7 @@ runtime-benchmarks = [ "assets-common/runtime-benchmarks", "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -121,11 +128,13 @@ runtime-benchmarks = [ "pallet-asset-conversion-ops/runtime-benchmarks", "pallet-asset-conversion-tx-payment/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", + "pallet-asset-rewards/runtime-benchmarks", "pallet-assets-freezer/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", + "pallet-migrations/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-nft-fractionalization/runtime-benchmarks", "pallet-nfts/runtime-benchmarks", @@ -147,10 +156,12 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", @@ -160,6 +171,7 @@ try-runtime = [ "pallet-asset-conversion-ops/try-runtime", "pallet-asset-conversion-tx-payment/try-runtime", "pallet-asset-conversion/try-runtime", + "pallet-asset-rewards/try-runtime", "pallet-assets-freezer/try-runtime", "pallet-assets/try-runtime", "pallet-aura/try-runtime", @@ -167,6 +179,7 @@ try-runtime = [ "pallet-balances/try-runtime", "pallet-collator-selection/try-runtime", "pallet-message-queue/try-runtime", + "pallet-migrations/try-runtime", "pallet-multisig/try-runtime", "pallet-nft-fractionalization/try-runtime", "pallet-nfts/try-runtime", @@ -194,11 +207,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", @@ -212,6 +225,7 @@ std = [ "pallet-asset-conversion-ops/std", "pallet-asset-conversion-tx-payment/std", "pallet-asset-conversion/std", + "pallet-asset-rewards/std", "pallet-assets-freezer/std", "pallet-assets/std", "pallet-aura/std", @@ -219,6 +233,7 @@ std = [ "pallet-balances/std", "pallet-collator-selection/std", "pallet-message-queue/std", + "pallet-migrations/std", "pallet-multisig/std", "pallet-nft-fractionalization/std", "pallet-nfts-runtime-api/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index cafea3b6ff8bb626c4e798b8c1ef41154b5024a6..f73db17194bcfe4f81eea249e9e84228eec75f9f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -33,21 +33,23 @@ extern crate alloc; use alloc::{vec, vec::Vec}; use assets_common::{ local_and_foreign_assets::{LocalFromLeft, TargetFromLeft}, - AssetIdForTrustBackedAssetsConvert, + AssetIdForPoolAssets, AssetIdForPoolAssetsConvert, AssetIdForTrustBackedAssetsConvert, }; use codec::{Decode, Encode, MaxEncodedLen}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector, ParaId}; use frame_support::{ construct_runtime, derive_impl, - dispatch::DispatchClass, + dispatch::{DispatchClass, DispatchInfo}, genesis_builder_helper::{build_state, get_preset}, ord_parameter_types, parameter_types, traits::{ - fungible, fungibles, + fungible, + fungible::HoldConsideration, + fungibles, tokens::{imbalance::ResolveAssetTo, nonfungibles_v2::Inspect}, - AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, InstanceFilter, - Nothing, TransformOrigin, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, + ConstantStoragePrice, Equals, InstanceFilter, Nothing, TransformOrigin, }, weights::{ConstantMultiplier, Weight, WeightToFee as _}, BoundedVec, PalletId, @@ -59,6 +61,7 @@ use frame_system::{ use pallet_asset_conversion_tx_payment::SwapAssetAdapter; use pallet_nfts::{DestroyWitness, PalletFeatures}; use pallet_revive::{evm::runtime::EthExtra, AddressMapper}; +use pallet_xcm::EnsureXcm; use parachains_common::{ impls::DealWithFees, message_queue::*, AccountId, AssetIdForTrustBackedAssets, AuraId, Balance, BlockNumber, CollectionId, Hash, Header, ItemId, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, @@ -68,7 +71,9 @@ use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata, H160, U256}; use sp_runtime::{ generic, impl_opaque_keys, - traits::{AccountIdConversion, BlakeTwo256, Block as BlockT, Saturating, Verify}, + traits::{ + AccountIdConversion, BlakeTwo256, Block as BlockT, Saturating, TransactionExtension, Verify, + }, transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, Perbill, Permill, RuntimeDebug, }; @@ -80,8 +85,8 @@ use testnet_parachains_constants::westend::{ }; use xcm_config::{ ForeignAssetsConvertedConcreteId, LocationToAccountId, PoolAssetsConvertedConcreteId, - TrustBackedAssetsConvertedConcreteId, TrustBackedAssetsPalletLocation, WestendLocation, - XcmOriginToTransactDispatchOrigin, + PoolAssetsPalletLocation, TrustBackedAssetsConvertedConcreteId, + TrustBackedAssetsPalletLocation, WestendLocation, XcmOriginToTransactDispatchOrigin, }; #[cfg(any(feature = "std", test))] @@ -92,11 +97,15 @@ use assets_common::{ matching::{FromNetwork, FromSiblingParachain}, }; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; +use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use xcm::{ latest::prelude::AssetId, prelude::{VersionedAsset, VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm}, }; +#[cfg(feature = "runtime-benchmarks")] +use frame_support::traits::PalletInfoAccess; + #[cfg(feature = "runtime-benchmarks")] use xcm::latest::prelude::{ Asset, Assets as XcmAssets, Fungible, Here, InteriorLocation, Junction, Junction::*, Location, @@ -108,8 +117,6 @@ use xcm_runtime_apis::{ fees::Error as XcmPaymentApiError, }; -use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; - impl_opaque_keys! { pub struct SessionKeys { pub aura: Aura, @@ -124,7 +131,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("westmint"), impl_name: alloc::borrow::Cow::Borrowed("westmint"), authoring_version: 1, - spec_version: 1_016_006, + spec_version: 1_017_007, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, @@ -180,6 +187,11 @@ impl frame_system::Config for Runtime { type SS58Prefix = SS58Prefix; type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode<Self>; type MaxConsumers = frame_support::traits::ConstU32<16>; + type MultiBlockMigrator = MultiBlockMigrations; +} + +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo<Runtime>; } impl pallet_timestamp::Config for Runtime { @@ -213,8 +225,8 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; - type FreezeIdentifier = (); - type MaxFreezes = ConstU32<0>; + type FreezeIdentifier = RuntimeFreezeReason; + type MaxFreezes = ConstU32<50>; type DoneSlashHandler = (); } @@ -337,8 +349,21 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< AccountId, >; +/// Union fungibles implementation for `AssetsFreezer` and `ForeignAssetsFreezer`. +pub type LocalAndForeignAssetsFreezer = fungibles::UnionOf< + AssetsFreezer, + ForeignAssetsFreezer, + LocalFromLeft< + AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocation, xcm::v5::Location>, + AssetIdForTrustBackedAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + /// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`. -pub type NativeAndAssets = fungible::UnionOf< +pub type NativeAndNonPoolAssets = fungible::UnionOf< Balances, LocalAndForeignAssets, TargetFromLeft<WestendLocation, xcm::v5::Location>, @@ -346,6 +371,45 @@ pub type NativeAndAssets = fungible::UnionOf< AccountId, >; +/// Union fungibles implementation for [`LocalAndForeignAssetsFreezer`] and [`Balances`]. +pub type NativeAndNonPoolAssetsFreezer = fungible::UnionOf< + Balances, + LocalAndForeignAssetsFreezer, + TargetFromLeft<WestendLocation, xcm::v5::Location>, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`PoolAssets`] and [`NativeAndNonPoolAssets`]. +/// +/// NOTE: Should be kept updated to include ALL balances and assets in the runtime. +pub type NativeAndAllAssets = fungibles::UnionOf< + PoolAssets, + NativeAndNonPoolAssets, + LocalFromLeft< + AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation, xcm::v5::Location>, + AssetIdForPoolAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`PoolAssetsFreezer`] and [`NativeAndNonPoolAssetsFreezer`]. +/// +/// NOTE: Should be kept updated to include ALL balances and assets in the runtime. +pub type NativeAndAllAssetsFreezer = fungibles::UnionOf< + PoolAssetsFreezer, + NativeAndNonPoolAssetsFreezer, + LocalFromLeft< + AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation, xcm::v5::Location>, + AssetIdForPoolAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter< AssetConversionPalletId, (xcm::v5::Location, xcm::v5::Location), @@ -356,7 +420,7 @@ impl pallet_asset_conversion::Config for Runtime { type Balance = Balance; type HigherPrecisionBalance = sp_core::U256; type AssetKind = xcm::v5::Location; - type Assets = NativeAndAssets; + type Assets = NativeAndNonPoolAssets; type PoolId = (Self::AssetKind, Self::AssetKind); type PoolLocator = pallet_asset_conversion::WithFirstAsset< WestendLocation, @@ -384,6 +448,55 @@ impl pallet_asset_conversion::Config for Runtime { >; } +#[cfg(feature = "runtime-benchmarks")] +pub struct PalletAssetRewardsBenchmarkHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_asset_rewards::benchmarking::BenchmarkHelper<xcm::v5::Location> + for PalletAssetRewardsBenchmarkHelper +{ + fn staked_asset() -> Location { + Location::new( + 0, + [PalletInstance(<Assets as PalletInfoAccess>::index() as u8), GeneralIndex(100)], + ) + } + fn reward_asset() -> Location { + Location::new( + 0, + [PalletInstance(<Assets as PalletInfoAccess>::index() as u8), GeneralIndex(101)], + ) + } +} + +parameter_types! { + pub const AssetRewardsPalletId: PalletId = PalletId(*b"py/astrd"); + pub const RewardsPoolCreationHoldReason: RuntimeHoldReason = + RuntimeHoldReason::AssetRewards(pallet_asset_rewards::HoldReason::PoolCreation); + // 1 item, 135 bytes into the storage on pool creation. + pub const StakePoolCreationDeposit: Balance = deposit(1, 135); +} + +impl pallet_asset_rewards::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PalletId = AssetRewardsPalletId; + type Balance = Balance; + type Assets = NativeAndAllAssets; + type AssetsFreezer = NativeAndAllAssetsFreezer; + type AssetId = xcm::v5::Location; + type CreatePoolOrigin = EnsureSigned<AccountId>; + type RuntimeFreezeReason = RuntimeFreezeReason; + type Consideration = HoldConsideration< + AccountId, + Balances, + RewardsPoolCreationHoldReason, + ConstantStoragePrice<StakePoolCreationDeposit, Balance>, + >; + type WeightInfo = weights::pallet_asset_rewards::WeightInfo<Runtime>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = PalletAssetRewardsBenchmarkHelper; +} + impl pallet_asset_conversion_ops::Config for Runtime { type RuntimeEvent = RuntimeEvent; type PriorAccountIdConverter = pallet_asset_conversion::AccountIdConverterNoSeed< @@ -812,9 +925,9 @@ impl pallet_asset_conversion_tx_payment::Config for Runtime { type AssetId = xcm::v5::Location; type OnChargeAssetTransaction = SwapAssetAdapter< WestendLocation, - NativeAndAssets, + NativeAndNonPoolAssets, AssetConversion, - ResolveAssetTo<StakingPot, NativeAndAssets>, + ResolveAssetTo<StakingPot, NativeAndNonPoolAssets>, >; type WeightInfo = weights::pallet_asset_conversion_tx_payment::WeightInfo<Runtime>; #[cfg(feature = "runtime-benchmarks")] @@ -930,6 +1043,10 @@ impl pallet_xcm_bridge_hub_router::Config<ToRococoXcmRouterInstance> for Runtime type Bridges = xcm_config::bridging::NetworkExportTable; type DestinationVersion = PolkadotXcm; + type BridgeHubOrigin = frame_support::traits::EitherOfDiverse< + EnsureRoot<AccountId>, + EnsureXcm<Equals<Self::SiblingBridgeHubLocation>>, + >; type ToBridgeHubSender = XcmpQueue; type LocalXcmChannelManager = cumulus_pallet_xcmp_queue::bridging::InAndOutXcmpChannelStatusProvider<Runtime>; @@ -944,11 +1061,6 @@ parameter_types! { pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30); } -type EventRecord = frame_system::EventRecord< - <Runtime as frame_system::Config>::RuntimeEvent, - <Runtime as frame_system::Config>::Hash, ->; - impl pallet_revive::Config for Runtime { type Time = Timestamp; type Currency = Balances; @@ -968,10 +1080,11 @@ impl pallet_revive::Config for Runtime { type InstantiateOrigin = EnsureSigned<Self::AccountId>; type RuntimeHoldReason = RuntimeHoldReason; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Debug = (); type Xcm = pallet_xcm::Pallet<Self>; type ChainId = ConstU64<420_420_421>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. + type EthGasEncoder = (); + type FindAuthor = <Runtime as pallet_authorship::Config>::FindAuthor; } impl TryFrom<RuntimeCall> for pallet_revive::Call<Runtime> { @@ -985,6 +1098,25 @@ impl TryFrom<RuntimeCall> for pallet_revive::Call<Runtime> { } } +parameter_types! { + pub MbmServiceWeight: Weight = Perbill::from_percent(80) * RuntimeBlockWeights::get().max_block; +} + +impl pallet_migrations::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + #[cfg(not(feature = "runtime-benchmarks"))] + type Migrations = pallet_migrations::migrations::ResetPallet<Runtime, Revive>; + // Benchmarks need mocked migrations to guarantee that they succeed. + #[cfg(feature = "runtime-benchmarks")] + type Migrations = pallet_migrations::mock_helpers::MockedMigrations; + type CursorMaxLen = ConstU32<65_536>; + type IdentifierMaxLen = ConstU32<256>; + type MigrationStatusHandler = (); + type FailedMigrationHandler = frame_support::migrations::FreezeChainOnFailedMigration; + type MaxServiceWeight = MbmServiceWeight; + type WeightInfo = weights::pallet_migrations::WeightInfo<Runtime>; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime @@ -995,6 +1127,8 @@ construct_runtime!( // RandomnessCollectiveFlip = 2 removed Timestamp: pallet_timestamp = 3, ParachainInfo: parachain_info = 4, + WeightReclaim: cumulus_pallet_weight_reclaim = 5, + MultiBlockMigrations: pallet_migrations = 6, // Monetary stuff. Balances: pallet_balances = 10, @@ -1030,11 +1164,14 @@ construct_runtime!( NftFractionalization: pallet_nft_fractionalization = 54, PoolAssets: pallet_assets::<Instance3> = 55, AssetConversion: pallet_asset_conversion = 56, + AssetsFreezer: pallet_assets_freezer::<Instance1> = 57, ForeignAssetsFreezer: pallet_assets_freezer::<Instance2> = 58, PoolAssetsFreezer: pallet_assets_freezer::<Instance3> = 59, Revive: pallet_revive = 60, + AssetRewards: pallet_asset_rewards = 61, + StateTrieMigration: pallet_state_trie_migration = 70, // TODO: the pallet instance should be removed once all pools have migrated @@ -1052,18 +1189,20 @@ pub type SignedBlock = generic::SignedBlock<Block>; /// BlockId type as expected by this runtime. pub type BlockId = generic::BlockId<Block>; /// The extension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender<Runtime>, - frame_system::CheckSpecVersion<Runtime>, - frame_system::CheckTxVersion<Runtime>, - frame_system::CheckGenesis<Runtime>, - frame_system::CheckEra<Runtime>, - frame_system::CheckNonce<Runtime>, - frame_system::CheckWeight<Runtime>, - pallet_asset_conversion_tx_payment::ChargeAssetTxPayment<Runtime>, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim<Runtime>, - frame_metadata_hash_extension::CheckMetadataHash<Runtime>, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckTxVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckEra<Runtime>, + frame_system::CheckNonce<Runtime>, + frame_system::CheckWeight<Runtime>, + pallet_asset_conversion_tx_payment::ChargeAssetTxPayment<Runtime>, + frame_metadata_hash_extension::CheckMetadataHash<Runtime>, + ), +>; /// Default extensions applied to Ethereum transactions. #[derive(Clone, PartialEq, Eq, Debug)] @@ -1083,9 +1222,9 @@ impl EthExtra for EthExtraImpl { frame_system::CheckNonce::<Runtime>::from(nonce), frame_system::CheckWeight::<Runtime>::new(), pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::<Runtime>::from(tip, None), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::<Runtime>::new(), frame_metadata_hash_extension::CheckMetadataHash::<Runtime>::new(false), ) + .into() } } @@ -1116,6 +1255,7 @@ pub type Migrations = ( >, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion<Runtime>, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1<Runtime>, ); /// Asset Hub Westend has some undecodable storage, delete it. @@ -1309,9 +1449,11 @@ mod benches { [pallet_assets, Foreign] [pallet_assets, Pool] [pallet_asset_conversion, AssetConversion] + [pallet_asset_rewards, AssetRewards] [pallet_asset_conversion_tx_payment, AssetTxPayment] [pallet_balances, Balances] [pallet_message_queue, MessageQueue] + [pallet_migrations, MultiBlockMigrations] [pallet_multisig, Multisig] [pallet_nft_fractionalization, NftFractionalization] [pallet_nfts, Nfts] @@ -1326,12 +1468,12 @@ mod benches { [cumulus_pallet_xcmp_queue, XcmpQueue] [pallet_xcm_bridge_hub_router, ToRococo] [pallet_asset_conversion_ops, AssetConversionMigration] - [pallet_revive, Revive] // XCM [pallet_xcm, PalletXcmExtrinsicsBenchmark::<Runtime>] // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); } @@ -1528,38 +1670,31 @@ impl_runtime_apis! { // We accept the native token to pay fees. let mut acceptable_assets = vec![AssetId(native_token.clone())]; // We also accept all assets in a pool with the native token. - let assets_in_pool_with_native = assets_common::get_assets_in_pool_with::< - Runtime, - xcm::v5::Location - >(&native_token).map_err(|()| XcmPaymentApiError::VersionedConversionFailed)?.into_iter(); - acceptable_assets.extend(assets_in_pool_with_native); + acceptable_assets.extend( + assets_common::PoolAdapter::<Runtime>::get_assets_in_pool_with(native_token) + .map_err(|()| XcmPaymentApiError::VersionedConversionFailed)? + ); PolkadotXcm::query_acceptable_payment_assets(xcm_version, acceptable_assets) } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> { let native_asset = xcm_config::WestendLocation::get(); let fee_in_native = WeightToFee::weight_to_fee(&weight); - match asset.try_as::<AssetId>() { + let latest_asset_id: Result<AssetId, ()> = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == native_asset => { // for native asset Ok(fee_in_native) }, Ok(asset_id) => { - // We recognize assets in a pool with the native one. - let assets_in_pool_with_this_asset: Vec<_> = assets_common::get_assets_in_pool_with::< - Runtime, - xcm::v5::Location - >(&asset_id.0).map_err(|()| XcmPaymentApiError::VersionedConversionFailed)?; - if assets_in_pool_with_this_asset - .into_iter() - .map(|asset_id| asset_id.0) - .any(|location| location == native_asset) { - pallet_asset_conversion::Pallet::<Runtime>::quote_price_tokens_for_exact_tokens( - asset_id.clone().0, + // Try to get current price of `asset_id` in `native_asset`. + if let Ok(Some(swapped_in_native)) = assets_common::PoolAdapter::<Runtime>::quote_price_tokens_for_exact_tokens( + asset_id.0.clone(), native_asset, fee_in_native, true, // We include the fee. - ).ok_or(XcmPaymentApiError::AssetNotFound) + ) { + Ok(swapped_in_native) } else { log::trace!(target: "xcm::xcm_runtime_apis", "query_weight_to_asset_fee - unhandled asset_id: {asset_id:?}!"); Err(XcmPaymentApiError::AssetNotFound) @@ -1672,6 +1807,12 @@ impl_runtime_apis! { } } + impl pallet_asset_rewards::AssetRewards<Block, Balance> for Runtime { + fn pool_creation_cost() -> Balance { + StakePoolCreationDeposit::get() + } + } + impl cumulus_primitives_core::GetCoreSelectorApi<Block> for Runtime { fn core_selector() -> (CoreSelector, ClaimQueueOffset) { ParachainSystem::core_selector() @@ -2032,20 +2173,8 @@ impl_runtime_apis! { type ToRococo = XcmBridgeHubRouterBench<Runtime, ToRococoXcmRouterInstance>; - let whitelist: Vec<TrackedStorageKey> = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - //TODO: use from relay_well_known_keys::ACTIVE_CONFIG - hex_literal::hex!("06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec<TrackedStorageKey> = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::<BenchmarkBatch>::new(); let params = (&config, &whitelist); @@ -2078,46 +2207,41 @@ impl_runtime_apis! { } } - impl pallet_revive::ReviveApi<Block, AccountId, Balance, Nonce, BlockNumber, EventRecord> for Runtime + impl pallet_revive::ReviveApi<Block, AccountId, Balance, Nonce, BlockNumber> for Runtime { fn balance(address: H160) -> U256 { Revive::evm_balance(&address) } + fn block_gas_limit() -> U256 { + Revive::evm_block_gas_limit() + } + + fn gas_price() -> U256 { + Revive::evm_gas_price() + } + fn nonce(address: H160) -> Nonce { let account = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&address); System::account_nonce(account) } - fn eth_transact( - from: H160, - dest: Option<H160>, - value: U256, - input: Vec<u8>, - gas_limit: Option<Weight>, - storage_deposit_limit: Option<Balance>, - ) -> pallet_revive::EthContractResult<Balance> - { - use pallet_revive::AddressMapper; - let blockweights = <Runtime as frame_system::Config>::BlockWeights::get(); - let origin = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&from); - let encoded_size = |pallet_call| { + fn eth_transact(tx: pallet_revive::evm::GenericTransaction) -> Result<pallet_revive::EthTransactInfo<Balance>, pallet_revive::EthTransactError> + { + let blockweights: BlockWeights = <Runtime as frame_system::Config>::BlockWeights::get(); + let tx_fee = |pallet_call, mut dispatch_info: DispatchInfo| { let call = RuntimeCall::Revive(pallet_call); + dispatch_info.extension_weight = EthExtraImpl::get_eth_extension(0, 0u32.into()).weight(&call); let uxt: UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic::new_bare(call).into(); - uxt.encoded_size() as u32 + + pallet_transaction_payment::Pallet::<Runtime>::compute_fee( + uxt.encoded_size() as u32, + &dispatch_info, + 0u32.into(), + ) }; - Revive::bare_eth_transact( - origin, - dest, - value, - input, - gas_limit.unwrap_or(blockweights.max_block), - storage_deposit_limit.unwrap_or(u128::MAX), - encoded_size, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, - ) + Revive::bare_eth_transact(tx, blockweights.max_block, tx_fee) } fn call( @@ -2127,17 +2251,15 @@ impl_runtime_apis! { gas_limit: Option<Weight>, storage_deposit_limit: Option<Balance>, input_data: Vec<u8>, - ) -> pallet_revive::ContractResult<pallet_revive::ExecReturnValue, Balance, EventRecord> { + ) -> pallet_revive::ContractResult<pallet_revive::ExecReturnValue, Balance> { let blockweights= <Runtime as frame_system::Config>::BlockWeights::get(); Revive::bare_call( RuntimeOrigin::signed(origin), dest, value, gas_limit.unwrap_or(blockweights.max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), input_data, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } @@ -2149,19 +2271,17 @@ impl_runtime_apis! { code: pallet_revive::Code, data: Vec<u8>, salt: Option<[u8; 32]>, - ) -> pallet_revive::ContractResult<pallet_revive::InstantiateReturnValue, Balance, EventRecord> + ) -> pallet_revive::ContractResult<pallet_revive::InstantiateReturnValue, Balance> { let blockweights= <Runtime as frame_system::Config>::BlockWeights::get(); Revive::bare_instantiate( RuntimeOrigin::signed(origin), value, gas_limit.unwrap_or(blockweights.max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), code, data, salt, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 0000000000000000000000000000000000000000..1573a278e24674344714b1d466e47e723fbaca7b --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=asset-hub-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo<T>(PhantomData<T>); +impl<T: frame_system::Config> cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo<T> { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 7_470_000 picoseconds. + Weight::from_parts(7_695_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/frame_system_extensions.rs index e8dd9763c28261c19928d05719ccd3a4f5492416..a1bb92cf7008b99030421ca5fc81e1c5fda6faa8 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/frame_system_extensions.rs @@ -16,28 +16,29 @@ //! Autogenerated weights for `frame_system_extensions` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-21, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-10-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `gleipnir`, CPU: `AMD Ryzen 9 7900X 12-Core Processor` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 +//! HOSTNAME: `697235d969a1`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 // Executed Command: -// ./target/release/polkadot-parachain +// frame-omni-bencher +// v1 // benchmark // pallet -// --wasm-execution=compiled +// --extrinsic=* +// --runtime=target/release/wbuild/asset-hub-westend-runtime/asset_hub_westend_runtime.wasm // --pallet=frame_system_extensions +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 // --no-storage-info -// --no-median-slopes // --no-min-squares -// --extrinsic=* -// --steps=2 -// --repeat=2 -// --json -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/ -// --chain=asset-hub-westend-dev +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,8 +57,8 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `54` // Estimated: `3509` - // Minimum execution time: 3_206_000 picoseconds. - Weight::from_parts(6_212_000, 0) + // Minimum execution time: 6_329_000 picoseconds. + Weight::from_parts(6_665_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -67,8 +68,8 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `92` // Estimated: `3509` - // Minimum execution time: 5_851_000 picoseconds. - Weight::from_parts(8_847_000, 0) + // Minimum execution time: 12_110_000 picoseconds. + Weight::from_parts(12_883_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -78,8 +79,8 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `92` // Estimated: `3509` - // Minimum execution time: 5_851_000 picoseconds. - Weight::from_parts(8_847_000, 0) + // Minimum execution time: 12_241_000 picoseconds. + Weight::from_parts(12_780_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -87,44 +88,64 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 631_000 picoseconds. - Weight::from_parts(3_086_000, 0) + // Minimum execution time: 825_000 picoseconds. + Weight::from_parts(890_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn check_nonce() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_446_000 picoseconds. - Weight::from_parts(5_911_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `101` + // Estimated: `3593` + // Minimum execution time: 10_159_000 picoseconds. + Weight::from_parts(10_461_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } fn check_spec_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 481_000 picoseconds. - Weight::from_parts(2_916_000, 0) + // Minimum execution time: 578_000 picoseconds. + Weight::from_parts(660_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn check_tx_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 501_000 picoseconds. - Weight::from_parts(2_595_000, 0) + // Minimum execution time: 618_000 picoseconds. + Weight::from_parts(682_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `System::BlockWeight` (r:1 w:1) /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn check_weight() -> Weight { // Proof Size summary in bytes: // Measured: `24` // Estimated: `1533` - // Minimum execution time: 3_927_000 picoseconds. - Weight::from_parts(6_613_000, 0) + // Minimum execution time: 9_964_000 picoseconds. + Weight::from_parts(10_419_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1533` + // Minimum execution time: 4_890_000 picoseconds. + Weight::from_parts(5_163_000, 0) .saturating_add(Weight::from_parts(0, 1533)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs index b0f986768f408fac1a31877f9f86839d4fffb921..86cd12507401f0df64dee2806b51b6e42e8b6a86 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs @@ -15,6 +15,7 @@ pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; @@ -22,12 +23,14 @@ pub mod frame_system_extensions; pub mod pallet_asset_conversion; pub mod pallet_asset_conversion_ops; pub mod pallet_asset_conversion_tx_payment; +pub mod pallet_asset_rewards; pub mod pallet_assets_foreign; pub mod pallet_assets_local; pub mod pallet_assets_pool; pub mod pallet_balances; pub mod pallet_collator_selection; pub mod pallet_message_queue; +pub mod pallet_migrations; pub mod pallet_multisig; pub mod pallet_nft_fractionalization; pub mod pallet_nfts; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs new file mode 100644 index 0000000000000000000000000000000000000000..3bbc289fec7b61433041c8007cc1d579a74ea7e5 --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs @@ -0,0 +1,217 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `pallet_asset_rewards` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_asset_rewards +// --chain=asset-hub-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_asset_rewards`. +pub struct WeightInfo<T>(PhantomData<T>); +impl<T: frame_system::Config> pallet_asset_rewards::WeightInfo for WeightInfo<T> { + /// Storage: `Assets::Asset` (r:2 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::NextPoolId` (r:1 w:1) + /// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(157), added: 2632, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:0 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::Pools` (r:0 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `392` + // Estimated: `6360` + // Minimum execution time: 60_734_000 picoseconds. + Weight::from_parts(61_828_000, 0) + .saturating_add(Weight::from_parts(0, 6360)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `906` + // Estimated: `4809` + // Minimum execution time: 56_014_000 picoseconds. + Weight::from_parts(58_487_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn unstake() -> Weight { + // Proof Size summary in bytes: + // Measured: `906` + // Estimated: `4809` + // Minimum execution time: 59_071_000 picoseconds. + Weight::from_parts(60_631_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn harvest_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1106` + // Estimated: `6208` + // Minimum execution time: 80_585_000 picoseconds. + Weight::from_parts(82_186_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_reward_rate_per_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 17_083_000 picoseconds. + Weight::from_parts(17_816_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_admin() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 15_269_000 picoseconds. + Weight::from_parts(15_881_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_expiry_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 17_482_000 picoseconds. + Weight::from_parts(18_124_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn deposit_reward_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `781` + // Estimated: `6208` + // Minimum execution time: 66_644_000 picoseconds. + Weight::from_parts(67_950_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:0) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:1 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(157), added: 2632, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:0 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + fn cleanup_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `1139` + // Estimated: `6208` + // Minimum execution time: 124_136_000 picoseconds. + Weight::from_parts(128_642_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(10)) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_migrations.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_migrations.rs new file mode 100644 index 0000000000000000000000000000000000000000..e771059cec8596ae5b86a60e3be85600d79bbe0c --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_migrations.rs @@ -0,0 +1,225 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `pallet_migrations` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `17938671047b`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 + +// Executed Command: +// frame-omni-bencher +// v1 +// benchmark +// pallet +// --extrinsic=* +// --runtime=target/production/wbuild/asset-hub-westend-runtime/asset_hub_westend_runtime.wasm +// --pallet=pallet_migrations +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes + +#![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_migrations`. +pub struct WeightInfo<T>(PhantomData<T>); +impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { + /// Storage: `MultiBlockMigrations::Cursor` (r:1 w:1) + /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) + fn onboard_new_mbms() -> Weight { + // Proof Size summary in bytes: + // Measured: `171` + // Estimated: `67035` + // Minimum execution time: 8_697_000 picoseconds. + Weight::from_parts(8_998_000, 0) + .saturating_add(Weight::from_parts(0, 67035)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `MultiBlockMigrations::Cursor` (r:1 w:0) + /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) + fn progress_mbms_none() -> Weight { + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `67035` + // Minimum execution time: 2_737_000 picoseconds. + Weight::from_parts(2_813_000, 0) + .saturating_add(Weight::from_parts(0, 67035)) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) + /// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1) + /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) + fn exec_migration_completed() -> Weight { + // Proof Size summary in bytes: + // Measured: `129` + // Estimated: `3594` + // Minimum execution time: 6_181_000 picoseconds. + Weight::from_parts(6_458_000, 0) + .saturating_add(Weight::from_parts(0, 3594)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) + /// Storage: `MultiBlockMigrations::Historic` (r:1 w:0) + /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) + fn exec_migration_skipped_historic() -> Weight { + // Proof Size summary in bytes: + // Measured: `225` + // Estimated: `3731` + // Minimum execution time: 11_932_000 picoseconds. + Weight::from_parts(12_539_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) + } + /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) + /// Storage: `MultiBlockMigrations::Historic` (r:1 w:0) + /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) + fn exec_migration_advance() -> Weight { + // Proof Size summary in bytes: + // Measured: `171` + // Estimated: `3731` + // Minimum execution time: 11_127_000 picoseconds. + Weight::from_parts(11_584_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) + } + /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) + /// Storage: `MultiBlockMigrations::Historic` (r:1 w:1) + /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) + fn exec_migration_complete() -> Weight { + // Proof Size summary in bytes: + // Measured: `171` + // Estimated: `3731` + // Minimum execution time: 12_930_000 picoseconds. + Weight::from_parts(13_272_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) + /// Storage: `MultiBlockMigrations::Historic` (r:1 w:0) + /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) + /// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1) + /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) + fn exec_migration_fail() -> Weight { + // Proof Size summary in bytes: + // Measured: `171` + // Estimated: `3731` + // Minimum execution time: 13_709_000 picoseconds. + Weight::from_parts(14_123_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn on_init_loop() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 162_000 picoseconds. + Weight::from_parts(188_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1) + /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) + fn force_set_cursor() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_737_000 picoseconds. + Weight::from_parts(2_919_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1) + /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) + fn force_set_active_cursor() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_087_000 picoseconds. + Weight::from_parts(3_320_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `MultiBlockMigrations::Cursor` (r:1 w:0) + /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) + fn force_onboard_mbms() -> Weight { + // Proof Size summary in bytes: + // Measured: `147` + // Estimated: `67035` + // Minimum execution time: 6_470_000 picoseconds. + Weight::from_parts(6_760_000, 0) + .saturating_add(Weight::from_parts(0, 67035)) + .saturating_add(T::DbWeight::get().reads(2)) + } + /// Storage: `MultiBlockMigrations::Historic` (r:256 w:256) + /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 256]`. + fn clear_historic(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1022 + n * (271 ±0)` + // Estimated: `3834 + n * (2740 ±0)` + // Minimum execution time: 15_864_000 picoseconds. + Weight::from_parts(24_535_162, 0) + .saturating_add(Weight::from_parts(0, 3834)) + // Standard Error: 8_688 + .saturating_add(Weight::from_parts(1_530_542, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2740).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, 2048]`. + fn reset_pallet_migration(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1680 + n * (38 ±0)` + // Estimated: `758 + n * (39 ±0)` + // Minimum execution time: 2_168_000 picoseconds. + Weight::from_parts(2_226_000, 0) + .saturating_add(Weight::from_parts(0, 758)) + // Standard Error: 2_841 + .saturating_add(Weight::from_parts(935_438, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 39).saturating_mul(n.into())) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_multisig.rs index 27687e10751b3ad052ddd6a9269cdcc415ef563d..737ee0f54df0cc7957a962d8275a6b68d97dcb2f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_multisig.rs @@ -1,42 +1,43 @@ // 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 <http://www.gnu.org/licenses/>. //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --extrinsic=* // --chain=asset-hub-westend-dev -// --wasm-execution=compiled // --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-westend/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,11 +55,11 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 14_098_000 picoseconds. - Weight::from_parts(14_915_657, 0) + // Minimum execution time: 16_032_000 picoseconds. + Weight::from_parts(16_636_014, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 6 - .saturating_add(Weight::from_parts(454, 0).saturating_mul(z.into())) + // Standard Error: 11 + .saturating_add(Weight::from_parts(632, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -66,15 +67,15 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `262 + s * (2 ±0)` + // Measured: `295 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 44_573_000 picoseconds. - Weight::from_parts(32_633_219, 0) + // Minimum execution time: 47_519_000 picoseconds. + Weight::from_parts(33_881_382, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_256 - .saturating_add(Weight::from_parts(131_767, 0).saturating_mul(s.into())) - // Standard Error: 12 - .saturating_add(Weight::from_parts(1_512, 0).saturating_mul(z.into())) + // Standard Error: 1_770 + .saturating_add(Weight::from_parts(159_560, 0).saturating_mul(s.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(2_031, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -84,15 +85,15 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 30_035_000 picoseconds. - Weight::from_parts(20_179_371, 0) + // Minimum execution time: 31_369_000 picoseconds. + Weight::from_parts(18_862_672, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 827 - .saturating_add(Weight::from_parts(110_520, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_419, 0).saturating_mul(z.into())) + // Standard Error: 1_519 + .saturating_add(Weight::from_parts(141_546, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_057, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -104,60 +105,63 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `385 + s * (33 ±0)` + // Measured: `418 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 50_444_000 picoseconds. - Weight::from_parts(36_060_265, 0) + // Minimum execution time: 55_421_000 picoseconds. + Weight::from_parts(33_628_199, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_604 - .saturating_add(Weight::from_parts(187_796, 0).saturating_mul(s.into())) - // Standard Error: 15 - .saturating_add(Weight::from_parts(1_506, 0).saturating_mul(z.into())) + // Standard Error: 2_430 + .saturating_add(Weight::from_parts(247_959, 0).saturating_mul(s.into())) + // Standard Error: 23 + .saturating_add(Weight::from_parts(2_339, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `295 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 30_298_000 picoseconds. - Weight::from_parts(31_284_628, 0) + // Minimum execution time: 30_380_000 picoseconds. + Weight::from_parts(32_147_463, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 924 - .saturating_add(Weight::from_parts(132_724, 0).saturating_mul(s.into())) + // Standard Error: 1_530 + .saturating_add(Weight::from_parts(156_234, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 17_486_000 picoseconds. - Weight::from_parts(18_518_530, 0) + // Minimum execution time: 17_016_000 picoseconds. + Weight::from_parts(17_777_791, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_274 - .saturating_add(Weight::from_parts(103_767, 0).saturating_mul(s.into())) + // Standard Error: 1_216 + .saturating_add(Weight::from_parts(137_967, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `482 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 31_236_000 picoseconds. - Weight::from_parts(32_663_816, 0) + // Minimum execution time: 31_594_000 picoseconds. + Weight::from_parts(31_850_574, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_445 - .saturating_add(Weight::from_parts(131_060, 0).saturating_mul(s.into())) + // Standard Error: 2_031 + .saturating_add(Weight::from_parts(159_513, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs index be3d7661ab3cde8d94cf7e22eeed0a48ffa1cd5c..93409463d4e507eab4efe674c0bc61a19d2f509f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-04-01, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-f3xfxtob-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `c0a5c14955e4`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=asset-hub-westend-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=asset-hub-westend-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -64,14 +66,16 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 21_050_000 picoseconds. - Weight::from_parts(21_834_000, 0) + // Minimum execution time: 28_333_000 picoseconds. + Weight::from_parts(29_115_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) @@ -90,18 +94,20 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 92_497_000 picoseconds. - Weight::from_parts(95_473_000, 0) + // Minimum execution time: 111_150_000 picoseconds. + Weight::from_parts(113_250_000, 0) .saturating_add(Weight::from_parts(0, 3610)) - .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().reads(9)) .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::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`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: `XcmpQueue::DeliveryFeeFactor` (r:1 w:0) - /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: Some(28), added: 2503, 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) @@ -111,25 +117,29 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Storage: `ParachainSystem::RelevantMessagingState` (r:1 w:0) /// Proof: `ParachainSystem::RelevantMessagingState` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) - /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: Some(1282), added: 1777, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) - /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: Some(105506), added: 107981, mode: `MaxEncodedLen`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `367` + // Measured: `400` // Estimated: `6196` - // Minimum execution time: 120_059_000 picoseconds. - Weight::from_parts(122_894_000, 0) + // Minimum execution time: 135_730_000 picoseconds. + Weight::from_parts(140_479_000, 0) .saturating_add(Weight::from_parts(0, 6196)) - .saturating_add(T::DbWeight::get().reads(9)) + .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`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Assets::Asset` (r:1 w:1) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) /// Storage: `Assets::Account` (r:2 w:2) /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) @@ -146,21 +156,24 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `496` + // Measured: `571` // Estimated: `6208` - // Minimum execution time: 141_977_000 picoseconds. - Weight::from_parts(145_981_000, 0) + // Minimum execution time: 174_654_000 picoseconds. + Weight::from_parts(182_260_000, 0) .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().reads(14)) .saturating_add(T::DbWeight::get().writes(7)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 7_426_000 picoseconds. - Weight::from_parts(7_791_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `103` + // Estimated: `1588` + // Minimum execution time: 12_750_000 picoseconds. + Weight::from_parts(13_124_000, 0) + .saturating_add(Weight::from_parts(0, 1588)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -168,8 +181,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_224_000 picoseconds. - Weight::from_parts(6_793_000, 0) + // Minimum execution time: 7_083_000 picoseconds. + Weight::from_parts(7_353_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -179,8 +192,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_812_000 picoseconds. - Weight::from_parts(2_008_000, 0) + // Minimum execution time: 2_254_000 picoseconds. + Weight::from_parts(2_408_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -206,8 +219,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 26_586_000 picoseconds. - Weight::from_parts(27_181_000, 0) + // Minimum execution time: 34_983_000 picoseconds. + Weight::from_parts(35_949_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -232,8 +245,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `363` // Estimated: `3828` - // Minimum execution time: 28_295_000 picoseconds. - Weight::from_parts(29_280_000, 0) + // Minimum execution time: 38_226_000 picoseconds. + Weight::from_parts(39_353_000, 0) .saturating_add(Weight::from_parts(0, 3828)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -244,45 +257,45 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_803_000 picoseconds. - Weight::from_parts(1_876_000, 0) + // Minimum execution time: 2_254_000 picoseconds. + Weight::from_parts(2_432_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `159` - // Estimated: `13524` - // Minimum execution time: 18_946_000 picoseconds. - Weight::from_parts(19_456_000, 0) - .saturating_add(Weight::from_parts(0, 13524)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15999` + // Minimum execution time: 25_561_000 picoseconds. + Weight::from_parts(26_274_000, 0) + .saturating_add(Weight::from_parts(0, 15999)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `163` - // Estimated: `13528` - // Minimum execution time: 19_080_000 picoseconds. - Weight::from_parts(19_498_000, 0) - .saturating_add(Weight::from_parts(0, 13528)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `16003` + // Minimum execution time: 25_950_000 picoseconds. + Weight::from_parts(26_532_000, 0) + .saturating_add(Weight::from_parts(0, 16003)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `173` - // Estimated: `16013` - // Minimum execution time: 20_637_000 picoseconds. - Weight::from_parts(21_388_000, 0) - .saturating_add(Weight::from_parts(0, 16013)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18488` + // Minimum execution time: 28_508_000 picoseconds. + Weight::from_parts(29_178_000, 0) + .saturating_add(Weight::from_parts(0, 18488)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -302,36 +315,36 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `212` // Estimated: `6152` - // Minimum execution time: 25_701_000 picoseconds. - Weight::from_parts(26_269_000, 0) + // Minimum execution time: 33_244_000 picoseconds. + Weight::from_parts(33_946_000, 0) .saturating_add(Weight::from_parts(0, 6152)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `206` - // Estimated: `11096` - // Minimum execution time: 11_949_000 picoseconds. - Weight::from_parts(12_249_000, 0) - .saturating_add(Weight::from_parts(0, 11096)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `176` + // Estimated: `13541` + // Minimum execution time: 18_071_000 picoseconds. + Weight::from_parts(18_677_000, 0) + .saturating_add(Weight::from_parts(0, 13541)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `170` - // Estimated: `13535` - // Minimum execution time: 19_278_000 picoseconds. - Weight::from_parts(19_538_000, 0) - .saturating_add(Weight::from_parts(0, 13535)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `16010` + // Minimum execution time: 25_605_000 picoseconds. + Weight::from_parts(26_284_000, 0) + .saturating_add(Weight::from_parts(0, 16010)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -348,11 +361,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: // Measured: `212` - // Estimated: `13577` - // Minimum execution time: 35_098_000 picoseconds. - Weight::from_parts(35_871_000, 0) - .saturating_add(Weight::from_parts(0, 13577)) - .saturating_add(T::DbWeight::get().reads(11)) + // Estimated: `16052` + // Minimum execution time: 46_991_000 picoseconds. + Weight::from_parts(47_866_000, 0) + .saturating_add(Weight::from_parts(0, 16052)) + .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -363,8 +376,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `103` // Estimated: `1588` - // Minimum execution time: 3_862_000 picoseconds. - Weight::from_parts(4_082_000, 0) + // Minimum execution time: 5_685_000 picoseconds. + Weight::from_parts(5_816_000, 0) .saturating_add(Weight::from_parts(0, 1588)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -375,22 +388,24 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `7740` // Estimated: `11205` - // Minimum execution time: 25_423_000 picoseconds. - Weight::from_parts(25_872_000, 0) + // Minimum execution time: 31_271_000 picoseconds. + Weight::from_parts(32_195_000, 0) .saturating_add(Weight::from_parts(0, 11205)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `160` // Estimated: `3625` - // Minimum execution time: 37_148_000 picoseconds. - Weight::from_parts(37_709_000, 0) + // Minimum execution time: 43_530_000 picoseconds. + Weight::from_parts(44_942_000, 0) .saturating_add(Weight::from_parts(0, 3625)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs index c0898012e9f32be0b6d6a09ae6e02a41fdf05a38..78aa839deacd2e529741b99c57c8c134393a830d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm_bridge_hub_router` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-08-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-07, 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` +//! HOSTNAME: `runner-acd6uxux-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: @@ -52,14 +52,14 @@ impl<T: frame_system::Config> pallet_xcm_bridge_hub_router::WeightInfo for Weigh /// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: Some(4002), added: 4497, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:0) /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: Some(1282), added: 1777, mode: `MaxEncodedLen`) - /// Storage: `ToRococoXcmRouter::DeliveryFeeFactor` (r:1 w:1) - /// Proof: `ToRococoXcmRouter::DeliveryFeeFactor` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `ToRococoXcmRouter::Bridge` (r:1 w:1) + /// Proof: `ToRococoXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) fn on_initialize_when_non_congested() -> Weight { // Proof Size summary in bytes: - // Measured: `225` + // Measured: `259` // Estimated: `5487` - // Minimum execution time: 13_483_000 picoseconds. - Weight::from_parts(13_862_000, 0) + // Minimum execution time: 14_643_000 picoseconds. + Weight::from_parts(14_992_000, 0) .saturating_add(Weight::from_parts(0, 5487)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -70,11 +70,23 @@ impl<T: frame_system::Config> pallet_xcm_bridge_hub_router::WeightInfo for Weigh /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: Some(1282), added: 1777, mode: `MaxEncodedLen`) fn on_initialize_when_congested() -> Weight { // Proof Size summary in bytes: - // Measured: `111` + // Measured: `144` // Estimated: `5487` - // Minimum execution time: 5_078_000 picoseconds. - Weight::from_parts(5_233_000, 0) + // Minimum execution time: 5_367_000 picoseconds. + Weight::from_parts(5_604_000, 0) .saturating_add(Weight::from_parts(0, 5487)) .saturating_add(T::DbWeight::get().reads(2)) } + /// Storage: `ToRococoXcmRouter::Bridge` (r:1 w:1) + /// Proof: `ToRococoXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) + fn report_bridge_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `150` + // Estimated: `1502` + // Minimum execution time: 12_562_000 picoseconds. + Weight::from_parts(12_991_000, 0) + .saturating_add(Weight::from_parts(0, 1502)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs index 35ff2dc367c0d5d41f816779dacb416673866add..a0e9705ff01dc99b8323081a49ac8f0009e8d472 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs @@ -21,6 +21,7 @@ use alloc::vec::Vec; use frame_support::weights::Weight; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::{ latest::{prelude::*, AssetTransferFilter}, DoubleEncoded, @@ -83,7 +84,11 @@ impl<Call> XcmWeightInfo<Call> for AssetHubWestendXcmWeight<Call> { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::<Runtime>::transfer_reserve_asset()) } - fn transact(_origin_type: &OriginKind, _call: &DoubleEncoded<Call>) -> Weight { + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option<Weight>, + _call: &DoubleEncoded<Call>, + ) -> Weight { XcmGeneric::<Runtime>::transact() } fn hrmp_new_channel_open_request( @@ -172,8 +177,16 @@ impl<Call> XcmWeightInfo<Call> for AssetHubWestendXcmWeight<Call> { fn clear_error() -> Weight { XcmGeneric::<Runtime>::clear_error() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::<Runtime>::set_asset_claimer() + fn set_hints(hints: &BoundedVec<Hint, HintNumVariants>) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::<Runtime>::asset_claimer()); + }, + } + } + weight } fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::<Runtime>::claim_asset() diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 528694123115f440dae61a64f9c3560580e3cb7c..0ec2741c0490a5723a3e794cf6f35820e5b98f62 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -87,7 +87,7 @@ impl<T: frame_system::Config> WeightInfo<T> { // Minimum execution time: 5_580_000 picoseconds. Weight::from_parts(5_950_000, 0) } - pub fn set_asset_claimer() -> Weight { + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index b4e938f1f8b5709515a855302d1e26b03afb968e..1ea2ce5136abd09348e8843b48b7a356b00c57f0 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 @@ -65,6 +65,7 @@ use xcm_executor::XcmExecutor; parameter_types! { pub const RootLocation: Location = Location::here(); pub const WestendLocation: Location = Location::parent(); + pub const GovernanceLocation: Location = Location::parent(); pub const RelayNetwork: Option<NetworkId> = Some(NetworkId::ByGenesis(WESTEND_GENESIS_HASH)); pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); pub UniversalLocation: InteriorLocation = @@ -359,7 +360,7 @@ pub type TrustedTeleporters = ( /// asset and the asset required for fee payment. pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< crate::AssetConversion, - crate::NativeAndAssets, + crate::NativeAndNonPoolAssets, ( TrustBackedAssetsAsLocation<TrustBackedAssetsPalletLocation, Balance, xcm::v5::Location>, ForeignAssetsConvertedConcreteId, @@ -409,7 +410,7 @@ impl xcm_executor::Config for XcmConfig { WestendLocation, crate::AssetConversion, WeightToFee, - crate::NativeAndAssets, + crate::NativeAndNonPoolAssets, ( TrustBackedAssetsAsLocation< TrustBackedAssetsPalletLocation, @@ -418,7 +419,7 @@ impl xcm_executor::Config for XcmConfig { >, ForeignAssetsConvertedConcreteId, ), - ResolveAssetTo<StakingPot, crate::NativeAndAssets>, + ResolveAssetTo<StakingPot, crate::NativeAndNonPoolAssets>, AccountId, >, // This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated 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 5d0f843554a11572b21a67037f3dcf088039a823..24b6d83ffae418962f026ed0c97ea8f5bf04d037 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs @@ -24,10 +24,10 @@ use asset_hub_westend_runtime::{ ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, LocationToAccountId, StakingPot, TrustBackedAssetsPalletLocation, WestendLocation, XcmConfig, }, - AllPalletsWithoutSystem, Assets, Balances, ExistentialDeposit, ForeignAssets, + AllPalletsWithoutSystem, Assets, Balances, Block, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys, - TrustBackedAssetsInstance, XcmpQueue, + ToRococoXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue, }; pub use asset_hub_westend_runtime::{AssetConversion, AssetDeposit, CollatorSelection, System}; use asset_test_utils::{ @@ -1250,6 +1250,56 @@ fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_fees_paid_by_suffic ) } +#[test] +fn report_bridge_status_from_xcm_bridge_router_for_rococo_works() { + asset_test_utils::test_cases_over_bridge::report_bridge_status_from_xcm_bridge_router_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + LocationToAccountId, + ToRococoXcmRouterInstance, + >( + collator_session_keys(), + bridging_to_asset_hub_rococo, + || bp_asset_hub_westend::build_congestion_message(Default::default(), true).into(), + || bp_asset_hub_westend::build_congestion_message(Default::default(), false).into(), + ) +} + +#[test] +fn test_report_bridge_status_call_compatibility() { + // if this test fails, make sure `bp_asset_hub_rococo` has valid encoding + assert_eq!( + RuntimeCall::ToRococoXcmRouter(pallet_xcm_bridge_hub_router::Call::report_bridge_status { + bridge_id: Default::default(), + is_congested: true, + }) + .encode(), + bp_asset_hub_westend::Call::ToRococoXcmRouter( + bp_asset_hub_westend::XcmBridgeHubRouterCall::report_bridge_status { + bridge_id: Default::default(), + is_congested: true, + } + ) + .encode() + ) +} + +#[test] +fn check_sane_weight_report_bridge_status() { + use pallet_xcm_bridge_hub_router::WeightInfo; + let actual = <Runtime as pallet_xcm_bridge_hub_router::Config< + ToRococoXcmRouterInstance, + >>::WeightInfo::report_bridge_status(); + let max_weight = bp_asset_hub_westend::XcmBridgeHubRouterTransactCallMaxWeight::get(); + assert!( + actual.all_lte(max_weight), + "max_weight: {:?} should be adjusted to actual {:?}", + max_weight, + actual + ); +} + #[test] fn change_xcm_bridge_hub_router_byte_fee_by_governance_works() { asset_test_utils::test_cases::change_storage_constant_by_governance_works::< @@ -1446,3 +1496,19 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); + asset_test_utils::test_cases::xcm_payment_api_with_pools_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} diff --git a/cumulus/parachains/runtimes/assets/common/Cargo.toml b/cumulus/parachains/runtimes/assets/common/Cargo.toml index fb66f0de2322f30fec69baea941e72726b73ee4e..de74f59f43c054d0744daa989157a17a8172dbcf 100644 --- a/cumulus/parachains/runtimes/assets/common/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/common/Cargo.toml @@ -5,22 +5,24 @@ authors.workspace = true edition.workspace = true description = "Assets common utilities" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } -log = { workspace = true } impl-trait-for-tuples = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } # Substrate frame-support = { workspace = true } +pallet-asset-conversion = { workspace = true } +pallet-assets = { workspace = true } sp-api = { workspace = true } sp-runtime = { workspace = true } -pallet-assets = { workspace = true } -pallet-asset-conversion = { workspace = true } # Polkadot pallet-xcm = { workspace = true } @@ -29,8 +31,8 @@ xcm-builder = { workspace = true } xcm-executor = { workspace = true } # Cumulus -parachains-common = { workspace = true } cumulus-primitives-core = { workspace = true } +parachains-common = { workspace = true } [build-dependencies] substrate-wasm-builder = { workspace = true, default-features = true } @@ -64,4 +66,5 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index 1d2d45b42c5d02779be9e25f8bae63d4a9195519..50b1b63146bc863945aa9602797db4478d08bd53 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -28,7 +28,7 @@ extern crate alloc; use crate::matching::{LocalLocationPattern, ParentLocation}; use alloc::vec::Vec; use codec::{Decode, EncodeLike}; -use core::cmp::PartialEq; +use core::{cmp::PartialEq, marker::PhantomData}; use frame_support::traits::{Equals, EverythingBut}; use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId}; use sp_runtime::traits::TryConvertInto; @@ -123,10 +123,11 @@ pub type ForeignAssetsConvertedConcreteId< BalanceConverter, >; -type AssetIdForPoolAssets = u32; +pub type AssetIdForPoolAssets = u32; + /// `Location` vs `AssetIdForPoolAssets` converter for `PoolAssets`. -pub type AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation> = - AsPrefixedGeneralIndex<PoolAssetsPalletLocation, AssetIdForPoolAssets, TryConvertInto>; +pub type AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation, L = Location> = + AsPrefixedGeneralIndex<PoolAssetsPalletLocation, AssetIdForPoolAssets, TryConvertInto, L>; /// [`MatchedConvertedConcreteId`] converter dedicated for `PoolAssets` pub type PoolAssetsConvertedConcreteId<PoolAssetsPalletLocation, Balance> = MatchedConvertedConcreteId< @@ -137,24 +138,62 @@ pub type PoolAssetsConvertedConcreteId<PoolAssetsPalletLocation, Balance> = TryConvertInto, >; -/// Returns an iterator of all assets in a pool with `asset`. -/// -/// Should only be used in runtime APIs since it iterates over the whole -/// `pallet_asset_conversion::Pools` map. -/// -/// It takes in any version of an XCM Location but always returns the latest one. -/// This is to allow some margin of migrating the pools when updating the XCM version. -/// -/// An error of type `()` is returned if the version conversion fails for XCM locations. -/// This error should be mapped by the caller to a more descriptive one. -pub fn get_assets_in_pool_with< - Runtime: pallet_asset_conversion::Config<PoolId = (L, L)>, - L: TryInto<Location> + Clone + Decode + EncodeLike + PartialEq, ->( - asset: &L, -) -> Result<Vec<AssetId>, ()> { - pallet_asset_conversion::Pools::<Runtime>::iter_keys() - .filter_map(|(asset_1, asset_2)| { +/// Adapter implementation for accessing pools (`pallet_asset_conversion`) that uses `AssetKind` as +/// a `xcm::v*` which could be different from the `xcm::latest`. +pub struct PoolAdapter<Runtime>(PhantomData<Runtime>); +impl< + Runtime: pallet_asset_conversion::Config<PoolId = (L, L), AssetKind = L>, + L: TryFrom<Location> + TryInto<Location> + Clone + Decode + EncodeLike + PartialEq, + > PoolAdapter<Runtime> +{ + /// Returns a vector of all assets in a pool with `asset`. + /// + /// Should only be used in runtime APIs since it iterates over the whole + /// `pallet_asset_conversion::Pools` map. + /// + /// It takes in any version of an XCM Location but always returns the latest one. + /// This is to allow some margin of migrating the pools when updating the XCM version. + /// + /// An error of type `()` is returned if the version conversion fails for XCM locations. + /// This error should be mapped by the caller to a more descriptive one. + pub fn get_assets_in_pool_with(asset: Location) -> Result<Vec<AssetId>, ()> { + // convert latest to the `L` version. + let asset: L = asset.try_into().map_err(|_| ())?; + Self::iter_assets_in_pool_with(&asset) + .map(|location| { + // convert `L` to the latest `AssetId` + location.try_into().map_err(|_| ()).map(AssetId) + }) + .collect::<Result<Vec<_>, _>>() + } + + /// Provides a current prices. Wrapper over + /// `pallet_asset_conversion::Pallet::<T>::quote_price_tokens_for_exact_tokens`. + /// + /// An error of type `()` is returned if the version conversion fails for XCM locations. + /// This error should be mapped by the caller to a more descriptive one. + pub fn quote_price_tokens_for_exact_tokens( + asset_1: Location, + asset_2: Location, + amount: Runtime::Balance, + include_fees: bool, + ) -> Result<Option<Runtime::Balance>, ()> { + // Convert latest to the `L` version. + let asset_1: L = asset_1.try_into().map_err(|_| ())?; + let asset_2: L = asset_2.try_into().map_err(|_| ())?; + + // Quote swap price. + Ok(pallet_asset_conversion::Pallet::<Runtime>::quote_price_tokens_for_exact_tokens( + asset_1, + asset_2, + amount, + include_fees, + )) + } + + /// Helper function for filtering pool. + pub fn iter_assets_in_pool_with(asset: &L) -> impl Iterator<Item = L> + '_ { + pallet_asset_conversion::Pools::<Runtime>::iter_keys().filter_map(|(asset_1, asset_2)| { if asset_1 == *asset { Some(asset_2) } else if asset_2 == *asset { @@ -163,8 +202,7 @@ pub fn get_assets_in_pool_with< None } }) - .map(|location| location.try_into().map_err(|_| ()).map(AssetId)) - .collect::<Result<Vec<_>, _>>() + } } #[cfg(test)] diff --git a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml index 529d6460fc4e46b6a7d04ae77c01f7135f6eed9d..cad8d10a7da3dbf7c882ad382667301b583569a2 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Test utils for Asset Hub runtimes." license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -15,27 +17,29 @@ codec = { features = ["derive", "max-encoded-len"], workspace = true } # Substrate frame-support = { workspace = true } frame-system = { workspace = true } +pallet-asset-conversion = { workspace = true } pallet-assets = { workspace = true } pallet-balances = { workspace = true } -pallet-timestamp = { workspace = true } pallet-session = { workspace = true } +pallet-timestamp = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } # Cumulus cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } -pallet-collator-selection = { workspace = true } -parachains-common = { workspace = true } cumulus-primitives-core = { workspace = true } +pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } +parachains-common = { workspace = true } parachains-runtimes-test-utils = { workspace = true } # Polkadot +pallet-xcm = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } -pallet-xcm = { workspace = true } +xcm-runtime-apis = { workspace = true } # Bridges pallet-xcm-bridge-hub-router = { workspace = true } @@ -55,6 +59,7 @@ std = [ "cumulus-primitives-core/std", "frame-support/std", "frame-system/std", + "pallet-asset-conversion/std", "pallet-assets/std", "pallet-balances/std", "pallet-collator-selection/std", @@ -69,5 +74,6 @@ std = [ "sp-runtime/std", "xcm-builder/std", "xcm-executor/std", + "xcm-runtime-apis/std", "xcm/std", ] diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs index 8dc720e2775300910e820d5ade327b30b5099917..b1577e0ca7f683c37a698dced6f7116db10175db 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs @@ -34,11 +34,14 @@ use parachains_runtimes_test_utils::{ CollatorSessionKeys, ExtBuilder, SlotDurations, ValidatorIdOf, XcmReceivedFrom, }; use sp_runtime::{ - traits::{MaybeEquivalence, StaticLookup, Zero}, + traits::{Block as BlockT, MaybeEquivalence, StaticLookup, Zero}, DispatchError, Saturating, }; use xcm::{latest::prelude::*, VersionedAssets}; use xcm_executor::{traits::ConvertLocation, XcmExecutor}; +use xcm_runtime_apis::fees::{ + runtime_decl_for_xcm_payment_api::XcmPaymentApiV1, Error as XcmPaymentApiError, +}; type RuntimeHelper<Runtime, AllPalletsWithoutSystem = ()> = parachains_runtimes_test_utils::RuntimeHelper<Runtime, AllPalletsWithoutSystem>; @@ -1202,14 +1205,20 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor let xcm = Xcm(vec![ WithdrawAsset(buy_execution_fee.clone().into()), BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited }, - Transact { origin_kind: OriginKind::Xcm, call: foreign_asset_create.into() }, + Transact { + origin_kind: OriginKind::Xcm, + call: foreign_asset_create.into(), + fallback_max_weight: None, + }, Transact { origin_kind: OriginKind::SovereignAccount, call: foreign_asset_set_metadata.into(), + fallback_max_weight: None, }, Transact { origin_kind: OriginKind::SovereignAccount, call: foreign_asset_set_team.into(), + fallback_max_weight: None, }, ExpectTransactStatus(MaybeErrorCode::Success), ]); @@ -1315,7 +1324,11 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor let xcm = Xcm(vec![ WithdrawAsset(buy_execution_fee.clone().into()), BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited }, - Transact { origin_kind: OriginKind::Xcm, call: foreign_asset_create.into() }, + Transact { + origin_kind: OriginKind::Xcm, + call: foreign_asset_create.into(), + fallback_max_weight: None, + }, ExpectTransactStatus(MaybeErrorCode::from(DispatchError::BadOrigin.encode())), ]); @@ -1584,3 +1597,108 @@ pub fn reserve_transfer_native_asset_to_non_teleport_para_works< ); }) } + +pub fn xcm_payment_api_with_pools_works<Runtime, RuntimeCall, RuntimeOrigin, Block>() +where + Runtime: XcmPaymentApiV1<Block> + + frame_system::Config<RuntimeOrigin = RuntimeOrigin, AccountId = AccountId> + + pallet_balances::Config<Balance = u128> + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + cumulus_pallet_xcmp_queue::Config + + pallet_timestamp::Config + + pallet_assets::Config< + pallet_assets::Instance1, + AssetId = u32, + Balance = <Runtime as pallet_balances::Config>::Balance, + > + pallet_asset_conversion::Config< + AssetKind = xcm::v5::Location, + Balance = <Runtime as pallet_balances::Config>::Balance, + >, + ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>, + RuntimeOrigin: OriginTrait<AccountId = <Runtime as frame_system::Config>::AccountId>, + <<Runtime as frame_system::Config>::Lookup as StaticLookup>::Source: + From<<Runtime as frame_system::Config>::AccountId>, + Block: BlockT, +{ + use xcm::prelude::*; + + ExtBuilder::<Runtime>::default().build().execute_with(|| { + let test_account = AccountId::from([0u8; 32]); + let transfer_amount = 100u128; + let xcm_to_weigh = Xcm::<RuntimeCall>::builder_unsafe() + .withdraw_asset((Here, transfer_amount)) + .buy_execution((Here, transfer_amount), Unlimited) + .deposit_asset(AllCounted(1), [1u8; 32]) + .build(); + let versioned_xcm_to_weigh = VersionedXcm::from(xcm_to_weigh.clone().into()); + + let xcm_weight = Runtime::query_xcm_weight(versioned_xcm_to_weigh); + assert!(xcm_weight.is_ok()); + let native_token: Location = Parent.into(); + let native_token_versioned = VersionedAssetId::from(AssetId(native_token.clone())); + let execution_fees = + Runtime::query_weight_to_asset_fee(xcm_weight.unwrap(), native_token_versioned); + assert!(execution_fees.is_ok()); + + // We need some balance to create an asset. + assert_ok!( + pallet_balances::Pallet::<Runtime>::mint_into(&test_account, 3_000_000_000_000,) + ); + + // Now we try to use an asset that's not in a pool. + let asset_id = 1984u32; // USDT. + let asset_not_in_pool: Location = + (PalletInstance(50), GeneralIndex(asset_id.into())).into(); + assert_ok!(pallet_assets::Pallet::<Runtime, pallet_assets::Instance1>::create( + RuntimeOrigin::signed(test_account.clone()), + asset_id.into(), + test_account.clone().into(), + 1000 + )); + let execution_fees = Runtime::query_weight_to_asset_fee( + xcm_weight.unwrap(), + asset_not_in_pool.clone().into(), + ); + assert_eq!(execution_fees, Err(XcmPaymentApiError::AssetNotFound)); + + // We add it to a pool with native. + assert_ok!(pallet_asset_conversion::Pallet::<Runtime>::create_pool( + RuntimeOrigin::signed(test_account.clone()), + native_token.clone().try_into().unwrap(), + asset_not_in_pool.clone().try_into().unwrap() + )); + let execution_fees = Runtime::query_weight_to_asset_fee( + xcm_weight.unwrap(), + asset_not_in_pool.clone().into(), + ); + // Still not enough because it doesn't have any liquidity. + assert_eq!(execution_fees, Err(XcmPaymentApiError::AssetNotFound)); + + // We mint some of the asset... + assert_ok!(pallet_assets::Pallet::<Runtime, pallet_assets::Instance1>::mint( + RuntimeOrigin::signed(test_account.clone()), + asset_id.into(), + test_account.clone().into(), + 3_000_000_000_000, + )); + // ...so we can add liquidity to the pool. + assert_ok!(pallet_asset_conversion::Pallet::<Runtime>::add_liquidity( + RuntimeOrigin::signed(test_account.clone()), + native_token.try_into().unwrap(), + asset_not_in_pool.clone().try_into().unwrap(), + 1_000_000_000_000, + 2_000_000_000_000, + 0, + 0, + test_account + )); + let execution_fees = + Runtime::query_weight_to_asset_fee(xcm_weight.unwrap(), asset_not_in_pool.into()); + // Now it works! + assert_ok!(execution_fees); + }); +} 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 4f144e24aa300471b1e14e62816032c2e64234f8..9b05f2d46dfb53744c9e1a21adc0f919ec42ad09 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 @@ -551,10 +551,7 @@ pub fn report_bridge_status_from_xcm_bridge_router_works< Weight::zero(), ); assert_ok!(outcome.ensure_complete()); - assert_eq!( - is_congested, - <<Runtime as pallet_xcm_bridge_hub_router::Config<XcmBridgeHubRouterInstance>>::LocalXcmChannelManager as pallet_xcm_bridge_hub_router::XcmChannelStatusProvider>::is_congested(&local_bridge_hub_location) - ); + assert_eq!(is_congested, pallet_xcm_bridge_hub_router::Pallet::<Runtime, XcmBridgeHubRouterInstance>::bridge().is_congested); }; report_bridge_status(true); 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 4af8a9f4385047ef547d4cb58368e0b9a57d9cf6..b3d48adfedc5f30bb7c7f4c517481dc17ec6db44 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Rococo's BridgeHub parachain runtime" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -32,9 +34,9 @@ frame-try-runtime = { optional = true, workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } -pallet-session = { workspace = true } pallet-message-queue = { workspace = true } pallet-multisig = { workspace = true } +pallet-session = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } @@ -43,10 +45,10 @@ sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } -sp-keyring = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } sp-io = { workspace = true } +sp-keyring = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } @@ -56,11 +58,11 @@ sp-transaction-pool = { workspace = true } sp-version = { workspace = true } # Polkadot -rococo-runtime-constants = { workspace = true } pallet-xcm = { workspace = true } pallet-xcm-benchmarks = { optional = true, workspace = true } polkadot-parachain-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } +rococo-runtime-constants = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } @@ -70,11 +72,11 @@ xcm-runtime-apis = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { features = ["bridging"], workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } @@ -84,7 +86,6 @@ testnet-parachains-constants = { features = ["rococo"], workspace = true } # Bridges bp-asset-hub-rococo = { workspace = true } bp-asset-hub-westend = { workspace = true } -bp-bridge-hub-polkadot = { workspace = true } bp-bridge-hub-rococo = { workspace = true } bp-bridge-hub-westend = { workspace = true } bp-header-chain = { workspace = true } @@ -93,27 +94,28 @@ bp-parachains = { workspace = true } bp-polkadot-bulletin = { workspace = true } bp-polkadot-core = { workspace = true } bp-relayers = { workspace = true } -bp-runtime = { workspace = true } bp-rococo = { workspace = true } +bp-runtime = { workspace = true } bp-westend = { workspace = true } +bp-xcm-bridge-hub-router = { workspace = true } +bridge-runtime-common = { workspace = true } pallet-bridge-grandpa = { workspace = true } pallet-bridge-messages = { workspace = true } pallet-bridge-parachains = { workspace = true } pallet-bridge-relayers = { workspace = true } pallet-xcm-bridge-hub = { workspace = true } -bridge-runtime-common = { workspace = true } # Ethereum Bridge (Snowbridge) snowbridge-beacon-primitives = { workspace = true } -snowbridge-pallet-system = { workspace = true } -snowbridge-system-runtime-api = { workspace = true } snowbridge-core = { workspace = true } +snowbridge-outbound-queue-runtime-api = { workspace = true } snowbridge-pallet-ethereum-client = { workspace = true } snowbridge-pallet-inbound-queue = { workspace = true } snowbridge-pallet-outbound-queue = { workspace = true } -snowbridge-outbound-queue-runtime-api = { workspace = true } +snowbridge-pallet-system = { workspace = true } snowbridge-router-primitives = { workspace = true } snowbridge-runtime-common = { workspace = true } +snowbridge-system-runtime-api = { workspace = true } bridge-hub-common = { workspace = true } @@ -121,6 +123,7 @@ bridge-hub-common = { workspace = true } bridge-hub-test-utils = { workspace = true, default-features = true } bridge-runtime-common = { features = ["integrity-test"], workspace = true, default-features = true } pallet-bridge-relayers = { features = ["integrity-test"], workspace = true } +parachains-runtimes-test-utils = { workspace = true, default-features = true } snowbridge-runtime-test-common = { workspace = true, default-features = true } [features] @@ -128,7 +131,6 @@ default = ["std"] std = [ "bp-asset-hub-rococo/std", "bp-asset-hub-westend/std", - "bp-bridge-hub-polkadot/std", "bp-bridge-hub-rococo/std", "bp-bridge-hub-westend/std", "bp-header-chain/std", @@ -140,17 +142,18 @@ std = [ "bp-rococo/std", "bp-runtime/std", "bp-westend/std", + "bp-xcm-bridge-hub-router/std", "bridge-hub-common/std", "bridge-runtime-common/std", "codec/std", "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking/std", "frame-executive/std", @@ -225,6 +228,7 @@ runtime-benchmarks = [ "bridge-runtime-common/runtime-benchmarks", "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -261,11 +265,13 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs index b284fa9e7af7845a3aaa220b037806777faf8a40..1f58e9c2f2bade8cb3992acc9e74f439c0c813ba 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs @@ -22,14 +22,13 @@ use crate::{ bridge_common_config::RelayersForPermissionlessLanesInstance, weights, xcm_config::UniversalLocation, AccountId, Balance, Balances, BridgeRococoBulletinGrandpa, - BridgeRococoBulletinMessages, PolkadotXcm, Runtime, RuntimeEvent, RuntimeHoldReason, - XcmOverRococoBulletin, XcmRouter, + BridgeRococoBulletinMessages, Runtime, RuntimeEvent, RuntimeHoldReason, XcmOverRococoBulletin, + XcmRouter, }; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, - target_chain::FromBridgedChainMessagesProof, HashedLaneId, + target_chain::FromBridgedChainMessagesProof, LegacyLaneId, }; -use bridge_hub_common::xcm_version::XcmVersionOfDestAndRemoteBridge; use frame_support::{ parameter_types, @@ -46,6 +45,7 @@ use testnet_parachains_constants::rococo::currency::UNITS as ROC; use xcm::{ latest::prelude::*, prelude::{InteriorLocation, NetworkId}, + AlwaysV5, }; use xcm_builder::{BridgeBlobDispatcher, ParentIsPreset, SiblingParachainConvertsVia}; @@ -120,7 +120,7 @@ impl pallet_bridge_messages::Config<WithRococoBulletinMessagesInstance> for Runt type OutboundPayload = XcmAsPlainPayload; type InboundPayload = XcmAsPlainPayload; - type LaneId = HashedLaneId; + type LaneId = LegacyLaneId; type DeliveryPayments = (); type DeliveryConfirmationPayments = (); @@ -139,8 +139,7 @@ impl pallet_xcm_bridge_hub::Config<XcmOverPolkadotBulletinInstance> for Runtime type BridgeMessagesPalletInstance = WithRococoBulletinMessagesInstance; type MessageExportPrice = (); - type DestinationVersion = - XcmVersionOfDestAndRemoteBridge<PolkadotXcm, RococoBulletinGlobalConsensusNetworkLocation>; + type DestinationVersion = AlwaysV5; type ForceOrigin = EnsureRoot<AccountId>; // We don't want to allow creating bridges for this instance. @@ -204,6 +203,7 @@ mod tests { with_bridged_chain_messages_instance: WithRococoBulletinMessagesInstance, this_chain: bp_bridge_hub_rococo::BridgeHubRococo, bridged_chain: bp_polkadot_bulletin::PolkadotBulletin, + expected_payload_type: XcmAsPlainPayload, ); // we can't use `assert_complete_bridge_constants` here, because there's a trick with @@ -253,7 +253,7 @@ where let universal_source = [GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH)), Parachain(sibling_para_id)].into(); let universal_destination = - [GlobalConsensus(RococoBulletinGlobalConsensusNetwork::get()), Parachain(2075)].into(); + [GlobalConsensus(RococoBulletinGlobalConsensusNetwork::get())].into(); let bridge_id = BridgeId::new(&universal_source, &universal_destination); // insert only bridge metadata, because the benchmarks create lanes @@ -279,29 +279,3 @@ where universal_source } - -/// Contains the migration for the PeopleRococo<>RococoBulletin bridge. -pub mod migration { - use super::*; - use frame_support::traits::ConstBool; - - parameter_types! { - pub BulletinRococoLocation: InteriorLocation = [GlobalConsensus(RococoBulletinGlobalConsensusNetwork::get())].into(); - pub RococoPeopleToRococoBulletinMessagesLane: HashedLaneId = pallet_xcm_bridge_hub::Pallet::< Runtime, XcmOverPolkadotBulletinInstance >::bridge_locations( - PeopleRococoLocation::get(), - BulletinRococoLocation::get() - ) - .unwrap() - .calculate_lane_id(xcm::latest::VERSION).expect("Valid locations"); - } - - /// Ensure that the existing lanes for the People<>Bulletin bridge are correctly configured. - pub type StaticToDynamicLanes = pallet_xcm_bridge_hub::migration::OpenBridgeForLane< - Runtime, - XcmOverPolkadotBulletinInstance, - RococoPeopleToRococoBulletinMessagesLane, - ConstBool<true>, - PeopleRococoLocation, - BulletinRococoLocation, - >; -} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs index 2710d033d64b6ffad1fe8ebe5b91c074ab0398d0..d394af73e7478facb2b58d620b967829bcac451d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs @@ -24,14 +24,14 @@ use crate::{ weights, xcm_config::UniversalLocation, AccountId, Balance, Balances, BridgeWestendMessages, PolkadotXcm, Runtime, RuntimeEvent, - RuntimeHoldReason, XcmOverBridgeHubWestend, XcmRouter, + RuntimeHoldReason, XcmOverBridgeHubWestend, XcmRouter, XcmpQueue, }; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, target_chain::FromBridgedChainMessagesProof, LegacyLaneId, }; use bridge_hub_common::xcm_version::XcmVersionOfDestAndRemoteBridge; -use pallet_xcm_bridge_hub::XcmAsPlainPayload; +use pallet_xcm_bridge_hub::{BridgeId, XcmAsPlainPayload}; use frame_support::{parameter_types, traits::PalletInfoAccess}; use frame_system::{EnsureNever, EnsureRoot}; @@ -157,11 +157,46 @@ impl pallet_xcm_bridge_hub::Config<XcmOverBridgeHubWestendInstance> for Runtime type AllowWithoutBridgeDeposit = RelayOrOtherSystemParachains<AllSiblingSystemParachains, Runtime>; - // TODO:(bridges-v2) - add `LocalXcmChannelManager` impl - https://github.com/paritytech/parity-bridges-common/issues/3047 - type LocalXcmChannelManager = (); + type LocalXcmChannelManager = CongestionManager; type BlobDispatcher = FromWestendMessageBlobDispatcher; } +/// Implementation of `bp_xcm_bridge_hub::LocalXcmChannelManager` for congestion management. +pub struct CongestionManager; +impl pallet_xcm_bridge_hub::LocalXcmChannelManager for CongestionManager { + type Error = SendError; + + fn is_congested(with: &Location) -> bool { + // This is used to check the inbound bridge queue/messages to determine if they can be + // dispatched and sent to the sibling parachain. Therefore, checking outbound `XcmpQueue` + // is sufficient here. + use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; + cumulus_pallet_xcmp_queue::bridging::OutXcmpChannelStatusProvider::<Runtime>::is_congested( + with, + ) + } + + fn suspend_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> { + // This bridge is intended for AH<>AH communication with a hard-coded/static lane, + // so `local_origin` is expected to represent only the local AH. + send_xcm::<XcmpQueue>( + local_origin.clone(), + bp_asset_hub_rococo::build_congestion_message(bridge.inner(), true).into(), + ) + .map(|_| ()) + } + + fn resume_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> { + // This bridge is intended for AH<>AH communication with a hard-coded/static lane, + // so `local_origin` is expected to represent only the local AH. + send_xcm::<XcmpQueue>( + local_origin.clone(), + bp_asset_hub_rococo::build_congestion_message(bridge.inner(), false).into(), + ) + .map(|_| ()) + } +} + #[cfg(feature = "runtime-benchmarks")] pub(crate) fn open_bridge_for_benchmarks<R, XBHI, C>( with: pallet_xcm_bridge_hub::LaneIdOf<R, XBHI>, @@ -260,6 +295,7 @@ mod tests { with_bridged_chain_messages_instance: WithBridgeHubWestendMessagesInstance, this_chain: bp_bridge_hub_rococo::BridgeHubRococo, bridged_chain: bp_bridge_hub_westend::BridgeHubWestend, + expected_payload_type: XcmAsPlainPayload, ); assert_complete_with_parachain_bridge_constants::< diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs index 98e2450ee8327467701488a74e0d99c0cb8db2dd..55fd499c2f54acdc3941ce25343dea0e5a2b9110 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs @@ -61,10 +61,20 @@ fn bridge_hub_rococo_genesis( .collect(), }, polkadot_xcm: PolkadotXcmConfig { safe_xcm_version: Some(SAFE_XCM_VERSION) }, + bridge_polkadot_bulletin_grandpa: BridgePolkadotBulletinGrandpaConfig { + owner: bridges_pallet_owner.clone(), + }, bridge_westend_grandpa: BridgeWestendGrandpaConfig { owner: bridges_pallet_owner.clone() }, bridge_westend_messages: BridgeWestendMessagesConfig { owner: bridges_pallet_owner.clone(), }, + xcm_over_polkadot_bulletin: XcmOverPolkadotBulletinConfig { + opened_bridges: vec![( + Location::new(1, [Parachain(1004)]), + Junctions::from([GlobalConsensus(NetworkId::PolkadotBulletin).into()]), + Some(bp_messages::LegacyLaneId([0, 0, 0, 0])), + )], + }, xcm_over_bridge_hub_westend: XcmOverBridgeHubWestendConfig { opened_bridges }, ethereum_system: EthereumSystemConfig { para_id: id, asset_hub_para_id }, }) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 3f3316d0be49cd85e56db7b8b893b936f1bffa90..67bc06a9321ece8e8b3684b2ea302b19db24d76a 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -124,20 +124,22 @@ pub type SignedBlock = generic::SignedBlock<Block>; pub type BlockId = generic::BlockId<Block>; /// The TransactionExtension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender<Runtime>, - frame_system::CheckSpecVersion<Runtime>, - frame_system::CheckTxVersion<Runtime>, - frame_system::CheckGenesis<Runtime>, - frame_system::CheckEra<Runtime>, - frame_system::CheckNonce<Runtime>, - frame_system::CheckWeight<Runtime>, - pallet_transaction_payment::ChargeTransactionPayment<Runtime>, - BridgeRejectObsoleteHeadersAndMessages, - (bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages,), - frame_metadata_hash_extension::CheckMetadataHash<Runtime>, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim<Runtime>, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckTxVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckEra<Runtime>, + frame_system::CheckNonce<Runtime>, + frame_system::CheckWeight<Runtime>, + pallet_transaction_payment::ChargeTransactionPayment<Runtime>, + BridgeRejectObsoleteHeadersAndMessages, + (bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages,), + frame_metadata_hash_extension::CheckMetadataHash<Runtime>, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -169,7 +171,6 @@ pub type Migrations = ( bridge_to_westend_config::WithBridgeHubWestendMessagesInstance, >, bridge_to_westend_config::migration::StaticToDynamicLanes, - bridge_to_bulletin_config::migration::StaticToDynamicLanes, frame_support::migrations::RemoveStorage< BridgeWestendMessagesPalletName, OutboundLanesCongestedSignalsKey, @@ -183,6 +184,7 @@ pub type Migrations = ( pallet_bridge_relayers::migration::v1::MigrationToV1<Runtime, ()>, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion<Runtime>, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1<Runtime>, ); parameter_types! { @@ -241,7 +243,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("bridge-hub-rococo"), impl_name: alloc::borrow::Cow::Borrowed("bridge-hub-rococo"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, @@ -314,6 +316,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo<Runtime>; +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -556,6 +562,7 @@ construct_runtime!( ParachainSystem: cumulus_pallet_parachain_system = 1, Timestamp: pallet_timestamp = 2, ParachainInfo: parachain_info = 3, + WeightReclaim: cumulus_pallet_weight_reclaim = 4, // Monetary stuff. Balances: pallet_balances = 10, @@ -668,6 +675,7 @@ mod benches { [pallet_collator_selection, CollatorSelection] [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] + [cumulus_pallet_weight_reclaim, WeightReclaim] // XCM [pallet_xcm, PalletXcmExtrinsicsBenchmark::<Runtime>] // NOTE: Make sure you point to the individual modules below. @@ -847,7 +855,8 @@ impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> { - match asset.try_as::<AssetId>() { + let latest_asset_id: Result<AssetId, ()> = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::TokenLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) @@ -1498,18 +1507,8 @@ impl_runtime_apis! { } } - let whitelist: Vec<TrackedStorageKey> = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec<TrackedStorageKey> = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::<BenchmarkBatch>::new(); let params = (&config, &whitelist); @@ -1557,41 +1556,44 @@ mod tests { use bp_polkadot_core::SuffixedCommonTransactionExtensionExt; sp_io::TestExternalities::default().execute_with(|| { - frame_system::BlockHash::<Runtime>::insert(BlockNumber::zero(), Hash::default()); - let payload: TxExtension = ( - frame_system::CheckNonZeroSender::new(), - frame_system::CheckSpecVersion::new(), - frame_system::CheckTxVersion::new(), - frame_system::CheckGenesis::new(), - frame_system::CheckEra::from(Era::Immortal), - frame_system::CheckNonce::from(10), - frame_system::CheckWeight::new(), - pallet_transaction_payment::ChargeTransactionPayment::from(10), - BridgeRejectObsoleteHeadersAndMessages, - ( - bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages::default(), - ), - frame_metadata_hash_extension::CheckMetadataHash::new(false), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), - ); - - // for BridgeHubRococo - { - let bhr_indirect_payload = bp_bridge_hub_rococo::TransactionExtension::from_params( - VERSION.spec_version, - VERSION.transaction_version, - bp_runtime::TransactionEra::Immortal, - System::block_hash(BlockNumber::zero()), - 10, - 10, - (((), ()), ((), ())), - ); - assert_eq!(payload.encode().split_last().unwrap().1, bhr_indirect_payload.encode()); - assert_eq!( - TxExtension::implicit(&payload).unwrap().encode().split_last().unwrap().1, - sp_runtime::traits::TransactionExtension::<RuntimeCall>::implicit(&bhr_indirect_payload).unwrap().encode() - ) - } - }); + frame_system::BlockHash::<Runtime>::insert(BlockNumber::zero(), Hash::default()); + let payload: TxExtension = ( + frame_system::CheckNonZeroSender::new(), + frame_system::CheckSpecVersion::new(), + frame_system::CheckTxVersion::new(), + frame_system::CheckGenesis::new(), + frame_system::CheckEra::from(Era::Immortal), + frame_system::CheckNonce::from(10), + frame_system::CheckWeight::new(), + pallet_transaction_payment::ChargeTransactionPayment::from(10), + BridgeRejectObsoleteHeadersAndMessages, + ( + bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages::default(), + ), + frame_metadata_hash_extension::CheckMetadataHash::new(false), + ).into(); + + // for BridgeHubRococo + { + let bhr_indirect_payload = bp_bridge_hub_rococo::TransactionExtension::from_params( + VERSION.spec_version, + VERSION.transaction_version, + bp_runtime::TransactionEra::Immortal, + System::block_hash(BlockNumber::zero()), + 10, + 10, + (((), ()), ((), ())), + ); + assert_eq!(payload.encode().split_last().unwrap().1, bhr_indirect_payload.encode()); + assert_eq!( + TxExtension::implicit(&payload).unwrap().encode().split_last().unwrap().1, + sp_runtime::traits::TransactionExtension::<RuntimeCall>::implicit( + &bhr_indirect_payload + ) + .unwrap() + .encode() + ) + } + }); } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 0000000000000000000000000000000000000000..ca1d8dcbe56782dba07ea4a2bae3a4978394a1f5 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=bridge-hub-rococo-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo<T>(PhantomData<T>); +impl<T: frame_system::Config> cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo<T> { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 6_988_000 picoseconds. + Weight::from_parts(7_361_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/frame_system_extensions.rs index 64eef1b4f7405abe40327241fe538ed1373edeab..93fb6f3bbbe30706eb278000a66c7e48568cf9e5 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/frame_system_extensions.rs @@ -16,28 +16,26 @@ //! Autogenerated weights for `frame_system_extensions` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-21, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `gleipnir`, CPU: `AMD Ryzen 9 7900X 12-Core Processor` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/release/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* // --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=frame_system_extensions -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=2 -// --repeat=2 -// --json +// --chain=bridge-hub-rococo-dev // --header=./cumulus/file_header.txt // --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ -// --chain=bridge-hub-rococo-dev #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,81 +48,92 @@ use core::marker::PhantomData; /// Weight functions for `frame_system_extensions`. pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo<T> { - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_genesis() -> Weight { // Proof Size summary in bytes: // Measured: `54` - // Estimated: `3509` - // Minimum execution time: 3_136_000 picoseconds. - Weight::from_parts(5_842_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 4_211_000 picoseconds. + Weight::from_parts(4_470_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_mortal_transaction() -> Weight { // Proof Size summary in bytes: // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 5_771_000 picoseconds. - Weight::from_parts(8_857_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 8_792_000 picoseconds. + Weight::from_parts(9_026_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_immortal_transaction() -> Weight { // Proof Size summary in bytes: // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 5_771_000 picoseconds. - Weight::from_parts(8_857_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 8_700_000 picoseconds. + Weight::from_parts(9_142_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } fn check_non_zero_sender() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 732_000 picoseconds. - Weight::from_parts(2_875_000, 0) + // Minimum execution time: 487_000 picoseconds. + Weight::from_parts(534_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn check_nonce() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_627_000 picoseconds. - Weight::from_parts(6_322_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `101` + // Estimated: `3593` + // Minimum execution time: 6_719_000 picoseconds. + Weight::from_parts(6_846_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } fn check_spec_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 471_000 picoseconds. - Weight::from_parts(2_455_000, 0) + // Minimum execution time: 410_000 picoseconds. + Weight::from_parts(442_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn check_tx_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 491_000 picoseconds. - Weight::from_parts(2_916_000, 0) + // Minimum execution time: 390_000 picoseconds. + Weight::from_parts(425_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `System::BlockWeight` (r:1 w:1) /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn check_weight() -> Weight { // Proof Size summary in bytes: // Measured: `24` // Estimated: `1533` - // Minimum execution time: 3_798_000 picoseconds. - Weight::from_parts(6_272_000, 0) + // Minimum execution time: 5_965_000 picoseconds. + Weight::from_parts(6_291_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1533` + // Minimum execution time: 2_738_000 picoseconds. + Weight::from_parts(2_915_000, 0) .saturating_add(Weight::from_parts(0, 1533)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs index 74796e626a2ec24b9eec32e5a336819fdb6bc507..7a0accf2e7a4530c291e02fcdb742e834bd2e7c9 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs @@ -24,6 +24,7 @@ use ::pallet_bridge_relayers::WeightInfo as _; pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_multisig.rs index 832380d3876bc3d19339779c83bba031304d17db..4ee6f6725409b5bc0f0293cac4c8a42920cd0137 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_multisig.rs @@ -1,42 +1,43 @@ // 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 <http://www.gnu.org/licenses/>. //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --extrinsic=* // --chain=bridge-hub-rococo-dev -// --wasm-execution=compiled // --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,11 +55,11 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_958_000 picoseconds. - Weight::from_parts(14_501_711, 0) + // Minimum execution time: 16_890_000 picoseconds. + Weight::from_parts(17_493_920, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 4 - .saturating_add(Weight::from_parts(626, 0).saturating_mul(z.into())) + // Standard Error: 11 + .saturating_add(Weight::from_parts(559, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -66,15 +67,15 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `191 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 44_067_000 picoseconds. - Weight::from_parts(33_432_998, 0) + // Minimum execution time: 46_099_000 picoseconds. + Weight::from_parts(34_431_293, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_250 - .saturating_add(Weight::from_parts(131_851, 0).saturating_mul(s.into())) - // Standard Error: 12 - .saturating_add(Weight::from_parts(1_459, 0).saturating_mul(z.into())) + // Standard Error: 2_489 + .saturating_add(Weight::from_parts(151_886, 0).saturating_mul(s.into())) + // Standard Error: 24 + .saturating_add(Weight::from_parts(1_900, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -84,15 +85,15 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `210` // Estimated: `6811` - // Minimum execution time: 29_373_000 picoseconds. - Weight::from_parts(19_409_201, 0) + // Minimum execution time: 31_133_000 picoseconds. + Weight::from_parts(19_877_758, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 725 - .saturating_add(Weight::from_parts(110_824, 0).saturating_mul(s.into())) - // Standard Error: 7 - .saturating_add(Weight::from_parts(1_502, 0).saturating_mul(z.into())) + // Standard Error: 1_220 + .saturating_add(Weight::from_parts(132_155, 0).saturating_mul(s.into())) + // Standard Error: 11 + .saturating_add(Weight::from_parts(1_916, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -104,60 +105,63 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `388 + s * (33 ±0)` + // Measured: `316 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 49_724_000 picoseconds. - Weight::from_parts(34_153_321, 0) + // Minimum execution time: 58_414_000 picoseconds. + Weight::from_parts(32_980_753, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_376 - .saturating_add(Weight::from_parts(174_634, 0).saturating_mul(s.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(1_753, 0).saturating_mul(z.into())) + // Standard Error: 3_838 + .saturating_add(Weight::from_parts(302_359, 0).saturating_mul(s.into())) + // Standard Error: 37 + .saturating_add(Weight::from_parts(2_629, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `191 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 31_081_000 picoseconds. - Weight::from_parts(31_552_702, 0) + // Minimum execution time: 29_917_000 picoseconds. + Weight::from_parts(33_459_806, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_066 - .saturating_add(Weight::from_parts(135_081, 0).saturating_mul(s.into())) + // Standard Error: 1_607 + .saturating_add(Weight::from_parts(150_128, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `210` // Estimated: `6811` - // Minimum execution time: 17_807_000 picoseconds. - Weight::from_parts(18_241_044, 0) + // Minimum execution time: 16_739_000 picoseconds. + Weight::from_parts(16_757_542, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 768 - .saturating_add(Weight::from_parts(112_957, 0).saturating_mul(s.into())) + // Standard Error: 909 + .saturating_add(Weight::from_parts(138_791, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `382 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 32_421_000 picoseconds. - Weight::from_parts(32_554_061, 0) + // Minimum execution time: 35_004_000 picoseconds. + Weight::from_parts(35_434_253, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_157 - .saturating_add(Weight::from_parts(141_221, 0).saturating_mul(s.into())) + // Standard Error: 1_130 + .saturating_add(Weight::from_parts(158_542, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs index a732e1a573439c4b658191697024ac3c396c9de5..0a085b858251d2fab58602a96187df3667543662 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `902e7ad7764b`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=bridge-hub-rococo-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=bridge-hub-rococo-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -64,14 +66,16 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 18_513_000 picoseconds. - Weight::from_parts(19_156_000, 0) + // Minimum execution time: 25_273_000 picoseconds. + Weight::from_parts(25_810_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) @@ -90,10 +94,10 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `70` // Estimated: `3593` - // Minimum execution time: 88_096_000 picoseconds. - Weight::from_parts(89_732_000, 0) + // Minimum execution time: 112_156_000 picoseconds. + Weight::from_parts(115_999_000, 0) .saturating_add(Weight::from_parts(0, 3593)) - .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `Benchmark::Override` (r:0 w:0) @@ -108,6 +112,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) @@ -126,21 +132,22 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `70` // Estimated: `3593` - // Minimum execution time: 88_239_000 picoseconds. - Weight::from_parts(89_729_000, 0) + // Minimum execution time: 110_987_000 picoseconds. + Weight::from_parts(114_735_000, 0) .saturating_add(Weight::from_parts(0, 3593)) - .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `32` + // Estimated: `1517` + // Minimum execution time: 12_068_000 picoseconds. + Weight::from_parts(12_565_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -148,8 +155,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_955_000 picoseconds. - Weight::from_parts(6_266_000, 0) + // Minimum execution time: 7_155_000 picoseconds. + Weight::from_parts(7_606_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -159,8 +166,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_868_000 picoseconds. - Weight::from_parts(1_961_000, 0) + // Minimum execution time: 2_325_000 picoseconds. + Weight::from_parts(2_442_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -186,8 +193,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 24_388_000 picoseconds. - Weight::from_parts(25_072_000, 0) + // Minimum execution time: 31_747_000 picoseconds. + Weight::from_parts(33_122_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -212,8 +219,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `255` // Estimated: `3720` - // Minimum execution time: 26_762_000 picoseconds. - Weight::from_parts(27_631_000, 0) + // Minimum execution time: 36_396_000 picoseconds. + Weight::from_parts(37_638_000, 0) .saturating_add(Weight::from_parts(0, 3720)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -224,45 +231,45 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_856_000 picoseconds. - Weight::from_parts(2_033_000, 0) + // Minimum execution time: 2_470_000 picoseconds. + Weight::from_parts(2_594_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `89` - // Estimated: `13454` - // Minimum execution time: 17_718_000 picoseconds. - Weight::from_parts(18_208_000, 0) - .saturating_add(Weight::from_parts(0, 13454)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15929` + // Minimum execution time: 22_530_000 picoseconds. + Weight::from_parts(22_987_000, 0) + .saturating_add(Weight::from_parts(0, 15929)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `93` - // Estimated: `13458` - // Minimum execution time: 17_597_000 picoseconds. - Weight::from_parts(18_090_000, 0) - .saturating_add(Weight::from_parts(0, 13458)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15933` + // Minimum execution time: 23_016_000 picoseconds. + Weight::from_parts(23_461_000, 0) + .saturating_add(Weight::from_parts(0, 15933)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `15946` - // Minimum execution time: 19_533_000 picoseconds. - Weight::from_parts(20_164_000, 0) - .saturating_add(Weight::from_parts(0, 15946)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18421` + // Minimum execution time: 26_216_000 picoseconds. + Weight::from_parts(26_832_000, 0) + .saturating_add(Weight::from_parts(0, 18421)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -282,36 +289,36 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `106` // Estimated: `6046` - // Minimum execution time: 24_958_000 picoseconds. - Weight::from_parts(25_628_000, 0) + // Minimum execution time: 31_060_000 picoseconds. + Weight::from_parts(32_513_000, 0) .saturating_add(Weight::from_parts(0, 6046)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `136` - // Estimated: `11026` - // Minimum execution time: 12_209_000 picoseconds. - Weight::from_parts(12_612_000, 0) - .saturating_add(Weight::from_parts(0, 11026)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `109` + // Estimated: `13474` + // Minimum execution time: 17_334_000 picoseconds. + Weight::from_parts(17_747_000, 0) + .saturating_add(Weight::from_parts(0, 13474)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `100` - // Estimated: `13465` - // Minimum execution time: 17_844_000 picoseconds. - Weight::from_parts(18_266_000, 0) - .saturating_add(Weight::from_parts(0, 13465)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15940` + // Minimum execution time: 22_535_000 picoseconds. + Weight::from_parts(23_386_000, 0) + .saturating_add(Weight::from_parts(0, 15940)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -328,11 +335,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `13471` - // Minimum execution time: 34_131_000 picoseconds. - Weight::from_parts(34_766_000, 0) - .saturating_add(Weight::from_parts(0, 13471)) - .saturating_add(T::DbWeight::get().reads(11)) + // Estimated: `15946` + // Minimum execution time: 43_437_000 picoseconds. + Weight::from_parts(44_588_000, 0) + .saturating_add(Weight::from_parts(0, 15946)) + .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -343,8 +350,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_525_000 picoseconds. - Weight::from_parts(3_724_000, 0) + // Minimum execution time: 4_941_000 picoseconds. + Weight::from_parts(5_088_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -355,22 +362,24 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 24_975_000 picoseconds. - Weight::from_parts(25_517_000, 0) + // Minimum execution time: 29_996_000 picoseconds. + Weight::from_parts(30_700_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 33_761_000 picoseconds. - Weight::from_parts(34_674_000, 0) + // Minimum execution time: 41_828_000 picoseconds. + Weight::from_parts(43_026_000, 0) .saturating_add(Weight::from_parts(0, 3555)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs index 288aac38563c4c9b7aab775fe9db9923c771a06f..efc2798999bf1ad791994acef580a849ad53c447 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs @@ -22,6 +22,7 @@ use codec::Encode; use frame_support::weights::Weight; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::{ latest::{prelude::*, AssetTransferFilter}, DoubleEncoded, @@ -84,7 +85,11 @@ impl<Call> XcmWeightInfo<Call> for BridgeHubRococoXcmWeight<Call> { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::<Runtime>::transfer_reserve_asset()) } - fn transact(_origin_type: &OriginKind, _call: &DoubleEncoded<Call>) -> Weight { + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option<Weight>, + _call: &DoubleEncoded<Call>, + ) -> Weight { XcmGeneric::<Runtime>::transact() } fn hrmp_new_channel_open_request( @@ -253,8 +258,16 @@ impl<Call> XcmWeightInfo<Call> for BridgeHubRococoXcmWeight<Call> { fn unpaid_execution(_: &WeightLimit, _: &Option<Location>) -> Weight { XcmGeneric::<Runtime>::unpaid_execution() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::<Runtime>::set_asset_claimer() + fn set_hints(hints: &BoundedVec<Hint, HintNumVariants>) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::<Runtime>::asset_claimer()); + }, + } + } + weight } fn execute_with_origin(_: &Option<InteriorLocation>, _: &Xcm<Call>) -> Weight { XcmGeneric::<Runtime>::execute_with_origin() diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index bac73e0e056739c304f714e733a2f1ba9f6de219..daf22190a42b639c67a596f553c0b99c4cbed632 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -373,7 +373,7 @@ impl<T: frame_system::Config> WeightInfo<T> { // Minimum execution time: 1_085_000 picoseconds. Weight::from_parts(1_161_000, 0) } - pub fn set_asset_claimer() -> Weight { + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs index 8be2993c68f4f3d4a5034062dcf710c345b2ee5d..c40aae5a82a9035c787f391ebc712d356e56afb5 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs @@ -29,7 +29,7 @@ use frame_support::parameter_types; use parachains_common::{AccountId, AuraId, Balance}; use snowbridge_pallet_ethereum_client::WeightInfo; use sp_core::H160; -use sp_keyring::AccountKeyring::Alice; +use sp_keyring::Sr25519Keyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, AccountId32, @@ -166,7 +166,7 @@ pub fn ethereum_outbound_queue_processes_messages_before_message_queue_works() { } fn construct_extrinsic( - sender: sp_keyring::AccountKeyring, + sender: sp_keyring::Sr25519Keyring, call: RuntimeCall, ) -> UncheckedExtrinsic { let account_id = AccountId32::from(sender.public()); @@ -184,15 +184,15 @@ fn construct_extrinsic( BridgeRejectObsoleteHeadersAndMessages::default(), (OnBridgeHubRococoRefundBridgeHubWestendMessages::default(),), frame_metadata_hash_extension::CheckMetadataHash::<Runtime>::new(false), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), - ); + ) + .into(); let payload = SignedPayload::new(call.clone(), tx_ext.clone()).unwrap(); let signature = payload.using_encoded(|e| sender.sign(e)); UncheckedExtrinsic::new_signed(call, account_id.into(), Signature::Sr25519(signature), tx_ext) } fn construct_and_apply_extrinsic( - origin: sp_keyring::AccountKeyring, + origin: sp_keyring::Sr25519Keyring, call: RuntimeCall, ) -> sp_runtime::DispatchOutcome { let xt = construct_extrinsic(origin, call); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs index 6ca858e961d3ea9771a8f5dd98f65c2da850b121..b0f4366e29cf057b7ed6bc6b4c8a97fc46249456 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs @@ -20,9 +20,9 @@ use bp_polkadot_core::Signature; use bridge_hub_rococo_runtime::{ bridge_common_config, bridge_to_bulletin_config, bridge_to_westend_config, xcm_config::{RelayNetwork, TokenLocation, XcmConfig}, - AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, Executive, ExistentialDeposit, - ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys, - TransactionPayment, TxExtension, UncheckedExtrinsic, + AllPalletsWithoutSystem, Block, BridgeRejectObsoleteHeadersAndMessages, Executive, + ExistentialDeposit, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, + RuntimeOrigin, SessionKeys, TransactionPayment, TxExtension, UncheckedExtrinsic, }; use bridge_hub_test_utils::SlotDurations; use codec::{Decode, Encode}; @@ -31,7 +31,7 @@ use parachains_common::{AccountId, AuraId, Balance}; use snowbridge_core::ChannelId; use sp_consensus_aura::SlotDuration; use sp_core::{crypto::Ss58Codec, H160}; -use sp_keyring::AccountKeyring::Alice; +use sp_keyring::Sr25519Keyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, AccountId32, Perbill, @@ -45,7 +45,7 @@ parameter_types! { } fn construct_extrinsic( - sender: sp_keyring::AccountKeyring, + sender: sp_keyring::Sr25519Keyring, call: RuntimeCall, ) -> UncheckedExtrinsic { let account_id = AccountId32::from(sender.public()); @@ -63,7 +63,6 @@ fn construct_extrinsic( BridgeRejectObsoleteHeadersAndMessages::default(), (bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages::default(),), frame_metadata_hash_extension::CheckMetadataHash::new(false), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), ) .into(); let payload = SignedPayload::new(call.clone(), tx_ext.clone()).unwrap(); @@ -72,7 +71,7 @@ fn construct_extrinsic( } fn construct_and_apply_extrinsic( - relayer_at_target: sp_keyring::AccountKeyring, + relayer_at_target: sp_keyring::Sr25519Keyring, call: RuntimeCall, ) -> sp_runtime::DispatchOutcome { let xt = construct_extrinsic(relayer_at_target, call); @@ -501,10 +500,10 @@ mod bridge_hub_westend_tests { mod bridge_hub_bulletin_tests { use super::*; - use bp_messages::{HashedLaneId, LaneIdType}; + use bp_messages::LegacyLaneId; use bridge_common_config::BridgeGrandpaRococoBulletinInstance; use bridge_hub_rococo_runtime::{ - bridge_common_config::RelayersForPermissionlessLanesInstance, + bridge_common_config::RelayersForLegacyLaneIdsMessagesInstance, xcm_config::LocationToAccountId, }; use bridge_hub_test_utils::test_cases::from_grandpa_chain; @@ -528,7 +527,7 @@ mod bridge_hub_bulletin_tests { AllPalletsWithoutSystem, BridgeGrandpaRococoBulletinInstance, WithRococoBulletinMessagesInstance, - RelayersForPermissionlessLanesInstance, + RelayersForLegacyLaneIdsMessagesInstance, >; #[test] @@ -599,7 +598,7 @@ mod bridge_hub_bulletin_tests { bridge_hub_test_utils::open_bridge_with_storage::< Runtime, XcmOverPolkadotBulletinInstance - >(locations, HashedLaneId::try_new(1, 2).unwrap()) + >(locations, LegacyLaneId([0, 0, 0, 0])) } ).1 }, @@ -663,7 +662,7 @@ mod bridge_hub_bulletin_tests { bridge_hub_test_utils::open_bridge_with_storage::< Runtime, XcmOverPolkadotBulletinInstance, - >(locations, HashedLaneId::try_new(1, 2).unwrap()) + >(locations, LegacyLaneId([0, 0, 0, 0])) }, ) .1 @@ -697,7 +696,7 @@ mod bridge_hub_bulletin_tests { bridge_hub_test_utils::open_bridge_with_storage::< Runtime, XcmOverPolkadotBulletinInstance, - >(locations, HashedLaneId::try_new(1, 2).unwrap()) + >(locations, LegacyLaneId([0, 0, 0, 0])) }, ) .1 @@ -838,3 +837,13 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} 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 637e7c71064091c4f2736bf2dfd9c2b25e7201bd..444023eac722e1b7087a789ec68856f0c9cd812c 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Westend's BridgeHub parachain runtime" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -32,9 +34,9 @@ frame-try-runtime = { optional = true, workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } -pallet-session = { workspace = true } pallet-message-queue = { workspace = true } pallet-multisig = { workspace = true } +pallet-session = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } @@ -43,10 +45,10 @@ sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } -sp-keyring = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } sp-io = { workspace = true } +sp-keyring = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } @@ -56,11 +58,11 @@ sp-transaction-pool = { workspace = true } sp-version = { workspace = true } # Polkadot -westend-runtime-constants = { workspace = true } pallet-xcm = { workspace = true } pallet-xcm-benchmarks = { optional = true, workspace = true } polkadot-parachain-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } +westend-runtime-constants = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } @@ -70,12 +72,12 @@ xcm-runtime-apis = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { features = ["bridging"], workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-utility = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } @@ -92,34 +94,36 @@ bp-messages = { workspace = true } bp-parachains = { workspace = true } bp-polkadot-core = { workspace = true } bp-relayers = { workspace = true } -bp-runtime = { workspace = true } bp-rococo = { workspace = true } +bp-runtime = { workspace = true } bp-westend = { workspace = true } +bp-xcm-bridge-hub-router = { workspace = true } +bridge-hub-common = { workspace = true } +bridge-runtime-common = { workspace = true } pallet-bridge-grandpa = { workspace = true } pallet-bridge-messages = { workspace = true } pallet-bridge-parachains = { workspace = true } pallet-bridge-relayers = { workspace = true } pallet-xcm-bridge-hub = { workspace = true } -bridge-runtime-common = { workspace = true } -bridge-hub-common = { workspace = true } # Ethereum Bridge (Snowbridge) snowbridge-beacon-primitives = { workspace = true } -snowbridge-pallet-system = { workspace = true } -snowbridge-system-runtime-api = { workspace = true } snowbridge-core = { workspace = true } +snowbridge-outbound-queue-runtime-api = { workspace = true } snowbridge-pallet-ethereum-client = { workspace = true } snowbridge-pallet-inbound-queue = { workspace = true } snowbridge-pallet-outbound-queue = { workspace = true } -snowbridge-outbound-queue-runtime-api = { workspace = true } +snowbridge-pallet-system = { workspace = true } snowbridge-router-primitives = { workspace = true } snowbridge-runtime-common = { workspace = true } +snowbridge-system-runtime-api = { workspace = true } [dev-dependencies] bridge-hub-test-utils = { workspace = true, default-features = true } bridge-runtime-common = { features = ["integrity-test"], workspace = true, default-features = true } pallet-bridge-relayers = { features = ["integrity-test"], workspace = true } +parachains-runtimes-test-utils = { workspace = true, default-features = true } snowbridge-runtime-test-common = { workspace = true, default-features = true } [features] @@ -137,17 +141,18 @@ std = [ "bp-rococo/std", "bp-runtime/std", "bp-westend/std", + "bp-xcm-bridge-hub-router/std", "bridge-hub-common/std", "bridge-runtime-common/std", "codec/std", "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking/std", "frame-executive/std", @@ -222,6 +227,7 @@ runtime-benchmarks = [ "bridge-runtime-common/runtime-benchmarks", "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -258,11 +264,13 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs index cd34655131444af9d07afacd0e898a93b04821d5..a5fb33cf504d50dccd01ef38d6339341c8362f90 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs @@ -21,7 +21,7 @@ use crate::{ weights, xcm_config::UniversalLocation, AccountId, Balance, Balances, BridgeRococoMessages, PolkadotXcm, Runtime, RuntimeEvent, - RuntimeHoldReason, XcmOverBridgeHubRococo, XcmRouter, + RuntimeHoldReason, XcmOverBridgeHubRococo, XcmRouter, XcmpQueue, }; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, @@ -29,7 +29,7 @@ use bp_messages::{ }; use bp_parachains::SingleParaStoredHeaderDataBuilder; use bridge_hub_common::xcm_version::XcmVersionOfDestAndRemoteBridge; -use pallet_xcm_bridge_hub::XcmAsPlainPayload; +use pallet_xcm_bridge_hub::{BridgeId, XcmAsPlainPayload}; use frame_support::{ parameter_types, @@ -186,11 +186,46 @@ impl pallet_xcm_bridge_hub::Config<XcmOverBridgeHubRococoInstance> for Runtime { type AllowWithoutBridgeDeposit = RelayOrOtherSystemParachains<AllSiblingSystemParachains, Runtime>; - // TODO:(bridges-v2) - add `LocalXcmChannelManager` impl - https://github.com/paritytech/parity-bridges-common/issues/3047 - type LocalXcmChannelManager = (); + type LocalXcmChannelManager = CongestionManager; type BlobDispatcher = FromRococoMessageBlobDispatcher; } +/// Implementation of `bp_xcm_bridge_hub::LocalXcmChannelManager` for congestion management. +pub struct CongestionManager; +impl pallet_xcm_bridge_hub::LocalXcmChannelManager for CongestionManager { + type Error = SendError; + + fn is_congested(with: &Location) -> bool { + // This is used to check the inbound bridge queue/messages to determine if they can be + // dispatched and sent to the sibling parachain. Therefore, checking outbound `XcmpQueue` + // is sufficient here. + use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; + cumulus_pallet_xcmp_queue::bridging::OutXcmpChannelStatusProvider::<Runtime>::is_congested( + with, + ) + } + + fn suspend_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> { + // This bridge is intended for AH<>AH communication with a hard-coded/static lane, + // so `local_origin` is expected to represent only the local AH. + send_xcm::<XcmpQueue>( + local_origin.clone(), + bp_asset_hub_westend::build_congestion_message(bridge.inner(), true).into(), + ) + .map(|_| ()) + } + + fn resume_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> { + // This bridge is intended for AH<>AH communication with a hard-coded/static lane, + // so `local_origin` is expected to represent only the local AH. + send_xcm::<XcmpQueue>( + local_origin.clone(), + bp_asset_hub_westend::build_congestion_message(bridge.inner(), false).into(), + ) + .map(|_| ()) + } +} + #[cfg(feature = "runtime-benchmarks")] pub(crate) fn open_bridge_for_benchmarks<R, XBHI, C>( with: pallet_xcm_bridge_hub::LaneIdOf<R, XBHI>, @@ -288,6 +323,7 @@ mod tests { with_bridged_chain_messages_instance: WithBridgeHubRococoMessagesInstance, this_chain: bp_bridge_hub_westend::BridgeHubWestend, bridged_chain: bp_bridge_hub_rococo::BridgeHubRococo, + expected_payload_type: XcmAsPlainPayload, ); assert_complete_with_parachain_bridge_constants::< diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 65e7d291dc3719df0867c41c0346c9a87aa1db6f..36b565bdca1c9923cefe6f27617b9c38fc71ce0b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -120,20 +120,22 @@ pub type SignedBlock = generic::SignedBlock<Block>; pub type BlockId = generic::BlockId<Block>; /// The TransactionExtension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender<Runtime>, - frame_system::CheckSpecVersion<Runtime>, - frame_system::CheckTxVersion<Runtime>, - frame_system::CheckGenesis<Runtime>, - frame_system::CheckEra<Runtime>, - frame_system::CheckNonce<Runtime>, - frame_system::CheckWeight<Runtime>, - pallet_transaction_payment::ChargeTransactionPayment<Runtime>, - BridgeRejectObsoleteHeadersAndMessages, - (bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages,), - frame_metadata_hash_extension::CheckMetadataHash<Runtime>, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim<Runtime>, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckTxVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckEra<Runtime>, + frame_system::CheckNonce<Runtime>, + frame_system::CheckWeight<Runtime>, + pallet_transaction_payment::ChargeTransactionPayment<Runtime>, + BridgeRejectObsoleteHeadersAndMessages, + (bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages,), + frame_metadata_hash_extension::CheckMetadataHash<Runtime>, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -169,6 +171,7 @@ pub type Migrations = ( bridge_to_ethereum_config::migrations::MigrationForXcmV5<Runtime>, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion<Runtime>, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1<Runtime>, ); parameter_types! { @@ -226,7 +229,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("bridge-hub-westend"), impl_name: alloc::borrow::Cow::Borrowed("bridge-hub-westend"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, @@ -299,6 +302,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo<Runtime>; +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -387,10 +394,14 @@ parameter_types! { impl pallet_message_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = weights::pallet_message_queue::WeightInfo<Runtime>; - #[cfg(feature = "runtime-benchmarks")] + // Use the NoopMessageProcessor exclusively for benchmarks, not for tests with the + // runtime-benchmarks feature as tests require the BridgeHubMessageRouter to process messages. + // The "test" feature flag doesn't work, hence the reliance on the "std" feature, which is + // enabled during tests. + #[cfg(all(not(feature = "std"), feature = "runtime-benchmarks"))] type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor<AggregateMessageOrigin>; - #[cfg(not(feature = "runtime-benchmarks"))] + #[cfg(any(feature = "std", not(feature = "runtime-benchmarks")))] type MessageProcessor = bridge_hub_common::BridgeHubMessageRouter< xcm_builder::ProcessXcmMessage< AggregateMessageOrigin, @@ -532,6 +543,7 @@ construct_runtime!( ParachainSystem: cumulus_pallet_parachain_system = 1, Timestamp: pallet_timestamp = 2, ParachainInfo: parachain_info = 3, + WeightReclaim: cumulus_pallet_weight_reclaim = 4, // Monetary stuff. Balances: pallet_balances = 10, @@ -622,6 +634,7 @@ mod benches { [snowbridge_pallet_outbound_queue, EthereumOutboundQueue] [snowbridge_pallet_system, EthereumSystem] [snowbridge_pallet_ethereum_client, EthereumBeaconClient] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); } @@ -779,7 +792,8 @@ impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> { - match asset.try_as::<AssetId>() { + let latest_asset_id: Result<AssetId, ()> = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::WestendLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) @@ -1314,18 +1328,8 @@ impl_runtime_apis! { } } - let whitelist: Vec<TrackedStorageKey> = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec<TrackedStorageKey> = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::<BenchmarkBatch>::new(); let params = (&config, &whitelist); @@ -1378,40 +1382,43 @@ mod tests { use bp_polkadot_core::SuffixedCommonTransactionExtensionExt; sp_io::TestExternalities::default().execute_with(|| { - frame_system::BlockHash::<Runtime>::insert(BlockNumber::zero(), Hash::default()); - let payload: TxExtension = ( - frame_system::CheckNonZeroSender::new(), - frame_system::CheckSpecVersion::new(), - frame_system::CheckTxVersion::new(), - frame_system::CheckGenesis::new(), - frame_system::CheckEra::from(Era::Immortal), - frame_system::CheckNonce::from(10), - frame_system::CheckWeight::new(), - pallet_transaction_payment::ChargeTransactionPayment::from(10), - BridgeRejectObsoleteHeadersAndMessages, - ( - bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages::default(), - ), + frame_system::BlockHash::<Runtime>::insert(BlockNumber::zero(), Hash::default()); + let payload: TxExtension = ( + frame_system::CheckNonZeroSender::new(), + frame_system::CheckSpecVersion::new(), + frame_system::CheckTxVersion::new(), + frame_system::CheckGenesis::new(), + frame_system::CheckEra::from(Era::Immortal), + frame_system::CheckNonce::from(10), + frame_system::CheckWeight::new(), + pallet_transaction_payment::ChargeTransactionPayment::from(10), + BridgeRejectObsoleteHeadersAndMessages, + ( + bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages::default(), + ), frame_metadata_hash_extension::CheckMetadataHash::new(false), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), - ); - - { - let bh_indirect_payload = bp_bridge_hub_westend::TransactionExtension::from_params( - VERSION.spec_version, - VERSION.transaction_version, - bp_runtime::TransactionEra::Immortal, - System::block_hash(BlockNumber::zero()), - 10, - 10, - (((), ()), ((), ())), - ); - assert_eq!(payload.encode().split_last().unwrap().1, bh_indirect_payload.encode()); - assert_eq!( - TxExtension::implicit(&payload).unwrap().encode().split_last().unwrap().1, - sp_runtime::traits::TransactionExtension::<RuntimeCall>::implicit(&bh_indirect_payload).unwrap().encode() - ) - } - }); + ).into(); + + { + let bh_indirect_payload = bp_bridge_hub_westend::TransactionExtension::from_params( + VERSION.spec_version, + VERSION.transaction_version, + bp_runtime::TransactionEra::Immortal, + System::block_hash(BlockNumber::zero()), + 10, + 10, + (((), ()), ((), ())), + ); + assert_eq!(payload.encode().split_last().unwrap().1, bh_indirect_payload.encode()); + assert_eq!( + TxExtension::implicit(&payload).unwrap().encode().split_last().unwrap().1, + sp_runtime::traits::TransactionExtension::<RuntimeCall>::implicit( + &bh_indirect_payload + ) + .unwrap() + .encode() + ) + } + }); } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 0000000000000000000000000000000000000000..955b273254562376b7cdcb0c4175648a4de935f2 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=bridge-hub-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo<T>(PhantomData<T>); +impl<T: frame_system::Config> cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo<T> { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 6_810_000 picoseconds. + Weight::from_parts(7_250_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/frame_system_extensions.rs index 459b137d3b8419765362e4501db525db31135a46..21cadac25e161565c43e03efc9e0684db1c28bc4 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/frame_system_extensions.rs @@ -16,28 +16,26 @@ //! Autogenerated weights for `frame_system_extensions` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-21, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `gleipnir`, CPU: `AMD Ryzen 9 7900X 12-Core Processor` +//! HOSTNAME: `runner-ys-ssygq-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/release/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* // --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=frame_system_extensions -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=2 -// --repeat=2 -// --json +// --chain=bridge-hub-westend-dev // --header=./cumulus/file_header.txt // --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/ -// --chain=bridge-hub-westend-dev #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,81 +48,92 @@ use core::marker::PhantomData; /// Weight functions for `frame_system_extensions`. pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo<T> { - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_genesis() -> Weight { // Proof Size summary in bytes: // Measured: `54` - // Estimated: `3509` - // Minimum execution time: 3_166_000 picoseconds. - Weight::from_parts(6_021_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 4_363_000 picoseconds. + Weight::from_parts(4_521_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_mortal_transaction() -> Weight { // Proof Size summary in bytes: // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 5_651_000 picoseconds. - Weight::from_parts(9_177_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 8_522_000 picoseconds. + Weight::from_parts(8_847_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_immortal_transaction() -> Weight { // Proof Size summary in bytes: // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 5_651_000 picoseconds. - Weight::from_parts(9_177_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 8_617_000 picoseconds. + Weight::from_parts(8_789_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } fn check_non_zero_sender() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 601_000 picoseconds. - Weight::from_parts(2_805_000, 0) + // Minimum execution time: 485_000 picoseconds. + Weight::from_parts(557_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn check_nonce() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_727_000 picoseconds. - Weight::from_parts(6_051_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `101` + // Estimated: `3593` + // Minimum execution time: 6_682_000 picoseconds. + Weight::from_parts(6_821_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } fn check_spec_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 471_000 picoseconds. - Weight::from_parts(2_494_000, 0) + // Minimum execution time: 390_000 picoseconds. + Weight::from_parts(441_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn check_tx_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 521_000 picoseconds. - Weight::from_parts(2_655_000, 0) + // Minimum execution time: 395_000 picoseconds. + Weight::from_parts(455_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `System::BlockWeight` (r:1 w:1) /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn check_weight() -> Weight { // Proof Size summary in bytes: // Measured: `24` // Estimated: `1533` - // Minimum execution time: 3_808_000 picoseconds. - Weight::from_parts(6_402_000, 0) + // Minimum execution time: 6_134_000 picoseconds. + Weight::from_parts(6_308_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1533` + // Minimum execution time: 2_764_000 picoseconds. + Weight::from_parts(2_893_000, 0) .saturating_add(Weight::from_parts(0, 1533)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs index c1c5c337aca8900e7b6314b0e1aa56ed9a5fdec2..313da55831c8f2446ec570f5c78432ebc1ddca35 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs @@ -24,6 +24,7 @@ use ::pallet_bridge_relayers::WeightInfo as _; pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_multisig.rs index 91840ae0c6d77cfb4507d5f7d7b8f2aca7c84b35..599bed182de474b6e5b9d10e052e14ce32db21d0 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_multisig.rs @@ -16,28 +16,28 @@ //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-rococo-dev -// --wasm-execution=compiled -// --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares // --extrinsic=* +// --chain=bridge-hub-westend-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_958_000 picoseconds. - Weight::from_parts(14_501_711, 0) + // Minimum execution time: 16_960_000 picoseconds. + Weight::from_parts(17_458_038, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 4 - .saturating_add(Weight::from_parts(626, 0).saturating_mul(z.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(745, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -67,15 +67,15 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `296 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 44_067_000 picoseconds. - Weight::from_parts(33_432_998, 0) + // Minimum execution time: 49_023_000 picoseconds. + Weight::from_parts(36_653_713, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_250 - .saturating_add(Weight::from_parts(131_851, 0).saturating_mul(s.into())) - // Standard Error: 12 - .saturating_add(Weight::from_parts(1_459, 0).saturating_mul(z.into())) + // Standard Error: 1_966 + .saturating_add(Weight::from_parts(144_768, 0).saturating_mul(s.into())) + // Standard Error: 19 + .saturating_add(Weight::from_parts(1_983, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -85,15 +85,15 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 29_373_000 picoseconds. - Weight::from_parts(19_409_201, 0) + // Minimum execution time: 32_233_000 picoseconds. + Weight::from_parts(20_563_994, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 725 - .saturating_add(Weight::from_parts(110_824, 0).saturating_mul(s.into())) - // Standard Error: 7 - .saturating_add(Weight::from_parts(1_502, 0).saturating_mul(z.into())) + // Standard Error: 1_541 + .saturating_add(Weight::from_parts(137_834, 0).saturating_mul(s.into())) + // Standard Error: 15 + .saturating_add(Weight::from_parts(2_004, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -105,60 +105,63 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `388 + s * (33 ±0)` + // Measured: `421 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 49_724_000 picoseconds. - Weight::from_parts(34_153_321, 0) + // Minimum execution time: 57_893_000 picoseconds. + Weight::from_parts(32_138_684, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_376 - .saturating_add(Weight::from_parts(174_634, 0).saturating_mul(s.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(1_753, 0).saturating_mul(z.into())) + // Standard Error: 3_096 + .saturating_add(Weight::from_parts(324_931, 0).saturating_mul(s.into())) + // Standard Error: 30 + .saturating_add(Weight::from_parts(2_617, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `296 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 31_081_000 picoseconds. - Weight::from_parts(31_552_702, 0) + // Minimum execution time: 31_313_000 picoseconds. + Weight::from_parts(33_535_933, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_066 - .saturating_add(Weight::from_parts(135_081, 0).saturating_mul(s.into())) + // Standard Error: 1_649 + .saturating_add(Weight::from_parts(153_756, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 17_807_000 picoseconds. - Weight::from_parts(18_241_044, 0) + // Minimum execution time: 17_860_000 picoseconds. + Weight::from_parts(18_559_535, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 768 - .saturating_add(Weight::from_parts(112_957, 0).saturating_mul(s.into())) + // Standard Error: 1_036 + .saturating_add(Weight::from_parts(135_049, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `487 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 32_421_000 picoseconds. - Weight::from_parts(32_554_061, 0) + // Minimum execution time: 32_340_000 picoseconds. + Weight::from_parts(33_519_124, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_157 - .saturating_add(Weight::from_parts(141_221, 0).saturating_mul(s.into())) + // Standard Error: 1_932 + .saturating_add(Weight::from_parts(193_896, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs index a78ff2355efaf06562e44828a8df0730481d4098..fdae0c9a15229e16833929329ef463f4c9c46162 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `27f89d982f9b`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=bridge-hub-westend-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=bridge-hub-westend-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -64,14 +66,16 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 19_527_000 picoseconds. - Weight::from_parts(19_839_000, 0) + // Minimum execution time: 24_819_000 picoseconds. + Weight::from_parts(25_795_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) @@ -90,10 +94,10 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `107` // Estimated: `3593` - // Minimum execution time: 90_938_000 picoseconds. - Weight::from_parts(92_822_000, 0) + // Minimum execution time: 110_536_000 picoseconds. + Weight::from_parts(115_459_000, 0) .saturating_add(Weight::from_parts(0, 3593)) - .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `Benchmark::Override` (r:0 w:0) @@ -108,6 +112,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) @@ -126,21 +132,22 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `107` // Estimated: `3593` - // Minimum execution time: 90_133_000 picoseconds. - Weight::from_parts(92_308_000, 0) + // Minimum execution time: 109_742_000 picoseconds. + Weight::from_parts(114_362_000, 0) .saturating_add(Weight::from_parts(0, 3593)) - .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `32` + // Estimated: `1517` + // Minimum execution time: 12_252_000 picoseconds. + Weight::from_parts(12_681_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -148,8 +155,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_205_000 picoseconds. - Weight::from_parts(6_595_000, 0) + // Minimum execution time: 6_988_000 picoseconds. + Weight::from_parts(7_161_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -159,8 +166,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_927_000 picoseconds. - Weight::from_parts(2_062_000, 0) + // Minimum execution time: 2_249_000 picoseconds. + Weight::from_parts(2_479_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -186,8 +193,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 25_078_000 picoseconds. - Weight::from_parts(25_782_000, 0) + // Minimum execution time: 31_668_000 picoseconds. + Weight::from_parts(32_129_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -212,8 +219,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `255` // Estimated: `3720` - // Minimum execution time: 28_188_000 picoseconds. - Weight::from_parts(28_826_000, 0) + // Minimum execution time: 36_002_000 picoseconds. + Weight::from_parts(37_341_000, 0) .saturating_add(Weight::from_parts(0, 3720)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -224,45 +231,45 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_886_000 picoseconds. - Weight::from_parts(1_991_000, 0) + // Minimum execution time: 2_349_000 picoseconds. + Weight::from_parts(2_511_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `89` - // Estimated: `13454` - // Minimum execution time: 17_443_000 picoseconds. - Weight::from_parts(17_964_000, 0) - .saturating_add(Weight::from_parts(0, 13454)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15929` + // Minimum execution time: 22_283_000 picoseconds. + Weight::from_parts(22_654_000, 0) + .saturating_add(Weight::from_parts(0, 15929)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `93` - // Estimated: `13458` - // Minimum execution time: 17_357_000 picoseconds. - Weight::from_parts(18_006_000, 0) - .saturating_add(Weight::from_parts(0, 13458)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15933` + // Minimum execution time: 22_717_000 picoseconds. + Weight::from_parts(23_256_000, 0) + .saturating_add(Weight::from_parts(0, 15933)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `15946` - // Minimum execution time: 18_838_000 picoseconds. - Weight::from_parts(19_688_000, 0) - .saturating_add(Weight::from_parts(0, 15946)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18421` + // Minimum execution time: 25_988_000 picoseconds. + Weight::from_parts(26_794_000, 0) + .saturating_add(Weight::from_parts(0, 18421)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -282,36 +289,36 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `106` // Estimated: `6046` - // Minimum execution time: 25_517_000 picoseconds. - Weight::from_parts(26_131_000, 0) + // Minimum execution time: 31_112_000 picoseconds. + Weight::from_parts(32_395_000, 0) .saturating_add(Weight::from_parts(0, 6046)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `136` - // Estimated: `11026` - // Minimum execution time: 11_587_000 picoseconds. - Weight::from_parts(11_963_000, 0) - .saturating_add(Weight::from_parts(0, 11026)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `109` + // Estimated: `13474` + // Minimum execution time: 17_401_000 picoseconds. + Weight::from_parts(17_782_000, 0) + .saturating_add(Weight::from_parts(0, 13474)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `100` - // Estimated: `13465` - // Minimum execution time: 17_490_000 picoseconds. - Weight::from_parts(18_160_000, 0) - .saturating_add(Weight::from_parts(0, 13465)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15940` + // Minimum execution time: 22_772_000 picoseconds. + Weight::from_parts(23_194_000, 0) + .saturating_add(Weight::from_parts(0, 15940)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -328,11 +335,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `13471` - // Minimum execution time: 34_088_000 picoseconds. - Weight::from_parts(34_598_000, 0) - .saturating_add(Weight::from_parts(0, 13471)) - .saturating_add(T::DbWeight::get().reads(11)) + // Estimated: `15946` + // Minimum execution time: 43_571_000 picoseconds. + Weight::from_parts(44_891_000, 0) + .saturating_add(Weight::from_parts(0, 15946)) + .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -343,8 +350,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_566_000 picoseconds. - Weight::from_parts(3_754_000, 0) + // Minimum execution time: 4_896_000 picoseconds. + Weight::from_parts(5_112_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -355,22 +362,24 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 25_078_000 picoseconds. - Weight::from_parts(25_477_000, 0) + // Minimum execution time: 30_117_000 picoseconds. + Weight::from_parts(31_027_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 34_661_000 picoseconds. - Weight::from_parts(35_411_000, 0) + // Minimum execution time: 41_870_000 picoseconds. + Weight::from_parts(42_750_000, 0) .saturating_add(Weight::from_parts(0, 3555)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/mod.rs index fa1304d11c6f1df70f2098eee1e332b166bfc4aa..15a1dae09d9bb6afb3ff4c5babfe2624785826ac 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/mod.rs @@ -23,6 +23,7 @@ use codec::Encode; use frame_support::weights::Weight; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::{ latest::{prelude::*, AssetTransferFilter}, DoubleEncoded, @@ -85,7 +86,11 @@ impl<Call> XcmWeightInfo<Call> for BridgeHubWestendXcmWeight<Call> { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::<Runtime>::transfer_reserve_asset()) } - fn transact(_origin_type: &OriginKind, _call: &DoubleEncoded<Call>) -> Weight { + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option<Weight>, + _call: &DoubleEncoded<Call>, + ) -> Weight { XcmGeneric::<Runtime>::transact() } fn hrmp_new_channel_open_request( @@ -174,8 +179,16 @@ impl<Call> XcmWeightInfo<Call> for BridgeHubWestendXcmWeight<Call> { fn clear_error() -> Weight { XcmGeneric::<Runtime>::clear_error() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::<Runtime>::set_asset_claimer() + fn set_hints(hints: &BoundedVec<Hint, HintNumVariants>) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::<Runtime>::asset_claimer()); + }, + } + } + weight } fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::<Runtime>::claim_asset() diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 6434f6206fbe14d2c7dfa2b004e2b21f79e791ac..03cbaa866ad8041d4131477f59fa8dd0a908486d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -373,7 +373,7 @@ impl<T: frame_system::Config> WeightInfo<T> { // Minimum execution time: 995_000 picoseconds. Weight::from_parts(1_060_000, 0) } - pub fn set_asset_claimer() -> Weight { + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs index 1a1ce2a28ea35a62b3cd2d1fb90059be37c32b18..bc570ef7f74bc58495002cb49507304b928a38d6 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs @@ -30,7 +30,7 @@ use frame_support::parameter_types; use parachains_common::{AccountId, AuraId, Balance}; use snowbridge_pallet_ethereum_client::WeightInfo; use sp_core::H160; -use sp_keyring::AccountKeyring::Alice; +use sp_keyring::Sr25519Keyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, AccountId32, @@ -167,7 +167,7 @@ pub fn ethereum_outbound_queue_processes_messages_before_message_queue_works() { } fn construct_extrinsic( - sender: sp_keyring::AccountKeyring, + sender: sp_keyring::Sr25519Keyring, call: RuntimeCall, ) -> UncheckedExtrinsic { let account_id = AccountId32::from(sender.public()); @@ -185,15 +185,15 @@ fn construct_extrinsic( BridgeRejectObsoleteHeadersAndMessages::default(), (bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages::default(),), frame_metadata_hash_extension::CheckMetadataHash::<Runtime>::new(false), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), - ); + ) + .into(); let payload = SignedPayload::new(call.clone(), extra.clone()).unwrap(); let signature = payload.using_encoded(|e| sender.sign(e)); UncheckedExtrinsic::new_signed(call, account_id.into(), Signature::Sr25519(signature), extra) } fn construct_and_apply_extrinsic( - origin: sp_keyring::AccountKeyring, + origin: sp_keyring::Sr25519Keyring, call: RuntimeCall, ) -> sp_runtime::DispatchOutcome { let xt = construct_extrinsic(origin, call); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs index 84025c4cefeb5915f5004554a65fa6fee30466e9..d7e7fbe0c72e56f807c2d488d421519086171a77 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs @@ -27,9 +27,9 @@ use bridge_hub_westend_runtime::{ bridge_common_config, bridge_to_rococo_config, bridge_to_rococo_config::RococoGlobalConsensusNetwork, xcm_config::{LocationToAccountId, RelayNetwork, WestendLocation, XcmConfig}, - AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, Executive, ExistentialDeposit, - ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys, - TransactionPayment, TxExtension, UncheckedExtrinsic, + AllPalletsWithoutSystem, Block, BridgeRejectObsoleteHeadersAndMessages, Executive, + ExistentialDeposit, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, + RuntimeOrigin, SessionKeys, TransactionPayment, TxExtension, UncheckedExtrinsic, }; use bridge_to_rococo_config::{ BridgeGrandpaRococoInstance, BridgeHubRococoLocation, BridgeParachainRococoInstance, @@ -40,7 +40,7 @@ use frame_support::{dispatch::GetDispatchInfo, parameter_types, traits::ConstU8} use parachains_common::{AccountId, AuraId, Balance}; use sp_consensus_aura::SlotDuration; use sp_core::crypto::Ss58Codec; -use sp_keyring::AccountKeyring::Alice; +use sp_keyring::Sr25519Keyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, AccountId32, Perbill, @@ -77,7 +77,7 @@ parameter_types! { } fn construct_extrinsic( - sender: sp_keyring::AccountKeyring, + sender: sp_keyring::Sr25519Keyring, call: RuntimeCall, ) -> UncheckedExtrinsic { let account_id = AccountId32::from(sender.public()); @@ -95,7 +95,6 @@ fn construct_extrinsic( BridgeRejectObsoleteHeadersAndMessages::default(), (bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages::default(),), frame_metadata_hash_extension::CheckMetadataHash::new(false), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), ) .into(); let payload = SignedPayload::new(call.clone(), tx_ext.clone()).unwrap(); @@ -104,7 +103,7 @@ fn construct_extrinsic( } fn construct_and_apply_extrinsic( - relayer_at_target: sp_keyring::AccountKeyring, + relayer_at_target: sp_keyring::Sr25519Keyring, call: RuntimeCall, ) -> sp_runtime::DispatchOutcome { let xt = construct_extrinsic(relayer_at_target, call); @@ -525,3 +524,13 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml index 9cb24a2b2820ea50dd3fab96b11fb0a198ee1bc0..9c5c7c5139096782c6749bc72a987fa9d2cd0663 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml @@ -5,18 +5,22 @@ authors.workspace = true edition.workspace = true description = "Bridge hub common utilities" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } +cumulus-primitives-core = { workspace = true } frame-support = { workspace = true } +pallet-message-queue = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +snowbridge-core = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } -cumulus-primitives-core = { workspace = true } xcm = { workspace = true } -pallet-message-queue = { workspace = true } -snowbridge-core = { workspace = true } +xcm-builder = { workspace = true } +xcm-executor = { workspace = true } [features] default = ["std"] @@ -30,6 +34,8 @@ std = [ "sp-core/std", "sp-runtime/std", "sp-std/std", + "xcm-builder/std", + "xcm-executor/std", "xcm/std", ] @@ -39,4 +45,7 @@ runtime-benchmarks = [ "pallet-message-queue/runtime-benchmarks", "snowbridge-core/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/src/barriers.rs b/cumulus/parachains/runtimes/bridge-hubs/common/src/barriers.rs new file mode 100644 index 0000000000000000000000000000000000000000..6b5dee3de3842a3fec59568ebf0dee7445a27ada --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/common/src/barriers.rs @@ -0,0 +1,57 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 core::{marker::PhantomData, ops::ControlFlow}; +use cumulus_primitives_core::Weight; +use frame_support::traits::{Contains, ProcessMessageError}; +use xcm::prelude::{ExportMessage, Instruction, Location, NetworkId}; + +use xcm_builder::{CreateMatcher, MatchXcm}; +use xcm_executor::traits::{DenyExecution, Properties}; + +/// Deny execution if the message contains instruction `ExportMessage` with +/// a. origin is contained in `FromOrigin` (i.e.`FromOrigin::Contains(origin)`) +/// b. network is contained in `ToGlobalConsensus`, (i.e. `ToGlobalConsensus::contains(network)`) +pub struct DenyExportMessageFrom<FromOrigin, ToGlobalConsensus>( + PhantomData<(FromOrigin, ToGlobalConsensus)>, +); + +impl<FromOrigin, ToGlobalConsensus> DenyExecution + for DenyExportMessageFrom<FromOrigin, ToGlobalConsensus> +where + FromOrigin: Contains<Location>, + ToGlobalConsensus: Contains<NetworkId>, +{ + fn deny_execution<RuntimeCall>( + origin: &Location, + message: &mut [Instruction<RuntimeCall>], + _max_weight: Weight, + _properties: &mut Properties, + ) -> Result<(), ProcessMessageError> { + // This barrier only cares about messages with `origin` matching `FromOrigin`. + if !FromOrigin::contains(origin) { + return Ok(()) + } + message.matcher().match_next_inst_while( + |_| true, + |inst| match inst { + ExportMessage { network, .. } if ToGlobalConsensus::contains(network) => + Err(ProcessMessageError::Unsupported), + _ => Ok(ControlFlow::Continue(())), + }, + )?; + Ok(()) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/common/src/lib.rs index b806b8cdb22db5386baf53b302cc06902c06f215..dbd87bea2142f7ad9be8a45247c059cabcc8b04d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/common/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/common/src/lib.rs @@ -14,9 +14,11 @@ // limitations under the License. #![cfg_attr(not(feature = "std"), no_std)] +pub mod barriers; pub mod digest_item; pub mod message_queue; pub mod xcm_version; +pub use barriers::DenyExportMessageFrom; pub use digest_item::CustomDigestItem; pub use message_queue::{AggregateMessageOrigin, BridgeHubMessageRouter}; diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/common/tests/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..84c135728d5dce17cbc8ef52dc0bedd9b20e304a --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/common/tests/tests.rs @@ -0,0 +1,85 @@ +// 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 <http://www.gnu.org/licenses/>. + +#![cfg(test)] +use bridge_hub_common::DenyExportMessageFrom; +use frame_support::{ + parameter_types, + traits::{Equals, EverythingBut, ProcessMessageError::Unsupported}, +}; +use xcm::prelude::{ + AliasOrigin, ByGenesis, ExportMessage, Here, Instruction, Location, NetworkId, Parachain, + Weight, +}; +use xcm_executor::traits::{DenyExecution, Properties}; + +#[test] +fn test_deny_export_message_from() { + parameter_types! { + pub Source1: Location = Location::new(1, Parachain(1)); + pub Source2: Location = Location::new(1, Parachain(2)); + pub Remote1: NetworkId = ByGenesis([1;32]); + pub Remote2: NetworkId = ByGenesis([2;32]); + } + + // Deny ExportMessage when both of the conditions met: + // 1: source != Source1 + // 2: network == Remote1 + pub type Denied = DenyExportMessageFrom<EverythingBut<Equals<Source1>>, Equals<Remote1>>; + + let assert_deny_execution = |mut xcm: Vec<Instruction<()>>, origin, expected_result| { + assert_eq!( + Denied::deny_execution( + &origin, + &mut xcm, + Weight::zero(), + &mut Properties { weight_credit: Weight::zero(), message_id: None } + ), + expected_result + ); + }; + + // A message without an `ExportMessage` should pass + assert_deny_execution(vec![AliasOrigin(Here.into())], Source1::get(), Ok(())); + + // `ExportMessage` from source1 and remote1 should pass + assert_deny_execution( + vec![ExportMessage { network: Remote1::get(), destination: Here, xcm: Default::default() }], + Source1::get(), + Ok(()), + ); + + // `ExportMessage` from source1 and remote2 should pass + assert_deny_execution( + vec![ExportMessage { network: Remote2::get(), destination: Here, xcm: Default::default() }], + Source1::get(), + Ok(()), + ); + + // `ExportMessage` from source2 and remote2 should pass + assert_deny_execution( + vec![ExportMessage { network: Remote2::get(), destination: Here, xcm: Default::default() }], + Source2::get(), + Ok(()), + ); + + // `ExportMessage` from source2 and remote1 should be banned + assert_deny_execution( + vec![ExportMessage { network: Remote1::get(), destination: Here, xcm: Default::default() }], + Source2::get(), + Err(Unsupported), + ); +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml index 915b3090092f3c16f126ca6f86e700bd8e6ebdc1..dc390d48cc777f8e5369da174d5ffe404d487542 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Utils for BridgeHub testing" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -17,14 +19,15 @@ log = { workspace = true } # Substrate frame-support = { workspace = true } frame-system = { workspace = true } +pallet-balances = { workspace = true } +pallet-timestamp = { workspace = true } +pallet-utility = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-keyring = { workspace = true, default-features = true } sp-runtime = { workspace = true } +sp-std = { workspace = true } sp-tracing = { workspace = true, default-features = true } -pallet-balances = { workspace = true } -pallet-utility = { workspace = true } -pallet-timestamp = { workspace = true } # Cumulus asset-test-utils = { workspace = true, default-features = true } @@ -34,10 +37,10 @@ parachains-common = { workspace = true } parachains-runtimes-test-utils = { workspace = true } # Polkadot +pallet-xcm = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } -pallet-xcm = { workspace = true } # Bridges bp-header-chain = { workspace = true } @@ -47,13 +50,11 @@ bp-polkadot-core = { workspace = true } bp-relayers = { workspace = true } bp-runtime = { workspace = true } bp-test-utils = { workspace = true } -bp-xcm-bridge-hub = { workspace = true } pallet-bridge-grandpa = { workspace = true } -pallet-bridge-parachains = { workspace = true } pallet-bridge-messages = { features = ["test-helpers"], workspace = true } +pallet-bridge-parachains = { workspace = true } pallet-bridge-relayers = { workspace = true } pallet-xcm-bridge-hub = { workspace = true } -bridge-runtime-common = { workspace = true } [features] default = ["std"] @@ -66,8 +67,6 @@ std = [ "bp-relayers/std", "bp-runtime/std", "bp-test-utils/std", - "bp-xcm-bridge-hub/std", - "bridge-runtime-common/std", "codec/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-xcmp-queue/std", @@ -88,6 +87,7 @@ std = [ "sp-core/std", "sp-io/std", "sp-runtime/std", + "sp-std/std", "xcm-builder/std", "xcm-executor/std", "xcm/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs index bc28df0eb829cc02ba455b363ce6bea51d702950..240aac6c406350b0f1fbb59ddd3e9315af677099 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs @@ -24,7 +24,7 @@ extern crate alloc; pub use bp_test_utils::test_header; pub use parachains_runtimes_test_utils::*; use sp_runtime::Perbill; -pub use test_cases::helpers::{ +pub use test_cases::helpers::for_pallet_xcm_bridge_hub::{ ensure_opened_bridge, open_bridge_with_extrinsic, open_bridge_with_storage, }; diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs index 320f3030b60a63e362bf88f467ac09afb78ff64b..4a7975b2d9f288b1416e2d678a6af4130ecdfee2 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs @@ -20,13 +20,13 @@ use crate::{ test_cases::{bridges_prelude::*, helpers, run_test}, test_data, + test_data::XcmAsPlainPayload, }; use alloc::{boxed::Box, vec}; use bp_header_chain::ChainWithGrandpa; use bp_messages::UnrewardedRelayersState; use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; -use bp_xcm_bridge_hub::XcmAsPlainPayload; use frame_support::traits::{OnFinalize, OnInitialize}; use frame_system::pallet_prelude::BlockNumberFor; use pallet_bridge_messages::{BridgedChainOf, LaneIdOf, ThisChainOf}; @@ -34,7 +34,7 @@ use parachains_runtimes_test_utils::{ AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations, }; use sp_core::Get; -use sp_keyring::AccountKeyring::*; +use sp_keyring::Sr25519Keyring::*; use sp_runtime::{traits::Header as HeaderT, AccountId32}; use xcm::latest::prelude::*; @@ -103,7 +103,7 @@ pub fn relayed_incoming_message_works<RuntimeHelper>( local_relay_chain_id: NetworkId, prepare_configuration: impl Fn() -> LaneIdOf<RuntimeHelper::Runtime, RuntimeHelper::MPI>, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, RuntimeCallOf<RuntimeHelper::Runtime>, ) -> sp_runtime::DispatchOutcome, expect_rewards: bool, @@ -210,7 +210,7 @@ pub fn free_relay_extrinsic_works<RuntimeHelper>( local_relay_chain_id: NetworkId, prepare_configuration: impl Fn() -> LaneIdOf<RuntimeHelper::Runtime, RuntimeHelper::MPI>, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, RuntimeCallOf<RuntimeHelper::Runtime>, ) -> sp_runtime::DispatchOutcome, expect_rewards: bool, @@ -344,7 +344,7 @@ pub fn complex_relay_extrinsic_works<RuntimeHelper>( local_relay_chain_id: NetworkId, prepare_configuration: impl Fn() -> LaneIdOf<RuntimeHelper::Runtime, RuntimeHelper::MPI>, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, RuntimeCallOf<RuntimeHelper::Runtime>, ) -> sp_runtime::DispatchOutcome, ) where diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs index 1da901e0bcdf9e8da142fba80ad07d053f3fabe2..7e87d703888a374979e174b005d77bda80eca828 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs @@ -20,6 +20,7 @@ use crate::{ test_cases::{bridges_prelude::*, helpers, run_test}, test_data, + test_data::XcmAsPlainPayload, }; use alloc::{boxed::Box, vec}; @@ -28,7 +29,6 @@ use bp_messages::UnrewardedRelayersState; use bp_polkadot_core::parachains::ParaHash; use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; use bp_runtime::{Chain, Parachain}; -use bp_xcm_bridge_hub::XcmAsPlainPayload; use frame_support::traits::{OnFinalize, OnInitialize}; use frame_system::pallet_prelude::BlockNumberFor; use pallet_bridge_messages::{BridgedChainOf, LaneIdOf, ThisChainOf}; @@ -36,7 +36,7 @@ use parachains_runtimes_test_utils::{ AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations, }; use sp_core::Get; -use sp_keyring::AccountKeyring::*; +use sp_keyring::Sr25519Keyring::*; use sp_runtime::{traits::Header as HeaderT, AccountId32}; use xcm::latest::prelude::*; @@ -112,7 +112,7 @@ pub fn relayed_incoming_message_works<RuntimeHelper>( local_relay_chain_id: NetworkId, prepare_configuration: impl Fn() -> LaneIdOf<RuntimeHelper::Runtime, RuntimeHelper::MPI>, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, <RuntimeHelper::Runtime as frame_system::Config>::RuntimeCall, ) -> sp_runtime::DispatchOutcome, expect_rewards: bool, @@ -246,7 +246,7 @@ pub fn free_relay_extrinsic_works<RuntimeHelper>( local_relay_chain_id: NetworkId, prepare_configuration: impl Fn() -> LaneIdOf<RuntimeHelper::Runtime, RuntimeHelper::MPI>, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, <RuntimeHelper::Runtime as frame_system::Config>::RuntimeCall, ) -> sp_runtime::DispatchOutcome, expect_rewards: bool, @@ -414,7 +414,7 @@ pub fn complex_relay_extrinsic_works<RuntimeHelper>( local_relay_chain_id: NetworkId, prepare_configuration: impl Fn() -> LaneIdOf<RuntimeHelper::Runtime, RuntimeHelper::MPI>, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, <RuntimeHelper::Runtime as frame_system::Config>::RuntimeCall, ) -> sp_runtime::DispatchOutcome, ) where diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs index 03ddc4313b451a82813831d7348d40f60eada2f7..505babdb64155601eb65e032139c5f463def4c7a 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs @@ -23,7 +23,6 @@ use bp_messages::MessageNonce; use bp_polkadot_core::parachains::{ParaHash, ParaId}; use bp_relayers::RewardsAccountParams; use bp_runtime::Chain; -use bp_xcm_bridge_hub::BridgeLocations; use codec::Decode; use core::marker::PhantomData; use frame_support::{ @@ -39,7 +38,7 @@ use parachains_runtimes_test_utils::{ mock_open_hrmp_channel, AccountIdOf, CollatorSessionKeys, RuntimeCallOf, SlotDurations, }; use sp_core::Get; -use sp_keyring::AccountKeyring::*; +use sp_keyring::Sr25519Keyring::*; use sp_runtime::{traits::TrailingZeroInput, AccountId32}; use xcm::latest::prelude::*; use xcm_executor::traits::ConvertLocation; @@ -264,7 +263,7 @@ pub fn relayed_incoming_message_works<Runtime, AllPalletsWithoutSystem, MPI>( sibling_parachain_id: u32, local_relay_chain_id: NetworkId, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, RuntimeCallOf<Runtime>, ) -> sp_runtime::DispatchOutcome, prepare_message_proof_import: impl FnOnce( @@ -374,9 +373,9 @@ pub fn relayed_incoming_message_works<Runtime, AllPalletsWithoutSystem, MPI>( /// Execute every call and verify its outcome. fn execute_and_verify_calls<Runtime: frame_system::Config>( - submitter: sp_keyring::AccountKeyring, + submitter: sp_keyring::Sr25519Keyring, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, RuntimeCallOf<Runtime>, ) -> sp_runtime::DispatchOutcome, calls_and_verifiers: CallsAndVerifiers<Runtime>, @@ -388,203 +387,210 @@ fn execute_and_verify_calls<Runtime: frame_system::Config>( } } -/// Helper function to open the bridge/lane for `source` and `destination` while ensuring all -/// required balances are placed into the SA of the source. -pub fn ensure_opened_bridge< - Runtime, - XcmOverBridgePalletInstance, - LocationToAccountId, - TokenLocation> -(source: Location, destination: InteriorLocation, is_paid_xcm_execution: bool, bridge_opener: impl Fn(BridgeLocations, Option<Asset>)) -> (BridgeLocations, pallet_xcm_bridge_hub::LaneIdOf<Runtime, XcmOverBridgePalletInstance>) -where - Runtime: BasicParachainRuntime + BridgeXcmOverBridgeConfig<XcmOverBridgePalletInstance>, - XcmOverBridgePalletInstance: 'static, - <Runtime as frame_system::Config>::RuntimeCall: GetDispatchInfo + From<BridgeXcmOverBridgeCall<Runtime, XcmOverBridgePalletInstance>>, - <Runtime as pallet_balances::Config>::Balance: From<<<Runtime as pallet_bridge_messages::Config<<Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::BridgeMessagesPalletInstance>>::ThisChain as bp_runtime::Chain>::Balance>, - <Runtime as pallet_balances::Config>::Balance: From<u128>, - LocationToAccountId: ConvertLocation<AccountIdOf<Runtime>>, -TokenLocation: Get<Location>{ - // construct expected bridge configuration - let locations = - pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::bridge_locations( - source.clone().into(), - destination.clone().into(), +pub(crate) mod for_pallet_xcm_bridge_hub { + use super::{super::for_pallet_xcm_bridge_hub::*, *}; + + /// Helper function to open the bridge/lane for `source` and `destination` while ensuring all + /// required balances are placed into the SA of the source. + pub fn ensure_opened_bridge< + Runtime, + XcmOverBridgePalletInstance, + LocationToAccountId, + TokenLocation> + (source: Location, destination: InteriorLocation, is_paid_xcm_execution: bool, bridge_opener: impl Fn(pallet_xcm_bridge_hub::BridgeLocations, Option<Asset>)) -> (pallet_xcm_bridge_hub::BridgeLocations, pallet_xcm_bridge_hub::LaneIdOf<Runtime, XcmOverBridgePalletInstance>) + where + Runtime: BasicParachainRuntime + BridgeXcmOverBridgeConfig<XcmOverBridgePalletInstance>, + XcmOverBridgePalletInstance: 'static, + <Runtime as frame_system::Config>::RuntimeCall: GetDispatchInfo + From<BridgeXcmOverBridgeCall<Runtime, XcmOverBridgePalletInstance>>, + <Runtime as pallet_balances::Config>::Balance: From<<<Runtime as pallet_bridge_messages::Config<<Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::BridgeMessagesPalletInstance>>::ThisChain as bp_runtime::Chain>::Balance>, + <Runtime as pallet_balances::Config>::Balance: From<u128>, + LocationToAccountId: ConvertLocation<AccountIdOf<Runtime>>, + TokenLocation: Get<Location> + { + // construct expected bridge configuration + let locations = + pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::bridge_locations( + source.clone().into(), + destination.clone().into(), + ) + .expect("valid bridge locations"); + assert!(pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get( + locations.bridge_id() ) - .expect("valid bridge locations"); - assert!(pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get( - locations.bridge_id() - ) - .is_none()); + .is_none()); + + // SA of source location needs to have some required balance + if !<Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::AllowWithoutBridgeDeposit::contains(&source) { + // required balance: ED + fee + BridgeDeposit + let bridge_deposit = + <Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::BridgeDeposit::get(); + let balance_needed = <Runtime as pallet_balances::Config>::ExistentialDeposit::get() + bridge_deposit.into(); + + let source_account_id = LocationToAccountId::convert_location(&source).expect("valid location"); + let _ = <pallet_balances::Pallet<Runtime>>::mint_into(&source_account_id, balance_needed) + .expect("mint_into passes"); + }; + + let maybe_paid_execution = if is_paid_xcm_execution { + // random high enough value for `BuyExecution` fees + let buy_execution_fee_amount = 5_000_000_000_000_u128; + let buy_execution_fee = (TokenLocation::get(), buy_execution_fee_amount).into(); + + let balance_needed = <Runtime as pallet_balances::Config>::ExistentialDeposit::get() + + buy_execution_fee_amount.into(); + let source_account_id = + LocationToAccountId::convert_location(&source).expect("valid location"); + let _ = + <pallet_balances::Pallet<Runtime>>::mint_into(&source_account_id, balance_needed) + .expect("mint_into passes"); + Some(buy_execution_fee) + } else { + None + }; + + // call the bridge opener + bridge_opener(*locations.clone(), maybe_paid_execution); + + // check opened bridge + let bridge = pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get( + locations.bridge_id(), + ) + .expect("opened bridge"); - // SA of source location needs to have some required balance - if !<Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::AllowWithoutBridgeDeposit::contains(&source) { - // required balance: ED + fee + BridgeDeposit - let bridge_deposit = - <Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::BridgeDeposit::get( - ); - let balance_needed = <Runtime as pallet_balances::Config>::ExistentialDeposit::get() + bridge_deposit.into(); - - let source_account_id = LocationToAccountId::convert_location(&source).expect("valid location"); - let _ = <pallet_balances::Pallet<Runtime>>::mint_into(&source_account_id, balance_needed) - .expect("mint_into passes"); - }; - - let maybe_paid_execution = if is_paid_xcm_execution { - // random high enough value for `BuyExecution` fees - let buy_execution_fee_amount = 5_000_000_000_000_u128; - let buy_execution_fee = (TokenLocation::get(), buy_execution_fee_amount).into(); - - let balance_needed = <Runtime as pallet_balances::Config>::ExistentialDeposit::get() + - buy_execution_fee_amount.into(); - let source_account_id = - LocationToAccountId::convert_location(&source).expect("valid location"); - let _ = <pallet_balances::Pallet<Runtime>>::mint_into(&source_account_id, balance_needed) - .expect("mint_into passes"); - Some(buy_execution_fee) - } else { - None - }; - - // call the bridge opener - bridge_opener(*locations.clone(), maybe_paid_execution); - - // check opened bridge - let bridge = pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get( - locations.bridge_id(), - ) - .expect("opened bridge"); + // check state + assert_ok!( + pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::do_try_state() + ); - // check state - assert_ok!( - pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::do_try_state() - ); + // return locations + (*locations, bridge.lane_id) + } - // return locations - (*locations, bridge.lane_id) -} + /// Utility for opening bridge with dedicated `pallet_xcm_bridge_hub`'s extrinsic. + pub fn open_bridge_with_extrinsic<Runtime, XcmOverBridgePalletInstance>( + (origin, origin_kind): (Location, OriginKind), + bridge_destination_universal_location: InteriorLocation, + maybe_paid_execution: Option<Asset>, + ) where + Runtime: frame_system::Config + + pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance> + + cumulus_pallet_parachain_system::Config + + pallet_xcm::Config, + XcmOverBridgePalletInstance: 'static, + <Runtime as frame_system::Config>::RuntimeCall: + GetDispatchInfo + From<BridgeXcmOverBridgeCall<Runtime, XcmOverBridgePalletInstance>>, + { + // open bridge with `Transact` call + let open_bridge_call = RuntimeCallOf::<Runtime>::from(BridgeXcmOverBridgeCall::< + Runtime, + XcmOverBridgePalletInstance, + >::open_bridge { + bridge_destination_universal_location: Box::new( + bridge_destination_universal_location.clone().into(), + ), + }); + + // execute XCM as source origin would do with `Transact -> Origin::Xcm` + assert_ok!(RuntimeHelper::<Runtime>::execute_as_origin( + (origin, origin_kind), + open_bridge_call, + maybe_paid_execution + ) + .ensure_complete()); + } -/// Utility for opening bridge with dedicated `pallet_xcm_bridge_hub`'s extrinsic. -pub fn open_bridge_with_extrinsic<Runtime, XcmOverBridgePalletInstance>( - (origin, origin_kind): (Location, OriginKind), - bridge_destination_universal_location: InteriorLocation, - maybe_paid_execution: Option<Asset>, -) where - Runtime: frame_system::Config - + pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance> - + cumulus_pallet_parachain_system::Config - + pallet_xcm::Config, - XcmOverBridgePalletInstance: 'static, - <Runtime as frame_system::Config>::RuntimeCall: - GetDispatchInfo + From<BridgeXcmOverBridgeCall<Runtime, XcmOverBridgePalletInstance>>, -{ - // open bridge with `Transact` call - let open_bridge_call = RuntimeCallOf::<Runtime>::from(BridgeXcmOverBridgeCall::< - Runtime, - XcmOverBridgePalletInstance, - >::open_bridge { - bridge_destination_universal_location: Box::new( - bridge_destination_universal_location.clone().into(), - ), - }); - - // execute XCM as source origin would do with `Transact -> Origin::Xcm` - assert_ok!(RuntimeHelper::<Runtime>::execute_as_origin( - (origin, origin_kind), - open_bridge_call, - maybe_paid_execution - ) - .ensure_complete()); -} + /// Utility for opening bridge directly inserting data to the `pallet_xcm_bridge_hub`'s storage + /// (used only for legacy purposes). + pub fn open_bridge_with_storage<Runtime, XcmOverBridgePalletInstance>( + locations: pallet_xcm_bridge_hub::BridgeLocations, + lane_id: pallet_xcm_bridge_hub::LaneIdOf<Runtime, XcmOverBridgePalletInstance>, + ) where + Runtime: pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>, + XcmOverBridgePalletInstance: 'static, + { + // insert bridge data directly to the storage + assert_ok!( + pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::do_open_bridge( + Box::new(locations), + lane_id, + true + ) + ); + } -/// Utility for opening bridge directly inserting data to the storage (used only for legacy -/// purposes). -pub fn open_bridge_with_storage<Runtime, XcmOverBridgePalletInstance>( - locations: BridgeLocations, - lane_id: pallet_xcm_bridge_hub::LaneIdOf<Runtime, XcmOverBridgePalletInstance>, -) where - Runtime: pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>, - XcmOverBridgePalletInstance: 'static, -{ - // insert bridge data directly to the storage - assert_ok!( - pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::do_open_bridge( - Box::new(locations), - lane_id, - true + /// Helper function to close the bridge/lane for `source` and `destination`. + pub fn close_bridge<Runtime, XcmOverBridgePalletInstance, LocationToAccountId, TokenLocation>( + expected_source: Location, + bridge_destination_universal_location: InteriorLocation, + (origin, origin_kind): (Location, OriginKind), + is_paid_xcm_execution: bool + ) where + Runtime: BasicParachainRuntime + BridgeXcmOverBridgeConfig<XcmOverBridgePalletInstance>, + XcmOverBridgePalletInstance: 'static, + <Runtime as frame_system::Config>::RuntimeCall: GetDispatchInfo + From<BridgeXcmOverBridgeCall<Runtime, XcmOverBridgePalletInstance>>, + <Runtime as pallet_balances::Config>::Balance: From<<<Runtime as pallet_bridge_messages::Config<<Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::BridgeMessagesPalletInstance>>::ThisChain as bp_runtime::Chain>::Balance>, + <Runtime as pallet_balances::Config>::Balance: From<u128>, + LocationToAccountId: ConvertLocation<AccountIdOf<Runtime>>, + TokenLocation: Get<Location> + { + // construct expected bridge configuration + let locations = + pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::bridge_locations( + expected_source.clone().into(), + bridge_destination_universal_location.clone().into(), + ) + .expect("valid bridge locations"); + assert!(pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get( + locations.bridge_id() ) - ); -} + .is_some()); -/// Helper function to close the bridge/lane for `source` and `destination`. -pub fn close_bridge<Runtime, XcmOverBridgePalletInstance, LocationToAccountId, TokenLocation>( - expected_source: Location, - bridge_destination_universal_location: InteriorLocation, - (origin, origin_kind): (Location, OriginKind), - is_paid_xcm_execution: bool -) where - Runtime: BasicParachainRuntime + BridgeXcmOverBridgeConfig<XcmOverBridgePalletInstance>, - XcmOverBridgePalletInstance: 'static, - <Runtime as frame_system::Config>::RuntimeCall: GetDispatchInfo + From<BridgeXcmOverBridgeCall<Runtime, XcmOverBridgePalletInstance>>, - <Runtime as pallet_balances::Config>::Balance: From<<<Runtime as pallet_bridge_messages::Config<<Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::BridgeMessagesPalletInstance>>::ThisChain as bp_runtime::Chain>::Balance>, - <Runtime as pallet_balances::Config>::Balance: From<u128>, - LocationToAccountId: ConvertLocation<AccountIdOf<Runtime>>, -TokenLocation: Get<Location>{ - // construct expected bridge configuration - let locations = - pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::bridge_locations( - expected_source.clone().into(), - bridge_destination_universal_location.clone().into(), + // required balance: ED + fee + BridgeDeposit + let maybe_paid_execution = if is_paid_xcm_execution { + // random high enough value for `BuyExecution` fees + let buy_execution_fee_amount = 2_500_000_000_000_u128; + let buy_execution_fee = (TokenLocation::get(), buy_execution_fee_amount).into(); + + let balance_needed = <Runtime as pallet_balances::Config>::ExistentialDeposit::get() + + buy_execution_fee_amount.into(); + let source_account_id = + LocationToAccountId::convert_location(&expected_source).expect("valid location"); + let _ = + <pallet_balances::Pallet<Runtime>>::mint_into(&source_account_id, balance_needed) + .expect("mint_into passes"); + Some(buy_execution_fee) + } else { + None + }; + + // close bridge with `Transact` call + let close_bridge_call = RuntimeCallOf::<Runtime>::from(BridgeXcmOverBridgeCall::< + Runtime, + XcmOverBridgePalletInstance, + >::close_bridge { + bridge_destination_universal_location: Box::new( + bridge_destination_universal_location.into(), + ), + may_prune_messages: 16, + }); + + // execute XCM as source origin would do with `Transact -> Origin::Xcm` + assert_ok!(RuntimeHelper::<Runtime>::execute_as_origin( + (origin, origin_kind), + close_bridge_call, + maybe_paid_execution ) - .expect("valid bridge locations"); - assert!(pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get( - locations.bridge_id() - ) - .is_some()); - - // required balance: ED + fee + BridgeDeposit - let maybe_paid_execution = if is_paid_xcm_execution { - // random high enough value for `BuyExecution` fees - let buy_execution_fee_amount = 2_500_000_000_000_u128; - let buy_execution_fee = (TokenLocation::get(), buy_execution_fee_amount).into(); - - let balance_needed = <Runtime as pallet_balances::Config>::ExistentialDeposit::get() + - buy_execution_fee_amount.into(); - let source_account_id = - LocationToAccountId::convert_location(&expected_source).expect("valid location"); - let _ = <pallet_balances::Pallet<Runtime>>::mint_into(&source_account_id, balance_needed) - .expect("mint_into passes"); - Some(buy_execution_fee) - } else { - None - }; - - // close bridge with `Transact` call - let close_bridge_call = RuntimeCallOf::<Runtime>::from(BridgeXcmOverBridgeCall::< - Runtime, - XcmOverBridgePalletInstance, - >::close_bridge { - bridge_destination_universal_location: Box::new( - bridge_destination_universal_location.into(), - ), - may_prune_messages: 16, - }); - - // execute XCM as source origin would do with `Transact -> Origin::Xcm` - assert_ok!(RuntimeHelper::<Runtime>::execute_as_origin( - (origin, origin_kind), - close_bridge_call, - maybe_paid_execution - ) - .ensure_complete()); + .ensure_complete()); - // bridge is closed - assert!(pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get( - locations.bridge_id() - ) - .is_none()); + // bridge is closed + assert!(pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get( + locations.bridge_id() + ) + .is_none()); - // check state - assert_ok!( - pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::do_try_state() - ); + // check state + assert_ok!( + pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::do_try_state() + ); + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs index f96d0bf405b9cdf60cef53f9b2b858d4805b4981..fa0229ce06881a2193ac36d8e05240dc2e149744 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs @@ -32,7 +32,6 @@ use bp_messages::{ LaneState, MessageKey, MessagesOperatingMode, OutboundLaneData, }; use bp_runtime::BasicOperatingMode; -use bp_xcm_bridge_hub::{Bridge, BridgeState, XcmAsPlainPayload}; use codec::Encode; use frame_support::{ assert_ok, @@ -63,12 +62,11 @@ pub(crate) mod bridges_prelude { pub use pallet_bridge_parachains::{ Call as BridgeParachainsCall, Config as BridgeParachainsConfig, }; - pub use pallet_xcm_bridge_hub::{ - Call as BridgeXcmOverBridgeCall, Config as BridgeXcmOverBridgeConfig, LanesManagerOf, - XcmBlobMessageDispatchResult, - }; } +// Re-export test-case +pub use for_pallet_xcm_bridge_hub::open_and_close_bridge_works; + // Re-export test_case from assets pub use asset_test_utils::include_teleports_for_native_asset_works; use pallet_bridge_messages::LaneIdOf; @@ -77,7 +75,6 @@ pub type RuntimeHelper<Runtime, AllPalletsWithoutSystem = ()> = parachains_runtimes_test_utils::RuntimeHelper<Runtime, AllPalletsWithoutSystem>; // Re-export test_case from `parachains-runtimes-test-utils` -use crate::test_cases::helpers::open_bridge_with_extrinsic; pub use parachains_runtimes_test_utils::test_cases::{ change_storage_constant_by_governance_works, set_storage_keys_by_governance_works, }; @@ -439,7 +436,7 @@ pub fn message_dispatch_routing_works< ) where Runtime: BasicParachainRuntime + cumulus_pallet_xcmp_queue::Config - + BridgeMessagesConfig<MessagesPalletInstance, InboundPayload = XcmAsPlainPayload>, + + BridgeMessagesConfig<MessagesPalletInstance, InboundPayload = test_data::XcmAsPlainPayload>, AllPalletsWithoutSystem: OnInitialize<BlockNumberFor<Runtime>> + OnFinalize<BlockNumberFor<Runtime>>, AccountIdOf<Runtime>: From<AccountId32> @@ -459,9 +456,15 @@ pub fn message_dispatch_routing_works< Location::new(C::get(), [GlobalConsensus(N::get())]) } } - assert_ne!(runtime_para_id, sibling_parachain_id); + #[derive(Debug)] + enum XcmBlobMessageDispatchResult { + Dispatched, + #[allow(dead_code)] + NotDispatched(Option<DispatchBlobError>), + } + run_test::<Runtime, _>(collator_session_key, runtime_para_id, vec![], || { prepare_configuration(); @@ -650,139 +653,150 @@ where estimated_fee.into() } -/// Test-case makes sure that `Runtime` can open/close bridges. -pub fn open_and_close_bridge_works<Runtime, XcmOverBridgePalletInstance, LocationToAccountId, TokenLocation>( - collator_session_key: CollatorSessionKeys<Runtime>, - runtime_para_id: u32, - expected_source: Location, - destination: InteriorLocation, - origin_with_origin_kind: (Location, OriginKind), - is_paid_xcm_execution: bool, -) where - Runtime: BasicParachainRuntime + BridgeXcmOverBridgeConfig<XcmOverBridgePalletInstance>, - XcmOverBridgePalletInstance: 'static, - <Runtime as frame_system::Config>::RuntimeCall: GetDispatchInfo + From<BridgeXcmOverBridgeCall<Runtime, XcmOverBridgePalletInstance>>, - <Runtime as pallet_balances::Config>::Balance: From<<<Runtime as pallet_bridge_messages::Config<<Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::BridgeMessagesPalletInstance>>::ThisChain as bp_runtime::Chain>::Balance>, - <Runtime as pallet_balances::Config>::Balance: From<u128>, - <<Runtime as pallet_bridge_messages::Config<<Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::BridgeMessagesPalletInstance>>::ThisChain as bp_runtime::Chain>::AccountId: From<<Runtime as frame_system::Config>::AccountId>, - LocationToAccountId: ConvertLocation<AccountIdOf<Runtime>>, - TokenLocation: Get<Location>, -{ - run_test::<Runtime, _>(collator_session_key, runtime_para_id, vec![], || { - // construct expected bridge configuration - let locations = pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::bridge_locations( - expected_source.clone().into(), - destination.clone().into(), - ).expect("valid bridge locations"); - let expected_lane_id = - locations.calculate_lane_id(xcm::latest::VERSION).expect("valid laneId"); - let lanes_manager = LanesManagerOf::<Runtime, XcmOverBridgePalletInstance>::new(); - - let expected_deposit = if <Runtime as pallet_xcm_bridge_hub::Config< - XcmOverBridgePalletInstance, - >>::AllowWithoutBridgeDeposit::contains( - locations.bridge_origin_relative_location() - ) { - Zero::zero() - } else { - <Runtime as pallet_xcm_bridge_hub::Config< +pub(crate) mod for_pallet_xcm_bridge_hub { + use super::*; + use crate::test_cases::helpers::for_pallet_xcm_bridge_hub::{ + close_bridge, ensure_opened_bridge, open_bridge_with_extrinsic, + }; + pub(crate) use pallet_xcm_bridge_hub::{ + Bridge, BridgeState, Call as BridgeXcmOverBridgeCall, Config as BridgeXcmOverBridgeConfig, + LanesManagerOf, + }; + + /// Test-case makes sure that `Runtime` can open/close bridges. + pub fn open_and_close_bridge_works<Runtime, XcmOverBridgePalletInstance, LocationToAccountId, TokenLocation>( + collator_session_key: CollatorSessionKeys<Runtime>, + runtime_para_id: u32, + expected_source: Location, + destination: InteriorLocation, + origin_with_origin_kind: (Location, OriginKind), + is_paid_xcm_execution: bool, + ) where + Runtime: BasicParachainRuntime + BridgeXcmOverBridgeConfig<XcmOverBridgePalletInstance>, + XcmOverBridgePalletInstance: 'static, + <Runtime as frame_system::Config>::RuntimeCall: GetDispatchInfo + From<BridgeXcmOverBridgeCall<Runtime, XcmOverBridgePalletInstance>>, + <Runtime as pallet_balances::Config>::Balance: From<<<Runtime as pallet_bridge_messages::Config<<Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::BridgeMessagesPalletInstance>>::ThisChain as bp_runtime::Chain>::Balance>, + <Runtime as pallet_balances::Config>::Balance: From<u128>, + <<Runtime as pallet_bridge_messages::Config<<Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::BridgeMessagesPalletInstance>>::ThisChain as bp_runtime::Chain>::AccountId: From<<Runtime as frame_system::Config>::AccountId>, + LocationToAccountId: ConvertLocation<AccountIdOf<Runtime>>, + TokenLocation: Get<Location>, + { + run_test::<Runtime, _>(collator_session_key, runtime_para_id, vec![], || { + // construct expected bridge configuration + let locations = pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::bridge_locations( + expected_source.clone().into(), + destination.clone().into(), + ).expect("valid bridge locations"); + let expected_lane_id = + locations.calculate_lane_id(xcm::latest::VERSION).expect("valid laneId"); + let lanes_manager = LanesManagerOf::<Runtime, XcmOverBridgePalletInstance>::new(); + + let expected_deposit = if <Runtime as pallet_xcm_bridge_hub::Config< XcmOverBridgePalletInstance, - >>::BridgeDeposit::get() - }; + >>::AllowWithoutBridgeDeposit::contains( + locations.bridge_origin_relative_location() + ) { + Zero::zero() + } else { + <Runtime as pallet_xcm_bridge_hub::Config< + XcmOverBridgePalletInstance, + >>::BridgeDeposit::get() + }; - // check bridge/lane DOES not exist - assert_eq!( - pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get( - locations.bridge_id() - ), - None - ); - assert_eq!( - lanes_manager.active_inbound_lane(expected_lane_id).map(drop), - Err(LanesManagerError::UnknownInboundLane) - ); - assert_eq!( - lanes_manager.active_outbound_lane(expected_lane_id).map(drop), - Err(LanesManagerError::UnknownOutboundLane) - ); + // check bridge/lane DOES not exist + assert_eq!( + pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get( + locations.bridge_id() + ), + None + ); + assert_eq!( + lanes_manager.active_inbound_lane(expected_lane_id).map(drop), + Err(LanesManagerError::UnknownInboundLane) + ); + assert_eq!( + lanes_manager.active_outbound_lane(expected_lane_id).map(drop), + Err(LanesManagerError::UnknownOutboundLane) + ); - // open bridge with Transact call - assert_eq!( - helpers::ensure_opened_bridge::< - Runtime, - XcmOverBridgePalletInstance, - LocationToAccountId, - TokenLocation, - >( - expected_source.clone(), - destination.clone(), - is_paid_xcm_execution, - |locations, maybe_paid_execution| open_bridge_with_extrinsic::< + // open bridge with Transact call + assert_eq!( + ensure_opened_bridge::< Runtime, XcmOverBridgePalletInstance, + LocationToAccountId, + TokenLocation, >( - origin_with_origin_kind.clone(), - locations.bridge_destination_universal_location().clone(), - maybe_paid_execution + expected_source.clone(), + destination.clone(), + is_paid_xcm_execution, + |locations, maybe_paid_execution| open_bridge_with_extrinsic::< + Runtime, + XcmOverBridgePalletInstance, + >( + origin_with_origin_kind.clone(), + locations.bridge_destination_universal_location().clone(), + maybe_paid_execution + ) ) - ) - .0 - .bridge_id(), - locations.bridge_id() - ); - - // check bridge/lane DOES exist - assert_eq!( - pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get( + .0 + .bridge_id(), locations.bridge_id() - ), - Some(Bridge { - bridge_origin_relative_location: Box::new(expected_source.clone().into()), - bridge_origin_universal_location: Box::new( - locations.bridge_origin_universal_location().clone().into() - ), - bridge_destination_universal_location: Box::new( - locations.bridge_destination_universal_location().clone().into() + ); + + // check bridge/lane DOES exist + assert_eq!( + pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get( + locations.bridge_id() ), - state: BridgeState::Opened, - bridge_owner_account: LocationToAccountId::convert_location(&expected_source) - .expect("valid location") - .into(), - deposit: expected_deposit, - lane_id: expected_lane_id - }) - ); - assert_eq!( - lanes_manager.active_inbound_lane(expected_lane_id).map(|lane| lane.state()), - Ok(LaneState::Opened) - ); - assert_eq!( - lanes_manager.active_outbound_lane(expected_lane_id).map(|lane| lane.state()), - Ok(LaneState::Opened) - ); + Some(Bridge { + bridge_origin_relative_location: Box::new(expected_source.clone().into()), + bridge_origin_universal_location: Box::new( + locations.bridge_origin_universal_location().clone().into() + ), + bridge_destination_universal_location: Box::new( + locations.bridge_destination_universal_location().clone().into() + ), + state: BridgeState::Opened, + bridge_owner_account: LocationToAccountId::convert_location(&expected_source) + .expect("valid location") + .into(), + deposit: expected_deposit, + lane_id: expected_lane_id, + }) + ); + assert_eq!( + lanes_manager.active_inbound_lane(expected_lane_id).map(|lane| lane.state()), + Ok(LaneState::Opened) + ); + assert_eq!( + lanes_manager.active_outbound_lane(expected_lane_id).map(|lane| lane.state()), + Ok(LaneState::Opened) + ); - // close bridge with Transact call - helpers::close_bridge::< - Runtime, - XcmOverBridgePalletInstance, - LocationToAccountId, - TokenLocation, - >(expected_source, destination, origin_with_origin_kind, is_paid_xcm_execution); + // close bridge with Transact call + close_bridge::<Runtime, XcmOverBridgePalletInstance, LocationToAccountId, TokenLocation>( + expected_source, + destination, + origin_with_origin_kind, + is_paid_xcm_execution, + ); - // check bridge/lane DOES not exist - assert_eq!( - pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get( - locations.bridge_id() - ), - None - ); - assert_eq!( - lanes_manager.active_inbound_lane(expected_lane_id).map(drop), - Err(LanesManagerError::UnknownInboundLane) - ); - assert_eq!( - lanes_manager.active_outbound_lane(expected_lane_id).map(drop), - Err(LanesManagerError::UnknownOutboundLane) - ); - }); + // check bridge/lane DOES not exist + assert_eq!( + pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get( + locations.bridge_id() + ), + None + ); + assert_eq!( + lanes_manager.active_inbound_lane(expected_lane_id).map(drop), + Err(LanesManagerError::UnknownInboundLane) + ); + assert_eq!( + lanes_manager.active_outbound_lane(expected_lane_id).map(drop), + Err(LanesManagerError::UnknownOutboundLane) + ); + }); + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs index 7461085330f27691b4da39e425aebcf64046641d..37605350b8e643a2de91b88dbfcc3a44d9e2d6da 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs @@ -16,7 +16,7 @@ //! Generating test data for bridges with remote GRANDPA chains. -use crate::test_data::prepare_inbound_xcm; +use crate::test_data::{prepare_inbound_xcm, XcmAsPlainPayload}; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, @@ -25,7 +25,6 @@ use bp_messages::{ }; use bp_runtime::{AccountIdOf, BlockNumberOf, Chain, HeaderOf, UnverifiedStorageProofParams}; use bp_test_utils::make_default_justification; -use bp_xcm_bridge_hub::XcmAsPlainPayload; use codec::Encode; use pallet_bridge_grandpa::{BridgedChain, BridgedHeader}; use sp_runtime::traits::Header as HeaderT; diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs index a6659b8241dfd50aedbfe14ac2672756fa0a03af..4d91c8215880037cfb39b4c0fff00046b0e29b9b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs @@ -16,7 +16,10 @@ //! Generating test data for bridges with remote parachains. -use super::{from_grandpa_chain::make_complex_bridged_grandpa_header_proof, prepare_inbound_xcm}; +use super::{ + from_grandpa_chain::make_complex_bridged_grandpa_header_proof, prepare_inbound_xcm, + XcmAsPlainPayload, +}; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, @@ -28,7 +31,6 @@ use bp_runtime::{ AccountIdOf, BlockNumberOf, Chain, HeaderOf, Parachain, UnverifiedStorageProofParams, }; use bp_test_utils::prepare_parachain_heads_proof; -use bp_xcm_bridge_hub::XcmAsPlainPayload; use codec::Encode; use pallet_bridge_grandpa::BridgedHeader; use sp_runtime::traits::Header as HeaderT; diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/mod.rs index c34188af506896d9fb4e18781b09d804077c3941..cef3c84b8178568e7a0ddb4939f76ca066c52655 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/mod.rs @@ -35,6 +35,8 @@ use xcm::GetVersion; use xcm_builder::{BridgeMessage, HaulBlob, HaulBlobError, HaulBlobExporter}; use xcm_executor::traits::{validate_export, ExportXcm}; +pub(crate) type XcmAsPlainPayload = sp_std::vec::Vec<u8>; + pub fn prepare_inbound_xcm(xcm_message: Xcm<()>, destination: InteriorLocation) -> Vec<u8> { let location = xcm::VersionedInteriorLocation::from(destination); let xcm = xcm::VersionedXcm::<()>::from(xcm_message); diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml index e03fc934ceaf62e6174a6f91c5040ba384549ec4..f9cc54495aea088bf3a309d83a43f4862e55db49 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" description = "Westend Collectives Parachain Runtime" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -24,15 +26,19 @@ frame-system = { workspace = true } frame-system-benchmarking = { optional = true, workspace = true } frame-system-rpc-runtime-api = { workspace = true } frame-try-runtime = { optional = true, workspace = true } -pallet-asset-rate = { workspace = true } pallet-alliance = { workspace = true } +pallet-asset-rate = { workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } pallet-collective = { workspace = true } +pallet-core-fellowship = { workspace = true } pallet-multisig = { workspace = true } pallet-preimage = { workspace = true } pallet-proxy = { workspace = true } +pallet-ranked-collective = { workspace = true } +pallet-referenda = { workspace = true } +pallet-salary = { workspace = true } pallet-scheduler = { workspace = true } pallet-session = { workspace = true } pallet-state-trie-migration = { workspace = true } @@ -41,18 +47,14 @@ pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-treasury = { workspace = true } pallet-utility = { workspace = true } -pallet-referenda = { workspace = true } -pallet-ranked-collective = { workspace = true } -pallet-core-fellowship = { workspace = true } -pallet-salary = { workspace = true } sp-api = { workspace = true } sp-arithmetic = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } -sp-keyring = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } +sp-keyring = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } @@ -63,25 +65,26 @@ sp-version = { workspace = true } # Polkadot pallet-xcm = { workspace = true } +pallet-xcm-benchmarks = { optional = true, workspace = true } polkadot-parachain-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } +westend-runtime-constants = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } -westend-runtime-constants = { workspace = true } xcm-runtime-apis = { workspace = true } # Cumulus cumulus-pallet-aura-ext = { workspace = true } -pallet-message-queue = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-utility = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } +pallet-message-queue = { workspace = true } pallet-collator-selection = { workspace = true } pallet-collective-content = { workspace = true } @@ -93,6 +96,7 @@ testnet-parachains-constants = { features = ["westend"], workspace = true } substrate-wasm-builder = { optional = true, workspace = true, default-features = true } [dev-dependencies] +parachains-runtimes-test-utils = { workspace = true, default-features = true } sp-io = { features = ["std"], workspace = true, default-features = true } [features] @@ -100,6 +104,7 @@ default = ["std"] runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -127,6 +132,7 @@ runtime-benchmarks = [ "pallet-transaction-payment/runtime-benchmarks", "pallet-treasury/runtime-benchmarks", "pallet-utility/runtime-benchmarks", + "pallet-xcm-benchmarks/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "parachains-common/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", @@ -135,10 +141,12 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", @@ -178,11 +186,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", @@ -216,6 +224,7 @@ std = [ "pallet-transaction-payment/std", "pallet-treasury/std", "pallet-utility/std", + "pallet-xcm-benchmarks?/std", "pallet-xcm/std", "parachain-info/std", "parachains-common/std", diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index 0ee3a40687180b7607a830ea0a000974c7377a49..5e087832f0e82922fc52ac58a6158da74e06fd08 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -126,7 +126,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("collectives-westend"), impl_name: alloc::borrow::Cow::Borrowed("collectives-westend"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, @@ -191,6 +191,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo<Runtime>; +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -669,6 +673,7 @@ construct_runtime!( ParachainSystem: cumulus_pallet_parachain_system = 1, Timestamp: pallet_timestamp = 2, ParachainInfo: parachain_info = 3, + WeightReclaim: cumulus_pallet_weight_reclaim = 4, // Monetary stuff. Balances: pallet_balances = 10, @@ -735,16 +740,19 @@ pub type SignedBlock = generic::SignedBlock<Block>; /// BlockId type as expected by this runtime. pub type BlockId = generic::BlockId<Block>; /// The extension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender<Runtime>, - frame_system::CheckSpecVersion<Runtime>, - frame_system::CheckTxVersion<Runtime>, - frame_system::CheckGenesis<Runtime>, - frame_system::CheckEra<Runtime>, - frame_system::CheckNonce<Runtime>, - frame_system::CheckWeight<Runtime>, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim<Runtime>, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckTxVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckEra<Runtime>, + frame_system::CheckNonce<Runtime>, + frame_system::CheckWeight<Runtime>, + ), +>; + /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, TxExtension>; @@ -762,6 +770,7 @@ type Migrations = ( pallet_core_fellowship::migration::MigrateV0ToV1<Runtime, FellowshipCoreInstance>, // unreleased pallet_core_fellowship::migration::MigrateV0ToV1<Runtime, AmbassadorCoreInstance>, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1<Runtime>, ); /// Executive: handles dispatch to the various modules. @@ -792,7 +801,6 @@ mod benches { [cumulus_pallet_xcmp_queue, XcmpQueue] [pallet_alliance, Alliance] [pallet_collective, AllianceMotion] - [pallet_xcm, PalletXcmExtrinsicsBenchmark::<Runtime>] [pallet_preimage, Preimage] [pallet_scheduler, Scheduler] [pallet_referenda, FellowshipReferenda] @@ -806,6 +814,12 @@ mod benches { [pallet_salary, AmbassadorSalary] [pallet_treasury, FellowshipTreasury] [pallet_asset_rate, AssetRate] + [cumulus_pallet_weight_reclaim, WeightReclaim] + // XCM + [pallet_xcm, PalletXcmExtrinsicsBenchmark::<Runtime>] + // NOTE: Make sure you point to the individual modules below. + [pallet_xcm_benchmarks::fungible, XcmBalances] + [pallet_xcm_benchmarks::generic, XcmGeneric] ); } @@ -963,7 +977,8 @@ impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> { - match asset.try_as::<AssetId>() { + let latest_asset_id: Result<AssetId, ()> = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::WndLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) @@ -1054,6 +1069,12 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; + // This is defined once again in dispatch_benchmark, because list_benchmarks! + // and add_benchmarks! are macros exported by define_benchmarks! macros and those types + // are referenced in that call. + type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::<Runtime>; + type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::<Runtime>; + let mut list = Vec::<BenchmarkList>::new(); list_benchmarks!(list, extra); @@ -1082,10 +1103,11 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} + use xcm_config::WndLocation; parameter_types! { pub ExistentialDepositAsset: Option<Asset> = Some(( - xcm_config::WndLocation::get(), + WndLocation::get(), ExistentialDeposit::get() ).into()); } @@ -1138,24 +1160,119 @@ impl_runtime_apis! { } } - let whitelist: Vec<TrackedStorageKey> = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + impl pallet_xcm_benchmarks::Config for Runtime { + type XcmConfig = xcm_config::XcmConfig; + type AccountIdConverter = xcm_config::LocationToAccountId; + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn valid_destination() -> Result<Location, BenchmarkError> { + Ok(WndLocation::get()) + } + fn worst_case_holding(_depositable_count: u32) -> Assets { + // just concrete assets according to relay chain. + let assets: Vec<Asset> = vec![ + Asset { + id: AssetId(WndLocation::get()), + fun: Fungible(1_000_000 * UNITS), + } + ]; + assets.into() + } + } + + parameter_types! { + pub const TrustedTeleporter: Option<(Location, Asset)> = Some(( + WndLocation::get(), + Asset { fun: Fungible(UNITS), id: AssetId(WndLocation::get()) }, + )); + pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; + pub const TrustedReserve: Option<(Location, Asset)> = None; + } + + impl pallet_xcm_benchmarks::fungible::Config for Runtime { + type TransactAsset = Balances; + + type CheckedAccount = CheckedAccount; + type TrustedTeleporter = TrustedTeleporter; + type TrustedReserve = TrustedReserve; + + fn get_asset() -> Asset { + Asset { + id: AssetId(WndLocation::get()), + fun: Fungible(UNITS), + } + } + } + + impl pallet_xcm_benchmarks::generic::Config for Runtime { + type TransactAsset = Balances; + type RuntimeCall = RuntimeCall; + + fn worst_case_response() -> (u64, Response) { + (0u64, Response::Version(Default::default())) + } + + fn worst_case_asset_exchange() -> Result<(Assets, Assets), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn universal_alias() -> Result<(Location, Junction), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn transact_origin_and_runtime_call() -> Result<(Location, RuntimeCall), BenchmarkError> { + Ok((WndLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) + } + + fn subscribe_origin() -> Result<Location, BenchmarkError> { + Ok(WndLocation::get()) + } + + fn claimable_asset() -> Result<(Location, Location, Assets), BenchmarkError> { + let origin = WndLocation::get(); + let assets: Assets = (AssetId(WndLocation::get()), 1_000 * UNITS).into(); + let ticket = Location { parents: 0, interior: Here }; + Ok((origin, ticket, assets)) + } + + fn fee_asset() -> Result<Asset, BenchmarkError> { + Ok(Asset { + id: AssetId(WndLocation::get()), + fun: Fungible(1_000_000 * UNITS), + }) + } + + fn unlockable_asset() -> Result<(Location, Location, Asset), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn export_message_origin_and_destination( + ) -> Result<(Location, NetworkId, InteriorLocation), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn alias_origin() -> Result<(Location, Location), BenchmarkError> { + // Any location can alias to an internal location. + // Here parachain 1000 aliases to an internal account. + let origin = Location::new(1, [Parachain(1000)]); + let target = Location::new(1, [Parachain(1000), AccountId32 { id: [128u8; 32], network: None }]); + Ok((origin, target)) + } + } + + type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::<Runtime>; + type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::<Runtime>; + + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec<TrackedStorageKey> = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::<BenchmarkBatch>::new(); let params = (&config, &whitelist); add_benchmarks!(params, batches); - if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) } Ok(batches) } } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 0000000000000000000000000000000000000000..c286ba132022791157e6bd9861375c62156edf61 --- /dev/null +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=collectives-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo<T>(PhantomData<T>); +impl<T: frame_system::Config> cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo<T> { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 6_745_000 picoseconds. + Weight::from_parts(6_948_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/frame_system_extensions.rs index f32f2730313570cad4a759e64b2156f30110f60b..8c2abcd4e8c88fa2e888eb3caca38b57f9bac5eb 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/frame_system_extensions.rs @@ -16,28 +16,26 @@ //! Autogenerated weights for `frame_system_extensions` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-21, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `gleipnir`, CPU: `AMD Ryzen 9 7900X 12-Core Processor` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/release/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* // --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=frame_system_extensions -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=2 -// --repeat=2 -// --json +// --chain=collectives-westend-dev // --header=./cumulus/file_header.txt // --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/ -// --chain=collectives-westend-dev #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,81 +48,92 @@ use core::marker::PhantomData; /// Weight functions for `frame_system_extensions`. pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo<T> { - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_genesis() -> Weight { // Proof Size summary in bytes: // Measured: `54` - // Estimated: `3509` - // Minimum execution time: 3_497_000 picoseconds. - Weight::from_parts(5_961_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 4_206_000 picoseconds. + Weight::from_parts(4_485_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_mortal_transaction() -> Weight { // Proof Size summary in bytes: // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 5_240_000 picoseconds. - Weight::from_parts(8_175_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 7_537_000 picoseconds. + Weight::from_parts(7_706_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_immortal_transaction() -> Weight { // Proof Size summary in bytes: // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 5_240_000 picoseconds. - Weight::from_parts(8_175_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 7_512_000 picoseconds. + Weight::from_parts(7_655_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } fn check_non_zero_sender() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 671_000 picoseconds. - Weight::from_parts(3_005_000, 0) + // Minimum execution time: 447_000 picoseconds. + Weight::from_parts(499_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn check_nonce() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_426_000 picoseconds. - Weight::from_parts(6_131_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `101` + // Estimated: `3593` + // Minimum execution time: 6_667_000 picoseconds. + Weight::from_parts(6_868_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } fn check_spec_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 501_000 picoseconds. - Weight::from_parts(2_715_000, 0) + // Minimum execution time: 389_000 picoseconds. + Weight::from_parts(420_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn check_tx_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 491_000 picoseconds. - Weight::from_parts(2_635_000, 0) + // Minimum execution time: 379_000 picoseconds. + Weight::from_parts(420_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `System::BlockWeight` (r:1 w:1) /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn check_weight() -> Weight { // Proof Size summary in bytes: // Measured: `24` // Estimated: `1533` - // Minimum execution time: 3_958_000 picoseconds. - Weight::from_parts(6_753_000, 0) + // Minimum execution time: 6_330_000 picoseconds. + Weight::from_parts(6_605_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1533` + // Minimum execution time: 2_784_000 picoseconds. + Weight::from_parts(2_960_000, 0) .saturating_add(Weight::from_parts(0, 1533)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs index 00b3bd92d5ef9ce0081ca9e95e43bd35098c6aa0..ce85d23b21cbb6c5b66b61d4f90705bf7ad07ab4 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs @@ -15,6 +15,7 @@ pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; @@ -46,6 +47,7 @@ pub mod pallet_utility; pub mod pallet_xcm; pub mod paritydb_weights; pub mod rocksdb_weights; +pub mod xcm; pub use block_weights::constants::BlockExecutionWeight; pub use extrinsic_weights::constants::ExtrinsicBaseWeight; diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_ambassador_core.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_ambassador_core.rs index 6bedfcc7e012383657a059b87942517644057d8a..4d092ec80313bc8fbd7d6efee143342f8ea19580 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_ambassador_core.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_ambassador_core.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,25 +16,29 @@ //! Autogenerated weights for `pallet_core_fellowship` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-08-11, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `cob`, CPU: `<UNKNOWN>` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `623e9e4b814e`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 // Executed Command: -// target/release/polkadot-parachain +// frame-omni-bencher +// v1 // benchmark // pallet -// --chain=collectives-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_core_fellowship // --extrinsic=* -// --steps=2 -// --repeat=2 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/collectives/collectives-polkadot/src/weights/ +// --runtime=target/production/wbuild/collectives-westend-runtime/collectives_westend_runtime.wasm +// --pallet=pallet_core_fellowship +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,25 +52,26 @@ use core::marker::PhantomData; pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo<T> { /// Storage: `AmbassadorCore::Params` (r:0 w:1) - /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) fn set_params() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_000_000 picoseconds. - Weight::from_parts(11_000_000, 0) + // Minimum execution time: 9_131_000 picoseconds. + Weight::from_parts(9_371_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `AmbassadorCore::Params` (r:0 w:1) - /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCore::Params` (r:1 w:1) + /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) fn set_partial_params() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 11_000_000 picoseconds. - Weight::from_parts(11_000_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `471` + // Estimated: `1853` + // Minimum execution time: 18_375_000 picoseconds. + Weight::from_parts(18_872_000, 0) + .saturating_add(Weight::from_parts(0, 1853)) + .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `AmbassadorCore::Member` (r:1 w:1) @@ -74,44 +79,48 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< /// Storage: `AmbassadorCollective::Members` (r:1 w:1) /// Proof: `AmbassadorCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCore::Params` (r:1 w:0) - /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCollective::MemberCount` (r:1 w:1) /// Proof: `AmbassadorCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) - /// Storage: `AmbassadorCollective::IdToIndex` (r:1 w:0) + /// Storage: `AmbassadorCollective::IdToIndex` (r:1 w:1) /// Proof: `AmbassadorCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCore::MemberEvidence` (r:1 w:1) /// Proof: `AmbassadorCore::MemberEvidence` (`max_values`: None, `max_size`: Some(65581), added: 68056, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCollective::IndexToId` (r:0 w:1) + /// Proof: `AmbassadorCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) fn bump_offboard() -> Weight { // Proof Size summary in bytes: - // Measured: `66011` + // Measured: `66402` // Estimated: `69046` - // Minimum execution time: 96_000_000 picoseconds. - Weight::from_parts(111_000_000, 0) + // Minimum execution time: 156_752_000 picoseconds. + Weight::from_parts(164_242_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `AmbassadorCore::Member` (r:1 w:1) /// Proof: `AmbassadorCore::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCollective::Members` (r:1 w:1) /// Proof: `AmbassadorCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCore::Params` (r:1 w:0) - /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCollective::MemberCount` (r:1 w:1) /// Proof: `AmbassadorCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) - /// Storage: `AmbassadorCollective::IdToIndex` (r:1 w:0) + /// Storage: `AmbassadorCollective::IdToIndex` (r:1 w:1) /// Proof: `AmbassadorCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCore::MemberEvidence` (r:1 w:1) /// Proof: `AmbassadorCore::MemberEvidence` (`max_values`: None, `max_size`: Some(65581), added: 68056, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCollective::IndexToId` (r:0 w:1) + /// Proof: `AmbassadorCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) fn bump_demote() -> Weight { // Proof Size summary in bytes: - // Measured: `66121` + // Measured: `66512` // Estimated: `69046` - // Minimum execution time: 99_000_000 picoseconds. - Weight::from_parts(116_000_000, 0) + // Minimum execution time: 158_877_000 picoseconds. + Weight::from_parts(165_228_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `AmbassadorCollective::Members` (r:1 w:0) /// Proof: `AmbassadorCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) @@ -121,8 +130,8 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `360` // Estimated: `3514` - // Minimum execution time: 21_000_000 picoseconds. - Weight::from_parts(22_000_000, 0) + // Minimum execution time: 25_056_000 picoseconds. + Weight::from_parts(26_028_000, 0) .saturating_add(Weight::from_parts(0, 3514)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -141,8 +150,8 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `118` // Estimated: `3514` - // Minimum execution time: 36_000_000 picoseconds. - Weight::from_parts(36_000_000, 0) + // Minimum execution time: 34_784_000 picoseconds. + Weight::from_parts(35_970_000, 0) .saturating_add(Weight::from_parts(0, 3514)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(5)) @@ -152,7 +161,7 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< /// Storage: `AmbassadorCore::Member` (r:1 w:1) /// Proof: `AmbassadorCore::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCore::Params` (r:1 w:0) - /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCollective::MemberCount` (r:1 w:1) /// Proof: `AmbassadorCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCore::MemberEvidence` (r:1 w:1) @@ -163,25 +172,40 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< /// Proof: `AmbassadorCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) fn promote() -> Weight { // Proof Size summary in bytes: - // Measured: `65989` + // Measured: `66055` // Estimated: `69046` - // Minimum execution time: 95_000_000 picoseconds. - Weight::from_parts(110_000_000, 0) + // Minimum execution time: 147_616_000 picoseconds. + Weight::from_parts(154_534_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(6)) } + /// Storage: `AmbassadorCollective::Members` (r:1 w:1) + /// Proof: `AmbassadorCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCore::Member` (r:1 w:1) + /// Proof: `AmbassadorCore::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCollective::MemberCount` (r:9 w:9) + /// Proof: `AmbassadorCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCore::MemberEvidence` (r:1 w:1) + /// Proof: `AmbassadorCore::MemberEvidence` (`max_values`: None, `max_size`: Some(65581), added: 68056, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCollective::IndexToId` (r:0 w:9) + /// Proof: `AmbassadorCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCollective::IdToIndex` (r:0 w:9) + /// Proof: `AmbassadorCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) + /// The range of component `r` is `[1, 9]`. + /// The range of component `r` is `[1, 9]`. fn promote_fast(r: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `16844` - // Estimated: `19894 + r * (2489 ±0)` - // Minimum execution time: 45_065_000 picoseconds. - Weight::from_parts(34_090_392, 19894) - // Standard Error: 18_620 - .saturating_add(Weight::from_parts(13_578_046, 0).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(3_u64)) + // Measured: `65968` + // Estimated: `69046 + r * (2489 ±0)` + // Minimum execution time: 138_323_000 picoseconds. + Weight::from_parts(125_497_264, 0) + .saturating_add(Weight::from_parts(0, 69046)) + // Standard Error: 56_050 + .saturating_add(Weight::from_parts(19_863_853, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 2489).saturating_mul(r.into())) } @@ -193,10 +217,10 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< /// Proof: `AmbassadorCore::MemberEvidence` (`max_values`: None, `max_size`: Some(65581), added: 68056, mode: `MaxEncodedLen`) fn offboard() -> Weight { // Proof Size summary in bytes: - // Measured: `331` + // Measured: `265` // Estimated: `3514` - // Minimum execution time: 21_000_000 picoseconds. - Weight::from_parts(22_000_000, 0) + // Minimum execution time: 26_903_000 picoseconds. + Weight::from_parts(27_645_000, 0) .saturating_add(Weight::from_parts(0, 3514)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -209,8 +233,22 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `285` // Estimated: `3514` - // Minimum execution time: 20_000_000 picoseconds. - Weight::from_parts(21_000_000, 0) + // Minimum execution time: 23_286_000 picoseconds. + Weight::from_parts(23_848_000, 0) + .saturating_add(Weight::from_parts(0, 3514)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AmbassadorCore::Member` (r:1 w:1) + /// Proof: `AmbassadorCore::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCollective::Members` (r:1 w:0) + /// Proof: `AmbassadorCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) + fn import_member() -> Weight { + // Proof Size summary in bytes: + // Measured: `285` + // Estimated: `3514` + // Minimum execution time: 23_239_000 picoseconds. + Weight::from_parts(23_684_000, 0) .saturating_add(Weight::from_parts(0, 3514)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -225,8 +263,8 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `65967` // Estimated: `69046` - // Minimum execution time: 78_000_000 picoseconds. - Weight::from_parts(104_000_000, 0) + // Minimum execution time: 125_987_000 picoseconds. + Weight::from_parts(130_625_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) @@ -239,8 +277,8 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `151` // Estimated: `69046` - // Minimum execution time: 43_000_000 picoseconds. - Weight::from_parts(44_000_000, 0) + // Minimum execution time: 104_431_000 picoseconds. + Weight::from_parts(106_646_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_fellowship_core.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_fellowship_core.rs index 05014e273f0009bf212969ba8705879747eedd75..acb1f82985db700ffe4d6f49bf15d85af50fd15a 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_fellowship_core.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_fellowship_core.rs @@ -1,39 +1,44 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see <http://www.gnu.org/licenses/>. //! Autogenerated weights for `pallet_core_fellowship` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-08-11, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `cob`, CPU: `<UNKNOWN>` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `623e9e4b814e`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 // Executed Command: -// target/release/polkadot-parachain +// frame-omni-bencher +// v1 // benchmark // pallet -// --chain=collectives-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_core_fellowship // --extrinsic=* -// --steps=2 -// --repeat=2 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/collectives/collectives-polkadot/src/weights/ +// --runtime=target/production/wbuild/collectives-westend-runtime/collectives_westend_runtime.wasm +// --pallet=pallet_core_fellowship +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -47,25 +52,26 @@ use core::marker::PhantomData; pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo<T> { /// Storage: `FellowshipCore::Params` (r:0 w:1) - /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) fn set_params() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_000_000 picoseconds. - Weight::from_parts(12_000_000, 0) + // Minimum execution time: 9_115_000 picoseconds. + Weight::from_parts(9_523_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `FellowshipCore::Params` (r:0 w:1) - /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCore::Params` (r:1 w:1) + /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) fn set_partial_params() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 11_000_000 picoseconds. - Weight::from_parts(12_000_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `504` + // Estimated: `1853` + // Minimum execution time: 18_294_000 picoseconds. + Weight::from_parts(18_942_000, 0) + .saturating_add(Weight::from_parts(0, 1853)) + .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `FellowshipCore::Member` (r:1 w:1) @@ -73,44 +79,48 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< /// Storage: `FellowshipCollective::Members` (r:1 w:1) /// Proof: `FellowshipCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) /// Storage: `FellowshipCore::Params` (r:1 w:0) - /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) /// Storage: `FellowshipCollective::MemberCount` (r:1 w:1) /// Proof: `FellowshipCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) - /// Storage: `FellowshipCollective::IdToIndex` (r:1 w:0) + /// Storage: `FellowshipCollective::IdToIndex` (r:1 w:1) /// Proof: `FellowshipCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) /// Storage: `FellowshipCore::MemberEvidence` (r:1 w:1) /// Proof: `FellowshipCore::MemberEvidence` (`max_values`: None, `max_size`: Some(65581), added: 68056, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCollective::IndexToId` (r:0 w:1) + /// Proof: `FellowshipCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) fn bump_offboard() -> Weight { // Proof Size summary in bytes: - // Measured: `66144` + // Measured: `66535` // Estimated: `69046` - // Minimum execution time: 109_000_000 picoseconds. - Weight::from_parts(125_000_000, 0) + // Minimum execution time: 152_823_000 picoseconds. + Weight::from_parts(158_737_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `FellowshipCore::Member` (r:1 w:1) /// Proof: `FellowshipCore::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) /// Storage: `FellowshipCollective::Members` (r:1 w:1) /// Proof: `FellowshipCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) /// Storage: `FellowshipCore::Params` (r:1 w:0) - /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) /// Storage: `FellowshipCollective::MemberCount` (r:1 w:1) /// Proof: `FellowshipCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) - /// Storage: `FellowshipCollective::IdToIndex` (r:1 w:0) + /// Storage: `FellowshipCollective::IdToIndex` (r:1 w:1) /// Proof: `FellowshipCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) /// Storage: `FellowshipCore::MemberEvidence` (r:1 w:1) /// Proof: `FellowshipCore::MemberEvidence` (`max_values`: None, `max_size`: Some(65581), added: 68056, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCollective::IndexToId` (r:0 w:1) + /// Proof: `FellowshipCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) fn bump_demote() -> Weight { // Proof Size summary in bytes: - // Measured: `66254` + // Measured: `66645` // Estimated: `69046` - // Minimum execution time: 112_000_000 picoseconds. - Weight::from_parts(114_000_000, 0) + // Minimum execution time: 157_605_000 picoseconds. + Weight::from_parts(162_341_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `FellowshipCollective::Members` (r:1 w:0) /// Proof: `FellowshipCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) @@ -120,8 +130,8 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `493` // Estimated: `3514` - // Minimum execution time: 22_000_000 picoseconds. - Weight::from_parts(27_000_000, 0) + // Minimum execution time: 25_194_000 picoseconds. + Weight::from_parts(26_262_000, 0) .saturating_add(Weight::from_parts(0, 3514)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -140,8 +150,8 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `251` // Estimated: `3514` - // Minimum execution time: 35_000_000 picoseconds. - Weight::from_parts(36_000_000, 0) + // Minimum execution time: 35_479_000 picoseconds. + Weight::from_parts(36_360_000, 0) .saturating_add(Weight::from_parts(0, 3514)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(5)) @@ -151,7 +161,7 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< /// Storage: `FellowshipCore::Member` (r:1 w:1) /// Proof: `FellowshipCore::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) /// Storage: `FellowshipCore::Params` (r:1 w:0) - /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) /// Storage: `FellowshipCollective::MemberCount` (r:1 w:1) /// Proof: `FellowshipCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) /// Storage: `FellowshipCore::MemberEvidence` (r:1 w:1) @@ -162,25 +172,40 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< /// Proof: `FellowshipCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) fn promote() -> Weight { // Proof Size summary in bytes: - // Measured: `66122` + // Measured: `66188` // Estimated: `69046` - // Minimum execution time: 97_000_000 picoseconds. - Weight::from_parts(129_000_000, 0) + // Minimum execution time: 147_993_000 picoseconds. + Weight::from_parts(153_943_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(6)) } + /// Storage: `FellowshipCollective::Members` (r:1 w:1) + /// Proof: `FellowshipCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCore::Member` (r:1 w:1) + /// Proof: `FellowshipCore::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCollective::MemberCount` (r:9 w:9) + /// Proof: `FellowshipCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCore::MemberEvidence` (r:1 w:1) + /// Proof: `FellowshipCore::MemberEvidence` (`max_values`: None, `max_size`: Some(65581), added: 68056, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCollective::IndexToId` (r:0 w:9) + /// Proof: `FellowshipCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCollective::IdToIndex` (r:0 w:9) + /// Proof: `FellowshipCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) + /// The range of component `r` is `[1, 9]`. + /// The range of component `r` is `[1, 9]`. fn promote_fast(r: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `16844` - // Estimated: `19894 + r * (2489 ±0)` - // Minimum execution time: 45_065_000 picoseconds. - Weight::from_parts(34_090_392, 19894) - // Standard Error: 18_620 - .saturating_add(Weight::from_parts(13_578_046, 0).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(3_u64)) + // Measured: `66101` + // Estimated: `69046 + r * (2489 ±0)` + // Minimum execution time: 138_444_000 picoseconds. + Weight::from_parts(125_440_035, 0) + .saturating_add(Weight::from_parts(0, 69046)) + // Standard Error: 55_452 + .saturating_add(Weight::from_parts(19_946_954, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 2489).saturating_mul(r.into())) } @@ -192,10 +217,10 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< /// Proof: `FellowshipCore::MemberEvidence` (`max_values`: None, `max_size`: Some(65581), added: 68056, mode: `MaxEncodedLen`) fn offboard() -> Weight { // Proof Size summary in bytes: - // Measured: `464` + // Measured: `398` // Estimated: `3514` - // Minimum execution time: 22_000_000 picoseconds. - Weight::from_parts(22_000_000, 0) + // Minimum execution time: 27_392_000 picoseconds. + Weight::from_parts(28_134_000, 0) .saturating_add(Weight::from_parts(0, 3514)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -208,8 +233,22 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `418` // Estimated: `3514` - // Minimum execution time: 20_000_000 picoseconds. - Weight::from_parts(24_000_000, 0) + // Minimum execution time: 23_523_000 picoseconds. + Weight::from_parts(24_046_000, 0) + .saturating_add(Weight::from_parts(0, 3514)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `FellowshipCore::Member` (r:1 w:1) + /// Proof: `FellowshipCore::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCollective::Members` (r:1 w:0) + /// Proof: `FellowshipCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) + fn import_member() -> Weight { + // Proof Size summary in bytes: + // Measured: `418` + // Estimated: `3514` + // Minimum execution time: 23_369_000 picoseconds. + Weight::from_parts(24_088_000, 0) .saturating_add(Weight::from_parts(0, 3514)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -224,8 +263,8 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `66100` // Estimated: `69046` - // Minimum execution time: 89_000_000 picoseconds. - Weight::from_parts(119_000_000, 0) + // Minimum execution time: 127_137_000 picoseconds. + Weight::from_parts(131_638_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) @@ -238,8 +277,8 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `184` // Estimated: `69046` - // Minimum execution time: 43_000_000 picoseconds. - Weight::from_parts(52_000_000, 0) + // Minimum execution time: 103_212_000 picoseconds. + Weight::from_parts(105_488_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_multisig.rs index a7827b7200906370039d245b02fcaec6d22430d2..5c428bb5e5eacfee1c0a94625d1fa9ab3ec46493 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_multisig.rs @@ -1,42 +1,43 @@ // 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 <http://www.gnu.org/licenses/>. //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=collectives-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares // --extrinsic=* +// --chain=collectives-westend-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/collectives/collectives-polkadot/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,11 +55,11 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_288_000 picoseconds. - Weight::from_parts(14_235_741, 0) + // Minimum execution time: 16_309_000 picoseconds. + Weight::from_parts(17_281_100, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 5 - .saturating_add(Weight::from_parts(500, 0).saturating_mul(z.into())) + // Standard Error: 10 + .saturating_add(Weight::from_parts(549, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -68,13 +69,13 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `328 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 44_865_000 picoseconds. - Weight::from_parts(33_468_056, 0) + // Minimum execution time: 48_617_000 picoseconds. + Weight::from_parts(35_426_484, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_513 - .saturating_add(Weight::from_parts(130_544, 0).saturating_mul(s.into())) - // Standard Error: 14 - .saturating_add(Weight::from_parts(1_422, 0).saturating_mul(z.into())) + // Standard Error: 1_941 + .saturating_add(Weight::from_parts(164_183, 0).saturating_mul(s.into())) + // Standard Error: 19 + .saturating_add(Weight::from_parts(1_898, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -86,13 +87,13 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `348` // Estimated: `6811` - // Minimum execution time: 29_284_000 picoseconds. - Weight::from_parts(18_708_967, 0) + // Minimum execution time: 32_600_000 picoseconds. + Weight::from_parts(18_613_047, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 916 - .saturating_add(Weight::from_parts(119_202, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_447, 0).saturating_mul(z.into())) + // Standard Error: 1_498 + .saturating_add(Weight::from_parts(147_489, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_094, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -106,28 +107,29 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `451 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 49_462_000 picoseconds. - Weight::from_parts(34_470_286, 0) + // Minimum execution time: 55_580_000 picoseconds. + Weight::from_parts(32_757_473, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_738 - .saturating_add(Weight::from_parts(178_227, 0).saturating_mul(s.into())) - // Standard Error: 17 - .saturating_add(Weight::from_parts(1_644, 0).saturating_mul(z.into())) + // Standard Error: 3_265 + .saturating_add(Weight::from_parts(261_212, 0).saturating_mul(s.into())) + // Standard Error: 32 + .saturating_add(Weight::from_parts(2_407, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `329 + s * (2 ±0)` + // Measured: `328 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 30_749_000 picoseconds. - Weight::from_parts(31_841_438, 0) + // Minimum execution time: 31_137_000 picoseconds. + Weight::from_parts(32_271_159, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_033 - .saturating_add(Weight::from_parts(123_126, 0).saturating_mul(s.into())) + // Standard Error: 1_280 + .saturating_add(Weight::from_parts(163_156, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -138,11 +140,11 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `348` // Estimated: `6811` - // Minimum execution time: 17_436_000 picoseconds. - Weight::from_parts(18_036_002, 0) + // Minimum execution time: 17_763_000 picoseconds. + Weight::from_parts(18_235_437, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 829 - .saturating_add(Weight::from_parts(109_450, 0).saturating_mul(s.into())) + // Standard Error: 1_245 + .saturating_add(Weight::from_parts(138_553, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -151,13 +153,13 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `520 + s * (1 ±0)` + // Measured: `515 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 31_532_000 picoseconds. - Weight::from_parts(32_818_015, 0) + // Minimum execution time: 32_152_000 picoseconds. + Weight::from_parts(34_248_643, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 977 - .saturating_add(Weight::from_parts(123_121, 0).saturating_mul(s.into())) + // Standard Error: 1_943 + .saturating_add(Weight::from_parts(153_258, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs index 5d427d850046ff030c6c5b6247426849227e7ea1..c0389cbcdb42ceea8656d99f53b2eb7ddac2e407 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `17a605d70d1a`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=collectives-westend-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=collectives-westend-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,38 +50,34 @@ use core::marker::PhantomData; /// Weight functions for `pallet_xcm`. pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3610` - // Minimum execution time: 21_813_000 picoseconds. - Weight::from_parts(22_332_000, 0) - .saturating_add(Weight::from_parts(0, 3610)) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `111` + // Estimated: `3576` + // Minimum execution time: 26_877_000 picoseconds. + Weight::from_parts(27_778_000, 0) + .saturating_add(Weight::from_parts(0, 3576)) + .saturating_add(T::DbWeight::get().reads(5)) + .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::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `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) @@ -88,13 +86,13 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `214` - // Estimated: `3679` - // Minimum execution time: 93_243_000 picoseconds. - Weight::from_parts(95_650_000, 0) - .saturating_add(Weight::from_parts(0, 3679)) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `111` + // Estimated: `3593` + // Minimum execution time: 109_606_000 picoseconds. + Weight::from_parts(120_756_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Benchmark::Override` (r:0 w:0) /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -108,14 +106,12 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `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) @@ -124,23 +120,24 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `214` - // Estimated: `3679` - // Minimum execution time: 96_199_000 picoseconds. - Weight::from_parts(98_620_000, 0) - .saturating_add(Weight::from_parts(0, 3679)) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `111` + // Estimated: `3593` + // Minimum execution time: 109_165_000 picoseconds. + Weight::from_parts(110_899_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Estimated: `1485` + // Minimum execution time: 9_494_000 picoseconds. + Weight::from_parts(9_917_000, 0) + .saturating_add(Weight::from_parts(0, 1485)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -148,21 +145,18 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_442_000 picoseconds. - Weight::from_parts(6_682_000, 0) + // Minimum execution time: 7_515_000 picoseconds. + Weight::from_parts(7_771_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:0 w:1) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn force_default_xcm_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_833_000 picoseconds. - Weight::from_parts(1_973_000, 0) + // Minimum execution time: 2_430_000 picoseconds. + Weight::from_parts(2_536_000, 0) .saturating_add(Weight::from_parts(0, 0)) - .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `PolkadotXcm::VersionNotifiers` (r:1 w:1) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -172,10 +166,6 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) @@ -184,13 +174,13 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_subscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3610` - // Minimum execution time: 27_318_000 picoseconds. - Weight::from_parts(28_224_000, 0) - .saturating_add(Weight::from_parts(0, 3610)) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(5)) + // Measured: `42` + // Estimated: `3507` + // Minimum execution time: 28_913_000 picoseconds. + Weight::from_parts(29_949_000, 0) + .saturating_add(Weight::from_parts(0, 3507)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::VersionNotifiers` (r:1 w:1) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -198,10 +188,6 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) @@ -210,13 +196,13 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_unsubscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `363` - // Estimated: `3828` - // Minimum execution time: 29_070_000 picoseconds. - Weight::from_parts(30_205_000, 0) - .saturating_add(Weight::from_parts(0, 3828)) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `136` + // Estimated: `3601` + // Minimum execution time: 30_496_000 picoseconds. + Weight::from_parts(31_828_000, 0) + .saturating_add(Weight::from_parts(0, 3601)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `PolkadotXcm::XcmExecutionSuspended` (r:0 w:1) /// Proof: `PolkadotXcm::XcmExecutionSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -224,45 +210,45 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_904_000 picoseconds. - Weight::from_parts(2_033_000, 0) + // Minimum execution time: 2_435_000 picoseconds. + Weight::from_parts(2_635_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `159` - // Estimated: `13524` - // Minimum execution time: 18_348_000 picoseconds. - Weight::from_parts(18_853_000, 0) - .saturating_add(Weight::from_parts(0, 13524)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `22` + // Estimated: `15862` + // Minimum execution time: 21_713_000 picoseconds. + Weight::from_parts(22_209_000, 0) + .saturating_add(Weight::from_parts(0, 15862)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `163` - // Estimated: `13528` - // Minimum execution time: 17_964_000 picoseconds. - Weight::from_parts(18_548_000, 0) - .saturating_add(Weight::from_parts(0, 13528)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `26` + // Estimated: `15866` + // Minimum execution time: 22_035_000 picoseconds. + Weight::from_parts(22_675_000, 0) + .saturating_add(Weight::from_parts(0, 15866)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: - // Measured: `173` - // Estimated: `16013` - // Minimum execution time: 19_708_000 picoseconds. - Weight::from_parts(20_157_000, 0) - .saturating_add(Weight::from_parts(0, 16013)) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `36` + // Estimated: `18351` + // Minimum execution time: 24_882_000 picoseconds. + Weight::from_parts(25_172_000, 0) + .saturating_add(Weight::from_parts(0, 18351)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -270,70 +256,62 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn notify_current_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `212` - // Estimated: `6152` - // Minimum execution time: 26_632_000 picoseconds. - Weight::from_parts(27_314_000, 0) - .saturating_add(Weight::from_parts(0, 6152)) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `75` + // Estimated: `6015` + // Minimum execution time: 28_244_000 picoseconds. + Weight::from_parts(28_873_000, 0) + .saturating_add(Weight::from_parts(0, 6015)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `206` - // Estimated: `11096` - // Minimum execution time: 11_929_000 picoseconds. - Weight::from_parts(12_304_000, 0) - .saturating_add(Weight::from_parts(0, 11096)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `39` + // Estimated: `13404` + // Minimum execution time: 17_457_000 picoseconds. + Weight::from_parts(18_023_000, 0) + .saturating_add(Weight::from_parts(0, 13404)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `170` - // Estimated: `13535` - // Minimum execution time: 18_599_000 picoseconds. - Weight::from_parts(19_195_000, 0) - .saturating_add(Weight::from_parts(0, 13535)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `33` + // Estimated: `15873` + // Minimum execution time: 22_283_000 picoseconds. + Weight::from_parts(22_783_000, 0) + .saturating_add(Weight::from_parts(0, 15873)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `212` - // Estimated: `13577` - // Minimum execution time: 35_524_000 picoseconds. - Weight::from_parts(36_272_000, 0) - .saturating_add(Weight::from_parts(0, 13577)) - .saturating_add(T::DbWeight::get().reads(11)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `75` + // Estimated: `15915` + // Minimum execution time: 41_244_000 picoseconds. + Weight::from_parts(42_264_000, 0) + .saturating_add(Weight::from_parts(0, 15915)) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) /// Proof: `PolkadotXcm::QueryCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -341,11 +319,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn new_query() -> Weight { // Proof Size summary in bytes: - // Measured: `103` - // Estimated: `1588` - // Minimum execution time: 4_044_000 picoseconds. - Weight::from_parts(4_238_000, 0) - .saturating_add(Weight::from_parts(0, 1588)) + // Measured: `0` + // Estimated: `1485` + // Minimum execution time: 2_678_000 picoseconds. + Weight::from_parts(2_892_000, 0) + .saturating_add(Weight::from_parts(0, 1485)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -353,24 +331,26 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn take_response() -> Weight { // Proof Size summary in bytes: - // Measured: `7740` - // Estimated: `11205` - // Minimum execution time: 25_741_000 picoseconds. - Weight::from_parts(26_301_000, 0) - .saturating_add(Weight::from_parts(0, 11205)) + // Measured: `7576` + // Estimated: `11041` + // Minimum execution time: 26_677_000 picoseconds. + Weight::from_parts(27_470_000, 0) + .saturating_add(Weight::from_parts(0, 11041)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `160` - // Estimated: `3625` - // Minimum execution time: 35_925_000 picoseconds. - Weight::from_parts(36_978_000, 0) - .saturating_add(Weight::from_parts(0, 3625)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `23` + // Estimated: `3488` + // Minimum execution time: 40_143_000 picoseconds. + Weight::from_parts(41_712_000, 0) + .saturating_add(Weight::from_parts(0, 3488)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..d73ce8c440fc8d2e8b52981bb9253c5e62b39886 --- /dev/null +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/mod.rs @@ -0,0 +1,273 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod pallet_xcm_benchmarks_fungible; +mod pallet_xcm_benchmarks_generic; + +use crate::{xcm_config::MaxAssetsIntoHolding, Runtime}; +use alloc::vec::Vec; +use frame_support::weights::Weight; +use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; +use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; +use xcm::{ + latest::{prelude::*, AssetTransferFilter}, + DoubleEncoded, +}; + +trait WeighAssets { + fn weigh_assets(&self, weight: Weight) -> Weight; +} + +// Collectives only knows about WND. +const MAX_ASSETS: u64 = 1; + +impl WeighAssets for AssetFilter { + fn weigh_assets(&self, weight: Weight) -> Weight { + match self { + Self::Definite(assets) => weight.saturating_mul(assets.inner().iter().count() as u64), + Self::Wild(asset) => match asset { + All => weight.saturating_mul(MAX_ASSETS), + AllOf { fun, .. } => match fun { + WildFungibility::Fungible => weight, + // Magic number 2 has to do with the fact that we could have up to 2 times + // MaxAssetsIntoHolding in the worst-case scenario. + WildFungibility::NonFungible => + weight.saturating_mul((MaxAssetsIntoHolding::get() * 2) as u64), + }, + AllCounted(count) => weight.saturating_mul(MAX_ASSETS.min(*count as u64)), + AllOfCounted { count, .. } => weight.saturating_mul(MAX_ASSETS.min(*count as u64)), + }, + } + } +} + +impl WeighAssets for Assets { + fn weigh_assets(&self, weight: Weight) -> Weight { + weight.saturating_mul(self.inner().iter().count() as u64) + } +} + +pub struct CollectivesWestendXcmWeight<Call>(core::marker::PhantomData<Call>); +impl<Call> XcmWeightInfo<Call> for CollectivesWestendXcmWeight<Call> { + fn withdraw_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::<Runtime>::withdraw_asset()) + } + fn reserve_asset_deposited(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::<Runtime>::reserve_asset_deposited()) + } + fn receive_teleported_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::<Runtime>::receive_teleported_asset()) + } + fn query_response( + _query_id: &u64, + _response: &Response, + _max_weight: &Weight, + _querier: &Option<Location>, + ) -> Weight { + XcmGeneric::<Runtime>::query_response() + } + fn transfer_asset(assets: &Assets, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::<Runtime>::transfer_asset()) + } + fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::<Runtime>::transfer_reserve_asset()) + } + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option<Weight>, + _call: &DoubleEncoded<Call>, + ) -> Weight { + XcmGeneric::<Runtime>::transact() + } + fn hrmp_new_channel_open_request( + _sender: &u32, + _max_message_size: &u32, + _max_capacity: &u32, + ) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn hrmp_channel_accepted(_recipient: &u32) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn hrmp_channel_closing(_initiator: &u32, _sender: &u32, _recipient: &u32) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn clear_origin() -> Weight { + XcmGeneric::<Runtime>::clear_origin() + } + fn descend_origin(_who: &InteriorLocation) -> Weight { + XcmGeneric::<Runtime>::descend_origin() + } + fn report_error(_query_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::<Runtime>::report_error() + } + + fn deposit_asset(assets: &AssetFilter, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::<Runtime>::deposit_asset()) + } + fn deposit_reserve_asset(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::<Runtime>::deposit_reserve_asset()) + } + fn exchange_asset(_give: &AssetFilter, _receive: &Assets, _maximal: &bool) -> Weight { + Weight::MAX + } + fn initiate_reserve_withdraw( + assets: &AssetFilter, + _reserve: &Location, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_assets(XcmFungibleWeight::<Runtime>::initiate_reserve_withdraw()) + } + fn initiate_teleport(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::<Runtime>::initiate_teleport()) + } + fn initiate_transfer( + _dest: &Location, + remote_fees: &Option<AssetTransferFilter>, + _preserve_origin: &bool, + assets: &Vec<AssetTransferFilter>, + _xcm: &Xcm<()>, + ) -> Weight { + let mut weight = if let Some(remote_fees) = remote_fees { + let fees = remote_fees.inner(); + fees.weigh_assets(XcmFungibleWeight::<Runtime>::initiate_transfer()) + } else { + Weight::zero() + }; + for asset_filter in assets { + let assets = asset_filter.inner(); + let extra = assets.weigh_assets(XcmFungibleWeight::<Runtime>::initiate_transfer()); + weight = weight.saturating_add(extra); + } + weight + } + fn report_holding(_response_info: &QueryResponseInfo, _assets: &AssetFilter) -> Weight { + XcmGeneric::<Runtime>::report_holding() + } + fn buy_execution(_fees: &Asset, _weight_limit: &WeightLimit) -> Weight { + XcmGeneric::<Runtime>::buy_execution() + } + fn pay_fees(_asset: &Asset) -> Weight { + XcmGeneric::<Runtime>::pay_fees() + } + fn refund_surplus() -> Weight { + XcmGeneric::<Runtime>::refund_surplus() + } + fn set_error_handler(_xcm: &Xcm<Call>) -> Weight { + XcmGeneric::<Runtime>::set_error_handler() + } + fn set_appendix(_xcm: &Xcm<Call>) -> Weight { + XcmGeneric::<Runtime>::set_appendix() + } + fn clear_error() -> Weight { + XcmGeneric::<Runtime>::clear_error() + } + fn set_hints(hints: &BoundedVec<Hint, HintNumVariants>) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::<Runtime>::asset_claimer()); + }, + } + } + weight + } + fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { + XcmGeneric::<Runtime>::claim_asset() + } + fn trap(_code: &u64) -> Weight { + XcmGeneric::<Runtime>::trap() + } + fn subscribe_version(_query_id: &QueryId, _max_response_weight: &Weight) -> Weight { + XcmGeneric::<Runtime>::subscribe_version() + } + fn unsubscribe_version() -> Weight { + XcmGeneric::<Runtime>::unsubscribe_version() + } + fn burn_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::<Runtime>::burn_asset()) + } + fn expect_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::<Runtime>::expect_asset()) + } + fn expect_origin(_origin: &Option<Location>) -> Weight { + XcmGeneric::<Runtime>::expect_origin() + } + fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { + XcmGeneric::<Runtime>::expect_error() + } + fn expect_transact_status(_transact_status: &MaybeErrorCode) -> Weight { + XcmGeneric::<Runtime>::expect_transact_status() + } + fn query_pallet(_module_name: &Vec<u8>, _response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::<Runtime>::query_pallet() + } + fn expect_pallet( + _index: &u32, + _name: &Vec<u8>, + _module_name: &Vec<u8>, + _crate_major: &u32, + _min_crate_minor: &u32, + ) -> Weight { + XcmGeneric::<Runtime>::expect_pallet() + } + fn report_transact_status(_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::<Runtime>::report_transact_status() + } + fn clear_transact_status() -> Weight { + XcmGeneric::<Runtime>::clear_transact_status() + } + fn universal_origin(_: &Junction) -> Weight { + Weight::MAX + } + fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { + Weight::MAX + } + fn lock_asset(_: &Asset, _: &Location) -> Weight { + Weight::MAX + } + fn unlock_asset(_: &Asset, _: &Location) -> Weight { + Weight::MAX + } + fn note_unlockable(_: &Asset, _: &Location) -> Weight { + Weight::MAX + } + fn request_unlock(_: &Asset, _: &Location) -> Weight { + Weight::MAX + } + fn set_fees_mode(_: &bool) -> Weight { + XcmGeneric::<Runtime>::set_fees_mode() + } + fn set_topic(_topic: &[u8; 32]) -> Weight { + XcmGeneric::<Runtime>::set_topic() + } + fn clear_topic() -> Weight { + XcmGeneric::<Runtime>::clear_topic() + } + fn alias_origin(_: &Location) -> Weight { + XcmGeneric::<Runtime>::alias_origin() + } + fn unpaid_execution(_: &WeightLimit, _: &Option<Location>) -> Weight { + XcmGeneric::<Runtime>::unpaid_execution() + } + fn execute_with_origin(_: &Option<InteriorLocation>, _: &Xcm<Call>) -> Weight { + XcmGeneric::<Runtime>::execute_with_origin() + } +} diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs new file mode 100644 index 0000000000000000000000000000000000000000..f6a140f3157fceb5337c6a26e0d926c7b68b9c4b --- /dev/null +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -0,0 +1,205 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `pallet_xcm_benchmarks::fungible` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `17a605d70d1a`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: Compiled, CHAIN: Some("collectives-westend-dev"), DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --extrinsic=* +// --chain=collectives-westend-dev +// --pallet=pallet_xcm_benchmarks::fungible +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 +// --template=cumulus/templates/xcm-bench-template.hbs +// --no-storage-info +// --no-min-squares +// --no-median-slopes + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weights for `pallet_xcm_benchmarks::fungible`. +pub struct WeightInfo<T>(PhantomData<T>); +impl<T: frame_system::Config> WeightInfo<T> { + // 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: 32_692_000 picoseconds. + Weight::from_parts(33_469_000, 3593) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + pub fn transfer_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `101` + // Estimated: `6196` + // Minimum execution time: 42_464_000 picoseconds. + Weight::from_parts(43_897_000, 6196) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: `System::Account` (r:3 w:3) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `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: `212` + // Estimated: `8799` + // Minimum execution time: 105_472_000 picoseconds. + Weight::from_parts(115_465_000, 8799) + .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: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + pub fn initiate_reserve_withdraw() -> Weight { + // Proof Size summary in bytes: + // Measured: `212` + // Estimated: `6196` + // Minimum execution time: 72_377_000 picoseconds. + Weight::from_parts(76_456_000, 6196) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) + } + pub fn receive_teleported_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_556_000 picoseconds. + Weight::from_parts(2_960_000, 0) + } + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + pub fn deposit_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 24_560_000 picoseconds. + Weight::from_parts(24_926_000, 3593) + .saturating_add(T::DbWeight::get().reads(1)) + .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: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `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: `111` + // Estimated: `3593` + // Minimum execution time: 57_780_000 picoseconds. + Weight::from_parts(59_561_000, 3593) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `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: `111` + // Estimated: `3576` + // Minimum execution time: 37_041_000 picoseconds. + Weight::from_parts(38_101_000, 3576) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `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_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `111` + // Estimated: `6196` + // Minimum execution time: 87_635_000 picoseconds. + Weight::from_parts(89_712_000, 6196) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) + } +} diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs new file mode 100644 index 0000000000000000000000000000000000000000..8e732546437a9ae4993edfe1bd39e8887fafb1d4 --- /dev/null +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -0,0 +1,359 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `pallet_xcm_benchmarks::generic` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `96ae15bb1012`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: Compiled, CHAIN: Some("collectives-westend-dev"), DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --extrinsic=* +// --chain=collectives-westend-dev +// --pallet=pallet_xcm_benchmarks::generic +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 +// --template=cumulus/templates/xcm-bench-template.hbs +// --no-storage-info +// --no-min-squares +// --no-median-slopes + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weights for `pallet_xcm_benchmarks::generic`. +pub struct WeightInfo<T>(PhantomData<T>); +impl<T: frame_system::Config> WeightInfo<T> { + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + pub fn report_holding() -> Weight { + // Proof Size summary in bytes: + // Measured: `212` + // Estimated: `6196` + // Minimum execution time: 72_839_000 picoseconds. + Weight::from_parts(74_957_000, 6196) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) + } + pub fn buy_execution() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 592_000 picoseconds. + Weight::from_parts(646_000, 0) + } + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + pub fn pay_fees() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 3_630_000 picoseconds. + Weight::from_parts(3_843_000, 3593) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + pub fn asset_claimer() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 660_000 picoseconds. + Weight::from_parts(712_000, 0) + } + // Storage: `PolkadotXcm::Queries` (r:1 w:0) + // Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) + pub fn query_response() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3465` + // Minimum execution time: 5_996_000 picoseconds. + Weight::from_parts(6_277_000, 3465) + .saturating_add(T::DbWeight::get().reads(1)) + } + pub fn transact() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_427_000 picoseconds. + Weight::from_parts(7_817_000, 0) + } + pub fn refund_surplus() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_245_000 picoseconds. + Weight::from_parts(1_373_000, 0) + } + pub fn set_error_handler() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 589_000 picoseconds. + Weight::from_parts(647_000, 0) + } + pub fn set_appendix() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 593_000 picoseconds. + Weight::from_parts(653_000, 0) + } + pub fn clear_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 599_000 picoseconds. + Weight::from_parts(652_000, 0) + } + pub fn descend_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 620_000 picoseconds. + Weight::from_parts(670_000, 0) + } + pub fn execute_with_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 682_000 picoseconds. + Weight::from_parts(747_000, 0) + } + pub fn clear_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 596_000 picoseconds. + Weight::from_parts(650_000, 0) + } + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + pub fn report_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `212` + // Estimated: `6196` + // Minimum execution time: 68_183_000 picoseconds. + Weight::from_parts(70_042_000, 6196) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // 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: `23` + // Estimated: `3488` + // Minimum execution time: 9_661_000 picoseconds. + Weight::from_parts(9_943_000, 3488) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + pub fn trap() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 580_000 picoseconds. + Weight::from_parts(652_000, 0) + } + // Storage: `PolkadotXcm::VersionNotifyTargets` (r:1 w:1) + // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `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: `42` + // Estimated: `3507` + // Minimum execution time: 24_197_000 picoseconds. + Weight::from_parts(25_199_000, 3507) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // 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: 2_720_000 picoseconds. + Weight::from_parts(2_881_000, 0) + .saturating_add(T::DbWeight::get().writes(1)) + } + pub fn burn_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 950_000 picoseconds. + Weight::from_parts(1_076_000, 0) + } + pub fn expect_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 742_000 picoseconds. + Weight::from_parts(785_000, 0) + } + pub fn expect_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 598_000 picoseconds. + Weight::from_parts(671_000, 0) + } + pub fn expect_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 571_000 picoseconds. + Weight::from_parts(635_000, 0) + } + pub fn expect_transact_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 766_000 picoseconds. + Weight::from_parts(835_000, 0) + } + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + pub fn query_pallet() -> Weight { + // Proof Size summary in bytes: + // Measured: `212` + // Estimated: `6196` + // Minimum execution time: 76_301_000 picoseconds. + Weight::from_parts(79_269_000, 6196) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) + } + pub fn expect_pallet() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_452_000 picoseconds. + Weight::from_parts(5_721_000, 0) + } + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + pub fn report_transact_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `212` + // Estimated: `6196` + // Minimum execution time: 68_763_000 picoseconds. + Weight::from_parts(71_142_000, 6196) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) + } + pub fn clear_transact_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 630_000 picoseconds. + Weight::from_parts(676_000, 0) + } + pub fn set_topic() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 570_000 picoseconds. + Weight::from_parts(622_000, 0) + } + pub fn clear_topic() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 549_000 picoseconds. + Weight::from_parts(603_000, 0) + } + pub fn set_fees_mode() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 578_000 picoseconds. + Weight::from_parts(626_000, 0) + } + pub fn unpaid_execution() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 594_000 picoseconds. + Weight::from_parts(639_000, 0) + } + pub fn alias_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 637_000 picoseconds. + Weight::from_parts(676_000, 0) + } +} 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 56ef2e8ba02f25e400ac4bc05383978fcfec2af0..c5ab21fe8f904372fc4d12b20961006fd01df612 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs @@ -21,7 +21,6 @@ use super::{ use frame_support::{ parameter_types, traits::{tokens::imbalance::ResolveTo, ConstU32, Contains, Equals, Everything, Nothing}, - weights::Weight, }; use frame_system::EnsureRoot; use pallet_collator_selection::StakingPotAccountId; @@ -35,15 +34,16 @@ use polkadot_runtime_common::xcm_sender::ExponentialPrice; use westend_runtime_constants::xcm as xcm_constants; use xcm::latest::{prelude::*, WESTEND_GENESIS_HASH}; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, + AccountId32Aliases, AliasChildLocation, AliasOriginRootUsingFilter, + AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, - EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, - HashedDescription, IsConcrete, LocatableAssetId, OriginToPluralityVoice, ParentAsSuperuser, - ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsConcrete, + LocatableAssetId, OriginToPluralityVoice, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, }; use xcm_executor::XcmExecutor; @@ -124,11 +124,6 @@ pub type XcmOriginToTransactDispatchOrigin = ( ); parameter_types! { - /// The amount of weight an XCM operation takes. This is a safe overestimate. - pub const BaseXcmWeight: Weight = Weight::from_parts(1_000_000_000, 1024); - /// A temporary weight value for each XCM instruction. - /// NOTE: This should be removed after we account for PoV weights. - pub const TempFixedXcmWeight: Weight = Weight::from_parts(1_000_000_000, 0); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; // Fellows pluralistic body. @@ -191,6 +186,10 @@ pub type WaivedLocations = ( /// - DOT with the parent Relay Chain and sibling parachains. pub type TrustedTeleporters = ConcreteAssetFromSystem<WndLocation>; +/// We allow locations to alias into their own child locations, as well as +/// AssetHub to alias into anything. +pub type Aliasers = (AliasChildLocation, AliasOriginRootUsingFilter<AssetHub, Everything>); + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -203,7 +202,11 @@ impl xcm_executor::Config for XcmConfig { type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; type Barrier = Barrier; - type Weigher = FixedWeightBounds<TempFixedXcmWeight, RuntimeCall, MaxInstructions>; + type Weigher = WeightInfoBounds< + crate::weights::xcm::CollectivesWestendXcmWeight<RuntimeCall>, + RuntimeCall, + MaxInstructions, + >; type Trader = UsingComponents< WeightToFee, WndLocation, @@ -227,7 +230,7 @@ impl xcm_executor::Config for XcmConfig { type UniversalAliases = Nothing; type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; - type Aliasers = Nothing; + type Aliasers = Aliasers; type TransactionalProcessor = FrameTransactionalProcessor; type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); @@ -270,7 +273,11 @@ impl pallet_xcm::Config for Runtime { type XcmExecutor = XcmExecutor<XcmConfig>; type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Nothing; // This parachain is not meant as a reserve location. - type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>; + type Weigher = WeightInfoBounds< + crate::weights::xcm::CollectivesWestendXcmWeight<RuntimeCall>, + RuntimeCall, + MaxInstructions, + >; type UniversalLocation = UniversalLocation; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/tests/tests.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/tests/tests.rs index 7add10559d84de880dbe96117bd70fa6b82e675e..c9191eba49f6a57b6bd05069c6287f024429a527 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/tests/tests.rs @@ -16,7 +16,9 @@ #![cfg(test)] -use collectives_westend_runtime::xcm_config::LocationToAccountId; +use collectives_westend_runtime::{ + xcm_config::LocationToAccountId, Block, Runtime, RuntimeCall, RuntimeOrigin, +}; use parachains_common::AccountId; use sp_core::crypto::Ss58Codec; use xcm::latest::prelude::*; @@ -132,3 +134,13 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} diff --git a/cumulus/parachains/runtimes/constants/Cargo.toml b/cumulus/parachains/runtimes/constants/Cargo.toml index d54f1e7db6c167480858758bf56ed07941fb06c2..01b023e0fb89b1557eb42e8629648465ba392474 100644 --- a/cumulus/parachains/runtimes/constants/Cargo.toml +++ b/cumulus/parachains/runtimes/constants/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Common constants for Testnet Parachains runtimes" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml index c98ca7ba3e74ec9ab9c493b7cf6dc0f4ef0b495c..067c4df3b53654f28fdc40e0ac58a480d7fdfef3 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml @@ -5,6 +5,8 @@ description = "Parachain testnet runtime for FRAME Contracts pallet." authors.workspace = true edition.workspace = true license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -22,37 +24,37 @@ log = { workspace = true } scale-info = { features = ["derive"], workspace = true } # Substrate -sp-api = { workspace = true } -sp-block-builder = { workspace = true } -sp-consensus-aura = { workspace = true } -sp-core = { workspace = true } -sp-genesis-builder = { workspace = true } -sp-inherents = { workspace = true } -sp-offchain = { workspace = true } -sp-runtime = { workspace = true } -sp-session = { workspace = true } -sp-storage = { workspace = true } -sp-transaction-pool = { workspace = true } -sp-version = { workspace = true } frame-benchmarking = { optional = true, workspace = true } -frame-try-runtime = { optional = true, workspace = true } frame-executive = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } frame-system-benchmarking = { optional = true, workspace = true } frame-system-rpc-runtime-api = { workspace = true } +frame-try-runtime = { optional = true, workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } -pallet-insecure-randomness-collective-flip = { workspace = true } pallet-balances = { workspace = true } +pallet-contracts = { workspace = true } +pallet-insecure-randomness-collective-flip = { workspace = true } pallet-multisig = { workspace = true } pallet-session = { workspace = true } +pallet-sudo = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-utility = { workspace = true } -pallet-sudo = { workspace = true } -pallet-contracts = { workspace = true } +sp-api = { workspace = true } +sp-block-builder = { workspace = true } +sp-consensus-aura = { workspace = true } +sp-core = { workspace = true } +sp-genesis-builder = { workspace = true } +sp-inherents = { workspace = true } +sp-offchain = { workspace = true } +sp-runtime = { workspace = true } +sp-session = { workspace = true } +sp-storage = { workspace = true } +sp-transaction-pool = { workspace = true } +sp-version = { workspace = true } # Polkadot pallet-xcm = { workspace = true } @@ -66,15 +68,15 @@ xcm-runtime-apis = { workspace = true } # Cumulus cumulus-pallet-aura-ext = { workspace = true } -pallet-message-queue = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-utility = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } +pallet-message-queue = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } @@ -88,11 +90,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", @@ -146,6 +148,7 @@ std = [ runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -171,11 +174,13 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index 2951662a979b3ed2b6552c64cca49b34d8332aee..eaaaf0a9a9a7b019f245885e823c6cf635b60551 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -88,17 +88,19 @@ pub type SignedBlock = generic::SignedBlock<Block>; /// BlockId type as expected by this runtime. pub type BlockId = generic::BlockId<Block>; /// The extension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender<Runtime>, - frame_system::CheckSpecVersion<Runtime>, - frame_system::CheckTxVersion<Runtime>, - frame_system::CheckGenesis<Runtime>, - frame_system::CheckEra<Runtime>, - frame_system::CheckNonce<Runtime>, - frame_system::CheckWeight<Runtime>, - pallet_transaction_payment::ChargeTransactionPayment<Runtime>, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim<Runtime>, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckTxVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckEra<Runtime>, + frame_system::CheckNonce<Runtime>, + frame_system::CheckWeight<Runtime>, + pallet_transaction_payment::ChargeTransactionPayment<Runtime>, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, TxExtension>; @@ -116,6 +118,7 @@ pub type Migrations = ( cumulus_pallet_xcmp_queue::migration::v5::MigrateV4ToV5<Runtime>, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion<Runtime>, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1<Runtime>, ); type EventRecord = frame_system::EventRecord< @@ -144,7 +147,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("contracts-rococo"), impl_name: alloc::borrow::Cow::Borrowed("contracts-rococo"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 7, @@ -201,6 +204,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = (); +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -402,6 +409,7 @@ construct_runtime!( RandomnessCollectiveFlip: pallet_insecure_randomness_collective_flip = 2, Timestamp: pallet_timestamp = 3, ParachainInfo: parachain_info = 4, + WeightReclaim: cumulus_pallet_weight_reclaim = 5, // Monetary stuff. Balances: pallet_balances = 10, @@ -448,6 +456,7 @@ mod benches { [cumulus_pallet_parachain_system, ParachainSystem] [pallet_contracts, Contracts] [pallet_xcm, PalletXcmExtrinsicsBenchmark::<Runtime>] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); } @@ -849,18 +858,8 @@ impl_runtime_apis! { } } - let whitelist: Vec<TrackedStorageKey> = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec<TrackedStorageKey> = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::<BenchmarkBatch>::new(); let params = (&config, &whitelist); diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml index a38b7400cfa3e792517019039dc2430a364e7d3d..668b4cc6c7b9bd6a358254f1c86b639dbc487cf5 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Rococo's Coretime parachain runtime" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -31,8 +33,8 @@ frame-try-runtime = { optional = true, workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } -pallet-message-queue = { workspace = true } pallet-broker = { workspace = true } +pallet-message-queue = { workspace = true } pallet-multisig = { workspace = true } pallet-proxy = { workspace = true } pallet-session = { workspace = true } @@ -45,8 +47,8 @@ sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } -sp-inherents = { workspace = true } sp-genesis-builder = { workspace = true } +sp-inherents = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } @@ -69,17 +71,20 @@ xcm-runtime-apis = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-utility = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } parachains-common = { workspace = true } testnet-parachains-constants = { features = ["rococo"], workspace = true } +[dev-dependencies] +parachains-runtimes-test-utils = { workspace = true } + [features] default = ["std"] std = [ @@ -87,11 +92,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", @@ -120,6 +125,7 @@ std = [ "pallet-xcm/std", "parachain-info/std", "parachains-common/std", + "parachains-runtimes-test-utils/std", "polkadot-parachain-primitives/std", "polkadot-runtime-common/std", "rococo-runtime-constants/std", @@ -148,6 +154,7 @@ std = [ runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -174,11 +181,13 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs index d76ac443a147dd27e2fc953b7ac4280296a900a5..35c3dd8836a801448e2e90f2bbc1950a2edc21da 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs @@ -135,6 +135,7 @@ impl CoretimeInterface for CoretimeAllocator { Instruction::Transact { origin_kind: OriginKind::Native, call: request_core_count_call.encode().into(), + fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)), }, ]); @@ -164,6 +165,7 @@ impl CoretimeInterface for CoretimeAllocator { Instruction::Transact { origin_kind: OriginKind::Native, call: request_revenue_info_at_call.encode().into(), + fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)), }, ]); @@ -192,6 +194,7 @@ impl CoretimeInterface for CoretimeAllocator { Instruction::Transact { origin_kind: OriginKind::Native, call: credit_account_call.encode().into(), + fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)), }, ]); @@ -256,6 +259,7 @@ impl CoretimeInterface for CoretimeAllocator { Instruction::Transact { origin_kind: OriginKind::Native, call: assign_core_call.encode().into(), + fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)), }, ]); diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index 3f3126b749d8190cb424b6e4a1a515f537c99261..622a40e1d8dc09c9f9f31f0fd80f136b724763b2 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -99,18 +99,20 @@ pub type SignedBlock = generic::SignedBlock<Block>; pub type BlockId = generic::BlockId<Block>; /// The TransactionExtension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender<Runtime>, - frame_system::CheckSpecVersion<Runtime>, - frame_system::CheckTxVersion<Runtime>, - frame_system::CheckGenesis<Runtime>, - frame_system::CheckEra<Runtime>, - frame_system::CheckNonce<Runtime>, - frame_system::CheckWeight<Runtime>, - pallet_transaction_payment::ChargeTransactionPayment<Runtime>, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim<Runtime>, - frame_metadata_hash_extension::CheckMetadataHash<Runtime>, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckTxVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckEra<Runtime>, + frame_system::CheckNonce<Runtime>, + frame_system::CheckWeight<Runtime>, + pallet_transaction_payment::ChargeTransactionPayment<Runtime>, + frame_metadata_hash_extension::CheckMetadataHash<Runtime>, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -127,6 +129,7 @@ pub type Migrations = ( pallet_broker::migration::MigrateV3ToV4<Runtime, BrokerMigrationV4BlockConversion>, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion<Runtime>, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1<Runtime>, ); /// Executive: handles dispatch to the various modules. @@ -150,7 +153,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("coretime-rococo"), impl_name: alloc::borrow::Cow::Borrowed("coretime-rococo"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, @@ -221,6 +224,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo<Runtime>; +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -622,6 +629,7 @@ construct_runtime!( ParachainSystem: cumulus_pallet_parachain_system = 1, Timestamp: pallet_timestamp = 3, ParachainInfo: parachain_info = 4, + WeightReclaim: cumulus_pallet_weight_reclaim = 5, // Monetary stuff. Balances: pallet_balances = 10, @@ -672,6 +680,7 @@ mod benches { // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); } @@ -835,7 +844,8 @@ impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> { - match asset.try_as::<AssetId>() { + let latest_asset_id: Result<AssetId, ()> = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::RocRelayLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) @@ -1139,18 +1149,8 @@ impl_runtime_apis! { type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::<Runtime>; type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::<Runtime>; - let whitelist: Vec<TrackedStorageKey> = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec<TrackedStorageKey> = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::<BenchmarkBatch>::new(); let params = (&config, &whitelist); diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 0000000000000000000000000000000000000000..6298fd6e7f69815e659af5a404b1bc2bf0f41d5b --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=coretime-rococo-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo<T>(PhantomData<T>); +impl<T: frame_system::Config> cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo<T> { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 6_638_000 picoseconds. + Weight::from_parts(6_806_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/frame_system_extensions.rs index a4d09696a1a116ea5add5d9a1d41b86169bfe272..04b695b5769302f51ce764bfff219900bff107eb 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/frame_system_extensions.rs @@ -129,4 +129,18 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 3_687_000 picoseconds. + Weight::from_parts(6_192_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs index 24c4f50e6ab8b8c46ae1bfa177c8afada04a795e..7fee4a728b9ef750224efeac78408ca58827d643 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs @@ -19,6 +19,7 @@ pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs index 35708f22de20555656feec8ba2ce822dace81303..3e4bbf379c3ff9efe276cd2c996bf1863d775746 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_broker` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-06-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-x5tnzzy-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-acd6uxux-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<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_024_000 picoseconds. - Weight::from_parts(2_121_000, 0) + // Minimum execution time: 2_250_000 picoseconds. + Weight::from_parts(2_419_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -65,8 +65,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `10888` // Estimated: `13506` - // Minimum execution time: 21_654_000 picoseconds. - Weight::from_parts(22_591_000, 0) + // Minimum execution time: 25_785_000 picoseconds. + Weight::from_parts(26_335_000, 0) .saturating_add(Weight::from_parts(0, 13506)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -77,8 +77,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `12090` // Estimated: `13506` - // Minimum execution time: 20_769_000 picoseconds. - Weight::from_parts(21_328_000, 0) + // Minimum execution time: 24_549_000 picoseconds. + Weight::from_parts(25_010_000, 0) .saturating_add(Weight::from_parts(0, 13506)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -93,8 +93,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `466` // Estimated: `1951` - // Minimum execution time: 10_404_000 picoseconds. - Weight::from_parts(10_941_000, 0) + // Minimum execution time: 14_135_000 picoseconds. + Weight::from_parts(14_603_000, 0) .saturating_add(Weight::from_parts(0, 1951)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -121,6 +121,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::LastRelayChainBlockNumber` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::InstaPoolIo` (r:3 w:3) /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::AutoRenewals` (r:1 w:1) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(1002), added: 1497, mode: `MaxEncodedLen`) /// Storage: `Broker::SaleInfo` (r:0 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) /// Storage: `Broker::Status` (r:0 w:1) @@ -132,31 +134,33 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `12599` // Estimated: `15065 + n * (1 ±0)` - // Minimum execution time: 44_085_000 picoseconds. - Weight::from_parts(127_668_002, 0) + // Minimum execution time: 54_087_000 picoseconds. + Weight::from_parts(145_466_213, 0) .saturating_add(Weight::from_parts(0, 15065)) - // Standard Error: 2_231 - .saturating_add(Weight::from_parts(20_604, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(13)) - .saturating_add(T::DbWeight::get().writes(59)) + // Standard Error: 2_407 + .saturating_add(Weight::from_parts(20_971, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(14)) + .saturating_add(T::DbWeight::get().writes(60)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } /// Storage: `Broker::Status` (r:1 w:0) /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) /// Storage: `Broker::SaleInfo` (r:1 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`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: `Broker::Regions` (r:0 w:1) /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(86), added: 2561, mode: `MaxEncodedLen`) fn purchase() -> Weight { // Proof Size summary in bytes: - // Measured: `332` + // Measured: `437` // Estimated: `3593` - // Minimum execution time: 45_100_000 picoseconds. - Weight::from_parts(46_263_000, 0) + // Minimum execution time: 58_341_000 picoseconds. + Weight::from_parts(59_505_000, 0) .saturating_add(Weight::from_parts(0, 3593)) - .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `Broker::Configuration` (r:1 w:0) @@ -169,16 +173,18 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { /// Proof: `Broker::PotentialRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::Workplan` (r:0 w:1) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) fn renew() -> Weight { // Proof Size summary in bytes: - // Measured: `553` + // Measured: `658` // Estimated: `4698` - // Minimum execution time: 65_944_000 picoseconds. - Weight::from_parts(68_666_000, 0) + // Minimum execution time: 92_983_000 picoseconds. + Weight::from_parts(99_237_000, 0) .saturating_add(Weight::from_parts(0, 4698)) - .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `Broker::Regions` (r:1 w:1) @@ -187,8 +193,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `358` // Estimated: `3551` - // Minimum execution time: 13_794_000 picoseconds. - Weight::from_parts(14_450_000, 0) + // Minimum execution time: 17_512_000 picoseconds. + Weight::from_parts(18_099_000, 0) .saturating_add(Weight::from_parts(0, 3551)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -199,8 +205,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `358` // Estimated: `3551` - // Minimum execution time: 15_316_000 picoseconds. - Weight::from_parts(15_787_000, 0) + // Minimum execution time: 18_715_000 picoseconds. + Weight::from_parts(19_768_000, 0) .saturating_add(Weight::from_parts(0, 3551)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -211,8 +217,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `358` // Estimated: `3551` - // Minimum execution time: 16_375_000 picoseconds. - Weight::from_parts(17_113_000, 0) + // Minimum execution time: 20_349_000 picoseconds. + Weight::from_parts(21_050_000, 0) .saturating_add(Weight::from_parts(0, 3551)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(3)) @@ -229,8 +235,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `937` // Estimated: `4681` - // Minimum execution time: 25_952_000 picoseconds. - Weight::from_parts(27_198_000, 0) + // Minimum execution time: 31_876_000 picoseconds. + Weight::from_parts(33_536_000, 0) .saturating_add(Weight::from_parts(0, 4681)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -249,8 +255,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `1003` // Estimated: `5996` - // Minimum execution time: 31_790_000 picoseconds. - Weight::from_parts(32_920_000, 0) + // Minimum execution time: 39_500_000 picoseconds. + Weight::from_parts(40_666_000, 0) .saturating_add(Weight::from_parts(0, 5996)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) @@ -264,13 +270,13 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { /// The range of component `m` is `[1, 3]`. fn claim_revenue(m: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `652` + // Measured: `671` // Estimated: `6196 + m * (2520 ±0)` - // Minimum execution time: 56_286_000 picoseconds. - Weight::from_parts(56_946_240, 0) + // Minimum execution time: 65_843_000 picoseconds. + Weight::from_parts(65_768_512, 0) .saturating_add(Weight::from_parts(0, 6196)) - // Standard Error: 44_472 - .saturating_add(Weight::from_parts(1_684_838, 0).saturating_mul(m.into())) + // Standard Error: 40_994 + .saturating_add(Weight::from_parts(2_084_877, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(m.into()))) .saturating_add(T::DbWeight::get().writes(5)) @@ -290,11 +296,11 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn purchase_credit() -> Weight { // Proof Size summary in bytes: - // Measured: `322` - // Estimated: `3787` - // Minimum execution time: 64_967_000 picoseconds. - Weight::from_parts(66_504_000, 0) - .saturating_add(Weight::from_parts(0, 3787)) + // Measured: `323` + // Estimated: `3788` + // Minimum execution time: 73_250_000 picoseconds. + Weight::from_parts(75_059_000, 0) + .saturating_add(Weight::from_parts(0, 3788)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -306,8 +312,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `466` // Estimated: `3551` - // Minimum execution time: 37_552_000 picoseconds. - Weight::from_parts(46_263_000, 0) + // Minimum execution time: 55_088_000 picoseconds. + Weight::from_parts(65_329_000, 0) .saturating_add(Weight::from_parts(0, 3551)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -322,8 +328,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `463` // Estimated: `3533` - // Minimum execution time: 79_625_000 picoseconds. - Weight::from_parts(86_227_000, 0) + // Minimum execution time: 102_280_000 picoseconds. + Weight::from_parts(130_319_000, 0) .saturating_add(Weight::from_parts(0, 3533)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -338,10 +344,10 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn drop_history() -> Weight { // Proof Size summary in bytes: - // Measured: `857` + // Measured: `979` // Estimated: `3593` - // Minimum execution time: 88_005_000 picoseconds. - Weight::from_parts(92_984_000, 0) + // Minimum execution time: 78_195_000 picoseconds. + Weight::from_parts(105_946_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)) @@ -354,8 +360,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `957` // Estimated: `4698` - // Minimum execution time: 38_877_000 picoseconds. - Weight::from_parts(40_408_000, 0) + // Minimum execution time: 41_642_000 picoseconds. + Weight::from_parts(48_286_000, 0) .saturating_add(Weight::from_parts(0, 4698)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -371,15 +377,13 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// The range of component `n` is `[0, 1000]`. - fn request_core_count(n: u32, ) -> Weight { + fn request_core_count(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 20_581_000 picoseconds. - Weight::from_parts(21_610_297, 0) + // Minimum execution time: 23_727_000 picoseconds. + Weight::from_parts(25_029_439, 0) .saturating_add(Weight::from_parts(0, 3539)) - // Standard Error: 119 - .saturating_add(Weight::from_parts(144, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -390,11 +394,11 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `266` // Estimated: `1487` - // Minimum execution time: 6_079_000 picoseconds. - Weight::from_parts(6_540_110, 0) + // Minimum execution time: 7_887_000 picoseconds. + Weight::from_parts(8_477_863, 0) .saturating_add(Weight::from_parts(0, 1487)) - // Standard Error: 14 - .saturating_add(Weight::from_parts(10, 0).saturating_mul(n.into())) + // Standard Error: 18 + .saturating_add(Weight::from_parts(76, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -406,36 +410,50 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn process_revenue() -> Weight { // Proof Size summary in bytes: - // Measured: `442` + // Measured: `461` // Estimated: `6196` - // Minimum execution time: 42_947_000 picoseconds. - Weight::from_parts(43_767_000, 0) + // Minimum execution time: 52_505_000 picoseconds. + Weight::from_parts(53_392_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::InstaPoolIo` (r:3 w:3) /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) /// Storage: `Broker::Reservations` (r:1 w:0) /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) /// Storage: `Broker::Leases` (r:1 w:1) /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(401), added: 896, mode: `MaxEncodedLen`) + /// Storage: `Broker::AutoRenewals` (r:1 w:1) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(1002), added: 1497, mode: `MaxEncodedLen`) + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::PotentialRenewals` (r:100 w:200) + /// Proof: `Broker::PotentialRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:101 w:101) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Broker::SaleInfo` (r:0 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) - /// Storage: `Broker::Workplan` (r:0 w:60) + /// Storage: `Broker::Workplan` (r:0 w:1000) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 1000]`. fn rotate_sale(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `12514` - // Estimated: `13506` - // Minimum execution time: 93_426_000 picoseconds. - Weight::from_parts(96_185_447, 0) - .saturating_add(Weight::from_parts(0, 13506)) - // Standard Error: 116 - .saturating_add(Weight::from_parts(4, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(65)) + // Measured: `32497` + // Estimated: `233641 + n * (198 ±9)` + // Minimum execution time: 28_834_000 picoseconds. + Weight::from_parts(2_467_159_777, 0) + .saturating_add(Weight::from_parts(0, 233641)) + // Standard Error: 149_483 + .saturating_add(Weight::from_parts(4_045_956, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(126)) + .saturating_add(T::DbWeight::get().writes(181)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 198).saturating_mul(n.into())) } /// Storage: `Broker::InstaPoolIo` (r:1 w:0) /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) @@ -445,8 +463,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `42` // Estimated: `3493` - // Minimum execution time: 5_842_000 picoseconds. - Weight::from_parts(6_077_000, 0) + // Minimum execution time: 7_689_000 picoseconds. + Weight::from_parts(7_988_000, 0) .saturating_add(Weight::from_parts(0, 3493)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -469,8 +487,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `1321` // Estimated: `4786` - // Minimum execution time: 33_278_000 picoseconds. - Weight::from_parts(34_076_000, 0) + // Minimum execution time: 37_394_000 picoseconds. + Weight::from_parts(38_379_000, 0) .saturating_add(Weight::from_parts(0, 4786)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -489,8 +507,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 15_779_000 picoseconds. - Weight::from_parts(16_213_000, 0) + // Minimum execution time: 19_203_000 picoseconds. + Weight::from_parts(19_797_000, 0) .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -501,8 +519,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_774_000 picoseconds. - Weight::from_parts(1_873_000, 0) + // Minimum execution time: 2_129_000 picoseconds. + Weight::from_parts(2_266_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -512,8 +530,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_858_000 picoseconds. - Weight::from_parts(1_991_000, 0) + // Minimum execution time: 2_233_000 picoseconds. + Weight::from_parts(2_351_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -531,20 +549,38 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `408` // Estimated: `1893` - // Minimum execution time: 10_874_000 picoseconds. - Weight::from_parts(11_265_000, 0) + // Minimum execution time: 15_716_000 picoseconds. + Weight::from_parts(16_160_000, 0) .saturating_add(Weight::from_parts(0, 1893)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `Broker::SaleInfo` (r:1 w:0) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:1) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:2) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + fn force_reserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `11125` + // Estimated: `13506` + // Minimum execution time: 32_286_000 picoseconds. + Weight::from_parts(33_830_000, 0) + .saturating_add(Weight::from_parts(0, 13506)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } /// Storage: `Broker::Leases` (r:1 w:1) /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(401), added: 896, mode: `MaxEncodedLen`) fn swap_leases() -> Weight { // Proof Size summary in bytes: // Measured: `470` // Estimated: `1886` - // Minimum execution time: 6_525_000 picoseconds. - Weight::from_parts(6_769_000, 0) + // Minimum execution time: 8_887_000 picoseconds. + Weight::from_parts(9_178_000, 0) .saturating_add(Weight::from_parts(0, 1886)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -557,36 +593,36 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) /// Storage: `Broker::Status` (r:1 w:0) /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) + /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::AutoRenewals` (r:1 w:1) - /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(1002), added: 1497, mode: `MaxEncodedLen`) /// Storage: `Broker::Workplan` (r:0 w:1) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) fn enable_auto_renew() -> Weight { // Proof Size summary in bytes: - // Measured: `914` - // Estimated: `4698` - // Minimum execution time: 51_938_000 picoseconds. - Weight::from_parts(55_025_000, 4698) - .saturating_add(T::DbWeight::get().reads(8_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)) + // Measured: `2829` + // Estimated: `6196` + // Minimum execution time: 130_799_000 picoseconds. + Weight::from_parts(139_893_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(7)) } /// Storage: `Broker::AutoRenewals` (r:1 w:1) - /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(1002), added: 1497, mode: `MaxEncodedLen`) fn disable_auto_renew() -> Weight { // Proof Size summary in bytes: - // Measured: `480` - // Estimated: `1516` - // Minimum execution time: 9_628_000 picoseconds. - Weight::from_parts(10_400_000, 1516) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } + // Measured: `1307` + // Estimated: `2487` + // Minimum execution time: 22_945_000 picoseconds. + Weight::from_parts(24_855_000, 0) + .saturating_add(Weight::from_parts(0, 2487)) + .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: `PolkadotXcm::SupportedVersion` (r:1 w:0) @@ -601,11 +637,11 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn on_new_timeslice() -> Weight { // Proof Size summary in bytes: - // Measured: `322` - // Estimated: `3787` - // Minimum execution time: 45_561_000 picoseconds. - Weight::from_parts(47_306_000, 0) - .saturating_add(Weight::from_parts(0, 3787)) + // Measured: `323` + // Estimated: `3788` + // Minimum execution time: 56_864_000 picoseconds. + Weight::from_parts(59_119_000, 0) + .saturating_add(Weight::from_parts(0, 3788)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs index 8e010d768f643ceb55fd233b3a60e3b8e3c2c945..f3ab1b1cac8a50178040be3504bd3fafdb969c0c 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs @@ -16,28 +16,28 @@ //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2024-01-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-j8vvqcjr-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --extrinsic=* // --chain=coretime-rococo-dev -// --wasm-execution=compiled // --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_905_000 picoseconds. - Weight::from_parts(13_544_225, 0) + // Minimum execution time: 16_150_000 picoseconds. + Weight::from_parts(17_417_293, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 2 - .saturating_add(Weight::from_parts(596, 0).saturating_mul(z.into())) + // Standard Error: 10 + .saturating_add(Weight::from_parts(488, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -69,13 +69,13 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `262 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 38_729_000 picoseconds. - Weight::from_parts(27_942_442, 0) + // Minimum execution time: 47_027_000 picoseconds. + Weight::from_parts(33_446_171, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 648 - .saturating_add(Weight::from_parts(120_340, 0).saturating_mul(s.into())) - // Standard Error: 6 - .saturating_add(Weight::from_parts(1_578, 0).saturating_mul(z.into())) + // Standard Error: 1_434 + .saturating_add(Weight::from_parts(152_452, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_012, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -87,13 +87,13 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 25_936_000 picoseconds. - Weight::from_parts(16_537_903, 0) + // Minimum execution time: 32_131_000 picoseconds. + Weight::from_parts(18_539_623, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 412 - .saturating_add(Weight::from_parts(105_835, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_534, 0).saturating_mul(z.into())) + // Standard Error: 1_460 + .saturating_add(Weight::from_parts(140_999, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_033, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -107,58 +107,61 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `385 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 45_291_000 picoseconds. - Weight::from_parts(31_294_385, 0) + // Minimum execution time: 53_701_000 picoseconds. + Weight::from_parts(32_431_551, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 816 - .saturating_add(Weight::from_parts(152_838, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_638, 0).saturating_mul(z.into())) + // Standard Error: 2_797 + .saturating_add(Weight::from_parts(255_676, 0).saturating_mul(s.into())) + // Standard Error: 27 + .saturating_add(Weight::from_parts(2_261, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `262 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 26_585_000 picoseconds. - Weight::from_parts(27_424_168, 0) + // Minimum execution time: 30_011_000 picoseconds. + Weight::from_parts(32_146_378, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 732 - .saturating_add(Weight::from_parts(123_460, 0).saturating_mul(s.into())) + // Standard Error: 1_455 + .saturating_add(Weight::from_parts(160_784, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 15_228_000 picoseconds. - Weight::from_parts(15_568_631, 0) + // Minimum execution time: 16_968_000 picoseconds. + Weight::from_parts(16_851_993, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 441 - .saturating_add(Weight::from_parts(107_463, 0).saturating_mul(s.into())) + // Standard Error: 793 + .saturating_add(Weight::from_parts(142_320, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `449 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 28_033_000 picoseconds. - Weight::from_parts(29_228_827, 0) + // Minimum execution time: 31_706_000 picoseconds. + Weight::from_parts(33_679_423, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 748 - .saturating_add(Weight::from_parts(117_495, 0).saturating_mul(s.into())) + // Standard Error: 1_154 + .saturating_add(Weight::from_parts(145_059, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs index 7fb492173dad9b88be6d32ab063c10e532155f53..b2b8cd6e5349da82003132ef3c5a587943af9595 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! 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-12-18, 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: `902e7ad7764b`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=coretime-rococo-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=coretime-rococo-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -62,14 +64,16 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 19_121_000 picoseconds. - Weight::from_parts(19_582_000, 0) + // Minimum execution time: 23_660_000 picoseconds. + Weight::from_parts(24_537_000, 0) .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -84,18 +88,20 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 61_722_000 picoseconds. - Weight::from_parts(63_616_000, 0) + // Minimum execution time: 74_005_000 picoseconds. + Weight::from_parts(75_355_000, 0) .saturating_add(Weight::from_parts(0, 3571)) - .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::Regions` (r:1 w:1) /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(86), added: 2561, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::DeliveryFeeFactor` (r:1 w:0) - /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: Some(28), added: 2503, 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) @@ -107,17 +113,17 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Storage: `ParachainSystem::RelevantMessagingState` (r:1 w:0) /// Proof: `ParachainSystem::RelevantMessagingState` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) - /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: Some(1282), added: 1777, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) - /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: Some(105506), added: 107981, mode: `MaxEncodedLen`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: // Measured: `377` // Estimated: `3842` - // Minimum execution time: 97_823_000 picoseconds. - Weight::from_parts(102_022_000, 0) + // Minimum execution time: 116_231_000 picoseconds. + Weight::from_parts(121_254_000, 0) .saturating_add(Weight::from_parts(0, 3842)) - .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `Benchmark::Override` (r:0 w:0) @@ -130,13 +136,16 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 8_397_000 picoseconds. - Weight::from_parts(8_773_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `32` + // Estimated: `1517` + // Minimum execution time: 11_498_000 picoseconds. + Weight::from_parts(11_867_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -144,8 +153,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_806_000 picoseconds. - Weight::from_parts(6_106_000, 0) + // Minimum execution time: 7_163_000 picoseconds. + Weight::from_parts(7_501_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -155,8 +164,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_802_000 picoseconds. - Weight::from_parts(1_939_000, 0) + // Minimum execution time: 2_188_000 picoseconds. + Weight::from_parts(2_356_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -180,8 +189,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 24_300_000 picoseconds. - Weight::from_parts(25_359_000, 0) + // Minimum execution time: 30_503_000 picoseconds. + Weight::from_parts(31_361_000, 0) .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -204,8 +213,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `292` // Estimated: `3757` - // Minimum execution time: 27_579_000 picoseconds. - Weight::from_parts(28_414_000, 0) + // Minimum execution time: 35_562_000 picoseconds. + Weight::from_parts(36_710_000, 0) .saturating_add(Weight::from_parts(0, 3757)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -216,45 +225,45 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_762_000 picoseconds. - Weight::from_parts(1_884_000, 0) + // Minimum execution time: 2_223_000 picoseconds. + Weight::from_parts(2_432_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `89` - // Estimated: `13454` - // Minimum execution time: 16_512_000 picoseconds. - Weight::from_parts(16_818_000, 0) - .saturating_add(Weight::from_parts(0, 13454)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15929` + // Minimum execution time: 21_863_000 picoseconds. + Weight::from_parts(22_213_000, 0) + .saturating_add(Weight::from_parts(0, 15929)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `93` - // Estimated: `13458` - // Minimum execution time: 16_368_000 picoseconds. - Weight::from_parts(16_887_000, 0) - .saturating_add(Weight::from_parts(0, 13458)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15933` + // Minimum execution time: 22_044_000 picoseconds. + Weight::from_parts(22_548_000, 0) + .saturating_add(Weight::from_parts(0, 15933)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `15946` - // Minimum execution time: 17_661_000 picoseconds. - Weight::from_parts(17_963_000, 0) - .saturating_add(Weight::from_parts(0, 15946)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18421` + // Minimum execution time: 24_336_000 picoseconds. + Weight::from_parts(25_075_000, 0) + .saturating_add(Weight::from_parts(0, 18421)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -272,36 +281,36 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `142` // Estimated: `6082` - // Minimum execution time: 24_498_000 picoseconds. - Weight::from_parts(25_339_000, 0) + // Minimum execution time: 30_160_000 picoseconds. + Weight::from_parts(30_807_000, 0) .saturating_add(Weight::from_parts(0, 6082)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `136` - // Estimated: `11026` - // Minimum execution time: 10_675_000 picoseconds. - Weight::from_parts(11_106_000, 0) - .saturating_add(Weight::from_parts(0, 11026)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `109` + // Estimated: `13474` + // Minimum execution time: 16_129_000 picoseconds. + Weight::from_parts(16_686_000, 0) + .saturating_add(Weight::from_parts(0, 13474)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `100` - // Estimated: `13465` - // Minimum execution time: 16_520_000 picoseconds. - Weight::from_parts(16_915_000, 0) - .saturating_add(Weight::from_parts(0, 13465)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15940` + // Minimum execution time: 21_844_000 picoseconds. + Weight::from_parts(22_452_000, 0) + .saturating_add(Weight::from_parts(0, 15940)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -316,11 +325,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: // Measured: `142` - // Estimated: `13507` - // Minimum execution time: 32_851_000 picoseconds. - Weight::from_parts(33_772_000, 0) - .saturating_add(Weight::from_parts(0, 13507)) - .saturating_add(T::DbWeight::get().reads(10)) + // Estimated: `15982` + // Minimum execution time: 42_336_000 picoseconds. + Weight::from_parts(43_502_000, 0) + .saturating_add(Weight::from_parts(0, 15982)) + .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -331,8 +340,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_373_000 picoseconds. - Weight::from_parts(3_534_000, 0) + // Minimum execution time: 4_682_000 picoseconds. + Weight::from_parts(4_902_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -343,22 +352,24 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 26_027_000 picoseconds. - Weight::from_parts(26_467_000, 0) + // Minimum execution time: 27_848_000 picoseconds. + Weight::from_parts(28_267_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 35_692_000 picoseconds. - Weight::from_parts(36_136_000, 0) + // Minimum execution time: 41_653_000 picoseconds. + Weight::from_parts(42_316_000, 0) .saturating_add(Weight::from_parts(0, 3555)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs index f69736e3145141c4427a2d7222214a4c5c899ded..dc21e2ea117fbfd9877e5a1f1c31e41d6d1b8400 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs @@ -22,6 +22,7 @@ use alloc::vec::Vec; use frame_support::weights::Weight; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::{ latest::{prelude::*, AssetTransferFilter}, DoubleEncoded, @@ -84,7 +85,11 @@ impl<Call> XcmWeightInfo<Call> for CoretimeRococoXcmWeight<Call> { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::<Runtime>::transfer_reserve_asset()) } - fn transact(_origin_type: &OriginKind, _call: &DoubleEncoded<Call>) -> Weight { + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option<Weight>, + _call: &DoubleEncoded<Call>, + ) -> Weight { XcmGeneric::<Runtime>::transact() } fn hrmp_new_channel_open_request( @@ -251,8 +256,16 @@ impl<Call> XcmWeightInfo<Call> for CoretimeRococoXcmWeight<Call> { fn unpaid_execution(_: &WeightLimit, _: &Option<Location>) -> Weight { XcmGeneric::<Runtime>::unpaid_execution() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::<Runtime>::set_asset_claimer() + fn set_hints(hints: &BoundedVec<Hint, HintNumVariants>) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::<Runtime>::asset_claimer()); + }, + } + } + weight } fn execute_with_origin(_: &Option<InteriorLocation>, _: &Xcm<Call>) -> Weight { XcmGeneric::<Runtime>::execute_with_origin() diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index d207c09ffcd8d10205f4879af1c700800c354fdd..cdcba6134bf82876d18369bf903ec6c2671edfe8 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -331,7 +331,7 @@ impl<T: frame_system::Config> WeightInfo<T> { // Minimum execution time: 650_000 picoseconds. Weight::from_parts(673_000, 0) } - pub fn set_asset_claimer() -> Weight { + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/tests/tests.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/tests/tests.rs index 2cabce567b6e73a8f833d6922539580e6d23e9f6..89a593ab0f576690186d19584c73b10c49717b7c 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/tests/tests.rs @@ -16,7 +16,9 @@ #![cfg(test)] -use coretime_rococo_runtime::xcm_config::LocationToAccountId; +use coretime_rococo_runtime::{ + xcm_config::LocationToAccountId, Block, Runtime, RuntimeCall, RuntimeOrigin, +}; use parachains_common::AccountId; use sp_core::crypto::Ss58Codec; use xcm::latest::prelude::*; @@ -132,3 +134,13 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml index 149fa5d0b045c22da61def379263bad9d2182b53..915926ff9894ed3164dd37aeee828aa7229bb863 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Westend's Coretime parachain runtime" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -31,8 +33,8 @@ frame-try-runtime = { optional = true, workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } -pallet-message-queue = { workspace = true } pallet-broker = { workspace = true } +pallet-message-queue = { workspace = true } pallet-multisig = { workspace = true } pallet-proxy = { workspace = true } pallet-session = { workspace = true } @@ -44,8 +46,8 @@ sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } -sp-inherents = { workspace = true } sp-genesis-builder = { workspace = true } +sp-inherents = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } @@ -68,18 +70,21 @@ xcm-runtime-apis = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-utility = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } parachains-common = { workspace = true } testnet-parachains-constants = { features = ["westend"], workspace = true } +[dev-dependencies] +parachains-runtimes-test-utils = { workspace = true, default-features = true } + [features] default = ["std"] std = [ @@ -87,11 +92,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", @@ -147,6 +152,7 @@ std = [ runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -172,11 +178,13 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs index f0c03849750adc0ca81187926e921131c57ced45..985e64fb76f9ab4d177a4d43191b5d1f15429ab3 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs @@ -127,6 +127,12 @@ impl CoretimeInterface for CoretimeAllocator { use crate::coretime::CoretimeProviderCalls::RequestCoreCount; let request_core_count_call = RelayRuntimePallets::Coretime(RequestCoreCount(count)); + // Weight for `request_core_count` from westend benchmarks: + // `ref_time` = 7889000 + (3 * 25000000) + (1 * 100000000) = 182889000 + // `proof_size` = 1636 + // Add 5% to each component and round to 2 significant figures. + let call_weight = Weight::from_parts(190_000_000, 1700); + let message = Xcm(vec![ Instruction::UnpaidExecution { weight_limit: WeightLimit::Unlimited, @@ -135,6 +141,7 @@ impl CoretimeInterface for CoretimeAllocator { Instruction::Transact { origin_kind: OriginKind::Native, call: request_core_count_call.encode().into(), + fallback_max_weight: Some(call_weight), }, ]); @@ -164,6 +171,7 @@ impl CoretimeInterface for CoretimeAllocator { Instruction::Transact { origin_kind: OriginKind::Native, call: request_revenue_info_at_call.encode().into(), + fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)), }, ]); @@ -192,6 +200,7 @@ impl CoretimeInterface for CoretimeAllocator { Instruction::Transact { origin_kind: OriginKind::Native, call: credit_account_call.encode().into(), + fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)), }, ]); @@ -216,6 +225,12 @@ impl CoretimeInterface for CoretimeAllocator { ) { use crate::coretime::CoretimeProviderCalls::AssignCore; + // Weight for `assign_core` from westend benchmarks: + // `ref_time` = 10177115 + (1 * 25000000) + (2 * 100000000) + (57600 * 13932) = 937660315 + // `proof_size` = 3612 + // Add 5% to each component and round to 2 significant figures. + let call_weight = Weight::from_parts(980_000_000, 3800); + // The relay chain currently only allows `assign_core` to be called with a complete mask // and only ever with increasing `begin`. The assignments must be truncated to avoid // dropping that core's assignment completely. @@ -256,6 +271,7 @@ impl CoretimeInterface for CoretimeAllocator { Instruction::Transact { origin_kind: OriginKind::Native, call: assign_core_call.encode().into(), + fallback_max_weight: Some(call_weight), }, ]); diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index 098a17cc998419de5894bdb92e085cd3f56f57a9..7312c9c1639d29a69b11ee520e0b21d6289f815a 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -99,18 +99,20 @@ pub type SignedBlock = generic::SignedBlock<Block>; pub type BlockId = generic::BlockId<Block>; /// The TransactionExtension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender<Runtime>, - frame_system::CheckSpecVersion<Runtime>, - frame_system::CheckTxVersion<Runtime>, - frame_system::CheckGenesis<Runtime>, - frame_system::CheckEra<Runtime>, - frame_system::CheckNonce<Runtime>, - frame_system::CheckWeight<Runtime>, - pallet_transaction_payment::ChargeTransactionPayment<Runtime>, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim<Runtime>, - frame_metadata_hash_extension::CheckMetadataHash<Runtime>, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckTxVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckEra<Runtime>, + frame_system::CheckNonce<Runtime>, + frame_system::CheckWeight<Runtime>, + pallet_transaction_payment::ChargeTransactionPayment<Runtime>, + frame_metadata_hash_extension::CheckMetadataHash<Runtime>, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -127,6 +129,7 @@ pub type Migrations = ( pallet_broker::migration::MigrateV3ToV4<Runtime, BrokerMigrationV4BlockConversion>, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion<Runtime>, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1<Runtime>, ); /// Executive: handles dispatch to the various modules. @@ -150,7 +153,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("coretime-westend"), impl_name: alloc::borrow::Cow::Borrowed("coretime-westend"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, @@ -221,6 +224,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo<Runtime>; +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -617,6 +624,7 @@ construct_runtime!( ParachainSystem: cumulus_pallet_parachain_system = 1, Timestamp: pallet_timestamp = 3, ParachainInfo: parachain_info = 4, + WeightReclaim: cumulus_pallet_weight_reclaim = 5, // Monetary stuff. Balances: pallet_balances = 10, @@ -664,6 +672,7 @@ mod benches { // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); } @@ -827,7 +836,8 @@ impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> { - match asset.try_as::<AssetId>() { + let latest_asset_id: Result<AssetId, ()> = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::TokenRelayLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) @@ -1125,25 +1135,17 @@ impl_runtime_apis! { } fn alias_origin() -> Result<(Location, Location), BenchmarkError> { - Err(BenchmarkError::Skip) + let origin = Location::new(1, [Parachain(1000)]); + let target = Location::new(1, [Parachain(1000), AccountId32 { id: [128u8; 32], network: None }]); + Ok((origin, target)) } } type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::<Runtime>; type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::<Runtime>; - let whitelist: Vec<TrackedStorageKey> = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec<TrackedStorageKey> = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::<BenchmarkBatch>::new(); let params = (&config, &whitelist); diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 0000000000000000000000000000000000000000..55d52f4b04c8788f1bc05472516e1de4e6d65c92 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=coretime-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo<T>(PhantomData<T>); +impl<T: frame_system::Config> cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo<T> { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 6_658_000 picoseconds. + Weight::from_parts(6_905_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system_extensions.rs index d928b73613a3f110dc373ea91070377b644c4bbb..9527e0c5549a788b5d5995aa65a0ed30c909f426 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system_extensions.rs @@ -129,4 +129,18 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 3_687_000 picoseconds. + Weight::from_parts(6_192_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs index 24c4f50e6ab8b8c46ae1bfa177c8afada04a795e..7fee4a728b9ef750224efeac78408ca58827d643 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs @@ -19,6 +19,7 @@ pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs index 74b1c4e470297e212825a94da1886784e869ccd9..a0eee2d99efa3c2d84cc8ec5bab3fd4190db51f7 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_broker` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-06-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-x5tnzzy-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-acd6uxux-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<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_899_000 picoseconds. - Weight::from_parts(2_051_000, 0) + // Minimum execution time: 2_274_000 picoseconds. + Weight::from_parts(2_421_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -65,8 +65,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `10888` // Estimated: `13506` - // Minimum execution time: 21_965_000 picoseconds. - Weight::from_parts(22_774_000, 0) + // Minimum execution time: 26_257_000 picoseconds. + Weight::from_parts(26_802_000, 0) .saturating_add(Weight::from_parts(0, 13506)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -77,8 +77,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `12090` // Estimated: `13506` - // Minimum execution time: 20_748_000 picoseconds. - Weight::from_parts(21_464_000, 0) + // Minimum execution time: 24_692_000 picoseconds. + Weight::from_parts(25_275_000, 0) .saturating_add(Weight::from_parts(0, 13506)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -93,8 +93,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `146` // Estimated: `1631` - // Minimum execution time: 10_269_000 picoseconds. - Weight::from_parts(10_508_000, 0) + // Minimum execution time: 13_872_000 picoseconds. + Weight::from_parts(14_509_000, 0) .saturating_add(Weight::from_parts(0, 1631)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -121,6 +121,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::LastRelayChainBlockNumber` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::InstaPoolIo` (r:3 w:3) /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::AutoRenewals` (r:1 w:1) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(201), added: 696, mode: `MaxEncodedLen`) /// Storage: `Broker::SaleInfo` (r:0 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) /// Storage: `Broker::Status` (r:0 w:1) @@ -132,32 +134,34 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `12279` // Estimated: `14805 + n * (1 ±0)` - // Minimum execution time: 41_900_000 picoseconds. - Weight::from_parts(80_392_728, 0) + // Minimum execution time: 52_916_000 picoseconds. + Weight::from_parts(96_122_236, 0) .saturating_add(Weight::from_parts(0, 14805)) - // Standard Error: 870 - .saturating_add(Weight::from_parts(4_361, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(13)) - .saturating_add(T::DbWeight::get().writes(26)) + // Standard Error: 969 + .saturating_add(Weight::from_parts(5_732, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(14)) + .saturating_add(T::DbWeight::get().writes(27)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } /// Storage: `Broker::Status` (r:1 w:0) /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) /// Storage: `Broker::SaleInfo` (r:1 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:0) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`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: `Broker::Regions` (r:0 w:1) /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(86), added: 2561, mode: `MaxEncodedLen`) fn purchase() -> Weight { // Proof Size summary in bytes: - // Measured: `332` + // Measured: `437` // Estimated: `3593` - // Minimum execution time: 40_911_000 picoseconds. - Weight::from_parts(43_102_000, 0) + // Minimum execution time: 56_955_000 picoseconds. + Weight::from_parts(59_005_000, 0) .saturating_add(Weight::from_parts(0, 3593)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `Broker::Configuration` (r:1 w:0) /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) @@ -169,16 +173,18 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { /// Proof: `Broker::PotentialRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::Workplan` (r:0 w:1) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) fn renew() -> Weight { // Proof Size summary in bytes: - // Measured: `450` + // Measured: `658` // Estimated: `4698` - // Minimum execution time: 70_257_000 picoseconds. - Weight::from_parts(73_889_000, 0) + // Minimum execution time: 108_853_000 picoseconds. + Weight::from_parts(117_467_000, 0) .saturating_add(Weight::from_parts(0, 4698)) - .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `Broker::Regions` (r:1 w:1) @@ -187,8 +193,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `358` // Estimated: `3551` - // Minimum execution time: 13_302_000 picoseconds. - Weight::from_parts(13_852_000, 0) + // Minimum execution time: 16_922_000 picoseconds. + Weight::from_parts(17_544_000, 0) .saturating_add(Weight::from_parts(0, 3551)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -199,8 +205,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `358` // Estimated: `3551` - // Minimum execution time: 14_927_000 picoseconds. - Weight::from_parts(15_553_000, 0) + // Minimum execution time: 18_762_000 picoseconds. + Weight::from_parts(19_162_000, 0) .saturating_add(Weight::from_parts(0, 3551)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -211,8 +217,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `358` // Estimated: `3551` - // Minimum execution time: 16_237_000 picoseconds. - Weight::from_parts(16_995_000, 0) + // Minimum execution time: 20_297_000 picoseconds. + Weight::from_parts(20_767_000, 0) .saturating_add(Weight::from_parts(0, 3551)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(3)) @@ -229,8 +235,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `736` // Estimated: `4681` - // Minimum execution time: 24_621_000 picoseconds. - Weight::from_parts(25_165_000, 0) + // Minimum execution time: 31_347_000 picoseconds. + Weight::from_parts(32_259_000, 0) .saturating_add(Weight::from_parts(0, 4681)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -249,8 +255,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `802` // Estimated: `5996` - // Minimum execution time: 29_832_000 picoseconds. - Weight::from_parts(30_894_000, 0) + // Minimum execution time: 38_310_000 picoseconds. + Weight::from_parts(39_777_000, 0) .saturating_add(Weight::from_parts(0, 5996)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) @@ -264,13 +270,13 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { /// The range of component `m` is `[1, 3]`. fn claim_revenue(m: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `652` + // Measured: `671` // Estimated: `6196 + m * (2520 ±0)` - // Minimum execution time: 55_390_000 picoseconds. - Weight::from_parts(56_124_789, 0) + // Minimum execution time: 65_960_000 picoseconds. + Weight::from_parts(66_194_985, 0) .saturating_add(Weight::from_parts(0, 6196)) - // Standard Error: 41_724 - .saturating_add(Weight::from_parts(1_551_266, 0).saturating_mul(m.into())) + // Standard Error: 42_455 + .saturating_add(Weight::from_parts(1_808_497, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(m.into()))) .saturating_add(T::DbWeight::get().writes(5)) @@ -290,11 +296,11 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn purchase_credit() -> Weight { // Proof Size summary in bytes: - // Measured: `320` - // Estimated: `3785` - // Minimum execution time: 59_759_000 picoseconds. - Weight::from_parts(61_310_000, 0) - .saturating_add(Weight::from_parts(0, 3785)) + // Measured: `321` + // Estimated: `3786` + // Minimum execution time: 69_918_000 picoseconds. + Weight::from_parts(72_853_000, 0) + .saturating_add(Weight::from_parts(0, 3786)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -306,8 +312,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `466` // Estimated: `3551` - // Minimum execution time: 37_007_000 picoseconds. - Weight::from_parts(51_927_000, 0) + // Minimum execution time: 44_775_000 picoseconds. + Weight::from_parts(58_978_000, 0) .saturating_add(Weight::from_parts(0, 3551)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -322,8 +328,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `463` // Estimated: `3533` - // Minimum execution time: 86_563_000 picoseconds. - Weight::from_parts(91_274_000, 0) + // Minimum execution time: 67_098_000 picoseconds. + Weight::from_parts(93_626_000, 0) .saturating_add(Weight::from_parts(0, 3533)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -338,10 +344,10 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn drop_history() -> Weight { // Proof Size summary in bytes: - // Measured: `857` + // Measured: `979` // Estimated: `3593` - // Minimum execution time: 93_655_000 picoseconds. - Weight::from_parts(98_160_000, 0) + // Minimum execution time: 89_463_000 picoseconds. + Weight::from_parts(113_286_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)) @@ -354,8 +360,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `556` // Estimated: `4698` - // Minimum execution time: 33_985_000 picoseconds. - Weight::from_parts(43_618_000, 0) + // Minimum execution time: 42_073_000 picoseconds. + Weight::from_parts(52_211_000, 0) .saturating_add(Weight::from_parts(0, 4698)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -371,30 +377,26 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// The range of component `n` is `[0, 1000]`. - fn request_core_count(n: u32, ) -> Weight { + fn request_core_count(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 18_778_000 picoseconds. - Weight::from_parts(19_543_425, 0) + // Minimum execution time: 22_937_000 picoseconds. + Weight::from_parts(23_898_154, 0) .saturating_add(Weight::from_parts(0, 3539)) - // Standard Error: 41 - .saturating_add(Weight::from_parts(33, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Broker::CoreCountInbox` (r:1 w:1) /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 1000]`. - fn process_core_count(n: u32, ) -> Weight { + fn process_core_count(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `266` // Estimated: `1487` - // Minimum execution time: 5_505_000 picoseconds. - Weight::from_parts(5_982_015, 0) + // Minimum execution time: 7_650_000 picoseconds. + Weight::from_parts(8_166_809, 0) .saturating_add(Weight::from_parts(0, 1487)) - // Standard Error: 13 - .saturating_add(Weight::from_parts(44, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -402,40 +404,54 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { /// Proof: `Broker::RevenueInbox` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) /// Storage: `Broker::InstaPoolHistory` (r:1 w:1) /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:2 w:1) + /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn process_revenue() -> Weight { // Proof Size summary in bytes: - // Measured: `442` + // Measured: `461` // Estimated: `6196` - // Minimum execution time: 38_128_000 picoseconds. - Weight::from_parts(40_979_000, 0) + // Minimum execution time: 53_023_000 picoseconds. + Weight::from_parts(54_564_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes(4)) } + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::InstaPoolIo` (r:3 w:3) /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) /// Storage: `Broker::Reservations` (r:1 w:0) /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) /// Storage: `Broker::Leases` (r:1 w:1) /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(81), added: 576, mode: `MaxEncodedLen`) + /// Storage: `Broker::AutoRenewals` (r:1 w:1) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(201), added: 696, mode: `MaxEncodedLen`) + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::PotentialRenewals` (r:20 w:40) + /// Proof: `Broker::PotentialRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:21 w:20) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Broker::SaleInfo` (r:0 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) - /// Storage: `Broker::Workplan` (r:0 w:20) + /// Storage: `Broker::Workplan` (r:0 w:1000) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 1000]`. fn rotate_sale(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `12194` - // Estimated: `13506` - // Minimum execution time: 49_041_000 picoseconds. - Weight::from_parts(50_522_788, 0) - .saturating_add(Weight::from_parts(0, 13506)) - // Standard Error: 72 - .saturating_add(Weight::from_parts(78, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(25)) + // Measured: `16480` + // Estimated: `69404 + n * (8 ±1)` + // Minimum execution time: 29_313_000 picoseconds. + Weight::from_parts(746_062_644, 0) + .saturating_add(Weight::from_parts(0, 69404)) + // Standard Error: 22_496 + .saturating_add(Weight::from_parts(1_545_204, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(44)) + .saturating_add(T::DbWeight::get().writes(57)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(n.into())) } /// Storage: `Broker::InstaPoolIo` (r:1 w:0) /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) @@ -445,8 +461,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `42` // Estimated: `3493` - // Minimum execution time: 5_903_000 picoseconds. - Weight::from_parts(6_202_000, 0) + // Minimum execution time: 7_625_000 picoseconds. + Weight::from_parts(7_910_000, 0) .saturating_add(Weight::from_parts(0, 3493)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -469,8 +485,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `1321` // Estimated: `4786` - // Minimum execution time: 31_412_000 picoseconds. - Weight::from_parts(31_964_000, 0) + // Minimum execution time: 36_572_000 picoseconds. + Weight::from_parts(37_316_000, 0) .saturating_add(Weight::from_parts(0, 4786)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -489,8 +505,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 14_098_000 picoseconds. - Weight::from_parts(14_554_000, 0) + // Minimum execution time: 18_362_000 picoseconds. + Weight::from_parts(18_653_000, 0) .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -501,8 +517,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_723_000 picoseconds. - Weight::from_parts(1_822_000, 0) + // Minimum execution time: 2_193_000 picoseconds. + Weight::from_parts(2_393_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -512,8 +528,8 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_865_000 picoseconds. - Weight::from_parts(1_983_000, 0) + // Minimum execution time: 2_344_000 picoseconds. + Weight::from_parts(2_486_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -531,20 +547,38 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `408` // Estimated: `1893` - // Minimum execution time: 10_387_000 picoseconds. - Weight::from_parts(10_819_000, 0) + // Minimum execution time: 15_443_000 picoseconds. + Weight::from_parts(15_753_000, 0) .saturating_add(Weight::from_parts(0, 1893)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `Broker::SaleInfo` (r:1 w:0) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:1) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:2) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + fn force_reserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `11125` + // Estimated: `13506` + // Minimum execution time: 31_464_000 picoseconds. + Weight::from_parts(32_798_000, 0) + .saturating_add(Weight::from_parts(0, 13506)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } /// Storage: `Broker::Leases` (r:1 w:1) /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(81), added: 576, mode: `MaxEncodedLen`) fn swap_leases() -> Weight { // Proof Size summary in bytes: // Measured: `150` // Estimated: `1566` - // Minimum execution time: 5_996_000 picoseconds. - Weight::from_parts(6_278_000, 0) + // Minimum execution time: 8_637_000 picoseconds. + Weight::from_parts(8_883_000, 0) .saturating_add(Weight::from_parts(0, 1566)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -557,44 +591,44 @@ impl<T: frame_system::Config> pallet_broker::WeightInfo for WeightInfo<T> { /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) /// Storage: `Broker::Status` (r:1 w:0) /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) + /// Storage: `System::Account` (r:2 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::AutoRenewals` (r:1 w:1) - /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(201), added: 696, mode: `MaxEncodedLen`) /// Storage: `Broker::Workplan` (r:0 w:1) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) fn enable_auto_renew() -> Weight { // Proof Size summary in bytes: - // Measured: `914` - // Estimated: `4698` - // Minimum execution time: 51_938_000 picoseconds. - Weight::from_parts(55_025_000, 4698) - .saturating_add(T::DbWeight::get().reads(8_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)) + // Measured: `1451` + // Estimated: `6196` + // Minimum execution time: 120_585_000 picoseconds. + Weight::from_parts(148_755_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `Broker::AutoRenewals` (r:1 w:1) - /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(201), added: 696, mode: `MaxEncodedLen`) fn disable_auto_renew() -> Weight { // Proof Size summary in bytes: - // Measured: `480` - // Estimated: `1516` - // Minimum execution time: 9_628_000 picoseconds. - Weight::from_parts(10_400_000, 1516) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } + // Measured: `506` + // Estimated: `1686` + // Minimum execution time: 18_235_000 picoseconds. + Weight::from_parts(19_113_000, 0) + .saturating_add(Weight::from_parts(0, 1686)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn on_new_timeslice() -> Weight { // Proof Size summary in bytes: - // Measured: `0` + // Measured: `103` // Estimated: `3593` - // Minimum execution time: 2_187_000 picoseconds. - Weight::from_parts(2_372_000, 0) + // Minimum execution time: 4_863_000 picoseconds. + Weight::from_parts(5_045_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs index 1aaf3f4a6fb9dd88f83042915080b08bb735e664..044356f1e146728de721c78eba2e7b512d234113 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs @@ -17,27 +17,27 @@ //! Autogenerated weights for `pallet_multisig` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --extrinsic=* // --chain=coretime-westend-dev -// --wasm-execution=compiled // --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_938_000 picoseconds. - Weight::from_parts(13_021_007, 0) + // Minimum execution time: 16_090_000 picoseconds. + Weight::from_parts(16_926_991, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 4 - .saturating_add(Weight::from_parts(482, 0).saturating_mul(z.into())) + // Standard Error: 6 + .saturating_add(Weight::from_parts(500, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -69,13 +69,13 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `262 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 37_643_000 picoseconds. - Weight::from_parts(27_088_068, 0) + // Minimum execution time: 46_739_000 picoseconds. + Weight::from_parts(34_253_833, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 828 - .saturating_add(Weight::from_parts(123_693, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_456, 0).saturating_mul(z.into())) + // Standard Error: 1_258 + .saturating_add(Weight::from_parts(141_511, 0).saturating_mul(s.into())) + // Standard Error: 12 + .saturating_add(Weight::from_parts(1_969, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -87,13 +87,13 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 25_825_000 picoseconds. - Weight::from_parts(15_698_835, 0) + // Minimum execution time: 31_190_000 picoseconds. + Weight::from_parts(18_287_369, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 568 - .saturating_add(Weight::from_parts(111_928, 0).saturating_mul(s.into())) - // Standard Error: 5 - .saturating_add(Weight::from_parts(1_421, 0).saturating_mul(z.into())) + // Standard Error: 1_405 + .saturating_add(Weight::from_parts(143_414, 0).saturating_mul(s.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(2_047, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -107,58 +107,61 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `385 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 43_587_000 picoseconds. - Weight::from_parts(29_740_539, 0) + // Minimum execution time: 53_340_000 picoseconds. + Weight::from_parts(31_091_227, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 771 - .saturating_add(Weight::from_parts(154_861, 0).saturating_mul(s.into())) - // Standard Error: 7 - .saturating_add(Weight::from_parts(1_557, 0).saturating_mul(z.into())) + // Standard Error: 3_346 + .saturating_add(Weight::from_parts(256_292, 0).saturating_mul(s.into())) + // Standard Error: 32 + .saturating_add(Weight::from_parts(2_518, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `262 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 24_966_000 picoseconds. - Weight::from_parts(25_879_458, 0) + // Minimum execution time: 30_024_000 picoseconds. + Weight::from_parts(32_926_280, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 777 - .saturating_add(Weight::from_parts(122_823, 0).saturating_mul(s.into())) + // Standard Error: 1_559 + .saturating_add(Weight::from_parts(151_433, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 14_450_000 picoseconds. - Weight::from_parts(14_607_858, 0) + // Minimum execution time: 16_853_000 picoseconds. + Weight::from_parts(17_314_743, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 471 - .saturating_add(Weight::from_parts(107_007, 0).saturating_mul(s.into())) + // Standard Error: 1_022 + .saturating_add(Weight::from_parts(139_694, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `449 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 26_861_000 picoseconds. - Weight::from_parts(27_846_825, 0) + // Minimum execution time: 31_102_000 picoseconds. + Weight::from_parts(32_212_096, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 714 - .saturating_add(Weight::from_parts(116_914, 0).saturating_mul(s.into())) + // Standard Error: 1_524 + .saturating_add(Weight::from_parts(151_963, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs index fa588e982f0965218fa9cca7f93a20f9f70b37c5..7659b8a1ac7e1e417db05d43120e16d3912291cc 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! 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-12-18, 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: `eded932c29e2`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=coretime-westend-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=coretime-westend-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -62,14 +64,16 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 18_707_000 picoseconds. - Weight::from_parts(19_391_000, 0) + // Minimum execution time: 23_956_000 picoseconds. + Weight::from_parts(24_860_000, 0) .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -84,18 +88,20 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 61_874_000 picoseconds. - Weight::from_parts(63_862_000, 0) + // Minimum execution time: 74_020_000 picoseconds. + Weight::from_parts(76_288_000, 0) .saturating_add(Weight::from_parts(0, 3571)) - .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::Regions` (r:1 w:1) /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(86), added: 2561, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::DeliveryFeeFactor` (r:1 w:0) - /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: Some(28), added: 2503, 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) @@ -107,17 +113,17 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Storage: `ParachainSystem::RelevantMessagingState` (r:1 w:0) /// Proof: `ParachainSystem::RelevantMessagingState` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) - /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: Some(1282), added: 1777, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) - /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: Some(105506), added: 107981, mode: `MaxEncodedLen`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: // Measured: `377` // Estimated: `3842` - // Minimum execution time: 98_657_000 picoseconds. - Weight::from_parts(101_260_000, 0) + // Minimum execution time: 118_691_000 picoseconds. + Weight::from_parts(128_472_000, 0) .saturating_add(Weight::from_parts(0, 3842)) - .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `Benchmark::Override` (r:0 w:0) @@ -130,13 +136,16 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 8_455_000 picoseconds. - Weight::from_parts(8_842_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `32` + // Estimated: `1517` + // Minimum execution time: 11_608_000 picoseconds. + Weight::from_parts(12_117_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -144,8 +153,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_850_000 picoseconds. - Weight::from_parts(6_044_000, 0) + // Minimum execution time: 7_574_000 picoseconds. + Weight::from_parts(8_305_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -155,8 +164,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_754_000 picoseconds. - Weight::from_parts(1_832_000, 0) + // Minimum execution time: 2_438_000 picoseconds. + Weight::from_parts(2_663_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -180,8 +189,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 24_886_000 picoseconds. - Weight::from_parts(25_403_000, 0) + // Minimum execution time: 31_482_000 picoseconds. + Weight::from_parts(33_926_000, 0) .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -204,8 +213,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `292` // Estimated: `3757` - // Minimum execution time: 28_114_000 picoseconds. - Weight::from_parts(28_414_000, 0) + // Minimum execution time: 35_869_000 picoseconds. + Weight::from_parts(37_030_000, 0) .saturating_add(Weight::from_parts(0, 3757)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -216,45 +225,45 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_713_000 picoseconds. - Weight::from_parts(1_810_000, 0) + // Minimum execution time: 2_385_000 picoseconds. + Weight::from_parts(2_588_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `89` - // Estimated: `13454` - // Minimum execution time: 15_910_000 picoseconds. - Weight::from_parts(16_256_000, 0) - .saturating_add(Weight::from_parts(0, 13454)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15929` + // Minimum execution time: 21_919_000 picoseconds. + Weight::from_parts(22_926_000, 0) + .saturating_add(Weight::from_parts(0, 15929)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `93` - // Estimated: `13458` - // Minimum execution time: 15_801_000 picoseconds. - Weight::from_parts(16_298_000, 0) - .saturating_add(Weight::from_parts(0, 13458)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15933` + // Minimum execution time: 22_588_000 picoseconds. + Weight::from_parts(23_144_000, 0) + .saturating_add(Weight::from_parts(0, 15933)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `15946` - // Minimum execution time: 17_976_000 picoseconds. - Weight::from_parts(18_390_000, 0) - .saturating_add(Weight::from_parts(0, 15946)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18421` + // Minimum execution time: 25_527_000 picoseconds. + Weight::from_parts(26_002_000, 0) + .saturating_add(Weight::from_parts(0, 18421)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -272,36 +281,36 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `142` // Estimated: `6082` - // Minimum execution time: 24_723_000 picoseconds. - Weight::from_parts(25_531_000, 0) + // Minimum execution time: 30_751_000 picoseconds. + Weight::from_parts(31_977_000, 0) .saturating_add(Weight::from_parts(0, 6082)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `136` - // Estimated: `11026` - // Minimum execution time: 10_954_000 picoseconds. - Weight::from_parts(11_199_000, 0) - .saturating_add(Weight::from_parts(0, 11026)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `109` + // Estimated: `13474` + // Minimum execution time: 16_496_000 picoseconds. + Weight::from_parts(16_800_000, 0) + .saturating_add(Weight::from_parts(0, 13474)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `100` - // Estimated: `13465` - // Minimum execution time: 16_561_000 picoseconds. - Weight::from_parts(16_908_000, 0) - .saturating_add(Weight::from_parts(0, 13465)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15940` + // Minimum execution time: 22_667_000 picoseconds. + Weight::from_parts(23_049_000, 0) + .saturating_add(Weight::from_parts(0, 15940)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -316,11 +325,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: // Measured: `142` - // Estimated: `13507` - // Minimum execution time: 33_279_000 picoseconds. - Weight::from_parts(33_869_000, 0) - .saturating_add(Weight::from_parts(0, 13507)) - .saturating_add(T::DbWeight::get().reads(10)) + // Estimated: `15982` + // Minimum execution time: 43_208_000 picoseconds. + Weight::from_parts(44_012_000, 0) + .saturating_add(Weight::from_parts(0, 15982)) + .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -331,8 +340,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_405_000 picoseconds. - Weight::from_parts(3_489_000, 0) + // Minimum execution time: 4_726_000 picoseconds. + Weight::from_parts(4_989_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -343,22 +352,24 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 24_387_000 picoseconds. - Weight::from_parts(25_143_000, 0) + // Minimum execution time: 28_064_000 picoseconds. + Weight::from_parts(28_676_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 35_229_000 picoseconds. - Weight::from_parts(36_035_000, 0) + // Minimum execution time: 41_106_000 picoseconds. + Weight::from_parts(41_949_000, 0) .saturating_add(Weight::from_parts(0, 3555)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs index 1640baa38c9951d8d7f7b4e0ba82634030ea0b2d..2f7529481543dea80baf465becc75584f1331fa3 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs @@ -21,6 +21,7 @@ use alloc::vec::Vec; use frame_support::weights::Weight; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::{ latest::{prelude::*, AssetTransferFilter}, DoubleEncoded, @@ -83,7 +84,11 @@ impl<Call> XcmWeightInfo<Call> for CoretimeWestendXcmWeight<Call> { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::<Runtime>::transfer_reserve_asset()) } - fn transact(_origin_type: &OriginKind, _call: &DoubleEncoded<Call>) -> Weight { + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option<Weight>, + _call: &DoubleEncoded<Call>, + ) -> Weight { XcmGeneric::<Runtime>::transact() } fn hrmp_new_channel_open_request( @@ -172,8 +177,16 @@ impl<Call> XcmWeightInfo<Call> for CoretimeWestendXcmWeight<Call> { fn clear_error() -> Weight { XcmGeneric::<Runtime>::clear_error() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::<Runtime>::set_asset_claimer() + fn set_hints(hints: &BoundedVec<Hint, HintNumVariants>) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::<Runtime>::asset_claimer()); + }, + } + } + weight } fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::<Runtime>::claim_asset() @@ -248,8 +261,7 @@ impl<Call> XcmWeightInfo<Call> for CoretimeWestendXcmWeight<Call> { XcmGeneric::<Runtime>::clear_topic() } fn alias_origin(_: &Location) -> Weight { - // XCM Executor does not currently support alias origin operations - Weight::MAX + XcmGeneric::<Runtime>::alias_origin() } fn unpaid_execution(_: &WeightLimit, _: &Option<Location>) -> Weight { XcmGeneric::<Runtime>::unpaid_execution() diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index fb6e4631736d6689b8a863b4759620098f25242f..2d10ac16ea26b66bd4c5ec9be3f7b4a968985b5b 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -17,26 +17,28 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-08-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-svzsllib-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `9340d096ec0f`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=coretime-westend-dev +// --pallet=pallet_xcm_benchmarks::generic +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm_benchmarks::generic -// --chain=coretime-westend-dev -// --header=./cumulus/file_header.txt -// --template=./cumulus/templates/xcm-bench-template.hbs -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/ +// --template=cumulus/templates/xcm-bench-template.hbs +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -64,8 +66,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 29_463_000 picoseconds. - Weight::from_parts(30_178_000, 3571) + // Minimum execution time: 30_717_000 picoseconds. + Weight::from_parts(31_651_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -73,15 +75,26 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 568_000 picoseconds. - Weight::from_parts(608_000, 0) + // Minimum execution time: 618_000 picoseconds. + Weight::from_parts(659_000, 0) } + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) pub fn pay_fees() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 3_504_000 picoseconds. + Weight::from_parts(3_757_000, 3593) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_530_000 picoseconds. - Weight::from_parts(1_585_000, 0) + // Minimum execution time: 643_000 picoseconds. + Weight::from_parts(702_000, 0) } // Storage: `PolkadotXcm::Queries` (r:1 w:0) // Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -89,58 +102,65 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `32` // Estimated: `3497` - // Minimum execution time: 7_400_000 picoseconds. - Weight::from_parts(7_572_000, 3497) + // Minimum execution time: 7_799_000 picoseconds. + Weight::from_parts(8_037_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: 6_951_000 picoseconds. - Weight::from_parts(7_173_000, 0) + // Minimum execution time: 6_910_000 picoseconds. + Weight::from_parts(7_086_000, 0) } pub fn refund_surplus() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_245_000 picoseconds. - Weight::from_parts(1_342_000, 0) + // Minimum execution time: 1_257_000 picoseconds. + Weight::from_parts(1_384_000, 0) } pub fn set_error_handler() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 613_000 picoseconds. - Weight::from_parts(657_000, 0) + // Minimum execution time: 634_000 picoseconds. + Weight::from_parts(687_000, 0) } pub fn set_appendix() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 613_000 picoseconds. - Weight::from_parts(656_000, 0) + // Minimum execution time: 604_000 picoseconds. + Weight::from_parts(672_000, 0) } pub fn clear_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 570_000 picoseconds. - Weight::from_parts(608_000, 0) + // Minimum execution time: 593_000 picoseconds. + Weight::from_parts(643_000, 0) } pub fn descend_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 557_000 picoseconds. - Weight::from_parts(607_000, 0) + // Minimum execution time: 630_000 picoseconds. + Weight::from_parts(694_000, 0) + } + pub fn execute_with_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 706_000 picoseconds. + Weight::from_parts(764_000, 0) } pub fn clear_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 557_000 picoseconds. - Weight::from_parts(578_000, 0) + // Minimum execution time: 606_000 picoseconds. + Weight::from_parts(705_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -158,8 +178,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 26_179_000 picoseconds. - Weight::from_parts(27_089_000, 3571) + // Minimum execution time: 27_188_000 picoseconds. + Weight::from_parts(27_847_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -169,8 +189,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 10_724_000 picoseconds. - Weight::from_parts(10_896_000, 3555) + // Minimum execution time: 11_170_000 picoseconds. + Weight::from_parts(11_416_000, 3555) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -178,8 +198,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 567_000 picoseconds. - Weight::from_parts(623_000, 0) + // Minimum execution time: 590_000 picoseconds. + Weight::from_parts(653_000, 0) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:1 w:1) // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -197,8 +217,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 24_367_000 picoseconds. - Weight::from_parts(25_072_000, 3539) + // Minimum execution time: 25_196_000 picoseconds. + Weight::from_parts(25_641_000, 3539) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -208,44 +228,44 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_554_000 picoseconds. - Weight::from_parts(2_757_000, 0) + // Minimum execution time: 2_686_000 picoseconds. + Weight::from_parts(2_827_000, 0) .saturating_add(T::DbWeight::get().writes(1)) } pub fn burn_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 922_000 picoseconds. - Weight::from_parts(992_000, 0) + // Minimum execution time: 989_000 picoseconds. + Weight::from_parts(1_051_000, 0) } pub fn expect_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 688_000 picoseconds. - Weight::from_parts(723_000, 0) + // Minimum execution time: 713_000 picoseconds. + Weight::from_parts(766_000, 0) } pub fn expect_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 607_000 picoseconds. - Weight::from_parts(647_000, 0) + // Minimum execution time: 626_000 picoseconds. + Weight::from_parts(657_000, 0) } pub fn expect_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 591_000 picoseconds. - Weight::from_parts(620_000, 0) + // Minimum execution time: 595_000 picoseconds. + Weight::from_parts(639_000, 0) } pub fn expect_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 735_000 picoseconds. - Weight::from_parts(802_000, 0) + // Minimum execution time: 755_000 picoseconds. + Weight::from_parts(820_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -263,8 +283,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 29_923_000 picoseconds. - Weight::from_parts(30_770_000, 3571) + // Minimum execution time: 31_409_000 picoseconds. + Weight::from_parts(32_098_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -272,8 +292,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_884_000 picoseconds. - Weight::from_parts(3_088_000, 0) + // Minimum execution time: 3_258_000 picoseconds. + Weight::from_parts(3_448_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -291,8 +311,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 26_632_000 picoseconds. - Weight::from_parts(27_228_000, 3571) + // Minimum execution time: 27_200_000 picoseconds. + Weight::from_parts(28_299_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -300,49 +320,42 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 599_000 picoseconds. - Weight::from_parts(655_000, 0) + // Minimum execution time: 659_000 picoseconds. + Weight::from_parts(699_000, 0) } pub fn set_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 587_000 picoseconds. - Weight::from_parts(628_000, 0) + // Minimum execution time: 595_000 picoseconds. + Weight::from_parts(647_000, 0) } pub fn clear_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 572_000 picoseconds. - Weight::from_parts(631_000, 0) + // Minimum execution time: 583_000 picoseconds. + Weight::from_parts(617_000, 0) } pub fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 570_000 picoseconds. - Weight::from_parts(615_000, 0) + // Minimum execution time: 595_000 picoseconds. + Weight::from_parts(633_000, 0) } pub fn unpaid_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 624_000 picoseconds. - Weight::from_parts(659_000, 0) + // Minimum execution time: 610_000 picoseconds. + Weight::from_parts(670_000, 0) } - pub fn set_asset_claimer() -> Weight { + pub fn alias_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 707_000 picoseconds. - Weight::from_parts(749_000, 0) - } - pub fn execute_with_origin() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 713_000 picoseconds. - Weight::from_parts(776_000, 0) + // Minimum execution time: 630_000 picoseconds. + Weight::from_parts(700_000, 0) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs index 9f38975efae6d24cd21cc7299b9c7b09c2db58af..8a4879a1506eed39c1728ea62e3754687e75b501 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs @@ -39,7 +39,8 @@ use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::AccountIdConversion; use xcm::latest::{prelude::*, WESTEND_GENESIS_HASH}; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, + AccountId32Aliases, AliasChildLocation, AliasOriginRootUsingFilter, + AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsConcrete, @@ -54,6 +55,7 @@ use xcm_executor::XcmExecutor; parameter_types! { pub const RootLocation: Location = Location::here(); pub const TokenRelayLocation: Location = Location::parent(); + pub AssetHubLocation: Location = Location::new(1, [Parachain(1000)]); pub const RelayNetwork: Option<NetworkId> = Some(NetworkId::ByGenesis(WESTEND_GENESIS_HASH)); pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); pub UniversalLocation: InteriorLocation = @@ -191,6 +193,10 @@ pub type WaivedLocations = ( Equals<RelayTreasuryLocation>, ); +/// We allow locations to alias into their own child locations, as well as +/// AssetHub to alias into anything. +pub type Aliasers = (AliasChildLocation, AliasOriginRootUsingFilter<AssetHubLocation, Everything>); + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -232,7 +238,7 @@ impl xcm_executor::Config for XcmConfig { type UniversalAliases = Nothing; type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; - type Aliasers = Nothing; + type Aliasers = Aliasers; type TransactionalProcessor = FrameTransactionalProcessor; type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/tests/tests.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/tests/tests.rs index e391d71a9ab782e69a0897dc8a7f980190b8c9e1..976ce23d6e8741e351a2a7b075f96ab38000350c 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/tests/tests.rs @@ -16,7 +16,9 @@ #![cfg(test)] -use coretime_westend_runtime::xcm_config::LocationToAccountId; +use coretime_westend_runtime::{ + xcm_config::LocationToAccountId, Block, Runtime, RuntimeCall, RuntimeOrigin, +}; use parachains_common::AccountId; use sp_core::crypto::Ss58Codec; use xcm::latest::prelude::*; @@ -132,3 +134,13 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml index 09b4ef679d244696504761cb4d30216fef9c7807..1c1041a4317ef29d42c23e80bcde988d6ae1f69a 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" description = "Glutton parachain runtime." +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -18,11 +20,12 @@ frame-benchmarking = { optional = true, workspace = true } frame-executive = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -frame-system-rpc-runtime-api = { workspace = true } frame-system-benchmarking = { optional = true, workspace = true } +frame-system-rpc-runtime-api = { workspace = true } frame-try-runtime = { optional = true, workspace = true } pallet-aura = { workspace = true } pallet-glutton = { workspace = true } +pallet-message-queue = { workspace = true } pallet-sudo = { workspace = true } pallet-timestamp = { workspace = true } sp-api = { workspace = true } @@ -31,7 +34,6 @@ sp-consensus-aura = { workspace = true } sp-core = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } -pallet-message-queue = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } @@ -75,6 +77,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] std = [ "codec/std", diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs index fdf467ab64b8a11cf8672afe8ad47146e66a6b64..75f45297fe2cdade064fe3478ff1979aecb11ee2 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs @@ -102,7 +102,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("glutton-westend"), impl_name: alloc::borrow::Cow::Borrowed("glutton-westend"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -300,6 +300,7 @@ pub type TxExtension = ( frame_system::CheckEra<Runtime>, frame_system::CheckNonce<Runtime>, frame_system::CheckWeight<Runtime>, + frame_system::WeightReclaim<Runtime>, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/frame_system_extensions.rs index 4fbbb8d6f781d97aeedfa7d84779190464ff70a2..db9a14e2cf242ab40c4fb68aaa1a90fbe4605501 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/frame_system_extensions.rs @@ -16,28 +16,30 @@ //! Autogenerated weights for `frame_system_extensions` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-21, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-10-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `gleipnir`, CPU: `AMD Ryzen 9 7900X 12-Core Processor` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("glutton-westend-dev-1300")`, DB CACHE: 1024 +//! HOSTNAME: `697235d969a1`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 // Executed Command: -// ./target/release/polkadot-parachain +// frame-omni-bencher +// v1 // benchmark // pallet -// --wasm-execution=compiled +// --extrinsic=* +// --runtime=target/release/wbuild/glutton-westend-runtime/glutton_westend_runtime.wasm // --pallet=frame_system_extensions +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/glutton/glutton-westend/src/weights +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 // --no-storage-info -// --no-median-slopes // --no-min-squares -// --extrinsic=* -// --steps=2 -// --repeat=2 -// --json -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/ -// --chain=glutton-westend-dev-1300 +// --no-median-slopes +// --genesis-builder-policy=none #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,10 +56,10 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_genesis() -> Weight { // Proof Size summary in bytes: - // Measured: `54` + // Measured: `0` // Estimated: `3509` - // Minimum execution time: 3_908_000 picoseconds. - Weight::from_parts(4_007_000, 0) + // Minimum execution time: 2_572_000 picoseconds. + Weight::from_parts(2_680_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -65,10 +67,10 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_mortal_transaction() -> Weight { // Proof Size summary in bytes: - // Measured: `92` + // Measured: `0` // Estimated: `3509` - // Minimum execution time: 5_510_000 picoseconds. - Weight::from_parts(6_332_000, 0) + // Minimum execution time: 5_818_000 picoseconds. + Weight::from_parts(6_024_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -76,10 +78,10 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_immortal_transaction() -> Weight { // Proof Size summary in bytes: - // Measured: `92` + // Measured: `14` // Estimated: `3509` - // Minimum execution time: 5_510_000 picoseconds. - Weight::from_parts(6_332_000, 0) + // Minimum execution time: 7_364_000 picoseconds. + Weight::from_parts(7_676_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -87,44 +89,52 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 651_000 picoseconds. - Weight::from_parts(851_000, 0) + // Minimum execution time: 657_000 picoseconds. + Weight::from_parts(686_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) fn check_nonce() -> Weight { // Proof Size summary in bytes: // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_387_000 picoseconds. - Weight::from_parts(3_646_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Estimated: `3529` + // Minimum execution time: 6_931_000 picoseconds. + Weight::from_parts(7_096_000, 0) + .saturating_add(Weight::from_parts(0, 3529)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } fn check_spec_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 491_000 picoseconds. - Weight::from_parts(651_000, 0) + // Minimum execution time: 518_000 picoseconds. + Weight::from_parts(539_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn check_tx_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 451_000 picoseconds. - Weight::from_parts(662_000, 0) + // Minimum execution time: 530_000 picoseconds. + Weight::from_parts(550_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) - /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn check_weight() -> Weight { // Proof Size summary in bytes: - // Measured: `24` - // Estimated: `1489` - // Minimum execution time: 3_537_000 picoseconds. - Weight::from_parts(4_208_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_691_000 picoseconds. + Weight::from_parts(5_955_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_249_000 picoseconds. + Weight::from_parts(3_372_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } } diff --git a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml index 34458c2352fbd36a1f0bb47655ec1f8648eaefbf..6391f8c3eeb904586c9a0bc8449eb546218846da 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Rococo's People parachain runtime" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [build-dependencies] substrate-wasm-builder = { optional = true, workspace = true, default-features = true } @@ -66,17 +68,20 @@ xcm-runtime-apis = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-utility = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } parachains-common = { workspace = true } testnet-parachains-constants = { features = ["rococo"], workspace = true } +[dev-dependencies] +parachains-runtimes-test-utils = { workspace = true, default-features = true } + [features] default = ["std"] std = [ @@ -84,11 +89,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "enumflags2/std", "frame-benchmarking?/std", @@ -145,6 +150,7 @@ std = [ runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -171,11 +177,13 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index 7921030f2bb87ebda18ab412cd7890fde67ff912..cb0282b17a6ce54bfbd13061d71c14ae284cf301 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -92,17 +92,19 @@ pub type SignedBlock = generic::SignedBlock<Block>; pub type BlockId = generic::BlockId<Block>; /// The TransactionExtension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender<Runtime>, - frame_system::CheckSpecVersion<Runtime>, - frame_system::CheckTxVersion<Runtime>, - frame_system::CheckGenesis<Runtime>, - frame_system::CheckEra<Runtime>, - frame_system::CheckNonce<Runtime>, - frame_system::CheckWeight<Runtime>, - pallet_transaction_payment::ChargeTransactionPayment<Runtime>, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim<Runtime>, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckTxVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckEra<Runtime>, + frame_system::CheckNonce<Runtime>, + frame_system::CheckWeight<Runtime>, + pallet_transaction_payment::ChargeTransactionPayment<Runtime>, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -114,6 +116,7 @@ pub type Migrations = ( cumulus_pallet_xcmp_queue::migration::v5::MigrateV4ToV5<Runtime>, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion<Runtime>, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1<Runtime>, ); /// Executive: handles dispatch to the various modules. @@ -137,7 +140,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("people-rococo"), impl_name: alloc::borrow::Cow::Borrowed("people-rococo"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -196,6 +199,10 @@ impl frame_system::Config for Runtime { type MultiBlockMigrator = MultiBlockMigrations; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo<Runtime>; +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -567,6 +574,7 @@ construct_runtime!( ParachainSystem: cumulus_pallet_parachain_system = 1, Timestamp: pallet_timestamp = 2, ParachainInfo: parachain_info = 3, + WeightReclaim: cumulus_pallet_weight_reclaim = 4, // Monetary stuff. Balances: pallet_balances = 10, @@ -626,6 +634,7 @@ mod benches { [pallet_xcm, PalletXcmExtrinsicsBenchmark::<Runtime>] [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); } @@ -783,7 +792,8 @@ impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> { - match asset.try_as::<AssetId>() { + let latest_asset_id: Result<AssetId, ()> = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::RelayLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) @@ -1054,18 +1064,8 @@ impl_runtime_apis! { type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::<Runtime>; type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::<Runtime>; - let whitelist: Vec<TrackedStorageKey> = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec<TrackedStorageKey> = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::<BenchmarkBatch>::new(); let params = (&config, &whitelist); diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 0000000000000000000000000000000000000000..439855f857192a7c729f5aa49e2b90a1aabd7407 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=people-rococo-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo<T>(PhantomData<T>); +impl<T: frame_system::Config> cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo<T> { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 7_097_000 picoseconds. + Weight::from_parts(7_419_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/frame_system_extensions.rs index fb2b69e23e82b690e225517377aafd54fc72a240..3f12b25540ea669dff95ba4e8c9c87429a372af0 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/frame_system_extensions.rs @@ -129,4 +129,18 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 3_687_000 picoseconds. + Weight::from_parts(6_192_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } } diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/mod.rs index fab3c629ab3f31363233cf289c2abd1b96a59997..81906a11fe1c9ea7a9fde28cea1734bd2dd97252 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/mod.rs @@ -17,6 +17,7 @@ pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_migrations.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_migrations.rs index 61857ac8202a0383afdbb06198a528886dfbe418..57048b6e7095caa22ac4d4920d7040e56c13e398 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_migrations.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_migrations.rs @@ -1,19 +1,46 @@ // 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. -// Need to rerun! +// 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 <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `pallet_migrations` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `17938671047b`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 + +// Executed Command: +// frame-omni-bencher +// v1 +// benchmark +// pallet +// --extrinsic=* +// --runtime=target/production/wbuild/people-rococo-runtime/people_rococo_runtime.wasm +// --pallet=pallet_migrations +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-rococo/src/weights +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes +// --genesis-builder-policy=none +// --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -32,22 +59,24 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) fn onboard_new_mbms() -> Weight { // Proof Size summary in bytes: - // Measured: `276` + // Measured: `0` // Estimated: `67035` - // Minimum execution time: 7_762_000 picoseconds. - Weight::from_parts(8_100_000, 67035) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Minimum execution time: 4_483_000 picoseconds. + Weight::from_parts(4_781_000, 0) + .saturating_add(Weight::from_parts(0, 67035)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `MultiBlockMigrations::Cursor` (r:1 w:0) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn progress_mbms_none() -> Weight { // Proof Size summary in bytes: - // Measured: `142` + // Measured: `0` // Estimated: `67035` - // Minimum execution time: 2_077_000 picoseconds. - Weight::from_parts(2_138_000, 67035) - .saturating_add(T::DbWeight::get().reads(1_u64)) + // Minimum execution time: 864_000 picoseconds. + Weight::from_parts(907_000, 0) + .saturating_add(Weight::from_parts(0, 67035)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -55,12 +84,13 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn exec_migration_completed() -> Weight { // Proof Size summary in bytes: - // Measured: `134` - // Estimated: `3599` - // Minimum execution time: 5_868_000 picoseconds. - Weight::from_parts(6_143_000, 3599) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Measured: `0` + // Estimated: `3465` + // Minimum execution time: 3_978_000 picoseconds. + Weight::from_parts(4_149_000, 0) + .saturating_add(Weight::from_parts(0, 3465)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -68,11 +98,12 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_skipped_historic() -> Weight { // Proof Size summary in bytes: - // Measured: `330` - // Estimated: `3795` - // Minimum execution time: 10_283_000 picoseconds. - Weight::from_parts(10_964_000, 3795) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Measured: `34` + // Estimated: `3731` + // Minimum execution time: 7_432_000 picoseconds. + Weight::from_parts(7_663_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -80,11 +111,12 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_advance() -> Weight { // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `3741` - // Minimum execution time: 9_900_000 picoseconds. - Weight::from_parts(10_396_000, 3741) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Measured: `0` + // Estimated: `3731` + // Minimum execution time: 6_915_000 picoseconds. + Weight::from_parts(7_112_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -92,12 +124,13 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_complete() -> Weight { // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `3741` - // Minimum execution time: 11_411_000 picoseconds. - Weight::from_parts(11_956_000, 3741) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Measured: `0` + // Estimated: `3731` + // Minimum execution time: 8_561_000 picoseconds. + Weight::from_parts(8_701_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -107,19 +140,21 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn exec_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `3741` - // Minimum execution time: 12_398_000 picoseconds. - Weight::from_parts(12_910_000, 3741) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Measured: `0` + // Estimated: `3731` + // Minimum execution time: 8_998_000 picoseconds. + Weight::from_parts(9_348_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } fn on_init_loop() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 166_000 picoseconds. - Weight::from_parts(193_000, 0) + // Minimum execution time: 145_000 picoseconds. + Weight::from_parts(183_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) @@ -127,9 +162,10 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_686_000 picoseconds. - Weight::from_parts(2_859_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Minimum execution time: 2_137_000 picoseconds. + Weight::from_parts(2_275_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) @@ -137,9 +173,10 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_070_000 picoseconds. - Weight::from_parts(3_250_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Minimum execution time: 2_625_000 picoseconds. + Weight::from_parts(2_748_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `MultiBlockMigrations::Cursor` (r:1 w:0) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) @@ -147,26 +184,44 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) fn force_onboard_mbms() -> Weight { // Proof Size summary in bytes: - // Measured: `251` + // Measured: `0` // Estimated: `67035` - // Minimum execution time: 5_901_000 picoseconds. - Weight::from_parts(6_320_000, 67035) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Minimum execution time: 3_010_000 picoseconds. + Weight::from_parts(3_170_000, 0) + .saturating_add(Weight::from_parts(0, 67035)) + .saturating_add(T::DbWeight::get().reads(2)) } /// Storage: `MultiBlockMigrations::Historic` (r:256 w:256) /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 256]`. fn clear_historic(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1122 + n * (271 ±0)` + // Measured: `960 + n * (271 ±0)` // Estimated: `3834 + n * (2740 ±0)` - // Minimum execution time: 15_952_000 picoseconds. - Weight::from_parts(14_358_665, 3834) - // Standard Error: 3_358 - .saturating_add(Weight::from_parts(1_323_674, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(1_u64)) + // Minimum execution time: 15_088_000 picoseconds. + Weight::from_parts(27_216_754, 0) + .saturating_add(Weight::from_parts(0, 3834)) + // Standard Error: 5_635 + .saturating_add(Weight::from_parts(1_399_330, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2740).saturating_mul(n.into())) } -} \ No newline at end of file + /// 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, 2048]`. + fn reset_pallet_migration(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1605 + n * (38 ±0)` + // Estimated: `686 + n * (39 ±0)` + // Minimum execution time: 1_168_000 picoseconds. + Weight::from_parts(1_235_000, 0) + .saturating_add(Weight::from_parts(0, 686)) + // Standard Error: 2_626 + .saturating_add(Weight::from_parts(936_331, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 39).saturating_mul(n.into())) + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_multisig.rs index 73abb62b0482cc444fb2066e2eb6c4831ff5f159..82fcacf64aca642ac892929cb2ac9cc295533053 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_multisig.rs @@ -1,40 +1,43 @@ // 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 <http://www.gnu.org/licenses/>. //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, 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: `e20fc9f125eb`, 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 -// --chain=people-kusama-dev -// --execution=wasm -// --wasm-execution=compiled -// --pallet=pallet_multisig // --extrinsic=* +// --chain=people-rococo-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-rococo/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/people/people-kusama/src/weights/pallet_multisig.rs +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -52,110 +55,113 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_056_000 picoseconds. - Weight::from_parts(11_510_137, 0) + // Minimum execution time: 16_209_000 picoseconds. + Weight::from_parts(16_941_673, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 1 - .saturating_add(Weight::from_parts(528, 0).saturating_mul(z.into())) + // Standard Error: 10 + .saturating_add(Weight::from_parts(551, 0).saturating_mul(z.into())) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 41_105_000 picoseconds. - Weight::from_parts(34_947_072, 0) + // Minimum execution time: 47_880_000 picoseconds. + Weight::from_parts(35_747_073, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 499 - .saturating_add(Weight::from_parts(67_375, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_227, 0).saturating_mul(z.into())) + // Standard Error: 2_069 + .saturating_add(Weight::from_parts(147_421, 0).saturating_mul(s.into())) + // Standard Error: 20 + .saturating_add(Weight::from_parts(1_853, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[3, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 26_640_000 picoseconds. - Weight::from_parts(21_515_344, 0) + // Minimum execution time: 31_245_000 picoseconds. + Weight::from_parts(19_011_583, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 943 - .saturating_add(Weight::from_parts(58_769, 0).saturating_mul(s.into())) - // Standard Error: 9 - .saturating_add(Weight::from_parts(1_233, 0).saturating_mul(z.into())) + // Standard Error: 1_336 + .saturating_add(Weight::from_parts(136_422, 0).saturating_mul(s.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(2_013, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `388 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 45_875_000 picoseconds. - Weight::from_parts(38_052_994, 0) + // Minimum execution time: 52_116_000 picoseconds. + Weight::from_parts(33_912_565, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 507 - .saturating_add(Weight::from_parts(82_957, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_277, 0).saturating_mul(z.into())) + // Standard Error: 3_064 + .saturating_add(Weight::from_parts(258_562, 0).saturating_mul(s.into())) + // Standard Error: 30 + .saturating_add(Weight::from_parts(2_206, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 32_359_000 picoseconds. - Weight::from_parts(33_845_761, 0) + // Minimum execution time: 31_142_000 picoseconds. + Weight::from_parts(32_417_223, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 623 - .saturating_add(Weight::from_parts(69_809, 0).saturating_mul(s.into())) + // Standard Error: 1_622 + .saturating_add(Weight::from_parts(163_533, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 18_791_000 picoseconds. - Weight::from_parts(20_017_375, 0) + // Minimum execution time: 17_183_000 picoseconds. + Weight::from_parts(18_181_089, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 466 - .saturating_add(Weight::from_parts(64_780, 0).saturating_mul(s.into())) + // Standard Error: 1_123 + .saturating_add(Weight::from_parts(134_567, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `454 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 33_132_000 picoseconds. - Weight::from_parts(34_485_734, 0) + // Minimum execution time: 32_006_000 picoseconds. + Weight::from_parts(33_910_335, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 601 - .saturating_add(Weight::from_parts(70_191, 0).saturating_mul(s.into())) + // Standard Error: 1_347 + .saturating_add(Weight::from_parts(138_258, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs index fabce29b5fd9451f27364c38481d2dac98c430d8..d50afdbee47525c957f5f81291063c5ae8b750fb 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `902e7ad7764b`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-rococo-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=people-rococo-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-rococo/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=people-rococo-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/people/people-rococo/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,6 +50,8 @@ use core::marker::PhantomData; /// Weight functions for `pallet_xcm`. pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { + /// 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) @@ -60,16 +64,18 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `38` - // Estimated: `3503` - // Minimum execution time: 17_830_000 picoseconds. - Weight::from_parts(18_411_000, 0) - .saturating_add(Weight::from_parts(0, 3503)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `107` + // Estimated: `3572` + // Minimum execution time: 29_029_000 picoseconds. + Weight::from_parts(29_911_000, 0) + .saturating_add(Weight::from_parts(0, 3572)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -82,12 +88,12 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 55_456_000 picoseconds. - Weight::from_parts(56_808_000, 0) - .saturating_add(Weight::from_parts(0, 3535)) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `107` + // Estimated: `3572` + // Minimum execution time: 73_046_000 picoseconds. + Weight::from_parts(76_061_000, 0) + .saturating_add(Weight::from_parts(0, 3572)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Benchmark::Override` (r:0 w:0) @@ -110,15 +116,16 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `32` + // Estimated: `1517` + // Minimum execution time: 11_580_000 picoseconds. + Weight::from_parts(12_050_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -126,8 +133,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_996_000 picoseconds. - Weight::from_parts(6_154_000, 0) + // Minimum execution time: 6_963_000 picoseconds. + Weight::from_parts(7_371_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -137,8 +144,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_768_000 picoseconds. - Weight::from_parts(1_914_000, 0) + // Minimum execution time: 2_281_000 picoseconds. + Weight::from_parts(2_417_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -162,8 +169,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 24_120_000 picoseconds. - Weight::from_parts(24_745_000, 0) + // Minimum execution time: 30_422_000 picoseconds. + Weight::from_parts(31_342_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -186,8 +193,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `255` // Estimated: `3720` - // Minimum execution time: 26_630_000 picoseconds. - Weight::from_parts(27_289_000, 0) + // Minimum execution time: 35_290_000 picoseconds. + Weight::from_parts(36_161_000, 0) .saturating_add(Weight::from_parts(0, 3720)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -198,45 +205,45 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_821_000 picoseconds. - Weight::from_parts(1_946_000, 0) + // Minimum execution time: 2_115_000 picoseconds. + Weight::from_parts(2_389_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `89` - // Estimated: `13454` - // Minimum execution time: 16_586_000 picoseconds. - Weight::from_parts(16_977_000, 0) - .saturating_add(Weight::from_parts(0, 13454)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15929` + // Minimum execution time: 22_355_000 picoseconds. + Weight::from_parts(23_011_000, 0) + .saturating_add(Weight::from_parts(0, 15929)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `93` - // Estimated: `13458` - // Minimum execution time: 16_923_000 picoseconds. - Weight::from_parts(17_415_000, 0) - .saturating_add(Weight::from_parts(0, 13458)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15933` + // Minimum execution time: 22_043_000 picoseconds. + Weight::from_parts(22_506_000, 0) + .saturating_add(Weight::from_parts(0, 15933)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `15946` - // Minimum execution time: 18_596_000 picoseconds. - Weight::from_parts(18_823_000, 0) - .saturating_add(Weight::from_parts(0, 15946)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18421` + // Minimum execution time: 26_143_000 picoseconds. + Weight::from_parts(26_577_000, 0) + .saturating_add(Weight::from_parts(0, 18421)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -254,36 +261,36 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `106` // Estimated: `6046` - // Minimum execution time: 23_817_000 picoseconds. - Weight::from_parts(24_520_000, 0) + // Minimum execution time: 30_489_000 picoseconds. + Weight::from_parts(31_415_000, 0) .saturating_add(Weight::from_parts(0, 6046)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `136` - // Estimated: `11026` - // Minimum execution time: 11_042_000 picoseconds. - Weight::from_parts(11_578_000, 0) - .saturating_add(Weight::from_parts(0, 11026)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `109` + // Estimated: `13474` + // Minimum execution time: 16_848_000 picoseconds. + Weight::from_parts(17_169_000, 0) + .saturating_add(Weight::from_parts(0, 13474)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `100` - // Estimated: `13465` - // Minimum execution time: 17_306_000 picoseconds. - Weight::from_parts(17_817_000, 0) - .saturating_add(Weight::from_parts(0, 13465)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15940` + // Minimum execution time: 22_556_000 picoseconds. + Weight::from_parts(22_875_000, 0) + .saturating_add(Weight::from_parts(0, 15940)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -298,11 +305,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `13471` - // Minimum execution time: 32_141_000 picoseconds. - Weight::from_parts(32_954_000, 0) - .saturating_add(Weight::from_parts(0, 13471)) - .saturating_add(T::DbWeight::get().reads(10)) + // Estimated: `15946` + // Minimum execution time: 42_772_000 picoseconds. + Weight::from_parts(43_606_000, 0) + .saturating_add(Weight::from_parts(0, 15946)) + .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -313,8 +320,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_410_000 picoseconds. - Weight::from_parts(3_556_000, 0) + // Minimum execution time: 4_811_000 picoseconds. + Weight::from_parts(5_060_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -325,22 +332,24 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 25_021_000 picoseconds. - Weight::from_parts(25_240_000, 0) + // Minimum execution time: 31_925_000 picoseconds. + Weight::from_parts(32_294_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 33_801_000 picoseconds. - Weight::from_parts(34_655_000, 0) + // Minimum execution time: 41_804_000 picoseconds. + Weight::from_parts(42_347_000, 0) .saturating_add(Weight::from_parts(0, 3555)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs index 631cc7b7f0b023781ca191f00f08003eccd8a98e..d55198f60a00ceddc61ae8ee26dac48e67856e4d 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 @@ -21,6 +21,7 @@ use alloc::vec::Vec; use frame_support::weights::Weight; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::{ latest::{prelude::*, AssetTransferFilter}, DoubleEncoded, @@ -83,7 +84,11 @@ impl<Call> XcmWeightInfo<Call> for PeopleRococoXcmWeight<Call> { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::<Runtime>::transfer_reserve_asset()) } - fn transact(_origin_type: &OriginKind, _call: &DoubleEncoded<Call>) -> Weight { + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option<Weight>, + _call: &DoubleEncoded<Call>, + ) -> Weight { XcmGeneric::<Runtime>::transact() } fn hrmp_new_channel_open_request( @@ -250,8 +255,16 @@ impl<Call> XcmWeightInfo<Call> for PeopleRococoXcmWeight<Call> { fn unpaid_execution(_: &WeightLimit, _: &Option<Location>) -> Weight { XcmGeneric::<Runtime>::unpaid_execution() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::<Runtime>::set_asset_claimer() + fn set_hints(hints: &BoundedVec<Hint, HintNumVariants>) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::<Runtime>::asset_claimer()); + }, + } + } + weight } fn execute_with_origin(_: &Option<InteriorLocation>, _: &Xcm<Call>) -> Weight { XcmGeneric::<Runtime>::execute_with_origin() 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 6aac6119e7ec5bea8fe064fdd90c61795fb0d6df..caa91650734821681f5d2db4157422794f8e15a6 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 @@ -331,7 +331,7 @@ impl<T: frame_system::Config> WeightInfo<T> { // Minimum execution time: 685_000 picoseconds. Weight::from_parts(757_000, 0) } - pub fn set_asset_claimer() -> Weight { + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/cumulus/parachains/runtimes/people/people-rococo/tests/tests.rs b/cumulus/parachains/runtimes/people/people-rococo/tests/tests.rs index 3627d9c40ec2b5754553a123fd58c84d33b75d06..00fe7781822ae1cef10258e08c7d1b14be714b32 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/tests/tests.rs @@ -17,7 +17,9 @@ #![cfg(test)] use parachains_common::AccountId; -use people_rococo_runtime::xcm_config::LocationToAccountId; +use people_rococo_runtime::{ + xcm_config::LocationToAccountId, Block, Runtime, RuntimeCall, RuntimeOrigin, +}; use sp_core::crypto::Ss58Codec; use xcm::latest::prelude::*; use xcm_runtime_apis::conversions::LocationToAccountHelper; @@ -132,3 +134,13 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} diff --git a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml index 6840b97d8c3fa82a3ace10c468e885a38f06c2d0..fae0fd2e33324e500d75983ce09db276450fa79c 100644 --- a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Westend's People parachain runtime" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [build-dependencies] substrate-wasm-builder = { optional = true, workspace = true, default-features = true } @@ -66,17 +68,20 @@ xcm-runtime-apis = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-utility = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } parachains-common = { workspace = true } testnet-parachains-constants = { features = ["westend"], workspace = true } +[dev-dependencies] +parachains-runtimes-test-utils = { workspace = true, default-features = true } + [features] default = ["std"] std = [ @@ -84,11 +89,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "enumflags2/std", "frame-benchmarking?/std", @@ -145,6 +150,7 @@ std = [ runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -171,11 +177,13 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 19a64ab8d6e8fb44429c5782954ab91860aa3198..050256dd4f6a38a2908c16c57c9a0d3ee255bdaa 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -92,17 +92,19 @@ pub type SignedBlock = generic::SignedBlock<Block>; pub type BlockId = generic::BlockId<Block>; /// The transactionExtension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender<Runtime>, - frame_system::CheckSpecVersion<Runtime>, - frame_system::CheckTxVersion<Runtime>, - frame_system::CheckGenesis<Runtime>, - frame_system::CheckEra<Runtime>, - frame_system::CheckNonce<Runtime>, - frame_system::CheckWeight<Runtime>, - pallet_transaction_payment::ChargeTransactionPayment<Runtime>, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim<Runtime>, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckTxVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckEra<Runtime>, + frame_system::CheckNonce<Runtime>, + frame_system::CheckWeight<Runtime>, + pallet_transaction_payment::ChargeTransactionPayment<Runtime>, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -113,6 +115,7 @@ pub type Migrations = ( pallet_collator_selection::migration::v2::MigrationToV2<Runtime>, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion<Runtime>, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1<Runtime>, ); /// Executive: handles dispatch to the various modules. @@ -136,10 +139,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("people-westend"), impl_name: alloc::borrow::Cow::Borrowed("people-westend"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 1, + transaction_version: 2, system_version: 1, }; @@ -195,6 +198,10 @@ impl frame_system::Config for Runtime { type MultiBlockMigrator = MultiBlockMigrations; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo<Runtime>; +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -566,6 +573,7 @@ construct_runtime!( ParachainSystem: cumulus_pallet_parachain_system = 1, Timestamp: pallet_timestamp = 2, ParachainInfo: parachain_info = 3, + WeightReclaim: cumulus_pallet_weight_reclaim = 4, // Monetary stuff. Balances: pallet_balances = 10, @@ -624,6 +632,7 @@ mod benches { [pallet_xcm, PalletXcmExtrinsicsBenchmark::<Runtime>] [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); } @@ -781,7 +790,8 @@ impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> { - match asset.try_as::<AssetId>() { + let latest_asset_id: Result<AssetId, ()> = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::RelayLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) @@ -1045,25 +1055,17 @@ impl_runtime_apis! { } fn alias_origin() -> Result<(Location, Location), BenchmarkError> { - Err(BenchmarkError::Skip) + let origin = Location::new(1, [Parachain(1000)]); + let target = Location::new(1, [Parachain(1000), AccountId32 { id: [128u8; 32], network: None }]); + Ok((origin, target)) } } type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::<Runtime>; type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::<Runtime>; - let whitelist: Vec<TrackedStorageKey> = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec<TrackedStorageKey> = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::<BenchmarkBatch>::new(); let params = (&config, &whitelist); diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 0000000000000000000000000000000000000000..fd3018ec9740111167a2e66189820aff72765ddd --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=people-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo<T>(PhantomData<T>); +impl<T: frame_system::Config> cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo<T> { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 7_006_000 picoseconds. + Weight::from_parts(7_269_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/frame_system_extensions.rs index 0a4b9e8e26812bfe82aaaccd3dc19221d7327e37..422b8566ad08a58a6f26f7c87e975d235305b3b2 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/frame_system_extensions.rs @@ -129,4 +129,18 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 3_687_000 picoseconds. + Weight::from_parts(6_192_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } } diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/mod.rs index fab3c629ab3f31363233cf289c2abd1b96a59997..81906a11fe1c9ea7a9fde28cea1734bd2dd97252 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/mod.rs @@ -17,6 +17,7 @@ pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_migrations.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_migrations.rs index 61857ac8202a0383afdbb06198a528886dfbe418..ecc52360a3ce11f1db4d572c3d310941e89f846b 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_migrations.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_migrations.rs @@ -1,19 +1,46 @@ // 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. -// Need to rerun! +// 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 <http://www.gnu.org/licenses/>. + +//! Autogenerated weights for `pallet_migrations` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `17938671047b`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 + +// Executed Command: +// frame-omni-bencher +// v1 +// benchmark +// pallet +// --extrinsic=* +// --runtime=target/production/wbuild/people-westend-runtime/people_westend_runtime.wasm +// --pallet=pallet_migrations +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-westend/src/weights +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes +// --genesis-builder-policy=none +// --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -32,22 +59,24 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) fn onboard_new_mbms() -> Weight { // Proof Size summary in bytes: - // Measured: `276` + // Measured: `0` // Estimated: `67035` - // Minimum execution time: 7_762_000 picoseconds. - Weight::from_parts(8_100_000, 67035) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Minimum execution time: 4_484_000 picoseconds. + Weight::from_parts(4_646_000, 0) + .saturating_add(Weight::from_parts(0, 67035)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `MultiBlockMigrations::Cursor` (r:1 w:0) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn progress_mbms_none() -> Weight { // Proof Size summary in bytes: - // Measured: `142` + // Measured: `0` // Estimated: `67035` - // Minimum execution time: 2_077_000 picoseconds. - Weight::from_parts(2_138_000, 67035) - .saturating_add(T::DbWeight::get().reads(1_u64)) + // Minimum execution time: 777_000 picoseconds. + Weight::from_parts(841_000, 0) + .saturating_add(Weight::from_parts(0, 67035)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -55,12 +84,13 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn exec_migration_completed() -> Weight { // Proof Size summary in bytes: - // Measured: `134` - // Estimated: `3599` - // Minimum execution time: 5_868_000 picoseconds. - Weight::from_parts(6_143_000, 3599) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Measured: `0` + // Estimated: `3465` + // Minimum execution time: 3_883_000 picoseconds. + Weight::from_parts(4_097_000, 0) + .saturating_add(Weight::from_parts(0, 3465)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -68,11 +98,12 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_skipped_historic() -> Weight { // Proof Size summary in bytes: - // Measured: `330` - // Estimated: `3795` - // Minimum execution time: 10_283_000 picoseconds. - Weight::from_parts(10_964_000, 3795) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Measured: `34` + // Estimated: `3731` + // Minimum execution time: 7_695_000 picoseconds. + Weight::from_parts(8_015_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -80,11 +111,12 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_advance() -> Weight { // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `3741` - // Minimum execution time: 9_900_000 picoseconds. - Weight::from_parts(10_396_000, 3741) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Measured: `0` + // Estimated: `3731` + // Minimum execution time: 6_999_000 picoseconds. + Weight::from_parts(7_323_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -92,12 +124,13 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_complete() -> Weight { // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `3741` - // Minimum execution time: 11_411_000 picoseconds. - Weight::from_parts(11_956_000, 3741) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Measured: `0` + // Estimated: `3731` + // Minimum execution time: 8_302_000 picoseconds. + Weight::from_parts(8_589_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -107,19 +140,21 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn exec_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `3741` - // Minimum execution time: 12_398_000 picoseconds. - Weight::from_parts(12_910_000, 3741) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Measured: `0` + // Estimated: `3731` + // Minimum execution time: 9_122_000 picoseconds. + Weight::from_parts(9_541_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } fn on_init_loop() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 166_000 picoseconds. - Weight::from_parts(193_000, 0) + // Minimum execution time: 146_000 picoseconds. + Weight::from_parts(168_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) @@ -127,9 +162,10 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_686_000 picoseconds. - Weight::from_parts(2_859_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Minimum execution time: 2_271_000 picoseconds. + Weight::from_parts(2_367_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) @@ -137,9 +173,10 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_070_000 picoseconds. - Weight::from_parts(3_250_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Minimum execution time: 2_653_000 picoseconds. + Weight::from_parts(2_798_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `MultiBlockMigrations::Cursor` (r:1 w:0) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) @@ -147,26 +184,44 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) fn force_onboard_mbms() -> Weight { // Proof Size summary in bytes: - // Measured: `251` + // Measured: `0` // Estimated: `67035` - // Minimum execution time: 5_901_000 picoseconds. - Weight::from_parts(6_320_000, 67035) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Minimum execution time: 3_084_000 picoseconds. + Weight::from_parts(3_233_000, 0) + .saturating_add(Weight::from_parts(0, 67035)) + .saturating_add(T::DbWeight::get().reads(2)) } /// Storage: `MultiBlockMigrations::Historic` (r:256 w:256) /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 256]`. fn clear_historic(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1122 + n * (271 ±0)` + // Measured: `960 + n * (271 ±0)` // Estimated: `3834 + n * (2740 ±0)` - // Minimum execution time: 15_952_000 picoseconds. - Weight::from_parts(14_358_665, 3834) - // Standard Error: 3_358 - .saturating_add(Weight::from_parts(1_323_674, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(1_u64)) + // Minimum execution time: 18_761_000 picoseconds. + Weight::from_parts(22_980_278, 0) + .saturating_add(Weight::from_parts(0, 3834)) + // Standard Error: 5_634 + .saturating_add(Weight::from_parts(1_419_653, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2740).saturating_mul(n.into())) } -} \ No newline at end of file + /// 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, 2048]`. + fn reset_pallet_migration(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1605 + n * (38 ±0)` + // Estimated: `686 + n * (39 ±0)` + // Minimum execution time: 1_174_000 picoseconds. + Weight::from_parts(1_216_000, 0) + .saturating_add(Weight::from_parts(0, 686)) + // Standard Error: 3_009 + .saturating_add(Weight::from_parts(952_922, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 39).saturating_mul(n.into())) + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_multisig.rs index 70809dea2366cc71e5ffb6bfe62feeb6d546f46f..5857a140e05e089fd059208372f8d13d8fc18ab9 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_multisig.rs @@ -1,40 +1,43 @@ // 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 <http://www.gnu.org/licenses/>. //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, 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: `e20fc9f125eb`, 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 -// --chain=people-polkadot-dev -// --execution=wasm -// --wasm-execution=compiled -// --pallet=pallet_multisig // --extrinsic=* +// --chain=people-westend-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/people/people-polkadot/src/weights/pallet_multisig.rs +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -52,110 +55,113 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_337_000 picoseconds. - Weight::from_parts(11_960_522, 0) + // Minimum execution time: 15_664_000 picoseconds. + Weight::from_parts(16_483_544, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 9 - .saturating_add(Weight::from_parts(504, 0).saturating_mul(z.into())) + // Standard Error: 6 + .saturating_add(Weight::from_parts(527, 0).saturating_mul(z.into())) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 41_128_000 picoseconds. - Weight::from_parts(35_215_592, 0) + // Minimum execution time: 47_543_000 picoseconds. + Weight::from_parts(32_140_648, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 429 - .saturating_add(Weight::from_parts(65_959, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_230, 0).saturating_mul(z.into())) + // Standard Error: 2_184 + .saturating_add(Weight::from_parts(163_779, 0).saturating_mul(s.into())) + // Standard Error: 21 + .saturating_add(Weight::from_parts(2_192, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[3, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 26_878_000 picoseconds. - Weight::from_parts(21_448_577, 0) + // Minimum execution time: 31_080_000 picoseconds. + Weight::from_parts(19_282_980, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 354 - .saturating_add(Weight::from_parts(60_286, 0).saturating_mul(s.into())) - // Standard Error: 3 - .saturating_add(Weight::from_parts(1_236, 0).saturating_mul(z.into())) + // Standard Error: 1_261 + .saturating_add(Weight::from_parts(134_865, 0).saturating_mul(s.into())) + // Standard Error: 12 + .saturating_add(Weight::from_parts(2_015, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `388 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 45_716_000 picoseconds. - Weight::from_parts(38_332_947, 0) + // Minimum execution time: 54_063_000 picoseconds. + Weight::from_parts(34_760_071, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 554 - .saturating_add(Weight::from_parts(81_026, 0).saturating_mul(s.into())) - // Standard Error: 5 - .saturating_add(Weight::from_parts(1_265, 0).saturating_mul(z.into())) + // Standard Error: 2_858 + .saturating_add(Weight::from_parts(242_502, 0).saturating_mul(s.into())) + // Standard Error: 28 + .saturating_add(Weight::from_parts(2_187, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 32_089_000 picoseconds. - Weight::from_parts(33_664_508, 0) + // Minimum execution time: 30_997_000 picoseconds. + Weight::from_parts(32_861_544, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 487 - .saturating_add(Weight::from_parts(67_443, 0).saturating_mul(s.into())) + // Standard Error: 1_172 + .saturating_add(Weight::from_parts(144_646, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 18_631_000 picoseconds. - Weight::from_parts(19_909_964, 0) + // Minimum execution time: 17_110_000 picoseconds. + Weight::from_parts(16_883_743, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 434 - .saturating_add(Weight::from_parts(62_989, 0).saturating_mul(s.into())) + // Standard Error: 1_170 + .saturating_add(Weight::from_parts(141_623, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `454 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 32_486_000 picoseconds. - Weight::from_parts(34_303_784, 0) + // Minimum execution time: 31_575_000 picoseconds. + Weight::from_parts(33_599_222, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 585 - .saturating_add(Weight::from_parts(69_979, 0).saturating_mul(s.into())) + // Standard Error: 1_343 + .saturating_add(Weight::from_parts(148_578, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs index c337289243b748d721b60421bccf3349044ef194..f06669209a1873a2d1a697fc46b2e803a9346032 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `4105cf7eb2c7`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=people-westend-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-westend/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=people-westend-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/people/people-westend/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,6 +50,8 @@ use core::marker::PhantomData; /// Weight functions for `pallet_xcm`. pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { + /// 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) @@ -60,16 +64,18 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `38` - // Estimated: `3503` - // Minimum execution time: 17_856_000 picoseconds. - Weight::from_parts(18_473_000, 0) - .saturating_add(Weight::from_parts(0, 3503)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `107` + // Estimated: `3572` + // Minimum execution time: 29_434_000 picoseconds. + Weight::from_parts(30_114_000, 0) + .saturating_add(Weight::from_parts(0, 3572)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -82,12 +88,12 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 56_112_000 picoseconds. - Weight::from_parts(57_287_000, 0) - .saturating_add(Weight::from_parts(0, 3535)) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `107` + // Estimated: `3572` + // Minimum execution time: 73_433_000 picoseconds. + Weight::from_parts(75_377_000, 0) + .saturating_add(Weight::from_parts(0, 3572)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Benchmark::Override` (r:0 w:0) @@ -110,15 +116,16 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `32` + // Estimated: `1517` + // Minimum execution time: 11_627_000 picoseconds. + Weight::from_parts(12_034_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -126,8 +133,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_186_000 picoseconds. - Weight::from_parts(6_420_000, 0) + // Minimum execution time: 7_075_000 picoseconds. + Weight::from_parts(7_406_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -137,8 +144,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_824_000 picoseconds. - Weight::from_parts(1_999_000, 0) + // Minimum execution time: 2_308_000 picoseconds. + Weight::from_parts(2_485_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -162,8 +169,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 23_833_000 picoseconds. - Weight::from_parts(24_636_000, 0) + // Minimum execution time: 29_939_000 picoseconds. + Weight::from_parts(30_795_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -186,8 +193,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `255` // Estimated: `3720` - // Minimum execution time: 26_557_000 picoseconds. - Weight::from_parts(27_275_000, 0) + // Minimum execution time: 34_830_000 picoseconds. + Weight::from_parts(35_677_000, 0) .saturating_add(Weight::from_parts(0, 3720)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -198,45 +205,45 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_921_000 picoseconds. - Weight::from_parts(2_040_000, 0) + // Minimum execution time: 2_363_000 picoseconds. + Weight::from_parts(2_517_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `89` - // Estimated: `13454` - // Minimum execution time: 16_832_000 picoseconds. - Weight::from_parts(17_312_000, 0) - .saturating_add(Weight::from_parts(0, 13454)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15929` + // Minimum execution time: 22_322_000 picoseconds. + Weight::from_parts(22_709_000, 0) + .saturating_add(Weight::from_parts(0, 15929)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `93` - // Estimated: `13458` - // Minimum execution time: 16_687_000 picoseconds. - Weight::from_parts(17_123_000, 0) - .saturating_add(Weight::from_parts(0, 13458)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15933` + // Minimum execution time: 22_418_000 picoseconds. + Weight::from_parts(22_834_000, 0) + .saturating_add(Weight::from_parts(0, 15933)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `15946` - // Minimum execution time: 18_164_000 picoseconds. - Weight::from_parts(18_580_000, 0) - .saturating_add(Weight::from_parts(0, 15946)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18421` + // Minimum execution time: 26_310_000 picoseconds. + Weight::from_parts(26_623_000, 0) + .saturating_add(Weight::from_parts(0, 18421)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -254,36 +261,36 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `106` // Estimated: `6046` - // Minimum execution time: 23_577_000 picoseconds. - Weight::from_parts(24_324_000, 0) + // Minimum execution time: 29_863_000 picoseconds. + Weight::from_parts(30_467_000, 0) .saturating_add(Weight::from_parts(0, 6046)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `136` - // Estimated: `11026` - // Minimum execution time: 11_014_000 picoseconds. - Weight::from_parts(11_223_000, 0) - .saturating_add(Weight::from_parts(0, 11026)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `109` + // Estimated: `13474` + // Minimum execution time: 17_075_000 picoseconds. + Weight::from_parts(17_578_000, 0) + .saturating_add(Weight::from_parts(0, 13474)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `100` - // Estimated: `13465` - // Minimum execution time: 16_887_000 picoseconds. - Weight::from_parts(17_361_000, 0) - .saturating_add(Weight::from_parts(0, 13465)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15940` + // Minimum execution time: 22_816_000 picoseconds. + Weight::from_parts(23_175_000, 0) + .saturating_add(Weight::from_parts(0, 15940)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -298,11 +305,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `13471` - // Minimum execution time: 31_705_000 picoseconds. - Weight::from_parts(32_166_000, 0) - .saturating_add(Weight::from_parts(0, 13471)) - .saturating_add(T::DbWeight::get().reads(10)) + // Estimated: `15946` + // Minimum execution time: 42_767_000 picoseconds. + Weight::from_parts(43_308_000, 0) + .saturating_add(Weight::from_parts(0, 15946)) + .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -313,8 +320,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_568_000 picoseconds. - Weight::from_parts(3_669_000, 0) + // Minimum execution time: 4_864_000 picoseconds. + Weight::from_parts(5_010_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -325,22 +332,24 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 24_823_000 picoseconds. - Weight::from_parts(25_344_000, 0) + // Minimum execution time: 30_237_000 picoseconds. + Weight::from_parts(30_662_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 34_516_000 picoseconds. - Weight::from_parts(35_478_000, 0) + // Minimum execution time: 41_418_000 picoseconds. + Weight::from_parts(42_011_000, 0) .saturating_add(Weight::from_parts(0, 3555)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs index 4b51a3ba411b0e3c0fc7c6bec228993e1557f60c..466da1eadd550fd56cf9a87bbe8d2aea95e4d7b9 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 @@ -21,6 +21,7 @@ use alloc::vec::Vec; use frame_support::weights::Weight; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::{ latest::{prelude::*, AssetTransferFilter}, DoubleEncoded, @@ -83,7 +84,11 @@ impl<Call> XcmWeightInfo<Call> for PeopleWestendXcmWeight<Call> { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::<Runtime>::transfer_reserve_asset()) } - fn transact(_origin_type: &OriginKind, _call: &DoubleEncoded<Call>) -> Weight { + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option<Weight>, + _call: &DoubleEncoded<Call>, + ) -> Weight { XcmGeneric::<Runtime>::transact() } fn hrmp_new_channel_open_request( @@ -244,14 +249,21 @@ impl<Call> XcmWeightInfo<Call> for PeopleWestendXcmWeight<Call> { XcmGeneric::<Runtime>::clear_topic() } fn alias_origin(_: &Location) -> Weight { - // XCM Executor does not currently support alias origin operations - Weight::MAX + XcmGeneric::<Runtime>::alias_origin() } fn unpaid_execution(_: &WeightLimit, _: &Option<Location>) -> Weight { XcmGeneric::<Runtime>::unpaid_execution() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::<Runtime>::set_asset_claimer() + fn set_hints(hints: &BoundedVec<Hint, HintNumVariants>) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::<Runtime>::asset_claimer()); + }, + } + } + weight } fn execute_with_origin(_: &Option<InteriorLocation>, _: &Xcm<Call>) -> Weight { XcmGeneric::<Runtime>::execute_with_origin() 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 36400f2c1e6687130cb99ebeb3b5276bee2ac0bf..3fa51a816b69a9eeaace2da4a91bf8e7b5733e95 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 @@ -17,26 +17,28 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-08-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-svzsllib-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `9340d096ec0f`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: Compiled, CHAIN: Some("people-westend-dev"), DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=people-westend-dev +// --pallet=pallet_xcm_benchmarks::generic +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-westend/src/weights/xcm // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --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/ +// --template=cumulus/templates/xcm-bench-template.hbs +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -62,10 +64,10 @@ impl<T: frame_system::Config> WeightInfo<T> { // 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: 29_015_000 picoseconds. - Weight::from_parts(30_359_000, 3535) + // Measured: `107` + // Estimated: `3572` + // Minimum execution time: 31_309_000 picoseconds. + Weight::from_parts(31_924_000, 3572) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -73,15 +75,26 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 572_000 picoseconds. - Weight::from_parts(637_000, 0) + // Minimum execution time: 635_000 picoseconds. + Weight::from_parts(677_000, 0) } + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) pub fn pay_fees() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 3_457_000 picoseconds. + Weight::from_parts(3_656_000, 3593) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_550_000 picoseconds. - Weight::from_parts(1_604_000, 0) + // Minimum execution time: 644_000 picoseconds. + Weight::from_parts(695_000, 0) } // Storage: `PolkadotXcm::Queries` (r:1 w:0) // Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -89,58 +102,65 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `32` // Estimated: `3497` - // Minimum execution time: 7_354_000 picoseconds. - Weight::from_parts(7_808_000, 3497) + // Minimum execution time: 7_701_000 picoseconds. + Weight::from_parts(8_120_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: 6_716_000 picoseconds. - Weight::from_parts(7_067_000, 0) + // Minimum execution time: 6_945_000 picoseconds. + Weight::from_parts(7_187_000, 0) } pub fn refund_surplus() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_280_000 picoseconds. - Weight::from_parts(1_355_000, 0) + // Minimum execution time: 1_352_000 picoseconds. + Weight::from_parts(1_428_000, 0) } pub fn set_error_handler() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 587_000 picoseconds. - Weight::from_parts(645_000, 0) + // Minimum execution time: 603_000 picoseconds. + Weight::from_parts(648_000, 0) } pub fn set_appendix() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 629_000 picoseconds. - Weight::from_parts(662_000, 0) + // Minimum execution time: 621_000 picoseconds. + Weight::from_parts(661_000, 0) } pub fn clear_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 590_000 picoseconds. - Weight::from_parts(639_000, 0) + // Minimum execution time: 591_000 picoseconds. + Weight::from_parts(655_000, 0) } pub fn descend_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 651_000 picoseconds. - Weight::from_parts(688_000, 0) + // Minimum execution time: 666_000 picoseconds. + Weight::from_parts(736_000, 0) + } + pub fn execute_with_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 694_000 picoseconds. + Weight::from_parts(759_000, 0) } pub fn clear_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 601_000 picoseconds. - Weight::from_parts(630_000, 0) + // Minimum execution time: 632_000 picoseconds. + Weight::from_parts(664_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -156,10 +176,10 @@ impl<T: frame_system::Config> WeightInfo<T> { // 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: 25_650_000 picoseconds. - Weight::from_parts(26_440_000, 3535) + // Measured: `107` + // Estimated: `3572` + // Minimum execution time: 26_932_000 picoseconds. + Weight::from_parts(27_882_000, 3572) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -169,8 +189,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 10_492_000 picoseconds. - Weight::from_parts(10_875_000, 3555) + // Minimum execution time: 11_316_000 picoseconds. + Weight::from_parts(11_608_000, 3555) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -178,8 +198,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 597_000 picoseconds. - Weight::from_parts(647_000, 0) + // Minimum execution time: 564_000 picoseconds. + Weight::from_parts(614_000, 0) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:1 w:1) // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -197,8 +217,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 23_732_000 picoseconds. - Weight::from_parts(24_290_000, 3503) + // Minimum execution time: 24_373_000 picoseconds. + Weight::from_parts(25_068_000, 3503) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -208,44 +228,44 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_446_000 picoseconds. - Weight::from_parts(2_613_000, 0) + // Minimum execution time: 2_582_000 picoseconds. + Weight::from_parts(2_714_000, 0) .saturating_add(T::DbWeight::get().writes(1)) } pub fn burn_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 960_000 picoseconds. - Weight::from_parts(1_045_000, 0) + // Minimum execution time: 952_000 picoseconds. + Weight::from_parts(1_059_000, 0) } pub fn expect_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 703_000 picoseconds. - Weight::from_parts(739_000, 0) + // Minimum execution time: 684_000 picoseconds. + Weight::from_parts(734_000, 0) } pub fn expect_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 616_000 picoseconds. - Weight::from_parts(651_000, 0) + // Minimum execution time: 600_000 picoseconds. + Weight::from_parts(650_000, 0) } pub fn expect_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 621_000 picoseconds. - Weight::from_parts(660_000, 0) + // Minimum execution time: 599_000 picoseconds. + Weight::from_parts(628_000, 0) } pub fn expect_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 794_000 picoseconds. - Weight::from_parts(831_000, 0) + // Minimum execution time: 769_000 picoseconds. + Weight::from_parts(816_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -261,10 +281,10 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn query_pallet() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 29_527_000 picoseconds. - Weight::from_parts(30_614_000, 3535) + // Measured: `107` + // Estimated: `3572` + // Minimum execution time: 31_815_000 picoseconds. + Weight::from_parts(32_738_000, 3572) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -272,8 +292,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_189_000 picoseconds. - Weight::from_parts(3_296_000, 0) + // Minimum execution time: 3_462_000 picoseconds. + Weight::from_parts(3_563_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -289,10 +309,10 @@ impl<T: frame_system::Config> WeightInfo<T> { // 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_965_000 picoseconds. - Weight::from_parts(26_468_000, 3535) + // Measured: `107` + // Estimated: `3572` + // Minimum execution time: 27_752_000 picoseconds. + Weight::from_parts(28_455_000, 3572) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -300,49 +320,42 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 618_000 picoseconds. - Weight::from_parts(659_000, 0) + // Minimum execution time: 605_000 picoseconds. + Weight::from_parts(687_000, 0) } pub fn set_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 593_000 picoseconds. - Weight::from_parts(618_000, 0) + // Minimum execution time: 610_000 picoseconds. + Weight::from_parts(646_000, 0) } pub fn clear_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 603_000 picoseconds. - Weight::from_parts(634_000, 0) + // Minimum execution time: 579_000 picoseconds. + Weight::from_parts(636_000, 0) } pub fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 568_000 picoseconds. - Weight::from_parts(629_000, 0) + // Minimum execution time: 583_000 picoseconds. + Weight::from_parts(626_000, 0) } pub fn unpaid_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 598_000 picoseconds. - Weight::from_parts(655_000, 0) - } - pub fn set_asset_claimer() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 707_000 picoseconds. - Weight::from_parts(749_000, 0) + // Minimum execution time: 616_000 picoseconds. + Weight::from_parts(679_000, 0) } - pub fn execute_with_origin() -> Weight { + pub fn alias_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 713_000 picoseconds. - Weight::from_parts(776_000, 0) + // Minimum execution time: 626_000 picoseconds. + Weight::from_parts(687_000, 0) } } diff --git a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs index 25256495ef91e48c9e024d168486aecdc9620751..7eaa43c05b208fd9107976572fbd71e1e5b4cd27 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs @@ -36,7 +36,8 @@ use polkadot_parachain_primitives::primitives::Sibling; use sp_runtime::traits::AccountIdConversion; use xcm::latest::{prelude::*, WESTEND_GENESIS_HASH}; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, + AccountId32Aliases, AliasChildLocation, AliasOriginRootUsingFilter, + AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, DescribeTerminus, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, @@ -51,6 +52,7 @@ use xcm_executor::XcmExecutor; parameter_types! { pub const RootLocation: Location = Location::here(); pub const RelayLocation: Location = Location::parent(); + pub AssetHubLocation: Location = Location::new(1, [Parachain(1000)]); pub const RelayNetwork: Option<NetworkId> = Some(NetworkId::ByGenesis(WESTEND_GENESIS_HASH)); pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); pub UniversalLocation: InteriorLocation = @@ -195,6 +197,10 @@ pub type WaivedLocations = ( LocalPlurality, ); +/// We allow locations to alias into their own child locations, as well as +/// AssetHub to alias into anything. +pub type Aliasers = (AliasChildLocation, AliasOriginRootUsingFilter<AssetHubLocation, Everything>); + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -236,7 +242,7 @@ impl xcm_executor::Config for XcmConfig { type UniversalAliases = Nothing; type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; - type Aliasers = Nothing; + type Aliasers = Aliasers; type TransactionalProcessor = FrameTransactionalProcessor; type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); diff --git a/cumulus/parachains/runtimes/people/people-westend/tests/tests.rs b/cumulus/parachains/runtimes/people/people-westend/tests/tests.rs index fa9331952b4b53a6012ad72d0d9b1bba2f4afec7..5cefec44b1cddf19ef2c875c86ff478e379d0f0f 100644 --- a/cumulus/parachains/runtimes/people/people-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/people/people-westend/tests/tests.rs @@ -17,7 +17,9 @@ #![cfg(test)] use parachains_common::AccountId; -use people_westend_runtime::xcm_config::LocationToAccountId; +use people_westend_runtime::{ + xcm_config::LocationToAccountId, Block, Runtime, RuntimeCall, RuntimeOrigin, +}; use sp_core::crypto::Ss58Codec; use xcm::latest::prelude::*; use xcm_runtime_apis::conversions::LocationToAccountHelper; @@ -132,3 +134,13 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} diff --git a/cumulus/parachains/runtimes/test-utils/Cargo.toml b/cumulus/parachains/runtimes/test-utils/Cargo.toml index 01d7fcc2b5c8b77c7daa90d807634193545e95ec..cc8f29524514ff5fc659937882fc12b893545109 100644 --- a/cumulus/parachains/runtimes/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/test-utils/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Utils for Runtimes testing" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -19,25 +21,27 @@ pallet-balances = { workspace = true } pallet-session = { workspace = true } pallet-timestamp = { workspace = true } sp-consensus-aura = { workspace = true } +sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } sp-tracing = { workspace = true, default-features = true } -sp-core = { workspace = true } # Cumulus cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } -pallet-collator-selection = { workspace = true } -parachain-info = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-parachain-inherent = { workspace = true } cumulus-test-relay-sproof-builder = { workspace = true } +pallet-collator-selection = { workspace = true } +parachain-info = { workspace = true } +parachains-common = { workspace = true } # Polkadot -xcm = { workspace = true } -xcm-executor = { workspace = true } pallet-xcm = { workspace = true } polkadot-parachain-primitives = { workspace = true } +xcm = { workspace = true } +xcm-executor = { workspace = true } +xcm-runtime-apis = { workspace = true } [dev-dependencies] hex-literal = { workspace = true, default-features = true } @@ -62,11 +66,13 @@ std = [ "pallet-timestamp/std", "pallet-xcm/std", "parachain-info/std", + "parachains-common/std", "polkadot-parachain-primitives/std", "sp-consensus-aura/std", "sp-core/std", "sp-io/std", "sp-runtime/std", "xcm-executor/std", + "xcm-runtime-apis/std", "xcm/std", ] diff --git a/cumulus/parachains/runtimes/test-utils/src/lib.rs b/cumulus/parachains/runtimes/test-utils/src/lib.rs index 3f2e721d13f62419d486132d08610f46989ae075..b46a68312aa179775bfcfc873463ed83cb91d1a1 100644 --- a/cumulus/parachains/runtimes/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/test-utils/src/lib.rs @@ -230,7 +230,7 @@ impl<Runtime: BasicParachainRuntime> ExtBuilder<Runtime> { .unwrap(); } - pallet_balances::GenesisConfig::<Runtime> { balances: self.balances } + pallet_balances::GenesisConfig::<Runtime> { balances: self.balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -445,7 +445,11 @@ impl< // prepare xcm as governance will do let xcm = Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, - Transact { origin_kind: OriginKind::Superuser, call: call.into() }, + Transact { + origin_kind: OriginKind::Superuser, + call: call.into(), + fallback_max_weight: None, + }, ExpectTransactStatus(MaybeErrorCode::Success), ]); @@ -476,7 +480,7 @@ impl< // prepare `Transact` xcm instructions.extend(vec![ - Transact { origin_kind, call: call.encode().into() }, + Transact { origin_kind, call: call.encode().into(), fallback_max_weight: None }, ExpectTransactStatus(MaybeErrorCode::Success), ]); let xcm = Xcm(instructions); diff --git a/cumulus/parachains/runtimes/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/test-utils/src/test_cases.rs index a66163154cf696bf4f0708075e9cf853417d4700..6bdf3ef09d1b8c9b345d77e54a084b166d98dc69 100644 --- a/cumulus/parachains/runtimes/test-utils/src/test_cases.rs +++ b/cumulus/parachains/runtimes/test-utils/src/test_cases.rs @@ -18,7 +18,15 @@ use crate::{AccountIdOf, CollatorSessionKeys, ExtBuilder, ValidatorIdOf}; use codec::Encode; -use frame_support::{assert_ok, traits::Get}; +use frame_support::{ + assert_ok, + traits::{Get, OriginTrait}, +}; +use parachains_common::AccountId; +use sp_runtime::traits::{Block as BlockT, StaticLookup}; +use xcm_runtime_apis::fees::{ + runtime_decl_for_xcm_payment_api::XcmPaymentApiV1, Error as XcmPaymentApiError, +}; type RuntimeHelper<Runtime, AllPalletsWithoutSystem = ()> = crate::RuntimeHelper<Runtime, AllPalletsWithoutSystem>; @@ -128,3 +136,60 @@ pub fn set_storage_keys_by_governance_works<Runtime>( assert_storage(); }); } + +pub fn xcm_payment_api_with_native_token_works<Runtime, RuntimeCall, RuntimeOrigin, Block>() +where + Runtime: XcmPaymentApiV1<Block> + + frame_system::Config<RuntimeOrigin = RuntimeOrigin, AccountId = AccountId> + + pallet_balances::Config<Balance = u128> + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + cumulus_pallet_xcmp_queue::Config + + pallet_timestamp::Config, + ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>, + RuntimeOrigin: OriginTrait<AccountId = <Runtime as frame_system::Config>::AccountId>, + <<Runtime as frame_system::Config>::Lookup as StaticLookup>::Source: + From<<Runtime as frame_system::Config>::AccountId>, + Block: BlockT, +{ + use xcm::prelude::*; + ExtBuilder::<Runtime>::default().build().execute_with(|| { + let transfer_amount = 100u128; + let xcm_to_weigh = Xcm::<RuntimeCall>::builder_unsafe() + .withdraw_asset((Here, transfer_amount)) + .buy_execution((Here, transfer_amount), Unlimited) + .deposit_asset(AllCounted(1), [1u8; 32]) + .build(); + let versioned_xcm_to_weigh = VersionedXcm::from(xcm_to_weigh.clone().into()); + + // We first try calling it with a lower XCM version. + let lower_version_xcm_to_weigh = + versioned_xcm_to_weigh.clone().into_version(XCM_VERSION - 1).unwrap(); + let xcm_weight = Runtime::query_xcm_weight(lower_version_xcm_to_weigh); + assert!(xcm_weight.is_ok()); + let native_token: Location = Parent.into(); + let native_token_versioned = VersionedAssetId::from(AssetId(native_token)); + let lower_version_native_token = + native_token_versioned.clone().into_version(XCM_VERSION - 1).unwrap(); + let execution_fees = + Runtime::query_weight_to_asset_fee(xcm_weight.unwrap(), lower_version_native_token); + assert!(execution_fees.is_ok()); + + // Now we call it with the latest version. + let xcm_weight = Runtime::query_xcm_weight(versioned_xcm_to_weigh); + assert!(xcm_weight.is_ok()); + let execution_fees = + Runtime::query_weight_to_asset_fee(xcm_weight.unwrap(), native_token_versioned); + assert!(execution_fees.is_ok()); + + // If we call it with anything other than the native token it will error. + let non_existent_token: Location = Here.into(); + let non_existent_token_versioned = VersionedAssetId::from(AssetId(non_existent_token)); + let execution_fees = + Runtime::query_weight_to_asset_fee(xcm_weight.unwrap(), non_existent_token_versioned); + assert_eq!(execution_fees, Err(XcmPaymentApiError::AssetNotFound)); + }); +} diff --git a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml index 3a6b9d42f211bbca9895454c16483dff2d9042b8..5b17f4f5738803aeb0b73f86296145aee94ce4ed 100644 --- a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml @@ -32,6 +32,9 @@ frame-system = { workspace = true } frame-system-benchmarking = { optional = true, workspace = true } frame-system-rpc-runtime-api = { workspace = true } frame-try-runtime = { optional = true, workspace = true } +pallet-asset-conversion = { workspace = true } +pallet-asset-tx-payment = { workspace = true } +pallet-assets = { workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } @@ -40,9 +43,6 @@ pallet-sudo = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } -pallet-asset-tx-payment = { workspace = true } -pallet-assets = { workspace = true } -pallet-asset-conversion = { workspace = true } sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } @@ -57,9 +57,9 @@ sp-transaction-pool = { workspace = true } sp-version = { workspace = true } # Polkadot -polkadot-primitives = { workspace = true } pallet-xcm = { workspace = true } polkadot-parachain-primitives = { workspace = true } +polkadot-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } @@ -67,8 +67,8 @@ xcm-executor = { workspace = true } xcm-runtime-apis = { workspace = true } # Cumulus +assets-common = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } -pallet-message-queue = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } cumulus-pallet-xcm = { workspace = true } @@ -76,9 +76,9 @@ cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } +pallet-message-queue = { workspace = true } parachain-info = { workspace = true } parachains-common = { workspace = true } -assets-common = { workspace = true } snowbridge-router-primitives = { workspace = true } primitive-types = { version = "0.12.1", default-features = false, features = ["codec", "num-traits", "scale-info"] } @@ -175,6 +175,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index b51670c792d6badee368c5b176d086cc72bacc04..38ddf3bc1991b4e9541d111a6ec2c1fcf77dca88 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -140,6 +140,7 @@ pub type TxExtension = ( frame_system::CheckNonce<Runtime>, frame_system::CheckWeight<Runtime>, pallet_asset_tx_payment::ChargeAssetTxPayment<Runtime>, + frame_system::WeightReclaim<Runtime>, ); /// Unchecked extrinsic type as expected by this runtime. @@ -1132,18 +1133,8 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} - let whitelist: Vec<TrackedStorageKey> = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec<TrackedStorageKey> = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::<BenchmarkBatch>::new(); let params = (&config, &whitelist); diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml index b0581c8d43ff39ca2eea800fca1379a85e6f2ca9..826a2e9764fc1f788e9b6e62d977565d95630e00 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Simple runtime used by the rococo parachain(s)" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -41,25 +43,25 @@ sp-version = { workspace = true } # Polkadot pallet-xcm = { workspace = true } polkadot-parachain-primitives = { workspace = true } +polkadot-runtime-common = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } -polkadot-runtime-common = { workspace = true } # Cumulus cumulus-pallet-aura-ext = { workspace = true } -pallet-message-queue = { workspace = true } -cumulus-pallet-parachain-system = { workspace = true, features = ["experimental-ump-signals"] } +cumulus-pallet-parachain-system = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-ping = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } cumulus-primitives-utility = { workspace = true } +pallet-message-queue = { workspace = true } +parachain-info = { workspace = true } parachains-common = { workspace = true } testnet-parachains-constants = { features = ["rococo"], workspace = true } -parachain-info = { workspace = true } [build-dependencies] substrate-wasm-builder = { optional = true, workspace = true, default-features = true } @@ -70,12 +72,12 @@ std = [ "codec/std", "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-ping/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", @@ -115,6 +117,7 @@ std = [ ] runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -134,6 +137,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] # A feature that should be enabled when the runtime should be built for on-chain diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs index 42556e0b493cd6fb413742a67f7ce08012775213..89cd17d5450ac2af19f6c550e2a4b62abd298dbf 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -226,6 +226,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = (); +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -617,6 +621,7 @@ construct_runtime! { Timestamp: pallet_timestamp, Sudo: pallet_sudo, TransactionPayment: pallet_transaction_payment, + WeightReclaim: cumulus_pallet_weight_reclaim, ParachainSystem: cumulus_pallet_parachain_system = 20, ParachainInfo: parachain_info = 21, @@ -657,17 +662,20 @@ pub type SignedBlock = generic::SignedBlock<Block>; /// BlockId type as expected by this runtime. pub type BlockId = generic::BlockId<Block>; /// The extension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender<Runtime>, - frame_system::CheckSpecVersion<Runtime>, - frame_system::CheckTxVersion<Runtime>, - frame_system::CheckGenesis<Runtime>, - frame_system::CheckEra<Runtime>, - frame_system::CheckNonce<Runtime>, - frame_system::CheckWeight<Runtime>, - pallet_transaction_payment::ChargeTransactionPayment<Runtime>, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim<Runtime>, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckTxVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckEra<Runtime>, + frame_system::CheckNonce<Runtime>, + frame_system::CheckWeight<Runtime>, + pallet_transaction_payment::ChargeTransactionPayment<Runtime>, + ), +>; + /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, TxExtension>; diff --git a/cumulus/polkadot-omni-node/Cargo.toml b/cumulus/polkadot-omni-node/Cargo.toml index a736e1ef80c587ead7aad1d8c1b0159956a9d0a4..8b46bc8828684d3c5365112c65ae581e7c5714f5 100644 --- a/cumulus/polkadot-omni-node/Cargo.toml +++ b/cumulus/polkadot-omni-node/Cargo.toml @@ -6,6 +6,8 @@ edition.workspace = true build = "build.rs" description = "Generic binary that can run a parachain node with u32 block number and Aura consensus" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/polkadot-omni-node/README.md b/cumulus/polkadot-omni-node/README.md index d87b3b63c4071668a933d4ec28756dbfab43e08f..015019961c9f84a7c1a5753bc364137cacec2ec3 100644 --- a/cumulus/polkadot-omni-node/README.md +++ b/cumulus/polkadot-omni-node/README.md @@ -49,10 +49,10 @@ chain-spec-builder create --relay-chain <relay_chain_id> --para-id <id> -r <runt ### 3. Run Omni Node -And now with the generated chain spec we can start Omni Node like so: +And now with the generated chain spec we can start the node in development mode like so: ```bash -polkadot-omni-node --chain <chain_spec.json> +polkadot-omni-node --dev --chain <chain_spec.json> ``` ## Useful links diff --git a/cumulus/polkadot-omni-node/lib/Cargo.toml b/cumulus/polkadot-omni-node/lib/Cargo.toml index a690229f16959d464ffb385743e12dce0cbf1702..020d980d3d9d6f96ed05f6afa474f695873d40d9 100644 --- a/cumulus/polkadot-omni-node/lib/Cargo.toml +++ b/cumulus/polkadot-omni-node/lib/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Helper library that can be used to build a parachain node" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -17,55 +19,64 @@ async-trait = { workspace = true } clap = { features = ["derive"], workspace = true } codec = { workspace = true, default-features = true } color-print = { workspace = true } +docify = { 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 } +scale-info = { workspace = true } +subxt-metadata = { 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 } +frame-system-rpc-runtime-api = { workspace = true, default-features = true } +frame-try-runtime = { optional = true, workspace = true, default-features = true } +pallet-transaction-payment = { workspace = true, default-features = true } +pallet-transaction-payment-rpc = { workspace = true, default-features = true } +pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } +prometheus-endpoint = { workspace = true, default-features = true } +sc-basic-authorship = { workspace = true, default-features = true } +sc-chain-spec = { 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-consensus = { workspace = true, default-features = true } +sc-consensus-manual-seal = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } +sc-offchain = { workspace = true, default-features = true } +sc-rpc = { workspace = true, default-features = true } +sc-runtime-utilities = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } +sc-sysinfo = { workspace = true, default-features = true } sc-telemetry = { workspace = true, default-features = true } +sc-tracing = { 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 } +sc-transaction-pool-api = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } sp-block-builder = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } +sp-consensus-aura = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true } +sp-genesis-builder = { workspace = true } +sp-inherents = { 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-offchain = { workspace = true, default-features = true } +sp-runtime = { workspace = true } +sp-session = { workspace = true, default-features = true } +sp-storage = { workspace = true, default-features = true } +sp-timestamp = { workspace = true, default-features = true } +sp-transaction-pool = { 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-consensus-manual-seal = { 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 @@ -76,9 +87,9 @@ polkadot-primitives = { workspace = true, default-features = true } 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-consensus-relay-chain = { 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 } @@ -88,18 +99,15 @@ futures-timer = "3.0.3" [dev-dependencies] assert_cmd = { workspace = true } +cumulus-test-runtime = { 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", -] +rococo-native = ["polkadot-cli/rococo-native"] +westend-native = ["polkadot-cli/westend-native"] runtime-benchmarks = [ "cumulus-primitives-core/runtime-benchmarks", "frame-benchmarking-cli/runtime-benchmarks", diff --git a/cumulus/polkadot-omni-node/lib/src/cli.rs b/cumulus/polkadot-omni-node/lib/src/cli.rs index dc59c185d90998ec96b0811a3ef32e241e63933d..9c4e2561592db6682ea7051dc844465744d0da26 100644 --- a/cumulus/polkadot-omni-node/lib/src/cli.rs +++ b/cumulus/polkadot-omni-node/lib/src/cli.rs @@ -126,9 +126,14 @@ pub struct Cli<Config: CliConfig> { /// Start a dev node that produces a block each `dev_block_time` ms. /// - /// This is a dev option, and it won't result in starting or connecting to a parachain network. - /// The resulting node will work on its own, running the wasm blob and artificially producing - /// a block each `dev_block_time` ms, as if it was part of a parachain. + /// This is a dev option. It enables a manual sealing, meaning blocks are produced manually + /// rather than being part of an actual network consensus process. Using the option won't + /// result in starting or connecting to a parachain network. The resulting node will work on + /// its own, running the wasm blob and artificially producing a block each `dev_block_time` ms, + /// as if it was part of a parachain. + /// + /// The `--dev` flag sets the `dev_block_time` to a default value of 3000ms unless explicitly + /// provided. #[arg(long)] pub dev_block_time: Option<u64>, diff --git a/cumulus/polkadot-omni-node/lib/src/command.rs b/cumulus/polkadot-omni-node/lib/src/command.rs index cf283819966fa42546b16b852a1581345cb84e3d..fe7f7cac097199c3ec70d9278e9eb63c83b3327f 100644 --- a/cumulus/polkadot-omni-node/lib/src/command.rs +++ b/cumulus/polkadot-omni-node/lib/src/command.rs @@ -34,11 +34,13 @@ use cumulus_client_service::storage_proof_size::HostFunctions as ReclaimHostFunc use cumulus_primitives_core::ParaId; use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; use log::info; -use sc_cli::{Result, SubstrateCli}; +use sc_cli::{CliConfiguration, Result, SubstrateCli}; use sp_runtime::traits::AccountIdConversion; #[cfg(feature = "runtime-benchmarks")] use sp_runtime::traits::HashingFor; +const DEFAULT_DEV_BLOCK_TIME_MS: u64 = 3000; + /// 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 { @@ -230,10 +232,19 @@ pub fn run<CliConfig: crate::cli::CliConfig>(cmd_config: RunConfig) -> Result<() .ok_or("Could not find parachain extension in chain-spec.")?, ); + if cli.run.base.is_dev()? { + // Set default dev block time to 3000ms if not set. + // TODO: take block time from AURA config if set. + let dev_block_time = cli.dev_block_time.unwrap_or(DEFAULT_DEV_BLOCK_TIME_MS); + return node_spec + .start_manual_seal_node(config, para_id, dev_block_time) + .map_err(Into::into); + } + if let Some(dev_block_time) = cli.dev_block_time { return node_spec .start_manual_seal_node(config, para_id, dev_block_time) - .map_err(Into::into) + .map_err(Into::into); } // If Statemint (Statemine, Westmint, Rockmine) DB exists and we're using the diff --git a/cumulus/polkadot-omni-node/lib/src/common/mod.rs b/cumulus/polkadot-omni-node/lib/src/common/mod.rs index 37660a5347a20b8fad5b6452eed331f98806c8d7..843183425dabfa44f481855691dfaba69cd056fa 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/mod.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/mod.rs @@ -28,6 +28,7 @@ pub mod types; use cumulus_primitives_core::{CollectCollationInfo, GetCoreSelectorApi}; use sc_client_db::DbHash; +use sc_offchain::OffchainWorkerApi; use serde::de::DeserializeOwned; use sp_api::{ApiExt, CallApiAt, ConstructRuntimeApi, Metadata}; use sp_block_builder::BlockBuilder; @@ -65,6 +66,7 @@ pub trait NodeRuntimeApi<Block: BlockT>: + SessionKeys<Block> + BlockBuilder<Block> + TaggedTransactionQueue<Block> + + OffchainWorkerApi<Block> + CollectCollationInfo<Block> + GetCoreSelectorApi<Block> + Sized @@ -77,6 +79,7 @@ impl<T, Block: BlockT> NodeRuntimeApi<Block> for T where + SessionKeys<Block> + BlockBuilder<Block> + TaggedTransactionQueue<Block> + + OffchainWorkerApi<Block> + GetCoreSelectorApi<Block> + CollectCollationInfo<Block> { diff --git a/cumulus/polkadot-omni-node/lib/src/common/runtime.rs b/cumulus/polkadot-omni-node/lib/src/common/runtime.rs index 509d13b9d7a26c0068972dc6d2563ec869e07fa9..fcc1d7f0643e7702ee7da7687eb28d401a9092c2 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/runtime.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/runtime.rs @@ -16,7 +16,19 @@ //! Runtime parameters. +use codec::Decode; +use cumulus_client_service::ParachainHostFunctions; use sc_chain_spec::ChainSpec; +use sc_executor::WasmExecutor; +use sc_runtime_utilities::fetch_latest_metadata_from_code_blob; +use scale_info::{form::PortableForm, TypeDef, TypeDefPrimitive}; +use std::fmt::Display; +use subxt_metadata::{Metadata, StorageEntryType}; + +/// Expected parachain system pallet runtime type name. +pub const DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME: &str = "ParachainSystem"; +/// Expected frame system pallet runtime type name. +pub const DEFAULT_FRAME_SYSTEM_PALLET_NAME: &str = "System"; /// The Aura ID used by the Aura consensus #[derive(PartialEq)] @@ -35,7 +47,7 @@ pub enum Consensus { } /// The choice of block number for the parachain omni-node. -#[derive(PartialEq)] +#[derive(PartialEq, Debug)] pub enum BlockNumber { /// u32 U32, @@ -43,6 +55,34 @@ pub enum BlockNumber { U64, } +impl Display for BlockNumber { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BlockNumber::U32 => write!(f, "u32"), + BlockNumber::U64 => write!(f, "u64"), + } + } +} + +impl Into<TypeDefPrimitive> for BlockNumber { + fn into(self) -> TypeDefPrimitive { + match self { + BlockNumber::U32 => TypeDefPrimitive::U32, + BlockNumber::U64 => TypeDefPrimitive::U64, + } + } +} + +impl BlockNumber { + fn from_type_def(type_def: &TypeDef<PortableForm>) -> Option<BlockNumber> { + match type_def { + TypeDef::Primitive(TypeDefPrimitive::U32) => Some(BlockNumber::U32), + TypeDef::Primitive(TypeDefPrimitive::U64) => Some(BlockNumber::U64), + _ => None, + } + } +} + /// Helper enum listing the supported Runtime types #[derive(PartialEq)] pub enum Runtime { @@ -62,7 +102,112 @@ pub trait RuntimeResolver { pub struct DefaultRuntimeResolver; impl RuntimeResolver for DefaultRuntimeResolver { - fn runtime(&self, _chain_spec: &dyn ChainSpec) -> sc_cli::Result<Runtime> { - Ok(Runtime::Omni(BlockNumber::U32, Consensus::Aura(AuraConsensusId::Sr25519))) + fn runtime(&self, chain_spec: &dyn ChainSpec) -> sc_cli::Result<Runtime> { + let Ok(metadata_inspector) = MetadataInspector::new(chain_spec) else { + log::info!("Unable to check metadata. Skipping metadata checks. Metadata checks are supported for metadata versions v14 and higher."); + return Ok(Runtime::Omni(BlockNumber::U32, Consensus::Aura(AuraConsensusId::Sr25519))) + }; + + let block_number = match metadata_inspector.block_number() { + Some(inner) => inner, + None => { + log::warn!( + r#"âš ï¸ There isn't a runtime type named `System`, corresponding to the `frame-system` + pallet (https://docs.rs/frame-system/latest/frame_system/). Please check Omni Node docs for runtime conventions: + https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html#runtime-conventions. + Note: We'll assume a block number size of `u32`."# + ); + BlockNumber::U32 + }, + }; + + if !metadata_inspector.pallet_exists(DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME) { + log::warn!( + r#"âš ï¸ The parachain system pallet (https://docs.rs/crate/cumulus-pallet-parachain-system/latest) is + missing from the runtime’s metadata. Please check Omni Node docs for runtime conventions: + https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html#runtime-conventions."# + ); + } + + Ok(Runtime::Omni(block_number, Consensus::Aura(AuraConsensusId::Sr25519))) + } +} + +struct MetadataInspector(Metadata); + +impl MetadataInspector { + fn new(chain_spec: &dyn ChainSpec) -> Result<MetadataInspector, sc_cli::Error> { + MetadataInspector::fetch_metadata(chain_spec).map(MetadataInspector) + } + + fn pallet_exists(&self, name: &str) -> bool { + self.0.pallet_by_name(name).is_some() + } + + fn block_number(&self) -> Option<BlockNumber> { + let pallet_metadata = self.0.pallet_by_name(DEFAULT_FRAME_SYSTEM_PALLET_NAME); + pallet_metadata + .and_then(|inner| inner.storage()) + .and_then(|inner| inner.entry_by_name("Number")) + .and_then(|number_ty| match number_ty.entry_type() { + StorageEntryType::Plain(ty_id) => Some(ty_id), + _ => None, + }) + .and_then(|ty_id| self.0.types().resolve(*ty_id)) + .and_then(|portable_type| BlockNumber::from_type_def(&portable_type.type_def)) + } + + fn fetch_metadata(chain_spec: &dyn ChainSpec) -> Result<Metadata, sc_cli::Error> { + let mut storage = chain_spec.build_storage()?; + let code_bytes = storage + .top + .remove(sp_storage::well_known_keys::CODE) + .ok_or("chain spec genesis does not contain code")?; + let opaque_metadata = fetch_latest_metadata_from_code_blob( + &WasmExecutor::<ParachainHostFunctions>::builder() + .with_allow_missing_host_functions(true) + .build(), + sp_runtime::Cow::Borrowed(code_bytes.as_slice()), + ) + .map_err(|err| err.to_string())?; + + Metadata::decode(&mut (*opaque_metadata).as_slice()).map_err(Into::into) + } +} + +#[cfg(test)] +mod tests { + use crate::runtime::{ + BlockNumber, MetadataInspector, DEFAULT_FRAME_SYSTEM_PALLET_NAME, + DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME, + }; + use codec::Decode; + use cumulus_client_service::ParachainHostFunctions; + use sc_executor::WasmExecutor; + use sc_runtime_utilities::fetch_latest_metadata_from_code_blob; + + fn cumulus_test_runtime_metadata() -> subxt_metadata::Metadata { + let opaque_metadata = fetch_latest_metadata_from_code_blob( + &WasmExecutor::<ParachainHostFunctions>::builder() + .with_allow_missing_host_functions(true) + .build(), + sp_runtime::Cow::Borrowed(cumulus_test_runtime::WASM_BINARY.unwrap()), + ) + .unwrap(); + + subxt_metadata::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap() + } + + #[test] + fn test_pallet_exists() { + let metadata_inspector = MetadataInspector(cumulus_test_runtime_metadata()); + assert!(metadata_inspector.pallet_exists(DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME)); + assert!(metadata_inspector.pallet_exists(DEFAULT_FRAME_SYSTEM_PALLET_NAME)); + } + + #[test] + fn test_runtime_block_number() { + let metadata_inspector = MetadataInspector(cumulus_test_runtime_metadata()); + assert_eq!(metadata_inspector.block_number().unwrap(), BlockNumber::U32); } } diff --git a/cumulus/polkadot-omni-node/lib/src/common/spec.rs b/cumulus/polkadot-omni-node/lib/src/common/spec.rs index 259f89049c923e7ce1a4094e5d011a6acd81a0ac..d497337904b9798fcaaa2aa5d78853d33b959fb8 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/spec.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/spec.rs @@ -30,9 +30,11 @@ use cumulus_client_service::{ }; use cumulus_primitives_core::{BlockT, ParaId}; use cumulus_relay_chain_interface::{OverseerHandle, RelayChainInterface}; +use futures::FutureExt; use parachains_common::Hash; use polkadot_primitives::CollatorPair; use prometheus_endpoint::Registry; +use sc_client_api::Backend; use sc_consensus::DefaultImportQueue; use sc_executor::{HeapAllocStrategy, DEFAULT_HEAP_ALLOC_STRATEGY}; use sc_network::{config::FullNetworkConfiguration, NetworkBackend, NetworkBlock}; @@ -41,26 +43,32 @@ use sc_sysinfo::HwBench; use sc_telemetry::{TelemetryHandle, TelemetryWorker}; use sc_tracing::tracing::Instrument; use sc_transaction_pool::TransactionPoolHandle; +use sc_transaction_pool_api::OffchainTransactionPoolFactory; use sp_keystore::KeystorePtr; use std::{future::Future, pin::Pin, sync::Arc, time::Duration}; -pub(crate) trait BuildImportQueue<Block: BlockT, RuntimeApi> { +pub(crate) trait BuildImportQueue< + Block: BlockT, + RuntimeApi, + BlockImport: sc_consensus::BlockImport<Block>, +> +{ fn build_import_queue( client: Arc<ParachainClient<Block, RuntimeApi>>, - block_import: ParachainBlockImport<Block, RuntimeApi>, + block_import: ParachainBlockImport<Block, BlockImport>, config: &Configuration, telemetry_handle: Option<TelemetryHandle>, task_manager: &TaskManager, ) -> sc_service::error::Result<DefaultImportQueue<Block>>; } -pub(crate) trait StartConsensus<Block: BlockT, RuntimeApi> +pub(crate) trait StartConsensus<Block: BlockT, RuntimeApi, BI, BIAuxiliaryData> where RuntimeApi: ConstructNodeRuntimeApi<Block, ParachainClient<Block, RuntimeApi>>, { fn start_consensus( client: Arc<ParachainClient<Block, RuntimeApi>>, - block_import: ParachainBlockImport<Block, RuntimeApi>, + block_import: ParachainBlockImport<Block, BI>, prometheus_registry: Option<&Registry>, telemetry: Option<TelemetryHandle>, task_manager: &TaskManager, @@ -74,6 +82,7 @@ where announce_block: Arc<dyn Fn(Hash, Option<Vec<u8>>) + Send + Sync>, backend: Arc<ParachainBackend<Block>>, node_extra_args: NodeExtraArgs, + block_import_extra_return_value: BIAuxiliaryData, ) -> Result<(), sc_service::Error>; } @@ -92,6 +101,31 @@ fn warn_if_slow_hardware(hwbench: &sc_sysinfo::HwBench) { } } +pub(crate) trait InitBlockImport<Block: BlockT, RuntimeApi> { + type BlockImport: sc_consensus::BlockImport<Block> + Clone + Send + Sync; + type BlockImportAuxiliaryData; + + fn init_block_import( + client: Arc<ParachainClient<Block, RuntimeApi>>, + ) -> sc_service::error::Result<(Self::BlockImport, Self::BlockImportAuxiliaryData)>; +} + +pub(crate) struct ClientBlockImport; + +impl<Block: BlockT, RuntimeApi> InitBlockImport<Block, RuntimeApi> for ClientBlockImport +where + RuntimeApi: Send + ConstructNodeRuntimeApi<Block, ParachainClient<Block, RuntimeApi>>, +{ + type BlockImport = Arc<ParachainClient<Block, RuntimeApi>>; + type BlockImportAuxiliaryData = (); + + fn init_block_import( + client: Arc<ParachainClient<Block, RuntimeApi>>, + ) -> sc_service::error::Result<(Self::BlockImport, Self::BlockImportAuxiliaryData)> { + Ok((client.clone(), ())) + } +} + pub(crate) trait BaseNodeSpec { type Block: NodeBlock; @@ -100,7 +134,13 @@ pub(crate) trait BaseNodeSpec { ParachainClient<Self::Block, Self::RuntimeApi>, >; - type BuildImportQueue: BuildImportQueue<Self::Block, Self::RuntimeApi>; + type BuildImportQueue: BuildImportQueue< + Self::Block, + Self::RuntimeApi, + <Self::InitBlockImport as InitBlockImport<Self::Block, Self::RuntimeApi>>::BlockImport, + >; + + type InitBlockImport: self::InitBlockImport<Self::Block, Self::RuntimeApi>; /// Starts a `ServiceBuilder` for a full service. /// @@ -108,7 +148,14 @@ pub(crate) trait BaseNodeSpec { /// be able to perform chain operations. fn new_partial( config: &Configuration, - ) -> sc_service::error::Result<ParachainService<Self::Block, Self::RuntimeApi>> { + ) -> sc_service::error::Result< + ParachainService< + Self::Block, + Self::RuntimeApi, + <Self::InitBlockImport as InitBlockImport<Self::Block, Self::RuntimeApi>>::BlockImport, + <Self::InitBlockImport as InitBlockImport<Self::Block, Self::RuntimeApi>>::BlockImportAuxiliaryData + > + >{ let telemetry = config .telemetry_endpoints .clone() @@ -160,7 +207,10 @@ pub(crate) trait BaseNodeSpec { .build(), ); - let block_import = ParachainBlockImport::new(client.clone(), backend.clone()); + let (block_import, block_import_auxiliary_data) = + Self::InitBlockImport::init_block_import(client.clone())?; + + let block_import = ParachainBlockImport::new(block_import, backend.clone()); let import_queue = Self::BuildImportQueue::build_import_queue( client.clone(), @@ -178,7 +228,7 @@ pub(crate) trait BaseNodeSpec { task_manager, transaction_pool, select_chain: (), - other: (block_import, telemetry, telemetry_worker_handle), + other: (block_import, telemetry, telemetry_worker_handle, block_import_auxiliary_data), }) } } @@ -190,7 +240,12 @@ pub(crate) trait NodeSpec: BaseNodeSpec { TransactionPoolHandle<Self::Block, ParachainClient<Self::Block, Self::RuntimeApi>>, >; - type StartConsensus: StartConsensus<Self::Block, Self::RuntimeApi>; + type StartConsensus: StartConsensus< + Self::Block, + Self::RuntimeApi, + <Self::InitBlockImport as InitBlockImport<Self::Block, Self::RuntimeApi>>::BlockImport, + <Self::InitBlockImport as InitBlockImport<Self::Block, Self::RuntimeApi>>::BlockImportAuxiliaryData, + >; const SYBIL_RESISTANCE: CollatorSybilResistance; @@ -208,151 +263,174 @@ pub(crate) trait NodeSpec: BaseNodeSpec { where Net: NetworkBackend<Self::Block, Hash>, { - 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, 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, + let fut = 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, block_import_auxiliary_data) = + 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, sync_service) = + build_network(BuildNetworkParams { + parachain_config: ¶chain_config, + net_config, 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, + spawn_handle: task_manager.spawn_handle(), 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, - })?; + import_queue: params.import_queue, + sybil_resistance_level: Self::SYBIL_RESISTANCE, + }) + .await?; + + if parachain_config.offchain_worker.enabled { + let offchain_workers = + sc_offchain::OffchainWorkers::new(sc_offchain::OffchainWorkerOptions { + runtime_api_provider: client.clone(), + keystore: Some(params.keystore_container.keystore()), + offchain_db: backend.offchain_storage(), + transaction_pool: Some(OffchainTransactionPoolFactory::new( + transaction_pool.clone(), + )), + network_provider: Arc::new(network.clone()), + is_validator: parachain_config.role.is_authority(), + enable_http_requests: false, + custom_extensions: move |_| vec![], + })?; + task_manager.spawn_handle().spawn( + "offchain-workers-runner", + "offchain-work", + offchain_workers.run(client.clone(), task_manager.spawn_handle()).boxed(), + ); + } - if validator { - Self::StartConsensus::start_consensus( + 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(), - 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, - )?; + 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), + ); } + } - Ok(task_manager) + 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, + block_import_auxiliary_data, + )?; } - .instrument(sc_tracing::tracing::info_span!( + + Ok(task_manager) + }; + + Box::pin(Instrument::instrument( + fut, + sc_tracing::tracing::info_span!( sc_tracing::logging::PREFIX_LOG_SPAN, - name = "Parachain", - )), - ) + name = "Parachain" + ), + )) } } diff --git a/cumulus/polkadot-omni-node/lib/src/common/types.rs b/cumulus/polkadot-omni-node/lib/src/common/types.rs index 4bc58dc9db7e404cdfefb16c6e59fc0b0faa27bf..978368be25847233632bce5c39caf0bc07bdf42f 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/types.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/types.rs @@ -22,7 +22,6 @@ use sc_service::{PartialComponents, TFullBackend, TFullClient}; use sc_telemetry::{Telemetry, TelemetryWorkerHandle}; use sc_transaction_pool::TransactionPoolHandle; use sp_runtime::{generic, traits::BlakeTwo256}; -use std::sync::Arc; pub use parachains_common::{AccountId, Balance, Hash, Nonce}; @@ -42,15 +41,20 @@ pub type ParachainClient<Block, RuntimeApi> = pub type ParachainBackend<Block> = TFullBackend<Block>; -pub type ParachainBlockImport<Block, RuntimeApi> = - TParachainBlockImport<Block, Arc<ParachainClient<Block, RuntimeApi>>, ParachainBackend<Block>>; +pub type ParachainBlockImport<Block, BI> = + TParachainBlockImport<Block, BI, ParachainBackend<Block>>; /// Assembly of PartialComponents (enough to run chain ops subcommands) -pub type ParachainService<Block, RuntimeApi> = PartialComponents< +pub type ParachainService<Block, RuntimeApi, BI, BIExtraReturnValue> = PartialComponents< ParachainClient<Block, RuntimeApi>, ParachainBackend<Block>, (), DefaultImportQueue<Block>, TransactionPoolHandle<Block, ParachainClient<Block, RuntimeApi>>, - (ParachainBlockImport<Block, RuntimeApi>, Option<Telemetry>, Option<TelemetryWorkerHandle>), + ( + ParachainBlockImport<Block, BI>, + Option<Telemetry>, + Option<TelemetryWorkerHandle>, + BIExtraReturnValue, + ), >; diff --git a/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs b/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs index 6bfd5f4f4cbd1743782303d0d5f6ab78d16c7b4a..915f156f8cb53ceee4b4d5976297586f11438f96 100644 --- a/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs +++ b/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs @@ -111,6 +111,12 @@ macro_rules! impl_node_runtime_apis { } } + impl sp_offchain::OffchainWorkerApi<$block> for $runtime { + fn offchain_worker(_: &<$block as BlockT>::Header) { + unimplemented!() + } + } + impl sp_session::SessionKeys<$block> for $runtime { fn generate_session_keys(_: Option<Vec<u8>>) -> Vec<u8> { unimplemented!() diff --git a/cumulus/polkadot-omni-node/lib/src/lib.rs b/cumulus/polkadot-omni-node/lib/src/lib.rs index ccc1b542b253dd9e002baf3f62f23a6dc3b32f02..92ea3d7d8791f9929dbebdbb9f8d3de897e077f3 100644 --- a/cumulus/polkadot-omni-node/lib/src/lib.rs +++ b/cumulus/polkadot-omni-node/lib/src/lib.rs @@ -26,3 +26,4 @@ mod nodes; pub use cli::CliConfig; pub use command::{run, RunConfig}; pub use common::{chain_spec, runtime}; +pub use nodes::NODE_VERSION; diff --git a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs index 0b2c230f695d3b77f8d96c96bb8afc0ea755f760..cd0e35d0d0699d10ba46f15e002da90d59813cbb 100644 --- a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs +++ b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs @@ -18,7 +18,10 @@ use crate::{ common::{ aura::{AuraIdT, AuraRuntimeApi}, rpc::BuildParachainRpcExtensions, - spec::{BaseNodeSpec, BuildImportQueue, NodeSpec, StartConsensus}, + spec::{ + BaseNodeSpec, BuildImportQueue, ClientBlockImport, InitBlockImport, NodeSpec, + StartConsensus, + }, types::{ AccountId, Balance, Hash, Nonce, ParachainBackend, ParachainBlockImport, ParachainClient, @@ -30,11 +33,17 @@ use crate::{ 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_aura::{ + collators::{ + lookahead::{self as aura, Params as AuraParams}, + slot_based::{SlotBasedBlockImport, SlotBasedBlockImportHandle}, + }, + equivocation_import_queue::Verifier as EquivocationVerifier, +}; use cumulus_client_consensus_proposer::{Proposer, ProposerInterface}; use cumulus_client_consensus_relay_chain::Verifier as RelayChainVerifier; #[allow(deprecated)] @@ -91,67 +100,49 @@ where /// Build the import queue for parachain runtimes that started with relay chain consensus and /// switched to aura. -pub(crate) struct BuildRelayToAuraImportQueue<Block, RuntimeApi, AuraId>( - PhantomData<(Block, RuntimeApi, AuraId)>, +pub(crate) struct BuildRelayToAuraImportQueue<Block, RuntimeApi, AuraId, BlockImport>( + PhantomData<(Block, RuntimeApi, AuraId, BlockImport)>, ); -impl<Block: BlockT, RuntimeApi, AuraId> BuildImportQueue<Block, RuntimeApi> - for BuildRelayToAuraImportQueue<Block, RuntimeApi, AuraId> +impl<Block: BlockT, RuntimeApi, AuraId, BlockImport> + BuildImportQueue<Block, RuntimeApi, BlockImport> + for BuildRelayToAuraImportQueue<Block, RuntimeApi, AuraId, BlockImport> where RuntimeApi: ConstructNodeRuntimeApi<Block, ParachainClient<Block, RuntimeApi>>, RuntimeApi::RuntimeApi: AuraRuntimeApi<Block, AuraId>, AuraId: AuraIdT + Sync, + BlockImport: + sc_consensus::BlockImport<Block, Error = sp_consensus::Error> + Send + Sync + 'static, { fn build_import_queue( client: Arc<ParachainClient<Block, RuntimeApi>>, - block_import: ParachainBlockImport<Block, RuntimeApi>, + block_import: ParachainBlockImport<Block, BlockImport>, config: &Configuration, telemetry_handle: Option<TelemetryHandle>, task_manager: &TaskManager, ) -> sc_service::error::Result<DefaultImportQueue<Block>> { - let verifier_client = client.clone(); - - let aura_verifier = cumulus_client_consensus_aura::build_verifier::< - <AuraId as AppCrypto>::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 inherent_data_providers = + move |_, _| async move { Ok(sp_timestamp::InherentDataProvider::from_system_time()) }; + let registry = config.prometheus_registry(); + let spawner = task_manager.spawn_essential_handle(); let relay_chain_verifier = Box::new(RelayChainVerifier::new(client.clone(), |_, _| async { Ok(()) })); + let equivocation_aura_verifier = + EquivocationVerifier::<<AuraId as AppCrypto>::Pair, _, _, _>::new( + client.clone(), + inherent_data_providers, + telemetry_handle, + ); + let verifier = Verifier { client, + aura_verifier: Box::new(equivocation_aura_verifier), relay_chain_verifier, - aura_verifier: Box::new(aura_verifier), - _phantom: PhantomData, + _phantom: Default::default(), }; - let registry = config.prometheus_registry(); - let spawner = task_manager.spawn_essential_handle(); - Ok(BasicQueue::new(verifier, Box::new(block_import), None, &spawner, registry)) } } @@ -159,20 +150,20 @@ where /// Uses the lookahead collator to support async backing. /// /// Start an aura powered parachain node. Some system chains use this. -pub(crate) struct AuraNode<Block, RuntimeApi, AuraId, StartConsensus>( - pub PhantomData<(Block, RuntimeApi, AuraId, StartConsensus)>, +pub(crate) struct AuraNode<Block, RuntimeApi, AuraId, StartConsensus, InitBlockImport>( + pub PhantomData<(Block, RuntimeApi, AuraId, StartConsensus, InitBlockImport)>, ); -impl<Block, RuntimeApi, AuraId, StartConsensus> Default - for AuraNode<Block, RuntimeApi, AuraId, StartConsensus> +impl<Block, RuntimeApi, AuraId, StartConsensus, InitBlockImport> Default + for AuraNode<Block, RuntimeApi, AuraId, StartConsensus, InitBlockImport> { fn default() -> Self { Self(Default::default()) } } -impl<Block, RuntimeApi, AuraId, StartConsensus> BaseNodeSpec - for AuraNode<Block, RuntimeApi, AuraId, StartConsensus> +impl<Block, RuntimeApi, AuraId, StartConsensus, InitBlockImport> BaseNodeSpec + for AuraNode<Block, RuntimeApi, AuraId, StartConsensus, InitBlockImport> where Block: NodeBlock, RuntimeApi: ConstructNodeRuntimeApi<Block, ParachainClient<Block, RuntimeApi>>, @@ -180,14 +171,19 @@ where + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<Block, Balance> + substrate_frame_rpc_system::AccountNonceApi<Block, AccountId, Nonce>, AuraId: AuraIdT + Sync, + InitBlockImport: self::InitBlockImport<Block, RuntimeApi> + Send, + InitBlockImport::BlockImport: + sc_consensus::BlockImport<Block, Error = sp_consensus::Error> + 'static, { type Block = Block; type RuntimeApi = RuntimeApi; - type BuildImportQueue = BuildRelayToAuraImportQueue<Block, RuntimeApi, AuraId>; + type BuildImportQueue = + BuildRelayToAuraImportQueue<Block, RuntimeApi, AuraId, InitBlockImport::BlockImport>; + type InitBlockImport = InitBlockImport; } -impl<Block, RuntimeApi, AuraId, StartConsensus> NodeSpec - for AuraNode<Block, RuntimeApi, AuraId, StartConsensus> +impl<Block, RuntimeApi, AuraId, StartConsensus, InitBlockImport> NodeSpec + for AuraNode<Block, RuntimeApi, AuraId, StartConsensus, InitBlockImport> where Block: NodeBlock, RuntimeApi: ConstructNodeRuntimeApi<Block, ParachainClient<Block, RuntimeApi>>, @@ -195,7 +191,15 @@ where + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<Block, Balance> + substrate_frame_rpc_system::AccountNonceApi<Block, AccountId, Nonce>, AuraId: AuraIdT + Sync, - StartConsensus: self::StartConsensus<Block, RuntimeApi> + 'static, + StartConsensus: self::StartConsensus< + Block, + RuntimeApi, + InitBlockImport::BlockImport, + InitBlockImport::BlockImportAuxiliaryData, + > + 'static, + InitBlockImport: self::InitBlockImport<Block, RuntimeApi> + Send, + InitBlockImport::BlockImport: + sc_consensus::BlockImport<Block, Error = sp_consensus::Error> + 'static, { type BuildRpcExtensions = BuildParachainRpcExtensions<Block, RuntimeApi>; type StartConsensus = StartConsensus; @@ -219,6 +223,7 @@ where RuntimeApi, AuraId, StartSlotBasedAuraConsensus<Block, RuntimeApi, AuraId>, + StartSlotBasedAuraConsensus<Block, RuntimeApi, AuraId>, >::default()) } else { Box::new(AuraNode::< @@ -226,6 +231,7 @@ where RuntimeApi, AuraId, StartLookaheadAuraConsensus<Block, RuntimeApi, AuraId>, + ClientBlockImport, >::default()) } } @@ -245,7 +251,15 @@ where #[docify::export_content] fn launch_slot_based_collator<CIDP, CHP, Proposer, CS, Spawner>( params: SlotBasedParams< - ParachainBlockImport<Block, RuntimeApi>, + Block, + ParachainBlockImport< + Block, + SlotBasedBlockImport< + Block, + Arc<ParachainClient<Block, RuntimeApi>>, + ParachainClient<Block, RuntimeApi>, + >, + >, CIDP, ParachainClient<Block, RuntimeApi>, ParachainBackend<Block>, @@ -267,8 +281,17 @@ where } } -impl<Block: BlockT<Hash = DbHash>, RuntimeApi, AuraId> StartConsensus<Block, RuntimeApi> - for StartSlotBasedAuraConsensus<Block, RuntimeApi, AuraId> +impl<Block: BlockT<Hash = DbHash>, RuntimeApi, AuraId> + StartConsensus< + Block, + RuntimeApi, + SlotBasedBlockImport< + Block, + Arc<ParachainClient<Block, RuntimeApi>>, + ParachainClient<Block, RuntimeApi>, + >, + SlotBasedBlockImportHandle<Block>, + > for StartSlotBasedAuraConsensus<Block, RuntimeApi, AuraId> where RuntimeApi: ConstructNodeRuntimeApi<Block, ParachainClient<Block, RuntimeApi>>, RuntimeApi::RuntimeApi: AuraRuntimeApi<Block, AuraId>, @@ -276,7 +299,14 @@ where { fn start_consensus( client: Arc<ParachainClient<Block, RuntimeApi>>, - block_import: ParachainBlockImport<Block, RuntimeApi>, + block_import: ParachainBlockImport< + Block, + SlotBasedBlockImport< + Block, + Arc<ParachainClient<Block, RuntimeApi>>, + ParachainClient<Block, RuntimeApi>, + >, + >, prometheus_registry: Option<&Registry>, telemetry: Option<TelemetryHandle>, task_manager: &TaskManager, @@ -290,6 +320,7 @@ where announce_block: Arc<dyn Fn(Hash, Option<Vec<u8>>) + Send + Sync>, backend: Arc<ParachainBackend<Block>>, _node_extra_args: NodeExtraArgs, + block_import_handle: SlotBasedBlockImportHandle<Block>, ) -> Result<(), Error> { let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( task_manager.spawn_handle(), @@ -325,6 +356,7 @@ where authoring_duration: Duration::from_millis(2000), reinitialize: false, slot_drift: Duration::from_secs(1), + block_import_handle, spawner: task_manager.spawn_handle(), }; @@ -336,6 +368,27 @@ where } } +impl<Block: BlockT<Hash = DbHash>, RuntimeApi, AuraId> InitBlockImport<Block, RuntimeApi> + for StartSlotBasedAuraConsensus<Block, RuntimeApi, AuraId> +where + RuntimeApi: ConstructNodeRuntimeApi<Block, ParachainClient<Block, RuntimeApi>>, + RuntimeApi::RuntimeApi: AuraRuntimeApi<Block, AuraId>, + AuraId: AuraIdT + Sync, +{ + type BlockImport = SlotBasedBlockImport< + Block, + Arc<ParachainClient<Block, RuntimeApi>>, + ParachainClient<Block, RuntimeApi>, + >; + type BlockImportAuxiliaryData = SlotBasedBlockImportHandle<Block>; + + fn init_block_import( + client: Arc<ParachainClient<Block, RuntimeApi>>, + ) -> sc_service::error::Result<(Self::BlockImport, Self::BlockImportAuxiliaryData)> { + Ok(SlotBasedBlockImport::new(client.clone(), client)) + } +} + /// 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. @@ -364,7 +417,8 @@ pub(crate) struct StartLookaheadAuraConsensus<Block, RuntimeApi, AuraId>( PhantomData<(Block, RuntimeApi, AuraId)>, ); -impl<Block: BlockT<Hash = DbHash>, RuntimeApi, AuraId> StartConsensus<Block, RuntimeApi> +impl<Block: BlockT<Hash = DbHash>, RuntimeApi, AuraId> + StartConsensus<Block, RuntimeApi, Arc<ParachainClient<Block, RuntimeApi>>, ()> for StartLookaheadAuraConsensus<Block, RuntimeApi, AuraId> where RuntimeApi: ConstructNodeRuntimeApi<Block, ParachainClient<Block, RuntimeApi>>, @@ -373,7 +427,7 @@ where { fn start_consensus( client: Arc<ParachainClient<Block, RuntimeApi>>, - block_import: ParachainBlockImport<Block, RuntimeApi>, + block_import: ParachainBlockImport<Block, Arc<ParachainClient<Block, RuntimeApi>>>, prometheus_registry: Option<&Registry>, telemetry: Option<TelemetryHandle>, task_manager: &TaskManager, @@ -387,6 +441,7 @@ where announce_block: Arc<dyn Fn(Hash, Option<Vec<u8>>) + Send + Sync>, backend: Arc<ParachainBackend<Block>>, node_extra_args: NodeExtraArgs, + _: (), ) -> Result<(), Error> { let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( task_manager.spawn_handle(), diff --git a/cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs b/cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs index 7e36ce735af3fc5ea5279fe132d9168fbff3415b..96802177ec1049e5b508015cca634636b894355a 100644 --- a/cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs +++ b/cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs @@ -16,28 +16,41 @@ use crate::common::{ rpc::BuildRpcExtensions as BuildRpcExtensionsT, - spec::{BaseNodeSpec, BuildImportQueue, NodeSpec as NodeSpecT}, + spec::{BaseNodeSpec, BuildImportQueue, ClientBlockImport, NodeSpec as NodeSpecT}, types::{Hash, ParachainBlockImport, ParachainClient}, }; use codec::Encode; use cumulus_client_parachain_inherent::{MockValidationDataInherentDataProvider, MockXcmConfig}; -use cumulus_primitives_core::ParaId; +use cumulus_primitives_aura::AuraUnincludedSegmentApi; +use cumulus_primitives_core::{CollectCollationInfo, ParaId}; +use futures::FutureExt; +use polkadot_primitives::UpgradeGoAhead; +use sc_client_api::Backend; use sc_consensus::{DefaultImportQueue, LongestChain}; use sc_consensus_manual_seal::rpc::{ManualSeal, ManualSealApiServer}; use sc_network::NetworkBackend; use sc_service::{Configuration, PartialComponents, TaskManager}; use sc_telemetry::TelemetryHandle; +use sc_transaction_pool_api::OffchainTransactionPoolFactory; +use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_runtime::traits::Header; use std::{marker::PhantomData, sync::Arc}; pub struct ManualSealNode<NodeSpec>(PhantomData<NodeSpec>); -impl<NodeSpec: NodeSpecT> BuildImportQueue<NodeSpec::Block, NodeSpec::RuntimeApi> - for ManualSealNode<NodeSpec> +impl<NodeSpec: NodeSpecT> + BuildImportQueue< + NodeSpec::Block, + NodeSpec::RuntimeApi, + Arc<ParachainClient<NodeSpec::Block, NodeSpec::RuntimeApi>>, + > for ManualSealNode<NodeSpec> { fn build_import_queue( client: Arc<ParachainClient<NodeSpec::Block, NodeSpec::RuntimeApi>>, - _block_import: ParachainBlockImport<NodeSpec::Block, NodeSpec::RuntimeApi>, + _block_import: ParachainBlockImport< + NodeSpec::Block, + Arc<ParachainClient<NodeSpec::Block, NodeSpec::RuntimeApi>>, + >, config: &Configuration, _telemetry_handle: Option<TelemetryHandle>, task_manager: &TaskManager, @@ -54,6 +67,7 @@ impl<NodeSpec: NodeSpecT> BaseNodeSpec for ManualSealNode<NodeSpec> { type Block = NodeSpec::Block; type RuntimeApi = NodeSpec::RuntimeApi; type BuildImportQueue = Self; + type InitBlockImport = ClientBlockImport; } impl<NodeSpec: NodeSpecT> ManualSealNode<NodeSpec> { @@ -78,7 +92,7 @@ impl<NodeSpec: NodeSpecT> ManualSealNode<NodeSpec> { keystore_container, select_chain: _, transaction_pool, - other: (_, mut telemetry, _), + other: (_, mut telemetry, _, _), } = Self::new_partial(&config)?; let select_chain = LongestChain::new(backend.clone()); @@ -107,6 +121,27 @@ impl<NodeSpec: NodeSpecT> ManualSealNode<NodeSpec> { metrics, })?; + if config.offchain_worker.enabled { + let offchain_workers = + sc_offchain::OffchainWorkers::new(sc_offchain::OffchainWorkerOptions { + runtime_api_provider: client.clone(), + keystore: Some(keystore_container.keystore()), + offchain_db: backend.offchain_storage(), + transaction_pool: Some(OffchainTransactionPoolFactory::new( + transaction_pool.clone(), + )), + network_provider: Arc::new(network.clone()), + is_validator: config.role.is_authority(), + enable_http_requests: true, + custom_extensions: move |_| vec![], + })?; + task_manager.spawn_handle().spawn( + "offchain-workers-runner", + "offchain-work", + offchain_workers.run(client.clone(), task_manager.spawn_handle()).boxed(), + ); + } + let proposer = sc_basic_authorship::ProposerFactory::new( task_manager.spawn_handle(), client.clone(), @@ -147,6 +182,26 @@ impl<NodeSpec: NodeSpecT> ManualSealNode<NodeSpec> { .header(block) .expect("Header lookup should succeed") .expect("Header passed in as parent should be present in backend."); + + let should_send_go_ahead = client_for_cidp + .runtime_api() + .collect_collation_info(block, ¤t_para_head) + .map(|info| info.new_validation_code.is_some()) + .unwrap_or_default(); + + // The API version is relevant here because the constraints in the runtime changed + // in https://github.com/paritytech/polkadot-sdk/pull/6825. In general, the logic + // here assumes that we are using the aura-ext consensushook in the parachain + // runtime. + let requires_relay_progress = client_for_cidp + .runtime_api() + .has_api_with::<dyn AuraUnincludedSegmentApi<NodeSpec::Block>, _>( + block, + |version| version > 1, + ) + .ok() + .unwrap_or_default(); + let current_para_block_head = Some(polkadot_primitives::HeadData(current_para_head.encode())); let client_for_xcm = client_for_cidp.clone(); @@ -161,14 +216,22 @@ impl<NodeSpec: NodeSpecT> ManualSealNode<NodeSpec> { ), para_id, current_para_block_head, - relay_offset: 1000, - relay_blocks_per_para_block: 1, + relay_offset: 0, + relay_blocks_per_para_block: requires_relay_progress + .then(|| 1) + .unwrap_or_default(), para_blocks_per_relay_epoch: 10, relay_randomness_config: (), xcm_config: MockXcmConfig::new(&*client_for_xcm, block, Default::default()), raw_downward_messages: vec![], raw_horizontal_messages: vec![], additional_key_values: None, + upgrade_go_ahead: should_send_go_ahead.then(|| { + log::info!( + "Detected pending validation code, sending go-ahead signal." + ); + UpgradeGoAhead::GoAhead + }), }; Ok(( // This is intentional, as the runtime that we expect to run against this diff --git a/cumulus/polkadot-omni-node/lib/src/nodes/mod.rs b/cumulus/polkadot-omni-node/lib/src/nodes/mod.rs index ab13322e80ab9123732b427107c540107c38068d..5570170f90b2d55c0caa4627e44a4649c841e32c 100644 --- a/cumulus/polkadot-omni-node/lib/src/nodes/mod.rs +++ b/cumulus/polkadot-omni-node/lib/src/nodes/mod.rs @@ -22,6 +22,11 @@ use cumulus_primitives_core::ParaId; use manual_seal::ManualSealNode; use sc_service::{Configuration, TaskManager}; +/// The current node version for cumulus official binaries, which takes the basic +/// SemVer form `<major>.<minor>.<patch>`. It should correspond to the latest +/// `polkadot` version of a stable release. +pub const NODE_VERSION: &'static str = "1.17.1"; + /// Trait that extends the `DynNodeSpec` trait with manual seal related logic. /// /// We need it in order to be able to access both the `DynNodeSpec` and the manual seal logic diff --git a/cumulus/polkadot-omni-node/src/main.rs b/cumulus/polkadot-omni-node/src/main.rs index a6c1dd3cadbb0c90c1103d62db09d6b29e8bc556..1183f488df8b1525dbc33384abbb8c637d8c8263 100644 --- a/cumulus/polkadot-omni-node/src/main.rs +++ b/cumulus/polkadot-omni-node/src/main.rs @@ -23,14 +23,15 @@ use polkadot_omni_node_lib::{ chain_spec::DiskChainSpecLoader, run, runtime::DefaultRuntimeResolver, CliConfig as CliConfigT, - RunConfig, + RunConfig, NODE_VERSION, }; struct CliConfig; impl CliConfigT for CliConfig { fn impl_version() -> String { - env!("SUBSTRATE_CLI_IMPL_VERSION").into() + let commit_hash = env!("SUBSTRATE_CLI_COMMIT_HASH"); + format!("{}-{commit_hash}", NODE_VERSION) } fn author() -> String { diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index 5520126d0742a800e9f5f2343ad0467196e6dd9d..6b578779997c03eb3a8d308895bc477920ba2fa6 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -6,6 +6,8 @@ edition.workspace = true build = "build.rs" description = "Runs a polkadot parachain node" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -22,29 +24,28 @@ serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } # Local -polkadot-omni-node-lib = { features = ["rococo-native", "westend-native"], workspace = true } -rococo-parachain-runtime = { workspace = true } -glutton-westend-runtime = { workspace = true } asset-hub-rococo-runtime = { workspace = true, default-features = true } asset-hub-westend-runtime = { workspace = true } -collectives-westend-runtime = { workspace = true } -contracts-rococo-runtime = { workspace = true } bridge-hub-rococo-runtime = { workspace = true, default-features = true } +bridge-hub-westend-runtime = { workspace = true, default-features = true } +collectives-westend-runtime = { workspace = true } coretime-rococo-runtime = { workspace = true } coretime-westend-runtime = { workspace = true } -bridge-hub-westend-runtime = { workspace = true, default-features = true } +glutton-westend-runtime = { workspace = true } +parachains-common = { workspace = true, default-features = true } penpal-runtime = { workspace = true } people-rococo-runtime = { workspace = true } people-westend-runtime = { workspace = true } -parachains-common = { workspace = true, default-features = true } +polkadot-omni-node-lib = { features = ["rococo-native", "westend-native"], workspace = true } +rococo-parachain-runtime = { workspace = true } # Substrate -sp-core = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } +sc-chain-spec = { workspace = true, default-features = true } sc-cli = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } -sc-chain-spec = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-genesis-builder = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } # Polkadot xcm = { workspace = true, default-features = true } @@ -68,7 +69,6 @@ runtime-benchmarks = [ "bridge-hub-rococo-runtime/runtime-benchmarks", "bridge-hub-westend-runtime/runtime-benchmarks", "collectives-westend-runtime/runtime-benchmarks", - "contracts-rococo-runtime/runtime-benchmarks", "coretime-rococo-runtime/runtime-benchmarks", "coretime-westend-runtime/runtime-benchmarks", "glutton-westend-runtime/runtime-benchmarks", @@ -76,6 +76,7 @@ runtime-benchmarks = [ "people-rococo-runtime/runtime-benchmarks", "people-westend-runtime/runtime-benchmarks", "rococo-parachain-runtime/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "polkadot-omni-node-lib/try-runtime", @@ -85,7 +86,6 @@ try-runtime = [ "bridge-hub-rococo-runtime/try-runtime", "bridge-hub-westend-runtime/try-runtime", "collectives-westend-runtime/try-runtime", - "contracts-rococo-runtime/try-runtime", "coretime-rococo-runtime/try-runtime", "coretime-westend-runtime/try-runtime", "glutton-westend-runtime/try-runtime", diff --git a/cumulus/polkadot-parachain/src/main.rs b/cumulus/polkadot-parachain/src/main.rs index 61764636a0600f58578f87539ed857bb78d4e51a..a84fb0dfb18f8c9f86d3b8c481b682c82ddfec7b 100644 --- a/cumulus/polkadot-parachain/src/main.rs +++ b/cumulus/polkadot-parachain/src/main.rs @@ -21,13 +21,14 @@ mod chain_spec; -use polkadot_omni_node_lib::{run, CliConfig as CliConfigT, RunConfig}; +use polkadot_omni_node_lib::{run, CliConfig as CliConfigT, RunConfig, NODE_VERSION}; struct CliConfig; impl CliConfigT for CliConfig { fn impl_version() -> String { - env!("SUBSTRATE_CLI_IMPL_VERSION").into() + let commit_hash = env!("SUBSTRATE_CLI_COMMIT_HASH"); + format!("{}-{commit_hash}", NODE_VERSION) } fn author() -> String { diff --git a/cumulus/primitives/aura/Cargo.toml b/cumulus/primitives/aura/Cargo.toml index 185b2d40833f0614a0856b79015eb90f0c57c30a..715ce3e1a03ea5a6eb61f6dc91c50018c34ec268 100644 --- a/cumulus/primitives/aura/Cargo.toml +++ b/cumulus/primitives/aura/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" description = "Core primitives for Aura in Cumulus" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/primitives/aura/src/lib.rs b/cumulus/primitives/aura/src/lib.rs index aeeee5f8bafa16e02577f164dfe69e106c49c702..4e7d7dc3e79d629f593fa59900021dea6390b0e9 100644 --- a/cumulus/primitives/aura/src/lib.rs +++ b/cumulus/primitives/aura/src/lib.rs @@ -34,10 +34,14 @@ sp_api::decl_runtime_apis! { /// When the unincluded segment is short, Aura chains will allow authors to create multiple /// blocks per slot in order to build a backlog. When it is saturated, this API will limit /// the amount of blocks that can be created. + /// + /// Changes: + /// - Version 2: Update to `can_build_upon` to take a relay chain `Slot` instead of a parachain `Slot`. + #[api_version(2)] pub trait AuraUnincludedSegmentApi { /// Whether it is legal to extend the chain, assuming the given block is the most /// recently included one as-of the relay parent that will be built against, and - /// the given slot. + /// the given relay chain slot. /// /// This should be consistent with the logic the runtime uses when validating blocks to /// avoid issues. diff --git a/cumulus/primitives/core/Cargo.toml b/cumulus/primitives/core/Cargo.toml index 533d368d3b00ed883b1996d376a80f4d5d970570..307860897aec124d7585d5f49ad1241a12cac2a2 100644 --- a/cumulus/primitives/core/Cargo.toml +++ b/cumulus/primitives/core/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" description = "Cumulus related core primitive types and traits" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -41,4 +43,5 @@ runtime-benchmarks = [ "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/cumulus/primitives/parachain-inherent/Cargo.toml b/cumulus/primitives/parachain-inherent/Cargo.toml index a4271d3fd9cc34ac9a32787c3cce16d4cffc38ab..2ff990b8d514763f0ccc9600d24b150753dd8d89 100644 --- a/cumulus/primitives/parachain-inherent/Cargo.toml +++ b/cumulus/primitives/parachain-inherent/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Inherent that needs to be present in every parachain block. Contains messages and a relay chain storage-proof." license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/primitives/proof-size-hostfunction/Cargo.toml b/cumulus/primitives/proof-size-hostfunction/Cargo.toml index e61c865d05fb07aaff931f0ecd59a09c2592a0fa..b3b300d66ef32a28c25459e7ceec0393d06285b5 100644 --- a/cumulus/primitives/proof-size-hostfunction/Cargo.toml +++ b/cumulus/primitives/proof-size-hostfunction/Cargo.toml @@ -5,19 +5,21 @@ authors.workspace = true edition.workspace = true description = "Hostfunction exposing storage proof size to the runtime." license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] -sp-runtime-interface = { workspace = true } sp-externalities = { workspace = true } +sp-runtime-interface = { workspace = true } sp-trie = { workspace = true } [dev-dependencies] -sp-state-machine = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } +sp-state-machine = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/cumulus/primitives/storage-weight-reclaim/Cargo.toml b/cumulus/primitives/storage-weight-reclaim/Cargo.toml index e1ae6743335abac6be8de4ef41db7c7940764029..4bcbabc1f16c35f97f006162a1e3fc7ec6599644 100644 --- a/cumulus/primitives/storage-weight-reclaim/Cargo.toml +++ b/cumulus/primitives/storage-weight-reclaim/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Utilities to reclaim storage weight." license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -25,9 +27,9 @@ cumulus-primitives-proof-size-hostfunction = { workspace = true } docify = { workspace = true } [dev-dependencies] +cumulus-test-runtime = { workspace = true } sp-io = { workspace = true } sp-trie = { workspace = true } -cumulus-test-runtime = { workspace = true } [features] default = ["std"] diff --git a/cumulus/primitives/storage-weight-reclaim/src/lib.rs b/cumulus/primitives/storage-weight-reclaim/src/lib.rs index 5cbe662e2700924c55814680cda4d0428ad0c450..62ff60811904525c1e6af944776bfaac77b926fa 100644 --- a/cumulus/primitives/storage-weight-reclaim/src/lib.rs +++ b/cumulus/primitives/storage-weight-reclaim/src/lib.rs @@ -100,15 +100,30 @@ pub fn get_proof_size() -> Option<u64> { (proof_size != PROOF_RECORDING_DISABLED).then_some(proof_size) } -/// Storage weight reclaim mechanism. -/// -/// This extension checks the size of the node-side storage proof -/// before and after executing a given extrinsic. The difference between -/// benchmarked and spent weight can be reclaimed. -#[derive(Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)] -#[scale_info(skip_type_params(T))] -pub struct StorageWeightReclaim<T: Config + Send + Sync>(PhantomData<T>); +// Encapsulate into a mod so that macro generated code doesn't trigger a warning about deprecated +// usage. +#[allow(deprecated)] +mod allow_deprecated { + use super::*; + + /// Storage weight reclaim mechanism. + /// + /// This extension checks the size of the node-side storage proof + /// before and after executing a given extrinsic. The difference between + /// benchmarked and spent weight can be reclaimed. + #[deprecated(note = "This extension doesn't provide accurate reclaim for storage intensive \ + transaction extension pipeline; it ignores the validation and preparation of extensions prior \ + to itself and ignores the post dispatch logic for extensions subsequent to itself, it also + doesn't provide weight information. \ + Use `StorageWeightReclaim` in the `cumulus-pallet-weight-reclaim` crate")] + #[derive(Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)] + #[scale_info(skip_type_params(T))] + pub struct StorageWeightReclaim<T: Config + Send + Sync>(pub(super) PhantomData<T>); +} +#[allow(deprecated)] +pub use allow_deprecated::StorageWeightReclaim; +#[allow(deprecated)] impl<T: Config + Send + Sync> StorageWeightReclaim<T> { /// Create a new `StorageWeightReclaim` instance. pub fn new() -> Self { @@ -116,6 +131,7 @@ impl<T: Config + Send + Sync> StorageWeightReclaim<T> { } } +#[allow(deprecated)] impl<T: Config + Send + Sync> core::fmt::Debug for StorageWeightReclaim<T> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { let _ = write!(f, "StorageWeightReclaim"); @@ -123,6 +139,7 @@ impl<T: Config + Send + Sync> core::fmt::Debug for StorageWeightReclaim<T> { } } +#[allow(deprecated)] impl<T: Config + Send + Sync> TransactionExtension<T::RuntimeCall> for StorageWeightReclaim<T> where T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>, diff --git a/cumulus/primitives/storage-weight-reclaim/src/tests.rs b/cumulus/primitives/storage-weight-reclaim/src/tests.rs index ab83762cc0db1fb0947a6414089a0e3be463adcc..379b39afee0c1b33fdcf85aaea099de3190e8387 100644 --- a/cumulus/primitives/storage-weight-reclaim/src/tests.rs +++ b/cumulus/primitives/storage-weight-reclaim/src/tests.rs @@ -74,6 +74,7 @@ fn get_storage_weight() -> PerDispatchClass<Weight> { } #[test] +#[allow(deprecated)] fn basic_refund() { // The real cost will be 100 bytes of storage size let mut test_ext = setup_test_externalities(&[0, 100]); @@ -109,6 +110,7 @@ fn basic_refund() { } #[test] +#[allow(deprecated)] fn underestimating_refund() { // We fixed a bug where `pre dispatch info weight > consumed weight > post info weight` // resulted in error. @@ -149,6 +151,7 @@ fn underestimating_refund() { } #[test] +#[allow(deprecated)] 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. @@ -240,6 +243,7 @@ fn sets_to_node_storage_proof_if_higher() { } #[test] +#[allow(deprecated)] fn does_nothing_without_extension() { let mut test_ext = new_test_ext(); @@ -274,6 +278,7 @@ fn does_nothing_without_extension() { } #[test] +#[allow(deprecated)] fn negative_refund_is_added_to_weight() { let mut test_ext = setup_test_externalities(&[100, 300]); @@ -310,6 +315,7 @@ fn negative_refund_is_added_to_weight() { } #[test] +#[allow(deprecated)] fn test_zero_proof_size() { let mut test_ext = setup_test_externalities(&[0, 0]); @@ -340,6 +346,7 @@ fn test_zero_proof_size() { } #[test] +#[allow(deprecated)] fn test_larger_pre_dispatch_proof_size() { let mut test_ext = setup_test_externalities(&[300, 100]); @@ -374,6 +381,7 @@ fn test_larger_pre_dispatch_proof_size() { } #[test] +#[allow(deprecated)] fn test_incorporates_check_weight_unspent_weight() { let mut test_ext = setup_test_externalities(&[100, 300]); @@ -415,6 +423,7 @@ fn test_incorporates_check_weight_unspent_weight() { } #[test] +#[allow(deprecated)] fn test_incorporates_check_weight_unspent_weight_on_negative() { let mut test_ext = setup_test_externalities(&[100, 300]); @@ -456,6 +465,7 @@ fn test_incorporates_check_weight_unspent_weight_on_negative() { } #[test] +#[allow(deprecated)] fn test_nothing_relcaimed() { let mut test_ext = setup_test_externalities(&[0, 100]); @@ -505,6 +515,7 @@ fn test_nothing_relcaimed() { } #[test] +#[allow(deprecated)] fn test_incorporates_check_weight_unspent_weight_reverse_order() { let mut test_ext = setup_test_externalities(&[100, 300]); @@ -548,6 +559,7 @@ fn test_incorporates_check_weight_unspent_weight_reverse_order() { } #[test] +#[allow(deprecated)] fn test_incorporates_check_weight_unspent_weight_on_negative_reverse_order() { let mut test_ext = setup_test_externalities(&[100, 300]); @@ -616,6 +628,7 @@ fn storage_size_disabled_reported_correctly() { } #[test] +#[allow(deprecated)] fn test_reclaim_helper() { let mut test_ext = setup_test_externalities(&[1000, 1300, 1800]); @@ -635,6 +648,7 @@ fn test_reclaim_helper() { } #[test] +#[allow(deprecated)] fn test_reclaim_helper_does_not_reclaim_negative() { // Benchmarked weight does not change at all let mut test_ext = setup_test_externalities(&[1000, 1300]); @@ -669,6 +683,7 @@ fn get_benched_weight() -> Weight { /// Just here for doc purposes fn do_work() {} +#[allow(deprecated)] #[docify::export_content(simple_reclaimer_example)] fn reclaim_with_weight_meter() { let mut remaining_weight_meter = WeightMeter::with_limit(Weight::from_parts(10, 10)); diff --git a/cumulus/primitives/timestamp/Cargo.toml b/cumulus/primitives/timestamp/Cargo.toml index cb328e2f2cc6f1b79293179a238449754dfbab98..70cb3e607b983374520df4bba5b39ecf64dc3817 100644 --- a/cumulus/primitives/timestamp/Cargo.toml +++ b/cumulus/primitives/timestamp/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Provides timestamp related functionality for parachains." license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/primitives/utility/Cargo.toml b/cumulus/primitives/utility/Cargo.toml index 2ca8b82001d5ad0146d2b686a9a93f4ee5996385..84039b9345b271f144b18bff04132f75ef989c5b 100644 --- a/cumulus/primitives/utility/Cargo.toml +++ b/cumulus/primitives/utility/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" description = "Helper datatypes for Cumulus" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -15,14 +17,14 @@ log = { workspace = true } # Substrate frame-support = { workspace = true } -sp-runtime = { workspace = true } pallet-asset-conversion = { workspace = true } +sp-runtime = { workspace = true } # Polkadot polkadot-runtime-common = { workspace = true } xcm = { workspace = true } -xcm-executor = { workspace = true } xcm-builder = { workspace = true } +xcm-executor = { workspace = true } # Cumulus cumulus-primitives-core = { workspace = true } @@ -50,4 +52,5 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/cumulus/test/client/Cargo.toml b/cumulus/test/client/Cargo.toml index 33023816c718cd0a41335e8481fb6428ea6b6bdb..f64ee832ace3be5343a53e7b68d3634e79021bef 100644 --- a/cumulus/test/client/Cargo.toml +++ b/cumulus/test/client/Cargo.toml @@ -12,43 +12,44 @@ workspace = true codec = { features = ["derive"], workspace = true } # Substrate -sc-service = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } +pallet-transaction-payment = { workspace = true, default-features = true } +sc-block-builder = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-consensus-aura = { workspace = true, default-features = true } -sc-block-builder = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } sc-executor-common = { workspace = true, default-features = true } -substrate-test-client = { workspace = true } -sp-application-crypto = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } +sc-service = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sp-consensus-aura = { workspace = true, default-features = true } +sp-application-crypto = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } +sp-consensus-aura = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } sp-timestamp = { workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } -pallet-transaction-payment = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } +substrate-test-client = { workspace = true } # Polkadot -polkadot-primitives = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } # Cumulus -cumulus-test-runtime = { workspace = true } -cumulus-test-service = { workspace = true } -cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } +cumulus-pallet-weight-reclaim = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } -cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } cumulus-primitives-parachain-inherent = { workspace = true, default-features = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true, default-features = true } +cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } +cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } +cumulus-test-runtime = { workspace = true } +cumulus-test-service = { workspace = true } [features] runtime-benchmarks = [ + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-test-service/runtime-benchmarks", "frame-system/runtime-benchmarks", diff --git a/cumulus/test/client/src/lib.rs b/cumulus/test/client/src/lib.rs index 863a8fa93f6f5e3c263c939fa3acc8ca00bb672b..7861a42372a6519f57ccf84383b8383e4cb40b85 100644 --- a/cumulus/test/client/src/lib.rs +++ b/cumulus/test/client/src/lib.rs @@ -143,7 +143,6 @@ pub fn generate_extrinsic_with_pair( frame_system::CheckNonce::<Runtime>::from(nonce), frame_system::CheckWeight::<Runtime>::new(), pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::<Runtime>::new(), ) .into(); @@ -152,7 +151,7 @@ pub fn generate_extrinsic_with_pair( let raw_payload = SignedPayload::from_raw( function.clone(), tx_ext.clone(), - ((), VERSION.spec_version, genesis_block, current_block_hash, (), (), (), ()), + ((), VERSION.spec_version, genesis_block, current_block_hash, (), (), ()), ); let signature = raw_payload.using_encoded(|e| origin.sign(e)); @@ -167,7 +166,7 @@ pub fn generate_extrinsic_with_pair( /// Generate an extrinsic from the provided function call, origin and [`Client`]. pub fn generate_extrinsic( client: &Client, - origin: sp_keyring::AccountKeyring, + origin: sp_keyring::Sr25519Keyring, function: impl Into<RuntimeCall>, ) -> UncheckedExtrinsic { generate_extrinsic_with_pair(client, origin.into(), function, None) @@ -176,8 +175,8 @@ pub fn generate_extrinsic( /// Transfer some token from one account to another using a provided test [`Client`]. pub fn transfer( client: &Client, - origin: sp_keyring::AccountKeyring, - dest: sp_keyring::AccountKeyring, + origin: sp_keyring::Sr25519Keyring, + dest: sp_keyring::Sr25519Keyring, value: Balance, ) -> UncheckedExtrinsic { let function = RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { diff --git a/cumulus/test/relay-sproof-builder/Cargo.toml b/cumulus/test/relay-sproof-builder/Cargo.toml index e266b5807081aa1bc3dd8d5793d1f03342f094fc..c1efa141a45d207845975546993310bb2339875b 100644 --- a/cumulus/test/relay-sproof-builder/Cargo.toml +++ b/cumulus/test/relay-sproof-builder/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" description = "Mocked relay state proof builder for testing Cumulus." +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/test/runtime/Cargo.toml b/cumulus/test/runtime/Cargo.toml index 8117e6e6970961c6ce4ebc32a1f62462b1d6426d..711525a297c728e5f721282615b460aa70490d8e 100644 --- a/cumulus/test/runtime/Cargo.toml +++ b/cumulus/test/runtime/Cargo.toml @@ -18,37 +18,36 @@ frame-executive = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } frame-system-rpc-runtime-api = { workspace = true } +pallet-aura = { workspace = true } +pallet-authorship = { workspace = true } pallet-balances = { workspace = true } +pallet-glutton = { workspace = true } pallet-message-queue = { workspace = true } +pallet-session = { workspace = true } pallet-sudo = { workspace = true } -pallet-aura = { workspace = true } -pallet-authorship = { workspace = true } pallet-timestamp = { workspace = true } -pallet-glutton = { workspace = true } pallet-transaction-payment = { workspace = true } -pallet-session = { workspace = true } sp-api = { workspace = true } sp-block-builder = { workspace = true } +sp-consensus-aura = { workspace = true } sp-core = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } sp-io = { workspace = true } +sp-keyring = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } -sp-consensus-aura = { workspace = true } sp-transaction-pool = { workspace = true } sp-version = { workspace = true } -sp-keyring = { workspace = true } # Cumulus +cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } -parachain-info = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-primitives-aura = { workspace = true } -pallet-collator-selection = { workspace = true } -cumulus-pallet-aura-ext = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } +parachain-info = { workspace = true } [build-dependencies] substrate-wasm-builder = { optional = true, workspace = true, default-features = true } @@ -59,9 +58,9 @@ std = [ "codec/std", "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "frame-executive/std", "frame-support/std", "frame-system-rpc-runtime-api/std", @@ -69,7 +68,6 @@ std = [ "pallet-aura/std", "pallet-authorship/std", "pallet-balances/std", - "pallet-collator-selection/std", "pallet-glutton/std", "pallet-message-queue/std", "pallet-session/std", @@ -96,3 +94,5 @@ std = [ ] increment-spec-version = [] elastic-scaling = [] +elastic-scaling-500ms = [] +experimental-ump-signals = ["cumulus-pallet-parachain-system/experimental-ump-signals"] diff --git a/cumulus/test/runtime/build.rs b/cumulus/test/runtime/build.rs index 7a7fe8ffaa82e6fe2ab861dddaea12c44442fd06..99d30ce6dc37257ae1a8aadf3a4f96ad5be97864 100644 --- a/cumulus/test/runtime/build.rs +++ b/cumulus/test/runtime/build.rs @@ -29,8 +29,24 @@ fn main() { .with_current_project() .enable_feature("elastic-scaling") .import_memory() + .set_file_name("wasm_binary_elastic_scaling_mvp.rs") + .build(); + + WasmBuilder::new() + .with_current_project() + .enable_feature("elastic-scaling") + .enable_feature("experimental-ump-signals") + .import_memory() .set_file_name("wasm_binary_elastic_scaling.rs") .build(); + + WasmBuilder::new() + .with_current_project() + .enable_feature("elastic-scaling-500ms") + .enable_feature("experimental-ump-signals") + .import_memory() + .set_file_name("wasm_binary_elastic_scaling_500ms.rs") + .build(); } #[cfg(not(feature = "std"))] diff --git a/cumulus/test/runtime/src/lib.rs b/cumulus/test/runtime/src/lib.rs index b1649c410581a0f725e76312a535965db5e3cb6e..09e1361603a08377a8e3fdd2a71968dac8dedec2 100644 --- a/cumulus/test/runtime/src/lib.rs +++ b/cumulus/test/runtime/src/lib.rs @@ -27,6 +27,15 @@ pub mod wasm_spec_version_incremented { include!(concat!(env!("OUT_DIR"), "/wasm_binary_spec_version_incremented.rs")); } +pub mod elastic_scaling_500ms { + #[cfg(feature = "std")] + include!(concat!(env!("OUT_DIR"), "/wasm_binary_elastic_scaling_500ms.rs")); +} +pub mod elastic_scaling_mvp { + #[cfg(feature = "std")] + include!(concat!(env!("OUT_DIR"), "/wasm_binary_elastic_scaling_mvp.rs")); +} + pub mod elastic_scaling { #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary_elastic_scaling.rs")); @@ -93,21 +102,21 @@ impl_opaque_keys! { /// The para-id used in this runtime. pub const PARACHAIN_ID: u32 = 100; -#[cfg(not(feature = "elastic-scaling"))] -const UNINCLUDED_SEGMENT_CAPACITY: u32 = 4; -#[cfg(not(feature = "elastic-scaling"))] -const BLOCK_PROCESSING_VELOCITY: u32 = 1; - -#[cfg(feature = "elastic-scaling")] -const UNINCLUDED_SEGMENT_CAPACITY: u32 = 7; -#[cfg(feature = "elastic-scaling")] -const BLOCK_PROCESSING_VELOCITY: u32 = 4; - -#[cfg(not(feature = "elastic-scaling"))] +#[cfg(not(any(feature = "elastic-scaling", feature = "elastic-scaling-500ms")))] pub const MILLISECS_PER_BLOCK: u64 = 6000; -#[cfg(feature = "elastic-scaling")] + +#[cfg(all(feature = "elastic-scaling", not(feature = "elastic-scaling-500ms")))] pub const MILLISECS_PER_BLOCK: u64 = 2000; +#[cfg(feature = "elastic-scaling-500ms")] +pub const MILLISECS_PER_BLOCK: u64 = 500; + +const BLOCK_PROCESSING_VELOCITY: u32 = + RELAY_CHAIN_SLOT_DURATION_MILLIS / (MILLISECS_PER_BLOCK as u32); + +// The `+2` shouldn't be needed, https://github.com/paritytech/polkadot-sdk/issues/5260 +const UNINCLUDED_SEGMENT_CAPACITY: u32 = BLOCK_PROCESSING_VELOCITY * 2 + 2; + pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; @@ -227,6 +236,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = (); +} + parameter_types! { pub const MinimumPeriod: u64 = SLOT_DURATION / 2; pub const PotId: PalletId = PalletId(*b"PotStake"); @@ -342,6 +355,7 @@ construct_runtime! { Glutton: pallet_glutton, Aura: pallet_aura, AuraExt: cumulus_pallet_aura_ext, + WeightReclaim: cumulus_pallet_weight_reclaim, } } @@ -372,16 +386,18 @@ pub type SignedBlock = generic::SignedBlock<Block>; /// BlockId type as expected by this runtime. pub type BlockId = generic::BlockId<Block>; /// The extension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender<Runtime>, - frame_system::CheckSpecVersion<Runtime>, - frame_system::CheckGenesis<Runtime>, - frame_system::CheckEra<Runtime>, - frame_system::CheckNonce<Runtime>, - frame_system::CheckWeight<Runtime>, - pallet_transaction_payment::ChargeTransactionPayment<Runtime>, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim<Runtime>, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckEra<Runtime>, + frame_system::CheckNonce<Runtime>, + frame_system::CheckWeight<Runtime>, + pallet_transaction_payment::ChargeTransactionPayment<Runtime>, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, TxExtension>; diff --git a/cumulus/test/service/Cargo.toml b/cumulus/test/service/Cargo.toml index 86a8c48bb54fa0e54296139612d919573d67d5f8..407c657bd14ef6e839c6d5d9a624e3cc2293f49e 100644 --- a/cumulus/test/service/Cargo.toml +++ b/cumulus/test/service/Cargo.toml @@ -22,97 +22,93 @@ prometheus = { workspace = true } rand = { workspace = true, default-features = true } serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } +tempfile = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } tracing = { workspace = true, default-features = true } url = { workspace = true } -tempfile = { workspace = true } # Substrate frame-system = { workspace = true, default-features = true } frame-system-rpc-runtime-api = { workspace = true, default-features = true } pallet-transaction-payment = { workspace = true, default-features = true } sc-basic-authorship = { workspace = true, default-features = true } +sc-block-builder = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } +sc-cli = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-consensus-aura = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } +sc-executor-common = { workspace = true, default-features = true } +sc-executor-wasmtime = { 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-tracing = { workspace = true, default-features = true } sc-transaction-pool = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } -sc-telemetry = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } sp-arithmetic = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } +sp-consensus-aura = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-genesis-builder = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -sp-genesis-builder = { workspace = true, default-features = true } sp-runtime = { workspace = true } sp-state-machine = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } sp-timestamp = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } -sp-consensus-aura = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } substrate-test-client = { workspace = true } -sc-cli = { workspace = true, default-features = true } -sc-block-builder = { workspace = true, default-features = true } -sc-executor-wasmtime = { workspace = true, default-features = true } -sc-executor-common = { workspace = true, default-features = true } # Polkadot -polkadot-primitives = { workspace = true, default-features = true } -polkadot-service = { workspace = true, default-features = true } -polkadot-test-service = { workspace = true } polkadot-cli = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +polkadot-service = { workspace = true, default-features = true } +polkadot-test-service = { workspace = true } # Cumulus cumulus-client-cli = { workspace = true, default-features = true } -parachains-common = { 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-common = { workspace = true, default-features = true } cumulus-client-consensus-proposer = { 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-parachain-inherent = { workspace = true, default-features = true } +cumulus-client-pov-recovery = { workspace = true, default-features = true } cumulus-client-service = { workspace = true, default-features = true } -cumulus-client-collator = { workspace = true, default-features = true } +cumulus-pallet-parachain-system = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } cumulus-relay-chain-inprocess-interface = { workspace = true, default-features = true } cumulus-relay-chain-interface = { workspace = true, default-features = true } -cumulus-test-runtime = { workspace = true } cumulus-relay-chain-minimal-node = { workspace = true, default-features = true } -cumulus-client-pov-recovery = { workspace = true, default-features = true } cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } -cumulus-pallet-parachain-system = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true, default-features = true } +cumulus-test-runtime = { workspace = true } pallet-timestamp = { workspace = true, default-features = true } [dev-dependencies] +cumulus-test-client = { workspace = true } futures = { workspace = true } -portpicker = { workspace = true } sp-authority-discovery = { workspace = true, default-features = true } -cumulus-test-client = { workspace = true } # Polkadot dependencies polkadot-test-service = { workspace = true } # Substrate dependencies sc-cli = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } [features] runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-test-client/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-transaction-payment/runtime-benchmarks", - "parachains-common/runtime-benchmarks", "polkadot-cli/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", "polkadot-service/runtime-benchmarks", diff --git a/cumulus/test/service/src/chain_spec.rs b/cumulus/test/service/src/chain_spec.rs index 3d4e4dca5f8df864a05858ca894ac60ae62ad795..b59bd7ab46bdbd9fb1591cc6b71a5dae141f0e06 100644 --- a/cumulus/test/service/src/chain_spec.rs +++ b/cumulus/test/service/src/chain_spec.rs @@ -116,3 +116,23 @@ pub fn get_elastic_scaling_chain_spec(id: Option<ParaId>) -> ChainSpec { .expect("WASM binary was not built, please build it!"), ) } + +/// Get the chain spec for a specific parachain ID. +pub fn get_elastic_scaling_500ms_chain_spec(id: Option<ParaId>) -> ChainSpec { + get_chain_spec_with_extra_endowed( + id, + Default::default(), + cumulus_test_runtime::elastic_scaling_500ms::WASM_BINARY + .expect("WASM binary was not built, please build it!"), + ) +} + +/// Get the chain spec for a specific parachain ID. +pub fn get_elastic_scaling_mvp_chain_spec(id: Option<ParaId>) -> ChainSpec { + get_chain_spec_with_extra_endowed( + id, + Default::default(), + cumulus_test_runtime::elastic_scaling_mvp::WASM_BINARY + .expect("WASM binary was not built, please build it!"), + ) +} diff --git a/cumulus/test/service/src/cli.rs b/cumulus/test/service/src/cli.rs index 220b0449f3392727228f99b3d10f2ccbc2165693..7909ffbf7142f3ae34338340fdc6561fc709f22f 100644 --- a/cumulus/test/service/src/cli.rs +++ b/cumulus/test/service/src/cli.rs @@ -262,12 +262,24 @@ impl SubstrateCli for TestCollatorCli { tracing::info!("Using default test service chain spec."); Box::new(cumulus_test_service::get_chain_spec(Some(ParaId::from(2000)))) as Box<_> }, + "elastic-scaling-mvp" => { + tracing::info!("Using elastic-scaling mvp chain spec."); + Box::new(cumulus_test_service::get_elastic_scaling_mvp_chain_spec(Some( + ParaId::from(2100), + ))) as Box<_> + }, "elastic-scaling" => { tracing::info!("Using elastic-scaling chain spec."); Box::new(cumulus_test_service::get_elastic_scaling_chain_spec(Some(ParaId::from( - 2100, + 2200, )))) as Box<_> }, + "elastic-scaling-500ms" => { + tracing::info!("Using elastic-scaling 500ms chain spec."); + Box::new(cumulus_test_service::get_elastic_scaling_500ms_chain_spec(Some( + ParaId::from(2300), + ))) as Box<_> + }, path => { let chain_spec = cumulus_test_service::chain_spec::ChainSpec::from_json_file(path.into())?; diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index f01da9becef1c43fa30367f951d29ca114d058b1..f3f04cbb63835fb2dc2926b1e3b50a320e39794d 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -27,7 +27,10 @@ use cumulus_client_collator::service::CollatorService; use cumulus_client_consensus_aura::{ collators::{ lookahead::{self as aura, Params as AuraParams}, - slot_based::{self as slot_based, Params as SlotBasedParams}, + slot_based::{ + self as slot_based, Params as SlotBasedParams, SlotBasedBlockImport, + SlotBasedBlockImportHandle, + }, }, ImportQueueParams, }; @@ -131,7 +134,8 @@ pub type Client = TFullClient<runtime::NodeBlock, runtime::RuntimeApi, WasmExecu pub type Backend = TFullBackend<Block>; /// The block-import type being used by the test service. -pub type ParachainBlockImport = TParachainBlockImport<Block, Arc<Client>, Backend>; +pub type ParachainBlockImport = + TParachainBlockImport<Block, SlotBasedBlockImport<Block, Arc<Client>, Client>, Backend>; /// Transaction pool type used by the test service pub type TransactionPool = Arc<sc_transaction_pool::TransactionPoolHandle<Block, Client>>; @@ -184,7 +188,7 @@ pub type Service = PartialComponents< (), sc_consensus::import_queue::BasicQueue<Block>, sc_transaction_pool::TransactionPoolHandle<Block, Client>, - ParachainBlockImport, + (ParachainBlockImport, SlotBasedBlockImportHandle<Block>), >; /// Starts a `ServiceBuilder` for a full service. @@ -217,7 +221,9 @@ pub fn new_partial( )?; let client = Arc::new(client); - let block_import = ParachainBlockImport::new(client.clone(), backend.clone()); + let (block_import, slot_based_handle) = + SlotBasedBlockImport::new(client.clone(), client.clone()); + let block_import = ParachainBlockImport::new(block_import, backend.clone()); let transaction_pool = Arc::from( sc_transaction_pool::Builder::new( @@ -260,7 +266,7 @@ pub fn new_partial( task_manager, transaction_pool, select_chain: (), - other: block_import, + other: (block_import, slot_based_handle), }; Ok(params) @@ -349,7 +355,8 @@ where let client = params.client.clone(); let backend = params.backend.clone(); - let block_import = params.other; + let block_import = params.other.0; + let slot_based_handle = params.other.1; let relay_chain_interface = build_relay_chain_interface( relay_chain_config, parachain_config.prometheus_registry(), @@ -497,6 +504,7 @@ where authoring_duration: Duration::from_millis(2000), reinitialize: false, slot_drift: Duration::from_secs(1), + block_import_handle: slot_based_handle, spawner: task_manager.spawn_handle(), }; @@ -968,13 +976,12 @@ pub fn construct_extrinsic( frame_system::CheckNonce::<runtime::Runtime>::from(nonce), frame_system::CheckWeight::<runtime::Runtime>::new(), pallet_transaction_payment::ChargeTransactionPayment::<runtime::Runtime>::from(tip), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::<runtime::Runtime>::new(), ) .into(); let raw_payload = runtime::SignedPayload::from_raw( function.clone(), tx_ext.clone(), - ((), runtime::VERSION.spec_version, genesis_block, current_block_hash, (), (), (), ()), + ((), runtime::VERSION.spec_version, genesis_block, current_block_hash, (), (), ()), ); let signature = raw_payload.using_encoded(|e| caller.sign(e)); runtime::UncheckedExtrinsic::new_signed( diff --git a/cumulus/xcm/xcm-emulator/Cargo.toml b/cumulus/xcm/xcm-emulator/Cargo.toml index 8598481fae7670a2b57e4e52b418753501827717..b6fbbe3e4ce4f859a7fc5f17e4dac25a6f889e14 100644 --- a/cumulus/xcm/xcm-emulator/Cargo.toml +++ b/cumulus/xcm/xcm-emulator/Cargo.toml @@ -5,41 +5,42 @@ version = "0.5.0" authors.workspace = true edition.workspace = true license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +array-bytes = { workspace = true } codec = { workspace = true, default-features = true } -paste = { workspace = true, default-features = true } -log = { workspace = true } impl-trait-for-tuples = { workspace = true } -array-bytes = { workspace = true } +log = { workspace = true } +paste = { workspace = true, default-features = true } # Substrate frame-support = { workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } +pallet-message-queue = { workspace = true, default-features = true } +sp-arithmetic = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-arithmetic = { workspace = true, default-features = true } +sp-std = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } -pallet-message-queue = { workspace = true, default-features = true } # Cumulus -cumulus-primitives-core = { workspace = true, default-features = true } -cumulus-pallet-xcmp-queue = { workspace = true, default-features = true } cumulus-pallet-parachain-system = { workspace = true, default-features = true } +cumulus-primitives-core = { workspace = true, default-features = true } cumulus-primitives-parachain-inherent = { workspace = true, default-features = true } cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } parachains-common = { workspace = true, default-features = true } # Polkadot -xcm = { workspace = true, default-features = true } -xcm-executor = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } polkadot-runtime-parachains = { workspace = true, default-features = true } +xcm = { workspace = true, default-features = true } +xcm-executor = { workspace = true, default-features = true } diff --git a/cumulus/xcm/xcm-emulator/src/lib.rs b/cumulus/xcm/xcm-emulator/src/lib.rs index ff14b747973cf3e1b0658dc147a1749e2ca20ed1..d9b1e7fd9d04cbcf28933e10b91961bf4bdd8432 100644 --- a/cumulus/xcm/xcm-emulator/src/lib.rs +++ b/cumulus/xcm/xcm-emulator/src/lib.rs @@ -1118,6 +1118,7 @@ macro_rules! decl_test_networks { ) -> $crate::ParachainInherentData { let mut sproof = $crate::RelayStateSproofBuilder::default(); sproof.para_id = para_id.into(); + sproof.current_slot = $crate::polkadot_primitives::Slot::from(relay_parent_number as u64); // egress channel let e_index = sproof.hrmp_egress_channel_index.get_or_insert_with(Vec::new); diff --git a/cumulus/zombienet/tests/0008-elastic_authoring.toml b/cumulus/zombienet/tests/0008-elastic_authoring.toml index f2e2010a9e4582feefaebdaa355ab96b6a8f7695..516c152471b113e6c527045c9aaff1b58c8c1fed 100644 --- a/cumulus/zombienet/tests/0008-elastic_authoring.toml +++ b/cumulus/zombienet/tests/0008-elastic_authoring.toml @@ -1,10 +1,6 @@ [settings] timeout = 1000 -[relaychain.genesis.runtimeGenesis.patch.configuration.config.async_backing_params] - max_candidate_depth = 6 - allowed_ancestry_len = 3 - [relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] max_validators_per_core = 1 num_cores = 4 diff --git a/cumulus/zombienet/tests/0009-elastic_pov_recovery.toml b/cumulus/zombienet/tests/0009-elastic_pov_recovery.toml index 1cf0775a2e177be85445de1f5a85c745670a76ee..b65ed77ec1ba5bd986f9505c0915b15fbd98eee7 100644 --- a/cumulus/zombienet/tests/0009-elastic_pov_recovery.toml +++ b/cumulus/zombienet/tests/0009-elastic_pov_recovery.toml @@ -9,10 +9,6 @@ requests = { memory = "2G", cpu = "1" } limits = { memory = "4G", cpu = "2" } requests = { memory = "2G", cpu = "1" } -[relaychain.genesis.runtimeGenesis.patch.configuration.config.async_backing_params] - max_candidate_depth = 6 - allowed_ancestry_len = 3 - [relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] max_validators_per_core = 1 num_cores = 4 diff --git a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile index b1f4bffc772abab2d18abca1a5f8974f6cdb2e5d..f9879fea2082245107512aed77c6670f2bed911e 100644 --- a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile +++ b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile @@ -48,8 +48,9 @@ RUN set -eux; \ cd /home/nonroot/bridges-polkadot-sdk/bridges/testing/framework/utils/generate_hex_encoded_call; \ npm install +# use the non-root user +USER node # check if executable works in this container -USER nonroot RUN /usr/local/bin/polkadot --version RUN /usr/local/bin/polkadot-parachain --version RUN /usr/local/bin/substrate-relay --version diff --git a/docs/RELEASE.md b/docs/RELEASE.md index bea36741135969fb216fbe559709d8b43a1c4b35..677cb5465b67f179f7a90f861f1f5e9dddded7b5 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -14,7 +14,11 @@ Merging to it is restricted to [Backports](#backports). We are releasing multiple different things from this repository in one release, but we don't want to use the same version for everything. Thus, in the following we explain the versioning story for the crates, node and Westend & -Rococo. To easily refer to a release, it shall be named by its date in the form `stableYYMMDD`. +Rococo. + +To easily refer to a release, it shall be named by its date in the form `stableYYMM`. Patches to stable releases are +tagged in the form of `stableYYMM-PATCH`, with `PATCH` ranging from 1 to 99. For example, the fourth patch to +`stable2409` would be `stable2409-4`. ## Crate diff --git a/docs/contributor/container.md b/docs/contributor/container.md index ec51b8b9d7ccd1b4933c3977ce92c7f0f96966a6..e387f568d7b51d07aa8e954cac70d69dc0ea0054 100644 --- a/docs/contributor/container.md +++ b/docs/contributor/container.md @@ -24,7 +24,7 @@ The command below allows building a Linux binary without having to even install docker run --rm -it \ -w /polkadot-sdk \ -v $(pwd):/polkadot-sdk \ - docker.io/paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v20240408 \ + docker.io/paritytech/ci-unified:bullseye-1.81.0-2024-11-19-v202411281558 \ cargo build --release --locked -p polkadot-parachain-bin --bin polkadot-parachain sudo chown -R $(id -u):$(id -g) target/ ``` diff --git a/docs/contributor/prdoc.md b/docs/contributor/prdoc.md index 4a1a3c1f06880086d334b1bb5b1688e7d76aa455..b3f7a7e94f0ce840aa0401d5828579f4cd130531 100644 --- a/docs/contributor/prdoc.md +++ b/docs/contributor/prdoc.md @@ -1,73 +1,85 @@ # PRDoc -A [prdoc](https://github.com/paritytech/prdoc) is like a changelog but for a Pull Request. We use this approach to -record changes on a crate level. This information is then processed by the release team to apply the correct crate -version bumps and to generate the CHANGELOG of the next release. +A [prdoc](https://github.com/paritytech/prdoc) is like a changelog but for a Pull Request. We use +this approach to record changes on a crate level. This information is then processed by the release +team to apply the correct crate version bumps and to generate the CHANGELOG of the next release. ## Requirements -When creating a PR, the author needs to decide with the `R0-silent` label whether the PR has to contain a prdoc. The -`R0` label should only be placed for No-OP changes like correcting a typo in a comment or CI stuff. If unsure, ping -the [CODEOWNERS](../../.github/CODEOWNERS) for advice. +When creating a PR, the author needs to decide with the `R0-silent` label whether the PR has to +contain a prdoc. The `R0` label should only be placed for No-OP changes like correcting a typo in a +comment or CI stuff. If unsure, ping the [CODEOWNERS](../../.github/CODEOWNERS) for advice. -## PRDoc How-To +## Auto Generation -A `.prdoc` file is a YAML file with a defined structure (ie JSON Schema). Please follow these steps to generate one: - -1. Install the [`prdoc` CLI](https://github.com/paritytech/prdoc) by running `cargo install parity-prdoc`. -1. Open a Pull Request and get the PR number. -1. Generate the file with `prdoc generate <PR_NUMBER>`. The output filename will be printed. -1. Optional: Install the `prdoc/schema_user.json` schema in your editor, for example - [VsCode](https://github.com/paritytech/prdoc?tab=readme-ov-file#schemas). -1. Edit your `.prdoc` file according to the [Audience](#pick-an-audience) and [SemVer](#record-semver-changes) sections. -1. Check your prdoc with `prdoc check -n <PR_NUMBER>`. This is optional since the CI will also check it. - -> **Tip:** GitHub CLI and jq can be used to provide the number of your PR to generate the correct file: -> `prdoc generate $(gh pr view --json number | jq '.number') -o prdoc` - -Alternatively you can call the prdoc from PR via `/cmd prdoc` (see args with `/cmd prdoc --help`) -in a comment to PR to trigger it from CI. +You can create a PrDoc by using the `/cmd prdoc` command (see args with `/cmd prdoc --help`) in a +comment on your PR. Options: -- `pr`: The PR number to generate the PrDoc for. -- `audience`: The audience of whom the changes may concern. -- `bump`: A default bump level for all crates. - The PrDoc will likely need to be edited to reflect the actual changes after generation. -- `force`: Whether to overwrite any existing PrDoc. - -## Pick An Audience +- `audience` The audience of whom the changes may concern. + - `runtime_dev`: Anyone building a runtime themselves. For example parachain teams, or people + providing template runtimes. Also devs using pallets, FRAME etc directly. These are people who + care about the protocol (WASM), not the meta-protocol (client). + - `runtime_user`: Anyone using the runtime. Can be front-end devs reading the state, exchanges + listening for events, libraries that have hard-coded pallet indices etc. Anything that would + result in an observable change to the runtime behaviour must be marked with this. + - `node_dev`: Those who build around the client side code. Alternative client builders, SMOLDOT, + those who consume RPCs. These are people who are oblivious to the runtime changes. They only care + about the meta-protocol, not the protocol itself. + - `node_operator`: People who run the node. Think of validators, exchanges, indexer services, CI + actions. Anything that modifies how the binary behaves (its arguments, default arguments, error + messags, etc) must be marked with this. +- `bump:`: The default bump level for all crates. The PrDoc will likely need to be edited to reflect + the actual changes after generation. More details in the section below. + - `none`: There is no observable change. So to say: if someone were handed the old and the new + version of our software, it would be impossible to figure out what version is which. + - `patch`: Fixes that will never cause compilation errors if someone updates to this version. No + functionality has been changed. Should be limited to fixing bugs or No-OP implementation + changes. + - `minor`: Additions that will never cause compilation errors if someone updates to this version. + No functionality has been changed. Should be limited to adding new features. + - `major`: Anything goes. +- `force: true|false`: Whether to overwrite any existing PrDoc file. -While describing a PR, the author needs to consider which audience(s) need to be addressed. -The list of valid audiences is described and documented in the JSON schema as follow: +### Example -- `Node Dev`: Those who build around the client side code. Alternative client builders, SMOLDOT, those who consume RPCs. - These are people who are oblivious to the runtime changes. They only care about the meta-protocol, not the protocol - itself. +```bash +/cmd prdoc --audience runtime_dev --bump patch +``` -- `Runtime Dev`: All of those who rely on the runtime. A parachain team that is using a pallet. A DApp that is using a - pallet. These are people who care about the protocol (WASM), not the meta-protocol (client). +## Local Generation -- `Node Operator`: Those who don't write any code and only run code. +A `.prdoc` file is a YAML file with a defined structure (ie JSON Schema). Please follow these steps +to generate one: -- `Runtime User`: Anyone using the runtime. This can be a token holder or a dev writing a front end for a chain. +1. Install the [`prdoc` CLI](https://github.com/paritytech/prdoc) by running `cargo install + parity-prdoc`. +1. Open a Pull Request and get the PR number. +1. Generate the file with `prdoc generate <PR_NUMBER>`. The output filename will be printed. +1. Optional: Install the `prdoc/schema_user.json` schema in your editor, for example + [VsCode](https://github.com/paritytech/prdoc?tab=readme-ov-file#schemas). +1. Edit your `.prdoc` file according to the [Audience](#pick-an-audience) and + [SemVer](#record-semver-changes) sections. +1. Check your prdoc with `prdoc check -n <PR_NUMBER>`. This is optional since the CI will also check + it. -If you have a change that affects multiple audiences, you can either list them all, or write multiple sections and -re-phrase the changes for each audience. +> **Tip:** GitHub CLI and jq can be used to provide the number of your PR to generate the correct +> file: +> `prdoc generate $(gh pr view --json number | jq '.number') -o prdoc` ## Record SemVer Changes -All published crates that got modified need to have an entry in the `crates` section of your `PRDoc`. This entry tells -the release team how to bump the crate version prior to the next release. It is very important that this information is -correct, otherwise it could break the code of downstream teams. +All published crates that got modified need to have an entry in the `crates` section of your +`PRDoc`. This entry tells the release team how to bump the crate version prior to the next release. +It is very important that this information is correct, otherwise it could break the code of +downstream teams. The bump can either be `major`, `minor`, `patch` or `none`. The three first options are defined by -[rust-lang.org](https://doc.rust-lang.org/cargo/reference/semver.html), whereas `None` should be picked if no other -applies. The `None` option is equivalent to the `R0-silent` label, but on a crate level. Experimental and private APIs -are exempt from bumping and can be broken at any time. Please read the [Crate Section](../RELEASE.md) of the RELEASE doc -about them. - -> **Note**: There is currently no CI in place to sanity check this information, but should be added soon. +[rust-lang.org](https://doc.rust-lang.org/cargo/reference/semver.html), whereas `None` should be +picked if no other applies. The `None` option is equivalent to the `R0-silent` label, but on a crate +level. Experimental and private APIs are exempt from bumping and can be broken at any time. Please +read the [Crate Section](../RELEASE.md) of the RELEASE doc about them. ### Example @@ -81,12 +93,31 @@ crates: bump: minor ``` -It means that downstream code using `frame-example-pallet` is still guaranteed to work as before, while code using -`frame-example` might break. +It means that downstream code using `frame-example-pallet` is still guaranteed to work as before, +while code using `frame-example` might break. ### Dependencies -A crate that depends on another crate will automatically inherit its `major` bumps. This means that you do not need to -bump a crate that had a SemVer breaking change only from re-exporting another crate with a breaking change. -`minor` an `patch` bumps do not need to be inherited, since `cargo` will automatically update them to the latest -compatible version. +A crate that depends on another crate will automatically inherit its `major` bumps. This means that +you do not need to bump a crate that had a SemVer breaking change only from re-exporting another +crate with a breaking change. +`minor` an `patch` bumps do not need to be inherited, since `cargo` will automatically update them +to the latest compatible version. + +### Overwrite CI check + +The `check-semver` CI check is doing sanity checks based on the provided `PRDoc` and the mentioned +crate version bumps. The tooling is not perfect and it may recommends incorrect bumps of the version. +The CI check can be forced to accept the provided version bump. This can be done like: + +```yaml +crates: + - name: frame-example + bump: major + validate: false + - name: frame-example-pallet + bump: minor +``` + +By putting `validate: false` for `frame-example`, the version bump is ignored by the tooling. For +`frame-example-pallet` the version bump is still validated by the CI check. diff --git a/docs/sdk/Cargo.toml b/docs/sdk/Cargo.toml index 0c39367eeed35ec02e6e930d63b427a8a26b0190..4d83e2045ab01cfdb55f74cb61d841168d4143e2 100644 --- a/docs/sdk/Cargo.toml +++ b/docs/sdk/Cargo.toml @@ -16,112 +16,113 @@ workspace = true [dependencies] # Needed for all FRAME-based code codec = { workspace = true } -scale-info = { workspace = true } frame = { features = [ "experimental", "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 } +pallet-examples = { workspace = true } +scale-info = { workspace = true } # How we build docs in rust-docs -simple-mermaid = "0.1.1" docify = { workspace = true } serde_json = { workspace = true } +simple-mermaid = "0.1.1" # Polkadot SDK deps, typically all should only be in scope such that we can link to their doc item. -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 } -subkey = { workspace = true, default-features = true } -frame-system = { workspace = true } -frame-support = { workspace = true } -frame-executive = { workspace = true } frame-benchmarking = { workspace = true } -pallet-example-authorization-tx-extension = { workspace = true, default-features = true } -pallet-example-single-block-migrations = { workspace = true, default-features = true } +frame-executive = { workspace = true } frame-metadata-hash-extension = { workspace = true, default-features = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +kitchensink-runtime = { workspace = true } log = { workspace = true, default-features = true } +node-cli = { workspace = true } +pallet-example-authorization-tx-extension = { workspace = true, default-features = true } +pallet-example-single-block-migrations = { workspace = true, default-features = true } +polkadot-sdk = { features = ["runtime-full"], workspace = true, default-features = true } +subkey = { workspace = true, default-features = true } # Substrate Client -sc-network = { workspace = true, default-features = true } -sc-rpc-api = { workspace = true, default-features = true } -sc-rpc = { workspace = true, default-features = true } -sc-client-db = { workspace = true, default-features = true } +sc-chain-spec = { workspace = true, default-features = true } sc-cli = { workspace = true, default-features = true } +sc-client-db = { workspace = true, default-features = true } sc-consensus-aura = { workspace = true, default-features = true } sc-consensus-babe = { workspace = true, default-features = true } -sc-consensus-grandpa = { workspace = true, default-features = true } sc-consensus-beefy = { workspace = true, default-features = true } +sc-consensus-grandpa = { workspace = true, default-features = true } sc-consensus-manual-seal = { workspace = true, default-features = true } sc-consensus-pow = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } +sc-rpc = { workspace = true, default-features = true } +sc-rpc-api = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } -sc-chain-spec = { workspace = true, default-features = true } substrate-wasm-builder = { workspace = true, default-features = true } # Cumulus +cumulus-client-service = { workspace = true, default-features = true } cumulus-pallet-aura-ext = { workspace = true, default-features = true } cumulus-pallet-parachain-system = { workspace = true, default-features = true } -parachain-info = { workspace = true, default-features = true } +cumulus-pallet-weight-reclaim = { workspace = true, default-features = true } cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } -cumulus-client-service = { workspace = true, default-features = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true, default-features = true } +parachain-info = { workspace = true, default-features = true } # Omni Node polkadot-omni-node-lib = { workspace = true, default-features = true } # Pallets and FRAME internals -pallet-aura = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } -pallet-assets = { workspace = true, default-features = true } -pallet-preimage = { workspace = true, default-features = true } -pallet-transaction-payment = { workspace = true, default-features = true } -pallet-asset-tx-payment = { workspace = true, default-features = true } -pallet-skip-feeless-payment = { workspace = true, default-features = true } pallet-asset-conversion-tx-payment = { workspace = true, default-features = true } -pallet-utility = { workspace = true, default-features = true } -pallet-multisig = { workspace = true, default-features = true } -pallet-proxy = { workspace = true, default-features = true } +pallet-asset-tx-payment = { workspace = true, default-features = true } +pallet-assets = { workspace = true, default-features = true } +pallet-aura = { workspace = true, default-features = true } pallet-authorship = { workspace = true, default-features = true } +pallet-babe = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } +pallet-broker = { workspace = true, default-features = true } pallet-collective = { workspace = true, default-features = true } pallet-democracy = { workspace = true, default-features = true } -pallet-uniques = { workspace = true, default-features = true } +pallet-grandpa = { workspace = true, default-features = true } +pallet-multisig = { workspace = true, default-features = true } pallet-nfts = { workspace = true, default-features = true } -pallet-scheduler = { workspace = true, default-features = true } +pallet-preimage = { workspace = true, default-features = true } +pallet-proxy = { workspace = true, default-features = true } pallet-referenda = { workspace = true, default-features = true } -pallet-broker = { workspace = true, default-features = true } -pallet-babe = { workspace = true, default-features = true } -pallet-grandpa = { workspace = true, default-features = true } +pallet-scheduler = { workspace = true, default-features = true } +pallet-skip-feeless-payment = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } +pallet-transaction-payment = { workspace = true, default-features = true } +pallet-uniques = { workspace = true, default-features = true } +pallet-utility = { workspace = true, default-features = true } # Primitives -sp-io = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } -sp-runtime-interface = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-arithmetic = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-genesis-builder = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } sp-offchain = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-runtime-interface = { workspace = true, default-features = true } +sp-std = { workspace = true, default-features = true } +sp-storage = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } sp-weights = { workspace = true, default-features = true } # XCM +pallet-xcm = { workspace = true } xcm = { workspace = true, default-features = true } xcm-builder = { workspace = true } xcm-docs = { workspace = true } xcm-executor = { workspace = true } xcm-simulator = { workspace = true } -pallet-xcm = { workspace = true } # runtime guides @@ -129,13 +130,14 @@ chain-spec-guide-runtime = { workspace = true, default-features = true } # Templates minimal-template-runtime = { workspace = true, default-features = true } -solochain-template-runtime = { workspace = true, default-features = true } parachain-template-runtime = { workspace = true, default-features = true } +solochain-template-runtime = { workspace = true, default-features = true } # local packages -first-runtime = { workspace = true, default-features = true } first-pallet = { workspace = true, default-features = true } +first-runtime = { workspace = true, default-features = true } [dev-dependencies] assert_cmd = "2.0.14" +cmd_lib = { workspace = true } rand = "0.8" diff --git a/docs/sdk/packages/guides/first-pallet/Cargo.toml b/docs/sdk/packages/guides/first-pallet/Cargo.toml index dad5b88634945ec02ec5da793ba93ae4e831543a..e6325c31781a6571055983607879f0c920a13f0f 100644 --- a/docs/sdk/packages/guides/first-pallet/Cargo.toml +++ b/docs/sdk/packages/guides/first-pallet/Cargo.toml @@ -17,9 +17,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } docify = { workspace = true } +frame = { workspace = true, features = ["runtime"] } +scale-info = { workspace = true } [features] default = ["std"] diff --git a/docs/sdk/packages/guides/first-runtime/Cargo.toml b/docs/sdk/packages/guides/first-runtime/Cargo.toml index 303d5c5e7f5fc8a11c52d48d0105f4872e77bceb..8ed17dea1b71ec90295a2a75d6fa064e289ed6ca 100644 --- a/docs/sdk/packages/guides/first-runtime/Cargo.toml +++ b/docs/sdk/packages/guides/first-runtime/Cargo.toml @@ -18,7 +18,7 @@ scale-info = { workspace = true } serde_json = { workspace = true } # this is a frame-based runtime, thus importing `frame` with runtime feature enabled. -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } # pallets that we want to use pallet-balances = { workspace = true } diff --git a/docs/sdk/packages/guides/first-runtime/src/lib.rs b/docs/sdk/packages/guides/first-runtime/src/lib.rs index 61ca550c8750743d1291843710627b5d106b6277..2ab060c8c43fed6359a38848af167e0286c3442f 100644 --- a/docs/sdk/packages/guides/first-runtime/src/lib.rs +++ b/docs/sdk/packages/guides/first-runtime/src/lib.rs @@ -139,11 +139,11 @@ pub mod genesis_config_presets { let endowment = <MinimumBalance as Get<Balance>>::get().max(1) * 1000; build_struct_json_patch!(RuntimeGenesisConfig { balances: BalancesConfig { - balances: AccountKeyring::iter() + balances: Sr25519Keyring::iter() .map(|a| (a.to_account_id(), endowment)) .collect::<Vec<_>>(), }, - sudo: SudoConfig { key: Some(AccountKeyring::Alice.to_account_id()) }, + sudo: SudoConfig { key: Some(Sr25519Keyring::Alice.to_account_id()) }, }) } diff --git a/docs/sdk/src/guides/enable_pov_reclaim.rs b/docs/sdk/src/guides/enable_pov_reclaim.rs index cb6960b3df4ef1b5a507eb083baba808c9e99a0f..71abeacd18c8e024369159e3a10ee3f846df842b 100644 --- a/docs/sdk/src/guides/enable_pov_reclaim.rs +++ b/docs/sdk/src/guides/enable_pov_reclaim.rs @@ -62,8 +62,10 @@ //! //! In your runtime, you will find a list of TransactionExtensions. //! To enable the reclaiming, -//! add [`StorageWeightReclaim`](cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim) -//! to that list. For maximum efficiency, make sure that `StorageWeightReclaim` is last in the list. +//! set [`StorageWeightReclaim`](cumulus_pallet_weight_reclaim::StorageWeightReclaim) +//! as a warpper of that list. +//! It is necessary that this extension wraps all the other transaction extensions in order to catch +//! the whole PoV size of the transactions. //! The extension will check the size of the storage proof before and after an extrinsic execution. //! It reclaims the difference between the calculated size and the benchmarked size. #![doc = docify::embed!("../../templates/parachain/runtime/src/lib.rs", template_signed_extra)] diff --git a/docs/sdk/src/guides/your_first_node.rs b/docs/sdk/src/guides/your_first_node.rs index da37c11c206f3fc2417277b712738efab7700b4a..3c782e4793bab422a0ed06e8705ad58457910414 100644 --- a/docs/sdk/src/guides/your_first_node.rs +++ b/docs/sdk/src/guides/your_first_node.rs @@ -103,6 +103,7 @@ #[cfg(test)] mod tests { use assert_cmd::Command; + use cmd_lib::*; use rand::Rng; use sc_chain_spec::{DEV_RUNTIME_PRESET, LOCAL_TESTNET_RUNTIME_PRESET}; use sp_genesis_builder::PresetId; @@ -173,13 +174,10 @@ mod tests { println!("Building polkadot-sdk-docs-first-runtime..."); #[docify::export_content] fn build_runtime() { - Command::new("cargo") - .arg("build") - .arg("--release") - .arg("-p") - .arg(FIRST_RUNTIME) - .assert() - .success(); + run_cmd!( + cargo build --release -p $FIRST_RUNTIME + ) + .expect("Failed to run command"); } build_runtime() } @@ -274,14 +272,10 @@ mod tests { let chain_spec_builder = find_release_binary(&CHAIN_SPEC_BUILDER).unwrap(); let runtime_path = find_wasm(PARA_RUNTIME).unwrap(); let output = "/tmp/demo-chain-spec.json"; - Command::new(chain_spec_builder) - .args(["-c", output]) - .arg("create") - .args(["--para-id", "1000", "--relay-chain", "dontcare"]) - .args(["-r", runtime_path.to_str().unwrap()]) - .args(["named-preset", "development"]) - .assert() - .success(); + let runtime_str = runtime_path.to_str().unwrap(); + run_cmd!( + $chain_spec_builder -c $output create --para-id 1000 --relay-chain dontcare -r $runtime_str named-preset development + ).expect("Failed to run command"); std::fs::remove_file(output).unwrap(); } build_para_chain_spec_works(); diff --git a/docs/sdk/src/polkadot_sdk/frame_runtime.rs b/docs/sdk/src/polkadot_sdk/frame_runtime.rs index 8acf19f7641379c3fdfda62ce34c5799179d0b4a..24595e445fdd6e48377d62a69aff6ab5bab8daad 100644 --- a/docs/sdk/src/polkadot_sdk/frame_runtime.rs +++ b/docs/sdk/src/polkadot_sdk/frame_runtime.rs @@ -57,6 +57,7 @@ //! The following example showcases a minimal pallet. #![doc = docify::embed!("src/polkadot_sdk/frame_runtime.rs", pallet)] //! +//! ## Runtime //! //! A runtime is a collection of pallets that are amalgamated together. Each pallet typically has //! some configurations (exposed as a `trait Config`) that needs to be *specified* in the runtime. diff --git a/docs/sdk/src/reference_docs/chain_spec_genesis.rs b/docs/sdk/src/reference_docs/chain_spec_genesis.rs index b7a0a648d0cfd84db0123b298ea8c581738f295b..d5cc482711ad797614731869fdca64be7c6ba1ba 100644 --- a/docs/sdk/src/reference_docs/chain_spec_genesis.rs +++ b/docs/sdk/src/reference_docs/chain_spec_genesis.rs @@ -174,13 +174,13 @@ //! ``` //! Here are some examples in the form of rust tests: //! ## Listing available preset names: -#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", list_presets)] +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", cmd_list_presets)] //! ## Displaying preset with given name -#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", get_preset)] +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", cmd_get_preset)] //! ## Building a solo chain-spec (the default) using given preset -#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", generate_chain_spec)] +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", cmd_generate_chain_spec)] //! ## Building a parachain chain-spec using given preset -#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", generate_para_chain_spec)] +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", cmd_generate_para_chain_spec)] //! //! [`RuntimeGenesisConfig`]: //! chain_spec_guide_runtime::runtime::RuntimeGenesisConfig diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/Cargo.toml b/docs/sdk/src/reference_docs/chain_spec_runtime/Cargo.toml index 07c0342f5fbeaea4076aa4fd040668b912a297ed..925cb7bb2e65498a14d1ca0c7428ceb06dc5ccc7 100644 --- a/docs/sdk/src/reference_docs/chain_spec_runtime/Cargo.toml +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/Cargo.toml @@ -10,8 +10,8 @@ edition.workspace = true publish = false [dependencies] -docify = { workspace = true } codec = { workspace = true } +docify = { workspace = true } frame-support = { workspace = true } scale-info = { workspace = true } serde = { workspace = true } @@ -31,17 +31,18 @@ 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 } +sp-application-crypto = { features = ["serde"], workspace = true } sp-core = { workspace = true } +sp-genesis-builder = { workspace = true } sp-keyring = { workspace = true } -sp-application-crypto = { features = ["serde"], workspace = true } +sp-runtime = { features = ["serde"], workspace = true } [build-dependencies] substrate-wasm-builder = { optional = true, workspace = true, default-features = true } [dev-dependencies] chain-spec-builder = { workspace = true, default-features = true } +cmd_lib = { workspace = true } sc-chain-spec = { workspace = true, default-features = true } [features] diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/src/presets.rs b/docs/sdk/src/reference_docs/chain_spec_runtime/src/presets.rs index 5918f2b8ccd5961566ab96360f98c8e0a11c462a..5432d37e907d7f96e50127b6b4ee0f605e90dc1e 100644 --- a/docs/sdk/src/reference_docs/chain_spec_runtime/src/presets.rs +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/src/presets.rs @@ -25,7 +25,7 @@ use alloc::vec; use frame_support::build_struct_json_patch; use serde_json::{json, to_string, Value}; use sp_application_crypto::Ss58Codec; -use sp_keyring::AccountKeyring; +use sp_keyring::Sr25519Keyring; /// A demo preset with strings only. pub const PRESET_1: &str = "preset_1"; @@ -70,7 +70,7 @@ fn preset_2() -> Value { some_integer: 200, some_enum: FooEnum::Data2(SomeFooData2 { values: vec![0x0c, 0x10] }) }, - bar: BarConfig { initial_account: Some(AccountKeyring::Ferdie.public().into()) }, + bar: BarConfig { initial_account: Some(Sr25519Keyring::Ferdie.public().into()) }, }) } @@ -80,7 +80,7 @@ fn preset_2() -> Value { fn preset_3() -> Value { json!({ "bar": { - "initialAccount": AccountKeyring::Alice.public().to_ss58check(), + "initialAccount": Sr25519Keyring::Alice.public().to_ss58check(), }, "foo": { "someEnum": FooEnum::Data1( 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 282fc1ff489c0195973d712c085688bdaf77a30a..3ffa3d61263f2cc6c9204ec208b3c68cbfdc236a 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 @@ -57,7 +57,14 @@ type SignedExtra = (); mod runtime { /// The main runtime type. #[runtime::runtime] - #[runtime::derive(RuntimeCall, RuntimeEvent, RuntimeError, RuntimeOrigin, RuntimeTask)] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeTask, + RuntimeViewFunction + )] pub struct Runtime; /// Mandatory system pallet that should always be included in a FRAME runtime. diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs b/docs/sdk/src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs index c2fe5a6727e6b09b7ea82d8dd9903f3f615e43a9..b773af24de80617169a93e2b506c9ecfa6877986 100644 --- a/docs/sdk/src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs @@ -1,194 +1,192 @@ +use cmd_lib::*; use serde_json::{json, Value}; -use std::{process::Command, str}; +use std::str; -const WASM_FILE_PATH: &str = - "../../../../../target/release/wbuild/chain-spec-guide-runtime/chain_spec_guide_runtime.wasm"; +fn wasm_file_path() -> &'static str { + chain_spec_guide_runtime::runtime::WASM_BINARY_PATH + .expect("chain_spec_guide_runtime wasm should exist. qed") +} const CHAIN_SPEC_BUILDER_PATH: &str = "../../../../../target/release/chain-spec-builder"; +macro_rules! bash( + ( chain-spec-builder $($a:tt)* ) => {{ + let path = get_chain_spec_builder_path(); + spawn_with_output!( + $path $($a)* + ) + .expect("a process running. qed") + .wait_with_output() + .expect("to get output. qed.") + + }} +); + fn get_chain_spec_builder_path() -> &'static str { - // dev-dependencies do not build binary. So let's do the naive work-around here: - let _ = std::process::Command::new("cargo") - .arg("build") - .arg("--release") - .arg("-p") - .arg("staging-chain-spec-builder") - .arg("--bin") - .arg("chain-spec-builder") - .status() - .expect("Failed to execute command"); + run_cmd!( + cargo build --release -p staging-chain-spec-builder --bin chain-spec-builder + ) + .expect("Failed to execute command"); CHAIN_SPEC_BUILDER_PATH } +#[docify::export_content] +fn cmd_list_presets(runtime_path: &str) -> String { + bash!( + chain-spec-builder list-presets -r $runtime_path + ) +} + #[test] -#[docify::export] fn list_presets() { - let output = Command::new(get_chain_spec_builder_path()) - .arg("list-presets") - .arg("-r") - .arg(WASM_FILE_PATH) - .output() - .expect("Failed to execute command"); - - let output: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); + let output: serde_json::Value = + serde_json::from_slice(cmd_list_presets(wasm_file_path()).as_bytes()).unwrap(); + assert_eq!( + output, + json!({ + "presets":[ + "preset_1", + "preset_2", + "preset_3", + "preset_4", + "preset_invalid" + ] + }), + "Output did not match expected" + ); +} - let expected_output = json!({ - "presets":[ - "preset_1", - "preset_2", - "preset_3", - "preset_4", - "preset_invalid" - ] - }); - assert_eq!(output, expected_output, "Output did not match expected"); +#[docify::export_content] +fn cmd_get_preset(runtime_path: &str) -> String { + bash!( + chain-spec-builder display-preset -r $runtime_path -p preset_2 + ) } #[test] -#[docify::export] fn get_preset() { - let output = Command::new(get_chain_spec_builder_path()) - .arg("display-preset") - .arg("-r") - .arg(WASM_FILE_PATH) - .arg("-p") - .arg("preset_2") - .output() - .expect("Failed to execute command"); - - let output: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); - - //note: copy of chain_spec_guide_runtime::preset_2 - let expected_output = json!({ - "bar": { - "initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL", - }, - "foo": { - "someEnum": { - "Data2": { - "values": "0x0c10" - } + let output: serde_json::Value = + serde_json::from_slice(cmd_get_preset(wasm_file_path()).as_bytes()).unwrap(); + assert_eq!( + output, + json!({ + "bar": { + "initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL", + }, + "foo": { + "someEnum": { + "Data2": { + "values": "0x0c10" + } + }, + "someInteger": 200 }, - "someInteger": 200 - }, - }); - assert_eq!(output, expected_output, "Output did not match expected"); + }), + "Output did not match expected" + ); +} + +#[docify::export_content] +fn cmd_generate_chain_spec(runtime_path: &str) -> String { + bash!( + chain-spec-builder -c /dev/stdout create -r $runtime_path named-preset preset_2 + ) } #[test] -#[docify::export] fn generate_chain_spec() { - let output = Command::new(get_chain_spec_builder_path()) - .arg("-c") - .arg("/dev/stdout") - .arg("create") - .arg("-r") - .arg(WASM_FILE_PATH) - .arg("named-preset") - .arg("preset_2") - .output() - .expect("Failed to execute command"); - - let mut output: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); - - //remove code field for better readability + let mut output: serde_json::Value = + serde_json::from_slice(cmd_generate_chain_spec(wasm_file_path()).as_bytes()).unwrap(); if let Some(code) = output["genesis"]["runtimeGenesis"].as_object_mut().unwrap().get_mut("code") { *code = Value::String("0x123".to_string()); } - - let expected_output = json!({ - "name": "Custom", - "id": "custom", - "chainType": "Live", - "bootNodes": [], - "telemetryEndpoints": null, - "protocolId": null, - "properties": { "tokenDecimals": 12, "tokenSymbol": "UNIT" }, - "codeSubstitutes": {}, - "genesis": { - "runtimeGenesis": { - "code": "0x123", - "patch": { - "bar": { - "initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL" - }, - "foo": { - "someEnum": { - "Data2": { - "values": "0x0c10" + assert_eq!( + output, + json!({ + "name": "Custom", + "id": "custom", + "chainType": "Live", + "bootNodes": [], + "telemetryEndpoints": null, + "protocolId": null, + "properties": { "tokenDecimals": 12, "tokenSymbol": "UNIT" }, + "codeSubstitutes": {}, + "genesis": { + "runtimeGenesis": { + "code": "0x123", + "patch": { + "bar": { + "initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL" + }, + "foo": { + "someEnum": { + "Data2": { + "values": "0x0c10" + } + }, + "someInteger": 200 } - }, - "someInteger": 200 + } } } - } - } - }); - assert_eq!(output, expected_output, "Output did not match expected"); + }), + "Output did not match expected" + ); +} + +#[docify::export_content] +fn cmd_generate_para_chain_spec(runtime_path: &str) -> String { + bash!( + chain-spec-builder -c /dev/stdout create -c polkadot -p 1000 -r $runtime_path named-preset preset_2 + ) } #[test] -#[docify::export] fn generate_para_chain_spec() { - let output = Command::new(get_chain_spec_builder_path()) - .arg("-c") - .arg("/dev/stdout") - .arg("create") - .arg("-c") - .arg("polkadot") - .arg("-p") - .arg("1000") - .arg("-r") - .arg(WASM_FILE_PATH) - .arg("named-preset") - .arg("preset_2") - .output() - .expect("Failed to execute command"); - - let mut output: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); - - //remove code field for better readability + let mut output: serde_json::Value = + serde_json::from_slice(cmd_generate_para_chain_spec(wasm_file_path()).as_bytes()).unwrap(); if let Some(code) = output["genesis"]["runtimeGenesis"].as_object_mut().unwrap().get_mut("code") { *code = Value::String("0x123".to_string()); } - - let expected_output = json!({ - "name": "Custom", - "id": "custom", - "chainType": "Live", - "bootNodes": [], - "telemetryEndpoints": null, - "protocolId": null, - "relay_chain": "polkadot", - "para_id": 1000, - "properties": { "tokenDecimals": 12, "tokenSymbol": "UNIT" }, - "codeSubstitutes": {}, - "genesis": { - "runtimeGenesis": { - "code": "0x123", - "patch": { - "bar": { - "initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL" - }, - "foo": { - "someEnum": { - "Data2": { - "values": "0x0c10" - } + assert_eq!( + output, + json!({ + "name": "Custom", + "id": "custom", + "chainType": "Live", + "bootNodes": [], + "telemetryEndpoints": null, + "protocolId": null, + "relay_chain": "polkadot", + "para_id": 1000, + "properties": { "tokenDecimals": 12, "tokenSymbol": "UNIT" }, + "codeSubstitutes": {}, + "genesis": { + "runtimeGenesis": { + "code": "0x123", + "patch": { + "bar": { + "initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL" }, - "someInteger": 200 + "foo": { + "someEnum": { + "Data2": { + "values": "0x0c10" + } + }, + "someInteger": 200 + } } } - } - } - }); - assert_eq!(output, expected_output, "Output did not match expected"); + }}), + "Output did not match expected" + ); } #[test] -#[docify::export] +#[docify::export_content] fn preset_4_json() { assert_eq!( chain_spec_guide_runtime::presets::preset_4(), diff --git a/docs/sdk/src/reference_docs/frame_benchmarking_weight.rs b/docs/sdk/src/reference_docs/frame_benchmarking_weight.rs index 68d7d31f67f3e855ec66a7d9d27b281bfe8a46c8..98192bfd2a905ff8de709f19b798bba89ad3aae8 100644 --- a/docs/sdk/src/reference_docs/frame_benchmarking_weight.rs +++ b/docs/sdk/src/reference_docs/frame_benchmarking_weight.rs @@ -96,7 +96,7 @@ //! Two ways exist to run the benchmarks of a runtime. //! //! 1. The old school way: Most Polkadot-SDK based nodes (such as the ones integrated in -//! [`templates`]) have an a `benchmark` subcommand integrated into themselves. +//! [`templates`]) have a `benchmark` subcommand integrated into themselves. //! 2. The more [`crate::reference_docs::omni_node`] compatible way of running the benchmarks would //! be using [`frame-omni-bencher`] CLI, which only relies on a runtime. //! diff --git a/docs/sdk/src/reference_docs/mod.rs b/docs/sdk/src/reference_docs/mod.rs index e47eece784c4ce6b34ff9507873e0df2495dd912..7ad8a37241bf5ce58047572701a82d169116e8d9 100644 --- a/docs/sdk/src/reference_docs/mod.rs +++ b/docs/sdk/src/reference_docs/mod.rs @@ -111,3 +111,6 @@ pub mod custom_runtime_api_rpc; /// The [`polkadot-omni-node`](https://crates.io/crates/polkadot-omni-node) and its related binaries. pub mod omni_node; + +/// Learn about the state in Substrate. +pub mod state; diff --git a/docs/sdk/src/reference_docs/omni_node.rs b/docs/sdk/src/reference_docs/omni_node.rs index 44d63704a45856370021780c5376d8763af090dd..150755fb29a2977c07b6bcc863d6c92c4a94337e 100644 --- a/docs/sdk/src/reference_docs/omni_node.rs +++ b/docs/sdk/src/reference_docs/omni_node.rs @@ -177,9 +177,25 @@ //! [This](https://github.com/paritytech/polkadot-sdk/issues/5565) future improvement to OmniNode //! aims to make such checks automatic. //! +//! ### Runtime conventions +//! +//! The Omni Node needs to make some assumptions about the runtime. During startup, the node fetches +//! the runtime metadata and asserts that the runtime represents a compatible parachain. +//! The checks are best effort and will generate warning level logs in the Omni Node log file on +//! failure. +//! +//! The list of checks may evolve in the future and for now only few rules are implemented: +//! * runtimes must define a type for [`cumulus-pallet-parachain-system`], which is recommended to +//! be named as `ParachainSystem`. +//! * runtimes must define a type for [`frame-system`] pallet, which is recommended to be named as +//! `System`. The configured [`block number`] here will be used by Omni Node to configure AURA +//! accordingly. //! //! [`templates`]: crate::polkadot_sdk::templates //! [`parachain-template`]: https://github.com/paritytech/polkadot-sdk-parachain-template //! [`--dev-block-time`]: polkadot_omni_node_lib::cli::Cli::dev_block_time //! [`polkadot-omni-node`]: https://crates.io/crates/polkadot-omni-node //! [`chain-spec-builder`]: https://crates.io/crates/staging-chain-spec-builder +//! [`cumulus-pallet-parachain-system`]: https://docs.rs/cumulus-pallet-parachain-system/latest/cumulus_pallet_parachain_system/ +//! [`frame-system`]: https://docs.rs/frame-system/latest/frame_system/ +//! [`block number`]: https://docs.rs/frame-system/latest/frame_system/pallet/storage_types/struct.Number.html diff --git a/docs/sdk/src/reference_docs/state.rs b/docs/sdk/src/reference_docs/state.rs new file mode 100644 index 0000000000000000000000000000000000000000..a8138caebf1eb2913b36bb9549bd4f1998908575 --- /dev/null +++ b/docs/sdk/src/reference_docs/state.rs @@ -0,0 +1,12 @@ +//! # State +//! +//! The state is abstracted as a key-value like database. Every item that +//! needs to be persisted by the [State Transition +//! Function](crate::reference_docs::blockchain_state_machines) is written to the state. +//! +//! ## Special keys +//! +//! The key-value pairs in the state are represented as byte sequences. The node +//! doesn't know how to interpret most the key-value pairs. However, there exist some +//! special keys and its values that are known to the node, the so-called +//! [`well-known-keys`](sp_storage::well_known_keys). diff --git a/docs/sdk/src/reference_docs/transaction_extensions.rs b/docs/sdk/src/reference_docs/transaction_extensions.rs index 0f8198e8372d35975f69d811d545bf20b900407f..fe213458b25c0400a8aef014398fa6d04b7a2f2b 100644 --- a/docs/sdk/src/reference_docs/transaction_extensions.rs +++ b/docs/sdk/src/reference_docs/transaction_extensions.rs @@ -47,9 +47,11 @@ //! to include the so-called metadata hash. This is required by chains to support the generic //! Ledger application and other similar offline wallets. //! -//! - [`StorageWeightReclaim`](cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim): A -//! transaction extension for parachains that reclaims unused storage weight after executing a -//! transaction. +//! - [`WeightReclaim`](frame_system::WeightReclaim): A transaction extension for the relay chain +//! that reclaims unused weight after executing a transaction. +//! +//! - [`StorageWeightReclaim`](cumulus_pallet_weight_reclaim::StorageWeightReclaim): A transaction +//! extension for parachains that reclaims unused storage weight after executing a transaction. //! //! For more information about these extensions, follow the link to the type documentation. //! diff --git a/polkadot/Cargo.toml b/polkadot/Cargo.toml index 3a939464868fed72d4bf89f3501dae84769d97b0..ded8157ad90ecbcc6fead4cb9675144752f0c15b 100644 --- a/polkadot/Cargo.toml +++ b/polkadot/Cargo.toml @@ -20,6 +20,8 @@ authors.workspace = true edition.workspace = true version = "6.0.0" default-run = "polkadot" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -44,10 +46,10 @@ tikv-jemallocator = { version = "0.5.0", features = ["unprefixed_malloc_on_suppo [dev-dependencies] assert_cmd = { workspace = true } nix = { features = ["signal"], workspace = true } +polkadot-core-primitives = { workspace = true, default-features = true } +substrate-rpc-client = { workspace = true, default-features = true } tempfile = { workspace = true } tokio = { workspace = true, default-features = true } -substrate-rpc-client = { workspace = true, default-features = true } -polkadot-core-primitives = { workspace = true, default-features = true } [build-dependencies] substrate-build-script-utils = { workspace = true, default-features = true } diff --git a/polkadot/cli/Cargo.toml b/polkadot/cli/Cargo.toml index da37f6062c5725e9162718c7d49ee50d94617140..6909d142b3a632b7ff90ce2deeb2e271ed065a20 100644 --- a/polkadot/cli/Cargo.toml +++ b/polkadot/cli/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -20,27 +22,27 @@ crate-type = ["cdylib", "rlib"] [dependencies] cfg-if = { workspace = true } clap = { features = ["derive"], optional = true, workspace = true } -log = { workspace = true, default-features = true } -thiserror = { workspace = true } futures = { workspace = true } +log = { workspace = true, default-features = true } pyroscope = { optional = true, workspace = true } pyroscope_pprofrs = { optional = true, workspace = true } +thiserror = { workspace = true } polkadot-service = { optional = true, workspace = true } -sp-core = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -sp-maybe-compressed-blob = { workspace = true, default-features = true } frame-benchmarking-cli = { optional = true, workspace = true, default-features = true } -sc-cli = { optional = true, workspace = true, default-features = true } -sc-service = { optional = true, workspace = true, default-features = true } polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } -sc-tracing = { optional = true, workspace = true, default-features = true } -sc-sysinfo = { workspace = true, default-features = true } +sc-cli = { optional = true, workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } +sc-service = { optional = true, workspace = true, default-features = true } sc-storage-monitor = { workspace = true, default-features = true } +sc-sysinfo = { workspace = true, default-features = true } +sc-tracing = { optional = true, workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-maybe-compressed-blob = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } [build-dependencies] diff --git a/polkadot/core-primitives/Cargo.toml b/polkadot/core-primitives/Cargo.toml index 42ca27953738e5c06dc5cb904323ee6a5bbc91f2..1fb14e9d58e7ef768bdd4cf83ca8165ec1967c83 100644 --- a/polkadot/core-primitives/Cargo.toml +++ b/polkadot/core-primitives/Cargo.toml @@ -5,15 +5,17 @@ description = "Core Polkadot types used by Relay Chains and parachains." authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +codec = { features = ["derive"], workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -codec = { features = ["derive"], workspace = true } [features] default = ["std"] diff --git a/polkadot/erasure-coding/Cargo.toml b/polkadot/erasure-coding/Cargo.toml index 969742c5bb0aa792ea81b287f588b1315a48971d..ba712a89613b8b952ccc041c91276699fdccec8d 100644 --- a/polkadot/erasure-coding/Cargo.toml +++ b/polkadot/erasure-coding/Cargo.toml @@ -5,22 +5,24 @@ description = "Erasure coding used for Polkadot's availability system" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] -polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } -novelpoly = { workspace = true } codec = { features = ["derive", "std"], workspace = true } +novelpoly = { workspace = true } +polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-trie = { workspace = true, default-features = true } thiserror = { workspace = true } [dev-dependencies] -quickcheck = { workspace = true } criterion = { features = ["cargo_bench_support"], workspace = true } +quickcheck = { workspace = true } [[bench]] name = "scaling_with_validators" diff --git a/polkadot/erasure-coding/fuzzer/Cargo.toml b/polkadot/erasure-coding/fuzzer/Cargo.toml index 6f451f0319b23dee9ebbf08726dd4550f518e95d..5f1c2bda40580b4fa3ec0d9cecdb8b635aba84f5 100644 --- a/polkadot/erasure-coding/fuzzer/Cargo.toml +++ b/polkadot/erasure-coding/fuzzer/Cargo.toml @@ -10,10 +10,10 @@ publish = false workspace = true [dependencies] -polkadot-erasure-coding = { workspace = true, default-features = true } honggfuzz = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } +polkadot-erasure-coding = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } [[bin]] name = "reconstruct" diff --git a/polkadot/grafana/README.md b/polkadot/grafana/README.md index e909fdd29a757afba3ba6c76ade6466acffc7bda..0ecb0b70515b82722b9a361baee6d89ab4802612 100644 --- a/polkadot/grafana/README.md +++ b/polkadot/grafana/README.md @@ -90,4 +90,4 @@ and issue statement or initiate dispute. - **Assignment delay tranches**. Approval voting is designed such that validators assigned to check a specific candidate are split up into equal delay tranches (0.5 seconds each). All validators checks are ordered by the delay tranche index. Early tranches of validators have the opportunity to check the candidate first before later tranches -that act as as backups in case of no shows. +that act as backups in case of no shows. diff --git a/polkadot/grafana/parachains/status.json b/polkadot/grafana/parachains/status.json index 5942cbdf44793d611ca050eb36500086f4f0d455..22250967848dd6d67449f012a0691e355cf49cb6 100644 --- a/polkadot/grafana/parachains/status.json +++ b/polkadot/grafana/parachains/status.json @@ -1405,7 +1405,7 @@ "type": "prometheus", "uid": "$data_source" }, - "description": "Approval voting requires that validators which are assigned to check a specific \ncandidate are split up into delay tranches (0.5s each). Then, all validators checks are ordered by the delay \ntranche index. Early tranches of validators will check the candidate first and later tranches act as as backups in case of no shows.", + "description": "Approval voting requires that validators which are assigned to check a specific \ncandidate are split up into delay tranches (0.5s each). Then, all validators checks are ordered by the delay \ntranche index. Early tranches of validators will check the candidate first and later tranches act as backups in case of no shows.", "gridPos": { "h": 9, "w": 18, diff --git a/polkadot/node/collation-generation/Cargo.toml b/polkadot/node/collation-generation/Cargo.toml index 777458673f5b39dba447fb14467bf22b7a6968f3..eb9568cc22bc17720943ef1fe0aae5a2c050ba91 100644 --- a/polkadot/node/collation-generation/Cargo.toml +++ b/polkadot/node/collation-generation/Cargo.toml @@ -5,11 +5,14 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Collator-side subsystem that handles incoming candidate submissions from the parachain." +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +codec = { features = ["bit-vec", "derive"], workspace = true } futures = { workspace = true } gum = { workspace = true, default-features = true } polkadot-erasure-coding = { workspace = true, default-features = true } @@ -17,16 +20,15 @@ polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } +schnellru = { workspace = true } sp-core = { workspace = true, default-features = true } sp-maybe-compressed-blob = { workspace = true, default-features = true } thiserror = { workspace = true } -codec = { features = ["bit-vec", "derive"], workspace = true } -schnellru = { workspace = true } [dev-dependencies] +assert_matches = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } +polkadot-primitives = { workspace = true, features = ["test"] } polkadot-primitives-test-helpers = { workspace = true } -assert_matches = { workspace = true } rstest = { workspace = true } sp-keyring = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, features = ["test"] } diff --git a/polkadot/node/collation-generation/src/lib.rs b/polkadot/node/collation-generation/src/lib.rs index b371017a8289a9614661b5b3b0d700019762ab3d..3c8a216f5f35ff25656add6d2b6cd20577e2bf85 100644 --- a/polkadot/node/collation-generation/src/lib.rs +++ b/polkadot/node/collation-generation/src/lib.rs @@ -53,7 +53,7 @@ use polkadot_primitives::{ node_features::FeatureIndex, vstaging::{ transpose_claim_queue, CandidateDescriptorV2, CandidateReceiptV2 as CandidateReceipt, - CommittedCandidateReceiptV2, TransposedClaimQueue, + ClaimQueueOffset, CommittedCandidateReceiptV2, TransposedClaimQueue, }, CandidateCommitments, CandidateDescriptor, CollatorPair, CoreIndex, Hash, Id as ParaId, NodeFeatures, OccupiedCoreAssumption, PersistedValidationData, SessionIndex, @@ -61,7 +61,7 @@ use polkadot_primitives::{ }; use schnellru::{ByLength, LruMap}; use sp_core::crypto::Pair; -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; mod error; @@ -276,13 +276,15 @@ impl CollationGenerationSubsystem { let claim_queue = ClaimQueueSnapshot::from(request_claim_queue(relay_parent, ctx.sender()).await.await??); - let cores_to_build_on = claim_queue - .iter_claims_at_depth(0) - .filter_map(|(core_idx, para_id)| (para_id == config.para_id).then_some(core_idx)) + let assigned_cores = claim_queue + .iter_all_claims() + .filter_map(|(core_idx, para_ids)| { + para_ids.iter().any(|¶_id| para_id == config.para_id).then_some(*core_idx) + }) .collect::<Vec<_>>(); - // Nothing to do if no core assigned to us. - if cores_to_build_on.is_empty() { + // Nothing to do if no core is assigned to us at any depth. + if assigned_cores.is_empty() { return Ok(()) } @@ -342,9 +344,13 @@ impl CollationGenerationSubsystem { ctx.spawn( "chained-collation-builder", Box::pin(async move { - let transposed_claim_queue = transpose_claim_queue(claim_queue.0); + let transposed_claim_queue = transpose_claim_queue(claim_queue.0.clone()); - for core_index in cores_to_build_on { + // Track used core indexes not to submit collations on the same core. + let mut used_cores = HashSet::new(); + + for i in 0..assigned_cores.len() { + // Get the collation. let collator_fn = match task_config.collator.as_ref() { Some(x) => x, None => return, @@ -363,6 +369,68 @@ impl CollationGenerationSubsystem { }, }; + // Use the core_selector method from CandidateCommitments to extract + // CoreSelector and ClaimQueueOffset. + let mut commitments = CandidateCommitments::default(); + commitments.upward_messages = collation.upward_messages.clone(); + + let (cs_index, cq_offset) = match commitments.core_selector() { + // Use the CoreSelector's index if provided. + Ok(Some((sel, off))) => (sel.0 as usize, off), + // Fallback to the sequential index if no CoreSelector is provided. + Ok(None) => (i, ClaimQueueOffset(0)), + Err(err) => { + gum::debug!( + target: LOG_TARGET, + ?para_id, + "error processing UMP signals: {}", + err + ); + return + }, + }; + + // Identify the cores to build collations on using the given claim queue offset. + let cores_to_build_on = claim_queue + .iter_claims_at_depth(cq_offset.0 as usize) + .filter_map(|(core_idx, para_id)| { + (para_id == task_config.para_id).then_some(core_idx) + }) + .collect::<Vec<_>>(); + + if cores_to_build_on.is_empty() { + gum::debug!( + target: LOG_TARGET, + ?para_id, + "no core is assigned to para at depth {}", + cq_offset.0, + ); + return + } + + let descriptor_core_index = + cores_to_build_on[cs_index % cores_to_build_on.len()]; + + // Ensure the core index has not been used before. + if used_cores.contains(&descriptor_core_index.0) { + gum::warn!( + target: LOG_TARGET, + ?para_id, + "parachain repeatedly selected the same core index: {}", + descriptor_core_index.0, + ); + return + } + + used_cores.insert(descriptor_core_index.0); + gum::trace!( + target: LOG_TARGET, + ?para_id, + "selected core index: {}", + descriptor_core_index.0, + ); + + // Distribute the collation. let parent_head = collation.head_data.clone(); if let Err(err) = construct_and_distribute_receipt( PreparedCollation { @@ -372,7 +440,7 @@ impl CollationGenerationSubsystem { validation_data: validation_data.clone(), validation_code_hash, n_validators, - core_index, + core_index: descriptor_core_index, session_index, }, task_config.key.clone(), diff --git a/polkadot/node/collation-generation/src/tests.rs b/polkadot/node/collation-generation/src/tests.rs index f81c14cdf8f95289f36f13453877dd9b10853d92..dc1d7b3489c118857866b4b557ecc5e0d3597e5a 100644 --- a/polkadot/node/collation-generation/src/tests.rs +++ b/polkadot/node/collation-generation/src/tests.rs @@ -16,11 +16,10 @@ use super::*; use assert_matches::assert_matches; -use futures::{ - task::{Context as FuturesContext, Poll}, - Future, StreamExt, +use futures::{self, Future, StreamExt}; +use polkadot_node_primitives::{ + BlockData, Collation, CollationResult, CollatorFn, MaybeCompressedPoV, PoV, }; -use polkadot_node_primitives::{BlockData, Collation, CollationResult, MaybeCompressedPoV, PoV}; use polkadot_node_subsystem::{ messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest}, ActivatedLeaf, @@ -28,14 +27,16 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_test_helpers::TestSubsystemContextHandle; use polkadot_node_subsystem_util::TimeoutExt; use polkadot_primitives::{ - node_features, vstaging::CandidateDescriptorVersion, CollatorPair, PersistedValidationData, + node_features, + vstaging::{CandidateDescriptorVersion, CoreSelector, UMPSignal, UMP_SEPARATOR}, + CollatorPair, PersistedValidationData, }; use polkadot_primitives_test_helpers::dummy_head_data; use rstest::rstest; use sp_keyring::sr25519::Keyring as Sr25519Keyring; use std::{ collections::{BTreeMap, VecDeque}, - pin::Pin, + sync::Mutex, }; type VirtualOverseer = TestSubsystemContextHandle<CollationGenerationMessage>; @@ -79,17 +80,64 @@ fn test_collation() -> Collation { } } -struct TestCollator; +struct CoreSelectorData { + // The core selector index. + index: u8, + // The increment value for the core selector index. Normally 1, but can be set to 0 or another + // value for testing scenarios where a parachain repeatedly selects the same core index. + increment_index_by: u8, + // The claim queue offset. + cq_offset: u8, +} + +impl CoreSelectorData { + fn new(index: u8, increment_index_by: u8, cq_offset: u8) -> Self { + Self { index, increment_index_by, cq_offset } + } +} -impl Future for TestCollator { - type Output = Option<CollationResult>; +struct State { + core_selector_data: Option<CoreSelectorData>, +} - fn poll(self: Pin<&mut Self>, _cx: &mut FuturesContext) -> Poll<Self::Output> { - Poll::Ready(Some(CollationResult { collation: test_collation(), result_sender: None })) +impl State { + fn new(core_selector_data: Option<CoreSelectorData>) -> Self { + Self { core_selector_data } } } -impl Unpin for TestCollator {} +struct TestCollator { + state: Arc<Mutex<State>>, +} + +impl TestCollator { + fn new(core_selector_data: Option<CoreSelectorData>) -> Self { + Self { state: Arc::new(Mutex::new(State::new(core_selector_data))) } + } + + pub fn create_collation_function(&self) -> CollatorFn { + let state = Arc::clone(&self.state); + + Box::new(move |_relay_parent: Hash, _validation_data: &PersistedValidationData| { + let mut collation = test_collation(); + let mut state_guard = state.lock().unwrap(); + + if let Some(core_selector_data) = &mut state_guard.core_selector_data { + collation.upward_messages.force_push(UMP_SEPARATOR); + collation.upward_messages.force_push( + UMPSignal::SelectCore( + CoreSelector(core_selector_data.index), + ClaimQueueOffset(core_selector_data.cq_offset), + ) + .encode(), + ); + core_selector_data.index += core_selector_data.increment_index_by; + } + + async move { Some(CollationResult { collation, result_sender: None }) }.boxed() + }) + } +} const TIMEOUT: std::time::Duration = std::time::Duration::from_millis(2000); @@ -101,10 +149,14 @@ async fn overseer_recv(overseer: &mut VirtualOverseer) -> AllMessages { .expect(&format!("{:?} is long enough to receive messages", TIMEOUT)) } -fn test_config<Id: Into<ParaId>>(para_id: Id) -> CollationGenerationConfig { +fn test_config<Id: Into<ParaId>>( + para_id: Id, + core_selector_data: Option<CoreSelectorData>, +) -> CollationGenerationConfig { + let test_collator = TestCollator::new(core_selector_data); CollationGenerationConfig { key: CollatorPair::generate().0, - collator: Some(Box::new(|_: Hash, _vd: &PersistedValidationData| TestCollator.boxed())), + collator: Some(test_collator.create_collation_function()), para_id: para_id.into(), } } @@ -219,7 +271,7 @@ fn distribute_collation_only_for_assigned_para_id_at_offset_0() { .collect::<BTreeMap<_, _>>(); test_harness(|mut virtual_overseer| async move { - helpers::initialize_collator(&mut virtual_overseer, para_id).await; + helpers::initialize_collator(&mut virtual_overseer, para_id, None).await; helpers::activate_new_head(&mut virtual_overseer, activated_hash).await; helpers::handle_runtime_calls_on_new_head_activation( &mut virtual_overseer, @@ -259,7 +311,7 @@ fn distribute_collation_with_elastic_scaling(#[case] total_cores: u32) { .collect::<BTreeMap<_, _>>(); test_harness(|mut virtual_overseer| async move { - helpers::initialize_collator(&mut virtual_overseer, para_id).await; + helpers::initialize_collator(&mut virtual_overseer, para_id, None).await; helpers::activate_new_head(&mut virtual_overseer, activated_hash).await; helpers::handle_runtime_calls_on_new_head_activation( &mut virtual_overseer, @@ -281,6 +333,127 @@ fn distribute_collation_with_elastic_scaling(#[case] total_cores: u32) { }); } +// Tests when submission core indexes need to be selected using the core selectors provided in the +// UMP signals. The core selector index is an increasing number that can start with a non-negative +// value (even greater than the core index), but the collation generation protocol uses the +// remainder to select the core. UMP signals may also contain a claim queue offset, based on which +// we need to select the assigned core indexes for the para from that offset in the claim queue. +#[rstest] +#[case(0, 0, 0, false)] +#[case(1, 0, 0, true)] +#[case(1, 5, 0, false)] +#[case(2, 0, 1, true)] +#[case(4, 2, 2, false)] +fn distribute_collation_with_core_selectors( + #[case] total_cores: u32, + // The core selector index that will be obtained from the first collation. + #[case] init_cs_index: u8, + // Claim queue offset where the assigned cores will be stored. + #[case] cq_offset: u8, + // Enables v2 receipts feature, affecting core selector and claim queue handling. + #[case] v2_receipts: bool, +) { + let activated_hash: Hash = [1; 32].into(); + let para_id = ParaId::from(5); + let other_para_id = ParaId::from(10); + let node_features = + if v2_receipts { node_features_with_v2_enabled() } else { NodeFeatures::EMPTY }; + + let claim_queue = (0..total_cores) + .into_iter() + .map(|idx| { + // Set all cores assigned to para_id 5 at the cq_offset depth. + let mut vec = VecDeque::from(vec![other_para_id; cq_offset as usize]); + vec.push_back(para_id); + (CoreIndex(idx), vec) + }) + .collect::<BTreeMap<_, _>>(); + + test_harness(|mut virtual_overseer| async move { + helpers::initialize_collator( + &mut virtual_overseer, + para_id, + Some(CoreSelectorData::new(init_cs_index, 1, cq_offset)), + ) + .await; + helpers::activate_new_head(&mut virtual_overseer, activated_hash).await; + helpers::handle_runtime_calls_on_new_head_activation( + &mut virtual_overseer, + activated_hash, + claim_queue, + node_features, + ) + .await; + + let mut cores_assigned = (0..total_cores).collect::<Vec<_>>(); + if total_cores > 1 && init_cs_index > 0 { + // We need to rotate the list of cores because the first core selector index was + // non-zero, which should change the sequence of submissions. However, collations should + // still be submitted on all cores. + cores_assigned.rotate_left((init_cs_index as u32 % total_cores) as usize); + } + helpers::handle_cores_processing_for_a_leaf( + &mut virtual_overseer, + activated_hash, + para_id, + cores_assigned, + ) + .await; + + virtual_overseer + }); +} + +// Tests the behavior when a parachain repeatedly selects the same core index. +// Ensures that the system handles this behavior correctly while maintaining expected functionality. +#[rstest] +#[case(3, 0, vec![0])] +#[case(3, 1, vec![0, 1, 2])] +#[case(3, 2, vec![0, 2, 1])] +#[case(3, 3, vec![0])] +#[case(3, 4, vec![0, 1, 2])] +fn distribute_collation_with_repeated_core_selector_index( + #[case] total_cores: u32, + #[case] increment_cs_index_by: u8, + #[case] expected_selected_cores: Vec<u32>, +) { + let activated_hash: Hash = [1; 32].into(); + let para_id = ParaId::from(5); + let node_features = node_features_with_v2_enabled(); + + let claim_queue = (0..total_cores) + .into_iter() + .map(|idx| (CoreIndex(idx), VecDeque::from([para_id]))) + .collect::<BTreeMap<_, _>>(); + + test_harness(|mut virtual_overseer| async move { + helpers::initialize_collator( + &mut virtual_overseer, + para_id, + Some(CoreSelectorData::new(0, increment_cs_index_by, 0)), + ) + .await; + helpers::activate_new_head(&mut virtual_overseer, activated_hash).await; + helpers::handle_runtime_calls_on_new_head_activation( + &mut virtual_overseer, + activated_hash, + claim_queue, + node_features, + ) + .await; + + helpers::handle_cores_processing_for_a_leaf( + &mut virtual_overseer, + activated_hash, + para_id, + expected_selected_cores, + ) + .await; + + virtual_overseer + }); +} + #[rstest] #[case(true)] #[case(false)] @@ -405,10 +578,17 @@ mod helpers { use std::collections::{BTreeMap, VecDeque}; // Sends `Initialize` with a collator config - pub async fn initialize_collator(virtual_overseer: &mut VirtualOverseer, para_id: ParaId) { + pub async fn initialize_collator( + virtual_overseer: &mut VirtualOverseer, + para_id: ParaId, + core_selector_data: Option<CoreSelectorData>, + ) { virtual_overseer .send(FromOrchestra::Communication { - msg: CollationGenerationMessage::Initialize(test_config(para_id)), + msg: CollationGenerationMessage::Initialize(test_config( + para_id, + core_selector_data, + )), }) .await; } diff --git a/polkadot/node/core/approval-voting-parallel/Cargo.toml b/polkadot/node/core/approval-voting-parallel/Cargo.toml index 3a98cce80e920c3098b97ab3ef3888f9bf96983c..a3b3e97da4972a5fbbf4b4da2e7e0b670f28a0d7 100644 --- a/polkadot/node/core/approval-voting-parallel/Cargo.toml +++ b/polkadot/node/core/approval-voting-parallel/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Approval Voting Subsystem running approval work in parallel" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -17,38 +19,38 @@ gum = { workspace = true } itertools = { workspace = true } thiserror = { workspace = true } -polkadot-node-core-approval-voting = { workspace = true, default-features = true } polkadot-approval-distribution = { workspace = true, default-features = true } +polkadot-node-core-approval-voting = { workspace = true, default-features = true } +polkadot-node-metrics = { workspace = true, default-features = true } +polkadot-node-network-protocol = { workspace = true, default-features = true } +polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-node-network-protocol = { workspace = true, default-features = true } -polkadot-node-metrics = { workspace = true, default-features = true } sc-keystore = { workspace = true, default-features = false } +sp-application-crypto = { workspace = true, default-features = false, features = ["full_crypto"] } sp-consensus = { workspace = true, default-features = false } sp-consensus-slots = { workspace = true, default-features = false } -sp-application-crypto = { workspace = true, default-features = false, features = ["full_crypto"] } sp-runtime = { workspace = true, default-features = false } rand = { workspace = true } -rand_core = { workspace = true } rand_chacha = { workspace = true } +rand_core = { workspace = true } [dev-dependencies] +assert_matches = { workspace = true } async-trait = { workspace = true } +kvdb-memorydb = { workspace = true } +log = { workspace = true, default-features = true } parking_lot = { workspace = true } -sp-keyring = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-consensus-babe = { workspace = true, default-features = true } -sp-tracing = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true, default-features = true } -assert_matches = { workspace = true } -kvdb-memorydb = { workspace = true } polkadot-primitives-test-helpers = { workspace = true, default-features = true } -log = { workspace = true, default-features = true } polkadot-subsystem-bench = { workspace = true, default-features = true } schnorrkel = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-tracing = { workspace = true } diff --git a/polkadot/node/core/approval-voting/Cargo.toml b/polkadot/node/core/approval-voting/Cargo.toml index f9754d2babc909e1dacfe3b81e3d2a3009939b8e..2c292ba5efcbd640e8addc7ae2034c177cfc6f86 100644 --- a/polkadot/node/core/approval-voting/Cargo.toml +++ b/polkadot/node/core/approval-voting/Cargo.toml @@ -5,55 +5,57 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Approval Voting Subsystem of the Polkadot node" +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +async-trait = { workspace = true } +bitvec = { features = ["alloc"], workspace = true } +codec = { features = ["bit-vec", "derive"], workspace = true } +derive_more = { workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } -codec = { features = ["bit-vec", "derive"], workspace = true } gum = { workspace = true, default-features = true } -bitvec = { features = ["alloc"], workspace = true } -schnellru = { workspace = true } +itertools = { workspace = true } +kvdb = { workspace = true } merlin = { workspace = true, default-features = true } +schnellru = { workspace = true } schnorrkel = { workspace = true, default-features = true } -kvdb = { workspace = true } -derive_more = { workspace = true, default-features = true } thiserror = { workspace = true } -itertools = { workspace = true } -async-trait = { workspace = true } +polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } sc-keystore = { workspace = true } +sp-application-crypto = { features = ["full_crypto"], workspace = true } sp-consensus = { workspace = true } sp-consensus-slots = { workspace = true } -sp-application-crypto = { features = ["full_crypto"], workspace = true } sp-runtime = { workspace = true } # rand_core should match schnorrkel -rand_core = { workspace = true } -rand_chacha = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } +rand_chacha = { workspace = true, default-features = true } +rand_core = { workspace = true } [dev-dependencies] +assert_matches = { workspace = true } async-trait = { workspace = true } +kvdb-memorydb = { workspace = true } +log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-consensus-babe = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } -assert_matches = { workspace = true } -kvdb-memorydb = { workspace = true } +polkadot-primitives = { workspace = true, features = ["test"] } polkadot-primitives-test-helpers = { workspace = true } -log = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } sp-tracing = { workspace = true } -polkadot-primitives = { workspace = true, features = ["test"] } polkadot-subsystem-bench = { workspace = true } diff --git a/polkadot/node/core/approval-voting/src/approval_checking.rs b/polkadot/node/core/approval-voting/src/approval_checking.rs index 3b7262a46826eeba32811ad58f449b93dfaae61a..c7f38619ea1ff0c2db37c014a3f6c69ebf3332ba 100644 --- a/polkadot/node/core/approval-voting/src/approval_checking.rs +++ b/polkadot/node/core/approval-voting/src/approval_checking.rs @@ -712,13 +712,13 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(0, ValidatorIndex(1), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(1), block_tick, false); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1); - approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1, false); + approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1, false); - approval_entry.import_assignment(2, ValidatorIndex(4), block_tick + 2); + approval_entry.import_assignment(2, ValidatorIndex(4), block_tick + 2, false); let approvals = bitvec![u8, BitOrderLsb0; 1; 5]; @@ -757,8 +757,10 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, true); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick, false); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick, true); let approvals = bitvec![u8, BitOrderLsb0; 0; 10]; @@ -798,10 +800,10 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(0, ValidatorIndex(1), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(1), block_tick, false); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick, false); let mut approvals = bitvec![u8, BitOrderLsb0; 0; 10]; approvals.set(0, true); @@ -844,11 +846,11 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(0, ValidatorIndex(1), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(1), block_tick, false); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick); - approval_entry.import_assignment(1, ValidatorIndex(3), block_tick); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick, false); + approval_entry.import_assignment(1, ValidatorIndex(3), block_tick, false); let mut approvals = bitvec![u8, BitOrderLsb0; 0; n_validators]; approvals.set(0, true); @@ -913,14 +915,24 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(0, ValidatorIndex(1), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(1), block_tick, false); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1); - approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1, false); + approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1, false); - approval_entry.import_assignment(2, ValidatorIndex(4), block_tick + no_show_duration + 2); - approval_entry.import_assignment(2, ValidatorIndex(5), block_tick + no_show_duration + 2); + approval_entry.import_assignment( + 2, + ValidatorIndex(4), + block_tick + no_show_duration + 2, + false, + ); + approval_entry.import_assignment( + 2, + ValidatorIndex(5), + block_tick + no_show_duration + 2, + false, + ); let mut approvals = bitvec![u8, BitOrderLsb0; 0; n_validators]; approvals.set(0, true); @@ -1007,14 +1019,24 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(0, ValidatorIndex(1), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(1), block_tick, false); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1); - approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1, false); + approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1, false); - approval_entry.import_assignment(2, ValidatorIndex(4), block_tick + no_show_duration + 2); - approval_entry.import_assignment(2, ValidatorIndex(5), block_tick + no_show_duration + 2); + approval_entry.import_assignment( + 2, + ValidatorIndex(4), + block_tick + no_show_duration + 2, + false, + ); + approval_entry.import_assignment( + 2, + ValidatorIndex(5), + block_tick + no_show_duration + 2, + false, + ); let mut approvals = bitvec![u8, BitOrderLsb0; 0; n_validators]; approvals.set(0, true); @@ -1066,7 +1088,7 @@ mod tests { }, ); - approval_entry.import_assignment(3, ValidatorIndex(6), block_tick); + approval_entry.import_assignment(3, ValidatorIndex(6), block_tick, false); approvals.set(6, true); let tranche_now = no_show_duration as DelayTranche + 3; @@ -1176,7 +1198,7 @@ mod tests { // Populate the requested tranches. The assignments aren't inspected in // this test. for &t in &test_tranche { - approval_entry.import_assignment(t, ValidatorIndex(0), 0) + approval_entry.import_assignment(t, ValidatorIndex(0), 0, false); } let filled_tranches = filled_tranche_iterator(approval_entry.tranches()); diff --git a/polkadot/node/core/approval-voting/src/approval_db/v3/tests.rs b/polkadot/node/core/approval-voting/src/approval_db/v3/tests.rs index 372dd49803cb4e1edb04b5fccd7eda131e009484..69278868fa3d8ebbc49d51da24b80a90de0ad034 100644 --- a/polkadot/node/core/approval-voting/src/approval_db/v3/tests.rs +++ b/polkadot/node/core/approval-voting/src/approval_db/v3/tests.rs @@ -264,8 +264,8 @@ fn add_block_entry_adds_child() { fn canonicalize_works() { let (mut db, store) = make_db(); - // -> B1 -> C1 -> D1 - // A -> B2 -> C2 -> D2 + // -> B1 -> C1 -> D1 -> E1 + // A -> B2 -> C2 -> D2 -> E2 // // We'll canonicalize C1. Everything except D1 should disappear. // @@ -293,18 +293,22 @@ fn canonicalize_works() { let block_hash_c2 = Hash::repeat_byte(5); let block_hash_d1 = Hash::repeat_byte(6); let block_hash_d2 = Hash::repeat_byte(7); + let block_hash_e1 = Hash::repeat_byte(8); + let block_hash_e2 = Hash::repeat_byte(9); let candidate_receipt_genesis = make_candidate(ParaId::from(1_u32), genesis); let candidate_receipt_a = make_candidate(ParaId::from(2_u32), block_hash_a); let candidate_receipt_b = make_candidate(ParaId::from(3_u32), block_hash_a); let candidate_receipt_b1 = make_candidate(ParaId::from(4_u32), block_hash_b1); let candidate_receipt_c1 = make_candidate(ParaId::from(5_u32), block_hash_c1); + let candidate_receipt_e1 = make_candidate(ParaId::from(6_u32), block_hash_e1); let cand_hash_1 = candidate_receipt_genesis.hash(); let cand_hash_2 = candidate_receipt_a.hash(); let cand_hash_3 = candidate_receipt_b.hash(); let cand_hash_4 = candidate_receipt_b1.hash(); let cand_hash_5 = candidate_receipt_c1.hash(); + let cand_hash_6 = candidate_receipt_e1.hash(); let block_entry_a = make_block_entry(block_hash_a, genesis, 1, Vec::new()); let block_entry_b1 = make_block_entry(block_hash_b1, block_hash_a, 2, Vec::new()); @@ -326,6 +330,12 @@ fn canonicalize_works() { let block_entry_d2 = make_block_entry(block_hash_d2, block_hash_c2, 4, vec![(CoreIndex(0), cand_hash_5)]); + let block_entry_e1 = + make_block_entry(block_hash_e1, block_hash_d1, 5, vec![(CoreIndex(0), cand_hash_6)]); + + let block_entry_e2 = + make_block_entry(block_hash_e2, block_hash_d2, 5, vec![(CoreIndex(0), cand_hash_6)]); + let candidate_info = { let mut candidate_info = HashMap::new(); candidate_info.insert( @@ -345,6 +355,8 @@ fn canonicalize_works() { candidate_info .insert(cand_hash_5, NewCandidateInfo::new(candidate_receipt_c1, GroupIndex(5), None)); + candidate_info + .insert(cand_hash_6, NewCandidateInfo::new(candidate_receipt_e1, GroupIndex(6), None)); candidate_info }; @@ -357,6 +369,8 @@ fn canonicalize_works() { block_entry_c2.clone(), block_entry_d1.clone(), block_entry_d2.clone(), + block_entry_e1.clone(), + block_entry_e2.clone(), ]; let mut overlay_db = OverlayedBackend::new(&db); @@ -438,7 +452,7 @@ fn canonicalize_works() { assert_eq!( load_stored_blocks(store.as_ref(), &TEST_CONFIG).unwrap().unwrap(), - StoredBlockRange(4, 5) + StoredBlockRange(4, 6) ); check_candidates_in_store(vec![ @@ -447,6 +461,7 @@ fn canonicalize_works() { (cand_hash_3, Some(vec![block_hash_d1])), (cand_hash_4, Some(vec![block_hash_d1])), (cand_hash_5, None), + (cand_hash_6, Some(vec![block_hash_e1])), ]); check_blocks_in_store(vec![ @@ -456,6 +471,37 @@ fn canonicalize_works() { (block_hash_c1, None), (block_hash_c2, None), (block_hash_d1, Some(vec![cand_hash_3, cand_hash_4])), + (block_hash_e1, Some(vec![cand_hash_6])), + (block_hash_d2, None), + ]); + + let mut overlay_db = OverlayedBackend::new(&db); + canonicalize(&mut overlay_db, 4, block_hash_d1).unwrap(); + let write_ops = overlay_db.into_write_ops(); + db.write(write_ops).unwrap(); + + assert_eq!( + load_stored_blocks(store.as_ref(), &TEST_CONFIG).unwrap().unwrap(), + StoredBlockRange(5, 6) + ); + + check_candidates_in_store(vec![ + (cand_hash_1, None), + (cand_hash_2, None), + (cand_hash_3, None), + (cand_hash_4, None), + (cand_hash_5, None), + (cand_hash_6, Some(vec![block_hash_e1])), + ]); + + check_blocks_in_store(vec![ + (block_hash_a, None), + (block_hash_b1, None), + (block_hash_b2, None), + (block_hash_c1, None), + (block_hash_c2, None), + (block_hash_d1, None), + (block_hash_e1, Some(vec![cand_hash_6])), (block_hash_d2, None), ]); } diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index 2176cc7675beb972b6e323e1341e23a461a32620..2deca5a1aba8a55168c5f6ddf6463b5d0b8ed480 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -21,6 +21,7 @@ //! of others. It uses this information to determine when candidates and blocks have //! been sufficiently approved to finalize. +use futures_timer::Delay; use polkadot_node_primitives::{ approval::{ v1::{BlockApprovalMeta, DelayTranche}, @@ -122,12 +123,25 @@ const APPROVAL_CHECKING_TIMEOUT: Duration = Duration::from_secs(120); const WAIT_FOR_SIGS_TIMEOUT: Duration = Duration::from_millis(500); const APPROVAL_CACHE_SIZE: u32 = 1024; +/// The maximum number of times we retry to approve a block if is still needed. +const MAX_APPROVAL_RETRIES: u32 = 16; + const APPROVAL_DELAY: Tick = 2; pub(crate) const LOG_TARGET: &str = "parachain::approval-voting"; // The max number of ticks we delay sending the approval after we are ready to issue the approval const MAX_APPROVAL_COALESCE_WAIT_TICKS: Tick = 12; +// If the node restarted and the tranche has passed without the assignment +// being trigger, we won't trigger the assignment at restart because we don't have +// an wakeup schedule for it. +// The solution, is to always schedule a wake up after the restart and let the +// process_wakeup to decide if the assignment needs to be triggered. +// We need to have a delay after restart to give time to the node to catch up with +// messages and not trigger its assignment unnecessarily, because it hasn't seen +// the assignments from the other validators. +const RESTART_WAKEUP_DELAY: Tick = 12; + /// Configuration for the approval voting subsystem #[derive(Debug, Clone)] pub struct Config { @@ -165,6 +179,10 @@ pub struct ApprovalVotingSubsystem { metrics: Metrics, clock: Arc<dyn Clock + Send + Sync>, spawner: Arc<dyn overseer::gen::Spawner + 'static>, + /// The maximum time we retry to approve a block if it is still needed and PoV fetch failed. + max_approval_retries: u32, + /// The backoff before we retry the approval. + retry_backoff: Duration, } #[derive(Clone)] @@ -493,6 +511,8 @@ impl ApprovalVotingSubsystem { metrics, Arc::new(SystemClock {}), spawner, + MAX_APPROVAL_RETRIES, + APPROVAL_CHECKING_TIMEOUT / 2, ) } @@ -505,6 +525,8 @@ impl ApprovalVotingSubsystem { metrics: Metrics, clock: Arc<dyn Clock + Send + Sync>, spawner: Arc<dyn overseer::gen::Spawner + 'static>, + max_approval_retries: u32, + retry_backoff: Duration, ) -> Self { ApprovalVotingSubsystem { keystore, @@ -515,6 +537,8 @@ impl ApprovalVotingSubsystem { metrics, clock, spawner, + max_approval_retries, + retry_backoff, } } @@ -706,18 +730,53 @@ enum ApprovalOutcome { TimedOut, } +#[derive(Clone)] +struct RetryApprovalInfo { + candidate: CandidateReceipt, + backing_group: GroupIndex, + executor_params: ExecutorParams, + core_index: Option<CoreIndex>, + session_index: SessionIndex, + attempts_remaining: u32, + backoff: Duration, +} + struct ApprovalState { validator_index: ValidatorIndex, candidate_hash: CandidateHash, approval_outcome: ApprovalOutcome, + retry_info: Option<RetryApprovalInfo>, } impl ApprovalState { fn approved(validator_index: ValidatorIndex, candidate_hash: CandidateHash) -> Self { - Self { validator_index, candidate_hash, approval_outcome: ApprovalOutcome::Approved } + Self { + validator_index, + candidate_hash, + approval_outcome: ApprovalOutcome::Approved, + retry_info: None, + } } fn failed(validator_index: ValidatorIndex, candidate_hash: CandidateHash) -> Self { - Self { validator_index, candidate_hash, approval_outcome: ApprovalOutcome::Failed } + Self { + validator_index, + candidate_hash, + approval_outcome: ApprovalOutcome::Failed, + retry_info: None, + } + } + + fn failed_with_retry( + validator_index: ValidatorIndex, + candidate_hash: CandidateHash, + retry_info: Option<RetryApprovalInfo>, + ) -> Self { + Self { + validator_index, + candidate_hash, + approval_outcome: ApprovalOutcome::Failed, + retry_info, + } } } @@ -757,6 +816,7 @@ impl CurrentlyCheckingSet { candidate_hash, validator_index, approval_outcome: ApprovalOutcome::TimedOut, + retry_info: None, }, Some(approval_state) => approval_state, } @@ -1271,18 +1331,19 @@ where validator_index, candidate_hash, approval_outcome, + retry_info, } ) = approval_state; if matches!(approval_outcome, ApprovalOutcome::Approved) { let mut approvals: Vec<Action> = relay_block_hashes - .into_iter() + .iter() .map(|block_hash| Action::IssueApproval( candidate_hash, ApprovalVoteRequest { validator_index, - block_hash, + block_hash: *block_hash, }, ) ) @@ -1290,6 +1351,43 @@ where actions.append(&mut approvals); } + if let Some(retry_info) = retry_info { + for block_hash in relay_block_hashes { + if overlayed_db.load_block_entry(&block_hash).map(|block_info| block_info.is_some()).unwrap_or(false) { + let sender = to_other_subsystems.clone(); + let spawn_handle = subsystem.spawner.clone(); + let metrics = subsystem.metrics.clone(); + let retry_info = retry_info.clone(); + let executor_params = retry_info.executor_params.clone(); + let candidate = retry_info.candidate.clone(); + + currently_checking_set + .insert_relay_block_hash( + candidate_hash, + validator_index, + block_hash, + async move { + launch_approval( + sender, + spawn_handle, + metrics, + retry_info.session_index, + candidate, + validator_index, + block_hash, + retry_info.backing_group, + executor_params, + retry_info.core_index, + retry_info, + ) + .await + }, + ) + .await?; + } + } + } + actions }, (block_hash, validator_index) = delayed_approvals_timers.select_next_some() => { @@ -1340,6 +1438,8 @@ where &mut approvals_cache, &mut subsystem.mode, actions, + subsystem.max_approval_retries, + subsystem.retry_backoff, ) .await? { @@ -1389,6 +1489,8 @@ pub async fn start_approval_worker< metrics, clock, spawner, + MAX_APPROVAL_RETRIES, + APPROVAL_CHECKING_TIMEOUT / 2, ); let backend = DbBackend::new(db.clone(), approval_voting.db_config); let spawner = approval_voting.spawner.clone(); @@ -1456,6 +1558,8 @@ async fn handle_actions< approvals_cache: &mut LruMap<CandidateHash, ApprovalOutcome>, mode: &mut Mode, actions: Vec<Action>, + max_approval_retries: u32, + retry_backoff: Duration, ) -> SubsystemResult<bool> { let mut conclude = false; let mut actions_iter = actions.into_iter(); @@ -1542,6 +1646,16 @@ async fn handle_actions< let sender = sender.clone(); let spawn_handle = spawn_handle.clone(); + let retry = RetryApprovalInfo { + candidate: candidate.clone(), + backing_group, + executor_params: executor_params.clone(), + core_index, + session_index: session, + attempts_remaining: max_approval_retries, + backoff: retry_backoff, + }; + currently_checking_set .insert_relay_block_hash( candidate_hash, @@ -1559,6 +1673,7 @@ async fn handle_actions< backing_group, executor_params, core_index, + retry, ) .await }, @@ -1582,8 +1697,9 @@ async fn handle_actions< session_info_provider, ) .await?; - - approval_voting_sender.send_messages(messages.into_iter()).await; + for message in messages.into_iter() { + approval_voting_sender.send_unbounded_message(message); + } let next_actions: Vec<Action> = next_actions.into_iter().map(|v| v.clone()).chain(actions_iter).collect(); @@ -1668,6 +1784,7 @@ async fn distribution_messages_for_activation<Sender: SubsystemSender<RuntimeApi let mut approval_meta = Vec::with_capacity(all_blocks.len()); let mut messages = Vec::new(); + let mut approvals = Vec::new(); let mut actions = Vec::new(); messages.push(ApprovalDistributionMessage::NewBlocks(Vec::new())); // dummy value. @@ -1730,7 +1847,20 @@ async fn distribution_messages_for_activation<Sender: SubsystemSender<RuntimeApi match candidate_entry.approval_entry(&block_hash) { Some(approval_entry) => { match approval_entry.local_statements() { - (None, None) | (None, Some(_)) => {}, // second is impossible case. + (None, None) => + if approval_entry + .our_assignment() + .map(|assignment| !assignment.triggered()) + .unwrap_or(false) + { + actions.push(Action::ScheduleWakeup { + block_hash, + block_number: block_entry.block_number(), + candidate_hash: *candidate_hash, + tick: state.clock.tick_now() + RESTART_WAKEUP_DELAY, + }) + }, + (None, Some(_)) => {}, // second is impossible case. (Some(assignment), None) => { let claimed_core_indices = get_core_indices_on_startup(&assignment.cert().kind, *core_index); @@ -1839,7 +1969,7 @@ async fn distribution_messages_for_activation<Sender: SubsystemSender<RuntimeApi if signatures_queued .insert(approval_sig.signed_candidates_indices.clone()) { - messages.push(ApprovalDistributionMessage::DistributeApproval( + approvals.push(ApprovalDistributionMessage::DistributeApproval( IndirectSignedApprovalVoteV2 { block_hash, candidate_indices: approval_sig.signed_candidates_indices, @@ -1864,6 +1994,10 @@ async fn distribution_messages_for_activation<Sender: SubsystemSender<RuntimeApi } messages[0] = ApprovalDistributionMessage::NewBlocks(approval_meta); + // Approvals are appended at the end, to make sure all assignments are sent + // before the approvals, otherwise if they arrive ahead in approval-distribution + // they will be ignored. + messages.extend(approvals.into_iter()); Ok((messages, actions)) } @@ -2674,8 +2808,15 @@ where Vec::new(), )), }; - is_duplicate &= approval_entry.is_assigned(assignment.validator); - approval_entry.import_assignment(tranche, assignment.validator, tick_now); + + let is_duplicate_for_candidate = approval_entry.is_assigned(assignment.validator); + is_duplicate &= is_duplicate_for_candidate; + approval_entry.import_assignment( + tranche, + assignment.validator, + tick_now, + is_duplicate_for_candidate, + ); // We've imported a new assignment, so we need to schedule a wake-up for when that might // no-show. @@ -3323,6 +3464,7 @@ async fn launch_approval< backing_group: GroupIndex, executor_params: ExecutorParams, core_index: Option<CoreIndex>, + retry: RetryApprovalInfo, ) -> SubsystemResult<RemoteHandle<ApprovalState>> { let (a_tx, a_rx) = oneshot::channel(); let (code_tx, code_rx) = oneshot::channel(); @@ -3354,6 +3496,7 @@ async fn launch_approval< let candidate_hash = candidate.hash(); let para_id = candidate.descriptor.para_id(); + let mut next_retry = None; gum::trace!(target: LOG_TARGET, ?candidate_hash, ?para_id, "Recovering data."); let timer = metrics.time_recover_and_approve(); @@ -3382,7 +3525,6 @@ async fn launch_approval< let background = async move { // Force the move of the timer into the background task. let _timer = timer; - let available_data = match a_rx.await { Err(_) => return ApprovalState::failed(validator_index, candidate_hash), Ok(Ok(a)) => a, @@ -3393,10 +3535,27 @@ async fn launch_approval< target: LOG_TARGET, ?para_id, ?candidate_hash, + attempts_remaining = retry.attempts_remaining, "Data unavailable for candidate {:?}", (candidate_hash, candidate.descriptor.para_id()), ); - // do nothing. we'll just be a no-show and that'll cause others to rise up. + // Availability could fail if we did not discover much of the network, so + // let's back off and order the subsystem to retry at a later point if the + // approval is still needed, because no-show wasn't covered yet. + if retry.attempts_remaining > 0 { + Delay::new(retry.backoff).await; + next_retry = Some(RetryApprovalInfo { + candidate, + backing_group, + executor_params, + core_index, + session_index, + attempts_remaining: retry.attempts_remaining - 1, + backoff: retry.backoff, + }); + } else { + next_retry = None; + } metrics_guard.take().on_approval_unavailable(); }, &RecoveryError::ChannelClosed => { @@ -3427,7 +3586,7 @@ async fn launch_approval< metrics_guard.take().on_approval_invalid(); }, } - return ApprovalState::failed(validator_index, candidate_hash) + return ApprovalState::failed_with_retry(validator_index, candidate_hash, next_retry) }, }; diff --git a/polkadot/node/core/approval-voting/src/ops.rs b/polkadot/node/core/approval-voting/src/ops.rs index f105580009faa4641504074c5901c4860f3cb7f6..efdc8780da62083f167c968a968537ce7d4a52ff 100644 --- a/polkadot/node/core/approval-voting/src/ops.rs +++ b/polkadot/node/core/approval-voting/src/ops.rs @@ -90,7 +90,7 @@ pub fn canonicalize( ) -> SubsystemResult<()> { let range = match overlay_db.load_stored_blocks()? { None => return Ok(()), - Some(range) if range.0 >= canon_number => return Ok(()), + Some(range) if range.0 > canon_number => return Ok(()), Some(range) => range, }; diff --git a/polkadot/node/core/approval-voting/src/persisted_entries.rs b/polkadot/node/core/approval-voting/src/persisted_entries.rs index d891af01c3ab85621c72c4c0e6beda52335387da..14c678913dc3b163e221211212a09cc1dcf8a0d4 100644 --- a/polkadot/node/core/approval-voting/src/persisted_entries.rs +++ b/polkadot/node/core/approval-voting/src/persisted_entries.rs @@ -172,7 +172,7 @@ impl ApprovalEntry { }); our.map(|a| { - self.import_assignment(a.tranche(), a.validator_index(), tick_now); + self.import_assignment(a.tranche(), a.validator_index(), tick_now, false); (a.cert().clone(), a.validator_index(), a.tranche()) }) @@ -197,6 +197,7 @@ impl ApprovalEntry { tranche: DelayTranche, validator_index: ValidatorIndex, tick_now: Tick, + is_duplicate: bool, ) { // linear search probably faster than binary. not many tranches typically. let idx = match self.tranches.iter().position(|t| t.tranche >= tranche) { @@ -213,8 +214,15 @@ impl ApprovalEntry { self.tranches.len() - 1 }, }; - - self.tranches[idx].assignments.push((validator_index, tick_now)); + // At restart we might have duplicate assignments because approval-distribution is not + // persistent across restarts, so avoid adding duplicates. + // We already know if we have seen an assignment from this validator and since this + // function is on the hot path we can avoid iterating through tranches by using + // !is_duplicate to determine if it is already present in the vector and does not need + // adding. + if !is_duplicate { + self.tranches[idx].assignments.push((validator_index, tick_now)); + } self.assigned_validators.set(validator_index.0 as _, true); } @@ -561,7 +569,7 @@ impl BlockEntry { self.distributed_assignments.resize(new_len, false); self.distributed_assignments |= bitfield; - // If the an operation did not change our current bitfield, we return true. + // If an operation did not change our current bitfield, we return true. let distributed = total_one_bits == self.distributed_assignments.count_ones(); distributed diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs index 099ab419dfbfc8373490d43c294023e2611eba26..9fe716833b882880ca8fb62c13c3b277be966785 100644 --- a/polkadot/node/core/approval-voting/src/tests.rs +++ b/polkadot/node/core/approval-voting/src/tests.rs @@ -78,6 +78,9 @@ const SLOT_DURATION_MILLIS: u64 = 5000; const TIMEOUT: Duration = Duration::from_millis(2000); +const NUM_APPROVAL_RETRIES: u32 = 3; +const RETRY_BACKOFF: Duration = Duration::from_millis(300); + #[derive(Clone)] struct TestSyncOracle { flag: Arc<AtomicBool>, @@ -573,6 +576,8 @@ fn test_harness<T: Future<Output = VirtualOverseer>>( Metrics::default(), clock.clone(), Arc::new(SpawnGlue(pool)), + NUM_APPROVAL_RETRIES, + RETRY_BACKOFF, ), assignment_criteria, backend, @@ -3202,6 +3207,20 @@ async fn recover_available_data(virtual_overseer: &mut VirtualOverseer) { ); } +async fn recover_available_data_failure(virtual_overseer: &mut VirtualOverseer) { + let available_data = RecoveryError::Unavailable; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::AvailabilityRecovery( + AvailabilityRecoveryMessage::RecoverAvailableData(_, _, _, _, tx) + ) => { + tx.send(Err(available_data)).unwrap(); + }, + "overseer did not receive recover available data message", + ); +} + struct TriggersAssignmentConfig<F1, F2> { our_assigned_tranche: DelayTranche, assign_validator_tranche: F1, @@ -4459,6 +4478,114 @@ async fn setup_overseer_with_two_blocks_each_with_one_assignment_triggered( assert!(our_assignment.triggered()); } +// Builds a chain with a fork where both relay blocks include the same candidate. +async fn build_chain_with_block_with_two_candidates( + block_hash1: Hash, + slot: Slot, + sync_oracle_handle: TestSyncOracleHandle, + candidate_receipt: Vec<CandidateReceipt>, +) -> (ChainBuilder, SessionInfo) { + let validators = vec![ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Eve, + ]; + let session_info = SessionInfo { + validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![ + vec![ValidatorIndex(0), ValidatorIndex(1)], + vec![ValidatorIndex(2)], + vec![ValidatorIndex(3), ValidatorIndex(4)], + ]), + ..session_info(&validators) + }; + + let candidates = Some( + candidate_receipt + .iter() + .enumerate() + .map(|(i, receipt)| (receipt.clone(), CoreIndex(i as u32), GroupIndex(i as u32))) + .collect(), + ); + let mut chain_builder = ChainBuilder::new(); + + chain_builder + .major_syncing(sync_oracle_handle.is_major_syncing.clone()) + .add_block( + block_hash1, + ChainBuilder::GENESIS_HASH, + 1, + BlockConfig { + slot, + candidates: candidates.clone(), + session_info: Some(session_info.clone()), + end_syncing: true, + }, + ); + (chain_builder, session_info) +} + +async fn setup_overseer_with_blocks_with_two_assignments_triggered( + virtual_overseer: &mut VirtualOverseer, + store: TestStore, + clock: &Arc<MockClock>, + sync_oracle_handle: TestSyncOracleHandle, +) { + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => { + rx.send(Ok(0)).unwrap(); + } + ); + + let block_hash = Hash::repeat_byte(0x01); + let candidate_commitments = CandidateCommitments::default(); + let mut candidate_receipt = dummy_candidate_receipt_v2(block_hash); + candidate_receipt.commitments_hash = candidate_commitments.hash(); + let candidate_hash = candidate_receipt.hash(); + + let mut candidate_commitments2 = CandidateCommitments::default(); + candidate_commitments2.processed_downward_messages = 3; + let mut candidate_receipt2 = dummy_candidate_receipt_v2(block_hash); + candidate_receipt2.commitments_hash = candidate_commitments2.hash(); + let candidate_hash2 = candidate_receipt2.hash(); + + let slot = Slot::from(1); + let (chain_builder, _session_info) = build_chain_with_block_with_two_candidates( + block_hash, + slot, + sync_oracle_handle, + vec![candidate_receipt, candidate_receipt2], + ) + .await; + chain_builder.build(virtual_overseer).await; + + assert!(!clock.inner.lock().current_wakeup_is(1)); + clock.inner.lock().wakeup_all(1); + + assert!(clock.inner.lock().current_wakeup_is(slot_to_tick(slot))); + clock.inner.lock().wakeup_all(slot_to_tick(slot)); + + futures_timer::Delay::new(Duration::from_millis(200)).await; + + clock.inner.lock().wakeup_all(slot_to_tick(slot + 2)); + + assert_eq!(clock.inner.lock().wakeups.len(), 0); + + futures_timer::Delay::new(Duration::from_millis(200)).await; + + let candidate_entry = store.load_candidate_entry(&candidate_hash).unwrap().unwrap(); + let our_assignment = + candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap(); + assert!(our_assignment.triggered()); + + let candidate_entry = store.load_candidate_entry(&candidate_hash2).unwrap().unwrap(); + let our_assignment = + candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap(); + assert!(our_assignment.triggered()); +} + // Tests that for candidates that we did not approve yet, for which we triggered the assignment and // the approval work we restart the work to approve it. #[test] @@ -4683,6 +4810,133 @@ fn subsystem_relaunches_approval_work_on_restart() { }); } +/// Test that we retry the approval of candidate on availability failure, up to max retries. +#[test] +fn subsystem_relaunches_approval_work_on_availability_failure() { + let assignment_criteria = Box::new(MockAssignmentCriteria( + || { + let mut assignments = HashMap::new(); + + let _ = assignments.insert( + CoreIndex(0), + approval_db::v2::OurAssignment { + cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact { + core_bitfield: vec![CoreIndex(0), CoreIndex(2)].try_into().unwrap(), + }), + tranche: 0, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + + let _ = assignments.insert( + CoreIndex(1), + approval_db::v2::OurAssignment { + cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFDelay { + core_index: CoreIndex(1), + }), + tranche: 0, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + assignments + }, + |_| Ok(0), + )); + let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build(); + let store = config.backend(); + + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness; + + setup_overseer_with_blocks_with_two_assignments_triggered( + &mut virtual_overseer, + store, + &clock, + sync_oracle_handle, + ) + .await; + + // We have two candidates for one we are going to fail the availability for up to + // max_retries and for the other we are going to succeed on the last retry, so we should + // see the approval being distributed. + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + recover_available_data_failure(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _ + )) => { + } + ); + + recover_available_data_failure(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + recover_available_data_failure(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + recover_available_data_failure(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + recover_available_data_failure(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + recover_available_data_failure(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + recover_available_data_failure(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + recover_available_data(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive { + exec_kind, + response_sender, + .. + }) if exec_kind == PvfExecKind::Approval => { + response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default()))) + .unwrap(); + } + ); + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 1, + })); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_)) + ); + + // Assert that there are no more messages being sent by the subsystem + assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + + virtual_overseer + }); +} + // Test that cached approvals, which are candidates that we approved but we didn't issue // the signature yet because we want to coalesce it with more candidate are sent after restart. #[test] @@ -4920,6 +5174,458 @@ fn subsystem_sends_pending_approvals_on_approval_restart() { }); } +// Test that after restart approvals are sent after all assignments have been distributed. +#[test] +fn subsystem_sends_assignment_approval_in_correct_order_on_approval_restart() { + let assignment_criteria = Box::new(MockAssignmentCriteria( + || { + let mut assignments = HashMap::new(); + + let _ = assignments.insert( + CoreIndex(0), + approval_db::v2::OurAssignment { + cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact { + core_bitfield: vec![CoreIndex(0), CoreIndex(2)].try_into().unwrap(), + }), + tranche: 0, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + + let _ = assignments.insert( + CoreIndex(1), + approval_db::v2::OurAssignment { + cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFDelay { + core_index: CoreIndex(1), + }), + tranche: 0, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + assignments + }, + |_| Ok(0), + )); + let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build(); + let store = config.backend(); + let store_clone = config.backend(); + + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness; + + setup_overseer_with_blocks_with_two_assignments_triggered( + &mut virtual_overseer, + store, + &clock, + sync_oracle_handle, + ) + .await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + recover_available_data(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _ + )) => { + } + ); + + recover_available_data(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive { + exec_kind, + response_sender, + .. + }) if exec_kind == PvfExecKind::Approval => { + response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default()))) + .unwrap(); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive { + exec_kind, + response_sender, + .. + }) if exec_kind == PvfExecKind::Approval => { + response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default()))) + .unwrap(); + } + ); + + // Configure a big coalesce number, so that the signature is cached instead of being sent to + // approval-distribution. + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 2, + })); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 2, + })); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_)) + ); + + // Assert that there are no more messages being sent by the subsystem + assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + + virtual_overseer + }); + + let config = HarnessConfigBuilder::default().backend(store_clone).major_syncing(true).build(); + // On restart we should first distribute all assignments covering a coalesced approval. + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => { + rx.send(Ok(0)).unwrap(); + } + ); + + let block_hash = Hash::repeat_byte(0x01); + let candidate_commitments = CandidateCommitments::default(); + let mut candidate_receipt = dummy_candidate_receipt_v2(block_hash); + candidate_receipt.commitments_hash = candidate_commitments.hash(); + + let mut candidate_commitments2 = CandidateCommitments::default(); + candidate_commitments2.processed_downward_messages = 3; + let mut candidate_receipt2 = dummy_candidate_receipt_v2(block_hash); + candidate_receipt2.commitments_hash = candidate_commitments2.hash(); + + let slot = Slot::from(1); + + clock.inner.lock().set_tick(slot_to_tick(slot + 2)); + let (chain_builder, _session_info) = build_chain_with_block_with_two_candidates( + block_hash, + slot, + sync_oracle_handle, + vec![candidate_receipt.into(), candidate_receipt2.into()], + ) + .await; + chain_builder.build(&mut virtual_overseer).await; + + futures_timer::Delay::new(Duration::from_millis(2000)).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NewBlocks( + _, + )) => { + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(approval)) => { + assert_eq!(approval.candidate_indices.count_ones(), 2); + } + ); + + // Assert that there are no more messages being sent by the subsystem + assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + + virtual_overseer + }); +} + +// Test that if the subsystem missed the triggering of some tranches because it was not running +// it launches the missed assignements on restart. +#[test] +fn subsystem_launches_missed_assignments_on_restart() { + let test_tranche = 20; + let assignment_criteria = Box::new(MockAssignmentCriteria( + move || { + let mut assignments = HashMap::new(); + let _ = assignments.insert( + CoreIndex(0), + approval_db::v2::OurAssignment { + cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFDelay { + core_index: CoreIndex(0), + }), + tranche: test_tranche, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + + assignments + }, + |_| Ok(0), + )); + let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build(); + let store = config.backend(); + let store_clone = config.backend(); + + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => { + rx.send(Ok(0)).unwrap(); + } + ); + + let block_hash = Hash::repeat_byte(0x01); + let fork_block_hash = Hash::repeat_byte(0x02); + let candidate_commitments = CandidateCommitments::default(); + let mut candidate_receipt = dummy_candidate_receipt_v2(block_hash); + candidate_receipt.commitments_hash = candidate_commitments.hash(); + let candidate_hash = candidate_receipt.hash(); + let slot = Slot::from(1); + let (chain_builder, _session_info) = build_chain_with_two_blocks_with_one_candidate_each( + block_hash, + fork_block_hash, + slot, + sync_oracle_handle, + candidate_receipt, + ) + .await; + chain_builder.build(&mut virtual_overseer).await; + + assert!(!clock.inner.lock().current_wakeup_is(1)); + clock.inner.lock().wakeup_all(1); + + assert!(clock.inner.lock().current_wakeup_is(slot_to_tick(slot) + test_tranche as u64)); + clock.inner.lock().wakeup_all(slot_to_tick(slot)); + + futures_timer::Delay::new(Duration::from_millis(200)).await; + + clock.inner.lock().wakeup_all(slot_to_tick(slot + 2)); + + assert_eq!(clock.inner.lock().wakeups.len(), 0); + + futures_timer::Delay::new(Duration::from_millis(200)).await; + + let candidate_entry = store.load_candidate_entry(&candidate_hash).unwrap().unwrap(); + let our_assignment = + candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap(); + assert!(!our_assignment.triggered()); + + // Assignment is not triggered because its tranches has not been reached. + virtual_overseer + }); + + // Restart a new approval voting subsystem with the same database and major syncing true until + // the last leaf. + let config = HarnessConfigBuilder::default().backend(store_clone).major_syncing(true).build(); + + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness; + let slot = Slot::from(1); + // 1. Set the clock to the to a tick past the tranche where the assignment should be + // triggered. + clock.inner.lock().set_tick(slot_to_tick(slot) + 2 * test_tranche as u64); + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => { + rx.send(Ok(0)).unwrap(); + } + ); + + let block_hash = Hash::repeat_byte(0x01); + let fork_block_hash = Hash::repeat_byte(0x02); + let candidate_commitments = CandidateCommitments::default(); + let mut candidate_receipt = dummy_candidate_receipt_v2(block_hash); + candidate_receipt.commitments_hash = candidate_commitments.hash(); + let (chain_builder, session_info) = build_chain_with_two_blocks_with_one_candidate_each( + block_hash, + fork_block_hash, + slot, + sync_oracle_handle, + candidate_receipt, + ) + .await; + + chain_builder.build(&mut virtual_overseer).await; + + futures_timer::Delay::new(Duration::from_millis(2000)).await; + + // On major syncing ending Approval voting should send all the necessary messages for a + // candidate to be approved. + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NewBlocks( + _, + )) => { + } + ); + + clock + .inner + .lock() + .wakeup_all(slot_to_tick(slot) + 2 * test_tranche as u64 + RESTART_WAKEUP_DELAY - 1); + + // Subsystem should not send any messages because the assignment is not triggered yet. + assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + + // Set the clock to the tick where the assignment should be triggered. + clock + .inner + .lock() + .wakeup_all(slot_to_tick(slot) + 2 * test_tranche as u64 + RESTART_WAKEUP_DELAY); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request( + _, + RuntimeApiRequest::SessionInfo(_, si_tx), + ) + ) => { + si_tx.send(Ok(Some(session_info.clone()))).unwrap(); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request( + _, + RuntimeApiRequest::SessionExecutorParams(_, si_tx), + ) + ) => { + // Make sure all SessionExecutorParams calls are not made for the leaf (but for its relay parent) + si_tx.send(Ok(Some(ExecutorParams::default()))).unwrap(); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + // Guarantees the approval work has been relaunched. + recover_available_data(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive { + exec_kind, + response_sender, + .. + }) if exec_kind == PvfExecKind::Approval => { + response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default()))) + .unwrap(); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 1, + })); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_)) + ); + + clock + .inner + .lock() + .wakeup_all(slot_to_tick(slot) + 2 * test_tranche as u64 + RESTART_WAKEUP_DELAY); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 1, + })); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_)) + ); + + // Assert that there are no more messages being sent by the subsystem + assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + + virtual_overseer + }); +} + // Test we correctly update the timer when we mark the beginning of gathering assignments. #[test] fn test_gathering_assignments_statements() { diff --git a/polkadot/node/core/av-store/Cargo.toml b/polkadot/node/core/av-store/Cargo.toml index 1d14e4cfba37824f16bc49afd64669a08748a0b9..f3bd1f09caeadb4dd271f96befb70387bccb7d36 100644 --- a/polkadot/node/core/av-store/Cargo.toml +++ b/polkadot/node/core/av-store/Cargo.toml @@ -5,36 +5,38 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +bitvec = { workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } +gum = { workspace = true, default-features = true } kvdb = { workspace = true } thiserror = { workspace = true } -gum = { workspace = true, default-features = true } -bitvec = { workspace = true, default-features = true } codec = { features = ["derive"], workspace = true, default-features = true } polkadot-erasure-coding = { workspace = true, default-features = true } +polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } sp-consensus = { workspace = true } [dev-dependencies] -log = { workspace = true, default-features = true } assert_matches = { workspace = true } kvdb-memorydb = { workspace = true } +log = { workspace = true, default-features = true } sp-tracing = { workspace = true } -sp-core = { workspace = true, default-features = true } -polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-node-subsystem-test-helpers = { workspace = true } -sp-keyring = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } +polkadot-node-subsystem-test-helpers = { workspace = true } +polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-primitives-test-helpers = { workspace = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } diff --git a/polkadot/node/core/backing/Cargo.toml b/polkadot/node/core/backing/Cargo.toml index cd1acf9daa9390e7a24f3956e40f9c7477b32efc..be829a84ee6ea30f4b32e25902da4537ae110ae5 100644 --- a/polkadot/node/core/backing/Cargo.toml +++ b/polkadot/node/core/backing/Cargo.toml @@ -5,35 +5,37 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "The Candidate Backing Subsystem. Tracks parachain candidates that can be backed, as well as the issuance of statements about candidates." +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +bitvec = { features = ["alloc"], workspace = true } +fatality = { workspace = true } futures = { workspace = true } -sp-keystore = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } +gum = { workspace = true, default-features = true } +polkadot-erasure-coding = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-erasure-coding = { workspace = true, default-features = true } +polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } polkadot-statement-table = { workspace = true, default-features = true } -bitvec = { features = ["alloc"], workspace = true } -gum = { workspace = true, default-features = true } -thiserror = { workspace = true } -fatality = { workspace = true } schnellru = { workspace = true } +sp-keystore = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] -sp-core = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } -futures = { features = ["thread-pool"], workspace = true } assert_matches = { workspace = true } -rstest = { workspace = true } +futures = { features = ["thread-pool"], workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } -polkadot-primitives-test-helpers = { workspace = true } polkadot-primitives = { workspace = true, features = ["test"] } +polkadot-primitives-test-helpers = { workspace = true } +rstest = { workspace = true } +sc-keystore = { workspace = true, default-features = true } +sp-application-crypto = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } diff --git a/polkadot/node/core/bitfield-signing/Cargo.toml b/polkadot/node/core/bitfield-signing/Cargo.toml index 126a18a141661c49411b576d9730e255ca627692..e75404729dbdba3cc42cd47b2f04827161f19312 100644 --- a/polkadot/node/core/bitfield-signing/Cargo.toml +++ b/polkadot/node/core/bitfield-signing/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Bitfield signing subsystem for the Polkadot node" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -12,12 +14,12 @@ workspace = true [dependencies] futures = { workspace = true } gum = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } -wasm-timer = { workspace = true } thiserror = { workspace = true } +wasm-timer = { workspace = true } [dev-dependencies] polkadot-node-subsystem-test-helpers = { workspace = true } diff --git a/polkadot/node/core/candidate-validation/Cargo.toml b/polkadot/node/core/candidate-validation/Cargo.toml index 87855dbce415e4d24d73516c6b1541ff66e79dd2..e92976609f9e89cfbe55707517c37e54af17072e 100644 --- a/polkadot/node/core/candidate-validation/Cargo.toml +++ b/polkadot/node/core/candidate-validation/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -15,28 +17,28 @@ futures = { workspace = true } futures-timer = { workspace = true } gum = { workspace = true, default-features = true } -sp-keystore = { workspace = true } -sp-application-crypto = { workspace = true } codec = { features = ["bit-vec", "derive"], workspace = true } +sp-application-crypto = { workspace = true } +sp-keystore = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } -polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-node-metrics = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } +polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } [target.'cfg(not(any(target_os = "android", target_os = "unknown")))'.dependencies] polkadot-node-core-pvf = { workspace = true, default-features = true } [dev-dependencies] -sp-keyring = { workspace = true, default-features = true } -futures = { features = ["thread-pool"], workspace = true } assert_matches = { workspace = true } +futures = { features = ["thread-pool"], workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } -sp-maybe-compressed-blob = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, features = ["test"] } polkadot-primitives-test-helpers = { workspace = true } rstest = { workspace = true } -polkadot-primitives = { workspace = true, features = ["test"] } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-maybe-compressed-blob = { workspace = true, default-features = true } diff --git a/polkadot/node/core/candidate-validation/src/lib.rs b/polkadot/node/core/candidate-validation/src/lib.rs index 25614349486ea6c85ee4fc2d529be8d77a4472b4..24590fe0c90ea9f8a1e164efc52cd5da18dbf436 100644 --- a/polkadot/node/core/candidate-validation/src/lib.rs +++ b/polkadot/node/core/candidate-validation/src/lib.rs @@ -39,9 +39,9 @@ use polkadot_node_subsystem::{ }; use polkadot_node_subsystem_util::{ self as util, - runtime::{prospective_parachains_mode, ClaimQueueSnapshot, ProspectiveParachainsMode}, + runtime::{fetch_scheduling_lookahead, ClaimQueueSnapshot}, }; -use polkadot_overseer::ActiveLeavesUpdate; +use polkadot_overseer::{ActivatedLeaf, ActiveLeavesUpdate}; use polkadot_parachain_primitives::primitives::ValidationResult as WasmValidationResult; use polkadot_primitives::{ executor_params::{ @@ -158,7 +158,7 @@ where Sender: SubsystemSender<RuntimeApiMessage>, { match util::runtime::fetch_claim_queue(sender, relay_parent).await { - Ok(maybe_cq) => maybe_cq, + Ok(cq) => Some(cq), Err(err) => { gum::warn!( target: LOG_TARGET, @@ -190,40 +190,30 @@ where exec_kind, response_sender, .. - } => - async move { - let _timer = metrics.time_validate_from_exhaustive(); - let relay_parent = candidate_receipt.descriptor.relay_parent(); - - let maybe_claim_queue = claim_queue(relay_parent, &mut sender).await; - - let maybe_expected_session_index = - match util::request_session_index_for_child(relay_parent, &mut sender) - .await - .await - { - Ok(Ok(expected_session_index)) => Some(expected_session_index), - _ => None, - }; - - let res = validate_candidate_exhaustive( - maybe_expected_session_index, - validation_host, - validation_data, - validation_code, - candidate_receipt, - pov, - executor_params, - exec_kind, - &metrics, - maybe_claim_queue, - ) - .await; + } => async move { + let _timer = metrics.time_validate_from_exhaustive(); + let relay_parent = candidate_receipt.descriptor.relay_parent(); + + let maybe_claim_queue = claim_queue(relay_parent, &mut sender).await; + + let res = validate_candidate_exhaustive( + get_session_index(&mut sender, relay_parent).await, + validation_host, + validation_data, + validation_code, + candidate_receipt, + pov, + executor_params, + exec_kind, + &metrics, + maybe_claim_queue, + ) + .await; - metrics.on_validation_event(&res); - let _ = response_sender.send(res); - } - .boxed(), + metrics.on_validation_event(&res); + let _ = response_sender.send(res); + } + .boxed(), CandidateValidationMessage::PreCheck { relay_parent, validation_code_hash, @@ -257,7 +247,7 @@ async fn run<Context>( pvf_prepare_workers_hard_max_num, }: Config, ) -> SubsystemResult<()> { - let (validation_host, task) = polkadot_node_core_pvf::start( + let (mut validation_host, task) = polkadot_node_core_pvf::start( polkadot_node_core_pvf::Config::new( artifacts_cache_path, node_version, @@ -282,8 +272,13 @@ async fn run<Context>( comm = ctx.recv().fuse() => { match comm { Ok(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update))) => { - update_active_leaves(ctx.sender(), validation_host.clone(), update.clone()).await; - maybe_prepare_validation(ctx.sender(), keystore.clone(), validation_host.clone(), update, &mut prepare_state).await; + handle_active_leaves_update( + ctx.sender(), + keystore.clone(), + &mut validation_host, + update, + &mut prepare_state, + ).await }, Ok(FromOrchestra::Signal(OverseerSignal::BlockFinalized(..))) => {}, Ok(FromOrchestra::Signal(OverseerSignal::Conclude)) => return Ok(()), @@ -343,17 +338,46 @@ impl Default for PrepareValidationState { } } -async fn maybe_prepare_validation<Sender>( +async fn handle_active_leaves_update<Sender>( sender: &mut Sender, keystore: KeystorePtr, - validation_backend: impl ValidationBackend, + validation_host: &mut impl ValidationBackend, update: ActiveLeavesUpdate, + prepare_state: &mut PrepareValidationState, +) where + Sender: SubsystemSender<ChainApiMessage> + SubsystemSender<RuntimeApiMessage>, +{ + let maybe_session_index = update_active_leaves(sender, validation_host, update.clone()).await; + + if let Some(activated) = update.activated { + let maybe_new_session_index = match (prepare_state.session_index, maybe_session_index) { + (Some(existing_index), Some(new_index)) => + (new_index > existing_index).then_some(new_index), + (None, Some(new_index)) => Some(new_index), + _ => None, + }; + maybe_prepare_validation( + sender, + keystore.clone(), + validation_host, + activated, + prepare_state, + maybe_new_session_index, + ) + .await; + } +} + +async fn maybe_prepare_validation<Sender>( + sender: &mut Sender, + keystore: KeystorePtr, + validation_backend: &mut impl ValidationBackend, + leaf: ActivatedLeaf, state: &mut PrepareValidationState, + new_session_index: Option<SessionIndex>, ) where Sender: SubsystemSender<RuntimeApiMessage>, { - let Some(leaf) = update.activated else { return }; - let new_session_index = new_session_index(sender, state.session_index, leaf.hash).await; if new_session_index.is_some() { state.session_index = new_session_index; state.already_prepared_code_hashes.clear(); @@ -380,16 +404,11 @@ async fn maybe_prepare_validation<Sender>( } } -// Returns the new session index if it is greater than the current one. -async fn new_session_index<Sender>( - sender: &mut Sender, - session_index: Option<SessionIndex>, - relay_parent: Hash, -) -> Option<SessionIndex> +async fn get_session_index<Sender>(sender: &mut Sender, relay_parent: Hash) -> Option<SessionIndex> where Sender: SubsystemSender<RuntimeApiMessage>, { - let Ok(Ok(new_session_index)) = + let Ok(Ok(session_index)) = util::request_session_index_for_child(relay_parent, sender).await.await else { gum::warn!( @@ -400,13 +419,7 @@ where return None }; - session_index.map_or(Some(new_session_index), |index| { - if new_session_index > index { - Some(new_session_index) - } else { - None - } - }) + Some(session_index) } // Returns true if the node is an authority in the next session. @@ -460,7 +473,7 @@ where // Sends PVF with unknown code hashes to the validation host returning the list of code hashes sent. async fn prepare_pvfs_for_backed_candidates<Sender>( sender: &mut Sender, - mut validation_backend: impl ValidationBackend, + validation_backend: &mut impl ValidationBackend, relay_parent: Hash, already_prepared: &HashSet<ValidationCodeHash>, per_block_limit: usize, @@ -557,12 +570,21 @@ where async fn update_active_leaves<Sender>( sender: &mut Sender, - mut validation_backend: impl ValidationBackend, + validation_backend: &mut impl ValidationBackend, update: ActiveLeavesUpdate, -) where +) -> Option<SessionIndex> +where Sender: SubsystemSender<ChainApiMessage> + SubsystemSender<RuntimeApiMessage>, { - let ancestors = get_block_ancestors(sender, update.activated.as_ref().map(|x| x.hash)).await; + let maybe_new_leaf = if let Some(activated) = &update.activated { + get_session_index(sender, activated.hash) + .await + .map(|index| (activated.hash, index)) + } else { + None + }; + + let ancestors = get_block_ancestors(sender, maybe_new_leaf).await; if let Err(err) = validation_backend.update_active_leaves(update, ancestors).await { gum::warn!( target: LOG_TARGET, @@ -570,39 +592,33 @@ async fn update_active_leaves<Sender>( "cannot update active leaves in validation backend", ); }; -} -async fn get_allowed_ancestry_len<Sender>(sender: &mut Sender, relay_parent: Hash) -> Option<usize> -where - Sender: SubsystemSender<ChainApiMessage> + SubsystemSender<RuntimeApiMessage>, -{ - match prospective_parachains_mode(sender, relay_parent).await { - Ok(ProspectiveParachainsMode::Enabled { allowed_ancestry_len, .. }) => - Some(allowed_ancestry_len), - res => { - gum::warn!(target: LOG_TARGET, ?res, "async backing is disabled"); - None - }, - } + maybe_new_leaf.map(|l| l.1) } async fn get_block_ancestors<Sender>( sender: &mut Sender, - maybe_relay_parent: Option<Hash>, + maybe_new_leaf: Option<(Hash, SessionIndex)>, ) -> Vec<Hash> where Sender: SubsystemSender<ChainApiMessage> + SubsystemSender<RuntimeApiMessage>, { - let Some(relay_parent) = maybe_relay_parent else { return vec![] }; - let Some(allowed_ancestry_len) = get_allowed_ancestry_len(sender, relay_parent).await else { - return vec![] - }; + let Some((relay_parent, session_index)) = maybe_new_leaf else { return vec![] }; + let scheduling_lookahead = + match fetch_scheduling_lookahead(relay_parent, session_index, sender).await { + Ok(scheduling_lookahead) => scheduling_lookahead, + res => { + gum::warn!(target: LOG_TARGET, ?res, "Failed to request scheduling lookahead"); + return vec![] + }, + }; let (tx, rx) = oneshot::channel(); sender .send_message(ChainApiMessage::Ancestors { hash: relay_parent, - k: allowed_ancestry_len, + // Subtract 1 from the claim queue length, as it includes current `relay_parent`. + k: scheduling_lookahead.saturating_sub(1) as usize, response_channel: tx, }) .await; @@ -912,15 +928,10 @@ async fn validate_candidate_exhaustive( // invalid. Ok(ValidationResult::Invalid(InvalidCandidate::CommitmentsHashMismatch)) } else { - let core_index = candidate_receipt.descriptor.core_index(); - - match (core_index, exec_kind) { + match exec_kind { // Core selectors are optional for V2 descriptors, but we still check the // descriptor core index. - ( - Some(_core_index), - PvfExecKind::Backing(_) | PvfExecKind::BackingSystemParas(_), - ) => { + PvfExecKind::Backing(_) | PvfExecKind::BackingSystemParas(_) => { let Some(claim_queue) = maybe_claim_queue else { let error = "cannot fetch the claim queue from the runtime"; gum::warn!( @@ -937,9 +948,9 @@ async fn validate_candidate_exhaustive( { gum::warn!( target: LOG_TARGET, - ?err, candidate_hash = ?candidate_receipt.hash(), - "Candidate core index is invalid", + "Candidate core index is invalid: {}", + err ); return Ok(ValidationResult::Invalid( InvalidCandidate::InvalidCoreIndex, @@ -947,7 +958,7 @@ async fn validate_candidate_exhaustive( } }, // No checks for approvals and disputes - (_, _) => {}, + _ => {}, } Ok(ValidationResult::Valid( diff --git a/polkadot/node/core/candidate-validation/src/tests.rs b/polkadot/node/core/candidate-validation/src/tests.rs index 98e34a1cb4c1342216a89b0e4fd6457868d1d35d..ee72daa1f86eb5988c9e8f0aa52549344621454b 100644 --- a/polkadot/node/core/candidate-validation/src/tests.rs +++ b/polkadot/node/core/candidate-validation/src/tests.rs @@ -26,15 +26,16 @@ use futures::executor; use polkadot_node_core_pvf::PrepareError; use polkadot_node_primitives::{BlockData, VALIDATION_CODE_BOMB_LIMIT}; use polkadot_node_subsystem::messages::AllMessages; +use polkadot_node_subsystem_test_helpers::{make_subsystem_context, TestSubsystemContextHandle}; use polkadot_node_subsystem_util::reexports::SubsystemContext; use polkadot_overseer::ActivatedLeaf; use polkadot_primitives::{ vstaging::{ - CandidateDescriptorV2, ClaimQueueOffset, CoreSelector, MutateDescriptorV2, UMPSignal, - UMP_SEPARATOR, + CandidateDescriptorV2, CandidateDescriptorVersion, ClaimQueueOffset, CoreSelector, + MutateDescriptorV2, UMPSignal, UMP_SEPARATOR, }, CandidateDescriptor, CoreIndex, GroupIndex, HeadData, Id as ParaId, OccupiedCoreAssumption, - SessionInfo, UpwardMessage, ValidatorId, + SessionInfo, UpwardMessage, ValidatorId, DEFAULT_SCHEDULING_LOOKAHEAD, }; use polkadot_primitives_test_helpers::{ dummy_collator, dummy_collator_signature, dummy_hash, make_valid_candidate_descriptor, @@ -119,10 +120,7 @@ fn correctly_checks_included_assumption() { .into(); let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context::< - AllMessages, - _, - >(pool.clone()); + let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool.clone()); let (check_fut, check_result) = check_assumption_validation_data( ctx.sender(), @@ -194,10 +192,7 @@ fn correctly_checks_timed_out_assumption() { .into(); let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context::< - AllMessages, - _, - >(pool.clone()); + let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool.clone()); let (check_fut, check_result) = check_assumption_validation_data( ctx.sender(), @@ -267,10 +262,7 @@ fn check_is_bad_request_if_no_validation_data() { .into(); let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context::< - AllMessages, - _, - >(pool.clone()); + let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool.clone()); let (check_fut, check_result) = check_assumption_validation_data( ctx.sender(), @@ -324,10 +316,7 @@ fn check_is_bad_request_if_no_validation_code() { .into(); let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context::< - AllMessages, - _, - >(pool.clone()); + let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool.clone()); let (check_fut, check_result) = check_assumption_validation_data( ctx.sender(), @@ -393,10 +382,7 @@ fn check_does_not_match() { .into(); let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context::< - AllMessages, - _, - >(pool.clone()); + let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool.clone()); let (check_fut, check_result) = check_assumption_validation_data( ctx.sender(), @@ -851,7 +837,7 @@ fn invalid_session_or_core_index() { )) .unwrap(); - // Validation doesn't fail for approvals, core/session index is not checked. + // Validation doesn't fail for disputes, core/session index is not checked. assert_matches!(v, ValidationResult::Valid(outputs, used_validation_data) => { assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1])); assert_eq!(outputs.upward_messages, commitments.upward_messages); @@ -911,6 +897,69 @@ fn invalid_session_or_core_index() { assert_eq!(outputs.hrmp_watermark, 0); assert_eq!(used_validation_data, validation_data); }); + + // Test that a v1 candidate that outputs the core selector UMP signal is invalid. + let descriptor_v1 = make_valid_candidate_descriptor( + ParaId::from(1_u32), + dummy_hash(), + dummy_hash(), + pov.hash(), + validation_code.hash(), + validation_result.head_data.hash(), + dummy_hash(), + sp_keyring::Sr25519Keyring::Ferdie, + ); + let descriptor: CandidateDescriptorV2 = descriptor_v1.into(); + + perform_basic_checks(&descriptor, validation_data.max_pov_size, &pov, &validation_code.hash()) + .unwrap(); + assert_eq!(descriptor.version(), CandidateDescriptorVersion::V1); + let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: commitments.hash() }; + + for exec_kind in + [PvfExecKind::Backing(dummy_hash()), PvfExecKind::BackingSystemParas(dummy_hash())] + { + let result = executor::block_on(validate_candidate_exhaustive( + Some(1), + MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result.clone())), + validation_data.clone(), + validation_code.clone(), + candidate_receipt.clone(), + Arc::new(pov.clone()), + ExecutorParams::default(), + exec_kind, + &Default::default(), + Some(Default::default()), + )) + .unwrap(); + assert_matches!(result, ValidationResult::Invalid(InvalidCandidate::InvalidCoreIndex)); + } + + // Validation doesn't fail for approvals and disputes, core/session index is not checked. + for exec_kind in [PvfExecKind::Approval, PvfExecKind::Dispute] { + let v = executor::block_on(validate_candidate_exhaustive( + Some(1), + MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result.clone())), + validation_data.clone(), + validation_code.clone(), + candidate_receipt.clone(), + Arc::new(pov.clone()), + ExecutorParams::default(), + exec_kind, + &Default::default(), + Default::default(), + )) + .unwrap(); + + assert_matches!(v, ValidationResult::Valid(outputs, used_validation_data) => { + assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1])); + assert_eq!(outputs.upward_messages, commitments.upward_messages); + assert_eq!(outputs.horizontal_messages, Vec::new()); + assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into())); + assert_eq!(outputs.hrmp_watermark, 0); + assert_eq!(used_validation_data, validation_data); + }); + } } #[test] @@ -1330,10 +1379,7 @@ fn candidate_validation_code_mismatch_is_invalid() { let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: Hash::zero() }; let pool = TaskExecutor::new(); - let (_ctx, _ctx_handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context::< - AllMessages, - _, - >(pool.clone()); + let (_ctx, _ctx_handle) = make_subsystem_context::<AllMessages, _>(pool.clone()); let v = executor::block_on(validate_candidate_exhaustive( Some(1), @@ -1407,7 +1453,7 @@ fn compressed_code_works() { ExecutorParams::default(), PvfExecKind::Backing(dummy_hash()), &Default::default(), - Default::default(), + Some(Default::default()), )); assert_matches!(v, Ok(ValidationResult::Valid(_, _))); @@ -1461,10 +1507,7 @@ fn precheck_works() { let validation_code_hash = validation_code.hash(); let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context::< - AllMessages, - _, - >(pool.clone()); + let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool.clone()); let (check_fut, check_result) = precheck_pvf( ctx.sender(), @@ -1521,10 +1564,7 @@ fn precheck_properly_classifies_outcomes() { let validation_code_hash = validation_code.hash(); let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = - polkadot_node_subsystem_test_helpers::make_subsystem_context::<AllMessages, _>( - pool.clone(), - ); + let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool.clone()); let (check_fut, check_result) = precheck_pvf( ctx.sender(), @@ -1614,7 +1654,7 @@ impl ValidationBackend for MockHeadsUp { _update: ActiveLeavesUpdate, _ancestors: Vec<Hash>, ) -> Result<(), String> { - unreachable!() + Ok(()) } } @@ -1691,28 +1731,51 @@ fn dummy_session_info(keys: Vec<Public>) -> SessionInfo { } } +async fn assert_new_active_leaf_messages( + recv_handle: &mut TestSubsystemContextHandle<AllMessages>, + expected_session_index: SessionIndex, +) { + assert_matches!( + recv_handle.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::SessionIndexForChild(tx))) => { + let _ = tx.send(Ok(expected_session_index)); + } + ); + + let lookahead_value = DEFAULT_SCHEDULING_LOOKAHEAD; + assert_matches!( + recv_handle.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::SchedulingLookahead(index, tx))) => { + assert_eq!(index, expected_session_index); + let _ = tx.send(Ok(lookahead_value)); + } + ); + + assert_matches!( + recv_handle.recv().await, + AllMessages::ChainApi(ChainApiMessage::Ancestors {k, response_channel, ..}) => { + assert_eq!(k as u32, lookahead_value - 1); + let _ = response_channel.send(Ok((0..(lookahead_value - 1)).into_iter().map(|i| Hash::from_low_u64_be(i as u64)).collect())); + } + ); +} + #[test] fn maybe_prepare_validation_golden_path() { let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = - polkadot_node_subsystem_test_helpers::make_subsystem_context::<AllMessages, _>(pool); + let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool); let keystore = alice_keystore(); - let backend = MockHeadsUp::default(); + let mut backend = MockHeadsUp::default(); let activated_hash = Hash::random(); let update = dummy_active_leaves_update(activated_hash); let mut state = PrepareValidationState::default(); let check_fut = - maybe_prepare_validation(ctx.sender(), keystore, backend.clone(), update, &mut state); + handle_active_leaves_update(ctx.sender(), keystore, &mut backend, update, &mut state); let test_fut = async move { - assert_matches!( - ctx_handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::SessionIndexForChild(tx))) => { - let _ = tx.send(Ok(1)); - } - ); + assert_new_active_leaf_messages(&mut ctx_handle, 1).await; assert_matches!( ctx_handle.recv().await, @@ -1771,11 +1834,10 @@ fn maybe_prepare_validation_golden_path() { #[test] fn maybe_prepare_validation_checkes_authority_once_per_session() { let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = - polkadot_node_subsystem_test_helpers::make_subsystem_context::<AllMessages, _>(pool); + let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool); let keystore = alice_keystore(); - let backend = MockHeadsUp::default(); + let mut backend = MockHeadsUp::default(); let activated_hash = Hash::random(); let update = dummy_active_leaves_update(activated_hash); let mut state = PrepareValidationState { @@ -1785,16 +1847,9 @@ fn maybe_prepare_validation_checkes_authority_once_per_session() { }; let check_fut = - maybe_prepare_validation(ctx.sender(), keystore, backend.clone(), update, &mut state); + handle_active_leaves_update(ctx.sender(), keystore, &mut backend, update, &mut state); - let test_fut = async move { - assert_matches!( - ctx_handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::SessionIndexForChild(tx))) => { - let _ = tx.send(Ok(1)); - } - ); - }; + let test_fut = assert_new_active_leaf_messages(&mut ctx_handle, 1); let test_fut = future::join(test_fut, check_fut); executor::block_on(test_fut); @@ -1807,11 +1862,10 @@ fn maybe_prepare_validation_checkes_authority_once_per_session() { #[test] fn maybe_prepare_validation_resets_state_on_a_new_session() { let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = - polkadot_node_subsystem_test_helpers::make_subsystem_context::<AllMessages, _>(pool); + let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool); let keystore = alice_keystore(); - let backend = MockHeadsUp::default(); + let mut backend = MockHeadsUp::default(); let activated_hash = Hash::random(); let update = dummy_active_leaves_update(activated_hash); let mut state = PrepareValidationState { @@ -1822,15 +1876,10 @@ fn maybe_prepare_validation_resets_state_on_a_new_session() { }; let check_fut = - maybe_prepare_validation(ctx.sender(), keystore, backend.clone(), update, &mut state); + handle_active_leaves_update(ctx.sender(), keystore, &mut backend, update, &mut state); let test_fut = async move { - assert_matches!( - ctx_handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::SessionIndexForChild(tx))) => { - let _ = tx.send(Ok(2)); - } - ); + assert_new_active_leaf_messages(&mut ctx_handle, 2).await; assert_matches!( ctx_handle.recv().await, @@ -1860,26 +1909,18 @@ fn maybe_prepare_validation_resets_state_on_a_new_session() { #[test] fn maybe_prepare_validation_does_not_prepare_pvfs_if_no_new_session_and_not_a_validator() { let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = - polkadot_node_subsystem_test_helpers::make_subsystem_context::<AllMessages, _>(pool); + let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool); let keystore = alice_keystore(); - let backend = MockHeadsUp::default(); + let mut backend = MockHeadsUp::default(); let activated_hash = Hash::random(); let update = dummy_active_leaves_update(activated_hash); let mut state = PrepareValidationState { session_index: Some(1), ..Default::default() }; let check_fut = - maybe_prepare_validation(ctx.sender(), keystore, backend.clone(), update, &mut state); + handle_active_leaves_update(ctx.sender(), keystore, &mut backend, update, &mut state); - let test_fut = async move { - assert_matches!( - ctx_handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::SessionIndexForChild(tx))) => { - let _ = tx.send(Ok(1)); - } - ); - }; + let test_fut = assert_new_active_leaf_messages(&mut ctx_handle, 1); let test_fut = future::join(test_fut, check_fut); executor::block_on(test_fut); @@ -1892,11 +1933,10 @@ fn maybe_prepare_validation_does_not_prepare_pvfs_if_no_new_session_and_not_a_va #[test] fn maybe_prepare_validation_does_not_prepare_pvfs_if_no_new_session_but_a_validator() { let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = - polkadot_node_subsystem_test_helpers::make_subsystem_context::<AllMessages, _>(pool); + let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool); let keystore = alice_keystore(); - let backend = MockHeadsUp::default(); + let mut backend = MockHeadsUp::default(); let activated_hash = Hash::random(); let update = dummy_active_leaves_update(activated_hash); let mut state = PrepareValidationState { @@ -1906,15 +1946,10 @@ fn maybe_prepare_validation_does_not_prepare_pvfs_if_no_new_session_but_a_valida }; let check_fut = - maybe_prepare_validation(ctx.sender(), keystore, backend.clone(), update, &mut state); + handle_active_leaves_update(ctx.sender(), keystore, &mut backend, update, &mut state); let test_fut = async move { - assert_matches!( - ctx_handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::SessionIndexForChild(tx))) => { - let _ = tx.send(Ok(1)); - } - ); + assert_new_active_leaf_messages(&mut ctx_handle, 1).await; assert_matches!( ctx_handle.recv().await, @@ -1958,25 +1993,19 @@ fn maybe_prepare_validation_does_not_prepare_pvfs_if_no_new_session_but_a_valida #[test] fn maybe_prepare_validation_does_not_prepare_pvfs_if_not_a_validator_in_the_next_session() { let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = - polkadot_node_subsystem_test_helpers::make_subsystem_context::<AllMessages, _>(pool); + let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool); let keystore = alice_keystore(); - let backend = MockHeadsUp::default(); + let mut backend = MockHeadsUp::default(); let activated_hash = Hash::random(); let update = dummy_active_leaves_update(activated_hash); let mut state = PrepareValidationState::default(); let check_fut = - maybe_prepare_validation(ctx.sender(), keystore, backend.clone(), update, &mut state); + handle_active_leaves_update(ctx.sender(), keystore, &mut backend, update, &mut state); let test_fut = async move { - assert_matches!( - ctx_handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::SessionIndexForChild(tx))) => { - let _ = tx.send(Ok(1)); - } - ); + assert_new_active_leaf_messages(&mut ctx_handle, 1).await; assert_matches!( ctx_handle.recv().await, @@ -2005,25 +2034,19 @@ fn maybe_prepare_validation_does_not_prepare_pvfs_if_not_a_validator_in_the_next #[test] fn maybe_prepare_validation_does_not_prepare_pvfs_if_a_validator_in_the_current_session() { let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = - polkadot_node_subsystem_test_helpers::make_subsystem_context::<AllMessages, _>(pool); + let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool); let keystore = alice_keystore(); - let backend = MockHeadsUp::default(); + let mut backend = MockHeadsUp::default(); let activated_hash = Hash::random(); let update = dummy_active_leaves_update(activated_hash); let mut state = PrepareValidationState::default(); let check_fut = - maybe_prepare_validation(ctx.sender(), keystore, backend.clone(), update, &mut state); + handle_active_leaves_update(ctx.sender(), keystore, &mut backend, update, &mut state); let test_fut = async move { - assert_matches!( - ctx_handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::SessionIndexForChild(tx))) => { - let _ = tx.send(Ok(1)); - } - ); + assert_new_active_leaf_messages(&mut ctx_handle, 1).await; assert_matches!( ctx_handle.recv().await, @@ -2052,25 +2075,19 @@ fn maybe_prepare_validation_does_not_prepare_pvfs_if_a_validator_in_the_current_ #[test] fn maybe_prepare_validation_prepares_a_limited_number_of_pvfs() { let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = - polkadot_node_subsystem_test_helpers::make_subsystem_context::<AllMessages, _>(pool); + let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool); let keystore = alice_keystore(); - let backend = MockHeadsUp::default(); + let mut backend = MockHeadsUp::default(); let activated_hash = Hash::random(); let update = dummy_active_leaves_update(activated_hash); let mut state = PrepareValidationState { per_block_limit: 2, ..Default::default() }; let check_fut = - maybe_prepare_validation(ctx.sender(), keystore, backend.clone(), update, &mut state); + handle_active_leaves_update(ctx.sender(), keystore, &mut backend, update, &mut state); let test_fut = async move { - assert_matches!( - ctx_handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::SessionIndexForChild(tx))) => { - let _ = tx.send(Ok(1)); - } - ); + assert_new_active_leaf_messages(&mut ctx_handle, 1).await; assert_matches!( ctx_handle.recv().await, @@ -2143,11 +2160,10 @@ fn maybe_prepare_validation_prepares_a_limited_number_of_pvfs() { #[test] fn maybe_prepare_validation_does_not_prepare_already_prepared_pvfs() { let pool = TaskExecutor::new(); - let (mut ctx, mut ctx_handle) = - polkadot_node_subsystem_test_helpers::make_subsystem_context::<AllMessages, _>(pool); + let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool); let keystore = alice_keystore(); - let backend = MockHeadsUp::default(); + let mut backend = MockHeadsUp::default(); let activated_hash = Hash::random(); let update = dummy_active_leaves_update(activated_hash); let mut state = PrepareValidationState { @@ -2161,15 +2177,10 @@ fn maybe_prepare_validation_does_not_prepare_already_prepared_pvfs() { }; let check_fut = - maybe_prepare_validation(ctx.sender(), keystore, backend.clone(), update, &mut state); + handle_active_leaves_update(ctx.sender(), keystore, &mut backend, update, &mut state); let test_fut = async move { - assert_matches!( - ctx_handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::SessionIndexForChild(tx))) => { - let _ = tx.send(Ok(1)); - } - ); + assert_new_active_leaf_messages(&mut ctx_handle, 1).await; assert_matches!( ctx_handle.recv().await, diff --git a/polkadot/node/core/chain-api/Cargo.toml b/polkadot/node/core/chain-api/Cargo.toml index a8e911e0c5c9586c31109462e1a9527c9d8246f2..0689a41233c7913c56ea8034e91921a6b7a97696 100644 --- a/polkadot/node/core/chain-api/Cargo.toml +++ b/polkadot/node/core/chain-api/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "The Chain API subsystem provides access to chain related utility functions like block number to hash conversions." +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -19,11 +21,11 @@ sc-client-api = { workspace = true, default-features = true } sc-consensus-babe = { workspace = true, default-features = true } [dev-dependencies] +codec = { workspace = true, default-features = true } futures = { features = ["thread-pool"], workspace = true } maplit = { workspace = true } -codec = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } -sp-core = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } diff --git a/polkadot/node/core/chain-selection/Cargo.toml b/polkadot/node/core/chain-selection/Cargo.toml index 755d5cadeaaf388a4e21eff79699ae06368e8010..e425b9f862a5e691c4e2f963ed7b410366a4abf8 100644 --- a/polkadot/node/core/chain-selection/Cargo.toml +++ b/polkadot/node/core/chain-selection/Cargo.toml @@ -5,25 +5,27 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +codec = { workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } gum = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } +kvdb = { workspace = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } -kvdb = { workspace = true } +polkadot-primitives = { workspace = true, default-features = true } thiserror = { workspace = true } -codec = { workspace = true, default-features = true } [dev-dependencies] -polkadot-node-subsystem-test-helpers = { workspace = true } -sp-core = { workspace = true, default-features = true } -parking_lot = { workspace = true, default-features = true } assert_matches = { workspace = true } kvdb-memorydb = { workspace = true } +parking_lot = { workspace = true, default-features = true } +polkadot-node-subsystem-test-helpers = { workspace = true } +sp-core = { workspace = true, default-features = true } diff --git a/polkadot/node/core/dispute-coordinator/Cargo.toml b/polkadot/node/core/dispute-coordinator/Cargo.toml index 344b66af1933c1a544759186050a9bcde0a89380..6eb3020a0432f2b5f7b97e8ff7f05dfb7ceabb0e 100644 --- a/polkadot/node/core/dispute-coordinator/Cargo.toml +++ b/polkadot/node/core/dispute-coordinator/Cargo.toml @@ -5,39 +5,41 @@ description = "The node-side components that participate in disputes" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +codec = { workspace = true, default-features = true } +fatality = { workspace = true } futures = { workspace = true } gum = { workspace = true, default-features = true } -codec = { workspace = true, default-features = true } kvdb = { workspace = true } -thiserror = { workspace = true } schnellru = { workspace = true } -fatality = { workspace = true } +thiserror = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } sc-keystore = { workspace = true, default-features = true } [dev-dependencies] +assert_matches = { workspace = true } +futures-timer = { workspace = true } kvdb-memorydb = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } -sp-keyring = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -assert_matches = { workspace = true } +polkadot-primitives = { workspace = true, features = ["test"] } polkadot-primitives-test-helpers = { workspace = true } -futures-timer = { workspace = true } sp-application-crypto = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, features = ["test"] } [features] # If not enabled, the dispute coordinator will do nothing. diff --git a/polkadot/node/core/parachains-inherent/Cargo.toml b/polkadot/node/core/parachains-inherent/Cargo.toml index 1e4953f40d0bd87bd5ef69bf3b91b66935e90fdc..264b8da2b44d3d25903cc61edfffbdd20c960885 100644 --- a/polkadot/node/core/parachains-inherent/Cargo.toml +++ b/polkadot/node/core/parachains-inherent/Cargo.toml @@ -5,18 +5,20 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Parachains inherent data provider for Polkadot node" +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +async-trait = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } gum = { workspace = true, default-features = true } -thiserror = { workspace = true } -async-trait = { workspace = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } +thiserror = { workspace = true } diff --git a/polkadot/node/core/prospective-parachains/Cargo.toml b/polkadot/node/core/prospective-parachains/Cargo.toml index 5629e4ef7fbe25af6e769d195829819becb35831..0d0ede8d1d9bf4dd588be92893d26062617dfeb2 100644 --- a/polkadot/node/core/prospective-parachains/Cargo.toml +++ b/polkadot/node/core/prospective-parachains/Cargo.toml @@ -5,26 +5,28 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "The Prospective Parachains subsystem. Tracks and handles prospective parachain fragments." +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +fatality = { workspace = true } futures = { workspace = true } gum = { workspace = true, default-features = true } thiserror = { workspace = true } -fatality = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } [dev-dependencies] assert_matches = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } -polkadot-primitives-test-helpers = { workspace = true } polkadot-primitives = { workspace = true, features = ["test"] } -sp-tracing = { workspace = true } -sp-core = { workspace = true, default-features = true } +polkadot-primitives-test-helpers = { workspace = true } rand = { workspace = true } rstest = { workspace = true } +sp-core = { workspace = true, default-features = true } +sp-tracing = { workspace = true } diff --git a/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs b/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs index ded0a3ab73b2d79d31e64e03580c1facb8913708..d92c98623823d1baebdef6a890eacc8f271362dc 100644 --- a/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs +++ b/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs @@ -32,7 +32,7 @@ //! The best chain contains all the candidates pending availability and a subsequent chain //! of candidates that have reached the backing quorum and are better than any other backable forks //! according to the fork selection rule (more on this rule later). It has a length of size at most -//! `max_candidate_depth + 1`. +//! `num_of_pending_candidates + num_of_assigned_cores_for_para`. //! //! The unconnected storage keeps a record of seconded/backable candidates that may be //! added to the best chain in the future. @@ -100,13 +100,10 @@ //! bounded. This means that higher-level code needs to be selective about limiting the amount of //! candidates that are considered. //! -//! Practically speaking, the collator-protocol will not allow more than `max_candidate_depth + 1` -//! collations to be fetched at a relay parent and statement-distribution will not allow more than -//! `max_candidate_depth + 1` seconded candidates at a relay parent per each validator in the -//! backing group. Considering the `allowed_ancestry_len` configuration value, the number of -//! candidates in a `FragmentChain` (including its unconnected storage) should not exceed: -//! -//! `allowed_ancestry_len * (max_candidate_depth + 1) * backing_group_size`. +//! Practically speaking, the collator-protocol will limit the number of fetched collations per +//! core, to the number of claim queue assignments for the paraid on that core. +//! Statement-distribution will not allow more than `scheduler_params.lookahead` seconded candidates +//! at a relay parent per each validator in the backing group. //! //! The code in this module is not designed for speed or efficiency, but conceptual simplicity. //! Our assumption is that the amount of candidates and parachains we consider will be reasonably @@ -132,8 +129,8 @@ use std::{ use super::LOG_TARGET; use polkadot_node_subsystem::messages::Ancestors; use polkadot_node_subsystem_util::inclusion_emulator::{ - self, ConstraintModifications, Constraints, Fragment, HypotheticalOrConcreteCandidate, - ProspectiveCandidate, RelayChainBlockInfo, + self, validate_commitments, ConstraintModifications, Constraints, Fragment, + HypotheticalOrConcreteCandidate, ProspectiveCandidate, RelayChainBlockInfo, }; use polkadot_primitives::{ vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, BlockNumber, @@ -453,8 +450,8 @@ pub(crate) struct Scope { pending_availability: Vec<PendingAvailability>, /// The base constraints derived from the latest included candidate. base_constraints: Constraints, - /// Equal to `max_candidate_depth`. - max_depth: usize, + /// Maximum length of the best backable chain (including candidates pending availability). + max_backable_len: usize, } /// An error variant indicating that ancestors provided to a scope @@ -474,7 +471,8 @@ pub(crate) struct UnexpectedAncestor { impl Scope { /// Define a new [`Scope`]. /// - /// All arguments are straightforward except the ancestors. + /// `max_backable_len` should be the maximum length of the best backable chain (excluding + /// pending availability candidates). /// /// Ancestors should be in reverse order, starting with the parent /// of the `relay_parent`, and proceeding backwards in block number @@ -492,7 +490,7 @@ impl Scope { relay_parent: RelayChainBlockInfo, base_constraints: Constraints, pending_availability: Vec<PendingAvailability>, - max_depth: usize, + max_backable_len: usize, ancestors: impl IntoIterator<Item = RelayChainBlockInfo>, ) -> Result<Self, UnexpectedAncestor> { let mut ancestors_map = BTreeMap::new(); @@ -517,8 +515,8 @@ impl Scope { Ok(Scope { relay_parent, base_constraints, + max_backable_len: max_backable_len + pending_availability.len(), pending_availability, - max_depth, ancestors: ancestors_map, ancestors_by_hash, }) @@ -1052,7 +1050,7 @@ impl FragmentChain { // Try seeing if the parent candidate is in the current chain or if it is the latest // included candidate. If so, get the constraints the candidate must satisfy. - let (constraints, maybe_min_relay_parent_number) = + let (is_unconnected, constraints, maybe_min_relay_parent_number) = if let Some(parent_candidate) = self.best_chain.by_output_head.get(&parent_head_hash) { let Some(parent_candidate) = self.best_chain.chain.iter().find(|c| &c.candidate_hash == parent_candidate) @@ -1062,6 +1060,7 @@ impl FragmentChain { }; ( + false, self.scope .base_constraints .apply_modifications(&parent_candidate.cumulative_modifications) @@ -1070,11 +1069,10 @@ impl FragmentChain { ) } else if self.scope.base_constraints.required_parent.hash() == parent_head_hash { // It builds on the latest included candidate. - (self.scope.base_constraints.clone(), None) + (false, self.scope.base_constraints.clone(), None) } else { - // If the parent is not yet part of the chain, there's nothing else we can check for - // now. - return Ok(()) + // The parent is not yet part of the chain + (true, self.scope.base_constraints.clone(), None) }; // Check for cycles or invalid tree transitions. @@ -1088,6 +1086,17 @@ impl FragmentChain { candidate.persisted_validation_data(), candidate.validation_code_hash(), ) { + if is_unconnected { + // If the parent is not yet part of the chain, we can check the commitments only + // if we have the full candidate. + return validate_commitments( + &self.scope.base_constraints, + &relay_parent, + commitments, + &validation_code_hash, + ) + .map_err(Error::CheckAgainstConstraints) + } Fragment::check_against_constraints( &relay_parent, &constraints, @@ -1181,7 +1190,7 @@ impl FragmentChain { let Some(mut earliest_rp) = self.earliest_relay_parent() else { return }; loop { - if self.best_chain.chain.len() > self.scope.max_depth { + if self.best_chain.chain.len() >= self.scope.max_backable_len { break; } diff --git a/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs b/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs index 624dd74132c19f5ccc07ef533019e598852773dd..6bda09ecc26d4cf215b093516fcdb8f6cf112952 100644 --- a/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs +++ b/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs @@ -34,6 +34,7 @@ fn make_constraints( min_relay_parent_number, max_pov_size: 1_000_000, max_code_size: 1_000_000, + max_head_data_size: 20480, ump_remaining: 10, ump_remaining_bytes: 1_000, max_ump_num_per_candidate: 10, @@ -115,7 +116,7 @@ fn scope_rejects_ancestors_that_skip_blocks() { storage_root: Hash::repeat_byte(69), }]; - let max_depth = 2; + let max_depth = 3; let base_constraints = make_constraints(8, vec![8, 9], vec![1, 2, 3].into()); let pending_availability = Vec::new(); @@ -145,7 +146,7 @@ fn scope_rejects_ancestor_for_0_block() { storage_root: Hash::repeat_byte(69), }]; - let max_depth = 2; + let max_depth = 3; let base_constraints = make_constraints(0, vec![], vec![1, 2, 3].into()); let pending_availability = Vec::new(); @@ -187,7 +188,7 @@ fn scope_only_takes_ancestors_up_to_min() { }, ]; - let max_depth = 2; + let max_depth = 3; let base_constraints = make_constraints(3, vec![2], vec![1, 2, 3].into()); let pending_availability = Vec::new(); @@ -230,7 +231,7 @@ fn scope_rejects_unordered_ancestors() { }, ]; - let max_depth = 2; + let max_depth = 3; let base_constraints = make_constraints(0, vec![2], vec![1, 2, 3].into()); let pending_availability = Vec::new(); @@ -496,7 +497,7 @@ fn test_populate_and_check_potential() { relay_parent_z_info.clone(), wrong_constraints.clone(), vec![], - 4, + 5, ancestors.clone(), ) .unwrap(); @@ -529,7 +530,7 @@ fn test_populate_and_check_potential() { // Various depths { - // Depth is 0, only allows one candidate, but the others will be kept as potential. + // Depth is 0, doesn't allow any candidate, but the others will be kept as potential. let scope = Scope::with_ancestors( relay_parent_z_info.clone(), base_constraints.clone(), @@ -543,6 +544,27 @@ fn test_populate_and_check_potential() { assert!(chain.can_add_candidate_as_potential(&candidate_b_entry).is_ok()); assert!(chain.can_add_candidate_as_potential(&candidate_c_entry).is_ok()); + let chain = populate_chain_from_previous_storage(&scope, &storage); + assert!(chain.best_chain_vec().is_empty()); + assert_eq!( + chain.unconnected().map(|c| c.candidate_hash).collect::<HashSet<_>>(), + [candidate_a_hash, candidate_b_hash, candidate_c_hash].into_iter().collect() + ); + + // Depth is 1, only allows one candidate, but the others will be kept as potential. + let scope = Scope::with_ancestors( + relay_parent_z_info.clone(), + base_constraints.clone(), + vec![], + 1, + ancestors.clone(), + ) + .unwrap(); + let chain = FragmentChain::init(scope.clone(), CandidateStorage::default()); + assert!(chain.can_add_candidate_as_potential(&candidate_a_entry).is_ok()); + assert!(chain.can_add_candidate_as_potential(&candidate_b_entry).is_ok()); + assert!(chain.can_add_candidate_as_potential(&candidate_c_entry).is_ok()); + let chain = populate_chain_from_previous_storage(&scope, &storage); assert_eq!(chain.best_chain_vec(), vec![candidate_a_hash]); assert_eq!( @@ -550,12 +572,12 @@ fn test_populate_and_check_potential() { [candidate_b_hash, candidate_c_hash].into_iter().collect() ); - // depth is 1, allows two candidates + // depth is 2, allows two candidates let scope = Scope::with_ancestors( relay_parent_z_info.clone(), base_constraints.clone(), vec![], - 1, + 2, ancestors.clone(), ) .unwrap(); @@ -571,8 +593,8 @@ fn test_populate_and_check_potential() { [candidate_c_hash].into_iter().collect() ); - // depth is larger than 2, allows all three candidates - for depth in 2..6 { + // depth is at least 3, allows all three candidates + for depth in 3..6 { let scope = Scope::with_ancestors( relay_parent_z_info.clone(), base_constraints.clone(), @@ -604,7 +626,7 @@ fn test_populate_and_check_potential() { relay_parent_z_info.clone(), base_constraints.clone(), vec![], - 4, + 5, ancestors_without_x, ) .unwrap(); @@ -627,7 +649,7 @@ fn test_populate_and_check_potential() { relay_parent_z_info.clone(), base_constraints.clone(), vec![], - 4, + 5, vec![], ) .unwrap(); @@ -673,7 +695,7 @@ fn test_populate_and_check_potential() { relay_parent_z_info.clone(), base_constraints.clone(), vec![], - 4, + 5, ancestors.clone(), ) .unwrap(); @@ -715,7 +737,7 @@ fn test_populate_and_check_potential() { relay_parent_z_info.clone(), base_constraints.clone(), vec![], - 4, + 5, ancestors.clone(), ) .unwrap(); @@ -757,7 +779,7 @@ fn test_populate_and_check_potential() { relay_parent_z_info.clone(), base_constraints.clone(), vec![], - 4, + 5, ancestors.clone(), ) .unwrap(); @@ -986,7 +1008,7 @@ fn test_populate_and_check_potential() { relay_parent_z_info.clone(), base_constraints.clone(), vec![], - 2, + 3, ancestors.clone(), ) .unwrap(); @@ -1301,7 +1323,7 @@ fn test_populate_and_check_potential() { relay_parent: relay_parent_z_info.clone(), }, ], - 2, + 0, ancestors.clone(), ) .unwrap(); @@ -1326,7 +1348,7 @@ fn test_populate_and_check_potential() { relay_parent_z_info.clone(), base_constraints.clone(), vec![], - 2, + 3, ancestors.clone(), ) .unwrap(); @@ -1355,7 +1377,7 @@ fn test_populate_and_check_potential() { fn test_find_ancestor_path_and_find_backable_chain_empty_best_chain() { let relay_parent = Hash::repeat_byte(1); let required_parent: HeadData = vec![0xff].into(); - let max_depth = 10; + let max_depth = 11; // Empty chain let base_constraints = make_constraints(0, vec![0], required_parent.clone()); @@ -1382,7 +1404,7 @@ fn test_find_ancestor_path_and_find_backable_chain() { let para_id = ParaId::from(5u32); let relay_parent = Hash::repeat_byte(1); let required_parent: HeadData = vec![0xff].into(); - let max_depth = 5; + let max_depth = 6; let relay_parent_number = 0; let relay_parent_storage_root = Hash::zero(); @@ -1567,7 +1589,7 @@ fn test_find_ancestor_path_and_find_backable_chain() { candidate_hash: candidates[3], relay_parent: relay_parent_info, }], - max_depth, + max_depth - 1, vec![], ) .unwrap(); diff --git a/polkadot/node/core/prospective-parachains/src/lib.rs b/polkadot/node/core/prospective-parachains/src/lib.rs index 92aea8509f8c49805ece2accc2406b67c414b3d7..9b2006ee988a256c7b74aa51cb21ee88daf3f336 100644 --- a/polkadot/node/core/prospective-parachains/src/lib.rs +++ b/polkadot/node/core/prospective-parachains/src/lib.rs @@ -28,7 +28,7 @@ #![deny(unused_crate_dependencies)] -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap, HashSet}; use fragment_chain::CandidateStorage; use futures::{channel::oneshot, prelude::*}; @@ -45,15 +45,13 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_util::{ backing_implicit_view::{BlockInfoProspectiveParachains as BlockInfo, View as ImplicitView}, inclusion_emulator::{Constraints, RelayChainBlockInfo}, + request_backing_constraints, request_candidates_pending_availability, request_session_index_for_child, - runtime::{fetch_claim_queue, prospective_parachains_mode, ProspectiveParachainsMode}, + runtime::{fetch_claim_queue, fetch_scheduling_lookahead}, }; use polkadot_primitives::{ - vstaging::{ - async_backing::CandidatePendingAvailability, - CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - }, - BlockNumber, CandidateHash, Hash, HeadData, Header, Id as ParaId, PersistedValidationData, + vstaging::{transpose_claim_queue, CommittedCandidateReceiptV2 as CommittedCandidateReceipt}, + BlockNumber, CandidateHash, Hash, Header, Id as ParaId, PersistedValidationData, }; use crate::{ @@ -214,23 +212,8 @@ async fn handle_active_leaves_update<Context>( let hash = activated.hash; - let mode = prospective_parachains_mode(ctx.sender(), hash) - .await - .map_err(JfyiError::Runtime)?; - - let ProspectiveParachainsMode::Enabled { max_candidate_depth, allowed_ancestry_len } = mode - else { - gum::trace!( - target: LOG_TARGET, - block_hash = ?hash, - "Skipping leaf activation since async backing is disabled" - ); - - // Not a part of any allowed ancestry. - return Ok(()) - }; - - let scheduled_paras = fetch_upcoming_paras(ctx, hash).await?; + let transposed_claim_queue = + transpose_claim_queue(fetch_claim_queue(ctx.sender(), hash).await?.0); let block_info = match fetch_block_info(ctx, &mut temp_header_cache, hash).await? { None => { @@ -248,17 +231,27 @@ async fn handle_active_leaves_update<Context>( Some(info) => info, }; + let session_index = request_session_index_for_child(hash, ctx.sender()) + .await + .await + .map_err(JfyiError::RuntimeApiRequestCanceled)??; + let ancestry_len = fetch_scheduling_lookahead(hash, session_index, ctx.sender()) + .await? + .saturating_sub(1); + let ancestry = - fetch_ancestry(ctx, &mut temp_header_cache, hash, allowed_ancestry_len).await?; + fetch_ancestry(ctx, &mut temp_header_cache, hash, ancestry_len as usize, session_index) + .await?; let prev_fragment_chains = ancestry.first().and_then(|prev_leaf| view.get_fragment_chains(&prev_leaf.hash)); let mut fragment_chains = HashMap::new(); - for para in scheduled_paras { + for (para, claims_by_depth) in transposed_claim_queue.iter() { // Find constraints and pending availability candidates. - let backing_state = fetch_backing_state(ctx, hash, para).await?; - let Some((constraints, pending_availability)) = backing_state else { + let Some((constraints, pending_availability)) = + fetch_backing_constraints_and_candidates(ctx, hash, *para).await? + else { // This indicates a runtime conflict of some kind. gum::debug!( target: LOG_TARGET, @@ -273,7 +266,7 @@ async fn handle_active_leaves_update<Context>( let pending_availability = preprocess_candidates_pending_availability( ctx, &mut temp_header_cache, - constraints.required_parent.clone(), + &constraints, pending_availability, ) .await?; @@ -307,11 +300,13 @@ async fn handle_active_leaves_update<Context>( compact_pending.push(c.compact); } + let max_backable_chain_len = + claims_by_depth.values().flatten().collect::<BTreeSet<_>>().len(); let scope = match FragmentChainScope::with_ancestors( block_info.clone().into(), constraints, compact_pending, - max_candidate_depth, + max_backable_chain_len, ancestry .iter() .map(|a| RelayChainBlockInfo::from(a.clone())) @@ -322,7 +317,7 @@ async fn handle_active_leaves_update<Context>( gum::warn!( target: LOG_TARGET, para_id = ?para, - max_candidate_depth, + max_backable_chain_len, ?ancestry, leaf = ?hash, "Relay chain ancestors have wrong order: {:?}", @@ -336,6 +331,7 @@ async fn handle_active_leaves_update<Context>( target: LOG_TARGET, relay_parent = ?hash, min_relay_parent = scope.earliest_relay_parent().number, + max_backable_chain_len, para_id = ?para, ancestors = ?ancestry, "Creating fragment chain" @@ -360,7 +356,7 @@ async fn handle_active_leaves_update<Context>( // If we know the previous fragment chain, use that for further populating the fragment // chain. if let Some(prev_fragment_chain) = - prev_fragment_chains.and_then(|chains| chains.get(¶)) + prev_fragment_chains.and_then(|chains| chains.get(para)) { chain.populate_from_previous(prev_fragment_chain); } @@ -382,7 +378,7 @@ async fn handle_active_leaves_update<Context>( chain.unconnected().map(|candidate| candidate.hash()).collect::<Vec<_>>() ); - fragment_chains.insert(para, chain); + fragment_chains.insert(*para, chain); } view.per_relay_parent.insert(hash, RelayBlockViewData { fragment_chains }); @@ -445,22 +441,23 @@ struct ImportablePendingAvailability { async fn preprocess_candidates_pending_availability<Context>( ctx: &mut Context, cache: &mut HashMap<Hash, Header>, - required_parent: HeadData, - pending_availability: Vec<CandidatePendingAvailability>, + constraints: &Constraints, + pending_availability: Vec<CommittedCandidateReceipt>, ) -> JfyiErrorResult<Vec<ImportablePendingAvailability>> { - let mut required_parent = required_parent; + let mut required_parent = constraints.required_parent.clone(); let mut importable = Vec::new(); let expected_count = pending_availability.len(); for (i, pending) in pending_availability.into_iter().enumerate() { + let candidate_hash = pending.hash(); let Some(relay_parent) = fetch_block_info(ctx, cache, pending.descriptor.relay_parent()).await? else { let para_id = pending.descriptor.para_id(); gum::debug!( target: LOG_TARGET, - ?pending.candidate_hash, + ?candidate_hash, ?para_id, index = ?i, ?expected_count, @@ -478,12 +475,12 @@ async fn preprocess_candidates_pending_availability<Context>( }, persisted_validation_data: PersistedValidationData { parent_head: required_parent, - max_pov_size: pending.max_pov_size, + max_pov_size: constraints.max_pov_size as _, relay_parent_number: relay_parent.number, relay_parent_storage_root: relay_parent.storage_root, }, compact: fragment_chain::PendingAvailability { - candidate_hash: pending.candidate_hash, + candidate_hash, relay_parent: relay_parent.into(), }, }); @@ -883,7 +880,7 @@ async fn fetch_backing_state<Context>( ctx: &mut Context, relay_parent: Hash, para_id: ParaId, -) -> JfyiErrorResult<Option<(Constraints, Vec<CandidatePendingAvailability>)>> { +) -> JfyiErrorResult<Option<(Constraints, Vec<CommittedCandidateReceipt>)>> { let (tx, rx) = oneshot::channel(); ctx.send_message(RuntimeApiMessage::Request( relay_parent, @@ -891,61 +888,63 @@ async fn fetch_backing_state<Context>( )) .await; - Ok(rx - .await - .map_err(JfyiError::RuntimeApiRequestCanceled)?? - .map(|s| (From::from(s.constraints), s.pending_availability))) + Ok(rx.await.map_err(JfyiError::RuntimeApiRequestCanceled)??.map(|s| { + ( + From::from(s.constraints), + s.pending_availability + .into_iter() + .map(|c| CommittedCandidateReceipt { + descriptor: c.descriptor, + commitments: c.commitments, + }) + .collect(), + ) + })) } #[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)] -async fn fetch_upcoming_paras<Context>( +async fn fetch_backing_constraints_and_candidates<Context>( ctx: &mut Context, relay_parent: Hash, -) -> JfyiErrorResult<HashSet<ParaId>> { - Ok(match fetch_claim_queue(ctx.sender(), relay_parent).await? { - Some(claim_queue) => { - // Runtime supports claim queue - use it - claim_queue - .iter_all_claims() - .flat_map(|(_, paras)| paras.into_iter()) - .copied() - .collect() - }, - None => { - // fallback to availability cores - remove this branch once claim queue is released - // everywhere - let (tx, rx) = oneshot::channel(); - ctx.send_message(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::AvailabilityCores(tx), - )) - .await; - - let cores = rx.await.map_err(JfyiError::RuntimeApiRequestCanceled)??; - - let mut upcoming = HashSet::with_capacity(cores.len()); - for core in cores { - match core { - CoreState::Occupied(occupied) => { - // core sharing won't work optimally with this branch because the collations - // can't be prepared in advance. - if let Some(next_up_on_available) = occupied.next_up_on_available { - upcoming.insert(next_up_on_available.para_id); - } - if let Some(next_up_on_time_out) = occupied.next_up_on_time_out { - upcoming.insert(next_up_on_time_out.para_id); - } - }, - CoreState::Scheduled(scheduled) => { - upcoming.insert(scheduled.para_id); - }, - CoreState::Free => {}, - } - } + para_id: ParaId, +) -> JfyiErrorResult<Option<(Constraints, Vec<CommittedCandidateReceipt>)>> { + match fetch_backing_constraints_and_candidates_inner(ctx, relay_parent, para_id).await { + Err(error) => { + gum::debug!( + target: LOG_TARGET, + ?para_id, + ?relay_parent, + ?error, + "Failed to get constraints and candidates pending availability." + ); - upcoming + // Fallback to backing state. + fetch_backing_state(ctx, relay_parent, para_id).await }, - }) + Ok(maybe_constraints_and_candidatest) => Ok(maybe_constraints_and_candidatest), + } +} + +#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)] +async fn fetch_backing_constraints_and_candidates_inner<Context>( + ctx: &mut Context, + relay_parent: Hash, + para_id: ParaId, +) -> JfyiErrorResult<Option<(Constraints, Vec<CommittedCandidateReceipt>)>> { + let maybe_constraints = request_backing_constraints(relay_parent, para_id, ctx.sender()) + .await + .await + .map_err(JfyiError::RuntimeApiRequestCanceled)??; + + let Some(constraints) = maybe_constraints else { return Ok(None) }; + + let pending_availability = + request_candidates_pending_availability(relay_parent, para_id, ctx.sender()) + .await + .await + .map_err(JfyiError::RuntimeApiRequestCanceled)??; + + Ok(Some((From::from(constraints), pending_availability))) } // Fetch ancestors in descending order, up to the amount requested. @@ -955,6 +954,7 @@ async fn fetch_ancestry<Context>( cache: &mut HashMap<Hash, Header>, relay_hash: Hash, ancestors: usize, + required_session: u32, ) -> JfyiErrorResult<Vec<BlockInfo>> { if ancestors == 0 { return Ok(Vec::new()) @@ -969,10 +969,6 @@ async fn fetch_ancestry<Context>( .await; let hashes = rx.map_err(JfyiError::ChainApiRequestCanceled).await??; - let required_session = request_session_index_for_child(relay_hash, ctx.sender()) - .await - .await - .map_err(JfyiError::RuntimeApiRequestCanceled)??; let mut block_info = Vec::with_capacity(hashes.len()); for hash in hashes { diff --git a/polkadot/node/core/prospective-parachains/src/tests.rs b/polkadot/node/core/prospective-parachains/src/tests.rs index 3f1eaa4e41ed85610c7c5e6fd0f597040653704a..0900a3ee890005d84c25f14d4caafc9c5c8eb551 100644 --- a/polkadot/node/core/prospective-parachains/src/tests.rs +++ b/polkadot/node/core/prospective-parachains/src/tests.rs @@ -17,20 +17,21 @@ use super::*; use assert_matches::assert_matches; use polkadot_node_subsystem::{ - errors::RuntimeApiError, messages::{ AllMessages, HypotheticalMembershipRequest, ParentHeadData, ProspectiveParachainsMessage, ProspectiveValidationDataRequest, }, + RuntimeApiError, }; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_primitives::{ - async_backing::{AsyncBackingParams, Constraints, InboundHrmpLimitations}, + async_backing::{Constraints, InboundHrmpLimitations}, vstaging::{ - async_backing::BackingState, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, - MutateDescriptorV2, + async_backing::{BackingState, CandidatePendingAvailability, Constraints as ConstraintsV2}, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, MutateDescriptorV2, }, - CoreIndex, HeadData, Header, PersistedValidationData, ScheduledCore, ValidationCodeHash, + CoreIndex, HeadData, Header, PersistedValidationData, ValidationCodeHash, + DEFAULT_SCHEDULING_LOOKAHEAD, }; use polkadot_primitives_test_helpers::make_candidate; use rstest::rstest; @@ -40,11 +41,7 @@ use std::{ }; use test_helpers::mock::new_leaf; -const ALLOWED_ANCESTRY_LEN: u32 = 3; -const ASYNC_BACKING_PARAMETERS: AsyncBackingParams = - AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: ALLOWED_ANCESTRY_LEN }; - -const ASYNC_BACKING_DISABLED_ERROR: RuntimeApiError = +const RUNTIME_API_NOT_SUPPORTED: RuntimeApiError = RuntimeApiError::NotSupported { runtime_api_name: "test-runtime" }; const MAX_POV_SIZE: u32 = 1_000_000; @@ -76,6 +73,31 @@ fn dummy_constraints( } } +fn dummy_constraints_v2( + min_relay_parent_number: BlockNumber, + valid_watermarks: Vec<BlockNumber>, + required_parent: HeadData, + validation_code_hash: ValidationCodeHash, +) -> ConstraintsV2 { + ConstraintsV2 { + min_relay_parent_number, + max_pov_size: MAX_POV_SIZE, + max_head_data_size: 20480, + max_code_size: 1_000_000, + ump_remaining: 10, + ump_remaining_bytes: 1_000, + max_ump_num_per_candidate: 10, + dmp_remaining_messages: vec![], + hrmp_inbound: InboundHrmpLimitations { valid_watermarks }, + hrmp_channels_out: vec![], + max_hrmp_num_per_candidate: 0, + required_parent, + validation_code_hash, + upgrade_restriction: None, + future_validation_code: None, + } +} + struct TestState { claim_queue: BTreeMap<CoreIndex, VecDeque<ParaId>>, runtime_api_version: u32, @@ -88,15 +110,21 @@ impl Default for TestState { let chain_b = ParaId::from(2); let mut claim_queue = BTreeMap::new(); - claim_queue.insert(CoreIndex(0), [chain_a].into_iter().collect()); - claim_queue.insert(CoreIndex(1), [chain_b].into_iter().collect()); + claim_queue.insert( + CoreIndex(0), + std::iter::repeat(chain_a).take(DEFAULT_SCHEDULING_LOOKAHEAD as _).collect(), + ); + claim_queue.insert( + CoreIndex(1), + std::iter::repeat(chain_b).take(DEFAULT_SCHEDULING_LOOKAHEAD as _).collect(), + ); let validation_code_hash = Hash::repeat_byte(42).into(); Self { validation_code_hash, claim_queue, - runtime_api_version: RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT, + runtime_api_version: RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT, } } } @@ -204,15 +232,6 @@ async fn activate_leaf( virtual_overseer: &mut VirtualOverseer, leaf: &TestLeaf, test_state: &TestState, -) { - activate_leaf_with_params(virtual_overseer, leaf, test_state, ASYNC_BACKING_PARAMETERS).await; -} - -async fn activate_leaf_with_parent_hash_fn( - virtual_overseer: &mut VirtualOverseer, - leaf: &TestLeaf, - test_state: &TestState, - parent_hash_fn: impl Fn(Hash) -> Hash, ) { let TestLeaf { number, hash, .. } = leaf; @@ -224,21 +243,14 @@ async fn activate_leaf_with_parent_hash_fn( )))) .await; - handle_leaf_activation( - virtual_overseer, - leaf, - test_state, - ASYNC_BACKING_PARAMETERS, - parent_hash_fn, - ) - .await; + handle_leaf_activation(virtual_overseer, leaf, test_state, get_parent_hash).await; } -async fn activate_leaf_with_params( +async fn activate_leaf_with_parent_hash_fn( virtual_overseer: &mut VirtualOverseer, leaf: &TestLeaf, test_state: &TestState, - async_backing_params: AsyncBackingParams, + parent_hash_fn: impl Fn(Hash) -> Hash, ) { let TestLeaf { number, hash, .. } = leaf; @@ -250,21 +262,13 @@ async fn activate_leaf_with_params( )))) .await; - handle_leaf_activation( - virtual_overseer, - leaf, - test_state, - async_backing_params, - get_parent_hash, - ) - .await; + handle_leaf_activation(virtual_overseer, leaf, test_state, parent_hash_fn).await; } async fn handle_leaf_activation( virtual_overseer: &mut VirtualOverseer, leaf: &TestLeaf, test_state: &TestState, - async_backing_params: AsyncBackingParams, parent_hash_fn: impl Fn(Hash) -> Hash, ) { let TestLeaf { number, hash, para_data } = leaf; @@ -272,49 +276,31 @@ async fn handle_leaf_activation( assert_matches!( virtual_overseer.recv().await, AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::AsyncBackingParams(tx)) + RuntimeApiMessage::Request(parent, RuntimeApiRequest::ClaimQueue(tx)) ) if parent == *hash => { - tx.send(Ok(async_backing_params)).unwrap(); + tx.send(Ok(test_state.claim_queue.clone())).unwrap(); } ); + send_block_header(virtual_overseer, *hash, *number).await; + assert_matches!( virtual_overseer.recv().await, AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::Version(tx)) + RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx)) ) if parent == *hash => { - tx.send( - Ok(test_state.runtime_api_version) - ).unwrap(); + tx.send(Ok(1)).unwrap(); } ); - if test_state.runtime_api_version < RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT { - assert_matches!( - virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::AvailabilityCores(tx)) - ) if parent == *hash => { - tx.send(Ok(test_state.claim_queue.values().map(|paras| CoreState::Scheduled( - ScheduledCore { - para_id: *paras.front().unwrap(), - collator: None - } - )).collect())).unwrap(); - } - ); - } else { - assert_matches!( - virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::ClaimQueue(tx)) - ) if parent == *hash => { - tx.send(Ok(test_state.claim_queue.clone())).unwrap(); - } - ); - } - - send_block_header(virtual_overseer, *hash, *number).await; + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(parent, RuntimeApiRequest::SchedulingLookahead(session_index, tx)) + ) if parent == *hash && session_index == 1 => { + tx.send(Ok(DEFAULT_SCHEDULING_LOOKAHEAD)).unwrap(); + } + ); // Check that subsystem job issues a request for ancestors. let min_min = para_data.iter().map(|(_, data)| data.min_relay_parent).min().unwrap_or(*number); @@ -331,19 +317,10 @@ async fn handle_leaf_activation( virtual_overseer.recv().await, AllMessages::ChainApi( ChainApiMessage::Ancestors{hash: block_hash, k, response_channel: tx} - ) if block_hash == *hash && k == ALLOWED_ANCESTRY_LEN as usize => { + ) if block_hash == *hash && k == (DEFAULT_SCHEDULING_LOOKAHEAD - 1) as usize => { tx.send(Ok(ancestry_hashes.clone())).unwrap(); } ); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx)) - ) if parent == *hash => { - tx.send(Ok(1)).unwrap(); - } - ); } let mut used_relay_parents = HashSet::new(); @@ -364,47 +341,93 @@ async fn handle_leaf_activation( let paras: HashSet<_> = test_state.claim_queue.values().flatten().collect(); - for _ in 0..paras.len() { + // We expect two messages per parachain block. + for _ in 0..paras.len() * 2 { let message = virtual_overseer.recv().await; - // Get the para we are working with since the order is not deterministic. - let para_id = match &message { + let para_id = match message { + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::ParaBackingState(p_id, tx), + )) if parent == *hash => { + let PerParaData { min_relay_parent, head_data, pending_availability } = + leaf.para_data(p_id); + + let constraints = dummy_constraints( + *min_relay_parent, + vec![*number], + head_data.clone(), + test_state.validation_code_hash, + ); + + tx.send(Ok(Some(BackingState { + constraints, + pending_availability: pending_availability.clone(), + }))) + .unwrap(); + Some(p_id) + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::BackingConstraints(p_id, tx), + )) if parent == *hash && + test_state.runtime_api_version >= + RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT => + { + let PerParaData { min_relay_parent, head_data, pending_availability: _ } = + leaf.para_data(p_id); + let constraints = dummy_constraints_v2( + *min_relay_parent, + vec![*number], + head_data.clone(), + test_state.validation_code_hash, + ); + + tx.send(Ok(Some(constraints))).unwrap(); + None + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::BackingConstraints(_p_id, tx), + )) if parent == *hash && + test_state.runtime_api_version < + RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT => + { + tx.send(Err(RUNTIME_API_NOT_SUPPORTED)).unwrap(); + None + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::ParaBackingState(p_id, _), - )) => *p_id, + parent, + RuntimeApiRequest::CandidatesPendingAvailability(p_id, tx), + )) if parent == *hash => { + tx.send(Ok(leaf + .para_data(p_id) + .pending_availability + .clone() + .into_iter() + .map(|c| CommittedCandidateReceipt { + descriptor: c.descriptor, + commitments: c.commitments, + }) + .collect())) + .unwrap(); + Some(p_id) + }, _ => panic!("received unexpected message {:?}", message), }; - let PerParaData { min_relay_parent, head_data, pending_availability } = - leaf.para_data(para_id); - let constraints = dummy_constraints( - *min_relay_parent, - vec![*number], - head_data.clone(), - test_state.validation_code_hash, - ); - let backing_state = - BackingState { constraints, pending_availability: pending_availability.clone() }; - - assert_matches!( - message, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::ParaBackingState(p_id, tx)) - ) if parent == *hash && p_id == para_id => { - tx.send(Ok(Some(backing_state))).unwrap(); - } - ); - - for pending in pending_availability { - if !used_relay_parents.contains(&pending.descriptor.relay_parent()) { - send_block_header( - virtual_overseer, - pending.descriptor.relay_parent(), - pending.relay_parent_number, - ) - .await; - - used_relay_parents.insert(pending.descriptor.relay_parent()); + if let Some(para_id) = para_id { + for pending in leaf.para_data(para_id).pending_availability.clone() { + if !used_relay_parents.contains(&pending.descriptor.relay_parent()) { + send_block_header( + virtual_overseer, + pending.descriptor.relay_parent(), + pending.relay_parent_number, + ) + .await; + + used_relay_parents.insert(pending.descriptor.relay_parent()); + } } } } @@ -416,7 +439,9 @@ async fn handle_leaf_activation( msg: ProspectiveParachainsMessage::GetMinimumRelayParents(*hash, tx), }) .await; + let mut resp = rx.await.unwrap(); + resp.sort(); let mrp_response: Vec<(ParaId, BlockNumber)> = para_data .iter() @@ -580,45 +605,17 @@ macro_rules! make_and_back_candidate { }}; } -#[test] -fn should_do_no_work_if_async_backing_disabled_for_leaf() { - async fn activate_leaf_async_backing_disabled(virtual_overseer: &mut VirtualOverseer) { - let hash = Hash::from_low_u64_be(130); - - // Start work on some new parent. - virtual_overseer - .send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate::start_work(new_leaf(hash, 1)), - ))) - .await; - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::AsyncBackingParams(tx)) - ) if parent == hash => { - tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)).unwrap(); - } - ); - } - - let view = test_harness(|mut virtual_overseer| async move { - activate_leaf_async_backing_disabled(&mut virtual_overseer).await; - - virtual_overseer - }); - - assert!(view.active_leaves.is_empty()); -} - // Send some candidates and make sure all are found: // - Two for the same leaf A (one for parachain 1 and one for parachain 2) // - One for leaf B on parachain 1 // - One for leaf C on parachain 2 // Also tests a claim queue size larger than 1. -#[test] -fn introduce_candidates_basic() { +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn introduce_candidates_basic(#[case] runtime_api_version: u32) { let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let chain_a = ParaId::from(1); let chain_b = ParaId::from(2); @@ -786,9 +783,127 @@ fn introduce_candidates_basic() { assert_eq!(view.active_leaves.len(), 3); } -#[test] -fn introduce_candidate_multiple_times() { - let test_state = TestState::default(); +// Check if candidates are not backed if they fail constraint checks +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn introduce_candidates_error(#[case] runtime_api_version: u32) { + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); + test_state.claim_queue.insert( + CoreIndex(2), + std::iter::repeat(1.into()).take(DEFAULT_SCHEDULING_LOOKAHEAD as _).collect(), + ); + + let view = test_harness(|mut virtual_overseer| async move { + // Leaf A + let leaf_a = TestLeaf { + number: 100, + hash: Default::default(), + para_data: vec![ + (1.into(), PerParaData::new(98, HeadData(vec![1, 2, 3]))), + (2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))), + ], + }; + + // Activate leaves. + activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await; + + // Candidate A. + let (candidate_a, pvd_a) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1, 2, 3]), + HeadData(vec![1]), + test_state.validation_code_hash, + ); + + // Candidate B. + let (candidate_b, pvd_b) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1]), + HeadData(vec![1; 20480]), + test_state.validation_code_hash, + ); + + // Candidate C commits to oversized head data. + let (candidate_c, pvd_c) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1; 20480]), + HeadData(vec![0; 20485]), + test_state.validation_code_hash, + ); + + // Get hypothetical membership of candidates before adding candidate A. + // Candidate A can be added directly, candidates B and C are potential candidates. + for (candidate, pvd) in + [(candidate_a.clone(), pvd_a.clone()), (candidate_b.clone(), pvd_b.clone())] + { + get_hypothetical_membership( + &mut virtual_overseer, + candidate.hash(), + candidate, + pvd, + vec![leaf_a.hash], + ) + .await; + } + + // Fails constraints check + get_hypothetical_membership( + &mut virtual_overseer, + candidate_c.hash(), + candidate_c.clone(), + pvd_c.clone(), + Vec::new(), + ) + .await; + + // Add candidates + introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a.clone()) + .await; + introduce_seconded_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b.clone()) + .await; + // Fails constraints check + introduce_seconded_candidate_failed( + &mut virtual_overseer, + candidate_c.clone(), + pvd_c.clone(), + ) + .await; + + back_candidate(&mut virtual_overseer, &candidate_a, candidate_a.hash()).await; + back_candidate(&mut virtual_overseer, &candidate_b, candidate_b.hash()).await; + // This one will not be backed. + back_candidate(&mut virtual_overseer, &candidate_c, candidate_c.hash()).await; + + // Expect only A and B to be backable + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + Ancestors::default(), + 5, + vec![(candidate_a.hash(), leaf_a.hash), (candidate_b.hash(), leaf_a.hash)], + ) + .await; + virtual_overseer + }); + + assert_eq!(view.active_leaves.len(), 1); +} + +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn introduce_candidate_multiple_times(#[case] runtime_api_version: u32) { + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -858,7 +973,11 @@ fn introduce_candidate_multiple_times() { #[test] fn fragment_chain_best_chain_length_is_bounded() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); + test_state.claim_queue.insert( + CoreIndex(2), + std::iter::repeat(1.into()).take(DEFAULT_SCHEDULING_LOOKAHEAD as _).collect(), + ); let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -870,13 +989,7 @@ fn fragment_chain_best_chain_length_is_bounded() { ], }; // Activate leaves. - activate_leaf_with_params( - &mut virtual_overseer, - &leaf_a, - &test_state, - AsyncBackingParams { max_candidate_depth: 1, allowed_ancestry_len: 3 }, - ) - .await; + activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await; // Candidates A, B and C form a chain. let (candidate_a, pvd_a) = make_candidate( @@ -904,7 +1017,7 @@ fn fragment_chain_best_chain_length_is_bounded() { test_state.validation_code_hash, ); - // Introduce candidates A and B. Since max depth is 1, only these two will be allowed. + // Introduce candidates A and B. Since max depth is 2, only these two will be allowed. introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a).await; introduce_seconded_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b).await; @@ -1172,9 +1285,12 @@ fn introduce_candidate_parent_leaving_view() { } // Introduce a candidate to multiple forks, see how the membership is returned. -#[test] -fn introduce_candidate_on_multiple_forks() { - let test_state = TestState::default(); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn introduce_candidate_on_multiple_forks(#[case] runtime_api_version: u32) { + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf B let leaf_b = TestLeaf { @@ -1241,11 +1357,21 @@ fn introduce_candidate_on_multiple_forks() { assert_eq!(view.active_leaves.len(), 2); } -#[test] -fn unconnected_candidates_become_connected() { +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn unconnected_candidates_become_connected(#[case] runtime_api_version: u32) { // This doesn't test all the complicated cases with many unconnected candidates, as it's more // extensively tested in the `fragment_chain::tests` module. - let test_state = TestState::default(); + let mut test_state = TestState::default(); + for i in 2..=4 { + test_state.claim_queue.insert( + CoreIndex(i), + std::iter::repeat(1.into()).take(DEFAULT_SCHEDULING_LOOKAHEAD as _).collect(), + ); + } + + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -1345,7 +1471,11 @@ fn unconnected_candidates_become_connected() { // Backs some candidates and tests `GetBackableCandidates` when requesting a single candidate. #[test] fn check_backable_query_single_candidate() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); + test_state.claim_queue.insert( + CoreIndex(2), + std::iter::repeat(1.into()).take(DEFAULT_SCHEDULING_LOOKAHEAD as _).collect(), + ); let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -1483,9 +1613,23 @@ fn check_backable_query_single_candidate() { } // Backs some candidates and tests `GetBackableCandidates` when requesting a multiple candidates. -#[test] -fn check_backable_query_multiple_candidates() { - let test_state = TestState::default(); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] + +fn check_backable_query_multiple_candidates(#[case] runtime_api_version: u32) { + // This doesn't test all the complicated cases with many unconnected candidates, as it's more + // extensively tested in the `fragment_chain::tests` module. + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); + // Add three more cores for para A, so that we can get a chain of max length 4 + for i in 2..=4 { + test_state.claim_queue.insert( + CoreIndex(i), + std::iter::repeat(1.into()).take(DEFAULT_SCHEDULING_LOOKAHEAD as _).collect(), + ); + } + let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -1755,9 +1899,13 @@ fn check_backable_query_multiple_candidates() { } // Test hypothetical membership query. -#[test] -fn check_hypothetical_membership_query() { - let test_state = TestState::default(); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn check_hypothetical_membership_query(#[case] runtime_api_version: u32) { + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); + let view = test_harness(|mut virtual_overseer| async move { // Leaf B let leaf_b = TestLeaf { @@ -1779,20 +1927,8 @@ fn check_hypothetical_membership_query() { }; // Activate leaves. - activate_leaf_with_params( - &mut virtual_overseer, - &leaf_a, - &test_state, - AsyncBackingParams { allowed_ancestry_len: 3, max_candidate_depth: 1 }, - ) - .await; - activate_leaf_with_params( - &mut virtual_overseer, - &leaf_b, - &test_state, - AsyncBackingParams { allowed_ancestry_len: 3, max_candidate_depth: 1 }, - ) - .await; + activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await; + activate_leaf(&mut virtual_overseer, &leaf_b, &test_state).await; // Candidates will be valid on both leaves. @@ -1894,6 +2030,17 @@ fn check_hypothetical_membership_query() { ); introduce_seconded_candidate_failed(&mut virtual_overseer, candidate_d, pvd_d).await; + // Candidate E has invalid head data. + let (candidate_e, pvd_e) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![2]), + HeadData(vec![0; 20481]), + test_state.validation_code_hash, + ); + introduce_seconded_candidate_failed(&mut virtual_overseer, candidate_e, pvd_e).await; + // Add candidate B and back it. introduce_seconded_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b.clone()) .await; @@ -1921,9 +2068,14 @@ fn check_hypothetical_membership_query() { assert_eq!(view.active_leaves.len(), 2); } -#[test] -fn check_pvd_query() { - let test_state = TestState::default(); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn check_pvd_query(#[case] runtime_api_version: u32) { + // This doesn't test all the complicated cases with many unconnected candidates, as it's more + // extensively tested in the `fragment_chain::tests` module. + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -2058,14 +2210,9 @@ fn check_pvd_query() { // Test simultaneously activating and deactivating leaves, and simultaneously deactivating // multiple leaves. -// This test is parametrised with the runtime api version. For versions that don't support the claim -// queue API, we check that av-cores are used. -#[rstest] -#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] -#[case(8)] -fn correctly_updates_leaves(#[case] runtime_api_version: u32) { - let mut test_state = TestState::default(); - test_state.set_runtime_api_version(runtime_api_version); +#[test] +fn correctly_updates_leaves() { + let test_state = TestState::default(); let view = test_harness(|mut virtual_overseer| async move { // Leaf A @@ -2098,6 +2245,7 @@ fn correctly_updates_leaves(#[case] runtime_api_version: u32) { // Activate leaves. activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await; + activate_leaf(&mut virtual_overseer, &leaf_b, &test_state).await; // Try activating a duplicate leaf. @@ -2118,14 +2266,7 @@ fn correctly_updates_leaves(#[case] runtime_api_version: u32) { virtual_overseer .send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update))) .await; - handle_leaf_activation( - &mut virtual_overseer, - &leaf_c, - &test_state, - ASYNC_BACKING_PARAMETERS, - get_parent_hash, - ) - .await; + handle_leaf_activation(&mut virtual_overseer, &leaf_c, &test_state, get_parent_hash).await; // Remove all remaining leaves. let update = ActiveLeavesUpdate { @@ -2161,16 +2302,25 @@ fn correctly_updates_leaves(#[case] runtime_api_version: u32) { assert_eq!(view.active_leaves.len(), 0); } -#[test] -fn handle_active_leaves_update_gets_candidates_from_parent() { +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn handle_active_leaves_update_gets_candidates_from_parent(#[case] runtime_api_version: u32) { let para_id = ParaId::from(1); + + // This doesn't test all the complicated cases with many unconnected candidates, as it's more + // extensively tested in the `fragment_chain::tests` module. let mut test_state = TestState::default(); - test_state.claim_queue = test_state - .claim_queue - .into_iter() - .filter(|(_, paras)| matches!(paras.front(), Some(para) if para == ¶_id)) - .collect(); - assert_eq!(test_state.claim_queue.len(), 1); + test_state.set_runtime_api_version(runtime_api_version); + + test_state.claim_queue = BTreeMap::new(); + for i in 0..=4 { + test_state.claim_queue.insert( + CoreIndex(i), + std::iter::repeat(para_id).take(DEFAULT_SCHEDULING_LOOKAHEAD as _).collect(), + ); + } + let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -2423,12 +2573,14 @@ fn handle_active_leaves_update_bounded_implicit_view() { .collect(); assert_eq!(test_state.claim_queue.len(), 1); + let scheduling_lookahead = DEFAULT_SCHEDULING_LOOKAHEAD; + let mut leaves = vec![TestLeaf { number: 100, hash: Hash::from_low_u64_be(130), para_data: vec![( para_id, - PerParaData::new(100 - ALLOWED_ANCESTRY_LEN, HeadData(vec![1, 2, 3])), + PerParaData::new(100 - (scheduling_lookahead - 1), HeadData(vec![1, 2, 3])), )], }]; @@ -2440,7 +2592,7 @@ fn handle_active_leaves_update_bounded_implicit_view() { para_data: vec![( para_id, PerParaData::new( - prev_leaf.number - 1 - ALLOWED_ANCESTRY_LEN, + prev_leaf.number - 1 - (scheduling_lookahead - 1), HeadData(vec![1, 2, 3]), ), )], @@ -2464,22 +2616,24 @@ fn handle_active_leaves_update_bounded_implicit_view() { // Only latest leaf is active. assert_eq!(view.active_leaves.len(), 1); - // We keep allowed_ancestry_len implicit leaves. The latest leaf is also present here. - assert_eq!( - view.per_relay_parent.len() as u32, - ASYNC_BACKING_PARAMETERS.allowed_ancestry_len + 1 - ); + // We keep scheduling_lookahead - 1 implicit leaves. The latest leaf is also present here. + assert_eq!(view.per_relay_parent.len() as u32, scheduling_lookahead); assert_eq!(view.active_leaves, [leaves[9].hash].into_iter().collect()); assert_eq!( view.per_relay_parent.into_keys().collect::<HashSet<_>>(), - leaves[6..].into_iter().map(|l| l.hash).collect::<HashSet<_>>() + leaves[7..].into_iter().map(|l| l.hash).collect::<HashSet<_>>() ); } -#[test] -fn persists_pending_availability_candidate() { +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn persists_pending_availability_candidate(#[case] runtime_api_version: u32) { + // This doesn't test all the complicated cases with many unconnected candidates, as it's more + // extensively tested in the `fragment_chain::tests` module. let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let para_id = ParaId::from(1); test_state.claim_queue = test_state .claim_queue @@ -2496,7 +2650,7 @@ fn persists_pending_availability_candidate() { let candidate_relay_parent_number = 97; let leaf_a = TestLeaf { - number: candidate_relay_parent_number + ALLOWED_ANCESTRY_LEN, + number: candidate_relay_parent_number + DEFAULT_SCHEDULING_LOOKAHEAD, hash: Hash::from_low_u64_be(2), para_data: vec![( para_id, @@ -2584,102 +2738,12 @@ fn persists_pending_availability_candidate() { }); } -#[test] -fn backwards_compatible_with_non_async_backing_params() { - let mut test_state = TestState::default(); - let para_id = ParaId::from(1); - test_state.claim_queue = test_state - .claim_queue - .into_iter() - .filter(|(_, paras)| matches!(paras.front(), Some(para) if para == ¶_id)) - .collect(); - assert_eq!(test_state.claim_queue.len(), 1); - - test_harness(|mut virtual_overseer| async move { - let para_head = HeadData(vec![1, 2, 3]); - - let leaf_b_hash = Hash::repeat_byte(15); - let candidate_relay_parent = get_parent_hash(leaf_b_hash); - let candidate_relay_parent_number = 100; - - let leaf_a = TestLeaf { - number: candidate_relay_parent_number, - hash: candidate_relay_parent, - para_data: vec![( - para_id, - PerParaData::new(candidate_relay_parent_number, para_head.clone()), - )], - }; - - // Activate leaf. - activate_leaf_with_params( - &mut virtual_overseer, - &leaf_a, - &test_state, - AsyncBackingParams { allowed_ancestry_len: 0, max_candidate_depth: 0 }, - ) - .await; - - // Candidate A - let (candidate_a, pvd_a) = make_candidate( - candidate_relay_parent, - candidate_relay_parent_number, - para_id, - para_head.clone(), - HeadData(vec![1]), - test_state.validation_code_hash, - ); - let candidate_hash_a = candidate_a.hash(); - - introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a).await; - back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await; - - get_backable_candidates( - &mut virtual_overseer, - &leaf_a, - para_id, - Ancestors::new(), - 1, - vec![(candidate_hash_a, candidate_relay_parent)], - ) - .await; - - let leaf_b = TestLeaf { - number: candidate_relay_parent_number + 1, - hash: leaf_b_hash, - para_data: vec![( - para_id, - PerParaData::new(candidate_relay_parent_number + 1, para_head.clone()), - )], - }; - activate_leaf_with_params( - &mut virtual_overseer, - &leaf_b, - &test_state, - AsyncBackingParams { allowed_ancestry_len: 0, max_candidate_depth: 0 }, - ) - .await; - - get_backable_candidates( - &mut virtual_overseer, - &leaf_b, - para_id, - Ancestors::new(), - 1, - vec![], - ) - .await; - - virtual_overseer - }); -} - #[test] fn uses_ancestry_only_within_session() { test_harness(|mut virtual_overseer| async move { let number = 5; let hash = Hash::repeat_byte(5); - let ancestry_len = 3; + let scheduling_lookahead = DEFAULT_SCHEDULING_LOOKAHEAD; let session = 2; let ancestry_hashes = @@ -2694,51 +2758,41 @@ fn uses_ancestry_only_within_session() { ))) .await; - assert_matches!( - virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request( - parent, - RuntimeApiRequest::AsyncBackingParams(tx) - )) if parent == hash => { - tx.send(Ok(AsyncBackingParams { max_candidate_depth: 0, allowed_ancestry_len: ancestry_len})).unwrap(); - }); - assert_matches!( virtual_overseer.recv().await, AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::Version(tx)) + RuntimeApiMessage::Request(parent, RuntimeApiRequest::ClaimQueue(tx)) ) if parent == hash => { - tx.send(Ok(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)).unwrap(); + tx.send(Ok(BTreeMap::new())).unwrap(); } ); + send_block_header(&mut virtual_overseer, hash, number).await; + assert_matches!( virtual_overseer.recv().await, AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::ClaimQueue(tx)) + RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx)) ) if parent == hash => { - tx.send(Ok(BTreeMap::new())).unwrap(); + tx.send(Ok(session)).unwrap(); } ); - send_block_header(&mut virtual_overseer, hash, number).await; - assert_matches!( virtual_overseer.recv().await, - AllMessages::ChainApi( - ChainApiMessage::Ancestors{hash: block_hash, k, response_channel: tx} - ) if block_hash == hash && k == ancestry_len as usize => { - tx.send(Ok(ancestry_hashes.clone())).unwrap(); + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(parent, RuntimeApiRequest::SchedulingLookahead(session_index, tx)) + ) if parent == hash && session_index == session => { + tx.send(Ok(scheduling_lookahead)).unwrap(); } ); assert_matches!( virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx)) - ) if parent == hash => { - tx.send(Ok(session)).unwrap(); + AllMessages::ChainApi( + ChainApiMessage::Ancestors{hash: block_hash, k, response_channel: tx} + ) if block_hash == hash && k == (scheduling_lookahead - 1) as usize => { + tx.send(Ok(ancestry_hashes.clone())).unwrap(); } ); diff --git a/polkadot/node/core/provisioner/Cargo.toml b/polkadot/node/core/provisioner/Cargo.toml index 64a598b420f7a0390250b56070fee3d6739f3252..a3880d5a0f136eb9bd8fa30ffcc29e2baba6d081 100644 --- a/polkadot/node/core/provisioner/Cargo.toml +++ b/polkadot/node/core/provisioner/Cargo.toml @@ -5,28 +5,30 @@ description = "Responsible for assembling a relay chain block from a set of avai authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] bitvec = { features = ["alloc"], workspace = true } +fatality = { workspace = true } futures = { workspace = true } +futures-timer = { workspace = true } gum = { workspace = true, default-features = true } -thiserror = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } -futures-timer = { workspace = true } -fatality = { workspace = true } +polkadot-primitives = { workspace = true, default-features = true } schnellru = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] -sp-application-crypto = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } -polkadot-primitives-test-helpers = { workspace = true } polkadot-primitives = { workspace = true, features = ["test"] } +polkadot-primitives-test-helpers = { workspace = true } +sp-application-crypto = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } rstest = { workspace = true } diff --git a/polkadot/node/core/provisioner/src/disputes/prioritized_selection/tests.rs b/polkadot/node/core/provisioner/src/disputes/prioritized_selection/tests.rs index 8c0d478b67df4e65553ab5ed6614ff1a920d7491..1f814989fc6a85fc104d0f565402fcf09388391a 100644 --- a/polkadot/node/core/provisioner/src/disputes/prioritized_selection/tests.rs +++ b/polkadot/node/core/provisioner/src/disputes/prioritized_selection/tests.rs @@ -26,8 +26,8 @@ use polkadot_node_subsystem::messages::{ }; use polkadot_node_subsystem_test_helpers::{mock::new_leaf, TestSubsystemSender}; use polkadot_primitives::{ - CandidateHash, DisputeState, InvalidDisputeStatementKind, SessionIndex, - ValidDisputeStatementKind, ValidatorSignature, + vstaging::CandidateReceiptV2 as CandidateReceipt, CandidateHash, DisputeState, + InvalidDisputeStatementKind, SessionIndex, ValidDisputeStatementKind, ValidatorSignature, }; // diff --git a/polkadot/node/core/provisioner/src/lib.rs b/polkadot/node/core/provisioner/src/lib.rs index a95df6c5f8808950f4531c0ba5f20cac07a959ab..4aced8eaefdfe09e294a97e7cfce2840b0583cca 100644 --- a/polkadot/node/core/provisioner/src/lib.rs +++ b/polkadot/node/core/provisioner/src/lib.rs @@ -28,23 +28,21 @@ use schnellru::{ByLength, LruMap}; use polkadot_node_subsystem::{ messages::{ - Ancestors, CandidateBackingMessage, ChainApiMessage, ProspectiveParachainsMessage, - ProvisionableData, ProvisionerInherentData, ProvisionerMessage, RuntimeApiRequest, + Ancestors, CandidateBackingMessage, ProspectiveParachainsMessage, ProvisionableData, + ProvisionerInherentData, ProvisionerMessage, }, overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError, }; use polkadot_node_subsystem_util::{ - has_required_runtime, request_availability_cores, request_persisted_validation_data, - request_session_index_for_child, - runtime::{prospective_parachains_mode, request_node_features, ProspectiveParachainsMode}, + request_availability_cores, request_session_index_for_child, runtime::request_node_features, TimeoutExt, }; use polkadot_primitives::{ node_features::FeatureIndex, - vstaging::{BackedCandidate, CandidateReceiptV2 as CandidateReceipt, CoreState}, - BlockNumber, CandidateHash, CoreIndex, Hash, Id as ParaId, NodeFeatures, - OccupiedCoreAssumption, SessionIndex, SignedAvailabilityBitfield, ValidatorIndex, + vstaging::{BackedCandidate, CoreState}, + CandidateHash, CoreIndex, Hash, Id as ParaId, NodeFeatures, SessionIndex, + SignedAvailabilityBitfield, ValidatorIndex, }; use std::collections::{BTreeMap, HashMap}; @@ -65,9 +63,6 @@ const SEND_INHERENT_DATA_TIMEOUT: std::time::Duration = core::time::Duration::fr const LOG_TARGET: &str = "parachain::provisioner"; -const PRIORITIZED_SELECTION_RUNTIME_VERSION_REQUIREMENT: u32 = - RuntimeApiRequest::DISPUTES_RUNTIME_REQUIREMENT; - /// The provisioner subsystem. pub struct ProvisionerSubsystem { metrics: Metrics, @@ -82,15 +77,12 @@ impl ProvisionerSubsystem { /// Per-session info we need for the provisioner subsystem. pub struct PerSession { - prospective_parachains_mode: ProspectiveParachainsMode, elastic_scaling_mvp: bool, } /// A per-relay-parent state for the provisioning subsystem. pub struct PerRelayParent { leaf: ActivatedLeaf, - backed_candidates: Vec<CandidateReceipt>, - prospective_parachains_mode: ProspectiveParachainsMode, elastic_scaling_mvp: bool, signed_bitfields: Vec<SignedAvailabilityBitfield>, is_inherent_ready: bool, @@ -101,8 +93,6 @@ impl PerRelayParent { fn new(leaf: ActivatedLeaf, per_session: &PerSession) -> Self { Self { leaf, - backed_candidates: Vec::new(), - prospective_parachains_mode: per_session.prospective_parachains_mode, elastic_scaling_mvp: per_session.elastic_scaling_mvp, signed_bitfields: Vec::new(), is_inherent_ready: false, @@ -212,8 +202,6 @@ async fn handle_active_leaves_update( .await .map_err(Error::CanceledSessionIndex)??; if per_session.get(&session_index).is_none() { - let prospective_parachains_mode = - prospective_parachains_mode(sender, leaf.hash).await?; let elastic_scaling_mvp = request_node_features(leaf.hash, session_index, sender) .await? .unwrap_or(NodeFeatures::EMPTY) @@ -221,10 +209,7 @@ async fn handle_active_leaves_update( .map(|b| *b) .unwrap_or(false); - per_session.insert( - session_index, - PerSession { prospective_parachains_mode, elastic_scaling_mvp }, - ); + per_session.insert(session_index, PerSession { elastic_scaling_mvp }); } let session_info = per_session.get(&session_index).expect("Just inserted"); @@ -287,8 +272,6 @@ async fn send_inherent_data_bg<Context>( ) -> Result<(), Error> { let leaf = per_relay_parent.leaf.clone(); let signed_bitfields = per_relay_parent.signed_bitfields.clone(); - let backed_candidates = per_relay_parent.backed_candidates.clone(); - let mode = per_relay_parent.prospective_parachains_mode; let elastic_scaling_mvp = per_relay_parent.elastic_scaling_mvp; let mut sender = ctx.sender().clone(); @@ -305,8 +288,6 @@ async fn send_inherent_data_bg<Context>( let send_result = send_inherent_data( &leaf, &signed_bitfields, - &backed_candidates, - mode, elastic_scaling_mvp, return_senders, &mut sender, @@ -357,16 +338,6 @@ fn note_provisionable_data( match provisionable_data { ProvisionableData::Bitfield(_, signed_bitfield) => per_relay_parent.signed_bitfields.push(signed_bitfield), - ProvisionableData::BackedCandidate(backed_candidate) => { - let candidate_hash = backed_candidate.hash(); - gum::trace!( - target: LOG_TARGET, - ?candidate_hash, - para = ?backed_candidate.descriptor().para_id(), - "noted backed candidate", - ); - per_relay_parent.backed_candidates.push(backed_candidate); - }, // We choose not to punish these forms of misbehavior for the time being. // Risks from misbehavior are sufficiently mitigated at the protocol level // via reputation changes. Punitive actions here may become desirable @@ -412,8 +383,6 @@ type CoreAvailability = BitVec<u8, bitvec::order::Lsb0>; async fn send_inherent_data( leaf: &ActivatedLeaf, bitfields: &[SignedAvailabilityBitfield], - candidates: &[CandidateReceipt], - prospective_parachains_mode: ProspectiveParachainsMode, elastic_scaling_mvp: bool, return_senders: Vec<oneshot::Sender<ProvisionerInherentData>>, from_job: &mut impl overseer::ProvisionerSenderTrait, @@ -435,16 +404,6 @@ async fn send_inherent_data( "Selecting disputes" ); - debug_assert!( - has_required_runtime( - from_job, - leaf.hash, - PRIORITIZED_SELECTION_RUNTIME_VERSION_REQUIREMENT, - ) - .await, - "randomized selection no longer supported, please upgrade your runtime!" - ); - let disputes = disputes::prioritized_selection::select_disputes(from_job, metrics, leaf).await; gum::trace!( @@ -461,16 +420,9 @@ async fn send_inherent_data( "Selected bitfields" ); - let candidates = select_candidates( - &availability_cores, - &bitfields, - candidates, - prospective_parachains_mode, - elastic_scaling_mvp, - leaf.hash, - from_job, - ) - .await?; + let candidates = + select_candidates(&availability_cores, &bitfields, elastic_scaling_mvp, leaf, from_job) + .await?; gum::trace!( target: LOG_TARGET, @@ -577,114 +529,16 @@ fn select_availability_bitfields( selected.into_values().collect() } -/// Selects candidates from tracked ones to note in a relay chain block. -/// -/// Should be called when prospective parachains are disabled. -async fn select_candidate_hashes_from_tracked( - availability_cores: &[CoreState], - bitfields: &[SignedAvailabilityBitfield], - candidates: &[CandidateReceipt], - relay_parent: Hash, - sender: &mut impl overseer::ProvisionerSenderTrait, -) -> Result<HashMap<ParaId, Vec<(CandidateHash, Hash)>>, Error> { - let block_number = get_block_number_under_construction(relay_parent, sender).await?; - - let mut selected_candidates = - HashMap::with_capacity(candidates.len().min(availability_cores.len())); - - gum::debug!( - target: LOG_TARGET, - leaf_hash=?relay_parent, - n_candidates = candidates.len(), - "Candidate receipts (before selection)", - ); - - for (core_idx, core) in availability_cores.iter().enumerate() { - let (scheduled_core, assumption) = match core { - CoreState::Scheduled(scheduled_core) => (scheduled_core, OccupiedCoreAssumption::Free), - CoreState::Occupied(occupied_core) => { - if bitfields_indicate_availability(core_idx, bitfields, &occupied_core.availability) - { - if let Some(ref scheduled_core) = occupied_core.next_up_on_available { - (scheduled_core, OccupiedCoreAssumption::Included) - } else { - continue - } - } else { - if occupied_core.time_out_at != block_number { - continue - } - if let Some(ref scheduled_core) = occupied_core.next_up_on_time_out { - (scheduled_core, OccupiedCoreAssumption::TimedOut) - } else { - continue - } - } - }, - CoreState::Free => continue, - }; - - if selected_candidates.contains_key(&scheduled_core.para_id) { - // We already picked a candidate for this parachain. Elastic scaling only works with - // prospective parachains mode. - continue - } - - let validation_data = match request_persisted_validation_data( - relay_parent, - scheduled_core.para_id, - assumption, - sender, - ) - .await - .await - .map_err(|err| Error::CanceledPersistedValidationData(err))?? - { - Some(v) => v, - None => continue, - }; - - let computed_validation_data_hash = validation_data.hash(); - - // we arbitrarily pick the first of the backed candidates which match the appropriate - // selection criteria - if let Some(candidate) = candidates.iter().find(|backed_candidate| { - let descriptor = &backed_candidate.descriptor; - descriptor.para_id() == scheduled_core.para_id && - descriptor.persisted_validation_data_hash() == computed_validation_data_hash - }) { - let candidate_hash = candidate.hash(); - gum::trace!( - target: LOG_TARGET, - leaf_hash=?relay_parent, - ?candidate_hash, - para = ?candidate.descriptor.para_id(), - core = core_idx, - "Selected candidate receipt", - ); - - selected_candidates.insert( - candidate.descriptor.para_id(), - vec![(candidate_hash, candidate.descriptor.relay_parent())], - ); - } - } - - Ok(selected_candidates) -} - /// Requests backable candidates from Prospective Parachains subsystem /// based on core states. -/// -/// Should be called when prospective parachains are enabled. async fn request_backable_candidates( availability_cores: &[CoreState], elastic_scaling_mvp: bool, bitfields: &[SignedAvailabilityBitfield], - relay_parent: Hash, + relay_parent: &ActivatedLeaf, sender: &mut impl overseer::ProvisionerSenderTrait, ) -> Result<HashMap<ParaId, Vec<(CandidateHash, Hash)>>, Error> { - let block_number = get_block_number_under_construction(relay_parent, sender).await?; + let block_number_under_construction = relay_parent.number + 1; // Record how many cores are scheduled for each paraid. Use a BTreeMap because // we'll need to iterate through them. @@ -716,7 +570,7 @@ async fn request_backable_candidates( // Request a new backable candidate for the newly scheduled para id. *scheduled_cores_per_para.entry(scheduled_core.para_id).or_insert(0) += 1; } - } else if occupied_core.time_out_at <= block_number { + } else if occupied_core.time_out_at <= block_number_under_construction { // Timed out before being available. if let Some(ref scheduled_core) = occupied_core.next_up_on_time_out { @@ -747,7 +601,7 @@ async fn request_backable_candidates( } let response = get_backable_candidates( - relay_parent, + relay_parent.hash, para_id, para_ancestors, core_count as u32, @@ -758,7 +612,7 @@ async fn request_backable_candidates( if response.is_empty() { gum::debug!( target: LOG_TARGET, - leaf_hash = ?relay_parent, + leaf_hash = ?relay_parent.hash, ?para_id, "No backable candidate returned by prospective parachains", ); @@ -776,38 +630,26 @@ async fn request_backable_candidates( async fn select_candidates( availability_cores: &[CoreState], bitfields: &[SignedAvailabilityBitfield], - candidates: &[CandidateReceipt], - prospective_parachains_mode: ProspectiveParachainsMode, elastic_scaling_mvp: bool, - relay_parent: Hash, + leaf: &ActivatedLeaf, sender: &mut impl overseer::ProvisionerSenderTrait, ) -> Result<Vec<BackedCandidate>, Error> { + let relay_parent = leaf.hash; gum::trace!( target: LOG_TARGET, leaf_hash=?relay_parent, "before GetBackedCandidates" ); - let selected_candidates = match prospective_parachains_mode { - ProspectiveParachainsMode::Enabled { .. } => - request_backable_candidates( - availability_cores, - elastic_scaling_mvp, - bitfields, - relay_parent, - sender, - ) - .await?, - ProspectiveParachainsMode::Disabled => - select_candidate_hashes_from_tracked( - availability_cores, - bitfields, - &candidates, - relay_parent, - sender, - ) - .await?, - }; + let selected_candidates = request_backable_candidates( + availability_cores, + elastic_scaling_mvp, + bitfields, + leaf, + sender, + ) + .await?; + gum::debug!(target: LOG_TARGET, ?selected_candidates, "Got backable candidates"); // now get the backed candidates corresponding to these candidate receipts @@ -817,8 +659,11 @@ async fn select_candidates( tx, )); let candidates = rx.await.map_err(|err| Error::CanceledBackedCandidates(err))?; - gum::trace!(target: LOG_TARGET, leaf_hash=?relay_parent, - "Got {} backed candidates", candidates.len()); + gum::trace!( + target: LOG_TARGET, + leaf_hash=?relay_parent, + "Got {} backed candidates", candidates.len() + ); // keep only one candidate with validation code. let mut with_validation_code = false; @@ -850,22 +695,6 @@ async fn select_candidates( Ok(merged_candidates) } -/// Produces a block number 1 higher than that of the relay parent -/// in the event of an invalid `relay_parent`, returns `Ok(0)` -async fn get_block_number_under_construction( - relay_parent: Hash, - sender: &mut impl overseer::ProvisionerSenderTrait, -) -> Result<BlockNumber, Error> { - let (tx, rx) = oneshot::channel(); - sender.send_message(ChainApiMessage::BlockNumber(relay_parent, tx)).await; - - match rx.await.map_err(|err| Error::CanceledBlockNumber(err))? { - Ok(Some(n)) => Ok(n + 1), - Ok(None) => Ok(0), - Err(err) => Err(err.into()), - } -} - /// Requests backable candidates from Prospective Parachains based on /// the given ancestors in the fragment chain. The ancestors may not be ordered. async fn get_backable_candidates( diff --git a/polkadot/node/core/provisioner/src/tests.rs b/polkadot/node/core/provisioner/src/tests.rs index a09b243f3ab1338ecff24ba239978d60f2acaebc..4667f44d65a0310fbf3e32d712dcdbc7aed31f0a 100644 --- a/polkadot/node/core/provisioner/src/tests.rs +++ b/polkadot/node/core/provisioner/src/tests.rs @@ -254,10 +254,12 @@ mod select_candidates { AvailabilityCores, PersistedValidationData as PersistedValidationDataReq, }, }; - use polkadot_node_subsystem_test_helpers::TestSubsystemSender; - use polkadot_node_subsystem_util::runtime::ProspectiveParachainsMode; + use polkadot_node_subsystem_test_helpers::{mock::new_leaf, TestSubsystemSender}; use polkadot_primitives::{ - vstaging::{CommittedCandidateReceiptV2 as CommittedCandidateReceipt, MutateDescriptorV2}, + vstaging::{ + CandidateReceiptV2 as CandidateReceipt, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, MutateDescriptorV2, + }, BlockNumber, CandidateCommitments, PersistedValidationData, }; use polkadot_primitives_test_helpers::{dummy_candidate_descriptor_v2, dummy_hash}; @@ -557,9 +559,7 @@ mod select_candidates { mock_availability_cores: Vec<CoreState>, mut expected: Vec<BackedCandidate>, mut expected_ancestors: HashMap<Vec<CandidateHash>, Ancestors>, - prospective_parachains_mode: ProspectiveParachainsMode, ) { - use ChainApiMessage::BlockNumber; use RuntimeApiMessage::Request; let mut backed = expected.clone().into_iter().fold(HashMap::new(), |mut acc, candidate| { @@ -574,8 +574,6 @@ mod select_candidates { while let Some(from_job) = receiver.next().await { match from_job { - AllMessages::ChainApi(BlockNumber(_relay_parent, tx)) => - tx.send(Ok(Some(BLOCK_UNDER_PRODUCTION - 1))).unwrap(), AllMessages::RuntimeApi(Request( _parent_hash, PersistedValidationDataReq(_para_id, _assumption, tx), @@ -624,175 +622,57 @@ mod select_candidates { actual_ancestors, tx, ), - ) => match prospective_parachains_mode { - ProspectiveParachainsMode::Enabled { .. } => { - assert!(count > 0); - let candidates = - (&mut candidates_iter).take(count as usize).collect::<Vec<_>>(); - assert_eq!(candidates.len(), count as usize); - - if !expected_ancestors.is_empty() { - if let Some(expected_required_ancestors) = expected_ancestors.remove( - &(candidates - .clone() - .into_iter() - .take(actual_ancestors.len()) - .map(|(c_hash, _)| c_hash) - .collect::<Vec<_>>()), - ) { - assert_eq!(expected_required_ancestors, actual_ancestors); - } else { - assert_eq!(actual_ancestors.len(), 0); - } + ) => { + assert!(count > 0); + let candidates = + (&mut candidates_iter).take(count as usize).collect::<Vec<_>>(); + assert_eq!(candidates.len(), count as usize); + + if !expected_ancestors.is_empty() { + if let Some(expected_required_ancestors) = expected_ancestors.remove( + &(candidates + .clone() + .into_iter() + .take(actual_ancestors.len()) + .map(|(c_hash, _)| c_hash) + .collect::<Vec<_>>()), + ) { + assert_eq!(expected_required_ancestors, actual_ancestors); + } else { + assert_eq!(actual_ancestors.len(), 0); } + } - let _ = tx.send(candidates); - }, - ProspectiveParachainsMode::Disabled => - panic!("unexpected prospective parachains request"), + let _ = tx.send(candidates); }, _ => panic!("Unexpected message: {:?}", from_job), } } - if let ProspectiveParachainsMode::Enabled { .. } = prospective_parachains_mode { - assert_eq!(candidates_iter.next(), None); - } + assert_eq!(candidates_iter.next(), None); assert_eq!(expected_ancestors.len(), 0); } - #[rstest] - #[case(ProspectiveParachainsMode::Disabled)] - #[case(ProspectiveParachainsMode::Enabled {max_candidate_depth: 0, allowed_ancestry_len: 0})] - fn can_succeed(#[case] prospective_parachains_mode: ProspectiveParachainsMode) { + #[test] + fn can_succeed() { test_harness( - |r| { - mock_overseer( - r, - Vec::new(), - Vec::new(), - HashMap::new(), - prospective_parachains_mode, - ) - }, + |r| mock_overseer(r, Vec::new(), Vec::new(), HashMap::new()), |mut tx: TestSubsystemSender| async move { select_candidates( &[], &[], - &[], - prospective_parachains_mode, false, - Default::default(), - &mut tx, - ) - .await - .unwrap(); - }, - ) - } - - // Test candidate selection when prospective parachains mode is disabled. - // This tests that only the appropriate candidates get selected when prospective parachains mode - // is disabled. To accomplish this, we supply a candidate list containing one candidate per - // possible core; the candidate selection algorithm must filter them to the appropriate set - #[rstest] - // why those particular indices? see the comments on mock_availability_cores_*() functions. - #[case(mock_availability_cores_one_per_para(), vec![1, 4, 7, 8, 10], true)] - #[case(mock_availability_cores_one_per_para(), vec![1, 4, 7, 8, 10], false)] - #[case(mock_availability_cores_multiple_per_para(), vec![1, 4, 7, 8, 10, 12, 13, 14, 15], true)] - #[case(mock_availability_cores_multiple_per_para(), vec![1, 4, 7, 8, 10, 12, 13, 14, 15], false)] - fn test_in_subsystem_selection( - #[case] mock_cores: Vec<CoreState>, - #[case] expected_candidates: Vec<usize>, - #[case] elastic_scaling_mvp: bool, - ) { - let candidate_template = dummy_candidate_template(); - let candidates: Vec<_> = std::iter::repeat(candidate_template) - .take(mock_cores.len()) - .enumerate() - .map(|(idx, mut candidate)| { - candidate.descriptor.set_para_id(idx.into()); - candidate - }) - .cycle() - .take(mock_cores.len() * 3) - .enumerate() - .map(|(idx, mut candidate)| { - if idx < mock_cores.len() { - // first go-around: use candidates which should work - candidate - } else if idx < mock_cores.len() * 2 { - // for the second repetition of the candidates, give them the wrong hash - candidate.descriptor.set_persisted_validation_data_hash(Default::default()); - candidate - } else { - // third go-around: right hash, wrong para_id - candidate.descriptor.set_para_id(idx.into()); - candidate - } - }) - .collect(); - - let expected_candidates: Vec<_> = - expected_candidates.into_iter().map(|idx| candidates[idx].clone()).collect(); - let prospective_parachains_mode = ProspectiveParachainsMode::Disabled; - - let expected_backed = expected_candidates - .iter() - .map(|c| { - BackedCandidate::new( - CommittedCandidateReceipt { - descriptor: c.descriptor().clone(), - commitments: Default::default(), - }, - Vec::new(), - default_bitvec(MOCK_GROUP_SIZE), - None, - ) - }) - .collect(); - - let mock_cores_clone = mock_cores.clone(); - test_harness( - |r| { - mock_overseer( - r, - mock_cores_clone, - expected_backed, - HashMap::new(), - prospective_parachains_mode, - ) - }, - |mut tx: TestSubsystemSender| async move { - let result: Vec<BackedCandidate> = select_candidates( - &mock_cores, - &[], - &candidates, - prospective_parachains_mode, - elastic_scaling_mvp, - Default::default(), + &new_leaf(Default::default(), BLOCK_UNDER_PRODUCTION - 1), &mut tx, ) .await .unwrap(); - - result.into_iter().for_each(|c| { - assert!( - expected_candidates.iter().any(|c2| c.candidate().corresponds_to(c2)), - "Failed to find candidate: {:?}", - c, - ) - }); }, ) } - #[rstest] - #[case(ProspectiveParachainsMode::Disabled)] - #[case(ProspectiveParachainsMode::Enabled {max_candidate_depth: 0, allowed_ancestry_len: 0})] - fn selects_max_one_code_upgrade_one_core_per_para( - #[case] prospective_parachains_mode: ProspectiveParachainsMode, - ) { + #[test] + fn selects_max_one_code_upgrade_one_core_per_para() { let mock_cores = mock_availability_cores_one_per_para(); let empty_hash = PersistedValidationData::<Hash, BlockNumber>::default().hash(); @@ -855,23 +735,13 @@ mod select_candidates { let mock_cores_clone = mock_cores.clone(); test_harness( - |r| { - mock_overseer( - r, - mock_cores_clone, - expected_backed, - HashMap::new(), - prospective_parachains_mode, - ) - }, + |r| mock_overseer(r, mock_cores_clone, expected_backed, HashMap::new()), |mut tx: TestSubsystemSender| async move { let result = select_candidates( &mock_cores, &[], - &candidates, - prospective_parachains_mode, false, - Default::default(), + &new_leaf(Default::default(), BLOCK_UNDER_PRODUCTION - 1), &mut tx, ) .await @@ -890,8 +760,6 @@ mod select_candidates { #[test] fn selects_max_one_code_upgrade_multiple_cores_per_para() { - let prospective_parachains_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 0, allowed_ancestry_len: 0 }; let mock_cores = vec![ // 0: Scheduled(default), Scheduled(scheduled_core(1)), @@ -970,23 +838,13 @@ mod select_candidates { let mock_cores_clone = mock_cores.clone(); test_harness( - |r| { - mock_overseer( - r, - mock_cores_clone, - expected_backed, - HashMap::new(), - prospective_parachains_mode, - ) - }, + |r| mock_overseer(r, mock_cores_clone, expected_backed, HashMap::new()), |mut tx: TestSubsystemSender| async move { let result = select_candidates( &mock_cores, &[], - &candidates, - prospective_parachains_mode, true, - Default::default(), + &new_leaf(Default::default(), BLOCK_UNDER_PRODUCTION - 1), &mut tx, ) .await @@ -1004,7 +862,7 @@ mod select_candidates { #[rstest] #[case(true)] #[case(false)] - fn request_from_prospective_parachains_one_core_per_para(#[case] elastic_scaling_mvp: bool) { + fn one_core_per_para(#[case] elastic_scaling_mvp: bool) { let mock_cores = mock_availability_cores_one_per_para(); // why those particular indices? see the comments on mock_availability_cores() @@ -1012,10 +870,6 @@ mod select_candidates { let (candidates, expected_candidates) = make_candidates(mock_cores.len() + 1, expected_candidates); - // Expect prospective parachains subsystem requests. - let prospective_parachains_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 0, allowed_ancestry_len: 0 }; - let mut required_ancestors: HashMap<Vec<CandidateHash>, Ancestors> = HashMap::new(); required_ancestors.insert( vec![candidates[4]], @@ -1029,23 +883,13 @@ mod select_candidates { let mock_cores_clone = mock_cores.clone(); let expected_candidates_clone = expected_candidates.clone(); test_harness( - |r| { - mock_overseer( - r, - mock_cores_clone, - expected_candidates_clone, - required_ancestors, - prospective_parachains_mode, - ) - }, + |r| mock_overseer(r, mock_cores_clone, expected_candidates_clone, required_ancestors), |mut tx: TestSubsystemSender| async move { let result = select_candidates( &mock_cores, &[], - &[], - prospective_parachains_mode, elastic_scaling_mvp, - Default::default(), + &new_leaf(Default::default(), BLOCK_UNDER_PRODUCTION - 1), &mut tx, ) .await @@ -1066,15 +910,12 @@ mod select_candidates { } #[test] - fn request_from_prospective_parachains_multiple_cores_per_para_elastic_scaling_mvp() { + fn multiple_cores_per_para_elastic_scaling_mvp() { let mock_cores = mock_availability_cores_multiple_per_para(); // why those particular indices? see the comments on mock_availability_cores() let expected_candidates: Vec<_> = vec![1, 4, 7, 8, 10, 12, 12, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15]; - // Expect prospective parachains subsystem requests. - let prospective_parachains_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 0, allowed_ancestry_len: 0 }; let (candidates, expected_candidates) = make_candidates(mock_cores.len(), expected_candidates); @@ -1119,23 +960,13 @@ mod select_candidates { let mock_cores_clone = mock_cores.clone(); let expected_candidates_clone = expected_candidates.clone(); test_harness( - |r| { - mock_overseer( - r, - mock_cores_clone, - expected_candidates, - required_ancestors, - prospective_parachains_mode, - ) - }, + |r| mock_overseer(r, mock_cores_clone, expected_candidates, required_ancestors), |mut tx: TestSubsystemSender| async move { let result = select_candidates( &mock_cores, &[], - &[], - prospective_parachains_mode, true, - Default::default(), + &new_leaf(Default::default(), BLOCK_UNDER_PRODUCTION - 1), &mut tx, ) .await @@ -1156,14 +987,11 @@ mod select_candidates { } #[test] - fn request_from_prospective_parachains_multiple_cores_per_para_elastic_scaling_mvp_disabled() { + fn multiple_cores_per_para_elastic_scaling_mvp_disabled() { let mock_cores = mock_availability_cores_multiple_per_para(); // why those particular indices? see the comments on mock_availability_cores() let expected_candidates: Vec<_> = vec![1, 4, 7, 8, 10]; - // Expect prospective parachains subsystem requests. - let prospective_parachains_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 0, allowed_ancestry_len: 0 }; let (candidates, expected_candidates) = make_candidates(mock_cores.len(), expected_candidates); @@ -1181,23 +1009,13 @@ mod select_candidates { let mock_cores_clone = mock_cores.clone(); let expected_candidates_clone = expected_candidates.clone(); test_harness( - |r| { - mock_overseer( - r, - mock_cores_clone, - expected_candidates, - required_ancestors, - prospective_parachains_mode, - ) - }, + |r| mock_overseer(r, mock_cores_clone, expected_candidates, required_ancestors), |mut tx: TestSubsystemSender| async move { let result = select_candidates( &mock_cores, &[], - &[], - prospective_parachains_mode, false, - Default::default(), + &new_leaf(Default::default(), BLOCK_UNDER_PRODUCTION - 1), &mut tx, ) .await @@ -1235,9 +1053,6 @@ mod select_candidates { // why those particular indices? see the comments on mock_availability_cores() let expected_candidates: Vec<_> = [1, 4, 7, 8, 10, 12].iter().map(|&idx| candidates[idx].clone()).collect(); - // Expect prospective parachains subsystem requests. - let prospective_parachains_mode = - ProspectiveParachainsMode::Enabled { max_candidate_depth: 0, allowed_ancestry_len: 0 }; let expected_backed = expected_candidates .iter() @@ -1256,23 +1071,13 @@ mod select_candidates { let mock_cores_clone = mock_cores.clone(); test_harness( - |r| { - mock_overseer( - r, - mock_cores_clone, - expected_backed, - HashMap::new(), - prospective_parachains_mode, - ) - }, + |r| mock_overseer(r, mock_cores_clone, expected_backed, HashMap::new()), |mut tx: TestSubsystemSender| async move { let result = select_candidates( &mock_cores, &[], - &[], - prospective_parachains_mode, false, - Default::default(), + &new_leaf(Default::default(), BLOCK_UNDER_PRODUCTION - 1), &mut tx, ) .await diff --git a/polkadot/node/core/pvf-checker/Cargo.toml b/polkadot/node/core/pvf-checker/Cargo.toml index 73ef17a2843aedcffc40c6456d5519e62265b605..fac5f85b6b565cb24b95a9ec1e09dfa511a00d74 100644 --- a/polkadot/node/core/pvf-checker/Cargo.toml +++ b/polkadot/node/core/pvf-checker/Cargo.toml @@ -5,29 +5,31 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] futures = { workspace = true } -thiserror = { workspace = true } gum = { workspace = true, default-features = true } +thiserror = { workspace = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } [dev-dependencies] -sp-core = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } +futures-timer = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-primitives-test-helpers = { workspace = true } +sc-keystore = { workspace = true, default-features = true } sp-application-crypto = { workspace = true, default-features = true } -futures-timer = { workspace = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } diff --git a/polkadot/node/core/pvf-checker/src/interest_view.rs b/polkadot/node/core/pvf-checker/src/interest_view.rs index 05a6f12de5d8fbe2a0f1529af1dbbb742ecca8c9..617d0e0b5d88dd377ec9eeadaa8a53725daddf9d 100644 --- a/polkadot/node/core/pvf-checker/src/interest_view.rs +++ b/polkadot/node/core/pvf-checker/src/interest_view.rs @@ -58,7 +58,7 @@ impl PvfData { Self { judgement: None, seen_in } } - /// Mark a the `PvfData` as seen in the provided relay-chain block referenced by `relay_hash`. + /// Mark the `PvfData` as seen in the provided relay-chain block referenced by `relay_hash`. pub fn seen_in(&mut self, relay_hash: Hash) { self.seen_in.insert(relay_hash); } diff --git a/polkadot/node/core/pvf/Cargo.toml b/polkadot/node/core/pvf/Cargo.toml index 37d5878ea59700208a2d26e45018fbbf407b7725..f47f7b734285401ab814d82a143c4863000f5517 100644 --- a/polkadot/node/core/pvf/Cargo.toml +++ b/polkadot/node/core/pvf/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -21,28 +23,28 @@ is_executable = { optional = true, workspace = true } pin-project = { workspace = true } rand = { workspace = true, default-features = true } slotmap = { workspace = true } +strum = { features = ["derive"], workspace = true, default-features = true } tempfile = { workspace = true } thiserror = { workspace = true } tokio = { features = ["fs", "process"], workspace = true, default-features = true } -strum = { features = ["derive"], workspace = true, default-features = true } codec = { features = [ "derive", ], workspace = true } -polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-core-primitives = { workspace = true, default-features = true } polkadot-node-core-pvf-common = { workspace = true, default-features = true } polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } +polkadot-node-core-pvf-execute-worker = { optional = true, workspace = true, default-features = true } +polkadot-node-core-pvf-prepare-worker = { optional = true, workspace = true, default-features = true } sc-tracing = { workspace = true } sp-core = { workspace = true, default-features = true } sp-maybe-compressed-blob = { optional = true, workspace = true, default-features = true } -polkadot-node-core-pvf-prepare-worker = { optional = true, workspace = true, default-features = true } -polkadot-node-core-pvf-execute-worker = { optional = true, workspace = true, default-features = true } [dev-dependencies] assert_matches = { workspace = true } diff --git a/polkadot/node/core/pvf/common/Cargo.toml b/polkadot/node/core/pvf/common/Cargo.toml index 903c8dd1af2970adca2ff4f62b8d243f87bc88e0..d058d582fc266ff303e9376601d593fdce88331d 100644 --- a/polkadot/node/core/pvf/common/Cargo.toml +++ b/polkadot/node/core/pvf/common/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/pvf/execute-worker/Cargo.toml b/polkadot/node/core/pvf/execute-worker/Cargo.toml index 6ad340d25612860f87469497c332079e11eda266..4df425dfd199bb376f8a8fd600c927069d986bcb 100644 --- a/polkadot/node/core/pvf/execute-worker/Cargo.toml +++ b/polkadot/node/core/pvf/execute-worker/Cargo.toml @@ -5,16 +5,18 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +cfg-if = { workspace = true } cpu-time = { workspace = true } gum = { workspace = true, default-features = true } -cfg-if = { workspace = true } -nix = { features = ["process", "resource", "sched"], workspace = true } libc = { workspace = true } +nix = { features = ["process", "resource", "sched"], workspace = true } codec = { features = ["derive"], workspace = true } diff --git a/polkadot/node/core/pvf/prepare-worker/Cargo.toml b/polkadot/node/core/pvf/prepare-worker/Cargo.toml index 56235bd82192f2e671eb0b9e08006a186966a814..aa551c196c37d872a5d9d6ca9486a3224d2baeb2 100644 --- a/polkadot/node/core/pvf/prepare-worker/Cargo.toml +++ b/polkadot/node/core/pvf/prepare-worker/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -14,11 +16,11 @@ blake3 = { workspace = true } cfg-if = { workspace = true } gum = { workspace = true, default-features = true } libc = { workspace = true } +nix = { features = ["process", "resource", "sched"], workspace = true } rayon = { workspace = true } -tracking-allocator = { workspace = true, default-features = true } tikv-jemalloc-ctl = { optional = true, workspace = true } tikv-jemallocator = { optional = true, workspace = true } -nix = { features = ["process", "resource", "sched"], workspace = true } +tracking-allocator = { workspace = true, default-features = true } codec = { features = ["derive"], workspace = true } diff --git a/polkadot/node/core/pvf/tests/it/process.rs b/polkadot/node/core/pvf/tests/it/process.rs index 353367b394f348551bc4ebc93ca384bb30190db9..29326365b5baafbdb7bed9b6694ed28d354c809d 100644 --- a/polkadot/node/core/pvf/tests/it/process.rs +++ b/polkadot/node/core/pvf/tests/it/process.rs @@ -77,7 +77,9 @@ fn find_process_by_sid_and_name( let mut found = None; for process in all_processes { - let stat = process.stat().expect("/proc existed above. Potential race occurred"); + let Ok(stat) = process.stat() else { + continue; + }; if stat.session != sid || !process.exe().unwrap().to_str().unwrap().contains(exe_name) { continue diff --git a/polkadot/node/core/runtime-api/Cargo.toml b/polkadot/node/core/runtime-api/Cargo.toml index 834e4b300b9eba67c4764ac60e00261b66215250..65c92dc5c07046fa55aeffad8d4cccb8b5782ede 100644 --- a/polkadot/node/core/runtime-api/Cargo.toml +++ b/polkadot/node/core/runtime-api/Cargo.toml @@ -5,6 +5,8 @@ description = "Wrapper around the parachain-related runtime APIs" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -16,17 +18,17 @@ schnellru = { workspace = true } sp-consensus-babe = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-types = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } [dev-dependencies] -sp-api = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } async-trait = { workspace = true } futures = { features = ["thread-pool"], workspace = true } -polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-primitives-test-helpers = { workspace = true } +sp-api = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } diff --git a/polkadot/node/core/runtime-api/src/cache.rs b/polkadot/node/core/runtime-api/src/cache.rs index 7246010711e40647c2eec061ddfb6b8b7144d6e5..4ed42626d88eeba2fdcfcc0f3960b4803361dbb9 100644 --- a/polkadot/node/core/runtime-api/src/cache.rs +++ b/polkadot/node/core/runtime-api/src/cache.rs @@ -20,10 +20,10 @@ use schnellru::{ByLength, LruMap}; use sp_consensus_babe::Epoch; use polkadot_primitives::{ - async_backing, slashing, vstaging, + async_backing, slashing, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + self, async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, @@ -75,6 +75,8 @@ pub(crate) struct RequestResultCache { node_features: LruMap<SessionIndex, NodeFeatures>, approval_voting_params: LruMap<SessionIndex, ApprovalVotingParams>, claim_queue: LruMap<Hash, BTreeMap<CoreIndex, VecDeque<ParaId>>>, + backing_constraints: LruMap<(Hash, ParaId), Option<Constraints>>, + scheduling_lookahead: LruMap<SessionIndex, u32>, } impl Default for RequestResultCache { @@ -112,6 +114,8 @@ impl Default for RequestResultCache { async_backing_params: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), node_features: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), claim_queue: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), + backing_constraints: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), + scheduling_lookahead: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), } } } @@ -559,10 +563,36 @@ impl RequestResultCache { ) { self.claim_queue.insert(relay_parent, value); } + + pub(crate) fn backing_constraints( + &mut self, + key: (Hash, ParaId), + ) -> Option<&Option<Constraints>> { + self.backing_constraints.get(&key).map(|v| &*v) + } + + pub(crate) fn cache_backing_constraints( + &mut self, + key: (Hash, ParaId), + value: Option<Constraints>, + ) { + self.backing_constraints.insert(key, value); + } + + pub(crate) fn scheduling_lookahead(&mut self, session_index: SessionIndex) -> Option<u32> { + self.scheduling_lookahead.get(&session_index).copied() + } + + pub(crate) fn cache_scheduling_lookahead( + &mut self, + session_index: SessionIndex, + scheduling_lookahead: u32, + ) { + self.scheduling_lookahead.insert(session_index, scheduling_lookahead); + } } pub(crate) enum RequestResult { - // The structure of each variant is (relay_parent, [params,]*, result) Authorities(Hash, Vec<AuthorityDiscoveryId>), Validators(Hash, Vec<ValidatorId>), MinimumBackingVotes(SessionIndex, u32), @@ -610,4 +640,6 @@ pub(crate) enum RequestResult { NodeFeatures(SessionIndex, NodeFeatures), ClaimQueue(Hash, BTreeMap<CoreIndex, VecDeque<ParaId>>), CandidatesPendingAvailability(Hash, ParaId, Vec<CommittedCandidateReceipt>), + BackingConstraints(Hash, ParaId, Option<Constraints>), + SchedulingLookahead(SessionIndex, u32), } diff --git a/polkadot/node/core/runtime-api/src/lib.rs b/polkadot/node/core/runtime-api/src/lib.rs index c8b1d61e7be720734a65484a86f4685fc61580b8..2d864c8cf2f4c59355c35161f5ecab90a0dfeb30 100644 --- a/polkadot/node/core/runtime-api/src/lib.rs +++ b/polkadot/node/core/runtime-api/src/lib.rs @@ -168,7 +168,7 @@ where KeyOwnershipProof(relay_parent, validator_id, key_ownership_proof) => self .requests_cache .cache_key_ownership_proof((relay_parent, validator_id), key_ownership_proof), - RequestResult::ApprovalVotingParams(_relay_parent, session_index, params) => + ApprovalVotingParams(_relay_parent, session_index, params) => self.requests_cache.cache_approval_voting_params(session_index, params), SubmitReportDisputeLost(_) => {}, DisabledValidators(relay_parent, disabled_validators) => @@ -183,6 +183,12 @@ where ClaimQueue(relay_parent, sender) => { self.requests_cache.cache_claim_queue(relay_parent, sender); }, + BackingConstraints(relay_parent, para_id, constraints) => self + .requests_cache + .cache_backing_constraints((relay_parent, para_id), constraints), + SchedulingLookahead(session_index, scheduling_lookahead) => self + .requests_cache + .cache_scheduling_lookahead(session_index, scheduling_lookahead), } } @@ -340,6 +346,17 @@ where }, Request::ClaimQueue(sender) => query!(claim_queue(), sender).map(|sender| Request::ClaimQueue(sender)), + Request::BackingConstraints(para, sender) => query!(backing_constraints(para), sender) + .map(|sender| Request::BackingConstraints(para, sender)), + Request::SchedulingLookahead(index, sender) => { + if let Some(value) = self.requests_cache.scheduling_lookahead(index) { + self.metrics.on_cached_request(); + let _ = sender.send(Ok(value)); + None + } else { + Some(Request::SchedulingLookahead(index, sender)) + } + }, } } @@ -652,5 +669,20 @@ where ver = Request::CLAIM_QUEUE_RUNTIME_REQUIREMENT, sender ), + Request::BackingConstraints(para, sender) => { + query!( + BackingConstraints, + backing_constraints(para), + ver = Request::CONSTRAINTS_RUNTIME_REQUIREMENT, + sender + ) + }, + Request::SchedulingLookahead(index, sender) => query!( + SchedulingLookahead, + scheduling_lookahead(), + ver = Request::SCHEDULING_LOOKAHEAD_RUNTIME_REQUIREMENT, + sender, + result = (index) + ), } } diff --git a/polkadot/node/core/runtime-api/src/tests.rs b/polkadot/node/core/runtime-api/src/tests.rs index d4fa07323886dd834ac16f3b5ac7d6e24042a18f..bbc580129002271ad718e75d1b31874ddc839064 100644 --- a/polkadot/node/core/runtime-api/src/tests.rs +++ b/polkadot/node/core/runtime-api/src/tests.rs @@ -22,8 +22,8 @@ use polkadot_node_subsystem_test_helpers::make_subsystem_context; use polkadot_primitives::{ async_backing, slashing, vstaging, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Id as ParaId, @@ -307,6 +307,18 @@ impl RuntimeApiSubsystemClient for MockSubsystemClient { ) -> Result<BTreeMap<CoreIndex, VecDeque<ParaId>>, ApiError> { todo!("Not required for tests") } + + async fn scheduling_lookahead(&self, _: Hash) -> Result<u32, ApiError> { + todo!("Not required for tests") + } + + async fn backing_constraints( + &self, + _at: Hash, + _para_id: ParaId, + ) -> Result<Option<Constraints>, ApiError> { + todo!("Not required for tests") + } } #[test] diff --git a/polkadot/node/gum/Cargo.toml b/polkadot/node/gum/Cargo.toml index 9b2df435a06a90e4cb0561e19ce4a85dd281b127..f4c22dd7595e01ee0375e83e8e68829f7db463a3 100644 --- a/polkadot/node/gum/Cargo.toml +++ b/polkadot/node/gum/Cargo.toml @@ -5,12 +5,14 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Stick logs together with the TraceID as provided by tempo" +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] coarsetime = { workspace = true } -tracing = { workspace = true, default-features = true } gum-proc-macro = { workspace = true, default-features = true } polkadot-primitives = { features = ["std"], workspace = true, default-features = true } +tracing = { workspace = true, default-features = true } diff --git a/polkadot/node/gum/proc-macro/Cargo.toml b/polkadot/node/gum/proc-macro/Cargo.toml index da6364977cae25f16e8605f8024dc96aa86f6120..0b69d8b67cf194ddf91890797415369538307575 100644 --- a/polkadot/node/gum/proc-macro/Cargo.toml +++ b/polkadot/node/gum/proc-macro/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Generate an overseer including builder pattern and message wrapper from a single annotated struct definition." +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -16,11 +18,11 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { features = ["extra-traits", "full"], workspace = true } -quote = { workspace = true } -proc-macro2 = { workspace = true } -proc-macro-crate = { workspace = true } expander = { workspace = true } +proc-macro-crate = { workspace = true } +proc-macro2 = { workspace = true } +quote = { workspace = true } +syn = { features = ["extra-traits", "full"], workspace = true } [dev-dependencies] assert_matches = { workspace = true } diff --git a/polkadot/node/gum/proc-macro/src/lib.rs b/polkadot/node/gum/proc-macro/src/lib.rs index e8b6b599172d5eae93c928198ac9fc6bd7a42edc..96ff4417a5a27ccb1222cea57672f50f1d562fa6 100644 --- a/polkadot/node/gum/proc-macro/src/lib.rs +++ b/polkadot/node/gum/proc-macro/src/lib.rs @@ -90,7 +90,7 @@ pub(crate) fn gum(item: proc_macro::TokenStream, level: Level) -> proc_macro::To .add_comment("Generated overseer code by `gum::warn!(..)`".to_owned()) // `dry=true` until rust-analyzer can selectively disable features so it's // not all red squiggles. Originally: `!cfg!(feature = "expand")` - // ISSUE: <https://github.com/rust-analyzer/rust-analyzer/issues/11777> + // ISSUE: https://github.com/rust-lang/rust-analyzer/issues/11777 .dry(true) .verbose(false) .fmt(expander::Edition::_2021) diff --git a/polkadot/node/malus/Cargo.toml b/polkadot/node/malus/Cargo.toml index 49434606a61c8dff08a7c075c06c998cc8395a5b..84a58f382e20504bfa0b56835fb72243af5ae4bd 100644 --- a/polkadot/node/malus/Cargo.toml +++ b/polkadot/node/malus/Cargo.toml @@ -29,27 +29,27 @@ path = "../../src/bin/prepare-worker.rs" doc = false [dependencies] -polkadot-cli = { features = ["malus", "rococo-native", "westend-native"], workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } -polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-node-subsystem-types = { workspace = true, default-features = true } -polkadot-node-core-dispute-coordinator = { workspace = true, default-features = true } -polkadot-node-core-candidate-validation = { workspace = true, default-features = true } -polkadot-node-core-backing = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-node-network-protocol = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } -color-eyre = { workspace = true } assert_matches = { workspace = true } async-trait = { workspace = true } -sp-keystore = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } clap = { features = ["derive"], workspace = true } +color-eyre = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } gum = { workspace = true, default-features = true } +polkadot-cli = { features = ["malus", "rococo-native", "westend-native"], workspace = true, default-features = true } polkadot-erasure-coding = { workspace = true, default-features = true } +polkadot-node-core-backing = { workspace = true, default-features = true } +polkadot-node-core-candidate-validation = { workspace = true, default-features = true } +polkadot-node-core-dispute-coordinator = { workspace = true, default-features = true } +polkadot-node-network-protocol = { workspace = true, default-features = true } +polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-node-subsystem-types = { workspace = true, default-features = true } +polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } # Required for worker binaries to build. polkadot-node-core-pvf-common = { workspace = true, default-features = true } @@ -57,9 +57,9 @@ polkadot-node-core-pvf-execute-worker = { workspace = true, default-features = t polkadot-node-core-pvf-prepare-worker = { workspace = true, default-features = true } [dev-dependencies] +futures = { features = ["thread-pool"], workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } sp-core = { workspace = true, default-features = true } -futures = { features = ["thread-pool"], workspace = true } [build-dependencies] substrate-build-script-utils = { workspace = true, default-features = true } diff --git a/polkadot/node/metrics/Cargo.toml b/polkadot/node/metrics/Cargo.toml index 41b08b66e9b4881bf93285b02076714bf41dfb0f..8d15391b11c2a275cf9cf85c4f5d29538ace0e75 100644 --- a/polkadot/node/metrics/Cargo.toml +++ b/polkadot/node/metrics/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -16,28 +18,26 @@ gum = { workspace = true, default-features = true } metered = { features = ["futures_channel"], workspace = true } # Both `sc-service` and `sc-cli` are required by runtime metrics `logger_hook()`. +sc-cli = { workspace = true } sc-service = { workspace = true, default-features = true } -sc-cli = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } -sc-tracing = { workspace = true, default-features = true } -codec = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } bs58 = { features = ["alloc"], workspace = true, default-features = true } +codec = { workspace = true, default-features = true } log = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +prometheus-endpoint = { workspace = true, default-features = true } +sc-tracing = { workspace = true, default-features = true } [dev-dependencies] -assert_cmd = { workspace = true } -tempfile = { workspace = true } -hyper-util = { features = ["client-legacy", "tokio"], workspace = true } -hyper = { workspace = true } http-body-util = { workspace = true } -tokio = { workspace = true, default-features = true } +hyper = { workspace = true } +hyper-util = { features = ["client-legacy", "tokio"], workspace = true } polkadot-test-service = { features = ["runtime-metrics"], workspace = true } -substrate-test-utils = { workspace = true } +prometheus-parse = { workspace = true } sc-service = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -prometheus-parse = { workspace = true } +tempfile = { workspace = true } +tokio = { workspace = true, default-features = true } [features] default = [] diff --git a/polkadot/node/metrics/src/tests.rs b/polkadot/node/metrics/src/tests.rs index 4760138058eb7180232ddb81ded487e5d7713420..43dce0ec2ffe474a2e322b47f814329719fd66e4 100644 --- a/polkadot/node/metrics/src/tests.rs +++ b/polkadot/node/metrics/src/tests.rs @@ -21,7 +21,7 @@ use hyper::Uri; use hyper_util::{client::legacy::Client, rt::TokioExecutor}; use polkadot_primitives::metric_definitions::PARACHAIN_INHERENT_DATA_BITFIELDS_PROCESSED; use polkadot_test_service::{node_config, run_validator_node, test_prometheus_config}; -use sp_keyring::AccountKeyring::*; +use sp_keyring::Sr25519Keyring::*; use std::collections::HashMap; const DEFAULT_PROMETHEUS_PORT: u16 = 9616; diff --git a/polkadot/node/network/approval-distribution/Cargo.toml b/polkadot/node/network/approval-distribution/Cargo.toml index 8d674a733470681f4a26335fa51592963165315b..d9d3fd8635a62477034a103cb5c9fe8db6b130c8 100644 --- a/polkadot/node/network/approval-distribution/Cargo.toml +++ b/polkadot/node/network/approval-distribution/Cargo.toml @@ -5,11 +5,14 @@ description = "Polkadot Approval Distribution subsystem for the distribution of authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +itertools = { workspace = true } polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } @@ -17,12 +20,11 @@ polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } -itertools = { workspace = true } +bitvec = { features = ["alloc"], workspace = true } futures = { workspace = true } futures-timer = { workspace = true } gum = { workspace = true, default-features = true } -bitvec = { features = ["alloc"], workspace = true } [dev-dependencies] sc-keystore = { workspace = true } @@ -36,7 +38,7 @@ polkadot-primitives-test-helpers = { workspace = true } assert_matches = { workspace = true } schnorrkel = { workspace = true } # rand_core should match schnorrkel -rand_core = { workspace = true } +log = { workspace = true, default-features = true } rand_chacha = { workspace = true, default-features = true } +rand_core = { workspace = true } sp-tracing = { workspace = true } -log = { workspace = true, default-features = true } diff --git a/polkadot/node/network/approval-distribution/src/lib.rs b/polkadot/node/network/approval-distribution/src/lib.rs index 876cc59b9c283affbcf3a1e708625d8ca41494fd..cefb1d74499298cbf8b8c314c8f3d5573f2da142 100644 --- a/polkadot/node/network/approval-distribution/src/lib.rs +++ b/polkadot/node/network/approval-distribution/src/lib.rs @@ -163,8 +163,6 @@ enum ApprovalEntryError { InvalidCandidateIndex, DuplicateApproval, UnknownAssignment, - #[allow(dead_code)] - AssignmentsFollowedDifferentPaths(RequiredRouting, RequiredRouting), } impl ApprovalEntry { @@ -318,7 +316,7 @@ impl Default for AggressionConfig { fn default() -> Self { AggressionConfig { l1_threshold: Some(16), - l2_threshold: Some(28), + l2_threshold: Some(64), resend_unfinalized_period: Some(8), } } @@ -514,6 +512,8 @@ struct BlockEntry { vrf_story: RelayVRFStory, /// The block slot. slot: Slot, + /// Backing off from re-sending messages to peers. + last_resent_at_block_number: Option<u32>, } impl BlockEntry { @@ -568,7 +568,7 @@ impl BlockEntry { &mut self, approval: IndirectSignedApprovalVoteV2, ) -> Result<(RequiredRouting, HashSet<PeerId>), ApprovalEntryError> { - let mut required_routing = None; + let mut required_routing: Option<RequiredRouting> = None; let mut peers_randomly_routed_to = HashSet::new(); if self.candidates.len() < approval.candidate_indices.len() as usize { @@ -595,16 +595,11 @@ impl BlockEntry { peers_randomly_routed_to .extend(approval_entry.routing_info().peers_randomly_routed.iter()); - if let Some(required_routing) = required_routing { - if required_routing != approval_entry.routing_info().required_routing { - // This shouldn't happen since the required routing is computed based on the - // validator_index, so two assignments from the same validators will have - // the same required routing. - return Err(ApprovalEntryError::AssignmentsFollowedDifferentPaths( - required_routing, - approval_entry.routing_info().required_routing, - )) - } + if let Some(current_required_routing) = required_routing { + required_routing = Some( + current_required_routing + .combine(approval_entry.routing_info().required_routing), + ); } else { required_routing = Some(approval_entry.routing_info().required_routing) } @@ -885,6 +880,7 @@ impl State { candidates_metadata: meta.candidates, vrf_story: meta.vrf_story, slot: meta.slot, + last_resent_at_block_number: None, }); self.topologies.inc_session_refs(meta.session); @@ -1324,6 +1320,33 @@ impl State { self.enable_aggression(network_sender, Resend::No, metrics).await; } + // When finality is lagging as a last resort nodes start sending the messages they have + // multiples times. This means it is safe to accept duplicate messages without punishing the + // peer and reduce the reputation and can end up banning the Peer, which in turn will create + // more no-shows. + fn accept_duplicates_from_validators( + blocks_by_number: &BTreeMap<BlockNumber, Vec<Hash>>, + topologies: &SessionGridTopologies, + aggression_config: &AggressionConfig, + entry: &BlockEntry, + peer: PeerId, + ) -> bool { + let topology = topologies.get_topology(entry.session); + let min_age = blocks_by_number.iter().next().map(|(num, _)| num); + let max_age = blocks_by_number.iter().rev().next().map(|(num, _)| num); + + // Return if we don't have at least 1 block. + let (min_age, max_age) = match (min_age, max_age) { + (Some(min), Some(max)) => (*min, *max), + _ => return false, + }; + + let age = max_age.saturating_sub(min_age); + + aggression_config.should_trigger_aggression(age) && + topology.map(|topology| topology.is_validator(&peer)).unwrap_or(false) + } + async fn import_and_circulate_assignment<A, N, RA, R>( &mut self, approval_voting_sender: &mut A, @@ -1388,20 +1411,29 @@ impl State { if peer_knowledge.contains(&message_subject, message_kind) { // wasn't included before if !peer_knowledge.received.insert(message_subject.clone(), message_kind) { - gum::debug!( - target: LOG_TARGET, - ?peer_id, - ?message_subject, - "Duplicate assignment", - ); - - modify_reputation( - &mut self.reputation, - network_sender, + if !Self::accept_duplicates_from_validators( + &self.blocks_by_number, + &self.topologies, + &self.aggression_config, + entry, peer_id, - COST_DUPLICATE_MESSAGE, - ) - .await; + ) { + gum::debug!( + target: LOG_TARGET, + ?peer_id, + ?message_subject, + "Duplicate assignment", + ); + + modify_reputation( + &mut self.reputation, + network_sender, + peer_id, + COST_DUPLICATE_MESSAGE, + ) + .await; + } + metrics.on_assignment_duplicate(); } else { gum::trace!( @@ -1717,6 +1749,9 @@ impl State { assignments_knowledge_key: &Vec<(MessageSubject, MessageKind)>, approval_knowledge_key: &(MessageSubject, MessageKind), entry: &mut BlockEntry, + blocks_by_number: &BTreeMap<BlockNumber, Vec<Hash>>, + topologies: &SessionGridTopologies, + aggression_config: &AggressionConfig, reputation: &mut ReputationAggregator, peer_id: PeerId, metrics: &Metrics, @@ -1745,20 +1780,27 @@ impl State { .received .insert(approval_knowledge_key.0.clone(), approval_knowledge_key.1) { - gum::trace!( - target: LOG_TARGET, - ?peer_id, - ?approval_knowledge_key, - "Duplicate approval", - ); - - modify_reputation( - reputation, - network_sender, + if !Self::accept_duplicates_from_validators( + blocks_by_number, + topologies, + aggression_config, + entry, peer_id, - COST_DUPLICATE_MESSAGE, - ) - .await; + ) { + gum::trace!( + target: LOG_TARGET, + ?peer_id, + ?approval_knowledge_key, + "Duplicate approval", + ); + modify_reputation( + reputation, + network_sender, + peer_id, + COST_DUPLICATE_MESSAGE, + ) + .await; + } metrics.on_approval_duplicate(); } return false @@ -1850,6 +1892,9 @@ impl State { &assignments_knowledge_keys, &approval_knwowledge_key, entry, + &self.blocks_by_number, + &self.topologies, + &self.aggression_config, &mut self.reputation, peer_id, metrics, @@ -2260,18 +2305,43 @@ impl State { &self.topologies, |block_entry| { let block_age = max_age - block_entry.number; + // We want to resend only for blocks of min_age, there is no point in + // resending for blocks newer than that, because we are just going to create load + // and not gain anything. + let diff_from_min_age = block_entry.number - min_age; + + // We want to back-off on resending for blocks that have been resent recently, to + // give time for nodes to process all the extra messages, if we still have not + // finalized we are going to resend again after unfinalized_period * 2 since the + // last resend. + let blocks_since_last_sent = block_entry + .last_resent_at_block_number + .map(|last_resent_at_block_number| max_age - last_resent_at_block_number); + + let can_resend_at_this_age = blocks_since_last_sent + .zip(config.resend_unfinalized_period) + .map(|(blocks_since_last_sent, unfinalized_period)| { + blocks_since_last_sent >= unfinalized_period * 2 + }) + .unwrap_or(true); if resend == Resend::Yes && - config - .resend_unfinalized_period - .as_ref() - .map_or(false, |p| block_age > 0 && block_age % p == 0) - { + config.resend_unfinalized_period.as_ref().map_or(false, |p| { + block_age > 0 && + block_age % p == 0 && diff_from_min_age == 0 && + can_resend_at_this_age + }) { // Retry sending to all peers. for (_, knowledge) in block_entry.known_by.iter_mut() { knowledge.sent = Knowledge::default(); } - + block_entry.last_resent_at_block_number = Some(max_age); + gum::debug!( + target: LOG_TARGET, + block_number = ?block_entry.number, + ?max_age, + "Aggression enabled with resend for block", + ); true } else { false diff --git a/polkadot/node/network/approval-distribution/src/tests.rs b/polkadot/node/network/approval-distribution/src/tests.rs index 063e71f2f5289f3108468e81273dbca837f1c07e..5d79260e3ad2579a88894170c596f9e670d2bd66 100644 --- a/polkadot/node/network/approval-distribution/src/tests.rs +++ b/polkadot/node/network/approval-distribution/src/tests.rs @@ -1030,6 +1030,141 @@ fn peer_sending_us_the_same_we_just_sent_them_is_ok() { ); } +#[test] +fn peer_sending_us_duplicates_while_aggression_enabled_is_ok() { + let parent_hash = Hash::repeat_byte(0xFF); + let hash = Hash::repeat_byte(0xAA); + + let peers = make_peers_and_authority_ids(8); + let peer_a = peers.first().unwrap().0; + + let _ = test_harness( + Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), + Arc::new(SystemClock {}), + state_without_reputation_delay(), + |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + let peer = &peer_a; + setup_peer_with_view(overseer, peer, view![], ValidationVersion::V3).await; + + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); + // Setup a topology where peer_a is neighbor to current node. + setup_gossip_topology( + overseer, + make_gossip_topology(1, &peers_with_optional_peer_id, &[0], &[2], 1), + ) + .await; + + // new block `hash` with 1 candidates + let meta = BlockApprovalMeta { + hash, + parent_hash, + number: 1, + candidates: vec![Default::default(); 1], + slot: 1.into(), + session: 1, + vrf_story: RelayVRFStory(Default::default()), + }; + let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); + overseer_send(overseer, msg).await; + + // import an assignment related to `hash` locally + let validator_index = ValidatorIndex(0); + let candidate_indices: CandidateBitfield = + vec![0 as CandidateIndex].try_into().unwrap(); + let candidate_bitfields = vec![CoreIndex(0)].try_into().unwrap(); + let cert = fake_assignment_cert_v2(hash, validator_index, candidate_bitfields); + overseer_send( + overseer, + ApprovalDistributionMessage::DistributeAssignment( + cert.clone().into(), + candidate_indices.clone(), + ), + ) + .await; + + // update peer view to include the hash + overseer_send( + overseer, + ApprovalDistributionMessage::NetworkBridgeUpdate( + NetworkBridgeEvent::PeerViewChange(*peer, view![hash]), + ), + ) + .await; + + // we should send them the assignment + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) + )) + )) => { + assert_eq!(peers.len(), 1); + assert_eq!(assignments.len(), 1); + } + ); + + // but if someone else is sending it the same assignment + // the peer could send us it as well + let assignments = vec![(cert, candidate_indices)]; + let msg = protocol_v3::ApprovalDistributionMessage::Assignments(assignments); + send_message_from_peer_v3(overseer, peer, msg.clone()).await; + + assert!( + overseer.recv().timeout(TIMEOUT).await.is_none(), + "we should not punish the peer" + ); + + // send the assignments again + send_message_from_peer_v3(overseer, peer, msg.clone()).await; + + // now we should + expect_reputation_change(overseer, peer, COST_DUPLICATE_MESSAGE).await; + + // Peers will be continously punished for sending duplicates until approval-distribution + // aggression kicks, at which point they aren't anymore. + let mut parent_hash = hash; + for level in 0..16 { + // As long as the lag is bellow l1 aggression, punish peers for duplicates. + send_message_from_peer_v3(overseer, peer, msg.clone()).await; + expect_reputation_change(overseer, peer, COST_DUPLICATE_MESSAGE).await; + + let number = 1 + level + 1; // first block had number 1 + let hash = BlakeTwo256::hash_of(&(parent_hash, number)); + let meta = BlockApprovalMeta { + hash, + parent_hash, + number, + candidates: vec![], + slot: (level as u64).into(), + session: 1, + vrf_story: RelayVRFStory(Default::default()), + }; + + let msg = ApprovalDistributionMessage::ApprovalCheckingLagUpdate(level + 1); + overseer_send(overseer, msg).await; + + let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); + overseer_send(overseer, msg).await; + + parent_hash = hash; + } + + // send the assignments again, we should not punish the peer because aggression is + // enabled. + send_message_from_peer_v3(overseer, peer, msg).await; + + assert!(overseer.recv().timeout(TIMEOUT).await.is_none(), "no message should be sent"); + virtual_overseer + }, + ); +} + #[test] fn import_approval_happy_path_v1_v2_peers() { let peers = make_peers_and_authority_ids(15); @@ -1120,7 +1255,7 @@ fn import_approval_happy_path_v1_v2_peers() { } ); - // send the an approval from peer_b + // send an approval from peer_b let approval = IndirectSignedApprovalVoteV2 { block_hash: hash, candidate_indices: candidate_index.into(), @@ -1250,7 +1385,7 @@ fn import_approval_happy_path_v2() { } ); - // send the an approval from peer_b + // send an approval from peer_b let approval = IndirectSignedApprovalVoteV2 { block_hash: hash, candidate_indices, @@ -1758,7 +1893,7 @@ fn import_approval_bad() { .unwrap() .unwrap(); - // send the an approval from peer_b, we don't have an assignment yet + // send an approval from peer_b, we don't have an assignment yet let approval = IndirectSignedApprovalVoteV2 { block_hash: hash, candidate_indices: candidate_index.into(), @@ -3892,7 +4027,7 @@ fn resends_messages_periodically() { // Add blocks until resend is done. { let mut parent_hash = hash; - for level in 0..2 { + for level in 0..4 { number = number + 1; let hash = BlakeTwo256::hash_of(&(parent_hash, number)); let meta = BlockApprovalMeta { @@ -4037,7 +4172,7 @@ fn import_versioned_approval() { } ); - // send the an approval from peer_a + // send an approval from peer_a let approval = IndirectSignedApprovalVote { block_hash: hash, candidate_index, diff --git a/polkadot/node/network/availability-distribution/Cargo.toml b/polkadot/node/network/availability-distribution/Cargo.toml index 8c5574f244e4a0671e807cc46dcb1286bafaf3a2..7de8cb1915997c2c8f58394fc64b726e3cff5b8a 100644 --- a/polkadot/node/network/availability-distribution/Cargo.toml +++ b/polkadot/node/network/availability-distribution/Cargo.toml @@ -5,40 +5,42 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +codec = { features = ["std"], workspace = true, default-features = true } +derive_more = { workspace = true, default-features = true } +fatality = { workspace = true } futures = { workspace = true } gum = { workspace = true, default-features = true } -codec = { features = ["std"], workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-erasure-coding = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } +polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +rand = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } +schnellru = { workspace = true } sp-core = { features = ["std"], workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } thiserror = { workspace = true } -rand = { workspace = true, default-features = true } -derive_more = { workspace = true, default-features = true } -schnellru = { workspace = true } -fatality = { workspace = true } [dev-dependencies] +assert_matches = { workspace = true } +futures-timer = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } +polkadot-primitives-test-helpers = { workspace = true } +polkadot-subsystem-bench = { workspace = true } +rstest = { workspace = true } +sc-network = { workspace = true, default-features = true } sp-core = { features = ["std"], workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } -futures-timer = { workspace = true } -assert_matches = { workspace = true } -polkadot-primitives-test-helpers = { workspace = true } -rstest = { workspace = true } -polkadot-subsystem-bench = { workspace = true } [[bench]] diff --git a/polkadot/node/network/availability-recovery/Cargo.toml b/polkadot/node/network/availability-recovery/Cargo.toml index 41f09b1f7044358697fe923ab56f745bfdce22e7..8d4e6893b0a56e5e0c7d970c2daaebb57c9ef7fe 100644 --- a/polkadot/node/network/availability-recovery/Cargo.toml +++ b/polkadot/node/network/availability-recovery/Cargo.toml @@ -5,40 +5,42 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +async-trait = { workspace = true } +fatality = { workspace = true } futures = { workspace = true } -tokio = { workspace = true, default-features = true } -schnellru = { workspace = true } +gum = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } -fatality = { workspace = true } +schnellru = { workspace = true } thiserror = { workspace = true } -async-trait = { workspace = true } -gum = { workspace = true, default-features = true } +tokio = { workspace = true, default-features = true } +codec = { features = ["derive"], workspace = true } polkadot-erasure-coding = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } +polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-node-network-protocol = { workspace = true, default-features = true } -codec = { features = ["derive"], workspace = true } +polkadot-primitives = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } [dev-dependencies] assert_matches = { workspace = true } futures-timer = { workspace = true } -rstest = { workspace = true } log = { workspace = true, default-features = true } +rstest = { workspace = true } -sp-tracing = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } +sp-application-crypto = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-primitives-test-helpers = { workspace = true } diff --git a/polkadot/node/network/bitfield-distribution/Cargo.toml b/polkadot/node/network/bitfield-distribution/Cargo.toml index 6d007255c574fc35aff7142e244113dd8022e56d..74a205276906ecb21b2c73b0017059613f704533 100644 --- a/polkadot/node/network/bitfield-distribution/Cargo.toml +++ b/polkadot/node/network/bitfield-distribution/Cargo.toml @@ -5,6 +5,8 @@ description = "Polkadot Bitfiled Distribution subsystem, which gossips signed av authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -14,21 +16,21 @@ always-assert = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } gum = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } +polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-node-network-protocol = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } [dev-dependencies] -polkadot-node-subsystem-test-helpers = { workspace = true } +assert_matches = { workspace = true } bitvec = { features = ["alloc"], workspace = true } -sp-core = { workspace = true, default-features = true } +maplit = { workspace = true } +polkadot-node-subsystem-test-helpers = { workspace = true } +rand_chacha = { workspace = true, default-features = true } sp-application-crypto = { workspace = true, default-features = true } sp-authority-discovery = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -maplit = { workspace = true } +sp-keystore = { workspace = true, default-features = true } sp-tracing = { workspace = true } -assert_matches = { workspace = true } -rand_chacha = { workspace = true, default-features = true } diff --git a/polkadot/node/network/bridge/Cargo.toml b/polkadot/node/network/bridge/Cargo.toml index b4b5743853cd6dc2b43cce5d6b8c4d63bb893c8b..cdc1bc3f6c1bcd8b05d4c9f3f4c51b577721a2a2 100644 --- a/polkadot/node/network/bridge/Cargo.toml +++ b/polkadot/node/network/bridge/Cargo.toml @@ -5,6 +5,8 @@ description = "The Network Bridge Subsystem — protocol multiplexer for Polkado authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -12,26 +14,26 @@ workspace = true [dependencies] always-assert = { workspace = true } async-trait = { workspace = true } +bytes = { workspace = true, default-features = true } +codec = { features = ["derive"], workspace = true } +fatality = { workspace = true } futures = { workspace = true } gum = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } -codec = { features = ["derive"], workspace = true } -sc-network = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } +parking_lot = { workspace = true, default-features = true } polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } -parking_lot = { workspace = true, default-features = true } -bytes = { workspace = true, default-features = true } -fatality = { workspace = true } +polkadot-primitives = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } thiserror = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } +futures-timer = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-primitives-test-helpers = { workspace = true } sp-core = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -futures-timer = { workspace = true } -polkadot-primitives-test-helpers = { workspace = true } diff --git a/polkadot/node/network/collator-protocol/Cargo.toml b/polkadot/node/network/collator-protocol/Cargo.toml index 304cb23bb6aae5cc06e034b1ee6acb849196077b..a02b281b6fc42a1ec2c4c4c88c613af5da87077f 100644 --- a/polkadot/node/network/collator-protocol/Cargo.toml +++ b/polkadot/node/network/collator-protocol/Cargo.toml @@ -5,6 +5,8 @@ description = "Polkadot Collator Protocol subsystem. Allows collators and valida authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -17,28 +19,28 @@ gum = { workspace = true, default-features = true } schnellru.workspace = true sp-core = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } +fatality = { workspace = true } polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } -fatality = { workspace = true } +polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } thiserror = { workspace = true } tokio-util = { workspace = true } [dev-dependencies] -sp-tracing = { workspace = true } assert_matches = { workspace = true } rstest = { workspace = true } +sp-tracing = { workspace = true } -sp-core = { features = ["std"], workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } +codec = { features = ["std"], workspace = true, default-features = true } sc-keystore = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } -codec = { features = ["std"], workspace = true, default-features = true } +sp-core = { features = ["std"], workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-primitives-test-helpers = { workspace = true } diff --git a/polkadot/node/network/collator-protocol/src/collator_side/collation.rs b/polkadot/node/network/collator-protocol/src/collator_side/collation.rs index 6a570331f710b3a845d5ebcd02a007e45eb3f662..aa2fc52e90c522e52f31683a184a71e5557d8f1c 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/collation.rs @@ -21,9 +21,7 @@ use std::collections::{HashSet, VecDeque}; use futures::{future::BoxFuture, stream::FuturesUnordered}; use polkadot_node_network_protocol::{ - request_response::{ - incoming::OutgoingResponse, v1 as protocol_v1, v2 as protocol_v2, IncomingRequest, - }, + request_response::{incoming::OutgoingResponse, v2 as protocol_v2, IncomingRequest}, PeerId, }; use polkadot_node_primitives::PoV; @@ -90,16 +88,9 @@ pub struct WaitingCollationFetches { /// Backwards-compatible wrapper for incoming collations requests. pub enum VersionedCollationRequest { - V1(IncomingRequest<protocol_v1::CollationFetchingRequest>), V2(IncomingRequest<protocol_v2::CollationFetchingRequest>), } -impl From<IncomingRequest<protocol_v1::CollationFetchingRequest>> for VersionedCollationRequest { - fn from(req: IncomingRequest<protocol_v1::CollationFetchingRequest>) -> Self { - Self::V1(req) - } -} - impl From<IncomingRequest<protocol_v2::CollationFetchingRequest>> for VersionedCollationRequest { fn from(req: IncomingRequest<protocol_v2::CollationFetchingRequest>) -> Self { Self::V2(req) @@ -110,15 +101,20 @@ impl VersionedCollationRequest { /// Returns parachain id from the request payload. pub fn para_id(&self) -> ParaId { match self { - VersionedCollationRequest::V1(req) => req.payload.para_id, VersionedCollationRequest::V2(req) => req.payload.para_id, } } + /// Returns candidate hash from the request payload. + pub fn candidate_hash(&self) -> CandidateHash { + match self { + VersionedCollationRequest::V2(req) => req.payload.candidate_hash, + } + } + /// Returns relay parent from the request payload. pub fn relay_parent(&self) -> Hash { match self { - VersionedCollationRequest::V1(req) => req.payload.relay_parent, VersionedCollationRequest::V2(req) => req.payload.relay_parent, } } @@ -126,7 +122,6 @@ impl VersionedCollationRequest { /// Returns id of the peer the request was received from. pub fn peer_id(&self) -> PeerId { match self { - VersionedCollationRequest::V1(req) => req.peer, VersionedCollationRequest::V2(req) => req.peer, } } @@ -134,10 +129,9 @@ impl VersionedCollationRequest { /// Sends the response back to requester. pub fn send_outgoing_response( self, - response: OutgoingResponse<protocol_v1::CollationFetchingResponse>, + response: OutgoingResponse<protocol_v2::CollationFetchingResponse>, ) -> Result<(), ()> { match self { - VersionedCollationRequest::V1(req) => req.send_outgoing_response(response), VersionedCollationRequest::V2(req) => req.send_outgoing_response(response), } } diff --git a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs index 504b0d716043943875de9635832b473872e5fd7e..50c933599f5497a6e92862d5a69c17eccceb01f7 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs @@ -31,7 +31,7 @@ use polkadot_node_network_protocol::{ peer_set::{CollationVersion, PeerSet}, request_response::{ incoming::{self, OutgoingResponse}, - v1 as request_v1, v2 as request_v2, IncomingRequestReceiver, + v2 as request_v2, IncomingRequestReceiver, }, v1 as protocol_v1, v2 as protocol_v2, OurView, PeerId, UnifiedReputationChange as Rep, Versioned, View, @@ -40,23 +40,18 @@ use polkadot_node_primitives::{CollationSecondedSignal, PoV, Statement}; use polkadot_node_subsystem::{ messages::{ CollatorProtocolMessage, NetworkBridgeEvent, NetworkBridgeTxMessage, ParentHeadData, - RuntimeApiMessage, }, overseer, FromOrchestra, OverseerSignal, }; use polkadot_node_subsystem_util::{ backing_implicit_view::View as ImplicitView, reputation::{ReputationAggregator, REPUTATION_CHANGE_INTERVAL}, - runtime::{ - fetch_claim_queue, get_availability_cores, get_group_rotation_info, - prospective_parachains_mode, ProspectiveParachainsMode, RuntimeInfo, - }, + runtime::{fetch_claim_queue, get_group_rotation_info, ClaimQueueSnapshot, RuntimeInfo}, TimeoutExt, }; use polkadot_primitives::{ - vstaging::{CandidateReceiptV2 as CandidateReceipt, CoreState}, - AuthorityDiscoveryId, CandidateHash, CollatorPair, CoreIndex, GroupIndex, Hash, HeadData, - Id as ParaId, SessionIndex, + vstaging::CandidateReceiptV2 as CandidateReceipt, AuthorityDiscoveryId, CandidateHash, + CollatorPair, CoreIndex, GroupIndex, Hash, HeadData, Id as ParaId, SessionIndex, }; use super::LOG_TARGET; @@ -199,8 +194,6 @@ impl ValidatorGroup { struct PeerData { /// Peer's view. view: View, - /// Network protocol version. - version: CollationVersion, /// Unknown heads in the view. /// /// This can happen when the validator is faster at importing a block and sending out its @@ -229,21 +222,27 @@ impl CollationWithCoreIndex { } struct PerRelayParent { - prospective_parachains_mode: ProspectiveParachainsMode, /// Per core index validators group responsible for backing candidates built /// on top of this relay parent. validator_group: HashMap<CoreIndex, ValidatorGroup>, /// Distributed collations. collations: HashMap<CandidateHash, CollationWithCoreIndex>, + /// Number of assignments per core + assignments: HashMap<CoreIndex, usize>, } impl PerRelayParent { - fn new(mode: ProspectiveParachainsMode) -> Self { - Self { - prospective_parachains_mode: mode, - validator_group: HashMap::default(), - collations: HashMap::new(), - } + fn new(para_id: ParaId, claim_queue: ClaimQueueSnapshot) -> Self { + let assignments = + claim_queue.iter_all_claims().fold(HashMap::new(), |mut acc, (core, claims)| { + let n_claims = claims.iter().filter(|para| para == &¶_id).count(); + if n_claims > 0 { + acc.insert(*core, n_claims); + } + acc + }); + + Self { validator_group: HashMap::default(), collations: HashMap::new(), assignments } } } @@ -262,24 +261,11 @@ struct State { /// to determine what is relevant to them. peer_data: HashMap<PeerId, PeerData>, - /// Leaves that do support asynchronous backing along with - /// implicit ancestry. Leaves from the implicit view are present in - /// `active_leaves`, the opposite doesn't hold true. - /// - /// Relay-chain blocks which don't support prospective parachains are - /// never included in the fragment chains of active leaves which do. In - /// particular, this means that if a given relay parent belongs to implicit - /// ancestry of some active leaf, then it does support prospective parachains. + /// Leaves along with implicit ancestry. /// /// It's `None` if the collator is not yet collating for a paraid. implicit_view: Option<ImplicitView>, - /// All active leaves observed by us, including both that do and do not - /// support prospective parachains. This mapping works as a replacement for - /// [`polkadot_node_network_protocol::View`] and can be dropped once the transition - /// to asynchronous backing is done. - active_leaves: HashMap<Hash, ProspectiveParachainsMode>, - /// Validators and distributed collations tracked for each relay parent from /// our view, including both leaves and implicit ancestry. per_relay_parent: HashMap<Hash, PerRelayParent>, @@ -340,7 +326,6 @@ impl State { collating_on: Default::default(), peer_data: Default::default(), implicit_view: None, - active_leaves: Default::default(), per_relay_parent: Default::default(), collation_result_senders: Default::default(), peer_ids: Default::default(), @@ -359,7 +344,7 @@ impl State { /// Figure out the core our para is assigned to and the relevant validators. /// Issue a connection request to these validators. /// If the para is not scheduled or next up on any core, at the relay-parent, -/// or the relay-parent isn't in the active-leaves set, we ignore the message +/// or the relay-parent isn't in the implicit ancestry, we ignore the message /// as it must be invalid in that case - although this indicates a logic error /// elsewhere in the node. #[overseer::contextbounds(CollatorProtocol, prefix = self::overseer)] @@ -391,20 +376,32 @@ async fn distribute_collation<Context>( return Ok(()) }, }; - let relay_parent_mode = per_relay_parent.prospective_parachains_mode; - let collations_limit = match relay_parent_mode { - ProspectiveParachainsMode::Disabled => 1, - ProspectiveParachainsMode::Enabled { max_candidate_depth, .. } => max_candidate_depth + 1, + let Some(collations_limit) = per_relay_parent.assignments.get(&core_index) else { + gum::warn!( + target: LOG_TARGET, + para_id = %id, + relay_parent = ?candidate_relay_parent, + cores = ?per_relay_parent.assignments.keys(), + ?core_index, + "Attempting to distribute collation for a core we are not assigned to ", + ); + + return Ok(()) }; - if per_relay_parent.collations.len() >= collations_limit { + let current_collations_count = per_relay_parent + .collations + .values() + .filter(|c| c.core_index() == &core_index) + .count(); + if current_collations_count >= *collations_limit { gum::debug!( target: LOG_TARGET, ?candidate_relay_parent, - ?relay_parent_mode, - "The limit of {} collations per relay parent is already reached", + "The limit of {} collations per relay parent for core {} is already reached", collations_limit, + core_index.0, ); return Ok(()) } @@ -420,54 +417,21 @@ async fn distribute_collation<Context>( return Ok(()) } - // Determine which core(s) the para collated-on is assigned to. - // If it is not scheduled then ignore the message. - let (our_cores, num_cores) = - match determine_cores(ctx.sender(), id, candidate_relay_parent, relay_parent_mode).await? { - (cores, _num_cores) if cores.is_empty() => { - gum::warn!( - target: LOG_TARGET, - para_id = %id, - "looks like no core is assigned to {} at {}", id, candidate_relay_parent, - ); - - return Ok(()) - }, - (cores, num_cores) => (cores, num_cores), - }; - - let elastic_scaling = our_cores.len() > 1; + let elastic_scaling = per_relay_parent.assignments.len() > 1; if elastic_scaling { gum::debug!( target: LOG_TARGET, para_id = %id, - cores = ?our_cores, - "{} is assigned to {} cores at {}", id, our_cores.len(), candidate_relay_parent, + cores = ?per_relay_parent.assignments.keys(), + "{} is assigned to {} cores at {}", id, per_relay_parent.assignments.len(), candidate_relay_parent, ); } - // Double check that the specified `core_index` is among the ones our para has assignments for. - if !our_cores.iter().any(|assigned_core| assigned_core == &core_index) { - gum::warn!( - target: LOG_TARGET, - para_id = %id, - relay_parent = ?candidate_relay_parent, - cores = ?our_cores, - ?core_index, - "Attempting to distribute collation for a core we are not assigned to ", - ); - - return Ok(()) - } - let our_core = core_index; // Determine the group on that core. - // - // When prospective parachains are disabled, candidate relay parent here is - // guaranteed to be an active leaf. let GroupValidators { validators, session_index, group_index } = - determine_our_validators(ctx, runtime, our_core, num_cores, candidate_relay_parent).await?; + determine_our_validators(ctx, runtime, our_core, candidate_relay_parent).await?; if validators.is_empty() { gum::warn!( @@ -495,7 +459,6 @@ async fn distribute_collation<Context>( target: LOG_TARGET, para_id = %id, candidate_relay_parent = %candidate_relay_parent, - relay_parent_mode = ?relay_parent_mode, ?candidate_hash, pov_hash = ?pov.hash(), core = ?our_core, @@ -531,35 +494,32 @@ async fn distribute_collation<Context>( ), ); - // If prospective parachains are disabled, a leaf should be known to peer. - // Otherwise, it should be present in allowed ancestry of some leaf. + // The leaf should be present in the allowed ancestry of some leaf. // // It's collation-producer responsibility to verify that there exists // a hypothetical membership in a fragment chain for the candidate. - let interested = - state - .peer_data - .iter() - .filter(|(_, PeerData { view: v, .. })| match relay_parent_mode { - ProspectiveParachainsMode::Disabled => v.contains(&candidate_relay_parent), - ProspectiveParachainsMode::Enabled { .. } => v.iter().any(|block_hash| { - state.implicit_view.as_ref().map(|implicit_view| { - implicit_view - .known_allowed_relay_parents_under(block_hash, Some(id)) - .unwrap_or_default() - .contains(&candidate_relay_parent) - }) == Some(true) - }), - }); + let interested = state + .peer_data + .iter() + .filter(|(_, PeerData { view: v, .. })| { + v.iter().any(|block_hash| { + state.implicit_view.as_ref().map(|implicit_view| { + implicit_view + .known_allowed_relay_parents_under(block_hash, Some(id)) + .unwrap_or_default() + .contains(&candidate_relay_parent) + }) == Some(true) + }) + }) + .map(|(id, _)| id); // Make sure already connected peers get collations: - for (peer_id, peer_data) in interested { + for peer_id in interested { advertise_collation( ctx, candidate_relay_parent, per_relay_parent, peer_id, - peer_data.version, &state.peer_ids, &mut state.advertisement_timeouts, &state.metrics, @@ -570,45 +530,6 @@ async fn distribute_collation<Context>( Ok(()) } -/// Get the core indices that are assigned to the para being collated on if any -/// and the total number of cores. -async fn determine_cores( - sender: &mut impl overseer::SubsystemSender<RuntimeApiMessage>, - para_id: ParaId, - relay_parent: Hash, - relay_parent_mode: ProspectiveParachainsMode, -) -> Result<(Vec<CoreIndex>, usize)> { - let cores = get_availability_cores(sender, relay_parent).await?; - let n_cores = cores.len(); - let mut assigned_cores = Vec::new(); - let maybe_claim_queue = fetch_claim_queue(sender, relay_parent).await?; - - for (idx, core) in cores.iter().enumerate() { - let core_is_scheduled = match maybe_claim_queue { - Some(ref claim_queue) => { - // Runtime supports claim queue - use it. - claim_queue - .iter_claims_for_core(&CoreIndex(idx as u32)) - .any(|para| para == ¶_id) - }, - None => match core { - CoreState::Scheduled(scheduled) if scheduled.para_id == para_id => true, - CoreState::Occupied(occupied) if relay_parent_mode.is_enabled() => - // With async backing we don't care about the core state, - // it is only needed for figuring our validators group. - occupied.next_up_on_available.as_ref().map(|c| c.para_id) == Some(para_id), - _ => false, - }, - }; - - if core_is_scheduled { - assigned_cores.push(CoreIndex::from(idx as u32)); - } - } - - Ok((assigned_cores, n_cores)) -} - /// Validators of a particular group index. #[derive(Debug)] struct GroupValidators { @@ -627,7 +548,6 @@ async fn determine_our_validators<Context>( ctx: &mut Context, runtime: &mut RuntimeInfo, core_index: CoreIndex, - cores: usize, relay_parent: Hash, ) -> Result<GroupValidators> { let session_index = runtime.get_session_index_for_child(ctx.sender(), relay_parent).await?; @@ -637,9 +557,10 @@ async fn determine_our_validators<Context>( .session_info; gum::debug!(target: LOG_TARGET, ?session_index, "Received session info"); let groups = &info.validator_groups; + let num_cores = groups.len(); let rotation_info = get_group_rotation_info(ctx.sender(), relay_parent).await?; - let current_group_index = rotation_info.group_for_core(core_index, cores); + let current_group_index = rotation_info.group_for_core(core_index, num_cores); let current_validators = groups.get(current_group_index).map(|v| v.as_slice()).unwrap_or_default(); @@ -657,46 +578,24 @@ async fn determine_our_validators<Context>( Ok(current_validators) } -/// Construct the declare message to be sent to validator depending on its -/// network protocol version. +/// Construct the declare message to be sent to validator. fn declare_message( state: &mut State, - version: CollationVersion, ) -> Option<Versioned<protocol_v1::CollationProtocol, protocol_v2::CollationProtocol>> { let para_id = state.collating_on?; - Some(match version { - CollationVersion::V1 => { - let declare_signature_payload = - protocol_v1::declare_signature_payload(&state.local_peer_id); - let wire_message = protocol_v1::CollatorProtocolMessage::Declare( - state.collator_pair.public(), - para_id, - state.collator_pair.sign(&declare_signature_payload), - ); - Versioned::V1(protocol_v1::CollationProtocol::CollatorProtocol(wire_message)) - }, - CollationVersion::V2 => { - let declare_signature_payload = - protocol_v2::declare_signature_payload(&state.local_peer_id); - let wire_message = protocol_v2::CollatorProtocolMessage::Declare( - state.collator_pair.public(), - para_id, - state.collator_pair.sign(&declare_signature_payload), - ); - Versioned::V2(protocol_v2::CollationProtocol::CollatorProtocol(wire_message)) - }, - }) + let declare_signature_payload = protocol_v2::declare_signature_payload(&state.local_peer_id); + let wire_message = protocol_v2::CollatorProtocolMessage::Declare( + state.collator_pair.public(), + para_id, + state.collator_pair.sign(&declare_signature_payload), + ); + Some(Versioned::V2(protocol_v2::CollationProtocol::CollatorProtocol(wire_message))) } /// Issue versioned `Declare` collation message to the given `peer`. #[overseer::contextbounds(CollatorProtocol, prefix = self::overseer)] -async fn declare<Context>( - ctx: &mut Context, - state: &mut State, - peer: &PeerId, - version: CollationVersion, -) { - if let Some(wire_message) = declare_message(state, version) { +async fn declare<Context>(ctx: &mut Context, state: &mut State, peer: &PeerId) { + if let Some(wire_message) = declare_message(state) { ctx.send_message(NetworkBridgeTxMessage::SendCollationMessage(vec![*peer], wire_message)) .await; } @@ -735,7 +634,6 @@ async fn advertise_collation<Context>( relay_parent: Hash, per_relay_parent: &mut PerRelayParent, peer: &PeerId, - protocol_version: CollationVersion, peer_ids: &HashMap<PeerId, HashSet<AuthorityDiscoveryId>>, advertisement_timeouts: &mut FuturesUnordered<ResetInterestTimeout>, metrics: &Metrics, @@ -744,19 +642,6 @@ async fn advertise_collation<Context>( let core_index = *collation_and_core.core_index(); let collation = collation_and_core.collation_mut(); - // Check that peer will be able to request the collation. - if let CollationVersion::V1 = protocol_version { - if per_relay_parent.prospective_parachains_mode.is_enabled() { - gum::trace!( - target: LOG_TARGET, - ?relay_parent, - peer_id = %peer, - "Skipping advertising to validator, incorrect network protocol version", - ); - return - } - } - let Some(validator_group) = per_relay_parent.validator_group.get_mut(&core_index) else { gum::debug!( target: LOG_TARGET, @@ -793,25 +678,15 @@ async fn advertise_collation<Context>( collation.status.advance_to_advertised(); - let collation_message = match protocol_version { - CollationVersion::V2 => { - let wire_message = protocol_v2::CollatorProtocolMessage::AdvertiseCollation { + ctx.send_message(NetworkBridgeTxMessage::SendCollationMessage( + vec![*peer], + Versioned::V2(protocol_v2::CollationProtocol::CollatorProtocol( + protocol_v2::CollatorProtocolMessage::AdvertiseCollation { relay_parent, candidate_hash: *candidate_hash, parent_head_data_hash: collation.parent_head_data.hash(), - }; - Versioned::V2(protocol_v2::CollationProtocol::CollatorProtocol(wire_message)) - }, - CollationVersion::V1 => { - let wire_message = - protocol_v1::CollatorProtocolMessage::AdvertiseCollation(relay_parent); - Versioned::V1(protocol_v1::CollationProtocol::CollatorProtocol(wire_message)) - }, - }; - - ctx.send_message(NetworkBridgeTxMessage::SendCollationMessage( - vec![*peer], - collation_message, + }, + )), )) .await; @@ -899,7 +774,7 @@ async fn process_msg<Context>( ); } }, - msg @ (ReportCollator(..) | Invalid(..) | Seconded(..)) => { + msg @ (Invalid(..) | Seconded(..)) => { gum::warn!( target: LOG_TARGET, "{:?} message is not expected on the collator side of the protocol", @@ -933,7 +808,7 @@ async fn send_collation( parent_head_data: head_data, }), ParentHeadData::OnlyHash(_) => - Ok(request_v1::CollationFetchingResponse::Collation(receipt, pov)), + Ok(request_v2::CollationFetchingResponse::Collation(receipt, pov)), }; let response = @@ -998,7 +873,16 @@ async fn handle_incoming_peer_message<Context>( ctx.send_message(NetworkBridgeTxMessage::DisconnectPeer(origin, PeerSet::Collation)) .await; }, - Versioned::V1(V1::CollationSeconded(relay_parent, statement)) | + Versioned::V1(V1::CollationSeconded(relay_parent, statement)) => { + // Impossible, we no longer accept connections on v1. + gum::warn!( + target: LOG_TARGET, + ?statement, + ?origin, + ?relay_parent, + "Collation seconded message received on unsupported protocol version 1", + ); + }, Versioned::V2(V2::CollationSeconded(relay_parent, statement)) | Versioned::V3(V2::CollationSeconded(relay_parent, statement)) => { if !matches!(statement.unchecked_payload(), Statement::Seconded(_)) { @@ -1092,24 +976,10 @@ async fn handle_incoming_request<Context>( return Ok(()) }, }; - let mode = per_relay_parent.prospective_parachains_mode; let collation_with_core = match &req { - VersionedCollationRequest::V1(_) if !mode.is_enabled() => - per_relay_parent.collations.values_mut().next(), VersionedCollationRequest::V2(req) => per_relay_parent.collations.get_mut(&req.payload.candidate_hash), - _ => { - gum::warn!( - target: LOG_TARGET, - relay_parent = %relay_parent, - prospective_parachains_mode = ?mode, - ?peer_id, - "Collation request version is invalid", - ); - - return Ok(()) - }, }; let (receipt, pov, parent_head_data) = if let Some(collation_with_core) = collation_with_core { @@ -1187,9 +1057,7 @@ async fn handle_peer_view_change<Context>( peer_id: PeerId, view: View, ) { - let Some(PeerData { view: current, version, unknown_heads }) = - state.peer_data.get_mut(&peer_id) - else { + let Some(PeerData { view: current, unknown_heads }) = state.peer_data.get_mut(&peer_id) else { return }; @@ -1198,20 +1066,15 @@ async fn handle_peer_view_change<Context>( *current = view; for added in added.into_iter() { - let block_hashes = match state - .per_relay_parent - .get(&added) - .map(|per_relay_parent| per_relay_parent.prospective_parachains_mode) - { - Some(ProspectiveParachainsMode::Disabled) => std::slice::from_ref(&added), - Some(ProspectiveParachainsMode::Enabled { .. }) => state + let block_hashes = match state.per_relay_parent.contains_key(&added) { + true => state .implicit_view .as_ref() .and_then(|implicit_view| { implicit_view.known_allowed_relay_parents_under(&added, state.collating_on) }) .unwrap_or_default(), - None => { + false => { gum::trace!( target: LOG_TARGET, ?peer_id, @@ -1235,7 +1098,6 @@ async fn handle_peer_view_change<Context>( *block_hash, per_relay_parent, &peer_id, - *version, &state.peer_ids, &mut state.advertisement_timeouts, &state.metrics, @@ -1261,7 +1123,7 @@ async fn handle_network_msg<Context>( // it should be handled here. gum::trace!(target: LOG_TARGET, ?peer_id, ?observed_role, ?maybe_authority, "Peer connected"); - let version = match protocol_version.try_into() { + let version: CollationVersion = match protocol_version.try_into() { Ok(version) => version, Err(err) => { // Network bridge is expected to handle this. @@ -1275,9 +1137,25 @@ async fn handle_network_msg<Context>( return Ok(()) }, }; + if version == CollationVersion::V1 { + gum::warn!( + target: LOG_TARGET, + ?peer_id, + ?observed_role, + "Unsupported protocol version v1" + ); + + // V1 no longer supported, we should disconnect. + ctx.send_message(NetworkBridgeTxMessage::DisconnectPeer( + peer_id, + PeerSet::Collation, + )) + .await; + return Ok(()) + } + state.peer_data.entry(peer_id).or_insert_with(|| PeerData { view: View::default(), - version, // Unlikely that the collator is falling 10 blocks behind and if so, it probably is // not able to keep up any way. unknown_heads: LruMap::new(ByLength::new(10)), @@ -1292,7 +1170,7 @@ async fn handle_network_msg<Context>( ); state.peer_ids.insert(peer_id, authority_ids); - declare(ctx, state, &peer_id, version).await; + declare(ctx, state, &peer_id).await; } }, PeerViewChange(peer_id, view) => { @@ -1313,9 +1191,9 @@ async fn handle_network_msg<Context>( }, UpdatedAuthorityIds(peer_id, authority_ids) => { gum::trace!(target: LOG_TARGET, ?peer_id, ?authority_ids, "Updated authority ids"); - if let Some(version) = state.peer_data.get(&peer_id).map(|d| d.version) { + if state.peer_data.contains_key(&peer_id) { if state.peer_ids.insert(peer_id, authority_ids).is_none() { - declare(ctx, state, &peer_id, version).await; + declare(ctx, state, &peer_id).await; } } }, @@ -1334,77 +1212,66 @@ async fn handle_our_view_change<Context>( state: &mut State, view: OurView, ) -> Result<()> { - let current_leaves = state.active_leaves.clone(); + let Some(implicit_view) = &mut state.implicit_view else { return Ok(()) }; + let Some(para_id) = state.collating_on else { return Ok(()) }; - let removed = current_leaves.iter().filter(|(h, _)| !view.contains(h)); - let added = view.iter().filter(|h| !current_leaves.contains_key(h)); + let removed: Vec<_> = + implicit_view.leaves().map(|l| *l).filter(|h| !view.contains(h)).collect(); + let added: Vec<_> = view.iter().filter(|h| !implicit_view.contains_leaf(h)).collect(); for leaf in added { - let mode = prospective_parachains_mode(ctx.sender(), *leaf).await?; - - state.active_leaves.insert(*leaf, mode); - state.per_relay_parent.insert(*leaf, PerRelayParent::new(mode)); - - if mode.is_enabled() { - if let Some(ref mut implicit_view) = state.implicit_view { - implicit_view - .activate_leaf(ctx.sender(), *leaf) - .await - .map_err(Error::ImplicitViewFetchError)?; - - let allowed_ancestry = implicit_view - .known_allowed_relay_parents_under(leaf, state.collating_on) - .unwrap_or_default(); - - // Get the peers that already reported us this head, but we didn't knew it at this - // point. - let peers = state - .peer_data - .iter_mut() - .filter_map(|(id, data)| { - data.unknown_heads.remove(leaf).map(|_| (id, data.version)) - }) - .collect::<Vec<_>>(); - - for block_hash in allowed_ancestry { - let per_relay_parent = state - .per_relay_parent - .entry(*block_hash) - .or_insert_with(|| PerRelayParent::new(mode)); - - // Announce relevant collations to these peers. - for (peer_id, peer_version) in &peers { - advertise_collation( - ctx, - *block_hash, - per_relay_parent, - &peer_id, - *peer_version, - &state.peer_ids, - &mut state.advertisement_timeouts, - &state.metrics, - ) - .await; - } - } + let claim_queue = fetch_claim_queue(ctx.sender(), *leaf).await?; + state.per_relay_parent.insert(*leaf, PerRelayParent::new(para_id, claim_queue)); + + implicit_view + .activate_leaf(ctx.sender(), *leaf) + .await + .map_err(Error::ImplicitViewFetchError)?; + + let allowed_ancestry = implicit_view + .known_allowed_relay_parents_under(leaf, state.collating_on) + .unwrap_or_default(); + + // Get the peers that already reported us this head, but we didn't know it at this + // point. + let peers = state + .peer_data + .iter_mut() + .filter_map(|(id, data)| data.unknown_heads.remove(leaf).map(|_| id)) + .collect::<Vec<_>>(); + + for block_hash in allowed_ancestry { + if state.per_relay_parent.get(block_hash).is_none() { + let claim_queue = fetch_claim_queue(ctx.sender(), *block_hash).await?; + state + .per_relay_parent + .insert(*block_hash, PerRelayParent::new(para_id, claim_queue)); + } + + let per_relay_parent = + state.per_relay_parent.get_mut(block_hash).expect("Just inserted"); + + // Announce relevant collations to these peers. + for peer_id in &peers { + advertise_collation( + ctx, + *block_hash, + per_relay_parent, + &peer_id, + &state.peer_ids, + &mut state.advertisement_timeouts, + &state.metrics, + ) + .await; } } } - for (leaf, mode) in removed { - state.active_leaves.remove(leaf); + for leaf in removed { // If the leaf is deactivated it still may stay in the view as a part // of implicit ancestry. Only update the state after the hash is actually // pruned from the block info storage. - let pruned = if mode.is_enabled() { - state - .implicit_view - .as_mut() - .map(|view| view.deactivate_leaf(*leaf)) - .unwrap_or_default() - } else { - vec![*leaf] - }; + let pruned = implicit_view.deactivate_leaf(leaf); for removed in &pruned { gum::debug!(target: LOG_TARGET, relay_parent = ?removed, "Removing relay parent because our view changed."); @@ -1454,7 +1321,6 @@ pub(crate) async fn run<Context>( ctx: Context, local_peer_id: PeerId, collator_pair: CollatorPair, - req_v1_receiver: IncomingRequestReceiver<request_v1::CollationFetchingRequest>, req_v2_receiver: IncomingRequestReceiver<request_v2::CollationFetchingRequest>, metrics: Metrics, ) -> std::result::Result<(), FatalError> { @@ -1462,7 +1328,6 @@ pub(crate) async fn run<Context>( ctx, local_peer_id, collator_pair, - req_v1_receiver, req_v2_receiver, metrics, ReputationAggregator::default(), @@ -1476,7 +1341,6 @@ async fn run_inner<Context>( mut ctx: Context, local_peer_id: PeerId, collator_pair: CollatorPair, - mut req_v1_receiver: IncomingRequestReceiver<request_v1::CollationFetchingRequest>, mut req_v2_receiver: IncomingRequestReceiver<request_v2::CollationFetchingRequest>, metrics: Metrics, reputation: ReputationAggregator, @@ -1492,9 +1356,7 @@ async fn run_inner<Context>( loop { let reputation_changes = || vec![COST_INVALID_REQUEST]; - let recv_req_v1 = req_v1_receiver.recv(reputation_changes).fuse(); let recv_req_v2 = req_v2_receiver.recv(reputation_changes).fuse(); - pin_mut!(recv_req_v1); pin_mut!(recv_req_v2); let mut reconnect_timeout = &mut state.reconnect_timeout; @@ -1558,18 +1420,7 @@ async fn run_inner<Context>( None => continue, }; - match (per_relay_parent.prospective_parachains_mode, &next) { - (ProspectiveParachainsMode::Disabled, VersionedCollationRequest::V1(_)) => { - per_relay_parent.collations.values().next() - }, - (ProspectiveParachainsMode::Enabled { .. }, VersionedCollationRequest::V2(req)) => { - per_relay_parent.collations.get(&req.payload.candidate_hash) - }, - _ => { - // Request version is checked in `handle_incoming_request`. - continue - }, - } + per_relay_parent.collations.get(&next.candidate_hash()) }; if let Some(collation_with_core) = next_collation_with_core { @@ -1601,14 +1452,6 @@ async fn run_inner<Context>( "Peer-set updated due to a timeout" ); }, - in_req = recv_req_v1 => { - let request = in_req.map(VersionedCollationRequest::from); - - log_error( - handle_incoming_request(&mut ctx, &mut state, request).await, - "Handling incoming collation fetch request V1" - )?; - } in_req = recv_req_v2 => { let request = in_req.map(VersionedCollationRequest::from); diff --git a/polkadot/node/network/collator-protocol/src/collator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/collator_side/tests/mod.rs index 23954f8d781bdfa7c78a0ddd75898aff1d893bd3..5e7bc2d569d41e04a7dee77d9dbd989ec7d0af52 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/tests/mod.rs @@ -33,33 +33,31 @@ use sp_keyring::Sr25519Keyring; use sp_runtime::traits::AppVerify; use polkadot_node_network_protocol::{ - our_view, peer_set::CollationVersion, - request_response::{IncomingRequest, ReqProtocolNames}, + request_response::{ + v2::{CollationFetchingRequest, CollationFetchingResponse}, + IncomingRequest, ReqProtocolNames, + }, view, }; use polkadot_node_primitives::BlockData; -use polkadot_node_subsystem::{ - errors::RuntimeApiError, - messages::{AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest}, - ActiveLeavesUpdate, +use polkadot_node_subsystem::messages::{ + AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest, }; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::{reputation::add_reputation, TimeoutExt}; use polkadot_primitives::{ AuthorityDiscoveryId, Block, CollatorPair, ExecutorParams, GroupIndex, GroupRotationInfo, - IndexedVec, NodeFeatures, ScheduledCore, SessionIndex, SessionInfo, ValidatorId, - ValidatorIndex, + IndexedVec, NodeFeatures, SessionIndex, SessionInfo, ValidatorId, ValidatorIndex, }; use polkadot_primitives_test_helpers::TestCandidateBuilder; -use test_helpers::mock::new_leaf; mod prospective_parachains; +use prospective_parachains::{expect_declare_msg, update_view}; const REPUTATION_CHANGE_TEST_INTERVAL: Duration = Duration::from_millis(10); -const ASYNC_BACKING_DISABLED_ERROR: RuntimeApiError = - RuntimeApiError::NotSupported { runtime_api_name: "test-runtime" }; +const SCHEDULING_LOOKAHEAD: usize = 4; #[derive(Clone)] struct TestState { @@ -108,7 +106,8 @@ impl Default for TestState { GroupRotationInfo { session_start_block: 0, group_rotation_frequency: 100, now: 1 }; let mut claim_queue = BTreeMap::new(); - claim_queue.insert(CoreIndex(0), [para_id].into_iter().collect()); + claim_queue + .insert(CoreIndex(0), std::iter::repeat(para_id).take(SCHEDULING_LOOKAHEAD).collect()); claim_queue.insert(CoreIndex(1), VecDeque::new()); let relay_parent = Hash::random(); @@ -179,47 +178,6 @@ impl TestState { .map(|i| self.session_info.discovery_keys[i.0 as usize].clone()) .collect() } - - /// Generate a new relay parent and inform the subsystem about the new view. - /// - /// If `merge_views == true` it means the subsystem will be informed that we are working on the - /// old `relay_parent` and the new one. - async fn advance_to_new_round( - &mut self, - virtual_overseer: &mut VirtualOverseer, - merge_views: bool, - ) { - let old_relay_parent = self.relay_parent; - - while self.relay_parent == old_relay_parent { - self.relay_parent.randomize(); - } - - let our_view = if merge_views { - our_view![old_relay_parent, self.relay_parent] - } else { - our_view![self.relay_parent] - }; - - overseer_send( - virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view, - )), - ) - .await; - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::AsyncBackingParams(tx) - )) => { - assert_eq!(relay_parent, self.relay_parent); - tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)).unwrap(); - } - ); - } } type VirtualOverseer = @@ -227,7 +185,6 @@ type VirtualOverseer = struct TestHarness { virtual_overseer: VirtualOverseer, - req_v1_cfg: sc_network::config::RequestResponseConfig, req_v2_cfg: sc_network::config::RequestResponseConfig, } @@ -247,10 +204,6 @@ fn test_harness<T: Future<Output = TestHarness>>( let genesis_hash = Hash::repeat_byte(0xff); let req_protocol_names = ReqProtocolNames::new(&genesis_hash, None); - let (collation_req_receiver, req_v1_cfg) = IncomingRequest::get_config_receiver::< - Block, - sc_network::NetworkWorker<Block, Hash>, - >(&req_protocol_names); let (collation_req_v2_receiver, req_v2_cfg) = IncomingRequest::get_config_receiver::< Block, sc_network::NetworkWorker<Block, Hash>, @@ -260,7 +213,6 @@ fn test_harness<T: Future<Output = TestHarness>>( context, local_peer_id, collator_pair, - collation_req_receiver, collation_req_v2_receiver, Default::default(), reputation, @@ -270,7 +222,7 @@ fn test_harness<T: Future<Output = TestHarness>>( .unwrap(); }; - let test_fut = test(TestHarness { virtual_overseer, req_v1_cfg, req_v2_cfg }); + let test_fut = test(TestHarness { virtual_overseer, req_v2_cfg }); futures::pin_mut!(test_fut); futures::pin_mut!(subsystem); @@ -334,39 +286,6 @@ async fn overseer_signal(overseer: &mut VirtualOverseer, signal: OverseerSignal) .expect(&format!("{:?} is more than enough for sending signals.", TIMEOUT)); } -// Setup the system by sending the `CollateOn`, `ActiveLeaves` and `OurViewChange` messages. -async fn setup_system(virtual_overseer: &mut VirtualOverseer, test_state: &TestState) { - overseer_send(virtual_overseer, CollatorProtocolMessage::CollateOn(test_state.para_id)).await; - - overseer_signal( - virtual_overseer, - OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( - test_state.relay_parent, - 1, - ))), - ) - .await; - - overseer_send( - virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange(our_view![ - test_state.relay_parent - ])), - ) - .await; - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::AsyncBackingParams(tx) - )) => { - assert_eq!(relay_parent, test_state.relay_parent); - tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)).unwrap(); - } - ); -} - /// Result of [`distribute_collation`] struct DistributeCollation { candidate: CandidateReceipt, @@ -390,52 +309,11 @@ async fn distribute_collation_with_receipt( pov: pov.clone(), parent_head_data: HeadData(vec![1, 2, 3]), result_sender: None, - core_index: CoreIndex(0), + core_index: candidate.descriptor.core_index().unwrap(), }, ) .await; - // obtain the availability cores. - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _relay_parent, - RuntimeApiRequest::AvailabilityCores(tx) - )) => { - assert_eq!(relay_parent, _relay_parent); - tx.send(Ok(test_state.claim_queue.values().map(|paras| - if let Some(para) = paras.front() { - CoreState::Scheduled(ScheduledCore { para_id: *para, collator: None }) - } else { - CoreState::Free - } - ).collect())).unwrap(); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _relay_parent, - RuntimeApiRequest::Version(tx) - )) => { - assert_eq!(relay_parent, _relay_parent); - tx.send(Ok(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)).unwrap(); - } - ); - - // obtain the claim queue schedule. - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _relay_parent, - RuntimeApiRequest::ClaimQueue(tx) - )) => { - assert_eq!(relay_parent, _relay_parent); - tx.send(Ok(test_state.claim_queue.clone())).unwrap(); - } - ); - // We don't know precisely what is going to come as session info might be cached: loop { match overseer_recv(virtual_overseer).await { @@ -555,14 +433,16 @@ async fn connect_peer( ) .await; - overseer_send( - virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerViewChange( - peer, - view![], - )), - ) - .await; + if version != CollationVersion::V1 { + overseer_send( + virtual_overseer, + CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerViewChange( + peer, + view![], + )), + ) + .await; + } } /// Disconnect a peer @@ -574,52 +454,15 @@ async fn disconnect_peer(virtual_overseer: &mut VirtualOverseer, peer: PeerId) { .await; } -/// Check that the next received message is a `Declare` message. -async fn expect_declare_msg( - virtual_overseer: &mut VirtualOverseer, - test_state: &TestState, - peer: &PeerId, -) { - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendCollationMessage( - to, - Versioned::V1(protocol_v1::CollationProtocol::CollatorProtocol(wire_message)), - ) - ) => { - assert_eq!(to[0], *peer); - assert_matches!( - wire_message, - protocol_v1::CollatorProtocolMessage::Declare( - collator_id, - para_id, - signature, - ) => { - assert!(signature.verify( - &*protocol_v1::declare_signature_payload(&test_state.local_peer_id), - &collator_id), - ); - assert_eq!(collator_id, test_state.collator_pair.public()); - assert_eq!(para_id, test_state.para_id); - } - ); - } - ); -} - /// Check that the next received message is a collation advertisement message. -/// -/// Expects v2 message if `expected_candidate_hashes` is `Some`, v1 otherwise. async fn expect_advertise_collation_msg( virtual_overseer: &mut VirtualOverseer, any_peers: &[PeerId], expected_relay_parent: Hash, - expected_candidate_hashes: Option<Vec<CandidateHash>>, + expected_candidate_hashes: Vec<CandidateHash>, ) { - let mut candidate_hashes: Option<HashSet<_>> = - expected_candidate_hashes.map(|hashes| hashes.into_iter().collect()); - let iter_num = candidate_hashes.as_ref().map(|hashes| hashes.len()).unwrap_or(1); + let mut candidate_hashes: HashSet<_> = expected_candidate_hashes.into_iter().collect(); + let iter_num = candidate_hashes.len(); for _ in 0..iter_num { assert_matches!( @@ -631,23 +474,10 @@ async fn expect_advertise_collation_msg( ) ) => { assert!(any_peers.iter().any(|p| to.contains(p))); - match (candidate_hashes.as_mut(), wire_message) { - (None, Versioned::V1(protocol_v1::CollationProtocol::CollatorProtocol(wire_message))) => { - assert_matches!( - wire_message, - protocol_v1::CollatorProtocolMessage::AdvertiseCollation( - relay_parent, - ) => { - assert_eq!(relay_parent, expected_relay_parent); - } - ); - }, - ( - Some(candidate_hashes), - Versioned::V2(protocol_v2::CollationProtocol::CollatorProtocol( - wire_message, - )), - ) => { + match wire_message { + Versioned::V2(protocol_v2::CollationProtocol::CollatorProtocol( + wire_message, + )) => { assert_matches!( wire_message, protocol_v2::CollatorProtocolMessage::AdvertiseCollation { @@ -687,17 +517,59 @@ async fn send_peer_view_change( } fn decode_collation_response(bytes: &[u8]) -> (CandidateReceipt, PoV) { - let response: request_v1::CollationFetchingResponse = - request_v1::CollationFetchingResponse::decode(&mut &bytes[..]) - .expect("Decoding should work"); + let response: CollationFetchingResponse = + CollationFetchingResponse::decode(&mut &bytes[..]).expect("Decoding should work"); match response { - request_v1::CollationFetchingResponse::Collation(receipt, pov) => (receipt, pov), - request_v1::CollationFetchingResponse::CollationWithParentHeadData { - receipt, pov, .. - } => (receipt, pov), + CollationFetchingResponse::Collation(receipt, pov) => (receipt, pov), + CollationFetchingResponse::CollationWithParentHeadData { receipt, pov, .. } => + (receipt, pov), } } +// Test that connecting on v1 results in disconnect. +#[test] +fn v1_protocol_rejected() { + let test_state = TestState::default(); + let local_peer_id = test_state.local_peer_id; + let collator_pair = test_state.collator_pair.clone(); + + test_harness( + local_peer_id, + collator_pair, + ReputationAggregator::new(|_| true), + |mut test_harness| async move { + let virtual_overseer = &mut test_harness.virtual_overseer; + + overseer_send(virtual_overseer, CollatorProtocolMessage::CollateOn(test_state.para_id)) + .await; + + update_view(&test_state, virtual_overseer, vec![(test_state.relay_parent, 10)], 1) + .await; + + distribute_collation(virtual_overseer, &test_state, test_state.relay_parent, true) + .await; + + for (val, peer) in test_state + .current_group_validator_authority_ids() + .into_iter() + .zip(test_state.current_group_validator_peer_ids()) + { + connect_peer(virtual_overseer, peer, CollationVersion::V1, Some(val.clone())).await; + + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::DisconnectPeer(bad_peer, peer_set)) => { + assert_eq!(peer_set, PeerSet::Collation); + assert_eq!(bad_peer, peer); + } + ); + } + + test_harness + }, + ); +} + #[test] fn advertise_and_send_collation() { let mut test_state = TestState::default(); @@ -710,10 +582,16 @@ fn advertise_and_send_collation() { ReputationAggregator::new(|_| true), |test_harness| async move { let mut virtual_overseer = test_harness.virtual_overseer; - let mut req_v1_cfg = test_harness.req_v1_cfg; - let req_v2_cfg = test_harness.req_v2_cfg; + let mut req_v2_cfg = test_harness.req_v2_cfg; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::CollateOn(test_state.para_id), + ) + .await; - setup_system(&mut virtual_overseer, &test_state).await; + update_view(&test_state, &mut virtual_overseer, vec![(test_state.relay_parent, 10)], 1) + .await; let DistributeCollation { candidate, pov_block } = distribute_collation( &mut virtual_overseer, @@ -728,7 +606,7 @@ fn advertise_and_send_collation() { .into_iter() .zip(test_state.current_group_validator_peer_ids()) { - connect_peer(&mut virtual_overseer, peer, CollationVersion::V1, Some(val.clone())) + connect_peer(&mut virtual_overseer, peer, CollationVersion::V2, Some(val.clone())) .await; } @@ -751,20 +629,21 @@ fn advertise_and_send_collation() { &mut virtual_overseer, &[peer], test_state.relay_parent, - None, + vec![candidate.hash()], ) .await; // Request a collation. let (pending_response, rx) = oneshot::channel(); - req_v1_cfg + req_v2_cfg .inbound_queue .as_mut() .unwrap() .send(RawIncomingRequest { peer, - payload: request_v1::CollationFetchingRequest { + payload: CollationFetchingRequest { relay_parent: test_state.relay_parent, + candidate_hash: candidate.hash(), para_id: test_state.para_id, } .encode(), @@ -776,14 +655,15 @@ fn advertise_and_send_collation() { { let (pending_response, rx) = oneshot::channel(); - req_v1_cfg + req_v2_cfg .inbound_queue .as_mut() .unwrap() .send(RawIncomingRequest { peer, - payload: request_v1::CollationFetchingRequest { + payload: CollationFetchingRequest { relay_parent: test_state.relay_parent, + candidate_hash: candidate.hash(), para_id: test_state.para_id, } .encode(), @@ -817,21 +697,26 @@ fn advertise_and_send_collation() { ); let old_relay_parent = test_state.relay_parent; - test_state.advance_to_new_round(&mut virtual_overseer, false).await; + test_state.relay_parent.randomize(); + + // Update our view, making the old relay parent go out of the implicit view. + update_view(&test_state, &mut virtual_overseer, vec![(test_state.relay_parent, 20)], 1) + .await; let peer = test_state.validator_peer_id[2]; - // Re-request a collation. + // Re-request the collation. let (pending_response, rx) = oneshot::channel(); - req_v1_cfg + req_v2_cfg .inbound_queue .as_mut() .unwrap() .send(RawIncomingRequest { peer, - payload: request_v1::CollationFetchingRequest { + payload: CollationFetchingRequest { relay_parent: old_relay_parent, + candidate_hash: candidate.hash(), para_id: test_state.para_id, } .encode(), @@ -839,13 +724,18 @@ fn advertise_and_send_collation() { }) .await .unwrap(); - // Re-requesting collation should fail: + // Re-requesting collation should fail, becasue the relay parent is out of the view. rx.await.unwrap_err(); assert!(overseer_recv_with_timeout(&mut virtual_overseer, TIMEOUT).await.is_none()); - distribute_collation(&mut virtual_overseer, &test_state, test_state.relay_parent, true) - .await; + let DistributeCollation { candidate, .. } = distribute_collation( + &mut virtual_overseer, + &test_state, + test_state.relay_parent, + true, + ) + .await; // Send info about peer's view. overseer_send( @@ -861,10 +751,10 @@ fn advertise_and_send_collation() { &mut virtual_overseer, &[peer], test_state.relay_parent, - None, + vec![candidate.hash()], ) .await; - TestHarness { virtual_overseer, req_v1_cfg, req_v2_cfg } + TestHarness { virtual_overseer, req_v2_cfg } }, ); } @@ -881,12 +771,18 @@ fn delay_reputation_change() { ReputationAggregator::new(|_| false), |test_harness| async move { let mut virtual_overseer = test_harness.virtual_overseer; - let mut req_v1_cfg = test_harness.req_v1_cfg; - let req_v2_cfg = test_harness.req_v2_cfg; + let mut req_v2_cfg = test_harness.req_v2_cfg; - setup_system(&mut virtual_overseer, &test_state).await; + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::CollateOn(test_state.para_id), + ) + .await; + + update_view(&test_state, &mut virtual_overseer, vec![(test_state.relay_parent, 10)], 1) + .await; - let _ = distribute_collation( + let DistributeCollation { candidate, .. } = distribute_collation( &mut virtual_overseer, &test_state, test_state.relay_parent, @@ -899,7 +795,7 @@ fn delay_reputation_change() { .into_iter() .zip(test_state.current_group_validator_peer_ids()) { - connect_peer(&mut virtual_overseer, peer, CollationVersion::V1, Some(val.clone())) + connect_peer(&mut virtual_overseer, peer, CollationVersion::V2, Some(val.clone())) .await; } @@ -922,21 +818,22 @@ fn delay_reputation_change() { &mut virtual_overseer, &[peer], test_state.relay_parent, - None, + vec![candidate.hash()], ) .await; // Request a collation. let (pending_response, _rx) = oneshot::channel(); - req_v1_cfg + req_v2_cfg .inbound_queue .as_mut() .unwrap() .send(RawIncomingRequest { peer, - payload: request_v1::CollationFetchingRequest { + payload: CollationFetchingRequest { relay_parent: test_state.relay_parent, para_id: test_state.para_id, + candidate_hash: candidate.hash(), } .encode(), pending_response, @@ -947,15 +844,16 @@ fn delay_reputation_change() { { let (pending_response, _rx) = oneshot::channel(); - req_v1_cfg + req_v2_cfg .inbound_queue .as_mut() .unwrap() .send(RawIncomingRequest { peer, - payload: request_v1::CollationFetchingRequest { + payload: CollationFetchingRequest { relay_parent: test_state.relay_parent, para_id: test_state.para_id, + candidate_hash: candidate.hash(), } .encode(), pending_response, @@ -978,85 +876,7 @@ fn delay_reputation_change() { ); } - TestHarness { virtual_overseer, req_v1_cfg, req_v2_cfg } - }, - ); -} - -/// Tests that collator side works with v2 network protocol -/// before async backing is enabled. -#[test] -fn advertise_collation_v2_protocol() { - let test_state = TestState::default(); - let local_peer_id = test_state.local_peer_id; - let collator_pair = test_state.collator_pair.clone(); - - test_harness( - local_peer_id, - collator_pair, - ReputationAggregator::new(|_| true), - |mut test_harness| async move { - let virtual_overseer = &mut test_harness.virtual_overseer; - - setup_system(virtual_overseer, &test_state).await; - - let DistributeCollation { candidate, .. } = - distribute_collation(virtual_overseer, &test_state, test_state.relay_parent, true) - .await; - - let validators = test_state.current_group_validator_authority_ids(); - assert!(validators.len() >= 2); - let peer_ids = test_state.current_group_validator_peer_ids(); - - // Connect first peer with v1. - connect_peer( - virtual_overseer, - peer_ids[0], - CollationVersion::V1, - Some(validators[0].clone()), - ) - .await; - // The rest with v2. - for (val, peer) in validators.iter().zip(peer_ids.iter()).skip(1) { - connect_peer(virtual_overseer, *peer, CollationVersion::V2, Some(val.clone())) - .await; - } - - // Declare messages. - expect_declare_msg(virtual_overseer, &test_state, &peer_ids[0]).await; - for peer_id in peer_ids.iter().skip(1) { - prospective_parachains::expect_declare_msg_v2( - virtual_overseer, - &test_state, - &peer_id, - ) - .await; - } - - // Send info about peers view. - for peer in peer_ids.iter() { - send_peer_view_change(virtual_overseer, peer, vec![test_state.relay_parent]).await; - } - - // Versioned advertisements work. - expect_advertise_collation_msg( - virtual_overseer, - &[peer_ids[0]], - test_state.relay_parent, - None, - ) - .await; - for peer_id in peer_ids.iter().skip(1) { - expect_advertise_collation_msg( - virtual_overseer, - &[*peer_id], - test_state.relay_parent, - Some(vec![candidate.hash()]), // This is `Some`, advertisement is v2. - ) - .await; - } - - test_harness + TestHarness { virtual_overseer, req_v2_cfg } }, ); } @@ -1100,13 +920,25 @@ fn collators_declare_to_connected_peers() { let peer = test_state.validator_peer_id[0]; let validator_id = test_state.current_group_validator_authority_ids()[0].clone(); - setup_system(&mut test_harness.virtual_overseer, &test_state).await; + overseer_send( + &mut test_harness.virtual_overseer, + CollatorProtocolMessage::CollateOn(test_state.para_id), + ) + .await; + + update_view( + &test_state, + &mut test_harness.virtual_overseer, + vec![(test_state.relay_parent, 10)], + 1, + ) + .await; // A validator connected to us connect_peer( &mut test_harness.virtual_overseer, peer, - CollationVersion::V1, + CollationVersion::V2, Some(validator_id), ) .await; @@ -1135,13 +967,17 @@ fn collations_are_only_advertised_to_validators_with_correct_view() { let peer2 = test_state.current_group_validator_peer_ids()[1]; let validator_id2 = test_state.current_group_validator_authority_ids()[1].clone(); - setup_system(virtual_overseer, &test_state).await; + overseer_send(virtual_overseer, CollatorProtocolMessage::CollateOn(test_state.para_id)) + .await; + + update_view(&test_state, virtual_overseer, vec![(test_state.relay_parent, 10)], 1) + .await; // A validator connected to us - connect_peer(virtual_overseer, peer, CollationVersion::V1, Some(validator_id)).await; + connect_peer(virtual_overseer, peer, CollationVersion::V2, Some(validator_id)).await; // Connect the second validator - connect_peer(virtual_overseer, peer2, CollationVersion::V1, Some(validator_id2)).await; + connect_peer(virtual_overseer, peer2, CollationVersion::V2, Some(validator_id2)).await; expect_declare_msg(virtual_overseer, &test_state, &peer).await; expect_declare_msg(virtual_overseer, &test_state, &peer2).await; @@ -1149,14 +985,15 @@ fn collations_are_only_advertised_to_validators_with_correct_view() { // And let it tell us that it is has the same view. send_peer_view_change(virtual_overseer, &peer2, vec![test_state.relay_parent]).await; - distribute_collation(virtual_overseer, &test_state, test_state.relay_parent, true) - .await; + let DistributeCollation { candidate, .. } = + distribute_collation(virtual_overseer, &test_state, test_state.relay_parent, true) + .await; expect_advertise_collation_msg( virtual_overseer, &[peer2], test_state.relay_parent, - None, + vec![candidate.hash()], ) .await; @@ -1168,7 +1005,7 @@ fn collations_are_only_advertised_to_validators_with_correct_view() { virtual_overseer, &[peer], test_state.relay_parent, - None, + vec![candidate.hash()], ) .await; test_harness @@ -1195,31 +1032,50 @@ fn collate_on_two_different_relay_chain_blocks() { let peer2 = test_state.current_group_validator_peer_ids()[1]; let validator_id2 = test_state.current_group_validator_authority_ids()[1].clone(); - setup_system(virtual_overseer, &test_state).await; + overseer_send(virtual_overseer, CollatorProtocolMessage::CollateOn(test_state.para_id)) + .await; + + update_view(&test_state, virtual_overseer, vec![(test_state.relay_parent, 10)], 1) + .await; // A validator connected to us - connect_peer(virtual_overseer, peer, CollationVersion::V1, Some(validator_id)).await; + connect_peer(virtual_overseer, peer, CollationVersion::V2, Some(validator_id)).await; // Connect the second validator - connect_peer(virtual_overseer, peer2, CollationVersion::V1, Some(validator_id2)).await; + connect_peer(virtual_overseer, peer2, CollationVersion::V2, Some(validator_id2)).await; expect_declare_msg(virtual_overseer, &test_state, &peer).await; expect_declare_msg(virtual_overseer, &test_state, &peer2).await; - distribute_collation(virtual_overseer, &test_state, test_state.relay_parent, true) - .await; + let DistributeCollation { candidate: old_candidate, .. } = + distribute_collation(virtual_overseer, &test_state, test_state.relay_parent, true) + .await; let old_relay_parent = test_state.relay_parent; - // Advance to a new round, while informing the subsystem that the old and the new relay + // Update our view, informing the subsystem that the old and the new relay // parent are active. - test_state.advance_to_new_round(virtual_overseer, true).await; + test_state.relay_parent.randomize(); + update_view( + &test_state, + virtual_overseer, + vec![(old_relay_parent, 10), (test_state.relay_parent, 10)], + 1, + ) + .await; - distribute_collation(virtual_overseer, &test_state, test_state.relay_parent, true) - .await; + let DistributeCollation { candidate: new_candidate, .. } = + distribute_collation(virtual_overseer, &test_state, test_state.relay_parent, true) + .await; send_peer_view_change(virtual_overseer, &peer, vec![old_relay_parent]).await; - expect_advertise_collation_msg(virtual_overseer, &[peer], old_relay_parent, None).await; + expect_advertise_collation_msg( + virtual_overseer, + &[peer], + old_relay_parent, + vec![old_candidate.hash()], + ) + .await; send_peer_view_change(virtual_overseer, &peer2, vec![test_state.relay_parent]).await; @@ -1227,7 +1083,7 @@ fn collate_on_two_different_relay_chain_blocks() { virtual_overseer, &[peer2], test_state.relay_parent, - None, + vec![new_candidate.hash()], ) .await; test_harness @@ -1251,28 +1107,33 @@ fn validator_reconnect_does_not_advertise_a_second_time() { let peer = test_state.current_group_validator_peer_ids()[0]; let validator_id = test_state.current_group_validator_authority_ids()[0].clone(); - setup_system(virtual_overseer, &test_state).await; + overseer_send(virtual_overseer, CollatorProtocolMessage::CollateOn(test_state.para_id)) + .await; + + update_view(&test_state, virtual_overseer, vec![(test_state.relay_parent, 10)], 1) + .await; // A validator connected to us - connect_peer(virtual_overseer, peer, CollationVersion::V1, Some(validator_id.clone())) + connect_peer(virtual_overseer, peer, CollationVersion::V2, Some(validator_id.clone())) .await; expect_declare_msg(virtual_overseer, &test_state, &peer).await; - distribute_collation(virtual_overseer, &test_state, test_state.relay_parent, true) - .await; + let DistributeCollation { candidate, .. } = + distribute_collation(virtual_overseer, &test_state, test_state.relay_parent, true) + .await; send_peer_view_change(virtual_overseer, &peer, vec![test_state.relay_parent]).await; expect_advertise_collation_msg( virtual_overseer, &[peer], test_state.relay_parent, - None, + vec![candidate.hash()], ) .await; // Disconnect and reconnect directly disconnect_peer(virtual_overseer, peer).await; - connect_peer(virtual_overseer, peer, CollationVersion::V1, Some(validator_id)).await; + connect_peer(virtual_overseer, peer, CollationVersion::V2, Some(validator_id)).await; expect_declare_msg(virtual_overseer, &test_state, &peer).await; send_peer_view_change(virtual_overseer, &peer, vec![test_state.relay_parent]).await; @@ -1300,10 +1161,14 @@ fn collators_reject_declare_messages() { let peer = test_state.current_group_validator_peer_ids()[0]; let validator_id = test_state.current_group_validator_authority_ids()[0].clone(); - setup_system(virtual_overseer, &test_state).await; + overseer_send(virtual_overseer, CollatorProtocolMessage::CollateOn(test_state.para_id)) + .await; + + update_view(&test_state, virtual_overseer, vec![(test_state.relay_parent, 10)], 1) + .await; // A validator connected to us - connect_peer(virtual_overseer, peer, CollationVersion::V1, Some(validator_id)).await; + connect_peer(virtual_overseer, peer, CollationVersion::V2, Some(validator_id)).await; expect_declare_msg(virtual_overseer, &test_state, &peer).await; overseer_send( @@ -1355,9 +1220,13 @@ where ReputationAggregator::new(|_| true), |mut test_harness| async move { let virtual_overseer = &mut test_harness.virtual_overseer; - let req_cfg = &mut test_harness.req_v1_cfg; + let req_cfg = &mut test_harness.req_v2_cfg; + + overseer_send(virtual_overseer, CollatorProtocolMessage::CollateOn(test_state.para_id)) + .await; - setup_system(virtual_overseer, &test_state).await; + update_view(&test_state, virtual_overseer, vec![(test_state.relay_parent, 10)], 1) + .await; let DistributeCollation { candidate, pov_block } = distribute_collation(virtual_overseer, &test_state, test_state.relay_parent, true) @@ -1368,7 +1237,7 @@ where .into_iter() .zip(test_state.current_group_validator_peer_ids()) { - connect_peer(virtual_overseer, peer, CollationVersion::V1, Some(val.clone())).await; + connect_peer(virtual_overseer, peer, CollationVersion::V2, Some(val.clone())).await; } // We declare to the connected validators that we are a collator. @@ -1393,14 +1262,14 @@ where virtual_overseer, &[validator_0], test_state.relay_parent, - None, + vec![candidate.hash()], ) .await; expect_advertise_collation_msg( virtual_overseer, &[validator_1], test_state.relay_parent, - None, + vec![candidate.hash()], ) .await; @@ -1412,9 +1281,10 @@ where .unwrap() .send(RawIncomingRequest { peer: validator_0, - payload: request_v1::CollationFetchingRequest { + payload: CollationFetchingRequest { relay_parent: test_state.relay_parent, para_id: test_state.para_id, + candidate_hash: candidate.hash(), } .encode(), pending_response, @@ -1446,9 +1316,10 @@ where .unwrap() .send(RawIncomingRequest { peer: validator_1, - payload: request_v1::CollationFetchingRequest { + payload: CollationFetchingRequest { relay_parent: test_state.relay_parent, para_id: test_state.para_id, + candidate_hash: candidate.hash(), } .encode(), pending_response, @@ -1490,16 +1361,22 @@ fn connect_to_buffered_groups() { ReputationAggregator::new(|_| true), |test_harness| async move { let mut virtual_overseer = test_harness.virtual_overseer; - let mut req_cfg = test_harness.req_v1_cfg; - let req_v2_cfg = test_harness.req_v2_cfg; + let mut req_cfg = test_harness.req_v2_cfg; + + overseer_send( + &mut virtual_overseer, + CollatorProtocolMessage::CollateOn(test_state.para_id), + ) + .await; - setup_system(&mut virtual_overseer, &test_state).await; + update_view(&test_state, &mut virtual_overseer, vec![(test_state.relay_parent, 10)], 1) + .await; let group_a = test_state.current_group_validator_authority_ids(); let peers_a = test_state.current_group_validator_peer_ids(); assert!(group_a.len() > 1); - distribute_collation( + let DistributeCollation { candidate, .. } = distribute_collation( &mut virtual_overseer, &test_state, test_state.relay_parent, @@ -1519,7 +1396,7 @@ fn connect_to_buffered_groups() { let head_a = test_state.relay_parent; for (val, peer) in group_a.iter().zip(&peers_a) { - connect_peer(&mut virtual_overseer, *peer, CollationVersion::V1, Some(val.clone())) + connect_peer(&mut virtual_overseer, *peer, CollationVersion::V2, Some(val.clone())) .await; } @@ -1530,8 +1407,13 @@ fn connect_to_buffered_groups() { // Update views. for peer_id in &peers_a { send_peer_view_change(&mut virtual_overseer, peer_id, vec![head_a]).await; - expect_advertise_collation_msg(&mut virtual_overseer, &[*peer_id], head_a, None) - .await; + expect_advertise_collation_msg( + &mut virtual_overseer, + &[*peer_id], + head_a, + vec![candidate.hash()], + ) + .await; } let peer = peers_a[0]; @@ -1543,9 +1425,10 @@ fn connect_to_buffered_groups() { .unwrap() .send(RawIncomingRequest { peer, - payload: request_v1::CollationFetchingRequest { + payload: CollationFetchingRequest { relay_parent: head_a, para_id: test_state.para_id, + candidate_hash: candidate.hash(), } .encode(), pending_response, @@ -1565,7 +1448,17 @@ fn connect_to_buffered_groups() { // Let the subsystem process process the collation event. test_helpers::Yield::new().await; - test_state.advance_to_new_round(&mut virtual_overseer, true).await; + let old_relay_parent = test_state.relay_parent; + test_state.relay_parent.randomize(); + + // Update our view. + update_view( + &test_state, + &mut virtual_overseer, + vec![(old_relay_parent, 10), (test_state.relay_parent, 20)], + 1, + ) + .await; test_state.group_rotation_info = test_state.group_rotation_info.bump_rotation(); let head_b = test_state.relay_parent; @@ -1596,7 +1489,7 @@ fn connect_to_buffered_groups() { } ); - TestHarness { virtual_overseer, req_v1_cfg: req_cfg, req_v2_cfg } + TestHarness { virtual_overseer, req_v2_cfg: req_cfg } }, ); } diff --git a/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs index 348feb9dd1dbc01641556b25c813e4f4c205c870..a5c74a1205c9ef5ef3d2e6d3d55a7e54c912c9f6 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs @@ -19,17 +19,16 @@ use super::*; use polkadot_node_subsystem::messages::ChainApiMessage; -use polkadot_primitives::{AsyncBackingParams, Header}; - -const ASYNC_BACKING_PARAMETERS: AsyncBackingParams = - AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: 3 }; +use polkadot_primitives::Header; +use rstest::rstest; fn get_parent_hash(hash: Hash) -> Hash { Hash::from_low_u64_be(hash.to_low_u64_be() + 1) } /// Handle a view update. -async fn update_view( +pub(super) async fn update_view( + test_state: &TestState, virtual_overseer: &mut VirtualOverseer, new_view: Vec<(Hash, u32)>, // Hash and block number. activated: u8, // How many new heads does this update contain? @@ -45,18 +44,19 @@ async fn update_view( .await; for _ in 0..activated { + // obtain the claim queue schedule. let (leaf_hash, leaf_number) = assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::RuntimeApi(RuntimeApiMessage::Request( parent, - RuntimeApiRequest::AsyncBackingParams(tx), + RuntimeApiRequest::ClaimQueue(tx), )) => { - tx.send(Ok(ASYNC_BACKING_PARAMETERS)).unwrap(); + tx.send(Ok(test_state.claim_queue.clone())).unwrap(); (parent, new_view.get(&parent).copied().expect("Unknown parent requested")) } ); - let min_number = leaf_number.saturating_sub(ASYNC_BACKING_PARAMETERS.allowed_ancestry_len); + let min_number = leaf_number.saturating_sub(SCHEDULING_LOOKAHEAD as u32 - 1); let ancestry_len = leaf_number + 1 - min_number; let ancestry_hashes = std::iter::successors(Some(leaf_hash), |h| Some(get_parent_hash(*h))) @@ -85,12 +85,12 @@ async fn update_view( AllMessages::RuntimeApi( RuntimeApiMessage::Request( .., - RuntimeApiRequest::AsyncBackingParams( + RuntimeApiRequest::SessionIndexForChild( tx ) ) ) => { - tx.send(Ok(ASYNC_BACKING_PARAMETERS)).unwrap(); + tx.send(Ok(1)).unwrap(); } ); @@ -99,12 +99,14 @@ async fn update_view( AllMessages::RuntimeApi( RuntimeApiMessage::Request( .., - RuntimeApiRequest::SessionIndexForChild( + RuntimeApiRequest::SchedulingLookahead( + session_index, tx ) ) ) => { - tx.send(Ok(1)).unwrap(); + assert_eq!(session_index, 1); + tx.send(Ok(SCHEDULING_LOOKAHEAD as u32)).unwrap(); } ); @@ -117,9 +119,10 @@ async fn update_view( .. } ) => { - assert_eq!(k, ASYNC_BACKING_PARAMETERS.allowed_ancestry_len as usize); - - tx.send(Ok(ancestry_hashes.clone().skip(1).into_iter().collect())).unwrap(); + assert_eq!(k, SCHEDULING_LOOKAHEAD - 1); + let hashes: Vec<_> = ancestry_hashes.clone().skip(1).into_iter().collect(); + assert_eq!(k, hashes.len()); + tx.send(Ok(hashes)).unwrap(); } ); } @@ -140,10 +143,11 @@ async fn update_view( ); } - while let Some((hash, number)) = ancestry_iter.next() { + let mut iter_clone = ancestry_iter.clone(); + while let Some((hash, number)) = iter_clone.next() { // May be `None` for the last element. let parent_hash = - ancestry_iter.peek().map(|(h, _)| *h).unwrap_or_else(|| get_parent_hash(hash)); + iter_clone.peek().map(|(h, _)| *h).unwrap_or_else(|| get_parent_hash(hash)); let Some(msg) = overseer_peek_with_timeout(virtual_overseer, Duration::from_millis(50)).await @@ -175,11 +179,40 @@ async fn update_view( } ); } + + for _ in ancestry_iter { + let Some(msg) = + overseer_peek_with_timeout(virtual_overseer, Duration::from_millis(50)).await + else { + return + }; + + if !matches!( + &msg, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _, + RuntimeApiRequest::ClaimQueue(_) + )) + ) { + // Claim queue has already been fetched for this leaf. + break + } + + assert_matches!( + overseer_recv_with_timeout(virtual_overseer, Duration::from_millis(50)).await.unwrap(), + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _, + RuntimeApiRequest::ClaimQueue(tx), + )) => { + tx.send(Ok(test_state.claim_queue.clone())).unwrap(); + } + ); + } } } /// Check that the next received message is a `Declare` message. -pub(super) async fn expect_declare_msg_v2( +pub(super) async fn expect_declare_msg( virtual_overseer: &mut VirtualOverseer, test_state: &TestState, peer: &PeerId, @@ -214,8 +247,12 @@ pub(super) async fn expect_declare_msg_v2( /// Test that a collator distributes a collation from the allowed ancestry /// to correct validators group. -#[test] -fn distribute_collation_from_implicit_view() { +/// Run once with validators sending their view first and then the collator setting their own +/// view first. +#[rstest] +#[case(true)] +#[case(false)] +fn distribute_collation_from_implicit_view(#[case] validator_sends_view_first: bool) { let head_a = Hash::from_low_u64_be(126); let head_a_num: u32 = 66; @@ -227,163 +264,160 @@ fn distribute_collation_from_implicit_view() { let head_c = Hash::from_low_u64_be(130); let head_c_num = 62; - // Run once with validators sending their view first and then the collator setting their own - // view first. - for validator_sends_view_first in [true, false] { - let group_rotation_info = GroupRotationInfo { - session_start_block: head_c_num - 2, - group_rotation_frequency: 3, - now: head_c_num, - }; - - let mut test_state = TestState::default(); - test_state.group_rotation_info = group_rotation_info; - - let local_peer_id = test_state.local_peer_id; - let collator_pair = test_state.collator_pair.clone(); - - test_harness( - local_peer_id, - collator_pair, - ReputationAggregator::new(|_| true), - |mut test_harness| async move { - let virtual_overseer = &mut test_harness.virtual_overseer; - - // Set collating para id. - overseer_send( + let group_rotation_info = GroupRotationInfo { + session_start_block: head_c_num - 2, + group_rotation_frequency: 3, + now: head_c_num, + }; + + let mut test_state = TestState::default(); + test_state.group_rotation_info = group_rotation_info; + + let local_peer_id = test_state.local_peer_id; + let collator_pair = test_state.collator_pair.clone(); + + test_harness( + local_peer_id, + collator_pair, + ReputationAggregator::new(|_| true), + |mut test_harness| async move { + let virtual_overseer = &mut test_harness.virtual_overseer; + + // Set collating para id. + overseer_send(virtual_overseer, CollatorProtocolMessage::CollateOn(test_state.para_id)) + .await; + + if validator_sends_view_first { + // Activate leaf `c` to accept at least the collation. + update_view(&test_state, virtual_overseer, vec![(head_c, head_c_num)], 1).await; + } else { + // Activated leaf is `b`, but the collation will be based on `c`. + update_view(&test_state, virtual_overseer, vec![(head_b, head_b_num)], 1).await; + } + + let validator_peer_ids = test_state.current_group_validator_peer_ids(); + for (val, peer) in test_state + .current_group_validator_authority_ids() + .into_iter() + .zip(validator_peer_ids.clone()) + { + connect_peer(virtual_overseer, peer, CollationVersion::V2, Some(val.clone())).await; + } + + // Collator declared itself to each peer. + for peer_id in &validator_peer_ids { + expect_declare_msg(virtual_overseer, &test_state, peer_id).await; + } + + let pov = PoV { block_data: BlockData(vec![1, 2, 3]) }; + let parent_head_data_hash = Hash::repeat_byte(0xAA); + let candidate = TestCandidateBuilder { + para_id: test_state.para_id, + relay_parent: head_c, + pov_hash: pov.hash(), + ..Default::default() + } + .build(); + let DistributeCollation { candidate, pov_block: _ } = + distribute_collation_with_receipt( virtual_overseer, - CollatorProtocolMessage::CollateOn(test_state.para_id), + &test_state, + head_c, + false, // Check the group manually. + candidate, + pov, + parent_head_data_hash, ) .await; + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::NetworkBridgeTx( + NetworkBridgeTxMessage::ConnectToValidators { validator_ids, .. } + ) => { + let expected_validators = test_state.current_group_validator_authority_ids(); - if validator_sends_view_first { - // Activate leaf `c` to accept at least the collation. - update_view(virtual_overseer, vec![(head_c, head_c_num)], 1).await; - } else { - // Activated leaf is `b`, but the collation will be based on `c`. - update_view(virtual_overseer, vec![(head_b, head_b_num)], 1).await; + assert_eq!(expected_validators, validator_ids); } + ); - let validator_peer_ids = test_state.current_group_validator_peer_ids(); - for (val, peer) in test_state - .current_group_validator_authority_ids() - .into_iter() - .zip(validator_peer_ids.clone()) - { - connect_peer(virtual_overseer, peer, CollationVersion::V2, Some(val.clone())) - .await; - } + let candidate_hash = candidate.hash(); - // Collator declared itself to each peer. - for peer_id in &validator_peer_ids { - expect_declare_msg_v2(virtual_overseer, &test_state, peer_id).await; - } + // Update peer views. + for peer_id in &validator_peer_ids { + send_peer_view_change(virtual_overseer, peer_id, vec![head_b]).await; - let pov = PoV { block_data: BlockData(vec![1, 2, 3]) }; - let parent_head_data_hash = Hash::repeat_byte(0xAA); - let candidate = TestCandidateBuilder { - para_id: test_state.para_id, - relay_parent: head_c, - pov_hash: pov.hash(), - ..Default::default() - } - .build(); - let DistributeCollation { candidate, pov_block: _ } = - distribute_collation_with_receipt( + if !validator_sends_view_first { + expect_advertise_collation_msg( virtual_overseer, - &test_state, + &[*peer_id], head_c, - false, // Check the group manually. - candidate, - pov, - parent_head_data_hash, + vec![candidate_hash], ) .await; - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::ConnectToValidators { validator_ids, .. } - ) => { - let expected_validators = test_state.current_group_validator_authority_ids(); - - assert_eq!(expected_validators, validator_ids); - } - ); - - let candidate_hash = candidate.hash(); - - // Update peer views. - for peer_id in &validator_peer_ids { - send_peer_view_change(virtual_overseer, peer_id, vec![head_b]).await; - - if !validator_sends_view_first { - expect_advertise_collation_msg( - virtual_overseer, - &[*peer_id], - head_c, - Some(vec![candidate_hash]), - ) - .await; - } } + } - if validator_sends_view_first { - // Activated leaf is `b`, but the collation will be based on `c`. - update_view(virtual_overseer, vec![(head_b, head_b_num)], 1).await; + if validator_sends_view_first { + // Activated leaf is `b`, but the collation will be based on `c`. + update_view(&test_state, virtual_overseer, vec![(head_b, head_b_num)], 1).await; - for _ in &validator_peer_ids { - expect_advertise_collation_msg( - virtual_overseer, - &validator_peer_ids, - head_c, - Some(vec![candidate_hash]), - ) - .await; - } + for _ in &validator_peer_ids { + expect_advertise_collation_msg( + virtual_overseer, + &validator_peer_ids, + head_c, + vec![candidate_hash], + ) + .await; } + } - // Head `c` goes out of view. - // Build a different candidate for this relay parent and attempt to distribute it. - update_view(virtual_overseer, vec![(head_a, head_a_num)], 1).await; + // Head `c` goes out of view. + // Build a different candidate for this relay parent and attempt to distribute it. + update_view(&test_state, virtual_overseer, vec![(head_a, head_a_num)], 1).await; - let pov = PoV { block_data: BlockData(vec![4, 5, 6]) }; - let parent_head_data_hash = Hash::repeat_byte(0xBB); - let candidate = TestCandidateBuilder { - para_id: test_state.para_id, - relay_parent: head_c, - pov_hash: pov.hash(), - ..Default::default() - } - .build(); - overseer_send( - virtual_overseer, - CollatorProtocolMessage::DistributeCollation { - candidate_receipt: candidate.clone(), - parent_head_data_hash, - pov: pov.clone(), - parent_head_data: HeadData(vec![1, 2, 3]), - result_sender: None, - core_index: CoreIndex(0), - }, - ) - .await; + let pov = PoV { block_data: BlockData(vec![4, 5, 6]) }; + let parent_head_data_hash = Hash::repeat_byte(0xBB); + let candidate = TestCandidateBuilder { + para_id: test_state.para_id, + relay_parent: head_c, + pov_hash: pov.hash(), + ..Default::default() + } + .build(); + overseer_send( + virtual_overseer, + CollatorProtocolMessage::DistributeCollation { + candidate_receipt: candidate.clone(), + parent_head_data_hash, + pov: pov.clone(), + parent_head_data: HeadData(vec![1, 2, 3]), + result_sender: None, + core_index: CoreIndex(0), + }, + ) + .await; - // Parent out of view, nothing happens. - assert!(overseer_recv_with_timeout(virtual_overseer, Duration::from_millis(100)) - .await - .is_none()); + // Parent out of view, nothing happens. + assert!(overseer_recv_with_timeout(virtual_overseer, Duration::from_millis(100)) + .await + .is_none()); - test_harness - }, - ); - } + test_harness + }, + ); } -/// Tests that collator can distribute up to `MAX_CANDIDATE_DEPTH + 1` candidates -/// per relay parent. +/// Tests that collator respects the per relay parent limit of collations, which is equal to the +/// number of assignments they have in the claim queue for that core. #[test] fn distribute_collation_up_to_limit() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); + // Claim queue has 4 assignments for our paraid on core 0, 1 assignment for another paraid on + // core 1. Let's replace one of our assignments on core 0. + + *test_state.claim_queue.get_mut(&CoreIndex(0)).unwrap().get_mut(1).unwrap() = ParaId::from(3); + let expected_assignments = SCHEDULING_LOOKAHEAD - 1; let local_peer_id = test_state.local_peer_id; let collator_pair = test_state.collator_pair.clone(); @@ -405,15 +439,16 @@ fn distribute_collation_up_to_limit() { overseer_send(virtual_overseer, CollatorProtocolMessage::CollateOn(test_state.para_id)) .await; // Activated leaf is `a`, but the collation will be based on `b`. - update_view(virtual_overseer, vec![(head_a, head_a_num)], 1).await; + update_view(&test_state, virtual_overseer, vec![(head_a, head_a_num)], 1).await; - for i in 0..(ASYNC_BACKING_PARAMETERS.max_candidate_depth + 1) { + for i in 0..expected_assignments { let pov = PoV { block_data: BlockData(vec![i as u8]) }; let parent_head_data_hash = Hash::repeat_byte(0xAA); let candidate = TestCandidateBuilder { para_id: test_state.para_id, relay_parent: head_b, pov_hash: pov.hash(), + core_index: CoreIndex(0), ..Default::default() } .build(); @@ -435,6 +470,7 @@ fn distribute_collation_up_to_limit() { para_id: test_state.para_id, relay_parent: head_b, pov_hash: pov.hash(), + core_index: CoreIndex(0), ..Default::default() } .build(); @@ -452,6 +488,35 @@ fn distribute_collation_up_to_limit() { .await; // Limit has been reached. + assert!(overseer_recv_with_timeout(virtual_overseer, Duration::from_millis(100)) + .await + .is_none()); + + // Let's also try on core 1, where we don't have any assignments. + + let pov = PoV { block_data: BlockData(vec![10, 12, 6]) }; + let parent_head_data_hash = Hash::repeat_byte(0xBB); + let candidate = TestCandidateBuilder { + para_id: test_state.para_id, + relay_parent: head_b, + pov_hash: pov.hash(), + core_index: CoreIndex(1), + ..Default::default() + } + .build(); + overseer_send( + virtual_overseer, + CollatorProtocolMessage::DistributeCollation { + candidate_receipt: candidate.clone(), + parent_head_data_hash, + pov: pov.clone(), + parent_head_data: HeadData(vec![1, 2, 3]), + result_sender: None, + core_index: CoreIndex(1), + }, + ) + .await; + assert!(overseer_recv_with_timeout(virtual_overseer, Duration::from_millis(100)) .await .is_none()); @@ -476,7 +541,6 @@ fn send_parent_head_data_for_elastic_scaling() { ReputationAggregator::new(|_| true), |test_harness| async move { let mut virtual_overseer = test_harness.virtual_overseer; - let req_v1_cfg = test_harness.req_v1_cfg; let mut req_v2_cfg = test_harness.req_v2_cfg; let head_b = Hash::from_low_u64_be(129); @@ -488,7 +552,7 @@ fn send_parent_head_data_for_elastic_scaling() { CollatorProtocolMessage::CollateOn(test_state.para_id), ) .await; - update_view(&mut virtual_overseer, vec![(head_b, head_b_num)], 1).await; + update_view(&test_state, &mut virtual_overseer, vec![(head_b, head_b_num)], 1).await; let pov_data = PoV { block_data: BlockData(vec![1 as u8]) }; let candidate = TestCandidateBuilder { @@ -522,12 +586,11 @@ fn send_parent_head_data_for_elastic_scaling() { Some(validator_id.clone()), ) .await; - expect_declare_msg_v2(&mut virtual_overseer, &test_state, &peer).await; + expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await; send_peer_view_change(&mut virtual_overseer, &peer, vec![head_b]).await; let hashes: Vec<_> = vec![candidate.hash()]; - expect_advertise_collation_msg(&mut virtual_overseer, &[peer], head_b, Some(hashes)) - .await; + expect_advertise_collation_msg(&mut virtual_overseer, &[peer], head_b, hashes).await; let (pending_response, rx) = oneshot::channel(); req_v2_cfg @@ -536,7 +599,7 @@ fn send_parent_head_data_for_elastic_scaling() { .unwrap() .send(RawIncomingRequest { peer, - payload: request_v2::CollationFetchingRequest { + payload: CollationFetchingRequest { relay_parent: head_b, para_id: test_state.para_id, candidate_hash: candidate.hash(), @@ -550,14 +613,14 @@ fn send_parent_head_data_for_elastic_scaling() { assert_matches!( rx.await, Ok(full_response) => { - let response: request_v2::CollationFetchingResponse = - request_v2::CollationFetchingResponse::decode(&mut - full_response.result + let response: CollationFetchingResponse = + CollationFetchingResponse::decode( + &mut full_response.result .expect("We should have a proper answer").as_ref() ).expect("Decoding should work"); assert_matches!( response, - request_v1::CollationFetchingResponse::CollationWithParentHeadData { + CollationFetchingResponse::CollationWithParentHeadData { receipt, pov, parent_head_data } => { assert_eq!(receipt, candidate); @@ -568,7 +631,7 @@ fn send_parent_head_data_for_elastic_scaling() { } ); - TestHarness { virtual_overseer, req_v1_cfg, req_v2_cfg } + TestHarness { virtual_overseer, req_v2_cfg } }, ) } @@ -587,7 +650,6 @@ fn advertise_and_send_collation_by_hash() { ReputationAggregator::new(|_| true), |test_harness| async move { let mut virtual_overseer = test_harness.virtual_overseer; - let req_v1_cfg = test_harness.req_v1_cfg; let mut req_v2_cfg = test_harness.req_v2_cfg; let head_a = Hash::from_low_u64_be(128); @@ -603,8 +665,8 @@ fn advertise_and_send_collation_by_hash() { CollatorProtocolMessage::CollateOn(test_state.para_id), ) .await; - update_view(&mut virtual_overseer, vec![(head_b, head_b_num)], 1).await; - update_view(&mut virtual_overseer, vec![(head_a, head_a_num)], 1).await; + update_view(&test_state, &mut virtual_overseer, vec![(head_b, head_b_num)], 1).await; + update_view(&test_state, &mut virtual_overseer, vec![(head_a, head_a_num)], 1).await; let candidates: Vec<_> = (0..2) .map(|i| { @@ -641,13 +703,12 @@ fn advertise_and_send_collation_by_hash() { Some(validator_id.clone()), ) .await; - expect_declare_msg_v2(&mut virtual_overseer, &test_state, &peer).await; + expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await; // Head `b` is not a leaf, but both advertisements are still relevant. send_peer_view_change(&mut virtual_overseer, &peer, vec![head_b]).await; let hashes: Vec<_> = candidates.iter().map(|(candidate, _)| candidate.hash()).collect(); - expect_advertise_collation_msg(&mut virtual_overseer, &[peer], head_b, Some(hashes)) - .await; + expect_advertise_collation_msg(&mut virtual_overseer, &[peer], head_b, hashes).await; for (candidate, pov_block) in candidates { let (pending_response, rx) = oneshot::channel(); @@ -657,7 +718,7 @@ fn advertise_and_send_collation_by_hash() { .unwrap() .send(RawIncomingRequest { peer, - payload: request_v2::CollationFetchingRequest { + payload: CollationFetchingRequest { relay_parent: head_b, para_id: test_state.para_id, candidate_hash: candidate.hash(), @@ -682,7 +743,7 @@ fn advertise_and_send_collation_by_hash() { ); } - TestHarness { virtual_overseer, req_v1_cfg, req_v2_cfg } + TestHarness { virtual_overseer, req_v2_cfg } }, ) } diff --git a/polkadot/node/network/collator-protocol/src/error.rs b/polkadot/node/network/collator-protocol/src/error.rs index ae7f9a8c1fbc823cc0b6b89dc7303de57faed794..97fd4076bb8f597d11086b6ac1d7c6e977afcc67 100644 --- a/polkadot/node/network/collator-protocol/src/error.rs +++ b/polkadot/node/network/collator-protocol/src/error.rs @@ -70,6 +70,9 @@ pub enum Error { #[error("Response receiver for claim queue request cancelled")] CancelledClaimQueue(oneshot::Canceled), + + #[error("No state for the relay parent")] + RelayParentStateNotFound, } /// An error happened on the validator side of the protocol when attempting @@ -122,7 +125,7 @@ impl SecondingError { PersistedValidationDataMismatch | CandidateHashMismatch | RelayParentMismatch | - Duplicate | ParentHeadDataMismatch | + ParentHeadDataMismatch | InvalidCoreIndex(_, _) | InvalidSessionIndex(_, _) | InvalidReceiptVersion(_) diff --git a/polkadot/node/network/collator-protocol/src/lib.rs b/polkadot/node/network/collator-protocol/src/lib.rs index 1edc67664172400503158f8a451756ca3674a3d9..79b5719ba3a448e6ce6e4bcac40a8924359eb59c 100644 --- a/polkadot/node/network/collator-protocol/src/lib.rs +++ b/polkadot/node/network/collator-protocol/src/lib.rs @@ -32,7 +32,7 @@ use polkadot_node_subsystem_util::reputation::ReputationAggregator; use sp_keystore::KeystorePtr; use polkadot_node_network_protocol::{ - request_response::{v1 as request_v1, v2 as protocol_v2, IncomingRequestReceiver}, + request_response::{v2 as protocol_v2, IncomingRequestReceiver}, PeerId, UnifiedReputationChange as Rep, }; use polkadot_primitives::CollatorPair; @@ -81,8 +81,6 @@ pub enum ProtocolSide { peer_id: PeerId, /// Parachain collator pair. collator_pair: CollatorPair, - /// Receiver for v1 collation fetching requests. - request_receiver_v1: IncomingRequestReceiver<request_v1::CollationFetchingRequest>, /// Receiver for v2 collation fetching requests. request_receiver_v2: IncomingRequestReceiver<protocol_v2::CollationFetchingRequest>, /// Metrics. @@ -116,22 +114,10 @@ impl<Context> CollatorProtocolSubsystem { validator_side::run(ctx, keystore, eviction_policy, metrics) .map_err(|e| SubsystemError::with_origin("collator-protocol", e)) .boxed(), - ProtocolSide::Collator { - peer_id, - collator_pair, - request_receiver_v1, - request_receiver_v2, - metrics, - } => collator_side::run( - ctx, - peer_id, - collator_pair, - request_receiver_v1, - request_receiver_v2, - metrics, - ) - .map_err(|e| SubsystemError::with_origin("collator-protocol", e)) - .boxed(), + ProtocolSide::Collator { peer_id, collator_pair, request_receiver_v2, metrics } => + collator_side::run(ctx, peer_id, collator_pair, request_receiver_v2, metrics) + .map_err(|e| SubsystemError::with_origin("collator-protocol", e)) + .boxed(), ProtocolSide::None => return DummySubsystem.start(ctx), }; diff --git a/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs new file mode 100644 index 0000000000000000000000000000000000000000..d677da1ac4f03329bf77df2a85c355b96f8aeb9d --- /dev/null +++ b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs @@ -0,0 +1,1055 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! `ClaimQueueState` tracks the state of the claim queue over a set of relay blocks. Refer to +//! [`ClaimQueueState`] for more details. + +use std::collections::VecDeque; + +use crate::LOG_TARGET; +use polkadot_primitives::{Hash, Id as ParaId}; + +/// Represents a single claim from the claim queue, mapped to the relay chain block where it could +/// be backed on-chain. +#[derive(Debug, PartialEq)] +struct ClaimInfo { + // Hash of the relay chain block. Can be `None` if it is still not known (a future block). + hash: Option<Hash>, + /// Represents the `ParaId` scheduled for the block. Can be `None` if nothing is scheduled. + claim: Option<ParaId>, + /// The length of the claim queue at the block. It is used to determine the 'block window' + /// where a claim can be made. + claim_queue_len: usize, + /// A flag that indicates if the slot is claimed or not. + claimed: bool, +} + +/// Tracks the state of the claim queue over a set of relay blocks. +/// +/// Generally the claim queue represents the `ParaId` that should be scheduled at the current block +/// (the first element of the claim queue) and N other `ParaId`s which are supposed to be scheduled +/// on the next relay blocks. In other words the claim queue is a rolling window giving a hint what +/// should be built/fetched/accepted (depending on the context) at each block. +/// +/// Since the claim queue peeks into the future blocks there is a relation between the claim queue +/// state between the current block and the future blocks. +/// Let's see an example with 2 co-scheduled parachains: +/// - relay parent 1; Claim queue: [A, B, A] +/// - relay parent 2; Claim queue: [B, A, B] +/// - relay parent 3; Claim queue: [A, B, A] +/// - and so on +/// +/// Note that at rp1 the second element in the claim queue is equal to the first one in rp2. Also +/// the third element of the claim queue at rp1 is equal to the second one in rp2 and the first one +/// in rp3. +/// +/// So if we want to claim the third slot at rp 1 we are also claiming the second at rp2 and first +/// at rp3. To track this in a simple way we can project the claim queue onto the relay blocks like +/// this: +/// [A] [B] [A] -> this is the claim queue at rp3 +/// [B] [A] [B] -> this is the claim queue at rp2 +/// [A] [B] [A] -> this is the claim queue at rp1 +/// [RP 1][RP 2][RP 3][RP X][RP Y] -> relay blocks, RP x and RP Y are future blocks +/// +/// Note that the claims at each column are the same so we can simplify this by just projecting a +/// single claim over a block: +/// [A] [B] [A] [B] [A] -> claims effectively are the same +/// [RP 1][RP 2][RP 3][RP X][RP Y] -> relay blocks, RP x and RP Y are future blocks +/// +/// Basically this is how `ClaimQueueState` works. It keeps track of claims at each block by mapping +/// claims to relay blocks. +/// +/// How making a claim works? +/// At each relay block we keep track how long is the claim queue. This is a 'window' where we can +/// make a claim. So adding a claim just looks for a free spot at this window and claims it. +/// +/// Note on adding a new leaf. +/// When a new leaf is added we check if the first element in its claim queue matches with the +/// projection on the first element in 'future blocks'. If yes - the new relay block inherits this +/// claim. If not - this means that the claim queue changed for some reason so the claim can't be +/// inherited. This should not happen under normal circumstances. But if it happens it means that we +/// have got one claim which won't be satisfied in the worst case scenario. +pub(crate) struct ClaimQueueState { + block_state: VecDeque<ClaimInfo>, + future_blocks: VecDeque<ClaimInfo>, +} + +impl ClaimQueueState { + pub(crate) fn new() -> Self { + Self { block_state: VecDeque::new(), future_blocks: VecDeque::new() } + } + + // Appends a new leaf + pub(crate) fn add_leaf(&mut self, hash: &Hash, claim_queue: &Vec<ParaId>) { + if self.block_state.iter().any(|s| s.hash == Some(*hash)) { + return + } + + // First check if our view for the future blocks is consistent with the one in the claim + // queue of the new block. If not - the claim queue has changed for some reason and we need + // to readjust our view. + for (idx, expected_claim) in claim_queue.iter().enumerate() { + match self.future_blocks.get_mut(idx) { + Some(future_block) => + if future_block.claim.as_ref() != Some(expected_claim) { + // There is an inconsistency. Update our view with the one from the claim + // queue. `claimed` can't be true anymore since the `ParaId` has changed. + future_block.claimed = false; + future_block.claim = Some(*expected_claim); + }, + None => { + self.future_blocks.push_back(ClaimInfo { + hash: None, + claim: Some(*expected_claim), + // For future blocks we don't know the size of the claim queue. + // `claim_queue_len` could be an option but there is not much benefit from + // the extra boilerplate code to handle it. We set it to one since we + // usually know about one claim at each future block but this value is not + // used anywhere in the code. + claim_queue_len: 1, + claimed: false, + }); + }, + } + } + + // Now pop the first future block and add it as a leaf + let claim_info = if let Some(new_leaf) = self.future_blocks.pop_front() { + ClaimInfo { + hash: Some(*hash), + claim: claim_queue.first().copied(), + claim_queue_len: claim_queue.len(), + claimed: new_leaf.claimed, + } + } else { + // maybe the claim queue was empty but we still need to add a leaf + ClaimInfo { + hash: Some(*hash), + claim: claim_queue.first().copied(), + claim_queue_len: claim_queue.len(), + claimed: false, + } + }; + + // `future_blocks` can't be longer than the length of the claim queue at the last block - 1. + // For example this can happen if at relay block N we have got a claim queue of a length 4 + // and it's shrunk to 2. + self.future_blocks.truncate(claim_queue.len().saturating_sub(1)); + + self.block_state.push_back(claim_info); + } + + fn get_window<'a>( + &'a mut self, + relay_parent: &'a Hash, + ) -> impl Iterator<Item = &'a mut ClaimInfo> + 'a { + let mut window = self + .block_state + .iter_mut() + .skip_while(|b| b.hash != Some(*relay_parent)) + .peekable(); + let cq_len = window.peek().map_or(0, |b| b.claim_queue_len); + window.chain(self.future_blocks.iter_mut()).take(cq_len) + } + + pub(crate) fn claim_at(&mut self, relay_parent: &Hash, para_id: &ParaId) -> bool { + gum::trace!( + target: LOG_TARGET, + ?para_id, + ?relay_parent, + "claim_at" + ); + self.find_a_claim(relay_parent, para_id, true) + } + + pub(crate) fn can_claim_at(&mut self, relay_parent: &Hash, para_id: &ParaId) -> bool { + gum::trace!( + target: LOG_TARGET, + ?para_id, + ?relay_parent, + "can_claim_at" + ); + + self.find_a_claim(relay_parent, para_id, false) + } + + // Returns `true` if there is a claim within `relay_parent`'s view of the claim queue for + // `para_id`. If `claim_it` is set to `true` the slot is claimed. Otherwise the function just + // reports the availability of the slot. + fn find_a_claim(&mut self, relay_parent: &Hash, para_id: &ParaId, claim_it: bool) -> bool { + let window = self.get_window(relay_parent); + + for w in window { + gum::trace!( + target: LOG_TARGET, + ?para_id, + ?relay_parent, + claim_info=?w, + ?claim_it, + "Checking claim" + ); + + if !w.claimed && w.claim == Some(*para_id) { + w.claimed = claim_it; + return true + } + } + + false + } + + pub(crate) fn unclaimed_at(&mut self, relay_parent: &Hash) -> Vec<ParaId> { + let window = self.get_window(relay_parent); + + window.filter(|b| !b.claimed).filter_map(|b| b.claim).collect() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn sane_initial_state() { + let mut state = ClaimQueueState::new(); + let relay_parent = Hash::from_low_u64_be(1); + let para_id = ParaId::new(1); + + assert!(!state.can_claim_at(&relay_parent, ¶_id)); + assert!(!state.claim_at(&relay_parent, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent), vec![]); + } + + #[test] + fn add_leaf_works() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id = ParaId::new(1); + let claim_queue = vec![para_id, para_id, para_id]; + + state.add_leaf(&relay_parent_a, &claim_queue); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id, para_id, para_id]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: false, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false } + ]) + ); + + // should be no op + state.add_leaf(&relay_parent_a, &claim_queue); + assert_eq!(state.block_state.len(), 1); + assert_eq!(state.future_blocks.len(), 2); + + // add another leaf + let relay_parent_b = Hash::from_low_u64_be(2); + state.add_leaf(&relay_parent_b, &claim_queue); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: false, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id), + claim_queue_len: 3, + claimed: false, + } + ]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false } + ]) + ); + + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id, para_id, para_id]); + assert_eq!(state.unclaimed_at(&relay_parent_b), vec![para_id, para_id, para_id]); + } + + #[test] + fn claims_at_separate_relay_parents_work() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let relay_parent_b = Hash::from_low_u64_be(2); + let para_id = ParaId::new(1); + let claim_queue = vec![para_id, para_id, para_id]; + + state.add_leaf(&relay_parent_a, &claim_queue); + state.add_leaf(&relay_parent_b, &claim_queue); + + // add one claim for a + assert!(state.can_claim_at(&relay_parent_a, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id, para_id, para_id]); + assert!(state.claim_at(&relay_parent_a, ¶_id)); + + // and one for b + assert!(state.can_claim_at(&relay_parent_b, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_b), vec![para_id, para_id, para_id]); + assert!(state.claim_at(&relay_parent_b, ¶_id)); + + // a should have one claim since the one for b was claimed + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id]); + // and two more for b + assert_eq!(state.unclaimed_at(&relay_parent_b), vec![para_id, para_id]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + } + ]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false } + ]) + ); + } + + #[test] + fn claims_are_transferred_to_next_slot() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id = ParaId::new(1); + let claim_queue = vec![para_id, para_id, para_id]; + + state.add_leaf(&relay_parent_a, &claim_queue); + + // add two claims, 2nd should be transferred to a new leaf + assert!(state.can_claim_at(&relay_parent_a, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id, para_id, para_id]); + assert!(state.claim_at(&relay_parent_a, ¶_id)); + + assert!(state.can_claim_at(&relay_parent_a, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id, para_id]); + assert!(state.claim_at(&relay_parent_a, ¶_id)); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false } + ]) + ); + + // one more + assert!(state.can_claim_at(&relay_parent_a, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id]); + assert!(state.claim_at(&relay_parent_a, ¶_id)); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true } + ]) + ); + + // no more claims + assert!(!state.can_claim_at(&relay_parent_a, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + } + + #[test] + fn claims_are_transferred_to_new_leaves() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id = ParaId::new(1); + let claim_queue = vec![para_id, para_id, para_id]; + + state.add_leaf(&relay_parent_a, &claim_queue); + + for _ in 0..3 { + assert!(state.can_claim_at(&relay_parent_a, ¶_id)); + assert!(state.claim_at(&relay_parent_a, ¶_id)); + } + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true } + ]) + ); + + // no more claims + assert!(!state.can_claim_at(&relay_parent_a, ¶_id)); + + // new leaf + let relay_parent_b = Hash::from_low_u64_be(2); + state.add_leaf(&relay_parent_b, &claim_queue); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + } + ]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false } + ]) + ); + + // still no claims for a + assert!(!state.can_claim_at(&relay_parent_a, ¶_id)); + + // but can accept for b + assert!(state.can_claim_at(&relay_parent_b, ¶_id)); + assert!(state.claim_at(&relay_parent_b, ¶_id)); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + } + ]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true } + ]) + ); + } + + #[test] + fn two_paras() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id_a = ParaId::new(1); + let para_id_b = ParaId::new(2); + let claim_queue = vec![para_id_a, para_id_b, para_id_a]; + + state.add_leaf(&relay_parent_a, &claim_queue); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_a, para_id_b, para_id_a]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: false, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { + hash: None, + claim: Some(para_id_b), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + } + ]) + ); + + assert!(state.claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_b, para_id_a]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { + hash: None, + claim: Some(para_id_b), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + } + ]) + ); + + assert!(state.claim_at(&relay_parent_a, ¶_id_a)); + assert!(!state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_b]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { + hash: None, + claim: Some(para_id_b), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { hash: None, claim: Some(para_id_a), claim_queue_len: 1, claimed: true } + ]) + ); + + assert!(state.claim_at(&relay_parent_a, ¶_id_b)); + assert!(!state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(!state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id_b), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id_a), claim_queue_len: 1, claimed: true } + ]) + ); + } + + #[test] + fn claim_queue_changes_unexpectedly() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id_a = ParaId::new(1); + let para_id_b = ParaId::new(2); + let claim_queue_a = vec![para_id_a, para_id_b, para_id_a]; + + state.add_leaf(&relay_parent_a, &claim_queue_a); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert!(state.claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id_b), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id_a), claim_queue_len: 1, claimed: true } + ]) + ); + + let relay_parent_b = Hash::from_low_u64_be(2); + let claim_queue_b = vec![para_id_a, para_id_a, para_id_a]; // should be [b, a, ...] + state.add_leaf(&relay_parent_b, &claim_queue_b); + + // because of the unexpected change in claim queue we lost the claim for paraB and have one + // unclaimed for paraA + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_a]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: false, + } + ]) + ); + assert_eq!( + state.future_blocks, + // since the 3rd slot of the claim queue at rp1 is equal to the second one in rp2, this + // claim still exists + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id_a), claim_queue_len: 1, claimed: true }, + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + } + ]) + ); + } + + #[test] + fn claim_queue_changes_unexpectedly_with_two_blocks() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id_a = ParaId::new(1); + let para_id_b = ParaId::new(2); + let claim_queue_a = vec![para_id_a, para_id_b, para_id_b]; + + state.add_leaf(&relay_parent_a, &claim_queue_a); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert!(state.claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.claim_at(&relay_parent_a, ¶_id_b)); + assert!(state.claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id_b), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id_b), claim_queue_len: 1, claimed: true } + ]) + ); + + let relay_parent_b = Hash::from_low_u64_be(2); + let claim_queue_b = vec![para_id_a, para_id_a, para_id_a]; // should be [b, b, ...] + state.add_leaf(&relay_parent_b, &claim_queue_b); + + // because of the unexpected change in claim queue we lost both claims for paraB and have + // two unclaimed for paraA + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_a, para_id_a]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: false, + } + ]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + } + ]) + ); + } + + #[test] + fn empty_claim_queue() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id_a = ParaId::new(1); + let claim_queue_a = vec![]; + + state.add_leaf(&relay_parent_a, &claim_queue_a); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: None, + claim_queue_len: 0, + claimed: false, + },]) + ); + // no claim queue so we know nothing about future blocks + assert!(state.future_blocks.is_empty()); + + assert!(!state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(!state.claim_at(&relay_parent_a, ¶_id_a)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + + let relay_parent_b = Hash::from_low_u64_be(2); + let claim_queue_b = vec![para_id_a]; + state.add_leaf(&relay_parent_b, &claim_queue_b); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: None, + claim_queue_len: 0, + claimed: false, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false, + }, + ]) + ); + // claim queue with length 1 doesn't say anything about future blocks + assert!(state.future_blocks.is_empty()); + + assert!(!state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(!state.claim_at(&relay_parent_a, ¶_id_a)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + + assert!(state.can_claim_at(&relay_parent_b, ¶_id_a)); + assert_eq!(state.unclaimed_at(&relay_parent_b), vec![para_id_a]); + assert!(state.claim_at(&relay_parent_b, ¶_id_a)); + + let relay_parent_c = Hash::from_low_u64_be(3); + let claim_queue_c = vec![para_id_a, para_id_a]; + state.add_leaf(&relay_parent_c, &claim_queue_c); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: None, + claim_queue_len: 0, + claimed: false, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: true, + }, + ClaimInfo { + hash: Some(relay_parent_c), + claim: Some(para_id_a), + claim_queue_len: 2, + claimed: false, + }, + ]) + ); + // claim queue with length 2 fills only one future block + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false, + },]) + ); + + assert!(!state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(!state.claim_at(&relay_parent_a, ¶_id_a)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + + // already claimed + assert!(!state.can_claim_at(&relay_parent_b, ¶_id_a)); + assert_eq!(state.unclaimed_at(&relay_parent_b), vec![]); + assert!(!state.claim_at(&relay_parent_b, ¶_id_a)); + + assert!(state.can_claim_at(&relay_parent_c, ¶_id_a)); + assert_eq!(state.unclaimed_at(&relay_parent_c), vec![para_id_a, para_id_a]); + } + + #[test] + fn claim_queue_becomes_shorter() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id_a = ParaId::new(1); + let para_id_b = ParaId::new(2); + let claim_queue_a = vec![para_id_a, para_id_b, para_id_a]; + + state.add_leaf(&relay_parent_a, &claim_queue_a); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_a, para_id_b, para_id_a]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: false, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { + hash: None, + claim: Some(para_id_b), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + } + ]) + ); + + let relay_parent_b = Hash::from_low_u64_be(2); + let claim_queue_b = vec![para_id_a, para_id_b]; // should be [b, a] + state.add_leaf(&relay_parent_b, &claim_queue_b); + + assert_eq!(state.unclaimed_at(&relay_parent_b), vec![para_id_a, para_id_b]); + // claims for `relay_parent_a` has changed. + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_a, para_id_a, para_id_b]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: false, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id_a), + claim_queue_len: 2, + claimed: false, + } + ]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ClaimInfo { + hash: None, + claim: Some(para_id_b), + claim_queue_len: 1, + claimed: false + },]) + ); + } + + #[test] + fn claim_queue_becomes_shorter_and_drops_future_claims() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id_a = ParaId::new(1); + let para_id_b = ParaId::new(2); + let claim_queue_a = vec![para_id_a, para_id_b, para_id_a, para_id_b]; + + state.add_leaf(&relay_parent_a, &claim_queue_a); + + assert_eq!( + state.unclaimed_at(&relay_parent_a), + vec![para_id_a, para_id_b, para_id_a, para_id_b] + ); + + // We start with claim queue len 4. + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 4, + claimed: false, + },]) + ); + // we have got three future blocks + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { + hash: None, + claim: Some(para_id_b), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { + hash: None, + claim: Some(para_id_b), + claim_queue_len: 1, + claimed: false + } + ]) + ); + + // The next claim len is 2, so we loose one future block + let relay_parent_b = Hash::from_low_u64_be(2); + let para_id_a = ParaId::new(1); + let para_id_b = ParaId::new(2); + let claim_queue_b = vec![para_id_b, para_id_a]; + state.add_leaf(&relay_parent_b, &claim_queue_b); + + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_a, para_id_b, para_id_a]); + assert_eq!(state.unclaimed_at(&relay_parent_b), vec![para_id_b, para_id_a]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 4, + claimed: false, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id_b), + claim_queue_len: 2, + claimed: false, + } + ]) + ); + + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + },]) + ); + } +} diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index cc0de1cb70f66d22b396e44c3603d224eaf42f9e..625140a73966a764007307da78c730024d75000f 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -18,16 +18,28 @@ //! //! Usually a path of collations is as follows: //! 1. First, collation must be advertised by collator. -//! 2. If the advertisement was accepted, it's queued for fetch (per relay parent). -//! 3. Once it's requested, the collation is said to be Pending. -//! 4. Pending collation becomes Fetched once received, we send it to backing for validation. -//! 5. If it turns to be invalid or async backing allows seconding another candidate, carry on +//! 2. The validator inspects the claim queue and decides if the collation should be fetched +//! based on the entries there. A parachain can't have more fetched collations than the +//! entries in the claim queue at a specific relay parent. When calculating this limit the +//! validator counts all advertisements within its view not just at the relay parent. +//! 3. If the advertisement was accepted, it's queued for fetch (per relay parent). +//! 4. Once it's requested, the collation is said to be pending fetch +//! (`CollationStatus::Fetching`). +//! 5. Pending fetch collation becomes pending validation +//! (`CollationStatus::WaitingOnValidation`) once received, we send it to backing for +//! validation. +//! 6. If it turns to be invalid or async backing allows seconding another candidate, carry on //! with the next advertisement, otherwise we're done with this relay parent. //! -//! ┌──────────────────────────────────────────┠-//! └─▶Advertised ─▶ Pending ─▶ Fetched ─▶ Validated - -use std::{collections::VecDeque, future::Future, pin::Pin, task::Poll}; +//! ┌───────────────────────────────────┠+//! └─▶Waiting ─▶ Fetching ─▶ WaitingOnValidation + +use std::{ + collections::{BTreeMap, VecDeque}, + future::Future, + pin::Pin, + task::Poll, +}; use futures::{future::BoxFuture, FutureExt}; use polkadot_node_network_protocol::{ @@ -36,9 +48,7 @@ use polkadot_node_network_protocol::{ PeerId, }; use polkadot_node_primitives::PoV; -use polkadot_node_subsystem_util::{ - metrics::prometheus::prometheus::HistogramTimer, runtime::ProspectiveParachainsMode, -}; +use polkadot_node_subsystem_util::metrics::prometheus::prometheus::HistogramTimer; use polkadot_primitives::{ vstaging::CandidateReceiptV2 as CandidateReceipt, CandidateHash, CollatorId, Hash, HeadData, Id as ParaId, PersistedValidationData, @@ -187,12 +197,10 @@ pub struct PendingCollationFetch { pub enum CollationStatus { /// We are waiting for a collation to be advertised to us. Waiting, - /// We are currently fetching a collation. - Fetching, + /// We are currently fetching a collation for the specified `ParaId`. + Fetching(ParaId), /// We are waiting that a collation is being validated. WaitingOnValidation, - /// We have seconded a collation. - Seconded, } impl Default for CollationStatus { @@ -202,22 +210,22 @@ impl Default for CollationStatus { } impl CollationStatus { - /// Downgrades to `Waiting`, but only if `self != Seconded`. - fn back_to_waiting(&mut self, relay_parent_mode: ProspectiveParachainsMode) { - match self { - Self::Seconded => - if relay_parent_mode.is_enabled() { - // With async backing enabled it's allowed to - // second more candidates. - *self = Self::Waiting - }, - _ => *self = Self::Waiting, - } + /// Downgrades to `Waiting` + pub fn back_to_waiting(&mut self) { + *self = Self::Waiting } } +/// The number of claims in the claim queue and seconded candidates count for a specific `ParaId`. +#[derive(Default, Debug)] +struct CandidatesStatePerPara { + /// How many collations have been seconded. + pub seconded_per_para: usize, + // Claims in the claim queue for the `ParaId`. + pub claims_per_para: usize, +} + /// Information about collations per relay parent. -#[derive(Default)] pub struct Collations { /// What is the current status in regards to a collation for this relay parent? pub status: CollationStatus, @@ -226,75 +234,89 @@ pub struct Collations { /// This is the currently last started fetch, which did not exceed `MAX_UNSHARED_DOWNLOAD_TIME` /// yet. pub fetching_from: Option<(CollatorId, Option<CandidateHash>)>, - /// Collation that were advertised to us, but we did not yet fetch. - pub waiting_queue: VecDeque<(PendingCollation, CollatorId)>, - /// How many collations have been seconded. - pub seconded_count: usize, + /// Collation that were advertised to us, but we did not yet request or fetch. Grouped by + /// `ParaId`. + waiting_queue: BTreeMap<ParaId, VecDeque<(PendingCollation, CollatorId)>>, + /// Number of seconded candidates and claims in the claim queue per `ParaId`. + candidates_state: BTreeMap<ParaId, CandidatesStatePerPara>, } impl Collations { + pub(super) fn new(group_assignments: &Vec<ParaId>) -> Self { + let mut candidates_state = BTreeMap::<ParaId, CandidatesStatePerPara>::new(); + + for para_id in group_assignments { + candidates_state.entry(*para_id).or_default().claims_per_para += 1; + } + + Self { + status: Default::default(), + fetching_from: None, + waiting_queue: Default::default(), + candidates_state, + } + } + /// Note a seconded collation for a given para. - pub(super) fn note_seconded(&mut self) { - self.seconded_count += 1 + pub(super) fn note_seconded(&mut self, para_id: ParaId) { + self.candidates_state.entry(para_id).or_default().seconded_per_para += 1; + gum::trace!( + target: LOG_TARGET, + ?para_id, + new_count=self.candidates_state.entry(para_id).or_default().seconded_per_para, + "Note seconded." + ); + self.status.back_to_waiting(); } - /// Returns the next collation to fetch from the `waiting_queue`. + /// Adds a new collation to the waiting queue for the relay parent. This function doesn't + /// perform any limits check. The caller should assure that the collation limit is respected. + pub(super) fn add_to_waiting_queue(&mut self, collation: (PendingCollation, CollatorId)) { + self.waiting_queue.entry(collation.0.para_id).or_default().push_back(collation); + } + + /// Picks a collation to fetch from the waiting queue. + /// When fetching collations we need to ensure that each parachain has got a fair core time + /// share depending on its assignments in the claim queue. This means that the number of + /// collations seconded per parachain should ideally be equal to the number of claims for the + /// particular parachain in the claim queue. /// - /// This will reset the status back to `Waiting` using [`CollationStatus::back_to_waiting`]. + /// To achieve this each seconded collation is mapped to an entry from the claim queue. The next + /// fetch is the first unfulfilled entry from the claim queue for which there is an + /// advertisement. /// - /// Returns `Some(_)` if there is any collation to fetch, the `status` is not `Seconded` and - /// the passed in `finished_one` is the currently `waiting_collation`. - pub(super) fn get_next_collation_to_fetch( + /// `unfulfilled_claim_queue_entries` represents all claim queue entries which are still not + /// fulfilled. + pub(super) fn pick_a_collation_to_fetch( &mut self, - finished_one: &(CollatorId, Option<CandidateHash>), - relay_parent_mode: ProspectiveParachainsMode, + unfulfilled_claim_queue_entries: Vec<ParaId>, ) -> Option<(PendingCollation, CollatorId)> { - // If finished one does not match waiting_collation, then we already dequeued another fetch - // to replace it. - if let Some((collator_id, maybe_candidate_hash)) = self.fetching_from.as_ref() { - // If a candidate hash was saved previously, `finished_one` must include this too. - if collator_id != &finished_one.0 && - maybe_candidate_hash.map_or(true, |hash| Some(&hash) != finished_one.1.as_ref()) + gum::trace!( + target: LOG_TARGET, + waiting_queue=?self.waiting_queue, + candidates_state=?self.candidates_state, + "Pick a collation to fetch." + ); + + for assignment in unfulfilled_claim_queue_entries { + // if there is an unfulfilled assignment - return it + if let Some(collation) = self + .waiting_queue + .get_mut(&assignment) + .and_then(|collations| collations.pop_front()) { - gum::trace!( - target: LOG_TARGET, - waiting_collation = ?self.fetching_from, - ?finished_one, - "Not proceeding to the next collation - has already been done." - ); - return None + return Some(collation) } } - self.status.back_to_waiting(relay_parent_mode); - - match self.status { - // We don't need to fetch any other collation when we already have seconded one. - CollationStatus::Seconded => None, - CollationStatus::Waiting => - if self.is_seconded_limit_reached(relay_parent_mode) { - None - } else { - self.waiting_queue.pop_front() - }, - CollationStatus::WaitingOnValidation | CollationStatus::Fetching => - unreachable!("We have reset the status above!"), - } + + None } - /// Checks the limit of seconded candidates. - pub(super) fn is_seconded_limit_reached( - &self, - relay_parent_mode: ProspectiveParachainsMode, - ) -> bool { - let seconded_limit = - if let ProspectiveParachainsMode::Enabled { max_candidate_depth, .. } = - relay_parent_mode - { - max_candidate_depth + 1 - } else { - 1 - }; - self.seconded_count >= seconded_limit + pub(super) fn seconded_for_para(&self, para_id: &ParaId) -> usize { + self.candidates_state + .get(¶_id) + .map(|state| state.seconded_per_para) + .unwrap_or_default() } } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 86358f503d04d58d55fb8ff1f4f2df7bbdd9c4e4..93a8c31168c8980a6159121e9ab6d84fccbfb6d5 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -50,11 +50,11 @@ use polkadot_node_subsystem_util::{ backing_implicit_view::View as ImplicitView, reputation::{ReputationAggregator, REPUTATION_CHANGE_INTERVAL}, request_claim_queue, request_session_index_for_child, - runtime::{prospective_parachains_mode, request_node_features, ProspectiveParachainsMode}, + runtime::request_node_features, }; use polkadot_primitives::{ node_features, - vstaging::{CandidateDescriptorV2, CandidateDescriptorVersion, CoreState}, + vstaging::{CandidateDescriptorV2, CandidateDescriptorVersion}, CandidateHash, CollatorId, CoreIndex, Hash, HeadData, Id as ParaId, OccupiedCoreAssumption, PersistedValidationData, SessionIndex, }; @@ -63,8 +63,11 @@ use crate::error::{Error, FetchError, Result, SecondingError}; use self::collation::BlockedCollationId; +use self::claim_queue_state::ClaimQueueState; + use super::{modify_reputation, tick_stream, LOG_TARGET}; +mod claim_queue_state; mod collation; mod metrics; @@ -163,27 +166,19 @@ impl PeerData { fn update_view( &mut self, implicit_view: &ImplicitView, - active_leaves: &HashMap<Hash, ProspectiveParachainsMode>, - per_relay_parent: &HashMap<Hash, PerRelayParent>, + active_leaves: &HashSet<Hash>, new_view: View, ) { let old_view = std::mem::replace(&mut self.view, new_view); if let PeerState::Collating(ref mut peer_state) = self.state { for removed in old_view.difference(&self.view) { - // Remove relay parent advertisements if it went out - // of our (implicit) view. - let keep = per_relay_parent - .get(removed) - .map(|s| { - is_relay_parent_in_implicit_view( - removed, - s.prospective_parachains_mode, - implicit_view, - active_leaves, - peer_state.para_id, - ) - }) - .unwrap_or(false); + // Remove relay parent advertisements if it went out of our (implicit) view. + let keep = is_relay_parent_in_implicit_view( + removed, + implicit_view, + active_leaves, + peer_state.para_id, + ); if !keep { peer_state.advertisements.remove(&removed); @@ -196,8 +191,7 @@ impl PeerData { fn prune_old_advertisements( &mut self, implicit_view: &ImplicitView, - active_leaves: &HashMap<Hash, ProspectiveParachainsMode>, - per_relay_parent: &HashMap<Hash, PerRelayParent>, + active_leaves: &HashSet<Hash>, ) { if let PeerState::Collating(ref mut peer_state) = self.state { peer_state.advertisements.retain(|hash, _| { @@ -205,36 +199,30 @@ impl PeerData { // - Relay parent is an active leaf // - It belongs to allowed ancestry under some leaf // Discard otherwise. - per_relay_parent.get(hash).map_or(false, |s| { - is_relay_parent_in_implicit_view( - hash, - s.prospective_parachains_mode, - implicit_view, - active_leaves, - peer_state.para_id, - ) - }) + is_relay_parent_in_implicit_view( + hash, + implicit_view, + active_leaves, + peer_state.para_id, + ) }); } } - /// Note an advertisement by the collator. Returns `true` if the advertisement was imported - /// successfully. Fails if the advertisement is duplicate, out of view, or the peer has not - /// declared itself a collator. + /// Performs sanity check for an advertisement and notes it as advertised. fn insert_advertisement( &mut self, on_relay_parent: Hash, - relay_parent_mode: ProspectiveParachainsMode, candidate_hash: Option<CandidateHash>, implicit_view: &ImplicitView, - active_leaves: &HashMap<Hash, ProspectiveParachainsMode>, + active_leaves: &HashSet<Hash>, + per_relay_parent: &PerRelayParent, ) -> std::result::Result<(CollatorId, ParaId), InsertAdvertisementError> { match self.state { PeerState::Connected(_) => Err(InsertAdvertisementError::UndeclaredCollator), PeerState::Collating(ref mut state) => { if !is_relay_parent_in_implicit_view( &on_relay_parent, - relay_parent_mode, implicit_view, active_leaves, state.para_id, @@ -242,53 +230,41 @@ impl PeerData { return Err(InsertAdvertisementError::OutOfOurView) } - match (relay_parent_mode, candidate_hash) { - (ProspectiveParachainsMode::Disabled, candidate_hash) => { - if state.advertisements.contains_key(&on_relay_parent) { - return Err(InsertAdvertisementError::Duplicate) - } - state - .advertisements - .insert(on_relay_parent, HashSet::from_iter(candidate_hash)); - }, - ( - ProspectiveParachainsMode::Enabled { max_candidate_depth, .. }, - candidate_hash, - ) => { - if let Some(candidate_hash) = candidate_hash { - if state - .advertisements - .get(&on_relay_parent) - .map_or(false, |candidates| candidates.contains(&candidate_hash)) - { - return Err(InsertAdvertisementError::Duplicate) - } - - let candidates = - state.advertisements.entry(on_relay_parent).or_default(); - - if candidates.len() > max_candidate_depth { - return Err(InsertAdvertisementError::PeerLimitReached) - } - candidates.insert(candidate_hash); - } else { - if self.version != CollationVersion::V1 { - gum::error!( - target: LOG_TARGET, - "Programming error, `candidate_hash` can not be `None` \ - for non `V1` networking.", - ); - } - - if state.advertisements.contains_key(&on_relay_parent) { - return Err(InsertAdvertisementError::Duplicate) - } - state - .advertisements - .insert(on_relay_parent, HashSet::from_iter(candidate_hash)); - }; - }, - } + if let Some(candidate_hash) = candidate_hash { + if state + .advertisements + .get(&on_relay_parent) + .map_or(false, |candidates| candidates.contains(&candidate_hash)) + { + return Err(InsertAdvertisementError::Duplicate) + } + + let candidates = state.advertisements.entry(on_relay_parent).or_default(); + + // Current assignments is equal to the length of the claim queue. No honest + // collator should send that many advertisements. + if candidates.len() > per_relay_parent.assignment.current.len() { + return Err(InsertAdvertisementError::PeerLimitReached) + } + + candidates.insert(candidate_hash); + } else { + if self.version != CollationVersion::V1 { + gum::error!( + target: LOG_TARGET, + "Programming error, `candidate_hash` can not be `None` \ + for non `V1` networking.", + ); + } + + if state.advertisements.contains_key(&on_relay_parent) { + return Err(InsertAdvertisementError::Duplicate) + } + + state + .advertisements + .insert(on_relay_parent, HashSet::from_iter(candidate_hash)); + }; state.last_active = Instant::now(); Ok((state.collator_id.clone(), state.para_id)) @@ -369,7 +345,6 @@ struct GroupAssignments { } struct PerRelayParent { - prospective_parachains_mode: ProspectiveParachainsMode, assignment: GroupAssignments, collations: Collations, v2_receipts: bool, @@ -390,11 +365,10 @@ struct State { /// ancestry of some active leaf, then it does support prospective parachains. implicit_view: ImplicitView, - /// All active leaves observed by us, including both that do and do not - /// support prospective parachains. This mapping works as a replacement for + /// All active leaves observed by us. This works as a replacement for /// [`polkadot_node_network_protocol::View`] and can be dropped once the transition /// to asynchronous backing is done. - active_leaves: HashMap<Hash, ProspectiveParachainsMode>, + active_leaves: HashSet<Hash>, /// State tracked per relay parent. per_relay_parent: HashMap<Hash, PerRelayParent>, @@ -437,23 +411,69 @@ struct State { reputation: ReputationAggregator, } +impl State { + // Returns the number of seconded and pending collations for a specific `ParaId`. Pending + // collations are: + // 1. Collations being fetched from a collator. + // 2. Collations waiting for validation from backing subsystem. + // 3. Collations blocked from seconding due to parent not being known by backing subsystem. + fn seconded_and_pending_for_para(&self, relay_parent: &Hash, para_id: &ParaId) -> usize { + let seconded = self + .per_relay_parent + .get(relay_parent) + .map_or(0, |per_relay_parent| per_relay_parent.collations.seconded_for_para(para_id)); + + let pending_fetch = self.per_relay_parent.get(relay_parent).map_or(0, |rp_state| { + match rp_state.collations.status { + CollationStatus::Fetching(pending_para_id) if pending_para_id == *para_id => 1, + _ => 0, + } + }); + + let waiting_for_validation = self + .fetched_candidates + .keys() + .filter(|fc| fc.relay_parent == *relay_parent && fc.para_id == *para_id) + .count(); + + let blocked_from_seconding = + self.blocked_from_seconding.values().fold(0, |acc, blocked_collations| { + acc + blocked_collations + .iter() + .filter(|pc| { + pc.candidate_receipt.descriptor.para_id() == *para_id && + pc.candidate_receipt.descriptor.relay_parent() == *relay_parent + }) + .count() + }); + + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?para_id, + seconded, + pending_fetch, + waiting_for_validation, + blocked_from_seconding, + "Seconded and pending collations for para", + ); + + seconded + pending_fetch + waiting_for_validation + blocked_from_seconding + } +} + fn is_relay_parent_in_implicit_view( relay_parent: &Hash, - relay_parent_mode: ProspectiveParachainsMode, implicit_view: &ImplicitView, - active_leaves: &HashMap<Hash, ProspectiveParachainsMode>, + active_leaves: &HashSet<Hash>, para_id: ParaId, ) -> bool { - match relay_parent_mode { - ProspectiveParachainsMode::Disabled => active_leaves.contains_key(relay_parent), - ProspectiveParachainsMode::Enabled { .. } => active_leaves.iter().any(|(hash, mode)| { - mode.is_enabled() && - implicit_view - .known_allowed_relay_parents_under(hash, Some(para_id)) - .unwrap_or_default() - .contains(relay_parent) - }), - } + active_leaves.iter().any(|hash| { + implicit_view + .known_allowed_relay_parents_under(hash, Some(para_id)) + .unwrap_or_default() + .contains(relay_parent) + }) } async fn construct_per_relay_parent<Sender>( @@ -461,7 +481,6 @@ async fn construct_per_relay_parent<Sender>( current_assignments: &mut HashMap<ParaId, usize>, keystore: &KeystorePtr, relay_parent: Hash, - relay_parent_mode: ProspectiveParachainsMode, v2_receipts: bool, session_index: SessionIndex, ) -> Result<Option<PerRelayParent>> @@ -479,39 +498,24 @@ where .await .map_err(Error::CancelledValidatorGroups)??; - let cores = polkadot_node_subsystem_util::request_availability_cores(relay_parent, sender) - .await - .await - .map_err(Error::CancelledAvailabilityCores)??; - let core_now = if let Some(group) = polkadot_node_subsystem_util::signing_key_and_index(&validators, keystore).and_then( |(_, index)| polkadot_node_subsystem_util::find_validator_group(&groups, index), ) { - rotation_info.core_for_group(group, cores.len()) + rotation_info.core_for_group(group, groups.len()) } else { gum::trace!(target: LOG_TARGET, ?relay_parent, "Not a validator"); return Ok(None) }; - let claim_queue = request_claim_queue(relay_parent, sender) + let mut claim_queue = request_claim_queue(relay_parent, sender) .await .await .map_err(Error::CancelledClaimQueue)??; - let paras_now = cores - .get(core_now.0 as usize) - .and_then(|c| match (c, relay_parent_mode) { - (CoreState::Occupied(_), ProspectiveParachainsMode::Disabled) => None, - ( - CoreState::Occupied(_), - ProspectiveParachainsMode::Enabled { max_candidate_depth: 0, .. }, - ) => None, - _ => claim_queue.get(&core_now).cloned(), - }) - .unwrap_or_else(|| VecDeque::new()); - - for para_id in paras_now.iter() { + let assigned_paras = claim_queue.remove(&core_now).unwrap_or_else(|| VecDeque::new()); + + for para_id in assigned_paras.iter() { let entry = current_assignments.entry(*para_id).or_default(); *entry += 1; if *entry == 1 { @@ -524,10 +528,12 @@ where } } + let assignment = GroupAssignments { current: assigned_paras.into_iter().collect() }; + let collations = Collations::new(&assignment.current); + Ok(Some(PerRelayParent { - prospective_parachains_mode: relay_parent_mode, - assignment: GroupAssignments { current: paras_now.into_iter().collect() }, - collations: Collations::default(), + assignment, + collations, v2_receipts, session_index, current_core: core_now, @@ -655,12 +661,7 @@ fn handle_peer_view_change(state: &mut State, peer_id: PeerId, view: View) { None => return, }; - peer_data.update_view( - &state.implicit_view, - &state.active_leaves, - &state.per_relay_parent, - view, - ); + peer_data.update_view(&state.implicit_view, &state.active_leaves, view); state.collation_requests_cancel_handles.retain(|pc, handle| { let keep = pc.peer_id != peer_id || peer_data.has_advertised(&pc.relay_parent, None); if !keep { @@ -693,7 +694,6 @@ async fn request_collation( .get_mut(&relay_parent) .ok_or(FetchError::RelayParentOutOfView)?; - // Relay parent mode is checked in `handle_advertisement`. let (requests, response_recv) = match (peer_protocol_version, prospective_candidate) { (CollationVersion::V1, _) => { let (req, response_recv) = OutgoingRequest::new( @@ -739,7 +739,7 @@ async fn request_collation( let maybe_candidate_hash = prospective_candidate.as_ref().map(ProspectiveCandidate::candidate_hash); - per_relay_parent.collations.status = CollationStatus::Fetching; + per_relay_parent.collations.status = CollationStatus::Fetching(para_id); per_relay_parent .collations .fetching_from @@ -1050,6 +1050,62 @@ async fn second_unblocked_collations<Context>( } } +fn ensure_seconding_limit_is_respected( + relay_parent: &Hash, + para_id: ParaId, + state: &State, +) -> std::result::Result<(), AdvertisementError> { + let paths = state.implicit_view.paths_via_relay_parent(relay_parent); + + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?para_id, + ?paths, + "Checking seconding limit", + ); + + let mut has_claim_at_some_path = false; + for path in paths { + let mut cq_state = ClaimQueueState::new(); + for ancestor in &path { + let seconded_and_pending = state.seconded_and_pending_for_para(&ancestor, ¶_id); + cq_state.add_leaf( + &ancestor, + &state + .per_relay_parent + .get(ancestor) + .ok_or(AdvertisementError::RelayParentUnknown)? + .assignment + .current, + ); + for _ in 0..seconded_and_pending { + cq_state.claim_at(ancestor, ¶_id); + } + } + + if cq_state.can_claim_at(relay_parent, ¶_id) { + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?para_id, + ?path, + "Seconding limit respected at path", + ); + has_claim_at_some_path = true; + break + } + } + + // If there is a place in the claim queue for the candidate at at least one path we will accept + // it. + if has_claim_at_some_path { + Ok(()) + } else { + Err(AdvertisementError::SecondedLimitReached) + } +} + async fn handle_advertisement<Sender>( sender: &mut Sender, state: &mut State, @@ -1062,8 +1118,7 @@ where { let peer_data = state.peer_data.get_mut(&peer_id).ok_or(AdvertisementError::UnknownPeer)?; - if peer_data.version == CollationVersion::V1 && !state.active_leaves.contains_key(&relay_parent) - { + if peer_data.version == CollationVersion::V1 && !state.active_leaves.contains(&relay_parent) { return Err(AdvertisementError::ProtocolMisuse) } @@ -1072,7 +1127,6 @@ where .get(&relay_parent) .ok_or(AdvertisementError::RelayParentUnknown)?; - let relay_parent_mode = per_relay_parent.prospective_parachains_mode; let assignment = &per_relay_parent.assignment; let collator_para_id = @@ -1088,32 +1142,29 @@ where let (collator_id, para_id) = peer_data .insert_advertisement( relay_parent, - relay_parent_mode, candidate_hash, &state.implicit_view, &state.active_leaves, + &per_relay_parent, ) .map_err(AdvertisementError::Invalid)?; - if per_relay_parent.collations.is_seconded_limit_reached(relay_parent_mode) { - return Err(AdvertisementError::SecondedLimitReached) - } + ensure_seconding_limit_is_respected(&relay_parent, para_id, state)?; if let Some((candidate_hash, parent_head_data_hash)) = prospective_candidate { // Check if backing subsystem allows to second this candidate. // // This is also only important when async backing or elastic scaling is enabled. - let seconding_not_allowed = relay_parent_mode.is_enabled() && - !can_second( - sender, - collator_para_id, - relay_parent, - candidate_hash, - parent_head_data_hash, - ) - .await; + let can_second = can_second( + sender, + collator_para_id, + relay_parent, + candidate_hash, + parent_head_data_hash, + ) + .await; - if seconding_not_allowed { + if !can_second { return Err(AdvertisementError::BlockedByBacking) } } @@ -1143,8 +1194,8 @@ where Ok(()) } -/// Enqueue collation for fetching. The advertisement is expected to be -/// validated. +/// Enqueue collation for fetching. The advertisement is expected to be validated and the seconding +/// limit checked. async fn enqueue_collation<Sender>( sender: &mut Sender, state: &mut State, @@ -1179,7 +1230,6 @@ where return Ok(()) }, }; - let relay_parent_mode = per_relay_parent.prospective_parachains_mode; let prospective_candidate = prospective_candidate.map(|(candidate_hash, parent_head_data_hash)| ProspectiveCandidate { candidate_hash, @@ -1187,22 +1237,11 @@ where }); let collations = &mut per_relay_parent.collations; - if collations.is_seconded_limit_reached(relay_parent_mode) { - gum::trace!( - target: LOG_TARGET, - peer_id = ?peer_id, - %para_id, - ?relay_parent, - "Limit of seconded collations reached for valid advertisement", - ); - return Ok(()) - } - let pending_collation = PendingCollation::new(relay_parent, para_id, &peer_id, prospective_candidate); match collations.status { - CollationStatus::Fetching | CollationStatus::WaitingOnValidation => { + CollationStatus::Fetching(_) | CollationStatus::WaitingOnValidation => { gum::trace!( target: LOG_TARGET, peer_id = ?peer_id, @@ -1210,26 +1249,13 @@ where ?relay_parent, "Added collation to the pending list" ); - collations.waiting_queue.push_back((pending_collation, collator_id)); + collations.add_to_waiting_queue((pending_collation, collator_id)); }, CollationStatus::Waiting => { + // We were waiting for a collation to be advertised to us (we were idle) so we can fetch + // the new collation immediately fetch_collation(sender, state, pending_collation, collator_id).await?; }, - CollationStatus::Seconded if relay_parent_mode.is_enabled() => { - // Limit is not reached, it's allowed to second another - // collation. - fetch_collation(sender, state, pending_collation, collator_id).await?; - }, - CollationStatus::Seconded => { - gum::trace!( - target: LOG_TARGET, - peer_id = ?peer_id, - %para_id, - ?relay_parent, - ?relay_parent_mode, - "A collation has already been seconded", - ); - }, } Ok(()) @@ -1247,15 +1273,15 @@ where { let current_leaves = state.active_leaves.clone(); - let removed = current_leaves.iter().filter(|(h, _)| !view.contains(h)); - let added = view.iter().filter(|h| !current_leaves.contains_key(h)); + let removed = current_leaves.iter().filter(|h| !view.contains(h)); + let added = view.iter().filter(|h| !current_leaves.contains(h)); for leaf in added { let session_index = request_session_index_for_child(*leaf, sender) .await .await .map_err(Error::CancelledSessionIndex)??; - let mode = prospective_parachains_mode(sender, *leaf).await?; + let v2_receipts = request_node_features(*leaf, session_index, sender) .await? .unwrap_or_default() @@ -1268,7 +1294,6 @@ where &mut state.current_assignments, keystore, *leaf, - mode, v2_receipts, session_index, ) @@ -1277,53 +1302,53 @@ where continue }; - state.active_leaves.insert(*leaf, mode); + state.active_leaves.insert(*leaf); state.per_relay_parent.insert(*leaf, per_relay_parent); - if mode.is_enabled() { - state - .implicit_view - .activate_leaf(sender, *leaf) - .await - .map_err(Error::ImplicitViewFetchError)?; - - // Order is always descending. - let allowed_ancestry = state - .implicit_view - .known_allowed_relay_parents_under(leaf, None) - .unwrap_or_default(); - for block_hash in allowed_ancestry { - if let Entry::Vacant(entry) = state.per_relay_parent.entry(*block_hash) { - // Safe to use the same v2 receipts config for the allowed relay parents as well - // as the same session index since they must be in the same session. - if let Some(per_relay_parent) = construct_per_relay_parent( - sender, - &mut state.current_assignments, - keystore, - *block_hash, - mode, - v2_receipts, - session_index, - ) - .await? - { - entry.insert(per_relay_parent); - } + state + .implicit_view + .activate_leaf(sender, *leaf) + .await + .map_err(Error::ImplicitViewFetchError)?; + + // Order is always descending. + let allowed_ancestry = state + .implicit_view + .known_allowed_relay_parents_under(leaf, None) + .unwrap_or_default(); + for block_hash in allowed_ancestry { + if let Entry::Vacant(entry) = state.per_relay_parent.entry(*block_hash) { + // Safe to use the same v2 receipts config for the allowed relay parents as well + // as the same session index since they must be in the same session. + if let Some(per_relay_parent) = construct_per_relay_parent( + sender, + &mut state.current_assignments, + keystore, + *block_hash, + v2_receipts, + session_index, + ) + .await? + { + entry.insert(per_relay_parent); } } } } - for (removed, mode) in removed { + for removed in removed { + gum::trace!( + target: LOG_TARGET, + ?view, + ?removed, + "handle_our_view_change - removed", + ); + state.active_leaves.remove(removed); // If the leaf is deactivated it still may stay in the view as a part // of implicit ancestry. Only update the state after the hash is actually // pruned from the block info storage. - let pruned = if mode.is_enabled() { - state.implicit_view.deactivate_leaf(*removed) - } else { - vec![*removed] - }; + let pruned = state.implicit_view.deactivate_leaf(*removed); for removed in pruned { if let Some(per_relay_parent) = state.per_relay_parent.remove(&removed) { @@ -1353,11 +1378,7 @@ where }); for (peer_id, peer_data) in state.peer_data.iter_mut() { - peer_data.prune_old_advertisements( - &state.implicit_view, - &state.active_leaves, - &state.per_relay_parent, - ); + peer_data.prune_old_advertisements(&state.implicit_view, &state.active_leaves); // Disconnect peers who are not relevant to our current or next para. // @@ -1462,9 +1483,6 @@ async fn process_msg<Context>( "DistributeCollation message is not expected on the validator side of the protocol", ); }, - ReportCollator(id) => { - report_collator(&mut state.reputation, ctx.sender(), &state.peer_data, id).await; - }, NetworkBridgeUpdate(event) => { if let Err(e) = handle_network_msg(ctx, state, keystore, event).await { gum::warn!( @@ -1493,8 +1511,9 @@ async fn process_msg<Context>( if let Some(CollationEvent { collator_id, pending_collation, .. }) = state.fetched_candidates.remove(&fetched_collation) { - let PendingCollation { relay_parent, peer_id, prospective_candidate, .. } = - pending_collation; + let PendingCollation { + relay_parent, peer_id, prospective_candidate, para_id, .. + } = pending_collation; note_good_collation( &mut state.reputation, ctx.sender(), @@ -1514,8 +1533,7 @@ async fn process_msg<Context>( } if let Some(rp_state) = state.per_relay_parent.get_mut(&parent) { - rp_state.collations.status = CollationStatus::Seconded; - rp_state.collations.note_seconded(); + rp_state.collations.note_seconded(para_id); } // See if we've unblocked other collations for seconding. @@ -1644,6 +1662,7 @@ async fn run_inner<Context>( disconnect_inactive_peers(ctx.sender(), &eviction_policy, &state.peer_data).await; }, resp = state.collation_requests.select_next_some() => { + let relay_parent = resp.0.pending_collation.relay_parent; let res = match handle_collation_fetch_response( &mut state, resp, @@ -1652,9 +1671,17 @@ async fn run_inner<Context>( ).await { Err(Some((peer_id, rep))) => { modify_reputation(&mut state.reputation, ctx.sender(), peer_id, rep).await; + // Reset the status for the relay parent + state.per_relay_parent.get_mut(&relay_parent).map(|rp| { + rp.collations.status.back_to_waiting(); + }); continue }, Err(None) => { + // Reset the status for the relay parent + state.per_relay_parent.get_mut(&relay_parent).map(|rp| { + rp.collations.status.back_to_waiting(); + }); continue }, Ok(res) => res @@ -1733,11 +1760,7 @@ async fn dequeue_next_collation_and_fetch<Context>( // The collator we tried to fetch from last, optionally which candidate. previous_fetch: (CollatorId, Option<CandidateHash>), ) { - while let Some((next, id)) = state.per_relay_parent.get_mut(&relay_parent).and_then(|state| { - state - .collations - .get_next_collation_to_fetch(&previous_fetch, state.prospective_parachains_mode) - }) { + while let Some((next, id)) = get_next_collation_to_fetch(&previous_fetch, relay_parent, state) { gum::debug!( target: LOG_TARGET, ?relay_parent, @@ -1846,9 +1869,7 @@ async fn kick_off_seconding<Context>( collation_event.collator_protocol_version, collation_event.pending_collation.prospective_candidate, ) { - (CollationVersion::V2, Some(ProspectiveCandidate { parent_head_data_hash, .. })) - if per_relay_parent.prospective_parachains_mode.is_enabled() => - { + (CollationVersion::V2, Some(ProspectiveCandidate { parent_head_data_hash, .. })) => { let pvd = request_prospective_validation_data( ctx.sender(), relay_parent, @@ -1860,8 +1881,7 @@ async fn kick_off_seconding<Context>( (pvd, maybe_parent_head_data, Some(parent_head_data_hash)) }, - // Support V2 collators without async backing enabled. - (CollationVersion::V2, Some(_)) | (CollationVersion::V1, _) => { + (CollationVersion::V1, _) => { let pvd = request_persisted_validation_data( ctx.sender(), candidate_receipt.descriptor().relay_parent(), @@ -2110,6 +2130,106 @@ async fn handle_collation_fetch_response( result } +// Returns the claim queue without fetched or pending advertisement. The resulting `Vec` keeps the +// order in the claim queue so the earlier an element is located in the `Vec` the higher its +// priority is. +fn unfulfilled_claim_queue_entries(relay_parent: &Hash, state: &State) -> Result<Vec<ParaId>> { + let relay_parent_state = state + .per_relay_parent + .get(relay_parent) + .ok_or(Error::RelayParentStateNotFound)?; + let scheduled_paras = relay_parent_state.assignment.current.iter().collect::<HashSet<_>>(); + let paths = state.implicit_view.paths_via_relay_parent(relay_parent); + + let mut claim_queue_states = Vec::new(); + for path in paths { + let mut cq_state = ClaimQueueState::new(); + for ancestor in &path { + cq_state.add_leaf( + &ancestor, + &state + .per_relay_parent + .get(&ancestor) + .ok_or(Error::RelayParentStateNotFound)? + .assignment + .current, + ); + + for para_id in &scheduled_paras { + let seconded_and_pending = state.seconded_and_pending_for_para(&ancestor, ¶_id); + for _ in 0..seconded_and_pending { + cq_state.claim_at(&ancestor, ¶_id); + } + } + } + claim_queue_states.push(cq_state); + } + + // From the claim queue state for each leaf we have to return a combined single one. Go for a + // simple solution and return the longest one. In theory we always prefer the earliest entries + // in the claim queue so there is a good chance that the longest path is the one with + // unsatisfied entries in the beginning. This is not guaranteed as we might have fetched 2nd or + // 3rd spot from the claim queue but it should be good enough. + let unfulfilled_entries = claim_queue_states + .iter_mut() + .map(|cq| cq.unclaimed_at(relay_parent)) + .max_by(|a, b| a.len().cmp(&b.len())) + .unwrap_or_default(); + + Ok(unfulfilled_entries) +} + +/// Returns the next collation to fetch from the `waiting_queue` and reset the status back to +/// `Waiting`. +fn get_next_collation_to_fetch( + finished_one: &(CollatorId, Option<CandidateHash>), + relay_parent: Hash, + state: &mut State, +) -> Option<(PendingCollation, CollatorId)> { + let unfulfilled_entries = match unfulfilled_claim_queue_entries(&relay_parent, &state) { + Ok(entries) => entries, + Err(err) => { + gum::error!( + target: LOG_TARGET, + ?relay_parent, + ?err, + "Failed to get unfulfilled claim queue entries" + ); + return None + }, + }; + let rp_state = match state.per_relay_parent.get_mut(&relay_parent) { + Some(rp_state) => rp_state, + None => { + gum::error!( + target: LOG_TARGET, + ?relay_parent, + "Failed to get relay parent state" + ); + return None + }, + }; + + // If finished one does not match waiting_collation, then we already dequeued another fetch + // to replace it. + if let Some((collator_id, maybe_candidate_hash)) = rp_state.collations.fetching_from.as_ref() { + // If a candidate hash was saved previously, `finished_one` must include this too. + if collator_id != &finished_one.0 && + maybe_candidate_hash.map_or(true, |hash| Some(&hash) != finished_one.1.as_ref()) + { + gum::trace!( + target: LOG_TARGET, + waiting_collation = ?rp_state.collations.fetching_from, + ?finished_one, + "Not proceeding to the next collation - has already been done." + ); + return None + } + } + rp_state.collations.status.back_to_waiting(); + rp_state.collations.pick_a_collation_to_fetch(unfulfilled_entries) +} + // Sanity check the candidate descriptor version. fn descriptor_version_sanity_check( descriptor: &CandidateDescriptorV2, diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 7bc61dd4ebec5de5ba21c2feb60284cd74fdbdad..308aec578dee0ddc26f664f747ebf58ae5080a1c 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -28,28 +28,24 @@ use std::{ time::Duration, }; +use self::prospective_parachains::update_view; use polkadot_node_network_protocol::{ - our_view, peer_set::CollationVersion, request_response::{Requests, ResponseSender}, ObservedRole, }; use polkadot_node_primitives::{BlockData, PoV}; -use polkadot_node_subsystem::{ - errors::RuntimeApiError, - messages::{AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest}, +use polkadot_node_subsystem::messages::{ + AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest, }; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::{reputation::add_reputation, TimeoutExt}; use polkadot_primitives::{ - node_features, - vstaging::{CandidateReceiptV2 as CandidateReceipt, CoreState, OccupiedCore}, - CollatorPair, CoreIndex, GroupIndex, GroupRotationInfo, HeadData, NodeFeatures, - PersistedValidationData, ScheduledCore, ValidatorId, ValidatorIndex, -}; -use polkadot_primitives_test_helpers::{ - dummy_candidate_descriptor, dummy_candidate_receipt_bad_sig, dummy_hash, + node_features, vstaging::CandidateReceiptV2 as CandidateReceipt, CollatorPair, CoreIndex, + GroupRotationInfo, HeadData, NodeFeatures, PersistedValidationData, ValidatorId, + ValidatorIndex, }; +use polkadot_primitives_test_helpers::{dummy_candidate_receipt_bad_sig, dummy_hash}; mod prospective_parachains; @@ -57,9 +53,6 @@ const ACTIVITY_TIMEOUT: Duration = Duration::from_millis(500); const DECLARE_TIMEOUT: Duration = Duration::from_millis(25); const REPUTATION_CHANGE_TEST_INTERVAL: Duration = Duration::from_millis(10); -const ASYNC_BACKING_DISABLED_ERROR: RuntimeApiError = - RuntimeApiError::NotSupported { runtime_api_name: "test-runtime" }; - fn dummy_pvd() -> PersistedValidationData { PersistedValidationData { parent_head: HeadData(vec![7, 8, 9]), @@ -77,19 +70,17 @@ struct TestState { validator_public: Vec<ValidatorId>, validator_groups: Vec<Vec<ValidatorIndex>>, group_rotation_info: GroupRotationInfo, - cores: Vec<CoreState>, claim_queue: BTreeMap<CoreIndex, VecDeque<ParaId>>, + scheduling_lookahead: u32, node_features: NodeFeatures, session_index: SessionIndex, + // Used by `update_view` to keep track of latest requested ancestor + last_known_block: Option<u32>, } impl Default for TestState { fn default() -> Self { - let chain_a = ParaId::from(1); - let chain_b = ParaId::from(2); - - let chain_ids = vec![chain_a, chain_b]; - let relay_parent = Hash::repeat_byte(0x05); + let relay_parent = Hash::from_low_u64_be(0x05); let collators = iter::repeat(()).map(|_| CollatorPair::generate().0).take(5).collect(); let validators = vec![ @@ -110,50 +101,100 @@ impl Default for TestState { let group_rotation_info = GroupRotationInfo { session_start_block: 0, group_rotation_frequency: 1, now: 0 }; - let cores = vec![ - CoreState::Scheduled(ScheduledCore { para_id: chain_ids[0], collator: None }), - CoreState::Free, - CoreState::Occupied(OccupiedCore { - next_up_on_available: Some(ScheduledCore { para_id: chain_ids[1], collator: None }), - occupied_since: 0, - time_out_at: 1, - next_up_on_time_out: None, - availability: Default::default(), - group_responsible: GroupIndex(0), - candidate_hash: Default::default(), - candidate_descriptor: { - let mut d = dummy_candidate_descriptor(dummy_hash()); - d.para_id = chain_ids[1]; - - d.into() - }, - }), - ]; - + let scheduling_lookahead = 3; let mut claim_queue = BTreeMap::new(); - claim_queue.insert(CoreIndex(0), [chain_ids[0]].into_iter().collect()); + claim_queue.insert( + CoreIndex(0), + iter::repeat(ParaId::from(Self::CHAIN_IDS[0])) + .take(scheduling_lookahead as usize) + .collect(), + ); claim_queue.insert(CoreIndex(1), VecDeque::new()); - claim_queue.insert(CoreIndex(2), [chain_ids[1]].into_iter().collect()); + claim_queue.insert( + CoreIndex(2), + iter::repeat(ParaId::from(Self::CHAIN_IDS[1])) + .take(scheduling_lookahead as usize) + .collect(), + ); let mut node_features = NodeFeatures::EMPTY; node_features.resize(node_features::FeatureIndex::CandidateReceiptV2 as usize + 1, false); node_features.set(node_features::FeatureIndex::CandidateReceiptV2 as u8 as usize, true); Self { - chain_ids, + chain_ids: Self::CHAIN_IDS.map(|id| ParaId::from(id)).to_vec(), relay_parent, collators, validator_public, validator_groups, group_rotation_info, - cores, claim_queue, + scheduling_lookahead, node_features, session_index: 1, + last_known_block: None, } } } +impl TestState { + const CHAIN_IDS: [u32; 2] = [1, 2]; + + fn with_shared_core() -> Self { + let mut state = Self::default(); + + let mut claim_queue = BTreeMap::new(); + claim_queue.insert( + CoreIndex(0), + VecDeque::from_iter( + [ + ParaId::from(Self::CHAIN_IDS[1]), + ParaId::from(Self::CHAIN_IDS[0]), + ParaId::from(Self::CHAIN_IDS[0]), + ] + .into_iter(), + ), + ); + state.validator_groups.truncate(1); + + assert!( + claim_queue.get(&CoreIndex(0)).unwrap().len() == state.scheduling_lookahead as usize + ); + + state.claim_queue = claim_queue; + + state + } + + fn with_one_scheduled_para() -> Self { + let mut state = Self::default(); + + let validator_groups = vec![vec![ValidatorIndex(0), ValidatorIndex(1)]]; + + let mut claim_queue = BTreeMap::new(); + claim_queue.insert( + CoreIndex(0), + VecDeque::from_iter( + [ + ParaId::from(Self::CHAIN_IDS[0]), + ParaId::from(Self::CHAIN_IDS[0]), + ParaId::from(Self::CHAIN_IDS[0]), + ] + .into_iter(), + ), + ); + + assert!( + claim_queue.get(&CoreIndex(0)).unwrap().len() == state.scheduling_lookahead as usize + ); + + state.validator_groups = validator_groups; + state.claim_queue = claim_queue; + + state + } +} + type VirtualOverseer = polkadot_node_subsystem_test_helpers::TestSubsystemContextHandle<CollatorProtocolMessage>; @@ -246,91 +287,6 @@ async fn overseer_signal(overseer: &mut VirtualOverseer, signal: OverseerSignal) .expect(&format!("{:?} is more than enough for sending signals.", TIMEOUT)); } -async fn respond_to_runtime_api_queries( - virtual_overseer: &mut VirtualOverseer, - test_state: &TestState, - hash: Hash, -) { - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::SessionIndexForChild(tx) - )) => { - assert_eq!(rp, hash); - tx.send(Ok(test_state.session_index)).unwrap(); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::AsyncBackingParams(tx) - )) => { - assert_eq!(rp, hash); - tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)).unwrap(); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::NodeFeatures(_, tx) - )) => { - assert_eq!(rp, hash); - tx.send(Ok(test_state.node_features.clone())).unwrap(); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::Validators(tx), - )) => { - let _ = tx.send(Ok(test_state.validator_public.clone())); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::ValidatorGroups(tx), - )) => { - assert_eq!(rp, hash); - let _ = tx.send(Ok(( - test_state.validator_groups.clone(), - test_state.group_rotation_info.clone(), - ))); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::AvailabilityCores(tx), - )) => { - assert_eq!(rp, hash); - let _ = tx.send(Ok(test_state.cores.clone())); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::ClaimQueue(tx), - )) => { - assert_eq!(rp, hash); - let _ = tx.send(Ok(test_state.claim_queue.clone())); - } - ); -} - /// Assert that the next message is a `CandidateBacking(Second())`. async fn assert_candidate_backing_second( virtual_overseer: &mut VirtualOverseer, @@ -506,198 +462,6 @@ async fn advertise_collation( .await; } -// As we receive a relevant advertisement act on it and issue a collation request. -#[test] -fn act_on_advertisement() { - let test_state = TestState::default(); - - test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { - let TestHarness { mut virtual_overseer, .. } = test_harness; - - let pair = CollatorPair::generate().0; - gum::trace!("activating"); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; - - let peer_b = PeerId::random(); - - connect_and_declare_collator( - &mut virtual_overseer, - peer_b, - pair.clone(), - test_state.chain_ids[0], - CollationVersion::V1, - ) - .await; - - advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent, None).await; - - assert_fetch_collation_request( - &mut virtual_overseer, - test_state.relay_parent, - test_state.chain_ids[0], - None, - ) - .await; - - virtual_overseer - }); -} - -/// Tests that validator side works with v2 network protocol -/// before async backing is enabled. -#[test] -fn act_on_advertisement_v2() { - let test_state = TestState::default(); - - test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { - let TestHarness { mut virtual_overseer, .. } = test_harness; - - let pair = CollatorPair::generate().0; - gum::trace!("activating"); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; - - let peer_b = PeerId::random(); - - connect_and_declare_collator( - &mut virtual_overseer, - peer_b, - pair.clone(), - test_state.chain_ids[0], - CollationVersion::V2, - ) - .await; - - let pov = PoV { block_data: BlockData(vec![]) }; - let mut candidate_a = - dummy_candidate_receipt_bad_sig(dummy_hash(), Some(Default::default())); - candidate_a.descriptor.para_id = test_state.chain_ids[0]; - candidate_a.descriptor.relay_parent = test_state.relay_parent; - candidate_a.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); - - let candidate_hash = candidate_a.hash(); - let parent_head_data_hash = Hash::zero(); - // v2 advertisement. - advertise_collation( - &mut virtual_overseer, - peer_b, - test_state.relay_parent, - Some((candidate_hash, parent_head_data_hash)), - ) - .await; - - let response_channel = assert_fetch_collation_request( - &mut virtual_overseer, - test_state.relay_parent, - test_state.chain_ids[0], - Some(candidate_hash), - ) - .await; - - response_channel - .send(Ok(( - request_v1::CollationFetchingResponse::Collation( - candidate_a.clone().into(), - pov.clone(), - ) - .encode(), - ProtocolName::from(""), - ))) - .expect("Sending response should succeed"); - - assert_candidate_backing_second( - &mut virtual_overseer, - test_state.relay_parent, - test_state.chain_ids[0], - &pov, - // Async backing isn't enabled and thus it should do it the old way. - CollationVersion::V1, - ) - .await; - - virtual_overseer - }); -} - -// Test that other subsystems may modify collators' reputations. -#[test] -fn collator_reporting_works() { - let test_state = TestState::default(); - - test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { - let TestHarness { mut virtual_overseer, .. } = test_harness; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; - - let peer_b = PeerId::random(); - let peer_c = PeerId::random(); - - connect_and_declare_collator( - &mut virtual_overseer, - peer_b, - test_state.collators[0].clone(), - test_state.chain_ids[0], - CollationVersion::V1, - ) - .await; - - connect_and_declare_collator( - &mut virtual_overseer, - peer_c, - test_state.collators[1].clone(), - test_state.chain_ids[0], - CollationVersion::V1, - ) - .await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::ReportCollator(test_state.collators[0].public()), - ) - .await; - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(peer, rep)), - ) => { - assert_eq!(peer, peer_b); - assert_eq!(rep.value, COST_REPORT_BAD.cost_or_benefit()); - } - ); - - virtual_overseer - }); -} - // Test that we verify the signatures on `Declare` and `AdvertiseCollation` messages. #[test] fn collator_authentication_verification_works() { @@ -747,31 +511,18 @@ fn collator_authentication_verification_works() { }); } -/// Tests that a validator fetches only one collation at any moment of time -/// per relay parent and ignores other advertisements once a candidate gets -/// seconded. +/// Tests that on a V1 Advertisement a validator fetches only one collation at any moment of time +/// per relay parent and ignores other V1 advertisements once a candidate gets seconded. #[test] -fn fetch_one_collation_at_a_time() { - let test_state = TestState::default(); +fn fetch_one_collation_at_a_time_for_v1_advertisement() { + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - let second = Hash::random(); - - let our_view = our_view![test_state.relay_parent, second]; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view.clone(), - )), - ) - .await; - - // Iter over view since the order may change due to sorted invariant. - for hash in our_view.iter() { - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, *hash).await; - } + let second = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0), (second, 1)]) + .await; let peer_b = PeerId::random(); let peer_c = PeerId::random(); @@ -794,8 +545,8 @@ fn fetch_one_collation_at_a_time() { ) .await; - advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent, None).await; - advertise_collation(&mut virtual_overseer, peer_c, test_state.relay_parent, None).await; + advertise_collation(&mut virtual_overseer, peer_b, relay_parent, None).await; + advertise_collation(&mut virtual_overseer, peer_c, relay_parent, None).await; let response_channel = assert_fetch_collation_request( &mut virtual_overseer, @@ -850,26 +601,14 @@ fn fetch_one_collation_at_a_time() { /// timeout and in case of an error. #[test] fn fetches_next_collation() { - let test_state = TestState::default(); + let mut test_state = TestState::with_one_scheduled_para(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; + let first = test_state.relay_parent; let second = Hash::random(); - - let our_view = our_view![test_state.relay_parent, second]; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view.clone(), - )), - ) - .await; - - for hash in our_view.iter() { - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, *hash).await; - } + update_view(&mut virtual_overseer, &mut test_state, vec![(first, 0), (second, 1)]).await; let peer_b = PeerId::random(); let peer_c = PeerId::random(); @@ -979,21 +718,13 @@ fn fetches_next_collation() { #[test] fn reject_connection_to_next_group() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; let peer_b = PeerId::random(); @@ -1026,26 +757,13 @@ fn reject_connection_to_next_group() { // invalid. #[test] fn fetch_next_collation_on_invalid_collation() { - let test_state = TestState::default(); + let mut test_state = TestState::with_one_scheduled_para(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - let second = Hash::random(); - - let our_view = our_view![test_state.relay_parent, second]; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view.clone(), - )), - ) - .await; - - for hash in our_view.iter() { - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, *hash).await; - } + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; let peer_b = PeerId::random(); let peer_c = PeerId::random(); @@ -1068,12 +786,12 @@ fn fetch_next_collation_on_invalid_collation() { ) .await; - advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent, None).await; - advertise_collation(&mut virtual_overseer, peer_c, test_state.relay_parent, None).await; + advertise_collation(&mut virtual_overseer, peer_b, relay_parent, None).await; + advertise_collation(&mut virtual_overseer, peer_c, relay_parent, None).await; let response_channel = assert_fetch_collation_request( &mut virtual_overseer, - test_state.relay_parent, + relay_parent, test_state.chain_ids[0], None, ) @@ -1083,7 +801,7 @@ fn fetch_next_collation_on_invalid_collation() { let mut candidate_a = dummy_candidate_receipt_bad_sig(dummy_hash(), Some(Default::default())); candidate_a.descriptor.para_id = test_state.chain_ids[0]; - candidate_a.descriptor.relay_parent = test_state.relay_parent; + candidate_a.descriptor.relay_parent = relay_parent; candidate_a.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); response_channel .send(Ok(( @@ -1098,7 +816,7 @@ fn fetch_next_collation_on_invalid_collation() { let receipt = assert_candidate_backing_second( &mut virtual_overseer, - test_state.relay_parent, + relay_parent, test_state.chain_ids[0], &pov, CollationVersion::V1, @@ -1108,7 +826,7 @@ fn fetch_next_collation_on_invalid_collation() { // Inform that the candidate was invalid. overseer_send( &mut virtual_overseer, - CollatorProtocolMessage::Invalid(test_state.relay_parent, receipt), + CollatorProtocolMessage::Invalid(relay_parent, receipt), ) .await; @@ -1125,7 +843,7 @@ fn fetch_next_collation_on_invalid_collation() { // We should see a request for another collation. assert_fetch_collation_request( &mut virtual_overseer, - test_state.relay_parent, + relay_parent, test_state.chain_ids[0], None, ) @@ -1137,25 +855,15 @@ fn fetch_next_collation_on_invalid_collation() { #[test] fn inactive_disconnected() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; let pair = CollatorPair::generate().0; - let hash_a = test_state.relay_parent; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![hash_a], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; let peer_b = PeerId::random(); @@ -1167,11 +875,11 @@ fn inactive_disconnected() { CollationVersion::V1, ) .await; - advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent, None).await; + advertise_collation(&mut virtual_overseer, peer_b, relay_parent, None).await; assert_fetch_collation_request( &mut virtual_overseer, - test_state.relay_parent, + relay_parent, test_state.chain_ids[0], None, ) @@ -1186,31 +894,24 @@ fn inactive_disconnected() { #[test] fn activity_extends_life() { - let test_state = TestState::default(); + let mut test_state = TestState::with_one_scheduled_para(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; let pair = CollatorPair::generate().0; - let hash_a = test_state.relay_parent; - let hash_b = Hash::repeat_byte(1); - let hash_c = Hash::repeat_byte(2); - - let our_view = our_view![hash_a, hash_b, hash_c]; + let hash_a = Hash::from_low_u64_be(12); + let hash_b = Hash::from_low_u64_be(11); + let hash_c = Hash::from_low_u64_be(10); - overseer_send( + update_view( &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view.clone(), - )), + &mut test_state, + vec![(hash_a, 0), (hash_b, 1), (hash_c, 2)], ) .await; - for hash in our_view.iter() { - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, *hash).await; - } - let peer_b = PeerId::random(); connect_and_declare_collator( @@ -1268,21 +969,13 @@ fn activity_extends_life() { #[test] fn disconnect_if_no_declare() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; let peer_b = PeerId::random(); @@ -1305,26 +998,16 @@ fn disconnect_if_no_declare() { #[test] fn disconnect_if_wrong_declare() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - let pair = CollatorPair::generate().0; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; - let peer_b = PeerId::random(); + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; + overseer_send( &mut virtual_overseer, CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected( @@ -1367,26 +1050,16 @@ fn disconnect_if_wrong_declare() { #[test] fn delay_reputation_change() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| false), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - let pair = CollatorPair::generate().0; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; - let peer_b = PeerId::random(); + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; + overseer_send( &mut virtual_overseer, CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected( @@ -1460,42 +1133,24 @@ fn view_change_clears_old_collators() { let pair = CollatorPair::generate().0; - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; - - let peer_b = PeerId::random(); + let peer = PeerId::random(); + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; connect_and_declare_collator( &mut virtual_overseer, - peer_b, + peer, pair.clone(), test_state.chain_ids[0], CollationVersion::V1, ) .await; - let hash_b = Hash::repeat_byte(69); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![hash_b], - )), - ) - .await; - test_state.group_rotation_info = test_state.group_rotation_info.bump_rotation(); - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, hash_b).await; - assert_collator_disconnect(&mut virtual_overseer, peer_b).await; + update_view(&mut virtual_overseer, &mut test_state, vec![]).await; + + assert_collator_disconnect(&mut virtual_overseer, peer).await; virtual_overseer }) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index eda26e8539a1db817c55dc4b70db9a5850e29fc8..0a00fb6f7b783d028187963ef1721561332acc21 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -21,14 +21,11 @@ use super::*; use polkadot_node_subsystem::messages::ChainApiMessage; use polkadot_primitives::{ vstaging::{CommittedCandidateReceiptV2 as CommittedCandidateReceipt, MutateDescriptorV2}, - AsyncBackingParams, BlockNumber, CandidateCommitments, Header, SigningContext, ValidatorId, + BlockNumber, CandidateCommitments, Header, SigningContext, ValidatorId, }; use polkadot_primitives_test_helpers::dummy_committed_candidate_receipt_v2; use rstest::rstest; -const ASYNC_BACKING_PARAMETERS: AsyncBackingParams = - AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: 3 }; - fn get_parent_hash(hash: Hash) -> Hash { Hash::from_low_u64_be(hash.to_low_u64_be() + 1) } @@ -48,7 +45,8 @@ async fn assert_construct_per_relay_parent( msg, AllMessages::RuntimeApi( RuntimeApiMessage::Request(parent, RuntimeApiRequest::Validators(tx)) - ) if parent == hash => { + ) => { + assert_eq!(parent, hash); tx.send(Ok(test_state.validator_public.clone())).unwrap(); } ); @@ -65,15 +63,6 @@ async fn assert_construct_per_relay_parent( } ); - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::AvailabilityCores(tx)) - ) if parent == hash => { - tx.send(Ok(test_state.cores.clone())).unwrap(); - } - ); - assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::RuntimeApi(RuntimeApiMessage::Request( @@ -88,12 +77,11 @@ async fn assert_construct_per_relay_parent( /// Handle a view update. pub(super) async fn update_view( virtual_overseer: &mut VirtualOverseer, - test_state: &TestState, + test_state: &mut TestState, new_view: Vec<(Hash, u32)>, // Hash and block number. - activated: u8, // How many new heads does this update contain? ) -> Option<AllMessages> { + let last_block_from_view = new_view.last().map(|t| t.1); let new_view: HashMap<Hash, u32> = HashMap::from_iter(new_view); - let our_view = OurView::new(new_view.keys().map(|hash| *hash), 0); overseer_send( @@ -103,9 +91,14 @@ pub(super) async fn update_view( .await; let mut next_overseer_message = None; - for _ in 0..activated { + for _ in 0..new_view.len() { + let msg = match next_overseer_message.take() { + Some(msg) => msg, + None => overseer_recv(virtual_overseer).await, + }; + let (leaf_hash, leaf_number) = assert_matches!( - overseer_recv(virtual_overseer).await, + msg, AllMessages::RuntimeApi(RuntimeApiMessage::Request( parent, RuntimeApiRequest::SessionIndexForChild(tx) @@ -115,16 +108,6 @@ pub(super) async fn update_view( } ); - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::AsyncBackingParams(tx), - )) => { - tx.send(Ok(ASYNC_BACKING_PARAMETERS)).unwrap(); - } - ); - assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::RuntimeApi(RuntimeApiMessage::Request( @@ -144,7 +127,7 @@ pub(super) async fn update_view( ) .await; - let min_number = leaf_number.saturating_sub(ASYNC_BACKING_PARAMETERS.allowed_ancestry_len); + let min_number = leaf_number.saturating_sub(test_state.scheduling_lookahead); let ancestry_len = leaf_number + 1 - min_number; let ancestry_hashes = std::iter::successors(Some(leaf_hash), |h| Some(get_parent_hash(*h))) @@ -157,6 +140,10 @@ pub(super) async fn update_view( { let mut ancestry_iter = ancestry_iter.clone(); while let Some((hash, number)) = ancestry_iter.next() { + if Some(number) == test_state.last_known_block { + break; + } + // May be `None` for the last element. let parent_hash = ancestry_iter.peek().map(|(h, _)| *h).unwrap_or_else(|| get_parent_hash(hash)); @@ -204,6 +191,9 @@ pub(super) async fn update_view( // Skip the leaf. for (hash, number) in ancestry_iter.skip(1).take(requested_len.saturating_sub(1)) { + if Some(number) == test_state.last_known_block { + break; + } assert_construct_per_relay_parent( virtual_overseer, test_state, @@ -214,6 +204,9 @@ pub(super) async fn update_view( .await; } } + + test_state.last_known_block = last_block_from_view; + next_overseer_message } @@ -337,9 +330,140 @@ async fn assert_persisted_validation_data( } } +// Combines dummy candidate creation, advertisement and fetching in a single call +async fn submit_second_and_assert( + virtual_overseer: &mut VirtualOverseer, + keystore: KeystorePtr, + para_id: ParaId, + relay_parent: Hash, + collator: PeerId, + candidate_head_data: HeadData, +) { + let (candidate, commitments) = + create_dummy_candidate_and_commitments(para_id, candidate_head_data, relay_parent); + + let candidate_hash = candidate.hash(); + let parent_head_data_hash = Hash::zero(); + + assert_advertise_collation( + virtual_overseer, + collator, + relay_parent, + para_id, + (candidate_hash, parent_head_data_hash), + ) + .await; + + let response_channel = assert_fetch_collation_request( + virtual_overseer, + relay_parent, + para_id, + Some(candidate_hash), + ) + .await; + + let pov = PoV { block_data: BlockData(vec![1]) }; + + send_collation_and_assert_processing( + virtual_overseer, + keystore, + relay_parent, + para_id, + collator, + response_channel, + candidate, + commitments, + pov, + ) + .await; +} + +fn create_dummy_candidate_and_commitments( + para_id: ParaId, + candidate_head_data: HeadData, + relay_parent: Hash, +) -> (CandidateReceipt, CandidateCommitments) { + let mut candidate = dummy_candidate_receipt_bad_sig(relay_parent, Some(Default::default())); + candidate.descriptor.para_id = para_id; + candidate.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); + let commitments = CandidateCommitments { + head_data: candidate_head_data, + horizontal_messages: Default::default(), + upward_messages: Default::default(), + new_validation_code: None, + processed_downward_messages: 0, + hrmp_watermark: 0, + }; + candidate.commitments_hash = commitments.hash(); + + (candidate.into(), commitments) +} + +async fn assert_advertise_collation( + virtual_overseer: &mut VirtualOverseer, + peer: PeerId, + relay_parent: Hash, + expected_para_id: ParaId, + candidate: (CandidateHash, Hash), +) { + advertise_collation(virtual_overseer, peer, relay_parent, Some(candidate)).await; + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::CandidateBacking( + CandidateBackingMessage::CanSecond(request, tx), + ) => { + assert_eq!(request.candidate_hash, candidate.0); + assert_eq!(request.candidate_para_id, expected_para_id); + assert_eq!(request.parent_head_data_hash, candidate.1); + tx.send(true).expect("receiving side should be alive"); + } + ); +} + +async fn send_collation_and_assert_processing( + virtual_overseer: &mut VirtualOverseer, + keystore: KeystorePtr, + relay_parent: Hash, + expected_para_id: ParaId, + expected_peer_id: PeerId, + response_channel: ResponseSender, + candidate: CandidateReceipt, + commitments: CandidateCommitments, + pov: PoV, +) { + response_channel + .send(Ok(( + request_v2::CollationFetchingResponse::Collation(candidate.clone(), pov.clone()) + .encode(), + ProtocolName::from(""), + ))) + .expect("Sending response should succeed"); + + assert_candidate_backing_second( + virtual_overseer, + relay_parent, + expected_para_id, + &pov, + CollationVersion::V2, + ) + .await; + + let candidate = CommittedCandidateReceipt { descriptor: candidate.descriptor, commitments }; + + send_seconded_statement(virtual_overseer, keystore.clone(), &candidate).await; + + assert_collation_seconded( + virtual_overseer, + relay_parent, + expected_peer_id, + CollationVersion::V2, + ) + .await; +} + #[test] fn v1_advertisement_accepted_and_seconded() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; @@ -349,7 +473,7 @@ fn v1_advertisement_accepted_and_seconded() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 0; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -377,7 +501,7 @@ fn v1_advertisement_accepted_and_seconded() { candidate.descriptor.para_id = test_state.chain_ids[0]; candidate.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); let commitments = CandidateCommitments { - head_data: HeadData(vec![1 as u8]), + head_data: HeadData(vec![1u8]), horizontal_messages: Default::default(), upward_messages: Default::default(), new_validation_code: None, @@ -418,7 +542,7 @@ fn v1_advertisement_accepted_and_seconded() { #[test] fn v1_advertisement_rejected_on_non_active_leaf() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -428,7 +552,7 @@ fn v1_advertisement_rejected_on_non_active_leaf() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 5; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -460,7 +584,7 @@ fn v1_advertisement_rejected_on_non_active_leaf() { #[test] fn accept_advertisements_from_implicit_view() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -478,7 +602,7 @@ fn accept_advertisements_from_implicit_view() { let head_d = get_parent_hash(head_c); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); let peer_b = PeerId::random(); @@ -563,24 +687,26 @@ fn accept_advertisements_from_implicit_view() { #[test] fn second_multiple_candidates_per_relay_parent() { - let test_state = TestState::default(); + let mut test_state = TestState::with_one_scheduled_para(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; let pair = CollatorPair::generate().0; - // Grandparent of head `a`. + let head_a = Hash::from_low_u64_be(130); + let head_a_num: u32 = 0; + let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 2; - // Grandparent of head `b`. - // Group rotation frequency is 1 by default, at `c` we're assigned - // to the first para. - let head_c = Hash::from_low_u64_be(130); - - // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + // Activated leaf is `a` and `b`.The collation will be based on `b`. + update_view( + &mut virtual_overseer, + &mut test_state, + vec![(head_a, head_a_num), (head_b, head_b_num)], + ) + .await; let peer_a = PeerId::random(); @@ -593,80 +719,16 @@ fn second_multiple_candidates_per_relay_parent() { ) .await; - for i in 0..(ASYNC_BACKING_PARAMETERS.max_candidate_depth + 1) { - let mut candidate = dummy_candidate_receipt_bad_sig(head_c, Some(Default::default())); - candidate.descriptor.para_id = test_state.chain_ids[0]; - candidate.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); - let commitments = CandidateCommitments { - head_data: HeadData(vec![i as u8]), - horizontal_messages: Default::default(), - upward_messages: Default::default(), - new_validation_code: None, - processed_downward_messages: 0, - hrmp_watermark: 0, - }; - candidate.commitments_hash = commitments.hash(); - let candidate: CandidateReceipt = candidate.into(); - - let candidate_hash = candidate.hash(); - let parent_head_data_hash = Hash::zero(); - - advertise_collation( - &mut virtual_overseer, - peer_a, - head_c, - Some((candidate_hash, parent_head_data_hash)), - ) - .await; - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::CandidateBacking( - CandidateBackingMessage::CanSecond(request, tx), - ) => { - assert_eq!(request.candidate_hash, candidate_hash); - assert_eq!(request.candidate_para_id, test_state.chain_ids[0]); - assert_eq!(request.parent_head_data_hash, parent_head_data_hash); - tx.send(true).expect("receiving side should be alive"); - } - ); - - let response_channel = assert_fetch_collation_request( - &mut virtual_overseer, - head_c, - test_state.chain_ids[0], - Some(candidate_hash), - ) - .await; - - let pov = PoV { block_data: BlockData(vec![1]) }; - - response_channel - .send(Ok(( - request_v2::CollationFetchingResponse::Collation( - candidate.clone(), - pov.clone(), - ) - .encode(), - ProtocolName::from(""), - ))) - .expect("Sending response should succeed"); - - assert_candidate_backing_second( + for i in 0..test_state.scheduling_lookahead { + submit_second_and_assert( &mut virtual_overseer, - head_c, + keystore.clone(), test_state.chain_ids[0], - &pov, - CollationVersion::V2, + head_a, + peer_a, + HeadData(vec![i as u8]), ) .await; - - let candidate = - CommittedCandidateReceipt { descriptor: candidate.descriptor, commitments }; - - send_seconded_statement(&mut virtual_overseer, keystore.clone(), &candidate).await; - - assert_collation_seconded(&mut virtual_overseer, head_c, peer_a, CollationVersion::V2) - .await; } // No more advertisements can be made for this relay parent. @@ -674,21 +736,14 @@ fn second_multiple_candidates_per_relay_parent() { advertise_collation( &mut virtual_overseer, peer_a, - head_c, + head_a, Some((candidate_hash, Hash::zero())), ) .await; - // Reported because reached the limit of advertisements per relay parent. - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(peer_id, rep)), - ) => { - assert_eq!(peer_a, peer_id); - assert_eq!(rep.value, COST_UNEXPECTED_MESSAGE.cost_or_benefit()); - } - ); + // Rejected but not reported because reached the limit of advertisements for the para_id + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); // By different peer too (not reported). let pair_b = CollatorPair::generate().0; @@ -707,7 +762,7 @@ fn second_multiple_candidates_per_relay_parent() { advertise_collation( &mut virtual_overseer, peer_b, - head_c, + head_a, Some((candidate_hash, Hash::zero())), ) .await; @@ -721,7 +776,7 @@ fn second_multiple_candidates_per_relay_parent() { #[test] fn fetched_collation_sanity_check() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -738,7 +793,7 @@ fn fetched_collation_sanity_check() { let head_c = Hash::from_low_u64_be(130); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -832,7 +887,7 @@ fn fetched_collation_sanity_check() { #[test] fn sanity_check_invalid_parent_head_data() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -842,7 +897,7 @@ fn sanity_check_invalid_parent_head_data() { let head_c = Hash::from_low_u64_be(130); let head_c_num = 3; - update_view(&mut virtual_overseer, &test_state, vec![(head_c, head_c_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_c, head_c_num)]).await; let peer_a = PeerId::random(); @@ -952,7 +1007,7 @@ fn sanity_check_invalid_parent_head_data() { #[test] fn advertisement_spam_protection() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -965,7 +1020,7 @@ fn advertisement_spam_protection() { let head_c = get_parent_hash(head_b); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); connect_and_declare_collator( @@ -1026,7 +1081,7 @@ fn advertisement_spam_protection() { #[case(true)] #[case(false)] fn child_blocked_from_seconding_by_parent(#[case] valid_parent: bool) { - let test_state = TestState::default(); + let mut test_state = TestState::with_one_scheduled_para(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; @@ -1043,7 +1098,7 @@ fn child_blocked_from_seconding_by_parent(#[case] valid_parent: bool) { let head_c = Hash::from_low_u64_be(130); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -1344,7 +1399,7 @@ fn v2_descriptor(#[case] v2_feature_enabled: bool) { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 0; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -1442,7 +1497,7 @@ fn v2_descriptor(#[case] v2_feature_enabled: bool) { #[test] fn invalid_v2_descriptor() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -1452,7 +1507,7 @@ fn invalid_v2_descriptor() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 0; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -1545,3 +1600,865 @@ fn invalid_v2_descriptor() { virtual_overseer }); } + +#[test] +fn fair_collation_fetches() { + let mut test_state = TestState::with_shared_core(); + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let head_b = Hash::from_low_u64_be(128); + let head_b_num: u32 = 2; + + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; + + let peer_a = PeerId::random(); + let pair_a = CollatorPair::generate().0; + + connect_and_declare_collator( + &mut virtual_overseer, + peer_a, + pair_a.clone(), + test_state.chain_ids[0], + CollationVersion::V2, + ) + .await; + + let peer_b = PeerId::random(); + let pair_b = CollatorPair::generate().0; + + connect_and_declare_collator( + &mut virtual_overseer, + peer_b, + pair_b.clone(), + test_state.chain_ids[1], + CollationVersion::V2, + ) + .await; + + // `peer_a` sends two advertisements (its claim queue limit) + for i in 0..2u8 { + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + head_b, + peer_a, + HeadData(vec![i]), + ) + .await; + } + + // `peer_a` sends another advertisement and it is ignored + let candidate_hash = CandidateHash(Hash::repeat_byte(0xAA)); + advertise_collation( + &mut virtual_overseer, + peer_a, + head_b, + Some((candidate_hash, Hash::zero())), + ) + .await; + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // `peer_b` should still be able to advertise its collation + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[1]), + head_b, + peer_b, + HeadData(vec![0u8]), + ) + .await; + + // And no more advertisements can be made for this relay parent. + + // verify for peer_a + let candidate_hash = CandidateHash(Hash::repeat_byte(0xBB)); + advertise_collation( + &mut virtual_overseer, + peer_a, + head_b, + Some((candidate_hash, Hash::zero())), + ) + .await; + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // verify for peer_b + let candidate_hash = CandidateHash(Hash::repeat_byte(0xCC)); + advertise_collation( + &mut virtual_overseer, + peer_b, + head_b, + Some((candidate_hash, Hash::zero())), + ) + .await; + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} + +#[test] +fn collation_fetching_prefer_entries_earlier_in_claim_queue() { + let mut test_state = TestState::with_shared_core(); + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + let pair_b = CollatorPair::generate().0; + let collator_b = PeerId::random(); + let para_id_b = test_state.chain_ids[1]; + + let head = Hash::from_low_u64_be(128); + let head_num: u32 = 2; + + update_view(&mut virtual_overseer, &mut test_state, vec![(head, head_num)]).await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_b, + pair_b.clone(), + para_id_b, + CollationVersion::V2, + ) + .await; + + let (candidate_a1, commitments_a1) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![0u8]), head); + let (candidate_b1, commitments_b1) = + create_dummy_candidate_and_commitments(para_id_b, HeadData(vec![1u8]), head); + let (candidate_a2, commitments_a2) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![2u8]), head); + let (candidate_a3, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![3u8]), head); + let parent_head_data_a1 = HeadData(vec![0u8]); + let parent_head_data_b1 = HeadData(vec![1u8]); + let parent_head_data_a2 = HeadData(vec![2u8]); + let parent_head_data_a3 = HeadData(vec![3u8]); + + // advertise a collation for `para_id_a` but don't send the collation. This will be a + // pending fetch. + assert_advertise_collation( + &mut virtual_overseer, + collator_a, + head, + para_id_a, + (candidate_a1.hash(), parent_head_data_a1.hash()), + ) + .await; + + let response_channel_a1 = assert_fetch_collation_request( + &mut virtual_overseer, + head, + para_id_a, + Some(candidate_a1.hash()), + ) + .await; + + // advertise another collation for `para_id_a`. This one should be fetched last. + assert_advertise_collation( + &mut virtual_overseer, + collator_a, + head, + para_id_a, + (candidate_a2.hash(), parent_head_data_a2.hash()), + ) + .await; + + // There is a pending collation so nothing should be fetched + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // Advertise a collation for `para_id_b`. This should be fetched second + assert_advertise_collation( + &mut virtual_overseer, + collator_b, + head, + para_id_b, + (candidate_b1.hash(), parent_head_data_b1.hash()), + ) + .await; + + // Again - no fetch because of the pending collation + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + //Now send a response for the first fetch and examine the second fetch + send_collation_and_assert_processing( + &mut virtual_overseer, + keystore.clone(), + head, + para_id_a, + collator_a, + response_channel_a1, + candidate_a1, + commitments_a1, + PoV { block_data: BlockData(vec![1]) }, + ) + .await; + + // The next fetch should be for `para_id_b` + let response_channel_b = assert_fetch_collation_request( + &mut virtual_overseer, + head, + para_id_b, + Some(candidate_b1.hash()), + ) + .await; + + send_collation_and_assert_processing( + &mut virtual_overseer, + keystore.clone(), + head, + para_id_b, + collator_b, + response_channel_b, + candidate_b1, + commitments_b1, + PoV { block_data: BlockData(vec![2]) }, + ) + .await; + + // and the final one for `para_id_a` + let response_channel_a2 = assert_fetch_collation_request( + &mut virtual_overseer, + head, + para_id_a, + Some(candidate_a2.hash()), + ) + .await; + + // Advertise another collation for `para_id_a`. This should be rejected as there is no slot + // in the claim queue for it. One is fetched and one is pending. + advertise_collation( + &mut virtual_overseer, + collator_a, + head, + Some((candidate_a3.hash(), parent_head_data_a3.hash())), + ) + .await; + + // `CanSecond` shouldn't be sent as the advertisement should be ignored + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // Fetch the pending collation + send_collation_and_assert_processing( + &mut virtual_overseer, + keystore.clone(), + head, + para_id_a, + collator_a, + response_channel_a2, + candidate_a2, + commitments_a2, + PoV { block_data: BlockData(vec![3]) }, + ) + .await; + + virtual_overseer + }); +} + +#[test] +fn collation_fetching_considers_advertisements_from_the_whole_view() { + let mut test_state = TestState::with_shared_core(); + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + let pair_b = CollatorPair::generate().0; + let collator_b = PeerId::random(); + let para_id_b = test_state.chain_ids[1]; + + let relay_parent_2 = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); + + assert_eq!( + *test_state.claim_queue.get(&CoreIndex(0)).unwrap(), + VecDeque::from([para_id_b, para_id_a, para_id_a]) + ); + + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_2, 2)]).await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_b, + pair_b.clone(), + para_id_b, + CollationVersion::V2, + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_2, + collator_a, + HeadData(vec![0u8]), + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_b, + relay_parent_2, + collator_b, + HeadData(vec![1u8]), + ) + .await; + + let relay_parent_3 = Hash::from_low_u64_be(relay_parent_2.to_low_u64_be() - 1); + *test_state.claim_queue.get_mut(&CoreIndex(0)).unwrap() = + VecDeque::from([para_id_a, para_id_a, para_id_b]); + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_3, 3)]).await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_b, + relay_parent_3, + collator_b, + HeadData(vec![3u8]), + ) + .await; + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_3, + collator_a, + HeadData(vec![3u8]), + ) + .await; + + // At this point the claim queue is satisfied and any advertisement at `relay_parent_4` + // must be ignored + + let (candidate_a, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![5u8]), relay_parent_3); + let parent_head_data_a = HeadData(vec![5u8]); + + advertise_collation( + &mut virtual_overseer, + collator_a, + relay_parent_3, + Some((candidate_a.hash(), parent_head_data_a.hash())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + let (candidate_b, _) = + create_dummy_candidate_and_commitments(para_id_b, HeadData(vec![6u8]), relay_parent_3); + let parent_head_data_b = HeadData(vec![6u8]); + + advertise_collation( + &mut virtual_overseer, + collator_b, + relay_parent_3, + Some((candidate_b.hash(), parent_head_data_b.hash())), + ) + .await; + + // `CanSecond` shouldn't be sent as the advertisement should be ignored + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // At `relay_parent_6` the advertisement for `para_id_b` falls out of the view so a new one + // can be accepted + let relay_parent_6 = Hash::from_low_u64_be(relay_parent_3.to_low_u64_be() - 2); + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_6, 6)]).await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_6, + collator_a, + HeadData(vec![3u8]), + ) + .await; + + virtual_overseer + }); +} + +#[test] +fn collation_fetching_fairness_handles_old_claims() { + let mut test_state = TestState::with_shared_core(); + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + let pair_b = CollatorPair::generate().0; + let collator_b = PeerId::random(); + let para_id_b = test_state.chain_ids[1]; + + let relay_parent_2 = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); + + *test_state.claim_queue.get_mut(&CoreIndex(0)).unwrap() = + VecDeque::from([para_id_a, para_id_b, para_id_a]); + + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_2, 2)]).await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_b, + pair_b.clone(), + para_id_b, + CollationVersion::V2, + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_2, + collator_a, + HeadData(vec![0u8]), + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_b, + relay_parent_2, + collator_b, + HeadData(vec![1u8]), + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_2, + collator_a, + HeadData(vec![2u8]), + ) + .await; + + let relay_parent_3 = Hash::from_low_u64_be(relay_parent_2.to_low_u64_be() - 1); + + *test_state.claim_queue.get_mut(&CoreIndex(0)).unwrap() = + VecDeque::from([para_id_b, para_id_a, para_id_b]); + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_3, 3)]).await; + + // nothing is advertised here + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + let relay_parent_4 = Hash::from_low_u64_be(relay_parent_3.to_low_u64_be() - 1); + + *test_state.claim_queue.get_mut(&CoreIndex(0)).unwrap() = + VecDeque::from([para_id_a, para_id_b, para_id_a]); + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_4, 4)]).await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_b, + relay_parent_4, + collator_b, + HeadData(vec![3u8]), + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_4, + collator_a, + HeadData(vec![4u8]), + ) + .await; + + // At this point the claim queue is satisfied and any advertisement at `relay_parent_4` + // must be ignored + + // Advertisement for `para_id_a` at `relay_parent_4` which must be ignored + let (candidate_a, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![5u8]), relay_parent_4); + let parent_head_data_a = HeadData(vec![5u8]); + + advertise_collation( + &mut virtual_overseer, + collator_a, + relay_parent_4, + Some((candidate_a.hash(), parent_head_data_a.hash())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // Advertisement for `para_id_b` at `relay_parent_4` which must be ignored + let (candidate_b, _) = + create_dummy_candidate_and_commitments(para_id_b, HeadData(vec![6u8]), relay_parent_4); + let parent_head_data_b = HeadData(vec![6u8]); + + advertise_collation( + &mut virtual_overseer, + collator_b, + relay_parent_4, + Some((candidate_b.hash(), parent_head_data_b.hash())), + ) + .await; + + // `CanSecond` shouldn't be sent as the advertisement should be ignored + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} + +#[test] +fn claims_below_are_counted_correctly() { + let mut test_state = TestState::with_one_scheduled_para(); + + // Shorten the claim queue to make the test smaller + let mut claim_queue = BTreeMap::new(); + claim_queue.insert( + CoreIndex(0), + VecDeque::from_iter( + [ParaId::from(test_state.chain_ids[0]), ParaId::from(test_state.chain_ids[0])] + .into_iter(), + ), + ); + test_state.claim_queue = claim_queue; + test_state.scheduling_lookahead = 2; + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let hash_a = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); + let hash_b = Hash::from_low_u64_be(hash_a.to_low_u64_be() - 1); + let hash_c = Hash::from_low_u64_be(hash_b.to_low_u64_be() - 1); + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + update_view(&mut virtual_overseer, &mut test_state, vec![(hash_c, 2)]).await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + // A collation at hash_a claims the spot at hash_a + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_a, + collator_a, + HeadData(vec![0u8]), + ) + .await; + + // Another collation at hash_a claims the spot at hash_b + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_a, + collator_a, + HeadData(vec![1u8]), + ) + .await; + + // Collation at hash_c claims its own spot + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_c, + collator_a, + HeadData(vec![2u8]), + ) + .await; + + // Collation at hash_b should be ignored because the claim queue is satisfied + let (ignored_candidate, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![3u8]), hash_b); + + advertise_collation( + &mut virtual_overseer, + collator_a, + hash_b, + Some((ignored_candidate.hash(), Hash::random())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} + +#[test] +fn claims_above_are_counted_correctly() { + let mut test_state = TestState::with_one_scheduled_para(); + + // Shorten the claim queue to make the test smaller + let mut claim_queue = BTreeMap::new(); + claim_queue.insert( + CoreIndex(0), + VecDeque::from_iter( + [ParaId::from(test_state.chain_ids[0]), ParaId::from(test_state.chain_ids[0])] + .into_iter(), + ), + ); + test_state.claim_queue = claim_queue; + test_state.scheduling_lookahead = 2; + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let hash_a = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); // block 0 + let hash_b = Hash::from_low_u64_be(hash_a.to_low_u64_be() - 1); // block 1 + let hash_c = Hash::from_low_u64_be(hash_b.to_low_u64_be() - 1); // block 2 + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + update_view(&mut virtual_overseer, &mut test_state, vec![(hash_c, 2)]).await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + // A collation at hash_b claims the spot at hash_b + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_b, + collator_a, + HeadData(vec![0u8]), + ) + .await; + + // Another collation at hash_b claims the spot at hash_c + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_b, + collator_a, + HeadData(vec![1u8]), + ) + .await; + + // Collation at hash_a claims its own spot + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_a, + collator_a, + HeadData(vec![0u8]), + ) + .await; + + // Another Collation at hash_a should be ignored because the claim queue is satisfied + let (ignored_candidate, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![2u8]), hash_a); + + advertise_collation( + &mut virtual_overseer, + collator_a, + hash_a, + Some((ignored_candidate.hash(), Hash::random())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // Same for hash_b + let (ignored_candidate, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![3u8]), hash_b); + + advertise_collation( + &mut virtual_overseer, + collator_a, + hash_b, + Some((ignored_candidate.hash(), Hash::random())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} + +#[test] +fn claim_fills_last_free_slot() { + let mut test_state = TestState::with_one_scheduled_para(); + + // Shorten the claim queue to make the test smaller + let mut claim_queue = BTreeMap::new(); + claim_queue.insert( + CoreIndex(0), + VecDeque::from_iter( + [ParaId::from(test_state.chain_ids[0]), ParaId::from(test_state.chain_ids[0])] + .into_iter(), + ), + ); + test_state.claim_queue = claim_queue; + test_state.scheduling_lookahead = 2; + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let hash_a = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); // block 0 + let hash_b = Hash::from_low_u64_be(hash_a.to_low_u64_be() - 1); // block 1 + let hash_c = Hash::from_low_u64_be(hash_b.to_low_u64_be() - 1); // block 2 + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + update_view(&mut virtual_overseer, &mut test_state, vec![(hash_c, 2)]).await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + // A collation at hash_a claims its spot + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_a, + collator_a, + HeadData(vec![0u8]), + ) + .await; + + // Collation at hash_b claims its own spot + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_b, + collator_a, + HeadData(vec![3u8]), + ) + .await; + + // Collation at hash_c claims its own spot + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_c, + collator_a, + HeadData(vec![2u8]), + ) + .await; + + // Another Collation at hash_a should be ignored because the claim queue is satisfied + let (ignored_candidate, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![3u8]), hash_a); + + advertise_collation( + &mut virtual_overseer, + collator_a, + hash_a, + Some((ignored_candidate.hash(), Hash::random())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // Same for hash_b + let (ignored_candidate, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![4u8]), hash_b); + + advertise_collation( + &mut virtual_overseer, + collator_a, + hash_b, + Some((ignored_candidate.hash(), Hash::random())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} diff --git a/polkadot/node/network/dispute-distribution/Cargo.toml b/polkadot/node/network/dispute-distribution/Cargo.toml index b4dcafe09eb60f9991af206d7c826d559f32d00a..079a37ca0aff0afd5623874edf9269b0f8332ba6 100644 --- a/polkadot/node/network/dispute-distribution/Cargo.toml +++ b/polkadot/node/network/dispute-distribution/Cargo.toml @@ -5,37 +5,39 @@ description = "Polkadot Dispute Distribution subsystem, which ensures all concer authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +codec = { features = ["std"], workspace = true, default-features = true } +derive_more = { workspace = true, default-features = true } +fatality = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } gum = { workspace = true, default-features = true } -derive_more = { workspace = true, default-features = true } -codec = { features = ["std"], workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } +indexmap = { workspace = true } polkadot-erasure-coding = { workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } -polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } +schnellru = { workspace = true } sp-application-crypto = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } thiserror = { workspace = true } -fatality = { workspace = true } -schnellru = { workspace = true } -indexmap = { workspace = true } [dev-dependencies] +assert_matches = { workspace = true } async-channel = { workspace = true } async-trait = { workspace = true } +futures-timer = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } +polkadot-primitives-test-helpers = { workspace = true } +sc-keystore = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } -futures-timer = { workspace = true } -assert_matches = { workspace = true } -polkadot-primitives-test-helpers = { workspace = true } diff --git a/polkadot/node/network/gossip-support/Cargo.toml b/polkadot/node/network/gossip-support/Cargo.toml index c8c19e5de070fdb981a484f78d5349161fd6d71c..1ba556fc46b0479d0fb094cb96db909e9c444ac2 100644 --- a/polkadot/node/network/gossip-support/Cargo.toml +++ b/polkadot/node/network/gossip-support/Cargo.toml @@ -5,17 +5,19 @@ description = "Polkadot Gossip Support subsystem. Responsible for keeping track authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +sc-network = { workspace = true, default-features = true } +sc-network-common = { workspace = true, default-features = true } sp-application-crypto = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } -sc-network-common = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } @@ -24,15 +26,15 @@ polkadot-primitives = { workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } +gum = { workspace = true, default-features = true } rand = { workspace = true } rand_chacha = { workspace = true } -gum = { workspace = true, default-features = true } [dev-dependencies] -sp-keyring = { workspace = true, default-features = true } +sp-authority-discovery = { workspace = true, default-features = true } sp-consensus-babe = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -sp-authority-discovery = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } diff --git a/polkadot/node/network/protocol/Cargo.toml b/polkadot/node/network/protocol/Cargo.toml index 3d51d3c0a5659ca168a39e29f537d1028a0dcdbe..83a24959f60a96ed1791739a8f9b1414d91474c1 100644 --- a/polkadot/node/network/protocol/Cargo.toml +++ b/polkadot/node/network/protocol/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Primitives types for the Node-side" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -12,22 +14,22 @@ workspace = true [dependencies] async-channel = { workspace = true } async-trait = { workspace = true } +bitvec = { workspace = true, default-features = true } +codec = { features = ["derive"], workspace = true } +derive_more = { workspace = true, default-features = true } +fatality = { workspace = true } +futures = { workspace = true } +gum = { workspace = true, default-features = true } hex = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } -codec = { features = ["derive"], workspace = true } +polkadot-primitives = { workspace = true, default-features = true } +rand = { workspace = true, default-features = true } +sc-authority-discovery = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } -sc-authority-discovery = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } strum = { features = ["derive"], workspace = true, default-features = true } -futures = { workspace = true } thiserror = { workspace = true } -fatality = { workspace = true } -rand = { workspace = true, default-features = true } -derive_more = { workspace = true, default-features = true } -gum = { workspace = true, default-features = true } -bitvec = { workspace = true, default-features = true } [dev-dependencies] rand_chacha = { workspace = true, default-features = true } diff --git a/polkadot/node/network/protocol/src/grid_topology.rs b/polkadot/node/network/protocol/src/grid_topology.rs index 4dd7d29fc25cde5a010e6c4c28969c142dba6098..f4c1a07ba3c2a3a9138b5ef7b369d75f5bdac375 100644 --- a/polkadot/node/network/protocol/src/grid_topology.rs +++ b/polkadot/node/network/protocol/src/grid_topology.rs @@ -575,6 +575,22 @@ impl RequiredRouting { _ => false, } } + + /// Combine two required routing sets into one that would cover both routing modes. + pub fn combine(self, other: Self) -> Self { + match (self, other) { + (RequiredRouting::All, _) | (_, RequiredRouting::All) => RequiredRouting::All, + (RequiredRouting::GridXY, _) | (_, RequiredRouting::GridXY) => RequiredRouting::GridXY, + (RequiredRouting::GridX, RequiredRouting::GridY) | + (RequiredRouting::GridY, RequiredRouting::GridX) => RequiredRouting::GridXY, + (RequiredRouting::GridX, RequiredRouting::GridX) => RequiredRouting::GridX, + (RequiredRouting::GridY, RequiredRouting::GridY) => RequiredRouting::GridY, + (RequiredRouting::None, RequiredRouting::PendingTopology) | + (RequiredRouting::PendingTopology, RequiredRouting::None) => RequiredRouting::PendingTopology, + (RequiredRouting::None, _) | (RequiredRouting::PendingTopology, _) => other, + (_, RequiredRouting::None) | (_, RequiredRouting::PendingTopology) => self, + } + } } #[cfg(test)] @@ -587,6 +603,50 @@ mod tests { rand_chacha::ChaCha12Rng::seed_from_u64(12345) } + #[test] + fn test_required_routing_combine() { + assert_eq!(RequiredRouting::All.combine(RequiredRouting::None), RequiredRouting::All); + assert_eq!(RequiredRouting::All.combine(RequiredRouting::GridXY), RequiredRouting::All); + assert_eq!(RequiredRouting::GridXY.combine(RequiredRouting::All), RequiredRouting::All); + assert_eq!(RequiredRouting::None.combine(RequiredRouting::All), RequiredRouting::All); + assert_eq!(RequiredRouting::None.combine(RequiredRouting::None), RequiredRouting::None); + assert_eq!( + RequiredRouting::PendingTopology.combine(RequiredRouting::GridX), + RequiredRouting::GridX + ); + + assert_eq!( + RequiredRouting::GridX.combine(RequiredRouting::PendingTopology), + RequiredRouting::GridX + ); + assert_eq!(RequiredRouting::GridX.combine(RequiredRouting::GridY), RequiredRouting::GridXY); + assert_eq!(RequiredRouting::GridY.combine(RequiredRouting::GridX), RequiredRouting::GridXY); + assert_eq!( + RequiredRouting::GridXY.combine(RequiredRouting::GridXY), + RequiredRouting::GridXY + ); + assert_eq!(RequiredRouting::GridX.combine(RequiredRouting::GridX), RequiredRouting::GridX); + assert_eq!(RequiredRouting::GridY.combine(RequiredRouting::GridY), RequiredRouting::GridY); + + assert_eq!(RequiredRouting::None.combine(RequiredRouting::GridY), RequiredRouting::GridY); + assert_eq!(RequiredRouting::None.combine(RequiredRouting::GridX), RequiredRouting::GridX); + assert_eq!(RequiredRouting::None.combine(RequiredRouting::GridXY), RequiredRouting::GridXY); + + assert_eq!(RequiredRouting::GridY.combine(RequiredRouting::None), RequiredRouting::GridY); + assert_eq!(RequiredRouting::GridX.combine(RequiredRouting::None), RequiredRouting::GridX); + assert_eq!(RequiredRouting::GridXY.combine(RequiredRouting::None), RequiredRouting::GridXY); + + assert_eq!( + RequiredRouting::PendingTopology.combine(RequiredRouting::None), + RequiredRouting::PendingTopology + ); + + assert_eq!( + RequiredRouting::None.combine(RequiredRouting::PendingTopology), + RequiredRouting::PendingTopology + ); + } + #[test] fn test_random_routing_sample() { // This test is fragile as it relies on a specific ChaCha12Rng diff --git a/polkadot/node/network/statement-distribution/Cargo.toml b/polkadot/node/network/statement-distribution/Cargo.toml index de07937ffb0a5c32f2357781ca675a4ad200d3ab..8bd058b8c849c2e14d55366587f94021844669ce 100644 --- a/polkadot/node/network/statement-distribution/Cargo.toml +++ b/polkadot/node/network/statement-distribution/Cargo.toml @@ -5,46 +5,48 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +arrayvec = { workspace = true } +bitvec = { workspace = true, default-features = true } +codec = { features = ["derive"], workspace = true } +fatality = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } gum = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } -sp-staking = { workspace = true } -sp-keystore = { workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } +indexmap = { workspace = true } +polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-node-network-protocol = { workspace = true, default-features = true } -arrayvec = { workspace = true } -indexmap = { workspace = true } -codec = { features = ["derive"], workspace = true } +polkadot-primitives = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-staking = { workspace = true } thiserror = { workspace = true } -fatality = { workspace = true } -bitvec = { workspace = true, default-features = true } [dev-dependencies] -async-channel = { workspace = true } assert_matches = { workspace = true } -polkadot-node-subsystem-test-helpers = { workspace = true } -sp-authority-discovery = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } +async-channel = { workspace = true } futures-timer = { workspace = true } +polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-primitives = { workspace = true, features = ["test"] } polkadot-primitives-test-helpers = { workspace = true } -rand_chacha = { workspace = true, default-features = true } polkadot-subsystem-bench = { workspace = true } +rand_chacha = { workspace = true, default-features = true } rstest = { workspace = true } +sc-keystore = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } +sp-application-crypto = { workspace = true, default-features = true } +sp-authority-discovery = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } [[bench]] name = "statement-distribution-regression-bench" diff --git a/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs b/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs index bd6d4ebe755cde4d86be23a8cc24483741208fe9..06e52dbe3a4572b1511ff7046511f84294f75895 100644 --- a/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs +++ b/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs @@ -13,7 +13,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see <http://www.gnu.org/licenses/>. - +#![allow(dead_code)] use codec::Encode; use net_protocol::{filter_by_peer_version, peer_set::ProtocolVersion}; diff --git a/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs b/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs index d2fd016ec2f1ef6408590d8cf4b3368442d03499..92acfa870154f58f34884215e00af56fc38b7045 100644 --- a/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs +++ b/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs @@ -20,51 +20,27 @@ use super::*; use crate::{metrics::Metrics, *}; use assert_matches::assert_matches; -use codec::{Decode, Encode}; use futures::executor; -use futures_timer::Delay; use polkadot_node_network_protocol::{ - grid_topology::{SessionGridTopology, TopologyPeerInfo}, - peer_set::ValidationVersion, - request_response::{ - v1::{StatementFetchingRequest, StatementFetchingResponse}, - IncomingRequest, Recipient, ReqProtocolNames, Requests, - }, - view, ObservedRole, VersionedValidationProtocol, + peer_set::ValidationVersion, view, VersionedValidationProtocol, }; use polkadot_node_primitives::{ SignedFullStatementWithPVD, Statement, UncheckedSignedFullStatement, }; -use polkadot_node_subsystem::{ - messages::{ - network_bridge_event, AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest, - }, - RuntimeApiError, -}; -use polkadot_node_subsystem_test_helpers::mock::{make_ferdie_keystore, new_leaf}; -use polkadot_primitives::{ - Block, ExecutorParams, GroupIndex, Hash, HeadData, Id as ParaId, IndexedVec, NodeFeatures, - SessionInfo, ValidationCode, -}; +use polkadot_node_subsystem::messages::AllMessages; +use polkadot_primitives::{GroupIndex, Hash, HeadData, Id as ParaId, IndexedVec, SessionInfo}; use polkadot_primitives_test_helpers::{ - dummy_committed_candidate_receipt, dummy_committed_candidate_receipt_v2, dummy_hash, - AlwaysZeroRng, + dummy_committed_candidate_receipt, dummy_hash, AlwaysZeroRng, }; use sc_keystore::LocalKeystore; -use sc_network::ProtocolName; use sp_application_crypto::{sr25519::Pair, AppCrypto, Pair as TraitPair}; -use sp_authority_discovery::AuthorityPair; use sp_keyring::Sr25519Keyring; use sp_keystore::{Keystore, KeystorePtr}; -use std::{sync::Arc, time::Duration}; -use util::reputation::add_reputation; +use std::sync::Arc; // Some deterministic genesis hash for protocol names const GENESIS_HASH: Hash = Hash::repeat_byte(0xff); -const ASYNC_BACKING_DISABLED_ERROR: RuntimeApiError = - RuntimeApiError::NotSupported { runtime_api_name: "test-runtime" }; - fn dummy_pvd() -> PersistedValidationData { PersistedValidationData { parent_head: HeadData(vec![7, 8, 9]), @@ -737,2320 +713,6 @@ fn circulated_statement_goes_to_all_peers_with_view() { }); } -#[test] -fn receiving_from_one_sends_to_another_and_to_candidate_backing() { - const PARA_ID: ParaId = ParaId::new(1); - let hash_a = Hash::repeat_byte(1); - let pvd = dummy_pvd(); - - let candidate = { - let mut c = dummy_committed_candidate_receipt(dummy_hash()); - c.descriptor.relay_parent = hash_a; - c.descriptor.para_id = PARA_ID; - c.into() - }; - - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - - let validators = vec![ - Sr25519Keyring::Alice.pair(), - Sr25519Keyring::Bob.pair(), - Sr25519Keyring::Charlie.pair(), - ]; - - let session_info = make_session_info(validators, vec![]); - - let session_index = 1; - - let pool = sp_core::testing::TaskExecutor::new(); - let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); - - let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); - let (statement_req_receiver, _) = IncomingRequest::get_config_receiver::< - Block, - sc_network::NetworkWorker<Block, Hash>, - >(&req_protocol_names); - let (candidate_req_receiver, _) = IncomingRequest::get_config_receiver::< - Block, - sc_network::NetworkWorker<Block, Hash>, - >(&req_protocol_names); - - let bg = async move { - let s = StatementDistributionSubsystem { - keystore: Arc::new(LocalKeystore::in_memory()), - v1_req_receiver: Some(statement_req_receiver), - req_receiver: Some(candidate_req_receiver), - metrics: Default::default(), - rng: AlwaysZeroRng, - reputation: ReputationAggregator::new(|_| true), - }; - s.run(ctx).await.unwrap(); - }; - - let test_fut = async move { - // register our active heads. - handle - .send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate::start_work(new_leaf(hash_a, 1)), - ))) - .await; - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::AsyncBackingParams(tx)) - ) - if r == hash_a - => { - let _ = tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionIndexForChild(tx)) - ) - if r == hash_a - => { - let _ = tx.send(Ok(session_index)); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionInfo(sess_index, tx)) - ) - if r == hash_a && sess_index == session_index - => { - let _ = tx.send(Ok(Some(session_info))); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionExecutorParams(sess_index, tx)) - ) - if r == hash_a && sess_index == session_index - => { - let _ = tx.send(Ok(Some(ExecutorParams::default()))); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) - ) => { - si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); - } - ); - - // notify of peers and view - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - peer_a, - ObservedRole::Full, - ValidationVersion::V1.into(), - None, - ), - ), - }) - .await; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - peer_b, - ObservedRole::Full, - ValidationVersion::V1.into(), - None, - ), - ), - }) - .await; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(peer_a, view![hash_a]), - ), - }) - .await; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(peer_b, view![hash_a]), - ), - }) - .await; - - // receive a seconded statement from peer A. it should be propagated onwards to peer B and - // to candidate backing. - let statement = { - let signing_context = SigningContext { parent_hash: hash_a, session_index }; - - let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory()); - let alice_public = Keystore::sr25519_generate_new( - &*keystore, - ValidatorId::ID, - Some(&Sr25519Keyring::Alice.to_seed()), - ) - .unwrap(); - - SignedFullStatement::sign( - &keystore, - Statement::Seconded(candidate), - &signing_context, - ValidatorIndex(0), - &alice_public.into(), - ) - .ok() - .flatten() - .expect("should be signed") - }; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerMessage( - peer_a, - Versioned::V1(protocol_v1::StatementDistributionMessage::Statement( - hash_a, - statement.clone().into(), - )), - ), - ), - }) - .await; - - let statement_with_pvd = extend_statement_with_pvd(statement.clone(), pvd.clone()); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - hash, - RuntimeApiRequest::PersistedValidationData(para_id, assumption, tx), - )) if para_id == PARA_ID && - assumption == OccupiedCoreAssumption::Free && - hash == hash_a => - { - tx.send(Ok(Some(pvd))).unwrap(); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)) - ) if p == peer_a && r == BENEFIT_VALID_STATEMENT_FIRST.into() => {} - ); - assert_matches!( - handle.recv().await, - AllMessages::CandidateBacking( - CandidateBackingMessage::Statement(r, s) - ) if r == hash_a && s == statement_with_pvd => {} - ); - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendValidationMessage( - recipients, - Versioned::V1(protocol_v1::ValidationProtocol::StatementDistribution( - protocol_v1::StatementDistributionMessage::Statement(r, s) - )), - ) - ) => { - assert_eq!(recipients, vec![peer_b]); - assert_eq!(r, hash_a); - assert_eq!(s, statement.into()); - } - ); - handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await; - }; - - futures::pin_mut!(test_fut); - futures::pin_mut!(bg); - - executor::block_on(future::join(test_fut, bg)); -} - -#[test] -fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing() { - const PARA_ID: ParaId = ParaId::new(1); - let pvd = dummy_pvd(); - - sp_tracing::try_init_simple(); - let hash_a = Hash::repeat_byte(1); - let hash_b = Hash::repeat_byte(2); - - let candidate = { - let mut c = dummy_committed_candidate_receipt(dummy_hash()); - c.descriptor.relay_parent = hash_a; - c.descriptor.para_id = PARA_ID; - c.commitments.new_validation_code = Some(ValidationCode(vec![1, 2, 3])); - c - }; - - let peer_a = PeerId::random(); // Alice - let peer_b = PeerId::random(); // Bob - let peer_c = PeerId::random(); // Charlie - let peer_bad = PeerId::random(); // No validator - - let validators = vec![ - Sr25519Keyring::Alice.pair(), - Sr25519Keyring::Bob.pair(), - Sr25519Keyring::Charlie.pair(), - // We: - Sr25519Keyring::Ferdie.pair(), - ]; - - let session_info = make_session_info(validators, vec![vec![0, 1, 2, 4], vec![3]]); - - let session_index = 1; - - let pool = sp_core::testing::TaskExecutor::new(); - let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); - - let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); - let (statement_req_receiver, mut req_cfg) = IncomingRequest::get_config_receiver::< - Block, - sc_network::NetworkWorker<Block, Hash>, - >(&req_protocol_names); - let (candidate_req_receiver, _) = IncomingRequest::get_config_receiver::< - Block, - sc_network::NetworkWorker<Block, Hash>, - >(&req_protocol_names); - - let bg = async move { - let s = StatementDistributionSubsystem { - keystore: make_ferdie_keystore(), - v1_req_receiver: Some(statement_req_receiver), - req_receiver: Some(candidate_req_receiver), - metrics: Default::default(), - rng: AlwaysZeroRng, - reputation: ReputationAggregator::new(|_| true), - }; - s.run(ctx).await.unwrap(); - }; - - let test_fut = async move { - // register our active heads. - handle - .send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate::start_work(new_leaf(hash_a, 1)), - ))) - .await; - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::AsyncBackingParams(tx)) - ) - if r == hash_a - => { - let _ = tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionIndexForChild(tx)) - ) - if r == hash_a - => { - let _ = tx.send(Ok(session_index)); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionInfo(sess_index, tx)) - ) - if r == hash_a && sess_index == session_index - => { - let _ = tx.send(Ok(Some(session_info))); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionExecutorParams(sess_index, tx)) - ) - if r == hash_a && sess_index == session_index - => { - let _ = tx.send(Ok(Some(ExecutorParams::default()))); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) - ) => { - si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); - } - ); - - // notify of peers and view - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - peer_a, - ObservedRole::Full, - ValidationVersion::V1.into(), - Some(HashSet::from([Sr25519Keyring::Alice.public().into()])), - ), - ), - }) - .await; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - peer_b, - ObservedRole::Full, - ValidationVersion::V1.into(), - Some(HashSet::from([Sr25519Keyring::Bob.public().into()])), - ), - ), - }) - .await; - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - peer_c, - ObservedRole::Full, - ValidationVersion::V1.into(), - Some(HashSet::from([Sr25519Keyring::Charlie.public().into()])), - ), - ), - }) - .await; - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - peer_bad, - ObservedRole::Full, - ValidationVersion::V1.into(), - None, - ), - ), - }) - .await; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(peer_a, view![hash_a]), - ), - }) - .await; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(peer_b, view![hash_a]), - ), - }) - .await; - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(peer_c, view![hash_a]), - ), - }) - .await; - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(peer_bad, view![hash_a]), - ), - }) - .await; - - // receive a seconded statement from peer A, which does not provide the request data, - // then get that data from peer C. It should be propagated onwards to peer B and to - // candidate backing. - let statement = { - let signing_context = SigningContext { parent_hash: hash_a, session_index }; - - let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory()); - let alice_public = Keystore::sr25519_generate_new( - &*keystore, - ValidatorId::ID, - Some(&Sr25519Keyring::Alice.to_seed()), - ) - .unwrap(); - - SignedFullStatement::sign( - &keystore, - Statement::Seconded(candidate.clone().into()), - &signing_context, - ValidatorIndex(0), - &alice_public.into(), - ) - .ok() - .flatten() - .expect("should be signed") - }; - - let metadata = derive_metadata_assuming_seconded(hash_a, statement.clone().into()); - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerMessage( - peer_a, - Versioned::V1(protocol_v1::StatementDistributionMessage::LargeStatement( - metadata.clone(), - )), - ), - ), - }) - .await; - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetchingV1(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - assert_eq!(outgoing.peer, Recipient::Peer(peer_a)); - // Just drop request - should trigger error. - } - ); - - // There is a race between request handler asking for more peers and processing of the - // coming `PeerMessage`s, we want the request handler to ask first here for better test - // coverage: - Delay::new(Duration::from_millis(20)).await; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerMessage( - peer_c, - Versioned::V1(protocol_v1::StatementDistributionMessage::LargeStatement( - metadata.clone(), - )), - ), - ), - }) - .await; - - // Malicious peer: - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerMessage( - peer_bad, - Versioned::V1(protocol_v1::StatementDistributionMessage::LargeStatement( - metadata.clone(), - )), - ), - ), - }) - .await; - - // Let c fail once too: - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetchingV1(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - assert_eq!(outgoing.peer, Recipient::Peer(peer_c)); - } - ); - - // a fails again: - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetchingV1(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - // On retry, we should have reverse order: - assert_eq!(outgoing.peer, Recipient::Peer(peer_a)); - } - ); - - // Send invalid response (all other peers have been tried now): - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetchingV1(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - assert_eq!(outgoing.peer, Recipient::Peer(peer_bad)); - let bad_candidate = { - let mut bad = candidate.clone(); - bad.descriptor.para_id = 0xeadbeaf.into(); - bad.into() - }; - let response = StatementFetchingResponse::Statement(bad_candidate); - outgoing.pending_response.send(Ok((response.encode(), ProtocolName::from("")))).unwrap(); - } - ); - - // Should get punished and never tried again: - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)) - ) if p == peer_bad && r == COST_WRONG_HASH.into() => {} - ); - - // a is tried again (retried in reverse order): - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetchingV1(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - // On retry, we should have reverse order: - assert_eq!(outgoing.peer, Recipient::Peer(peer_a)); - } - ); - - // c succeeds now: - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetchingV1(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - // On retry, we should have reverse order: - assert_eq!(outgoing.peer, Recipient::Peer(peer_c)); - let response = StatementFetchingResponse::Statement(candidate.clone().into()); - outgoing.pending_response.send(Ok((response.encode(), ProtocolName::from("")))).unwrap(); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)) - ) if p == peer_a && r == COST_FETCH_FAIL.into() => {} - ); - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)) - ) if p == peer_c && r == BENEFIT_VALID_RESPONSE.into() => {} - ); - - let statement_with_pvd = extend_statement_with_pvd(statement.clone(), pvd.clone()); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - hash, - RuntimeApiRequest::PersistedValidationData(para_id, assumption, tx), - )) if para_id == PARA_ID && - assumption == OccupiedCoreAssumption::Free && - hash == hash_a => - { - tx.send(Ok(Some(pvd))).unwrap(); - } - ); - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)) - ) if p == peer_a && r == BENEFIT_VALID_STATEMENT_FIRST.into() => {} - ); - - assert_matches!( - handle.recv().await, - AllMessages::CandidateBacking( - CandidateBackingMessage::Statement(r, s) - ) if r == hash_a && s == statement_with_pvd => {} - ); - - // Now messages should go out: - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendValidationMessage( - mut recipients, - Versioned::V1(protocol_v1::ValidationProtocol::StatementDistribution( - protocol_v1::StatementDistributionMessage::LargeStatement(meta) - )), - ) - ) => { - gum::debug!( - target: LOG_TARGET, - ?recipients, - "Recipients received" - ); - recipients.sort(); - let mut expected = vec![peer_b, peer_c, peer_bad]; - expected.sort(); - assert_eq!(recipients, expected); - assert_eq!(meta.relay_parent, hash_a); - assert_eq!(meta.candidate_hash, statement.payload().candidate_hash()); - assert_eq!(meta.signed_by, statement.validator_index()); - assert_eq!(&meta.signature, statement.signature()); - } - ); - - // Now that it has the candidate it should answer requests accordingly (even after a - // failed request): - - // Failing request first (wrong relay parent hash): - let (pending_response, response_rx) = oneshot::channel(); - let inner_req = StatementFetchingRequest { - relay_parent: hash_b, - candidate_hash: metadata.candidate_hash, - }; - let req = sc_network::config::IncomingRequest { - peer: peer_b, - payload: inner_req.encode(), - pending_response, - }; - req_cfg.inbound_queue.as_mut().unwrap().send(req).await.unwrap(); - assert_matches!( - response_rx.await.unwrap().result, - Err(()) => {} - ); - - // Another failing request (peer_a never received a statement from us, so it is not - // allowed to request the data): - let (pending_response, response_rx) = oneshot::channel(); - let inner_req = StatementFetchingRequest { - relay_parent: metadata.relay_parent, - candidate_hash: metadata.candidate_hash, - }; - let req = sc_network::config::IncomingRequest { - peer: peer_a, - payload: inner_req.encode(), - pending_response, - }; - req_cfg.inbound_queue.as_mut().unwrap().send(req).await.unwrap(); - assert_matches!( - response_rx.await.unwrap().result, - Err(()) => {} - ); - - // And now the succeeding request from peer_b: - let (pending_response, response_rx) = oneshot::channel(); - let inner_req = StatementFetchingRequest { - relay_parent: metadata.relay_parent, - candidate_hash: metadata.candidate_hash, - }; - let req = sc_network::config::IncomingRequest { - peer: peer_b, - payload: inner_req.encode(), - pending_response, - }; - req_cfg.inbound_queue.as_mut().unwrap().send(req).await.unwrap(); - let StatementFetchingResponse::Statement(committed) = - Decode::decode(&mut response_rx.await.unwrap().result.unwrap().as_ref()).unwrap(); - assert_eq!(committed, candidate.into()); - - handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await; - }; - - futures::pin_mut!(test_fut); - futures::pin_mut!(bg); - - executor::block_on(future::join(test_fut, bg)); -} - -#[test] -fn delay_reputation_changes() { - sp_tracing::try_init_simple(); - let hash_a = Hash::repeat_byte(1); - let pvd = dummy_pvd(); - - let candidate = { - let mut c = dummy_committed_candidate_receipt(dummy_hash()); - c.descriptor.relay_parent = hash_a; - c.descriptor.para_id = 1.into(); - c.commitments.new_validation_code = Some(ValidationCode(vec![1, 2, 3])); - c - }; - - let peer_a = PeerId::random(); // Alice - let peer_b = PeerId::random(); // Bob - let peer_c = PeerId::random(); // Charlie - let peer_bad = PeerId::random(); // No validator - - let validators = vec![ - Sr25519Keyring::Alice.pair(), - Sr25519Keyring::Bob.pair(), - Sr25519Keyring::Charlie.pair(), - // We: - Sr25519Keyring::Ferdie.pair(), - ]; - - let session_info = make_session_info(validators, vec![vec![0, 1, 2, 4], vec![3]]); - - let session_index = 1; - - let pool = sp_core::testing::TaskExecutor::new(); - let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); - - let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); - let (statement_req_receiver, _) = IncomingRequest::get_config_receiver::< - Block, - sc_network::NetworkWorker<Block, Hash>, - >(&req_protocol_names); - let (candidate_req_receiver, _) = IncomingRequest::get_config_receiver::< - Block, - sc_network::NetworkWorker<Block, Hash>, - >(&req_protocol_names); - - let reputation_interval = Duration::from_millis(100); - - let bg = async move { - let s = StatementDistributionSubsystem { - keystore: make_ferdie_keystore(), - v1_req_receiver: Some(statement_req_receiver), - req_receiver: Some(candidate_req_receiver), - metrics: Default::default(), - rng: AlwaysZeroRng, - reputation: ReputationAggregator::new(|_| false), - }; - s.run_inner(ctx, reputation_interval).await.unwrap(); - }; - - let test_fut = async move { - // register our active heads. - handle - .send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate::start_work(new_leaf(hash_a, 1)), - ))) - .await; - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::AsyncBackingParams(tx)) - ) - if r == hash_a - => { - let _ = tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionIndexForChild(tx)) - ) - if r == hash_a - => { - let _ = tx.send(Ok(session_index)); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionInfo(sess_index, tx)) - ) - if r == hash_a && sess_index == session_index - => { - let _ = tx.send(Ok(Some(session_info))); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionExecutorParams(sess_index, tx)) - ) - if r == hash_a && sess_index == session_index - => { - let _ = tx.send(Ok(Some(ExecutorParams::default()))); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) - ) => { - si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); - } - ); - - // notify of peers and view - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - peer_a, - ObservedRole::Full, - ValidationVersion::V1.into(), - Some(HashSet::from([Sr25519Keyring::Alice.public().into()])), - ), - ), - }) - .await; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - peer_b, - ObservedRole::Full, - ValidationVersion::V1.into(), - Some(HashSet::from([Sr25519Keyring::Bob.public().into()])), - ), - ), - }) - .await; - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - peer_c, - ObservedRole::Full, - ValidationVersion::V1.into(), - Some(HashSet::from([Sr25519Keyring::Charlie.public().into()])), - ), - ), - }) - .await; - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - peer_bad, - ObservedRole::Full, - ValidationVersion::V1.into(), - None, - ), - ), - }) - .await; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(peer_a, view![hash_a]), - ), - }) - .await; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(peer_b, view![hash_a]), - ), - }) - .await; - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(peer_c, view![hash_a]), - ), - }) - .await; - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(peer_bad, view![hash_a]), - ), - }) - .await; - - // receive a seconded statement from peer A, which does not provide the request data, - // then get that data from peer C. It should be propagated onwards to peer B and to - // candidate backing. - let statement = { - let signing_context = SigningContext { parent_hash: hash_a, session_index }; - - let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory()); - let alice_public = Keystore::sr25519_generate_new( - &*keystore, - ValidatorId::ID, - Some(&Sr25519Keyring::Alice.to_seed()), - ) - .unwrap(); - - SignedFullStatement::sign( - &keystore, - Statement::Seconded(candidate.clone().into()), - &signing_context, - ValidatorIndex(0), - &alice_public.into(), - ) - .ok() - .flatten() - .expect("should be signed") - }; - - let metadata = derive_metadata_assuming_seconded(hash_a, statement.clone().into()); - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerMessage( - peer_a, - Versioned::V1(protocol_v1::StatementDistributionMessage::LargeStatement( - metadata.clone(), - )), - ), - ), - }) - .await; - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetchingV1(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - assert_eq!(outgoing.peer, Recipient::Peer(peer_a)); - // Just drop request - should trigger error. - } - ); - - // There is a race between request handler asking for more peers and processing of the - // coming `PeerMessage`s, we want the request handler to ask first here for better test - // coverage: - Delay::new(Duration::from_millis(20)).await; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerMessage( - peer_c, - Versioned::V1(protocol_v1::StatementDistributionMessage::LargeStatement( - metadata.clone(), - )), - ), - ), - }) - .await; - - // Malicious peer: - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerMessage( - peer_bad, - Versioned::V1(protocol_v1::StatementDistributionMessage::LargeStatement( - metadata.clone(), - )), - ), - ), - }) - .await; - - // Let c fail once too: - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetchingV1(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - assert_eq!(outgoing.peer, Recipient::Peer(peer_c)); - } - ); - - // a fails again: - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetchingV1(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - // On retry, we should have reverse order: - assert_eq!(outgoing.peer, Recipient::Peer(peer_a)); - } - ); - - // Send invalid response (all other peers have been tried now): - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetchingV1(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - assert_eq!(outgoing.peer, Recipient::Peer(peer_bad)); - let bad_candidate = { - let mut bad = candidate.clone(); - bad.descriptor.para_id = 0xeadbeaf.into(); - bad - }; - let response = StatementFetchingResponse::Statement(bad_candidate.into()); - outgoing.pending_response.send(Ok((response.encode(), ProtocolName::from("")))).unwrap(); - } - ); - - // a is tried again (retried in reverse order): - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetchingV1(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - // On retry, we should have reverse order: - assert_eq!(outgoing.peer, Recipient::Peer(peer_a)); - } - ); - - // c succeeds now: - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendRequests( - mut reqs, IfDisconnected::ImmediateError - ) - ) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetchingV1(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - // On retry, we should have reverse order: - assert_eq!(outgoing.peer, Recipient::Peer(peer_c)); - let response = StatementFetchingResponse::Statement(candidate.clone().into()); - outgoing.pending_response.send(Ok((response.encode(), ProtocolName::from("")))).unwrap(); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - hash, - RuntimeApiRequest::PersistedValidationData(_, assumption, tx), - )) if assumption == OccupiedCoreAssumption::Free && hash == hash_a => - { - tx.send(Ok(Some(pvd))).unwrap(); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::CandidateBacking(CandidateBackingMessage::Statement(..)) - ); - - // Now messages should go out: - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendValidationMessage( - mut recipients, - Versioned::V1(protocol_v1::ValidationProtocol::StatementDistribution( - protocol_v1::StatementDistributionMessage::LargeStatement(meta) - )), - ) - ) => { - gum::debug!( - target: LOG_TARGET, - ?recipients, - "Recipients received" - ); - recipients.sort(); - let mut expected = vec![peer_b, peer_c, peer_bad]; - expected.sort(); - assert_eq!(recipients, expected); - assert_eq!(meta.relay_parent, hash_a); - assert_eq!(meta.candidate_hash, statement.payload().candidate_hash()); - assert_eq!(meta.signed_by, statement.validator_index()); - assert_eq!(&meta.signature, statement.signature()); - } - ); - - // Wait enough to fire reputation delay - futures_timer::Delay::new(reputation_interval).await; - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Batch(v)) - ) => { - let mut expected_change = HashMap::new(); - for rep in vec![COST_FETCH_FAIL, BENEFIT_VALID_STATEMENT_FIRST] { - add_reputation(&mut expected_change, peer_a, rep) - } - for rep in vec![BENEFIT_VALID_RESPONSE, BENEFIT_VALID_STATEMENT] { - add_reputation(&mut expected_change, peer_c, rep) - } - for rep in vec![COST_WRONG_HASH, BENEFIT_VALID_STATEMENT] { - add_reputation(&mut expected_change, peer_bad, rep) - } - assert_eq!(v, expected_change); - } - ); - - handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await; - }; - - futures::pin_mut!(test_fut); - futures::pin_mut!(bg); - - executor::block_on(future::join(test_fut, bg)); -} - -#[test] -fn share_prioritizes_backing_group() { - sp_tracing::try_init_simple(); - let hash_a = Hash::repeat_byte(1); - - let candidate = { - let mut c = dummy_committed_candidate_receipt(dummy_hash()); - c.descriptor.relay_parent = hash_a; - c.descriptor.para_id = 1.into(); - c.commitments.new_validation_code = Some(ValidationCode(vec![1, 2, 3])); - c - }; - - let peer_a = PeerId::random(); // Alice - let peer_b = PeerId::random(); // Bob - let peer_c = PeerId::random(); // Charlie - let peer_bad = PeerId::random(); // No validator - let peer_other_group = PeerId::random(); //Ferdie - - let mut validators = vec![ - Sr25519Keyring::Alice.pair(), - Sr25519Keyring::Bob.pair(), - Sr25519Keyring::Charlie.pair(), - // other group - Sr25519Keyring::Dave.pair(), - // We: - Sr25519Keyring::Ferdie.pair(), - ]; - - // Strictly speaking we only need MIN_GOSSIP_PEERS - 3 to make sure only priority peers - // will be served, but by using a larger value we test for overflow errors: - let dummy_count = MIN_GOSSIP_PEERS; - - // We artificially inflate our group, so there won't be any free slots for other peers. (We - // want to test that our group is prioritized): - let dummy_pairs: Vec<_> = - std::iter::repeat_with(|| Pair::generate().0).take(dummy_count).collect(); - let dummy_peers: Vec<_> = - std::iter::repeat_with(|| PeerId::random()).take(dummy_count).collect(); - - validators = validators.into_iter().chain(dummy_pairs.clone()).collect(); - - let mut first_group = vec![0, 1, 2, 4]; - first_group.append(&mut (0..dummy_count as u32).map(|v| v + 5).collect()); - let session_info = make_session_info(validators, vec![first_group, vec![3]]); - - let session_index = 1; - - let pool = sp_core::testing::TaskExecutor::new(); - let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); - - let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); - let (statement_req_receiver, mut req_cfg) = IncomingRequest::get_config_receiver::< - Block, - sc_network::NetworkWorker<Block, Hash>, - >(&req_protocol_names); - let (candidate_req_receiver, _) = IncomingRequest::get_config_receiver::< - Block, - sc_network::NetworkWorker<Block, Hash>, - >(&req_protocol_names); - - let bg = async move { - let s = StatementDistributionSubsystem { - keystore: make_ferdie_keystore(), - v1_req_receiver: Some(statement_req_receiver), - req_receiver: Some(candidate_req_receiver), - metrics: Default::default(), - rng: AlwaysZeroRng, - reputation: ReputationAggregator::new(|_| true), - }; - s.run(ctx).await.unwrap(); - }; - - let test_fut = async move { - // register our active heads. - handle - .send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate::start_work(new_leaf(hash_a, 1)), - ))) - .await; - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::AsyncBackingParams(tx)) - ) - if r == hash_a - => { - let _ = tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionIndexForChild(tx)) - ) - if r == hash_a - => { - let _ = tx.send(Ok(session_index)); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionInfo(sess_index, tx)) - ) - if r == hash_a && sess_index == session_index - => { - let _ = tx.send(Ok(Some(session_info))); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionExecutorParams(sess_index, tx)) - ) - if r == hash_a && sess_index == session_index - => { - let _ = tx.send(Ok(Some(ExecutorParams::default()))); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) - ) => { - si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); - } - ); - - // notify of dummy peers and view - for (peer, pair) in dummy_peers.clone().into_iter().zip(dummy_pairs) { - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - peer, - ObservedRole::Full, - ValidationVersion::V1.into(), - Some(HashSet::from([pair.public().into()])), - ), - ), - }) - .await; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(peer, view![hash_a]), - ), - }) - .await; - } - - // notify of peers and view - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - peer_a, - ObservedRole::Full, - ValidationVersion::V1.into(), - Some(HashSet::from([Sr25519Keyring::Alice.public().into()])), - ), - ), - }) - .await; - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - peer_b, - ObservedRole::Full, - ValidationVersion::V1.into(), - Some(HashSet::from([Sr25519Keyring::Bob.public().into()])), - ), - ), - }) - .await; - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - peer_c, - ObservedRole::Full, - ValidationVersion::V1.into(), - Some(HashSet::from([Sr25519Keyring::Charlie.public().into()])), - ), - ), - }) - .await; - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - peer_bad, - ObservedRole::Full, - ValidationVersion::V1.into(), - None, - ), - ), - }) - .await; - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - peer_other_group, - ObservedRole::Full, - ValidationVersion::V1.into(), - Some(HashSet::from([Sr25519Keyring::Dave.public().into()])), - ), - ), - }) - .await; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(peer_a, view![hash_a]), - ), - }) - .await; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(peer_b, view![hash_a]), - ), - }) - .await; - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(peer_c, view![hash_a]), - ), - }) - .await; - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(peer_bad, view![hash_a]), - ), - }) - .await; - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(peer_other_group, view![hash_a]), - ), - }) - .await; - - // receive a seconded statement from peer A, which does not provide the request data, - // then get that data from peer C. It should be propagated onwards to peer B and to - // candidate backing. - let statement = { - let signing_context = SigningContext { parent_hash: hash_a, session_index }; - - let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory()); - let ferdie_public = Keystore::sr25519_generate_new( - &*keystore, - ValidatorId::ID, - Some(&Sr25519Keyring::Ferdie.to_seed()), - ) - .unwrap(); - - // note: this is ignored by legacy-v1 code. - let pvd = PersistedValidationData { - parent_head: HeadData::from(vec![1, 2, 3]), - relay_parent_number: 0, - relay_parent_storage_root: Hash::repeat_byte(42), - max_pov_size: 100, - }; - - SignedFullStatementWithPVD::sign( - &keystore, - Statement::Seconded(candidate.clone().into()).supply_pvd(pvd), - &signing_context, - ValidatorIndex(4), - &ferdie_public.into(), - ) - .ok() - .flatten() - .expect("should be signed") - }; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::Share(hash_a, statement.clone()), - }) - .await; - - let statement = StatementWithPVD::drop_pvd_from_signed(statement); - let metadata = derive_metadata_assuming_seconded(hash_a, statement.clone().into()); - - // Messages should go out: - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendValidationMessage( - mut recipients, - Versioned::V1(protocol_v1::ValidationProtocol::StatementDistribution( - protocol_v1::StatementDistributionMessage::LargeStatement(meta) - )), - ) - ) => { - gum::debug!( - target: LOG_TARGET, - ?recipients, - "Recipients received" - ); - recipients.sort(); - // We expect only our backing group to be the recipients, du to the inflated - // test group above: - let mut expected: Vec<_> = vec![peer_a, peer_b, peer_c].into_iter().chain(dummy_peers).collect(); - expected.sort(); - assert_eq!(recipients.len(), expected.len()); - assert_eq!(recipients, expected); - assert_eq!(meta.relay_parent, hash_a); - assert_eq!(meta.candidate_hash, statement.payload().candidate_hash()); - assert_eq!(meta.signed_by, statement.validator_index()); - assert_eq!(&meta.signature, statement.signature()); - } - ); - - // Now that it has the candidate it should answer requests accordingly: - - let (pending_response, response_rx) = oneshot::channel(); - let inner_req = StatementFetchingRequest { - relay_parent: metadata.relay_parent, - candidate_hash: metadata.candidate_hash, - }; - let req = sc_network::config::IncomingRequest { - peer: peer_b, - payload: inner_req.encode(), - pending_response, - }; - req_cfg.inbound_queue.as_mut().unwrap().send(req).await.unwrap(); - let StatementFetchingResponse::Statement(committed) = - Decode::decode(&mut response_rx.await.unwrap().result.unwrap().as_ref()).unwrap(); - assert_eq!(committed, candidate.into()); - - handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await; - }; - - futures::pin_mut!(test_fut); - futures::pin_mut!(bg); - - executor::block_on(future::join(test_fut, bg)); -} - -#[test] -fn peer_cant_flood_with_large_statements() { - sp_tracing::try_init_simple(); - let hash_a = Hash::repeat_byte(1); - - let candidate = { - let mut c = dummy_committed_candidate_receipt(dummy_hash()); - c.descriptor.relay_parent = hash_a; - c.descriptor.para_id = 1.into(); - c.commitments.new_validation_code = Some(ValidationCode(vec![1, 2, 3])); - c - }; - - let peer_a = PeerId::random(); // Alice - - let validators = vec![ - Sr25519Keyring::Alice.pair(), - Sr25519Keyring::Bob.pair(), - Sr25519Keyring::Charlie.pair(), - // other group - Sr25519Keyring::Dave.pair(), - // We: - Sr25519Keyring::Ferdie.pair(), - ]; - - let first_group = vec![0, 1, 2, 4]; - let session_info = make_session_info(validators, vec![first_group, vec![3]]); - - let session_index = 1; - - let pool = sp_core::testing::TaskExecutor::new(); - let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); - - let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); - let (statement_req_receiver, _) = IncomingRequest::get_config_receiver::< - Block, - sc_network::NetworkWorker<Block, Hash>, - >(&req_protocol_names); - let (candidate_req_receiver, _) = IncomingRequest::get_config_receiver::< - Block, - sc_network::NetworkWorker<Block, Hash>, - >(&req_protocol_names); - let bg = async move { - let s = StatementDistributionSubsystem { - keystore: make_ferdie_keystore(), - v1_req_receiver: Some(statement_req_receiver), - req_receiver: Some(candidate_req_receiver), - metrics: Default::default(), - rng: AlwaysZeroRng, - reputation: ReputationAggregator::new(|_| true), - }; - s.run(ctx).await.unwrap(); - }; - - let test_fut = async move { - // register our active heads. - handle - .send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate::start_work(new_leaf(hash_a, 1)), - ))) - .await; - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::AsyncBackingParams(tx)) - ) - if r == hash_a - => { - let _ = tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionIndexForChild(tx)) - ) - if r == hash_a - => { - let _ = tx.send(Ok(session_index)); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionInfo(sess_index, tx)) - ) - if r == hash_a && sess_index == session_index - => { - let _ = tx.send(Ok(Some(session_info))); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionExecutorParams(sess_index, tx)) - ) - if r == hash_a && sess_index == session_index - => { - let _ = tx.send(Ok(Some(ExecutorParams::default()))); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) - ) => { - si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); - } - ); - - // notify of peers and view - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - peer_a, - ObservedRole::Full, - ValidationVersion::V1.into(), - Some(HashSet::from([Sr25519Keyring::Alice.public().into()])), - ), - ), - }) - .await; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(peer_a, view![hash_a]), - ), - }) - .await; - - // receive a seconded statement from peer A. - let statement = { - let signing_context = SigningContext { parent_hash: hash_a, session_index }; - - let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory()); - let alice_public = Keystore::sr25519_generate_new( - &*keystore, - ValidatorId::ID, - Some(&Sr25519Keyring::Alice.to_seed()), - ) - .unwrap(); - - SignedFullStatement::sign( - &keystore, - Statement::Seconded(candidate.clone().into()), - &signing_context, - ValidatorIndex(0), - &alice_public.into(), - ) - .ok() - .flatten() - .expect("should be signed") - }; - - let metadata = derive_metadata_assuming_seconded(hash_a, statement.clone().into()); - - for _ in 0..MAX_LARGE_STATEMENTS_PER_SENDER + 1 { - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerMessage( - peer_a, - Versioned::V1( - protocol_v1::StatementDistributionMessage::LargeStatement( - metadata.clone(), - ), - ), - ), - ), - }) - .await; - } - - // We should try to fetch the data and punish the peer (but we don't know what comes - // first): - let mut requested = false; - let mut punished = false; - for _ in 0..2 { - match handle.recv().await { - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendRequests( - mut reqs, - IfDisconnected::ImmediateError, - )) => { - let reqs = reqs.pop().unwrap(); - let outgoing = match reqs { - Requests::StatementFetchingV1(outgoing) => outgoing, - _ => panic!("Unexpected request"), - }; - let req = outgoing.payload; - assert_eq!(req.relay_parent, metadata.relay_parent); - assert_eq!(req.candidate_hash, metadata.candidate_hash); - assert_eq!(outgoing.peer, Recipient::Peer(peer_a)); - // Just drop request - should trigger error. - requested = true; - }, - - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer( - ReportPeerMessage::Single(p, r), - )) if p == peer_a && r == COST_APPARENT_FLOOD.into() => { - punished = true; - }, - - m => panic!("Unexpected message: {:?}", m), - } - } - assert!(requested, "large data has not been requested."); - assert!(punished, "Peer should have been punished for flooding."); - - handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await; - }; - - futures::pin_mut!(test_fut); - futures::pin_mut!(bg); - - executor::block_on(future::join(test_fut, bg)); -} - -// This test addresses an issue when received knowledge is not updated on a -// subsequent `Seconded` statements -// See https://github.com/paritytech/polkadot/pull/5177 -#[test] -fn handle_multiple_seconded_statements() { - let relay_parent_hash = Hash::repeat_byte(1); - let pvd = dummy_pvd(); - - let candidate = dummy_committed_candidate_receipt_v2(relay_parent_hash); - let candidate_hash = candidate.hash(); - - // We want to ensure that our peers are not lucky - let mut all_peers: Vec<PeerId> = Vec::with_capacity(MIN_GOSSIP_PEERS + 4); - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - assert_ne!(peer_a, peer_b); - - for _ in 0..MIN_GOSSIP_PEERS + 2 { - all_peers.push(PeerId::random()); - } - all_peers.push(peer_a); - all_peers.push(peer_b); - - let mut lucky_peers = all_peers.clone(); - util::choose_random_subset_with_rng( - |_| false, - &mut lucky_peers, - &mut AlwaysZeroRng, - MIN_GOSSIP_PEERS, - ); - lucky_peers.sort(); - assert_eq!(lucky_peers.len(), MIN_GOSSIP_PEERS); - assert!(!lucky_peers.contains(&peer_a)); - assert!(!lucky_peers.contains(&peer_b)); - - let validators = vec![ - Sr25519Keyring::Alice.pair(), - Sr25519Keyring::Bob.pair(), - Sr25519Keyring::Charlie.pair(), - ]; - - let session_info = make_session_info(validators, vec![]); - - let session_index = 1; - - let pool = sp_core::testing::TaskExecutor::new(); - let (ctx, mut handle) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); - - let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); - let (statement_req_receiver, _) = IncomingRequest::get_config_receiver::< - Block, - sc_network::NetworkWorker<Block, Hash>, - >(&req_protocol_names); - let (candidate_req_receiver, _) = IncomingRequest::get_config_receiver::< - Block, - sc_network::NetworkWorker<Block, Hash>, - >(&req_protocol_names); - - let virtual_overseer_fut = async move { - let s = StatementDistributionSubsystem { - keystore: Arc::new(LocalKeystore::in_memory()), - v1_req_receiver: Some(statement_req_receiver), - req_receiver: Some(candidate_req_receiver), - metrics: Default::default(), - rng: AlwaysZeroRng, - reputation: ReputationAggregator::new(|_| true), - }; - s.run(ctx).await.unwrap(); - }; - - let test_fut = async move { - // register our active heads. - handle - .send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves( - ActiveLeavesUpdate::start_work(new_leaf(relay_parent_hash, 1)), - ))) - .await; - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::AsyncBackingParams(tx)) - ) - if r == relay_parent_hash - => { - let _ = tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionIndexForChild(tx)) - ) - if r == relay_parent_hash - => { - let _ = tx.send(Ok(session_index)); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionInfo(sess_index, tx)) - ) - if r == relay_parent_hash && sess_index == session_index - => { - let _ = tx.send(Ok(Some(session_info))); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(r, RuntimeApiRequest::SessionExecutorParams(sess_index, tx)) - ) - if r == relay_parent_hash && sess_index == session_index - => { - let _ = tx.send(Ok(Some(ExecutorParams::default()))); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) - ) => { - si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); - } - ); - // notify of peers and view - for peer in all_peers.iter() { - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerConnected( - *peer, - ObservedRole::Full, - ValidationVersion::V1.into(), - None, - ), - ), - }) - .await; - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerViewChange(*peer, view![relay_parent_hash]), - ), - }) - .await; - } - - // Set up a topology which puts peers a & b in a column together. - let gossip_topology = { - // create a lucky_peers+1 * lucky_peers+1 grid topology where we are at index 2, sharing - // a row with peer_a (0) and peer_b (1) and a column with all the lucky peers. - // the rest is filled with junk. - // This is an absolute garbage hack depending on quirks of the implementation - // and not on sound architecture. - - let n_lucky = lucky_peers.len(); - let dim = n_lucky + 1; - let grid_size = dim * dim; - let topology_peer_info: Vec<_> = (0..grid_size) - .map(|i| { - if i == 0 { - TopologyPeerInfo { - peer_ids: vec![peer_a], - validator_index: ValidatorIndex(0), - discovery_id: AuthorityPair::generate().0.public(), - } - } else if i == 1 { - TopologyPeerInfo { - peer_ids: vec![peer_b], - validator_index: ValidatorIndex(1), - discovery_id: AuthorityPair::generate().0.public(), - } - } else if i == 2 { - TopologyPeerInfo { - peer_ids: vec![], - validator_index: ValidatorIndex(2), - discovery_id: AuthorityPair::generate().0.public(), - } - } else if (i - 2) % dim == 0 { - let lucky_index = ((i - 2) / dim) - 1; - TopologyPeerInfo { - peer_ids: vec![lucky_peers[lucky_index]], - validator_index: ValidatorIndex(i as _), - discovery_id: AuthorityPair::generate().0.public(), - } - } else { - TopologyPeerInfo { - peer_ids: vec![PeerId::random()], - validator_index: ValidatorIndex(i as _), - discovery_id: AuthorityPair::generate().0.public(), - } - } - }) - .collect(); - - // also a hack: this is only required to be accurate for - // the validator indices we compute grid neighbors for. - let mut shuffled_indices = vec![0; grid_size]; - shuffled_indices[2] = 2; - - // Some sanity checking to make sure this hack is set up correctly. - let topology = SessionGridTopology::new(shuffled_indices, topology_peer_info); - let grid_neighbors = topology.compute_grid_neighbors_for(ValidatorIndex(2)).unwrap(); - assert_eq!(grid_neighbors.peers_x.len(), 25); - assert!(grid_neighbors.peers_x.contains(&peer_a)); - assert!(grid_neighbors.peers_x.contains(&peer_b)); - assert!(!grid_neighbors.peers_y.contains(&peer_b)); - assert!(!grid_neighbors.route_to_peer(RequiredRouting::GridY, &peer_b)); - assert_eq!(grid_neighbors.peers_y.len(), lucky_peers.len()); - for lucky in &lucky_peers { - assert!(grid_neighbors.peers_y.contains(lucky)); - } - - network_bridge_event::NewGossipTopology { - session: 1, - topology, - local_index: Some(ValidatorIndex(2)), - } - }; - - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::NewGossipTopology(gossip_topology), - ), - }) - .await; - - // receive a seconded statement from peer A. it should be propagated onwards to peer B and - // to candidate backing. - let statement = { - let signing_context = SigningContext { parent_hash: relay_parent_hash, session_index }; - - let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory()); - let alice_public = Keystore::sr25519_generate_new( - &*keystore, - ValidatorId::ID, - Some(&Sr25519Keyring::Alice.to_seed()), - ) - .unwrap(); - - SignedFullStatement::sign( - &keystore, - Statement::Seconded(candidate.clone()), - &signing_context, - ValidatorIndex(0), - &alice_public.into(), - ) - .ok() - .flatten() - .expect("should be signed") - }; - - // `PeerA` sends a `Seconded` message - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerMessage( - peer_a, - Versioned::V1(protocol_v1::StatementDistributionMessage::Statement( - relay_parent_hash, - statement.clone().into(), - )), - ), - ), - }) - .await; - - let statement_with_pvd = extend_statement_with_pvd(statement.clone(), pvd.clone()); - - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::PersistedValidationData(_, assumption, tx), - )) if assumption == OccupiedCoreAssumption::Free => { - tx.send(Ok(Some(pvd.clone()))).unwrap(); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)) - ) => { - assert_eq!(p, peer_a); - assert_eq!(r, BENEFIT_VALID_STATEMENT_FIRST.into()); - } - ); - - // After the first valid statement, we expect messages to be circulated - assert_matches!( - handle.recv().await, - AllMessages::CandidateBacking( - CandidateBackingMessage::Statement(r, s) - ) => { - assert_eq!(r, relay_parent_hash); - assert_eq!(s, statement_with_pvd); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendValidationMessage( - recipients, - Versioned::V1(protocol_v1::ValidationProtocol::StatementDistribution( - protocol_v1::StatementDistributionMessage::Statement(r, s) - )), - ) - ) => { - assert!(!recipients.contains(&peer_b)); - assert_eq!(r, relay_parent_hash); - assert_eq!(s, statement.clone().into()); - } - ); - - // `PeerB` sends a `Seconded` message: valid but known - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerMessage( - peer_b, - Versioned::V1(protocol_v1::StatementDistributionMessage::Statement( - relay_parent_hash, - statement.clone().into(), - )), - ), - ), - }) - .await; - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)) - ) => { - assert_eq!(p, peer_b); - assert_eq!(r, BENEFIT_VALID_STATEMENT.into()); - } - ); - - // Create a `Valid` statement - let statement = { - let signing_context = SigningContext { parent_hash: relay_parent_hash, session_index }; - - let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory()); - let alice_public = Keystore::sr25519_generate_new( - &*keystore, - ValidatorId::ID, - Some(&Sr25519Keyring::Alice.to_seed()), - ) - .unwrap(); - - SignedFullStatement::sign( - &keystore, - Statement::Valid(candidate_hash), - &signing_context, - ValidatorIndex(0), - &alice_public.into(), - ) - .ok() - .flatten() - .expect("should be signed") - }; - - // `PeerA` sends a `Valid` message - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerMessage( - peer_a, - Versioned::V1(protocol_v1::StatementDistributionMessage::Statement( - relay_parent_hash, - statement.clone().into(), - )), - ), - ), - }) - .await; - - let statement_with_pvd = extend_statement_with_pvd(statement.clone(), pvd.clone()); - - // Persisted validation data is cached. - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)) - ) => { - assert_eq!(p, peer_a); - assert_eq!(r, BENEFIT_VALID_STATEMENT_FIRST.into()); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::CandidateBacking( - CandidateBackingMessage::Statement(r, s) - ) => { - assert_eq!(r, relay_parent_hash); - assert_eq!(s, statement_with_pvd); - } - ); - - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::SendValidationMessage( - recipients, - Versioned::V1(protocol_v1::ValidationProtocol::StatementDistribution( - protocol_v1::StatementDistributionMessage::Statement(r, s) - )), - ) - ) => { - assert!(!recipients.contains(&peer_b)); - assert_eq!(r, relay_parent_hash); - assert_eq!(s, statement.clone().into()); - } - ); - - // `PeerB` sends a `Valid` message - handle - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerMessage( - peer_b, - Versioned::V1(protocol_v1::StatementDistributionMessage::Statement( - relay_parent_hash, - statement.clone().into(), - )), - ), - ), - }) - .await; - - // We expect that this is still valid despite the fact that `PeerB` was not - // the first when sending `Seconded` - assert_matches!( - handle.recv().await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)) - ) => { - assert_eq!(p, peer_b); - assert_eq!(r, BENEFIT_VALID_STATEMENT.into()); - } - ); - - handle.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await; - }; - - futures::pin_mut!(test_fut); - futures::pin_mut!(virtual_overseer_fut); - - executor::block_on(future::join(test_fut, virtual_overseer_fut)); -} - fn make_session_info(validators: Vec<Pair>, groups: Vec<Vec<u32>>) -> SessionInfo { let validator_groups: IndexedVec<GroupIndex, Vec<ValidatorIndex>> = groups .iter() diff --git a/polkadot/node/network/statement-distribution/src/lib.rs b/polkadot/node/network/statement-distribution/src/lib.rs index 33431eb1edce585943100e4c23d7234a39779f9c..3b5d00921ca0cf00387e6aa7626524df00ff4c31 100644 --- a/polkadot/node/network/statement-distribution/src/lib.rs +++ b/polkadot/node/network/statement-distribution/src/lib.rs @@ -28,7 +28,6 @@ use polkadot_node_network_protocol::{ request_response::{v1 as request_v1, v2::AttestedCandidateRequest, IncomingRequestReceiver}, v2 as protocol_v2, v3 as protocol_v3, Versioned, }; -use polkadot_node_primitives::StatementWithPVD; use polkadot_node_subsystem::{ messages::{NetworkBridgeEvent, StatementDistributionMessage}, overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError, @@ -36,7 +35,6 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_util::{ rand, reputation::{ReputationAggregator, REPUTATION_CHANGE_INTERVAL}, - runtime::{prospective_parachains_mode, ProspectiveParachainsMode}, }; use futures::{channel::mpsc, prelude::*}; @@ -322,33 +320,14 @@ impl<R: rand::Rng> StatementDistributionSubsystem<R> { })) => { let _timer = metrics.time_active_leaves_update(); - // v2 should handle activated first because of implicit view. if let Some(ref activated) = activated { - let mode = prospective_parachains_mode(ctx.sender(), activated.hash).await?; - if let ProspectiveParachainsMode::Enabled { .. } = mode { - let res = - v2::handle_active_leaves_update(ctx, state, activated, mode, &metrics) - .await; - // Regardless of the result of leaf activation, we always prune before - // handling it to avoid leaks. - v2::handle_deactivate_leaves(state, &deactivated); - res?; - } else if let ProspectiveParachainsMode::Disabled = mode { - for deactivated in &deactivated { - crate::legacy_v1::handle_deactivate_leaf(legacy_v1_state, *deactivated); - } - - crate::legacy_v1::handle_activated_leaf( - ctx, - legacy_v1_state, - activated.clone(), - ) - .await?; - } + let res = + v2::handle_active_leaves_update(ctx, state, activated, &metrics).await; + // Regardless of the result of leaf activation, we always prune before + // handling it to avoid leaks. + v2::handle_deactivate_leaves(state, &deactivated); + res?; } else { - for deactivated in &deactivated { - crate::legacy_v1::handle_deactivate_leaf(legacy_v1_state, *deactivated); - } v2::handle_deactivate_leaves(state, &deactivated); } }, @@ -360,28 +339,15 @@ impl<R: rand::Rng> StatementDistributionSubsystem<R> { StatementDistributionMessage::Share(relay_parent, statement) => { let _timer = metrics.time_share(); - // pass to legacy if legacy state contains head. - if legacy_v1_state.contains_relay_parent(&relay_parent) { - crate::legacy_v1::share_local_statement( - ctx, - legacy_v1_state, - relay_parent, - StatementWithPVD::drop_pvd_from_signed(statement), - &mut self.rng, - metrics, - ) - .await?; - } else { - v2::share_local_statement( - ctx, - state, - relay_parent, - statement, - &mut self.reputation, - &self.metrics, - ) - .await?; - } + v2::share_local_statement( + ctx, + state, + relay_parent, + statement, + &mut self.reputation, + &self.metrics, + ) + .await?; }, StatementDistributionMessage::NetworkBridgeUpdate(event) => { // pass all events to both protocols except for messages, diff --git a/polkadot/node/network/statement-distribution/src/v2/mod.rs b/polkadot/node/network/statement-distribution/src/v2/mod.rs index 6bb49e5de13dd35ed0d285dd2789b6f12d9e702c..3034ca7caf851df09c267880991f15718abeb277 100644 --- a/polkadot/node/network/statement-distribution/src/v2/mod.rs +++ b/polkadot/node/network/statement-distribution/src/v2/mod.rs @@ -45,10 +45,7 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_util::{ backing_implicit_view::View as ImplicitView, reputation::ReputationAggregator, - runtime::{ - request_min_backing_votes, request_node_features, ClaimQueueSnapshot, - ProspectiveParachainsMode, - }, + runtime::{request_min_backing_votes, request_node_features, ClaimQueueSnapshot}, }; use polkadot_primitives::{ node_features::FeatureIndex, @@ -163,11 +160,11 @@ pub(crate) const REQUEST_RETRY_DELAY: Duration = Duration::from_secs(1); struct PerRelayParentState { local_validator: Option<LocalValidatorState>, statement_store: StatementStore, - seconding_limit: usize, session: SessionIndex, transposed_cq: TransposedClaimQueue, groups_per_para: HashMap<ParaId, Vec<GroupIndex>>, disabled_validators: HashSet<ValidatorIndex>, + assignments_per_group: HashMap<GroupIndex, Vec<ParaId>>, } impl PerRelayParentState { @@ -304,8 +301,6 @@ impl PerSessionState { pub(crate) struct State { /// The utility for managing the implicit and explicit views in a consistent way. - /// - /// We only feed leaves which have prospective parachains enabled to this view. implicit_view: ImplicitView, candidates: Candidates, per_relay_parent: HashMap<Hash, PerRelayParentState>, @@ -572,23 +567,14 @@ pub(crate) async fn handle_network_update<Context>( } } -/// If there is a new leaf, this should only be called for leaves which support -/// prospective parachains. +/// Called on new leaf updates. #[overseer::contextbounds(StatementDistribution, prefix=self::overseer)] pub(crate) async fn handle_active_leaves_update<Context>( ctx: &mut Context, state: &mut State, activated: &ActivatedLeaf, - leaf_mode: ProspectiveParachainsMode, metrics: &Metrics, ) -> JfyiErrorResult<()> { - let max_candidate_depth = match leaf_mode { - ProspectiveParachainsMode::Disabled => return Ok(()), - ProspectiveParachainsMode::Enabled { max_candidate_depth, .. } => max_candidate_depth, - }; - - let seconding_limit = max_candidate_depth + 1; - state .implicit_view .activate_leaf(ctx.sender(), activated.hash) @@ -697,27 +683,21 @@ pub(crate) async fn handle_active_leaves_update<Context>( .map_err(JfyiError::FetchClaimQueue)?, ); + let (groups_per_para, assignments_per_group) = determine_group_assignments( + per_session.groups.all().len(), + group_rotation_info, + &claim_queue, + ) + .await; + let local_validator = per_session.local_validator.and_then(|v| { if let LocalValidatorIndex::Active(idx) = v { - find_active_validator_state( - idx, - &per_session.groups, - &group_rotation_info, - &claim_queue, - seconding_limit, - ) + find_active_validator_state(idx, &per_session.groups, &assignments_per_group) } else { Some(LocalValidatorState { grid_tracker: GridTracker::default(), active: None }) } }); - let groups_per_para = determine_groups_per_para( - per_session.groups.all().len(), - group_rotation_info, - &claim_queue, - ) - .await; - let transposed_cq = transpose_claim_queue(claim_queue.0); state.per_relay_parent.insert( @@ -725,11 +705,11 @@ pub(crate) async fn handle_active_leaves_update<Context>( PerRelayParentState { local_validator, statement_store: StatementStore::new(&per_session.groups), - seconding_limit, session: session_index, groups_per_para, disabled_validators, transposed_cq, + assignments_per_group, }, ); } @@ -769,9 +749,7 @@ pub(crate) async fn handle_active_leaves_update<Context>( fn find_active_validator_state( validator_index: ValidatorIndex, groups: &Groups, - group_rotation_info: &GroupRotationInfo, - claim_queue: &ClaimQueueSnapshot, - seconding_limit: usize, + assignments_per_group: &HashMap<GroupIndex, Vec<ParaId>>, ) -> Option<LocalValidatorState> { if groups.all().is_empty() { return None @@ -779,17 +757,17 @@ fn find_active_validator_state( let our_group = groups.by_validator_index(validator_index)?; - let core_index = group_rotation_info.core_for_group(our_group, groups.all().len()); - let paras_assigned_to_core = claim_queue.iter_claims_for_core(&core_index).copied().collect(); let group_validators = groups.get(our_group)?.to_owned(); + let paras_assigned_to_core = assignments_per_group.get(&our_group).cloned().unwrap_or_default(); + let seconding_limit = paras_assigned_to_core.len(); Some(LocalValidatorState { active: Some(ActiveValidatorState { index: validator_index, group: our_group, - assignments: paras_assigned_to_core, cluster_tracker: ClusterTracker::new(group_validators, seconding_limit) .expect("group is non-empty because we are in it; qed"), + assignments: paras_assigned_to_core.clone(), }), grid_tracker: GridTracker::default(), }) @@ -1231,13 +1209,14 @@ pub(crate) async fn share_local_statement<Context>( return Err(JfyiError::InvalidShare) } + let seconding_limit = local_assignments.len(); + if is_seconded && - per_relay_parent.statement_store.seconded_count(&local_index) == - per_relay_parent.seconding_limit + per_relay_parent.statement_store.seconded_count(&local_index) >= seconding_limit { gum::warn!( target: LOG_TARGET, - limit = ?per_relay_parent.seconding_limit, + limit = ?seconding_limit, "Local node has issued too many `Seconded` statements", ); return Err(JfyiError::InvalidShare) @@ -2183,12 +2162,14 @@ async fn provide_candidate_to_grid<Context>( } } -// Utility function to populate per relay parent `ParaId` to `GroupIndex` mappings. -async fn determine_groups_per_para( +// Utility function to populate: +// - per relay parent `ParaId` to `GroupIndex` mappings. +// - per `GroupIndex` claim queue assignments +async fn determine_group_assignments( n_cores: usize, group_rotation_info: GroupRotationInfo, claim_queue: &ClaimQueueSnapshot, -) -> HashMap<ParaId, Vec<GroupIndex>> { +) -> (HashMap<ParaId, Vec<GroupIndex>>, HashMap<GroupIndex, Vec<ParaId>>) { // Determine the core indices occupied by each para at the current relay parent. To support // on-demand parachains we also consider the core indices at next blocks. let schedule: HashMap<CoreIndex, Vec<ParaId>> = claim_queue @@ -2197,16 +2178,19 @@ async fn determine_groups_per_para( .collect(); let mut groups_per_para = HashMap::new(); + let mut assignments_per_group = HashMap::with_capacity(schedule.len()); + // Map from `CoreIndex` to `GroupIndex` and collect as `HashMap`. for (core_index, paras) in schedule { let group_index = group_rotation_info.group_for_core(core_index, n_cores); + assignments_per_group.insert(group_index, paras.clone()); for para in paras { groups_per_para.entry(para).or_insert_with(Vec::new).push(group_index); } } - groups_per_para + (groups_per_para, assignments_per_group) } #[overseer::contextbounds(StatementDistribution, prefix=self::overseer)] @@ -2351,10 +2335,7 @@ async fn handle_incoming_manifest_common<'a, Context>( reputation: &mut ReputationAggregator, ) -> Option<ManifestImportSuccess<'a>> { // 1. sanity checks: peer is connected, relay-parent in state, para ID matches group index. - let peer_state = match peers.get(&peer) { - None => return None, - Some(p) => p, - }; + let peer_state = peers.get(&peer)?; let relay_parent_state = match per_relay_parent.get_mut(&relay_parent) { None => { @@ -2370,10 +2351,7 @@ async fn handle_incoming_manifest_common<'a, Context>( Some(s) => s, }; - let per_session = match per_session.get(&relay_parent_state.session) { - None => return None, - Some(s) => s, - }; + let per_session = per_session.get(&relay_parent_state.session)?; if relay_parent_state.local_validator.is_none() { if per_session.is_not_validator() { @@ -2398,10 +2376,7 @@ async fn handle_incoming_manifest_common<'a, Context>( return None } - let grid_topology = match per_session.grid_view.as_ref() { - None => return None, - Some(x) => x, - }; + let grid_topology = per_session.grid_view.as_ref()?; let sender_index = grid_topology .iter_sending_for_group(manifest_summary.claimed_group_index, manifest_kind) @@ -2436,11 +2411,18 @@ async fn handle_incoming_manifest_common<'a, Context>( let local_validator = relay_parent_state.local_validator.as_mut().expect("checked above; qed"); + let seconding_limit = relay_parent_state + .assignments_per_group + .get(&group_index)? + .iter() + .filter(|para| para == &¶_id) + .count(); + let acknowledge = match local_validator.grid_tracker.import_manifest( grid_topology, &per_session.groups, candidate_hash, - relay_parent_state.seconding_limit, + seconding_limit, manifest_summary, manifest_kind, sender_index, @@ -3015,7 +2997,7 @@ pub(crate) async fn dispatch_requests<Context>(ctx: &mut Context, state: &mut St let relay_parent_state = state.per_relay_parent.get(&relay_parent)?; let per_session = state.per_session.get(&relay_parent_state.session)?; let group = per_session.groups.get(group_index)?; - let seconding_limit = relay_parent_state.seconding_limit; + let seconding_limit = relay_parent_state.assignments_per_group.get(&group_index)?.len(); // Request nothing which would be an 'over-seconded' statement. let mut unwanted_mask = StatementFilter::blank(group.len()); diff --git a/polkadot/node/network/statement-distribution/src/v2/statement_store.rs b/polkadot/node/network/statement-distribution/src/v2/statement_store.rs index 56a54f6316c070df0111d22bbc9649fb8de6d0b4..e1dfc2f5a2faaecee68926fae4f1fc298ae87821 100644 --- a/polkadot/node/network/statement-distribution/src/v2/statement_store.rs +++ b/polkadot/node/network/statement-distribution/src/v2/statement_store.rs @@ -217,7 +217,7 @@ impl StatementStore { &'a self, validators: &'a [ValidatorIndex], candidate_hash: CandidateHash, - ) -> impl Iterator<Item = &SignedStatement> + 'a { + ) -> impl Iterator<Item = &'a SignedStatement> + 'a { let s_st = CompactStatement::Seconded(candidate_hash); let v_st = CompactStatement::Valid(candidate_hash); diff --git a/polkadot/node/network/statement-distribution/src/v2/tests/cluster.rs b/polkadot/node/network/statement-distribution/src/v2/tests/cluster.rs index 040123f1774cf727341be0bd0a1899b2430b51bd..750bcca9cb50b2d11c316bcc7b01c7dfe9064495 100644 --- a/polkadot/node/network/statement-distribution/src/v2/tests/cluster.rs +++ b/polkadot/node/network/statement-distribution/src/v2/tests/cluster.rs @@ -24,7 +24,6 @@ fn share_seconded_circulated_to_cluster() { validator_count: 20, group_size: 3, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -125,7 +124,6 @@ fn cluster_valid_statement_before_seconded_ignored() { validator_count: 20, group_size: 3, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -186,7 +184,6 @@ fn cluster_statement_bad_signature() { validator_count: 20, group_size: 3, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -260,7 +257,6 @@ fn useful_cluster_statement_from_non_cluster_peer_rejected() { validator_count: 20, group_size: 3, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -323,7 +319,6 @@ fn elastic_scaling_useful_cluster_statement_from_non_cluster_peer_rejected() { validator_count: 20, group_size: 3, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -383,7 +378,6 @@ fn statement_from_non_cluster_originator_unexpected() { validator_count: 20, group_size: 3, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -439,7 +433,6 @@ fn seconded_statement_leads_to_request() { validator_count: 20, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -528,7 +521,6 @@ fn cluster_statements_shared_seconded_first() { validator_count: 20, group_size: 3, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -643,7 +635,6 @@ fn cluster_accounts_for_implicit_view() { validator_count: 20, group_size: 3, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -743,8 +734,7 @@ fn cluster_accounts_for_implicit_view() { // peer B never had the relay parent in its view, so this tests that // the implicit view is working correctly for B. // - // the fact that the statement isn't sent again to A also indicates that it works - // it's working. + // the fact that the statement isn't sent again to A also indicates that it's working. assert_matches!( overseer.recv().await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessages(messages)) => { @@ -780,7 +770,6 @@ fn cluster_messages_imported_after_confirmed_candidate_importable_check() { validator_count: 20, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -904,7 +893,6 @@ fn cluster_messages_imported_after_new_leaf_importable_check() { validator_count: 20, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -1033,18 +1021,14 @@ fn cluster_messages_imported_after_new_leaf_importable_check() { #[test] fn ensure_seconding_limit_is_respected() { - // `max_candidate_depth: 1` for a `seconding_limit` of 2. + // use a scheduling_lookahead of two to restrict the per-core seconding limit to 2. + let scheduling_lookahead = 2; let config = TestConfig { validator_count: 20, group_size: 4, local_validator: LocalRole::Validator, - async_backing_params: Some(AsyncBackingParams { - max_candidate_depth: 1, - allowed_ancestry_len: 3, - }), allow_v2_descriptors: false, }; - let relay_parent = Hash::repeat_byte(1); let peer_a = PeerId::random(); @@ -1053,7 +1037,8 @@ fn ensure_seconding_limit_is_respected() { let local_group_index = local_validator.group_index.unwrap(); let local_para = ParaId::from(local_group_index.0); - let test_leaf = state.make_dummy_leaf(relay_parent); + let test_leaf = + state.make_dummy_leaf_with_scheduling_lookahead(relay_parent, scheduling_lookahead); let (candidate_1, pvd_1) = make_candidate( relay_parent, @@ -1232,3 +1217,117 @@ fn ensure_seconding_limit_is_respected() { overseer }); } + +#[test] +fn delayed_reputation_changes() { + let config = TestConfig { + validator_count: 20, + group_size: 3, + local_validator: LocalRole::Validator, + allow_v2_descriptors: false, + }; + + let keystore = test_helpers::mock::make_ferdie_keystore(); + let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); + let (statement_req_receiver, _) = IncomingRequest::get_config_receiver::< + Block, + sc_network::NetworkWorker<Block, Hash>, + >(&req_protocol_names); + let (candidate_req_receiver, req_cfg) = IncomingRequest::get_config_receiver::< + Block, + sc_network::NetworkWorker<Block, Hash>, + >(&req_protocol_names); + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0); + + let state = TestState::from_config(config, req_cfg.inbound_queue.unwrap(), &mut rng); + + // We can't use the test harness as we need to spawn our own subsystem with custom config. + let (context, mut virtual_overseer) = + polkadot_node_subsystem_test_helpers::make_subsystem_context( + sp_core::testing::TaskExecutor::new(), + ); + let subsystem = async move { + let subsystem = crate::StatementDistributionSubsystem { + keystore, + v1_req_receiver: Some(statement_req_receiver), + req_receiver: Some(candidate_req_receiver), + metrics: Default::default(), + rng, + reputation: ReputationAggregator::new(|_| false), + }; + + if let Err(e) = subsystem.run_inner(context, Duration::from_millis(100)).await { + panic!("Fatal error: {:?}", e); + } + }; + + let test_fut = async move { + let relay_parent = Hash::repeat_byte(1); + let peer_a = PeerId::random(); + + let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); + let candidate_hash = CandidateHash(Hash::repeat_byte(42)); + + let test_leaf = state.make_dummy_leaf(relay_parent); + + // peer A is in group, has relay parent in view. + let other_group_validators = state.group_validators(local_group_index, true); + let v_a = other_group_validators[0]; + connect_peer( + &mut virtual_overseer, + peer_a.clone(), + Some(vec![state.discovery_id(v_a)].into_iter().collect()), + ) + .await; + + send_peer_view_change(&mut virtual_overseer, peer_a.clone(), view![relay_parent]).await; + activate_leaf(&mut virtual_overseer, &test_leaf, &state, true, vec![]).await; + + let signed_valid = state.sign_statement( + v_a, + CompactStatement::Valid(candidate_hash), + &SigningContext { parent_hash: relay_parent, session_index: 1 }, + ); + + send_peer_message( + &mut virtual_overseer, + peer_a.clone(), + protocol_v2::StatementDistributionMessage::Statement( + relay_parent, + signed_valid.as_unchecked().clone(), + ), + ) + .await; + + assert_matches!(virtual_overseer.rx.next().timeout(Duration::from_millis(50)).await, None); + // Wait enough to fire reputation delay + futures_timer::Delay::new(Duration::from_millis(60)).await; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Batch(reps))) => { + let mut expected = HashMap::new(); + expected.insert(peer_a, COST_UNEXPECTED_STATEMENT_CLUSTER_REJECTED.cost_or_benefit()); + assert_eq!(expected, reps); + } + ); + + virtual_overseer + }; + + futures::pin_mut!(test_fut); + futures::pin_mut!(subsystem); + futures::executor::block_on(future::join( + async move { + let mut virtual_overseer = test_fut.await; + // Ensure we have handled all responses. + if let Ok(Some(msg)) = virtual_overseer.rx.try_next() { + panic!("Did not handle all responses: {:?}", msg); + } + // Conclude. + virtual_overseer.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await; + }, + subsystem, + )); +} diff --git a/polkadot/node/network/statement-distribution/src/v2/tests/grid.rs b/polkadot/node/network/statement-distribution/src/v2/tests/grid.rs index 0133d9e219f6eafacf0d1f73f6c261eb550a9af2..494e2a7f5dbf57a9ed7b9d659f23367d6626f6c7 100644 --- a/polkadot/node/network/statement-distribution/src/v2/tests/grid.rs +++ b/polkadot/node/network/statement-distribution/src/v2/tests/grid.rs @@ -30,7 +30,6 @@ fn backed_candidate_leads_to_advertisement() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -240,7 +239,6 @@ fn received_advertisement_before_confirmation_leads_to_request() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -413,7 +411,6 @@ fn received_advertisement_after_backing_leads_to_acknowledgement() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -595,7 +592,6 @@ fn receive_ack_for_unconfirmed_candidate() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -657,7 +653,6 @@ fn received_acknowledgements_for_locally_confirmed() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -820,7 +815,6 @@ fn received_acknowledgements_for_externally_confirmed() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -956,7 +950,6 @@ fn received_advertisement_after_confirmation_before_backing() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -1135,7 +1128,6 @@ fn additional_statements_are_shared_after_manifest_exchange() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -1423,7 +1415,6 @@ fn advertisement_sent_when_peer_enters_relay_parent_view() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -1637,7 +1628,6 @@ fn advertisement_not_re_sent_when_peer_re_enters_view() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -1849,7 +1839,6 @@ fn inner_grid_statements_imported_to_backing(groups_for_first_para: usize) { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -2058,7 +2047,6 @@ fn advertisements_rejected_from_incorrect_peers() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -2195,7 +2183,6 @@ fn manifest_rejected_with_unknown_relay_parent() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -2293,7 +2280,6 @@ fn manifest_rejected_when_not_a_validator() { validator_count, group_size, local_validator: LocalRole::None, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -2387,7 +2373,6 @@ fn manifest_rejected_when_group_does_not_match_para() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -2486,7 +2471,6 @@ fn peer_reported_for_advertisement_conflicting_with_confirmed_candidate() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -2677,7 +2661,6 @@ fn inactive_local_participates_in_grid() { validator_count, group_size, local_validator: LocalRole::InactiveValidator, - async_backing_params: None, allow_v2_descriptors: false, }; diff --git a/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs b/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs index 46b72f5adac9859f05f3ee9733a9b960b991715b..5a9b8efa2a13b60144a51a8fb9b26ec5358d8e32 100644 --- a/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs +++ b/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs @@ -24,7 +24,7 @@ use polkadot_node_network_protocol::{ v2::{BackedCandidateAcknowledgement, BackedCandidateManifest}, view, ObservedRole, }; -use polkadot_node_primitives::Statement; +use polkadot_node_primitives::{Statement, StatementWithPVD}; use polkadot_node_subsystem::messages::{ network_bridge_event::NewGossipTopology, AllMessages, ChainApiMessage, HypotheticalCandidate, HypotheticalMembership, NetworkBridgeEvent, ProspectiveParachainsMessage, ReportPeerMessage, @@ -33,9 +33,9 @@ use polkadot_node_subsystem::messages::{ use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::TimeoutExt; use polkadot_primitives::{ - vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, AssignmentPair, - AsyncBackingParams, Block, BlockNumber, GroupRotationInfo, HeadData, Header, IndexedVec, - PersistedValidationData, SessionIndex, SessionInfo, ValidatorPair, + vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, AssignmentPair, Block, + BlockNumber, GroupRotationInfo, HeadData, Header, IndexedVec, PersistedValidationData, + SessionIndex, SessionInfo, ValidatorPair, DEFAULT_SCHEDULING_LOOKAHEAD, }; use sc_keystore::LocalKeystore; use sc_network::ProtocolName; @@ -58,9 +58,6 @@ mod requests; type VirtualOverseer = polkadot_node_subsystem_test_helpers::TestSubsystemContextHandle<StatementDistributionMessage>; -const DEFAULT_ASYNC_BACKING_PARAMETERS: AsyncBackingParams = - AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: 3 }; - // Some deterministic genesis hash for req/res protocol names const GENESIS_HASH: Hash = Hash::repeat_byte(0xff); @@ -81,7 +78,6 @@ struct TestConfig { group_size: usize, // whether the local node should be a validator local_validator: LocalRole, - async_backing_params: Option<AsyncBackingParams>, // allow v2 descriptors (feature bit) allow_v2_descriptors: bool, } @@ -187,23 +183,26 @@ impl TestState { } fn make_dummy_leaf(&self, relay_parent: Hash) -> TestLeaf { - self.make_dummy_leaf_with_multiple_cores_per_para(relay_parent, 1) + self.make_dummy_leaf_inner(relay_parent, 1, DEFAULT_SCHEDULING_LOOKAHEAD as usize) } - fn make_dummy_leaf_with_multiple_cores_per_para( + fn make_dummy_leaf_inner( &self, relay_parent: Hash, groups_for_first_para: usize, + scheduling_lookahead: usize, ) -> TestLeaf { let mut cq = std::collections::BTreeMap::new(); for i in 0..self.session_info.validator_groups.len() { if i < groups_for_first_para { - cq.entry(CoreIndex(i as u32)) - .or_insert_with(|| vec![ParaId::from(0u32), ParaId::from(0u32)].into()); + cq.entry(CoreIndex(i as u32)).or_insert_with(|| { + std::iter::repeat(ParaId::from(0u32)).take(scheduling_lookahead).collect() + }); } else { - cq.entry(CoreIndex(i as u32)) - .or_insert_with(|| vec![ParaId::from(i), ParaId::from(i)].into()); + cq.entry(CoreIndex(i as u32)).or_insert_with(|| { + std::iter::repeat(ParaId::from(i)).take(scheduling_lookahead).collect() + }); }; } @@ -229,6 +228,26 @@ impl TestState { } } + fn make_dummy_leaf_with_scheduling_lookahead( + &self, + relay_parent: Hash, + scheduling_lookahead: usize, + ) -> TestLeaf { + self.make_dummy_leaf_inner(relay_parent, 1, scheduling_lookahead) + } + + fn make_dummy_leaf_with_multiple_cores_per_para( + &self, + relay_parent: Hash, + groups_for_first_para: usize, + ) -> TestLeaf { + self.make_dummy_leaf_inner( + relay_parent, + groups_for_first_para, + DEFAULT_SCHEDULING_LOOKAHEAD as usize, + ) + } + fn make_dummy_leaf_with_disabled_validators( &self, relay_parent: Hash, @@ -588,15 +607,6 @@ async fn handle_leaf_activation( claim_queue, } = leaf; - assert_matches!( - virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::AsyncBackingParams(tx)) - ) if parent == *hash => { - tx.send(Ok(test_state.config.async_backing_params.unwrap_or(DEFAULT_ASYNC_BACKING_PARAMETERS))).unwrap(); - } - ); - let header = Header { parent_hash: *parent_hash, number: *number, diff --git a/polkadot/node/network/statement-distribution/src/v2/tests/requests.rs b/polkadot/node/network/statement-distribution/src/v2/tests/requests.rs index fc880c1d9a836d9ece1475c687d81b1e5b8fea37..91df8e0a2f8c3417a23452f80aa205526595c775 100644 --- a/polkadot/node/network/statement-distribution/src/v2/tests/requests.rs +++ b/polkadot/node/network/statement-distribution/src/v2/tests/requests.rs @@ -38,7 +38,6 @@ fn cluster_peer_allowed_to_send_incomplete_statements(#[case] allow_v2_descripto validator_count: 20, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors, }; @@ -202,14 +201,12 @@ fn peer_reported_for_providing_statements_meant_to_be_masked_out() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: Some(AsyncBackingParams { - // Makes `seconding_limit: 2` (easier to hit the limit). - max_candidate_depth: 1, - allowed_ancestry_len: 3, - }), allow_v2_descriptors: false, }; + // use a scheduling_lookahead of two to restrict the per-core seconding limit to 2. + let scheduling_lookahead = 2; + let relay_parent = Hash::repeat_byte(1); let peer_c = PeerId::random(); let peer_d = PeerId::random(); @@ -222,7 +219,8 @@ fn peer_reported_for_providing_statements_meant_to_be_masked_out() { let other_group = next_group_index(local_group_index, validator_count, group_size); let other_para = ParaId::from(other_group.0); - let test_leaf = state.make_dummy_leaf(relay_parent); + let test_leaf = + state.make_dummy_leaf_with_scheduling_lookahead(relay_parent, scheduling_lookahead); let (candidate_1, pvd_1) = make_candidate( relay_parent, @@ -482,7 +480,6 @@ fn peer_reported_for_not_enough_statements() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -670,7 +667,6 @@ fn peer_reported_for_duplicate_statements() { validator_count: 20, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -824,7 +820,6 @@ fn peer_reported_for_providing_statements_with_invalid_signatures() { validator_count: 20, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -956,7 +951,6 @@ fn peer_reported_for_invalid_v2_descriptor() { validator_count: 20, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: true, }; @@ -988,7 +982,6 @@ fn peer_reported_for_invalid_v2_descriptor() { let other_group_validators = state.group_validators(local_group_index, true); let v_a = other_group_validators[0]; let v_b = other_group_validators[1]; - let v_c = other_group_validators[1]; // peer A is in group, has relay parent in view. // peer B is in group, has no relay parent in view. @@ -997,14 +990,14 @@ fn peer_reported_for_invalid_v2_descriptor() { connect_peer( &mut overseer, peer_a.clone(), - Some(vec![state.discovery_id(other_group_validators[0])].into_iter().collect()), + Some(vec![state.discovery_id(v_a)].into_iter().collect()), ) .await; connect_peer( &mut overseer, peer_b.clone(), - Some(vec![state.discovery_id(other_group_validators[1])].into_iter().collect()), + Some(vec![state.discovery_id(v_b)].into_iter().collect()), ) .await; @@ -1177,11 +1170,17 @@ fn peer_reported_for_invalid_v2_descriptor() { .clone(); let statements = vec![b_seconded_invalid.clone()]; + // v_a has exhausted its seconded statements (3). + let mut statement_filter = StatementFilter::blank(group_size); + statement_filter + .seconded_in_group + .set(state.index_within_group(local_group_index, v_a).unwrap(), true); + handle_sent_request( &mut overseer, peer_a, candidate_hash, - StatementFilter::blank(group_size), + statement_filter, candidate.clone(), pvd.clone(), statements, @@ -1213,7 +1212,7 @@ fn peer_reported_for_invalid_v2_descriptor() { assert_eq!(peers, vec![peer_a.clone()]); assert_eq!(r, relay_parent); assert_eq!(s.unchecked_payload(), &CompactStatement::Seconded(candidate_hash)); - assert_eq!(s.unchecked_validator_index(), v_c); + assert_eq!(s.unchecked_validator_index(), v_b); } ); @@ -1234,7 +1233,6 @@ fn v2_descriptors_filtered(#[case] allow_v2_descriptors: bool) { validator_count: 20, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors, }; @@ -1365,7 +1363,6 @@ fn peer_reported_for_providing_statements_with_wrong_validator_id() { validator_count: 20, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -1496,7 +1493,6 @@ fn disabled_validators_added_to_unwanted_mask() { validator_count: 20, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -1663,7 +1659,6 @@ fn disabling_works_from_relay_parent_not_the_latest_state() { validator_count: 20, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -1863,7 +1858,6 @@ fn local_node_sanity_checks_incoming_requests() { validator_count: 20, group_size: 3, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -2065,7 +2059,6 @@ fn local_node_checks_that_peer_can_request_before_responding() { validator_count: 20, group_size: 3, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -2265,7 +2258,6 @@ fn local_node_respects_statement_mask() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; @@ -2508,7 +2500,6 @@ fn should_delay_before_retrying_dropped_requests() { validator_count, group_size, local_validator: LocalRole::Validator, - async_backing_params: None, allow_v2_descriptors: false, }; diff --git a/polkadot/node/overseer/Cargo.toml b/polkadot/node/overseer/Cargo.toml index 2253a5ae0c668c0c107f19ad15ef933c1cf17abc..fd7f1e039247ed1ac3629903bdb79e89b79eea93 100644 --- a/polkadot/node/overseer/Cargo.toml +++ b/polkadot/node/overseer/Cargo.toml @@ -5,35 +5,37 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "System overseer of the Polkadot node" +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] -sc-client-api = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } +async-trait = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } +gum = { workspace = true, default-features = true } +orchestra = { features = ["futures_channel"], workspace = true } parking_lot = { workspace = true, default-features = true } +polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem-types = { workspace = true, default-features = true } -polkadot-node-metrics = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } -orchestra = { features = ["futures_channel"], workspace = true } -gum = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } -async-trait = { workspace = true } tikv-jemalloc-ctl = { optional = true, workspace = true } [dev-dependencies] -metered = { features = ["futures_channel"], workspace = true } -sp-core = { workspace = true, default-features = true } -futures = { features = ["thread-pool"], workspace = true } -femme = { workspace = true } assert_matches = { workspace = true } -polkadot-primitives-test-helpers = { workspace = true } +femme = { workspace = true } +futures = { features = ["thread-pool"], workspace = true } +metered = { features = ["futures_channel"], workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } +polkadot-primitives-test-helpers = { workspace = true } +sp-core = { workspace = true, default-features = true } [target.'cfg(target_os = "linux")'.dependencies] tikv-jemalloc-ctl = "0.5.0" diff --git a/polkadot/node/overseer/src/lib.rs b/polkadot/node/overseer/src/lib.rs index 3881ddbcc9046dfb1f805c82a46c824d169e19f6..17470d74577d2147bb4faf2461aa2e1faa85ffae 100644 --- a/polkadot/node/overseer/src/lib.rs +++ b/polkadot/node/overseer/src/lib.rs @@ -533,7 +533,6 @@ pub struct Overseer<SupportsParachains> { #[subsystem(ProvisionerMessage, sends: [ RuntimeApiMessage, CandidateBackingMessage, - ChainApiMessage, DisputeCoordinatorMessage, ProspectiveParachainsMessage, ])] diff --git a/polkadot/node/primitives/Cargo.toml b/polkadot/node/primitives/Cargo.toml index 7185205f905b6d24fa5a148d32572a106794d7f6..d138b77dea8fb3d3f2848703fa7639c4f79cec03 100644 --- a/polkadot/node/primitives/Cargo.toml +++ b/polkadot/node/primitives/Cargo.toml @@ -5,29 +5,31 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +bitvec = { features = ["alloc"], workspace = true } bounded-vec = { workspace = true } +codec = { features = ["derive"], workspace = true } futures = { workspace = true } futures-timer = { workspace = true } +polkadot-parachain-primitives = { workspace = true } polkadot-primitives = { workspace = true, default-features = true } -codec = { features = ["derive"], workspace = true } -sp-core = { workspace = true, default-features = true } +sc-keystore = { workspace = true } +schnorrkel = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } sp-application-crypto = { workspace = true, default-features = true } sp-consensus-babe = { workspace = true, default-features = true } sp-consensus-slots = { workspace = true } +sp-core = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-maybe-compressed-blob = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -polkadot-parachain-primitives = { workspace = true } -schnorrkel = { workspace = true, default-features = true } thiserror = { workspace = true } -bitvec = { features = ["alloc"], workspace = true } -serde = { features = ["derive"], workspace = true, default-features = true } -sc-keystore = { workspace = true } [target.'cfg(not(target_os = "unknown"))'.dependencies] zstd = { version = "0.12.4", default-features = false } diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs index 6985e86098b01938ed37904417778f2534674333..845daa2679850cea080400dfe6abdec11146d8aa 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.16.1"; +pub const NODE_VERSION: &'static str = "1.17.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/Cargo.toml b/polkadot/node/service/Cargo.toml index 6e8eade21a4389ea1815f2e6463722b7ba896823..122040a9b20794566d90a177baf790ea595fbb91 100644 --- a/polkadot/node/service/Cargo.toml +++ b/polkadot/node/service/Cargo.toml @@ -6,104 +6,106 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Utils to tie different Polkadot components together and allow instantiation of a node." +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] # Substrate Client -sc-authority-discovery = { workspace = true, default-features = true } -sc-consensus-babe = { workspace = true, default-features = true } -sc-consensus-beefy = { workspace = true, default-features = true } -sc-consensus-grandpa = { workspace = true, default-features = true } mmr-gadget = { workspace = true, default-features = true } -sp-mmr-primitives = { workspace = true, default-features = true } -sp-genesis-builder = { workspace = true, default-features = true } +sc-authority-discovery = { workspace = true, default-features = true } +sc-basic-authorship = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } +sc-consensus-babe = { workspace = true, default-features = true } +sc-consensus-beefy = { workspace = true, default-features = true } +sc-consensus-grandpa = { workspace = true, default-features = true } sc-consensus-slots = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } +sc-keystore = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } -sc-transaction-pool = { workspace = true, default-features = true } -sc-transaction-pool-api = { workspace = true, default-features = true } -sc-sync-state-rpc = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } -sc-basic-authorship = { workspace = true, default-features = true } sc-offchain = { workspace = true, default-features = true } -sc-sysinfo = { workspace = true, default-features = true } sc-service = { workspace = true } +sc-sync-state-rpc = { workspace = true, default-features = true } +sc-sysinfo = { 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 } +sp-genesis-builder = { workspace = true, default-features = true } +sp-mmr-primitives = { workspace = true, default-features = true } # Substrate Primitives +pallet-transaction-payment = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } sp-authority-discovery = { workspace = true, default-features = true } +sp-block-builder = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } sp-consensus-beefy = { workspace = true, default-features = true } sp-consensus-grandpa = { workspace = true, default-features = true } -sp-inherents = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } -sp-block-builder = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-inherents = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } sp-offchain = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-session = { workspace = true, default-features = true } -sp-transaction-pool = { workspace = true, default-features = true } -pallet-transaction-payment = { workspace = true, default-features = true } sp-timestamp = { workspace = true, default-features = true } -sp-consensus-babe = { workspace = true, default-features = true } -sp-weights = { workspace = true, default-features = true } +sp-transaction-pool = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } +sp-weights = { workspace = true, default-features = true } # Substrate Pallets -pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } frame-metadata-hash-extension = { optional = true, workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } +pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } # Substrate Other +frame-benchmarking = { workspace = true, default-features = true } +frame-benchmarking-cli = { workspace = true, default-features = true } frame-system-rpc-runtime-api = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } -frame-benchmarking-cli = { workspace = true, default-features = true } -frame-benchmarking = { workspace = true, default-features = true } # External Crates async-trait = { workspace = true } +codec = { workspace = true, default-features = true } futures = { workspace = true } -is_executable = { workspace = true } gum = { 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 } -thiserror = { workspace = true } +is_executable = { workspace = true } kvdb = { workspace = true } kvdb-rocksdb = { optional = true, workspace = true } +log = { workspace = true, default-features = true } parity-db = { optional = true, workspace = true } -codec = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } # Polkadot polkadot-core-primitives = { workspace = true, default-features = true } polkadot-node-core-parachains-inherent = { workspace = true, default-features = true } -polkadot-overseer = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } +polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-rpc = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } -polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-node-subsystem-types = { workspace = true, default-features = true } +polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-overseer = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +polkadot-rpc = { workspace = true, default-features = true } polkadot-runtime-parachains = { workspace = true, default-features = true } -polkadot-node-network-protocol = { workspace = true, default-features = true } # Polkadot Runtime Constants rococo-runtime-constants = { optional = true, workspace = true, default-features = true } westend-runtime-constants = { optional = true, workspace = true, default-features = true } # Polkadot Runtimes -westend-runtime = { optional = true, workspace = true } rococo-runtime = { optional = true, workspace = true } +westend-runtime = { optional = true, workspace = true } # Polkadot Subsystems polkadot-approval-distribution = { optional = true, workspace = true, default-features = true } @@ -135,11 +137,11 @@ xcm = { workspace = true, default-features = true } xcm-runtime-apis = { workspace = true, default-features = true } [dev-dependencies] -polkadot-test-client = { workspace = true } +assert_matches = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-primitives-test-helpers = { workspace = true } +polkadot-test-client = { workspace = true } sp-tracing = { workspace = true } -assert_matches = { workspace = true } tempfile = { workspace = true } [features] @@ -208,6 +210,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "westend-runtime?/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-system/try-runtime", diff --git a/polkadot/node/service/src/benchmarking.rs b/polkadot/node/service/src/benchmarking.rs index 0cf16edc03cc9c79f6163d3c1249be6a65fe2605..5b814a22d2f877d2772790188ee81ba8877ae1cb 100644 --- a/polkadot/node/service/src/benchmarking.rs +++ b/polkadot/node/service/src/benchmarking.rs @@ -155,6 +155,7 @@ fn westend_sign_call( frame_system::CheckWeight::<runtime::Runtime>::new(), pallet_transaction_payment::ChargeTransactionPayment::<runtime::Runtime>::from(0), frame_metadata_hash_extension::CheckMetadataHash::<runtime::Runtime>::new(false), + frame_system::WeightReclaim::<runtime::Runtime>::new(), ) .into(); @@ -171,6 +172,7 @@ fn westend_sign_call( (), (), None, + (), ), ); @@ -210,6 +212,7 @@ fn rococo_sign_call( frame_system::CheckWeight::<runtime::Runtime>::new(), pallet_transaction_payment::ChargeTransactionPayment::<runtime::Runtime>::from(0), frame_metadata_hash_extension::CheckMetadataHash::<runtime::Runtime>::new(false), + frame_system::WeightReclaim::<runtime::Runtime>::new(), ) .into(); @@ -226,6 +229,7 @@ fn rococo_sign_call( (), (), None, + (), ), ); diff --git a/polkadot/node/service/src/lib.rs b/polkadot/node/service/src/lib.rs index 227bc52539946eb95dee4ec7a1b01505460f8487..820cce8d083a63903d7887247990545bce114315 100644 --- a/polkadot/node/service/src/lib.rs +++ b/polkadot/node/service/src/lib.rs @@ -944,14 +944,9 @@ pub fn new_full< secure_validator_mode, prep_worker_path, exec_worker_path, - pvf_execute_workers_max_num: execute_workers_max_num.unwrap_or_else( - || match config.chain_spec.identify_chain() { - // The intention is to use this logic for gradual increasing from 2 to 4 - // of this configuration chain by chain until it reaches production chain. - Chain::Polkadot | Chain::Kusama => 2, - Chain::Rococo | Chain::Westend | Chain::Unknown => 4, - }, - ), + // Default execution workers is 4 because we have 8 cores on the reference hardware, + // and this accounts for 50% of that cpu capacity. + pvf_execute_workers_max_num: execute_workers_max_num.unwrap_or(4), pvf_prepare_workers_soft_max_num: prepare_workers_soft_max_num.unwrap_or(1), pvf_prepare_workers_hard_max_num: prepare_workers_hard_max_num.unwrap_or(2), }) diff --git a/polkadot/node/service/src/overseer.rs b/polkadot/node/service/src/overseer.rs index 279b6ff80704dc52c00b440b096ea87d18ea4235..a90570a149b52807d1da3c5dc47a13e7d4d1fbdd 100644 --- a/polkadot/node/service/src/overseer.rs +++ b/polkadot/node/service/src/overseer.rs @@ -210,7 +210,7 @@ pub fn validator_overseer_builder<Spawner, RuntimeClient>( AuthorityDiscoveryService, >, ChainApiSubsystem<RuntimeClient>, - CollationGenerationSubsystem, + DummySubsystem, CollatorProtocolSubsystem, ApprovalDistributionSubsystem, ApprovalVotingSubsystem, @@ -237,6 +237,7 @@ where let network_bridge_metrics: NetworkBridgeMetrics = Metrics::register(registry)?; let approval_voting_parallel_metrics: ApprovalVotingParallelMetrics = Metrics::register(registry)?; + let builder = Overseer::builder() .network_bridge_tx(NetworkBridgeTxSubsystem::new( network_service.clone(), @@ -295,7 +296,7 @@ where )) .pvf_checker(PvfCheckerSubsystem::new(keystore.clone(), Metrics::register(registry)?)) .chain_api(ChainApiSubsystem::new(runtime_client.clone(), Metrics::register(registry)?)) - .collation_generation(CollationGenerationSubsystem::new(Metrics::register(registry)?)) + .collation_generation(DummySubsystem) .collator_protocol({ let side = match is_parachain_node { IsParachainNode::Collator(_) | IsParachainNode::FullNode => @@ -434,7 +435,7 @@ pub fn validator_with_parallel_overseer_builder<Spawner, RuntimeClient>( AuthorityDiscoveryService, >, ChainApiSubsystem<RuntimeClient>, - CollationGenerationSubsystem, + DummySubsystem, CollatorProtocolSubsystem, DummySubsystem, DummySubsystem, @@ -519,7 +520,7 @@ where )) .pvf_checker(PvfCheckerSubsystem::new(keystore.clone(), Metrics::register(registry)?)) .chain_api(ChainApiSubsystem::new(runtime_client.clone(), Metrics::register(registry)?)) - .collation_generation(CollationGenerationSubsystem::new(Metrics::register(registry)?)) + .collation_generation(DummySubsystem) .collator_protocol({ let side = match is_parachain_node { IsParachainNode::Collator(_) | IsParachainNode::FullNode => @@ -599,7 +600,7 @@ pub fn collator_overseer_builder<Spawner, RuntimeClient>( network_service, sync_service, authority_discovery_service, - collation_req_v1_receiver, + collation_req_v1_receiver: _, collation_req_v2_receiver, available_data_req_receiver, registry, @@ -702,7 +703,6 @@ where IsParachainNode::Collator(collator_pair) => ProtocolSide::Collator { peer_id: network_service.local_peer_id(), collator_pair, - request_receiver_v1: collation_req_v1_receiver, request_receiver_v2: collation_req_v2_receiver, metrics: Metrics::register(registry)?, }, diff --git a/polkadot/node/subsystem-bench/Cargo.toml b/polkadot/node/subsystem-bench/Cargo.toml index 8633818e775da0e4ea5213c3c5f6a436b535a664..e288ee2b78d3392309d6585efa9ec26fd508750a 100644 --- a/polkadot/node/subsystem-bench/Cargo.toml +++ b/polkadot/node/subsystem-bench/Cargo.toml @@ -21,79 +21,79 @@ doc = false [dependencies] -tikv-jemallocator = { features = ["profiling", "unprefixed_malloc_on_supported_platforms"], workspace = true, optional = true } -jemalloc_pprof = { workspace = true, optional = true } -polkadot-service = { workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } -polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-node-subsystem-types = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, features = ["test"] } -polkadot-node-network-protocol = { workspace = true, default-features = true } -polkadot-availability-recovery = { features = ["subsystem-benchmarks"], workspace = true, default-features = true } -polkadot-availability-distribution = { workspace = true, default-features = true } -polkadot-statement-distribution = { workspace = true, default-features = true } -polkadot-node-core-av-store = { workspace = true, default-features = true } -polkadot-node-core-chain-api = { workspace = true, default-features = true } -polkadot-availability-bitfield-distribution = { workspace = true, default-features = true } -color-eyre = { workspace = true } -polkadot-overseer = { workspace = true, default-features = true } -colored = { workspace = true } assert_matches = { workspace = true } async-trait = { workspace = true } -sp-keystore = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } +bincode = { workspace = true } clap = { features = ["derive"], workspace = true } +color-eyre = { workspace = true } +colored = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } -bincode = { workspace = true } -sha1 = { workspace = true } -hex = { workspace = true, default-features = true } gum = { workspace = true, default-features = true } -polkadot-erasure-coding = { workspace = true, default-features = true } +hex = { workspace = true, default-features = true } +jemalloc_pprof = { workspace = true, optional = true } log = { workspace = true, default-features = true } -sp-tracing = { workspace = true } +polkadot-availability-bitfield-distribution = { workspace = true, default-features = true } +polkadot-availability-distribution = { workspace = true, default-features = true } +polkadot-availability-recovery = { features = ["subsystem-benchmarks"], workspace = true, default-features = true } +polkadot-erasure-coding = { workspace = true, default-features = true } +polkadot-node-core-av-store = { workspace = true, default-features = true } +polkadot-node-core-chain-api = { workspace = true, default-features = true } +polkadot-node-network-protocol = { workspace = true, default-features = true } +polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-node-subsystem-types = { workspace = true, default-features = true } +polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-overseer = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, features = ["test"] } +polkadot-service = { workspace = true, default-features = true } +polkadot-statement-distribution = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } +sc-keystore = { workspace = true, default-features = true } +sha1 = { workspace = true } +sp-core = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-tracing = { workspace = true } +tikv-jemallocator = { features = ["profiling", "unprefixed_malloc_on_supported_platforms"], workspace = true, optional = true } # `rand` only supports uniform distribution, we need normal distribution for latency. -rand_distr = { workspace = true } bitvec = { workspace = true, default-features = true } kvdb-memorydb = { workspace = true } +rand_distr = { workspace = true } -codec = { features = ["derive", "std"], workspace = true, default-features = true } -tokio = { features = ["parking_lot", "rt-multi-thread"], workspace = true, default-features = true } clap-num = { workspace = true } +codec = { features = ["derive", "std"], workspace = true, default-features = true } +itertools = { workspace = true } +polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } -sp-keyring = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true, default-features = true } +polkadot-primitives-test-helpers = { workspace = true } +prometheus = { workspace = true } +prometheus-endpoint = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } -polkadot-node-metrics = { workspace = true, default-features = true } -itertools = { workspace = true } -polkadot-primitives-test-helpers = { workspace = true } -prometheus-endpoint = { workspace = true, default-features = true } -prometheus = { workspace = true } serde = { workspace = true, default-features = true } -serde_yaml = { workspace = true } serde_json = { workspace = true } +serde_yaml = { workspace = true } +sp-application-crypto = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +tokio = { features = ["parking_lot", "rt-multi-thread"], workspace = true, default-features = true } +polkadot-approval-distribution = { workspace = true, default-features = true } polkadot-node-core-approval-voting = { workspace = true, default-features = true } polkadot-node-core-approval-voting-parallel = { workspace = true, default-features = true } -polkadot-approval-distribution = { workspace = true, default-features = true } sp-consensus-babe = { workspace = true, default-features = true } sp-runtime = { workspace = true } sp-timestamp = { workspace = true, default-features = true } schnorrkel = { workspace = true } # rand_core should match schnorrkel -rand_core = { workspace = true } -rand_chacha = { workspace = true, default-features = true } -paste = { workspace = true, default-features = true } orchestra = { features = ["futures_channel"], workspace = true } +paste = { workspace = true, default-features = true } pyroscope = { workspace = true } pyroscope_pprofrs = { workspace = true } +rand_chacha = { workspace = true, default-features = true } +rand_core = { workspace = true } strum = { features = ["derive"], workspace = true, default-features = true } [features] diff --git a/polkadot/node/subsystem-bench/src/lib/approval/mod.rs b/polkadot/node/subsystem-bench/src/lib/approval/mod.rs index 1b20960a3f8a604309ebc2cf22b6ff8f1c55c8d9..5f1689cb226b3919346fadbb8698572ac5d1c47c 100644 --- a/polkadot/node/subsystem-bench/src/lib/approval/mod.rs +++ b/polkadot/node/subsystem-bench/src/lib/approval/mod.rs @@ -891,6 +891,8 @@ fn build_overseer( state.approval_voting_parallel_metrics.approval_voting_metrics(), Arc::new(system_clock.clone()), Arc::new(SpawnGlue(spawn_task_handle.clone())), + 1, + Duration::from_secs(1), ); let approval_distribution = ApprovalDistribution::new_with_clock( diff --git a/polkadot/node/subsystem-test-helpers/Cargo.toml b/polkadot/node/subsystem-test-helpers/Cargo.toml index d3229291673c6da0b1ba12d6983c678ed2d5a343..4e660b15c1e29e817a1b8a12d1ce7e6e8f7cab90 100644 --- a/polkadot/node/subsystem-test-helpers/Cargo.toml +++ b/polkadot/node/subsystem-test-helpers/Cargo.toml @@ -14,16 +14,16 @@ workspace = true async-trait = { workspace = true } futures = { workspace = true } parking_lot = { workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-erasure-coding = { workspace = true, default-features = true } +polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } +sc-keystore = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } +sp-application-crypto = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } diff --git a/polkadot/node/subsystem-types/Cargo.toml b/polkadot/node/subsystem-types/Cargo.toml index b5686ec96be127b9c3f33151278366844a362648..6c88a4474137db8af2ee6cf4fa6e49c0de4c48e5 100644 --- a/polkadot/node/subsystem-types/Cargo.toml +++ b/polkadot/node/subsystem-types/Cargo.toml @@ -5,30 +5,32 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +async-trait = { workspace = true } +bitvec = { features = ["alloc"], workspace = true } derive_more = { workspace = true, default-features = true } fatality = { workspace = true } futures = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } +orchestra = { features = ["futures_channel"], workspace = true } polkadot-node-network-protocol = { workspace = true, default-features = true } +polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } polkadot-statement-table = { workspace = true, default-features = true } -orchestra = { features = ["futures_channel"], workspace = true } +prometheus-endpoint = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } +sc-transaction-pool-api = { workspace = true, default-features = true } +smallvec = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } +sp-authority-discovery = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus-babe = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-authority-discovery = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sc-transaction-pool-api = { workspace = true, default-features = true } -smallvec = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } thiserror = { workspace = true } -async-trait = { workspace = true } -bitvec = { features = ["alloc"], workspace = true } diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs index 28a3a1ab82ab20bdb92e023e56a93ca12b846391..4d4fc89a6addc10866ff7c2b00af75c7a28b2ae3 100644 --- a/polkadot/node/subsystem-types/src/messages.rs +++ b/polkadot/node/subsystem-types/src/messages.rs @@ -42,18 +42,18 @@ use polkadot_node_primitives::{ ValidationResult, }; use polkadot_primitives::{ - async_backing, slashing, vstaging, + async_backing, slashing, vstaging::{ - BackedCandidate, CandidateReceiptV2 as CandidateReceipt, + self, async_backing::Constraints, BackedCandidate, CandidateReceiptV2 as CandidateReceipt, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, }, ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateHash, - CandidateIndex, CollatorId, CoreIndex, DisputeState, ExecutorParams, GroupIndex, - GroupRotationInfo, Hash, HeadData, Header as BlockHeader, Id as ParaId, InboundDownwardMessage, - InboundHrmpMessage, MultiDisputeStatementSet, NodeFeatures, OccupiedCoreAssumption, - PersistedValidationData, PvfCheckStatement, PvfExecKind as RuntimePvfExecKind, SessionIndex, - SessionInfo, SignedAvailabilityBitfield, SignedAvailabilityBitfields, ValidationCode, - ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, + CandidateIndex, CoreIndex, DisputeState, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, + HeadData, Header as BlockHeader, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, + MultiDisputeStatementSet, NodeFeatures, OccupiedCoreAssumption, PersistedValidationData, + PvfCheckStatement, PvfExecKind as RuntimePvfExecKind, SessionIndex, SessionInfo, + SignedAvailabilityBitfield, SignedAvailabilityBitfields, ValidationCode, ValidationCodeHash, + ValidatorId, ValidatorIndex, ValidatorSignature, }; use polkadot_statement_table::v2::Misbehavior; use std::{ @@ -250,9 +250,6 @@ pub enum CollatorProtocolMessage { /// The core index where the candidate should be backed. core_index: CoreIndex, }, - /// Report a collator as having provided an invalid collation. This should lead to disconnect - /// and blacklist of the collator. - ReportCollator(CollatorId), /// Get a network bridge update. #[from] NetworkBridgeUpdate(NetworkBridgeEvent<net_protocol::CollatorProtocolMessage>), @@ -775,6 +772,12 @@ pub enum RuntimeApiRequest { /// Get the candidates pending availability for a particular parachain /// `V11` CandidatesPendingAvailability(ParaId, RuntimeApiSender<Vec<CommittedCandidateReceipt>>), + /// Get the backing constraints for a particular parachain. + /// `V12` + BackingConstraints(ParaId, RuntimeApiSender<Option<Constraints>>), + /// Get the lookahead from the scheduler params. + /// `V12` + SchedulingLookahead(SessionIndex, RuntimeApiSender<u32>), } impl RuntimeApiRequest { @@ -815,6 +818,12 @@ impl RuntimeApiRequest { /// `candidates_pending_availability` pub const CANDIDATES_PENDING_AVAILABILITY_RUNTIME_REQUIREMENT: u32 = 11; + + /// `backing_constraints` + pub const CONSTRAINTS_RUNTIME_REQUIREMENT: u32 = 12; + + /// `SchedulingLookahead` + pub const SCHEDULING_LOOKAHEAD_RUNTIME_REQUIREMENT: u32 = 12; } /// A message to the Runtime API subsystem. @@ -849,9 +858,6 @@ pub enum StatementDistributionMessage { pub enum ProvisionableData { /// This bitfield indicates the availability of various candidate blocks. Bitfield(Hash, SignedAvailabilityBitfield), - /// The Candidate Backing subsystem believes that this candidate is valid, pending - /// availability. - BackedCandidate(CandidateReceipt), /// Misbehavior reports are self-contained proofs of validator misbehavior. MisbehaviorReport(Hash, ValidatorIndex, Misbehavior), /// Disputes trigger a broad dispute resolution process. diff --git a/polkadot/node/subsystem-types/src/runtime_client.rs b/polkadot/node/subsystem-types/src/runtime_client.rs index 4b96009f44bf89ca2365f82f6303c7e48e1aacdb..7e3849c20694dc2343bcd32e644b79af03ac761e 100644 --- a/polkadot/node/subsystem-types/src/runtime_client.rs +++ b/polkadot/node/subsystem-types/src/runtime_client.rs @@ -18,10 +18,10 @@ use async_trait::async_trait; use polkadot_primitives::{ async_backing, runtime_api::ParachainHost, - slashing, vstaging, + slashing, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + self, async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, Block, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Header, Id, InboundDownwardMessage, @@ -241,23 +241,18 @@ pub trait RuntimeApiSubsystemClient { /***** Added in v3 **** */ /// Returns all onchain disputes. - /// This is a staging method! Do not use on production runtimes! async fn disputes( &self, at: Hash, ) -> Result<Vec<(SessionIndex, CandidateHash, DisputeState<BlockNumber>)>, ApiError>; /// Returns a list of validators that lost a past session dispute and need to be slashed. - /// - /// WARNING: This is a staging method! Do not use on production runtimes! async fn unapplied_slashes( &self, at: Hash, ) -> Result<Vec<(SessionIndex, CandidateHash, slashing::PendingSlashes)>, ApiError>; /// Returns a merkle proof of a validator session key in a past session. - /// - /// WARNING: This is a staging method! Do not use on production runtimes! async fn key_ownership_proof( &self, at: Hash, @@ -266,8 +261,6 @@ pub trait RuntimeApiSubsystemClient { /// Submits an unsigned extrinsic to slash validators who lost a dispute about /// a candidate of a past session. - /// - /// WARNING: This is a staging method! Do not use on production runtimes! async fn submit_report_dispute_lost( &self, at: Hash, @@ -347,6 +340,19 @@ pub trait RuntimeApiSubsystemClient { at: Hash, para_id: Id, ) -> Result<Vec<CommittedCandidateReceipt<Hash>>, ApiError>; + + // == v12 == + /// Get the constraints on the actions that can be taken by a new parachain + /// block. + async fn backing_constraints( + &self, + at: Hash, + para_id: Id, + ) -> Result<Option<Constraints>, ApiError>; + + // === v12 === + /// Fetch the scheduling lookahead value + async fn scheduling_lookahead(&self, at: Hash) -> Result<u32, ApiError>; } /// Default implementation of [`RuntimeApiSubsystemClient`] using the client. @@ -624,6 +630,18 @@ where async fn claim_queue(&self, at: Hash) -> Result<BTreeMap<CoreIndex, VecDeque<Id>>, ApiError> { self.client.runtime_api().claim_queue(at) } + + async fn backing_constraints( + &self, + at: Hash, + para_id: Id, + ) -> Result<Option<Constraints>, ApiError> { + self.client.runtime_api().backing_constraints(at, para_id) + } + + async fn scheduling_lookahead(&self, at: Hash) -> Result<u32, ApiError> { + self.client.runtime_api().scheduling_lookahead(at) + } } impl<Client, Block> HeaderBackend<Block> for DefaultSubsystemClient<Client> diff --git a/polkadot/node/subsystem-util/Cargo.toml b/polkadot/node/subsystem-util/Cargo.toml index d12daa57205521243251200fabc4aead5286e023..0e6ebf61199723260b92a8c445719ddc8d363dd0 100644 --- a/polkadot/node/subsystem-util/Cargo.toml +++ b/polkadot/node/subsystem-util/Cargo.toml @@ -5,39 +5,41 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] async-trait = { workspace = true } +codec = { features = ["derive"], workspace = true } +derive_more = { workspace = true, default-features = true } +fatality = { workspace = true } futures = { workspace = true } futures-channel = { workspace = true } +gum = { workspace = true, default-features = true } itertools = { workspace = true } -codec = { features = ["derive"], workspace = true } parking_lot = { workspace = true, default-features = true } pin-project = { workspace = true } rand = { workspace = true, default-features = true } -thiserror = { workspace = true } -fatality = { workspace = true } -gum = { workspace = true, default-features = true } -derive_more = { workspace = true, default-features = true } schnellru = { workspace = true } +thiserror = { workspace = true } +metered = { features = ["futures_channel"], workspace = true } polkadot-erasure-coding = { workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } -polkadot-node-subsystem-types = { workspace = true, default-features = true } polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-node-subsystem-types = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } -metered = { features = ["futures_channel"], workspace = true } +polkadot-primitives = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } sp-application-crypto = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } kvdb = { workspace = true } parity-db = { workspace = true } @@ -45,9 +47,9 @@ parity-db = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } futures = { features = ["thread-pool"], workspace = true } +kvdb-memorydb = { workspace = true } +kvdb-shared-tests = { workspace = true } log = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-primitives-test-helpers = { workspace = true } -kvdb-shared-tests = { workspace = true } tempfile = { workspace = true } -kvdb-memorydb = { workspace = true } diff --git a/polkadot/node/subsystem-util/src/backing_implicit_view.rs b/polkadot/node/subsystem-util/src/backing_implicit_view.rs index 6f2191e7add2adad68e2a197fac868d7a9aff2f1..d8e242109955a1ef778c88d6f5161dd05b4feb8a 100644 --- a/polkadot/node/subsystem-util/src/backing_implicit_view.rs +++ b/polkadot/node/subsystem-util/src/backing_implicit_view.rs @@ -20,14 +20,15 @@ use polkadot_node_subsystem::{ messages::{ChainApiMessage, ProspectiveParachainsMessage, RuntimeApiMessage}, SubsystemSender, }; -use polkadot_primitives::{AsyncBackingParams, BlockNumber, Hash, Id as ParaId}; +use polkadot_primitives::{BlockNumber, Hash, Id as ParaId}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use crate::{ inclusion_emulator::RelayChainBlockInfo, - request_async_backing_params, request_session_index_for_child, - runtime::{self, recv_runtime}, + request_session_index_for_child, + runtime::{self, fetch_scheduling_lookahead, recv_runtime}, + LOG_TARGET, }; // Always aim to retain 1 block before the active leaves. @@ -148,6 +149,11 @@ impl View { self.leaves.keys() } + /// Check if the given block hash is an active leaf of the current view. + pub fn contains_leaf(&self, leaf_hash: &Hash) -> bool { + self.leaves.contains_key(leaf_hash) + } + /// Activate a leaf in the view. /// This will request the minimum relay parents the leaf and will load headers in the /// ancestry of the leaf as needed. These are the 'implicit ancestors' of the leaf. @@ -173,13 +179,7 @@ impl View { return Err(FetchError::AlreadyKnown) } - let res = fetch_fresh_leaf_and_insert_ancestry( - leaf_hash, - &mut self.block_info_storage, - &mut *sender, - self.collating_for, - ) - .await; + let res = self.fetch_fresh_leaf_and_insert_ancestry(leaf_hash, &mut *sender).await; match res { Ok(fetched) => { @@ -323,6 +323,205 @@ impl View { .as_ref() .map(|mins| mins.allowed_relay_parents_for(para_id, block_info.block_number)) } + + /// Returns all paths from each leaf to the last block in state containing `relay_parent`. If no + /// paths exist the function will return an empty `Vec`. + pub fn paths_via_relay_parent(&self, relay_parent: &Hash) -> Vec<Vec<Hash>> { + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + leaves=?self.leaves, + block_info_storage=?self.block_info_storage, + "Finding paths via relay parent" + ); + + if self.leaves.is_empty() { + // No leaves so the view should be empty. Don't return any paths. + return vec![] + }; + + if !self.block_info_storage.contains_key(relay_parent) { + // `relay_parent` is not in the view - don't return any paths + return vec![] + } + + // Find all paths from each leaf to `relay_parent`. + let mut paths = Vec::new(); + for (leaf, _) in &self.leaves { + let mut path = Vec::new(); + let mut current_leaf = *leaf; + let mut visited = HashSet::new(); + let mut path_contains_target = false; + + // Start from the leaf and traverse all known blocks + loop { + if visited.contains(¤t_leaf) { + // There is a cycle - abandon this path + break + } + + current_leaf = match self.block_info_storage.get(¤t_leaf) { + Some(info) => { + // `current_leaf` is a known block - add it to the path and mark it as + // visited + path.push(current_leaf); + visited.insert(current_leaf); + + // `current_leaf` is the target `relay_parent`. Mark the path so that it's + // included in the result + if current_leaf == *relay_parent { + path_contains_target = true; + } + + // update `current_leaf` with the parent + info.parent_hash + }, + None => { + // path is complete + if path_contains_target { + // we want the path ordered from oldest to newest so reverse it + paths.push(path.into_iter().rev().collect()); + } + break + }, + }; + } + } + + paths + } + + async fn fetch_fresh_leaf_and_insert_ancestry<Sender>( + &mut self, + leaf_hash: Hash, + sender: &mut Sender, + ) -> Result<FetchSummary, FetchError> + where + Sender: SubsystemSender<ChainApiMessage> + + SubsystemSender<ProspectiveParachainsMessage> + + SubsystemSender<RuntimeApiMessage>, + { + let leaf_header = { + let (tx, rx) = oneshot::channel(); + sender.send_message(ChainApiMessage::BlockHeader(leaf_hash, tx)).await; + + match rx.await { + Ok(Ok(Some(header))) => header, + Ok(Ok(None)) => + return Err(FetchError::BlockHeaderUnavailable( + leaf_hash, + BlockHeaderUnavailableReason::Unknown, + )), + Ok(Err(e)) => + return Err(FetchError::BlockHeaderUnavailable( + leaf_hash, + BlockHeaderUnavailableReason::Internal(e), + )), + Err(_) => + return Err(FetchError::BlockHeaderUnavailable( + leaf_hash, + BlockHeaderUnavailableReason::SubsystemUnavailable, + )), + } + }; + + // If the node is a collator, bypass prospective-parachains. We're only interested in the + // one paraid and the subsystem is not present. + let min_relay_parents = if let Some(para_id) = self.collating_for { + fetch_min_relay_parents_for_collator(leaf_hash, leaf_header.number, sender) + .await? + .map(|x| vec![(para_id, x)]) + .unwrap_or_default() + } else { + fetch_min_relay_parents_from_prospective_parachains(leaf_hash, sender).await? + }; + + let min_min = min_relay_parents.iter().map(|x| x.1).min().unwrap_or(leaf_header.number); + let expected_ancestry_len = (leaf_header.number.saturating_sub(min_min) as usize) + 1; + + let ancestry = if leaf_header.number > 0 { + let mut next_ancestor_number = leaf_header.number - 1; + let mut next_ancestor_hash = leaf_header.parent_hash; + + let mut ancestry = Vec::with_capacity(expected_ancestry_len); + ancestry.push(leaf_hash); + + // Ensure all ancestors up to and including `min_min` are in the + // block storage. When views advance incrementally, everything + // should already be present. + while next_ancestor_number >= min_min { + let parent_hash = if let Some(info) = + self.block_info_storage.get(&next_ancestor_hash) + { + info.parent_hash + } else { + // load the header and insert into block storage. + let (tx, rx) = oneshot::channel(); + sender.send_message(ChainApiMessage::BlockHeader(next_ancestor_hash, tx)).await; + + let header = match rx.await { + Ok(Ok(Some(header))) => header, + Ok(Ok(None)) => + return Err(FetchError::BlockHeaderUnavailable( + next_ancestor_hash, + BlockHeaderUnavailableReason::Unknown, + )), + Ok(Err(e)) => + return Err(FetchError::BlockHeaderUnavailable( + next_ancestor_hash, + BlockHeaderUnavailableReason::Internal(e), + )), + Err(_) => + return Err(FetchError::BlockHeaderUnavailable( + next_ancestor_hash, + BlockHeaderUnavailableReason::SubsystemUnavailable, + )), + }; + + self.block_info_storage.insert( + next_ancestor_hash, + BlockInfo { + block_number: next_ancestor_number, + parent_hash: header.parent_hash, + maybe_allowed_relay_parents: None, + }, + ); + + header.parent_hash + }; + + ancestry.push(next_ancestor_hash); + if next_ancestor_number == 0 { + break + } + + next_ancestor_number -= 1; + next_ancestor_hash = parent_hash; + } + + ancestry + } else { + vec![leaf_hash] + }; + + let fetched_ancestry = + FetchSummary { minimum_ancestor_number: min_min, leaf_number: leaf_header.number }; + + let allowed_relay_parents = AllowedRelayParents { + minimum_relay_parents: min_relay_parents.into_iter().collect(), + allowed_relay_parents_contiguous: ancestry, + }; + + let leaf_block_info = BlockInfo { + parent_hash: leaf_header.parent_hash, + block_number: leaf_header.number, + maybe_allowed_relay_parents: Some(allowed_relay_parents), + }; + + self.block_info_storage.insert(leaf_hash, leaf_block_info); + + Ok(fetched_ancestry) + } } /// Errors when fetching a leaf and associated ancestry. @@ -396,22 +595,22 @@ where + SubsystemSender<RuntimeApiMessage> + SubsystemSender<ChainApiMessage>, { - let AsyncBackingParams { allowed_ancestry_len, .. } = - recv_runtime(request_async_backing_params(leaf_hash, sender).await).await?; - // Fetch the session of the leaf. We must make sure that we stop at the ancestor which has a // different session index. let required_session = recv_runtime(request_session_index_for_child(leaf_hash, sender).await).await?; + let scheduling_lookahead = + fetch_scheduling_lookahead(leaf_hash, required_session, sender).await?; + let mut min = leaf_number; - // Fetch the ancestors, up to allowed_ancestry_len. + // Fetch the ancestors, up to (scheduling_lookahead - 1). let (tx, rx) = oneshot::channel(); sender .send_message(ChainApiMessage::Ancestors { hash: leaf_hash, - k: allowed_ancestry_len as usize, + k: scheduling_lookahead.saturating_sub(1) as usize, response_channel: tx, }) .await; @@ -437,137 +636,6 @@ where Ok(Some(min)) } -async fn fetch_fresh_leaf_and_insert_ancestry<Sender>( - leaf_hash: Hash, - block_info_storage: &mut HashMap<Hash, BlockInfo>, - sender: &mut Sender, - collating_for: Option<ParaId>, -) -> Result<FetchSummary, FetchError> -where - Sender: SubsystemSender<ChainApiMessage> - + SubsystemSender<ProspectiveParachainsMessage> - + SubsystemSender<RuntimeApiMessage>, -{ - let leaf_header = { - let (tx, rx) = oneshot::channel(); - sender.send_message(ChainApiMessage::BlockHeader(leaf_hash, tx)).await; - - match rx.await { - Ok(Ok(Some(header))) => header, - Ok(Ok(None)) => - return Err(FetchError::BlockHeaderUnavailable( - leaf_hash, - BlockHeaderUnavailableReason::Unknown, - )), - Ok(Err(e)) => - return Err(FetchError::BlockHeaderUnavailable( - leaf_hash, - BlockHeaderUnavailableReason::Internal(e), - )), - Err(_) => - return Err(FetchError::BlockHeaderUnavailable( - leaf_hash, - BlockHeaderUnavailableReason::SubsystemUnavailable, - )), - } - }; - - // If the node is a collator, bypass prospective-parachains. We're only interested in the one - // paraid and the subsystem is not present. - let min_relay_parents = if let Some(para_id) = collating_for { - fetch_min_relay_parents_for_collator(leaf_hash, leaf_header.number, sender) - .await? - .map(|x| vec![(para_id, x)]) - .unwrap_or_default() - } else { - fetch_min_relay_parents_from_prospective_parachains(leaf_hash, sender).await? - }; - - let min_min = min_relay_parents.iter().map(|x| x.1).min().unwrap_or(leaf_header.number); - let expected_ancestry_len = (leaf_header.number.saturating_sub(min_min) as usize) + 1; - - let ancestry = if leaf_header.number > 0 { - let mut next_ancestor_number = leaf_header.number - 1; - let mut next_ancestor_hash = leaf_header.parent_hash; - - let mut ancestry = Vec::with_capacity(expected_ancestry_len); - ancestry.push(leaf_hash); - - // Ensure all ancestors up to and including `min_min` are in the - // block storage. When views advance incrementally, everything - // should already be present. - while next_ancestor_number >= min_min { - let parent_hash = if let Some(info) = block_info_storage.get(&next_ancestor_hash) { - info.parent_hash - } else { - // load the header and insert into block storage. - let (tx, rx) = oneshot::channel(); - sender.send_message(ChainApiMessage::BlockHeader(next_ancestor_hash, tx)).await; - - let header = match rx.await { - Ok(Ok(Some(header))) => header, - Ok(Ok(None)) => - return Err(FetchError::BlockHeaderUnavailable( - next_ancestor_hash, - BlockHeaderUnavailableReason::Unknown, - )), - Ok(Err(e)) => - return Err(FetchError::BlockHeaderUnavailable( - next_ancestor_hash, - BlockHeaderUnavailableReason::Internal(e), - )), - Err(_) => - return Err(FetchError::BlockHeaderUnavailable( - next_ancestor_hash, - BlockHeaderUnavailableReason::SubsystemUnavailable, - )), - }; - - block_info_storage.insert( - next_ancestor_hash, - BlockInfo { - block_number: next_ancestor_number, - parent_hash: header.parent_hash, - maybe_allowed_relay_parents: None, - }, - ); - - header.parent_hash - }; - - ancestry.push(next_ancestor_hash); - if next_ancestor_number == 0 { - break - } - - next_ancestor_number -= 1; - next_ancestor_hash = parent_hash; - } - - ancestry - } else { - vec![leaf_hash] - }; - - let fetched_ancestry = - FetchSummary { minimum_ancestor_number: min_min, leaf_number: leaf_header.number }; - - let allowed_relay_parents = AllowedRelayParents { - minimum_relay_parents: min_relay_parents.into_iter().collect(), - allowed_relay_parents_contiguous: ancestry, - }; - - let leaf_block_info = BlockInfo { - parent_hash: leaf_header.parent_hash, - block_number: leaf_header.number, - maybe_allowed_relay_parents: Some(allowed_relay_parents), - }; - - block_info_storage.insert(leaf_hash, leaf_block_info); - - Ok(fetched_ancestry) -} - #[cfg(test)] mod tests { use super::*; @@ -579,7 +647,7 @@ mod tests { make_subsystem_context, TestSubsystemContextHandle, }; use polkadot_overseer::SubsystemContext; - use polkadot_primitives::{AsyncBackingParams, Header}; + use polkadot_primitives::Header; use sp_core::testing::TaskExecutor; use std::time::Duration; @@ -680,23 +748,24 @@ mod tests { ); } - async fn assert_async_backing_params_request( + async fn assert_scheduling_lookahead_request( virtual_overseer: &mut VirtualOverseer, leaf: Hash, - params: AsyncBackingParams, + lookahead: u32, ) { assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::RuntimeApi( RuntimeApiMessage::Request( leaf_hash, - RuntimeApiRequest::AsyncBackingParams( + RuntimeApiRequest::SchedulingLookahead( + _, tx ) ) ) => { assert_eq!(leaf, leaf_hash, "received unexpected leaf hash"); - tx.send(Ok(params)).unwrap(); + tx.send(Ok(lookahead)).unwrap(); } ); } @@ -798,6 +867,23 @@ mod tests { assert_eq!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_A)), Some(&expected_ancestry[..(PARA_A_MIN_PARENT - 1) as usize])); assert_eq!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_B)), Some(&expected_ancestry[..])); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_C)).unwrap().is_empty()); + + assert_eq!(view.leaves.len(), 1); + assert!(view.leaves.contains_key(leaf)); + assert!(view.paths_via_relay_parent(&CHAIN_B[0]).is_empty()); + assert!(view.paths_via_relay_parent(&CHAIN_A[0]).is_empty()); + assert_eq!( + view.paths_via_relay_parent(&CHAIN_B[min_min_idx]), + vec![CHAIN_B[min_min_idx..].to_vec()] + ); + assert_eq!( + view.paths_via_relay_parent(&CHAIN_B[min_min_idx + 1]), + vec![CHAIN_B[min_min_idx..].to_vec()] + ); + assert_eq!( + view.paths_via_relay_parent(&leaf), + vec![CHAIN_B[min_min_idx..].to_vec()] + ); } ); @@ -866,18 +952,11 @@ mod tests { let overseer_fut = async { assert_block_header_requests(&mut ctx_handle, CHAIN_B, &CHAIN_B[leaf_idx..]).await; - assert_async_backing_params_request( - &mut ctx_handle, - *leaf, - AsyncBackingParams { - max_candidate_depth: 0, - allowed_ancestry_len: PARA_A_MIN_PARENT, - }, - ) - .await; - assert_session_index_request(&mut ctx_handle, *leaf, current_session).await; + assert_scheduling_lookahead_request(&mut ctx_handle, *leaf, PARA_A_MIN_PARENT + 1) + .await; + assert_ancestors_request( &mut ctx_handle, *leaf, @@ -918,6 +997,12 @@ mod tests { assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_B)).unwrap().is_empty()); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_C)).unwrap().is_empty()); + + assert!(view.paths_via_relay_parent(&CHAIN_A[0]).is_empty()); + assert_eq!( + view.paths_via_relay_parent(&CHAIN_B[min_min_idx]), + vec![CHAIN_B[min_min_idx..].to_vec()] + ); } ); @@ -934,18 +1019,11 @@ mod tests { let overseer_fut = async { assert_block_header_requests(&mut ctx_handle, CHAIN_A, &blocks[leaf_idx..]).await; - assert_async_backing_params_request( - &mut ctx_handle, - *leaf, - AsyncBackingParams { - max_candidate_depth: 0, - allowed_ancestry_len: blocks.len() as u32, - }, - ) - .await; - assert_session_index_request(&mut ctx_handle, *leaf, current_session).await; + assert_scheduling_lookahead_request(&mut ctx_handle, *leaf, blocks.len() as u32 + 1) + .await; + assert_ancestors_request( &mut ctx_handle, *leaf, @@ -986,6 +1064,12 @@ mod tests { assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_B)).unwrap().is_empty()); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_C)).unwrap().is_empty()); + + assert!(view.paths_via_relay_parent(&GENESIS_HASH).is_empty()); + assert_eq!( + view.paths_via_relay_parent(&CHAIN_A[0]), + vec![CHAIN_A.to_vec()] + ); } ); } @@ -1160,4 +1244,69 @@ mod tests { Some(hashes) if hashes == &[GENESIS_HASH] ); } + + #[test] + fn path_with_fork() { + let pool = TaskExecutor::new(); + let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool); + + let mut view = View::default(); + + assert_eq!(view.collating_for, None); + + // Chain A + let prospective_response = vec![(PARA_A, 0)]; // was PARA_A_MIN_PARENT + let leaf = CHAIN_A.last().unwrap(); + let blocks = [&[GENESIS_HASH], CHAIN_A].concat(); + let leaf_idx = blocks.len() - 1; + + let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| { + res.expect("`activate_leaf` timed out").unwrap(); + }); + let overseer_fut = async { + assert_block_header_requests(&mut ctx_handle, CHAIN_A, &blocks[leaf_idx..]).await; + assert_min_relay_parents_request(&mut ctx_handle, leaf, prospective_response).await; + assert_block_header_requests(&mut ctx_handle, CHAIN_A, &blocks[..leaf_idx]).await; + }; + futures::executor::block_on(join(fut, overseer_fut)); + + // Chain B + let prospective_response = vec![(PARA_A, 1)]; + + let leaf = CHAIN_B.last().unwrap(); + let leaf_idx = CHAIN_B.len() - 1; + + let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| { + res.expect("`activate_leaf` timed out").unwrap(); + }); + let overseer_fut = async { + assert_block_header_requests(&mut ctx_handle, CHAIN_B, &CHAIN_B[leaf_idx..]).await; + assert_min_relay_parents_request(&mut ctx_handle, leaf, prospective_response).await; + assert_block_header_requests(&mut ctx_handle, CHAIN_B, &CHAIN_B[0..leaf_idx]).await; + }; + futures::executor::block_on(join(fut, overseer_fut)); + + assert_eq!(view.leaves.len(), 2); + + let mut paths_to_genesis = view.paths_via_relay_parent(&GENESIS_HASH); + paths_to_genesis.sort(); + let mut expected_paths_to_genesis = vec![ + [GENESIS_HASH].iter().chain(CHAIN_A.iter()).copied().collect::<Vec<_>>(), + [GENESIS_HASH].iter().chain(CHAIN_B.iter()).copied().collect::<Vec<_>>(), + ]; + expected_paths_to_genesis.sort(); + assert_eq!(paths_to_genesis, expected_paths_to_genesis); + + let path_to_leaf_in_a = view.paths_via_relay_parent(&CHAIN_A[1]); + let expected_path_to_leaf_in_a = + vec![[GENESIS_HASH].iter().chain(CHAIN_A.iter()).copied().collect::<Vec<_>>()]; + assert_eq!(path_to_leaf_in_a, expected_path_to_leaf_in_a); + + let path_to_leaf_in_b = view.paths_via_relay_parent(&CHAIN_B[4]); + let expected_path_to_leaf_in_b = + vec![[GENESIS_HASH].iter().chain(CHAIN_B.iter()).copied().collect::<Vec<_>>()]; + assert_eq!(path_to_leaf_in_b, expected_path_to_leaf_in_b); + + assert_eq!(view.paths_via_relay_parent(&Hash::repeat_byte(0x0A)), Vec::<Vec<Hash>>::new()); + } } diff --git a/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs b/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs index 48d3f27b1fa6d0b99a30515de26ffaa3e793ce1e..8a620db4ab0c93c185a894345d24c40c7b85dd38 100644 --- a/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs +++ b/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs @@ -82,9 +82,10 @@ /// in practice at most once every few weeks. use polkadot_node_subsystem::messages::HypotheticalCandidate; use polkadot_primitives::{ - async_backing::Constraints as PrimitiveConstraints, vstaging::skip_ump_signals, BlockNumber, - CandidateCommitments, CandidateHash, Hash, HeadData, Id as ParaId, PersistedValidationData, - UpgradeRestriction, ValidationCodeHash, + async_backing::Constraints as OldPrimitiveConstraints, + vstaging::{async_backing::Constraints as PrimitiveConstraints, skip_ump_signals}, + BlockNumber, CandidateCommitments, CandidateHash, Hash, HeadData, Id as ParaId, + PersistedValidationData, UpgradeRestriction, ValidationCodeHash, }; use std::{collections::HashMap, sync::Arc}; @@ -115,6 +116,8 @@ pub struct Constraints { pub max_pov_size: usize, /// The maximum new validation code size allowed, in bytes. pub max_code_size: usize, + /// The maximum head-data size, in bytes. + pub max_head_data_size: usize, /// The amount of UMP messages remaining. pub ump_remaining: usize, /// The amount of UMP bytes remaining. @@ -146,6 +149,44 @@ impl From<PrimitiveConstraints> for Constraints { min_relay_parent_number: c.min_relay_parent_number, max_pov_size: c.max_pov_size as _, max_code_size: c.max_code_size as _, + max_head_data_size: c.max_head_data_size as _, + ump_remaining: c.ump_remaining as _, + ump_remaining_bytes: c.ump_remaining_bytes as _, + max_ump_num_per_candidate: c.max_ump_num_per_candidate as _, + dmp_remaining_messages: c.dmp_remaining_messages, + hrmp_inbound: InboundHrmpLimitations { + valid_watermarks: c.hrmp_inbound.valid_watermarks, + }, + hrmp_channels_out: c + .hrmp_channels_out + .into_iter() + .map(|(para_id, limits)| { + ( + para_id, + OutboundHrmpChannelLimitations { + bytes_remaining: limits.bytes_remaining as _, + messages_remaining: limits.messages_remaining as _, + }, + ) + }) + .collect(), + max_hrmp_num_per_candidate: c.max_hrmp_num_per_candidate as _, + required_parent: c.required_parent, + validation_code_hash: c.validation_code_hash, + upgrade_restriction: c.upgrade_restriction, + future_validation_code: c.future_validation_code, + } + } +} + +impl From<OldPrimitiveConstraints> for Constraints { + fn from(c: OldPrimitiveConstraints) -> Self { + Constraints { + min_relay_parent_number: c.min_relay_parent_number, + max_pov_size: c.max_pov_size as _, + max_code_size: c.max_code_size as _, + // Equal to Polkadot/Kusama config. + max_head_data_size: 20480, ump_remaining: c.ump_remaining as _, ump_remaining_bytes: c.ump_remaining_bytes as _, max_ump_num_per_candidate: c.max_ump_num_per_candidate as _, @@ -520,6 +561,10 @@ pub enum FragmentValidityError { /// /// Max allowed, new. CodeSizeTooLarge(usize, usize), + /// Head data size too big. + /// + /// Max allowed, new. + HeadDataTooLarge(usize, usize), /// Relay parent too old. /// /// Min allowed, current. @@ -686,28 +731,13 @@ impl Fragment { } } -fn validate_against_constraints( +/// Validates if the candidate commitments are obeying the constraints. +pub fn validate_commitments( constraints: &Constraints, relay_parent: &RelayChainBlockInfo, commitments: &CandidateCommitments, - persisted_validation_data: &PersistedValidationData, validation_code_hash: &ValidationCodeHash, - modifications: &ConstraintModifications, ) -> Result<(), FragmentValidityError> { - let expected_pvd = PersistedValidationData { - parent_head: constraints.required_parent.clone(), - relay_parent_number: relay_parent.number, - relay_parent_storage_root: relay_parent.storage_root, - max_pov_size: constraints.max_pov_size as u32, - }; - - if expected_pvd != *persisted_validation_data { - return Err(FragmentValidityError::PersistedValidationDataMismatch( - expected_pvd, - persisted_validation_data.clone(), - )) - } - if constraints.validation_code_hash != *validation_code_hash { return Err(FragmentValidityError::ValidationCodeMismatch( constraints.validation_code_hash, @@ -715,6 +745,13 @@ fn validate_against_constraints( )) } + if commitments.head_data.0.len() > constraints.max_head_data_size { + return Err(FragmentValidityError::HeadDataTooLarge( + constraints.max_head_data_size, + commitments.head_data.0.len(), + )) + } + if relay_parent.number < constraints.min_relay_parent_number { return Err(FragmentValidityError::RelayParentTooOld( constraints.min_relay_parent_number, @@ -740,6 +777,39 @@ fn validate_against_constraints( )) } + if commitments.horizontal_messages.len() > constraints.max_hrmp_num_per_candidate { + return Err(FragmentValidityError::HrmpMessagesPerCandidateOverflow { + messages_allowed: constraints.max_hrmp_num_per_candidate, + messages_submitted: commitments.horizontal_messages.len(), + }) + } + + Ok(()) +} + +fn validate_against_constraints( + constraints: &Constraints, + relay_parent: &RelayChainBlockInfo, + commitments: &CandidateCommitments, + persisted_validation_data: &PersistedValidationData, + validation_code_hash: &ValidationCodeHash, + modifications: &ConstraintModifications, +) -> Result<(), FragmentValidityError> { + validate_commitments(constraints, relay_parent, commitments, validation_code_hash)?; + + let expected_pvd = PersistedValidationData { + parent_head: constraints.required_parent.clone(), + relay_parent_number: relay_parent.number, + relay_parent_storage_root: relay_parent.storage_root, + max_pov_size: constraints.max_pov_size as u32, + }; + + if expected_pvd != *persisted_validation_data { + return Err(FragmentValidityError::PersistedValidationDataMismatch( + expected_pvd, + persisted_validation_data.clone(), + )) + } if modifications.dmp_messages_processed == 0 { if constraints .dmp_remaining_messages @@ -750,20 +820,12 @@ fn validate_against_constraints( } } - if commitments.horizontal_messages.len() > constraints.max_hrmp_num_per_candidate { - return Err(FragmentValidityError::HrmpMessagesPerCandidateOverflow { - messages_allowed: constraints.max_hrmp_num_per_candidate, - messages_submitted: commitments.horizontal_messages.len(), - }) - } - if modifications.ump_messages_sent > constraints.max_ump_num_per_candidate { return Err(FragmentValidityError::UmpMessagesPerCandidateOverflow { messages_allowed: constraints.max_ump_num_per_candidate, messages_submitted: commitments.upward_messages.len(), }) } - constraints .check_modifications(&modifications) .map_err(FragmentValidityError::OutputsInvalid) @@ -971,6 +1033,7 @@ mod tests { validation_code_hash: ValidationCode(vec![4, 5, 6]).hash(), upgrade_restriction: None, future_validation_code: None, + max_head_data_size: 1024, } } @@ -1478,4 +1541,24 @@ mod tests { Err(FragmentValidityError::HrmpMessagesDescendingOrDuplicate(1)), ); } + + #[test] + fn head_data_size_too_large() { + let relay_parent = RelayChainBlockInfo { + number: 6, + hash: Hash::repeat_byte(0xcc), + storage_root: Hash::repeat_byte(0xff), + }; + + let constraints = make_constraints(); + let mut candidate = make_candidate(&constraints, &relay_parent); + + let head_data_size = constraints.max_head_data_size; + candidate.commitments.head_data = vec![0; head_data_size + 1].into(); + + assert_eq!( + Fragment::new(relay_parent, constraints, Arc::new(candidate.clone())), + Err(FragmentValidityError::HeadDataTooLarge(head_data_size, head_data_size + 1)), + ); + } } diff --git a/polkadot/node/subsystem-util/src/lib.rs b/polkadot/node/subsystem-util/src/lib.rs index 3bed18558941951b0a7bab79b8979cb703b7fd09..6b069ee86113f39e3f3beca8b5c2309a1739763e 100644 --- a/polkadot/node/subsystem-util/src/lib.rs +++ b/polkadot/node/subsystem-util/src/lib.rs @@ -43,8 +43,9 @@ use futures::channel::{mpsc, oneshot}; use polkadot_primitives::{ slashing, vstaging::{ - async_backing::BackingState, CandidateEvent, - CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, + async_backing::{BackingState, Constraints}, + CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, + ScrapedOnChainVotes, }, AsyncBackingParams, AuthorityDiscoveryId, CandidateHash, CoreIndex, EncodeAs, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, Id as ParaId, OccupiedCoreAssumption, @@ -313,6 +314,8 @@ specialize_requests! { fn request_async_backing_params() -> AsyncBackingParams; AsyncBackingParams; fn request_claim_queue() -> BTreeMap<CoreIndex, VecDeque<ParaId>>; ClaimQueue; fn request_para_backing_state(para_id: ParaId) -> Option<BackingState>; ParaBackingState; + fn request_backing_constraints(para_id: ParaId) -> Option<Constraints>; BackingConstraints; + } /// Requests executor parameters from the runtime effective at given relay-parent. First obtains diff --git a/polkadot/node/subsystem-util/src/runtime/mod.rs b/polkadot/node/subsystem-util/src/runtime/mod.rs index d84951ae1366518c5a7037dcbe227c9ec574ea66..dd843cfb01fa9030511ddd426417f85433d8802b 100644 --- a/polkadot/node/subsystem-util/src/runtime/mod.rs +++ b/polkadot/node/subsystem-util/src/runtime/mod.rs @@ -33,21 +33,20 @@ use polkadot_primitives::{ node_features::FeatureIndex, slashing, vstaging::{CandidateEvent, CoreState, OccupiedCore, ScrapedOnChainVotes}, - AsyncBackingParams, CandidateHash, CoreIndex, EncodeAs, ExecutorParams, GroupIndex, - GroupRotationInfo, Hash, Id as ParaId, IndexedVec, NodeFeatures, SessionIndex, SessionInfo, - Signed, SigningContext, UncheckedSigned, ValidationCode, ValidationCodeHash, ValidatorId, - ValidatorIndex, LEGACY_MIN_BACKING_VOTES, + CandidateHash, CoreIndex, EncodeAs, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, + Id as ParaId, IndexedVec, NodeFeatures, SessionIndex, SessionInfo, Signed, SigningContext, + UncheckedSigned, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, + DEFAULT_SCHEDULING_LOOKAHEAD, LEGACY_MIN_BACKING_VOTES, }; use std::collections::{BTreeMap, VecDeque}; use crate::{ - has_required_runtime, request_async_backing_params, request_availability_cores, - request_candidate_events, request_claim_queue, request_disabled_validators, - request_from_runtime, request_key_ownership_proof, request_on_chain_votes, - request_session_executor_params, request_session_index_for_child, request_session_info, - request_submit_report_dispute_lost, request_unapplied_slashes, request_validation_code_by_hash, - request_validator_groups, + has_required_runtime, request_availability_cores, request_candidate_events, + request_claim_queue, request_disabled_validators, request_from_runtime, + request_key_ownership_proof, request_on_chain_votes, request_session_executor_params, + request_session_index_for_child, request_session_info, request_submit_report_dispute_lost, + request_unapplied_slashes, request_validation_code_by_hash, request_validator_groups, }; /// Errors that can happen on runtime fetches. @@ -469,64 +468,6 @@ where .await } -/// Prospective parachains mode of a relay parent. Defined by -/// the Runtime API version. -/// -/// Needed for the period of transition to asynchronous backing. -#[derive(Debug, Copy, Clone)] -pub enum ProspectiveParachainsMode { - /// Runtime API without support of `async_backing_params`: no prospective parachains. - Disabled, - /// v6 runtime API: prospective parachains. - Enabled { - /// The maximum number of para blocks between the para head in a relay parent - /// and a new candidate. Restricts nodes from building arbitrary long chains - /// and spamming other validators. - max_candidate_depth: usize, - /// How many ancestors of a relay parent are allowed to build candidates on top - /// of. - allowed_ancestry_len: usize, - }, -} - -impl ProspectiveParachainsMode { - /// Returns `true` if mode is enabled, `false` otherwise. - pub fn is_enabled(&self) -> bool { - matches!(self, ProspectiveParachainsMode::Enabled { .. }) - } -} - -/// Requests prospective parachains mode for a given relay parent based on -/// the Runtime API version. -pub async fn prospective_parachains_mode<Sender>( - sender: &mut Sender, - relay_parent: Hash, -) -> Result<ProspectiveParachainsMode> -where - Sender: SubsystemSender<RuntimeApiMessage>, -{ - let result = recv_runtime(request_async_backing_params(relay_parent, sender).await).await; - - if let Err(error::Error::RuntimeRequest(RuntimeApiError::NotSupported { runtime_api_name })) = - &result - { - gum::trace!( - target: LOG_TARGET, - ?relay_parent, - "Prospective parachains are disabled, {} is not supported by the current Runtime API", - runtime_api_name, - ); - - Ok(ProspectiveParachainsMode::Disabled) - } else { - let AsyncBackingParams { max_candidate_depth, allowed_ancestry_len } = result?; - Ok(ProspectiveParachainsMode::Enabled { - max_candidate_depth: max_candidate_depth as _, - allowed_ancestry_len: allowed_ancestry_len as _, - }) - } -} - /// Request the min backing votes value. /// Prior to runtime API version 6, just return a hardcoded constant. pub async fn request_min_backing_votes( @@ -655,27 +596,44 @@ pub async fn get_disabled_validators_with_fallback<Sender: SubsystemSender<Runti Ok(disabled_validators) } -/// Checks if the runtime supports `request_claim_queue` and attempts to fetch the claim queue. -/// Returns `ClaimQueueSnapshot` or `None` if claim queue API is not supported by runtime. -/// Any specific `RuntimeApiError` is bubbled up to the caller. +/// Fetch the claim queue and wrap it into a helpful `ClaimQueueSnapshot` pub async fn fetch_claim_queue( sender: &mut impl SubsystemSender<RuntimeApiMessage>, relay_parent: Hash, -) -> Result<Option<ClaimQueueSnapshot>> { - if has_required_runtime( - sender, - relay_parent, - RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT, +) -> Result<ClaimQueueSnapshot> { + let cq = request_claim_queue(relay_parent, sender) + .await + .await + .map_err(Error::RuntimeRequestCanceled)??; + + Ok(cq.into()) +} + +/// Checks if the runtime supports `request_claim_queue` and attempts to fetch the claim queue. +/// Returns `ClaimQueueSnapshot` or `None` if claim queue API is not supported by runtime. +pub async fn fetch_scheduling_lookahead( + parent: Hash, + session_index: SessionIndex, + sender: &mut impl overseer::SubsystemSender<RuntimeApiMessage>, +) -> Result<u32> { + let res = recv_runtime( + request_from_runtime(parent, sender, |tx| { + RuntimeApiRequest::SchedulingLookahead(session_index, tx) + }) + .await, ) - .await - { - let res = request_claim_queue(relay_parent, sender) - .await - .await - .map_err(Error::RuntimeRequestCanceled)??; - Ok(Some(res.into())) + .await; + + if let Err(Error::RuntimeRequest(RuntimeApiError::NotSupported { .. })) = res { + gum::trace!( + target: LOG_TARGET, + ?parent, + "Querying the scheduling lookahead from the runtime is not supported by the current Runtime API, falling back to default value of {}", + DEFAULT_SCHEDULING_LOOKAHEAD + ); + + Ok(DEFAULT_SCHEDULING_LOOKAHEAD) } else { - gum::trace!(target: LOG_TARGET, "Runtime doesn't support `request_claim_queue`"); - Ok(None) + res } } diff --git a/polkadot/node/subsystem/Cargo.toml b/polkadot/node/subsystem/Cargo.toml index ce4bceec7336e59efdee79ecc2c88293d66bfa67..8b4a26e33ee6eee012820e36a7d1c1b07ca9b8ca 100644 --- a/polkadot/node/subsystem/Cargo.toml +++ b/polkadot/node/subsystem/Cargo.toml @@ -5,10 +5,12 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] -polkadot-overseer = { workspace = true, default-features = true } polkadot-node-subsystem-types = { workspace = true, default-features = true } +polkadot-overseer = { workspace = true, default-features = true } diff --git a/polkadot/node/test/client/Cargo.toml b/polkadot/node/test/client/Cargo.toml index 587af659fbd2dfeb142c4f307ce71a92b0fae35b..13b14c0b92318fd46933e4bc5037d6b25a0bf00e 100644 --- a/polkadot/node/test/client/Cargo.toml +++ b/polkadot/node/test/client/Cargo.toml @@ -13,32 +13,32 @@ workspace = true codec = { features = ["derive"], workspace = true } # Polkadot dependencies +polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } polkadot-test-runtime = { workspace = true } polkadot-test-service = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } # Substrate dependencies -substrate-test-client = { workspace = true } -sc-service = { workspace = true, default-features = true } +frame-benchmarking = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-offchain = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } -sp-inherents = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } +sc-service = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } -sp-timestamp = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-consensus-babe = { workspace = true, default-features = true } -sp-state-machine = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-inherents = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -frame-benchmarking = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-state-machine = { workspace = true, default-features = true } +sp-timestamp = { workspace = true, default-features = true } +substrate-test-client = { workspace = true } [dev-dependencies] -sp-keyring = { workspace = true, default-features = true } futures = { workspace = true } +sp-keyring = { workspace = true, default-features = true } [features] runtime-benchmarks = [ diff --git a/polkadot/node/test/service/Cargo.toml b/polkadot/node/test/service/Cargo.toml index 4ef9d88621fb4ee23044a56d7492aad9fa19d0a3..96bbdd2e7bdefd558b2a9a3fa7649612e62463cd 100644 --- a/polkadot/node/test/service/Cargo.toml +++ b/polkadot/node/test/service/Cargo.toml @@ -11,50 +11,50 @@ workspace = true [dependencies] futures = { workspace = true } -hex = { workspace = true, default-features = true } gum = { workspace = true, default-features = true } +hex = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } tempfile = { workspace = true } tokio = { workspace = true, default-features = true } # Polkadot dependencies +polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } polkadot-rpc = { workspace = true, default-features = true } polkadot-runtime-common = { workspace = true, default-features = true } +polkadot-runtime-parachains = { workspace = true, default-features = true } polkadot-service = { workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } polkadot-test-runtime = { workspace = true } test-runtime-constants = { workspace = true, default-features = true } -polkadot-runtime-parachains = { workspace = true, default-features = true } # Substrate dependencies -sp-authority-discovery = { workspace = true, default-features = true } -sc-authority-discovery = { workspace = true, default-features = true } -sc-consensus-babe = { workspace = true, default-features = true } -sp-consensus-babe = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } -sc-consensus-grandpa = { workspace = true, default-features = true } -sp-consensus-grandpa = { workspace = true, default-features = true } -sp-inherents = { workspace = true, default-features = true } -pallet-staking = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } +pallet-staking = { workspace = true, default-features = true } pallet-transaction-payment = { workspace = true, default-features = true } +sc-authority-discovery = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } sc-cli = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } +sc-consensus-babe = { workspace = true, default-features = true } +sc-consensus-grandpa = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } +sc-service = { workspace = true } sc-tracing = { workspace = true, default-features = true } sc-transaction-pool = { workspace = true, default-features = true } -sc-service = { workspace = true } sp-arithmetic = { workspace = true, default-features = true } +sp-authority-discovery = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } +sp-consensus-grandpa = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-inherents = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } @@ -62,7 +62,6 @@ substrate-test-client = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true } -substrate-test-utils = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } [features] diff --git a/polkadot/node/test/service/src/chain_spec.rs b/polkadot/node/test/service/src/chain_spec.rs index ae4e84b7725e50348a2d635e0e340614dd445043..ef83c4795dc6889895dbf03e9e87c36161e6bb1e 100644 --- a/polkadot/node/test/service/src/chain_spec.rs +++ b/polkadot/node/test/service/src/chain_spec.rs @@ -18,7 +18,8 @@ use pallet_staking::Forcing; use polkadot_primitives::{ - AccountId, AssignmentId, SchedulerParams, ValidatorId, MAX_CODE_SIZE, MAX_POV_SIZE, + node_features, AccountId, AssignmentId, NodeFeatures, SchedulerParams, ValidatorId, + MAX_CODE_SIZE, MAX_POV_SIZE, }; use polkadot_service::chain_spec::Extensions; use polkadot_test_runtime::BABE_GENESIS_EPOCH_CONFIG; @@ -110,6 +111,11 @@ fn polkadot_testnet_genesis( const ENDOWMENT: u128 = 1_000_000 * DOTS; const STASH: u128 = 100 * DOTS; + // Prepare node features with V2 receipts enabled. + let mut node_features = NodeFeatures::new(); + node_features.resize(node_features::FeatureIndex::CandidateReceiptV2 as usize + 1, false); + node_features.set(node_features::FeatureIndex::CandidateReceiptV2 as u8 as usize, true); + serde_json::json!({ "balances": { "balances": endowed_accounts.iter().map(|k| (k.clone(), ENDOWMENT)).collect::<Vec<_>>(), @@ -158,6 +164,7 @@ fn polkadot_testnet_genesis( no_show_slots: 10, minimum_validation_upgrade_delay: 5, max_downward_message_size: 1024, + node_features, scheduler_params: SchedulerParams { group_rotation_frequency: 20, paras_availability_period: 4, diff --git a/polkadot/node/test/service/src/lib.rs b/polkadot/node/test/service/src/lib.rs index 6e09bb9e4310549d904b1d9fb4b01141dd3f96dd..75fd0d9af3013c944a7feaf85a884ba212f42f23 100644 --- a/polkadot/node/test/service/src/lib.rs +++ b/polkadot/node/test/service/src/lib.rs @@ -423,6 +423,7 @@ pub fn construct_extrinsic( frame_system::CheckNonce::<Runtime>::from(nonce), frame_system::CheckWeight::<Runtime>::new(), pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip), + frame_system::WeightReclaim::<Runtime>::new(), ) .into(); let raw_payload = SignedPayload::from_raw( @@ -437,6 +438,7 @@ pub fn construct_extrinsic( (), (), (), + (), ), ); let signature = raw_payload.using_encoded(|e| caller.sign(e)); @@ -451,8 +453,8 @@ pub fn construct_extrinsic( /// Construct a transfer extrinsic. pub fn construct_transfer_extrinsic( client: &Client, - origin: sp_keyring::AccountKeyring, - dest: sp_keyring::AccountKeyring, + origin: sp_keyring::Sr25519Keyring, + dest: sp_keyring::Sr25519Keyring, value: Balance, ) -> UncheckedExtrinsic { let function = diff --git a/polkadot/node/tracking-allocator/Cargo.toml b/polkadot/node/tracking-allocator/Cargo.toml index d98377e53759c9df35e5eb96f2de3860555a87b8..0fbf526ccb8b6bfc7a8341cc815832a6e479cf71 100644 --- a/polkadot/node/tracking-allocator/Cargo.toml +++ b/polkadot/node/tracking-allocator/Cargo.toml @@ -5,6 +5,8 @@ version = "2.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/zombienet-backchannel/Cargo.toml b/polkadot/node/zombienet-backchannel/Cargo.toml index 56c49a1ec305e17e949c71104bd659bdb5c379a6..0d04012e28a8ad02bb5c78d031db0c8e16720e0d 100644 --- a/polkadot/node/zombienet-backchannel/Cargo.toml +++ b/polkadot/node/zombienet-backchannel/Cargo.toml @@ -12,13 +12,13 @@ license.workspace = true workspace = true [dependencies] -tokio = { features = ["macros", "net", "rt-multi-thread", "sync"], workspace = true } -url = { workspace = true } -tokio-tungstenite = { workspace = true } -futures-util = { workspace = true, default-features = true } codec = { features = ["derive"], workspace = true, default-features = true } -reqwest = { features = ["rustls-tls"], workspace = true } -thiserror = { workspace = true } +futures-util = { workspace = true, default-features = true } gum = { workspace = true, default-features = true } +reqwest = { features = ["rustls-tls"], workspace = true } serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } +tokio = { features = ["macros", "net", "rt-multi-thread", "sync"], workspace = true } +tokio-tungstenite = { workspace = true } +url = { workspace = true } diff --git a/polkadot/parachain/Cargo.toml b/polkadot/parachain/Cargo.toml index 9d0518fd46ade9644af5c21c0a4c5873bfd458e3..0dd103d58b25ef393588695474f0f3145846961b 100644 --- a/polkadot/parachain/Cargo.toml +++ b/polkadot/parachain/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true version = "6.0.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -13,14 +15,14 @@ workspace = true # note: special care is taken to avoid inclusion of `sp-io` externals when compiling # this crate for WASM. This is critical to avoid forcing all parachain WASM into implementing # various unnecessary Substrate-specific endpoints. +bounded-collections = { features = ["serde"], workspace = true } codec = { features = ["derive"], workspace = true } +derive_more = { workspace = true, default-features = true } +polkadot-core-primitives = { workspace = true } scale-info = { features = ["derive", "serde"], workspace = true } -sp-runtime = { features = ["serde"], workspace = true } sp-core = { features = ["serde"], workspace = true } +sp-runtime = { features = ["serde"], workspace = true } sp-weights = { workspace = true } -polkadot-core-primitives = { workspace = true } -derive_more = { workspace = true, default-features = true } -bounded-collections = { features = ["serde"], workspace = true } # all optional crates. serde = { features = ["alloc", "derive"], workspace = true } diff --git a/polkadot/parachain/src/primitives.rs b/polkadot/parachain/src/primitives.rs index c5757928c3fc2f86fdd6ddb779856f0f39db70f7..1f2f9e2e9cdc7068a623db6469c935833dbedab0 100644 --- a/polkadot/parachain/src/primitives.rs +++ b/polkadot/parachain/src/primitives.rs @@ -57,6 +57,8 @@ impl HeadData { } } +impl codec::EncodeLike<HeadData> for alloc::vec::Vec<u8> {} + /// Parachain validation code. #[derive( PartialEq, @@ -154,6 +156,9 @@ pub struct BlockData(#[cfg_attr(feature = "std", serde(with = "bytes"))] pub Vec #[cfg_attr(feature = "std", derive(derive_more::Display))] pub struct Id(u32); +impl codec::EncodeLike<u32> for Id {} +impl codec::EncodeLike<Id> for u32 {} + impl TypeId for Id { const TYPE_ID: [u8; 4] = *b"para"; } diff --git a/polkadot/parachain/test-parachains/Cargo.toml b/polkadot/parachain/test-parachains/Cargo.toml index 9f35653f957f3ba8423c5a1e47ca08a05d6bf6c5..2a1e1722bff932d8d3a32fda9e4b9a7141026045 100644 --- a/polkadot/parachain/test-parachains/Cargo.toml +++ b/polkadot/parachain/test-parachains/Cargo.toml @@ -11,8 +11,8 @@ publish = false workspace = true [dependencies] -tiny-keccak = { features = ["keccak"], workspace = true } codec = { features = ["derive"], workspace = true } +tiny-keccak = { features = ["keccak"], workspace = true } test-parachain-adder = { workspace = true } test-parachain-halt = { workspace = true } diff --git a/polkadot/parachain/test-parachains/adder/Cargo.toml b/polkadot/parachain/test-parachains/adder/Cargo.toml index 7a150b75d5cdb28a82cdaf9cdeef3cb0c3a4582d..945b0e15690479fe89fb1e3ce4b2b4e129e91eff 100644 --- a/polkadot/parachain/test-parachains/adder/Cargo.toml +++ b/polkadot/parachain/test-parachains/adder/Cargo.toml @@ -12,10 +12,10 @@ publish = false workspace = true [dependencies] -polkadot-parachain-primitives = { features = ["wasm-api"], workspace = true } codec = { features = ["derive"], workspace = true } -tiny-keccak = { features = ["keccak"], workspace = true } dlmalloc = { features = ["global"], workspace = true } +polkadot-parachain-primitives = { features = ["wasm-api"], workspace = true } +tiny-keccak = { features = ["keccak"], workspace = true } # We need to make sure the global allocator is disabled until we have support of full substrate externalities sp-io = { features = ["disable_allocator"], workspace = true } diff --git a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml index 061378a76a82eb8edcf8823d7ee3d43d7d3fc7e9..301a0d10ba851e189396fb199903027efe8bf2c0 100644 --- a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml @@ -15,29 +15,28 @@ name = "adder-collator" path = "src/main.rs" [dependencies] -codec = { features = ["derive"], workspace = true } clap = { features = ["derive"], workspace = true } +codec = { features = ["derive"], workspace = true } futures = { workspace = true } futures-timer = { workspace = true } log = { workspace = true, default-features = true } -test-parachain-adder = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-cli = { workspace = true, default-features = true } -polkadot-service = { features = ["rococo-native"], workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +polkadot-service = { features = ["rococo-native"], workspace = true, default-features = true } +test-parachain-adder = { workspace = true } sc-cli = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } [dev-dependencies] +polkadot-node-core-pvf = { features = ["test-utils"], workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-test-service = { workspace = true } -polkadot-node-core-pvf = { features = ["test-utils"], workspace = true, default-features = true } -substrate-test-utils = { workspace = true } sc-service = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } diff --git a/polkadot/parachain/test-parachains/adder/collator/tests/integration.rs b/polkadot/parachain/test-parachains/adder/collator/tests/integration.rs index 85abf8bf36b978bb33aae5115711443da5968a79..5d728517c4bb2ee5d91c7b2578c0d1b1bca84078 100644 --- a/polkadot/parachain/test-parachains/adder/collator/tests/integration.rs +++ b/polkadot/parachain/test-parachains/adder/collator/tests/integration.rs @@ -23,7 +23,7 @@ #[tokio::test(flavor = "multi_thread")] async fn collating_using_adder_collator() { use polkadot_primitives::Id as ParaId; - use sp_keyring::AccountKeyring::*; + use sp_keyring::Sr25519Keyring::*; let mut builder = sc_cli::LoggerBuilder::new(""); builder.with_colors(false); diff --git a/polkadot/parachain/test-parachains/halt/Cargo.toml b/polkadot/parachain/test-parachains/halt/Cargo.toml index f8272f6ed19681adf3faa57dfac3c4f126db6839..ea8372ccd121fbe8e7b401143626feb01c794b3d 100644 --- a/polkadot/parachain/test-parachains/halt/Cargo.toml +++ b/polkadot/parachain/test-parachains/halt/Cargo.toml @@ -14,8 +14,8 @@ workspace = true [dependencies] [build-dependencies] -substrate-wasm-builder = { workspace = true, default-features = true } rustversion = { workspace = true } +substrate-wasm-builder = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/polkadot/parachain/test-parachains/undying/Cargo.toml b/polkadot/parachain/test-parachains/undying/Cargo.toml index 4b2e12ebf435422c713fd2319eee08d9fa45d8dc..f8dfc8936c0ef3bd7abd7e85fe3094c9318edb02 100644 --- a/polkadot/parachain/test-parachains/undying/Cargo.toml +++ b/polkadot/parachain/test-parachains/undying/Cargo.toml @@ -12,11 +12,12 @@ license.workspace = true workspace = true [dependencies] -polkadot-parachain-primitives = { features = ["wasm-api"], workspace = true } codec = { features = ["derive"], workspace = true } -tiny-keccak = { features = ["keccak"], workspace = true } dlmalloc = { features = ["global"], workspace = true } log = { workspace = true } +polkadot-parachain-primitives = { features = ["wasm-api"], workspace = true } +polkadot-primitives = { workspace = true, default-features = false } +tiny-keccak = { features = ["keccak"], workspace = true } # We need to make sure the global allocator is disabled until we have support of full substrate externalities sp-io = { features = ["disable_allocator"], workspace = true } @@ -30,5 +31,6 @@ std = [ "codec/std", "log/std", "polkadot-parachain-primitives/std", + "polkadot-primitives/std", "sp-io/std", ] diff --git a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml index 5760258c70ea5757f5fda90abbfe5a41e5ee0fb4..e26b9f59acd4bf7724c9308dda6827c95e8a8651 100644 --- a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml @@ -15,29 +15,30 @@ name = "undying-collator" path = "src/main.rs" [dependencies] -codec = { features = ["derive"], workspace = true } clap = { features = ["derive"], workspace = true } +codec = { features = ["derive"], workspace = true } futures = { workspace = true } futures-timer = { workspace = true } log = { workspace = true, default-features = true } -test-parachain-undying = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-cli = { workspace = true, default-features = true } -polkadot-service = { features = ["rococo-native"], workspace = true, default-features = true } +polkadot-erasure-coding = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +polkadot-service = { features = ["rococo-native"], workspace = true, default-features = true } +test-parachain-undying = { workspace = true } sc-cli = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } [dev-dependencies] +polkadot-node-core-pvf = { features = ["test-utils"], workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-test-service = { workspace = true } -polkadot-node-core-pvf = { features = ["test-utils"], workspace = true, default-features = true } -substrate-test-utils = { workspace = true } sc-service = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } diff --git a/polkadot/parachain/test-parachains/undying/collator/src/cli.rs b/polkadot/parachain/test-parachains/undying/collator/src/cli.rs index 9572887a51a2a195a01e6ceced60ee711288ead2..a3de7c80d214a2e9c0669dacdd33be65bdf9ab18 100644 --- a/polkadot/parachain/test-parachains/undying/collator/src/cli.rs +++ b/polkadot/parachain/test-parachains/undying/collator/src/cli.rs @@ -61,6 +61,15 @@ pub struct ExportGenesisWasmCommand { pub output: Option<PathBuf>, } +/// Enum representing different types of malicious behaviors for collators. +#[derive(Debug, Parser, Clone, PartialEq, clap::ValueEnum)] +pub enum MalusType { + /// No malicious behavior. + None, + /// Submit the same collations to all assigned cores. + DuplicateCollations, +} + #[allow(missing_docs)] #[derive(Debug, Parser)] #[group(skip)] @@ -81,6 +90,10 @@ pub struct RunCmd { /// we compute per block. #[arg(long, default_value_t = 1)] pub pvf_complexity: u32, + + /// Specifies the malicious behavior of the collator. + #[arg(long, value_enum, default_value_t = MalusType::None)] + pub malus_type: MalusType, } #[allow(missing_docs)] diff --git a/polkadot/parachain/test-parachains/undying/collator/src/lib.rs b/polkadot/parachain/test-parachains/undying/collator/src/lib.rs index 448c181ae062bbaac7dc1b8312d50d26f1643ea9..3d5724ae79ba8fcfd24f4bd56e6a7b0eaa23bdf4 100644 --- a/polkadot/parachain/test-parachains/undying/collator/src/lib.rs +++ b/polkadot/parachain/test-parachains/undying/collator/src/lib.rs @@ -17,14 +17,25 @@ //! Collator for the `Undying` test parachain. use codec::{Decode, Encode}; -use futures::channel::oneshot; +use futures::{channel::oneshot, StreamExt}; use futures_timer::Delay; +use polkadot_cli::ProvideRuntimeApi; use polkadot_node_primitives::{ - maybe_compress_pov, Collation, CollationResult, CollationSecondedSignal, CollatorFn, - MaybeCompressedPoV, PoV, Statement, + maybe_compress_pov, AvailableData, Collation, CollationResult, CollationSecondedSignal, + CollatorFn, MaybeCompressedPoV, PoV, Statement, UpwardMessages, }; -use polkadot_primitives::{CollatorId, CollatorPair, Hash}; +use polkadot_node_subsystem::messages::CollatorProtocolMessage; +use polkadot_primitives::{ + vstaging::{ + CandidateDescriptorV2, CandidateReceiptV2, ClaimQueueOffset, DEFAULT_CLAIM_QUEUE_OFFSET, + }, + CandidateCommitments, CollatorId, CollatorPair, CoreIndex, Hash, Id as ParaId, + OccupiedCoreAssumption, +}; +use polkadot_service::{Handle, NewFull, ParachainHost}; +use sc_client_api::client::BlockchainEvents; use sp_core::Pair; + use std::{ collections::HashMap, sync::{ @@ -37,6 +48,8 @@ use test_parachain_undying::{ execute, hash_state, BlockData, GraveyardState, HeadData, StateMismatch, }; +pub const LOG_TARGET: &str = "parachain::undying-collator"; + /// Default PoV size which also drives state size. const DEFAULT_POV_SIZE: usize = 1000; /// Default PVF time complexity - 1 signature per block. @@ -52,19 +65,20 @@ fn calculate_head_and_state_for_number( let mut graveyard = vec![0u8; graveyard_size * graveyard_size]; let zombies = 0; let seal = [0u8; 32]; + let core_selector_number = 0; // Ensure a larger compressed PoV. graveyard.iter_mut().enumerate().for_each(|(i, grave)| { *grave = i as u8; }); - let mut state = GraveyardState { index, graveyard, zombies, seal }; + let mut state = GraveyardState { index, graveyard, zombies, seal, core_selector_number }; let mut head = HeadData { number: 0, parent_hash: Hash::default().into(), post_state: hash_state(&state) }; while head.number < number { let block = BlockData { state, tombstones: 1_000, iterations: pvf_complexity }; - let (new_head, new_state) = execute(head.hash(), head.clone(), block)?; + let (new_head, new_state, _) = execute(head.hash(), head.clone(), block)?; head = new_head; state = new_state; } @@ -99,13 +113,14 @@ impl State { let mut graveyard = vec![0u8; graveyard_size * graveyard_size]; let zombies = 0; let seal = [0u8; 32]; + let core_selector_number = 0; // Ensure a larger compressed PoV. graveyard.iter_mut().enumerate().for_each(|(i, grave)| { *grave = i as u8; }); - let state = GraveyardState { index, graveyard, zombies, seal }; + let state = GraveyardState { index, graveyard, zombies, seal, core_selector_number }; let head_data = HeadData { number: 0, parent_hash: Default::default(), post_state: hash_state(&state) }; @@ -123,7 +138,10 @@ impl State { /// Advance the state and produce a new block based on the given `parent_head`. /// /// Returns the new [`BlockData`] and the new [`HeadData`]. - fn advance(&mut self, parent_head: HeadData) -> Result<(BlockData, HeadData), StateMismatch> { + fn advance( + &mut self, + parent_head: HeadData, + ) -> Result<(BlockData, HeadData, UpwardMessages), StateMismatch> { self.best_block = parent_head.number; let state = if let Some(state) = self @@ -144,14 +162,15 @@ impl State { // Start with prev state and transaction to execute (place 1000 tombstones). let block = BlockData { state, tombstones: 1000, iterations: self.pvf_complexity }; - let (new_head, new_state) = execute(parent_head.hash(), parent_head, block.clone())?; + let (new_head, new_state, upward_messages) = + execute(parent_head.hash(), parent_head, block.clone())?; let new_head_arc = Arc::new(new_head.clone()); self.head_to_state.insert(new_head_arc.clone(), new_state); self.number_to_head.insert(new_head.number, new_head_arc); - Ok((block, new_head)) + Ok((block, new_head, upward_messages)) } } @@ -175,13 +194,18 @@ impl Collator { let graveyard_size = ((pov_size / std::mem::size_of::<u8>()) as f64).sqrt().ceil() as usize; log::info!( + target: LOG_TARGET, "PoV target size: {} bytes. Graveyard size: ({} x {})", pov_size, graveyard_size, - graveyard_size + graveyard_size, ); - log::info!("PVF time complexity: {}", pvf_complexity); + log::info!( + target: LOG_TARGET, + "PVF time complexity: {}", + pvf_complexity, + ); Self { state: Arc::new(Mutex::new(State::genesis(graveyard_size, pvf_complexity))), @@ -232,21 +256,32 @@ impl Collator { Box::new(move |relay_parent, validation_data| { let parent = match HeadData::decode(&mut &validation_data.parent_head.0[..]) { Err(err) => { - log::error!("Requested to build on top of malformed head-data: {:?}", err); + log::error!( + target: LOG_TARGET, + "Requested to build on top of malformed head-data: {:?}", + err, + ); return futures::future::ready(None).boxed() }, Ok(p) => p, }; - let (block_data, head_data) = match state.lock().unwrap().advance(parent.clone()) { - Err(err) => { - log::error!("Unable to build on top of {:?}: {:?}", parent, err); - return futures::future::ready(None).boxed() - }, - Ok(x) => x, - }; + let (block_data, head_data, upward_messages) = + match state.lock().unwrap().advance(parent.clone()) { + Err(err) => { + log::error!( + target: LOG_TARGET, + "Unable to build on top of {:?}: {:?}", + parent, + err, + ); + return futures::future::ready(None).boxed() + }, + Ok(x) => x, + }; log::info!( + target: LOG_TARGET, "created a new collation on relay-parent({}): {:?}", relay_parent, head_data, @@ -256,7 +291,7 @@ impl Collator { let pov = PoV { block_data: block_data.encode().into() }; let collation = Collation { - upward_messages: Default::default(), + upward_messages, horizontal_messages: Default::default(), new_validation_code: None, head_data: head_data.encode().into(), @@ -265,10 +300,15 @@ impl Collator { hrmp_watermark: validation_data.relay_parent_number, }; - log::info!("Raw PoV size for collation: {} bytes", pov.block_data.0.len(),); + log::info!( + target: LOG_TARGET, + "Raw PoV size for collation: {} bytes", + pov.block_data.0.len(), + ); let compressed_pov = maybe_compress_pov(pov); log::info!( + target: LOG_TARGET, "Compressed PoV size for collation: {} bytes", compressed_pov.block_data.0.len(), ); @@ -285,8 +325,9 @@ impl Collator { Statement::Seconded(s) if s.descriptor.pov_hash() == compressed_pov.hash(), ) { log::error!( + target: LOG_TARGET, "Seconded statement should match our collation: {:?}", - res.statement.payload() + res.statement.payload(), ); } @@ -330,6 +371,259 @@ impl Collator { } } } + + pub fn send_same_collations_to_all_assigned_cores( + &self, + full_node: &NewFull, + mut overseer_handle: Handle, + para_id: ParaId, + ) { + let client = full_node.client.clone(); + + let collation_function = + self.create_collation_function(full_node.task_manager.spawn_handle()); + + full_node + .task_manager + .spawn_handle() + .spawn("malus-undying-collator", None, async move { + // Subscribe to relay chain block import notifications. In each iteration, build a + // collation in response to a block import notification and submits it to all cores + // assigned to the parachain. + let mut import_notifications = client.import_notification_stream(); + + while let Some(notification) = import_notifications.next().await { + let relay_parent = notification.hash; + + // Get the list of cores assigned to the parachain. + let claim_queue = match client.runtime_api().claim_queue(relay_parent) { + Ok(claim_queue) => claim_queue, + Err(error) => { + log::error!( + target: LOG_TARGET, + "Failed to query claim queue runtime API: {error:?}", + ); + continue; + }, + }; + + let claim_queue_offset = ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET); + + let scheduled_cores: Vec<CoreIndex> = claim_queue + .iter() + .filter_map(move |(core_index, paras)| { + paras.get(claim_queue_offset.0 as usize).and_then(|core_para_id| { + (core_para_id == ¶_id).then_some(*core_index) + }) + }) + .collect(); + + if scheduled_cores.is_empty() { + log::info!( + target: LOG_TARGET, + "Scheduled cores is empty.", + ); + continue; + } + + if scheduled_cores.len() == 1 { + log::info!( + target: LOG_TARGET, + "Malus collator configured with duplicate collations, but only 1 core assigned. \ + Collator will not do anything malicious.", + ); + } + + // Fetch validation data for the collation. + let validation_data = match client.runtime_api().persisted_validation_data( + relay_parent, + para_id, + OccupiedCoreAssumption::Included, + ) { + Ok(Some(validation_data)) => validation_data, + Ok(None) => { + log::info!( + target: LOG_TARGET, + "Persisted validation data is None.", + ); + continue; + }, + Err(error) => { + log::error!( + target: LOG_TARGET, + "Failed to query persisted validation data runtime API: {error:?}", + ); + continue; + }, + }; + + // Generate the collation. + let collation = + match collation_function(relay_parent, &validation_data).await { + Some(collation) => collation, + None => { + log::info!( + target: LOG_TARGET, + "Collation result is None.", + ); + continue; + }, + } + .collation; + + // Fetch the validation code hash. + let validation_code_hash = match client.runtime_api().validation_code_hash( + relay_parent, + para_id, + OccupiedCoreAssumption::Included, + ) { + Ok(Some(validation_code_hash)) => validation_code_hash, + Ok(None) => { + log::info!( + target: LOG_TARGET, + "Validation code hash is None.", + ); + continue; + }, + Err(error) => { + log::error!( + target: LOG_TARGET, + "Failed to query validation code hash runtime API: {error:?}", + ); + continue; + }, + }; + + // Fetch the session index. + let session_index = + match client.runtime_api().session_index_for_child(relay_parent) { + Ok(session_index) => session_index, + Err(error) => { + log::error!( + target: LOG_TARGET, + "Failed to query session index for child runtime API: {error:?}", + ); + continue; + }, + }; + + let persisted_validation_data_hash = validation_data.hash(); + let parent_head_data = validation_data.parent_head.clone(); + let parent_head_data_hash = validation_data.parent_head.hash(); + + // Apply compression to the block data. + let pov = { + let pov = collation.proof_of_validity.into_compressed(); + let encoded_size = pov.encoded_size(); + let max_pov_size = validation_data.max_pov_size as usize; + + // As long as `POV_BOMB_LIMIT` is at least `max_pov_size`, this ensures + // that honest collators never produce a PoV which is uncompressed. + // + // As such, honest collators never produce an uncompressed PoV which starts + // with a compression magic number, which would lead validators to + // reject the collation. + if encoded_size > max_pov_size { + log::error!( + target: LOG_TARGET, + "PoV size {encoded_size} exceeded maximum size of {max_pov_size}", + ); + continue; + } + + pov + }; + + let pov_hash = pov.hash(); + + // Fetch the session info. + let session_info = + match client.runtime_api().session_info(relay_parent, session_index) { + Ok(Some(session_info)) => session_info, + Ok(None) => { + log::info!( + target: LOG_TARGET, + "Session info is None.", + ); + continue; + }, + Err(error) => { + log::error!( + target: LOG_TARGET, + "Failed to query session info runtime API: {error:?}", + ); + continue; + }, + }; + + let n_validators = session_info.validators.len(); + + let available_data = + AvailableData { validation_data, pov: Arc::new(pov.clone()) }; + let chunks = match polkadot_erasure_coding::obtain_chunks_v1( + n_validators, + &available_data, + ) { + Ok(chunks) => chunks, + Err(error) => { + log::error!( + target: LOG_TARGET, + "Failed to obtain chunks v1: {error:?}", + ); + continue; + }, + }; + let erasure_root = polkadot_erasure_coding::branches(&chunks).root(); + + let commitments = CandidateCommitments { + upward_messages: collation.upward_messages, + horizontal_messages: collation.horizontal_messages, + new_validation_code: collation.new_validation_code, + head_data: collation.head_data, + processed_downward_messages: collation.processed_downward_messages, + hrmp_watermark: collation.hrmp_watermark, + }; + + // Submit the same collation to all assigned cores. + for core_index in &scheduled_cores { + let candidate_receipt = CandidateReceiptV2 { + descriptor: CandidateDescriptorV2::new( + para_id, + relay_parent, + *core_index, + session_index, + persisted_validation_data_hash, + pov_hash, + erasure_root, + commitments.head_data.hash(), + validation_code_hash, + ), + commitments_hash: commitments.hash(), + }; + + // We cannot use SubmitCollation here because it includes an additional + // check for the core index by calling `check_core_index`. This check + // enforces that the parachain always selects the correct core by comparing + // the descriptor and commitments core indexes. To bypass this check, we are + // simulating the behavior of SubmitCollation while skipping the core index + // validation. + overseer_handle + .send_msg( + CollatorProtocolMessage::DistributeCollation { + candidate_receipt, + parent_head_data_hash, + pov: pov.clone(), + parent_head_data: parent_head_data.clone(), + result_sender: None, + core_index: *core_index, + }, + "Collator", + ) + .await; + } + } + }); + } } use sp_core::traits::SpawnNamed; diff --git a/polkadot/parachain/test-parachains/undying/collator/src/main.rs b/polkadot/parachain/test-parachains/undying/collator/src/main.rs index 017eefe5ee31e1695272ac63ad43567604152f03..9d993dd818b2f7486665103b83e7d613d8f86071 100644 --- a/polkadot/parachain/test-parachains/undying/collator/src/main.rs +++ b/polkadot/parachain/test-parachains/undying/collator/src/main.rs @@ -29,7 +29,7 @@ use std::{ use test_parachain_undying_collator::Collator; mod cli; -use cli::Cli; +use cli::{Cli, MalusType}; fn main() -> Result<()> { let cli = Cli::from_args(); @@ -105,6 +105,7 @@ fn main() -> Result<()> { .map_err(|e| e.to_string())?; let mut overseer_handle = full_node .overseer_handle + .clone() .expect("Overseer handle should be initialized for collators"); let genesis_head_hex = @@ -120,9 +121,16 @@ fn main() -> Result<()> { let config = CollationGenerationConfig { key: collator.collator_key(), - collator: Some( - collator.create_collation_function(full_node.task_manager.spawn_handle()), - ), + // If the collator is malicious, disable the collation function + // (set to None) and manually handle collation submission later. + collator: if cli.run.malus_type == MalusType::None { + Some( + collator + .create_collation_function(full_node.task_manager.spawn_handle()), + ) + } else { + None + }, para_id, }; overseer_handle @@ -133,6 +141,16 @@ fn main() -> Result<()> { .send_msg(CollatorProtocolMessage::CollateOn(para_id), "Collator") .await; + // If the collator is configured to behave maliciously, simulate the specified + // malicious behavior. + if cli.run.malus_type == MalusType::DuplicateCollations { + collator.send_same_collations_to_all_assigned_cores( + &full_node, + overseer_handle, + para_id, + ); + } + Ok(full_node.task_manager) }) }, diff --git a/polkadot/parachain/test-parachains/undying/collator/tests/integration.rs b/polkadot/parachain/test-parachains/undying/collator/tests/integration.rs index 8be535b9bb4cc1275b59a9a3782ef352ac60d643..866b2f888f84e86a67bb578269c3571dd84bf4f5 100644 --- a/polkadot/parachain/test-parachains/undying/collator/tests/integration.rs +++ b/polkadot/parachain/test-parachains/undying/collator/tests/integration.rs @@ -19,10 +19,16 @@ // If this test is failing, make sure to run all tests with the `real-overseer` feature being // enabled. + +use polkadot_node_subsystem::TimeoutExt; +use std::time::Duration; + +const TIMEOUT: Duration = Duration::from_secs(120); + #[tokio::test(flavor = "multi_thread")] async fn collating_using_undying_collator() { use polkadot_primitives::Id as ParaId; - use sp_keyring::AccountKeyring::*; + use sp_keyring::Sr25519Keyring::*; let mut builder = sc_cli::LoggerBuilder::new(""); builder.with_colors(false); @@ -82,8 +88,16 @@ async fn collating_using_undying_collator() { .await; // Wait until the parachain has 4 blocks produced. - collator.wait_for_blocks(4).await; + collator + .wait_for_blocks(4) + .timeout(TIMEOUT) + .await + .expect("Timed out waiting for 4 produced blocks"); // Wait until the collator received `12` seconded statements for its collations. - collator.wait_for_seconded_collations(12).await; + collator + .wait_for_seconded_collations(12) + .timeout(TIMEOUT) + .await + .expect("Timed out waiting for 12 seconded collations"); } diff --git a/polkadot/parachain/test-parachains/undying/src/lib.rs b/polkadot/parachain/test-parachains/undying/src/lib.rs index e4ec7e99346bbef4f3ce2fdb03b6084c053926f8..4f014320d09bbd4f7045376016dfa564e65d1053 100644 --- a/polkadot/parachain/test-parachains/undying/src/lib.rs +++ b/polkadot/parachain/test-parachains/undying/src/lib.rs @@ -22,6 +22,10 @@ extern crate alloc; use alloc::vec::Vec; use codec::{Decode, Encode}; +use polkadot_parachain_primitives::primitives::UpwardMessages; +use polkadot_primitives::vstaging::{ + ClaimQueueOffset, CoreSelector, UMPSignal, DEFAULT_CLAIM_QUEUE_OFFSET, UMP_SEPARATOR, +}; use tiny_keccak::{Hasher as _, Keccak}; #[cfg(not(feature = "std"))] @@ -86,6 +90,8 @@ pub struct GraveyardState { pub zombies: u64, // Grave seal. pub seal: [u8; 32], + // Increasing sequence number for core selector. + pub core_selector_number: u8, } /// Block data for this parachain. @@ -119,6 +125,7 @@ pub fn execute_transaction(mut block_data: BlockData) -> GraveyardState { // Chain hash the seals and burn CPU. block_data.state.seal = hash_state(&block_data.state); } + block_data.state.core_selector_number = block_data.state.core_selector_number.wrapping_add(1); block_data.state } @@ -133,7 +140,7 @@ pub fn execute( parent_hash: [u8; 32], parent_head: HeadData, block_data: BlockData, -) -> Result<(HeadData, GraveyardState), StateMismatch> { +) -> Result<(HeadData, GraveyardState, UpwardMessages), StateMismatch> { assert_eq!(parent_hash, parent_head.hash()); if hash_state(&block_data.state) != parent_head.post_state { @@ -146,6 +153,16 @@ pub fn execute( return Err(StateMismatch) } + let mut upward_messages: UpwardMessages = Default::default(); + upward_messages.force_push(UMP_SEPARATOR); + upward_messages.force_push( + UMPSignal::SelectCore( + CoreSelector(block_data.state.core_selector_number), + ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET), + ) + .encode(), + ); + // We need to clone the block data as the fn will mutate it's state. let new_state = execute_transaction(block_data.clone()); @@ -156,5 +173,6 @@ pub fn execute( post_state: hash_state(&new_state), }, new_state, + upward_messages, )) } diff --git a/polkadot/parachain/test-parachains/undying/src/wasm_validation.rs b/polkadot/parachain/test-parachains/undying/src/wasm_validation.rs index 46b66aa518e490e117c6d190d52a4d4dc85574d7..42917484cfdc2f0622e30260cb0b7c979a64c9d9 100644 --- a/polkadot/parachain/test-parachains/undying/src/wasm_validation.rs +++ b/polkadot/parachain/test-parachains/undying/src/wasm_validation.rs @@ -31,13 +31,13 @@ pub extern "C" fn validate_block(params: *const u8, len: usize) -> u64 { let parent_hash = crate::keccak256(¶ms.parent_head.0[..]); - let (new_head, _) = + let (new_head, _, upward_messages) = crate::execute(parent_hash, parent_head, block_data).expect("Executes block"); polkadot_parachain_primitives::write_result(&ValidationResult { head_data: GenericHeadData(new_head.encode()), new_validation_code: None, - upward_messages: alloc::vec::Vec::new().try_into().expect("empty vec fits within bounds"), + upward_messages, horizontal_messages: alloc::vec::Vec::new() .try_into() .expect("empty vec fits within bounds"), diff --git a/polkadot/primitives/Cargo.toml b/polkadot/primitives/Cargo.toml index dd269caa2d607ccc9721022d63f35d1029d15063..e693fe8c4a8ce2ef803b8248ee614225822fef5a 100644 --- a/polkadot/primitives/Cargo.toml +++ b/polkadot/primitives/Cargo.toml @@ -5,29 +5,31 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Shared primitives used by Polkadot runtime" +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] bitvec = { features = ["alloc", "serde"], workspace = true } -hex-literal = { workspace = true, default-features = true } codec = { features = ["bit-vec", "derive"], workspace = true } -scale-info = { features = ["bit-vec", "derive", "serde"], workspace = true } +hex-literal = { workspace = true, default-features = true } log = { workspace = true } +scale-info = { features = ["bit-vec", "derive", "serde"], workspace = true } serde = { features = ["alloc", "derive"], workspace = true } thiserror = { workspace = true, optional = true } -sp-application-crypto = { features = ["serde"], workspace = true } -sp-inherents = { workspace = true } -sp-core = { workspace = true } -sp-runtime = { workspace = true } sp-api = { workspace = true } +sp-application-crypto = { features = ["serde"], workspace = true } sp-arithmetic = { features = ["serde"], workspace = true } sp-authority-discovery = { features = ["serde"], workspace = true } sp-consensus-slots = { features = ["serde"], workspace = true } +sp-core = { workspace = true } +sp-inherents = { workspace = true } sp-io = { workspace = true } sp-keystore = { optional = true, workspace = true } +sp-runtime = { workspace = true } sp-staking = { features = ["serde"], workspace = true } sp-std = { workspace = true, optional = true } diff --git a/polkadot/primitives/src/lib.rs b/polkadot/primitives/src/lib.rs index 493f9fb5ba92ee66320ad6ead60e5bb1111213b9..361b66cf27f677fc2e7c6d52568ee65a799b8fdb 100644 --- a/polkadot/primitives/src/lib.rs +++ b/polkadot/primitives/src/lib.rs @@ -60,8 +60,8 @@ pub use v8::{ UncheckedSignedAvailabilityBitfields, UncheckedSignedStatement, UpgradeGoAhead, UpgradeRestriction, UpwardMessage, ValidDisputeStatementKind, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, ValidityAttestation, - ValidityError, ASSIGNMENT_KEY_TYPE_ID, LEGACY_MIN_BACKING_VOTES, LOWEST_PUBLIC_ID, - MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MAX_POV_SIZE, MIN_CODE_SIZE, + ValidityError, ASSIGNMENT_KEY_TYPE_ID, DEFAULT_SCHEDULING_LOOKAHEAD, LEGACY_MIN_BACKING_VOTES, + LOWEST_PUBLIC_ID, MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MAX_POV_SIZE, MIN_CODE_SIZE, ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, ON_DEMAND_MAX_QUEUE_MAX_SIZE, PARACHAINS_INHERENT_IDENTIFIER, PARACHAIN_KEY_TYPE_ID, }; diff --git a/polkadot/primitives/src/runtime_api.rs b/polkadot/primitives/src/runtime_api.rs index 3c90c050baed1d9ff6b8239b0d69fa3dc14b16f5..e0516a2f77e423345776df303171b2df7c385f89 100644 --- a/polkadot/primitives/src/runtime_api.rs +++ b/polkadot/primitives/src/runtime_api.rs @@ -116,8 +116,8 @@ use crate::{ slashing, vstaging::{ - self, CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + self, async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, AsyncBackingParams, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, NodeFeatures, @@ -297,5 +297,16 @@ sp_api::decl_runtime_apis! { /// Elastic scaling support #[api_version(11)] fn candidates_pending_availability(para_id: ppp::Id) -> Vec<CommittedCandidateReceipt<Hash>>; + + /***** Added in v12 *****/ + /// Returns the constraints on the actions that can be taken by a new parachain + /// block. + #[api_version(12)] + fn backing_constraints(para_id: ppp::Id) -> Option<Constraints>; + + /***** Added in v12 *****/ + /// Retrieve the scheduling lookahead + #[api_version(12)] + fn scheduling_lookahead() -> u32; } } diff --git a/polkadot/primitives/src/v8/mod.rs b/polkadot/primitives/src/v8/mod.rs index fdcb9fe8fb7e344f1574ff156cd2fe4462bf39ef..93bb5ef23667288eb8a4a79e8589a39f32c742fc 100644 --- a/polkadot/primitives/src/v8/mod.rs +++ b/polkadot/primitives/src/v8/mod.rs @@ -444,6 +444,9 @@ pub const ON_DEMAND_MAX_QUEUE_MAX_SIZE: u32 = 1_000_000_000; /// prior to v9 configuration migration. pub const LEGACY_MIN_BACKING_VOTES: u32 = 2; +/// Default value for `SchedulerParams.lookahead` +pub const DEFAULT_SCHEDULING_LOOKAHEAD: u32 = 3; + // The public key of a keypair used by a validator for determining assignments /// to approve included parachain candidates. mod assignment_app { @@ -1900,7 +1903,7 @@ pub struct SessionInfo { /// participating in parachain consensus. See /// [`max_validators`](https://github.com/paritytech/polkadot/blob/a52dca2be7840b23c19c153cf7e110b1e3e475f8/runtime/parachains/src/configuration.rs#L148). /// - /// `SessionInfo::validators` will be limited to to `max_validators` when set. + /// `SessionInfo::validators` will be limited to `max_validators` when set. pub validators: IndexedVec<ValidatorIndex, ValidatorId>, /// Validators' authority discovery keys for the session in canonical ordering. /// @@ -2132,11 +2135,13 @@ impl<BlockNumber: Default + From<u32>> Default for SchedulerParams<BlockNumber> } #[cfg(test)] +/// Test helpers pub mod tests { use super::*; use bitvec::bitvec; use sp_core::sr25519; + /// Create a dummy committed candidate receipt pub fn dummy_committed_candidate_receipt() -> CommittedCandidateReceipt { let zeros = Hash::zero(); diff --git a/polkadot/primitives/src/vstaging/async_backing.rs b/polkadot/primitives/src/vstaging/async_backing.rs index 8706214b5a0109b779140c4ffba7d52cbaada62e..ce9954538056f1aecaa166beb2673b6cf8e8853c 100644 --- a/polkadot/primitives/src/vstaging/async_backing.rs +++ b/polkadot/primitives/src/vstaging/async_backing.rs @@ -50,12 +50,50 @@ impl<H: Copy> From<CandidatePendingAvailability<H>> } } +/// Constraints on the actions that can be taken by a new parachain +/// block. These limitations are implicitly associated with some particular +/// parachain, which should be apparent from usage. +#[derive(RuntimeDebug, Clone, PartialEq, Encode, Decode, TypeInfo)] +pub struct Constraints<N = BlockNumber> { + /// The minimum relay-parent number accepted under these constraints. + pub min_relay_parent_number: N, + /// The maximum Proof-of-Validity size allowed, in bytes. + pub max_pov_size: u32, + /// The maximum new validation code size allowed, in bytes. + pub max_code_size: u32, + /// The maximum head-data size, in bytes. + pub max_head_data_size: u32, + /// The amount of UMP messages remaining. + pub ump_remaining: u32, + /// The amount of UMP bytes remaining. + pub ump_remaining_bytes: u32, + /// The maximum number of UMP messages allowed per candidate. + pub max_ump_num_per_candidate: u32, + /// Remaining DMP queue. Only includes sent-at block numbers. + pub dmp_remaining_messages: Vec<N>, + /// The limitations of all registered inbound HRMP channels. + pub hrmp_inbound: InboundHrmpLimitations<N>, + /// The limitations of all registered outbound HRMP channels. + pub hrmp_channels_out: Vec<(Id, OutboundHrmpChannelLimitations)>, + /// The maximum number of HRMP messages allowed per candidate. + pub max_hrmp_num_per_candidate: u32, + /// The required parent head-data of the parachain. + pub required_parent: HeadData, + /// The expected validation-code-hash of this parachain. + pub validation_code_hash: ValidationCodeHash, + /// The code upgrade restriction signal as-of this parachain. + pub upgrade_restriction: Option<UpgradeRestriction>, + /// The future validation code hash, if any, and at what relay-parent + /// number the upgrade would be minimally applied. + pub future_validation_code: Option<(N, ValidationCodeHash)>, +} + /// The per-parachain state of the backing system, including /// state-machine constraints and candidates pending availability. #[derive(RuntimeDebug, Clone, PartialEq, Encode, Decode, TypeInfo)] pub struct BackingState<H = Hash, N = BlockNumber> { /// The state-machine constraints of the parachain. - pub constraints: Constraints<N>, + pub constraints: crate::async_backing::Constraints<N>, /// The candidates pending availability. These should be ordered, i.e. they should form /// a sub-chain, where the first candidate builds on top of the required parent of the /// constraints and each subsequent builds on top of the previous head-data. diff --git a/polkadot/primitives/src/vstaging/mod.rs b/polkadot/primitives/src/vstaging/mod.rs index 271f78efe090112993329fd7952184909b15058e..5da4595af65801fe8b7d7891a4f990b3e49034e6 100644 --- a/polkadot/primitives/src/vstaging/mod.rs +++ b/polkadot/primitives/src/vstaging/mod.rs @@ -19,10 +19,11 @@ use crate::{ValidatorIndex, ValidityAttestation}; // Put any primitives used by staging APIs functions here use super::{ - async_backing::Constraints, BlakeTwo256, BlockNumber, CandidateCommitments, - CandidateDescriptor, CandidateHash, CollatorId, CollatorSignature, CoreIndex, GroupIndex, Hash, - HashT, HeadData, Header, Id, Id as ParaId, MultiDisputeStatementSet, ScheduledCore, - UncheckedSignedAvailabilityBitfields, ValidationCodeHash, + async_backing::{InboundHrmpLimitations, OutboundHrmpChannelLimitations}, + BlakeTwo256, BlockNumber, CandidateCommitments, CandidateDescriptor, CandidateHash, CollatorId, + CollatorSignature, CoreIndex, GroupIndex, Hash, HashT, HeadData, Header, Id, Id as ParaId, + MultiDisputeStatementSet, ScheduledCore, UncheckedSignedAvailabilityBitfields, + UpgradeRestriction, ValidationCodeHash, }; use alloc::{ collections::{BTreeMap, BTreeSet, VecDeque}, @@ -505,6 +506,10 @@ pub enum CommittedCandidateReceiptError { /// Currenly only one such message is allowed. #[cfg_attr(feature = "std", error("Too many UMP signals"))] TooManyUMPSignals, + /// If the parachain runtime started sending core selectors, v1 descriptors are no longer + /// allowed. + #[cfg_attr(feature = "std", error("Version 1 receipt does not support core selectors"))] + CoreSelectorWithV1Decriptor, } macro_rules! impl_getter { @@ -603,15 +608,25 @@ impl<H: Copy> CommittedCandidateReceiptV2<H> { &self, cores_per_para: &TransposedClaimQueue, ) -> Result<(), CommittedCandidateReceiptError> { + let maybe_core_selector = self.commitments.core_selector()?; + match self.descriptor.version() { - // Don't check v1 descriptors. - CandidateDescriptorVersion::V1 => return Ok(()), + CandidateDescriptorVersion::V1 => { + // If the parachain runtime started sending core selectors, v1 descriptors are no + // longer allowed. + if maybe_core_selector.is_some() { + return Err(CommittedCandidateReceiptError::CoreSelectorWithV1Decriptor) + } else { + // Nothing else to check for v1 descriptors. + return Ok(()) + } + }, CandidateDescriptorVersion::V2 => {}, CandidateDescriptorVersion::Unknown => return Err(CommittedCandidateReceiptError::UnknownVersion(self.descriptor.version)), } - let (maybe_core_index_selector, cq_offset) = self.commitments.core_selector()?.map_or_else( + let (maybe_core_index_selector, cq_offset) = maybe_core_selector.map_or_else( || (None, ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET)), |(sel, off)| (Some(sel), off), ); @@ -1207,8 +1222,7 @@ mod tests { assert_eq!(new_ccr.hash(), v2_ccr.hash()); } - // Only check descriptor `core_index` field of v2 descriptors. If it is v1, that field - // will be garbage. + // V1 descriptors are forbidden once the parachain runtime started sending UMP signals. #[test] fn test_v1_descriptors_with_ump_signal() { let mut ccr = dummy_old_committed_candidate_receipt(); @@ -1234,9 +1248,12 @@ mod tests { cq.insert(CoreIndex(0), vec![v1_ccr.descriptor.para_id()].into()); cq.insert(CoreIndex(1), vec![v1_ccr.descriptor.para_id()].into()); - assert!(v1_ccr.check_core_index(&transpose_claim_queue(cq)).is_ok()); - assert_eq!(v1_ccr.descriptor.core_index(), None); + + assert_eq!( + v1_ccr.check_core_index(&transpose_claim_queue(cq)), + Err(CommittedCandidateReceiptError::CoreSelectorWithV1Decriptor) + ); } #[test] diff --git a/polkadot/primitives/test-helpers/Cargo.toml b/polkadot/primitives/test-helpers/Cargo.toml index 27de3c4b9c56c6fbd182c3c0126325b5e80f7131..962b210848c894a1db5bdf19fe5297a99f84d3ca 100644 --- a/polkadot/primitives/test-helpers/Cargo.toml +++ b/polkadot/primitives/test-helpers/Cargo.toml @@ -10,9 +10,9 @@ license.workspace = true workspace = true [dependencies] -sp-keyring = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true } -sp-runtime = { workspace = true, default-features = true } -sp-core = { features = ["std"], workspace = true, default-features = true } polkadot-primitives = { features = ["test"], workspace = true, default-features = true } rand = { workspace = true, default-features = true } +sp-application-crypto = { workspace = true } +sp-core = { features = ["std"], workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } diff --git a/polkadot/primitives/test-helpers/src/lib.rs b/polkadot/primitives/test-helpers/src/lib.rs index 1717dd5b0edae7ee3c2d67c0da8a04403e79972a..b7cdfa83e10d32c3d0beaf5975b9af61c1adccc8 100644 --- a/polkadot/primitives/test-helpers/src/lib.rs +++ b/polkadot/primitives/test-helpers/src/lib.rs @@ -381,22 +381,30 @@ pub struct TestCandidateBuilder { pub pov_hash: Hash, pub relay_parent: Hash, pub commitments_hash: Hash, + pub core_index: CoreIndex, } impl std::default::Default for TestCandidateBuilder { fn default() -> Self { let zeros = Hash::zero(); - Self { para_id: 0.into(), pov_hash: zeros, relay_parent: zeros, commitments_hash: zeros } + Self { + para_id: 0.into(), + pov_hash: zeros, + relay_parent: zeros, + commitments_hash: zeros, + core_index: CoreIndex(0), + } } } impl TestCandidateBuilder { /// Build a `CandidateReceipt`. pub fn build(self) -> CandidateReceiptV2 { - let mut descriptor = dummy_candidate_descriptor(self.relay_parent); - descriptor.para_id = self.para_id; - descriptor.pov_hash = self.pov_hash; - CandidateReceipt { descriptor, commitments_hash: self.commitments_hash }.into() + let mut descriptor = dummy_candidate_descriptor_v2(self.relay_parent); + descriptor.set_para_id(self.para_id); + descriptor.set_pov_hash(self.pov_hash); + descriptor.set_core_index(self.core_index); + CandidateReceiptV2 { descriptor, commitments_hash: self.commitments_hash } } } diff --git a/polkadot/roadmap/implementers-guide/src/architecture.md b/polkadot/roadmap/implementers-guide/src/architecture.md index b75270662005005f4b0a1102b4d95b3d9af2dab0..e2be92e4cddbcc014c58ab1d8223c6c0d0b3e9fd 100644 --- a/polkadot/roadmap/implementers-guide/src/architecture.md +++ b/polkadot/roadmap/implementers-guide/src/architecture.md @@ -93,7 +93,7 @@ Runtime logic is divided up into Modules and APIs. Modules encapsulate particula consist of storage, routines, and entry-points. Routines are invoked by entry points, by other modules, upon block initialization or closing. Routines can read and alter the storage of the module. Entry-points are the means by which new information is introduced to a module and can limit the origins (user, root, parachain) that they accept being -called by. Each block in the blockchain contains a set of Extrinsics. Each extrinsic targets a a specific entry point to +called by. Each block in the blockchain contains a set of Extrinsics. Each extrinsic targets a specific entry point to trigger and which data should be passed to it. Runtime APIs provide a means for Node-side behavior to extract meaningful information from the state of a single fork. diff --git a/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting.md b/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting.md index 40394412d81b0d47d1cc96b7a399662b63611176..7e155cdf7d58f56df71be4928afceeb191e5e264 100644 --- a/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting.md +++ b/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting.md @@ -406,7 +406,7 @@ Some(core_index), response_sender)` * Construct a `IndirectSignedApprovalVote` using the information about the vote. * Dispatch `ApprovalDistributionMessage::DistributeApproval`. * ELSE - * Re-arm the timer with latest tick we have the send a the vote. + * Re-arm the timer with latest tick we have then send the vote. ### Determining Approval of Candidate diff --git a/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md b/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md index 61278621cf565c226c2133eb90c0d7e0430c5624..0f210a07864006cc410fee1160ce55aa1a2d9461 100644 --- a/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md +++ b/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md @@ -126,6 +126,9 @@ prospective validation data. This is unlikely to change. - `RuntimeApiRequest::ParaBackingState` - Gets the backing state of the given para (the constraints of the para and candidates pending availability). +- `RuntimeApiRequest::BackingConstraints` + - Gets the constraints on the actions that can be taken by a new parachain + block. - `RuntimeApiRequest::AvailabilityCores` - Gets information on all availability cores. - `ChainApiMessage::Ancestors` diff --git a/polkadot/roadmap/implementers-guide/src/node/collators/collator-protocol.md b/polkadot/roadmap/implementers-guide/src/node/collators/collator-protocol.md index 432d9ab69bab99297b51fb5af285e7636e8b90ae..586a4169b5bc5c92808e32d540dfcabb06b8c6bb 100644 --- a/polkadot/roadmap/implementers-guide/src/node/collators/collator-protocol.md +++ b/polkadot/roadmap/implementers-guide/src/node/collators/collator-protocol.md @@ -151,12 +151,6 @@ time per relay parent. This reduces the bandwidth requirements and as we can sec the others are probably not required anyway. If the request times out, we need to note the collator as being unreliable and reduce its priority relative to other collators. -As a validator, once the collation has been fetched some other subsystem will inspect and do deeper validation of the -collation. The subsystem will report to this subsystem with a [`CollatorProtocolMessage`][CPM]`::ReportCollator`. In -that case, if we are connected directly to the collator, we apply a cost to the `PeerId` associated with the collator -and potentially disconnect or blacklist it. If the collation is seconded, we notify the collator and apply a benefit to -the `PeerId` associated with the collator. - ### Interaction with [Candidate Backing][CB] As collators advertise the availability, a validator will simply second the first valid parablock candidate per relay diff --git a/polkadot/roadmap/implementers-guide/src/node/subsystems-and-jobs.md b/polkadot/roadmap/implementers-guide/src/node/subsystems-and-jobs.md index a3ca7347eb63efedb787c90a23c8e5b95138bcdb..a96f3fa3d4a000ce3596fa0e3d4630f05f09cb73 100644 --- a/polkadot/roadmap/implementers-guide/src/node/subsystems-and-jobs.md +++ b/polkadot/roadmap/implementers-guide/src/node/subsystems-and-jobs.md @@ -129,7 +129,6 @@ digraph { cand_sel -> coll_prot [arrowhead = "diamond", label = "FetchCollation"] cand_sel -> cand_back [arrowhead = "onormal", label = "Second"] - cand_sel -> coll_prot [arrowhead = "onormal", label = "ReportCollator"] cand_val -> runt_api [arrowhead = "diamond", label = "Request::PersistedValidationData"] cand_val -> runt_api [arrowhead = "diamond", label = "Request::ValidationCode"] @@ -231,7 +230,7 @@ sequenceDiagram VS ->> CandidateSelection: Collation - Note over CandidateSelection: Lots of other machinery in play here,<br/>but there are only three outcomes from the<br/>perspective of the `CollatorProtocol`: + Note over CandidateSelection: Lots of other machinery in play here,<br/>but there are only two outcomes from the<br/>perspective of the `CollatorProtocol`: alt happy path CandidateSelection -->> VS: FetchCollation @@ -242,10 +241,6 @@ sequenceDiagram NB ->> VS: Collation Deactivate VS - else collation invalid or unexpected - CandidateSelection ->> VS: ReportCollator - VS ->> NB: ReportPeer - else CandidateSelection already selected a different candidate Note over CandidateSelection: silently drop end diff --git a/polkadot/roadmap/implementers-guide/src/protocol-approval.md b/polkadot/roadmap/implementers-guide/src/protocol-approval.md index b6aa16646ad25f339af3a6b3db9d7b2d65997c4d..25d4fa5dadaef84b940d84a9aba238528bc49caa 100644 --- a/polkadot/roadmap/implementers-guide/src/protocol-approval.md +++ b/polkadot/roadmap/implementers-guide/src/protocol-approval.md @@ -84,7 +84,7 @@ slashing risk for validator operators. In future, we shall determine which among the several hardening techniques best benefits the network as a whole. We could provide a multi-process multi-machine architecture for validators, perhaps even reminiscent of GNUNet, or perhaps -more resembling smart HSM tooling. We might instead design a system that more resembled full systems, like like Cosmos' +more resembling smart HSM tooling. We might instead design a system that more resembled full systems, like Cosmos' sentry nodes. In either case, approval assignments might be handled by a slightly hardened machine, but not necessarily nearly as hardened as approval votes, but approval votes machines must similarly run foreign WASM code, which increases their risk, so assignments being separate sounds helpful. diff --git a/polkadot/roadmap/implementers-guide/src/protocol-validator-disabling.md b/polkadot/roadmap/implementers-guide/src/protocol-validator-disabling.md index 9fd44c00fa0a1c9ba7f5bb0d5abd54bdc55b8e4e..c2861b4035e76885b527e4e53864fe812767ed89 100644 --- a/polkadot/roadmap/implementers-guide/src/protocol-validator-disabling.md +++ b/polkadot/roadmap/implementers-guide/src/protocol-validator-disabling.md @@ -111,7 +111,7 @@ checking (% for 30-ish malicious in a row). There are also censorship or liveness issues if backing is suddenly dominated by malicious nodes but in general even if some honest blocks get backed liveness should be preserved. -> **Note:** It is worth noting that is is fundamentally a defense in depth strategy because if we assume disputes are +> **Note:** It is worth noting that is fundamentally a defense in depth strategy because if we assume disputes are > perfect it should not be a real concern. In reality disputes and determinism are difficult to get right, and > non-determinism and happen so defense in depth is crucial when handling those subsystems. diff --git a/polkadot/roadmap/implementers-guide/src/runtime/session_info.md b/polkadot/roadmap/implementers-guide/src/runtime/session_info.md index fa7f55c4f0b41c42270fc4c5f7d6c826f903f20e..daf7e5c7fd80a1b6b438c8f1dc10c70a55db0999 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/session_info.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/session_info.md @@ -16,7 +16,7 @@ struct SessionInfo { /// in parachain consensus. See /// [`max_validators`](https://github.com/paritytech/polkadot/blob/a52dca2be7840b23c19c153cf7e110b1e3e475f8/runtime/parachains/src/configuration.rs#L148). /// - /// `SessionInfo::validators` will be limited to to `max_validators` when set. + /// `SessionInfo::validators` will be limited to `max_validators` when set. validators: Vec<ValidatorId>, /// Validators' authority discovery keys for the session in canonical ordering. /// diff --git a/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md b/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md index 85415e42a11c0aa4cefc665592937aced77bd0ed..cb862440727b6e414695e7c2c7d13ffd99a85f2c 100644 --- a/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md +++ b/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md @@ -436,9 +436,6 @@ enum CollatorProtocolMessage { DistributeCollation(CandidateReceipt, PoV, Option<oneshot::Sender<CollationSecondedSignal>>), /// Fetch a collation under the given relay-parent for the given ParaId. FetchCollation(Hash, ParaId, ResponseChannel<(CandidateReceipt, PoV)>), - /// Report a collator as having provided an invalid collation. This should lead to disconnect - /// and blacklist of the collator. - ReportCollator(CollatorId), /// Note a collator as having provided a good collation. NoteGoodCollation(CollatorId, SignedFullStatement), /// Notify a collator that its collation was seconded. diff --git a/polkadot/rpc/Cargo.toml b/polkadot/rpc/Cargo.toml index d01528d4dee07d2d1d5ae59f5dac6a61e60fb197..33ce3ff4acc68ce35a4956196b8871eb1cc77779 100644 --- a/polkadot/rpc/Cargo.toml +++ b/polkadot/rpc/Cargo.toml @@ -5,25 +5,19 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Polkadot specific RPC functionality." +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] jsonrpsee = { features = ["server"], workspace = true } +mmr-rpc = { workspace = true, default-features = true } +pallet-transaction-payment-rpc = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } -sp-consensus-babe = { workspace = true, default-features = true } -sp-consensus-beefy = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } -sc-rpc = { workspace = true, default-features = true } -sc-rpc-spec-v2 = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } sc-consensus-babe = { workspace = true, default-features = true } sc-consensus-babe-rpc = { workspace = true, default-features = true } sc-consensus-beefy = { workspace = true, default-features = true } @@ -31,10 +25,18 @@ sc-consensus-beefy-rpc = { workspace = true, default-features = true } sc-consensus-epochs = { workspace = true, default-features = true } sc-consensus-grandpa = { workspace = true, default-features = true } sc-consensus-grandpa-rpc = { workspace = true, default-features = true } +sc-rpc = { workspace = true, default-features = true } +sc-rpc-spec-v2 = { workspace = true, default-features = true } sc-sync-state-rpc = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } -substrate-frame-rpc-system = { workspace = true, default-features = true } -mmr-rpc = { workspace = true, default-features = true } -pallet-transaction-payment-rpc = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } +sp-application-crypto = { workspace = true, default-features = true } sp-block-builder = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } +sp-consensus-beefy = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +substrate-frame-rpc-system = { workspace = true, default-features = true } substrate-state-trie-migration-rpc = { workspace = true, default-features = true } diff --git a/polkadot/runtime/common/Cargo.toml b/polkadot/runtime/common/Cargo.toml index 01b56b31cf20c3bcc931c988a418a379707ee849..4ffa5c475ed779fb19e0f777770f5ff72c9ef499 100644 --- a/polkadot/runtime/common/Cargo.toml +++ b/polkadot/runtime/common/Cargo.toml @@ -5,14 +5,16 @@ description = "Pallets and constants used in Relay Chain networks." authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] -impl-trait-for-tuples = { workspace = true } bitvec = { features = ["alloc"], workspace = true } codec = { features = ["derive"], workspace = true } +impl-trait-for-tuples = { workspace = true } log = { workspace = true } rustc-hex = { workspace = true } scale-info = { features = ["derive"], workspace = true } @@ -21,55 +23,55 @@ serde_derive = { workspace = true } static_assertions = { workspace = true, default-features = true } sp-api = { workspace = true } +sp-core = { features = ["serde"], workspace = true } sp-inherents = { workspace = true } sp-io = { workspace = true } +sp-keyring = { workspace = true } +sp-npos-elections = { features = ["serde"], workspace = true } sp-runtime = { features = ["serde"], workspace = true } sp-session = { workspace = true } sp-staking = { features = ["serde"], workspace = true } -sp-core = { features = ["serde"], workspace = true } -sp-keyring = { workspace = true } -sp-npos-elections = { features = ["serde"], workspace = true } +frame-election-provider-support = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-asset-rate = { optional = true, workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } pallet-broker = { workspace = true } +pallet-election-provider-multi-phase = { workspace = true } pallet-fast-unstake = { workspace = true } pallet-identity = { workspace = true } pallet-session = { workspace = true } -frame-support = { workspace = true } pallet-staking = { workspace = true } pallet-staking-reward-fn = { workspace = true } -frame-system = { workspace = true } pallet-timestamp = { workspace = true } -pallet-vesting = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-treasury = { workspace = true } -pallet-asset-rate = { optional = true, workspace = true } -pallet-election-provider-multi-phase = { workspace = true } -frame-election-provider-support = { workspace = true } +pallet-vesting = { workspace = true } frame-benchmarking = { optional = true, workspace = true } pallet-babe = { optional = true, workspace = true } -polkadot-primitives = { workspace = true } libsecp256k1 = { workspace = true } +polkadot-primitives = { workspace = true } polkadot-runtime-parachains = { workspace = true } slot-range-helper = { workspace = true } xcm = { workspace = true } -xcm-executor = { optional = true, workspace = true } xcm-builder = { workspace = true } +xcm-executor = { optional = true, workspace = true } [dev-dependencies] -hex-literal = { workspace = true, default-features = true } frame-support-test = { workspace = true } +hex-literal = { workspace = true, default-features = true } +libsecp256k1 = { workspace = true, default-features = true } pallet-babe = { workspace = true, default-features = true } pallet-treasury = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -libsecp256k1 = { workspace = true, default-features = true } polkadot-primitives-test-helpers = { workspace = true } +serde_json = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } [features] default = ["std"] @@ -140,6 +142,7 @@ runtime-benchmarks = [ "sp-staking/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-election-provider-support/try-runtime", diff --git a/polkadot/runtime/common/slot_range_helper/Cargo.toml b/polkadot/runtime/common/slot_range_helper/Cargo.toml index 02810b75283f8babda0de26fc91e16b6ac493669..684cdcd01e14422fdfa5e804dbf7a6d0d5e25cf6 100644 --- a/polkadot/runtime/common/slot_range_helper/Cargo.toml +++ b/polkadot/runtime/common/slot_range_helper/Cargo.toml @@ -5,14 +5,16 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Helper crate for generating slot ranges for the Polkadot runtime." +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] -paste = { workspace = true, default-features = true } -enumn = { workspace = true } codec = { features = ["derive"], workspace = true } +enumn = { workspace = true } +paste = { workspace = true, default-features = true } sp-runtime = { workspace = true } [features] diff --git a/polkadot/runtime/common/src/assigned_slots/mod.rs b/polkadot/runtime/common/src/assigned_slots/mod.rs index 65942c127b1cc05260c67c44b2350cf8b8b4490c..81e2986ab6b3cd8cde7251caff3e5f826dd0c567 100644 --- a/polkadot/runtime/common/src/assigned_slots/mod.rs +++ b/polkadot/runtime/common/src/assigned_slots/mod.rs @@ -773,6 +773,7 @@ mod tests { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); pallet_balances::GenesisConfig::<Test> { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -788,39 +789,14 @@ mod tests { t.into() } - fn run_to_block(n: BlockNumber) { - while System::block_number() < n { - let mut block = System::block_number(); - // on_finalize hooks - AssignedSlots::on_finalize(block); - Slots::on_finalize(block); - Parachains::on_finalize(block); - ParasShared::on_finalize(block); - Configuration::on_finalize(block); - Balances::on_finalize(block); - System::on_finalize(block); - // Set next block - System::set_block_number(block + 1); - block = System::block_number(); - // on_initialize hooks - System::on_initialize(block); - Balances::on_initialize(block); - Configuration::on_initialize(block); - ParasShared::on_initialize(block); - Parachains::on_initialize(block); - Slots::on_initialize(block); - AssignedSlots::on_initialize(block); - } - } - #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_eq!(AssignedSlots::current_lease_period_index(), 0); assert_eq!(Slots::deposit_held(1.into(), &1), 0); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert_eq!(AssignedSlots::current_lease_period_index(), 1); }); } @@ -828,7 +804,7 @@ mod tests { #[test] fn assign_perm_slot_fails_for_unknown_para() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_noop!( AssignedSlots::assign_perm_parachain_slot( @@ -843,7 +819,7 @@ mod tests { #[test] fn assign_perm_slot_fails_for_invalid_origin() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_noop!( AssignedSlots::assign_perm_parachain_slot( @@ -858,7 +834,7 @@ mod tests { #[test] fn assign_perm_slot_fails_when_not_parathread() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(TestRegistrar::<Test>::register( 1, @@ -881,7 +857,7 @@ mod tests { #[test] fn assign_perm_slot_fails_when_existing_lease() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(TestRegistrar::<Test>::register( 1, @@ -920,7 +896,7 @@ mod tests { #[test] fn assign_perm_slot_fails_when_max_perm_slots_exceeded() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(TestRegistrar::<Test>::register( 1, @@ -967,7 +943,7 @@ mod tests { fn assign_perm_slot_succeeds_for_parathread() { new_test_ext().execute_with(|| { let mut block = 1; - run_to_block(block); + System::run_to_block::<AllPalletsWithSystem>(block); assert_ok!(TestRegistrar::<Test>::register( 1, ParaId::from(1_u32), @@ -1000,7 +976,7 @@ mod tests { assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 2), true); block += 1; - run_to_block(block); + System::run_to_block::<AllPalletsWithSystem>(block); } // Para lease ended, downgraded back to parathread (on-demand parachain) @@ -1012,7 +988,7 @@ mod tests { #[test] fn assign_temp_slot_fails_for_unknown_para() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_noop!( AssignedSlots::assign_temp_parachain_slot( @@ -1028,7 +1004,7 @@ mod tests { #[test] fn assign_temp_slot_fails_for_invalid_origin() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_noop!( AssignedSlots::assign_temp_parachain_slot( @@ -1044,7 +1020,7 @@ mod tests { #[test] fn assign_temp_slot_fails_when_not_parathread() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(TestRegistrar::<Test>::register( 1, @@ -1068,7 +1044,7 @@ mod tests { #[test] fn assign_temp_slot_fails_when_existing_lease() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(TestRegistrar::<Test>::register( 1, @@ -1109,7 +1085,7 @@ mod tests { #[test] fn assign_temp_slot_fails_when_max_temp_slots_exceeded() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); // Register 6 paras & a temp slot for each for n in 0..=5 { @@ -1151,7 +1127,7 @@ mod tests { fn assign_temp_slot_succeeds_for_single_parathread() { new_test_ext().execute_with(|| { let mut block = 1; - run_to_block(block); + System::run_to_block::<AllPalletsWithSystem>(block); assert_ok!(TestRegistrar::<Test>::register( 1, ParaId::from(1_u32), @@ -1195,7 +1171,7 @@ mod tests { assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 1), true); block += 1; - run_to_block(block); + System::run_to_block::<AllPalletsWithSystem>(block); } // Block 6 @@ -1210,7 +1186,7 @@ mod tests { // Block 12 // Para should get a turn after TemporarySlotLeasePeriodLength * LeasePeriod blocks - run_to_block(12); + System::run_to_block::<AllPalletsWithSystem>(12); println!("block #{}", block); println!("lease period #{}", AssignedSlots::current_lease_period_index()); println!("lease {:?}", slots::Leases::<Test>::get(ParaId::from(1_u32))); @@ -1225,7 +1201,7 @@ mod tests { fn assign_temp_slot_succeeds_for_multiple_parathreads() { new_test_ext().execute_with(|| { // Block 1, Period 0 - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); // Register 6 paras & a temp slot for each // (3 slots in current lease period, 3 in the next one) @@ -1251,7 +1227,7 @@ mod tests { // Block 1-5, Period 0-1 for n in 1..=5 { if n > 1 { - run_to_block(n); + System::run_to_block::<AllPalletsWithSystem>(n); } assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(0)), true); assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(1_u32)), false); @@ -1264,7 +1240,7 @@ mod tests { // Block 6-11, Period 2-3 for n in 6..=11 { - run_to_block(n); + System::run_to_block::<AllPalletsWithSystem>(n); assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(0)), false); assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(1_u32)), true); assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(2_u32)), false); @@ -1276,7 +1252,7 @@ mod tests { // Block 12-17, Period 4-5 for n in 12..=17 { - run_to_block(n); + System::run_to_block::<AllPalletsWithSystem>(n); assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(0)), false); assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(1_u32)), false); assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(2_u32)), false); @@ -1288,7 +1264,7 @@ mod tests { // Block 18-23, Period 6-7 for n in 18..=23 { - run_to_block(n); + System::run_to_block::<AllPalletsWithSystem>(n); assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(0)), true); assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(1_u32)), false); assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(2_u32)), true); @@ -1300,7 +1276,7 @@ mod tests { // Block 24-29, Period 8-9 for n in 24..=29 { - run_to_block(n); + System::run_to_block::<AllPalletsWithSystem>(n); assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(0)), false); assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(1_u32)), true); assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(2_u32)), false); @@ -1312,7 +1288,7 @@ mod tests { // Block 30-35, Period 10-11 for n in 30..=35 { - run_to_block(n); + System::run_to_block::<AllPalletsWithSystem>(n); assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(0)), false); assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(1_u32)), false); assert_eq!(TestRegistrar::<Test>::is_parachain(ParaId::from(2_u32)), false); @@ -1327,7 +1303,7 @@ mod tests { #[test] fn unassign_slot_fails_for_unknown_para() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_noop!( AssignedSlots::unassign_parachain_slot(RuntimeOrigin::root(), ParaId::from(1_u32),), @@ -1339,7 +1315,7 @@ mod tests { #[test] fn unassign_slot_fails_for_invalid_origin() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_noop!( AssignedSlots::assign_perm_parachain_slot( @@ -1354,7 +1330,7 @@ mod tests { #[test] fn unassign_perm_slot_succeeds() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(TestRegistrar::<Test>::register( 1, @@ -1386,7 +1362,7 @@ mod tests { #[test] fn unassign_temp_slot_succeeds() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(TestRegistrar::<Test>::register( 1, @@ -1419,7 +1395,7 @@ mod tests { #[test] fn set_max_permanent_slots_fails_for_no_root_origin() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_noop!( AssignedSlots::set_max_permanent_slots(RuntimeOrigin::signed(1), 5), @@ -1430,7 +1406,7 @@ mod tests { #[test] fn set_max_permanent_slots_succeeds() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_eq!(MaxPermanentSlots::<Test>::get(), 2); assert_ok!(AssignedSlots::set_max_permanent_slots(RuntimeOrigin::root(), 10),); @@ -1441,7 +1417,7 @@ mod tests { #[test] fn set_max_temporary_slots_fails_for_no_root_origin() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_noop!( AssignedSlots::set_max_temporary_slots(RuntimeOrigin::signed(1), 5), @@ -1452,7 +1428,7 @@ mod tests { #[test] fn set_max_temporary_slots_succeeds() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_eq!(MaxTemporarySlots::<Test>::get(), 6); assert_ok!(AssignedSlots::set_max_temporary_slots(RuntimeOrigin::root(), 12),); diff --git a/polkadot/runtime/common/src/auctions.rs b/polkadot/runtime/common/src/auctions.rs deleted file mode 100644 index 78f20d918bab52b11eccc076070f67a76b95e3de..0000000000000000000000000000000000000000 --- a/polkadot/runtime/common/src/auctions.rs +++ /dev/null @@ -1,1934 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see <http://www.gnu.org/licenses/>. - -//! Auctioning system to determine the set of Parachains in operation. This includes logic for the -//! auctioning mechanism and for reserving balance as part of the "payment". Unreserving the balance -//! happens elsewhere. - -use crate::{ - slot_range::SlotRange, - traits::{AuctionStatus, Auctioneer, LeaseError, Leaser, Registrar}, -}; -use alloc::{vec, vec::Vec}; -use codec::Decode; -use core::mem::swap; -use frame_support::{ - dispatch::DispatchResult, - ensure, - traits::{Currency, Get, Randomness, ReservableCurrency}, - weights::Weight, -}; -use frame_system::pallet_prelude::BlockNumberFor; -pub use pallet::*; -use polkadot_primitives::Id as ParaId; -use sp_runtime::traits::{CheckedSub, One, Saturating, Zero}; - -type CurrencyOf<T> = <<T as Config>::Leaser as Leaser<BlockNumberFor<T>>>::Currency; -type BalanceOf<T> = <<<T as Config>::Leaser as Leaser<BlockNumberFor<T>>>::Currency as Currency< - <T as frame_system::Config>::AccountId, ->>::Balance; - -pub trait WeightInfo { - fn new_auction() -> Weight; - fn bid() -> Weight; - fn cancel_auction() -> Weight; - fn on_initialize() -> Weight; -} - -pub struct TestWeightInfo; -impl WeightInfo for TestWeightInfo { - fn new_auction() -> Weight { - Weight::zero() - } - fn bid() -> Weight { - Weight::zero() - } - fn cancel_auction() -> Weight { - Weight::zero() - } - fn on_initialize() -> Weight { - Weight::zero() - } -} - -/// An auction index. We count auctions in this type. -pub type AuctionIndex = u32; - -type LeasePeriodOf<T> = <<T as Config>::Leaser as Leaser<BlockNumberFor<T>>>::LeasePeriod; - -// Winning data type. This encodes the top bidders of each range together with their bid. -type WinningData<T> = [Option<(<T as frame_system::Config>::AccountId, ParaId, BalanceOf<T>)>; - SlotRange::SLOT_RANGE_COUNT]; -// Winners data type. This encodes each of the final winners of a parachain auction, the parachain -// index assigned to them, their winning bid and the range that they won. -type WinnersData<T> = - Vec<(<T as frame_system::Config>::AccountId, ParaId, BalanceOf<T>, SlotRange)>; - -#[frame_support::pallet] -pub mod pallet { - use super::*; - use frame_support::{dispatch::DispatchClass, pallet_prelude::*, traits::EnsureOrigin}; - use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; - - #[pallet::pallet] - pub struct Pallet<T>(_); - - /// The module's configuration trait. - #[pallet::config] - pub trait Config: frame_system::Config { - /// The overarching event type. - type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; - - /// The type representing the leasing system. - type Leaser: Leaser< - BlockNumberFor<Self>, - AccountId = Self::AccountId, - LeasePeriod = BlockNumberFor<Self>, - >; - - /// The parachain registrar type. - type Registrar: Registrar<AccountId = Self::AccountId>; - - /// The number of blocks over which an auction may be retroactively ended. - #[pallet::constant] - type EndingPeriod: Get<BlockNumberFor<Self>>; - - /// The length of each sample to take during the ending period. - /// - /// `EndingPeriod` / `SampleLength` = Total # of Samples - #[pallet::constant] - type SampleLength: Get<BlockNumberFor<Self>>; - - /// Something that provides randomness in the runtime. - type Randomness: Randomness<Self::Hash, BlockNumberFor<Self>>; - - /// The origin which may initiate auctions. - type InitiateOrigin: EnsureOrigin<Self::RuntimeOrigin>; - - /// Weight Information for the Extrinsics in the Pallet - type WeightInfo: WeightInfo; - } - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event<T: Config> { - /// An auction started. Provides its index and the block number where it will begin to - /// close and the first lease period of the quadruplet that is auctioned. - AuctionStarted { - auction_index: AuctionIndex, - lease_period: LeasePeriodOf<T>, - ending: BlockNumberFor<T>, - }, - /// An auction ended. All funds become unreserved. - AuctionClosed { auction_index: AuctionIndex }, - /// Funds were reserved for a winning bid. First balance is the extra amount reserved. - /// Second is the total. - Reserved { bidder: T::AccountId, extra_reserved: BalanceOf<T>, total_amount: BalanceOf<T> }, - /// Funds were unreserved since bidder is no longer active. `[bidder, amount]` - Unreserved { bidder: T::AccountId, amount: BalanceOf<T> }, - /// Someone attempted to lease the same slot twice for a parachain. The amount is held in - /// reserve but no parachain slot has been leased. - ReserveConfiscated { para_id: ParaId, leaser: T::AccountId, amount: BalanceOf<T> }, - /// A new bid has been accepted as the current winner. - BidAccepted { - bidder: T::AccountId, - para_id: ParaId, - amount: BalanceOf<T>, - first_slot: LeasePeriodOf<T>, - last_slot: LeasePeriodOf<T>, - }, - /// The winning offset was chosen for an auction. This will map into the `Winning` storage - /// map. - WinningOffset { auction_index: AuctionIndex, block_number: BlockNumberFor<T> }, - } - - #[pallet::error] - pub enum Error<T> { - /// This auction is already in progress. - AuctionInProgress, - /// The lease period is in the past. - LeasePeriodInPast, - /// Para is not registered - ParaNotRegistered, - /// Not a current auction. - NotCurrentAuction, - /// Not an auction. - NotAuction, - /// Auction has already ended. - AuctionEnded, - /// The para is already leased out for part of this range. - AlreadyLeasedOut, - } - - /// Number of auctions started so far. - #[pallet::storage] - pub type AuctionCounter<T> = StorageValue<_, AuctionIndex, ValueQuery>; - - /// Information relating to the current auction, if there is one. - /// - /// The first item in the tuple is the lease period index that the first of the four - /// contiguous lease periods on auction is for. The second is the block number when the - /// auction will "begin to end", i.e. the first block of the Ending Period of the auction. - #[pallet::storage] - pub type AuctionInfo<T: Config> = StorageValue<_, (LeasePeriodOf<T>, BlockNumberFor<T>)>; - - /// Amounts currently reserved in the accounts of the bidders currently winning - /// (sub-)ranges. - #[pallet::storage] - pub type ReservedAmounts<T: Config> = - StorageMap<_, Twox64Concat, (T::AccountId, ParaId), BalanceOf<T>>; - - /// The winning bids for each of the 10 ranges at each sample in the final Ending Period of - /// the current auction. The map's key is the 0-based index into the Sample Size. The - /// first sample of the ending period is 0; the last is `Sample Size - 1`. - #[pallet::storage] - pub type Winning<T: Config> = StorageMap<_, Twox64Concat, BlockNumberFor<T>, WinningData<T>>; - - #[pallet::extra_constants] - impl<T: Config> Pallet<T> { - #[pallet::constant_name(SlotRangeCount)] - fn slot_range_count() -> u32 { - SlotRange::SLOT_RANGE_COUNT as u32 - } - - #[pallet::constant_name(LeasePeriodsPerSlot)] - fn lease_periods_per_slot() -> u32 { - SlotRange::LEASE_PERIODS_PER_SLOT as u32 - } - } - - #[pallet::hooks] - impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { - fn on_initialize(n: BlockNumberFor<T>) -> Weight { - let mut weight = T::DbWeight::get().reads(1); - - // If the current auction was in its ending period last block, then ensure that the - // (sub-)range winner information is duplicated from the previous block in case no bids - // happened in the last block. - if let AuctionStatus::EndingPeriod(offset, _sub_sample) = Self::auction_status(n) { - weight = weight.saturating_add(T::DbWeight::get().reads(1)); - if !Winning::<T>::contains_key(&offset) { - weight = weight.saturating_add(T::DbWeight::get().writes(1)); - let winning_data = offset - .checked_sub(&One::one()) - .and_then(Winning::<T>::get) - .unwrap_or([Self::EMPTY; SlotRange::SLOT_RANGE_COUNT]); - Winning::<T>::insert(offset, winning_data); - } - } - - // Check to see if an auction just ended. - if let Some((winning_ranges, auction_lease_period_index)) = Self::check_auction_end(n) { - // Auction is ended now. We have the winning ranges and the lease period index which - // acts as the offset. Handle it. - Self::manage_auction_end(auction_lease_period_index, winning_ranges); - weight = weight.saturating_add(T::WeightInfo::on_initialize()); - } - - weight - } - } - - #[pallet::call] - impl<T: Config> Pallet<T> { - /// Create a new auction. - /// - /// This can only happen when there isn't already an auction in progress and may only be - /// called by the root origin. Accepts the `duration` of this auction and the - /// `lease_period_index` of the initial lease period of the four that are to be auctioned. - #[pallet::call_index(0)] - #[pallet::weight((T::WeightInfo::new_auction(), DispatchClass::Operational))] - pub fn new_auction( - origin: OriginFor<T>, - #[pallet::compact] duration: BlockNumberFor<T>, - #[pallet::compact] lease_period_index: LeasePeriodOf<T>, - ) -> DispatchResult { - T::InitiateOrigin::ensure_origin(origin)?; - Self::do_new_auction(duration, lease_period_index) - } - - /// Make a new bid from an account (including a parachain account) for deploying a new - /// parachain. - /// - /// Multiple simultaneous bids from the same bidder are allowed only as long as all active - /// bids overlap each other (i.e. are mutually exclusive). Bids cannot be redacted. - /// - /// - `sub` is the sub-bidder ID, allowing for multiple competing bids to be made by (and - /// funded by) the same account. - /// - `auction_index` is the index of the auction to bid on. Should just be the present - /// value of `AuctionCounter`. - /// - `first_slot` is the first lease period index of the range to bid on. This is the - /// absolute lease period index value, not an auction-specific offset. - /// - `last_slot` is the last lease period index of the range to bid on. This is the - /// absolute lease period index value, not an auction-specific offset. - /// - `amount` is the amount to bid to be held as deposit for the parachain should the - /// bid win. This amount is held throughout the range. - #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::bid())] - pub fn bid( - origin: OriginFor<T>, - #[pallet::compact] para: ParaId, - #[pallet::compact] auction_index: AuctionIndex, - #[pallet::compact] first_slot: LeasePeriodOf<T>, - #[pallet::compact] last_slot: LeasePeriodOf<T>, - #[pallet::compact] amount: BalanceOf<T>, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - Self::handle_bid(who, para, auction_index, first_slot, last_slot, amount)?; - Ok(()) - } - - /// Cancel an in-progress auction. - /// - /// Can only be called by Root origin. - #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::cancel_auction())] - pub fn cancel_auction(origin: OriginFor<T>) -> DispatchResult { - ensure_root(origin)?; - // Unreserve all bids. - for ((bidder, _), amount) in ReservedAmounts::<T>::drain() { - CurrencyOf::<T>::unreserve(&bidder, amount); - } - #[allow(deprecated)] - Winning::<T>::remove_all(None); - AuctionInfo::<T>::kill(); - Ok(()) - } - } -} - -impl<T: Config> Auctioneer<BlockNumberFor<T>> for Pallet<T> { - type AccountId = T::AccountId; - type LeasePeriod = BlockNumberFor<T>; - type Currency = CurrencyOf<T>; - - fn new_auction( - duration: BlockNumberFor<T>, - lease_period_index: LeasePeriodOf<T>, - ) -> DispatchResult { - Self::do_new_auction(duration, lease_period_index) - } - - // Returns the status of the auction given the current block number. - fn auction_status(now: BlockNumberFor<T>) -> AuctionStatus<BlockNumberFor<T>> { - let early_end = match AuctionInfo::<T>::get() { - Some((_, early_end)) => early_end, - None => return AuctionStatus::NotStarted, - }; - - let after_early_end = match now.checked_sub(&early_end) { - Some(after_early_end) => after_early_end, - None => return AuctionStatus::StartingPeriod, - }; - - let ending_period = T::EndingPeriod::get(); - if after_early_end < ending_period { - let sample_length = T::SampleLength::get().max(One::one()); - let sample = after_early_end / sample_length; - let sub_sample = after_early_end % sample_length; - return AuctionStatus::EndingPeriod(sample, sub_sample) - } else { - // This is safe because of the comparison operator above - return AuctionStatus::VrfDelay(after_early_end - ending_period) - } - } - - fn place_bid( - bidder: T::AccountId, - para: ParaId, - first_slot: LeasePeriodOf<T>, - last_slot: LeasePeriodOf<T>, - amount: BalanceOf<T>, - ) -> DispatchResult { - Self::handle_bid(bidder, para, AuctionCounter::<T>::get(), first_slot, last_slot, amount) - } - - fn lease_period_index(b: BlockNumberFor<T>) -> Option<(Self::LeasePeriod, bool)> { - T::Leaser::lease_period_index(b) - } - - #[cfg(any(feature = "runtime-benchmarks", test))] - fn lease_period_length() -> (BlockNumberFor<T>, BlockNumberFor<T>) { - T::Leaser::lease_period_length() - } - - fn has_won_an_auction(para: ParaId, bidder: &T::AccountId) -> bool { - !T::Leaser::deposit_held(para, bidder).is_zero() - } -} - -impl<T: Config> Pallet<T> { - // A trick to allow me to initialize large arrays with nothing in them. - const EMPTY: Option<(<T as frame_system::Config>::AccountId, ParaId, BalanceOf<T>)> = None; - - /// Create a new auction. - /// - /// This can only happen when there isn't already an auction in progress. Accepts the `duration` - /// of this auction and the `lease_period_index` of the initial lease period of the four that - /// are to be auctioned. - fn do_new_auction( - duration: BlockNumberFor<T>, - lease_period_index: LeasePeriodOf<T>, - ) -> DispatchResult { - let maybe_auction = AuctionInfo::<T>::get(); - ensure!(maybe_auction.is_none(), Error::<T>::AuctionInProgress); - let now = frame_system::Pallet::<T>::block_number(); - if let Some((current_lease_period, _)) = T::Leaser::lease_period_index(now) { - // If there is no active lease period, then we don't need to make this check. - ensure!(lease_period_index >= current_lease_period, Error::<T>::LeasePeriodInPast); - } - - // Bump the counter. - let n = AuctionCounter::<T>::mutate(|n| { - *n += 1; - *n - }); - - // Set the information. - let ending = frame_system::Pallet::<T>::block_number().saturating_add(duration); - AuctionInfo::<T>::put((lease_period_index, ending)); - - Self::deposit_event(Event::<T>::AuctionStarted { - auction_index: n, - lease_period: lease_period_index, - ending, - }); - Ok(()) - } - - /// Actually place a bid in the current auction. - /// - /// - `bidder`: The account that will be funding this bid. - /// - `auction_index`: The auction index of the bid. For this to succeed, must equal - /// the current value of `AuctionCounter`. - /// - `first_slot`: The first lease period index of the range to be bid on. - /// - `last_slot`: The last lease period index of the range to be bid on (inclusive). - /// - `amount`: The total amount to be the bid for deposit over the range. - pub fn handle_bid( - bidder: T::AccountId, - para: ParaId, - auction_index: u32, - first_slot: LeasePeriodOf<T>, - last_slot: LeasePeriodOf<T>, - amount: BalanceOf<T>, - ) -> DispatchResult { - // Ensure para is registered before placing a bid on it. - ensure!(T::Registrar::is_registered(para), Error::<T>::ParaNotRegistered); - // Bidding on latest auction. - ensure!(auction_index == AuctionCounter::<T>::get(), Error::<T>::NotCurrentAuction); - // Assume it's actually an auction (this should never fail because of above). - let (first_lease_period, _) = AuctionInfo::<T>::get().ok_or(Error::<T>::NotAuction)?; - - // Get the auction status and the current sample block. For the starting period, the sample - // block is zero. - let auction_status = Self::auction_status(frame_system::Pallet::<T>::block_number()); - // The offset into the ending samples of the auction. - let offset = match auction_status { - AuctionStatus::NotStarted => return Err(Error::<T>::AuctionEnded.into()), - AuctionStatus::StartingPeriod => Zero::zero(), - AuctionStatus::EndingPeriod(o, _) => o, - AuctionStatus::VrfDelay(_) => return Err(Error::<T>::AuctionEnded.into()), - }; - - // We also make sure that the bid is not for any existing leases the para already has. - ensure!( - !T::Leaser::already_leased(para, first_slot, last_slot), - Error::<T>::AlreadyLeasedOut - ); - - // Our range. - let range = SlotRange::new_bounded(first_lease_period, first_slot, last_slot)?; - // Range as an array index. - let range_index = range as u8 as usize; - - // The current winning ranges. - let mut current_winning = Winning::<T>::get(offset) - .or_else(|| offset.checked_sub(&One::one()).and_then(Winning::<T>::get)) - .unwrap_or([Self::EMPTY; SlotRange::SLOT_RANGE_COUNT]); - - // If this bid beat the previous winner of our range. - if current_winning[range_index].as_ref().map_or(true, |last| amount > last.2) { - // Ok; we are the new winner of this range - reserve the additional amount and record. - - // Get the amount already held on deposit if this is a renewal bid (i.e. there's - // an existing lease on the same para by the same leaser). - let existing_lease_deposit = T::Leaser::deposit_held(para, &bidder); - let reserve_required = amount.saturating_sub(existing_lease_deposit); - - // Get the amount already reserved in any prior and still active bids by us. - let bidder_para = (bidder.clone(), para); - let already_reserved = ReservedAmounts::<T>::get(&bidder_para).unwrap_or_default(); - - // If these don't already cover the bid... - if let Some(additional) = reserve_required.checked_sub(&already_reserved) { - // ...then reserve some more funds from their account, failing if there's not - // enough funds. - CurrencyOf::<T>::reserve(&bidder, additional)?; - // ...and record the amount reserved. - ReservedAmounts::<T>::insert(&bidder_para, reserve_required); - - Self::deposit_event(Event::<T>::Reserved { - bidder: bidder.clone(), - extra_reserved: additional, - total_amount: reserve_required, - }); - } - - // Return any funds reserved for the previous winner if we are not in the ending period - // and they no longer have any active bids. - let mut outgoing_winner = Some((bidder.clone(), para, amount)); - swap(&mut current_winning[range_index], &mut outgoing_winner); - if let Some((who, para, _amount)) = outgoing_winner { - if auction_status.is_starting() && - current_winning - .iter() - .filter_map(Option::as_ref) - .all(|&(ref other, other_para, _)| other != &who || other_para != para) - { - // Previous bidder is no longer winning any ranges: unreserve their funds. - if let Some(amount) = ReservedAmounts::<T>::take(&(who.clone(), para)) { - // It really should be reserved; there's not much we can do here on fail. - let err_amt = CurrencyOf::<T>::unreserve(&who, amount); - debug_assert!(err_amt.is_zero()); - Self::deposit_event(Event::<T>::Unreserved { bidder: who, amount }); - } - } - } - - // Update the range winner. - Winning::<T>::insert(offset, ¤t_winning); - Self::deposit_event(Event::<T>::BidAccepted { - bidder, - para_id: para, - amount, - first_slot, - last_slot, - }); - } - Ok(()) - } - - /// Some when the auction's end is known (with the end block number). None if it is unknown. - /// If `Some` then the block number must be at most the previous block and at least the - /// previous block minus `T::EndingPeriod::get()`. - /// - /// This mutates the state, cleaning up `AuctionInfo` and `Winning` in the case of an auction - /// ending. An immediately subsequent call with the same argument will always return `None`. - fn check_auction_end(now: BlockNumberFor<T>) -> Option<(WinningData<T>, LeasePeriodOf<T>)> { - if let Some((lease_period_index, early_end)) = AuctionInfo::<T>::get() { - let ending_period = T::EndingPeriod::get(); - let late_end = early_end.saturating_add(ending_period); - let is_ended = now >= late_end; - if is_ended { - // auction definitely ended. - // check to see if we can determine the actual ending point. - let (raw_offset, known_since) = T::Randomness::random(&b"para_auction"[..]); - - if late_end <= known_since { - // Our random seed was known only after the auction ended. Good to use. - let raw_offset_block_number = <BlockNumberFor<T>>::decode( - &mut raw_offset.as_ref(), - ) - .expect("secure hashes should always be bigger than the block number; qed"); - let offset = (raw_offset_block_number % ending_period) / - T::SampleLength::get().max(One::one()); - - let auction_counter = AuctionCounter::<T>::get(); - Self::deposit_event(Event::<T>::WinningOffset { - auction_index: auction_counter, - block_number: offset, - }); - let res = Winning::<T>::get(offset) - .unwrap_or([Self::EMPTY; SlotRange::SLOT_RANGE_COUNT]); - // This `remove_all` statement should remove at most `EndingPeriod` / - // `SampleLength` items, which should be bounded and sensibly configured in the - // runtime. - #[allow(deprecated)] - Winning::<T>::remove_all(None); - AuctionInfo::<T>::kill(); - return Some((res, lease_period_index)) - } - } - } - None - } - - /// Auction just ended. We have the current lease period, the auction's lease period (which - /// is guaranteed to be at least the current period) and the bidders that were winning each - /// range at the time of the auction's close. - fn manage_auction_end( - auction_lease_period_index: LeasePeriodOf<T>, - winning_ranges: WinningData<T>, - ) { - // First, unreserve all amounts that were reserved for the bids. We will later re-reserve - // the amounts from the bidders that ended up being assigned the slot so there's no need to - // special-case them here. - for ((bidder, _), amount) in ReservedAmounts::<T>::drain() { - CurrencyOf::<T>::unreserve(&bidder, amount); - } - - // Next, calculate the winning combination of slots and thus the final winners of the - // auction. - let winners = Self::calculate_winners(winning_ranges); - - // Go through those winners and re-reserve their bid, updating our table of deposits - // accordingly. - for (leaser, para, amount, range) in winners.into_iter() { - let begin_offset = LeasePeriodOf::<T>::from(range.as_pair().0 as u32); - let period_begin = auction_lease_period_index + begin_offset; - let period_count = LeasePeriodOf::<T>::from(range.len() as u32); - - match T::Leaser::lease_out(para, &leaser, amount, period_begin, period_count) { - Err(LeaseError::ReserveFailed) | - Err(LeaseError::AlreadyEnded) | - Err(LeaseError::NoLeasePeriod) => { - // Should never happen since we just unreserved this amount (and our offset is - // from the present period). But if it does, there's not much we can do. - }, - Err(LeaseError::AlreadyLeased) => { - // The leaser attempted to get a second lease on the same para ID, possibly - // griefing us. Let's keep the amount reserved and let governance sort it out. - if CurrencyOf::<T>::reserve(&leaser, amount).is_ok() { - Self::deposit_event(Event::<T>::ReserveConfiscated { - para_id: para, - leaser, - amount, - }); - } - }, - Ok(()) => {}, // Nothing to report. - } - } - - Self::deposit_event(Event::<T>::AuctionClosed { - auction_index: AuctionCounter::<T>::get(), - }); - } - - /// Calculate the final winners from the winning slots. - /// - /// This is a simple dynamic programming algorithm designed by Al, the original code is at: - /// `https://github.com/w3f/consensus/blob/master/NPoS/auctiondynamicthing.py` - fn calculate_winners(mut winning: WinningData<T>) -> WinnersData<T> { - let winning_ranges = { - let mut best_winners_ending_at: [(Vec<SlotRange>, BalanceOf<T>); - SlotRange::LEASE_PERIODS_PER_SLOT] = Default::default(); - let best_bid = |range: SlotRange| { - winning[range as u8 as usize] - .as_ref() - .map(|(_, _, amount)| *amount * (range.len() as u32).into()) - }; - for i in 0..SlotRange::LEASE_PERIODS_PER_SLOT { - let r = SlotRange::new_bounded(0, 0, i as u32).expect("`i < LPPS`; qed"); - if let Some(bid) = best_bid(r) { - best_winners_ending_at[i] = (vec![r], bid); - } - for j in 0..i { - let r = SlotRange::new_bounded(0, j as u32 + 1, i as u32) - .expect("`i < LPPS`; `j < i`; `j + 1 < LPPS`; qed"); - if let Some(mut bid) = best_bid(r) { - bid += best_winners_ending_at[j].1; - if bid > best_winners_ending_at[i].1 { - let mut new_winners = best_winners_ending_at[j].0.clone(); - new_winners.push(r); - best_winners_ending_at[i] = (new_winners, bid); - } - } else { - if best_winners_ending_at[j].1 > best_winners_ending_at[i].1 { - best_winners_ending_at[i] = best_winners_ending_at[j].clone(); - } - } - } - } - best_winners_ending_at[SlotRange::LEASE_PERIODS_PER_SLOT - 1].0.clone() - }; - - winning_ranges - .into_iter() - .filter_map(|range| { - winning[range as u8 as usize] - .take() - .map(|(bidder, para, amount)| (bidder, para, amount, range)) - }) - .collect::<Vec<_>>() - } -} - -/// tests for this module -#[cfg(test)] -mod tests { - use super::*; - use crate::{auctions, mock::TestRegistrar}; - use frame_support::{ - assert_noop, assert_ok, assert_storage_noop, derive_impl, ord_parameter_types, - parameter_types, - traits::{EitherOfDiverse, OnFinalize, OnInitialize}, - }; - use frame_system::{EnsureRoot, EnsureSignedBy}; - use pallet_balances; - use polkadot_primitives::{BlockNumber, Id as ParaId}; - use polkadot_primitives_test_helpers::{dummy_hash, dummy_head_data, dummy_validation_code}; - use sp_core::H256; - use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, - DispatchError::BadOrigin, - }; - use std::{cell::RefCell, collections::BTreeMap}; - - type Block = frame_system::mocking::MockBlockU32<Test>; - - frame_support::construct_runtime!( - pub enum Test - { - System: frame_system, - Balances: pallet_balances, - Auctions: auctions, - } - ); - - #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] - impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup<Self::AccountId>; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData<u64>; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; - } - - #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] - impl pallet_balances::Config for Test { - type AccountStore = System; - } - - #[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug)] - pub struct LeaseData { - leaser: u64, - amount: u64, - } - - thread_local! { - pub static LEASES: - RefCell<BTreeMap<(ParaId, BlockNumber), LeaseData>> = RefCell::new(BTreeMap::new()); - } - - fn leases() -> Vec<((ParaId, BlockNumber), LeaseData)> { - LEASES.with(|p| (&*p.borrow()).clone().into_iter().collect::<Vec<_>>()) - } - - pub struct TestLeaser; - impl Leaser<BlockNumber> for TestLeaser { - type AccountId = u64; - type LeasePeriod = BlockNumber; - type Currency = Balances; - - fn lease_out( - para: ParaId, - leaser: &Self::AccountId, - amount: <Self::Currency as Currency<Self::AccountId>>::Balance, - period_begin: Self::LeasePeriod, - period_count: Self::LeasePeriod, - ) -> Result<(), LeaseError> { - LEASES.with(|l| { - let mut leases = l.borrow_mut(); - let now = System::block_number(); - let (current_lease_period, _) = - Self::lease_period_index(now).ok_or(LeaseError::NoLeasePeriod)?; - if period_begin < current_lease_period { - return Err(LeaseError::AlreadyEnded) - } - for period in period_begin..(period_begin + period_count) { - if leases.contains_key(&(para, period)) { - return Err(LeaseError::AlreadyLeased) - } - leases.insert((para, period), LeaseData { leaser: *leaser, amount }); - } - Ok(()) - }) - } - - fn deposit_held( - para: ParaId, - leaser: &Self::AccountId, - ) -> <Self::Currency as Currency<Self::AccountId>>::Balance { - leases() - .iter() - .filter_map(|((id, _period), data)| { - if id == ¶ && &data.leaser == leaser { - Some(data.amount) - } else { - None - } - }) - .max() - .unwrap_or_default() - } - - fn lease_period_length() -> (BlockNumber, BlockNumber) { - (10, 0) - } - - fn lease_period_index(b: BlockNumber) -> Option<(Self::LeasePeriod, bool)> { - let (lease_period_length, offset) = Self::lease_period_length(); - let b = b.checked_sub(offset)?; - - let lease_period = b / lease_period_length; - let first_block = (b % lease_period_length).is_zero(); - - Some((lease_period, first_block)) - } - - fn already_leased( - para_id: ParaId, - first_period: Self::LeasePeriod, - last_period: Self::LeasePeriod, - ) -> bool { - leases().into_iter().any(|((para, period), _data)| { - para == para_id && first_period <= period && period <= last_period - }) - } - } - - ord_parameter_types! { - pub const Six: u64 = 6; - } - - type RootOrSix = EitherOfDiverse<EnsureRoot<u64>, EnsureSignedBy<Six, u64>>; - - thread_local! { - pub static LAST_RANDOM: RefCell<Option<(H256, u32)>> = RefCell::new(None); - } - fn set_last_random(output: H256, known_since: u32) { - LAST_RANDOM.with(|p| *p.borrow_mut() = Some((output, known_since))) - } - pub struct TestPastRandomness; - impl Randomness<H256, BlockNumber> for TestPastRandomness { - fn random(_subject: &[u8]) -> (H256, u32) { - LAST_RANDOM.with(|p| { - if let Some((output, known_since)) = &*p.borrow() { - (*output, *known_since) - } else { - (H256::zero(), frame_system::Pallet::<Test>::block_number()) - } - }) - } - } - - parameter_types! { - pub static EndingPeriod: BlockNumber = 3; - pub static SampleLength: BlockNumber = 1; - } - - impl Config for Test { - type RuntimeEvent = RuntimeEvent; - type Leaser = TestLeaser; - type Registrar = TestRegistrar<Self>; - type EndingPeriod = EndingPeriod; - type SampleLength = SampleLength; - type Randomness = TestPastRandomness; - type InitiateOrigin = RootOrSix; - type WeightInfo = crate::auctions::TestWeightInfo; - } - - // This function basically just builds a genesis storage key/value store according to - // our desired mock up. - pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); - pallet_balances::GenesisConfig::<Test> { - balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], - } - .assimilate_storage(&mut t) - .unwrap(); - let mut ext: sp_io::TestExternalities = t.into(); - ext.execute_with(|| { - // Register para 0, 1, 2, and 3 for tests - assert_ok!(TestRegistrar::<Test>::register( - 1, - 0.into(), - dummy_head_data(), - dummy_validation_code() - )); - assert_ok!(TestRegistrar::<Test>::register( - 1, - 1.into(), - dummy_head_data(), - dummy_validation_code() - )); - assert_ok!(TestRegistrar::<Test>::register( - 1, - 2.into(), - dummy_head_data(), - dummy_validation_code() - )); - assert_ok!(TestRegistrar::<Test>::register( - 1, - 3.into(), - dummy_head_data(), - dummy_validation_code() - )); - }); - ext - } - - fn run_to_block(n: BlockNumber) { - while System::block_number() < n { - Auctions::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); - Auctions::on_initialize(System::block_number()); - } - } - - #[test] - fn basic_setup_works() { - new_test_ext().execute_with(|| { - assert_eq!(AuctionCounter::<Test>::get(), 0); - assert_eq!(TestLeaser::deposit_held(0u32.into(), &1), 0); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::NotStarted - ); - - run_to_block(10); - - assert_eq!(AuctionCounter::<Test>::get(), 0); - assert_eq!(TestLeaser::deposit_held(0u32.into(), &1), 0); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::NotStarted - ); - }); - } - - #[test] - fn can_start_auction() { - new_test_ext().execute_with(|| { - run_to_block(1); - - assert_noop!(Auctions::new_auction(RuntimeOrigin::signed(1), 5, 1), BadOrigin); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - - assert_eq!(AuctionCounter::<Test>::get(), 1); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::StartingPeriod - ); - }); - } - - #[test] - fn bidding_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); - - assert_eq!(Balances::reserved_balance(1), 5); - assert_eq!(Balances::free_balance(1), 5); - assert_eq!( - Winning::<Test>::get(0).unwrap()[SlotRange::ZeroThree as u8 as usize], - Some((1, 0.into(), 5)) - ); - }); - } - - #[test] - fn under_bidding_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); - - assert_storage_noop!({ - assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 1, 4, 1)); - }); - }); - } - - #[test] - fn over_bidding_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 1, 4, 6)); - - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(Balances::free_balance(1), 10); - assert_eq!(Balances::reserved_balance(2), 6); - assert_eq!(Balances::free_balance(2), 14); - assert_eq!( - Winning::<Test>::get(0).unwrap()[SlotRange::ZeroThree as u8 as usize], - Some((2, 0.into(), 6)) - ); - }); - } - - #[test] - fn auction_proceeds_correctly() { - new_test_ext().execute_with(|| { - run_to_block(1); - - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - - assert_eq!(AuctionCounter::<Test>::get(), 1); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::StartingPeriod - ); - - run_to_block(2); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::StartingPeriod - ); - - run_to_block(3); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::StartingPeriod - ); - - run_to_block(4); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::StartingPeriod - ); - - run_to_block(5); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::StartingPeriod - ); - - run_to_block(6); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::EndingPeriod(0, 0) - ); - - run_to_block(7); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::EndingPeriod(1, 0) - ); - - run_to_block(8); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::EndingPeriod(2, 0) - ); - - run_to_block(9); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::NotStarted - ); - }); - } - - #[test] - fn can_win_auction() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); - assert_eq!(Balances::reserved_balance(1), 1); - assert_eq!(Balances::free_balance(1), 9); - run_to_block(9); - - assert_eq!( - leases(), - vec![ - ((0.into(), 1), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 2), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 3), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 4), LeaseData { leaser: 1, amount: 1 }), - ] - ); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1); - }); - } - - #[test] - fn can_win_auction_with_late_randomness() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); - assert_eq!(Balances::reserved_balance(1), 1); - assert_eq!(Balances::free_balance(1), 9); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::StartingPeriod - ); - run_to_block(8); - // Auction has not yet ended. - assert_eq!(leases(), vec![]); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::EndingPeriod(2, 0) - ); - // This will prevent the auction's winner from being decided in the next block, since - // the random seed was known before the final bids were made. - set_last_random(H256::zero(), 8); - // Auction definitely ended now, but we don't know exactly when in the last 3 blocks yet - // since no randomness available yet. - run_to_block(9); - // Auction has now ended... But auction winner still not yet decided, so no leases yet. - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::VrfDelay(0) - ); - assert_eq!(leases(), vec![]); - - // Random seed now updated to a value known at block 9, when the auction ended. This - // means that the winner can now be chosen. - set_last_random(H256::zero(), 9); - run_to_block(10); - // Auction ended and winner selected - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::NotStarted - ); - assert_eq!( - leases(), - vec![ - ((0.into(), 1), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 2), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 3), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 4), LeaseData { leaser: 1, amount: 1 }), - ] - ); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1); - }); - } - - #[test] - fn can_win_incomplete_auction() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 4, 4, 5)); - run_to_block(9); - - assert_eq!(leases(), vec![((0.into(), 4), LeaseData { leaser: 1, amount: 5 }),]); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); - }); - } - - #[test] - fn should_choose_best_combination() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 2, 3, 4)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), 0.into(), 1, 4, 4, 2)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1.into(), 1, 1, 4, 2)); - run_to_block(9); - - assert_eq!( - leases(), - vec![ - ((0.into(), 1), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 2), LeaseData { leaser: 2, amount: 4 }), - ((0.into(), 3), LeaseData { leaser: 2, amount: 4 }), - ((0.into(), 4), LeaseData { leaser: 3, amount: 2 }), - ] - ); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1); - assert_eq!(TestLeaser::deposit_held(1.into(), &1), 0); - assert_eq!(TestLeaser::deposit_held(0.into(), &2), 4); - assert_eq!(TestLeaser::deposit_held(0.into(), &3), 2); - }); - } - - #[test] - fn gap_bid_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - - // User 1 will make a bid for period 1 and 4 for the same Para 0 - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 4, 4, 4)); - - // User 2 and 3 will make a bid for para 1 on period 2 and 3 respectively - assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 1.into(), 1, 2, 2, 2)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), 1.into(), 1, 3, 3, 3)); - - // Total reserved should be the max of the two - assert_eq!(Balances::reserved_balance(1), 4); - - // Other people are reserved correctly too - assert_eq!(Balances::reserved_balance(2), 2); - assert_eq!(Balances::reserved_balance(3), 3); - - // End the auction. - run_to_block(9); - - assert_eq!( - leases(), - vec![ - ((0.into(), 1), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 4), LeaseData { leaser: 1, amount: 4 }), - ((1.into(), 2), LeaseData { leaser: 2, amount: 2 }), - ((1.into(), 3), LeaseData { leaser: 3, amount: 3 }), - ] - ); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 4); - assert_eq!(TestLeaser::deposit_held(1.into(), &2), 2); - assert_eq!(TestLeaser::deposit_held(1.into(), &3), 3); - }); - } - - #[test] - fn deposit_credit_should_work() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 5)); - assert_eq!(Balances::reserved_balance(1), 5); - run_to_block(10); - - assert_eq!(leases(), vec![((0.into(), 1), LeaseData { leaser: 1, amount: 5 }),]); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); - - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 2)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 2, 2, 6)); - // Only 1 reserved since we have a deposit credit of 5. - assert_eq!(Balances::reserved_balance(1), 1); - run_to_block(20); - - assert_eq!( - leases(), - vec![ - ((0.into(), 1), LeaseData { leaser: 1, amount: 5 }), - ((0.into(), 2), LeaseData { leaser: 1, amount: 6 }), - ] - ); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 6); - }); - } - - #[test] - fn deposit_credit_on_alt_para_should_not_count() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 5)); - assert_eq!(Balances::reserved_balance(1), 5); - run_to_block(10); - - assert_eq!(leases(), vec![((0.into(), 1), LeaseData { leaser: 1, amount: 5 }),]); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); - - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 2)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1.into(), 2, 2, 2, 6)); - // 6 reserved since we are bidding on a new para; only works because we don't - assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(20); - - assert_eq!( - leases(), - vec![ - ((0.into(), 1), LeaseData { leaser: 1, amount: 5 }), - ((1.into(), 2), LeaseData { leaser: 1, amount: 6 }), - ] - ); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); - assert_eq!(TestLeaser::deposit_held(1.into(), &1), 6); - }); - } - - #[test] - fn multiple_bids_work_pre_ending() { - new_test_ext().execute_with(|| { - run_to_block(1); - - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - - for i in 1..6u64 { - run_to_block(i as _); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(i), 0.into(), 1, 1, 4, i)); - for j in 1..6 { - assert_eq!(Balances::reserved_balance(j), if j == i { j } else { 0 }); - assert_eq!(Balances::free_balance(j), if j == i { j * 9 } else { j * 10 }); - } - } - - run_to_block(9); - assert_eq!( - leases(), - vec![ - ((0.into(), 1), LeaseData { leaser: 5, amount: 5 }), - ((0.into(), 2), LeaseData { leaser: 5, amount: 5 }), - ((0.into(), 3), LeaseData { leaser: 5, amount: 5 }), - ((0.into(), 4), LeaseData { leaser: 5, amount: 5 }), - ] - ); - }); - } - - #[test] - fn multiple_bids_work_post_ending() { - new_test_ext().execute_with(|| { - run_to_block(1); - - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 0, 1)); - - for i in 1..6u64 { - run_to_block(((i - 1) / 2 + 1) as _); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(i), 0.into(), 1, 1, 4, i)); - for j in 1..6 { - assert_eq!(Balances::reserved_balance(j), if j <= i { j } else { 0 }); - assert_eq!(Balances::free_balance(j), if j <= i { j * 9 } else { j * 10 }); - } - } - for i in 1..6u64 { - assert_eq!(ReservedAmounts::<Test>::get((i, ParaId::from(0))).unwrap(), i); - } - - run_to_block(5); - assert_eq!( - leases(), - (1..=4) - .map(|i| ((0.into(), i), LeaseData { leaser: 2, amount: 2 })) - .collect::<Vec<_>>() - ); - }); - } - - #[test] - fn incomplete_calculate_winners_works() { - let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; - winning[SlotRange::ThreeThree as u8 as usize] = Some((1, 0.into(), 1)); - - let winners = vec![(1, 0.into(), 1, SlotRange::ThreeThree)]; - - assert_eq!(Auctions::calculate_winners(winning), winners); - } - - #[test] - fn first_incomplete_calculate_winners_works() { - let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; - winning[0] = Some((1, 0.into(), 1)); - - let winners = vec![(1, 0.into(), 1, SlotRange::ZeroZero)]; - - assert_eq!(Auctions::calculate_winners(winning), winners); - } - - #[test] - fn calculate_winners_works() { - let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; - winning[SlotRange::ZeroZero as u8 as usize] = Some((2, 0.into(), 2)); - winning[SlotRange::ZeroThree as u8 as usize] = Some((1, 100.into(), 1)); - winning[SlotRange::OneOne as u8 as usize] = Some((3, 1.into(), 1)); - winning[SlotRange::TwoTwo as u8 as usize] = Some((1, 2.into(), 53)); - winning[SlotRange::ThreeThree as u8 as usize] = Some((5, 3.into(), 1)); - - let winners = vec![ - (2, 0.into(), 2, SlotRange::ZeroZero), - (3, 1.into(), 1, SlotRange::OneOne), - (1, 2.into(), 53, SlotRange::TwoTwo), - (5, 3.into(), 1, SlotRange::ThreeThree), - ]; - assert_eq!(Auctions::calculate_winners(winning), winners); - - winning[SlotRange::ZeroOne as u8 as usize] = Some((4, 10.into(), 3)); - let winners = vec![ - (4, 10.into(), 3, SlotRange::ZeroOne), - (1, 2.into(), 53, SlotRange::TwoTwo), - (5, 3.into(), 1, SlotRange::ThreeThree), - ]; - assert_eq!(Auctions::calculate_winners(winning), winners); - - winning[SlotRange::ZeroThree as u8 as usize] = Some((1, 100.into(), 100)); - let winners = vec![(1, 100.into(), 100, SlotRange::ZeroThree)]; - assert_eq!(Auctions::calculate_winners(winning), winners); - } - - #[test] - fn lower_bids_are_correctly_refunded() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 1, 1)); - let para_1 = ParaId::from(1_u32); - let para_2 = ParaId::from(2_u32); - - // Make a bid and reserve a balance - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), para_1, 1, 1, 4, 9)); - assert_eq!(Balances::reserved_balance(1), 9); - assert_eq!(ReservedAmounts::<Test>::get((1, para_1)), Some(9)); - assert_eq!(Balances::reserved_balance(2), 0); - assert_eq!(ReservedAmounts::<Test>::get((2, para_2)), None); - - // Bigger bid, reserves new balance and returns funds - assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), para_2, 1, 1, 4, 19)); - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(ReservedAmounts::<Test>::get((1, para_1)), None); - assert_eq!(Balances::reserved_balance(2), 19); - assert_eq!(ReservedAmounts::<Test>::get((2, para_2)), Some(19)); - }); - } - - #[test] - fn initialize_winners_in_ending_period_works() { - new_test_ext().execute_with(|| { - let ed: u64 = <Test as pallet_balances::Config>::ExistentialDeposit::get(); - assert_eq!(ed, 1); - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 1)); - let para_1 = ParaId::from(1_u32); - let para_2 = ParaId::from(2_u32); - let para_3 = ParaId::from(3_u32); - - // Make bids - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), para_1, 1, 1, 4, 9)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), para_2, 1, 3, 4, 19)); - - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::StartingPeriod - ); - let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; - winning[SlotRange::ZeroThree as u8 as usize] = Some((1, para_1, 9)); - winning[SlotRange::TwoThree as u8 as usize] = Some((2, para_2, 19)); - assert_eq!(Winning::<Test>::get(0), Some(winning)); - - run_to_block(9); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::StartingPeriod - ); - - run_to_block(10); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::EndingPeriod(0, 0) - ); - assert_eq!(Winning::<Test>::get(0), Some(winning)); - - run_to_block(11); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::EndingPeriod(1, 0) - ); - assert_eq!(Winning::<Test>::get(1), Some(winning)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 3, 4, 29)); - - run_to_block(12); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::EndingPeriod(2, 0) - ); - winning[SlotRange::TwoThree as u8 as usize] = Some((3, para_3, 29)); - assert_eq!(Winning::<Test>::get(2), Some(winning)); - }); - } - - #[test] - fn handle_bid_requires_registered_para() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_noop!( - Auctions::bid(RuntimeOrigin::signed(1), 1337.into(), 1, 1, 4, 1), - Error::<Test>::ParaNotRegistered - ); - assert_ok!(TestRegistrar::<Test>::register( - 1, - 1337.into(), - dummy_head_data(), - dummy_validation_code() - )); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1337.into(), 1, 1, 4, 1)); - }); - } - - #[test] - fn handle_bid_checks_existing_lease_periods() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 2, 3, 1)); - assert_eq!(Balances::reserved_balance(1), 1); - assert_eq!(Balances::free_balance(1), 9); - run_to_block(9); - - assert_eq!( - leases(), - vec![ - ((0.into(), 2), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 3), LeaseData { leaser: 1, amount: 1 }), - ] - ); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1); - - // Para 1 just won an auction above and won some lease periods. - // No bids can work which overlap these periods. - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_noop!( - Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 4, 1), - Error::<Test>::AlreadyLeasedOut, - ); - assert_noop!( - Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 2, 1), - Error::<Test>::AlreadyLeasedOut, - ); - assert_noop!( - Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 3, 4, 1), - Error::<Test>::AlreadyLeasedOut, - ); - // This is okay, not an overlapping bid. - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 1, 1)); - }); - } - - // Here we will test that taking only 10 samples during the ending period works as expected. - #[test] - fn less_winning_samples_work() { - new_test_ext().execute_with(|| { - let ed: u64 = <Test as pallet_balances::Config>::ExistentialDeposit::get(); - assert_eq!(ed, 1); - EndingPeriod::set(30); - SampleLength::set(10); - - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 11)); - let para_1 = ParaId::from(1_u32); - let para_2 = ParaId::from(2_u32); - let para_3 = ParaId::from(3_u32); - - // Make bids - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), para_1, 1, 11, 14, 9)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), para_2, 1, 13, 14, 19)); - - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::StartingPeriod - ); - let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; - winning[SlotRange::ZeroThree as u8 as usize] = Some((1, para_1, 9)); - winning[SlotRange::TwoThree as u8 as usize] = Some((2, para_2, 19)); - assert_eq!(Winning::<Test>::get(0), Some(winning)); - - run_to_block(9); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::StartingPeriod - ); - - run_to_block(10); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::EndingPeriod(0, 0) - ); - assert_eq!(Winning::<Test>::get(0), Some(winning)); - - // New bids update the current winning - assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 14, 14, 29)); - winning[SlotRange::ThreeThree as u8 as usize] = Some((3, para_3, 29)); - assert_eq!(Winning::<Test>::get(0), Some(winning)); - - run_to_block(20); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::EndingPeriod(1, 0) - ); - assert_eq!(Winning::<Test>::get(1), Some(winning)); - run_to_block(25); - // Overbid mid sample - assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 13, 14, 29)); - winning[SlotRange::TwoThree as u8 as usize] = Some((3, para_3, 29)); - assert_eq!(Winning::<Test>::get(1), Some(winning)); - - run_to_block(30); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::EndingPeriod(2, 0) - ); - assert_eq!(Winning::<Test>::get(2), Some(winning)); - - set_last_random(H256::from([254; 32]), 40); - run_to_block(40); - // Auction ended and winner selected - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::NotStarted - ); - assert_eq!( - leases(), - vec![ - ((3.into(), 13), LeaseData { leaser: 3, amount: 29 }), - ((3.into(), 14), LeaseData { leaser: 3, amount: 29 }), - ] - ); - }); - } - - #[test] - fn auction_status_works() { - new_test_ext().execute_with(|| { - EndingPeriod::set(30); - SampleLength::set(10); - set_last_random(dummy_hash(), 0); - - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::NotStarted - ); - - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 11)); - - run_to_block(9); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::StartingPeriod - ); - - run_to_block(10); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::EndingPeriod(0, 0) - ); - - run_to_block(11); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::EndingPeriod(0, 1) - ); - - run_to_block(19); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::EndingPeriod(0, 9) - ); - - run_to_block(20); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::EndingPeriod(1, 0) - ); - - run_to_block(25); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::EndingPeriod(1, 5) - ); - - run_to_block(30); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::EndingPeriod(2, 0) - ); - - run_to_block(39); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::EndingPeriod(2, 9) - ); - - run_to_block(40); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::VrfDelay(0) - ); - - run_to_block(44); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::VrfDelay(4) - ); - - set_last_random(dummy_hash(), 45); - run_to_block(45); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::<u32>::NotStarted - ); - }); - } - - #[test] - fn can_cancel_auction() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); - assert_eq!(Balances::reserved_balance(1), 1); - assert_eq!(Balances::free_balance(1), 9); - - assert_noop!(Auctions::cancel_auction(RuntimeOrigin::signed(6)), BadOrigin); - assert_ok!(Auctions::cancel_auction(RuntimeOrigin::root())); - - assert!(AuctionInfo::<Test>::get().is_none()); - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(ReservedAmounts::<Test>::iter().count(), 0); - assert_eq!(Winning::<Test>::iter().count(), 0); - }); - } -} - -#[cfg(feature = "runtime-benchmarks")] -mod benchmarking { - use super::{Pallet as Auctions, *}; - use frame_support::{ - assert_ok, - traits::{EnsureOrigin, OnInitialize}, - }; - use frame_system::RawOrigin; - use polkadot_runtime_parachains::paras; - use sp_runtime::{traits::Bounded, SaturatedConversion}; - - use frame_benchmarking::{account, benchmarks, whitelisted_caller, BenchmarkError}; - - fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) { - let events = frame_system::Pallet::<T>::events(); - let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into(); - // compare to the last event record - let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; - assert_eq!(event, &system_event); - } - - fn fill_winners<T: Config + paras::Config>(lease_period_index: LeasePeriodOf<T>) { - let auction_index = AuctionCounter::<T>::get(); - let minimum_balance = CurrencyOf::<T>::minimum_balance(); - - for n in 1..=SlotRange::SLOT_RANGE_COUNT as u32 { - let owner = account("owner", n, 0); - let worst_validation_code = T::Registrar::worst_validation_code(); - let worst_head_data = T::Registrar::worst_head_data(); - CurrencyOf::<T>::make_free_balance_be(&owner, BalanceOf::<T>::max_value()); - - assert!(T::Registrar::register( - owner, - ParaId::from(n), - worst_head_data, - worst_validation_code - ) - .is_ok()); - } - assert_ok!(paras::Pallet::<T>::add_trusted_validation_code( - frame_system::Origin::<T>::Root.into(), - T::Registrar::worst_validation_code(), - )); - - T::Registrar::execute_pending_transitions(); - - for n in 1..=SlotRange::SLOT_RANGE_COUNT as u32 { - let bidder = account("bidder", n, 0); - CurrencyOf::<T>::make_free_balance_be(&bidder, BalanceOf::<T>::max_value()); - - let slot_range = SlotRange::n((n - 1) as u8).unwrap(); - let (start, end) = slot_range.as_pair(); - - assert!(Auctions::<T>::bid( - RawOrigin::Signed(bidder).into(), - ParaId::from(n), - auction_index, - lease_period_index + start.into(), // First Slot - lease_period_index + end.into(), // Last slot - minimum_balance.saturating_mul(n.into()), // Amount - ) - .is_ok()); - } - } - - benchmarks! { - where_clause { where T: pallet_babe::Config + paras::Config } - - new_auction { - let duration = BlockNumberFor::<T>::max_value(); - let lease_period_index = LeasePeriodOf::<T>::max_value(); - let origin = - T::InitiateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - }: _<T::RuntimeOrigin>(origin, duration, lease_period_index) - verify { - assert_last_event::<T>(Event::<T>::AuctionStarted { - auction_index: AuctionCounter::<T>::get(), - lease_period: LeasePeriodOf::<T>::max_value(), - ending: BlockNumberFor::<T>::max_value(), - }.into()); - } - - // Worst case scenario a new bid comes in which kicks out an existing bid for the same slot. - bid { - // If there is an offset, we need to be on that block to be able to do lease things. - let (_, offset) = T::Leaser::lease_period_length(); - frame_system::Pallet::<T>::set_block_number(offset + One::one()); - - // Create a new auction - let duration = BlockNumberFor::<T>::max_value(); - let lease_period_index = LeasePeriodOf::<T>::zero(); - let origin = T::InitiateOrigin::try_successful_origin() - .expect("InitiateOrigin has no successful origin required for the benchmark"); - Auctions::<T>::new_auction(origin, duration, lease_period_index)?; - - let para = ParaId::from(0); - let new_para = ParaId::from(1_u32); - - // Register the paras - let owner = account("owner", 0, 0); - CurrencyOf::<T>::make_free_balance_be(&owner, BalanceOf::<T>::max_value()); - let worst_head_data = T::Registrar::worst_head_data(); - let worst_validation_code = T::Registrar::worst_validation_code(); - T::Registrar::register(owner.clone(), para, worst_head_data.clone(), worst_validation_code.clone())?; - T::Registrar::register(owner, new_para, worst_head_data, worst_validation_code.clone())?; - assert_ok!(paras::Pallet::<T>::add_trusted_validation_code( - frame_system::Origin::<T>::Root.into(), - worst_validation_code, - )); - - T::Registrar::execute_pending_transitions(); - - // Make an existing bid - let auction_index = AuctionCounter::<T>::get(); - let first_slot = AuctionInfo::<T>::get().unwrap().0; - let last_slot = first_slot + 3u32.into(); - let first_amount = CurrencyOf::<T>::minimum_balance(); - let first_bidder: T::AccountId = account("first_bidder", 0, 0); - CurrencyOf::<T>::make_free_balance_be(&first_bidder, BalanceOf::<T>::max_value()); - Auctions::<T>::bid( - RawOrigin::Signed(first_bidder.clone()).into(), - para, - auction_index, - first_slot, - last_slot, - first_amount, - )?; - - let caller: T::AccountId = whitelisted_caller(); - CurrencyOf::<T>::make_free_balance_be(&caller, BalanceOf::<T>::max_value()); - let bigger_amount = CurrencyOf::<T>::minimum_balance().saturating_mul(10u32.into()); - assert_eq!(CurrencyOf::<T>::reserved_balance(&first_bidder), first_amount); - }: _(RawOrigin::Signed(caller.clone()), new_para, auction_index, first_slot, last_slot, bigger_amount) - verify { - // Confirms that we unreserved funds from a previous bidder, which is worst case scenario. - assert_eq!(CurrencyOf::<T>::reserved_balance(&caller), bigger_amount); - } - - // Worst case: 10 bidders taking all wining spots, and we need to calculate the winner for auction end. - // Entire winner map should be full and removed at the end of the benchmark. - on_initialize { - // If there is an offset, we need to be on that block to be able to do lease things. - let (lease_length, offset) = T::Leaser::lease_period_length(); - frame_system::Pallet::<T>::set_block_number(offset + One::one()); - - // Create a new auction - let duration: BlockNumberFor<T> = lease_length / 2u32.into(); - let lease_period_index = LeasePeriodOf::<T>::zero(); - let now = frame_system::Pallet::<T>::block_number(); - let origin = T::InitiateOrigin::try_successful_origin() - .expect("InitiateOrigin has no successful origin required for the benchmark"); - Auctions::<T>::new_auction(origin, duration, lease_period_index)?; - - fill_winners::<T>(lease_period_index); - - for winner in Winning::<T>::get(BlockNumberFor::<T>::from(0u32)).unwrap().iter() { - assert!(winner.is_some()); - } - - let winning_data = Winning::<T>::get(BlockNumberFor::<T>::from(0u32)).unwrap(); - // Make winning map full - for i in 0u32 .. (T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() { - Winning::<T>::insert(BlockNumberFor::<T>::from(i), winning_data.clone()); - } - - // Move ahead to the block we want to initialize - frame_system::Pallet::<T>::set_block_number(duration + now + T::EndingPeriod::get()); - - // Trigger epoch change for new random number value: - { - pallet_babe::EpochStart::<T>::set((Zero::zero(), u32::MAX.into())); - pallet_babe::Pallet::<T>::on_initialize(duration + now + T::EndingPeriod::get()); - let authorities = pallet_babe::Pallet::<T>::authorities(); - // Check for non empty authority set since it otherwise emits a No-OP warning. - if !authorities.is_empty() { - pallet_babe::Pallet::<T>::enact_epoch_change(authorities.clone(), authorities, None); - } - } - - }: { - Auctions::<T>::on_initialize(duration + now + T::EndingPeriod::get()); - } verify { - let auction_index = AuctionCounter::<T>::get(); - assert_last_event::<T>(Event::<T>::AuctionClosed { auction_index }.into()); - assert!(Winning::<T>::iter().count().is_zero()); - } - - // Worst case: 10 bidders taking all wining spots, and winning data is full. - cancel_auction { - // If there is an offset, we need to be on that block to be able to do lease things. - let (lease_length, offset) = T::Leaser::lease_period_length(); - frame_system::Pallet::<T>::set_block_number(offset + One::one()); - - // Create a new auction - let duration: BlockNumberFor<T> = lease_length / 2u32.into(); - let lease_period_index = LeasePeriodOf::<T>::zero(); - let now = frame_system::Pallet::<T>::block_number(); - let origin = T::InitiateOrigin::try_successful_origin() - .expect("InitiateOrigin has no successful origin required for the benchmark"); - Auctions::<T>::new_auction(origin, duration, lease_period_index)?; - - fill_winners::<T>(lease_period_index); - - let winning_data = Winning::<T>::get(BlockNumberFor::<T>::from(0u32)).unwrap(); - for winner in winning_data.iter() { - assert!(winner.is_some()); - } - - // Make winning map full - for i in 0u32 .. (T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() { - Winning::<T>::insert(BlockNumberFor::<T>::from(i), winning_data.clone()); - } - assert!(AuctionInfo::<T>::get().is_some()); - }: _(RawOrigin::Root) - verify { - assert!(AuctionInfo::<T>::get().is_none()); - } - - impl_benchmark_test_suite!( - Auctions, - crate::integration_tests::new_test_ext(), - crate::integration_tests::Test, - ); - } -} diff --git a/polkadot/runtime/common/src/auctions/benchmarking.rs b/polkadot/runtime/common/src/auctions/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..6d52cd850b6ff963c0ece1aeb3670d76694eefb6 --- /dev/null +++ b/polkadot/runtime/common/src/auctions/benchmarking.rs @@ -0,0 +1,282 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Benchmarking for auctions pallet + +#![cfg(feature = "runtime-benchmarks")] +use super::{Pallet as Auctions, *}; +use frame_support::{ + assert_ok, + traits::{EnsureOrigin, OnInitialize}, +}; +use frame_system::RawOrigin; +use polkadot_runtime_parachains::paras; +use sp_runtime::{traits::Bounded, SaturatedConversion}; + +use frame_benchmarking::v2::*; + +fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) { + let events = frame_system::Pallet::<T>::events(); + let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +fn fill_winners<T: Config + paras::Config>(lease_period_index: LeasePeriodOf<T>) { + let auction_index = AuctionCounter::<T>::get(); + let minimum_balance = CurrencyOf::<T>::minimum_balance(); + + for n in 1..=SlotRange::SLOT_RANGE_COUNT as u32 { + let owner = account("owner", n, 0); + let worst_validation_code = T::Registrar::worst_validation_code(); + let worst_head_data = T::Registrar::worst_head_data(); + CurrencyOf::<T>::make_free_balance_be(&owner, BalanceOf::<T>::max_value()); + + assert!(T::Registrar::register( + owner, + ParaId::from(n), + worst_head_data, + worst_validation_code + ) + .is_ok()); + } + assert_ok!(paras::Pallet::<T>::add_trusted_validation_code( + frame_system::Origin::<T>::Root.into(), + T::Registrar::worst_validation_code(), + )); + + T::Registrar::execute_pending_transitions(); + + for n in 1..=SlotRange::SLOT_RANGE_COUNT as u32 { + let bidder = account("bidder", n, 0); + CurrencyOf::<T>::make_free_balance_be(&bidder, BalanceOf::<T>::max_value()); + + let slot_range = SlotRange::n((n - 1) as u8).unwrap(); + let (start, end) = slot_range.as_pair(); + + assert!(Auctions::<T>::bid( + RawOrigin::Signed(bidder).into(), + ParaId::from(n), + auction_index, + lease_period_index + start.into(), // First Slot + lease_period_index + end.into(), // Last slot + minimum_balance.saturating_mul(n.into()), // Amount + ) + .is_ok()); + } +} + +#[benchmarks( + where T: pallet_babe::Config + paras::Config, + )] +mod benchmarks { + use super::*; + + #[benchmark] + fn new_auction() -> Result<(), BenchmarkError> { + let duration = BlockNumberFor::<T>::max_value(); + let lease_period_index = LeasePeriodOf::<T>::max_value(); + let origin = + T::InitiateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, duration, lease_period_index); + + assert_last_event::<T>( + Event::<T>::AuctionStarted { + auction_index: AuctionCounter::<T>::get(), + lease_period: LeasePeriodOf::<T>::max_value(), + ending: BlockNumberFor::<T>::max_value(), + } + .into(), + ); + + Ok(()) + } + + // Worst case scenario a new bid comes in which kicks out an existing bid for the same slot. + #[benchmark] + fn bid() -> Result<(), BenchmarkError> { + // If there is an offset, we need to be on that block to be able to do lease things. + let (_, offset) = T::Leaser::lease_period_length(); + frame_system::Pallet::<T>::set_block_number(offset + One::one()); + + // Create a new auction + let duration = BlockNumberFor::<T>::max_value(); + let lease_period_index = LeasePeriodOf::<T>::zero(); + let origin = T::InitiateOrigin::try_successful_origin() + .expect("InitiateOrigin has no successful origin required for the benchmark"); + Auctions::<T>::new_auction(origin, duration, lease_period_index)?; + + let para = ParaId::from(0); + let new_para = ParaId::from(1_u32); + + // Register the paras + let owner = account("owner", 0, 0); + CurrencyOf::<T>::make_free_balance_be(&owner, BalanceOf::<T>::max_value()); + let worst_head_data = T::Registrar::worst_head_data(); + let worst_validation_code = T::Registrar::worst_validation_code(); + T::Registrar::register( + owner.clone(), + para, + worst_head_data.clone(), + worst_validation_code.clone(), + )?; + T::Registrar::register(owner, new_para, worst_head_data, worst_validation_code.clone())?; + assert_ok!(paras::Pallet::<T>::add_trusted_validation_code( + frame_system::Origin::<T>::Root.into(), + worst_validation_code, + )); + + T::Registrar::execute_pending_transitions(); + + // Make an existing bid + let auction_index = AuctionCounter::<T>::get(); + let first_slot = AuctionInfo::<T>::get().unwrap().0; + let last_slot = first_slot + 3u32.into(); + let first_amount = CurrencyOf::<T>::minimum_balance(); + let first_bidder: T::AccountId = account("first_bidder", 0, 0); + CurrencyOf::<T>::make_free_balance_be(&first_bidder, BalanceOf::<T>::max_value()); + Auctions::<T>::bid( + RawOrigin::Signed(first_bidder.clone()).into(), + para, + auction_index, + first_slot, + last_slot, + first_amount, + )?; + + let caller: T::AccountId = whitelisted_caller(); + CurrencyOf::<T>::make_free_balance_be(&caller, BalanceOf::<T>::max_value()); + let bigger_amount = CurrencyOf::<T>::minimum_balance().saturating_mul(10u32.into()); + assert_eq!(CurrencyOf::<T>::reserved_balance(&first_bidder), first_amount); + + #[extrinsic_call] + _( + RawOrigin::Signed(caller.clone()), + new_para, + auction_index, + first_slot, + last_slot, + bigger_amount, + ); + + // Confirms that we unreserved funds from a previous bidder, which is worst case + // scenario. + assert_eq!(CurrencyOf::<T>::reserved_balance(&caller), bigger_amount); + + Ok(()) + } + + // Worst case: 10 bidders taking all wining spots, and we need to calculate the winner for + // auction end. Entire winner map should be full and removed at the end of the benchmark. + #[benchmark] + fn on_initialize() -> Result<(), BenchmarkError> { + // If there is an offset, we need to be on that block to be able to do lease things. + let (lease_length, offset) = T::Leaser::lease_period_length(); + frame_system::Pallet::<T>::set_block_number(offset + One::one()); + + // Create a new auction + let duration: BlockNumberFor<T> = lease_length / 2u32.into(); + let lease_period_index = LeasePeriodOf::<T>::zero(); + let now = frame_system::Pallet::<T>::block_number(); + let origin = T::InitiateOrigin::try_successful_origin() + .expect("InitiateOrigin has no successful origin required for the benchmark"); + Auctions::<T>::new_auction(origin, duration, lease_period_index)?; + + fill_winners::<T>(lease_period_index); + + for winner in Winning::<T>::get(BlockNumberFor::<T>::from(0u32)).unwrap().iter() { + assert!(winner.is_some()); + } + + let winning_data = Winning::<T>::get(BlockNumberFor::<T>::from(0u32)).unwrap(); + // Make winning map full + for i in 0u32..(T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() { + Winning::<T>::insert(BlockNumberFor::<T>::from(i), winning_data.clone()); + } + + // Move ahead to the block we want to initialize + frame_system::Pallet::<T>::set_block_number(duration + now + T::EndingPeriod::get()); + + // Trigger epoch change for new random number value: + { + pallet_babe::EpochStart::<T>::set((Zero::zero(), u32::MAX.into())); + pallet_babe::Pallet::<T>::on_initialize(duration + now + T::EndingPeriod::get()); + let authorities = pallet_babe::Pallet::<T>::authorities(); + // Check for non empty authority set since it otherwise emits a No-OP warning. + if !authorities.is_empty() { + pallet_babe::Pallet::<T>::enact_epoch_change( + authorities.clone(), + authorities, + None, + ); + } + } + + #[block] + { + Auctions::<T>::on_initialize(duration + now + T::EndingPeriod::get()); + } + + let auction_index = AuctionCounter::<T>::get(); + assert_last_event::<T>(Event::<T>::AuctionClosed { auction_index }.into()); + assert!(Winning::<T>::iter().count().is_zero()); + + Ok(()) + } + + // Worst case: 10 bidders taking all wining spots, and winning data is full. + #[benchmark] + fn cancel_auction() -> Result<(), BenchmarkError> { + // If there is an offset, we need to be on that block to be able to do lease things. + let (lease_length, offset) = T::Leaser::lease_period_length(); + frame_system::Pallet::<T>::set_block_number(offset + One::one()); + + // Create a new auction + let duration: BlockNumberFor<T> = lease_length / 2u32.into(); + let lease_period_index = LeasePeriodOf::<T>::zero(); + let origin = T::InitiateOrigin::try_successful_origin() + .expect("InitiateOrigin has no successful origin required for the benchmark"); + Auctions::<T>::new_auction(origin, duration, lease_period_index)?; + + fill_winners::<T>(lease_period_index); + + let winning_data = Winning::<T>::get(BlockNumberFor::<T>::from(0u32)).unwrap(); + for winner in winning_data.iter() { + assert!(winner.is_some()); + } + + // Make winning map full + for i in 0u32..(T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() { + Winning::<T>::insert(BlockNumberFor::<T>::from(i), winning_data.clone()); + } + assert!(AuctionInfo::<T>::get().is_some()); + + #[extrinsic_call] + _(RawOrigin::Root); + + assert!(AuctionInfo::<T>::get().is_none()); + Ok(()) + } + + impl_benchmark_test_suite!( + Auctions, + crate::integration_tests::new_test_ext(), + crate::integration_tests::Test, + ); +} diff --git a/polkadot/runtime/common/src/auctions/mock.rs b/polkadot/runtime/common/src/auctions/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..191608f8c8783f5ea66fbb2d1b0399f23a956d0c --- /dev/null +++ b/polkadot/runtime/common/src/auctions/mock.rs @@ -0,0 +1,246 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Mocking utilities for testing in auctions pallet. + +#[cfg(test)] +use super::*; +use crate::{auctions, mock::TestRegistrar}; +use frame_support::{ + assert_ok, derive_impl, ord_parameter_types, parameter_types, traits::EitherOfDiverse, +}; +use frame_system::{EnsureRoot, EnsureSignedBy}; +use pallet_balances; +use polkadot_primitives::{BlockNumber, Id as ParaId}; +use polkadot_primitives_test_helpers::{dummy_head_data, dummy_validation_code}; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; +use std::{cell::RefCell, collections::BTreeMap}; + +type Block = frame_system::mocking::MockBlockU32<Test>; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Auctions: auctions, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup<Self::AccountId>; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData<u64>; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type AccountStore = System; +} + +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug)] +pub struct LeaseData { + pub leaser: u64, + pub amount: u64, +} + +thread_local! { + pub static LEASES: + RefCell<BTreeMap<(ParaId, BlockNumber), LeaseData>> = RefCell::new(BTreeMap::new()); +} + +pub fn leases() -> Vec<((ParaId, BlockNumber), LeaseData)> { + LEASES.with(|p| (&*p.borrow()).clone().into_iter().collect::<Vec<_>>()) +} + +pub struct TestLeaser; +impl Leaser<BlockNumber> for TestLeaser { + type AccountId = u64; + type LeasePeriod = BlockNumber; + type Currency = Balances; + + fn lease_out( + para: ParaId, + leaser: &Self::AccountId, + amount: <Self::Currency as Currency<Self::AccountId>>::Balance, + period_begin: Self::LeasePeriod, + period_count: Self::LeasePeriod, + ) -> Result<(), LeaseError> { + LEASES.with(|l| { + let mut leases = l.borrow_mut(); + let now = System::block_number(); + let (current_lease_period, _) = + Self::lease_period_index(now).ok_or(LeaseError::NoLeasePeriod)?; + if period_begin < current_lease_period { + return Err(LeaseError::AlreadyEnded); + } + for period in period_begin..(period_begin + period_count) { + if leases.contains_key(&(para, period)) { + return Err(LeaseError::AlreadyLeased); + } + leases.insert((para, period), LeaseData { leaser: *leaser, amount }); + } + Ok(()) + }) + } + + fn deposit_held( + para: ParaId, + leaser: &Self::AccountId, + ) -> <Self::Currency as Currency<Self::AccountId>>::Balance { + leases() + .iter() + .filter_map(|((id, _period), data)| { + if id == ¶ && &data.leaser == leaser { + Some(data.amount) + } else { + None + } + }) + .max() + .unwrap_or_default() + } + + fn lease_period_length() -> (BlockNumber, BlockNumber) { + (10, 0) + } + + fn lease_period_index(b: BlockNumber) -> Option<(Self::LeasePeriod, bool)> { + let (lease_period_length, offset) = Self::lease_period_length(); + let b = b.checked_sub(offset)?; + + let lease_period = b / lease_period_length; + let first_block = (b % lease_period_length).is_zero(); + + Some((lease_period, first_block)) + } + + fn already_leased( + para_id: ParaId, + first_period: Self::LeasePeriod, + last_period: Self::LeasePeriod, + ) -> bool { + leases().into_iter().any(|((para, period), _data)| { + para == para_id && first_period <= period && period <= last_period + }) + } +} + +ord_parameter_types! { + pub const Six: u64 = 6; +} + +type RootOrSix = EitherOfDiverse<EnsureRoot<u64>, EnsureSignedBy<Six, u64>>; + +thread_local! { + pub static LAST_RANDOM: RefCell<Option<(H256, u32)>> = RefCell::new(None); +} +pub fn set_last_random(output: H256, known_since: u32) { + LAST_RANDOM.with(|p| *p.borrow_mut() = Some((output, known_since))) +} +pub struct TestPastRandomness; +impl Randomness<H256, BlockNumber> for TestPastRandomness { + fn random(_subject: &[u8]) -> (H256, u32) { + LAST_RANDOM.with(|p| { + if let Some((output, known_since)) = &*p.borrow() { + (*output, *known_since) + } else { + (H256::zero(), frame_system::Pallet::<Test>::block_number()) + } + }) + } +} + +parameter_types! { + pub static EndingPeriod: BlockNumber = 3; + pub static SampleLength: BlockNumber = 1; +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type Leaser = TestLeaser; + type Registrar = TestRegistrar<Self>; + type EndingPeriod = EndingPeriod; + type SampleLength = SampleLength; + type Randomness = TestPastRandomness; + type InitiateOrigin = RootOrSix; + type WeightInfo = crate::auctions::TestWeightInfo; +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mock up. +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); + pallet_balances::GenesisConfig::<Test> { + balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); + let mut ext: sp_io::TestExternalities = t.into(); + ext.execute_with(|| { + // Register para 0, 1, 2, and 3 for tests + assert_ok!(TestRegistrar::<Test>::register( + 1, + 0.into(), + dummy_head_data(), + dummy_validation_code() + )); + assert_ok!(TestRegistrar::<Test>::register( + 1, + 1.into(), + dummy_head_data(), + dummy_validation_code() + )); + assert_ok!(TestRegistrar::<Test>::register( + 1, + 2.into(), + dummy_head_data(), + dummy_validation_code() + )); + assert_ok!(TestRegistrar::<Test>::register( + 1, + 3.into(), + dummy_head_data(), + dummy_validation_code() + )); + }); + ext +} diff --git a/polkadot/runtime/common/src/auctions/mod.rs b/polkadot/runtime/common/src/auctions/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..84d8a3846d40e079ca0bc85f01ef02b381a52011 --- /dev/null +++ b/polkadot/runtime/common/src/auctions/mod.rs @@ -0,0 +1,677 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Auctioning system to determine the set of Parachains in operation. This includes logic for the +//! auctioning mechanism and for reserving balance as part of the "payment". Unreserving the balance +//! happens elsewhere. + +use crate::{ + slot_range::SlotRange, + traits::{AuctionStatus, Auctioneer, LeaseError, Leaser, Registrar}, +}; +use alloc::{vec, vec::Vec}; +use codec::Decode; +use core::mem::swap; +use frame_support::{ + dispatch::DispatchResult, + ensure, + traits::{Currency, Get, Randomness, ReservableCurrency}, + weights::Weight, +}; +use frame_system::pallet_prelude::BlockNumberFor; +pub use pallet::*; +use polkadot_primitives::Id as ParaId; +use sp_runtime::traits::{CheckedSub, One, Saturating, Zero}; + +type CurrencyOf<T> = <<T as Config>::Leaser as Leaser<BlockNumberFor<T>>>::Currency; +type BalanceOf<T> = <<<T as Config>::Leaser as Leaser<BlockNumberFor<T>>>::Currency as Currency< + <T as frame_system::Config>::AccountId, +>>::Balance; + +pub trait WeightInfo { + fn new_auction() -> Weight; + fn bid() -> Weight; + fn cancel_auction() -> Weight; + fn on_initialize() -> Weight; +} + +pub struct TestWeightInfo; +impl WeightInfo for TestWeightInfo { + fn new_auction() -> Weight { + Weight::zero() + } + fn bid() -> Weight { + Weight::zero() + } + fn cancel_auction() -> Weight { + Weight::zero() + } + fn on_initialize() -> Weight { + Weight::zero() + } +} + +/// An auction index. We count auctions in this type. +pub type AuctionIndex = u32; + +type LeasePeriodOf<T> = <<T as Config>::Leaser as Leaser<BlockNumberFor<T>>>::LeasePeriod; + +// Winning data type. This encodes the top bidders of each range together with their bid. +type WinningData<T> = [Option<(<T as frame_system::Config>::AccountId, ParaId, BalanceOf<T>)>; + SlotRange::SLOT_RANGE_COUNT]; +// Winners data type. This encodes each of the final winners of a parachain auction, the parachain +// index assigned to them, their winning bid and the range that they won. +type WinnersData<T> = + Vec<(<T as frame_system::Config>::AccountId, ParaId, BalanceOf<T>, SlotRange)>; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{dispatch::DispatchClass, pallet_prelude::*, traits::EnsureOrigin}; + use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; + + #[pallet::pallet] + pub struct Pallet<T>(_); + + /// The module's configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; + + /// The type representing the leasing system. + type Leaser: Leaser< + BlockNumberFor<Self>, + AccountId = Self::AccountId, + LeasePeriod = BlockNumberFor<Self>, + >; + + /// The parachain registrar type. + type Registrar: Registrar<AccountId = Self::AccountId>; + + /// The number of blocks over which an auction may be retroactively ended. + #[pallet::constant] + type EndingPeriod: Get<BlockNumberFor<Self>>; + + /// The length of each sample to take during the ending period. + /// + /// `EndingPeriod` / `SampleLength` = Total # of Samples + #[pallet::constant] + type SampleLength: Get<BlockNumberFor<Self>>; + + /// Something that provides randomness in the runtime. + type Randomness: Randomness<Self::Hash, BlockNumberFor<Self>>; + + /// The origin which may initiate auctions. + type InitiateOrigin: EnsureOrigin<Self::RuntimeOrigin>; + + /// Weight Information for the Extrinsics in the Pallet + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event<T: Config> { + /// An auction started. Provides its index and the block number where it will begin to + /// close and the first lease period of the quadruplet that is auctioned. + AuctionStarted { + auction_index: AuctionIndex, + lease_period: LeasePeriodOf<T>, + ending: BlockNumberFor<T>, + }, + /// An auction ended. All funds become unreserved. + AuctionClosed { auction_index: AuctionIndex }, + /// Funds were reserved for a winning bid. First balance is the extra amount reserved. + /// Second is the total. + Reserved { bidder: T::AccountId, extra_reserved: BalanceOf<T>, total_amount: BalanceOf<T> }, + /// Funds were unreserved since bidder is no longer active. `[bidder, amount]` + Unreserved { bidder: T::AccountId, amount: BalanceOf<T> }, + /// Someone attempted to lease the same slot twice for a parachain. The amount is held in + /// reserve but no parachain slot has been leased. + ReserveConfiscated { para_id: ParaId, leaser: T::AccountId, amount: BalanceOf<T> }, + /// A new bid has been accepted as the current winner. + BidAccepted { + bidder: T::AccountId, + para_id: ParaId, + amount: BalanceOf<T>, + first_slot: LeasePeriodOf<T>, + last_slot: LeasePeriodOf<T>, + }, + /// The winning offset was chosen for an auction. This will map into the `Winning` storage + /// map. + WinningOffset { auction_index: AuctionIndex, block_number: BlockNumberFor<T> }, + } + + #[pallet::error] + pub enum Error<T> { + /// This auction is already in progress. + AuctionInProgress, + /// The lease period is in the past. + LeasePeriodInPast, + /// Para is not registered + ParaNotRegistered, + /// Not a current auction. + NotCurrentAuction, + /// Not an auction. + NotAuction, + /// Auction has already ended. + AuctionEnded, + /// The para is already leased out for part of this range. + AlreadyLeasedOut, + } + + /// Number of auctions started so far. + #[pallet::storage] + pub type AuctionCounter<T> = StorageValue<_, AuctionIndex, ValueQuery>; + + /// Information relating to the current auction, if there is one. + /// + /// The first item in the tuple is the lease period index that the first of the four + /// contiguous lease periods on auction is for. The second is the block number when the + /// auction will "begin to end", i.e. the first block of the Ending Period of the auction. + #[pallet::storage] + pub type AuctionInfo<T: Config> = StorageValue<_, (LeasePeriodOf<T>, BlockNumberFor<T>)>; + + /// Amounts currently reserved in the accounts of the bidders currently winning + /// (sub-)ranges. + #[pallet::storage] + pub type ReservedAmounts<T: Config> = + StorageMap<_, Twox64Concat, (T::AccountId, ParaId), BalanceOf<T>>; + + /// The winning bids for each of the 10 ranges at each sample in the final Ending Period of + /// the current auction. The map's key is the 0-based index into the Sample Size. The + /// first sample of the ending period is 0; the last is `Sample Size - 1`. + #[pallet::storage] + pub type Winning<T: Config> = StorageMap<_, Twox64Concat, BlockNumberFor<T>, WinningData<T>>; + + #[pallet::extra_constants] + impl<T: Config> Pallet<T> { + #[pallet::constant_name(SlotRangeCount)] + fn slot_range_count() -> u32 { + SlotRange::SLOT_RANGE_COUNT as u32 + } + + #[pallet::constant_name(LeasePeriodsPerSlot)] + fn lease_periods_per_slot() -> u32 { + SlotRange::LEASE_PERIODS_PER_SLOT as u32 + } + } + + #[pallet::hooks] + impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { + fn on_initialize(n: BlockNumberFor<T>) -> Weight { + let mut weight = T::DbWeight::get().reads(1); + + // If the current auction was in its ending period last block, then ensure that the + // (sub-)range winner information is duplicated from the previous block in case no bids + // happened in the last block. + if let AuctionStatus::EndingPeriod(offset, _sub_sample) = Self::auction_status(n) { + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + if !Winning::<T>::contains_key(&offset) { + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + let winning_data = offset + .checked_sub(&One::one()) + .and_then(Winning::<T>::get) + .unwrap_or([Self::EMPTY; SlotRange::SLOT_RANGE_COUNT]); + Winning::<T>::insert(offset, winning_data); + } + } + + // Check to see if an auction just ended. + if let Some((winning_ranges, auction_lease_period_index)) = Self::check_auction_end(n) { + // Auction is ended now. We have the winning ranges and the lease period index which + // acts as the offset. Handle it. + Self::manage_auction_end(auction_lease_period_index, winning_ranges); + weight = weight.saturating_add(T::WeightInfo::on_initialize()); + } + + weight + } + } + + #[pallet::call] + impl<T: Config> Pallet<T> { + /// Create a new auction. + /// + /// This can only happen when there isn't already an auction in progress and may only be + /// called by the root origin. Accepts the `duration` of this auction and the + /// `lease_period_index` of the initial lease period of the four that are to be auctioned. + #[pallet::call_index(0)] + #[pallet::weight((T::WeightInfo::new_auction(), DispatchClass::Operational))] + pub fn new_auction( + origin: OriginFor<T>, + #[pallet::compact] duration: BlockNumberFor<T>, + #[pallet::compact] lease_period_index: LeasePeriodOf<T>, + ) -> DispatchResult { + T::InitiateOrigin::ensure_origin(origin)?; + Self::do_new_auction(duration, lease_period_index) + } + + /// Make a new bid from an account (including a parachain account) for deploying a new + /// parachain. + /// + /// Multiple simultaneous bids from the same bidder are allowed only as long as all active + /// bids overlap each other (i.e. are mutually exclusive). Bids cannot be redacted. + /// + /// - `sub` is the sub-bidder ID, allowing for multiple competing bids to be made by (and + /// funded by) the same account. + /// - `auction_index` is the index of the auction to bid on. Should just be the present + /// value of `AuctionCounter`. + /// - `first_slot` is the first lease period index of the range to bid on. This is the + /// absolute lease period index value, not an auction-specific offset. + /// - `last_slot` is the last lease period index of the range to bid on. This is the + /// absolute lease period index value, not an auction-specific offset. + /// - `amount` is the amount to bid to be held as deposit for the parachain should the + /// bid win. This amount is held throughout the range. + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::bid())] + pub fn bid( + origin: OriginFor<T>, + #[pallet::compact] para: ParaId, + #[pallet::compact] auction_index: AuctionIndex, + #[pallet::compact] first_slot: LeasePeriodOf<T>, + #[pallet::compact] last_slot: LeasePeriodOf<T>, + #[pallet::compact] amount: BalanceOf<T>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::handle_bid(who, para, auction_index, first_slot, last_slot, amount)?; + Ok(()) + } + + /// Cancel an in-progress auction. + /// + /// Can only be called by Root origin. + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::cancel_auction())] + pub fn cancel_auction(origin: OriginFor<T>) -> DispatchResult { + ensure_root(origin)?; + // Unreserve all bids. + for ((bidder, _), amount) in ReservedAmounts::<T>::drain() { + CurrencyOf::<T>::unreserve(&bidder, amount); + } + #[allow(deprecated)] + Winning::<T>::remove_all(None); + AuctionInfo::<T>::kill(); + Ok(()) + } + } +} + +impl<T: Config> Auctioneer<BlockNumberFor<T>> for Pallet<T> { + type AccountId = T::AccountId; + type LeasePeriod = BlockNumberFor<T>; + type Currency = CurrencyOf<T>; + + fn new_auction( + duration: BlockNumberFor<T>, + lease_period_index: LeasePeriodOf<T>, + ) -> DispatchResult { + Self::do_new_auction(duration, lease_period_index) + } + + // Returns the status of the auction given the current block number. + fn auction_status(now: BlockNumberFor<T>) -> AuctionStatus<BlockNumberFor<T>> { + let early_end = match AuctionInfo::<T>::get() { + Some((_, early_end)) => early_end, + None => return AuctionStatus::NotStarted, + }; + + let after_early_end = match now.checked_sub(&early_end) { + Some(after_early_end) => after_early_end, + None => return AuctionStatus::StartingPeriod, + }; + + let ending_period = T::EndingPeriod::get(); + if after_early_end < ending_period { + let sample_length = T::SampleLength::get().max(One::one()); + let sample = after_early_end / sample_length; + let sub_sample = after_early_end % sample_length; + return AuctionStatus::EndingPeriod(sample, sub_sample) + } else { + // This is safe because of the comparison operator above + return AuctionStatus::VrfDelay(after_early_end - ending_period) + } + } + + fn place_bid( + bidder: T::AccountId, + para: ParaId, + first_slot: LeasePeriodOf<T>, + last_slot: LeasePeriodOf<T>, + amount: BalanceOf<T>, + ) -> DispatchResult { + Self::handle_bid(bidder, para, AuctionCounter::<T>::get(), first_slot, last_slot, amount) + } + + fn lease_period_index(b: BlockNumberFor<T>) -> Option<(Self::LeasePeriod, bool)> { + T::Leaser::lease_period_index(b) + } + + #[cfg(any(feature = "runtime-benchmarks", test))] + fn lease_period_length() -> (BlockNumberFor<T>, BlockNumberFor<T>) { + T::Leaser::lease_period_length() + } + + fn has_won_an_auction(para: ParaId, bidder: &T::AccountId) -> bool { + !T::Leaser::deposit_held(para, bidder).is_zero() + } +} + +impl<T: Config> Pallet<T> { + // A trick to allow me to initialize large arrays with nothing in them. + const EMPTY: Option<(<T as frame_system::Config>::AccountId, ParaId, BalanceOf<T>)> = None; + + /// Create a new auction. + /// + /// This can only happen when there isn't already an auction in progress. Accepts the `duration` + /// of this auction and the `lease_period_index` of the initial lease period of the four that + /// are to be auctioned. + fn do_new_auction( + duration: BlockNumberFor<T>, + lease_period_index: LeasePeriodOf<T>, + ) -> DispatchResult { + let maybe_auction = AuctionInfo::<T>::get(); + ensure!(maybe_auction.is_none(), Error::<T>::AuctionInProgress); + let now = frame_system::Pallet::<T>::block_number(); + if let Some((current_lease_period, _)) = T::Leaser::lease_period_index(now) { + // If there is no active lease period, then we don't need to make this check. + ensure!(lease_period_index >= current_lease_period, Error::<T>::LeasePeriodInPast); + } + + // Bump the counter. + let n = AuctionCounter::<T>::mutate(|n| { + *n += 1; + *n + }); + + // Set the information. + let ending = frame_system::Pallet::<T>::block_number().saturating_add(duration); + AuctionInfo::<T>::put((lease_period_index, ending)); + + Self::deposit_event(Event::<T>::AuctionStarted { + auction_index: n, + lease_period: lease_period_index, + ending, + }); + Ok(()) + } + + /// Actually place a bid in the current auction. + /// + /// - `bidder`: The account that will be funding this bid. + /// - `auction_index`: The auction index of the bid. For this to succeed, must equal + /// the current value of `AuctionCounter`. + /// - `first_slot`: The first lease period index of the range to be bid on. + /// - `last_slot`: The last lease period index of the range to be bid on (inclusive). + /// - `amount`: The total amount to be the bid for deposit over the range. + pub fn handle_bid( + bidder: T::AccountId, + para: ParaId, + auction_index: u32, + first_slot: LeasePeriodOf<T>, + last_slot: LeasePeriodOf<T>, + amount: BalanceOf<T>, + ) -> DispatchResult { + // Ensure para is registered before placing a bid on it. + ensure!(T::Registrar::is_registered(para), Error::<T>::ParaNotRegistered); + // Bidding on latest auction. + ensure!(auction_index == AuctionCounter::<T>::get(), Error::<T>::NotCurrentAuction); + // Assume it's actually an auction (this should never fail because of above). + let (first_lease_period, _) = AuctionInfo::<T>::get().ok_or(Error::<T>::NotAuction)?; + + // Get the auction status and the current sample block. For the starting period, the sample + // block is zero. + let auction_status = Self::auction_status(frame_system::Pallet::<T>::block_number()); + // The offset into the ending samples of the auction. + let offset = match auction_status { + AuctionStatus::NotStarted => return Err(Error::<T>::AuctionEnded.into()), + AuctionStatus::StartingPeriod => Zero::zero(), + AuctionStatus::EndingPeriod(o, _) => o, + AuctionStatus::VrfDelay(_) => return Err(Error::<T>::AuctionEnded.into()), + }; + + // We also make sure that the bid is not for any existing leases the para already has. + ensure!( + !T::Leaser::already_leased(para, first_slot, last_slot), + Error::<T>::AlreadyLeasedOut + ); + + // Our range. + let range = SlotRange::new_bounded(first_lease_period, first_slot, last_slot)?; + // Range as an array index. + let range_index = range as u8 as usize; + + // The current winning ranges. + let mut current_winning = Winning::<T>::get(offset) + .or_else(|| offset.checked_sub(&One::one()).and_then(Winning::<T>::get)) + .unwrap_or([Self::EMPTY; SlotRange::SLOT_RANGE_COUNT]); + + // If this bid beat the previous winner of our range. + if current_winning[range_index].as_ref().map_or(true, |last| amount > last.2) { + // Ok; we are the new winner of this range - reserve the additional amount and record. + + // Get the amount already held on deposit if this is a renewal bid (i.e. there's + // an existing lease on the same para by the same leaser). + let existing_lease_deposit = T::Leaser::deposit_held(para, &bidder); + let reserve_required = amount.saturating_sub(existing_lease_deposit); + + // Get the amount already reserved in any prior and still active bids by us. + let bidder_para = (bidder.clone(), para); + let already_reserved = ReservedAmounts::<T>::get(&bidder_para).unwrap_or_default(); + + // If these don't already cover the bid... + if let Some(additional) = reserve_required.checked_sub(&already_reserved) { + // ...then reserve some more funds from their account, failing if there's not + // enough funds. + CurrencyOf::<T>::reserve(&bidder, additional)?; + // ...and record the amount reserved. + ReservedAmounts::<T>::insert(&bidder_para, reserve_required); + + Self::deposit_event(Event::<T>::Reserved { + bidder: bidder.clone(), + extra_reserved: additional, + total_amount: reserve_required, + }); + } + + // Return any funds reserved for the previous winner if we are not in the ending period + // and they no longer have any active bids. + let mut outgoing_winner = Some((bidder.clone(), para, amount)); + swap(&mut current_winning[range_index], &mut outgoing_winner); + if let Some((who, para, _amount)) = outgoing_winner { + if auction_status.is_starting() && + current_winning + .iter() + .filter_map(Option::as_ref) + .all(|&(ref other, other_para, _)| other != &who || other_para != para) + { + // Previous bidder is no longer winning any ranges: unreserve their funds. + if let Some(amount) = ReservedAmounts::<T>::take(&(who.clone(), para)) { + // It really should be reserved; there's not much we can do here on fail. + let err_amt = CurrencyOf::<T>::unreserve(&who, amount); + debug_assert!(err_amt.is_zero()); + Self::deposit_event(Event::<T>::Unreserved { bidder: who, amount }); + } + } + } + + // Update the range winner. + Winning::<T>::insert(offset, ¤t_winning); + Self::deposit_event(Event::<T>::BidAccepted { + bidder, + para_id: para, + amount, + first_slot, + last_slot, + }); + } + Ok(()) + } + + /// Some when the auction's end is known (with the end block number). None if it is unknown. + /// If `Some` then the block number must be at most the previous block and at least the + /// previous block minus `T::EndingPeriod::get()`. + /// + /// This mutates the state, cleaning up `AuctionInfo` and `Winning` in the case of an auction + /// ending. An immediately subsequent call with the same argument will always return `None`. + fn check_auction_end(now: BlockNumberFor<T>) -> Option<(WinningData<T>, LeasePeriodOf<T>)> { + if let Some((lease_period_index, early_end)) = AuctionInfo::<T>::get() { + let ending_period = T::EndingPeriod::get(); + let late_end = early_end.saturating_add(ending_period); + let is_ended = now >= late_end; + if is_ended { + // auction definitely ended. + // check to see if we can determine the actual ending point. + let (raw_offset, known_since) = T::Randomness::random(&b"para_auction"[..]); + + if late_end <= known_since { + // Our random seed was known only after the auction ended. Good to use. + let raw_offset_block_number = <BlockNumberFor<T>>::decode( + &mut raw_offset.as_ref(), + ) + .expect("secure hashes should always be bigger than the block number; qed"); + let offset = (raw_offset_block_number % ending_period) / + T::SampleLength::get().max(One::one()); + + let auction_counter = AuctionCounter::<T>::get(); + Self::deposit_event(Event::<T>::WinningOffset { + auction_index: auction_counter, + block_number: offset, + }); + let res = Winning::<T>::get(offset) + .unwrap_or([Self::EMPTY; SlotRange::SLOT_RANGE_COUNT]); + // This `remove_all` statement should remove at most `EndingPeriod` / + // `SampleLength` items, which should be bounded and sensibly configured in the + // runtime. + #[allow(deprecated)] + Winning::<T>::remove_all(None); + AuctionInfo::<T>::kill(); + return Some((res, lease_period_index)) + } + } + } + None + } + + /// Auction just ended. We have the current lease period, the auction's lease period (which + /// is guaranteed to be at least the current period) and the bidders that were winning each + /// range at the time of the auction's close. + fn manage_auction_end( + auction_lease_period_index: LeasePeriodOf<T>, + winning_ranges: WinningData<T>, + ) { + // First, unreserve all amounts that were reserved for the bids. We will later re-reserve + // the amounts from the bidders that ended up being assigned the slot so there's no need to + // special-case them here. + for ((bidder, _), amount) in ReservedAmounts::<T>::drain() { + CurrencyOf::<T>::unreserve(&bidder, amount); + } + + // Next, calculate the winning combination of slots and thus the final winners of the + // auction. + let winners = Self::calculate_winners(winning_ranges); + + // Go through those winners and re-reserve their bid, updating our table of deposits + // accordingly. + for (leaser, para, amount, range) in winners.into_iter() { + let begin_offset = LeasePeriodOf::<T>::from(range.as_pair().0 as u32); + let period_begin = auction_lease_period_index + begin_offset; + let period_count = LeasePeriodOf::<T>::from(range.len() as u32); + + match T::Leaser::lease_out(para, &leaser, amount, period_begin, period_count) { + Err(LeaseError::ReserveFailed) | + Err(LeaseError::AlreadyEnded) | + Err(LeaseError::NoLeasePeriod) => { + // Should never happen since we just unreserved this amount (and our offset is + // from the present period). But if it does, there's not much we can do. + }, + Err(LeaseError::AlreadyLeased) => { + // The leaser attempted to get a second lease on the same para ID, possibly + // griefing us. Let's keep the amount reserved and let governance sort it out. + if CurrencyOf::<T>::reserve(&leaser, amount).is_ok() { + Self::deposit_event(Event::<T>::ReserveConfiscated { + para_id: para, + leaser, + amount, + }); + } + }, + Ok(()) => {}, // Nothing to report. + } + } + + Self::deposit_event(Event::<T>::AuctionClosed { + auction_index: AuctionCounter::<T>::get(), + }); + } + + /// Calculate the final winners from the winning slots. + /// + /// This is a simple dynamic programming algorithm designed by Al, the original code is at: + /// `https://github.com/w3f/consensus/blob/master/NPoS/auctiondynamicthing.py` + fn calculate_winners(mut winning: WinningData<T>) -> WinnersData<T> { + let winning_ranges = { + let mut best_winners_ending_at: [(Vec<SlotRange>, BalanceOf<T>); + SlotRange::LEASE_PERIODS_PER_SLOT] = Default::default(); + let best_bid = |range: SlotRange| { + winning[range as u8 as usize] + .as_ref() + .map(|(_, _, amount)| *amount * (range.len() as u32).into()) + }; + for i in 0..SlotRange::LEASE_PERIODS_PER_SLOT { + let r = SlotRange::new_bounded(0, 0, i as u32).expect("`i < LPPS`; qed"); + if let Some(bid) = best_bid(r) { + best_winners_ending_at[i] = (vec![r], bid); + } + for j in 0..i { + let r = SlotRange::new_bounded(0, j as u32 + 1, i as u32) + .expect("`i < LPPS`; `j < i`; `j + 1 < LPPS`; qed"); + if let Some(mut bid) = best_bid(r) { + bid += best_winners_ending_at[j].1; + if bid > best_winners_ending_at[i].1 { + let mut new_winners = best_winners_ending_at[j].0.clone(); + new_winners.push(r); + best_winners_ending_at[i] = (new_winners, bid); + } + } else { + if best_winners_ending_at[j].1 > best_winners_ending_at[i].1 { + best_winners_ending_at[i] = best_winners_ending_at[j].clone(); + } + } + } + } + best_winners_ending_at[SlotRange::LEASE_PERIODS_PER_SLOT - 1].0.clone() + }; + + winning_ranges + .into_iter() + .filter_map(|range| { + winning[range as u8 as usize] + .take() + .map(|(bidder, para, amount)| (bidder, para, amount, range)) + }) + .collect::<Vec<_>>() + } +} + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; diff --git a/polkadot/runtime/common/src/auctions/tests.rs b/polkadot/runtime/common/src/auctions/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..26e2ac47df84e49e05652108d788a7e4a011a70e --- /dev/null +++ b/polkadot/runtime/common/src/auctions/tests.rs @@ -0,0 +1,821 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Tests for the auctions pallet. + +#[cfg(test)] +use super::*; +use crate::{auctions::mock::*, mock::TestRegistrar}; +use frame_support::{assert_noop, assert_ok, assert_storage_noop}; +use pallet_balances; +use polkadot_primitives::Id as ParaId; +use polkadot_primitives_test_helpers::{dummy_hash, dummy_head_data, dummy_validation_code}; +use sp_core::H256; +use sp_runtime::DispatchError::BadOrigin; + +#[test] +fn basic_setup_works() { + new_test_ext().execute_with(|| { + assert_eq!(AuctionCounter::<Test>::get(), 0); + assert_eq!(TestLeaser::deposit_held(0u32.into(), &1), 0); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::NotStarted + ); + + System::run_to_block::<AllPalletsWithSystem>(10); + + assert_eq!(AuctionCounter::<Test>::get(), 0); + assert_eq!(TestLeaser::deposit_held(0u32.into(), &1), 0); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::NotStarted + ); + }); +} + +#[test] +fn can_start_auction() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + + assert_noop!(Auctions::new_auction(RuntimeOrigin::signed(1), 5, 1), BadOrigin); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + + assert_eq!(AuctionCounter::<Test>::get(), 1); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::StartingPeriod + ); + }); +} + +#[test] +fn bidding_works() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); + + assert_eq!(Balances::reserved_balance(1), 5); + assert_eq!(Balances::free_balance(1), 5); + assert_eq!( + Winning::<Test>::get(0).unwrap()[SlotRange::ZeroThree as u8 as usize], + Some((1, 0.into(), 5)) + ); + }); +} + +#[test] +fn under_bidding_works() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); + + assert_storage_noop!({ + assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 1, 4, 1)); + }); + }); +} + +#[test] +fn over_bidding_works() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 1, 4, 6)); + + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::reserved_balance(2), 6); + assert_eq!(Balances::free_balance(2), 14); + assert_eq!( + Winning::<Test>::get(0).unwrap()[SlotRange::ZeroThree as u8 as usize], + Some((2, 0.into(), 6)) + ); + }); +} + +#[test] +fn auction_proceeds_correctly() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + + assert_eq!(AuctionCounter::<Test>::get(), 1); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::StartingPeriod + ); + + System::run_to_block::<AllPalletsWithSystem>(2); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::StartingPeriod + ); + + System::run_to_block::<AllPalletsWithSystem>(3); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::StartingPeriod + ); + + System::run_to_block::<AllPalletsWithSystem>(4); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::StartingPeriod + ); + + System::run_to_block::<AllPalletsWithSystem>(5); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::StartingPeriod + ); + + System::run_to_block::<AllPalletsWithSystem>(6); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::EndingPeriod(0, 0) + ); + + System::run_to_block::<AllPalletsWithSystem>(7); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::EndingPeriod(1, 0) + ); + + System::run_to_block::<AllPalletsWithSystem>(8); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::EndingPeriod(2, 0) + ); + + System::run_to_block::<AllPalletsWithSystem>(9); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::NotStarted + ); + }); +} + +#[test] +fn can_win_auction() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); + assert_eq!(Balances::reserved_balance(1), 1); + assert_eq!(Balances::free_balance(1), 9); + System::run_to_block::<AllPalletsWithSystem>(9); + + assert_eq!( + leases(), + vec![ + ((0.into(), 1), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 2), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 3), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 4), LeaseData { leaser: 1, amount: 1 }), + ] + ); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1); + }); +} + +#[test] +fn can_win_auction_with_late_randomness() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); + assert_eq!(Balances::reserved_balance(1), 1); + assert_eq!(Balances::free_balance(1), 9); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::StartingPeriod + ); + System::run_to_block::<AllPalletsWithSystem>(8); + // Auction has not yet ended. + assert_eq!(leases(), vec![]); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::EndingPeriod(2, 0) + ); + // This will prevent the auction's winner from being decided in the next block, since + // the random seed was known before the final bids were made. + set_last_random(H256::zero(), 8); + // Auction definitely ended now, but we don't know exactly when in the last 3 blocks yet + // since no randomness available yet. + System::run_to_block::<AllPalletsWithSystem>(9); + // Auction has now ended... But auction winner still not yet decided, so no leases yet. + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::VrfDelay(0) + ); + assert_eq!(leases(), vec![]); + + // Random seed now updated to a value known at block 9, when the auction ended. This + // means that the winner can now be chosen. + set_last_random(H256::zero(), 9); + System::run_to_block::<AllPalletsWithSystem>(10); + // Auction ended and winner selected + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::NotStarted + ); + assert_eq!( + leases(), + vec![ + ((0.into(), 1), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 2), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 3), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 4), LeaseData { leaser: 1, amount: 1 }), + ] + ); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1); + }); +} + +#[test] +fn can_win_incomplete_auction() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 4, 4, 5)); + System::run_to_block::<AllPalletsWithSystem>(9); + + assert_eq!(leases(), vec![((0.into(), 4), LeaseData { leaser: 1, amount: 5 }),]); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); + }); +} + +#[test] +fn should_choose_best_combination() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 2, 3, 4)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), 0.into(), 1, 4, 4, 2)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1.into(), 1, 1, 4, 2)); + System::run_to_block::<AllPalletsWithSystem>(9); + + assert_eq!( + leases(), + vec![ + ((0.into(), 1), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 2), LeaseData { leaser: 2, amount: 4 }), + ((0.into(), 3), LeaseData { leaser: 2, amount: 4 }), + ((0.into(), 4), LeaseData { leaser: 3, amount: 2 }), + ] + ); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1); + assert_eq!(TestLeaser::deposit_held(1.into(), &1), 0); + assert_eq!(TestLeaser::deposit_held(0.into(), &2), 4); + assert_eq!(TestLeaser::deposit_held(0.into(), &3), 2); + }); +} + +#[test] +fn gap_bid_works() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + + // User 1 will make a bid for period 1 and 4 for the same Para 0 + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 4, 4, 4)); + + // User 2 and 3 will make a bid for para 1 on period 2 and 3 respectively + assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 1.into(), 1, 2, 2, 2)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), 1.into(), 1, 3, 3, 3)); + + // Total reserved should be the max of the two + assert_eq!(Balances::reserved_balance(1), 4); + + // Other people are reserved correctly too + assert_eq!(Balances::reserved_balance(2), 2); + assert_eq!(Balances::reserved_balance(3), 3); + + // End the auction. + System::run_to_block::<AllPalletsWithSystem>(9); + + assert_eq!( + leases(), + vec![ + ((0.into(), 1), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 4), LeaseData { leaser: 1, amount: 4 }), + ((1.into(), 2), LeaseData { leaser: 2, amount: 2 }), + ((1.into(), 3), LeaseData { leaser: 3, amount: 3 }), + ] + ); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 4); + assert_eq!(TestLeaser::deposit_held(1.into(), &2), 2); + assert_eq!(TestLeaser::deposit_held(1.into(), &3), 3); + }); +} + +#[test] +fn deposit_credit_should_work() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 5)); + assert_eq!(Balances::reserved_balance(1), 5); + System::run_to_block::<AllPalletsWithSystem>(10); + + assert_eq!(leases(), vec![((0.into(), 1), LeaseData { leaser: 1, amount: 5 }),]); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); + + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 2)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 2, 2, 6)); + // Only 1 reserved since we have a deposit credit of 5. + assert_eq!(Balances::reserved_balance(1), 1); + System::run_to_block::<AllPalletsWithSystem>(20); + + assert_eq!( + leases(), + vec![ + ((0.into(), 1), LeaseData { leaser: 1, amount: 5 }), + ((0.into(), 2), LeaseData { leaser: 1, amount: 6 }), + ] + ); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 6); + }); +} + +#[test] +fn deposit_credit_on_alt_para_should_not_count() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 5)); + assert_eq!(Balances::reserved_balance(1), 5); + System::run_to_block::<AllPalletsWithSystem>(10); + + assert_eq!(leases(), vec![((0.into(), 1), LeaseData { leaser: 1, amount: 5 }),]); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); + + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 2)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1.into(), 2, 2, 2, 6)); + // 6 reserved since we are bidding on a new para; only works because we don't + assert_eq!(Balances::reserved_balance(1), 6); + System::run_to_block::<AllPalletsWithSystem>(20); + + assert_eq!( + leases(), + vec![ + ((0.into(), 1), LeaseData { leaser: 1, amount: 5 }), + ((1.into(), 2), LeaseData { leaser: 1, amount: 6 }), + ] + ); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); + assert_eq!(TestLeaser::deposit_held(1.into(), &1), 6); + }); +} + +#[test] +fn multiple_bids_work_pre_ending() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + + for i in 1..6u64 { + System::run_to_block::<AllPalletsWithSystem>(i as _); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(i), 0.into(), 1, 1, 4, i)); + for j in 1..6 { + assert_eq!(Balances::reserved_balance(j), if j == i { j } else { 0 }); + assert_eq!(Balances::free_balance(j), if j == i { j * 9 } else { j * 10 }); + } + } + + System::run_to_block::<AllPalletsWithSystem>(9); + assert_eq!( + leases(), + vec![ + ((0.into(), 1), LeaseData { leaser: 5, amount: 5 }), + ((0.into(), 2), LeaseData { leaser: 5, amount: 5 }), + ((0.into(), 3), LeaseData { leaser: 5, amount: 5 }), + ((0.into(), 4), LeaseData { leaser: 5, amount: 5 }), + ] + ); + }); +} + +#[test] +fn multiple_bids_work_post_ending() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 0, 1)); + + for i in 1..6u64 { + System::run_to_block::<AllPalletsWithSystem>(((i - 1) / 2 + 1) as _); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(i), 0.into(), 1, 1, 4, i)); + for j in 1..6 { + assert_eq!(Balances::reserved_balance(j), if j <= i { j } else { 0 }); + assert_eq!(Balances::free_balance(j), if j <= i { j * 9 } else { j * 10 }); + } + } + for i in 1..6u64 { + assert_eq!(ReservedAmounts::<Test>::get((i, ParaId::from(0))).unwrap(), i); + } + + System::run_to_block::<AllPalletsWithSystem>(5); + assert_eq!( + leases(), + (1..=4) + .map(|i| ((0.into(), i), LeaseData { leaser: 2, amount: 2 })) + .collect::<Vec<_>>() + ); + }); +} + +#[test] +fn incomplete_calculate_winners_works() { + let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; + winning[SlotRange::ThreeThree as u8 as usize] = Some((1, 0.into(), 1)); + + let winners = vec![(1, 0.into(), 1, SlotRange::ThreeThree)]; + + assert_eq!(Auctions::calculate_winners(winning), winners); +} + +#[test] +fn first_incomplete_calculate_winners_works() { + let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; + winning[0] = Some((1, 0.into(), 1)); + + let winners = vec![(1, 0.into(), 1, SlotRange::ZeroZero)]; + + assert_eq!(Auctions::calculate_winners(winning), winners); +} + +#[test] +fn calculate_winners_works() { + let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; + winning[SlotRange::ZeroZero as u8 as usize] = Some((2, 0.into(), 2)); + winning[SlotRange::ZeroThree as u8 as usize] = Some((1, 100.into(), 1)); + winning[SlotRange::OneOne as u8 as usize] = Some((3, 1.into(), 1)); + winning[SlotRange::TwoTwo as u8 as usize] = Some((1, 2.into(), 53)); + winning[SlotRange::ThreeThree as u8 as usize] = Some((5, 3.into(), 1)); + + let winners = vec![ + (2, 0.into(), 2, SlotRange::ZeroZero), + (3, 1.into(), 1, SlotRange::OneOne), + (1, 2.into(), 53, SlotRange::TwoTwo), + (5, 3.into(), 1, SlotRange::ThreeThree), + ]; + assert_eq!(Auctions::calculate_winners(winning), winners); + + winning[SlotRange::ZeroOne as u8 as usize] = Some((4, 10.into(), 3)); + let winners = vec![ + (4, 10.into(), 3, SlotRange::ZeroOne), + (1, 2.into(), 53, SlotRange::TwoTwo), + (5, 3.into(), 1, SlotRange::ThreeThree), + ]; + assert_eq!(Auctions::calculate_winners(winning), winners); + + winning[SlotRange::ZeroThree as u8 as usize] = Some((1, 100.into(), 100)); + let winners = vec![(1, 100.into(), 100, SlotRange::ZeroThree)]; + assert_eq!(Auctions::calculate_winners(winning), winners); +} + +#[test] +fn lower_bids_are_correctly_refunded() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 1, 1)); + let para_1 = ParaId::from(1_u32); + let para_2 = ParaId::from(2_u32); + + // Make a bid and reserve a balance + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), para_1, 1, 1, 4, 9)); + assert_eq!(Balances::reserved_balance(1), 9); + assert_eq!(ReservedAmounts::<Test>::get((1, para_1)), Some(9)); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(ReservedAmounts::<Test>::get((2, para_2)), None); + + // Bigger bid, reserves new balance and returns funds + assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), para_2, 1, 1, 4, 19)); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(ReservedAmounts::<Test>::get((1, para_1)), None); + assert_eq!(Balances::reserved_balance(2), 19); + assert_eq!(ReservedAmounts::<Test>::get((2, para_2)), Some(19)); + }); +} + +#[test] +fn initialize_winners_in_ending_period_works() { + new_test_ext().execute_with(|| { + let ed: u64 = <Test as pallet_balances::Config>::ExistentialDeposit::get(); + assert_eq!(ed, 1); + System::run_to_block::<AllPalletsWithSystem>(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 1)); + let para_1 = ParaId::from(1_u32); + let para_2 = ParaId::from(2_u32); + let para_3 = ParaId::from(3_u32); + + // Make bids + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), para_1, 1, 1, 4, 9)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), para_2, 1, 3, 4, 19)); + + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::StartingPeriod + ); + let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; + winning[SlotRange::ZeroThree as u8 as usize] = Some((1, para_1, 9)); + winning[SlotRange::TwoThree as u8 as usize] = Some((2, para_2, 19)); + assert_eq!(Winning::<Test>::get(0), Some(winning)); + + System::run_to_block::<AllPalletsWithSystem>(9); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::StartingPeriod + ); + + System::run_to_block::<AllPalletsWithSystem>(10); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::EndingPeriod(0, 0) + ); + assert_eq!(Winning::<Test>::get(0), Some(winning)); + + System::run_to_block::<AllPalletsWithSystem>(11); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::EndingPeriod(1, 0) + ); + assert_eq!(Winning::<Test>::get(1), Some(winning)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 3, 4, 29)); + + System::run_to_block::<AllPalletsWithSystem>(12); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::EndingPeriod(2, 0) + ); + winning[SlotRange::TwoThree as u8 as usize] = Some((3, para_3, 29)); + assert_eq!(Winning::<Test>::get(2), Some(winning)); + }); +} + +#[test] +fn handle_bid_requires_registered_para() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_noop!( + Auctions::bid(RuntimeOrigin::signed(1), 1337.into(), 1, 1, 4, 1), + Error::<Test>::ParaNotRegistered + ); + assert_ok!(TestRegistrar::<Test>::register( + 1, + 1337.into(), + dummy_head_data(), + dummy_validation_code() + )); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1337.into(), 1, 1, 4, 1)); + }); +} + +#[test] +fn handle_bid_checks_existing_lease_periods() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 2, 3, 1)); + assert_eq!(Balances::reserved_balance(1), 1); + assert_eq!(Balances::free_balance(1), 9); + System::run_to_block::<AllPalletsWithSystem>(9); + + assert_eq!( + leases(), + vec![ + ((0.into(), 2), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 3), LeaseData { leaser: 1, amount: 1 }), + ] + ); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1); + + // Para 1 just won an auction above and won some lease periods. + // No bids can work which overlap these periods. + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_noop!( + Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 4, 1), + Error::<Test>::AlreadyLeasedOut, + ); + assert_noop!( + Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 2, 1), + Error::<Test>::AlreadyLeasedOut, + ); + assert_noop!( + Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 3, 4, 1), + Error::<Test>::AlreadyLeasedOut, + ); + // This is okay, not an overlapping bid. + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 1, 1)); + }); +} + +// Here we will test that taking only 10 samples during the ending period works as expected. +#[test] +fn less_winning_samples_work() { + new_test_ext().execute_with(|| { + let ed: u64 = <Test as pallet_balances::Config>::ExistentialDeposit::get(); + assert_eq!(ed, 1); + EndingPeriod::set(30); + SampleLength::set(10); + + System::run_to_block::<AllPalletsWithSystem>(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 11)); + let para_1 = ParaId::from(1_u32); + let para_2 = ParaId::from(2_u32); + let para_3 = ParaId::from(3_u32); + + // Make bids + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), para_1, 1, 11, 14, 9)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), para_2, 1, 13, 14, 19)); + + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::StartingPeriod + ); + let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; + winning[SlotRange::ZeroThree as u8 as usize] = Some((1, para_1, 9)); + winning[SlotRange::TwoThree as u8 as usize] = Some((2, para_2, 19)); + assert_eq!(Winning::<Test>::get(0), Some(winning)); + + System::run_to_block::<AllPalletsWithSystem>(9); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::StartingPeriod + ); + + System::run_to_block::<AllPalletsWithSystem>(10); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::EndingPeriod(0, 0) + ); + assert_eq!(Winning::<Test>::get(0), Some(winning)); + + // New bids update the current winning + assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 14, 14, 29)); + winning[SlotRange::ThreeThree as u8 as usize] = Some((3, para_3, 29)); + assert_eq!(Winning::<Test>::get(0), Some(winning)); + + System::run_to_block::<AllPalletsWithSystem>(20); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::EndingPeriod(1, 0) + ); + assert_eq!(Winning::<Test>::get(1), Some(winning)); + System::run_to_block::<AllPalletsWithSystem>(25); + // Overbid mid sample + assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 13, 14, 29)); + winning[SlotRange::TwoThree as u8 as usize] = Some((3, para_3, 29)); + assert_eq!(Winning::<Test>::get(1), Some(winning)); + + System::run_to_block::<AllPalletsWithSystem>(30); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::EndingPeriod(2, 0) + ); + assert_eq!(Winning::<Test>::get(2), Some(winning)); + + set_last_random(H256::from([254; 32]), 40); + System::run_to_block::<AllPalletsWithSystem>(40); + // Auction ended and winner selected + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::NotStarted + ); + assert_eq!( + leases(), + vec![ + ((3.into(), 13), LeaseData { leaser: 3, amount: 29 }), + ((3.into(), 14), LeaseData { leaser: 3, amount: 29 }), + ] + ); + }); +} + +#[test] +fn auction_status_works() { + new_test_ext().execute_with(|| { + EndingPeriod::set(30); + SampleLength::set(10); + set_last_random(dummy_hash(), 0); + + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::NotStarted + ); + + System::run_to_block::<AllPalletsWithSystem>(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 11)); + + System::run_to_block::<AllPalletsWithSystem>(9); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::StartingPeriod + ); + + System::run_to_block::<AllPalletsWithSystem>(10); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::EndingPeriod(0, 0) + ); + + System::run_to_block::<AllPalletsWithSystem>(11); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::EndingPeriod(0, 1) + ); + + System::run_to_block::<AllPalletsWithSystem>(19); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::EndingPeriod(0, 9) + ); + + System::run_to_block::<AllPalletsWithSystem>(20); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::EndingPeriod(1, 0) + ); + + System::run_to_block::<AllPalletsWithSystem>(25); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::EndingPeriod(1, 5) + ); + + System::run_to_block::<AllPalletsWithSystem>(30); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::EndingPeriod(2, 0) + ); + + System::run_to_block::<AllPalletsWithSystem>(39); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::EndingPeriod(2, 9) + ); + + System::run_to_block::<AllPalletsWithSystem>(40); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::VrfDelay(0) + ); + + System::run_to_block::<AllPalletsWithSystem>(44); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::VrfDelay(4) + ); + + set_last_random(dummy_hash(), 45); + System::run_to_block::<AllPalletsWithSystem>(45); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::<u32>::NotStarted + ); + }); +} + +#[test] +fn can_cancel_auction() { + new_test_ext().execute_with(|| { + System::run_to_block::<AllPalletsWithSystem>(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); + assert_eq!(Balances::reserved_balance(1), 1); + assert_eq!(Balances::free_balance(1), 9); + + assert_noop!(Auctions::cancel_auction(RuntimeOrigin::signed(6)), BadOrigin); + assert_ok!(Auctions::cancel_auction(RuntimeOrigin::root())); + + assert!(AuctionInfo::<Test>::get().is_none()); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(ReservedAmounts::<Test>::iter().count(), 0); + assert_eq!(Winning::<Test>::iter().count(), 0); + }); +} diff --git a/polkadot/runtime/common/src/claims.rs b/polkadot/runtime/common/src/claims.rs deleted file mode 100644 index 1ee80dd76e2d2ff45490b5cd0922329d50a9c813..0000000000000000000000000000000000000000 --- a/polkadot/runtime/common/src/claims.rs +++ /dev/null @@ -1,1758 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see <http://www.gnu.org/licenses/>. - -//! Pallet to process claims from Ethereum addresses. - -#[cfg(not(feature = "std"))] -use alloc::{format, string::String}; -use alloc::{vec, vec::Vec}; -use codec::{Decode, Encode, MaxEncodedLen}; -use core::fmt::Debug; -use frame_support::{ - ensure, - traits::{Currency, Get, IsSubType, VestingSchedule}, - weights::Weight, - DefaultNoBound, -}; -pub use pallet::*; -use polkadot_primitives::ValidityError; -use scale_info::TypeInfo; -use serde::{self, Deserialize, Deserializer, Serialize, Serializer}; -use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256}; -use sp_runtime::{ - impl_tx_ext_default, - traits::{ - AsSystemOriginSigner, AsTransactionAuthorizedOrigin, CheckedSub, DispatchInfoOf, - Dispatchable, TransactionExtension, Zero, - }, - transaction_validity::{ - InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError, - ValidTransaction, - }, - RuntimeDebug, -}; - -type CurrencyOf<T> = <<T as Config>::VestingSchedule as VestingSchedule< - <T as frame_system::Config>::AccountId, ->>::Currency; -type BalanceOf<T> = <CurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::Balance; - -pub trait WeightInfo { - fn claim() -> Weight; - fn mint_claim() -> Weight; - fn claim_attest() -> Weight; - fn attest() -> Weight; - fn move_claim() -> Weight; - fn prevalidate_attests() -> Weight; -} - -pub struct TestWeightInfo; -impl WeightInfo for TestWeightInfo { - fn claim() -> Weight { - Weight::zero() - } - fn mint_claim() -> Weight { - Weight::zero() - } - fn claim_attest() -> Weight { - Weight::zero() - } - fn attest() -> Weight { - Weight::zero() - } - fn move_claim() -> Weight { - Weight::zero() - } - fn prevalidate_attests() -> Weight { - Weight::zero() - } -} - -/// The kind of statement an account needs to make for a claim to be valid. -#[derive( - Encode, - Decode, - Clone, - Copy, - Eq, - PartialEq, - RuntimeDebug, - TypeInfo, - Serialize, - Deserialize, - MaxEncodedLen, -)] -pub enum StatementKind { - /// Statement required to be made by non-SAFT holders. - Regular, - /// Statement required to be made by SAFT holders. - Saft, -} - -impl StatementKind { - /// Convert this to the (English) statement it represents. - fn to_text(self) -> &'static [u8] { - match self { - StatementKind::Regular => - &b"I hereby agree to the terms of the statement whose SHA-256 multihash is \ - Qmc1XYqT6S39WNp2UeiRUrZichUWUPpGEThDE6dAb3f6Ny. (This may be found at the URL: \ - https://statement.polkadot.network/regular.html)"[..], - StatementKind::Saft => - &b"I hereby agree to the terms of the statement whose SHA-256 multihash is \ - QmXEkMahfhHJPzT3RjkXiZVFi77ZeVeuxtAjhojGRNYckz. (This may be found at the URL: \ - https://statement.polkadot.network/saft.html)"[..], - } - } -} - -impl Default for StatementKind { - fn default() -> Self { - StatementKind::Regular - } -} - -/// An Ethereum address (i.e. 20 bytes, used to represent an Ethereum account). -/// -/// This gets serialized to the 0x-prefixed hex representation. -#[derive( - Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen, -)] -pub struct EthereumAddress([u8; 20]); - -impl Serialize for EthereumAddress { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - { - let hex: String = rustc_hex::ToHex::to_hex(&self.0[..]); - serializer.serialize_str(&format!("0x{}", hex)) - } -} - -impl<'de> Deserialize<'de> for EthereumAddress { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - let base_string = String::deserialize(deserializer)?; - let offset = if base_string.starts_with("0x") { 2 } else { 0 }; - let s = &base_string[offset..]; - if s.len() != 40 { - Err(serde::de::Error::custom( - "Bad length of Ethereum address (should be 42 including '0x')", - ))?; - } - let raw: Vec<u8> = rustc_hex::FromHex::from_hex(s) - .map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?; - let mut r = Self::default(); - r.0.copy_from_slice(&raw); - Ok(r) - } -} - -#[derive(Encode, Decode, Clone, TypeInfo, MaxEncodedLen)] -pub struct EcdsaSignature(pub [u8; 65]); - -impl PartialEq for EcdsaSignature { - fn eq(&self, other: &Self) -> bool { - &self.0[..] == &other.0[..] - } -} - -impl core::fmt::Debug for EcdsaSignature { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "EcdsaSignature({:?})", &self.0[..]) - } -} - -#[frame_support::pallet] -pub mod pallet { - use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; - - #[pallet::pallet] - pub struct Pallet<T>(_); - - /// Configuration trait. - #[pallet::config] - pub trait Config: frame_system::Config { - /// The overarching event type. - type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; - type VestingSchedule: VestingSchedule<Self::AccountId, Moment = BlockNumberFor<Self>>; - #[pallet::constant] - type Prefix: Get<&'static [u8]>; - type MoveClaimOrigin: EnsureOrigin<Self::RuntimeOrigin>; - type WeightInfo: WeightInfo; - } - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event<T: Config> { - /// Someone claimed some DOTs. - Claimed { who: T::AccountId, ethereum_address: EthereumAddress, amount: BalanceOf<T> }, - } - - #[pallet::error] - pub enum Error<T> { - /// Invalid Ethereum signature. - InvalidEthereumSignature, - /// Ethereum address has no claim. - SignerHasNoClaim, - /// Account ID sending transaction has no claim. - SenderHasNoClaim, - /// There's not enough in the pot to pay out some unvested amount. Generally implies a - /// logic error. - PotUnderflow, - /// A needed statement was not included. - InvalidStatement, - /// The account already has a vested balance. - VestedBalanceExists, - } - - #[pallet::storage] - pub type Claims<T: Config> = StorageMap<_, Identity, EthereumAddress, BalanceOf<T>>; - - #[pallet::storage] - pub type Total<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>; - - /// Vesting schedule for a claim. - /// First balance is the total amount that should be held for vesting. - /// Second balance is how much should be unlocked per block. - /// The block number is when the vesting should start. - #[pallet::storage] - pub type Vesting<T: Config> = - StorageMap<_, Identity, EthereumAddress, (BalanceOf<T>, BalanceOf<T>, BlockNumberFor<T>)>; - - /// The statement kind that must be signed, if any. - #[pallet::storage] - pub(super) type Signing<T> = StorageMap<_, Identity, EthereumAddress, StatementKind>; - - /// Pre-claimed Ethereum accounts, by the Account ID that they are claimed to. - #[pallet::storage] - pub(super) type Preclaims<T: Config> = StorageMap<_, Identity, T::AccountId, EthereumAddress>; - - #[pallet::genesis_config] - #[derive(DefaultNoBound)] - pub struct GenesisConfig<T: Config> { - pub claims: - Vec<(EthereumAddress, BalanceOf<T>, Option<T::AccountId>, Option<StatementKind>)>, - pub vesting: Vec<(EthereumAddress, (BalanceOf<T>, BalanceOf<T>, BlockNumberFor<T>))>, - } - - #[pallet::genesis_build] - impl<T: Config> BuildGenesisConfig for GenesisConfig<T> { - fn build(&self) { - // build `Claims` - self.claims.iter().map(|(a, b, _, _)| (*a, *b)).for_each(|(a, b)| { - Claims::<T>::insert(a, b); - }); - // build `Total` - Total::<T>::put( - self.claims - .iter() - .fold(Zero::zero(), |acc: BalanceOf<T>, &(_, b, _, _)| acc + b), - ); - // build `Vesting` - self.vesting.iter().for_each(|(k, v)| { - Vesting::<T>::insert(k, v); - }); - // build `Signing` - self.claims - .iter() - .filter_map(|(a, _, _, s)| Some((*a, (*s)?))) - .for_each(|(a, s)| { - Signing::<T>::insert(a, s); - }); - // build `Preclaims` - self.claims.iter().filter_map(|(a, _, i, _)| Some((i.clone()?, *a))).for_each( - |(i, a)| { - Preclaims::<T>::insert(i, a); - }, - ); - } - } - - #[pallet::hooks] - impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {} - - #[pallet::call] - impl<T: Config> Pallet<T> { - /// Make a claim to collect your DOTs. - /// - /// The dispatch origin for this call must be _None_. - /// - /// Unsigned Validation: - /// A call to claim is deemed valid if the signature provided matches - /// the expected signed message of: - /// - /// > Ethereum Signed Message: - /// > (configured prefix string)(address) - /// - /// and `address` matches the `dest` account. - /// - /// Parameters: - /// - `dest`: The destination account to payout the claim. - /// - `ethereum_signature`: The signature of an ethereum signed message matching the format - /// described above. - /// - /// <weight> - /// The weight of this call is invariant over the input parameters. - /// Weight includes logic to validate unsigned `claim` call. - /// - /// Total Complexity: O(1) - /// </weight> - #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::claim())] - pub fn claim( - origin: OriginFor<T>, - dest: T::AccountId, - ethereum_signature: EcdsaSignature, - ) -> DispatchResult { - ensure_none(origin)?; - - let data = dest.using_encoded(to_ascii_hex); - let signer = Self::eth_recover(ðereum_signature, &data, &[][..]) - .ok_or(Error::<T>::InvalidEthereumSignature)?; - ensure!(Signing::<T>::get(&signer).is_none(), Error::<T>::InvalidStatement); - - Self::process_claim(signer, dest)?; - Ok(()) - } - - /// Mint a new claim to collect DOTs. - /// - /// The dispatch origin for this call must be _Root_. - /// - /// Parameters: - /// - `who`: The Ethereum address allowed to collect this claim. - /// - `value`: The number of DOTs that will be claimed. - /// - `vesting_schedule`: An optional vesting schedule for these DOTs. - /// - /// <weight> - /// The weight of this call is invariant over the input parameters. - /// We assume worst case that both vesting and statement is being inserted. - /// - /// Total Complexity: O(1) - /// </weight> - #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::mint_claim())] - pub fn mint_claim( - origin: OriginFor<T>, - who: EthereumAddress, - value: BalanceOf<T>, - vesting_schedule: Option<(BalanceOf<T>, BalanceOf<T>, BlockNumberFor<T>)>, - statement: Option<StatementKind>, - ) -> DispatchResult { - ensure_root(origin)?; - - Total::<T>::mutate(|t| *t += value); - Claims::<T>::insert(who, value); - if let Some(vs) = vesting_schedule { - Vesting::<T>::insert(who, vs); - } - if let Some(s) = statement { - Signing::<T>::insert(who, s); - } - Ok(()) - } - - /// Make a claim to collect your DOTs by signing a statement. - /// - /// The dispatch origin for this call must be _None_. - /// - /// Unsigned Validation: - /// A call to `claim_attest` is deemed valid if the signature provided matches - /// the expected signed message of: - /// - /// > Ethereum Signed Message: - /// > (configured prefix string)(address)(statement) - /// - /// and `address` matches the `dest` account; the `statement` must match that which is - /// expected according to your purchase arrangement. - /// - /// Parameters: - /// - `dest`: The destination account to payout the claim. - /// - `ethereum_signature`: The signature of an ethereum signed message matching the format - /// described above. - /// - `statement`: The identity of the statement which is being attested to in the - /// signature. - /// - /// <weight> - /// The weight of this call is invariant over the input parameters. - /// Weight includes logic to validate unsigned `claim_attest` call. - /// - /// Total Complexity: O(1) - /// </weight> - #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::claim_attest())] - pub fn claim_attest( - origin: OriginFor<T>, - dest: T::AccountId, - ethereum_signature: EcdsaSignature, - statement: Vec<u8>, - ) -> DispatchResult { - ensure_none(origin)?; - - let data = dest.using_encoded(to_ascii_hex); - let signer = Self::eth_recover(ðereum_signature, &data, &statement) - .ok_or(Error::<T>::InvalidEthereumSignature)?; - if let Some(s) = Signing::<T>::get(signer) { - ensure!(s.to_text() == &statement[..], Error::<T>::InvalidStatement); - } - Self::process_claim(signer, dest)?; - Ok(()) - } - - /// Attest to a statement, needed to finalize the claims process. - /// - /// WARNING: Insecure unless your chain includes `PrevalidateAttests` as a - /// `TransactionExtension`. - /// - /// Unsigned Validation: - /// A call to attest is deemed valid if the sender has a `Preclaim` registered - /// and provides a `statement` which is expected for the account. - /// - /// Parameters: - /// - `statement`: The identity of the statement which is being attested to in the - /// signature. - /// - /// <weight> - /// The weight of this call is invariant over the input parameters. - /// Weight includes logic to do pre-validation on `attest` call. - /// - /// Total Complexity: O(1) - /// </weight> - #[pallet::call_index(3)] - #[pallet::weight(( - T::WeightInfo::attest(), - DispatchClass::Normal, - Pays::No - ))] - pub fn attest(origin: OriginFor<T>, statement: Vec<u8>) -> DispatchResult { - let who = ensure_signed(origin)?; - let signer = Preclaims::<T>::get(&who).ok_or(Error::<T>::SenderHasNoClaim)?; - if let Some(s) = Signing::<T>::get(signer) { - ensure!(s.to_text() == &statement[..], Error::<T>::InvalidStatement); - } - Self::process_claim(signer, who.clone())?; - Preclaims::<T>::remove(&who); - Ok(()) - } - - #[pallet::call_index(4)] - #[pallet::weight(T::WeightInfo::move_claim())] - pub fn move_claim( - origin: OriginFor<T>, - old: EthereumAddress, - new: EthereumAddress, - maybe_preclaim: Option<T::AccountId>, - ) -> DispatchResultWithPostInfo { - T::MoveClaimOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?; - - Claims::<T>::take(&old).map(|c| Claims::<T>::insert(&new, c)); - Vesting::<T>::take(&old).map(|c| Vesting::<T>::insert(&new, c)); - Signing::<T>::take(&old).map(|c| Signing::<T>::insert(&new, c)); - maybe_preclaim.map(|preclaim| { - Preclaims::<T>::mutate(&preclaim, |maybe_o| { - if maybe_o.as_ref().map_or(false, |o| o == &old) { - *maybe_o = Some(new) - } - }) - }); - Ok(Pays::No.into()) - } - } - - #[pallet::validate_unsigned] - impl<T: Config> ValidateUnsigned for Pallet<T> { - type Call = Call<T>; - - fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { - const PRIORITY: u64 = 100; - - let (maybe_signer, maybe_statement) = match call { - // <weight> - // The weight of this logic is included in the `claim` dispatchable. - // </weight> - Call::claim { dest: account, ethereum_signature } => { - let data = account.using_encoded(to_ascii_hex); - (Self::eth_recover(ðereum_signature, &data, &[][..]), None) - }, - // <weight> - // The weight of this logic is included in the `claim_attest` dispatchable. - // </weight> - Call::claim_attest { dest: account, ethereum_signature, statement } => { - let data = account.using_encoded(to_ascii_hex); - ( - Self::eth_recover(ðereum_signature, &data, &statement), - Some(statement.as_slice()), - ) - }, - _ => return Err(InvalidTransaction::Call.into()), - }; - - let signer = maybe_signer.ok_or(InvalidTransaction::Custom( - ValidityError::InvalidEthereumSignature.into(), - ))?; - - let e = InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()); - ensure!(Claims::<T>::contains_key(&signer), e); - - let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into()); - match Signing::<T>::get(signer) { - None => ensure!(maybe_statement.is_none(), e), - Some(s) => ensure!(Some(s.to_text()) == maybe_statement, e), - } - - Ok(ValidTransaction { - priority: PRIORITY, - requires: vec![], - provides: vec![("claims", signer).encode()], - longevity: TransactionLongevity::max_value(), - propagate: true, - }) - } - } -} - -/// Converts the given binary data into ASCII-encoded hex. It will be twice the length. -fn to_ascii_hex(data: &[u8]) -> Vec<u8> { - let mut r = Vec::with_capacity(data.len() * 2); - let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n }); - for &b in data.iter() { - push_nibble(b / 16); - push_nibble(b % 16); - } - r -} - -impl<T: Config> Pallet<T> { - // Constructs the message that Ethereum RPC's `personal_sign` and `eth_sign` would sign. - fn ethereum_signable_message(what: &[u8], extra: &[u8]) -> Vec<u8> { - let prefix = T::Prefix::get(); - let mut l = prefix.len() + what.len() + extra.len(); - let mut rev = Vec::new(); - while l > 0 { - rev.push(b'0' + (l % 10) as u8); - l /= 10; - } - let mut v = b"\x19Ethereum Signed Message:\n".to_vec(); - v.extend(rev.into_iter().rev()); - v.extend_from_slice(prefix); - v.extend_from_slice(what); - v.extend_from_slice(extra); - v - } - - // Attempts to recover the Ethereum address from a message signature signed by using - // the Ethereum RPC's `personal_sign` and `eth_sign`. - fn eth_recover(s: &EcdsaSignature, what: &[u8], extra: &[u8]) -> Option<EthereumAddress> { - let msg = keccak_256(&Self::ethereum_signable_message(what, extra)); - let mut res = EthereumAddress::default(); - res.0 - .copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]); - Some(res) - } - - fn process_claim(signer: EthereumAddress, dest: T::AccountId) -> sp_runtime::DispatchResult { - let balance_due = Claims::<T>::get(&signer).ok_or(Error::<T>::SignerHasNoClaim)?; - - let new_total = - Total::<T>::get().checked_sub(&balance_due).ok_or(Error::<T>::PotUnderflow)?; - - let vesting = Vesting::<T>::get(&signer); - if vesting.is_some() && T::VestingSchedule::vesting_balance(&dest).is_some() { - return Err(Error::<T>::VestedBalanceExists.into()) - } - - // We first need to deposit the balance to ensure that the account exists. - let _ = CurrencyOf::<T>::deposit_creating(&dest, balance_due); - - // Check if this claim should have a vesting schedule. - if let Some(vs) = vesting { - // This can only fail if the account already has a vesting schedule, - // but this is checked above. - T::VestingSchedule::add_vesting_schedule(&dest, vs.0, vs.1, vs.2) - .expect("No other vesting schedule exists, as checked above; qed"); - } - - Total::<T>::put(new_total); - Claims::<T>::remove(&signer); - Vesting::<T>::remove(&signer); - Signing::<T>::remove(&signer); - - // Let's deposit an event to let the outside world know this happened. - Self::deposit_event(Event::<T>::Claimed { - who: dest, - ethereum_address: signer, - amount: balance_due, - }); - - Ok(()) - } -} - -/// Validate `attest` calls prior to execution. Needed to avoid a DoS attack since they are -/// otherwise free to place on chain. -#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] -#[scale_info(skip_type_params(T))] -pub struct PrevalidateAttests<T>(core::marker::PhantomData<fn(T)>); - -impl<T: Config> Debug for PrevalidateAttests<T> -where - <T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>>, -{ - #[cfg(feature = "std")] - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!(f, "PrevalidateAttests") - } - - #[cfg(not(feature = "std"))] - fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result { - Ok(()) - } -} - -impl<T: Config> PrevalidateAttests<T> -where - <T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>>, -{ - /// Create new `TransactionExtension` to check runtime version. - pub fn new() -> Self { - Self(core::marker::PhantomData) - } -} - -impl<T: Config> TransactionExtension<T::RuntimeCall> for PrevalidateAttests<T> -where - <T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>>, - <<T as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin: - AsSystemOriginSigner<T::AccountId> + AsTransactionAuthorizedOrigin + Clone, -{ - const IDENTIFIER: &'static str = "PrevalidateAttests"; - type Implicit = (); - type Pre = (); - type Val = (); - - fn weight(&self, call: &T::RuntimeCall) -> Weight { - if let Some(Call::attest { .. }) = call.is_sub_type() { - T::WeightInfo::prevalidate_attests() - } else { - Weight::zero() - } - } - - fn validate( - &self, - origin: <T::RuntimeCall as Dispatchable>::RuntimeOrigin, - call: &T::RuntimeCall, - _info: &DispatchInfoOf<T::RuntimeCall>, - _len: usize, - _self_implicit: Self::Implicit, - _inherited_implication: &impl Encode, - _source: TransactionSource, - ) -> Result< - (ValidTransaction, Self::Val, <T::RuntimeCall as Dispatchable>::RuntimeOrigin), - TransactionValidityError, - > { - if let Some(Call::attest { statement: attested_statement }) = call.is_sub_type() { - let who = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?; - let signer = Preclaims::<T>::get(who) - .ok_or(InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()))?; - if let Some(s) = Signing::<T>::get(signer) { - let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into()); - ensure!(&attested_statement[..] == s.to_text(), e); - } - } - Ok((ValidTransaction::default(), (), origin)) - } - - impl_tx_ext_default!(T::RuntimeCall; prepare); -} - -#[cfg(any(test, feature = "runtime-benchmarks"))] -mod secp_utils { - use super::*; - - pub fn public(secret: &libsecp256k1::SecretKey) -> libsecp256k1::PublicKey { - libsecp256k1::PublicKey::from_secret_key(secret) - } - pub fn eth(secret: &libsecp256k1::SecretKey) -> EthereumAddress { - let mut res = EthereumAddress::default(); - res.0.copy_from_slice(&keccak_256(&public(secret).serialize()[1..65])[12..]); - res - } - pub fn sig<T: Config>( - secret: &libsecp256k1::SecretKey, - what: &[u8], - extra: &[u8], - ) -> EcdsaSignature { - let msg = keccak_256(&super::Pallet::<T>::ethereum_signable_message( - &to_ascii_hex(what)[..], - extra, - )); - let (sig, recovery_id) = libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), secret); - let mut r = [0u8; 65]; - r[0..64].copy_from_slice(&sig.serialize()[..]); - r[64] = recovery_id.serialize(); - EcdsaSignature(r) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use hex_literal::hex; - use secp_utils::*; - use sp_runtime::transaction_validity::TransactionSource::External; - - use codec::Encode; - // The testing primitives are very useful for avoiding having to work with signatures - // or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. - use crate::claims; - use claims::Call as ClaimsCall; - use frame_support::{ - assert_err, assert_noop, assert_ok, derive_impl, - dispatch::{GetDispatchInfo, Pays}, - ord_parameter_types, parameter_types, - traits::{ExistenceRequirement, WithdrawReasons}, - }; - use pallet_balances; - use sp_runtime::{ - traits::{DispatchTransaction, Identity}, - transaction_validity::TransactionLongevity, - BuildStorage, - DispatchError::BadOrigin, - TokenError, - }; - - type Block = frame_system::mocking::MockBlock<Test>; - - frame_support::construct_runtime!( - pub enum Test - { - System: frame_system, - Balances: pallet_balances, - Vesting: pallet_vesting, - Claims: claims, - } - ); - - #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] - impl frame_system::Config for Test { - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type AccountData = pallet_balances::AccountData<u64>; - type MaxConsumers = frame_support::traits::ConstU32<16>; - } - - #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] - impl pallet_balances::Config for Test { - type AccountStore = System; - } - - parameter_types! { - pub const MinVestedTransfer: u64 = 1; - pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = - WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); - } - - impl pallet_vesting::Config for Test { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type BlockNumberToBalance = Identity; - type MinVestedTransfer = MinVestedTransfer; - type WeightInfo = (); - type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; - type BlockNumberProvider = System; - const MAX_VESTING_SCHEDULES: u32 = 28; - } - - parameter_types! { - pub Prefix: &'static [u8] = b"Pay RUSTs to the TEST account:"; - } - ord_parameter_types! { - pub const Six: u64 = 6; - } - - impl Config for Test { - type RuntimeEvent = RuntimeEvent; - type VestingSchedule = Vesting; - type Prefix = Prefix; - type MoveClaimOrigin = frame_system::EnsureSignedBy<Six, u64>; - type WeightInfo = TestWeightInfo; - } - - fn alice() -> libsecp256k1::SecretKey { - libsecp256k1::SecretKey::parse(&keccak_256(b"Alice")).unwrap() - } - fn bob() -> libsecp256k1::SecretKey { - libsecp256k1::SecretKey::parse(&keccak_256(b"Bob")).unwrap() - } - fn dave() -> libsecp256k1::SecretKey { - libsecp256k1::SecretKey::parse(&keccak_256(b"Dave")).unwrap() - } - fn eve() -> libsecp256k1::SecretKey { - libsecp256k1::SecretKey::parse(&keccak_256(b"Eve")).unwrap() - } - fn frank() -> libsecp256k1::SecretKey { - libsecp256k1::SecretKey::parse(&keccak_256(b"Frank")).unwrap() - } - - // This function basically just builds a genesis storage key/value store according to - // our desired mockup. - pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); - // We use default for brevity, but you can configure as desired if needed. - pallet_balances::GenesisConfig::<Test>::default() - .assimilate_storage(&mut t) - .unwrap(); - claims::GenesisConfig::<Test> { - claims: vec![ - (eth(&alice()), 100, None, None), - (eth(&dave()), 200, None, Some(StatementKind::Regular)), - (eth(&eve()), 300, Some(42), Some(StatementKind::Saft)), - (eth(&frank()), 400, Some(43), None), - ], - vesting: vec![(eth(&alice()), (50, 10, 1))], - } - .assimilate_storage(&mut t) - .unwrap(); - t.into() - } - - fn total_claims() -> u64 { - 100 + 200 + 300 + 400 - } - - #[test] - fn basic_setup_works() { - new_test_ext().execute_with(|| { - assert_eq!(claims::Total::<Test>::get(), total_claims()); - assert_eq!(claims::Claims::<Test>::get(ð(&alice())), Some(100)); - assert_eq!(claims::Claims::<Test>::get(ð(&dave())), Some(200)); - assert_eq!(claims::Claims::<Test>::get(ð(&eve())), Some(300)); - assert_eq!(claims::Claims::<Test>::get(ð(&frank())), Some(400)); - assert_eq!(claims::Claims::<Test>::get(&EthereumAddress::default()), None); - assert_eq!(claims::Vesting::<Test>::get(ð(&alice())), Some((50, 10, 1))); - }); - } - - #[test] - fn serde_works() { - let x = EthereumAddress(hex!["0123456789abcdef0123456789abcdef01234567"]); - let y = serde_json::to_string(&x).unwrap(); - assert_eq!(y, "\"0x0123456789abcdef0123456789abcdef01234567\""); - let z: EthereumAddress = serde_json::from_str(&y).unwrap(); - assert_eq!(x, z); - } - - #[test] - fn claiming_works() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - assert_ok!(Claims::claim( - RuntimeOrigin::none(), - 42, - sig::<Test>(&alice(), &42u64.encode(), &[][..]) - )); - assert_eq!(Balances::free_balance(&42), 100); - assert_eq!(Vesting::vesting_balance(&42), Some(50)); - assert_eq!(claims::Total::<Test>::get(), total_claims() - 100); - }); - } - - #[test] - fn basic_claim_moving_works() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - assert_noop!( - Claims::move_claim(RuntimeOrigin::signed(1), eth(&alice()), eth(&bob()), None), - BadOrigin - ); - assert_ok!(Claims::move_claim( - RuntimeOrigin::signed(6), - eth(&alice()), - eth(&bob()), - None - )); - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 42, - sig::<Test>(&alice(), &42u64.encode(), &[][..]) - ), - Error::<Test>::SignerHasNoClaim - ); - assert_ok!(Claims::claim( - RuntimeOrigin::none(), - 42, - sig::<Test>(&bob(), &42u64.encode(), &[][..]) - )); - assert_eq!(Balances::free_balance(&42), 100); - assert_eq!(Vesting::vesting_balance(&42), Some(50)); - assert_eq!(claims::Total::<Test>::get(), total_claims() - 100); - }); - } - - #[test] - fn claim_attest_moving_works() { - new_test_ext().execute_with(|| { - assert_ok!(Claims::move_claim( - RuntimeOrigin::signed(6), - eth(&dave()), - eth(&bob()), - None - )); - let s = sig::<Test>(&bob(), &42u64.encode(), StatementKind::Regular.to_text()); - assert_ok!(Claims::claim_attest( - RuntimeOrigin::none(), - 42, - s, - StatementKind::Regular.to_text().to_vec() - )); - assert_eq!(Balances::free_balance(&42), 200); - }); - } - - #[test] - fn attest_moving_works() { - new_test_ext().execute_with(|| { - assert_ok!(Claims::move_claim( - RuntimeOrigin::signed(6), - eth(&eve()), - eth(&bob()), - Some(42) - )); - assert_ok!(Claims::attest( - RuntimeOrigin::signed(42), - StatementKind::Saft.to_text().to_vec() - )); - assert_eq!(Balances::free_balance(&42), 300); - }); - } - - #[test] - fn claiming_does_not_bypass_signing() { - new_test_ext().execute_with(|| { - assert_ok!(Claims::claim( - RuntimeOrigin::none(), - 42, - sig::<Test>(&alice(), &42u64.encode(), &[][..]) - )); - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 42, - sig::<Test>(&dave(), &42u64.encode(), &[][..]) - ), - Error::<Test>::InvalidStatement, - ); - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 42, - sig::<Test>(&eve(), &42u64.encode(), &[][..]) - ), - Error::<Test>::InvalidStatement, - ); - assert_ok!(Claims::claim( - RuntimeOrigin::none(), - 42, - sig::<Test>(&frank(), &42u64.encode(), &[][..]) - )); - }); - } - - #[test] - fn attest_claiming_works() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - let s = sig::<Test>(&dave(), &42u64.encode(), StatementKind::Saft.to_text()); - let r = Claims::claim_attest( - RuntimeOrigin::none(), - 42, - s.clone(), - StatementKind::Saft.to_text().to_vec(), - ); - assert_noop!(r, Error::<Test>::InvalidStatement); - - let r = Claims::claim_attest( - RuntimeOrigin::none(), - 42, - s, - StatementKind::Regular.to_text().to_vec(), - ); - assert_noop!(r, Error::<Test>::SignerHasNoClaim); - // ^^^ we use ecdsa_recover, so an invalid signature just results in a random signer id - // being recovered, which realistically will never have a claim. - - let s = sig::<Test>(&dave(), &42u64.encode(), StatementKind::Regular.to_text()); - assert_ok!(Claims::claim_attest( - RuntimeOrigin::none(), - 42, - s, - StatementKind::Regular.to_text().to_vec() - )); - assert_eq!(Balances::free_balance(&42), 200); - assert_eq!(claims::Total::<Test>::get(), total_claims() - 200); - - let s = sig::<Test>(&dave(), &42u64.encode(), StatementKind::Regular.to_text()); - let r = Claims::claim_attest( - RuntimeOrigin::none(), - 42, - s, - StatementKind::Regular.to_text().to_vec(), - ); - assert_noop!(r, Error::<Test>::SignerHasNoClaim); - }); - } - - #[test] - fn attesting_works() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - assert_noop!( - Claims::attest(RuntimeOrigin::signed(69), StatementKind::Saft.to_text().to_vec()), - Error::<Test>::SenderHasNoClaim - ); - assert_noop!( - Claims::attest( - RuntimeOrigin::signed(42), - StatementKind::Regular.to_text().to_vec() - ), - Error::<Test>::InvalidStatement - ); - assert_ok!(Claims::attest( - RuntimeOrigin::signed(42), - StatementKind::Saft.to_text().to_vec() - )); - assert_eq!(Balances::free_balance(&42), 300); - assert_eq!(claims::Total::<Test>::get(), total_claims() - 300); - }); - } - - #[test] - fn claim_cannot_clobber_preclaim() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - // Alice's claim is 100 - assert_ok!(Claims::claim( - RuntimeOrigin::none(), - 42, - sig::<Test>(&alice(), &42u64.encode(), &[][..]) - )); - assert_eq!(Balances::free_balance(&42), 100); - // Eve's claim is 300 through Account 42 - assert_ok!(Claims::attest( - RuntimeOrigin::signed(42), - StatementKind::Saft.to_text().to_vec() - )); - assert_eq!(Balances::free_balance(&42), 100 + 300); - assert_eq!(claims::Total::<Test>::get(), total_claims() - 400); - }); - } - - #[test] - fn valid_attest_transactions_are_free() { - new_test_ext().execute_with(|| { - let p = PrevalidateAttests::<Test>::new(); - let c = RuntimeCall::Claims(ClaimsCall::attest { - statement: StatementKind::Saft.to_text().to_vec(), - }); - let di = c.get_dispatch_info(); - assert_eq!(di.pays_fee, Pays::No); - let r = p.validate_only(Some(42).into(), &c, &di, 20, External, 0); - assert_eq!(r.unwrap().0, ValidTransaction::default()); - }); - } - - #[test] - fn invalid_attest_transactions_are_recognized() { - new_test_ext().execute_with(|| { - let p = PrevalidateAttests::<Test>::new(); - let c = RuntimeCall::Claims(ClaimsCall::attest { - statement: StatementKind::Regular.to_text().to_vec(), - }); - let di = c.get_dispatch_info(); - let r = p.validate_only(Some(42).into(), &c, &di, 20, External, 0); - assert!(r.is_err()); - let c = RuntimeCall::Claims(ClaimsCall::attest { - statement: StatementKind::Saft.to_text().to_vec(), - }); - let di = c.get_dispatch_info(); - let r = p.validate_only(Some(69).into(), &c, &di, 20, External, 0); - assert!(r.is_err()); - }); - } - - #[test] - fn cannot_bypass_attest_claiming() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - let s = sig::<Test>(&dave(), &42u64.encode(), &[]); - let r = Claims::claim(RuntimeOrigin::none(), 42, s.clone()); - assert_noop!(r, Error::<Test>::InvalidStatement); - }); - } - - #[test] - fn add_claim_works() { - new_test_ext().execute_with(|| { - assert_noop!( - Claims::mint_claim(RuntimeOrigin::signed(42), eth(&bob()), 200, None, None), - sp_runtime::traits::BadOrigin, - ); - assert_eq!(Balances::free_balance(42), 0); - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 69, - sig::<Test>(&bob(), &69u64.encode(), &[][..]) - ), - Error::<Test>::SignerHasNoClaim, - ); - assert_ok!(Claims::mint_claim(RuntimeOrigin::root(), eth(&bob()), 200, None, None)); - assert_eq!(claims::Total::<Test>::get(), total_claims() + 200); - assert_ok!(Claims::claim( - RuntimeOrigin::none(), - 69, - sig::<Test>(&bob(), &69u64.encode(), &[][..]) - )); - assert_eq!(Balances::free_balance(&69), 200); - assert_eq!(Vesting::vesting_balance(&69), None); - assert_eq!(claims::Total::<Test>::get(), total_claims()); - }); - } - - #[test] - fn add_claim_with_vesting_works() { - new_test_ext().execute_with(|| { - assert_noop!( - Claims::mint_claim( - RuntimeOrigin::signed(42), - eth(&bob()), - 200, - Some((50, 10, 1)), - None - ), - sp_runtime::traits::BadOrigin, - ); - assert_eq!(Balances::free_balance(42), 0); - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 69, - sig::<Test>(&bob(), &69u64.encode(), &[][..]) - ), - Error::<Test>::SignerHasNoClaim, - ); - assert_ok!(Claims::mint_claim( - RuntimeOrigin::root(), - eth(&bob()), - 200, - Some((50, 10, 1)), - None - )); - assert_ok!(Claims::claim( - RuntimeOrigin::none(), - 69, - sig::<Test>(&bob(), &69u64.encode(), &[][..]) - )); - assert_eq!(Balances::free_balance(&69), 200); - assert_eq!(Vesting::vesting_balance(&69), Some(50)); - - // Make sure we can not transfer the vested balance. - assert_err!( - <Balances as Currency<_>>::transfer( - &69, - &80, - 180, - ExistenceRequirement::AllowDeath - ), - TokenError::Frozen, - ); - }); - } - - #[test] - fn add_claim_with_statement_works() { - new_test_ext().execute_with(|| { - assert_noop!( - Claims::mint_claim( - RuntimeOrigin::signed(42), - eth(&bob()), - 200, - None, - Some(StatementKind::Regular) - ), - sp_runtime::traits::BadOrigin, - ); - assert_eq!(Balances::free_balance(42), 0); - let signature = sig::<Test>(&bob(), &69u64.encode(), StatementKind::Regular.to_text()); - assert_noop!( - Claims::claim_attest( - RuntimeOrigin::none(), - 69, - signature.clone(), - StatementKind::Regular.to_text().to_vec() - ), - Error::<Test>::SignerHasNoClaim - ); - assert_ok!(Claims::mint_claim( - RuntimeOrigin::root(), - eth(&bob()), - 200, - None, - Some(StatementKind::Regular) - )); - assert_noop!( - Claims::claim_attest(RuntimeOrigin::none(), 69, signature.clone(), vec![],), - Error::<Test>::SignerHasNoClaim - ); - assert_ok!(Claims::claim_attest( - RuntimeOrigin::none(), - 69, - signature.clone(), - StatementKind::Regular.to_text().to_vec() - )); - assert_eq!(Balances::free_balance(&69), 200); - }); - } - - #[test] - fn origin_signed_claiming_fail() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - assert_err!( - Claims::claim( - RuntimeOrigin::signed(42), - 42, - sig::<Test>(&alice(), &42u64.encode(), &[][..]) - ), - sp_runtime::traits::BadOrigin, - ); - }); - } - - #[test] - fn double_claiming_doesnt_work() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - assert_ok!(Claims::claim( - RuntimeOrigin::none(), - 42, - sig::<Test>(&alice(), &42u64.encode(), &[][..]) - )); - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 42, - sig::<Test>(&alice(), &42u64.encode(), &[][..]) - ), - Error::<Test>::SignerHasNoClaim - ); - }); - } - - #[test] - fn claiming_while_vested_doesnt_work() { - new_test_ext().execute_with(|| { - CurrencyOf::<Test>::make_free_balance_be(&69, total_claims()); - assert_eq!(Balances::free_balance(69), total_claims()); - // A user is already vested - assert_ok!(<Test as Config>::VestingSchedule::add_vesting_schedule( - &69, - total_claims(), - 100, - 10 - )); - assert_ok!(Claims::mint_claim( - RuntimeOrigin::root(), - eth(&bob()), - 200, - Some((50, 10, 1)), - None - )); - // New total - assert_eq!(claims::Total::<Test>::get(), total_claims() + 200); - - // They should not be able to claim - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 69, - sig::<Test>(&bob(), &69u64.encode(), &[][..]) - ), - Error::<Test>::VestedBalanceExists, - ); - }); - } - - #[test] - fn non_sender_sig_doesnt_work() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 42, - sig::<Test>(&alice(), &69u64.encode(), &[][..]) - ), - Error::<Test>::SignerHasNoClaim - ); - }); - } - - #[test] - fn non_claimant_doesnt_work() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 42, - sig::<Test>(&bob(), &69u64.encode(), &[][..]) - ), - Error::<Test>::SignerHasNoClaim - ); - }); - } - - #[test] - fn real_eth_sig_works() { - new_test_ext().execute_with(|| { - // "Pay RUSTs to the TEST account:2a00000000000000" - let sig = hex!["444023e89b67e67c0562ed0305d252a5dd12b2af5ac51d6d3cb69a0b486bc4b3191401802dc29d26d586221f7256cd3329fe82174bdf659baea149a40e1c495d1c"]; - let sig = EcdsaSignature(sig); - let who = 42u64.using_encoded(to_ascii_hex); - let signer = Claims::eth_recover(&sig, &who, &[][..]).unwrap(); - assert_eq!(signer.0, hex!["6d31165d5d932d571f3b44695653b46dcc327e84"]); - }); - } - - #[test] - fn validate_unsigned_works() { - use sp_runtime::traits::ValidateUnsigned; - let source = sp_runtime::transaction_validity::TransactionSource::External; - - new_test_ext().execute_with(|| { - assert_eq!( - Pallet::<Test>::validate_unsigned( - source, - &ClaimsCall::claim { - dest: 1, - ethereum_signature: sig::<Test>(&alice(), &1u64.encode(), &[][..]) - } - ), - Ok(ValidTransaction { - priority: 100, - requires: vec![], - provides: vec![("claims", eth(&alice())).encode()], - longevity: TransactionLongevity::max_value(), - propagate: true, - }) - ); - assert_eq!( - Pallet::<Test>::validate_unsigned( - source, - &ClaimsCall::claim { dest: 0, ethereum_signature: EcdsaSignature([0; 65]) } - ), - InvalidTransaction::Custom(ValidityError::InvalidEthereumSignature.into()).into(), - ); - assert_eq!( - Pallet::<Test>::validate_unsigned( - source, - &ClaimsCall::claim { - dest: 1, - ethereum_signature: sig::<Test>(&bob(), &1u64.encode(), &[][..]) - } - ), - InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(), - ); - let s = sig::<Test>(&dave(), &1u64.encode(), StatementKind::Regular.to_text()); - let call = ClaimsCall::claim_attest { - dest: 1, - ethereum_signature: s, - statement: StatementKind::Regular.to_text().to_vec(), - }; - assert_eq!( - Pallet::<Test>::validate_unsigned(source, &call), - Ok(ValidTransaction { - priority: 100, - requires: vec![], - provides: vec![("claims", eth(&dave())).encode()], - longevity: TransactionLongevity::max_value(), - propagate: true, - }) - ); - assert_eq!( - Pallet::<Test>::validate_unsigned( - source, - &ClaimsCall::claim_attest { - dest: 1, - ethereum_signature: EcdsaSignature([0; 65]), - statement: StatementKind::Regular.to_text().to_vec() - } - ), - InvalidTransaction::Custom(ValidityError::InvalidEthereumSignature.into()).into(), - ); - - let s = sig::<Test>(&bob(), &1u64.encode(), StatementKind::Regular.to_text()); - let call = ClaimsCall::claim_attest { - dest: 1, - ethereum_signature: s, - statement: StatementKind::Regular.to_text().to_vec(), - }; - assert_eq!( - Pallet::<Test>::validate_unsigned(source, &call), - InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(), - ); - - let s = sig::<Test>(&dave(), &1u64.encode(), StatementKind::Saft.to_text()); - let call = ClaimsCall::claim_attest { - dest: 1, - ethereum_signature: s, - statement: StatementKind::Regular.to_text().to_vec(), - }; - assert_eq!( - Pallet::<Test>::validate_unsigned(source, &call), - InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(), - ); - - let s = sig::<Test>(&dave(), &1u64.encode(), StatementKind::Saft.to_text()); - let call = ClaimsCall::claim_attest { - dest: 1, - ethereum_signature: s, - statement: StatementKind::Saft.to_text().to_vec(), - }; - assert_eq!( - Pallet::<Test>::validate_unsigned(source, &call), - InvalidTransaction::Custom(ValidityError::InvalidStatement.into()).into(), - ); - }); - } -} - -#[cfg(feature = "runtime-benchmarks")] -mod benchmarking { - use super::*; - use crate::claims::Call; - use frame_benchmarking::v2::*; - use frame_support::{ - dispatch::{DispatchInfo, GetDispatchInfo}, - traits::UnfilteredDispatchable, - }; - use frame_system::RawOrigin; - use secp_utils::*; - use sp_runtime::{ - traits::{DispatchTransaction, ValidateUnsigned}, - DispatchResult, - }; - - const SEED: u32 = 0; - - const MAX_CLAIMS: u32 = 10_000; - const VALUE: u32 = 1_000_000; - - fn create_claim<T: Config>(input: u32) -> DispatchResult { - let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&input.encode())).unwrap(); - let eth_address = eth(&secret_key); - let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); - super::Pallet::<T>::mint_claim( - RawOrigin::Root.into(), - eth_address, - VALUE.into(), - vesting, - None, - )?; - Ok(()) - } - - fn create_claim_attest<T: Config>(input: u32) -> DispatchResult { - let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&input.encode())).unwrap(); - let eth_address = eth(&secret_key); - let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); - super::Pallet::<T>::mint_claim( - RawOrigin::Root.into(), - eth_address, - VALUE.into(), - vesting, - Some(Default::default()), - )?; - Ok(()) - } - - #[benchmarks( - where - <T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>> + From<Call<T>>, - <T as frame_system::Config>::RuntimeCall: Dispatchable<Info = DispatchInfo> + GetDispatchInfo, - <<T as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin: AsSystemOriginSigner<T::AccountId> + AsTransactionAuthorizedOrigin + Clone, - <<T as frame_system::Config>::RuntimeCall as Dispatchable>::PostInfo: Default, - )] - mod benchmarks { - use super::*; - - // Benchmark `claim` including `validate_unsigned` logic. - #[benchmark] - fn claim() -> Result<(), BenchmarkError> { - let c = MAX_CLAIMS; - for _ in 0..c / 2 { - create_claim::<T>(c)?; - create_claim_attest::<T>(u32::MAX - c)?; - } - let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&c.encode())).unwrap(); - let eth_address = eth(&secret_key); - let account: T::AccountId = account("user", c, SEED); - let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); - let signature = sig::<T>(&secret_key, &account.encode(), &[][..]); - super::Pallet::<T>::mint_claim( - RawOrigin::Root.into(), - eth_address, - VALUE.into(), - vesting, - None, - )?; - assert_eq!(Claims::<T>::get(eth_address), Some(VALUE.into())); - let source = sp_runtime::transaction_validity::TransactionSource::External; - let call_enc = - Call::<T>::claim { dest: account.clone(), ethereum_signature: signature.clone() } - .encode(); - - #[block] - { - let call = <Call<T> as Decode>::decode(&mut &*call_enc) - .expect("call is encoded above, encoding must be correct"); - super::Pallet::<T>::validate_unsigned(source, &call) - .map_err(|e| -> &'static str { e.into() })?; - call.dispatch_bypass_filter(RawOrigin::None.into())?; - } - - assert_eq!(Claims::<T>::get(eth_address), None); - Ok(()) - } - - // Benchmark `mint_claim` when there already exists `c` claims in storage. - #[benchmark] - fn mint_claim() -> Result<(), BenchmarkError> { - let c = MAX_CLAIMS; - for _ in 0..c / 2 { - create_claim::<T>(c)?; - create_claim_attest::<T>(u32::MAX - c)?; - } - let eth_address = account("eth_address", 0, SEED); - let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); - let statement = StatementKind::Regular; - - #[extrinsic_call] - _(RawOrigin::Root, eth_address, VALUE.into(), vesting, Some(statement)); - - assert_eq!(Claims::<T>::get(eth_address), Some(VALUE.into())); - Ok(()) - } - - // Benchmark `claim_attest` including `validate_unsigned` logic. - #[benchmark] - fn claim_attest() -> Result<(), BenchmarkError> { - let c = MAX_CLAIMS; - for _ in 0..c / 2 { - create_claim::<T>(c)?; - create_claim_attest::<T>(u32::MAX - c)?; - } - // Crate signature - let attest_c = u32::MAX - c; - let secret_key = - libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); - let eth_address = eth(&secret_key); - let account: T::AccountId = account("user", c, SEED); - let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); - let statement = StatementKind::Regular; - let signature = sig::<T>(&secret_key, &account.encode(), statement.to_text()); - super::Pallet::<T>::mint_claim( - RawOrigin::Root.into(), - eth_address, - VALUE.into(), - vesting, - Some(statement), - )?; - assert_eq!(Claims::<T>::get(eth_address), Some(VALUE.into())); - let call_enc = Call::<T>::claim_attest { - dest: account.clone(), - ethereum_signature: signature.clone(), - statement: StatementKind::Regular.to_text().to_vec(), - } - .encode(); - let source = sp_runtime::transaction_validity::TransactionSource::External; - - #[block] - { - let call = <Call<T> as Decode>::decode(&mut &*call_enc) - .expect("call is encoded above, encoding must be correct"); - super::Pallet::<T>::validate_unsigned(source, &call) - .map_err(|e| -> &'static str { e.into() })?; - call.dispatch_bypass_filter(RawOrigin::None.into())?; - } - - assert_eq!(Claims::<T>::get(eth_address), None); - Ok(()) - } - - // Benchmark `attest` including prevalidate logic. - #[benchmark] - fn attest() -> Result<(), BenchmarkError> { - let c = MAX_CLAIMS; - for _ in 0..c / 2 { - create_claim::<T>(c)?; - create_claim_attest::<T>(u32::MAX - c)?; - } - let attest_c = u32::MAX - c; - let secret_key = - libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); - let eth_address = eth(&secret_key); - let account: T::AccountId = account("user", c, SEED); - let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); - let statement = StatementKind::Regular; - super::Pallet::<T>::mint_claim( - RawOrigin::Root.into(), - eth_address, - VALUE.into(), - vesting, - Some(statement), - )?; - Preclaims::<T>::insert(&account, eth_address); - assert_eq!(Claims::<T>::get(eth_address), Some(VALUE.into())); - - let stmt = StatementKind::Regular.to_text().to_vec(); - - #[extrinsic_call] - _(RawOrigin::Signed(account), stmt); - - assert_eq!(Claims::<T>::get(eth_address), None); - Ok(()) - } - - #[benchmark] - fn move_claim() -> Result<(), BenchmarkError> { - let c = MAX_CLAIMS; - for _ in 0..c / 2 { - create_claim::<T>(c)?; - create_claim_attest::<T>(u32::MAX - c)?; - } - let attest_c = u32::MAX - c; - let secret_key = - libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); - let eth_address = eth(&secret_key); - - let new_secret_key = - libsecp256k1::SecretKey::parse(&keccak_256(&(u32::MAX / 2).encode())).unwrap(); - let new_eth_address = eth(&new_secret_key); - - let account: T::AccountId = account("user", c, SEED); - Preclaims::<T>::insert(&account, eth_address); - - assert!(Claims::<T>::contains_key(eth_address)); - assert!(!Claims::<T>::contains_key(new_eth_address)); - - #[extrinsic_call] - _(RawOrigin::Root, eth_address, new_eth_address, Some(account)); - - assert!(!Claims::<T>::contains_key(eth_address)); - assert!(Claims::<T>::contains_key(new_eth_address)); - Ok(()) - } - - // Benchmark the time it takes to do `repeat` number of keccak256 hashes - #[benchmark(extra)] - fn keccak256(i: Linear<0, 10_000>) { - let bytes = (i).encode(); - - #[block] - { - for _ in 0..i { - let _hash = keccak_256(&bytes); - } - } - } - - // Benchmark the time it takes to do `repeat` number of `eth_recover` - #[benchmark(extra)] - fn eth_recover(i: Linear<0, 1_000>) { - // Crate signature - let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&i.encode())).unwrap(); - let account: T::AccountId = account("user", i, SEED); - let signature = sig::<T>(&secret_key, &account.encode(), &[][..]); - let data = account.using_encoded(to_ascii_hex); - let extra = StatementKind::default().to_text(); - - #[block] - { - for _ in 0..i { - assert!(super::Pallet::<T>::eth_recover(&signature, &data, extra).is_some()); - } - } - } - - #[benchmark] - fn prevalidate_attests() -> Result<(), BenchmarkError> { - let c = MAX_CLAIMS; - for _ in 0..c / 2 { - create_claim::<T>(c)?; - create_claim_attest::<T>(u32::MAX - c)?; - } - let ext = PrevalidateAttests::<T>::new(); - let call = super::Call::attest { statement: StatementKind::Regular.to_text().to_vec() }; - let call: <T as frame_system::Config>::RuntimeCall = call.into(); - let info = call.get_dispatch_info(); - let attest_c = u32::MAX - c; - let secret_key = - libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); - let eth_address = eth(&secret_key); - let account: T::AccountId = account("user", c, SEED); - let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); - let statement = StatementKind::Regular; - super::Pallet::<T>::mint_claim( - RawOrigin::Root.into(), - eth_address, - VALUE.into(), - vesting, - Some(statement), - )?; - Preclaims::<T>::insert(&account, eth_address); - assert_eq!(Claims::<T>::get(eth_address), Some(VALUE.into())); - - #[block] - { - assert!(ext - .test_run(RawOrigin::Signed(account).into(), &call, &info, 0, 0, |_| { - Ok(Default::default()) - }) - .unwrap() - .is_ok()); - } - - Ok(()) - } - - impl_benchmark_test_suite!( - Pallet, - crate::claims::tests::new_test_ext(), - crate::claims::tests::Test, - ); - } -} diff --git a/polkadot/runtime/common/src/claims/benchmarking.rs b/polkadot/runtime/common/src/claims/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..f9150f7980e506f8d3798325381e6f15fef44764 --- /dev/null +++ b/polkadot/runtime/common/src/claims/benchmarking.rs @@ -0,0 +1,318 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Benchmarking for claims pallet + +#[cfg(feature = "runtime-benchmarks")] +use super::*; +use crate::claims::Call; +use frame_benchmarking::v2::*; +use frame_support::{ + dispatch::{DispatchInfo, GetDispatchInfo}, + traits::UnfilteredDispatchable, +}; +use frame_system::RawOrigin; +use secp_utils::*; +use sp_runtime::{ + traits::{DispatchTransaction, ValidateUnsigned}, + DispatchResult, +}; + +const SEED: u32 = 0; + +const MAX_CLAIMS: u32 = 10_000; +const VALUE: u32 = 1_000_000; + +fn create_claim<T: Config>(input: u32) -> DispatchResult { + let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&input.encode())).unwrap(); + let eth_address = eth(&secret_key); + let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); + super::Pallet::<T>::mint_claim( + RawOrigin::Root.into(), + eth_address, + VALUE.into(), + vesting, + None, + )?; + Ok(()) +} + +fn create_claim_attest<T: Config>(input: u32) -> DispatchResult { + let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&input.encode())).unwrap(); + let eth_address = eth(&secret_key); + let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); + super::Pallet::<T>::mint_claim( + RawOrigin::Root.into(), + eth_address, + VALUE.into(), + vesting, + Some(Default::default()), + )?; + Ok(()) +} + +#[benchmarks( + where + <T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>> + From<Call<T>>, + <T as frame_system::Config>::RuntimeCall: Dispatchable<Info = DispatchInfo> + GetDispatchInfo, + <<T as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin: AsSystemOriginSigner<T::AccountId> + AsTransactionAuthorizedOrigin + Clone, + <<T as frame_system::Config>::RuntimeCall as Dispatchable>::PostInfo: Default, + )] +mod benchmarks { + use super::*; + + // Benchmark `claim` including `validate_unsigned` logic. + #[benchmark] + fn claim() -> Result<(), BenchmarkError> { + let c = MAX_CLAIMS; + for _ in 0..c / 2 { + create_claim::<T>(c)?; + create_claim_attest::<T>(u32::MAX - c)?; + } + let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&c.encode())).unwrap(); + let eth_address = eth(&secret_key); + let account: T::AccountId = account("user", c, SEED); + let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); + let signature = sig::<T>(&secret_key, &account.encode(), &[][..]); + super::Pallet::<T>::mint_claim( + RawOrigin::Root.into(), + eth_address, + VALUE.into(), + vesting, + None, + )?; + assert_eq!(Claims::<T>::get(eth_address), Some(VALUE.into())); + let source = sp_runtime::transaction_validity::TransactionSource::External; + let call_enc = + Call::<T>::claim { dest: account.clone(), ethereum_signature: signature.clone() } + .encode(); + + #[block] + { + let call = <Call<T> as Decode>::decode(&mut &*call_enc) + .expect("call is encoded above, encoding must be correct"); + super::Pallet::<T>::validate_unsigned(source, &call) + .map_err(|e| -> &'static str { e.into() })?; + call.dispatch_bypass_filter(RawOrigin::None.into())?; + } + + assert_eq!(Claims::<T>::get(eth_address), None); + Ok(()) + } + + // Benchmark `mint_claim` when there already exists `c` claims in storage. + #[benchmark] + fn mint_claim() -> Result<(), BenchmarkError> { + let c = MAX_CLAIMS; + for _ in 0..c / 2 { + create_claim::<T>(c)?; + create_claim_attest::<T>(u32::MAX - c)?; + } + let eth_address = account("eth_address", 0, SEED); + let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); + let statement = StatementKind::Regular; + + #[extrinsic_call] + _(RawOrigin::Root, eth_address, VALUE.into(), vesting, Some(statement)); + + assert_eq!(Claims::<T>::get(eth_address), Some(VALUE.into())); + Ok(()) + } + + // Benchmark `claim_attest` including `validate_unsigned` logic. + #[benchmark] + fn claim_attest() -> Result<(), BenchmarkError> { + let c = MAX_CLAIMS; + for _ in 0..c / 2 { + create_claim::<T>(c)?; + create_claim_attest::<T>(u32::MAX - c)?; + } + // Crate signature + let attest_c = u32::MAX - c; + let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); + let eth_address = eth(&secret_key); + let account: T::AccountId = account("user", c, SEED); + let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); + let statement = StatementKind::Regular; + let signature = sig::<T>(&secret_key, &account.encode(), statement.to_text()); + super::Pallet::<T>::mint_claim( + RawOrigin::Root.into(), + eth_address, + VALUE.into(), + vesting, + Some(statement), + )?; + assert_eq!(Claims::<T>::get(eth_address), Some(VALUE.into())); + let call_enc = Call::<T>::claim_attest { + dest: account.clone(), + ethereum_signature: signature.clone(), + statement: StatementKind::Regular.to_text().to_vec(), + } + .encode(); + let source = sp_runtime::transaction_validity::TransactionSource::External; + + #[block] + { + let call = <Call<T> as Decode>::decode(&mut &*call_enc) + .expect("call is encoded above, encoding must be correct"); + super::Pallet::<T>::validate_unsigned(source, &call) + .map_err(|e| -> &'static str { e.into() })?; + call.dispatch_bypass_filter(RawOrigin::None.into())?; + } + + assert_eq!(Claims::<T>::get(eth_address), None); + Ok(()) + } + + // Benchmark `attest` including prevalidate logic. + #[benchmark] + fn attest() -> Result<(), BenchmarkError> { + let c = MAX_CLAIMS; + for _ in 0..c / 2 { + create_claim::<T>(c)?; + create_claim_attest::<T>(u32::MAX - c)?; + } + let attest_c = u32::MAX - c; + let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); + let eth_address = eth(&secret_key); + let account: T::AccountId = account("user", c, SEED); + let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); + let statement = StatementKind::Regular; + super::Pallet::<T>::mint_claim( + RawOrigin::Root.into(), + eth_address, + VALUE.into(), + vesting, + Some(statement), + )?; + Preclaims::<T>::insert(&account, eth_address); + assert_eq!(Claims::<T>::get(eth_address), Some(VALUE.into())); + + let stmt = StatementKind::Regular.to_text().to_vec(); + + #[extrinsic_call] + _(RawOrigin::Signed(account), stmt); + + assert_eq!(Claims::<T>::get(eth_address), None); + Ok(()) + } + + #[benchmark] + fn move_claim() -> Result<(), BenchmarkError> { + let c = MAX_CLAIMS; + for _ in 0..c / 2 { + create_claim::<T>(c)?; + create_claim_attest::<T>(u32::MAX - c)?; + } + let attest_c = u32::MAX - c; + let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); + let eth_address = eth(&secret_key); + + let new_secret_key = + libsecp256k1::SecretKey::parse(&keccak_256(&(u32::MAX / 2).encode())).unwrap(); + let new_eth_address = eth(&new_secret_key); + + let account: T::AccountId = account("user", c, SEED); + Preclaims::<T>::insert(&account, eth_address); + + assert!(Claims::<T>::contains_key(eth_address)); + assert!(!Claims::<T>::contains_key(new_eth_address)); + + #[extrinsic_call] + _(RawOrigin::Root, eth_address, new_eth_address, Some(account)); + + assert!(!Claims::<T>::contains_key(eth_address)); + assert!(Claims::<T>::contains_key(new_eth_address)); + Ok(()) + } + + // Benchmark the time it takes to do `repeat` number of keccak256 hashes + #[benchmark(extra)] + fn keccak256(i: Linear<0, 10_000>) { + let bytes = (i).encode(); + + #[block] + { + for _ in 0..i { + let _hash = keccak_256(&bytes); + } + } + } + + // Benchmark the time it takes to do `repeat` number of `eth_recover` + #[benchmark(extra)] + fn eth_recover(i: Linear<0, 1_000>) { + // Crate signature + let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&i.encode())).unwrap(); + let account: T::AccountId = account("user", i, SEED); + let signature = sig::<T>(&secret_key, &account.encode(), &[][..]); + let data = account.using_encoded(to_ascii_hex); + let extra = StatementKind::default().to_text(); + + #[block] + { + for _ in 0..i { + assert!(super::Pallet::<T>::eth_recover(&signature, &data, extra).is_some()); + } + } + } + + #[benchmark] + fn prevalidate_attests() -> Result<(), BenchmarkError> { + let c = MAX_CLAIMS; + for _ in 0..c / 2 { + create_claim::<T>(c)?; + create_claim_attest::<T>(u32::MAX - c)?; + } + let ext = PrevalidateAttests::<T>::new(); + let call = super::Call::attest { statement: StatementKind::Regular.to_text().to_vec() }; + let call: <T as frame_system::Config>::RuntimeCall = call.into(); + let info = call.get_dispatch_info(); + let attest_c = u32::MAX - c; + let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); + let eth_address = eth(&secret_key); + let account: T::AccountId = account("user", c, SEED); + let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); + let statement = StatementKind::Regular; + super::Pallet::<T>::mint_claim( + RawOrigin::Root.into(), + eth_address, + VALUE.into(), + vesting, + Some(statement), + )?; + Preclaims::<T>::insert(&account, eth_address); + assert_eq!(Claims::<T>::get(eth_address), Some(VALUE.into())); + + #[block] + { + assert!(ext + .test_run(RawOrigin::Signed(account).into(), &call, &info, 0, 0, |_| { + Ok(Default::default()) + }) + .unwrap() + .is_ok()); + } + + Ok(()) + } + + impl_benchmark_test_suite!( + Pallet, + crate::claims::mock::new_test_ext(), + crate::claims::mock::Test, + ); +} diff --git a/polkadot/runtime/common/src/claims/mock.rs b/polkadot/runtime/common/src/claims/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..640df6ec6a8abc694e4db4f8d7aa14647e673bec --- /dev/null +++ b/polkadot/runtime/common/src/claims/mock.rs @@ -0,0 +1,129 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Mocking utilities for testing in claims pallet. + +#[cfg(test)] +use super::*; +use secp_utils::*; + +// The testing primitives are very useful for avoiding having to work with signatures +// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. +use crate::claims; +use frame_support::{derive_impl, ord_parameter_types, parameter_types, traits::WithdrawReasons}; +use pallet_balances; +use sp_runtime::{traits::Identity, BuildStorage}; + +type Block = frame_system::mocking::MockBlock<Test>; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Vesting: pallet_vesting, + Claims: claims, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type AccountData = pallet_balances::AccountData<u64>; + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type AccountStore = System; +} + +parameter_types! { + pub const MinVestedTransfer: u64 = 1; + pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = + WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); +} + +impl pallet_vesting::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type BlockNumberToBalance = Identity; + type MinVestedTransfer = MinVestedTransfer; + type WeightInfo = (); + type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; + const MAX_VESTING_SCHEDULES: u32 = 28; +} + +parameter_types! { + pub Prefix: &'static [u8] = b"Pay RUSTs to the TEST account:"; +} +ord_parameter_types! { + pub const Six: u64 = 6; +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type VestingSchedule = Vesting; + type Prefix = Prefix; + type MoveClaimOrigin = frame_system::EnsureSignedBy<Six, u64>; + type WeightInfo = TestWeightInfo; +} + +pub fn alice() -> libsecp256k1::SecretKey { + libsecp256k1::SecretKey::parse(&keccak_256(b"Alice")).unwrap() +} +pub fn bob() -> libsecp256k1::SecretKey { + libsecp256k1::SecretKey::parse(&keccak_256(b"Bob")).unwrap() +} +pub fn dave() -> libsecp256k1::SecretKey { + libsecp256k1::SecretKey::parse(&keccak_256(b"Dave")).unwrap() +} +pub fn eve() -> libsecp256k1::SecretKey { + libsecp256k1::SecretKey::parse(&keccak_256(b"Eve")).unwrap() +} +pub fn frank() -> libsecp256k1::SecretKey { + libsecp256k1::SecretKey::parse(&keccak_256(b"Frank")).unwrap() +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); + // We use default for brevity, but you can configure as desired if needed. + pallet_balances::GenesisConfig::<Test>::default() + .assimilate_storage(&mut t) + .unwrap(); + claims::GenesisConfig::<Test> { + claims: vec![ + (eth(&alice()), 100, None, None), + (eth(&dave()), 200, None, Some(StatementKind::Regular)), + (eth(&eve()), 300, Some(42), Some(StatementKind::Saft)), + (eth(&frank()), 400, Some(43), None), + ], + vesting: vec![(eth(&alice()), (50, 10, 1))], + } + .assimilate_storage(&mut t) + .unwrap(); + t.into() +} + +pub fn total_claims() -> u64 { + 100 + 200 + 300 + 400 +} diff --git a/polkadot/runtime/common/src/claims/mod.rs b/polkadot/runtime/common/src/claims/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..f48e40ee188789f91733cab5f6486fcc94760461 --- /dev/null +++ b/polkadot/runtime/common/src/claims/mod.rs @@ -0,0 +1,723 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see <http://www.gnu.org/licenses/>. + +//! Pallet to process claims from Ethereum addresses. + +#[cfg(not(feature = "std"))] +use alloc::{format, string::String}; +use alloc::{vec, vec::Vec}; +use codec::{Decode, Encode, MaxEncodedLen}; +use core::fmt::Debug; +use frame_support::{ + ensure, + traits::{Currency, Get, IsSubType, VestingSchedule}, + weights::Weight, + DefaultNoBound, +}; +pub use pallet::*; +use polkadot_primitives::ValidityError; +use scale_info::TypeInfo; +use serde::{self, Deserialize, Deserializer, Serialize, Serializer}; +use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256}; +use sp_runtime::{ + impl_tx_ext_default, + traits::{ + AsSystemOriginSigner, AsTransactionAuthorizedOrigin, CheckedSub, DispatchInfoOf, + Dispatchable, TransactionExtension, Zero, + }, + transaction_validity::{ + InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError, + ValidTransaction, + }, + RuntimeDebug, +}; + +type CurrencyOf<T> = <<T as Config>::VestingSchedule as VestingSchedule< + <T as frame_system::Config>::AccountId, +>>::Currency; +type BalanceOf<T> = <CurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::Balance; + +pub trait WeightInfo { + fn claim() -> Weight; + fn mint_claim() -> Weight; + fn claim_attest() -> Weight; + fn attest() -> Weight; + fn move_claim() -> Weight; + fn prevalidate_attests() -> Weight; +} + +pub struct TestWeightInfo; +impl WeightInfo for TestWeightInfo { + fn claim() -> Weight { + Weight::zero() + } + fn mint_claim() -> Weight { + Weight::zero() + } + fn claim_attest() -> Weight { + Weight::zero() + } + fn attest() -> Weight { + Weight::zero() + } + fn move_claim() -> Weight { + Weight::zero() + } + fn prevalidate_attests() -> Weight { + Weight::zero() + } +} + +/// The kind of statement an account needs to make for a claim to be valid. +#[derive( + Encode, + Decode, + Clone, + Copy, + Eq, + PartialEq, + RuntimeDebug, + TypeInfo, + Serialize, + Deserialize, + MaxEncodedLen, +)] +pub enum StatementKind { + /// Statement required to be made by non-SAFT holders. + Regular, + /// Statement required to be made by SAFT holders. + Saft, +} + +impl StatementKind { + /// Convert this to the (English) statement it represents. + fn to_text(self) -> &'static [u8] { + match self { + StatementKind::Regular => + &b"I hereby agree to the terms of the statement whose SHA-256 multihash is \ + Qmc1XYqT6S39WNp2UeiRUrZichUWUPpGEThDE6dAb3f6Ny. (This may be found at the URL: \ + https://statement.polkadot.network/regular.html)"[..], + StatementKind::Saft => + &b"I hereby agree to the terms of the statement whose SHA-256 multihash is \ + QmXEkMahfhHJPzT3RjkXiZVFi77ZeVeuxtAjhojGRNYckz. (This may be found at the URL: \ + https://statement.polkadot.network/saft.html)"[..], + } + } +} + +impl Default for StatementKind { + fn default() -> Self { + StatementKind::Regular + } +} + +/// An Ethereum address (i.e. 20 bytes, used to represent an Ethereum account). +/// +/// This gets serialized to the 0x-prefixed hex representation. +#[derive( + Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen, +)] +pub struct EthereumAddress([u8; 20]); + +impl Serialize for EthereumAddress { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let hex: String = rustc_hex::ToHex::to_hex(&self.0[..]); + serializer.serialize_str(&format!("0x{}", hex)) + } +} + +impl<'de> Deserialize<'de> for EthereumAddress { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let base_string = String::deserialize(deserializer)?; + let offset = if base_string.starts_with("0x") { 2 } else { 0 }; + let s = &base_string[offset..]; + if s.len() != 40 { + Err(serde::de::Error::custom( + "Bad length of Ethereum address (should be 42 including '0x')", + ))?; + } + let raw: Vec<u8> = rustc_hex::FromHex::from_hex(s) + .map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?; + let mut r = Self::default(); + r.0.copy_from_slice(&raw); + Ok(r) + } +} + +#[derive(Encode, Decode, Clone, TypeInfo, MaxEncodedLen)] +pub struct EcdsaSignature(pub [u8; 65]); + +impl PartialEq for EcdsaSignature { + fn eq(&self, other: &Self) -> bool { + &self.0[..] == &other.0[..] + } +} + +impl core::fmt::Debug for EcdsaSignature { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "EcdsaSignature({:?})", &self.0[..]) + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet<T>(_); + + /// Configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; + type VestingSchedule: VestingSchedule<Self::AccountId, Moment = BlockNumberFor<Self>>; + #[pallet::constant] + type Prefix: Get<&'static [u8]>; + type MoveClaimOrigin: EnsureOrigin<Self::RuntimeOrigin>; + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event<T: Config> { + /// Someone claimed some DOTs. + Claimed { who: T::AccountId, ethereum_address: EthereumAddress, amount: BalanceOf<T> }, + } + + #[pallet::error] + pub enum Error<T> { + /// Invalid Ethereum signature. + InvalidEthereumSignature, + /// Ethereum address has no claim. + SignerHasNoClaim, + /// Account ID sending transaction has no claim. + SenderHasNoClaim, + /// There's not enough in the pot to pay out some unvested amount. Generally implies a + /// logic error. + PotUnderflow, + /// A needed statement was not included. + InvalidStatement, + /// The account already has a vested balance. + VestedBalanceExists, + } + + #[pallet::storage] + pub type Claims<T: Config> = StorageMap<_, Identity, EthereumAddress, BalanceOf<T>>; + + #[pallet::storage] + pub type Total<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>; + + /// Vesting schedule for a claim. + /// First balance is the total amount that should be held for vesting. + /// Second balance is how much should be unlocked per block. + /// The block number is when the vesting should start. + #[pallet::storage] + pub type Vesting<T: Config> = + StorageMap<_, Identity, EthereumAddress, (BalanceOf<T>, BalanceOf<T>, BlockNumberFor<T>)>; + + /// The statement kind that must be signed, if any. + #[pallet::storage] + pub(super) type Signing<T> = StorageMap<_, Identity, EthereumAddress, StatementKind>; + + /// Pre-claimed Ethereum accounts, by the Account ID that they are claimed to. + #[pallet::storage] + pub(super) type Preclaims<T: Config> = StorageMap<_, Identity, T::AccountId, EthereumAddress>; + + #[pallet::genesis_config] + #[derive(DefaultNoBound)] + pub struct GenesisConfig<T: Config> { + pub claims: + Vec<(EthereumAddress, BalanceOf<T>, Option<T::AccountId>, Option<StatementKind>)>, + pub vesting: Vec<(EthereumAddress, (BalanceOf<T>, BalanceOf<T>, BlockNumberFor<T>))>, + } + + #[pallet::genesis_build] + impl<T: Config> BuildGenesisConfig for GenesisConfig<T> { + fn build(&self) { + // build `Claims` + self.claims.iter().map(|(a, b, _, _)| (*a, *b)).for_each(|(a, b)| { + Claims::<T>::insert(a, b); + }); + // build `Total` + Total::<T>::put( + self.claims + .iter() + .fold(Zero::zero(), |acc: BalanceOf<T>, &(_, b, _, _)| acc + b), + ); + // build `Vesting` + self.vesting.iter().for_each(|(k, v)| { + Vesting::<T>::insert(k, v); + }); + // build `Signing` + self.claims + .iter() + .filter_map(|(a, _, _, s)| Some((*a, (*s)?))) + .for_each(|(a, s)| { + Signing::<T>::insert(a, s); + }); + // build `Preclaims` + self.claims.iter().filter_map(|(a, _, i, _)| Some((i.clone()?, *a))).for_each( + |(i, a)| { + Preclaims::<T>::insert(i, a); + }, + ); + } + } + + #[pallet::hooks] + impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {} + + #[pallet::call] + impl<T: Config> Pallet<T> { + /// Make a claim to collect your DOTs. + /// + /// The dispatch origin for this call must be _None_. + /// + /// Unsigned Validation: + /// A call to claim is deemed valid if the signature provided matches + /// the expected signed message of: + /// + /// > Ethereum Signed Message: + /// > (configured prefix string)(address) + /// + /// and `address` matches the `dest` account. + /// + /// Parameters: + /// - `dest`: The destination account to payout the claim. + /// - `ethereum_signature`: The signature of an ethereum signed message matching the format + /// described above. + /// + /// <weight> + /// The weight of this call is invariant over the input parameters. + /// Weight includes logic to validate unsigned `claim` call. + /// + /// Total Complexity: O(1) + /// </weight> + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::claim())] + pub fn claim( + origin: OriginFor<T>, + dest: T::AccountId, + ethereum_signature: EcdsaSignature, + ) -> DispatchResult { + ensure_none(origin)?; + + let data = dest.using_encoded(to_ascii_hex); + let signer = Self::eth_recover(ðereum_signature, &data, &[][..]) + .ok_or(Error::<T>::InvalidEthereumSignature)?; + ensure!(Signing::<T>::get(&signer).is_none(), Error::<T>::InvalidStatement); + + Self::process_claim(signer, dest)?; + Ok(()) + } + + /// Mint a new claim to collect DOTs. + /// + /// The dispatch origin for this call must be _Root_. + /// + /// Parameters: + /// - `who`: The Ethereum address allowed to collect this claim. + /// - `value`: The number of DOTs that will be claimed. + /// - `vesting_schedule`: An optional vesting schedule for these DOTs. + /// + /// <weight> + /// The weight of this call is invariant over the input parameters. + /// We assume worst case that both vesting and statement is being inserted. + /// + /// Total Complexity: O(1) + /// </weight> + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::mint_claim())] + pub fn mint_claim( + origin: OriginFor<T>, + who: EthereumAddress, + value: BalanceOf<T>, + vesting_schedule: Option<(BalanceOf<T>, BalanceOf<T>, BlockNumberFor<T>)>, + statement: Option<StatementKind>, + ) -> DispatchResult { + ensure_root(origin)?; + + Total::<T>::mutate(|t| *t += value); + Claims::<T>::insert(who, value); + if let Some(vs) = vesting_schedule { + Vesting::<T>::insert(who, vs); + } + if let Some(s) = statement { + Signing::<T>::insert(who, s); + } + Ok(()) + } + + /// Make a claim to collect your DOTs by signing a statement. + /// + /// The dispatch origin for this call must be _None_. + /// + /// Unsigned Validation: + /// A call to `claim_attest` is deemed valid if the signature provided matches + /// the expected signed message of: + /// + /// > Ethereum Signed Message: + /// > (configured prefix string)(address)(statement) + /// + /// and `address` matches the `dest` account; the `statement` must match that which is + /// expected according to your purchase arrangement. + /// + /// Parameters: + /// - `dest`: The destination account to payout the claim. + /// - `ethereum_signature`: The signature of an ethereum signed message matching the format + /// described above. + /// - `statement`: The identity of the statement which is being attested to in the + /// signature. + /// + /// <weight> + /// The weight of this call is invariant over the input parameters. + /// Weight includes logic to validate unsigned `claim_attest` call. + /// + /// Total Complexity: O(1) + /// </weight> + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::claim_attest())] + pub fn claim_attest( + origin: OriginFor<T>, + dest: T::AccountId, + ethereum_signature: EcdsaSignature, + statement: Vec<u8>, + ) -> DispatchResult { + ensure_none(origin)?; + + let data = dest.using_encoded(to_ascii_hex); + let signer = Self::eth_recover(ðereum_signature, &data, &statement) + .ok_or(Error::<T>::InvalidEthereumSignature)?; + if let Some(s) = Signing::<T>::get(signer) { + ensure!(s.to_text() == &statement[..], Error::<T>::InvalidStatement); + } + Self::process_claim(signer, dest)?; + Ok(()) + } + + /// Attest to a statement, needed to finalize the claims process. + /// + /// WARNING: Insecure unless your chain includes `PrevalidateAttests` as a + /// `TransactionExtension`. + /// + /// Unsigned Validation: + /// A call to attest is deemed valid if the sender has a `Preclaim` registered + /// and provides a `statement` which is expected for the account. + /// + /// Parameters: + /// - `statement`: The identity of the statement which is being attested to in the + /// signature. + /// + /// <weight> + /// The weight of this call is invariant over the input parameters. + /// Weight includes logic to do pre-validation on `attest` call. + /// + /// Total Complexity: O(1) + /// </weight> + #[pallet::call_index(3)] + #[pallet::weight(( + T::WeightInfo::attest(), + DispatchClass::Normal, + Pays::No + ))] + pub fn attest(origin: OriginFor<T>, statement: Vec<u8>) -> DispatchResult { + let who = ensure_signed(origin)?; + let signer = Preclaims::<T>::get(&who).ok_or(Error::<T>::SenderHasNoClaim)?; + if let Some(s) = Signing::<T>::get(signer) { + ensure!(s.to_text() == &statement[..], Error::<T>::InvalidStatement); + } + Self::process_claim(signer, who.clone())?; + Preclaims::<T>::remove(&who); + Ok(()) + } + + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::move_claim())] + pub fn move_claim( + origin: OriginFor<T>, + old: EthereumAddress, + new: EthereumAddress, + maybe_preclaim: Option<T::AccountId>, + ) -> DispatchResultWithPostInfo { + T::MoveClaimOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?; + + Claims::<T>::take(&old).map(|c| Claims::<T>::insert(&new, c)); + Vesting::<T>::take(&old).map(|c| Vesting::<T>::insert(&new, c)); + Signing::<T>::take(&old).map(|c| Signing::<T>::insert(&new, c)); + maybe_preclaim.map(|preclaim| { + Preclaims::<T>::mutate(&preclaim, |maybe_o| { + if maybe_o.as_ref().map_or(false, |o| o == &old) { + *maybe_o = Some(new) + } + }) + }); + Ok(Pays::No.into()) + } + } + + #[pallet::validate_unsigned] + impl<T: Config> ValidateUnsigned for Pallet<T> { + type Call = Call<T>; + + fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { + const PRIORITY: u64 = 100; + + let (maybe_signer, maybe_statement) = match call { + // <weight> + // The weight of this logic is included in the `claim` dispatchable. + // </weight> + Call::claim { dest: account, ethereum_signature } => { + let data = account.using_encoded(to_ascii_hex); + (Self::eth_recover(ðereum_signature, &data, &[][..]), None) + }, + // <weight> + // The weight of this logic is included in the `claim_attest` dispatchable. + // </weight> + Call::claim_attest { dest: account, ethereum_signature, statement } => { + let data = account.using_encoded(to_ascii_hex); + ( + Self::eth_recover(ðereum_signature, &data, &statement), + Some(statement.as_slice()), + ) + }, + _ => return Err(InvalidTransaction::Call.into()), + }; + + let signer = maybe_signer.ok_or(InvalidTransaction::Custom( + ValidityError::InvalidEthereumSignature.into(), + ))?; + + let e = InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()); + ensure!(Claims::<T>::contains_key(&signer), e); + + let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into()); + match Signing::<T>::get(signer) { + None => ensure!(maybe_statement.is_none(), e), + Some(s) => ensure!(Some(s.to_text()) == maybe_statement, e), + } + + Ok(ValidTransaction { + priority: PRIORITY, + requires: vec![], + provides: vec![("claims", signer).encode()], + longevity: TransactionLongevity::max_value(), + propagate: true, + }) + } + } +} + +/// Converts the given binary data into ASCII-encoded hex. It will be twice the length. +fn to_ascii_hex(data: &[u8]) -> Vec<u8> { + let mut r = Vec::with_capacity(data.len() * 2); + let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n }); + for &b in data.iter() { + push_nibble(b / 16); + push_nibble(b % 16); + } + r +} + +impl<T: Config> Pallet<T> { + // Constructs the message that Ethereum RPC's `personal_sign` and `eth_sign` would sign. + fn ethereum_signable_message(what: &[u8], extra: &[u8]) -> Vec<u8> { + let prefix = T::Prefix::get(); + let mut l = prefix.len() + what.len() + extra.len(); + let mut rev = Vec::new(); + while l > 0 { + rev.push(b'0' + (l % 10) as u8); + l /= 10; + } + let mut v = b"\x19Ethereum Signed Message:\n".to_vec(); + v.extend(rev.into_iter().rev()); + v.extend_from_slice(prefix); + v.extend_from_slice(what); + v.extend_from_slice(extra); + v + } + + // Attempts to recover the Ethereum address from a message signature signed by using + // the Ethereum RPC's `personal_sign` and `eth_sign`. + fn eth_recover(s: &EcdsaSignature, what: &[u8], extra: &[u8]) -> Option<EthereumAddress> { + let msg = keccak_256(&Self::ethereum_signable_message(what, extra)); + let mut res = EthereumAddress::default(); + res.0 + .copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]); + Some(res) + } + + fn process_claim(signer: EthereumAddress, dest: T::AccountId) -> sp_runtime::DispatchResult { + let balance_due = Claims::<T>::get(&signer).ok_or(Error::<T>::SignerHasNoClaim)?; + + let new_total = + Total::<T>::get().checked_sub(&balance_due).ok_or(Error::<T>::PotUnderflow)?; + + let vesting = Vesting::<T>::get(&signer); + if vesting.is_some() && T::VestingSchedule::vesting_balance(&dest).is_some() { + return Err(Error::<T>::VestedBalanceExists.into()) + } + + // We first need to deposit the balance to ensure that the account exists. + let _ = CurrencyOf::<T>::deposit_creating(&dest, balance_due); + + // Check if this claim should have a vesting schedule. + if let Some(vs) = vesting { + // This can only fail if the account already has a vesting schedule, + // but this is checked above. + T::VestingSchedule::add_vesting_schedule(&dest, vs.0, vs.1, vs.2) + .expect("No other vesting schedule exists, as checked above; qed"); + } + + Total::<T>::put(new_total); + Claims::<T>::remove(&signer); + Vesting::<T>::remove(&signer); + Signing::<T>::remove(&signer); + + // Let's deposit an event to let the outside world know this happened. + Self::deposit_event(Event::<T>::Claimed { + who: dest, + ethereum_address: signer, + amount: balance_due, + }); + + Ok(()) + } +} + +/// Validate `attest` calls prior to execution. Needed to avoid a DoS attack since they are +/// otherwise free to place on chain. +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct PrevalidateAttests<T>(core::marker::PhantomData<fn(T)>); + +impl<T: Config> Debug for PrevalidateAttests<T> +where + <T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>>, +{ + #[cfg(feature = "std")] + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "PrevalidateAttests") + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result { + Ok(()) + } +} + +impl<T: Config> PrevalidateAttests<T> +where + <T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>>, +{ + /// Create new `TransactionExtension` to check runtime version. + pub fn new() -> Self { + Self(core::marker::PhantomData) + } +} + +impl<T: Config> TransactionExtension<T::RuntimeCall> for PrevalidateAttests<T> +where + <T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>>, + <<T as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin: + AsSystemOriginSigner<T::AccountId> + AsTransactionAuthorizedOrigin + Clone, +{ + const IDENTIFIER: &'static str = "PrevalidateAttests"; + type Implicit = (); + type Pre = (); + type Val = (); + + fn weight(&self, call: &T::RuntimeCall) -> Weight { + if let Some(Call::attest { .. }) = call.is_sub_type() { + T::WeightInfo::prevalidate_attests() + } else { + Weight::zero() + } + } + + fn validate( + &self, + origin: <T::RuntimeCall as Dispatchable>::RuntimeOrigin, + call: &T::RuntimeCall, + _info: &DispatchInfoOf<T::RuntimeCall>, + _len: usize, + _self_implicit: Self::Implicit, + _inherited_implication: &impl Encode, + _source: TransactionSource, + ) -> Result< + (ValidTransaction, Self::Val, <T::RuntimeCall as Dispatchable>::RuntimeOrigin), + TransactionValidityError, + > { + if let Some(Call::attest { statement: attested_statement }) = call.is_sub_type() { + let who = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?; + let signer = Preclaims::<T>::get(who) + .ok_or(InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()))?; + if let Some(s) = Signing::<T>::get(signer) { + let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into()); + ensure!(&attested_statement[..] == s.to_text(), e); + } + } + Ok((ValidTransaction::default(), (), origin)) + } + + impl_tx_ext_default!(T::RuntimeCall; prepare); +} + +#[cfg(any(test, feature = "runtime-benchmarks"))] +mod secp_utils { + use super::*; + + pub fn public(secret: &libsecp256k1::SecretKey) -> libsecp256k1::PublicKey { + libsecp256k1::PublicKey::from_secret_key(secret) + } + pub fn eth(secret: &libsecp256k1::SecretKey) -> EthereumAddress { + let mut res = EthereumAddress::default(); + res.0.copy_from_slice(&keccak_256(&public(secret).serialize()[1..65])[12..]); + res + } + pub fn sig<T: Config>( + secret: &libsecp256k1::SecretKey, + what: &[u8], + extra: &[u8], + ) -> EcdsaSignature { + let msg = keccak_256(&super::Pallet::<T>::ethereum_signable_message( + &to_ascii_hex(what)[..], + extra, + )); + let (sig, recovery_id) = libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), secret); + let mut r = [0u8; 65]; + r[0..64].copy_from_slice(&sig.serialize()[..]); + r[64] = recovery_id.serialize(); + EcdsaSignature(r) + } +} + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; diff --git a/polkadot/runtime/common/src/claims/tests.rs b/polkadot/runtime/common/src/claims/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..dff2623cb93425b72b8eac4c1fb4453f49382f48 --- /dev/null +++ b/polkadot/runtime/common/src/claims/tests.rs @@ -0,0 +1,666 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Tests for the claims pallet. + +#[cfg(test)] +use super::*; +use crate::{claims, claims::mock::*}; +use claims::Call as ClaimsCall; +use hex_literal::hex; +use secp_utils::*; +use sp_runtime::transaction_validity::TransactionSource::External; + +use codec::Encode; +// The testing primitives are very useful for avoiding having to work with signatures +// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. +use frame_support::{ + assert_err, assert_noop, assert_ok, + dispatch::{GetDispatchInfo, Pays}, + traits::ExistenceRequirement, +}; +use sp_runtime::{ + traits::DispatchTransaction, transaction_validity::TransactionLongevity, + DispatchError::BadOrigin, TokenError, +}; + +#[test] +fn basic_setup_works() { + new_test_ext().execute_with(|| { + assert_eq!(claims::Total::<Test>::get(), total_claims()); + assert_eq!(claims::Claims::<Test>::get(ð(&alice())), Some(100)); + assert_eq!(claims::Claims::<Test>::get(ð(&dave())), Some(200)); + assert_eq!(claims::Claims::<Test>::get(ð(&eve())), Some(300)); + assert_eq!(claims::Claims::<Test>::get(ð(&frank())), Some(400)); + assert_eq!(claims::Claims::<Test>::get(&EthereumAddress::default()), None); + assert_eq!(claims::Vesting::<Test>::get(ð(&alice())), Some((50, 10, 1))); + }); +} + +#[test] +fn serde_works() { + let x = EthereumAddress(hex!["0123456789abcdef0123456789abcdef01234567"]); + let y = serde_json::to_string(&x).unwrap(); + assert_eq!(y, "\"0x0123456789abcdef0123456789abcdef01234567\""); + let z: EthereumAddress = serde_json::from_str(&y).unwrap(); + assert_eq!(x, z); +} + +#[test] +fn claiming_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_ok!(claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::<Test>(&alice(), &42u64.encode(), &[][..]) + )); + assert_eq!(Balances::free_balance(&42), 100); + assert_eq!(claims::mock::Vesting::vesting_balance(&42), Some(50)); + assert_eq!(claims::Total::<Test>::get(), total_claims() - 100); + }); +} + +#[test] +fn basic_claim_moving_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_noop!( + claims::mock::Claims::move_claim( + RuntimeOrigin::signed(1), + eth(&alice()), + eth(&bob()), + None + ), + BadOrigin + ); + assert_ok!(claims::mock::Claims::move_claim( + RuntimeOrigin::signed(6), + eth(&alice()), + eth(&bob()), + None + )); + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::<Test>(&alice(), &42u64.encode(), &[][..]) + ), + Error::<Test>::SignerHasNoClaim + ); + assert_ok!(claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::<Test>(&bob(), &42u64.encode(), &[][..]) + )); + assert_eq!(Balances::free_balance(&42), 100); + assert_eq!(claims::mock::Vesting::vesting_balance(&42), Some(50)); + assert_eq!(claims::Total::<Test>::get(), total_claims() - 100); + }); +} + +#[test] +fn claim_attest_moving_works() { + new_test_ext().execute_with(|| { + assert_ok!(claims::mock::Claims::move_claim( + RuntimeOrigin::signed(6), + eth(&dave()), + eth(&bob()), + None + )); + let s = sig::<Test>(&bob(), &42u64.encode(), StatementKind::Regular.to_text()); + assert_ok!(claims::mock::Claims::claim_attest( + RuntimeOrigin::none(), + 42, + s, + StatementKind::Regular.to_text().to_vec() + )); + assert_eq!(Balances::free_balance(&42), 200); + }); +} + +#[test] +fn attest_moving_works() { + new_test_ext().execute_with(|| { + assert_ok!(claims::mock::Claims::move_claim( + RuntimeOrigin::signed(6), + eth(&eve()), + eth(&bob()), + Some(42) + )); + assert_ok!(claims::mock::Claims::attest( + RuntimeOrigin::signed(42), + StatementKind::Saft.to_text().to_vec() + )); + assert_eq!(Balances::free_balance(&42), 300); + }); +} + +#[test] +fn claiming_does_not_bypass_signing() { + new_test_ext().execute_with(|| { + assert_ok!(claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::<Test>(&alice(), &42u64.encode(), &[][..]) + )); + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::<Test>(&dave(), &42u64.encode(), &[][..]) + ), + Error::<Test>::InvalidStatement, + ); + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::<Test>(&eve(), &42u64.encode(), &[][..]) + ), + Error::<Test>::InvalidStatement, + ); + assert_ok!(claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::<Test>(&frank(), &42u64.encode(), &[][..]) + )); + }); +} + +#[test] +fn attest_claiming_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + let s = sig::<Test>(&dave(), &42u64.encode(), StatementKind::Saft.to_text()); + let r = claims::mock::Claims::claim_attest( + RuntimeOrigin::none(), + 42, + s.clone(), + StatementKind::Saft.to_text().to_vec(), + ); + assert_noop!(r, Error::<Test>::InvalidStatement); + + let r = claims::mock::Claims::claim_attest( + RuntimeOrigin::none(), + 42, + s, + StatementKind::Regular.to_text().to_vec(), + ); + assert_noop!(r, Error::<Test>::SignerHasNoClaim); + // ^^^ we use ecdsa_recover, so an invalid signature just results in a random signer id + // being recovered, which realistically will never have a claim. + + let s = sig::<Test>(&dave(), &42u64.encode(), StatementKind::Regular.to_text()); + assert_ok!(claims::mock::Claims::claim_attest( + RuntimeOrigin::none(), + 42, + s, + StatementKind::Regular.to_text().to_vec() + )); + assert_eq!(Balances::free_balance(&42), 200); + assert_eq!(claims::Total::<Test>::get(), total_claims() - 200); + + let s = sig::<Test>(&dave(), &42u64.encode(), StatementKind::Regular.to_text()); + let r = claims::mock::Claims::claim_attest( + RuntimeOrigin::none(), + 42, + s, + StatementKind::Regular.to_text().to_vec(), + ); + assert_noop!(r, Error::<Test>::SignerHasNoClaim); + }); +} + +#[test] +fn attesting_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_noop!( + claims::mock::Claims::attest( + RuntimeOrigin::signed(69), + StatementKind::Saft.to_text().to_vec() + ), + Error::<Test>::SenderHasNoClaim + ); + assert_noop!( + claims::mock::Claims::attest( + RuntimeOrigin::signed(42), + StatementKind::Regular.to_text().to_vec() + ), + Error::<Test>::InvalidStatement + ); + assert_ok!(claims::mock::Claims::attest( + RuntimeOrigin::signed(42), + StatementKind::Saft.to_text().to_vec() + )); + assert_eq!(Balances::free_balance(&42), 300); + assert_eq!(claims::Total::<Test>::get(), total_claims() - 300); + }); +} + +#[test] +fn claim_cannot_clobber_preclaim() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + // Alice's claim is 100 + assert_ok!(claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::<Test>(&alice(), &42u64.encode(), &[][..]) + )); + assert_eq!(Balances::free_balance(&42), 100); + // Eve's claim is 300 through Account 42 + assert_ok!(claims::mock::Claims::attest( + RuntimeOrigin::signed(42), + StatementKind::Saft.to_text().to_vec() + )); + assert_eq!(Balances::free_balance(&42), 100 + 300); + assert_eq!(claims::Total::<Test>::get(), total_claims() - 400); + }); +} + +#[test] +fn valid_attest_transactions_are_free() { + new_test_ext().execute_with(|| { + let p = PrevalidateAttests::<Test>::new(); + let c = claims::mock::RuntimeCall::Claims(ClaimsCall::attest { + statement: StatementKind::Saft.to_text().to_vec(), + }); + let di = c.get_dispatch_info(); + assert_eq!(di.pays_fee, Pays::No); + let r = p.validate_only(Some(42).into(), &c, &di, 20, External, 0); + assert_eq!(r.unwrap().0, ValidTransaction::default()); + }); +} + +#[test] +fn invalid_attest_transactions_are_recognized() { + new_test_ext().execute_with(|| { + let p = PrevalidateAttests::<Test>::new(); + let c = claims::mock::RuntimeCall::Claims(ClaimsCall::attest { + statement: StatementKind::Regular.to_text().to_vec(), + }); + let di = c.get_dispatch_info(); + let r = p.validate_only(Some(42).into(), &c, &di, 20, External, 0); + assert!(r.is_err()); + let c = claims::mock::RuntimeCall::Claims(ClaimsCall::attest { + statement: StatementKind::Saft.to_text().to_vec(), + }); + let di = c.get_dispatch_info(); + let r = p.validate_only(Some(69).into(), &c, &di, 20, External, 0); + assert!(r.is_err()); + }); +} + +#[test] +fn cannot_bypass_attest_claiming() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + let s = sig::<Test>(&dave(), &42u64.encode(), &[]); + let r = claims::mock::Claims::claim(RuntimeOrigin::none(), 42, s.clone()); + assert_noop!(r, Error::<Test>::InvalidStatement); + }); +} + +#[test] +fn add_claim_works() { + new_test_ext().execute_with(|| { + assert_noop!( + claims::mock::Claims::mint_claim( + RuntimeOrigin::signed(42), + eth(&bob()), + 200, + None, + None + ), + sp_runtime::traits::BadOrigin, + ); + assert_eq!(Balances::free_balance(42), 0); + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 69, + sig::<Test>(&bob(), &69u64.encode(), &[][..]) + ), + Error::<Test>::SignerHasNoClaim, + ); + assert_ok!(claims::mock::Claims::mint_claim( + RuntimeOrigin::root(), + eth(&bob()), + 200, + None, + None + )); + assert_eq!(claims::Total::<Test>::get(), total_claims() + 200); + assert_ok!(claims::mock::Claims::claim( + RuntimeOrigin::none(), + 69, + sig::<Test>(&bob(), &69u64.encode(), &[][..]) + )); + assert_eq!(Balances::free_balance(&69), 200); + assert_eq!(claims::mock::Vesting::vesting_balance(&69), None); + assert_eq!(claims::Total::<Test>::get(), total_claims()); + }); +} + +#[test] +fn add_claim_with_vesting_works() { + new_test_ext().execute_with(|| { + assert_noop!( + claims::mock::Claims::mint_claim( + RuntimeOrigin::signed(42), + eth(&bob()), + 200, + Some((50, 10, 1)), + None + ), + sp_runtime::traits::BadOrigin, + ); + assert_eq!(Balances::free_balance(42), 0); + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 69, + sig::<Test>(&bob(), &69u64.encode(), &[][..]) + ), + Error::<Test>::SignerHasNoClaim, + ); + assert_ok!(claims::mock::Claims::mint_claim( + RuntimeOrigin::root(), + eth(&bob()), + 200, + Some((50, 10, 1)), + None + )); + assert_ok!(claims::mock::Claims::claim( + RuntimeOrigin::none(), + 69, + sig::<Test>(&bob(), &69u64.encode(), &[][..]) + )); + assert_eq!(Balances::free_balance(&69), 200); + assert_eq!(claims::mock::Vesting::vesting_balance(&69), Some(50)); + + // Make sure we can not transfer the vested balance. + assert_err!( + <Balances as Currency<_>>::transfer(&69, &80, 180, ExistenceRequirement::AllowDeath), + TokenError::Frozen, + ); + }); +} + +#[test] +fn add_claim_with_statement_works() { + new_test_ext().execute_with(|| { + assert_noop!( + claims::mock::Claims::mint_claim( + RuntimeOrigin::signed(42), + eth(&bob()), + 200, + None, + Some(StatementKind::Regular) + ), + sp_runtime::traits::BadOrigin, + ); + assert_eq!(Balances::free_balance(42), 0); + let signature = sig::<Test>(&bob(), &69u64.encode(), StatementKind::Regular.to_text()); + assert_noop!( + claims::mock::Claims::claim_attest( + RuntimeOrigin::none(), + 69, + signature.clone(), + StatementKind::Regular.to_text().to_vec() + ), + Error::<Test>::SignerHasNoClaim + ); + assert_ok!(claims::mock::Claims::mint_claim( + RuntimeOrigin::root(), + eth(&bob()), + 200, + None, + Some(StatementKind::Regular) + )); + assert_noop!( + claims::mock::Claims::claim_attest( + RuntimeOrigin::none(), + 69, + signature.clone(), + vec![], + ), + Error::<Test>::SignerHasNoClaim + ); + assert_ok!(claims::mock::Claims::claim_attest( + RuntimeOrigin::none(), + 69, + signature.clone(), + StatementKind::Regular.to_text().to_vec() + )); + assert_eq!(Balances::free_balance(&69), 200); + }); +} + +#[test] +fn origin_signed_claiming_fail() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_err!( + claims::mock::Claims::claim( + RuntimeOrigin::signed(42), + 42, + sig::<Test>(&alice(), &42u64.encode(), &[][..]) + ), + sp_runtime::traits::BadOrigin, + ); + }); +} + +#[test] +fn double_claiming_doesnt_work() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_ok!(claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::<Test>(&alice(), &42u64.encode(), &[][..]) + )); + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::<Test>(&alice(), &42u64.encode(), &[][..]) + ), + Error::<Test>::SignerHasNoClaim + ); + }); +} + +#[test] +fn claiming_while_vested_doesnt_work() { + new_test_ext().execute_with(|| { + CurrencyOf::<Test>::make_free_balance_be(&69, total_claims()); + assert_eq!(Balances::free_balance(69), total_claims()); + // A user is already vested + assert_ok!(<Test as Config>::VestingSchedule::add_vesting_schedule( + &69, + total_claims(), + 100, + 10 + )); + assert_ok!(claims::mock::Claims::mint_claim( + RuntimeOrigin::root(), + eth(&bob()), + 200, + Some((50, 10, 1)), + None + )); + // New total + assert_eq!(claims::Total::<Test>::get(), total_claims() + 200); + + // They should not be able to claim + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 69, + sig::<Test>(&bob(), &69u64.encode(), &[][..]) + ), + Error::<Test>::VestedBalanceExists, + ); + }); +} + +#[test] +fn non_sender_sig_doesnt_work() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::<Test>(&alice(), &69u64.encode(), &[][..]) + ), + Error::<Test>::SignerHasNoClaim + ); + }); +} + +#[test] +fn non_claimant_doesnt_work() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::<Test>(&bob(), &69u64.encode(), &[][..]) + ), + Error::<Test>::SignerHasNoClaim + ); + }); +} + +#[test] +fn real_eth_sig_works() { + new_test_ext().execute_with(|| { + // "Pay RUSTs to the TEST account:2a00000000000000" + let sig = hex!["444023e89b67e67c0562ed0305d252a5dd12b2af5ac51d6d3cb69a0b486bc4b3191401802dc29d26d586221f7256cd3329fe82174bdf659baea149a40e1c495d1c"]; + let sig = EcdsaSignature(sig); + let who = 42u64.using_encoded(to_ascii_hex); + let signer = claims::mock::Claims::eth_recover(&sig, &who, &[][..]).unwrap(); + assert_eq!(signer.0, hex!["6d31165d5d932d571f3b44695653b46dcc327e84"]); + }); +} + +#[test] +fn validate_unsigned_works() { + use sp_runtime::traits::ValidateUnsigned; + let source = sp_runtime::transaction_validity::TransactionSource::External; + + new_test_ext().execute_with(|| { + assert_eq!( + Pallet::<Test>::validate_unsigned( + source, + &ClaimsCall::claim { + dest: 1, + ethereum_signature: sig::<Test>(&alice(), &1u64.encode(), &[][..]) + } + ), + Ok(ValidTransaction { + priority: 100, + requires: vec![], + provides: vec![("claims", eth(&alice())).encode()], + longevity: TransactionLongevity::max_value(), + propagate: true, + }) + ); + assert_eq!( + Pallet::<Test>::validate_unsigned( + source, + &ClaimsCall::claim { dest: 0, ethereum_signature: EcdsaSignature([0; 65]) } + ), + InvalidTransaction::Custom(ValidityError::InvalidEthereumSignature.into()).into(), + ); + assert_eq!( + Pallet::<Test>::validate_unsigned( + source, + &ClaimsCall::claim { + dest: 1, + ethereum_signature: sig::<Test>(&bob(), &1u64.encode(), &[][..]) + } + ), + InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(), + ); + let s = sig::<Test>(&dave(), &1u64.encode(), StatementKind::Regular.to_text()); + let call = ClaimsCall::claim_attest { + dest: 1, + ethereum_signature: s, + statement: StatementKind::Regular.to_text().to_vec(), + }; + assert_eq!( + Pallet::<Test>::validate_unsigned(source, &call), + Ok(ValidTransaction { + priority: 100, + requires: vec![], + provides: vec![("claims", eth(&dave())).encode()], + longevity: TransactionLongevity::max_value(), + propagate: true, + }) + ); + assert_eq!( + Pallet::<Test>::validate_unsigned( + source, + &ClaimsCall::claim_attest { + dest: 1, + ethereum_signature: EcdsaSignature([0; 65]), + statement: StatementKind::Regular.to_text().to_vec() + } + ), + InvalidTransaction::Custom(ValidityError::InvalidEthereumSignature.into()).into(), + ); + + let s = sig::<Test>(&bob(), &1u64.encode(), StatementKind::Regular.to_text()); + let call = ClaimsCall::claim_attest { + dest: 1, + ethereum_signature: s, + statement: StatementKind::Regular.to_text().to_vec(), + }; + assert_eq!( + Pallet::<Test>::validate_unsigned(source, &call), + InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(), + ); + + let s = sig::<Test>(&dave(), &1u64.encode(), StatementKind::Saft.to_text()); + let call = ClaimsCall::claim_attest { + dest: 1, + ethereum_signature: s, + statement: StatementKind::Regular.to_text().to_vec(), + }; + assert_eq!( + Pallet::<Test>::validate_unsigned(source, &call), + InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(), + ); + + let s = sig::<Test>(&dave(), &1u64.encode(), StatementKind::Saft.to_text()); + let call = ClaimsCall::claim_attest { + dest: 1, + ethereum_signature: s, + statement: StatementKind::Saft.to_text().to_vec(), + }; + assert_eq!( + Pallet::<Test>::validate_unsigned(source, &call), + InvalidTransaction::Custom(ValidityError::InvalidStatement.into()).into(), + ); + }); +} diff --git a/polkadot/runtime/common/src/crowdloan/mod.rs b/polkadot/runtime/common/src/crowdloan/mod.rs index 8cf288197e3dd56085f96c928ae6a5b7f79a2a86..1b40f248bfb11dfadde06d8ed448b28badb0b1cc 100644 --- a/polkadot/runtime/common/src/crowdloan/mod.rs +++ b/polkadot/runtime/common/src/crowdloan/mod.rs @@ -858,10 +858,7 @@ mod crypto { mod tests { use super::*; - use frame_support::{ - assert_noop, assert_ok, derive_impl, parameter_types, - traits::{OnFinalize, OnInitialize}, - }; + use frame_support::{assert_noop, assert_ok, derive_impl, parameter_types}; use polkadot_primitives::Id as ParaId; use sp_core::H256; use std::{cell::RefCell, collections::BTreeMap, sync::Arc}; @@ -1085,6 +1082,7 @@ mod tests { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); pallet_balances::GenesisConfig::<Test> { balances: vec![(1, 1000), (2, 2000), (3, 3000), (4, 4000)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -1111,18 +1109,6 @@ mod tests { unreachable!() } - fn run_to_block(n: u64) { - while System::block_number() < n { - Crowdloan::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); - Crowdloan::on_initialize(System::block_number()); - } - } - fn last_event() -> RuntimeEvent { System::events().pop().expect("RuntimeEvent expected").event } @@ -1426,7 +1412,7 @@ mod tests { ); // Move past end date - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); // Cannot contribute to ended fund assert_noop!( @@ -1451,7 +1437,7 @@ mod tests { // crowdloan that has starting period 1. let para_3 = new_para(); assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para_3, 1000, 1, 4, 40, None)); - run_to_block(40); + System::run_to_block::<AllPalletsWithSystem>(40); let now = System::block_number(); assert_eq!(TestAuctioneer::lease_period_index(now).unwrap().0, 2); assert_noop!( @@ -1483,12 +1469,12 @@ mod tests { None )); - run_to_block(8); + System::run_to_block::<AllPalletsWithSystem>(8); // Can def contribute when auction is running. assert!(TestAuctioneer::auction_status(System::block_number()).is_ending().is_some()); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 250, None)); - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); // Can't contribute when auction is in the VRF delay period. assert!(TestAuctioneer::auction_status(System::block_number()).is_vrf()); assert_noop!( @@ -1496,7 +1482,7 @@ mod tests { Error::<Test>::VrfDelayInProgress ); - run_to_block(15); + System::run_to_block::<AllPalletsWithSystem>(15); // Its fine to contribute when no auction is running. assert!(!TestAuctioneer::auction_status(System::block_number()).is_in_progress()); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 250, None)); @@ -1526,15 +1512,15 @@ mod tests { let bidder = Crowdloan::fund_account_id(index); // Fund crowdloan - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None)); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 150, None)); - run_to_block(5); + System::run_to_block::<AllPalletsWithSystem>(5); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(4), para, 200, None)); - run_to_block(8); + System::run_to_block::<AllPalletsWithSystem>(8); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 250, None)); - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); assert_eq!( bids(), @@ -1561,7 +1547,7 @@ mod tests { assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None)); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 50, None)); - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); let account_id = Crowdloan::fund_account_id(index); // para has no reserved funds, indicating it did not win the auction. assert_eq!(Balances::reserved_balance(&account_id), 0); @@ -1591,7 +1577,7 @@ mod tests { assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 1, 9, None)); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None)); - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); let account_id = Crowdloan::fund_account_id(index); // user sends the crowdloan funds trying to make an accounting error @@ -1636,7 +1622,7 @@ mod tests { ); // Move to the end of the crowdloan - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(1337), para)); // Funds are returned @@ -1671,7 +1657,7 @@ mod tests { assert_eq!(Balances::free_balance(account_id), 21000); // Move to the end of the crowdloan - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(1337), para)); assert_eq!( last_event(), @@ -1705,7 +1691,7 @@ mod tests { assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None)); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 50, None)); - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); // All funds are refunded assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(2), para)); @@ -1730,7 +1716,7 @@ mod tests { assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None)); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 50, None)); - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); // We test the historic case where crowdloan accounts only have one provider: { @@ -1770,7 +1756,7 @@ mod tests { Error::<Test>::NotReadyToDissolve ); - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); set_winner(para, 1, true); // Can't dissolve when it won. assert_noop!( @@ -1815,13 +1801,13 @@ mod tests { // simulate the reserving of para's funds. this actually happens in the Slots pallet. assert_ok!(Balances::reserve(&account_id, 149)); - run_to_block(19); + System::run_to_block::<AllPalletsWithSystem>(19); assert_noop!( Crowdloan::withdraw(RuntimeOrigin::signed(2), 2, para), Error::<Test>::BidOrLeaseActive ); - run_to_block(20); + System::run_to_block::<AllPalletsWithSystem>(20); // simulate the unreserving of para's funds, now that the lease expired. this actually // happens in the Slots pallet. Balances::unreserve(&account_id, 150); @@ -1949,7 +1935,7 @@ mod tests { Error::<Test>::NoContributions ); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para_1, 100, None)); - run_to_block(6); + System::run_to_block::<AllPalletsWithSystem>(6); assert_ok!(Crowdloan::poke(RuntimeOrigin::signed(1), para_1)); assert_eq!(crowdloan::NewRaise::<Test>::get(), vec![para_1]); assert_noop!( diff --git a/polkadot/runtime/common/src/identity_migrator.rs b/polkadot/runtime/common/src/identity_migrator.rs index 126c886280e6ed713d81c93f5afedbb69b03a1b5..e3835b692526e5cac1a8a541f91a85675e4fa5f1 100644 --- a/polkadot/runtime/common/src/identity_migrator.rs +++ b/polkadot/runtime/common/src/identity_migrator.rs @@ -160,12 +160,22 @@ pub trait OnReapIdentity<AccountId> { /// - `bytes`: The byte size of `IdentityInfo`. /// - `subs`: The number of sub-accounts they had. fn on_reap_identity(who: &AccountId, bytes: u32, subs: u32) -> DispatchResult; + + /// Ensure that identity reaping will be succesful in benchmarking. + /// + /// Should setup the state in a way that the same call ot `[Self::on_reap_identity]` will be + /// successful. + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_identity_reaping(who: &AccountId, bytes: u32, subs: u32); } impl<AccountId> OnReapIdentity<AccountId> for () { fn on_reap_identity(_who: &AccountId, _bytes: u32, _subs: u32) -> DispatchResult { Ok(()) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_identity_reaping(_: &AccountId, _: u32, _: u32) {} } #[cfg(feature = "runtime-benchmarks")] @@ -219,6 +229,12 @@ mod benchmarks { } Identity::<T>::set_subs(target_origin.clone(), subs.clone())?; + T::ReapIdentityHandler::ensure_successful_identity_reaping( + &target, + info.encoded_size() as u32, + subs.len() as u32, + ); + // add registrars and provide judgements let registrar_origin = T::RegistrarOrigin::try_successful_origin() .expect("RegistrarOrigin has no successful origin required for the benchmark"); diff --git a/polkadot/runtime/common/src/integration_tests.rs b/polkadot/runtime/common/src/integration_tests.rs index 8a76a138305ea8ffe3aa098c06d35bfae3dab7c7..bb4ad8b75065cb3761bc6690baf0fd1fce8a0cca 100644 --- a/polkadot/runtime/common/src/integration_tests.rs +++ b/polkadot/runtime/common/src/integration_tests.rs @@ -28,7 +28,7 @@ use alloc::sync::Arc; use codec::Encode; use frame_support::{ assert_noop, assert_ok, derive_impl, parameter_types, - traits::{ConstU32, Currency, OnFinalize, OnInitialize}, + traits::{ConstU32, Currency}, weights::Weight, PalletId, }; @@ -377,14 +377,12 @@ fn add_blocks(n: u32) { } fn run_to_block(n: u32) { - assert!(System::block_number() < n); - while System::block_number() < n { - let block_number = System::block_number(); - AllPalletsWithSystem::on_finalize(block_number); - System::set_block_number(block_number + 1); - maybe_new_session(block_number + 1); - AllPalletsWithSystem::on_initialize(block_number + 1); - } + System::run_to_block_with::<AllPalletsWithSystem>( + n, + frame_system::RunToBlockHooks::default().before_initialize(|bn| { + maybe_new_session(bn); + }), + ); } fn run_to_session(n: u32) { diff --git a/polkadot/runtime/common/src/paras_registrar/benchmarking.rs b/polkadot/runtime/common/src/paras_registrar/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..95df8a96957673fd48a428163c4874041526f80c --- /dev/null +++ b/polkadot/runtime/common/src/paras_registrar/benchmarking.rs @@ -0,0 +1,171 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Benchmarking for paras_registrar pallet + +#[cfg(feature = "runtime-benchmarks")] +use super::{Pallet as Registrar, *}; +use crate::traits::Registrar as RegistrarT; +use frame_support::assert_ok; +use frame_system::RawOrigin; +use polkadot_primitives::{MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MIN_CODE_SIZE}; +use polkadot_runtime_parachains::{paras, shared, Origin as ParaOrigin}; +use sp_runtime::traits::Bounded; + +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; + +fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) { + let events = frame_system::Pallet::<T>::events(); + let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +fn register_para<T: Config>(id: u32) -> ParaId { + let para = ParaId::from(id); + let genesis_head = Registrar::<T>::worst_head_data(); + let validation_code = Registrar::<T>::worst_validation_code(); + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value()); + assert_ok!(Registrar::<T>::reserve(RawOrigin::Signed(caller.clone()).into())); + assert_ok!(Registrar::<T>::register( + RawOrigin::Signed(caller).into(), + para, + genesis_head, + validation_code.clone() + )); + assert_ok!(polkadot_runtime_parachains::paras::Pallet::<T>::add_trusted_validation_code( + frame_system::Origin::<T>::Root.into(), + validation_code, + )); + return para +} + +fn para_origin(id: u32) -> ParaOrigin { + ParaOrigin::Parachain(id.into()) +} + +// This function moves forward to the next scheduled session for parachain lifecycle upgrades. +fn next_scheduled_session<T: Config>() { + shared::Pallet::<T>::set_session_index(shared::Pallet::<T>::scheduled_session()); + paras::Pallet::<T>::test_on_new_session(); +} + +benchmarks! { + where_clause { where ParaOrigin: Into<<T as frame_system::Config>::RuntimeOrigin> } + + reserve { + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value()); + }: _(RawOrigin::Signed(caller.clone())) + verify { + assert_last_event::<T>(Event::<T>::Reserved { para_id: LOWEST_PUBLIC_ID, who: caller }.into()); + assert!(Paras::<T>::get(LOWEST_PUBLIC_ID).is_some()); + assert_eq!(paras::Pallet::<T>::lifecycle(LOWEST_PUBLIC_ID), None); + } + + register { + let para = LOWEST_PUBLIC_ID; + let genesis_head = Registrar::<T>::worst_head_data(); + let validation_code = Registrar::<T>::worst_validation_code(); + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value()); + assert_ok!(Registrar::<T>::reserve(RawOrigin::Signed(caller.clone()).into())); + }: _(RawOrigin::Signed(caller.clone()), para, genesis_head, validation_code.clone()) + verify { + assert_last_event::<T>(Event::<T>::Registered{ para_id: para, manager: caller }.into()); + assert_eq!(paras::Pallet::<T>::lifecycle(para), Some(ParaLifecycle::Onboarding)); + assert_ok!(polkadot_runtime_parachains::paras::Pallet::<T>::add_trusted_validation_code( + frame_system::Origin::<T>::Root.into(), + validation_code, + )); + next_scheduled_session::<T>(); + assert_eq!(paras::Pallet::<T>::lifecycle(para), Some(ParaLifecycle::Parathread)); + } + + force_register { + let manager: T::AccountId = account("manager", 0, 0); + let deposit = 0u32.into(); + let para = ParaId::from(69); + let genesis_head = Registrar::<T>::worst_head_data(); + let validation_code = Registrar::<T>::worst_validation_code(); + }: _(RawOrigin::Root, manager.clone(), deposit, para, genesis_head, validation_code.clone()) + verify { + assert_last_event::<T>(Event::<T>::Registered { para_id: para, manager }.into()); + assert_eq!(paras::Pallet::<T>::lifecycle(para), Some(ParaLifecycle::Onboarding)); + assert_ok!(polkadot_runtime_parachains::paras::Pallet::<T>::add_trusted_validation_code( + frame_system::Origin::<T>::Root.into(), + validation_code, + )); + next_scheduled_session::<T>(); + assert_eq!(paras::Pallet::<T>::lifecycle(para), Some(ParaLifecycle::Parathread)); + } + + deregister { + let para = register_para::<T>(LOWEST_PUBLIC_ID.into()); + next_scheduled_session::<T>(); + let caller: T::AccountId = whitelisted_caller(); + }: _(RawOrigin::Signed(caller), para) + verify { + assert_last_event::<T>(Event::<T>::Deregistered { para_id: para }.into()); + } + + swap { + // On demand parachain + let parathread = register_para::<T>(LOWEST_PUBLIC_ID.into()); + let parachain = register_para::<T>((LOWEST_PUBLIC_ID + 1).into()); + + let parachain_origin = para_origin(parachain.into()); + + // Actually finish registration process + next_scheduled_session::<T>(); + + // Upgrade the parachain + Registrar::<T>::make_parachain(parachain)?; + next_scheduled_session::<T>(); + + assert_eq!(paras::Pallet::<T>::lifecycle(parachain), Some(ParaLifecycle::Parachain)); + assert_eq!(paras::Pallet::<T>::lifecycle(parathread), Some(ParaLifecycle::Parathread)); + + let caller: T::AccountId = whitelisted_caller(); + Registrar::<T>::swap(parachain_origin.into(), parachain, parathread)?; + }: _(RawOrigin::Signed(caller.clone()), parathread, parachain) + verify { + next_scheduled_session::<T>(); + // Swapped! + assert_eq!(paras::Pallet::<T>::lifecycle(parachain), Some(ParaLifecycle::Parathread)); + assert_eq!(paras::Pallet::<T>::lifecycle(parathread), Some(ParaLifecycle::Parachain)); + } + + schedule_code_upgrade { + let b in MIN_CODE_SIZE .. MAX_CODE_SIZE; + let new_code = ValidationCode(vec![0; b as usize]); + let para_id = ParaId::from(1000); + }: _(RawOrigin::Root, para_id, new_code) + + set_current_head { + let b in 1 .. MAX_HEAD_DATA_SIZE; + let new_head = HeadData(vec![0; b as usize]); + let para_id = ParaId::from(1000); + }: _(RawOrigin::Root, para_id, new_head) + + impl_benchmark_test_suite!( + Registrar, + crate::integration_tests::new_test_ext(), + crate::integration_tests::Test, + ); +} diff --git a/polkadot/runtime/common/src/paras_registrar/mock.rs b/polkadot/runtime/common/src/paras_registrar/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..bb3728f0e12a07cbc213dc31b316671a4bc8df07 --- /dev/null +++ b/polkadot/runtime/common/src/paras_registrar/mock.rs @@ -0,0 +1,247 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see <http://www.gnu.org/licenses/>. + +//! Mocking utilities for testing in paras_registrar pallet. + +#[cfg(test)] +use super::*; +use crate::paras_registrar; +use alloc::collections::btree_map::BTreeMap; +use frame_support::{derive_impl, parameter_types}; +use frame_system::limits; +use polkadot_primitives::{Balance, BlockNumber, MAX_CODE_SIZE}; +use polkadot_runtime_parachains::{configuration, origin, shared}; +use sp_core::H256; +use sp_io::TestExternalities; +use sp_keyring::Sr25519Keyring; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + transaction_validity::TransactionPriority, + BuildStorage, Perbill, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>; +type Block = frame_system::mocking::MockBlockU32<Test>; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Configuration: configuration, + Parachains: paras, + ParasShared: shared, + Registrar: paras_registrar, + ParachainsOrigin: origin, + } +); + +impl<C> frame_system::offchain::CreateTransactionBase<C> for Test +where + RuntimeCall: From<C>, +{ + type Extrinsic = UncheckedExtrinsic; + type RuntimeCall = RuntimeCall; +} + +impl<C> frame_system::offchain::CreateInherent<C> for Test +where + RuntimeCall: From<C>, +{ + fn create_inherent(call: Self::RuntimeCall) -> Self::Extrinsic { + UncheckedExtrinsic::new_bare(call) + } +} + +const NORMAL_RATIO: Perbill = Perbill::from_percent(75); +parameter_types! { + pub BlockWeights: limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, u64::MAX)); + pub BlockLength: limits::BlockLength = + limits::BlockLength::max_with_normal_ratio(4 * 1024 * 1024, NORMAL_RATIO); +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup<u64>; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type DbWeight = (); + type BlockWeights = BlockWeights; + type BlockLength = BlockLength; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData<u128>; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const ExistentialDeposit: Balance = 1; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type Balance = Balance; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} + +impl shared::Config for Test { + type DisabledValidators = (); +} + +impl origin::Config for Test {} + +parameter_types! { + pub const ParasUnsignedPriority: TransactionPriority = TransactionPriority::max_value(); +} + +impl paras::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = paras::TestWeightInfo; + type UnsignedPriority = ParasUnsignedPriority; + type QueueFootprinter = (); + type NextSessionRotation = crate::mock::TestNextSessionRotation; + type OnNewHead = (); + type AssignCoretime = (); +} + +impl configuration::Config for Test { + type WeightInfo = configuration::TestWeightInfo; +} + +parameter_types! { + pub const ParaDeposit: Balance = 10; + pub const DataDepositPerByte: Balance = 1; + pub const MaxRetries: u32 = 3; +} + +impl Config for Test { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type OnSwap = MockSwap; + type ParaDeposit = ParaDeposit; + type DataDepositPerByte = DataDepositPerByte; + type WeightInfo = TestWeightInfo; +} + +pub fn new_test_ext() -> TestExternalities { + let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); + + configuration::GenesisConfig::<Test> { + config: configuration::HostConfiguration { + max_code_size: MAX_CODE_SIZE, + max_head_data_size: 1 * 1024 * 1024, // 1 MB + ..Default::default() + }, + } + .assimilate_storage(&mut t) + .unwrap(); + + pallet_balances::GenesisConfig::<Test> { + balances: vec![(1, 10_000_000), (2, 10_000_000), (3, 10_000_000)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); + + t.into() +} + +parameter_types! { + pub static SwapData: BTreeMap<ParaId, u64> = BTreeMap::new(); +} + +pub struct MockSwap; +impl OnSwap for MockSwap { + fn on_swap(one: ParaId, other: ParaId) { + let mut swap_data = SwapData::get(); + let one_data = swap_data.remove(&one).unwrap_or_default(); + let other_data = swap_data.remove(&other).unwrap_or_default(); + swap_data.insert(one, other_data); + swap_data.insert(other, one_data); + SwapData::set(swap_data); + } +} + +pub const BLOCKS_PER_SESSION: u32 = 3; + +pub const VALIDATORS: &[Sr25519Keyring] = &[ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Ferdie, +]; + +pub fn run_to_block(n: BlockNumber) { + // NOTE that this function only simulates modules of interest. Depending on new pallet may + // require adding it here. + System::run_to_block_with::<AllPalletsWithSystem>( + n, + frame_system::RunToBlockHooks::default().before_finalize(|bn| { + // Session change every 3 blocks. + if (bn + 1) % BLOCKS_PER_SESSION == 0 { + let session_index = shared::CurrentSessionIndex::<Test>::get() + 1; + let validators_pub_keys = VALIDATORS.iter().map(|v| v.public().into()).collect(); + + shared::Pallet::<Test>::set_session_index(session_index); + shared::Pallet::<Test>::set_active_validators_ascending(validators_pub_keys); + + Parachains::test_on_new_session(); + } + }), + ); +} + +pub fn run_to_session(n: BlockNumber) { + let block_number = n * BLOCKS_PER_SESSION; + run_to_block(block_number); +} + +pub fn test_genesis_head(size: usize) -> HeadData { + HeadData(vec![0u8; size]) +} + +pub fn test_validation_code(size: usize) -> ValidationCode { + let validation_code = vec![0u8; size as usize]; + ValidationCode(validation_code) +} + +pub fn para_origin(id: ParaId) -> RuntimeOrigin { + polkadot_runtime_parachains::Origin::Parachain(id).into() +} + +pub fn max_code_size() -> u32 { + configuration::ActiveConfig::<Test>::get().max_code_size +} + +pub fn max_head_size() -> u32 { + configuration::ActiveConfig::<Test>::get().max_head_data_size +} diff --git a/polkadot/runtime/common/src/paras_registrar/mod.rs b/polkadot/runtime/common/src/paras_registrar/mod.rs index 2ead621dedf0cb7b4b4b08da579035a474e22d2e..aed0729c9d517e820af013f3a9725db37d720939 100644 --- a/polkadot/runtime/common/src/paras_registrar/mod.rs +++ b/polkadot/runtime/common/src/paras_registrar/mod.rs @@ -561,15 +561,16 @@ impl<T: Config> Pallet<T> { origin: <T as frame_system::Config>::RuntimeOrigin, id: ParaId, ) -> DispatchResult { - ensure_signed(origin.clone()) - .map_err(|e| e.into()) - .and_then(|who| -> DispatchResult { - let para_info = Paras::<T>::get(id).ok_or(Error::<T>::NotRegistered)?; + if let Ok(who) = ensure_signed(origin.clone()) { + let para_info = Paras::<T>::get(id).ok_or(Error::<T>::NotRegistered)?; + + if para_info.manager == who { ensure!(!para_info.is_locked(), Error::<T>::ParaLocked); - ensure!(para_info.manager == who, Error::<T>::NotOwner); - Ok(()) - }) - .or_else(|_| -> DispatchResult { Self::ensure_root_or_para(origin, id) }) + return Ok(()) + } + } + + Self::ensure_root_or_para(origin, id) } /// Ensure the origin is one of Root or the `para` itself. @@ -577,14 +578,14 @@ impl<T: Config> Pallet<T> { origin: <T as frame_system::Config>::RuntimeOrigin, id: ParaId, ) -> DispatchResult { - if let Ok(caller_id) = ensure_parachain(<T as Config>::RuntimeOrigin::from(origin.clone())) - { - // Check if matching para id... - ensure!(caller_id == id, Error::<T>::NotOwner); - } else { - // Check if root... - ensure_root(origin.clone())?; + if ensure_root(origin.clone()).is_ok() { + return Ok(()) } + + let caller_id = ensure_parachain(<T as Config>::RuntimeOrigin::from(origin))?; + // Check if matching para id... + ensure!(caller_id == id, Error::<T>::NotOwner); + Ok(()) } @@ -713,967 +714,10 @@ impl<T: Config> OnNewHead for Pallet<T> { } #[cfg(test)] -mod tests { - use super::*; - use crate::{ - mock::conclude_pvf_checking, paras_registrar, traits::Registrar as RegistrarTrait, - }; - use alloc::collections::btree_map::BTreeMap; - use frame_support::{ - assert_noop, assert_ok, derive_impl, parameter_types, - traits::{OnFinalize, OnInitialize}, - }; - use frame_system::limits; - use pallet_balances::Error as BalancesError; - use polkadot_primitives::{Balance, BlockNumber, SessionIndex, MAX_CODE_SIZE}; - use polkadot_runtime_parachains::{configuration, origin, shared}; - use sp_core::H256; - use sp_io::TestExternalities; - use sp_keyring::Sr25519Keyring; - use sp_runtime::{ - traits::{BadOrigin, BlakeTwo256, IdentityLookup}, - transaction_validity::TransactionPriority, - BuildStorage, Perbill, - }; - - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>; - type Block = frame_system::mocking::MockBlockU32<Test>; - - frame_support::construct_runtime!( - pub enum Test - { - System: frame_system, - Balances: pallet_balances, - Configuration: configuration, - Parachains: paras, - ParasShared: shared, - Registrar: paras_registrar, - ParachainsOrigin: origin, - } - ); - - impl<C> frame_system::offchain::CreateTransactionBase<C> for Test - where - RuntimeCall: From<C>, - { - type Extrinsic = UncheckedExtrinsic; - type RuntimeCall = RuntimeCall; - } - - impl<C> frame_system::offchain::CreateInherent<C> for Test - where - RuntimeCall: From<C>, - { - fn create_inherent(call: Self::RuntimeCall) -> Self::Extrinsic { - UncheckedExtrinsic::new_bare(call) - } - } - - const NORMAL_RATIO: Perbill = Perbill::from_percent(75); - parameter_types! { - pub BlockWeights: limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, u64::MAX)); - pub BlockLength: limits::BlockLength = - limits::BlockLength::max_with_normal_ratio(4 * 1024 * 1024, NORMAL_RATIO); - } - - #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] - impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup<u64>; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type DbWeight = (); - type BlockWeights = BlockWeights; - type BlockLength = BlockLength; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData<u128>; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; - } - - parameter_types! { - pub const ExistentialDeposit: Balance = 1; - } - - #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] - impl pallet_balances::Config for Test { - type Balance = Balance; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - } - - impl shared::Config for Test { - type DisabledValidators = (); - } - - impl origin::Config for Test {} - - parameter_types! { - pub const ParasUnsignedPriority: TransactionPriority = TransactionPriority::max_value(); - } - - impl paras::Config for Test { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = paras::TestWeightInfo; - type UnsignedPriority = ParasUnsignedPriority; - type QueueFootprinter = (); - type NextSessionRotation = crate::mock::TestNextSessionRotation; - type OnNewHead = (); - type AssignCoretime = (); - } - - impl configuration::Config for Test { - type WeightInfo = configuration::TestWeightInfo; - } - - parameter_types! { - pub const ParaDeposit: Balance = 10; - pub const DataDepositPerByte: Balance = 1; - pub const MaxRetries: u32 = 3; - } - - impl Config for Test { - type RuntimeOrigin = RuntimeOrigin; - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type OnSwap = MockSwap; - type ParaDeposit = ParaDeposit; - type DataDepositPerByte = DataDepositPerByte; - type WeightInfo = TestWeightInfo; - } - - pub fn new_test_ext() -> TestExternalities { - let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); - - configuration::GenesisConfig::<Test> { - config: configuration::HostConfiguration { - max_code_size: MAX_CODE_SIZE, - max_head_data_size: 1 * 1024 * 1024, // 1 MB - ..Default::default() - }, - } - .assimilate_storage(&mut t) - .unwrap(); - - pallet_balances::GenesisConfig::<Test> { - balances: vec![(1, 10_000_000), (2, 10_000_000), (3, 10_000_000)], - } - .assimilate_storage(&mut t) - .unwrap(); - - t.into() - } - - parameter_types! { - pub static SwapData: BTreeMap<ParaId, u64> = BTreeMap::new(); - } - - pub struct MockSwap; - impl OnSwap for MockSwap { - fn on_swap(one: ParaId, other: ParaId) { - let mut swap_data = SwapData::get(); - let one_data = swap_data.remove(&one).unwrap_or_default(); - let other_data = swap_data.remove(&other).unwrap_or_default(); - swap_data.insert(one, other_data); - swap_data.insert(other, one_data); - SwapData::set(swap_data); - } - } - - const BLOCKS_PER_SESSION: u32 = 3; - - const VALIDATORS: &[Sr25519Keyring] = &[ - Sr25519Keyring::Alice, - Sr25519Keyring::Bob, - Sr25519Keyring::Charlie, - Sr25519Keyring::Dave, - Sr25519Keyring::Ferdie, - ]; - - fn run_to_block(n: BlockNumber) { - // NOTE that this function only simulates modules of interest. Depending on new pallet may - // require adding it here. - assert!(System::block_number() < n); - while System::block_number() < n { - let b = System::block_number(); - - if System::block_number() > 1 { - System::on_finalize(System::block_number()); - } - // Session change every 3 blocks. - if (b + 1) % BLOCKS_PER_SESSION == 0 { - let session_index = shared::CurrentSessionIndex::<Test>::get() + 1; - let validators_pub_keys = VALIDATORS.iter().map(|v| v.public().into()).collect(); - - shared::Pallet::<Test>::set_session_index(session_index); - shared::Pallet::<Test>::set_active_validators_ascending(validators_pub_keys); - - Parachains::test_on_new_session(); - } - System::set_block_number(b + 1); - System::on_initialize(System::block_number()); - } - } - - fn run_to_session(n: BlockNumber) { - let block_number = n * BLOCKS_PER_SESSION; - run_to_block(block_number); - } - - fn test_genesis_head(size: usize) -> HeadData { - HeadData(vec![0u8; size]) - } +mod mock; - fn test_validation_code(size: usize) -> ValidationCode { - let validation_code = vec![0u8; size as usize]; - ValidationCode(validation_code) - } - - fn para_origin(id: ParaId) -> RuntimeOrigin { - polkadot_runtime_parachains::Origin::Parachain(id).into() - } - - fn max_code_size() -> u32 { - configuration::ActiveConfig::<Test>::get().max_code_size - } - - fn max_head_size() -> u32 { - configuration::ActiveConfig::<Test>::get().max_head_data_size - } - - #[test] - fn basic_setup_works() { - new_test_ext().execute_with(|| { - assert_eq!(PendingSwap::<Test>::get(&ParaId::from(0u32)), None); - assert_eq!(Paras::<Test>::get(&ParaId::from(0u32)), None); - }); - } - - #[test] - fn end_to_end_scenario_works() { - new_test_ext().execute_with(|| { - let para_id = LOWEST_PUBLIC_ID; - - const START_SESSION_INDEX: SessionIndex = 1; - run_to_session(START_SESSION_INDEX); - - // first para is not yet registered - assert!(!Parachains::is_parathread(para_id)); - // We register the Para ID - let validation_code = test_validation_code(32); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_id, - test_genesis_head(32), - validation_code.clone(), - )); - conclude_pvf_checking::<Test>(&validation_code, VALIDATORS, START_SESSION_INDEX); - - run_to_session(START_SESSION_INDEX + 2); - // It is now a parathread (on-demand parachain). - assert!(Parachains::is_parathread(para_id)); - assert!(!Parachains::is_parachain(para_id)); - // Some other external process will elevate on-demand to lease holding parachain - assert_ok!(Registrar::make_parachain(para_id)); - run_to_session(START_SESSION_INDEX + 4); - // It is now a lease holding parachain. - assert!(!Parachains::is_parathread(para_id)); - assert!(Parachains::is_parachain(para_id)); - // Turn it back into a parathread (on-demand parachain) - assert_ok!(Registrar::make_parathread(para_id)); - run_to_session(START_SESSION_INDEX + 6); - assert!(Parachains::is_parathread(para_id)); - assert!(!Parachains::is_parachain(para_id)); - // Deregister it - assert_ok!(Registrar::deregister(RuntimeOrigin::root(), para_id,)); - run_to_session(START_SESSION_INDEX + 8); - // It is nothing - assert!(!Parachains::is_parathread(para_id)); - assert!(!Parachains::is_parachain(para_id)); - }); - } - - #[test] - fn register_works() { - new_test_ext().execute_with(|| { - const START_SESSION_INDEX: SessionIndex = 1; - run_to_session(START_SESSION_INDEX); - - let para_id = LOWEST_PUBLIC_ID; - assert!(!Parachains::is_parathread(para_id)); - - let validation_code = test_validation_code(32); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - assert_eq!(Balances::reserved_balance(&1), <Test as Config>::ParaDeposit::get()); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_id, - test_genesis_head(32), - validation_code.clone(), - )); - conclude_pvf_checking::<Test>(&validation_code, VALIDATORS, START_SESSION_INDEX); - - run_to_session(START_SESSION_INDEX + 2); - assert!(Parachains::is_parathread(para_id)); - // Even though the registered validation code has a smaller size than the maximum the - // para manager's deposit is reserved as though they registered the maximum-sized code. - // Consequently, they can upgrade their code to the maximum size at any point without - // additional cost. - let validation_code_deposit = - max_code_size() as BalanceOf<Test> * <Test as Config>::DataDepositPerByte::get(); - let head_deposit = 32 * <Test as Config>::DataDepositPerByte::get(); - assert_eq!( - Balances::reserved_balance(&1), - <Test as Config>::ParaDeposit::get() + head_deposit + validation_code_deposit - ); - }); - } - - #[test] - fn schedule_code_upgrade_validates_code() { - new_test_ext().execute_with(|| { - const START_SESSION_INDEX: SessionIndex = 1; - run_to_session(START_SESSION_INDEX); - - let para_id = LOWEST_PUBLIC_ID; - assert!(!Parachains::is_parathread(para_id)); - - let validation_code = test_validation_code(32); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - assert_eq!(Balances::reserved_balance(&1), <Test as Config>::ParaDeposit::get()); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_id, - test_genesis_head(32), - validation_code.clone(), - )); - conclude_pvf_checking::<Test>(&validation_code, VALIDATORS, START_SESSION_INDEX); - - run_to_session(START_SESSION_INDEX + 2); - assert!(Parachains::is_parathread(para_id)); - - let new_code = test_validation_code(0); - assert_noop!( - Registrar::schedule_code_upgrade( - RuntimeOrigin::signed(1), - para_id, - new_code.clone(), - ), - paras::Error::<Test>::InvalidCode - ); - - let new_code = test_validation_code(max_code_size() as usize + 1); - assert_noop!( - Registrar::schedule_code_upgrade( - RuntimeOrigin::signed(1), - para_id, - new_code.clone(), - ), - paras::Error::<Test>::InvalidCode - ); - }); - } - - #[test] - fn register_handles_basic_errors() { - new_test_ext().execute_with(|| { - let para_id = LOWEST_PUBLIC_ID; - - assert_noop!( - Registrar::register( - RuntimeOrigin::signed(1), - para_id, - test_genesis_head(max_head_size() as usize), - test_validation_code(max_code_size() as usize), - ), - Error::<Test>::NotReserved - ); - - // Successfully register para - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - - assert_noop!( - Registrar::register( - RuntimeOrigin::signed(2), - para_id, - test_genesis_head(max_head_size() as usize), - test_validation_code(max_code_size() as usize), - ), - Error::<Test>::NotOwner - ); - - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_id, - test_genesis_head(max_head_size() as usize), - test_validation_code(max_code_size() as usize), - )); - // Can skip pre-check and deregister para which's still onboarding. - run_to_session(2); - - assert_ok!(Registrar::deregister(RuntimeOrigin::root(), para_id)); - - // Can't do it again - assert_noop!( - Registrar::register( - RuntimeOrigin::signed(1), - para_id, - test_genesis_head(max_head_size() as usize), - test_validation_code(max_code_size() as usize), - ), - Error::<Test>::NotReserved - ); - - // Head Size Check - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(2))); - assert_noop!( - Registrar::register( - RuntimeOrigin::signed(2), - para_id + 1, - test_genesis_head((max_head_size() + 1) as usize), - test_validation_code(max_code_size() as usize), - ), - Error::<Test>::HeadDataTooLarge - ); - - // Code Size Check - assert_noop!( - Registrar::register( - RuntimeOrigin::signed(2), - para_id + 1, - test_genesis_head(max_head_size() as usize), - test_validation_code((max_code_size() + 1) as usize), - ), - Error::<Test>::CodeTooLarge - ); - - // Needs enough funds for deposit - assert_noop!( - Registrar::reserve(RuntimeOrigin::signed(1337)), - BalancesError::<Test, _>::InsufficientBalance - ); - }); - } - - #[test] - fn deregister_works() { - new_test_ext().execute_with(|| { - const START_SESSION_INDEX: SessionIndex = 1; - run_to_session(START_SESSION_INDEX); - - let para_id = LOWEST_PUBLIC_ID; - assert!(!Parachains::is_parathread(para_id)); - - let validation_code = test_validation_code(32); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_id, - test_genesis_head(32), - validation_code.clone(), - )); - conclude_pvf_checking::<Test>(&validation_code, VALIDATORS, START_SESSION_INDEX); - - run_to_session(START_SESSION_INDEX + 2); - assert!(Parachains::is_parathread(para_id)); - assert_ok!(Registrar::deregister(RuntimeOrigin::root(), para_id,)); - run_to_session(START_SESSION_INDEX + 4); - assert!(paras::Pallet::<Test>::lifecycle(para_id).is_none()); - assert_eq!(Balances::reserved_balance(&1), 0); - }); - } - - #[test] - fn deregister_handles_basic_errors() { - new_test_ext().execute_with(|| { - const START_SESSION_INDEX: SessionIndex = 1; - run_to_session(START_SESSION_INDEX); - - let para_id = LOWEST_PUBLIC_ID; - assert!(!Parachains::is_parathread(para_id)); - - let validation_code = test_validation_code(32); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_id, - test_genesis_head(32), - validation_code.clone(), - )); - conclude_pvf_checking::<Test>(&validation_code, VALIDATORS, START_SESSION_INDEX); - - run_to_session(START_SESSION_INDEX + 2); - assert!(Parachains::is_parathread(para_id)); - // Owner check - assert_noop!(Registrar::deregister(RuntimeOrigin::signed(2), para_id,), BadOrigin); - assert_ok!(Registrar::make_parachain(para_id)); - run_to_session(START_SESSION_INDEX + 4); - // Cant directly deregister parachain - assert_noop!( - Registrar::deregister(RuntimeOrigin::root(), para_id,), - Error::<Test>::NotParathread - ); - }); - } - - #[test] - fn swap_works() { - new_test_ext().execute_with(|| { - const START_SESSION_INDEX: SessionIndex = 1; - run_to_session(START_SESSION_INDEX); - - // Successfully register first two parachains - let para_1 = LOWEST_PUBLIC_ID; - let para_2 = LOWEST_PUBLIC_ID + 1; - - let validation_code = test_validation_code(max_code_size() as usize); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_1, - test_genesis_head(max_head_size() as usize), - validation_code.clone(), - )); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(2))); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(2), - para_2, - test_genesis_head(max_head_size() as usize), - validation_code.clone(), - )); - conclude_pvf_checking::<Test>(&validation_code, VALIDATORS, START_SESSION_INDEX); - - run_to_session(START_SESSION_INDEX + 2); - - // Upgrade para 1 into a parachain - assert_ok!(Registrar::make_parachain(para_1)); - - // Set some mock swap data. - let mut swap_data = SwapData::get(); - swap_data.insert(para_1, 69); - swap_data.insert(para_2, 1337); - SwapData::set(swap_data); - - run_to_session(START_SESSION_INDEX + 4); - - // Roles are as we expect - assert!(Parachains::is_parachain(para_1)); - assert!(!Parachains::is_parathread(para_1)); - assert!(!Parachains::is_parachain(para_2)); - assert!(Parachains::is_parathread(para_2)); - - // Both paras initiate a swap - // Swap between parachain and parathread - assert_ok!(Registrar::swap(para_origin(para_1), para_1, para_2,)); - assert_ok!(Registrar::swap(para_origin(para_2), para_2, para_1,)); - System::assert_last_event(RuntimeEvent::Registrar(paras_registrar::Event::Swapped { - para_id: para_2, - other_id: para_1, - })); - - run_to_session(START_SESSION_INDEX + 6); - - // Roles are swapped - assert!(!Parachains::is_parachain(para_1)); - assert!(Parachains::is_parathread(para_1)); - assert!(Parachains::is_parachain(para_2)); - assert!(!Parachains::is_parathread(para_2)); - - // Data is swapped - assert_eq!(SwapData::get().get(¶_1).unwrap(), &1337); - assert_eq!(SwapData::get().get(¶_2).unwrap(), &69); - - // Both paras initiate a swap - // Swap between parathread and parachain - assert_ok!(Registrar::swap(para_origin(para_1), para_1, para_2,)); - assert_ok!(Registrar::swap(para_origin(para_2), para_2, para_1,)); - System::assert_last_event(RuntimeEvent::Registrar(paras_registrar::Event::Swapped { - para_id: para_2, - other_id: para_1, - })); - - // Data is swapped - assert_eq!(SwapData::get().get(¶_1).unwrap(), &69); - assert_eq!(SwapData::get().get(¶_2).unwrap(), &1337); - - // Parachain to parachain swap - let para_3 = LOWEST_PUBLIC_ID + 2; - let validation_code = test_validation_code(max_code_size() as usize); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(3))); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(3), - para_3, - test_genesis_head(max_head_size() as usize), - validation_code.clone(), - )); - conclude_pvf_checking::<Test>(&validation_code, VALIDATORS, START_SESSION_INDEX + 6); - - run_to_session(START_SESSION_INDEX + 8); - - // Upgrade para 3 into a parachain - assert_ok!(Registrar::make_parachain(para_3)); - - // Set some mock swap data. - let mut swap_data = SwapData::get(); - swap_data.insert(para_3, 777); - SwapData::set(swap_data); - - run_to_session(START_SESSION_INDEX + 10); - - // Both are parachains - assert!(Parachains::is_parachain(para_3)); - assert!(!Parachains::is_parathread(para_3)); - assert!(Parachains::is_parachain(para_1)); - assert!(!Parachains::is_parathread(para_1)); - - // Both paras initiate a swap - // Swap between parachain and parachain - assert_ok!(Registrar::swap(para_origin(para_1), para_1, para_3,)); - assert_ok!(Registrar::swap(para_origin(para_3), para_3, para_1,)); - System::assert_last_event(RuntimeEvent::Registrar(paras_registrar::Event::Swapped { - para_id: para_3, - other_id: para_1, - })); - - // Data is swapped - assert_eq!(SwapData::get().get(¶_3).unwrap(), &69); - assert_eq!(SwapData::get().get(¶_1).unwrap(), &777); - }); - } - - #[test] - fn para_lock_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - let para_id = LOWEST_PUBLIC_ID; - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_id, - vec![1; 3].into(), - test_validation_code(32) - )); - - assert_noop!(Registrar::add_lock(RuntimeOrigin::signed(2), para_id), BadOrigin); - - // Once they produces new block, we lock them in. - Registrar::on_new_head(para_id, &Default::default()); - - // Owner cannot pass origin check when checking lock - assert_noop!( - Registrar::ensure_root_para_or_owner(RuntimeOrigin::signed(1), para_id), - BadOrigin - ); - // Owner cannot remove lock. - assert_noop!(Registrar::remove_lock(RuntimeOrigin::signed(1), para_id), BadOrigin); - // Para can. - assert_ok!(Registrar::remove_lock(para_origin(para_id), para_id)); - // Owner can pass origin check again - assert_ok!(Registrar::ensure_root_para_or_owner(RuntimeOrigin::signed(1), para_id)); - - // Won't lock again after it is unlocked - Registrar::on_new_head(para_id, &Default::default()); - - assert_ok!(Registrar::ensure_root_para_or_owner(RuntimeOrigin::signed(1), para_id)); - }); - } - - #[test] - fn swap_handles_bad_states() { - new_test_ext().execute_with(|| { - const START_SESSION_INDEX: SessionIndex = 1; - run_to_session(START_SESSION_INDEX); - - let para_1 = LOWEST_PUBLIC_ID; - let para_2 = LOWEST_PUBLIC_ID + 1; - - // paras are not yet registered - assert!(!Parachains::is_parathread(para_1)); - assert!(!Parachains::is_parathread(para_2)); - - // Cannot even start a swap - assert_noop!( - Registrar::swap(RuntimeOrigin::root(), para_1, para_2), - Error::<Test>::NotRegistered - ); - - // We register Paras 1 and 2 - let validation_code = test_validation_code(32); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(2))); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_1, - test_genesis_head(32), - validation_code.clone(), - )); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(2), - para_2, - test_genesis_head(32), - validation_code.clone(), - )); - conclude_pvf_checking::<Test>(&validation_code, VALIDATORS, START_SESSION_INDEX); - - // Cannot swap - assert_ok!(Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); - assert_noop!( - Registrar::swap(RuntimeOrigin::root(), para_2, para_1), - Error::<Test>::CannotSwap - ); - - run_to_session(START_SESSION_INDEX + 2); - - // They are now parathreads (on-demand parachains). - assert!(Parachains::is_parathread(para_1)); - assert!(Parachains::is_parathread(para_2)); - - // Cannot swap - assert_ok!(Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); - assert_noop!( - Registrar::swap(RuntimeOrigin::root(), para_2, para_1), - Error::<Test>::CannotSwap - ); - - // Some other external process will elevate one on-demand - // parachain to a lease holding parachain - assert_ok!(Registrar::make_parachain(para_1)); - - // Cannot swap - assert_ok!(Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); - assert_noop!( - Registrar::swap(RuntimeOrigin::root(), para_2, para_1), - Error::<Test>::CannotSwap - ); - - run_to_session(START_SESSION_INDEX + 3); - - // Cannot swap - assert_ok!(Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); - assert_noop!( - Registrar::swap(RuntimeOrigin::root(), para_2, para_1), - Error::<Test>::CannotSwap - ); - - run_to_session(START_SESSION_INDEX + 4); - - // It is now a lease holding parachain. - assert!(Parachains::is_parachain(para_1)); - assert!(Parachains::is_parathread(para_2)); - - // Swap works here. - assert_ok!(Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); - assert_ok!(Registrar::swap(RuntimeOrigin::root(), para_2, para_1)); - assert!(System::events().iter().any(|r| matches!( - r.event, - RuntimeEvent::Registrar(paras_registrar::Event::Swapped { .. }) - ))); - - run_to_session(START_SESSION_INDEX + 5); - - // Cannot swap - assert_ok!(Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); - assert_noop!( - Registrar::swap(RuntimeOrigin::root(), para_2, para_1), - Error::<Test>::CannotSwap - ); - - run_to_session(START_SESSION_INDEX + 6); - - // Swap worked! - assert!(Parachains::is_parachain(para_2)); - assert!(Parachains::is_parathread(para_1)); - assert!(System::events().iter().any(|r| matches!( - r.event, - RuntimeEvent::Registrar(paras_registrar::Event::Swapped { .. }) - ))); - - // Something starts to downgrade a para - assert_ok!(Registrar::make_parathread(para_2)); - - run_to_session(START_SESSION_INDEX + 7); - - // Cannot swap - assert_ok!(Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); - assert_noop!( - Registrar::swap(RuntimeOrigin::root(), para_2, para_1), - Error::<Test>::CannotSwap - ); - - run_to_session(START_SESSION_INDEX + 8); - - assert!(Parachains::is_parathread(para_1)); - assert!(Parachains::is_parathread(para_2)); - }); - } -} +#[cfg(test)] +mod tests; #[cfg(feature = "runtime-benchmarks")] -mod benchmarking { - use super::{Pallet as Registrar, *}; - use crate::traits::Registrar as RegistrarT; - use frame_support::assert_ok; - use frame_system::RawOrigin; - use polkadot_primitives::{MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MIN_CODE_SIZE}; - use polkadot_runtime_parachains::{paras, shared, Origin as ParaOrigin}; - use sp_runtime::traits::Bounded; - - use frame_benchmarking::{account, benchmarks, whitelisted_caller}; - - fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) { - let events = frame_system::Pallet::<T>::events(); - let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into(); - // compare to the last event record - let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; - assert_eq!(event, &system_event); - } - - fn register_para<T: Config>(id: u32) -> ParaId { - let para = ParaId::from(id); - let genesis_head = Registrar::<T>::worst_head_data(); - let validation_code = Registrar::<T>::worst_validation_code(); - let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value()); - assert_ok!(Registrar::<T>::reserve(RawOrigin::Signed(caller.clone()).into())); - assert_ok!(Registrar::<T>::register( - RawOrigin::Signed(caller).into(), - para, - genesis_head, - validation_code.clone() - )); - assert_ok!(polkadot_runtime_parachains::paras::Pallet::<T>::add_trusted_validation_code( - frame_system::Origin::<T>::Root.into(), - validation_code, - )); - return para - } - - fn para_origin(id: u32) -> ParaOrigin { - ParaOrigin::Parachain(id.into()) - } - - // This function moves forward to the next scheduled session for parachain lifecycle upgrades. - fn next_scheduled_session<T: Config>() { - shared::Pallet::<T>::set_session_index(shared::Pallet::<T>::scheduled_session()); - paras::Pallet::<T>::test_on_new_session(); - } - - benchmarks! { - where_clause { where ParaOrigin: Into<<T as frame_system::Config>::RuntimeOrigin> } - - reserve { - let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value()); - }: _(RawOrigin::Signed(caller.clone())) - verify { - assert_last_event::<T>(Event::<T>::Reserved { para_id: LOWEST_PUBLIC_ID, who: caller }.into()); - assert!(Paras::<T>::get(LOWEST_PUBLIC_ID).is_some()); - assert_eq!(paras::Pallet::<T>::lifecycle(LOWEST_PUBLIC_ID), None); - } - - register { - let para = LOWEST_PUBLIC_ID; - let genesis_head = Registrar::<T>::worst_head_data(); - let validation_code = Registrar::<T>::worst_validation_code(); - let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value()); - assert_ok!(Registrar::<T>::reserve(RawOrigin::Signed(caller.clone()).into())); - }: _(RawOrigin::Signed(caller.clone()), para, genesis_head, validation_code.clone()) - verify { - assert_last_event::<T>(Event::<T>::Registered{ para_id: para, manager: caller }.into()); - assert_eq!(paras::Pallet::<T>::lifecycle(para), Some(ParaLifecycle::Onboarding)); - assert_ok!(polkadot_runtime_parachains::paras::Pallet::<T>::add_trusted_validation_code( - frame_system::Origin::<T>::Root.into(), - validation_code, - )); - next_scheduled_session::<T>(); - assert_eq!(paras::Pallet::<T>::lifecycle(para), Some(ParaLifecycle::Parathread)); - } - - force_register { - let manager: T::AccountId = account("manager", 0, 0); - let deposit = 0u32.into(); - let para = ParaId::from(69); - let genesis_head = Registrar::<T>::worst_head_data(); - let validation_code = Registrar::<T>::worst_validation_code(); - }: _(RawOrigin::Root, manager.clone(), deposit, para, genesis_head, validation_code.clone()) - verify { - assert_last_event::<T>(Event::<T>::Registered { para_id: para, manager }.into()); - assert_eq!(paras::Pallet::<T>::lifecycle(para), Some(ParaLifecycle::Onboarding)); - assert_ok!(polkadot_runtime_parachains::paras::Pallet::<T>::add_trusted_validation_code( - frame_system::Origin::<T>::Root.into(), - validation_code, - )); - next_scheduled_session::<T>(); - assert_eq!(paras::Pallet::<T>::lifecycle(para), Some(ParaLifecycle::Parathread)); - } - - deregister { - let para = register_para::<T>(LOWEST_PUBLIC_ID.into()); - next_scheduled_session::<T>(); - let caller: T::AccountId = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), para) - verify { - assert_last_event::<T>(Event::<T>::Deregistered { para_id: para }.into()); - } - - swap { - // On demand parachain - let parathread = register_para::<T>(LOWEST_PUBLIC_ID.into()); - let parachain = register_para::<T>((LOWEST_PUBLIC_ID + 1).into()); - - let parachain_origin = para_origin(parachain.into()); - - // Actually finish registration process - next_scheduled_session::<T>(); - - // Upgrade the parachain - Registrar::<T>::make_parachain(parachain)?; - next_scheduled_session::<T>(); - - assert_eq!(paras::Pallet::<T>::lifecycle(parachain), Some(ParaLifecycle::Parachain)); - assert_eq!(paras::Pallet::<T>::lifecycle(parathread), Some(ParaLifecycle::Parathread)); - - let caller: T::AccountId = whitelisted_caller(); - Registrar::<T>::swap(parachain_origin.into(), parachain, parathread)?; - }: _(RawOrigin::Signed(caller.clone()), parathread, parachain) - verify { - next_scheduled_session::<T>(); - // Swapped! - assert_eq!(paras::Pallet::<T>::lifecycle(parachain), Some(ParaLifecycle::Parathread)); - assert_eq!(paras::Pallet::<T>::lifecycle(parathread), Some(ParaLifecycle::Parachain)); - } - - schedule_code_upgrade { - let b in MIN_CODE_SIZE .. MAX_CODE_SIZE; - let new_code = ValidationCode(vec![0; b as usize]); - let para_id = ParaId::from(1000); - }: _(RawOrigin::Root, para_id, new_code) - - set_current_head { - let b in 1 .. MAX_HEAD_DATA_SIZE; - let new_head = HeadData(vec![0; b as usize]); - let para_id = ParaId::from(1000); - }: _(RawOrigin::Root, para_id, new_head) - - impl_benchmark_test_suite!( - Registrar, - crate::integration_tests::new_test_ext(), - crate::integration_tests::Test, - ); - } -} +mod benchmarking; diff --git a/polkadot/runtime/common/src/paras_registrar/tests.rs b/polkadot/runtime/common/src/paras_registrar/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..66fef31c9afd8229d939b83a7e1df910364d597f --- /dev/null +++ b/polkadot/runtime/common/src/paras_registrar/tests.rs @@ -0,0 +1,588 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Tests for the paras_registrar pallet. + +#[cfg(test)] +use super::*; +use crate::{ + mock::conclude_pvf_checking, paras_registrar, paras_registrar::mock::*, + traits::Registrar as RegistrarTrait, +}; +use frame_support::{assert_noop, assert_ok}; +use pallet_balances::Error as BalancesError; +use polkadot_primitives::SessionIndex; +use sp_runtime::traits::BadOrigin; + +#[test] +fn end_to_end_scenario_works() { + new_test_ext().execute_with(|| { + let para_id = LOWEST_PUBLIC_ID; + + const START_SESSION_INDEX: SessionIndex = 1; + run_to_session(START_SESSION_INDEX); + + // first para is not yet registered + assert!(!Parachains::is_parathread(para_id)); + // We register the Para ID + let validation_code = test_validation_code(32); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(32), + validation_code.clone(), + )); + conclude_pvf_checking::<Test>(&validation_code, VALIDATORS, START_SESSION_INDEX); + + run_to_session(START_SESSION_INDEX + 2); + // It is now a parathread (on-demand parachain). + assert!(Parachains::is_parathread(para_id)); + assert!(!Parachains::is_parachain(para_id)); + // Some other external process will elevate on-demand to lease holding parachain + assert_ok!(mock::Registrar::make_parachain(para_id)); + run_to_session(START_SESSION_INDEX + 4); + // It is now a lease holding parachain. + assert!(!Parachains::is_parathread(para_id)); + assert!(Parachains::is_parachain(para_id)); + // Turn it back into a parathread (on-demand parachain) + assert_ok!(mock::Registrar::make_parathread(para_id)); + run_to_session(START_SESSION_INDEX + 6); + assert!(Parachains::is_parathread(para_id)); + assert!(!Parachains::is_parachain(para_id)); + // Deregister it + assert_ok!(mock::Registrar::deregister(RuntimeOrigin::root(), para_id,)); + run_to_session(START_SESSION_INDEX + 8); + // It is nothing + assert!(!Parachains::is_parathread(para_id)); + assert!(!Parachains::is_parachain(para_id)); + }); +} + +#[test] +fn register_works() { + new_test_ext().execute_with(|| { + const START_SESSION_INDEX: SessionIndex = 1; + run_to_session(START_SESSION_INDEX); + + let para_id = LOWEST_PUBLIC_ID; + assert!(!Parachains::is_parathread(para_id)); + + let validation_code = test_validation_code(32); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + assert_eq!(Balances::reserved_balance(&1), <Test as Config>::ParaDeposit::get()); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(32), + validation_code.clone(), + )); + conclude_pvf_checking::<Test>(&validation_code, VALIDATORS, START_SESSION_INDEX); + + run_to_session(START_SESSION_INDEX + 2); + assert!(Parachains::is_parathread(para_id)); + // Even though the registered validation code has a smaller size than the maximum the + // para manager's deposit is reserved as though they registered the maximum-sized code. + // Consequently, they can upgrade their code to the maximum size at any point without + // additional cost. + let validation_code_deposit = + max_code_size() as BalanceOf<Test> * <Test as Config>::DataDepositPerByte::get(); + let head_deposit = 32 * <Test as Config>::DataDepositPerByte::get(); + assert_eq!( + Balances::reserved_balance(&1), + <Test as Config>::ParaDeposit::get() + head_deposit + validation_code_deposit + ); + }); +} + +#[test] +fn schedule_code_upgrade_validates_code() { + new_test_ext().execute_with(|| { + const START_SESSION_INDEX: SessionIndex = 1; + run_to_session(START_SESSION_INDEX); + + let para_id = LOWEST_PUBLIC_ID; + assert!(!Parachains::is_parathread(para_id)); + + let validation_code = test_validation_code(32); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + assert_eq!(Balances::reserved_balance(&1), <Test as Config>::ParaDeposit::get()); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(32), + validation_code.clone(), + )); + conclude_pvf_checking::<Test>(&validation_code, VALIDATORS, START_SESSION_INDEX); + + run_to_session(START_SESSION_INDEX + 2); + assert!(Parachains::is_parathread(para_id)); + + let new_code = test_validation_code(0); + assert_noop!( + mock::Registrar::schedule_code_upgrade( + RuntimeOrigin::signed(1), + para_id, + new_code.clone(), + ), + paras::Error::<Test>::InvalidCode + ); + + let new_code = test_validation_code(max_code_size() as usize + 1); + assert_noop!( + mock::Registrar::schedule_code_upgrade( + RuntimeOrigin::signed(1), + para_id, + new_code.clone(), + ), + paras::Error::<Test>::InvalidCode + ); + }); +} + +#[test] +fn register_handles_basic_errors() { + new_test_ext().execute_with(|| { + let para_id = LOWEST_PUBLIC_ID; + + assert_noop!( + mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(max_head_size() as usize), + test_validation_code(max_code_size() as usize), + ), + Error::<Test>::NotReserved + ); + + // Successfully register para + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + + assert_noop!( + mock::Registrar::register( + RuntimeOrigin::signed(2), + para_id, + test_genesis_head(max_head_size() as usize), + test_validation_code(max_code_size() as usize), + ), + Error::<Test>::NotOwner + ); + + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(max_head_size() as usize), + test_validation_code(max_code_size() as usize), + )); + // Can skip pre-check and deregister para which's still onboarding. + run_to_session(2); + + assert_ok!(mock::Registrar::deregister(RuntimeOrigin::root(), para_id)); + + // Can't do it again + assert_noop!( + mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(max_head_size() as usize), + test_validation_code(max_code_size() as usize), + ), + Error::<Test>::NotReserved + ); + + // Head Size Check + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(2))); + assert_noop!( + mock::Registrar::register( + RuntimeOrigin::signed(2), + para_id + 1, + test_genesis_head((max_head_size() + 1) as usize), + test_validation_code(max_code_size() as usize), + ), + Error::<Test>::HeadDataTooLarge + ); + + // Code Size Check + assert_noop!( + mock::Registrar::register( + RuntimeOrigin::signed(2), + para_id + 1, + test_genesis_head(max_head_size() as usize), + test_validation_code((max_code_size() + 1) as usize), + ), + Error::<Test>::CodeTooLarge + ); + + // Needs enough funds for deposit + assert_noop!( + mock::Registrar::reserve(RuntimeOrigin::signed(1337)), + BalancesError::<Test, _>::InsufficientBalance + ); + }); +} + +#[test] +fn deregister_works() { + new_test_ext().execute_with(|| { + const START_SESSION_INDEX: SessionIndex = 1; + run_to_session(START_SESSION_INDEX); + + let para_id = LOWEST_PUBLIC_ID; + assert!(!Parachains::is_parathread(para_id)); + + let validation_code = test_validation_code(32); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(32), + validation_code.clone(), + )); + conclude_pvf_checking::<Test>(&validation_code, VALIDATORS, START_SESSION_INDEX); + + run_to_session(START_SESSION_INDEX + 2); + assert!(Parachains::is_parathread(para_id)); + assert_ok!(mock::Registrar::deregister(RuntimeOrigin::root(), para_id,)); + run_to_session(START_SESSION_INDEX + 4); + assert!(paras::Pallet::<Test>::lifecycle(para_id).is_none()); + assert_eq!(Balances::reserved_balance(&1), 0); + }); +} + +#[test] +fn deregister_handles_basic_errors() { + new_test_ext().execute_with(|| { + const START_SESSION_INDEX: SessionIndex = 1; + run_to_session(START_SESSION_INDEX); + + let para_id = LOWEST_PUBLIC_ID; + assert!(!Parachains::is_parathread(para_id)); + + let validation_code = test_validation_code(32); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(32), + validation_code.clone(), + )); + conclude_pvf_checking::<Test>(&validation_code, VALIDATORS, START_SESSION_INDEX); + + run_to_session(START_SESSION_INDEX + 2); + assert!(Parachains::is_parathread(para_id)); + // Owner check + assert_noop!(mock::Registrar::deregister(RuntimeOrigin::signed(2), para_id,), BadOrigin); + assert_ok!(mock::Registrar::make_parachain(para_id)); + run_to_session(START_SESSION_INDEX + 4); + // Cant directly deregister parachain + assert_noop!( + mock::Registrar::deregister(RuntimeOrigin::root(), para_id,), + Error::<Test>::NotParathread + ); + }); +} + +#[test] +fn swap_works() { + new_test_ext().execute_with(|| { + const START_SESSION_INDEX: SessionIndex = 1; + run_to_session(START_SESSION_INDEX); + + // Successfully register first two parachains + let para_1 = LOWEST_PUBLIC_ID; + let para_2 = LOWEST_PUBLIC_ID + 1; + + let validation_code = test_validation_code(max_code_size() as usize); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_1, + test_genesis_head(max_head_size() as usize), + validation_code.clone(), + )); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(2))); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(2), + para_2, + test_genesis_head(max_head_size() as usize), + validation_code.clone(), + )); + conclude_pvf_checking::<Test>(&validation_code, VALIDATORS, START_SESSION_INDEX); + + run_to_session(START_SESSION_INDEX + 2); + + // Upgrade para 1 into a parachain + assert_ok!(mock::Registrar::make_parachain(para_1)); + + // Set some mock swap data. + let mut swap_data = SwapData::get(); + swap_data.insert(para_1, 69); + swap_data.insert(para_2, 1337); + SwapData::set(swap_data); + + run_to_session(START_SESSION_INDEX + 4); + + // Roles are as we expect + assert!(Parachains::is_parachain(para_1)); + assert!(!Parachains::is_parathread(para_1)); + assert!(!Parachains::is_parachain(para_2)); + assert!(Parachains::is_parathread(para_2)); + + // Both paras initiate a swap + // Swap between parachain and parathread + assert_ok!(mock::Registrar::swap(para_origin(para_1), para_1, para_2,)); + assert_ok!(mock::Registrar::swap(para_origin(para_2), para_2, para_1,)); + System::assert_last_event(RuntimeEvent::Registrar(paras_registrar::Event::Swapped { + para_id: para_2, + other_id: para_1, + })); + + run_to_session(START_SESSION_INDEX + 6); + + // Roles are swapped + assert!(!Parachains::is_parachain(para_1)); + assert!(Parachains::is_parathread(para_1)); + assert!(Parachains::is_parachain(para_2)); + assert!(!Parachains::is_parathread(para_2)); + + // Data is swapped + assert_eq!(SwapData::get().get(¶_1).unwrap(), &1337); + assert_eq!(SwapData::get().get(¶_2).unwrap(), &69); + + // Both paras initiate a swap + // Swap between parathread and parachain + assert_ok!(mock::Registrar::swap(para_origin(para_1), para_1, para_2,)); + assert_ok!(mock::Registrar::swap(para_origin(para_2), para_2, para_1,)); + System::assert_last_event(RuntimeEvent::Registrar(paras_registrar::Event::Swapped { + para_id: para_2, + other_id: para_1, + })); + + // Data is swapped + assert_eq!(SwapData::get().get(¶_1).unwrap(), &69); + assert_eq!(SwapData::get().get(¶_2).unwrap(), &1337); + + // Parachain to parachain swap + let para_3 = LOWEST_PUBLIC_ID + 2; + let validation_code = test_validation_code(max_code_size() as usize); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(3))); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(3), + para_3, + test_genesis_head(max_head_size() as usize), + validation_code.clone(), + )); + conclude_pvf_checking::<Test>(&validation_code, VALIDATORS, START_SESSION_INDEX + 6); + + run_to_session(START_SESSION_INDEX + 8); + + // Upgrade para 3 into a parachain + assert_ok!(mock::Registrar::make_parachain(para_3)); + + // Set some mock swap data. + let mut swap_data = SwapData::get(); + swap_data.insert(para_3, 777); + SwapData::set(swap_data); + + run_to_session(START_SESSION_INDEX + 10); + + // Both are parachains + assert!(Parachains::is_parachain(para_3)); + assert!(!Parachains::is_parathread(para_3)); + assert!(Parachains::is_parachain(para_1)); + assert!(!Parachains::is_parathread(para_1)); + + // Both paras initiate a swap + // Swap between parachain and parachain + assert_ok!(mock::Registrar::swap(para_origin(para_1), para_1, para_3,)); + assert_ok!(mock::Registrar::swap(para_origin(para_3), para_3, para_1,)); + System::assert_last_event(RuntimeEvent::Registrar(paras_registrar::Event::Swapped { + para_id: para_3, + other_id: para_1, + })); + + // Data is swapped + assert_eq!(SwapData::get().get(¶_3).unwrap(), &69); + assert_eq!(SwapData::get().get(¶_1).unwrap(), &777); + }); +} + +#[test] +fn para_lock_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + let para_id = LOWEST_PUBLIC_ID; + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + vec![1; 3].into(), + test_validation_code(32) + )); + + assert_noop!(mock::Registrar::add_lock(RuntimeOrigin::signed(2), para_id), BadOrigin); + + // Once they produces new block, we lock them in. + mock::Registrar::on_new_head(para_id, &Default::default()); + + // Owner cannot pass origin check when checking lock + assert_noop!( + mock::Registrar::ensure_root_para_or_owner(RuntimeOrigin::signed(1), para_id), + Error::<Test>::ParaLocked, + ); + // Owner cannot remove lock. + assert_noop!(mock::Registrar::remove_lock(RuntimeOrigin::signed(1), para_id), BadOrigin); + // Para can. + assert_ok!(mock::Registrar::remove_lock(para_origin(para_id), para_id)); + // Owner can pass origin check again + assert_ok!(mock::Registrar::ensure_root_para_or_owner(RuntimeOrigin::signed(1), para_id)); + + // Won't lock again after it is unlocked + mock::Registrar::on_new_head(para_id, &Default::default()); + + assert_ok!(mock::Registrar::ensure_root_para_or_owner(RuntimeOrigin::signed(1), para_id)); + }); +} + +#[test] +fn swap_handles_bad_states() { + new_test_ext().execute_with(|| { + const START_SESSION_INDEX: SessionIndex = 1; + run_to_session(START_SESSION_INDEX); + + let para_1 = LOWEST_PUBLIC_ID; + let para_2 = LOWEST_PUBLIC_ID + 1; + + // paras are not yet registered + assert!(!Parachains::is_parathread(para_1)); + assert!(!Parachains::is_parathread(para_2)); + + // Cannot even start a swap + assert_noop!( + mock::Registrar::swap(RuntimeOrigin::root(), para_1, para_2), + Error::<Test>::NotRegistered + ); + + // We register Paras 1 and 2 + let validation_code = test_validation_code(32); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(2))); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_1, + test_genesis_head(32), + validation_code.clone(), + )); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(2), + para_2, + test_genesis_head(32), + validation_code.clone(), + )); + conclude_pvf_checking::<Test>(&validation_code, VALIDATORS, START_SESSION_INDEX); + + // Cannot swap + assert_ok!(mock::Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); + assert_noop!( + mock::Registrar::swap(RuntimeOrigin::root(), para_2, para_1), + Error::<Test>::CannotSwap + ); + + run_to_session(START_SESSION_INDEX + 2); + + // They are now parathreads (on-demand parachains). + assert!(Parachains::is_parathread(para_1)); + assert!(Parachains::is_parathread(para_2)); + + // Cannot swap + assert_ok!(mock::Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); + assert_noop!( + mock::Registrar::swap(RuntimeOrigin::root(), para_2, para_1), + Error::<Test>::CannotSwap + ); + + // Some other external process will elevate one on-demand + // parachain to a lease holding parachain + assert_ok!(mock::Registrar::make_parachain(para_1)); + + // Cannot swap + assert_ok!(mock::Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); + assert_noop!( + mock::Registrar::swap(RuntimeOrigin::root(), para_2, para_1), + Error::<Test>::CannotSwap + ); + + run_to_session(START_SESSION_INDEX + 3); + + // Cannot swap + assert_ok!(mock::Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); + assert_noop!( + mock::Registrar::swap(RuntimeOrigin::root(), para_2, para_1), + Error::<Test>::CannotSwap + ); + + run_to_session(START_SESSION_INDEX + 4); + + // It is now a lease holding parachain. + assert!(Parachains::is_parachain(para_1)); + assert!(Parachains::is_parathread(para_2)); + + // Swap works here. + assert_ok!(mock::Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); + assert_ok!(mock::Registrar::swap(RuntimeOrigin::root(), para_2, para_1)); + assert!(System::events().iter().any(|r| matches!( + r.event, + RuntimeEvent::Registrar(paras_registrar::Event::Swapped { .. }) + ))); + + run_to_session(START_SESSION_INDEX + 5); + + // Cannot swap + assert_ok!(mock::Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); + assert_noop!( + mock::Registrar::swap(RuntimeOrigin::root(), para_2, para_1), + Error::<Test>::CannotSwap + ); + + run_to_session(START_SESSION_INDEX + 6); + + // Swap worked! + assert!(Parachains::is_parachain(para_2)); + assert!(Parachains::is_parathread(para_1)); + assert!(System::events().iter().any(|r| matches!( + r.event, + RuntimeEvent::Registrar(paras_registrar::Event::Swapped { .. }) + ))); + + // Something starts to downgrade a para + assert_ok!(mock::Registrar::make_parathread(para_2)); + + run_to_session(START_SESSION_INDEX + 7); + + // Cannot swap + assert_ok!(mock::Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); + assert_noop!( + mock::Registrar::swap(RuntimeOrigin::root(), para_2, para_1), + Error::<Test>::CannotSwap + ); + + run_to_session(START_SESSION_INDEX + 8); + + assert!(Parachains::is_parathread(para_1)); + assert!(Parachains::is_parathread(para_2)); + }); +} diff --git a/polkadot/runtime/common/src/paras_sudo_wrapper.rs b/polkadot/runtime/common/src/paras_sudo_wrapper.rs index a93c209e927995ea34f26c6f90ba0d2cd798ce40..bd5984b3b63eee0f5cdf1dc92ccc2cb06a00a658 100644 --- a/polkadot/runtime/common/src/paras_sudo_wrapper.rs +++ b/polkadot/runtime/common/src/paras_sudo_wrapper.rs @@ -48,6 +48,8 @@ pub mod pallet { /// A DMP message couldn't be sent because it exceeds the maximum size allowed for a /// downward message. ExceedsMaxMessageSize, + /// A DMP message couldn't be sent because the destination is unreachable. + Unroutable, /// Could not schedule para cleanup. CouldntCleanup, /// Not a parathread (on-demand parachain). @@ -157,6 +159,7 @@ pub mod pallet { { dmp::QueueDownwardMessageError::ExceedsMaxMessageSize => Error::<T>::ExceedsMaxMessageSize.into(), + dmp::QueueDownwardMessageError::Unroutable => Error::<T>::Unroutable.into(), }) } diff --git a/polkadot/runtime/common/src/purchase.rs b/polkadot/runtime/common/src/purchase.rs deleted file mode 100644 index cec92540654cc8567ebc8aef297cd987ca235e11..0000000000000000000000000000000000000000 --- a/polkadot/runtime/common/src/purchase.rs +++ /dev/null @@ -1,1178 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see <http://www.gnu.org/licenses/>. - -//! Pallet to process purchase of DOTs. - -use alloc::vec::Vec; -use codec::{Decode, Encode}; -use frame_support::{ - pallet_prelude::*, - traits::{Currency, EnsureOrigin, ExistenceRequirement, Get, VestingSchedule}, -}; -use frame_system::pallet_prelude::*; -pub use pallet::*; -use scale_info::TypeInfo; -use sp_core::sr25519; -use sp_runtime::{ - traits::{CheckedAdd, Saturating, Verify, Zero}, - AnySignature, DispatchError, DispatchResult, Permill, RuntimeDebug, -}; - -type BalanceOf<T> = - <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance; - -/// The kind of statement an account needs to make for a claim to be valid. -#[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub enum AccountValidity { - /// Account is not valid. - Invalid, - /// Account has initiated the account creation process. - Initiated, - /// Account is pending validation. - Pending, - /// Account is valid with a low contribution amount. - ValidLow, - /// Account is valid with a high contribution amount. - ValidHigh, - /// Account has completed the purchase process. - Completed, -} - -impl Default for AccountValidity { - fn default() -> Self { - AccountValidity::Invalid - } -} - -impl AccountValidity { - fn is_valid(&self) -> bool { - match self { - Self::Invalid => false, - Self::Initiated => false, - Self::Pending => false, - Self::ValidLow => true, - Self::ValidHigh => true, - Self::Completed => false, - } - } -} - -/// All information about an account regarding the purchase of DOTs. -#[derive(Encode, Decode, Default, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct AccountStatus<Balance> { - /// The current validity status of the user. Will denote if the user has passed KYC, - /// how much they are able to purchase, and when their purchase process has completed. - validity: AccountValidity, - /// The amount of free DOTs they have purchased. - free_balance: Balance, - /// The amount of locked DOTs they have purchased. - locked_balance: Balance, - /// Their sr25519/ed25519 signature verifying they have signed our required statement. - signature: Vec<u8>, - /// The percentage of VAT the purchaser is responsible for. This is already factored into - /// account balance. - vat: Permill, -} - -#[frame_support::pallet] -pub mod pallet { - use super::*; - - #[pallet::pallet] - #[pallet::without_storage_info] - pub struct Pallet<T>(_); - - #[pallet::config] - pub trait Config: frame_system::Config { - /// The overarching event type. - type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; - - /// Balances Pallet - type Currency: Currency<Self::AccountId>; - - /// Vesting Pallet - type VestingSchedule: VestingSchedule< - Self::AccountId, - Moment = BlockNumberFor<Self>, - Currency = Self::Currency, - >; - - /// The origin allowed to set account status. - type ValidityOrigin: EnsureOrigin<Self::RuntimeOrigin>; - - /// The origin allowed to make configurations to the pallet. - type ConfigurationOrigin: EnsureOrigin<Self::RuntimeOrigin>; - - /// The maximum statement length for the statement users to sign when creating an account. - #[pallet::constant] - type MaxStatementLength: Get<u32>; - - /// The amount of purchased locked DOTs that we will unlock for basic actions on the chain. - #[pallet::constant] - type UnlockedProportion: Get<Permill>; - - /// The maximum amount of locked DOTs that we will unlock. - #[pallet::constant] - type MaxUnlocked: Get<BalanceOf<Self>>; - } - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event<T: Config> { - /// A new account was created. - AccountCreated { who: T::AccountId }, - /// Someone's account validity was updated. - ValidityUpdated { who: T::AccountId, validity: AccountValidity }, - /// Someone's purchase balance was updated. - BalanceUpdated { who: T::AccountId, free: BalanceOf<T>, locked: BalanceOf<T> }, - /// A payout was made to a purchaser. - PaymentComplete { who: T::AccountId, free: BalanceOf<T>, locked: BalanceOf<T> }, - /// A new payment account was set. - PaymentAccountSet { who: T::AccountId }, - /// A new statement was set. - StatementUpdated, - /// A new statement was set. `[block_number]` - UnlockBlockUpdated { block_number: BlockNumberFor<T> }, - } - - #[pallet::error] - pub enum Error<T> { - /// Account is not currently valid to use. - InvalidAccount, - /// Account used in the purchase already exists. - ExistingAccount, - /// Provided signature is invalid - InvalidSignature, - /// Account has already completed the purchase process. - AlreadyCompleted, - /// An overflow occurred when doing calculations. - Overflow, - /// The statement is too long to be stored on chain. - InvalidStatement, - /// The unlock block is in the past! - InvalidUnlockBlock, - /// Vesting schedule already exists for this account. - VestingScheduleExists, - } - - // A map of all participants in the DOT purchase process. - #[pallet::storage] - pub(super) type Accounts<T: Config> = - StorageMap<_, Blake2_128Concat, T::AccountId, AccountStatus<BalanceOf<T>>, ValueQuery>; - - // The account that will be used to payout participants of the DOT purchase process. - #[pallet::storage] - pub(super) type PaymentAccount<T: Config> = StorageValue<_, T::AccountId, OptionQuery>; - - // The statement purchasers will need to sign to participate. - #[pallet::storage] - pub(super) type Statement<T> = StorageValue<_, Vec<u8>, ValueQuery>; - - // The block where all locked dots will unlock. - #[pallet::storage] - pub(super) type UnlockBlock<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>; - - #[pallet::hooks] - impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {} - - #[pallet::call] - impl<T: Config> Pallet<T> { - /// Create a new account. Proof of existence through a valid signed message. - /// - /// We check that the account does not exist at this stage. - /// - /// Origin must match the `ValidityOrigin`. - #[pallet::call_index(0)] - #[pallet::weight(Weight::from_parts(200_000_000, 0) + T::DbWeight::get().reads_writes(4, 1))] - pub fn create_account( - origin: OriginFor<T>, - who: T::AccountId, - signature: Vec<u8>, - ) -> DispatchResult { - T::ValidityOrigin::ensure_origin(origin)?; - // Account is already being tracked by the pallet. - ensure!(!Accounts::<T>::contains_key(&who), Error::<T>::ExistingAccount); - // Account should not have a vesting schedule. - ensure!( - T::VestingSchedule::vesting_balance(&who).is_none(), - Error::<T>::VestingScheduleExists - ); - - // Verify the signature provided is valid for the statement. - Self::verify_signature(&who, &signature)?; - - // Create a new pending account. - let status = AccountStatus { - validity: AccountValidity::Initiated, - signature, - free_balance: Zero::zero(), - locked_balance: Zero::zero(), - vat: Permill::zero(), - }; - Accounts::<T>::insert(&who, status); - Self::deposit_event(Event::<T>::AccountCreated { who }); - Ok(()) - } - - /// Update the validity status of an existing account. If set to completed, the account - /// will no longer be able to continue through the crowdfund process. - /// - /// We check that the account exists at this stage, but has not completed the process. - /// - /// Origin must match the `ValidityOrigin`. - #[pallet::call_index(1)] - #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] - pub fn update_validity_status( - origin: OriginFor<T>, - who: T::AccountId, - validity: AccountValidity, - ) -> DispatchResult { - T::ValidityOrigin::ensure_origin(origin)?; - ensure!(Accounts::<T>::contains_key(&who), Error::<T>::InvalidAccount); - Accounts::<T>::try_mutate( - &who, - |status: &mut AccountStatus<BalanceOf<T>>| -> DispatchResult { - ensure!( - status.validity != AccountValidity::Completed, - Error::<T>::AlreadyCompleted - ); - status.validity = validity; - Ok(()) - }, - )?; - Self::deposit_event(Event::<T>::ValidityUpdated { who, validity }); - Ok(()) - } - - /// Update the balance of a valid account. - /// - /// We check that the account is valid for a balance transfer at this point. - /// - /// Origin must match the `ValidityOrigin`. - #[pallet::call_index(2)] - #[pallet::weight(T::DbWeight::get().reads_writes(2, 1))] - pub fn update_balance( - origin: OriginFor<T>, - who: T::AccountId, - free_balance: BalanceOf<T>, - locked_balance: BalanceOf<T>, - vat: Permill, - ) -> DispatchResult { - T::ValidityOrigin::ensure_origin(origin)?; - - Accounts::<T>::try_mutate( - &who, - |status: &mut AccountStatus<BalanceOf<T>>| -> DispatchResult { - // Account has a valid status (not Invalid, Pending, or Completed)... - ensure!(status.validity.is_valid(), Error::<T>::InvalidAccount); - - free_balance.checked_add(&locked_balance).ok_or(Error::<T>::Overflow)?; - status.free_balance = free_balance; - status.locked_balance = locked_balance; - status.vat = vat; - Ok(()) - }, - )?; - Self::deposit_event(Event::<T>::BalanceUpdated { - who, - free: free_balance, - locked: locked_balance, - }); - Ok(()) - } - - /// Pay the user and complete the purchase process. - /// - /// We reverify all assumptions about the state of an account, and complete the process. - /// - /// Origin must match the configured `PaymentAccount` (if it is not configured then this - /// will always fail with `BadOrigin`). - #[pallet::call_index(3)] - #[pallet::weight(T::DbWeight::get().reads_writes(4, 2))] - pub fn payout(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult { - // Payments must be made directly by the `PaymentAccount`. - let payment_account = ensure_signed(origin)?; - let test_against = PaymentAccount::<T>::get().ok_or(DispatchError::BadOrigin)?; - ensure!(payment_account == test_against, DispatchError::BadOrigin); - - // Account should not have a vesting schedule. - ensure!( - T::VestingSchedule::vesting_balance(&who).is_none(), - Error::<T>::VestingScheduleExists - ); - - Accounts::<T>::try_mutate( - &who, - |status: &mut AccountStatus<BalanceOf<T>>| -> DispatchResult { - // Account has a valid status (not Invalid, Pending, or Completed)... - ensure!(status.validity.is_valid(), Error::<T>::InvalidAccount); - - // Transfer funds from the payment account into the purchasing user. - let total_balance = status - .free_balance - .checked_add(&status.locked_balance) - .ok_or(Error::<T>::Overflow)?; - T::Currency::transfer( - &payment_account, - &who, - total_balance, - ExistenceRequirement::AllowDeath, - )?; - - if !status.locked_balance.is_zero() { - let unlock_block = UnlockBlock::<T>::get(); - // We allow some configurable portion of the purchased locked DOTs to be - // unlocked for basic usage. - let unlocked = (T::UnlockedProportion::get() * status.locked_balance) - .min(T::MaxUnlocked::get()); - let locked = status.locked_balance.saturating_sub(unlocked); - // We checked that this account has no existing vesting schedule. So this - // function should never fail, however if it does, not much we can do about - // it at this point. - let _ = T::VestingSchedule::add_vesting_schedule( - // Apply vesting schedule to this user - &who, - // For this much amount - locked, - // Unlocking the full amount after one block - locked, - // When everything unlocks - unlock_block, - ); - } - - // Setting the user account to `Completed` ends the purchase process for this - // user. - status.validity = AccountValidity::Completed; - Self::deposit_event(Event::<T>::PaymentComplete { - who: who.clone(), - free: status.free_balance, - locked: status.locked_balance, - }); - Ok(()) - }, - )?; - Ok(()) - } - - /* Configuration Operations */ - - /// Set the account that will be used to payout users in the DOT purchase process. - /// - /// Origin must match the `ConfigurationOrigin` - #[pallet::call_index(4)] - #[pallet::weight(T::DbWeight::get().writes(1))] - pub fn set_payment_account(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult { - T::ConfigurationOrigin::ensure_origin(origin)?; - // Possibly this is worse than having the caller account be the payment account? - PaymentAccount::<T>::put(who.clone()); - Self::deposit_event(Event::<T>::PaymentAccountSet { who }); - Ok(()) - } - - /// Set the statement that must be signed for a user to participate on the DOT sale. - /// - /// Origin must match the `ConfigurationOrigin` - #[pallet::call_index(5)] - #[pallet::weight(T::DbWeight::get().writes(1))] - pub fn set_statement(origin: OriginFor<T>, statement: Vec<u8>) -> DispatchResult { - T::ConfigurationOrigin::ensure_origin(origin)?; - ensure!( - (statement.len() as u32) < T::MaxStatementLength::get(), - Error::<T>::InvalidStatement - ); - // Possibly this is worse than having the caller account be the payment account? - Statement::<T>::set(statement); - Self::deposit_event(Event::<T>::StatementUpdated); - Ok(()) - } - - /// Set the block where locked DOTs will become unlocked. - /// - /// Origin must match the `ConfigurationOrigin` - #[pallet::call_index(6)] - #[pallet::weight(T::DbWeight::get().writes(1))] - pub fn set_unlock_block( - origin: OriginFor<T>, - unlock_block: BlockNumberFor<T>, - ) -> DispatchResult { - T::ConfigurationOrigin::ensure_origin(origin)?; - ensure!( - unlock_block > frame_system::Pallet::<T>::block_number(), - Error::<T>::InvalidUnlockBlock - ); - // Possibly this is worse than having the caller account be the payment account? - UnlockBlock::<T>::set(unlock_block); - Self::deposit_event(Event::<T>::UnlockBlockUpdated { block_number: unlock_block }); - Ok(()) - } - } -} - -impl<T: Config> Pallet<T> { - fn verify_signature(who: &T::AccountId, signature: &[u8]) -> Result<(), DispatchError> { - // sr25519 always expects a 64 byte signature. - let signature: AnySignature = sr25519::Signature::try_from(signature) - .map_err(|_| Error::<T>::InvalidSignature)? - .into(); - - // In Polkadot, the AccountId is always the same as the 32 byte public key. - let account_bytes: [u8; 32] = account_to_bytes(who)?; - let public_key = sr25519::Public::from_raw(account_bytes); - - let message = Statement::<T>::get(); - - // Check if everything is good or not. - match signature.verify(message.as_slice(), &public_key) { - true => Ok(()), - false => Err(Error::<T>::InvalidSignature)?, - } - } -} - -// This function converts a 32 byte AccountId to its byte-array equivalent form. -fn account_to_bytes<AccountId>(account: &AccountId) -> Result<[u8; 32], DispatchError> -where - AccountId: Encode, -{ - let account_vec = account.encode(); - ensure!(account_vec.len() == 32, "AccountId must be 32 bytes."); - let mut bytes = [0u8; 32]; - bytes.copy_from_slice(&account_vec); - Ok(bytes) -} - -/// WARNING: Executing this function will clear all storage used by this pallet. -/// Be sure this is what you want... -pub fn remove_pallet<T>() -> frame_support::weights::Weight -where - T: frame_system::Config, -{ - #[allow(deprecated)] - use frame_support::migration::remove_storage_prefix; - #[allow(deprecated)] - remove_storage_prefix(b"Purchase", b"Accounts", b""); - #[allow(deprecated)] - remove_storage_prefix(b"Purchase", b"PaymentAccount", b""); - #[allow(deprecated)] - remove_storage_prefix(b"Purchase", b"Statement", b""); - #[allow(deprecated)] - remove_storage_prefix(b"Purchase", b"UnlockBlock", b""); - - <T as frame_system::Config>::BlockWeights::get().max_block -} - -#[cfg(test)] -mod tests { - use super::*; - - use sp_core::{crypto::AccountId32, H256}; - use sp_keyring::{Ed25519Keyring, Sr25519Keyring}; - // The testing primitives are very useful for avoiding having to work with signatures - // or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. - use crate::purchase; - use frame_support::{ - assert_noop, assert_ok, derive_impl, ord_parameter_types, parameter_types, - traits::{Currency, WithdrawReasons}, - }; - use sp_runtime::{ - traits::{BlakeTwo256, Dispatchable, Identity, IdentityLookup}, - ArithmeticError, BuildStorage, - DispatchError::BadOrigin, - }; - - type Block = frame_system::mocking::MockBlock<Test>; - - frame_support::construct_runtime!( - pub enum Test - { - System: frame_system, - Balances: pallet_balances, - Vesting: pallet_vesting, - Purchase: purchase, - } - ); - - type AccountId = AccountId32; - - #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] - impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup<AccountId>; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData<u64>; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; - } - - #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] - impl pallet_balances::Config for Test { - type AccountStore = System; - } - - parameter_types! { - pub const MinVestedTransfer: u64 = 1; - pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = - WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); - } - - impl pallet_vesting::Config for Test { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type BlockNumberToBalance = Identity; - type MinVestedTransfer = MinVestedTransfer; - type WeightInfo = (); - type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; - type BlockNumberProvider = System; - const MAX_VESTING_SCHEDULES: u32 = 28; - } - - parameter_types! { - pub const MaxStatementLength: u32 = 1_000; - pub const UnlockedProportion: Permill = Permill::from_percent(10); - pub const MaxUnlocked: u64 = 10; - } - - ord_parameter_types! { - pub const ValidityOrigin: AccountId = AccountId32::from([0u8; 32]); - pub const PaymentOrigin: AccountId = AccountId32::from([1u8; 32]); - pub const ConfigurationOrigin: AccountId = AccountId32::from([2u8; 32]); - } - - impl Config for Test { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type VestingSchedule = Vesting; - type ValidityOrigin = frame_system::EnsureSignedBy<ValidityOrigin, AccountId>; - type ConfigurationOrigin = frame_system::EnsureSignedBy<ConfigurationOrigin, AccountId>; - type MaxStatementLength = MaxStatementLength; - type UnlockedProportion = UnlockedProportion; - type MaxUnlocked = MaxUnlocked; - } - - // This function basically just builds a genesis storage key/value store according to - // our desired mockup. It also executes our `setup` function which sets up this pallet for use. - pub fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| setup()); - ext - } - - fn setup() { - let statement = b"Hello, World".to_vec(); - let unlock_block = 100; - Purchase::set_statement(RuntimeOrigin::signed(configuration_origin()), statement).unwrap(); - Purchase::set_unlock_block(RuntimeOrigin::signed(configuration_origin()), unlock_block) - .unwrap(); - Purchase::set_payment_account( - RuntimeOrigin::signed(configuration_origin()), - payment_account(), - ) - .unwrap(); - Balances::make_free_balance_be(&payment_account(), 100_000); - } - - fn alice() -> AccountId { - Sr25519Keyring::Alice.to_account_id() - } - - fn alice_ed25519() -> AccountId { - Ed25519Keyring::Alice.to_account_id() - } - - fn bob() -> AccountId { - Sr25519Keyring::Bob.to_account_id() - } - - fn alice_signature() -> [u8; 64] { - // echo -n "Hello, World" | subkey -s sign "bottom drive obey lake curtain smoke basket hold - // race lonely fit walk//Alice" - hex_literal::hex!("20e0faffdf4dfe939f2faa560f73b1d01cde8472e2b690b7b40606a374244c3a2e9eb9c8107c10b605138374003af8819bd4387d7c24a66ee9253c2e688ab881") - } - - fn bob_signature() -> [u8; 64] { - // echo -n "Hello, World" | subkey -s sign "bottom drive obey lake curtain smoke basket hold - // race lonely fit walk//Bob" - hex_literal::hex!("d6d460187ecf530f3ec2d6e3ac91b9d083c8fbd8f1112d92a82e4d84df552d18d338e6da8944eba6e84afaacf8a9850f54e7b53a84530d649be2e0119c7ce889") - } - - fn alice_signature_ed25519() -> [u8; 64] { - // echo -n "Hello, World" | subkey -e sign "bottom drive obey lake curtain smoke basket hold - // race lonely fit walk//Alice" - hex_literal::hex!("ee3f5a6cbfc12a8f00c18b811dc921b550ddf272354cda4b9a57b1d06213fcd8509f5af18425d39a279d13622f14806c3e978e2163981f2ec1c06e9628460b0e") - } - - fn validity_origin() -> AccountId { - ValidityOrigin::get() - } - - fn configuration_origin() -> AccountId { - ConfigurationOrigin::get() - } - - fn payment_account() -> AccountId { - [42u8; 32].into() - } - - #[test] - fn set_statement_works_and_handles_basic_errors() { - new_test_ext().execute_with(|| { - let statement = b"Test Set Statement".to_vec(); - // Invalid origin - assert_noop!( - Purchase::set_statement(RuntimeOrigin::signed(alice()), statement.clone()), - BadOrigin, - ); - // Too Long - let long_statement = [0u8; 10_000].to_vec(); - assert_noop!( - Purchase::set_statement( - RuntimeOrigin::signed(configuration_origin()), - long_statement - ), - Error::<Test>::InvalidStatement, - ); - // Just right... - assert_ok!(Purchase::set_statement( - RuntimeOrigin::signed(configuration_origin()), - statement.clone() - )); - assert_eq!(Statement::<Test>::get(), statement); - }); - } - - #[test] - fn set_unlock_block_works_and_handles_basic_errors() { - new_test_ext().execute_with(|| { - let unlock_block = 69; - // Invalid origin - assert_noop!( - Purchase::set_unlock_block(RuntimeOrigin::signed(alice()), unlock_block), - BadOrigin, - ); - // Block Number in Past - let bad_unlock_block = 50; - System::set_block_number(bad_unlock_block); - assert_noop!( - Purchase::set_unlock_block( - RuntimeOrigin::signed(configuration_origin()), - bad_unlock_block - ), - Error::<Test>::InvalidUnlockBlock, - ); - // Just right... - assert_ok!(Purchase::set_unlock_block( - RuntimeOrigin::signed(configuration_origin()), - unlock_block - )); - assert_eq!(UnlockBlock::<Test>::get(), unlock_block); - }); - } - - #[test] - fn set_payment_account_works_and_handles_basic_errors() { - new_test_ext().execute_with(|| { - let payment_account: AccountId = [69u8; 32].into(); - // Invalid Origin - assert_noop!( - Purchase::set_payment_account( - RuntimeOrigin::signed(alice()), - payment_account.clone() - ), - BadOrigin, - ); - // Just right... - assert_ok!(Purchase::set_payment_account( - RuntimeOrigin::signed(configuration_origin()), - payment_account.clone() - )); - assert_eq!(PaymentAccount::<Test>::get(), Some(payment_account)); - }); - } - - #[test] - fn signature_verification_works() { - new_test_ext().execute_with(|| { - assert_ok!(Purchase::verify_signature(&alice(), &alice_signature())); - assert_ok!(Purchase::verify_signature(&alice_ed25519(), &alice_signature_ed25519())); - assert_ok!(Purchase::verify_signature(&bob(), &bob_signature())); - - // Mixing and matching fails - assert_noop!( - Purchase::verify_signature(&alice(), &bob_signature()), - Error::<Test>::InvalidSignature - ); - assert_noop!( - Purchase::verify_signature(&bob(), &alice_signature()), - Error::<Test>::InvalidSignature - ); - }); - } - - #[test] - fn account_creation_works() { - new_test_ext().execute_with(|| { - assert!(!Accounts::<Test>::contains_key(alice())); - assert_ok!(Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - alice(), - alice_signature().to_vec(), - )); - assert_eq!( - Accounts::<Test>::get(alice()), - AccountStatus { - validity: AccountValidity::Initiated, - free_balance: Zero::zero(), - locked_balance: Zero::zero(), - signature: alice_signature().to_vec(), - vat: Permill::zero(), - } - ); - }); - } - - #[test] - fn account_creation_handles_basic_errors() { - new_test_ext().execute_with(|| { - // Wrong Origin - assert_noop!( - Purchase::create_account( - RuntimeOrigin::signed(alice()), - alice(), - alice_signature().to_vec() - ), - BadOrigin, - ); - - // Wrong Account/Signature - assert_noop!( - Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - alice(), - bob_signature().to_vec() - ), - Error::<Test>::InvalidSignature, - ); - - // Account with vesting - Balances::make_free_balance_be(&alice(), 100); - assert_ok!(<Test as Config>::VestingSchedule::add_vesting_schedule( - &alice(), - 100, - 1, - 50 - )); - assert_noop!( - Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - alice(), - alice_signature().to_vec() - ), - Error::<Test>::VestingScheduleExists, - ); - - // Duplicate Purchasing Account - assert_ok!(Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - bob(), - bob_signature().to_vec() - )); - assert_noop!( - Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - bob(), - bob_signature().to_vec() - ), - Error::<Test>::ExistingAccount, - ); - }); - } - - #[test] - fn update_validity_status_works() { - new_test_ext().execute_with(|| { - // Alice account is created. - assert_ok!(Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - alice(), - alice_signature().to_vec(), - )); - // She submits KYC, and we update the status to `Pending`. - assert_ok!(Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::Pending, - )); - // KYC comes back negative, so we mark the account invalid. - assert_ok!(Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::Invalid, - )); - assert_eq!( - Accounts::<Test>::get(alice()), - AccountStatus { - validity: AccountValidity::Invalid, - free_balance: Zero::zero(), - locked_balance: Zero::zero(), - signature: alice_signature().to_vec(), - vat: Permill::zero(), - } - ); - // She fixes it, we mark her account valid. - assert_ok!(Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::ValidLow, - )); - assert_eq!( - Accounts::<Test>::get(alice()), - AccountStatus { - validity: AccountValidity::ValidLow, - free_balance: Zero::zero(), - locked_balance: Zero::zero(), - signature: alice_signature().to_vec(), - vat: Permill::zero(), - } - ); - }); - } - - #[test] - fn update_validity_status_handles_basic_errors() { - new_test_ext().execute_with(|| { - // Wrong Origin - assert_noop!( - Purchase::update_validity_status( - RuntimeOrigin::signed(alice()), - alice(), - AccountValidity::Pending, - ), - BadOrigin - ); - // Inactive Account - assert_noop!( - Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::Pending, - ), - Error::<Test>::InvalidAccount - ); - // Already Completed - assert_ok!(Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - alice(), - alice_signature().to_vec(), - )); - assert_ok!(Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::Completed, - )); - assert_noop!( - Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::Pending, - ), - Error::<Test>::AlreadyCompleted - ); - }); - } - - #[test] - fn update_balance_works() { - new_test_ext().execute_with(|| { - // Alice account is created - assert_ok!(Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - alice(), - alice_signature().to_vec() - )); - // And approved for basic contribution - assert_ok!(Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::ValidLow, - )); - // We set a balance on the user based on the payment they made. 50 locked, 50 free. - assert_ok!(Purchase::update_balance( - RuntimeOrigin::signed(validity_origin()), - alice(), - 50, - 50, - Permill::from_rational(77u32, 1000u32), - )); - assert_eq!( - Accounts::<Test>::get(alice()), - AccountStatus { - validity: AccountValidity::ValidLow, - free_balance: 50, - locked_balance: 50, - signature: alice_signature().to_vec(), - vat: Permill::from_parts(77000), - } - ); - // We can update the balance based on new information. - assert_ok!(Purchase::update_balance( - RuntimeOrigin::signed(validity_origin()), - alice(), - 25, - 50, - Permill::zero(), - )); - assert_eq!( - Accounts::<Test>::get(alice()), - AccountStatus { - validity: AccountValidity::ValidLow, - free_balance: 25, - locked_balance: 50, - signature: alice_signature().to_vec(), - vat: Permill::zero(), - } - ); - }); - } - - #[test] - fn update_balance_handles_basic_errors() { - new_test_ext().execute_with(|| { - // Wrong Origin - assert_noop!( - Purchase::update_balance( - RuntimeOrigin::signed(alice()), - alice(), - 50, - 50, - Permill::zero(), - ), - BadOrigin - ); - // Inactive Account - assert_noop!( - Purchase::update_balance( - RuntimeOrigin::signed(validity_origin()), - alice(), - 50, - 50, - Permill::zero(), - ), - Error::<Test>::InvalidAccount - ); - // Overflow - assert_noop!( - Purchase::update_balance( - RuntimeOrigin::signed(validity_origin()), - alice(), - u64::MAX, - u64::MAX, - Permill::zero(), - ), - Error::<Test>::InvalidAccount - ); - }); - } - - #[test] - fn payout_works() { - new_test_ext().execute_with(|| { - // Alice and Bob accounts are created - assert_ok!(Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - alice(), - alice_signature().to_vec() - )); - assert_ok!(Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - bob(), - bob_signature().to_vec() - )); - // Alice is approved for basic contribution - assert_ok!(Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::ValidLow, - )); - // Bob is approved for high contribution - assert_ok!(Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - bob(), - AccountValidity::ValidHigh, - )); - // We set a balance on the users based on the payment they made. 50 locked, 50 free. - assert_ok!(Purchase::update_balance( - RuntimeOrigin::signed(validity_origin()), - alice(), - 50, - 50, - Permill::zero(), - )); - assert_ok!(Purchase::update_balance( - RuntimeOrigin::signed(validity_origin()), - bob(), - 100, - 150, - Permill::zero(), - )); - // Now we call payout for Alice and Bob. - assert_ok!(Purchase::payout(RuntimeOrigin::signed(payment_account()), alice(),)); - assert_ok!(Purchase::payout(RuntimeOrigin::signed(payment_account()), bob(),)); - // Payment is made. - assert_eq!(<Test as Config>::Currency::free_balance(&payment_account()), 99_650); - assert_eq!(<Test as Config>::Currency::free_balance(&alice()), 100); - // 10% of the 50 units is unlocked automatically for Alice - assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&alice()), Some(45)); - assert_eq!(<Test as Config>::Currency::free_balance(&bob()), 250); - // A max of 10 units is unlocked automatically for Bob - assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&bob()), Some(140)); - // Status is completed. - assert_eq!( - Accounts::<Test>::get(alice()), - AccountStatus { - validity: AccountValidity::Completed, - free_balance: 50, - locked_balance: 50, - signature: alice_signature().to_vec(), - vat: Permill::zero(), - } - ); - assert_eq!( - Accounts::<Test>::get(bob()), - AccountStatus { - validity: AccountValidity::Completed, - free_balance: 100, - locked_balance: 150, - signature: bob_signature().to_vec(), - vat: Permill::zero(), - } - ); - // Vesting lock is removed in whole on block 101 (100 blocks after block 1) - System::set_block_number(100); - let vest_call = RuntimeCall::Vesting(pallet_vesting::Call::<Test>::vest {}); - assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(alice()))); - assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(bob()))); - assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&alice()), Some(45)); - assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&bob()), Some(140)); - System::set_block_number(101); - assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(alice()))); - assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(bob()))); - assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&alice()), None); - assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&bob()), None); - }); - } - - #[test] - fn payout_handles_basic_errors() { - new_test_ext().execute_with(|| { - // Wrong Origin - assert_noop!(Purchase::payout(RuntimeOrigin::signed(alice()), alice(),), BadOrigin); - // Account with Existing Vesting Schedule - Balances::make_free_balance_be(&bob(), 100); - assert_ok!( - <Test as Config>::VestingSchedule::add_vesting_schedule(&bob(), 100, 1, 50,) - ); - assert_noop!( - Purchase::payout(RuntimeOrigin::signed(payment_account()), bob(),), - Error::<Test>::VestingScheduleExists - ); - // Invalid Account (never created) - assert_noop!( - Purchase::payout(RuntimeOrigin::signed(payment_account()), alice(),), - Error::<Test>::InvalidAccount - ); - // Invalid Account (created, but not valid) - assert_ok!(Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - alice(), - alice_signature().to_vec() - )); - assert_noop!( - Purchase::payout(RuntimeOrigin::signed(payment_account()), alice(),), - Error::<Test>::InvalidAccount - ); - // Not enough funds in payment account - assert_ok!(Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::ValidHigh, - )); - assert_ok!(Purchase::update_balance( - RuntimeOrigin::signed(validity_origin()), - alice(), - 100_000, - 100_000, - Permill::zero(), - )); - assert_noop!( - Purchase::payout(RuntimeOrigin::signed(payment_account()), alice()), - ArithmeticError::Underflow - ); - }); - } - - #[test] - fn remove_pallet_works() { - new_test_ext().execute_with(|| { - let account_status = AccountStatus { - validity: AccountValidity::Completed, - free_balance: 1234, - locked_balance: 4321, - signature: b"my signature".to_vec(), - vat: Permill::from_percent(50), - }; - - // Add some storage. - Accounts::<Test>::insert(alice(), account_status.clone()); - Accounts::<Test>::insert(bob(), account_status); - PaymentAccount::<Test>::put(alice()); - Statement::<Test>::put(b"hello, world!".to_vec()); - UnlockBlock::<Test>::put(4); - - // Verify storage exists. - assert_eq!(Accounts::<Test>::iter().count(), 2); - assert!(PaymentAccount::<Test>::exists()); - assert!(Statement::<Test>::exists()); - assert!(UnlockBlock::<Test>::exists()); - - // Remove storage. - remove_pallet::<Test>(); - - // Verify storage is gone. - assert_eq!(Accounts::<Test>::iter().count(), 0); - assert!(!PaymentAccount::<Test>::exists()); - assert!(!Statement::<Test>::exists()); - assert!(!UnlockBlock::<Test>::exists()); - }); - } -} diff --git a/polkadot/runtime/common/src/purchase/mock.rs b/polkadot/runtime/common/src/purchase/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..ec8599f3b792b94a372a7588b7da4c8d3fa35001 --- /dev/null +++ b/polkadot/runtime/common/src/purchase/mock.rs @@ -0,0 +1,181 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Mocking utilities for testing in purchase pallet. + +#[cfg(test)] +use super::*; + +use sp_core::{crypto::AccountId32, H256}; +use sp_keyring::{Ed25519Keyring, Sr25519Keyring}; +// The testing primitives are very useful for avoiding having to work with signatures +// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. +use crate::purchase; +use frame_support::{ + derive_impl, ord_parameter_types, parameter_types, + traits::{Currency, WithdrawReasons}, +}; +use sp_runtime::{ + traits::{BlakeTwo256, Identity, IdentityLookup}, + BuildStorage, +}; + +type Block = frame_system::mocking::MockBlock<Test>; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Vesting: pallet_vesting, + Purchase: purchase, + } +); + +type AccountId = AccountId32; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup<AccountId>; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData<u64>; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type AccountStore = System; +} + +parameter_types! { + pub const MinVestedTransfer: u64 = 1; + pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = + WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); +} + +impl pallet_vesting::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type BlockNumberToBalance = Identity; + type MinVestedTransfer = MinVestedTransfer; + type WeightInfo = (); + type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; + const MAX_VESTING_SCHEDULES: u32 = 28; +} + +parameter_types! { + pub const MaxStatementLength: u32 = 1_000; + pub const UnlockedProportion: Permill = Permill::from_percent(10); + pub const MaxUnlocked: u64 = 10; +} + +ord_parameter_types! { + pub const ValidityOrigin: AccountId = AccountId32::from([0u8; 32]); + pub const PaymentOrigin: AccountId = AccountId32::from([1u8; 32]); + pub const ConfigurationOrigin: AccountId = AccountId32::from([2u8; 32]); +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type VestingSchedule = Vesting; + type ValidityOrigin = frame_system::EnsureSignedBy<ValidityOrigin, AccountId>; + type ConfigurationOrigin = frame_system::EnsureSignedBy<ConfigurationOrigin, AccountId>; + type MaxStatementLength = MaxStatementLength; + type UnlockedProportion = UnlockedProportion; + type MaxUnlocked = MaxUnlocked; +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. It also executes our `setup` function which sets up this pallet for use. +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| setup()); + ext +} + +pub fn setup() { + let statement = b"Hello, World".to_vec(); + let unlock_block = 100; + Purchase::set_statement(RuntimeOrigin::signed(configuration_origin()), statement).unwrap(); + Purchase::set_unlock_block(RuntimeOrigin::signed(configuration_origin()), unlock_block) + .unwrap(); + Purchase::set_payment_account(RuntimeOrigin::signed(configuration_origin()), payment_account()) + .unwrap(); + Balances::make_free_balance_be(&payment_account(), 100_000); +} + +pub fn alice() -> AccountId { + Sr25519Keyring::Alice.to_account_id() +} + +pub fn alice_ed25519() -> AccountId { + Ed25519Keyring::Alice.to_account_id() +} + +pub fn bob() -> AccountId { + Sr25519Keyring::Bob.to_account_id() +} + +pub fn alice_signature() -> [u8; 64] { + // echo -n "Hello, World" | subkey -s sign "bottom drive obey lake curtain smoke basket hold + // race lonely fit walk//Alice" + hex_literal::hex!("20e0faffdf4dfe939f2faa560f73b1d01cde8472e2b690b7b40606a374244c3a2e9eb9c8107c10b605138374003af8819bd4387d7c24a66ee9253c2e688ab881") +} + +pub fn bob_signature() -> [u8; 64] { + // echo -n "Hello, World" | subkey -s sign "bottom drive obey lake curtain smoke basket hold + // race lonely fit walk//Bob" + hex_literal::hex!("d6d460187ecf530f3ec2d6e3ac91b9d083c8fbd8f1112d92a82e4d84df552d18d338e6da8944eba6e84afaacf8a9850f54e7b53a84530d649be2e0119c7ce889") +} + +pub fn alice_signature_ed25519() -> [u8; 64] { + // echo -n "Hello, World" | subkey -e sign "bottom drive obey lake curtain smoke basket hold + // race lonely fit walk//Alice" + hex_literal::hex!("ee3f5a6cbfc12a8f00c18b811dc921b550ddf272354cda4b9a57b1d06213fcd8509f5af18425d39a279d13622f14806c3e978e2163981f2ec1c06e9628460b0e") +} + +pub fn validity_origin() -> AccountId { + ValidityOrigin::get() +} + +pub fn configuration_origin() -> AccountId { + ConfigurationOrigin::get() +} + +pub fn payment_account() -> AccountId { + [42u8; 32].into() +} diff --git a/polkadot/runtime/common/src/purchase/mod.rs b/polkadot/runtime/common/src/purchase/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..71dc5b57967063f349264ca90ce345029512ad10 --- /dev/null +++ b/polkadot/runtime/common/src/purchase/mod.rs @@ -0,0 +1,482 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Pallet to process purchase of DOTs. + +use alloc::vec::Vec; +use codec::{Decode, Encode}; +use frame_support::{ + pallet_prelude::*, + traits::{Currency, EnsureOrigin, ExistenceRequirement, Get, VestingSchedule}, +}; +use frame_system::pallet_prelude::*; +pub use pallet::*; +use scale_info::TypeInfo; +use sp_core::sr25519; +use sp_runtime::{ + traits::{CheckedAdd, Saturating, Verify, Zero}, + AnySignature, DispatchError, DispatchResult, Permill, RuntimeDebug, +}; + +type BalanceOf<T> = + <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance; + +/// The kind of statement an account needs to make for a claim to be valid. +#[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub enum AccountValidity { + /// Account is not valid. + Invalid, + /// Account has initiated the account creation process. + Initiated, + /// Account is pending validation. + Pending, + /// Account is valid with a low contribution amount. + ValidLow, + /// Account is valid with a high contribution amount. + ValidHigh, + /// Account has completed the purchase process. + Completed, +} + +impl Default for AccountValidity { + fn default() -> Self { + AccountValidity::Invalid + } +} + +impl AccountValidity { + fn is_valid(&self) -> bool { + match self { + Self::Invalid => false, + Self::Initiated => false, + Self::Pending => false, + Self::ValidLow => true, + Self::ValidHigh => true, + Self::Completed => false, + } + } +} + +/// All information about an account regarding the purchase of DOTs. +#[derive(Encode, Decode, Default, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub struct AccountStatus<Balance> { + /// The current validity status of the user. Will denote if the user has passed KYC, + /// how much they are able to purchase, and when their purchase process has completed. + validity: AccountValidity, + /// The amount of free DOTs they have purchased. + free_balance: Balance, + /// The amount of locked DOTs they have purchased. + locked_balance: Balance, + /// Their sr25519/ed25519 signature verifying they have signed our required statement. + signature: Vec<u8>, + /// The percentage of VAT the purchaser is responsible for. This is already factored into + /// account balance. + vat: Permill, +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet<T>(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; + + /// Balances Pallet + type Currency: Currency<Self::AccountId>; + + /// Vesting Pallet + type VestingSchedule: VestingSchedule< + Self::AccountId, + Moment = BlockNumberFor<Self>, + Currency = Self::Currency, + >; + + /// The origin allowed to set account status. + type ValidityOrigin: EnsureOrigin<Self::RuntimeOrigin>; + + /// The origin allowed to make configurations to the pallet. + type ConfigurationOrigin: EnsureOrigin<Self::RuntimeOrigin>; + + /// The maximum statement length for the statement users to sign when creating an account. + #[pallet::constant] + type MaxStatementLength: Get<u32>; + + /// The amount of purchased locked DOTs that we will unlock for basic actions on the chain. + #[pallet::constant] + type UnlockedProportion: Get<Permill>; + + /// The maximum amount of locked DOTs that we will unlock. + #[pallet::constant] + type MaxUnlocked: Get<BalanceOf<Self>>; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event<T: Config> { + /// A new account was created. + AccountCreated { who: T::AccountId }, + /// Someone's account validity was updated. + ValidityUpdated { who: T::AccountId, validity: AccountValidity }, + /// Someone's purchase balance was updated. + BalanceUpdated { who: T::AccountId, free: BalanceOf<T>, locked: BalanceOf<T> }, + /// A payout was made to a purchaser. + PaymentComplete { who: T::AccountId, free: BalanceOf<T>, locked: BalanceOf<T> }, + /// A new payment account was set. + PaymentAccountSet { who: T::AccountId }, + /// A new statement was set. + StatementUpdated, + /// A new statement was set. `[block_number]` + UnlockBlockUpdated { block_number: BlockNumberFor<T> }, + } + + #[pallet::error] + pub enum Error<T> { + /// Account is not currently valid to use. + InvalidAccount, + /// Account used in the purchase already exists. + ExistingAccount, + /// Provided signature is invalid + InvalidSignature, + /// Account has already completed the purchase process. + AlreadyCompleted, + /// An overflow occurred when doing calculations. + Overflow, + /// The statement is too long to be stored on chain. + InvalidStatement, + /// The unlock block is in the past! + InvalidUnlockBlock, + /// Vesting schedule already exists for this account. + VestingScheduleExists, + } + + // A map of all participants in the DOT purchase process. + #[pallet::storage] + pub(super) type Accounts<T: Config> = + StorageMap<_, Blake2_128Concat, T::AccountId, AccountStatus<BalanceOf<T>>, ValueQuery>; + + // The account that will be used to payout participants of the DOT purchase process. + #[pallet::storage] + pub(super) type PaymentAccount<T: Config> = StorageValue<_, T::AccountId, OptionQuery>; + + // The statement purchasers will need to sign to participate. + #[pallet::storage] + pub(super) type Statement<T> = StorageValue<_, Vec<u8>, ValueQuery>; + + // The block where all locked dots will unlock. + #[pallet::storage] + pub(super) type UnlockBlock<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>; + + #[pallet::hooks] + impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {} + + #[pallet::call] + impl<T: Config> Pallet<T> { + /// Create a new account. Proof of existence through a valid signed message. + /// + /// We check that the account does not exist at this stage. + /// + /// Origin must match the `ValidityOrigin`. + #[pallet::call_index(0)] + #[pallet::weight(Weight::from_parts(200_000_000, 0) + T::DbWeight::get().reads_writes(4, 1))] + pub fn create_account( + origin: OriginFor<T>, + who: T::AccountId, + signature: Vec<u8>, + ) -> DispatchResult { + T::ValidityOrigin::ensure_origin(origin)?; + // Account is already being tracked by the pallet. + ensure!(!Accounts::<T>::contains_key(&who), Error::<T>::ExistingAccount); + // Account should not have a vesting schedule. + ensure!( + T::VestingSchedule::vesting_balance(&who).is_none(), + Error::<T>::VestingScheduleExists + ); + + // Verify the signature provided is valid for the statement. + Self::verify_signature(&who, &signature)?; + + // Create a new pending account. + let status = AccountStatus { + validity: AccountValidity::Initiated, + signature, + free_balance: Zero::zero(), + locked_balance: Zero::zero(), + vat: Permill::zero(), + }; + Accounts::<T>::insert(&who, status); + Self::deposit_event(Event::<T>::AccountCreated { who }); + Ok(()) + } + + /// Update the validity status of an existing account. If set to completed, the account + /// will no longer be able to continue through the crowdfund process. + /// + /// We check that the account exists at this stage, but has not completed the process. + /// + /// Origin must match the `ValidityOrigin`. + #[pallet::call_index(1)] + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] + pub fn update_validity_status( + origin: OriginFor<T>, + who: T::AccountId, + validity: AccountValidity, + ) -> DispatchResult { + T::ValidityOrigin::ensure_origin(origin)?; + ensure!(Accounts::<T>::contains_key(&who), Error::<T>::InvalidAccount); + Accounts::<T>::try_mutate( + &who, + |status: &mut AccountStatus<BalanceOf<T>>| -> DispatchResult { + ensure!( + status.validity != AccountValidity::Completed, + Error::<T>::AlreadyCompleted + ); + status.validity = validity; + Ok(()) + }, + )?; + Self::deposit_event(Event::<T>::ValidityUpdated { who, validity }); + Ok(()) + } + + /// Update the balance of a valid account. + /// + /// We check that the account is valid for a balance transfer at this point. + /// + /// Origin must match the `ValidityOrigin`. + #[pallet::call_index(2)] + #[pallet::weight(T::DbWeight::get().reads_writes(2, 1))] + pub fn update_balance( + origin: OriginFor<T>, + who: T::AccountId, + free_balance: BalanceOf<T>, + locked_balance: BalanceOf<T>, + vat: Permill, + ) -> DispatchResult { + T::ValidityOrigin::ensure_origin(origin)?; + + Accounts::<T>::try_mutate( + &who, + |status: &mut AccountStatus<BalanceOf<T>>| -> DispatchResult { + // Account has a valid status (not Invalid, Pending, or Completed)... + ensure!(status.validity.is_valid(), Error::<T>::InvalidAccount); + + free_balance.checked_add(&locked_balance).ok_or(Error::<T>::Overflow)?; + status.free_balance = free_balance; + status.locked_balance = locked_balance; + status.vat = vat; + Ok(()) + }, + )?; + Self::deposit_event(Event::<T>::BalanceUpdated { + who, + free: free_balance, + locked: locked_balance, + }); + Ok(()) + } + + /// Pay the user and complete the purchase process. + /// + /// We reverify all assumptions about the state of an account, and complete the process. + /// + /// Origin must match the configured `PaymentAccount` (if it is not configured then this + /// will always fail with `BadOrigin`). + #[pallet::call_index(3)] + #[pallet::weight(T::DbWeight::get().reads_writes(4, 2))] + pub fn payout(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult { + // Payments must be made directly by the `PaymentAccount`. + let payment_account = ensure_signed(origin)?; + let test_against = PaymentAccount::<T>::get().ok_or(DispatchError::BadOrigin)?; + ensure!(payment_account == test_against, DispatchError::BadOrigin); + + // Account should not have a vesting schedule. + ensure!( + T::VestingSchedule::vesting_balance(&who).is_none(), + Error::<T>::VestingScheduleExists + ); + + Accounts::<T>::try_mutate( + &who, + |status: &mut AccountStatus<BalanceOf<T>>| -> DispatchResult { + // Account has a valid status (not Invalid, Pending, or Completed)... + ensure!(status.validity.is_valid(), Error::<T>::InvalidAccount); + + // Transfer funds from the payment account into the purchasing user. + let total_balance = status + .free_balance + .checked_add(&status.locked_balance) + .ok_or(Error::<T>::Overflow)?; + T::Currency::transfer( + &payment_account, + &who, + total_balance, + ExistenceRequirement::AllowDeath, + )?; + + if !status.locked_balance.is_zero() { + let unlock_block = UnlockBlock::<T>::get(); + // We allow some configurable portion of the purchased locked DOTs to be + // unlocked for basic usage. + let unlocked = (T::UnlockedProportion::get() * status.locked_balance) + .min(T::MaxUnlocked::get()); + let locked = status.locked_balance.saturating_sub(unlocked); + // We checked that this account has no existing vesting schedule. So this + // function should never fail, however if it does, not much we can do about + // it at this point. + let _ = T::VestingSchedule::add_vesting_schedule( + // Apply vesting schedule to this user + &who, + // For this much amount + locked, + // Unlocking the full amount after one block + locked, + // When everything unlocks + unlock_block, + ); + } + + // Setting the user account to `Completed` ends the purchase process for this + // user. + status.validity = AccountValidity::Completed; + Self::deposit_event(Event::<T>::PaymentComplete { + who: who.clone(), + free: status.free_balance, + locked: status.locked_balance, + }); + Ok(()) + }, + )?; + Ok(()) + } + + /* Configuration Operations */ + + /// Set the account that will be used to payout users in the DOT purchase process. + /// + /// Origin must match the `ConfigurationOrigin` + #[pallet::call_index(4)] + #[pallet::weight(T::DbWeight::get().writes(1))] + pub fn set_payment_account(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult { + T::ConfigurationOrigin::ensure_origin(origin)?; + // Possibly this is worse than having the caller account be the payment account? + PaymentAccount::<T>::put(who.clone()); + Self::deposit_event(Event::<T>::PaymentAccountSet { who }); + Ok(()) + } + + /// Set the statement that must be signed for a user to participate on the DOT sale. + /// + /// Origin must match the `ConfigurationOrigin` + #[pallet::call_index(5)] + #[pallet::weight(T::DbWeight::get().writes(1))] + pub fn set_statement(origin: OriginFor<T>, statement: Vec<u8>) -> DispatchResult { + T::ConfigurationOrigin::ensure_origin(origin)?; + ensure!( + (statement.len() as u32) < T::MaxStatementLength::get(), + Error::<T>::InvalidStatement + ); + // Possibly this is worse than having the caller account be the payment account? + Statement::<T>::set(statement); + Self::deposit_event(Event::<T>::StatementUpdated); + Ok(()) + } + + /// Set the block where locked DOTs will become unlocked. + /// + /// Origin must match the `ConfigurationOrigin` + #[pallet::call_index(6)] + #[pallet::weight(T::DbWeight::get().writes(1))] + pub fn set_unlock_block( + origin: OriginFor<T>, + unlock_block: BlockNumberFor<T>, + ) -> DispatchResult { + T::ConfigurationOrigin::ensure_origin(origin)?; + ensure!( + unlock_block > frame_system::Pallet::<T>::block_number(), + Error::<T>::InvalidUnlockBlock + ); + // Possibly this is worse than having the caller account be the payment account? + UnlockBlock::<T>::set(unlock_block); + Self::deposit_event(Event::<T>::UnlockBlockUpdated { block_number: unlock_block }); + Ok(()) + } + } +} + +impl<T: Config> Pallet<T> { + fn verify_signature(who: &T::AccountId, signature: &[u8]) -> Result<(), DispatchError> { + // sr25519 always expects a 64 byte signature. + let signature: AnySignature = sr25519::Signature::try_from(signature) + .map_err(|_| Error::<T>::InvalidSignature)? + .into(); + + // In Polkadot, the AccountId is always the same as the 32 byte public key. + let account_bytes: [u8; 32] = account_to_bytes(who)?; + let public_key = sr25519::Public::from_raw(account_bytes); + + let message = Statement::<T>::get(); + + // Check if everything is good or not. + match signature.verify(message.as_slice(), &public_key) { + true => Ok(()), + false => Err(Error::<T>::InvalidSignature)?, + } + } +} + +// This function converts a 32 byte AccountId to its byte-array equivalent form. +fn account_to_bytes<AccountId>(account: &AccountId) -> Result<[u8; 32], DispatchError> +where + AccountId: Encode, +{ + let account_vec = account.encode(); + ensure!(account_vec.len() == 32, "AccountId must be 32 bytes."); + let mut bytes = [0u8; 32]; + bytes.copy_from_slice(&account_vec); + Ok(bytes) +} + +/// WARNING: Executing this function will clear all storage used by this pallet. +/// Be sure this is what you want... +pub fn remove_pallet<T>() -> frame_support::weights::Weight +where + T: frame_system::Config, +{ + #[allow(deprecated)] + use frame_support::migration::remove_storage_prefix; + #[allow(deprecated)] + remove_storage_prefix(b"Purchase", b"Accounts", b""); + #[allow(deprecated)] + remove_storage_prefix(b"Purchase", b"PaymentAccount", b""); + #[allow(deprecated)] + remove_storage_prefix(b"Purchase", b"Statement", b""); + #[allow(deprecated)] + remove_storage_prefix(b"Purchase", b"UnlockBlock", b""); + + <T as frame_system::Config>::BlockWeights::get().max_block +} + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; diff --git a/polkadot/runtime/common/src/purchase/tests.rs b/polkadot/runtime/common/src/purchase/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..8cf2a124d245997432afef1c148a22d4ea1a74a2 --- /dev/null +++ b/polkadot/runtime/common/src/purchase/tests.rs @@ -0,0 +1,547 @@ +// 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 <http://www.gnu.org/licenses/>. + +//! Tests for the purchase pallet. + +#[cfg(test)] +use super::*; + +use sp_core::crypto::AccountId32; +// The testing primitives are very useful for avoiding having to work with signatures +// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. +use frame_support::{assert_noop, assert_ok, traits::Currency}; +use sp_runtime::{traits::Dispatchable, ArithmeticError, DispatchError::BadOrigin}; + +use crate::purchase::mock::*; + +#[test] +fn set_statement_works_and_handles_basic_errors() { + new_test_ext().execute_with(|| { + let statement = b"Test Set Statement".to_vec(); + // Invalid origin + assert_noop!( + Purchase::set_statement(RuntimeOrigin::signed(alice()), statement.clone()), + BadOrigin, + ); + // Too Long + let long_statement = [0u8; 10_000].to_vec(); + assert_noop!( + Purchase::set_statement(RuntimeOrigin::signed(configuration_origin()), long_statement), + Error::<Test>::InvalidStatement, + ); + // Just right... + assert_ok!(Purchase::set_statement( + RuntimeOrigin::signed(configuration_origin()), + statement.clone() + )); + assert_eq!(Statement::<Test>::get(), statement); + }); +} + +#[test] +fn set_unlock_block_works_and_handles_basic_errors() { + new_test_ext().execute_with(|| { + let unlock_block = 69; + // Invalid origin + assert_noop!( + Purchase::set_unlock_block(RuntimeOrigin::signed(alice()), unlock_block), + BadOrigin, + ); + // Block Number in Past + let bad_unlock_block = 50; + System::set_block_number(bad_unlock_block); + assert_noop!( + Purchase::set_unlock_block( + RuntimeOrigin::signed(configuration_origin()), + bad_unlock_block + ), + Error::<Test>::InvalidUnlockBlock, + ); + // Just right... + assert_ok!(Purchase::set_unlock_block( + RuntimeOrigin::signed(configuration_origin()), + unlock_block + )); + assert_eq!(UnlockBlock::<Test>::get(), unlock_block); + }); +} + +#[test] +fn set_payment_account_works_and_handles_basic_errors() { + new_test_ext().execute_with(|| { + let payment_account: AccountId32 = [69u8; 32].into(); + // Invalid Origin + assert_noop!( + Purchase::set_payment_account(RuntimeOrigin::signed(alice()), payment_account.clone()), + BadOrigin, + ); + // Just right... + assert_ok!(Purchase::set_payment_account( + RuntimeOrigin::signed(configuration_origin()), + payment_account.clone() + )); + assert_eq!(PaymentAccount::<Test>::get(), Some(payment_account)); + }); +} + +#[test] +fn signature_verification_works() { + new_test_ext().execute_with(|| { + assert_ok!(Purchase::verify_signature(&alice(), &alice_signature())); + assert_ok!(Purchase::verify_signature(&alice_ed25519(), &alice_signature_ed25519())); + assert_ok!(Purchase::verify_signature(&bob(), &bob_signature())); + + // Mixing and matching fails + assert_noop!( + Purchase::verify_signature(&alice(), &bob_signature()), + Error::<Test>::InvalidSignature + ); + assert_noop!( + Purchase::verify_signature(&bob(), &alice_signature()), + Error::<Test>::InvalidSignature + ); + }); +} + +#[test] +fn account_creation_works() { + new_test_ext().execute_with(|| { + assert!(!Accounts::<Test>::contains_key(alice())); + assert_ok!(Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + alice(), + alice_signature().to_vec(), + )); + assert_eq!( + Accounts::<Test>::get(alice()), + AccountStatus { + validity: AccountValidity::Initiated, + free_balance: Zero::zero(), + locked_balance: Zero::zero(), + signature: alice_signature().to_vec(), + vat: Permill::zero(), + } + ); + }); +} + +#[test] +fn account_creation_handles_basic_errors() { + new_test_ext().execute_with(|| { + // Wrong Origin + assert_noop!( + Purchase::create_account( + RuntimeOrigin::signed(alice()), + alice(), + alice_signature().to_vec() + ), + BadOrigin, + ); + + // Wrong Account/Signature + assert_noop!( + Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + alice(), + bob_signature().to_vec() + ), + Error::<Test>::InvalidSignature, + ); + + // Account with vesting + Balances::make_free_balance_be(&alice(), 100); + assert_ok!(<Test as Config>::VestingSchedule::add_vesting_schedule(&alice(), 100, 1, 50)); + assert_noop!( + Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + alice(), + alice_signature().to_vec() + ), + Error::<Test>::VestingScheduleExists, + ); + + // Duplicate Purchasing Account + assert_ok!(Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + bob(), + bob_signature().to_vec() + )); + assert_noop!( + Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + bob(), + bob_signature().to_vec() + ), + Error::<Test>::ExistingAccount, + ); + }); +} + +#[test] +fn update_validity_status_works() { + new_test_ext().execute_with(|| { + // Alice account is created. + assert_ok!(Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + alice(), + alice_signature().to_vec(), + )); + // She submits KYC, and we update the status to `Pending`. + assert_ok!(Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::Pending, + )); + // KYC comes back negative, so we mark the account invalid. + assert_ok!(Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::Invalid, + )); + assert_eq!( + Accounts::<Test>::get(alice()), + AccountStatus { + validity: AccountValidity::Invalid, + free_balance: Zero::zero(), + locked_balance: Zero::zero(), + signature: alice_signature().to_vec(), + vat: Permill::zero(), + } + ); + // She fixes it, we mark her account valid. + assert_ok!(Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::ValidLow, + )); + assert_eq!( + Accounts::<Test>::get(alice()), + AccountStatus { + validity: AccountValidity::ValidLow, + free_balance: Zero::zero(), + locked_balance: Zero::zero(), + signature: alice_signature().to_vec(), + vat: Permill::zero(), + } + ); + }); +} + +#[test] +fn update_validity_status_handles_basic_errors() { + new_test_ext().execute_with(|| { + // Wrong Origin + assert_noop!( + Purchase::update_validity_status( + RuntimeOrigin::signed(alice()), + alice(), + AccountValidity::Pending, + ), + BadOrigin + ); + // Inactive Account + assert_noop!( + Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::Pending, + ), + Error::<Test>::InvalidAccount + ); + // Already Completed + assert_ok!(Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + alice(), + alice_signature().to_vec(), + )); + assert_ok!(Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::Completed, + )); + assert_noop!( + Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::Pending, + ), + Error::<Test>::AlreadyCompleted + ); + }); +} + +#[test] +fn update_balance_works() { + new_test_ext().execute_with(|| { + // Alice account is created + assert_ok!(Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + alice(), + alice_signature().to_vec() + )); + // And approved for basic contribution + assert_ok!(Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::ValidLow, + )); + // We set a balance on the user based on the payment they made. 50 locked, 50 free. + assert_ok!(Purchase::update_balance( + RuntimeOrigin::signed(validity_origin()), + alice(), + 50, + 50, + Permill::from_rational(77u32, 1000u32), + )); + assert_eq!( + Accounts::<Test>::get(alice()), + AccountStatus { + validity: AccountValidity::ValidLow, + free_balance: 50, + locked_balance: 50, + signature: alice_signature().to_vec(), + vat: Permill::from_parts(77000), + } + ); + // We can update the balance based on new information. + assert_ok!(Purchase::update_balance( + RuntimeOrigin::signed(validity_origin()), + alice(), + 25, + 50, + Permill::zero(), + )); + assert_eq!( + Accounts::<Test>::get(alice()), + AccountStatus { + validity: AccountValidity::ValidLow, + free_balance: 25, + locked_balance: 50, + signature: alice_signature().to_vec(), + vat: Permill::zero(), + } + ); + }); +} + +#[test] +fn update_balance_handles_basic_errors() { + new_test_ext().execute_with(|| { + // Wrong Origin + assert_noop!( + Purchase::update_balance( + RuntimeOrigin::signed(alice()), + alice(), + 50, + 50, + Permill::zero(), + ), + BadOrigin + ); + // Inactive Account + assert_noop!( + Purchase::update_balance( + RuntimeOrigin::signed(validity_origin()), + alice(), + 50, + 50, + Permill::zero(), + ), + Error::<Test>::InvalidAccount + ); + // Overflow + assert_noop!( + Purchase::update_balance( + RuntimeOrigin::signed(validity_origin()), + alice(), + u64::MAX, + u64::MAX, + Permill::zero(), + ), + Error::<Test>::InvalidAccount + ); + }); +} + +#[test] +fn payout_works() { + new_test_ext().execute_with(|| { + // Alice and Bob accounts are created + assert_ok!(Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + alice(), + alice_signature().to_vec() + )); + assert_ok!(Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + bob(), + bob_signature().to_vec() + )); + // Alice is approved for basic contribution + assert_ok!(Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::ValidLow, + )); + // Bob is approved for high contribution + assert_ok!(Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + bob(), + AccountValidity::ValidHigh, + )); + // We set a balance on the users based on the payment they made. 50 locked, 50 free. + assert_ok!(Purchase::update_balance( + RuntimeOrigin::signed(validity_origin()), + alice(), + 50, + 50, + Permill::zero(), + )); + assert_ok!(Purchase::update_balance( + RuntimeOrigin::signed(validity_origin()), + bob(), + 100, + 150, + Permill::zero(), + )); + // Now we call payout for Alice and Bob. + assert_ok!(Purchase::payout(RuntimeOrigin::signed(payment_account()), alice(),)); + assert_ok!(Purchase::payout(RuntimeOrigin::signed(payment_account()), bob(),)); + // Payment is made. + assert_eq!(<Test as Config>::Currency::free_balance(&payment_account()), 99_650); + assert_eq!(<Test as Config>::Currency::free_balance(&alice()), 100); + // 10% of the 50 units is unlocked automatically for Alice + assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&alice()), Some(45)); + assert_eq!(<Test as Config>::Currency::free_balance(&bob()), 250); + // A max of 10 units is unlocked automatically for Bob + assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&bob()), Some(140)); + // Status is completed. + assert_eq!( + Accounts::<Test>::get(alice()), + AccountStatus { + validity: AccountValidity::Completed, + free_balance: 50, + locked_balance: 50, + signature: alice_signature().to_vec(), + vat: Permill::zero(), + } + ); + assert_eq!( + Accounts::<Test>::get(bob()), + AccountStatus { + validity: AccountValidity::Completed, + free_balance: 100, + locked_balance: 150, + signature: bob_signature().to_vec(), + vat: Permill::zero(), + } + ); + // Vesting lock is removed in whole on block 101 (100 blocks after block 1) + System::set_block_number(100); + let vest_call = RuntimeCall::Vesting(pallet_vesting::Call::<Test>::vest {}); + assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(alice()))); + assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(bob()))); + assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&alice()), Some(45)); + assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&bob()), Some(140)); + System::set_block_number(101); + assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(alice()))); + assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(bob()))); + assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&alice()), None); + assert_eq!(<Test as Config>::VestingSchedule::vesting_balance(&bob()), None); + }); +} + +#[test] +fn payout_handles_basic_errors() { + new_test_ext().execute_with(|| { + // Wrong Origin + assert_noop!(Purchase::payout(RuntimeOrigin::signed(alice()), alice(),), BadOrigin); + // Account with Existing Vesting Schedule + Balances::make_free_balance_be(&bob(), 100); + assert_ok!(<Test as Config>::VestingSchedule::add_vesting_schedule(&bob(), 100, 1, 50,)); + assert_noop!( + Purchase::payout(RuntimeOrigin::signed(payment_account()), bob(),), + Error::<Test>::VestingScheduleExists + ); + // Invalid Account (never created) + assert_noop!( + Purchase::payout(RuntimeOrigin::signed(payment_account()), alice(),), + Error::<Test>::InvalidAccount + ); + // Invalid Account (created, but not valid) + assert_ok!(Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + alice(), + alice_signature().to_vec() + )); + assert_noop!( + Purchase::payout(RuntimeOrigin::signed(payment_account()), alice(),), + Error::<Test>::InvalidAccount + ); + // Not enough funds in payment account + assert_ok!(Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::ValidHigh, + )); + assert_ok!(Purchase::update_balance( + RuntimeOrigin::signed(validity_origin()), + alice(), + 100_000, + 100_000, + Permill::zero(), + )); + assert_noop!( + Purchase::payout(RuntimeOrigin::signed(payment_account()), alice()), + ArithmeticError::Underflow + ); + }); +} + +#[test] +fn remove_pallet_works() { + new_test_ext().execute_with(|| { + let account_status = AccountStatus { + validity: AccountValidity::Completed, + free_balance: 1234, + locked_balance: 4321, + signature: b"my signature".to_vec(), + vat: Permill::from_percent(50), + }; + + // Add some storage. + Accounts::<Test>::insert(alice(), account_status.clone()); + Accounts::<Test>::insert(bob(), account_status); + PaymentAccount::<Test>::put(alice()); + Statement::<Test>::put(b"hello, world!".to_vec()); + UnlockBlock::<Test>::put(4); + + // Verify storage exists. + assert_eq!(Accounts::<Test>::iter().count(), 2); + assert!(PaymentAccount::<Test>::exists()); + assert!(Statement::<Test>::exists()); + assert!(UnlockBlock::<Test>::exists()); + + // Remove storage. + remove_pallet::<Test>(); + + // Verify storage is gone. + assert_eq!(Accounts::<Test>::iter().count(), 0); + assert!(!PaymentAccount::<Test>::exists()); + assert!(!Statement::<Test>::exists()); + assert!(!UnlockBlock::<Test>::exists()); + }); +} diff --git a/polkadot/runtime/common/src/slots/mod.rs b/polkadot/runtime/common/src/slots/mod.rs index 333f14c6608acb1d62df7fadbfe297741a39d6cd..1fbfe451dcbf7304b8e17de8ec590893ffa71533 100644 --- a/polkadot/runtime/common/src/slots/mod.rs +++ b/polkadot/runtime/common/src/slots/mod.rs @@ -149,7 +149,7 @@ pub mod pallet { if let Some((lease_period, first_block)) = Self::lease_period_index(n) { // If we're beginning a new lease period then handle that. if first_block { - return Self::manage_lease_period_start(lease_period) + return Self::manage_lease_period_start(lease_period); } } @@ -237,7 +237,7 @@ impl<T: Config> Pallet<T> { let mut parachains = Vec::new(); for (para, mut lease_periods) in Leases::<T>::iter() { if lease_periods.is_empty() { - continue + continue; } // ^^ should never be empty since we would have deleted the entry otherwise. @@ -381,7 +381,7 @@ impl<T: Config> Leaser<BlockNumberFor<T>> for Pallet<T> { // attempt. // // We bail, not giving any lease and leave it for governance to sort out. - return Err(LeaseError::AlreadyLeased) + return Err(LeaseError::AlreadyLeased); } } else if d.len() == i { // Doesn't exist. This is usual. @@ -488,7 +488,7 @@ impl<T: Config> Leaser<BlockNumberFor<T>> for Pallet<T> { for slot in offset..=offset + period_count { if let Some(Some(_)) = leases.get(slot) { // If there exists any lease period, we exit early and return true. - return true + return true; } } @@ -578,34 +578,23 @@ mod tests { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); pallet_balances::GenesisConfig::<Test> { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); t.into() } - fn run_to_block(n: BlockNumber) { - while System::block_number() < n { - Slots::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); - Slots::on_initialize(System::block_number()); - } - } - #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_eq!(Slots::lease_period_length(), (10, 0)); let now = System::block_number(); assert_eq!(Slots::lease_period_index(now).unwrap().0, 0); assert_eq!(Slots::deposit_held(1.into(), &1), 0); - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); let now = System::block_number(); assert_eq!(Slots::lease_period_index(now).unwrap().0, 1); }); @@ -614,7 +603,7 @@ mod tests { #[test] fn lease_lifecycle_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(TestRegistrar::<Test>::register( 1, @@ -627,11 +616,11 @@ mod tests { assert_eq!(Slots::deposit_held(1.into(), &1), 1); assert_eq!(Balances::reserved_balance(1), 1); - run_to_block(19); + System::run_to_block::<AllPalletsWithSystem>(19); assert_eq!(Slots::deposit_held(1.into(), &1), 1); assert_eq!(Balances::reserved_balance(1), 1); - run_to_block(20); + System::run_to_block::<AllPalletsWithSystem>(20); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); @@ -645,7 +634,7 @@ mod tests { #[test] fn lease_interrupted_lifecycle_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(TestRegistrar::<Test>::register( 1, @@ -657,19 +646,19 @@ mod tests { assert_ok!(Slots::lease_out(1.into(), &1, 6, 1, 1)); assert_ok!(Slots::lease_out(1.into(), &1, 4, 3, 1)); - run_to_block(19); + System::run_to_block::<AllPalletsWithSystem>(19); assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(20); + System::run_to_block::<AllPalletsWithSystem>(20); assert_eq!(Slots::deposit_held(1.into(), &1), 4); assert_eq!(Balances::reserved_balance(1), 4); - run_to_block(39); + System::run_to_block::<AllPalletsWithSystem>(39); assert_eq!(Slots::deposit_held(1.into(), &1), 4); assert_eq!(Balances::reserved_balance(1), 4); - run_to_block(40); + System::run_to_block::<AllPalletsWithSystem>(40); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); @@ -688,7 +677,7 @@ mod tests { #[test] fn lease_relayed_lifecycle_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(TestRegistrar::<Test>::register( 1, @@ -704,25 +693,25 @@ mod tests { assert_eq!(Slots::deposit_held(1.into(), &2), 4); assert_eq!(Balances::reserved_balance(2), 4); - run_to_block(19); + System::run_to_block::<AllPalletsWithSystem>(19); assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); assert_eq!(Slots::deposit_held(1.into(), &2), 4); assert_eq!(Balances::reserved_balance(2), 4); - run_to_block(20); + System::run_to_block::<AllPalletsWithSystem>(20); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); assert_eq!(Slots::deposit_held(1.into(), &2), 4); assert_eq!(Balances::reserved_balance(2), 4); - run_to_block(29); + System::run_to_block::<AllPalletsWithSystem>(29); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); assert_eq!(Slots::deposit_held(1.into(), &2), 4); assert_eq!(Balances::reserved_balance(2), 4); - run_to_block(30); + System::run_to_block::<AllPalletsWithSystem>(30); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); assert_eq!(Slots::deposit_held(1.into(), &2), 0); @@ -738,7 +727,7 @@ mod tests { #[test] fn lease_deposit_increase_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(TestRegistrar::<Test>::register( 1, @@ -755,11 +744,11 @@ mod tests { assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(29); + System::run_to_block::<AllPalletsWithSystem>(29); assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(30); + System::run_to_block::<AllPalletsWithSystem>(30); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); @@ -773,7 +762,7 @@ mod tests { #[test] fn lease_deposit_decrease_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(TestRegistrar::<Test>::register( 1, @@ -790,19 +779,19 @@ mod tests { assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(19); + System::run_to_block::<AllPalletsWithSystem>(19); assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(20); + System::run_to_block::<AllPalletsWithSystem>(20); assert_eq!(Slots::deposit_held(1.into(), &1), 4); assert_eq!(Balances::reserved_balance(1), 4); - run_to_block(29); + System::run_to_block::<AllPalletsWithSystem>(29); assert_eq!(Slots::deposit_held(1.into(), &1), 4); assert_eq!(Balances::reserved_balance(1), 4); - run_to_block(30); + System::run_to_block::<AllPalletsWithSystem>(30); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); @@ -816,7 +805,7 @@ mod tests { #[test] fn clear_all_leases_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(TestRegistrar::<Test>::register( 1, @@ -852,7 +841,7 @@ mod tests { #[test] fn lease_out_current_lease_period() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(TestRegistrar::<Test>::register( 1, @@ -867,7 +856,7 @@ mod tests { dummy_validation_code() )); - run_to_block(20); + System::run_to_block::<AllPalletsWithSystem>(20); let now = System::block_number(); assert_eq!(Slots::lease_period_index(now).unwrap().0, 2); // Can't lease from the past @@ -884,7 +873,7 @@ mod tests { #[test] fn trigger_onboard_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(TestRegistrar::<Test>::register( 1, ParaId::from(1_u32), @@ -973,7 +962,7 @@ mod benchmarking { use polkadot_runtime_parachains::paras; use sp_runtime::traits::{Bounded, One}; - use frame_benchmarking::{account, benchmarks, whitelisted_caller, BenchmarkError}; + use frame_benchmarking::v2::*; use crate::slots::Pallet as Slots; @@ -1009,10 +998,15 @@ mod benchmarking { (para, leaser) } - benchmarks! { - where_clause { where T: paras::Config } + #[benchmarks( + where T: paras::Config, + )] + + mod benchmarks { + use super::*; - force_lease { + #[benchmark] + fn force_lease() -> Result<(), BenchmarkError> { // If there is an offset, we need to be on that block to be able to do lease things. frame_system::Pallet::<T>::set_block_number(T::LeaseOffset::get() + One::one()); let para = ParaId::from(1337); @@ -1023,23 +1017,32 @@ mod benchmarking { let period_count = 3u32.into(); let origin = T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - }: _<T::RuntimeOrigin>(origin, para, leaser.clone(), amount, period_begin, period_count) - verify { - assert_last_event::<T>(Event::<T>::Leased { - para_id: para, - leaser, period_begin, - period_count, - extra_reserved: amount, - total_amount: amount, - }.into()); - } - // Worst case scenario, T on-demand parachains onboard, and C lease holding parachains offboard. - manage_lease_period_start { - // Assume reasonable maximum of 100 paras at any time - let c in 0 .. 100; - let t in 0 .. 100; + #[extrinsic_call] + _(origin as T::RuntimeOrigin, para, leaser.clone(), amount, period_begin, period_count); + + assert_last_event::<T>( + Event::<T>::Leased { + para_id: para, + leaser, + period_begin, + period_count, + extra_reserved: amount, + total_amount: amount, + } + .into(), + ); + + Ok(()) + } + // Worst case scenario, T on-demand parachains onboard, and C lease holding parachains + // offboard. Assume reasonable maximum of 100 paras at any time + #[benchmark] + fn manage_lease_period_start( + c: Linear<0, 100>, + t: Linear<0, 100>, + ) -> Result<(), BenchmarkError> { let period_begin = 1u32.into(); let period_count = 4u32.into(); @@ -1047,9 +1050,7 @@ mod benchmarking { frame_system::Pallet::<T>::set_block_number(T::LeaseOffset::get() + One::one()); // Make T parathreads (on-demand parachains) - let paras_info = (0..t).map(|i| { - register_a_parathread::<T>(i) - }).collect::<Vec<_>>(); + let paras_info = (0..t).map(|i| register_a_parathread::<T>(i)).collect::<Vec<_>>(); T::Registrar::execute_pending_transitions(); @@ -1064,43 +1065,48 @@ mod benchmarking { T::Registrar::execute_pending_transitions(); // C lease holding parachains are downgrading to on-demand parachains - for i in 200 .. 200 + c { - let (para, leaser) = register_a_parathread::<T>(i); + for i in 200..200 + c { + let (para, _) = register_a_parathread::<T>(i); T::Registrar::make_parachain(para)?; } T::Registrar::execute_pending_transitions(); - for i in 0 .. t { + for i in 0..t { assert!(T::Registrar::is_parathread(ParaId::from(i))); } - for i in 200 .. 200 + c { + for i in 200..200 + c { assert!(T::Registrar::is_parachain(ParaId::from(i))); } - }: { - Slots::<T>::manage_lease_period_start(period_begin); - } verify { + #[block] + { + let _ = Slots::<T>::manage_lease_period_start(period_begin); + } + // All paras should have switched. T::Registrar::execute_pending_transitions(); - for i in 0 .. t { + for i in 0..t { assert!(T::Registrar::is_parachain(ParaId::from(i))); } - for i in 200 .. 200 + c { + for i in 200..200 + c { assert!(T::Registrar::is_parathread(ParaId::from(i))); } + + Ok(()) } // Assume that at most 8 people have deposits for leases on a parachain. // This would cover at least 4 years of leases in the worst case scenario. - clear_all_leases { + #[benchmark] + fn clear_all_leases() -> Result<(), BenchmarkError> { let max_people = 8; let (para, _) = register_a_parathread::<T>(1); // If there is an offset, we need to be on that block to be able to do lease things. frame_system::Pallet::<T>::set_block_number(T::LeaseOffset::get() + One::one()); - for i in 0 .. max_people { + for i in 0..max_people { let leaser = account("lease_deposit", i, 0); let amount = T::Currency::minimum_balance(); T::Currency::make_free_balance_be(&leaser, BalanceOf::<T>::max_value()); @@ -1113,31 +1119,45 @@ mod benchmarking { Slots::<T>::force_lease(origin, para, leaser, amount, period_begin, period_count)?; } - for i in 0 .. max_people { + for i in 0..max_people { let leaser = account("lease_deposit", i, 0); assert_eq!(T::Currency::reserved_balance(&leaser), T::Currency::minimum_balance()); } let origin = T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - }: _<T::RuntimeOrigin>(origin, para) - verify { - for i in 0 .. max_people { + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, para); + + for i in 0..max_people { let leaser = account("lease_deposit", i, 0); assert_eq!(T::Currency::reserved_balance(&leaser), 0u32.into()); } + + Ok(()) } - trigger_onboard { + #[benchmark] + fn trigger_onboard() -> Result<(), BenchmarkError> { // get a parachain into a bad state where they did not onboard let (para, _) = register_a_parathread::<T>(1); - Leases::<T>::insert(para, vec![Some((account::<T::AccountId>("lease_insert", 0, 0), BalanceOf::<T>::default()))]); + Leases::<T>::insert( + para, + vec![Some(( + account::<T::AccountId>("lease_insert", 0, 0), + BalanceOf::<T>::default(), + ))], + ); assert!(T::Registrar::is_parathread(para)); let caller = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), para) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(caller), para); + T::Registrar::execute_pending_transitions(); assert!(T::Registrar::is_parachain(para)); + Ok(()) } impl_benchmark_test_suite!( diff --git a/polkadot/runtime/common/src/xcm_sender.rs b/polkadot/runtime/common/src/xcm_sender.rs index 7ff7f69faf148e9dde971f62d33c7e7a74609bf7..32ea4fdd2f27090acc1ca7ccf890d5c6fdcb3624 100644 --- a/polkadot/runtime/common/src/xcm_sender.rs +++ b/polkadot/runtime/common/src/xcm_sender.rs @@ -138,6 +138,13 @@ where .map(|()| hash) .map_err(|_| SendError::Transport(&"Error placing into DMP queue")) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(location: Option<Location>) { + if let Some((0, [Parachain(id)])) = location.as_ref().map(|l| l.unpack()) { + dmp::Pallet::<T>::make_parachain_reachable(*id); + } + } } impl<T: dmp::Config, W, P> InspectMessageQueues for ChildParachainRouter<T, W, P> { @@ -190,7 +197,7 @@ impl< ExistentialDeposit: Get<Option<Asset>>, PriceForDelivery: PriceForMessageDelivery<Id = ParaId>, Parachain: Get<ParaId>, - ToParachainHelper: EnsureForParachain, + ToParachainHelper: polkadot_runtime_parachains::EnsureForParachain, > xcm_builder::EnsureDelivery for ToParachainDeliveryHelper< XcmConfig, @@ -219,6 +226,9 @@ impl< return (None, None) } + // allow more initialization for target parachain + ToParachainHelper::ensure(Parachain::get()); + let mut fees_mode = None; if !XcmConfig::FeeManager::is_waived(Some(origin_ref), fee_reason) { // if not waived, we need to set up accounts for paying and receiving fees @@ -238,9 +248,6 @@ impl< XcmConfig::AssetTransactor::deposit_asset(&fee, &origin_ref, None).unwrap(); } - // allow more initialization for target parachain - ToParachainHelper::ensure(Parachain::get()); - // expected worst case - direct withdraw fees_mode = Some(FeesMode { jit_withdraw: true }); } @@ -248,18 +255,6 @@ impl< } } -/// Ensure more initialization for `ParaId`. (e.g. open HRMP channels, ...) -#[cfg(feature = "runtime-benchmarks")] -pub trait EnsureForParachain { - fn ensure(para_id: ParaId); -} -#[cfg(feature = "runtime-benchmarks")] -impl EnsureForParachain for () { - fn ensure(_: ParaId) { - // doing nothing - } -} - #[cfg(test)] mod tests { use super::*; @@ -349,6 +344,8 @@ mod tests { c.max_downward_message_size = u32::MAX; }); + dmp::Pallet::<crate::integration_tests::Test>::make_parachain_reachable(5555); + // Check that the good message is validated: assert_ok!(<Router as SendXcm>::validate( &mut Some(dest.into()), diff --git a/polkadot/runtime/metrics/Cargo.toml b/polkadot/runtime/metrics/Cargo.toml index 3709e1eb697ea1a5941b307a4a8abd06238d3b57..beb7e3236d5a8b4efbf37ec69af4ad648f9916fc 100644 --- a/polkadot/runtime/metrics/Cargo.toml +++ b/polkadot/runtime/metrics/Cargo.toml @@ -5,15 +5,17 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Runtime metric interface for the Polkadot node" +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] -sp-tracing = { workspace = true } codec = { workspace = true } -polkadot-primitives = { workspace = true } frame-benchmarking = { optional = true, workspace = true } +polkadot-primitives = { workspace = true } +sp-tracing = { workspace = true } bs58 = { features = ["alloc"], workspace = true } diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index a3eec3f9d961ac2119df82c430178855b212cbb0..6c87f7773c235dcca7f591791e5295a84e2c26cf 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -5,74 +5,75 @@ description = "Relay Chain runtime code responsible for Parachains." authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] -impl-trait-for-tuples = { workspace = true } +bitflags = { workspace = true } bitvec = { features = ["alloc"], workspace = true } codec = { features = ["derive", "max-encoded-len"], workspace = true } +derive_more = { workspace = true, default-features = true } +impl-trait-for-tuples = { workspace = true } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { features = ["alloc", "derive"], workspace = true } -derive_more = { workspace = true, default-features = true } -bitflags = { workspace = true } sp-api = { workspace = true } +sp-application-crypto = { optional = true, workspace = true } +sp-arithmetic = { workspace = true } +sp-core = { features = ["serde"], workspace = true } sp-inherents = { workspace = true } sp-io = { workspace = true } +sp-keystore = { optional = true, workspace = true } sp-runtime = { features = ["serde"], workspace = true } sp-session = { workspace = true } sp-staking = { features = ["serde"], workspace = true } -sp-core = { features = ["serde"], workspace = true } -sp-keystore = { optional = true, workspace = true } -sp-application-crypto = { optional = true, workspace = true } -sp-tracing = { optional = true, workspace = true } -sp-arithmetic = { workspace = true } sp-std = { workspace = true, optional = true } +sp-tracing = { optional = true, workspace = true } +frame-benchmarking = { optional = true, workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } pallet-authority-discovery = { workspace = true } pallet-authorship = { workspace = true } -pallet-balances = { workspace = true } pallet-babe = { workspace = true } +pallet-balances = { workspace = true } pallet-broker = { workspace = true } pallet-message-queue = { workspace = true } pallet-mmr = { workspace = true, optional = true } pallet-session = { workspace = true } pallet-staking = { workspace = true } pallet-timestamp = { workspace = true } -pallet-vesting = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } +polkadot-primitives = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } -polkadot-primitives = { workspace = true } +polkadot-core-primitives = { workspace = true } +polkadot-parachain-primitives = { workspace = true } +polkadot-runtime-metrics = { workspace = true } rand = { workspace = true } rand_chacha = { workspace = true } static_assertions = { optional = true, workspace = true, default-features = true } -polkadot-parachain-primitives = { workspace = true } -polkadot-runtime-metrics = { workspace = true } -polkadot-core-primitives = { workspace = true } [dev-dependencies] polkadot-primitives = { workspace = true, features = ["test"] } +assert_matches = { workspace = true } +frame-support-test = { workspace = true } futures = { workspace = true } hex-literal = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -frame-support-test = { workspace = true } -sc-keystore = { workspace = true, default-features = true } polkadot-primitives-test-helpers = { workspace = true } -sp-tracing = { workspace = true, default-features = true } -sp-crypto-hashing = { workspace = true, default-features = true } -thousands = { workspace = true } -assert_matches = { workspace = true } rstest = { workspace = true } +sc-keystore = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } +thousands = { workspace = true } [features] default = ["std"] @@ -94,7 +95,6 @@ std = [ "pallet-session/std", "pallet-staking/std", "pallet-timestamp/std", - "pallet-vesting/std", "polkadot-core-primitives/std", "polkadot-parachain-primitives/std", "polkadot-primitives/std", @@ -129,7 +129,6 @@ runtime-benchmarks = [ "pallet-mmr/runtime-benchmarks", "pallet-staking/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", - "pallet-vesting/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", "sp-application-crypto", @@ -138,6 +137,7 @@ runtime-benchmarks = [ "sp-std", "static_assertions", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support-test/try-runtime", @@ -153,7 +153,6 @@ try-runtime = [ "pallet-session/try-runtime", "pallet-staking/try-runtime", "pallet-timestamp/try-runtime", - "pallet-vesting/try-runtime", "sp-runtime/try-runtime", ] runtime-metrics = [ diff --git a/polkadot/runtime/parachains/src/coretime/benchmarking.rs b/polkadot/runtime/parachains/src/coretime/benchmarking.rs index 6d593f1954ff1500fd59ae5d178e038dd38e01b5..49e3d8a88c0158f09aa3a4d8e98a969ca907be44 100644 --- a/polkadot/runtime/parachains/src/coretime/benchmarking.rs +++ b/polkadot/runtime/parachains/src/coretime/benchmarking.rs @@ -43,6 +43,8 @@ mod benchmarks { .unwrap(); on_demand::Revenue::<T>::put(rev); + crate::paras::Heads::<T>::insert(ParaId::from(T::BrokerId::get()), vec![1, 2, 3]); + <T as on_demand::Config>::Currency::make_free_balance_be( &<on_demand::Pallet<T>>::account_id(), minimum_balance * (mhr * (mhr + 1)).into(), diff --git a/polkadot/runtime/parachains/src/coretime/mod.rs b/polkadot/runtime/parachains/src/coretime/mod.rs index 966b7997a2775fc5111a243d5dd67f7e8b433cec..5656e92b90be064d45f52481460e5fce36cc591d 100644 --- a/polkadot/runtime/parachains/src/coretime/mod.rs +++ b/polkadot/runtime/parachains/src/coretime/mod.rs @@ -136,6 +136,11 @@ pub mod pallet { type AssetTransactor: TransactAsset; /// AccountId to Location converter type AccountToLocation: for<'a> TryConvert<&'a Self::AccountId, Location>; + + /// Maximum weight for any XCM transact call that should be executed on the coretime chain. + /// + /// Basically should be `max_weight(set_leases, reserve, notify_core_count)`. + type MaxXcmTransactWeight: Get<Weight>; } #[pallet::event] @@ -333,6 +338,7 @@ impl<T: Config> OnNewSession<BlockNumberFor<T>> for Pallet<T> { fn mk_coretime_call<T: Config>(call: crate::coretime::CoretimeCalls) -> Instruction<()> { Instruction::Transact { origin_kind: OriginKind::Superuser, + fallback_max_weight: Some(T::MaxXcmTransactWeight::get()), call: BrokerRuntimePallets::Broker(call).encode().into(), } } diff --git a/polkadot/runtime/parachains/src/disputes/benchmarking.rs b/polkadot/runtime/parachains/src/disputes/benchmarking.rs index 05f4b3f1ac8198ebdb7cf161f5e95bd9f7152fc9..571c44d1ac24d2a1296ea7c72101c7d3f93c19e5 100644 --- a/polkadot/runtime/parachains/src/disputes/benchmarking.rs +++ b/polkadot/runtime/parachains/src/disputes/benchmarking.rs @@ -16,15 +16,21 @@ use super::*; -use frame_benchmarking::benchmarks; +use frame_benchmarking::v2::*; use frame_system::RawOrigin; use sp_runtime::traits::One; -benchmarks! { - force_unfreeze { +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn force_unfreeze() { Frozen::<T>::set(Some(One::one())); - }: _(RawOrigin::Root) - verify { + + #[extrinsic_call] + _(RawOrigin::Root); + assert!(Frozen::<T>::get().is_none()) } diff --git a/polkadot/runtime/parachains/src/disputes/slashing.rs b/polkadot/runtime/parachains/src/disputes/slashing.rs index 2e09ea667f74c7ad116e015d1a7d63afaa908ad6..95dbf2ba42bb9b450ed8e206a0b104489b8eea83 100644 --- a/polkadot/runtime/parachains/src/disputes/slashing.rs +++ b/polkadot/runtime/parachains/src/disputes/slashing.rs @@ -355,12 +355,12 @@ impl<T: Config> HandleReports<T> for () { } pub trait WeightInfo { - fn report_dispute_lost(validator_count: ValidatorSetCount) -> Weight; + fn report_dispute_lost_unsigned(validator_count: ValidatorSetCount) -> Weight; } pub struct TestWeightInfo; impl WeightInfo for TestWeightInfo { - fn report_dispute_lost(_validator_count: ValidatorSetCount) -> Weight { + fn report_dispute_lost_unsigned(_validator_count: ValidatorSetCount) -> Weight { Weight::zero() } } @@ -445,7 +445,7 @@ pub mod pallet { #[pallet::call] impl<T: Config> Pallet<T> { #[pallet::call_index(0)] - #[pallet::weight(<T as Config>::WeightInfo::report_dispute_lost( + #[pallet::weight(<T as Config>::WeightInfo::report_dispute_lost_unsigned( key_owner_proof.validator_count() ))] pub fn report_dispute_lost_unsigned( diff --git a/polkadot/runtime/parachains/src/disputes/slashing/benchmarking.rs b/polkadot/runtime/parachains/src/disputes/slashing/benchmarking.rs index b53f98caeea30cd2d55317eb59e705517ed07ec5..bfd46d75243853e081e72bfedf219b19a725a840 100644 --- a/polkadot/runtime/parachains/src/disputes/slashing/benchmarking.rs +++ b/polkadot/runtime/parachains/src/disputes/slashing/benchmarking.rs @@ -18,7 +18,7 @@ use super::*; use crate::{disputes::SlashingHandler, initializer, shared}; use codec::Decode; -use frame_benchmarking::{benchmarks, whitelist_account}; +use frame_benchmarking::v2::*; use frame_support::traits::{OnFinalize, OnInitialize}; use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use pallet_staking::testing_utils::create_validators; @@ -29,6 +29,11 @@ use sp_session::MembershipProof; // Candidate hash of the disputed candidate. const CANDIDATE_HASH: CandidateHash = CandidateHash(Hash::zero()); +// Simplify getting the value in the benchmark +pub const fn max_validators_for<T: super::Config>() -> u32 { + <<T>::BenchmarkingConfig as BenchmarkingConfiguration>::MAX_VALIDATORS +} + pub trait Config: pallet_session::Config + pallet_session::historical::Config @@ -106,6 +111,7 @@ where (session_index, key_owner_proof, validator_id) } +/// Submits a single `ForInvalid` dispute. fn setup_dispute<T>(session_index: SessionIndex, validator_id: ValidatorId) -> DisputeProof where T: Config, @@ -125,6 +131,7 @@ where dispute_proof(session_index, validator_id, validator_index) } +/// Creates a `ForInvalid` dispute proof. fn dispute_proof( session_index: SessionIndex, validator_id: ValidatorId, @@ -136,27 +143,20 @@ fn dispute_proof( DisputeProof { time_slot, kind, validator_index, validator_id } } -benchmarks! { - where_clause { - where T: Config<KeyOwnerProof = MembershipProof>, - } - - // in this setup we have a single `ForInvalid` dispute - // submitted for a past session - report_dispute_lost { - let n in 4..<<T as super::Config>::BenchmarkingConfig as BenchmarkingConfiguration>::MAX_VALIDATORS; +#[benchmarks(where T: Config<KeyOwnerProof = MembershipProof>)] +mod benchmarks { + use super::*; - let origin = RawOrigin::None.into(); + #[benchmark] + fn report_dispute_lost_unsigned(n: Linear<4, { max_validators_for::<T>() }>) { let (session_index, key_owner_proof, validator_id) = setup_validator_set::<T>(n); + + // submit a single `ForInvalid` dispute for a past session. let dispute_proof = setup_dispute::<T>(session_index, validator_id); - }: { - let result = Pallet::<T>::report_dispute_lost_unsigned( - origin, - Box::new(dispute_proof), - key_owner_proof, - ); - assert!(result.is_ok()); - } verify { + + #[extrinsic_call] + _(RawOrigin::None, Box::new(dispute_proof), key_owner_proof); + let unapplied = <UnappliedSlashes<T>>::get(session_index, CANDIDATE_HASH); assert!(unapplied.is_none()); } diff --git a/polkadot/runtime/parachains/src/dmp.rs b/polkadot/runtime/parachains/src/dmp.rs index 03580e11b8e9cbe7dce4c9e375741ec3677bd9fe..3c9cf8004186527bc999f1eccc71325b81bf5af1 100644 --- a/polkadot/runtime/parachains/src/dmp.rs +++ b/polkadot/runtime/parachains/src/dmp.rs @@ -44,7 +44,7 @@ use crate::{ configuration::{self, HostConfiguration}, - initializer, FeeTracker, + initializer, paras, FeeTracker, }; use alloc::vec::Vec; use core::fmt; @@ -72,12 +72,15 @@ const MESSAGE_SIZE_FEE_BASE: FixedU128 = FixedU128::from_rational(1, 1000); // 0 pub enum QueueDownwardMessageError { /// The message being sent exceeds the configured max message size. ExceedsMaxMessageSize, + /// The destination is unknown. + Unroutable, } impl From<QueueDownwardMessageError> for SendError { fn from(err: QueueDownwardMessageError) -> Self { match err { QueueDownwardMessageError::ExceedsMaxMessageSize => SendError::ExceedsMaxMessageSize, + QueueDownwardMessageError::Unroutable => SendError::Unroutable, } } } @@ -116,7 +119,7 @@ pub mod pallet { pub struct Pallet<T>(_); #[pallet::config] - pub trait Config: frame_system::Config + configuration::Config {} + pub trait Config: frame_system::Config + configuration::Config + paras::Config {} /// The downward messages addressed for a certain para. #[pallet::storage] @@ -200,6 +203,11 @@ impl<T: Config> Pallet<T> { return Err(QueueDownwardMessageError::ExceedsMaxMessageSize) } + // If the head exists, we assume the parachain is legit and exists. + if !paras::Heads::<T>::contains_key(para) { + return Err(QueueDownwardMessageError::Unroutable) + } + Ok(()) } @@ -217,14 +225,7 @@ impl<T: Config> Pallet<T> { msg: DownwardMessage, ) -> Result<(), QueueDownwardMessageError> { let serialized_len = msg.len() as u32; - if serialized_len > config.max_downward_message_size { - return Err(QueueDownwardMessageError::ExceedsMaxMessageSize) - } - - // Hard limit on Queue size - if Self::dmq_length(para) > Self::dmq_max_length(config.max_downward_message_size) { - return Err(QueueDownwardMessageError::ExceedsMaxMessageSize) - } + Self::can_queue_downward_message(config, ¶, &msg)?; let inbound = InboundDownwardMessage { msg, sent_at: frame_system::Pallet::<T>::block_number() }; @@ -336,6 +337,15 @@ impl<T: Config> Pallet<T> { ) -> Vec<InboundDownwardMessage<BlockNumberFor<T>>> { DownwardMessageQueues::<T>::get(&recipient) } + + /// Make the parachain reachable for downward messages. + /// + /// Only useable in benchmarks or tests. + #[cfg(any(feature = "runtime-benchmarks", feature = "std"))] + pub fn make_parachain_reachable(para: impl Into<ParaId>) { + let para = para.into(); + crate::paras::Heads::<T>::insert(para, para.encode()); + } } impl<T: Config> FeeTracker for Pallet<T> { @@ -359,3 +369,10 @@ impl<T: Config> FeeTracker for Pallet<T> { }) } } + +#[cfg(feature = "runtime-benchmarks")] +impl<T: Config> crate::EnsureForParachain for Pallet<T> { + fn ensure(para: ParaId) { + Self::make_parachain_reachable(para); + } +} diff --git a/polkadot/runtime/parachains/src/dmp/tests.rs b/polkadot/runtime/parachains/src/dmp/tests.rs index de15159581252aa29775addda7797d84abc4d115..617c9488bd2a22c713f188ff89993253374848f4 100644 --- a/polkadot/runtime/parachains/src/dmp/tests.rs +++ b/polkadot/runtime/parachains/src/dmp/tests.rs @@ -61,6 +61,12 @@ fn queue_downward_message( Dmp::queue_downward_message(&configuration::ActiveConfig::<Test>::get(), para_id, msg) } +fn register_paras(paras: &[ParaId]) { + paras.iter().for_each(|p| { + Dmp::make_parachain_reachable(*p); + }); +} + #[test] fn clean_dmp_works() { let a = ParaId::from(1312); @@ -68,6 +74,8 @@ fn clean_dmp_works() { let c = ParaId::from(123); new_test_ext(default_genesis_config()).execute_with(|| { + register_paras(&[a, b, c]); + // enqueue downward messages to A, B and C. queue_downward_message(a, vec![1, 2, 3]).unwrap(); queue_downward_message(b, vec![4, 5, 6]).unwrap(); @@ -89,6 +97,8 @@ fn dmq_length_and_head_updated_properly() { let b = ParaId::from(228); new_test_ext(default_genesis_config()).execute_with(|| { + register_paras(&[a, b]); + assert_eq!(Dmp::dmq_length(a), 0); assert_eq!(Dmp::dmq_length(b), 0); @@ -101,11 +111,30 @@ fn dmq_length_and_head_updated_properly() { }); } +#[test] +fn dmq_fail_if_para_does_not_exist() { + let a = ParaId::from(1312); + + new_test_ext(default_genesis_config()).execute_with(|| { + assert_eq!(Dmp::dmq_length(a), 0); + + assert!(matches!( + queue_downward_message(a, vec![1, 2, 3]), + Err(QueueDownwardMessageError::Unroutable) + )); + + assert_eq!(Dmp::dmq_length(a), 0); + assert!(Dmp::dmq_mqc_head(a).is_zero()); + }); +} + #[test] fn dmp_mqc_head_fixture() { let a = ParaId::from(2000); new_test_ext(default_genesis_config()).execute_with(|| { + register_paras(&[a]); + run_to_block(2, None); assert!(Dmp::dmq_mqc_head(a).is_zero()); queue_downward_message(a, vec![1, 2, 3]).unwrap(); @@ -125,6 +154,8 @@ fn check_processed_downward_messages() { let a = ParaId::from(1312); new_test_ext(default_genesis_config()).execute_with(|| { + register_paras(&[a]); + let block_number = System::block_number(); // processed_downward_messages=0 is allowed when the DMQ is empty. @@ -150,6 +181,8 @@ fn check_processed_downward_messages_advancement_rule() { let a = ParaId::from(1312); new_test_ext(default_genesis_config()).execute_with(|| { + register_paras(&[a]); + let block_number = System::block_number(); run_to_block(block_number + 1, None); @@ -170,6 +203,8 @@ fn dmq_pruning() { let a = ParaId::from(1312); new_test_ext(default_genesis_config()).execute_with(|| { + register_paras(&[a]); + assert_eq!(Dmp::dmq_length(a), 0); queue_downward_message(a, vec![1, 2, 3]).unwrap(); @@ -194,6 +229,8 @@ fn queue_downward_message_critical() { genesis.configuration.config.max_downward_message_size = 7; new_test_ext(genesis).execute_with(|| { + register_paras(&[a]); + let smol = [0; 3].to_vec(); let big = [0; 8].to_vec(); @@ -215,6 +252,8 @@ fn verify_dmq_mqc_head_is_externally_accessible() { let a = ParaId::from(2020); new_test_ext(default_genesis_config()).execute_with(|| { + register_paras(&[a]); + let head = sp_io::storage::get(&well_known_keys::dmq_mqc_head(a)); assert_eq!(head, None); @@ -235,9 +274,12 @@ fn verify_dmq_mqc_head_is_externally_accessible() { #[test] fn verify_fee_increase_and_decrease() { let a = ParaId::from(123); + let mut genesis = default_genesis_config(); genesis.configuration.config.max_downward_message_size = 16777216; new_test_ext(genesis).execute_with(|| { + register_paras(&[a]); + let initial = InitialFactor::get(); assert_eq!(DeliveryFeeFactor::<Test>::get(a), initial); @@ -287,6 +329,8 @@ fn verify_fee_factor_reaches_high_value() { let mut genesis = default_genesis_config(); genesis.configuration.config.max_downward_message_size = 51200; new_test_ext(genesis).execute_with(|| { + register_paras(&[a]); + let max_messages = Dmp::dmq_max_length(ActiveConfig::<Test>::get().max_downward_message_size); let mut total_fee_factor = FixedU128::from_float(1.0); diff --git a/polkadot/runtime/parachains/src/inclusion/benchmarking.rs b/polkadot/runtime/parachains/src/inclusion/benchmarking.rs index 1dac3c92cf166cd30c2170d9d8fb142fad6af54f..ab95c5c2366a4d085e98393a58b0ed88495a8b89 100644 --- a/polkadot/runtime/parachains/src/inclusion/benchmarking.rs +++ b/polkadot/runtime/parachains/src/inclusion/benchmarking.rs @@ -14,6 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see <http://www.gnu.org/licenses/>. +use bitvec::{bitvec, prelude::Lsb0}; +use frame_benchmarking::v2::*; +use pallet_message_queue as mq; +use polkadot_primitives::{ + vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CandidateCommitments, + HrmpChannelId, OutboundHrmpMessage, SessionIndex, +}; + use super::*; use crate::{ builder::generate_validator_pairs, @@ -21,13 +29,6 @@ use crate::{ hrmp::{HrmpChannel, HrmpChannels}, initializer, HeadData, ValidationCode, }; -use bitvec::{bitvec, prelude::Lsb0}; -use frame_benchmarking::benchmarks; -use pallet_message_queue as mq; -use polkadot_primitives::{ - vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CandidateCommitments, - HrmpChannelId, OutboundHrmpMessage, SessionIndex, -}; fn create_candidate_commitments<T: crate::hrmp::pallet::Config>( para_id: ParaId, @@ -70,7 +71,7 @@ fn create_candidate_commitments<T: crate::hrmp::pallet::Config>( BoundedVec::truncate_from(unbounded) }; - let new_validation_code = code_upgrade.then_some(ValidationCode(vec![42u8; 1024])); + let new_validation_code = code_upgrade.then_some(ValidationCode(vec![42_u8; 1024])); CandidateCommitments::<u32> { upward_messages, @@ -87,18 +88,13 @@ fn create_messages(msg_len: usize, n_msgs: usize) -> Vec<Vec<u8>> { vec![vec![best_number; msg_len]; n_msgs] } -benchmarks! { - where_clause { - where - T: mq::Config + configuration::Config + initializer::Config, - } - - enact_candidate { - let u in 0 .. 2; - let h in 0 .. 2; - let c in 0 .. 1; +#[benchmarks(where T: mq::Config + configuration::Config + initializer::Config)] +mod benchmarks { + use super::*; - let para = 42_u32.into(); // not especially important. + #[benchmark] + fn enact_candidate(u: Linear<0, 2>, h: Linear<0, 2>, c: Linear<0, 1>) { + let para = 42_u32.into(); // not especially important. let max_len = mq::MaxMessageLenOf::<T>::get() as usize; @@ -106,7 +102,7 @@ benchmarks! { let n_validators = config.max_validators.unwrap_or(500); let validators = generate_validator_pairs::<T>(n_validators); - let session = SessionIndex::from(0u32); + let session = SessionIndex::from(0_u32); initializer::Pallet::<T>::test_trigger_on_new_session( false, session, @@ -116,7 +112,7 @@ benchmarks! { let backing_group_size = config.scheduler_params.max_validators_per_core.unwrap_or(5); let head_data = HeadData(vec![0xFF; 1024]); - let relay_parent_number = BlockNumberFor::<T>::from(10u32); + let relay_parent_number = BlockNumberFor::<T>::from(10_u32); let commitments = create_candidate_commitments::<T>(para, head_data, max_len, u, h, c != 0); let backers = bitvec![u8, Lsb0; 1; backing_group_size as usize]; let availability_votes = bitvec![u8, Lsb0; 1; n_validators as usize]; @@ -135,17 +131,26 @@ benchmarks! { ValidationCode(vec![1, 2, 3]).hash(), ); - let receipt = CommittedCandidateReceipt::<T::Hash> { - descriptor, - commitments, - }; + let receipt = CommittedCandidateReceipt::<T::Hash> { descriptor, commitments }; - Pallet::<T>::receive_upward_messages(para, vec![vec![0; max_len]; 1].as_slice()); - } : { Pallet::<T>::enact_candidate(relay_parent_number, receipt, backers, availability_votes, core_index, backing_group) } + Pallet::<T>::receive_upward_messages(para, &vec![vec![0; max_len]; 1]); - impl_benchmark_test_suite!( + #[block] + { + Pallet::<T>::enact_candidate( + relay_parent_number, + receipt, + backers, + availability_votes, + core_index, + backing_group, + ); + } + } + + impl_benchmark_test_suite! { Pallet, crate::mock::new_test_ext(Default::default()), crate::mock::Test - ); + } } diff --git a/polkadot/runtime/parachains/src/lib.rs b/polkadot/runtime/parachains/src/lib.rs index 828c0b9bcef21410d15107191c1255d4439a1bc5..b1ff5419470e18c16546397fb2ebbc2d5440f309 100644 --- a/polkadot/runtime/parachains/src/lib.rs +++ b/polkadot/runtime/parachains/src/lib.rs @@ -114,3 +114,19 @@ pub fn schedule_code_upgrade<T: paras::Config>( pub fn set_current_head<T: paras::Config>(id: ParaId, new_head: HeadData) { paras::Pallet::<T>::set_current_head(id, new_head) } + +/// Ensure more initialization for `ParaId` when benchmarking. (e.g. open HRMP channels, ...) +#[cfg(feature = "runtime-benchmarks")] +pub trait EnsureForParachain { + fn ensure(para_id: ParaId); +} + +#[cfg(feature = "runtime-benchmarks")] +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl EnsureForParachain for Tuple { + fn ensure(para: ParaId) { + for_tuples!( #( + Tuple::ensure(para); + )* ); + } +} diff --git a/polkadot/runtime/parachains/src/mock.rs b/polkadot/runtime/parachains/src/mock.rs index d701e1f9bd80f4a9d5979e30086de645685aefac..ee1990a7b618ab9ad8a3a6261a55b2e1163d3080 100644 --- a/polkadot/runtime/parachains/src/mock.rs +++ b/polkadot/runtime/parachains/src/mock.rs @@ -421,6 +421,7 @@ impl assigner_coretime::Config for Test {} parameter_types! { pub const BrokerId: u32 = 10u32; + pub MaxXcmTransactWeight: Weight = Weight::from_parts(10_000_000, 10_000); } pub struct BrokerPot; @@ -437,6 +438,7 @@ impl coretime::Config for Test { type BrokerId = BrokerId; type WeightInfo = crate::coretime::TestWeightInfo; type SendXcm = DummyXcmSender; + type MaxXcmTransactWeight = MaxXcmTransactWeight; type BrokerPotLocation = BrokerPot; type AssetTransactor = (); type AccountToLocation = (); diff --git a/polkadot/runtime/parachains/src/paras/benchmarking.rs b/polkadot/runtime/parachains/src/paras/benchmarking.rs index 7bf8b833ed915df49aabcb9047ec889076836af0..4d617cbb05bbfd7ba5f39d6a84ad5580a45b0210 100644 --- a/polkadot/runtime/parachains/src/paras/benchmarking.rs +++ b/polkadot/runtime/parachains/src/paras/benchmarking.rs @@ -17,7 +17,7 @@ use super::*; use crate::configuration::HostConfiguration; use alloc::vec; -use frame_benchmarking::benchmarks; +use frame_benchmarking::v2::*; use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use polkadot_primitives::{ HeadData, Id as ParaId, ValidationCode, MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, @@ -84,41 +84,58 @@ fn generate_disordered_actions_queue<T: Config>() { }); } -benchmarks! { - force_set_current_code { - let c in MIN_CODE_SIZE .. MAX_CODE_SIZE; +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn force_set_current_code(c: Linear<MIN_CODE_SIZE, MAX_CODE_SIZE>) { let new_code = ValidationCode(vec![0; c as usize]); let para_id = ParaId::from(c as u32); CurrentCodeHash::<T>::insert(¶_id, new_code.hash()); generate_disordered_pruning::<T>(); - }: _(RawOrigin::Root, para_id, new_code) - verify { + + #[extrinsic_call] + _(RawOrigin::Root, para_id, new_code); + assert_last_event::<T>(Event::CurrentCodeUpdated(para_id).into()); } - force_set_current_head { - let s in MIN_CODE_SIZE .. MAX_HEAD_DATA_SIZE; + + #[benchmark] + fn force_set_current_head(s: Linear<MIN_CODE_SIZE, MAX_HEAD_DATA_SIZE>) { let new_head = HeadData(vec![0; s as usize]); let para_id = ParaId::from(1000); - }: _(RawOrigin::Root, para_id, new_head) - verify { + + #[extrinsic_call] + _(RawOrigin::Root, para_id, new_head); + assert_last_event::<T>(Event::CurrentHeadUpdated(para_id).into()); } - force_set_most_recent_context { + + #[benchmark] + fn force_set_most_recent_context() { let para_id = ParaId::from(1000); let context = BlockNumberFor::<T>::from(1000u32); - }: _(RawOrigin::Root, para_id, context) - force_schedule_code_upgrade { - let c in MIN_CODE_SIZE .. MAX_CODE_SIZE; + + #[extrinsic_call] + _(RawOrigin::Root, para_id, context); + } + + #[benchmark] + fn force_schedule_code_upgrade(c: Linear<MIN_CODE_SIZE, MAX_CODE_SIZE>) { let new_code = ValidationCode(vec![0; c as usize]); let para_id = ParaId::from(c as u32); let block = BlockNumberFor::<T>::from(c); generate_disordered_upgrades::<T>(); - }: _(RawOrigin::Root, para_id, new_code, block) - verify { + + #[extrinsic_call] + _(RawOrigin::Root, para_id, new_code, block); + assert_last_event::<T>(Event::CodeUpgradeScheduled(para_id).into()); } - force_note_new_head { - let s in MIN_CODE_SIZE .. MAX_HEAD_DATA_SIZE; + + #[benchmark] + fn force_note_new_head(s: Linear<MIN_CODE_SIZE, MAX_HEAD_DATA_SIZE>) { let para_id = ParaId::from(1000); let new_head = HeadData(vec![0; s as usize]); let old_code_hash = ValidationCode(vec![0]).hash(); @@ -135,70 +152,101 @@ benchmarks! { &config, UpgradeStrategy::SetGoAheadSignal, ); - }: _(RawOrigin::Root, para_id, new_head) - verify { + + #[extrinsic_call] + _(RawOrigin::Root, para_id, new_head); + assert_last_event::<T>(Event::NewHeadNoted(para_id).into()); } - force_queue_action { + + #[benchmark] + fn force_queue_action() { let para_id = ParaId::from(1000); generate_disordered_actions_queue::<T>(); - }: _(RawOrigin::Root, para_id) - verify { - let next_session = crate::shared::CurrentSessionIndex::<T>::get().saturating_add(One::one()); + + #[extrinsic_call] + _(RawOrigin::Root, para_id); + + let next_session = + crate::shared::CurrentSessionIndex::<T>::get().saturating_add(One::one()); assert_last_event::<T>(Event::ActionQueued(para_id, next_session).into()); } - add_trusted_validation_code { - let c in MIN_CODE_SIZE .. MAX_CODE_SIZE; + #[benchmark] + fn add_trusted_validation_code(c: Linear<MIN_CODE_SIZE, MAX_CODE_SIZE>) { let new_code = ValidationCode(vec![0; c as usize]); pvf_check::prepare_bypassing_bench::<T>(new_code.clone()); - }: _(RawOrigin::Root, new_code) - poke_unused_validation_code { + #[extrinsic_call] + _(RawOrigin::Root, new_code); + } + + #[benchmark] + fn poke_unused_validation_code() { let code_hash = [0; 32].into(); - }: _(RawOrigin::Root, code_hash) - include_pvf_check_statement { + #[extrinsic_call] + _(RawOrigin::Root, code_hash); + } + + #[benchmark] + fn include_pvf_check_statement() { let (stmt, signature) = pvf_check::prepare_inclusion_bench::<T>(); - }: { - let _ = Pallet::<T>::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + + #[block] + { + let _ = + Pallet::<T>::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + } } - include_pvf_check_statement_finalize_upgrade_accept { - let (stmt, signature) = pvf_check::prepare_finalization_bench::<T>( - VoteCause::Upgrade, - VoteOutcome::Accept, - ); - }: { - let _ = Pallet::<T>::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + #[benchmark] + fn include_pvf_check_statement_finalize_upgrade_accept() { + let (stmt, signature) = + pvf_check::prepare_finalization_bench::<T>(VoteCause::Upgrade, VoteOutcome::Accept); + + #[block] + { + let _ = + Pallet::<T>::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + } } - include_pvf_check_statement_finalize_upgrade_reject { - let (stmt, signature) = pvf_check::prepare_finalization_bench::<T>( - VoteCause::Upgrade, - VoteOutcome::Reject, - ); - }: { - let _ = Pallet::<T>::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + #[benchmark] + fn include_pvf_check_statement_finalize_upgrade_reject() { + let (stmt, signature) = + pvf_check::prepare_finalization_bench::<T>(VoteCause::Upgrade, VoteOutcome::Reject); + + #[block] + { + let _ = + Pallet::<T>::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + } } - include_pvf_check_statement_finalize_onboarding_accept { - let (stmt, signature) = pvf_check::prepare_finalization_bench::<T>( - VoteCause::Onboarding, - VoteOutcome::Accept, - ); - }: { - let _ = Pallet::<T>::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + #[benchmark] + fn include_pvf_check_statement_finalize_onboarding_accept() { + let (stmt, signature) = + pvf_check::prepare_finalization_bench::<T>(VoteCause::Onboarding, VoteOutcome::Accept); + + #[block] + { + let _ = + Pallet::<T>::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + } } - include_pvf_check_statement_finalize_onboarding_reject { - let (stmt, signature) = pvf_check::prepare_finalization_bench::<T>( - VoteCause::Onboarding, - VoteOutcome::Reject, - ); - }: { - let _ = Pallet::<T>::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + #[benchmark] + fn include_pvf_check_statement_finalize_onboarding_reject() { + let (stmt, signature) = + pvf_check::prepare_finalization_bench::<T>(VoteCause::Onboarding, VoteOutcome::Reject); + + #[block] + { + let _ = + Pallet::<T>::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + } } impl_benchmark_test_suite!( diff --git a/polkadot/runtime/parachains/src/paras_inherent/mod.rs b/polkadot/runtime/parachains/src/paras_inherent/mod.rs index 4c1394fd1347395371a581619d56a70df1bc9c73..b3057c25d8563cb1de61216e80b1373000312ee5 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/mod.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/mod.rs @@ -329,7 +329,7 @@ impl<T: Config> Pallet<T> { }) .collect(), parent_number, - config.async_backing_params.allowed_ancestry_len, + config.scheduler_params.lookahead, ); }); } diff --git a/polkadot/runtime/parachains/src/paras_inherent/weights.rs b/polkadot/runtime/parachains/src/paras_inherent/weights.rs index 81c926a90e0bf81e427fe6ecfbb71c4c4c62f3b2..31958cf4acfb340274aeebcfb4e4997581fe9f59 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/weights.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/weights.rs @@ -94,10 +94,13 @@ pub fn paras_inherent_total_weight<T: Config>( bitfields: &UncheckedSignedAvailabilityBitfields, disputes: &MultiDisputeStatementSet, ) -> Weight { - backed_candidates_weight::<T>(backed_candidates) + let weight = backed_candidates_weight::<T>(backed_candidates) .saturating_add(signed_bitfields_weight::<T>(bitfields)) .saturating_add(multi_dispute_statement_sets_weight::<T>(disputes)) - .saturating_add(enact_candidates_max_weight::<T>(bitfields)) + .saturating_add(enact_candidates_max_weight::<T>(bitfields)); + // Relay chain blocks pre-dispatch weight can be set to any high enough value + // but the proof size is irrelevant for the relay chain either way. + weight.set_proof_size(u64::MAX) } pub fn multi_dispute_statement_sets_weight<T: Config>( diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs index e9327bc7641a3602eaaf7096cbd16d2d6e24b780..133d4ca1877d2ba1b2900e10e9d886fc5bbe95fa 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs @@ -401,10 +401,10 @@ pub fn minimum_backing_votes<T: initializer::Config>() -> u32 { configuration::ActiveConfig::<T>::get().minimum_backing_votes } -/// Implementation for `ParaBackingState` function from the runtime API -pub fn backing_state<T: initializer::Config>( +// Helper function that returns the backing constraints given a parachain id. +pub(crate) fn backing_constraints<T: initializer::Config>( para_id: ParaId, -) -> Option<BackingState<T::Hash, BlockNumberFor<T>>> { +) -> Option<Constraints<BlockNumberFor<T>>> { let config = configuration::ActiveConfig::<T>::get(); // Async backing is only expected to be enabled with a tracker capacity of 1. // Subsequent configuration update gets applied on new session, which always @@ -420,12 +420,12 @@ pub fn backing_state<T: initializer::Config>( { shared::migration::v0::AllowedRelayParents::<T>::get().hypothetical_earliest_block_number( now, - config.async_backing_params.allowed_ancestry_len, + config.scheduler_params.lookahead.saturating_sub(1), ) } else { shared::AllowedRelayParents::<T>::get().hypothetical_earliest_block_number( now, - config.async_backing_params.allowed_ancestry_len, + config.scheduler_params.lookahead.saturating_sub(1), ) }; @@ -458,7 +458,7 @@ pub fn backing_state<T: initializer::Config>( }) .collect(); - let constraints = Constraints { + Some(Constraints { min_relay_parent_number, max_pov_size: config.max_pov_size, max_code_size: config.max_code_size, @@ -473,7 +473,16 @@ pub fn backing_state<T: initializer::Config>( validation_code_hash, upgrade_restriction, future_validation_code, - }; + }) +} + +/// Implementation for `ParaBackingState` function from the runtime API +#[deprecated(note = "`backing_state` will be removed. Use `backing_constraints` and + `candidates_pending_availability` instead.")] +pub fn backing_state<T: initializer::Config>( + para_id: ParaId, +) -> Option<BackingState<T::Hash, BlockNumberFor<T>>> { + let constraints = backing_constraints::<T>(para_id)?; let pending_availability = { crate::inclusion::PendingAvailability::<T>::get(¶_id) @@ -499,6 +508,7 @@ pub fn backing_state<T: initializer::Config>( } /// Implementation for `AsyncBackingParams` function from the runtime API +#[deprecated = "AsyncBackingParams are going to be removed and ignored by relay chain validators, in favour of dynamically computed values based on the claim queue assignments"] pub fn async_backing_params<T: configuration::Config>() -> AsyncBackingParams { configuration::ActiveConfig::<T>::get().async_backing_params } diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs index d01b543630c31c80747d2d1e80480b931ae86886..5a77af0d79731e5d06d1c5b7e5bf9f02b7a3485d 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs @@ -15,3 +15,38 @@ // along with Polkadot. If not, see <http://www.gnu.org/licenses/>. //! Put implementations of functions from staging APIs here. + +use crate::{configuration, initializer}; +use frame_system::pallet_prelude::*; +use polkadot_primitives::{vstaging::async_backing::Constraints, Id as ParaId}; + +/// Implementation for `constraints` function from the runtime API +pub fn backing_constraints<T: initializer::Config>( + para_id: ParaId, +) -> Option<Constraints<BlockNumberFor<T>>> { + let config = configuration::ActiveConfig::<T>::get(); + let constraints_v11 = super::v11::backing_constraints::<T>(para_id)?; + + Some(Constraints { + min_relay_parent_number: constraints_v11.min_relay_parent_number, + max_pov_size: constraints_v11.max_pov_size, + max_code_size: constraints_v11.max_code_size, + max_head_data_size: config.max_head_data_size, + ump_remaining: constraints_v11.ump_remaining, + ump_remaining_bytes: constraints_v11.ump_remaining_bytes, + max_ump_num_per_candidate: constraints_v11.max_ump_num_per_candidate, + dmp_remaining_messages: constraints_v11.dmp_remaining_messages, + hrmp_inbound: constraints_v11.hrmp_inbound, + hrmp_channels_out: constraints_v11.hrmp_channels_out, + max_hrmp_num_per_candidate: constraints_v11.max_hrmp_num_per_candidate, + required_parent: constraints_v11.required_parent, + validation_code_hash: constraints_v11.validation_code_hash, + upgrade_restriction: constraints_v11.upgrade_restriction, + future_validation_code: constraints_v11.future_validation_code, + }) +} + +/// Implementation for `scheduling_lookahead` function from the runtime API +pub fn scheduling_lookahead<T: initializer::Config>() -> u32 { + configuration::ActiveConfig::<T>::get().scheduler_params.lookahead +} diff --git a/polkadot/runtime/parachains/src/shared.rs b/polkadot/runtime/parachains/src/shared.rs index 473c1aba7a066d198f7bcdad8ad920cf7cf955e8..94b69e4ae4b4a4b89aaa35b0b7a044281f4ab1e8 100644 --- a/polkadot/runtime/parachains/src/shared.rs +++ b/polkadot/runtime/parachains/src/shared.rs @@ -96,13 +96,10 @@ impl<Hash: PartialEq + Copy, BlockNumber: AtLeast32BitUnsigned + Copy> let claim_queue = transpose_claim_queue(claim_queue); - // + 1 for the most recent block, which is always allowed. - let buffer_size_limit = max_ancestry_len as usize + 1; - self.buffer.push_back(RelayParentInfo { relay_parent, state_root, claim_queue }); self.latest_number = number; - while self.buffer.len() > buffer_size_limit { + while self.buffer.len() > (max_ancestry_len as usize) { let _ = self.buffer.pop_front(); } @@ -295,7 +292,7 @@ impl<T: Config> Pallet<T> { max_ancestry_len: u32, ) { AllowedRelayParents::<T>::mutate(|tracker| { - tracker.update(relay_parent, state_root, claim_queue, number, max_ancestry_len) + tracker.update(relay_parent, state_root, claim_queue, number, max_ancestry_len + 1) }) } } diff --git a/polkadot/runtime/parachains/src/shared/tests.rs b/polkadot/runtime/parachains/src/shared/tests.rs index f7ea5148ce33417740f09b9e39b64f94b0a2c29b..a35945549e74261c37e4e28575a12e074f0c5a39 100644 --- a/polkadot/runtime/parachains/src/shared/tests.rs +++ b/polkadot/runtime/parachains/src/shared/tests.rs @@ -36,8 +36,8 @@ fn tracker_earliest_block_number() { // Push a single block into the tracker, suppose max capacity is 1. let max_ancestry_len = 0; - tracker.update(Hash::zero(), Hash::zero(), Default::default(), 0, max_ancestry_len); - assert_eq!(tracker.hypothetical_earliest_block_number(now, max_ancestry_len), now); + tracker.update(Hash::zero(), Hash::zero(), Default::default(), 0, max_ancestry_len + 1); + assert_eq!(tracker.hypothetical_earliest_block_number(now, max_ancestry_len as _), now); // Test a greater capacity. let max_ancestry_len = 4; @@ -48,14 +48,14 @@ fn tracker_earliest_block_number() { Hash::zero(), Default::default(), i, - max_ancestry_len, + max_ancestry_len + 1, ); - assert_eq!(tracker.hypothetical_earliest_block_number(i + 1, max_ancestry_len), 0); + assert_eq!(tracker.hypothetical_earliest_block_number(i + 1, max_ancestry_len as _), 0); } // Capacity exceeded. tracker.update(Hash::zero(), Hash::zero(), Default::default(), now, max_ancestry_len); - assert_eq!(tracker.hypothetical_earliest_block_number(now + 1, max_ancestry_len), 1); + assert_eq!(tracker.hypothetical_earliest_block_number(now + 1, max_ancestry_len as _), 1); } #[test] @@ -67,7 +67,7 @@ fn tracker_claim_queue_transpose() { claim_queue.insert(CoreIndex(1), vec![Id::from(0), Id::from(0), Id::from(100)].into()); claim_queue.insert(CoreIndex(2), vec![Id::from(1), Id::from(2), Id::from(100)].into()); - tracker.update(Hash::zero(), Hash::zero(), claim_queue, 1u32, 3u32); + tracker.update(Hash::zero(), Hash::zero(), claim_queue, 1u32, 4); let (info, _block_num) = tracker.acquire_info(Hash::zero(), None).unwrap(); assert_eq!( @@ -120,14 +120,20 @@ fn tracker_acquire_info() { ]; let (relay_parent, state_root) = blocks[0]; - tracker.update(relay_parent, state_root, Default::default(), 0, max_ancestry_len); + tracker.update(relay_parent, state_root, Default::default(), 0, max_ancestry_len + 1); assert_matches!( tracker.acquire_info(relay_parent, None), Some((s, b)) if s.state_root == state_root && b == 0 ); // Try to push a duplicate. Should be ignored. - tracker.update(relay_parent, Hash::repeat_byte(13), Default::default(), 0, max_ancestry_len); + tracker.update( + relay_parent, + Hash::repeat_byte(13), + Default::default(), + 0, + max_ancestry_len + 1, + ); assert_eq!(tracker.buffer.len(), 1); assert_matches!( tracker.acquire_info(relay_parent, None), @@ -135,9 +141,9 @@ fn tracker_acquire_info() { ); let (relay_parent, state_root) = blocks[1]; - tracker.update(relay_parent, state_root, Default::default(), 1u32, max_ancestry_len); + tracker.update(relay_parent, state_root, Default::default(), 1u32, max_ancestry_len + 1); let (relay_parent, state_root) = blocks[2]; - tracker.update(relay_parent, state_root, Default::default(), 2u32, max_ancestry_len); + tracker.update(relay_parent, state_root, Default::default(), 2u32, max_ancestry_len + 1); for (block_num, (rp, state_root)) in blocks.iter().enumerate().take(2) { assert_matches!( tracker.acquire_info(*rp, None), diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index 3b11c977edf38be11797f3724ec2e68340675986..67c7cacc296b9bb210bbca53284e2656852a6611 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -6,44 +6,51 @@ description = "Rococo testnet Relay Chain runtime." authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] +bitvec = { features = ["alloc"], workspace = true } codec = { features = ["derive", "max-encoded-len"], workspace = true } -scale-info = { features = ["derive"], workspace = true } log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } serde = { workspace = true } serde_derive = { optional = true, workspace = true } serde_json = { features = ["alloc"], workspace = true } -static_assertions = { workspace = true, default-features = true } smallvec = { workspace = true, default-features = true } -bitvec = { features = ["alloc"], workspace = true } +static_assertions = { workspace = true, default-features = true } +binary-merkle-tree = { workspace = true } +rococo-runtime-constants = { workspace = true } +sp-api = { workspace = true } +sp-arithmetic = { workspace = true } sp-authority-discovery = { workspace = true } +sp-block-builder = { workspace = true } sp-consensus-babe = { workspace = true } sp-consensus-beefy = { workspace = true } sp-consensus-grandpa = { workspace = true } -binary-merkle-tree = { workspace = true } -rococo-runtime-constants = { workspace = true } -sp-api = { workspace = true } +sp-core = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } -sp-offchain = { workspace = true } -sp-arithmetic = { workspace = true } sp-io = { workspace = true } +sp-keyring = { workspace = true } sp-mmr-primitives = { workspace = true } +sp-offchain = { workspace = true } sp-runtime = { workspace = true } -sp-staking = { workspace = true } -sp-core = { workspace = true } sp-session = { workspace = true } +sp-staking = { workspace = true } sp-storage = { workspace = true } -sp-version = { workspace = true } sp-transaction-pool = { workspace = true } -sp-block-builder = { workspace = true } -sp-keyring = { workspace = true } +sp-version = { workspace = true } +frame-executive = { workspace = true } +frame-support = { features = ["tuples-96"], workspace = true } +frame-system = { workspace = true } +frame-system-rpc-runtime-api = { workspace = true } +pallet-asset-rate = { workspace = true } pallet-authority-discovery = { workspace = true } pallet-authorship = { workspace = true } pallet-babe = { workspace = true } @@ -52,19 +59,12 @@ pallet-beefy = { workspace = true } pallet-beefy-mmr = { workspace = true } pallet-bounties = { workspace = true } pallet-child-bounties = { workspace = true } -pallet-state-trie-migration = { workspace = true } -pallet-transaction-payment = { workspace = true } -pallet-transaction-payment-rpc-runtime-api = { workspace = true } -pallet-collective = { workspace = true } pallet-conviction-voting = { workspace = true } pallet-democracy = { workspace = true } pallet-elections-phragmen = { workspace = true } -pallet-asset-rate = { workspace = true } -frame-executive = { workspace = true } pallet-grandpa = { workspace = true } pallet-identity = { workspace = true } pallet-indices = { workspace = true } -pallet-membership = { workspace = true } pallet-message-queue = { workspace = true } pallet-migrations = { workspace = true } pallet-mmr = { workspace = true } @@ -77,48 +77,46 @@ pallet-proxy = { workspace = true } pallet-ranked-collective = { workspace = true } pallet-recovery = { workspace = true } pallet-referenda = { workspace = true } +pallet-root-testing = { workspace = true } pallet-scheduler = { workspace = true } pallet-session = { workspace = true } pallet-society = { workspace = true } -pallet-sudo = { workspace = true } -frame-support = { features = ["tuples-96"], workspace = true } pallet-staking = { workspace = true } -frame-system = { workspace = true } -frame-system-rpc-runtime-api = { workspace = true } +pallet-state-trie-migration = { workspace = true } +pallet-sudo = { workspace = true } pallet-timestamp = { workspace = true } pallet-tips = { workspace = true } +pallet-transaction-payment = { workspace = true } +pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-treasury = { workspace = true } pallet-utility = { workspace = true } pallet-vesting = { workspace = true } pallet-whitelist = { workspace = true } pallet-xcm = { workspace = true } pallet-xcm-benchmarks = { optional = true, workspace = true } -pallet-root-testing = { workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-metadata-hash-extension = { workspace = true } -frame-try-runtime = { optional = true, workspace = true } frame-system-benchmarking = { optional = true, workspace = true } +frame-try-runtime = { optional = true, workspace = true } hex-literal = { workspace = true, default-features = true } +polkadot-parachain-primitives = { workspace = true } +polkadot-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } polkadot-runtime-parachains = { workspace = true } -polkadot-primitives = { workspace = true } -polkadot-parachain-primitives = { workspace = true } xcm = { workspace = true } -xcm-executor = { workspace = true } xcm-builder = { workspace = true } +xcm-executor = { workspace = true } xcm-runtime-apis = { workspace = true } [dev-dependencies] -tiny-keccak = { features = ["keccak"], workspace = true } -sp-keyring = { workspace = true, default-features = true } remote-externalities = { workspace = true, default-features = true } -sp-trie = { workspace = true, default-features = true } -separator = { workspace = true } serde_json = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } sp-tracing = { workspace = true } +sp-trie = { workspace = true, default-features = true } tokio = { features = ["macros"], workspace = true, default-features = true } [build-dependencies] @@ -149,14 +147,12 @@ std = [ "pallet-beefy/std", "pallet-bounties/std", "pallet-child-bounties/std", - "pallet-collective/std", "pallet-conviction-voting/std", "pallet-democracy/std", "pallet-elections-phragmen/std", "pallet-grandpa/std", "pallet-identity/std", "pallet-indices/std", - "pallet-membership/std", "pallet-message-queue/std", "pallet-migrations/std", "pallet-mmr/std", @@ -232,14 +228,12 @@ runtime-benchmarks = [ "pallet-beefy-mmr/runtime-benchmarks", "pallet-bounties/runtime-benchmarks", "pallet-child-bounties/runtime-benchmarks", - "pallet-collective/runtime-benchmarks", "pallet-conviction-voting/runtime-benchmarks", "pallet-democracy/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", "pallet-grandpa/runtime-benchmarks", "pallet-identity/runtime-benchmarks", "pallet-indices/runtime-benchmarks", - "pallet-membership/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "pallet-migrations/runtime-benchmarks", "pallet-mmr/runtime-benchmarks", @@ -275,6 +269,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-executive/try-runtime", @@ -291,14 +286,12 @@ try-runtime = [ "pallet-beefy/try-runtime", "pallet-bounties/try-runtime", "pallet-child-bounties/try-runtime", - "pallet-collective/try-runtime", "pallet-conviction-voting/try-runtime", "pallet-democracy/try-runtime", "pallet-elections-phragmen/try-runtime", "pallet-grandpa/try-runtime", "pallet-identity/try-runtime", "pallet-indices/try-runtime", - "pallet-membership/try-runtime", "pallet-message-queue/try-runtime", "pallet-migrations/try-runtime", "pallet-mmr/try-runtime", diff --git a/polkadot/runtime/rococo/constants/Cargo.toml b/polkadot/runtime/rococo/constants/Cargo.toml index 1d0adac44af4dcbb099182ebfbc1a4a1333f8422..cc62d230d2c012457efa3b538a41d71243c7aede 100644 --- a/polkadot/runtime/rococo/constants/Cargo.toml +++ b/polkadot/runtime/rococo/constants/Cargo.toml @@ -5,6 +5,8 @@ description = "Constants used throughout the Rococo network." authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [package.metadata.polkadot-sdk] exclude-from-umbrella = true @@ -18,9 +20,9 @@ smallvec = { workspace = true, default-features = true } frame-support = { workspace = true } polkadot-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } +sp-core = { workspace = true } sp-runtime = { workspace = true } sp-weights = { workspace = true } -sp-core = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } diff --git a/polkadot/runtime/rococo/src/genesis_config_presets.rs b/polkadot/runtime/rococo/src/genesis_config_presets.rs index bdbf6f37d92c221df09fc8d7c98f333089e68a6f..80be075bea2566d95de7ee7e97f90bbb788ab9d7 100644 --- a/polkadot/runtime/rococo/src/genesis_config_presets.rs +++ b/polkadot/runtime/rococo/src/genesis_config_presets.rs @@ -125,14 +125,16 @@ fn default_parachains_host_configuration( zeroth_delay_tranche_width: 0, minimum_validation_upgrade_delay: 5, async_backing_params: AsyncBackingParams { - max_candidate_depth: 3, - allowed_ancestry_len: 2, + max_candidate_depth: 0, + allowed_ancestry_len: 0, }, node_features: bitvec::vec::BitVec::from_element( - 1u8 << (FeatureIndex::ElasticScalingMVP as usize), + 1u8 << (FeatureIndex::ElasticScalingMVP as usize) | + 1u8 << (FeatureIndex::EnableAssignmentsV2 as usize) | + 1u8 << (FeatureIndex::CandidateReceiptV2 as usize), ), scheduler_params: SchedulerParams { - lookahead: 2, + lookahead: 3, group_rotation_frequency: 20, paras_availability_period: 4, ..Default::default() diff --git a/polkadot/runtime/rococo/src/impls.rs b/polkadot/runtime/rococo/src/impls.rs index ab796edc54b1a3a5b8e12549f2b047e034846322..a5cb2eddfa0d7d23c649cab899680d0d9fb15339 100644 --- a/polkadot/runtime/rococo/src/impls.rs +++ b/polkadot/runtime/rococo/src/impls.rs @@ -21,7 +21,7 @@ use core::marker::PhantomData; use frame_support::pallet_prelude::DispatchResult; use frame_system::RawOrigin; use polkadot_primitives::Balance; -use polkadot_runtime_common::identity_migrator::OnReapIdentity; +use polkadot_runtime_common::identity_migrator::{OnReapIdentity, WeightInfo}; use rococo_runtime_constants::currency::*; use xcm::{latest::prelude::*, VersionedLocation, VersionedXcm}; use xcm_executor::traits::TransactAsset; @@ -88,7 +88,10 @@ where AccountId: Into<[u8; 32]> + Clone + Encode, { fn on_reap_identity(who: &AccountId, fields: u32, subs: u32) -> DispatchResult { - use crate::impls::IdentityMigratorCalls::PokeDeposit; + use crate::{ + impls::IdentityMigratorCalls::PokeDeposit, + weights::polkadot_runtime_common_identity_migrator::WeightInfo as MigratorWeights, + }; let total_to_send = Self::calculate_remote_deposit(fields, subs); @@ -141,6 +144,7 @@ where .into(); let poke = PeopleRuntimePallets::<AccountId>::IdentityMigrator(PokeDeposit(who.clone())); + let remote_weight_limit = MigratorWeights::<Runtime>::poke_deposit().saturating_mul(2); // Actual program to execute on People Chain. let program: Xcm<()> = Xcm(vec![ @@ -157,7 +161,11 @@ where .into(), }, // Poke the deposit to reserve the appropriate amount on the parachain. - Transact { origin_kind: OriginKind::Superuser, call: poke.encode().into() }, + Transact { + origin_kind: OriginKind::Superuser, + fallback_max_weight: Some(remote_weight_limit), + call: poke.encode().into(), + }, ]); // send @@ -168,4 +176,9 @@ where )?; Ok(()) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_identity_reaping(_: &AccountId, _: u32, _: u32) { + crate::Dmp::make_parachain_reachable(1004); + } } diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 5da9da86f02e0f87635f75dfd4006b7835e9f072..f7716e8b7d9cfa92d19d1f738649b358e72e7b20 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -20,6 +20,17 @@ // `construct_runtime!` does a lot of recursion and requires us to increase the limit. #![recursion_limit = "512"] +#[cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), target_feature = "e"))] +// Allocate 2 MiB stack. +// +// TODO: A workaround. Invoke polkavm_derive::min_stack_size!() instead +// later on. +::core::arch::global_asm!( + ".pushsection .polkavm_min_stack_size,\"R\",@note\n", + ".4byte 2097152", + ".popsection\n", +); + extern crate alloc; use alloc::{ @@ -38,8 +49,8 @@ use pallet_nis::WithMaximumOf; use polkadot_primitives::{ slashing, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, AccountId, AccountIndex, ApprovalVotingParams, Balance, BlockNumber, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, @@ -67,7 +78,9 @@ use polkadot_runtime_parachains::{ initializer as parachains_initializer, on_demand as parachains_on_demand, origin as parachains_origin, paras as parachains_paras, paras_inherent as parachains_paras_inherent, - runtime_api_impl::v11 as parachains_runtime_api_impl, + runtime_api_impl::{ + v11 as parachains_runtime_api_impl, vstaging as parachains_staging_runtime_api_impl, + }, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -171,7 +184,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("rococo"), impl_name: alloc::borrow::Cow::Borrowed("parity-rococo-v2.0"), authoring_version: 0, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 26, @@ -663,6 +676,7 @@ where frame_system::CheckWeight::<Runtime>::new(), pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip), frame_metadata_hash_extension::CheckMetadataHash::new(true), + frame_system::WeightReclaim::<Runtime>::new(), ) .into(); let raw_payload = SignedPayload::new(call, tx_ext) @@ -781,6 +795,7 @@ impl pallet_recovery::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type RuntimeCall = RuntimeCall; + type BlockNumberProvider = System; type Currency = Balances; type ConfigDepositBase = ConfigDepositBase; type FriendDepositFactor = FriendDepositFactor; @@ -1100,6 +1115,7 @@ impl parachains_scheduler::Config for Runtime { parameter_types! { pub const BrokerId: u32 = BROKER_ID; pub const BrokerPalletId: PalletId = PalletId(*b"py/broke"); + pub MaxXcmTransactWeight: Weight = Weight::from_parts(200_000_000, 20_000); } pub struct BrokerPot; @@ -1123,6 +1139,7 @@ impl coretime::Config for Runtime { xcm_config::ThisNetwork, <Runtime as frame_system::Config>::AccountId, >; + type MaxXcmTransactWeight = MaxXcmTransactWeight; } parameter_types! { @@ -1603,6 +1620,7 @@ pub type TxExtension = ( frame_system::CheckWeight<Runtime>, pallet_transaction_payment::ChargeTransactionPayment<Runtime>, frame_metadata_hash_extension::CheckMetadataHash<Runtime>, + frame_system::WeightReclaim<Runtime>, ); /// Unchecked extrinsic type as expected by this runtime. @@ -1806,7 +1824,7 @@ mod benches { [polkadot_runtime_parachains::initializer, Initializer] [polkadot_runtime_parachains::paras_inherent, ParaInherent] [polkadot_runtime_parachains::paras, Paras] - [polkadot_runtime_parachains::assigner_on_demand, OnDemandAssignmentProvider] + [polkadot_runtime_parachains::on_demand, OnDemandAssignmentProvider] // Substrate [pallet_balances, Balances] [pallet_balances, NisCounterpartBalances] @@ -1869,7 +1887,8 @@ sp_api::impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> { - match asset.try_as::<AssetId>() { + let latest_asset_id: Result<AssetId, ()> = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::TokenLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) @@ -1967,7 +1986,7 @@ sp_api::impl_runtime_apis! { } } - #[api_version(11)] + #[api_version(12)] impl polkadot_primitives::runtime_api::ParachainHost<Block> for Runtime { fn validators() -> Vec<ValidatorId> { parachains_runtime_api_impl::validators::<Runtime>() @@ -2105,10 +2124,12 @@ sp_api::impl_runtime_apis! { } fn para_backing_state(para_id: ParaId) -> Option<polkadot_primitives::vstaging::async_backing::BackingState> { + #[allow(deprecated)] parachains_runtime_api_impl::backing_state::<Runtime>(para_id) } fn async_backing_params() -> polkadot_primitives::AsyncBackingParams { + #[allow(deprecated)] parachains_runtime_api_impl::async_backing_params::<Runtime>() } @@ -2131,6 +2152,14 @@ sp_api::impl_runtime_apis! { fn candidates_pending_availability(para_id: ParaId) -> Vec<CommittedCandidateReceipt<Hash>> { parachains_runtime_api_impl::candidates_pending_availability::<Runtime>(para_id) } + + fn backing_constraints(para_id: ParaId) -> Option<Constraints> { + parachains_staging_runtime_api_impl::backing_constraints::<Runtime>(para_id) + } + + fn scheduling_lookahead() -> u32 { + parachains_staging_runtime_api_impl::scheduling_lookahead::<Runtime>() + } } #[api_version(5)] @@ -2260,7 +2289,7 @@ sp_api::impl_runtime_apis! { } fn current_set_id() -> fg_primitives::SetId { - Grandpa::current_set_id() + pallet_grandpa::CurrentSetId::<Runtime>::get() } fn submit_report_equivocation_unsigned_extrinsic( @@ -2469,14 +2498,14 @@ sp_api::impl_runtime_apis! { ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, AssetHubParaId, - (), + Dmp, >, polkadot_runtime_common::xcm_sender::ToParachainDeliveryHelper< XcmConfig, ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, RandomParaId, - (), + Dmp, > ); @@ -2535,7 +2564,7 @@ sp_api::impl_runtime_apis! { ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, AssetHubParaId, - (), + Dmp, >; fn valid_destination() -> Result<Location, BenchmarkError> { Ok(AssetHub::get()) diff --git a/polkadot/runtime/rococo/src/tests.rs b/polkadot/runtime/rococo/src/tests.rs index 01eaad87e342479bec6341944ebc911ab3c73cdd..0b46caec5a35b9e4d4b56cfba8dcf48e542bcc63 100644 --- a/polkadot/runtime/rococo/src/tests.rs +++ b/polkadot/runtime/rococo/src/tests.rs @@ -22,7 +22,7 @@ use std::collections::HashSet; use crate::xcm_config::LocationConverter; use frame_support::traits::WhitelistedStorageKeys; use sp_core::{crypto::Ss58Codec, hexdisplay::HexDisplay}; -use sp_keyring::AccountKeyring::Alice; +use sp_keyring::Sr25519Keyring::Alice; use xcm_runtime_apis::conversions::LocationToAccountHelper; #[test] diff --git a/polkadot/runtime/rococo/src/weights/frame_system_extensions.rs b/polkadot/runtime/rococo/src/weights/frame_system_extensions.rs index 99dac1ba75f06f8d8e5e9b835a48f3b5f2974d90..88596a37cc01bbb00d630e84d761e35419b51369 100644 --- a/polkadot/runtime/rococo/src/weights/frame_system_extensions.rs +++ b/polkadot/runtime/rococo/src/weights/frame_system_extensions.rs @@ -17,25 +17,23 @@ //! Autogenerated weights for `frame_system_extensions` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet -// --chain=rococo-dev // --steps=50 // --repeat=20 -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --pallet=frame_system_extensions // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=frame_system_extensions +// --chain=rococo-dev // --header=./polkadot/file_header.txt // --output=./polkadot/runtime/rococo/src/weights/ @@ -50,45 +48,36 @@ use core::marker::PhantomData; /// Weight functions for `frame_system_extensions`. pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo<T> { - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_genesis() -> Weight { // Proof Size summary in bytes: - // Measured: `54` - // Estimated: `3509` - // Minimum execution time: 3_262_000 picoseconds. - Weight::from_parts(3_497_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `30` + // Estimated: `0` + // Minimum execution time: 3_528_000 picoseconds. + Weight::from_parts(3_657_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_mortal_transaction() -> Weight { // Proof Size summary in bytes: - // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 5_416_000 picoseconds. - Weight::from_parts(5_690_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `68` + // Estimated: `0` + // Minimum execution time: 6_456_000 picoseconds. + Weight::from_parts(6_706_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_immortal_transaction() -> Weight { // Proof Size summary in bytes: - // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 5_416_000 picoseconds. - Weight::from_parts(5_690_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `68` + // Estimated: `0` + // Minimum execution time: 6_210_000 picoseconds. + Weight::from_parts(6_581_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } fn check_non_zero_sender() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 471_000 picoseconds. - Weight::from_parts(552_000, 0) + // Minimum execution time: 529_000 picoseconds. + Weight::from_parts(561_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `System::Account` (r:1 w:1) @@ -97,8 +86,8 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 4_847_000 picoseconds. - Weight::from_parts(5_091_000, 0) + // Minimum execution time: 6_935_000 picoseconds. + Weight::from_parts(7_264_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -107,28 +96,32 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 388_000 picoseconds. - Weight::from_parts(421_000, 0) + // Minimum execution time: 452_000 picoseconds. + Weight::from_parts(474_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn check_tx_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 378_000 picoseconds. - Weight::from_parts(440_000, 0) + // Minimum execution time: 422_000 picoseconds. + Weight::from_parts(460_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) - /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn check_weight() -> Weight { // Proof Size summary in bytes: - // Measured: `24` - // Estimated: `1489` - // Minimum execution time: 3_402_000 picoseconds. - Weight::from_parts(3_627_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_632_000 picoseconds. + Weight::from_parts(3_784_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_209_000 picoseconds. + Weight::from_parts(2_335_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } } diff --git a/polkadot/runtime/rococo/src/weights/pallet_beefy_mmr.rs b/polkadot/runtime/rococo/src/weights/pallet_beefy_mmr.rs index 317c9149ec6c57c8e1c9d5bc5deb5ee648175ada..54989c4f549c55a16abd14139334a2f2a513a7e6 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_beefy_mmr.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_beefy_mmr.rs @@ -17,9 +17,9 @@ //! 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: `[]` +//! DATE: 2024-12-02, 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` +//! HOSTNAME: `runner-wiukf8gn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -48,14 +48,25 @@ use core::marker::PhantomData; /// Weight functions for `pallet_beefy_mmr`. pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> pallet_beefy_mmr::WeightInfo for WeightInfo<T> { + /// The range of component `n` is `[2, 512]`. + fn n_leafs_proof_is_optimal(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 622_000 picoseconds. + Weight::from_parts(1_166_954, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 65 + .saturating_add(Weight::from_parts(1_356, 0).saturating_mul(n.into())) + } /// 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` + // Measured: `68` // Estimated: `3509` - // Minimum execution time: 7_116_000 picoseconds. - Weight::from_parts(7_343_000, 0) + // Minimum execution time: 6_272_000 picoseconds. + Weight::from_parts(6_452_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -63,10 +74,10 @@ impl<T: frame_system::Config> pallet_beefy_mmr::WeightInfo for WeightInfo<T> { /// 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` + // Measured: `254` // Estimated: `3505` - // Minimum execution time: 5_652_000 picoseconds. - Weight::from_parts(5_963_000, 0) + // Minimum execution time: 6_576_000 picoseconds. + Weight::from_parts(6_760_000, 0) .saturating_add(Weight::from_parts(0, 3505)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -77,13 +88,13 @@ impl<T: frame_system::Config> pallet_beefy_mmr::WeightInfo for WeightInfo<T> { /// 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` + // Measured: `246` // Estimated: `1517` - // Minimum execution time: 11_953_000 picoseconds. - Weight::from_parts(15_978_891, 0) + // Minimum execution time: 12_538_000 picoseconds. + Weight::from_parts(24_516_023, 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())) + // Standard Error: 1_923 + .saturating_add(Weight::from_parts(1_426_781, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(2)) } } diff --git a/polkadot/runtime/rococo/src/weights/pallet_migrations.rs b/polkadot/runtime/rococo/src/weights/pallet_migrations.rs index 4fa07a23bb8ab4376d64cb4aa425f1cc515bb4fa..a0623a9c951331a54c036b9a3b2e1fee42df8d70 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_migrations.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_migrations.rs @@ -14,7 +14,31 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see <http://www.gnu.org/licenses/>. -// Need to rerun! +//! Autogenerated weights for `pallet_migrations` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `17938671047b`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 + +// Executed Command: +// frame-omni-bencher +// v1 +// benchmark +// pallet +// --extrinsic=* +// --runtime=target/production/wbuild/rococo-runtime/rococo_runtime.wasm +// --pallet=pallet_migrations +// --header=/__w/polkadot-sdk/polkadot-sdk/polkadot/file_header.txt +// --output=./polkadot/runtime/rococo/src/weights +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -33,22 +57,24 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) fn onboard_new_mbms() -> Weight { // Proof Size summary in bytes: - // Measured: `276` + // Measured: `100` // Estimated: `67035` - // Minimum execution time: 7_762_000 picoseconds. - Weight::from_parts(8_100_000, 67035) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Minimum execution time: 8_300_000 picoseconds. + Weight::from_parts(8_664_000, 0) + .saturating_add(Weight::from_parts(0, 67035)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `MultiBlockMigrations::Cursor` (r:1 w:0) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn progress_mbms_none() -> Weight { // Proof Size summary in bytes: - // Measured: `142` + // Measured: `4` // Estimated: `67035` - // Minimum execution time: 2_077_000 picoseconds. - Weight::from_parts(2_138_000, 67035) - .saturating_add(T::DbWeight::get().reads(1_u64)) + // Minimum execution time: 2_017_000 picoseconds. + Weight::from_parts(2_129_000, 0) + .saturating_add(Weight::from_parts(0, 67035)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -56,12 +82,13 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn exec_migration_completed() -> Weight { // Proof Size summary in bytes: - // Measured: `134` - // Estimated: `3599` - // Minimum execution time: 5_868_000 picoseconds. - Weight::from_parts(6_143_000, 3599) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Measured: `96` + // Estimated: `3561` + // Minimum execution time: 6_414_000 picoseconds. + Weight::from_parts(6_644_000, 0) + .saturating_add(Weight::from_parts(0, 3561)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -69,11 +96,12 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_skipped_historic() -> Weight { // Proof Size summary in bytes: - // Measured: `330` - // Estimated: `3795` - // Minimum execution time: 10_283_000 picoseconds. - Weight::from_parts(10_964_000, 3795) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Measured: `154` + // Estimated: `3731` + // Minimum execution time: 11_600_000 picoseconds. + Weight::from_parts(12_137_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -81,11 +109,12 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_advance() -> Weight { // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `3741` - // Minimum execution time: 9_900_000 picoseconds. - Weight::from_parts(10_396_000, 3741) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Measured: `100` + // Estimated: `3731` + // Minimum execution time: 10_944_000 picoseconds. + Weight::from_parts(11_354_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -93,12 +122,13 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_complete() -> Weight { // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `3741` - // Minimum execution time: 11_411_000 picoseconds. - Weight::from_parts(11_956_000, 3741) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Measured: `100` + // Estimated: `3731` + // Minimum execution time: 12_525_000 picoseconds. + Weight::from_parts(12_890_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -108,19 +138,21 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn exec_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `3741` - // Minimum execution time: 12_398_000 picoseconds. - Weight::from_parts(12_910_000, 3741) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Measured: `100` + // Estimated: `3731` + // Minimum execution time: 13_749_000 picoseconds. + Weight::from_parts(14_411_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } fn on_init_loop() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 166_000 picoseconds. - Weight::from_parts(193_000, 0) + // Minimum execution time: 174_000 picoseconds. + Weight::from_parts(232_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) @@ -128,9 +160,10 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_686_000 picoseconds. - Weight::from_parts(2_859_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Minimum execution time: 2_906_000 picoseconds. + Weight::from_parts(3_195_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) @@ -138,9 +171,10 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_070_000 picoseconds. - Weight::from_parts(3_250_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Minimum execution time: 3_313_000 picoseconds. + Weight::from_parts(3_469_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `MultiBlockMigrations::Cursor` (r:1 w:0) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) @@ -148,26 +182,44 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) fn force_onboard_mbms() -> Weight { // Proof Size summary in bytes: - // Measured: `251` + // Measured: `76` // Estimated: `67035` - // Minimum execution time: 5_901_000 picoseconds. - Weight::from_parts(6_320_000, 67035) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Minimum execution time: 5_960_000 picoseconds. + Weight::from_parts(6_262_000, 0) + .saturating_add(Weight::from_parts(0, 67035)) + .saturating_add(T::DbWeight::get().reads(2)) } /// Storage: `MultiBlockMigrations::Historic` (r:256 w:256) /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 256]`. fn clear_historic(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1122 + n * (271 ±0)` + // Measured: `984 + n * (271 ±0)` // Estimated: `3834 + n * (2740 ±0)` - // Minimum execution time: 15_952_000 picoseconds. - Weight::from_parts(14_358_665, 3834) - // Standard Error: 3_358 - .saturating_add(Weight::from_parts(1_323_674, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(1_u64)) + // Minimum execution time: 24_007_000 picoseconds. + Weight::from_parts(19_756_256, 0) + .saturating_add(Weight::from_parts(0, 3834)) + // Standard Error: 6_508 + .saturating_add(Weight::from_parts(1_553_207, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2740).saturating_mul(n.into())) } -} \ No newline at end of file + /// 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, 2048]`. + fn reset_pallet_migration(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1676 + n * (38 ±0)` + // Estimated: `754 + n * (39 ±0)` + // Minimum execution time: 2_019_000 picoseconds. + Weight::from_parts(6_578_665, 0) + .saturating_add(Weight::from_parts(0, 754)) + // Standard Error: 5_209 + .saturating_add(Weight::from_parts(894_607, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 39).saturating_mul(n.into())) + } +} diff --git a/polkadot/runtime/rococo/src/weights/pallet_multisig.rs b/polkadot/runtime/rococo/src/weights/pallet_multisig.rs index f1b81759ece6c4c360a4bfb61afafb76836d11b2..d63c82daacdef2cbc197d93fcb9762347308b6e6 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_multisig.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_multisig.rs @@ -17,27 +17,27 @@ //! Autogenerated weights for `pallet_multisig` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet +// --extrinsic=* // --chain=rococo-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/polkadot/file_header.txt +// --output=./polkadot/runtime/rococo/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 +// --heap-pages=4096 // --no-storage-info -// --no-median-slopes // --no-min-squares -// --pallet=pallet_multisig -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./polkadot/file_header.txt -// --output=./polkadot/runtime/rococo/src/weights/ +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_023_000 picoseconds. - Weight::from_parts(12_643_116, 0) + // Minimum execution time: 15_707_000 picoseconds. + Weight::from_parts(17_199_004, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 3 - .saturating_add(Weight::from_parts(582, 0).saturating_mul(z.into())) + // Standard Error: 15 + .saturating_add(Weight::from_parts(639, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -69,13 +69,13 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `229 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 39_339_000 picoseconds. - Weight::from_parts(27_243_033, 0) + // Minimum execution time: 47_949_000 picoseconds. + Weight::from_parts(33_500_294, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_319 - .saturating_add(Weight::from_parts(142_212, 0).saturating_mul(s.into())) - // Standard Error: 12 - .saturating_add(Weight::from_parts(1_592, 0).saturating_mul(z.into())) + // Standard Error: 1_775 + .saturating_add(Weight::from_parts(159_011, 0).saturating_mul(s.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(2_213, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -87,13 +87,13 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `248` // Estimated: `6811` - // Minimum execution time: 27_647_000 picoseconds. - Weight::from_parts(15_828_725, 0) + // Minimum execution time: 31_197_000 picoseconds. + Weight::from_parts(19_488_352, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 908 - .saturating_add(Weight::from_parts(130_880, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_532, 0).saturating_mul(z.into())) + // Standard Error: 1_332 + .saturating_add(Weight::from_parts(138_347, 0).saturating_mul(s.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(2_122, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -107,28 +107,29 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `354 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 46_971_000 picoseconds. - Weight::from_parts(32_150_393, 0) + // Minimum execution time: 54_297_000 picoseconds. + Weight::from_parts(33_256_178, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_129 - .saturating_add(Weight::from_parts(154_796, 0).saturating_mul(s.into())) - // Standard Error: 11 - .saturating_add(Weight::from_parts(1_603, 0).saturating_mul(z.into())) + // Standard Error: 3_088 + .saturating_add(Weight::from_parts(256_364, 0).saturating_mul(s.into())) + // Standard Error: 30 + .saturating_add(Weight::from_parts(2_488, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `229 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 24_947_000 picoseconds. - Weight::from_parts(26_497_183, 0) + // Minimum execution time: 31_246_000 picoseconds. + Weight::from_parts(32_245_711, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_615 - .saturating_add(Weight::from_parts(147_071, 0).saturating_mul(s.into())) + // Standard Error: 1_704 + .saturating_add(Weight::from_parts(156_235, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -139,11 +140,11 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `248` // Estimated: `6811` - // Minimum execution time: 13_897_000 picoseconds. - Weight::from_parts(14_828_339, 0) + // Minimum execution time: 17_353_000 picoseconds. + Weight::from_parts(17_418_506, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_136 - .saturating_add(Weight::from_parts(133_925, 0).saturating_mul(s.into())) + // Standard Error: 1_126 + .saturating_add(Weight::from_parts(136_788, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -154,11 +155,11 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `420 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 28_984_000 picoseconds. - Weight::from_parts(29_853_232, 0) + // Minimum execution time: 32_603_000 picoseconds. + Weight::from_parts(33_456_399, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 650 - .saturating_add(Weight::from_parts(113_440, 0).saturating_mul(s.into())) + // Standard Error: 1_239 + .saturating_add(Weight::from_parts(146_249, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/polkadot/runtime/rococo/src/weights/pallet_xcm.rs b/polkadot/runtime/rococo/src/weights/pallet_xcm.rs index d5cf33515e6be23e7cde0d892b97c8c1d07d8c2f..b60165934f9217601829edae27428d4f81dda8a7 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_xcm.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_xcm.rs @@ -17,27 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `65a7f4d3191f`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet +// --extrinsic=* // --chain=rococo-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/polkadot/file_header.txt +// --output=./polkadot/runtime/rococo/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 +// --heap-pages=4096 // --no-storage-info -// --no-median-slopes // --no-min-squares -// --pallet=pallet_xcm -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./polkadot/file_header.txt -// --output=./polkadot/runtime/rococo/src/weights/ +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,38 +56,46 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// 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: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `180` - // Estimated: `3645` - // Minimum execution time: 25_521_000 picoseconds. - Weight::from_parts(25_922_000, 0) - .saturating_add(Weight::from_parts(0, 3645)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `245` + // Estimated: `3710` + // Minimum execution time: 37_787_000 picoseconds. + Weight::from_parts(39_345_000, 0) + .saturating_add(Weight::from_parts(0, 3710)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `180` - // Estimated: `3645` - // Minimum execution time: 112_185_000 picoseconds. - Weight::from_parts(115_991_000, 0) - .saturating_add(Weight::from_parts(0, 3645)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `245` + // Estimated: `3710` + // Minimum execution time: 138_755_000 picoseconds. + Weight::from_parts(142_908_000, 0) + .saturating_add(Weight::from_parts(0, 3710)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`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: `Dmp::DeliveryFeeFactor` (r:1 w:0) @@ -96,45 +104,54 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// 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: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `232` - // Estimated: `3697` - // Minimum execution time: 108_693_000 picoseconds. - Weight::from_parts(111_853_000, 0) - .saturating_add(Weight::from_parts(0, 3697)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `297` + // Estimated: `3762` + // Minimum execution time: 134_917_000 picoseconds. + Weight::from_parts(138_809_000, 0) + .saturating_add(Weight::from_parts(0, 3762)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `180` - // Estimated: `3645` - // Minimum execution time: 113_040_000 picoseconds. - Weight::from_parts(115_635_000, 0) - .saturating_add(Weight::from_parts(0, 3645)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `245` + // Estimated: `3710` + // Minimum execution time: 141_303_000 picoseconds. + Weight::from_parts(144_640_000, 0) + .saturating_add(Weight::from_parts(0, 3710)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: // Measured: `0` - // Estimated: `0` - // Minimum execution time: 6_979_000 picoseconds. - Weight::from_parts(7_342_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Estimated: `1485` + // Minimum execution time: 9_872_000 picoseconds. + Weight::from_parts(10_402_000, 0) + .saturating_add(Weight::from_parts(0, 1485)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `XcmPallet::SupportedVersion` (r:0 w:1) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -142,8 +159,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_144_000 picoseconds. - Weight::from_parts(7_297_000, 0) + // Minimum execution time: 8_312_000 picoseconds. + Weight::from_parts(8_867_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -151,8 +168,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_886_000 picoseconds. - Weight::from_parts(1_995_000, 0) + // Minimum execution time: 2_524_000 picoseconds. + Weight::from_parts(2_800_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `XcmPallet::VersionNotifiers` (r:1 w:1) @@ -165,18 +182,20 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// 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: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::Queries` (r:0 w:1) /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_subscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `180` - // Estimated: `3645` - // Minimum execution time: 31_238_000 picoseconds. - Weight::from_parts(31_955_000, 0) - .saturating_add(Weight::from_parts(0, 3645)) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `245` + // Estimated: `3710` + // Minimum execution time: 45_426_000 picoseconds. + Weight::from_parts(48_021_000, 0) + .saturating_add(Weight::from_parts(0, 3710)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `XcmPallet::VersionNotifiers` (r:1 w:1) @@ -187,18 +206,20 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// 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: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::Queries` (r:0 w:1) /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_unsubscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `360` - // Estimated: `3825` - // Minimum execution time: 37_237_000 picoseconds. - Weight::from_parts(38_569_000, 0) - .saturating_add(Weight::from_parts(0, 3825)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `425` + // Estimated: `3890` + // Minimum execution time: 50_854_000 picoseconds. + Weight::from_parts(52_044_000, 0) + .saturating_add(Weight::from_parts(0, 3890)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `XcmPallet::XcmExecutionSuspended` (r:0 w:1) @@ -207,45 +228,45 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_884_000 picoseconds. - Weight::from_parts(2_028_000, 0) + // Minimum execution time: 2_566_000 picoseconds. + Weight::from_parts(2_771_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `XcmPallet::SupportedVersion` (r:5 w:2) + /// Storage: `XcmPallet::SupportedVersion` (r:6 w:2) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `22` - // Estimated: `13387` - // Minimum execution time: 16_048_000 picoseconds. - Weight::from_parts(16_617_000, 0) - .saturating_add(Weight::from_parts(0, 13387)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15862` + // Minimum execution time: 21_854_000 picoseconds. + Weight::from_parts(22_528_000, 0) + .saturating_add(Weight::from_parts(0, 15862)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `XcmPallet::VersionNotifiers` (r:5 w:2) + /// Storage: `XcmPallet::VersionNotifiers` (r:6 w:2) /// Proof: `XcmPallet::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `26` - // Estimated: `13391` - // Minimum execution time: 16_073_000 picoseconds. - Weight::from_parts(16_672_000, 0) - .saturating_add(Weight::from_parts(0, 13391)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15866` + // Minimum execution time: 21_821_000 picoseconds. + Weight::from_parts(22_368_000, 0) + .saturating_add(Weight::from_parts(0, 15866)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `XcmPallet::VersionNotifyTargets` (r:6 w:0) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:7 w:0) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `40` - // Estimated: `15880` - // Minimum execution time: 18_422_000 picoseconds. - Weight::from_parts(18_900_000, 0) - .saturating_add(Weight::from_parts(0, 15880)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18355` + // Minimum execution time: 25_795_000 picoseconds. + Weight::from_parts(26_284_000, 0) + .saturating_add(Weight::from_parts(0, 18355)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `XcmPallet::VersionNotifyTargets` (r:2 w:1) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -253,62 +274,62 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// 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) + /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:0) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) - /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_current_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `216` - // Estimated: `6156` - // Minimum execution time: 30_373_000 picoseconds. - Weight::from_parts(30_972_000, 0) - .saturating_add(Weight::from_parts(0, 6156)) + // Measured: `244` + // Estimated: `6184` + // Minimum execution time: 33_182_000 picoseconds. + Weight::from_parts(34_506_000, 0) + .saturating_add(Weight::from_parts(0, 6184)) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `XcmPallet::VersionNotifyTargets` (r:4 w:0) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:5 w:0) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `69` - // Estimated: `10959` - // Minimum execution time: 11_863_000 picoseconds. - Weight::from_parts(12_270_000, 0) - .saturating_add(Weight::from_parts(0, 10959)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `40` + // Estimated: `13405` + // Minimum execution time: 17_573_000 picoseconds. + Weight::from_parts(18_154_000, 0) + .saturating_add(Weight::from_parts(0, 13405)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `XcmPallet::VersionNotifyTargets` (r:5 w:2) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:6 w:2) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `33` - // Estimated: `13398` - // Minimum execution time: 16_733_000 picoseconds. - Weight::from_parts(17_094_000, 0) - .saturating_add(Weight::from_parts(0, 13398)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15873` + // Minimum execution time: 22_491_000 picoseconds. + Weight::from_parts(22_793_000, 0) + .saturating_add(Weight::from_parts(0, 15873)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `XcmPallet::VersionNotifyTargets` (r:5 w:2) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:6 w:1) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) + /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:0) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) - /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `216` - // Estimated: `13581` - // Minimum execution time: 39_236_000 picoseconds. - Weight::from_parts(40_587_000, 0) - .saturating_add(Weight::from_parts(0, 13581)) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `244` + // Estimated: `16084` + // Minimum execution time: 44_441_000 picoseconds. + Weight::from_parts(45_782_000, 0) + .saturating_add(Weight::from_parts(0, 16084)) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `XcmPallet::QueryCounter` (r:1 w:1) /// Proof: `XcmPallet::QueryCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -318,8 +339,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `1485` - // Minimum execution time: 2_145_000 picoseconds. - Weight::from_parts(2_255_000, 0) + // Minimum execution time: 2_809_000 picoseconds. + Weight::from_parts(2_960_000, 0) .saturating_add(Weight::from_parts(0, 1485)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -330,22 +351,24 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `7576` // Estimated: `11041` - // Minimum execution time: 22_518_000 picoseconds. - Weight::from_parts(22_926_000, 0) + // Minimum execution time: 26_248_000 picoseconds. + Weight::from_parts(26_996_000, 0) .saturating_add(Weight::from_parts(0, 11041)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::AssetTraps` (r:1 w:1) /// Proof: `XcmPallet::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `23` // Estimated: `3488` - // Minimum execution time: 34_438_000 picoseconds. - Weight::from_parts(35_514_000, 0) + // Minimum execution time: 40_299_000 picoseconds. + Weight::from_parts(41_396_000, 0) .saturating_add(Weight::from_parts(0, 3488)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/polkadot/runtime/rococo/src/weights/xcm/mod.rs b/polkadot/runtime/rococo/src/weights/xcm/mod.rs index a28b46800874280a8c89cbdeacc13e7171a03473..eb27e5c5a8979c9e8996d03e8ecb466d104182be 100644 --- a/polkadot/runtime/rococo/src/weights/xcm/mod.rs +++ b/polkadot/runtime/rococo/src/weights/xcm/mod.rs @@ -24,6 +24,7 @@ use xcm::{latest::prelude::*, DoubleEncoded}; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmBalancesWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::latest::AssetTransferFilter; /// Types of asset supported by the Rococo runtime. @@ -111,7 +112,11 @@ impl<RuntimeCall> XcmWeightInfo<RuntimeCall> for RococoXcmWeight<RuntimeCall> { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmBalancesWeight::<Runtime>::transfer_reserve_asset()) } - fn transact(_origin_kind: &OriginKind, _call: &DoubleEncoded<RuntimeCall>) -> Weight { + fn transact( + _origin_kind: &OriginKind, + _fallback_max_weight: &Option<Weight>, + _call: &DoubleEncoded<RuntimeCall>, + ) -> Weight { XcmGeneric::<Runtime>::transact() } fn hrmp_new_channel_open_request( @@ -286,8 +291,16 @@ impl<RuntimeCall> XcmWeightInfo<RuntimeCall> for RococoXcmWeight<RuntimeCall> { fn unpaid_execution(_: &WeightLimit, _: &Option<Location>) -> Weight { XcmGeneric::<Runtime>::unpaid_execution() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::<Runtime>::set_asset_claimer() + fn set_hints(hints: &BoundedVec<Hint, HintNumVariants>) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::<Runtime>::asset_claimer()); + }, + } + } + weight } fn execute_with_origin(_: &Option<InteriorLocation>, _: &Xcm<RuntimeCall>) -> Weight { XcmGeneric::<Runtime>::execute_with_origin() diff --git a/polkadot/runtime/rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/polkadot/runtime/rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index e5915a7986bf995ee8aeb72c0d8b71e33007a509..2dc8880c83265fd52cdb63ca01f50d292048dcb4 100644 --- a/polkadot/runtime/rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/polkadot/runtime/rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -82,7 +82,7 @@ impl<T: frame_system::Config> WeightInfo<T> { // Minimum execution time: 2_899_000 picoseconds. Weight::from_parts(3_090_000, 0) } - pub(crate) fn set_asset_claimer() -> Weight { + pub(crate) fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/polkadot/runtime/rococo/src/xcm_config.rs b/polkadot/runtime/rococo/src/xcm_config.rs index 82a3136cc0d921d8c7246be23feba86cbe0b02e0..10c3f6c0cbfcf2a25efc02524e46f6c4d256674d 100644 --- a/polkadot/runtime/rococo/src/xcm_config.rs +++ b/polkadot/runtime/rococo/src/xcm_config.rs @@ -18,7 +18,8 @@ use super::{ parachains_origin, AccountId, AllPalletsWithSystem, Balances, Dmp, Fellows, ParaId, Runtime, - RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, Treasury, WeightToFee, XcmPallet, + RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, Treasurer, Treasury, WeightToFee, + XcmPallet, }; use crate::governance::StakingAdmin; @@ -84,7 +85,7 @@ pub type LocalAssetTransactor = FungibleAdapter< LocalCheckAccount, >; -/// The means that we convert an the XCM message origin location into a local dispatch origin. +/// The means that we convert the XCM message origin location into a local dispatch origin. type LocalOriginConverter = ( // A `Signed` origin of the sovereign account that the original location controls. SovereignSignedViaLocation<LocationConverter, RuntimeOrigin>, @@ -228,11 +229,14 @@ impl xcm_executor::Config for XcmConfig { } parameter_types! { + /// Collective pluralistic body. pub const CollectiveBodyId: BodyId = BodyId::Unit; - // StakingAdmin pluralistic body. + /// StakingAdmin pluralistic body. pub const StakingAdminBodyId: BodyId = BodyId::Defense; - // Fellows pluralistic body. + /// Fellows pluralistic body. pub const FellowsBodyId: BodyId = BodyId::Technical; + /// Treasury pluralistic body. + pub const TreasuryBodyId: BodyId = BodyId::Treasury; } /// Type to convert an `Origin` type value into a `Location` value which represents an interior @@ -249,6 +253,9 @@ pub type StakingAdminToPlurality = /// Type to convert the Fellows origin to a Plurality `Location` value. pub type FellowsToPlurality = OriginToPluralityVoice<RuntimeOrigin, Fellows, FellowsBodyId>; +/// Type to convert the Treasury origin to a Plurality `Location` value. +pub type TreasurerToPlurality = OriginToPluralityVoice<RuntimeOrigin, Treasurer, TreasuryBodyId>; + /// Type to convert a pallet `Origin` type value into a `Location` value which represents an /// interior location of this chain for a destination chain. pub type LocalPalletOriginToLocation = ( @@ -256,13 +263,18 @@ pub type LocalPalletOriginToLocation = ( StakingAdminToPlurality, // Fellows origin to be used in XCM as a corresponding Plurality `Location` value. FellowsToPlurality, + // Treasurer origin to be used in XCM as a corresponding Plurality `Location` value. + TreasurerToPlurality, ); impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; // Note that this configuration of `SendXcmOrigin` is different from the one present in // production. - type SendXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>; + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin< + RuntimeOrigin, + (LocalPalletOriginToLocation, LocalOriginToLocation), + >; type XcmRouter = XcmRouter; // Anyone can execute XCM messages locally. type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>; diff --git a/polkadot/runtime/test-runtime/Cargo.toml b/polkadot/runtime/test-runtime/Cargo.toml index 90a0285cd17bc82f6de361c1a5cf208219919fb1..cd5507decd5d1a81085d9e84705e4942d04621ba 100644 --- a/polkadot/runtime/test-runtime/Cargo.toml +++ b/polkadot/runtime/test-runtime/Cargo.toml @@ -16,59 +16,58 @@ log = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { workspace = true } +frame-election-provider-support = { workspace = true } +sp-api = { workspace = true } sp-authority-discovery = { workspace = true } +sp-block-builder = { workspace = true } sp-consensus-babe = { workspace = true } sp-consensus-beefy = { workspace = true } -sp-api = { workspace = true } -sp-inherents = { workspace = true } -sp-offchain = { workspace = true } -sp-io = { workspace = true } -sp-runtime = { workspace = true } -sp-staking = { workspace = true } sp-core = { workspace = true } sp-genesis-builder = { workspace = true } +sp-inherents = { workspace = true } +sp-io = { workspace = true } sp-mmr-primitives = { workspace = true } +sp-offchain = { workspace = true } +sp-runtime = { workspace = true } sp-session = { workspace = true } -sp-version = { workspace = true } -frame-election-provider-support = { workspace = true } +sp-staking = { workspace = true } sp-transaction-pool = { workspace = true } -sp-block-builder = { workspace = true } +sp-version = { workspace = true } +frame-executive = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +frame-system-rpc-runtime-api = { workspace = true } pallet-authority-discovery = { workspace = true } pallet-authorship = { workspace = true } pallet-babe = { workspace = true } pallet-balances = { workspace = true } -pallet-transaction-payment = { workspace = true } -pallet-transaction-payment-rpc-runtime-api = { workspace = true } -frame-executive = { workspace = true } pallet-grandpa = { workspace = true } pallet-indices = { workspace = true } pallet-offences = { workspace = true } pallet-session = { workspace = true } -frame-support = { workspace = true } pallet-staking = { workspace = true } pallet-staking-reward-curve = { workspace = true, default-features = true } -frame-system = { workspace = true } -frame-system-rpc-runtime-api = { workspace = true } -test-runtime-constants = { workspace = true } -pallet-timestamp = { workspace = true } pallet-sudo = { workspace = true } +pallet-timestamp = { workspace = true } +pallet-transaction-payment = { workspace = true } +pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-vesting = { workspace = true } +test-runtime-constants = { workspace = true } -polkadot-runtime-common = { workspace = true } -polkadot-primitives = { workspace = true } pallet-xcm = { workspace = true } +polkadot-primitives = { workspace = true } +polkadot-runtime-common = { workspace = true } polkadot-runtime-parachains = { workspace = true } +xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } -xcm = { workspace = true } [dev-dependencies] hex-literal = { workspace = true, default-features = true } -tiny-keccak = { features = ["keccak"], workspace = true } +serde_json = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-trie = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } [build-dependencies] substrate-wasm-builder = { workspace = true, default-features = true } @@ -154,4 +153,5 @@ runtime-benchmarks = [ "sp-staking/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index 69ce187dce409fc366974b39d208d42fe5dfa001..1a19b637b798afbd03ac2cae5694407fdf05edb2 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -32,12 +32,14 @@ use pallet_transaction_payment::FungibleAdapter; use polkadot_runtime_parachains::{ assigner_coretime as parachains_assigner_coretime, configuration as parachains_configuration, - configuration::ActiveConfigHrmpChannelSizeAndCapacityRatio, coretime, - disputes as parachains_disputes, disputes::slashing as parachains_slashing, + configuration::ActiveConfigHrmpChannelSizeAndCapacityRatio, + coretime, disputes as parachains_disputes, + disputes::slashing as parachains_slashing, dmp as parachains_dmp, hrmp as parachains_hrmp, inclusion as parachains_inclusion, initializer as parachains_initializer, on_demand as parachains_on_demand, origin as parachains_origin, paras as parachains_paras, - paras_inherent as parachains_paras_inherent, runtime_api_impl::v11 as runtime_impl, + paras_inherent as parachains_paras_inherent, + runtime_api_impl::{v11 as runtime_impl, vstaging as staging_runtime_impl}, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -61,8 +63,8 @@ use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; use polkadot_primitives::{ slashing, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, AccountId, AccountIndex, Balance, BlockNumber, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash as HashT, Id as ParaId, InboundDownwardMessage, @@ -366,11 +368,13 @@ impl onchain::Config for OnChainSeqPhragmen { const MAX_QUOTA_NOMINATIONS: u32 = 16; impl pallet_staking::Config for Runtime { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = Balance; type UnixTime = Timestamp; type CurrencyToVote = polkadot_runtime_common::CurrencyToVote; type RewardRemainder = (); + type RuntimeHoldReason = RuntimeHoldReason; type RuntimeEvent = RuntimeEvent; type Slash = (); type Reward = (); @@ -443,6 +447,7 @@ where frame_system::CheckNonce::<Runtime>::from(nonce), frame_system::CheckWeight::<Runtime>::new(), pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip), + frame_system::WeightReclaim::<Runtime>::new(), ) .into(); let raw_payload = SignedPayload::new(call, tx_ext) @@ -584,6 +589,7 @@ impl parachains_paras::Config for Runtime { parameter_types! { pub const BrokerId: u32 = 10u32; + pub MaxXcmTransactWeight: Weight = Weight::from_parts(10_000_000, 10_000); } pub struct BrokerPot; @@ -657,6 +663,7 @@ impl coretime::Config for Runtime { type BrokerId = BrokerId; type WeightInfo = crate::coretime::TestWeightInfo; type SendXcm = DummyXcmSender; + type MaxXcmTransactWeight = MaxXcmTransactWeight; type BrokerPotLocation = BrokerPot; type AssetTransactor = (); type AccountToLocation = (); @@ -832,6 +839,7 @@ pub type TxExtension = ( frame_system::CheckNonce<Runtime>, frame_system::CheckWeight<Runtime>, pallet_transaction_payment::ChargeTransactionPayment<Runtime>, + frame_system::WeightReclaim<Runtime>, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -926,7 +934,7 @@ sp_api::impl_runtime_apis! { } } - #[api_version(11)] + #[api_version(12)] impl polkadot_primitives::runtime_api::ParachainHost<Block> for Runtime { fn validators() -> Vec<ValidatorId> { runtime_impl::validators::<Runtime>() @@ -1061,10 +1069,12 @@ sp_api::impl_runtime_apis! { } fn para_backing_state(para_id: ParaId) -> Option<polkadot_primitives::vstaging::async_backing::BackingState> { + #[allow(deprecated)] runtime_impl::backing_state::<Runtime>(para_id) } fn async_backing_params() -> polkadot_primitives::AsyncBackingParams { + #[allow(deprecated)] runtime_impl::async_backing_params::<Runtime>() } @@ -1087,6 +1097,14 @@ sp_api::impl_runtime_apis! { fn candidates_pending_availability(para_id: ParaId) -> Vec<CommittedCandidateReceipt<Hash>> { runtime_impl::candidates_pending_availability::<Runtime>(para_id) } + + fn backing_constraints(para_id: ParaId) -> Option<Constraints> { + staging_runtime_impl::backing_constraints::<Runtime>(para_id) + } + + fn scheduling_lookahead() -> u32 { + staging_runtime_impl::scheduling_lookahead::<Runtime>() + } } impl sp_consensus_beefy::BeefyApi<Block, BeefyId> for Runtime { @@ -1182,7 +1200,7 @@ sp_api::impl_runtime_apis! { } fn current_set_id() -> fg_primitives::SetId { - Grandpa::current_set_id() + pallet_grandpa::CurrentSetId::<Runtime>::get() } fn submit_report_equivocation_unsigned_extrinsic( diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index f94301baab09f568518273199eb14684ef897719..3317484419a9a2472da1b28f6dcf9ce1d04e0d45 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -6,6 +6,8 @@ description = "Westend testnet Relay Chain runtime." authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -13,36 +15,36 @@ workspace = true [dependencies] bitvec = { features = ["alloc"], workspace = true } codec = { features = ["derive", "max-encoded-len"], workspace = true } -scale-info = { features = ["derive"], workspace = true } log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } serde = { workspace = true } serde_derive = { optional = true, workspace = true } serde_json = { features = ["alloc"], workspace = true } smallvec = { workspace = true, default-features = true } -sp-authority-discovery = { workspace = true } -sp-consensus-babe = { workspace = true } -sp-consensus-beefy = { workspace = true } -sp-consensus-grandpa = { workspace = true } binary-merkle-tree = { workspace = true } -sp-inherents = { workspace = true } -sp-offchain = { workspace = true } sp-api = { workspace = true } sp-application-crypto = { workspace = true } sp-arithmetic = { workspace = true } +sp-authority-discovery = { workspace = true } +sp-block-builder = { workspace = true } +sp-consensus-babe = { workspace = true } +sp-consensus-beefy = { workspace = true } +sp-consensus-grandpa = { workspace = true } +sp-core = { workspace = true } sp-genesis-builder = { workspace = true } +sp-inherents = { workspace = true } sp-io = { workspace = true } +sp-keyring = { workspace = true } sp-mmr-primitives = { workspace = true } +sp-npos-elections = { workspace = true } +sp-offchain = { workspace = true } sp-runtime = { workspace = true } -sp-staking = { workspace = true } -sp-core = { workspace = true } sp-session = { workspace = true } +sp-staking = { workspace = true } sp-storage = { workspace = true } -sp-version = { workspace = true } sp-transaction-pool = { workspace = true } -sp-block-builder = { workspace = true } -sp-npos-elections = { workspace = true } -sp-keyring = { workspace = true } +sp-version = { workspace = true } frame-election-provider-support = { workspace = true } frame-executive = { workspace = true } @@ -50,7 +52,6 @@ frame-metadata-hash-extension = { workspace = true } frame-support = { features = ["experimental", "tuples-96"], workspace = true } frame-system = { workspace = true } frame-system-rpc-runtime-api = { workspace = true } -westend-runtime-constants = { workspace = true } pallet-asset-rate = { workspace = true } pallet-authority-discovery = { workspace = true } pallet-authorship = { workspace = true } @@ -59,10 +60,10 @@ pallet-bags-list = { workspace = true } pallet-balances = { workspace = true } pallet-beefy = { workspace = true } pallet-beefy-mmr = { workspace = true } -pallet-collective = { workspace = true } -pallet-democracy = { workspace = true } -pallet-elections-phragmen = { workspace = true } +pallet-conviction-voting = { workspace = true } +pallet-delegated-staking = { workspace = true } pallet-election-provider-multi-phase = { workspace = true } +pallet-elections-phragmen = { workspace = true } pallet-fast-unstake = { workspace = true } pallet-grandpa = { workspace = true } pallet-identity = { workspace = true } @@ -73,60 +74,59 @@ pallet-migrations = { workspace = true } pallet-mmr = { workspace = true } pallet-multisig = { workspace = true } pallet-nomination-pools = { workspace = true } -pallet-conviction-voting = { workspace = true } +pallet-nomination-pools-runtime-api = { workspace = true } pallet-offences = { workspace = true } pallet-parameters = { workspace = true } pallet-preimage = { workspace = true } pallet-proxy = { workspace = true } pallet-recovery = { workspace = true } pallet-referenda = { workspace = true } +pallet-root-testing = { workspace = true } pallet-scheduler = { workspace = true } pallet-session = { workspace = true } pallet-society = { workspace = true } pallet-staking = { workspace = true } pallet-staking-runtime-api = { workspace = true } -pallet-delegated-staking = { workspace = true } pallet-state-trie-migration = { workspace = true } pallet-sudo = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } -pallet-nomination-pools-runtime-api = { workspace = true } pallet-treasury = { workspace = true } pallet-utility = { workspace = true } pallet-vesting = { workspace = true } pallet-whitelist = { workspace = true } pallet-xcm = { workspace = true } pallet-xcm-benchmarks = { optional = true, workspace = true } -pallet-root-testing = { workspace = true } +westend-runtime-constants = { workspace = true } frame-benchmarking = { optional = true, workspace = true } -frame-try-runtime = { optional = true, workspace = true } frame-system-benchmarking = { optional = true, workspace = true } +frame-try-runtime = { optional = true, workspace = true } +hex-literal = { workspace = true, default-features = true } pallet-election-provider-support-benchmarking = { optional = true, workspace = true } pallet-nomination-pools-benchmarking = { optional = true, workspace = true } pallet-offences-benchmarking = { optional = true, workspace = true } pallet-session-benchmarking = { optional = true, workspace = true } -hex-literal = { workspace = true, default-features = true } -polkadot-runtime-common = { workspace = true } -polkadot-primitives = { workspace = true } polkadot-parachain-primitives = { workspace = true } +polkadot-primitives = { workspace = true } +polkadot-runtime-common = { workspace = true } polkadot-runtime-parachains = { workspace = true } xcm = { workspace = true } -xcm-executor = { workspace = true } xcm-builder = { workspace = true } +xcm-executor = { workspace = true } xcm-runtime-apis = { workspace = true } [dev-dependencies] approx = { workspace = true } -tiny-keccak = { features = ["keccak"], workspace = true } -sp-keyring = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } remote-externalities = { workspace = true, default-features = true } -tokio = { features = ["macros"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } sp-tracing = { workspace = true } +tiny-keccak = { features = ["keccak"], workspace = true } +tokio = { features = ["macros"], workspace = true, default-features = true } [build-dependencies] substrate-wasm-builder = { workspace = true, default-features = true } @@ -157,10 +157,8 @@ std = [ "pallet-balances/std", "pallet-beefy-mmr/std", "pallet-beefy/std", - "pallet-collective/std", "pallet-conviction-voting/std", "pallet-delegated-staking/std", - "pallet-democracy/std", "pallet-election-provider-multi-phase/std", "pallet-election-provider-support-benchmarking?/std", "pallet-elections-phragmen/std", @@ -248,10 +246,8 @@ runtime-benchmarks = [ "pallet-bags-list/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-beefy-mmr/runtime-benchmarks", - "pallet-collective/runtime-benchmarks", "pallet-conviction-voting/runtime-benchmarks", "pallet-delegated-staking/runtime-benchmarks", - "pallet-democracy/runtime-benchmarks", "pallet-election-provider-multi-phase/runtime-benchmarks", "pallet-election-provider-support-benchmarking/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", @@ -296,6 +292,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-election-provider-support/try-runtime", @@ -312,10 +309,8 @@ try-runtime = [ "pallet-balances/try-runtime", "pallet-beefy-mmr/try-runtime", "pallet-beefy/try-runtime", - "pallet-collective/try-runtime", "pallet-conviction-voting/try-runtime", "pallet-delegated-staking/try-runtime", - "pallet-democracy/try-runtime", "pallet-election-provider-multi-phase/try-runtime", "pallet-elections-phragmen/try-runtime", "pallet-fast-unstake/try-runtime", diff --git a/polkadot/runtime/westend/constants/Cargo.toml b/polkadot/runtime/westend/constants/Cargo.toml index 27d5b19b8e77113a8f4ea5f14bfb701364308f07..f3dbcc309ee1e79b4449a050aa49f78d75f49274 100644 --- a/polkadot/runtime/westend/constants/Cargo.toml +++ b/polkadot/runtime/westend/constants/Cargo.toml @@ -5,6 +5,8 @@ description = "Constants used throughout the Westend network." authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [package.metadata.polkadot-sdk] exclude-from-umbrella = true @@ -18,9 +20,9 @@ smallvec = { workspace = true, default-features = true } frame-support = { workspace = true } polkadot-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } +sp-core = { workspace = true } sp-runtime = { workspace = true } sp-weights = { workspace = true } -sp-core = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } diff --git a/polkadot/runtime/westend/src/genesis_config_presets.rs b/polkadot/runtime/westend/src/genesis_config_presets.rs index b8f7710089e044b15ec97645ad95223181f0dd46..76c0ce015c0d8a8fb3106f06cb570cbdf4bd8608 100644 --- a/polkadot/runtime/westend/src/genesis_config_presets.rs +++ b/polkadot/runtime/westend/src/genesis_config_presets.rs @@ -128,15 +128,16 @@ fn default_parachains_host_configuration( zeroth_delay_tranche_width: 0, minimum_validation_upgrade_delay: 5, async_backing_params: AsyncBackingParams { - max_candidate_depth: 3, - allowed_ancestry_len: 2, + max_candidate_depth: 0, + allowed_ancestry_len: 0, }, node_features: bitvec::vec::BitVec::from_element( 1u8 << (FeatureIndex::ElasticScalingMVP as usize) | - 1u8 << (FeatureIndex::EnableAssignmentsV2 as usize), + 1u8 << (FeatureIndex::EnableAssignmentsV2 as usize) | + 1u8 << (FeatureIndex::CandidateReceiptV2 as usize), ), scheduler_params: SchedulerParams { - lookahead: 2, + lookahead: 3, group_rotation_frequency: 20, paras_availability_period: 4, ..Default::default() diff --git a/polkadot/runtime/westend/src/impls.rs b/polkadot/runtime/westend/src/impls.rs index d7281dad56d4c416057e840732a40243ea4dde2a..0e0d345a0ed44a35f6490f1586ef6e6721df0aa6 100644 --- a/polkadot/runtime/westend/src/impls.rs +++ b/polkadot/runtime/westend/src/impls.rs @@ -21,7 +21,7 @@ use core::marker::PhantomData; use frame_support::pallet_prelude::DispatchResult; use frame_system::RawOrigin; use polkadot_primitives::Balance; -use polkadot_runtime_common::identity_migrator::OnReapIdentity; +use polkadot_runtime_common::identity_migrator::{OnReapIdentity, WeightInfo}; use westend_runtime_constants::currency::*; use xcm::{latest::prelude::*, VersionedLocation, VersionedXcm}; use xcm_executor::traits::TransactAsset; @@ -88,7 +88,10 @@ where AccountId: Into<[u8; 32]> + Clone + Encode, { fn on_reap_identity(who: &AccountId, fields: u32, subs: u32) -> DispatchResult { - use crate::impls::IdentityMigratorCalls::PokeDeposit; + use crate::{ + impls::IdentityMigratorCalls::PokeDeposit, + weights::polkadot_runtime_common_identity_migrator::WeightInfo as MigratorWeights, + }; let total_to_send = Self::calculate_remote_deposit(fields, subs); @@ -141,6 +144,7 @@ where .into(); let poke = PeopleRuntimePallets::<AccountId>::IdentityMigrator(PokeDeposit(who.clone())); + let remote_weight_limit = MigratorWeights::<Runtime>::poke_deposit().saturating_mul(2); // Actual program to execute on People Chain. let program: Xcm<()> = Xcm(vec![ @@ -157,7 +161,11 @@ where .into(), }, // Poke the deposit to reserve the appropriate amount on the parachain. - Transact { origin_kind: OriginKind::Superuser, call: poke.encode().into() }, + Transact { + origin_kind: OriginKind::Superuser, + call: poke.encode().into(), + fallback_max_weight: Some(remote_weight_limit), + }, ]); // send @@ -168,4 +176,9 @@ where )?; Ok(()) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_identity_reaping(_: &AccountId, _: u32, _: u32) { + crate::Dmp::make_parachain_reachable(1004); + } } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 9f0b701f20befd5e5e687f2f5e0470c0c9385157..51eede7c7342ee1976c0c12cb69d9d395602cd65 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -52,8 +52,8 @@ use pallet_transaction_payment::{FeeDetails, FungibleAdapter, RuntimeDispatchInf use polkadot_primitives::{ slashing, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, AccountId, AccountIndex, ApprovalVotingParams, Balance, BlockNumber, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, @@ -84,7 +84,9 @@ use polkadot_runtime_parachains::{ initializer as parachains_initializer, on_demand as parachains_on_demand, origin as parachains_origin, paras as parachains_paras, paras_inherent as parachains_paras_inherent, reward_points as parachains_reward_points, - runtime_api_impl::v11 as parachains_runtime_api_impl, + runtime_api_impl::{ + v11 as parachains_runtime_api_impl, vstaging as parachains_staging_runtime_api_impl, + }, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -172,10 +174,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("westend"), impl_name: alloc::borrow::Cow::Borrowed("parity-westend"), authoring_version: 2, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 26, + transaction_version: 27, system_version: 1, }; @@ -728,8 +730,10 @@ parameter_types! { } impl pallet_staking::Config for Runtime { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = Balance; + type RuntimeHoldReason = RuntimeHoldReason; type UnixTime = Timestamp; type CurrencyToVote = CurrencyToVote; type RewardRemainder = (); @@ -923,6 +927,7 @@ where frame_system::CheckWeight::<Runtime>::new(), pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip), frame_metadata_hash_extension::CheckMetadataHash::<Runtime>::new(true), + frame_system::WeightReclaim::<Runtime>::new(), ) .into(); let raw_payload = SignedPayload::new(call, tx_ext) @@ -1018,6 +1023,7 @@ impl pallet_recovery::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type RuntimeCall = RuntimeCall; + type BlockNumberProvider = System; type Currency = Balances; type ConfigDepositBase = ConfigDepositBase; type FriendDepositFactor = FriendDepositFactor; @@ -1083,6 +1089,7 @@ pub enum ProxyType { CancelProxy, Auction, NominationPools, + ParaRegistration, } impl Default for ProxyType { fn default() -> Self { @@ -1179,6 +1186,15 @@ impl InstanceFilter<RuntimeCall> for ProxyType { RuntimeCall::Registrar(..) | RuntimeCall::Slots(..) ), + ProxyType::ParaRegistration => matches!( + c, + RuntimeCall::Registrar(paras_registrar::Call::reserve { .. }) | + RuntimeCall::Registrar(paras_registrar::Call::register { .. }) | + RuntimeCall::Utility(pallet_utility::Call::batch { .. }) | + RuntimeCall::Utility(pallet_utility::Call::batch_all { .. }) | + RuntimeCall::Utility(pallet_utility::Call::force_batch { .. }) | + RuntimeCall::Proxy(pallet_proxy::Call::remove_proxy { .. }) + ), } } fn is_superset(&self, o: &Self) -> bool { @@ -1326,6 +1342,7 @@ impl parachains_scheduler::Config for Runtime { parameter_types! { pub const BrokerId: u32 = BROKER_ID; pub const BrokerPalletId: PalletId = PalletId(*b"py/broke"); + pub MaxXcmTransactWeight: Weight = Weight::from_parts(200_000_000, 20_000); } pub struct BrokerPot; @@ -1349,6 +1366,7 @@ impl coretime::Config for Runtime { xcm_config::ThisNetwork, <Runtime as frame_system::Config>::AccountId, >; + type MaxXcmTransactWeight = MaxXcmTransactWeight; } parameter_types! { @@ -1595,7 +1613,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; @@ -1811,6 +1830,7 @@ pub type TxExtension = ( frame_system::CheckWeight<Runtime>, pallet_transaction_payment::ChargeTransactionPayment<Runtime>, frame_metadata_hash_extension::CheckMetadataHash<Runtime>, + frame_system::WeightReclaim<Runtime>, ); parameter_types! { @@ -1956,6 +1976,12 @@ sp_api::impl_runtime_apis! { } } + impl frame_support::view_functions::runtime_api::RuntimeViewFunction<Block> for Runtime { + fn execute_view_function(id: frame_support::view_functions::ViewFunctionId, input: Vec<u8>) -> Result<Vec<u8>, frame_support::view_functions::ViewFunctionDispatchError> { + Runtime::execute_view_function(id, input) + } + } + impl sp_block_builder::BlockBuilder<Block> for Runtime { fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult { Executive::apply_extrinsic(extrinsic) @@ -1993,7 +2019,7 @@ sp_api::impl_runtime_apis! { } } - #[api_version(11)] + #[api_version(12)] impl polkadot_primitives::runtime_api::ParachainHost<Block> for Runtime { fn validators() -> Vec<ValidatorId> { parachains_runtime_api_impl::validators::<Runtime>() @@ -2131,10 +2157,12 @@ sp_api::impl_runtime_apis! { } fn para_backing_state(para_id: ParaId) -> Option<polkadot_primitives::vstaging::async_backing::BackingState> { + #[allow(deprecated)] parachains_runtime_api_impl::backing_state::<Runtime>(para_id) } fn async_backing_params() -> polkadot_primitives::AsyncBackingParams { + #[allow(deprecated)] parachains_runtime_api_impl::async_backing_params::<Runtime>() } @@ -2157,6 +2185,14 @@ sp_api::impl_runtime_apis! { fn candidates_pending_availability(para_id: ParaId) -> Vec<CommittedCandidateReceipt<Hash>> { parachains_runtime_api_impl::candidates_pending_availability::<Runtime>(para_id) } + + fn backing_constraints(para_id: ParaId) -> Option<Constraints> { + parachains_staging_runtime_api_impl::backing_constraints::<Runtime>(para_id) + } + + fn scheduling_lookahead() -> u32 { + parachains_staging_runtime_api_impl::scheduling_lookahead::<Runtime>() + } } #[api_version(5)] @@ -2295,7 +2331,7 @@ sp_api::impl_runtime_apis! { } fn current_set_id() -> fg_primitives::SetId { - Grandpa::current_set_id() + pallet_grandpa::CurrentSetId::<Runtime>::get() } fn submit_report_equivocation_unsigned_extrinsic( @@ -2440,7 +2476,8 @@ sp_api::impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> { - match asset.try_as::<AssetId>() { + let latest_asset_id: Result<AssetId, ()> = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::TokenLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) @@ -2637,14 +2674,14 @@ sp_api::impl_runtime_apis! { ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, AssetHubParaId, - (), + Dmp, >, polkadot_runtime_common::xcm_sender::ToParachainDeliveryHelper< xcm_config::XcmConfig, ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, RandomParaId, - (), + Dmp, > ); @@ -2710,7 +2747,7 @@ sp_api::impl_runtime_apis! { ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, AssetHubParaId, - (), + Dmp, >; fn valid_destination() -> Result<Location, BenchmarkError> { Ok(AssetHub::get()) @@ -2799,8 +2836,9 @@ sp_api::impl_runtime_apis! { } fn alias_origin() -> Result<(Location, Location), BenchmarkError> { - // The XCM executor of Westend doesn't have a configured `Aliasers` - Err(BenchmarkError::Skip) + let origin = Location::new(0, [Parachain(1000)]); + let target = Location::new(0, [Parachain(1000), AccountId32 { id: [128u8; 32], network: None }]); + Ok((origin, target)) } } diff --git a/polkadot/runtime/westend/src/tests.rs b/polkadot/runtime/westend/src/tests.rs index 02fd6b61496baae0a9acd266397e2b6573e1535b..65b81cc00f06975e7be235cb7d7700824b356853 100644 --- a/polkadot/runtime/westend/src/tests.rs +++ b/polkadot/runtime/westend/src/tests.rs @@ -23,7 +23,7 @@ use approx::assert_relative_eq; use frame_support::traits::WhitelistedStorageKeys; use pallet_staking::EraPayout; use sp_core::{crypto::Ss58Codec, hexdisplay::HexDisplay}; -use sp_keyring::AccountKeyring::Alice; +use sp_keyring::Sr25519Keyring::Alice; use xcm_runtime_apis::conversions::LocationToAccountHelper; const MILLISECONDS_PER_HOUR: u64 = 60 * 60 * 1000; @@ -155,25 +155,27 @@ mod remote_tests { let transport: Transport = var("WS").unwrap_or("ws://127.0.0.1:9900".to_string()).into(); let maybe_state_snapshot: Option<SnapshotConfig> = var("SNAP").map(|s| s.into()).ok(); + let online_config = OnlineConfig { + transport, + state_snapshot: maybe_state_snapshot.clone(), + child_trie: false, + pallets: vec![ + "Staking".into(), + "System".into(), + "Balances".into(), + "NominationPools".into(), + "DelegatedStaking".into(), + ], + ..Default::default() + }; let mut ext = Builder::<Block>::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() - }, + online_config, ) } else { - Mode::Online(OnlineConfig { transport, ..Default::default() }) + Mode::Online(online_config) }) .build() .await @@ -241,6 +243,77 @@ mod remote_tests { ); }); } + + #[tokio::test] + async fn staking_curr_fun_migrate() { + // Intended to be run only manually. + if var("RUN_MIGRATION_TESTS").is_err() { + return; + } + sp_tracing::try_init_simple(); + + let transport: Transport = var("WS").unwrap_or("ws://127.0.0.1:9944".to_string()).into(); + let maybe_state_snapshot: Option<SnapshotConfig> = var("SNAP").map(|s| s.into()).ok(); + let online_config = OnlineConfig { + transport, + state_snapshot: maybe_state_snapshot.clone(), + child_trie: false, + pallets: vec!["Staking".into(), "System".into(), "Balances".into()], + ..Default::default() + }; + let mut ext = Builder::<Block>::default() + .mode(if let Some(state_snapshot) = maybe_state_snapshot { + Mode::OfflineOrElseOnline( + OfflineConfig { state_snapshot: state_snapshot.clone() }, + online_config, + ) + } else { + Mode::Online(online_config) + }) + .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); + + let mut success = 0; + let mut err = 0; + let mut force_withdraw_acc = 0; + // iterate over all pools + pallet_staking::Ledger::<Runtime>::iter().for_each(|(ctrl, ledger)| { + match pallet_staking::Pallet::<Runtime>::migrate_currency( + RuntimeOrigin::signed(alice.clone()).into(), + ledger.stash.clone(), + ) { + Ok(_) => { + let updated_ledger = + pallet_staking::Ledger::<Runtime>::get(&ctrl).expect("ledger exists"); + let force_withdraw = ledger.total - updated_ledger.total; + if force_withdraw > 0 { + force_withdraw_acc += force_withdraw; + log::info!(target: "remote_test", "Force withdraw from stash {:?}: value {:?}", ledger.stash, force_withdraw); + } + success += 1; + }, + Err(e) => { + log::error!(target: "remote_test", "Error migrating {:?}: {:?}", ledger.stash, e); + err += 1; + }, + } + }); + + log::info!( + target: "remote_test", + "Migration stats: success: {}, err: {}, total force withdrawn stake: {}", + success, + err, + force_withdraw_acc + ); + }); + } } #[test] diff --git a/polkadot/runtime/westend/src/weights/frame_system_extensions.rs b/polkadot/runtime/westend/src/weights/frame_system_extensions.rs index 048f23fbcb91329eadfd721dd2ae63e1f218a89e..75f4f6d00b562dc589173c003b0e325e77f1b65f 100644 --- a/polkadot/runtime/westend/src/weights/frame_system_extensions.rs +++ b/polkadot/runtime/westend/src/weights/frame_system_extensions.rs @@ -17,24 +17,25 @@ //! Autogenerated weights for `frame_system_extensions` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-09-12, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `gleipnir`, CPU: `AMD Ryzen 9 7900X 12-Core Processor` +//! HOSTNAME: `runner-ys-ssygq-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/debug/polkadot +// target/production/polkadot // benchmark // pallet -// --steps=2 -// --repeat=2 +// --steps=50 +// --repeat=20 // --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --pallet=frame-system-extensions +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=frame_system_extensions // --chain=westend-dev -// --output=./polkadot/runtime/westend/src/weights/ // --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -47,45 +48,36 @@ use core::marker::PhantomData; /// Weight functions for `frame_system_extensions`. pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo<T> { - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_genesis() -> Weight { // Proof Size summary in bytes: - // Measured: `54` - // Estimated: `3509` - // Minimum execution time: 75_764_000 picoseconds. - Weight::from_parts(85_402_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `30` + // Estimated: `0` + // Minimum execution time: 3_357_000 picoseconds. + Weight::from_parts(3_484_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_mortal_transaction() -> Weight { // Proof Size summary in bytes: - // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 118_233_000 picoseconds. - Weight::from_parts(126_539_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `68` + // Estimated: `0` + // Minimum execution time: 6_242_000 picoseconds. + Weight::from_parts(6_566_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_immortal_transaction() -> Weight { // Proof Size summary in bytes: - // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 118_233_000 picoseconds. - Weight::from_parts(126_539_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `68` + // Estimated: `0` + // Minimum execution time: 6_268_000 picoseconds. + Weight::from_parts(6_631_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } fn check_non_zero_sender() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_885_000 picoseconds. - Weight::from_parts(12_784_000, 0) + // Minimum execution time: 567_000 picoseconds. + Weight::from_parts(617_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `System::Account` (r:1 w:1) @@ -94,8 +86,8 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 104_237_000 picoseconds. - Weight::from_parts(110_910_000, 0) + // Minimum execution time: 6_990_000 picoseconds. + Weight::from_parts(7_343_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -104,28 +96,32 @@ impl<T: frame_system::Config> frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_141_000 picoseconds. - Weight::from_parts(11_502_000, 0) + // Minimum execution time: 422_000 picoseconds. + Weight::from_parts(475_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn check_tx_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_192_000 picoseconds. - Weight::from_parts(11_481_000, 0) + // Minimum execution time: 434_000 picoseconds. + Weight::from_parts(519_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) - /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn check_weight() -> Weight { // Proof Size summary in bytes: - // Measured: `24` - // Estimated: `1489` - // Minimum execution time: 87_616_000 picoseconds. - Weight::from_parts(93_607_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_524_000 picoseconds. + Weight::from_parts(3_706_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_216_000 picoseconds. + Weight::from_parts(2_337_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } } diff --git a/polkadot/runtime/westend/src/weights/pallet_balances.rs b/polkadot/runtime/westend/src/weights/pallet_balances.rs index 5e91f31920cab122d044465fa6c3538053f4acf5..deaf8840462b37aea314455ec9f66978828e9abe 100644 --- a/polkadot/runtime/westend/src/weights/pallet_balances.rs +++ b/polkadot/runtime/westend/src/weights/pallet_balances.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_balances` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-05-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-06, 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: `95c137a642c3`, 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=* +// --chain=westend-dev +// --pallet=pallet_balances +// --header=/__w/polkadot-sdk/polkadot-sdk/polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_balances -// --chain=westend-dev -// --header=./polkadot/file_header.txt -// --output=./polkadot/runtime/westend/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,8 +56,8 @@ impl<T: frame_system::Config> pallet_balances::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 43_248_000 picoseconds. - Weight::from_parts(43_872_000, 0) + // Minimum execution time: 51_474_000 picoseconds. + Weight::from_parts(52_840_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -66,8 +68,8 @@ impl<T: frame_system::Config> pallet_balances::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 33_990_000 picoseconds. - Weight::from_parts(34_693_000, 0) + // Minimum execution time: 39_875_000 picoseconds. + Weight::from_parts(41_408_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -78,8 +80,8 @@ impl<T: frame_system::Config> pallet_balances::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 12_681_000 picoseconds. - Weight::from_parts(13_183_000, 0) + // Minimum execution time: 19_614_000 picoseconds. + Weight::from_parts(20_194_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -90,8 +92,8 @@ impl<T: frame_system::Config> pallet_balances::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 17_474_000 picoseconds. - Weight::from_parts(18_063_000, 0) + // Minimum execution time: 27_430_000 picoseconds. + Weight::from_parts(28_151_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -102,8 +104,8 @@ impl<T: frame_system::Config> pallet_balances::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `103` // Estimated: `6196` - // Minimum execution time: 45_699_000 picoseconds. - Weight::from_parts(46_099_000, 0) + // Minimum execution time: 54_131_000 picoseconds. + Weight::from_parts(54_810_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -114,8 +116,8 @@ impl<T: frame_system::Config> pallet_balances::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 42_453_000 picoseconds. - Weight::from_parts(43_133_000, 0) + // Minimum execution time: 48_692_000 picoseconds. + Weight::from_parts(51_416_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -126,8 +128,8 @@ impl<T: frame_system::Config> pallet_balances::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 15_066_000 picoseconds. - Weight::from_parts(15_605_000, 0) + // Minimum execution time: 22_604_000 picoseconds. + Weight::from_parts(23_336_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -139,11 +141,11 @@ impl<T: frame_system::Config> pallet_balances::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0 + u * (136 ±0)` // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 14_180_000 picoseconds. - Weight::from_parts(14_598_000, 0) + // Minimum execution time: 18_118_000 picoseconds. + Weight::from_parts(18_352_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 13_221 - .saturating_add(Weight::from_parts(13_422_901, 0).saturating_mul(u.into())) + // Standard Error: 14_688 + .saturating_add(Weight::from_parts(15_412_440, 0).saturating_mul(u.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(u.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(u.into()))) .saturating_add(Weight::from_parts(0, 2603).saturating_mul(u.into())) @@ -152,24 +154,24 @@ impl<T: frame_system::Config> pallet_balances::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_130_000 picoseconds. - Weight::from_parts(5_257_000, 0) + // Minimum execution time: 6_779_000 picoseconds. + Weight::from_parts(7_246_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn burn_allow_death() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 27_328_000 picoseconds. - Weight::from_parts(27_785_000, 0) + // Minimum execution time: 30_935_000 picoseconds. + Weight::from_parts(32_251_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn burn_keep_alive() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 17_797_000 picoseconds. - Weight::from_parts(18_103_000, 0) + // Minimum execution time: 21_002_000 picoseconds. + Weight::from_parts(21_760_000, 0) .saturating_add(Weight::from_parts(0, 0)) } } diff --git a/polkadot/runtime/westend/src/weights/pallet_beefy_mmr.rs b/polkadot/runtime/westend/src/weights/pallet_beefy_mmr.rs index 5be207e3fcff484a789d209b3f3cdd7db7c49e15..8de9f6ab53e6a8f252ea13102aa7534e6fe4f179 100644 --- a/polkadot/runtime/westend/src/weights/pallet_beefy_mmr.rs +++ b/polkadot/runtime/westend/src/weights/pallet_beefy_mmr.rs @@ -17,9 +17,9 @@ //! 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: `[]` +//! DATE: 2024-12-02, 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` +//! HOSTNAME: `runner-wiukf8gn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -48,14 +48,25 @@ use core::marker::PhantomData; /// Weight functions for `pallet_beefy_mmr`. pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> pallet_beefy_mmr::WeightInfo for WeightInfo<T> { + /// The range of component `n` is `[2, 512]`. + fn n_leafs_proof_is_optimal(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 628_000 picoseconds. + Weight::from_parts(1_200_102, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 63 + .saturating_add(Weight::from_parts(1_110, 0).saturating_mul(n.into())) + } /// 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` + // Measured: `68` // Estimated: `3509` - // Minimum execution time: 7_850_000 picoseconds. - Weight::from_parts(8_169_000, 0) + // Minimum execution time: 9_862_000 picoseconds. + Weight::from_parts(10_329_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -63,10 +74,10 @@ impl<T: frame_system::Config> pallet_beefy_mmr::WeightInfo for WeightInfo<T> { /// 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` + // Measured: `221` // Estimated: `3505` - // Minimum execution time: 6_852_000 picoseconds. - Weight::from_parts(7_448_000, 0) + // Minimum execution time: 6_396_000 picoseconds. + Weight::from_parts(6_691_000, 0) .saturating_add(Weight::from_parts(0, 3505)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -77,13 +88,13 @@ impl<T: frame_system::Config> pallet_beefy_mmr::WeightInfo for WeightInfo<T> { /// 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` + // Measured: `213` // Estimated: `1517` - // Minimum execution time: 12_860_000 picoseconds. - Weight::from_parts(17_158_162, 0) + // Minimum execution time: 12_553_000 picoseconds. + Weight::from_parts(24_003_920, 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())) + // Standard Error: 2_023 + .saturating_add(Weight::from_parts(1_390_986, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(2)) } } diff --git a/polkadot/runtime/westend/src/weights/pallet_migrations.rs b/polkadot/runtime/westend/src/weights/pallet_migrations.rs index 4fa07a23bb8ab4376d64cb4aa425f1cc515bb4fa..f5d4f079ca6d3b509a899ca11ddd041ca7b644fc 100644 --- a/polkadot/runtime/westend/src/weights/pallet_migrations.rs +++ b/polkadot/runtime/westend/src/weights/pallet_migrations.rs @@ -14,7 +14,31 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see <http://www.gnu.org/licenses/>. -// Need to rerun! +//! Autogenerated weights for `pallet_migrations` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `17938671047b`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 + +// Executed Command: +// frame-omni-bencher +// v1 +// benchmark +// pallet +// --extrinsic=* +// --runtime=target/production/wbuild/westend-runtime/westend_runtime.wasm +// --pallet=pallet_migrations +// --header=/__w/polkadot-sdk/polkadot-sdk/polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -33,22 +57,24 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) fn onboard_new_mbms() -> Weight { // Proof Size summary in bytes: - // Measured: `276` + // Measured: `133` // Estimated: `67035` - // Minimum execution time: 7_762_000 picoseconds. - Weight::from_parts(8_100_000, 67035) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Minimum execution time: 8_228_000 picoseconds. + Weight::from_parts(8_589_000, 0) + .saturating_add(Weight::from_parts(0, 67035)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `MultiBlockMigrations::Cursor` (r:1 w:0) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn progress_mbms_none() -> Weight { // Proof Size summary in bytes: - // Measured: `142` + // Measured: `4` // Estimated: `67035` - // Minimum execution time: 2_077_000 picoseconds. - Weight::from_parts(2_138_000, 67035) - .saturating_add(T::DbWeight::get().reads(1_u64)) + // Minimum execution time: 1_980_000 picoseconds. + Weight::from_parts(2_175_000, 0) + .saturating_add(Weight::from_parts(0, 67035)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -56,12 +82,13 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn exec_migration_completed() -> Weight { // Proof Size summary in bytes: - // Measured: `134` - // Estimated: `3599` - // Minimum execution time: 5_868_000 picoseconds. - Weight::from_parts(6_143_000, 3599) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Measured: `129` + // Estimated: `3594` + // Minimum execution time: 6_390_000 picoseconds. + Weight::from_parts(6_711_000, 0) + .saturating_add(Weight::from_parts(0, 3594)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -69,11 +96,12 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_skipped_historic() -> Weight { // Proof Size summary in bytes: - // Measured: `330` - // Estimated: `3795` - // Minimum execution time: 10_283_000 picoseconds. - Weight::from_parts(10_964_000, 3795) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Measured: `187` + // Estimated: `3731` + // Minimum execution time: 14_970_000 picoseconds. + Weight::from_parts(16_023_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -81,11 +109,12 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_advance() -> Weight { // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `3741` - // Minimum execution time: 9_900_000 picoseconds. - Weight::from_parts(10_396_000, 3741) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Measured: `133` + // Estimated: `3731` + // Minimum execution time: 10_908_000 picoseconds. + Weight::from_parts(11_291_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -93,12 +122,13 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_complete() -> Weight { // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `3741` - // Minimum execution time: 11_411_000 picoseconds. - Weight::from_parts(11_956_000, 3741) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Measured: `133` + // Estimated: `3731` + // Minimum execution time: 12_433_000 picoseconds. + Weight::from_parts(12_862_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -108,19 +138,21 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn exec_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `3741` - // Minimum execution time: 12_398_000 picoseconds. - Weight::from_parts(12_910_000, 3741) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Measured: `133` + // Estimated: `3731` + // Minimum execution time: 13_407_000 picoseconds. + Weight::from_parts(13_901_000, 0) + .saturating_add(Weight::from_parts(0, 3731)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } fn on_init_loop() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 166_000 picoseconds. - Weight::from_parts(193_000, 0) + // Minimum execution time: 162_000 picoseconds. + Weight::from_parts(207_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) @@ -128,9 +160,10 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_686_000 picoseconds. - Weight::from_parts(2_859_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Minimum execution time: 2_696_000 picoseconds. + Weight::from_parts(2_867_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) @@ -138,9 +171,10 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_070_000 picoseconds. - Weight::from_parts(3_250_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Minimum execution time: 3_232_000 picoseconds. + Weight::from_parts(3_436_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `MultiBlockMigrations::Cursor` (r:1 w:0) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) @@ -148,26 +182,44 @@ impl<T: frame_system::Config> pallet_migrations::WeightInfo for WeightInfo<T> { /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) fn force_onboard_mbms() -> Weight { // Proof Size summary in bytes: - // Measured: `251` + // Measured: `109` // Estimated: `67035` - // Minimum execution time: 5_901_000 picoseconds. - Weight::from_parts(6_320_000, 67035) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Minimum execution time: 5_849_000 picoseconds. + Weight::from_parts(6_156_000, 0) + .saturating_add(Weight::from_parts(0, 67035)) + .saturating_add(T::DbWeight::get().reads(2)) } /// Storage: `MultiBlockMigrations::Historic` (r:256 w:256) /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 256]`. fn clear_historic(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1122 + n * (271 ±0)` + // Measured: `984 + n * (271 ±0)` // Estimated: `3834 + n * (2740 ±0)` - // Minimum execution time: 15_952_000 picoseconds. - Weight::from_parts(14_358_665, 3834) - // Standard Error: 3_358 - .saturating_add(Weight::from_parts(1_323_674, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(1_u64)) + // Minimum execution time: 20_906_000 picoseconds. + Weight::from_parts(15_361_535, 0) + .saturating_add(Weight::from_parts(0, 3834)) + // Standard Error: 7_911 + .saturating_add(Weight::from_parts(1_518_172, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2740).saturating_mul(n.into())) } -} \ No newline at end of file + /// 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, 2048]`. + fn reset_pallet_migration(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1676 + n * (38 ±0)` + // Estimated: `754 + n * (39 ±0)` + // Minimum execution time: 1_913_000 picoseconds. + Weight::from_parts(1_986_000, 0) + .saturating_add(Weight::from_parts(0, 754)) + // Standard Error: 2_511 + .saturating_add(Weight::from_parts(919_965, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 39).saturating_mul(n.into())) + } +} diff --git a/polkadot/runtime/westend/src/weights/pallet_multisig.rs b/polkadot/runtime/westend/src/weights/pallet_multisig.rs index 616aea9c8e73f0bf078de9d5e4cb55d8565b40b9..83521f3d1927b81b37371fafe21bc6fdcfbfc397 100644 --- a/polkadot/runtime/westend/src/weights/pallet_multisig.rs +++ b/polkadot/runtime/westend/src/weights/pallet_multisig.rs @@ -16,28 +16,28 @@ //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner--ss9ysm1-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet +// --extrinsic=* // --chain=westend-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 +// --heap-pages=4096 // --no-storage-info -// --no-median-slopes // --no-min-squares -// --pallet=pallet_multisig -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/westend/src/weights/ +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,110 +55,111 @@ impl<T: frame_system::Config> pallet_multisig::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_218_000 picoseconds. - Weight::from_parts(14_749_472, 0) + // Minimum execution time: 15_705_000 picoseconds. + Weight::from_parts(16_890_096, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 10 - .saturating_add(Weight::from_parts(507, 0).saturating_mul(z.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(549, 0).saturating_mul(z.into())) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `309 + s * (2 ±0)` + // Measured: `267 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 45_891_000 picoseconds. - Weight::from_parts(33_546_627, 0) + // Minimum execution time: 54_293_000 picoseconds. + Weight::from_parts(39_710_880, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 2_347 - .saturating_add(Weight::from_parts(136_466, 0).saturating_mul(s.into())) - // Standard Error: 23 - .saturating_add(Weight::from_parts(1_595, 0).saturating_mul(z.into())) + // Standard Error: 1_591 + .saturating_add(Weight::from_parts(164_846, 0).saturating_mul(s.into())) + // Standard Error: 15 + .saturating_add(Weight::from_parts(1_993, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[3, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `286` // Estimated: `6811` - // Minimum execution time: 30_355_000 picoseconds. - Weight::from_parts(19_611_682, 0) + // Minimum execution time: 36_477_000 picoseconds. + Weight::from_parts(22_595_904, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_383 - .saturating_add(Weight::from_parts(123_652, 0).saturating_mul(s.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(1_488, 0).saturating_mul(z.into())) + // Standard Error: 1_526 + .saturating_add(Weight::from_parts(159_314, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_219, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `392 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 50_453_000 picoseconds. - Weight::from_parts(35_628_285, 0) + // Minimum execution time: 60_127_000 picoseconds. + Weight::from_parts(33_469_803, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 3_693 - .saturating_add(Weight::from_parts(203_453, 0).saturating_mul(s.into())) - // Standard Error: 36 - .saturating_add(Weight::from_parts(1_726, 0).saturating_mul(z.into())) + // Standard Error: 3_400 + .saturating_add(Weight::from_parts(309_634, 0).saturating_mul(s.into())) + // Standard Error: 33 + .saturating_add(Weight::from_parts(2_795, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `314 + s * (2 ±0)` + // Measured: `267 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 32_500_000 picoseconds. - Weight::from_parts(33_231_806, 0) + // Minimum execution time: 36_697_000 picoseconds. + Weight::from_parts(38_746_125, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_511 - .saturating_add(Weight::from_parts(134_500, 0).saturating_mul(s.into())) + // Standard Error: 2_073 + .saturating_add(Weight::from_parts(159_426, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `286` // Estimated: `6811` - // Minimum execution time: 17_906_000 picoseconds. - Weight::from_parts(18_757_928, 0) + // Minimum execution time: 21_909_000 picoseconds. + Weight::from_parts(22_227_385, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_172 - .saturating_add(Weight::from_parts(113_535, 0).saturating_mul(s.into())) + // Standard Error: 1_063 + .saturating_add(Weight::from_parts(146_021, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `458 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 33_018_000 picoseconds. - Weight::from_parts(34_186_533, 0) + // Minimum execution time: 36_637_000 picoseconds. + Weight::from_parts(36_457_379, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_188 - .saturating_add(Weight::from_parts(128_449, 0).saturating_mul(s.into())) + // Standard Error: 1_709 + .saturating_add(Weight::from_parts(171_090, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/polkadot/runtime/westend/src/weights/pallet_staking.rs b/polkadot/runtime/westend/src/weights/pallet_staking.rs index 393fa0b37176a0ca5ce8160eec7e8e3ac56c3458..f1e7f5ba1576eafbd768f51cf98faa41cbe02b5c 100644 --- a/polkadot/runtime/westend/src/weights/pallet_staking.rs +++ b/polkadot/runtime/westend/src/weights/pallet_staking.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_staking` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-03-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-09-17, 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-obbyq9g6-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -52,19 +52,19 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:0 w:1) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn bond() -> Weight { // Proof Size summary in bytes: - // Measured: `1009` - // Estimated: `4764` - // Minimum execution time: 40_585_000 picoseconds. - Weight::from_parts(41_800_000, 0) - .saturating_add(Weight::from_parts(0, 4764)) + // Measured: `1035` + // Estimated: `4556` + // Minimum execution time: 70_147_000 picoseconds. + Weight::from_parts(71_795_000, 0) + .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -72,20 +72,20 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn bond_extra() -> Weight { // Proof Size summary in bytes: - // Measured: `1921` + // Measured: `1947` // Estimated: `8877` - // Minimum execution time: 81_809_000 picoseconds. - Weight::from_parts(84_387_000, 0) + // Minimum execution time: 125_203_000 picoseconds. + Weight::from_parts(128_088_000, 0) .saturating_add(Weight::from_parts(0, 8877)) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(7)) @@ -100,23 +100,23 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn unbond() -> Weight { // Proof Size summary in bytes: - // Measured: `2128` + // Measured: `2051` // Estimated: `8877` - // Minimum execution time: 89_419_000 picoseconds. - Weight::from_parts(91_237_000, 0) + // Minimum execution time: 101_991_000 picoseconds. + Weight::from_parts(104_567_000, 0) .saturating_add(Weight::from_parts(0, 8877)) .saturating_add(T::DbWeight::get().reads(12)) - .saturating_add(T::DbWeight::get().writes(7)) + .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) @@ -124,23 +124,25 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:0) /// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Agents` (r:1 w:0) + /// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1223` - // Estimated: `4764` - // Minimum execution time: 45_152_000 picoseconds. - Weight::from_parts(46_460_819, 0) - .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 972 - .saturating_add(Weight::from_parts(55_473, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `1253` + // Estimated: `4556` + // Minimum execution time: 76_450_000 picoseconds. + Weight::from_parts(78_836_594, 0) + .saturating_add(Weight::from_parts(0, 4556)) + // Standard Error: 1_529 + .saturating_add(Weight::from_parts(66_662, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Staking::Ledger` (r:1 w:1) @@ -151,10 +153,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashingSpans` (r:1 w:1) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -174,15 +176,15 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2127 + s * (4 ±0)` + // Measured: `2153 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 82_762_000 picoseconds. - Weight::from_parts(91_035_077, 0) + // Minimum execution time: 121_962_000 picoseconds. + Weight::from_parts(131_000_151, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 3_771 - .saturating_add(Weight::from_parts(1_217_871, 0).saturating_mul(s.into())) + // Standard Error: 3_846 + .saturating_add(Weight::from_parts(1_277_843, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13)) - .saturating_add(T::DbWeight::get().writes(11)) + .saturating_add(T::DbWeight::get().writes(12)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -210,10 +212,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// Proof: `Staking::CounterForValidators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn validate() -> Weight { // Proof Size summary in bytes: - // Measured: `1301` + // Measured: `1334` // Estimated: `4556` - // Minimum execution time: 50_555_000 picoseconds. - Weight::from_parts(52_052_000, 0) + // Minimum execution time: 66_450_000 picoseconds. + Weight::from_parts(68_302_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(5)) @@ -227,13 +229,13 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1778 + k * (572 ±0)` + // Measured: `1811 + k * (572 ±0)` // Estimated: `4556 + k * (3033 ±0)` - // Minimum execution time: 35_037_000 picoseconds. - Weight::from_parts(35_081_878, 0) + // Minimum execution time: 43_875_000 picoseconds. + Weight::from_parts(47_332_240, 0) .saturating_add(Weight::from_parts(0, 4556)) - // Standard Error: 5_473 - .saturating_add(Weight::from_parts(6_667_924, 0).saturating_mul(k.into())) + // Standard Error: 6_530 + .saturating_add(Weight::from_parts(7_398_001, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -264,13 +266,13 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1797 + n * (102 ±0)` + // Measured: `1830 + n * (102 ±0)` // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 62_098_000 picoseconds. - Weight::from_parts(60_154_061, 0) + // Minimum execution time: 80_640_000 picoseconds. + Weight::from_parts(78_801_092, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 19_257 - .saturating_add(Weight::from_parts(3_839_855, 0).saturating_mul(n.into())) + // Standard Error: 22_249 + .saturating_add(Weight::from_parts(4_996_344, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(6)) @@ -294,10 +296,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill() -> Weight { // Proof Size summary in bytes: - // Measured: `1747` + // Measured: `1780` // Estimated: `6248` - // Minimum execution time: 54_993_000 picoseconds. - Weight::from_parts(56_698_000, 0) + // Minimum execution time: 71_494_000 picoseconds. + Weight::from_parts(73_487_000, 0) .saturating_add(Weight::from_parts(0, 6248)) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(6)) @@ -310,10 +312,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn set_payee() -> Weight { // Proof Size summary in bytes: - // Measured: `865` + // Measured: `898` // Estimated: `4556` - // Minimum execution time: 18_100_000 picoseconds. - Weight::from_parts(18_547_000, 0) + // Minimum execution time: 24_310_000 picoseconds. + Weight::from_parts(24_676_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -326,10 +328,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn update_payee() -> Weight { // Proof Size summary in bytes: - // Measured: `932` + // Measured: `965` // Estimated: `4556` - // Minimum execution time: 23_428_000 picoseconds. - Weight::from_parts(24_080_000, 0) + // Minimum execution time: 31_348_000 picoseconds. + Weight::from_parts(32_384_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -340,10 +342,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) fn set_controller() -> Weight { // Proof Size summary in bytes: - // Measured: `865` + // Measured: `898` // Estimated: `8122` - // Minimum execution time: 21_159_000 picoseconds. - Weight::from_parts(21_706_000, 0) + // Minimum execution time: 27_537_000 picoseconds. + Weight::from_parts(28_714_000, 0) .saturating_add(Weight::from_parts(0, 8122)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) @@ -354,8 +356,8 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_910_000 picoseconds. - Weight::from_parts(2_003_000, 0) + // Minimum execution time: 2_362_000 picoseconds. + Weight::from_parts(2_518_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -365,8 +367,8 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_076_000 picoseconds. - Weight::from_parts(7_349_000, 0) + // Minimum execution time: 7_752_000 picoseconds. + Weight::from_parts(8_105_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -376,8 +378,8 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_067_000 picoseconds. - Weight::from_parts(7_389_000, 0) + // Minimum execution time: 7_868_000 picoseconds. + Weight::from_parts(8_175_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -387,8 +389,8 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_148_000 picoseconds. - Weight::from_parts(7_446_000, 0) + // Minimum execution time: 7_945_000 picoseconds. + Weight::from_parts(8_203_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -399,11 +401,11 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_025_000 picoseconds. - Weight::from_parts(2_229_953, 0) + // Minimum execution time: 2_458_000 picoseconds. + Weight::from_parts(2_815_664, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 67 - .saturating_add(Weight::from_parts(11_785, 0).saturating_mul(v.into())) + .saturating_add(Weight::from_parts(12_287, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Staking::Ledger` (r:1502 w:1502) @@ -415,13 +417,13 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// The range of component `i` is `[0, 751]`. fn deprecate_controller_batch(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `680 + i * (227 ±0)` + // Measured: `713 + i * (227 ±0)` // Estimated: `990 + i * (7132 ±0)` - // Minimum execution time: 4_321_000 picoseconds. - Weight::from_parts(4_407_000, 0) + // Minimum execution time: 4_976_000 picoseconds. + Weight::from_parts(5_102_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 37_239 - .saturating_add(Weight::from_parts(21_300_598, 0).saturating_mul(i.into())) + // Standard Error: 36_458 + .saturating_add(Weight::from_parts(25_359_275, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(i.into()))) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(i.into()))) .saturating_add(Weight::from_parts(0, 7132).saturating_mul(i.into())) @@ -432,10 +434,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) @@ -457,15 +459,15 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2127 + s * (4 ±0)` + // Measured: `2153 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 78_908_000 picoseconds. - Weight::from_parts(84_886_373, 0) + // Minimum execution time: 116_776_000 picoseconds. + Weight::from_parts(125_460_389, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 3_376 - .saturating_add(Weight::from_parts(1_217_850, 0).saturating_mul(s.into())) + // Standard Error: 3_095 + .saturating_add(Weight::from_parts(1_300_502, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13)) - .saturating_add(T::DbWeight::get().writes(12)) + .saturating_add(T::DbWeight::get().writes(13)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -474,13 +476,13 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `66639` - // Estimated: `70104` - // Minimum execution time: 136_389_000 picoseconds. - Weight::from_parts(1_207_241_524, 0) - .saturating_add(Weight::from_parts(0, 70104)) - // Standard Error: 77_138 - .saturating_add(Weight::from_parts(6_443_948, 0).saturating_mul(s.into())) + // Measured: `66672` + // Estimated: `70137` + // Minimum execution time: 135_135_000 picoseconds. + Weight::from_parts(937_565_332, 0) + .saturating_add(Weight::from_parts(0, 70137)) + // Standard Error: 57_675 + .saturating_add(Weight::from_parts(4_828_080, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -498,12 +500,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasValidatorReward` (r:1 w:0) /// Proof: `Staking::ErasValidatorReward` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:65 w:65) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:65 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:65 w:65) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:65 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:65 w:65) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasStakersPaged` (r:1 w:0) /// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Staking::ErasRewardPoints` (r:1 w:0) @@ -512,30 +512,32 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:65 w:0) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:65 w:65) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 64]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `8249 + n * (396 ±0)` - // Estimated: `10779 + n * (3774 ±0)` - // Minimum execution time: 130_222_000 picoseconds. - Weight::from_parts(167_236_150, 0) - .saturating_add(Weight::from_parts(0, 10779)) - // Standard Error: 34_051 - .saturating_add(Weight::from_parts(39_899_917, 0).saturating_mul(n.into())) + // Measured: `8275 + n * (389 ±0)` + // Estimated: `10805 + n * (3566 ±0)` + // Minimum execution time: 180_144_000 picoseconds. + Weight::from_parts(237_134_733, 0) + .saturating_add(Weight::from_parts(0, 10805)) + // Standard Error: 52_498 + .saturating_add(Weight::from_parts(73_633_326, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(14)) .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 3774).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) } /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) @@ -543,26 +545,26 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1922 + l * (5 ±0)` + // Measured: `1845 + l * (5 ±0)` // Estimated: `8877` - // Minimum execution time: 79_136_000 picoseconds. - Weight::from_parts(82_129_497, 0) + // Minimum execution time: 89_307_000 picoseconds. + Weight::from_parts(92_902_634, 0) .saturating_add(Weight::from_parts(0, 8877)) - // Standard Error: 3_867 - .saturating_add(Weight::from_parts(75_156, 0).saturating_mul(l.into())) + // Standard Error: 4_446 + .saturating_add(Weight::from_parts(73_546, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(7)) + .saturating_add(T::DbWeight::get().writes(6)) } + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashingSpans` (r:1 w:1) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -582,15 +584,15 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2127 + s * (4 ±0)` + // Measured: `2153 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 89_375_000 picoseconds. - Weight::from_parts(91_224_907, 0) + // Minimum execution time: 130_544_000 picoseconds. + Weight::from_parts(133_260_598, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 3_424 - .saturating_add(Weight::from_parts(1_219_542, 0).saturating_mul(s.into())) + // Standard Error: 3_545 + .saturating_add(Weight::from_parts(1_313_348, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(12)) - .saturating_add(T::DbWeight::get().writes(11)) + .saturating_add(T::DbWeight::get().writes(12)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -633,14 +635,14 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { fn new_era(v: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0 + n * (716 ±0) + v * (3594 ±0)` - // Estimated: `456136 + n * (3566 ±4) + v * (3566 ±0)` - // Minimum execution time: 520_905_000 picoseconds. - Weight::from_parts(523_771_000, 0) + // Estimated: `456136 + n * (3566 ±4) + v * (3566 ±40)` + // Minimum execution time: 654_756_000 picoseconds. + Weight::from_parts(658_861_000, 0) .saturating_add(Weight::from_parts(0, 456136)) - // Standard Error: 2_142_714 - .saturating_add(Weight::from_parts(68_631_588, 0).saturating_mul(v.into())) - // Standard Error: 213_509 - .saturating_add(Weight::from_parts(19_343_025, 0).saturating_mul(n.into())) + // Standard Error: 2_078_102 + .saturating_add(Weight::from_parts(67_775_668, 0).saturating_mul(v.into())) + // Standard Error: 207_071 + .saturating_add(Weight::from_parts(22_624_711, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(184)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -669,15 +671,15 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// The range of component `n` is `[500, 1000]`. fn get_npos_voters(v: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `3108 + n * (907 ±0) + v * (391 ±0)` + // Measured: `3141 + n * (907 ±0) + v * (391 ±0)` // Estimated: `456136 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 36_848_619_000 picoseconds. - Weight::from_parts(37_362_442_000, 0) + // Minimum execution time: 42_790_195_000 picoseconds. + Weight::from_parts(42_954_437_000, 0) .saturating_add(Weight::from_parts(0, 456136)) - // Standard Error: 415_031 - .saturating_add(Weight::from_parts(5_204_987, 0).saturating_mul(v.into())) - // Standard Error: 415_031 - .saturating_add(Weight::from_parts(4_132_636, 0).saturating_mul(n.into())) + // Standard Error: 478_107 + .saturating_add(Weight::from_parts(6_744_044, 0).saturating_mul(v.into())) + // Standard Error: 478_107 + .saturating_add(Weight::from_parts(4_837_739, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(179)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -692,13 +694,13 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `946 + v * (50 ±0)` + // Measured: `979 + v * (50 ±0)` // Estimated: `3510 + v * (2520 ±0)` - // Minimum execution time: 2_512_817_000 picoseconds. - Weight::from_parts(119_401_374, 0) + // Minimum execution time: 2_851_801_000 picoseconds. + Weight::from_parts(4_477_533, 0) .saturating_add(Weight::from_parts(0, 3510)) - // Standard Error: 8_463 - .saturating_add(Weight::from_parts(4_860_364, 0).saturating_mul(v.into())) + // Standard Error: 8_644 + .saturating_add(Weight::from_parts(5_811_682, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) .saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into())) @@ -721,8 +723,8 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_686_000 picoseconds. - Weight::from_parts(3_881_000, 0) + // Minimum execution time: 4_250_000 picoseconds. + Weight::from_parts(4_472_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -744,8 +746,8 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_143_000 picoseconds. - Weight::from_parts(3_424_000, 0) + // Minimum execution time: 3_986_000 picoseconds. + Weight::from_parts(4_144_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -773,10 +775,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill_other() -> Weight { // Proof Size summary in bytes: - // Measured: `1870` + // Measured: `1903` // Estimated: `6248` - // Minimum execution time: 66_946_000 picoseconds. - Weight::from_parts(69_382_000, 0) + // Minimum execution time: 87_291_000 picoseconds. + Weight::from_parts(89_344_000, 0) .saturating_add(Weight::from_parts(0, 6248)) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(6)) @@ -787,10 +789,10 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) fn force_apply_min_commission() -> Weight { // Proof Size summary in bytes: - // Measured: `658` + // Measured: `691` // Estimated: `3510` - // Minimum execution time: 11_278_000 picoseconds. - Weight::from_parts(11_603_000, 0) + // Minimum execution time: 16_113_000 picoseconds. + Weight::from_parts(16_593_000, 0) .saturating_add(Weight::from_parts(0, 3510)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -801,29 +803,53 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_963_000 picoseconds. - Weight::from_parts(2_077_000, 0) + // Minimum execution time: 2_433_000 picoseconds. + Weight::from_parts(2_561_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:0) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + fn restore_ledger() -> Weight { + // Proof Size summary in bytes: + // Measured: `1040` + // Estimated: `4764` + // Minimum execution time: 50_167_000 picoseconds. + Weight::from_parts(51_108_000, 0) + .saturating_add(Weight::from_parts(0, 4764)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) - fn restore_ledger() -> Weight { + fn migrate_currency() -> Weight { // Proof Size summary in bytes: - // Measured: `1014` + // Measured: `1209` // Estimated: `4764` - // Minimum execution time: 40_258_000 picoseconds. - Weight::from_parts(41_210_000, 0) + // Minimum execution time: 91_790_000 picoseconds. + Weight::from_parts(92_991_000, 0) .saturating_add(Weight::from_parts(0, 4764)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } } diff --git a/polkadot/runtime/westend/src/weights/pallet_xcm.rs b/polkadot/runtime/westend/src/weights/pallet_xcm.rs index 10725cecf24995e34b3254baa23535cb91dd9981..e2c0232139fb851bf991f5acd79a98540858ea43 100644 --- a/polkadot/runtime/westend/src/weights/pallet_xcm.rs +++ b/polkadot/runtime/westend/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `3a528d69c69e`, 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=* +// --chain=westend-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=westend-dev -// --header=./polkadot/file_header.txt -// --output=./polkadot/runtime/westend/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,38 +56,46 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// 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: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `147` - // Estimated: `3612` - // Minimum execution time: 25_725_000 picoseconds. - Weight::from_parts(26_174_000, 0) - .saturating_add(Weight::from_parts(0, 3612)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `212` + // Estimated: `3677` + // Minimum execution time: 41_425_000 picoseconds. + Weight::from_parts(43_275_000, 0) + .saturating_add(Weight::from_parts(0, 3677)) + .saturating_add(T::DbWeight::get().reads(5)) .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: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `250` + // Measured: `315` // Estimated: `6196` - // Minimum execution time: 113_140_000 picoseconds. - Weight::from_parts(116_204_000, 0) + // Minimum execution time: 145_227_000 picoseconds. + Weight::from_parts(151_656_000, 0) .saturating_add(Weight::from_parts(0, 6196)) - .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(4)) } + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`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: `Dmp::DeliveryFeeFactor` (r:1 w:0) @@ -94,47 +104,54 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// 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: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `302` + // Measured: `367` // Estimated: `6196` - // Minimum execution time: 108_571_000 picoseconds. - Weight::from_parts(110_650_000, 0) + // Minimum execution time: 141_439_000 picoseconds. + Weight::from_parts(146_252_000, 0) .saturating_add(Weight::from_parts(0, 6196)) - .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `250` + // Measured: `315` // Estimated: `6196` - // Minimum execution time: 111_836_000 picoseconds. - Weight::from_parts(114_435_000, 0) + // Minimum execution time: 146_651_000 picoseconds. + Weight::from_parts(150_134_000, 0) .saturating_add(Weight::from_parts(0, 6196)) - .saturating_add(T::DbWeight::get().reads(6)) + .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`) + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Estimated: `1485` + // Minimum execution time: 9_663_000 picoseconds. + Weight::from_parts(10_012_000, 0) + .saturating_add(Weight::from_parts(0, 1485)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `XcmPallet::SupportedVersion` (r:0 w:1) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -142,8 +159,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_160_000 picoseconds. - Weight::from_parts(7_477_000, 0) + // Minimum execution time: 8_113_000 picoseconds. + Weight::from_parts(8_469_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -151,8 +168,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_934_000 picoseconds. - Weight::from_parts(2_053_000, 0) + // Minimum execution time: 2_493_000 picoseconds. + Weight::from_parts(2_630_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `XcmPallet::VersionNotifiers` (r:1 w:1) @@ -165,18 +182,20 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// 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: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::Queries` (r:0 w:1) /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_subscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `147` - // Estimated: `3612` - // Minimum execution time: 31_123_000 picoseconds. - Weight::from_parts(31_798_000, 0) - .saturating_add(Weight::from_parts(0, 3612)) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `212` + // Estimated: `3677` + // Minimum execution time: 47_890_000 picoseconds. + Weight::from_parts(49_994_000, 0) + .saturating_add(Weight::from_parts(0, 3677)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `XcmPallet::VersionNotifiers` (r:1 w:1) @@ -187,18 +206,20 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// 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: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::Queries` (r:0 w:1) /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_unsubscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `327` - // Estimated: `3792` - // Minimum execution time: 35_175_000 picoseconds. - Weight::from_parts(36_098_000, 0) - .saturating_add(Weight::from_parts(0, 3792)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `392` + // Estimated: `3857` + // Minimum execution time: 52_967_000 picoseconds. + Weight::from_parts(55_345_000, 0) + .saturating_add(Weight::from_parts(0, 3857)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `XcmPallet::XcmExecutionSuspended` (r:0 w:1) @@ -207,45 +228,45 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_974_000 picoseconds. - Weight::from_parts(2_096_000, 0) + // Minimum execution time: 2_451_000 picoseconds. + Weight::from_parts(2_623_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `XcmPallet::SupportedVersion` (r:5 w:2) + /// Storage: `XcmPallet::SupportedVersion` (r:6 w:2) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `22` - // Estimated: `13387` - // Minimum execution time: 16_626_000 picoseconds. - Weight::from_parts(17_170_000, 0) - .saturating_add(Weight::from_parts(0, 13387)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15862` + // Minimum execution time: 22_292_000 picoseconds. + Weight::from_parts(22_860_000, 0) + .saturating_add(Weight::from_parts(0, 15862)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `XcmPallet::VersionNotifiers` (r:5 w:2) + /// Storage: `XcmPallet::VersionNotifiers` (r:6 w:2) /// Proof: `XcmPallet::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `26` - // Estimated: `13391` - // Minimum execution time: 16_937_000 picoseconds. - Weight::from_parts(17_447_000, 0) - .saturating_add(Weight::from_parts(0, 13391)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15866` + // Minimum execution time: 21_847_000 picoseconds. + Weight::from_parts(22_419_000, 0) + .saturating_add(Weight::from_parts(0, 15866)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `XcmPallet::VersionNotifyTargets` (r:6 w:0) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:7 w:0) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `40` - // Estimated: `15880` - // Minimum execution time: 19_157_000 picoseconds. - Weight::from_parts(19_659_000, 0) - .saturating_add(Weight::from_parts(0, 15880)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18355` + // Minimum execution time: 24_764_000 picoseconds. + Weight::from_parts(25_873_000, 0) + .saturating_add(Weight::from_parts(0, 18355)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `XcmPallet::VersionNotifyTargets` (r:2 w:1) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -253,62 +274,62 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// 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) + /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:0) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) - /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_current_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `183` - // Estimated: `6123` - // Minimum execution time: 30_699_000 picoseconds. - Weight::from_parts(31_537_000, 0) - .saturating_add(Weight::from_parts(0, 6123)) + // Measured: `211` + // Estimated: `6151` + // Minimum execution time: 36_482_000 picoseconds. + Weight::from_parts(37_672_000, 0) + .saturating_add(Weight::from_parts(0, 6151)) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `XcmPallet::VersionNotifyTargets` (r:4 w:0) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:5 w:0) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `69` - // Estimated: `10959` - // Minimum execution time: 12_303_000 picoseconds. - Weight::from_parts(12_670_000, 0) - .saturating_add(Weight::from_parts(0, 10959)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `40` + // Estimated: `13405` + // Minimum execution time: 17_580_000 picoseconds. + Weight::from_parts(17_908_000, 0) + .saturating_add(Weight::from_parts(0, 13405)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `XcmPallet::VersionNotifyTargets` (r:5 w:2) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:6 w:2) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `33` - // Estimated: `13398` - // Minimum execution time: 17_129_000 picoseconds. - Weight::from_parts(17_668_000, 0) - .saturating_add(Weight::from_parts(0, 13398)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15873` + // Minimum execution time: 21_946_000 picoseconds. + Weight::from_parts(22_548_000, 0) + .saturating_add(Weight::from_parts(0, 15873)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `XcmPallet::VersionNotifyTargets` (r:5 w:2) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:6 w:1) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) + /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:0) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) - /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `183` - // Estimated: `13548` - // Minimum execution time: 39_960_000 picoseconds. - Weight::from_parts(41_068_000, 0) - .saturating_add(Weight::from_parts(0, 13548)) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `211` + // Estimated: `16051` + // Minimum execution time: 47_261_000 picoseconds. + Weight::from_parts(48_970_000, 0) + .saturating_add(Weight::from_parts(0, 16051)) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `XcmPallet::QueryCounter` (r:1 w:1) /// Proof: `XcmPallet::QueryCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -318,8 +339,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `1485` - // Minimum execution time: 2_333_000 picoseconds. - Weight::from_parts(2_504_000, 0) + // Minimum execution time: 2_794_000 picoseconds. + Weight::from_parts(2_895_000, 0) .saturating_add(Weight::from_parts(0, 1485)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -330,22 +351,24 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `7576` // Estimated: `11041` - // Minimum execution time: 22_932_000 picoseconds. - Weight::from_parts(23_307_000, 0) + // Minimum execution time: 25_946_000 picoseconds. + Weight::from_parts(26_503_000, 0) .saturating_add(Weight::from_parts(0, 11041)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::AssetTraps` (r:1 w:1) /// Proof: `XcmPallet::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `23` // Estimated: `3488` - // Minimum execution time: 34_558_000 picoseconds. - Weight::from_parts(35_299_000, 0) + // Minimum execution time: 40_780_000 picoseconds. + Weight::from_parts(41_910_000, 0) .saturating_add(Weight::from_parts(0, 3488)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_disputes_slashing.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_disputes_slashing.rs index a035ea2b0b5e2e226a1a2a36f0a11e570c56839f..f4dbca0f29ffc2eab7bd4802ec83eef7caa56e2f 100644 --- a/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_disputes_slashing.rs +++ b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_disputes_slashing.rs @@ -85,7 +85,7 @@ impl<T: frame_system::Config> polkadot_runtime_parachains::disputes::slashing::W /// Storage: Staking UnappliedSlashes (r:1 w:1) /// Proof Skipped: Staking UnappliedSlashes (max_values: None, max_size: None, mode: Measured) /// The range of component `n` is `[4, 300]`. - fn report_dispute_lost(n: u32, ) -> Weight { + fn report_dispute_lost_unsigned(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `4531 + n * (189 ±0)` // Estimated: `7843 + n * (192 ±0)` diff --git a/polkadot/runtime/westend/src/weights/xcm/mod.rs b/polkadot/runtime/westend/src/weights/xcm/mod.rs index 5be9bad824da38ad0d6b767db0ae9131a5bba673..a5fb82a668371f73d653fdf7a964549d3ccfc2db 100644 --- a/polkadot/runtime/westend/src/weights/xcm/mod.rs +++ b/polkadot/runtime/westend/src/weights/xcm/mod.rs @@ -27,6 +27,7 @@ use xcm::{ use pallet_xcm_benchmarks_fungible::WeightInfo as XcmBalancesWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::latest::AssetTransferFilter; /// Types of asset supported by the westend runtime. @@ -114,7 +115,11 @@ impl<RuntimeCall> XcmWeightInfo<RuntimeCall> for WestendXcmWeight<RuntimeCall> { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmBalancesWeight::<Runtime>::transfer_reserve_asset()) } - fn transact(_origin_kind: &OriginKind, _call: &DoubleEncoded<RuntimeCall>) -> Weight { + fn transact( + _origin_kind: &OriginKind, + _fallback_max_weight: &Option<Weight>, + _call: &DoubleEncoded<RuntimeCall>, + ) -> Weight { XcmGeneric::<Runtime>::transact() } fn hrmp_new_channel_open_request( @@ -204,11 +209,17 @@ impl<RuntimeCall> XcmWeightInfo<RuntimeCall> for WestendXcmWeight<RuntimeCall> { fn clear_error() -> Weight { XcmGeneric::<Runtime>::clear_error() } - - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::<Runtime>::set_asset_claimer() + fn set_hints(hints: &BoundedVec<Hint, HintNumVariants>) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::<Runtime>::asset_claimer()); + }, + } + } + weight } - fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::<Runtime>::claim_asset() } @@ -288,8 +299,7 @@ impl<RuntimeCall> XcmWeightInfo<RuntimeCall> for WestendXcmWeight<RuntimeCall> { XcmGeneric::<Runtime>::clear_topic() } fn alias_origin(_: &Location) -> Weight { - // XCM Executor does not currently support alias origin operations - Weight::MAX + XcmGeneric::<Runtime>::alias_origin() } fn unpaid_execution(_: &WeightLimit, _: &Option<Location>) -> Weight { XcmGeneric::<Runtime>::unpaid_execution() diff --git a/polkadot/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/polkadot/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 076744a59753ab3d98fe2e7762708b6c43136769..4e10e72356ab08e9c60ca6fd910201b456721473 100644 --- a/polkadot/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/polkadot/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -17,26 +17,28 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-11-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-vcatxqpx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `aa8403b52523`, 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=* +// --chain=westend-dev +// --pallet=pallet_xcm_benchmarks::generic +// --header=/__w/polkadot-sdk/polkadot-sdk/polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights/xcm // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm_benchmarks::generic -// --chain=westend-dev -// --header=./polkadot/file_header.txt -// --template=./polkadot/xcm/pallet-xcm-benchmarks/template.hbs -// --output=./polkadot/runtime/westend/src/weights/xcm/ +// --template=polkadot/xcm/pallet-xcm-benchmarks/template.hbs +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -63,8 +65,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `351` // Estimated: `6196` - // Minimum execution time: 69_051_000 picoseconds. - Weight::from_parts(71_282_000, 6196) + // Minimum execution time: 74_868_000 picoseconds. + Weight::from_parts(77_531_000, 6196) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -72,22 +74,22 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 660_000 picoseconds. - Weight::from_parts(695_000, 0) + // Minimum execution time: 688_000 picoseconds. + Weight::from_parts(733_000, 0) } pub(crate) fn pay_fees() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_096_000 picoseconds. - Weight::from_parts(3_313_000, 0) + // Minimum execution time: 3_491_000 picoseconds. + Weight::from_parts(3_667_000, 0) } - pub(crate) fn set_asset_claimer() -> Weight { + pub(crate) fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 661_000 picoseconds. - Weight::from_parts(707_000, 0) + // Minimum execution time: 757_000 picoseconds. + Weight::from_parts(804_000, 0) } /// Storage: `XcmPallet::Queries` (r:1 w:0) /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -95,65 +97,65 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3465` - // Minimum execution time: 6_054_000 picoseconds. - Weight::from_parts(6_151_000, 3465) + // Minimum execution time: 6_322_000 picoseconds. + Weight::from_parts(6_565_000, 3465) .saturating_add(T::DbWeight::get().reads(1)) } pub(crate) fn transact() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_462_000 picoseconds. - Weight::from_parts(7_750_000, 0) + // Minimum execution time: 7_841_000 picoseconds. + Weight::from_parts(8_240_000, 0) } pub(crate) fn refund_surplus() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_378_000 picoseconds. - Weight::from_parts(1_454_000, 0) + // Minimum execution time: 1_327_000 picoseconds. + Weight::from_parts(1_460_000, 0) } pub(crate) fn set_error_handler() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 660_000 picoseconds. - Weight::from_parts(744_000, 0) + // Minimum execution time: 680_000 picoseconds. + Weight::from_parts(752_000, 0) } pub(crate) fn set_appendix() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 713_000 picoseconds. - Weight::from_parts(755_000, 0) + // Minimum execution time: 712_000 picoseconds. + Weight::from_parts(764_000, 0) } pub(crate) fn clear_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 632_000 picoseconds. - Weight::from_parts(703_000, 0) + // Minimum execution time: 663_000 picoseconds. + Weight::from_parts(712_000, 0) } pub(crate) fn descend_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 712_000 picoseconds. - Weight::from_parts(771_000, 0) + // Minimum execution time: 756_000 picoseconds. + Weight::from_parts(801_000, 0) } pub(crate) fn execute_with_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 740_000 picoseconds. - Weight::from_parts(826_000, 0) + // Minimum execution time: 773_000 picoseconds. + Weight::from_parts(822_000, 0) } pub(crate) fn clear_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 653_000 picoseconds. - Weight::from_parts(707_000, 0) + // Minimum execution time: 669_000 picoseconds. + Weight::from_parts(750_000, 0) } /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -169,8 +171,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `351` // Estimated: `6196` - // Minimum execution time: 66_765_000 picoseconds. - Weight::from_parts(69_016_000, 6196) + // Minimum execution time: 73_173_000 picoseconds. + Weight::from_parts(75_569_000, 6196) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -180,8 +182,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `23` // Estimated: `3488` - // Minimum execution time: 9_545_000 picoseconds. - Weight::from_parts(9_853_000, 3488) + // Minimum execution time: 9_851_000 picoseconds. + Weight::from_parts(10_087_000, 3488) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -189,8 +191,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 676_000 picoseconds. - Weight::from_parts(723_000, 0) + // Minimum execution time: 673_000 picoseconds. + Weight::from_parts(744_000, 0) } /// Storage: `XcmPallet::VersionNotifyTargets` (r:1 w:1) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -206,8 +208,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `147` // Estimated: `3612` - // Minimum execution time: 31_324_000 picoseconds. - Weight::from_parts(32_023_000, 3612) + // Minimum execution time: 35_714_000 picoseconds. + Weight::from_parts(36_987_000, 3612) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -217,44 +219,44 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_058_000 picoseconds. - Weight::from_parts(3_199_000, 0) + // Minimum execution time: 3_128_000 picoseconds. + Weight::from_parts(3_364_000, 0) .saturating_add(T::DbWeight::get().writes(1)) } pub(crate) fn burn_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 994_000 picoseconds. - Weight::from_parts(1_115_000, 0) + // Minimum execution time: 1_070_000 picoseconds. + Weight::from_parts(1_188_000, 0) } pub(crate) fn expect_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 763_000 picoseconds. - Weight::from_parts(824_000, 0) + // Minimum execution time: 764_000 picoseconds. + Weight::from_parts(863_000, 0) } pub(crate) fn expect_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 665_000 picoseconds. - Weight::from_parts(712_000, 0) + // Minimum execution time: 675_000 picoseconds. + Weight::from_parts(755_000, 0) } pub(crate) fn expect_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 627_000 picoseconds. - Weight::from_parts(695_000, 0) + // Minimum execution time: 666_000 picoseconds. + Weight::from_parts(745_000, 0) } pub(crate) fn expect_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 839_000 picoseconds. - Weight::from_parts(889_000, 0) + // Minimum execution time: 838_000 picoseconds. + Weight::from_parts(918_000, 0) } /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -270,8 +272,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `351` // Estimated: `6196` - // Minimum execution time: 75_853_000 picoseconds. - Weight::from_parts(77_515_000, 6196) + // Minimum execution time: 82_721_000 picoseconds. + Weight::from_parts(85_411_000, 6196) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -279,8 +281,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_183_000 picoseconds. - Weight::from_parts(8_378_000, 0) + // Minimum execution time: 8_138_000 picoseconds. + Weight::from_parts(8_344_000, 0) } /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -296,8 +298,8 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `351` // Estimated: `6196` - // Minimum execution time: 66_576_000 picoseconds. - Weight::from_parts(69_465_000, 6196) + // Minimum execution time: 73_617_000 picoseconds. + Weight::from_parts(76_999_000, 6196) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -305,35 +307,42 @@ impl<T: frame_system::Config> WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 739_000 picoseconds. - Weight::from_parts(773_000, 0) + // Minimum execution time: 714_000 picoseconds. + Weight::from_parts(806_000, 0) } pub(crate) fn set_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 648_000 picoseconds. - Weight::from_parts(693_000, 0) + // Minimum execution time: 676_000 picoseconds. + Weight::from_parts(720_000, 0) } pub(crate) fn clear_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 654_000 picoseconds. - Weight::from_parts(700_000, 0) + // Minimum execution time: 666_000 picoseconds. + Weight::from_parts(731_000, 0) } pub(crate) fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 646_000 picoseconds. - Weight::from_parts(702_000, 0) + // Minimum execution time: 662_000 picoseconds. + Weight::from_parts(696_000, 0) } pub(crate) fn unpaid_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 665_000 picoseconds. - Weight::from_parts(714_000, 0) + // Minimum execution time: 693_000 picoseconds. + Weight::from_parts(760_000, 0) + } + pub(crate) fn alias_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 705_000 picoseconds. + Weight::from_parts(746_000, 0) } } diff --git a/polkadot/runtime/westend/src/xcm_config.rs b/polkadot/runtime/westend/src/xcm_config.rs index f8bb2676de3f9a884a6a0873879f2ddf175453f1..4235edf82b24de71f82c21dda1bd69c231048ac9 100644 --- a/polkadot/runtime/westend/src/xcm_config.rs +++ b/polkadot/runtime/westend/src/xcm_config.rs @@ -38,13 +38,14 @@ use westend_runtime_constants::{ }; use xcm::latest::{prelude::*, WESTEND_GENESIS_HASH}; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, ChildParachainAsNative, - ChildParachainConvertsVia, DescribeAllTerminal, DescribeFamily, FrameTransactionalProcessor, - FungibleAdapter, HashedDescription, IsChildSystemParachain, IsConcrete, MintLocation, - OriginToPluralityVoice, SendXcmFeeToAccount, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + AccountId32Aliases, AliasChildLocation, AllowExplicitUnpaidExecutionFrom, + AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, + ChildParachainAsNative, ChildParachainConvertsVia, DescribeAllTerminal, DescribeFamily, + FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsChildSystemParachain, + IsConcrete, MintLocation, OriginToPluralityVoice, SendXcmFeeToAccount, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, }; use xcm_executor::XcmExecutor; @@ -183,6 +184,11 @@ pub type Barrier = TrailingSetTopicAsId<( /// We only waive fees for system functions, which these locations represent. pub type WaivedLocations = (SystemParachains, Equals<RootLocation>, LocalPlurality); +/// We let locations alias into child locations of their own. +/// This is a very simple aliasing rule, mimicking the behaviour of +/// the `DescendOrigin` instruction. +pub type Aliasers = AliasChildLocation; + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -216,7 +222,7 @@ impl xcm_executor::Config for XcmConfig { type UniversalAliases = Nothing; type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; - type Aliasers = Nothing; + type Aliasers = Aliasers; type TransactionalProcessor = FrameTransactionalProcessor; type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); @@ -274,7 +280,10 @@ impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; // Note that this configuration of `SendXcmOrigin` is different from the one present in // production. - type SendXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>; + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin< + RuntimeOrigin, + (LocalPalletOriginToLocation, LocalOriginToLocation), + >; type XcmRouter = XcmRouter; // Anyone can execute XCM messages locally. type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>; diff --git a/polkadot/statement-table/Cargo.toml b/polkadot/statement-table/Cargo.toml index 53ea0b74463bc2466df2ff37f190f429d9b2973a..1155600f3d0ce90c85d1ead8d16a773a4164a154 100644 --- a/polkadot/statement-table/Cargo.toml +++ b/polkadot/statement-table/Cargo.toml @@ -5,12 +5,14 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Stores messages other authorities issue about candidates in Polkadot." +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] codec = { features = ["derive"], workspace = true } -sp-core = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } gum = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } diff --git a/polkadot/utils/generate-bags/Cargo.toml b/polkadot/utils/generate-bags/Cargo.toml index 16205b0f51f57a62b923eff0952a45795c6cb3b6..3006d8325ef96f181a5bc17ef7918961d1f62771 100644 --- a/polkadot/utils/generate-bags/Cargo.toml +++ b/polkadot/utils/generate-bags/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "CLI to generate voter bags for Polkadot runtimes" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml b/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml index 206ca8cf19a90df9bc2becd0c65039f35d714191..1a6c23e0518eb2cd703d427da478f4601daf6e4e 100644 --- a/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml +++ b/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml @@ -13,10 +13,10 @@ workspace = true westend-runtime = { workspace = true } westend-runtime-constants = { workspace = true, default-features = true } -pallet-bags-list-remote-tests = { workspace = true } -sp-tracing = { workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } +pallet-bags-list-remote-tests = { workspace = true } sp-core = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } clap = { features = ["derive"], workspace = true } log = { workspace = true, default-features = true } diff --git a/polkadot/xcm/Cargo.toml b/polkadot/xcm/Cargo.toml index 86c7067ad6fa8d9eac63df07a64a2a5e8488cdbe..f5f824ee409f0b12d05a822f2970d1c92b3c7649 100644 --- a/polkadot/xcm/Cargo.toml +++ b/polkadot/xcm/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -12,23 +14,23 @@ workspace = true [dependencies] array-bytes = { workspace = true, default-features = true } bounded-collections = { features = ["serde"], workspace = true } -derivative = { features = ["use_core"], workspace = true } +codec = { features = ["derive", "max-encoded-len"], workspace = true } +derive-where = { workspace = true } +environmental = { workspace = true } +frame-support = { workspace = true } +hex-literal = { workspace = true, default-features = true } impl-trait-for-tuples = { workspace = true } log = { workspace = true } -codec = { features = ["derive", "max-encoded-len"], workspace = true } scale-info = { features = ["derive", "serde"], workspace = true } +schemars = { default-features = true, optional = true, workspace = true } +serde = { features = ["alloc", "derive", "rc"], workspace = true } sp-runtime = { workspace = true } sp-weights = { features = ["serde"], workspace = true } -serde = { features = ["alloc", "derive", "rc"], workspace = true } -schemars = { default-features = true, optional = true, workspace = true } xcm-procedural = { workspace = true, default-features = true } -environmental = { workspace = true } -hex-literal = { workspace = true, default-features = true } -frame-support = { workspace = true } [dev-dependencies] -sp-io = { workspace = true, default-features = true } hex = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } [features] default = ["std"] @@ -49,3 +51,7 @@ json-schema = [ "dep:schemars", "sp-weights/json-schema", ] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] diff --git a/polkadot/xcm/docs/Cargo.toml b/polkadot/xcm/docs/Cargo.toml index 9d8f4c0a6430b71e2b2d1ba09719a8c397b4e4d5..c3bda50619c1572202db2a3334bc14d4d11c6b4b 100644 --- a/polkadot/xcm/docs/Cargo.toml +++ b/polkadot/xcm/docs/Cargo.toml @@ -10,30 +10,30 @@ publish = false [dependencies] # For XCM stuff +pallet-xcm = { workspace = true, default-features = true } xcm = { workspace = true, default-features = true } -xcm-executor = { workspace = true, default-features = true } xcm-builder = { workspace = true, default-features = true } +xcm-executor = { workspace = true, default-features = true } xcm-simulator = { workspace = true, default-features = true } -pallet-xcm = { workspace = true, default-features = true } # For building FRAME runtimes -frame = { features = ["experimental", "runtime"], workspace = true, default-features = true } codec = { workspace = true, default-features = true } -scale-info = { workspace = true } +frame = { features = ["runtime"], workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } -polkadot-runtime-parachains = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } +polkadot-runtime-parachains = { workspace = true, default-features = true } +scale-info = { workspace = true } +sp-io = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-std = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } # Some pallets -pallet-message-queue = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } +pallet-message-queue = { workspace = true, default-features = true } # For building docs -simple-mermaid = { git = "https://github.com/kianenigma/simple-mermaid.git", branch = "main" } docify = { workspace = true } +simple-mermaid = { git = "https://github.com/kianenigma/simple-mermaid.git", branch = "main" } [dev-dependencies] test-log = { workspace = true } diff --git a/polkadot/xcm/docs/src/cookbook/relay_token_transactor/network.rs b/polkadot/xcm/docs/src/cookbook/relay_token_transactor/network.rs index 46ac0e5df6372babf84b3494436e554353ba2820..71c14f6b241b6f68e501777457f87dd79716f602 100644 --- a/polkadot/xcm/docs/src/cookbook/relay_token_transactor/network.rs +++ b/polkadot/xcm/docs/src/cookbook/relay_token_transactor/network.rs @@ -78,9 +78,12 @@ pub fn relay_ext() -> TestExternalities { let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap(); - pallet_balances::GenesisConfig::<Runtime> { balances: vec![(ALICE, INITIAL_BALANCE)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig::<Runtime> { + balances: vec![(ALICE, INITIAL_BALANCE)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); let mut ext = TestExternalities::new(t); ext.execute_with(|| { diff --git a/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml b/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml index b07bdfdca3d196095a45370a9b2d622dd2a1636a..5d5926ae01e0d9d2f9671892607a49246a20656e 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml +++ b/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml @@ -5,6 +5,8 @@ edition.workspace = true license.workspace = true version = "7.0.0" description = "Benchmarks for the XCM pallet" +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -14,20 +16,20 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } +frame-benchmarking = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -sp-runtime = { workspace = true } +log = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } -xcm-executor = { workspace = true } -frame-benchmarking = { workspace = true } +sp-runtime = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } -log = { workspace = true, default-features = true } +xcm-executor = { workspace = true } [dev-dependencies] -pallet-balances = { workspace = true, default-features = true } pallet-assets = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } xcm = { workspace = true, default-features = true } # temp @@ -62,4 +64,5 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs index 303ff9493f71b6a6411427146d93774209850e5c..4428076aa07715083bf62876d5c899d07228d249 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs @@ -231,6 +231,13 @@ benchmarks_instance_pallet! { let dest_account = T::AccountIdConverter::convert_location(&dest_location).unwrap(); assert!(T::TransactAsset::balance(&dest_account).is_zero()); + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + let (_, _) = T::DeliveryHelper::ensure_successful_delivery( + &Default::default(), + &dest_location, + FeeReason::ChargeFees, + ); + let mut executor = new_executor::<T>(Default::default()); executor.set_holding(holding.into()); let instruction = Instruction::<XcmCallOf<T>>::DepositAsset { @@ -257,6 +264,13 @@ benchmarks_instance_pallet! { let dest_account = T::AccountIdConverter::convert_location(&dest_location).unwrap(); assert!(T::TransactAsset::balance(&dest_account).is_zero()); + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + let (_, _) = T::DeliveryHelper::ensure_successful_delivery( + &Default::default(), + &dest_location, + FeeReason::ChargeFees, + ); + let mut executor = new_executor::<T>(Default::default()); executor.set_holding(holding.into()); let instruction = Instruction::<XcmCallOf<T>>::DepositReserveAsset { @@ -281,12 +295,20 @@ benchmarks_instance_pallet! { // Checked account starts at zero assert!(T::CheckedAccount::get().map_or(true, |(c, _)| T::TransactAsset::balance(&c).is_zero())); + let dest_location = T::valid_destination()?; + + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + let (_, _) = T::DeliveryHelper::ensure_successful_delivery( + &Default::default(), + &dest_location, + FeeReason::ChargeFees, + ); let mut executor = new_executor::<T>(Default::default()); executor.set_holding(holding.into()); let instruction = Instruction::<XcmCallOf<T>>::InitiateTeleport { assets: asset.into(), - dest: T::valid_destination()?, + dest: dest_location, xcm: Xcm::new(), }; let xcm = Xcm(vec![instruction]); @@ -303,6 +325,15 @@ benchmarks_instance_pallet! { let (sender_account, sender_location) = account_and_location::<T>(1); let asset = T::get_asset(); let mut holding = T::worst_case_holding(1); + let dest_location = T::valid_destination()?; + + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + let (_, _) = T::DeliveryHelper::ensure_successful_delivery( + &sender_location, + &dest_location, + FeeReason::ChargeFees, + ); + let sender_account_balance_before = T::TransactAsset::balance(&sender_account); // Add our asset to the holding. @@ -311,7 +342,7 @@ benchmarks_instance_pallet! { let mut executor = new_executor::<T>(sender_location); executor.set_holding(holding.into()); let instruction = Instruction::<XcmCallOf<T>>::InitiateTransfer { - destination: T::valid_destination()?, + destination: dest_location, // ReserveDeposit is the most expensive filter. remote_fees: Some(AssetTransferFilter::ReserveDeposit(asset.clone().into())), // It's more expensive if we reanchor the origin. diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs index 87bf27e4ff1853978a78701e20be8a47a9471399..1c62bb5886d80c4e2d0a59afe63a32cef87336d4 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs @@ -13,13 +13,14 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see <http://www.gnu.org/licenses/>. +#![cfg(feature = "runtime-benchmarks")] use super::*; use crate::{account_and_location, new_executor, EnsureDelivery, XcmCallOf}; use alloc::{vec, vec::Vec}; use codec::Encode; -use frame_benchmarking::{benchmarks, BenchmarkError}; -use frame_support::traits::fungible::Inspect; +use frame_benchmarking::v2::*; +use frame_support::{traits::fungible::Inspect, BoundedVec}; use xcm::{ latest::{prelude::*, MaxDispatchErrorLen, MaybeErrorCode, Weight, MAX_ITEMS_IN_ASSETS}, DoubleEncoded, @@ -29,16 +30,21 @@ use xcm_executor::{ ExecutorError, FeesMode, }; -benchmarks! { - report_holding { +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn report_holding() -> Result<(), BenchmarkError> { let (sender_account, sender_location) = account_and_location::<T>(1); let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?; - let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( - &sender_location, - &destination, - FeeReason::Report, - ); + let (expected_fees_mode, expected_assets_in_holding) = + T::DeliveryHelper::ensure_successful_delivery( + &sender_location, + &destination, + FeeReason::Report, + ); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); // generate holding and add possible required fees @@ -64,21 +70,33 @@ benchmarks! { query_id: Default::default(), max_weight: Weight::MAX, }, - // Worst case is looking through all holdings for every asset explicitly - respecting the limit `MAX_ITEMS_IN_ASSETS`. - assets: Definite(holding.into_inner().into_iter().take(MAX_ITEMS_IN_ASSETS).collect::<Vec<_>>().into()), + // Worst case is looking through all holdings for every asset explicitly - respecting + // the limit `MAX_ITEMS_IN_ASSETS`. + assets: Definite( + holding + .into_inner() + .into_iter() + .take(MAX_ITEMS_IN_ASSETS) + .collect::<Vec<_>>() + .into(), + ), }; let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // Check we charged the delivery fees assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before); + + Ok(()) } // This benchmark does not use any additional orders or instructions. This should be managed // by the `deep` and `shallow` implementation. - buy_execution { + #[benchmark] + fn buy_execution() -> Result<(), BenchmarkError> { let holding = T::worst_case_holding(0).into(); let mut executor = new_executor::<T>(Default::default()); @@ -92,13 +110,16 @@ benchmarks! { }; let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } + Ok(()) } - pay_fees { + #[benchmark] + fn pay_fees() -> Result<(), BenchmarkError> { let holding = T::worst_case_holding(0).into(); let mut executor = new_executor::<T>(Default::default()); @@ -111,40 +132,57 @@ benchmarks! { let instruction = Instruction::<XcmCallOf<T>>::PayFees { asset: fee_asset }; let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify {} + #[block] + { + executor.bench_process(xcm)?; + } + Ok(()) + } - set_asset_claimer { + #[benchmark] + fn asset_claimer() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); let (_, sender_location) = account_and_location::<T>(1); - let instruction = Instruction::SetAssetClaimer{ location:sender_location.clone() }; + let instruction = Instruction::SetHints { + hints: BoundedVec::<Hint, HintNumVariants>::truncate_from(vec![AssetClaimer { + location: sender_location.clone(), + }]), + }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.asset_claimer(), Some(sender_location.clone())); + + Ok(()) } - query_response { + #[benchmark] + fn query_response() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); let (query_id, response) = T::worst_case_response(); let max_weight = Weight::MAX; let querier: Option<Location> = Some(Here.into()); let instruction = Instruction::QueryResponse { query_id, response, max_weight, querier }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + + #[block] + { + executor.bench_process(xcm)?; + } // The assert above is enough to show this XCM succeeded + + Ok(()) } // We don't care about the call itself, since that is accounted for in the weight parameter // and included in the final weight calculation. So this is just the overhead of submitting // a noop call. - transact { + #[benchmark] + fn transact() -> Result<(), BenchmarkError> { let (origin, noop_call) = T::transact_origin_and_runtime_call()?; let mut executor = new_executor::<T>(origin); let double_encoded_noop_call: DoubleEncoded<_> = noop_call.encode().into(); @@ -152,121 +190,148 @@ benchmarks! { let instruction = Instruction::Transact { origin_kind: OriginKind::SovereignAccount, call: double_encoded_noop_call, + fallback_max_weight: None, }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // TODO Make the assertion configurable? + + Ok(()) } - refund_surplus { + #[benchmark] + fn refund_surplus() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); let holding_assets = T::worst_case_holding(1); // We can already buy execution since we'll load the holding register manually let asset_for_fees = T::fee_asset().unwrap(); - let previous_xcm = Xcm(vec![BuyExecution { fees: asset_for_fees, weight_limit: Limited(Weight::from_parts(1337, 1337)) }]); + let previous_xcm = Xcm(vec![BuyExecution { + fees: asset_for_fees, + weight_limit: Limited(Weight::from_parts(1337, 1337)), + }]); executor.set_holding(holding_assets.into()); executor.set_total_surplus(Weight::from_parts(1337, 1337)); executor.set_total_refunded(Weight::zero()); - executor.bench_process(previous_xcm).expect("Holding has been loaded, so we can buy execution here"); + executor + .bench_process(previous_xcm) + .expect("Holding has been loaded, so we can buy execution here"); let instruction = Instruction::<XcmCallOf<T>>::RefundSurplus; let xcm = Xcm(vec![instruction]); - } : { - let result = executor.bench_process(xcm)?; - } verify { + #[block] + { + let _result = executor.bench_process(xcm)?; + } assert_eq!(executor.total_surplus(), &Weight::from_parts(1337, 1337)); assert_eq!(executor.total_refunded(), &Weight::from_parts(1337, 1337)); + + Ok(()) } - set_error_handler { + #[benchmark] + fn set_error_handler() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); let instruction = Instruction::<XcmCallOf<T>>::SetErrorHandler(Xcm(vec![])); let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.error_handler(), &Xcm(vec![])); + + Ok(()) } - set_appendix { + #[benchmark] + fn set_appendix() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); let appendix = Xcm(vec![]); let instruction = Instruction::<XcmCallOf<T>>::SetAppendix(appendix); let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.appendix(), &Xcm(vec![])); + Ok(()) } - clear_error { + #[benchmark] + fn clear_error() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); executor.set_error(Some((5u32, XcmError::Overflow))); let instruction = Instruction::<XcmCallOf<T>>::ClearError; let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { - assert!(executor.error().is_none()) + #[block] + { + executor.bench_process(xcm)?; + } + assert!(executor.error().is_none()); + Ok(()) } - descend_origin { + #[benchmark] + fn descend_origin() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); let who = Junctions::from([OnlyChild, OnlyChild]); let instruction = Instruction::DescendOrigin(who.clone()); let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { - assert_eq!( - executor.origin(), - &Some(Location { - parents: 0, - interior: who, - }), - ); + #[block] + { + executor.bench_process(xcm)?; + } + assert_eq!(executor.origin(), &Some(Location { parents: 0, interior: who }),); + + Ok(()) } - execute_with_origin { + #[benchmark] + fn execute_with_origin() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); let who: Junctions = Junctions::from([AccountId32 { id: [0u8; 32], network: None }]); - let instruction = Instruction::ExecuteWithOrigin { descendant_origin: Some(who.clone()), xcm: Xcm(vec![]) }; + let instruction = Instruction::ExecuteWithOrigin { + descendant_origin: Some(who.clone()), + xcm: Xcm(vec![]), + }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { - assert_eq!( - executor.origin(), - &Some(Location { - parents: 0, - interior: Here, - }), - ); + #[block] + { + executor.bench_process(xcm)?; + } + assert_eq!(executor.origin(), &Some(Location { parents: 0, interior: Here }),); + + Ok(()) } - clear_origin { + #[benchmark] + fn clear_origin() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); let instruction = Instruction::ClearOrigin; let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.origin(), &None); + Ok(()) } - report_error { + #[benchmark] + fn report_error() -> Result<(), BenchmarkError> { let (sender_account, sender_location) = account_and_location::<T>(1); let query_id = Default::default(); let max_weight = Default::default(); let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?; - let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( - &sender_location, - &destination, - FeeReason::Report, - ); + let (expected_fees_mode, expected_assets_in_holding) = + T::DeliveryHelper::ensure_successful_delivery( + &sender_location, + &destination, + FeeReason::Report, + ); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); let mut executor = new_executor::<T>(sender_location); @@ -278,18 +343,21 @@ benchmarks! { } executor.set_error(Some((0u32, XcmError::Unimplemented))); - let instruction = Instruction::ReportError(QueryResponseInfo { - query_id, destination, max_weight - }); + let instruction = + Instruction::ReportError(QueryResponseInfo { query_id, destination, max_weight }); let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // Check we charged the delivery fees assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before); + + Ok(()) } - claim_asset { + #[benchmark] + fn claim_asset() -> Result<(), BenchmarkError> { use xcm_executor::traits::DropAssets; let (origin, ticket, assets) = T::claimable_asset()?; @@ -298,11 +366,7 @@ benchmarks! { <T::XcmConfig as xcm_executor::Config>::AssetTrap::drop_assets( &origin, assets.clone().into(), - &XcmContext { - origin: Some(origin.clone()), - message_id: [0; 32], - topic: None, - }, + &XcmContext { origin: Some(origin.clone()), message_id: [0; 32], topic: None }, ); // Assets should be in the trap now. @@ -310,28 +374,32 @@ benchmarks! { let mut executor = new_executor::<T>(origin); let instruction = Instruction::ClaimAsset { assets: assets.clone(), ticket }; let xcm = Xcm(vec![instruction]); - } :{ - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert!(executor.holding().ensure_contains(&assets).is_ok()); + Ok(()) } - trap { + #[benchmark] + fn trap() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); let instruction = Instruction::Trap(10); let xcm = Xcm(vec![instruction]); // In order to access result in the verification below, it needs to be defined here. - let mut _result = Ok(()); - } : { - _result = executor.bench_process(xcm); - } verify { - assert!(matches!(_result, Err(ExecutorError { - xcm_error: XcmError::Trap(10), - .. - }))); + let result; + #[block] + { + result = executor.bench_process(xcm); + } + assert!(matches!(result, Err(ExecutorError { xcm_error: XcmError::Trap(10), .. }))); + + Ok(()) } - subscribe_version { + #[benchmark] + fn subscribe_version() -> Result<(), BenchmarkError> { use xcm_executor::traits::VersionChangeNotifier; let origin = T::subscribe_origin()?; let query_id = Default::default(); @@ -339,40 +407,55 @@ benchmarks! { let mut executor = new_executor::<T>(origin.clone()); let instruction = Instruction::SubscribeVersion { query_id, max_response_weight }; let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { - assert!(<T::XcmConfig as xcm_executor::Config>::SubscriptionService::is_subscribed(&origin)); + + T::DeliveryHelper::ensure_successful_delivery(&origin, &origin, FeeReason::QueryPallet); + + #[block] + { + executor.bench_process(xcm)?; + } + assert!(<T::XcmConfig as xcm_executor::Config>::SubscriptionService::is_subscribed( + &origin + )); + Ok(()) } - unsubscribe_version { + #[benchmark] + fn unsubscribe_version() -> Result<(), BenchmarkError> { use xcm_executor::traits::VersionChangeNotifier; // First we need to subscribe to notifications. let (origin, _) = T::transact_origin_and_runtime_call()?; + + T::DeliveryHelper::ensure_successful_delivery(&origin, &origin, FeeReason::QueryPallet); + let query_id = Default::default(); let max_response_weight = Default::default(); <T::XcmConfig as xcm_executor::Config>::SubscriptionService::start( &origin, query_id, max_response_weight, - &XcmContext { - origin: Some(origin.clone()), - message_id: [0; 32], - topic: None, - }, - ).map_err(|_| "Could not start subscription")?; - assert!(<T::XcmConfig as xcm_executor::Config>::SubscriptionService::is_subscribed(&origin)); + &XcmContext { origin: Some(origin.clone()), message_id: [0; 32], topic: None }, + ) + .map_err(|_| "Could not start subscription")?; + assert!(<T::XcmConfig as xcm_executor::Config>::SubscriptionService::is_subscribed( + &origin + )); let mut executor = new_executor::<T>(origin.clone()); let instruction = Instruction::UnsubscribeVersion; let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { - assert!(!<T::XcmConfig as xcm_executor::Config>::SubscriptionService::is_subscribed(&origin)); + #[block] + { + executor.bench_process(xcm)?; + } + assert!(!<T::XcmConfig as xcm_executor::Config>::SubscriptionService::is_subscribed( + &origin + )); + Ok(()) } - burn_asset { + #[benchmark] + fn burn_asset() -> Result<(), BenchmarkError> { let holding = T::worst_case_holding(0); let assets = holding.clone(); @@ -381,13 +464,16 @@ benchmarks! { let instruction = Instruction::BurnAsset(assets.into()); let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert!(executor.holding().is_empty()); + Ok(()) } - expect_asset { + #[benchmark] + fn expect_asset() -> Result<(), BenchmarkError> { let holding = T::worst_case_holding(0); let assets = holding.clone(); @@ -396,71 +482,86 @@ benchmarks! { let instruction = Instruction::ExpectAsset(assets.into()); let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // `execute` completing successfully is as good as we can check. + + Ok(()) } - expect_origin { + #[benchmark] + fn expect_origin() -> Result<(), BenchmarkError> { let expected_origin = Parent.into(); let mut executor = new_executor::<T>(Default::default()); let instruction = Instruction::ExpectOrigin(Some(expected_origin)); let xcm = Xcm(vec![instruction]); let mut _result = Ok(()); - }: { - _result = executor.bench_process(xcm); - } verify { - assert!(matches!(_result, Err(ExecutorError { - xcm_error: XcmError::ExpectationFalse, - .. - }))); + #[block] + { + _result = executor.bench_process(xcm); + } + assert!(matches!( + _result, + Err(ExecutorError { xcm_error: XcmError::ExpectationFalse, .. }) + )); + + Ok(()) } - expect_error { + #[benchmark] + fn expect_error() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); executor.set_error(Some((3u32, XcmError::Overflow))); let instruction = Instruction::ExpectError(None); let xcm = Xcm(vec![instruction]); let mut _result = Ok(()); - }: { - _result = executor.bench_process(xcm); - } verify { - assert!(matches!(_result, Err(ExecutorError { - xcm_error: XcmError::ExpectationFalse, - .. - }))); + #[block] + { + _result = executor.bench_process(xcm); + } + assert!(matches!( + _result, + Err(ExecutorError { xcm_error: XcmError::ExpectationFalse, .. }) + )); + + Ok(()) } - expect_transact_status { + #[benchmark] + fn expect_transact_status() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); - let worst_error = || -> MaybeErrorCode { - vec![0; MaxDispatchErrorLen::get() as usize].into() - }; + let worst_error = + || -> MaybeErrorCode { vec![0; MaxDispatchErrorLen::get() as usize].into() }; executor.set_transact_status(worst_error()); let instruction = Instruction::ExpectTransactStatus(worst_error()); let xcm = Xcm(vec![instruction]); let mut _result = Ok(()); - }: { - _result = executor.bench_process(xcm); - } verify { + #[block] + { + _result = executor.bench_process(xcm); + } assert!(matches!(_result, Ok(..))); + Ok(()) } - query_pallet { + #[benchmark] + fn query_pallet() -> Result<(), BenchmarkError> { let (sender_account, sender_location) = account_and_location::<T>(1); let query_id = Default::default(); let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?; let max_weight = Default::default(); - let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( - &sender_location, - &destination, - FeeReason::QueryPallet, - ); + let (expected_fees_mode, expected_assets_in_holding) = + T::DeliveryHelper::ensure_successful_delivery( + &sender_location, + &destination, + FeeReason::QueryPallet, + ); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); let mut executor = new_executor::<T>(sender_location); if let Some(expected_fees_mode) = expected_fees_mode { @@ -476,15 +577,19 @@ benchmarks! { response_info: QueryResponseInfo { destination, query_id, max_weight }, }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // Check we charged the delivery fees assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before); // TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426 + + Ok(()) } - expect_pallet { + #[benchmark] + fn expect_pallet() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); let valid_pallet = T::valid_pallet(); let instruction = Instruction::ExpectPallet { @@ -495,23 +600,27 @@ benchmarks! { min_crate_minor: valid_pallet.crate_version.minor.into(), }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // the execution succeeding is all we need to verify this xcm was successful + Ok(()) } - report_transact_status { + #[benchmark] + fn report_transact_status() -> Result<(), BenchmarkError> { let (sender_account, sender_location) = account_and_location::<T>(1); let query_id = Default::default(); let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?; let max_weight = Default::default(); - let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( - &sender_location, - &destination, - FeeReason::Report, - ); + let (expected_fees_mode, expected_assets_in_holding) = + T::DeliveryHelper::ensure_successful_delivery( + &sender_location, + &destination, + FeeReason::Report, + ); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); let mut executor = new_executor::<T>(sender_location); @@ -529,84 +638,102 @@ benchmarks! { max_weight, }); let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // Check we charged the delivery fees assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before); // TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426 + Ok(()) } - clear_transact_status { + #[benchmark] + fn clear_transact_status() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); executor.set_transact_status(b"MyError".to_vec().into()); let instruction = Instruction::ClearTransactStatus; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.transact_status(), &MaybeErrorCode::Success); + Ok(()) } - set_topic { + #[benchmark] + fn set_topic() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); let instruction = Instruction::SetTopic([1; 32]); let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.topic(), &Some([1; 32])); + Ok(()) } - clear_topic { + #[benchmark] + fn clear_topic() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); executor.set_topic(Some([2; 32])); let instruction = Instruction::ClearTopic; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.topic(), &None); + Ok(()) } - exchange_asset { + #[benchmark] + fn exchange_asset() -> Result<(), BenchmarkError> { let (give, want) = T::worst_case_asset_exchange().map_err(|_| BenchmarkError::Skip)?; let assets = give.clone(); let mut executor = new_executor::<T>(Default::default()); executor.set_holding(give.into()); - let instruction = Instruction::ExchangeAsset { - give: assets.into(), - want: want.clone(), - maximal: true, - }; + let instruction = + Instruction::ExchangeAsset { give: assets.into(), want: want.clone(), maximal: true }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.holding(), &want.into()); + Ok(()) } - universal_origin { + #[benchmark] + fn universal_origin() -> Result<(), BenchmarkError> { let (origin, alias) = T::universal_alias().map_err(|_| BenchmarkError::Skip)?; let mut executor = new_executor::<T>(origin); let instruction = Instruction::UniversalOrigin(alias); let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } use frame_support::traits::Get; let universal_location = <T::XcmConfig as xcm_executor::Config>::UniversalLocation::get(); - assert_eq!(executor.origin(), &Some(Junctions::from([alias]).relative_to(&universal_location))); + assert_eq!( + executor.origin(), + &Some(Junctions::from([alias]).relative_to(&universal_location)) + ); + + Ok(()) } - export_message { - let x in 1 .. 1000; + #[benchmark] + fn export_message(x: Linear<1, 1000>) -> Result<(), BenchmarkError> { // The `inner_xcm` influences `ExportMessage` total weight based on // `inner_xcm.encoded_size()`, so for this benchmark use smallest encoded instruction // to approximate weight per "unit" of encoded size; then actual weight can be estimated @@ -616,11 +743,12 @@ benchmarks! { // Get `origin`, `network` and `destination` from configured runtime. let (origin, network, destination) = T::export_message_origin_and_destination()?; - let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( - &origin, - &destination.clone().into(), - FeeReason::Export { network, destination: destination.clone() }, - ); + let (expected_fees_mode, expected_assets_in_holding) = + T::DeliveryHelper::ensure_successful_delivery( + &origin, + &destination.clone().into(), + FeeReason::Export { network, destination: destination.clone() }, + ); let sender_account = T::AccountIdConverter::convert_location(&origin).unwrap(); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); @@ -631,37 +759,39 @@ benchmarks! { if let Some(expected_assets_in_holding) = expected_assets_in_holding { executor.set_holding(expected_assets_in_holding.into()); } - let xcm = Xcm(vec![ExportMessage { - network, destination: destination.clone(), xcm: inner_xcm, - }]); - }: { - executor.bench_process(xcm)?; - } verify { + let xcm = + Xcm(vec![ExportMessage { network, destination: destination.clone(), xcm: inner_xcm }]); + #[block] + { + executor.bench_process(xcm)?; + } // Check we charged the delivery fees assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before); // TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426 + Ok(()) } - set_fees_mode { + #[benchmark] + fn set_fees_mode() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); executor.set_fees_mode(FeesMode { jit_withdraw: false }); let instruction = Instruction::SetFeesMode { jit_withdraw: true }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.fees_mode(), &FeesMode { jit_withdraw: true }); + Ok(()) } - lock_asset { + #[benchmark] + fn lock_asset() -> Result<(), BenchmarkError> { let (unlocker, owner, asset) = T::unlockable_asset()?; - let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( - &owner, - &unlocker, - FeeReason::LockAsset, - ); + let (expected_fees_mode, expected_assets_in_holding) = + T::DeliveryHelper::ensure_successful_delivery(&owner, &unlocker, FeeReason::LockAsset); let sender_account = T::AccountIdConverter::convert_location(&owner).unwrap(); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); @@ -681,15 +811,18 @@ benchmarks! { let instruction = Instruction::LockAsset { asset, unlocker }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // Check delivery fees assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before); // TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426 + Ok(()) } - unlock_asset { + #[benchmark] + fn unlock_asset() -> Result<(), BenchmarkError> { use xcm_executor::traits::{AssetLock, Enact}; let (unlocker, owner, asset) = T::unlockable_asset()?; @@ -709,13 +842,15 @@ benchmarks! { // ... then unlock them with the UnlockAsset instruction. let instruction = Instruction::UnlockAsset { asset, target: owner }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { - + #[block] + { + executor.bench_process(xcm)?; + } + Ok(()) } - note_unlockable { + #[benchmark] + fn note_unlockable() -> Result<(), BenchmarkError> { use xcm_executor::traits::{AssetLock, Enact}; let (unlocker, owner, asset) = T::unlockable_asset()?; @@ -735,13 +870,15 @@ benchmarks! { // ... then note them as unlockable with the NoteUnlockable instruction. let instruction = Instruction::NoteUnlockable { asset, owner }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { - + #[block] + { + executor.bench_process(xcm)?; + } + Ok(()) } - request_unlock { + #[benchmark] + fn request_unlock() -> Result<(), BenchmarkError> { use xcm_executor::traits::{AssetLock, Enact}; let (locker, owner, asset) = T::unlockable_asset()?; @@ -756,11 +893,12 @@ benchmarks! { .enact() .map_err(|_| BenchmarkError::Skip)?; - let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( - &owner, - &locker, - FeeReason::RequestUnlock, - ); + let (expected_fees_mode, expected_assets_in_holding) = + T::DeliveryHelper::ensure_successful_delivery( + &owner, + &locker, + FeeReason::RequestUnlock, + ); let sender_account = T::AccountIdConverter::convert_location(&owner).unwrap(); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); @@ -774,15 +912,18 @@ benchmarks! { } let instruction = Instruction::RequestUnlock { asset, locker }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // Check we charged the delivery fees assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before); // TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426 + Ok(()) } - unpaid_execution { + #[benchmark] + fn unpaid_execution() -> Result<(), BenchmarkError> { let mut executor = new_executor::<T>(Default::default()); executor.set_origin(Some(Here.into())); @@ -792,21 +933,27 @@ benchmarks! { }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; + #[block] + { + executor.bench_process(xcm)?; + } + Ok(()) } - alias_origin { + #[benchmark] + fn alias_origin() -> Result<(), BenchmarkError> { let (origin, target) = T::alias_origin().map_err(|_| BenchmarkError::Skip)?; let mut executor = new_executor::<T>(origin); let instruction = Instruction::AliasOrigin(target.clone()); let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.origin(), &Some(target)); + Ok(()) } impl_benchmark_test_suite!( diff --git a/polkadot/xcm/pallet-xcm/Cargo.toml b/polkadot/xcm/pallet-xcm/Cargo.toml index 4d44d75e34dd33d62dc36e767c6428050b6a1f82..85beba03b15762dea5c36a82eee9294e87dcd83a 100644 --- a/polkadot/xcm/pallet-xcm/Cargo.toml +++ b/polkadot/xcm/pallet-xcm/Cargo.toml @@ -5,6 +5,8 @@ description = "A pallet for handling XCM programs." authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -23,8 +25,8 @@ sp-io = { workspace = true } sp-runtime = { workspace = true } xcm = { workspace = true } -xcm-executor = { workspace = true } xcm-builder = { workspace = true } +xcm-executor = { workspace = true } xcm-runtime-apis = { workspace = true } # marked optional, used in benchmarking @@ -33,8 +35,8 @@ pallet-balances = { optional = true, workspace = true } [dev-dependencies] pallet-assets = { workspace = true, default-features = true } -polkadot-runtime-parachains = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-runtime-parachains = { workspace = true, default-features = true } [features] default = ["std"] @@ -68,6 +70,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/polkadot/xcm/pallet-xcm/src/benchmarking.rs b/polkadot/xcm/pallet-xcm/src/benchmarking.rs index e493d4838f5ca4a7a6bca0c8a5def791046f84ef..3ca048057ee497b5f2537b0f341e6aea1b6332cb 100644 --- a/polkadot/xcm/pallet-xcm/src/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm/src/benchmarking.rs @@ -15,7 +15,7 @@ // along with Polkadot. If not, see <http://www.gnu.org/licenses/>. use super::*; -use frame_benchmarking::{benchmarks, whitelisted_caller, BenchmarkError, BenchmarkResult}; +use frame_benchmarking::v2::*; use frame_support::{assert_ok, weights::Weight}; use frame_system::RawOrigin; use xcm::latest::prelude::*; @@ -83,25 +83,41 @@ pub trait Config: crate::Config { fn get_asset() -> Asset; } -benchmarks! { - send { +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn send() -> Result<(), BenchmarkError> { let send_origin = T::SendXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; if T::SendXcmOrigin::try_origin(send_origin.clone()).is_err() { return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) } let msg = Xcm(vec![ClearOrigin]); - let versioned_dest: VersionedLocation = T::reachable_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), - )? - .into(); + let versioned_dest: VersionedLocation = T::reachable_dest() + .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))? + .into(); let versioned_msg = VersionedXcm::from(msg); - }: _<RuntimeOrigin<T>>(send_origin, Box::new(versioned_dest), Box::new(versioned_msg)) - teleport_assets { - let (asset, destination) = T::teleportable_asset_and_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), - )?; + // Ensure that origin can send to destination + // (e.g. setup delivery fees, ensure router setup, ...) + T::DeliveryHelper::ensure_successful_delivery( + &Default::default(), + &versioned_dest.clone().try_into().unwrap(), + FeeReason::ChargeFees, + ); + + #[extrinsic_call] + _(send_origin as RuntimeOrigin<T>, Box::new(versioned_dest), Box::new(versioned_msg)); + + Ok(()) + } + + #[benchmark] + fn teleport_assets() -> Result<(), BenchmarkError> { + let (asset, destination) = T::teleportable_asset_and_dest() + .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; let assets: Assets = asset.clone().into(); @@ -109,11 +125,13 @@ benchmarks! { let send_origin = RawOrigin::Signed(caller.clone()); let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into()) .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; - if !T::XcmTeleportFilter::contains(&(origin_location.clone(), assets.clone().into_inner())) { + if !T::XcmTeleportFilter::contains(&(origin_location.clone(), assets.clone().into_inner())) + { return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) } - // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + // Ensure that origin can send to destination + // (e.g. setup delivery fees, ensure router setup, ...) let (_, _) = T::DeliveryHelper::ensure_successful_delivery( &origin_location, &destination, @@ -127,18 +145,23 @@ benchmarks! { &Asset { fun: Fungible(*amount), id: asset.id }, &origin_location, None, - ).map_err(|error| { - tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error); - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) + ) + .map_err(|error| { + tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error); + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) + })?; + }, + NonFungible(_instance) => { + <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::deposit_asset( + &asset, + &origin_location, + None, + ) + .map_err(|error| { + tracing::error!("Nonfungible asset couldn't be deposited, error: {:?}", error); + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) })?; }, - NonFungible(instance) => { - <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::deposit_asset(&asset, &origin_location, None) - .map_err(|error| { - tracing::error!("Nonfungible asset couldn't be deposited, error: {:?}", error); - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) - })?; - } }; let recipient = [0u8; 32]; @@ -146,12 +169,23 @@ benchmarks! { let versioned_beneficiary: VersionedLocation = AccountId32 { network: None, id: recipient.into() }.into(); let versioned_assets: VersionedAssets = assets.into(); - }: _<RuntimeOrigin<T>>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0) - reserve_transfer_assets { - let (asset, destination) = T::reserve_transferable_asset_and_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), - )?; + #[extrinsic_call] + _( + send_origin, + Box::new(versioned_dest), + Box::new(versioned_beneficiary), + Box::new(versioned_assets), + 0, + ); + + Ok(()) + } + + #[benchmark] + fn reserve_transfer_assets() -> Result<(), BenchmarkError> { + let (asset, destination) = T::reserve_transferable_asset_and_dest() + .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; let assets: Assets = asset.clone().into(); @@ -159,11 +193,15 @@ benchmarks! { let send_origin = RawOrigin::Signed(caller.clone()); let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into()) .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; - if !T::XcmReserveTransferFilter::contains(&(origin_location.clone(), assets.clone().into_inner())) { + if !T::XcmReserveTransferFilter::contains(&( + origin_location.clone(), + assets.clone().into_inner(), + )) { return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) } - // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + // Ensure that origin can send to destination + // (e.g. setup delivery fees, ensure router setup, ...) let (_, _) = T::DeliveryHelper::ensure_successful_delivery( &origin_location, &destination, @@ -177,18 +215,23 @@ benchmarks! { &Asset { fun: Fungible(*amount), id: asset.id.clone() }, &origin_location, None, - ).map_err(|error| { - tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error); - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) + ) + .map_err(|error| { + tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error); + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) + })?; + }, + NonFungible(_instance) => { + <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::deposit_asset( + &asset, + &origin_location, + None, + ) + .map_err(|error| { + tracing::error!("Nonfungible asset couldn't be deposited, error: {:?}", error); + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) })?; }, - NonFungible(instance) => { - <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::deposit_asset(&asset, &origin_location, None) - .map_err(|error| { - tracing::error!("Nonfungible asset couldn't be deposited, error: {:?}", error); - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) - })?; - } }; let recipient = [0u8; 32]; @@ -196,8 +239,16 @@ benchmarks! { let versioned_beneficiary: VersionedLocation = AccountId32 { network: None, id: recipient.into() }.into(); let versioned_assets: VersionedAssets = assets.into(); - }: _<RuntimeOrigin<T>>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0) - verify { + + #[extrinsic_call] + _( + send_origin, + Box::new(versioned_dest), + Box::new(versioned_beneficiary), + Box::new(versioned_assets), + 0, + ); + match &asset.fun { Fungible(amount) => { assert_ok!(<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::withdraw_asset( @@ -206,20 +257,22 @@ benchmarks! { None, )); }, - NonFungible(instance) => { + NonFungible(_instance) => { assert_ok!(<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::withdraw_asset( &asset, &destination, None, )); - } + }, }; + + Ok(()) } - transfer_assets { - let (assets, fee_index, destination, verify) = T::set_up_complex_asset_transfer().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), - )?; + #[benchmark] + fn transfer_assets() -> Result<(), BenchmarkError> { + let (assets, _fee_index, destination, verify_fn) = T::set_up_complex_asset_transfer() + .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; let caller: T::AccountId = whitelisted_caller(); let send_origin = RawOrigin::Signed(caller.clone()); let recipient = [0u8; 32]; @@ -227,13 +280,32 @@ benchmarks! { let versioned_beneficiary: VersionedLocation = AccountId32 { network: None, id: recipient.into() }.into(); let versioned_assets: VersionedAssets = assets.into(); - }: _<RuntimeOrigin<T>>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0, WeightLimit::Unlimited) - verify { + + // Ensure that origin can send to destination + // (e.g. setup delivery fees, ensure router setup, ...) + T::DeliveryHelper::ensure_successful_delivery( + &Default::default(), + &versioned_dest.clone().try_into().unwrap(), + FeeReason::ChargeFees, + ); + + #[extrinsic_call] + _( + send_origin, + Box::new(versioned_dest), + Box::new(versioned_beneficiary), + Box::new(versioned_assets), + 0, + WeightLimit::Unlimited, + ); + // run provided verification function - verify(); + verify_fn(); + Ok(()) } - execute { + #[benchmark] + fn execute() -> Result<(), BenchmarkError> { let execute_origin = T::ExecuteXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let origin_location = T::ExecuteXcmOrigin::try_origin(execute_origin.clone()) @@ -243,147 +315,287 @@ benchmarks! { return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) } let versioned_msg = VersionedXcm::from(msg); - }: _<RuntimeOrigin<T>>(execute_origin, Box::new(versioned_msg), Weight::MAX) - force_xcm_version { - let loc = T::reachable_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), - )?; + #[extrinsic_call] + _(execute_origin as RuntimeOrigin<T>, Box::new(versioned_msg), Weight::MAX); + + Ok(()) + } + + #[benchmark] + fn force_xcm_version() -> Result<(), BenchmarkError> { + let loc = T::reachable_dest() + .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; let xcm_version = 2; - }: _(RawOrigin::Root, Box::new(loc), xcm_version) - force_default_xcm_version {}: _(RawOrigin::Root, Some(2)) + #[extrinsic_call] + _(RawOrigin::Root, Box::new(loc), xcm_version); + + Ok(()) + } + + #[benchmark] + fn force_default_xcm_version() { + #[extrinsic_call] + _(RawOrigin::Root, Some(2)) + } + + #[benchmark] + fn force_subscribe_version_notify() -> Result<(), BenchmarkError> { + let versioned_loc: VersionedLocation = T::reachable_dest() + .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))? + .into(); + + // Ensure that origin can send to destination + // (e.g. setup delivery fees, ensure router setup, ...) + T::DeliveryHelper::ensure_successful_delivery( + &Default::default(), + &versioned_loc.clone().try_into().unwrap(), + FeeReason::ChargeFees, + ); + + #[extrinsic_call] + _(RawOrigin::Root, Box::new(versioned_loc)); - force_subscribe_version_notify { - let versioned_loc: VersionedLocation = T::reachable_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), - )? - .into(); - }: _(RawOrigin::Root, Box::new(versioned_loc)) + Ok(()) + } - force_unsubscribe_version_notify { - let loc = T::reachable_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), - )?; + #[benchmark] + fn force_unsubscribe_version_notify() -> Result<(), BenchmarkError> { + let loc = T::reachable_dest() + .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; let versioned_loc: VersionedLocation = loc.clone().into(); + + // Ensure that origin can send to destination + // (e.g. setup delivery fees, ensure router setup, ...) + T::DeliveryHelper::ensure_successful_delivery( + &Default::default(), + &versioned_loc.clone().try_into().unwrap(), + FeeReason::ChargeFees, + ); + let _ = crate::Pallet::<T>::request_version_notify(loc); - }: _(RawOrigin::Root, Box::new(versioned_loc)) - force_suspension {}: _(RawOrigin::Root, true) + #[extrinsic_call] + _(RawOrigin::Root, Box::new(versioned_loc)); + + Ok(()) + } - migrate_supported_version { + #[benchmark] + fn force_suspension() { + #[extrinsic_call] + _(RawOrigin::Root, true) + } + + #[benchmark] + fn migrate_supported_version() { let old_version = XCM_VERSION - 1; let loc = VersionedLocation::from(Location::from(Parent)); SupportedVersion::<T>::insert(old_version, loc, old_version); - }: { - crate::Pallet::<T>::check_xcm_version_change(VersionMigrationStage::MigrateSupportedVersion, Weight::zero()); + + #[block] + { + crate::Pallet::<T>::check_xcm_version_change( + VersionMigrationStage::MigrateSupportedVersion, + Weight::zero(), + ); + } } - migrate_version_notifiers { + #[benchmark] + fn migrate_version_notifiers() { let old_version = XCM_VERSION - 1; let loc = VersionedLocation::from(Location::from(Parent)); VersionNotifiers::<T>::insert(old_version, loc, 0); - }: { - crate::Pallet::<T>::check_xcm_version_change(VersionMigrationStage::MigrateVersionNotifiers, Weight::zero()); + + #[block] + { + crate::Pallet::<T>::check_xcm_version_change( + VersionMigrationStage::MigrateVersionNotifiers, + Weight::zero(), + ); + } } - already_notified_target { - let loc = T::reachable_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads(1))), - )?; + #[benchmark] + fn already_notified_target() -> Result<(), BenchmarkError> { + let loc = T::reachable_dest().ok_or(BenchmarkError::Override( + BenchmarkResult::from_weight(T::DbWeight::get().reads(1)), + ))?; let loc = VersionedLocation::from(loc); let current_version = T::AdvertisedXcmVersion::get(); - VersionNotifyTargets::<T>::insert(current_version, loc, (0, Weight::zero(), current_version)); - }: { - crate::Pallet::<T>::check_xcm_version_change(VersionMigrationStage::NotifyCurrentTargets(None), Weight::zero()); + VersionNotifyTargets::<T>::insert( + current_version, + loc, + (0, Weight::zero(), current_version), + ); + + #[block] + { + crate::Pallet::<T>::check_xcm_version_change( + VersionMigrationStage::NotifyCurrentTargets(None), + Weight::zero(), + ); + } + + Ok(()) } - notify_current_targets { - let loc = T::reachable_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3))), - )?; + #[benchmark] + fn notify_current_targets() -> Result<(), BenchmarkError> { + let loc = T::reachable_dest().ok_or(BenchmarkError::Override( + BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3)), + ))?; let loc = VersionedLocation::from(loc); let current_version = T::AdvertisedXcmVersion::get(); let old_version = current_version - 1; VersionNotifyTargets::<T>::insert(current_version, loc, (0, Weight::zero(), old_version)); - }: { - crate::Pallet::<T>::check_xcm_version_change(VersionMigrationStage::NotifyCurrentTargets(None), Weight::zero()); + + #[block] + { + crate::Pallet::<T>::check_xcm_version_change( + VersionMigrationStage::NotifyCurrentTargets(None), + Weight::zero(), + ); + } + + Ok(()) } - notify_target_migration_fail { + #[benchmark] + fn notify_target_migration_fail() { let newer_xcm_version = xcm::prelude::XCM_VERSION; let older_xcm_version = newer_xcm_version - 1; - let bad_location: Location = Plurality { - id: BodyId::Unit, - part: BodyPart::Voice, - }.into(); + let bad_location: Location = Plurality { id: BodyId::Unit, part: BodyPart::Voice }.into(); let bad_location = VersionedLocation::from(bad_location) .into_version(older_xcm_version) .expect("Version convertion should work"); let current_version = T::AdvertisedXcmVersion::get(); - VersionNotifyTargets::<T>::insert(current_version, bad_location, (0, Weight::zero(), current_version)); - }: { - crate::Pallet::<T>::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero()); + VersionNotifyTargets::<T>::insert( + current_version, + bad_location, + (0, Weight::zero(), current_version), + ); + + #[block] + { + crate::Pallet::<T>::check_xcm_version_change( + VersionMigrationStage::MigrateAndNotifyOldTargets, + Weight::zero(), + ); + } } - migrate_version_notify_targets { + #[benchmark] + fn migrate_version_notify_targets() { let current_version = T::AdvertisedXcmVersion::get(); let old_version = current_version - 1; let loc = VersionedLocation::from(Location::from(Parent)); VersionNotifyTargets::<T>::insert(old_version, loc, (0, Weight::zero(), current_version)); - }: { - crate::Pallet::<T>::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero()); + + #[block] + { + crate::Pallet::<T>::check_xcm_version_change( + VersionMigrationStage::MigrateAndNotifyOldTargets, + Weight::zero(), + ); + } } - migrate_and_notify_old_targets { - let loc = T::reachable_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3))), - )?; + #[benchmark] + fn migrate_and_notify_old_targets() -> Result<(), BenchmarkError> { + let loc = T::reachable_dest().ok_or(BenchmarkError::Override( + BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3)), + ))?; let loc = VersionedLocation::from(loc); let old_version = T::AdvertisedXcmVersion::get() - 1; VersionNotifyTargets::<T>::insert(old_version, loc, (0, Weight::zero(), old_version)); - }: { - crate::Pallet::<T>::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero()); + + #[block] + { + crate::Pallet::<T>::check_xcm_version_change( + VersionMigrationStage::MigrateAndNotifyOldTargets, + Weight::zero(), + ); + } + + Ok(()) } - new_query { + #[benchmark] + fn new_query() { let responder = Location::from(Parent); let timeout = 1u32.into(); let match_querier = Location::from(Here); - }: { - crate::Pallet::<T>::new_query(responder, timeout, match_querier); + + #[block] + { + crate::Pallet::<T>::new_query(responder, timeout, match_querier); + } } - take_response { + #[benchmark] + fn take_response() { let responder = Location::from(Parent); let timeout = 1u32.into(); let match_querier = Location::from(Here); let query_id = crate::Pallet::<T>::new_query(responder, timeout, match_querier); - let infos = (0 .. xcm::v3::MaxPalletsInfo::get()).map(|_| PalletInfo::new( - u32::MAX, - (0..xcm::v3::MaxPalletNameLen::get()).map(|_| 97u8).collect::<Vec<_>>().try_into().unwrap(), - (0..xcm::v3::MaxPalletNameLen::get()).map(|_| 97u8).collect::<Vec<_>>().try_into().unwrap(), - u32::MAX, - u32::MAX, - u32::MAX, - ).unwrap()).collect::<Vec<_>>(); - crate::Pallet::<T>::expect_response(query_id, Response::PalletsInfo(infos.try_into().unwrap())); - }: { - <crate::Pallet::<T> as QueryHandler>::take_response(query_id); + let infos = (0..xcm::v3::MaxPalletsInfo::get()) + .map(|_| { + PalletInfo::new( + u32::MAX, + (0..xcm::v3::MaxPalletNameLen::get()) + .map(|_| 97u8) + .collect::<Vec<_>>() + .try_into() + .unwrap(), + (0..xcm::v3::MaxPalletNameLen::get()) + .map(|_| 97u8) + .collect::<Vec<_>>() + .try_into() + .unwrap(), + u32::MAX, + u32::MAX, + u32::MAX, + ) + .unwrap() + }) + .collect::<Vec<_>>(); + crate::Pallet::<T>::expect_response( + query_id, + Response::PalletsInfo(infos.try_into().unwrap()), + ); + + #[block] + { + <crate::Pallet<T> as QueryHandler>::take_response(query_id); + } } - claim_assets { + #[benchmark] + fn claim_assets() -> Result<(), BenchmarkError> { let claim_origin = RawOrigin::Signed(whitelisted_caller()); - let claim_location = T::ExecuteXcmOrigin::try_origin(claim_origin.clone().into()).map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; + let claim_location = T::ExecuteXcmOrigin::try_origin(claim_origin.clone().into()) + .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; let asset: Asset = T::get_asset(); // Trap assets for claiming later crate::Pallet::<T>::drop_assets( &claim_location, asset.clone().into(), - &XcmContext { origin: None, message_id: [0u8; 32], topic: None } + &XcmContext { origin: None, message_id: [0u8; 32], topic: None }, ); let versioned_assets = VersionedAssets::from(Assets::from(asset)); - }: _<RuntimeOrigin<T>>(claim_origin.into(), Box::new(versioned_assets), Box::new(VersionedLocation::from(claim_location))) + + #[extrinsic_call] + _( + claim_origin, + Box::new(versioned_assets), + Box::new(VersionedLocation::from(claim_location)), + ); + + Ok(()) + } impl_benchmark_test_suite!( Pallet, diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index 5e0512c6a9fdb18c2fd7b30ed47cf51c36006205..6360298b21c3660dd86096968b1d4f0e0327d10b 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -363,7 +363,10 @@ pub mod pallet { let message: Xcm<()> = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?; let message_id = Self::send_xcm(interior, dest.clone(), message.clone()) - .map_err(Error::<T>::from)?; + .map_err(|error| { + tracing::error!(target: "xcm::pallet_xcm::send", ?error, ?dest, ?message, "XCM send failed with error"); + Error::<T>::from(error) + })?; let e = Event::Sent { origin: origin_location, destination: dest, message, message_id }; Self::deposit_event(e); Ok(message_id) @@ -1800,7 +1803,10 @@ impl<T: Config> Pallet<T> { if let Some(remote_xcm) = remote_xcm { let (ticket, price) = validate_send::<T::XcmRouter>(dest.clone(), remote_xcm.clone()) - .map_err(Error::<T>::from)?; + .map_err(|error| { + tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM validate_send failed with error"); + Error::<T>::from(error) + })?; if origin != Here.into_location() { Self::charge_fees(origin.clone(), price.clone()).map_err(|error| { tracing::error!( @@ -1810,7 +1816,11 @@ impl<T: Config> Pallet<T> { Error::<T>::FeesNotMet })?; } - let message_id = T::XcmRouter::deliver(ticket).map_err(Error::<T>::from)?; + let message_id = T::XcmRouter::deliver(ticket) + .map_err(|error| { + tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM deliver failed with error"); + Error::<T>::from(error) + })?; let e = Event::Sent { origin, destination: dest, message: remote_xcm, message_id }; Self::deposit_event(e); diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs index 8d0476b0e70d77f7cc1d63033392eb274dbb337c..58b4226ccf191d001a0ed3f3178c45b5724cf0be 100644 --- a/polkadot/xcm/pallet-xcm/src/mock.rs +++ b/polkadot/xcm/pallet-xcm/src/mock.rs @@ -700,7 +700,7 @@ pub(crate) fn new_test_ext_with_balances_and_xcm_version( ) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); - pallet_balances::GenesisConfig::<Test> { balances } + pallet_balances::GenesisConfig::<Test> { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/xcm/procedural/Cargo.toml b/polkadot/xcm/procedural/Cargo.toml index 83b35d19cf7eee67ef47f9af507d4a61b75fa9dc..0843da86f038b6b6cf307527d080f29e8271c6e9 100644 --- a/polkadot/xcm/procedural/Cargo.toml +++ b/polkadot/xcm/procedural/Cargo.toml @@ -6,6 +6,8 @@ edition.workspace = true license.workspace = true version = "7.0.0" publish = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true @@ -14,13 +16,15 @@ workspace = true proc-macro = true [dependencies] +Inflector = { workspace = true } proc-macro2 = { workspace = true } quote = { workspace = true } syn = { workspace = true } -Inflector = { workspace = true } [dev-dependencies] trybuild = { features = ["diff"], workspace = true } # NOTE: we have to explicitly specify `std` because of trybuild # https://github.com/paritytech/polkadot-sdk/pull/5167 xcm = { workspace = true, default-features = true, features = ["std"] } +# For testing macros. +frame-support = { workspace = true } diff --git a/polkadot/xcm/procedural/src/builder_pattern.rs b/polkadot/xcm/procedural/src/builder_pattern.rs index b65290332af9808809fbca4b42deb77f63d90696..34b89f13422c0ef16b4ac514722861630f644f23 100644 --- a/polkadot/xcm/procedural/src/builder_pattern.rs +++ b/polkadot/xcm/procedural/src/builder_pattern.rs @@ -20,8 +20,8 @@ use inflector::Inflector; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; use syn::{ - Data, DataEnum, DeriveInput, Error, Expr, ExprLit, Fields, Ident, Lit, Meta, MetaNameValue, - Result, Variant, + Data, DataEnum, DeriveInput, Error, Expr, ExprLit, Fields, GenericArgument, Ident, Lit, Meta, + MetaNameValue, PathArguments, Result, Type, TypePath, Variant, }; pub fn derive(input: DeriveInput) -> Result<TokenStream2> { @@ -29,7 +29,7 @@ pub fn derive(input: DeriveInput) -> Result<TokenStream2> { Data::Enum(data_enum) => data_enum, _ => return Err(Error::new_spanned(&input, "Expected the `Instruction` enum")), }; - let builder_raw_impl = generate_builder_raw_impl(&input.ident, data_enum); + let builder_raw_impl = generate_builder_raw_impl(&input.ident, data_enum)?; let builder_impl = generate_builder_impl(&input.ident, data_enum)?; let builder_unpaid_impl = generate_builder_unpaid_impl(&input.ident, data_enum)?; let output = quote! { @@ -83,54 +83,12 @@ pub fn derive(input: DeriveInput) -> Result<TokenStream2> { Ok(output) } -fn generate_builder_raw_impl(name: &Ident, data_enum: &DataEnum) -> TokenStream2 { - let methods = data_enum.variants.iter().map(|variant| { - let variant_name = &variant.ident; - let method_name_string = &variant_name.to_string().to_snake_case(); - let method_name = syn::Ident::new(method_name_string, variant_name.span()); - let docs = get_doc_comments(variant); - let method = match &variant.fields { - Fields::Unit => { - quote! { - pub fn #method_name(mut self) -> Self { - self.instructions.push(#name::<Call>::#variant_name); - self - } - } - }, - Fields::Unnamed(fields) => { - let arg_names: Vec<_> = fields - .unnamed - .iter() - .enumerate() - .map(|(index, _)| format_ident!("arg{}", index)) - .collect(); - let arg_types: Vec<_> = fields.unnamed.iter().map(|field| &field.ty).collect(); - quote! { - pub fn #method_name(mut self, #(#arg_names: impl Into<#arg_types>),*) -> Self { - #(let #arg_names = #arg_names.into();)* - self.instructions.push(#name::<Call>::#variant_name(#(#arg_names),*)); - self - } - } - }, - Fields::Named(fields) => { - let arg_names: Vec<_> = fields.named.iter().map(|field| &field.ident).collect(); - let arg_types: Vec<_> = fields.named.iter().map(|field| &field.ty).collect(); - quote! { - pub fn #method_name(mut self, #(#arg_names: impl Into<#arg_types>),*) -> Self { - #(let #arg_names = #arg_names.into();)* - self.instructions.push(#name::<Call>::#variant_name { #(#arg_names),* }); - self - } - } - }, - }; - quote! { - #(#docs)* - #method - } - }); +fn generate_builder_raw_impl(name: &Ident, data_enum: &DataEnum) -> Result<TokenStream2> { + let methods = data_enum + .variants + .iter() + .map(|variant| convert_variant_to_method(name, variant, None)) + .collect::<Result<Vec<_>>>()?; let output = quote! { impl<Call> XcmBuilder<Call, AnythingGoes> { #(#methods)* @@ -140,7 +98,7 @@ fn generate_builder_raw_impl(name: &Ident, data_enum: &DataEnum) -> TokenStream2 } } }; - output + Ok(output) } fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result<TokenStream2> { @@ -165,11 +123,17 @@ fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result<TokenStre "Expected `builder(loads_holding)` or `builder(pays_fees)`", ) })?; - let ident_to_match: Ident = syn::parse_quote!(loads_holding); - if inner_ident == ident_to_match { + let loads_holding_ident: Ident = syn::parse_quote!(loads_holding); + let pays_fees_ident: Ident = syn::parse_quote!(pays_fees); + if inner_ident == loads_holding_ident { Ok(Some(variant)) + } else if inner_ident == pays_fees_ident { + Ok(None) } else { - Ok(None) // Must have been `pays_fees` instead. + Err(Error::new_spanned( + &builder_attr, + "Expected `builder(loads_holding)` or `builder(pays_fees)`", + )) } }) .collect::<Result<Vec<_>>>()?; @@ -178,57 +142,14 @@ fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result<TokenStre .into_iter() .flatten() .map(|variant| { - let variant_name = &variant.ident; - let method_name_string = &variant_name.to_string().to_snake_case(); - let method_name = syn::Ident::new(method_name_string, variant_name.span()); - let docs = get_doc_comments(variant); - let method = match &variant.fields { - Fields::Unnamed(fields) => { - let arg_names: Vec<_> = fields - .unnamed - .iter() - .enumerate() - .map(|(index, _)| format_ident!("arg{}", index)) - .collect(); - let arg_types: Vec<_> = fields.unnamed.iter().map(|field| &field.ty).collect(); - quote! { - #(#docs)* - pub fn #method_name(self, #(#arg_names: impl Into<#arg_types>),*) -> XcmBuilder<Call, LoadedHolding> { - let mut new_instructions = self.instructions; - #(let #arg_names = #arg_names.into();)* - new_instructions.push(#name::<Call>::#variant_name(#(#arg_names),*)); - XcmBuilder { - instructions: new_instructions, - state: core::marker::PhantomData, - } - } - } - }, - Fields::Named(fields) => { - let arg_names: Vec<_> = fields.named.iter().map(|field| &field.ident).collect(); - let arg_types: Vec<_> = fields.named.iter().map(|field| &field.ty).collect(); - quote! { - #(#docs)* - pub fn #method_name(self, #(#arg_names: impl Into<#arg_types>),*) -> XcmBuilder<Call, LoadedHolding> { - let mut new_instructions = self.instructions; - #(let #arg_names = #arg_names.into();)* - new_instructions.push(#name::<Call>::#variant_name { #(#arg_names),* }); - XcmBuilder { - instructions: new_instructions, - state: core::marker::PhantomData, - } - } - } - }, - _ => - return Err(Error::new_spanned( - variant, - "Instructions that load the holding register should take operands", - )), - }; + let method = convert_variant_to_method( + name, + variant, + Some(quote! { XcmBuilder<Call, LoadedHolding> }), + )?; Ok(method) }) - .collect::<std::result::Result<Vec<_>, _>>()?; + .collect::<Result<Vec<_>>>()?; let first_impl = quote! { impl<Call> XcmBuilder<Call, PaymentRequired> { @@ -240,27 +161,12 @@ fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result<TokenStre let allowed_after_load_holding_methods: Vec<TokenStream2> = data_enum .variants .iter() - .filter(|variant| variant.ident == "ClearOrigin") + .filter(|variant| variant.ident == "ClearOrigin" || variant.ident == "SetHints") .map(|variant| { - let variant_name = &variant.ident; - let method_name_string = &variant_name.to_string().to_snake_case(); - let method_name = syn::Ident::new(method_name_string, variant_name.span()); - let docs = get_doc_comments(variant); - let method = match &variant.fields { - Fields::Unit => { - quote! { - #(#docs)* - pub fn #method_name(mut self) -> XcmBuilder<Call, LoadedHolding> { - self.instructions.push(#name::<Call>::#variant_name); - self - } - } - }, - _ => return Err(Error::new_spanned(variant, "ClearOrigin should have no fields")), - }; + let method = convert_variant_to_method(name, variant, None)?; Ok(method) }) - .collect::<std::result::Result<Vec<_>, _>>()?; + .collect::<Result<Vec<_>>>()?; // Then we require fees to be paid let pay_fees_variants = data_enum @@ -295,36 +201,12 @@ fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result<TokenStre .into_iter() .flatten() .map(|variant| { - let variant_name = &variant.ident; - let method_name_string = &variant_name.to_string().to_snake_case(); - let method_name = syn::Ident::new(method_name_string, variant_name.span()); - let docs = get_doc_comments(variant); - let fields = match &variant.fields { - Fields::Named(fields) => { - let arg_names: Vec<_> = - fields.named.iter().map(|field| &field.ident).collect(); - let arg_types: Vec<_> = - fields.named.iter().map(|field| &field.ty).collect(); - quote! { - #(#docs)* - pub fn #method_name(self, #(#arg_names: impl Into<#arg_types>),*) -> XcmBuilder<Call, AnythingGoes> { - let mut new_instructions = self.instructions; - #(let #arg_names = #arg_names.into();)* - new_instructions.push(#name::<Call>::#variant_name { #(#arg_names),* }); - XcmBuilder { - instructions: new_instructions, - state: core::marker::PhantomData, - } - } - } - }, - _ => - return Err(Error::new_spanned( - variant, - "Both BuyExecution and PayFees have named fields", - )), - }; - Ok(fields) + let method = convert_variant_to_method( + name, + variant, + Some(quote! { XcmBuilder<Call, AnythingGoes> }), + )?; + Ok(method) }) .collect::<Result<Vec<_>>>()?; @@ -349,35 +231,156 @@ fn generate_builder_unpaid_impl(name: &Ident, data_enum: &DataEnum) -> Result<To .iter() .find(|variant| variant.ident == "UnpaidExecution") .ok_or(Error::new_spanned(&data_enum.variants, "No UnpaidExecution instruction"))?; - let unpaid_execution_ident = &unpaid_execution_variant.ident; - let unpaid_execution_method_name = Ident::new( - &unpaid_execution_ident.to_string().to_snake_case(), - unpaid_execution_ident.span(), - ); - let docs = get_doc_comments(unpaid_execution_variant); - let fields = match &unpaid_execution_variant.fields { - Fields::Named(fields) => fields, - _ => - return Err(Error::new_spanned( - unpaid_execution_variant, - "UnpaidExecution should have named fields", - )), - }; - let arg_names: Vec<_> = fields.named.iter().map(|field| &field.ident).collect(); - let arg_types: Vec<_> = fields.named.iter().map(|field| &field.ty).collect(); + let method = convert_variant_to_method( + name, + &unpaid_execution_variant, + Some(quote! { XcmBuilder<Call, AnythingGoes> }), + )?; Ok(quote! { impl<Call> XcmBuilder<Call, ExplicitUnpaidRequired> { - #(#docs)* - pub fn #unpaid_execution_method_name(self, #(#arg_names: impl Into<#arg_types>),*) -> XcmBuilder<Call, AnythingGoes> { - let mut new_instructions = self.instructions; - #(let #arg_names = #arg_names.into();)* - new_instructions.push(#name::<Call>::#unpaid_execution_ident { #(#arg_names),* }); - XcmBuilder { - instructions: new_instructions, - state: core::marker::PhantomData, + #method + } + }) +} + +// Have to call with `XcmBuilder<Call, LoadedHolding>` in allowed_after_load_holding_methods. +fn convert_variant_to_method( + name: &Ident, + variant: &Variant, + maybe_return_type: Option<TokenStream2>, +) -> Result<TokenStream2> { + let variant_name = &variant.ident; + let method_name_string = &variant_name.to_string().to_snake_case(); + let method_name = syn::Ident::new(method_name_string, variant_name.span()); + let docs = get_doc_comments(variant); + let method = match &variant.fields { + Fields::Unit => + if let Some(return_type) = maybe_return_type { + quote! { + pub fn #method_name(self) -> #return_type { + let mut new_instructions = self.instructions; + new_instructions.push(#name::<Call>::#variant_name); + XcmBuilder { + instructions: new_instructions, + state: core::marker::PhantomData, + } + } + } + } else { + quote! { + pub fn #method_name(mut self) -> Self { + self.instructions.push(#name::<Call>::#variant_name); + self + } + } + }, + Fields::Unnamed(fields) => { + let arg_names: Vec<_> = fields + .unnamed + .iter() + .enumerate() + .map(|(index, _)| format_ident!("arg{}", index)) + .collect(); + let arg_types: Vec<_> = fields.unnamed.iter().map(|field| &field.ty).collect(); + if let Some(return_type) = maybe_return_type { + quote! { + pub fn #method_name(self, #(#arg_names: impl Into<#arg_types>),*) -> #return_type { + let mut new_instructions = self.instructions; + #(let #arg_names = #arg_names.into();)* + new_instructions.push(#name::<Call>::#variant_name(#(#arg_names),*)); + XcmBuilder { + instructions: new_instructions, + state: core::marker::PhantomData, + } + } + } + } else { + quote! { + pub fn #method_name(mut self, #(#arg_names: impl Into<#arg_types>),*) -> Self { + #(let #arg_names = #arg_names.into();)* + self.instructions.push(#name::<Call>::#variant_name(#(#arg_names),*)); + self + } } } - } + }, + Fields::Named(fields) => { + let normal_fields: Vec<_> = fields + .named + .iter() + .filter(|field| { + if let Type::Path(TypePath { path, .. }) = &field.ty { + for segment in &path.segments { + if segment.ident == format_ident!("BoundedVec") { + return false; + } + } + true + } else { + true + } + }) + .collect(); + let bounded_fields: Vec<_> = fields + .named + .iter() + .filter(|field| { + if let Type::Path(TypePath { path, .. }) = &field.ty { + for segment in &path.segments { + if segment.ident == format_ident!("BoundedVec") { + return true; + } + } + false + } else { + false + } + }) + .collect(); + let arg_names: Vec<_> = normal_fields.iter().map(|field| &field.ident).collect(); + let arg_types: Vec<_> = normal_fields.iter().map(|field| &field.ty).collect(); + let bounded_names: Vec<_> = bounded_fields.iter().map(|field| &field.ident).collect(); + let bounded_types = bounded_fields + .iter() + .map(|field| extract_generic_argument(&field.ty, 0, "BoundedVec's inner type")) + .collect::<Result<Vec<_>>>()?; + let bounded_sizes = bounded_fields + .iter() + .map(|field| extract_generic_argument(&field.ty, 1, "BoundedVec's size")) + .collect::<Result<Vec<_>>>()?; + let comma_in_the_middle = if normal_fields.is_empty() { + quote! {} + } else { + quote! {,} + }; + if let Some(return_type) = maybe_return_type { + quote! { + pub fn #method_name(self, #(#arg_names: impl Into<#arg_types>),* #comma_in_the_middle #(#bounded_names: Vec<#bounded_types>),*) -> #return_type { + let mut new_instructions = self.instructions; + #(let #arg_names = #arg_names.into();)* + #(let #bounded_names = BoundedVec::<#bounded_types, #bounded_sizes>::truncate_from(#bounded_names);)* + new_instructions.push(#name::<Call>::#variant_name { #(#arg_names),* #comma_in_the_middle #(#bounded_names),* }); + XcmBuilder { + instructions: new_instructions, + state: core::marker::PhantomData, + } + } + } + } else { + quote! { + pub fn #method_name(mut self, #(#arg_names: impl Into<#arg_types>),* #comma_in_the_middle #(#bounded_names: Vec<#bounded_types>),*) -> Self { + #(let #arg_names = #arg_names.into();)* + #(let #bounded_names = BoundedVec::<#bounded_types, #bounded_sizes>::truncate_from(#bounded_names);)* + self.instructions.push(#name::<Call>::#variant_name { #(#arg_names),* #comma_in_the_middle #(#bounded_names),* }); + self + } + } + } + }, + }; + Ok(quote! { + #(#docs)* + #method }) } @@ -395,3 +398,40 @@ fn get_doc_comments(variant: &Variant) -> Vec<TokenStream2> { .map(|doc| syn::parse_str::<TokenStream2>(&format!("/// {}", doc)).unwrap()) .collect() } + +fn extract_generic_argument<'a>( + field_ty: &'a Type, + index: usize, + expected_msg: &str, +) -> Result<&'a Ident> { + if let Type::Path(type_path) = field_ty { + if let Some(segment) = type_path.path.segments.last() { + if let PathArguments::AngleBracketed(angle_brackets) = &segment.arguments { + let args: Vec<_> = angle_brackets.args.iter().collect(); + if let Some(GenericArgument::Type(Type::Path(TypePath { path, .. }))) = + args.get(index) + { + return path.get_ident().ok_or_else(|| { + Error::new_spanned( + path, + format!("Expected an identifier for {}", expected_msg), + ) + }); + } + return Err(Error::new_spanned( + angle_brackets, + format!("Expected a generic argument at index {} for {}", index, expected_msg), + )); + } + return Err(Error::new_spanned( + &segment.arguments, + format!("Expected angle-bracketed arguments for {}", expected_msg), + )); + } + return Err(Error::new_spanned( + &type_path.path, + format!("Expected at least one path segment for {}", expected_msg), + )); + } + Err(Error::new_spanned(field_ty, format!("Expected a path type for {}", expected_msg))) +} diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.rs b/polkadot/xcm/procedural/src/enum_variants.rs similarity index 51% rename from polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.rs rename to polkadot/xcm/procedural/src/enum_variants.rs index 070f0be6bacc995aa38a341fa9d242d395c4e045..f9f2d9e156755f6a067ef96772d6f2dc51e42369 100644 --- a/polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.rs +++ b/polkadot/xcm/procedural/src/enum_variants.rs @@ -14,19 +14,25 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see <http://www.gnu.org/licenses/>. -//! Test error when an instruction that loads the holding register doesn't take operands. - -use xcm_procedural::Builder; - -struct Xcm<Call>(pub Vec<Instruction<Call>>); - -#[derive(Builder)] -enum Instruction<Call> { - #[builder(loads_holding)] - WithdrawAsset, - BuyExecution { fees: u128 }, - UnpaidExecution { weight_limit: (u32, u32) }, - Transact { call: Call }, +//! Simple derive macro for getting the number of variants in an enum. + +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use syn::{Data, DeriveInput, Error, Result}; + +pub fn derive(input: DeriveInput) -> Result<TokenStream2> { + let data_enum = match &input.data { + Data::Enum(data_enum) => data_enum, + _ => return Err(Error::new_spanned(&input, "Expected an enum.")), + }; + let ident = format_ident!("{}NumVariants", input.ident); + let number_of_variants: usize = data_enum.variants.iter().count(); + Ok(quote! { + pub struct #ident; + impl ::frame_support::traits::Get<u32> for #ident { + fn get() -> u32 { + #number_of_variants as u32 + } + } + }) } - -fn main() {} diff --git a/polkadot/xcm/procedural/src/lib.rs b/polkadot/xcm/procedural/src/lib.rs index 9971fdceb69a678ccd80f8191273ba6bfdef66f7..0dd270286f69ca53f23361cd229e8c8d7dda46fa 100644 --- a/polkadot/xcm/procedural/src/lib.rs +++ b/polkadot/xcm/procedural/src/lib.rs @@ -20,6 +20,7 @@ use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput}; mod builder_pattern; +mod enum_variants; mod v3; mod v4; mod v5; @@ -86,3 +87,11 @@ pub fn derive_builder(input: TokenStream) -> TokenStream { .unwrap_or_else(syn::Error::into_compile_error) .into() } + +#[proc_macro_derive(NumVariants)] +pub fn derive_num_variants(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + enum_variants::derive(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} diff --git a/polkadot/xcm/procedural/tests/builder_pattern.rs b/polkadot/xcm/procedural/tests/builder_pattern.rs index 4202309bf3f71cb6a6a81968ad19eea2cc999515..3915621916d4b849bad270133e903253e4dfd121 100644 --- a/polkadot/xcm/procedural/tests/builder_pattern.rs +++ b/polkadot/xcm/procedural/tests/builder_pattern.rs @@ -17,6 +17,7 @@ //! Test the methods generated by the Builder derive macro. //! Tests directly on the actual Xcm struct and Instruction enum. +use frame_support::BoundedVec; use xcm::latest::prelude::*; #[test] @@ -100,3 +101,61 @@ fn default_builder_allows_clear_origin_before_buy_execution() { ]) ); } + +#[test] +fn bounded_vecs_use_vecs_and_truncate_them() { + let claimer = Location::parent(); + // We can use a vec instead of a bounded vec for specifying hints. + let xcm: Xcm<()> = Xcm::builder_unsafe() + .set_hints(vec![AssetClaimer { location: claimer.clone() }]) + .build(); + assert_eq!( + xcm, + Xcm(vec![SetHints { + hints: BoundedVec::<Hint, HintNumVariants>::truncate_from(vec![AssetClaimer { + location: Location::parent() + },]), + },]) + ); + + // If we include more than the limit they'll get truncated. + let xcm: Xcm<()> = Xcm::builder_unsafe() + .set_hints(vec![ + AssetClaimer { location: claimer.clone() }, + AssetClaimer { location: Location::here() }, + ]) + .build(); + assert_eq!( + xcm, + Xcm(vec![SetHints { + hints: BoundedVec::<Hint, HintNumVariants>::truncate_from(vec![AssetClaimer { + location: Location::parent() + },]), + },]) + ); + + let xcm: Xcm<()> = Xcm::builder() + .withdraw_asset((Here, 100u128)) + .set_hints(vec![AssetClaimer { location: claimer }]) + .clear_origin() + .pay_fees((Here, 10u128)) + .deposit_asset(All, [0u8; 32]) + .build(); + assert_eq!( + xcm, + Xcm(vec![ + WithdrawAsset(Asset { id: AssetId(Location::here()), fun: Fungible(100) }.into()), + SetHints { + hints: BoundedVec::<Hint, HintNumVariants>::truncate_from(vec![AssetClaimer { + location: Location::parent() + }]) + }, + ClearOrigin, + PayFees { asset: Asset { id: AssetId(Location::here()), fun: Fungible(10) } }, + DepositAsset { + assets: All.into(), + beneficiary: AccountId32 { id: [0u8; 32], network: None }.into() + }, + ]) + ); +} diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.rs b/polkadot/xcm/procedural/tests/enum_variants.rs similarity index 70% rename from polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.rs rename to polkadot/xcm/procedural/tests/enum_variants.rs index bb98d603fd91567406063b72d6133ba87671b4c9..4a5362c1579a1b343090ab02a292fd47f2178eed 100644 --- a/polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.rs +++ b/polkadot/xcm/procedural/tests/enum_variants.rs @@ -14,17 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see <http://www.gnu.org/licenses/>. -//! Test error when the `BuyExecution` instruction doesn't take named fields. +//! Test the struct generated by the `NumVariants` derive macro. -use xcm_procedural::Builder; +use frame_support::traits::Get; +use xcm_procedural::NumVariants; -struct Xcm<Call>(pub Vec<Instruction<Call>>); - -#[derive(Builder)] -enum Instruction<Call> { - BuyExecution { fees: u128 }, - UnpaidExecution(u32, u32), - Transact { call: Call }, +#[allow(dead_code)] +#[derive(NumVariants)] +enum SomeEnum { + Variant1, + Variant2, + Variant3, } -fn main() {} +#[test] +fn num_variants_works() { + assert_eq!(SomeEnumNumVariants::get(), 3); +} diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.stderr b/polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.stderr deleted file mode 100644 index 0358a35ad3dd7bb48ddd51b69e4f395642d44edf..0000000000000000000000000000000000000000 --- a/polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.stderr +++ /dev/null @@ -1,6 +0,0 @@ -error: Instructions that load the holding register should take operands - --> tests/ui/builder_pattern/loads_holding_no_operands.rs:25:5 - | -25 | / #[builder(loads_holding)] -26 | | WithdrawAsset, - | |_________________^ diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/unexpected_attribute.stderr b/polkadot/xcm/procedural/tests/ui/builder_pattern/unexpected_attribute.stderr new file mode 100644 index 0000000000000000000000000000000000000000..c4d711e0d455afc08284e6851c31a6185ad7b725 --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/unexpected_attribute.stderr @@ -0,0 +1,5 @@ +error: Expected `builder(loads_holding)` or `builder(pays_fees)` + --> tests/ui/builder_pattern/unexpected_attribute.rs:25:5 + | +25 | #[builder(funds_holding)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.stderr b/polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.stderr deleted file mode 100644 index 0a3c0a40a33b0a5224f6b940c1ed18552a15fdf3..0000000000000000000000000000000000000000 --- a/polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: UnpaidExecution should have named fields - --> tests/ui/builder_pattern/unpaid_execution_named_fields.rs:26:5 - | -26 | UnpaidExecution(u32, u32), - | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/polkadot/xcm/src/lib.rs b/polkadot/xcm/src/lib.rs index a41a8e797b0f703302f62be04769c07ad2b9c3c4..2271835a9a5e87b6616e61f42d35ee8775abdb9b 100644 --- a/polkadot/xcm/src/lib.rs +++ b/polkadot/xcm/src/lib.rs @@ -25,7 +25,7 @@ extern crate alloc; use codec::{Decode, DecodeLimit, Encode, Error as CodecError, Input, MaxEncodedLen}; -use derivative::Derivative; +use derive_where::derive_where; use frame_support::dispatch::GetDispatchInfo; use scale_info::TypeInfo; @@ -88,13 +88,7 @@ macro_rules! versioned_type { $(#[$index5:meta])+ V5($v5:ty), }) => { - #[derive(Derivative, Encode, Decode, TypeInfo)] - #[derivative( - Clone(bound = ""), - Eq(bound = ""), - PartialEq(bound = ""), - Debug(bound = "") - )] + #[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, TypeInfo)] #[codec(encode_bound())] #[codec(decode_bound())] #[scale_info(replace_segment("staging_xcm", "xcm"))] @@ -311,8 +305,8 @@ versioned_type! { } /// A single XCM message, together with its version code. -#[derive(Derivative, Encode, Decode, TypeInfo)] -#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[derive(Encode, Decode, TypeInfo)] +#[derive_where(Clone, Eq, PartialEq, Debug)] #[codec(encode_bound())] #[codec(decode_bound())] #[scale_info(bounds(), skip_type_params(RuntimeCall))] diff --git a/polkadot/xcm/src/v3/mod.rs b/polkadot/xcm/src/v3/mod.rs index b60209a440c620f52746c407691213b664d215b7..6ae987a9830f1c6e426c1030316b97ecf6611262 100644 --- a/polkadot/xcm/src/v3/mod.rs +++ b/polkadot/xcm/src/v3/mod.rs @@ -28,7 +28,7 @@ use codec::{ MaxEncodedLen, }; use core::{fmt::Debug, result}; -use derivative::Derivative; +use derive_where::derive_where; use scale_info::TypeInfo; mod junction; @@ -57,8 +57,8 @@ pub const VERSION: super::Version = 3; /// An identifier for a query. pub type QueryId = u64; -#[derive(Derivative, Default, Encode, TypeInfo)] -#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[derive(Default, Encode, TypeInfo)] +#[derive_where(Clone, Eq, PartialEq, Debug)] #[codec(encode_bound())] #[scale_info(bounds(), skip_type_params(Call))] #[scale_info(replace_segment("staging_xcm", "xcm"))] @@ -474,15 +474,8 @@ impl XcmContext { /// /// This is the inner XCM format and is version-sensitive. Messages are typically passed using the /// outer XCM format, known as `VersionedXcm`. -#[derive( - Derivative, - Encode, - Decode, - TypeInfo, - xcm_procedural::XcmWeightInfoTrait, - xcm_procedural::Builder, -)] -#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[derive(Encode, Decode, TypeInfo, xcm_procedural::XcmWeightInfoTrait, xcm_procedural::Builder)] +#[derive_where(Clone, Eq, PartialEq, Debug)] #[codec(encode_bound())] #[codec(decode_bound())] #[scale_info(bounds(), skip_type_params(Call))] diff --git a/polkadot/xcm/src/v3/traits.rs b/polkadot/xcm/src/v3/traits.rs index 1c8620708922319f9934dbf5943f77f39164ed09..cbf85b454cc6c2ba019c763b0376291c3ae8dd68 100644 --- a/polkadot/xcm/src/v3/traits.rs +++ b/polkadot/xcm/src/v3/traits.rs @@ -547,13 +547,13 @@ impl SendXcm for Tuple { } /// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// both in `Some` before passing them as mutable references into `T::send_xcm`. pub fn validate_send<T: SendXcm>(dest: MultiLocation, msg: Xcm<()>) -> SendResult<T::Ticket> { T::validate(&mut Some(dest), &mut Some(msg)) } /// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// both in `Some` before passing them as mutable references into `T::send_xcm`. /// /// Returns either `Ok` with the price of the delivery, or `Err` with the reason why the message /// could not be sent. diff --git a/polkadot/xcm/src/v4/mod.rs b/polkadot/xcm/src/v4/mod.rs index 9baf58eacfb03bafb81af92870a23899e0379d3d..66816e2fb6e7e3e4aaee7cef5de32d647c679d66 100644 --- a/polkadot/xcm/src/v4/mod.rs +++ b/polkadot/xcm/src/v4/mod.rs @@ -35,7 +35,7 @@ use codec::{ MaxEncodedLen, }; use core::{fmt::Debug, result}; -use derivative::Derivative; +use derive_where::derive_where; use frame_support::dispatch::GetDispatchInfo; use scale_info::TypeInfo; @@ -65,8 +65,8 @@ pub const VERSION: super::Version = 4; /// An identifier for a query. pub type QueryId = u64; -#[derive(Derivative, Default, Encode, TypeInfo)] -#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[derive(Default, Encode, TypeInfo)] +#[derive_where(Clone, Eq, PartialEq, Debug)] #[codec(encode_bound())] #[codec(decode_bound())] #[scale_info(bounds(), skip_type_params(Call))] @@ -436,15 +436,8 @@ impl XcmContext { /// /// This is the inner XCM format and is version-sensitive. Messages are typically passed using the /// outer XCM format, known as `VersionedXcm`. -#[derive( - Derivative, - Encode, - Decode, - TypeInfo, - xcm_procedural::XcmWeightInfoTrait, - xcm_procedural::Builder, -)] -#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[derive(Encode, Decode, TypeInfo, xcm_procedural::XcmWeightInfoTrait, xcm_procedural::Builder)] +#[derive_where(Clone, Eq, PartialEq, Debug)] #[codec(encode_bound())] #[codec(decode_bound())] #[scale_info(bounds(), skip_type_params(Call))] @@ -1314,8 +1307,22 @@ impl<Call: Decode + GetDispatchInfo> TryFrom<NewInstruction<Call>> for Instructi HrmpChannelAccepted { recipient } => Self::HrmpChannelAccepted { recipient }, HrmpChannelClosing { initiator, sender, recipient } => Self::HrmpChannelClosing { initiator, sender, recipient }, - Transact { origin_kind, mut call } => { - let require_weight_at_most = call.take_decoded()?.get_dispatch_info().call_weight; + Transact { origin_kind, mut call, fallback_max_weight } => { + // We first try to decode the call, if we can't, we use the fallback weight, + // if there's no fallback, we just return `Weight::MAX`. + let require_weight_at_most = match call.take_decoded() { + Ok(decoded) => decoded.get_dispatch_info().call_weight, + Err(error) => { + let fallback_weight = fallback_max_weight.unwrap_or(Weight::MAX); + log::debug!( + target: "xcm::versions::v5Tov4", + "Couldn't decode call in Transact: {:?}, using fallback weight: {:?}", + error, + fallback_weight, + ); + fallback_weight + }, + }; Self::Transact { origin_kind, require_weight_at_most, call: call.into() } }, ReportError(response_info) => Self::ReportError(QueryResponseInfo { @@ -1423,7 +1430,7 @@ impl<Call: Decode + GetDispatchInfo> TryFrom<NewInstruction<Call>> for Instructi }, InitiateTransfer { .. } | PayFees { .. } | - SetAssetClaimer { .. } | + SetHints { .. } | ExecuteWithOrigin { .. } => { log::debug!(target: "xcm::versions::v5tov4", "`{new_instruction:?}` not supported by v4"); return Err(()); diff --git a/polkadot/xcm/src/v4/traits.rs b/polkadot/xcm/src/v4/traits.rs index f32b26fb163d357b5f2f025dd43abd3887685b2a..178093d271770325ca5d6a6593cb4e334a8baa2c 100644 --- a/polkadot/xcm/src/v4/traits.rs +++ b/polkadot/xcm/src/v4/traits.rs @@ -289,13 +289,13 @@ impl SendXcm for Tuple { } /// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// both in `Some` before passing them as mutable references into `T::send_xcm`. pub fn validate_send<T: SendXcm>(dest: Location, msg: Xcm<()>) -> SendResult<T::Ticket> { T::validate(&mut Some(dest), &mut Some(msg)) } /// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// both in `Some` before passing them as mutable references into `T::send_xcm`. /// /// Returns either `Ok` with the price of the delivery, or `Err` with the reason why the message /// could not be sent. diff --git a/polkadot/xcm/src/v5/junction.rs b/polkadot/xcm/src/v5/junction.rs index 952b61cd9ffed661b6b583bf0d8672a83ef3eb47..d86a762fcf44514b7b414ecf89e21988ebc3d42a 100644 --- a/polkadot/xcm/src/v5/junction.rs +++ b/polkadot/xcm/src/v5/junction.rs @@ -143,16 +143,20 @@ pub enum NetworkId { /// The Kusama canary-net Relay-chain. Kusama, /// An Ethereum network specified by its chain ID. + #[codec(index = 7)] Ethereum { /// The EIP-155 chain ID. #[codec(compact)] chain_id: u64, }, /// The Bitcoin network, including hard-forks supported by Bitcoin Core development team. + #[codec(index = 8)] BitcoinCore, /// The Bitcoin network, including hard-forks supported by Bitcoin Cash developers. + #[codec(index = 9)] BitcoinCash, /// The Polkadot Bulletin chain. + #[codec(index = 10)] PolkadotBulletin, } diff --git a/polkadot/xcm/src/v5/mod.rs b/polkadot/xcm/src/v5/mod.rs index 830b23cc44b76cc245b2f10afeb4aca15ce5ba2d..51f6d839e972abfc3b98e94c7e649f7f2b9426bb 100644 --- a/polkadot/xcm/src/v5/mod.rs +++ b/polkadot/xcm/src/v5/mod.rs @@ -29,7 +29,7 @@ use codec::{ MaxEncodedLen, }; use core::{fmt::Debug, result}; -use derivative::Derivative; +use derive_where::derive_where; use scale_info::TypeInfo; mod asset; @@ -59,8 +59,8 @@ pub const VERSION: super::Version = 5; /// An identifier for a query. pub type QueryId = u64; -#[derive(Derivative, Default, Encode, TypeInfo)] -#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[derive(Default, Encode, TypeInfo)] +#[derive_where(Clone, Eq, PartialEq, Debug)] #[codec(encode_bound())] #[codec(decode_bound())] #[scale_info(bounds(), skip_type_params(Call))] @@ -196,6 +196,8 @@ pub mod prelude { AssetInstance::{self, *}, Assets, BodyId, BodyPart, Error as XcmError, ExecuteXcm, Fungibility::{self, *}, + Hint::{self, *}, + HintNumVariants, Instruction::*, InteriorLocation, Junction::{self, *}, @@ -376,15 +378,8 @@ impl XcmContext { /// /// This is the inner XCM format and is version-sensitive. Messages are typically passed using the /// outer XCM format, known as `VersionedXcm`. -#[derive( - Derivative, - Encode, - Decode, - TypeInfo, - xcm_procedural::XcmWeightInfoTrait, - xcm_procedural::Builder, -)] -#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[derive(Encode, Decode, TypeInfo, xcm_procedural::XcmWeightInfoTrait, xcm_procedural::Builder)] +#[derive_where(Clone, Eq, PartialEq, Debug)] #[codec(encode_bound())] #[codec(decode_bound())] #[scale_info(bounds(), skip_type_params(Call))] @@ -493,13 +488,21 @@ pub enum Instruction<Call> { /// /// - `origin_kind`: The means of expressing the message origin as a dispatch origin. /// - `call`: The encoded transaction to be applied. + /// - `fallback_max_weight`: Used for compatibility with previous versions. Corresponds to the + /// `require_weight_at_most` parameter in previous versions. If you don't care about + /// compatibility you can just put `None`. WARNING: If you do, your XCM might not work with + /// older versions. Make sure to dry-run and validate. /// /// Safety: No concerns. /// /// Kind: *Command*. /// /// Errors: - Transact { origin_kind: OriginKind, call: DoubleEncoded<Call> }, + Transact { + origin_kind: OriginKind, + fallback_max_weight: Option<Weight>, + call: DoubleEncoded<Call>, + }, /// A message to notify about a new incoming HRMP channel. This message is meant to be sent by /// the relay-chain to a para. @@ -739,15 +742,6 @@ pub enum Instruction<Call> { /// Errors: None. ClearError, - /// Set asset claimer for all the trapped assets during the execution. - /// - /// - `location`: The claimer of any assets potentially trapped during the execution of current - /// XCM. It can be an arbitrary location, not necessarily the caller or origin. - /// - /// Kind: *Command* - /// - /// Errors: None. - SetAssetClaimer { location: Location }, /// Create some assets which are being held on behalf of the origin. /// /// - `assets`: The assets which are to be claimed. This must match exactly with the assets @@ -1128,6 +1122,25 @@ pub enum Instruction<Call> { /// Errors: /// - `BadOrigin` ExecuteWithOrigin { descendant_origin: Option<InteriorLocation>, xcm: Xcm<Call> }, + + /// Set hints for XCM execution. + /// + /// These hints change the behaviour of the XCM program they are present in. + /// + /// Parameters: + /// + /// - `hints`: A bounded vector of `ExecutionHint`, specifying the different hints that will + /// be activated. + SetHints { hints: BoundedVec<Hint, HintNumVariants> }, +} + +#[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Eq, Clone, xcm_procedural::NumVariants)] +pub enum Hint { + /// Set asset claimer for all the trapped assets during the execution. + /// + /// - `location`: The claimer of any assets potentially trapped during the execution of current + /// XCM. It can be an arbitrary location, not necessarily the caller or origin. + AssetClaimer { location: Location }, } impl<Call> Xcm<Call> { @@ -1159,7 +1172,8 @@ impl<Call> Instruction<Call> { HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, HrmpChannelClosing { initiator, sender, recipient } => HrmpChannelClosing { initiator, sender, recipient }, - Transact { origin_kind, call } => Transact { origin_kind, call: call.into() }, + Transact { origin_kind, call, fallback_max_weight } => + Transact { origin_kind, call: call.into(), fallback_max_weight }, ReportError(response_info) => ReportError(response_info), DepositAsset { assets, beneficiary } => DepositAsset { assets, beneficiary }, DepositReserveAsset { assets, dest, xcm } => DepositReserveAsset { assets, dest, xcm }, @@ -1175,7 +1189,7 @@ impl<Call> Instruction<Call> { SetErrorHandler(xcm) => SetErrorHandler(xcm.into()), SetAppendix(xcm) => SetAppendix(xcm.into()), ClearError => ClearError, - SetAssetClaimer { location } => SetAssetClaimer { location }, + SetHints { hints } => SetHints { hints }, ClaimAsset { assets, ticket } => ClaimAsset { assets, ticket }, Trap(code) => Trap(code), SubscribeVersion { query_id, max_response_weight } => @@ -1227,7 +1241,8 @@ impl<Call, W: XcmWeightInfo<Call>> GetWeight<W> for Instruction<Call> { TransferAsset { assets, beneficiary } => W::transfer_asset(assets, beneficiary), TransferReserveAsset { assets, dest, xcm } => W::transfer_reserve_asset(&assets, dest, xcm), - Transact { origin_kind, call } => W::transact(origin_kind, call), + Transact { origin_kind, fallback_max_weight, call } => + W::transact(origin_kind, fallback_max_weight, call), HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => W::hrmp_new_channel_open_request(sender, max_message_size, max_capacity), HrmpChannelAccepted { recipient } => W::hrmp_channel_accepted(recipient), @@ -1249,7 +1264,7 @@ impl<Call, W: XcmWeightInfo<Call>> GetWeight<W> for Instruction<Call> { SetErrorHandler(xcm) => W::set_error_handler(xcm), SetAppendix(xcm) => W::set_appendix(xcm), ClearError => W::clear_error(), - SetAssetClaimer { location } => W::set_asset_claimer(location), + SetHints { hints } => W::set_hints(hints), ClaimAsset { assets, ticket } => W::claim_asset(assets, ticket), Trap(code) => W::trap(code), SubscribeVersion { query_id, max_response_weight } => @@ -1343,8 +1358,11 @@ impl<Call> TryFrom<OldInstruction<Call>> for Instruction<Call> { HrmpChannelAccepted { recipient } => Self::HrmpChannelAccepted { recipient }, HrmpChannelClosing { initiator, sender, recipient } => Self::HrmpChannelClosing { initiator, sender, recipient }, - Transact { origin_kind, require_weight_at_most: _, call } => - Self::Transact { origin_kind, call: call.into() }, + Transact { origin_kind, require_weight_at_most, call } => Self::Transact { + origin_kind, + call: call.into(), + fallback_max_weight: Some(require_weight_at_most), + }, ReportError(response_info) => Self::ReportError(QueryResponseInfo { query_id: response_info.query_id, destination: response_info.destination.try_into().map_err(|_| ())?, @@ -1577,6 +1595,59 @@ mod tests { assert_eq!(new_xcm, xcm); } + #[test] + fn transact_roundtrip_works() { + // We can convert as long as there's a fallback. + let xcm = Xcm::<()>(vec![ + WithdrawAsset((Here, 1u128).into()), + Transact { + origin_kind: OriginKind::SovereignAccount, + call: vec![200, 200, 200].into(), + fallback_max_weight: Some(Weight::from_parts(1_000_000, 1_024)), + }, + ]); + let old_xcm = OldXcm::<()>(vec![ + OldInstruction::WithdrawAsset((OldHere, 1u128).into()), + OldInstruction::Transact { + origin_kind: OriginKind::SovereignAccount, + call: vec![200, 200, 200].into(), + require_weight_at_most: Weight::from_parts(1_000_000, 1_024), + }, + ]); + assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); + let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); + assert_eq!(new_xcm, xcm); + + // If we have no fallback the resulting message won't know the weight. + let xcm_without_fallback = Xcm::<()>(vec![ + WithdrawAsset((Here, 1u128).into()), + Transact { + origin_kind: OriginKind::SovereignAccount, + call: vec![200, 200, 200].into(), + fallback_max_weight: None, + }, + ]); + let old_xcm = OldXcm::<()>(vec![ + OldInstruction::WithdrawAsset((OldHere, 1u128).into()), + OldInstruction::Transact { + origin_kind: OriginKind::SovereignAccount, + call: vec![200, 200, 200].into(), + require_weight_at_most: Weight::MAX, + }, + ]); + assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm_without_fallback.clone()).unwrap()); + let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); + let xcm_with_max_weight_fallback = Xcm::<()>(vec![ + WithdrawAsset((Here, 1u128).into()), + Transact { + origin_kind: OriginKind::SovereignAccount, + call: vec![200, 200, 200].into(), + fallback_max_weight: Some(Weight::MAX), + }, + ]); + assert_eq!(new_xcm, xcm_with_max_weight_fallback); + } + #[test] fn decoding_respects_limit() { let max_xcm = Xcm::<()>(vec![ClearOrigin; MAX_INSTRUCTIONS_TO_DECODE as usize]); diff --git a/polkadot/xcm/src/v5/traits.rs b/polkadot/xcm/src/v5/traits.rs index 1f5041ca8d8422d7c3ead0f8c9e84124c4d7f5dd..79d3285614287fd98e7cbc8013dd2f45e209c4e5 100644 --- a/polkadot/xcm/src/v5/traits.rs +++ b/polkadot/xcm/src/v5/traits.rs @@ -428,6 +428,7 @@ pub type SendResult<T> = result::Result<(T, Assets), SendError>; /// let message = Xcm(vec![Instruction::Transact { /// origin_kind: OriginKind::Superuser, /// call: call.into(), +/// fallback_max_weight: None, /// }]); /// let message_hash = message.using_encoded(sp_io::hashing::blake2_256); /// @@ -459,6 +460,10 @@ pub trait SendXcm { /// Actually carry out the delivery operation for a previously validated message sending. fn deliver(ticket: Self::Ticket) -> result::Result<XcmHash, SendError>; + + /// Ensure `[Self::delivery]` is successful for the given `location` when called in benchmarks. + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(_location: Option<Location>) {} } #[impl_trait_for_tuples::impl_for_tuples(30)] @@ -499,16 +504,23 @@ impl SendXcm for Tuple { )* ); Err(SendError::Unroutable) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(location: Option<Location>) { + for_tuples!( #( + return Tuple::ensure_successful_delivery(location.clone()); + )* ); + } } /// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// both in `Some` before passing them as mutable references into `T::send_xcm`. pub fn validate_send<T: SendXcm>(dest: Location, msg: Xcm<()>) -> SendResult<T::Ticket> { T::validate(&mut Some(dest), &mut Some(msg)) } /// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// both in `Some` before passing them as mutable references into `T::send_xcm`. /// /// Returns either `Ok` with the price of the delivery, or `Err` with the reason why the message /// could not be sent. diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml index eaa115740f3e4e35ce0883f941a283c8bbe5d998..5169f586d723784e408afc3b4437635a6485ec02 100644 --- a/polkadot/xcm/xcm-builder/Cargo.toml +++ b/polkadot/xcm/xcm-builder/Cargo.toml @@ -5,40 +5,41 @@ authors.workspace = true edition.workspace = true license.workspace = true version = "7.0.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] -impl-trait-for-tuples = { workspace = true } codec = { features = ["derive"], workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +impl-trait-for-tuples = { workspace = true } +log = { workspace = true } +pallet-asset-conversion = { workspace = true } +pallet-transaction-payment = { workspace = true } scale-info = { features = ["derive"], workspace = true } -xcm = { workspace = true } -xcm-executor = { workspace = true } sp-arithmetic = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } sp-weights = { workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } -pallet-transaction-payment = { workspace = true } -pallet-asset-conversion = { workspace = true } -log = { workspace = true } +xcm = { workspace = true } +xcm-executor = { workspace = true } # Polkadot dependencies polkadot-parachain-primitives = { workspace = true } [dev-dependencies] -sp-core = { workspace = true, default-features = true } -primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } +pallet-assets = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } -pallet-xcm = { workspace = true, default-features = true } pallet-salary = { workspace = true, default-features = true } -pallet-assets = { workspace = true, default-features = true } +pallet-xcm = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-runtime-parachains = { workspace = true, default-features = true } -assert_matches = { workspace = true } polkadot-test-runtime = { workspace = true } +primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } +sp-core = { workspace = true, default-features = true } [features] default = ["std"] @@ -57,6 +58,7 @@ runtime-benchmarks = [ "polkadot-test-runtime/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] std = [ "codec/std", diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs index e6fe8e45c26518540a35550001c4a47f93ec7d18..55a924dbaa63e56ed8dbc565dcbe17e2fb4a3d54 100644 --- a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs +++ b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs @@ -339,6 +339,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig::<Runtime> { balances: vec![(0, INITIAL_BALANCE), (1, INITIAL_BALANCE), (2, INITIAL_BALANCE)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/xcm/xcm-builder/src/barriers.rs b/polkadot/xcm/xcm-builder/src/barriers.rs index 56a8493ef0ab2f519bd4a34440a234eb12600f12..9f9d3c2321228d088ab12078dfec83d7c00e9bc6 100644 --- a/polkadot/xcm/xcm-builder/src/barriers.rs +++ b/polkadot/xcm/xcm-builder/src/barriers.rs @@ -24,7 +24,7 @@ use frame_support::{ }; use polkadot_parachain_primitives::primitives::IsSystem; use xcm::prelude::*; -use xcm_executor::traits::{CheckSuspension, OnResponse, Properties, ShouldExecute}; +use xcm_executor::traits::{CheckSuspension, DenyExecution, OnResponse, Properties, ShouldExecute}; /// Execution barrier that just takes `max_weight` from `properties.weight_credit`. /// @@ -95,7 +95,8 @@ impl<T: Contains<Location>> ShouldExecute for AllowTopLevelPaidExecutionFrom<T> })? .skip_inst_while(|inst| { matches!(inst, ClearOrigin | AliasOrigin(..)) || - matches!(inst, DescendOrigin(child) if child != &Here) + matches!(inst, DescendOrigin(child) if child != &Here) || + matches!(inst, SetHints { .. }) })? .match_next_inst(|inst| match inst { BuyExecution { weight_limit: Limited(ref mut weight), .. } @@ -443,12 +444,12 @@ impl ShouldExecute for AllowHrmpNotificationsFromRelayChain { /// If it passes the Deny, and matches one of the Allow cases then it is let through. pub struct DenyThenTry<Deny, Allow>(PhantomData<Deny>, PhantomData<Allow>) where - Deny: ShouldExecute, + Deny: DenyExecution, Allow: ShouldExecute; impl<Deny, Allow> ShouldExecute for DenyThenTry<Deny, Allow> where - Deny: ShouldExecute, + Deny: DenyExecution, Allow: ShouldExecute, { fn should_execute<RuntimeCall>( @@ -457,15 +458,15 @@ where max_weight: Weight, properties: &mut Properties, ) -> Result<(), ProcessMessageError> { - Deny::should_execute(origin, message, max_weight, properties)?; + Deny::deny_execution(origin, message, max_weight, properties)?; Allow::should_execute(origin, message, max_weight, properties) } } // See issue <https://github.com/paritytech/polkadot/issues/5233> pub struct DenyReserveTransferToRelayChain; -impl ShouldExecute for DenyReserveTransferToRelayChain { - fn should_execute<RuntimeCall>( +impl DenyExecution for DenyReserveTransferToRelayChain { + fn deny_execution<RuntimeCall>( origin: &Location, message: &mut [Instruction<RuntimeCall>], _max_weight: Weight, @@ -489,7 +490,7 @@ impl ShouldExecute for DenyReserveTransferToRelayChain { if matches!(origin, Location { parents: 1, interior: Here }) => { log::warn!( - target: "xcm::barrier", + target: "xcm::barriers", "Unexpected ReserveAssetDeposited from the Relay Chain", ); Ok(ControlFlow::Continue(())) @@ -498,8 +499,6 @@ impl ShouldExecute for DenyReserveTransferToRelayChain { _ => Ok(ControlFlow::Continue(())), }, )?; - - // Permit everything else Ok(()) } } diff --git a/polkadot/xcm/xcm-builder/src/lib.rs b/polkadot/xcm/xcm-builder/src/lib.rs index 3d68d8ed16ae1459f7ac2dd39f1df80ecb44131e..e23412a97ebcb3e6f2999d0f48d555e53d02503e 100644 --- a/polkadot/xcm/xcm-builder/src/lib.rs +++ b/polkadot/xcm/xcm-builder/src/lib.rs @@ -132,11 +132,13 @@ pub use routing::{ mod transactional; pub use transactional::FrameTransactionalProcessor; +#[allow(deprecated)] +pub use universal_exports::UnpaidLocalExporter; mod universal_exports; pub use universal_exports::{ ensure_is_remote, BridgeBlobDispatcher, BridgeMessage, DispatchBlob, DispatchBlobError, - ExporterFor, HaulBlob, HaulBlobError, HaulBlobExporter, NetworkExportTable, - NetworkExportTableItem, SovereignPaidRemoteExporter, UnpaidLocalExporter, UnpaidRemoteExporter, + ExporterFor, HaulBlob, HaulBlobError, HaulBlobExporter, LocalExporter, NetworkExportTable, + NetworkExportTableItem, SovereignPaidRemoteExporter, UnpaidRemoteExporter, }; mod weight; diff --git a/polkadot/xcm/xcm-builder/src/pay.rs b/polkadot/xcm/xcm-builder/src/pay.rs index 978c6870cdaf1e65ffc74adb175fd2e80ba53031..0093051290b74bcda97721944361d2af562ed738 100644 --- a/polkadot/xcm/xcm-builder/src/pay.rs +++ b/polkadot/xcm/xcm-builder/src/pay.rs @@ -70,8 +70,8 @@ impl< Router: SendXcm, Querier: QueryHandler, Timeout: Get<Querier::BlockNumber>, - Beneficiary: Clone, - AssetKind, + Beneficiary: Clone + core::fmt::Debug, + AssetKind: core::fmt::Debug, AssetKindToLocatableAsset: TryConvert<AssetKind, LocatableAssetId>, BeneficiaryRefToLocation: for<'a> TryConvert<&'a Beneficiary, Location>, > Pay @@ -144,10 +144,9 @@ impl< } #[cfg(feature = "runtime-benchmarks")] - fn ensure_successful(_: &Self::Beneficiary, _: Self::AssetKind, _: Self::Balance) { - // We cannot generally guarantee this will go through successfully since we don't have any - // control over the XCM transport layers. We just assume that the benchmark environment - // will be sending it somewhere sensible. + fn ensure_successful(_: &Self::Beneficiary, asset_kind: Self::AssetKind, _: Self::Balance) { + let locatable = AssetKindToLocatableAsset::try_convert(asset_kind).unwrap(); + Router::ensure_successful_delivery(Some(locatable.location)); } #[cfg(feature = "runtime-benchmarks")] diff --git a/polkadot/xcm/xcm-builder/src/process_xcm_message.rs b/polkadot/xcm/xcm-builder/src/process_xcm_message.rs index 8dafbf66adf0a8db5775d2f07d05fae52e721ed1..67c05c116e9dcf1c384fb43b294df582ccae3891 100644 --- a/polkadot/xcm/xcm-builder/src/process_xcm_message.rs +++ b/polkadot/xcm/xcm-builder/src/process_xcm_message.rs @@ -58,7 +58,7 @@ impl< let message = Xcm::<Call>::try_from(versioned_message).map_err(|_| { log::trace!( target: LOG_TARGET, - "Failed to convert `VersionedXcm` into `XcmV3`.", + "Failed to convert `VersionedXcm` into `xcm::prelude::Xcm`!", ); ProcessMessageError::Unsupported diff --git a/polkadot/xcm/xcm-builder/src/routing.rs b/polkadot/xcm/xcm-builder/src/routing.rs index fc2de89d21285076fa2db281108ae9c98bacc7bb..5b0d0a5f983503b570ccc41259981a7bd1d549a2 100644 --- a/polkadot/xcm/xcm-builder/src/routing.rs +++ b/polkadot/xcm/xcm-builder/src/routing.rs @@ -60,6 +60,11 @@ impl<Inner: SendXcm> SendXcm for WithUniqueTopic<Inner> { Inner::deliver(ticket)?; Ok(unique_id) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(location: Option<Location>) { + Inner::ensure_successful_delivery(location); + } } impl<Inner: InspectMessageQueues> InspectMessageQueues for WithUniqueTopic<Inner> { fn clear_messages() { @@ -114,6 +119,11 @@ impl<Inner: SendXcm, TopicSource: SourceTopic> SendXcm for WithTopicSource<Inner Inner::deliver(ticket)?; Ok(unique_id) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(location: Option<Location>) { + Inner::ensure_successful_delivery(location); + } } /// Trait for a type which ensures all requirements for successful delivery with XCM transport @@ -211,4 +221,9 @@ impl<Inner: SendXcm> SendXcm for EnsureDecodableXcm<Inner> { fn deliver(ticket: Self::Ticket) -> Result<XcmHash, SendError> { Inner::deliver(ticket) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(location: Option<Location>) { + Inner::ensure_successful_delivery(location); + } } diff --git a/polkadot/xcm/xcm-builder/src/tests/barriers.rs b/polkadot/xcm/xcm-builder/src/tests/barriers.rs index cd2b6db66efcfed828139311d482700faaa02484..2fb8e8ed0363b09794f7e4720f8eeab58994e0ff 100644 --- a/polkadot/xcm/xcm-builder/src/tests/barriers.rs +++ b/polkadot/xcm/xcm-builder/src/tests/barriers.rs @@ -333,6 +333,26 @@ fn allow_paid_should_deprivilege_origin() { assert_eq!(r, Err(ProcessMessageError::Overweight(Weight::from_parts(30, 30)))); } +#[test] +fn allow_paid_should_allow_hints() { + AllowPaidFrom::set(vec![Parent.into()]); + let fees = (Parent, 1).into(); + + let mut paying_message_with_hints = Xcm::<()>(vec![ + ReserveAssetDeposited((Parent, 100).into()), + SetHints { hints: vec![AssetClaimer { location: Location::here() }].try_into().unwrap() }, + BuyExecution { fees, weight_limit: Limited(Weight::from_parts(30, 30)) }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() }, + ]); + let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute( + &Parent.into(), + paying_message_with_hints.inner_mut(), + Weight::from_parts(30, 30), + &mut props(Weight::zero()), + ); + assert_eq!(r, Ok(())); +} + #[test] fn suspension_should_work() { TestSuspender::set_suspended(true); @@ -512,3 +532,235 @@ fn allow_hrmp_notifications_from_relay_chain_should_work() { Ok(()), ); } + +#[test] +fn deny_then_try_works() { + /// A dummy `DenyExecution` impl which returns `ProcessMessageError::Yield` when XCM contains + /// `ClearTransactStatus` + struct DenyClearTransactStatusAsYield; + impl DenyExecution for DenyClearTransactStatusAsYield { + fn deny_execution<RuntimeCall>( + _origin: &Location, + instructions: &mut [Instruction<RuntimeCall>], + _max_weight: Weight, + _properties: &mut Properties, + ) -> Result<(), ProcessMessageError> { + instructions.matcher().match_next_inst_while( + |_| true, + |inst| match inst { + ClearTransactStatus { .. } => Err(ProcessMessageError::Yield), + _ => Ok(ControlFlow::Continue(())), + }, + )?; + Ok(()) + } + } + + /// A dummy `DenyExecution` impl which returns `ProcessMessageError::BadFormat` when XCM + /// contains `ClearOrigin` with origin location from `Here` + struct DenyClearOriginFromHereAsBadFormat; + impl DenyExecution for DenyClearOriginFromHereAsBadFormat { + fn deny_execution<RuntimeCall>( + origin: &Location, + instructions: &mut [Instruction<RuntimeCall>], + _max_weight: Weight, + _properties: &mut Properties, + ) -> Result<(), ProcessMessageError> { + instructions.matcher().match_next_inst_while( + |_| true, + |inst| match inst { + ClearOrigin { .. } => + if origin.clone() == Here.into_location() { + Err(ProcessMessageError::BadFormat) + } else { + Ok(ControlFlow::Continue(())) + }, + _ => Ok(ControlFlow::Continue(())), + }, + )?; + Ok(()) + } + } + + /// A dummy `DenyExecution` impl which returns `ProcessMessageError::StackLimitReached` when XCM + /// contains a single `UnsubscribeVersion` + struct DenyUnsubscribeVersionAsStackLimitReached; + impl DenyExecution for DenyUnsubscribeVersionAsStackLimitReached { + fn deny_execution<RuntimeCall>( + _origin: &Location, + instructions: &mut [Instruction<RuntimeCall>], + _max_weight: Weight, + _properties: &mut Properties, + ) -> Result<(), ProcessMessageError> { + if instructions.len() != 1 { + return Ok(()) + } + match instructions.get(0).unwrap() { + UnsubscribeVersion { .. } => Err(ProcessMessageError::StackLimitReached), + _ => Ok(()), + } + } + } + + /// A dummy `ShouldExecute` impl which returns `Ok(())` when XCM contains a single `ClearError`, + /// else return `ProcessMessageError::Yield` + struct AllowSingleClearErrorOrYield; + impl ShouldExecute for AllowSingleClearErrorOrYield { + fn should_execute<Call>( + _origin: &Location, + instructions: &mut [Instruction<Call>], + _max_weight: Weight, + _properties: &mut Properties, + ) -> Result<(), ProcessMessageError> { + instructions.matcher().assert_remaining_insts(1)?.match_next_inst( + |inst| match inst { + ClearError { .. } => Ok(()), + _ => Err(ProcessMessageError::Yield), + }, + )?; + Ok(()) + } + } + + /// A dummy `ShouldExecute` impl which returns `Ok(())` when XCM contains `ClearTopic` and + /// origin from `Here`, else return `ProcessMessageError::Unsupported` + struct AllowClearTopicFromHere; + impl ShouldExecute for AllowClearTopicFromHere { + fn should_execute<Call>( + origin: &Location, + instructions: &mut [Instruction<Call>], + _max_weight: Weight, + _properties: &mut Properties, + ) -> Result<(), ProcessMessageError> { + ensure!(origin.clone() == Here.into_location(), ProcessMessageError::Unsupported); + let mut found = false; + instructions.matcher().match_next_inst_while( + |_| true, + |inst| match inst { + ClearTopic { .. } => { + found = true; + Ok(ControlFlow::Break(())) + }, + _ => Ok(ControlFlow::Continue(())), + }, + )?; + ensure!(found, ProcessMessageError::Unsupported); + Ok(()) + } + } + // closure for (xcm, origin) testing with `DenyThenTry` + let assert_should_execute = |mut xcm: Vec<Instruction<()>>, origin, expected_result| { + pub type Barrier = DenyThenTry< + ( + DenyClearTransactStatusAsYield, + DenyClearOriginFromHereAsBadFormat, + DenyUnsubscribeVersionAsStackLimitReached, + ), + (AllowSingleClearErrorOrYield, AllowClearTopicFromHere), + >; + assert_eq!( + Barrier::should_execute( + &origin, + &mut xcm, + Weight::from_parts(10, 10), + &mut props(Weight::zero()), + ), + expected_result + ); + }; + + // Deny cases: + // trigger DenyClearTransactStatusAsYield + assert_should_execute( + vec![ClearTransactStatus], + Parachain(1).into_location(), + Err(ProcessMessageError::Yield), + ); + // DenyClearTransactStatusAsYield wins against AllowSingleClearErrorOrYield + assert_should_execute( + vec![ClearError, ClearTransactStatus], + Parachain(1).into_location(), + Err(ProcessMessageError::Yield), + ); + // trigger DenyClearOriginFromHereAsBadFormat + assert_should_execute( + vec![ClearOrigin], + Here.into_location(), + Err(ProcessMessageError::BadFormat), + ); + // trigger DenyUnsubscribeVersionAsStackLimitReached + assert_should_execute( + vec![UnsubscribeVersion], + Here.into_location(), + Err(ProcessMessageError::StackLimitReached), + ); + + // deny because none of the allow items match + assert_should_execute( + vec![ClearError, ClearTopic], + Parachain(1).into_location(), + Err(ProcessMessageError::Unsupported), + ); + + // ok + assert_should_execute(vec![ClearError], Parachain(1).into_location(), Ok(())); + assert_should_execute(vec![ClearTopic], Here.into(), Ok(())); + assert_should_execute(vec![ClearError, ClearTopic], Here.into_location(), Ok(())); +} + +#[test] +fn deny_reserve_transfer_to_relaychain_should_work() { + let assert_deny_execution = |mut xcm: Vec<Instruction<()>>, origin, expected_result| { + assert_eq!( + DenyReserveTransferToRelayChain::deny_execution( + &origin, + &mut xcm, + Weight::from_parts(10, 10), + &mut props(Weight::zero()), + ), + expected_result + ); + }; + // deny DepositReserveAsset to RelayChain + assert_deny_execution( + vec![DepositReserveAsset { + assets: Wild(All), + dest: Location::parent(), + xcm: vec![].into(), + }], + Here.into_location(), + Err(ProcessMessageError::Unsupported), + ); + // deny InitiateReserveWithdraw to RelayChain + assert_deny_execution( + vec![InitiateReserveWithdraw { + assets: Wild(All), + reserve: Location::parent(), + xcm: vec![].into(), + }], + Here.into_location(), + Err(ProcessMessageError::Unsupported), + ); + // deny TransferReserveAsset to RelayChain + assert_deny_execution( + vec![TransferReserveAsset { + assets: vec![].into(), + dest: Location::parent(), + xcm: vec![].into(), + }], + Here.into_location(), + Err(ProcessMessageError::Unsupported), + ); + // accept DepositReserveAsset to destination other than RelayChain + assert_deny_execution( + vec![DepositReserveAsset { + assets: Wild(All), + dest: Here.into_location(), + xcm: vec![].into(), + }], + Here.into_location(), + Ok(()), + ); + // others instructions should pass + assert_deny_execution(vec![ClearOrigin], Here.into_location(), Ok(())); +} diff --git a/polkadot/xcm/xcm-builder/src/tests/bridging/local_para_para.rs b/polkadot/xcm/xcm-builder/src/tests/bridging/local_para_para.rs index ea584bf9d485a25c38ce0b673259b6ed8df7368f..5e930fe575c2b07c3c8d172f478d7f20ff519e76 100644 --- a/polkadot/xcm/xcm-builder/src/tests/bridging/local_para_para.rs +++ b/polkadot/xcm/xcm-builder/src/tests/bridging/local_para_para.rs @@ -28,7 +28,7 @@ parameter_types! { type TheBridge = TestBridge<BridgeBlobDispatcher<TestRemoteIncomingRouter, RemoteUniversalLocation, ()>>; type Router = TestTopic< - UnpaidLocalExporter< + LocalExporter< HaulBlobExporter<TheBridge, RemoteNetwork, AlwaysLatest, Price>, UniversalLocation, >, diff --git a/polkadot/xcm/xcm-builder/src/tests/bridging/local_relay_relay.rs b/polkadot/xcm/xcm-builder/src/tests/bridging/local_relay_relay.rs index 38ffe2532d580a017efc21043e7313028700c046..a41f09721812a99f71fdcb58345e265472b163c6 100644 --- a/polkadot/xcm/xcm-builder/src/tests/bridging/local_relay_relay.rs +++ b/polkadot/xcm/xcm-builder/src/tests/bridging/local_relay_relay.rs @@ -28,7 +28,7 @@ parameter_types! { type TheBridge = TestBridge<BridgeBlobDispatcher<TestRemoteIncomingRouter, RemoteUniversalLocation, ()>>; type Router = TestTopic< - UnpaidLocalExporter< + LocalExporter< HaulBlobExporter<TheBridge, RemoteNetwork, AlwaysLatest, Price>, UniversalLocation, >, diff --git a/polkadot/xcm/xcm-builder/src/tests/bridging/mod.rs b/polkadot/xcm/xcm-builder/src/tests/bridging/mod.rs index 767575e7f2dd9efa29cc1441a8cc2bf2cdaf3d19..90ad9921d65a13b8b5590507d0a7c36ba79b063a 100644 --- a/polkadot/xcm/xcm-builder/src/tests/bridging/mod.rs +++ b/polkadot/xcm/xcm-builder/src/tests/bridging/mod.rs @@ -209,7 +209,7 @@ impl<Local: Get<Junctions>, Remote: Get<Junctions>, RemoteExporter: ExportXcm> S let origin = Local::get().relative_to(&Remote::get()); AllowUnpaidFrom::set(vec![origin.clone()]); set_exporter_override(price::<RemoteExporter>, deliver::<RemoteExporter>); - // The we execute it: + // Then we execute it: let mut id = fake_id(); let outcome = XcmExecutor::<TestConfig>::prepare_and_execute( origin, diff --git a/polkadot/xcm/xcm-builder/src/tests/mock.rs b/polkadot/xcm/xcm-builder/src/tests/mock.rs index bec7b253977b6de1319cd3e974e7b74d8a36835d..127888104a4ad77b0272d8e230f724d3e1cca7ac 100644 --- a/polkadot/xcm/xcm-builder/src/tests/mock.rs +++ b/polkadot/xcm/xcm-builder/src/tests/mock.rs @@ -31,6 +31,7 @@ pub use codec::{Decode, Encode}; pub use core::{ cell::{Cell, RefCell}, fmt::Debug, + ops::ControlFlow, }; use frame_support::traits::{ContainsPair, Everything}; pub use frame_support::{ @@ -40,11 +41,11 @@ pub use frame_support::{ traits::{Contains, Get, IsInVec}, }; pub use xcm::latest::{prelude::*, QueryId, Weight}; -use xcm_executor::traits::{Properties, QueryHandler, QueryResponseStatus}; pub use xcm_executor::{ traits::{ - AssetExchange, AssetLock, CheckSuspension, ConvertOrigin, Enact, ExportXcm, FeeManager, - FeeReason, LockError, OnResponse, TransactAsset, + AssetExchange, AssetLock, CheckSuspension, ConvertOrigin, DenyExecution, Enact, ExportXcm, + FeeManager, FeeReason, LockError, OnResponse, Properties, QueryHandler, + QueryResponseStatus, TransactAsset, }, AssetsInHolding, Config, }; diff --git a/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs b/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs index 26ea226313f068d9203bd8057790d3665bae2d11..6ebf6476f7e570f15a37908c40ea53c9810b2875 100644 --- a/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs +++ b/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs @@ -37,6 +37,7 @@ pub type TxExtension = ( frame_system::CheckMortality<Test>, frame_system::CheckNonce<Test>, frame_system::CheckWeight<Test>, + frame_system::WeightReclaim<Test>, ); pub type Address = sp_runtime::MultiAddress<AccountId, AccountIndex>; pub type UncheckedExtrinsic = diff --git a/polkadot/xcm/xcm-builder/src/tests/pay/pay.rs b/polkadot/xcm/xcm-builder/src/tests/pay/pay.rs index 062faee2abd96a07afda49883adf86e9b2107e5a..b4718edc6c9889cf8c5b248a2a97dd19c3b5eac5 100644 --- a/polkadot/xcm/xcm-builder/src/tests/pay/pay.rs +++ b/polkadot/xcm/xcm-builder/src/tests/pay/pay.rs @@ -22,7 +22,7 @@ use frame_support::{assert_ok, traits::tokens::Pay}; /// Type representing both a location and an asset that is held at that location. /// The id of the held asset is relative to the location where it is being held. -#[derive(Encode, Decode, Clone, PartialEq, Eq)] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] pub struct AssetKind { destination: Location, asset_id: AssetId, diff --git a/polkadot/xcm/xcm-builder/src/tests/transacting.rs b/polkadot/xcm/xcm-builder/src/tests/transacting.rs index 8963e7147fdc03fbb6a68cf77308f9ee99940e46..ba932beaeb3d9bdd6fe545ec49cfc513ec5e26a7 100644 --- a/polkadot/xcm/xcm-builder/src/tests/transacting.rs +++ b/polkadot/xcm/xcm-builder/src/tests/transacting.rs @@ -23,6 +23,7 @@ fn transacting_should_work() { let message = Xcm::<TestCall>(vec![Transact { origin_kind: OriginKind::Native, call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }]); let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(60, 60); @@ -43,6 +44,7 @@ fn transacting_should_respect_max_weight_requirement() { let message = Xcm::<TestCall>(vec![Transact { origin_kind: OriginKind::Native, call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }]); let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(60, 60); @@ -65,6 +67,7 @@ fn transacting_should_refund_weight() { call: TestCall::Any(Weight::from_parts(50, 50), Some(Weight::from_parts(30, 30))) .encode() .into(), + fallback_max_weight: None, }]); let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(60, 60); @@ -96,6 +99,7 @@ fn paid_transacting_should_refund_payment_for_unused_weight() { call: TestCall::Any(Weight::from_parts(50, 50), Some(Weight::from_parts(10, 10))) .encode() .into(), + fallback_max_weight: None, }, RefundSurplus, DepositAsset { assets: AllCounted(1).into(), beneficiary: one }, @@ -124,6 +128,7 @@ fn report_successful_transact_status_should_work() { Transact { origin_kind: OriginKind::Native, call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }, ReportTransactStatus(QueryResponseInfo { destination: Parent.into(), @@ -159,6 +164,7 @@ fn report_failed_transact_status_should_work() { Transact { origin_kind: OriginKind::Native, call: TestCall::OnlyRoot(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }, ReportTransactStatus(QueryResponseInfo { destination: Parent.into(), @@ -194,6 +200,7 @@ fn expect_successful_transact_status_should_work() { Transact { origin_kind: OriginKind::Native, call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }, ExpectTransactStatus(MaybeErrorCode::Success), ]); @@ -212,6 +219,7 @@ fn expect_successful_transact_status_should_work() { Transact { origin_kind: OriginKind::Native, call: TestCall::OnlyRoot(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }, ExpectTransactStatus(MaybeErrorCode::Success), ]); @@ -238,6 +246,7 @@ fn expect_failed_transact_status_should_work() { Transact { origin_kind: OriginKind::Native, call: TestCall::OnlyRoot(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }, ExpectTransactStatus(vec![2].into()), ]); @@ -256,6 +265,7 @@ fn expect_failed_transact_status_should_work() { Transact { origin_kind: OriginKind::Native, call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }, ExpectTransactStatus(vec![2].into()), ]); @@ -282,6 +292,7 @@ fn clear_transact_status_should_work() { Transact { origin_kind: OriginKind::Native, call: TestCall::OnlyRoot(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }, ClearTransactStatus, ReportTransactStatus(QueryResponseInfo { diff --git a/polkadot/xcm/xcm-builder/src/universal_exports.rs b/polkadot/xcm/xcm-builder/src/universal_exports.rs index 5c754f01ec0ad2b5ebdd29a4ab4b3cc4649ea393..e215aea3ab6858a0bcecd5b952cb4fd1384f52a9 100644 --- a/polkadot/xcm/xcm-builder/src/universal_exports.rs +++ b/polkadot/xcm/xcm-builder/src/universal_exports.rs @@ -16,6 +16,8 @@ //! Traits and utilities to help with origin mutation and bridging. +#![allow(deprecated)] + use crate::InspectMessageQueues; use alloc::{vec, vec::Vec}; use codec::{Decode, Encode}; @@ -58,6 +60,8 @@ pub fn ensure_is_remote( /// that the message sending cannot be abused in any way. /// /// This is only useful when the local chain has bridging capabilities. +#[deprecated(note = "Will be removed after July 2025; It uses hard-coded channel `0`, \ + use `xcm_builder::LocalExporter` directly instead.")] pub struct UnpaidLocalExporter<Exporter, UniversalLocation>( PhantomData<(Exporter, UniversalLocation)>, ); @@ -68,25 +72,84 @@ impl<Exporter: ExportXcm, UniversalLocation: Get<InteriorLocation>> SendXcm fn validate( dest: &mut Option<Location>, - xcm: &mut Option<Xcm<()>>, + msg: &mut Option<Xcm<()>>, ) -> SendResult<Exporter::Ticket> { - let d = dest.take().ok_or(MissingArgument)?; + // This `clone` ensures that `dest` is not consumed in any case. + let d = dest.clone().take().ok_or(MissingArgument)?; let universal_source = UniversalLocation::get(); - let devolved = match ensure_is_remote(universal_source.clone(), d) { - Ok(x) => x, - Err(d) => { - *dest = Some(d); - return Err(NotApplicable) - }, - }; - let (network, destination) = devolved; - let xcm = xcm.take().ok_or(SendError::MissingArgument)?; - validate_export::<Exporter>(network, 0, universal_source, destination, xcm) + let devolved = ensure_is_remote(universal_source.clone(), d).map_err(|_| NotApplicable)?; + let (remote_network, remote_location) = devolved; + let xcm = msg.take().ok_or(MissingArgument)?; + + validate_export::<Exporter>( + remote_network, + 0, + universal_source, + remote_location, + xcm.clone(), + ) + .inspect_err(|err| { + if let NotApplicable = err { + // We need to make sure that msg is not consumed in case of `NotApplicable`. + *msg = Some(xcm); + } + }) + } + + fn deliver(ticket: Exporter::Ticket) -> Result<XcmHash, SendError> { + Exporter::deliver(ticket) + } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(_: Option<Location>) {} +} + +/// Implementation of `SendXcm` which uses the given `ExportXcm` implementation in order to forward +/// the message over a bridge. +/// +/// This is only useful when the local chain has bridging capabilities. +pub struct LocalExporter<Exporter, UniversalLocation>(PhantomData<(Exporter, UniversalLocation)>); +impl<Exporter: ExportXcm, UniversalLocation: Get<InteriorLocation>> SendXcm + for LocalExporter<Exporter, UniversalLocation> +{ + type Ticket = Exporter::Ticket; + + fn validate( + dest: &mut Option<Location>, + msg: &mut Option<Xcm<()>>, + ) -> SendResult<Exporter::Ticket> { + // This `clone` ensures that `dest` is not consumed in any case. + let d = dest.clone().take().ok_or(MissingArgument)?; + let universal_source = UniversalLocation::get(); + let devolved = ensure_is_remote(universal_source.clone(), d).map_err(|_| NotApplicable)?; + let (remote_network, remote_location) = devolved; + let xcm = msg.take().ok_or(MissingArgument)?; + + let hash = + (Some(Location::here()), &remote_location).using_encoded(sp_io::hashing::blake2_128); + let channel = u32::decode(&mut hash.as_ref()).unwrap_or(0); + + validate_export::<Exporter>( + remote_network, + channel, + universal_source, + remote_location, + xcm.clone(), + ) + .inspect_err(|err| { + if let NotApplicable = err { + // We need to make sure that msg is not consumed in case of `NotApplicable`. + *msg = Some(xcm); + } + }) } fn deliver(ticket: Exporter::Ticket) -> Result<XcmHash, SendError> { Exporter::deliver(ticket) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(_: Option<Location>) {} } pub trait ExporterFor { @@ -95,7 +158,7 @@ pub trait ExporterFor { /// /// The payment is specified from the local context, not the bridge chain. This is the /// total amount to withdraw in to Holding and should cover both payment for the execution on - /// the bridge chain as well as payment for the use of the `ExportMessage` instruction. + /// the bridge chain and payment for the use of the `ExportMessage` instruction. fn exporter_for( network: &NetworkId, remote_location: &InteriorLocation, @@ -205,7 +268,8 @@ impl<Bridges: ExporterFor, Router: SendXcm, UniversalLocation: Get<InteriorLocat dest: &mut Option<Location>, msg: &mut Option<Xcm<()>>, ) -> SendResult<Router::Ticket> { - let d = dest.clone().ok_or(MissingArgument)?; + // This `clone` ensures that `dest` is not consumed in any case. + let d = dest.clone().take().ok_or(MissingArgument)?; let devolved = ensure_is_remote(UniversalLocation::get(), d).map_err(|_| NotApplicable)?; let (remote_network, remote_location) = devolved; let xcm = msg.take().ok_or(MissingArgument)?; @@ -216,7 +280,7 @@ impl<Bridges: ExporterFor, Router: SendXcm, UniversalLocation: Get<InteriorLocat else { // We need to make sure that msg is not consumed in case of `NotApplicable`. *msg = Some(xcm); - return Err(SendError::NotApplicable) + return Err(NotApplicable) }; ensure!(maybe_payment.is_none(), Unroutable); @@ -232,17 +296,31 @@ impl<Bridges: ExporterFor, Router: SendXcm, UniversalLocation: Get<InteriorLocat // export for free. Common-good chains will typically be afforded this. let mut message = Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, - ExportMessage { network: remote_network, destination: remote_location, xcm }, + ExportMessage { + network: remote_network, + destination: remote_location, + xcm: xcm.clone(), + }, ]); if let Some(forward_id) = maybe_forward_id { message.0.push(SetTopic(forward_id)); } - validate_send::<Router>(bridge, message) + validate_send::<Router>(bridge, message).inspect_err(|err| { + if let NotApplicable = err { + // We need to make sure that msg is not consumed in case of `NotApplicable`. + *msg = Some(xcm); + } + }) } fn deliver(validation: Self::Ticket) -> Result<XcmHash, SendError> { Router::deliver(validation) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(location: Option<Location>) { + Router::ensure_successful_delivery(location); + } } /// Implementation of `SendXcm` which wraps the message inside an `ExportMessage` instruction @@ -272,9 +350,9 @@ impl<Bridges: ExporterFor, Router: SendXcm, UniversalLocation: Get<InteriorLocat dest: &mut Option<Location>, msg: &mut Option<Xcm<()>>, ) -> SendResult<Router::Ticket> { - let d = dest.as_ref().ok_or(MissingArgument)?; - let devolved = - ensure_is_remote(UniversalLocation::get(), d.clone()).map_err(|_| NotApplicable)?; + // This `clone` ensures that `dest` is not consumed in any case. + let d = dest.clone().take().ok_or(MissingArgument)?; + let devolved = ensure_is_remote(UniversalLocation::get(), d).map_err(|_| NotApplicable)?; let (remote_network, remote_location) = devolved; let xcm = msg.take().ok_or(MissingArgument)?; @@ -284,7 +362,7 @@ impl<Bridges: ExporterFor, Router: SendXcm, UniversalLocation: Get<InteriorLocat else { // We need to make sure that msg is not consumed in case of `NotApplicable`. *msg = Some(xcm); - return Err(SendError::NotApplicable) + return Err(NotApplicable) }; // `xcm` should already end with `SetTopic` - if it does, then extract and derive into @@ -296,8 +374,11 @@ impl<Bridges: ExporterFor, Router: SendXcm, UniversalLocation: Get<InteriorLocat let local_from_bridge = UniversalLocation::get().invert_target(&bridge).map_err(|_| Unroutable)?; - let export_instruction = - ExportMessage { network: remote_network, destination: remote_location, xcm }; + let export_instruction = ExportMessage { + network: remote_network, + destination: remote_location, + xcm: xcm.clone(), + }; let mut message = Xcm(if let Some(ref payment) = maybe_payment { let fees = payment @@ -325,7 +406,12 @@ impl<Bridges: ExporterFor, Router: SendXcm, UniversalLocation: Get<InteriorLocat // We then send a normal message to the bridge asking it to export the prepended // message to the remote chain. - let (v, mut cost) = validate_send::<Router>(bridge, message)?; + let (v, mut cost) = validate_send::<Router>(bridge, message).inspect_err(|err| { + if let NotApplicable = err { + // We need to make sure that msg is not consumed in case of `NotApplicable`. + *msg = Some(xcm); + } + })?; if let Some(bridge_payment) = maybe_payment { cost.push(bridge_payment); } @@ -335,6 +421,11 @@ impl<Bridges: ExporterFor, Router: SendXcm, UniversalLocation: Get<InteriorLocat fn deliver(ticket: Router::Ticket) -> Result<XcmHash, SendError> { Router::deliver(ticket) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(location: Option<Location>) { + Router::ensure_successful_delivery(location); + } } impl<Bridges, Router, UniversalLocation> InspectMessageQueues @@ -476,10 +567,10 @@ impl< let Location { parents, interior: mut junctions } = BridgedNetwork::get(); match junctions.take_first() { Some(GlobalConsensus(network)) => (network, parents), - _ => return Err(SendError::NotApplicable), + _ => return Err(NotApplicable), } }; - ensure!(&network == &bridged_network, SendError::NotApplicable); + ensure!(&network == &bridged_network, NotApplicable); // We don't/can't use the `channel` for this adapter. let dest = destination.take().ok_or(SendError::MissingArgument)?; @@ -496,7 +587,7 @@ impl< }, Err((dest, _)) => { *destination = Some(dest); - return Err(SendError::NotApplicable) + return Err(NotApplicable) }, }; @@ -540,6 +631,10 @@ impl< #[cfg(test)] mod tests { use super::*; + use frame_support::{ + assert_err, assert_ok, + traits::{Contains, Equals}, + }; #[test] fn ensure_is_remote_works() { @@ -564,20 +659,50 @@ mod tests { assert_eq!(x, Err((Parent, Polkadot, Parachain(1000)).into())); } - pub struct OkSender; - impl SendXcm for OkSender { + pub struct OkFor<Filter>(PhantomData<Filter>); + impl<Filter: Contains<Location>> SendXcm for OkFor<Filter> { type Ticket = (); fn validate( - _destination: &mut Option<Location>, + destination: &mut Option<Location>, _message: &mut Option<Xcm<()>>, ) -> SendResult<Self::Ticket> { - Ok(((), Assets::new())) + if let Some(d) = destination.as_ref() { + if Filter::contains(&d) { + return Ok(((), Assets::new())) + } + } + Err(NotApplicable) } fn deliver(_ticket: Self::Ticket) -> Result<XcmHash, SendError> { Ok([0; 32]) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(_: Option<Location>) {} + } + impl<Filter: Contains<(NetworkId, InteriorLocation)>> ExportXcm for OkFor<Filter> { + type Ticket = (); + + fn validate( + network: NetworkId, + _: u32, + _: &mut Option<InteriorLocation>, + destination: &mut Option<InteriorLocation>, + _: &mut Option<Xcm<()>>, + ) -> SendResult<Self::Ticket> { + if let Some(d) = destination.as_ref() { + if Filter::contains(&(network, d.clone())) { + return Ok(((), Assets::new())) + } + } + Err(NotApplicable) + } + + fn deliver(_ticket: Self::Ticket) -> Result<XcmHash, SendError> { + Ok([1; 32]) + } } /// Generic test case asserting that dest and msg is not consumed by `validate` implementation @@ -598,46 +723,168 @@ mod tests { } #[test] - fn remote_exporters_does_not_consume_dest_or_msg_on_not_applicable() { + fn local_exporters_works() { frame_support::parameter_types! { pub Local: NetworkId = ByGenesis([0; 32]); pub UniversalLocation: InteriorLocation = [GlobalConsensus(Local::get()), Parachain(1234)].into(); pub DifferentRemote: NetworkId = ByGenesis([22; 32]); - // no routers - pub BridgeTable: Vec<NetworkExportTableItem> = vec![]; + pub RemoteDestination: Junction = Parachain(9657); + pub RoutableBridgeFilter: (NetworkId, InteriorLocation) = (DifferentRemote::get(), RemoteDestination::get().into()); } + type RoutableBridgeExporter = OkFor<Equals<RoutableBridgeFilter>>; + type NotApplicableBridgeExporter = OkFor<()>; + assert_ok!(validate_export::<RoutableBridgeExporter>( + DifferentRemote::get(), + 0, + UniversalLocation::get(), + RemoteDestination::get().into(), + Xcm::default() + )); + assert_err!( + validate_export::<NotApplicableBridgeExporter>( + DifferentRemote::get(), + 0, + UniversalLocation::get(), + RemoteDestination::get().into(), + Xcm::default() + ), + NotApplicable + ); - // check with local destination (should be remote) + // 1. check with local destination (should be remote) let local_dest: Location = (Parent, Parachain(5678)).into(); assert!(ensure_is_remote(UniversalLocation::get(), local_dest.clone()).is_err()); + // LocalExporter ensure_validate_does_not_consume_dest_or_msg::< - UnpaidRemoteExporter<NetworkExportTable<BridgeTable>, OkSender, UniversalLocation>, + LocalExporter<RoutableBridgeExporter, UniversalLocation>, >(local_dest.clone(), |result| assert_eq!(Err(NotApplicable), result)); + // 2. check with not applicable from the inner router (using `NotApplicableBridgeSender`) + let remote_dest: Location = + (Parent, Parent, DifferentRemote::get(), RemoteDestination::get()).into(); + assert!(ensure_is_remote(UniversalLocation::get(), remote_dest.clone()).is_ok()); + + // LocalExporter + ensure_validate_does_not_consume_dest_or_msg::< + LocalExporter<NotApplicableBridgeExporter, UniversalLocation>, + >(remote_dest.clone(), |result| assert_eq!(Err(NotApplicable), result)); + + // 3. Ok - deliver + // UnpaidRemoteExporter + assert_ok!(send_xcm::<LocalExporter<RoutableBridgeExporter, UniversalLocation>>( + remote_dest, + Xcm::default() + )); + } + + #[test] + fn remote_exporters_works() { + frame_support::parameter_types! { + pub Local: NetworkId = ByGenesis([0; 32]); + pub UniversalLocation: InteriorLocation = [GlobalConsensus(Local::get()), Parachain(1234)].into(); + pub DifferentRemote: NetworkId = ByGenesis([22; 32]); + pub RoutableBridge: Location = Location::new(1, Parachain(9657)); + // not routable + pub NotApplicableBridgeTable: Vec<NetworkExportTableItem> = vec![]; + // routable + pub RoutableBridgeTable: Vec<NetworkExportTableItem> = vec![ + NetworkExportTableItem::new( + DifferentRemote::get(), + None, + RoutableBridge::get(), + None + ) + ]; + } + type RoutableBridgeSender = OkFor<Equals<RoutableBridge>>; + type NotApplicableBridgeSender = OkFor<()>; + assert_ok!(validate_send::<RoutableBridgeSender>(RoutableBridge::get(), Xcm::default())); + assert_err!( + validate_send::<NotApplicableBridgeSender>(RoutableBridge::get(), Xcm::default()), + NotApplicable + ); + + // 1. check with local destination (should be remote) + let local_dest: Location = (Parent, Parachain(5678)).into(); + assert!(ensure_is_remote(UniversalLocation::get(), local_dest.clone()).is_err()); + + // UnpaidRemoteExporter + ensure_validate_does_not_consume_dest_or_msg::< + UnpaidRemoteExporter< + NetworkExportTable<RoutableBridgeTable>, + RoutableBridgeSender, + UniversalLocation, + >, + >(local_dest.clone(), |result| assert_eq!(Err(NotApplicable), result)); + // SovereignPaidRemoteExporter ensure_validate_does_not_consume_dest_or_msg::< SovereignPaidRemoteExporter< - NetworkExportTable<BridgeTable>, - OkSender, + NetworkExportTable<RoutableBridgeTable>, + RoutableBridgeSender, UniversalLocation, >, >(local_dest, |result| assert_eq!(Err(NotApplicable), result)); - // check with not applicable destination + // 2. check with not applicable destination (`NotApplicableBridgeTable`) let remote_dest: Location = (Parent, Parent, DifferentRemote::get()).into(); assert!(ensure_is_remote(UniversalLocation::get(), remote_dest.clone()).is_ok()); + // UnpaidRemoteExporter ensure_validate_does_not_consume_dest_or_msg::< - UnpaidRemoteExporter<NetworkExportTable<BridgeTable>, OkSender, UniversalLocation>, + UnpaidRemoteExporter< + NetworkExportTable<NotApplicableBridgeTable>, + RoutableBridgeSender, + UniversalLocation, + >, >(remote_dest.clone(), |result| assert_eq!(Err(NotApplicable), result)); - + // SovereignPaidRemoteExporter ensure_validate_does_not_consume_dest_or_msg::< SovereignPaidRemoteExporter< - NetworkExportTable<BridgeTable>, - OkSender, + NetworkExportTable<NotApplicableBridgeTable>, + RoutableBridgeSender, UniversalLocation, >, >(remote_dest, |result| assert_eq!(Err(NotApplicable), result)); + + // 3. check with not applicable from the inner router (using `NotApplicableBridgeSender`) + let remote_dest: Location = (Parent, Parent, DifferentRemote::get()).into(); + assert!(ensure_is_remote(UniversalLocation::get(), remote_dest.clone()).is_ok()); + + // UnpaidRemoteExporter + ensure_validate_does_not_consume_dest_or_msg::< + UnpaidRemoteExporter< + NetworkExportTable<RoutableBridgeTable>, + NotApplicableBridgeSender, + UniversalLocation, + >, + >(remote_dest.clone(), |result| assert_eq!(Err(NotApplicable), result)); + // SovereignPaidRemoteExporter + ensure_validate_does_not_consume_dest_or_msg::< + SovereignPaidRemoteExporter< + NetworkExportTable<RoutableBridgeTable>, + NotApplicableBridgeSender, + UniversalLocation, + >, + >(remote_dest.clone(), |result| assert_eq!(Err(NotApplicable), result)); + + // 4. Ok - deliver + // UnpaidRemoteExporter + assert_ok!(send_xcm::< + UnpaidRemoteExporter< + NetworkExportTable<RoutableBridgeTable>, + RoutableBridgeSender, + UniversalLocation, + >, + >(remote_dest.clone(), Xcm::default())); + // SovereignPaidRemoteExporter + assert_ok!(send_xcm::< + SovereignPaidRemoteExporter< + NetworkExportTable<RoutableBridgeTable>, + RoutableBridgeSender, + UniversalLocation, + >, + >(remote_dest, Xcm::default())); } #[test] diff --git a/polkadot/xcm/xcm-builder/tests/mock/mod.rs b/polkadot/xcm/xcm-builder/tests/mock/mod.rs index 0468b0a5410c416a0703502cc38a13dba2a79853..c3e53284508248c52aec7d702af20f34ad8a6ddf 100644 --- a/polkadot/xcm/xcm-builder/tests/mock/mod.rs +++ b/polkadot/xcm/xcm-builder/tests/mock/mod.rs @@ -243,7 +243,7 @@ construct_runtime!( pub fn kusama_like_with_balances(balances: Vec<(AccountId, Balance)>) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap(); - pallet_balances::GenesisConfig::<Runtime> { balances } + pallet_balances::GenesisConfig::<Runtime> { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/xcm/xcm-executor/Cargo.toml b/polkadot/xcm/xcm-executor/Cargo.toml index cc966f91fe4db948c38a9d4faeff267aefc7e59c..381dca54a5fba1fcb8e5fad76814e9206c73a095 100644 --- a/polkadot/xcm/xcm-executor/Cargo.toml +++ b/polkadot/xcm/xcm-executor/Cargo.toml @@ -5,24 +5,26 @@ authors.workspace = true edition.workspace = true license.workspace = true version = "7.0.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] -impl-trait-for-tuples = { workspace = true } -environmental = { workspace = true } codec = { features = ["derive"], workspace = true } +environmental = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } +frame-support = { workspace = true } +impl-trait-for-tuples = { workspace = true } scale-info = { features = ["derive", "serde"], workspace = true } -xcm = { workspace = true } -sp-io = { workspace = true } sp-arithmetic = { workspace = true } sp-core = { workspace = true } +sp-io = { workspace = true } sp-runtime = { workspace = true } sp-weights = { workspace = true } -frame-support = { workspace = true } tracing = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } +xcm = { workspace = true } [features] default = ["std"] @@ -30,6 +32,7 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "xcm/runtime-benchmarks", ] std = [ "codec/std", diff --git a/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml b/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml index 7e6bfe967b9049f729c91f49329b1e75940332ec..6c2e56669bc378a32d96785dfcc8f668e5e8e3cd 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml +++ b/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml @@ -13,21 +13,24 @@ workspace = true [dependencies] codec = { workspace = true, default-features = true } frame-support = { workspace = true } +frame-system = { workspace = true, default-features = true } futures = { workspace = true } +pallet-sudo = { workspace = true, default-features = true } pallet-transaction-payment = { workspace = true, default-features = true } pallet-xcm = { workspace = true, default-features = true } +polkadot-runtime-parachains = { workspace = true, default-features = true } polkadot-test-client = { workspace = true } polkadot-test-runtime = { workspace = true } polkadot-test-service = { workspace = true } sp-consensus = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-runtime = { workspace = true } sp-state-machine = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } xcm = { workspace = true } xcm-executor = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } [features] default = ["std"] -std = ["frame-support/std", "sp-runtime/std", "xcm/std"] +std = ["frame-support/std", "frame-system/std", "pallet-sudo/std", "polkadot-runtime-parachains/std", "sp-runtime/std", "xcm/std"] diff --git a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs index 9b918fd7eeed95a246be9a188ac66c6d65d3b10b..dfcc3fc4187fa751682c11f2c765ff305549135b 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs @@ -79,7 +79,11 @@ fn transact_recursion_limit_works() { Xcm(vec![ WithdrawAsset((Here, 1_000).into()), BuyExecution { fees: (Here, 1).into(), weight_limit: Unlimited }, - Transact { origin_kind: OriginKind::Native, call: call.encode().into() }, + Transact { + origin_kind: OriginKind::Native, + call: call.encode().into(), + fallback_max_weight: None, + }, ]) }; let mut call: Option<polkadot_test_runtime::RuntimeCall> = None; @@ -371,6 +375,26 @@ fn deposit_reserve_asset_works_for_any_xcm_sender() { let mut block_builder = client.init_polkadot_block_builder(); + // Make the para available, so that `DMP` doesn't reject the XCM because the para is unknown. + let make_para_available = + construct_extrinsic( + &client, + polkadot_test_runtime::RuntimeCall::Sudo(pallet_sudo::Call::sudo { + call: Box::new(polkadot_test_runtime::RuntimeCall::System( + frame_system::Call::set_storage { + items: vec![( + polkadot_runtime_parachains::paras::Heads::< + polkadot_test_runtime::Runtime, + >::hashed_key_for(2000u32), + vec![1, 2, 3], + )], + }, + )), + }), + sp_keyring::Sr25519Keyring::Alice, + 0, + ); + // Simulate execution of an incoming XCM message at the reserve chain let execute = construct_extrinsic( &client, @@ -379,9 +403,12 @@ fn deposit_reserve_asset_works_for_any_xcm_sender() { max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), }), sp_keyring::Sr25519Keyring::Alice, - 0, + 1, ); + block_builder + .push_polkadot_extrinsic(make_para_available) + .expect("pushes extrinsic"); block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic"); let block = block_builder.build().expect("Finalizes the block").block; diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 4e051f24050c7d18a054809a30cfe7a5885f2bca..d0f18aea1ab318e223859cd3ba106cec4c29775d 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -939,7 +939,8 @@ impl<Config: config::Config> XcmExecutor<Config> { Ok(()) }) }, - Transact { origin_kind, mut call } => { + // `fallback_max_weight` is not used in the executor, it's only for conversions. + Transact { origin_kind, mut call, .. } => { // We assume that the Relay-chain is allowed to use transact on this parachain. let origin = self.cloned_origin().ok_or_else(|| { tracing::trace!( @@ -1086,18 +1087,19 @@ impl<Config: config::Config> XcmExecutor<Config> { DepositReserveAsset { assets, dest, xcm } => { let old_holding = self.holding.clone(); let result = Config::TransactionalProcessor::process(|| { - let maybe_delivery_fee_from_holding = if self.fees.is_empty() { - self.get_delivery_fee_from_holding(&assets, &dest, &xcm)? + let mut assets = self.holding.saturating_take(assets); + // When not using `PayFees`, nor `JIT_WITHDRAW`, delivery fees are paid from + // transferred assets. + let maybe_delivery_fee_from_assets = if self.fees.is_empty() && !self.fees_mode.jit_withdraw { + // Deduct and return the part of `assets` that shall be used for delivery fees. + self.take_delivery_fee_from_assets(&mut assets, &dest, FeeReason::DepositReserveAsset, &xcm)? } else { None }; - let mut message = Vec::with_capacity(xcm.len() + 2); - // now take assets to deposit (after having taken delivery fees) - let deposited = self.holding.saturating_take(assets); - tracing::trace!(target: "xcm::DepositReserveAsset", ?deposited, "Assets except delivery fee"); + tracing::trace!(target: "xcm::DepositReserveAsset", ?assets, "Assets except delivery fee"); Self::do_reserve_deposit_assets( - deposited, + assets, &dest, &mut message, Some(&self.context), @@ -1106,7 +1108,7 @@ impl<Config: config::Config> XcmExecutor<Config> { message.push(ClearOrigin); // append custom instructions message.extend(xcm.0.into_iter()); - if let Some(delivery_fee) = maybe_delivery_fee_from_holding { + if let Some(delivery_fee) = maybe_delivery_fee_from_assets { // Put back delivery_fee in holding register to be charged by XcmSender. self.holding.subsume_assets(delivery_fee); } @@ -1121,7 +1123,15 @@ impl<Config: config::Config> XcmExecutor<Config> { InitiateReserveWithdraw { assets, reserve, xcm } => { let old_holding = self.holding.clone(); let result = Config::TransactionalProcessor::process(|| { - let assets = self.holding.saturating_take(assets); + let mut assets = self.holding.saturating_take(assets); + // When not using `PayFees`, nor `JIT_WITHDRAW`, delivery fees are paid from + // transferred assets. + let maybe_delivery_fee_from_assets = if self.fees.is_empty() && !self.fees_mode.jit_withdraw { + // Deduct and return the part of `assets` that shall be used for delivery fees. + self.take_delivery_fee_from_assets(&mut assets, &reserve, FeeReason::InitiateReserveWithdraw, &xcm)? + } else { + None + }; let mut message = Vec::with_capacity(xcm.len() + 2); Self::do_reserve_withdraw_assets( assets, @@ -1133,6 +1143,10 @@ impl<Config: config::Config> XcmExecutor<Config> { message.push(ClearOrigin); // append custom instructions message.extend(xcm.0.into_iter()); + if let Some(delivery_fee) = maybe_delivery_fee_from_assets { + // Put back delivery_fee in holding register to be charged by XcmSender. + self.holding.subsume_assets(delivery_fee); + } self.send(reserve, Xcm(message), FeeReason::InitiateReserveWithdraw)?; Ok(()) }); @@ -1144,13 +1158,25 @@ impl<Config: config::Config> XcmExecutor<Config> { InitiateTeleport { assets, dest, xcm } => { let old_holding = self.holding.clone(); let result = Config::TransactionalProcessor::process(|| { - let assets = self.holding.saturating_take(assets); + let mut assets = self.holding.saturating_take(assets); + // When not using `PayFees`, nor `JIT_WITHDRAW`, delivery fees are paid from + // transferred assets. + let maybe_delivery_fee_from_assets = if self.fees.is_empty() && !self.fees_mode.jit_withdraw { + // Deduct and return the part of `assets` that shall be used for delivery fees. + self.take_delivery_fee_from_assets(&mut assets, &dest, FeeReason::InitiateTeleport, &xcm)? + } else { + None + }; let mut message = Vec::with_capacity(xcm.len() + 2); Self::do_teleport_assets(assets, &dest, &mut message, &self.context)?; // clear origin for subsequent custom instructions message.push(ClearOrigin); // append custom instructions message.extend(xcm.0.into_iter()); + if let Some(delivery_fee) = maybe_delivery_fee_from_assets { + // Put back delivery_fee in holding register to be charged by XcmSender. + self.holding.subsume_assets(delivery_fee); + } self.send(dest.clone(), Xcm(message), FeeReason::InitiateTeleport)?; Ok(()) }); @@ -1370,8 +1396,14 @@ impl<Config: config::Config> XcmExecutor<Config> { self.error = None; Ok(()) }, - SetAssetClaimer { location } => { - self.asset_claimer = Some(location); + SetHints { hints } => { + for hint in hints.into_iter() { + match hint { + AssetClaimer { location } => { + self.asset_claimer = Some(location) + }, + } + } Ok(()) }, ClaimAsset { assets, ticket } => { @@ -1707,36 +1739,48 @@ impl<Config: config::Config> XcmExecutor<Config> { Ok(()) } - /// Gets the necessary delivery fee to send a reserve transfer message to `destination` from - /// holding. + /// Take from transferred `assets` the delivery fee required to send an onward transfer message + /// to `destination`. /// /// Will be removed once the transition from `BuyExecution` to `PayFees` is complete. - fn get_delivery_fee_from_holding( - &mut self, - assets: &AssetFilter, + fn take_delivery_fee_from_assets( + &self, + assets: &mut AssetsInHolding, destination: &Location, + reason: FeeReason, xcm: &Xcm<()>, ) -> Result<Option<AssetsInHolding>, XcmError> { - // we need to do this take/put cycle to solve wildcards and get exact assets to - // be weighed - let to_weigh = self.holding.saturating_take(assets.clone()); - self.holding.subsume_assets(to_weigh.clone()); + let to_weigh = assets.clone(); let to_weigh_reanchored = Self::reanchored(to_weigh, &destination, None); - let mut message_to_weigh = vec![ReserveAssetDeposited(to_weigh_reanchored), ClearOrigin]; + let remote_instruction = match reason { + FeeReason::DepositReserveAsset => ReserveAssetDeposited(to_weigh_reanchored), + FeeReason::InitiateReserveWithdraw => WithdrawAsset(to_weigh_reanchored), + FeeReason::InitiateTeleport => ReceiveTeleportedAsset(to_weigh_reanchored), + _ => { + tracing::debug!( + target: "xcm::take_delivery_fee_from_assets", + "Unexpected delivery fee reason", + ); + return Err(XcmError::NotHoldingFees); + }, + }; + let mut message_to_weigh = Vec::with_capacity(xcm.len() + 2); + message_to_weigh.push(remote_instruction); + message_to_weigh.push(ClearOrigin); message_to_weigh.extend(xcm.0.clone().into_iter()); let (_, fee) = validate_send::<Config::XcmSender>(destination.clone(), Xcm(message_to_weigh))?; let maybe_delivery_fee = fee.get(0).map(|asset_needed_for_fees| { tracing::trace!( - target: "xcm::fees::DepositReserveAsset", + target: "xcm::fees::take_delivery_fee_from_assets", "Asset provided to pay for fees {:?}, asset required for delivery fees: {:?}", self.asset_used_in_buy_execution, asset_needed_for_fees, ); let asset_to_pay_for_fees = self.calculate_asset_for_delivery_fees(asset_needed_for_fees.clone()); // set aside fee to be charged by XcmSender - let delivery_fee = self.holding.saturating_take(asset_to_pay_for_fees.into()); - tracing::trace!(target: "xcm::fees::DepositReserveAsset", ?delivery_fee); + let delivery_fee = assets.saturating_take(asset_to_pay_for_fees.into()); + tracing::trace!(target: "xcm::fees::take_delivery_fee_from_assets", ?delivery_fee); delivery_fee }); Ok(maybe_delivery_fee) diff --git a/polkadot/xcm/xcm-executor/src/tests/set_asset_claimer.rs b/polkadot/xcm/xcm-executor/src/tests/set_asset_claimer.rs index bc504b8db2a296000bcff463bbad5837e8534cd0..cc97e2b3a16eac45494c811c9bf2563baf0731f7 100644 --- a/polkadot/xcm/xcm-executor/src/tests/set_asset_claimer.rs +++ b/polkadot/xcm/xcm-executor/src/tests/set_asset_claimer.rs @@ -38,7 +38,7 @@ fn set_asset_claimer() { // if withdrawing fails we're not missing any corner case. .withdraw_asset((Here, 100u128)) .clear_origin() - .set_asset_claimer(bob.clone()) + .set_hints(vec![AssetClaimer { location: bob.clone() }]) .pay_fees((Here, 10u128)) // 10% destined for fees, not more. .build(); @@ -93,7 +93,7 @@ fn trap_then_set_asset_claimer() { .withdraw_asset((Here, 100u128)) .clear_origin() .trap(0u64) - .set_asset_claimer(bob) + .set_hints(vec![AssetClaimer { location: bob }]) .pay_fees((Here, 10u128)) // 10% destined for fees, not more. .build(); @@ -121,7 +121,7 @@ fn set_asset_claimer_then_trap() { // if withdrawing fails we're not missing any corner case. .withdraw_asset((Here, 100u128)) .clear_origin() - .set_asset_claimer(bob.clone()) + .set_hints(vec![AssetClaimer { location: bob.clone() }]) .trap(0u64) .pay_fees((Here, 10u128)) // 10% destined for fees, not more. .build(); diff --git a/polkadot/xcm/xcm-executor/src/traits/export.rs b/polkadot/xcm/xcm-executor/src/traits/export.rs index b356e0da7df7801b52cb63652fa321507223a824..3e9275edab37b430757631b7b4eba7e7475af2b6 100644 --- a/polkadot/xcm/xcm-executor/src/traits/export.rs +++ b/polkadot/xcm/xcm-executor/src/traits/export.rs @@ -108,7 +108,7 @@ impl ExportXcm for Tuple { } /// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// both in `Some` before passing them as mutable references into `T::send_xcm`. pub fn validate_export<T: ExportXcm>( network: NetworkId, channel: u32, @@ -120,7 +120,7 @@ pub fn validate_export<T: ExportXcm>( } /// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// both in `Some` before passing them as mutable references into `T::send_xcm`. /// /// Returns either `Ok` with the price of the delivery, or `Err` with the reason why the message /// could not be sent. diff --git a/polkadot/xcm/xcm-executor/src/traits/mod.rs b/polkadot/xcm/xcm-executor/src/traits/mod.rs index feb2922bcdffce1ea9e3a40e22875c085c82870f..fe73477f516fbc7e0b42fcf1edff004d4b8a89fd 100644 --- a/polkadot/xcm/xcm-executor/src/traits/mod.rs +++ b/polkadot/xcm/xcm-executor/src/traits/mod.rs @@ -42,7 +42,7 @@ pub use on_response::{OnResponse, QueryHandler, QueryResponseStatus, VersionChan mod process_transaction; pub use process_transaction::ProcessTransaction; mod should_execute; -pub use should_execute::{CheckSuspension, Properties, ShouldExecute}; +pub use should_execute::{CheckSuspension, DenyExecution, Properties, ShouldExecute}; mod transact_asset; pub use transact_asset::TransactAsset; mod hrmp; diff --git a/polkadot/xcm/xcm-executor/src/traits/should_execute.rs b/polkadot/xcm/xcm-executor/src/traits/should_execute.rs index ec9ef70cc817e5a230ae843581fb736ce06770ee..48a562a1648ddb28bb90a68a3c7e501c2077bec2 100644 --- a/polkadot/xcm/xcm-executor/src/traits/should_execute.rs +++ b/polkadot/xcm/xcm-executor/src/traits/should_execute.rs @@ -127,3 +127,67 @@ impl CheckSuspension for Tuple { false } } + +/// Trait to determine whether the execution engine should not execute a given XCM. +/// +/// Can be amalgamated into a tuple to have multiple traits. If any of the tuple elements returns +/// `Err(ProcessMessageError)`, the execution stops. Else, `Ok(())` is returned if all elements +/// accept the message. +pub trait DenyExecution { + /// Returns `Ok(())` if there is no reason to deny execution, + /// while `Err(ProcessMessageError)` indicates there is a reason to deny execution. + /// + /// - `origin`: The origin (sender) of the message. + /// - `instructions`: The message itself. + /// - `max_weight`: The (possibly over-) estimation of the weight of execution of the message. + /// - `properties`: Various pre-established properties of the message which may be mutated by + /// this API. + fn deny_execution<RuntimeCall>( + origin: &Location, + instructions: &mut [Instruction<RuntimeCall>], + max_weight: Weight, + properties: &mut Properties, + ) -> Result<(), ProcessMessageError>; +} + +#[impl_trait_for_tuples::impl_for_tuples(10)] +impl DenyExecution for Tuple { + fn deny_execution<RuntimeCall>( + origin: &Location, + instructions: &mut [Instruction<RuntimeCall>], + max_weight: Weight, + properties: &mut Properties, + ) -> Result<(), ProcessMessageError> { + for_tuples!( #( + let barrier = core::any::type_name::<Tuple>(); + match Tuple::deny_execution(origin, instructions, max_weight, properties) { + Err(error) => { + tracing::error!( + target: "xcm::deny_execution", + ?origin, + ?instructions, + ?max_weight, + ?properties, + ?error, + %barrier, + "did not pass barrier", + ); + return Err(error); + }, + Ok(()) => { + tracing::trace!( + target: "xcm::deny_execution", + ?origin, + ?instructions, + ?max_weight, + ?properties, + %barrier, + "pass barrier", + ); + }, + } + )* ); + + Ok(()) + } +} diff --git a/polkadot/xcm/xcm-runtime-apis/Cargo.toml b/polkadot/xcm/xcm-runtime-apis/Cargo.toml index 9ccca76c321cc031e0fcaf0a0f6486ea238f0068..96afb10e53971cb128d68b1608773fb7ccd265d0 100644 --- a/polkadot/xcm/xcm-runtime-apis/Cargo.toml +++ b/polkadot/xcm/xcm-runtime-apis/Cargo.toml @@ -21,17 +21,17 @@ xcm = { workspace = true } xcm-executor = { workspace = true } [dev-dependencies] +frame-executive = { workspace = true } frame-system = { workspace = true } -sp-io = { workspace = true } -xcm-builder = { workspace = true } hex-literal = { workspace = true } -pallet-xcm = { workspace = true } -pallet-balances = { workspace = true } -pallet-assets = { workspace = true } -xcm-executor = { workspace = true } -frame-executive = { workspace = true } log = { workspace = true } +pallet-assets = { workspace = true } +pallet-balances = { workspace = true } +pallet-xcm = { workspace = true } +sp-io = { workspace = true } sp-tracing = { workspace = true, default-features = true } +xcm-builder = { workspace = true } +xcm-executor = { workspace = true } [features] default = ["std"] @@ -60,4 +60,5 @@ runtime-benchmarks = [ "pallet-xcm/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs b/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs index 2d14b4e571c6724574d0751a12ea1f470c125da5..c3046b134d1fe041fcb770a642104b240d35ef55 100644 --- a/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs +++ b/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs @@ -353,3 +353,26 @@ fn dry_run_xcm() { ); }); } + +#[test] +fn calling_payment_api_with_a_lower_version_works() { + let transfer_amount = 100u128; + let xcm_to_weigh = Xcm::<RuntimeCall>::builder_unsafe() + .withdraw_asset((Here, transfer_amount)) + .buy_execution((Here, transfer_amount), Unlimited) + .deposit_asset(AllCounted(1), [1u8; 32]) + .build(); + let versioned_xcm_to_weigh = VersionedXcm::from(xcm_to_weigh.clone().into()); + let lower_version_xcm_to_weigh = versioned_xcm_to_weigh.into_version(XCM_VERSION - 1).unwrap(); + let client = TestClient; + let runtime_api = client.runtime_api(); + let xcm_weight = + runtime_api.query_xcm_weight(H256::zero(), lower_version_xcm_to_weigh).unwrap(); + assert!(xcm_weight.is_ok()); + let native_token = VersionedAssetId::from(AssetId(Here.into())); + let lower_version_native_token = native_token.into_version(XCM_VERSION - 1).unwrap(); + let execution_fees = runtime_api + .query_weight_to_asset_fee(H256::zero(), xcm_weight.unwrap(), lower_version_native_token) + .unwrap(); + assert!(execution_fees.is_ok()); +} diff --git a/polkadot/xcm/xcm-runtime-apis/tests/mock.rs b/polkadot/xcm/xcm-runtime-apis/tests/mock.rs index f0a5be908f693b0cfdc5a41d71559728a06d73fc..18d9dce9245a33d7903084363d6b39f3e66359c9 100644 --- a/polkadot/xcm/xcm-runtime-apis/tests/mock.rs +++ b/polkadot/xcm/xcm-runtime-apis/tests/mock.rs @@ -60,7 +60,8 @@ construct_runtime! { } } -pub type TxExtension = (frame_system::CheckWeight<TestRuntime>,); +pub type TxExtension = + (frame_system::CheckWeight<TestRuntime>, frame_system::WeightReclaim<TestRuntime>); // we only use the hash type from this, so using the mock should be fine. pub(crate) type Extrinsic = sp_runtime::generic::UncheckedExtrinsic< @@ -364,7 +365,7 @@ impl pallet_xcm::Config for TestRuntime { pub fn new_test_ext_with_balances(balances: Vec<(AccountId, Balance)>) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap(); - pallet_balances::GenesisConfig::<TestRuntime> { balances } + pallet_balances::GenesisConfig::<TestRuntime> { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -380,7 +381,7 @@ pub fn new_test_ext_with_balances_and_assets( ) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap(); - pallet_balances::GenesisConfig::<TestRuntime> { balances } + pallet_balances::GenesisConfig::<TestRuntime> { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -453,7 +454,8 @@ sp_api::mock_impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> { - match asset.try_as::<AssetId>() { + let latest_asset_id: Result<AssetId, ()> = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == HereLocation::get() => { Ok(WeightToFee::weight_to_fee(&weight)) }, diff --git a/polkadot/xcm/xcm-simulator/Cargo.toml b/polkadot/xcm/xcm-simulator/Cargo.toml index c7caa49393ed5c7fa505b38d1a2eaac208d63465..10c6f14bc8b960c68deb5297bebecb3315f25214 100644 --- a/polkadot/xcm/xcm-simulator/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/Cargo.toml @@ -5,25 +5,27 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] codec = { workspace = true, default-features = true } -scale-info = { workspace = true } paste = { workspace = true, default-features = true } +scale-info = { workspace = true } frame-support = { workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +sp-std = { workspace = true, default-features = true } -xcm = { workspace = true, default-features = true } -xcm-executor = { workspace = true, default-features = true } -xcm-builder = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-core-primitives = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } polkadot-runtime-parachains = { workspace = true, default-features = true } +xcm = { workspace = true, default-features = true } +xcm-builder = { workspace = true, default-features = true } +xcm-executor = { workspace = true, default-features = true } diff --git a/polkadot/xcm/xcm-simulator/example/Cargo.toml b/polkadot/xcm/xcm-simulator/example/Cargo.toml index e0aff9b7782a7e4fa8c73dc5cfaae51af2035533..ccf0ecc39c4c89b38d0aff077f2190bed1221617 100644 --- a/polkadot/xcm/xcm-simulator/example/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/example/Cargo.toml @@ -5,34 +5,36 @@ authors.workspace = true edition.workspace = true license.workspace = true version = "7.0.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true [dependencies] codec = { workspace = true, default-features = true } -scale-info = { features = ["derive"], workspace = true, default-features = true } log = { workspace = true } +scale-info = { features = ["derive"], workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } frame-support = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-message-queue = { workspace = true, default-features = true } pallet-uniques = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-std = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -xcm = { workspace = true, default-features = true } -xcm-simulator = { workspace = true, default-features = true } -xcm-executor = { workspace = true, default-features = true } -xcm-builder = { workspace = true, default-features = true } pallet-xcm = { workspace = true, default-features = true } polkadot-core-primitives = { workspace = true, default-features = true } -polkadot-runtime-parachains = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-runtime-parachains = { workspace = true, default-features = true } +xcm = { workspace = true, default-features = true } +xcm-builder = { workspace = true, default-features = true } +xcm-executor = { workspace = true, default-features = true } +xcm-simulator = { workspace = true, default-features = true } [features] default = [] @@ -48,4 +50,5 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/polkadot/xcm/xcm-simulator/example/src/lib.rs b/polkadot/xcm/xcm-simulator/example/src/lib.rs index 6fb9a69770ea8f74860f528bce56ecd44cc2fc9f..8a05569831b5c1d2adee9a4a03a8c5fb3e790fe3 100644 --- a/polkadot/xcm/xcm-simulator/example/src/lib.rs +++ b/polkadot/xcm/xcm-simulator/example/src/lib.rs @@ -101,6 +101,7 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { pallet_balances::GenesisConfig::<Runtime> { balances: vec![(ALICE, INITIAL_BALANCE), (parent_account_id(), INITIAL_BALANCE)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -125,6 +126,7 @@ pub fn relay_ext() -> sp_io::TestExternalities { (child_account_id(1), INITIAL_BALANCE), (child_account_id(2), INITIAL_BALANCE), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/xcm/xcm-simulator/example/src/tests.rs b/polkadot/xcm/xcm-simulator/example/src/tests.rs index bbac44ed8a1f1f3883b1546c7611198d400b1c21..f971812f4f4d69e7c437a645f0fb2670a499417f 100644 --- a/polkadot/xcm/xcm-simulator/example/src/tests.rs +++ b/polkadot/xcm/xcm-simulator/example/src/tests.rs @@ -47,6 +47,7 @@ fn dmp() { Xcm(vec![Transact { origin_kind: OriginKind::SovereignAccount, call: remark.encode().into(), + fallback_max_weight: None, }]), )); }); @@ -74,6 +75,7 @@ fn ump() { Xcm(vec![Transact { origin_kind: OriginKind::SovereignAccount, call: remark.encode().into(), + fallback_max_weight: None, }]), )); }); @@ -101,6 +103,7 @@ fn xcmp() { Xcm(vec![Transact { origin_kind: OriginKind::SovereignAccount, call: remark.encode().into(), + fallback_max_weight: None, }]), )); }); @@ -388,6 +391,7 @@ fn reserve_asset_class_create_and_reserve_transfer() { ) .encode() .into(), + fallback_max_weight: None, }]); // Send creation. assert_ok!(RelayChainPalletXcm::send_xcm(Here, Parachain(1), message)); diff --git a/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml b/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml index 04f8ba115173465b14127cb277bcc74776ea1a5b..62a047975c87a5c62bc59febadd321410de6979a 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml @@ -11,30 +11,30 @@ publish = false workspace = true [dependencies] +arbitrary = { workspace = true } codec = { workspace = true, default-features = true } honggfuzz = { workspace = true } -arbitrary = { workspace = true } scale-info = { features = ["derive"], workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } -frame-support = { workspace = true, default-features = true } frame-executive = { workspace = true, default-features = true } +frame-support = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } frame-try-runtime = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-message-queue = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-std = { workspace = true, default-features = true } -xcm = { workspace = true, default-features = true } -xcm-simulator = { workspace = true, default-features = true } -xcm-executor = { workspace = true, default-features = true } -xcm-builder = { workspace = true, default-features = true } pallet-xcm = { workspace = true, default-features = true } polkadot-core-primitives = { workspace = true, default-features = true } -polkadot-runtime-parachains = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-runtime-parachains = { workspace = true, default-features = true } +xcm = { workspace = true, default-features = true } +xcm-builder = { workspace = true, default-features = true } +xcm-executor = { workspace = true, default-features = true } +xcm-simulator = { workspace = true, default-features = true } [features] try-runtime = [ @@ -59,6 +59,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] [[bin]] diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs index adf6cacd278b9f25a02f9199fbdabc0af3434414..8ea5e033f3ad7174776afb273f3773ff8d18399d 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs @@ -117,6 +117,7 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { pallet_balances::GenesisConfig::<Runtime> { balances: (0..6).map(|i| ([i; 32].into(), INITIAL_BALANCE)).collect(), + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -138,7 +139,7 @@ pub fn relay_ext() -> sp_io::TestExternalities { balances.append(&mut (1..=3).map(|i| (para_account_id(i), INITIAL_BALANCE)).collect()); balances.append(&mut (0..6).map(|i| ([i; 32].into(), INITIAL_BALANCE)).collect()); - pallet_balances::GenesisConfig::<Runtime> { balances } + pallet_balances::GenesisConfig::<Runtime> { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/zombienet-sdk-tests/Cargo.toml b/polkadot/zombienet-sdk-tests/Cargo.toml index 4eac7af49f8a9ea4ae775491b3aaed9fb436fd60..ba7517ddce6631df2b56f3c9632c3ec7f55c5b6f 100644 --- a/polkadot/zombienet-sdk-tests/Cargo.toml +++ b/polkadot/zombienet-sdk-tests/Cargo.toml @@ -8,16 +8,17 @@ license.workspace = true publish = false [dependencies] +anyhow = { workspace = true } +codec = { workspace = true, features = ["derive"] } env_logger = { workspace = true } log = { workspace = true } +polkadot-primitives = { workspace = true, default-features = true } +serde = { workspace = true } +serde_json = { workspace = true } subxt = { workspace = true, features = ["substrate-compat"] } subxt-signer = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread"] } -anyhow = { workspace = true } zombienet-sdk = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -codec = { workspace = true, features = ["derive"] } [features] zombie-metadata = [] diff --git a/polkadot/zombienet-sdk-tests/build.rs b/polkadot/zombienet-sdk-tests/build.rs index 240d86386af2ee831f887d83a2ff9ced16c9e175..f7a62a53a8ac5f72a351b02113bffd6fafb3aced 100644 --- a/polkadot/zombienet-sdk-tests/build.rs +++ b/polkadot/zombienet-sdk-tests/build.rs @@ -25,39 +25,47 @@ fn make_env_key(k: &str) -> String { replace_dashes(&k.to_ascii_uppercase()) } +fn wasm_sub_path(chain: &str) -> String { + let (package, runtime_name) = + if let Some(cumulus_test_runtime) = chain.strip_prefix("cumulus-test-runtime-") { + ( + "cumulus-test-runtime".to_string(), + format!("wasm_binary_{}.rs", replace_dashes(cumulus_test_runtime)), + ) + } else { + (format!("{chain}-runtime"), replace_dashes(&format!("{chain}-runtime"))) + }; + + format!("{}/{}.wasm", package, runtime_name) +} + fn find_wasm(chain: &str) -> Option<PathBuf> { const PROFILES: [&str; 2] = ["release", "testnet"]; let manifest_path = env::var("CARGO_WORKSPACE_ROOT_DIR").unwrap(); let manifest_path = manifest_path.strip_suffix('/').unwrap(); debug_output!("manifest_path is : {}", manifest_path); - let package = format!("{chain}-runtime"); + + let sub_path = wasm_sub_path(chain); + let profile = PROFILES.into_iter().find(|p| { - let full_path = format!( - "{}/target/{}/wbuild/{}/{}.wasm", - manifest_path, - p, - &package, - replace_dashes(&package) - ); + let full_path = format!("{}/target/{}/wbuild/{}", manifest_path, p, sub_path); debug_output!("checking wasm at : {}", full_path); matches!(path::PathBuf::from(&full_path).try_exists(), Ok(true)) }); debug_output!("profile is : {:?}", profile); profile.map(|profile| { - PathBuf::from(&format!( - "{}/target/{}/wbuild/{}/{}.wasm", - manifest_path, - profile, - &package, - replace_dashes(&package) - )) + PathBuf::from(&format!("{}/target/{}/wbuild/{}", manifest_path, profile, sub_path)) }) } // based on https://gist.github.com/s0me0ne-unkn0wn/bbd83fe32ce10327086adbf13e750eec fn build_wasm(chain: &str) -> PathBuf { - let package = format!("{chain}-runtime"); + let package = if chain.starts_with("cumulus-test-runtime-") { + String::from("cumulus-test-runtime") + } else { + format!("{chain}-runtime") + }; let cargo = env::var("CARGO").unwrap(); let target = env::var("TARGET").unwrap(); @@ -81,11 +89,7 @@ fn build_wasm(chain: &str) -> PathBuf { .status() .unwrap(); - let wasm_path = &format!( - "{target_dir}/{target}/release/wbuild/{}/{}.wasm", - &package, - replace_dashes(&package) - ); + let wasm_path = &format!("{target_dir}/{target}/release/wbuild/{}", wasm_sub_path(chain)); PathBuf::from(wasm_path) } @@ -128,6 +132,10 @@ fn main() { const METADATA_DIR: &str = "metadata-files"; const CHAINS: [&str; 2] = ["rococo", "coretime-rococo"]; + // Add some cumulus test runtimes if needed. Formatted like + // "cumulus-test-runtime-elastic-scaling". + const CUMULUS_TEST_RUNTIMES: [&str; 0] = []; + let metadata_path = format!("{manifest_path}/{METADATA_DIR}"); for chain in CHAINS { @@ -145,6 +153,21 @@ fn main() { }; } + for chain in CUMULUS_TEST_RUNTIMES { + let full_path = format!("{metadata_path}/{chain}-local.scale"); + let output_path = path::PathBuf::from(&full_path); + + match output_path.try_exists() { + Ok(true) => { + debug_output!("got: {}", full_path); + }, + _ => { + debug_output!("needs: {}", full_path); + fetch_metadata_file(chain, &output_path); + }, + }; + } + substrate_build_script_utils::generate_cargo_keys(); substrate_build_script_utils::rerun_if_git_head_changed(); println!("cargo:rerun-if-changed={}", metadata_path); diff --git a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/basic_3cores.rs b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/basic_3cores.rs new file mode 100644 index 0000000000000000000000000000000000000000..1bf972750d67f238ea8ccac8dffc0e6f796d6375 --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/basic_3cores.rs @@ -0,0 +1,131 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Test that a parachain that uses a basic collator (like adder-collator) with elastic scaling +// can achieve full throughput of 3 candidates per block. + +use anyhow::anyhow; + +use crate::helpers::{ + assert_para_throughput, rococo, + rococo::runtime_types::{ + pallet_broker::coretime_interface::CoreAssignment, + polkadot_runtime_parachains::assigner_coretime::PartsOf57600, + }, +}; +use polkadot_primitives::Id as ParaId; +use serde_json::json; +use subxt::{OnlineClient, PolkadotConfig}; +use subxt_signer::sr25519::dev; +use zombienet_sdk::NetworkConfigBuilder; + +#[tokio::test(flavor = "multi_thread")] +async fn basic_3cores_test() -> Result<(), anyhow::Error> { + let _ = env_logger::try_init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + let images = zombienet_sdk::environment::get_images_from_env(); + + let config = NetworkConfigBuilder::new() + .with_relaychain(|r| { + let r = r + .with_chain("rococo-local") + .with_default_command("polkadot") + .with_default_image(images.polkadot.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_genesis_overrides(json!({ + "configuration": { + "config": { + "scheduler_params": { + "num_cores": 2, + "max_validators_per_core": 1 + }, + } + } + })) + // Have to set a `with_node` outside of the loop below, so that `r` has the right + // type. + .with_node(|node| node.with_name("validator-0")); + + (1..4).fold(r, |acc, i| acc.with_node(|node| node.with_name(&format!("validator-{i}")))) + }) + .with_parachain(|p| { + p.with_id(2000) + .with_default_command("adder-collator") + .cumulus_based(false) + .with_default_image(images.cumulus.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_collator(|n| n.with_name("adder-2000")) + }) + .with_parachain(|p| { + p.with_id(2001) + .with_default_command("adder-collator") + .cumulus_based(false) + .with_default_image(images.cumulus.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_collator(|n| n.with_name("adder-2001")) + }) + .build() + .map_err(|e| { + let errs = e.into_iter().map(|e| e.to_string()).collect::<Vec<_>>().join(" "); + anyhow!("config errs: {errs}") + })?; + + let spawn_fn = zombienet_sdk::environment::get_spawn_fn(); + let network = spawn_fn(config).await?; + + let relay_node = network.get_node("validator-0")?; + + let relay_client: OnlineClient<PolkadotConfig> = relay_node.wait_client().await?; + let alice = dev::alice(); + + // Assign two extra cores to adder-2000. + relay_client + .tx() + .sign_and_submit_then_watch_default( + &rococo::tx() + .sudo() + .sudo(rococo::runtime_types::rococo_runtime::RuntimeCall::Utility( + rococo::runtime_types::pallet_utility::pallet::Call::batch { + calls: vec![ + rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: 0, + begin: 0, + assignment: vec![(CoreAssignment::Task(2000), PartsOf57600(57600))], + end_hint: None + } + ), + rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: 1, + begin: 0, + assignment: vec![(CoreAssignment::Task(2000), PartsOf57600(57600))], + end_hint: None + } + ), + ], + }, + )), + &alice, + ) + .await? + .wait_for_finalized_success() + .await?; + + log::info!("2 more cores assigned to adder-2000"); + + assert_para_throughput( + &relay_client, + 15, + [(ParaId::from(2000), 40..46), (ParaId::from(2001), 12..16)] + .into_iter() + .collect(), + ) + .await?; + + log::info!("Test finished successfully"); + + Ok(()) +} diff --git a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/doesnt_break_parachains.rs b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/doesnt_break_parachains.rs new file mode 100644 index 0000000000000000000000000000000000000000..37b36efec772577ab613b3f67f7d45bb9ec0f713 --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/doesnt_break_parachains.rs @@ -0,0 +1,129 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Test that a paraid that doesn't use elastic scaling which acquired multiple cores does not brick +// itself if ElasticScalingMVP feature is enabled in genesis. + +use anyhow::anyhow; + +use crate::helpers::{ + assert_finalized_block_height, assert_para_throughput, rococo, + rococo::runtime_types::{ + pallet_broker::coretime_interface::CoreAssignment, + polkadot_runtime_parachains::assigner_coretime::PartsOf57600, + }, +}; +use polkadot_primitives::{CoreIndex, Id as ParaId}; +use serde_json::json; +use std::collections::{BTreeMap, VecDeque}; +use subxt::{OnlineClient, PolkadotConfig}; +use subxt_signer::sr25519::dev; +use zombienet_sdk::NetworkConfigBuilder; + +#[tokio::test(flavor = "multi_thread")] +async fn doesnt_break_parachains_test() -> Result<(), anyhow::Error> { + let _ = env_logger::try_init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + let images = zombienet_sdk::environment::get_images_from_env(); + + let config = NetworkConfigBuilder::new() + .with_relaychain(|r| { + let r = r + .with_chain("rococo-local") + .with_default_command("polkadot") + .with_default_image(images.polkadot.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_genesis_overrides(json!({ + "configuration": { + "config": { + "scheduler_params": { + "num_cores": 1, + "max_validators_per_core": 2, + } + } + } + })) + // Have to set a `with_node` outside of the loop below, so that `r` has the right + // type. + .with_node(|node| node.with_name("validator-0")); + + (1..4).fold(r, |acc, i| acc.with_node(|node| node.with_name(&format!("validator-{i}")))) + }) + .with_parachain(|p| { + // Use rococo-parachain default, which has 6 second slot time. Also, don't use + // slot-based collator. + p.with_id(2000) + .with_default_command("polkadot-parachain") + .with_default_image(images.cumulus.as_str()) + .with_default_args(vec![("-lparachain=debug,aura=debug").into()]) + .with_collator(|n| n.with_name("collator-2000")) + }) + .build() + .map_err(|e| { + let errs = e.into_iter().map(|e| e.to_string()).collect::<Vec<_>>().join(" "); + anyhow!("config errs: {errs}") + })?; + + let spawn_fn = zombienet_sdk::environment::get_spawn_fn(); + let network = spawn_fn(config).await?; + + let relay_node = network.get_node("validator-0")?; + let para_node = network.get_node("collator-2000")?; + + let relay_client: OnlineClient<PolkadotConfig> = relay_node.wait_client().await?; + let alice = dev::alice(); + + relay_client + .tx() + .sign_and_submit_then_watch_default( + &rococo::tx() + .sudo() + .sudo(rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: 0, + begin: 0, + assignment: vec![(CoreAssignment::Task(2000), PartsOf57600(57600))], + end_hint: None + } + )), + &alice, + ) + .await? + .wait_for_finalized_success() + .await?; + + log::info!("1 more core assigned to the parachain"); + + let para_id = ParaId::from(2000); + // Expect the parachain to be making normal progress, 1 candidate backed per relay chain block. + assert_para_throughput(&relay_client, 15, [(para_id, 13..16)].into_iter().collect()).await?; + + let para_client = para_node.wait_client().await?; + // Assert the parachain finalized block height is also on par with the number of backed + // candidates. + assert_finalized_block_height(¶_client, 12..16).await?; + + // Sanity check that indeed the parachain has two assigned cores. + let cq = relay_client + .runtime_api() + .at_latest() + .await? + .call_raw::<BTreeMap<CoreIndex, VecDeque<ParaId>>>("ParachainHost_claim_queue", None) + .await?; + + assert_eq!( + cq, + [ + (CoreIndex(0), std::iter::repeat(para_id).take(3).collect()), + (CoreIndex(1), std::iter::repeat(para_id).take(3).collect()), + ] + .into_iter() + .collect() + ); + + log::info!("Test finished successfully"); + + Ok(()) +} diff --git a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/mod.rs b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..a993e8e27214b649c00e35cbf8e24105a1d1dd60 --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/mod.rs @@ -0,0 +1,7 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +mod basic_3cores; +mod doesnt_break_parachains; +mod slot_based_12cores; +mod slot_based_3cores; diff --git a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_12cores.rs b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_12cores.rs new file mode 100644 index 0000000000000000000000000000000000000000..4d0e1adad0849a5a695c0a47db33ec7e3fe5378f --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_12cores.rs @@ -0,0 +1,129 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Test that a parachain that uses a single slot-based collator with elastic scaling can use 12 +// cores in order to achieve 500ms blocks. + +use anyhow::anyhow; + +use crate::helpers::{ + assert_finalized_block_height, assert_para_throughput, rococo, + rococo::runtime_types::{ + pallet_broker::coretime_interface::CoreAssignment, + polkadot_runtime_parachains::assigner_coretime::PartsOf57600, + }, +}; +use polkadot_primitives::Id as ParaId; +use serde_json::json; +use subxt::{OnlineClient, PolkadotConfig}; +use subxt_signer::sr25519::dev; +use zombienet_sdk::NetworkConfigBuilder; + +#[tokio::test(flavor = "multi_thread")] +async fn slot_based_12cores_test() -> Result<(), anyhow::Error> { + let _ = env_logger::try_init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + let images = zombienet_sdk::environment::get_images_from_env(); + + let config = NetworkConfigBuilder::new() + .with_relaychain(|r| { + let r = r + .with_chain("rococo-local") + .with_default_command("polkadot") + .with_default_image(images.polkadot.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_genesis_overrides(json!({ + "configuration": { + "config": { + "scheduler_params": { + "num_cores": 11, + "max_validators_per_core": 1 + }, + "async_backing_params": { + "max_candidate_depth": 24, + "allowed_ancestry_len": 2 + } + } + } + })) + // Have to set a `with_node` outside of the loop below, so that `r` has the right + // type. + .with_node(|node| node.with_name("validator-0")); + + (1..12) + .fold(r, |acc, i| acc.with_node(|node| node.with_name(&format!("validator-{i}")))) + }) + .with_parachain(|p| { + p.with_id(2300) + .with_default_command("test-parachain") + .with_default_image(images.cumulus.as_str()) + .with_chain("elastic-scaling-500ms") + .with_default_args(vec![ + ("--experimental-use-slot-based").into(), + ("-lparachain=debug,aura=debug").into(), + ]) + .with_collator(|n| n.with_name("collator-elastic")) + }) + .build() + .map_err(|e| { + let errs = e.into_iter().map(|e| e.to_string()).collect::<Vec<_>>().join(" "); + anyhow!("config errs: {errs}") + })?; + + let spawn_fn = zombienet_sdk::environment::get_spawn_fn(); + let network = spawn_fn(config).await?; + + let relay_node = network.get_node("validator-0")?; + let para_node = network.get_node("collator-elastic")?; + + let relay_client: OnlineClient<PolkadotConfig> = relay_node.wait_client().await?; + let alice = dev::alice(); + + // Assign 11 extra cores to the parachain. + + relay_client + .tx() + .sign_and_submit_then_watch_default( + &rococo::tx() + .sudo() + .sudo(rococo::runtime_types::rococo_runtime::RuntimeCall::Utility( + rococo::runtime_types::pallet_utility::pallet::Call::batch { + calls: (0..11).map(|idx| rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: idx, + begin: 0, + assignment: vec![(CoreAssignment::Task(2300), PartsOf57600(57600))], + end_hint: None + } + )).collect() + }, + )), + &alice, + ) + .await? + .wait_for_finalized_success() + .await?; + + log::info!("11 more cores assigned to the parachain"); + + // Expect a backed candidate count of at least 170 in 15 relay chain blocks + // (11.33 candidates per para per relay chain block). + // Note that only blocks after the first session change and blocks that don't contain a session + // change will be counted. + assert_para_throughput( + &relay_client, + 15, + [(ParaId::from(2300), 170..181)].into_iter().collect(), + ) + .await?; + + // Assert the parachain finalized block height is also on par with the number of backed + // candidates. + assert_finalized_block_height(¶_node.wait_client().await?, 158..181).await?; + + log::info!("Test finished successfully"); + + Ok(()) +} diff --git a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_3cores.rs b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_3cores.rs new file mode 100644 index 0000000000000000000000000000000000000000..aa1e54d7da5d98c02dbbc74eb8149f204212744c --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_3cores.rs @@ -0,0 +1,170 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Test that parachains that use a single slot-based collator with elastic scaling MVP and with +// elastic scaling with RFC103 can achieve full throughput of 3 candidates per block. + +use anyhow::anyhow; + +use crate::helpers::{ + assert_finalized_block_height, assert_para_throughput, rococo, + rococo::runtime_types::{ + pallet_broker::coretime_interface::CoreAssignment, + polkadot_runtime_parachains::assigner_coretime::PartsOf57600, + }, +}; +use polkadot_primitives::Id as ParaId; +use serde_json::json; +use subxt::{OnlineClient, PolkadotConfig}; +use subxt_signer::sr25519::dev; +use zombienet_sdk::NetworkConfigBuilder; + +#[tokio::test(flavor = "multi_thread")] +async fn slot_based_3cores_test() -> Result<(), anyhow::Error> { + let _ = env_logger::try_init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + let images = zombienet_sdk::environment::get_images_from_env(); + + let config = NetworkConfigBuilder::new() + .with_relaychain(|r| { + let r = r + .with_chain("rococo-local") + .with_default_command("polkadot") + .with_default_image(images.polkadot.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_genesis_overrides(json!({ + "configuration": { + "config": { + "scheduler_params": { + // Num cores is 4, because 2 extra will be added automatically when registering the paras. + "num_cores": 4, + "max_validators_per_core": 2 + } + } + } + })) + // Have to set a `with_node` outside of the loop below, so that `r` has the right + // type. + .with_node(|node| node.with_name("validator-0")); + + (1..12) + .fold(r, |acc, i| acc.with_node(|node| node.with_name(&format!("validator-{i}")))) + }) + .with_parachain(|p| { + // Para 2100 uses the old elastic scaling mvp, which doesn't send the new UMP signal + // commitment for selecting the core index. + p.with_id(2100) + .with_default_command("test-parachain") + .with_default_image(images.cumulus.as_str()) + .with_chain("elastic-scaling-mvp") + .with_default_args(vec![ + ("--experimental-use-slot-based").into(), + ("-lparachain=debug,aura=debug").into(), + ]) + .with_collator(|n| n.with_name("collator-elastic-mvp")) + }) + .with_parachain(|p| { + // Para 2200 uses the new RFC103-enabled collator which sends the UMP signal commitment + // for selecting the core index + p.with_id(2200) + .with_default_command("test-parachain") + .with_default_image(images.cumulus.as_str()) + .with_chain("elastic-scaling") + .with_default_args(vec![ + ("--experimental-use-slot-based").into(), + ("-lparachain=debug,aura=debug").into(), + ]) + .with_collator(|n| n.with_name("collator-elastic")) + }) + .build() + .map_err(|e| { + let errs = e.into_iter().map(|e| e.to_string()).collect::<Vec<_>>().join(" "); + anyhow!("config errs: {errs}") + })?; + + let spawn_fn = zombienet_sdk::environment::get_spawn_fn(); + let network = spawn_fn(config).await?; + + let relay_node = network.get_node("validator-0")?; + let para_node_elastic = network.get_node("collator-elastic")?; + let para_node_elastic_mvp = network.get_node("collator-elastic-mvp")?; + + let relay_client: OnlineClient<PolkadotConfig> = relay_node.wait_client().await?; + let alice = dev::alice(); + + // Assign two extra cores to each parachain. + relay_client + .tx() + .sign_and_submit_then_watch_default( + &rococo::tx() + .sudo() + .sudo(rococo::runtime_types::rococo_runtime::RuntimeCall::Utility( + rococo::runtime_types::pallet_utility::pallet::Call::batch { + calls: vec![ + rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: 0, + begin: 0, + assignment: vec![(CoreAssignment::Task(2100), PartsOf57600(57600))], + end_hint: None + } + ), + rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: 1, + begin: 0, + assignment: vec![(CoreAssignment::Task(2100), PartsOf57600(57600))], + end_hint: None + } + ), + rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: 2, + begin: 0, + assignment: vec![(CoreAssignment::Task(2200), PartsOf57600(57600))], + end_hint: None + } + ), + rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: 3, + begin: 0, + assignment: vec![(CoreAssignment::Task(2200), PartsOf57600(57600))], + end_hint: None + } + ) + ], + }, + )), + &alice, + ) + .await? + .wait_for_finalized_success() + .await?; + + log::info!("2 more cores assigned to each parachain"); + + // Expect a backed candidate count of at least 39 for each parachain in 15 relay chain blocks + // (2.6 candidates per para per relay chain block). + // Note that only blocks after the first session change and blocks that don't contain a session + // change will be counted. + assert_para_throughput( + &relay_client, + 15, + [(ParaId::from(2100), 39..46), (ParaId::from(2200), 39..46)] + .into_iter() + .collect(), + ) + .await?; + + // Assert the parachain finalized block height is also on par with the number of backed + // candidates. + assert_finalized_block_height(¶_node_elastic.wait_client().await?, 36..46).await?; + assert_finalized_block_height(¶_node_elastic_mvp.wait_client().await?, 36..46).await?; + + log::info!("Test finished successfully"); + + Ok(()) +} diff --git a/polkadot/zombienet-sdk-tests/tests/functional/async_backing_6_seconds_rate.rs b/polkadot/zombienet-sdk-tests/tests/functional/async_backing_6_seconds_rate.rs new file mode 100644 index 0000000000000000000000000000000000000000..1f8c2aeff1c23ad2c22ac00a0ca9a7b45c1d45ba --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/functional/async_backing_6_seconds_rate.rs @@ -0,0 +1,92 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Test we are producing 12-second parachain blocks if using an old collator, pre async-backing. + +use anyhow::anyhow; + +use crate::helpers::{assert_finalized_block_height, assert_para_throughput}; +use polkadot_primitives::Id as ParaId; +use serde_json::json; +use subxt::{OnlineClient, PolkadotConfig}; +use zombienet_sdk::NetworkConfigBuilder; + +#[tokio::test(flavor = "multi_thread")] +async fn async_backing_6_seconds_rate_test() -> Result<(), anyhow::Error> { + let _ = env_logger::try_init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + let images = zombienet_sdk::environment::get_images_from_env(); + + let config = NetworkConfigBuilder::new() + .with_relaychain(|r| { + let r = r + .with_chain("rococo-local") + .with_default_command("polkadot") + .with_default_image(images.polkadot.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_genesis_overrides(json!({ + "configuration": { + "config": { + "scheduler_params": { + "group_rotation_frequency": 4 + } + } + } + })) + .with_node(|node| node.with_name("validator-0")); + + (1..12) + .fold(r, |acc, i| acc.with_node(|node| node.with_name(&format!("validator-{i}")))) + }) + .with_parachain(|p| { + p.with_id(2000) + .with_default_command("adder-collator") + .with_default_image( + std::env::var("COL_IMAGE") + .unwrap_or("docker.io/paritypr/colander:latest".to_string()) + .as_str(), + ) + .cumulus_based(false) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_collator(|n| n.with_name("collator-adder-2000")) + }) + .with_parachain(|p| { + p.with_id(2001) + .with_default_command("polkadot-parachain") + .with_default_image(images.cumulus.as_str()) + .with_default_args(vec![("-lparachain=debug,aura=debug").into()]) + .with_collator(|n| n.with_name("collator-2001")) + }) + .build() + .map_err(|e| { + let errs = e.into_iter().map(|e| e.to_string()).collect::<Vec<_>>().join(" "); + anyhow!("config errs: {errs}") + })?; + + let spawn_fn = zombienet_sdk::environment::get_spawn_fn(); + let network = spawn_fn(config).await?; + + let relay_node = network.get_node("validator-0")?; + let para_node_2001 = network.get_node("collator-2001")?; + + let relay_client: OnlineClient<PolkadotConfig> = relay_node.wait_client().await?; + + assert_para_throughput( + &relay_client, + 15, + [(ParaId::from(2000), 11..16), (ParaId::from(2001), 11..16)] + .into_iter() + .collect(), + ) + .await?; + + // Assert the parachain finalized block height is also on par with the number of backed + // candidates. We can only do this for the collator based on cumulus. + assert_finalized_block_height(¶_node_2001.wait_client().await?, 10..16).await?; + + log::info!("Test finished successfully"); + + Ok(()) +} diff --git a/polkadot/zombienet-sdk-tests/tests/functional/duplicate_collations.rs b/polkadot/zombienet-sdk-tests/tests/functional/duplicate_collations.rs new file mode 100644 index 0000000000000000000000000000000000000000..43420692d32ed1999fc1d256b6baafa89389afba --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/functional/duplicate_collations.rs @@ -0,0 +1,154 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Test that a parachain using a malus undying collator, sending the same collation to all assigned +// cores, does not break the relay chain and that blocks are included, backed by a normal collator. + +use anyhow::anyhow; + +use crate::helpers::{ + assert_para_throughput, rococo, + rococo::runtime_types::{ + pallet_broker::coretime_interface::CoreAssignment, + polkadot_runtime_parachains::assigner_coretime::PartsOf57600, + }, +}; +use polkadot_primitives::Id as ParaId; +use serde_json::json; +use subxt::{OnlineClient, PolkadotConfig}; +use subxt_signer::sr25519::dev; +use zombienet_sdk::NetworkConfigBuilder; + +const VALIDATOR_COUNT: u8 = 3; + +#[tokio::test(flavor = "multi_thread")] +async fn duplicate_collations_test() -> Result<(), anyhow::Error> { + let _ = env_logger::try_init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + let images = zombienet_sdk::environment::get_images_from_env(); + + let config = NetworkConfigBuilder::new() + .with_relaychain(|r| { + let r = r + .with_chain("rococo-local") + .with_default_command("polkadot") + .with_default_image(images.polkadot.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_genesis_overrides(json!({ + "configuration": { + "config": { + "scheduler_params": { + "num_cores": 2 + }, + "async_backing_params": { + "max_candidate_depth": 6 + } + } + } + })) + // Have to set a `with_node` outside of the loop below, so that `r` has the right + // type. + .with_node(|node| node.with_name("validator-0")); + + (1..VALIDATOR_COUNT) + .fold(r, |acc, i| acc.with_node(|node| node.with_name(&format!("validator-{i}")))) + }) + .with_parachain(|p| { + p.with_id(2000) + .with_default_command("undying-collator") + .cumulus_based(false) + .with_default_image( + std::env::var("COL_IMAGE") + .unwrap_or("docker.io/paritypr/colander:latest".to_string()) + .as_str(), + ) + .with_collator(|n| { + n.with_name("normal-collator").with_args(vec![("-lparachain=debug").into()]) + }) + .with_collator(|n| { + n.with_name("malus-collator").with_args(vec![ + ("-lparachain=debug").into(), + ("--malus-type=duplicate-collations").into(), + ]) + }) + }) + .build() + .map_err(|e| { + let errs = e.into_iter().map(|e| e.to_string()).collect::<Vec<_>>().join(" "); + anyhow!("config errs: {errs}") + })?; + + let spawn_fn = zombienet_sdk::environment::get_spawn_fn(); + let network = spawn_fn(config).await?; + + let relay_node = network.get_node("validator-0")?; + + let relay_client: OnlineClient<PolkadotConfig> = relay_node.wait_client().await?; + let alice = dev::alice(); + + // Assign two extra cores to parachain-2000. + relay_client + .tx() + .sign_and_submit_then_watch_default( + &rococo::tx() + .sudo() + .sudo(rococo::runtime_types::rococo_runtime::RuntimeCall::Utility( + rococo::runtime_types::pallet_utility::pallet::Call::batch { + calls: vec![ + rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: 0, + begin: 0, + assignment: vec![(CoreAssignment::Task(2000), PartsOf57600(57600))], + end_hint: None + } + ), + rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: 1, + begin: 0, + assignment: vec![(CoreAssignment::Task(2000), PartsOf57600(57600))], + end_hint: None + } + ), + ], + }, + )), + &alice, + ) + .await? + .wait_for_finalized_success() + .await?; + + log::info!("2 more cores assigned to parachain-2000"); + + assert_para_throughput(&relay_client, 15, [(ParaId::from(2000), 40..46)].into_iter().collect()) + .await?; + + // Verify that all validators detect the malicious collator by checking their logs. This check + // must be performed after the para throughput check because the validator group needs to rotate + // at least once. This ensures that all validators have had a chance to detect the malicious + // behavior. + for i in 0..VALIDATOR_COUNT { + let validator_name = &format!("validator-{}", i); + let validator_node = network.get_node(validator_name)?; + validator_node + .wait_log_line_count_with_timeout( + "Candidate core index is invalid: The core index in commitments doesn't match the one in descriptor", + false, + 1_usize, + // Since we have this check after the para throughput check, all validators + // should have already detected the malicious collator, and all expected logs + // should have already appeared, so there is no need to wait more than 1 second. + 1_u64, + ) + .await + .unwrap_or_else(|error| panic!("Expected log not found for {}: {:?}", validator_name, error)); + } + + log::info!("Test finished successfully"); + + Ok(()) +} diff --git a/polkadot/zombienet-sdk-tests/tests/functional/mod.rs b/polkadot/zombienet-sdk-tests/tests/functional/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..7e5d313ff68dd194db0d6cdaa2ad8154e6010359 --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/functional/mod.rs @@ -0,0 +1,6 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +mod async_backing_6_seconds_rate; +mod duplicate_collations; +mod sync_backing; diff --git a/polkadot/zombienet-sdk-tests/tests/functional/sync_backing.rs b/polkadot/zombienet-sdk-tests/tests/functional/sync_backing.rs new file mode 100644 index 0000000000000000000000000000000000000000..6da45e2844919ea924317d2a65ced7e57e7c8060 --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/functional/sync_backing.rs @@ -0,0 +1,74 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Test we are producing 12-second parachain blocks if using an old collator, pre async-backing. + +use anyhow::anyhow; + +use crate::helpers::{assert_finalized_block_height, assert_para_throughput}; +use polkadot_primitives::Id as ParaId; +use serde_json::json; +use subxt::{OnlineClient, PolkadotConfig}; +use zombienet_sdk::NetworkConfigBuilder; + +#[tokio::test(flavor = "multi_thread")] +async fn sync_backing_test() -> Result<(), anyhow::Error> { + let _ = env_logger::try_init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + let images = zombienet_sdk::environment::get_images_from_env(); + + let config = NetworkConfigBuilder::new() + .with_relaychain(|r| { + let r = r + .with_chain("rococo-local") + .with_default_command("polkadot") + .with_default_image(images.polkadot.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_genesis_overrides(json!({ + "configuration": { + "config": { + "scheduler_params": { + "group_rotation_frequency": 4, + }, + } + } + })) + .with_node(|node| node.with_name("validator-0")); + + (1..5).fold(r, |acc, i| acc.with_node(|node| node.with_name(&format!("validator-{i}")))) + }) + .with_parachain(|p| { + p.with_id(2000) + .with_default_command("polkadot-parachain") + // This must be a very old polkadot-parachain image, pre async backing + .with_default_image(images.cumulus.as_str()) + .with_default_args(vec![("-lparachain=debug,aura=debug").into()]) + .with_collator(|n| n.with_name("collator-2000")) + }) + .build() + .map_err(|e| { + let errs = e.into_iter().map(|e| e.to_string()).collect::<Vec<_>>().join(" "); + anyhow!("config errs: {errs}") + })?; + + let spawn_fn = zombienet_sdk::environment::get_spawn_fn(); + let network = spawn_fn(config).await?; + + let relay_node = network.get_node("validator-0")?; + let para_node = network.get_node("collator-2000")?; + + let relay_client: OnlineClient<PolkadotConfig> = relay_node.wait_client().await?; + + assert_para_throughput(&relay_client, 15, [(ParaId::from(2000), 5..9)].into_iter().collect()) + .await?; + + // Assert the parachain finalized block height is also on par with the number of backed + // candidates. + assert_finalized_block_height(¶_node.wait_client().await?, 5..9).await?; + + log::info!("Test finished successfully"); + + Ok(()) +} diff --git a/polkadot/zombienet-sdk-tests/tests/helpers/mod.rs b/polkadot/zombienet-sdk-tests/tests/helpers/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..470345ca4d621fb87100b9c783098d288b22cdb6 --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/helpers/mod.rs @@ -0,0 +1,81 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +use polkadot_primitives::Id as ParaId; +use std::{collections::HashMap, ops::Range}; +use subxt::{OnlineClient, PolkadotConfig}; + +#[subxt::subxt(runtime_metadata_path = "metadata-files/rococo-local.scale")] +pub mod rococo {} + +// Helper function for asserting the throughput of parachains (total number of backed candidates in +// a window of relay chain blocks), after the first session change. +pub async fn assert_para_throughput( + relay_client: &OnlineClient<PolkadotConfig>, + stop_at: u32, + expected_candidate_ranges: HashMap<ParaId, Range<u32>>, +) -> Result<(), anyhow::Error> { + let mut blocks_sub = relay_client.blocks().subscribe_finalized().await?; + let mut candidate_count: HashMap<ParaId, u32> = HashMap::new(); + let mut current_block_count = 0; + let mut had_first_session_change = false; + + while let Some(block) = blocks_sub.next().await { + let block = block?; + log::debug!("Finalized relay chain block {}", block.number()); + let events = block.events().await?; + let is_session_change = events.has::<rococo::session::events::NewSession>()?; + + if !had_first_session_change && is_session_change { + had_first_session_change = true; + } + + if had_first_session_change && !is_session_change { + current_block_count += 1; + + for event in events.find::<rococo::para_inclusion::events::CandidateBacked>() { + *(candidate_count.entry(event?.0.descriptor.para_id.0.into()).or_default()) += 1; + } + } + + if current_block_count == stop_at { + break; + } + } + + log::info!( + "Reached {} finalized relay chain blocks that contain backed candidates. The per-parachain distribution is: {:#?}", + stop_at, + candidate_count + ); + + for (para_id, expected_candidate_range) in expected_candidate_ranges { + let actual = candidate_count + .get(¶_id) + .expect("ParaId did not have any backed candidates"); + assert!( + expected_candidate_range.contains(actual), + "Candidate count {actual} not within range {expected_candidate_range:?}" + ); + } + + Ok(()) +} + +// Helper function for retrieving the latest finalized block height and asserting it's within a +// range. +pub async fn assert_finalized_block_height( + client: &OnlineClient<PolkadotConfig>, + expected_range: Range<u32>, +) -> Result<(), anyhow::Error> { + if let Some(block) = client.blocks().subscribe_finalized().await?.next().await { + let height = block?.number(); + log::info!("Finalized block number {height}"); + + assert!( + expected_range.contains(&height), + "Finalized block number {height} not within range {expected_range:?}" + ); + } + Ok(()) +} diff --git a/polkadot/zombienet-sdk-tests/tests/lib.rs b/polkadot/zombienet-sdk-tests/tests/lib.rs index 74cdc0765600f52688c0bf255763c60c822c798a..9feb9775e450e0149b0e357c4861a62beff5732e 100644 --- a/polkadot/zombienet-sdk-tests/tests/lib.rs +++ b/polkadot/zombienet-sdk-tests/tests/lib.rs @@ -1,4 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 +#[cfg(feature = "zombie-metadata")] +mod helpers; + +#[cfg(feature = "zombie-metadata")] +mod elastic_scaling; +#[cfg(feature = "zombie-metadata")] +mod functional; +#[cfg(feature = "zombie-metadata")] mod smoke; diff --git a/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs b/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs index 7880dc782d05d0550c4b178c14362c897b1bcb08..59a71a83e01ecf91bbad9b5358caa0bc08f0cd0e 100644 --- a/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs +++ b/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs @@ -10,21 +10,24 @@ //! normal parachain runtime WILL mess things up. use anyhow::anyhow; -#[subxt::subxt(runtime_metadata_path = "metadata-files/rococo-local.scale")] -pub mod rococo {} #[subxt::subxt(runtime_metadata_path = "metadata-files/coretime-rococo-local.scale")] mod coretime_rococo {} -use rococo::runtime_types::{ - staging_xcm::v4::{ - asset::{Asset, AssetId, Assets, Fungibility}, - junction::Junction, - junctions::Junctions, - location::Location, +use crate::helpers::rococo::{ + self as rococo_api, + runtime_types::{ + polkadot_parachain_primitives::primitives, + staging_xcm::v4::{ + asset::{Asset, AssetId, Assets, Fungibility}, + junction::Junction, + junctions::Junctions, + location::Location, + }, + xcm::{VersionedAssets, VersionedLocation}, }, - xcm::{VersionedAssets, VersionedLocation}, }; + use serde_json::json; use std::{fmt::Display, sync::Arc}; use subxt::{events::StaticEvent, utils::AccountId32, OnlineClient, PolkadotConfig}; @@ -41,8 +44,6 @@ use coretime_rococo::{ }, }; -use rococo::{self as rococo_api, runtime_types::polkadot_parachain_primitives::primitives}; - type CoretimeRuntimeCall = coretime_api::runtime_types::coretime_rococo_runtime::RuntimeCall; type CoretimeUtilityCall = coretime_api::runtime_types::pallet_utility::pallet::Call; type CoretimeBrokerCall = coretime_api::runtime_types::pallet_broker::pallet::Call; @@ -180,7 +181,7 @@ where #[tokio::test(flavor = "multi_thread")] async fn coretime_revenue_test() -> Result<(), anyhow::Error> { - env_logger::init_from_env( + let _ = env_logger::try_init_from_env( env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), ); @@ -499,7 +500,7 @@ async fn coretime_revenue_test() -> Result<(), anyhow::Error> { assert_total_issuance(relay_client.clone(), para_client.clone(), total_issuance).await; - log::info!("Test finished successfuly"); + log::info!("Test finished successfully"); Ok(()) } diff --git a/polkadot/zombienet-sdk-tests/tests/smoke/mod.rs b/polkadot/zombienet-sdk-tests/tests/smoke/mod.rs index a3fe153826740dbd5d1f92e641fce21fc31cebeb..072a9d54ecdad96b2cc7790ea0cfc63ae1fdb96c 100644 --- a/polkadot/zombienet-sdk-tests/tests/smoke/mod.rs +++ b/polkadot/zombienet-sdk-tests/tests/smoke/mod.rs @@ -1,5 +1,4 @@ // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 -#[cfg(feature = "zombie-metadata")] mod coretime_revenue; diff --git a/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.toml b/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.toml deleted file mode 100644 index 611978a33a5f145274dd3c6c158e0de69a1c436a..0000000000000000000000000000000000000000 --- a/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.toml +++ /dev/null @@ -1,49 +0,0 @@ -[settings] -timeout = 1000 - -[relaychain.genesis.runtimeGenesis.patch.configuration.config.async_backing_params] - max_candidate_depth = 6 - allowed_ancestry_len = 2 - -[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] - max_validators_per_core = 1 - num_cores = 3 - -[relaychain.genesis.runtimeGenesis.patch.configuration.config.approval_voting_params] - max_approval_coalesce_count = 5 - -[relaychain] -default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" -chain = "rococo-local" -default_command = "polkadot" - - [relaychain.default_resources] - limits = { memory = "4G", cpu = "3" } - requests = { memory = "4G", cpu = "3" } - - [[relaychain.node_groups]] - name = "elastic-validator" - count = 5 - args = [ "-lparachain=debug,parachain::candidate-backing=trace,parachain::provisioner=trace,parachain::prospective-parachains=trace,runtime=debug"] - -{% for id in range(2000,2002) %} -[[parachains]] -id = {{id}} -addToGenesis = true - [parachains.default_resources] - limits = { memory = "4G", cpu = "3" } - requests = { memory = "4G", cpu = "3" } - - [parachains.collator] - name = "some-parachain" - image = "{{COL_IMAGE}}" - command = "adder-collator" - args = ["-lparachain::collation-generation=trace,parachain::collator-protocol=trace,parachain=debug"] - -{% endfor %} - -# This represents the layout of the adder collator block header. -[types.Header] -number = "u64" -parent_hash = "Hash" -post_state = "Hash" diff --git a/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.zndsl b/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.zndsl deleted file mode 100644 index d47ef8f415f7ac9ca94b825de23580ab6131f013..0000000000000000000000000000000000000000 --- a/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.zndsl +++ /dev/null @@ -1,28 +0,0 @@ -Description: Test with adder collator using 3 cores and async backing -Network: ./0001-basic-3cores-6s-blocks.toml -Creds: config - -# Check authority status. -elastic-validator-0: reports node_roles is 4 -elastic-validator-1: reports node_roles is 4 -elastic-validator-2: reports node_roles is 4 -elastic-validator-3: reports node_roles is 4 -elastic-validator-4: reports node_roles is 4 - - -# Register 2 extra cores to this some-parachain. -elastic-validator-0: js-script ./assign-core.js with "0,2000,57600" return is 0 within 600 seconds -elastic-validator-0: js-script ./assign-core.js with "1,2000,57600" return is 0 within 600 seconds - -# Wait for 20 relay chain blocks -elastic-validator-0: reports substrate_block_height{status="best"} is at least 20 within 600 seconds - -# Non elastic parachain should progress normally -some-parachain-1: count of log lines containing "Parachain velocity: 1" is at least 5 within 20 seconds -# Sanity -some-parachain-1: count of log lines containing "Parachain velocity: 2" is 0 - -# Parachain should progress 3 blocks per relay chain block ideally, however CI might not be -# the most performant environment so we'd just use a lower bound of 2 blocks per RCB -elastic-validator-0: parachain 2000 block height is at least 20 within 200 seconds - diff --git a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml b/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml deleted file mode 100644 index 9b3576eaa3c212bd9490095c12ae4bca65f6fa54..0000000000000000000000000000000000000000 --- a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml +++ /dev/null @@ -1,40 +0,0 @@ -[settings] -timeout = 1000 -bootnode = true - -[relaychain.genesis.runtimeGenesis.patch.configuration.config] - needed_approvals = 4 - -[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] - max_validators_per_core = 2 - num_cores = 2 - -[relaychain] -default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" -chain = "rococo-local" -default_command = "polkadot" - -[relaychain.default_resources] -limits = { memory = "4G", cpu = "2" } -requests = { memory = "2G", cpu = "1" } - - [[relaychain.nodes]] - name = "alice" - validator = "true" - - [[relaychain.node_groups]] - name = "validator" - count = 3 - args = [ "-lparachain=debug,runtime=debug"] - -[[parachains]] -id = 2000 -default_command = "polkadot-parachain" -add_to_genesis = false -register_para = true -onboard_as_parachain = false - - [parachains.collator] - name = "collator2000" - command = "polkadot-parachain" - args = [ "-lparachain=debug" ] diff --git a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.zndsl b/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.zndsl deleted file mode 100644 index 7ba896e1c90397642ff0736477f9c793bdb746ec..0000000000000000000000000000000000000000 --- a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.zndsl +++ /dev/null @@ -1,20 +0,0 @@ -Description: Test that a paraid acquiring multiple cores does not brick itself if ElasticScalingMVP feature is enabled in genesis -Network: ./0002-elastic-scaling-doesnt-break-parachains.toml -Creds: config - -# Check authority status. -validator: reports node_roles is 4 - -validator: reports substrate_block_height{status="finalized"} is at least 10 within 100 seconds - -# Ensure parachain was able to make progress. -validator: parachain 2000 block height is at least 10 within 200 seconds - -# Register the second core assigned to this parachain. -alice: js-script ./assign-core.js with "0,2000,57600" return is 0 within 600 seconds -alice: js-script ./assign-core.js with "0,2000,57600" return is 0 within 600 seconds - -validator: reports substrate_block_height{status="finalized"} is at least 35 within 100 seconds - -# Ensure parachain is now making progress. -validator: parachain 2000 block height is at least 30 within 200 seconds diff --git a/polkadot/zombienet_tests/elastic_scaling/assign-core.js b/polkadot/zombienet_tests/elastic_scaling/assign-core.js deleted file mode 120000 index eeb6402c06f5e52cedf150f924d6791beb1d9867..0000000000000000000000000000000000000000 --- a/polkadot/zombienet_tests/elastic_scaling/assign-core.js +++ /dev/null @@ -1 +0,0 @@ -../assign-core.js \ No newline at end of file diff --git a/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml b/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml deleted file mode 100644 index b776622fdce33df2e9a78debc31ee3e62ae4805d..0000000000000000000000000000000000000000 --- a/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml +++ /dev/null @@ -1,54 +0,0 @@ -[settings] -timeout = 1000 - -[relaychain] -default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" -chain = "rococo-local" - -[relaychain.genesis.runtimeGenesis.patch.configuration.config] - needed_approvals = 4 - relay_vrf_modulo_samples = 6 - -[relaychain.genesis.runtimeGenesis.patch.configuration.config.async_backing_params] - max_candidate_depth = 3 - allowed_ancestry_len = 2 - -[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] - lookahead = 2 - group_rotation_frequency = 4 - - -[relaychain.default_resources] -limits = { memory = "4G", cpu = "2" } -requests = { memory = "2G", cpu = "1" } - - [[relaychain.node_groups]] - name = "alice" - args = [ "-lparachain=debug" ] - count = 12 - -[[parachains]] -id = 2000 -addToGenesis = true -genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=1" - - [parachains.collator] - name = "collator01" - image = "{{COL_IMAGE}}" - command = "undying-collator" - args = ["-lparachain=debug", "--pov-size=100000", "--pvf-complexity=1", "--parachain-id=2000"] - -[[parachains]] -id = 2001 -cumulus_based = true - - [parachains.collator] - name = "collator02" - image = "{{CUMULUS_IMAGE}}" - command = "polkadot-parachain" - args = ["-lparachain=debug"] - -[types.Header] -number = "u64" -parent_hash = "Hash" -post_state = "Hash" \ No newline at end of file diff --git a/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.zndsl b/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.zndsl deleted file mode 100644 index 0d01af82833e36afd3c38b2e00e9d604ace46797..0000000000000000000000000000000000000000 --- a/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.zndsl +++ /dev/null @@ -1,20 +0,0 @@ -Description: Test we are producing blocks at 6 seconds clip -Network: ./0011-async-backing-6-seconds-rate.toml -Creds: config - -# Check authority status. -alice: reports node_roles is 4 - -# Ensure parachains are registered. -alice: parachain 2000 is registered within 60 seconds -alice: parachain 2001 is registered within 60 seconds - -# Ensure parachains made progress. -alice: reports substrate_block_height{status="finalized"} is at least 10 within 100 seconds - -# This parachains should produce blocks at 6s clip, let's assume an 8s rate, allowing for -# some slots to be missed on slower machines -alice: parachain 2000 block height is at least 30 within 240 seconds -# This should already have produced the needed blocks -alice: parachain 2001 block height is at least 30 within 6 seconds - diff --git a/polkadot/zombienet_tests/functional/0014-chunk-fetching-network-compatibility.toml b/polkadot/zombienet_tests/functional/0014-chunk-fetching-network-compatibility.toml index 881abab64fd07b8495189982119b3f985332b8a7..874b8a09bb2489e0b00a677da3fd3aaea1a08beb 100644 --- a/polkadot/zombienet_tests/functional/0014-chunk-fetching-network-compatibility.toml +++ b/polkadot/zombienet_tests/functional/0014-chunk-fetching-network-compatibility.toml @@ -42,7 +42,8 @@ chain = "glutton-westend-local-{{id}}" [parachains.collator] name = "collator" - image = "{{CUMULUS_IMAGE}}" + # Use an old image that does not send out v2 receipts, as the old validators will still check the collator signatures. + image = "docker.io/paritypr/polkadot-parachain-debug:master-bde0bbe5" args = ["-lparachain=debug"] {% endfor %} diff --git a/polkadot/zombienet_tests/functional/0015-coretime-shared-core.toml b/polkadot/zombienet_tests/functional/0015-coretime-shared-core.toml index fed30e0db05321631fdce66da858e1431ded64dd..c6545e476a64d22b242f2c4e238a3e7ba5df7ec0 100644 --- a/polkadot/zombienet_tests/functional/0015-coretime-shared-core.toml +++ b/polkadot/zombienet_tests/functional/0015-coretime-shared-core.toml @@ -1,13 +1,8 @@ [settings] timeout = 1000 -[relaychain.genesis.runtimeGenesis.patch.configuration.config.async_backing_params] - max_candidate_depth = 3 - allowed_ancestry_len = 2 - [relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] max_validators_per_core = 1 - lookahead = 2 num_cores = 4 [relaychain.genesis.runtimeGenesis.patch.configuration.config.approval_voting_params] diff --git a/polkadot/zombienet_tests/functional/0017-sync-backing.toml b/polkadot/zombienet_tests/functional/0017-sync-backing.toml deleted file mode 100644 index 2550054c8dadaf75b34b4c4a50f402576f3f5266..0000000000000000000000000000000000000000 --- a/polkadot/zombienet_tests/functional/0017-sync-backing.toml +++ /dev/null @@ -1,48 +0,0 @@ -[settings] -timeout = 1000 - -[relaychain] -default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" -chain = "rococo-local" - -[relaychain.genesis.runtimeGenesis.patch.configuration.config.async_backing_params] - max_candidate_depth = 0 - allowed_ancestry_len = 0 - -[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] - lookahead = 2 - group_rotation_frequency = 4 - -[relaychain.default_resources] -limits = { memory = "4G", cpu = "2" } -requests = { memory = "2G", cpu = "1" } - - [[relaychain.node_groups]] - name = "alice" - args = [ "-lparachain=debug" ] - count = 10 - -[[parachains]] -id = 2000 -addToGenesis = true - - [parachains.collator] - name = "collator01" - image = "{{COL_IMAGE}}" - command = "adder-collator" - args = ["-lparachain=debug"] - -[[parachains]] -id = 2001 -cumulus_based = true - - [parachains.collator] - name = "collator02" - image = "{{CUMULUS_IMAGE}}" - command = "polkadot-parachain" - args = ["-lparachain=debug"] - -[types.Header] -number = "u64" -parent_hash = "Hash" -post_state = "Hash" \ No newline at end of file diff --git a/polkadot/zombienet_tests/functional/0017-sync-backing.zndsl b/polkadot/zombienet_tests/functional/0017-sync-backing.zndsl deleted file mode 100644 index a53de784b2d1349fa890ce51984b6865c2c94fd5..0000000000000000000000000000000000000000 --- a/polkadot/zombienet_tests/functional/0017-sync-backing.zndsl +++ /dev/null @@ -1,22 +0,0 @@ -Description: Test we are producing 12-second parachain blocks if sync backing is configured -Network: ./0017-sync-backing.toml -Creds: config - -# Check authority status. -alice: reports node_roles is 4 - -# Ensure parachains are registered. -alice: parachain 2000 is registered within 60 seconds -alice: parachain 2001 is registered within 60 seconds - -# Ensure parachains made progress. -alice: reports substrate_block_height{status="finalized"} is at least 10 within 100 seconds - -# This parachains should produce blocks at 12s clip, let's assume an 14s rate, allowing for -# some slots to be missed on slower machines -alice: parachain 2000 block height is at least 21 within 300 seconds -alice: parachain 2000 block height is lower than 25 within 2 seconds - -# This should already have produced the needed blocks -alice: parachain 2001 block height is at least 21 within 10 seconds -alice: parachain 2001 block height is lower than 25 within 2 seconds diff --git a/polkadot/zombienet_tests/functional/0018-shared-core-idle-parachain.toml b/polkadot/zombienet_tests/functional/0018-shared-core-idle-parachain.toml index 745c4f9e24b1bbcd79608a3ddfc20d8ea833be6e..050c1f01923bc07ecd80271bbf9f1ab98fd754b2 100644 --- a/polkadot/zombienet_tests/functional/0018-shared-core-idle-parachain.toml +++ b/polkadot/zombienet_tests/functional/0018-shared-core-idle-parachain.toml @@ -3,7 +3,6 @@ timeout = 1000 [relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] max_validators_per_core = 2 - lookahead = 2 num_cores = 4 group_rotation_frequency = 4 @@ -36,4 +35,4 @@ chain = "glutton-westend-local-2000" name = "collator-2000" image = "{{CUMULUS_IMAGE}}" command = "polkadot-parachain" - args = ["-lparachain=debug"] + args = ["-lparachain=debug", "--experimental-use-slot-based"] diff --git a/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml new file mode 100644 index 0000000000000000000000000000000000000000..f9028b930cfec9b32b54185f4097867beb9b02cf --- /dev/null +++ b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml @@ -0,0 +1,53 @@ +[settings] +timeout = 1000 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] + max_validators_per_core = 4 + num_cores = 1 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.approval_voting_params] + needed_approvals = 3 + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +chain = "rococo-local" +command = "polkadot" + + [[relaychain.node_groups]] + name = "validator" + args = ["-lparachain=debug,parachain::collator-protocol=trace" ] + count = 4 + +[[parachains]] +id = 2000 +register_para = false +onboard_as_parachain = false +add_to_genesis = false +chain = "glutton-westend-local-2000" + [parachains.genesis.runtimeGenesis.patch.glutton] + compute = "50000000" + storage = "2500000000" + trashDataCount = 5120 + + [parachains.collator] + name = "collator-2000" + image = "{{CUMULUS_IMAGE}}" + command = "polkadot-parachain" + args = ["-lparachain=debug,parachain::collator-protocol=trace", "--experimental-use-slot-based"] + +[[parachains]] +id = 2001 +register_para = false +onboard_as_parachain = false +add_to_genesis = false +chain = "glutton-westend-local-2001" + [parachains.genesis.runtimeGenesis.patch.glutton] + compute = "50000000" + storage = "2500000000" + trashDataCount = 5120 + + [parachains.collator] + name = "collator-2001" + image = "{{CUMULUS_IMAGE}}" + command = "polkadot-parachain" + args = ["-lparachain=debug"] diff --git a/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.zndsl b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..8892b03ac29cb2ea9cf17903d744906c6b3a4e13 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.zndsl @@ -0,0 +1,16 @@ +Description: CT shared core fairness test +Network: ./0019-coretime-collation-fetching-fairness.toml +Creds: config + +validator: reports node_roles is 4 + +validator-0: js-script ./force-register-paras.js with "2000,2001" return is 0 within 600 seconds +# core 0 is shared 3:1 between paras +validator-0: js-script ./assign-core.js with "0,2000,43200,2001,14400" return is 0 within 600 seconds + +collator-2000: reports block height is at least 9 within 200 seconds +collator-2001: reports block height is at least 3 within 10 seconds + +# hardcoded check to verify that included onchain events are indeed 3:1 +validator-0: js-script ./0019-verify-included-events.js return is 1 within 120 seconds + diff --git a/polkadot/zombienet_tests/functional/0019-verify-included-events.js b/polkadot/zombienet_tests/functional/0019-verify-included-events.js new file mode 100644 index 0000000000000000000000000000000000000000..6557a5a80e6b7f7934c8c00cf9c701f199717c13 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0019-verify-included-events.js @@ -0,0 +1,51 @@ +function parse_pjs_int(input) { + return parseInt(input.replace(/,/g, '')); +} + +async function run(nodeName, networkInfo) { + const { wsUri, userDefinedTypes } = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + let blocks_per_para = {}; + + await new Promise(async (resolve, _) => { + let block_count = 0; + const unsubscribe = await api.query.system.events(async (events, block_hash) => { + block_count++; + + events.forEach((record) => { + const event = record.event; + + if (event.method != 'CandidateIncluded') { + return; + } + + let included_para_id = parse_pjs_int(event.toHuman().data[0].descriptor.paraId); + let relay_parent = event.toHuman().data[0].descriptor.relayParent; + if (blocks_per_para[included_para_id] == undefined) { + blocks_per_para[included_para_id] = 1; + } else { + blocks_per_para[included_para_id]++; + } + console.log(`CandidateIncluded for ${included_para_id}: block_offset=${block_count} relay_parent=${relay_parent}`); + }); + + if (block_count == 12) { + unsubscribe(); + return resolve(); + } + }); + }); + + console.log(`Result: 2000: ${blocks_per_para[2000]}, 2001: ${blocks_per_para[2001]}`); + // This check assumes that para 2000 runs slot based collator which respects its claim queue + // and para 2001 runs lookahead which generates blocks for each relay parent. + // + // For 12 blocks there will be one session change. One block won't have anything backed/included. + // In the next there will be one backed so for 12 blocks we should expect 10 included events - no + // more than 4 for para 2001 and at least 6 for para 2000. This should also cover the unlucky + // case when we observe two session changes during the 12 block period. + return (blocks_per_para[2000] >= 6) && (blocks_per_para[2001] <= 4); +} + +module.exports = { run }; diff --git a/polkadot/zombienet_tests/misc/0002-upgrade-node.toml b/polkadot/zombienet_tests/misc/0002-upgrade-node.toml index 1edb18abcececa32cadcf3756ac11e66be5f12c6..5e5e3719936ab0432479fcd217945bf113659b6d 100644 --- a/polkadot/zombienet_tests/misc/0002-upgrade-node.toml +++ b/polkadot/zombienet_tests/misc/0002-upgrade-node.toml @@ -30,7 +30,7 @@ addToGenesis = true [parachains.collator] name = "collator01" image = "{{COL_IMAGE}}" - command = "undying-collator" + command = "adder-collator" args = ["-lparachain=debug"] [[parachains]] @@ -40,7 +40,7 @@ addToGenesis = true [parachains.collator] name = "collator02" image = "{{COL_IMAGE}}" - command = "undying-collator" + command = "adder-collator" args = ["-lparachain=debug"] [types.Header] diff --git a/prdoc/pr_3926.prdoc b/prdoc/pr_3926.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..7f352f7a45fb39876d7d2d0771d3e5cee496ead3 --- /dev/null +++ b/prdoc/pr_3926.prdoc @@ -0,0 +1,30 @@ +title: Introduce pallet-asset-rewards + +doc: + - audience: Runtime Dev + description: | + Introduce pallet-asset-rewards, which allows accounts to be rewarded for freezing fungible + tokens. The motivation for creating this pallet is to allow incentivising LPs. + See the pallet docs for more info about the pallet. + +crates: + - name: pallet-asset-rewards + bump: major + - name: polkadot-sdk + bump: minor + - name: kitchensink-runtime + bump: major + - name: asset-hub-rococo-runtime + bump: major + - name: asset-hub-westend-runtime + bump: major + - name: assets-common + bump: minor + - name: rococo-runtime + bump: minor + - name: westend-runtime + bump: patch + - name: frame-support + bump: minor + - name: emulated-integration-tests-common + bump: minor diff --git a/prdoc/pr_4273.prdoc b/prdoc/pr_4273.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1ff0a5782a412fbecdacf3108d3e59afcb6d3d78 --- /dev/null +++ b/prdoc/pr_4273.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "[pallet-broker] add extrinsic to reserve a system core without having to wait two sale boundaries" + +doc: + - audience: Runtime User + description: | + When calling the reserve extrinsic after sales have started, the assignment will be reserved, + but two sale period boundaries must pass before the core is actually assigned. A new + `force_reserve` extrinsic is introduced to allow a core to be immediately assigned. + +crates: + - name: pallet-broker + bump: major + - name: coretime-rococo-runtime + bump: patch + - name: coretime-westend-runtime + bump: patch diff --git a/prdoc/pr_4529.prdoc b/prdoc/pr_4529.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..32beea17ad6b667893564c9105f3a46b3c06e061 --- /dev/null +++ b/prdoc/pr_4529.prdoc @@ -0,0 +1,22 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Removed `pallet::getter` usage from pallet-grandpa + +doc: + - audience: Runtime Dev + description: | + This PR removed the `pallet::getter`s from `pallet-grandpa`. + The syntax `StorageItem::<T, I>::get()` should be used instead + +crates: + - name: pallet-grandpa + bump: minor + - name: kitchensink-runtime + bump: none + - name: westend-runtime + bump: none + - name: polkadot-test-runtime + bump: none + - name: rococo-runtime + bump: none diff --git a/prdoc/pr_4722.prdoc b/prdoc/pr_4722.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a5bdbbeb3df9aa45868aa8cdd0156d40ae23e683 --- /dev/null +++ b/prdoc/pr_4722.prdoc @@ -0,0 +1,33 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Implement pallet view functions + +doc: + - audience: Runtime Dev + description: | + Read-only view functions can now be defined on pallets. These functions provide an interface for querying state, + from both outside and inside the runtime. Common queries can be defined on pallets, without users having to + access the storage directly. + + - audience: Runtime User + description: | + Querying the runtime state is now easier with the introduction of pallet view functions. Clients can call commonly + defined view functions rather than accessing the storage directly. These are similar to the Runtime APIs, but + are defined within the runtime itself. + +crates: + - name: frame-support + bump: minor + - name: sp-metadata-ir + bump: major + - name: frame-support-procedural + bump: patch + - name: pallet-example-view-functions + bump: patch + - name: cumulus-pov-validator + bump: none + - name: cumulus-pallet-weight-reclaim + bump: patch + - name: westend-runtime + bump: minor \ No newline at end of file diff --git a/prdoc/pr_4880.prdoc b/prdoc/pr_4880.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1bcd09088b5f03a3ff75339ae781bc09874765e4 --- /dev/null +++ b/prdoc/pr_4880.prdoc @@ -0,0 +1,31 @@ +title: Collation fetching fairness in collator protocol + +doc: + - audience: "Node Dev" + description: | + Implements collation fetching fairness in the validator side of the collator protocol. With + core time in place if two (or more) parachains share a single core no fairness was guaranteed + between them in terms of collation fetching. The current implementation was accepting up to + `max_candidate_depth + 1` seconded collations per relay parent and once this limit is reached + no new collations are accepted. A misbehaving collator can abuse this fact and prevent other + collators/parachains from advertising collations by advertising `max_candidate_depth + 1` + collations of its own. + To address this issue two changes are made: + 1. For each parachain id the validator accepts advertisements until the number of entries in + the claim queue equals the number of seconded candidates. + 2. When new collation should be fetched the validator inspects what was seconded so far, + what's in the claim queue and picks the first slot which hasn't got a collation seconded + and there is no candidate pending seconding for it. If there is an advertisement in the + waiting queue for it it is fetched. Otherwise the next free slot is picked. + These two changes guarantee that: + 1. Validator doesn't accept more collations than it can actually back. + 2. Each parachain has got a fair share of core time based on its allocations in the claim + queue. + +crates: + - name: polkadot-collator-protocol + bump: patch + - name: polkadot + bump: patch + - name: polkadot-node-subsystem-util + bump: minor \ No newline at end of file diff --git a/prdoc/pr_5501.prdoc b/prdoc/pr_5501.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f2a5aa9a4667980452d4ac4dc728752da3591634 --- /dev/null +++ b/prdoc/pr_5501.prdoc @@ -0,0 +1,47 @@ +# 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: Currency to Fungible migration for pallet-staking + +doc: + - audience: Runtime User + description: | + Lazy migration of staking balance from `Currency::locks` to `Fungible::holds`. New extrinsic + `staking::migrate_currency` removes the old lock along with other housekeeping. Additionally, any ledger mutation + creates hold if it does not exist. + + The pallet-staking configuration item `Currency` is updated to use `fungible::hold::Mutate` type while still + requiring `LockableCurrency` type to be passed as `OldCurrency` for migration purposes. + + +crates: + - name: westend-runtime + bump: major + - name: kitchensink-runtime + bump: minor + - name: pallet-delegated-staking + bump: patch + - name: pallet-nomination-pools + bump: minor + - name: pallet-nomination-pools-runtime-api + bump: patch + - name: sp-staking + bump: patch + - name: pallet-beefy + bump: patch + - name: pallet-fast-unstake + bump: patch + - name: pallet-staking + bump: major + - name: pallet-grandpa + bump: patch + - name: pallet-babe + bump: patch + - name: pallet-nomination-pools-benchmarking + bump: patch + - name: pallet-session-benchmarking + bump: patch + - name: pallet-root-offences + bump: patch + - name: pallet-offences-benchmarking + bump: patch diff --git a/prdoc/pr_5855.prdoc b/prdoc/pr_5855.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..7735cfee9f37ddce294f9e507ffeeb1f512caab5 --- /dev/null +++ b/prdoc/pr_5855.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Remove feature `test-helpers` from sc-service + +doc: + - audience: Node Dev + description: | + Removes feature `test-helpers` from sc-service. + +crates: + - name: sc-service + bump: major + - name: sc-rpc-spec-v2 + bump: major diff --git a/prdoc/pr_5899.prdoc b/prdoc/pr_5899.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..fef810dd5f208e48aac8e11572023dc790489f3d --- /dev/null +++ b/prdoc/pr_5899.prdoc @@ -0,0 +1,52 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Remove usage of AccountKeyring" + +doc: + - audience: Runtime Dev + description: | + Compared with AccountKeyring, Sr25519Keyring and Ed25519Keyring are more intuitive. + When both Sr25519Keyring and Ed25519Keyring are required, using AccountKeyring bring confusion. + There are two AccountKeyring definitions, it becomes more complex if export two AccountKeyring from frame. + +crates: + - name: frame-support + bump: patch + - name: sp-keyring + bump: major + - name: sc-service + bump: patch + - name: sc-chain-spec + bump: patch + - name: sc-rpc + bump: patch + - name: sc-transaction-pool + bump: patch + - name: sc-rpc-spec-v2 + bump: patch + - name: polkadot-node-metrics + bump: patch + - name: substrate-frame-rpc-system + bump: patch + - name: westend-runtime + bump: patch + - name: polkadot-sdk-frame + bump: patch + - name: rococo-runtime + bump: patch + - name: sc-basic-authorship + bump: patch + - name: bridge-hub-test-utils + bump: patch + - name: sc-consensus-manual-seal + bump: patch + - name: snowbridge-pallet-inbound-queue + bump: patch + - name: snowbridge-runtime-test-common + bump: patch + - name: bridge-hub-rococo-runtime + bump: patch + - name: bridge-hub-westend-runtime + bump: patch + diff --git a/prdoc/pr_6140.prdoc b/prdoc/pr_6140.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..7e2bd3802cd7cc8b25abfefb1d98d4528c5c0b06 --- /dev/null +++ b/prdoc/pr_6140.prdoc @@ -0,0 +1,95 @@ +title: Accurate weight reclaim with frame_system::WeightReclaim and cumulus `StorageWeightReclaim` transaction extensions + +doc: + - audience: Runtime Dev + description: | + Since the introduction of transaction extension, the transaction extension weight is no longer part of base extrinsic weight. As a consequence some weight of transaction extensions are missed when calculating post dispatch weight and reclaiming unused block weight. + + For solo chains, in order to reclaim the weight accurately `frame_system::WeightReclaim` transaction extension must be used at the end of the transaction extension pipeline. + + For para chains `StorageWeightReclaim` in `cumulus-primitives-storage-weight-reclaim` is deprecated. + A new transaction extension `StorageWeightReclaim` in `cumulus-pallet-weight-reclaim` is introduced. + `StorageWeightReclaim` is meant to be used as a wrapping of the whole transaction extension pipeline, and will take into account all proof size accurately. + + The new wrapping transaction extension is used like this: + ```rust + /// The TransactionExtension to the basic transaction logic. + pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckTxVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckEra<Runtime>, + frame_system::CheckNonce<Runtime>, + pallet_transaction_payment::ChargeTransactionPayment<Runtime>, + BridgeRejectObsoleteHeadersAndMessages, + (bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages,), + frame_metadata_hash_extension::CheckMetadataHash<Runtime>, + frame_system::CheckWeight<Runtime>, + ), + >; + ``` + + NOTE: prior to transaction extension, `StorageWeightReclaim` also missed the some proof size used by other transaction extension prior to itself. This is also fixed by the wrapping `StorageWeightReclaim`. + +crates: +- name: cumulus-primitives-storage-weight-reclaim + bump: minor +- name: sp-runtime + bump: patch +- name: polkadot-sdk + bump: minor +- name: asset-hub-rococo-runtime + bump: major +- name: asset-hub-westend-runtime + bump: major +- name: bridge-hub-rococo-runtime + bump: major +- name: bridge-hub-westend-runtime + bump: major +- name: collectives-westend-runtime + bump: major +- name: coretime-rococo-runtime + bump: major +- name: coretime-westend-runtime + bump: major +- name: people-rococo-runtime + bump: major +- name: people-westend-runtime + bump: major +- name: contracts-rococo-runtime + bump: major +- name: frame-support + bump: minor +- name: frame-executive + bump: patch +- name: frame-system + bump: major +- name: staging-xcm-builder + bump: patch +- name: xcm-runtime-apis + bump: patch +- name: cumulus-pallet-weight-reclaim + bump: major +- name: polkadot-service + bump: major +- name: westend-runtime + bump: major +- name: frame-metadata-hash-extension + bump: patch +- name: frame-system-benchmarking + bump: major +- name: polkadot-sdk-frame + bump: major +- name: rococo-runtime + bump: major +- name: cumulus-pov-validator + bump: patch +- name: penpal-runtime + bump: major +- name: glutton-westend-runtime + bump: major +- name: rococo-parachain-runtime + bump: major diff --git a/prdoc/pr_6248.prdoc b/prdoc/pr_6248.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..71fb0891cac6501013e2a8c8973235c355af4d8f --- /dev/null +++ b/prdoc/pr_6248.prdoc @@ -0,0 +1,16 @@ +title: Upgrade libp2p to 0.54.1 + +doc: + - audience: [Node Dev, Node Operator] + description: | + Upgrade libp2p from 0.52.4 to 0.54.1 + +crates: + - name: sc-network + bump: major + - name: sc-network-types + bump: minor + - name: sc-network-sync + bump: patch + - name: sc-telemetry + bump: minor diff --git a/prdoc/pr_6267.prdoc b/prdoc/pr_6267.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..30ada4456259296f3a3a6781eae3b0d4547bef27 --- /dev/null +++ b/prdoc/pr_6267.prdoc @@ -0,0 +1,171 @@ +# 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 configurable number of genesis accounts with specified balances for benchmarking. + +doc: + - audience: Runtime Dev + description: | + This pull request adds an additional field `dev_accounts` to the `GenesisConfig` + of the balances pallet, feature gated by `runtime-benchmarks`. + + Bringing about an abitrary number of derived dev accounts when building the genesis + state. Runtime developers should supply a derivation path that includes an index placeholder + (i.e. "//Sender/{}") to generate multiple accounts from the same root in a consistent + manner. + +crates: + - name: substrate-test-runtime + bump: minor + - name: pallet-vesting + bump: patch + - name: pallet-utility + bump: patch + - name: pallet-tx-pause + bump: patch + - name: pallet-treasury + bump: patch + - name: pallet-transaction-storage + bump: patch + - name: pallet-transaction-payment + bump: patch + - name: pallet-asset-tx-payment + bump: patch + - name: pallet-asset-conversion-tx-payment + bump: patch + - name: pallet-tips + bump: patch + - name: pallet-state-trie-migration + bump: patch + - name: pallet-staking + bump: patch + - name: pallet-society + bump: patch + - name: pallet-safe-mode + bump: patch + - name: pallet-scored-pool + bump: patch + - name: pallet-statement + bump: patch + - name: pallet-root-offences + bump: patch + - name: pallet-revive + bump: patch + - name: pallet-revive-mock-network + bump: patch + - name: pallet-referenda + bump: patch + - name: pallet-recovery + bump: patch + - name: pallet-proxy + bump: patch + - name: pallet-preimage + bump: patch + - name: pallet-nis + bump: patch + - name: pallet-nomination-pools-test-delegate-stake + bump: minor + - name: pallet-multisig + bump: patch + - name: pallet-lottery + bump: patch + - name: pallet-indices + bump: patch + - name: pallet-identity + bump: patch + - name: pallet-grandpa + bump: patch + - name: pallet-fast-unstake + bump: patch + - name: frame-executive + bump: patch + - name: pallet-elections-phragmen + bump: patch + - name: pallet-election-provider-e2e-test + bump: minor + - name: pallet-election-provider-multi-phase + bump: patch + - name: pallet-democracy + bump: patch + - name: pallet-delegated-staking + bump: patch + - name: pallet-conviction-voting + bump: patch + - name: pallet-contracts + bump: patch + - name: pallet-contracts-mock-network + bump: patch + - name: pallet-collective + bump: patch + - name: pallet-child-bounties + bump: patch + - name: pallet-bounties + bump: patch + - name: pallet-beefy + bump: patch + - name: pallet-balances + bump: major + - name: pallet-babe + bump: patch + - name: pallet-asset-conversion + bump: patch + - name: pallet-asset-conversion-ops + bump: patch + - name: pallet-asset-rewards + bump: patch + - name: pallet-atomic-swap + bump: patch + - name: pallet-alliance + bump: patch + - name: node-testing + bump: minor + - name: sc-chain-spec + bump: patch + - name: staging-chain-spec-builder + bump: patch + - name: xcm-simulator-fuzzer + bump: minor + - name: xcm-simulator-fuzzer + bump: minor + - name: xcm-simulator-example + bump: patch + - name: xcm-runtime-apis + bump: patch + - name: staging-xcm-builder + bump: patch + - name: pallet-xcm + bump: patch + - name: xcm-docs + bump: minor + - name: polkadot-runtime-common + bump: patch + - name: parachains-runtimes-test-utils + bump: patch + - name: westend-emulated-chain + bump: minor + - name: rococo-emulated-chain + bump: minor + - name: penpal-emulated-chain + bump: minor + - name: people-westend-emulated-chain + bump: minor + - name: people-rococo-emulated-chain + bump: minor + - name: coretime-westend-emulated-chain + bump: minor + - name: coretime-rococo-emulated-chain + bump: minor + - name: collectives-westend-emulated-chain + bump: minor + - name: bridge-hub-westend-emulated-chain + bump: minor + - name: bridge-hub-rococo-emulated-chain + bump: minor + - name: asset-hub-westend-emulated-chain + bump: minor + - name: asset-hub-rococo-emulated-chain + bump: minor + - name: pallet-collator-selection + bump: patch + - name: pallet-bridge-messages + bump: patch diff --git a/prdoc/pr_6368.prdoc b/prdoc/pr_6368.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..4fd3963eb05ecf032130ad9d4a6607b87d043f10 --- /dev/null +++ b/prdoc/pr_6368.prdoc @@ -0,0 +1,7 @@ +title: Migrate inclusion benchmark to v2 +doc: +- audience: Runtime Dev + description: Migrate inclusion benchmark to v2. +crates: +- name: polkadot-runtime-parachains + bump: patch diff --git a/prdoc/pr_6405.prdoc b/prdoc/pr_6405.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9e4e0b3c6c20ef34bd41f7ed623137fa31e32721 --- /dev/null +++ b/prdoc/pr_6405.prdoc @@ -0,0 +1,9 @@ +title: '`fatxpool`: handling limits and priorities improvements' +doc: +- audience: Node Dev + description: |- + This PR provides a number of improvements and fixes around handling limits and priorities in the fork-aware transaction pool. + +crates: +- name: sc-transaction-pool + bump: major diff --git a/prdoc/pr_6419.prdoc b/prdoc/pr_6419.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..6cc155d64b91388f6462b0bba0b12a095c7a2db6 --- /dev/null +++ b/prdoc/pr_6419.prdoc @@ -0,0 +1,12 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Use the custom target riscv32emac-unknown-none-polkavm +doc: + - audience: Runtime Dev + description: | + Closes: https://github.com/paritytech/polkadot-sdk/issues/6335 + +crates: +- name: substrate-wasm-builder + bump: patch diff --git a/prdoc/pr_6446.prdoc b/prdoc/pr_6446.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3bfe7d0c7a60b7132caf5555baf8f2c018539e6d --- /dev/null +++ b/prdoc/pr_6446.prdoc @@ -0,0 +1,16 @@ +title: Make pallet-recovery supports `BlockNumberProvider` +doc: +- audience: Runtime Dev + description: |- + pallet-recovery now allows configuring the block provider to be utilized within this pallet. This block is employed for the delay in the recovery process. + + A new associated type has been introduced in the `Config` trait: `BlockNumberProvider`. This can be assigned to `System` to maintain the previous behavior, or it can be set to another block number provider, such as `RelayChain`. + + If the block provider is configured with a value different from `System`, a migration will be necessary for the `Recoverable` and `ActiveRecoveries` storage items. +crates: +- name: rococo-runtime + bump: major +- name: westend-runtime + bump: major +- name: pallet-recovery + bump: major diff --git a/prdoc/pr_6450.prdoc b/prdoc/pr_6450.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a9e927e451068d6f9a6bfac49140ea5ab0166def --- /dev/null +++ b/prdoc/pr_6450.prdoc @@ -0,0 +1,21 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Add omni-node checks for runtime parachain compatibility + +doc: + - audience: [ Node Dev, Runtime Dev ] + description: | + OmniNode parses runtime metadata and checks against the existence of `cumulus-pallet-parachain-system` + and `frame-system`, by filtering pallets by names: `ParachainSystem` and `System`. It also checks the + `frame-system` pallet storage `Number` type, and then uses it to configure AURA if `u32` or `u64`. + +crates: + - name: polkadot-omni-node-lib + bump: minor + - name: polkadot-sdk + bump: minor + - name: sc-runtime-utilities + bump: patch + - name: frame-benchmarking-cli + bump: major diff --git a/prdoc/pr_6452.prdoc b/prdoc/pr_6452.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f2cb69875e958d356586aa3b9adb27a5efbcd5c5 --- /dev/null +++ b/prdoc/pr_6452.prdoc @@ -0,0 +1,16 @@ +title: "elastic scaling RFC 103 end-to-end tests" + +doc: + - audience: [Node Dev, Runtime Dev] + description: | + Adds end-to-end zombienet-sdk tests for elastic scaling using the RFC103 implementation. + Only notable user-facing change is that the default chain configurations of westend and rococo + now enable by default the CandidateReceiptV2 node feature. + +crates: + - name: westend-runtime + bump: patch + - name: rococo-runtime + bump: patch + - name: rococo-parachain-runtime + bump: patch diff --git a/prdoc/pr_6459.prdoc b/prdoc/pr_6459.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..592ba4c6b29d0863405f3d5547ab2ce1040e3dd6 --- /dev/null +++ b/prdoc/pr_6459.prdoc @@ -0,0 +1,22 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Fix version conversion in XcmPaymentApi::query_weight_to_asset_fee. + +doc: + - audience: Runtime Dev + description: | + The `query_weight_to_asset_fee` function of the `XcmPaymentApi` was trying + to convert versions in the wrong way. + This resulted in all calls made with lower versions failing. + The version conversion is now done correctly and these same calls will now succeed. + +crates: + - name: asset-hub-westend-runtime + bump: patch + - name: asset-hub-rococo-runtime + bump: patch + - name: xcm-runtime-apis + bump: patch + - name: assets-common + bump: patch diff --git a/prdoc/pr_6481.prdoc b/prdoc/pr_6481.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..83ba0a32eb24b513c4c640499d79af921f9ec5d0 --- /dev/null +++ b/prdoc/pr_6481.prdoc @@ -0,0 +1,10 @@ +title: 'slot-based-collator: Implement dedicated block import' +doc: +- audience: Node Dev + description: |- + The `SlotBasedBlockImport` job is to collect the storage proofs of all blocks getting imported. These storage proofs alongside the block are being forwarded to the collation task. Right now they are just being thrown away. More logic will follow later. Basically this will be required to include multiple blocks into one `PoV` which will then be done by the collation task. +crates: +- name: cumulus-client-consensus-aura + bump: major +- name: polkadot-omni-node-lib + bump: major diff --git a/prdoc/pr_6503.prdoc b/prdoc/pr_6503.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..dc296a93f0eb2427abe196742463e9612ce40a10 --- /dev/null +++ b/prdoc/pr_6503.prdoc @@ -0,0 +1,10 @@ +title: "xcm: minor fix for compatibility with V4" + +doc: + - audience: ["Runtime Dev", "Runtime User"] + description: | + Following the removal of `Rococo`, `Westend` and `Wococo` from `NetworkId`, fixed `xcm::v5::NetworkId` encoding/decoding to be compatible with `xcm::v4::NetworkId` + +crates: +- name: staging-xcm + bump: patch diff --git a/prdoc/pr_6506.prdoc b/prdoc/pr_6506.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..7c6164a9959acb702b6b7d8851fb620dd69df7e1 --- /dev/null +++ b/prdoc/pr_6506.prdoc @@ -0,0 +1,10 @@ +title: Zero refund check for FungibleAdapter +doc: +- audience: Runtime User + description: |- + `FungibleAdapter` will now check if the _refund amount_ is zero before calling deposit & emitting an event. + + Fixes https://github.com/paritytech/polkadot-sdk/issues/6469. +crates: +- name: pallet-transaction-payment + bump: patch diff --git a/prdoc/pr_6533.prdoc b/prdoc/pr_6533.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..eb72a97db0f8129ae1599f396413da5af5e34b64 --- /dev/null +++ b/prdoc/pr_6533.prdoc @@ -0,0 +1,20 @@ +title: "Migrate executor into PolkaVM 0.18.0" +doc: + - audience: Runtime Dev + description: | + Bump `polkavm` to 0.18.0, and update `sc-polkavm-executor` to be + compatible with the API changes. In addition, bump also `polkavm-derive` + and `polkavm-linker` in order to make sure that the all parts of the + Polkadot SDK use the exact same ABI for `.polkavm` binaries. + + Purely relying on RV32E/RV64E ABI is not possible, as PolkaVM uses a + RISCV-V alike ISA, which is derived from RV32E/RV64E but it is still its + own microarchitecture, i.e. not fully binary compatible. + +crates: + - name: sc-executor-common + bump: major + - name: sc-executor-polkavm + bump: minor + - name: substrate-wasm-builder + bump: minor diff --git a/prdoc/pr_6549.prdoc b/prdoc/pr_6549.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..61a64c72418576b6e35b7ae235e2690fa28b8ccf --- /dev/null +++ b/prdoc/pr_6549.prdoc @@ -0,0 +1,247 @@ +doc: [] + +crates: + - name: polkadot-sdk + bump: none + - name: asset-test-utils + bump: none + - name: cumulus-pallet-parachain-system + bump: none + - name: cumulus-pallet-parachain-system-proc-macro + bump: none + - name: cumulus-primitives-core + bump: none + - name: polkadot-core-primitives + bump: none + - name: polkadot-parachain-primitives + bump: none + - name: polkadot-primitives + bump: none + - name: staging-xcm + bump: none + - name: xcm-procedural + bump: none + - name: cumulus-primitives-parachain-inherent + bump: none + - name: cumulus-primitives-proof-size-hostfunction + bump: none + - name: polkadot-runtime-common + bump: none + - name: polkadot-runtime-parachains + bump: none + - name: polkadot-runtime-metrics + bump: none + - name: staging-xcm-executor + bump: none + - name: slot-range-helper + bump: none + - name: staging-xcm-builder + bump: none + - name: pallet-xcm + bump: none + - name: cumulus-primitives-storage-weight-reclaim + bump: none + - name: cumulus-pallet-aura-ext + bump: none + - name: cumulus-primitives-aura + bump: none + - name: staging-parachain-info + bump: none + - name: cumulus-test-relay-sproof-builder + bump: none + - name: cumulus-client-cli + bump: none + - name: cumulus-client-collator + bump: none + - name: cumulus-client-consensus-common + bump: none + - name: cumulus-client-pov-recovery + bump: none + - name: cumulus-relay-chain-interface + bump: none + - name: polkadot-overseer + bump: none + - name: tracing-gum + bump: none + - name: tracing-gum-proc-macro + bump: none + - name: polkadot-node-metrics + bump: none + - name: polkadot-node-primitives + bump: none + - name: polkadot-erasure-coding + bump: none + - name: polkadot-node-subsystem + bump: none + - name: polkadot-node-subsystem-types + bump: none + - name: polkadot-node-network-protocol + bump: none + - name: polkadot-statement-table + bump: none + - name: polkadot-rpc + bump: none + - name: polkadot-service + bump: none + - name: cumulus-client-parachain-inherent + bump: none + - name: westend-runtime + bump: none + - name: pallet-xcm-benchmarks + bump: none + - name: westend-runtime-constants + bump: none + - name: polkadot-approval-distribution + bump: none + - name: polkadot-node-subsystem-util + bump: none + - name: polkadot-availability-bitfield-distribution + bump: none + - name: polkadot-availability-distribution + bump: none + - name: polkadot-availability-recovery + bump: none + - name: polkadot-node-core-approval-voting + bump: none + - name: polkadot-node-core-approval-voting-parallel + bump: none + - name: polkadot-node-core-av-store + bump: none + - name: polkadot-node-core-chain-api + bump: none + - name: polkadot-statement-distribution + bump: none + - name: polkadot-collator-protocol + bump: none + - name: polkadot-dispute-distribution + bump: none + - name: polkadot-gossip-support + bump: none + - name: polkadot-network-bridge + bump: none + - name: polkadot-node-collation-generation + bump: none + - name: polkadot-node-core-backing + bump: none + - name: polkadot-node-core-bitfield-signing + bump: none + - name: polkadot-node-core-candidate-validation + bump: none + - name: polkadot-node-core-pvf + bump: none + - name: polkadot-node-core-pvf-common + bump: none + - name: polkadot-node-core-pvf-execute-worker + bump: none + - name: polkadot-node-core-pvf-prepare-worker + bump: none + - name: staging-tracking-allocator + bump: none + - name: rococo-runtime + bump: none + - name: rococo-runtime-constants + bump: none + - name: polkadot-node-core-chain-selection + bump: none + - name: polkadot-node-core-dispute-coordinator + bump: none + - name: polkadot-node-core-parachains-inherent + bump: none + - name: polkadot-node-core-prospective-parachains + bump: none + - name: polkadot-node-core-provisioner + bump: none + - name: polkadot-node-core-pvf-checker + bump: none + - name: polkadot-node-core-runtime-api + bump: none + - name: cumulus-client-network + bump: none + - name: cumulus-relay-chain-inprocess-interface + bump: none + - name: polkadot-cli + bump: none + - name: cumulus-client-consensus-aura + bump: none + - name: cumulus-client-consensus-proposer + bump: none + - name: cumulus-client-consensus-relay-chain + bump: none + - name: cumulus-client-service + bump: none + - name: cumulus-relay-chain-minimal-node + bump: none + - name: cumulus-relay-chain-rpc-interface + bump: none + - name: parachains-common + bump: none + - name: cumulus-primitives-utility + bump: none + - name: cumulus-pallet-xcmp-queue + bump: none + - name: parachains-runtimes-test-utils + bump: none + - name: assets-common + bump: none + - name: bridge-hub-common + bump: none + - name: bridge-hub-test-utils + bump: none + - name: cumulus-pallet-solo-to-para + bump: none + - name: cumulus-pallet-xcm + bump: none + - name: cumulus-ping + bump: none + - name: cumulus-primitives-timestamp + bump: none + - name: emulated-integration-tests-common + bump: none + - name: xcm-emulator + bump: none + - name: pallet-collective-content + bump: none + - name: xcm-simulator + bump: none + - name: pallet-revive-fixtures + bump: none + - name: polkadot-omni-node-lib + bump: none + - name: snowbridge-runtime-test-common + bump: none + - name: testnet-parachains-constants + bump: none + - name: asset-hub-rococo-runtime + bump: none + - name: asset-hub-westend-runtime + bump: none + - name: bridge-hub-rococo-runtime + bump: none + - name: bridge-hub-westend-runtime + bump: none + - name: collectives-westend-runtime + bump: none + - name: coretime-rococo-runtime + bump: none + - name: coretime-westend-runtime + bump: none + - name: people-rococo-runtime + bump: none + - name: people-westend-runtime + bump: none + - name: contracts-rococo-runtime + bump: none + - name: glutton-westend-runtime + bump: none + - name: rococo-parachain-runtime + bump: none + - name: polkadot-omni-node + bump: none + - name: polkadot-parachain-bin + bump: none + - name: polkadot + bump: none + - name: polkadot-voter-bags + bump: none + - name: xcm-simulator-example + bump: none diff --git a/prdoc/pr_6562.prdoc b/prdoc/pr_6562.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..250b656aefb56da13d0500cb51f8b72e517f5c10 --- /dev/null +++ b/prdoc/pr_6562.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: Hide nonce implementation details in metadata + +doc: + - audience: Runtime Dev + description: | + Use custom implementation of TypeInfo for TypeWithDefault to show inner value's type info. + This should bring back nonce to u64 in metadata. + +crates: +- name: sp-runtime + bump: minor \ No newline at end of file diff --git a/prdoc/pr_6565.prdoc b/prdoc/pr_6565.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f9a75a16a6a7f038730a48f44809c398682958ab --- /dev/null +++ b/prdoc/pr_6565.prdoc @@ -0,0 +1,35 @@ +title: 'pallet_revive: Switch to 64bit RISC-V' +doc: +- audience: Runtime Dev + description: |- + This PR updates pallet_revive to the newest PolkaVM version and adapts the test fixtures and syscall interface to work under 64bit. + + Please note that after this PR no 32bit contracts can be deployed (they will be rejected at deploy time). Pre-deployed 32bit contracts are now considered defunct since we changes how parameters are passed for functions with more than 6 arguments. + + ## Fixtures + + The fixtures are now built for the 64bit target. I also removed the temporary directory mechanism that triggered a full rebuild every time. It also makes it easier to find the compiled fixtures since they are now always in `target/pallet-revive-fixtures`. + + ## Syscall interface + + ### Passing pointer + + Registers and pointers are now 64bit wide. This allows us to pass u64 arguments in a single register. Before we needed two registers to pass them. This means that just as before we need one register per pointer we pass. We keep pointers as `u32` argument by truncating the register. This is done since the memory space of PolkaVM is 32bit. + + ### Functions with more than 6 arguments + + We only have 6 registers to pass arguments. This is why we pass a pointer to a struct when we need more than 6. Before this PR we expected a packed struct and interpreted it as SCALE encoded tuple. However, this was buggy because the `MaxEncodedLen` returned something that was larger than the packed size of the structure. This wasn't a problem before. But now the memory space changed in a way that things were placed at the edges of the memory space and those extra bytes lead to an out of bound access. + + This is why this PR drops SCALE and expects the arguments to be passed as a pointer to a `C` aligned struct. This avoids unaligned accesses. However, revive needs to adapt its codegen to properly align the structure fields. + + ## TODO + - [ ] Add multi block migration that wipes all existing contracts as we made breaking changes to the syscall interface +crates: +- name: pallet-revive + bump: major +- name: pallet-revive-fixtures + bump: major +- name: pallet-revive-proc-macro + bump: major +- name: pallet-revive-uapi + bump: major diff --git a/prdoc/pr_6583.prdoc b/prdoc/pr_6583.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..0e67ed33e27cb24a35bf0e7ae6b03ffd1146f84e --- /dev/null +++ b/prdoc/pr_6583.prdoc @@ -0,0 +1,7 @@ +title: Bump Westend AH +doc: +- audience: Runtime Dev + description: Bump Asset-Hub westend spec version +crates: +- name: asset-hub-westend-runtime + bump: minor diff --git a/prdoc/pr_6604.prdoc b/prdoc/pr_6604.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..dc198287ff679ac7b03d2239eae1f4dd80f71c9e --- /dev/null +++ b/prdoc/pr_6604.prdoc @@ -0,0 +1,106 @@ +title: 'dmp: Check that the para exist before delivering a message' +doc: +- audience: Runtime Dev + description: | + Ensure that a para exists before trying to deliver a message to it. + Besides that `ensure_successful_delivery` function is added to `SendXcm`. This function + should be used by benchmarks to ensure that the delivery of a Xcm will work in the benchmark. +crates: +- name: polkadot-runtime-parachains + bump: major +- name: polkadot-runtime-common + bump: major +- name: polkadot-parachain-primitives + bump: major +- name: rococo-runtime + bump: major +- name: westend-runtime + bump: major +- name: pallet-xcm-benchmarks + bump: major +- name: pallet-xcm + bump: major +- name: cumulus-pallet-parachain-system + bump: major +- name: staging-xcm + bump: major +- name: staging-xcm-builder + bump: major +- name: bridge-runtime-common + bump: major +- name: pallet-xcm-bridge-hub-router + bump: major +- name: pallet-xcm-bridge-hub + bump: major +- name: snowbridge-pallet-inbound-queue + bump: major +- name: snowbridge-pallet-system + bump: major +- name: snowbridge-core + bump: major +- name: snowbridge-router-primitives + bump: major +- name: snowbridge-runtime-common + bump: major +- name: snowbridge-runtime-test-common + bump: major +- name: cumulus-pallet-dmp-queue + bump: major +- name: cumulus-pallet-xcmp-queue + bump: major +- name: parachains-common + bump: major +- name: asset-hub-rococo-runtime + bump: major +- name: asset-hub-westend-runtime + bump: major +- name: assets-common + bump: major +- name: bridge-hub-rococo-runtime + bump: major +- name: bridge-hub-westend-runtime + bump: major +- name: bridge-hub-common + bump: major +- name: collectives-westend-runtime + bump: major +- name: contracts-rococo-runtime + bump: major +- name: coretime-rococo-runtime + bump: major +- name: coretime-westend-runtime + bump: major +- name: glutton-westend-runtime + bump: major +- name: people-rococo-runtime + bump: major +- name: people-westend-runtime + bump: major +- name: penpal-runtime + bump: major +- name: rococo-parachain-runtime + bump: major +- name: polkadot-parachain-bin + bump: major +- name: cumulus-primitives-core + bump: major +- name: cumulus-primitives-utility + bump: major +- name: polkadot-service + bump: major +- name: staging-xcm-executor + bump: major +- name: xcm-runtime-apis + bump: major +- name: xcm-simulator-example + bump: major +- name: pallet-contracts + bump: major +- name: pallet-contracts-mock-network + bump: major +- name: pallet-revive + bump: major +- name: pallet-revive-mock-network + bump: major +- name: polkadot-sdk + bump: major diff --git a/prdoc/pr_6605.prdoc b/prdoc/pr_6605.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..2adb1d8aee359877e8a1acfa91d68ef43e2121e5 --- /dev/null +++ b/prdoc/pr_6605.prdoc @@ -0,0 +1,10 @@ +title: Notify telemetry only every second about the tx pool status +doc: +- audience: Node Operator + description: |- + Before this was done for every imported transaction. When a lot of transactions got imported, the import notification channel was filled. The underlying problem was that the `status` call is read locking the `validated_pool` which will be write locked by the internal submitting logic. Thus, the submitting and status reading was interferring which each other. +crates: +- name: cumulus-client-service + bump: patch +- name: sc-service + bump: patch diff --git a/prdoc/pr_6608.prdoc b/prdoc/pr_6608.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b9cd7008de47bcf2294c446183e91514d45ef805 --- /dev/null +++ b/prdoc/pr_6608.prdoc @@ -0,0 +1,14 @@ +title: '[pallet-revive] eth-prc fix geth diff' +doc: +- audience: Runtime Dev + description: |- + * Add a bunch of differential tests to ensure that responses from eth-rpc matches the one from `geth` + * EVM RPC server will not fail gas_estimation if no gas is specified, I updated pallet-revive to add an extra `skip_transfer` boolean check to replicate this behavior in our pallet + * `eth_transact` and `bare_eth_transact` api have been updated to use `GenericTransaction` directly as this is what is used by `eth_estimateGas` and `eth_call` +crates: +- name: pallet-revive-eth-rpc + bump: minor +- name: pallet-revive + bump: minor +- name: asset-hub-westend-runtime + bump: minor diff --git a/prdoc/pr_6624.prdoc b/prdoc/pr_6624.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..4db55a46e8dfd3d2aa03117680041f9704642993 --- /dev/null +++ b/prdoc/pr_6624.prdoc @@ -0,0 +1,11 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Use `cmd_lib` instead of `std::process::Command` when using `#[docify::export]` + +doc: + - audience: Runtime Dev + description: | + Simplified the display of commands and ensured they are tested for chain spec builder's `polkadot-sdk` reference docs. + +crates: [] \ No newline at end of file diff --git a/prdoc/pr_6628.prdoc b/prdoc/pr_6628.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..7ea0c4968385642328be5a285fad89caec085bd6 --- /dev/null +++ b/prdoc/pr_6628.prdoc @@ -0,0 +1,12 @@ +title: "Remove ReportCollator message" + +doc: + - audience: Node Dev + description: | + Remove unused message ReportCollator and test related to this message on the collator protocol validator side. + +crates: + - name: polkadot-node-subsystem-types + bump: patch + - name: polkadot-collator-protocol + bump: major \ No newline at end of file diff --git a/prdoc/pr_6636.prdoc b/prdoc/pr_6636.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1db5fd54d97168675802b570685a0c92610ccb8c --- /dev/null +++ b/prdoc/pr_6636.prdoc @@ -0,0 +1,9 @@ +title: Optimize initialization of networking protocol benchmarks +doc: +- audience: Node Dev + description: |- + These changes should enhance the quality of benchmark results by excluding worker initialization time from the measurements and reducing the overall duration of the benchmarks. + +crates: +- name: sc-network + validate: false diff --git a/prdoc/pr_6647.prdoc b/prdoc/pr_6647.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..47af9924ef1c077218ebf5b90d17a6b647d96632 --- /dev/null +++ b/prdoc/pr_6647.prdoc @@ -0,0 +1,8 @@ +title: '`fatxpool`: proper handling of priorities when mempool is full' +doc: +- audience: Node Dev + description: |- + Higher-priority transactions can now replace lower-priority transactions even when the internal _tx_mem_pool_ is full. +crates: +- name: sc-transaction-pool + bump: minor diff --git a/prdoc/pr_6665.prdoc b/prdoc/pr_6665.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b5aaf8a3b18433efaff7d1b8a08219af46b96af0 --- /dev/null +++ b/prdoc/pr_6665.prdoc @@ -0,0 +1,15 @@ +title: Fix runtime api impl detection by construct runtime +doc: +- audience: Runtime Dev + description: |- + Construct runtime uses autoref-based specialization to fetch the metadata about the implemented runtime apis. This is done to not fail to compile when there are no runtime apis implemented. However, there was an issue with detecting runtime apis when they were implemented in a different file. The problem is solved by moving the trait implemented by `impl_runtime_apis!` to the metadata ir crate. + + + Closes: https://github.com/paritytech/polkadot-sdk/issues/6659 +crates: +- name: frame-support-procedural + bump: patch +- name: sp-api-proc-macro + bump: patch +- name: sp-metadata-ir + bump: patch diff --git a/prdoc/pr_6673.prdoc b/prdoc/pr_6673.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d2ca3c61ff394d5c12068a4d1f3c88eef0407690 --- /dev/null +++ b/prdoc/pr_6673.prdoc @@ -0,0 +1,7 @@ +title: 'chain-spec-guide-runtime: path to wasm blob fixed' +doc: +- audience: Runtime Dev + description: In `chain-spec-guide-runtime` crate's tests, there was assumption that + release version of wasm blob exists. This PR uses `chain_spec_guide_runtime::runtime::WASM_BINARY_PATH` + const to use correct path to runtime blob. +crates: [] diff --git a/prdoc/pr_6681.prdoc b/prdoc/pr_6681.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..93a967d4a66cb0e3a3cb8187c94e10d19332b56e --- /dev/null +++ b/prdoc/pr_6681.prdoc @@ -0,0 +1,406 @@ +# 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 scale-info to 2.11.6 + +doc: + - audience: Runtime Dev + description: | + Updates scale-info to 2.11.1 from 2.11.5. + Updated version of scale-info annotates generated code with `allow(deprecated)` + +crates: + - name: bridge-runtime-common + bump: none + - name: bp-header-chain + bump: none + - name: bp-runtime + bump: none + - name: frame-support + bump: none + - name: sp-core + bump: none + - name: sp-trie + bump: none + - name: sp-runtime + bump: none + - name: sp-application-crypto + bump: none + - name: sp-arithmetic + bump: none + - name: sp-weights + bump: none + - name: sp-api + bump: none + - name: sp-metadata-ir + bump: none + - name: sp-version + bump: none + - name: sp-inherents + bump: none + - name: frame-executive + bump: none + - name: frame-system + bump: none + - name: pallet-balances + bump: none + - name: frame-benchmarking + bump: none + - name: pallet-migrations + bump: none + - name: cumulus-pallet-parachain-system + bump: none + - name: cumulus-primitives-core + bump: none + - name: polkadot-core-primitives + bump: none + - name: polkadot-parachain-primitives + bump: none + - name: polkadot-primitives + bump: none + - name: sp-authority-discovery + bump: none + - name: sp-consensus-slots + bump: none + - name: sp-staking + bump: none + - name: staging-xcm + bump: none + - name: cumulus-primitives-parachain-inherent + bump: none + - name: pallet-message-queue + bump: none + - name: polkadot-runtime-common + bump: none + - name: frame-election-provider-support + bump: none + - name: sp-npos-elections + bump: none + - name: sp-consensus-grandpa + bump: none + - name: polkadot-primitives + bump: none + - name: sp-authority-discovery + bump: none + - name: sp-consensus-grandpa + bump: none + - name: sp-genesis-builder + bump: none + - name: sp-consensus-babe + bump: none + - name: sp-mixnet + bump: none + - name: sc-rpc-api + bump: none + - name: sp-session + bump: none + - name: sp-statement-store + bump: none + - name: sp-transaction-storage-proof + bump: none + - name: pallet-asset-rate + bump: none + - name: pallet-authorship + bump: none + - name: pallet-babe + bump: none + - name: pallet-session + bump: none + - name: pallet-timestamp + bump: none + - name: pallet-offences + bump: none + - name: pallet-staking + bump: none + - name: pallet-bags-list + bump: none + - name: pallet-broker + bump: none + - name: pallet-election-provider-multi-phase + bump: none + - name: pallet-fast-unstake + bump: none + - name: pallet-identity + bump: none + - name: pallet-transaction-payment + bump: none + - name: pallet-treasury + bump: none + - name: pallet-utility + bump: none + - name: pallet-collective + bump: none + - name: pallet-root-testing + bump: none + - name: pallet-vesting + bump: none + - name: polkadot-runtime-parachains + bump: none + - name: pallet-authority-discovery + bump: none + - name: pallet-mmr + bump: none + - name: sp-mmr-primitives + bump: none + - name: staging-xcm-executor + bump: none + - name: staging-xcm-builder + bump: none + - name: pallet-asset-conversion + bump: none + - name: pallet-assets + bump: none + - name: pallet-salary + bump: none + - name: pallet-ranked-collective + bump: none + - name: pallet-xcm + bump: none + - name: xcm-runtime-apis + bump: none + - name: pallet-grandpa + bump: none + - name: pallet-indices + bump: none + - name: pallet-sudo + bump: none + - name: sp-consensus-beefy + bump: none + - name: cumulus-primitives-storage-weight-reclaim + bump: none + - name: cumulus-pallet-aura-ext + bump: none + - name: pallet-aura + bump: none + - name: sp-consensus-aura + bump: none + - name: pallet-collator-selection + bump: none + - name: pallet-glutton + bump: none + - name: staging-parachain-info + bump: none + - name: westend-runtime + bump: none + - name: frame-metadata-hash-extension + bump: none + - name: frame-system-benchmarking + bump: none + - name: pallet-beefy + bump: none + - name: pallet-beefy-mmr + bump: none + - name: pallet-conviction-voting + bump: none + - name: pallet-scheduler + bump: none + - name: pallet-preimage + bump: none + - name: pallet-delegated-staking + bump: none + - name: pallet-nomination-pools + bump: none + - name: pallet-democracy + bump: none + - name: pallet-elections-phragmen + bump: none + - name: pallet-membership + bump: none + - name: pallet-multisig + bump: none + - name: polkadot-sdk-frame + bump: none + - name: pallet-dev-mode + bump: none + - name: pallet-verify-signature + bump: none + - name: pallet-nomination-pools-benchmarking + bump: none + - name: pallet-offences-benchmarking + bump: none + - name: pallet-im-online + bump: none + - name: pallet-parameters + bump: none + - name: pallet-proxy + bump: none + - name: pallet-recovery + bump: none + - name: pallet-referenda + bump: none + - name: pallet-society + bump: none + - name: pallet-state-trie-migration + bump: none + - name: pallet-whitelist + bump: none + - name: pallet-xcm-benchmarks + bump: none + - name: rococo-runtime + bump: none + - name: pallet-bounties + bump: none + - name: pallet-child-bounties + bump: none + - name: pallet-nis + bump: none + - name: pallet-tips + bump: none + - name: parachains-common + bump: none + - name: pallet-asset-tx-payment + bump: none + - name: cumulus-pallet-xcmp-queue + bump: none + - name: bp-xcm-bridge-hub-router + bump: none + - name: pallet-xcm-bridge-hub-router + bump: none + - name: assets-common + bump: none + - name: bp-messages + bump: none + - name: bp-parachains + bump: none + - name: bp-polkadot-core + bump: none + - name: bp-relayers + bump: none + - name: bp-xcm-bridge-hub + bump: none + - name: bridge-hub-common + bump: none + - name: snowbridge-core + bump: none + - name: snowbridge-beacon-primitives + bump: none + - name: snowbridge-ethereum + bump: none + - name: pallet-bridge-grandpa + bump: none + - name: pallet-bridge-messages + bump: none + - name: pallet-bridge-parachains + bump: none + - name: pallet-bridge-relayers + bump: none + - name: pallet-xcm-bridge-hub + bump: none + - name: cumulus-pallet-dmp-queue + bump: none + - name: cumulus-pallet-solo-to-para + bump: none + - name: cumulus-pallet-xcm + bump: none + - name: cumulus-ping + bump: none + - name: frame-benchmarking-pallet-pov + bump: none + - name: pallet-alliance + bump: none + - name: pallet-asset-conversion-ops + bump: none + - name: pallet-asset-conversion-tx-payment + bump: none + - name: pallet-assets-freezer + bump: none + - name: pallet-atomic-swap + bump: none + - name: pallet-collective-content + bump: none + - name: pallet-contracts + bump: none + - name: pallet-contracts-uapi + bump: none + - name: pallet-insecure-randomness-collective-flip + bump: none + - name: pallet-contracts-mock-network + bump: none + - name: xcm-simulator + bump: none + - name: pallet-core-fellowship + bump: none + - name: pallet-lottery + bump: none + - name: pallet-mixnet + bump: none + - name: pallet-nft-fractionalization + bump: none + - name: pallet-nfts + bump: none + - name: pallet-node-authorization + bump: none + - name: pallet-paged-list + bump: none + - name: pallet-remark + bump: none + - name: pallet-revive + bump: none + - name: pallet-revive-uapi + bump: none + - name: pallet-revive-eth-rpc + bump: none + - name: pallet-skip-feeless-payment + bump: none + - name: pallet-revive-mock-network + bump: none + - name: pallet-root-offences + bump: none + - name: pallet-safe-mode + bump: none + - name: pallet-scored-pool + bump: none + - name: pallet-statement + bump: none + - name: pallet-transaction-storage + bump: none + - name: pallet-tx-pause + bump: none + - name: pallet-uniques + bump: none + - name: snowbridge-outbound-queue-merkle-tree + bump: none + - name: snowbridge-pallet-ethereum-client + bump: none + - name: snowbridge-pallet-inbound-queue + bump: none + - name: snowbridge-router-primitives + bump: none + - name: snowbridge-pallet-outbound-queue + bump: none + - name: snowbridge-pallet-system + bump: none + - name: bp-asset-hub-rococo + bump: none + - name: bp-asset-hub-westend + bump: none + - name: bp-polkadot-bulletin + bump: none + - name: asset-hub-rococo-runtime + bump: none + - name: asset-hub-westend-runtime + bump: none + - name: bridge-hub-rococo-runtime + bump: none + - name: bridge-hub-westend-runtime + bump: none + - name: collectives-westend-runtime + bump: none + - name: coretime-rococo-runtime + bump: none + - name: coretime-westend-runtime + bump: none + - name: people-rococo-runtime + bump: none + - name: people-westend-runtime + bump: none + - name: penpal-runtime + bump: none + - name: contracts-rococo-runtime + bump: none + - name: glutton-westend-runtime + bump: none + - name: rococo-parachain-runtime + bump: none + - name: xcm-simulator-example + bump: none \ No newline at end of file diff --git a/prdoc/pr_6689.prdoc b/prdoc/pr_6689.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..2cbb49cd7dd2457ab256a1325ca88c4f0830e57e --- /dev/null +++ b/prdoc/pr_6689.prdoc @@ -0,0 +1,19 @@ +title: '[pallet-revive] Update gas encoding' +doc: +- audience: Runtime Dev + description: |- + Update the current approach to attach the `ref_time`, `pov` and `deposit` parameters to an Ethereum transaction. +Previously, these three parameters were passed along with the signed payload, and the fees resulting from gas × gas_price were checked to ensure they matched the actual fees paid by the user for the extrinsic + + This approach unfortunately can be attacked. A malicious actor could force such a transaction to fail by injecting low values for some of these extra parameters as they are not part of the signed payload. + + The new approach encodes these 3 extra parameters in the lower digits of the transaction gas, using the log2 of the actual values to encode each components on 2 digits +crates: +- name: pallet-revive-eth-rpc + bump: minor +- name: pallet-revive + bump: minor +- name: asset-hub-westend-runtime + bump: minor +- name: pallet-revive-mock-network + bump: minor diff --git a/prdoc/pr_6695.prdoc b/prdoc/pr_6695.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..7a950e8546cd442f9364c729719ad82d49ee9568 --- /dev/null +++ b/prdoc/pr_6695.prdoc @@ -0,0 +1,8 @@ +title: '[pallet-revive] bugfix decoding 64bit args in the decoder' +doc: +- audience: Runtime Dev + description: The argument index of the next argument is dictated by the size of + the current one. +crates: +- name: pallet-revive-proc-macro + bump: patch diff --git a/prdoc/pr_6703.prdoc b/prdoc/pr_6703.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..2dd0962a3eea3c2994b105e33a0168dcce7de201 --- /dev/null +++ b/prdoc/pr_6703.prdoc @@ -0,0 +1,7 @@ +title: 'network/libp2p-backend: Suppress warning adding already reserved node as reserved' +doc: +- audience: Node Dev + description: Fixes https://github.com/paritytech/polkadot-sdk/issues/6598. +crates: +- name: sc-network + bump: patch diff --git a/prdoc/pr_6711.prdoc b/prdoc/pr_6711.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ec09035e1356cf8f8adb7c2ed09e42cee12ee51a --- /dev/null +++ b/prdoc/pr_6711.prdoc @@ -0,0 +1,13 @@ +title: Expose DHT content providers API from `sc-network` +doc: +- audience: Node Dev + description: |- + Expose the Kademlia content providers API for the use by `sc-network` client code: + 1. Extend the `NetworkDHTProvider` trait with functions to start/stop providing content and query the DHT for the list of content providers for a given key. + 2. Extend the `DhtEvent` enum with events reporting the found providers or query failures. + 3. Implement the above for libp2p & litep2p network backends. +crates: +- name: sc-network + bump: major +- name: sc-authority-discovery + bump: major diff --git a/prdoc/pr_6728.prdoc b/prdoc/pr_6728.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..68f61190d94739770ee9717bd5fed63cb21b2280 --- /dev/null +++ b/prdoc/pr_6728.prdoc @@ -0,0 +1,12 @@ +title: '[pallet-revive] eth-rpc add missing tests' +doc: +- audience: Runtime Dev + description: |- + Add tests for #6608 + + fix https://github.com/paritytech/contract-issues/issues/12 +crates: +- name: pallet-revive-eth-rpc + bump: minor +- name: pallet-revive + bump: minor diff --git a/prdoc/pr_6741.prdoc b/prdoc/pr_6741.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d4b795038bcd997f219967769367036b939c6fa5 --- /dev/null +++ b/prdoc/pr_6741.prdoc @@ -0,0 +1,16 @@ +title: 'pallet-revive: Adjust error handling of sub calls' +doc: +- audience: Runtime Dev + description: |- + We were trapping the host context in case a sub call was exhausting the storage deposit limit set for this sub call. This prevents the caller from handling this error. In this PR we added a new error code that is returned when either gas or storage deposit limit is exhausted by the sub call. + + We also remove the longer used `NotCallable` error. No longer used because this is no longer an error: It will just be a balance transfer. + + We also make `set_code_hash` infallible to be consistent with other host functions which just trap on any error condition. +crates: +- name: pallet-revive + bump: major +- name: pallet-revive-uapi + bump: major +- name: pallet-revive-fixtures + bump: major diff --git a/prdoc/pr_6743.prdoc b/prdoc/pr_6743.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..4c35ff46ca670c23ffe05a7bdd78f35dca451442 --- /dev/null +++ b/prdoc/pr_6743.prdoc @@ -0,0 +1,10 @@ +title: 'umbrella: Remove `pallet-revive-fixtures`' +doc: +- audience: Runtime Dev + description: |- + No need to have them in the umbrella crate also by having them in the umbrella crate they are bleeding into the normal build. +crates: +- name: pallet-revive-fixtures + bump: major +- name: polkadot-sdk + bump: major diff --git a/prdoc/pr_6759.prdoc b/prdoc/pr_6759.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3dff12d740d40991f3ea2d01703b9db8a50377be --- /dev/null +++ b/prdoc/pr_6759.prdoc @@ -0,0 +1,16 @@ +title: 'pallet-revive: Statically verify imports on code deployment' +doc: +- audience: Runtime Dev + description: |- + Previously, we failed at runtime if an unknown or unstable host function was called. This requires us to keep track of when a host function was added and when a code was deployed. We used the `api_version` to track at which API version each code was deployed. This made sure that when a new host function was added that old code won't have access to it. This is necessary as otherwise the behavior of a contract that made calls to this previously non existent host function would change from "trap" to "do something". + + In this PR we remove the API version. Instead, we statically verify on upload that no non-existent host function is ever used in the code. This will allow us to add new host function later without needing to keep track when they were added. + + This simplifies the code and also gives an immediate feedback if unknown host functions are used. +crates: +- name: pallet-revive-proc-macro + bump: major +- name: pallet-revive + bump: major +- name: pallet-revive-fixtures + bump: major diff --git a/prdoc/pr_6768.prdoc b/prdoc/pr_6768.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3e194078df26566e03b69f078286095764702ef1 --- /dev/null +++ b/prdoc/pr_6768.prdoc @@ -0,0 +1,14 @@ +title: '`basic-authorship`: debug level is now less spammy' +doc: +- audience: Node Dev + description: |- + The `debug` level in `sc-basic-authorship` is now less spammy. Previously it was outputing logs per individual transactions. It made quite hard to follow the logs (and also generates unneeded traffic in grafana). + + Now debug level only show some internal details, without spamming output with per-transaction logs. They were moved to `trace` level. + + I also added the `EndProposingReason` to the summary INFO message. This allows us to know what was the block limit (which is very useful for debugging). +crates: +- name: sc-basic-authorship + bump: major +- name: sc-proposer-metrics + bump: major diff --git a/prdoc/pr_6792.prdoc b/prdoc/pr_6792.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..80982a34b3e86b8c7ddca39013f2c296aa0dca67 --- /dev/null +++ b/prdoc/pr_6792.prdoc @@ -0,0 +1,11 @@ +title: Add fallback_max_weight to snowbridge Transact +doc: +- audience: Runtime Dev + description: |- + We removed the `require_weight_at_most` field and later changed it to `fallback_max_weight`. + This was to have a fallback when sending a message to v4 chains, which happens in the small time window when chains are upgrading. + We originally put no fallback for a message in snowbridge's inbound queue but we should have one. + This PR adds it. +crates: +- name: snowbridge-router-primitives + bump: patch diff --git a/prdoc/pr_6796.prdoc b/prdoc/pr_6796.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..aeb305847bf85420451d3e106fd34939c4c6e678 --- /dev/null +++ b/prdoc/pr_6796.prdoc @@ -0,0 +1,9 @@ +title: 'pallet-revive: Remove unused dependencies' +doc: +- audience: Runtime Dev + description: The dependency on `pallet_balances` doesn't seem to be necessary. At + least everything compiles for me without it. Removed this dependency and a few + others that seem to be left overs. +crates: +- name: pallet-revive + bump: major diff --git a/prdoc/pr_6820.prdoc b/prdoc/pr_6820.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..85249a33341dcec7fb08a63b281ce701db40a479 --- /dev/null +++ b/prdoc/pr_6820.prdoc @@ -0,0 +1,8 @@ +title: Add XCM benchmarks to collectives-westend +doc: +- audience: Runtime Dev + description: Collectives-westend was using `FixedWeightBounds`, meaning the same + weight per instruction. Added proper benchmarks. +crates: +- name: collectives-westend-runtime + bump: patch diff --git a/prdoc/pr_6832.prdoc b/prdoc/pr_6832.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..bd0abbfba8538d43eba8549b762dcc628269e307 --- /dev/null +++ b/prdoc/pr_6832.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Remove collation-generation subsystem from validator nodes" + +doc: + - audience: Node Dev + description: | + Collation-generation is only needed for Collators, and therefore not needed for validators + +crates: + - name: polkadot-service + bump: patch \ No newline at end of file diff --git a/prdoc/pr_6835.prdoc b/prdoc/pr_6835.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..73d1a81e761c2d215c7a3f06a91c9a19c99b28d8 --- /dev/null +++ b/prdoc/pr_6835.prdoc @@ -0,0 +1,12 @@ +title: '[pallet-revive] implement the call data load API' +doc: +- audience: Runtime Dev + description: |- + This PR implements the call data load API akin to [how it works on ethereum](https://www.evm.codes/?fork=cancun#35). +crates: +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/prdoc/pr_6836.prdoc b/prdoc/pr_6836.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1de081bbaa400156185747ca2e8082e9e6f0c24d --- /dev/null +++ b/prdoc/pr_6836.prdoc @@ -0,0 +1,17 @@ +title: '[pallet-revive-eth-rpc] persist eth transaction hash' +doc: +- audience: Runtime Dev + description: |- + Add an option to persist EVM transaction hash to a SQL db. + This make it possible to run a full archive ETH RPC node (assuming the substrate node is also a full archive node) + + Some queries such as eth_getTransactionByHash, eth_getBlockTransactionCountByHash, and other need to work with a transaction hash index, which is not available in Substrate and need to be stored by the eth-rpc proxy. + + The refactoring break down the Client into a `BlockInfoProvider` and `ReceiptProvider` + - BlockInfoProvider does not need any persistence data, as we can fetch all block info from the source substrate chain + - ReceiptProvider comes in two flavor, + - An in memory cache implementation - This is the one we had so far. + - A DB implementation - This one persist rows with the block_hash, the transaction_index and the transaction_hash, so that we can later fetch the block and extrinsic for that receipt and reconstruct the ReceiptInfo object. +crates: +- name: pallet-revive-eth-rpc + bump: minor diff --git a/prdoc/pr_6844.prdoc b/prdoc/pr_6844.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..32901bf04df93db24309993dd84d79446eaf869d --- /dev/null +++ b/prdoc/pr_6844.prdoc @@ -0,0 +1,8 @@ +title: 'pallet-revive: disable host functions unused in solidity PolkaVM compiler' +doc: +- audience: Runtime Dev + description: Disables host functions in contracts that are not enabled + in solidity PolkaVM compiler to reduce surface of possible attack vectors. +crates: +- name: pallet-revive + bump: major diff --git a/prdoc/pr_6856.prdoc b/prdoc/pr_6856.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..480c3acea1952c1e7642cca93437e901c8b7359e --- /dev/null +++ b/prdoc/pr_6856.prdoc @@ -0,0 +1,28 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Enable report_fork_voting() + +doc: + - audience: + - Runtime Dev + - Runtime User + description: | + This PR enables calling `report_fork_voting`. + In order to do this we needed to also check that the ancestry proof is optimal. + +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: minor + - name: pallet-beefy-mmr + bump: minor + - name: westend-runtime + bump: minor diff --git a/prdoc/pr_6857.prdoc b/prdoc/pr_6857.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3930f5910487494123abbcb7a42f617789d144b4 --- /dev/null +++ b/prdoc/pr_6857.prdoc @@ -0,0 +1,14 @@ +title: '[pallet-revive] implement the call data size API' +doc: +- audience: Runtime Dev + description: |- + This PR adds an API method to query the contract call data input size. + + Part of #6770 +crates: +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/prdoc/pr_6865.prdoc b/prdoc/pr_6865.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c0581f2af24f6775a8f0b0dae393743dfb5aa601 --- /dev/null +++ b/prdoc/pr_6865.prdoc @@ -0,0 +1,9 @@ +title: Rename PanicInfo to PanicHookInfo +doc: +- audience: Node Dev + description: Starting with Rust 1.82 `PanicInfo` is deprecated and will throw warnings + when used. The new type is available since Rust 1.81 and should be available on + our CI. +crates: +- name: sp-panic-handler + bump: patch diff --git a/prdoc/pr_6866.prdoc b/prdoc/pr_6866.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..fac40dc103d7a56a648d911807d441d9dc856d52 --- /dev/null +++ b/prdoc/pr_6866.prdoc @@ -0,0 +1,13 @@ +title: Refactor `pallet-revive-uapi` pallet +doc: +- audience: Runtime Dev + description: Puts unstable host functions in `uapi` under + `unstable-api` feature while moving those functions after + stable functions. +crates: +- name: pallet-revive + bump: patch +- name: pallet-revive-fixtures + bump: patch +- name: pallet-revive-uapi + bump: major diff --git a/prdoc/pr_6867.prdoc b/prdoc/pr_6867.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..afa35533d463921f5bf81b2dd5695be20ecb8c1b --- /dev/null +++ b/prdoc/pr_6867.prdoc @@ -0,0 +1,30 @@ +title: Deprecate ParaBackingState API +doc: +- audience: [ Runtime Dev, Node Dev ] + description: |- + Deprecates the `para_backing_state` API. Introduces and new `backing_constraints` API that can be used + together with existing `candidates_pending_availability` to retrieve the same information provided by + `para_backing_state`. + +crates: +- name: polkadot-primitives + bump: minor +- name: polkadot-runtime-parachains + bump: minor +- name: rococo-runtime + bump: minor +- name: westend-runtime + bump: minor +- name: cumulus-relay-chain-rpc-interface + bump: minor +- name: polkadot-node-core-prospective-parachains + bump: patch +- name: polkadot-node-core-runtime-api + bump: minor +- name: polkadot-node-subsystem-types + bump: major +- name: polkadot-node-subsystem-util + bump: major +- name: cumulus-relay-chain-minimal-node + bump: minor + diff --git a/prdoc/pr_6880.prdoc b/prdoc/pr_6880.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9d59382f0e0babd0d5ba8095fd2df57ffd448980 --- /dev/null +++ b/prdoc/pr_6880.prdoc @@ -0,0 +1,14 @@ +title: '[pallet-revive] implement the call data copy API' +doc: +- audience: Runtime Dev + description: |- + This PR implements the call data copy API by adjusting the input method. + + Closes #6770 +crates: +- name: pallet-revive-fixtures + bump: major +- name: pallet-revive + bump: major +- name: pallet-revive-uapi + bump: major \ No newline at end of file diff --git a/prdoc/pr_6889.prdoc b/prdoc/pr_6889.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..01edd49b685a13cea4ae2a9ef4449d9ae8441232 --- /dev/null +++ b/prdoc/pr_6889.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Remove polkadot-omni-node-lib unused dependency + +doc: + - audience: Node Dev + description: + Removed an unused dependency for `polkadot-omni-node-lib`. + +crates: + - name: polkadot-omni-node-lib + bump: patch diff --git a/prdoc/pr_6890.prdoc b/prdoc/pr_6890.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b22a339035d81be722b139bd4de1337d4179872d --- /dev/null +++ b/prdoc/pr_6890.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Alter semantic meaning of 0 in metering limits of EVM contract calls + +doc: + - audience: [ Runtime Dev, Runtime User ] + description: | + A limit of 0, for gas meters and storage meters, no longer has the meaning of unlimited metering. + +crates: + - name: pallet-revive + bump: patch + - name: pallet-revive-fixtures + bump: patch + - name: pallet-revive-uapi + bump: patch + - name: pallet-revive-eth-rpc + bump: patch diff --git a/prdoc/pr_6896.prdoc b/prdoc/pr_6896.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a56e4303d9af7cc645b7a6dd7bd2b33d86830f71 --- /dev/null +++ b/prdoc/pr_6896.prdoc @@ -0,0 +1,16 @@ +title: 'pallet-revive: Fix docs.rs' +doc: +- audience: Runtime Dev + description: |- + - Fixed failing docs.rs build for `pallet-revive-uapi` by fixing a writing attribute in the manifest (we were using `default-target` instead of `targets`) + - Removed the macros defining host functions because the cfg attributes introduced in #6866 won't work on them + - Added an docs.rs specific attribute so that the `unstable-hostfn` feature tag will show up on the functions that are guarded behind it. +crates: +- name: pallet-contracts-uapi + bump: major +- name: pallet-revive-uapi + bump: major +- name: pallet-revive-fixtures + bump: major +- name: pallet-revive-proc-macro + bump: major diff --git a/prdoc/pr_6897.prdoc b/prdoc/pr_6897.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..38fd9417f48aba86065a3421656464dc961832b7 --- /dev/null +++ b/prdoc/pr_6897.prdoc @@ -0,0 +1,7 @@ +title: 'Tracing Log for fork-aware transaction pool' +doc: +- audience: Node Dev + description: Replacement of log crate with tracing crate for better logging. +crates: +- name: sc-transaction-pool + bump: minor \ No newline at end of file diff --git a/prdoc/pr_6908.prdoc b/prdoc/pr_6908.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..0be9e613f88ae028dc7845b7637b7ba7b1f2e300 --- /dev/null +++ b/prdoc/pr_6908.prdoc @@ -0,0 +1,12 @@ +title: '[pallet-revive] implement the ref_time_left API' +doc: +- audience: Runtime Dev + description: This PR implements the ref_time_left API method. Solidity knows only + a single "gas" dimension; Solidity contracts will use this to query the gas left. +crates: +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/prdoc/pr_6917.prdoc b/prdoc/pr_6917.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..dd7f59b951268236b3f551d63b92f3d31f1b5a4c --- /dev/null +++ b/prdoc/pr_6917.prdoc @@ -0,0 +1,14 @@ +title: Remove unused dependencies from pallet_revive +doc: +- audience: Runtime Dev + description: Removing apparently unused dependencies from `pallet_revive` and related + crates. +crates: +- name: pallet-revive + bump: major +- name: pallet-revive-fixtures + bump: major +- name: pallet-revive-mock-network + bump: major +- name: pallet-revive-eth-rpc + bump: major diff --git a/prdoc/pr_6920.prdoc b/prdoc/pr_6920.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d80a77e0a71f50c53b36a6e81a73e6d1ba4c1013 --- /dev/null +++ b/prdoc/pr_6920.prdoc @@ -0,0 +1,14 @@ +title: '[pallet-revive] change some getter APIs to return value in register' +doc: +- audience: Runtime Dev + description: Call data, return data and code sizes can never exceed `u32::MAX`; + they are also not generic. Hence we know that they are guaranteed to always fit + into a 64bit register and `revive` can just zero extend them into a 256bit integer + value. Which is slightly more efficient than passing them on the stack. +crates: +- name: pallet-revive-fixtures + bump: major +- name: pallet-revive + bump: major +- name: pallet-revive-uapi + bump: major diff --git a/prdoc/pr_6923.prdoc b/prdoc/pr_6923.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..5d88d7158e7fdf0e28e0d8714620aa6205e895a5 --- /dev/null +++ b/prdoc/pr_6923.prdoc @@ -0,0 +1,12 @@ +title: 'omni-node: Tolerate failing metadata check' +doc: +- audience: Node Operator + description: |- + #6450 introduced metadata checks. Supported are metadata v14 and higher. + + However, of course old chain-specs have a genesis code blob that might be on older version. This needs to be tolerated. We should just skip the checks in that case. + + Fixes #6921 +crates: +- name: polkadot-omni-node-lib + bump: patch diff --git a/prdoc/pr_6924.prdoc b/prdoc/pr_6924.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..dc27bb9adfcba0029d6438c62762134ede3595aa --- /dev/null +++ b/prdoc/pr_6924.prdoc @@ -0,0 +1,19 @@ +title: "malus-collator: implement malicious collator submitting same collation to all backing groups" + +doc: + - audience: Node Dev + description: | + This PR modifies the undying collator to include a malus mode, + enabling it to submit the same collation to all assigned backing groups. + + It also includes a test that spawns a network with the malus collator + and verifies that everything functions correctly. + +crates: + - name: polkadot + bump: none + validate: false + - name: test-parachain-undying + bump: patch + - name: test-parachain-undying-collator + bump: patch diff --git a/prdoc/pr_6926.prdoc b/prdoc/pr_6926.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..788d6c110873ab39bb86ba3ce530c0b6fa230cb8 --- /dev/null +++ b/prdoc/pr_6926.prdoc @@ -0,0 +1,13 @@ +title: '[pallet-revive] implement the gas limit API' +doc: +- audience: Runtime Dev + description: This PR implements the gas limit API, returning the maximum ref_time + per block. Solidity contracts only know a single weight dimension and can use + this method to get the block ref_time limit. +crates: +- name: pallet-revive-fixtures + bump: major +- name: pallet-revive + bump: major +- name: pallet-revive-uapi + bump: major diff --git a/prdoc/pr_6928.prdoc b/prdoc/pr_6928.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..4b9023ab03a659d4bde5e9ae0fa808e6f422f17e --- /dev/null +++ b/prdoc/pr_6928.prdoc @@ -0,0 +1,34 @@ +title: '[Backport] Version bumps and `prdocs` reordering form 2412' +doc: +- audience: Runtime Dev + description: This PR includes backport of the regular version bumps and `prdocs` + reordering from the `stable2412` branch back ro master +crates: +- name: polkadot-node-primitives + bump: none +- name: asset-hub-rococo-runtime + bump: none +- name: bridge-hub-rococo-runtime + bump: none +- name: bridge-hub-westend-runtime + bump: none +- name: collectives-westend-runtime + bump: none +- name: contracts-rococo-runtime + bump: none +- name: coretime-rococo-runtime + bump: none +- name: coretime-westend-runtime + bump: none +- name: glutton-westend-runtime + bump: none +- name: people-rococo-runtime + bump: none +- name: people-westend-runtime + bump: none +- name: rococo-runtime + bump: none +- name: westend-runtime + bump: none +- name: asset-hub-westend-runtime + bump: none diff --git a/prdoc/pr_6937.prdoc b/prdoc/pr_6937.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..5c6806df0b5c04ef537313bb5185283a386f0bcf --- /dev/null +++ b/prdoc/pr_6937.prdoc @@ -0,0 +1,12 @@ +title: '[pallet-revive] bump polkavm to 0.18' +doc: +- audience: Runtime Dev + description: Update to the latest polkavm version, containing a linker fix I need + for revive. +crates: +- name: pallet-revive + bump: patch +- name: pallet-revive-fixtures + bump: patch +- name: pallet-revive-uapi + bump: patch diff --git a/prdoc/pr_6954.prdoc b/prdoc/pr_6954.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..8e8faf5fffd20f89106560855eee552b87812d9d --- /dev/null +++ b/prdoc/pr_6954.prdoc @@ -0,0 +1,13 @@ +title: '[pallet-revive] implement the gas price API' +doc: +- audience: Runtime Dev + description: This PR implements the EVM gas price syscall API method. Currently + this is a compile time constant in revive, but in the EVM it is an opcode. Thus + we should provide an opcode for this in the pallet. +crates: +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/prdoc/pr_6963.prdoc b/prdoc/pr_6963.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..7657349277b373ea59855b4c7393c9109adf4664 --- /dev/null +++ b/prdoc/pr_6963.prdoc @@ -0,0 +1,10 @@ +title: 'grandpa: Ensure `WarpProof` stays in its limits' +doc: +- audience: Node Dev + description: |- + There was the chance that a `WarpProof` was bigger than the maximum warp sync proof size. This could have happened when inserting the last justification, which then may pushed the total proof size above the maximum. The solution is simply to ensure that the last justfication also fits into the limits. + + Close: https://github.com/paritytech/polkadot-sdk/issues/6957 +crates: +- name: sc-consensus-grandpa + bump: patch diff --git a/prdoc/pr_6964.prdoc b/prdoc/pr_6964.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3a88fa72e96359cb4db2fda03b31a0faa7d13cc2 --- /dev/null +++ b/prdoc/pr_6964.prdoc @@ -0,0 +1,15 @@ +title: '[pallet-revive] implement the base fee API' +doc: +- audience: Runtime Dev + description: This PR implements the base fee syscall API method. Currently this + is implemented as a compile time constant in the revive compiler, returning 0. + However, since this is an opocde, if we ever need to implement it for compatibility + reasons with [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md), + it would break already deployed contracts. Thus we provide a syscall method instead. +crates: +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/prdoc/pr_6979.prdoc b/prdoc/pr_6979.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..fae7feeec2df7c33c5845b405d52d180086b4820 --- /dev/null +++ b/prdoc/pr_6979.prdoc @@ -0,0 +1,8 @@ +title: Update prometheus binding failure logging format +doc: +- audience: Node Dev + description: |- + Using `{:#?}` for the error details is a bit annoying, this change makes a more consistent formatting style for error messages. +crates: +- name: substrate-prometheus-endpoint + bump: patch diff --git a/prdoc/pr_6981.prdoc b/prdoc/pr_6981.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..8ed70e51ef454fafb84e1231b436e25f1fdd511b --- /dev/null +++ b/prdoc/pr_6981.prdoc @@ -0,0 +1,7 @@ +title: '[pallet-revive] fix file case' +doc: +- audience: Runtime Dev + description: "fix https://github.com/paritytech/polkadot-sdk/issues/6970\r\n" +crates: +- name: pallet-revive-eth-rpc + bump: minor diff --git a/prdoc/pr_6983.prdoc b/prdoc/pr_6983.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..fddf831ead12761b4a60742bf77eb79cae19fd16 --- /dev/null +++ b/prdoc/pr_6983.prdoc @@ -0,0 +1,17 @@ +title: 'cumulus: bump PARENT_SEARCH_DEPTH to allow for 12-core elastic scaling' +doc: +- audience: Node Dev + description: | + Bumps the PARENT_SEARCH_DEPTH constant to a larger value (30). + This is a node-side limit that restricts the number of allowed pending availability candidates when choosing the parent parablock during authoring. + This limit is rather redundant, as the parachain runtime already restricts the unincluded segment length to the configured value in the + FixedVelocityConsensusHook. + For 12 cores, a value of 24 should be enough, but bumped it to 30 to have some extra buffer. + +crates: +- name: cumulus-client-consensus-aura + bump: patch +- name: cumulus-test-runtime + bump: minor +- name: cumulus-test-service + bump: minor diff --git a/prdoc/pr_6986.prdoc b/prdoc/pr_6986.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..8deb6b04bd1cdd43fc37feab352aed4b56d16e9a --- /dev/null +++ b/prdoc/pr_6986.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: '[pallet-mixnet] Migrate to using frame umbrella crate' + +doc: + - audience: Runtime Dev + description: This PR migrates the pallet-mixnet to use the frame umbrella crate. This + is part of the ongoing effort to migrate all pallets to use the frame umbrella crate. + The effort is tracked [here](https://github.com/paritytech/polkadot-sdk/issues/6504). + +crates: + - name: pallet-mixnet + bump: minor + - name: polkadot-sdk-frame + bump: minor + - name: polkadot-sdk + bump: none \ No newline at end of file diff --git a/prdoc/pr_6988.prdoc b/prdoc/pr_6988.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..18f70f9fd97f1f316bec59a8072e89a8acec1c8b --- /dev/null +++ b/prdoc/pr_6988.prdoc @@ -0,0 +1,5 @@ +doc: [] + +crates: + - name: polkadot + bump: none \ No newline at end of file diff --git a/prdoc/pr_6989.prdoc b/prdoc/pr_6989.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..86c56698d41eec2403f50ec8bddaff689ee84318 --- /dev/null +++ b/prdoc/pr_6989.prdoc @@ -0,0 +1,10 @@ +title: 'paras-registrar: Improve error reporting' +doc: +- audience: Runtime User + description: |- + This pr improves the error reporting by paras registrar when an owner wants to access a locked parachain. + + Closes: https://github.com/paritytech/polkadot-sdk/issues/6745 +crates: +- name: polkadot-runtime-common + bump: patch diff --git a/prdoc/pr_6995.prdoc b/prdoc/pr_6995.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ffdb4738a6fd5b1841d07ddd8e5e462c42abd704 --- /dev/null +++ b/prdoc/pr_6995.prdoc @@ -0,0 +1,14 @@ +title: added new proxy ParaRegistration to Westend +doc: +- audience: Runtime User + description: |- + This adds a new Proxy type to Westend Runtime called ParaRegistration. This is related to: https://github.com/polkadot-fellows/runtimes/pull/520. + + This new proxy allows: + 1. Reserve paraID + 2. Register Parachain + 3. Leverage Utilites pallet + 4. Remove proxy. +crates: +- name: westend-runtime + bump: major diff --git a/prdoc/pr_7005.prdoc b/prdoc/pr_7005.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a61f7c5b9b714ab2f3ca56024f764fe8e04c4c9e --- /dev/null +++ b/prdoc/pr_7005.prdoc @@ -0,0 +1,7 @@ +title: Log peerset set ID -> protocol name mapping +doc: +- audience: Node Dev + description: To simplify debugging of peerset related issues like https://github.com/paritytech/polkadot-sdk/issues/6573#issuecomment-2563091343. +crates: +- name: sc-network + bump: patch diff --git a/prdoc/pr_7011.prdoc b/prdoc/pr_7011.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..55fe0c73ca091365fedec3dfe43a6c7988d9679f --- /dev/null +++ b/prdoc/pr_7011.prdoc @@ -0,0 +1,16 @@ +title: 'sync: Send already connected peers to new subscribers' +doc: +- audience: Node Dev + description: |- + Introduce `SyncEvent::InitialPeers` message sent to new subscribers to allow them correctly tracking sync peers. This resolves a race condition described in https://github.com/paritytech/polkadot-sdk/issues/6573#issuecomment-2563091343. + + Fixes https://github.com/paritytech/polkadot-sdk/issues/6573. +crates: +- name: sc-network-gossip + bump: major +- name: sc-network-statement + bump: patch +- name: sc-network-sync + bump: major +- name: sc-network-transactions + bump: patch diff --git a/prdoc/pr_7020.prdoc b/prdoc/pr_7020.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..5bbdb44c45a0f71050223f8f018d8475650f8ab2 --- /dev/null +++ b/prdoc/pr_7020.prdoc @@ -0,0 +1,18 @@ +title: Remove warning log from frame-omni-bencher CLI +doc: +- audience: Node Operator + description: |- + # Description + + This PR removes the outdated warning message from the `frame-omni-bencher` CLI that states the tool is "not yet battle tested". Fixes #7019 + + ## Integration + + No integration steps are required. + + ## Review Notes + + The functionality of the tool remains unchanged. Removes the warning message from the CLI output. +crates: +- name: frame-omni-bencher + bump: patch diff --git a/prdoc/pr_7021.prdoc b/prdoc/pr_7021.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..5443579bbd9295b6cfc507cd000da0a80951ef5b --- /dev/null +++ b/prdoc/pr_7021.prdoc @@ -0,0 +1,8 @@ +title: Improve remote externalities logging +doc: +- audience: Node Dev + description: |- + Automatically detect if current env is tty. If not disable the spinner logging. +crates: +- name: frame-remote-externalities + bump: patch diff --git a/prdoc/pr_7030.prdoc b/prdoc/pr_7030.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3b1f7be558d8f7d484c92d5e74446b39c37c0262 --- /dev/null +++ b/prdoc/pr_7030.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: "[core-fellowship] Add permissionless import_member" + +doc: + - audience: [Runtime Dev, Runtime User] + description: | + Changes: + - Add call `import_member` to the core-fellowship pallet. + - Move common logic between `import` and `import_member` into `do_import`. + + This is a minor change as to not impact UI and downstream integration. + + ## `import_member` + + Can be used to induct an arbitrary collective member and is callable by any signed origin. Pays no fees upon success. + This is useful in the case that members did not induct themselves and are idling on their rank. + +crates: +- name: pallet-core-fellowship + bump: major +- name: collectives-westend-runtime + bump: patch diff --git a/prdoc/pr_7040.prdoc b/prdoc/pr_7040.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f88e96a703712b42894806b398deaa7856bbdb4f --- /dev/null +++ b/prdoc/pr_7040.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: '[pallet-node-authorization] Migrate to using frame umbrella crate' + +doc: + - audience: Runtime Dev + description: This PR migrates the pallet-node-authorization to use the frame umbrella crate. This + is part of the ongoing effort to migrate all pallets to use the frame umbrella crate. + The effort is tracked [here](https://github.com/paritytech/polkadot-sdk/issues/6504). + +crates: + - name: pallet-node-authorization + bump: minor + - name: polkadot-sdk-frame + bump: minor diff --git a/prdoc/pr_7042.prdoc b/prdoc/pr_7042.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..00fb34c6af493b9b5d1e560bde28555242fa0d3d --- /dev/null +++ b/prdoc/pr_7042.prdoc @@ -0,0 +1,9 @@ +title: `networking::TransactionPool` should accept `Arc` +doc: +- audience: Node Dev + description: The `sc_network_transactions::config::TransactionPool` trait now returns an `Arc` for transactions. +crates: +- name: sc-network-transactions + bump: minor +- name: sc-service + bump: minor \ No newline at end of file diff --git a/prdoc/pr_7043.prdoc b/prdoc/pr_7043.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d7f6cd6907c8588af8d500037d31d76b5ce59877 --- /dev/null +++ b/prdoc/pr_7043.prdoc @@ -0,0 +1,51 @@ +title: Remove usage of `sp-std` from Substrate +doc: +- audience: Runtime Dev + description: |- + # Description + + This PR removes usage of deprecated `sp-std` from Substrate. (following PR of #5010) + + ## Integration + + This PR doesn't remove re-exported `sp_std` from any crates yet, so downstream projects using re-exported `sp_std` will not be affected. + + ## Review Notes + + The existing code using `sp-std` is refactored to use `alloc` and `core` directly. The key-value maps are instantiated from an array of tuples directly instead of using `sp_std::map!` macro. + + This PR replaces `sp_std::Writer`, a helper type for using `Vec<u8>` with `core::fmt::Write` trait, with `alloc::string::String`. + +crates: +- name: pallet-contracts + bump: patch +- name: pallet-revive + bump: patch +- name: sp-runtime + bump: patch +- name: frame-support-procedural + bump: patch +- name: frame-system + bump: patch +- name: pallet-contracts-proc-macro + bump: patch +- name: pallet-revive-proc-macro + bump: patch +- name: frame-support + bump: patch +- name: sc-sysinfo + bump: patch +- name: pallet-bags-list-remote-tests + bump: patch +- name: pallet-election-provider-e2e-test + bump: patch +- name: pallet-nft-fractionalization + bump: patch +- name: pallet-nomination-pools-test-delegate-stake + bump: patch +- name: pallet-nomination-pools-test-transfer-stake + bump: patch +- name: pallet-root-offences + bump: patch +- name: pallet-uniques + bump: patch diff --git a/prdoc/pr_7046.prdoc b/prdoc/pr_7046.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..113cc9c7aac5cc0d1b36cb2b8f0e3105dcc622b9 --- /dev/null +++ b/prdoc/pr_7046.prdoc @@ -0,0 +1,7 @@ +title: adding warning when using default substrateWeight in production +doc: +- audience: Runtime Dev + description: |- + PR for #3581 + Added a cfg to show a deprecated warning message when using std +crates: [] diff --git a/prdoc/pr_7048.prdoc b/prdoc/pr_7048.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..0f3856bc12876aeeae32350539e3fc3f350fdf45 --- /dev/null +++ b/prdoc/pr_7048.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: '[pallet-salary] Migrate to using frame umbrella crate' + +doc: + - audience: Runtime Dev + description: > + This PR migrates the `pallet-salary` to use the FRAME umbrella crate. + This is part of the ongoing effort to migrate all pallets to use the FRAME umbrella crate. + The effort is tracked [here](https://github.com/paritytech/polkadot-sdk/issues/6504). + +crates: + - name: pallet-salary + bump: minor + - name: polkadot-sdk-frame + bump: minor diff --git a/prdoc/pr_7069.prdoc b/prdoc/pr_7069.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a0fc5cafb020b4f23c1a1a61a21b9e9ce33c8478 --- /dev/null +++ b/prdoc/pr_7069.prdoc @@ -0,0 +1,10 @@ +title: Fix defensive! macro to be used in umbrella crates +doc: +- audience: Runtime Dev + description: |- + PR for #7054 + + Replaced frame_support with $crate from @gui1117 's suggestion to fix the dependency issue +crates: +- name: frame-support + bump: patch diff --git a/prdoc/pr_7073.prdoc b/prdoc/pr_7073.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3bcd129d03172cf7e52e4b59ad505bf36ca01043 --- /dev/null +++ b/prdoc/pr_7073.prdoc @@ -0,0 +1,16 @@ +title: Implement NetworkRequest for litep2p +doc: +- audience: Node Dev + description: |- + # Description + + Implements NetworkRequest::request for litep2p that we need for networking benchmarks + + + ## Review Notes + + Duplicates implementation for NetworkService + https://github.com/paritytech/polkadot-sdk/blob/5bf9dd2aa9bf944434203128783925bdc2ad8c01/substrate/client/network/src/service.rs#L1186-L1205 +crates: +- name: sc-network + bump: patch diff --git a/prdoc/pr_7081.prdoc b/prdoc/pr_7081.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..be1d8aa6ee013247f3a6b4495e8b7220d25dd76f --- /dev/null +++ b/prdoc/pr_7081.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: '[pallet-mmr] Migrate to using frame umbrella crate' + +doc: + - audience: Runtime Dev + description: This PR migrates the pallet-mmr to use the frame umbrella crate. This + is part of the ongoing effort to migrate all pallets to use the frame umbrella crate. + The effort is tracked [here](https://github.com/paritytech/polkadot-sdk/issues/6504). + +crates: + - name: pallet-mmr + bump: minor diff --git a/prdoc/pr_7086.prdoc b/prdoc/pr_7086.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..55fed9bca3e6c6e1f7df0c641dac73db4c6aa185 --- /dev/null +++ b/prdoc/pr_7086.prdoc @@ -0,0 +1,11 @@ +title: '[pallet-revive] Fix `caller_is_root` return value' +doc: +- audience: Runtime Dev + description: The return type of the host function `caller_is_root` was denoted as `u32` + in `pallet_revive_uapi`. This PR fixes the return type to `bool`. As a drive-by, the + PR re-exports `pallet_revive::exec::Origin` to extend what can be tested externally. +crates: +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: major diff --git a/prdoc/pr_7091.prdoc b/prdoc/pr_7091.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..badea4e82fdbec9a48eb8948bd762b90cd49275a --- /dev/null +++ b/prdoc/pr_7091.prdoc @@ -0,0 +1,12 @@ +title: '[pallet-revive] Add new host function `to_account_id`' +doc: +- audience: Runtime Dev + description: A new host function `to_account_id` is added. It allows retrieving + the account id for a `H160` address. +crates: +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/prdoc/pr_7093.prdoc b/prdoc/pr_7093.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..cad4477e8832fa40c88f656864563f0033da34c4 --- /dev/null +++ b/prdoc/pr_7093.prdoc @@ -0,0 +1,8 @@ +title: 'initial docify readme with some content #6333' +doc: +- audience: Runtime Dev + description: | + Docifying the README.MD under templates/parachain by adding a Docify. + Also Adding the Cargo.toml under the same folder, essentially making it a crate as Docify acts + for Readmes only under the same crate. +crates: [ ] diff --git a/prdoc/pr_7102.prdoc b/prdoc/pr_7102.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b1923aafc3db4a755b4e79f8961a9eda52d09038 --- /dev/null +++ b/prdoc/pr_7102.prdoc @@ -0,0 +1,8 @@ +title: '`fatxpool`: rotator cache size now depends on pool''s limits' +doc: +- audience: Node Dev + description: |- + This PR modifies the hard-coded size of extrinsics cache within `PoolRotator` to be inline with pool limits. It only applies to fork-aware transaction pool. For the legacy (single-state) transaction pool the logic remains untouched. +crates: +- name: sc-transaction-pool + bump: minor diff --git a/prdoc/pr_7104.prdoc b/prdoc/pr_7104.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..bd05e2b60e1ff8b879617a3e7111a7ba2b820a6b --- /dev/null +++ b/prdoc/pr_7104.prdoc @@ -0,0 +1,23 @@ +title: "collation-generation: resolve mismatch between descriptor and commitments core index" + +doc: + - audience: Node Dev + description: | + This PR resolves a bug where collators failed to generate and submit collations, + resulting in the following error: + + ``` + ERROR tokio-runtime-worker parachain::collation-generation: Failed to construct and + distribute collation: V2 core index check failed: The core index in commitments doesn't + match the one in descriptor. + ``` + + This issue affects only legacy and test collators that still use the collation function. + It is not a problem for lookahead or slot-based collators. + + This fix ensures the descriptor core index contains the value determined by the core + selector UMP signal when the parachain is using RFC103. + +crates: + - name: polkadot-node-collation-generation + bump: patch diff --git a/prdoc/pr_7109.prdoc b/prdoc/pr_7109.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..e54ef3295135c50222a11efed540b82d5d8f64c9 --- /dev/null +++ b/prdoc/pr_7109.prdoc @@ -0,0 +1,11 @@ +title: Add "run to block" tools +doc: +- audience: Runtime Dev + description: |- + Introduce `frame_system::Pallet::run_to_block`, `frame_system::Pallet::run_to_block_with`, and `frame_system::RunToBlockHooks` to establish a generic `run_to_block` mechanism for mock tests, minimizing redundant implementations across various pallets. + + Closes #299. + +crates: +- name: frame-system + bump: minor diff --git a/prdoc/pr_7126.prdoc b/prdoc/pr_7126.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1a86af1b2d1da8ade29829a01f83753637e915dd --- /dev/null +++ b/prdoc/pr_7126.prdoc @@ -0,0 +1,7 @@ +title: 'xcm: Fixes for `UnpaidLocalExporter`' +doc: +- audience: Runtime Dev + description: This PR deprecates `UnpaidLocalExporter` in favor of the new `LocalExporter`. First, the name is misleading, as it can be used in both paid and unpaid scenarios. Second, it contains a hard-coded channel 0, whereas `LocalExporter` uses the same algorithm as `xcm-exporter`. +crates: +- name: staging-xcm-builder + bump: minor diff --git a/prdoc/pr_7127.prdoc b/prdoc/pr_7127.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..761ddd04dbe157f40309d41ef7d1f3ac83cd353a --- /dev/null +++ b/prdoc/pr_7127.prdoc @@ -0,0 +1,9 @@ +title: 'Forbid v1 descriptors with UMP signals' +doc: +- audience: [Runtime Dev, Node Dev] + description: Adds a check that parachain candidates do not send out UMP signals with v1 descriptors. +crates: +- name: polkadot-node-core-candidate-validation + bump: minor +- name: polkadot-primitives + bump: major diff --git a/prdoc/pr_7134.prdoc b/prdoc/pr_7134.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..095d4757f43800bd402bbe988a471133f2914dcb --- /dev/null +++ b/prdoc/pr_7134.prdoc @@ -0,0 +1,11 @@ +title: 'xcm: convert properly assets in xcmpayment apis' +doc: +- audience: Runtime User + description: |- + Port #6459 changes to relays as well, which were probably forgotten in that PR. + Thanks! +crates: +- name: rococo-runtime + bump: patch +- name: westend-runtime + bump: patch diff --git a/prdoc/pr_7163.prdoc b/prdoc/pr_7163.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..669c480b835bd982699f6a0eec21fbb343d8b6e3 --- /dev/null +++ b/prdoc/pr_7163.prdoc @@ -0,0 +1,13 @@ +title: '[pallet-revive] Remove debug buffer' +doc: +- audience: Runtime Dev + description: Remove the `debug_buffer` feature +crates: +- name: asset-hub-westend-runtime + bump: minor +- name: pallet-revive + bump: major +- name: pallet-revive-proc-macro + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/prdoc/pr_7164.prdoc b/prdoc/pr_7164.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..cb0410a9de79954bab775de09629610ba420b2b4 --- /dev/null +++ b/prdoc/pr_7164.prdoc @@ -0,0 +1,8 @@ +title: '[pallet-revive] Remove revive events' +doc: +- audience: Runtime Dev + description: Remove all pallet::events except for the `ContractEmitted` event that + is emitted by contracts +crates: +- name: pallet-revive + bump: major diff --git a/prdoc/pr_7169.prdoc b/prdoc/pr_7169.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f78dbfd8d2cd18cccbd3f7c94958c23371fdfbaf --- /dev/null +++ b/prdoc/pr_7169.prdoc @@ -0,0 +1,14 @@ +title: 'xcm: fix DenyThenTry when work with multiple Deny tuples' +doc: +- audience: Runtime User + description: |- + This PR changes the behavior of DenyThenTry to fix #7148 + If any of the tuple elements returns `Err(())`, the execution stops. + Else, `Ok(_)` is returned if all elements accept the message. +crates: +- name: staging-xcm-executor + bump: minor +- name: staging-xcm-builder + bump: minor +- name: bridge-hub-common + bump: minor diff --git a/prdoc/pr_7170.prdoc b/prdoc/pr_7170.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..fae908f7407d65096910c06ea765c72fb071620d --- /dev/null +++ b/prdoc/pr_7170.prdoc @@ -0,0 +1,8 @@ +title: Fix reversed error message in DispatchInfo +doc: +- audience: Runtime Dev + description: "Fix error message in `DispatchInfo` where post-dispatch and pre-dispatch\ + \ weight was reversed.\r\n" +crates: +- name: frame-support + bump: patch diff --git a/prdoc/pr_7176.prdoc b/prdoc/pr_7176.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b78f42014afea5e04d6c3057e91c62d9f9928db5 --- /dev/null +++ b/prdoc/pr_7176.prdoc @@ -0,0 +1,9 @@ +title: '[pallet-revive] Bump asset-hub westend spec version' +doc: +- audience: Runtime Dev + description: Bump asset-hub westend spec version +crates: +- name: asset-hub-westend-runtime + bump: minor +- name: pallet-revive-eth-rpc + bump: minor diff --git a/prdoc/pr_7177.prdoc b/prdoc/pr_7177.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9ab0be1f20a936efe9feadf81c79a5f74f3531f6 --- /dev/null +++ b/prdoc/pr_7177.prdoc @@ -0,0 +1,20 @@ +title: Make frame crate not experimental +doc: +- audience: Runtime Dev + description: |- + Frame crate may still be unstable, but it is no longer feature gated by the feature `experimental`. +crates: +- name: polkadot-sdk-frame + bump: minor +- name: pallet-salary + bump: patch +- name: pallet-multisig + bump: patch +- name: pallet-proxy + bump: patch +- name: pallet-atomic-swap + bump: patch +- name: pallet-mixnet + bump: patch +- name: pallet-node-authorization + bump: patch diff --git a/prdoc/pr_7194.prdoc b/prdoc/pr_7194.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3a9db46ceae96fdbb9f352a4558b1ab95ffd1450 --- /dev/null +++ b/prdoc/pr_7194.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: '[FRAME] `pallet_asset_tx_payment`: replace `AssetId` bound from `Copy` to `Clone`' + +doc: + - audience: Runtime Dev + description: | + `OnChargeAssetTransaction`'s associated type `AssetId` is bounded by `Copy` which makes it impossible + to use `staging_xcm::v4::Location` as `AssetId`. This PR bounds `AssetId` to `Clone` instead, which is + more lenient. + +crates: + - name: pallet-asset-tx-payment + bump: minor diff --git a/prdoc/pr_7195.prdoc b/prdoc/pr_7195.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..db4f877b156ad7255ab7e49ea9387e150a7a367d --- /dev/null +++ b/prdoc/pr_7195.prdoc @@ -0,0 +1,7 @@ +title: Unify Import verifier usage across parachain template and omninode +doc: +- audience: Node Dev + description: |- + In polkadot-omni-node block import pipeline it uses default aura verifier without checking equivocation, + This Pr replaces the check with full verification with equivocation like in parachain template block import +crates: [] diff --git a/prdoc/pr_7198.prdoc b/prdoc/pr_7198.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..15478d9341d689d4e775f0359fb5c1e7de0f46e5 --- /dev/null +++ b/prdoc/pr_7198.prdoc @@ -0,0 +1,12 @@ +title: '[pallet-revive] implement the block author API ' +doc: +- audience: Runtime Dev + description: This PR implements the block author API method. Runtimes ought to implement + it such that it corresponds to the `coinbase` EVM opcode. +crates: +- name: pallet-revive + bump: major +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/prdoc/pr_7203.prdoc b/prdoc/pr_7203.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..96a3d19472e9f77189eaaf0f9ad3c829aa27e8a4 --- /dev/null +++ b/prdoc/pr_7203.prdoc @@ -0,0 +1,13 @@ +title: 'pallet_revive: Bump PolkaVM' +doc: +- audience: Runtime Dev + description: Update to PolkaVM `0.19`. This version renumbers the opcodes in order + to be in-line with the grey paper. Hopefully, for the last time. This means that + it breaks existing contracts. +crates: +- name: pallet-revive + bump: patch +- name: pallet-revive-fixtures + bump: patch +- name: pallet-revive-uapi + bump: patch diff --git a/prdoc/pr_7206.prdoc b/prdoc/pr_7206.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d605308ba54c05ca02f766f33258c67977d1314d --- /dev/null +++ b/prdoc/pr_7206.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Add an extra_constant to pallet-collator-selection" + +doc: + - audience: Runtime Dev + description: | + - Allows to query collator-selection's pot account via extra constant. + +crates: + - name: pallet-collator-selection + bump: minor \ No newline at end of file diff --git a/prdoc/pr_7230.prdoc b/prdoc/pr_7230.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..027694f604fe907d7781783e70698a0952e27644 --- /dev/null +++ b/prdoc/pr_7230.prdoc @@ -0,0 +1,46 @@ +title: 'revive: Include immutable storage deposit into the contracts `storage_base_deposit`' +doc: +- audience: Runtime Dev + description: |- + This PR is centered around a main fix regarding the base deposit and a bunch of drive by or related fixtures that make sense to resolve in one go. It could be broken down more but I am constantly rebasing this PR and would appreciate getting those fixes in as-one. + + ## Record the deposit for immutable data into the `storage_base_deposit` + + The `storage_base_deposit` are all the deposit a contract has to pay for existing. It included the deposit for its own metadata and a deposit proportional (< 1.0x) to the size of its code. However, the immutable code size was not recorded there. This would lead to the situation where on terminate this portion wouldn't be refunded staying locked into the contract. It would also make the calculation of the deposit changes on `set_code_hash` more complicated when it updates the immutable data (to be done in #6985). Reason is because it didn't know how much was payed before since the storage prices could have changed in the mean time. + + In order for this solution to work I needed to delay the deposit calculation for a new contract for after the contract is done executing is constructor as only then we know the immutable data size. Before, we just charged this eagerly in `charge_instantiate` before we execute the constructor. Now, we merely send the ED as free balance before the constructor in order to create the account. After the constructor is done we calculate the contract base deposit and charge it. This will make `set_code_hash` much easier to implement. + + As a side effect it is now legal to call `set_immutable_data` multiple times per constructor (even though I see no reason to do so). It simply overrides the immutable data with the new value. The deposit accounting will be done after the constructor returns (as mentioned above) instead of when setting the immutable data. + + ## Don't pre-charge for reading immutable data + + I noticed that we were pre-charging weight for the max allowable immutable data when reading those values and then refunding after read. This is not necessary as we know its length without reading the storage as we store it out of band in contract metadata. This makes reading it free. Less pre-charging less problems. + + ## Remove delegate locking + + Fixes #7092 + + This is also in the spirit of making #6985 easier to implement. The locking complicates `set_code_hash` as we might need to block settings the code hash when locks exist. Check #7092 for further rationale. + + ## Enforce "no terminate in constructor" eagerly + + We used to enforce this rule after the contract execution returned. Now we error out early in the host call. This makes it easier to be sure to argue that a contract info still exists (wasn't terminated) when a constructor successfully returns. All around this his just much simpler than dealing this check. + + ## Moved refcount functions to `CodeInfo` + + They never really made sense to exist on `Stack`. But now with the locking gone this makes even less sense. The refcount is stored inside `CodeInfo` to lets just move them there. + + ## Set `CodeHashLockupDepositPercent` for test runtime + + The test runtime was setting `CodeHashLockupDepositPercent` to zero. This was trivializing many code paths and excluded them from testing. I set it to `30%` which is our default value and fixed up all the tests that broke. This should give us confidence that the lockup doeposit collections properly works. + + ## Reworked the `MockExecutable` to have both a `deploy` and a `call` entry point + + This type used for testing could only have either entry points but not both. In order to fix the `immutable_data_set_overrides` I needed to a new function `add_both` to `MockExecutable` that allows to have both entry points. Make sure to make use of it in the future :) +crates: +- name: pallet-revive-fixtures + bump: patch +- name: pallet-revive + bump: patch +- name: pallet-revive-uapi + bump: major diff --git a/prdoc/pr_7251.prdoc b/prdoc/pr_7251.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..98e371dc940ffb32a111b5ba572ac5bef89396c4 --- /dev/null +++ b/prdoc/pr_7251.prdoc @@ -0,0 +1,7 @@ +title: '[pallet-revive] eth-rpc error logging' +doc: +- audience: Runtime Dev + description: Log error instead of failing with an error when block processing fails +crates: +- name: pallet-revive-eth-rpc + bump: minor diff --git a/prdoc/pr_7254.prdoc b/prdoc/pr_7254.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a6a6cc5f1ef535bfed37f840f87eb41d478f14d5 --- /dev/null +++ b/prdoc/pr_7254.prdoc @@ -0,0 +1,58 @@ +title: deprecate AsyncBackingParams +doc: + +- audience: [Node Dev, Runtime Dev] + description: |- + Removes all usage of the static async backing params, replacing them with dynamically computed equivalent values (based on the claim queue and scheduling lookahead). + + Adds a new runtime API for querying the scheduling lookahead value. If not present, falls back to 3 (the default value that is backwards compatible with values we have on production networks for allowed_ancestry_len) + + Also removes most code that handles async backing not yet being enabled, which includes support for collation protocol version 1 on collators, as it only worked for leaves not supporting async backing (which are none). + +crates: +- name: cumulus-relay-chain-minimal-node + bump: minor +- name: cumulus-relay-chain-rpc-interface + bump: minor +- name: polkadot-node-core-candidate-validation + bump: minor +- name: polkadot-node-core-prospective-parachains + bump: minor +- name: polkadot-node-core-provisioner + bump: minor +- name: polkadot-node-core-runtime-api + bump: minor +- name: polkadot-collator-protocol + bump: major +- name: polkadot-overseer + bump: major +- name: polkadot-node-subsystem-types + bump: major +- name: polkadot-node-subsystem-util + bump: major +- name: polkadot-primitives + bump: minor +- name: polkadot-runtime-parachains + bump: minor +- name: rococo-runtime + bump: minor +- name: westend-runtime + bump: minor +- name: cumulus-client-consensus-aura + bump: minor +- name: cumulus-relay-chain-inprocess-interface + bump: minor +- name: cumulus-relay-chain-interface + bump: major +- name: polkadot-statement-distribution + bump: major +- name: polkadot + bump: none +- name: polkadot-service + bump: minor +- name: cumulus-client-consensus-common + bump: minor +- name: cumulus-client-network + bump: minor +- name: cumulus-client-pov-recovery + bump: minor diff --git a/prdoc/pr_7260.prdoc b/prdoc/pr_7260.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..62f73120bc19834e8ea90ecef1fa2962c978bf45 --- /dev/null +++ b/prdoc/pr_7260.prdoc @@ -0,0 +1,10 @@ +title: '[eth-indexer] subscribe to finalize blocks instead of best blocks' +doc: +- audience: Runtime Dev + description: 'For eth-indexer, it''s probably safer to use `subscribe_finalized` + and index these blocks into the DB rather than `subscribe_best` + + ' +crates: +- name: pallet-revive-eth-rpc + bump: minor diff --git a/prdoc/pr_7263.prdoc b/prdoc/pr_7263.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..892e8049395594839c5d2cabfa8414d1002f55f4 --- /dev/null +++ b/prdoc/pr_7263.prdoc @@ -0,0 +1,28 @@ +title: Fix `frame-benchmarking-cli` not buildable without rocksdb +doc: +- audience: Runtime Dev + description: |- + ## Description + + The `frame-benchmarking-cli` crate has not been buildable without the `rocksdb` feature since version 1.17.0. + + **Error:** + ```rust + self.database()?.unwrap_or(Database::RocksDb), + ^^^^^^^ variant or associated item not found in `Database` + ``` + + This issue is also related to the `rocksdb` feature bleeding (#3793), where the `rocksdb` feature was always activated even when compiling this crate with `--no-default-features`. + + **Fix:** + - Resolved the error by choosing `paritydb` as the default database when compiled without the `rocksdb` feature. + - Fixed the issue where the `sc-cli` crate's `rocksdb` feature was always active, even compiling `frame-benchmarking-cli` with `--no-default-features`. + + ## Review Notes + + Fix the crate to be built without rocksdb, not intended to solve #3793. +crates: +- name: polkadot-node-metrics + bump: patch +- name: frame-benchmarking-cli + bump: patch diff --git a/prdoc/pr_7266.prdoc b/prdoc/pr_7266.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..4fa7ddb7b41a50b2ba15a1fc3d531cbcf8c73406 --- /dev/null +++ b/prdoc/pr_7266.prdoc @@ -0,0 +1,13 @@ +title: Add `offchain_localStorageClear` RPC method +doc: +- audience: Node Operator + description: |- + Adds RPC method `offchain_localStorageClear` to clear the offchain local storage. +crates: +- name: sc-offchain + bump: minor +- name: sc-rpc-api + bump: minor + validate: false +- name: sc-rpc + bump: minor diff --git a/prdoc/pr_7281.prdoc b/prdoc/pr_7281.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..33e04c419bad925eb3a3f991a61a2cc5975173d5 --- /dev/null +++ b/prdoc/pr_7281.prdoc @@ -0,0 +1,13 @@ +title: '[pallet-revive] fix eth fee estimation' +doc: +- audience: Runtime Dev + description: |- + Fix EVM fee cost estimation. + The current estimation was shown in Native and not EVM decimal currency. +crates: +- name: asset-hub-westend-runtime + bump: minor +- name: pallet-revive-eth-rpc + bump: minor +- name: pallet-revive + bump: minor diff --git a/prdoc/pr_7307.prdoc b/prdoc/pr_7307.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b27aace0bd1391338477d104ef23772d844881e1 --- /dev/null +++ b/prdoc/pr_7307.prdoc @@ -0,0 +1,16 @@ +title: Bridges small nits/improvements +doc: +- audience: Runtime Dev + description: | + This PR introduces a new `expected_payload_type` parameter to the Bridges `assert_complete_bridge_types` macro. +crates: +- name: bridge-runtime-common + bump: patch +- name: bridge-hub-rococo-runtime + bump: patch +- name: bridge-hub-westend-runtime + bump: patch +- name: staging-xcm-builder + bump: patch +- name: emulated-integration-tests-common + bump: patch diff --git a/prdoc/pr_7318.prdoc b/prdoc/pr_7318.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ec41b648a9c2862a7785ce244c0004f6a8871ac0 --- /dev/null +++ b/prdoc/pr_7318.prdoc @@ -0,0 +1,8 @@ +title: 'revive: Fix compilation of `uapi` crate when `unstable-hostfn` is not set' +doc: +- audience: Runtime Dev + description: This regression was introduced with some of the recent PRs. Regression + fixed and test added. +crates: +- name: pallet-revive-uapi + bump: minor diff --git a/prdoc/pr_7319.prdoc b/prdoc/pr_7319.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d572f7e707e1f39dbfbb34d3043ec19ad51ca2e6 --- /dev/null +++ b/prdoc/pr_7319.prdoc @@ -0,0 +1,16 @@ +title: '[pallet-revive] pack exceeding syscall arguments into registers' +doc: +- audience: Runtime Dev + description: |- + This PR changes how we call runtime API methods with more than 6 arguments: They are no longer spilled to the stack but packed into registers instead. Pointers are 32 bit wide so we can pack two of them into a single 64 bit register. Since we mostly pass pointers, this technique effectively increases the number of arguments we can pass using the available registers. + + To make this work for `instantiate` too we now pass the code hash and the call data in the same buffer, akin to how the `create` family opcodes work in the EVM. The code hash is fixed in size, implying the start of the constructor call data. +crates: +- name: pallet-revive-fixtures + bump: major +- name: pallet-revive-proc-macro + bump: major +- name: pallet-revive + bump: major +- name: pallet-revive-uapi + bump: major diff --git a/prdoc/pr_7324.prdoc b/prdoc/pr_7324.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..e4fb7db781766656a6d02cf0d9bd7b7fd16bce8c --- /dev/null +++ b/prdoc/pr_7324.prdoc @@ -0,0 +1,17 @@ +title: Replace derivative dependency with derive-where +author: conr2d +topic: runtime + +doc: +- audience: Runtime Dev + description: |- + The `derivative` crate, previously used to derive basic traits for structs with + generics or enums, is no longer actively maintained. It has been replaced with + the `derive-where` crate, which offers a more straightforward syntax while + providing the same features as `derivative`. + +crates: + - name: cumulus-pallet-weight-reclaim + bump: patch + - name: staging-xcm + bump: patch diff --git a/prdoc/pr_7325.prdoc b/prdoc/pr_7325.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..788f01cb32470ab5f7a10dfc520c6dcc9f5d4310 --- /dev/null +++ b/prdoc/pr_7325.prdoc @@ -0,0 +1,11 @@ +title: '[pallet-revive] eth-rpc minor fixes' +doc: +- audience: Runtime Dev + description: |- + - Add option to specify database_url from an environment variable + - Add a test-deployment.rs rust script that can be used to test deployment and call of a contract before releasing eth-rpc + - Make evm_block non fallible so that it can return an Ok response for older blocks when the runtime API is not available + - Update subxt version to integrate changes from https://github.com/paritytech/subxt/pull/1904 +crates: +- name: pallet-revive-eth-rpc + bump: minor diff --git a/prdoc/pr_7327.prdoc b/prdoc/pr_7327.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..bb2d7a671af31781dec2fe819b2c155b52780111 --- /dev/null +++ b/prdoc/pr_7327.prdoc @@ -0,0 +1,11 @@ +title: Correctly register the weight n `set_validation_data` in `cumulus-pallet-parachain-system` + +doc: + - audience: Runtime Dev + description: | + The actual weight of the call was register as a refund, but the pre-dispatch weight is 0, + and we can't refund from 0. Now the actual weight is registered manually instead of ignored. + +crates: + - name: cumulus-pallet-parachain-system + bump: patch diff --git a/prdoc/pr_7338.prdoc b/prdoc/pr_7338.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..20948eb0d52f6e234294843f1d85e11022bc4bfb --- /dev/null +++ b/prdoc/pr_7338.prdoc @@ -0,0 +1,10 @@ +title: '[net/libp2p] Use raw `Identify` observed addresses to discover external addresses' +doc: +- audience: Node Dev + description: |- + Instead of using libp2p-provided external address candidates, susceptible to address translation issues, use litep2p-backend approach based on confirming addresses observed by multiple peers as external. + + Fixes https://github.com/paritytech/polkadot-sdk/issues/7207. +crates: +- name: sc-network + bump: major diff --git a/prdoc/pr_7359.prdoc b/prdoc/pr_7359.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..e54fcb877d1de78f153b54ce3ff740bed070e441 --- /dev/null +++ b/prdoc/pr_7359.prdoc @@ -0,0 +1,7 @@ +title: Improve `set_validation_data` error message. +doc: +- audience: Runtime Dev + description: Adds a more elaborate error message to the error that appears when `set_validation_data` is missing in a parachain block. +crates: +- name: cumulus-pallet-parachain-system + bump: patch diff --git a/prdoc/pr_7365.prdoc b/prdoc/pr_7365.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..dcee76e01c789ce2c67f714701d0725e1a59db18 --- /dev/null +++ b/prdoc/pr_7365.prdoc @@ -0,0 +1,12 @@ +title: Use checked math in frame-balances named_reserve +doc: +- audience: Runtime Dev + description: |- + This PR modifies `named_reserve()` in frame-balances to use checked math instead of defensive saturating math. + + The use of saturating math relies on the assumption that the value will always fit in `u128::MAX`. However, there is nothing preventing the implementing pallet from passing a larger value which overflows. This can happen if the implementing pallet does not validate user input and instead relies on `named_reserve()` to return an error (this saves an additional read) + + This is not a security concern, as the method will subsequently return an error thanks to `<Self as ReservableCurrency<_>>::reserve(who, value)?;`. However, the `defensive_saturating_add` will panic in `--all-features`, creating false positive crashes in fuzzing operations. +crates: +- name: pallet-balances + bump: patch diff --git a/prdoc/pr_7377.prdoc b/prdoc/pr_7377.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1dfa88099a16e260b162227f28ac7f93ada8649c --- /dev/null +++ b/prdoc/pr_7377.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 missing events to nomination pool extrinsics + +doc: + - audience: [Runtime Dev, Runtime User] + description: | + Introduces events to extrinsics from `pallet_nomination_pools` that previously had none: + - `set_metadata` + - `nominate` + - `chill` + - `set_configs` + - `set_claim_permission` + +crates: +- name: pallet-nomination-pools + bump: major +- name: pallet-staking + bump: none \ No newline at end of file diff --git a/prdoc/pr_7378.prdoc b/prdoc/pr_7378.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..8754966d3e82c18cd7abfa1f4ef6065e1d5c2edf --- /dev/null +++ b/prdoc/pr_7378.prdoc @@ -0,0 +1,13 @@ +title: fix pre-dispatch PoV underweight for ParasInherent +doc: +- audience: Runtime Dev + description: |- + This should fix the error log related to PoV pre-dispatch weight being lower than post-dispatch for `ParasInherent`: + ``` + ERROR tokio-runtime-worker runtime::frame-support: Post dispatch weight is greater than pre dispatch weight. Pre dispatch weight may underestimating the actual weight. Greater post dispatch weight components are ignored. + Pre dispatch weight: Weight { ref_time: 47793353978, proof_size: 1019 }, + Post dispatch weight: Weight { ref_time: 5030321719, proof_size: 135395 } + ``` +crates: +- name: polkadot-runtime-parachains + bump: patch diff --git a/prdoc/pr_7379.prdoc b/prdoc/pr_7379.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..0bd904346d68d4e88d43170c05115be27f12231b --- /dev/null +++ b/prdoc/pr_7379.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Add support for feature pallet_balances/insecure_zero_ed in benchmarks and testing" + +doc: + - audience: Runtime Dev + description: | + Currently benchmarks and tests on pallet_balances would fail when the feature insecure_zero_ed is enabled. This PR allows to run such benchmark and tests keeping into account the fact that accounts would not be deleted when their balance goes below a threshold. + +crates: + - name: pallet-balances + bump: patch diff --git a/prdoc/pr_7383.prdoc b/prdoc/pr_7383.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..bd421d73ab2025f530ebe15ed4d291fa9411533b --- /dev/null +++ b/prdoc/pr_7383.prdoc @@ -0,0 +1,12 @@ +title: Bridges small nits/improvements +doc: +- audience: Runtime Dev + description: 'This PR contains small fixes and backwards compatibility issues identified + during work on the larger PR: https://github.com/paritytech/polkadot-sdk/issues/6906.' +crates: +- name: cumulus-pallet-xcmp-queue + bump: patch +- name: pallet-xcm-bridge-hub + bump: minor +- name: bridge-hub-test-utils + bump: minor diff --git a/prdoc/pr_7414.prdoc b/prdoc/pr_7414.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..41edc1fe7cb38214c14e48f474b2eec3bd0a3dcf --- /dev/null +++ b/prdoc/pr_7414.prdoc @@ -0,0 +1,20 @@ +title: '[pallet-revive] do not trap the caller on instantiations with duplicate contracts' +doc: +- audience: Runtime Dev + description: |- + This PR changes the behavior of `instantiate` when the resulting contract address already exists (because the caller tried to instantiate the same contract with the same salt multiple times): Instead of trapping the caller, return an error code. + + Solidity allows `catch`ing this, which doesn't work if we are trapping the caller. For example, the change makes the following snippet work: + + ```Solidity + try new Foo{salt: hex"00"}() returns (Foo) { + // Instantiation was successful (contract address was free and constructor did not revert) + } catch { + // This branch is expected to be taken if the instantiation failed because of a duplicate salt + } + ``` +crates: +- name: pallet-revive + bump: major +- name: pallet-revive-uapi + bump: major diff --git a/prdoc/pr_7451.prdoc b/prdoc/pr_7451.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..dd8090cdd7cb72ea5b58eaf811e79ccba21e5dbe --- /dev/null +++ b/prdoc/pr_7451.prdoc @@ -0,0 +1,8 @@ +title: 'omni-node: Adjust manual seal parameters' +doc: +- audience: Runtime Dev + description: |- + This PR restores compatibility of older runtimes with the dev mode of omni-node. Before, runtimes built without the changes in https://github.com/paritytech/polkadot-sdk/pull/6825 were failing. +crates: +- name: polkadot-omni-node-lib + bump: patch diff --git a/prdoc/pr_7463.prdoc b/prdoc/pr_7463.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..885fc24efa6b62a8dbbf05d779406287d9db0eed --- /dev/null +++ b/prdoc/pr_7463.prdoc @@ -0,0 +1,17 @@ +title: '[pallet-revive] tx fee fixes' +doc: +- audience: Runtime Dev + description: |- + Apply some fixes to properly estimate ethereum tx fees: + + - Set the `extension_weight` on the dispatch_info to properly calculate the fee with pallet_transaction_payment + - Expose the gas_price through Runtime API, just in case we decide to tweak the value in future updates, it should be read from the chain rather than be a shared constant exposed by the crate + - add a `evm_gas_to_fee` utility function to properly convert gas to substrate fee + - Fix some minor gas encoding for edge cases +crates: +- name: asset-hub-westend-runtime + bump: minor +- name: pallet-revive-eth-rpc + bump: minor +- name: pallet-revive + bump: minor diff --git a/prdoc/pr_7479.prdoc b/prdoc/pr_7479.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..444eaa420a4562ebe2ad0bb298b197fd83e5f036 --- /dev/null +++ b/prdoc/pr_7479.prdoc @@ -0,0 +1,9 @@ +title: 'omni-node: add offchain worker' +doc: +- audience: [ Runtime Dev, Node Dev, Node Operator ] + description: |- + Added support for offchain worker to omni-node-lib for both aura and manual seal nodes. + +crates: +- name: polkadot-omni-node-lib + bump: patch diff --git a/prdoc/pr_7488.prdoc b/prdoc/pr_7488.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..55ce87d42d86dff00dca7d1b172fe7bbbf76a589 --- /dev/null +++ b/prdoc/pr_7488.prdoc @@ -0,0 +1,10 @@ +title: Increase litep2p keep-alive to 10 seconds to mirror libp2p + +doc: + - audience: [Node Dev, Node Operator] + description: | + Increase litep2p keep-alive to 10 seconds to mirror libp2p behavior. + +crates: + - name: sc-network + bump: patch diff --git a/prdoc/pr_6463.prdoc b/prdoc/stable2412-1/pr_6463.prdoc similarity index 100% rename from prdoc/pr_6463.prdoc rename to prdoc/stable2412-1/pr_6463.prdoc diff --git a/prdoc/stable2412-1/pr_6807.prdoc b/prdoc/stable2412-1/pr_6807.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b9564dfb2fe26fd1ec399f6c490f6bcf86de3c5d --- /dev/null +++ b/prdoc/stable2412-1/pr_6807.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Retry approval on availability failure if the check is still needed + +doc: + - audience: Node Dev + description: | + Recovering the POV can fail in situation where the node just restart and the DHT topology + wasn't fully discovered yet, so the current node can't connect to most of its Peers. + This is bad because for gossiping the assignment you need to be connected to just a few + peers, so because we can't approve the candidate other nodes will see this as a no show. + Fix it by retrying to approve a candidate for a fixed number of atttempts if the block is + still needed. + + +crates: + - name: polkadot-node-core-approval-voting + bump: minor diff --git a/prdoc/stable2412-1/pr_6825.prdoc b/prdoc/stable2412-1/pr_6825.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d57b2b573a1014c603a1022dd9b49760c8d7eb61 --- /dev/null +++ b/prdoc/stable2412-1/pr_6825.prdoc @@ -0,0 +1,50 @@ +# 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 relay chain slot for velocity measurement on parachains + +doc: + - audience: Runtime Dev + description: | + The AuraExt pallets `ConsensusHook` is performing checks based on a parachains velocity. It was previously + checking how many blocks where produced in a given parachain slot. This only works well when the parachain + and relay chain slot length is the same. After this PR, we are checking against the relay chain slot. + + **🚨 Action Required:** A migration of name `cumulus_pallet_aura_ext::migration::MigrateV0ToV1` is included + that cleans up a renamed storage item. Parachain must add it to their runtimes. More information is available in + the [reference docs](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/frame_runtime_upgrades_and_migrations/index.html#single-block-migrations). + +crates: + - name: cumulus-pallet-parachain-system + bump: minor + - name: cumulus-pallet-aura-ext + bump: major + - name: cumulus-primitives-aura + bump: major + - name: cumulus-client-parachain-inherent + bump: minor + - name: cumulus-client-consensus-aura + bump: minor + - name: xcm-emulator + bump: minor + - name: asset-hub-rococo-runtime + bump: minor + - name: asset-hub-westend-runtime + bump: minor + - name: bridge-hub-rococo-runtime + bump: minor + - name: bridge-hub-westend-runtime + bump: minor + - name: collectives-westend-runtime + bump: minor + - name: coretime-rococo-runtime + bump: minor + - name: coretime-westend-runtime + bump: minor + - name: people-rococo-runtime + bump: minor + - name: people-westend-runtime + bump: minor + - name: contracts-rococo-runtime + bump: minor + diff --git a/prdoc/stable2412-1/pr_6855.prdoc b/prdoc/stable2412-1/pr_6855.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a665115ce6c72ab2e147538018bbc33ee14820e9 --- /dev/null +++ b/prdoc/stable2412-1/pr_6855.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Snowbridge - Support bridging native ETH + +doc: + - audience: Runtime User + description: + Support Native ETH as an asset type instead of only supporting WETH. WETH is still supported, but adds + support for ETH in the inbound and outbound routers. + +crates: + - name: snowbridge-router-primitives + bump: minor + - name: snowbridge-pallet-inbound-queue-fixtures + bump: minor diff --git a/prdoc/stable2412-1/pr_6971.prdoc b/prdoc/stable2412-1/pr_6971.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..4790d773fee4e66af262abaa6f506ba7f2aedd91 --- /dev/null +++ b/prdoc/stable2412-1/pr_6971.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Make importing of duplicate assignment idempotent + +doc: + - audience: Node Dev + description: | + Normally, approval-voting wouldn't receive duplicate assignments because approval-distribution makes + sure of it, however in the situation where we restart we might receive the same assignment again and + since approval-voting already persisted it we will end up inserting it twice in ApprovalEntry.tranches.assignments + because that's an array. Fix this by inserting only assignments that are not duplicate. + +crates: + - name: polkadot-node-core-approval-voting + bump: minor diff --git a/prdoc/stable2412-1/pr_6973.prdoc b/prdoc/stable2412-1/pr_6973.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..416789b9171ad3ad779e36add96158731ee22eb9 --- /dev/null +++ b/prdoc/stable2412-1/pr_6973.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: approval-voting fix sending of assignments after restart + +doc: + - audience: Node Dev + description: | + There is a problem on restart where nodes will not trigger their needed assignment if + they were offline and the time of the assignment passed, so after restart always + schedule a wakeup so that nodes a have the opportunity of triggering their assignments + if they are still needed. + +crates: + - name: polkadot-node-core-approval-voting + bump: minor diff --git a/prdoc/stable2412-1/pr_7013.prdoc b/prdoc/stable2412-1/pr_7013.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..138fa7f2310221b4023eca54ff646aaadd728c49 --- /dev/null +++ b/prdoc/stable2412-1/pr_7013.prdoc @@ -0,0 +1,7 @@ +title: 'pallet-bounties: Fix benchmarks for 0 ED' +doc: +- audience: Runtime Dev + description: 'Closes: https://github.com/paritytech/polkadot-sdk/issues/7009' +crates: +- name: pallet-bounties + bump: patch diff --git a/prdoc/stable2412-1/pr_7028.prdoc b/prdoc/stable2412-1/pr_7028.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ead918fc2e0077ef4972075705e1e0cc10447cc4 --- /dev/null +++ b/prdoc/stable2412-1/pr_7028.prdoc @@ -0,0 +1,25 @@ +title: 'Fix implication order in implementation of `TransactionExtension` for tuple' +doc: +- audience: + - Runtime Dev + - Runtime User + description: |- + Before this PR, the implications were different in the pipeline `(A, B, C)` and `((A, B), C)`. + This PR fixes this behavior and make nested tuple transparant, the implication order of tuple of + tuple is now the same as in a single tuple. + + For runtime users this mean that the implication can be breaking depending on the pipeline used + in the runtime. + + For runtime developers this breaks usage of `TransactionExtension::validate`. + When calling `TransactionExtension::validate` the implication must now implement `Implication` + trait, you can use `TxBaseImplication` to wrap the type and use it as the base implication. + E.g. instead of `&(extension_version, call),` you can write `&TxBaseImplication((extension_version, call))`. + +crates: +- name: sp-runtime + bump: major +- name: pallet-skip-feeless-payment + bump: major +- name: frame-system + bump: major diff --git a/prdoc/stable2412-1/pr_7050.prdoc b/prdoc/stable2412-1/pr_7050.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..da9dd808033d02b8e51d1beac4f0cf218b926c87 --- /dev/null +++ b/prdoc/stable2412-1/pr_7050.prdoc @@ -0,0 +1,11 @@ +title: Avoid incomplete block import pipeline with full verifying import queue +doc: +- audience: Node Dev + description: |- + When warp syncing a node using the equivocation checking verifier, we now properly set the fork_choice rule. + Affected are mostly nodes that are derived from the parachain template. Omni-node is not affected. + + The prevents the error `ClientImport("Incomplete block import pipeline.")` after state sync. +crates: +- name: cumulus-client-consensus-aura + bump: patch diff --git a/prdoc/stable2412-1/pr_7067.prdoc b/prdoc/stable2412-1/pr_7067.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ead918fc2e0077ef4972075705e1e0cc10447cc4 --- /dev/null +++ b/prdoc/stable2412-1/pr_7067.prdoc @@ -0,0 +1,25 @@ +title: 'Fix implication order in implementation of `TransactionExtension` for tuple' +doc: +- audience: + - Runtime Dev + - Runtime User + description: |- + Before this PR, the implications were different in the pipeline `(A, B, C)` and `((A, B), C)`. + This PR fixes this behavior and make nested tuple transparant, the implication order of tuple of + tuple is now the same as in a single tuple. + + For runtime users this mean that the implication can be breaking depending on the pipeline used + in the runtime. + + For runtime developers this breaks usage of `TransactionExtension::validate`. + When calling `TransactionExtension::validate` the implication must now implement `Implication` + trait, you can use `TxBaseImplication` to wrap the type and use it as the base implication. + E.g. instead of `&(extension_version, call),` you can write `&TxBaseImplication((extension_version, call))`. + +crates: +- name: sp-runtime + bump: major +- name: pallet-skip-feeless-payment + bump: major +- name: frame-system + bump: major diff --git a/prdoc/stable2412-1/pr_7074.prdoc b/prdoc/stable2412-1/pr_7074.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d49e5f8d831fe9cca6096f85c9c6a33b0279301c --- /dev/null +++ b/prdoc/stable2412-1/pr_7074.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: Unset SKIP_WASM_BUILD=1 for aarch64 binaries release + +doc: + - audience: [ Node Dev, Runtime Dev] + description: + Fix the release pipeline environment by unsetting SKIP_WASM_BUILD=1 + so that aarch64 binaries are built so that they contain runtimes + accordingly. + +crates: [ ] diff --git a/prdoc/stable2412-1/pr_7090.prdoc b/prdoc/stable2412-1/pr_7090.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a665115ce6c72ab2e147538018bbc33ee14820e9 --- /dev/null +++ b/prdoc/stable2412-1/pr_7090.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Snowbridge - Support bridging native ETH + +doc: + - audience: Runtime User + description: + Support Native ETH as an asset type instead of only supporting WETH. WETH is still supported, but adds + support for ETH in the inbound and outbound routers. + +crates: + - name: snowbridge-router-primitives + bump: minor + - name: snowbridge-pallet-inbound-queue-fixtures + bump: minor diff --git a/prdoc/stable2412-1/pr_7099.prdoc b/prdoc/stable2412-1/pr_7099.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..58d809f3c0909b9236381aa0ab34a2f17ba84a40 --- /dev/null +++ b/prdoc/stable2412-1/pr_7099.prdoc @@ -0,0 +1,16 @@ +title: Provide partial results to speedup GetRecord queries + +doc: + - audience: Node Dev + description: | + This PR provides the partial results of the GetRecord kademlia query. + + This significantly improves the authority discovery records, from ~37 minutes to ~2/3 minutes. + In contrast, libp2p discovers authority records in around ~10 minutes. + + The authority discovery was slow because litep2p provided the records only after the Kademlia query was completed. A normal Kademlia query completes in around 40 seconds to a few minutes. + In this PR, partial records are provided as soon as they are discovered from the network. + +crates: + - name: sc-network + bump: patch diff --git a/prdoc/stable2412-1/pr_7116.prdoc b/prdoc/stable2412-1/pr_7116.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..95a5254778a4d3add09d9219f3d170beb4f9f8dd --- /dev/null +++ b/prdoc/stable2412-1/pr_7116.prdoc @@ -0,0 +1,8 @@ +title: Increase the number of pvf execution workers from 2 to 4 +doc: +- audience: Node Dev + description: |- + Increase the number of pvf execution workers from 2 to 4. +crates: +- name: polkadot-service + bump: patch diff --git a/prdoc/stable2412-1/pr_7133.prdoc b/prdoc/stable2412-1/pr_7133.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ca0d2bb0bd483b2e78749e8387f7d21897f91dc0 --- /dev/null +++ b/prdoc/stable2412-1/pr_7133.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: Sufix litep2p to the identify agent version for visibility + +doc: + - audience: [Node Dev, Node Operator] + description: | + This PR adds the `(litep2p)` suffix to the agent version (user agent) of the identify protocol. + The change is needed to gain visibility into network backends and determine exactly the number of validators that are running litep2p. + Using tools like subp2p-explorer, we can determine if the validators are running litep2p nodes. + +crates: +- name: sc-network + bump: patch diff --git a/prdoc/stable2412-1/pr_7158.prdoc b/prdoc/stable2412-1/pr_7158.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..e113a7fdcd1c56ec37227db9282df0acc576e2bb --- /dev/null +++ b/prdoc/stable2412-1/pr_7158.prdoc @@ -0,0 +1,12 @@ +title: Reject litep2p inbound requests from banned peers + +doc: + - audience: Node Dev + description: | + This PR rejects inbound requests from banned peers (reputation is below the banned threshold). + This mirrors the request-response implementation from the libp2p side. + While at it, have registered a new inbound failure metric to have visibility into this. + +crates: +- name: sc-network + bump: patch diff --git a/prdoc/stable2412-1/pr_7205.prdoc b/prdoc/stable2412-1/pr_7205.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..758beb0b6313c764f161c02ba927868a233a419a --- /dev/null +++ b/prdoc/stable2412-1/pr_7205.prdoc @@ -0,0 +1,10 @@ +title: 'Collator: Fix `can_build_upon` by always allowing to build on included block' +doc: +- audience: Node Dev + description: |- + Fixes a bug introduced in #6825. + We should always allow building on the included block of parachains. In situations where the unincluded segment + is full, but the included block moved to the most recent block, building was wrongly disallowed. +crates: +- name: cumulus-client-consensus-aura + bump: minor diff --git a/prdoc/stable2412-1/pr_7222.prdoc b/prdoc/stable2412-1/pr_7222.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..40b89b0a1820d89a38ea12f0a1859cd23aadf4c5 --- /dev/null +++ b/prdoc/stable2412-1/pr_7222.prdoc @@ -0,0 +1,19 @@ +title: Enforce libp2p outbound request-response timeout limits + +doc: + - audience: Node Dev + description: | + This PR enforces that outbound requests are finished within the specified protocol timeout. + The stable2412 version running libp2p 0.52.4 contains a bug which does not track request timeouts properly + https://github.com/libp2p/rust-libp2p/pull/5429. + + The issue has been detected while submitting libp2p to litep2p requests in Kusama. + This aims to check that pending outbound requests have not timed out. + Although the issue has been fixed in libp2p, there might be other cases where this may happen. + For example, https://github.com/libp2p/rust-libp2p/pull/5417. + + For more context see https://github.com/paritytech/polkadot-sdk/issues/7076#issuecomment-2596085096. + +crates: +- name: sc-network + bump: patch diff --git a/prdoc/stable2412-1/pr_7322.prdoc b/prdoc/stable2412-1/pr_7322.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..72c566f7a8146a3ecb52c95168fecf5c98317b8c --- /dev/null +++ b/prdoc/stable2412-1/pr_7322.prdoc @@ -0,0 +1,8 @@ +title: 'Bridges: emulated tests small nits/improvements' +doc: +- audience: Runtime Dev + description: |- + This PR removes the use of `open_bridge_between_asset_hub_rococo_and_asset_hub_westend`. This function was used in the generic `test_dry_run_transfer_across_pk_bridge` macro, which could cause compilation issues when used in other contexts (e.g. fellows repo). +crates: +- name: emulated-integration-tests-common + bump: patch diff --git a/prdoc/stable2412-1/pr_7344.prdoc b/prdoc/stable2412-1/pr_7344.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a3ffa32f125c3729af09ab6037fd0dbb0e28d12d --- /dev/null +++ b/prdoc/stable2412-1/pr_7344.prdoc @@ -0,0 +1,14 @@ +title: '[sync] Let new subscribers know about already connected peers (backward-compatible)' +doc: +- audience: Node Dev + description: Revert https://github.com/paritytech/polkadot-sdk/pull/7011 and replace + it with a backward-compatible solution suitable for backporting to a release branch. +crates: +- name: sc-network-gossip + bump: patch +- name: sc-network-statement + bump: patch +- name: sc-network-sync + bump: patch +- name: sc-network-transactions + bump: patch diff --git a/prdoc/pr_3151.prdoc b/prdoc/stable2412/pr_3151.prdoc similarity index 100% rename from prdoc/pr_3151.prdoc rename to prdoc/stable2412/pr_3151.prdoc diff --git a/prdoc/pr_3685.prdoc b/prdoc/stable2412/pr_3685.prdoc similarity index 100% rename from prdoc/pr_3685.prdoc rename to prdoc/stable2412/pr_3685.prdoc diff --git a/prdoc/pr_3881.prdoc b/prdoc/stable2412/pr_3881.prdoc similarity index 100% rename from prdoc/pr_3881.prdoc rename to prdoc/stable2412/pr_3881.prdoc diff --git a/prdoc/pr_3970.prdoc b/prdoc/stable2412/pr_3970.prdoc similarity index 100% rename from prdoc/pr_3970.prdoc rename to prdoc/stable2412/pr_3970.prdoc diff --git a/prdoc/pr_4012.prdoc b/prdoc/stable2412/pr_4012.prdoc similarity index 100% rename from prdoc/pr_4012.prdoc rename to prdoc/stable2412/pr_4012.prdoc diff --git a/prdoc/pr_4251.prdoc b/prdoc/stable2412/pr_4251.prdoc similarity index 100% rename from prdoc/pr_4251.prdoc rename to prdoc/stable2412/pr_4251.prdoc diff --git a/prdoc/pr_4257.prdoc b/prdoc/stable2412/pr_4257.prdoc similarity index 100% rename from prdoc/pr_4257.prdoc rename to prdoc/stable2412/pr_4257.prdoc diff --git a/prdoc/pr_4639.prdoc b/prdoc/stable2412/pr_4639.prdoc similarity index 100% rename from prdoc/pr_4639.prdoc rename to prdoc/stable2412/pr_4639.prdoc diff --git a/prdoc/pr_4826.prdoc b/prdoc/stable2412/pr_4826.prdoc similarity index 100% rename from prdoc/pr_4826.prdoc rename to prdoc/stable2412/pr_4826.prdoc diff --git a/prdoc/stable2412/pr_4834.prdoc b/prdoc/stable2412/pr_4834.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b7c8b15cb073d16e281fd75ccced09868df05112 --- /dev/null +++ b/prdoc/stable2412/pr_4834.prdoc @@ -0,0 +1,15 @@ +title: "xcm-executor: take delivery fee from transferred assets if necessary" + +doc: + - audience: Runtime Dev + description: | + In asset transfers, as a last resort, XCM delivery fees are taken from + transferred assets rather than failing the transfer. + +crates: + - name: staging-xcm-executor + bump: patch + - name: snowbridge-router-primitives + bump: patch + - name: snowbridge-pallet-inbound-queue + bump: patch diff --git a/prdoc/pr_4837.prdoc b/prdoc/stable2412/pr_4837.prdoc similarity index 100% rename from prdoc/pr_4837.prdoc rename to prdoc/stable2412/pr_4837.prdoc diff --git a/prdoc/pr_4846.prdoc b/prdoc/stable2412/pr_4846.prdoc similarity index 100% rename from prdoc/pr_4846.prdoc rename to prdoc/stable2412/pr_4846.prdoc diff --git a/prdoc/pr_4849.prdoc b/prdoc/stable2412/pr_4849.prdoc similarity index 100% rename from prdoc/pr_4849.prdoc rename to prdoc/stable2412/pr_4849.prdoc diff --git a/prdoc/pr_4851.prdoc b/prdoc/stable2412/pr_4851.prdoc similarity index 100% rename from prdoc/pr_4851.prdoc rename to prdoc/stable2412/pr_4851.prdoc diff --git a/prdoc/pr_4889.prdoc b/prdoc/stable2412/pr_4889.prdoc similarity index 100% rename from prdoc/pr_4889.prdoc rename to prdoc/stable2412/pr_4889.prdoc diff --git a/prdoc/pr_4974.prdoc b/prdoc/stable2412/pr_4974.prdoc similarity index 100% rename from prdoc/pr_4974.prdoc rename to prdoc/stable2412/pr_4974.prdoc diff --git a/prdoc/pr_4982.prdoc b/prdoc/stable2412/pr_4982.prdoc similarity index 100% rename from prdoc/pr_4982.prdoc rename to prdoc/stable2412/pr_4982.prdoc diff --git a/prdoc/pr_5038.prdoc b/prdoc/stable2412/pr_5038.prdoc similarity index 100% rename from prdoc/pr_5038.prdoc rename to prdoc/stable2412/pr_5038.prdoc diff --git a/prdoc/pr_5194.prdoc b/prdoc/stable2412/pr_5194.prdoc similarity index 100% rename from prdoc/pr_5194.prdoc rename to prdoc/stable2412/pr_5194.prdoc diff --git a/prdoc/pr_5198.prdoc b/prdoc/stable2412/pr_5198.prdoc similarity index 100% rename from prdoc/pr_5198.prdoc rename to prdoc/stable2412/pr_5198.prdoc diff --git a/prdoc/pr_5201.prdoc b/prdoc/stable2412/pr_5201.prdoc similarity index 100% rename from prdoc/pr_5201.prdoc rename to prdoc/stable2412/pr_5201.prdoc diff --git a/prdoc/pr_5274.prdoc b/prdoc/stable2412/pr_5274.prdoc similarity index 100% rename from prdoc/pr_5274.prdoc rename to prdoc/stable2412/pr_5274.prdoc diff --git a/prdoc/pr_5311.prdoc b/prdoc/stable2412/pr_5311.prdoc similarity index 100% rename from prdoc/pr_5311.prdoc rename to prdoc/stable2412/pr_5311.prdoc diff --git a/prdoc/pr_5322.prdoc b/prdoc/stable2412/pr_5322.prdoc similarity index 100% rename from prdoc/pr_5322.prdoc rename to prdoc/stable2412/pr_5322.prdoc diff --git a/prdoc/pr_5343.prdoc b/prdoc/stable2412/pr_5343.prdoc similarity index 100% rename from prdoc/pr_5343.prdoc rename to prdoc/stable2412/pr_5343.prdoc diff --git a/prdoc/pr_5372.prdoc b/prdoc/stable2412/pr_5372.prdoc similarity index 100% rename from prdoc/pr_5372.prdoc rename to prdoc/stable2412/pr_5372.prdoc diff --git a/prdoc/pr_5390.prdoc b/prdoc/stable2412/pr_5390.prdoc similarity index 100% rename from prdoc/pr_5390.prdoc rename to prdoc/stable2412/pr_5390.prdoc diff --git a/prdoc/pr_5420.prdoc b/prdoc/stable2412/pr_5420.prdoc similarity index 100% rename from prdoc/pr_5420.prdoc rename to prdoc/stable2412/pr_5420.prdoc diff --git a/prdoc/pr_5423.prdoc b/prdoc/stable2412/pr_5423.prdoc similarity index 100% rename from prdoc/pr_5423.prdoc rename to prdoc/stable2412/pr_5423.prdoc diff --git a/prdoc/pr_5435.prdoc b/prdoc/stable2412/pr_5435.prdoc similarity index 100% rename from prdoc/pr_5435.prdoc rename to prdoc/stable2412/pr_5435.prdoc diff --git a/prdoc/pr_5461.prdoc b/prdoc/stable2412/pr_5461.prdoc similarity index 100% rename from prdoc/pr_5461.prdoc rename to prdoc/stable2412/pr_5461.prdoc diff --git a/prdoc/pr_5469.prdoc b/prdoc/stable2412/pr_5469.prdoc similarity index 100% rename from prdoc/pr_5469.prdoc rename to prdoc/stable2412/pr_5469.prdoc diff --git a/prdoc/pr_5502.prdoc b/prdoc/stable2412/pr_5502.prdoc similarity index 100% rename from prdoc/pr_5502.prdoc rename to prdoc/stable2412/pr_5502.prdoc diff --git a/prdoc/pr_5515.prdoc b/prdoc/stable2412/pr_5515.prdoc similarity index 100% rename from prdoc/pr_5515.prdoc rename to prdoc/stable2412/pr_5515.prdoc diff --git a/prdoc/pr_5521.prdoc b/prdoc/stable2412/pr_5521.prdoc similarity index 100% rename from prdoc/pr_5521.prdoc rename to prdoc/stable2412/pr_5521.prdoc diff --git a/prdoc/pr_5526.prdoc b/prdoc/stable2412/pr_5526.prdoc similarity index 100% rename from prdoc/pr_5526.prdoc rename to prdoc/stable2412/pr_5526.prdoc diff --git a/prdoc/pr_5540.prdoc b/prdoc/stable2412/pr_5540.prdoc similarity index 100% rename from prdoc/pr_5540.prdoc rename to prdoc/stable2412/pr_5540.prdoc diff --git a/prdoc/pr_5548.prdoc b/prdoc/stable2412/pr_5548.prdoc similarity index 100% rename from prdoc/pr_5548.prdoc rename to prdoc/stable2412/pr_5548.prdoc diff --git a/prdoc/pr_5554.prdoc b/prdoc/stable2412/pr_5554.prdoc similarity index 100% rename from prdoc/pr_5554.prdoc rename to prdoc/stable2412/pr_5554.prdoc diff --git a/prdoc/pr_5555.prdoc b/prdoc/stable2412/pr_5555.prdoc similarity index 100% rename from prdoc/pr_5555.prdoc rename to prdoc/stable2412/pr_5555.prdoc diff --git a/prdoc/pr_5556.prdoc b/prdoc/stable2412/pr_5556.prdoc similarity index 100% rename from prdoc/pr_5556.prdoc rename to prdoc/stable2412/pr_5556.prdoc diff --git a/prdoc/pr_5572.prdoc b/prdoc/stable2412/pr_5572.prdoc similarity index 100% rename from prdoc/pr_5572.prdoc rename to prdoc/stable2412/pr_5572.prdoc diff --git a/prdoc/pr_5585.prdoc b/prdoc/stable2412/pr_5585.prdoc similarity index 100% rename from prdoc/pr_5585.prdoc rename to prdoc/stable2412/pr_5585.prdoc diff --git a/prdoc/pr_5592.prdoc b/prdoc/stable2412/pr_5592.prdoc similarity index 100% rename from prdoc/pr_5592.prdoc rename to prdoc/stable2412/pr_5592.prdoc diff --git a/prdoc/pr_5601.prdoc b/prdoc/stable2412/pr_5601.prdoc similarity index 100% rename from prdoc/pr_5601.prdoc rename to prdoc/stable2412/pr_5601.prdoc diff --git a/prdoc/pr_5606.prdoc b/prdoc/stable2412/pr_5606.prdoc similarity index 100% rename from prdoc/pr_5606.prdoc rename to prdoc/stable2412/pr_5606.prdoc diff --git a/prdoc/pr_5608.prdoc b/prdoc/stable2412/pr_5608.prdoc similarity index 100% rename from prdoc/pr_5608.prdoc rename to prdoc/stable2412/pr_5608.prdoc diff --git a/prdoc/pr_5609.prdoc b/prdoc/stable2412/pr_5609.prdoc similarity index 100% rename from prdoc/pr_5609.prdoc rename to prdoc/stable2412/pr_5609.prdoc diff --git a/prdoc/pr_5616.prdoc b/prdoc/stable2412/pr_5616.prdoc similarity index 100% rename from prdoc/pr_5616.prdoc rename to prdoc/stable2412/pr_5616.prdoc diff --git a/prdoc/pr_5623.prdoc b/prdoc/stable2412/pr_5623.prdoc similarity index 100% rename from prdoc/pr_5623.prdoc rename to prdoc/stable2412/pr_5623.prdoc diff --git a/prdoc/pr_5630.prdoc b/prdoc/stable2412/pr_5630.prdoc similarity index 100% rename from prdoc/pr_5630.prdoc rename to prdoc/stable2412/pr_5630.prdoc diff --git a/prdoc/pr_5635.prdoc b/prdoc/stable2412/pr_5635.prdoc similarity index 100% rename from prdoc/pr_5635.prdoc rename to prdoc/stable2412/pr_5635.prdoc diff --git a/prdoc/pr_5640.prdoc b/prdoc/stable2412/pr_5640.prdoc similarity index 100% rename from prdoc/pr_5640.prdoc rename to prdoc/stable2412/pr_5640.prdoc diff --git a/prdoc/pr_5664.prdoc b/prdoc/stable2412/pr_5664.prdoc similarity index 100% rename from prdoc/pr_5664.prdoc rename to prdoc/stable2412/pr_5664.prdoc diff --git a/prdoc/pr_5665.prdoc b/prdoc/stable2412/pr_5665.prdoc similarity index 100% rename from prdoc/pr_5665.prdoc rename to prdoc/stable2412/pr_5665.prdoc diff --git a/prdoc/pr_5666.prdoc b/prdoc/stable2412/pr_5666.prdoc similarity index 100% rename from prdoc/pr_5666.prdoc rename to prdoc/stable2412/pr_5666.prdoc diff --git a/prdoc/pr_5675.prdoc b/prdoc/stable2412/pr_5675.prdoc similarity index 100% rename from prdoc/pr_5675.prdoc rename to prdoc/stable2412/pr_5675.prdoc diff --git a/prdoc/pr_5676.prdoc b/prdoc/stable2412/pr_5676.prdoc similarity index 100% rename from prdoc/pr_5676.prdoc rename to prdoc/stable2412/pr_5676.prdoc diff --git a/prdoc/pr_5679.prdoc b/prdoc/stable2412/pr_5679.prdoc similarity index 100% rename from prdoc/pr_5679.prdoc rename to prdoc/stable2412/pr_5679.prdoc diff --git a/prdoc/pr_5682.prdoc b/prdoc/stable2412/pr_5682.prdoc similarity index 100% rename from prdoc/pr_5682.prdoc rename to prdoc/stable2412/pr_5682.prdoc diff --git a/prdoc/pr_5684.prdoc b/prdoc/stable2412/pr_5684.prdoc similarity index 100% rename from prdoc/pr_5684.prdoc rename to prdoc/stable2412/pr_5684.prdoc diff --git a/prdoc/pr_5686.prdoc b/prdoc/stable2412/pr_5686.prdoc similarity index 100% rename from prdoc/pr_5686.prdoc rename to prdoc/stable2412/pr_5686.prdoc diff --git a/prdoc/pr_5687.prdoc b/prdoc/stable2412/pr_5687.prdoc similarity index 100% rename from prdoc/pr_5687.prdoc rename to prdoc/stable2412/pr_5687.prdoc diff --git a/prdoc/pr_5693.prdoc b/prdoc/stable2412/pr_5693.prdoc similarity index 100% rename from prdoc/pr_5693.prdoc rename to prdoc/stable2412/pr_5693.prdoc diff --git a/prdoc/pr_5701.prdoc b/prdoc/stable2412/pr_5701.prdoc similarity index 100% rename from prdoc/pr_5701.prdoc rename to prdoc/stable2412/pr_5701.prdoc diff --git a/prdoc/pr_5707.prdoc b/prdoc/stable2412/pr_5707.prdoc similarity index 100% rename from prdoc/pr_5707.prdoc rename to prdoc/stable2412/pr_5707.prdoc diff --git a/prdoc/pr_5716.prdoc b/prdoc/stable2412/pr_5716.prdoc similarity index 100% rename from prdoc/pr_5716.prdoc rename to prdoc/stable2412/pr_5716.prdoc diff --git a/prdoc/pr_5726.prdoc b/prdoc/stable2412/pr_5726.prdoc similarity index 100% rename from prdoc/pr_5726.prdoc rename to prdoc/stable2412/pr_5726.prdoc diff --git a/prdoc/stable2412/pr_5732.prdoc b/prdoc/stable2412/pr_5732.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..6f3f9b8a1668be712ad527b77829053479791da0 --- /dev/null +++ b/prdoc/stable2412/pr_5732.prdoc @@ -0,0 +1,29 @@ +title: Expose the unstable metadata v16 +doc: +- audience: Node Dev + description: | + This PR exposes the *unstable* metadata V16. The metadata is exposed under the unstable u32::MAX number. + Developers can start experimenting with the new features of the metadata v16. *Please note that this metadata is under development and expect breaking changes until stabilization.* + The `ExtrinsicMetadata` trait receives a breaking change. Its associated type `VERSION` is rename to `VERSIONS` and now supports a constant static list of metadata versions. + The versions implemented for `UncheckedExtrinsic` are v4 (legacy version) and v5 (new version). + For metadata collection, it is assumed that all `TransactionExtensions` are under version 0. + +crates: + - name: sp-metadata-ir + bump: major + - name: frame-support-procedural + bump: patch + - name: frame-support + bump: minor + - name: frame-support-test + bump: major + - name: frame-metadata-hash-extension + bump: patch + - name: substrate-wasm-builder + bump: minor + - name: pallet-revive + bump: minor + - name: sp-runtime + bump: major + - name: frame-benchmarking-cli + bump: patch diff --git a/prdoc/pr_5737.prdoc b/prdoc/stable2412/pr_5737.prdoc similarity index 100% rename from prdoc/pr_5737.prdoc rename to prdoc/stable2412/pr_5737.prdoc diff --git a/prdoc/pr_5741.prdoc b/prdoc/stable2412/pr_5741.prdoc similarity index 100% rename from prdoc/pr_5741.prdoc rename to prdoc/stable2412/pr_5741.prdoc diff --git a/prdoc/pr_5743.prdoc b/prdoc/stable2412/pr_5743.prdoc similarity index 100% rename from prdoc/pr_5743.prdoc rename to prdoc/stable2412/pr_5743.prdoc diff --git a/prdoc/pr_5745.prdoc b/prdoc/stable2412/pr_5745.prdoc similarity index 100% rename from prdoc/pr_5745.prdoc rename to prdoc/stable2412/pr_5745.prdoc diff --git a/prdoc/pr_5756.prdoc b/prdoc/stable2412/pr_5756.prdoc similarity index 100% rename from prdoc/pr_5756.prdoc rename to prdoc/stable2412/pr_5756.prdoc diff --git a/prdoc/pr_5762.prdoc b/prdoc/stable2412/pr_5762.prdoc similarity index 100% rename from prdoc/pr_5762.prdoc rename to prdoc/stable2412/pr_5762.prdoc diff --git a/prdoc/pr_5765.prdoc b/prdoc/stable2412/pr_5765.prdoc similarity index 100% rename from prdoc/pr_5765.prdoc rename to prdoc/stable2412/pr_5765.prdoc diff --git a/prdoc/pr_5768.prdoc b/prdoc/stable2412/pr_5768.prdoc similarity index 100% rename from prdoc/pr_5768.prdoc rename to prdoc/stable2412/pr_5768.prdoc diff --git a/prdoc/pr_5774.prdoc b/prdoc/stable2412/pr_5774.prdoc similarity index 100% rename from prdoc/pr_5774.prdoc rename to prdoc/stable2412/pr_5774.prdoc diff --git a/prdoc/pr_5779.prdoc b/prdoc/stable2412/pr_5779.prdoc similarity index 100% rename from prdoc/pr_5779.prdoc rename to prdoc/stable2412/pr_5779.prdoc diff --git a/prdoc/pr_5787.prdoc b/prdoc/stable2412/pr_5787.prdoc similarity index 100% rename from prdoc/pr_5787.prdoc rename to prdoc/stable2412/pr_5787.prdoc diff --git a/prdoc/pr_5789.prdoc b/prdoc/stable2412/pr_5789.prdoc similarity index 100% rename from prdoc/pr_5789.prdoc rename to prdoc/stable2412/pr_5789.prdoc diff --git a/prdoc/pr_5796.prdoc b/prdoc/stable2412/pr_5796.prdoc similarity index 100% rename from prdoc/pr_5796.prdoc rename to prdoc/stable2412/pr_5796.prdoc diff --git a/prdoc/pr_5804.prdoc b/prdoc/stable2412/pr_5804.prdoc similarity index 100% rename from prdoc/pr_5804.prdoc rename to prdoc/stable2412/pr_5804.prdoc diff --git a/prdoc/pr_5807.prdoc b/prdoc/stable2412/pr_5807.prdoc similarity index 100% rename from prdoc/pr_5807.prdoc rename to prdoc/stable2412/pr_5807.prdoc diff --git a/prdoc/pr_5811.prdoc b/prdoc/stable2412/pr_5811.prdoc similarity index 100% rename from prdoc/pr_5811.prdoc rename to prdoc/stable2412/pr_5811.prdoc diff --git a/prdoc/pr_5813.prdoc b/prdoc/stable2412/pr_5813.prdoc similarity index 100% rename from prdoc/pr_5813.prdoc rename to prdoc/stable2412/pr_5813.prdoc diff --git a/prdoc/pr_5824.prdoc b/prdoc/stable2412/pr_5824.prdoc similarity index 100% rename from prdoc/pr_5824.prdoc rename to prdoc/stable2412/pr_5824.prdoc diff --git a/prdoc/pr_5830.prdoc b/prdoc/stable2412/pr_5830.prdoc similarity index 100% rename from prdoc/pr_5830.prdoc rename to prdoc/stable2412/pr_5830.prdoc diff --git a/prdoc/pr_5838.prdoc b/prdoc/stable2412/pr_5838.prdoc similarity index 100% rename from prdoc/pr_5838.prdoc rename to prdoc/stable2412/pr_5838.prdoc diff --git a/prdoc/pr_5839.prdoc b/prdoc/stable2412/pr_5839.prdoc similarity index 100% rename from prdoc/pr_5839.prdoc rename to prdoc/stable2412/pr_5839.prdoc diff --git a/prdoc/pr_5845.prdoc b/prdoc/stable2412/pr_5845.prdoc similarity index 100% rename from prdoc/pr_5845.prdoc rename to prdoc/stable2412/pr_5845.prdoc diff --git a/prdoc/pr_5847.prdoc b/prdoc/stable2412/pr_5847.prdoc similarity index 100% rename from prdoc/pr_5847.prdoc rename to prdoc/stable2412/pr_5847.prdoc diff --git a/prdoc/pr_5856.prdoc b/prdoc/stable2412/pr_5856.prdoc similarity index 100% rename from prdoc/pr_5856.prdoc rename to prdoc/stable2412/pr_5856.prdoc diff --git a/prdoc/pr_5857.prdoc b/prdoc/stable2412/pr_5857.prdoc similarity index 100% rename from prdoc/pr_5857.prdoc rename to prdoc/stable2412/pr_5857.prdoc diff --git a/prdoc/pr_5859.prdoc b/prdoc/stable2412/pr_5859.prdoc similarity index 100% rename from prdoc/pr_5859.prdoc rename to prdoc/stable2412/pr_5859.prdoc diff --git a/prdoc/pr_5861.prdoc b/prdoc/stable2412/pr_5861.prdoc similarity index 100% rename from prdoc/pr_5861.prdoc rename to prdoc/stable2412/pr_5861.prdoc diff --git a/prdoc/pr_5866.prdoc b/prdoc/stable2412/pr_5866.prdoc similarity index 100% rename from prdoc/pr_5866.prdoc rename to prdoc/stable2412/pr_5866.prdoc diff --git a/prdoc/pr_5872.prdoc b/prdoc/stable2412/pr_5872.prdoc similarity index 100% rename from prdoc/pr_5872.prdoc rename to prdoc/stable2412/pr_5872.prdoc diff --git a/prdoc/pr_5875.prdoc b/prdoc/stable2412/pr_5875.prdoc similarity index 100% rename from prdoc/pr_5875.prdoc rename to prdoc/stable2412/pr_5875.prdoc diff --git a/prdoc/pr_5876.prdoc b/prdoc/stable2412/pr_5876.prdoc similarity index 100% rename from prdoc/pr_5876.prdoc rename to prdoc/stable2412/pr_5876.prdoc diff --git a/prdoc/pr_5880.prdoc b/prdoc/stable2412/pr_5880.prdoc similarity index 100% rename from prdoc/pr_5880.prdoc rename to prdoc/stable2412/pr_5880.prdoc diff --git a/prdoc/pr_5883.prdoc b/prdoc/stable2412/pr_5883.prdoc similarity index 100% rename from prdoc/pr_5883.prdoc rename to prdoc/stable2412/pr_5883.prdoc diff --git a/prdoc/pr_5886.prdoc b/prdoc/stable2412/pr_5886.prdoc similarity index 100% rename from prdoc/pr_5886.prdoc rename to prdoc/stable2412/pr_5886.prdoc diff --git a/prdoc/pr_5888.prdoc b/prdoc/stable2412/pr_5888.prdoc similarity index 100% rename from prdoc/pr_5888.prdoc rename to prdoc/stable2412/pr_5888.prdoc diff --git a/prdoc/pr_5891.prdoc b/prdoc/stable2412/pr_5891.prdoc similarity index 100% rename from prdoc/pr_5891.prdoc rename to prdoc/stable2412/pr_5891.prdoc diff --git a/prdoc/pr_5892.prdoc b/prdoc/stable2412/pr_5892.prdoc similarity index 100% rename from prdoc/pr_5892.prdoc rename to prdoc/stable2412/pr_5892.prdoc diff --git a/prdoc/pr_5901.prdoc b/prdoc/stable2412/pr_5901.prdoc similarity index 100% rename from prdoc/pr_5901.prdoc rename to prdoc/stable2412/pr_5901.prdoc diff --git a/prdoc/pr_5908.prdoc b/prdoc/stable2412/pr_5908.prdoc similarity index 100% rename from prdoc/pr_5908.prdoc rename to prdoc/stable2412/pr_5908.prdoc diff --git a/prdoc/pr_5911.prdoc b/prdoc/stable2412/pr_5911.prdoc similarity index 100% rename from prdoc/pr_5911.prdoc rename to prdoc/stable2412/pr_5911.prdoc diff --git a/prdoc/pr_5915.prdoc b/prdoc/stable2412/pr_5915.prdoc similarity index 100% rename from prdoc/pr_5915.prdoc rename to prdoc/stable2412/pr_5915.prdoc diff --git a/prdoc/pr_5917.prdoc b/prdoc/stable2412/pr_5917.prdoc similarity index 100% rename from prdoc/pr_5917.prdoc rename to prdoc/stable2412/pr_5917.prdoc diff --git a/prdoc/pr_5919.prdoc b/prdoc/stable2412/pr_5919.prdoc similarity index 100% rename from prdoc/pr_5919.prdoc rename to prdoc/stable2412/pr_5919.prdoc diff --git a/prdoc/pr_5924.prdoc b/prdoc/stable2412/pr_5924.prdoc similarity index 100% rename from prdoc/pr_5924.prdoc rename to prdoc/stable2412/pr_5924.prdoc diff --git a/prdoc/pr_5939.prdoc b/prdoc/stable2412/pr_5939.prdoc similarity index 100% rename from prdoc/pr_5939.prdoc rename to prdoc/stable2412/pr_5939.prdoc diff --git a/prdoc/pr_5941.prdoc b/prdoc/stable2412/pr_5941.prdoc similarity index 100% rename from prdoc/pr_5941.prdoc rename to prdoc/stable2412/pr_5941.prdoc diff --git a/prdoc/pr_5946.prdoc b/prdoc/stable2412/pr_5946.prdoc similarity index 100% rename from prdoc/pr_5946.prdoc rename to prdoc/stable2412/pr_5946.prdoc diff --git a/prdoc/pr_5954.prdoc b/prdoc/stable2412/pr_5954.prdoc similarity index 100% rename from prdoc/pr_5954.prdoc rename to prdoc/stable2412/pr_5954.prdoc diff --git a/prdoc/pr_5961.prdoc b/prdoc/stable2412/pr_5961.prdoc similarity index 100% rename from prdoc/pr_5961.prdoc rename to prdoc/stable2412/pr_5961.prdoc diff --git a/prdoc/pr_5971.prdoc b/prdoc/stable2412/pr_5971.prdoc similarity index 100% rename from prdoc/pr_5971.prdoc rename to prdoc/stable2412/pr_5971.prdoc diff --git a/prdoc/pr_5984.prdoc b/prdoc/stable2412/pr_5984.prdoc similarity index 100% rename from prdoc/pr_5984.prdoc rename to prdoc/stable2412/pr_5984.prdoc diff --git a/prdoc/pr_5994.prdoc b/prdoc/stable2412/pr_5994.prdoc similarity index 100% rename from prdoc/pr_5994.prdoc rename to prdoc/stable2412/pr_5994.prdoc diff --git a/prdoc/pr_5995.prdoc b/prdoc/stable2412/pr_5995.prdoc similarity index 100% rename from prdoc/pr_5995.prdoc rename to prdoc/stable2412/pr_5995.prdoc diff --git a/prdoc/stable2412/pr_5997.prdoc b/prdoc/stable2412/pr_5997.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..6bac36a44586b8d55d8ae26738ab36100312088b --- /dev/null +++ b/prdoc/stable2412/pr_5997.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: Implement archive_unstable_storageDiff method + +doc: + - audience: Node Dev + description: | + This PR implements the `archive_unstable_storageDiff` rpc-v2 method. + Developers can use this method to fetch the storage differences + between two blocks. This is useful for oracles and archive nodes. + For more details see: https://github.com/paritytech/json-rpc-interface-spec/blob/main/src/api/archive_unstable_storageDiff.md. + +crates: + - name: sc-rpc-spec-v2 + bump: major + - name: sc-service + bump: patch diff --git a/prdoc/pr_5998.prdoc b/prdoc/stable2412/pr_5998.prdoc similarity index 100% rename from prdoc/pr_5998.prdoc rename to prdoc/stable2412/pr_5998.prdoc diff --git a/prdoc/pr_5999.prdoc b/prdoc/stable2412/pr_5999.prdoc similarity index 100% rename from prdoc/pr_5999.prdoc rename to prdoc/stable2412/pr_5999.prdoc diff --git a/prdoc/pr_6011.prdoc b/prdoc/stable2412/pr_6011.prdoc similarity index 100% rename from prdoc/pr_6011.prdoc rename to prdoc/stable2412/pr_6011.prdoc diff --git a/prdoc/pr_6015.prdoc b/prdoc/stable2412/pr_6015.prdoc similarity index 100% rename from prdoc/pr_6015.prdoc rename to prdoc/stable2412/pr_6015.prdoc diff --git a/prdoc/pr_6016.prdoc b/prdoc/stable2412/pr_6016.prdoc similarity index 100% rename from prdoc/pr_6016.prdoc rename to prdoc/stable2412/pr_6016.prdoc diff --git a/prdoc/pr_6022.prdoc b/prdoc/stable2412/pr_6022.prdoc similarity index 100% rename from prdoc/pr_6022.prdoc rename to prdoc/stable2412/pr_6022.prdoc diff --git a/prdoc/pr_6023.prdoc b/prdoc/stable2412/pr_6023.prdoc similarity index 100% rename from prdoc/pr_6023.prdoc rename to prdoc/stable2412/pr_6023.prdoc diff --git a/prdoc/pr_6025.prdoc b/prdoc/stable2412/pr_6025.prdoc similarity index 100% rename from prdoc/pr_6025.prdoc rename to prdoc/stable2412/pr_6025.prdoc diff --git a/prdoc/pr_6027.prdoc b/prdoc/stable2412/pr_6027.prdoc similarity index 100% rename from prdoc/pr_6027.prdoc rename to prdoc/stable2412/pr_6027.prdoc diff --git a/prdoc/pr_6032.prdoc b/prdoc/stable2412/pr_6032.prdoc similarity index 100% rename from prdoc/pr_6032.prdoc rename to prdoc/stable2412/pr_6032.prdoc diff --git a/prdoc/pr_6039.prdoc b/prdoc/stable2412/pr_6039.prdoc similarity index 100% rename from prdoc/pr_6039.prdoc rename to prdoc/stable2412/pr_6039.prdoc diff --git a/prdoc/pr_6045.prdoc b/prdoc/stable2412/pr_6045.prdoc similarity index 100% rename from prdoc/pr_6045.prdoc rename to prdoc/stable2412/pr_6045.prdoc diff --git a/prdoc/pr_6058.prdoc b/prdoc/stable2412/pr_6058.prdoc similarity index 100% rename from prdoc/pr_6058.prdoc rename to prdoc/stable2412/pr_6058.prdoc diff --git a/prdoc/pr_6061.prdoc b/prdoc/stable2412/pr_6061.prdoc similarity index 100% rename from prdoc/pr_6061.prdoc rename to prdoc/stable2412/pr_6061.prdoc diff --git a/prdoc/pr_6073.prdoc b/prdoc/stable2412/pr_6073.prdoc similarity index 100% rename from prdoc/pr_6073.prdoc rename to prdoc/stable2412/pr_6073.prdoc diff --git a/prdoc/pr_6077.prdoc b/prdoc/stable2412/pr_6077.prdoc similarity index 100% rename from prdoc/pr_6077.prdoc rename to prdoc/stable2412/pr_6077.prdoc diff --git a/prdoc/pr_6080.prdoc b/prdoc/stable2412/pr_6080.prdoc similarity index 100% rename from prdoc/pr_6080.prdoc rename to prdoc/stable2412/pr_6080.prdoc diff --git a/prdoc/pr_6087.prdoc b/prdoc/stable2412/pr_6087.prdoc similarity index 100% rename from prdoc/pr_6087.prdoc rename to prdoc/stable2412/pr_6087.prdoc diff --git a/prdoc/pr_6088.prdoc b/prdoc/stable2412/pr_6088.prdoc similarity index 100% rename from prdoc/pr_6088.prdoc rename to prdoc/stable2412/pr_6088.prdoc diff --git a/prdoc/pr_6094.prdoc b/prdoc/stable2412/pr_6094.prdoc similarity index 100% rename from prdoc/pr_6094.prdoc rename to prdoc/stable2412/pr_6094.prdoc diff --git a/prdoc/pr_6096.prdoc b/prdoc/stable2412/pr_6096.prdoc similarity index 100% rename from prdoc/pr_6096.prdoc rename to prdoc/stable2412/pr_6096.prdoc diff --git a/prdoc/pr_6104.prdoc b/prdoc/stable2412/pr_6104.prdoc similarity index 100% rename from prdoc/pr_6104.prdoc rename to prdoc/stable2412/pr_6104.prdoc diff --git a/prdoc/pr_6105.prdoc b/prdoc/stable2412/pr_6105.prdoc similarity index 100% rename from prdoc/pr_6105.prdoc rename to prdoc/stable2412/pr_6105.prdoc diff --git a/prdoc/pr_6129.prdoc b/prdoc/stable2412/pr_6129.prdoc similarity index 100% rename from prdoc/pr_6129.prdoc rename to prdoc/stable2412/pr_6129.prdoc diff --git a/prdoc/pr_6141.prdoc b/prdoc/stable2412/pr_6141.prdoc similarity index 100% rename from prdoc/pr_6141.prdoc rename to prdoc/stable2412/pr_6141.prdoc diff --git a/prdoc/pr_6147.prdoc b/prdoc/stable2412/pr_6147.prdoc similarity index 100% rename from prdoc/pr_6147.prdoc rename to prdoc/stable2412/pr_6147.prdoc diff --git a/prdoc/pr_6148.prdoc b/prdoc/stable2412/pr_6148.prdoc similarity index 100% rename from prdoc/pr_6148.prdoc rename to prdoc/stable2412/pr_6148.prdoc diff --git a/prdoc/pr_6156.prdoc b/prdoc/stable2412/pr_6156.prdoc similarity index 100% rename from prdoc/pr_6156.prdoc rename to prdoc/stable2412/pr_6156.prdoc diff --git a/prdoc/pr_6169.prdoc b/prdoc/stable2412/pr_6169.prdoc similarity index 100% rename from prdoc/pr_6169.prdoc rename to prdoc/stable2412/pr_6169.prdoc diff --git a/prdoc/pr_6171.prdoc b/prdoc/stable2412/pr_6171.prdoc similarity index 100% rename from prdoc/pr_6171.prdoc rename to prdoc/stable2412/pr_6171.prdoc diff --git a/prdoc/pr_6174.prdoc b/prdoc/stable2412/pr_6174.prdoc similarity index 100% rename from prdoc/pr_6174.prdoc rename to prdoc/stable2412/pr_6174.prdoc diff --git a/prdoc/pr_6187.prdoc b/prdoc/stable2412/pr_6187.prdoc similarity index 100% rename from prdoc/pr_6187.prdoc rename to prdoc/stable2412/pr_6187.prdoc diff --git a/prdoc/pr_6192.prdoc b/prdoc/stable2412/pr_6192.prdoc similarity index 100% rename from prdoc/pr_6192.prdoc rename to prdoc/stable2412/pr_6192.prdoc diff --git a/prdoc/pr_6205.prdoc b/prdoc/stable2412/pr_6205.prdoc similarity index 100% rename from prdoc/pr_6205.prdoc rename to prdoc/stable2412/pr_6205.prdoc diff --git a/prdoc/pr_6212.prdoc b/prdoc/stable2412/pr_6212.prdoc similarity index 100% rename from prdoc/pr_6212.prdoc rename to prdoc/stable2412/pr_6212.prdoc diff --git a/prdoc/pr_6214.prdoc b/prdoc/stable2412/pr_6214.prdoc similarity index 100% rename from prdoc/pr_6214.prdoc rename to prdoc/stable2412/pr_6214.prdoc diff --git a/prdoc/pr_6217.prdoc b/prdoc/stable2412/pr_6217.prdoc similarity index 100% rename from prdoc/pr_6217.prdoc rename to prdoc/stable2412/pr_6217.prdoc diff --git a/prdoc/pr_6218.prdoc b/prdoc/stable2412/pr_6218.prdoc similarity index 100% rename from prdoc/pr_6218.prdoc rename to prdoc/stable2412/pr_6218.prdoc diff --git a/prdoc/pr_6221.prdoc b/prdoc/stable2412/pr_6221.prdoc similarity index 100% rename from prdoc/pr_6221.prdoc rename to prdoc/stable2412/pr_6221.prdoc diff --git a/prdoc/pr_6228.prdoc b/prdoc/stable2412/pr_6228.prdoc similarity index 100% rename from prdoc/pr_6228.prdoc rename to prdoc/stable2412/pr_6228.prdoc diff --git a/prdoc/pr_6246.prdoc b/prdoc/stable2412/pr_6246.prdoc similarity index 100% rename from prdoc/pr_6246.prdoc rename to prdoc/stable2412/pr_6246.prdoc diff --git a/prdoc/pr_6255.prdoc b/prdoc/stable2412/pr_6255.prdoc similarity index 100% rename from prdoc/pr_6255.prdoc rename to prdoc/stable2412/pr_6255.prdoc diff --git a/prdoc/pr_6257.prdoc b/prdoc/stable2412/pr_6257.prdoc similarity index 100% rename from prdoc/pr_6257.prdoc rename to prdoc/stable2412/pr_6257.prdoc diff --git a/prdoc/pr_6260.prdoc b/prdoc/stable2412/pr_6260.prdoc similarity index 100% rename from prdoc/pr_6260.prdoc rename to prdoc/stable2412/pr_6260.prdoc diff --git a/prdoc/pr_6261.prdoc b/prdoc/stable2412/pr_6261.prdoc similarity index 100% rename from prdoc/pr_6261.prdoc rename to prdoc/stable2412/pr_6261.prdoc diff --git a/prdoc/pr_6263.prdoc b/prdoc/stable2412/pr_6263.prdoc similarity index 100% rename from prdoc/pr_6263.prdoc rename to prdoc/stable2412/pr_6263.prdoc diff --git a/prdoc/pr_6264.prdoc b/prdoc/stable2412/pr_6264.prdoc similarity index 100% rename from prdoc/pr_6264.prdoc rename to prdoc/stable2412/pr_6264.prdoc diff --git a/prdoc/pr_6268.prdoc b/prdoc/stable2412/pr_6268.prdoc similarity index 100% rename from prdoc/pr_6268.prdoc rename to prdoc/stable2412/pr_6268.prdoc diff --git a/prdoc/pr_6278.prdoc b/prdoc/stable2412/pr_6278.prdoc similarity index 100% rename from prdoc/pr_6278.prdoc rename to prdoc/stable2412/pr_6278.prdoc diff --git a/prdoc/pr_6288.prdoc b/prdoc/stable2412/pr_6288.prdoc similarity index 100% rename from prdoc/pr_6288.prdoc rename to prdoc/stable2412/pr_6288.prdoc diff --git a/prdoc/pr_6291.prdoc b/prdoc/stable2412/pr_6291.prdoc similarity index 100% rename from prdoc/pr_6291.prdoc rename to prdoc/stable2412/pr_6291.prdoc diff --git a/prdoc/pr_6295.prdoc b/prdoc/stable2412/pr_6295.prdoc similarity index 100% rename from prdoc/pr_6295.prdoc rename to prdoc/stable2412/pr_6295.prdoc diff --git a/prdoc/pr_6296.prdoc b/prdoc/stable2412/pr_6296.prdoc similarity index 100% rename from prdoc/pr_6296.prdoc rename to prdoc/stable2412/pr_6296.prdoc diff --git a/prdoc/pr_6298.prdoc b/prdoc/stable2412/pr_6298.prdoc similarity index 100% rename from prdoc/pr_6298.prdoc rename to prdoc/stable2412/pr_6298.prdoc diff --git a/prdoc/pr_6299.prdoc b/prdoc/stable2412/pr_6299.prdoc similarity index 100% rename from prdoc/pr_6299.prdoc rename to prdoc/stable2412/pr_6299.prdoc diff --git a/prdoc/pr_6304.prdoc b/prdoc/stable2412/pr_6304.prdoc similarity index 100% rename from prdoc/pr_6304.prdoc rename to prdoc/stable2412/pr_6304.prdoc diff --git a/prdoc/pr_6305.prdoc b/prdoc/stable2412/pr_6305.prdoc similarity index 100% rename from prdoc/pr_6305.prdoc rename to prdoc/stable2412/pr_6305.prdoc diff --git a/prdoc/pr_6314.prdoc b/prdoc/stable2412/pr_6314.prdoc similarity index 100% rename from prdoc/pr_6314.prdoc rename to prdoc/stable2412/pr_6314.prdoc diff --git a/prdoc/pr_6315.prdoc b/prdoc/stable2412/pr_6315.prdoc similarity index 100% rename from prdoc/pr_6315.prdoc rename to prdoc/stable2412/pr_6315.prdoc diff --git a/prdoc/pr_6316.prdoc b/prdoc/stable2412/pr_6316.prdoc similarity index 100% rename from prdoc/pr_6316.prdoc rename to prdoc/stable2412/pr_6316.prdoc diff --git a/prdoc/pr_6317.prdoc b/prdoc/stable2412/pr_6317.prdoc similarity index 100% rename from prdoc/pr_6317.prdoc rename to prdoc/stable2412/pr_6317.prdoc diff --git a/prdoc/pr_6318.prdoc b/prdoc/stable2412/pr_6318.prdoc similarity index 100% rename from prdoc/pr_6318.prdoc rename to prdoc/stable2412/pr_6318.prdoc diff --git a/prdoc/pr_6323.prdoc b/prdoc/stable2412/pr_6323.prdoc similarity index 100% rename from prdoc/pr_6323.prdoc rename to prdoc/stable2412/pr_6323.prdoc diff --git a/prdoc/pr_6337.prdoc b/prdoc/stable2412/pr_6337.prdoc similarity index 100% rename from prdoc/pr_6337.prdoc rename to prdoc/stable2412/pr_6337.prdoc diff --git a/prdoc/pr_6353.prdoc b/prdoc/stable2412/pr_6353.prdoc similarity index 100% rename from prdoc/pr_6353.prdoc rename to prdoc/stable2412/pr_6353.prdoc diff --git a/prdoc/pr_6357.prdoc b/prdoc/stable2412/pr_6357.prdoc similarity index 100% rename from prdoc/pr_6357.prdoc rename to prdoc/stable2412/pr_6357.prdoc diff --git a/prdoc/pr_6360.prdoc b/prdoc/stable2412/pr_6360.prdoc similarity index 100% rename from prdoc/pr_6360.prdoc rename to prdoc/stable2412/pr_6360.prdoc diff --git a/prdoc/pr_6365.prdoc b/prdoc/stable2412/pr_6365.prdoc similarity index 100% rename from prdoc/pr_6365.prdoc rename to prdoc/stable2412/pr_6365.prdoc diff --git a/prdoc/pr_6373.prdoc b/prdoc/stable2412/pr_6373.prdoc similarity index 100% rename from prdoc/pr_6373.prdoc rename to prdoc/stable2412/pr_6373.prdoc diff --git a/prdoc/pr_6380.prdoc b/prdoc/stable2412/pr_6380.prdoc similarity index 100% rename from prdoc/pr_6380.prdoc rename to prdoc/stable2412/pr_6380.prdoc diff --git a/prdoc/pr_6382.prdoc b/prdoc/stable2412/pr_6382.prdoc similarity index 100% rename from prdoc/pr_6382.prdoc rename to prdoc/stable2412/pr_6382.prdoc diff --git a/prdoc/pr_6384.prdoc b/prdoc/stable2412/pr_6384.prdoc similarity index 100% rename from prdoc/pr_6384.prdoc rename to prdoc/stable2412/pr_6384.prdoc diff --git a/prdoc/pr_6406.prdoc b/prdoc/stable2412/pr_6406.prdoc similarity index 100% rename from prdoc/pr_6406.prdoc rename to prdoc/stable2412/pr_6406.prdoc diff --git a/prdoc/pr_6418.prdoc b/prdoc/stable2412/pr_6418.prdoc similarity index 100% rename from prdoc/pr_6418.prdoc rename to prdoc/stable2412/pr_6418.prdoc diff --git a/prdoc/pr_6454.prdoc b/prdoc/stable2412/pr_6454.prdoc similarity index 100% rename from prdoc/pr_6454.prdoc rename to prdoc/stable2412/pr_6454.prdoc diff --git a/prdoc/pr_6484.prdoc b/prdoc/stable2412/pr_6484.prdoc similarity index 100% rename from prdoc/pr_6484.prdoc rename to prdoc/stable2412/pr_6484.prdoc diff --git a/prdoc/stable2412/pr_6505.prdoc b/prdoc/stable2412/pr_6505.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ae00dd17fed505b8a4d7785fe6753633833ef307 --- /dev/null +++ b/prdoc/stable2412/pr_6505.prdoc @@ -0,0 +1,14 @@ +title: '[pallet-broker] Fix auto renew benchmarks' +doc: +- audience: Runtime Dev + description: |- + Fix the broker pallet auto-renew benchmarks which have been broken since #4424, yielding `Weightless` due to some prices being set too low, as reported in #6474. + + Upon further investigation it turned out that the auto-renew contribution to `rotate_sale` was always failing but the error was mapped. This is also fixed at the cost of a bit of setup overhead. +crates: +- name: pallet-broker + bump: patch +- name: coretime-rococo-runtime + bump: patch +- name: coretime-westend-runtime + bump: patch diff --git a/prdoc/pr_6536.prdoc b/prdoc/stable2412/pr_6536.prdoc similarity index 100% rename from prdoc/pr_6536.prdoc rename to prdoc/stable2412/pr_6536.prdoc diff --git a/prdoc/stable2412/pr_6566.prdoc b/prdoc/stable2412/pr_6566.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..bbd48b79953876dd4ecbf564718213291515c127 --- /dev/null +++ b/prdoc/stable2412/pr_6566.prdoc @@ -0,0 +1,45 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: XCMv5 - SetHints instruction + +doc: + - audience: Runtime Dev + description: | + Implementation of fellowship RFC 107. + The new SetHints instruction is a repackaging of SetAssetClaimer that also allows future + "hints" which alter the default behaviour of the executor. + The AllowTopLevelPaidExecutionFrom barrier allows this instruction between WithdrawAsset and + BuyExecution/PayFees to configure things before the actual meat of the program. + +crates: + - name: asset-hub-rococo-runtime + bump: major + - name: asset-hub-westend-runtime + bump: major + - name: bridge-hub-rococo-runtime + bump: major + - name: bridge-hub-westend-runtime + bump: major + - name: coretime-rococo-runtime + bump: major + - name: coretime-westend-runtime + bump: major + - name: people-rococo-runtime + bump: major + - name: people-westend-runtime + bump: major + - name: rococo-runtime + bump: major + - name: westend-runtime + bump: major + - name: pallet-xcm-benchmarks + bump: major + - name: xcm-procedural + bump: minor + - name: staging-xcm + bump: major + - name: staging-xcm-builder + bump: major + - name: staging-xcm-executor + bump: major diff --git a/prdoc/stable2412/pr_6588.prdoc b/prdoc/stable2412/pr_6588.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..bf44b2ed37845736c58df75751a519e7004ee3f7 --- /dev/null +++ b/prdoc/stable2412/pr_6588.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: "rpc server: fix subscription id_provider being reset to default one" + +doc: + - audience: Node Dev + description: | + The modification ensures that the id_provider variable is cloned instead of taken, which can help prevent issues related id provider being reset to the default. + + +crates: + - name: sc-rpc-server + bump: patch \ No newline at end of file diff --git a/prdoc/stable2412/pr_6603.prdoc b/prdoc/stable2412/pr_6603.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..20c5e7294dfa045e7577cb4e15e33119f489f1fe --- /dev/null +++ b/prdoc/stable2412/pr_6603.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Always provide main protocol name in litep2p responses + +doc: + - audience: [ Node Dev, Node Operator ] + description: | + This PR aligns litep2p behavior with libp2p. Previously, litep2p network backend + would provide the actual negotiated request-response protocol that produced a + response message. After this PR, only the main protocol name is reported to other + subsystems. + +crates: + - name: sc-network + bump: patch diff --git a/prdoc/stable2412/pr_6643.prdoc b/prdoc/stable2412/pr_6643.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c111f6356519e839f93b98d4c01b9b3655c884df --- /dev/null +++ b/prdoc/stable2412/pr_6643.prdoc @@ -0,0 +1,47 @@ +title: Added fallback_max_weight to Transact for sending messages to V4 chains +doc: +- audience: Runtime Dev + description: |- + Removing the `require_weight_at_most` parameter in V5 Transact introduced a problem when converting a message from V5 to V4 to send to chains that didn't upgrade yet. + The local chain doesn't know how to decode calls for remote chains so it can't automatically populate `require_weight_at_most` required by V4 Transact. + To fix this, XCM v5 Transact now also takes a `fallback_max_weight: Option<Weight>` parameter. + This can be set to `None` if the instruction is not meant to be sent to chains running XCM versions lower than V5. + If set to `Some(weight)`, a subsequent conversion to V4 will result in `Transact { require_weight_at_most: weight, .. }`. + The plan is to remove this workaround in V6 since there will be a good conversion path from V6 to V5. +crates: +- name: snowbridge-router-primitives + bump: major +- name: emulated-integration-tests-common + bump: major +- name: asset-hub-rococo-runtime + bump: major +- name: asset-hub-westend-runtime + bump: major +- name: asset-test-utils + bump: major +- name: bridge-hub-rococo-runtime + bump: major +- name: bridge-hub-westend-runtime + bump: major +- name: coretime-rococo-runtime + bump: major +- name: coretime-westend-runtime + bump: major +- name: people-rococo-runtime + bump: major +- name: people-westend-runtime + bump: major +- name: parachains-runtimes-test-utils + bump: major +- name: polkadot-runtime-parachains + bump: major +- name: rococo-runtime + bump: major +- name: westend-runtime + bump: major +- name: staging-xcm + bump: major +- name: staging-xcm-builder + bump: major +- name: staging-xcm-executor + bump: major diff --git a/prdoc/stable2412/pr_6645.prdoc b/prdoc/stable2412/pr_6645.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f033cadc0b6e5ae12526631fc8d5b0a6fb995713 --- /dev/null +++ b/prdoc/stable2412/pr_6645.prdoc @@ -0,0 +1,14 @@ +title: 'xcm: fix local/remote exports when inner routers return `NotApplicable`' +doc: +- audience: Runtime Dev + description: |- + Resolved a bug in the `local/remote exporters` used for bridging. Previously, they consumed `dest` and `msg` without returning them when inner routers/exporters failed with `NotApplicable`. This PR ensures compliance with the [`SendXcm`](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/xcm/src/v5/traits.rs#L449-L450) and [`ExportXcm`](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/xcm/xcm-executor/src/traits/export.rs#L44-L45) traits. +crates: +- name: staging-xcm-builder + bump: patch +- name: polkadot + bump: none +- name: staging-xcm + bump: none +- name: staging-xcm-executor + bump: none diff --git a/prdoc/stable2412/pr_6646.prdoc b/prdoc/stable2412/pr_6646.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..4dcda8d41bda6dc6c9a2e6e787a2b36c73288571 --- /dev/null +++ b/prdoc/stable2412/pr_6646.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: OmniNode --dev flag starts node with manual seal + +doc: + - audience: [ Runtime Dev, Node Dev ] + description: | + `polkadot-omni-node` lib supports `--dev` flag now by allowing also to pass over a chain spec, + and starts the node with manual seal. It will seal the node at each `dev_block_time` milliseconds, + which can be set via `--dev-block-time`, and if not set will default to `3000ms`. + +crates: + - name: sc-cli + bump: patch + - name: polkadot-omni-node-lib + bump: patch + - name: polkadot-omni-node + bump: patch diff --git a/prdoc/stable2412/pr_6652.prdoc b/prdoc/stable2412/pr_6652.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a303311e138fe8acce83cd3ebf95ddf88311c202 --- /dev/null +++ b/prdoc/stable2412/pr_6652.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: "rpc server: re-use server builder per rpc interface" + +doc: + - audience: Node Dev + description: | + This changes that the RPC server builder is re-used for each RPC interface which is more efficient than to build it for every connection. + +crates: + - name: sc-rpc-server + bump: patch diff --git a/prdoc/stable2412/pr_6677.prdoc b/prdoc/stable2412/pr_6677.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c6766889e68d2d03a71632324c78811ccbdf5ca6 --- /dev/null +++ b/prdoc/stable2412/pr_6677.prdoc @@ -0,0 +1,11 @@ +title: 'chore: Update litep2p to v0.8.2' +doc: +- audience: Node Dev + description: |- + This includes a critical fix for debug release versions of litep2p (which are running in Kusama as validators). + + While at it, have stopped the oncall pain of alerts around `incoming_connections_total`. We can rethink the metric expose of litep2p in Q1. + +crates: +- name: sc-network + bump: minor diff --git a/prdoc/stable2412/pr_6690.prdoc b/prdoc/stable2412/pr_6690.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..0e4a2437ef96299f0fa451b9ee2c9ec78a0ee4c4 --- /dev/null +++ b/prdoc/stable2412/pr_6690.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: Fix Possible bug, Vote import failed after aggression is enabled + +doc: + - audience: Node Dev + description: | + Fix the appearance of Possible bug: Vote import failed after aggression is enabled, the log itself is + harmless because approval gets imported anyway and aggression is able to distribute it, nevertheless + is something that can be easily be fixed by picking the highest required routing possible. + +crates: + - name: polkadot-node-network-protocol + bump: minor + - name: polkadot-approval-distribution + bump: minor diff --git a/prdoc/stable2412/pr_6696.prdoc b/prdoc/stable2412/pr_6696.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c5c73f831886b82eecbbbfb2c8af372b41510706 --- /dev/null +++ b/prdoc/stable2412/pr_6696.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: Make approval-distribution aggression a bit more robust and less spammy + +doc: + - audience: Node Dev + description: | + The problem with the current implementation of approval-distribution aggression is that is too spammy, + and can overload the nodes, so make it less spammy by moving back the moment we trigger L2 aggression + and make resend enable only for the latest unfinalized block. + +crates: + - name: polkadot-approval-distribution + bump: minor diff --git a/prdoc/stable2412/pr_6729.prdoc b/prdoc/stable2412/pr_6729.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9eaa67363c9a7d9bf643c971cb5424da896a4e9f --- /dev/null +++ b/prdoc/stable2412/pr_6729.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 order of resending messages after restart + +doc: + - audience: Node Dev + description: | + At restart when dealing with a coalesced approval we might end up in a situation where we sent to + approval-distribution the approval before all assignments covering it, in that case, the approval + is ignored and never distribute, which will lead to no-shows. + +crates: + - name: polkadot-node-core-approval-voting + bump: minor diff --git a/prdoc/stable2412/pr_6742.prdoc b/prdoc/stable2412/pr_6742.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..92c3755a3c28ae98ca24d2b696bd746c4002ca5b --- /dev/null +++ b/prdoc/stable2412/pr_6742.prdoc @@ -0,0 +1,11 @@ +title: Update litep2p backend to v0.8.3 +doc: +- audience: Node Dev + description: |- + This release includes two fixes for small memory leaks on edge-cases in the notification and request-response protocols. + While at it, have downgraded a log message from litep2p. + +crates: +- name: sc-network + bump: patch + diff --git a/prdoc/stable2412/pr_6760.prdoc b/prdoc/stable2412/pr_6760.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..8224b72fb0a4414c4c93c9b59181b1e444000ac2 --- /dev/null +++ b/prdoc/stable2412/pr_6760.prdoc @@ -0,0 +1,9 @@ +title: 'chainHead: Always report discarded items for storage operations' +doc: +- audience: [Node Dev, Node Operator] + description: |- + This PR ensures that substrate always reports discarded items as zero. + This is needed to align with the rpc-v2 spec +crates: +- name: sc-rpc-spec-v2 + bump: patch diff --git a/prdoc/stable2412/pr_6781.prdoc b/prdoc/stable2412/pr_6781.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..8090be42034180b1d54901be4fdeff1aa7b6d83c --- /dev/null +++ b/prdoc/stable2412/pr_6781.prdoc @@ -0,0 +1,28 @@ +title: Bridges - revert-back congestion mechanism + +doc: +- audience: Runtime Dev + description: |- + With [permissionless lanes PR#4949](https://github.com/paritytech/polkadot-sdk/pull/4949), the congestion mechanism based on sending `Transact(report_bridge_status(is_congested))` from `pallet-xcm-bridge-hub` to `pallet-xcm-bridge-hub-router` was replaced with a congestion mechanism that relied on monitoring XCMP queues. However, this approach could cause issues, such as suspending the entire XCMP queue instead of isolating the affected bridge. This PR reverts back to using `report_bridge_status` as before. + +crates: +- name: pallet-xcm-bridge-hub-router + bump: patch +- name: pallet-xcm-bridge-hub + bump: patch +- name: bp-xcm-bridge-hub + bump: patch +- name: bp-asset-hub-rococo + bump: patch +- name: bp-asset-hub-westend + bump: patch +- name: asset-hub-rococo-runtime + bump: patch +- name: asset-hub-westend-runtime + bump: patch +- name: asset-test-utils + bump: patch +- name: bridge-hub-rococo-runtime + bump: patch +- name: bridge-hub-westend-runtime + bump: patch diff --git a/prdoc/stable2412/pr_6814.prdoc b/prdoc/stable2412/pr_6814.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..4edbf2f8ed282c00806b6beb58afd34b08f41684 --- /dev/null +++ b/prdoc/stable2412/pr_6814.prdoc @@ -0,0 +1,32 @@ +title: Add aliasers to westend chains +doc: +- audience: Runtime Dev + description: |- + `InitiateTransfer`, the new instruction introduced in XCMv5, allows preserving the origin after a cross-chain transfer via the usage of the `AliasOrigin` instruction. The receiving chain needs to be configured to allow such this instruction to have its intended effect and not just throw an error. + + In this PR, I add the alias rules specified in the [RFC for origin preservation](https://github.com/polkadot-fellows/RFCs/blob/main/text/0122-alias-origin-on-asset-transfers.md) to westend chains so we can test these scenarios in the testnet. + + The new scenarios include: + - Sending a cross-chain transfer from one system chain to another and doing a Transact on the same message (1 hop) + - Sending a reserve asset transfer from one chain to another going through asset hub and doing Transact on the same message (2 hops) + + The updated chains are: + - Relay: added `AliasChildLocation` + - Collectives: added `AliasChildLocation` and `AliasOriginRootUsingFilter<AssetHubLocation, Everything>` + - People: added `AliasChildLocation` and `AliasOriginRootUsingFilter<AssetHubLocation, Everything>` + - Coretime: added `AliasChildLocation` and `AliasOriginRootUsingFilter<AssetHubLocation, Everything>` + + AssetHub already has `AliasChildLocation` and doesn't need the other config item. + BridgeHub is not intended to be used by end users so I didn't add any config item. + Only added `AliasChildOrigin` to the relay since we intend for it to be used less. +crates: +- name: westend-runtime + bump: patch +- name: collectives-westend-runtime + bump: patch +- name: people-westend-runtime + bump: patch +- name: coretime-westend-runtime + bump: patch +- name: pallet-xcm-benchmarks + bump: patch diff --git a/prdoc/stable2412/pr_6860.prdoc b/prdoc/stable2412/pr_6860.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..76b460ce52dd9cb00c1273dbf8d241b8a1e5be91 --- /dev/null +++ b/prdoc/stable2412/pr_6860.prdoc @@ -0,0 +1,10 @@ +title: Update litep2p network backend to v0.8.4 + +doc: + - audience: [ Node Dev, Node Operator ] + description: | + This PR updates the Litep2p network backend to version 0.8.4 + +crates: + - name: sc-network + bump: patch diff --git a/prdoc/stable2412/pr_6863.prdoc b/prdoc/stable2412/pr_6863.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..0dd416e5e4388b03396d8fdf4667515ed55d86e6 --- /dev/null +++ b/prdoc/stable2412/pr_6863.prdoc @@ -0,0 +1,9 @@ +title: Update merkleized-metadata to 0.2.0 +doc: +- audience: Node Dev + description: |- + 0.1.2 was yanked as it was breaking semver. +crates: + - name: substrate-wasm-builder + bump: patch + validate: false diff --git a/prdoc/stable2412/pr_6864.prdoc b/prdoc/stable2412/pr_6864.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..6d6c84e22da4a2ee4c3c65f700e1914762f42788 --- /dev/null +++ b/prdoc/stable2412/pr_6864.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 approval-voting canonicalize off by one + +doc: + - audience: Node Dev + description: | + The approval-voting canonicalize was off by one, which lead to blocks being + cleaned up every other 2 blocks. Normally, this is not an issue, but on restart + we might end up sending NewBlocks to approval-distribution with finalized blocks. + This would be problematic in the case were finalization was already lagging before + restart, so after restart approval-distribution will trigger aggression on the wrong + already finalized block. + +crates: + - name: polkadot-node-core-approval-voting + bump: minor diff --git a/prdoc/stable2412/pr_6885.prdoc b/prdoc/stable2412/pr_6885.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..986d76962289ac53a6697c0b708f06b69794fe38 --- /dev/null +++ b/prdoc/stable2412/pr_6885.prdoc @@ -0,0 +1,11 @@ +title: 'Omni-node: Detect pending code in storage and send go ahead signal in dev-mode.' +doc: +- audience: Runtime Dev + description: |- + When using the polkadot-omni-node with manual seal (`--dev-block-time`), it is now possible to perform runtime + upgrades. The node will detect the pending validation code and send a go-ahead signal to the parachain. +crates: +- name: cumulus-client-parachain-inherent + bump: major +- name: polkadot-omni-node-lib + bump: patch diff --git a/scripts/generate-umbrella.py b/scripts/generate-umbrella.py index 8326909c34493658a760b8dce3d63ffc333bbfee..ae3873180553eb6e8de9985927201f290e413a00 100644 --- a/scripts/generate-umbrella.py +++ b/scripts/generate-umbrella.py @@ -120,6 +120,8 @@ def main(path, version): "edition": { "workspace": True }, "authors": { "workspace": True }, "description": "Polkadot SDK umbrella crate.", + "homepage": { "workspace": True }, + "repository": { "workspace": True }, "license": "Apache-2.0", "metadata": { "docs": { "rs": { "features": ["runtime-full", "node"], diff --git a/substrate/.maintain/frame-umbrella-weight-template.hbs b/substrate/.maintain/frame-umbrella-weight-template.hbs index b174823b38403028af697d9c7103c0108201e74a..050e74a16d7e7a8163e4cd08f1f3d5bcbcd36582 100644 --- a/substrate/.maintain/frame-umbrella-weight-template.hbs +++ b/substrate/.maintain/frame-umbrella-weight-template.hbs @@ -16,6 +16,7 @@ #![allow(unused_parens)] #![allow(unused_imports)] #![allow(missing_docs)] +#[allow(dead_code)] use frame::weights_prelude::*; diff --git a/substrate/.maintain/frame-weight-template.hbs b/substrate/.maintain/frame-weight-template.hbs index ec9eee205cee37b534eca5187ece031daa54dd99..541f064850a71c9dc06497612991daf0063c4ced 100644 --- a/substrate/.maintain/frame-weight-template.hbs +++ b/substrate/.maintain/frame-weight-template.hbs @@ -16,6 +16,7 @@ #![allow(unused_parens)] #![allow(unused_imports)] #![allow(missing_docs)] +#[allow(dead_code)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use core::marker::PhantomData; diff --git a/substrate/bin/node/bench/Cargo.toml b/substrate/bin/node/bench/Cargo.toml index 447f947107c1b593eb62e0c4f200503d6864ffea..83f7b82cd2b51e755c4b9d9273a281db12a23ad7 100644 --- a/substrate/bin/node/bench/Cargo.toml +++ b/substrate/bin/node/bench/Cargo.toml @@ -15,33 +15,33 @@ workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-trait = { workspace = true } array-bytes = { workspace = true, default-features = true } +async-trait = { workspace = true } clap = { features = ["derive"], workspace = true } +derive_more = { features = ["display"], workspace = true } +fs_extra = { workspace = true } +futures = { features = ["thread-pool"], workspace = true } +hash-db = { workspace = true, default-features = true } +kitchensink-runtime = { workspace = true } +kvdb = { workspace = true } +kvdb-rocksdb = { workspace = true } log = { workspace = true, default-features = true } node-primitives = { workspace = true, default-features = true } node-testing = { workspace = true } -kitchensink-runtime = { workspace = true } +parity-db = { workspace = true } +rand = { features = ["small_rng"], workspace = true, default-features = true } +sc-basic-authorship = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } -sp-state-machine = { workspace = true, default-features = true } +sc-transaction-pool = { workspace = true, default-features = true } +sc-transaction-pool-api = { workspace = true, default-features = true } serde = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } -derive_more = { features = ["display"], workspace = true } -kvdb = { workspace = true } -kvdb-rocksdb = { workspace = true } -sp-trie = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } -sc-basic-authorship = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-state-machine = { workspace = true, default-features = true } sp-timestamp = { workspace = true } sp-tracing = { workspace = true, default-features = true } -hash-db = { workspace = true, default-features = true } +sp-trie = { workspace = true, default-features = true } tempfile = { workspace = true } -fs_extra = { workspace = true } -rand = { features = ["small_rng"], workspace = true, default-features = true } -parity-db = { workspace = true } -sc-transaction-pool = { workspace = true, default-features = true } -sc-transaction-pool-api = { workspace = true, default-features = true } -futures = { features = ["thread-pool"], workspace = true } diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index c179579c1885359b04c6a74f9c86efaa285e19bd..7b355074823c3dd17d1414205d9b8d4cd47e945c 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -40,11 +40,11 @@ crate-type = ["cdylib", "rlib"] array-bytes = { workspace = true, default-features = true } clap = { features = ["derive"], optional = true, workspace = true } codec = { workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true, default-features = true } -jsonrpsee = { features = ["server"], workspace = true } futures = { workspace = true } +jsonrpsee = { features = ["server"], workspace = true } log = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } subxt-signer = { workspace = true, features = ["unstable-eth"] } @@ -135,32 +135,31 @@ polkadot-sdk = { features = [ # Shared code between the staging node and kitchensink runtime: kitchensink-runtime = { workspace = true } -node-rpc = { workspace = true } -node-primitives = { workspace = true, default-features = true } node-inspect = { optional = true, workspace = true, default-features = true } +node-primitives = { workspace = true, default-features = true } +node-rpc = { workspace = true } [dev-dependencies] -futures = { workspace = true } -tempfile = { workspace = true } assert_cmd = { workspace = true } +criterion = { features = ["async_tokio"], workspace = true, default-features = true } +futures = { workspace = true } nix = { features = ["signal"], workspace = true } +pretty_assertions.workspace = true regex = { workspace = true } -platforms = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } soketto = { workspace = true } -criterion = { features = ["async_tokio"], workspace = true, default-features = true } +sp-keyring = { workspace = true } +tempfile = { workspace = true } tokio = { features = ["macros", "parking_lot", "time"], workspace = true, default-features = true } tokio-util = { features = ["compat"], workspace = true } wait-timeout = { workspace = true } wat = { workspace = true } -serde_json = { workspace = true, default-features = true } -scale-info = { features = ["derive", "serde"], workspace = true, default-features = true } -sp-keyring = { workspace = true } -pretty_assertions.workspace = true # These testing-only dependencies are not exported by the Polkadot-SDK crate: node-testing = { workspace = true } -substrate-cli-test-utils = { workspace = true } sc-service-test = { workspace = true } +substrate-cli-test-utils = { workspace = true } [build-dependencies] clap = { optional = true, workspace = true } diff --git a/substrate/bin/node/cli/src/service.rs b/substrate/bin/node/cli/src/service.rs index 5cde85ae5790e97663d01f4ddb2dc8f255800c9d..e531097dbb5e8adf16d7e8f7de60cba980fe27be 100644 --- a/substrate/bin/node/cli/src/service.rs +++ b/substrate/bin/node/cli/src/service.rs @@ -138,6 +138,7 @@ pub fn create_extrinsic( >::from(tip, None), ), frame_metadata_hash_extension::CheckMetadataHash::new(false), + frame_system::WeightReclaim::<kitchensink_runtime::Runtime>::new(), ); let raw_payload = kitchensink_runtime::SignedPayload::from_raw( @@ -153,6 +154,7 @@ pub fn create_extrinsic( (), (), None, + (), ), ); let signature = raw_payload.using_encoded(|e| sender.sign(e)); @@ -871,7 +873,7 @@ mod tests { use sp_consensus::{BlockOrigin, Environment, Proposer}; use sp_core::crypto::Pair; use sp_inherents::InherentDataProvider; - use sp_keyring::AccountKeyring; + use sp_keyring::Sr25519Keyring; use sp_keystore::KeystorePtr; use sp_runtime::{ generic::{self, Digest, Era, SignedPayload}, @@ -906,8 +908,8 @@ mod tests { let mut slot = 1u64; // For the extrinsics factory - let bob = Arc::new(AccountKeyring::Bob.pair()); - let charlie = Arc::new(AccountKeyring::Charlie.pair()); + let bob = Arc::new(Sr25519Keyring::Bob.pair()); + let charlie = Arc::new(Sr25519Keyring::Charlie.pair()); let mut index = 0; sc_service_test::sync( @@ -1060,6 +1062,7 @@ mod tests { let tx_payment = pallet_skip_feeless_payment::SkipCheckIfFeeless::from( pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(0, None), ); + let weight_reclaim = frame_system::WeightReclaim::new(); let metadata_hash = frame_metadata_hash_extension::CheckMetadataHash::new(false); let tx_ext: TxExtension = ( check_non_zero_sender, @@ -1071,6 +1074,7 @@ mod tests { check_weight, tx_payment, metadata_hash, + weight_reclaim, ); let raw_payload = SignedPayload::from_raw( function, @@ -1085,6 +1089,7 @@ mod tests { (), (), None, + (), ), ); let signature = raw_payload.using_encoded(|payload| signer.sign(payload)); diff --git a/substrate/bin/node/cli/tests/res/default_genesis_config.json b/substrate/bin/node/cli/tests/res/default_genesis_config.json index a2e52837d88222b18c42996bd13d400c97d4e920..8ad2428f7855488c7d98ebbcccce9a4b434b7f57 100644 --- a/substrate/bin/node/cli/tests/res/default_genesis_config.json +++ b/substrate/bin/node/cli/tests/res/default_genesis_config.json @@ -14,7 +14,8 @@ "indices": [] }, "balances": { - "balances": [] + "balances": [], + "devAccounts": null }, "broker": {}, "transactionPayment": { diff --git a/substrate/bin/node/inspect/Cargo.toml b/substrate/bin/node/inspect/Cargo.toml index 6c8a4e59f68d7e2c848463361755b51eeecac668..0cf13bef71f16113b8c127efac13b4cd1dfa0471 100644 --- a/substrate/bin/node/inspect/Cargo.toml +++ b/substrate/bin/node/inspect/Cargo.toml @@ -17,7 +17,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] clap = { features = ["derive"], workspace = true } codec = { workspace = true, default-features = true } -thiserror = { workspace = true } sc-cli = { workspace = true } sc-client-api = { workspace = true, default-features = true } sc-service = { workspace = true } @@ -26,6 +25,7 @@ sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-statement-store = { workspace = true, default-features = true } +thiserror = { workspace = true } [features] runtime-benchmarks = [ diff --git a/substrate/bin/node/rpc/Cargo.toml b/substrate/bin/node/rpc/Cargo.toml index 02f5d9a4a70258f1259042bbd1b6f3096c70725c..c8b20287650bde7c8fb2fda3dd527a6288495293 100644 --- a/substrate/bin/node/rpc/Cargo.toml +++ b/substrate/bin/node/rpc/Cargo.toml @@ -17,16 +17,15 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] jsonrpsee = { features = ["server"], workspace = true } +mmr-rpc = { workspace = true, default-features = true } node-primitives = { workspace = true, default-features = true } pallet-transaction-payment-rpc = { workspace = true, default-features = true } -mmr-rpc = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus-babe = { workspace = true, default-features = true } sc-consensus-babe-rpc = { workspace = true, default-features = true } sc-consensus-beefy = { workspace = true, default-features = true } sc-consensus-beefy-rpc = { workspace = true, default-features = true } -sp-consensus-beefy = { workspace = true, default-features = true } sc-consensus-grandpa = { workspace = true, default-features = true } sc-consensus-grandpa-rpc = { workspace = true, default-features = true } sc-mixnet = { workspace = true, default-features = true } @@ -34,13 +33,14 @@ sc-rpc = { 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 } +sp-application-crypto = { workspace = true, default-features = true } sp-block-builder = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-consensus-babe = { workspace = true, default-features = true } +sp-consensus-beefy = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true, default-features = true } sp-statement-store = { workspace = true, default-features = true } substrate-frame-rpc-system = { workspace = true, default-features = true } substrate-state-trie-migration-rpc = { workspace = true, default-features = true } diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index 3ad6315561d0aab36f94cb841eb639165ac54acc..6d377cc92cce1ec1aae3ee07ef2f2b7c319ee97b 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -23,11 +23,11 @@ codec = { features = [ "derive", "max-encoded-len", ], workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } -static_assertions = { workspace = true, default-features = true } log = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } serde_json = { features = ["alloc", "arbitrary_precision"], workspace = true } sp-debug-derive = { workspace = true, features = ["force-debug"] } +static_assertions = { workspace = true, default-features = true } # pallet-asset-conversion: turn on "num-traits" feature primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 86c879feda65c57bbd1aab4cbc7700d793d39d95..47d2508ec95e95f8e8caaa951cdb04c8da704d51 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -41,7 +41,7 @@ use frame_election_provider_support::{ }; use frame_support::{ derive_impl, - dispatch::DispatchClass, + dispatch::{DispatchClass, DispatchInfo}, dynamic_params::{dynamic_pallet_params, dynamic_params}, genesis_builder_helper::{build_state, get_preset}, instances::{Instance1, Instance2}, @@ -53,13 +53,15 @@ use frame_support::{ Balanced, Credit, HoldConsideration, ItemOf, NativeFromLeft, NativeOrWithId, UnionOf, }, tokens::{ - imbalance::ResolveAssetTo, nonfungibles_v2::Inspect, pay::PayAssetFromAccount, + imbalance::{ResolveAssetTo, ResolveTo}, + nonfungibles_v2::Inspect, + pay::PayAssetFromAccount, GetSalary, PayFromAccount, }, - AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, Contains, - Currency, EitherOfDiverse, EnsureOriginWithArg, EqualPrivilegeOnly, Imbalance, InsideBoth, - InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, LockIdentifier, Nothing, - OnUnbalanced, VariantCountOf, WithdrawReasons, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, + ConstantStoragePrice, Contains, Currency, EitherOfDiverse, EnsureOriginWithArg, + EqualPrivilegeOnly, Imbalance, InsideBoth, InstanceFilter, KeyOwnerProofSystem, + LinearStoragePrice, LockIdentifier, Nothing, OnUnbalanced, VariantCountOf, WithdrawReasons, }, weights::{ constants::{ @@ -87,6 +89,7 @@ use pallet_nomination_pools::PoolId; use pallet_revive::{evm::runtime::EthExtra, AddressMapper}; use pallet_session::historical as pallet_session_historical; use sp_core::U256; +use sp_runtime::traits::TransactionExtension; // Can't use `FungibleAdapter` here until Treasury pallet migrates to fungibles // <https://github.com/paritytech/polkadot-sdk/issues/226> use pallet_broker::TaskId; @@ -511,7 +514,8 @@ impl pallet_glutton::Config for Runtime { } parameter_types! { - pub const PreimageHoldReason: RuntimeHoldReason = RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage); + pub const PreimageHoldReason: RuntimeHoldReason = + RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage); } impl pallet_preimage::Config for Runtime { @@ -618,6 +622,12 @@ impl pallet_transaction_payment::Config for Runtime { type WeightInfo = pallet_transaction_payment::weights::SubstrateWeight<Runtime>; } +pub type AssetsFreezerInstance = pallet_assets_freezer::Instance1; +impl pallet_assets_freezer::Config<AssetsFreezerInstance> for Runtime { + type RuntimeFreezeReason = RuntimeFreezeReason; + type RuntimeEvent = RuntimeEvent; +} + impl pallet_asset_conversion_tx_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; type AssetId = NativeOrWithId<u32>; @@ -712,13 +722,15 @@ impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig { } impl pallet_staking::Config for Runtime { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = Balance; type UnixTime = Timestamp; type CurrencyToVote = sp_staking::currency_to_vote::U128CurrencyToVote; - type RewardRemainder = Treasury; + type RewardRemainder = ResolveTo<TreasuryAccount, Balances>; type RuntimeEvent = RuntimeEvent; - type Slash = Treasury; // send the slashed funds to the treasury. + type RuntimeHoldReason = RuntimeHoldReason; + type Slash = ResolveTo<TreasuryAccount, Balances>; // send the slashed funds to the treasury. type Reward = (); // rewards are minted from the void type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; @@ -741,7 +753,7 @@ impl pallet_staking::Config for Runtime { type MaxUnlockingChunks = ConstU32<32>; type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch; type HistoryDepth = HistoryDepth; - type EventListeners = NominationPools; + type EventListeners = (NominationPools, DelegatedStaking); type WeightInfo = pallet_staking::weights::SubstrateWeight<Runtime>; type BenchmarkingConfig = StakingBenchmarkingConfig; type DisablingStrategy = pallet_staking::UpToLimitWithReEnablingDisablingStrategy; @@ -925,6 +937,21 @@ impl pallet_bags_list::Config<VoterBagsListInstance> for Runtime { type WeightInfo = pallet_bags_list::weights::SubstrateWeight<Runtime>; } +parameter_types! { + pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk"); + pub const SlashRewardFraction: Perbill = Perbill::from_percent(1); +} + +impl pallet_delegated_staking::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PalletId = DelegatedStakingPalletId; + type Currency = Balances; + type OnSlash = (); + type SlashRewardFraction = SlashRewardFraction; + type RuntimeHoldReason = RuntimeHoldReason; + type CoreStaking = Staking; +} + parameter_types! { pub const PostUnbondPoolsWindow: u32 = 4; pub const NominationPoolsPalletId: PalletId = PalletId(*b"py/nopls"); @@ -953,7 +980,8 @@ impl pallet_nomination_pools::Config for Runtime { type RewardCounter = FixedU128; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; - type StakeAdapter = pallet_nomination_pools::adapter::TransferStake<Self, Staking>; + type StakeAdapter = + pallet_nomination_pools::adapter::DelegateStake<Self, Staking, DelegatedStaking>; type PostUnbondingPoolsWindow = PostUnbondPoolsWindow; type MaxMetadataLen = ConstU32<256>; type MaxUnbonding = ConstU32<8>; @@ -1465,10 +1493,11 @@ impl pallet_revive::Config for Runtime { type InstantiateOrigin = EnsureSigned<Self::AccountId>; type RuntimeHoldReason = RuntimeHoldReason; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Debug = (); type Xcm = (); type ChainId = ConstU64<420_420_420>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. + type EthGasEncoder = (); + type FindAuthor = <Runtime as pallet_authorship::Config>::FindAuthor; } impl pallet_sudo::Config for Runtime { @@ -1533,6 +1562,7 @@ where ), ), frame_metadata_hash_extension::CheckMetadataHash::new(false), + frame_system::WeightReclaim::<Runtime>::new(), ); let raw_payload = SignedPayload::new(call, tx_ext) @@ -1654,6 +1684,7 @@ impl pallet_recovery::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = pallet_recovery::weights::SubstrateWeight<Runtime>; type RuntimeCall = RuntimeCall; + type BlockNumberProvider = System; type Currency = Balances; type ConfigDepositBase = ConfigDepositBase; type FriendDepositFactor = FriendDepositFactor; @@ -1856,6 +1887,53 @@ impl pallet_asset_conversion::Config for Runtime { type BenchmarkHelper = (); } +pub type NativeAndAssetsFreezer = + UnionOf<Balances, AssetsFreezer, NativeFromLeft, NativeOrWithId<u32>, AccountId>; + +/// Benchmark Helper +#[cfg(feature = "runtime-benchmarks")] +pub struct AssetRewardsBenchmarkHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_asset_rewards::benchmarking::BenchmarkHelper<NativeOrWithId<u32>> + for AssetRewardsBenchmarkHelper +{ + fn staked_asset() -> NativeOrWithId<u32> { + NativeOrWithId::<u32>::WithId(100) + } + fn reward_asset() -> NativeOrWithId<u32> { + NativeOrWithId::<u32>::WithId(101) + } +} + +parameter_types! { + pub const StakingRewardsPalletId: PalletId = PalletId(*b"py/stkrd"); + pub const CreationHoldReason: RuntimeHoldReason = + RuntimeHoldReason::AssetRewards(pallet_asset_rewards::HoldReason::PoolCreation); + // 1 item, 135 bytes into the storage on pool creation. + pub const StakePoolCreationDeposit: Balance = deposit(1, 135); +} + +impl pallet_asset_rewards::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeFreezeReason = RuntimeFreezeReason; + type AssetId = NativeOrWithId<u32>; + type Balance = Balance; + type Assets = NativeAndAssets; + type PalletId = StakingRewardsPalletId; + type CreatePoolOrigin = EnsureSigned<AccountId>; + type WeightInfo = (); + type AssetsFreezer = NativeAndAssetsFreezer; + type Consideration = HoldConsideration< + AccountId, + Balances, + CreationHoldReason, + ConstantStoragePrice<StakePoolCreationDeposit, Balance>, + >; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = AssetRewardsBenchmarkHelper; +} + impl pallet_asset_conversion_ops::Config for Runtime { type RuntimeEvent = RuntimeEvent; type PriorAccountIdConverter = pallet_asset_conversion::AccountIdConverterNoSeed<( @@ -2384,7 +2462,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; @@ -2634,6 +2713,15 @@ mod runtime { #[runtime::pallet_index(81)] pub type VerifySignature = pallet_verify_signature::Pallet<Runtime>; + + #[runtime::pallet_index(82)] + pub type DelegatedStaking = pallet_delegated_staking::Pallet<Runtime>; + + #[runtime::pallet_index(83)] + pub type AssetRewards = pallet_asset_rewards::Pallet<Runtime>; + + #[runtime::pallet_index(84)] + pub type AssetsFreezer = pallet_assets_freezer::Pallet<Runtime, Instance1>; } impl TryFrom<RuntimeCall> for pallet_revive::Call<Runtime> { @@ -2674,6 +2762,7 @@ pub type TxExtension = ( pallet_asset_conversion_tx_payment::ChargeAssetTxPayment<Runtime>, >, frame_metadata_hash_extension::CheckMetadataHash<Runtime>, + frame_system::WeightReclaim<Runtime>, ); #[derive(Clone, PartialEq, Eq, Debug)] @@ -2695,6 +2784,7 @@ impl EthExtra for EthExtraImpl { pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::<Runtime>::from(tip, None) .into(), frame_metadata_hash_extension::CheckMetadataHash::<Runtime>::new(false), + frame_system::WeightReclaim::<Runtime>::new(), ) } } @@ -2842,6 +2932,7 @@ mod benches { [pallet_example_tasks, TasksExample] [pallet_democracy, Democracy] [pallet_asset_conversion, AssetConversion] + [pallet_asset_rewards, AssetRewards] [pallet_asset_conversion_tx_payment, AssetConversionTxPayment] [pallet_transaction_payment, TransactionPayment] [pallet_election_provider_multi_phase, ElectionProviderMultiPhase] @@ -2926,6 +3017,12 @@ impl_runtime_apis! { } } + impl frame_support::view_functions::runtime_api::RuntimeViewFunction<Block> for Runtime { + fn execute_view_function(id: frame_support::view_functions::ViewFunctionId, input: Vec<u8>) -> Result<Vec<u8>, frame_support::view_functions::ViewFunctionDispatchError> { + Runtime::execute_view_function(id, input) + } + } + impl sp_block_builder::BlockBuilder<Block> for Runtime { fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult { Executive::apply_extrinsic(extrinsic) @@ -2975,7 +3072,7 @@ impl_runtime_apis! { } fn current_set_id() -> sp_consensus_grandpa::SetId { - Grandpa::current_set_id() + pallet_grandpa::CurrentSetId::<Runtime>::get() } fn submit_report_equivocation_unsigned_extrinsic( @@ -3208,47 +3305,41 @@ impl_runtime_apis! { } } - impl pallet_revive::ReviveApi<Block, AccountId, Balance, Nonce, BlockNumber, EventRecord> for Runtime + impl pallet_revive::ReviveApi<Block, AccountId, Balance, Nonce, BlockNumber> for Runtime { fn balance(address: H160) -> U256 { Revive::evm_balance(&address) } + fn block_gas_limit() -> U256 { + Revive::evm_block_gas_limit() + } + + fn gas_price() -> U256 { + Revive::evm_gas_price() + } + fn nonce(address: H160) -> Nonce { let account = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&address); System::account_nonce(account) } - fn eth_transact( - from: H160, - dest: Option<H160>, - value: U256, - input: Vec<u8>, - gas_limit: Option<Weight>, - storage_deposit_limit: Option<Balance>, - ) -> pallet_revive::EthContractResult<Balance> + fn eth_transact(tx: pallet_revive::evm::GenericTransaction) -> Result<pallet_revive::EthTransactInfo<Balance>, pallet_revive::EthTransactError> { - use pallet_revive::AddressMapper; let blockweights: BlockWeights = <Runtime as frame_system::Config>::BlockWeights::get(); - let origin = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&from); - - let encoded_size = |pallet_call| { + let tx_fee = |pallet_call, mut dispatch_info: DispatchInfo| { let call = RuntimeCall::Revive(pallet_call); + dispatch_info.extension_weight = EthExtraImpl::get_eth_extension(0, 0u32.into()).weight(&call); let uxt: UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic::new_bare(call).into(); - uxt.encoded_size() as u32 + + pallet_transaction_payment::Pallet::<Runtime>::compute_fee( + uxt.encoded_size() as u32, + &dispatch_info, + 0u32.into(), + ) }; - Revive::bare_eth_transact( - origin, - dest, - value, - input, - gas_limit.unwrap_or(blockweights.max_block), - storage_deposit_limit.unwrap_or(u128::MAX), - encoded_size, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, - ) + Revive::bare_eth_transact(tx, blockweights.max_block, tx_fee) } fn call( @@ -3258,16 +3349,14 @@ impl_runtime_apis! { gas_limit: Option<Weight>, storage_deposit_limit: Option<Balance>, input_data: Vec<u8>, - ) -> pallet_revive::ContractResult<pallet_revive::ExecReturnValue, Balance, EventRecord> { + ) -> pallet_revive::ContractResult<pallet_revive::ExecReturnValue, Balance> { Revive::bare_call( RuntimeOrigin::signed(origin), dest, value, gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), input_data, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } @@ -3279,18 +3368,16 @@ impl_runtime_apis! { code: pallet_revive::Code, data: Vec<u8>, salt: Option<[u8; 32]>, - ) -> pallet_revive::ContractResult<pallet_revive::InstantiateReturnValue, Balance, EventRecord> + ) -> pallet_revive::ContractResult<pallet_revive::InstantiateReturnValue, Balance> { Revive::bare_instantiate( RuntimeOrigin::signed(origin), value, gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), code, data, salt, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } @@ -3568,6 +3655,12 @@ impl_runtime_apis! { } } + impl pallet_asset_rewards::AssetRewards<Block, Balance> for Runtime { + fn pool_creation_cost() -> Balance { + StakePoolCreationDeposit::get() + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime<Block> for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { diff --git a/substrate/bin/node/testing/Cargo.toml b/substrate/bin/node/testing/Cargo.toml index 16112386ad7cbb1894e4c94f350a97abd795794e..13477a172fb8b01d9ba7f9cf266c639a16da8110 100644 --- a/substrate/bin/node/testing/Cargo.toml +++ b/substrate/bin/node/testing/Cargo.toml @@ -17,27 +17,26 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true, default-features = true } +frame-metadata-hash-extension = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } fs_extra = { workspace = true } futures = { workspace = true } +kitchensink-runtime = { workspace = true } log = { workspace = true, default-features = true } -tempfile = { workspace = true } -frame-metadata-hash-extension = { workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } node-cli = { workspace = true } node-primitives = { workspace = true, default-features = true } -kitchensink-runtime = { workspace = true } pallet-asset-conversion = { workspace = true, default-features = true } -pallet-assets = { workspace = true, default-features = true } -pallet-revive = { workspace = true, default-features = true } pallet-asset-conversion-tx-payment = { workspace = true, default-features = true } pallet-asset-tx-payment = { workspace = true, default-features = true } +pallet-assets = { workspace = true, default-features = true } +pallet-revive = { workspace = true, default-features = true } pallet-skip-feeless-payment = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-client-db = { features = ["rocksdb"], workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } -sc-service = { features = ["rocksdb", "test-helpers"], workspace = true, default-features = true } +sc-service = { features = ["rocksdb"], workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-block-builder = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } @@ -50,3 +49,4 @@ sp-keyring = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-timestamp = { workspace = true } substrate-test-client = { workspace = true } +tempfile = { workspace = true } diff --git a/substrate/bin/node/testing/src/genesis.rs b/substrate/bin/node/testing/src/genesis.rs index 7f5364744c667f711a5e2c32e20636e904ee02c2..624b00b4d6c23f4f91bba9bdca85fa98c8decebf 100644 --- a/substrate/bin/node/testing/src/genesis.rs +++ b/substrate/bin/node/testing/src/genesis.rs @@ -38,16 +38,16 @@ pub fn config_endowed(extra_endowed: Vec<AccountId>) -> RuntimeGenesisConfig { (alice(), 111 * DOLLARS), (bob(), 100 * DOLLARS), (charlie(), 100_000_000 * DOLLARS), - (dave(), 111 * DOLLARS), + (dave(), 112 * DOLLARS), (eve(), 101 * DOLLARS), - (ferdie(), 100 * DOLLARS), + (ferdie(), 101 * DOLLARS), ]; endowed.extend(extra_endowed.into_iter().map(|endowed| (endowed, 100 * DOLLARS))); RuntimeGenesisConfig { indices: IndicesConfig { indices: vec![] }, - balances: BalancesConfig { balances: endowed }, + balances: BalancesConfig { balances: endowed, ..Default::default() }, session: SessionConfig { keys: vec![ (alice(), dave(), session_keys_from_seed(Ed25519Keyring::Alice.into())), diff --git a/substrate/bin/node/testing/src/keyring.rs b/substrate/bin/node/testing/src/keyring.rs index e5b0299f01a83b2756e50aae44fc781aa35c6ee3..08d6ad6dcc35d95740f2ac684ed5c6265d023c6a 100644 --- a/substrate/bin/node/testing/src/keyring.rs +++ b/substrate/bin/node/testing/src/keyring.rs @@ -86,6 +86,7 @@ pub fn tx_ext(nonce: Nonce, extra_fee: Balance) -> TxExtension { pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(extra_fee, None), ), frame_metadata_hash_extension::CheckMetadataHash::new(false), + frame_system::WeightReclaim::new(), ) } diff --git a/substrate/bin/utils/chain-spec-builder/Cargo.toml b/substrate/bin/utils/chain-spec-builder/Cargo.toml index b71e935a918f2e52a5a8324b011ec6891c68a510..f3adc568296955619f11b9dfddc4c50e3b2ff13a 100644 --- a/substrate/bin/utils/chain-spec-builder/Cargo.toml +++ b/substrate/bin/utils/chain-spec-builder/Cargo.toml @@ -34,14 +34,14 @@ log = { workspace = true, default-features = true } sc-chain-spec = { features = [ "clap", ], workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } [dev-dependencies] -substrate-test-runtime = { workspace = true } cmd_lib = { workspace = true } docify = { workspace = true } +substrate-test-runtime = { workspace = true } [features] # `cargo build --feature=generate-readme` updates the `README.md` file. diff --git a/substrate/bin/utils/chain-spec-builder/tests/expected/create_default.json b/substrate/bin/utils/chain-spec-builder/tests/expected/create_default.json index ac67aef93345fdf6738b2b6fde3e4ab6b7df1a86..77891ac93ead144ba44a67ded5812f2089774975 100644 --- a/substrate/bin/utils/chain-spec-builder/tests/expected/create_default.json +++ b/substrate/bin/utils/chain-spec-builder/tests/expected/create_default.json @@ -25,7 +25,8 @@ } }, "balances": { - "balances": [] + "balances": [], + "devAccounts": null }, "substrateTest": { "authorities": [] diff --git a/substrate/bin/utils/chain-spec-builder/tests/expected/create_parachain.json b/substrate/bin/utils/chain-spec-builder/tests/expected/create_parachain.json index 7106b4b50dc593a0efcb77edc74d67acd5121b92..22b0ca6571b4097c3adfd123fd6065e48e97231d 100644 --- a/substrate/bin/utils/chain-spec-builder/tests/expected/create_parachain.json +++ b/substrate/bin/utils/chain-spec-builder/tests/expected/create_parachain.json @@ -27,7 +27,8 @@ } }, "balances": { - "balances": [] + "balances": [], + "devAccounts": null }, "substrateTest": { "authorities": [] diff --git a/substrate/bin/utils/chain-spec-builder/tests/expected/create_with_params.json b/substrate/bin/utils/chain-spec-builder/tests/expected/create_with_params.json index 5aedd5b5c18baab559757196295bd05dd586af9e..641df669e1888add1bbf719a9a063284f91d96b2 100644 --- a/substrate/bin/utils/chain-spec-builder/tests/expected/create_with_params.json +++ b/substrate/bin/utils/chain-spec-builder/tests/expected/create_with_params.json @@ -25,7 +25,8 @@ } }, "balances": { - "balances": [] + "balances": [], + "devAccounts": null }, "substrateTest": { "authorities": [] diff --git a/substrate/bin/utils/chain-spec-builder/tests/expected/doc/create_default.json b/substrate/bin/utils/chain-spec-builder/tests/expected/doc/create_default.json index 203b6716cb26883a31ff3c90500f0d0d810dc78f..e5957624ead2d55bf614bb8e12d5b4c86daa251d 100644 --- a/substrate/bin/utils/chain-spec-builder/tests/expected/doc/create_default.json +++ b/substrate/bin/utils/chain-spec-builder/tests/expected/doc/create_default.json @@ -24,7 +24,8 @@ } }, "balances": { - "balances": [] + "balances": [], + "devAccounts": null }, "substrateTest": { "authorities": [] diff --git a/substrate/bin/utils/chain-spec-builder/tests/expected/doc/display_preset.json b/substrate/bin/utils/chain-spec-builder/tests/expected/doc/display_preset.json index 6aa6799af771d197636dea3b3add454150cc2c4c..6bbb475d35c7818515e4e13d916f1f9a6107b836 100644 --- a/substrate/bin/utils/chain-spec-builder/tests/expected/doc/display_preset.json +++ b/substrate/bin/utils/chain-spec-builder/tests/expected/doc/display_preset.json @@ -1 +1 @@ -{"babe":{"authorities":[],"epochConfig":{"allowed_slots":"PrimaryAndSecondaryVRFSlots","c":[1,4]}},"balances":{"balances":[]},"substrateTest":{"authorities":[]},"system":{}} +{"babe":{"authorities":[],"epochConfig":{"allowed_slots":"PrimaryAndSecondaryVRFSlots","c":[1,4]}},"balances":{"balances":[], "devAccounts": null},"substrateTest":{"authorities":[]},"system":{}} diff --git a/substrate/client/allocator/Cargo.toml b/substrate/client/allocator/Cargo.toml index a8b3bdc864c9ae2ce1e10a3e24bd4d5255058522..c0ce640566b07501c66e4b29c84b749ed04ce443 100644 --- a/substrate/client/allocator/Cargo.toml +++ b/substrate/client/allocator/Cargo.toml @@ -18,6 +18,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] log = { workspace = true, default-features = true } -thiserror = { workspace = true } sp-core = { workspace = true, default-features = true } sp-wasm-interface = { workspace = true, default-features = true } +thiserror = { workspace = true } diff --git a/substrate/client/allocator/src/freeing_bump.rs b/substrate/client/allocator/src/freeing_bump.rs index 144c0764540db3f14f6f9bea5b113506f92879d9..405916adc3c3ff1bba878b7513fb1125c2f3462a 100644 --- a/substrate/client/allocator/src/freeing_bump.rs +++ b/substrate/client/allocator/src/freeing_bump.rs @@ -182,7 +182,7 @@ const NIL_MARKER: u32 = u32::MAX; enum Link { /// Nil, denotes that there is no next element. Nil, - /// Link to the next element represented as a pointer to the a header. + /// Link to the next element represented as a pointer to the header. Ptr(u32), } diff --git a/substrate/client/api/Cargo.toml b/substrate/client/api/Cargo.toml index 670c746844672cc531174c18ad2ede216df0ba44..dede50fc01e8cacec8063fb1b3fdeca8d29c381a 100644 --- a/substrate/client/api/Cargo.toml +++ b/substrate/client/api/Cargo.toml @@ -41,6 +41,5 @@ sp-storage = { workspace = true, default-features = true } sp-trie = { workspace = true, default-features = true } [dev-dependencies] -thiserror = { workspace = true } -sp-test-primitives = { workspace = true } substrate-test-runtime = { workspace = true } +thiserror = { workspace = true } diff --git a/substrate/client/api/src/proof_provider.rs b/substrate/client/api/src/proof_provider.rs index 7f60f856ae8095b9d8f9e445f479cbb549473074..9043d3482723911c90263c80045af36facea8db0 100644 --- a/substrate/client/api/src/proof_provider.rs +++ b/substrate/client/api/src/proof_provider.rs @@ -82,7 +82,7 @@ pub trait ProofProvider<Block: BlockT> { ) -> sp_blockchain::Result<Vec<(KeyValueStorageLevel, bool)>>; /// Verify read storage proof for a set of keys. - /// Returns collected key-value pairs and a the nested state + /// Returns collected key-value pairs and the nested state /// depth of current iteration or 0 if completed. fn verify_range_proof( &self, diff --git a/substrate/client/authority-discovery/Cargo.toml b/substrate/client/authority-discovery/Cargo.toml index fc88d07ef936e46d28c278924fbdea5c45b45141..ac1891451ec0d93b37264909659b4e03d0b007d1 100644 --- a/substrate/client/authority-discovery/Cargo.toml +++ b/substrate/client/authority-discovery/Cargo.toml @@ -20,17 +20,17 @@ targets = ["x86_64-unknown-linux-gnu"] prost-build = { workspace = true } [dependencies] +async-trait = { workspace = true } codec = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } ip_network = { workspace = true } -multihash = { workspace = true } linked_hash_set = { workspace = true } log = { workspace = true, default-features = true } +multihash = { workspace = true } +prometheus-endpoint = { workspace = true, default-features = true } prost = { workspace = true } rand = { workspace = true, default-features = true } -thiserror = { workspace = true } -prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } @@ -40,7 +40,7 @@ sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -async-trait = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] quickcheck = { workspace = true } diff --git a/substrate/client/authority-discovery/src/worker.rs b/substrate/client/authority-discovery/src/worker.rs index ba82910efcdf2af16212ab2c1d3be26bd56f54e3..6630b7157d967e4d2005e635b6a746fbf94d3ca8 100644 --- a/substrate/client/authority-discovery/src/worker.rs +++ b/substrate/client/authority-discovery/src/worker.rs @@ -677,6 +677,9 @@ where metrics.dht_event_received.with_label_values(&["put_record_req"]).inc(); } }, + DhtEvent::StartProvidingFailed(..) => {}, + DhtEvent::ProvidersFound(..) => {}, + DhtEvent::ProvidersNotFound(..) => {}, } } diff --git a/substrate/client/authority-discovery/src/worker/tests.rs b/substrate/client/authority-discovery/src/worker/tests.rs index 6c3a3b56b1cbfd34f931561996fecc5eb148b5ab..c147715856555963fddc09f58111bbf39423e338 100644 --- a/substrate/client/authority-discovery/src/worker/tests.rs +++ b/substrate/client/authority-discovery/src/worker/tests.rs @@ -231,6 +231,18 @@ impl NetworkDHTProvider for TestNetwork { .unbounded_send(TestNetworkEvent::StoreRecordCalled) .unwrap(); } + + fn start_providing(&self, _: KademliaKey) { + unimplemented!() + } + + fn stop_providing(&self, _: KademliaKey) { + unimplemented!() + } + + fn get_providers(&self, _: KademliaKey) { + unimplemented!() + } } impl NetworkStateInfo for TestNetwork { diff --git a/substrate/client/basic-authorship/src/basic_authorship.rs b/substrate/client/basic-authorship/src/basic_authorship.rs index 79e6fddae99fca12d9800152557b70e60a881519..2096af1c25bb771f7307dc5ee286c93217df804f 100644 --- a/substrate/client/basic-authorship/src/basic_authorship.rs +++ b/substrate/client/basic-authorship/src/basic_authorship.rs @@ -483,7 +483,7 @@ where match sc_block_builder::BlockBuilder::push(block_builder, pending_tx_data) { Ok(()) => { transaction_pushed = true; - debug!(target: LOG_TARGET, "[{:?}] Pushed to the block.", pending_tx_hash); + trace!(target: LOG_TARGET, "[{:?}] Pushed to the block.", pending_tx_hash); }, Err(ApplyExtrinsicFailed(Validity(e))) if e.exhausted_resources() => { pending_iterator.report_invalid(&pending_tx); @@ -565,20 +565,22 @@ where if log::log_enabled!(log::Level::Info) { info!( - "🎠Prepared block for proposing at {} ({} ms) [hash: {:?}; parent_hash: {}; extrinsics_count: {}", + "🎠Prepared block for proposing at {} ({} ms) hash: {:?}; parent_hash: {}; end: {:?}; extrinsics_count: {}", block.header().number(), block_took.as_millis(), <Block as BlockT>::Hash::from(block.header().hash()), block.header().parent_hash(), + end_reason, extrinsics.len() ) - } else if log::log_enabled!(log::Level::Debug) { - debug!( - "🎠Prepared block for proposing at {} ({} ms) [hash: {:?}; parent_hash: {}; {extrinsics_summary}", + } else if log::log_enabled!(log::Level::Trace) { + trace!( + "🎠Prepared block for proposing at {} ({} ms) hash: {:?}; parent_hash: {}; end: {:?}; {extrinsics_summary}", block.header().number(), block_took.as_millis(), <Block as BlockT>::Hash::from(block.header().hash()), block.header().parent_hash(), + end_reason ); } @@ -908,8 +910,8 @@ mod tests { let extrinsics_num = 5; let extrinsics = std::iter::once( Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Bob.into(), amount: 100, nonce: 0, } @@ -1014,7 +1016,7 @@ mod tests { }; let huge = |who| { ExtrinsicBuilder::new_fill_block(Perbill::from_parts(HUGE)) - .signer(AccountKeyring::numeric(who)) + .signer(Sr25519Keyring::numeric(who)) .build() }; @@ -1080,13 +1082,13 @@ mod tests { let tiny = |who| { ExtrinsicBuilder::new_fill_block(Perbill::from_parts(TINY)) - .signer(AccountKeyring::numeric(who)) + .signer(Sr25519Keyring::numeric(who)) .nonce(1) .build() }; let huge = |who| { ExtrinsicBuilder::new_fill_block(Perbill::from_parts(HUGE)) - .signer(AccountKeyring::numeric(who)) + .signer(Sr25519Keyring::numeric(who)) .build() }; diff --git a/substrate/client/basic-authorship/src/lib.rs b/substrate/client/basic-authorship/src/lib.rs index adea7a3571dd88dbf83263e1166af0def3cff72b..13c75fd08c3c8a4ba1118381fa1d092ff59887e8 100644 --- a/substrate/client/basic-authorship/src/lib.rs +++ b/substrate/client/basic-authorship/src/lib.rs @@ -26,7 +26,7 @@ //! # use sp_runtime::generic::BlockId; //! # use std::{sync::Arc, time::Duration}; //! # use substrate_test_runtime_client::{ -//! # runtime::Transfer, AccountKeyring, +//! # runtime::Transfer, Sr25519Keyring, //! # DefaultTestClientBuilderExt, TestClientBuilderExt, //! # }; //! # use sc_transaction_pool::{BasicPool, FullChainApi}; diff --git a/substrate/client/block-builder/Cargo.toml b/substrate/client/block-builder/Cargo.toml index 08392e18227f93a2fec12ee7796433c68d4122c8..c61a5a7ad3c1940b34db6b16b01568cbd0d2e595 100644 --- a/substrate/client/block-builder/Cargo.toml +++ b/substrate/client/block-builder/Cargo.toml @@ -23,9 +23,9 @@ sp-api = { workspace = true, default-features = true } sp-block-builder = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } -sp-trie = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +sp-trie = { workspace = true, default-features = true } [dev-dependencies] sp-state-machine = { workspace = true, default-features = true } diff --git a/substrate/client/chain-spec/Cargo.toml b/substrate/client/chain-spec/Cargo.toml index 2e885240936fe94fcdd408a56e512eec2cf46f5a..f63ff6c64447893a26fb1614cd374581b62615ab 100644 --- a/substrate/client/chain-spec/Cargo.toml +++ b/substrate/client/chain-spec/Cargo.toml @@ -16,31 +16,31 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +array-bytes = { workspace = true, default-features = true } clap = { features = ["derive"], optional = true, workspace = true } codec = { features = ["derive"], workspace = true } +docify = { workspace = true } +log = { workspace = true } memmap2 = { workspace = true } -serde = { features = ["derive"], workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } sc-chain-spec-derive = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } -sp-io = { workspace = true } sc-network = { workspace = true, default-features = true } sc-telemetry = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } sp-genesis-builder = { workspace = true, default-features = true } +sp-io = { workspace = true } sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } -log = { workspace = true } sp-tracing = { workspace = true, default-features = true } -array-bytes = { workspace = true, default-features = true } -docify = { workspace = true } [dev-dependencies] -substrate-test-runtime = { workspace = true } -sp-keyring = { workspace = true, default-features = true } +regex = { workspace = true } sp-application-crypto = { features = ["serde"], workspace = true } sp-consensus-babe = { features = ["serde"], workspace = true } -regex = { workspace = true } +sp-keyring = { workspace = true, default-features = true } +substrate-test-runtime = { workspace = true } diff --git a/substrate/client/chain-spec/src/chain_spec.rs b/substrate/client/chain-spec/src/chain_spec.rs index aa3c1ba3e6f132f2b400b6c1dd8a7af2ad1aa9b9..fa161f1202ab1a07556be62119cd86b867bf0e45 100644 --- a/substrate/client/chain-spec/src/chain_spec.rs +++ b/substrate/client/chain-spec/src/chain_spec.rs @@ -782,7 +782,7 @@ mod tests { use serde_json::{from_str, json, Value}; use sp_application_crypto::Ss58Codec; use sp_core::storage::well_known_keys; - use sp_keyring::AccountKeyring; + use sp_keyring::Sr25519Keyring; type TestSpec = ChainSpec; @@ -924,8 +924,8 @@ mod tests { }, "substrateTest": { "authorities": [ - AccountKeyring::Ferdie.public().to_ss58check(), - AccountKeyring::Alice.public().to_ss58check() + Sr25519Keyring::Ferdie.public().to_ss58check(), + Sr25519Keyring::Alice.public().to_ss58check() ], } })) @@ -980,8 +980,8 @@ mod tests { }, "substrateTest": { "authorities": [ - AccountKeyring::Ferdie.public().to_ss58check(), - AccountKeyring::Alice.public().to_ss58check() + Sr25519Keyring::Ferdie.public().to_ss58check(), + Sr25519Keyring::Alice.public().to_ss58check() ], } })) @@ -1083,8 +1083,8 @@ mod tests { "invalid_pallet": {}, "substrateTest": { "authorities": [ - AccountKeyring::Ferdie.public().to_ss58check(), - AccountKeyring::Alice.public().to_ss58check() + Sr25519Keyring::Ferdie.public().to_ss58check(), + Sr25519Keyring::Alice.public().to_ss58check() ], } })) diff --git a/substrate/client/chain-spec/src/genesis_config_builder.rs b/substrate/client/chain-spec/src/genesis_config_builder.rs index 5fe8f9dc053c136d038953e77f452c5a46ae671e..c7b5ae4bf168076b5aefe6eff10d402724def908 100644 --- a/substrate/client/chain-spec/src/genesis_config_builder.rs +++ b/substrate/client/chain-spec/src/genesis_config_builder.rs @@ -196,7 +196,7 @@ mod tests { <GenesisConfigBuilderRuntimeCaller>::new(substrate_test_runtime::wasm_binary_unwrap()) .get_default_config() .unwrap(); - let expected = r#"{"babe": {"authorities": [], "epochConfig": {"allowed_slots": "PrimaryAndSecondaryVRFSlots", "c": [1, 4]}}, "balances": {"balances": []}, "substrateTest": {"authorities": []}, "system": {}}"#; + let expected = r#"{"babe": {"authorities": [], "epochConfig": {"allowed_slots": "PrimaryAndSecondaryVRFSlots", "c": [1, 4]}}, "balances": {"balances": [], "devAccounts": null}, "substrateTest": {"authorities": []}, "system": {}}"#; assert_eq!(from_str::<Value>(expected).unwrap(), config); } diff --git a/substrate/client/cli/Cargo.toml b/substrate/client/cli/Cargo.toml index f0b9f8f9b9051587eb11d626b79e9c5d2b76fe09..d7b4489b6cc5f842106bbfaea73daaeea77f0268 100644 --- a/substrate/client/cli/Cargo.toml +++ b/substrate/client/cli/Cargo.toml @@ -19,13 +19,13 @@ targets = ["x86_64-unknown-linux-gnu"] array-bytes = { workspace = true, default-features = true } chrono = { workspace = true } clap = { features = ["derive", "string", "wrap_help"], workspace = true } +codec = { workspace = true, default-features = true } fdlimit = { workspace = true } futures = { workspace = true } itertools = { workspace = true } libp2p-identity = { features = ["ed25519", "peerid"], workspace = true } log = { workspace = true, default-features = true } names = { workspace = true } -codec = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } regex = { workspace = true } rpassword = { workspace = true } @@ -34,7 +34,6 @@ serde_json = { workspace = true, default-features = true } thiserror = { workspace = true } # personal fork here as workaround for: https://github.com/rust-bitcoin/rust-bip39/pull/64 bip39 = { package = "parity-bip39", version = "2.0.1", features = ["rand"] } -tokio = { features = ["parking_lot", "rt-multi-thread", "signal"], workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-client-db = { workspace = true } sc-keystore = { workspace = true, default-features = true } @@ -52,11 +51,12 @@ sp-keystore = { workspace = true, default-features = true } sp-panic-handler = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } +tokio = { features = ["parking_lot", "rt-multi-thread", "signal"], workspace = true, default-features = true } [dev-dependencies] -tempfile = { workspace = true } futures-timer = { workspace = true } sp-tracing = { workspace = true, default-features = true } +tempfile = { workspace = true } [features] default = ["rocksdb"] diff --git a/substrate/client/cli/src/params/shared_params.rs b/substrate/client/cli/src/params/shared_params.rs index 465372fba17d4bb8036477d587e37f12cdd414d6..e0c52deb44cad686be9197169aa4de2bc38a03d9 100644 --- a/substrate/client/cli/src/params/shared_params.rs +++ b/substrate/client/cli/src/params/shared_params.rs @@ -33,10 +33,12 @@ pub struct SharedParams { /// Specify the development chain. /// - /// This flag sets `--chain=dev`, `--force-authoring`, `--rpc-cors=all`, - /// `--alice`, and `--tmp` flags, unless explicitly overridden. - /// It also disables local peer discovery (see --no-mdns and --discover-local) - #[arg(long, conflicts_with_all = &["chain"])] + /// This flag sets `--chain=dev`, `--force-authoring`, `--rpc-cors=all`, `--alice`, and `--tmp` + /// flags, unless explicitly overridden. It also disables local peer discovery (see `--no-mdns` + /// and `--discover-local`). With this flag some nodes might start with manual seal, producing + /// blocks at certain events (e.g. `polkadot-omni-node`, which produces blocks at certain + /// intervals dictated by `--dev-block-time`). + #[arg(long)] pub dev: bool, /// Specify custom base path. @@ -109,12 +111,8 @@ impl SharedParams { pub fn chain_id(&self, is_dev: bool) -> String { match self.chain { Some(ref chain) => chain.clone(), - None => - if is_dev { - "dev".into() - } else { - "".into() - }, + None if is_dev => "dev".into(), + _ => "".into(), } } diff --git a/substrate/client/consensus/aura/Cargo.toml b/substrate/client/consensus/aura/Cargo.toml index 98e8ad676be3c9b64aafdc96b08434c0462de62c..6af6736171182230c101cd05514dc9aaa94e4ab3 100644 --- a/substrate/client/consensus/aura/Cargo.toml +++ b/substrate/client/consensus/aura/Cargo.toml @@ -20,7 +20,6 @@ async-trait = { workspace = true } codec = { workspace = true, default-features = true } futures = { workspace = true } log = { workspace = true, default-features = true } -thiserror = { workspace = true } prometheus-endpoint = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } @@ -38,10 +37,10 @@ sp-core = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] parking_lot = { workspace = true, default-features = true } -tempfile = { workspace = true } sc-keystore = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-test = { workspace = true } @@ -49,4 +48,5 @@ sp-keyring = { workspace = true, default-features = true } sp-timestamp = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } +tempfile = { workspace = true } tokio = { workspace = true, default-features = true } diff --git a/substrate/client/consensus/babe/Cargo.toml b/substrate/client/consensus/babe/Cargo.toml index af55e72a9b7eed54a956f2c283c06fab0b494294..305409b80c787af832484a182add01b7ecf27a30 100644 --- a/substrate/client/consensus/babe/Cargo.toml +++ b/substrate/client/consensus/babe/Cargo.toml @@ -19,14 +19,13 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = { workspace = true } codec = { features = ["derive"], workspace = true, default-features = true } +fork-tree = { workspace = true, default-features = true } futures = { workspace = true } log = { workspace = true, default-features = true } num-bigint = { workspace = true } num-rational = { workspace = true } num-traits = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -thiserror = { workspace = true } -fork-tree = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } @@ -46,11 +45,12 @@ sp-crypto-hashing = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] sc-block-builder = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } sc-network-test = { workspace = true } +sp-keyring = { workspace = true, default-features = true } sp-timestamp = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } diff --git a/substrate/client/consensus/babe/rpc/Cargo.toml b/substrate/client/consensus/babe/rpc/Cargo.toml index ce5b1baec0b57700b5831a5d86eabd62a841c237..3e383418993873cd2cb5994bdeb1d70eb500ac81 100644 --- a/substrate/client/consensus/babe/rpc/Cargo.toml +++ b/substrate/client/consensus/babe/rpc/Cargo.toml @@ -16,13 +16,12 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } futures = { workspace = true } -serde = { features = ["derive"], workspace = true, default-features = true } -thiserror = { workspace = true } +jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } sc-consensus-babe = { workspace = true, default-features = true } sc-consensus-epochs = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-application-crypto = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } @@ -31,12 +30,13 @@ sp-consensus-babe = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] -serde_json = { workspace = true, default-features = true } -tokio = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-keystore = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } +tokio = { workspace = true, default-features = true } diff --git a/substrate/client/consensus/beefy/Cargo.toml b/substrate/client/consensus/beefy/Cargo.toml index 900a44b95e0442159c3efb42965cc256d5998e9c..bfe7e2c3d5dc5c983c3521f337602a57a7d1b1b1 100644 --- a/substrate/client/consensus/beefy/Cargo.toml +++ b/substrate/client/consensus/beefy/Cargo.toml @@ -20,8 +20,6 @@ fnv = { workspace = true } futures = { workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -thiserror = { workspace = true } -wasm-timer = { workspace = true } prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } @@ -40,18 +38,20 @@ sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } tokio = { workspace = true, default-features = true } +wasm-timer = { workspace = true } [dev-dependencies] -serde = { workspace = true, default-features = true } -tempfile = { workspace = true } sc-block-builder = { workspace = true, default-features = true } sc-network-test = { workspace = true } +serde = { workspace = true, default-features = true } sp-consensus-grandpa = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-mmr-primitives = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } +tempfile = { workspace = true } [features] # This feature adds BLS crypto primitives. It should not be used in production since diff --git a/substrate/client/consensus/beefy/rpc/Cargo.toml b/substrate/client/consensus/beefy/rpc/Cargo.toml index e1956dacf396125c8b7a08a2e43da5d6b75b43b7..f8f24250ad93d1c1bbcfc92173e24ccac5a59293 100644 --- a/substrate/client/consensus/beefy/rpc/Cargo.toml +++ b/substrate/client/consensus/beefy/rpc/Cargo.toml @@ -17,17 +17,17 @@ futures = { workspace = true } jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true, default-features = true } -thiserror = { workspace = true } sc-consensus-beefy = { workspace = true, default-features = true } -sp-consensus-beefy = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } +sp-application-crypto = { workspace = true, default-features = true } +sp-consensus-beefy = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] -serde_json = { workspace = true, default-features = true } sc-rpc = { features = ["test-helpers"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } diff --git a/substrate/client/consensus/common/Cargo.toml b/substrate/client/consensus/common/Cargo.toml index 77cd50ad784bb5e055480466036e573b9ec7cbc8..1b0f799f81bca89fe4fee6025e7f64b5014dbe83 100644 --- a/substrate/client/consensus/common/Cargo.toml +++ b/substrate/client/consensus/common/Cargo.toml @@ -21,18 +21,18 @@ futures = { features = ["thread-pool"], workspace = true } log = { workspace = true, default-features = true } mockall = { workspace = true } parking_lot = { workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true, default-features = true } -thiserror = { workspace = true } prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] sp-test-primitives = { workspace = true } diff --git a/substrate/client/consensus/grandpa/Cargo.toml b/substrate/client/consensus/grandpa/Cargo.toml index 65ba39d34c214014c0a2f29e67fbe211e4763b25..f361fac54af71e637de58591c64f3f3865552077 100644 --- a/substrate/client/consensus/grandpa/Cargo.toml +++ b/substrate/client/consensus/grandpa/Cargo.toml @@ -20,48 +20,48 @@ targets = ["x86_64-unknown-linux-gnu"] ahash = { workspace = true } array-bytes = { workspace = true, default-features = true } async-trait = { workspace = true } +codec = { features = ["derive"], workspace = true, default-features = true } dyn-clone = { workspace = true } finality-grandpa = { features = ["derive-codec"], workspace = true, default-features = true } +fork-tree = { workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } log = { workspace = true, default-features = true } -codec = { features = ["derive"], workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -rand = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -thiserror = { workspace = true } -fork-tree = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } +rand = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } -sc-transaction-pool-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } -sc-network-gossip = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } +sc-network-gossip = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } sc-telemetry = { workspace = true, default-features = true } +sc-transaction-pool-api = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-application-crypto = { workspace = true, default-features = true } sp-arithmetic = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } +sp-consensus-grandpa = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } -sp-consensus-grandpa = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } finality-grandpa = { features = ["derive-codec", "test-helpers"], workspace = true, default-features = true } -serde = { workspace = true, default-features = true } -tokio = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-test = { workspace = true } +serde = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } +tokio = { workspace = true, default-features = true } diff --git a/substrate/client/consensus/grandpa/rpc/Cargo.toml b/substrate/client/consensus/grandpa/rpc/Cargo.toml index 86513ac5df153f600f898bffa88513bd19d263e4..1fb8bd9367c4f715cdbe717d0028d87b2e480fab 100644 --- a/substrate/client/consensus/grandpa/rpc/Cargo.toml +++ b/substrate/client/consensus/grandpa/rpc/Cargo.toml @@ -13,25 +13,25 @@ homepage.workspace = true workspace = true [dependencies] +codec = { features = ["derive"], workspace = true, default-features = true } finality-grandpa = { features = ["derive-codec"], workspace = true, default-features = true } futures = { workspace = true } jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } log = { workspace = true, default-features = true } -codec = { features = ["derive"], workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true, default-features = true } -thiserror = { workspace = true } sc-client-api = { workspace = true, default-features = true } sc-consensus-grandpa = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] sc-block-builder = { workspace = true, default-features = true } sc-rpc = { features = ["test-helpers"], workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } sp-consensus-grandpa = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } diff --git a/substrate/client/consensus/grandpa/src/warp_proof.rs b/substrate/client/consensus/grandpa/src/warp_proof.rs index a79581b1e9f13092b917b64a443af31aa7cd8a27..ada3a45e186e0a48de5f6b1b7e912df7dd5a4c3a 100644 --- a/substrate/client/consensus/grandpa/src/warp_proof.rs +++ b/substrate/client/consensus/grandpa/src/warp_proof.rs @@ -174,10 +174,20 @@ impl<Block: BlockT> WarpSyncProof<Block> { let header = blockchain.header(latest_justification.target().1)? .expect("header hash corresponds to a justification in db; must exist in db as well; qed."); - proofs.push(WarpSyncFragment { header, justification: latest_justification }) + let proof = WarpSyncFragment { header, justification: latest_justification }; + + // Check for the limit. We remove some bytes from the maximum size, because we're + // only counting the size of the `WarpSyncFragment`s. The extra margin is here + // to leave room for rest of the data (the size of the `Vec` and the boolean). + if proofs_encoded_len + proof.encoded_size() >= MAX_WARP_SYNC_PROOF_SIZE - 50 { + false + } else { + proofs.push(proof); + true + } + } else { + true } - - true }; let final_outcome = WarpSyncProof { proofs, is_finished }; diff --git a/substrate/client/consensus/manual-seal/Cargo.toml b/substrate/client/consensus/manual-seal/Cargo.toml index 49111434015af9cbba80a091102259a781972853..4d232f7256cb7e27e82a8ec730860c3525f7108a 100644 --- a/substrate/client/consensus/manual-seal/Cargo.toml +++ b/substrate/client/consensus/manual-seal/Cargo.toml @@ -16,15 +16,13 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } assert_matches = { workspace = true } async-trait = { workspace = true } codec = { workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } +jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } log = { workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true, default-features = true } -thiserror = { workspace = true } prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } @@ -33,6 +31,7 @@ sc-consensus-babe = { workspace = true, default-features = true } sc-consensus-epochs = { workspace = true, default-features = true } sc-transaction-pool = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } @@ -44,9 +43,10 @@ sp-inherents = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-timestamp = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] -tokio = { features = ["macros", "rt-multi-thread"], workspace = true, default-features = true } sc-basic-authorship = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } substrate-test-runtime-transaction-pool = { workspace = true } +tokio = { features = ["macros", "rt-multi-thread"], workspace = true, default-features = true } diff --git a/substrate/client/consensus/manual-seal/src/lib.rs b/substrate/client/consensus/manual-seal/src/lib.rs index 39f8f8609d8d7f35867cc8108ad6667263fe5b74..af9bcc8d56d6f45a7eddcb46b8ec8fa981ab4bc6 100644 --- a/substrate/client/consensus/manual-seal/src/lib.rs +++ b/substrate/client/consensus/manual-seal/src/lib.rs @@ -353,7 +353,7 @@ mod tests { use sp_inherents::InherentData; use sp_runtime::generic::{Digest, DigestItem}; use substrate_test_runtime_client::{ - AccountKeyring::*, DefaultTestClientBuilderExt, TestClientBuilder, TestClientBuilderExt, + DefaultTestClientBuilderExt, Sr25519Keyring::*, TestClientBuilder, TestClientBuilderExt, }; use substrate_test_runtime_transaction_pool::{uxt, TestApi}; diff --git a/substrate/client/consensus/pow/Cargo.toml b/substrate/client/consensus/pow/Cargo.toml index bc89deb0b50d60d2872cc73aba7705f360b53381..a051bf3f47794346000c7f5b82c920db7abcbf78 100644 --- a/substrate/client/consensus/pow/Cargo.toml +++ b/substrate/client/consensus/pow/Cargo.toml @@ -22,7 +22,6 @@ futures = { workspace = true } futures-timer = { workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -thiserror = { workspace = true } prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } @@ -34,3 +33,4 @@ sp-consensus-pow = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } diff --git a/substrate/client/db/Cargo.toml b/substrate/client/db/Cargo.toml index 5725155579fc70450afad9ff46580afc721d2ea0..9268ccf8a0645018126f1fb486da99daf78ef539 100644 --- a/substrate/client/db/Cargo.toml +++ b/substrate/client/db/Cargo.toml @@ -39,15 +39,14 @@ sp-state-machine = { workspace = true, default-features = true } sp-trie = { workspace = true, default-features = true } [dev-dependencies] +array-bytes = { workspace = true, default-features = true } criterion = { workspace = true, default-features = true } +kitchensink-runtime = { workspace = true } kvdb-rocksdb = { workspace = true } rand = { workspace = true, default-features = true } -tempfile = { workspace = true } -quickcheck = { workspace = true } -kitchensink-runtime = { workspace = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } -array-bytes = { workspace = true, default-features = true } +tempfile = { workspace = true } [features] default = [] diff --git a/substrate/client/executor/Cargo.toml b/substrate/client/executor/Cargo.toml index ca78afd470681227c07a2fad61bcc5b945d01e94..5cb4936e7534ac9986fa196f2e12009fb0c1a323 100644 --- a/substrate/client/executor/Cargo.toml +++ b/substrate/client/executor/Cargo.toml @@ -38,21 +38,21 @@ sp-wasm-interface = { workspace = true, default-features = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } assert_matches = { workspace = true } -wat = { workspace = true } +criterion = { workspace = true, default-features = true } +num_cpus = { workspace = true } +paste = { workspace = true, default-features = true } +regex = { workspace = true } sc-runtime-test = { workspace = true } -substrate-test-runtime = { workspace = true } +sc-tracing = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } -sp-state-machine = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-maybe-compressed-blob = { workspace = true, default-features = true } -sc-tracing = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-state-machine = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -tracing-subscriber = { workspace = true } -paste = { workspace = true, default-features = true } -regex = { workspace = true } -criterion = { workspace = true, default-features = true } -num_cpus = { workspace = true } +substrate-test-runtime = { workspace = true } tempfile = { workspace = true } +tracing-subscriber = { workspace = true } +wat = { workspace = true } [[bench]] name = "bench" diff --git a/substrate/client/executor/common/Cargo.toml b/substrate/client/executor/common/Cargo.toml index 58fb0b423f24235a0ddd277b3feaca5c193482d2..aaf13a8ae768808eb1da9f951dbbc79d9690c4e6 100644 --- a/substrate/client/executor/common/Cargo.toml +++ b/substrate/client/executor/common/Cargo.toml @@ -17,12 +17,12 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -thiserror = { workspace = true } -wasm-instrument = { workspace = true, default-features = true } +polkavm = { workspace = true } sc-allocator = { workspace = true, default-features = true } sp-maybe-compressed-blob = { workspace = true, default-features = true } sp-wasm-interface = { workspace = true, default-features = true } -polkavm = { workspace = true } +thiserror = { workspace = true } +wasm-instrument = { workspace = true, default-features = true } [features] default = [] diff --git a/substrate/client/executor/common/src/error.rs b/substrate/client/executor/common/src/error.rs index 9d489eaae4200035b6e0ffc0b1bafb045b2ce2b7..a94c1d493134227a08ee77e9457f0954ffb7766c 100644 --- a/substrate/client/executor/common/src/error.rs +++ b/substrate/client/executor/common/src/error.rs @@ -150,8 +150,8 @@ pub enum WasmError { Other(String), } -impl From<polkavm::ProgramParseError> for WasmError { - fn from(error: polkavm::ProgramParseError) -> Self { +impl From<polkavm::program::ProgramParseError> for WasmError { + fn from(error: polkavm::program::ProgramParseError) -> Self { WasmError::Other(error.to_string()) } } diff --git a/substrate/client/executor/common/src/runtime_blob/runtime_blob.rs b/substrate/client/executor/common/src/runtime_blob/runtime_blob.rs index d689083b2f85cd7f3fc16f220c1b45a474dd87f5..e3f4b4ad97745edd45d69113a6697fbb091c897d 100644 --- a/substrate/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/substrate/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -17,6 +17,7 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. use crate::{error::WasmError, wasm_runtime::HeapAllocStrategy}; +use polkavm::ArcBytes; use wasm_instrument::parity_wasm::elements::{ deserialize_buffer, serialize, ExportEntry, External, Internal, MemorySection, MemoryType, Module, Section, @@ -29,7 +30,7 @@ pub struct RuntimeBlob(BlobKind); #[derive(Clone)] enum BlobKind { WebAssembly(Module), - PolkaVM(polkavm::ProgramBlob<'static>), + PolkaVM((polkavm::ProgramBlob, ArcBytes)), } impl RuntimeBlob { @@ -52,9 +53,9 @@ impl RuntimeBlob { pub fn new(raw_blob: &[u8]) -> Result<Self, WasmError> { if raw_blob.starts_with(b"PVM\0") { if crate::is_polkavm_enabled() { - return Ok(Self(BlobKind::PolkaVM( - polkavm::ProgramBlob::parse(raw_blob)?.into_owned(), - ))); + let raw = ArcBytes::from(raw_blob); + let blob = polkavm::ProgramBlob::parse(raw.clone())?; + return Ok(Self(BlobKind::PolkaVM((blob, raw)))); } else { return Err(WasmError::Other("expected a WASM runtime blob, found a PolkaVM runtime blob; set the 'SUBSTRATE_ENABLE_POLKAVM' environment variable to enable the experimental PolkaVM-based executor".to_string())); } @@ -192,7 +193,7 @@ impl RuntimeBlob { match self.0 { BlobKind::WebAssembly(raw_module) => serialize(raw_module).expect("serializing into a vec should succeed; qed"), - BlobKind::PolkaVM(ref blob) => blob.as_bytes().to_vec(), + BlobKind::PolkaVM(ref blob) => blob.1.to_vec(), } } @@ -227,7 +228,7 @@ impl RuntimeBlob { pub fn as_polkavm_blob(&self) -> Option<&polkavm::ProgramBlob> { match self.0 { BlobKind::WebAssembly(..) => None, - BlobKind::PolkaVM(ref blob) => Some(blob), + BlobKind::PolkaVM((ref blob, _)) => Some(blob), } } } diff --git a/substrate/client/executor/polkavm/src/lib.rs b/substrate/client/executor/polkavm/src/lib.rs index 1bd72eb33d309997832de5fca9250ca651090ce7..134f9ea3d8c42eca4bb5ddb89d4b4d46bb40e8ca 100644 --- a/substrate/client/executor/polkavm/src/lib.rs +++ b/substrate/client/executor/polkavm/src/lib.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. -use polkavm::{Caller, Reg}; +use polkavm::{CallError, Caller, Reg}; use sc_executor_common::{ error::{Error, WasmError}, wasm_runtime::{AllocationStats, WasmInstance, WasmModule}, @@ -26,10 +26,10 @@ use sp_wasm_interface::{ }; #[repr(transparent)] -pub struct InstancePre(polkavm::InstancePre<()>); +pub struct InstancePre(polkavm::InstancePre<(), String>); #[repr(transparent)] -pub struct Instance(polkavm::Instance<()>); +pub struct Instance(polkavm::Instance<(), String>); impl WasmModule for InstancePre { fn new_instance(&self) -> Result<Box<dyn WasmInstance>, Error> { @@ -43,11 +43,13 @@ impl WasmInstance for Instance { name: &str, raw_data: &[u8], ) -> (Result<Vec<u8>, Error>, Option<AllocationStats>) { - let Some(method_index) = self.0.module().lookup_export(name) else { - return ( - Err(format!("cannot call into the runtime: export not found: '{name}'").into()), - None, - ); + let pc = match self.0.module().exports().find(|e| e.symbol() == name) { + Some(export) => export.program_counter(), + None => + return ( + Err(format!("cannot call into the runtime: export not found: '{name}'").into()), + None, + ), }; let Ok(raw_data_length) = u32::try_from(raw_data.len()) else { @@ -58,56 +60,60 @@ impl WasmInstance for Instance { }; // TODO: This will leak guest memory; find a better solution. - let mut state_args = polkavm::StateArgs::new(); - // Make sure the memory is cleared... - state_args.reset_memory(true); - // ...and allocate space for the input payload. - state_args.sbrk(raw_data_length); + // Make sure that the memory is cleared... + if let Err(err) = self.0.reset_memory() { + return ( + Err(format!( + "call into the runtime method '{name}' failed: reset memory failed: {err}" + ) + .into()), + None, + ); + } - match self.0.update_state(state_args) { - Ok(()) => {}, - Err(polkavm::ExecutionError::Trap(trap)) => { - return (Err(format!("call into the runtime method '{name}' failed: failed to prepare the guest's memory: {trap}").into()), None); - }, - Err(polkavm::ExecutionError::Error(error)) => { - return (Err(format!("call into the runtime method '{name}' failed: failed to prepare the guest's memory: {error}").into()), None); - }, - Err(polkavm::ExecutionError::OutOfGas) => unreachable!("gas metering is never enabled"), + // ... and allocate space for the input payload. + if let Err(err) = self.0.sbrk(raw_data_length) { + return ( + Err(format!( + "call into the runtime method '{name}' failed: reset memory failed: {err}" + ) + .into()), + None, + ); } // Grab the address of where the guest's heap starts; that's where we've just allocated // the memory for the input payload. let data_pointer = self.0.module().memory_map().heap_base(); - if let Err(error) = self.0.write_memory(data_pointer, raw_data) { - return (Err(format!("call into the runtime method '{name}': failed to write the input payload into guest memory: {error}").into()), None); + if let Err(err) = self.0.write_memory(data_pointer, raw_data) { + return (Err(format!("call into the runtime method '{name}': failed to write the input payload into guest memory: {err}").into()), None); } - let mut state = (); - let mut call_args = polkavm::CallArgs::new(&mut state, method_index); - call_args.args_untyped(&[data_pointer, raw_data_length]); - - match self.0.call(Default::default(), call_args) { + match self.0.call_typed(&mut (), pc, (data_pointer, raw_data_length)) { Ok(()) => {}, - Err(polkavm::ExecutionError::Trap(trap)) => { + Err(CallError::Trap) => return ( - Err(format!("call into the runtime method '{name}' failed: {trap}").into()), + Err(format!("call into the runtime method '{name}' failed: trap").into()), None, - ); - }, - Err(polkavm::ExecutionError::Error(error)) => { + ), + Err(CallError::Error(err)) => return ( - Err(format!("call into the runtime method '{name}' failed: {error}").into()), + Err(format!("call into the runtime method '{name}' failed: {err}").into()), None, - ); - }, - Err(polkavm::ExecutionError::OutOfGas) => unreachable!("gas metering is never enabled"), - } + ), + Err(CallError::User(err)) => + return ( + Err(format!("call into the runtime method '{name}' failed: {err}").into()), + None, + ), + Err(CallError::NotEnoughGas) => unreachable!("gas metering is never enabled"), + }; - let result_pointer = self.0.get_reg(Reg::A0); - let result_length = self.0.get_reg(Reg::A1); - let output = match self.0.read_memory_into_vec(result_pointer, result_length) { + let result_pointer = self.0.reg(Reg::A0); + let result_length = self.0.reg(Reg::A1); + let output = match self.0.read_memory(result_pointer as u32, result_length as u32) { Ok(output) => output, Err(error) => { return (Err(format!("call into the runtime method '{name}' failed: failed to read the return payload: {error}").into()), None) @@ -127,20 +133,31 @@ impl<'r, 'a> FunctionContext for Context<'r, 'a> { dest: &mut [u8], ) -> sp_wasm_interface::Result<()> { self.0 - .read_memory_into_slice(u32::from(address), dest) + .instance + .read_memory_into(u32::from(address), dest) .map_err(|error| error.to_string()) .map(|_| ()) } fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> sp_wasm_interface::Result<()> { - self.0.write_memory(u32::from(address), data).map_err(|error| error.to_string()) + self.0 + .instance + .write_memory(u32::from(address), data) + .map_err(|error| error.to_string()) } fn allocate_memory(&mut self, size: WordSize) -> sp_wasm_interface::Result<Pointer<u8>> { - let pointer = self.0.sbrk(0).expect("fetching the current heap pointer never fails"); + let pointer = match self.0.instance.sbrk(0) { + Ok(pointer) => pointer.expect("fetching the current heap pointer never fails"), + Err(err) => return Err(format!("sbrk failed: {err}")), + }; // TODO: This will leak guest memory; find a better solution. - self.0.sbrk(size).ok_or_else(|| String::from("allocation failed"))?; + match self.0.instance.sbrk(size) { + Ok(Some(_)) => (), + Ok(None) => return Err(String::from("allocation error")), + Err(err) => return Err(format!("sbrk failed: {err}")), + } Ok(Pointer::new(pointer)) } @@ -155,41 +172,46 @@ impl<'r, 'a> FunctionContext for Context<'r, 'a> { } } -fn call_host_function( - caller: &mut Caller<()>, - function: &dyn Function, -) -> Result<(), polkavm::Trap> { +fn call_host_function(caller: &mut Caller<()>, function: &dyn Function) -> Result<(), String> { let mut args = [Value::I64(0); Reg::ARG_REGS.len()]; let mut nth_reg = 0; for (nth_arg, kind) in function.signature().args.iter().enumerate() { match kind { ValueType::I32 => { - args[nth_arg] = Value::I32(caller.get_reg(Reg::ARG_REGS[nth_reg]) as i32); + args[nth_arg] = Value::I32(caller.instance.reg(Reg::ARG_REGS[nth_reg]) as i32); nth_reg += 1; }, ValueType::F32 => { - args[nth_arg] = Value::F32(caller.get_reg(Reg::ARG_REGS[nth_reg])); - nth_reg += 1; - }, - ValueType::I64 => { - let value_lo = caller.get_reg(Reg::ARG_REGS[nth_reg]); - nth_reg += 1; - - let value_hi = caller.get_reg(Reg::ARG_REGS[nth_reg]); - nth_reg += 1; - - args[nth_arg] = - Value::I64((u64::from(value_lo) | (u64::from(value_hi) << 32)) as i64); - }, - ValueType::F64 => { - let value_lo = caller.get_reg(Reg::ARG_REGS[nth_reg]); + args[nth_arg] = Value::F32(caller.instance.reg(Reg::ARG_REGS[nth_reg]) as u32); nth_reg += 1; - - let value_hi = caller.get_reg(Reg::ARG_REGS[nth_reg]); - nth_reg += 1; - - args[nth_arg] = Value::F64(u64::from(value_lo) | (u64::from(value_hi) << 32)); }, + ValueType::I64 => + if caller.instance.is_64_bit() { + args[nth_arg] = Value::I64(caller.instance.reg(Reg::ARG_REGS[nth_reg]) as i64); + nth_reg += 1; + } else { + let value_lo = caller.instance.reg(Reg::ARG_REGS[nth_reg]); + nth_reg += 1; + + let value_hi = caller.instance.reg(Reg::ARG_REGS[nth_reg]); + nth_reg += 1; + + args[nth_arg] = + Value::I64((u64::from(value_lo) | (u64::from(value_hi) << 32)) as i64); + }, + ValueType::F64 => + if caller.instance.is_64_bit() { + args[nth_arg] = Value::F64(caller.instance.reg(Reg::ARG_REGS[nth_reg])); + nth_reg += 1; + } else { + let value_lo = caller.instance.reg(Reg::ARG_REGS[nth_reg]); + nth_reg += 1; + + let value_hi = caller.instance.reg(Reg::ARG_REGS[nth_reg]); + nth_reg += 1; + + args[nth_arg] = Value::F64(u64::from(value_lo) | (u64::from(value_hi) << 32)); + }, } } @@ -204,27 +226,33 @@ fn call_host_function( { Ok(value) => value, Err(error) => { - log::warn!("Call into the host function '{}' failed: {error}", function.name()); - return Err(polkavm::Trap::default()); + let name = function.name(); + return Err(format!("call into the host function '{name}' failed: {error}")) }, }; if let Some(value) = value { match value { Value::I32(value) => { - caller.set_reg(Reg::A0, value as u32); + caller.instance.set_reg(Reg::A0, value as u64); }, Value::F32(value) => { - caller.set_reg(Reg::A0, value); - }, - Value::I64(value) => { - caller.set_reg(Reg::A0, value as u32); - caller.set_reg(Reg::A1, (value >> 32) as u32); - }, - Value::F64(value) => { - caller.set_reg(Reg::A0, value as u32); - caller.set_reg(Reg::A1, (value >> 32) as u32); + caller.instance.set_reg(Reg::A0, value as u64); }, + Value::I64(value) => + if caller.instance.is_64_bit() { + caller.instance.set_reg(Reg::A0, value as u64); + } else { + caller.instance.set_reg(Reg::A0, value as u64); + caller.instance.set_reg(Reg::A1, (value >> 32) as u64); + }, + Value::F64(value) => + if caller.instance.is_64_bit() { + caller.instance.set_reg(Reg::A0, value as u64); + } else { + caller.instance.set_reg(Reg::A0, value as u64); + caller.instance.set_reg(Reg::A1, (value >> 32) as u64); + }, } } @@ -250,12 +278,16 @@ where }, }; - let module = polkavm::Module::from_blob(&engine, &polkavm::ModuleConfig::default(), blob)?; - let mut linker = polkavm::Linker::new(&engine); + let module = + polkavm::Module::from_blob(&engine, &polkavm::ModuleConfig::default(), blob.clone())?; + + let mut linker = polkavm::Linker::new(); + for function in H::host_functions() { - linker.func_new(function.name(), |mut caller| call_host_function(&mut caller, function))?; + linker.define_untyped(function.name(), |mut caller: Caller<()>| { + call_host_function(&mut caller, function) + })?; } - let instance_pre = linker.instantiate_pre(&module)?; Ok(Box::new(InstancePre(instance_pre))) } diff --git a/substrate/client/executor/wasmtime/Cargo.toml b/substrate/client/executor/wasmtime/Cargo.toml index ef8e5da876aa606e32790e9e8eb0ffb077ce2174..7ea94568e1b7f89f725c35d52e1e9c94ee370392 100644 --- a/substrate/client/executor/wasmtime/Cargo.toml +++ b/substrate/client/executor/wasmtime/Cargo.toml @@ -16,13 +16,18 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { workspace = true, default-features = true } cfg-if = { workspace = true } libc = { workspace = true } +log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } # When bumping wasmtime do not forget to also bump rustix # to exactly the same version as used by wasmtime! +anyhow = { workspace = true } +sc-allocator = { workspace = true, default-features = true } +sc-executor-common = { workspace = true, default-features = true } +sp-runtime-interface = { workspace = true, default-features = true } +sp-wasm-interface = { features = ["wasmtime"], workspace = true, default-features = true } wasmtime = { features = [ "cache", "cranelift", @@ -30,11 +35,6 @@ wasmtime = { features = [ "parallel-compilation", "pooling-allocator", ], workspace = true } -anyhow = { workspace = true } -sc-allocator = { workspace = true, default-features = true } -sc-executor-common = { workspace = true, default-features = true } -sp-runtime-interface = { workspace = true, default-features = true } -sp-wasm-interface = { features = ["wasmtime"], workspace = true, default-features = true } # Here we include the rustix crate in the exactly same semver-compatible version as used by # wasmtime and enable its 'use-libc' flag. @@ -45,10 +45,10 @@ sp-wasm-interface = { features = ["wasmtime"], workspace = true, default-feature rustix = { features = ["fs", "mm", "param", "std", "use-libc"], workspace = true } [dev-dependencies] -wat = { workspace = true } +cargo_metadata = { workspace = true } +codec = { workspace = true, default-features = true } +paste = { workspace = true, default-features = true } sc-runtime-test = { workspace = true } sp-io = { workspace = true, default-features = true } tempfile = { workspace = true } -paste = { workspace = true, default-features = true } -codec = { workspace = true, default-features = true } -cargo_metadata = { workspace = true } +wat = { workspace = true } diff --git a/substrate/client/informant/Cargo.toml b/substrate/client/informant/Cargo.toml index 87a4be320d689d984f0b8ac94c12f78fda84eaa8..209964e02ef38bcb2723b4193d01f3ab2b8f2463 100644 --- a/substrate/client/informant/Cargo.toml +++ b/substrate/client/informant/Cargo.toml @@ -21,8 +21,8 @@ futures = { workspace = true } futures-timer = { workspace = true } log = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } diff --git a/substrate/client/keystore/Cargo.toml b/substrate/client/keystore/Cargo.toml index d338bb1af61acfb39cde2213703247877bc067ad..e46fafbc3729c52483fa2633abe148fd66948e08 100644 --- a/substrate/client/keystore/Cargo.toml +++ b/substrate/client/keystore/Cargo.toml @@ -20,10 +20,10 @@ targets = ["x86_64-unknown-linux-gnu"] array-bytes = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } -thiserror = { workspace = true } sp-application-crypto = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/substrate/client/merkle-mountain-range/Cargo.toml b/substrate/client/merkle-mountain-range/Cargo.toml index 6639a10d33f10650ec35f8a4984df25bb3333a0c..7849eac5f5167a2a856750543cf75badb323fdf9 100644 --- a/substrate/client/merkle-mountain-range/Cargo.toml +++ b/substrate/client/merkle-mountain-range/Cargo.toml @@ -17,14 +17,14 @@ workspace = true codec = { workspace = true, default-features = true } futures = { workspace = true } log = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } +sc-offchain = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sp-consensus-beefy = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } +sp-consensus-beefy = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-mmr-primitives = { workspace = true, default-features = true } -sc-offchain = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } [dev-dependencies] diff --git a/substrate/client/network-gossip/Cargo.toml b/substrate/client/network-gossip/Cargo.toml index 94bc9a671f84b93912df606d313ca7416b673188..ea52913aea1608219019c080bffa6ecb12f7ecab 100644 --- a/substrate/client/network-gossip/Cargo.toml +++ b/substrate/client/network-gossip/Cargo.toml @@ -21,18 +21,18 @@ ahash = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } log = { workspace = true, default-features = true } -schnellru = { workspace = true } -tracing = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } +schnellru = { workspace = true } sp-runtime = { workspace = true, default-features = true } +tracing = { workspace = true, default-features = true } [dev-dependencies] -tokio = { workspace = true, default-features = true } async-trait = { workspace = true } codec = { features = ["derive"], workspace = true, default-features = true } quickcheck = { workspace = true } substrate-test-runtime-client = { workspace = true } +tokio = { workspace = true, default-features = true } diff --git a/substrate/client/network/Cargo.toml b/substrate/client/network/Cargo.toml index c8fd28e0810943a898c14ba25cad618f372769c2..19af70867658c7c37b982a25fb0891ff104003ca 100644 --- a/substrate/client/network/Cargo.toml +++ b/substrate/client/network/Cargo.toml @@ -34,54 +34,54 @@ futures-timer = { workspace = true } ip_network = { workspace = true } libp2p = { features = ["dns", "identify", "kad", "macros", "mdns", "noise", "ping", "request-response", "tcp", "tokio", "websocket", "yamux"], workspace = true } linked_hash_set = { workspace = true } +litep2p = { workspace = true } log = { workspace = true, default-features = true } mockall = { workspace = true } +once_cell = { workspace = true } parking_lot = { workspace = true, default-features = true } partial_sort = { workspace = true } pin-project = { workspace = true } -rand = { workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -smallvec = { workspace = true, default-features = true } -thiserror = { workspace = true } -tokio = { features = ["macros", "sync"], workspace = true, default-features = true } -tokio-stream = { workspace = true } -unsigned-varint = { features = ["asynchronous_codec", "futures"], workspace = true } -zeroize = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } prost = { workspace = true } +rand = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } +schnellru = { workspace = true } +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +smallvec = { workspace = true, default-features = true } sp-arithmetic = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -wasm-timer = { workspace = true } -litep2p = { workspace = true } -once_cell = { workspace = true } +thiserror = { workspace = true } +tokio = { features = ["macros", "sync"], workspace = true, default-features = true } +tokio-stream = { workspace = true } +unsigned-varint = { features = ["asynchronous_codec", "futures"], workspace = true } void = { workspace = true } -schnellru = { workspace = true } +wasm-timer = { workspace = true } +zeroize = { workspace = true, default-features = true } [dev-dependencies] assert_matches = { workspace = true } mockall = { workspace = true } multistream-select = { workspace = true } rand = { workspace = true, default-features = true } -tempfile = { workspace = true } -tokio = { features = ["macros", "rt-multi-thread"], workspace = true, default-features = true } -tokio-util = { features = ["compat"], workspace = true } -tokio-test = { workspace = true } sc-block-builder = { workspace = true, default-features = true } sc-network-light = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } -sp-crypto-hashing = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true, default-features = true } sp-test-primitives = { workspace = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime = { workspace = true } substrate-test-runtime-client = { workspace = true } +tempfile = { workspace = true } +tokio = { features = ["macros", "rt-multi-thread"], workspace = true, default-features = true } +tokio-test = { workspace = true } +tokio-util = { features = ["compat"], workspace = true } criterion = { workspace = true, default-features = true, features = ["async_tokio"] } sc-consensus = { workspace = true, default-features = true } diff --git a/substrate/client/network/README.md b/substrate/client/network/README.md index f4031fbd308539c6235f3bc2697dfa13136f039d..4336bb78533ce5676e57f9471a952d1179bb1b65 100644 --- a/substrate/client/network/README.md +++ b/substrate/client/network/README.md @@ -245,7 +245,7 @@ only downloads finalized authority set changes. GRANDPA keeps justifications for each finalized authority set change. Each change is signed by the authorities from the previous set. By downloading and verifying these signed hand-offs starting from genesis, we arrive at a recent header faster than downloading full header chain. Each `WarpSyncRequest` contains a block -hash to a to start collecting proofs from. `WarpSyncResponse` contains a sequence of block headers and +hash to start collecting proofs from. `WarpSyncResponse` contains a sequence of block headers and justifications. The proof downloader checks the justifications and continues requesting proofs from the last header hash, until it arrives at some recent header. @@ -261,7 +261,7 @@ data. I.e. it is unable to serve bock bodies and headers other than the most rec nodes have block history available, a background sync process is started that downloads all the missing blocks. It is run in parallel with the keep-up sync and does not interfere with downloading of the recent blocks. During this download we also import GRANDPA justifications for blocks with authority set changes, so that -the warp-synced node has all the data to serve for other nodes nodes that might want to sync from it with +the warp-synced node has all the data to serve for other nodes that might want to sync from it with any method. # Usage diff --git a/substrate/client/network/benches/notifications_protocol.rs b/substrate/client/network/benches/notifications_protocol.rs index c1e18c7b7f4748642b571a42bc8a719441da6ab7..a406e328d5a64a678083c8df33df8e47409ec82b 100644 --- a/substrate/client/network/benches/notifications_protocol.rs +++ b/substrate/client/network/benches/notifications_protocol.rs @@ -25,24 +25,20 @@ use sc_network::{ FullNetworkConfiguration, MultiaddrWithPeerId, NetworkConfiguration, NonReservedPeerMode, NotificationHandshake, Params, ProtocolId, Role, SetConfig, }, - service::traits::NotificationEvent, + service::traits::{NetworkService, NotificationEvent}, Litep2pNetworkBackend, NetworkBackend, NetworkWorker, NotificationMetrics, NotificationService, - Roles, + PeerId, Roles, }; use sc_network_common::{sync::message::BlockAnnouncesHandshake, ExHashT}; -use sc_network_types::build_multiaddr; use sp_core::H256; use sp_runtime::traits::{Block as BlockT, Zero}; -use std::{ - net::{IpAddr, Ipv4Addr, TcpListener}, - str::FromStr, -}; +use std::{sync::Arc, time::Duration}; use substrate_test_runtime_client::runtime; +use tokio::{sync::Mutex, task::JoinHandle}; -const MAX_SIZE: u64 = 2u64.pow(30); -const SAMPLE_SIZE: usize = 50; -const NOTIFICATIONS: usize = 50; -const EXPONENTS: &[(u32, &'static str)] = &[ +const NUMBER_OF_NOTIFICATIONS: usize = 100; +const PAYLOAD: &[(u32, &'static str)] = &[ + // (Exponent of size, label) (6, "64B"), (9, "512B"), (12, "4KB"), @@ -50,30 +46,18 @@ const EXPONENTS: &[(u32, &'static str)] = &[ (18, "256KB"), (21, "2MB"), (24, "16MB"), - (27, "128MB"), ]; - -// TODO: It's be better to bind system-provided port when initializing the worker -fn get_listen_address() -> sc_network::Multiaddr { - let ip = Ipv4Addr::from_str("127.0.0.1").unwrap(); - let listener = TcpListener::bind((IpAddr::V4(ip), 0)).unwrap(); // Bind to a random port - let local_addr = listener.local_addr().unwrap(); - let port = local_addr.port(); - - build_multiaddr!(Ip4(ip), Tcp(port)) -} +const MAX_SIZE: u64 = 2u64.pow(30); fn create_network_worker<B, H, N>( - listen_addr: sc_network::Multiaddr, -) -> (N, Box<dyn NotificationService>) +) -> (N, Arc<dyn NetworkService>, Arc<Mutex<Box<dyn NotificationService>>>) where B: BlockT<Hash = H256> + 'static, H: ExHashT, N: NetworkBackend<B, H>, { let role = Role::Full; - let mut net_conf = NetworkConfiguration::new_local(); - net_conf.listen_addresses = vec![listen_addr]; + let net_conf = NetworkConfiguration::new_local(); let network_config = FullNetworkConfiguration::<B, H, N>::new(&net_conf, None); let genesis_hash = runtime::Hash::zero(); let (block_announce_config, notification_service) = N::notification_config( @@ -110,96 +94,129 @@ where notification_metrics: NotificationMetrics::new(None), }) .unwrap(); + let network_service = worker.network_service(); + let notification_service = Arc::new(Mutex::new(notification_service)); + + (worker, network_service, notification_service) +} + +struct BenchSetup { + notification_service1: Arc<Mutex<Box<dyn NotificationService>>>, + notification_service2: Arc<Mutex<Box<dyn NotificationService>>>, + peer_id2: PeerId, + handle1: JoinHandle<()>, + handle2: JoinHandle<()>, +} - (worker, notification_service) +impl Drop for BenchSetup { + fn drop(&mut self) { + self.handle1.abort(); + self.handle2.abort(); + } } -async fn run_serially<B, H, N>(size: usize, limit: usize) +fn setup_workers<B, H, N>(rt: &tokio::runtime::Runtime) -> Arc<BenchSetup> where B: BlockT<Hash = H256> + 'static, H: ExHashT, N: NetworkBackend<B, H>, { - let listen_address1 = get_listen_address(); - let listen_address2 = get_listen_address(); - let (worker1, mut notification_service1) = create_network_worker::<B, H, N>(listen_address1); - let (worker2, mut notification_service2) = - create_network_worker::<B, H, N>(listen_address2.clone()); - let peer_id2: sc_network::PeerId = worker2.network_service().local_peer_id().into(); + let _guard = rt.enter(); - worker1 - .network_service() - .add_reserved_peer(MultiaddrWithPeerId { multiaddr: listen_address2, peer_id: peer_id2 }) - .unwrap(); + let (worker1, network_service1, notification_service1) = create_network_worker::<B, H, N>(); + let (worker2, network_service2, notification_service2) = create_network_worker::<B, H, N>(); + let peer_id2: sc_network::PeerId = network_service2.local_peer_id().into(); + let handle1 = tokio::spawn(worker1.run()); + let handle2 = tokio::spawn(worker2.run()); - let network1_run = worker1.run(); - let network2_run = worker2.run(); - let (tx, rx) = async_channel::bounded(10); + let ready = tokio::spawn({ + let notification_service1 = Arc::clone(¬ification_service1); + let notification_service2 = Arc::clone(¬ification_service2); - let network1 = tokio::spawn(async move { - let mut sent_counter = 0; - tokio::pin!(network1_run); - loop { - tokio::select! { - _ = &mut network1_run => {}, - event = notification_service1.next_event() => { - match event { - Some(NotificationEvent::NotificationStreamOpened { .. }) => { - sent_counter += 1; - notification_service1 - .send_async_notification(&peer_id2, vec![0; size]) - .await - .unwrap(); - }, - Some(NotificationEvent::NotificationStreamClosed { .. }) => { - if sent_counter >= limit { + async move { + let listen_address2 = { + while network_service2.listen_addresses().is_empty() { + tokio::time::sleep(Duration::from_millis(10)).await; + } + network_service2.listen_addresses()[0].clone() + }; + network_service1 + .add_reserved_peer(MultiaddrWithPeerId { + multiaddr: listen_address2, + peer_id: peer_id2, + }) + .unwrap(); + + let mut notification_service1 = notification_service1.lock().await; + let mut notification_service2 = notification_service2.lock().await; + loop { + tokio::select! { + Some(event) = notification_service1.next_event() => { + if let NotificationEvent::NotificationStreamOpened { .. } = event { + // Send a 32MB notification to preheat the network + notification_service1.send_async_notification(&peer_id2, vec![0; 2usize.pow(25)]).await.unwrap(); + } + }, + Some(event) = notification_service2.next_event() => { + match event { + NotificationEvent::ValidateInboundSubstream { result_tx, .. } => { + result_tx.send(sc_network::service::traits::ValidationResult::Accept).unwrap(); + }, + NotificationEvent::NotificationReceived { .. } => { break; } - panic!("Unexpected stream closure {:?}", event); + _ => {} } - event => panic!("Unexpected event {:?}", event), - }; - }, - message = rx.recv() => { - match message { - Ok(Some(_)) => { - sent_counter += 1; - notification_service1 - .send_async_notification(&peer_id2, vec![0; size]) - .await - .unwrap(); - }, - Ok(None) => break, - Err(err) => panic!("Unexpected error {:?}", err), - - } + }, } } } }); - let network2 = tokio::spawn(async move { - let mut received_counter = 0; - tokio::pin!(network2_run); - loop { - tokio::select! { - _ = &mut network2_run => {}, - event = notification_service2.next_event() => { - match event { - Some(NotificationEvent::ValidateInboundSubstream { result_tx, .. }) => { - result_tx.send(sc_network::service::traits::ValidationResult::Accept).unwrap(); - }, - Some(NotificationEvent::NotificationStreamOpened { .. }) => {}, - Some(NotificationEvent::NotificationReceived { .. }) => { - received_counter += 1; - if received_counter >= limit { - let _ = tx.send(None).await; - break - } - let _ = tx.send(Some(())).await; - }, - event => panic!("Unexpected event {:?}", event), - }; - }, + + tokio::task::block_in_place(|| { + let _ = tokio::runtime::Handle::current().block_on(ready); + }); + + Arc::new(BenchSetup { + notification_service1, + notification_service2, + peer_id2, + handle1, + handle2, + }) +} + +async fn run_serially(setup: Arc<BenchSetup>, size: usize, limit: usize) { + let (tx, rx) = async_channel::bounded(1); + let _ = tx.send(Some(())).await; + let network1 = tokio::spawn({ + let notification_service1 = Arc::clone(&setup.notification_service1); + let peer_id2 = setup.peer_id2; + async move { + let mut notification_service1 = notification_service1.lock().await; + while let Ok(message) = rx.recv().await { + let Some(_) = message else { break }; + notification_service1 + .send_async_notification(&peer_id2, vec![0; size]) + .await + .unwrap(); + } + } + }); + let network2 = tokio::spawn({ + let notification_service2 = Arc::clone(&setup.notification_service2); + async move { + let mut notification_service2 = notification_service2.lock().await; + let mut received_counter = 0; + while let Some(event) = notification_service2.next_event().await { + if let NotificationEvent::NotificationReceived { .. } = event { + received_counter += 1; + if received_counter >= limit { + let _ = tx.send(None).await; + break; + } + let _ = tx.send(Some(())).await; + } } } }); @@ -207,77 +224,34 @@ where let _ = tokio::join!(network1, network2); } -async fn run_with_backpressure<B, H, N>(size: usize, limit: usize) -where - B: BlockT<Hash = H256> + 'static, - H: ExHashT, - N: NetworkBackend<B, H>, -{ - let listen_address1 = get_listen_address(); - let listen_address2 = get_listen_address(); - let (worker1, mut notification_service1) = create_network_worker::<B, H, N>(listen_address1); - let (worker2, mut notification_service2) = - create_network_worker::<B, H, N>(listen_address2.clone()); - let peer_id2: sc_network::PeerId = worker2.network_service().local_peer_id().into(); - - worker1 - .network_service() - .add_reserved_peer(MultiaddrWithPeerId { multiaddr: listen_address2, peer_id: peer_id2 }) - .unwrap(); - - let network1_run = worker1.run(); - let network2_run = worker2.run(); - - let network1 = tokio::spawn(async move { - let mut sent_counter = 0; - tokio::pin!(network1_run); - loop { - tokio::select! { - _ = &mut network1_run => {}, - event = notification_service1.next_event() => { - match event { - Some(NotificationEvent::NotificationStreamOpened { .. }) => { - while sent_counter < limit { - sent_counter += 1; - notification_service1 - .send_async_notification(&peer_id2, vec![0; size]) - .await - .unwrap(); - } - }, - Some(NotificationEvent::NotificationStreamClosed { .. }) => { - if sent_counter != limit { panic!("Stream closed unexpectedly") } - break - }, - event => panic!("Unexpected event {:?}", event), - }; - }, +async fn run_with_backpressure(setup: Arc<BenchSetup>, size: usize, limit: usize) { + let (tx, rx) = async_channel::bounded(1); + let network1 = tokio::spawn({ + let setup = Arc::clone(&setup); + async move { + let mut notification_service1 = setup.notification_service1.lock().await; + for _ in 0..limit { + notification_service1 + .send_async_notification(&setup.peer_id2, vec![0; size]) + .await + .unwrap(); } + let _ = rx.recv().await; } }); - let network2 = tokio::spawn(async move { - let mut received_counter = 0; - tokio::pin!(network2_run); - loop { - tokio::select! { - _ = &mut network2_run => {}, - event = notification_service2.next_event() => { - match event { - Some(NotificationEvent::ValidateInboundSubstream { result_tx, .. }) => { - result_tx.send(sc_network::service::traits::ValidationResult::Accept).unwrap(); - }, - Some(NotificationEvent::NotificationStreamOpened { .. }) => {}, - Some(NotificationEvent::NotificationStreamClosed { .. }) => { - if received_counter != limit { panic!("Stream closed unexpectedly") } - break - }, - Some(NotificationEvent::NotificationReceived { .. }) => { - received_counter += 1; - if received_counter >= limit { break } - }, - event => panic!("Unexpected event {:?}", event), - }; - }, + let network2 = tokio::spawn({ + let setup = Arc::clone(&setup); + async move { + let mut notification_service2 = setup.notification_service2.lock().await; + let mut received_counter = 0; + while let Some(event) = notification_service2.next_event().await { + if let NotificationEvent::NotificationReceived { .. } = event { + received_counter += 1; + if received_counter >= limit { + let _ = tx.send(()).await; + break; + } + } } } }); @@ -288,61 +262,50 @@ where fn run_benchmark(c: &mut Criterion) { let rt = tokio::runtime::Runtime::new().unwrap(); let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic); - let mut group = c.benchmark_group("notifications_benchmark"); + let mut group = c.benchmark_group("notifications_protocol"); group.plot_config(plot_config); + group.sample_size(10); - for &(exponent, label) in EXPONENTS.iter() { + let libp2p_setup = setup_workers::<runtime::Block, runtime::Hash, NetworkWorker<_, _>>(&rt); + for &(exponent, label) in PAYLOAD.iter() { let size = 2usize.pow(exponent); - group.throughput(Throughput::Bytes(NOTIFICATIONS as u64 * size as u64)); - - group.bench_with_input( - BenchmarkId::new("libp2p/serially", label), - &(size, NOTIFICATIONS), - |b, &(size, limit)| { - b.to_async(&rt).iter(|| { - run_serially::<runtime::Block, runtime::Hash, NetworkWorker<_, _>>(size, limit) - }); - }, - ); - group.bench_with_input( - BenchmarkId::new("litep2p/serially", label), - &(size, NOTIFICATIONS), - |b, &(size, limit)| { - b.to_async(&rt).iter(|| { - run_serially::<runtime::Block, runtime::Hash, Litep2pNetworkBackend>( - size, limit, - ) - }); - }, - ); + group.throughput(Throughput::Bytes(NUMBER_OF_NOTIFICATIONS as u64 * size as u64)); + group.bench_with_input(BenchmarkId::new("libp2p/serially", label), &size, |b, &size| { + b.to_async(&rt) + .iter(|| run_serially(Arc::clone(&libp2p_setup), size, NUMBER_OF_NOTIFICATIONS)); + }); group.bench_with_input( BenchmarkId::new("libp2p/with_backpressure", label), - &(size, NOTIFICATIONS), - |b, &(size, limit)| { + &size, + |b, &size| { b.to_async(&rt).iter(|| { - run_with_backpressure::<runtime::Block, runtime::Hash, NetworkWorker<_, _>>( - size, limit, - ) + run_with_backpressure(Arc::clone(&libp2p_setup), size, NUMBER_OF_NOTIFICATIONS) }); }, ); + } + drop(libp2p_setup); + + let litep2p_setup = setup_workers::<runtime::Block, runtime::Hash, Litep2pNetworkBackend>(&rt); + for &(exponent, label) in PAYLOAD.iter() { + let size = 2usize.pow(exponent); + group.throughput(Throughput::Bytes(NUMBER_OF_NOTIFICATIONS as u64 * size as u64)); + group.bench_with_input(BenchmarkId::new("litep2p/serially", label), &size, |b, &size| { + b.to_async(&rt) + .iter(|| run_serially(Arc::clone(&litep2p_setup), size, NUMBER_OF_NOTIFICATIONS)); + }); group.bench_with_input( BenchmarkId::new("litep2p/with_backpressure", label), - &(size, NOTIFICATIONS), - |b, &(size, limit)| { + &size, + |b, &size| { b.to_async(&rt).iter(|| { - run_with_backpressure::<runtime::Block, runtime::Hash, Litep2pNetworkBackend>( - size, limit, - ) + run_with_backpressure(Arc::clone(&litep2p_setup), size, NUMBER_OF_NOTIFICATIONS) }); }, ); } + drop(litep2p_setup); } -criterion_group! { - name = benches; - config = Criterion::default().sample_size(SAMPLE_SIZE); - targets = run_benchmark -} +criterion_group!(benches, run_benchmark); criterion_main!(benches); diff --git a/substrate/client/network/benches/request_response_protocol.rs b/substrate/client/network/benches/request_response_protocol.rs index b428d0d75ac59aac5d49b52b88865b6b980b864f..97c6d72ddf1ef78524810b6bcb5e2175e15a0d9f 100644 --- a/substrate/client/network/benches/request_response_protocol.rs +++ b/substrate/client/network/benches/request_response_protocol.rs @@ -25,24 +25,21 @@ use sc_network::{ FullNetworkConfiguration, IncomingRequest, NetworkConfiguration, NonReservedPeerMode, NotificationHandshake, OutgoingResponse, Params, ProtocolId, Role, SetConfig, }, + service::traits::NetworkService, IfDisconnected, Litep2pNetworkBackend, NetworkBackend, NetworkRequest, NetworkWorker, - NotificationMetrics, NotificationService, Roles, + NotificationMetrics, NotificationService, PeerId, Roles, }; use sc_network_common::{sync::message::BlockAnnouncesHandshake, ExHashT}; -use sc_network_types::build_multiaddr; use sp_core::H256; use sp_runtime::traits::{Block as BlockT, Zero}; -use std::{ - net::{IpAddr, Ipv4Addr, TcpListener}, - str::FromStr, - time::Duration, -}; +use std::{sync::Arc, time::Duration}; use substrate_test_runtime_client::runtime; +use tokio::{sync::Mutex, task::JoinHandle}; const MAX_SIZE: u64 = 2u64.pow(30); -const SAMPLE_SIZE: usize = 50; -const REQUESTS: usize = 50; -const EXPONENTS: &[(u32, &'static str)] = &[ +const NUMBER_OF_REQUESTS: usize = 100; +const PAYLOAD: &[(u32, &'static str)] = &[ + // (Exponent of size, label) (6, "64B"), (9, "512B"), (12, "4KB"), @@ -50,21 +47,14 @@ const EXPONENTS: &[(u32, &'static str)] = &[ (18, "256KB"), (21, "2MB"), (24, "16MB"), - (27, "128MB"), ]; -fn get_listen_address() -> sc_network::Multiaddr { - let ip = Ipv4Addr::from_str("127.0.0.1").unwrap(); - let listener = TcpListener::bind((IpAddr::V4(ip), 0)).unwrap(); // Bind to a random port - let local_addr = listener.local_addr().unwrap(); - let port = local_addr.port(); - - build_multiaddr!(Ip4(ip), Tcp(port)) -} - -pub fn create_network_worker<B, H, N>( - listen_addr: sc_network::Multiaddr, -) -> (N, async_channel::Receiver<IncomingRequest>, Box<dyn NotificationService>) +pub fn create_network_worker<B, H, N>() -> ( + N, + Arc<dyn NetworkService>, + async_channel::Receiver<IncomingRequest>, + Arc<Mutex<Box<dyn NotificationService>>>, +) where B: BlockT<Hash = H256> + 'static, H: ExHashT, @@ -80,8 +70,7 @@ where Some(tx), ); let role = Role::Full; - let mut net_conf = NetworkConfiguration::new_local(); - net_conf.listen_addresses = vec![listen_addr]; + let net_conf = NetworkConfiguration::new_local(); let mut network_config = FullNetworkConfiguration::new(&net_conf, None); network_config.add_request_response_protocol(request_response_config); let genesis_hash = runtime::Hash::zero(); @@ -119,33 +108,76 @@ where notification_metrics: NotificationMetrics::new(None), }) .unwrap(); + let notification_service = Arc::new(Mutex::new(notification_service)); + let network_service = worker.network_service(); + + (worker, network_service, rx, notification_service) +} - (worker, rx, notification_service) +struct BenchSetup { + #[allow(dead_code)] + notification_service1: Arc<Mutex<Box<dyn NotificationService>>>, + #[allow(dead_code)] + notification_service2: Arc<Mutex<Box<dyn NotificationService>>>, + network_service1: Arc<dyn NetworkService>, + peer_id2: PeerId, + handle1: JoinHandle<()>, + handle2: JoinHandle<()>, + #[allow(dead_code)] + rx1: async_channel::Receiver<IncomingRequest>, + rx2: async_channel::Receiver<IncomingRequest>, } -async fn run_serially<B, H, N>(size: usize, limit: usize) +impl Drop for BenchSetup { + fn drop(&mut self) { + self.handle1.abort(); + self.handle2.abort(); + } +} + +fn setup_workers<B, H, N>(rt: &tokio::runtime::Runtime) -> Arc<BenchSetup> where B: BlockT<Hash = H256> + 'static, H: ExHashT, N: NetworkBackend<B, H>, { - let listen_address1 = get_listen_address(); - let listen_address2 = get_listen_address(); - let (worker1, _rx1, _notification_service1) = create_network_worker::<B, H, N>(listen_address1); - let service1 = worker1.network_service().clone(); - let (worker2, rx2, _notification_service2) = - create_network_worker::<B, H, N>(listen_address2.clone()); + let _guard = rt.enter(); + + let (worker1, network_service1, rx1, notification_service1) = + create_network_worker::<B, H, N>(); + let (worker2, network_service2, rx2, notification_service2) = + create_network_worker::<B, H, N>(); let peer_id2 = worker2.network_service().local_peer_id(); + let handle1 = tokio::spawn(worker1.run()); + let handle2 = tokio::spawn(worker2.run()); + + let _ = tokio::spawn({ + let rx2 = rx2.clone(); - worker1.network_service().add_known_address(peer_id2, listen_address2.into()); + async move { + let req = rx2.recv().await.unwrap(); + req.pending_response + .send(OutgoingResponse { + result: Ok(vec![0; 2usize.pow(25)]), + reputation_changes: vec![], + sent_feedback: None, + }) + .unwrap(); + } + }); - let network1_run = worker1.run(); - let network2_run = worker2.run(); - let (break_tx, break_rx) = async_channel::bounded(10); - let requests = async move { - let mut sent_counter = 0; - while sent_counter < limit { - let _ = service1 + let ready = tokio::spawn({ + let network_service1 = Arc::clone(&network_service1); + + async move { + let listen_address2 = { + while network_service2.listen_addresses().is_empty() { + tokio::time::sleep(Duration::from_millis(10)).await; + } + network_service2.listen_addresses()[0].clone() + }; + network_service1.add_known_address(peer_id2, listen_address2.into()); + let _ = network_service1 .request( peer_id2.into(), "/request-response/1".into(), @@ -155,35 +187,61 @@ where ) .await .unwrap(); - sent_counter += 1; } - let _ = break_tx.send(()).await; - }; + }); - let network1 = tokio::spawn(async move { - tokio::pin!(requests); - tokio::pin!(network1_run); - loop { - tokio::select! { - _ = &mut network1_run => {}, - _ = &mut requests => break, + tokio::task::block_in_place(|| { + let _ = tokio::runtime::Handle::current().block_on(ready); + }); + + Arc::new(BenchSetup { + notification_service1, + notification_service2, + network_service1, + peer_id2, + handle1, + handle2, + rx1, + rx2, + }) +} + +async fn run_serially(setup: Arc<BenchSetup>, size: usize, limit: usize) { + let (break_tx, break_rx) = async_channel::bounded(1); + let network1 = tokio::spawn({ + let network_service1 = Arc::clone(&setup.network_service1); + let peer_id2 = setup.peer_id2; + async move { + for _ in 0..limit { + let _ = network_service1 + .request( + peer_id2.into(), + "/request-response/1".into(), + vec![0; 2], + None, + IfDisconnected::TryConnect, + ) + .await + .unwrap(); } + let _ = break_tx.send(()).await; } }); - let network2 = tokio::spawn(async move { - tokio::pin!(network2_run); - loop { - tokio::select! { - _ = &mut network2_run => {}, - res = rx2.recv() => { - let IncomingRequest { pending_response, .. } = res.unwrap(); - pending_response.send(OutgoingResponse { - result: Ok(vec![0; size]), - reputation_changes: vec![], - sent_feedback: None, - }).unwrap(); - }, - _ = break_rx.recv() => break, + let network2 = tokio::spawn({ + let rx2 = setup.rx2.clone(); + async move { + loop { + tokio::select! { + req = rx2.recv() => { + let IncomingRequest { pending_response, .. } = req.unwrap(); + pending_response.send(OutgoingResponse { + result: Ok(vec![0; size]), + reputation_changes: vec![], + sent_feedback: None, + }).unwrap(); + }, + _ = break_rx.recv() => break, + } } } }); @@ -194,29 +252,12 @@ where // The libp2p request-response implementation does not provide any backpressure feedback. // So this benchmark is useless until we implement it for litep2p. #[allow(dead_code)] -async fn run_with_backpressure<B, H, N>(size: usize, limit: usize) -where - B: BlockT<Hash = H256> + 'static, - H: ExHashT, - N: NetworkBackend<B, H>, -{ - let listen_address1 = get_listen_address(); - let listen_address2 = get_listen_address(); - let (worker1, _rx1, _notification_service1) = create_network_worker::<B, H, N>(listen_address1); - let service1 = worker1.network_service().clone(); - let (worker2, rx2, _notification_service2) = - create_network_worker::<B, H, N>(listen_address2.clone()); - let peer_id2 = worker2.network_service().local_peer_id(); - - worker1.network_service().add_known_address(peer_id2, listen_address2.into()); - - let network1_run = worker1.run(); - let network2_run = worker2.run(); - let (break_tx, break_rx) = async_channel::bounded(10); +async fn run_with_backpressure(setup: Arc<BenchSetup>, size: usize, limit: usize) { + let (break_tx, break_rx) = async_channel::bounded(1); let requests = futures::future::join_all((0..limit).into_iter().map(|_| { let (tx, rx) = futures::channel::oneshot::channel(); - service1.start_request( - peer_id2.into(), + setup.network_service1.start_request( + setup.peer_id2.into(), "/request-response/1".into(), vec![0; 8], None, @@ -227,37 +268,24 @@ where })); let network1 = tokio::spawn(async move { - tokio::pin!(requests); - tokio::pin!(network1_run); - loop { - tokio::select! { - _ = &mut network1_run => {}, - responses = &mut requests => { - for res in responses { - res.unwrap().unwrap(); - } - let _ = break_tx.send(()).await; - break; - }, - } + let responses = requests.await; + for res in responses { + res.unwrap().unwrap(); } + let _ = break_tx.send(()).await; }); let network2 = tokio::spawn(async move { - tokio::pin!(network2_run); - loop { - tokio::select! { - _ = &mut network2_run => {}, - res = rx2.recv() => { - let IncomingRequest { pending_response, .. } = res.unwrap(); - pending_response.send(OutgoingResponse { - result: Ok(vec![0; size]), - reputation_changes: vec![], - sent_feedback: None, - }).unwrap(); - }, - _ = break_rx.recv() => break, - } + for _ in 0..limit { + let IncomingRequest { pending_response, .. } = setup.rx2.recv().await.unwrap(); + pending_response + .send(OutgoingResponse { + result: Ok(vec![0; size]), + reputation_changes: vec![], + sent_feedback: None, + }) + .unwrap(); } + break_rx.recv().await }); let _ = tokio::join!(network1, network2); @@ -266,38 +294,32 @@ where fn run_benchmark(c: &mut Criterion) { let rt = tokio::runtime::Runtime::new().unwrap(); let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic); - let mut group = c.benchmark_group("request_response_benchmark"); + let mut group = c.benchmark_group("request_response_protocol"); group.plot_config(plot_config); + group.sample_size(10); - for &(exponent, label) in EXPONENTS.iter() { + let libp2p_setup = setup_workers::<runtime::Block, runtime::Hash, NetworkWorker<_, _>>(&rt); + for &(exponent, label) in PAYLOAD.iter() { let size = 2usize.pow(exponent); - group.throughput(Throughput::Bytes(REQUESTS as u64 * size as u64)); - group.bench_with_input( - BenchmarkId::new("libp2p/serially", label), - &(size, REQUESTS), - |b, &(size, limit)| { - b.to_async(&rt).iter(|| { - run_serially::<runtime::Block, runtime::Hash, NetworkWorker<_, _>>(size, limit) - }); - }, - ); - group.bench_with_input( - BenchmarkId::new("litep2p/serially", label), - &(size, REQUESTS), - |b, &(size, limit)| { - b.to_async(&rt).iter(|| { - run_serially::<runtime::Block, runtime::Hash, Litep2pNetworkBackend>( - size, limit, - ) - }); - }, - ); + group.throughput(Throughput::Bytes(NUMBER_OF_REQUESTS as u64 * size as u64)); + group.bench_with_input(BenchmarkId::new("libp2p/serially", label), &size, |b, &size| { + b.to_async(&rt) + .iter(|| run_serially(Arc::clone(&libp2p_setup), size, NUMBER_OF_REQUESTS)); + }); } -} + drop(libp2p_setup); -criterion_group! { - name = benches; - config = Criterion::default().sample_size(SAMPLE_SIZE); - targets = run_benchmark + let litep2p_setup = setup_workers::<runtime::Block, runtime::Hash, Litep2pNetworkBackend>(&rt); + for &(exponent, label) in PAYLOAD.iter() { + let size = 2usize.pow(exponent); + group.throughput(Throughput::Bytes(NUMBER_OF_REQUESTS as u64 * size as u64)); + group.bench_with_input(BenchmarkId::new("litep2p/serially", label), &size, |b, &size| { + b.to_async(&rt) + .iter(|| run_serially(Arc::clone(&litep2p_setup), size, NUMBER_OF_REQUESTS)); + }); + } + drop(litep2p_setup); } + +criterion_group!(benches, run_benchmark); criterion_main!(benches); diff --git a/substrate/client/network/common/Cargo.toml b/substrate/client/network/common/Cargo.toml index cd1bc1cfe8eb84454c17cb8f27ebdb2652b473a3..30407423da29aa6a7b122af215171bda50d42fb7 100644 --- a/substrate/client/network/common/Cargo.toml +++ b/substrate/client/network/common/Cargo.toml @@ -19,17 +19,11 @@ targets = ["x86_64-unknown-linux-gnu"] prost-build = { workspace = true } [dependencies] -async-trait = { workspace = true } bitflags = { workspace = true } codec = { features = [ "derive", ], workspace = true, default-features = true } futures = { workspace = true } -libp2p-identity = { features = ["peerid"], workspace = true } -sc-consensus = { workspace = true, default-features = true } -sc-network-types = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } -sp-consensus-grandpa = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } [dev-dependencies] diff --git a/substrate/client/network/light/Cargo.toml b/substrate/client/network/light/Cargo.toml index 34ba4f061c44f4e75ead7838d3e335ab11d923e1..fad7ae42585842dfc75ae690d88e5805eb616cf9 100644 --- a/substrate/client/network/light/Cargo.toml +++ b/substrate/client/network/light/Cargo.toml @@ -19,18 +19,18 @@ targets = ["x86_64-unknown-linux-gnu"] prost-build = { workspace = true } [dependencies] -async-channel = { workspace = true } array-bytes = { workspace = true, default-features = true } +async-channel = { workspace = true } codec = { features = [ "derive", ], workspace = true, default-features = true } futures = { workspace = true } log = { workspace = true, default-features = true } prost = { workspace = true } -sp-blockchain = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } -sc-network-types = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } +sc-network-types = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } thiserror = { workspace = true } diff --git a/substrate/client/network/src/behaviour.rs b/substrate/client/network/src/behaviour.rs index dbb72381b6604afc087bafa9d53c0480a7026073..0f6b1ab3450785bf0e41a727ead8a71491a88979 100644 --- a/substrate/client/network/src/behaviour.rs +++ b/substrate/client/network/src/behaviour.rs @@ -68,6 +68,7 @@ pub struct Behaviour<B: BlockT> { } /// Event generated by `Behaviour`. +#[derive(Debug)] pub enum BehaviourOut { /// Started a random iterative Kademlia discovery query. RandomKademliaStarted, @@ -183,6 +184,7 @@ impl<B: BlockT> Behaviour<B> { request_response_protocols: Vec<ProtocolConfig>, peer_store_handle: Arc<dyn PeerStoreProvider>, external_addresses: Arc<Mutex<HashSet<Multiaddr>>>, + public_addresses: Vec<Multiaddr>, connection_limits: ConnectionLimits, ) -> Result<Self, request_responses::RegisterError> { Ok(Self { @@ -191,6 +193,7 @@ impl<B: BlockT> Behaviour<B> { user_agent, local_public_key, external_addresses, + public_addresses, ), discovery: disco_config.finish(), request_responses: request_responses::RequestResponsesBehaviour::new( @@ -310,6 +313,22 @@ impl<B: BlockT> Behaviour<B> { ) { self.discovery.store_record(record_key, record_value, publisher, expires); } + + /// Start providing `key` on the DHT. + pub fn start_providing(&mut self, key: RecordKey) { + self.discovery.start_providing(key) + } + + /// Stop providing `key` on the DHT. + pub fn stop_providing(&mut self, key: &RecordKey) { + self.discovery.stop_providing(key) + } + + /// Start searching for providers on the DHT. Will later produce either a `ProvidersFound` + /// or `ProvidersNotFound` event. + pub fn get_providers(&mut self, key: RecordKey) { + self.discovery.get_providers(key) + } } impl From<CustomMessageOutcome> for BehaviourOut { @@ -387,6 +406,17 @@ impl From<DiscoveryOut> for BehaviourOut { ), DiscoveryOut::ValuePutFailed(key, duration) => BehaviourOut::Dht(DhtEvent::ValuePutFailed(key.into()), Some(duration)), + DiscoveryOut::StartProvidingFailed(key) => + BehaviourOut::Dht(DhtEvent::StartProvidingFailed(key.into()), None), + DiscoveryOut::ProvidersFound(key, providers, duration) => BehaviourOut::Dht( + DhtEvent::ProvidersFound( + key.into(), + providers.into_iter().map(Into::into).collect(), + ), + Some(duration), + ), + DiscoveryOut::ProvidersNotFound(key, duration) => + BehaviourOut::Dht(DhtEvent::ProvidersNotFound(key.into()), Some(duration)), DiscoveryOut::RandomKademliaStarted => BehaviourOut::RandomKademliaStarted, } } diff --git a/substrate/client/network/src/discovery.rs b/substrate/client/network/src/discovery.rs index 8080bda9a5749ec5f644137eee5378273951d98e..917449cf228c6ef8e8864699d5241f80158f25b7 100644 --- a/substrate/client/network/src/discovery.rs +++ b/substrate/client/network/src/discovery.rs @@ -53,13 +53,13 @@ use futures::prelude::*; use futures_timer::Delay; use ip_network::IpNetwork; use libp2p::{ - core::{Endpoint, Multiaddr}, + core::{transport::PortUse, Endpoint, Multiaddr}, kad::{ self, - record::store::{MemoryStore, RecordStore}, + store::{MemoryStore, RecordStore}, Behaviour as Kademlia, BucketInserts, Config as KademliaConfig, Event as KademliaEvent, - GetClosestPeersError, GetRecordOk, PeerRecord, QueryId, QueryResult, Quorum, Record, - RecordKey, + Event, GetClosestPeersError, GetProvidersError, GetProvidersOk, GetRecordOk, PeerRecord, + QueryId, QueryResult, Quorum, Record, RecordKey, }, mdns::{self, tokio::Behaviour as TokioMdns}, multiaddr::Protocol, @@ -68,8 +68,8 @@ use libp2p::{ toggle::{Toggle, ToggleConnectionHandler}, DialFailure, ExternalAddrConfirmed, FromSwarm, }, - ConnectionDenied, ConnectionId, DialError, NetworkBehaviour, PollParameters, - StreamProtocol, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, + ConnectionDenied, ConnectionId, DialError, NetworkBehaviour, StreamProtocol, THandler, + THandlerInEvent, THandlerOutEvent, ToSwarm, }, PeerId, }; @@ -214,23 +214,14 @@ impl DiscoveryConfig { enable_mdns, kademlia_disjoint_query_paths, kademlia_protocol, - kademlia_legacy_protocol, + kademlia_legacy_protocol: _, kademlia_replication_factor, } = self; let kademlia = if let Some(ref kademlia_protocol) = kademlia_protocol { - let mut config = KademliaConfig::default(); + let mut config = KademliaConfig::new(kademlia_protocol.clone()); config.set_replication_factor(kademlia_replication_factor); - // Populate kad with both the legacy and the new protocol names. - // Remove the legacy protocol: - // https://github.com/paritytech/polkadot-sdk/issues/504 - let kademlia_protocols = if let Some(legacy_protocol) = kademlia_legacy_protocol { - vec![kademlia_protocol.clone(), legacy_protocol] - } else { - vec![kademlia_protocol.clone()] - }; - config.set_protocol_names(kademlia_protocols.into_iter().map(Into::into).collect()); config.set_record_filtering(libp2p::kad::StoreInserts::FilterBoth); @@ -466,6 +457,31 @@ impl DiscoveryBehaviour { } } } + + /// Register as a content provider on the DHT for `key`. + pub fn start_providing(&mut self, key: RecordKey) { + if let Some(kad) = self.kademlia.as_mut() { + if let Err(e) = kad.start_providing(key.clone()) { + warn!(target: "sub-libp2p", "Libp2p => Failed to start providing {key:?}: {e}."); + self.pending_events.push_back(DiscoveryOut::StartProvidingFailed(key)); + } + } + } + + /// Deregister as a content provider on the DHT for `key`. + pub fn stop_providing(&mut self, key: &RecordKey) { + if let Some(kad) = self.kademlia.as_mut() { + kad.stop_providing(key); + } + } + + /// Get content providers for `key` from the DHT. + pub fn get_providers(&mut self, key: RecordKey) { + if let Some(kad) = self.kademlia.as_mut() { + kad.get_providers(key); + } + } + /// Store a record in the Kademlia record store. pub fn store_record( &mut self, @@ -581,6 +597,15 @@ pub enum DiscoveryOut { /// Returning the corresponding key as well as the request duration. ValuePutFailed(RecordKey, Duration), + /// Starting providing a key failed. + StartProvidingFailed(RecordKey), + + /// The DHT yielded results for the providers request. + ProvidersFound(RecordKey, HashSet<PeerId>, Duration), + + /// Providers for the requested key were not found in the DHT. + ProvidersNotFound(RecordKey, Duration), + /// Started a random Kademlia query. /// /// Only happens if [`DiscoveryConfig::with_dht_random_walk`] has been configured to `true`. @@ -613,12 +638,14 @@ impl NetworkBehaviour for DiscoveryBehaviour { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result<THandler<Self>, ConnectionDenied> { self.kademlia.handle_established_outbound_connection( connection_id, peer, addr, role_override, + port_use, ) } @@ -690,7 +717,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { Ok(list.into_iter().collect()) } - fn on_swarm_event(&mut self, event: FromSwarm<Self::ConnectionHandler>) { + fn on_swarm_event(&mut self, event: FromSwarm) { match event { FromSwarm::ConnectionEstablished(e) => { self.num_connections += 1; @@ -777,6 +804,10 @@ impl NetworkBehaviour for DiscoveryBehaviour { self.kademlia.on_swarm_event(FromSwarm::ExternalAddrConfirmed(e)); }, + event => { + debug!(target: "sub-libp2p", "New unknown `FromSwarm` libp2p event: {event:?}"); + self.kademlia.on_swarm_event(event); + }, } } @@ -789,11 +820,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { self.kademlia.on_connection_handler_event(peer_id, connection_id, event); } - fn poll( - &mut self, - cx: &mut Context, - params: &mut impl PollParameters, - ) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> { + fn poll(&mut self, cx: &mut Context) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> { // Immediately process the content of `discovered`. if let Some(ev) = self.pending_events.pop_front() { return Poll::Ready(ToSwarm::GenerateEvent(ev)) @@ -836,7 +863,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { } } - while let Poll::Ready(ev) = self.kademlia.poll(cx, params) { + while let Poll::Ready(ev) = self.kademlia.poll(cx) { match ev { ToSwarm::GenerateEvent(ev) => match ev { KademliaEvent::RoutingUpdated { peer, .. } => { @@ -982,6 +1009,56 @@ impl NetworkBehaviour for DiscoveryBehaviour { }; return Poll::Ready(ToSwarm::GenerateEvent(ev)) }, + KademliaEvent::OutboundQueryProgressed { + result: QueryResult::GetProviders(res), + stats, + id, + .. + } => { + let ev = match res { + Ok(GetProvidersOk::FoundProviders { key, providers }) => { + debug!( + target: "sub-libp2p", + "Libp2p => Found providers {:?} for key {:?}, id {:?}, stats {:?}", + providers, + key, + id, + stats, + ); + + DiscoveryOut::ProvidersFound( + key, + providers, + stats.duration().unwrap_or_default(), + ) + }, + Ok(GetProvidersOk::FinishedWithNoAdditionalRecord { + closest_peers: _, + }) => { + debug!( + target: "sub-libp2p", + "Libp2p => Finished with no additional providers {:?}, stats {:?}, took {:?} ms", + id, + stats, + stats.duration().map(|val| val.as_millis()) + ); + + continue + }, + Err(GetProvidersError::Timeout { key, closest_peers: _ }) => { + debug!( + target: "sub-libp2p", + "Libp2p => Failed to get providers for {key:?} due to timeout.", + ); + + DiscoveryOut::ProvidersNotFound( + key, + stats.duration().unwrap_or_default(), + ) + }, + }; + return Poll::Ready(ToSwarm::GenerateEvent(ev)) + }, KademliaEvent::OutboundQueryProgressed { result: QueryResult::PutRecord(res), stats, @@ -1019,30 +1096,38 @@ impl NetworkBehaviour for DiscoveryBehaviour { e.key(), e, ), }, + KademliaEvent::OutboundQueryProgressed { + result: QueryResult::Bootstrap(res), + .. + } => match res { + Ok(ok) => debug!( + target: "sub-libp2p", + "Libp2p => DHT bootstrap progressed: {ok:?}", + ), + Err(e) => warn!( + target: "sub-libp2p", + "Libp2p => DHT bootstrap error: {e:?}", + ), + }, // We never start any other type of query. KademliaEvent::OutboundQueryProgressed { result: e, .. } => { warn!(target: "sub-libp2p", "Libp2p => Unhandled Kademlia event: {:?}", e) }, + Event::ModeChanged { new_mode } => { + debug!(target: "sub-libp2p", "Libp2p => Kademlia mode changed: {new_mode}") + }, }, ToSwarm::Dial { opts } => return Poll::Ready(ToSwarm::Dial { opts }), - ToSwarm::NotifyHandler { peer_id, handler, event } => - return Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, event }), - ToSwarm::CloseConnection { peer_id, connection } => - return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), - ToSwarm::NewExternalAddrCandidate(observed) => - return Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)), - ToSwarm::ExternalAddrConfirmed(addr) => - return Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)), - ToSwarm::ExternalAddrExpired(addr) => - return Poll::Ready(ToSwarm::ExternalAddrExpired(addr)), - ToSwarm::ListenOn { opts } => return Poll::Ready(ToSwarm::ListenOn { opts }), - ToSwarm::RemoveListener { id } => - return Poll::Ready(ToSwarm::RemoveListener { id }), + event => { + return Poll::Ready(event.map_out(|_| { + unreachable!("`GenerateEvent` is handled in a branch above; qed") + })); + }, } } // Poll mDNS. - while let Poll::Ready(ev) = self.mdns.poll(cx, params) { + while let Poll::Ready(ev) = self.mdns.poll(cx) { match ev { ToSwarm::GenerateEvent(event) => match event { mdns::Event::Discovered(list) => { @@ -1064,17 +1149,17 @@ impl NetworkBehaviour for DiscoveryBehaviour { }, // `event` is an enum with no variant ToSwarm::NotifyHandler { event, .. } => match event {}, - ToSwarm::CloseConnection { peer_id, connection } => - return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), - ToSwarm::NewExternalAddrCandidate(observed) => - return Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)), - ToSwarm::ExternalAddrConfirmed(addr) => - return Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)), - ToSwarm::ExternalAddrExpired(addr) => - return Poll::Ready(ToSwarm::ExternalAddrExpired(addr)), - ToSwarm::ListenOn { opts } => return Poll::Ready(ToSwarm::ListenOn { opts }), - ToSwarm::RemoveListener { id } => - return Poll::Ready(ToSwarm::RemoveListener { id }), + event => { + return Poll::Ready( + event + .map_in(|_| { + unreachable!("`NotifyHandler` is handled in a branch above; qed") + }) + .map_out(|_| { + unreachable!("`GenerateEvent` is handled in a branch above; qed") + }), + ); + }, } } @@ -1117,21 +1202,14 @@ mod tests { }, identity::Keypair, noise, - swarm::{Executor, Swarm, SwarmEvent}, + swarm::{Swarm, SwarmEvent}, yamux, Multiaddr, }; use sp_core::hash::H256; - use std::{collections::HashSet, pin::Pin, task::Poll}; + use std::{collections::HashSet, task::Poll, time::Duration}; - struct TokioExecutor(tokio::runtime::Runtime); - impl Executor for TokioExecutor { - fn exec(&self, f: Pin<Box<dyn Future<Output = ()> + Send>>) { - let _ = self.0.spawn(f); - } - } - - #[test] - fn discovery_working() { + #[tokio::test] + async fn discovery_working() { let mut first_swarm_peer_id_and_addr = None; let genesis_hash = H256::from_low_u64_be(1); @@ -1142,42 +1220,40 @@ mod tests { // the first swarm via `with_permanent_addresses`. let mut swarms = (0..25) .map(|i| { - let keypair = Keypair::generate_ed25519(); - - let transport = MemoryTransport::new() - .upgrade(upgrade::Version::V1) - .authenticate(noise::Config::new(&keypair).unwrap()) - .multiplex(yamux::Config::default()) - .boxed(); - - let behaviour = { - let mut config = DiscoveryConfig::new(keypair.public().to_peer_id()); - config - .with_permanent_addresses(first_swarm_peer_id_and_addr.clone()) - .allow_private_ip(true) - .allow_non_globals_in_dht(true) - .discovery_limit(50) - .with_kademlia(genesis_hash, fork_id, &protocol_id); - - config.finish() - }; - - let runtime = tokio::runtime::Runtime::new().unwrap(); - #[allow(deprecated)] - let mut swarm = libp2p::swarm::SwarmBuilder::with_executor( - transport, - behaviour, - keypair.public().to_peer_id(), - TokioExecutor(runtime), - ) - .build(); + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_other_transport(|keypair| { + MemoryTransport::new() + .upgrade(upgrade::Version::V1) + .authenticate(noise::Config::new(&keypair).unwrap()) + .multiplex(yamux::Config::default()) + .boxed() + }) + .unwrap() + .with_behaviour(|keypair| { + let mut config = DiscoveryConfig::new(keypair.public().to_peer_id()); + config + .with_permanent_addresses(first_swarm_peer_id_and_addr.clone()) + .allow_private_ip(true) + .allow_non_globals_in_dht(true) + .discovery_limit(50) + .with_kademlia(genesis_hash, fork_id, &protocol_id); + + config.finish() + }) + .unwrap() + .with_swarm_config(|config| { + // This is taken care of by notification protocols in non-test environment + config.with_idle_connection_timeout(Duration::from_secs(10)) + }) + .build(); let listen_addr: Multiaddr = format!("/memory/{}", rand::random::<u64>()).parse().unwrap(); if i == 0 { first_swarm_peer_id_and_addr = - Some((keypair.public().to_peer_id(), listen_addr.clone())) + Some((*swarm.local_peer_id(), listen_addr.clone())) } swarm.listen_on(listen_addr.clone()).unwrap(); @@ -1264,7 +1340,7 @@ mod tests { } }); - futures::executor::block_on(fut); + fut.await } #[test] diff --git a/substrate/client/network/src/event.rs b/substrate/client/network/src/event.rs index 626cf516a7ec2dfb5eefd83578710ffcf6089cb0..e8ec1eee254501958848a7c8c79525b95f931195 100644 --- a/substrate/client/network/src/event.rs +++ b/substrate/client/network/src/event.rs @@ -45,8 +45,17 @@ pub enum DhtEvent { /// An error has occurred while putting a record into the DHT. ValuePutFailed(Key), + /// An error occured while registering as a content provider on the DHT. + StartProvidingFailed(Key), + /// The DHT received a put record request. PutRecordRequest(Key, Vec<u8>, Option<sc_network_types::PeerId>, Option<std::time::Instant>), + + /// The providers for [`Key`] were found. + ProvidersFound(Key, Vec<PeerId>), + + /// The providers for [`Key`] were not found. + ProvidersNotFound(Key), } /// Type for events generated by networking layer. diff --git a/substrate/client/network/src/lib.rs b/substrate/client/network/src/lib.rs index 9300cbccc9ad3054d9e8b3cdf0c5fa4801adece6..f19c4dd2191a1a4cd00ec0329efbe475910a361c 100644 --- a/substrate/client/network/src/lib.rs +++ b/substrate/client/network/src/lib.rs @@ -291,6 +291,9 @@ pub use service::{ }; pub use types::ProtocolName; +/// Log target for `sc-network`. +const LOG_TARGET: &str = "sub-libp2p"; + /// The maximum allowed number of established connections per peer. /// /// Typically, and by design of the network behaviours in this crate, diff --git a/substrate/client/network/src/litep2p/discovery.rs b/substrate/client/network/src/litep2p/discovery.rs index 3a9454e317ccd4c16b2a88b1bf7c8f631f9a0ec4..48ec0684e763c1a0fe83f5083918e5108d06e35d 100644 --- a/substrate/client/network/src/litep2p/discovery.rs +++ b/substrate/client/network/src/litep2p/discovery.rs @@ -32,9 +32,9 @@ use litep2p::{ libp2p::{ identify::{Config as IdentifyConfig, IdentifyEvent}, kademlia::{ - Config as KademliaConfig, ConfigBuilder as KademliaConfigBuilder, - IncomingRecordValidationMode, KademliaEvent, KademliaHandle, QueryId, Quorum, - Record, RecordKey, RecordsType, + Config as KademliaConfig, ConfigBuilder as KademliaConfigBuilder, ContentProvider, + IncomingRecordValidationMode, KademliaEvent, KademliaHandle, PeerRecord, QueryId, + Quorum, Record, RecordKey, }, ping::{Config as PingConfig, PingEvent}, }, @@ -50,6 +50,7 @@ use schnellru::{ByLength, LruMap}; use std::{ cmp, collections::{HashMap, HashSet, VecDeque}, + iter, num::NonZeroUsize, pin::Pin, sync::Arc, @@ -72,11 +73,9 @@ const GET_RECORD_REDUNDANCY_FACTOR: usize = 4; /// The maximum number of tracked external addresses we allow. const MAX_EXTERNAL_ADDRESSES: u32 = 32; -/// Minimum number of confirmations received before an address is verified. -/// -/// Note: all addresses are confirmed by libp2p on the first encounter. This aims to make -/// addresses a bit more robust. -const MIN_ADDRESS_CONFIRMATIONS: usize = 2; +/// Number of times observed address is received from different peers before it is confirmed as +/// external. +const MIN_ADDRESS_CONFIRMATIONS: usize = 3; /// Discovery events. #[derive(Debug)] @@ -129,13 +128,19 @@ pub enum DiscoveryEvent { address: Multiaddr, }, - /// Record was found from the DHT. + /// `GetRecord` query succeeded. GetRecordSuccess { /// Query ID. query_id: QueryId, + }, + + /// Record was found from the DHT. + GetRecordPartialResult { + /// Query ID. + query_id: QueryId, - /// Records. - records: RecordsType, + /// Record. + record: PeerRecord, }, /// Record was successfully stored on the DHT. @@ -144,6 +149,14 @@ pub enum DiscoveryEvent { query_id: QueryId, }, + /// Providers were successfully retrieved. + GetProvidersSuccess { + /// Query ID. + query_id: QueryId, + /// Found providers sorted by distance to provided key. + providers: Vec<ContentProvider>, + }, + /// Query failed. QueryFailed { /// Query ID. @@ -246,7 +259,7 @@ impl Discovery { _peerstore_handle: Arc<dyn PeerStoreProvider>, ) -> (Self, PingConfig, IdentifyConfig, KademliaConfig, Option<MdnsConfig>) { let (ping_config, ping_event_stream) = PingConfig::default(); - let user_agent = format!("{} ({})", config.client_version, config.node_name); + let user_agent = format!("{} ({}) (litep2p)", config.client_version, config.node_name); let (identify_config, identify_event_stream) = IdentifyConfig::new("/substrate/1.0".to_string(), Some(user_agent)); @@ -407,6 +420,21 @@ impl Discovery { .await; } + /// Start providing `key`. + pub async fn start_providing(&mut self, key: KademliaKey) { + self.kademlia_handle.start_providing(key.into()).await; + } + + /// Stop providing `key`. + pub async fn stop_providing(&mut self, key: KademliaKey) { + self.kademlia_handle.stop_providing(key.into()).await; + } + + /// Get providers for `key`. + pub async fn get_providers(&mut self, key: KademliaKey) -> QueryId { + self.kademlia_handle.get_providers(key.into()).await + } + /// Check if the observed address is a known address. fn is_known_address(known: &Multiaddr, observed: &Multiaddr) -> bool { let mut known = known.iter(); @@ -480,7 +508,7 @@ impl Discovery { .flatten() .flatten(); - self.address_confirmations.insert(address.clone(), Default::default()); + self.address_confirmations.insert(address.clone(), iter::once(peer).collect()); return (false, oldest) }, @@ -550,13 +578,24 @@ impl Stream for Discovery { peers: peers.into_iter().collect(), })) }, - Poll::Ready(Some(KademliaEvent::GetRecordSuccess { query_id, records })) => { + Poll::Ready(Some(KademliaEvent::GetRecordSuccess { query_id })) => { + log::trace!( + target: LOG_TARGET, + "`GET_RECORD` succeeded for {query_id:?}", + ); + + return Poll::Ready(Some(DiscoveryEvent::GetRecordSuccess { query_id })); + }, + Poll::Ready(Some(KademliaEvent::GetRecordPartialResult { query_id, record })) => { log::trace!( target: LOG_TARGET, - "`GET_RECORD` succeeded for {query_id:?}: {records:?}", + "`GET_RECORD` intermediary succeeded for {query_id:?}: {record:?}", ); - return Poll::Ready(Some(DiscoveryEvent::GetRecordSuccess { query_id, records })); + return Poll::Ready(Some(DiscoveryEvent::GetRecordPartialResult { + query_id, + record, + })); }, Poll::Ready(Some(KademliaEvent::PutRecordSuccess { query_id, key: _ })) => return Poll::Ready(Some(DiscoveryEvent::PutRecordSuccess { query_id })), @@ -581,8 +620,22 @@ impl Stream for Discovery { return Poll::Ready(Some(DiscoveryEvent::IncomingRecord { record })) }, - // Content provider events are ignored for now. - Poll::Ready(Some(KademliaEvent::GetProvidersSuccess { .. })) | + Poll::Ready(Some(KademliaEvent::GetProvidersSuccess { + provided_key, + providers, + query_id, + })) => { + log::trace!( + target: LOG_TARGET, + "`GET_PROVIDERS` for {query_id:?} with {provided_key:?} yielded {providers:?}", + ); + + return Poll::Ready(Some(DiscoveryEvent::GetProvidersSuccess { + query_id, + providers, + })) + }, + // We do not validate incoming providers. Poll::Ready(Some(KademliaEvent::IncomingProvider { .. })) => {}, } diff --git a/substrate/client/network/src/litep2p/mod.rs b/substrate/client/network/src/litep2p/mod.rs index 10cf9f4da36dfe61ff56fec13bc7bd802de963cd..e8e132228ca8f82d1a32ff2508b5fe84efc242ca 100644 --- a/substrate/client/network/src/litep2p/mod.rs +++ b/substrate/client/network/src/litep2p/mod.rs @@ -58,7 +58,7 @@ use litep2p::{ protocol::{ libp2p::{ bitswap::Config as BitswapConfig, - kademlia::{QueryId, Record, RecordsType}, + kademlia::{QueryId, Record}, }, request_response::ConfigBuilder as RequestResponseConfigBuilder, }, @@ -100,6 +100,9 @@ mod peerstore; mod service; mod shim; +/// Timeout for connection waiting new substreams. +const KEEP_ALIVE_TIMEOUT: Duration = Duration::from_secs(10); + /// Litep2p bandwidth sink. struct Litep2pBandwidthSink { sink: litep2p::BandwidthSink, @@ -143,6 +146,17 @@ struct ConnectionContext { num_connections: usize, } +/// Kademlia query we are tracking. +#[derive(Debug)] +enum KadQuery { + /// `GET_VALUE` query for key and when it was initiated. + GetValue(RecordKey, Instant), + /// `PUT_VALUE` query for key and when it was initiated. + PutValue(RecordKey, Instant), + /// `GET_PROVIDERS` query for key and when it was initiated. + GetProviders(RecordKey, Instant), +} + /// Networking backend for `litep2p`. pub struct Litep2pNetworkBackend { /// Main `litep2p` object. @@ -157,11 +171,8 @@ pub struct Litep2pNetworkBackend { /// `Peerset` handles to notification protocols. peerset_handles: HashMap<ProtocolName, ProtocolControlHandle>, - /// Pending `GET_VALUE` queries. - pending_get_values: HashMap<QueryId, (RecordKey, Instant)>, - - /// Pending `PUT_VALUE` queries. - pending_put_values: HashMap<QueryId, (RecordKey, Instant)>, + /// Pending Kademlia queries. + pending_queries: HashMap<QueryId, KadQuery>, /// Discovery. discovery: Discovery, @@ -558,6 +569,9 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac .with_connection_limits(ConnectionLimitsConfig::default().max_incoming_connections( Some(crate::MAX_CONNECTIONS_ESTABLISHED_INCOMING as usize), )) + // This has the same effect as `libp2p::Swarm::with_idle_connection_timeout` which is + // set to 10 seconds as well. + .with_keep_alive_timeout(KEEP_ALIVE_TIMEOUT) .with_executor(executor); if let Some(config) = maybe_mdns_config { @@ -615,8 +629,7 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac peerset_handles: notif_protocols, num_connected, discovery, - pending_put_values: HashMap::new(), - pending_get_values: HashMap::new(), + pending_queries: HashMap::new(), peerstore_handle: peer_store_handle, block_announce_protocol, event_streams: out_events::OutChannels::new(None)?, @@ -704,21 +717,30 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac Some(command) => match command { NetworkServiceCommand::GetValue{ key } => { let query_id = self.discovery.get_value(key.clone()).await; - self.pending_get_values.insert(query_id, (key, Instant::now())); + self.pending_queries.insert(query_id, KadQuery::GetValue(key, Instant::now())); } NetworkServiceCommand::PutValue { key, value } => { let query_id = self.discovery.put_value(key.clone(), value).await; - self.pending_put_values.insert(query_id, (key, Instant::now())); + self.pending_queries.insert(query_id, KadQuery::PutValue(key, Instant::now())); } NetworkServiceCommand::PutValueTo { record, peers, update_local_storage} => { let kademlia_key = record.key.clone(); let query_id = self.discovery.put_value_to_peers(record.into(), peers, update_local_storage).await; - self.pending_put_values.insert(query_id, (kademlia_key, Instant::now())); + self.pending_queries.insert(query_id, KadQuery::PutValue(kademlia_key, Instant::now())); } - NetworkServiceCommand::StoreRecord { key, value, publisher, expires } => { self.discovery.store_record(key, value, publisher.map(Into::into), expires).await; } + NetworkServiceCommand::StartProviding { key } => { + self.discovery.start_providing(key).await; + } + NetworkServiceCommand::StopProviding { key } => { + self.discovery.stop_providing(key).await; + } + NetworkServiceCommand::GetProviders { key } => { + let query_id = self.discovery.get_providers(key.clone()).await; + self.pending_queries.insert(query_id, KadQuery::GetProviders(key, Instant::now())); + } NetworkServiceCommand::EventStream { tx } => { self.event_streams.push(tx); } @@ -753,7 +775,7 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac } if self.litep2p.add_known_address(peer.into(), iter::once(address.clone())) == 0usize { - log::warn!( + log::debug!( target: LOG_TARGET, "couldn't add known address ({address}) for {peer:?}, unsupported transport" ); @@ -820,27 +842,45 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac self.peerstore_handle.add_known_peer(peer.into()); } } - Some(DiscoveryEvent::GetRecordSuccess { query_id, records }) => { - match self.pending_get_values.remove(&query_id) { - None => log::warn!( + Some(DiscoveryEvent::GetRecordPartialResult { query_id, record }) => { + if !self.pending_queries.contains_key(&query_id) { + log::error!( target: LOG_TARGET, - "`GET_VALUE` succeeded for a non-existent query", - ), - Some((key, started)) => { + "Missing/invalid pending query for `GET_VALUE` partial result: {query_id:?}" + ); + + continue + } + + let peer_id: sc_network_types::PeerId = record.peer.into(); + let record = PeerRecord { + record: P2PRecord { + key: record.record.key.to_vec().into(), + value: record.record.value, + publisher: record.record.publisher.map(|peer_id| { + let peer_id: sc_network_types::PeerId = peer_id.into(); + peer_id.into() + }), + expires: record.record.expires, + }, + peer: Some(peer_id.into()), + }; + + self.event_streams.send( + Event::Dht( + DhtEvent::ValueFound( + record.into() + ) + ) + ); + } + Some(DiscoveryEvent::GetRecordSuccess { query_id }) => { + match self.pending_queries.remove(&query_id) { + Some(KadQuery::GetValue(key, started)) => { log::trace!( target: LOG_TARGET, - "`GET_VALUE` for {:?} ({query_id:?}) succeeded", - key, + "`GET_VALUE` for {key:?} ({query_id:?}) succeeded", ); - for record in litep2p_to_libp2p_peer_record(records) { - self.event_streams.send( - Event::Dht( - DhtEvent::ValueFound( - record.into() - ) - ) - ); - } if let Some(ref metrics) = self.metrics { metrics @@ -848,16 +888,19 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac .with_label_values(&["value-get"]) .observe(started.elapsed().as_secs_f64()); } - } + }, + query => { + log::error!( + target: LOG_TARGET, + "Missing/invalid pending query for `GET_VALUE`: {query:?}" + ); + debug_assert!(false); + }, } } Some(DiscoveryEvent::PutRecordSuccess { query_id }) => { - match self.pending_put_values.remove(&query_id) { - None => log::warn!( - target: LOG_TARGET, - "`PUT_VALUE` succeeded for a non-existent query", - ), - Some((key, started)) => { + match self.pending_queries.remove(&query_id) { + Some(KadQuery::PutValue(key, started)) => { log::trace!( target: LOG_TARGET, "`PUT_VALUE` for {key:?} ({query_id:?}) succeeded", @@ -873,35 +916,50 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac .with_label_values(&["value-put"]) .observe(started.elapsed().as_secs_f64()); } + }, + query => { + log::error!( + target: LOG_TARGET, + "Missing/invalid pending query for `PUT_VALUE`: {query:?}" + ); + debug_assert!(false); } } } - Some(DiscoveryEvent::QueryFailed { query_id }) => { - match self.pending_get_values.remove(&query_id) { - None => match self.pending_put_values.remove(&query_id) { - None => log::warn!( + Some(DiscoveryEvent::GetProvidersSuccess { query_id, providers }) => { + match self.pending_queries.remove(&query_id) { + Some(KadQuery::GetProviders(key, started)) => { + log::trace!( target: LOG_TARGET, - "non-existent query failed ({query_id:?})", - ), - Some((key, started)) => { - log::debug!( - target: LOG_TARGET, - "`PUT_VALUE` ({query_id:?}) failed for key {key:?}", - ); - - self.event_streams.send(Event::Dht( - DhtEvent::ValuePutFailed(key) - )); - - if let Some(ref metrics) = self.metrics { - metrics - .kademlia_query_duration - .with_label_values(&["value-put-failed"]) - .observe(started.elapsed().as_secs_f64()); - } + "`GET_PROVIDERS` for {key:?} ({query_id:?}) succeeded", + ); + + self.event_streams.send(Event::Dht( + DhtEvent::ProvidersFound( + key.into(), + providers.into_iter().map(|p| p.peer.into()).collect() + ) + )); + + if let Some(ref metrics) = self.metrics { + metrics + .kademlia_query_duration + .with_label_values(&["providers-get"]) + .observe(started.elapsed().as_secs_f64()); } + }, + query => { + log::error!( + target: LOG_TARGET, + "Missing/invalid pending query for `GET_PROVIDERS`: {query:?}" + ); + debug_assert!(false); } - Some((key, started)) => { + } + } + Some(DiscoveryEvent::QueryFailed { query_id }) => { + match self.pending_queries.remove(&query_id) { + Some(KadQuery::GetValue(key, started)) => { log::debug!( target: LOG_TARGET, "`GET_VALUE` ({query_id:?}) failed for key {key:?}", @@ -917,6 +975,46 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac .with_label_values(&["value-get-failed"]) .observe(started.elapsed().as_secs_f64()); } + }, + Some(KadQuery::PutValue(key, started)) => { + log::debug!( + target: LOG_TARGET, + "`PUT_VALUE` ({query_id:?}) failed for key {key:?}", + ); + + self.event_streams.send(Event::Dht( + DhtEvent::ValuePutFailed(key) + )); + + if let Some(ref metrics) = self.metrics { + metrics + .kademlia_query_duration + .with_label_values(&["value-put-failed"]) + .observe(started.elapsed().as_secs_f64()); + } + }, + Some(KadQuery::GetProviders(key, started)) => { + log::debug!( + target: LOG_TARGET, + "`GET_PROVIDERS` ({query_id:?}) failed for key {key:?}" + ); + + self.event_streams.send(Event::Dht( + DhtEvent::ProvidersNotFound(key) + )); + + if let Some(ref metrics) = self.metrics { + metrics + .kademlia_query_duration + .with_label_values(&["providers-get-failed"]) + .observe(started.elapsed().as_secs_f64()); + } + }, + None => { + log::warn!( + target: LOG_TARGET, + "non-existent query failed ({query_id:?})", + ); } } } @@ -986,7 +1084,15 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac let direction = match endpoint { Endpoint::Dialer { .. } => "out", - Endpoint::Listener { .. } => "in", + Endpoint::Listener { .. } => { + // Increment incoming connections counter. + // + // Note: For litep2p these are represented by established negotiated connections, + // while for libp2p (legacy) these represent not-yet-negotiated connections. + metrics.incoming_connections_total.inc(); + + "in" + }, }; metrics.connections_opened_total.with_label_values(&[direction]).inc(); @@ -1058,6 +1164,7 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac NegotiationError::ParseError(_) => "parse-error", NegotiationError::IoError(_) => "io-error", NegotiationError::WebSocket(_) => "webscoket-error", + NegotiationError::BadSignature => "bad-signature", } }; @@ -1086,42 +1193,3 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkBackend<B, H> for Litep2pNetworkBac } } } - -// Glue code to convert from a litep2p records type to a libp2p2 PeerRecord. -fn litep2p_to_libp2p_peer_record(records: RecordsType) -> Vec<PeerRecord> { - match records { - litep2p::protocol::libp2p::kademlia::RecordsType::LocalStore(record) => { - vec![PeerRecord { - record: P2PRecord { - key: record.key.to_vec().into(), - value: record.value, - publisher: record.publisher.map(|peer_id| { - let peer_id: sc_network_types::PeerId = peer_id.into(); - peer_id.into() - }), - expires: record.expires, - }, - peer: None, - }] - }, - litep2p::protocol::libp2p::kademlia::RecordsType::Network(records) => records - .into_iter() - .map(|record| { - let peer_id: sc_network_types::PeerId = record.peer.into(); - - PeerRecord { - record: P2PRecord { - key: record.record.key.to_vec().into(), - value: record.record.value, - publisher: record.record.publisher.map(|peer_id| { - let peer_id: sc_network_types::PeerId = peer_id.into(); - peer_id.into() - }), - expires: record.record.expires, - }, - peer: Some(peer_id.into()), - } - }) - .collect::<Vec<_>>(), - } -} diff --git a/substrate/client/network/src/litep2p/service.rs b/substrate/client/network/src/litep2p/service.rs index fa1d47e5a1b792733295ce5d72e51316730cbb35..2d4a117d15631fea4dc5cdf0643fb5bbae06c66e 100644 --- a/substrate/client/network/src/litep2p/service.rs +++ b/substrate/client/network/src/litep2p/service.rs @@ -28,8 +28,8 @@ use crate::{ peer_store::PeerStoreProvider, service::out_events, Event, IfDisconnected, NetworkDHTProvider, NetworkEventStream, NetworkPeers, NetworkRequest, - NetworkSigner, NetworkStateInfo, NetworkStatus, NetworkStatusProvider, ProtocolName, - RequestFailure, Signature, + NetworkSigner, NetworkStateInfo, NetworkStatus, NetworkStatusProvider, OutboundFailure, + ProtocolName, RequestFailure, Signature, }; use codec::DecodeAll; @@ -104,6 +104,15 @@ pub enum NetworkServiceCommand { expires: Option<Instant>, }, + /// Start providing `key`. + StartProviding { key: KademliaKey }, + + /// Stop providing `key`. + StopProviding { key: KademliaKey }, + + /// Get providers for `key`. + GetProviders { key: KademliaKey }, + /// Query network status. Status { /// `oneshot::Sender` for sending the status. @@ -296,6 +305,18 @@ impl NetworkDHTProvider for Litep2pNetworkService { expires, }); } + + fn start_providing(&self, key: KademliaKey) { + let _ = self.cmd_tx.unbounded_send(NetworkServiceCommand::StartProviding { key }); + } + + fn stop_providing(&self, key: KademliaKey) { + let _ = self.cmd_tx.unbounded_send(NetworkServiceCommand::StopProviding { key }); + } + + fn get_providers(&self, key: KademliaKey) { + let _ = self.cmd_tx.unbounded_send(NetworkServiceCommand::GetProviders { key }); + } } #[async_trait::async_trait] @@ -505,13 +526,23 @@ impl NetworkStateInfo for Litep2pNetworkService { impl NetworkRequest for Litep2pNetworkService { async fn request( &self, - _target: PeerId, - _protocol: ProtocolName, - _request: Vec<u8>, - _fallback_request: Option<(Vec<u8>, ProtocolName)>, - _connect: IfDisconnected, + target: PeerId, + protocol: ProtocolName, + request: Vec<u8>, + fallback_request: Option<(Vec<u8>, ProtocolName)>, + connect: IfDisconnected, ) -> Result<(Vec<u8>, ProtocolName), RequestFailure> { - unimplemented!(); + let (tx, rx) = oneshot::channel(); + + self.start_request(target, protocol, request, fallback_request, tx, connect); + + match rx.await { + Ok(v) => v, + // The channel can only be closed if the network worker no longer exists. If the + // network worker no longer exists, then all connections to `target` are necessarily + // closed, and we legitimately report this situation as a "ConnectionClosed". + Err(_) => Err(RequestFailure::Network(OutboundFailure::ConnectionClosed)), + } } fn start_request( diff --git a/substrate/client/network/src/litep2p/shim/request_response/mod.rs b/substrate/client/network/src/litep2p/shim/request_response/mod.rs index bfd7a60ef9fe60f65c8407eeb9343453b2409306..690d5a31e6ad645103dcc227a088217a94ffb9a1 100644 --- a/substrate/client/network/src/litep2p/shim/request_response/mod.rs +++ b/substrate/client/network/src/litep2p/shim/request_response/mod.rs @@ -273,6 +273,13 @@ impl RequestResponseProtocol { request_id: RequestId, request: Vec<u8>, ) { + log::trace!( + target: LOG_TARGET, + "{}: request received from {peer:?} ({fallback:?} {request_id:?}), request size {:?}", + self.protocol, + request.len(), + ); + let Some(inbound_queue) = &self.inbound_queue else { log::trace!( target: LOG_TARGET, @@ -284,12 +291,18 @@ impl RequestResponseProtocol { return; }; - log::trace!( - target: LOG_TARGET, - "{}: request received from {peer:?} ({fallback:?} {request_id:?}), request size {:?}", - self.protocol, - request.len(), - ); + if self.peerstore_handle.is_banned(&peer.into()) { + log::trace!( + target: LOG_TARGET, + "{}: rejecting inbound request from banned {peer:?} ({request_id:?})", + self.protocol, + ); + + self.handle.reject_request(request_id); + self.metrics.register_inbound_request_failure("banned-peer"); + return; + } + let (tx, rx) = oneshot::channel(); match inbound_queue.try_send(IncomingRequest { @@ -320,7 +333,7 @@ impl RequestResponseProtocol { &mut self, peer: litep2p::PeerId, request_id: RequestId, - fallback: Option<litep2p::ProtocolName>, + _fallback: Option<litep2p::ProtocolName>, response: Vec<u8>, ) { match self.pending_inbound_responses.remove(&request_id) { @@ -337,10 +350,7 @@ impl RequestResponseProtocol { response.len(), ); - let _ = tx.send(Ok(( - response, - fallback.map_or_else(|| self.protocol.clone(), Into::into), - ))); + let _ = tx.send(Ok((response, self.protocol.clone()))); self.metrics.register_outbound_request_success(started.elapsed()); }, } diff --git a/substrate/client/network/src/network_state.rs b/substrate/client/network/src/network_state.rs index cf8b8b55a7ff8e40fed261b90ca9a75fe979ebe6..65fd494739ee39909601733966b1e092d070eee4 100644 --- a/substrate/client/network/src/network_state.rs +++ b/substrate/client/network/src/network_state.rs @@ -106,7 +106,7 @@ pub enum Endpoint { impl From<ConnectedPoint> for PeerEndpoint { fn from(endpoint: ConnectedPoint) -> Self { match endpoint { - ConnectedPoint::Dialer { address, role_override } => + ConnectedPoint::Dialer { address, role_override, port_use: _ } => Self::Dialing(address, role_override.into()), ConnectedPoint::Listener { local_addr, send_back_addr } => Self::Listening { local_addr, send_back_addr }, diff --git a/substrate/client/network/src/peer_info.rs b/substrate/client/network/src/peer_info.rs index 21eeea6bcc0c30415c646fdd18abb6f5f56ae09d..29544b8be70aad018a3e28e17e8fdc119e5654d6 100644 --- a/substrate/client/network/src/peer_info.rs +++ b/substrate/client/network/src/peer_info.rs @@ -19,36 +19,38 @@ //! [`PeerInfoBehaviour`] is implementation of `NetworkBehaviour` that holds information about peers //! in cache. -use crate::utils::interval; +use crate::{utils::interval, LOG_TARGET}; use either::Either; use fnv::FnvHashMap; use futures::prelude::*; use libp2p::{ - core::{ConnectedPoint, Endpoint}, + core::{transport::PortUse, ConnectedPoint, Endpoint}, identify::{ Behaviour as Identify, Config as IdentifyConfig, Event as IdentifyEvent, Info as IdentifyInfo, }, identity::PublicKey, + multiaddr::Protocol, ping::{Behaviour as Ping, Config as PingConfig, Event as PingEvent}, swarm::{ behaviour::{ - AddressChange, ConnectionClosed, ConnectionEstablished, DialFailure, - ExternalAddrConfirmed, FromSwarm, ListenFailure, + AddressChange, ConnectionClosed, ConnectionEstablished, DialFailure, FromSwarm, + ListenFailure, }, ConnectionDenied, ConnectionHandler, ConnectionHandlerSelect, ConnectionId, - NetworkBehaviour, NewExternalAddrCandidate, PollParameters, THandler, THandlerInEvent, - THandlerOutEvent, ToSwarm, + NetworkBehaviour, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }, Multiaddr, PeerId, }; -use log::{debug, error, trace}; +use log::{debug, error, trace, warn}; use parking_lot::Mutex; +use schnellru::{ByLength, LruMap}; use smallvec::SmallVec; use std::{ collections::{hash_map::Entry, HashSet, VecDeque}, + iter, pin::Pin, sync::Arc, task::{Context, Poll}, @@ -59,6 +61,11 @@ use std::{ const CACHE_EXPIRE: Duration = Duration::from_secs(10 * 60); /// Interval at which we perform garbage collection on the node info. const GARBAGE_COLLECT_INTERVAL: Duration = Duration::from_secs(2 * 60); +/// The maximum number of tracked external addresses we allow. +const MAX_EXTERNAL_ADDRESSES: u32 = 32; +/// Number of times observed address is received from different peers before it is confirmed as +/// external. +const MIN_ADDRESS_CONFIRMATIONS: usize = 3; /// Implementation of `NetworkBehaviour` that holds information about peers in cache. pub struct PeerInfoBehaviour { @@ -70,7 +77,16 @@ pub struct PeerInfoBehaviour { nodes_info: FnvHashMap<PeerId, NodeInfo>, /// Interval at which we perform garbage collection in `nodes_info`. garbage_collect: Pin<Box<dyn Stream<Item = ()> + Send>>, + /// PeerId of the local node. + local_peer_id: PeerId, + /// Public addresses supplied by the operator. Never expire. + public_addresses: Vec<Multiaddr>, + /// Listen addresses. External addresses matching listen addresses never expire. + listen_addresses: HashSet<Multiaddr>, + /// External address confirmations. + address_confirmations: LruMap<Multiaddr, HashSet<PeerId>>, /// Record keeping of external addresses. Data is queried by the `NetworkService`. + /// The addresses contain the `/p2p/...` part with local peer ID. external_addresses: ExternalAddresses, /// Pending events to emit to [`Swarm`](libp2p::swarm::Swarm). pending_actions: VecDeque<ToSwarm<PeerInfoEvent, THandlerInEvent<PeerInfoBehaviour>>>, @@ -106,13 +122,13 @@ pub struct ExternalAddresses { impl ExternalAddresses { /// Add an external address. - pub fn add(&mut self, addr: Multiaddr) { - self.addresses.lock().insert(addr); + pub fn add(&mut self, addr: Multiaddr) -> bool { + self.addresses.lock().insert(addr) } /// Remove an external address. - pub fn remove(&mut self, addr: &Multiaddr) { - self.addresses.lock().remove(addr); + pub fn remove(&mut self, addr: &Multiaddr) -> bool { + self.addresses.lock().remove(addr) } } @@ -122,9 +138,10 @@ impl PeerInfoBehaviour { user_agent: String, local_public_key: PublicKey, external_addresses: Arc<Mutex<HashSet<Multiaddr>>>, + public_addresses: Vec<Multiaddr>, ) -> Self { let identify = { - let cfg = IdentifyConfig::new("/substrate/1.0".to_string(), local_public_key) + let cfg = IdentifyConfig::new("/substrate/1.0".to_string(), local_public_key.clone()) .with_agent_version(user_agent) // We don't need any peer information cached. .with_cache_size(0); @@ -136,6 +153,10 @@ impl PeerInfoBehaviour { identify, nodes_info: FnvHashMap::default(), garbage_collect: Box::pin(interval(GARBAGE_COLLECT_INTERVAL)), + local_peer_id: local_public_key.to_peer_id(), + public_addresses, + listen_addresses: HashSet::new(), + address_confirmations: LruMap::new(ByLength::new(MAX_EXTERNAL_ADDRESSES)), external_addresses: ExternalAddresses { addresses: external_addresses }, pending_actions: Default::default(), } @@ -158,25 +179,137 @@ impl PeerInfoBehaviour { ping_time: Duration, connection: ConnectionId, ) { - trace!(target: "sub-libp2p", "Ping time with {:?} via {:?}: {:?}", peer_id, connection, ping_time); + trace!(target: LOG_TARGET, "Ping time with {:?} via {:?}: {:?}", peer_id, connection, ping_time); if let Some(entry) = self.nodes_info.get_mut(peer_id) { entry.latest_ping = Some(ping_time); } else { - error!(target: "sub-libp2p", + error!(target: LOG_TARGET, "Received ping from node we're not connected to {:?} via {:?}", peer_id, connection); } } - /// Inserts an identify record in the cache. Has no effect if we don't have any entry for that - /// node, which shouldn't happen. + /// Ensure address has the `/p2p/...` part with local peer id. Returns `Err` if the address + /// already contains a different peer id. + fn with_local_peer_id(&self, address: Multiaddr) -> Result<Multiaddr, Multiaddr> { + if let Some(Protocol::P2p(peer_id)) = address.iter().last() { + if peer_id == self.local_peer_id { + Ok(address) + } else { + Err(address) + } + } else { + Ok(address.with(Protocol::P2p(self.local_peer_id))) + } + } + + /// Inserts an identify record in the cache & discovers external addresses when multiple + /// peers report the same address as observed. fn handle_identify_report(&mut self, peer_id: &PeerId, info: &IdentifyInfo) { - trace!(target: "sub-libp2p", "Identified {:?} => {:?}", peer_id, info); + trace!(target: LOG_TARGET, "Identified {:?} => {:?}", peer_id, info); if let Some(entry) = self.nodes_info.get_mut(peer_id) { entry.client_version = Some(info.agent_version.clone()); } else { - error!(target: "sub-libp2p", - "Received pong from node we're not connected to {:?}", peer_id); + error!(target: LOG_TARGET, + "Received identify message from node we're not connected to {peer_id:?}"); + } + // Discover external addresses. + match self.with_local_peer_id(info.observed_addr.clone()) { + Ok(observed_addr) => { + let (is_new, expired) = self.is_new_external_address(&observed_addr, *peer_id); + if is_new && self.external_addresses.add(observed_addr.clone()) { + trace!( + target: LOG_TARGET, + "Observed address reported by Identify confirmed as external {}", + observed_addr, + ); + self.pending_actions.push_back(ToSwarm::ExternalAddrConfirmed(observed_addr)); + } + if let Some(expired) = expired { + trace!(target: LOG_TARGET, "Removing replaced external address: {expired}"); + self.external_addresses.remove(&expired); + self.pending_actions.push_back(ToSwarm::ExternalAddrExpired(expired)); + } + }, + Err(addr) => { + warn!( + target: LOG_TARGET, + "Identify reported observed address for a peer that is not us: {addr}", + ); + }, + } + } + + /// Check if addresses are equal taking into account they can contain or not contain + /// the `/p2p/...` part. + fn is_same_address(left: &Multiaddr, right: &Multiaddr) -> bool { + let mut left = left.iter(); + let mut right = right.iter(); + + loop { + match (left.next(), right.next()) { + (None, None) => return true, + (None, Some(Protocol::P2p(_))) => return true, + (Some(Protocol::P2p(_)), None) => return true, + (left, right) if left != right => return false, + _ => {}, + } + } + } + + /// Check if `address` can be considered a new external address. + /// + /// If this address replaces an older address, the expired address is returned. + fn is_new_external_address( + &mut self, + address: &Multiaddr, + peer_id: PeerId, + ) -> (bool, Option<Multiaddr>) { + trace!(target: LOG_TARGET, "Verify new external address: {address}"); + + // Public and listen addresses don't count towards discovered external addresses + // and are always confirmed. + // Because they are not kept in the LRU, they are never replaced by discovered + // external addresses. + if self + .listen_addresses + .iter() + .chain(self.public_addresses.iter()) + .any(|known_address| PeerInfoBehaviour::is_same_address(&known_address, &address)) + { + return (true, None) + } + + match self.address_confirmations.get(address) { + Some(confirmations) => { + confirmations.insert(peer_id); + + if confirmations.len() >= MIN_ADDRESS_CONFIRMATIONS { + return (true, None) + } + }, + None => { + let oldest = (self.address_confirmations.len() >= + self.address_confirmations.limiter().max_length() as usize) + .then(|| { + self.address_confirmations.pop_oldest().map(|(address, peers)| { + if peers.len() >= MIN_ADDRESS_CONFIRMATIONS { + return Some(address) + } else { + None + } + }) + }) + .flatten() + .flatten(); + + self.address_confirmations + .insert(address.clone(), iter::once(peer_id).collect()); + + return (false, oldest) + }, } + + (false, None) } } @@ -275,23 +408,26 @@ impl NetworkBehaviour for PeerInfoBehaviour { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result<THandler<Self>, ConnectionDenied> { let ping_handler = self.ping.handle_established_outbound_connection( connection_id, peer, addr, role_override, + port_use, )?; let identify_handler = self.identify.handle_established_outbound_connection( connection_id, peer, addr, role_override, + port_use, )?; Ok(ping_handler.select(identify_handler)) } - fn on_swarm_event(&mut self, event: FromSwarm<Self::ConnectionHandler>) { + fn on_swarm_event(&mut self, event: FromSwarm) { match event { FromSwarm::ConnectionEstablished( e @ ConnectionEstablished { peer_id, endpoint, .. }, @@ -319,22 +455,21 @@ impl NetworkBehaviour for PeerInfoBehaviour { peer_id, connection_id, endpoint, - handler, + cause, remaining_established, }) => { - let (ping_handler, identity_handler) = handler.into_inner(); self.ping.on_swarm_event(FromSwarm::ConnectionClosed(ConnectionClosed { peer_id, connection_id, endpoint, - handler: ping_handler, + cause, remaining_established, })); self.identify.on_swarm_event(FromSwarm::ConnectionClosed(ConnectionClosed { peer_id, connection_id, endpoint, - handler: identity_handler, + cause, remaining_established, })); @@ -344,7 +479,7 @@ impl NetworkBehaviour for PeerInfoBehaviour { } entry.endpoints.retain(|ep| ep != endpoint) } else { - error!(target: "sub-libp2p", + error!(target: LOG_TARGET, "Unknown connection to {:?} closed: {:?}", peer_id, endpoint); } }, @@ -369,18 +504,21 @@ impl NetworkBehaviour for PeerInfoBehaviour { send_back_addr, error, connection_id, + peer_id, }) => { self.ping.on_swarm_event(FromSwarm::ListenFailure(ListenFailure { local_addr, send_back_addr, error, connection_id, + peer_id, })); self.identify.on_swarm_event(FromSwarm::ListenFailure(ListenFailure { local_addr, send_back_addr, error, connection_id, + peer_id, })); }, FromSwarm::ListenerError(e) => { @@ -395,28 +533,36 @@ impl NetworkBehaviour for PeerInfoBehaviour { self.ping.on_swarm_event(FromSwarm::NewListener(e)); self.identify.on_swarm_event(FromSwarm::NewListener(e)); }, + FromSwarm::NewListenAddr(e) => { + self.ping.on_swarm_event(FromSwarm::NewListenAddr(e)); + self.identify.on_swarm_event(FromSwarm::NewListenAddr(e)); + self.listen_addresses.insert(e.addr.clone()); + }, FromSwarm::ExpiredListenAddr(e) => { self.ping.on_swarm_event(FromSwarm::ExpiredListenAddr(e)); self.identify.on_swarm_event(FromSwarm::ExpiredListenAddr(e)); - self.external_addresses.remove(e.addr); + self.listen_addresses.remove(e.addr); + // Remove matching external address. + match self.with_local_peer_id(e.addr.clone()) { + Ok(addr) => { + self.external_addresses.remove(&addr); + self.pending_actions.push_back(ToSwarm::ExternalAddrExpired(addr)); + }, + Err(addr) => { + warn!( + target: LOG_TARGET, + "Listen address expired with peer ID that is not us: {addr}", + ); + }, + } }, - FromSwarm::NewExternalAddrCandidate(e @ NewExternalAddrCandidate { addr }) => { + FromSwarm::NewExternalAddrCandidate(e) => { self.ping.on_swarm_event(FromSwarm::NewExternalAddrCandidate(e)); self.identify.on_swarm_event(FromSwarm::NewExternalAddrCandidate(e)); - - // Manually confirm all external address candidates. - // TODO: consider adding [AutoNAT protocol](https://docs.rs/libp2p/0.52.3/libp2p/autonat/index.html) - // (must go through the polkadot protocol spec) or implemeting heuristics for - // approving external address candidates. This can be done, for example, by - // approving only addresses reported by multiple peers. - // See also https://github.com/libp2p/rust-libp2p/pull/4721 introduced - // in libp2p v0.53 for heuristics approach. - self.pending_actions.push_back(ToSwarm::ExternalAddrConfirmed(addr.clone())); }, - FromSwarm::ExternalAddrConfirmed(e @ ExternalAddrConfirmed { addr }) => { + FromSwarm::ExternalAddrConfirmed(e) => { self.ping.on_swarm_event(FromSwarm::ExternalAddrConfirmed(e)); self.identify.on_swarm_event(FromSwarm::ExternalAddrConfirmed(e)); - self.external_addresses.add(addr.clone()); }, FromSwarm::AddressChange(e @ AddressChange { peer_id, old, new, .. }) => { self.ping.on_swarm_event(FromSwarm::AddressChange(e)); @@ -426,17 +572,18 @@ impl NetworkBehaviour for PeerInfoBehaviour { if let Some(endpoint) = entry.endpoints.iter_mut().find(|e| e == &old) { *endpoint = new.clone(); } else { - error!(target: "sub-libp2p", + error!(target: LOG_TARGET, "Unknown address change for peer {:?} from {:?} to {:?}", peer_id, old, new); } } else { - error!(target: "sub-libp2p", + error!(target: LOG_TARGET, "Unknown peer {:?} to change address from {:?} to {:?}", peer_id, old, new); } }, - FromSwarm::NewListenAddr(e) => { - self.ping.on_swarm_event(FromSwarm::NewListenAddr(e)); - self.identify.on_swarm_event(FromSwarm::NewListenAddr(e)); + event => { + debug!(target: LOG_TARGET, "New unknown `FromSwarm` libp2p event: {event:?}"); + self.ping.on_swarm_event(event); + self.identify.on_swarm_event(event); }, } } @@ -455,47 +602,29 @@ impl NetworkBehaviour for PeerInfoBehaviour { } } - fn poll( - &mut self, - cx: &mut Context, - params: &mut impl PollParameters, - ) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> { + fn poll(&mut self, cx: &mut Context) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> { if let Some(event) = self.pending_actions.pop_front() { return Poll::Ready(event) } loop { - match self.ping.poll(cx, params) { + match self.ping.poll(cx) { Poll::Pending => break, Poll::Ready(ToSwarm::GenerateEvent(ev)) => { if let PingEvent { peer, result: Ok(rtt), connection } = ev { self.handle_ping_report(&peer, rtt, connection) } }, - Poll::Ready(ToSwarm::Dial { opts }) => return Poll::Ready(ToSwarm::Dial { opts }), - Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, event }) => - return Poll::Ready(ToSwarm::NotifyHandler { - peer_id, - handler, - event: Either::Left(event), - }), - Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }) => - return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), - Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)) => - return Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)), - Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)) => - return Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)), - Poll::Ready(ToSwarm::ExternalAddrExpired(addr)) => - return Poll::Ready(ToSwarm::ExternalAddrExpired(addr)), - Poll::Ready(ToSwarm::ListenOn { opts }) => - return Poll::Ready(ToSwarm::ListenOn { opts }), - Poll::Ready(ToSwarm::RemoveListener { id }) => - return Poll::Ready(ToSwarm::RemoveListener { id }), + Poll::Ready(event) => { + return Poll::Ready(event.map_in(Either::Left).map_out(|_| { + unreachable!("`GenerateEvent` is handled in a branch above; qed") + })); + }, } } loop { - match self.identify.poll(cx, params) { + match self.identify.poll(cx) { Poll::Pending => break, Poll::Ready(ToSwarm::GenerateEvent(event)) => match event { IdentifyEvent::Received { peer_id, info, .. } => { @@ -503,31 +632,20 @@ impl NetworkBehaviour for PeerInfoBehaviour { let event = PeerInfoEvent::Identified { peer_id, info }; return Poll::Ready(ToSwarm::GenerateEvent(event)) }, - IdentifyEvent::Error { peer_id, error } => { - debug!(target: "sub-libp2p", "Identification with peer {:?} failed => {}", peer_id, error) + IdentifyEvent::Error { connection_id, peer_id, error } => { + debug!( + target: LOG_TARGET, + "Identification with peer {peer_id:?}({connection_id}) failed => {error}" + ); }, IdentifyEvent::Pushed { .. } => {}, IdentifyEvent::Sent { .. } => {}, }, - Poll::Ready(ToSwarm::Dial { opts }) => return Poll::Ready(ToSwarm::Dial { opts }), - Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, event }) => - return Poll::Ready(ToSwarm::NotifyHandler { - peer_id, - handler, - event: Either::Right(event), - }), - Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }) => - return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), - Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)) => - return Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)), - Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)) => - return Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)), - Poll::Ready(ToSwarm::ExternalAddrExpired(addr)) => - return Poll::Ready(ToSwarm::ExternalAddrExpired(addr)), - Poll::Ready(ToSwarm::ListenOn { opts }) => - return Poll::Ready(ToSwarm::ListenOn { opts }), - Poll::Ready(ToSwarm::RemoveListener { id }) => - return Poll::Ready(ToSwarm::RemoveListener { id }), + Poll::Ready(event) => { + return Poll::Ready(event.map_in(Either::Right).map_out(|_| { + unreachable!("`GenerateEvent` is handled in a branch above; qed") + })); + }, } } diff --git a/substrate/client/network/src/protocol.rs b/substrate/client/network/src/protocol.rs index 402baa7bb2a407cfd9d44f9248bbac2ee6cc8cdf..81e1848adefa277d86e6bd05c8941bbda13e8cda 100644 --- a/substrate/client/network/src/protocol.rs +++ b/substrate/client/network/src/protocol.rs @@ -27,14 +27,14 @@ use crate::{ use codec::Encode; use libp2p::{ - core::Endpoint, + core::{transport::PortUse, Endpoint}, swarm::{ - behaviour::FromSwarm, ConnectionDenied, ConnectionId, NetworkBehaviour, PollParameters, - THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, + behaviour::FromSwarm, ConnectionDenied, ConnectionId, NetworkBehaviour, THandler, + THandlerInEvent, THandlerOutEvent, ToSwarm, }, Multiaddr, PeerId, }; -use log::warn; +use log::{debug, warn}; use codec::DecodeAll; use sc_network_common::role::Roles; @@ -47,14 +47,15 @@ use notifications::{Notifications, NotificationsOut}; pub(crate) use notifications::ProtocolHandle; -pub use notifications::{ - notification_service, NotificationsSink, NotifsHandlerError, ProtocolHandlePair, Ready, -}; +pub use notifications::{notification_service, NotificationsSink, ProtocolHandlePair, Ready}; mod notifications; pub mod message; +// Log target for this file. +const LOG_TARGET: &str = "sub-libp2p"; + /// Maximum size used for notifications in the block announce and transaction protocols. // Must be equal to `max(MAX_BLOCK_ANNOUNCE_SIZE, MAX_TRANSACTIONS_SIZE)`. pub(crate) const BLOCK_ANNOUNCES_TRANSACTIONS_SUBSTREAM_SIZE: u64 = MAX_RESPONSE_SIZE; @@ -126,6 +127,10 @@ impl<B: BlockT> Protocol<B> { handle.set_metrics(notification_metrics.clone()); }); + protocol_configs.iter().enumerate().for_each(|(i, (p, _, _))| { + debug!(target: LOG_TARGET, "Notifications protocol {:?}: {}", SetId::from(i), p.name); + }); + ( Notifications::new( protocol_controller_handles, @@ -166,7 +171,7 @@ impl<B: BlockT> Protocol<B> { { self.behaviour.disconnect_peer(peer_id, SetId::from(position)); } else { - warn!(target: "sub-libp2p", "disconnect_peer() with invalid protocol name") + warn!(target: LOG_TARGET, "disconnect_peer() with invalid protocol name") } } @@ -250,12 +255,14 @@ impl<B: BlockT> NetworkBehaviour for Protocol<B> { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result<THandler<Self>, ConnectionDenied> { self.behaviour.handle_established_outbound_connection( connection_id, peer, addr, role_override, + port_use, ) } @@ -271,7 +278,7 @@ impl<B: BlockT> NetworkBehaviour for Protocol<B> { Ok(Vec::new()) } - fn on_swarm_event(&mut self, event: FromSwarm<Self::ConnectionHandler>) { + fn on_swarm_event(&mut self, event: FromSwarm) { self.behaviour.on_swarm_event(event); } @@ -287,26 +294,15 @@ impl<B: BlockT> NetworkBehaviour for Protocol<B> { fn poll( &mut self, cx: &mut std::task::Context, - params: &mut impl PollParameters, ) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> { - let event = match self.behaviour.poll(cx, params) { + let event = match self.behaviour.poll(cx) { Poll::Pending => return Poll::Pending, Poll::Ready(ToSwarm::GenerateEvent(ev)) => ev, - Poll::Ready(ToSwarm::Dial { opts }) => return Poll::Ready(ToSwarm::Dial { opts }), - Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, event }) => - return Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, event }), - Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }) => - return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), - Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)) => - return Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)), - Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)) => - return Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)), - Poll::Ready(ToSwarm::ExternalAddrExpired(addr)) => - return Poll::Ready(ToSwarm::ExternalAddrExpired(addr)), - Poll::Ready(ToSwarm::ListenOn { opts }) => - return Poll::Ready(ToSwarm::ListenOn { opts }), - Poll::Ready(ToSwarm::RemoveListener { id }) => - return Poll::Ready(ToSwarm::RemoveListener { id }), + Poll::Ready(event) => { + return Poll::Ready(event.map_out(|_| { + unreachable!("`GenerateEvent` is handled in a branch above; qed") + })); + }, }; let outcome = match event { diff --git a/substrate/client/network/src/protocol/message.rs b/substrate/client/network/src/protocol/message.rs index 5f2511fd6ddc93d4ef2018b43d7be9e3d138ba32..e9dc57a79356cb2813b7161b3b61140bccacd066 100644 --- a/substrate/client/network/src/protocol/message.rs +++ b/substrate/client/network/src/protocol/message.rs @@ -22,16 +22,6 @@ use codec::{Decode, Encode}; use sc_client_api::StorageProof; use sc_network_common::message::RequestId; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; - -/// Type alias for using the message type using block type parameters. -#[allow(unused)] -pub type Message<B> = generic::Message< - <B as BlockT>::Header, - <B as BlockT>::Hash, - <<B as BlockT>::Header as HeaderT>::Number, - <B as BlockT>::Extrinsic, ->; /// Remote call response. #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] @@ -53,17 +43,9 @@ pub struct RemoteReadResponse { /// Generic types. pub mod generic { - use super::{RemoteCallResponse, RemoteReadResponse}; use codec::{Decode, Encode, Input}; use sc_client_api::StorageProof; - use sc_network_common::{ - message::RequestId, - role::Roles, - sync::message::{ - generic::{BlockRequest, BlockResponse}, - BlockAnnounce, - }, - }; + use sc_network_common::{message::RequestId, role::Roles}; use sp_runtime::ConsensusEngineId; /// Consensus is mostly opaque to us @@ -75,47 +57,6 @@ pub mod generic { pub data: Vec<u8>, } - /// A network message. - #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] - pub enum Message<Header, Hash, Number, Extrinsic> { - /// Status packet. - Status(Status<Hash, Number>), - /// Block request. - BlockRequest(BlockRequest<Hash, Number>), - /// Block response. - BlockResponse(BlockResponse<Header, Hash, Extrinsic>), - /// Block announce. - BlockAnnounce(BlockAnnounce<Header>), - /// Consensus protocol message. - // NOTE: index is incremented by 1 due to transaction-related - // message that was removed - #[codec(index = 6)] - Consensus(ConsensusMessage), - /// Remote method call request. - RemoteCallRequest(RemoteCallRequest<Hash>), - /// Remote method call response. - RemoteCallResponse(RemoteCallResponse), - /// Remote storage read request. - RemoteReadRequest(RemoteReadRequest<Hash>), - /// Remote storage read response. - RemoteReadResponse(RemoteReadResponse), - /// Remote header request. - RemoteHeaderRequest(RemoteHeaderRequest<Number>), - /// Remote header response. - RemoteHeaderResponse(RemoteHeaderResponse<Header>), - /// Remote changes request. - RemoteChangesRequest(RemoteChangesRequest<Hash>), - /// Remote changes response. - RemoteChangesResponse(RemoteChangesResponse<Number, Hash>), - /// Remote child storage read request. - RemoteReadChildRequest(RemoteReadChildRequest<Hash>), - /// Batch of consensus protocol messages. - // NOTE: index is incremented by 2 due to finality proof related - // messages that were removed. - #[codec(index = 17)] - ConsensusBatch(Vec<ConsensusMessage>), - } - /// Status sent on connection. // TODO https://github.com/paritytech/substrate/issues/4674: replace the `Status` // struct with this one, after waiting a few releases beyond `NetworkSpecialization`'s diff --git a/substrate/client/network/src/protocol/notifications.rs b/substrate/client/network/src/protocol/notifications.rs index 10fa329097d1b662350d50aee9ddf77de0d09e21..2691496234adad487cfcf7708654352764e87cc2 100644 --- a/substrate/client/network/src/protocol/notifications.rs +++ b/substrate/client/network/src/protocol/notifications.rs @@ -21,7 +21,7 @@ pub use self::{ behaviour::{Notifications, NotificationsOut, ProtocolConfig}, - handler::{NotificationsSink, NotifsHandlerError, Ready}, + handler::{NotificationsSink, Ready}, service::{notification_service, ProtocolHandlePair}, }; diff --git a/substrate/client/network/src/protocol/notifications/behaviour.rs b/substrate/client/network/src/protocol/notifications/behaviour.rs index a562546145c8ef635151f3227702ea3104633e6b..e6909fcdefeaf874b5014dc24f41174182767b81 100644 --- a/substrate/client/network/src/protocol/notifications/behaviour.rs +++ b/substrate/client/network/src/protocol/notifications/behaviour.rs @@ -33,11 +33,11 @@ use bytes::BytesMut; use fnv::FnvHashMap; use futures::{future::BoxFuture, prelude::*, stream::FuturesUnordered}; use libp2p::{ - core::{Endpoint, Multiaddr}, + core::{transport::PortUse, Endpoint, Multiaddr}, swarm::{ behaviour::{ConnectionClosed, ConnectionEstablished, DialFailure, FromSwarm}, - ConnectionDenied, ConnectionId, DialError, NetworkBehaviour, NotifyHandler, PollParameters, - THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, + ConnectionDenied, ConnectionId, DialError, NetworkBehaviour, NotifyHandler, THandler, + THandlerInEvent, THandlerOutEvent, ToSwarm, }, PeerId, }; @@ -49,6 +49,7 @@ use smallvec::SmallVec; use tokio::sync::oneshot::error::RecvError; use tokio_stream::StreamMap; +use libp2p::swarm::CloseConnection; use std::{ cmp, collections::{hash_map::Entry, VecDeque}, @@ -1233,11 +1234,12 @@ impl NetworkBehaviour for Notifications { peer: PeerId, _addr: &Multiaddr, _role_override: Endpoint, + _port_use: PortUse, ) -> Result<THandler<Self>, ConnectionDenied> { Ok(NotifsHandler::new(peer, self.notif_protocols.clone(), Some(self.metrics.clone()))) } - fn on_swarm_event(&mut self, event: FromSwarm<Self::ConnectionHandler>) { + fn on_swarm_event(&mut self, event: FromSwarm) { match event { FromSwarm::ConnectionEstablished(ConnectionEstablished { peer_id, @@ -1670,6 +1672,9 @@ impl NetworkBehaviour for Notifications { FromSwarm::ExternalAddrConfirmed(_) => {}, FromSwarm::AddressChange(_) => {}, FromSwarm::NewListenAddr(_) => {}, + event => { + warn!(target: "sub-libp2p", "New unknown `FromSwarm` libp2p event: {event:?}"); + }, } } @@ -2217,14 +2222,19 @@ impl NetworkBehaviour for Notifications { ); } }, + NotifsHandlerOut::Close { protocol_index } => { + let set_id = SetId::from(protocol_index); + + trace!(target: "sub-libp2p", "Handler({}, {:?}) => SyncNotificationsClogged({:?})", peer_id, connection_id, set_id); + self.events.push_back(ToSwarm::CloseConnection { + peer_id, + connection: CloseConnection::One(connection_id), + }); + }, } } - fn poll( - &mut self, - cx: &mut Context, - _params: &mut impl PollParameters, - ) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> { + fn poll(&mut self, cx: &mut Context) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> { if let Some(event) = self.events.pop_front() { return Poll::Ready(event) } @@ -2359,7 +2369,6 @@ impl NetworkBehaviour for Notifications { } #[cfg(test)] -#[allow(deprecated)] mod tests { use super::*; use crate::{ @@ -2386,17 +2395,6 @@ mod tests { } } - #[derive(Clone)] - struct MockPollParams {} - - impl PollParameters for MockPollParams { - type SupportedProtocolsIter = std::vec::IntoIter<Vec<u8>>; - - fn supported_protocols(&self) -> Self::SupportedProtocolsIter { - vec![].into_iter() - } - } - fn development_notifs( ) -> (Notifications, ProtocolController, Box<dyn crate::service::traits::NotificationService>) { @@ -2654,7 +2652,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -2854,7 +2852,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3007,7 +3005,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3051,7 +3049,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3121,7 +3119,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3269,7 +3267,7 @@ mod tests { peer_id: peer, connection_id: conn1, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3395,7 +3393,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3469,7 +3467,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3532,7 +3530,7 @@ mod tests { peer_id: peer, connection_id: conn1, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3546,7 +3544,7 @@ mod tests { peer_id: peer, connection_id: conn2, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3600,7 +3598,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3658,7 +3656,7 @@ mod tests { peer_id: peer, connection_id: conn2, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3719,7 +3717,7 @@ mod tests { peer_id: peer, connection_id: conn1, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3788,7 +3786,7 @@ mod tests { peer_id: peer, connection_id: conn1, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3829,7 +3827,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3952,7 +3950,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3972,11 +3970,9 @@ mod tests { assert!(notif.peers.get(&(peer, set_id)).is_some()); if tokio::time::timeout(Duration::from_secs(5), async { - let mut params = MockPollParams {}; - loop { futures::future::poll_fn(|cx| { - let _ = notif.poll(cx, &mut params); + let _ = notif.poll(cx); Poll::Ready(()) }) .await; @@ -4080,11 +4076,9 @@ mod tests { // verify that the code continues to keep the peer disabled by resetting the timer // after the first one expired. if tokio::time::timeout(Duration::from_secs(5), async { - let mut params = MockPollParams {}; - loop { futures::future::poll_fn(|cx| { - let _ = notif.poll(cx, &mut params); + let _ = notif.poll(cx); Poll::Ready(()) }) .await; @@ -4262,7 +4256,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -4503,7 +4497,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(0), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -4605,7 +4599,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(0), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -4687,7 +4681,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(0), endpoint: &endpoint.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -4804,7 +4798,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(1337), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -4839,7 +4833,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(1337), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -4890,7 +4884,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(1337), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -4937,7 +4931,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -4987,7 +4981,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(1337), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -5030,7 +5024,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -5041,7 +5035,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); diff --git a/substrate/client/network/src/protocol/notifications/handler.rs b/substrate/client/network/src/protocol/notifications/handler.rs index bff60ba1125f64e133d9e3b12e3923b9385c600e..332de9f19c410f8ac2c9914dfd7d25e6215b7c01 100644 --- a/substrate/client/network/src/protocol/notifications/handler.rs +++ b/substrate/client/network/src/protocol/notifications/handler.rs @@ -74,12 +74,12 @@ use futures::{ }; use libp2p::{ swarm::{ - handler::ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, KeepAlive, Stream, + handler::ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, Stream, SubstreamProtocol, }, PeerId, }; -use log::error; +use log::{error, warn}; use parking_lot::{Mutex, RwLock}; use std::{ collections::VecDeque, @@ -87,7 +87,7 @@ use std::{ pin::Pin, sync::Arc, task::{Context, Poll}, - time::{Duration, Instant}, + time::Duration, }; /// Number of pending notifications in asynchronous contexts. @@ -113,16 +113,18 @@ pub struct NotifsHandler { /// List of notification protocols, specified by the user at initialization. protocols: Vec<Protocol>, - /// When the connection with the remote has been successfully established. - when_connection_open: Instant, + /// Whether to keep connection alive + keep_alive: bool, + + /// Optional future that keeps connection alive for a certain amount of time. + // TODO: this should be safe to remove, see https://github.com/paritytech/polkadot-sdk/issues/6350 + keep_alive_timeout_future: Option<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>, /// Remote we are connected to. peer_id: PeerId, /// Events to return in priority from `poll`. - events_queue: VecDeque< - ConnectionHandlerEvent<NotificationsOut, usize, NotifsHandlerOut, NotifsHandlerError>, - >, + events_queue: VecDeque<ConnectionHandlerEvent<NotificationsOut, usize, NotifsHandlerOut>>, /// Metrics. metrics: Option<Arc<NotificationMetrics>>, @@ -149,7 +151,12 @@ impl NotifsHandler { }) .collect(), peer_id, - when_connection_open: Instant::now(), + // Keep connection alive initially until below timeout expires + keep_alive: true, + // A grace period of `INITIAL_KEEPALIVE_TIME` must be given to leave time for the remote + // to express desire to open substreams. + // TODO: This is a hack and ideally should not be necessary + keep_alive_timeout_future: Some(Box::pin(tokio::time::sleep(INITIAL_KEEPALIVE_TIME))), events_queue: VecDeque::with_capacity(16), metrics: metrics.map_or(None, |metrics| Some(Arc::new(metrics))), } @@ -327,6 +334,12 @@ pub enum NotifsHandlerOut { /// Message that has been received. message: BytesMut, }, + + /// Close connection + Close { + /// Index of the protocol in the list of protocols passed at initialization. + protocol_index: usize, + }, } /// Sink connected directly to the node background task. Allows sending notifications to the peer. @@ -465,17 +478,9 @@ impl<'a> Ready<'a> { } } -/// Error specific to the collection of protocols. -#[derive(Debug, thiserror::Error)] -pub enum NotifsHandlerError { - #[error("Channel of synchronous notifications is full.")] - SyncNotificationsClogged, -} - impl ConnectionHandler for NotifsHandler { type FromBehaviour = NotifsHandlerIn; type ToBehaviour = NotifsHandlerOut; - type Error = NotifsHandlerError; type InboundProtocol = UpgradeCollec<NotificationsIn>; type OutboundProtocol = NotificationsOut; // Index within the `out_protocols`. @@ -616,6 +621,9 @@ impl ConnectionHandler for NotifsHandler { State::Open { .. } => debug_assert!(false), }, ConnectionEvent::ListenUpgradeError(_listen_upgrade_error) => {}, + event => { + warn!(target: "sub-libp2p", "New unknown `ConnectionEvent` libp2p event: {event:?}"); + }, } } @@ -711,35 +719,36 @@ impl ConnectionHandler for NotifsHandler { } } - fn connection_keep_alive(&self) -> KeepAlive { + fn connection_keep_alive(&self) -> bool { // `Yes` if any protocol has some activity. if self.protocols.iter().any(|p| !matches!(p.state, State::Closed { .. })) { - return KeepAlive::Yes + return true; } - // A grace period of `INITIAL_KEEPALIVE_TIME` must be given to leave time for the remote - // to express desire to open substreams. - #[allow(deprecated)] - KeepAlive::Until(self.when_connection_open + INITIAL_KEEPALIVE_TIME) + self.keep_alive } - #[allow(deprecated)] fn poll( &mut self, cx: &mut Context, ) -> Poll< - ConnectionHandlerEvent< - Self::OutboundProtocol, - Self::OutboundOpenInfo, - Self::ToBehaviour, - Self::Error, - >, + ConnectionHandlerEvent<Self::OutboundProtocol, Self::OutboundOpenInfo, Self::ToBehaviour>, > { + { + let maybe_keep_alive_timeout_future = &mut self.keep_alive_timeout_future; + if let Some(keep_alive_timeout_future) = maybe_keep_alive_timeout_future { + if keep_alive_timeout_future.poll_unpin(cx).is_ready() { + maybe_keep_alive_timeout_future.take(); + self.keep_alive = false; + } + } + } + if let Some(ev) = self.events_queue.pop_front() { return Poll::Ready(ev) } - // For each open substream, try send messages from `notifications_sink_rx` to the + // For each open substream, try to send messages from `notifications_sink_rx` to the // substream. for protocol_index in 0..self.protocols.len() { if let State::Open { @@ -750,11 +759,10 @@ impl ConnectionHandler for NotifsHandler { // Only proceed with `out_substream.poll_ready_unpin` if there is an element // available in `notifications_sink_rx`. This avoids waking up the task when // a substream is ready to send if there isn't actually something to send. - #[allow(deprecated)] match Pin::new(&mut *notifications_sink_rx).as_mut().poll_peek(cx) { Poll::Ready(Some(&NotificationsSinkMessage::ForceClose)) => - return Poll::Ready(ConnectionHandlerEvent::Close( - NotifsHandlerError::SyncNotificationsClogged, + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( + NotifsHandlerOut::Close { protocol_index }, )), Poll::Ready(Some(&NotificationsSinkMessage::Notification { .. })) => {}, Poll::Ready(None) | Poll::Pending => break, @@ -975,6 +983,17 @@ pub mod tests { rx_buffer: BytesMut, } + /// Mirror of `ActiveStreamCounter` in `libp2p` + #[allow(dead_code)] + struct MockActiveStreamCounter(Arc<()>); + + // Mirror of `Stream` in `libp2p` + #[allow(dead_code)] + struct MockStream { + stream: Negotiated<SubstreamBox>, + counter: Option<MockActiveStreamCounter>, + } + impl MockSubstream { /// Create new substream pair. pub fn new() -> (Self, Self) { @@ -1004,16 +1023,11 @@ pub mod tests { /// Unsafe substitute for `Stream::new` private constructor. fn stream_new(stream: Negotiated<SubstreamBox>) -> Stream { + let stream = MockStream { stream, counter: None }; // Static asserts to make sure this doesn't break. const _: () = { - assert!( - core::mem::size_of::<Stream>() == - core::mem::size_of::<Negotiated<SubstreamBox>>() - ); - assert!( - core::mem::align_of::<Stream>() == - core::mem::align_of::<Negotiated<SubstreamBox>>() - ); + assert!(core::mem::size_of::<Stream>() == core::mem::size_of::<MockStream>()); + assert!(core::mem::align_of::<Stream>() == core::mem::align_of::<MockStream>()); }; unsafe { core::mem::transmute(stream) } @@ -1084,24 +1098,16 @@ pub mod tests { /// Create new [`NotifsHandler`]. fn notifs_handler() -> NotifsHandler { - let proto = Protocol { - config: ProtocolConfig { + NotifsHandler::new( + PeerId::random(), + vec![ProtocolConfig { name: "/foo".into(), fallback_names: vec![], handshake: Arc::new(RwLock::new(b"hello, world".to_vec())), max_notification_size: u64::MAX, - }, - in_upgrade: NotificationsIn::new("/foo", Vec::new(), u64::MAX), - state: State::Closed { pending_opening: false }, - }; - - NotifsHandler { - protocols: vec![proto], - when_connection_open: Instant::now(), - peer_id: PeerId::random(), - events_queue: VecDeque::new(), - metrics: None, - } + }], + None, + ) } // verify that if another substream is attempted to be opened by remote while an inbound @@ -1608,12 +1614,11 @@ pub mod tests { notifications_sink.send_sync_notification(vec![1, 3, 3, 9]); notifications_sink.send_sync_notification(vec![1, 3, 4, 0]); - #[allow(deprecated)] futures::future::poll_fn(|cx| { assert!(std::matches!( handler.poll(cx), - Poll::Ready(ConnectionHandlerEvent::Close( - NotifsHandlerError::SyncNotificationsClogged, + Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( + NotifsHandlerOut::Close { .. } )) )); Poll::Ready(()) diff --git a/substrate/client/network/src/protocol/notifications/tests.rs b/substrate/client/network/src/protocol/notifications/tests.rs index a8eeb2bb198002d2311a3e187fe0077632f7e4ce..50f03b5911b67cb526800b64f7395525a9b14436 100644 --- a/substrate/client/network/src/protocol/notifications/tests.rs +++ b/substrate/client/network/src/protocol/notifications/tests.rs @@ -30,30 +30,25 @@ use crate::{ use futures::{future::BoxFuture, prelude::*}; use libp2p::{ - core::{transport::MemoryTransport, upgrade, Endpoint}, + core::{ + transport::{MemoryTransport, PortUse}, + upgrade, Endpoint, + }, identity, noise, swarm::{ - self, behaviour::FromSwarm, ConnectionDenied, ConnectionId, Executor, NetworkBehaviour, - PollParameters, Swarm, SwarmEvent, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, + behaviour::FromSwarm, ConnectionDenied, ConnectionId, NetworkBehaviour, Swarm, SwarmEvent, + THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }, - yamux, Multiaddr, PeerId, Transport, + yamux, Multiaddr, PeerId, SwarmBuilder, Transport, }; use sc_utils::mpsc::tracing_unbounded; use std::{ iter, - pin::Pin, sync::Arc, task::{Context, Poll}, time::Duration, }; -struct TokioExecutor(tokio::runtime::Runtime); -impl Executor for TokioExecutor { - fn exec(&self, f: Pin<Box<dyn Future<Output = ()> + Send>>) { - let _ = self.0.spawn(f); - } -} - /// Builds two nodes that have each other as bootstrap nodes. /// This is to be used only for testing, and a panic will happen if something goes wrong. fn build_nodes() -> (Swarm<CustomProtoWithAddr>, Swarm<CustomProtoWithAddr>) { @@ -67,13 +62,6 @@ fn build_nodes() -> (Swarm<CustomProtoWithAddr>, Swarm<CustomProtoWithAddr>) { for index in 0..2 { let keypair = keypairs[index].clone(); - let transport = MemoryTransport::new() - .upgrade(upgrade::Version::V1) - .authenticate(noise::Config::new(&keypair).unwrap()) - .multiplex(yamux::Config::default()) - .timeout(Duration::from_secs(20)) - .boxed(); - let (protocol_handle_pair, mut notif_service) = crate::protocol::notifications::service::notification_service("/foo".into()); // The first swarm has the second peer ID present in the peerstore. @@ -102,39 +90,8 @@ fn build_nodes() -> (Swarm<CustomProtoWithAddr>, Swarm<CustomProtoWithAddr>) { ); let (notif_handle, command_stream) = protocol_handle_pair.split(); - let behaviour = CustomProtoWithAddr { - inner: Notifications::new( - vec![controller_handle], - from_controller, - NotificationMetrics::new(None), - iter::once(( - ProtocolConfig { - name: "/foo".into(), - fallback_names: Vec::new(), - handshake: Vec::new(), - max_notification_size: 1024 * 1024, - }, - notif_handle, - command_stream, - )), - ), - peer_store_future: peer_store.run().boxed(), - protocol_controller_future: controller.run().boxed(), - addrs: addrs - .iter() - .enumerate() - .filter_map(|(n, a)| { - if n != index { - Some((keypairs[n].public().to_peer_id(), a.clone())) - } else { - None - } - }) - .collect(), - }; - let runtime = tokio::runtime::Runtime::new().unwrap(); - runtime.spawn(async move { + tokio::spawn(async move { loop { if let NotificationEvent::ValidateInboundSubstream { result_tx, .. } = notif_service.next_event().await.unwrap() @@ -144,12 +101,49 @@ fn build_nodes() -> (Swarm<CustomProtoWithAddr>, Swarm<CustomProtoWithAddr>) { } }); - let mut swarm = Swarm::new( - transport, - behaviour, - keypairs[index].public().to_peer_id(), - swarm::Config::with_executor(TokioExecutor(runtime)), - ); + let mut swarm = SwarmBuilder::with_existing_identity(keypair) + .with_tokio() + .with_other_transport(|keypair| { + MemoryTransport::new() + .upgrade(upgrade::Version::V1) + .authenticate(noise::Config::new(&keypair).unwrap()) + .multiplex(yamux::Config::default()) + .timeout(Duration::from_secs(20)) + .boxed() + }) + .unwrap() + .with_behaviour(|_keypair| CustomProtoWithAddr { + inner: Notifications::new( + vec![controller_handle], + from_controller, + NotificationMetrics::new(None), + iter::once(( + ProtocolConfig { + name: "/foo".into(), + fallback_names: Vec::new(), + handshake: Vec::new(), + max_notification_size: 1024 * 1024, + }, + notif_handle, + command_stream, + )), + ), + peer_store_future: peer_store.run().boxed(), + protocol_controller_future: controller.run().boxed(), + addrs: addrs + .iter() + .enumerate() + .filter_map(|(n, a)| { + if n != index { + Some((keypairs[n].public().to_peer_id(), a.clone())) + } else { + None + } + }) + .collect(), + }) + .unwrap() + .build(); swarm.listen_on(addrs[index].clone()).unwrap(); out.push(swarm); } @@ -241,12 +235,18 @@ impl NetworkBehaviour for CustomProtoWithAddr { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result<THandler<Self>, ConnectionDenied> { - self.inner - .handle_established_outbound_connection(connection_id, peer, addr, role_override) + self.inner.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + port_use, + ) } - fn on_swarm_event(&mut self, event: FromSwarm<Self::ConnectionHandler>) { + fn on_swarm_event(&mut self, event: FromSwarm) { self.inner.on_swarm_event(event); } @@ -259,19 +259,15 @@ impl NetworkBehaviour for CustomProtoWithAddr { self.inner.on_connection_handler_event(peer_id, connection_id, event); } - fn poll( - &mut self, - cx: &mut Context, - params: &mut impl PollParameters, - ) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> { + fn poll(&mut self, cx: &mut Context) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> { let _ = self.peer_store_future.poll_unpin(cx); let _ = self.protocol_controller_future.poll_unpin(cx); - self.inner.poll(cx, params) + self.inner.poll(cx) } } -#[test] -fn reconnect_after_disconnect() { +#[tokio::test] +async fn reconnect_after_disconnect() { // We connect two nodes together, then force a disconnect (through the API of the `Service`), // check that the disconnect worked, and finally check whether they successfully reconnect. @@ -288,108 +284,106 @@ fn reconnect_after_disconnect() { let mut service1_state = ServiceState::NotConnected; let mut service2_state = ServiceState::NotConnected; - futures::executor::block_on(async move { - loop { - // Grab next event from services. - let event = { - let s1 = service1.select_next_some(); - let s2 = service2.select_next_some(); - futures::pin_mut!(s1, s2); - match future::select(s1, s2).await { - future::Either::Left((ev, _)) => future::Either::Left(ev), - future::Either::Right((ev, _)) => future::Either::Right(ev), - } - }; - - match event { - future::Either::Left(SwarmEvent::Behaviour( - NotificationsOut::CustomProtocolOpen { .. }, - )) => match service1_state { - ServiceState::NotConnected => { - service1_state = ServiceState::FirstConnec; - if service2_state == ServiceState::FirstConnec { - service1 - .behaviour_mut() - .disconnect_peer(Swarm::local_peer_id(&service2), SetId::from(0)); - } - }, - ServiceState::Disconnected => service1_state = ServiceState::ConnectedAgain, - ServiceState::FirstConnec | ServiceState::ConnectedAgain => panic!(), - }, - future::Either::Left(SwarmEvent::Behaviour( - NotificationsOut::CustomProtocolClosed { .. }, - )) => match service1_state { - ServiceState::FirstConnec => service1_state = ServiceState::Disconnected, - ServiceState::ConnectedAgain | - ServiceState::NotConnected | - ServiceState::Disconnected => panic!(), - }, - future::Either::Right(SwarmEvent::Behaviour( - NotificationsOut::CustomProtocolOpen { .. }, - )) => match service2_state { - ServiceState::NotConnected => { - service2_state = ServiceState::FirstConnec; - if service1_state == ServiceState::FirstConnec { - service1 - .behaviour_mut() - .disconnect_peer(Swarm::local_peer_id(&service2), SetId::from(0)); - } - }, - ServiceState::Disconnected => service2_state = ServiceState::ConnectedAgain, - ServiceState::FirstConnec | ServiceState::ConnectedAgain => panic!(), - }, - future::Either::Right(SwarmEvent::Behaviour( - NotificationsOut::CustomProtocolClosed { .. }, - )) => match service2_state { - ServiceState::FirstConnec => service2_state = ServiceState::Disconnected, - ServiceState::ConnectedAgain | - ServiceState::NotConnected | - ServiceState::Disconnected => panic!(), - }, - _ => {}, + loop { + // Grab next event from services. + let event = { + let s1 = service1.select_next_some(); + let s2 = service2.select_next_some(); + futures::pin_mut!(s1, s2); + match future::select(s1, s2).await { + future::Either::Left((ev, _)) => future::Either::Left(ev), + future::Either::Right((ev, _)) => future::Either::Right(ev), } + }; - // Due to the bug in `Notifications`, the disconnected node does not always detect that - // it was disconnected. The closed inbound substream is tolerated by design, and the - // closed outbound substream is not detected until something is sent into it. - // See [PR #13396](https://github.com/paritytech/substrate/pull/13396). - // This happens if the disconnecting node reconnects to it fast enough. - // In this case the disconnected node does not transit via `ServiceState::NotConnected` - // and stays in `ServiceState::FirstConnec`. - // TODO: update this once the fix is finally merged. - if service1_state == ServiceState::ConnectedAgain && - service2_state == ServiceState::ConnectedAgain || - service1_state == ServiceState::ConnectedAgain && - service2_state == ServiceState::FirstConnec || - service1_state == ServiceState::FirstConnec && - service2_state == ServiceState::ConnectedAgain - { - break - } + match event { + future::Either::Left(SwarmEvent::Behaviour(NotificationsOut::CustomProtocolOpen { + .. + })) => match service1_state { + ServiceState::NotConnected => { + service1_state = ServiceState::FirstConnec; + if service2_state == ServiceState::FirstConnec { + service1 + .behaviour_mut() + .disconnect_peer(Swarm::local_peer_id(&service2), SetId::from(0)); + } + }, + ServiceState::Disconnected => service1_state = ServiceState::ConnectedAgain, + ServiceState::FirstConnec | ServiceState::ConnectedAgain => panic!(), + }, + future::Either::Left(SwarmEvent::Behaviour( + NotificationsOut::CustomProtocolClosed { .. }, + )) => match service1_state { + ServiceState::FirstConnec => service1_state = ServiceState::Disconnected, + ServiceState::ConnectedAgain | + ServiceState::NotConnected | + ServiceState::Disconnected => panic!(), + }, + future::Either::Right(SwarmEvent::Behaviour( + NotificationsOut::CustomProtocolOpen { .. }, + )) => match service2_state { + ServiceState::NotConnected => { + service2_state = ServiceState::FirstConnec; + if service1_state == ServiceState::FirstConnec { + service1 + .behaviour_mut() + .disconnect_peer(Swarm::local_peer_id(&service2), SetId::from(0)); + } + }, + ServiceState::Disconnected => service2_state = ServiceState::ConnectedAgain, + ServiceState::FirstConnec | ServiceState::ConnectedAgain => panic!(), + }, + future::Either::Right(SwarmEvent::Behaviour( + NotificationsOut::CustomProtocolClosed { .. }, + )) => match service2_state { + ServiceState::FirstConnec => service2_state = ServiceState::Disconnected, + ServiceState::ConnectedAgain | + ServiceState::NotConnected | + ServiceState::Disconnected => panic!(), + }, + _ => {}, } - // Now that the two services have disconnected and reconnected, wait for 3 seconds and - // check whether they're still connected. - let mut delay = futures_timer::Delay::new(Duration::from_secs(3)); - - loop { - // Grab next event from services. - let event = { - let s1 = service1.select_next_some(); - let s2 = service2.select_next_some(); - futures::pin_mut!(s1, s2); - match future::select(future::select(s1, s2), &mut delay).await { - future::Either::Right(_) => break, // success - future::Either::Left((future::Either::Left((ev, _)), _)) => ev, - future::Either::Left((future::Either::Right((ev, _)), _)) => ev, - } - }; + // Due to the bug in `Notifications`, the disconnected node does not always detect that + // it was disconnected. The closed inbound substream is tolerated by design, and the + // closed outbound substream is not detected until something is sent into it. + // See [PR #13396](https://github.com/paritytech/substrate/pull/13396). + // This happens if the disconnecting node reconnects to it fast enough. + // In this case the disconnected node does not transit via `ServiceState::NotConnected` + // and stays in `ServiceState::FirstConnec`. + // TODO: update this once the fix is finally merged. + if service1_state == ServiceState::ConnectedAgain && + service2_state == ServiceState::ConnectedAgain || + service1_state == ServiceState::ConnectedAgain && + service2_state == ServiceState::FirstConnec || + service1_state == ServiceState::FirstConnec && + service2_state == ServiceState::ConnectedAgain + { + break + } + } - match event { - SwarmEvent::Behaviour(NotificationsOut::CustomProtocolOpen { .. }) | - SwarmEvent::Behaviour(NotificationsOut::CustomProtocolClosed { .. }) => panic!(), - _ => {}, + // Now that the two services have disconnected and reconnected, wait for 3 seconds and + // check whether they're still connected. + let mut delay = futures_timer::Delay::new(Duration::from_secs(3)); + + loop { + // Grab next event from services. + let event = { + let s1 = service1.select_next_some(); + let s2 = service2.select_next_some(); + futures::pin_mut!(s1, s2); + match future::select(future::select(s1, s2), &mut delay).await { + future::Either::Right(_) => break, // success + future::Either::Left((future::Either::Left((ev, _)), _)) => ev, + future::Either::Left((future::Either::Right((ev, _)), _)) => ev, } + }; + + match event { + SwarmEvent::Behaviour(NotificationsOut::CustomProtocolOpen { .. }) | + SwarmEvent::Behaviour(NotificationsOut::CustomProtocolClosed { .. }) => panic!(), + _ => {}, } - }); + } } diff --git a/substrate/client/network/src/protocol/notifications/upgrade/notifications.rs b/substrate/client/network/src/protocol/notifications/upgrade/notifications.rs index e01bcbe0bad795dc452b73b802e89f8c2043ca14..9e8a03fc07c9c3490394428c355364f7a0065345 100644 --- a/substrate/client/network/src/protocol/notifications/upgrade/notifications.rs +++ b/substrate/client/network/src/protocol/notifications/upgrade/notifications.rs @@ -39,12 +39,12 @@ use crate::types::ProtocolName; use asynchronous_codec::Framed; use bytes::BytesMut; use futures::prelude::*; -use libp2p::core::{upgrade, InboundUpgrade, OutboundUpgrade, UpgradeInfo}; +use libp2p::core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use log::{error, warn}; use unsigned_varint::codec::UviBytes; use std::{ - io, mem, + fmt, io, mem, pin::Pin, task::{Context, Poll}, vec, @@ -187,6 +187,14 @@ pub struct NotificationsInOpen<TSubstream> { pub substream: NotificationsInSubstream<TSubstream>, } +impl<TSubstream> fmt::Debug for NotificationsInOpen<TSubstream> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("NotificationsInOpen") + .field("handshake", &self.handshake) + .finish_non_exhaustive() + } +} + impl<TSubstream> NotificationsInSubstream<TSubstream> where TSubstream: AsyncRead + AsyncWrite + Unpin, @@ -370,7 +378,14 @@ where fn upgrade_outbound(self, mut socket: TSubstream, negotiated_name: Self::Info) -> Self::Future { Box::pin(async move { - upgrade::write_length_prefixed(&mut socket, &self.initial_message).await?; + { + let mut len_data = unsigned_varint::encode::usize_buffer(); + let encoded_len = + unsigned_varint::encode::usize(self.initial_message.len(), &mut len_data).len(); + socket.write_all(&len_data[..encoded_len]).await?; + } + socket.write_all(&self.initial_message).await?; + socket.flush().await?; // Reading handshake. let handshake_len = unsigned_varint::aio::read_usize(&mut socket).await?; @@ -413,6 +428,15 @@ pub struct NotificationsOutOpen<TSubstream> { pub substream: NotificationsOutSubstream<TSubstream>, } +impl<TSubstream> fmt::Debug for NotificationsOutOpen<TSubstream> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("NotificationsOutOpen") + .field("handshake", &self.handshake) + .field("negotiated_fallback", &self.negotiated_fallback) + .finish_non_exhaustive() + } +} + impl<TSubstream> Sink<Vec<u8>> for NotificationsOutSubstream<TSubstream> where TSubstream: AsyncRead + AsyncWrite + Unpin, diff --git a/substrate/client/network/src/protocol_controller.rs b/substrate/client/network/src/protocol_controller.rs index af7adb50907f990119d230c578c1852b93a58277..11f5321294d008e3b27046c932d4c547c36af215 100644 --- a/substrate/client/network/src/protocol_controller.rs +++ b/substrate/client/network/src/protocol_controller.rs @@ -464,7 +464,7 @@ impl ProtocolController { /// maintain connections with such peers. fn on_add_reserved_peer(&mut self, peer_id: PeerId) { if self.reserved_nodes.contains_key(&peer_id) { - warn!( + debug!( target: LOG_TARGET, "Trying to add an already reserved node {peer_id} as reserved on {:?}.", self.set_id, diff --git a/substrate/client/network/src/request_responses.rs b/substrate/client/network/src/request_responses.rs index 6c2631924df4a96f0067fe53e8e1c68520329204..e21773632ed7756185873429187fb5a595326040 100644 --- a/substrate/client/network/src/request_responses.rs +++ b/substrate/client/network/src/request_responses.rs @@ -43,13 +43,11 @@ use crate::{ use futures::{channel::oneshot, prelude::*}; use libp2p::{ - core::{Endpoint, Multiaddr}, + core::{transport::PortUse, Endpoint, Multiaddr}, request_response::{self, Behaviour, Codec, Message, ProtocolSupport, ResponseChannel}, swarm::{ - behaviour::{ConnectionClosed, FromSwarm}, - handler::multi::MultiHandler, - ConnectionDenied, ConnectionId, NetworkBehaviour, PollParameters, THandler, - THandlerInEvent, THandlerOutEvent, ToSwarm, + behaviour::FromSwarm, handler::multi::MultiHandler, ConnectionDenied, ConnectionId, + NetworkBehaviour, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }, PeerId, }; @@ -64,11 +62,14 @@ use std::{ time::{Duration, Instant}, }; -pub use libp2p::request_response::{Config, RequestId}; +pub use libp2p::request_response::{Config, InboundRequestId, OutboundRequestId}; + +/// Periodically check if requests are taking too long. +const PERIODIC_REQUEST_CHECK: Duration = Duration::from_secs(2); /// Possible failures occurring in the context of sending an outbound request and receiving the /// response. -#[derive(Debug, thiserror::Error)] +#[derive(Debug, Clone, thiserror::Error)] pub enum OutboundFailure { /// The request could not be sent because a dialing attempt failed. #[error("Failed to dial the requested peer")] @@ -82,6 +83,9 @@ pub enum OutboundFailure { /// The remote supports none of the requested protocols. #[error("The remote supports none of the requested protocols")] UnsupportedProtocols, + /// An IO failure happened on an outbound stream. + #[error("An IO failure happened on an outbound stream")] + Io(Arc<io::Error>), } impl From<request_response::OutboundFailure> for OutboundFailure { @@ -93,6 +97,7 @@ impl From<request_response::OutboundFailure> for OutboundFailure { OutboundFailure::ConnectionClosed, request_response::OutboundFailure::UnsupportedProtocols => OutboundFailure::UnsupportedProtocols, + request_response::OutboundFailure::Io(error) => OutboundFailure::Io(Arc::new(error)), } } } @@ -114,6 +119,9 @@ pub enum InboundFailure { /// The local peer failed to respond to an inbound request #[error("The response channel was dropped without sending a response to the remote")] ResponseOmission, + /// An IO failure happened on an inbound stream. + #[error("An IO failure happened on an inbound stream")] + Io(Arc<io::Error>), } impl From<request_response::InboundFailure> for InboundFailure { @@ -124,6 +132,7 @@ impl From<request_response::InboundFailure> for InboundFailure { request_response::InboundFailure::ConnectionClosed => InboundFailure::ConnectionClosed, request_response::InboundFailure::UnsupportedProtocols => InboundFailure::UnsupportedProtocols, + request_response::InboundFailure::Io(error) => InboundFailure::Io(Arc::new(error)), } } } @@ -245,8 +254,14 @@ pub struct OutgoingResponse { /// Information stored about a pending request. struct PendingRequest { + /// The time when the request was sent to the libp2p request-response protocol. started_at: Instant, - response_tx: oneshot::Sender<Result<(Vec<u8>, ProtocolName), RequestFailure>>, + /// The channel to send the response back to the caller. + /// + /// This is wrapped in an `Option` to allow for the channel to be taken out + /// on force-detected timeouts. + response_tx: Option<oneshot::Sender<Result<(Vec<u8>, ProtocolName), RequestFailure>>>, + /// Fallback request to send if the primary request fails. fallback_request: Option<(Vec<u8>, ProtocolName)>, } @@ -319,30 +334,34 @@ pub enum Event { /// requests. There is no uniqueness guarantee in a set of both inbound and outbound /// [`ProtocolRequestId`]s. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct ProtocolRequestId { +struct ProtocolRequestId<RequestId> { protocol: ProtocolName, request_id: RequestId, } -impl From<(ProtocolName, RequestId)> for ProtocolRequestId { +impl<RequestId> From<(ProtocolName, RequestId)> for ProtocolRequestId<RequestId> { fn from((protocol, request_id): (ProtocolName, RequestId)) -> Self { Self { protocol, request_id } } } +/// Details of a request-response protocol. +struct ProtocolDetails { + behaviour: Behaviour<GenericCodec>, + inbound_queue: Option<async_channel::Sender<IncomingRequest>>, + request_timeout: Duration, +} + /// Implementation of `NetworkBehaviour` that provides support for request-response protocols. pub struct RequestResponsesBehaviour { /// The multiple sub-protocols, by name. /// /// Contains the underlying libp2p request-response [`Behaviour`], plus an optional /// "response builder" used to build responses for incoming requests. - protocols: HashMap< - ProtocolName, - (Behaviour<GenericCodec>, Option<async_channel::Sender<IncomingRequest>>), - >, + protocols: HashMap<ProtocolName, ProtocolDetails>, /// Pending requests, passed down to a request-response [`Behaviour`], awaiting a reply. - pending_requests: HashMap<ProtocolRequestId, PendingRequest>, + pending_requests: HashMap<ProtocolRequestId<OutboundRequestId>, PendingRequest>, /// Whenever an incoming request arrives, a `Future` is added to this list and will yield the /// start time and the response to send back to the remote. @@ -351,20 +370,28 @@ pub struct RequestResponsesBehaviour { >, /// Whenever an incoming request arrives, the arrival [`Instant`] is recorded here. - pending_responses_arrival_time: HashMap<ProtocolRequestId, Instant>, + pending_responses_arrival_time: HashMap<ProtocolRequestId<InboundRequestId>, Instant>, /// Whenever a response is received on `pending_responses`, insert a channel to be notified /// when the request has been sent out. - send_feedback: HashMap<ProtocolRequestId, oneshot::Sender<()>>, + send_feedback: HashMap<ProtocolRequestId<InboundRequestId>, oneshot::Sender<()>>, /// Primarily used to get a reputation of a node. peer_store: Arc<dyn PeerStoreProvider>, + + /// Interval to check that the requests are not taking too long. + /// + /// We had issues in the past where libp2p did not produce a timeout event in due time. + /// + /// For more details, see: + /// - <https://github.com/paritytech/polkadot-sdk/issues/7076#issuecomment-2596085096> + periodic_request_check: tokio::time::Interval, } /// Generated by the response builder and waiting to be processed. struct RequestProcessingOutcome { peer: PeerId, - request_id: RequestId, + request_id: InboundRequestId, protocol: ProtocolName, inner_channel: ResponseChannel<Result<Vec<u8>, ()>>, response: OutgoingResponse, @@ -379,8 +406,7 @@ impl RequestResponsesBehaviour { ) -> Result<Self, RegisterError> { let mut protocols = HashMap::new(); for protocol in list { - let mut cfg = Config::default(); - cfg.set_request_timeout(protocol.request_timeout); + let cfg = Config::default().with_request_timeout(protocol.request_timeout); let protocol_support = if protocol.inbound_queue.is_some() { ProtocolSupport::Full @@ -388,7 +414,7 @@ impl RequestResponsesBehaviour { ProtocolSupport::Outbound }; - let rq_rp = Behaviour::with_codec( + let behaviour = Behaviour::with_codec( GenericCodec { max_request_size: protocol.max_request_size, max_response_size: protocol.max_response_size, @@ -400,7 +426,11 @@ impl RequestResponsesBehaviour { ); match protocols.entry(protocol.name) { - Entry::Vacant(e) => e.insert((rq_rp, protocol.inbound_queue)), + Entry::Vacant(e) => e.insert(ProtocolDetails { + behaviour, + inbound_queue: protocol.inbound_queue, + request_timeout: protocol.request_timeout, + }), Entry::Occupied(e) => return Err(RegisterError::DuplicateProtocol(e.key().clone())), }; } @@ -412,6 +442,7 @@ impl RequestResponsesBehaviour { pending_responses_arrival_time: Default::default(), send_feedback: Default::default(), peer_store, + periodic_request_check: tokio::time::interval(PERIODIC_REQUEST_CHECK), }) } @@ -432,9 +463,11 @@ impl RequestResponsesBehaviour { ) { log::trace!(target: "sub-libp2p", "send request to {target} ({protocol_name:?}), {} bytes", request.len()); - if let Some((protocol, _)) = self.protocols.get_mut(protocol_name.deref()) { + if let Some(ProtocolDetails { behaviour, .. }) = + self.protocols.get_mut(protocol_name.deref()) + { Self::send_request_inner( - protocol, + behaviour, &mut self.pending_requests, target, protocol_name, @@ -455,7 +488,7 @@ impl RequestResponsesBehaviour { fn send_request_inner( behaviour: &mut Behaviour<GenericCodec>, - pending_requests: &mut HashMap<ProtocolRequestId, PendingRequest>, + pending_requests: &mut HashMap<ProtocolRequestId<OutboundRequestId>, PendingRequest>, target: &PeerId, protocol_name: ProtocolName, request: Vec<u8>, @@ -469,7 +502,7 @@ impl RequestResponsesBehaviour { (protocol_name.to_string().into(), request_id).into(), PendingRequest { started_at: Instant::now(), - response_tx: pending_response, + response_tx: Some(pending_response), fallback_request, }, ); @@ -516,18 +549,19 @@ impl NetworkBehaviour for RequestResponsesBehaviour { local_addr: &Multiaddr, remote_addr: &Multiaddr, ) -> Result<THandler<Self>, ConnectionDenied> { - let iter = self.protocols.iter_mut().filter_map(|(p, (r, _))| { - if let Ok(handler) = r.handle_established_inbound_connection( - connection_id, - peer, - local_addr, - remote_addr, - ) { - Some((p.to_string(), handler)) - } else { - None - } - }); + let iter = + self.protocols.iter_mut().filter_map(|(p, ProtocolDetails { behaviour, .. })| { + if let Ok(handler) = behaviour.handle_established_inbound_connection( + connection_id, + peer, + local_addr, + remote_addr, + ) { + Some((p.to_string(), handler)) + } else { + None + } + }); Ok(MultiHandler::try_from_iter(iter).expect( "Protocols are in a HashMap and there can be at most one handler per protocol name, \ @@ -541,16 +575,22 @@ impl NetworkBehaviour for RequestResponsesBehaviour { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result<THandler<Self>, ConnectionDenied> { - let iter = self.protocols.iter_mut().filter_map(|(p, (r, _))| { - if let Ok(handler) = - r.handle_established_outbound_connection(connection_id, peer, addr, role_override) - { - Some((p.to_string(), handler)) - } else { - None - } - }); + let iter = + self.protocols.iter_mut().filter_map(|(p, ProtocolDetails { behaviour, .. })| { + if let Ok(handler) = behaviour.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + port_use, + ) { + Some((p.to_string(), handler)) + } else { + None + } + }); Ok(MultiHandler::try_from_iter(iter).expect( "Protocols are in a HashMap and there can be at most one handler per protocol name, \ @@ -558,80 +598,9 @@ impl NetworkBehaviour for RequestResponsesBehaviour { )) } - fn on_swarm_event(&mut self, event: FromSwarm<Self::ConnectionHandler>) { - match event { - FromSwarm::ConnectionEstablished(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::ConnectionEstablished(e)); - }, - FromSwarm::ConnectionClosed(ConnectionClosed { - peer_id, - connection_id, - endpoint, - handler, - remaining_established, - }) => - for (p_name, p_handler) in handler.into_iter() { - if let Some((proto, _)) = self.protocols.get_mut(p_name.as_str()) { - proto.on_swarm_event(FromSwarm::ConnectionClosed(ConnectionClosed { - peer_id, - connection_id, - endpoint, - handler: p_handler, - remaining_established, - })); - } else { - log::error!( - target: "sub-libp2p", - "on_swarm_event/connection_closed: no request-response instance registered for protocol {:?}", - p_name, - ) - } - }, - FromSwarm::DialFailure(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::DialFailure(e)); - }, - FromSwarm::ListenerClosed(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::ListenerClosed(e)); - }, - FromSwarm::ListenFailure(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::ListenFailure(e)); - }, - FromSwarm::ListenerError(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::ListenerError(e)); - }, - FromSwarm::ExternalAddrExpired(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::ExternalAddrExpired(e)); - }, - FromSwarm::NewListener(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::NewListener(e)); - }, - FromSwarm::ExpiredListenAddr(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::ExpiredListenAddr(e)); - }, - FromSwarm::NewExternalAddrCandidate(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::NewExternalAddrCandidate(e)); - }, - FromSwarm::ExternalAddrConfirmed(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::ExternalAddrConfirmed(e)); - }, - FromSwarm::AddressChange(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::AddressChange(e)); - }, - FromSwarm::NewListenAddr(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::NewListenAddr(e)); - }, + fn on_swarm_event(&mut self, event: FromSwarm) { + for ProtocolDetails { behaviour, .. } in self.protocols.values_mut() { + behaviour.on_swarm_event(event); } } @@ -642,8 +611,8 @@ impl NetworkBehaviour for RequestResponsesBehaviour { event: THandlerOutEvent<Self>, ) { let p_name = event.0; - if let Some((proto, _)) = self.protocols.get_mut(p_name.as_str()) { - return proto.on_connection_handler_event(peer_id, connection_id, event.1) + if let Some(ProtocolDetails { behaviour, .. }) = self.protocols.get_mut(p_name.as_str()) { + return behaviour.on_connection_handler_event(peer_id, connection_id, event.1) } else { log::warn!( target: "sub-libp2p", @@ -653,12 +622,53 @@ impl NetworkBehaviour for RequestResponsesBehaviour { } } - fn poll( - &mut self, - cx: &mut Context, - params: &mut impl PollParameters, - ) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> { + fn poll(&mut self, cx: &mut Context) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> { 'poll_all: loop { + // Poll the periodic request check. + if self.periodic_request_check.poll_tick(cx).is_ready() { + self.pending_requests.retain(|id, req| { + let Some(ProtocolDetails { request_timeout, .. }) = + self.protocols.get(&id.protocol) + else { + log::warn!( + target: "sub-libp2p", + "Request {id:?} has no protocol registered.", + ); + + if let Some(response_tx) = req.response_tx.take() { + if response_tx.send(Err(RequestFailure::UnknownProtocol)).is_err() { + log::debug!( + target: "sub-libp2p", + "Request {id:?} has no protocol registered. At the same time local node is no longer interested in the result.", + ); + } + } + return false + }; + + let elapsed = req.started_at.elapsed(); + if elapsed > *request_timeout { + log::debug!( + target: "sub-libp2p", + "Request {id:?} force detected as timeout.", + ); + + if let Some(response_tx) = req.response_tx.take() { + if response_tx.send(Err(RequestFailure::Network(OutboundFailure::Timeout))).is_err() { + log::debug!( + target: "sub-libp2p", + "Request {id:?} force detected as timeout. At the same time local node is no longer interested in the result.", + ); + } + } + + false + } else { + true + } + }); + } + // Poll to see if any response is ready to be sent back. while let Poll::Ready(Some(outcome)) = self.pending_responses.poll_next_unpin(cx) { let RequestProcessingOutcome { @@ -675,10 +685,12 @@ impl NetworkBehaviour for RequestResponsesBehaviour { }; if let Ok(payload) = result { - if let Some((protocol, _)) = self.protocols.get_mut(&*protocol_name) { + if let Some(ProtocolDetails { behaviour, .. }) = + self.protocols.get_mut(&*protocol_name) + { log::trace!(target: "sub-libp2p", "send response to {peer} ({protocol_name:?}), {} bytes", payload.len()); - if protocol.send_response(inner_channel, Ok(payload)).is_err() { + if behaviour.send_response(inner_channel, Ok(payload)).is_err() { // Note: Failure is handled further below when receiving // `InboundFailure` event from request-response [`Behaviour`]. log::debug!( @@ -706,8 +718,9 @@ impl NetworkBehaviour for RequestResponsesBehaviour { let mut fallback_requests = vec![]; // Poll request-responses protocols. - for (protocol, (ref mut behaviour, ref mut resp_builder)) in &mut self.protocols { - 'poll_protocol: while let Poll::Ready(ev) = behaviour.poll(cx, params) { + for (protocol, ProtocolDetails { behaviour, inbound_queue, .. }) in &mut self.protocols + { + 'poll_protocol: while let Poll::Ready(ev) = behaviour.poll(cx) { let ev = match ev { // Main events we are interested in. ToSwarm::GenerateEvent(ev) => ev, @@ -717,29 +730,23 @@ impl NetworkBehaviour for RequestResponsesBehaviour { ToSwarm::Dial { opts } => { if opts.get_peer_id().is_none() { log::error!( + target: "sub-libp2p", "The request-response isn't supposed to start dialing addresses" ); } return Poll::Ready(ToSwarm::Dial { opts }) }, - ToSwarm::NotifyHandler { peer_id, handler, event } => - return Poll::Ready(ToSwarm::NotifyHandler { - peer_id, - handler, - event: ((*protocol).to_string(), event), - }), - ToSwarm::CloseConnection { peer_id, connection } => - return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), - ToSwarm::NewExternalAddrCandidate(observed) => - return Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)), - ToSwarm::ExternalAddrConfirmed(addr) => - return Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)), - ToSwarm::ExternalAddrExpired(addr) => - return Poll::Ready(ToSwarm::ExternalAddrExpired(addr)), - ToSwarm::ListenOn { opts } => - return Poll::Ready(ToSwarm::ListenOn { opts }), - ToSwarm::RemoveListener { id } => - return Poll::Ready(ToSwarm::RemoveListener { id }), + event => { + return Poll::Ready( + event.map_in(|event| ((*protocol).to_string(), event)).map_out( + |_| { + unreachable!( + "`GenerateEvent` is handled in a branch above; qed" + ) + }, + ), + ); + }, }; match ev { @@ -767,7 +774,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { // Submit the request to the "response builder" passed by the user at // initialization. - if let Some(resp_builder) = resp_builder { + if let Some(resp_builder) = inbound_queue { // If the response builder is too busy, silently drop `tx`. This // will be reported by the corresponding request-response // [`Behaviour`] through an `InboundFailure::Omission` event. @@ -815,7 +822,11 @@ impl NetworkBehaviour for RequestResponsesBehaviour { .pending_requests .remove(&(protocol.clone(), request_id).into()) { - Some(PendingRequest { started_at, response_tx, .. }) => { + Some(PendingRequest { + started_at, + response_tx: Some(response_tx), + .. + }) => { log::trace!( target: "sub-libp2p", "received response from {peer} ({protocol:?}), {} bytes", @@ -831,13 +842,13 @@ impl NetworkBehaviour for RequestResponsesBehaviour { .map_err(|_| RequestFailure::Obsolete); (started_at, delivered) }, - None => { - log::warn!( + _ => { + log::debug!( target: "sub-libp2p", - "Received `RequestResponseEvent::Message` with unexpected request id {:?}", + "Received `RequestResponseEvent::Message` with unexpected request id {:?} from {:?}", request_id, + peer, ); - debug_assert!(false); continue }, }; @@ -859,20 +870,19 @@ impl NetworkBehaviour for RequestResponsesBehaviour { error, .. } => { + let error = OutboundFailure::from(error); let started = match self .pending_requests .remove(&(protocol.clone(), request_id).into()) { Some(PendingRequest { started_at, - response_tx, + response_tx: Some(response_tx), fallback_request, }) => { // Try using the fallback request if the protocol was not // supported. - if let request_response::OutboundFailure::UnsupportedProtocols = - error - { + if matches!(error, OutboundFailure::UnsupportedProtocols) { if let Some((fallback_request, fallback_protocol)) = fallback_request { @@ -893,7 +903,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { } if response_tx - .send(Err(RequestFailure::Network(error.clone().into()))) + .send(Err(RequestFailure::Network(error.clone()))) .is_err() { log::debug!( @@ -905,13 +915,14 @@ impl NetworkBehaviour for RequestResponsesBehaviour { } started_at }, - None => { - log::warn!( + _ => { + log::debug!( target: "sub-libp2p", - "Received `RequestResponseEvent::Message` with unexpected request id {:?}", + "Received `RequestResponseEvent::OutboundFailure` with unexpected request id {:?} error {:?} from {:?}", request_id, + error, + peer ); - debug_assert!(false); continue }, }; @@ -920,7 +931,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { peer, protocol: protocol.clone(), duration: started.elapsed(), - result: Err(RequestFailure::Network(error.into())), + result: Err(RequestFailure::Network(error)), }; return Poll::Ready(ToSwarm::GenerateEvent(out)) @@ -976,7 +987,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { // Send out fallback requests. for (peer, protocol, request, pending_response) in fallback_requests.drain(..) { - if let Some((behaviour, _)) = self.protocols.get_mut(&protocol) { + if let Some(ProtocolDetails { behaviour, .. }) = self.protocols.get_mut(&protocol) { Self::send_request_inner( behaviour, &mut self.pending_requests, @@ -1145,7 +1156,7 @@ mod tests { use crate::mock::MockPeerStore; use assert_matches::assert_matches; - use futures::{channel::oneshot, executor::LocalPool, task::Spawn}; + use futures::channel::oneshot; use libp2p::{ core::{ transport::{MemoryTransport, Transport}, @@ -1158,10 +1169,10 @@ mod tests { }; use std::{iter, time::Duration}; - struct TokioExecutor(tokio::runtime::Runtime); + struct TokioExecutor; impl Executor for TokioExecutor { fn exec(&self, f: Pin<Box<dyn Future<Output = ()> + Send>>) { - let _ = self.0.spawn(f); + tokio::spawn(f); } } @@ -1178,13 +1189,14 @@ mod tests { let behaviour = RequestResponsesBehaviour::new(list, Arc::new(MockPeerStore {})).unwrap(); - let runtime = tokio::runtime::Runtime::new().unwrap(); - let mut swarm = Swarm::new( transport, behaviour, keypair.public().to_peer_id(), - SwarmConfig::with_executor(TokioExecutor(runtime)), + SwarmConfig::with_executor(TokioExecutor {}) + // This is taken care of by notification protocols in non-test environment + // It is very slow in test environment for some reason, hence larger timeout + .with_idle_connection_timeout(Duration::from_secs(10)), ); let listen_addr: Multiaddr = format!("/memory/{}", rand::random::<u64>()).parse().unwrap(); @@ -1194,34 +1206,27 @@ mod tests { (swarm, listen_addr) } - #[test] - fn basic_request_response_works() { + #[tokio::test] + async fn basic_request_response_works() { let protocol_name = ProtocolName::from("/test/req-resp/1"); - let mut pool = LocalPool::new(); // Build swarms whose behaviour is [`RequestResponsesBehaviour`]. let mut swarms = (0..2) .map(|_| { let (tx, mut rx) = async_channel::bounded::<IncomingRequest>(64); - pool.spawner() - .spawn_obj( - async move { - while let Some(rq) = rx.next().await { - let (fb_tx, fb_rx) = oneshot::channel(); - assert_eq!(rq.payload, b"this is a request"); - let _ = rq.pending_response.send(super::OutgoingResponse { - result: Ok(b"this is a response".to_vec()), - reputation_changes: Vec::new(), - sent_feedback: Some(fb_tx), - }); - fb_rx.await.unwrap(); - } - } - .boxed() - .into(), - ) - .unwrap(); + tokio::spawn(async move { + while let Some(rq) = rx.next().await { + let (fb_tx, fb_rx) = oneshot::channel(); + assert_eq!(rq.payload, b"this is a request"); + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"this is a response".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: Some(fb_tx), + }); + fb_rx.await.unwrap(); + } + }); let protocol_config = ProtocolConfig { name: protocol_name.clone(), @@ -1245,84 +1250,69 @@ mod tests { let (mut swarm, _) = swarms.remove(0); // Running `swarm[0]` in the background. - pool.spawner() - .spawn_obj({ - async move { - loop { - match swarm.select_next_some().await { - SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { - result.unwrap(); - }, - _ => {}, - } - } - } - .boxed() - .into() - }) - .unwrap(); - - // Remove and run the remaining swarm. - let (mut swarm, _) = swarms.remove(0); - pool.run_until(async move { - let mut response_receiver = None; - + tokio::spawn(async move { loop { match swarm.select_next_some().await { - SwarmEvent::ConnectionEstablished { peer_id, .. } => { - let (sender, receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - &peer_id, - protocol_name.clone(), - b"this is a request".to_vec(), - None, - sender, - IfDisconnected::ImmediateError, - ); - assert!(response_receiver.is_none()); - response_receiver = Some(receiver); - }, - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { result.unwrap(); - break }, _ => {}, } } - - assert_eq!( - response_receiver.unwrap().await.unwrap().unwrap(), - (b"this is a response".to_vec(), protocol_name) - ); }); + + // Remove and run the remaining swarm. + let (mut swarm, _) = swarms.remove(0); + let mut response_receiver = None; + + loop { + match swarm.select_next_some().await { + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + let (sender, receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + &peer_id, + protocol_name.clone(), + b"this is a request".to_vec(), + None, + sender, + IfDisconnected::ImmediateError, + ); + assert!(response_receiver.is_none()); + response_receiver = Some(receiver); + }, + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + result.unwrap(); + break + }, + _ => {}, + } + } + + assert_eq!( + response_receiver.unwrap().await.unwrap().unwrap(), + (b"this is a response".to_vec(), protocol_name) + ); } - #[test] - fn max_response_size_exceeded() { + #[tokio::test] + async fn max_response_size_exceeded() { let protocol_name = ProtocolName::from("/test/req-resp/1"); - let mut pool = LocalPool::new(); // Build swarms whose behaviour is [`RequestResponsesBehaviour`]. let mut swarms = (0..2) .map(|_| { let (tx, mut rx) = async_channel::bounded::<IncomingRequest>(64); - pool.spawner() - .spawn_obj( - async move { - while let Some(rq) = rx.next().await { - assert_eq!(rq.payload, b"this is a request"); - let _ = rq.pending_response.send(super::OutgoingResponse { - result: Ok(b"this response exceeds the limit".to_vec()), - reputation_changes: Vec::new(), - sent_feedback: None, - }); - } - } - .boxed() - .into(), - ) - .unwrap(); + tokio::spawn(async move { + while let Some(rq) = rx.next().await { + assert_eq!(rq.payload, b"this is a request"); + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"this response exceeds the limit".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: None, + }); + } + }); let protocol_config = ProtocolConfig { name: protocol_name.clone(), @@ -1347,74 +1337,68 @@ mod tests { // Running `swarm[0]` in the background until a `InboundRequest` event happens, // which is a hint about the test having ended. let (mut swarm, _) = swarms.remove(0); - pool.spawner() - .spawn_obj({ - async move { - loop { - match swarm.select_next_some().await { - SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { - assert!(result.is_ok()); - break - }, - _ => {}, - } - } - } - .boxed() - .into() - }) - .unwrap(); - - // Remove and run the remaining swarm. - let (mut swarm, _) = swarms.remove(0); - pool.run_until(async move { - let mut response_receiver = None; - + tokio::spawn(async move { loop { match swarm.select_next_some().await { - SwarmEvent::ConnectionEstablished { peer_id, .. } => { - let (sender, receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - &peer_id, - protocol_name.clone(), - b"this is a request".to_vec(), - None, - sender, - IfDisconnected::ImmediateError, - ); - assert!(response_receiver.is_none()); - response_receiver = Some(receiver); + SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { + assert!(result.is_ok()); }, - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - assert!(result.is_err()); - break + SwarmEvent::ConnectionClosed { .. } => { + break; }, _ => {}, } } + }); + + // Remove and run the remaining swarm. + let (mut swarm, _) = swarms.remove(0); + + let mut response_receiver = None; - match response_receiver.unwrap().await.unwrap().unwrap_err() { - RequestFailure::Network(OutboundFailure::ConnectionClosed) => {}, - _ => panic!(), + loop { + match swarm.select_next_some().await { + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + let (sender, receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + &peer_id, + protocol_name.clone(), + b"this is a request".to_vec(), + None, + sender, + IfDisconnected::ImmediateError, + ); + assert!(response_receiver.is_none()); + response_receiver = Some(receiver); + }, + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + assert!(result.is_err()); + break + }, + _ => {}, } - }); + } + + match response_receiver.unwrap().await.unwrap().unwrap_err() { + RequestFailure::Network(OutboundFailure::Io(_)) => {}, + request_failure => panic!("Unexpected failure: {request_failure:?}"), + } } - /// A [`RequestId`] is a unique identifier among either all inbound or all outbound requests for + /// A `RequestId` is a unique identifier among either all inbound or all outbound requests for /// a single [`RequestResponsesBehaviour`] behaviour. It is not guaranteed to be unique across - /// multiple [`RequestResponsesBehaviour`] behaviours. Thus when handling [`RequestId`] in the + /// multiple [`RequestResponsesBehaviour`] behaviours. Thus, when handling `RequestId` in the /// context of multiple [`RequestResponsesBehaviour`] behaviours, one needs to couple the - /// protocol name with the [`RequestId`] to get a unique request identifier. + /// protocol name with the `RequestId` to get a unique request identifier. /// /// This test ensures that two requests on different protocols can be handled concurrently - /// without a [`RequestId`] collision. + /// without a `RequestId` collision. /// /// See [`ProtocolRequestId`] for additional information. - #[test] - fn request_id_collision() { + #[tokio::test] + async fn request_id_collision() { let protocol_name_1 = ProtocolName::from("/test/req-resp-1/1"); let protocol_name_2 = ProtocolName::from("/test/req-resp-2/1"); - let mut pool = LocalPool::new(); let mut swarm_1 = { let protocol_configs = vec![ @@ -1472,114 +1456,100 @@ mod tests { swarm_1.dial(listen_add_2).unwrap(); // Run swarm 2 in the background, receiving two requests. - pool.spawner() - .spawn_obj( - async move { - loop { - match swarm_2.select_next_some().await { - SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { - result.unwrap(); - }, - _ => {}, - } - } + tokio::spawn(async move { + loop { + match swarm_2.select_next_some().await { + SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { + result.unwrap(); + }, + _ => {}, } - .boxed() - .into(), - ) - .unwrap(); + } + }); // Handle both requests sent by swarm 1 to swarm 2 in the background. // // Make sure both requests overlap, by answering the first only after receiving the // second. - pool.spawner() - .spawn_obj( - async move { - let protocol_1_request = swarm_2_handler_1.next().await; - let protocol_2_request = swarm_2_handler_2.next().await; - - protocol_1_request - .unwrap() - .pending_response - .send(OutgoingResponse { - result: Ok(b"this is a response".to_vec()), - reputation_changes: Vec::new(), - sent_feedback: None, - }) - .unwrap(); - protocol_2_request - .unwrap() - .pending_response - .send(OutgoingResponse { - result: Ok(b"this is a response".to_vec()), - reputation_changes: Vec::new(), - sent_feedback: None, - }) - .unwrap(); - } - .boxed() - .into(), - ) - .unwrap(); + tokio::spawn(async move { + let protocol_1_request = swarm_2_handler_1.next().await; + let protocol_2_request = swarm_2_handler_2.next().await; + + protocol_1_request + .unwrap() + .pending_response + .send(OutgoingResponse { + result: Ok(b"this is a response".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: None, + }) + .unwrap(); + protocol_2_request + .unwrap() + .pending_response + .send(OutgoingResponse { + result: Ok(b"this is a response".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: None, + }) + .unwrap(); + }); // Have swarm 1 send two requests to swarm 2 and await responses. - pool.run_until(async move { - let mut response_receivers = None; - let mut num_responses = 0; - loop { - match swarm_1.select_next_some().await { - SwarmEvent::ConnectionEstablished { peer_id, .. } => { - let (sender_1, receiver_1) = oneshot::channel(); - let (sender_2, receiver_2) = oneshot::channel(); - swarm_1.behaviour_mut().send_request( - &peer_id, - protocol_name_1.clone(), - b"this is a request".to_vec(), - None, - sender_1, - IfDisconnected::ImmediateError, - ); - swarm_1.behaviour_mut().send_request( - &peer_id, - protocol_name_2.clone(), - b"this is a request".to_vec(), - None, - sender_2, - IfDisconnected::ImmediateError, - ); - assert!(response_receivers.is_none()); - response_receivers = Some((receiver_1, receiver_2)); - }, - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - num_responses += 1; - result.unwrap(); - if num_responses == 2 { - break - } - }, - _ => {}, - } + let mut response_receivers = None; + let mut num_responses = 0; + + loop { + match swarm_1.select_next_some().await { + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + let (sender_1, receiver_1) = oneshot::channel(); + let (sender_2, receiver_2) = oneshot::channel(); + swarm_1.behaviour_mut().send_request( + &peer_id, + protocol_name_1.clone(), + b"this is a request".to_vec(), + None, + sender_1, + IfDisconnected::ImmediateError, + ); + swarm_1.behaviour_mut().send_request( + &peer_id, + protocol_name_2.clone(), + b"this is a request".to_vec(), + None, + sender_2, + IfDisconnected::ImmediateError, + ); + assert!(response_receivers.is_none()); + response_receivers = Some((receiver_1, receiver_2)); + }, + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + num_responses += 1; + result.unwrap(); + if num_responses == 2 { + break + } + }, + _ => {}, } - let (response_receiver_1, response_receiver_2) = response_receivers.unwrap(); - assert_eq!( - response_receiver_1.await.unwrap().unwrap(), - (b"this is a response".to_vec(), protocol_name_1) - ); - assert_eq!( - response_receiver_2.await.unwrap().unwrap(), - (b"this is a response".to_vec(), protocol_name_2) - ); - }); + } + let (response_receiver_1, response_receiver_2) = response_receivers.unwrap(); + assert_eq!( + response_receiver_1.await.unwrap().unwrap(), + (b"this is a response".to_vec(), protocol_name_1) + ); + assert_eq!( + response_receiver_2.await.unwrap().unwrap(), + (b"this is a response".to_vec(), protocol_name_2) + ); } - #[test] - fn request_fallback() { + #[tokio::test] + async fn request_fallback() { let protocol_name_1 = ProtocolName::from("/test/req-resp/2"); let protocol_name_1_fallback = ProtocolName::from("/test/req-resp/1"); let protocol_name_2 = ProtocolName::from("/test/another"); - let mut pool = LocalPool::new(); let protocol_config_1 = ProtocolConfig { name: protocol_name_1.clone(), @@ -1617,39 +1587,31 @@ mod tests { let mut protocol_config_2 = protocol_config_2.clone(); protocol_config_2.inbound_queue = Some(tx_2); - pool.spawner() - .spawn_obj( - async move { - for _ in 0..2 { - if let Some(rq) = rx_1.next().await { - let (fb_tx, fb_rx) = oneshot::channel(); - assert_eq!(rq.payload, b"request on protocol /test/req-resp/1"); - let _ = rq.pending_response.send(super::OutgoingResponse { - result: Ok( - b"this is a response on protocol /test/req-resp/1".to_vec() - ), - reputation_changes: Vec::new(), - sent_feedback: Some(fb_tx), - }); - fb_rx.await.unwrap(); - } - } - - if let Some(rq) = rx_2.next().await { - let (fb_tx, fb_rx) = oneshot::channel(); - assert_eq!(rq.payload, b"request on protocol /test/other"); - let _ = rq.pending_response.send(super::OutgoingResponse { - result: Ok(b"this is a response on protocol /test/other".to_vec()), - reputation_changes: Vec::new(), - sent_feedback: Some(fb_tx), - }); - fb_rx.await.unwrap(); - } + tokio::spawn(async move { + for _ in 0..2 { + if let Some(rq) = rx_1.next().await { + let (fb_tx, fb_rx) = oneshot::channel(); + assert_eq!(rq.payload, b"request on protocol /test/req-resp/1"); + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"this is a response on protocol /test/req-resp/1".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: Some(fb_tx), + }); + fb_rx.await.unwrap(); } - .boxed() - .into(), - ) - .unwrap(); + } + + if let Some(rq) = rx_2.next().await { + let (fb_tx, fb_rx) = oneshot::channel(); + assert_eq!(rq.payload, b"request on protocol /test/other"); + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"this is a response on protocol /test/other".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: Some(fb_tx), + }); + fb_rx.await.unwrap(); + } + }); build_swarm(vec![protocol_config_1_fallback, protocol_config_2].into_iter()) }; @@ -1670,132 +1632,269 @@ mod tests { } // Running `older_swarm`` in the background. - pool.spawner() - .spawn_obj({ - async move { - loop { - _ = older_swarm.0.select_next_some().await; - } - } - .boxed() - .into() - }) - .unwrap(); + tokio::spawn(async move { + loop { + _ = older_swarm.0.select_next_some().await; + } + }); // Run the newer swarm. Attempt to make requests on all protocols. let (mut swarm, _) = new_swarm; let mut older_peer_id = None; - pool.run_until(async move { - let mut response_receiver = None; - // Try the new protocol with a fallback. - loop { - match swarm.select_next_some().await { - SwarmEvent::ConnectionEstablished { peer_id, .. } => { - older_peer_id = Some(peer_id); - let (sender, receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - &peer_id, - protocol_name_1.clone(), - b"request on protocol /test/req-resp/2".to_vec(), - Some(( - b"request on protocol /test/req-resp/1".to_vec(), - protocol_config_1_fallback.name.clone(), - )), - sender, - IfDisconnected::ImmediateError, - ); - response_receiver = Some(receiver); - }, - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - result.unwrap(); - break - }, - _ => {}, - } + let mut response_receiver = None; + // Try the new protocol with a fallback. + loop { + match swarm.select_next_some().await { + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + older_peer_id = Some(peer_id); + let (sender, receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + &peer_id, + protocol_name_1.clone(), + b"request on protocol /test/req-resp/2".to_vec(), + Some(( + b"request on protocol /test/req-resp/1".to_vec(), + protocol_config_1_fallback.name.clone(), + )), + sender, + IfDisconnected::ImmediateError, + ); + response_receiver = Some(receiver); + }, + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + result.unwrap(); + break + }, + _ => {}, } - assert_eq!( - response_receiver.unwrap().await.unwrap().unwrap(), - ( - b"this is a response on protocol /test/req-resp/1".to_vec(), - protocol_name_1_fallback.clone() - ) - ); - // Try the old protocol with a useless fallback. - let (sender, response_receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - older_peer_id.as_ref().unwrap(), - protocol_name_1_fallback.clone(), - b"request on protocol /test/req-resp/1".to_vec(), - Some(( - b"dummy request, will fail if processed".to_vec(), - protocol_config_1_fallback.name.clone(), - )), - sender, - IfDisconnected::ImmediateError, - ); - loop { - match swarm.select_next_some().await { - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - result.unwrap(); - break - }, - _ => {}, - } + } + assert_eq!( + response_receiver.unwrap().await.unwrap().unwrap(), + ( + b"this is a response on protocol /test/req-resp/1".to_vec(), + protocol_name_1_fallback.clone() + ) + ); + // Try the old protocol with a useless fallback. + let (sender, response_receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + older_peer_id.as_ref().unwrap(), + protocol_name_1_fallback.clone(), + b"request on protocol /test/req-resp/1".to_vec(), + Some(( + b"dummy request, will fail if processed".to_vec(), + protocol_config_1_fallback.name.clone(), + )), + sender, + IfDisconnected::ImmediateError, + ); + loop { + match swarm.select_next_some().await { + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + result.unwrap(); + break + }, + _ => {}, } - assert_eq!( - response_receiver.await.unwrap().unwrap(), - ( - b"this is a response on protocol /test/req-resp/1".to_vec(), - protocol_name_1_fallback.clone() - ) - ); - // Try the new protocol with no fallback. Should fail. - let (sender, response_receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - older_peer_id.as_ref().unwrap(), - protocol_name_1.clone(), - b"request on protocol /test/req-resp-2".to_vec(), - None, - sender, - IfDisconnected::ImmediateError, - ); - loop { - match swarm.select_next_some().await { - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - assert_matches!( - result.unwrap_err(), - RequestFailure::Network(OutboundFailure::UnsupportedProtocols) - ); - break - }, - _ => {}, - } + } + assert_eq!( + response_receiver.await.unwrap().unwrap(), + ( + b"this is a response on protocol /test/req-resp/1".to_vec(), + protocol_name_1_fallback.clone() + ) + ); + // Try the new protocol with no fallback. Should fail. + let (sender, response_receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + older_peer_id.as_ref().unwrap(), + protocol_name_1.clone(), + b"request on protocol /test/req-resp-2".to_vec(), + None, + sender, + IfDisconnected::ImmediateError, + ); + loop { + match swarm.select_next_some().await { + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + assert_matches!( + result.unwrap_err(), + RequestFailure::Network(OutboundFailure::UnsupportedProtocols) + ); + break + }, + _ => {}, } - assert!(response_receiver.await.unwrap().is_err()); - // Try the other protocol with no fallback. - let (sender, response_receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - older_peer_id.as_ref().unwrap(), - protocol_name_2.clone(), - b"request on protocol /test/other".to_vec(), - None, - sender, - IfDisconnected::ImmediateError, - ); + } + assert!(response_receiver.await.unwrap().is_err()); + // Try the other protocol with no fallback. + let (sender, response_receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + older_peer_id.as_ref().unwrap(), + protocol_name_2.clone(), + b"request on protocol /test/other".to_vec(), + None, + sender, + IfDisconnected::ImmediateError, + ); + loop { + match swarm.select_next_some().await { + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + result.unwrap(); + break + }, + _ => {}, + } + } + assert_eq!( + response_receiver.await.unwrap().unwrap(), + (b"this is a response on protocol /test/other".to_vec(), protocol_name_2.clone()) + ); + } + + /// This test ensures the `RequestResponsesBehaviour` propagates back the Request::Timeout error + /// even if the libp2p component hangs. + /// + /// For testing purposes, the communication happens on the `/test/req-resp/1` protocol. + /// + /// This is achieved by: + /// - Two swarms are connected, the first one is slow to respond and has the timeout set to 10 + /// seconds. The second swarm is configured with a timeout of 10 seconds in libp2p, however in + /// substrate this is set to 1 second. + /// + /// - The first swarm introduces a delay of 2 seconds before responding to the request. + /// + /// - The second swarm must enforce the 1 second timeout. + #[tokio::test] + async fn enforce_outbound_timeouts() { + const REQUEST_TIMEOUT: Duration = Duration::from_secs(10); + const REQUEST_TIMEOUT_SHORT: Duration = Duration::from_secs(1); + + // These swarms only speaks protocol_name. + let protocol_name = ProtocolName::from("/test/req-resp/1"); + + let protocol_config = ProtocolConfig { + name: protocol_name.clone(), + fallback_names: Vec::new(), + max_request_size: 1024, + max_response_size: 1024 * 1024, + request_timeout: REQUEST_TIMEOUT, // <-- important for the test + inbound_queue: None, + }; + + // Build swarms whose behaviour is [`RequestResponsesBehaviour`]. + let (mut first_swarm, _) = { + let (tx, mut rx) = async_channel::bounded::<IncomingRequest>(64); + + tokio::spawn(async move { + if let Some(rq) = rx.next().await { + assert_eq!(rq.payload, b"this is a request"); + + // Sleep for more than `REQUEST_TIMEOUT_SHORT` and less than + // `REQUEST_TIMEOUT`. + tokio::time::sleep(REQUEST_TIMEOUT_SHORT * 2).await; + + // By the time the response is sent back, the second swarm + // received Timeout. + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"Second swarm already timedout".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: None, + }); + } + }); + + let mut protocol_config = protocol_config.clone(); + protocol_config.inbound_queue = Some(tx); + + build_swarm(iter::once(protocol_config)) + }; + + let (mut second_swarm, second_address) = { + let (tx, mut rx) = async_channel::bounded::<IncomingRequest>(64); + + tokio::spawn(async move { + while let Some(rq) = rx.next().await { + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"This is the response".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: None, + }); + } + }); + let mut protocol_config = protocol_config.clone(); + protocol_config.inbound_queue = Some(tx); + + build_swarm(iter::once(protocol_config.clone())) + }; + // Modify the second swarm to have a shorter timeout. + second_swarm + .behaviour_mut() + .protocols + .get_mut(&protocol_name) + .unwrap() + .request_timeout = REQUEST_TIMEOUT_SHORT; + + // Ask first swarm to dial the second swarm. + { + Swarm::dial(&mut first_swarm, second_address).unwrap(); + } + + // Running the first swarm in the background until a `InboundRequest` event happens, + // which is a hint about the test having ended. + tokio::spawn(async move { loop { - match swarm.select_next_some().await { - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - result.unwrap(); - break + let event = first_swarm.select_next_some().await; + match event { + SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { + assert!(result.is_ok()); + break; + }, + SwarmEvent::ConnectionClosed { .. } => { + break; }, _ => {}, } } - assert_eq!( - response_receiver.await.unwrap().unwrap(), - (b"this is a response on protocol /test/other".to_vec(), protocol_name_2.clone()) - ); }); + + // Run the second swarm. + // - on connection established send the request to the first swarm + // - expect to receive a timeout + let mut response_receiver = None; + loop { + let event = second_swarm.select_next_some().await; + + match event { + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + let (sender, receiver) = oneshot::channel(); + second_swarm.behaviour_mut().send_request( + &peer_id, + protocol_name.clone(), + b"this is a request".to_vec(), + None, + sender, + IfDisconnected::ImmediateError, + ); + assert!(response_receiver.is_none()); + response_receiver = Some(receiver); + }, + SwarmEvent::ConnectionClosed { .. } => { + break; + }, + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + assert!(result.is_err()); + break + }, + _ => {}, + } + } + + // Expect the timeout. + match response_receiver.unwrap().await.unwrap().unwrap_err() { + RequestFailure::Network(OutboundFailure::Timeout) => {}, + request_failure => panic!("Unexpected failure: {request_failure:?}"), + } } } diff --git a/substrate/client/network/src/service.rs b/substrate/client/network/src/service.rs index 5e5e4ee285894a987bea356351ab0ed05ac1f2a5..b4463ad480891eafe40199d9ff7c5ef23a111681 100644 --- a/substrate/client/network/src/service.rs +++ b/substrate/client/network/src/service.rs @@ -41,7 +41,7 @@ use crate::{ NetworkState, NotConnectedPeer as NetworkStateNotConnectedPeer, Peer as NetworkStatePeer, }, peer_store::{PeerStore, PeerStoreProvider}, - protocol::{self, NotifsHandlerError, Protocol, Ready}, + protocol::{self, Protocol, Ready}, protocol_controller::{self, ProtoSetConfig, ProtocolController, SetId}, request_responses::{IfDisconnected, ProtocolConfig as RequestResponseConfig, RequestFailure}, service::{ @@ -59,10 +59,7 @@ use crate::{ }; use codec::DecodeAll; -use either::Either; use futures::{channel::oneshot, prelude::*}; -#[allow(deprecated)] -use libp2p::swarm::THandlerErr; use libp2p::{ connection_limits::{ConnectionLimits, Exceeded}, core::{upgrade, ConnectedPoint, Endpoint}, @@ -94,7 +91,6 @@ pub use libp2p::identity::{DecodingError, Keypair, PublicKey}; pub use metrics::NotificationMetrics; pub use protocol::NotificationsSink; use std::{ - cmp, collections::{HashMap, HashSet}, fs, iter, marker::PhantomData, @@ -115,6 +111,7 @@ pub mod signature; pub mod traits; struct Libp2pBandwidthSink { + #[allow(deprecated)] sink: Arc<transport::BandwidthSinks>, } @@ -336,7 +333,7 @@ where "🷠Local node identity is: {}", local_peer_id.to_base58(), ); - log::info!(target: "sub-libp2p", "Running libp2p network backend"); + info!(target: "sub-libp2p", "Running libp2p network backend"); let (transport, bandwidth) = { let config_mem = match network_config.transport { @@ -344,46 +341,7 @@ where TransportConfig::Normal { .. } => false, }; - // The yamux buffer size limit is configured to be equal to the maximum frame size - // of all protocols. 10 bytes are added to each limit for the length prefix that - // is not included in the upper layer protocols limit but is still present in the - // yamux buffer. These 10 bytes correspond to the maximum size required to encode - // a variable-length-encoding 64bits number. In other words, we make the - // assumption that no notification larger than 2^64 will ever be sent. - let yamux_maximum_buffer_size = { - let requests_max = request_response_protocols - .iter() - .map(|cfg| usize::try_from(cfg.max_request_size).unwrap_or(usize::MAX)); - let responses_max = request_response_protocols - .iter() - .map(|cfg| usize::try_from(cfg.max_response_size).unwrap_or(usize::MAX)); - let notifs_max = notification_protocols - .iter() - .map(|cfg| usize::try_from(cfg.max_notification_size()).unwrap_or(usize::MAX)); - - // A "default" max is added to cover all the other protocols: ping, identify, - // kademlia, block announces, and transactions. - let default_max = cmp::max( - 1024 * 1024, - usize::try_from(protocol::BLOCK_ANNOUNCES_TRANSACTIONS_SUBSTREAM_SIZE) - .unwrap_or(usize::MAX), - ); - - iter::once(default_max) - .chain(requests_max) - .chain(responses_max) - .chain(notifs_max) - .max() - .expect("iterator known to always yield at least one element; qed") - .saturating_add(10) - }; - - transport::build_transport( - local_identity.clone().into(), - config_mem, - network_config.yamux_window_size, - yamux_maximum_buffer_size, - ) + transport::build_transport(local_identity.clone().into(), config_mem) }; let (to_notifications, from_protocol_controllers) = @@ -558,6 +516,7 @@ where request_response_protocols, Arc::clone(&peer_store_handle), external_addresses.clone(), + network_config.public_addresses.iter().cloned().map(Into::into).collect(), ConnectionLimits::default() .with_max_established_per_peer(Some(crate::MAX_CONNECTIONS_PER_PEER as u32)) .with_max_established_incoming(Some( @@ -973,6 +932,18 @@ where expires, )); } + + fn start_providing(&self, key: KademliaKey) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::StartProviding(key)); + } + + fn stop_providing(&self, key: KademliaKey) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::StopProviding(key)); + } + + fn get_providers(&self, key: KademliaKey) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::GetProviders(key)); + } } #[async_trait::async_trait] @@ -1333,6 +1304,9 @@ enum ServiceToWorkerMsg { update_local_storage: bool, }, StoreRecord(KademliaKey, Vec<u8>, Option<PeerId>, Option<Instant>), + StartProviding(KademliaKey), + StopProviding(KademliaKey), + GetProviders(KademliaKey), AddKnownAddress(PeerId, Multiaddr), EventStream(out_events::Sender), Request { @@ -1466,6 +1440,12 @@ where .network_service .behaviour_mut() .store_record(key.into(), value, publisher, expires), + ServiceToWorkerMsg::StartProviding(key) => + self.network_service.behaviour_mut().start_providing(key.into()), + ServiceToWorkerMsg::StopProviding(key) => + self.network_service.behaviour_mut().stop_providing(&key.into()), + ServiceToWorkerMsg::GetProviders(key) => + self.network_service.behaviour_mut().get_providers(key.into()), ServiceToWorkerMsg::AddKnownAddress(peer_id, addr) => self.network_service.behaviour_mut().add_known_address(peer_id, addr), ServiceToWorkerMsg::EventStream(sender) => self.event_streams.push(sender), @@ -1501,8 +1481,7 @@ where } /// Process the next event coming from `Swarm`. - #[allow(deprecated)] - fn handle_swarm_event(&mut self, event: SwarmEvent<BehaviourOut, THandlerErr<Behaviour<B>>>) { + fn handle_swarm_event(&mut self, event: SwarmEvent<BehaviourOut>) { match event { SwarmEvent::Behaviour(BehaviourOut::InboundRequest { protocol, result, .. }) => { if let Some(metrics) = self.metrics.as_ref() { @@ -1527,6 +1506,7 @@ where Some("busy-omitted"), ResponseFailure::Network(InboundFailure::ConnectionClosed) => Some("connection-closed"), + ResponseFailure::Network(InboundFailure::Io(_)) => Some("io"), }; if let Some(reason) = reason { @@ -1566,6 +1546,7 @@ where "connection-closed", RequestFailure::Network(OutboundFailure::UnsupportedProtocols) => "unsupported", + RequestFailure::Network(OutboundFailure::Io(_)) => "io", }; metrics @@ -1678,6 +1659,9 @@ where DhtEvent::ValuePut(_) => "value-put", DhtEvent::ValuePutFailed(_) => "value-put-failed", DhtEvent::PutRecordRequest(_, _, _, _) => "put-record-request", + DhtEvent::StartProvidingFailed(_) => "start-providing-failed", + DhtEvent::ProvidersFound(_, _) => "providers-found", + DhtEvent::ProvidersNotFound(_) => "providers-not-found", }; metrics .kademlia_query_duration @@ -1732,15 +1716,6 @@ where }; let reason = match cause { Some(ConnectionError::IO(_)) => "transport-error", - Some(ConnectionError::Handler(Either::Left(Either::Left( - Either::Left(Either::Right( - NotifsHandlerError::SyncNotificationsClogged, - )), - )))) => "sync-notifications-clogged", - Some(ConnectionError::Handler(Either::Left(Either::Left( - Either::Right(Either::Left(_)), - )))) => "ping-timeout", - Some(ConnectionError::Handler(_)) => "protocol-error", Some(ConnectionError::KeepAliveTimeout) => "keep-alive-timeout", None => "actively-closed", }; @@ -1779,7 +1754,12 @@ where not_reported.then(|| self.boot_node_ids.get(&peer_id)).flatten() { if let DialError::WrongPeerId { obtained, endpoint } = &error { - if let ConnectedPoint::Dialer { address, role_override: _ } = endpoint { + if let ConnectedPoint::Dialer { + address, + role_override: _, + port_use: _, + } = endpoint + { let address_without_peer_id = parse_addr(address.clone().into()) .map_or_else(|_| address.clone(), |r| r.1.into()); @@ -1800,7 +1780,6 @@ where } if let Some(metrics) = self.metrics.as_ref() { - #[allow(deprecated)] let reason = match error { DialError::Denied { cause } => if cause.downcast::<Exceeded>().is_ok() { @@ -1840,7 +1819,6 @@ where "Libp2p => IncomingConnectionError({local_addr},{send_back_addr} via {connection_id:?}): {error}" ); if let Some(metrics) = self.metrics.as_ref() { - #[allow(deprecated)] let reason = match error { ListenError::Denied { cause } => if cause.downcast::<Exceeded>().is_ok() { @@ -1893,6 +1871,21 @@ where metrics.listeners_errors_total.inc(); } }, + SwarmEvent::NewExternalAddrCandidate { address } => { + trace!(target: "sub-libp2p", "Libp2p => NewExternalAddrCandidate: {address:?}"); + }, + SwarmEvent::ExternalAddrConfirmed { address } => { + trace!(target: "sub-libp2p", "Libp2p => ExternalAddrConfirmed: {address:?}"); + }, + SwarmEvent::ExternalAddrExpired { address } => { + trace!(target: "sub-libp2p", "Libp2p => ExternalAddrExpired: {address:?}"); + }, + SwarmEvent::NewExternalAddrOfPeer { peer_id, address } => { + trace!(target: "sub-libp2p", "Libp2p => NewExternalAddrOfPeer({peer_id:?}): {address:?}") + }, + event => { + warn!(target: "sub-libp2p", "New unknown SwarmEvent libp2p event: {event:?}"); + }, } } } diff --git a/substrate/client/network/src/service/traits.rs b/substrate/client/network/src/service/traits.rs index f5dd2995acb14254b4c7bfb7fdb2763d7cb3f082..acfed9ea894c23a9c7bbf95bf6aa0aa3106f0680 100644 --- a/substrate/client/network/src/service/traits.rs +++ b/substrate/client/network/src/service/traits.rs @@ -234,6 +234,15 @@ pub trait NetworkDHTProvider { publisher: Option<PeerId>, expires: Option<Instant>, ); + + /// Register this node as a provider for `key` on the DHT. + fn start_providing(&self, key: KademliaKey); + + /// Deregister this node as a provider for `key` on the DHT. + fn stop_providing(&self, key: KademliaKey); + + /// Start getting the list of providers for `key` on the DHT. + fn get_providers(&self, key: KademliaKey); } impl<T> NetworkDHTProvider for Arc<T> @@ -262,6 +271,18 @@ where ) { T::store_record(self, key, value, publisher, expires) } + + fn start_providing(&self, key: KademliaKey) { + T::start_providing(self, key) + } + + fn stop_providing(&self, key: KademliaKey) { + T::stop_providing(self, key) + } + + fn get_providers(&self, key: KademliaKey) { + T::get_providers(self, key) + } } /// Provides an ability to set a fork sync request for a particular block. diff --git a/substrate/client/network/src/transport.rs b/substrate/client/network/src/transport.rs index ed7e7c574e16f41ef51d85b41ac041486dde0efd..2f6b7a643c48002fc32ef3170c3f86cc0044b8e7 100644 --- a/substrate/client/network/src/transport.rs +++ b/substrate/client/network/src/transport.rs @@ -29,6 +29,8 @@ use libp2p::{ }; use std::{sync::Arc, time::Duration}; +// TODO: Create a wrapper similar to upstream `BandwidthTransport` that tracks sent/received bytes +#[allow(deprecated)] pub use libp2p::bandwidth::BandwidthSinks; /// Builds the transport that serves as a common ground for all connections. @@ -36,21 +38,12 @@ pub use libp2p::bandwidth::BandwidthSinks; /// If `memory_only` is true, then only communication within the same process are allowed. Only /// addresses with the format `/memory/...` are allowed. /// -/// `yamux_window_size` is the maximum size of the Yamux receive windows. `None` to leave the -/// default (256kiB). -/// -/// `yamux_maximum_buffer_size` is the maximum allowed size of the Yamux buffer. This should be -/// set either to the maximum of all the maximum allowed sizes of messages frames of all -/// high-level protocols combined, or to some generously high value if you are sure that a maximum -/// size is enforced on all high-level protocols. -/// /// Returns a `BandwidthSinks` object that allows querying the average bandwidth produced by all /// the connections spawned with this transport. +#[allow(deprecated)] pub fn build_transport( keypair: identity::Keypair, memory_only: bool, - yamux_window_size: Option<u32>, - yamux_maximum_buffer_size: usize, ) -> (Boxed<(PeerId, StreamMuxerBox)>, Arc<BandwidthSinks>) { // Build the base layer of the transport. let transport = if !memory_only { @@ -81,19 +74,7 @@ pub fn build_transport( }; let authentication_config = noise::Config::new(&keypair).expect("Can create noise config. qed"); - let multiplexing_config = { - let mut yamux_config = libp2p::yamux::Config::default(); - // Enable proper flow-control: window updates are only sent when - // buffered data has been consumed. - yamux_config.set_window_update_mode(libp2p::yamux::WindowUpdateMode::on_read()); - yamux_config.set_max_buffer_size(yamux_maximum_buffer_size); - - if let Some(yamux_window_size) = yamux_window_size { - yamux_config.set_receive_window_size(yamux_window_size); - } - - yamux_config - }; + let multiplexing_config = libp2p::yamux::Config::default(); let transport = transport .upgrade(upgrade::Version::V1Lazy) diff --git a/substrate/client/network/statement/Cargo.toml b/substrate/client/network/statement/Cargo.toml index 43933f066edd279353cbee883346af938ab2c114..dd3a8bef8a2f61600a8fced575008800e6671eea 100644 --- a/substrate/client/network/statement/Cargo.toml +++ b/substrate/client/network/statement/Cargo.toml @@ -22,10 +22,10 @@ codec = { features = ["derive"], workspace = true, default-features = true } futures = { workspace = true } log = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-statement-store = { workspace = true, default-features = true } diff --git a/substrate/client/network/sync/Cargo.toml b/substrate/client/network/sync/Cargo.toml index 378b7c12e9b75f46c798f5f1cc5da1c571db3d0d..fdc290a2d01e85cf3a81a16835564d8d4184998c 100644 --- a/substrate/client/network/sync/Cargo.toml +++ b/substrate/client/network/sync/Cargo.toml @@ -23,30 +23,30 @@ array-bytes = { workspace = true, default-features = true } async-channel = { workspace = true } async-trait = { workspace = true } codec = { features = ["derive"], workspace = true, default-features = true } +fork-tree = { workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } log = { workspace = true, default-features = true } mockall = { workspace = true } -prost = { workspace = true } -schnellru = { workspace = true } -smallvec = { workspace = true, default-features = true } -thiserror = { workspace = true } -tokio-stream = { workspace = true } -tokio = { features = ["macros", "time"], workspace = true, default-features = true } -fork-tree = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } +prost = { workspace = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } +schnellru = { workspace = true } +smallvec = { workspace = true, default-features = true } sp-arithmetic = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } sp-consensus-grandpa = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } +tokio = { features = ["macros", "time"], workspace = true, default-features = true } +tokio-stream = { workspace = true } [dev-dependencies] mockall = { workspace = true } diff --git a/substrate/client/network/sync/src/engine.rs b/substrate/client/network/sync/src/engine.rs index 349c41ee1f4a4fcd46aa07ca03c0cbc2755b7623..3b9c5f38329b116144e56b484cc8e8d95aa7b500 100644 --- a/substrate/client/network/sync/src/engine.rs +++ b/substrate/client/network/sync/src/engine.rs @@ -100,6 +100,8 @@ mod rep { pub const REFUSED: Rep = Rep::new(-(1 << 10), "Request refused"); /// Reputation change when a peer doesn't respond in time to our messages. pub const TIMEOUT: Rep = Rep::new(-(1 << 10), "Request timeout"); + /// Reputation change when a peer connection failed with IO error. + pub const IO: Rep = Rep::new(-(1 << 10), "IO error during request"); } struct Metrics { @@ -654,7 +656,13 @@ where ToServiceCommand::SetSyncForkRequest(peers, hash, number) => { self.strategy.set_sync_fork_request(peers, &hash, number); }, - ToServiceCommand::EventStream(tx) => self.event_streams.push(tx), + ToServiceCommand::EventStream(tx) => { + // Let a new subscriber know about already connected peers. + for peer_id in self.peers.keys() { + let _ = tx.unbounded_send(SyncEvent::PeerConnected(*peer_id)); + } + self.event_streams.push(tx); + }, ToServiceCommand::RequestJustification(hash, number) => self.strategy.request_justification(&hash, number), ToServiceCommand::ClearJustificationRequests => @@ -1019,9 +1027,14 @@ where debug_assert!( false, "Can not receive `RequestFailure::Obsolete` after dropping the \ - response receiver.", + response receiver.", ); }, + RequestFailure::Network(OutboundFailure::Io(_)) => { + self.network_service.report_peer(peer_id, rep::IO); + self.network_service + .disconnect_peer(peer_id, self.block_announce_protocol_name.clone()); + }, } }, Err(oneshot::Canceled) => { diff --git a/substrate/client/network/test/Cargo.toml b/substrate/client/network/test/Cargo.toml index ebece1762f294ce39ea415291e6883dfa959de37..783d47f21fa76eee2431796c2cf3d5e0e92bd2db 100644 --- a/substrate/client/network/test/Cargo.toml +++ b/substrate/client/network/test/Cargo.toml @@ -16,7 +16,6 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -tokio = { workspace = true, default-features = true } async-trait = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } @@ -29,11 +28,11 @@ sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } -sc-network-types = { workspace = true, default-features = true } -sc-utils = { workspace = true, default-features = true } sc-network-light = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } -sc-service = { features = ["test-helpers"], workspace = true } +sc-network-types = { workspace = true, default-features = true } +sc-service = { workspace = true } +sc-utils = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } @@ -41,3 +40,4 @@ sp-runtime = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime = { workspace = true } substrate-test-runtime-client = { workspace = true } +tokio = { workspace = true, default-features = true } diff --git a/substrate/client/network/test/src/lib.rs b/substrate/client/network/test/src/lib.rs index 825481314c672ae26c2309b43880eb11e68f94c3..3cdf211e07f6898d044ec6fffdcdfa5a49ea47e0 100644 --- a/substrate/client/network/test/src/lib.rs +++ b/substrate/client/network/test/src/lib.rs @@ -91,7 +91,7 @@ use sp_runtime::{ traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero}, Justification, Justifications, }; -use substrate_test_runtime_client::AccountKeyring; +use substrate_test_runtime_client::Sr25519Keyring; pub use substrate_test_runtime_client::{ runtime::{Block, ExtrinsicBuilder, Hash, Header, Transfer}, TestClient, TestClientBuilder, TestClientBuilderExt, @@ -475,8 +475,8 @@ where BlockOrigin::File, |mut builder| { let transfer = Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Alice.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Alice.into(), amount: 1, nonce, }; diff --git a/substrate/client/network/transactions/Cargo.toml b/substrate/client/network/transactions/Cargo.toml index 2ffd6f5f466005ffe829874a74173efb7cb697d4..ef9ea1c46197061bb82c8034038947f844a7b2a6 100644 --- a/substrate/client/network/transactions/Cargo.toml +++ b/substrate/client/network/transactions/Cargo.toml @@ -26,5 +26,5 @@ sc-network-common = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } diff --git a/substrate/client/network/transactions/src/config.rs b/substrate/client/network/transactions/src/config.rs index 239b76b51485f128f2a374575c0ffee352929e48..42a335d7041ad14363a82863238ee86f44b6e3f5 100644 --- a/substrate/client/network/transactions/src/config.rs +++ b/substrate/client/network/transactions/src/config.rs @@ -22,7 +22,7 @@ use futures::prelude::*; use sc_network::MAX_RESPONSE_SIZE; use sc_network_common::ExHashT; use sp_runtime::traits::Block as BlockT; -use std::{collections::HashMap, future::Future, pin::Pin, time}; +use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc, time}; /// Interval at which we propagate transactions; pub(crate) const PROPAGATE_TIMEOUT: time::Duration = time::Duration::from_millis(2900); @@ -57,7 +57,7 @@ pub type TransactionImportFuture = Pin<Box<dyn Future<Output = TransactionImport /// Transaction pool interface pub trait TransactionPool<H: ExHashT, B: BlockT>: Send + Sync { /// Get transactions from the pool that are ready to be propagated. - fn transactions(&self) -> Vec<(H, B::Extrinsic)>; + fn transactions(&self) -> Vec<(H, Arc<B::Extrinsic>)>; /// Get hash of transaction. fn hash_of(&self, transaction: &B::Extrinsic) -> H; /// Import a transaction into the pool. @@ -67,7 +67,7 @@ pub trait TransactionPool<H: ExHashT, B: BlockT>: Send + Sync { /// Notify the pool about transactions broadcast. fn on_broadcasted(&self, propagations: HashMap<H, Vec<String>>); /// Get transaction by hash. - fn transaction(&self, hash: &H) -> Option<B::Extrinsic>; + fn transaction(&self, hash: &H) -> Option<Arc<B::Extrinsic>>; } /// Dummy implementation of the [`TransactionPool`] trait for a transaction pool that is always @@ -79,7 +79,7 @@ pub trait TransactionPool<H: ExHashT, B: BlockT>: Send + Sync { pub struct EmptyTransactionPool; impl<H: ExHashT + Default, B: BlockT> TransactionPool<H, B> for EmptyTransactionPool { - fn transactions(&self) -> Vec<(H, B::Extrinsic)> { + fn transactions(&self) -> Vec<(H, Arc<B::Extrinsic>)> { Vec::new() } @@ -93,7 +93,7 @@ impl<H: ExHashT + Default, B: BlockT> TransactionPool<H, B> for EmptyTransaction fn on_broadcasted(&self, _: HashMap<H, Vec<String>>) {} - fn transaction(&self, _h: &H) -> Option<B::Extrinsic> { + fn transaction(&self, _h: &H) -> Option<Arc<B::Extrinsic>> { None } } diff --git a/substrate/client/network/transactions/src/lib.rs b/substrate/client/network/transactions/src/lib.rs index 44fa702ef6d4f7bff2e4046728508922ab1fa00a..a6a95520794592b451b8147cf3471b67853a2cb0 100644 --- a/substrate/client/network/transactions/src/lib.rs +++ b/substrate/client/network/transactions/src/lib.rs @@ -469,7 +469,7 @@ where fn do_propagate_transactions( &mut self, - transactions: &[(H, B::Extrinsic)], + transactions: &[(H, Arc<B::Extrinsic>)], ) -> HashMap<H, Vec<String>> { let mut propagated_to = HashMap::<_, Vec<_>>::new(); let mut propagated_transactions = 0; diff --git a/substrate/client/network/types/Cargo.toml b/substrate/client/network/types/Cargo.toml index 7438eaeffcd2e564979b395c1a32fd119f1f2e12..67814f135d39803f9c1b3a382ab39048e53cd0ab 100644 --- a/substrate/client/network/types/Cargo.toml +++ b/substrate/client/network/types/Cargo.toml @@ -14,7 +14,7 @@ bs58 = { workspace = true, default-features = true } bytes = { version = "1.4.0", default-features = false } ed25519-dalek = { workspace = true, default-features = true } libp2p-identity = { features = ["ed25519", "peerid", "rand"], workspace = true } -libp2p-kad = { version = "0.44.6", default-features = false } +libp2p-kad = { version = "0.46.2", default-features = false } litep2p = { workspace = true } log = { workspace = true, default-features = true } multiaddr = { workspace = true } diff --git a/substrate/client/offchain/Cargo.toml b/substrate/client/offchain/Cargo.toml index 71b40211e1263e7ad808e7037191fee26a0e4909..bfdb29cc4c351a227b0eaa484d516b2ebe7ed311 100644 --- a/substrate/client/offchain/Cargo.toml +++ b/substrate/client/offchain/Cargo.toml @@ -26,13 +26,12 @@ http-body-util = { workspace = true } hyper = { features = ["http1", "http2"], workspace = true, default-features = true } hyper-rustls = { workspace = true } hyper-util = { features = ["client-legacy", "http1", "http2"], workspace = true } +log = { workspace = true, default-features = true } num_cpus = { workspace = true } once_cell = { workspace = true } parking_lot = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } rustls = { workspace = true } -threadpool = { workspace = true } -tracing = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } @@ -41,15 +40,15 @@ sc-transaction-pool-api = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-externalities = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } sp-offchain = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sp-externalities = { workspace = true, default-features = true } -log = { workspace = true, default-features = true } +threadpool = { workspace = true } +tracing = { workspace = true, default-features = true } [dev-dependencies] async-trait = { workspace = true } -tokio = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-client-db = { default-features = true, workspace = true } sc-transaction-pool = { workspace = true, default-features = true } @@ -57,6 +56,7 @@ sc-transaction-pool-api = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } +tokio = { workspace = true, default-features = true } [features] default = [] diff --git a/substrate/client/offchain/src/api.rs b/substrate/client/offchain/src/api.rs index a5981f14c093ce3c41a9e97e7e3331b6f2ff351e..7d5c07deca4fb6d8374ec26d35585dae236c5140 100644 --- a/substrate/client/offchain/src/api.rs +++ b/substrate/client/offchain/src/api.rs @@ -375,7 +375,7 @@ mod tests { } #[test] - fn should_set_and_get_local_storage() { + fn should_set_get_and_clear_local_storage() { // given let kind = StorageKind::PERSISTENT; let mut api = offchain_db(); @@ -387,6 +387,12 @@ mod tests { // then assert_eq!(api.local_storage_get(kind, key), Some(b"value".to_vec())); + + // when + api.local_storage_clear(kind, key); + + // then + assert_eq!(api.local_storage_get(kind, key), None); } #[test] diff --git a/substrate/client/proposer-metrics/src/lib.rs b/substrate/client/proposer-metrics/src/lib.rs index 2856300cf8027b45bb0ef32b8781c533ae2e340c..a62278988f1229a473d5629ccd61fbbb3756c112 100644 --- a/substrate/client/proposer-metrics/src/lib.rs +++ b/substrate/client/proposer-metrics/src/lib.rs @@ -44,7 +44,7 @@ impl MetricsLink { } /// The reason why proposing a block ended. -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum EndProposingReason { NoMoreTransactions, HitDeadline, diff --git a/substrate/client/rpc-api/Cargo.toml b/substrate/client/rpc-api/Cargo.toml index 3263285aa2b111ece735305c20eaa6661f37cca7..e7bb723d8839839a60a3d3741e8a04e7e27e61e1 100644 --- a/substrate/client/rpc-api/Cargo.toml +++ b/substrate/client/rpc-api/Cargo.toml @@ -17,15 +17,15 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true, default-features = true } -scale-info = { features = ["derive"], workspace = true } -serde = { features = ["derive"], workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -thiserror = { workspace = true } +jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } sc-chain-spec = { workspace = true, default-features = true } sc-mixnet = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-rpc = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } -jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } +thiserror = { workspace = true } diff --git a/substrate/client/rpc-api/src/offchain/mod.rs b/substrate/client/rpc-api/src/offchain/mod.rs index 4dd5b066d49fe90063d14a276a5ec2bf9731b8f9..606d441231b052f45e600c520a32c90d68ad6a97 100644 --- a/substrate/client/rpc-api/src/offchain/mod.rs +++ b/substrate/client/rpc-api/src/offchain/mod.rs @@ -31,6 +31,10 @@ pub trait OffchainApi { #[method(name = "offchain_localStorageSet", with_extensions)] fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> Result<(), Error>; + /// Clear offchain local storage under given key and prefix. + #[method(name = "offchain_localStorageClear", with_extensions)] + fn clear_local_storage(&self, kind: StorageKind, key: Bytes) -> Result<(), Error>; + /// Get offchain local storage under given key and prefix. #[method(name = "offchain_localStorageGet", with_extensions)] fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> Result<Option<Bytes>, Error>; diff --git a/substrate/client/rpc-servers/src/lib.rs b/substrate/client/rpc-servers/src/lib.rs index 31e4042d81f27342a80b48587bb7ad130607edcc..4234ff3196ef004609dfe684969a1f4518f366a3 100644 --- a/substrate/client/rpc-servers/src/lib.rs +++ b/substrate/client/rpc-servers/src/lib.rs @@ -144,11 +144,56 @@ where local_addrs.push(local_addr); let cfg = cfg.clone(); - let mut id_provider2 = id_provider.clone(); + 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, + } = listener.rpc_settings(); + + 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()); + + if let Some(provider) = id_provider.clone() { + builder = builder.set_id_provider(provider); + } else { + builder = builder.set_id_provider(RandomStringIdProvider::new(16)); + }; + + let service_builder = builder.to_service_builder(); + let deny_unsafe = deny_unsafe(&local_addr, &rpc_methods); tokio_handle.spawn(async move { loop { - let (sock, remote_addr, rpc_cfg) = tokio::select! { + let (sock, remote_addr) = tokio::select! { res = listener.accept() => { match res { Ok(s) => s, @@ -161,57 +206,10 @@ where _ = cfg.stop_handle.clone().shutdown() => break, }; - 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 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 rate_limit_whitelisted_ips2 = rate_limit_whitelisted_ips.clone(); let svc = tower::service_fn(move |mut req: http::Request<hyper::body::Incoming>| { @@ -224,14 +222,14 @@ where let proxy_ip = if rate_limit_trust_proxy_headers { get_proxy_ip(&req) } else { None }; - let rate_limit_cfg = if rate_limit_whitelisted_ips + let rate_limit_cfg = if rate_limit_whitelisted_ips2 .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() { + if !rate_limit_whitelisted_ips2.is_empty() { log::debug!(target: "rpc", "ip={ip}, proxy_ip={:?} is not trusted, rate-limit enabled", proxy_ip); } rate_limit diff --git a/substrate/client/rpc-servers/src/utils.rs b/substrate/client/rpc-servers/src/utils.rs index 51cce6224298427f0626b295b83da548a59cd801..b76cfced34015c17068bb703372c13a28616e00c 100644 --- a/substrate/client/rpc-servers/src/utils.rs +++ b/substrate/client/rpc-servers/src/utils.rs @@ -176,17 +176,19 @@ pub(crate) struct Listener { impl Listener { /// Accepts a new connection. - pub(crate) async fn accept( - &mut self, - ) -> std::io::Result<(tokio::net::TcpStream, SocketAddr, RpcSettings)> { + pub(crate) async fn accept(&mut self) -> std::io::Result<(tokio::net::TcpStream, SocketAddr)> { let (sock, remote_addr) = self.listener.accept().await?; - Ok((sock, remote_addr, self.cfg.clone())) + Ok((sock, remote_addr)) } /// Returns the local address the listener is bound to. pub fn local_addr(&self) -> SocketAddr { self.local_addr } + + pub fn rpc_settings(&self) -> RpcSettings { + self.cfg.clone() + } } pub(crate) fn host_filtering(enabled: bool, addr: SocketAddr) -> Option<HostFilterLayer> { diff --git a/substrate/client/rpc-spec-v2/Cargo.toml b/substrate/client/rpc-spec-v2/Cargo.toml index daa805912fb958a19714e9d22b0dd911f002d26f..ebe7e7eca7b4fd1f9eeb72284975008fd2b5a178 100644 --- a/substrate/client/rpc-spec-v2/Cargo.toml +++ b/substrate/client/rpc-spec-v2/Cargo.toml @@ -20,44 +20,45 @@ jsonrpsee = { workspace = true, features = ["client-core", "macros", "server-cor # Internal chain structures for "chain_spec". sc-chain-spec = { workspace = true, default-features = true } # Pool for submitting extrinsics required by "transaction" +array-bytes = { workspace = true, default-features = true } +codec = { workspace = true, default-features = true } +futures = { workspace = true } +futures-util = { workspace = true } +hex = { workspace = true, default-features = true } +itertools = { workspace = true } +log = { workspace = true, default-features = true } +parking_lot = { workspace = true, default-features = true } +rand = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } +sc-rpc = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } +schnellru = { workspace = true } +serde = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } -sp-rpc = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-rpc = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sc-rpc = { workspace = true, default-features = true } -codec = { workspace = true, default-features = true } thiserror = { workspace = true } -serde = { workspace = true, default-features = true } -hex = { workspace = true, default-features = true } -futures = { workspace = true } -parking_lot = { workspace = true, default-features = true } -tokio-stream = { features = ["sync"], workspace = true } tokio = { features = ["sync"], workspace = true, default-features = true } -array-bytes = { workspace = true, default-features = true } -log = { workspace = true, default-features = true } -futures-util = { workspace = true } -rand = { workspace = true, default-features = true } -schnellru = { workspace = true } +tokio-stream = { features = ["sync"], workspace = true } [dev-dependencies] +assert_matches = { workspace = true } async-trait = { 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 } -substrate-test-runtime = { workspace = true } -substrate-test-runtime-transaction-pool = { workspace = true } -sp-consensus = { workspace = true, default-features = true } -sp-externalities = { workspace = true, default-features = true } -sp-maybe-compressed-blob = { workspace = true, default-features = true } +pretty_assertions = { workspace = true } sc-block-builder = { workspace = true, default-features = true } -sc-service = { features = ["test-helpers"], workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true, features = ["test-helpers"] } -assert_matches = { workspace = true } -pretty_assertions = { workspace = true } +sc-service = { workspace = true, default-features = true } sc-transaction-pool = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } +sp-externalities = { workspace = true, default-features = true } +sp-maybe-compressed-blob = { workspace = true, default-features = true } +substrate-test-runtime = { workspace = true } +substrate-test-runtime-client = { workspace = true } +substrate-test-runtime-transaction-pool = { workspace = true } +tokio = { features = ["macros"], workspace = true, default-features = true } diff --git a/substrate/client/rpc-spec-v2/src/archive/api.rs b/substrate/client/rpc-spec-v2/src/archive/api.rs index b197383040005de0b585c684a91c034ebc5e80ec..a205d0502c936ae1600ba1c46147c9800cadf0b9 100644 --- a/substrate/client/rpc-spec-v2/src/archive/api.rs +++ b/substrate/client/rpc-spec-v2/src/archive/api.rs @@ -19,7 +19,9 @@ //! API trait of the archive methods. use crate::{ - common::events::{ArchiveStorageResult, PaginatedStorageQuery}, + common::events::{ + ArchiveStorageDiffEvent, ArchiveStorageDiffItem, ArchiveStorageEvent, StorageQuery, + }, MethodResult, }; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; @@ -97,11 +99,32 @@ pub trait ArchiveApi<Hash> { /// # Unstable /// /// This method is unstable and subject to change in the future. - #[method(name = "archive_unstable_storage", blocking)] + #[subscription( + name = "archive_unstable_storage" => "archive_unstable_storageEvent", + unsubscribe = "archive_unstable_stopStorage", + item = ArchiveStorageEvent, + )] fn archive_unstable_storage( &self, hash: Hash, - items: Vec<PaginatedStorageQuery<String>>, + items: Vec<StorageQuery<String>>, child_trie: Option<String>, - ) -> RpcResult<ArchiveStorageResult>; + ); + + /// Returns the storage difference between two blocks. + /// + /// # Unstable + /// + /// This method is unstable and can change in minor or patch releases. + #[subscription( + name = "archive_unstable_storageDiff" => "archive_unstable_storageDiffEvent", + unsubscribe = "archive_unstable_storageDiff_stopStorageDiff", + item = ArchiveStorageDiffEvent, + )] + fn archive_unstable_storage_diff( + &self, + hash: Hash, + items: Vec<ArchiveStorageDiffItem<String>>, + previous_hash: Option<Hash>, + ); } diff --git a/substrate/client/rpc-spec-v2/src/archive/archive.rs b/substrate/client/rpc-spec-v2/src/archive/archive.rs index dd6c566a76ed475f41acf189dc5e6734e68f58bc..62e44a0162419fb22acca9c56344a416eedfc5d8 100644 --- a/substrate/client/rpc-spec-v2/src/archive/archive.rs +++ b/substrate/client/rpc-spec-v2/src/archive/archive.rs @@ -19,17 +19,29 @@ //! API implementation for `archive`. use crate::{ - archive::{error::Error as ArchiveError, ArchiveApiServer}, - common::events::{ArchiveStorageResult, PaginatedStorageQuery}, - hex_string, MethodResult, + archive::{ + archive_storage::ArchiveStorageDiff, error::Error as ArchiveError, ArchiveApiServer, + }, + common::{ + events::{ + ArchiveStorageDiffEvent, ArchiveStorageDiffItem, ArchiveStorageEvent, StorageQuery, + }, + storage::{QueryResult, StorageSubscriptionClient}, + }, + hex_string, MethodResult, SubscriptionTaskExecutor, }; use codec::Encode; -use jsonrpsee::core::{async_trait, RpcResult}; +use futures::FutureExt; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + PendingSubscriptionSink, +}; use sc_client_api::{ Backend, BlockBackend, BlockchainEvents, CallExecutor, ChildInfo, ExecutorProvider, StorageKey, StorageProvider, }; +use sc_rpc::utils::Subscription; use sp_api::{CallApiAt, CallContext}; use sp_blockchain::{ Backend as BlockChainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata, @@ -41,37 +53,15 @@ use sp_runtime::{ }; use std::{collections::HashSet, marker::PhantomData, sync::Arc}; -use super::archive_storage::ArchiveStorage; - -/// The configuration of [`Archive`]. -pub struct ArchiveConfig { - /// The maximum number of items the `archive_storage` can return for a descendant query before - /// pagination is required. - pub max_descendant_responses: usize, - /// The maximum number of queried items allowed for the `archive_storage` at a time. - pub max_queried_items: usize, -} +use tokio::sync::mpsc; -/// The maximum number of items the `archive_storage` can return for a descendant query before -/// pagination is required. -/// -/// Note: this is identical to the `chainHead` value. -const MAX_DESCENDANT_RESPONSES: usize = 5; +pub(crate) const LOG_TARGET: &str = "rpc-spec-v2::archive"; -/// The maximum number of queried items allowed for the `archive_storage` at a time. +/// The buffer capacity for each storage query. /// -/// Note: A queried item can also be a descendant query which can return up to -/// `MAX_DESCENDANT_RESPONSES`. -const MAX_QUERIED_ITEMS: usize = 8; - -impl Default for ArchiveConfig { - fn default() -> Self { - Self { - max_descendant_responses: MAX_DESCENDANT_RESPONSES, - max_queried_items: MAX_QUERIED_ITEMS, - } - } -} +/// This is small because the underlying JSON-RPC server has +/// its down buffer capacity per connection as well. +const STORAGE_QUERY_BUF: usize = 16; /// An API for archive RPC calls. pub struct Archive<BE: Backend<Block>, Block: BlockT, Client> { @@ -79,13 +69,10 @@ pub struct Archive<BE: Backend<Block>, Block: BlockT, Client> { client: Arc<Client>, /// Backend of the chain. backend: Arc<BE>, + /// Executor to spawn subscriptions. + executor: SubscriptionTaskExecutor, /// The hexadecimal encoded hash of the genesis block. genesis_hash: String, - /// The maximum number of items the `archive_storage` can return for a descendant query before - /// pagination is required. - storage_max_descendant_responses: usize, - /// The maximum number of queried items allowed for the `archive_storage` at a time. - storage_max_queried_items: usize, /// Phantom member to pin the block type. _phantom: PhantomData<Block>, } @@ -96,17 +83,10 @@ impl<BE: Backend<Block>, Block: BlockT, Client> Archive<BE, Block, Client> { client: Arc<Client>, backend: Arc<BE>, genesis_hash: GenesisHash, - config: ArchiveConfig, + executor: SubscriptionTaskExecutor, ) -> Self { let genesis_hash = hex_string(&genesis_hash.as_ref()); - Self { - client, - backend, - genesis_hash, - storage_max_descendant_responses: config.max_descendant_responses, - storage_max_queried_items: config.max_queried_items, - _phantom: PhantomData, - } + Self { client, backend, executor, genesis_hash, _phantom: PhantomData } } } @@ -236,46 +216,157 @@ where fn archive_unstable_storage( &self, + pending: PendingSubscriptionSink, hash: Block::Hash, - items: Vec<PaginatedStorageQuery<String>>, + items: Vec<StorageQuery<String>>, child_trie: Option<String>, - ) -> RpcResult<ArchiveStorageResult> { - let items = items - .into_iter() - .map(|query| { - let key = StorageKey(parse_hex_param(query.key)?); - let pagination_start_key = query - .pagination_start_key - .map(|key| parse_hex_param(key).map(|key| StorageKey(key))) - .transpose()?; - - // Paginated start key is only supported - if pagination_start_key.is_some() && !query.query_type.is_descendant_query() { - return Err(ArchiveError::InvalidParam( - "Pagination start key is only supported for descendants queries" - .to_string(), - )) + ) { + let mut storage_client = + StorageSubscriptionClient::<Client, Block, BE>::new(self.client.clone()); + + let fut = async move { + let Ok(mut sink) = pending.accept().await.map(Subscription::from) else { return }; + + let items = match items + .into_iter() + .map(|query| { + let key = StorageKey(parse_hex_param(query.key)?); + Ok(StorageQuery { key, query_type: query.query_type }) + }) + .collect::<Result<Vec<_>, ArchiveError>>() + { + Ok(items) => items, + Err(error) => { + let _ = sink.send(&ArchiveStorageEvent::err(error.to_string())); + return + }, + }; + + let child_trie = child_trie.map(|child_trie| parse_hex_param(child_trie)).transpose(); + let child_trie = match child_trie { + Ok(child_trie) => child_trie.map(ChildInfo::new_default_from_vec), + Err(error) => { + let _ = sink.send(&ArchiveStorageEvent::err(error.to_string())); + return + }, + }; + + let (tx, mut rx) = tokio::sync::mpsc::channel(STORAGE_QUERY_BUF); + let storage_fut = storage_client.generate_events(hash, items, child_trie, tx); + + // We don't care about the return value of this join: + // - process_events might encounter an error (if the client disconnected) + // - storage_fut might encounter an error while processing a trie queries and + // the error is propagated via the sink. + let _ = futures::future::join(storage_fut, process_storage_events(&mut rx, &mut sink)) + .await; + }; + + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + } + + fn archive_unstable_storage_diff( + &self, + pending: PendingSubscriptionSink, + hash: Block::Hash, + items: Vec<ArchiveStorageDiffItem<String>>, + previous_hash: Option<Block::Hash>, + ) { + let storage_client = ArchiveStorageDiff::new(self.client.clone()); + let client = self.client.clone(); + + log::trace!(target: LOG_TARGET, "Storage diff subscription started"); + + let fut = async move { + let Ok(mut sink) = pending.accept().await.map(Subscription::from) else { return }; + + let previous_hash = if let Some(previous_hash) = previous_hash { + previous_hash + } else { + let Ok(Some(current_header)) = client.header(hash) else { + let message = format!("Block header is not present: {hash}"); + let _ = sink.send(&ArchiveStorageDiffEvent::err(message)).await; + return + }; + *current_header.parent_hash() + }; + + let (tx, mut rx) = tokio::sync::mpsc::channel(STORAGE_QUERY_BUF); + let storage_fut = + storage_client.handle_trie_queries(hash, items, previous_hash, tx.clone()); + + // We don't care about the return value of this join: + // - process_events might encounter an error (if the client disconnected) + // - storage_fut might encounter an error while processing a trie queries and + // the error is propagated via the sink. + let _ = + futures::future::join(storage_fut, process_storage_diff_events(&mut rx, &mut sink)) + .await; + }; + + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + } +} + +/// Sends all the events of the storage_diff method to the sink. +async fn process_storage_diff_events( + rx: &mut mpsc::Receiver<ArchiveStorageDiffEvent>, + sink: &mut Subscription, +) { + loop { + tokio::select! { + _ = sink.closed() => { + return + }, + + maybe_event = rx.recv() => { + let Some(event) = maybe_event else { + break; + }; + + if event.is_done() { + log::debug!(target: LOG_TARGET, "Finished processing partial trie query"); + } else if event.is_err() { + log::debug!(target: LOG_TARGET, "Error encountered while processing partial trie query"); } - Ok(PaginatedStorageQuery { - key, - query_type: query.query_type, - pagination_start_key, - }) - }) - .collect::<Result<Vec<_>, ArchiveError>>()?; + if sink.send(&event).await.is_err() { + return + } + } + } + } +} - let child_trie = child_trie - .map(|child_trie| parse_hex_param(child_trie)) - .transpose()? - .map(ChildInfo::new_default_from_vec); +/// Sends all the events of the storage method to the sink. +async fn process_storage_events(rx: &mut mpsc::Receiver<QueryResult>, sink: &mut Subscription) { + loop { + tokio::select! { + _ = sink.closed() => { + break + } + + maybe_storage = rx.recv() => { + let Some(event) = maybe_storage else { + break; + }; - let storage_client = ArchiveStorage::new( - self.client.clone(), - self.storage_max_descendant_responses, - self.storage_max_queried_items, - ); + match event { + Ok(None) => continue, - Ok(storage_client.handle_query(hash, items, child_trie)) + Ok(Some(event)) => + if sink.send(&ArchiveStorageEvent::result(event)).await.is_err() { + return + }, + + Err(error) => { + let _ = sink.send(&ArchiveStorageEvent::err(error)).await; + return + } + } + } + } } + + let _ = sink.send(&ArchiveStorageEvent::StorageDone).await; } diff --git a/substrate/client/rpc-spec-v2/src/archive/archive_storage.rs b/substrate/client/rpc-spec-v2/src/archive/archive_storage.rs index 26e7c299de418e499863c796c6566e912ac28f06..390db765a48f6340416047570ce8950b61dde184 100644 --- a/substrate/client/rpc-spec-v2/src/archive/archive_storage.rs +++ b/substrate/client/rpc-spec-v2/src/archive/archive_storage.rs @@ -18,112 +18,832 @@ //! Implementation of the `archive_storage` method. -use std::sync::Arc; +use std::{ + collections::{hash_map::Entry, HashMap}, + sync::Arc, +}; +use itertools::Itertools; use sc_client_api::{Backend, ChildInfo, StorageKey, StorageProvider}; use sp_runtime::traits::Block as BlockT; -use crate::common::{ - events::{ArchiveStorageResult, PaginatedStorageQuery, StorageQueryType}, - storage::{IterQueryType, QueryIter, Storage}, +use super::error::Error as ArchiveError; +use crate::{ + archive::archive::LOG_TARGET, + common::{ + events::{ + ArchiveStorageDiffEvent, ArchiveStorageDiffItem, ArchiveStorageDiffOperationType, + ArchiveStorageDiffResult, ArchiveStorageDiffType, StorageResult, + }, + storage::Storage, + }, }; +use tokio::sync::mpsc; + +/// Parse hex-encoded string parameter as raw bytes. +/// +/// If the parsing fails, returns an error propagated to the RPC method. +pub fn parse_hex_param(param: String) -> Result<Vec<u8>, ArchiveError> { + // Methods can accept empty parameters. + if param.is_empty() { + return Ok(Default::default()) + } + + array_bytes::hex2bytes(¶m).map_err(|_| ArchiveError::InvalidParam(param)) +} + +#[derive(Debug, PartialEq, Clone)] +pub struct DiffDetails { + key: StorageKey, + return_type: ArchiveStorageDiffType, + child_trie_key: Option<ChildInfo>, + child_trie_key_string: Option<String>, +} + +/// The type of storage query. +#[derive(Debug, PartialEq, Clone, Copy)] +enum FetchStorageType { + /// Only fetch the value. + Value, + /// Only fetch the hash. + Hash, + /// Fetch both the value and the hash. + Both, +} -/// Generates the events of the `archive_storage` method. -pub struct ArchiveStorage<Client, Block, BE> { - /// Storage client. +/// The return value of the `fetch_storage` method. +#[derive(Debug, PartialEq, Clone)] +enum FetchedStorage { + /// Storage value under a key. + Value(StorageResult), + /// Storage hash under a key. + Hash(StorageResult), + /// Both storage value and hash under a key. + Both { value: StorageResult, hash: StorageResult }, +} + +pub struct ArchiveStorageDiff<Client, Block, BE> { client: Storage<Client, Block, BE>, - /// The maximum number of responses the API can return for a descendant query at a time. - storage_max_descendant_responses: usize, - /// The maximum number of queried items allowed for the `archive_storage` at a time. - storage_max_queried_items: usize, } -impl<Client, Block, BE> ArchiveStorage<Client, Block, BE> { - /// Constructs a new [`ArchiveStorage`]. - pub fn new( - client: Arc<Client>, - storage_max_descendant_responses: usize, - storage_max_queried_items: usize, - ) -> Self { - Self { - client: Storage::new(client), - storage_max_descendant_responses, - storage_max_queried_items, - } +impl<Client, Block, BE> ArchiveStorageDiff<Client, Block, BE> { + pub fn new(client: Arc<Client>) -> Self { + Self { client: Storage::new(client) } } } -impl<Client, Block, BE> ArchiveStorage<Client, Block, BE> +impl<Client, Block, BE> ArchiveStorageDiff<Client, Block, BE> where Block: BlockT + 'static, BE: Backend<Block> + 'static, - Client: StorageProvider<Block, BE> + 'static, + Client: StorageProvider<Block, BE> + Send + Sync + 'static, { - /// Generate the response of the `archive_storage` method. - pub fn handle_query( + /// Fetch the storage from the given key. + fn fetch_storage( &self, hash: Block::Hash, - mut items: Vec<PaginatedStorageQuery<StorageKey>>, - child_key: Option<ChildInfo>, - ) -> ArchiveStorageResult { - let discarded_items = items.len().saturating_sub(self.storage_max_queried_items); - items.truncate(self.storage_max_queried_items); + key: StorageKey, + maybe_child_trie: Option<ChildInfo>, + ty: FetchStorageType, + ) -> Result<Option<FetchedStorage>, String> { + match ty { + FetchStorageType::Value => { + let result = self.client.query_value(hash, &key, maybe_child_trie.as_ref())?; + + Ok(result.map(FetchedStorage::Value)) + }, + + FetchStorageType::Hash => { + let result = self.client.query_hash(hash, &key, maybe_child_trie.as_ref())?; + + Ok(result.map(FetchedStorage::Hash)) + }, + + FetchStorageType::Both => { + let Some(value) = self.client.query_value(hash, &key, maybe_child_trie.as_ref())? + else { + return Ok(None); + }; + + let Some(hash) = self.client.query_hash(hash, &key, maybe_child_trie.as_ref())? + else { + return Ok(None); + }; + + Ok(Some(FetchedStorage::Both { value, hash })) + }, + } + } + + /// Check if the key belongs to the provided query items. + /// + /// A key belongs to the query items when: + /// - the provided key is a prefix of the key in the query items. + /// - the query items are empty. + /// + /// Returns an optional `FetchStorageType` based on the query items. + /// If the key does not belong to the query items, returns `None`. + fn belongs_to_query(key: &StorageKey, items: &[DiffDetails]) -> Option<FetchStorageType> { + // User has requested all keys, by default this fallbacks to fetching the value. + if items.is_empty() { + return Some(FetchStorageType::Value) + } + + let mut value = false; + let mut hash = false; - let mut storage_results = Vec::with_capacity(items.len()); for item in items { - match item.query_type { - StorageQueryType::Value => { - match self.client.query_value(hash, &item.key, child_key.as_ref()) { - Ok(Some(value)) => storage_results.push(value), - Ok(None) => continue, - Err(error) => return ArchiveStorageResult::err(error), - } - }, - StorageQueryType::Hash => - match self.client.query_hash(hash, &item.key, child_key.as_ref()) { - Ok(Some(value)) => storage_results.push(value), - Ok(None) => continue, - Err(error) => return ArchiveStorageResult::err(error), - }, - StorageQueryType::ClosestDescendantMerkleValue => - match self.client.query_merkle_value(hash, &item.key, child_key.as_ref()) { - Ok(Some(value)) => storage_results.push(value), - Ok(None) => continue, - Err(error) => return ArchiveStorageResult::err(error), - }, - StorageQueryType::DescendantsValues => { - match self.client.query_iter_pagination( - QueryIter { - query_key: item.key, - ty: IterQueryType::Value, - pagination_start_key: item.pagination_start_key, - }, + if key.as_ref().starts_with(&item.key.as_ref()) { + match item.return_type { + ArchiveStorageDiffType::Value => value = true, + ArchiveStorageDiffType::Hash => hash = true, + } + } + } + + match (value, hash) { + (true, true) => Some(FetchStorageType::Both), + (true, false) => Some(FetchStorageType::Value), + (false, true) => Some(FetchStorageType::Hash), + (false, false) => None, + } + } + + /// Send the provided result to the `tx` sender. + /// + /// Returns `false` if the sender has been closed. + fn send_result( + tx: &mpsc::Sender<ArchiveStorageDiffEvent>, + result: FetchedStorage, + operation_type: ArchiveStorageDiffOperationType, + child_trie_key: Option<String>, + ) -> bool { + let items = match result { + FetchedStorage::Value(storage_result) | FetchedStorage::Hash(storage_result) => + vec![storage_result], + FetchedStorage::Both { value, hash } => vec![value, hash], + }; + + for item in items { + let res = ArchiveStorageDiffEvent::StorageDiff(ArchiveStorageDiffResult { + key: item.key, + result: item.result, + operation_type, + child_trie_key: child_trie_key.clone(), + }); + if tx.blocking_send(res).is_err() { + return false + } + } + + true + } + + fn handle_trie_queries_inner( + &self, + hash: Block::Hash, + previous_hash: Block::Hash, + items: Vec<DiffDetails>, + tx: &mpsc::Sender<ArchiveStorageDiffEvent>, + ) -> Result<(), String> { + // Parse the child trie key as `ChildInfo` and `String`. + let maybe_child_trie = items.first().and_then(|item| item.child_trie_key.clone()); + let maybe_child_trie_str = + items.first().and_then(|item| item.child_trie_key_string.clone()); + + // Iterator over the current block and previous block + // at the same time to compare the keys. This approach effectively + // leverages backpressure to avoid memory consumption. + let keys_iter = self.client.raw_keys_iter(hash, maybe_child_trie.clone())?; + let previous_keys_iter = + self.client.raw_keys_iter(previous_hash, maybe_child_trie.clone())?; + + let mut diff_iter = lexicographic_diff(keys_iter, previous_keys_iter); + + while let Some(item) = diff_iter.next() { + let (operation_type, key) = match item { + Diff::Added(key) => (ArchiveStorageDiffOperationType::Added, key), + Diff::Deleted(key) => (ArchiveStorageDiffOperationType::Deleted, key), + Diff::Equal(key) => (ArchiveStorageDiffOperationType::Modified, key), + }; + + let Some(fetch_type) = Self::belongs_to_query(&key, &items) else { + // The key does not belong the the query items. + continue; + }; + + let maybe_result = match operation_type { + ArchiveStorageDiffOperationType::Added => + self.fetch_storage(hash, key.clone(), maybe_child_trie.clone(), fetch_type)?, + ArchiveStorageDiffOperationType::Deleted => self.fetch_storage( + previous_hash, + key.clone(), + maybe_child_trie.clone(), + fetch_type, + )?, + ArchiveStorageDiffOperationType::Modified => { + let Some(storage_result) = self.fetch_storage( hash, - child_key.as_ref(), - self.storage_max_descendant_responses, - ) { - Ok((results, _)) => storage_results.extend(results), - Err(error) => return ArchiveStorageResult::err(error), + key.clone(), + maybe_child_trie.clone(), + fetch_type, + )? + else { + continue + }; + + let Some(previous_storage_result) = self.fetch_storage( + previous_hash, + key.clone(), + maybe_child_trie.clone(), + fetch_type, + )? + else { + continue + }; + + // For modified records we need to check the actual storage values. + if storage_result == previous_storage_result { + continue } + + Some(storage_result) }, - StorageQueryType::DescendantsHashes => { - match self.client.query_iter_pagination( - QueryIter { - query_key: item.key, - ty: IterQueryType::Hash, - pagination_start_key: item.pagination_start_key, - }, - hash, - child_key.as_ref(), - self.storage_max_descendant_responses, - ) { - Ok((results, _)) => storage_results.extend(results), - Err(error) => return ArchiveStorageResult::err(error), - } + }; + + if let Some(storage_result) = maybe_result { + if !Self::send_result( + &tx, + storage_result, + operation_type, + maybe_child_trie_str.clone(), + ) { + return Ok(()) + } + } + } + + Ok(()) + } + + /// This method will iterate over the keys of the main trie or a child trie and fetch the + /// given keys. The fetched keys will be sent to the provided `tx` sender to leverage + /// the backpressure mechanism. + pub async fn handle_trie_queries( + &self, + hash: Block::Hash, + items: Vec<ArchiveStorageDiffItem<String>>, + previous_hash: Block::Hash, + tx: mpsc::Sender<ArchiveStorageDiffEvent>, + ) -> Result<(), tokio::task::JoinError> { + let this = ArchiveStorageDiff { client: self.client.clone() }; + + tokio::task::spawn_blocking(move || { + // Deduplicate the items. + let mut trie_items = match deduplicate_storage_diff_items(items) { + Ok(items) => items, + Err(error) => { + let _ = tx.blocking_send(ArchiveStorageDiffEvent::err(error.to_string())); + return }, }; + // Default to using the main storage trie if no items are provided. + if trie_items.is_empty() { + trie_items.push(Vec::new()); + } + log::trace!(target: LOG_TARGET, "Storage diff deduplicated items: {:?}", trie_items); + + for items in trie_items { + log::trace!( + target: LOG_TARGET, + "handle_trie_queries: hash={:?}, previous_hash={:?}, items={:?}", + hash, + previous_hash, + items + ); + + let result = this.handle_trie_queries_inner(hash, previous_hash, items, &tx); + + if let Err(error) = result { + log::trace!( + target: LOG_TARGET, + "handle_trie_queries: sending error={:?}", + error, + ); + + let _ = tx.blocking_send(ArchiveStorageDiffEvent::err(error)); + + return + } else { + log::trace!( + target: LOG_TARGET, + "handle_trie_queries: sending storage diff done", + ); + } + } + + let _ = tx.blocking_send(ArchiveStorageDiffEvent::StorageDiffDone); + }) + .await?; + + Ok(()) + } +} + +/// The result of the `lexicographic_diff` method. +#[derive(Debug, PartialEq)] +enum Diff<T> { + Added(T), + Deleted(T), + Equal(T), +} + +/// Compare two iterators lexicographically and return the differences. +fn lexicographic_diff<T, LeftIter, RightIter>( + mut left: LeftIter, + mut right: RightIter, +) -> impl Iterator<Item = Diff<T>> +where + T: Ord, + LeftIter: Iterator<Item = T>, + RightIter: Iterator<Item = T>, +{ + let mut a = left.next(); + let mut b = right.next(); + + core::iter::from_fn(move || match (a.take(), b.take()) { + (Some(a_value), Some(b_value)) => + if a_value < b_value { + b = Some(b_value); + a = left.next(); + + Some(Diff::Added(a_value)) + } else if a_value > b_value { + a = Some(a_value); + b = right.next(); + + Some(Diff::Deleted(b_value)) + } else { + a = left.next(); + b = right.next(); + + Some(Diff::Equal(a_value)) + }, + (Some(a_value), None) => { + a = left.next(); + Some(Diff::Added(a_value)) + }, + (None, Some(b_value)) => { + b = right.next(); + Some(Diff::Deleted(b_value)) + }, + (None, None) => None, + }) +} + +/// Deduplicate the provided items and return a list of `DiffDetails`. +/// +/// Each list corresponds to a single child trie or the main trie. +fn deduplicate_storage_diff_items( + items: Vec<ArchiveStorageDiffItem<String>>, +) -> Result<Vec<Vec<DiffDetails>>, ArchiveError> { + let mut deduplicated: HashMap<Option<ChildInfo>, Vec<DiffDetails>> = HashMap::new(); + + for diff_item in items { + // Ensure the provided hex keys are valid before deduplication. + let key = StorageKey(parse_hex_param(diff_item.key)?); + let child_trie_key_string = diff_item.child_trie_key.clone(); + let child_trie_key = diff_item + .child_trie_key + .map(|child_trie_key| parse_hex_param(child_trie_key)) + .transpose()? + .map(ChildInfo::new_default_from_vec); + + let diff_item = DiffDetails { + key, + return_type: diff_item.return_type, + child_trie_key: child_trie_key.clone(), + child_trie_key_string, + }; + + match deduplicated.entry(child_trie_key.clone()) { + Entry::Occupied(mut entry) => { + let mut should_insert = true; + + for existing in entry.get() { + // This points to a different return type. + if existing.return_type != diff_item.return_type { + continue + } + // Keys and return types are identical. + if existing.key == diff_item.key { + should_insert = false; + break + } + + // The following two conditions ensure that we keep the shortest key. + + // The current key is a longer prefix of the existing key. + if diff_item.key.as_ref().starts_with(&existing.key.as_ref()) { + should_insert = false; + break + } + + // The existing key is a longer prefix of the current key. + // We need to keep the current key and remove the existing one. + if existing.key.as_ref().starts_with(&diff_item.key.as_ref()) { + let to_remove = existing.clone(); + entry.get_mut().retain(|item| item != &to_remove); + break; + } + } + + if should_insert { + entry.get_mut().push(diff_item); + } + }, + Entry::Vacant(entry) => { + entry.insert(vec![diff_item]); + }, } + } + + Ok(deduplicated + .into_iter() + .sorted_by_key(|(child_trie_key, _)| child_trie_key.clone()) + .map(|(_, values)| values) + .collect()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn dedup_empty() { + let items = vec![]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert!(result.is_empty()); + } + + #[test] + fn dedup_single() { + let items = vec![ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].len(), 1); + + let expected = DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + child_trie_key_string: None, + }; + assert_eq!(result[0][0], expected); + } + + #[test] + fn dedup_with_different_keys() { + let items = vec![ + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ArchiveStorageDiffItem { + key: "0x02".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].len(), 2); + + let expected = vec![ + DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + child_trie_key_string: None, + }, + DiffDetails { + key: StorageKey(vec![2]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + child_trie_key_string: None, + }, + ]; + assert_eq!(result[0], expected); + } + + #[test] + fn dedup_with_same_keys() { + // Identical keys. + let items = vec![ + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].len(), 1); + + let expected = vec![DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + child_trie_key_string: None, + }]; + assert_eq!(result[0], expected); + } + + #[test] + fn dedup_with_same_prefix() { + // Identical keys. + let items = vec![ + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ArchiveStorageDiffItem { + key: "0x01ff".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].len(), 1); + + let expected = vec![DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + child_trie_key_string: None, + }]; + assert_eq!(result[0], expected); + } + + #[test] + fn dedup_with_different_return_types() { + let items = vec![ + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: None, + }, + ]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].len(), 2); + + let expected = vec![ + DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + child_trie_key_string: None, + }, + DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: None, + child_trie_key_string: None, + }, + ]; + assert_eq!(result[0], expected); + } + + #[test] + fn dedup_with_different_child_tries() { + let items = vec![ + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some("0x01".into()), + }, + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some("0x02".into()), + }, + ]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert_eq!(result.len(), 2); + assert_eq!(result[0].len(), 1); + assert_eq!(result[1].len(), 1); + + let expected = vec![ + vec![DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some(ChildInfo::new_default_from_vec(vec![1])), + child_trie_key_string: Some("0x01".into()), + }], + vec![DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some(ChildInfo::new_default_from_vec(vec![2])), + child_trie_key_string: Some("0x02".into()), + }], + ]; + assert_eq!(result, expected); + } + + #[test] + fn dedup_with_same_child_tries() { + let items = vec![ + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some("0x01".into()), + }, + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some("0x01".into()), + }, + ]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].len(), 1); + + let expected = vec![DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some(ChildInfo::new_default_from_vec(vec![1])), + child_trie_key_string: Some("0x01".into()), + }]; + assert_eq!(result[0], expected); + } + + #[test] + fn dedup_with_shorter_key_reverse_order() { + let items = vec![ + ArchiveStorageDiffItem { + key: "0x01ff".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].len(), 1); + + let expected = vec![DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + child_trie_key_string: None, + }]; + assert_eq!(result[0], expected); + } + + #[test] + fn dedup_multiple_child_tries() { + let items = vec![ + ArchiveStorageDiffItem { + key: "0x02".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some("0x01".into()), + }, + ArchiveStorageDiffItem { + key: "0x02".into(), + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: Some("0x01".into()), + }, + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some("0x02".into()), + }, + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: Some("0x02".into()), + }, + ArchiveStorageDiffItem { + key: "0x01ff".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some("0x02".into()), + }, + ]; + + let result = deduplicate_storage_diff_items(items).unwrap(); + + let expected = vec![ + vec![DiffDetails { + key: StorageKey(vec![2]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + child_trie_key_string: None, + }], + vec![ + DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some(ChildInfo::new_default_from_vec(vec![1])), + child_trie_key_string: Some("0x01".into()), + }, + DiffDetails { + key: StorageKey(vec![2]), + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: Some(ChildInfo::new_default_from_vec(vec![1])), + child_trie_key_string: Some("0x01".into()), + }, + ], + vec![ + DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some(ChildInfo::new_default_from_vec(vec![2])), + child_trie_key_string: Some("0x02".into()), + }, + DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: Some(ChildInfo::new_default_from_vec(vec![2])), + child_trie_key_string: Some("0x02".into()), + }, + ], + ]; + + assert_eq!(result, expected); + } + + #[test] + fn test_lexicographic_diff() { + let left = vec![1, 2, 3, 4, 5]; + let right = vec![2, 3, 4, 5, 6]; + + let diff = lexicographic_diff(left.into_iter(), right.into_iter()).collect::<Vec<_>>(); + let expected = vec![ + Diff::Added(1), + Diff::Equal(2), + Diff::Equal(3), + Diff::Equal(4), + Diff::Equal(5), + Diff::Deleted(6), + ]; + assert_eq!(diff, expected); + } + + #[test] + fn test_lexicographic_diff_one_side_empty() { + let left = vec![]; + let right = vec![1, 2, 3, 4, 5, 6]; + + let diff = lexicographic_diff(left.into_iter(), right.into_iter()).collect::<Vec<_>>(); + let expected = vec![ + Diff::Deleted(1), + Diff::Deleted(2), + Diff::Deleted(3), + Diff::Deleted(4), + Diff::Deleted(5), + Diff::Deleted(6), + ]; + assert_eq!(diff, expected); + + let left = vec![1, 2, 3, 4, 5, 6]; + let right = vec![]; - ArchiveStorageResult::ok(storage_results, discarded_items) + let diff = lexicographic_diff(left.into_iter(), right.into_iter()).collect::<Vec<_>>(); + let expected = vec![ + Diff::Added(1), + Diff::Added(2), + Diff::Added(3), + Diff::Added(4), + Diff::Added(5), + Diff::Added(6), + ]; + assert_eq!(diff, expected); } } diff --git a/substrate/client/rpc-spec-v2/src/archive/mod.rs b/substrate/client/rpc-spec-v2/src/archive/mod.rs index 5f020c203eab4fba2bb20e5a7314ded1092e9b6c..14fa104c113a476562034e2241e223499d4d865a 100644 --- a/substrate/client/rpc-spec-v2/src/archive/mod.rs +++ b/substrate/client/rpc-spec-v2/src/archive/mod.rs @@ -32,4 +32,4 @@ pub mod archive; pub mod error; pub use api::ArchiveApiServer; -pub use archive::{Archive, ArchiveConfig}; +pub use archive::Archive; diff --git a/substrate/client/rpc-spec-v2/src/archive/tests.rs b/substrate/client/rpc-spec-v2/src/archive/tests.rs index 078016f5b3e210311c80c26fc6afaf587a1780dd..48cbbaa4934aa8d575279fe47128aaefdc8497be 100644 --- a/substrate/client/rpc-spec-v2/src/archive/tests.rs +++ b/substrate/client/rpc-spec-v2/src/archive/tests.rs @@ -18,24 +18,25 @@ use crate::{ common::events::{ - ArchiveStorageMethodOk, ArchiveStorageResult, PaginatedStorageQuery, StorageQueryType, - StorageResultType, + ArchiveStorageDiffEvent, ArchiveStorageDiffItem, ArchiveStorageDiffOperationType, + ArchiveStorageDiffResult, ArchiveStorageDiffType, ArchiveStorageEvent, StorageQuery, + StorageQueryType, StorageResult, StorageResultType, }, hex_string, MethodResult, }; -use super::{ - archive::{Archive, ArchiveConfig}, - *, -}; +use super::{archive::Archive, *}; use assert_matches::assert_matches; use codec::{Decode, Encode}; use jsonrpsee::{ - core::EmptyServerParams as EmptyParams, rpc_params, MethodsError as Error, RpcModule, + core::{server::Subscription as RpcSubscription, EmptyServerParams as EmptyParams}, + rpc_params, MethodsError as Error, RpcModule, }; + use sc_block_builder::BlockBuilderBuilder; use sc_client_api::ChildInfo; +use sc_rpc::testing::TokioTestExecutor; use sp_blockchain::HeaderBackend; use sp_consensus::BlockOrigin; use sp_core::{Blake2Hasher, Hasher}; @@ -51,8 +52,6 @@ use substrate_test_runtime_client::{ const CHAIN_GENESIS: [u8; 32] = [0; 32]; const INVALID_HASH: [u8; 32] = [1; 32]; -const MAX_PAGINATION_LIMIT: usize = 5; -const MAX_QUERIED_LIMIT: usize = 5; const KEY: &[u8] = b":mock"; const VALUE: &[u8] = b"hello world"; const CHILD_STORAGE_KEY: &[u8] = b"child"; @@ -61,10 +60,7 @@ const CHILD_VALUE: &[u8] = b"child value"; type Header = substrate_test_runtime_client::runtime::Header; type Block = substrate_test_runtime_client::runtime::Block; -fn setup_api( - max_descendant_responses: usize, - max_queried_items: usize, -) -> (Arc<Client<Backend>>, RpcModule<Archive<Backend, Block, Client<Backend>>>) { +fn setup_api() -> (Arc<Client<Backend>>, RpcModule<Archive<Backend, Block, Client<Backend>>>) { let child_info = ChildInfo::new_default(CHILD_STORAGE_KEY); let builder = TestClientBuilder::new().add_extra_child_storage( &child_info, @@ -78,16 +74,25 @@ fn setup_api( client.clone(), backend, CHAIN_GENESIS, - ArchiveConfig { max_descendant_responses, max_queried_items }, + Arc::new(TokioTestExecutor::default()), ) .into_rpc(); (client, api) } +async fn get_next_event<T: serde::de::DeserializeOwned>(sub: &mut RpcSubscription) -> T { + let (event, _sub_id) = tokio::time::timeout(std::time::Duration::from_secs(60), sub.next()) + .await + .unwrap() + .unwrap() + .unwrap(); + event +} + #[tokio::test] async fn archive_genesis() { - let (_client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (_client, api) = setup_api(); let genesis: String = api.call("archive_unstable_genesisHash", EmptyParams::new()).await.unwrap(); @@ -96,7 +101,7 @@ async fn archive_genesis() { #[tokio::test] async fn archive_body() { - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); // Invalid block hash. let invalid_hash = hex_string(&INVALID_HASH); @@ -112,8 +117,8 @@ async fn archive_body() { builder .push_transfer(runtime::Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 42, nonce: 0, }) @@ -130,7 +135,7 @@ async fn archive_body() { #[tokio::test] async fn archive_header() { - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); // Invalid block hash. let invalid_hash = hex_string(&INVALID_HASH); @@ -146,8 +151,8 @@ async fn archive_header() { builder .push_transfer(runtime::Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 42, nonce: 0, }) @@ -164,7 +169,7 @@ async fn archive_header() { #[tokio::test] async fn archive_finalized_height() { - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); let client_height: u32 = client.info().finalized_number.saturated_into(); @@ -176,7 +181,7 @@ async fn archive_finalized_height() { #[tokio::test] async fn archive_hash_by_height() { - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); // Genesis height. let hashes: Vec<String> = api.call("archive_unstable_hashByHeight", [0]).await.unwrap(); @@ -244,8 +249,8 @@ async fn archive_hash_by_height() { // imported block_builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -282,7 +287,7 @@ async fn archive_hash_by_height() { #[tokio::test] async fn archive_call() { - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); let invalid_hash = hex_string(&INVALID_HASH); // Invalid parameter (non-hex). @@ -325,7 +330,7 @@ async fn archive_call() { client.import(BlockOrigin::Own, block_1.clone()).await.unwrap(); // Valid call. - let alice_id = AccountKeyring::Alice.to_account_id(); + let alice_id = Sr25519Keyring::Alice.to_account_id(); // Hex encoded scale encoded bytes representing the call parameters. let call_parameters = hex_string(&alice_id.encode()); let result: MethodResult = api @@ -341,7 +346,7 @@ async fn archive_call() { #[tokio::test] async fn archive_storage_hashes_values() { - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); let block = BlockBuilderBuilder::new(&*client) .on_parent_block(client.chain_info().genesis_hash) @@ -355,42 +360,23 @@ async fn archive_storage_hashes_values() { let block_hash = format!("{:?}", block.header.hash()); let key = hex_string(&KEY); - let items: Vec<PaginatedStorageQuery<String>> = vec![ - PaginatedStorageQuery { - key: key.clone(), - query_type: StorageQueryType::DescendantsHashes, - pagination_start_key: None, - }, - PaginatedStorageQuery { - key: key.clone(), - query_type: StorageQueryType::DescendantsValues, - pagination_start_key: None, - }, - PaginatedStorageQuery { - key: key.clone(), - query_type: StorageQueryType::Hash, - pagination_start_key: None, - }, - PaginatedStorageQuery { - key: key.clone(), - query_type: StorageQueryType::Value, - pagination_start_key: None, - }, + let items: Vec<StorageQuery<String>> = vec![ + StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsHashes }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsValues }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::Value }, ]; - let result: ArchiveStorageResult = api - .call("archive_unstable_storage", rpc_params![&block_hash, items.clone()]) + let mut sub = api + .subscribe_unbounded("archive_unstable_storage", rpc_params![&block_hash, items.clone()]) .await .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - // Key has not been imported yet. - assert_eq!(result.len(), 0); - assert_eq!(discarded_items, 0); - }, - _ => panic!("Unexpected result"), - }; + // Key has not been imported yet. + assert_eq!( + get_next_event::<ArchiveStorageEvent>(&mut sub).await, + ArchiveStorageEvent::StorageDone, + ); // Import a block with the given key value pair. let mut builder = BlockBuilderBuilder::new(&*client) @@ -406,32 +392,103 @@ async fn archive_storage_hashes_values() { let expected_hash = format!("{:?}", Blake2Hasher::hash(&VALUE)); let expected_value = hex_string(&VALUE); - let result: ArchiveStorageResult = api - .call("archive_unstable_storage", rpc_params![&block_hash, items]) + let mut sub = api + .subscribe_unbounded("archive_unstable_storage", rpc_params![&block_hash, items]) .await .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - assert_eq!(result.len(), 4); - assert_eq!(discarded_items, 0); - - assert_eq!(result[0].key, key); - assert_eq!(result[0].result, StorageResultType::Hash(expected_hash.clone())); - assert_eq!(result[1].key, key); - assert_eq!(result[1].result, StorageResultType::Value(expected_value.clone())); - assert_eq!(result[2].key, key); - assert_eq!(result[2].result, StorageResultType::Hash(expected_hash)); - assert_eq!(result[3].key, key); - assert_eq!(result[3].result, StorageResultType::Value(expected_value)); - }, - _ => panic!("Unexpected result"), - }; + assert_eq!( + get_next_event::<ArchiveStorageEvent>(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: key.clone(), + result: StorageResultType::Hash(expected_hash.clone()), + child_trie_key: None, + }), + ); + + assert_eq!( + get_next_event::<ArchiveStorageEvent>(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: key.clone(), + result: StorageResultType::Value(expected_value.clone()), + child_trie_key: None, + }), + ); + + assert_eq!( + get_next_event::<ArchiveStorageEvent>(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: key.clone(), + result: StorageResultType::Hash(expected_hash), + child_trie_key: None, + }), + ); + + assert_eq!( + get_next_event::<ArchiveStorageEvent>(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: key.clone(), + result: StorageResultType::Value(expected_value), + child_trie_key: None, + }), + ); + + assert_matches!( + get_next_event::<ArchiveStorageEvent>(&mut sub).await, + ArchiveStorageEvent::StorageDone + ); +} + +#[tokio::test] +async fn archive_storage_hashes_values_child_trie() { + let (client, api) = setup_api(); + + // Get child storage values set in `setup_api`. + let child_info = hex_string(&CHILD_STORAGE_KEY); + let key = hex_string(&KEY); + let genesis_hash = format!("{:?}", client.genesis_hash()); + let expected_hash = format!("{:?}", Blake2Hasher::hash(&CHILD_VALUE)); + let expected_value = hex_string(&CHILD_VALUE); + + let items: Vec<StorageQuery<String>> = vec![ + StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsHashes }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsValues }, + ]; + let mut sub = api + .subscribe_unbounded( + "archive_unstable_storage", + rpc_params![&genesis_hash, items, &child_info], + ) + .await + .unwrap(); + + assert_eq!( + get_next_event::<ArchiveStorageEvent>(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: key.clone(), + result: StorageResultType::Hash(expected_hash.clone()), + child_trie_key: Some(child_info.clone()), + }) + ); + + assert_eq!( + get_next_event::<ArchiveStorageEvent>(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: key.clone(), + result: StorageResultType::Value(expected_value.clone()), + child_trie_key: Some(child_info.clone()), + }) + ); + + assert_eq!( + get_next_event::<ArchiveStorageEvent>(&mut sub).await, + ArchiveStorageEvent::StorageDone, + ); } #[tokio::test] async fn archive_storage_closest_merkle_value() { - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); /// The core of this test. /// @@ -443,55 +500,47 @@ async fn archive_storage_closest_merkle_value() { api: &RpcModule<Archive<Backend, Block, Client<Backend>>>, block_hash: String, ) -> HashMap<String, String> { - let result: ArchiveStorageResult = api - .call( + let mut sub = api + .subscribe_unbounded( "archive_unstable_storage", rpc_params![ &block_hash, vec![ - PaginatedStorageQuery { + StorageQuery { key: hex_string(b":AAAA"), query_type: StorageQueryType::ClosestDescendantMerkleValue, - pagination_start_key: None, }, - PaginatedStorageQuery { + StorageQuery { key: hex_string(b":AAAB"), query_type: StorageQueryType::ClosestDescendantMerkleValue, - pagination_start_key: None, }, // Key with descendant. - PaginatedStorageQuery { + StorageQuery { key: hex_string(b":A"), query_type: StorageQueryType::ClosestDescendantMerkleValue, - pagination_start_key: None, }, - PaginatedStorageQuery { + StorageQuery { key: hex_string(b":AA"), query_type: StorageQueryType::ClosestDescendantMerkleValue, - pagination_start_key: None, }, // Keys below this comment do not produce a result. // Key that exceed the keyspace of the trie. - PaginatedStorageQuery { + StorageQuery { key: hex_string(b":AAAAX"), query_type: StorageQueryType::ClosestDescendantMerkleValue, - pagination_start_key: None, }, - PaginatedStorageQuery { + StorageQuery { key: hex_string(b":AAABX"), query_type: StorageQueryType::ClosestDescendantMerkleValue, - pagination_start_key: None, }, // Key that are not part of the trie. - PaginatedStorageQuery { + StorageQuery { key: hex_string(b":AAX"), query_type: StorageQueryType::ClosestDescendantMerkleValue, - pagination_start_key: None, }, - PaginatedStorageQuery { + StorageQuery { key: hex_string(b":AAAX"), query_type: StorageQueryType::ClosestDescendantMerkleValue, - pagination_start_key: None, }, ] ], @@ -499,19 +548,21 @@ async fn archive_storage_closest_merkle_value() { .await .unwrap(); - let merkle_values: HashMap<_, _> = match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, .. }) => result - .into_iter() - .map(|res| { - let value = match res.result { + let mut merkle_values = HashMap::new(); + loop { + let event = get_next_event::<ArchiveStorageEvent>(&mut sub).await; + match event { + ArchiveStorageEvent::Storage(result) => { + let str_result = match result.result { StorageResultType::ClosestDescendantMerkleValue(value) => value, - _ => panic!("Unexpected StorageResultType"), + _ => panic!("Unexpected result type"), }; - (res.key, value) - }) - .collect(), - _ => panic!("Unexpected result"), - }; + merkle_values.insert(result.key, str_result); + }, + ArchiveStorageEvent::StorageError(err) => panic!("Unexpected error {err:?}"), + ArchiveStorageEvent::StorageDone => break, + } + } // Response for AAAA, AAAB, A and AA. assert_eq!(merkle_values.len(), 4); @@ -590,9 +641,9 @@ async fn archive_storage_closest_merkle_value() { } #[tokio::test] -async fn archive_storage_paginate_iterations() { +async fn archive_storage_iterations() { // 1 iteration allowed before pagination kicks in. - let (client, api) = setup_api(1, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); // Import a new block with storage changes. let mut builder = BlockBuilderBuilder::new(&*client) @@ -611,230 +662,344 @@ async fn archive_storage_paginate_iterations() { // Calling with an invalid hash. let invalid_hash = hex_string(&INVALID_HASH); - let result: ArchiveStorageResult = api - .call( + let mut sub = api + .subscribe_unbounded( "archive_unstable_storage", rpc_params![ &invalid_hash, - vec![PaginatedStorageQuery { + vec![StorageQuery { key: hex_string(b":m"), query_type: StorageQueryType::DescendantsValues, - pagination_start_key: None, }] ], ) .await .unwrap(); - match result { - ArchiveStorageResult::Err(_) => (), - _ => panic!("Unexpected result"), - }; + + assert_matches!( + get_next_event::<ArchiveStorageEvent>(&mut sub).await, + ArchiveStorageEvent::StorageError(_) + ); // Valid call with storage at the key. - let result: ArchiveStorageResult = api - .call( + let mut sub = api + .subscribe_unbounded( "archive_unstable_storage", rpc_params![ &block_hash, - vec![PaginatedStorageQuery { + vec![StorageQuery { key: hex_string(b":m"), query_type: StorageQueryType::DescendantsValues, - pagination_start_key: None, }] ], ) .await .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - assert_eq!(result.len(), 1); - assert_eq!(discarded_items, 0); - assert_eq!(result[0].key, hex_string(b":m")); - assert_eq!(result[0].result, StorageResultType::Value(hex_string(b"a"))); - }, - _ => panic!("Unexpected result"), - }; + assert_eq!( + get_next_event::<ArchiveStorageEvent>(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":m"), + result: StorageResultType::Value(hex_string(b"a")), + child_trie_key: None, + }) + ); - // Continue with pagination. - let result: ArchiveStorageResult = api - .call( - "archive_unstable_storage", - rpc_params![ - &block_hash, - vec![PaginatedStorageQuery { - key: hex_string(b":m"), - query_type: StorageQueryType::DescendantsValues, - pagination_start_key: Some(hex_string(b":m")), - }] - ], - ) - .await - .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - assert_eq!(result.len(), 1); - assert_eq!(discarded_items, 0); + assert_eq!( + get_next_event::<ArchiveStorageEvent>(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":mo"), + result: StorageResultType::Value(hex_string(b"ab")), + child_trie_key: None, + }) + ); - assert_eq!(result[0].key, hex_string(b":mo")); - assert_eq!(result[0].result, StorageResultType::Value(hex_string(b"ab"))); - }, - _ => panic!("Unexpected result"), - }; + assert_eq!( + get_next_event::<ArchiveStorageEvent>(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":moD"), + result: StorageResultType::Value(hex_string(b"abcmoD")), + child_trie_key: None, + }) + ); - // Continue with pagination. - let result: ArchiveStorageResult = api - .call( - "archive_unstable_storage", - rpc_params![ - &block_hash, - vec![PaginatedStorageQuery { - key: hex_string(b":m"), - query_type: StorageQueryType::DescendantsValues, - pagination_start_key: Some(hex_string(b":mo")), - }] - ], - ) - .await + assert_eq!( + get_next_event::<ArchiveStorageEvent>(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":moc"), + result: StorageResultType::Value(hex_string(b"abc")), + child_trie_key: None, + }) + ); + + assert_eq!( + get_next_event::<ArchiveStorageEvent>(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":mock"), + result: StorageResultType::Value(hex_string(b"abcd")), + child_trie_key: None, + }) + ); + + assert_matches!( + get_next_event::<ArchiveStorageEvent>(&mut sub).await, + ArchiveStorageEvent::StorageDone + ); +} + +#[tokio::test] +async fn archive_storage_diff_main_trie() { + let (client, api) = setup_api(); + + let mut builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().genesis_hash) + .with_parent_block_number(0) + .build() .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - assert_eq!(result.len(), 1); - assert_eq!(discarded_items, 0); + builder.push_storage_change(b":A".to_vec(), Some(b"B".to_vec())).unwrap(); + builder.push_storage_change(b":AA".to_vec(), Some(b"BB".to_vec())).unwrap(); + let prev_block = builder.build().unwrap().block; + let prev_hash = format!("{:?}", prev_block.header.hash()); + client.import(BlockOrigin::Own, prev_block.clone()).await.unwrap(); - assert_eq!(result[0].key, hex_string(b":moD")); - assert_eq!(result[0].result, StorageResultType::Value(hex_string(b"abcmoD"))); - }, - _ => panic!("Unexpected result"), - }; + let mut builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(prev_block.hash()) + .with_parent_block_number(1) + .build() + .unwrap(); + builder.push_storage_change(b":A".to_vec(), Some(b"11".to_vec())).unwrap(); + builder.push_storage_change(b":AA".to_vec(), Some(b"22".to_vec())).unwrap(); + builder.push_storage_change(b":AAA".to_vec(), Some(b"222".to_vec())).unwrap(); + let block = builder.build().unwrap().block; + let block_hash = format!("{:?}", block.header.hash()); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); - // Continue with pagination. - let result: ArchiveStorageResult = api - .call( - "archive_unstable_storage", - rpc_params![ - &block_hash, - vec![PaginatedStorageQuery { - key: hex_string(b":m"), - query_type: StorageQueryType::DescendantsValues, - pagination_start_key: Some(hex_string(b":moD")), - }] - ], + // Search for items in the main trie: + // - values of keys under ":A" + // - hashes of keys under ":AA" + let items = vec![ + ArchiveStorageDiffItem::<String> { + key: hex_string(b":A"), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ArchiveStorageDiffItem::<String> { + key: hex_string(b":AA"), + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: None, + }, + ]; + let mut sub = api + .subscribe_unbounded( + "archive_unstable_storageDiff", + rpc_params![&block_hash, items.clone(), &prev_hash], ) .await .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - assert_eq!(result.len(), 1); - assert_eq!(discarded_items, 0); - assert_eq!(result[0].key, hex_string(b":moc")); - assert_eq!(result[0].result, StorageResultType::Value(hex_string(b"abc"))); - }, - _ => panic!("Unexpected result"), - }; + let event = get_next_event::<ArchiveStorageDiffEvent>(&mut sub).await; + assert_eq!( + ArchiveStorageDiffEvent::StorageDiff(ArchiveStorageDiffResult { + key: hex_string(b":A"), + result: StorageResultType::Value(hex_string(b"11")), + operation_type: ArchiveStorageDiffOperationType::Modified, + child_trie_key: None, + }), + event, + ); - // Continue with pagination. - let result: ArchiveStorageResult = api - .call( - "archive_unstable_storage", - rpc_params![ - &block_hash, - vec![PaginatedStorageQuery { - key: hex_string(b":m"), - query_type: StorageQueryType::DescendantsValues, - pagination_start_key: Some(hex_string(b":moc")), - }] - ], - ) - .await + let event = get_next_event::<ArchiveStorageDiffEvent>(&mut sub).await; + assert_eq!( + ArchiveStorageDiffEvent::StorageDiff(ArchiveStorageDiffResult { + key: hex_string(b":AA"), + result: StorageResultType::Value(hex_string(b"22")), + operation_type: ArchiveStorageDiffOperationType::Modified, + child_trie_key: None, + }), + event, + ); + + let event = get_next_event::<ArchiveStorageDiffEvent>(&mut sub).await; + assert_eq!( + ArchiveStorageDiffEvent::StorageDiff(ArchiveStorageDiffResult { + key: hex_string(b":AA"), + result: StorageResultType::Hash(format!("{:?}", Blake2Hasher::hash(b"22"))), + operation_type: ArchiveStorageDiffOperationType::Modified, + child_trie_key: None, + }), + event, + ); + + // Added key. + let event = get_next_event::<ArchiveStorageDiffEvent>(&mut sub).await; + assert_eq!( + ArchiveStorageDiffEvent::StorageDiff(ArchiveStorageDiffResult { + key: hex_string(b":AAA"), + result: StorageResultType::Value(hex_string(b"222")), + operation_type: ArchiveStorageDiffOperationType::Added, + child_trie_key: None, + }), + event, + ); + + let event = get_next_event::<ArchiveStorageDiffEvent>(&mut sub).await; + assert_eq!( + ArchiveStorageDiffEvent::StorageDiff(ArchiveStorageDiffResult { + key: hex_string(b":AAA"), + result: StorageResultType::Hash(format!("{:?}", Blake2Hasher::hash(b"222"))), + operation_type: ArchiveStorageDiffOperationType::Added, + child_trie_key: None, + }), + event, + ); + + let event = get_next_event::<ArchiveStorageDiffEvent>(&mut sub).await; + assert_eq!(ArchiveStorageDiffEvent::StorageDiffDone, event); +} + +#[tokio::test] +async fn archive_storage_diff_no_changes() { + let (client, api) = setup_api(); + + // Build 2 identical blocks. + let mut builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().genesis_hash) + .with_parent_block_number(0) + .build() .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - assert_eq!(result.len(), 1); - assert_eq!(discarded_items, 0); + builder.push_storage_change(b":A".to_vec(), Some(b"B".to_vec())).unwrap(); + builder.push_storage_change(b":AA".to_vec(), Some(b"BB".to_vec())).unwrap(); + builder.push_storage_change(b":B".to_vec(), Some(b"CC".to_vec())).unwrap(); + builder.push_storage_change(b":BA".to_vec(), Some(b"CC".to_vec())).unwrap(); + let prev_block = builder.build().unwrap().block; + let prev_hash = format!("{:?}", prev_block.header.hash()); + client.import(BlockOrigin::Own, prev_block.clone()).await.unwrap(); - assert_eq!(result[0].key, hex_string(b":mock")); - assert_eq!(result[0].result, StorageResultType::Value(hex_string(b"abcd"))); - }, - _ => panic!("Unexpected result"), - }; + let mut builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(prev_block.hash()) + .with_parent_block_number(1) + .build() + .unwrap(); + builder.push_storage_change(b":A".to_vec(), Some(b"B".to_vec())).unwrap(); + builder.push_storage_change(b":AA".to_vec(), Some(b"BB".to_vec())).unwrap(); + let block = builder.build().unwrap().block; + let block_hash = format!("{:?}", block.header.hash()); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); - // Continue with pagination until no keys are returned. - let result: ArchiveStorageResult = api - .call( - "archive_unstable_storage", - rpc_params![ - &block_hash, - vec![PaginatedStorageQuery { - key: hex_string(b":m"), - query_type: StorageQueryType::DescendantsValues, - pagination_start_key: Some(hex_string(b":mock")), - }] - ], + // Search for items in the main trie with keys prefixed with ":A". + let items = vec![ArchiveStorageDiffItem::<String> { + key: hex_string(b":A"), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }]; + let mut sub = api + .subscribe_unbounded( + "archive_unstable_storageDiff", + rpc_params![&block_hash, items.clone(), &prev_hash], ) .await .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - assert_eq!(result.len(), 0); - assert_eq!(discarded_items, 0); - }, - _ => panic!("Unexpected result"), - }; + + let event = get_next_event::<ArchiveStorageDiffEvent>(&mut sub).await; + assert_eq!(ArchiveStorageDiffEvent::StorageDiffDone, event); } #[tokio::test] -async fn archive_storage_discarded_items() { - // One query at a time - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, 1); +async fn archive_storage_diff_deleted_changes() { + let (client, api) = setup_api(); - // Import a new block with storage changes. + // Blocks are imported as forks. let mut builder = BlockBuilderBuilder::new(&*client) .on_parent_block(client.chain_info().genesis_hash) .with_parent_block_number(0) .build() .unwrap(); - builder.push_storage_change(b":m".to_vec(), Some(b"a".to_vec())).unwrap(); + builder.push_storage_change(b":A".to_vec(), Some(b"B".to_vec())).unwrap(); + builder.push_storage_change(b":AA".to_vec(), Some(b"BB".to_vec())).unwrap(); + builder.push_storage_change(b":B".to_vec(), Some(b"CC".to_vec())).unwrap(); + builder.push_storage_change(b":BA".to_vec(), Some(b"CC".to_vec())).unwrap(); + let prev_block = builder.build().unwrap().block; + let prev_hash = format!("{:?}", prev_block.header.hash()); + client.import(BlockOrigin::Own, prev_block.clone()).await.unwrap(); + + let mut builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().genesis_hash) + .with_parent_block_number(0) + .build() + .unwrap(); + builder + .push_transfer(Transfer { + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), + amount: 41, + nonce: 0, + }) + .unwrap(); + builder.push_storage_change(b":A".to_vec(), Some(b"B".to_vec())).unwrap(); let block = builder.build().unwrap().block; let block_hash = format!("{:?}", block.header.hash()); client.import(BlockOrigin::Own, block.clone()).await.unwrap(); - // Valid call with storage at the key. - let result: ArchiveStorageResult = api - .call( - "archive_unstable_storage", - rpc_params![ - &block_hash, - vec![ - PaginatedStorageQuery { - key: hex_string(b":m"), - query_type: StorageQueryType::Value, - pagination_start_key: None, - }, - PaginatedStorageQuery { - key: hex_string(b":m"), - query_type: StorageQueryType::Hash, - pagination_start_key: None, - }, - PaginatedStorageQuery { - key: hex_string(b":m"), - query_type: StorageQueryType::Hash, - pagination_start_key: None, - } - ] - ], + // Search for items in the main trie with keys prefixed with ":A". + let items = vec![ArchiveStorageDiffItem::<String> { + key: hex_string(b":A"), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }]; + + let mut sub = api + .subscribe_unbounded( + "archive_unstable_storageDiff", + rpc_params![&block_hash, items.clone(), &prev_hash], ) .await .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - assert_eq!(result.len(), 1); - assert_eq!(discarded_items, 2); - assert_eq!(result[0].key, hex_string(b":m")); - assert_eq!(result[0].result, StorageResultType::Value(hex_string(b"a"))); - }, - _ => panic!("Unexpected result"), - }; + let event = get_next_event::<ArchiveStorageDiffEvent>(&mut sub).await; + assert_eq!( + ArchiveStorageDiffEvent::StorageDiff(ArchiveStorageDiffResult { + key: hex_string(b":AA"), + result: StorageResultType::Value(hex_string(b"BB")), + operation_type: ArchiveStorageDiffOperationType::Deleted, + child_trie_key: None, + }), + event, + ); + + let event = get_next_event::<ArchiveStorageDiffEvent>(&mut sub).await; + assert_eq!(ArchiveStorageDiffEvent::StorageDiffDone, event); +} + +#[tokio::test] +async fn archive_storage_diff_invalid_params() { + let invalid_hash = hex_string(&INVALID_HASH); + let (_, api) = setup_api(); + + // Invalid shape for parameters. + let items: Vec<ArchiveStorageDiffItem<String>> = Vec::new(); + let err = api + .subscribe_unbounded( + "archive_unstable_storageDiff", + rpc_params!["123", items.clone(), &invalid_hash], + ) + .await + .unwrap_err(); + assert_matches!(err, + Error::JsonRpc(ref err) if err.code() == crate::chain_head::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message() == "Invalid params" + ); + + // The shape is right, but the block hash is invalid. + let items: Vec<ArchiveStorageDiffItem<String>> = Vec::new(); + let mut sub = api + .subscribe_unbounded( + "archive_unstable_storageDiff", + rpc_params![&invalid_hash, items.clone(), &invalid_hash], + ) + .await + .unwrap(); + + let event = get_next_event::<ArchiveStorageDiffEvent>(&mut sub).await; + assert_matches!(event, + ArchiveStorageDiffEvent::StorageDiffError(ref err) if err.error.contains("Header was not found") + ); } diff --git a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs index 61eb47d1b9abac4c2baa27ea13347adc977946c2..b949fb25402bf0f9af06b8b8863f6da4669d1d96 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs @@ -318,7 +318,7 @@ where }), }; - let (rp, rp_fut) = method_started_response(operation_id); + let (rp, rp_fut) = method_started_response(operation_id, None); let fut = async move { // Wait for the server to send out the response and if it produces an error no event // should be generated. @@ -432,7 +432,8 @@ where let mut storage_client = ChainHeadStorage::<Client, Block, BE>::new(self.client.clone()); - let (rp, rp_fut) = method_started_response(block_guard.operation().operation_id()); + // Storage items are never discarded. + let (rp, rp_fut) = method_started_response(block_guard.operation().operation_id(), Some(0)); let fut = async move { // Wait for the server to send out the response and if it produces an error no event @@ -507,7 +508,7 @@ where let operation_id = block_guard.operation().operation_id(); let client = self.client.clone(); - let (rp, rp_fut) = method_started_response(operation_id.clone()); + let (rp, rp_fut) = method_started_response(operation_id.clone(), None); let fut = async move { // Wait for the server to send out the response and if it produces an error no event // should be generated. @@ -630,8 +631,9 @@ where fn method_started_response( operation_id: String, + discarded_items: Option<usize>, ) -> (ResponsePayload<'static, MethodResponse>, MethodResponseFuture) { - let rp = MethodResponse::Started(MethodResponseStarted { operation_id, discarded_items: None }); + let rp = MethodResponse::Started(MethodResponseStarted { operation_id, discarded_items }); ResponsePayload::success(rp).notify_on_completion() } diff --git a/substrate/client/rpc-spec-v2/src/chain_head/event.rs b/substrate/client/rpc-spec-v2/src/chain_head/event.rs index bd9863060910542b1fdd2785d69fc9bb3472879b..de74145a3f088ed38784845bddad89fd86570c6b 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/event.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/event.rs @@ -235,7 +235,7 @@ pub struct OperationCallDone { pub output: String, } -/// The response of the `chainHead_call` method. +/// The response of the `chainHead_storage` method. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct OperationStorageItems { @@ -536,6 +536,7 @@ mod tests { items: vec![StorageResult { key: "0x1".into(), result: StorageResultType::Value("0x123".to_string()), + child_trie_key: None, }], }); 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 95a7c7fe183205d376d9c6d0da797cae1eaa967c..3e1bd23776d319e4c0f9c6e145ee85cb68879564 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 @@ -784,7 +784,7 @@ mod tests { use super::*; use jsonrpsee::ConnectionId; use sc_block_builder::BlockBuilderBuilder; - use sc_service::client::new_in_mem; + use sc_service::client::new_with_backend; use sp_consensus::BlockOrigin; use sp_core::{testing::TaskExecutor, H256}; use substrate_test_runtime_client::{ @@ -811,13 +811,13 @@ mod tests { ) .unwrap(); let client = Arc::new( - new_in_mem::<_, Block, _, RuntimeApi>( + new_with_backend::<_, _, Block, _, RuntimeApi>( backend.clone(), executor, genesis_block_builder, + Box::new(TaskExecutor::new()), None, None, - Box::new(TaskExecutor::new()), client_config, ) .unwrap(), 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 c505566d887dba429c493cca3f32c4e1fc2ba9ac..3ec5e805ecd5a2cc19d9e310c18cb665e0aeeef1 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs @@ -34,7 +34,7 @@ use jsonrpsee::{ use sc_block_builder::BlockBuilderBuilder; use sc_client_api::ChildInfo; use sc_rpc::testing::TokioTestExecutor; -use sc_service::client::new_in_mem; +use sc_service::client::new_with_backend; use sp_blockchain::HeaderBackend; use sp_consensus::BlockOrigin; use sp_core::{ @@ -506,8 +506,8 @@ async fn get_body() { .unwrap(); builder .push_transfer(runtime::Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 42, nonce: 0, }) @@ -580,7 +580,7 @@ async fn call_runtime() { ); // Valid call. - let alice_id = AccountKeyring::Alice.to_account_id(); + let alice_id = Sr25519Keyring::Alice.to_account_id(); // Hex encoded scale encoded bytes representing the call parameters. let call_parameters = hex_string(&alice_id.encode()); let response: MethodResponse = api @@ -670,7 +670,7 @@ async fn call_runtime_without_flag() { ); // Valid runtime call on a subscription started with `with_runtime` false. - let alice_id = AccountKeyring::Alice.to_account_id(); + let alice_id = Sr25519Keyring::Alice.to_account_id(); let call_parameters = hex_string(&alice_id.encode()); let err = api .call::<_, serde_json::Value>( @@ -1256,7 +1256,7 @@ async fn unique_operation_ids() { assert!(op_ids.insert(operation_id)); // Valid `chainHead_v1_call` call. - let alice_id = AccountKeyring::Alice.to_account_id(); + let alice_id = Sr25519Keyring::Alice.to_account_id(); let call_parameters = hex_string(&alice_id.encode()); let response: MethodResponse = api .call( @@ -1423,8 +1423,8 @@ async fn follow_generates_initial_blocks() { // imported block_builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -2046,8 +2046,8 @@ async fn follow_prune_best_block() { // imported block_builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -2217,8 +2217,8 @@ async fn follow_forks_pruned_block() { // imported block_builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -2233,8 +2233,8 @@ async fn follow_forks_pruned_block() { .unwrap(); block_builder .push_transfer(Transfer { - from: AccountKeyring::Bob.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Bob.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -2379,8 +2379,8 @@ async fn follow_report_multiple_pruned_block() { // imported block_builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -2397,8 +2397,8 @@ async fn follow_report_multiple_pruned_block() { block_builder .push_transfer(Transfer { - from: AccountKeyring::Bob.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Bob.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -2547,13 +2547,13 @@ async fn pin_block_references() { .unwrap(); let client = Arc::new( - new_in_mem::<_, Block, _, RuntimeApi>( + new_with_backend::<_, _, Block, _, RuntimeApi>( backend.clone(), executor, genesis_block_builder, + Box::new(TokioTestExecutor::default()), None, None, - Box::new(TokioTestExecutor::default()), client_config, ) .unwrap(), @@ -2871,7 +2871,7 @@ async fn ensure_operation_limits_works() { let operation_id = match response { MethodResponse::Started(started) => { // Check discarded items. - assert!(started.discarded_items.is_none()); + assert_eq!(started.discarded_items, Some(0)); started.operation_id }, MethodResponse::LimitReached => panic!("Expected started response"), @@ -2883,7 +2883,7 @@ async fn ensure_operation_limits_works() { ); // The storage is finished and capacity must be released. - let alice_id = AccountKeyring::Alice.to_account_id(); + let alice_id = Sr25519Keyring::Alice.to_account_id(); // Hex encoded scale encoded bytes representing the call parameters. let call_parameters = hex_string(&alice_id.encode()); let response: MethodResponse = api @@ -3228,7 +3228,10 @@ async fn storage_closest_merkle_value() { .await .unwrap(); let operation_id = match response { - MethodResponse::Started(started) => started.operation_id, + MethodResponse::Started(started) => { + assert_eq!(started.discarded_items, Some(0)); + started.operation_id + }, MethodResponse::LimitReached => panic!("Expected started response"), }; @@ -3534,7 +3537,7 @@ async fn chain_head_single_connection_context() { .unwrap(); assert_matches!(response, MethodResponse::LimitReached); - let alice_id = AccountKeyring::Alice.to_account_id(); + let alice_id = Sr25519Keyring::Alice.to_account_id(); // Hex encoded scale encoded bytes representing the call parameters. let call_parameters = hex_string(&alice_id.encode()); let response: MethodResponse = ChainHeadApiClient::<String>::chain_head_unstable_call( @@ -3660,8 +3663,8 @@ async fn follow_unique_pruned_blocks() { let block_6_hash = import_block(client.clone(), block_2_f_hash, 2).await.hash(); // Import block 2 as best on the fork. let mut tx_alice_ferdie = Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }; @@ -3843,8 +3846,8 @@ async fn follow_report_best_block_of_a_known_block() { // imported block_builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) diff --git a/substrate/client/rpc-spec-v2/src/common/events.rs b/substrate/client/rpc-spec-v2/src/common/events.rs index b1627d74c844e539dd6b71383008a083aeaa5de0..44f722c0c61ba51af147aea70b2954008aa1c768 100644 --- a/substrate/client/rpc-spec-v2/src/common/events.rs +++ b/substrate/client/rpc-spec-v2/src/common/events.rs @@ -78,10 +78,14 @@ pub struct StorageResult { /// The result of the query. #[serde(flatten)] pub result: StorageResultType, + /// The child trie key if provided. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub child_trie_key: Option<String>, } /// The type of the storage query. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum StorageResultType { /// Fetch the value of the provided key. @@ -105,23 +109,41 @@ pub struct StorageResultErr { /// The result of a storage call. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum ArchiveStorageResult { +#[serde(rename_all = "camelCase")] +#[serde(tag = "event")] +pub enum ArchiveStorageEvent { /// Query generated a result. - Ok(ArchiveStorageMethodOk), + Storage(StorageResult), /// Query encountered an error. - Err(ArchiveStorageMethodErr), + StorageError(ArchiveStorageMethodErr), + /// Operation storage is done. + StorageDone, } -impl ArchiveStorageResult { - /// Create a new `ArchiveStorageResult::Ok` result. - pub fn ok(result: Vec<StorageResult>, discarded_items: usize) -> Self { - Self::Ok(ArchiveStorageMethodOk { result, discarded_items }) +impl ArchiveStorageEvent { + /// Create a new `ArchiveStorageEvent::StorageErr` event. + pub fn err(error: String) -> Self { + Self::StorageError(ArchiveStorageMethodErr { error }) } - /// Create a new `ArchiveStorageResult::Err` result. - pub fn err(error: String) -> Self { - Self::Err(ArchiveStorageMethodErr { error }) + /// Create a new `ArchiveStorageEvent::StorageResult` event. + pub fn result(result: StorageResult) -> Self { + Self::Storage(result) + } + + /// Checks if the event is a `StorageDone` event. + pub fn is_done(&self) -> bool { + matches!(self, Self::StorageDone) + } + + /// Checks if the event is a `StorageErr` event. + pub fn is_err(&self) -> bool { + matches!(self, Self::StorageError(_)) + } + + /// Checks if the event is a `StorageResult` event. + pub fn is_result(&self) -> bool { + matches!(self, Self::Storage(_)) } } @@ -136,22 +158,229 @@ pub struct ArchiveStorageMethodOk { } /// The error of a storage call. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ArchiveStorageMethodErr { /// Reported error. pub error: String, } +/// The type of the archive storage difference query. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum ArchiveStorageDiffType { + /// The result is provided as value of the key. + Value, + /// The result the hash of the value of the key. + Hash, +} + +/// The storage item to query. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ArchiveStorageDiffItem<Key> { + /// The provided key. + pub key: Key, + /// The type of the storage query. + pub return_type: ArchiveStorageDiffType, + /// The child trie key if provided. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub child_trie_key: Option<Key>, +} + +/// The result of a storage difference call. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ArchiveStorageDiffMethodResult { + /// Reported results. + pub result: Vec<ArchiveStorageDiffResult>, +} + +/// The result of a storage difference call operation type. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum ArchiveStorageDiffOperationType { + /// The key is added. + Added, + /// The key is modified. + Modified, + /// The key is removed. + Deleted, +} + +/// The result of an individual storage difference key. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ArchiveStorageDiffResult { + /// The hex-encoded key of the result. + pub key: String, + /// The result of the query. + #[serde(flatten)] + pub result: StorageResultType, + /// The operation type. + #[serde(rename = "type")] + pub operation_type: ArchiveStorageDiffOperationType, + /// The child trie key if provided. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub child_trie_key: Option<String>, +} + +/// The event generated by the `archive_storageDiff` method. +/// +/// The `archive_storageDiff` can generate the following events: +/// - `storageDiff` event - generated when a `ArchiveStorageDiffResult` is produced. +/// - `storageDiffError` event - generated when an error is produced. +/// - `storageDiffDone` event - generated when the `archive_storageDiff` method completed. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(tag = "event")] +pub enum ArchiveStorageDiffEvent { + /// The `storageDiff` event. + StorageDiff(ArchiveStorageDiffResult), + /// The `storageDiffError` event. + StorageDiffError(ArchiveStorageMethodErr), + /// The `storageDiffDone` event. + StorageDiffDone, +} + +impl ArchiveStorageDiffEvent { + /// Create a new `ArchiveStorageDiffEvent::StorageDiffError` event. + pub fn err(error: String) -> Self { + Self::StorageDiffError(ArchiveStorageMethodErr { error }) + } + + /// Checks if the event is a `StorageDiffDone` event. + pub fn is_done(&self) -> bool { + matches!(self, Self::StorageDiffDone) + } + + /// Checks if the event is a `StorageDiffError` event. + pub fn is_err(&self) -> bool { + matches!(self, Self::StorageDiffError(_)) + } +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn archive_diff_input() { + // Item with Value. + let item = ArchiveStorageDiffItem { + key: "0x1", + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }; + // Encode + let ser = serde_json::to_string(&item).unwrap(); + let exp = r#"{"key":"0x1","returnType":"value"}"#; + assert_eq!(ser, exp); + // Decode + let dec: ArchiveStorageDiffItem<&str> = serde_json::from_str(exp).unwrap(); + assert_eq!(dec, item); + + // Item with Hash. + let item = ArchiveStorageDiffItem { + key: "0x1", + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: None, + }; + // Encode + let ser = serde_json::to_string(&item).unwrap(); + let exp = r#"{"key":"0x1","returnType":"hash"}"#; + assert_eq!(ser, exp); + // Decode + let dec: ArchiveStorageDiffItem<&str> = serde_json::from_str(exp).unwrap(); + assert_eq!(dec, item); + + // Item with Value and child trie key. + let item = ArchiveStorageDiffItem { + key: "0x1", + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some("0x2"), + }; + // Encode + let ser = serde_json::to_string(&item).unwrap(); + let exp = r#"{"key":"0x1","returnType":"value","childTrieKey":"0x2"}"#; + assert_eq!(ser, exp); + // Decode + let dec: ArchiveStorageDiffItem<&str> = serde_json::from_str(exp).unwrap(); + assert_eq!(dec, item); + + // Item with Hash and child trie key. + let item = ArchiveStorageDiffItem { + key: "0x1", + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: Some("0x2"), + }; + // Encode + let ser = serde_json::to_string(&item).unwrap(); + let exp = r#"{"key":"0x1","returnType":"hash","childTrieKey":"0x2"}"#; + assert_eq!(ser, exp); + // Decode + let dec: ArchiveStorageDiffItem<&str> = serde_json::from_str(exp).unwrap(); + assert_eq!(dec, item); + } + + #[test] + fn archive_diff_output() { + // Item with Value. + let item = ArchiveStorageDiffResult { + key: "0x1".into(), + result: StorageResultType::Value("res".into()), + operation_type: ArchiveStorageDiffOperationType::Added, + child_trie_key: None, + }; + // Encode + let ser = serde_json::to_string(&item).unwrap(); + let exp = r#"{"key":"0x1","value":"res","type":"added"}"#; + assert_eq!(ser, exp); + // Decode + let dec: ArchiveStorageDiffResult = serde_json::from_str(exp).unwrap(); + assert_eq!(dec, item); + + // Item with Hash. + let item = ArchiveStorageDiffResult { + key: "0x1".into(), + result: StorageResultType::Hash("res".into()), + operation_type: ArchiveStorageDiffOperationType::Modified, + child_trie_key: None, + }; + // Encode + let ser = serde_json::to_string(&item).unwrap(); + let exp = r#"{"key":"0x1","hash":"res","type":"modified"}"#; + assert_eq!(ser, exp); + // Decode + let dec: ArchiveStorageDiffResult = serde_json::from_str(exp).unwrap(); + assert_eq!(dec, item); + + // Item with Hash, child trie key and removed. + let item = ArchiveStorageDiffResult { + key: "0x1".into(), + result: StorageResultType::Hash("res".into()), + operation_type: ArchiveStorageDiffOperationType::Deleted, + child_trie_key: Some("0x2".into()), + }; + // Encode + let ser = serde_json::to_string(&item).unwrap(); + let exp = r#"{"key":"0x1","hash":"res","type":"deleted","childTrieKey":"0x2"}"#; + assert_eq!(ser, exp); + // Decode + let dec: ArchiveStorageDiffResult = serde_json::from_str(exp).unwrap(); + assert_eq!(dec, item); + } + #[test] fn storage_result() { // Item with Value. - let item = - StorageResult { key: "0x1".into(), result: StorageResultType::Value("res".into()) }; + let item = StorageResult { + key: "0x1".into(), + result: StorageResultType::Value("res".into()), + child_trie_key: None, + }; // Encode let ser = serde_json::to_string(&item).unwrap(); let exp = r#"{"key":"0x1","value":"res"}"#; @@ -161,8 +390,11 @@ mod tests { assert_eq!(dec, item); // Item with Hash. - let item = - StorageResult { key: "0x1".into(), result: StorageResultType::Hash("res".into()) }; + let item = StorageResult { + key: "0x1".into(), + result: StorageResultType::Hash("res".into()), + child_trie_key: None, + }; // Encode let ser = serde_json::to_string(&item).unwrap(); let exp = r#"{"key":"0x1","hash":"res"}"#; @@ -175,6 +407,7 @@ mod tests { let item = StorageResult { key: "0x1".into(), result: StorageResultType::ClosestDescendantMerkleValue("res".into()), + child_trie_key: None, }; // Encode let ser = serde_json::to_string(&item).unwrap(); diff --git a/substrate/client/rpc-spec-v2/src/common/storage.rs b/substrate/client/rpc-spec-v2/src/common/storage.rs index 2e24a8da8ca8451e93b91d0aa1e220a1829df7e4..a1e34d51530ec7b894a17d43ad8f2b56f550bc67 100644 --- a/substrate/client/rpc-spec-v2/src/common/storage.rs +++ b/substrate/client/rpc-spec-v2/src/common/storage.rs @@ -24,7 +24,7 @@ use sc_client_api::{Backend, ChildInfo, StorageKey, StorageProvider}; use sp_runtime::traits::Block as BlockT; use tokio::sync::mpsc; -use super::events::{StorageResult, StorageResultType}; +use super::events::{StorageQuery, StorageQueryType, StorageResult, StorageResultType}; use crate::hex_string; /// Call into the storage of blocks. @@ -70,9 +70,6 @@ pub enum IterQueryType { /// The result of making a query call. pub type QueryResult = Result<Option<StorageResult>, String>; -/// The result of iterating over keys. -pub type QueryIterResult = Result<(Vec<StorageResult>, Option<QueryIter>), String>; - impl<Client, Block, BE> Storage<Client, Block, BE> where Block: BlockT + 'static, @@ -97,6 +94,7 @@ where QueryResult::Ok(opt.map(|storage_data| StorageResult { key: hex_string(&key.0), result: StorageResultType::Value(hex_string(&storage_data.0)), + child_trie_key: child_key.map(|c| hex_string(&c.storage_key())), })) }) .unwrap_or_else(|error| QueryResult::Err(error.to_string())) @@ -120,6 +118,7 @@ where QueryResult::Ok(opt.map(|storage_data| StorageResult { key: hex_string(&key.0), result: StorageResultType::Hash(hex_string(&storage_data.as_ref())), + child_trie_key: child_key.map(|c| hex_string(&c.storage_key())), })) }) .unwrap_or_else(|error| QueryResult::Err(error.to_string())) @@ -149,6 +148,7 @@ where StorageResult { key: hex_string(&key.0), result: StorageResultType::ClosestDescendantMerkleValue(result), + child_trie_key: child_key.map(|c| hex_string(&c.storage_key())), } })) }) @@ -199,53 +199,111 @@ where } } - /// Iterate over at most the provided number of keys. - /// - /// Returns the storage result with a potential next key to resume iteration. - pub fn query_iter_pagination( + /// Raw iterator over the keys. + pub fn raw_keys_iter( &self, - query: QueryIter, hash: Block::Hash, - child_key: Option<&ChildInfo>, - count: usize, - ) -> QueryIterResult { - let QueryIter { ty, query_key, pagination_start_key } = query; - - let mut keys_iter = if let Some(child_key) = child_key { - self.client.child_storage_keys( - hash, - child_key.to_owned(), - Some(&query_key), - pagination_start_key.as_ref(), - ) + child_key: Option<ChildInfo>, + ) -> Result<impl Iterator<Item = StorageKey>, String> { + let keys_iter = if let Some(child_key) = child_key { + self.client.child_storage_keys(hash, child_key, None, None) } else { - self.client.storage_keys(hash, Some(&query_key), pagination_start_key.as_ref()) - } - .map_err(|err| err.to_string())?; + self.client.storage_keys(hash, None, None) + }; + + keys_iter.map_err(|err| err.to_string()) + } +} - let mut ret = Vec::with_capacity(count); - let mut next_pagination_key = None; - for _ in 0..count { - let Some(key) = keys_iter.next() else { break }; +/// Generates storage events for `chainHead_storage` and `archive_storage` subscriptions. +pub struct StorageSubscriptionClient<Client, Block, BE> { + /// Storage client. + client: Storage<Client, Block, BE>, + _phandom: PhantomData<(BE, Block)>, +} - next_pagination_key = Some(key.clone()); +impl<Client, Block, BE> Clone for StorageSubscriptionClient<Client, Block, BE> { + fn clone(&self) -> Self { + Self { client: self.client.clone(), _phandom: PhantomData } + } +} - let result = match ty { - IterQueryType::Value => self.query_value(hash, &key, child_key), - IterQueryType::Hash => self.query_hash(hash, &key, child_key), - }?; +impl<Client, Block, BE> StorageSubscriptionClient<Client, Block, BE> { + /// Constructs a new [`StorageSubscriptionClient`]. + pub fn new(client: Arc<Client>) -> Self { + Self { client: Storage::new(client), _phandom: PhantomData } + } +} - if let Some(value) = result { - ret.push(value); +impl<Client, Block, BE> StorageSubscriptionClient<Client, Block, BE> +where + Block: BlockT + 'static, + BE: Backend<Block> + 'static, + Client: StorageProvider<Block, BE> + Send + Sync + 'static, +{ + /// Generate storage events to the provided sender. + pub async fn generate_events( + &mut self, + hash: Block::Hash, + items: Vec<StorageQuery<StorageKey>>, + child_key: Option<ChildInfo>, + tx: mpsc::Sender<QueryResult>, + ) -> Result<(), tokio::task::JoinError> { + let this = self.clone(); + + tokio::task::spawn_blocking(move || { + for item in items { + match item.query_type { + StorageQueryType::Value => { + let rp = this.client.query_value(hash, &item.key, child_key.as_ref()); + if tx.blocking_send(rp).is_err() { + break; + } + }, + StorageQueryType::Hash => { + let rp = this.client.query_hash(hash, &item.key, child_key.as_ref()); + if tx.blocking_send(rp).is_err() { + break; + } + }, + StorageQueryType::ClosestDescendantMerkleValue => { + let rp = + this.client.query_merkle_value(hash, &item.key, child_key.as_ref()); + if tx.blocking_send(rp).is_err() { + break; + } + }, + StorageQueryType::DescendantsValues => { + let query = QueryIter { + query_key: item.key, + ty: IterQueryType::Value, + pagination_start_key: None, + }; + this.client.query_iter_pagination_with_producer( + query, + hash, + child_key.as_ref(), + &tx, + ) + }, + StorageQueryType::DescendantsHashes => { + let query = QueryIter { + query_key: item.key, + ty: IterQueryType::Hash, + pagination_start_key: None, + }; + this.client.query_iter_pagination_with_producer( + query, + hash, + child_key.as_ref(), + &tx, + ) + }, + } } - } + }) + .await?; - // Save the next key if any to continue the iteration. - let maybe_next_query = keys_iter.next().map(|_| QueryIter { - ty, - query_key, - pagination_start_key: next_pagination_key, - }); - Ok((ret, maybe_next_query)) + Ok(()) } } diff --git a/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_broadcast_tests.rs b/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_broadcast_tests.rs index efb3bd94ddbfd2df8af96a49eb4d704e5f86af26..c2f11878e8fcc5fe4cda672d2dd2a7c1cb6f4945 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_broadcast_tests.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_broadcast_tests.rs @@ -23,7 +23,7 @@ use jsonrpsee::{rpc_params, MethodsError as Error}; use sc_transaction_pool::{Options, PoolLimit}; use sc_transaction_pool_api::{ChainEvent, MaintainedTransactionPool, TransactionPool}; use std::sync::Arc; -use substrate_test_runtime_client::AccountKeyring::*; +use substrate_test_runtime_client::Sr25519Keyring::*; use substrate_test_runtime_transaction_pool::uxt; const MAX_TX_PER_CONNECTION: usize = 4; diff --git a/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_tests.rs b/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_tests.rs index 53c5b8ce389512512b6d51c46f0c7476e18537a8..879d51eaf5f392769d713e1b17bab36fef9b2bca 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_tests.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_tests.rs @@ -26,7 +26,7 @@ use jsonrpsee::rpc_params; use sc_transaction_pool_api::{ChainEvent, MaintainedTransactionPool}; use sp_core::H256; use std::{sync::Arc, vec}; -use substrate_test_runtime_client::AccountKeyring::*; +use substrate_test_runtime_client::Sr25519Keyring::*; use substrate_test_runtime_transaction_pool::uxt; // Test helpers. diff --git a/substrate/client/rpc/Cargo.toml b/substrate/client/rpc/Cargo.toml index 6fe28a3873e9ac62c086858c4c71b0ccd8bf2589..8be932f02ed400b4079b22203a636f6188771dae 100644 --- a/substrate/client/rpc/Cargo.toml +++ b/substrate/client/rpc/Cargo.toml @@ -21,7 +21,6 @@ futures = { workspace = true } jsonrpsee = { features = ["server"], workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } @@ -30,6 +29,7 @@ sc-rpc-api = { workspace = true, default-features = true } sc-tracing = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } @@ -38,22 +38,22 @@ sp-offchain = { workspace = true, default-features = true } sp-rpc = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-session = { workspace = true, default-features = true } -sp-version = { workspace = true, default-features = true } sp-statement-store = { workspace = true, default-features = true } +sp-version = { workspace = true, default-features = true } tokio = { workspace = true, default-features = true } [dev-dependencies] assert_matches = { workspace = true } +pretty_assertions = { workspace = true } sc-block-builder = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } sc-transaction-pool = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } -tokio = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } -pretty_assertions = { workspace = true } +tokio = { workspace = true, default-features = true } [features] test-helpers = [] diff --git a/substrate/client/rpc/src/author/tests.rs b/substrate/client/rpc/src/author/tests.rs index ab0b8bdab69919275d044566ba069e1a9aec8ac6..b1c899667624e6fc3f37e2bad3390d98ee0aa9f2 100644 --- a/substrate/client/rpc/src/author/tests.rs +++ b/substrate/client/rpc/src/author/tests.rs @@ -39,15 +39,15 @@ use std::sync::Arc; use substrate_test_runtime_client::{ self, runtime::{Block, Extrinsic, ExtrinsicBuilder, SessionKeys, Transfer}, - AccountKeyring, Backend, Client, DefaultTestClientBuilderExt, TestClientBuilderExt, + Backend, Client, DefaultTestClientBuilderExt, Sr25519Keyring, TestClientBuilderExt, }; -fn uxt(sender: AccountKeyring, nonce: u64) -> Extrinsic { +fn uxt(sender: Sr25519Keyring, nonce: u64) -> Extrinsic { let tx = Transfer { amount: Default::default(), nonce, from: sender.into(), - to: AccountKeyring::Bob.into(), + to: Sr25519Keyring::Bob.into(), }; ExtrinsicBuilder::new_transfer(tx).build() } @@ -99,7 +99,7 @@ impl TestSetup { async fn author_submit_transaction_should_not_cause_error() { let api = TestSetup::into_rpc(); - let xt: Bytes = uxt(AccountKeyring::Alice, 1).encode().into(); + let xt: Bytes = uxt(Sr25519Keyring::Alice, 1).encode().into(); let extrinsic_hash: H256 = blake2_256(&xt).into(); let response: H256 = api.call("author_submitExtrinsic", [xt.clone()]).await.unwrap(); @@ -116,7 +116,7 @@ async fn author_should_watch_extrinsic() { let api = TestSetup::into_rpc(); let xt = to_hex( &ExtrinsicBuilder::new_call_with_priority(0) - .signer(AccountKeyring::Alice.into()) + .signer(Sr25519Keyring::Alice.into()) .build() .encode(), true, @@ -135,7 +135,7 @@ async fn author_should_watch_extrinsic() { // Replace the extrinsic and observe the subscription is notified. let (xt_replacement, xt_hash) = { let tx = ExtrinsicBuilder::new_call_with_priority(1) - .signer(AccountKeyring::Alice.into()) + .signer(Sr25519Keyring::Alice.into()) .build() .encode(); let hash = blake2_256(&tx); @@ -172,7 +172,7 @@ async fn author_should_return_watch_validation_error() { async fn author_should_return_pending_extrinsics() { let api = TestSetup::into_rpc(); - let xt_bytes: Bytes = uxt(AccountKeyring::Alice, 0).encode().into(); + let xt_bytes: Bytes = uxt(Sr25519Keyring::Alice, 0).encode().into(); api.call::<_, H256>("author_submitExtrinsic", [to_hex(&xt_bytes, true)]) .await .unwrap(); @@ -190,14 +190,14 @@ async fn author_should_remove_extrinsics() { // Submit three extrinsics, then remove two of them (will cause the third to be removed as well, // having a higher nonce) - let xt1_bytes = uxt(AccountKeyring::Alice, 0).encode(); + let xt1_bytes = uxt(Sr25519Keyring::Alice, 0).encode(); let xt1 = to_hex(&xt1_bytes, true); let xt1_hash: H256 = api.call("author_submitExtrinsic", [xt1]).await.unwrap(); - let xt2 = to_hex(&uxt(AccountKeyring::Alice, 1).encode(), true); + let xt2 = to_hex(&uxt(Sr25519Keyring::Alice, 1).encode(), true); let xt2_hash: H256 = api.call("author_submitExtrinsic", [xt2]).await.unwrap(); - let xt3 = to_hex(&uxt(AccountKeyring::Bob, 0).encode(), true); + let xt3 = to_hex(&uxt(Sr25519Keyring::Bob, 0).encode(), true); let xt3_hash: H256 = api.call("author_submitExtrinsic", [xt3]).await.unwrap(); assert_eq!(setup.pool.status().ready, 3); diff --git a/substrate/client/rpc/src/offchain/mod.rs b/substrate/client/rpc/src/offchain/mod.rs index af6bc1ba58c8fdce53679465f204332d6a59f66b..f5b1b35be1063062fb1f04cf744f7938deb365bb 100644 --- a/substrate/client/rpc/src/offchain/mod.rs +++ b/substrate/client/rpc/src/offchain/mod.rs @@ -66,6 +66,23 @@ impl<T: OffchainStorage + 'static> OffchainApiServer for Offchain<T> { Ok(()) } + fn clear_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, + StorageKind::LOCAL => return Err(Error::UnavailableStorageKind), + }; + self.storage.write().remove(prefix, &key); + + Ok(()) + } + fn get_local_storage( &self, ext: &Extensions, diff --git a/substrate/client/rpc/src/offchain/tests.rs b/substrate/client/rpc/src/offchain/tests.rs index 41f22c2dc964227cb2989dd10a8322e49ef54103..6b8225a7b5eb21ece1cb9e39a109c4cad4af6d71 100644 --- a/substrate/client/rpc/src/offchain/tests.rs +++ b/substrate/client/rpc/src/offchain/tests.rs @@ -35,9 +35,14 @@ fn local_storage_should_work() { Ok(()) ); assert_matches!( - offchain.get_local_storage(&ext, StorageKind::PERSISTENT, key), + offchain.get_local_storage(&ext, StorageKind::PERSISTENT, key.clone()), Ok(Some(ref v)) if *v == value ); + assert_matches!( + offchain.clear_local_storage(&ext, StorageKind::PERSISTENT, key.clone()), + Ok(()) + ); + assert_matches!(offchain.get_local_storage(&ext, StorageKind::PERSISTENT, key), Ok(None)); } #[test] @@ -55,6 +60,12 @@ fn offchain_calls_considered_unsafe() { assert_eq!(e.to_string(), "RPC call is unsafe to be called externally") } ); + assert_matches!( + offchain.clear_local_storage(&ext, StorageKind::PERSISTENT, key.clone()), + Err(Error::UnsafeRpcCalled(e)) => { + assert_eq!(e.to_string(), "RPC call is unsafe to be called externally") + } + ); assert_matches!( offchain.get_local_storage(&ext, StorageKind::PERSISTENT, key), Err(Error::UnsafeRpcCalled(e)) => { diff --git a/substrate/client/rpc/src/state/tests.rs b/substrate/client/rpc/src/state/tests.rs index 6b711f2425e95891ad2fd775528e7bafaef451ee..c02f0d0b759bfbf1fb3bf69b20d2aefd29f66028 100644 --- a/substrate/client/rpc/src/state/tests.rs +++ b/substrate/client/rpc/src/state/tests.rs @@ -228,8 +228,8 @@ async fn should_notify_about_storage_changes() { .unwrap(); builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 42, nonce: 0, }) @@ -255,11 +255,11 @@ async fn should_send_initial_storage_changes_and_notifications() { let alice_balance_key = [ sp_crypto_hashing::twox_128(b"System"), sp_crypto_hashing::twox_128(b"Account"), - sp_crypto_hashing::blake2_128(&AccountKeyring::Alice.public()), + sp_crypto_hashing::blake2_128(&Sr25519Keyring::Alice.public()), ] .concat() .iter() - .chain(AccountKeyring::Alice.public().0.iter()) + .chain(Sr25519Keyring::Alice.public().0.iter()) .cloned() .collect::<Vec<u8>>(); @@ -281,8 +281,8 @@ async fn should_send_initial_storage_changes_and_notifications() { .unwrap(); builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 42, nonce: 0, }) diff --git a/substrate/client/runtime-utilities/Cargo.toml b/substrate/client/runtime-utilities/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..716b577d384aa018771723c406d5b695b356019b --- /dev/null +++ b/substrate/client/runtime-utilities/Cargo.toml @@ -0,0 +1,36 @@ +[package] +description = "Substrate client utilities for frame runtime functions calls." +name = "sc-runtime-utilities" +version = "0.1.0" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true +documentation = "https://docs.rs/sc-metadata" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true, default-features = true } + +sc-executor = { workspace = true, default-features = true } +sc-executor-common = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true, default-features = true } +sp-state-machine = { workspace = true, default-features = true } +sp-wasm-interface = { workspace = true, default-features = true } + + +thiserror = { workspace = true } + +[dev-dependencies] +cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } +cumulus-test-runtime = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } +sp-version = { workspace = true, default-features = true } +subxt = { workspace = true, features = ["native"] } diff --git a/substrate/client/runtime-utilities/src/error.rs b/substrate/client/runtime-utilities/src/error.rs new file mode 100644 index 0000000000000000000000000000000000000000..a0f1e45a5e57d669341fab7a0f90e0811c7e2d14 --- /dev/null +++ b/substrate/client/runtime-utilities/src/error.rs @@ -0,0 +1,35 @@ +// 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 <https://www.gnu.org/licenses/>. +//! Errors types of runtime utilities. + +/// Generic result for the runtime utilities. +pub type Result<T> = std::result::Result<T, Error>; + +/// Error type for the runtime utilities. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error("Scale codec error: {0}")] + ScaleCodec(#[from] codec::Error), + #[error("Opaque metadata not found")] + OpaqueMetadataNotFound, + #[error("Stable metadata version not found")] + StableMetadataVersionNotFound, + #[error("WASM executor error: {0}")] + Executor(#[from] sc_executor_common::error::Error), +} diff --git a/substrate/utils/frame/benchmarking-cli/src/overhead/runtime_utilities.rs b/substrate/client/runtime-utilities/src/lib.rs similarity index 56% rename from substrate/utils/frame/benchmarking-cli/src/overhead/runtime_utilities.rs rename to substrate/client/runtime-utilities/src/lib.rs index c498da38afb04ce4fa50204afd9ea75fd8a3486f..1ae3e2f1105a39a026341fdb304194b5841f131e 100644 --- a/substrate/utils/frame/benchmarking-cli/src/overhead/runtime_utilities.rs +++ b/substrate/client/runtime-utilities/src/lib.rs @@ -1,21 +1,29 @@ // 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. +// 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 <https://www.gnu.org/licenses/>. + +//! Substrate client runtime utilities. +//! +//! Provides convenient APIs to ease calling functions contained by a FRAME +//! runtime WASM blob. +#![warn(missing_docs)] use codec::{Decode, Encode}; +use error::{Error, Result}; use sc_executor::WasmExecutor; use sp_core::{ traits::{CallContext, CodeExecutor, FetchRuntimeCode, RuntimeCode}, @@ -25,36 +33,35 @@ use sp_state_machine::BasicExternalities; use sp_wasm_interface::HostFunctions; use std::borrow::Cow; +pub mod error; + /// Fetches the latest metadata from the given runtime blob. pub fn fetch_latest_metadata_from_code_blob<HF: HostFunctions>( executor: &WasmExecutor<HF>, code_bytes: Cow<[u8]>, -) -> sc_cli::Result<subxt::Metadata> { +) -> Result<OpaqueMetadata> { let runtime_caller = RuntimeCaller::new(executor, code_bytes); let version_result = runtime_caller.call("Metadata_metadata_versions", ()); - let opaque_metadata: OpaqueMetadata = match version_result { + match version_result { Ok(supported_versions) => { - let latest_version = Vec::<u32>::decode(&mut supported_versions.as_slice()) - .map_err(|e| format!("Unable to decode version list: {e}"))? - .pop() - .ok_or("No metadata versions supported".to_string())?; - - let encoded = runtime_caller - .call("Metadata_metadata_at_version", latest_version) - .map_err(|_| "Unable to fetch metadata from blob".to_string())?; + let supported_versions = Vec::<u32>::decode(&mut supported_versions.as_slice())?; + let latest_stable = supported_versions + .into_iter() + .filter(|v| *v != u32::MAX) + .max() + .ok_or(Error::StableMetadataVersionNotFound)?; + + let encoded = runtime_caller.call("Metadata_metadata_at_version", latest_stable)?; + Option::<OpaqueMetadata>::decode(&mut encoded.as_slice())? - .ok_or_else(|| "Metadata not found".to_string())? + .ok_or(Error::OpaqueMetadataNotFound) }, Err(_) => { - let encoded = runtime_caller - .call("Metadata_metadata", ()) - .map_err(|_| "Unable to fetch metadata from blob".to_string())?; - Decode::decode(&mut encoded.as_slice())? + let encoded = runtime_caller.call("Metadata_metadata", ())?; + Decode::decode(&mut encoded.as_slice()).map_err(Into::into) }, - }; - - Ok(subxt::Metadata::decode(&mut (*opaque_metadata).as_slice())?) + } } struct BasicCodeFetcher<'a> { @@ -69,11 +76,11 @@ impl<'a> FetchRuntimeCode for BasicCodeFetcher<'a> { } impl<'a> BasicCodeFetcher<'a> { - pub fn new(code: Cow<'a, [u8]>) -> Self { + fn new(code: Cow<'a, [u8]>) -> Self { Self { hash: sp_crypto_hashing::blake2_256(&code).to_vec(), code } } - pub fn runtime_code(&'a self) -> RuntimeCode<'a> { + fn runtime_code(&'a self) -> RuntimeCode<'a> { RuntimeCode { code_fetcher: self as &'a dyn FetchRuntimeCode, heap_pages: None, @@ -83,17 +90,20 @@ impl<'a> BasicCodeFetcher<'a> { } /// Simple utility that is used to call into the runtime. -struct RuntimeCaller<'a, 'b, HF: HostFunctions> { +pub struct RuntimeCaller<'a, 'b, HF: HostFunctions> { executor: &'b WasmExecutor<HF>, code_fetcher: BasicCodeFetcher<'a>, } impl<'a, 'b, HF: HostFunctions> RuntimeCaller<'a, 'b, HF> { + /// Instantiate a new runtime caller. pub fn new(executor: &'b WasmExecutor<HF>, code_bytes: Cow<'a, [u8]>) -> Self { Self { executor, code_fetcher: BasicCodeFetcher::new(code_bytes) } } - fn call(&self, method: &str, data: impl Encode) -> sc_executor_common::error::Result<Vec<u8>> { + /// Calls a runtime function represented by a `method` name and `parity-scale-codec` + /// encodable arguments that will be passed to it. + pub fn call(&self, method: &str, data: impl Encode) -> Result<Vec<u8>> { let mut ext = BasicExternalities::default(); self.executor .call( @@ -104,24 +114,33 @@ impl<'a, 'b, HF: HostFunctions> RuntimeCaller<'a, 'b, HF> { CallContext::Offchain, ) .0 + .map_err(Into::into) } } #[cfg(test)] mod tests { - use crate::overhead::command::ParachainHostFunctions; use codec::Decode; use sc_executor::WasmExecutor; use sp_version::RuntimeVersion; + type ParachainHostFunctions = ( + cumulus_primitives_proof_size_hostfunction::storage_proof_size::HostFunctions, + sp_io::SubstrateHostFunctions, + ); + #[test] fn test_fetch_latest_metadata_from_blob_fetches_metadata() { let executor: WasmExecutor<ParachainHostFunctions> = WasmExecutor::builder().build(); let code_bytes = cumulus_test_runtime::WASM_BINARY .expect("To run this test, build the wasm binary of cumulus-test-runtime") .to_vec(); - let metadata = - super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()).unwrap(); + let metadata = subxt::Metadata::decode( + &mut (*super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()) + .unwrap()) + .as_slice(), + ) + .unwrap(); assert!(metadata.pallet_by_name("ParachainInfo").is_some()); } diff --git a/substrate/client/service/Cargo.toml b/substrate/client/service/Cargo.toml index f2fc65ef24399d7c5d91a124b5997f8271436054..e46b252f30bf636d063baf4cae88d107497515f0 100644 --- a/substrate/client/service/Cargo.toml +++ b/substrate/client/service/Cargo.toml @@ -20,72 +20,70 @@ default = ["rocksdb"] # The RocksDB feature activates the RocksDB database backend. If it is not activated, and you pass # a path to a database, an error will be produced at runtime. rocksdb = ["sc-client-db/rocksdb"] -# exposes the client type -test-helpers = [] runtime-benchmarks = [ "sc-client-db/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] [dependencies] -jsonrpsee = { features = ["server"], workspace = true } -thiserror = { workspace = true } +async-trait = { workspace = true } +codec = { workspace = true, default-features = true } +directories = { workspace = true } +exit-future = { workspace = true } futures = { workspace = true } -rand = { workspace = true, default-features = true } -parking_lot = { workspace = true, default-features = true } -log = { workspace = true, default-features = true } futures-timer = { workspace = true } -exit-future = { workspace = true } +jsonrpsee = { features = ["server"], workspace = true } +log = { workspace = true, default-features = true } +parking_lot = { workspace = true, default-features = true } pin-project = { workspace = true } -serde = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } -sp-trie = { workspace = true, default-features = true } -sp-externalities = { workspace = true, default-features = true } -sc-utils = { workspace = true, default-features = true } -sp-version = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sp-session = { workspace = true, default-features = true } -sp-state-machine = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } +prometheus-endpoint = { workspace = true, default-features = true } +rand = { workspace = true, default-features = true } +sc-chain-spec = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } +sc-client-db = { workspace = true } sc-consensus = { workspace = true, default-features = true } -sp-storage = { workspace = true, default-features = true } +sc-executor = { workspace = true, default-features = true } +sc-informant = { workspace = true, default-features = true } +sc-keystore = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } sc-network-light = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } -sc-network-types = { workspace = true, default-features = true } sc-network-transactions = { workspace = true, default-features = true } -sc-chain-spec = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } -sc-client-db = { workspace = true } -codec = { workspace = true, default-features = true } -sc-executor = { workspace = true, default-features = true } -sc-transaction-pool = { workspace = true, default-features = true } -sp-transaction-pool = { workspace = true, default-features = true } -sc-transaction-pool-api = { workspace = true, default-features = true } -sp-transaction-storage-proof = { workspace = true, default-features = true } -sc-rpc-server = { workspace = true, default-features = true } +sc-network-types = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } +sc-rpc-server = { workspace = true, default-features = true } sc-rpc-spec-v2 = { workspace = true, default-features = true } -sc-informant = { workspace = true, default-features = true } +sc-sysinfo = { workspace = true, default-features = true } sc-telemetry = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } sc-tracing = { workspace = true, default-features = true } -sc-sysinfo = { workspace = true, default-features = true } +sc-transaction-pool = { workspace = true, default-features = true } +sc-transaction-pool-api = { workspace = true, default-features = true } +sc-utils = { workspace = true, default-features = true } +schnellru = { workspace = true } +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-externalities = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-session = { workspace = true, default-features = true } +sp-state-machine = { workspace = true, default-features = true } +sp-storage = { workspace = true, default-features = true } +sp-transaction-pool = { workspace = true, default-features = true } +sp-transaction-storage-proof = { workspace = true, default-features = true } +sp-trie = { workspace = true, default-features = true } +sp-version = { workspace = true, default-features = true } +static_init = { workspace = true } +tempfile = { workspace = true } +thiserror = { workspace = true } +tokio = { features = ["parking_lot", "rt-multi-thread", "time"], workspace = true, default-features = true } tracing = { workspace = true, default-features = true } tracing-futures = { workspace = true } -async-trait = { workspace = true } -tokio = { features = ["parking_lot", "rt-multi-thread", "time"], workspace = true, default-features = true } -tempfile = { workspace = true } -directories = { workspace = true } -static_init = { workspace = true } -schnellru = { workspace = true } [dev-dependencies] -substrate-test-runtime-client = { workspace = true } substrate-test-runtime = { workspace = true } +substrate-test-runtime-client = { workspace = true } diff --git a/substrate/client/service/src/builder.rs b/substrate/client/service/src/builder.rs index 68ac94539df8972fa52a0c19aab5daeec1a86b44..a47a05c0a19012e6cf70653b0b37c29d77f6d1f5 100644 --- a/substrate/client/service/src/builder.rs +++ b/substrate/client/service/src/builder.rs @@ -25,7 +25,7 @@ use crate::{ start_rpc_servers, BuildGenesisBlock, GenesisBlockBuilder, RpcHandlers, SpawnTaskHandle, TaskManager, TransactionPoolAdapter, }; -use futures::{future::ready, FutureExt, StreamExt}; +use futures::{select, FutureExt, StreamExt}; use jsonrpsee::RpcModule; use log::info; use prometheus_endpoint::Registry; @@ -90,7 +90,11 @@ use sp_consensus::block_validation::{ use sp_core::traits::{CodeExecutor, SpawnNamed}; use sp_keystore::KeystorePtr; use sp_runtime::traits::{Block as BlockT, BlockIdTo, NumberFor, Zero}; -use std::{str::FromStr, sync::Arc, time::SystemTime}; +use std::{ + str::FromStr, + sync::Arc, + time::{Duration, SystemTime}, +}; /// Full client type. pub type TFullClient<TBl, TRtApi, TExec> = @@ -577,22 +581,42 @@ pub async fn propagate_transaction_notifications<Block, ExPool>( Block: BlockT, ExPool: MaintainedTransactionPool<Block = Block, Hash = <Block as BlockT>::Hash>, { + const TELEMETRY_INTERVAL: Duration = Duration::from_secs(1); + // transaction notifications - transaction_pool - .import_notification_stream() - .for_each(move |hash| { - tx_handler_controller.propagate_transaction(hash); - let status = transaction_pool.status(); - telemetry!( - telemetry; - SUBSTRATE_INFO; - "txpool.import"; - "ready" => status.ready, - "future" => status.future, - ); - ready(()) - }) - .await; + let mut notifications = transaction_pool.import_notification_stream().fuse(); + let mut timer = futures_timer::Delay::new(TELEMETRY_INTERVAL).fuse(); + let mut tx_imported = false; + + loop { + select! { + notification = notifications.next() => { + let Some(hash) = notification else { return }; + + tx_handler_controller.propagate_transaction(hash); + + tx_imported = true; + }, + _ = timer => { + timer = futures_timer::Delay::new(TELEMETRY_INTERVAL).fuse(); + + if !tx_imported { + continue; + } + + tx_imported = false; + let status = transaction_pool.status(); + + telemetry!( + telemetry; + SUBSTRATE_INFO; + "txpool.import"; + "ready" => status.ready, + "future" => status.future, + ); + } + } + } } /// Initialize telemetry with provided configuration and return telemetry handle @@ -731,8 +755,7 @@ where client.clone(), backend.clone(), genesis_hash, - // Defaults to sensible limits for the `Archive`. - sc_rpc_spec_v2::archive::ArchiveConfig::default(), + task_executor.clone(), ) .into_rpc(); rpc_api.merge(archive_v2).map_err(|e| Error::Application(e.into()))?; diff --git a/substrate/client/service/src/client/client.rs b/substrate/client/service/src/client/client.rs index ce5b92551bf2ef69931b2c6cf14c5fd2f42866c2..eddbb9260c053abd6c3fff646a36d1e96561e632 100644 --- a/substrate/client/service/src/client/client.rs +++ b/substrate/client/service/src/client/client.rs @@ -85,10 +85,8 @@ use std::{ sync::Arc, }; -#[cfg(feature = "test-helpers")] -use { - super::call_executor::LocalCallExecutor, sc_client_api::in_mem, sp_core::traits::CodeExecutor, -}; +use super::call_executor::LocalCallExecutor; +use sp_core::traits::CodeExecutor; type NotificationSinks<T> = Mutex<Vec<TracingUnboundedSender<T>>>; @@ -152,39 +150,6 @@ enum PrepareStorageChangesResult<Block: BlockT> { Discard(ImportResult), Import(Option<sc_consensus::StorageChanges<Block>>), } - -/// Create an instance of in-memory client. -#[cfg(feature = "test-helpers")] -pub fn new_in_mem<E, Block, G, RA>( - backend: Arc<in_mem::Backend<Block>>, - executor: E, - genesis_block_builder: G, - prometheus_registry: Option<Registry>, - telemetry: Option<TelemetryHandle>, - spawn_handle: Box<dyn SpawnNamed>, - config: ClientConfig<Block>, -) -> sp_blockchain::Result< - Client<in_mem::Backend<Block>, LocalCallExecutor<Block, in_mem::Backend<Block>, E>, Block, RA>, -> -where - E: CodeExecutor + sc_executor::RuntimeVersionOf, - Block: BlockT, - G: BuildGenesisBlock< - Block, - BlockImportOperation = <in_mem::Backend<Block> as backend::Backend<Block>>::BlockImportOperation, - >, -{ - new_with_backend( - backend, - executor, - genesis_block_builder, - spawn_handle, - prometheus_registry, - telemetry, - config, - ) -} - /// Client configuration items. #[derive(Debug, Clone)] pub struct ClientConfig<Block: BlockT> { @@ -218,7 +183,6 @@ impl<Block: BlockT> Default for ClientConfig<Block> { /// Create a client with the explicitly provided backend. /// This is useful for testing backend implementations. -#[cfg(feature = "test-helpers")] pub fn new_with_backend<B, E, Block, G, RA>( backend: Arc<B>, executor: E, diff --git a/substrate/client/service/src/client/mod.rs b/substrate/client/service/src/client/mod.rs index ec77a92f162f0c2927a8d5fb6537e4e2ebb861a8..3020b3d296f4bf1508cd1651b7f81f6a442443e9 100644 --- a/substrate/client/service/src/client/mod.rs +++ b/substrate/client/service/src/client/mod.rs @@ -56,5 +56,4 @@ pub use call_executor::LocalCallExecutor; pub use client::{Client, ClientConfig}; pub(crate) use code_provider::CodeProvider; -#[cfg(feature = "test-helpers")] -pub use self::client::{new_in_mem, new_with_backend}; +pub use self::client::new_with_backend; diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index 9c01d7288a819a78cc3b5d2ada2e82d7697b0006..52a19da220c05be35edf336c46745e496b76d06d 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -23,14 +23,11 @@ #![recursion_limit = "1024"] pub mod chain_ops; +pub mod client; pub mod config; pub mod error; mod builder; -#[cfg(feature = "test-helpers")] -pub mod client; -#[cfg(not(feature = "test-helpers"))] -mod client; mod metrics; mod task_manager; @@ -43,7 +40,7 @@ use std::{ use codec::{Decode, Encode}; use futures::{pin_mut, FutureExt, StreamExt}; use jsonrpsee::RpcModule; -use log::{debug, error, warn}; +use log::{debug, error, trace, warn}; use sc_client_api::{blockchain::HeaderBackend, BlockBackend, BlockchainEvents, ProofProvider}; use sc_network::{ config::MultiaddrWithPeerId, service::traits::NetworkService, NetworkBackend, NetworkBlock, @@ -477,7 +474,7 @@ impl<C, P> TransactionPoolAdapter<C, P> { /// Get transactions for propagation. /// /// Function extracted to simplify the test and prevent creating `ServiceFactory`. -fn transactions_to_propagate<Pool, B, H, E>(pool: &Pool) -> Vec<(H, B::Extrinsic)> +fn transactions_to_propagate<Pool, B, H, E>(pool: &Pool) -> Vec<(H, Arc<B::Extrinsic>)> where Pool: TransactionPool<Block = B, Hash = H, Error = E>, B: BlockT, @@ -488,7 +485,7 @@ where .filter(|t| t.is_propagable()) .map(|t| { let hash = t.hash().clone(); - let ex: B::Extrinsic = (**t.data()).clone(); + let ex = t.data().clone(); (hash, ex) }) .collect() @@ -509,7 +506,7 @@ where H: std::hash::Hash + Eq + sp_runtime::traits::Member + sp_runtime::traits::MaybeSerialize, E: 'static + IntoPoolError + From<sc_transaction_pool_api::error::Error>, { - fn transactions(&self) -> Vec<(H, B::Extrinsic)> { + fn transactions(&self) -> Vec<(H, Arc<B::Extrinsic>)> { transactions_to_propagate(&*self.pool) } @@ -541,7 +538,7 @@ where { Ok(_) => { let elapsed = start.elapsed(); - debug!(target: sc_transaction_pool::LOG_TARGET, "import transaction: {elapsed:?}"); + trace!(target: sc_transaction_pool::LOG_TARGET, "import transaction: {elapsed:?}"); TransactionImport::NewGood }, Err(e) => match e.into_pool_error() { @@ -562,10 +559,10 @@ where self.pool.on_broadcasted(propagations) } - fn transaction(&self, hash: &H) -> Option<B::Extrinsic> { + fn transaction(&self, hash: &H) -> Option<Arc<B::Extrinsic>> { self.pool.ready_transaction(hash).and_then( // Only propagable transactions should be resolved for network service. - |tx| if tx.is_propagable() { Some((**tx.data()).clone()) } else { None }, + |tx| tx.is_propagable().then(|| tx.data().clone()), ) } } @@ -599,8 +596,8 @@ mod tests { let transaction = Transfer { amount: 5, nonce: 0, - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Bob.into(), } .into_unchecked_extrinsic(); block_on(pool.submit_one(best.hash(), source, transaction.clone())).unwrap(); @@ -617,6 +614,6 @@ mod tests { // then assert_eq!(transactions.len(), 1); - assert!(TransferData::try_from(&transactions[0].1).is_ok()); + assert!(TransferData::try_from(&*transactions[0].1).is_ok()); } } diff --git a/substrate/client/service/test/Cargo.toml b/substrate/client/service/test/Cargo.toml index 0edfc5b193144c01a2923e7263423a7f08d69daf..45b2d8c5eea3a979b9bf0f4450c59beb9a6d2266 100644 --- a/substrate/client/service/test/Cargo.toml +++ b/substrate/client/service/test/Cargo.toml @@ -15,15 +15,13 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-channel = { workspace = true } array-bytes = { workspace = true, default-features = true } +async-channel = { workspace = true } +codec = { workspace = true, default-features = true } fdlimit = { workspace = true } futures = { workspace = true } log = { workspace = true, default-features = true } -codec = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -tempfile = { workspace = true } -tokio = { features = ["time"], workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-client-db = { workspace = true } @@ -31,17 +29,19 @@ sc-consensus = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } -sc-service = { features = ["test-helpers"], workspace = true, default-features = true } +sc-service = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } sp-storage = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } sp-trie = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } substrate-test-runtime = { workspace = true } substrate-test-runtime-client = { workspace = true } +tempfile = { workspace = true } +tokio = { features = ["time"], workspace = true, default-features = true } diff --git a/substrate/client/service/test/src/client/mod.rs b/substrate/client/service/test/src/client/mod.rs index 55bbfcdd8594800d6ab2d0cbdf9e6a3842b155b1..ef5de93d64caebd4eb02e5d625a6295047755cf5 100644 --- a/substrate/client/service/test/src/client/mod.rs +++ b/substrate/client/service/test/src/client/mod.rs @@ -29,7 +29,7 @@ use sc_consensus::{ BlockCheckParams, BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult, }; use sc_executor::WasmExecutor; -use sc_service::client::{new_in_mem, Client, LocalCallExecutor}; +use sc_service::client::{new_with_backend, Client, LocalCallExecutor}; use sp_api::ProvideRuntimeApi; use sp_consensus::{BlockOrigin, Error as ConsensusError, SelectChain}; use sp_core::{testing::TaskExecutor, traits::CallContext, H256}; @@ -48,8 +48,8 @@ use substrate_test_runtime_client::{ genesismap::{insert_genesis_block, GenesisStorageBuilder}, Block, BlockNumber, Digest, Hash, Header, RuntimeApi, Transfer, }, - AccountKeyring, BlockBuilderExt, ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt, - Sr25519Keyring, TestClientBuilder, TestClientBuilderExt, + BlockBuilderExt, ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt, Sr25519Keyring, + TestClientBuilder, TestClientBuilderExt, }; mod db; @@ -126,8 +126,8 @@ fn block1(genesis_hash: Hash, backend: &InMemoryBackend<BlakeTwo256>) -> Vec<u8> 1, genesis_hash, vec![Transfer { - from: AccountKeyring::One.into(), - to: AccountKeyring::Two.into(), + from: Sr25519Keyring::One.into(), + to: Sr25519Keyring::Two.into(), amount: 69 * DOLLARS, nonce: 0, }], @@ -158,7 +158,7 @@ fn finality_notification_check( fn construct_genesis_should_work_with_native() { let mut storage = GenesisStorageBuilder::new( vec![Sr25519Keyring::One.public().into(), Sr25519Keyring::Two.public().into()], - vec![AccountKeyring::One.into(), AccountKeyring::Two.into()], + vec![Sr25519Keyring::One.into(), Sr25519Keyring::Two.into()], 1000 * DOLLARS, ) .build(); @@ -189,7 +189,7 @@ fn construct_genesis_should_work_with_native() { fn construct_genesis_should_work_with_wasm() { let mut storage = GenesisStorageBuilder::new( vec![Sr25519Keyring::One.public().into(), Sr25519Keyring::Two.public().into()], - vec![AccountKeyring::One.into(), AccountKeyring::Two.into()], + vec![Sr25519Keyring::One.into(), Sr25519Keyring::Two.into()], 1000 * DOLLARS, ) .build(); @@ -223,14 +223,14 @@ fn client_initializes_from_genesis_ok() { assert_eq!( client .runtime_api() - .balance_of(client.chain_info().best_hash, AccountKeyring::Alice.into()) + .balance_of(client.chain_info().best_hash, Sr25519Keyring::Alice.into()) .unwrap(), 1000 * DOLLARS ); assert_eq!( client .runtime_api() - .balance_of(client.chain_info().best_hash, AccountKeyring::Ferdie.into()) + .balance_of(client.chain_info().best_hash, Sr25519Keyring::Ferdie.into()) .unwrap(), 0 * DOLLARS ); @@ -266,8 +266,8 @@ fn block_builder_works_with_transactions() { builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 42 * DOLLARS, nonce: 0, }) @@ -301,14 +301,14 @@ fn block_builder_works_with_transactions() { assert_eq!( client .runtime_api() - .balance_of(client.chain_info().best_hash, AccountKeyring::Alice.into()) + .balance_of(client.chain_info().best_hash, Sr25519Keyring::Alice.into()) .unwrap(), 958 * DOLLARS ); assert_eq!( client .runtime_api() - .balance_of(client.chain_info().best_hash, AccountKeyring::Ferdie.into()) + .balance_of(client.chain_info().best_hash, Sr25519Keyring::Ferdie.into()) .unwrap(), 42 * DOLLARS ); @@ -325,8 +325,8 @@ fn block_builder_does_not_include_invalid() { builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 42 * DOLLARS, nonce: 0, }) @@ -334,8 +334,8 @@ fn block_builder_does_not_include_invalid() { assert!(builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 30 * DOLLARS, nonce: 0, }) @@ -491,8 +491,8 @@ fn uncles_with_multiple_forks() { // this push is required as otherwise B2 has the same hash as A2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41 * DOLLARS, nonce: 0, }) @@ -531,8 +531,8 @@ fn uncles_with_multiple_forks() { // this push is required as otherwise C3 has the same hash as B3 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 1, }) @@ -549,8 +549,8 @@ fn uncles_with_multiple_forks() { // this push is required as otherwise D2 has the same hash as B2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 0, }) @@ -691,8 +691,8 @@ fn finality_target_on_longest_chain_with_multiple_forks() { // this push is required as otherwise B2 has the same hash as A2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41 * DOLLARS, nonce: 0, }) @@ -732,8 +732,8 @@ fn finality_target_on_longest_chain_with_multiple_forks() { // this push is required as otherwise C3 has the same hash as B3 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 1, }) @@ -751,8 +751,8 @@ fn finality_target_on_longest_chain_with_multiple_forks() { // this push is required as otherwise D2 has the same hash as B2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 0, }) @@ -982,8 +982,8 @@ fn finality_target_with_best_not_on_longest_chain() { // this push is required as otherwise B2 has the same hash as A2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41 * DOLLARS, nonce: 0, }) @@ -1134,8 +1134,8 @@ fn importing_diverged_finalized_block_should_trigger_reorg() { .unwrap(); // needed to make sure B1 gets a different hash from A1 b1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 0, }) @@ -1195,8 +1195,8 @@ fn finalizing_diverged_block_should_trigger_reorg() { .unwrap(); // needed to make sure B1 gets a different hash from A1 b1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 0, }) @@ -1303,8 +1303,8 @@ fn finality_notifications_content() { .unwrap(); // needed to make sure B1 gets a different hash from A1 b1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1, nonce: 0, }) @@ -1329,8 +1329,8 @@ fn finality_notifications_content() { .unwrap(); // needed to make sure B1 gets a different hash from A1 c1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 2 * DOLLARS, nonce: 0, }) @@ -1346,8 +1346,8 @@ fn finality_notifications_content() { // needed to make sure D3 gets a different hash from A3 d3.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 2 * DOLLARS, nonce: 0, }) @@ -1415,7 +1415,7 @@ fn state_reverted_on_reorg() { let current_balance = |client: &substrate_test_runtime_client::TestClient| { client .runtime_api() - .balance_of(client.chain_info().best_hash, AccountKeyring::Alice.into()) + .balance_of(client.chain_info().best_hash, Sr25519Keyring::Alice.into()) .unwrap() }; @@ -1428,8 +1428,8 @@ fn state_reverted_on_reorg() { .build() .unwrap(); a1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Bob.into(), amount: 10 * DOLLARS, nonce: 0, }) @@ -1443,8 +1443,8 @@ fn state_reverted_on_reorg() { .build() .unwrap(); b1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 50 * DOLLARS, nonce: 0, }) @@ -1460,8 +1460,8 @@ fn state_reverted_on_reorg() { .build() .unwrap(); a2.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Charlie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Charlie.into(), amount: 10 * DOLLARS, nonce: 1, }) @@ -1530,8 +1530,8 @@ fn doesnt_import_blocks_that_revert_finality() { // needed to make sure B1 gets a different hash from A1 b1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 0, }) @@ -1580,8 +1580,8 @@ fn doesnt_import_blocks_that_revert_finality() { // needed to make sure C1 gets a different hash from A1 and B1 c1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 2 * DOLLARS, nonce: 0, }) @@ -1788,8 +1788,8 @@ fn returns_status_for_pruned_blocks() { // b1 is created, but not imported b1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 0, }) @@ -2087,13 +2087,13 @@ fn cleans_up_closed_notification_sinks_on_block_import() { // NOTE: we need to build the client here instead of using the client // provided by test_runtime_client otherwise we can't access the private // `import_notification_sinks` and `finality_notification_sinks` fields. - let mut client = new_in_mem::<_, Block, _, RuntimeApi>( + let mut client = new_with_backend::<_, _, Block, _, RuntimeApi>( backend, executor, genesis_block_builder, + Box::new(TaskExecutor::new()), None, None, - Box::new(TaskExecutor::new()), client_config, ) .unwrap(); @@ -2191,8 +2191,8 @@ fn reorg_triggers_a_notification_even_for_sources_that_should_not_trigger_notifi // needed to make sure B1 gets a different hash from A1 b1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 0, }) diff --git a/substrate/client/statement-store/Cargo.toml b/substrate/client/statement-store/Cargo.toml index e5087eae6eca556e15033771f7ade7f0c3592465..c0219b294cede40480e4731c57bce19d98496a7e 100644 --- a/substrate/client/statement-store/Cargo.toml +++ b/substrate/client/statement-store/Cargo.toml @@ -17,18 +17,18 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] log = { workspace = true, default-features = true } -parking_lot = { workspace = true, default-features = true } parity-db = { workspace = true } -tokio = { features = ["time"], workspace = true, default-features = true } -sp-statement-store = { workspace = true, default-features = true } +parking_lot = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } +sc-keystore = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } +sp-statement-store = { workspace = true, default-features = true } +tokio = { features = ["time"], workspace = true, default-features = true } [dev-dependencies] -tempfile = { workspace = true } sp-tracing = { workspace = true } +tempfile = { workspace = true } diff --git a/substrate/client/storage-monitor/Cargo.toml b/substrate/client/storage-monitor/Cargo.toml index c017184ced66b41780d8dbfab6cbfab1cdde67e1..3d8cb72b1a92af234d3f9556fc02342aa0c39683 100644 --- a/substrate/client/storage-monitor/Cargo.toml +++ b/substrate/client/storage-monitor/Cargo.toml @@ -13,8 +13,8 @@ workspace = true [dependencies] clap = { features = ["derive", "string"], workspace = true } -log = { workspace = true, default-features = true } fs4 = { workspace = true } +log = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } -tokio = { features = ["time"], workspace = true, default-features = true } thiserror = { workspace = true } +tokio = { features = ["time"], workspace = true, default-features = true } diff --git a/substrate/client/sync-state-rpc/Cargo.toml b/substrate/client/sync-state-rpc/Cargo.toml index cbab8f4d7b0d87dcfb48bcfb9a021f300eee3217..91c30f5aa2cc213ac385178bc5a3756851a00e88 100644 --- a/substrate/client/sync-state-rpc/Cargo.toml +++ b/substrate/client/sync-state-rpc/Cargo.toml @@ -17,13 +17,13 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true, default-features = true } jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } -serde = { features = ["derive"], workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -thiserror = { workspace = true } sc-chain-spec = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus-babe = { workspace = true, default-features = true } sc-consensus-epochs = { workspace = true, default-features = true } sc-consensus-grandpa = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } diff --git a/substrate/client/sysinfo/Cargo.toml b/substrate/client/sysinfo/Cargo.toml index 190e6e279b90aa40b06ca031099e80136cd3e1f5..afc464c358811191f6deaa5afe1e6fdc90d269c8 100644 --- a/substrate/client/sysinfo/Cargo.toml +++ b/substrate/client/sysinfo/Cargo.toml @@ -17,20 +17,19 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +derive_more = { workspace = true, default-features = true } futures = { workspace = true } libc = { workspace = true } log = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } rand_pcg = { workspace = true } -derive_more = { workspace = true, default-features = true } regex = { workspace = true } +sc-telemetry = { workspace = true, default-features = true } serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } -sc-telemetry = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } [dev-dependencies] sp-runtime = { workspace = true, default-features = true } diff --git a/substrate/client/telemetry/Cargo.toml b/substrate/client/telemetry/Cargo.toml index f87e8b66f7318f1d5736fec4a1f0818ecbad4ac8..1ebff618e0cef584b89a99bc44905ebb93ba50ab 100644 --- a/substrate/client/telemetry/Cargo.toml +++ b/substrate/client/telemetry/Cargo.toml @@ -19,13 +19,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] chrono = { workspace = true } futures = { workspace = true } -libp2p = { features = ["dns", "tcp", "tokio", "wasm-ext", "websocket"], workspace = true } +libp2p = { features = ["dns", "tcp", "tokio", "websocket"], workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } pin-project = { workspace = true } -sc-utils = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } +sc-utils = { workspace = true, default-features = true } serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } thiserror = { workspace = true } diff --git a/substrate/client/telemetry/src/node.rs b/substrate/client/telemetry/src/node.rs index 0bbdbfb622ef1ba478b62174d277e49ac836ff64..2c8d424c4340f2d63dcca6711b6ee8133e1b2df6 100644 --- a/substrate/client/telemetry/src/node.rs +++ b/substrate/client/telemetry/src/node.rs @@ -18,7 +18,13 @@ use crate::TelemetryPayload; use futures::{channel::mpsc, prelude::*}; -use libp2p::{core::transport::Transport, Multiaddr}; +use libp2p::{ + core::{ + transport::{DialOpts, PortUse, Transport}, + Endpoint, + }, + Multiaddr, +}; use rand::Rng as _; use std::{ fmt, mem, @@ -229,7 +235,10 @@ where }, NodeSocket::ReconnectNow => { let addr = self.addr.clone(); - match self.transport.dial(addr) { + match self + .transport + .dial(addr, DialOpts { role: Endpoint::Dialer, port_use: PortUse::New }) + { Ok(d) => { log::trace!(target: "telemetry", "Re-dialing {}", self.addr); socket = NodeSocket::Dialing(d); diff --git a/substrate/client/tracing/Cargo.toml b/substrate/client/tracing/Cargo.toml index b8f5e40caf83b5c04d5823bb9ecefa0aa9b10bee..949f6f6018ad3fa3c7471a9fca39846482b8432c 100644 --- a/substrate/client/tracing/Cargo.toml +++ b/substrate/client/tracing/Cargo.toml @@ -16,30 +16,30 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -console = { workspace = true } -is-terminal = { workspace = true } chrono = { workspace = true } codec = { workspace = true, default-features = true } +console = { workspace = true } +is-terminal = { workspace = true } libc = { workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } rustc-hash = { workspace = true } -serde = { workspace = true, default-features = true } -thiserror = { workspace = true } -tracing = { workspace = true, default-features = true } -tracing-log = { workspace = true } -tracing-subscriber = { workspace = true, features = [ - "env-filter", - "parking_lot", -] } sc-client-api = { workspace = true, default-features = true } sc-tracing-proc-macro = { workspace = true, default-features = true } +serde = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-rpc = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } +thiserror = { workspace = true } +tracing = { workspace = true, default-features = true } +tracing-log = { workspace = true } +tracing-subscriber = { workspace = true, features = [ + "env-filter", + "parking_lot", +] } [dev-dependencies] criterion = { workspace = true, default-features = true } diff --git a/substrate/client/transaction-pool/Cargo.toml b/substrate/client/transaction-pool/Cargo.toml index d346add93a64df7809df8e6a3ae549da49788b0c..26bbf58f1522d39ee854072d0f908ed29f7d2117 100644 --- a/substrate/client/transaction-pool/Cargo.toml +++ b/substrate/client/transaction-pool/Cargo.toml @@ -25,12 +25,11 @@ itertools = { workspace = true } linked-hash-map = { workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true, default-features = true } -thiserror = { workspace = true } prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } @@ -38,8 +37,10 @@ sp-crypto-hashing = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } sp-transaction-pool = { workspace = true, default-features = true } -tokio-stream = { workspace = true } +thiserror = { workspace = true } tokio = { workspace = true, default-features = true, features = ["macros", "time"] } +tokio-stream = { workspace = true } +tracing = { workspace = true, default-features = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } diff --git a/substrate/client/transaction-pool/api/Cargo.toml b/substrate/client/transaction-pool/api/Cargo.toml index c55ee70b2cf59d213d83c1e20a959a6547ed9aff..6671492a4e926b8bed92384bc50cbadde3ed8573 100644 --- a/substrate/client/transaction-pool/api/Cargo.toml +++ b/substrate/client/transaction-pool/api/Cargo.toml @@ -17,10 +17,10 @@ codec = { workspace = true, default-features = true } futures = { workspace = true } log = { workspace = true, default-features = true } serde = { features = ["derive"], workspace = true, default-features = true } -thiserror = { workspace = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true } sp-runtime = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] serde_json = { workspace = true, default-features = true } diff --git a/substrate/client/transaction-pool/benches/basics.rs b/substrate/client/transaction-pool/benches/basics.rs index 0d8c1cbba9b414f9222248d7873fe61fcc496acc..5ba9dd40c15680ee2c5c83803f8bc62b59e96c57 100644 --- a/substrate/client/transaction-pool/benches/basics.rs +++ b/substrate/client/transaction-pool/benches/basics.rs @@ -152,7 +152,7 @@ fn uxt(transfer: TransferData) -> Extrinsic { } fn bench_configured(pool: Pool<TestApi>, number: u64, api: Arc<TestApi>) { - let source = TransactionSource::External; + let source = TimedTransactionSource::new_external(false); let mut futures = Vec::new(); let mut tags = Vec::new(); let at = HashAndNumber { @@ -171,7 +171,7 @@ fn bench_configured(pool: Pool<TestApi>, number: u64, api: Arc<TestApi>) { tags.push(to_tag(nonce, AccountId::from_h256(H256::from_low_u64_be(1)))); - futures.push(pool.submit_one(&at, source, xt)); + futures.push(pool.submit_one(&at, source.clone(), xt)); } let res = block_on(futures::future::join_all(futures.into_iter())); @@ -197,14 +197,22 @@ fn benchmark_main(c: &mut Criterion) { c.bench_function("sequential 50 tx", |b| { b.iter(|| { let api = Arc::from(TestApi::new_dependant()); - bench_configured(Pool::new(Default::default(), true.into(), api.clone()), 50, api); + bench_configured( + Pool::new_with_staticly_sized_rotator(Default::default(), true.into(), api.clone()), + 50, + api, + ); }); }); c.bench_function("random 100 tx", |b| { b.iter(|| { let api = Arc::from(TestApi::default()); - bench_configured(Pool::new(Default::default(), true.into(), api.clone()), 100, api); + bench_configured( + Pool::new_with_staticly_sized_rotator(Default::default(), true.into(), api.clone()), + 100, + api, + ); }); }); } diff --git a/substrate/client/transaction-pool/src/common/mod.rs b/substrate/client/transaction-pool/src/common/mod.rs index fb280e8780ad4927cfc5f0045f1378363f06afe1..446a5c2ec0225b2494c50264cd6a3d7044d307ff 100644 --- a/substrate/client/transaction-pool/src/common/mod.rs +++ b/substrate/client/transaction-pool/src/common/mod.rs @@ -25,6 +25,7 @@ pub(crate) mod log_xt; pub(crate) mod metrics; #[cfg(test)] pub(crate) mod tests; +pub(crate) mod tracing_log_xt; use futures::StreamExt; use std::sync::Arc; diff --git a/substrate/client/transaction-pool/src/common/tests.rs b/substrate/client/transaction-pool/src/common/tests.rs index b00cf5fbfede903b3adeeaf13bc8aa28281cd633..7f2cbe24d8ef62bae4ccb4ffda8d974d92c38c97 100644 --- a/substrate/client/transaction-pool/src/common/tests.rs +++ b/substrate/client/transaction-pool/src/common/tests.rs @@ -222,5 +222,5 @@ pub(crate) fn uxt(transfer: Transfer) -> Extrinsic { pub(crate) fn pool() -> (Pool<TestApi>, Arc<TestApi>) { let api = Arc::new(TestApi::default()); - (Pool::new(Default::default(), true.into(), api.clone()), api) + (Pool::new_with_staticly_sized_rotator(Default::default(), true.into(), api.clone()), api) } diff --git a/substrate/client/transaction-pool/src/common/tracing_log_xt.rs b/substrate/client/transaction-pool/src/common/tracing_log_xt.rs new file mode 100644 index 0000000000000000000000000000000000000000..4d1c5d09cc7ac41219e1a532281cb1d51bba2640 --- /dev/null +++ b/substrate/client/transaction-pool/src/common/tracing_log_xt.rs @@ -0,0 +1,69 @@ +// 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 <https://www.gnu.org/licenses/>. + +//! Utility for logging transaction collections with tracing crate. + +/// Logs every transaction from given `tx_collection` with given level. +macro_rules! log_xt { + (data: hash, target: $target:expr, $level:expr, $tx_collection:expr, $text_with_format:expr) => { + for tx in $tx_collection { + tracing::event!( + $level, + target = $target, + tx_hash = format!("{:?}", tx), + $text_with_format, + ); + } + }; + (data: hash, target: $target:expr, $level:expr, $tx_collection:expr, $text_with_format:expr, $($arg:expr),*) => { + for tx in $tx_collection { + tracing::event!( + $level, + target = $target, + tx_hash = format!("{:?}", tx), + $text_with_format, + $($arg),* + ); + } + }; + (data: tuple, target: $target:expr, $level:expr, $tx_collection:expr, $text_with_format:expr) => { + for tx in $tx_collection { + tracing::event!( + $level, + target = $target, + tx_hash = format!("{:?}", tx.0), + $text_with_format, + tx.1 + ); + } + }; +} +macro_rules! log_xt_trace { + (data: $datatype:ident, target: $target:expr, $($arg:tt)+) => { + $crate::common::tracing_log_xt::log_xt!(data: $datatype, target: $target, tracing::Level::TRACE, $($arg)+); + }; + (target: $target:expr, $tx_collection:expr, $text_with_format:expr) => { + $crate::common::tracing_log_xt::log_xt!(data: hash, target: $target, tracing::Level::TRACE, $tx_collection, $text_with_format); + }; + (target: $target:expr, $tx_collection:expr, $text_with_format:expr, $($arg:expr)*) => { + $crate::common::tracing_log_xt::log_xt!(data: hash, target: $target, tracing::Level::TRACE, $tx_collection, $text_with_format, $($arg)*); + }; +} + +pub(crate) use log_xt; +pub(crate) use log_xt_trace; diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs index ecae21395c9164b2e11b8361e7e79512219cf111..be20a1608961945c8727e6e6feb24146ba64e9f5 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs @@ -22,32 +22,67 @@ //! by any view are detected and properly notified. use crate::{ - common::log_xt::log_xt_trace, + common::tracing_log_xt::log_xt_trace, fork_aware_txpool::stream_map_util::next_event, - graph::{BlockHash, ChainApi, ExtrinsicHash}, + graph::{self, BlockHash, ExtrinsicHash}, LOG_TARGET, }; use futures::stream::StreamExt; -use log::{debug, trace}; use sc_transaction_pool_api::TransactionStatus; use sc_utils::mpsc; use sp_runtime::traits::Block as BlockT; use std::{ - collections::{hash_map::Entry, HashMap, HashSet}, + collections::{ + hash_map::{Entry, OccupiedEntry}, + HashMap, HashSet, + }, fmt::{self, Debug, Formatter}, pin::Pin, }; use tokio_stream::StreamMap; +use tracing::{debug, trace}; + +/// Represents a transaction that was removed from the transaction pool, including the reason of its +/// removal. +#[derive(Debug, PartialEq)] +pub struct DroppedTransaction<Hash> { + /// Hash of the dropped extrinsic. + pub tx_hash: Hash, + /// Reason of the transaction being dropped. + pub reason: DroppedReason<Hash>, +} + +impl<Hash> DroppedTransaction<Hash> { + /// Creates a new instance with reason set to `DroppedReason::Usurped(by)`. + pub fn new_usurped(tx_hash: Hash, by: Hash) -> Self { + Self { reason: DroppedReason::Usurped(by), tx_hash } + } + + /// Creates a new instance with reason set to `DroppedReason::LimitsEnforced`. + pub fn new_enforced_by_limts(tx_hash: Hash) -> Self { + Self { reason: DroppedReason::LimitsEnforced, tx_hash } + } +} + +/// Provides reason of why transactions was dropped. +#[derive(Debug, PartialEq)] +pub enum DroppedReason<Hash> { + /// Transaction was replaced by other transaction (e.g. because of higher priority). + Usurped(Hash), + /// Transaction was dropped because of internal pool limits being enforced. + LimitsEnforced, +} /// Dropped-logic related event from the single view. -pub type ViewStreamEvent<C> = crate::graph::DroppedByLimitsEvent<ExtrinsicHash<C>, BlockHash<C>>; +pub type ViewStreamEvent<C> = crate::graph::TransactionStatusEvent<ExtrinsicHash<C>, BlockHash<C>>; /// Dropped-logic stream of events coming from the single view. type ViewStream<C> = Pin<Box<dyn futures::Stream<Item = ViewStreamEvent<C>> + Send>>; /// Stream of extrinsic hashes that were dropped by the views and have no references by existing /// views. -pub(crate) type StreamOfDropped<C> = Pin<Box<dyn futures::Stream<Item = ExtrinsicHash<C>> + Send>>; +pub(crate) type StreamOfDropped<C> = + Pin<Box<dyn futures::Stream<Item = DroppedTransaction<ExtrinsicHash<C>>> + Send>>; /// A type alias for a sender used as the controller of the [`MultiViewDropWatcherContext`]. /// Used to send control commands from the [`MultiViewDroppedWatcherController`] to @@ -59,24 +94,24 @@ type Controller<T> = mpsc::TracingUnboundedSender<T>; type CommandReceiver<T> = mpsc::TracingUnboundedReceiver<T>; /// Commands to control the instance of dropped transactions stream [`StreamOfDropped`]. -enum Command<C> +enum Command<ChainApi> where - C: ChainApi, + ChainApi: graph::ChainApi, { /// Adds a new stream of dropped-related events originating in a view with a specific block /// hash - AddView(BlockHash<C>, ViewStream<C>), + AddView(BlockHash<ChainApi>, ViewStream<ChainApi>), /// Removes an existing view's stream associated with a specific block hash. - RemoveView(BlockHash<C>), - /// Removes internal states for given extrinsic hashes. + RemoveView(BlockHash<ChainApi>), + /// Removes referencing views for given extrinsic hashes. /// /// Intended to ba called on finalization. - RemoveFinalizedTxs(Vec<ExtrinsicHash<C>>), + RemoveFinalizedTxs(Vec<ExtrinsicHash<ChainApi>>), } -impl<C> Debug for Command<C> +impl<ChainApi> Debug for Command<ChainApi> where - C: ChainApi, + ChainApi: graph::ChainApi, { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { @@ -92,30 +127,114 @@ where /// /// This struct maintains a mapping of active views and their corresponding streams, as well as the /// state of each transaction with respect to these views. -struct MultiViewDropWatcherContext<C> +struct MultiViewDropWatcherContext<ChainApi> where - C: ChainApi, + ChainApi: graph::ChainApi, { /// A map that associates the views identified by corresponding block hashes with their streams /// of dropped-related events. This map is used to keep track of active views and their event /// streams. - stream_map: StreamMap<BlockHash<C>, ViewStream<C>>, + stream_map: StreamMap<BlockHash<ChainApi>, ViewStream<ChainApi>>, /// A receiver for commands to control the state of the stream, allowing the addition and /// removal of views. This is used to dynamically update which views are being tracked. - command_receiver: CommandReceiver<Command<C>>, - + command_receiver: CommandReceiver<Command<ChainApi>>, + /// For each transaction hash we keep the set of hashes representing the views that see this + /// transaction as ready or in_block. + /// + /// Even if all views referencing a ready transactions are removed, we still want to keep + /// transaction, there can be a fork which sees the transaction as ready. + /// + /// Once transaction is dropped, dropping view is removed from the set. + ready_transaction_views: HashMap<ExtrinsicHash<ChainApi>, HashSet<BlockHash<ChainApi>>>, /// For each transaction hash we keep the set of hashes representing the views that see this - /// transaction as ready or future. + /// transaction as future. + /// + /// Once all views referencing a future transactions are removed, the future can be dropped. /// /// Once transaction is dropped, dropping view is removed from the set. - transaction_states: HashMap<ExtrinsicHash<C>, HashSet<BlockHash<C>>>, + future_transaction_views: HashMap<ExtrinsicHash<ChainApi>, HashSet<BlockHash<ChainApi>>>, + + /// Transactions that need to be notified as dropped. + pending_dropped_transactions: Vec<ExtrinsicHash<ChainApi>>, } impl<C> MultiViewDropWatcherContext<C> where - C: ChainApi + 'static, - <<C as ChainApi>::Block as BlockT>::Hash: Unpin, + C: graph::ChainApi + 'static, + <<C as graph::ChainApi>::Block as BlockT>::Hash: Unpin, { + /// Provides the ready or future `HashSet` containing views referencing given transaction. + fn transaction_views( + &mut self, + tx_hash: ExtrinsicHash<C>, + ) -> Option<OccupiedEntry<ExtrinsicHash<C>, HashSet<BlockHash<C>>>> { + if let Entry::Occupied(views_keeping_tx_valid) = self.ready_transaction_views.entry(tx_hash) + { + return Some(views_keeping_tx_valid) + } + if let Entry::Occupied(views_keeping_tx_valid) = + self.future_transaction_views.entry(tx_hash) + { + return Some(views_keeping_tx_valid) + } + None + } + + /// Processes the command and updates internal state accordingly. + fn handle_command(&mut self, cmd: Command<C>) { + match cmd { + Command::AddView(key, stream) => { + trace!( + target: LOG_TARGET, + "dropped_watcher: Command::AddView {key:?} views:{:?}", + self.stream_map.keys().collect::<Vec<_>>() + ); + self.stream_map.insert(key, stream); + }, + Command::RemoveView(key) => { + trace!( + target: LOG_TARGET, + "dropped_watcher: Command::RemoveView {key:?} views:{:?}", + self.stream_map.keys().collect::<Vec<_>>() + ); + self.stream_map.remove(&key); + self.ready_transaction_views.iter_mut().for_each(|(tx_hash, views)| { + trace!( + target: LOG_TARGET, + "[{:?}] dropped_watcher: Command::RemoveView ready views: {:?}", + tx_hash, + views + ); + views.remove(&key); + }); + + self.future_transaction_views.iter_mut().for_each(|(tx_hash, views)| { + trace!( + target: LOG_TARGET, + "[{:?}] dropped_watcher: Command::RemoveView future views: {:?}", + tx_hash, + views + ); + views.remove(&key); + if views.is_empty() { + self.pending_dropped_transactions.push(*tx_hash); + } + }); + }, + Command::RemoveFinalizedTxs(xts) => { + log_xt_trace!( + target: LOG_TARGET, + xts.clone(), + "dropped_watcher: finalized xt removed" + ); + xts.iter().for_each(|xt| { + self.ready_transaction_views.remove(xt); + self.future_transaction_views.remove(xt); + }); + }, + } + } + /// Processes a `ViewStreamEvent` from a specific view and updates the internal state /// accordingly. /// @@ -125,41 +244,71 @@ where &mut self, block_hash: BlockHash<C>, event: ViewStreamEvent<C>, - ) -> Option<ExtrinsicHash<C>> { + ) -> Option<DroppedTransaction<ExtrinsicHash<C>>> { trace!( target: LOG_TARGET, - "dropped_watcher: handle_event: event:{:?} views:{:?}, ", - event, + "dropped_watcher: handle_event: event:{event:?} from:{block_hash:?} future_views:{:?} ready_views:{:?} stream_map views:{:?}, ", + self.future_transaction_views.get(&event.0), + self.ready_transaction_views.get(&event.0), self.stream_map.keys().collect::<Vec<_>>(), ); let (tx_hash, status) = event; match status { - TransactionStatus::Ready | TransactionStatus::Future => { - self.transaction_states.entry(tx_hash).or_default().insert(block_hash); + TransactionStatus::Future => { + self.future_transaction_views.entry(tx_hash).or_default().insert(block_hash); + }, + TransactionStatus::Ready | TransactionStatus::InBlock(..) => { + // note: if future transaction was once seen as the ready we may want to treat it + // as ready transaction. The rationale behind this is as follows: we want to remove + // unreferenced future transactions when the last referencing view is removed (to + // avoid clogging mempool). For ready transactions we prefer to keep them in mempool + // even if no view is currently referencing them. Future transcaction once seen as + // ready is likely quite close to be included in some future fork (it is close to be + // ready, so we make exception and treat such transaction as ready). + if let Some(mut views) = self.future_transaction_views.remove(&tx_hash) { + views.insert(block_hash); + self.ready_transaction_views.insert(tx_hash, views); + } else { + self.ready_transaction_views.entry(tx_hash).or_default().insert(block_hash); + } }, - TransactionStatus::Dropped | TransactionStatus::Usurped(_) => { - if let Entry::Occupied(mut views_keeping_tx_valid) = - self.transaction_states.entry(tx_hash) - { + TransactionStatus::Dropped => { + if let Some(mut views_keeping_tx_valid) = self.transaction_views(tx_hash) { views_keeping_tx_valid.get_mut().remove(&block_hash); - if views_keeping_tx_valid.get().is_empty() || - views_keeping_tx_valid - .get() - .iter() - .all(|h| !self.stream_map.contains_key(h)) - { - return Some(tx_hash) + if views_keeping_tx_valid.get().is_empty() { + return Some(DroppedTransaction::new_enforced_by_limts(tx_hash)) } } else { - debug!("[{:?}] dropped_watcher: removing (non-tracked) tx", tx_hash); - return Some(tx_hash) + debug!(target: LOG_TARGET, ?tx_hash, "dropped_watcher: removing (non-tracked) tx"); + return Some(DroppedTransaction::new_enforced_by_limts(tx_hash)) } }, + TransactionStatus::Usurped(by) => + return Some(DroppedTransaction::new_usurped(tx_hash, by)), _ => {}, }; None } + /// Gets pending dropped transactions if any. + fn get_pending_dropped_transaction(&mut self) -> Option<DroppedTransaction<ExtrinsicHash<C>>> { + while let Some(tx_hash) = self.pending_dropped_transactions.pop() { + // never drop transaction that was seen as ready. It may not have a referencing + // view now, but such fork can appear. + if self.ready_transaction_views.get(&tx_hash).is_some() { + continue + } + + if let Some(views) = self.future_transaction_views.get(&tx_hash) { + if views.is_empty() { + self.future_transaction_views.remove(&tx_hash); + return Some(DroppedTransaction::new_enforced_by_limts(tx_hash)) + } + } + } + None + } + /// Creates a new `StreamOfDropped` and its associated event stream controller. /// /// This method initializes the internal structures and unfolds the stream of dropped @@ -176,42 +325,29 @@ where let ctx = Self { stream_map: StreamMap::new(), command_receiver, - transaction_states: Default::default(), + ready_transaction_views: Default::default(), + future_transaction_views: Default::default(), + pending_dropped_transactions: Default::default(), }; let stream_map = futures::stream::unfold(ctx, |mut ctx| async move { loop { + if let Some(dropped) = ctx.get_pending_dropped_transaction() { + trace!("dropped_watcher: sending out (pending): {dropped:?}"); + return Some((dropped, ctx)); + } tokio::select! { biased; - cmd = ctx.command_receiver.next() => { - match cmd? { - Command::AddView(key,stream) => { - trace!(target: LOG_TARGET,"dropped_watcher: Command::AddView {key:?} views:{:?}",ctx.stream_map.keys().collect::<Vec<_>>()); - ctx.stream_map.insert(key,stream); - }, - Command::RemoveView(key) => { - trace!(target: LOG_TARGET,"dropped_watcher: Command::RemoveView {key:?} views:{:?}",ctx.stream_map.keys().collect::<Vec<_>>()); - ctx.stream_map.remove(&key); - ctx.transaction_states.iter_mut().for_each(|(_,state)| { - state.remove(&key); - }); - }, - Command::RemoveFinalizedTxs(xts) => { - log_xt_trace!(target: LOG_TARGET, xts.clone(), "[{:?}] dropped_watcher: finalized xt removed"); - xts.iter().for_each(|xt| { - ctx.transaction_states.remove(xt); - }); - - }, - } - }, - Some(event) = next_event(&mut ctx.stream_map) => { if let Some(dropped) = ctx.handle_event(event.0, event.1) { - debug!("dropped_watcher: sending out: {dropped:?}"); + trace!("dropped_watcher: sending out: {dropped:?}"); return Some((dropped, ctx)); } + }, + cmd = ctx.command_receiver.next() => { + ctx.handle_command(cmd?); } + } } }) @@ -225,30 +361,30 @@ where /// /// This struct provides methods to add and remove streams associated with views to and from the /// stream. -pub struct MultiViewDroppedWatcherController<C: ChainApi> { +pub struct MultiViewDroppedWatcherController<ChainApi: graph::ChainApi> { /// A controller allowing to update the state of the associated [`StreamOfDropped`]. - controller: Controller<Command<C>>, + controller: Controller<Command<ChainApi>>, } -impl<C: ChainApi> Clone for MultiViewDroppedWatcherController<C> { +impl<ChainApi: graph::ChainApi> Clone for MultiViewDroppedWatcherController<ChainApi> { fn clone(&self) -> Self { Self { controller: self.controller.clone() } } } -impl<C> MultiViewDroppedWatcherController<C> +impl<ChainApi> MultiViewDroppedWatcherController<ChainApi> where - C: ChainApi + 'static, - <<C as ChainApi>::Block as BlockT>::Hash: Unpin, + ChainApi: graph::ChainApi + 'static, + <<ChainApi as graph::ChainApi>::Block as BlockT>::Hash: Unpin, { /// Creates new [`StreamOfDropped`] and its controller. - pub fn new() -> (MultiViewDroppedWatcherController<C>, StreamOfDropped<C>) { - let (stream_map, ctrl) = MultiViewDropWatcherContext::<C>::event_stream(); + pub fn new() -> (MultiViewDroppedWatcherController<ChainApi>, StreamOfDropped<ChainApi>) { + let (stream_map, ctrl) = MultiViewDropWatcherContext::<ChainApi>::event_stream(); (Self { controller: ctrl }, stream_map.boxed()) } /// Notifies the [`StreamOfDropped`] that new view was created. - pub fn add_view(&self, key: BlockHash<C>, view: ViewStream<C>) { + pub fn add_view(&self, key: BlockHash<ChainApi>, view: ViewStream<ChainApi>) { let _ = self.controller.unbounded_send(Command::AddView(key, view)).map_err(|e| { trace!(target: LOG_TARGET, "dropped_watcher: add_view {key:?} send message failed: {e}"); }); @@ -256,14 +392,17 @@ where /// Notifies the [`StreamOfDropped`] that the view was destroyed and shall be removed the /// stream map. - pub fn remove_view(&self, key: BlockHash<C>) { + pub fn remove_view(&self, key: BlockHash<ChainApi>) { let _ = self.controller.unbounded_send(Command::RemoveView(key)).map_err(|e| { trace!(target: LOG_TARGET, "dropped_watcher: remove_view {key:?} send message failed: {e}"); }); } /// Removes status info for finalized transactions. - pub fn remove_finalized_txs(&self, xts: impl IntoIterator<Item = ExtrinsicHash<C>> + Clone) { + pub fn remove_finalized_txs( + &self, + xts: impl IntoIterator<Item = ExtrinsicHash<ChainApi>> + Clone, + ) { let _ = self .controller .unbounded_send(Command::RemoveFinalizedTxs(xts.into_iter().collect())) @@ -298,7 +437,7 @@ mod dropped_watcher_tests { watcher.add_view(block_hash, view_stream); let handle = tokio::spawn(async move { output_stream.take(1).collect::<Vec<_>>().await }); - assert_eq!(handle.await.unwrap(), vec![tx_hash]); + assert_eq!(handle.await.unwrap(), vec![DroppedTransaction::new_enforced_by_limts(tx_hash)]); } #[tokio::test] @@ -348,7 +487,10 @@ mod dropped_watcher_tests { watcher.add_view(block_hash0, view_stream0); watcher.add_view(block_hash1, view_stream1); let handle = tokio::spawn(async move { output_stream.take(1).collect::<Vec<_>>().await }); - assert_eq!(handle.await.unwrap(), vec![tx_hash1]); + assert_eq!( + handle.await.unwrap(), + vec![DroppedTransaction::new_enforced_by_limts(tx_hash1)] + ); } #[tokio::test] @@ -373,10 +515,11 @@ mod dropped_watcher_tests { watcher.add_view(block_hash0, view_stream0); assert!(output_stream.next().now_or_never().is_none()); + watcher.remove_view(block_hash0); watcher.add_view(block_hash1, view_stream1); let handle = tokio::spawn(async move { output_stream.take(1).collect::<Vec<_>>().await }); - assert_eq!(handle.await.unwrap(), vec![tx_hash]); + assert_eq!(handle.await.unwrap(), vec![DroppedTransaction::new_enforced_by_limts(tx_hash)]); } #[tokio::test] @@ -419,6 +562,6 @@ mod dropped_watcher_tests { let block_hash2 = H256::repeat_byte(0x03); watcher.add_view(block_hash2, view_stream2); let handle = tokio::spawn(async move { output_stream.take(1).collect::<Vec<_>>().await }); - assert_eq!(handle.await.unwrap(), vec![tx_hash]); + assert_eq!(handle.await.unwrap(), vec![DroppedTransaction::new_enforced_by_limts(tx_hash)]); } } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs index 065d0cb3a274f2d400b8e7f2bdc090c046153e4e..ffe6c20d92b72d2c3aa5c444224f6c32f83aad8e 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs @@ -23,16 +23,23 @@ use super::{ import_notification_sink::MultiViewImportNotificationSink, metrics::MetricsLink as PrometheusMetrics, multi_view_listener::MultiViewListener, - tx_mem_pool::{TxInMemPool, TxMemPool, TXMEMPOOL_TRANSACTION_LIMIT_MULTIPLIER}, + tx_mem_pool::{InsertionInfo, TxMemPool, TXMEMPOOL_TRANSACTION_LIMIT_MULTIPLIER}, view::View, view_store::ViewStore, }; use crate::{ api::FullChainApi, - common::log_xt::log_xt_trace, + common::tracing_log_xt::log_xt_trace, enactment_state::{EnactmentAction, EnactmentState}, - fork_aware_txpool::revalidation_worker, - graph::{self, base_pool::Transaction, ExtrinsicFor, ExtrinsicHash, IsValidator, Options}, + fork_aware_txpool::{ + dropped_watcher::{DroppedReason, DroppedTransaction}, + revalidation_worker, + }, + graph::{ + self, + base_pool::{TimedTransactionSource, Transaction}, + ExtrinsicFor, ExtrinsicHash, IsValidator, Options, + }, ReadyIteratorFor, LOG_TARGET, }; use async_trait::async_trait; @@ -45,14 +52,16 @@ use futures::{ use parking_lot::Mutex; use prometheus_endpoint::Registry as PrometheusRegistry; use sc_transaction_pool_api::{ - ChainEvent, ImportNotificationStream, MaintainedTransactionPool, PoolStatus, TransactionFor, - TransactionPool, TransactionSource, TransactionStatusStreamFor, TxHash, + error::Error as TxPoolApiError, ChainEvent, ImportNotificationStream, + MaintainedTransactionPool, PoolStatus, TransactionFor, TransactionPool, TransactionPriority, + TransactionSource, TransactionStatusStreamFor, TxHash, }; use sp_blockchain::{HashAndNumber, TreeRoute}; use sp_core::traits::SpawnEssentialNamed; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, NumberFor}, + transaction_validity::{TransactionValidityError, ValidTransaction}, }; use std::{ collections::{HashMap, HashSet}, @@ -61,6 +70,7 @@ use std::{ time::Instant, }; use tokio::select; +use tracing::{debug, info, trace, warn}; /// Fork aware transaction pool task, that needs to be polled. pub type ForkAwareTxPoolTask = Pin<Box<dyn Future<Output = ()> + Send>>; @@ -96,10 +106,10 @@ where /// /// `ready_iterator` is a closure that generates the result data to be sent to the pollers. fn trigger(&mut self, at: Block::Hash, ready_iterator: impl Fn() -> T) { - log::trace!(target: LOG_TARGET, "fatp::trigger {at:?} pending keys: {:?}", self.pollers.keys()); + trace!(target: LOG_TARGET, ?at, keys = ?self.pollers.keys(), "fatp::trigger"); let Some(pollers) = self.pollers.remove(&at) else { return }; pollers.into_iter().for_each(|p| { - log::debug!(target: LOG_TARGET, "trigger ready signal at block {}", at); + debug!(target: LOG_TARGET, "trigger ready signal at block {}", at); let _ = p.send(ready_iterator()); }); } @@ -183,7 +193,9 @@ where future_limits: crate::PoolLimit, mempool_max_transactions_count: usize, ) -> (Self, ForkAwareTxPoolTask) { - let listener = Arc::from(MultiViewListener::new()); + let (listener, listener_task) = MultiViewListener::new_with_worker(); + let listener = Arc::new(listener); + let (import_notification_sink, import_notification_sink_task) = MultiViewImportNotificationSink::new_with_worker(); @@ -197,14 +209,20 @@ where let (dropped_stream_controller, dropped_stream) = MultiViewDroppedWatcherController::<ChainApi>::new(); + + let view_store = + Arc::new(ViewStore::new(pool_api.clone(), listener, dropped_stream_controller)); + let dropped_monitor_task = Self::dropped_monitor_task( dropped_stream, mempool.clone(), + view_store.clone(), import_notification_sink.clone(), ); let combined_tasks = async move { tokio::select! { + _ = listener_task => {}, _ = import_notification_sink_task => {}, _ = dropped_monitor_task => {} } @@ -216,8 +234,8 @@ where ( Self { mempool, - api: pool_api.clone(), - view_store: Arc::new(ViewStore::new(pool_api, listener, dropped_stream_controller)), + api: pool_api, + view_store, ready_poll: Arc::from(Mutex::from(ReadyPoll::new())), enactment_state: Arc::new(Mutex::new(EnactmentState::new( best_block_hash, @@ -233,14 +251,17 @@ where ) } - /// Monitors the stream of dropped transactions and removes them from the mempool. + /// Monitors the stream of dropped transactions and removes them from the mempool and + /// view_store. /// /// This asynchronous task continuously listens for dropped transaction notifications provided /// within `dropped_stream` and ensures that these transactions are removed from the `mempool` - /// and `import_notification_sink` instances. + /// and `import_notification_sink` instances. For Usurped events, the transaction is also + /// removed from the view_store. async fn dropped_monitor_task( mut dropped_stream: StreamOfDropped<ChainApi>, mempool: Arc<TxMemPool<ChainApi, Block>>, + view_store: Arc<ViewStore<ChainApi, Block>>, import_notification_sink: MultiViewImportNotificationSink< Block::Hash, ExtrinsicHash<ChainApi>, @@ -248,12 +269,34 @@ where ) { loop { let Some(dropped) = dropped_stream.next().await else { - log::debug!(target: LOG_TARGET, "fatp::dropped_monitor_task: terminated..."); + debug!(target: LOG_TARGET, "fatp::dropped_monitor_task: terminated..."); break; }; - log::trace!(target: LOG_TARGET, "[{:?}] fatp::dropped notification, removing", dropped); - mempool.remove_dropped_transactions(&[dropped]).await; - import_notification_sink.clean_notified_items(&[dropped]); + let tx_hash = dropped.tx_hash; + trace!( + target: LOG_TARGET, + ?tx_hash, + reason = ?dropped.reason, + "fatp::dropped notification, removing" + ); + match dropped.reason { + DroppedReason::Usurped(new_tx_hash) => { + if let Some(new_tx) = mempool.get_by_hash(new_tx_hash) { + view_store.replace_transaction(new_tx.source(), new_tx.tx(), tx_hash).await; + } else { + trace!( + target: LOG_TARGET, + tx_hash = ?new_tx_hash, + "error: dropped_monitor_task: no entry in mempool for new transaction" + ); + } + }, + DroppedReason::LimitsEnforced => {}, + }; + + mempool.remove_transaction(&tx_hash); + view_store.listener.transaction_dropped(dropped); + import_notification_sink.clean_notified_items(&[tx_hash]); } } @@ -271,7 +314,10 @@ where finalized_hash: Block::Hash, ) -> Self { let metrics = PrometheusMetrics::new(prometheus); - let listener = Arc::from(MultiViewListener::new()); + + let (listener, listener_task) = MultiViewListener::new_with_worker(); + let listener = Arc::new(listener); + let (revalidation_queue, revalidation_task) = revalidation_worker::RevalidationQueue::new_with_worker(); @@ -282,20 +328,25 @@ where pool_api.clone(), listener.clone(), metrics.clone(), - TXMEMPOOL_TRANSACTION_LIMIT_MULTIPLIER * (options.ready.count + options.future.count), + TXMEMPOOL_TRANSACTION_LIMIT_MULTIPLIER * options.total_count(), options.ready.total_bytes + options.future.total_bytes, )); let (dropped_stream_controller, dropped_stream) = MultiViewDroppedWatcherController::<ChainApi>::new(); + + let view_store = + Arc::new(ViewStore::new(pool_api.clone(), listener, dropped_stream_controller)); let dropped_monitor_task = Self::dropped_monitor_task( dropped_stream, mempool.clone(), + view_store.clone(), import_notification_sink.clone(), ); let combined_tasks = async move { tokio::select! { + _ = listener_task => {} _ = revalidation_task => {}, _ = import_notification_sink_task => {}, _ = dropped_monitor_task => {} @@ -306,8 +357,8 @@ where Self { mempool, - api: pool_api.clone(), - view_store: Arc::new(ViewStore::new(pool_api, listener, dropped_stream_controller)), + api: pool_api, + view_store, ready_poll: Arc::from(Mutex::from(ReadyPoll::new())), enactment_state: Arc::new(Mutex::new(EnactmentState::new( best_block_hash, @@ -366,6 +417,16 @@ where self.mempool.unwatched_and_watched_count() } + /// Returns a set of future transactions for given block hash. + /// + /// Intended for logging / tests. + pub fn futures_at( + &self, + at: Block::Hash, + ) -> Option<Vec<Transaction<ExtrinsicHash<ChainApi>, ExtrinsicFor<ChainApi>>>> { + self.view_store.futures_at(at) + } + /// Returns a best-effort set of ready transactions for a given block, without executing full /// maintain process. /// @@ -378,7 +439,11 @@ where pub async fn ready_at_light(&self, at: Block::Hash) -> ReadyIteratorFor<ChainApi> { let start = Instant::now(); let api = self.api.clone(); - log::trace!(target: LOG_TARGET, "fatp::ready_at_light {:?}", at); + trace!( + target: LOG_TARGET, + ?at, + "fatp::ready_at_light" + ); let Ok(block_number) = self.api.resolve_block_number(at) else { return Box::new(std::iter::empty()) @@ -410,8 +475,12 @@ where let extrinsics = api .block_body(h.hash) .await - .unwrap_or_else(|e| { - log::warn!(target: LOG_TARGET, "Compute ready light transactions: error request: {}", e); + .unwrap_or_else(|error| { + warn!( + target: LOG_TARGET, + %error, + "Compute ready light transactions: error request" + ); None }) .unwrap_or_default() @@ -432,19 +501,25 @@ where let _ = tmp_view.pool.validated_pool().prune_tags(tags); let after_count = tmp_view.pool.validated_pool().status().ready; - log::debug!(target: LOG_TARGET, - "fatp::ready_at_light {} from {} before: {} to be removed: {} after: {} took:{:?}", - at, - best_view.at.hash, + debug!( + target: LOG_TARGET, + ?at, + best_view_hash = ?best_view.at.hash, before_count, - all_extrinsics.len(), + to_be_removed = all_extrinsics.len(), after_count, - start.elapsed() + duration = ?start.elapsed(), + "fatp::ready_at_light" ); Box::new(tmp_view.pool.validated_pool().ready()) } else { let empty: ReadyIteratorFor<ChainApi> = Box::new(std::iter::empty()); - log::debug!(target: LOG_TARGET, "fatp::ready_at_light {} -> empty, took:{:?}", at, start.elapsed()); + debug!( + target: LOG_TARGET, + ?at, + duration = ?start.elapsed(), + "fatp::ready_at_light -> empty" + ); empty } } @@ -464,8 +539,12 @@ where at: Block::Hash, timeout: std::time::Duration, ) -> ReadyIteratorFor<ChainApi> { - log::debug!(target: LOG_TARGET, "fatp::ready_at_with_timeout at {:?} allowed delay: {:?}", at, timeout); - + debug!( + target: LOG_TARGET, + ?at, + ?timeout, + "fatp::ready_at_with_timeout" + ); let timeout = futures_timer::Delay::new(timeout); let (view_already_exists, ready_at) = self.ready_at_internal(at); @@ -477,10 +556,10 @@ where select! { ready = ready_at => Some(ready), _ = timeout => { - log::warn!(target: LOG_TARGET, - "Timeout fired waiting for transaction pool at block: ({:?}). \ - Proceeding with production.", - at, + warn!( + target: LOG_TARGET, + ?at, + "Timeout fired waiting for transaction pool at block. Proceeding with production." ); None } @@ -500,7 +579,12 @@ where let mut ready_poll = self.ready_poll.lock(); if let Some((view, inactive)) = self.view_store.get_view_at(at, true) { - log::debug!(target: LOG_TARGET, "fatp::ready_at_internal {at:?} (inactive:{inactive:?})"); + debug!( + target: LOG_TARGET, + ?at, + ?inactive, + "fatp::ready_at_internal" + ); let iterator: ReadyIteratorFor<ChainApi> = Box::new(view.pool.validated_pool().ready()); return (true, async move { iterator }.boxed()); } @@ -508,15 +592,21 @@ where let pending = ready_poll .add(at) .map(|received| { - received.unwrap_or_else(|e| { - log::warn!(target: LOG_TARGET, "Error receiving ready-set iterator: {:?}", e); + received.unwrap_or_else(|error| { + warn!( + target: LOG_TARGET, + %error, + "Error receiving ready-set iterator" + ); Box::new(std::iter::empty()) }) }) .boxed(); - log::debug!(target: LOG_TARGET, - "fatp::ready_at_internal {at:?} pending keys: {:?}", - ready_poll.pollers.keys() + debug!( + target: LOG_TARGET, + ?at, + pending_keys = ?ready_poll.pollers.keys(), + "fatp::ready_at_internal" ); (false, pending) } @@ -548,7 +638,7 @@ where /// out: /// [ Ok(xth0), Ok(xth1), Err ] /// ``` -fn reduce_multiview_result<H, E>(input: HashMap<H, Vec<Result<H, E>>>) -> Vec<Result<H, E>> { +fn reduce_multiview_result<H, D, E>(input: HashMap<H, Vec<Result<D, E>>>) -> Vec<Result<D, E>> { let mut values = input.values(); let Some(first) = values.next() else { return Default::default(); @@ -594,40 +684,91 @@ where xts: Vec<TransactionFor<Self>>, ) -> Result<Vec<Result<TxHash<Self>, Self::Error>>, Self::Error> { let view_store = self.view_store.clone(); - log::debug!(target: LOG_TARGET, "fatp::submit_at count:{} views:{}", xts.len(), self.active_views_count()); - log_xt_trace!(target: LOG_TARGET, xts.iter().map(|xt| self.tx_hash(xt)), "[{:?}] fatp::submit_at"); + debug!( + target: LOG_TARGET, + count = xts.len(), + active_views_count = self.active_views_count(), + "fatp::submit_at" + ); + log_xt_trace!(target: LOG_TARGET, xts.iter().map(|xt| self.tx_hash(xt)), "fatp::submit_at"); let xts = xts.into_iter().map(Arc::from).collect::<Vec<_>>(); let mempool_results = self.mempool.extend_unwatched(source, &xts); if view_store.is_empty() { - return Ok(mempool_results) + return Ok(mempool_results + .into_iter() + .map(|r| r.map(|r| r.hash).map_err(Into::into)) + .collect::<Vec<_>>()) } + // Submit all the transactions to the mempool + let retries = mempool_results + .into_iter() + .zip(xts.clone()) + .map(|(result, xt)| async move { + match result { + Err(TxPoolApiError::ImmediatelyDropped) => + self.attempt_transaction_replacement(source, false, xt).await, + _ => result, + } + }) + .collect::<Vec<_>>(); + + let mempool_results = futures::future::join_all(retries).await; + + // Collect transactions that were successfully submitted to the mempool... let to_be_submitted = mempool_results .iter() .zip(xts) - .filter_map(|(result, xt)| result.as_ref().ok().map(|_| xt)) + .filter_map(|(result, xt)| { + result.as_ref().ok().map(|insertion| (insertion.source.clone(), xt)) + }) .collect::<Vec<_>>(); self.metrics .report(|metrics| metrics.submitted_transactions.inc_by(to_be_submitted.len() as _)); + // ... and submit them to the view_store. Please note that transactions rejected by mempool + // are not sent here. let mempool = self.mempool.clone(); - let results_map = view_store.submit(source, to_be_submitted.into_iter()).await; + let results_map = view_store.submit(to_be_submitted.into_iter()).await; let mut submission_results = reduce_multiview_result(results_map).into_iter(); + // Note for composing final result: + // + // For each failed insertion into the mempool, the mempool result should be placed into + // the returned vector. + // + // For each successful insertion into the mempool, the corresponding + // view_store submission result needs to be examined: + // - If there is an error during view_store submission, the transaction is removed from + // the mempool, and the final result recorded in the vector for this transaction is the + // view_store submission error. + // + // - If the view_store submission is successful, the transaction priority is updated in the + // mempool. + // + // Finally, it collects the hashes of updated transactions or submission errors (either + // from the mempool or view_store) into a returned vector. Ok(mempool_results .into_iter() .map(|result| { - result.and_then(|xt_hash| { - submission_results - .next() - .expect("The number of Ok results in mempool is exactly the same as the size of to-views-submission result. qed.") - .inspect_err(|_| - mempool.remove(xt_hash) - ) + result + .map_err(Into::into) + .and_then(|insertion| { + submission_results + .next() + .expect("The number of Ok results in mempool is exactly the same as the size of view_store submission result. qed.") + .inspect_err(|_|{ + mempool.remove_transaction(&insertion.hash); + }) }) + }) + .map(|r| r.map(|r| { + mempool.update_transaction_priority(&r); + r.hash() + })) .collect::<Vec<_>>()) } @@ -640,7 +781,12 @@ where source: TransactionSource, xt: TransactionFor<Self>, ) -> Result<TxHash<Self>, Self::Error> { - log::trace!(target: LOG_TARGET, "[{:?}] fatp::submit_one views:{}", self.tx_hash(&xt), self.active_views_count()); + trace!( + target: LOG_TARGET, + tx_hash = ?self.tx_hash(&xt), + active_views_count = self.active_views_count(), + "fatp::submit_one" + ); match self.submit_at(_at, source, vec![xt]).await { Ok(mut v) => v.pop().expect("There is exactly one element in result of submit_at. qed."), @@ -658,21 +804,34 @@ where source: TransactionSource, xt: TransactionFor<Self>, ) -> Result<Pin<Box<TransactionStatusStreamFor<Self>>>, Self::Error> { - log::trace!(target: LOG_TARGET, "[{:?}] fatp::submit_and_watch views:{}", self.tx_hash(&xt), self.active_views_count()); + trace!( + target: LOG_TARGET, + tx_hash = ?self.tx_hash(&xt), + views = self.active_views_count(), + "fatp::submit_and_watch" + ); let xt = Arc::from(xt); - let xt_hash = match self.mempool.push_watched(source, xt.clone()) { - Ok(xt_hash) => xt_hash, - Err(e) => return Err(e), - }; + + let InsertionInfo { hash: xt_hash, source: timed_source, .. } = + match self.mempool.push_watched(source, xt.clone()) { + Ok(result) => result, + Err(TxPoolApiError::ImmediatelyDropped) => + self.attempt_transaction_replacement(source, true, xt.clone()).await?, + Err(e) => return Err(e.into()), + }; self.metrics.report(|metrics| metrics.submitted_transactions.inc()); - let view_store = self.view_store.clone(); - let mempool = self.mempool.clone(); - view_store - .submit_and_watch(at, source, xt) + self.view_store + .submit_and_watch(at, timed_source, xt) .await - .inspect_err(|_| mempool.remove(xt_hash)) + .inspect_err(|_| { + self.mempool.remove_transaction(&xt_hash); + }) + .map(|mut outcome| { + self.mempool.update_transaction_priority(&outcome); + outcome.expect_watcher() + }) } /// Intended to remove transactions identified by the given hashes, and any dependent @@ -682,8 +841,7 @@ where // useful for verification for debugging purposes). fn remove_invalid(&self, hashes: &[TxHash<Self>]) -> Vec<Arc<Self::InPoolTransaction>> { if !hashes.is_empty() { - log::debug!(target: LOG_TARGET, "fatp::remove_invalid {}", hashes.len()); - log_xt_trace!(target:LOG_TARGET, hashes, "[{:?}] fatp::remove_invalid"); + log_xt_trace!(target:LOG_TARGET, hashes, "fatp::remove_invalid"); self.metrics .report(|metrics| metrics.removed_invalid_txs.inc_by(hashes.len() as _)); } @@ -733,11 +891,12 @@ where let result = most_recent_view .map(|block_hash| self.view_store.ready_transaction(block_hash, tx_hash)) .flatten(); - log::trace!( + trace!( target: LOG_TARGET, - "[{tx_hash:?}] ready_transaction: {} {:?}", - result.is_some(), - most_recent_view + ?tx_hash, + is_ready = result.is_some(), + ?most_recent_view, + "ready_transaction" ); result } @@ -777,36 +936,51 @@ where } } -impl<Block, Client> sc_transaction_pool_api::LocalTransactionPool - for ForkAwareTxPool<FullChainApi<Client, Block>, Block> +impl<ChainApi, Block> sc_transaction_pool_api::LocalTransactionPool + for ForkAwareTxPool<ChainApi, Block> where Block: BlockT, + ChainApi: 'static + graph::ChainApi<Block = Block>, <Block as BlockT>::Hash: Unpin, - Client: sp_api::ProvideRuntimeApi<Block> - + sc_client_api::BlockBackend<Block> - + sc_client_api::blockchain::HeaderBackend<Block> - + sp_runtime::traits::BlockIdTo<Block> - + sp_blockchain::HeaderMetadata<Block, Error = sp_blockchain::Error>, - Client: Send + Sync + 'static, - Client::Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block>, { type Block = Block; - type Hash = ExtrinsicHash<FullChainApi<Client, Block>>; - type Error = <FullChainApi<Client, Block> as graph::ChainApi>::Error; + type Hash = ExtrinsicHash<ChainApi>; + type Error = ChainApi::Error; fn submit_local( &self, _at: Block::Hash, xt: sc_transaction_pool_api::LocalTransactionFor<Self>, ) -> Result<Self::Hash, Self::Error> { - log::debug!(target: LOG_TARGET, "fatp::submit_local views:{}", self.active_views_count()); + debug!( + target: LOG_TARGET, + active_views_count = self.active_views_count(), + "fatp::submit_local" + ); let xt = Arc::from(xt); - let result = self - .mempool - .extend_unwatched(TransactionSource::Local, &[xt.clone()]) - .remove(0)?; - self.view_store.submit_local(xt).or_else(|_| Ok(result)) + let result = + self.mempool.extend_unwatched(TransactionSource::Local, &[xt.clone()]).remove(0); + + let insertion = match result { + Err(TxPoolApiError::ImmediatelyDropped) => self.attempt_transaction_replacement_sync( + TransactionSource::Local, + false, + xt.clone(), + ), + _ => result, + }?; + + self.view_store + .submit_local(xt) + .inspect_err(|_| { + self.mempool.remove_transaction(&insertion.hash); + }) + .map(|outcome| { + self.mempool.update_transaction_priority(&outcome); + outcome.hash() + }) + .or_else(|_| Ok(insertion.hash)) } } @@ -827,20 +1001,20 @@ where let hash_and_number = match tree_route.last() { Some(hash_and_number) => hash_and_number, None => { - log::warn!( + warn!( target: LOG_TARGET, - "Skipping ChainEvent - no last block in tree route {:?}", - tree_route, + ?tree_route, + "Skipping ChainEvent - no last block in tree route" ); return }, }; if self.has_view(&hash_and_number.hash) { - log::trace!( + trace!( target: LOG_TARGET, - "view already exists for block: {:?}", - hash_and_number, + ?hash_and_number, + "view already exists for block" ); return } @@ -875,12 +1049,12 @@ where at: &HashAndNumber<Block>, tree_route: &TreeRoute<Block>, ) -> Option<Arc<View<ChainApi>>> { - log::debug!( + debug!( target: LOG_TARGET, - "build_new_view: for: {:?} from: {:?} tree_route: {:?}", - at, - origin_view.as_ref().map(|v| v.at.clone()), - tree_route + ?at, + origin_view_at = ?origin_view.as_ref().map(|v| v.at.clone()), + ?tree_route, + "build_new_view" ); let mut view = if let Some(origin_view) = origin_view { let mut view = View::new_from_other(&origin_view, at); @@ -889,7 +1063,11 @@ where } view } else { - log::debug!(target: LOG_TARGET, "creating non-cloned view: for: {at:?}"); + debug!( + target: LOG_TARGET, + ?at, + "creating non-cloned view" + ); View::new( self.api.clone(), at.clone(), @@ -899,6 +1077,7 @@ where ) }; + let start = Instant::now(); // 1. Capture all import notification from the very beginning, so first register all //the listeners. self.import_notification_sink.add_view( @@ -911,24 +1090,40 @@ where view.pool.validated_pool().create_dropped_by_limits_stream().boxed(), ); - let start = Instant::now(); - let watched_xts = self.register_listeners(&mut view).await; - let duration = start.elapsed(); - log::debug!(target: LOG_TARGET, "register_listeners: at {at:?} took {duration:?}"); + self.view_store.listener.add_view_aggregated_stream( + view.at.hash, + view.pool.validated_pool().create_aggregated_stream().boxed(), + ); + // sync the transactions statuses and referencing views in all the listeners with newly + // cloned view. + view.pool.validated_pool().retrigger_notifications(); + debug!( + target: LOG_TARGET, + ?at, + duration = ?start.elapsed(), + "register_listeners" + ); // 2. Handle transactions from the tree route. Pruning transactions from the view first // will make some space for mempool transactions in case we are at the view's limits. let start = Instant::now(); self.update_view_with_fork(&view, tree_route, at.clone()).await; - let duration = start.elapsed(); - log::debug!(target: LOG_TARGET, "update_view_with_fork: at {at:?} took {duration:?}"); + debug!( + target: LOG_TARGET, + ?at, + duration = ?start.elapsed(), + "update_view_with_fork" + ); // 3. Finally, submit transactions from the mempool. let start = Instant::now(); - self.update_view_with_mempool(&mut view, watched_xts).await; - let duration = start.elapsed(); - log::debug!(target: LOG_TARGET, "update_view_with_mempool: at {at:?} took {duration:?}"); - + self.update_view_with_mempool(&mut view).await; + debug!( + target: LOG_TARGET, + ?at, + duration= ?start.elapsed(), + "update_view_with_mempool" + ); let view = Arc::from(view); self.view_store.insert_new_view(view.clone(), tree_route).await; Some(view) @@ -951,8 +1146,12 @@ where for h in tree_route.enacted().iter().rev() { api.block_body(h.hash) .await - .unwrap_or_else(|e| { - log::warn!(target: LOG_TARGET, "Compute ready light transactions: error request: {}", e); + .unwrap_or_else(|error| { + warn!( + target: LOG_TARGET, + %error, + "Compute ready light transactions: error request" + ); None }) .unwrap_or_default() @@ -963,56 +1162,15 @@ where }); } - log::debug!(target: LOG_TARGET, - "fatp::extrinsics_included_since_finalized {} from {} count: {} took:{:?}", - at, - recent_finalized_block, - all_extrinsics.len(), - start.elapsed() - ); - all_extrinsics - } - - /// For every watched transaction in the mempool registers a transaction listener in the view. - /// - /// The transaction listener for a given view is also added to multi-view listener. This allows - /// to track aggreagated progress of the transaction within the transaction pool. - /// - /// Function returns a list of currently watched transactions in the mempool. - async fn register_listeners( - &self, - view: &View<ChainApi>, - ) -> Vec<(ExtrinsicHash<ChainApi>, Arc<TxInMemPool<ChainApi, Block>>)> { - log::debug!( + debug!( target: LOG_TARGET, - "register_listeners: {:?} xts:{:?} v:{}", - view.at, - self.mempool.unwatched_and_watched_count(), - self.active_views_count() + ?at, + ?recent_finalized_block, + extrinsics_count = all_extrinsics.len(), + duration = ?start.elapsed(), + "fatp::extrinsics_included_since_finalized" ); - - //todo [#5495]: maybe we don't need to register listener in view? We could use - // multi_view_listener.transaction_in_block - let results = self - .mempool - .clone_watched() - .into_iter() - .map(|(tx_hash, tx)| { - let watcher = view.create_watcher(tx_hash); - let at = view.at.clone(); - async move { - log::trace!(target: LOG_TARGET, "[{:?}] adding watcher {:?}", tx_hash, at.hash); - self.view_store.listener.add_view_watcher_for_tx( - tx_hash, - at.hash, - watcher.into_stream().boxed(), - ); - (tx_hash, tx) - } - }) - .collect::<Vec<_>>(); - - future::join_all(results).await + all_extrinsics } /// Updates the given view with the transactions from the internal mempol. @@ -1024,83 +1182,57 @@ where /// If there are no views, and mempool transaction is reported as invalid for the given view, /// the transaction is reported as invalid and removed from the mempool. This does not apply to /// stale and temporarily banned transactions. - /// - /// As the listeners for watched transactions were registered at the very beginning of maintain - /// procedure (`register_listeners`), this function accepts the list of watched transactions - /// from the mempool for which listener was actually registered to avoid submit/maintain races. - async fn update_view_with_mempool( - &self, - view: &View<ChainApi>, - watched_xts: Vec<(ExtrinsicHash<ChainApi>, Arc<TxInMemPool<ChainApi, Block>>)>, - ) { - log::debug!( + async fn update_view_with_mempool(&self, view: &View<ChainApi>) { + debug!( target: LOG_TARGET, - "update_view_with_mempool: {:?} xts:{:?} v:{}", - view.at, - self.mempool.unwatched_and_watched_count(), - self.active_views_count() + view_at = ?view.at, + xts_count = ?self.mempool.unwatched_and_watched_count(), + active_views_count = self.active_views_count(), + "update_view_with_mempool" ); let included_xts = self.extrinsics_included_since_finalized(view.at.hash).await; - let xts = self.mempool.clone_unwatched(); - - let mut all_submitted_count = 0; - if !xts.is_empty() { - let unwatched_count = xts.len(); - let mut buckets = HashMap::<TransactionSource, Vec<ExtrinsicFor<ChainApi>>>::default(); - xts.into_iter() - .filter(|(hash, _)| !view.pool.validated_pool().pool.read().is_imported(hash)) - .filter(|(hash, _)| !included_xts.contains(&hash)) - .map(|(_, tx)| (tx.source(), tx.tx())) - .for_each(|(source, tx)| buckets.entry(source).or_default().push(tx)); - - for (source, xts) in buckets { - all_submitted_count += xts.len(); - let _ = view.submit_many(source, xts).await; - } - log::debug!(target: LOG_TARGET, "update_view_with_mempool: at {:?} unwatched {}/{}", view.at.hash, all_submitted_count, unwatched_count); - } - - let watched_submitted_count = watched_xts.len(); - let mut buckets = HashMap::< - TransactionSource, - Vec<(ExtrinsicHash<ChainApi>, ExtrinsicFor<ChainApi>)>, - >::default(); - watched_xts + let (hashes, xts_filtered): (Vec<_>, Vec<_>) = self + .mempool + .clone_transactions() .into_iter() + .filter(|(hash, _)| !view.is_imported(hash)) .filter(|(hash, _)| !included_xts.contains(&hash)) - .map(|(tx_hash, tx)| (tx.source(), tx_hash, tx.tx())) - .for_each(|(source, tx_hash, tx)| { - buckets.entry(source).or_default().push((tx_hash, tx)) - }); + .map(|(tx_hash, tx)| (tx_hash, (tx.source(), tx.tx()))) + .unzip(); - let mut watched_results = Vec::default(); - for (source, watched_xts) in buckets { - let hashes = watched_xts.iter().map(|i| i.0).collect::<Vec<_>>(); - let results = view - .submit_many(source, watched_xts.into_iter().map(|i| i.1)) - .await - .into_iter() - .zip(hashes) - .map(|(result, tx_hash)| result.or_else(|_| Err(tx_hash))) - .collect::<Vec<_>>(); - watched_results.extend(results); - } + let results = view + .submit_many(xts_filtered) + .await + .into_iter() + .zip(hashes) + .map(|(result, tx_hash)| { + result + .map(|outcome| self.mempool.update_transaction_priority(&outcome.into())) + .or_else(|_| Err(tx_hash)) + }) + .collect::<Vec<_>>(); - log::debug!(target: LOG_TARGET, "update_view_with_mempool: at {:?} watched {}/{}", view.at.hash, watched_submitted_count, self.mempool_len().1); + let submitted_count = results.len(); - all_submitted_count += watched_submitted_count; - let _ = all_submitted_count - .try_into() - .map(|v| self.metrics.report(|metrics| metrics.submitted_from_mempool_txs.inc_by(v))); + debug!( + target: LOG_TARGET, + view_at_hash = ?view.at.hash, + submitted_count, + mempool_len = self.mempool.len(), + "update_view_with_mempool" + ); + + self.metrics + .report(|metrics| metrics.submitted_from_mempool_txs.inc_by(submitted_count as _)); // if there are no views yet, and a single newly created view is reporting error, just send // out the invalid event, and remove transaction. if self.view_store.is_empty() { - for result in watched_results { + for result in results { if let Err(tx_hash) = result { - self.view_store.listener.invalidate_transactions(&[tx_hash]); - self.mempool.remove(tx_hash); + self.view_store.listener.transactions_invalidated(&[tx_hash]); + self.mempool.remove_transaction(&tx_hash); } } } @@ -1116,7 +1248,12 @@ where tree_route: &TreeRoute<Block>, hash_and_number: HashAndNumber<Block>, ) { - log::debug!(target: LOG_TARGET, "update_view_with_fork tree_route: {:?} {tree_route:?}", view.at); + debug!( + target: LOG_TARGET, + ?tree_route, + at = ?view.at, + "update_view_with_fork" + ); let api = self.api.clone(); // We keep track of everything we prune so that later we won't add @@ -1145,8 +1282,12 @@ where let block_transactions = api .block_body(hash) .await - .unwrap_or_else(|e| { - log::warn!(target: LOG_TARGET, "Failed to fetch block body: {}", e); + .unwrap_or_else(|error| { + warn!( + target: LOG_TARGET, + %error, + "Failed to fetch block body" + ); None }) .unwrap_or_default() @@ -1165,18 +1306,25 @@ where resubmitted_to_report += 1; if !contains { - log::trace!( + trace!( target: LOG_TARGET, - "[{:?}]: Resubmitting from retracted block {:?}", - tx_hash, - hash, + ?tx_hash, + ?hash, + "Resubmitting from retracted block" ); } !contains }) .map(|(tx_hash, tx)| { //find arc if tx is known - self.mempool.get_by_hash(tx_hash).unwrap_or_else(|| Arc::from(tx)) + self.mempool + .get_by_hash(tx_hash) + .map(|tx| (tx.source(), tx.tx())) + .unwrap_or_else(|| { + // These transactions are coming from retracted blocks, we + // should simply consider them external. + (TimedTransactionSource::new_external(true), Arc::from(tx)) + }) }), ); @@ -1185,16 +1333,7 @@ where }); } - let _ = view - .pool - .resubmit_at( - &hash_and_number, - // These transactions are coming from retracted blocks, we should - // simply consider them external. - TransactionSource::External, - resubmit_transactions, - ) - .await; + let _ = view.pool.resubmit_at(&hash_and_number, resubmit_transactions).await; } } @@ -1205,8 +1344,13 @@ where /// - purging finalized transactions from the mempool and triggering mempool revalidation, async fn handle_finalized(&self, finalized_hash: Block::Hash, tree_route: &[Block::Hash]) { let finalized_number = self.api.block_id_to_number(&BlockId::Hash(finalized_hash)); - log::debug!(target: LOG_TARGET, "handle_finalized {finalized_number:?} tree_route: {tree_route:?} views_count:{}", self.active_views_count()); - + debug!( + target: LOG_TARGET, + ?finalized_number, + ?tree_route, + active_views_count = self.active_views_count(), + "handle_finalized" + ); let finalized_xts = self.view_store.handle_finalized(finalized_hash, tree_route).await; self.mempool.purge_finalized_transactions(&finalized_xts).await; @@ -1223,17 +1367,120 @@ where ) .await; } else { - log::trace!(target: LOG_TARGET, "purge_transactions_later skipped, cannot find block number {finalized_number:?}"); + trace!( + target: LOG_TARGET, + ?finalized_number, + "purge_transactions_later skipped, cannot find block number" + ); } self.ready_poll.lock().remove_cancelled(); - log::trace!(target: LOG_TARGET, "handle_finalized after views_count:{:?}", self.active_views_count()); + trace!( + target: LOG_TARGET, + active_views_count = self.active_views_count(), + "handle_finalized after" + ); } /// Computes a hash of the provided transaction fn tx_hash(&self, xt: &TransactionFor<Self>) -> TxHash<Self> { self.api.hash_and_length(xt).0 } + + /// Attempts to find and replace a lower-priority transaction in the transaction pool with a new + /// one. + /// + /// This asynchronous function verifies the new transaction against the most recent view. If a + /// transaction with a lower priority exists in the transaction pool, it is replaced with the + /// new transaction. + /// + /// If no lower-priority transaction is found, the function returns an error indicating the + /// transaction was dropped immediately. + async fn attempt_transaction_replacement( + &self, + source: TransactionSource, + watched: bool, + xt: ExtrinsicFor<ChainApi>, + ) -> Result<InsertionInfo<ExtrinsicHash<ChainApi>>, TxPoolApiError> { + let at = self + .view_store + .most_recent_view + .read() + .ok_or(TxPoolApiError::ImmediatelyDropped)?; + + let (best_view, _) = self + .view_store + .get_view_at(at, false) + .ok_or(TxPoolApiError::ImmediatelyDropped)?; + + let (xt_hash, validated_tx) = best_view + .pool + .verify_one( + best_view.at.hash, + best_view.at.number, + TimedTransactionSource::from_transaction_source(source, false), + xt.clone(), + crate::graph::CheckBannedBeforeVerify::Yes, + ) + .await; + + let Some(priority) = validated_tx.priority() else { + return Err(TxPoolApiError::ImmediatelyDropped) + }; + + self.attempt_transaction_replacement_inner(xt, xt_hash, priority, source, watched) + } + + /// Sync version of [`Self::attempt_transaction_replacement`]. + fn attempt_transaction_replacement_sync( + &self, + source: TransactionSource, + watched: bool, + xt: ExtrinsicFor<ChainApi>, + ) -> Result<InsertionInfo<ExtrinsicHash<ChainApi>>, TxPoolApiError> { + let at = self + .view_store + .most_recent_view + .read() + .ok_or(TxPoolApiError::ImmediatelyDropped)?; + + let ValidTransaction { priority, .. } = self + .api + .validate_transaction_blocking(at, TransactionSource::Local, Arc::from(xt.clone())) + .map_err(|_| TxPoolApiError::ImmediatelyDropped)? + .map_err(|e| match e { + TransactionValidityError::Invalid(i) => TxPoolApiError::InvalidTransaction(i), + TransactionValidityError::Unknown(u) => TxPoolApiError::UnknownTransaction(u), + })?; + let xt_hash = self.hash_of(&xt); + self.attempt_transaction_replacement_inner(xt, xt_hash, priority, source, watched) + } + + fn attempt_transaction_replacement_inner( + &self, + xt: ExtrinsicFor<ChainApi>, + tx_hash: ExtrinsicHash<ChainApi>, + priority: TransactionPriority, + source: TransactionSource, + watched: bool, + ) -> Result<InsertionInfo<ExtrinsicHash<ChainApi>>, TxPoolApiError> { + let insertion_info = + self.mempool.try_insert_with_replacement(xt, priority, source, watched)?; + + for worst_hash in &insertion_info.removed { + log::trace!(target: LOG_TARGET, "removed: {worst_hash:?} replaced by {tx_hash:?}"); + self.view_store + .listener + .transaction_dropped(DroppedTransaction::new_enforced_by_limts(*worst_hash)); + + self.view_store + .remove_transaction_subtree(*worst_hash, |listener, removed_tx_hash| { + listener.limits_enforced(&removed_tx_hash); + }); + } + + return Ok(insertion_info) + } } #[async_trait] @@ -1246,7 +1493,11 @@ where /// Executes the maintainance for the given chain event. async fn maintain(&self, event: ChainEvent<Self::Block>) { let start = Instant::now(); - log::debug!(target: LOG_TARGET, "processing event: {event:?}"); + debug!( + target: LOG_TARGET, + ?event, + "processing event" + ); self.view_store.finish_background_revalidations().await; @@ -1270,8 +1521,12 @@ where .update(&event, &compute_tree_route, &block_id_to_number); match result { - Err(msg) => { - log::trace!(target: LOG_TARGET, "enactment_state::update error: {msg}"); + Err(error) => { + trace!( + target: LOG_TARGET, + %error, + "enactment_state::update error" + ); self.enactment_state.lock().force_update(&event); }, Ok(EnactmentAction::Skip) => return, @@ -1297,23 +1552,25 @@ where ChainEvent::Finalized { hash, ref tree_route } => { self.handle_finalized(hash, tree_route).await; - log::trace!( + trace!( target: LOG_TARGET, - "on-finalized enacted: {tree_route:?}, previously finalized: \ - {prev_finalized_block:?}", + ?tree_route, + ?prev_finalized_block, + "on-finalized enacted" ); }, } - let maintain_duration = start.elapsed(); + let duration = start.elapsed(); - log::info!( + info!( target: LOG_TARGET, - "maintain: txs:{:?} views:[{};{:?}] event:{event:?} took:{:?}", - self.mempool_len(), - self.active_views_count(), - self.views_stats(), - maintain_duration + txs = ?self.mempool_len(), + active_views_count = self.active_views_count(), + views = ?self.views_stats(), + ?event, + ?duration, + "maintain" ); self.metrics.report(|metrics| { @@ -1324,7 +1581,7 @@ where watched.try_into().map(|v| metrics.watched_txs.set(v)), unwatched.try_into().map(|v| metrics.unwatched_txs.set(v)), ); - metrics.maintain_duration.observe(maintain_duration.as_secs_f64()); + metrics.maintain_duration.observe(duration.as_secs_f64()); }); } } @@ -1381,7 +1638,7 @@ mod reduce_multiview_result_tests { fn empty() { sp_tracing::try_init_simple(); let input = HashMap::default(); - let r = reduce_multiview_result::<H256, Error>(input); + let r = reduce_multiview_result::<H256, H256, Error>(input); assert!(r.is_empty()); } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/import_notification_sink.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/import_notification_sink.rs index 7fbdcade63b86a9f6a78e8264b05e728f7c852e4..1ca287fa237151e4a46f0e56110edb276148f5c1 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/import_notification_sink.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/import_notification_sink.rs @@ -27,7 +27,6 @@ use futures::{ stream::StreamExt, Future, FutureExt, }; -use log::trace; use parking_lot::RwLock; use sc_utils::mpsc; use std::{ @@ -38,6 +37,7 @@ use std::{ sync::Arc, }; use tokio_stream::StreamMap; +use tracing::trace; /// A type alias for a pinned, boxed stream of items of type `I`. /// This alias is particularly useful for defining the types of the incoming streams from various @@ -109,14 +109,22 @@ where cmd = ctx.command_receiver.next() => { match cmd? { Command::AddView(key,stream) => { - trace!(target: LOG_TARGET,"Command::AddView {key:?}"); + trace!( + target: LOG_TARGET, + ?key, + "Command::AddView" + ); ctx.stream_map.insert(key,stream); }, } }, Some(event) = next_event(&mut ctx.stream_map) => { - trace!(target: LOG_TARGET, "import_notification_sink: select_next_some -> {:?}", event); + trace!( + target: LOG_TARGET, + ?event, + "import_notification_sink: select_next_some" + ); return Some((event.1, ctx)); } } @@ -179,9 +187,17 @@ where async move { if already_notified_items.write().insert(event.clone()) { external_sinks.write().retain_mut(|sink| { - trace!(target: LOG_TARGET, "[{:?}] import_sink_worker sending out imported", event); - if let Err(e) = sink.try_send(event.clone()) { - trace!(target: LOG_TARGET, "import_sink_worker sending message failed: {e}"); + trace!( + target: LOG_TARGET, + ?event, + "import_sink_worker sending out imported" + ); + if let Err(error) = sink.try_send(event.clone()) { + trace!( + target: LOG_TARGET, + %error, + "import_sink_worker sending message failed" + ); false } else { true @@ -199,12 +215,17 @@ where /// The new view's stream is added to the internal aggregated stream context by sending command /// to its `command_receiver`. pub fn add_view(&self, key: K, view: StreamOf<I>) { - let _ = self - .controller - .unbounded_send(Command::AddView(key.clone(), view)) - .map_err(|e| { - trace!(target: LOG_TARGET, "add_view {key:?} send message failed: {e}"); - }); + let _ = + self.controller + .unbounded_send(Command::AddView(key.clone(), view)) + .map_err(|error| { + trace!( + target: LOG_TARGET, + ?key, + %error, + "add_view send message failed" + ); + }); } /// Creates and returns a new external stream of ready transactions hashes notifications. @@ -326,6 +347,7 @@ mod tests { let j0 = tokio::spawn(runnable); let stream = ctrl.event_stream(); + let stream2 = ctrl.event_stream(); let mut v1 = View::new(vec![(10, 1), (10, 2), (10, 3)]); let mut v2 = View::new(vec![(20, 1), (20, 2), (20, 6)]); @@ -342,20 +364,16 @@ mod tests { ctrl.add_view(1000, o1); ctrl.add_view(2000, o2); - let j4 = { - let ctrl = ctrl.clone(); - tokio::spawn(async move { - tokio::time::sleep(Duration::from_millis(70)).await; - ctrl.clean_notified_items(&vec![1, 3]); - ctrl.add_view(3000, o3.boxed()); - }) - }; + let out = stream.take(4).collect::<Vec<_>>().await; + assert_eq!(out, vec![1, 2, 3, 6]); - let out = stream.take(6).collect::<Vec<_>>().await; + ctrl.clean_notified_items(&vec![1, 3]); + ctrl.add_view(3000, o3.boxed()); + let out = stream2.take(6).collect::<Vec<_>>().await; assert_eq!(out, vec![1, 2, 3, 6, 1, 3]); - drop(ctrl); - futures::future::join_all(vec![j0, j1, j2, j3, j4]).await; + drop(ctrl); + futures::future::join_all(vec![j0, j1, j2, j3]).await; } #[tokio::test] diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/mod.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/mod.rs index 5f7294a24fd75df4d4bea38ee585eb744f2123ab..2c4da0182a2524431077ed3284b406bc6fc8869c 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/mod.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/mod.rs @@ -84,7 +84,8 @@ //! //! ### Multi-view listeners //! There is a number of event streams that are provided by individual views: -//! - [transaction status][`Watcher`], +//! - aggregated stream of [transactions statuses][`AggregatedStream`] for all the transactions +//! within the view in the form of `(transaction-hash, status)` tuple, //! - [ready notification][`vp::import_notification_stream`] (see [networking //! section](#networking)), //! - [dropped notification][`create_dropped_by_limits_stream`]. @@ -93,10 +94,9 @@ //! internally). Those aggregators are often referred as multi-view listeners and they implement //! stream-specific or event-specific logic. //! -//! The most important is [`MultiViewListener`] which is owned by view store. -//! More information about it is provided in [transaction -//! route](#transaction-route-submit_and_watch) section. -//! +//! The most important is [`MultiViewListener`] which is owned by view store. Some internal details +//! on events' flow is provided in [transaction status](#monitoring-the-status-of-a-transaction) +//! section. //! //! ### Intermediate transactions buffer: [`TxMemPool`] //! The main purpose of an internal [`TxMemPool`] (referred to as *mempool*) is to prevent a @@ -106,10 +106,11 @@ //! procedure. Additionally, it allows the pool to accept transactions when no blocks have been //! reported yet. //! -//! Since watched and non-watched transactions require a different treatment, the *mempool* keeps a -//! track on how the transaction was submitted. The [transaction source][`TransactionSource`] used -//! to submit transactions also needs to be kept in the *mempool*. The *mempool* transaction is a -//! simple [wrapper][`TxInMemPool`] around the [`Arc`] reference to the actual extrinsic body. +//! The *mempool* keeps a track on how the transaction was submitted - keeping number of watched and +//! non-watched transactions is useful for testing and metrics. The [transaction +//! source][`TransactionSource`] used to submit transactions also needs to be kept in the *mempool*. +//! The *mempool* transaction is a simple [wrapper][`TxInMemPool`] around the [`Arc`] reference to +//! the actual extrinsic body. //! //! Once the view is created, all transactions from *mempool* are submitted to and validated at this //! view. @@ -138,20 +139,37 @@ //! ### Transaction route: [`submit_and_watch`][`api_submit_and_watch`] //! //! The [`submit_and_watch`] function allows to submit the transaction and track its -//! [status][`TransactionStatus`] within the pool. Every view is providing an independent -//! [stream][`View::submit_and_watch`] of events, which needs to be merged into the single stream -//! exposed to the [external listener][`TransactionStatusStreamFor`]. For majority of events simple -//! forwarding of events would not work (e.g. we could get multiple [`Ready`] events, or [`Ready`] / -//! [`Future`] mix). Some additional stateful logic is required to filter and process the views' -//! events. It is also easier to trigger some events (e.g. [`Finalized`], [`Invalid`], and -//! [`Broadcast`]) using some side-channel and simply ignoring these events from the view. All the -//! before mentioned functionality is provided by the [`MultiViewListener`]. -//! -//! When watched transaction is submitted to the pool it is added the *mempool* with watched -//! flag. The external stream for the transaction is created in a [`MultiViewListener`]. Then -//! transaction is submitted to every active [`View`] (using -//! [`submit_and_watch`][`View::submit_and_watch`]) and the resulting -//! views' stream is connected to the [`MultiViewListener`]. +//! [status][`TransactionStatus`] within the pool. +//! +//! When a watched transaction is submitted to the pool it is added to the *mempool* with the +//! watched flag. The external stream for the transaction is created in a [`MultiViewListener`]. +//! Then a transaction is submitted to every active [`View`] (using +//! [`submit_many`][`View::submit_many`]). The view's [aggregated +//! stream][`create_aggregated_stream`] was already connected to the [`MultiViewListener`] when new +//! view was created, so no additional action is required upon the submission. The view will provide +//! the required updates for all the transactions over this single stream. +//! +//! +//! #### Monitoring the status of a transaction +//! +//! Transaction status monitoring and triggering events to [external +//! listener][`TransactionStatusStreamFor`] (e.g. to RPC client) is responsibility of the +//! [`MultiViewListener`]. +//! +//! Every view is providing an independent aggreagated [stream][`create_aggregated_stream`] of +//! events for all transactions in this view, which needs to be merged into the single stream +//! exposed to the [external listener][`TransactionStatusStreamFor`] (e.g. to RPC client). For +//! majority of events simple forwarding would not work (e.g. we could get multiple [`Ready`] +//! events, or [`Ready`] / [`Future`] mix). Some additional stateful logic (implemented by +//! [`MultiViewListener`]) is required to filter and process the views' events. +//! +//! It is not possible to trigger some external events (e.g., [`Dropped`], [`Finalized`], +//! [`Invalid`], and [`Broadcast`]) using only the view-aggregated streams. These events require a +//! pool-wide understanding of the transaction state. For example, dropping a transaction from a +//! single view does not mean it was dropped from other views. Broadcast and finalized notifications +//! are sent to the transaction pool API, not at the view level. These events are simply ignored +//! when they originate in the view. The pool uses a dedicated side channel exposed by +//! [`MultiViewListener`] to trigger the beforementioned events. //! //! ### Maintain //! The transaction pool exposes the [task][`notification_future`] that listens to the @@ -169,8 +187,8 @@ //! *mempool* //! - all transactions from the *mempool* (with some obvious filtering applied) are submitted to //! the view, -//! - for all watched transactions from the *mempool* the watcher is registered in the new view, -//! and it is connected to the multi-view-listener, +//! - the new [aggregated stream][`create_aggregated_stream`] of all transactions statuses is +//! created for the new view and it is connected to the multi-view-listener, //! - [update the view][ForkAwareTxPool::update_view_with_fork] with the transactions from the [tree //! route][`TreeRoute`] (which is computed from the recent best block to newly notified one by //! [enactment state][`EnactmentState`] helper): @@ -292,7 +310,7 @@ //! [`View`]: crate::fork_aware_txpool::view::View //! [`view::revalidate`]: crate::fork_aware_txpool::view::View::revalidate //! [`start_background_revalidation`]: crate::fork_aware_txpool::view::View::start_background_revalidation -//! [`View::submit_and_watch`]: crate::fork_aware_txpool::view::View::submit_and_watch +//! [`View::submit_many`]: crate::fork_aware_txpool::view::View::submit_many //! [`ViewStore`]: crate::fork_aware_txpool::view_store::ViewStore //! [`finish_background_revalidations`]: crate::fork_aware_txpool::view_store::ViewStore::finish_background_revalidations //! [find_best_view]: crate::fork_aware_txpool::view_store::ViewStore::find_best_view @@ -305,10 +323,12 @@ //! [`MultiViewListener`]: crate::fork_aware_txpool::multi_view_listener::MultiViewListener //! [`Pool`]: crate::graph::Pool //! [`Watcher`]: crate::graph::watcher::Watcher +//! [`AggregatedStream`]: crate::graph::AggregatedStream //! [`Options`]: crate::graph::Options //! [`vp::import_notification_stream`]: ../graph/validated_pool/struct.ValidatedPool.html#method.import_notification_stream //! [`vp::enforce_limits`]: ../graph/validated_pool/struct.ValidatedPool.html#method.enforce_limits //! [`create_dropped_by_limits_stream`]: ../graph/validated_pool/struct.ValidatedPool.html#method.create_dropped_by_limits_stream +//! [`create_aggregated_stream`]: ../graph/validated_pool/struct.ValidatedPool.html#method.create_aggregated_stream //! [`ChainEvent`]: sc_transaction_pool_api::ChainEvent //! [`TransactionStatusStreamFor`]: sc_transaction_pool_api::TransactionStatusStreamFor //! [`api_submit`]: sc_transaction_pool_api::TransactionPool::submit_at @@ -323,6 +343,7 @@ //! [`Invalid`]:sc_transaction_pool_api::TransactionStatus::Invalid //! [`InBlock`]:sc_transaction_pool_api::TransactionStatus::InBlock //! [`Finalized`]:sc_transaction_pool_api::TransactionStatus::Finalized +//! [`Dropped`]:sc_transaction_pool_api::TransactionStatus::Dropped //! [`ReadyTransactions`]:sc_transaction_pool_api::ReadyTransactions //! [`dropped_monitor_task`]: ForkAwareTxPool::dropped_monitor_task //! [`ready_poll`]: ForkAwareTxPool::ready_poll diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs index 8d0e69db2e9ac3e81f07d3d7b3bb8390f422442d..107c2941ec183470e91efb28d9d46e6156713aa0 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs @@ -21,20 +21,25 @@ //! aggregated streams of transaction events. use crate::{ + common::tracing_log_xt::log_xt_trace, fork_aware_txpool::stream_map_util::next_event, - graph::{self, BlockHash, ExtrinsicHash}, + graph::{self, BlockHash, ExtrinsicHash, TransactionStatusEvent}, LOG_TARGET, }; -use futures::StreamExt; -use log::{debug, trace}; +use futures::{Future, FutureExt, Stream, StreamExt}; +use parking_lot::RwLock; use sc_transaction_pool_api::{TransactionStatus, TransactionStatusStream, TxIndex}; use sc_utils::mpsc; use sp_runtime::traits::Block as BlockT; use std::{ collections::{hash_map::Entry, HashMap, HashSet}, pin::Pin, + sync::Arc, }; use tokio_stream::StreamMap; +use tracing::trace; + +use super::dropped_watcher::{DroppedReason, DroppedTransaction}; /// A side channel allowing to control the external stream instance (one per transaction) with /// [`ControllerCommand`]. @@ -52,99 +57,202 @@ type CommandReceiver<T> = mpsc::TracingUnboundedReceiver<T>; /// It can represent both a single view's stream and an external watcher stream. pub type TxStatusStream<T> = Pin<Box<TransactionStatusStream<ExtrinsicHash<T>, BlockHash<T>>>>; -/// Commands to control the single external stream living within the multi view listener. -enum ControllerCommand<ChainApi: graph::ChainApi> { - /// Adds a new stream of transaction statuses originating in the view associated with a - /// specific block hash. - AddViewStream(BlockHash<ChainApi>, TxStatusStream<ChainApi>), +/// An aggregated stream providing events for all transactions from the view. +/// +/// This stream delivers updates for all transactions in the view, rather than for individual +/// transactions. +pub type ViewStatusStream<T> = + Pin<Box<dyn Stream<Item = TransactionStatusEvent<ExtrinsicHash<T>, BlockHash<T>>> + Send>>; +/// Commands to control / drive the task of the multi view listener. +enum ControllerCommand<ChainApi: graph::ChainApi> { + /// Requests transaction status updated. Sent by transaction pool implementation. + TransactionStatusRequest(TransactionStatusUpdate<ChainApi>), + /// Adds a new (aggregated) stream of transactions statuses originating in the view associated + /// with a specific block hash. + AddViewStream(BlockHash<ChainApi>, ViewStatusStream<ChainApi>), /// Removes an existing view's stream associated with a specific block hash. RemoveViewStream(BlockHash<ChainApi>), +} +/// Represents the transaction status update performed by transaction pool state machine. The +/// corresponding statuses coming from the view would typically be ignored in the external watcher. +enum TransactionStatusUpdate<ChainApi: graph::ChainApi> { /// Marks a transaction as invalidated. /// /// If all pre-conditions are met, an external invalid event will be sent out. - TransactionInvalidated, + TransactionInvalidated(ExtrinsicHash<ChainApi>), /// Notifies that a transaction was finalized in a specific block hash and transaction index. /// /// Send out an external finalized event. - FinalizeTransaction(BlockHash<ChainApi>, TxIndex), + TransactionFinalized(ExtrinsicHash<ChainApi>, BlockHash<ChainApi>, TxIndex), /// Notifies that a transaction was broadcasted with a list of peer addresses. /// /// Sends out an external broadcasted event. - TransactionBroadcasted(Vec<String>), + TransactionBroadcasted(ExtrinsicHash<ChainApi>, Vec<String>), /// Notifies that a transaction was dropped from the pool. /// /// If all preconditions are met, an external dropped event will be sent out. - TransactionDropped, + TransactionDropped(ExtrinsicHash<ChainApi>, DroppedReason<ExtrinsicHash<ChainApi>>), } -impl<ChainApi> std::fmt::Debug for ControllerCommand<ChainApi> +impl<ChainApi> TransactionStatusUpdate<ChainApi> +where + ChainApi: graph::ChainApi, +{ + fn hash(&self) -> ExtrinsicHash<ChainApi> { + match self { + Self::TransactionInvalidated(hash) | + Self::TransactionFinalized(hash, _, _) | + Self::TransactionBroadcasted(hash, _) | + Self::TransactionDropped(hash, _) => *hash, + } + } +} + +impl<ChainApi> std::fmt::Debug for TransactionStatusUpdate<ChainApi> where ChainApi: graph::ChainApi, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ControllerCommand::AddViewStream(h, _) => write!(f, "ListenerAction::AddView({h})"), - ControllerCommand::RemoveViewStream(h) => write!(f, "ListenerAction::RemoveView({h})"), - ControllerCommand::TransactionInvalidated => { - write!(f, "ListenerAction::TransactionInvalidated") + Self::TransactionInvalidated(h) => { + write!(f, "TransactionInvalidated({h})") + }, + Self::TransactionFinalized(h, b, i) => { + write!(f, "FinalizeTransaction({h},{b},{i})") }, - ControllerCommand::FinalizeTransaction(h, i) => { - write!(f, "ListenerAction::FinalizeTransaction({h},{i})") + Self::TransactionBroadcasted(h, _) => { + write!(f, "TransactionBroadcasted({h})") }, - ControllerCommand::TransactionBroadcasted(_) => { - write!(f, "ListenerAction::TransactionBroadcasted(...)") + Self::TransactionDropped(h, r) => { + write!(f, "TransactionDropped({h},{r:?})") }, - ControllerCommand::TransactionDropped => { - write!(f, "ListenerAction::TransactionDropped") + } + } +} + +impl<ChainApi> std::fmt::Debug for ControllerCommand<ChainApi> +where + ChainApi: graph::ChainApi, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ControllerCommand::AddViewStream(h, _) => write!(f, "AddView({h})"), + ControllerCommand::RemoveViewStream(h) => write!(f, "RemoveView({h})"), + ControllerCommand::TransactionStatusRequest(c) => { + write!(f, "TransactionStatusRequest({c:?})") }, } } } +impl<ChainApi> ControllerCommand<ChainApi> +where + ChainApi: graph::ChainApi, +{ + /// Creates new instance of a command requesting [`TransactionStatus::Invalid`] transaction + /// status. + fn new_transaction_invalidated(tx_hash: ExtrinsicHash<ChainApi>) -> Self { + ControllerCommand::TransactionStatusRequest( + TransactionStatusUpdate::TransactionInvalidated(tx_hash), + ) + } + /// Creates new instance of a command requesting [`TransactionStatus::Broadcast`] transaction + /// status. + fn new_transaction_broadcasted(tx_hash: ExtrinsicHash<ChainApi>, peers: Vec<String>) -> Self { + ControllerCommand::TransactionStatusRequest( + TransactionStatusUpdate::TransactionBroadcasted(tx_hash, peers), + ) + } + /// Creates new instance of a command requesting [`TransactionStatus::Finalized`] transaction + /// status. + fn new_transaction_finalized( + tx_hash: ExtrinsicHash<ChainApi>, + block_hash: BlockHash<ChainApi>, + index: TxIndex, + ) -> Self { + ControllerCommand::TransactionStatusRequest(TransactionStatusUpdate::TransactionFinalized( + tx_hash, block_hash, index, + )) + } + /// Creates new instance of a command requesting [`TransactionStatus::Dropped`] transaction + /// status. + fn new_transaction_dropped( + tx_hash: ExtrinsicHash<ChainApi>, + reason: DroppedReason<ExtrinsicHash<ChainApi>>, + ) -> Self { + ControllerCommand::TransactionStatusRequest(TransactionStatusUpdate::TransactionDropped( + tx_hash, reason, + )) + } +} /// This struct allows to create and control listener for multiple transactions. /// -/// For every transaction the view's stream generating its own events can be added. The events are -/// flattened and sent out to the external listener. (The *external* term here means that it can be -/// exposed to [`sc_transaction_pool_api::TransactionPool`] API client e.g. over RPC.) +/// For every view, an aggregated stream of transactions events can be added. The events are +/// flattened and sent out to the external listener for individual transactions. (The *external* +/// term here means that it can be exposed to [`sc_transaction_pool_api::TransactionPool`] API +/// client e.g. over RPC.) /// -/// The listener allows to add and remove view's stream (per transaction). +/// The listener allows to add and remove view's stream. /// /// The listener provides a side channel that allows triggering specific events (finalized, dropped, -/// invalid) independently of the view's stream. +/// invalid, broadcast) independently of the view's stream. pub struct MultiViewListener<ChainApi: graph::ChainApi> { - /// Provides the set of controllers for the events streams corresponding to individual - /// transactions identified by transaction hashes. - controllers: parking_lot::RwLock< - HashMap<ExtrinsicHash<ChainApi>, Controller<ControllerCommand<ChainApi>>>, - >, + /// Provides the controller for sending control commands to the listener's task. + controller: Controller<ControllerCommand<ChainApi>>, + + /// The map containing the sinks of the streams representing the external listeners of + /// the individual transactions. Hash of the transaction is used as a map's key. A map is + /// shared with listener's task. + external_controllers: + Arc<RwLock<HashMap<ExtrinsicHash<ChainApi>, Controller<ExternalWatcherCommand<ChainApi>>>>>, } +/// A type representing a `MultiViewListener` task. For more details refer to +/// [`MultiViewListener::task`]. +pub type MultiViewListenerTask = Pin<Box<dyn Future<Output = ()> + Send>>; + /// The external stream unfolding context. /// -/// This context is used to unfold the external events stream for a single transaction, it -/// facilitates the logic of converting single view's events to the external events stream. +/// This context is used to unfold the external events stream for a individual transaction, it +/// facilitates the logic of converting events incoming from numerous views into the external events +/// stream. struct ExternalWatcherContext<ChainApi: graph::ChainApi> { /// The hash of the transaction being monitored within this context. tx_hash: ExtrinsicHash<ChainApi>, - /// A stream map of transaction status streams coming from individual views, keyed by - /// block hash associated with view. - status_stream_map: StreamMap<BlockHash<ChainApi>, TxStatusStream<ChainApi>>, - /// A receiver for controller commands. - command_receiver: CommandReceiver<ControllerCommand<ChainApi>>, + /// A receiver for controller commands sent by [`MultiViewListener`]'s task. + command_receiver: CommandReceiver<ExternalWatcherCommand<ChainApi>>, /// A flag indicating whether the context should terminate. terminate: bool, /// A flag indicating if a `Future` status has been encountered. future_seen: bool, /// A flag indicating if a `Ready` status has been encountered. ready_seen: bool, - /// A hash set of block hashes from views that consider the transaction valid. views_keeping_tx_valid: HashSet<BlockHash<ChainApi>>, + /// The set of views (represented by block hashes) currently maintained by the transaction + /// pool. + known_views: HashSet<BlockHash<ChainApi>>, +} + +/// Commands to control the single external stream living within the multi view listener. These +/// commands are sent from listener's task to [`ExternalWatcherContext`]. +enum ExternalWatcherCommand<ChainApi: graph::ChainApi> { + /// Command for triggering some of the transaction states, that are decided by the pool logic. + PoolTransactionStatus(TransactionStatusUpdate<ChainApi>), + /// Transaction status updates coming from the individual views. + ViewTransactionStatus( + BlockHash<ChainApi>, + TransactionStatus<ExtrinsicHash<ChainApi>, BlockHash<ChainApi>>, + ), + /// Notification about new view being added. + AddView(BlockHash<ChainApi>), + /// Notification about view being removed. + RemoveView(BlockHash<ChainApi>), } impl<ChainApi: graph::ChainApi> ExternalWatcherContext<ChainApi> @@ -153,39 +261,85 @@ where { /// Creates new `ExternalWatcherContext` for particular transaction identified by `tx_hash` /// - /// The `command_receiver` is a side channel for receiving controller's commands. + /// The `command_receiver` is a side channel for receiving controller's + /// [commands][`ExternalWatcherCommand`]. fn new( tx_hash: ExtrinsicHash<ChainApi>, - command_receiver: CommandReceiver<ControllerCommand<ChainApi>>, + command_receiver: CommandReceiver<ExternalWatcherCommand<ChainApi>>, ) -> Self { Self { tx_hash, - status_stream_map: StreamMap::new(), command_receiver, terminate: false, future_seen: false, ready_seen: false, views_keeping_tx_valid: Default::default(), + known_views: Default::default(), } } - /// Handles various transaction status updates and manages internal states based on the status. + /// Handles transaction status updates from the pool and manages internal states based on the + /// input value. + /// + /// Function may set the context termination flag, which will close the stream. + /// + /// Returns `Some` with the `event` to be sent out or `None`. + fn handle_pool_transaction_status( + &mut self, + request: TransactionStatusUpdate<ChainApi>, + ) -> Option<TransactionStatus<ExtrinsicHash<ChainApi>, BlockHash<ChainApi>>> { + match request { + TransactionStatusUpdate::TransactionInvalidated(..) => + if self.handle_invalidate_transaction() { + log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Invalid", self.tx_hash); + return Some(TransactionStatus::Invalid) + }, + TransactionStatusUpdate::TransactionFinalized(_, block, index) => { + log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Finalized", self.tx_hash); + self.terminate = true; + return Some(TransactionStatus::Finalized((block, index))) + }, + TransactionStatusUpdate::TransactionBroadcasted(_, peers) => { + log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Broadcasted", self.tx_hash); + return Some(TransactionStatus::Broadcast(peers)) + }, + TransactionStatusUpdate::TransactionDropped(_, DroppedReason::LimitsEnforced) => { + log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Dropped", self.tx_hash); + self.terminate = true; + return Some(TransactionStatus::Dropped) + }, + TransactionStatusUpdate::TransactionDropped(_, DroppedReason::Usurped(by)) => { + log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Usurped({:?})", self.tx_hash, by); + self.terminate = true; + return Some(TransactionStatus::Usurped(by)) + }, + }; + None + } + + /// Handles various transaction status updates from individual views and manages internal states + /// based on the input value. /// /// Function may set the context termination flag, which will close the stream. /// - /// Returns `Some` with the `event` to forward or `None`. - fn handle( + /// Returns `Some` with the `event` to be sent out or `None`. + fn handle_view_transaction_status( &mut self, + block_hash: BlockHash<ChainApi>, status: TransactionStatus<ExtrinsicHash<ChainApi>, BlockHash<ChainApi>>, - hash: BlockHash<ChainApi>, ) -> Option<TransactionStatus<ExtrinsicHash<ChainApi>, BlockHash<ChainApi>>> { trace!( - target: LOG_TARGET, "[{:?}] mvl handle event from {hash:?}: {status:?} views:{:?}", self.tx_hash, - self.status_stream_map.keys().collect::<Vec<_>>() + target: LOG_TARGET, + tx_hash = ?self.tx_hash, + ?block_hash, + ?status, + views = ?self.known_views.iter().collect::<Vec<_>>(), + "mvl handle event" ); + match status { TransactionStatus::Future => { - self.views_keeping_tx_valid.insert(hash); + self.views_keeping_tx_valid.insert(block_hash); if self.ready_seen || self.future_seen { None } else { @@ -194,7 +348,7 @@ where } }, TransactionStatus::Ready => { - self.views_keeping_tx_valid.insert(hash); + self.views_keeping_tx_valid.insert(block_hash); if self.ready_seen { None } else { @@ -202,9 +356,8 @@ where Some(status) } }, - TransactionStatus::Broadcast(_) => None, TransactionStatus::InBlock((..)) => { - self.views_keeping_tx_valid.insert(hash); + self.views_keeping_tx_valid.insert(block_hash); if !(self.ready_seen || self.future_seen) { self.ready_seen = true; Some(status) @@ -212,12 +365,13 @@ where Some(status) } }, - TransactionStatus::Retracted(_) => None, TransactionStatus::FinalityTimeout(_) => Some(status), TransactionStatus::Finalized(_) => { self.terminate = true; Some(status) }, + TransactionStatus::Retracted(_) | + TransactionStatus::Broadcast(_) | TransactionStatus::Usurped(_) | TransactionStatus::Dropped | TransactionStatus::Invalid => None, @@ -231,13 +385,12 @@ where /// Returns true if the event should be sent out, and false if the invalidation request should /// be skipped. fn handle_invalidate_transaction(&mut self) -> bool { - let keys = HashSet::<BlockHash<ChainApi>>::from_iter( - self.status_stream_map.keys().map(Clone::clone), - ); + let keys = self.known_views.clone(); trace!( target: LOG_TARGET, - "[{:?}] got invalidate_transaction: views:{:?}", self.tx_hash, - self.status_stream_map.keys().collect::<Vec<_>>() + tx_hash = ?self.tx_hash, + views = ?self.known_views.iter().collect::<Vec<_>>(), + "got invalidate_transaction" ); if self.views_keeping_tx_valid.is_disjoint(&keys) { self.terminate = true; @@ -253,22 +406,35 @@ where } } - /// Adds a new transaction status stream. + /// Adds a new aggragted transaction status stream. /// - /// Inserts a new view's transaction status stream associated with a specific block hash into - /// the stream map. - fn add_stream(&mut self, block_hash: BlockHash<ChainApi>, stream: TxStatusStream<ChainApi>) { - self.status_stream_map.insert(block_hash, stream); - trace!(target: LOG_TARGET, "[{:?}] AddView view: {:?} views:{:?}", self.tx_hash, block_hash, self.status_stream_map.keys().collect::<Vec<_>>()); + /// Inserts a new view's transaction status stream into the stream map. The view is represented + /// by `block_hash`. + fn add_view(&mut self, block_hash: BlockHash<ChainApi>) { + trace!( + target: LOG_TARGET, + tx_hash = ?self.tx_hash, + ?block_hash, + views = ?self.known_views.iter().collect::<Vec<_>>(), + "AddView view" + ); + self.known_views.insert(block_hash); } - /// Removes an existing transaction status stream. + /// Removes an existing aggreagated transaction status stream. /// - /// Removes a transaction status stream associated with a specific block hash from the - /// stream map. + /// Removes an aggregated transaction status stream associated with a specific block hash from + /// the stream map. fn remove_view(&mut self, block_hash: BlockHash<ChainApi>) { - self.status_stream_map.remove(&block_hash); - trace!(target: LOG_TARGET, "[{:?}] RemoveView view: {:?} views:{:?}", self.tx_hash, block_hash, self.status_stream_map.keys().collect::<Vec<_>>()); + self.known_views.remove(&block_hash); + self.views_keeping_tx_valid.remove(&block_hash); + trace!( + target: LOG_TARGET, + tx_hash = ?self.tx_hash, + ?block_hash, + views = ?self.known_views.iter().collect::<Vec<_>>(), + "RemoveView view" + ); } } @@ -277,79 +443,180 @@ where ChainApi: graph::ChainApi + 'static, <<ChainApi as graph::ChainApi>::Block as BlockT>::Hash: Unpin, { - /// Creates new instance of `MultiViewListener`. - pub fn new() -> Self { - Self { controllers: Default::default() } + /// A worker task associated with `MultiViewListener` instance. + /// + /// An asynchronous listener's task responsible for dispatching: + /// - stream_map containing aggregated transaction status streams from multiple views, + /// - view add/remove requests, + /// - transaction commands, + /// to multiple individual per-transaction external watcher contexts. + /// + /// The future shall be polled by instantiator of `MultiViewListener`. + async fn task( + external_watchers_tx_hash_map: Arc< + RwLock<HashMap<ExtrinsicHash<ChainApi>, Controller<ExternalWatcherCommand<ChainApi>>>>, + >, + mut command_receiver: CommandReceiver<ControllerCommand<ChainApi>>, + ) { + let mut aggregated_streams_map: StreamMap<BlockHash<ChainApi>, ViewStatusStream<ChainApi>> = + Default::default(); + + loop { + tokio::select! { + biased; + Some((view_hash, (tx_hash, status))) = next_event(&mut aggregated_streams_map) => { + if let Entry::Occupied(mut ctrl) = external_watchers_tx_hash_map.write().entry(tx_hash) { + log::trace!( + target: LOG_TARGET, + "[{:?}] aggregated_stream_map event: view:{} status:{:?}", + tx_hash, + view_hash, + status + ); + if let Err(e) = ctrl + .get_mut() + .unbounded_send(ExternalWatcherCommand::ViewTransactionStatus(view_hash, status)) + { + trace!(target: LOG_TARGET, "[{:?}] send status failed: {:?}", tx_hash, e); + ctrl.remove(); + } + } + }, + cmd = command_receiver.next() => { + log::trace!(target: LOG_TARGET, "cmd {:?}", cmd); + match cmd { + Some(ControllerCommand::AddViewStream(h,stream)) => { + aggregated_streams_map.insert(h,stream); + // //todo: aysnc and join all? + external_watchers_tx_hash_map.write().retain(|tx_hash, ctrl| { + ctrl.unbounded_send(ExternalWatcherCommand::AddView(h)) + .inspect_err(|e| { + trace!(target: LOG_TARGET, "[{:?}] invalidate_transaction: send message failed: {:?}", tx_hash, e); + }) + .is_ok() + }) + }, + Some(ControllerCommand::RemoveViewStream(h)) => { + aggregated_streams_map.remove(&h); + //todo: aysnc and join all? + external_watchers_tx_hash_map.write().retain(|tx_hash, ctrl| { + ctrl.unbounded_send(ExternalWatcherCommand::RemoveView(h)) + .inspect_err(|e| { + trace!(target: LOG_TARGET, "[{:?}] invalidate_transaction: send message failed: {:?}", tx_hash, e); + }) + .is_ok() + }) + }, + + Some(ControllerCommand::TransactionStatusRequest(request)) => { + let tx_hash = request.hash(); + if let Entry::Occupied(mut ctrl) = external_watchers_tx_hash_map.write().entry(tx_hash) { + if let Err(e) = ctrl + .get_mut() + .unbounded_send(ExternalWatcherCommand::PoolTransactionStatus(request)) + { + trace!(target: LOG_TARGET, "[{:?}] send message failed: {:?}", tx_hash, e); + ctrl.remove(); + } + } + }, + None => {} + } + }, + }; + } + } + + /// Creates a new [`MultiViewListener`] instance along with its associated worker task. + /// + /// This function instansiates the new `MultiViewListener` and provides the worker task that + /// relays messages to the external transactions listeners. The task shall be polled by caller. + /// + /// Returns a tuple containing the [`MultiViewListener`] and the + /// [`MultiViewListenerTask`]. + pub fn new_with_worker() -> (Self, MultiViewListenerTask) { + let external_controllers = Arc::from(RwLock::from(HashMap::< + ExtrinsicHash<ChainApi>, + Controller<ExternalWatcherCommand<ChainApi>>, + >::default())); + + const CONTROLLER_QUEUE_WARN_SIZE: usize = 100_000; + let (tx, rx) = mpsc::tracing_unbounded( + "txpool-multi-view-listener-task-controller", + CONTROLLER_QUEUE_WARN_SIZE, + ); + let task = Self::task(external_controllers.clone(), rx); + + (Self { external_controllers, controller: tx }, task.boxed()) } - /// Creates an external aggregated stream of events for given transaction. + /// Creates an external tstream of events for given transaction. /// /// This method initializes an `ExternalWatcherContext` for the provided transaction hash, sets - /// up the necessary communication channels, and unfolds an external (meaning that it can be - /// exposed to [`sc_transaction_pool_api::TransactionPool`] API client e.g. rpc) stream of - /// transaction status events. If an external watcher is already present for the given - /// transaction, it returns `None`. + /// up the necessary communication channel with listener's task, and unfolds an external + /// (meaning that it can be exposed to [`sc_transaction_pool_api::TransactionPool`] API client + /// e.g. rpc) stream of transaction status events. If an external watcher is already present for + /// the given transaction, it returns `None`. pub(crate) fn create_external_watcher_for_tx( &self, tx_hash: ExtrinsicHash<ChainApi>, ) -> Option<TxStatusStream<ChainApi>> { - let mut controllers = self.controllers.write(); - if controllers.contains_key(&tx_hash) { - return None - } - - trace!(target: LOG_TARGET, "[{:?}] create_external_watcher_for_tx", tx_hash); - - let (tx, rx) = mpsc::tracing_unbounded("txpool-multi-view-listener", 32); - controllers.insert(tx_hash, tx); + let external_ctx = match self.external_controllers.write().entry(tx_hash) { + Entry::Occupied(_) => return None, + Entry::Vacant(entry) => { + const EXT_CONTROLLER_QUEUE_WARN_THRESHOLD: usize = 128; + let (tx, rx) = mpsc::tracing_unbounded( + "txpool-multi-view-listener", + EXT_CONTROLLER_QUEUE_WARN_THRESHOLD, + ); + entry.insert(tx); + ExternalWatcherContext::new(tx_hash, rx) + }, + }; - let ctx = ExternalWatcherContext::new(tx_hash, rx); + trace!( + target: LOG_TARGET, + ?tx_hash, + "create_external_watcher_for_tx" + ); Some( - futures::stream::unfold(ctx, |mut ctx| async move { + futures::stream::unfold(external_ctx, |mut ctx| async move { if ctx.terminate { + log::trace!(target: LOG_TARGET, "[{:?}] terminate", ctx.tx_hash); return None } loop { tokio::select! { - biased; - Some((view_hash, status)) = next_event(&mut ctx.status_stream_map) => { - if let Some(new_status) = ctx.handle(status, view_hash) { - log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: {new_status:?}", ctx.tx_hash); - return Some((new_status, ctx)) - } - }, cmd = ctx.command_receiver.next() => { - log::trace!(target: LOG_TARGET, "[{:?}] select::rx views:{:?}", - ctx.tx_hash, - ctx.status_stream_map.keys().collect::<Vec<_>>() - ); match cmd? { - ControllerCommand::AddViewStream(h,stream) => { - ctx.add_stream(h, stream); - }, - ControllerCommand::RemoveViewStream(h) => { - ctx.remove_view(h); - }, - ControllerCommand::TransactionInvalidated => { - if ctx.handle_invalidate_transaction() { - log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Invalid", ctx.tx_hash); - return Some((TransactionStatus::Invalid, ctx)) + ExternalWatcherCommand::ViewTransactionStatus(view_hash, status) => { + if let Some(new_status) = ctx.handle_view_transaction_status(view_hash, status) { + trace!( + target: LOG_TARGET, + tx_hash = ?ctx.tx_hash, + ?new_status, + "mvl sending out" + ); + return Some((new_status, ctx)) } }, - ControllerCommand::FinalizeTransaction(block, index) => { - log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Finalized", ctx.tx_hash); - ctx.terminate = true; - return Some((TransactionStatus::Finalized((block, index)), ctx)) - }, - ControllerCommand::TransactionBroadcasted(peers) => { - log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Broadcasted", ctx.tx_hash); - return Some((TransactionStatus::Broadcast(peers), ctx)) + ExternalWatcherCommand::PoolTransactionStatus(request) => { + if let Some(new_status) = ctx.handle_pool_transaction_status(request) { + trace!( + target: LOG_TARGET, + tx_hash = ?ctx.tx_hash, + ?new_status, + "mvl sending out" + ); + return Some((new_status, ctx)) + } + } + ExternalWatcherCommand::AddView(h) => { + ctx.add_view(h); }, - ControllerCommand::TransactionDropped => { - log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Dropped", ctx.tx_hash); - ctx.terminate = true; - return Some((TransactionStatus::Dropped, ctx)) + ExternalWatcherCommand::RemoveView(h) => { + ctx.remove_view(h); }, } }, @@ -360,125 +627,142 @@ where ) } - /// Adds a view's transaction status stream for particular transaction. + /// Adds an aggregated view's transaction status stream. /// - /// This method sends a `AddViewStream` command to the controller of each transaction to - /// remove the view's stream corresponding to the given block hash. - pub(crate) fn add_view_watcher_for_tx( + /// This method sends a `AddViewStream` command to the task, from where it is further dispatched + /// to the external watcher context for every watched transaction. + /// + /// The stream is associated with a view represented by `block_hash`. + pub(crate) fn add_view_aggregated_stream( &self, - tx_hash: ExtrinsicHash<ChainApi>, block_hash: BlockHash<ChainApi>, - stream: TxStatusStream<ChainApi>, + stream: ViewStatusStream<ChainApi>, ) { - let mut controllers = self.controllers.write(); - - if let Entry::Occupied(mut tx) = controllers.entry(tx_hash) { - if let Err(e) = tx - .get_mut() - .unbounded_send(ControllerCommand::AddViewStream(block_hash, stream)) - { - trace!(target: LOG_TARGET, "[{:?}] add_view_watcher_for_tx: send message failed: {:?}", tx_hash, e); - tx.remove(); - } + trace!(target: LOG_TARGET, ?block_hash, "mvl::add_view_aggregated_stream"); + if let Err(error) = self + .controller + .unbounded_send(ControllerCommand::AddViewStream(block_hash, stream)) + { + trace!( + target: LOG_TARGET, + ?block_hash, + %error, + "add_view_aggregated_stream: send message failed" + ); } } - /// Removes a view's stream associated with a specific view hash across all transactions. + /// Removes a view's stream associated with a specific view hash. /// - /// This method sends a `RemoveViewStream` command to the controller of each transaction to - /// remove the view's stream corresponding to the given block hash. + /// This method sends a `RemoveViewStream` command to the listener's task, from where is further + /// dispatched to the external watcher context for every watched transaction. pub(crate) fn remove_view(&self, block_hash: BlockHash<ChainApi>) { - self.controllers.write().retain(|tx_hash, sender| { - sender - .unbounded_send(ControllerCommand::RemoveViewStream(block_hash)) - .map_err(|e| { - log::trace!(target: LOG_TARGET, "[{:?}] remove_view: send message failed: {:?}", tx_hash, e); - e - }) - .is_ok() - }); + trace!(target: LOG_TARGET, ?block_hash, "mvl::remove_view"); + if let Err(error) = + self.controller.unbounded_send(ControllerCommand::RemoveViewStream(block_hash)) + { + trace!( + target: LOG_TARGET, + ?block_hash, + %error, + "remove_view: send message failed" + ); + } } /// Invalidate given transaction. /// - /// This method sends a `TransactionInvalidated` command to the controller of each transaction - /// provided to process the invalidation request. + /// This method sends a `TransactionInvalidated` command to the task's controller of each + /// transaction provided to process the invalidation request. /// /// The external event will be sent if no view is referencing the transaction as `Ready` or /// `Future`. - pub(crate) fn invalidate_transactions(&self, invalid_hashes: &[ExtrinsicHash<ChainApi>]) { - let mut controllers = self.controllers.write(); - invalid_hashes.iter().for_each(|tx_hash| { - if let Entry::Occupied(mut tx) = controllers.entry(*tx_hash) { - trace!(target: LOG_TARGET, "[{:?}] invalidate_transaction", tx_hash); - if let Err(e) = - tx.get_mut().unbounded_send(ControllerCommand::TransactionInvalidated) - { - trace!(target: LOG_TARGET, "[{:?}] invalidate_transaction: send message failed: {:?}", tx_hash, e); - tx.remove(); - } + pub(crate) fn transactions_invalidated(&self, invalid_hashes: &[ExtrinsicHash<ChainApi>]) { + log_xt_trace!(target: LOG_TARGET, invalid_hashes, "transactions_invalidated"); + for tx_hash in invalid_hashes { + if let Err(error) = self + .controller + .unbounded_send(ControllerCommand::new_transaction_invalidated(*tx_hash)) + { + trace!( + target: LOG_TARGET, + ?tx_hash, + %error, + "transactions_invalidated: send message failed" + ); } - }); + } } /// Send `Broadcasted` event to listeners of all transactions. /// - /// This method sends a `TransactionBroadcasted` command to the controller of each transaction - /// provided prompting the external `Broadcasted` event. + /// This method sends a `TransactionBroadcasted` command to the task's controller for each + /// transaction provided. It will prompt the external `Broadcasted` event. pub(crate) fn transactions_broadcasted( &self, propagated: HashMap<ExtrinsicHash<ChainApi>, Vec<String>>, ) { - let mut controllers = self.controllers.write(); - propagated.into_iter().for_each(|(tx_hash, peers)| { - if let Entry::Occupied(mut tx) = controllers.entry(tx_hash) { - trace!(target: LOG_TARGET, "[{:?}] transaction_broadcasted", tx_hash); - if let Err(e) = tx.get_mut().unbounded_send(ControllerCommand::TransactionBroadcasted(peers)) { - trace!(target: LOG_TARGET, "[{:?}] transactions_broadcasted: send message failed: {:?}", tx_hash, e); - tx.remove(); - } + for (tx_hash, peers) in propagated { + if let Err(error) = self + .controller + .unbounded_send(ControllerCommand::new_transaction_broadcasted(tx_hash, peers)) + { + trace!( + target: LOG_TARGET, + ?tx_hash, + %error, + "transactions_broadcasted: send message failed" + ); } - }); + } } /// Send `Dropped` event to listeners of transactions. /// - /// This method sends a `TransactionDropped` command to the controller of each requested - /// transaction prompting and external `Broadcasted` event. - pub(crate) fn transactions_dropped(&self, dropped: &[ExtrinsicHash<ChainApi>]) { - let mut controllers = self.controllers.write(); - debug!(target: LOG_TARGET, "mvl::transactions_dropped: {:?}", dropped); - for tx_hash in dropped { - if let Some(tx) = controllers.remove(&tx_hash) { - debug!(target: LOG_TARGET, "[{:?}] transaction_dropped", tx_hash); - if let Err(e) = tx.unbounded_send(ControllerCommand::TransactionDropped) { - trace!(target: LOG_TARGET, "[{:?}] transactions_dropped: send message failed: {:?}", tx_hash, e); - }; - } + /// This method sends a `TransactionDropped` command to the task's controller. It will prompt + /// the external `Broadcasted` event. + pub(crate) fn transaction_dropped(&self, dropped: DroppedTransaction<ExtrinsicHash<ChainApi>>) { + let DroppedTransaction { tx_hash, reason } = dropped; + trace!(target: LOG_TARGET, ?tx_hash, ?reason, "transaction_dropped"); + if let Err(error) = self + .controller + .unbounded_send(ControllerCommand::new_transaction_dropped(tx_hash, reason)) + { + trace!( + target: LOG_TARGET, + ?tx_hash, + %error, + "transaction_dropped: send message failed" + ); } } /// Send `Finalized` event for given transaction at given block. /// - /// This will send `Finalized` event to the external watcher. - pub(crate) fn finalize_transaction( + /// This will trigger `Finalized` event to the external watcher. + pub(crate) fn transaction_finalized( &self, tx_hash: ExtrinsicHash<ChainApi>, block: BlockHash<ChainApi>, idx: TxIndex, ) { - let mut controllers = self.controllers.write(); - if let Some(tx) = controllers.remove(&tx_hash) { - trace!(target: LOG_TARGET, "[{:?}] finalize_transaction", tx_hash); - if let Err(e) = tx.unbounded_send(ControllerCommand::FinalizeTransaction(block, idx)) { - trace!(target: LOG_TARGET, "[{:?}] finalize_transaction: send message failed: {:?}", tx_hash, e); - } + trace!(target: LOG_TARGET, ?tx_hash, "transaction_finalized"); + if let Err(error) = self + .controller + .unbounded_send(ControllerCommand::new_transaction_finalized(tx_hash, block, idx)) + { + trace!( + target: LOG_TARGET, + ?tx_hash, + %error, + "transaction_finalized: send message failed" + ); }; } /// Removes stale controllers. pub(crate) fn remove_stale_controllers(&self) { - self.controllers.write().retain(|_, c| !c.is_closed()); + self.external_controllers.write().retain(|_, c| !c.is_closed()); } } @@ -488,38 +772,60 @@ mod tests { use crate::common::tests::TestApi; use futures::{stream, StreamExt}; use sp_core::H256; + use tokio::{select, task::JoinHandle}; + use tracing::debug; type MultiViewListener = super::MultiViewListener<TestApi>; + fn create_multi_view_listener( + ) -> (MultiViewListener, tokio::sync::oneshot::Sender<()>, JoinHandle<()>) { + let (listener, listener_task) = MultiViewListener::new_with_worker(); + + let (tx, rx) = tokio::sync::oneshot::channel(); + + let listener_handle = tokio::spawn(async move { + select! { + _ = listener_task => {}, + _ = rx => { return; } + } + }); + + (listener, tx, listener_handle) + } + #[tokio::test] async fn test01() { sp_tracing::try_init_simple(); - let listener = MultiViewListener::new(); + let (listener, terminate_listener, listener_task) = create_multi_view_listener(); let block_hash = H256::repeat_byte(0x01); + let tx_hash = H256::repeat_byte(0x0a); let events = vec![ TransactionStatus::Ready, TransactionStatus::InBlock((block_hash, 0)), TransactionStatus::Finalized((block_hash, 0)), ]; - let tx_hash = H256::repeat_byte(0x0a); let external_watcher = listener.create_external_watcher_for_tx(tx_hash).unwrap(); let handle = tokio::spawn(async move { external_watcher.collect::<Vec<_>>().await }); - let view_stream = futures::stream::iter(events.clone()); + let view_stream = + futures::stream::iter(std::iter::repeat(tx_hash).zip(events.clone().into_iter())); - listener.add_view_watcher_for_tx(tx_hash, block_hash, view_stream.boxed()); + listener.add_view_aggregated_stream(block_hash, view_stream.boxed()); let out = handle.await.unwrap(); assert_eq!(out, events); - log::debug!("out: {:#?}", out); + debug!("out: {:#?}", out); + + let _ = terminate_listener.send(()); + let _ = listener_task.await.unwrap(); } #[tokio::test] async fn test02() { sp_tracing::try_init_simple(); - let listener = MultiViewListener::new(); + let (listener, terminate_listener, listener_task) = create_multi_view_listener(); let block_hash0 = H256::repeat_byte(0x01); let events0 = vec![ @@ -538,17 +844,19 @@ mod tests { let tx_hash = H256::repeat_byte(0x0a); let external_watcher = listener.create_external_watcher_for_tx(tx_hash).unwrap(); - let view_stream0 = futures::stream::iter(events0.clone()); - let view_stream1 = futures::stream::iter(events1.clone()); + let view_stream0 = + futures::stream::iter(std::iter::repeat(tx_hash).zip(events0.clone().into_iter())); + let view_stream1 = + futures::stream::iter(std::iter::repeat(tx_hash).zip(events1.clone().into_iter())); let handle = tokio::spawn(async move { external_watcher.collect::<Vec<_>>().await }); - listener.add_view_watcher_for_tx(tx_hash, block_hash0, view_stream0.boxed()); - listener.add_view_watcher_for_tx(tx_hash, block_hash1, view_stream1.boxed()); + listener.add_view_aggregated_stream(block_hash0, view_stream0.boxed()); + listener.add_view_aggregated_stream(block_hash1, view_stream1.boxed()); let out = handle.await.unwrap(); - log::debug!("out: {:#?}", out); + debug!("out: {:#?}", out); assert!(out.iter().all(|v| vec![ TransactionStatus::Future, TransactionStatus::Ready, @@ -558,12 +866,15 @@ mod tests { ] .contains(v))); assert_eq!(out.len(), 5); + + let _ = terminate_listener.send(()); + let _ = listener_task.await.unwrap(); } #[tokio::test] async fn test03() { sp_tracing::try_init_simple(); - let listener = MultiViewListener::new(); + let (listener, terminate_listener, listener_task) = create_multi_view_listener(); let block_hash0 = H256::repeat_byte(0x01); let events0 = vec![ @@ -579,16 +890,21 @@ mod tests { let external_watcher = listener.create_external_watcher_for_tx(tx_hash).unwrap(); let handle = tokio::spawn(async move { external_watcher.collect::<Vec<_>>().await }); - let view_stream0 = futures::stream::iter(events0.clone()); - let view_stream1 = futures::stream::iter(events1.clone()); + let view_stream0 = + futures::stream::iter(std::iter::repeat(tx_hash).zip(events0.clone().into_iter())); + let view_stream1 = + futures::stream::iter(std::iter::repeat(tx_hash).zip(events1.clone().into_iter())); - listener.add_view_watcher_for_tx(tx_hash, block_hash0, view_stream0.boxed()); - listener.add_view_watcher_for_tx(tx_hash, block_hash1, view_stream1.boxed()); + listener.add_view_aggregated_stream(block_hash0, view_stream0.boxed()); + listener.add_view_aggregated_stream(block_hash1, view_stream1.boxed()); - listener.invalidate_transactions(&[tx_hash]); + listener.remove_view(block_hash0); + listener.remove_view(block_hash1); + + listener.transactions_invalidated(&[tx_hash]); let out = handle.await.unwrap(); - log::debug!("out: {:#?}", out); + debug!("out: {:#?}", out); assert!(out.iter().all(|v| vec![ TransactionStatus::Future, TransactionStatus::Ready, @@ -597,12 +913,15 @@ mod tests { ] .contains(v))); assert_eq!(out.len(), 4); - } + let _ = terminate_listener.send(()); + let _ = listener_task.await.unwrap(); + } + // #[tokio::test] async fn test032() { sp_tracing::try_init_simple(); - let listener = MultiViewListener::new(); + let (listener, terminate_listener, listener_task) = create_multi_view_listener(); let block_hash0 = H256::repeat_byte(0x01); let events0_tx0 = vec![TransactionStatus::Future]; @@ -625,25 +944,32 @@ mod tests { let handle0 = tokio::spawn(async move { external_watcher_tx0.collect::<Vec<_>>().await }); let handle1 = tokio::spawn(async move { external_watcher_tx1.collect::<Vec<_>>().await }); - let view0_tx0_stream = futures::stream::iter(events0_tx0.clone()); - let view0_tx1_stream = futures::stream::iter(events0_tx1.clone()); + let view0_tx0_stream = + futures::stream::iter(std::iter::repeat(tx0_hash).zip(events0_tx0.clone())); + let view0_tx1_stream = + futures::stream::iter(std::iter::repeat(tx1_hash).zip(events0_tx1.clone())); + + let view1_tx0_stream = + futures::stream::iter(std::iter::repeat(tx0_hash).zip(events1_tx0.clone())); + let view1_tx1_stream = + futures::stream::iter(std::iter::repeat(tx1_hash).zip(events1_tx1.clone())); - let view1_tx0_stream = futures::stream::iter(events1_tx0.clone()); - let view1_tx1_stream = futures::stream::iter(events1_tx1.clone()); + listener.add_view_aggregated_stream(block_hash0, view0_tx0_stream.boxed()); + listener.add_view_aggregated_stream(block_hash1, view1_tx0_stream.boxed()); + listener.add_view_aggregated_stream(block_hash0, view0_tx1_stream.boxed()); + listener.add_view_aggregated_stream(block_hash1, view1_tx1_stream.boxed()); - listener.add_view_watcher_for_tx(tx0_hash, block_hash0, view0_tx0_stream.boxed()); - listener.add_view_watcher_for_tx(tx0_hash, block_hash1, view1_tx0_stream.boxed()); - listener.add_view_watcher_for_tx(tx1_hash, block_hash0, view0_tx1_stream.boxed()); - listener.add_view_watcher_for_tx(tx1_hash, block_hash1, view1_tx1_stream.boxed()); + listener.remove_view(block_hash0); + listener.remove_view(block_hash1); - listener.invalidate_transactions(&[tx0_hash]); - listener.invalidate_transactions(&[tx1_hash]); + listener.transactions_invalidated(&[tx0_hash]); + listener.transactions_invalidated(&[tx1_hash]); let out_tx0 = handle0.await.unwrap(); let out_tx1 = handle1.await.unwrap(); - log::debug!("out_tx0: {:#?}", out_tx0); - log::debug!("out_tx1: {:#?}", out_tx1); + debug!("out_tx0: {:#?}", out_tx0); + debug!("out_tx1: {:#?}", out_tx1); assert!(out_tx0.iter().all(|v| vec![ TransactionStatus::Future, TransactionStatus::Ready, @@ -660,12 +986,15 @@ mod tests { .contains(v))); assert_eq!(out_tx0.len(), 4); assert_eq!(out_tx1.len(), 3); + + let _ = terminate_listener.send(()); + let _ = listener_task.await.unwrap(); } #[tokio::test] async fn test04() { sp_tracing::try_init_simple(); - let listener = MultiViewListener::new(); + let (listener, terminate_listener, listener_task) = create_multi_view_listener(); let block_hash0 = H256::repeat_byte(0x01); let events0 = vec![ @@ -681,21 +1010,23 @@ mod tests { let external_watcher = listener.create_external_watcher_for_tx(tx_hash).unwrap(); //views will keep transaction valid, invalidation shall not happen - let view_stream0 = futures::stream::iter(events0.clone()).chain(stream::pending().boxed()); - let view_stream1 = futures::stream::iter(events1.clone()).chain(stream::pending().boxed()); + let view_stream0 = futures::stream::iter(std::iter::repeat(tx_hash).zip(events0.clone())) + .chain(stream::pending().boxed()); + let view_stream1 = futures::stream::iter(std::iter::repeat(tx_hash).zip(events1.clone())) + .chain(stream::pending().boxed()); let handle = tokio::spawn(async move { // views are still there, we need to fetch 3 events external_watcher.take(3).collect::<Vec<_>>().await }); - listener.add_view_watcher_for_tx(tx_hash, block_hash0, view_stream0.boxed()); - listener.add_view_watcher_for_tx(tx_hash, block_hash1, view_stream1.boxed()); + listener.add_view_aggregated_stream(block_hash0, view_stream0.boxed()); + listener.add_view_aggregated_stream(block_hash1, view_stream1.boxed()); - listener.invalidate_transactions(&[tx_hash]); + listener.transactions_invalidated(&[tx_hash]); let out = handle.await.unwrap(); - log::debug!("out: {:#?}", out); + debug!("out: {:#?}", out); // invalid shall not be sent assert!(out.iter().all(|v| vec![ @@ -705,12 +1036,14 @@ mod tests { ] .contains(v))); assert_eq!(out.len(), 3); + let _ = terminate_listener.send(()); + let _ = listener_task.await.unwrap(); } #[tokio::test] async fn test05() { sp_tracing::try_init_simple(); - let listener = MultiViewListener::new(); + let (listener, terminate_listener, listener_task) = create_multi_view_listener(); let block_hash0 = H256::repeat_byte(0x01); let events0 = vec![TransactionStatus::Invalid]; @@ -719,18 +1052,24 @@ mod tests { let external_watcher = listener.create_external_watcher_for_tx(tx_hash).unwrap(); let handle = tokio::spawn(async move { external_watcher.collect::<Vec<_>>().await }); - let view_stream0 = futures::stream::iter(events0.clone()).chain(stream::pending().boxed()); + let view_stream0 = futures::stream::iter(std::iter::repeat(tx_hash).zip(events0.clone())) + .chain(stream::pending().boxed()); // Note: this generates actual Invalid event. - // Invalid event from View's stream is intentionally ignored. - listener.invalidate_transactions(&[tx_hash]); + // Invalid event from View's stream is intentionally ignored . + // we need to explicitely remove the view + listener.remove_view(block_hash0); + listener.transactions_invalidated(&[tx_hash]); - listener.add_view_watcher_for_tx(tx_hash, block_hash0, view_stream0.boxed()); + listener.add_view_aggregated_stream(block_hash0, view_stream0.boxed()); let out = handle.await.unwrap(); - log::debug!("out: {:#?}", out); + debug!("out: {:#?}", out); assert!(out.iter().all(|v| vec![TransactionStatus::Invalid].contains(v))); assert_eq!(out.len(), 1); + + let _ = terminate_listener.send(()); + let _ = listener_task.await.unwrap(); } } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs index 9464ab3f5766706fb8e2d85a3219123332ba2d57..0025d3e9f2d42aad0db1c1747fe9e4cc9d7476ef 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs @@ -30,6 +30,7 @@ use sp_runtime::traits::Block as BlockT; use super::tx_mem_pool::TxMemPool; use futures::prelude::*; +use tracing::{trace, warn}; use super::view::{FinishRevalidationWorkerChannels, View}; @@ -131,18 +132,22 @@ where view: Arc<View<Api>>, finish_revalidation_worker_channels: FinishRevalidationWorkerChannels<Api>, ) { - log::trace!( + trace!( target: LOG_TARGET, - "revalidation_queue::revalidate_view: Sending view to revalidation queue at {}", - view.at.hash + view_at_hash = ?view.at.hash, + "revalidation_queue::revalidate_view: Sending view to revalidation queue" ); if let Some(ref to_worker) = self.background { - if let Err(e) = to_worker.unbounded_send(WorkerPayload::RevalidateView( + if let Err(error) = to_worker.unbounded_send(WorkerPayload::RevalidateView( view, finish_revalidation_worker_channels, )) { - log::warn!(target: LOG_TARGET, "revalidation_queue::revalidate_view: Failed to update background worker: {:?}", e); + warn!( + target: LOG_TARGET, + ?error, + "revalidation_queue::revalidate_view: Failed to update background worker" + ); } } else { view.revalidate(finish_revalidation_worker_channels).await @@ -161,17 +166,21 @@ where mempool: Arc<TxMemPool<Api, Block>>, finalized_hash: HashAndNumber<Block>, ) { - log::trace!( + trace!( target: LOG_TARGET, - "Sent mempool to revalidation queue at hash: {:?}", - finalized_hash + ?finalized_hash, + "Sent mempool to revalidation queue" ); if let Some(ref to_worker) = self.background { - if let Err(e) = + if let Err(error) = to_worker.unbounded_send(WorkerPayload::RevalidateMempool(mempool, finalized_hash)) { - log::warn!(target: LOG_TARGET, "Failed to update background worker: {:?}", e); + warn!( + target: LOG_TARGET, + ?error, + "Failed to update background worker" + ); } } else { mempool.revalidate(finalized_hash).await @@ -186,11 +195,11 @@ mod tests { use crate::{ common::tests::{uxt, TestApi}, fork_aware_txpool::view::FinishRevalidationLocalChannels, + TimedTransactionSource, }; use futures::executor::block_on; - use sc_transaction_pool_api::TransactionSource; use substrate_test_runtime::{AccountId, Transfer, H256}; - use substrate_test_runtime_client::AccountKeyring::Alice; + use substrate_test_runtime_client::Sr25519Keyring::Alice; #[test] fn revalidation_queue_works() { let api = Arc::new(TestApi::default()); @@ -212,9 +221,10 @@ mod tests { nonce: 0, }); - let _ = block_on( - view.submit_many(TransactionSource::External, std::iter::once(uxt.clone().into())), - ); + let _ = block_on(view.submit_many(std::iter::once(( + TimedTransactionSource::new_external(false), + uxt.clone().into(), + )))); assert_eq!(api.validation_requests().len(), 1); let (finish_revalidation_request_tx, finish_revalidation_request_rx) = diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs index 86c07008c3f3b468fab59047b64ef9e5b5f4c6f4..e141016ccb28b39a1be816478c0f6da748d875ce 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs @@ -26,25 +26,38 @@ //! it), while on other forks tx can be valid. Depending on which view is chosen to be cloned, //! such transaction could not be present in the newly created view. -use super::{metrics::MetricsLink as PrometheusMetrics, multi_view_listener::MultiViewListener}; -use crate::{ - common::log_xt::log_xt_trace, - graph, - graph::{tracked_map::Size, ExtrinsicFor, ExtrinsicHash}, - LOG_TARGET, +use std::{ + cmp::Ordering, + collections::HashMap, + sync::{ + atomic::{self, AtomicU64}, + Arc, + }, + time::Instant, }; + use futures::FutureExt; use itertools::Itertools; -use sc_transaction_pool_api::TransactionSource; +use parking_lot::RwLock; +use tracing::{debug, trace}; + +use sc_transaction_pool_api::{TransactionPriority, TransactionSource}; use sp_blockchain::HashAndNumber; use sp_runtime::{ traits::Block as BlockT, transaction_validity::{InvalidTransaction, TransactionValidityError}, }; -use std::{ - collections::HashMap, - sync::{atomic, atomic::AtomicU64, Arc}, - time::Instant, + +use crate::{ + common::tracing_log_xt::log_xt_trace, + graph, + graph::{base_pool::TimedTransactionSource, tracked_map::Size, ExtrinsicFor, ExtrinsicHash}, + LOG_TARGET, +}; + +use super::{ + metrics::MetricsLink as PrometheusMetrics, multi_view_listener::MultiViewListener, + view_store::ViewStoreSubmitOutcome, }; /// The minimum interval between single transaction revalidations. Given in blocks. @@ -64,27 +77,22 @@ where Block: BlockT, ChainApi: graph::ChainApi<Block = Block> + 'static, { - //todo: add listener for updating listeners with events [#5495] /// Is the progress of transaction watched. /// - /// Was transaction sent with `submit_and_watch`. + /// Indicates if transaction was sent with `submit_and_watch`. Serves only stats/testing + /// purposes. watched: bool, /// Extrinsic actual body. tx: ExtrinsicFor<ChainApi>, /// Size of the extrinsics actual body. bytes: usize, /// Transaction source. - source: TransactionSource, + source: TimedTransactionSource, /// When the transaction was revalidated, used to periodically revalidate the mem pool buffer. validated_at: AtomicU64, - //todo: we need to add future / ready status at finalized block. - //If future transactions are stuck in tx_mem_pool (due to limits being hit), we need a means - // to replace them somehow with newly coming transactions. - // For sure priority is one of them, but some additional criteria maybe required. - // - // The other maybe simple solution for this could be just obeying 10% limit for future in - // tx_mem_pool. Oldest future transaction could be just dropped. *(Status at finalized would - // also be needed). Probably is_future_at_finalized:Option<bool> flag will be enought + /// Priority of transaction at some block. It is assumed it will not be changed often. None if + /// not known. + priority: RwLock<Option<TransactionPriority>>, } impl<ChainApi, Block> TxInMemPool<ChainApi, Block> @@ -95,18 +103,57 @@ where /// Shall the progress of transaction be watched. /// /// Was transaction sent with `submit_and_watch`. - fn is_watched(&self) -> bool { + pub(crate) fn is_watched(&self) -> bool { self.watched } /// Creates a new instance of wrapper for unwatched transaction. fn new_unwatched(source: TransactionSource, tx: ExtrinsicFor<ChainApi>, bytes: usize) -> Self { - Self { watched: false, tx, source, validated_at: AtomicU64::new(0), bytes } + Self::new(false, source, tx, bytes) } /// Creates a new instance of wrapper for watched transaction. fn new_watched(source: TransactionSource, tx: ExtrinsicFor<ChainApi>, bytes: usize) -> Self { - Self { watched: true, tx, source, validated_at: AtomicU64::new(0), bytes } + Self::new(true, source, tx, bytes) + } + + /// Creates a new instance of wrapper for a transaction with no priority. + fn new( + watched: bool, + source: TransactionSource, + tx: ExtrinsicFor<ChainApi>, + bytes: usize, + ) -> Self { + Self::new_with_optional_priority(watched, source, tx, bytes, None) + } + + /// Creates a new instance of wrapper for a transaction with given priority. + fn new_with_priority( + watched: bool, + source: TransactionSource, + tx: ExtrinsicFor<ChainApi>, + bytes: usize, + priority: TransactionPriority, + ) -> Self { + Self::new_with_optional_priority(watched, source, tx, bytes, Some(priority)) + } + + /// Creates a new instance of wrapper for a transaction with optional priority. + fn new_with_optional_priority( + watched: bool, + source: TransactionSource, + tx: ExtrinsicFor<ChainApi>, + bytes: usize, + priority: Option<TransactionPriority>, + ) -> Self { + Self { + watched, + tx, + source: TimedTransactionSource::from_transaction_source(source, true), + validated_at: AtomicU64::new(0), + bytes, + priority: priority.into(), + } } /// Provides a clone of actual transaction body. @@ -117,8 +164,13 @@ where } /// Returns the source of the transaction. - pub(crate) fn source(&self) -> TransactionSource { - self.source + pub(crate) fn source(&self) -> TimedTransactionSource { + self.source.clone() + } + + /// Returns the priority of the transaction. + pub(crate) fn priority(&self) -> Option<TransactionPriority> { + *self.priority.read() } } @@ -155,7 +207,6 @@ where /// A shared instance of the `MultiViewListener`. /// /// Provides a side-channel allowing to send per-transaction state changes notification. - //todo: could be removed after removing watched field (and adding listener into tx) [#5495] listener: Arc<MultiViewListener<ChainApi>>, /// A map that stores the transactions currently in the memory pool. @@ -174,6 +225,23 @@ where max_transactions_total_bytes: usize, } +/// Helper structure to encapsulate a result of [`TxMemPool::try_insert`]. +#[derive(Debug)] +pub(super) struct InsertionInfo<Hash> { + pub(super) hash: Hash, + pub(super) source: TimedTransactionSource, + pub(super) removed: Vec<Hash>, +} + +impl<Hash> InsertionInfo<Hash> { + fn new(hash: Hash, source: TimedTransactionSource) -> Self { + Self::new_with_removed(hash, source, Default::default()) + } + fn new_with_removed(hash: Hash, source: TimedTransactionSource, removed: Vec<Hash>) -> Self { + Self { hash, source, removed } + } +} + impl<ChainApi, Block> TxMemPool<ChainApi, Block> where Block: BlockT, @@ -200,7 +268,7 @@ where } /// Creates a new `TxMemPool` instance for testing purposes. - #[allow(dead_code)] + #[cfg(test)] fn new_test( api: Arc<ChainApi>, max_transactions_count: usize, @@ -208,7 +276,7 @@ where ) -> Self { Self { api, - listener: Arc::from(MultiViewListener::new()), + listener: Arc::from(MultiViewListener::new_with_worker().0), transactions: Default::default(), metrics: Default::default(), max_transactions_count, @@ -220,8 +288,8 @@ where pub(super) fn get_by_hash( &self, hash: ExtrinsicHash<ChainApi>, - ) -> Option<ExtrinsicFor<ChainApi>> { - self.transactions.read().get(&hash).map(|t| t.tx()) + ) -> Option<Arc<TxInMemPool<ChainApi, Block>>> { + self.transactions.read().get(&hash).map(Clone::clone) } /// Returns a tuple with the count of unwatched and watched transactions in the memory pool. @@ -231,6 +299,11 @@ where (transactions.len() - watched_count, watched_count) } + /// Returns a total number of transactions kept within mempool. + pub fn len(&self) -> usize { + self.transactions.read().len() + } + /// Returns the number of bytes used by all extrinsics in the the pool. #[cfg(test)] pub fn bytes(&self) -> usize { @@ -247,28 +320,115 @@ where /// exceed the maximum allowed transaction count. fn try_insert( &self, - hash: ExtrinsicHash<ChainApi>, + tx_hash: ExtrinsicHash<ChainApi>, tx: TxInMemPool<ChainApi, Block>, - ) -> Result<ExtrinsicHash<ChainApi>, ChainApi::Error> { - let bytes = self.transactions.bytes(); + ) -> Result<InsertionInfo<ExtrinsicHash<ChainApi>>, sc_transaction_pool_api::error::Error> { let mut transactions = self.transactions.write(); + + let bytes = self.transactions.bytes(); + let result = match ( - !self.is_limit_exceeded(transactions.len() + 1, bytes + tx.bytes), - transactions.contains_key(&hash), + self.is_limit_exceeded(transactions.len() + 1, bytes + tx.bytes), + transactions.contains_key(&tx_hash), ) { - (true, false) => { - transactions.insert(hash, Arc::from(tx)); - Ok(hash) + (false, false) => { + let source = tx.source(); + transactions.insert(tx_hash, Arc::from(tx)); + Ok(InsertionInfo::new(tx_hash, source)) }, (_, true) => - Err(sc_transaction_pool_api::error::Error::AlreadyImported(Box::new(hash)).into()), - (false, _) => Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped.into()), + Err(sc_transaction_pool_api::error::Error::AlreadyImported(Box::new(tx_hash))), + (true, _) => Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped), }; - log::trace!(target: LOG_TARGET, "[{:?}] mempool::try_insert: {:?}", hash, result); - + trace!( + target: LOG_TARGET, + ?tx_hash, + result_hash = ?result.as_ref().map(|r| r.hash), + "mempool::try_insert" + ); result } + /// Attempts to insert a new transaction in the memory pool and drop some worse existing + /// transactions. + /// + /// A "worse" transaction means transaction with lower priority, or older transaction with the + /// same prio. + /// + /// This operation will not overflow the limit of the mempool. It means that cumulative + /// size of removed transactions will be equal (or greated) then size of newly inserted + /// transaction. + /// + /// Returns a `Result` containing `InsertionInfo` if the new transaction is successfully + /// inserted; otherwise, returns an appropriate error indicating the failure. + pub(super) fn try_insert_with_replacement( + &self, + new_tx: ExtrinsicFor<ChainApi>, + priority: TransactionPriority, + source: TransactionSource, + watched: bool, + ) -> Result<InsertionInfo<ExtrinsicHash<ChainApi>>, sc_transaction_pool_api::error::Error> { + let (hash, length) = self.api.hash_and_length(&new_tx); + let new_tx = TxInMemPool::new_with_priority(watched, source, new_tx, length, priority); + if new_tx.bytes > self.max_transactions_total_bytes { + return Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped); + } + + let mut transactions = self.transactions.write(); + + if transactions.contains_key(&hash) { + return Err(sc_transaction_pool_api::error::Error::AlreadyImported(Box::new(hash))); + } + + let mut sorted = transactions + .iter() + .filter_map(|(h, v)| v.priority().map(|_| (*h, v.clone()))) + .collect::<Vec<_>>(); + + // When pushing higher prio transaction, we need to find a number of lower prio txs, such + // that the sum of their bytes is ge then size of new tx. Otherwise we could overflow size + // limits. Naive way to do it - rev-sort by priority and eat the tail. + + // reverse (oldest, lowest prio last) + sorted.sort_by(|(_, a), (_, b)| match b.priority().cmp(&a.priority()) { + Ordering::Equal => match (a.source.timestamp, b.source.timestamp) { + (Some(a), Some(b)) => b.cmp(&a), + _ => Ordering::Equal, + }, + ordering => ordering, + }); + + let mut total_size_removed = 0usize; + let mut to_be_removed = vec![]; + let free_bytes = self.max_transactions_total_bytes - self.transactions.bytes(); + + loop { + let Some((worst_hash, worst_tx)) = sorted.pop() else { + return Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped); + }; + + if worst_tx.priority() >= new_tx.priority() { + return Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped); + } + + total_size_removed += worst_tx.bytes; + to_be_removed.push(worst_hash); + + if free_bytes + total_size_removed >= new_tx.bytes { + break; + } + } + + let source = new_tx.source(); + transactions.insert(hash, Arc::from(new_tx)); + for worst_hash in &to_be_removed { + transactions.remove(worst_hash); + } + debug_assert!(!self.is_limit_exceeded(transactions.len(), self.transactions.bytes())); + + Ok(InsertionInfo::new_with_removed(hash, source, to_be_removed)) + } + /// Adds a new unwatched transactions to the internal buffer not exceeding the limit. /// /// Returns the vector of results for each transaction, the order corresponds to the input @@ -277,7 +437,8 @@ where &self, source: TransactionSource, xts: &[ExtrinsicFor<ChainApi>], - ) -> Vec<Result<ExtrinsicHash<ChainApi>, ChainApi::Error>> { + ) -> Vec<Result<InsertionInfo<ExtrinsicHash<ChainApi>>, sc_transaction_pool_api::error::Error>> + { let result = xts .iter() .map(|xt| { @@ -294,60 +455,36 @@ where &self, source: TransactionSource, xt: ExtrinsicFor<ChainApi>, - ) -> Result<ExtrinsicHash<ChainApi>, ChainApi::Error> { + ) -> Result<InsertionInfo<ExtrinsicHash<ChainApi>>, sc_transaction_pool_api::error::Error> { let (hash, length) = self.api.hash_and_length(&xt); self.try_insert(hash, TxInMemPool::new_watched(source, xt.clone(), length)) } - /// Removes transactions from the memory pool which are specified by the given list of hashes - /// and send the `Dropped` event to the listeners of these transactions. - pub(super) async fn remove_dropped_transactions( - &self, - to_be_removed: &[ExtrinsicHash<ChainApi>], - ) { - log::debug!(target: LOG_TARGET, "remove_dropped_transactions count:{:?}", to_be_removed.len()); - log_xt_trace!(target: LOG_TARGET, to_be_removed, "[{:?}] mempool::remove_dropped_transactions"); - let mut transactions = self.transactions.write(); - to_be_removed.iter().for_each(|t| { - transactions.remove(t); - }); - - self.listener.transactions_dropped(to_be_removed); - } - - /// Clones and returns a `HashMap` of references to all unwatched transactions in the memory - /// pool. - pub(super) fn clone_unwatched( + /// Clones and returns a `HashMap` of references to all transactions in the memory pool. + pub(super) fn clone_transactions( &self, ) -> HashMap<ExtrinsicHash<ChainApi>, Arc<TxInMemPool<ChainApi, Block>>> { - self.transactions - .read() - .iter() - .filter_map(|(hash, tx)| (!tx.is_watched()).then(|| (*hash, tx.clone()))) - .collect::<HashMap<_, _>>() + self.transactions.clone_map() } - /// Clones and returns a `HashMap` of references to all watched transactions in the memory pool. - pub(super) fn clone_watched( + /// Removes a transaction with given hash from the memory pool. + pub(super) fn remove_transaction( &self, - ) -> HashMap<ExtrinsicHash<ChainApi>, Arc<TxInMemPool<ChainApi, Block>>> { - self.transactions - .read() - .iter() - .filter_map(|(hash, tx)| (tx.is_watched()).then(|| (*hash, tx.clone()))) - .collect::<HashMap<_, _>>() - } - - /// Removes a transaction from the memory pool based on a given hash. - pub(super) fn remove(&self, hash: ExtrinsicHash<ChainApi>) { - let _ = self.transactions.write().remove(&hash); + tx_hash: &ExtrinsicHash<ChainApi>, + ) -> Option<Arc<TxInMemPool<ChainApi, Block>>> { + debug!(target: LOG_TARGET, ?tx_hash, "mempool::remove_transaction"); + self.transactions.write().remove(tx_hash) } /// Revalidates a batch of transactions against the provided finalized block. /// /// Returns a vector of invalid transaction hashes. async fn revalidate_inner(&self, finalized_block: HashAndNumber<Block>) -> Vec<Block::Hash> { - log::trace!(target: LOG_TARGET, "mempool::revalidate at:{finalized_block:?}"); + trace!( + target: LOG_TARGET, + ?finalized_block, + "mempool::revalidate" + ); let start = Instant::now(); let (count, input) = { @@ -369,13 +506,13 @@ where }; let validations_futures = input.into_iter().map(|(xt_hash, xt)| { - self.api.validate_transaction(finalized_block.hash, xt.source, xt.tx()).map( - move |validation_result| { + self.api + .validate_transaction(finalized_block.hash, xt.source.clone().into(), xt.tx()) + .map(move |validation_result| { xt.validated_at .store(finalized_block.number.into().as_u64(), atomic::Ordering::Relaxed); (xt_hash, validation_result) - }, - ) + }) }); let validation_results = futures::future::join_all(validations_futures).await; let input_len = validation_results.len(); @@ -384,26 +521,31 @@ where let invalid_hashes = validation_results .into_iter() - .filter_map(|(xt_hash, validation_result)| match validation_result { + .filter_map(|(tx_hash, validation_result)| match validation_result { Ok(Ok(_)) | Ok(Err(TransactionValidityError::Invalid(InvalidTransaction::Future))) => None, Err(_) | Ok(Err(TransactionValidityError::Unknown(_))) | Ok(Err(TransactionValidityError::Invalid(_))) => { - log::trace!( + trace!( target: LOG_TARGET, - "[{:?}]: Purging: invalid: {:?}", - xt_hash, - validation_result, + ?tx_hash, + ?validation_result, + "Purging: invalid" ); - Some(xt_hash) + Some(tx_hash) }, }) .collect::<Vec<_>>(); - log::debug!( + debug!( target: LOG_TARGET, - "mempool::revalidate: at {finalized_block:?} count:{input_len}/{count} purged:{} took {duration:?}", invalid_hashes.len(), + ?finalized_block, + input_len, + count, + invalid_hashes = invalid_hashes.len(), + ?duration, + "mempool::revalidate" ); invalid_hashes @@ -414,8 +556,12 @@ where &self, finalized_xts: &Vec<ExtrinsicHash<ChainApi>>, ) { - log::debug!(target: LOG_TARGET, "purge_finalized_transactions count:{:?}", finalized_xts.len()); - log_xt_trace!(target: LOG_TARGET, finalized_xts, "[{:?}] purged finalized transactions"); + debug!( + target: LOG_TARGET, + count = finalized_xts.len(), + "purge_finalized_transactions" + ); + log_xt_trace!(target: LOG_TARGET, finalized_xts, "purged finalized transactions"); let mut transactions = self.transactions.write(); finalized_xts.iter().for_each(|t| { transactions.remove(t); @@ -425,7 +571,11 @@ where /// Revalidates transactions in the memory pool against a given finalized block and removes /// invalid ones. pub(super) async fn revalidate(&self, finalized_block: HashAndNumber<Block>) { - log::trace!(target: LOG_TARGET, "purge_transactions at:{:?}", finalized_block); + trace!( + target: LOG_TARGET, + ?finalized_block, + "purge_transactions" + ); let invalid_hashes = self.revalidate_inner(finalized_block.clone()).await; self.metrics.report(|metrics| { @@ -436,16 +586,30 @@ where invalid_hashes.iter().for_each(|i| { transactions.remove(i); }); - self.listener.invalidate_transactions(&invalid_hashes); + self.listener.transactions_invalidated(&invalid_hashes); + } + + /// Updates the priority of transaction stored in mempool using provided view_store submission + /// outcome. + pub(super) fn update_transaction_priority(&self, outcome: &ViewStoreSubmitOutcome<ChainApi>) { + outcome.priority().map(|priority| { + self.transactions + .write() + .get_mut(&outcome.hash()) + .map(|p| *p.priority.write() = Some(priority)) + }); } } #[cfg(test)] mod tx_mem_pool_tests { - use super::*; - use crate::{common::tests::TestApi, graph::ChainApi}; use substrate_test_runtime::{AccountId, Extrinsic, ExtrinsicBuilder, Transfer, H256}; - use substrate_test_runtime_client::AccountKeyring::*; + use substrate_test_runtime_client::Sr25519Keyring::*; + + use crate::{common::tests::TestApi, graph::ChainApi}; + + use super::*; + fn uxt(nonce: u64) -> Extrinsic { crate::common::tests::uxt(Transfer { from: Alice.into(), @@ -559,6 +723,9 @@ mod tx_mem_pool_tests { assert_eq!(mempool.unwatched_and_watched_count(), (10, 5)); } + /// size of large extrinsic + const LARGE_XT_SIZE: usize = 1129; + fn large_uxt(x: usize) -> Extrinsic { ExtrinsicBuilder::new_include_data(vec![x as u8; 1024]).build() } @@ -568,8 +735,7 @@ mod tx_mem_pool_tests { sp_tracing::try_init_simple(); let max = 10; let api = Arc::from(TestApi::default()); - //size of large extrinsic is: 1129 - let mempool = TxMemPool::new_test(api.clone(), usize::MAX, max * 1129); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, max * LARGE_XT_SIZE); let xts = (0..max).map(|x| Arc::from(large_uxt(x))).collect::<Vec<_>>(); @@ -593,4 +759,200 @@ mod tx_mem_pool_tests { sc_transaction_pool_api::error::Error::ImmediatelyDropped )); } + + #[test] + fn replacing_txs_works_for_same_tx_size() { + sp_tracing::try_init_simple(); + let max = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, max * LARGE_XT_SIZE); + + let xts = (0..max).map(|x| Arc::from(large_uxt(x))).collect::<Vec<_>>(); + + let low_prio = 0u64; + let hi_prio = u64::MAX; + + let total_xts_bytes = xts.iter().fold(0, |r, x| r + api.hash_and_length(&x).1); + let (submit_outcomes, hashes): (Vec<_>, Vec<_>) = xts + .iter() + .map(|t| { + let h = api.hash_and_length(t).0; + (ViewStoreSubmitOutcome::new(h, Some(low_prio)), h) + }) + .unzip(); + + let results = mempool.extend_unwatched(TransactionSource::External, &xts); + assert!(results.iter().all(Result::is_ok)); + assert_eq!(mempool.bytes(), total_xts_bytes); + + submit_outcomes + .into_iter() + .for_each(|o| mempool.update_transaction_priority(&o)); + + let xt = Arc::from(large_uxt(98)); + let hash = api.hash_and_length(&xt).0; + let result = mempool + .try_insert_with_replacement(xt, hi_prio, TransactionSource::External, false) + .unwrap(); + + assert_eq!(result.hash, hash); + assert_eq!(result.removed, hashes[0..1]); + } + + #[test] + fn replacing_txs_removes_proper_size_of_txs() { + sp_tracing::try_init_simple(); + let max = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, max * LARGE_XT_SIZE); + + let xts = (0..max).map(|x| Arc::from(large_uxt(x))).collect::<Vec<_>>(); + + let low_prio = 0u64; + let hi_prio = u64::MAX; + + let total_xts_bytes = xts.iter().fold(0, |r, x| r + api.hash_and_length(&x).1); + let (submit_outcomes, hashes): (Vec<_>, Vec<_>) = xts + .iter() + .map(|t| { + let h = api.hash_and_length(t).0; + (ViewStoreSubmitOutcome::new(h, Some(low_prio)), h) + }) + .unzip(); + + let results = mempool.extend_unwatched(TransactionSource::External, &xts); + assert!(results.iter().all(Result::is_ok)); + assert_eq!(mempool.bytes(), total_xts_bytes); + assert_eq!(total_xts_bytes, max * LARGE_XT_SIZE); + + submit_outcomes + .into_iter() + .for_each(|o| mempool.update_transaction_priority(&o)); + + //this one should drop 2 xts (size: 1130): + let xt = Arc::from(ExtrinsicBuilder::new_include_data(vec![98 as u8; 1025]).build()); + let (hash, length) = api.hash_and_length(&xt); + assert_eq!(length, 1130); + let result = mempool + .try_insert_with_replacement(xt, hi_prio, TransactionSource::External, false) + .unwrap(); + + assert_eq!(result.hash, hash); + assert_eq!(result.removed, hashes[0..2]); + } + + #[test] + fn replacing_txs_removes_proper_size_and_prios() { + sp_tracing::try_init_simple(); + const COUNT: usize = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, COUNT * LARGE_XT_SIZE); + + let xts = (0..COUNT).map(|x| Arc::from(large_uxt(x))).collect::<Vec<_>>(); + + let hi_prio = u64::MAX; + + let total_xts_bytes = xts.iter().fold(0, |r, x| r + api.hash_and_length(&x).1); + let (submit_outcomes, hashes): (Vec<_>, Vec<_>) = xts + .iter() + .enumerate() + .map(|(prio, t)| { + let h = api.hash_and_length(t).0; + (ViewStoreSubmitOutcome::new(h, Some((COUNT - prio).try_into().unwrap())), h) + }) + .unzip(); + + let results = mempool.extend_unwatched(TransactionSource::External, &xts); + assert!(results.iter().all(Result::is_ok)); + assert_eq!(mempool.bytes(), total_xts_bytes); + + submit_outcomes + .into_iter() + .for_each(|o| mempool.update_transaction_priority(&o)); + + //this one should drop 3 xts (each of size 1129) + let xt = Arc::from(ExtrinsicBuilder::new_include_data(vec![98 as u8; 2154]).build()); + let (hash, length) = api.hash_and_length(&xt); + // overhead is 105, thus length: 105 + 2154 + assert_eq!(length, 2 * LARGE_XT_SIZE + 1); + let result = mempool + .try_insert_with_replacement(xt, hi_prio, TransactionSource::External, false) + .unwrap(); + + assert_eq!(result.hash, hash); + assert!(result.removed.iter().eq(hashes[COUNT - 3..COUNT].iter().rev())); + } + + #[test] + fn replacing_txs_skips_lower_prio_tx() { + sp_tracing::try_init_simple(); + const COUNT: usize = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, COUNT * LARGE_XT_SIZE); + + let xts = (0..COUNT).map(|x| Arc::from(large_uxt(x))).collect::<Vec<_>>(); + + let hi_prio = 100u64; + let low_prio = 10u64; + + let total_xts_bytes = xts.iter().fold(0, |r, x| r + api.hash_and_length(&x).1); + let submit_outcomes = xts + .iter() + .map(|t| { + let h = api.hash_and_length(t).0; + ViewStoreSubmitOutcome::new(h, Some(hi_prio)) + }) + .collect::<Vec<_>>(); + + let results = mempool.extend_unwatched(TransactionSource::External, &xts); + assert!(results.iter().all(Result::is_ok)); + assert_eq!(mempool.bytes(), total_xts_bytes); + + submit_outcomes + .into_iter() + .for_each(|o| mempool.update_transaction_priority(&o)); + + let xt = Arc::from(large_uxt(98)); + let result = + mempool.try_insert_with_replacement(xt, low_prio, TransactionSource::External, false); + + // lower prio tx is rejected immediately + assert!(matches!( + result.unwrap_err(), + sc_transaction_pool_api::error::Error::ImmediatelyDropped + )); + } + + #[test] + fn replacing_txs_is_skipped_if_prios_are_not_set() { + sp_tracing::try_init_simple(); + const COUNT: usize = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, COUNT * LARGE_XT_SIZE); + + let xts = (0..COUNT).map(|x| Arc::from(large_uxt(x))).collect::<Vec<_>>(); + + let hi_prio = u64::MAX; + + let total_xts_bytes = xts.iter().fold(0, |r, x| r + api.hash_and_length(&x).1); + + let results = mempool.extend_unwatched(TransactionSource::External, &xts); + assert!(results.iter().all(Result::is_ok)); + assert_eq!(mempool.bytes(), total_xts_bytes); + + //this one could drop 3 xts (each of size 1129) + let xt = Arc::from(ExtrinsicBuilder::new_include_data(vec![98 as u8; 2154]).build()); + let length = api.hash_and_length(&xt).1; + // overhead is 105, thus length: 105 + 2154 + assert_eq!(length, 2 * LARGE_XT_SIZE + 1); + + let result = + mempool.try_insert_with_replacement(xt, hi_prio, TransactionSource::External, false); + + // we did not update priorities (update_transaction_priority was not called): + assert!(matches!( + result.unwrap_err(), + sc_transaction_pool_api::error::Error::ImmediatelyDropped + )); + } } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs index 99095d88cb0accaa0bdec86f3b594b20a25eb4ba..555444956122b721d220b3754fa67d2308381e89 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs @@ -25,21 +25,22 @@ use super::metrics::MetricsLink as PrometheusMetrics; use crate::{ - common::log_xt::log_xt_trace, + common::tracing_log_xt::log_xt_trace, graph::{ - self, watcher::Watcher, ExtrinsicFor, ExtrinsicHash, IsValidator, ValidatedTransaction, - ValidatedTransactionFor, + self, base_pool::TimedTransactionSource, ExtrinsicFor, ExtrinsicHash, IsValidator, + ValidatedPoolSubmitOutcome, ValidatedTransaction, ValidatedTransactionFor, }, LOG_TARGET, }; use parking_lot::Mutex; -use sc_transaction_pool_api::{error::Error as TxPoolError, PoolStatus, TransactionSource}; +use sc_transaction_pool_api::{error::Error as TxPoolError, PoolStatus}; use sp_blockchain::HashAndNumber; use sp_runtime::{ generic::BlockId, traits::Block as BlockT, transaction_validity::TransactionValidityError, SaturatedConversion, }; use std::{collections::HashMap, sync::Arc, time::Instant}; +use tracing::{debug, trace}; pub(super) struct RevalidationResult<ChainApi: graph::ChainApi> { revalidated: HashMap<ExtrinsicHash<ChainApi>, ValidatedTransactionFor<ChainApi>>, @@ -154,46 +155,55 @@ where } } + /// Imports single unvalidated extrinsic into the view. + pub(super) async fn submit_one( + &self, + source: TimedTransactionSource, + xt: ExtrinsicFor<ChainApi>, + ) -> Result<ValidatedPoolSubmitOutcome<ChainApi>, ChainApi::Error> { + self.submit_many(std::iter::once((source, xt))) + .await + .pop() + .expect("There is exactly one result, qed.") + } + /// Imports many unvalidated extrinsics into the view. pub(super) async fn submit_many( &self, - source: TransactionSource, - xts: impl IntoIterator<Item = ExtrinsicFor<ChainApi>>, - ) -> Vec<Result<ExtrinsicHash<ChainApi>, ChainApi::Error>> { - if log::log_enabled!(target: LOG_TARGET, log::Level::Trace) { + xts: impl IntoIterator<Item = (TimedTransactionSource, ExtrinsicFor<ChainApi>)>, + ) -> Vec<Result<ValidatedPoolSubmitOutcome<ChainApi>, ChainApi::Error>> { + if tracing::enabled!(target: LOG_TARGET, tracing::Level::TRACE) { let xts = xts.into_iter().collect::<Vec<_>>(); - log_xt_trace!(target: LOG_TARGET, xts.iter().map(|xt| self.pool.validated_pool().api().hash_and_length(xt).0), "[{:?}] view::submit_many at:{}", self.at.hash); - self.pool.submit_at(&self.at, source, xts).await + log_xt_trace!( + target: LOG_TARGET, + xts.iter().map(|(_,xt)| self.pool.validated_pool().api().hash_and_length(xt).0), + "view::submit_many at:{}", + self.at.hash); + self.pool.submit_at(&self.at, xts).await } else { - self.pool.submit_at(&self.at, source, xts).await + self.pool.submit_at(&self.at, xts).await } } - /// Import a single extrinsic and starts to watch its progress in the view. - pub(super) async fn submit_and_watch( - &self, - source: TransactionSource, - xt: ExtrinsicFor<ChainApi>, - ) -> Result<Watcher<ExtrinsicHash<ChainApi>, ExtrinsicHash<ChainApi>>, ChainApi::Error> { - log::trace!(target: LOG_TARGET, "[{:?}] view::submit_and_watch at:{}", self.pool.validated_pool().api().hash_and_length(&xt).0, self.at.hash); - self.pool.submit_and_watch(&self.at, source, xt).await - } - /// Synchronously imports single unvalidated extrinsics into the view. pub(super) fn submit_local( &self, xt: ExtrinsicFor<ChainApi>, - ) -> Result<ExtrinsicHash<ChainApi>, ChainApi::Error> { - let (hash, length) = self.pool.validated_pool().api().hash_and_length(&xt); - log::trace!(target: LOG_TARGET, "[{:?}] view::submit_local at:{}", hash, self.at.hash); - + ) -> Result<ValidatedPoolSubmitOutcome<ChainApi>, ChainApi::Error> { + let (tx_hash, length) = self.pool.validated_pool().api().hash_and_length(&xt); + trace!( + target: LOG_TARGET, + ?tx_hash, + view_at_hash = ?self.at.hash, + "view::submit_local" + ); let validity = self .pool .validated_pool() .api() .validate_transaction_blocking( self.at.hash, - TransactionSource::Local, + sc_transaction_pool_api::TransactionSource::Local, Arc::from(xt.clone()), )? .map_err(|e| { @@ -213,8 +223,8 @@ where let validated = ValidatedTransaction::valid_at( block_number.saturated_into::<u64>(), - hash, - TransactionSource::Local, + tx_hash, + TimedTransactionSource::new_local(true), Arc::from(xt), length, validity, @@ -228,18 +238,6 @@ where self.pool.validated_pool().status() } - /// Creates a watcher for given transaction. - /// - /// Intended to be called for the transaction that already exists in the pool - pub(super) fn create_watcher( - &self, - tx_hash: ExtrinsicHash<ChainApi>, - ) -> Watcher<ExtrinsicHash<ChainApi>, ExtrinsicHash<ChainApi>> { - //todo(minor): some assert could be added here - to make sure that transaction actually - // exists in the view. - self.pool.validated_pool().create_watcher(tx_hash) - } - /// Revalidates some part of transaction from the internal pool. /// /// Intended to be called from the revalidation worker. The revalidation process can be @@ -259,7 +257,11 @@ where revalidation_result_tx, } = finish_revalidation_worker_channels; - log::trace!(target:LOG_TARGET, "view::revalidate: at {} starting", self.at.hash); + trace!( + target: LOG_TARGET, + at_hash = ?self.at.hash, + "view::revalidate: at starting" + ); let start = Instant::now(); let validated_pool = self.pool.validated_pool(); let api = validated_pool.api(); @@ -280,12 +282,16 @@ where let mut should_break = false; tokio::select! { _ = finish_revalidation_request_rx.recv() => { - log::trace!(target: LOG_TARGET, "view::revalidate: finish revalidation request received at {}.", self.at.hash); + trace!( + target: LOG_TARGET, + at_hash = ?self.at.hash, + "view::revalidate: finish revalidation request received" + ); break } _ = async { if let Some(tx) = batch_iter.next() { - let validation_result = (api.validate_transaction(self.at.hash, tx.source, tx.data.clone()).await, tx.hash, tx); + let validation_result = (api.validate_transaction(self.at.hash, tx.source.clone().into(), tx.data.clone()).await, tx.hash, tx); validation_results.push(validation_result); } else { self.revalidation_worker_channels.lock().as_mut().map(|ch| ch.remove_sender()); @@ -303,16 +309,15 @@ where self.metrics.report(|metrics| { metrics.view_revalidation_duration.observe(revalidation_duration.as_secs_f64()); }); - log::debug!( - target:LOG_TARGET, - "view::revalidate: at {:?} count: {}/{} took {:?}", - self.at.hash, - validation_results.len(), + debug!( + target: LOG_TARGET, + at_hash = ?self.at.hash, + count = validation_results.len(), batch_len, - revalidation_duration + duration = ?revalidation_duration, + "view::revalidate" ); - log_xt_trace!(data:tuple, target:LOG_TARGET, validation_results.iter().map(|x| (x.1, &x.0)), "[{:?}] view::revalidateresult: {:?}"); - + log_xt_trace!(data:tuple, target:LOG_TARGET, validation_results.iter().map(|x| (x.1, &x.0)), "view::revalidate result: {:?}"); for (validation_result, tx_hash, tx) in validation_results { match validation_result { Ok(Err(TransactionValidityError::Invalid(_))) => { @@ -324,40 +329,49 @@ where ValidatedTransaction::valid_at( self.at.number.saturated_into::<u64>(), tx_hash, - tx.source, + tx.source.clone(), tx.data.clone(), api.hash_and_length(&tx.data).1, validity, ), ); }, - Ok(Err(TransactionValidityError::Unknown(e))) => { - log::trace!( + Ok(Err(TransactionValidityError::Unknown(error))) => { + trace!( target: LOG_TARGET, - "[{:?}]: Removing. Cannot determine transaction validity: {:?}", - tx_hash, - e + ?tx_hash, + ?error, + "Removing. Cannot determine transaction validity" ); invalid_hashes.push(tx_hash); }, - Err(validation_err) => { - log::trace!( + Err(error) => { + trace!( target: LOG_TARGET, - "[{:?}]: Removing due to error during revalidation: {}", - tx_hash, - validation_err + ?tx_hash, + %error, + "Removing due to error during revalidation" ); invalid_hashes.push(tx_hash); }, } } - log::trace!(target:LOG_TARGET, "view::revalidate: sending revalidation result at {}", self.at.hash); - if let Err(e) = revalidation_result_tx + trace!( + target: LOG_TARGET, + at_hash = ?self.at.hash, + "view::revalidate: sending revalidation result" + ); + if let Err(error) = revalidation_result_tx .send(RevalidationResult { invalid_hashes, revalidated }) .await { - log::trace!(target:LOG_TARGET, "view::revalidate: sending revalidation_result at {} failed {:?}", self.at.hash, e); + trace!( + target: LOG_TARGET, + at_hash = ?self.at.hash, + ?error, + "view::revalidate: sending revalidation_result failed" + ); } } @@ -375,7 +389,11 @@ where super::revalidation_worker::RevalidationQueue<ChainApi, ChainApi::Block>, >, ) { - log::trace!(target:LOG_TARGET,"view::start_background_revalidation: at {}", view.at.hash); + trace!( + target: LOG_TARGET, + at_hash = ?view.at.hash, + "view::start_background_revalidation" + ); let (finish_revalidation_request_tx, finish_revalidation_request_rx) = tokio::sync::mpsc::channel(1); let (revalidation_result_tx, revalidation_result_rx) = tokio::sync::mpsc::channel(1); @@ -405,10 +423,14 @@ where /// /// Refer to [*View revalidation*](../index.html#view-revalidation) for more details. pub(super) async fn finish_revalidation(&self) { - log::trace!(target:LOG_TARGET,"view::finish_revalidation: at {}", self.at.hash); + trace!( + target: LOG_TARGET, + at_hash = ?self.at.hash, + "view::finish_revalidation" + ); let Some(revalidation_worker_channels) = self.revalidation_worker_channels.lock().take() else { - log::trace!(target:LOG_TARGET, "view::finish_revalidation: no finish_revalidation_request_tx"); + trace!(target:LOG_TARGET, "view::finish_revalidation: no finish_revalidation_request_tx"); return }; @@ -418,8 +440,13 @@ where } = revalidation_worker_channels; if let Some(finish_revalidation_request_tx) = finish_revalidation_request_tx { - if let Err(e) = finish_revalidation_request_tx.send(()).await { - log::trace!(target:LOG_TARGET, "view::finish_revalidation: sending cancellation request at {} failed {:?}", self.at.hash, e); + if let Err(error) = finish_revalidation_request_tx.send(()).await { + trace!( + target: LOG_TARGET, + at_hash = ?self.at.hash, + %error, + "view::finish_revalidation: sending cancellation request failed" + ); } } @@ -445,14 +472,34 @@ where ); }); - log::debug!( - target:LOG_TARGET, - "view::finish_revalidation: applying revalidation result invalid: {} revalidated: {} at {:?} took {:?}", - revalidation_result.invalid_hashes.len(), - revalidated_len, - self.at.hash, - start.elapsed() + debug!( + target: LOG_TARGET, + invalid = revalidation_result.invalid_hashes.len(), + revalidated = revalidated_len, + at_hash = ?self.at.hash, + duration = ?start.elapsed(), + "view::finish_revalidation: applying revalidation result" ); } } + + /// Returns true if the transaction with given hash is already imported into the view. + pub(super) fn is_imported(&self, tx_hash: &ExtrinsicHash<ChainApi>) -> bool { + const IGNORE_BANNED: bool = false; + self.pool.validated_pool().check_is_known(tx_hash, IGNORE_BANNED).is_err() + } + + /// Removes the whole transaction subtree from the inner pool. + /// + /// Refer to [`crate::graph::ValidatedPool::remove_subtree`] for more details. + pub fn remove_subtree<F>( + &self, + tx_hash: ExtrinsicHash<ChainApi>, + listener_action: F, + ) -> Vec<ExtrinsicHash<ChainApi>> + where + F: Fn(&mut crate::graph::Listener<ChainApi>, ExtrinsicHash<ChainApi>), + { + self.pool.validated_pool().remove_subtree(tx_hash, listener_action) + } } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs index f23dcedd5bfd1b17b1c976708812fbb6251ca13c..e534decf9b1ada3d692a1ed3392475b4983b56ae 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs @@ -24,17 +24,114 @@ use super::{ }; use crate::{ fork_aware_txpool::dropped_watcher::MultiViewDroppedWatcherController, - graph, - graph::{base_pool::Transaction, ExtrinsicFor, ExtrinsicHash, TransactionFor}, + graph::{ + self, + base_pool::{TimedTransactionSource, Transaction}, + BaseSubmitOutcome, ExtrinsicFor, ExtrinsicHash, TransactionFor, ValidatedPoolSubmitOutcome, + }, ReadyIteratorFor, LOG_TARGET, }; -use futures::prelude::*; use itertools::Itertools; use parking_lot::RwLock; -use sc_transaction_pool_api::{error::Error as PoolError, PoolStatus, TransactionSource}; +use sc_transaction_pool_api::{error::Error as PoolError, PoolStatus}; use sp_blockchain::TreeRoute; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; -use std::{collections::HashMap, sync::Arc, time::Instant}; +use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + sync::Arc, + time::Instant, +}; +use tracing::{trace, warn}; + +/// Helper struct to maintain the context for pending transaction submission, executed for +/// newly inserted views. +#[derive(Clone)] +struct PendingTxSubmission<ChainApi> +where + ChainApi: graph::ChainApi, +{ + /// New transaction replacing the old one. + xt: ExtrinsicFor<ChainApi>, + /// Source of the transaction. + source: TimedTransactionSource, +} + +/// Helper type representing the callback allowing to trigger per-transaction events on +/// `ValidatedPool`'s listener. +type RemovalListener<ChainApi> = + Arc<dyn Fn(&mut crate::graph::Listener<ChainApi>, ExtrinsicHash<ChainApi>) + Send + Sync>; + +/// Helper struct to maintain the context for pending transaction removal, executed for +/// newly inserted views. +struct PendingTxRemoval<ChainApi> +where + ChainApi: graph::ChainApi, +{ + /// Hash of the transaction that will be removed, + xt_hash: ExtrinsicHash<ChainApi>, + /// Action that shall be executed on underlying `ValidatedPool`'s listener. + listener_action: RemovalListener<ChainApi>, +} + +/// This enum represents an action that should be executed on the newly built +/// view before this view is inserted into the view store. +enum PreInsertAction<ChainApi> +where + ChainApi: graph::ChainApi, +{ + /// Represents the action of submitting a new transaction. Intended to use to handle usurped + /// transactions. + SubmitTx(PendingTxSubmission<ChainApi>), + + /// Represents the action of removing a subtree of transactions. + RemoveSubtree(PendingTxRemoval<ChainApi>), +} + +/// Represents a task awaiting execution, to be performed immediately prior to the view insertion +/// into the view store. +struct PendingPreInsertTask<ChainApi> +where + ChainApi: graph::ChainApi, +{ + /// The action to be applied when inserting a new view. + action: PreInsertAction<ChainApi>, + /// Indicates if the action was already applied to all the views in the view_store. + /// If true, it can be removed after inserting any new view. + processed: bool, +} + +impl<ChainApi> PendingPreInsertTask<ChainApi> +where + ChainApi: graph::ChainApi, +{ + /// Creates new unprocessed instance of pending transaction submission. + fn new_submission_action(xt: ExtrinsicFor<ChainApi>, source: TimedTransactionSource) -> Self { + Self { + processed: false, + action: PreInsertAction::SubmitTx(PendingTxSubmission { xt, source }), + } + } + + /// Creates new unprocessed instance of pending transaction removal. + fn new_removal_action( + xt_hash: ExtrinsicHash<ChainApi>, + listener: RemovalListener<ChainApi>, + ) -> Self { + Self { + processed: false, + action: PreInsertAction::RemoveSubtree(PendingTxRemoval { + xt_hash, + listener_action: listener, + }), + } + } + + /// Marks a task as done for every view present in view store. Basically means that can be + /// removed on new view insertion. + fn mark_processed(&mut self) { + self.processed = true; + } +} /// The helper structure encapsulates all the views. pub(super) struct ViewStore<ChainApi, Block> @@ -62,6 +159,24 @@ where pub(super) most_recent_view: RwLock<Option<Block::Hash>>, /// The controller of multi view dropped stream. pub(super) dropped_stream_controller: MultiViewDroppedWatcherController<ChainApi>, + /// The map used to synchronize replacement of transactions between maintain and dropped + /// notifcication threads. It is meant to assure that replaced transaction is also removed from + /// newly built views in maintain process. + /// + /// The map's key is hash of actionable extrinsic (to avoid duplicated entries). + pending_txs_tasks: RwLock<HashMap<ExtrinsicHash<ChainApi>, PendingPreInsertTask<ChainApi>>>, +} + +/// Type alias to outcome of submission to `ViewStore`. +pub(super) type ViewStoreSubmitOutcome<ChainApi> = + BaseSubmitOutcome<ChainApi, TxStatusStream<ChainApi>>; + +impl<ChainApi: graph::ChainApi> From<ValidatedPoolSubmitOutcome<ChainApi>> + for ViewStoreSubmitOutcome<ChainApi> +{ + fn from(value: ValidatedPoolSubmitOutcome<ChainApi>) -> Self { + Self::new(value.hash(), value.priority()) + } } impl<ChainApi, Block> ViewStore<ChainApi, Block> @@ -83,15 +198,15 @@ where listener, most_recent_view: RwLock::from(None), dropped_stream_controller, + pending_txs_tasks: Default::default(), } } /// Imports a bunch of unverified extrinsics to every active view. pub(super) async fn submit( &self, - source: TransactionSource, - xts: impl IntoIterator<Item = ExtrinsicFor<ChainApi>> + Clone, - ) -> HashMap<Block::Hash, Vec<Result<ExtrinsicHash<ChainApi>, ChainApi::Error>>> { + xts: impl IntoIterator<Item = (TimedTransactionSource, ExtrinsicFor<ChainApi>)> + Clone, + ) -> HashMap<Block::Hash, Vec<Result<ViewStoreSubmitOutcome<ChainApi>, ChainApi::Error>>> { let submit_futures = { let active_views = self.active_views.read(); active_views @@ -99,7 +214,16 @@ where .map(|(_, view)| { let view = view.clone(); let xts = xts.clone(); - async move { (view.at.hash, view.submit_many(source, xts).await) } + async move { + ( + view.at.hash, + view.submit_many(xts) + .await + .into_iter() + .map(|r| r.map(Into::into)) + .collect::<Vec<_>>(), + ) + } }) .collect::<Vec<_>>() }; @@ -112,7 +236,7 @@ where pub(super) fn submit_local( &self, xt: ExtrinsicFor<ChainApi>, - ) -> Result<ExtrinsicHash<ChainApi>, ChainApi::Error> { + ) -> Result<ViewStoreSubmitOutcome<ChainApi>, ChainApi::Error> { let active_views = self .active_views .read() @@ -127,12 +251,19 @@ where .map(|view| view.submit_local(xt.clone())) .find_or_first(Result::is_ok); - if let Some(Err(err)) = result { - log::trace!(target: LOG_TARGET, "[{:?}] submit_local: err: {}", tx_hash, err); - return Err(err) - }; - - Ok(tx_hash) + match result { + Some(Err(error)) => { + trace!( + target: LOG_TARGET, + ?tx_hash, + %error, + "submit_local: err" + ); + Err(error) + }, + None => Ok(ViewStoreSubmitOutcome::new(tx_hash, None)), + Some(Ok(r)) => Ok(r.into()), + } } /// Import a single extrinsic and starts to watch its progress in the pool. @@ -145,47 +276,44 @@ where pub(super) async fn submit_and_watch( &self, _at: Block::Hash, - source: TransactionSource, + source: TimedTransactionSource, xt: ExtrinsicFor<ChainApi>, - ) -> Result<TxStatusStream<ChainApi>, ChainApi::Error> { + ) -> Result<ViewStoreSubmitOutcome<ChainApi>, ChainApi::Error> { let tx_hash = self.api.hash_and_length(&xt).0; let Some(external_watcher) = self.listener.create_external_watcher_for_tx(tx_hash) else { return Err(PoolError::AlreadyImported(Box::new(tx_hash)).into()) }; - let submit_and_watch_futures = { + let submit_futures = { let active_views = self.active_views.read(); active_views .iter() .map(|(_, view)| { let view = view.clone(); let xt = xt.clone(); - async move { - match view.submit_and_watch(source, xt).await { - Ok(watcher) => { - self.listener.add_view_watcher_for_tx( - tx_hash, - view.at.hash, - watcher.into_stream().boxed(), - ); - Ok(()) - }, - Err(e) => Err(e), - } - } + let source = source.clone(); + async move { view.submit_one(source, xt).await } }) .collect::<Vec<_>>() }; - let maybe_error = futures::future::join_all(submit_and_watch_futures) + let result = futures::future::join_all(submit_futures) .await .into_iter() .find_or_first(Result::is_ok); - if let Some(Err(err)) = maybe_error { - log::trace!(target: LOG_TARGET, "[{:?}] submit_and_watch: err: {}", tx_hash, err); - return Err(err); - }; - - Ok(external_watcher) + match result { + Some(Err(error)) => { + trace!( + target: LOG_TARGET, + ?tx_hash, + %error, + "submit_and_watch: err" + ); + return Err(error); + }, + Some(Ok(result)) => + Ok(ViewStoreSubmitOutcome::from(result).with_watcher(external_watcher)), + None => Ok(ViewStoreSubmitOutcome::new(tx_hash, None).with_watcher(external_watcher)), + } } /// Returns the pool status for every active view. @@ -261,12 +389,20 @@ where ) -> Vec<Transaction<ExtrinsicHash<ChainApi>, ExtrinsicFor<ChainApi>>> { self.most_recent_view .read() - .map(|at| self.get_view_at(at, true)) + .map(|at| self.futures_at(at)) .flatten() - .map(|(v, _)| v.pool.validated_pool().pool.read().futures().cloned().collect()) .unwrap_or_default() } + /// Returns a list of future transactions in the view at given block hash. + pub(super) fn futures_at( + &self, + at: Block::Hash, + ) -> Option<Vec<Transaction<ExtrinsicHash<ChainApi>, ExtrinsicFor<ChainApi>>>> { + self.get_view_at(at, true) + .map(|(v, _)| v.pool.validated_pool().pool.read().futures().cloned().collect()) + } + /// Collects all the transactions included in the blocks on the provided `tree_route` and /// triggers finalization event for them. /// @@ -278,8 +414,12 @@ where finalized_hash: Block::Hash, tree_route: &[Block::Hash], ) -> Vec<ExtrinsicHash<ChainApi>> { - log::trace!(target: LOG_TARGET, "finalize_route finalized_hash:{finalized_hash:?} tree_route: {tree_route:?}"); - + trace!( + target: LOG_TARGET, + ?finalized_hash, + ?tree_route, + "finalize_route" + ); let mut finalized_transactions = Vec::new(); for block in tree_route.iter().chain(std::iter::once(&finalized_hash)) { @@ -287,8 +427,12 @@ where .api .block_body(*block) .await - .unwrap_or_else(|e| { - log::warn!(target: LOG_TARGET, "Finalize route: error request: {}", e); + .unwrap_or_else(|error| { + warn!( + target: LOG_TARGET, + %error, + "Finalize route: error request" + ); None }) .unwrap_or_default() @@ -299,7 +443,7 @@ where extrinsics .iter() .enumerate() - .for_each(|(i, tx_hash)| self.listener.finalize_transaction(*tx_hash, *block, i)); + .for_each(|(i, tx_hash)| self.listener.transaction_finalized(*tx_hash, *block, i)); finalized_transactions.extend(extrinsics); } @@ -329,12 +473,16 @@ where /// - moved to the inactive views set (`inactive_views`), /// - removed from the multi view listeners. /// - /// The `most_recent_view` is update with the reference to the newly inserted view. + /// The `most_recent_view` is updated with the reference to the newly inserted view. + /// + /// If there are any pending tx replacments, they are applied to the new view. pub(super) async fn insert_new_view( &self, view: Arc<View<ChainApi>>, tree_route: &TreeRoute<Block>, ) { + self.apply_pending_tx_replacements(view.clone()).await; + //note: most_recent_view must be synced with changes in in/active_views. { let mut most_recent_view_lock = self.most_recent_view.write(); @@ -352,7 +500,11 @@ where active_views.insert(view.at.hash, view.clone()); most_recent_view_lock.replace(view.at.hash); }; - log::trace!(target:LOG_TARGET,"insert_new_view: inactive_views: {:?}", self.inactive_views.read().keys()); + trace!( + target: LOG_TARGET, + inactive_views = ?self.inactive_views.read().keys(), + "insert_new_view" + ); } /// Returns an optional reference to the view at given hash. @@ -386,8 +538,10 @@ where let mut removed_views = vec![]; { - self.active_views - .read() + let active_views = self.active_views.read(); + let inactive_views = self.inactive_views.read(); + + active_views .iter() .filter(|(hash, v)| !match finalized_number { Err(_) | Ok(None) => **hash == finalized_hash, @@ -396,11 +550,8 @@ where }) .map(|(_, v)| removed_views.push(v.at.hash)) .for_each(drop); - } - { - self.inactive_views - .read() + inactive_views .iter() .filter(|(_, v)| !match finalized_number { Err(_) | Ok(None) => false, @@ -410,8 +561,11 @@ where .for_each(drop); } - log::trace!(target:LOG_TARGET,"handle_pre_finalized: removed_views: {:?}", removed_views); - + trace!( + target: LOG_TARGET, + ?removed_views, + "handle_pre_finalized" + ); removed_views.iter().for_each(|view| { self.dropped_stream_controller.remove_view(*view); }); @@ -438,30 +592,56 @@ where let finalized_xts = self.finalize_route(finalized_hash, tree_route).await; let finalized_number = self.api.block_id_to_number(&BlockId::Hash(finalized_hash)); + let mut dropped_views = vec![]; //clean up older then finalized { let mut active_views = self.active_views.write(); - active_views.retain(|hash, v| match finalized_number { - Err(_) | Ok(None) => *hash == finalized_hash, - Ok(Some(n)) if v.at.number == n => *hash == finalized_hash, - Ok(Some(n)) => v.at.number > n, + let mut inactive_views = self.inactive_views.write(); + active_views.retain(|hash, v| { + let retain = match finalized_number { + Err(_) | Ok(None) => *hash == finalized_hash, + Ok(Some(n)) if v.at.number == n => *hash == finalized_hash, + Ok(Some(n)) => v.at.number > n, + }; + if !retain { + dropped_views.push(*hash); + } + retain }); - } - { - let mut inactive_views = self.inactive_views.write(); - inactive_views.retain(|_, v| match finalized_number { - Err(_) | Ok(None) => false, - Ok(Some(n)) => v.at.number >= n, + inactive_views.retain(|hash, v| { + let retain = match finalized_number { + Err(_) | Ok(None) => false, + Ok(Some(n)) => v.at.number >= n, + }; + if !retain { + dropped_views.push(*hash); + } + retain }); - log::trace!(target:LOG_TARGET,"handle_finalized: inactive_views: {:?}", inactive_views.keys()); + trace!( + target: LOG_TARGET, + inactive_views = ?inactive_views.keys(), + "handle_finalized" + ); } - self.listener.remove_view(finalized_hash); + trace!( + target: LOG_TARGET, + ?dropped_views, + "handle_finalized" + ); + self.listener.remove_stale_controllers(); self.dropped_stream_controller.remove_finalized_txs(finalized_xts.clone()); + self.listener.remove_view(finalized_hash); + for view in dropped_views { + self.listener.remove_view(view); + self.dropped_stream_controller.remove_view(view); + } + finalized_xts } @@ -482,6 +662,179 @@ where .collect::<Vec<_>>() }; futures::future::join_all(finish_revalidation_futures).await; - log::trace!(target:LOG_TARGET,"finish_background_revalidations took {:?}", start.elapsed()); + trace!( + target: LOG_TARGET, + duration = ?start.elapsed(), + "finish_background_revalidations" + ); + } + + /// Replaces an existing transaction in the view_store with a new one. + /// + /// Attempts to replace a transaction identified by `replaced` with a new transaction `xt`. + /// + /// Before submitting a transaction to the views, the new *unprocessed* transaction replacement + /// record will be inserted into a pending replacement map. Once the submission to all the views + /// is accomplished, the record is marked as *processed*. + /// + /// This map is later applied in `insert_new_view` method executed from different thread. + /// + /// If the transaction is already being replaced, it will simply return without making + /// changes. + pub(super) async fn replace_transaction( + &self, + source: TimedTransactionSource, + xt: ExtrinsicFor<ChainApi>, + replaced: ExtrinsicHash<ChainApi>, + ) { + if let Entry::Vacant(entry) = self.pending_txs_tasks.write().entry(replaced) { + entry.insert(PendingPreInsertTask::new_submission_action(xt.clone(), source.clone())); + } else { + return + }; + + let tx_hash = self.api.hash_and_length(&xt).0; + trace!( + target: LOG_TARGET, + ?replaced, + ?tx_hash, + "replace_transaction" + ); + self.replace_transaction_in_views(source, xt, tx_hash, replaced).await; + + if let Some(replacement) = self.pending_txs_tasks.write().get_mut(&replaced) { + replacement.mark_processed(); + } + } + + /// Applies pending transaction replacements to the specified view. + /// + /// After application, all already processed replacements are removed. + async fn apply_pending_tx_replacements(&self, view: Arc<View<ChainApi>>) { + let mut futures = vec![]; + for replacement in self.pending_txs_tasks.read().values() { + match replacement.action { + PreInsertAction::SubmitTx(ref submission) => { + let xt_hash = self.api.hash_and_length(&submission.xt).0; + futures.push(self.replace_transaction_in_view( + view.clone(), + submission.source.clone(), + submission.xt.clone(), + xt_hash, + )); + }, + PreInsertAction::RemoveSubtree(ref removal) => { + view.remove_subtree(removal.xt_hash, &*removal.listener_action); + }, + } + } + let _results = futures::future::join_all(futures).await; + self.pending_txs_tasks.write().retain(|_, r| r.processed); + } + + /// Submits `xt` to the given view. + /// + /// For watched transaction stream is added to the listener. + async fn replace_transaction_in_view( + &self, + view: Arc<View<ChainApi>>, + source: TimedTransactionSource, + xt: ExtrinsicFor<ChainApi>, + tx_hash: ExtrinsicHash<ChainApi>, + ) { + if let Err(error) = view.submit_one(source, xt).await { + trace!( + target: LOG_TARGET, + ?tx_hash, + at_hash = ?view.at.hash, + %error, + "replace_transaction: submit failed" + ); + } + } + + /// Sends `xt` to every view (both active and inactive) containing `replaced` extrinsics. + /// + /// It is assumed that transaction is already known by the pool. Intended to ba called when `xt` + /// is replacing `replaced` extrinsic. + async fn replace_transaction_in_views( + &self, + source: TimedTransactionSource, + xt: ExtrinsicFor<ChainApi>, + tx_hash: ExtrinsicHash<ChainApi>, + replaced: ExtrinsicHash<ChainApi>, + ) { + let submit_futures = { + let active_views = self.active_views.read(); + let inactive_views = self.inactive_views.read(); + active_views + .iter() + .chain(inactive_views.iter()) + .filter(|(_, view)| view.is_imported(&replaced)) + .map(|(_, view)| { + self.replace_transaction_in_view( + view.clone(), + source.clone(), + xt.clone(), + tx_hash, + ) + }) + .collect::<Vec<_>>() + }; + let _results = futures::future::join_all(submit_futures).await; + } + + /// Removes a transaction subtree from every view in the view_store, starting from the given + /// transaction hash. + /// + /// This function traverses the dependency graph of transactions and removes the specified + /// transaction along with all its descendant transactions from every view. + /// + /// A `listener_action` callback function is invoked for every transaction that is removed, + /// providing a reference to the pool's listener and the hash of the removed transaction. This + /// allows to trigger the required events. Note that listener may be called multiple times for + /// the same hash. + /// + /// Function will also schedule view pre-insertion actions to ensure that transactions will be + /// removed from newly created view. + /// + /// Returns a vector containing the hashes of all removed transactions, including the root + /// transaction specified by `tx_hash`. Vector contains only unique hashes. + pub(super) fn remove_transaction_subtree<F>( + &self, + xt_hash: ExtrinsicHash<ChainApi>, + listener_action: F, + ) -> Vec<ExtrinsicHash<ChainApi>> + where + F: Fn(&mut crate::graph::Listener<ChainApi>, ExtrinsicHash<ChainApi>) + + Clone + + Send + + Sync + + 'static, + { + if let Entry::Vacant(entry) = self.pending_txs_tasks.write().entry(xt_hash) { + entry.insert(PendingPreInsertTask::new_removal_action( + xt_hash, + Arc::from(listener_action.clone()), + )); + }; + + let mut seen = HashSet::new(); + + let removed = self + .active_views + .read() + .iter() + .chain(self.inactive_views.read().iter()) + .filter(|(_, view)| view.is_imported(&xt_hash)) + .flat_map(|(_, view)| view.remove_subtree(xt_hash, &listener_action)) + .filter(|xt_hash| seen.insert(*xt_hash)) + .collect(); + + if let Some(removal_action) = self.pending_txs_tasks.write().get_mut(&xt_hash) { + removal_action.mark_processed(); + } + + removed } } diff --git a/substrate/client/transaction-pool/src/graph/base_pool.rs b/substrate/client/transaction-pool/src/graph/base_pool.rs index e4c3a6c425a9e539585e58d0571ce2a6a2f68c0a..3b4afc88b7897cd063f38ad4d553265aa456bb91 100644 --- a/substrate/client/transaction-pool/src/graph/base_pool.rs +++ b/substrate/client/transaction-pool/src/graph/base_pool.rs @@ -20,7 +20,7 @@ //! //! For a more full-featured pool, have a look at the `pool` module. -use std::{cmp::Ordering, collections::HashSet, fmt, hash, sync::Arc}; +use std::{cmp::Ordering, collections::HashSet, fmt, hash, sync::Arc, time::Instant}; use crate::LOG_TARGET; use log::{trace, warn}; @@ -30,8 +30,8 @@ use sp_core::hexdisplay::HexDisplay; use sp_runtime::{ traits::Member, transaction_validity::{ - TransactionLongevity as Longevity, TransactionPriority as Priority, - TransactionSource as Source, TransactionTag as Tag, + TransactionLongevity as Longevity, TransactionPriority as Priority, TransactionSource, + TransactionTag as Tag, }, }; @@ -83,6 +83,44 @@ pub struct PruneStatus<Hash, Ex> { pub pruned: Vec<Arc<Transaction<Hash, Ex>>>, } +/// A transaction source that includes a timestamp indicating when the transaction was submitted. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TimedTransactionSource { + /// The original source of the transaction. + pub source: TransactionSource, + + /// The time at which the transaction was submitted. + pub timestamp: Option<Instant>, +} + +impl From<TimedTransactionSource> for TransactionSource { + fn from(value: TimedTransactionSource) -> Self { + value.source + } +} + +impl TimedTransactionSource { + /// Creates a new instance with an internal `TransactionSource::InBlock` source and an optional + /// timestamp. + pub fn new_in_block(with_timestamp: bool) -> Self { + Self { source: TransactionSource::InBlock, timestamp: with_timestamp.then(Instant::now) } + } + /// Creates a new instance with an internal `TransactionSource::External` source and an optional + /// timestamp. + pub fn new_external(with_timestamp: bool) -> Self { + Self { source: TransactionSource::External, timestamp: with_timestamp.then(Instant::now) } + } + /// Creates a new instance with an internal `TransactionSource::Local` source and an optional + /// timestamp. + pub fn new_local(with_timestamp: bool) -> Self { + Self { source: TransactionSource::Local, timestamp: with_timestamp.then(Instant::now) } + } + /// Creates a new instance with an given source and an optional timestamp. + pub fn from_transaction_source(source: TransactionSource, with_timestamp: bool) -> Self { + Self { source, timestamp: with_timestamp.then(Instant::now) } + } +} + /// Immutable transaction #[derive(PartialEq, Eq, Clone)] pub struct Transaction<Hash, Extrinsic> { @@ -102,8 +140,8 @@ pub struct Transaction<Hash, Extrinsic> { pub provides: Vec<Tag>, /// Should that transaction be propagated. pub propagate: bool, - /// Source of that transaction. - pub source: Source, + /// Timed source of that transaction. + pub source: TimedTransactionSource, } impl<Hash, Extrinsic> AsRef<Extrinsic> for Transaction<Hash, Extrinsic> { @@ -157,7 +195,7 @@ impl<Hash: Clone, Extrinsic: Clone> Transaction<Hash, Extrinsic> { bytes: self.bytes, hash: self.hash.clone(), priority: self.priority, - source: self.source, + source: self.source.clone(), valid_till: self.valid_till, requires: self.requires.clone(), provides: self.provides.clone(), @@ -322,22 +360,36 @@ impl<Hash: hash::Hash + Member + Serialize, Ex: std::fmt::Debug> BasePool<Hash, // import this transaction let current_hash = tx.transaction.hash.clone(); + let current_tx = tx.transaction.clone(); match self.ready.import(tx) { Ok(mut replaced) => { if !first { - promoted.push(current_hash); + promoted.push(current_hash.clone()); } + // If there were conflicting future transactions promoted, removed them from + // promoted set. + promoted.retain(|hash| replaced.iter().all(|tx| *hash != tx.hash)); // The transactions were removed from the ready pool. We might attempt to // re-import them. removed.append(&mut replaced); }, + Err(e @ error::Error::TooLowPriority { .. }) => + if first { + trace!(target: LOG_TARGET, "[{:?}] Error importing {first}: {:?}", current_tx.hash, e); + return Err(e) + } else { + trace!(target: LOG_TARGET, "[{:?}] Error importing {first}: {:?}", current_tx.hash, e); + removed.push(current_tx); + promoted.retain(|hash| *hash != current_hash); + }, // transaction failed to be imported. Err(e) => if first { - trace!(target: LOG_TARGET, "[{:?}] Error importing: {:?}", current_hash, e); + trace!(target: LOG_TARGET, "[{:?}] Error importing {first}: {:?}", current_tx.hash, e); return Err(e) } else { - failed.push(current_hash); + trace!(target: LOG_TARGET, "[{:?}] Error importing {first}: {:?}", current_tx.hash, e); + failed.push(current_tx.hash.clone()); }, } first = false; @@ -401,27 +453,29 @@ impl<Hash: hash::Hash + Member + Serialize, Ex: std::fmt::Debug> BasePool<Hash, while ready.is_exceeded(self.ready.len(), self.ready.bytes()) { // find the worst transaction - let worst = self.ready.fold::<TransactionRef<Hash, Ex>, _>(|worst, current| { - let transaction = ¤t.transaction; - worst - .map(|worst| { - // Here we don't use `TransactionRef`'s ordering implementation because - // while it prefers priority like need here, it also prefers older - // transactions for inclusion purposes and limit enforcement needs to prefer - // newer transactions instead and drop the older ones. - match worst.transaction.priority.cmp(&transaction.transaction.priority) { - Ordering::Less => worst, - Ordering::Equal => - if worst.insertion_id > transaction.insertion_id { - transaction.clone() - } else { - worst - }, - Ordering::Greater => transaction.clone(), - } - }) - .or_else(|| Some(transaction.clone())) - }); + let worst = + self.ready.fold::<Option<TransactionRef<Hash, Ex>>, _>(None, |worst, current| { + let transaction = ¤t.transaction; + worst + .map(|worst| { + // Here we don't use `TransactionRef`'s ordering implementation because + // while it prefers priority like need here, it also prefers older + // transactions for inclusion purposes and limit enforcement needs to + // prefer newer transactions instead and drop the older ones. + match worst.transaction.priority.cmp(&transaction.transaction.priority) + { + Ordering::Less => worst, + Ordering::Equal => + if worst.insertion_id > transaction.insertion_id { + transaction.clone() + } else { + worst + }, + Ordering::Greater => transaction.clone(), + } + }) + .or_else(|| Some(transaction.clone())) + }); if let Some(worst) = worst { removed.append(&mut self.remove_subtree(&[worst.transaction.hash.clone()])) @@ -434,8 +488,24 @@ impl<Hash: hash::Hash + Member + Serialize, Ex: std::fmt::Debug> BasePool<Hash, // find the worst transaction let worst = self.future.fold(|worst, current| match worst { None => Some(current.clone()), - Some(ref tx) if tx.imported_at > current.imported_at => Some(current.clone()), - other => other, + Some(worst) => Some( + match (worst.transaction.source.timestamp, current.transaction.source.timestamp) + { + (Some(worst_timestamp), Some(current_timestamp)) => { + if worst_timestamp > current_timestamp { + current.clone() + } else { + worst + } + }, + _ => + if worst.imported_at > current.imported_at { + current.clone() + } else { + worst + }, + }, + ), }); if let Some(worst) = worst { @@ -562,7 +632,7 @@ mod tests { requires: vec![], provides: vec![], propagate: true, - source: Source::External, + source: TimedTransactionSource::new_external(false), } } @@ -760,6 +830,58 @@ mod tests { ); } + #[test] + fn should_remove_conflicting_future() { + let mut pool = pool(); + pool.import(Transaction { + data: vec![3u8].into(), + hash: 3, + requires: vec![vec![1]], + priority: 50u64, + provides: vec![vec![3]], + ..default_tx().clone() + }) + .unwrap(); + assert_eq!(pool.ready().count(), 0); + assert_eq!(pool.ready.len(), 0); + + let tx2 = Transaction { + data: vec![2u8].into(), + hash: 2, + requires: vec![vec![1]], + provides: vec![vec![3]], + ..default_tx().clone() + }; + pool.import(tx2.clone()).unwrap(); + assert_eq!(pool.future.len(), 2); + + let res = pool + .import(Transaction { + data: vec![1u8].into(), + hash: 1, + provides: vec![vec![1]], + ..default_tx().clone() + }) + .unwrap(); + + assert_eq!( + res, + Imported::Ready { + hash: 1, + promoted: vec![3], + failed: vec![], + removed: vec![tx2.into()] + } + ); + + let mut it = pool.ready().into_iter().map(|tx| tx.data[0]); + assert_eq!(it.next(), Some(1)); + assert_eq!(it.next(), Some(3)); + assert_eq!(it.next(), None); + + assert_eq!(pool.future.len(), 0); + } + #[test] fn should_handle_a_cycle() { // given @@ -783,14 +905,14 @@ mod tests { assert_eq!(pool.ready.len(), 0); // when - pool.import(Transaction { + let tx2 = Transaction { data: vec![2u8].into(), hash: 2, requires: vec![vec![2]], provides: vec![vec![0]], ..default_tx().clone() - }) - .unwrap(); + }; + pool.import(tx2.clone()).unwrap(); // then { @@ -817,7 +939,12 @@ mod tests { assert_eq!(it.next(), None); assert_eq!( res, - Imported::Ready { hash: 4, promoted: vec![1, 3], failed: vec![2], removed: vec![] } + Imported::Ready { + hash: 4, + promoted: vec![1, 3], + failed: vec![], + removed: vec![tx2.into()] + } ); assert_eq!(pool.future.len(), 0); } @@ -1024,7 +1151,7 @@ mod tests { ), "Transaction { \ hash: 4, priority: 1000, valid_till: 64, bytes: 1, propagate: true, \ -source: TransactionSource::External, requires: [03, 02], provides: [04], data: [4]}" +source: TimedTransactionSource { source: TransactionSource::External, timestamp: None }, requires: [03, 02], provides: [04], data: [4]}" .to_owned() ); } diff --git a/substrate/client/transaction-pool/src/graph/future.rs b/substrate/client/transaction-pool/src/graph/future.rs index 2c1e64c04b7f2d72fb4e6a073b04fa40b828b538..848893b026c5cbb345ed481e197666774e717cc0 100644 --- a/substrate/client/transaction-pool/src/graph/future.rs +++ b/substrate/client/transaction-pool/src/graph/future.rs @@ -27,7 +27,7 @@ use sp_runtime::transaction_validity::TransactionTag as Tag; use std::time::Instant; use super::base_pool::Transaction; -use crate::{common::log_xt::log_xt_trace, LOG_TARGET}; +use crate::{common::tracing_log_xt::log_xt_trace, LOG_TARGET}; /// Transaction with partially satisfied dependencies. pub struct WaitingTransaction<Hash, Ex> { @@ -184,7 +184,7 @@ impl<Hash: hash::Hash + Eq + Clone + std::fmt::Debug, Ex: std::fmt::Debug> }) .collect::<Vec<_>>(); - log_xt_trace!(target: LOG_TARGET, &pruned, "[{:?}] FutureTransactions: removed while pruning tags."); + log_xt_trace!(target: LOG_TARGET, &pruned, "FutureTransactions: removed while pruning tags."); self.remove(&pruned) } diff --git a/substrate/client/transaction-pool/src/graph/listener.rs b/substrate/client/transaction-pool/src/graph/listener.rs index a5593920eec4053cda1555f81331b16b804304ab..0e70334ea0e2485967e35e533dc7281a3cef9421 100644 --- a/substrate/client/transaction-pool/src/graph/listener.rs +++ b/substrate/client/transaction-pool/src/graph/listener.rs @@ -29,20 +29,29 @@ use super::{watcher, BlockHash, ChainApi, ExtrinsicHash}; static LOG_TARGET: &str = "txpool::watcher"; -/// Single event used in dropped by limits stream. It is one of Ready/Future/Dropped. -pub type DroppedByLimitsEvent<H, BH> = (H, TransactionStatus<H, BH>); -/// Stream of events used to determine if a transaction was dropped. -pub type DroppedByLimitsStream<H, BH> = TracingUnboundedReceiver<DroppedByLimitsEvent<H, BH>>; +/// Single event used in aggregated stream. Tuple containing hash of transactions and its status. +pub type TransactionStatusEvent<H, BH> = (H, TransactionStatus<H, BH>); +/// Stream of events providing statuses of all the transactions within the pool. +pub type AggregatedStream<H, BH> = TracingUnboundedReceiver<TransactionStatusEvent<H, BH>>; + +/// Warning threshold for (unbounded) channel used in aggregated stream. +const AGGREGATED_STREAM_WARN_THRESHOLD: usize = 100_000; /// Extrinsic pool default listener. pub struct Listener<H: hash::Hash + Eq, C: ChainApi> { + /// Map containing per-transaction sinks for emitting transaction status events. watchers: HashMap<H, watcher::Sender<H, BlockHash<C>>>, finality_watchers: LinkedHashMap<ExtrinsicHash<C>, Vec<H>>, - /// The sink used to notify dropped-by-enforcing-limits transactions. Also ready and future - /// statuses are reported via this channel to allow consumer of the stream tracking actual - /// drops. - dropped_by_limits_sink: Option<TracingUnboundedSender<DroppedByLimitsEvent<H, BlockHash<C>>>>, + /// The sink used to notify dropped by enforcing limits or by being usurped transactions. + /// + /// Note: Ready and future statuses are alse communicated through this channel, enabling the + /// stream consumer to track views that reference the transaction. + dropped_stream_sink: Option<TracingUnboundedSender<TransactionStatusEvent<H, BlockHash<C>>>>, + + /// The sink of the single, merged stream providing updates for all the transactions in the + /// associated pool. + aggregated_stream_sink: Option<TracingUnboundedSender<TransactionStatusEvent<H, BlockHash<C>>>>, } /// Maximum number of blocks awaiting finality at any time. @@ -53,7 +62,8 @@ impl<H: hash::Hash + Eq + Debug, C: ChainApi> Default for Listener<H, C> { Self { watchers: Default::default(), finality_watchers: Default::default(), - dropped_by_limits_sink: None, + dropped_stream_sink: None, + aggregated_stream_sink: None, } } } @@ -83,21 +93,60 @@ impl<H: hash::Hash + traits::Member + Serialize + Clone, C: ChainApi> Listener<H sender.new_watcher(hash) } - /// Creates a new single stream for entire pool. + /// Creates a new single stream intended to watch dropped transactions only. /// - /// The stream can be used to subscribe to life-cycle events of all extrinsics in the pool. - pub fn create_dropped_by_limits_stream(&mut self) -> DroppedByLimitsStream<H, BlockHash<C>> { - let (sender, single_stream) = tracing_unbounded("mpsc_txpool_watcher", 100_000); - self.dropped_by_limits_sink = Some(sender); + /// The stream can be used to subscribe to events related to dropping of all extrinsics in the + /// pool. + pub fn create_dropped_by_limits_stream(&mut self) -> AggregatedStream<H, BlockHash<C>> { + let (sender, single_stream) = + tracing_unbounded("mpsc_txpool_watcher", AGGREGATED_STREAM_WARN_THRESHOLD); + self.dropped_stream_sink = Some(sender); single_stream } - /// Notify the listeners about extrinsic broadcast. + /// Creates a new single merged stream for all extrinsics in the associated pool. + /// + /// The stream can be used to subscribe to life-cycle events of all extrinsics in the pool. For + /// some implementations (e.g. fork-aware pool) this approach may be more efficient than using + /// individual streams for every transaction. + /// + /// Note: some of the events which are currently ignored on the other side of this channel + /// (external watcher) are not sent. + pub fn create_aggregated_stream(&mut self) -> AggregatedStream<H, BlockHash<C>> { + let (sender, aggregated_stream) = + tracing_unbounded("mpsc_txpool_aggregated_stream", AGGREGATED_STREAM_WARN_THRESHOLD); + self.aggregated_stream_sink = Some(sender); + aggregated_stream + } + + /// Notify the listeners about the extrinsic broadcast. pub fn broadcasted(&mut self, hash: &H, peers: Vec<String>) { trace!(target: LOG_TARGET, "[{:?}] Broadcasted", hash); self.fire(hash, |watcher| watcher.broadcast(peers)); } + /// Sends given event to the `dropped_stream_sink`. + fn send_to_dropped_stream_sink(&mut self, tx: &H, status: TransactionStatus<H, BlockHash<C>>) { + if let Some(ref sink) = self.dropped_stream_sink { + if let Err(e) = sink.unbounded_send((tx.clone(), status.clone())) { + trace!(target: LOG_TARGET, "[{:?}] dropped_sink: {:?} send message failed: {:?}", tx, status, e); + } + } + } + + /// Sends given event to the `aggregated_stream_sink`. + fn send_to_aggregated_stream_sink( + &mut self, + tx: &H, + status: TransactionStatus<H, BlockHash<C>>, + ) { + if let Some(ref sink) = self.aggregated_stream_sink { + if let Err(e) = sink.unbounded_send((tx.clone(), status.clone())) { + trace!(target: LOG_TARGET, "[{:?}] aggregated_stream {:?} send message failed: {:?}", tx, status, e); + } + } + } + /// New transaction was added to the ready pool or promoted from the future pool. pub fn ready(&mut self, tx: &H, old: Option<&H>) { trace!(target: LOG_TARGET, "[{:?}] Ready (replaced with {:?})", tx, old); @@ -106,43 +155,40 @@ impl<H: hash::Hash + traits::Member + Serialize + Clone, C: ChainApi> Listener<H self.fire(old, |watcher| watcher.usurped(tx.clone())); } - if let Some(ref sink) = self.dropped_by_limits_sink { - if let Err(e) = sink.unbounded_send((tx.clone(), TransactionStatus::Ready)) { - trace!(target: LOG_TARGET, "[{:?}] dropped_sink/ready: send message failed: {:?}", tx, e); - } - } + self.send_to_dropped_stream_sink(tx, TransactionStatus::Ready); + self.send_to_aggregated_stream_sink(tx, TransactionStatus::Ready); } /// New transaction was added to the future pool. pub fn future(&mut self, tx: &H) { trace!(target: LOG_TARGET, "[{:?}] Future", tx); self.fire(tx, |watcher| watcher.future()); - if let Some(ref sink) = self.dropped_by_limits_sink { - if let Err(e) = sink.unbounded_send((tx.clone(), TransactionStatus::Future)) { - trace!(target: LOG_TARGET, "[{:?}] dropped_sink/future: send message failed: {:?}", tx, e); - } - } + + self.send_to_dropped_stream_sink(tx, TransactionStatus::Future); + self.send_to_aggregated_stream_sink(tx, TransactionStatus::Future); } - /// Transaction was dropped from the pool because of the limit. - /// - /// If the function was actually called due to enforcing limits, the `limits_enforced` flag - /// shall be set to true. - pub fn dropped(&mut self, tx: &H, by: Option<&H>, limits_enforced: bool) { + /// Transaction was dropped from the pool because of enforcing the limit. + pub fn limits_enforced(&mut self, tx: &H) { + trace!(target: LOG_TARGET, "[{:?}] Dropped (limits enforced)", tx); + self.fire(tx, |watcher| watcher.limit_enforced()); + + self.send_to_dropped_stream_sink(tx, TransactionStatus::Dropped); + } + + /// Transaction was replaced with other extrinsic. + pub fn usurped(&mut self, tx: &H, by: &H) { trace!(target: LOG_TARGET, "[{:?}] Dropped (replaced with {:?})", tx, by); - self.fire(tx, |watcher| match by { - Some(t) => watcher.usurped(t.clone()), - None => watcher.dropped(), - }); - - //note: LimitEnforced could be introduced as new status to get rid of this flag. - if limits_enforced { - if let Some(ref sink) = self.dropped_by_limits_sink { - if let Err(e) = sink.unbounded_send((tx.clone(), TransactionStatus::Dropped)) { - trace!(target: LOG_TARGET, "[{:?}] dropped_sink/future: send message failed: {:?}", tx, e); - } - } - } + self.fire(tx, |watcher| watcher.usurped(by.clone())); + + self.send_to_dropped_stream_sink(tx, TransactionStatus::Usurped(by.clone())); + } + + /// Transaction was dropped from the pool because of the failure during the resubmission of + /// revalidate transactions or failure during pruning tags. + pub fn dropped(&mut self, tx: &H) { + trace!(target: LOG_TARGET, "[{:?}] Dropped", tx); + self.fire(tx, |watcher| watcher.dropped()); } /// Transaction was removed as invalid. @@ -161,11 +207,17 @@ impl<H: hash::Hash + traits::Member + Serialize + Clone, C: ChainApi> Listener<H let tx_index = txs.len() - 1; self.fire(tx, |watcher| watcher.in_block(block_hash, tx_index)); + self.send_to_aggregated_stream_sink(tx, TransactionStatus::InBlock((block_hash, tx_index))); while self.finality_watchers.len() > MAX_FINALITY_WATCHERS { if let Some((hash, txs)) = self.finality_watchers.pop_front() { for tx in txs { self.fire(&tx, |watcher| watcher.finality_timeout(hash)); + //todo: do we need this? [related issue: #5482] + self.send_to_aggregated_stream_sink( + &tx, + TransactionStatus::FinalityTimeout(hash), + ); } } } @@ -175,7 +227,8 @@ impl<H: hash::Hash + traits::Member + Serialize + Clone, C: ChainApi> Listener<H pub fn retracted(&mut self, block_hash: BlockHash<C>) { if let Some(hashes) = self.finality_watchers.remove(&block_hash) { for hash in hashes { - self.fire(&hash, |watcher| watcher.retracted(block_hash)) + self.fire(&hash, |watcher| watcher.retracted(block_hash)); + // note: [#5479], we do not send to aggregated stream. } } } diff --git a/substrate/client/transaction-pool/src/graph/mod.rs b/substrate/client/transaction-pool/src/graph/mod.rs index d93898b1b22ab7dbeeb910bd80e4da7f644fceae..c3161799785a97f668c93c8773257e57d3dfe91d 100644 --- a/substrate/client/transaction-pool/src/graph/mod.rs +++ b/substrate/client/transaction-pool/src/graph/mod.rs @@ -41,6 +41,14 @@ pub use self::pool::{ BlockHash, ChainApi, ExtrinsicFor, ExtrinsicHash, NumberFor, Options, Pool, RawExtrinsicFor, TransactionFor, ValidatedTransactionFor, }; -pub use validated_pool::{IsValidator, ValidatedTransaction}; +pub use validated_pool::{ + BaseSubmitOutcome, IsValidator, Listener, ValidatedPoolSubmitOutcome, ValidatedTransaction, +}; + +pub(crate) use self::pool::CheckBannedBeforeVerify; +pub(crate) use listener::TransactionStatusEvent; -pub(crate) use listener::DroppedByLimitsEvent; +#[cfg(doc)] +pub(crate) use listener::AggregatedStream; +#[cfg(doc)] +pub(crate) use validated_pool::ValidatedPool; diff --git a/substrate/client/transaction-pool/src/graph/pool.rs b/substrate/client/transaction-pool/src/graph/pool.rs index 2dd8de352c6bf3e640ccc2e229d7a008cc931331..52b12e3fabae6c0ed883427d36111401fe9c9b13 100644 --- a/substrate/client/transaction-pool/src/graph/pool.rs +++ b/substrate/client/transaction-pool/src/graph/pool.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. -use crate::{common::log_xt::log_xt_trace, LOG_TARGET}; +use crate::{common::tracing_log_xt::log_xt_trace, LOG_TARGET}; use futures::{channel::mpsc::Receiver, Future}; use indexmap::IndexMap; use sc_transaction_pool_api::error; @@ -37,7 +37,7 @@ use std::{ use super::{ base_pool as base, validated_pool::{IsValidator, ValidatedPool, ValidatedTransaction}, - watcher::Watcher, + ValidatedPoolSubmitOutcome, }; /// Modification notification event stream type; @@ -158,10 +158,17 @@ impl Default for Options { } } +impl Options { + /// Total (ready+future) maximal number of transactions in the pool. + pub fn total_count(&self) -> usize { + self.ready.count + self.future.count + } +} + /// Should we check that the transaction is banned /// in the pool, before we verify it? #[derive(Copy, Clone)] -enum CheckBannedBeforeVerify { +pub(crate) enum CheckBannedBeforeVerify { Yes, No, } @@ -172,6 +179,21 @@ pub struct Pool<B: ChainApi> { } impl<B: ChainApi> Pool<B> { + /// Create a new transaction pool with statically sized rotator. + pub fn new_with_staticly_sized_rotator( + options: Options, + is_validator: IsValidator, + api: Arc<B>, + ) -> Self { + Self { + validated_pool: Arc::new(ValidatedPool::new_with_staticly_sized_rotator( + options, + is_validator, + api, + )), + } + } + /// Create a new transaction pool. pub fn new(options: Options, is_validator: IsValidator, api: Arc<B>) -> Self { Self { validated_pool: Arc::new(ValidatedPool::new(options, is_validator, api)) } @@ -181,10 +203,8 @@ impl<B: ChainApi> Pool<B> { pub async fn submit_at( &self, at: &HashAndNumber<B::Block>, - source: TransactionSource, - xts: impl IntoIterator<Item = ExtrinsicFor<B>>, - ) -> Vec<Result<ExtrinsicHash<B>, B::Error>> { - let xts = xts.into_iter().map(|xt| (source, xt)); + xts: impl IntoIterator<Item = (base::TimedTransactionSource, ExtrinsicFor<B>)>, + ) -> Vec<Result<ValidatedPoolSubmitOutcome<B>, B::Error>> { let validated_transactions = self.verify(at, xts, CheckBannedBeforeVerify::Yes).await; self.validated_pool.submit(validated_transactions.into_values()) } @@ -195,10 +215,8 @@ impl<B: ChainApi> Pool<B> { pub async fn resubmit_at( &self, at: &HashAndNumber<B::Block>, - source: TransactionSource, - xts: impl IntoIterator<Item = ExtrinsicFor<B>>, - ) -> Vec<Result<ExtrinsicHash<B>, B::Error>> { - let xts = xts.into_iter().map(|xt| (source, xt)); + xts: impl IntoIterator<Item = (base::TimedTransactionSource, ExtrinsicFor<B>)>, + ) -> Vec<Result<ValidatedPoolSubmitOutcome<B>, B::Error>> { let validated_transactions = self.verify(at, xts, CheckBannedBeforeVerify::No).await; self.validated_pool.submit(validated_transactions.into_values()) } @@ -207,10 +225,10 @@ impl<B: ChainApi> Pool<B> { pub async fn submit_one( &self, at: &HashAndNumber<B::Block>, - source: TransactionSource, + source: base::TimedTransactionSource, xt: ExtrinsicFor<B>, - ) -> Result<ExtrinsicHash<B>, B::Error> { - let res = self.submit_at(at, source, std::iter::once(xt)).await.pop(); + ) -> Result<ValidatedPoolSubmitOutcome<B>, B::Error> { + let res = self.submit_at(at, std::iter::once((source, xt))).await.pop(); res.expect("One extrinsic passed; one result returned; qed") } @@ -218,9 +236,9 @@ impl<B: ChainApi> Pool<B> { pub async fn submit_and_watch( &self, at: &HashAndNumber<B::Block>, - source: TransactionSource, + source: base::TimedTransactionSource, xt: ExtrinsicFor<B>, - ) -> Result<Watcher<ExtrinsicHash<B>, ExtrinsicHash<B>>, B::Error> { + ) -> Result<ValidatedPoolSubmitOutcome<B>, B::Error> { let (_, tx) = self .verify_one(at.hash, at.number, source, xt, CheckBannedBeforeVerify::Yes) .await; @@ -288,6 +306,7 @@ impl<B: ChainApi> Pool<B> { let mut validated_counter: usize = 0; let mut future_tags = Vec::new(); + let now = Instant::now(); for (extrinsic, in_pool_tags) in all { match in_pool_tags { // reuse the tags for extrinsics that were found in the pool @@ -323,7 +342,7 @@ impl<B: ChainApi> Pool<B> { } } - log::trace!(target: LOG_TARGET,"prune: validated_counter:{validated_counter}"); + log::debug!(target: LOG_TARGET,"prune: validated_counter:{validated_counter}, took:{:?}", now.elapsed()); self.prune_tags(at, future_tags, in_pool_hashes).await } @@ -355,6 +374,7 @@ impl<B: ChainApi> Pool<B> { tags: impl IntoIterator<Item = Tag>, known_imported_hashes: impl IntoIterator<Item = ExtrinsicHash<B>> + Clone, ) { + let now = Instant::now(); log::trace!(target: LOG_TARGET, "Pruning at {:?}", at); // Prune all transactions that provide given tags let prune_status = self.validated_pool.prune_tags(tags); @@ -368,15 +388,14 @@ impl<B: ChainApi> Pool<B> { // Try to re-validate pruned transactions since some of them might be still valid. // note that `known_imported_hashes` will be rejected here due to temporary ban. let pruned_transactions = - prune_status.pruned.into_iter().map(|tx| (tx.source, tx.data.clone())); + prune_status.pruned.into_iter().map(|tx| (tx.source.clone(), tx.data.clone())); let reverified_transactions = self.verify(at, pruned_transactions, CheckBannedBeforeVerify::Yes).await; - let pruned_hashes = reverified_transactions.keys().map(Clone::clone).collect(); - - log::trace!(target: LOG_TARGET, "Pruning at {:?}. Resubmitting transactions: {}", &at, reverified_transactions.len()); - log_xt_trace!(data: tuple, target: LOG_TARGET, &reverified_transactions, "[{:?}] Resubmitting transaction: {:?}"); + let pruned_hashes = reverified_transactions.keys().map(Clone::clone).collect::<Vec<_>>(); + log::debug!(target: LOG_TARGET, "Pruning at {:?}. Resubmitting transactions: {}, reverification took: {:?}", &at, reverified_transactions.len(), now.elapsed()); + log_xt_trace!(data: tuple, target: LOG_TARGET, &reverified_transactions, "Resubmitting transaction: {:?}"); // And finally - submit reverified transactions back to the pool self.validated_pool.resubmit_pruned( @@ -396,7 +415,7 @@ impl<B: ChainApi> Pool<B> { async fn verify( &self, at: &HashAndNumber<B::Block>, - xts: impl IntoIterator<Item = (TransactionSource, ExtrinsicFor<B>)>, + xts: impl IntoIterator<Item = (base::TimedTransactionSource, ExtrinsicFor<B>)>, check: CheckBannedBeforeVerify, ) -> IndexMap<ExtrinsicHash<B>, ValidatedTransactionFor<B>> { let HashAndNumber { number, hash } = *at; @@ -413,11 +432,11 @@ impl<B: ChainApi> Pool<B> { } /// Returns future that validates single transaction at given block. - async fn verify_one( + pub(crate) async fn verify_one( &self, block_hash: <B::Block as BlockT>::Hash, block_number: NumberFor<B>, - source: TransactionSource, + source: base::TimedTransactionSource, xt: ExtrinsicFor<B>, check: CheckBannedBeforeVerify, ) -> (ExtrinsicHash<B>, ValidatedTransactionFor<B>) { @@ -431,7 +450,7 @@ impl<B: ChainApi> Pool<B> { let validation_result = self .validated_pool .api() - .validate_transaction(block_hash, source, xt.clone()) + .validate_transaction(block_hash, source.clone().into(), xt.clone()) .await; let status = match validation_result { @@ -488,6 +507,7 @@ mod tests { use super::{super::base_pool::Limit, *}; use crate::common::tests::{pool, uxt, TestApi, INVALID_NONCE}; use assert_matches::assert_matches; + use base::TimedTransactionSource; use codec::Encode; use futures::executor::block_on; use parking_lot::Mutex; @@ -495,9 +515,10 @@ mod tests { use sp_runtime::transaction_validity::TransactionSource; use std::{collections::HashMap, time::Instant}; use substrate_test_runtime::{AccountId, ExtrinsicBuilder, Transfer, H256}; - use substrate_test_runtime_client::AccountKeyring::{Alice, Bob}; + use substrate_test_runtime_client::Sr25519Keyring::{Alice, Bob}; - const SOURCE: TransactionSource = TransactionSource::External; + const SOURCE: TimedTransactionSource = + TimedTransactionSource { source: TransactionSource::External, timestamp: None }; #[test] fn should_validate_and_import_transaction() { @@ -518,6 +539,7 @@ mod tests { .into(), ), ) + .map(|outcome| outcome.hash()) .unwrap(); // then @@ -545,8 +567,11 @@ mod tests { let initial_hashes = txs.iter().map(|t| api.hash_and_length(t).0).collect::<Vec<_>>(); // when - let txs = txs.into_iter().map(|x| Arc::from(x)).collect::<Vec<_>>(); - let hashes = block_on(pool.submit_at(&api.expect_hash_and_number(0), SOURCE, txs)); + let txs = txs.into_iter().map(|x| (SOURCE, Arc::from(x))).collect::<Vec<_>>(); + let hashes = block_on(pool.submit_at(&api.expect_hash_and_number(0), txs)) + .into_iter() + .map(|r| r.map(|o| o.hash())) + .collect::<Vec<_>>(); log::debug!("--> {hashes:#?}"); // then @@ -570,7 +595,8 @@ mod tests { // when pool.validated_pool.ban(&Instant::now(), vec![pool.hash_of(&uxt)]); - let res = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.into())); + let res = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.into())) + .map(|o| o.hash()); assert_eq!(pool.validated_pool().status().ready, 0); assert_eq!(pool.validated_pool().status().future, 0); @@ -582,7 +608,7 @@ mod tests { fn should_reject_unactionable_transactions() { // given let api = Arc::new(TestApi::default()); - let pool = Pool::new( + let pool = Pool::new_with_staticly_sized_rotator( Default::default(), // the node does not author blocks false.into(), @@ -593,7 +619,8 @@ mod tests { let uxt = ExtrinsicBuilder::new_include_data(vec![42]).build(); // when - let res = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.into())); + let res = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.into())) + .map(|o| o.hash()); // then assert_matches!(res.unwrap_err(), error::Error::Unactionable); @@ -621,7 +648,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); let hash1 = block_on( pool.submit_one( &han_of_block0, @@ -635,7 +663,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); // future doesn't count let _hash = block_on( pool.submit_one( @@ -650,7 +679,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); assert_eq!(pool.validated_pool().status().ready, 2); assert_eq!(pool.validated_pool().status().future, 1); @@ -683,7 +713,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); let hash2 = block_on( pool.submit_one( &han_of_block0, @@ -697,7 +728,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); let hash3 = block_on( pool.submit_one( &han_of_block0, @@ -711,7 +743,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); // when pool.validated_pool.clear_stale(&api.expect_hash_and_number(5)); @@ -743,7 +776,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); // when block_on(pool.prune_tags(&api.expect_hash_and_number(1), vec![vec![0]], vec![hash1])); @@ -769,10 +803,11 @@ mod tests { let options = Options { ready: limit.clone(), future: limit.clone(), ..Default::default() }; let api = Arc::new(TestApi::default()); - let pool = Pool::new(options, true.into(), api.clone()); + let pool = Pool::new_with_staticly_sized_rotator(options, true.into(), api.clone()); - let hash1 = - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, xt.into())).unwrap(); + let hash1 = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, xt.into())) + .unwrap() + .hash(); assert_eq!(pool.validated_pool().status().future, 1); // when @@ -789,7 +824,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); // then assert_eq!(pool.validated_pool().status().future, 1); @@ -805,7 +841,7 @@ mod tests { let options = Options { ready: limit.clone(), future: limit.clone(), ..Default::default() }; let api = Arc::new(TestApi::default()); - let pool = Pool::new(options, true.into(), api.clone()); + let pool = Pool::new_with_staticly_sized_rotator(options, true.into(), api.clone()); // when block_on( @@ -821,6 +857,7 @@ mod tests { .into(), ), ) + .map(|o| o.hash()) .unwrap_err(); // then @@ -847,6 +884,7 @@ mod tests { .into(), ), ) + .map(|o| o.hash()) .unwrap_err(); // then @@ -875,7 +913,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); assert_eq!(pool.validated_pool().status().future, 0); @@ -912,7 +951,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); assert_eq!(pool.validated_pool().status().future, 0); @@ -951,7 +991,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 0); assert_eq!(pool.validated_pool().status().future, 1); @@ -990,7 +1031,8 @@ mod tests { }); let watcher = block_on(pool.submit_and_watch(&api.expect_hash_and_number(0), SOURCE, uxt.into())) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); // when @@ -1015,7 +1057,8 @@ mod tests { }); let watcher = block_on(pool.submit_and_watch(&api.expect_hash_and_number(0), SOURCE, uxt.into())) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); // when @@ -1038,7 +1081,7 @@ mod tests { Options { ready: limit.clone(), future: limit.clone(), ..Default::default() }; let api = Arc::new(TestApi::default()); - let pool = Pool::new(options, true.into(), api.clone()); + let pool = Pool::new_with_staticly_sized_rotator(options, true.into(), api.clone()); let xt = uxt(Transfer { from: Alice.into(), @@ -1048,7 +1091,8 @@ mod tests { }); let watcher = block_on(pool.submit_and_watch(&api.expect_hash_and_number(0), SOURCE, xt.into())) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); // when @@ -1076,7 +1120,7 @@ mod tests { Options { ready: limit.clone(), future: limit.clone(), ..Default::default() }; let api = Arc::new(TestApi::default()); - let pool = Pool::new(options, true.into(), api.clone()); + let pool = Pool::new_with_staticly_sized_rotator(options, true.into(), api.clone()); // after validation `IncludeData` will have priority set to 9001 // (validate_transaction mock) @@ -1108,14 +1152,16 @@ mod tests { Options { ready: limit.clone(), future: limit.clone(), ..Default::default() }; let api = Arc::new(TestApi::default()); - let pool = Pool::new(options, true.into(), api.clone()); + let pool = Pool::new_with_staticly_sized_rotator(options, true.into(), api.clone()); let han_of_block0 = api.expect_hash_and_number(0); // after validation `IncludeData` will have priority set to 9001 // (validate_transaction mock) let xt = ExtrinsicBuilder::new_include_data(Vec::new()).build(); - block_on(pool.submit_and_watch(&han_of_block0, SOURCE, xt.into())).unwrap(); + block_on(pool.submit_and_watch(&han_of_block0, SOURCE, xt.into())) + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); // after validation `Transfer` will have priority set to 4 (validate_transaction @@ -1126,8 +1172,9 @@ mod tests { amount: 5, nonce: 0, }); - let watcher = - block_on(pool.submit_and_watch(&han_of_block0, SOURCE, xt.into())).unwrap(); + let watcher = block_on(pool.submit_and_watch(&han_of_block0, SOURCE, xt.into())) + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 2); // when @@ -1153,7 +1200,11 @@ mod tests { let mut api = TestApi::default(); api.delay = Arc::new(Mutex::new(rx.into())); let api = Arc::new(api); - let pool = Arc::new(Pool::new(Default::default(), true.into(), api.clone())); + let pool = Arc::new(Pool::new_with_staticly_sized_rotator( + Default::default(), + true.into(), + api.clone(), + )); let han_of_block0 = api.expect_hash_and_number(0); diff --git a/substrate/client/transaction-pool/src/graph/ready.rs b/substrate/client/transaction-pool/src/graph/ready.rs index 860bcff0bacead422552a5375ec7462e1550075d..b8aef99e638dccee26ed5dde4b7c0c738625c24b 100644 --- a/substrate/client/transaction-pool/src/graph/ready.rs +++ b/substrate/client/transaction-pool/src/graph/ready.rs @@ -232,12 +232,10 @@ impl<Hash: hash::Hash + Member + Serialize, Ex> ReadyTransactions<Hash, Ex> { Ok(replaced) } - /// Fold a list of ready transactions to compute a single value. - pub fn fold<R, F: FnMut(Option<R>, &ReadyTx<Hash, Ex>) -> Option<R>>( - &mut self, - f: F, - ) -> Option<R> { - self.ready.read().values().fold(None, f) + /// Fold a list of ready transactions to compute a single value using initial value of + /// accumulator. + pub fn fold<R, F: FnMut(R, &ReadyTx<Hash, Ex>) -> R>(&self, init: R, f: F) -> R { + self.ready.read().values().fold(init, f) } /// Returns true if given transaction is part of the queue. @@ -589,7 +587,6 @@ fn remove_item<T: PartialEq>(vec: &mut Vec<T>, item: &T) { #[cfg(test)] mod tests { use super::*; - use sp_runtime::transaction_validity::TransactionSource as Source; fn tx(id: u8) -> Transaction<u64, Vec<u8>> { Transaction { @@ -601,7 +598,7 @@ mod tests { requires: vec![vec![1], vec![2]], provides: vec![vec![3], vec![4]], propagate: true, - source: Source::External, + source: crate::TimedTransactionSource::new_external(false), } } @@ -711,7 +708,7 @@ mod tests { requires: vec![tx1.provides[0].clone()], provides: vec![], propagate: true, - source: Source::External, + source: crate::TimedTransactionSource::new_external(false), }; // when diff --git a/substrate/client/transaction-pool/src/graph/rotator.rs b/substrate/client/transaction-pool/src/graph/rotator.rs index 61a26fb4138cac966bb78214c2e57b80db8aaa91..80d8f24144c8a02cd086bd2c765ea29055979483 100644 --- a/substrate/client/transaction-pool/src/graph/rotator.rs +++ b/substrate/client/transaction-pool/src/graph/rotator.rs @@ -31,7 +31,10 @@ use std::{ use super::base_pool::Transaction; /// Expected size of the banned extrinsics cache. -const EXPECTED_SIZE: usize = 2048; +const DEFAULT_EXPECTED_SIZE: usize = 2048; + +/// The default duration, in seconds, for which an extrinsic is banned. +const DEFAULT_BAN_TIME_SECS: u64 = 30 * 60; /// Pool rotator is responsible to only keep fresh extrinsics in the pool. /// @@ -42,18 +45,39 @@ pub struct PoolRotator<Hash> { ban_time: Duration, /// Currently banned extrinsics. banned_until: RwLock<HashMap<Hash, Instant>>, + /// Expected size of the banned extrinsics cache. + expected_size: usize, +} + +impl<Hash: Clone> Clone for PoolRotator<Hash> { + fn clone(&self) -> Self { + Self { + ban_time: self.ban_time, + banned_until: RwLock::new(self.banned_until.read().clone()), + expected_size: self.expected_size, + } + } } impl<Hash: hash::Hash + Eq> Default for PoolRotator<Hash> { fn default() -> Self { - Self { ban_time: Duration::from_secs(60 * 30), banned_until: Default::default() } + Self { + ban_time: Duration::from_secs(DEFAULT_BAN_TIME_SECS), + banned_until: Default::default(), + expected_size: DEFAULT_EXPECTED_SIZE, + } } } impl<Hash: hash::Hash + Eq + Clone> PoolRotator<Hash> { /// New rotator instance with specified ban time. pub fn new(ban_time: Duration) -> Self { - Self { ban_time, banned_until: Default::default() } + Self { ban_time, ..Self::default() } + } + + /// New rotator instance with specified ban time and expected cache size. + pub fn new_with_expected_size(ban_time: Duration, expected_size: usize) -> Self { + Self { expected_size, ..Self::new(ban_time) } } /// Returns `true` if extrinsic hash is currently banned. @@ -69,8 +93,8 @@ impl<Hash: hash::Hash + Eq + Clone> PoolRotator<Hash> { banned.insert(hash, *now + self.ban_time); } - if banned.len() > 2 * EXPECTED_SIZE { - while banned.len() > EXPECTED_SIZE { + if banned.len() > 2 * self.expected_size { + while banned.len() > self.expected_size { if let Some(key) = banned.keys().next().cloned() { banned.remove(&key); } @@ -106,7 +130,6 @@ impl<Hash: hash::Hash + Eq + Clone> PoolRotator<Hash> { #[cfg(test)] mod tests { use super::*; - use sp_runtime::transaction_validity::TransactionSource; type Hash = u64; type Ex = (); @@ -126,7 +149,7 @@ mod tests { requires: vec![], provides: vec![], propagate: true, - source: TransactionSource::External, + source: crate::TimedTransactionSource::new_external(false), }; (hash, tx) @@ -192,7 +215,7 @@ mod tests { requires: vec![], provides: vec![], propagate: true, - source: TransactionSource::External, + source: crate::TimedTransactionSource::new_external(false), } } @@ -202,16 +225,16 @@ mod tests { let past_block = 0; // when - for i in 0..2 * EXPECTED_SIZE { + for i in 0..2 * DEFAULT_EXPECTED_SIZE { let tx = tx_with(i as u64, past_block); assert!(rotator.ban_if_stale(&now, past_block, &tx)); } - assert_eq!(rotator.banned_until.read().len(), 2 * EXPECTED_SIZE); + assert_eq!(rotator.banned_until.read().len(), 2 * DEFAULT_EXPECTED_SIZE); // then - let tx = tx_with(2 * EXPECTED_SIZE as u64, past_block); + let tx = tx_with(2 * DEFAULT_EXPECTED_SIZE as u64, past_block); // trigger a garbage collection assert!(rotator.ban_if_stale(&now, past_block, &tx)); - assert_eq!(rotator.banned_until.read().len(), EXPECTED_SIZE); + assert_eq!(rotator.banned_until.read().len(), DEFAULT_EXPECTED_SIZE); } } diff --git a/substrate/client/transaction-pool/src/graph/tracked_map.rs b/substrate/client/transaction-pool/src/graph/tracked_map.rs index 6c3bbbf34b553e3e03f47d11317cd9f52aafc847..ca1ee035cf37e1e5b8834ed8df49fcd818d37b51 100644 --- a/substrate/client/transaction-pool/src/graph/tracked_map.rs +++ b/substrate/client/transaction-pool/src/graph/tracked_map.rs @@ -120,11 +120,6 @@ where pub fn len(&self) -> usize { self.inner_guard.len() } - - /// Returns an iterator over all key-value pairs. - pub fn iter(&self) -> Iter<'_, K, V> { - self.inner_guard.iter() - } } pub struct TrackedMapWriteAccess<'a, K, V> { @@ -173,6 +168,11 @@ where pub fn len(&mut self) -> usize { self.inner_guard.len() } + + /// Returns an iterator over all key-value pairs. + pub fn iter(&self) -> Iter<'_, K, V> { + self.inner_guard.iter() + } } #[cfg(test)] diff --git a/substrate/client/transaction-pool/src/graph/validated_pool.rs b/substrate/client/transaction-pool/src/graph/validated_pool.rs index d7f55198a40a9ded48f8c7a94ffb481d781f8219..9631a27ead93416b4fbc52aa35d2f0b2c036bfad 100644 --- a/substrate/client/transaction-pool/src/graph/validated_pool.rs +++ b/substrate/client/transaction-pool/src/graph/validated_pool.rs @@ -18,25 +18,22 @@ use std::{ collections::{HashMap, HashSet}, - hash, sync::Arc, }; -use crate::{common::log_xt::log_xt_trace, LOG_TARGET}; +use crate::{common::tracing_log_xt::log_xt_trace, LOG_TARGET}; use futures::channel::mpsc::{channel, Sender}; use parking_lot::{Mutex, RwLock}; -use sc_transaction_pool_api::{error, PoolStatus, ReadyTransactions}; -use serde::Serialize; +use sc_transaction_pool_api::{error, PoolStatus, ReadyTransactions, TransactionPriority}; use sp_blockchain::HashAndNumber; use sp_runtime::{ - traits::{self, SaturatedConversion}, - transaction_validity::{TransactionSource, TransactionTag as Tag, ValidTransaction}, + traits::SaturatedConversion, + transaction_validity::{TransactionTag as Tag, ValidTransaction}, }; use std::time::Instant; use super::{ base_pool::{self as base, PruneStatus}, - listener::Listener, pool::{ BlockHash, ChainApi, EventStream, ExtrinsicFor, ExtrinsicHash, Options, TransactionFor, }, @@ -62,7 +59,7 @@ impl<Hash, Ex, Error> ValidatedTransaction<Hash, Ex, Error> { pub fn valid_at( at: u64, hash: Hash, - source: TransactionSource, + source: base::TimedTransactionSource, data: Ex, bytes: usize, validity: ValidTransaction, @@ -79,12 +76,23 @@ impl<Hash, Ex, Error> ValidatedTransaction<Hash, Ex, Error> { valid_till: at.saturated_into::<u64>().saturating_add(validity.longevity), }) } + + /// Returns priority for valid transaction, None if transaction is not valid. + pub fn priority(&self) -> Option<TransactionPriority> { + match self { + ValidatedTransaction::Valid(base::Transaction { priority, .. }) => Some(*priority), + _ => None, + } + } } -/// A type of validated transaction stored in the pool. +/// A type of validated transaction stored in the validated pool. pub type ValidatedTransactionFor<B> = ValidatedTransaction<ExtrinsicHash<B>, ExtrinsicFor<B>, <B as ChainApi>::Error>; +/// A type alias representing ValidatedPool listener for given ChainApi type. +pub type Listener<B> = super::listener::Listener<ExtrinsicHash<B>, B>; + /// A closure that returns true if the local node is a validator that can author blocks. #[derive(Clone)] pub struct IsValidator(Arc<Box<dyn Fn() -> bool + Send + Sync>>); @@ -101,12 +109,56 @@ impl From<Box<dyn Fn() -> bool + Send + Sync>> for IsValidator { } } +/// Represents the result of `submit` or `submit_and_watch` operations. +pub struct BaseSubmitOutcome<B: ChainApi, W> { + /// The hash of the submitted transaction. + hash: ExtrinsicHash<B>, + /// A transaction watcher. This is `Some` for `submit_and_watch` and `None` for `submit`. + watcher: Option<W>, + + /// The priority of the transaction. Defaults to None if unknown. + priority: Option<TransactionPriority>, +} + +/// Type alias to outcome of submission to `ValidatedPool`. +pub type ValidatedPoolSubmitOutcome<B> = + BaseSubmitOutcome<B, Watcher<ExtrinsicHash<B>, ExtrinsicHash<B>>>; + +impl<B: ChainApi, W> BaseSubmitOutcome<B, W> { + /// Creates a new instance with given hash and priority. + pub fn new(hash: ExtrinsicHash<B>, priority: Option<TransactionPriority>) -> Self { + Self { hash, priority, watcher: None } + } + + /// Sets the transaction watcher. + pub fn with_watcher(mut self, watcher: W) -> Self { + self.watcher = Some(watcher); + self + } + + /// Provides priority of submitted transaction. + pub fn priority(&self) -> Option<TransactionPriority> { + self.priority + } + + /// Provides hash of submitted transaction. + pub fn hash(&self) -> ExtrinsicHash<B> { + self.hash + } + + /// Provides a watcher. Should only be called on outcomes of `submit_and_watch`. Otherwise will + /// panic (that would mean logical error in program). + pub fn expect_watcher(&mut self) -> W { + self.watcher.take().expect("watcher was set in submit_and_watch. qed") + } +} + /// Pool that deals with validated transactions. pub struct ValidatedPool<B: ChainApi> { api: Arc<B>, is_validator: IsValidator, options: Options, - listener: RwLock<Listener<ExtrinsicHash<B>, B>>, + listener: RwLock<Listener<B>>, pub(crate) pool: RwLock<base::BasePool<ExtrinsicHash<B>, ExtrinsicFor<B>>>, import_notification_sinks: Mutex<Vec<Sender<ExtrinsicHash<B>>>>, rotator: PoolRotator<ExtrinsicHash<B>>, @@ -121,16 +173,41 @@ impl<B: ChainApi> Clone for ValidatedPool<B> { listener: Default::default(), pool: RwLock::from(self.pool.read().clone()), import_notification_sinks: Default::default(), - rotator: PoolRotator::default(), + rotator: self.rotator.clone(), } } } impl<B: ChainApi> ValidatedPool<B> { + /// Create a new transaction pool with statically sized rotator. + pub fn new_with_staticly_sized_rotator( + options: Options, + is_validator: IsValidator, + api: Arc<B>, + ) -> Self { + let ban_time = options.ban_time; + Self::new_with_rotator(options, is_validator, api, PoolRotator::new(ban_time)) + } + /// Create a new transaction pool. pub fn new(options: Options, is_validator: IsValidator, api: Arc<B>) -> Self { - let base_pool = base::BasePool::new(options.reject_future_transactions); let ban_time = options.ban_time; + let total_count = options.total_count(); + Self::new_with_rotator( + options, + is_validator, + api, + PoolRotator::new_with_expected_size(ban_time, total_count), + ) + } + + fn new_with_rotator( + options: Options, + is_validator: IsValidator, + api: Arc<B>, + rotator: PoolRotator<ExtrinsicHash<B>>, + ) -> Self { + let base_pool = base::BasePool::new(options.reject_future_transactions); Self { is_validator, options, @@ -138,7 +215,7 @@ impl<B: ChainApi> ValidatedPool<B> { api, pool: RwLock::new(base_pool), import_notification_sinks: Default::default(), - rotator: PoolRotator::new(ban_time), + rotator, } } @@ -175,7 +252,7 @@ impl<B: ChainApi> ValidatedPool<B> { pub fn submit( &self, txs: impl IntoIterator<Item = ValidatedTransactionFor<B>>, - ) -> Vec<Result<ExtrinsicHash<B>, B::Error>> { + ) -> Vec<Result<ValidatedPoolSubmitOutcome<B>, B::Error>> { let results = txs .into_iter() .map(|validated_tx| self.submit_one(validated_tx)) @@ -191,7 +268,7 @@ impl<B: ChainApi> ValidatedPool<B> { results .into_iter() .map(|res| match res { - Ok(ref hash) if removed.contains(hash) => + Ok(outcome) if removed.contains(&outcome.hash) => Err(error::Error::ImmediatelyDropped.into()), other => other, }) @@ -199,9 +276,13 @@ impl<B: ChainApi> ValidatedPool<B> { } /// Submit single pre-validated transaction to the pool. - fn submit_one(&self, tx: ValidatedTransactionFor<B>) -> Result<ExtrinsicHash<B>, B::Error> { + fn submit_one( + &self, + tx: ValidatedTransactionFor<B>, + ) -> Result<ValidatedPoolSubmitOutcome<B>, B::Error> { match tx { ValidatedTransaction::Valid(tx) => { + let priority = tx.priority; log::trace!(target: LOG_TARGET, "[{:?}] ValidatedPool::submit_one", tx.hash); if !tx.propagate && !(self.is_validator.0)() { return Err(error::Error::Unactionable.into()) @@ -229,7 +310,7 @@ impl<B: ChainApi> ValidatedPool<B> { let mut listener = self.listener.write(); fire_events(&mut *listener, &imported); - Ok(*imported.hash()) + Ok(ValidatedPoolSubmitOutcome::new(*imported.hash(), Some(priority))) }, ValidatedTransaction::Invalid(hash, err) => { log::trace!(target: LOG_TARGET, "[{:?}] ValidatedPool::submit_one invalid: {:?}", hash, err); @@ -280,7 +361,7 @@ impl<B: ChainApi> ValidatedPool<B> { // run notifications let mut listener = self.listener.write(); for h in &removed { - listener.dropped(h, None, true); + listener.limits_enforced(h); } removed @@ -293,7 +374,7 @@ impl<B: ChainApi> ValidatedPool<B> { pub fn submit_and_watch( &self, tx: ValidatedTransactionFor<B>, - ) -> Result<Watcher<ExtrinsicHash<B>, ExtrinsicHash<B>>, B::Error> { + ) -> Result<ValidatedPoolSubmitOutcome<B>, B::Error> { match tx { ValidatedTransaction::Valid(tx) => { let hash = self.api.hash_and_length(&tx.data).0; @@ -301,7 +382,7 @@ impl<B: ChainApi> ValidatedPool<B> { self.submit(std::iter::once(ValidatedTransaction::Valid(tx))) .pop() .expect("One extrinsic passed; one result returned; qed") - .map(|_| watcher) + .map(|outcome| outcome.with_watcher(watcher)) }, ValidatedTransaction::Invalid(hash, err) => { self.rotator.ban(&Instant::now(), std::iter::once(hash)); @@ -453,7 +534,7 @@ impl<B: ChainApi> ValidatedPool<B> { match final_status { Status::Future => listener.future(&hash), Status::Ready => listener.ready(&hash, None), - Status::Dropped => listener.dropped(&hash, None, false), + Status::Dropped => listener.dropped(&hash), Status::Failed => listener.invalid(&hash), } } @@ -492,7 +573,7 @@ impl<B: ChainApi> ValidatedPool<B> { fire_events(&mut *listener, promoted); } for f in &status.failed { - listener.dropped(f, None, false); + listener.dropped(f); } } @@ -625,7 +706,7 @@ impl<B: ChainApi> ValidatedPool<B> { let invalid = self.pool.write().remove_subtree(hashes); log::trace!(target: LOG_TARGET, "Removed invalid transactions: {:?}", invalid.len()); - log_xt_trace!(target: LOG_TARGET, invalid.iter().map(|t| t.hash), "{:?} Removed invalid transaction"); + log_xt_trace!(target: LOG_TARGET, invalid.iter().map(|t| t.hash), "Removed invalid transaction"); let mut listener = self.listener.write(); for tx in &invalid { @@ -666,23 +747,77 @@ impl<B: ChainApi> ValidatedPool<B> { self.listener.write().retracted(block_hash) } + /// Refer to [`Listener::create_dropped_by_limits_stream`] for details. pub fn create_dropped_by_limits_stream( &self, - ) -> super::listener::DroppedByLimitsStream<ExtrinsicHash<B>, BlockHash<B>> { + ) -> super::listener::AggregatedStream<ExtrinsicHash<B>, BlockHash<B>> { self.listener.write().create_dropped_by_limits_stream() } + + /// Refer to [`Listener::create_aggregated_stream`] + pub fn create_aggregated_stream( + &self, + ) -> super::listener::AggregatedStream<ExtrinsicHash<B>, BlockHash<B>> { + self.listener.write().create_aggregated_stream() + } + + /// Resends ready and future events for all the ready and future transactions that are already + /// in the pool. + /// + /// Intended to be called after cloning the instance of `ValidatedPool`. + pub fn retrigger_notifications(&self) { + let pool = self.pool.read(); + let mut listener = self.listener.write(); + pool.ready().for_each(|r| { + listener.ready(&r.hash, None); + }); + pool.futures().for_each(|f| { + listener.future(&f.hash); + }); + } + + /// Removes a transaction subtree from the pool, starting from the given transaction hash. + /// + /// This function traverses the dependency graph of transactions and removes the specified + /// transaction along with all its descendant transactions from the pool. + /// + /// A `listener_action` callback function is invoked for every transaction that is removed, + /// providing a reference to the pool's listener and the hash of the removed transaction. This + /// allows to trigger the required events. + /// + /// Returns a vector containing the hashes of all removed transactions, including the root + /// transaction specified by `tx_hash`. + pub fn remove_subtree<F>( + &self, + tx_hash: ExtrinsicHash<B>, + listener_action: F, + ) -> Vec<ExtrinsicHash<B>> + where + F: Fn(&mut Listener<B>, ExtrinsicHash<B>), + { + self.pool + .write() + .remove_subtree(&[tx_hash]) + .into_iter() + .map(|tx| { + let removed_tx_hash = tx.hash; + let mut listener = self.listener.write(); + listener_action(&mut *listener, removed_tx_hash); + removed_tx_hash + }) + .collect::<Vec<_>>() + } } -fn fire_events<H, B, Ex>(listener: &mut Listener<H, B>, imported: &base::Imported<H, Ex>) +fn fire_events<B, Ex>(listener: &mut Listener<B>, imported: &base::Imported<ExtrinsicHash<B>, Ex>) where - H: hash::Hash + Eq + traits::Member + Serialize, B: ChainApi, { match *imported { base::Imported::Ready { ref promoted, ref failed, ref removed, ref hash } => { listener.ready(hash, None); failed.iter().for_each(|f| listener.invalid(f)); - removed.iter().for_each(|r| listener.dropped(&r.hash, Some(hash), false)); + removed.iter().for_each(|r| listener.usurped(&r.hash, hash)); promoted.iter().for_each(|p| listener.ready(p, None)); }, base::Imported::Future { ref hash } => listener.future(hash), diff --git a/substrate/client/transaction-pool/src/graph/watcher.rs b/substrate/client/transaction-pool/src/graph/watcher.rs index fb7cf99d4dc6caecf53eccff06efe1d59c32faee..2fd31e772fd8976fee377abd9f7f1203569892f8 100644 --- a/substrate/client/transaction-pool/src/graph/watcher.rs +++ b/substrate/client/transaction-pool/src/graph/watcher.rs @@ -113,6 +113,12 @@ impl<H: Clone, BH: Clone> Sender<H, BH> { } /// Transaction has been dropped from the pool because of the limit. + pub fn limit_enforced(&mut self) { + self.send(TransactionStatus::Dropped); + self.is_finalized = true; + } + + /// Transaction has been dropped from the pool. pub fn dropped(&mut self) { self.send(TransactionStatus::Dropped); self.is_finalized = true; diff --git a/substrate/client/transaction-pool/src/lib.rs b/substrate/client/transaction-pool/src/lib.rs index 3d3d596c291fa2d84bb417108e5d708c83ca0884..366d91a973d27581be914d74b60c6dd03cde15d8 100644 --- a/substrate/client/transaction-pool/src/lib.rs +++ b/substrate/client/transaction-pool/src/lib.rs @@ -36,7 +36,10 @@ pub use api::FullChainApi; pub use builder::{Builder, TransactionPoolHandle, TransactionPoolOptions, TransactionPoolType}; pub use common::notification_future; pub use fork_aware_txpool::{ForkAwareTxPool, ForkAwareTxPoolTask}; -pub use graph::{base_pool::Limit as PoolLimit, ChainApi, Options, Pool}; +pub use graph::{ + base_pool::{Limit as PoolLimit, TimedTransactionSource}, + ChainApi, Options, Pool, +}; use single_state_txpool::prune_known_txs_for_block; pub use single_state_txpool::{BasicPool, RevalidationType}; pub use transaction_pool_wrapper::TransactionPoolWrapper; diff --git a/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs b/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs index 5ef726c9f7d3b6ae3cab17be5ab4253fd7b4f3a9..2a691ae35eaf78549ca145d41d933a37edd64d1d 100644 --- a/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs +++ b/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs @@ -88,7 +88,7 @@ async fn batch_revalidate<Api: ChainApi>( let validation_results = futures::future::join_all(batch.into_iter().filter_map(|ext_hash| { pool.validated_pool().ready_by_hash(&ext_hash).map(|ext| { - api.validate_transaction(at, ext.source, ext.data.clone()) + api.validate_transaction(at, ext.source.clone().into(), ext.data.clone()) .map(move |validation_result| (validation_result, ext_hash, ext)) }) })) @@ -121,7 +121,7 @@ async fn batch_revalidate<Api: ChainApi>( ValidatedTransaction::valid_at( block_number.saturated_into::<u64>(), ext_hash, - ext.source, + ext.source.clone(), ext.data.clone(), api.hash_and_length(&ext.data).1, validity, @@ -375,16 +375,20 @@ mod tests { use crate::{ common::tests::{uxt, TestApi}, graph::Pool, + TimedTransactionSource, }; use futures::executor::block_on; - use sc_transaction_pool_api::TransactionSource; use substrate_test_runtime::{AccountId, Transfer, H256}; - use substrate_test_runtime_client::AccountKeyring::{Alice, Bob}; + use substrate_test_runtime_client::Sr25519Keyring::{Alice, Bob}; #[test] fn revalidation_queue_works() { let api = Arc::new(TestApi::default()); - let pool = Arc::new(Pool::new(Default::default(), true.into(), api.clone())); + let pool = Arc::new(Pool::new_with_staticly_sized_rotator( + Default::default(), + true.into(), + api.clone(), + )); let queue = Arc::new(RevalidationQueue::new(api.clone(), pool.clone())); let uxt = uxt(Transfer { @@ -398,10 +402,11 @@ mod tests { let uxt_hash = block_on(pool.submit_one( &han_of_block0, - TransactionSource::External, + TimedTransactionSource::new_external(false), uxt.clone().into(), )) - .expect("Should be valid"); + .expect("Should be valid") + .hash(); block_on(queue.revalidate_later(han_of_block0.hash, vec![uxt_hash])); @@ -414,7 +419,11 @@ mod tests { #[test] fn revalidation_queue_skips_revalidation_for_unknown_block_hash() { let api = Arc::new(TestApi::default()); - let pool = Arc::new(Pool::new(Default::default(), true.into(), api.clone())); + let pool = Arc::new(Pool::new_with_staticly_sized_rotator( + Default::default(), + true.into(), + api.clone(), + )); let queue = Arc::new(RevalidationQueue::new(api.clone(), pool.clone())); let uxt0 = uxt(Transfer { @@ -433,14 +442,15 @@ mod tests { let han_of_block0 = api.expect_hash_and_number(0); let unknown_block = H256::repeat_byte(0x13); - let uxt_hashes = block_on(pool.submit_at( - &han_of_block0, - TransactionSource::External, - vec![uxt0.into(), uxt1.into()], - )) - .into_iter() - .map(|r| r.expect("Should be valid")) - .collect::<Vec<_>>(); + let source = TimedTransactionSource::new_external(false); + let uxt_hashes = + block_on(pool.submit_at( + &han_of_block0, + vec![(source.clone(), uxt0.into()), (source, uxt1.into())], + )) + .into_iter() + .map(|r| r.expect("Should be valid").hash()) + .collect::<Vec<_>>(); assert_eq!(api.validation_requests().len(), 2); assert_eq!(pool.validated_pool().status().ready, 2); diff --git a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs index b29630b563bb973fd973cc791cdcfa9c4407567c..3598f9dbc2af15f3b56cb8ccd8fce91777716abc 100644 --- a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs +++ b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs @@ -29,7 +29,7 @@ use crate::{ error, log_xt::log_xt_trace, }, - graph::{self, ExtrinsicHash, IsValidator}, + graph::{self, base_pool::TimedTransactionSource, ExtrinsicHash, IsValidator}, ReadyIteratorFor, LOG_TARGET, }; use async_trait::async_trait; @@ -141,7 +141,11 @@ where finalized_hash: Block::Hash, options: graph::Options, ) -> (Self, Pin<Box<dyn Future<Output = ()> + Send>>) { - let pool = Arc::new(graph::Pool::new(options, true.into(), pool_api.clone())); + let pool = Arc::new(graph::Pool::new_with_staticly_sized_rotator( + options, + true.into(), + pool_api.clone(), + )); let (revalidation_queue, background_task) = revalidation::RevalidationQueue::new_background( pool_api.clone(), pool.clone(), @@ -177,7 +181,11 @@ where best_block_hash: Block::Hash, finalized_hash: Block::Hash, ) -> Self { - let pool = Arc::new(graph::Pool::new(options, is_validator, pool_api.clone())); + let pool = Arc::new(graph::Pool::new_with_staticly_sized_rotator( + options, + is_validator, + pool_api.clone(), + )); let (revalidation_queue, background_task) = match revalidation_type { RevalidationType::Light => (revalidation::RevalidationQueue::new(pool_api.clone(), pool.clone()), None), @@ -254,14 +262,24 @@ where xts: Vec<TransactionFor<Self>>, ) -> Result<Vec<Result<TxHash<Self>, Self::Error>>, Self::Error> { let pool = self.pool.clone(); - let xts = xts.into_iter().map(Arc::from).collect::<Vec<_>>(); + let xts = xts + .into_iter() + .map(|xt| { + (TimedTransactionSource::from_transaction_source(source, false), Arc::from(xt)) + }) + .collect::<Vec<_>>(); self.metrics .report(|metrics| metrics.submitted_transactions.inc_by(xts.len() as u64)); let number = self.api.resolve_block_number(at); let at = HashAndNumber { hash: at, number: number? }; - Ok(pool.submit_at(&at, source, xts).await) + Ok(pool + .submit_at(&at, xts) + .await + .into_iter() + .map(|result| result.map(|outcome| outcome.hash())) + .collect()) } async fn submit_one( @@ -277,7 +295,9 @@ where let number = self.api.resolve_block_number(at); let at = HashAndNumber { hash: at, number: number? }; - pool.submit_one(&at, source, xt).await + pool.submit_one(&at, TimedTransactionSource::from_transaction_source(source, false), xt) + .await + .map(|outcome| outcome.hash()) } async fn submit_and_watch( @@ -294,9 +314,13 @@ where let number = self.api.resolve_block_number(at); let at = HashAndNumber { hash: at, number: number? }; - let watcher = pool.submit_and_watch(&at, source, xt).await?; - - Ok(watcher.into_stream().boxed()) + pool.submit_and_watch( + &at, + TimedTransactionSource::from_transaction_source(source, false), + xt, + ) + .await + .map(|mut outcome| outcome.expect_watcher().into_stream().boxed()) } fn remove_invalid(&self, hashes: &[TxHash<Self>]) -> Vec<Arc<Self::InPoolTransaction>> { @@ -458,13 +482,17 @@ where let validated = ValidatedTransaction::valid_at( block_number.saturated_into::<u64>(), hash, - TransactionSource::Local, + TimedTransactionSource::new_local(false), Arc::from(xt), bytes, validity, ); - self.pool.validated_pool().submit(vec![validated]).remove(0) + self.pool + .validated_pool() + .submit(vec![validated]) + .remove(0) + .map(|outcome| outcome.hash()) } } @@ -662,8 +690,8 @@ where resubmit_transactions.extend( //todo: arctx - we need to get ref from somewhere - block_transactions.into_iter().map(Arc::from).filter(|tx| { - let tx_hash = pool.hash_of(tx); + block_transactions.into_iter().map(Arc::from).filter_map(|tx| { + let tx_hash = pool.hash_of(&tx); let contains = pruned_log.contains(&tx_hash); // need to count all transactions, not just filtered, here @@ -676,8 +704,15 @@ where tx_hash, hash, ); + Some(( + // These transactions are coming from retracted blocks, we should + // simply consider them external. + TimedTransactionSource::new_external(false), + tx, + )) + } else { + None } - !contains }), ); @@ -686,14 +721,7 @@ where }); } - pool.resubmit_at( - &hash_and_number, - // These transactions are coming from retracted blocks, we should - // simply consider them external. - TransactionSource::External, - resubmit_transactions, - ) - .await; + pool.resubmit_at(&hash_and_number, resubmit_transactions).await; } let extra_pool = pool.clone(); diff --git a/substrate/client/transaction-pool/tests/fatp.rs b/substrate/client/transaction-pool/tests/fatp.rs index 9f343a9bd0293ee35bbafeba246ef58c6c32fb9b..dd82c52a6047b865c516ddf99197bec638cbfe48 100644 --- a/substrate/client/transaction-pool/tests/fatp.rs +++ b/substrate/client/transaction-pool/tests/fatp.rs @@ -30,7 +30,7 @@ use sc_transaction_pool_api::{ }; use sp_runtime::transaction_validity::InvalidTransaction; use std::{sync::Arc, time::Duration}; -use substrate_test_runtime_client::AccountKeyring::*; +use substrate_test_runtime_client::Sr25519Keyring::*; use substrate_test_runtime_transaction_pool::uxt; pub mod fatp_common; @@ -2199,7 +2199,7 @@ fn import_sink_works3() { pool.submit_one(genesis, SOURCE, xt1.clone()), ]; - let x = block_on(futures::future::join_all(submissions)); + block_on(futures::future::join_all(submissions)); let header01a = api.push_block(1, vec![], true); let header01b = api.push_block(1, vec![], true); @@ -2213,8 +2213,6 @@ fn import_sink_works3() { assert_pool_status!(header01a.hash(), &pool, 1, 1); assert_pool_status!(header01b.hash(), &pool, 1, 1); - log::debug!("xxx {x:#?}"); - let import_events = futures::executor::block_on_stream(import_stream).take(1).collect::<Vec<_>>(); @@ -2267,19 +2265,13 @@ fn fatp_avoid_stuck_transaction() { assert_pool_status!(header06.hash(), &pool, 0, 0); - // Import enough blocks to make xt4i revalidated - let mut prev_header = header03; - // wait 10 blocks for revalidation - for n in 7..=11 { - let header = api.push_block(n, vec![], true); - let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); - block_on(pool.maintain(event)); - prev_header = header; - } + let header07 = api.push_block(7, vec![], true); + let event = finalized_block_event(&pool, header03.hash(), header07.hash()); + block_on(pool.maintain(event)); let xt4i_events = futures::executor::block_on_stream(xt4i_watcher).collect::<Vec<_>>(); log::debug!("xt4i_events: {:#?}", xt4i_events); - assert_eq!(xt4i_events, vec![TransactionStatus::Future, TransactionStatus::Invalid]); + assert_eq!(xt4i_events, vec![TransactionStatus::Future, TransactionStatus::Dropped]); assert_eq!(pool.mempool_len(), (0, 0)); } diff --git a/substrate/client/transaction-pool/tests/fatp_common/mod.rs b/substrate/client/transaction-pool/tests/fatp_common/mod.rs index 15f2b7f79c14764c655b3d58d9e381fcbeb3eaea..20178fdc7c4e36837f414c5762c1796e3c09a9cd 100644 --- a/substrate/client/transaction-pool/tests/fatp_common/mod.rs +++ b/substrate/client/transaction-pool/tests/fatp_common/mod.rs @@ -24,7 +24,7 @@ use sp_runtime::transaction_validity::TransactionSource; use std::sync::Arc; use substrate_test_runtime_client::{ runtime::{Block, Hash, Header}, - AccountKeyring::*, + Sr25519Keyring::*, }; use substrate_test_runtime_transaction_pool::{uxt, TestApi}; pub const LOG_TARGET: &str = "txpool"; @@ -192,12 +192,35 @@ macro_rules! assert_ready_iterator { let output: Vec<_> = ready_iterator.collect(); log::debug!(target:LOG_TARGET, "expected: {:#?}", expected); log::debug!(target:LOG_TARGET, "output: {:#?}", output); + let output = output.into_iter().map(|t|t.hash).collect::<Vec<_>>(); assert_eq!(expected.len(), output.len()); - assert!( - output.iter().zip(expected.iter()).all(|(o,e)| { - o.hash == *e - }) - ); + assert_eq!(output,expected); + }}; +} + +#[macro_export] +macro_rules! assert_future_iterator { + ($hash:expr, $pool:expr, [$( $xt:expr ),*]) => {{ + let futures = $pool.futures_at($hash).unwrap(); + let expected = vec![ $($pool.api().hash_and_length(&$xt).0),*]; + log::debug!(target:LOG_TARGET, "expected: {:#?}", expected); + log::debug!(target:LOG_TARGET, "output: {:#?}", futures); + assert_eq!(expected.len(), futures.len()); + let hsf = futures.iter().map(|a| a.hash).collect::<std::collections::HashSet<_>>(); + let hse = expected.into_iter().collect::<std::collections::HashSet<_>>(); + assert_eq!(hse,hsf); + }}; +} + +#[macro_export] +macro_rules! assert_watcher_stream { + ($stream:ident, [$( $event:expr ),*]) => {{ + let expected = vec![ $($event),*]; + log::debug!(target:LOG_TARGET, "expected: {:#?} {}, block now:", expected, expected.len()); + let output = futures::executor::block_on_stream($stream).take(expected.len()).collect::<Vec<_>>(); + log::debug!(target:LOG_TARGET, "output: {:#?}", output); + assert_eq!(expected.len(), output.len()); + assert_eq!(output, expected); }}; } diff --git a/substrate/client/transaction-pool/tests/fatp_limits.rs b/substrate/client/transaction-pool/tests/fatp_limits.rs index 03792fd89dfacbf30e00a9ecaaf8a4a8cabbcfb8..50e75e1e28e776c8e14be4cc9ca627b986b46aa6 100644 --- a/substrate/client/transaction-pool/tests/fatp_limits.rs +++ b/substrate/client/transaction-pool/tests/fatp_limits.rs @@ -29,7 +29,7 @@ use sc_transaction_pool_api::{ error::Error as TxPoolError, MaintainedTransactionPool, TransactionPool, TransactionStatus, }; use std::thread::sleep; -use substrate_test_runtime_client::AccountKeyring::*; +use substrate_test_runtime_client::Sr25519Keyring::*; use substrate_test_runtime_transaction_pool::uxt; #[test] @@ -377,12 +377,11 @@ fn fatp_limits_watcher_view_can_drop_transcation() { assert_eq!(xt0_status, vec![TransactionStatus::Ready, TransactionStatus::Dropped,]); assert_ready_iterator!(header01.hash(), pool, [xt1, xt2]); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); let header02 = api.push_block_with_parent(header01.hash(), vec![], true); block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header02.hash()))); - let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); - let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(2).collect::<Vec<_>>(); assert_eq!(xt1_status, vec![TransactionStatus::Ready, TransactionStatus::Dropped]); @@ -641,3 +640,192 @@ fn fatp_limits_future_size_works() { assert_pool_status!(header01.hash(), &pool, 0, 3); assert_eq!(pool.mempool_len().0, 3); } + +#[test] +fn fatp_limits_watcher_ready_transactions_are_not_droped_when_view_is_dropped() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(6).with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + api.set_nonce(api.genesis_hash(), Eve.into(), 600); + api.set_nonce(api.genesis_hash(), Ferdie.into(), 700); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + + let xt3 = uxt(Dave, 500); + let xt4 = uxt(Eve, 600); + let xt5 = uxt(Ferdie, 700); + + let _xt0_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let _xt1_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 2); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + let _xt2_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let _xt3_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header02.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 4); + + let header03 = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02.hash()), header03.hash()))); + + let _xt4_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap(); + let _xt5_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt5.clone())).unwrap(); + + assert_pool_status!(header03.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 6); + + let header04 = + api.push_block_with_parent(header03.hash(), vec![xt4.clone(), xt5.clone()], true); + api.set_nonce(header04.hash(), Alice.into(), 201); + api.set_nonce(header04.hash(), Bob.into(), 301); + api.set_nonce(header04.hash(), Charlie.into(), 401); + api.set_nonce(header04.hash(), Dave.into(), 501); + api.set_nonce(header04.hash(), Eve.into(), 601); + api.set_nonce(header04.hash(), Ferdie.into(), 701); + block_on(pool.maintain(new_best_block_event(&pool, Some(header03.hash()), header04.hash()))); + + assert_ready_iterator!(header01.hash(), pool, [xt0, xt1]); + assert_ready_iterator!(header02.hash(), pool, [xt2, xt3]); + assert_ready_iterator!(header03.hash(), pool, [xt4, xt5]); + assert_ready_iterator!(header04.hash(), pool, []); + + block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header01.hash()))); + assert!(!pool.status_all().contains_key(&header01.hash())); + + block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header02.hash()))); + assert!(!pool.status_all().contains_key(&header02.hash())); + + //view 01 was dropped + assert!(pool.ready_at(header01.hash()).now_or_never().is_none()); + assert_eq!(pool.mempool_len().1, 6); + + block_on(pool.maintain(finalized_block_event(&pool, header02.hash(), header03.hash()))); + + //no revalidation has happened yet, all txs are kept + assert_eq!(pool.mempool_len().1, 6); + + //view 03 is still there + assert!(!pool.status_all().contains_key(&header03.hash())); + + //view 02 was dropped + assert!(pool.ready_at(header02.hash()).now_or_never().is_none()); + + let mut prev_header = header03; + for n in 5..=11 { + let header = api.push_block(n, vec![], true); + let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); + block_on(pool.maintain(event)); + prev_header = header; + } + + //now revalidation has happened, all txs are dropped + assert_eq!(pool.mempool_len().1, 0); +} + +#[test] +fn fatp_limits_watcher_future_transactions_are_droped_when_view_is_dropped() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(6).with_future_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + api.set_nonce(api.genesis_hash(), Eve.into(), 600); + api.set_nonce(api.genesis_hash(), Ferdie.into(), 700); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 201); + let xt1 = uxt(Bob, 301); + let xt2 = uxt(Charlie, 401); + + let xt3 = uxt(Dave, 501); + let xt4 = uxt(Eve, 601); + let xt5 = uxt(Ferdie, 701); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 0, 2); + assert_eq!(pool.mempool_len().1, 2); + assert_future_iterator!(header01.hash(), pool, [xt0, xt1]); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header02.hash(), &pool, 0, 2); + assert_eq!(pool.mempool_len().1, 4); + assert_future_iterator!(header02.hash(), pool, [xt2, xt3]); + + let header03 = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02.hash()), header03.hash()))); + + let xt4_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap(); + let xt5_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt5.clone())).unwrap(); + + assert_pool_status!(header03.hash(), &pool, 0, 2); + assert_eq!(pool.mempool_len().1, 6); + assert_future_iterator!(header03.hash(), pool, [xt4, xt5]); + + let header04 = api.push_block_with_parent(header03.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header03.hash()), header04.hash()))); + + assert_pool_status!(header04.hash(), &pool, 0, 2); + assert_eq!(pool.futures().len(), 2); + assert_future_iterator!(header04.hash(), pool, [xt4, xt5]); + + block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header04.hash()))); + assert_eq!(pool.active_views_count(), 1); + assert_eq!(pool.inactive_views_count(), 0); + //todo: can we do better? We don't have API to check if event was processed internally. + let mut counter = 0; + while pool.mempool_len().1 != 2 { + sleep(std::time::Duration::from_millis(1)); + counter = counter + 1; + if counter > 20 { + assert!(false, "timeout {}", pool.mempool_len().1); + } + } + assert_eq!(pool.mempool_len().1, 2); + assert_pool_status!(header04.hash(), &pool, 0, 2); + assert_eq!(pool.futures().len(), 2); + + let to_be_checked = vec![xt0_watcher, xt1_watcher, xt2_watcher, xt3_watcher]; + for x in to_be_checked { + let x_status = futures::executor::block_on_stream(x).take(2).collect::<Vec<_>>(); + assert_eq!(x_status, vec![TransactionStatus::Future, TransactionStatus::Dropped]); + } + + let to_be_checked = vec![xt4_watcher, xt5_watcher]; + for x in to_be_checked { + let x_status = futures::executor::block_on_stream(x).take(1).collect::<Vec<_>>(); + assert_eq!(x_status, vec![TransactionStatus::Future]); + } +} diff --git a/substrate/client/transaction-pool/tests/fatp_prios.rs b/substrate/client/transaction-pool/tests/fatp_prios.rs new file mode 100644 index 0000000000000000000000000000000000000000..af5e7e8c5a6a83269ff7532d750d8f036e77397b --- /dev/null +++ b/substrate/client/transaction-pool/tests/fatp_prios.rs @@ -0,0 +1,560 @@ +// 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 <https://www.gnu.org/licenses/>. + +//! Tests of priorities for fork-aware transaction pool. + +pub mod fatp_common; + +use fatp_common::{invalid_hash, new_best_block_event, TestPoolBuilder, LOG_TARGET, SOURCE}; +use futures::{executor::block_on, FutureExt}; +use sc_transaction_pool::ChainApi; +use sc_transaction_pool_api::{ + error::Error as TxPoolError, LocalTransactionPool, MaintainedTransactionPool, TransactionPool, + TransactionStatus, +}; +use substrate_test_runtime_client::Sr25519Keyring::*; +use substrate_test_runtime_transaction_pool::uxt; +#[test] +fn fatp_prio_ready_higher_evicts_lower() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build(); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 200); + + api.set_priority(&xt0, 2); + api.set_priority(&xt1, 3); + + let result0 = block_on(pool.submit_one(header01.hash(), SOURCE, xt0.clone())); + let result1 = block_on(pool.submit_one(header01.hash(), SOURCE, xt1.clone())); + + log::info!("r0 => {:?}", result0); + log::info!("r1 => {:?}", result1); + log::info!("len: {:?}", pool.mempool_len()); + log::info!("len: {:?}", pool.status_all()[&header01.hash()]); + assert_ready_iterator!(header01.hash(), pool, [xt1]); + assert_pool_status!(header01.hash(), &pool, 1, 0); +} + +#[test] +fn fatp_prio_watcher_ready_higher_evicts_lower() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build(); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 200); + + api.set_priority(&xt0, 2); + api.set_priority(&xt1, 3); + + let xt0_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt1.clone())).unwrap(); + + let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::<Vec<_>>(); + assert_eq!( + xt0_status, + vec![TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt1).0)] + ); + let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(1).collect::<Vec<_>>(); + assert_eq!(xt1_status, vec![TransactionStatus::Ready]); + + log::info!("len: {:?}", pool.mempool_len()); + log::info!("len: {:?}", pool.status_all()[&header01.hash()]); + assert_ready_iterator!(header01.hash(), pool, [xt1]); + assert_pool_status!(header01.hash(), &pool, 1, 0); +} + +#[test] +fn fatp_prio_watcher_future_higher_evicts_lower() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(3).build(); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 201); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 200); + + api.set_priority(&xt0, 2); + api.set_priority(&xt1, 3); + + let xt0_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt2.clone())).unwrap(); + + let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::<Vec<_>>(); + + assert_eq!( + xt0_status, + vec![TransactionStatus::Future, TransactionStatus::Usurped(api.hash_and_length(&xt2).0)] + ); + let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(2).collect::<Vec<_>>(); + assert_eq!(xt1_status, vec![TransactionStatus::Future, TransactionStatus::Ready]); + let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(1).collect::<Vec<_>>(); + assert_eq!(xt2_status, vec![TransactionStatus::Ready]); + + assert_eq!(pool.mempool_len().1, 2); + assert_ready_iterator!(header01.hash(), pool, [xt2, xt1]); + assert_pool_status!(header01.hash(), &pool, 2, 0); +} + +#[test] +fn fatp_prio_watcher_ready_lower_prio_gets_dropped_from_all_views() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build(); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 200); + + api.set_priority(&xt0, 2); + api.set_priority(&xt1, 3); + + let xt0_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt0.clone())).unwrap(); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + let header03a = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header03a.hash()))); + + let header03b = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header03a.hash()), header03b.hash()))); + + assert_pool_status!(header03a.hash(), &pool, 1, 0); + assert_ready_iterator!(header03a.hash(), pool, [xt0]); + assert_pool_status!(header03b.hash(), &pool, 1, 0); + assert_ready_iterator!(header03b.hash(), pool, [xt0]); + assert_ready_iterator!(header01.hash(), pool, [xt0]); + assert_ready_iterator!(header02.hash(), pool, [xt0]); + + let xt1_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt1.clone())).unwrap(); + + let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(1).collect::<Vec<_>>(); + assert_eq!(xt1_status, vec![TransactionStatus::Ready]); + let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::<Vec<_>>(); + assert_eq!( + xt0_status, + vec![TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt1).0)] + ); + assert_ready_iterator!(header03a.hash(), pool, [xt1]); + assert_ready_iterator!(header03b.hash(), pool, [xt1]); + assert_ready_iterator!(header01.hash(), pool, [xt1]); + assert_ready_iterator!(header02.hash(), pool, [xt1]); +} + +#[test] +fn fatp_prio_watcher_future_lower_prio_gets_dropped_from_all_views() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build(); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let xt0 = uxt(Alice, 201); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 200); + + api.set_priority(&xt0, 2); + api.set_priority(&xt1, 3); + + let xt0_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt0.clone())).unwrap(); + + let xt1_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt1.clone())).unwrap(); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + let header03a = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header03a.hash()))); + + let header03b = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header03a.hash()), header03b.hash()))); + + assert_pool_status!(header03a.hash(), &pool, 0, 2); + assert_future_iterator!(header03a.hash(), pool, [xt0, xt1]); + assert_pool_status!(header03b.hash(), &pool, 0, 2); + assert_future_iterator!(header03b.hash(), pool, [xt0, xt1]); + assert_future_iterator!(header01.hash(), pool, [xt0, xt1]); + assert_future_iterator!(header02.hash(), pool, [xt0, xt1]); + + let xt2_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt2.clone())).unwrap(); + + let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(1).collect::<Vec<_>>(); + assert_eq!(xt2_status, vec![TransactionStatus::Ready]); + let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(1).collect::<Vec<_>>(); + assert_eq!(xt1_status, vec![TransactionStatus::Future]); + let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::<Vec<_>>(); + assert_eq!( + xt0_status, + vec![TransactionStatus::Future, TransactionStatus::Usurped(api.hash_and_length(&xt2).0)] + ); + assert_future_iterator!(header03a.hash(), pool, []); + assert_future_iterator!(header03b.hash(), pool, []); + assert_future_iterator!(header01.hash(), pool, []); + assert_future_iterator!(header02.hash(), pool, []); + + assert_ready_iterator!(header03a.hash(), pool, [xt2, xt1]); + assert_ready_iterator!(header03b.hash(), pool, [xt2, xt1]); + assert_ready_iterator!(header01.hash(), pool, [xt2, xt1]); + assert_ready_iterator!(header02.hash(), pool, [xt2, xt1]); +} + +#[test] +fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + api.set_nonce(api.genesis_hash(), Eve.into(), 600); + api.set_nonce(api.genesis_hash(), Ferdie.into(), 700); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + + let xt3 = uxt(Dave, 500); + + let xt4 = uxt(Eve, 600); + let xt5 = uxt(Ferdie, 700); + + api.set_priority(&xt0, 1); + api.set_priority(&xt1, 2); + api.set_priority(&xt2, 3); + api.set_priority(&xt3, 4); + + api.set_priority(&xt4, 5); + api.set_priority(&xt5, 6); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 2); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + let _xt2_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let _xt3_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header02.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 4); + + let header03 = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02.hash()), header03.hash()))); + + let _xt4_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap(); + let _xt5_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt5.clone())).unwrap(); + + assert_pool_status!(header03.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 4); + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + + assert_ready_iterator!(header01.hash(), pool, []); + assert_ready_iterator!(header02.hash(), pool, [xt3, xt2]); + assert_ready_iterator!(header03.hash(), pool, [xt5, xt4]); +} + +#[test] +fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted_with_subtree() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(4).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 202); + let xt3 = uxt(Bob, 300); + let xt4 = uxt(Charlie, 400); + + api.set_priority(&xt0, 1); + api.set_priority(&xt1, 3); + api.set_priority(&xt2, 3); + api.set_priority(&xt3, 2); + api.set_priority(&xt4, 2); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_ready_iterator!(header01.hash(), pool, [xt3, xt0, xt1, xt2]); + assert_pool_status!(header01.hash(), &pool, 4, 0); + assert_eq!(pool.mempool_len().1, 4); + + let xt4_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap(); + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_ready_iterator!(header01.hash(), pool, [xt3, xt4]); + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt4_watcher, [TransactionStatus::Ready]); +} + +#[test] +fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted_with_subtree2() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(4).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 202); + let xt3 = uxt(Bob, 300); + let xt4 = uxt(Charlie, 400); + + api.set_priority(&xt0, 1); + api.set_priority(&xt1, 3); + api.set_priority(&xt2, 3); + api.set_priority(&xt3, 2); + api.set_priority(&xt4, 2); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_ready_iterator!(header01.hash(), pool, [xt3, xt0, xt1, xt2]); + assert_pool_status!(header01.hash(), &pool, 4, 0); + assert_eq!(pool.mempool_len().1, 4); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + let xt4_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap(); + assert_ready_iterator!(header01.hash(), pool, [xt3]); + assert_pool_status!(header02.hash(), &pool, 2, 0); + assert_ready_iterator!(header02.hash(), pool, [xt3, xt4]); + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt4_watcher, [TransactionStatus::Ready]); +} + +#[test] +fn fatp_prios_watcher_full_mempool_lower_prio_gets_rejected() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(2).with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + let xt3 = uxt(Dave, 500); + + api.set_priority(&xt0, 2); + api.set_priority(&xt1, 2); + api.set_priority(&xt2, 2); + api.set_priority(&xt3, 1); + + let _xt0_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let _xt1_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 2); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + assert_pool_status!(header02.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 2); + + assert_ready_iterator!(header01.hash(), pool, [xt0, xt1]); + assert_ready_iterator!(header02.hash(), pool, [xt0, xt1]); + + let result2 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).map(|_| ()); + assert!(matches!(result2.as_ref().unwrap_err().0, TxPoolError::ImmediatelyDropped)); + let result3 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).map(|_| ()); + assert!(matches!(result3.as_ref().unwrap_err().0, TxPoolError::ImmediatelyDropped)); +} + +#[test] +fn fatp_prios_watcher_full_mempool_does_not_keep_dropped_transaction() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + let xt3 = uxt(Dave, 500); + + api.set_priority(&xt0, 2); + api.set_priority(&xt1, 2); + api.set_priority(&xt2, 2); + api.set_priority(&xt3, 2); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]); + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); +} + +#[test] +fn fatp_prios_submit_local_full_mempool_higher_prio_is_accepted() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + api.set_nonce(api.genesis_hash(), Eve.into(), 600); + api.set_nonce(api.genesis_hash(), Ferdie.into(), 700); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + + let xt3 = uxt(Dave, 500); + + let xt4 = uxt(Eve, 600); + let xt5 = uxt(Ferdie, 700); + + api.set_priority(&xt0, 1); + api.set_priority(&xt1, 2); + api.set_priority(&xt2, 3); + api.set_priority(&xt3, 4); + + api.set_priority(&xt4, 5); + api.set_priority(&xt5, 6); + pool.submit_local(invalid_hash(), xt0.clone()).unwrap(); + pool.submit_local(invalid_hash(), xt1.clone()).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().0, 2); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + pool.submit_local(invalid_hash(), xt2.clone()).unwrap(); + pool.submit_local(invalid_hash(), xt3.clone()).unwrap(); + + assert_pool_status!(header02.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().0, 4); + + let header03 = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02.hash()), header03.hash()))); + + pool.submit_local(invalid_hash(), xt4.clone()).unwrap(); + pool.submit_local(invalid_hash(), xt5.clone()).unwrap(); + + assert_pool_status!(header03.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().0, 4); + + assert_ready_iterator!(header01.hash(), pool, []); + assert_ready_iterator!(header02.hash(), pool, [xt3, xt2]); + assert_ready_iterator!(header03.hash(), pool, [xt5, xt4]); +} diff --git a/substrate/client/transaction-pool/tests/pool.rs b/substrate/client/transaction-pool/tests/pool.rs index ed0fd7d4e65530aca1b3e419362457bd5bc451cc..c70f4548331454f166e8112df00e42e1652e1fb6 100644 --- a/substrate/client/transaction-pool/tests/pool.rs +++ b/substrate/client/transaction-pool/tests/pool.rs @@ -40,8 +40,8 @@ use sp_runtime::{ use std::{collections::BTreeSet, pin::Pin, sync::Arc}; use substrate_test_runtime_client::{ runtime::{Block, Extrinsic, ExtrinsicBuilder, Hash, Header, Nonce, Transfer, TransferData}, - AccountKeyring::*, ClientBlockImportExt, + Sr25519Keyring::*, }; use substrate_test_runtime_transaction_pool::{uxt, TestApi}; @@ -49,7 +49,7 @@ const LOG_TARGET: &str = "txpool"; fn pool() -> (Pool<TestApi>, Arc<TestApi>) { let api = Arc::new(TestApi::with_alice_nonce(209)); - (Pool::new(Default::default(), true.into(), api.clone()), api) + (Pool::new_with_staticly_sized_rotator(Default::default(), true.into(), api.clone()), api) } fn maintained_pool() -> (BasicPool<TestApi, Block>, Arc<TestApi>, futures::executor::ThreadPool) { @@ -80,12 +80,14 @@ fn create_basic_pool(test_api: TestApi) -> BasicPool<TestApi, Block> { create_basic_pool_with_genesis(Arc::from(test_api)).0 } +const TSOURCE: TimedTransactionSource = + TimedTransactionSource { source: TransactionSource::External, timestamp: None }; const SOURCE: TransactionSource = TransactionSource::External; #[test] fn submission_should_work() { let (pool, api) = pool(); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 209).into())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 209).into())) .unwrap(); let pending: Vec<_> = pool @@ -99,9 +101,9 @@ fn submission_should_work() { #[test] fn multiple_submission_should_work() { let (pool, api) = pool(); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 209).into())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 209).into())) .unwrap(); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 210).into())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 210).into())) .unwrap(); let pending: Vec<_> = pool @@ -116,7 +118,7 @@ fn multiple_submission_should_work() { fn early_nonce_should_be_culled() { sp_tracing::try_init_simple(); let (pool, api) = pool(); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 208).into())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 208).into())) .unwrap(); log::debug!("-> {:?}", pool.validated_pool().status()); @@ -132,7 +134,7 @@ fn early_nonce_should_be_culled() { fn late_nonce_should_be_queued() { let (pool, api) = pool(); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 210).into())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 210).into())) .unwrap(); let pending: Vec<_> = pool .validated_pool() @@ -141,7 +143,7 @@ fn late_nonce_should_be_queued() { .collect(); assert_eq!(pending, Vec::<Nonce>::new()); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 209).into())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 209).into())) .unwrap(); let pending: Vec<_> = pool .validated_pool() @@ -155,9 +157,10 @@ fn late_nonce_should_be_queued() { fn prune_tags_should_work() { let (pool, api) = pool(); let hash209 = - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 209).into())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 209).into())) + .map(|o| o.hash()) .unwrap(); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 210).into())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 210).into())) .unwrap(); let pending: Vec<_> = pool @@ -182,10 +185,13 @@ fn prune_tags_should_work() { fn should_ban_invalid_transactions() { let (pool, api) = pool(); let uxt = Arc::from(uxt(Alice, 209)); - let hash = - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.clone())).unwrap(); + let hash = block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())) + .unwrap() + .hash(); pool.validated_pool().remove_invalid(&[hash]); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.clone())).unwrap_err(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())) + .map(|_| ()) + .unwrap_err(); // when let pending: Vec<_> = pool @@ -196,7 +202,9 @@ fn should_ban_invalid_transactions() { assert_eq!(pending, Vec::<Nonce>::new()); // then - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.clone())).unwrap_err(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())) + .map(|_| ()) + .unwrap_err(); } #[test] @@ -222,9 +230,9 @@ fn should_correctly_prune_transactions_providing_more_than_one_tag() { api.set_valid_modifier(Box::new(|v: &mut ValidTransaction| { v.provides.push(vec![155]); })); - let pool = Pool::new(Default::default(), true.into(), api.clone()); + let pool = Pool::new_with_staticly_sized_rotator(Default::default(), true.into(), api.clone()); let xt0 = Arc::from(uxt(Alice, 209)); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, xt0.clone())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, xt0.clone())) .expect("1. Imported"); assert_eq!(pool.validated_pool().status().ready, 1); assert_eq!(api.validation_requests().len(), 1); @@ -242,7 +250,7 @@ fn should_correctly_prune_transactions_providing_more_than_one_tag() { api.increment_nonce(Alice.into()); api.push_block(2, Vec::new(), true); let xt1 = uxt(Alice, 211); - block_on(pool.submit_one(&api.expect_hash_and_number(2), SOURCE, xt1.clone().into())) + block_on(pool.submit_one(&api.expect_hash_and_number(2), TSOURCE, xt1.clone().into())) .expect("2. Imported"); assert_eq!(api.validation_requests().len(), 3); assert_eq!(pool.validated_pool().status().ready, 1); diff --git a/substrate/docs/Upgrading-2.0-to-3.0.md b/substrate/docs/Upgrading-2.0-to-3.0.md index 1be41a34ef340e4d07b397b0964b83b66011da0e..f6fc5cf4b0798fde9646187dc70ede1dd58318b4 100644 --- a/substrate/docs/Upgrading-2.0-to-3.0.md +++ b/substrate/docs/Upgrading-2.0-to-3.0.md @@ -1003,7 +1003,7 @@ modified your chain you should probably try to apply these patches: }; use sp_timestamp; - use sp_finality_tracker; - use sp_keyring::AccountKeyring; + use sp_keyring::Sr25519Keyring; use sc_service_test::TestNetNode; use crate::service::{new_full_base, new_light_base, NewFullBase}; - use sp_runtime::traits::IdentifyAccount; @@ -1034,7 +1034,7 @@ modified your chain you should probably try to apply these patches: + let mut slot = 1u64; // For the extrinsics factory - let bob = Arc::new(AccountKeyring::Bob.pair()); + let bob = Arc::new(Sr25519Keyring::Bob.pair()); @@ -528,14 +539,13 @@ mod tests { Ok((node, (inherent_data_providers, setup_handles.unwrap()))) }, diff --git a/substrate/frame/Cargo.toml b/substrate/frame/Cargo.toml index 2d0daf82997d90e7d21ff7f603cce328800f393a..8fc0d84684305bf062dcf2566eff7b934bb8407d 100644 --- a/substrate/frame/Cargo.toml +++ b/substrate/frame/Cargo.toml @@ -26,28 +26,28 @@ scale-info = { features = [ ], workspace = true } # primitive deps, used for developing FRAME pallets. -sp-runtime = { workspace = true } -sp-io = { workspace = true } -sp-core = { workspace = true } sp-arithmetic = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } # frame deps, for developing FRAME pallets. frame-support = { workspace = true } frame-system = { workspace = true } # primitive types used for developing FRAME runtimes. -sp-version = { optional = true, workspace = true } sp-api = { optional = true, workspace = true } sp-block-builder = { optional = true, workspace = true } -sp-transaction-pool = { optional = true, workspace = true } -sp-offchain = { optional = true, workspace = true } -sp-session = { optional = true, workspace = true } sp-consensus-aura = { optional = true, workspace = true } sp-consensus-grandpa = { optional = true, workspace = true } sp-genesis-builder = { optional = true, workspace = true } sp-inherents = { optional = true, workspace = true } -sp-storage = { optional = true, workspace = true } sp-keyring = { optional = true, workspace = true } +sp-offchain = { optional = true, workspace = true } +sp-session = { optional = true, workspace = true } +sp-storage = { optional = true, workspace = true } +sp-transaction-pool = { optional = true, workspace = true } +sp-version = { optional = true, workspace = true } frame-executive = { optional = true, workspace = true } frame-system-rpc-runtime-api = { optional = true, workspace = true } diff --git a/substrate/frame/alliance/Cargo.toml b/substrate/frame/alliance/Cargo.toml index 451b86b35ddef7fa96945888e9b885444171dc9d..9d21b9e964c9a0346e98933b94391cd3546c7b1e 100644 --- a/substrate/frame/alliance/Cargo.toml +++ b/substrate/frame/alliance/Cargo.toml @@ -31,14 +31,14 @@ frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -pallet-identity = { workspace = true } pallet-collective = { optional = true, workspace = true } +pallet-identity = { workspace = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } -sp-crypto-hashing = { workspace = true } pallet-balances = { workspace = true, default-features = true } pallet-collective = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/alliance/src/mock.rs b/substrate/frame/alliance/src/mock.rs index 625cabf3457f3626bdf04702faf5f4a837b920ed..069c29a88d38c1ad1dad483d78e931df0cce90db 100644 --- a/substrate/frame/alliance/src/mock.rs +++ b/substrate/frame/alliance/src/mock.rs @@ -283,6 +283,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { (8, 1000), (9, 1000), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/asset-conversion/Cargo.toml b/substrate/frame/asset-conversion/Cargo.toml index 10a118e95639a102ad6be155a2453a9f9449f846..8987e44ee000a3a549fffdd09bc4e9942662e9c5 100644 --- a/substrate/frame/asset-conversion/Cargo.toml +++ b/substrate/frame/asset-conversion/Cargo.toml @@ -17,20 +17,20 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } +log = { workspace = true } scale-info = { features = ["derive"], workspace = true } sp-api = { workspace = true } +sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -sp-arithmetic = { workspace = true } [dev-dependencies] -pallet-balances = { workspace = true, default-features = true } pallet-assets = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } [features] diff --git a/substrate/frame/asset-conversion/ops/Cargo.toml b/substrate/frame/asset-conversion/ops/Cargo.toml index 66333f973d7f277145e07df53770ad3d714f760c..ebd31bd296de1262af83e20154f23ddb7148bda4 100644 --- a/substrate/frame/asset-conversion/ops/Cargo.toml +++ b/substrate/frame/asset-conversion/ops/Cargo.toml @@ -16,20 +16,20 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } +log = { workspace = true } pallet-asset-conversion = { workspace = true } scale-info = { features = ["derive"], workspace = true } +sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -sp-arithmetic = { workspace = true } [dev-dependencies] -pallet-balances = { workspace = true, default-features = true } pallet-assets = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } [features] diff --git a/substrate/frame/asset-conversion/ops/src/mock.rs b/substrate/frame/asset-conversion/ops/src/mock.rs index 5c05faa6aa88dba9bb5c55b58cd5a1de7054dac1..576b266b39c17e26d9f4d2837e0ce86ba6360153 100644 --- a/substrate/frame/asset-conversion/ops/src/mock.rs +++ b/substrate/frame/asset-conversion/ops/src/mock.rs @@ -135,6 +135,7 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig::<Test> { balances: vec![(1, 10000), (2, 20000), (3, 30000), (4, 40000)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/asset-conversion/src/mock.rs b/substrate/frame/asset-conversion/src/mock.rs index d8832d70488af7d38507c83a964bc1c2c07815d0..313d9f9857e49be8c9c18f3f926249839e814d82 100644 --- a/substrate/frame/asset-conversion/src/mock.rs +++ b/substrate/frame/asset-conversion/src/mock.rs @@ -162,6 +162,7 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig::<Test> { balances: vec![(1, 10000), (2, 20000), (3, 30000), (4, 40000)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/asset-rate/Cargo.toml b/substrate/frame/asset-rate/Cargo.toml index 514b6fa40c2b40b315b3964a71f932143bf62482..01a5ca21b199b40f8310336947d63e9d13550362 100644 --- a/substrate/frame/asset-rate/Cargo.toml +++ b/substrate/frame/asset-rate/Cargo.toml @@ -18,17 +18,17 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -sp-runtime = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { optional = true, workspace = true } +sp-runtime = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } sp-core = { workspace = true } +sp-io = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/asset-rewards/Cargo.toml b/substrate/frame/asset-rewards/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..a03fa17cf0dc0e1911dadab39a4dbadbb542a372 --- /dev/null +++ b/substrate/frame/asset-rewards/Cargo.toml @@ -0,0 +1,71 @@ +[package] +name = "pallet-asset-rewards" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "FRAME asset rewards pallet" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +frame-benchmarking = { workspace = true, optional = true } +frame-support = { workspace = true, features = ["experimental"] } +frame-system = { workspace = true } +scale-info = { workspace = true, features = ["derive"] } +sp-api = { workspace = true } +sp-arithmetic = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +[dev-dependencies] +pallet-assets = { workspace = true } +pallet-assets-freezer = { workspace = true } +pallet-balances = { workspace = true } +primitive-types = { workspace = true, features = ["codec", "num-traits", "scale-info"] } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "pallet-assets-freezer/std", + "pallet-assets/std", + "pallet-balances/std", + "primitive-types/std", + "scale-info/std", + "sp-api/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-assets-freezer/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-assets-freezer/try-runtime", + "pallet-assets/try-runtime", + "pallet-balances/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/asset-rewards/src/benchmarking.rs b/substrate/frame/asset-rewards/src/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..5605804dd20ece8eb27509de9bb4fe44d0037ab3 --- /dev/null +++ b/substrate/frame/asset-rewards/src/benchmarking.rs @@ -0,0 +1,355 @@ +// 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. + +//! Asset Rewards pallet benchmarking. + +use super::*; +use crate::Pallet as AssetRewards; +use frame_benchmarking::{v2::*, whitelisted_caller, BenchmarkError}; +use frame_support::{ + assert_ok, + traits::{ + fungibles::{Create, Inspect, Mutate}, + Consideration, EnsureOrigin, Footprint, + }, +}; +use frame_system::{pallet_prelude::BlockNumberFor, Pallet as System, RawOrigin}; +use sp_runtime::{traits::One, Saturating}; +use sp_std::prelude::*; + +/// Benchmark Helper +pub trait BenchmarkHelper<AssetId> { + /// Returns the staked asset id. + /// + /// If the asset does not exist, it will be created by the benchmark. + fn staked_asset() -> AssetId; + /// Returns the reward asset id. + /// + /// If the asset does not exist, it will be created by the benchmark. + fn reward_asset() -> AssetId; +} + +fn pool_expire<T: Config>() -> DispatchTime<BlockNumberFor<T>> { + DispatchTime::At(BlockNumberFor::<T>::from(100u32)) +} + +fn create_reward_pool<T: Config>() -> Result<T::RuntimeOrigin, BenchmarkError> +where + T::Assets: Create<T::AccountId> + Mutate<T::AccountId>, +{ + let caller_origin = + T::CreatePoolOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let caller = T::CreatePoolOrigin::ensure_origin(caller_origin.clone()).unwrap(); + + let footprint = Footprint::from_mel::<(PoolId, PoolInfoFor<T>)>(); + T::Consideration::ensure_successful(&caller, footprint); + + let staked_asset = T::BenchmarkHelper::staked_asset(); + let reward_asset = T::BenchmarkHelper::reward_asset(); + + let min_staked_balance = + T::Assets::minimum_balance(staked_asset.clone()).max(T::Balance::one()); + if !T::Assets::asset_exists(staked_asset.clone()) { + assert_ok!(T::Assets::create( + staked_asset.clone(), + caller.clone(), + true, + min_staked_balance + )); + } + let min_reward_balance = + T::Assets::minimum_balance(reward_asset.clone()).max(T::Balance::one()); + if !T::Assets::asset_exists(reward_asset.clone()) { + assert_ok!(T::Assets::create( + reward_asset.clone(), + caller.clone(), + true, + min_reward_balance + )); + } + + assert_ok!(AssetRewards::<T>::create_pool( + caller_origin.clone(), + Box::new(staked_asset), + Box::new(reward_asset), + // reward rate per block + min_reward_balance, + pool_expire::<T>(), + Some(caller), + )); + + Ok(caller_origin) +} + +fn mint_into<T: Config>(caller: &T::AccountId, asset: &T::AssetId) -> T::Balance +where + T::Assets: Mutate<T::AccountId>, +{ + let min_balance = T::Assets::minimum_balance(asset.clone()); + assert_ok!(T::Assets::mint_into( + asset.clone(), + &caller, + min_balance.saturating_mul(10u32.into()) + )); + min_balance +} + +fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) { + System::<T>::assert_last_event(generic_event.into()); +} + +#[benchmarks(where T::Assets: Create<T::AccountId> + Mutate<T::AccountId>)] +mod benchmarks { + use super::*; + + #[benchmark] + fn create_pool() -> Result<(), BenchmarkError> { + let caller_origin = + T::CreatePoolOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let caller = T::CreatePoolOrigin::ensure_origin(caller_origin.clone()).unwrap(); + + let footprint = Footprint::from_mel::<(PoolId, PoolInfoFor<T>)>(); + T::Consideration::ensure_successful(&caller, footprint); + + let staked_asset = T::BenchmarkHelper::staked_asset(); + let reward_asset = T::BenchmarkHelper::reward_asset(); + + let min_balance = T::Assets::minimum_balance(staked_asset.clone()).max(T::Balance::one()); + if !T::Assets::asset_exists(staked_asset.clone()) { + assert_ok!(T::Assets::create(staked_asset.clone(), caller.clone(), true, min_balance)); + } + let min_balance = T::Assets::minimum_balance(reward_asset.clone()).max(T::Balance::one()); + if !T::Assets::asset_exists(reward_asset.clone()) { + assert_ok!(T::Assets::create(reward_asset.clone(), caller.clone(), true, min_balance)); + } + + #[extrinsic_call] + _( + caller_origin as T::RuntimeOrigin, + Box::new(staked_asset.clone()), + Box::new(reward_asset.clone()), + min_balance, + pool_expire::<T>(), + Some(caller.clone()), + ); + + assert_last_event::<T>( + Event::PoolCreated { + creator: caller.clone(), + admin: caller, + staked_asset_id: staked_asset, + reward_asset_id: reward_asset, + reward_rate_per_block: min_balance, + expiry_block: pool_expire::<T>().evaluate(System::<T>::block_number()), + pool_id: 0, + } + .into(), + ); + + Ok(()) + } + + #[benchmark] + fn stake() -> Result<(), BenchmarkError> { + create_reward_pool::<T>()?; + + let staker: T::AccountId = whitelisted_caller(); + let min_balance = mint_into::<T>(&staker, &T::BenchmarkHelper::staked_asset()); + + // stake first to get worth case benchmark. + assert_ok!(AssetRewards::<T>::stake( + RawOrigin::Signed(staker.clone()).into(), + 0, + min_balance + )); + + #[extrinsic_call] + _(RawOrigin::Signed(staker.clone()), 0, min_balance); + + assert_last_event::<T>(Event::Staked { staker, pool_id: 0, amount: min_balance }.into()); + + Ok(()) + } + + #[benchmark] + fn unstake() -> Result<(), BenchmarkError> { + create_reward_pool::<T>()?; + + let staker: T::AccountId = whitelisted_caller(); + let min_balance = mint_into::<T>(&staker, &T::BenchmarkHelper::staked_asset()); + + assert_ok!(AssetRewards::<T>::stake( + RawOrigin::Signed(staker.clone()).into(), + 0, + min_balance, + )); + + #[extrinsic_call] + _(RawOrigin::Signed(staker.clone()), 0, min_balance, None); + + assert_last_event::<T>( + Event::Unstaked { caller: staker.clone(), staker, pool_id: 0, amount: min_balance } + .into(), + ); + + Ok(()) + } + + #[benchmark] + fn harvest_rewards() -> Result<(), BenchmarkError> { + create_reward_pool::<T>()?; + + let pool_acc = AssetRewards::<T>::pool_account_id(&0u32); + let min_reward_balance = mint_into::<T>(&pool_acc, &T::BenchmarkHelper::reward_asset()); + + let staker = whitelisted_caller(); + let _ = mint_into::<T>(&staker, &T::BenchmarkHelper::staked_asset()); + assert_ok!(AssetRewards::<T>::stake( + RawOrigin::Signed(staker.clone()).into(), + 0, + T::Balance::one(), + )); + + System::<T>::set_block_number(System::<T>::block_number() + BlockNumberFor::<T>::one()); + + #[extrinsic_call] + _(RawOrigin::Signed(staker.clone()), 0, None); + + assert_last_event::<T>( + Event::RewardsHarvested { + caller: staker.clone(), + staker, + pool_id: 0, + amount: min_reward_balance, + } + .into(), + ); + + Ok(()) + } + + #[benchmark] + fn set_pool_reward_rate_per_block() -> Result<(), BenchmarkError> { + let caller_origin = create_reward_pool::<T>()?; + + // stake first to get worth case benchmark. + { + let staker: T::AccountId = whitelisted_caller(); + let min_balance = mint_into::<T>(&staker, &T::BenchmarkHelper::staked_asset()); + + assert_ok!(AssetRewards::<T>::stake(RawOrigin::Signed(staker).into(), 0, min_balance)); + } + + let new_reward_rate_per_block = + T::Assets::minimum_balance(T::BenchmarkHelper::reward_asset()).max(T::Balance::one()) + + T::Balance::one(); + + #[extrinsic_call] + _(caller_origin as T::RuntimeOrigin, 0, new_reward_rate_per_block); + + assert_last_event::<T>( + Event::PoolRewardRateModified { pool_id: 0, new_reward_rate_per_block }.into(), + ); + Ok(()) + } + + #[benchmark] + fn set_pool_admin() -> Result<(), BenchmarkError> { + let caller_origin = create_reward_pool::<T>()?; + let new_admin: T::AccountId = whitelisted_caller(); + + #[extrinsic_call] + _(caller_origin as T::RuntimeOrigin, 0, new_admin.clone()); + + assert_last_event::<T>(Event::PoolAdminModified { pool_id: 0, new_admin }.into()); + + Ok(()) + } + + #[benchmark] + fn set_pool_expiry_block() -> Result<(), BenchmarkError> { + let create_origin = create_reward_pool::<T>()?; + + // stake first to get worth case benchmark. + { + let staker: T::AccountId = whitelisted_caller(); + let min_balance = mint_into::<T>(&staker, &T::BenchmarkHelper::staked_asset()); + + assert_ok!(AssetRewards::<T>::stake(RawOrigin::Signed(staker).into(), 0, min_balance)); + } + + let new_expiry_block = + pool_expire::<T>().evaluate(System::<T>::block_number()) + BlockNumberFor::<T>::one(); + + #[extrinsic_call] + _(create_origin as T::RuntimeOrigin, 0, DispatchTime::At(new_expiry_block)); + + assert_last_event::<T>( + Event::PoolExpiryBlockModified { pool_id: 0, new_expiry_block }.into(), + ); + + Ok(()) + } + + #[benchmark] + fn deposit_reward_tokens() -> Result<(), BenchmarkError> { + create_reward_pool::<T>()?; + let caller = whitelisted_caller(); + + let reward_asset = T::BenchmarkHelper::reward_asset(); + let pool_acc = AssetRewards::<T>::pool_account_id(&0u32); + let min_balance = mint_into::<T>(&caller, &reward_asset); + + let balance_before = T::Assets::balance(reward_asset.clone(), &pool_acc); + + #[extrinsic_call] + _(RawOrigin::Signed(caller), 0, min_balance); + + let balance_after = T::Assets::balance(reward_asset, &pool_acc); + + assert_eq!(balance_after, balance_before + min_balance); + + Ok(()) + } + + #[benchmark] + fn cleanup_pool() -> Result<(), BenchmarkError> { + let create_origin = create_reward_pool::<T>()?; + let caller = T::CreatePoolOrigin::ensure_origin(create_origin.clone()).unwrap(); + + // deposit rewards tokens to get worth case benchmark. + { + let caller = whitelisted_caller(); + let reward_asset = T::BenchmarkHelper::reward_asset(); + let min_balance = mint_into::<T>(&caller, &reward_asset); + assert_ok!(AssetRewards::<T>::deposit_reward_tokens( + RawOrigin::Signed(caller).into(), + 0, + min_balance + )); + } + + #[extrinsic_call] + _(RawOrigin::Signed(caller), 0); + + assert_last_event::<T>(Event::PoolCleanedUp { pool_id: 0 }.into()); + + Ok(()) + } + + impl_benchmark_test_suite!(AssetRewards, crate::mock::new_test_ext(), crate::mock::MockRuntime); +} diff --git a/substrate/frame/asset-rewards/src/lib.rs b/substrate/frame/asset-rewards/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..4ce73e9febf96f8c363ffa9203d0209f3b599d88 --- /dev/null +++ b/substrate/frame/asset-rewards/src/lib.rs @@ -0,0 +1,905 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # FRAME Staking Rewards Pallet +//! +//! Allows accounts to be rewarded for holding `fungible` asset/s, for example LP tokens. +//! +//! ## Overview +//! +//! Initiate an incentive program for a fungible asset by creating a new pool. +//! +//! During pool creation, a 'staking asset', 'reward asset', 'reward rate per block', 'expiry +//! block', and 'admin' are specified. +//! +//! Once created, holders of the 'staking asset' can 'stake' them in a corresponding pool, which +//! creates a Freeze on the asset. +//! +//! Once staked, rewards denominated in 'reward asset' begin accumulating to the staker, +//! proportional to their share of the total staked tokens in the pool. +//! +//! Reward assets pending distribution are held in an account unique to each pool. +//! +//! Care should be taken by the pool operator to keep pool accounts adequately funded with the +//! reward asset. +//! +//! The pool admin may increase reward rate per block, increase expiry block, and change admin. +//! +//! ## Disambiguation +//! +//! While this pallet shares some terminology with the `staking-pool` and similar native staking +//! related pallets, it is distinct and is entirely unrelated to native staking. +//! +//! ## Permissioning +//! +//! Currently, pool creation and management restricted to a configured Origin. +//! +//! Future iterations of this pallet may allow permissionless creation and management of pools. +//! +//! Note: The permissioned origin must return an AccountId. This can be achieved for any Origin by +//! wrapping it with `EnsureSuccess`. +//! +//! ## Implementation Notes +//! +//! Internal logic functions such as `update_pool_and_staker_rewards` were deliberately written +//! without side-effects. +//! +//! Storage interaction such as reads and writes are instead all performed in the top level +//! pallet Call method, which while slightly more verbose, makes it easier to understand the +//! code and reason about how storage reads and writes occur in the pallet. +//! +//! ## Rewards Algorithm +//! +//! The rewards algorithm is based on the Synthetix [StakingRewards.sol](https://github.com/Synthetixio/synthetix/blob/develop/contracts/StakingRewards.sol) +//! smart contract. +//! +//! Rewards are calculated JIT (just-in-time), and all operations are O(1) making the approach +//! scalable to many pools and stakers. +//! +//! ### Resources +//! +//! - [This video series](https://www.youtube.com/watch?v=6ZO5aYg1GI8), which walks through the math +//! of the algorithm. +//! - [This dev.to article](https://dev.to/heymarkkop/understanding-sushiswaps-masterchef-staking-rewards-1m6f), +//! which explains the algorithm of the SushiSwap MasterChef staking. While not identical to the +//! Synthetix approach, they are quite similar. +#![deny(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +use codec::{Codec, Decode, Encode, MaxEncodedLen}; +use frame_support::{ + traits::{ + fungibles::{Inspect, Mutate}, + schedule::DispatchTime, + tokens::Balance, + }, + PalletId, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use scale_info::TypeInfo; +use sp_core::Get; +use sp_runtime::{ + traits::{MaybeDisplay, Zero}, + DispatchError, +}; +use sp_std::boxed::Box; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; +mod weights; + +pub use weights::WeightInfo; + +/// Unique id type for each pool. +pub type PoolId = u32; + +/// Multiplier to maintain precision when calculating rewards. +pub(crate) const PRECISION_SCALING_FACTOR: u16 = 4096; + +/// Convenience alias for `PoolInfo`. +pub type PoolInfoFor<T> = PoolInfo< + <T as frame_system::Config>::AccountId, + <T as Config>::AssetId, + <T as Config>::Balance, + BlockNumberFor<T>, +>; + +/// The state of a staker in a pool. +#[derive(Debug, Default, Clone, Decode, Encode, MaxEncodedLen, TypeInfo)] +pub struct PoolStakerInfo<Balance> { + /// Amount of tokens staked. + amount: Balance, + /// Accumulated, unpaid rewards. + rewards: Balance, + /// Reward per token value at the time of the staker's last interaction with the contract. + reward_per_token_paid: Balance, +} + +/// The state and configuration of an incentive pool. +#[derive(Debug, Clone, Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)] +pub struct PoolInfo<AccountId, AssetId, Balance, BlockNumber> { + /// The asset staked in this pool. + staked_asset_id: AssetId, + /// The asset distributed as rewards by this pool. + reward_asset_id: AssetId, + /// The amount of tokens rewarded per block. + reward_rate_per_block: Balance, + /// The block the pool will cease distributing rewards. + expiry_block: BlockNumber, + /// The account authorized to manage this pool. + admin: AccountId, + /// The total amount of tokens staked in this pool. + total_tokens_staked: Balance, + /// Total rewards accumulated per token, up to the `last_update_block`. + reward_per_token_stored: Balance, + /// Last block number the pool was updated. + last_update_block: BlockNumber, + /// The account that holds the pool's rewards. + account: AccountId, +} + +sp_api::decl_runtime_apis! { + /// The runtime API for the asset rewards pallet. + pub trait AssetRewards<Cost: MaybeDisplay + Codec> { + /// Get the cost of creating a pool. + /// + /// This is especially useful when the cost is dynamic. + fn pool_creation_cost() -> Cost; + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{ + pallet_prelude::*, + traits::{ + fungibles::MutateFreeze, + tokens::{AssetId, Fortitude, Preservation}, + Consideration, Footprint, + }, + }; + use frame_system::pallet_prelude::*; + use sp_runtime::{ + traits::{ + AccountIdConversion, BadOrigin, EnsureAdd, EnsureAddAssign, EnsureDiv, EnsureMul, + EnsureSub, EnsureSubAssign, + }, + DispatchResult, + }; + + #[pallet::pallet] + pub struct Pallet<T>(_); + + /// A reason for the pallet placing a hold on funds. + #[pallet::composite_enum] + pub enum FreezeReason { + /// Funds are staked in the pallet. + #[codec(index = 0)] + Staked, + } + + /// A reason for the pallet placing a hold on funds. + #[pallet::composite_enum] + pub enum HoldReason { + /// Cost associated with storing pool information on-chain. + #[codec(index = 0)] + PoolCreation, + } + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Overarching event type. + type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; + + /// The pallet's unique identifier, used to derive the pool's account ID. + /// + /// The account ID is derived once during pool creation and stored in the storage. + #[pallet::constant] + type PalletId: Get<PalletId>; + + /// Identifier for each type of asset. + type AssetId: AssetId + Member + Parameter; + + /// The type in which the assets are measured. + type Balance: Balance + TypeInfo; + + /// The origin with permission to create pools. + /// + /// The Origin must return an AccountId. + type CreatePoolOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>; + + /// Registry of assets that can be configured to either stake for rewards, or be offered as + /// rewards for staking. + type Assets: Inspect<Self::AccountId, AssetId = Self::AssetId, Balance = Self::Balance> + + Mutate<Self::AccountId>; + + /// Freezer for the Assets. + type AssetsFreezer: MutateFreeze< + Self::AccountId, + Id = Self::RuntimeFreezeReason, + AssetId = Self::AssetId, + Balance = Self::Balance, + >; + + /// The overarching freeze reason. + type RuntimeFreezeReason: From<FreezeReason>; + + /// Means for associating a cost with the on-chain storage of pool information, which + /// is incurred by the pool creator. + /// + /// The passed `Footprint` specifically accounts for the storage footprint of the pool's + /// information itself, excluding any potential storage footprint related to the stakers. + type Consideration: Consideration<Self::AccountId, Footprint>; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// Helper for benchmarking. + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper: benchmarking::BenchmarkHelper<Self::AssetId>; + } + + /// State of pool stakers. + #[pallet::storage] + pub type PoolStakers<T: Config> = StorageDoubleMap< + _, + Blake2_128Concat, + PoolId, + Blake2_128Concat, + T::AccountId, + PoolStakerInfo<T::Balance>, + >; + + /// State and configuration of each staking pool. + #[pallet::storage] + pub type Pools<T: Config> = StorageMap<_, Blake2_128Concat, PoolId, PoolInfoFor<T>>; + + /// The cost associated with storing pool information on-chain which was incurred by the pool + /// creator. + /// + /// This cost may be [`None`], as determined by [`Config::Consideration`]. + #[pallet::storage] + pub type PoolCost<T: Config> = + StorageMap<_, Blake2_128Concat, PoolId, (T::AccountId, T::Consideration)>; + + /// Stores the [`PoolId`] to use for the next pool. + /// + /// Incremented when a new pool is created. + #[pallet::storage] + pub type NextPoolId<T: Config> = StorageValue<_, PoolId, ValueQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event<T: Config> { + /// An account staked some tokens in a pool. + Staked { + /// The account that staked assets. + staker: T::AccountId, + /// The pool. + pool_id: PoolId, + /// The staked asset amount. + amount: T::Balance, + }, + /// An account unstaked some tokens from a pool. + Unstaked { + /// The account that signed transaction. + caller: T::AccountId, + /// The account that unstaked assets. + staker: T::AccountId, + /// The pool. + pool_id: PoolId, + /// The unstaked asset amount. + amount: T::Balance, + }, + /// An account harvested some rewards. + RewardsHarvested { + /// The account that signed transaction. + caller: T::AccountId, + /// The staker whos rewards were harvested. + staker: T::AccountId, + /// The pool. + pool_id: PoolId, + /// The amount of harvested tokens. + amount: T::Balance, + }, + /// A new reward pool was created. + PoolCreated { + /// The account that created the pool. + creator: T::AccountId, + /// The unique ID for the new pool. + pool_id: PoolId, + /// The staking asset. + staked_asset_id: T::AssetId, + /// The reward asset. + reward_asset_id: T::AssetId, + /// The initial reward rate per block. + reward_rate_per_block: T::Balance, + /// The block the pool will cease to accumulate rewards. + expiry_block: BlockNumberFor<T>, + /// The account allowed to modify the pool. + admin: T::AccountId, + }, + /// A pool reward rate was modified by the admin. + PoolRewardRateModified { + /// The modified pool. + pool_id: PoolId, + /// The new reward rate per block. + new_reward_rate_per_block: T::Balance, + }, + /// A pool admin was modified. + PoolAdminModified { + /// The modified pool. + pool_id: PoolId, + /// The new admin. + new_admin: T::AccountId, + }, + /// A pool expiry block was modified by the admin. + PoolExpiryBlockModified { + /// The modified pool. + pool_id: PoolId, + /// The new expiry block. + new_expiry_block: BlockNumberFor<T>, + }, + /// A pool information was cleared after it's completion. + PoolCleanedUp { + /// The cleared pool. + pool_id: PoolId, + }, + } + + #[pallet::error] + pub enum Error<T> { + /// The staker does not have enough tokens to perform the operation. + NotEnoughTokens, + /// An operation was attempted on a non-existent pool. + NonExistentPool, + /// An operation was attempted for a non-existent staker. + NonExistentStaker, + /// An operation was attempted with a non-existent asset. + NonExistentAsset, + /// There was an error converting a block number. + BlockNumberConversionError, + /// The expiry block must be in the future. + ExpiryBlockMustBeInTheFuture, + /// Insufficient funds to create the freeze. + InsufficientFunds, + /// The expiry block can be only extended. + ExpiryCut, + /// The reward rate per block can be only increased. + RewardRateCut, + /// The pool still has staked tokens or rewards. + NonEmptyPool, + } + + #[pallet::hooks] + impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { + fn integrity_test() { + // The AccountId is at least 16 bytes to contain the unique PalletId. + let pool_id: PoolId = 1; + assert!( + <frame_support::PalletId as AccountIdConversion<T::AccountId>>::try_into_sub_account( + &T::PalletId::get(), pool_id, + ) + .is_some() + ); + } + } + + /// Pallet's callable functions. + #[pallet::call(weight(<T as Config>::WeightInfo))] + impl<T: Config> Pallet<T> { + /// Create a new reward pool. + /// + /// Parameters: + /// - `origin`: must be `Config::CreatePoolOrigin`; + /// - `staked_asset_id`: the asset to be staked in the pool; + /// - `reward_asset_id`: the asset to be distributed as rewards; + /// - `reward_rate_per_block`: the amount of reward tokens distributed per block; + /// - `expiry`: the block number at which the pool will cease to accumulate rewards. The + /// [`DispatchTime::After`] variant evaluated at the execution time. + /// - `admin`: the account allowed to extend the pool expiration, increase the rewards rate + /// and receive the unutilized reward tokens back after the pool completion. If `None`, + /// the caller is set as an admin. + #[pallet::call_index(0)] + pub fn create_pool( + origin: OriginFor<T>, + staked_asset_id: Box<T::AssetId>, + reward_asset_id: Box<T::AssetId>, + reward_rate_per_block: T::Balance, + expiry: DispatchTime<BlockNumberFor<T>>, + admin: Option<T::AccountId>, + ) -> DispatchResult { + // Check the origin. + let creator = T::CreatePoolOrigin::ensure_origin(origin)?; + + // Ensure the assets exist. + ensure!( + T::Assets::asset_exists(*staked_asset_id.clone()), + Error::<T>::NonExistentAsset + ); + ensure!( + T::Assets::asset_exists(*reward_asset_id.clone()), + Error::<T>::NonExistentAsset + ); + + // Check the expiry block. + let expiry_block = expiry.evaluate(frame_system::Pallet::<T>::block_number()); + ensure!( + expiry_block > frame_system::Pallet::<T>::block_number(), + Error::<T>::ExpiryBlockMustBeInTheFuture + ); + + let pool_id = NextPoolId::<T>::get(); + + let footprint = Self::pool_creation_footprint(); + let cost = T::Consideration::new(&creator, footprint)?; + PoolCost::<T>::insert(pool_id, (creator.clone(), cost)); + + let admin = admin.unwrap_or(creator.clone()); + + // Create the pool. + let pool = PoolInfoFor::<T> { + staked_asset_id: *staked_asset_id.clone(), + reward_asset_id: *reward_asset_id.clone(), + reward_rate_per_block, + total_tokens_staked: 0u32.into(), + reward_per_token_stored: 0u32.into(), + last_update_block: 0u32.into(), + expiry_block, + admin: admin.clone(), + account: Self::pool_account_id(&pool_id), + }; + + // Insert it into storage. + Pools::<T>::insert(pool_id, pool); + + NextPoolId::<T>::put(pool_id.ensure_add(1)?); + + // Emit created event. + Self::deposit_event(Event::PoolCreated { + creator, + pool_id, + staked_asset_id: *staked_asset_id, + reward_asset_id: *reward_asset_id, + reward_rate_per_block, + expiry_block, + admin, + }); + + Ok(()) + } + + /// Stake additional tokens in a pool. + /// + /// A freeze is placed on the staked tokens. + #[pallet::call_index(1)] + pub fn stake(origin: OriginFor<T>, pool_id: PoolId, amount: T::Balance) -> DispatchResult { + let staker = ensure_signed(origin)?; + + // Always start by updating staker and pool rewards. + let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?; + let staker_info = PoolStakers::<T>::get(pool_id, &staker).unwrap_or_default(); + let (mut pool_info, mut staker_info) = + Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?; + + T::AssetsFreezer::increase_frozen( + pool_info.staked_asset_id.clone(), + &FreezeReason::Staked.into(), + &staker, + amount, + )?; + + // Update Pools. + pool_info.total_tokens_staked.ensure_add_assign(amount)?; + + Pools::<T>::insert(pool_id, pool_info); + + // Update PoolStakers. + staker_info.amount.ensure_add_assign(amount)?; + PoolStakers::<T>::insert(pool_id, &staker, staker_info); + + // Emit event. + Self::deposit_event(Event::Staked { staker, pool_id, amount }); + + Ok(()) + } + + /// Unstake tokens from a pool. + /// + /// Removes the freeze on the staked tokens. + /// + /// Parameters: + /// - origin: must be the `staker` if the pool is still active. Otherwise, any account. + /// - pool_id: the pool to unstake from. + /// - amount: the amount of tokens to unstake. + /// - staker: the account to unstake from. If `None`, the caller is used. + #[pallet::call_index(2)] + pub fn unstake( + origin: OriginFor<T>, + pool_id: PoolId, + amount: T::Balance, + staker: Option<T::AccountId>, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let staker = staker.unwrap_or(caller.clone()); + + // Always start by updating the pool rewards. + let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?; + let now = frame_system::Pallet::<T>::block_number(); + ensure!(now > pool_info.expiry_block || caller == staker, BadOrigin); + + let staker_info = PoolStakers::<T>::get(pool_id, &staker).unwrap_or_default(); + let (mut pool_info, mut staker_info) = + Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?; + + // Check the staker has enough staked tokens. + ensure!(staker_info.amount >= amount, Error::<T>::NotEnoughTokens); + + // Unfreeze staker assets. + T::AssetsFreezer::decrease_frozen( + pool_info.staked_asset_id.clone(), + &FreezeReason::Staked.into(), + &staker, + amount, + )?; + + // Update Pools. + pool_info.total_tokens_staked.ensure_sub_assign(amount)?; + Pools::<T>::insert(pool_id, pool_info); + + // Update PoolStakers. + staker_info.amount.ensure_sub_assign(amount)?; + + if staker_info.amount.is_zero() && staker_info.rewards.is_zero() { + PoolStakers::<T>::remove(&pool_id, &staker); + } else { + PoolStakers::<T>::insert(&pool_id, &staker, staker_info); + } + + // Emit event. + Self::deposit_event(Event::Unstaked { caller, staker, pool_id, amount }); + + Ok(()) + } + + /// Harvest unclaimed pool rewards. + /// + /// Parameters: + /// - origin: must be the `staker` if the pool is still active. Otherwise, any account. + /// - pool_id: the pool to harvest from. + /// - staker: the account for which to harvest rewards. If `None`, the caller is used. + #[pallet::call_index(3)] + pub fn harvest_rewards( + origin: OriginFor<T>, + pool_id: PoolId, + staker: Option<T::AccountId>, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let staker = staker.unwrap_or(caller.clone()); + + // Always start by updating the pool and staker rewards. + let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?; + let now = frame_system::Pallet::<T>::block_number(); + ensure!(now > pool_info.expiry_block || caller == staker, BadOrigin); + + let staker_info = + PoolStakers::<T>::get(pool_id, &staker).ok_or(Error::<T>::NonExistentStaker)?; + let (pool_info, mut staker_info) = + Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?; + + // Transfer unclaimed rewards from the pool to the staker. + T::Assets::transfer( + pool_info.reward_asset_id, + &pool_info.account, + &staker, + staker_info.rewards, + // Could kill the account, but only if the pool was already almost empty. + Preservation::Expendable, + )?; + + // Emit event. + Self::deposit_event(Event::RewardsHarvested { + caller, + staker: staker.clone(), + pool_id, + amount: staker_info.rewards, + }); + + // Reset staker rewards. + staker_info.rewards = 0u32.into(); + + if staker_info.amount.is_zero() { + PoolStakers::<T>::remove(&pool_id, &staker); + } else { + PoolStakers::<T>::insert(&pool_id, &staker, staker_info); + } + + Ok(()) + } + + /// Modify a pool reward rate. + /// + /// Currently the reward rate can only be increased. + /// + /// Only the pool admin may perform this operation. + #[pallet::call_index(4)] + pub fn set_pool_reward_rate_per_block( + origin: OriginFor<T>, + pool_id: PoolId, + new_reward_rate_per_block: T::Balance, + ) -> DispatchResult { + let caller = T::CreatePoolOrigin::ensure_origin(origin.clone()) + .or_else(|_| ensure_signed(origin))?; + + let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?; + ensure!(pool_info.admin == caller, BadOrigin); + ensure!( + new_reward_rate_per_block > pool_info.reward_rate_per_block, + Error::<T>::RewardRateCut + ); + + // Always start by updating the pool rewards. + let rewards_per_token = Self::reward_per_token(&pool_info)?; + let mut pool_info = Self::update_pool_rewards(&pool_info, rewards_per_token)?; + + pool_info.reward_rate_per_block = new_reward_rate_per_block; + Pools::<T>::insert(pool_id, pool_info); + + Self::deposit_event(Event::PoolRewardRateModified { + pool_id, + new_reward_rate_per_block, + }); + + Ok(()) + } + + /// Modify a pool admin. + /// + /// Only the pool admin may perform this operation. + #[pallet::call_index(5)] + pub fn set_pool_admin( + origin: OriginFor<T>, + pool_id: PoolId, + new_admin: T::AccountId, + ) -> DispatchResult { + let caller = T::CreatePoolOrigin::ensure_origin(origin.clone()) + .or_else(|_| ensure_signed(origin))?; + + let mut pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?; + ensure!(pool_info.admin == caller, BadOrigin); + + pool_info.admin = new_admin.clone(); + Pools::<T>::insert(pool_id, pool_info); + + Self::deposit_event(Event::PoolAdminModified { pool_id, new_admin }); + + Ok(()) + } + + /// Set when the pool should expire. + /// + /// Currently the expiry block can only be extended. + /// + /// Only the pool admin may perform this operation. + #[pallet::call_index(6)] + pub fn set_pool_expiry_block( + origin: OriginFor<T>, + pool_id: PoolId, + new_expiry: DispatchTime<BlockNumberFor<T>>, + ) -> DispatchResult { + let caller = T::CreatePoolOrigin::ensure_origin(origin.clone()) + .or_else(|_| ensure_signed(origin))?; + + let new_expiry = new_expiry.evaluate(frame_system::Pallet::<T>::block_number()); + ensure!( + new_expiry > frame_system::Pallet::<T>::block_number(), + Error::<T>::ExpiryBlockMustBeInTheFuture + ); + + let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?; + ensure!(pool_info.admin == caller, BadOrigin); + ensure!(new_expiry > pool_info.expiry_block, Error::<T>::ExpiryCut); + + // Always start by updating the pool rewards. + let reward_per_token = Self::reward_per_token(&pool_info)?; + let mut pool_info = Self::update_pool_rewards(&pool_info, reward_per_token)?; + + pool_info.expiry_block = new_expiry; + Pools::<T>::insert(pool_id, pool_info); + + Self::deposit_event(Event::PoolExpiryBlockModified { + pool_id, + new_expiry_block: new_expiry, + }); + + Ok(()) + } + + /// Convenience method to deposit reward tokens into a pool. + /// + /// This method is not strictly necessary (tokens could be transferred directly to the + /// pool pot address), but is provided for convenience so manual derivation of the + /// account id is not required. + #[pallet::call_index(7)] + pub fn deposit_reward_tokens( + origin: OriginFor<T>, + pool_id: PoolId, + amount: T::Balance, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?; + T::Assets::transfer( + pool_info.reward_asset_id, + &caller, + &pool_info.account, + amount, + Preservation::Preserve, + )?; + Ok(()) + } + + /// Cleanup a pool. + /// + /// Origin must be the pool admin. + /// + /// Cleanup storage, release any associated storage cost and return the remaining reward + /// tokens to the admin. + #[pallet::call_index(8)] + pub fn cleanup_pool(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult { + let who = ensure_signed(origin)?; + + let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?; + ensure!(pool_info.admin == who, BadOrigin); + + let stakers = PoolStakers::<T>::iter_key_prefix(pool_id).next(); + ensure!(stakers.is_none(), Error::<T>::NonEmptyPool); + + let pool_balance = T::Assets::reducible_balance( + pool_info.reward_asset_id.clone(), + &pool_info.account, + Preservation::Expendable, + Fortitude::Polite, + ); + T::Assets::transfer( + pool_info.reward_asset_id, + &pool_info.account, + &pool_info.admin, + pool_balance, + Preservation::Expendable, + )?; + + if let Some((who, cost)) = PoolCost::<T>::take(pool_id) { + T::Consideration::drop(cost, &who)?; + } + + Pools::<T>::remove(pool_id); + + Self::deposit_event(Event::PoolCleanedUp { pool_id }); + + Ok(()) + } + } + + impl<T: Config> Pallet<T> { + /// The pool creation footprint. + /// + /// The footprint specifically accounts for the storage footprint of the pool's information + /// itself, excluding any potential storage footprint related to the stakers. + pub fn pool_creation_footprint() -> Footprint { + Footprint::from_mel::<(PoolId, PoolInfoFor<T>)>() + } + + /// Derive a pool account ID from the pool's ID. + pub fn pool_account_id(id: &PoolId) -> T::AccountId { + T::PalletId::get().into_sub_account_truncating(id) + } + + /// Computes update pool and staker reward state. + /// + /// Should be called prior to any operation involving a staker. + /// + /// Returns the updated pool and staker info. + /// + /// NOTE: this function has no side-effects. Side-effects such as storage modifications are + /// the responsibility of the caller. + pub fn update_pool_and_staker_rewards( + pool_info: &PoolInfoFor<T>, + staker_info: &PoolStakerInfo<T::Balance>, + ) -> Result<(PoolInfoFor<T>, PoolStakerInfo<T::Balance>), DispatchError> { + let reward_per_token = Self::reward_per_token(&pool_info)?; + let pool_info = Self::update_pool_rewards(pool_info, reward_per_token)?; + + let mut new_staker_info = staker_info.clone(); + new_staker_info.rewards = Self::derive_rewards(&staker_info, &reward_per_token)?; + new_staker_info.reward_per_token_paid = pool_info.reward_per_token_stored; + return Ok((pool_info, new_staker_info)); + } + + /// Computes update pool reward state. + /// + /// Should be called every time the pool is adjusted, and a staker is not involved. + /// + /// Returns the updated pool and staker info. + /// + /// NOTE: this function has no side-effects. Side-effects such as storage modifications are + /// the responsibility of the caller. + pub fn update_pool_rewards( + pool_info: &PoolInfoFor<T>, + reward_per_token: T::Balance, + ) -> Result<PoolInfoFor<T>, DispatchError> { + let mut new_pool_info = pool_info.clone(); + new_pool_info.last_update_block = frame_system::Pallet::<T>::block_number(); + new_pool_info.reward_per_token_stored = reward_per_token; + + Ok(new_pool_info) + } + + /// Derives the current reward per token for this pool. + fn reward_per_token(pool_info: &PoolInfoFor<T>) -> Result<T::Balance, DispatchError> { + if pool_info.total_tokens_staked.is_zero() { + return Ok(pool_info.reward_per_token_stored) + } + + let rewardable_blocks_elapsed: u32 = + match Self::last_block_reward_applicable(pool_info.expiry_block) + .ensure_sub(pool_info.last_update_block)? + .try_into() + { + Ok(b) => b, + Err(_) => return Err(Error::<T>::BlockNumberConversionError.into()), + }; + + Ok(pool_info.reward_per_token_stored.ensure_add( + pool_info + .reward_rate_per_block + .ensure_mul(rewardable_blocks_elapsed.into())? + .ensure_mul(PRECISION_SCALING_FACTOR.into())? + .ensure_div(pool_info.total_tokens_staked)?, + )?) + } + + /// Derives the amount of rewards earned by a staker. + /// + /// This is a helper function for `update_pool_rewards` and should not be called directly. + fn derive_rewards( + staker_info: &PoolStakerInfo<T::Balance>, + reward_per_token: &T::Balance, + ) -> Result<T::Balance, DispatchError> { + Ok(staker_info + .amount + .ensure_mul(reward_per_token.ensure_sub(staker_info.reward_per_token_paid)?)? + .ensure_div(PRECISION_SCALING_FACTOR.into())? + .ensure_add(staker_info.rewards)?) + } + + fn last_block_reward_applicable(pool_expiry_block: BlockNumberFor<T>) -> BlockNumberFor<T> { + let now = frame_system::Pallet::<T>::block_number(); + if now < pool_expiry_block { + now + } else { + pool_expiry_block + } + } + } +} diff --git a/substrate/frame/asset-rewards/src/mock.rs b/substrate/frame/asset-rewards/src/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..1e9b41104d4cd03e5810a5f256fb6a8e0f024ecc --- /dev/null +++ b/substrate/frame/asset-rewards/src/mock.rs @@ -0,0 +1,222 @@ +// 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. + +//! Test environment for Asset Rewards pallet. + +use super::*; +use crate as pallet_asset_rewards; +use core::default::Default; +use frame_support::{ + construct_runtime, derive_impl, + instances::Instance1, + parameter_types, + traits::{ + tokens::fungible::{HoldConsideration, NativeFromLeft, NativeOrWithId, UnionOf}, + AsEnsureOriginWithArg, ConstU128, ConstU32, EnsureOrigin, LinearStoragePrice, + }, + PalletId, +}; +use frame_system::EnsureSigned; +use sp_runtime::{traits::IdentityLookup, BuildStorage}; + +#[cfg(feature = "runtime-benchmarks")] +use self::benchmarking::BenchmarkHelper; + +type Block = frame_system::mocking::MockBlock<MockRuntime>; + +construct_runtime!( + pub enum MockRuntime + { + System: frame_system, + Balances: pallet_balances, + Assets: pallet_assets::<Instance1>, + AssetsFreezer: pallet_assets_freezer::<Instance1>, + StakingRewards: pallet_asset_rewards, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for MockRuntime { + type AccountId = u128; + type Lookup = IdentityLookup<Self::AccountId>; + type Block = Block; + type AccountData = pallet_balances::AccountData<u128>; +} + +impl pallet_balances::Config for MockRuntime { + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<100>; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = RuntimeFreezeReason; + type MaxFreezes = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type DoneSlashHandler = (); +} + +impl pallet_assets::Config<Instance1> for MockRuntime { + type RuntimeEvent = RuntimeEvent; + type Balance = u128; + type RemoveItemsLimit = ConstU32<1000>; + type AssetId = u32; + type AssetIdParameter = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg<EnsureSigned<Self::AccountId>>; + type ForceOrigin = frame_system::EnsureRoot<Self::AccountId>; + type AssetDeposit = ConstU128<1>; + type AssetAccountDeposit = ConstU128<10>; + type MetadataDepositBase = ConstU128<1>; + type MetadataDepositPerByte = ConstU128<1>; + type ApprovalDeposit = ConstU128<1>; + type StringLimit = ConstU32<50>; + type Freezer = AssetsFreezer; + type Extra = (); + type WeightInfo = (); + type CallbackHandle = (); + pallet_assets::runtime_benchmarks_enabled! { + type BenchmarkHelper = (); + } +} + +parameter_types! { + pub const StakingRewardsPalletId: PalletId = PalletId(*b"py/stkrd"); + pub const Native: NativeOrWithId<u32> = NativeOrWithId::Native; + pub const PermissionedAccountId: u128 = 0; +} + +/// Give Root Origin permission to create pools. +pub struct MockPermissionedOrigin; +impl EnsureOrigin<RuntimeOrigin> for MockPermissionedOrigin { + type Success = <MockRuntime as frame_system::Config>::AccountId; + + fn try_origin(origin: RuntimeOrigin) -> Result<Self::Success, RuntimeOrigin> { + match origin.clone().into() { + Ok(frame_system::RawOrigin::Root) => Ok(PermissionedAccountId::get()), + _ => Err(origin), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result<RuntimeOrigin, ()> { + Ok(RuntimeOrigin::root()) + } +} + +/// Allow Freezes for the `Assets` pallet +impl pallet_assets_freezer::Config<pallet_assets_freezer::Instance1> for MockRuntime { + type RuntimeFreezeReason = RuntimeFreezeReason; + type RuntimeEvent = RuntimeEvent; +} + +pub type NativeAndAssets = UnionOf<Balances, Assets, NativeFromLeft, NativeOrWithId<u32>, u128>; + +pub type NativeAndAssetsFreezer = + UnionOf<Balances, AssetsFreezer, NativeFromLeft, NativeOrWithId<u32>, u128>; + +#[cfg(feature = "runtime-benchmarks")] +pub struct AssetRewardsBenchmarkHelper; +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkHelper<NativeOrWithId<u32>> for AssetRewardsBenchmarkHelper { + fn staked_asset() -> NativeOrWithId<u32> { + NativeOrWithId::<u32>::WithId(101) + } + fn reward_asset() -> NativeOrWithId<u32> { + NativeOrWithId::<u32>::WithId(102) + } +} + +parameter_types! { + pub const CreationHoldReason: RuntimeHoldReason = + RuntimeHoldReason::StakingRewards(pallet_asset_rewards::HoldReason::PoolCreation); +} + +impl Config for MockRuntime { + type RuntimeEvent = RuntimeEvent; + type AssetId = NativeOrWithId<u32>; + type Balance = <Self as pallet_balances::Config>::Balance; + type Assets = NativeAndAssets; + type AssetsFreezer = NativeAndAssetsFreezer; + type PalletId = StakingRewardsPalletId; + type CreatePoolOrigin = MockPermissionedOrigin; + type WeightInfo = (); + type RuntimeFreezeReason = RuntimeFreezeReason; + type Consideration = HoldConsideration< + u128, + Balances, + CreationHoldReason, + LinearStoragePrice<ConstU128<100>, ConstU128<0>, u128>, + >; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = AssetRewardsBenchmarkHelper; +} + +pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::<MockRuntime>::default().build_storage().unwrap(); + + pallet_assets::GenesisConfig::<MockRuntime, Instance1> { + // Genesis assets: id, owner, is_sufficient, min_balance + // pub assets: Vec<(T::AssetId, T::AccountId, bool, T::Balance)>, + assets: vec![(1, 1, true, 1), (10, 1, true, 1), (20, 1, true, 1)], + // Genesis metadata: id, name, symbol, decimals + // pub metadata: Vec<(T::AssetId, Vec<u8>, Vec<u8>, u8)>, + metadata: vec![ + (1, b"test".to_vec(), b"TST".to_vec(), 18), + (10, b"test10".to_vec(), b"T10".to_vec(), 18), + (20, b"test20".to_vec(), b"T20".to_vec(), 18), + ], + // Genesis accounts: id, account_id, balance + // pub accounts: Vec<(T::AssetId, T::AccountId, T::Balance)>, + accounts: vec![ + (1, 1, 10000), + (1, 2, 20000), + (1, 3, 30000), + (1, 4, 40000), + (1, 10, 40000), + (1, 20, 40000), + ], + next_asset_id: None, + } + .assimilate_storage(&mut t) + .unwrap(); + + let pool_zero_account_id = 31086825966906540362769395565; + pallet_balances::GenesisConfig::<MockRuntime> { + balances: vec![ + (0, 10000), + (1, 10000), + (2, 20000), + (3, 30000), + (4, 40000), + (10, 40000), + (20, 40000), + (pool_zero_account_id, 100_000), // Top up the default pool account id + ], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/substrate/frame/asset-rewards/src/tests.rs b/substrate/frame/asset-rewards/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..399d6a54c939293e4fdc2c87118d85596e701374 --- /dev/null +++ b/substrate/frame/asset-rewards/src/tests.rs @@ -0,0 +1,1457 @@ +// 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::{mock::*, *}; +use frame_support::{ + assert_err, assert_noop, assert_ok, hypothetically, + traits::{ + fungible, + fungible::NativeOrWithId, + fungibles, + tokens::{Fortitude, Preservation}, + }, +}; +use sp_runtime::{traits::BadOrigin, ArithmeticError, TokenError}; + +const DEFAULT_STAKED_ASSET_ID: NativeOrWithId<u32> = NativeOrWithId::<u32>::WithId(1); +const DEFAULT_REWARD_ASSET_ID: NativeOrWithId<u32> = NativeOrWithId::<u32>::Native; +const DEFAULT_REWARD_RATE_PER_BLOCK: u128 = 100; +const DEFAULT_EXPIRE_AFTER: u64 = 200; +const DEFAULT_ADMIN: u128 = 1; + +/// Creates a basic pool with values: +/// - Staking asset: 1 +/// - Reward asset: Native +/// - Reward rate per block: 100 +/// - Lifetime: 100 +/// - Admin: 1 +/// +/// Useful to reduce boilerplate in tests when it's not important to customise or reuse pool +/// params. +pub fn create_default_pool() { + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(DEFAULT_STAKED_ASSET_ID.clone()), + Box::new(DEFAULT_REWARD_ASSET_ID.clone()), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + Some(DEFAULT_ADMIN) + )); +} + +/// The same as [`create_default_pool`], but with the admin parameter set to the creator. +pub fn create_default_pool_permissioned_admin() { + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(DEFAULT_STAKED_ASSET_ID.clone()), + Box::new(DEFAULT_REWARD_ASSET_ID.clone()), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + Some(PermissionedAccountId::get()), + )); +} + +fn assert_hypothetically_earned( + staker: u128, + expected_earned: u128, + pool_id: u32, + reward_asset_id: NativeOrWithId<u32>, +) { + hypothetically!({ + // Get the pre-harvest balance. + let balance_before: <MockRuntime as Config>::Balance = + <<MockRuntime as Config>::Assets>::balance(reward_asset_id.clone(), &staker); + + // Harvest the rewards. + assert_ok!(StakingRewards::harvest_rewards(RuntimeOrigin::signed(staker), pool_id, None),); + + // Sanity check: staker rewards are reset to 0 if some `amount` is still staked, otherwise + // the storage item removed. + if let Some(staker_pool) = PoolStakers::<MockRuntime>::get(pool_id, staker) { + assert!(staker_pool.rewards == 0); + assert!(staker_pool.amount > 0); + } + + // Check that the staker has earned the expected amount. + let balance_after = + <<MockRuntime as Config>::Assets>::balance(reward_asset_id.clone(), &staker); + assert_eq!(balance_after - balance_before, expected_earned); + }); +} + +fn events() -> Vec<Event<MockRuntime>> { + let result = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let mock::RuntimeEvent::StakingRewards(inner) = e { + Some(inner) + } else { + None + } + }) + .collect(); + + System::reset_events(); + + result +} + +fn pools() -> Vec<(u32, PoolInfo<u128, NativeOrWithId<u32>, u128, u64>)> { + Pools::<MockRuntime>::iter().collect() +} + +mod create_pool { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + assert_eq!(NextPoolId::<MockRuntime>::get(), 0); + + System::set_block_number(10); + let expected_expiry_block = DEFAULT_EXPIRE_AFTER + 10; + + // Create a pool with default values, and no admin override so [`PermissionedAccountId`] + // is admin. + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(DEFAULT_STAKED_ASSET_ID), + Box::new(DEFAULT_REWARD_ASSET_ID), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + Some(PermissionedAccountId::get()) + )); + + // Event is emitted. + assert_eq!( + events(), + [Event::<MockRuntime>::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id: 0, + staked_asset_id: DEFAULT_STAKED_ASSET_ID, + reward_asset_id: DEFAULT_REWARD_ASSET_ID, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + }] + ); + + // State is updated correctly. + assert_eq!(NextPoolId::<MockRuntime>::get(), 1); + assert_eq!( + pools(), + vec![( + 0, + PoolInfo { + staked_asset_id: DEFAULT_STAKED_ASSET_ID, + reward_asset_id: DEFAULT_REWARD_ASSET_ID, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + total_tokens_staked: 0, + reward_per_token_stored: 0, + last_update_block: 0, + account: StakingRewards::pool_account_id(&0), + } + )] + ); + + // Create another pool with explicit admin and other overrides. + let admin = 2; + let staked_asset_id = NativeOrWithId::<u32>::WithId(10); + let reward_asset_id = NativeOrWithId::<u32>::WithId(20); + let reward_rate_per_block = 250; + let expiry_block = 500; + let expected_expiry_block = expiry_block + 10; + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(staked_asset_id.clone()), + Box::new(reward_asset_id.clone()), + reward_rate_per_block, + DispatchTime::After(expiry_block), + Some(admin) + )); + + // Event is emitted. + assert_eq!( + events(), + [Event::<MockRuntime>::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id: 1, + staked_asset_id: staked_asset_id.clone(), + reward_asset_id: reward_asset_id.clone(), + reward_rate_per_block, + admin, + expiry_block: expected_expiry_block, + }] + ); + + // State is updated correctly. + assert_eq!(NextPoolId::<MockRuntime>::get(), 2); + assert_eq!( + pools(), + vec![ + ( + 0, + PoolInfo { + staked_asset_id: DEFAULT_STAKED_ASSET_ID, + reward_asset_id: DEFAULT_REWARD_ASSET_ID, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + admin: PermissionedAccountId::get(), + expiry_block: DEFAULT_EXPIRE_AFTER + 10, + total_tokens_staked: 0, + reward_per_token_stored: 0, + last_update_block: 0, + account: StakingRewards::pool_account_id(&0), + } + ), + ( + 1, + PoolInfo { + staked_asset_id, + reward_asset_id, + reward_rate_per_block, + admin, + total_tokens_staked: 0, + expiry_block: expected_expiry_block, + reward_per_token_stored: 0, + last_update_block: 0, + account: StakingRewards::pool_account_id(&1), + } + ) + ] + ); + }); + } + + #[test] + fn success_same_assets() { + new_test_ext().execute_with(|| { + assert_eq!(NextPoolId::<MockRuntime>::get(), 0); + + System::set_block_number(10); + let expected_expiry_block = DEFAULT_EXPIRE_AFTER + 10; + + // Create a pool with the same staking and reward asset. + let asset = NativeOrWithId::<u32>::Native; + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(asset.clone()), + Box::new(asset.clone()), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + Some(PermissionedAccountId::get()) + )); + + // Event is emitted. + assert_eq!( + events(), + [Event::<MockRuntime>::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id: 0, + staked_asset_id: asset.clone(), + reward_asset_id: asset.clone(), + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + }] + ); + + // State is updated correctly. + assert_eq!(NextPoolId::<MockRuntime>::get(), 1); + assert_eq!( + pools(), + vec![( + 0, + PoolInfo { + staked_asset_id: asset.clone(), + reward_asset_id: asset, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + total_tokens_staked: 0, + reward_per_token_stored: 0, + last_update_block: 0, + account: StakingRewards::pool_account_id(&0), + } + )] + ); + }) + } + + #[test] + fn fails_for_non_existent_asset() { + new_test_ext().execute_with(|| { + let valid_asset = NativeOrWithId::<u32>::WithId(1); + let invalid_asset = NativeOrWithId::<u32>::WithId(200); + + assert_err!( + StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(valid_asset.clone()), + Box::new(invalid_asset.clone()), + 10, + DispatchTime::After(10u64), + None + ), + Error::<MockRuntime>::NonExistentAsset + ); + + assert_err!( + StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(invalid_asset.clone()), + Box::new(valid_asset.clone()), + 10, + DispatchTime::After(10u64), + None + ), + Error::<MockRuntime>::NonExistentAsset + ); + + assert_err!( + StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(invalid_asset.clone()), + Box::new(invalid_asset.clone()), + 10, + DispatchTime::After(10u64), + None + ), + Error::<MockRuntime>::NonExistentAsset + ); + }) + } + + #[test] + fn fails_for_not_permissioned() { + new_test_ext().execute_with(|| { + let user = 100; + let staked_asset_id = NativeOrWithId::<u32>::Native; + let reward_asset_id = NativeOrWithId::<u32>::WithId(1); + let reward_rate_per_block = 100; + let expiry_block = 100u64; + assert_err!( + StakingRewards::create_pool( + RuntimeOrigin::signed(user), + Box::new(staked_asset_id.clone()), + Box::new(reward_asset_id.clone()), + reward_rate_per_block, + DispatchTime::After(expiry_block), + None + ), + BadOrigin + ); + }); + } + + #[test] + fn create_pool_with_caller_admin() { + new_test_ext().execute_with(|| { + assert_eq!(NextPoolId::<MockRuntime>::get(), 0); + + System::set_block_number(10); + let expected_expiry_block = DEFAULT_EXPIRE_AFTER + 10; + + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(DEFAULT_STAKED_ASSET_ID), + Box::new(DEFAULT_REWARD_ASSET_ID), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + None, + )); + + assert_eq!( + events(), + [Event::<MockRuntime>::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id: 0, + staked_asset_id: DEFAULT_STAKED_ASSET_ID, + reward_asset_id: DEFAULT_REWARD_ASSET_ID, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + }] + ); + + assert_eq!(Pools::<MockRuntime>::get(0).unwrap().admin, PermissionedAccountId::get()); + }); + } +} + +mod stake { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let user = 1; + create_default_pool(); + let pool_id = 0; + let initial_balance = <Assets as fungibles::Inspect<u128>>::reducible_balance( + 1, + &user, + Preservation::Expendable, + Fortitude::Force, + ); + + // User stakes tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 1000)); + + // Check that the user's staked amount is updated + assert_eq!(PoolStakers::<MockRuntime>::get(pool_id, user).unwrap().amount, 1000); + + // Event is emitted. + assert_eq!( + *events().last().unwrap(), + Event::<MockRuntime>::Staked { staker: user, amount: 1000, pool_id: 0 } + ); + + // Check that the pool's total tokens staked is updated + assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().total_tokens_staked, 1000); + + // Check user's frozen balance is updated + assert_eq!( + <Assets as fungibles::Inspect<u128>>::reducible_balance( + 1, + &user, + Preservation::Expendable, + Fortitude::Force, + ), + // - extra 1 for ed + initial_balance - 1000 - 1 + ); + + // User stakes more tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 500)); + + // Event is emitted. + assert_eq!( + *events().last().unwrap(), + Event::<MockRuntime>::Staked { staker: user, amount: 500, pool_id: 0 } + ); + + // Check that the user's staked amount is updated + assert_eq!(PoolStakers::<MockRuntime>::get(pool_id, user).unwrap().amount, 1000 + 500); + + // Check that the pool's total tokens staked is updated + assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().total_tokens_staked, 1000 + 500); + + assert_eq!( + <Assets as fungibles::Inspect<u128>>::reducible_balance( + 1, + &user, + Preservation::Expendable, + Fortitude::Force, + ), + // - extra 1 for ed + initial_balance - 1500 - 1 + ); + + // Event is emitted. + assert_eq!(events(), []); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let user = 1; + assert_err!( + StakingRewards::stake(RuntimeOrigin::signed(user), 999, 1000), + Error::<MockRuntime>::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_insufficient_balance() { + new_test_ext().execute_with(|| { + let user = 1; + create_default_pool(); + let pool_id = 0; + let initial_balance = <Assets as fungibles::Inspect<u128>>::reducible_balance( + 1, + &user, + Preservation::Expendable, + Fortitude::Force, + ); + assert_err!( + StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, initial_balance + 1), + TokenError::FundsUnavailable, + ); + }) + } +} + +mod unstake { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let user = 1; + create_default_pool(); + let pool_id = 0; + + // User stakes tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 1000)); + + // User unstakes tokens + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 500, None)); + + // Event is emitted. + assert_eq!( + *events().last().unwrap(), + Event::<MockRuntime>::Unstaked { + caller: user, + staker: user, + amount: 500, + pool_id: 0 + } + ); + + // Check that the user's staked amount is updated + assert_eq!(PoolStakers::<MockRuntime>::get(pool_id, user).unwrap().amount, 500); + + // Check that the pool's total tokens staked is updated + assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().total_tokens_staked, 500); + + // User unstakes remaining tokens + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 500, None)); + + // Check that the storage items is removed since stake amount and rewards are zero. + assert!(PoolStakers::<MockRuntime>::get(pool_id, user).is_none()); + + // Check that the pool's total tokens staked is zero + assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().total_tokens_staked, 0); + }); + } + + #[test] + fn unstake_for_other() { + new_test_ext().execute_with(|| { + let staker = 1; + let caller = 2; + let pool_id = 0; + let init_block = System::block_number(); + + create_default_pool(); + + // User stakes tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + + // Fails to unstake for other since pool is still active + assert_noop!( + StakingRewards::unstake(RuntimeOrigin::signed(caller), pool_id, 500, Some(staker)), + BadOrigin, + ); + + System::set_block_number(init_block + DEFAULT_EXPIRE_AFTER + 1); + + assert_ok!(StakingRewards::unstake( + RuntimeOrigin::signed(caller), + pool_id, + 500, + Some(staker) + )); + + // Event is emitted. + assert_eq!( + *events().last().unwrap(), + Event::<MockRuntime>::Unstaked { caller, staker, amount: 500, pool_id: 0 } + ); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let user = 1; + let non_existent_pool_id = 999; + + // User tries to unstake tokens from a non-existent pool + assert_err!( + StakingRewards::unstake( + RuntimeOrigin::signed(user), + non_existent_pool_id, + 500, + None + ), + Error::<MockRuntime>::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_insufficient_staked_amount() { + new_test_ext().execute_with(|| { + let user = 1; + create_default_pool(); + let pool_id = 0; + + // User stakes tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 1000)); + + // User tries to unstake more tokens than they have staked + assert_err!( + StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 1500, None), + Error::<MockRuntime>::NotEnoughTokens + ); + }); + } +} + +mod harvest_rewards { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let staker = 1; + let pool_id = 0; + let reward_asset_id = NativeOrWithId::<u32>::Native; + create_default_pool(); + + // Stake + System::set_block_number(10); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + + // Harvest + System::set_block_number(20); + let balance_before: <MockRuntime as Config>::Balance = + <<MockRuntime as Config>::Assets>::balance(reward_asset_id.clone(), &staker); + assert_ok!(StakingRewards::harvest_rewards( + RuntimeOrigin::signed(staker), + pool_id, + None + )); + let balance_after = + <<MockRuntime as Config>::Assets>::balance(reward_asset_id.clone(), &staker); + + // Assert + assert_eq!( + balance_after - balance_before, + 10 * Pools::<MockRuntime>::get(pool_id).unwrap().reward_rate_per_block + ); + assert_eq!( + *events().last().unwrap(), + Event::<MockRuntime>::RewardsHarvested { + caller: staker, + staker, + pool_id, + amount: 10 * Pools::<MockRuntime>::get(pool_id).unwrap().reward_rate_per_block + } + ); + }); + } + + #[test] + fn harvest_for_other() { + new_test_ext().execute_with(|| { + let caller = 2; + let staker = 1; + let pool_id = 0; + let init_block = System::block_number(); + + create_default_pool(); + + // Stake + System::set_block_number(10); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + + System::set_block_number(20); + + // Fails to harvest for staker since pool is still active + assert_noop!( + StakingRewards::harvest_rewards( + RuntimeOrigin::signed(caller), + pool_id, + Some(staker) + ), + BadOrigin + ); + + System::set_block_number(init_block + DEFAULT_EXPIRE_AFTER + 1); + + // Harvest for staker + assert_ok!(StakingRewards::harvest_rewards( + RuntimeOrigin::signed(caller), + pool_id, + Some(staker), + )); + + assert!(matches!( + events().last().unwrap(), + Event::<MockRuntime>::RewardsHarvested { + caller, + staker, + pool_id, + .. + } if caller == caller && staker == staker && pool_id == pool_id + )); + }); + } + + #[test] + fn fails_for_non_existent_staker() { + new_test_ext().execute_with(|| { + let non_existent_staker = 999; + + create_default_pool(); + assert_err!( + StakingRewards::harvest_rewards( + RuntimeOrigin::signed(non_existent_staker), + 0, + None + ), + Error::<MockRuntime>::NonExistentStaker + ); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let staker = 1; + let non_existent_pool_id = 999; + + assert_err!( + StakingRewards::harvest_rewards( + RuntimeOrigin::signed(staker), + non_existent_pool_id, + None, + ), + Error::<MockRuntime>::NonExistentPool + ); + }); + } +} + +mod set_pool_admin { + use super::*; + + #[test] + fn success_signed_admin() { + new_test_ext().execute_with(|| { + let admin = 1; + let new_admin = 2; + let pool_id = 0; + create_default_pool(); + + // Modify the pool admin + assert_ok!(StakingRewards::set_pool_admin( + RuntimeOrigin::signed(admin), + pool_id, + new_admin, + )); + + // Check state + assert_eq!( + *events().last().unwrap(), + Event::<MockRuntime>::PoolAdminModified { pool_id, new_admin } + ); + assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().admin, new_admin); + }); + } + + #[test] + fn success_permissioned_admin() { + new_test_ext().execute_with(|| { + let new_admin = 2; + let pool_id = 0; + create_default_pool_permissioned_admin(); + + // Modify the pool admin + assert_ok!(StakingRewards::set_pool_admin(RuntimeOrigin::root(), pool_id, new_admin)); + + // Check state + assert_eq!( + *events().last().unwrap(), + Event::<MockRuntime>::PoolAdminModified { pool_id, new_admin } + ); + assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().admin, new_admin); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let admin = 1; + let new_admin = 2; + let non_existent_pool_id = 999; + + assert_err!( + StakingRewards::set_pool_admin( + RuntimeOrigin::signed(admin), + non_existent_pool_id, + new_admin + ), + Error::<MockRuntime>::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_non_admin() { + new_test_ext().execute_with(|| { + let new_admin = 2; + let non_admin = 3; + let pool_id = 0; + create_default_pool(); + + assert_err!( + StakingRewards::set_pool_admin( + RuntimeOrigin::signed(non_admin), + pool_id, + new_admin + ), + BadOrigin + ); + }); + } +} + +mod set_pool_expiry_block { + use super::*; + + #[test] + fn success_permissioned_admin() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let new_expiry_block = System::block_number() + DEFAULT_EXPIRE_AFTER + 1u64; + create_default_pool_permissioned_admin(); + + assert_ok!(StakingRewards::set_pool_expiry_block( + RuntimeOrigin::root(), + pool_id, + DispatchTime::At(new_expiry_block), + )); + + // Check state + assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().expiry_block, new_expiry_block); + assert_eq!( + *events().last().unwrap(), + Event::<MockRuntime>::PoolExpiryBlockModified { pool_id, new_expiry_block } + ); + }); + } + + #[test] + fn success_signed_admin() { + new_test_ext().execute_with(|| { + let admin = 1; + let pool_id = 0; + let new_expiry_block = System::block_number() + DEFAULT_EXPIRE_AFTER + 1u64; + create_default_pool(); + + assert_ok!(StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::At(new_expiry_block) + )); + + // Check state + assert_eq!(Pools::<MockRuntime>::get(pool_id).unwrap().expiry_block, new_expiry_block); + assert_eq!( + *events().last().unwrap(), + Event::<MockRuntime>::PoolExpiryBlockModified { pool_id, new_expiry_block } + ); + }); + } + + #[test] + fn extends_reward_accumulation() { + new_test_ext().execute_with(|| { + let admin = 1; + let staker = 2; + let pool_id = 0; + let new_expiry_block = 300u64; + System::set_block_number(10); + create_default_pool(); + + // Regular reward accumulation + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + System::set_block_number(20); + assert_hypothetically_earned( + staker, + DEFAULT_REWARD_RATE_PER_BLOCK * 10, + pool_id, + NativeOrWithId::<u32>::Native, + ); + + // Expiry was block 210, so earned 200 at block 250 + System::set_block_number(250); + assert_hypothetically_earned( + staker, + DEFAULT_REWARD_RATE_PER_BLOCK * 200, + pool_id, + NativeOrWithId::<u32>::Native, + ); + + // Extend expiry 50 more blocks + assert_ok!(StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::At(new_expiry_block) + )); + System::set_block_number(350); + + // Staker has been in pool with rewards active for 250 blocks total + assert_hypothetically_earned( + staker, + DEFAULT_REWARD_RATE_PER_BLOCK * 250, + pool_id, + NativeOrWithId::<u32>::Native, + ); + }); + } + + #[test] + fn fails_to_cutback_expiration() { + new_test_ext().execute_with(|| { + let admin = 1; + let pool_id = 0; + create_default_pool(); + + assert_noop!( + StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::After(30) + ), + Error::<MockRuntime>::ExpiryCut + ); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let admin = 1; + let non_existent_pool_id = 999; + let new_expiry_block = 200u64; + + assert_err!( + StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + non_existent_pool_id, + DispatchTime::After(new_expiry_block) + ), + Error::<MockRuntime>::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_non_admin() { + new_test_ext().execute_with(|| { + let non_admin = 2; + let pool_id = 0; + let new_expiry_block = 200u64; + create_default_pool(); + + assert_err!( + StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(non_admin), + pool_id, + DispatchTime::After(new_expiry_block) + ), + BadOrigin + ); + }); + } + + #[test] + fn fails_for_expiry_block_in_the_past() { + new_test_ext().execute_with(|| { + let admin = 1; + let pool_id = 0; + create_default_pool(); + System::set_block_number(50); + assert_err!( + StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::At(40u64) + ), + Error::<MockRuntime>::ExpiryBlockMustBeInTheFuture + ); + }); + } +} + +mod set_pool_reward_rate_per_block { + use super::*; + + #[test] + fn success_signed_admin() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let new_reward_rate = 200; + create_default_pool(); + + // Pool Admin can modify + assert_ok!(StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(DEFAULT_ADMIN), + pool_id, + new_reward_rate + )); + + // Check state + assert_eq!( + Pools::<MockRuntime>::get(pool_id).unwrap().reward_rate_per_block, + new_reward_rate + ); + + // Check event + assert_eq!( + *events().last().unwrap(), + Event::<MockRuntime>::PoolRewardRateModified { + pool_id, + new_reward_rate_per_block: new_reward_rate + } + ); + }); + } + + #[test] + fn success_permissioned_admin() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let new_reward_rate = 200; + create_default_pool_permissioned_admin(); + + // Root can modify + assert_ok!(StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::root(), + pool_id, + new_reward_rate + )); + + // Check state + assert_eq!( + Pools::<MockRuntime>::get(pool_id).unwrap().reward_rate_per_block, + new_reward_rate + ); + + // Check event + assert_eq!( + *events().last().unwrap(), + Event::<MockRuntime>::PoolRewardRateModified { + pool_id, + new_reward_rate_per_block: new_reward_rate + } + ); + }); + } + + #[test] + fn staker_rewards_are_affected_correctly() { + new_test_ext().execute_with(|| { + let admin = 1; + let staker = 2; + let pool_id = 0; + let new_reward_rate = 150; + create_default_pool(); + + // Stake some tokens, and accumulate 10 blocks of rewards at the default pool rate (100) + System::set_block_number(10); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + System::set_block_number(20); + + // Increase the reward rate + assert_ok!(StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(admin), + pool_id, + new_reward_rate + )); + + // Accumulate 10 blocks of rewards at the new rate + System::set_block_number(30); + + // Check that rewards are calculated correctly with the updated rate + assert_hypothetically_earned( + staker, + 10 * 100 + 10 * new_reward_rate, + pool_id, + NativeOrWithId::<u32>::Native, + ); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let admin = 1; + let non_existent_pool_id = 999; + let new_reward_rate = 200; + + assert_err!( + StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(admin), + non_existent_pool_id, + new_reward_rate + ), + Error::<MockRuntime>::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_non_admin() { + new_test_ext().execute_with(|| { + let non_admin = 2; + let pool_id = 0; + let new_reward_rate = 200; + create_default_pool(); + + assert_err!( + StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(non_admin), + pool_id, + new_reward_rate + ), + BadOrigin + ); + }); + } + + #[test] + fn fails_to_decrease() { + new_test_ext().execute_with(|| { + create_default_pool_permissioned_admin(); + + assert_noop!( + StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::root(), + 0, + DEFAULT_REWARD_RATE_PER_BLOCK - 1 + ), + Error::<MockRuntime>::RewardRateCut + ); + }); + } +} + +mod deposit_reward_tokens { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let depositor = 1; + let pool_id = 0; + let amount = 1000; + let reward_asset_id = NativeOrWithId::<u32>::Native; + create_default_pool(); + let pool_account_id = StakingRewards::pool_account_id(&pool_id); + + let depositor_balance_before = + <<MockRuntime as Config>::Assets>::balance(reward_asset_id.clone(), &depositor); + let pool_balance_before = <<MockRuntime as Config>::Assets>::balance( + reward_asset_id.clone(), + &pool_account_id, + ); + assert_ok!(StakingRewards::deposit_reward_tokens( + RuntimeOrigin::signed(depositor), + pool_id, + amount + )); + let depositor_balance_after = + <<MockRuntime as Config>::Assets>::balance(reward_asset_id.clone(), &depositor); + let pool_balance_after = + <<MockRuntime as Config>::Assets>::balance(reward_asset_id, &pool_account_id); + + assert_eq!(pool_balance_after - pool_balance_before, amount); + assert_eq!(depositor_balance_before - depositor_balance_after, amount); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + assert_err!( + StakingRewards::deposit_reward_tokens(RuntimeOrigin::signed(1), 999, 100), + Error::<MockRuntime>::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_insufficient_balance() { + new_test_ext().execute_with(|| { + create_default_pool(); + assert_err!( + StakingRewards::deposit_reward_tokens(RuntimeOrigin::signed(1), 0, 100_000_000), + ArithmeticError::Underflow + ); + }); + } +} + +mod cleanup_pool { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let admin = DEFAULT_ADMIN; + let admin_balance_before = <Balances as fungible::Inspect<u128>>::balance(&admin); + + create_default_pool(); + assert!(Pools::<MockRuntime>::get(pool_id).is_some()); + + assert_ok!(StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id)); + + assert_eq!( + <Balances as fungible::Inspect<u128>>::balance(&admin), + // `100_000` initial pool account balance from Genesis config + admin_balance_before + 100_000, + ); + assert_eq!(Pools::<MockRuntime>::get(pool_id), None); + assert_eq!(PoolStakers::<MockRuntime>::iter_prefix_values(pool_id).count(), 0); + assert_eq!(PoolCost::<MockRuntime>::get(pool_id), None); + }); + } + + #[test] + fn success_only_when_pool_empty() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let staker = 20; + let admin = DEFAULT_ADMIN; + + create_default_pool(); + + // stake to prevent pool cleanup + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 100)); + + assert_noop!( + StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id), + Error::<MockRuntime>::NonEmptyPool + ); + + // unstake partially + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker), pool_id, 50, None)); + + assert_noop!( + StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id), + Error::<MockRuntime>::NonEmptyPool + ); + + // unstake all + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker), pool_id, 50, None)); + + assert_ok!(StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id),); + + assert_eq!(Pools::<MockRuntime>::get(pool_id), None); + assert_eq!(PoolStakers::<MockRuntime>::iter_prefix_values(pool_id).count(), 0); + assert_eq!(PoolCost::<MockRuntime>::get(pool_id), None); + }); + } + + #[test] + fn fails_on_wrong_origin() { + new_test_ext().execute_with(|| { + let caller = 888; + let pool_id = 0; + create_default_pool(); + + assert_noop!( + StakingRewards::cleanup_pool(RuntimeOrigin::signed(caller), pool_id), + BadOrigin + ); + }); + } +} + +/// This integration test +/// 1. Considers 2 stakers each staking and unstaking at different intervals, asserts their +/// claimable rewards are adjusted as expected, and that harvesting works. +/// 2. Checks that rewards are correctly halted after the pool's expiry block, and resume when the +/// pool is extended. +/// 3. Checks that reward rates adjustment works correctly. +/// +/// Note: There are occasionally off by 1 errors due to rounding. In practice this is +/// insignificant. +#[test] +fn integration() { + new_test_ext().execute_with(|| { + let admin = 1; + let staker1 = 10u128; + let staker2 = 20; + let staked_asset_id = NativeOrWithId::<u32>::WithId(1); + let reward_asset_id = NativeOrWithId::<u32>::Native; + let reward_rate_per_block = 100; + let lifetime = 24u64.into(); + System::set_block_number(1); + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(staked_asset_id.clone()), + Box::new(reward_asset_id.clone()), + reward_rate_per_block, + DispatchTime::After(lifetime), + Some(admin) + )); + let pool_id = 0; + + // Block 7: Staker 1 stakes 100 tokens. + System::set_block_number(7); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker1), pool_id, 100)); + // At this point + // - Staker 1 has earned 0 tokens. + // - Staker 1 is earning 100 tokens per block. + + // Check that Staker 1 has earned 0 tokens. + assert_hypothetically_earned(staker1, 0, pool_id, reward_asset_id.clone()); + + // Block 9: Staker 2 stakes 100 tokens. + System::set_block_number(9); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker2), pool_id, 100)); + // At this point + // - Staker 1 has earned 200 (100*2) tokens. + // - Staker 2 has earned 0 tokens. + // - Staker 1 is earning 50 tokens per block. + // - Staker 2 is earning 50 tokens per block. + + // Check that Staker 1 has earned 200 tokens and Staker 2 has earned 0 tokens. + assert_hypothetically_earned(staker1, 200, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 0, pool_id, reward_asset_id.clone()); + + // Block 12: Staker 1 stakes an additional 100 tokens. + System::set_block_number(12); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker1), pool_id, 100)); + // At this point + // - Staker 1 has earned 350 (200 + (50 * 3)) tokens. + // - Staker 2 has earned 150 (50 * 3) tokens. + // - Staker 1 is earning 66.66 tokens per block. + // - Staker 2 is earning 33.33 tokens per block. + + // Check that Staker 1 has earned 350 tokens and Staker 2 has earned 150 tokens. + assert_hypothetically_earned(staker1, 350, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 150, pool_id, reward_asset_id.clone()); + + // Block 22: Staker 1 unstakes 100 tokens. + System::set_block_number(22); + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker1), pool_id, 100, None)); + // - Staker 1 has earned 1016 (350 + 66.66 * 10) tokens. + // - Staker 2 has earned 483 (150 + 33.33 * 10) tokens. + // - Staker 1 is earning 50 tokens per block. + // - Staker 2 is earning 50 tokens per block. + assert_hypothetically_earned(staker1, 1016, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 483, pool_id, reward_asset_id.clone()); + + // Block 23: Staker 1 unstakes 100 tokens. + System::set_block_number(23); + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker1), pool_id, 100, None)); + // - Staker 1 has earned 1065 (1015 + 50) tokens. + // - Staker 2 has earned 533 (483 + 50) tokens. + // - Staker 1 is earning 0 tokens per block. + // - Staker 2 is earning 100 tokens per block. + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 533, pool_id, reward_asset_id.clone()); + + // Block 50: Stakers should only have earned 2 blocks worth of tokens (expiry is 25). + System::set_block_number(50); + // - Staker 1 has earned 1065 tokens. + // - Staker 2 has earned 733 (533 + 2 * 100) tokens. + // - Staker 1 is earning 0 tokens per block. + // - Staker 2 is earning 0 tokens per block. + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 733, pool_id, reward_asset_id.clone()); + + // Block 51: Extend the pool expiry block to 60. + System::set_block_number(51); + // - Staker 1 is earning 0 tokens per block. + // - Staker 2 is earning 100 tokens per block. + assert_ok!(StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::At(60u64), + )); + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 733, pool_id, reward_asset_id.clone()); + + // Block 53: Check rewards are resumed. + // - Staker 1 has earned 1065 tokens. + // - Staker 2 has earned 933 (733 + 2 * 100) tokens. + // - Staker 2 is earning 100 tokens per block. + System::set_block_number(53); + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 933, pool_id, reward_asset_id.clone()); + + // Block 55: Increase the block reward. + // - Staker 1 has earned 1065 tokens. + // - Staker 2 has earned 1133 (933 + 2 * 100) tokens. + // - Staker 2 is earning 50 tokens per block. + System::set_block_number(55); + assert_ok!(StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(admin), + pool_id, + 150 + )); + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 1133, pool_id, reward_asset_id.clone()); + + // Block 57: Staker2 harvests their rewards. + System::set_block_number(57); + // - Staker 2 has earned 1433 (1133 + 2 * 150) tokens. + assert_hypothetically_earned(staker2, 1433, pool_id, reward_asset_id.clone()); + // Get the pre-harvest balance. + let balance_before: <MockRuntime as Config>::Balance = + <<MockRuntime as Config>::Assets>::balance(reward_asset_id.clone(), &staker2); + assert_ok!(StakingRewards::harvest_rewards(RuntimeOrigin::signed(staker2), pool_id, None)); + let balance_after = + <<MockRuntime as Config>::Assets>::balance(reward_asset_id.clone(), &staker2); + assert_eq!(balance_after - balance_before, 1433u128); + + // Block 60: Check rewards were adjusted correctly. + // - Staker 1 has earned 1065 tokens. + // - Staker 2 has earned 450 (3 * 150) tokens. + System::set_block_number(60); + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 450, pool_id, reward_asset_id.clone()); + + // Finally, check events. + assert_eq!( + events(), + [ + Event::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id, + staked_asset_id, + reward_asset_id, + reward_rate_per_block: 100, + expiry_block: 25, + admin, + }, + Event::Staked { staker: staker1, pool_id, amount: 100 }, + Event::Staked { staker: staker2, pool_id, amount: 100 }, + Event::Staked { staker: staker1, pool_id, amount: 100 }, + Event::Unstaked { caller: staker1, staker: staker1, pool_id, amount: 100 }, + Event::Unstaked { caller: staker1, staker: staker1, pool_id, amount: 100 }, + Event::PoolExpiryBlockModified { pool_id, new_expiry_block: 60 }, + Event::PoolRewardRateModified { pool_id, new_reward_rate_per_block: 150 }, + Event::RewardsHarvested { caller: staker2, staker: staker2, pool_id, amount: 1433 } + ] + ); + }); +} diff --git a/substrate/frame/asset-rewards/src/weights.rs b/substrate/frame/asset-rewards/src/weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..c9e2d0fd251aa0f09b9d9da54b2307327a752380 --- /dev/null +++ b/substrate/frame/asset-rewards/src/weights.rs @@ -0,0 +1,368 @@ +// 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_asset_rewards` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-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_asset_rewards +// --chain=dev +// --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/asset-rewards/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_asset_rewards`. +pub trait WeightInfo { + fn create_pool() -> Weight; + fn stake() -> Weight; + fn unstake() -> Weight; + fn harvest_rewards() -> Weight; + fn set_pool_reward_rate_per_block() -> Weight; + fn set_pool_admin() -> Weight; + fn set_pool_expiry_block() -> Weight; + fn deposit_reward_tokens() -> Weight; + fn cleanup_pool() -> Weight; +} + +/// Weights for `pallet_asset_rewards` using the Substrate node and recommended hardware. +pub struct SubstrateWeight<T>(PhantomData<T>); +impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { + /// Storage: `Assets::Asset` (r:2 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::NextPoolId` (r:1 w:1) + /// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:0 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::Pools` (r:0 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `6360` + // Minimum execution time: 62_655_000 picoseconds. + Weight::from_parts(63_723_000, 6360) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `935` + // Estimated: `3615` + // Minimum execution time: 54_463_000 picoseconds. + Weight::from_parts(55_974_000, 3615) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn unstake() -> Weight { + // Proof Size summary in bytes: + // Measured: `935` + // Estimated: `3615` + // Minimum execution time: 55_749_000 picoseconds. + Weight::from_parts(57_652_000, 3615) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn harvest_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1021` + // Estimated: `6208` + // Minimum execution time: 69_372_000 picoseconds. + Weight::from_parts(70_278_000, 6208) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_reward_rate_per_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 19_284_000 picoseconds. + Weight::from_parts(19_791_000, 3615) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_admin() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 17_388_000 picoseconds. + Weight::from_parts(18_390_000, 3615) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_expiry_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 19_780_000 picoseconds. + Weight::from_parts(20_676_000, 3615) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn deposit_reward_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `840` + // Estimated: `6208` + // Minimum execution time: 57_746_000 picoseconds. + Weight::from_parts(59_669_000, 6208) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:0) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:1 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`) + fn cleanup_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `1236` + // Estimated: `6208` + // Minimum execution time: 110_443_000 picoseconds. + Weight::from_parts(113_149_000, 6208) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `Assets::Asset` (r:2 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::NextPoolId` (r:1 w:1) + /// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:0 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::Pools` (r:0 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `6360` + // Minimum execution time: 62_655_000 picoseconds. + Weight::from_parts(63_723_000, 6360) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `935` + // Estimated: `3615` + // Minimum execution time: 54_463_000 picoseconds. + Weight::from_parts(55_974_000, 3615) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn unstake() -> Weight { + // Proof Size summary in bytes: + // Measured: `935` + // Estimated: `3615` + // Minimum execution time: 55_749_000 picoseconds. + Weight::from_parts(57_652_000, 3615) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn harvest_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1021` + // Estimated: `6208` + // Minimum execution time: 69_372_000 picoseconds. + Weight::from_parts(70_278_000, 6208) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_reward_rate_per_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 19_284_000 picoseconds. + Weight::from_parts(19_791_000, 3615) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_admin() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 17_388_000 picoseconds. + Weight::from_parts(18_390_000, 3615) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_expiry_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 19_780_000 picoseconds. + Weight::from_parts(20_676_000, 3615) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn deposit_reward_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `840` + // Estimated: `6208` + // Minimum execution time: 57_746_000 picoseconds. + Weight::from_parts(59_669_000, 6208) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:0) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:1 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`) + fn cleanup_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `1236` + // Estimated: `6208` + // Minimum execution time: 110_443_000 picoseconds. + Weight::from_parts(113_149_000, 6208) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(8_u64)) + } +} diff --git a/substrate/frame/assets-freezer/Cargo.toml b/substrate/frame/assets-freezer/Cargo.toml index 68bfdd7cfb627c67c8097266dd05df6a8ab38914..d8c0ee6e442b269f707d03335b09734d4a140218 100644 --- a/substrate/frame/assets-freezer/Cargo.toml +++ b/substrate/frame/assets-freezer/Cargo.toml @@ -16,46 +16,31 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -frame-benchmarking = { optional = true, workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } pallet-assets = { workspace = true } -sp-runtime = { workspace = true } +scale-info = { features = ["derive"], workspace = true } [dev-dependencies] -sp-io = { workspace = true } -sp-core = { workspace = true } pallet-balances = { workspace = true } [features] default = ["std"] std = [ "codec/std", - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", + "frame/std", "log/std", "pallet-assets/std", "pallet-balances/std", "scale-info/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", ] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", + "frame/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", + "frame/try-runtime", "pallet-assets/try-runtime", "pallet-balances/try-runtime", - "sp-runtime/try-runtime", ] diff --git a/substrate/frame/assets-freezer/src/impls.rs b/substrate/frame/assets-freezer/src/impls.rs index cd383f1c3cd1e68f7cc0bcf0bb2841f0acf52119..8c9f148e1e9a4840cc398b01a938b2bbe5d8bc10 100644 --- a/substrate/frame/assets-freezer/src/impls.rs +++ b/substrate/frame/assets-freezer/src/impls.rs @@ -16,13 +16,7 @@ // limitations under the License. use super::*; - -use frame_support::traits::{ - fungibles::{Inspect, InspectFreeze, MutateFreeze}, - tokens::{DepositConsequence, Fortitude, Preservation, Provenance, WithdrawConsequence}, -}; use pallet_assets::FrozenBalance; -use sp_runtime::traits::Zero; // Implements [`FrozenBalance`] from [`pallet-assets`], so it can understand how much of an // account balance is frozen, and is able to signal to this pallet when to clear the state of an @@ -115,7 +109,7 @@ impl<T: Config<I>, I: 'static> MutateFreeze<T::AccountId> for Pallet<T, I> { id: &Self::Id, who: &T::AccountId, amount: Self::Balance, - ) -> sp_runtime::DispatchResult { + ) -> DispatchResult { if amount.is_zero() { return Self::thaw(asset, id, who); } @@ -135,7 +129,7 @@ impl<T: Config<I>, I: 'static> MutateFreeze<T::AccountId> for Pallet<T, I> { id: &Self::Id, who: &T::AccountId, amount: Self::Balance, - ) -> sp_runtime::DispatchResult { + ) -> DispatchResult { if amount.is_zero() { return Ok(()); } @@ -150,7 +144,7 @@ impl<T: Config<I>, I: 'static> MutateFreeze<T::AccountId> for Pallet<T, I> { Self::update_freezes(asset, who, freezes.as_bounded_slice()) } - fn thaw(asset: Self::AssetId, id: &Self::Id, who: &T::AccountId) -> sp_runtime::DispatchResult { + fn thaw(asset: Self::AssetId, id: &Self::Id, who: &T::AccountId) -> DispatchResult { let mut freezes = Freezes::<T, I>::get(asset.clone(), who); freezes.retain(|f| &f.id != id); Self::update_freezes(asset, who, freezes.as_bounded_slice()) diff --git a/substrate/frame/assets-freezer/src/lib.rs b/substrate/frame/assets-freezer/src/lib.rs index b42d41ac1d9255c0046de37ddf5cf1222173be7f..5f718ed84820f34654eeb2f475faa21982142ba7 100644 --- a/substrate/frame/assets-freezer/src/lib.rs +++ b/substrate/frame/assets-freezer/src/lib.rs @@ -18,10 +18,10 @@ //! # Assets Freezer Pallet //! //! A pallet capable of freezing fungibles from `pallet-assets`. This is an extension of -//! `pallet-assets`, wrapping [`fungibles::Inspect`](`frame_support::traits::fungibles::Inspect`). +//! `pallet-assets`, wrapping [`fungibles::Inspect`](`Inspect`). //! It implements both -//! [`fungibles::freeze::Inspect`](frame_support::traits::fungibles::freeze::Inspect) and -//! [`fungibles::freeze::Mutate`](frame_support::traits::fungibles::freeze::Mutate). The complexity +//! [`fungibles::freeze::Inspect`](InspectFreeze) and +//! [`fungibles::freeze::Mutate`](MutateFreeze). The complexity //! of the operations is `O(n)`. where `n` is the variant count of `RuntimeFreezeReason`. //! //! ## Pallet API @@ -35,26 +35,27 @@ //! //! - Pallet hooks allowing [`pallet-assets`] to know the frozen balance for an account on a given //! asset (see [`pallet_assets::FrozenBalance`]). -//! - An implementation of -//! [`fungibles::freeze::Inspect`](frame_support::traits::fungibles::freeze::Inspect) and -//! [`fungibles::freeze::Mutate`](frame_support::traits::fungibles::freeze::Mutate), allowing -//! other pallets to manage freezes for the `pallet-assets` assets. +//! - An implementation of [`fungibles::freeze::Inspect`](InspectFreeze) and +//! [`fungibles::freeze::Mutate`](MutateFreeze), allowing other pallets to manage freezes for the +//! `pallet-assets` assets. #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::{ - pallet_prelude::*, - traits::{tokens::IdAmount, VariantCount, VariantCountOf}, - BoundedVec, -}; -use frame_system::pallet_prelude::BlockNumberFor; -use sp_runtime::{ - traits::{Saturating, Zero}, - BoundedSlice, +use frame::{ + prelude::*, + traits::{ + fungibles::{Inspect, InspectFreeze, MutateFreeze}, + tokens::{ + DepositConsequence, Fortitude, IdAmount, Preservation, Provenance, WithdrawConsequence, + }, + }, }; pub use pallet::*; +#[cfg(feature = "try-runtime")] +use frame::try_runtime::TryRuntimeError; + #[cfg(test)] mod mock; #[cfg(test)] @@ -62,7 +63,7 @@ mod tests; mod impls; -#[frame_support::pallet] +#[frame::pallet] pub mod pallet { use super::*; @@ -125,7 +126,7 @@ pub mod pallet { #[pallet::hooks] impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> { #[cfg(feature = "try-runtime")] - fn try_state(_: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> { + fn try_state(_: BlockNumberFor<T>) -> Result<(), TryRuntimeError> { Self::do_try_state() } } @@ -159,13 +160,13 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> { Ok(()) } - #[cfg(any(test, feature = "try-runtime"))] - fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { + #[cfg(feature = "try-runtime")] + fn do_try_state() -> Result<(), TryRuntimeError> { for (asset, who, _) in FrozenBalances::<T, I>::iter() { let max_frozen_amount = Freezes::<T, I>::get(asset.clone(), who.clone()).iter().map(|l| l.amount).max(); - frame_support::ensure!( + ensure!( FrozenBalances::<T, I>::get(asset, who) == max_frozen_amount, "The `FrozenAmount` is not equal to the maximum amount in `Freezes` for (`asset`, `who`)" ); diff --git a/substrate/frame/assets-freezer/src/mock.rs b/substrate/frame/assets-freezer/src/mock.rs index bc903a018f7b8f6962d721f4160ac6cdfa35bbf2..ad08787aba27d95917fd6ac357a23d318eec28d5 100644 --- a/substrate/frame/assets-freezer/src/mock.rs +++ b/substrate/frame/assets-freezer/src/mock.rs @@ -20,23 +20,15 @@ use crate as pallet_assets_freezer; pub use crate::*; use codec::{Compact, Decode, Encode, MaxEncodedLen}; -use frame_support::{ - derive_impl, - traits::{AsEnsureOriginWithArg, ConstU64}, -}; +use frame::testing_prelude::*; use scale_info::TypeInfo; -use sp_core::{ConstU32, H256}; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; pub type AccountId = u64; pub type Balance = u64; pub type AssetId = u32; type Block = frame_system::mocking::MockBlock<Test>; -frame_support::construct_runtime!( +construct_runtime!( pub enum Test { System: frame_system, @@ -48,7 +40,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; + type BaseCallFilter = Everything; type BlockWeights = (); type BlockLength = (); type DbWeight = (); @@ -70,7 +62,7 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; + type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { @@ -132,7 +124,7 @@ impl Config for Test { type RuntimeEvent = RuntimeEvent; } -pub fn new_test_ext(execute: impl FnOnce()) -> sp_io::TestExternalities { +pub fn new_test_ext(execute: impl FnOnce()) -> TestExternalities { let t = RuntimeGenesisConfig { assets: pallet_assets::GenesisConfig { assets: vec![(1, 0, true, 1)], @@ -145,11 +137,12 @@ pub fn new_test_ext(execute: impl FnOnce()) -> sp_io::TestExternalities { } .build_storage() .unwrap(); - let mut ext: sp_io::TestExternalities = t.into(); + let mut ext: TestExternalities = t.into(); ext.execute_with(|| { System::set_block_number(1); execute(); - frame_support::assert_ok!(AssetsFreezer::do_try_state()); + #[cfg(feature = "try-runtime")] + assert_ok!(AssetsFreezer::do_try_state()); }); ext diff --git a/substrate/frame/assets-freezer/src/tests.rs b/substrate/frame/assets-freezer/src/tests.rs index 4f2dea79c705a9ecacba4ec427f2f8553920f9a1..b890dc98b574127fa25c488841236f4fc3a99d4c 100644 --- a/substrate/frame/assets-freezer/src/tests.rs +++ b/substrate/frame/assets-freezer/src/tests.rs @@ -17,22 +17,16 @@ //! Tests for pallet-assets-freezer. -use crate::mock::*; +use crate::mock::{self, *}; use codec::Compact; -use frame_support::{ - assert_ok, assert_storage_noop, - traits::{ - fungibles::{Inspect, InspectFreeze, MutateFreeze}, - tokens::{Fortitude, Preservation}, - }, -}; +use frame::testing_prelude::*; use pallet_assets::FrozenBalance; const WHO: AccountId = 1; -const ASSET_ID: AssetId = 1; +const ASSET_ID: mock::AssetId = 1; -fn test_set_freeze(id: DummyFreezeReason, amount: Balance) { +fn test_set_freeze(id: DummyFreezeReason, amount: mock::Balance) { let mut freezes = Freezes::<Test>::get(ASSET_ID, WHO); if let Some(i) = freezes.iter_mut().find(|l| l.id == id) { @@ -281,8 +275,6 @@ mod impl_mutate_freeze { } mod with_pallet_assets { - use frame_support::assert_noop; - use super::*; #[test] diff --git a/substrate/frame/assets/Cargo.toml b/substrate/frame/assets/Cargo.toml index e20b576d0836ec959ea3a050c4b269659b724214..a062a68d422065e779b91c0775e95628d4d96519 100644 --- a/substrate/frame/assets/Cargo.toml +++ b/substrate/frame/assets/Cargo.toml @@ -25,13 +25,13 @@ sp-runtime = { workspace = true } # Needed for type-safe access to storage DB. frame-support = { workspace = true } # `system` module provides us with all sorts of useful stuff and macros depend on it being around. -frame-system = { workspace = true } frame-benchmarking = { optional = true, workspace = true } +frame-system = { workspace = true } sp-core = { workspace = true } [dev-dependencies] -sp-io = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/atomic-swap/Cargo.toml b/substrate/frame/atomic-swap/Cargo.toml index db89a58da8f085bbde6e7242d9962c20df2b87e4..05a38ded91c516ac995d90cc54d92d688619d2f4 100644 --- a/substrate/frame/atomic-swap/Cargo.toml +++ b/substrate/frame/atomic-swap/Cargo.toml @@ -17,12 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } +frame = { workspace = true, features = ["runtime"] } scale-info = { features = ["derive"], workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } -sp-core = { workspace = true } -sp-io = { workspace = true } -sp-runtime = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } @@ -31,17 +27,11 @@ pallet-balances = { workspace = true, default-features = true } default = ["std"] std = [ "codec/std", - "frame-support/std", - "frame-system/std", + "frame/std", "pallet-balances/std", "scale-info/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", + "frame/try-runtime", "pallet-balances/try-runtime", - "sp-runtime/try-runtime", ] diff --git a/substrate/frame/atomic-swap/src/lib.rs b/substrate/frame/atomic-swap/src/lib.rs index c3010f5c9c03bce4b2dedc22f522a5fd84128980..9521f20fe0092c30c946c0b91c32e3c70c83404b 100644 --- a/substrate/frame/atomic-swap/src/lib.rs +++ b/substrate/frame/atomic-swap/src/lib.rs @@ -50,17 +50,11 @@ use core::{ marker::PhantomData, ops::{Deref, DerefMut}, }; -use frame_support::{ - dispatch::DispatchResult, - pallet_prelude::MaxEncodedLen, - traits::{BalanceStatus, Currency, Get, ReservableCurrency}, - weights::Weight, - RuntimeDebugNoBound, +use frame::{ + prelude::*, + traits::{BalanceStatus, Currency, ReservableCurrency}, }; -use frame_system::pallet_prelude::BlockNumberFor; use scale_info::TypeInfo; -use sp_io::hashing::blake2_256; -use sp_runtime::RuntimeDebug; /// Pending atomic swap operation. #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] @@ -159,11 +153,9 @@ where pub use pallet::*; -#[frame_support::pallet] +#[frame::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; /// Atomic swap's pallet configuration trait. #[pallet::config] diff --git a/substrate/frame/atomic-swap/src/tests.rs b/substrate/frame/atomic-swap/src/tests.rs index 47ebe6a8f0acf26021a84f37bf76d703aeba1374..d6384fab343d60c5e1326503651681b42d80fd68 100644 --- a/substrate/frame/atomic-swap/src/tests.rs +++ b/substrate/frame/atomic-swap/src/tests.rs @@ -19,13 +19,11 @@ use super::*; use crate as pallet_atomic_swap; - -use frame_support::{derive_impl, traits::ConstU32}; -use sp_runtime::BuildStorage; +use frame::testing_prelude::*; type Block = frame_system::mocking::MockBlock<Test>; -frame_support::construct_runtime!( +construct_runtime!( pub enum Test { System: frame_system, @@ -54,9 +52,12 @@ impl Config for Test { const A: u64 = 1; const B: u64 = 2; -pub fn new_test_ext() -> sp_io::TestExternalities { +pub fn new_test_ext() -> TestExternalities { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); - let genesis = pallet_balances::GenesisConfig::<Test> { balances: vec![(A, 100), (B, 200)] }; + let genesis = pallet_balances::GenesisConfig::<Test> { + balances: vec![(A, 100), (B, 200)], + ..Default::default() + }; genesis.assimilate_storage(&mut t).unwrap(); t.into() } diff --git a/substrate/frame/aura/Cargo.toml b/substrate/frame/aura/Cargo.toml index 94b057d665d412df64c4df7e8e8d06f25f3180d0..94a47e4d96cd6fb6241085d1c3f4e0c6881eb2be 100644 --- a/substrate/frame/aura/Cargo.toml +++ b/substrate/frame/aura/Cargo.toml @@ -17,11 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive", "max-encoded-len"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-timestamp = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-application-crypto = { workspace = true } sp-consensus-aura = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/authority-discovery/Cargo.toml b/substrate/frame/authority-discovery/Cargo.toml index 01f574a262ad3955dbcc7a9ff539a6ffab453b55..506c292c837b1d13edf37b759a706a1b12d3193d 100644 --- a/substrate/frame/authority-discovery/Cargo.toml +++ b/substrate/frame/authority-discovery/Cargo.toml @@ -19,12 +19,12 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-session = { features = [ "historical", ], workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-application-crypto = { workspace = true } sp-authority-discovery = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/authorship/Cargo.toml b/substrate/frame/authorship/Cargo.toml index 74a4a93147a8b606510f242b3b8ad6755dcf9952..f8b587d449090908a54a89c211ed7abeb6f5ae3a 100644 --- a/substrate/frame/authorship/Cargo.toml +++ b/substrate/frame/authorship/Cargo.toml @@ -19,10 +19,10 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -impl-trait-for-tuples = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +impl-trait-for-tuples = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } [dev-dependencies] diff --git a/substrate/frame/babe/Cargo.toml b/substrate/frame/babe/Cargo.toml index f0a7f4648c0a56821c31fe4602b3d952510bbfbf..8673e08472eb0400e291fa9eddc9137c9282942e 100644 --- a/substrate/frame/babe/Cargo.toml +++ b/substrate/frame/babe/Cargo.toml @@ -17,14 +17,14 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-authorship = { workspace = true } pallet-session = { workspace = true } pallet-timestamp = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } sp-application-crypto = { features = ["serde"], workspace = true } sp-consensus-babe = { features = ["serde"], workspace = true } sp-core = { features = ["serde"], workspace = true } diff --git a/substrate/frame/babe/src/mock.rs b/substrate/frame/babe/src/mock.rs index 23857470adc4a5c751ef276a4c2d72b34f078490..6f9f54cc7efcb627f0176200e525018ddbc6ea6a 100644 --- a/substrate/frame/babe/src/mock.rs +++ b/substrate/frame/babe/src/mock.rs @@ -157,6 +157,7 @@ impl onchain::Config for OnChainSeqPhragmen { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Test { + type OldCurrency = Balances; type Currency = Balances; type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; @@ -313,7 +314,7 @@ pub fn new_test_ext_raw_authorities(authorities: Vec<AuthorityId>) -> sp_io::Tes let balances: Vec<_> = (0..authorities.len()).map(|i| (i as u64, 10_000_000)).collect(); - pallet_balances::GenesisConfig::<Test> { balances } + pallet_balances::GenesisConfig::<Test> { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/bags-list/Cargo.toml b/substrate/frame/bags-list/Cargo.toml index 647f5d26686aa805a3a2632c23c9f63112598bf8..6b1c4809f77351837e8900e743dde195125d65c8 100644 --- a/substrate/frame/bags-list/Cargo.toml +++ b/substrate/frame/bags-list/Cargo.toml @@ -27,14 +27,14 @@ scale-info = { features = [ sp-runtime = { workspace = true } # FRAME +frame-election-provider-support = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -frame-election-provider-support = { workspace = true } # third party -log = { workspace = true } -docify = { workspace = true } aquamarine = { workspace = true } +docify = { workspace = true } +log = { workspace = true } # Optional imports for benchmarking frame-benchmarking = { optional = true, workspace = true } @@ -44,12 +44,12 @@ sp-io = { optional = true, workspace = true } sp-tracing = { optional = true, workspace = true } [dev-dependencies] +frame-benchmarking = { workspace = true, default-features = true } +frame-election-provider-support = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } -frame-election-provider-support = { workspace = true, default-features = true } -frame-benchmarking = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/bags-list/fuzzer/Cargo.toml b/substrate/frame/bags-list/fuzzer/Cargo.toml index b52fc88482376a590ac7fcf805aa705c8f3fdaba..db46bc6fe4465670b05076fb83d4cb056fb64e53 100644 --- a/substrate/frame/bags-list/fuzzer/Cargo.toml +++ b/substrate/frame/bags-list/fuzzer/Cargo.toml @@ -13,10 +13,10 @@ publish = false workspace = true [dependencies] -honggfuzz = { workspace = true } -rand = { features = ["small_rng", "std"], workspace = true, default-features = true } frame-election-provider-support = { features = ["fuzz"], workspace = true, default-features = true } +honggfuzz = { workspace = true } pallet-bags-list = { features = ["fuzz"], workspace = true, default-features = true } +rand = { features = ["small_rng", "std"], workspace = true, default-features = true } [[bin]] name = "bags-list" diff --git a/substrate/frame/bags-list/remote-tests/Cargo.toml b/substrate/frame/bags-list/remote-tests/Cargo.toml index 12d61b61c06d9802f2859a9f2efd95a2ae1f6f96..e3215803a02010e722873a39883197d466535886 100644 --- a/substrate/frame/bags-list/remote-tests/Cargo.toml +++ b/substrate/frame/bags-list/remote-tests/Cargo.toml @@ -17,18 +17,17 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # frame -pallet-staking = { workspace = true, default-features = true } -pallet-bags-list = { features = ["fuzz"], workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } frame-support = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } +pallet-bags-list = { features = ["fuzz"], workspace = true, default-features = true } +pallet-staking = { workspace = true, default-features = true } # core -sp-storage = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } +sp-storage = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } # utils remote-externalities = { workspace = true, default-features = true } diff --git a/substrate/frame/balances/Cargo.toml b/substrate/frame/balances/Cargo.toml index f0117555c37ea5c4ec3d3fee6741be421e58914a..4255ed414360159c4a6bd6572b7d7b01077bf0f3 100644 --- a/substrate/frame/balances/Cargo.toml +++ b/substrate/frame/balances/Cargo.toml @@ -17,20 +17,22 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive", "max-encoded-len"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } +docify = { workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +sp-core = { workspace = true } sp-runtime = { workspace = true } -docify = { workspace = true } [dev-dependencies] +frame-support = { features = [ + "experimental", +], workspace = true, default-features = true } pallet-transaction-payment = { workspace = true, default-features = true } -frame-support = { features = ["experimental"], workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } paste = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/balances/src/benchmarking.rs b/substrate/frame/balances/src/benchmarking.rs index c825300218d462a8465df2e80afda4e02c951cf3..a761f8e2af828ccbf4b4bedfdbe69bc09aba8f68 100644 --- a/substrate/frame/balances/src/benchmarking.rs +++ b/substrate/frame/balances/src/benchmarking.rs @@ -65,7 +65,12 @@ mod benchmarks { #[extrinsic_call] _(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount); - assert_eq!(Balances::<T, I>::free_balance(&caller), Zero::zero()); + if cfg!(feature = "insecure_zero_ed") { + assert_eq!(Balances::<T, I>::free_balance(&caller), balance - transfer_amount); + } else { + assert_eq!(Balances::<T, I>::free_balance(&caller), Zero::zero()); + } + assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount); } @@ -173,7 +178,12 @@ mod benchmarks { #[extrinsic_call] _(RawOrigin::Root, source_lookup, recipient_lookup, transfer_amount); - assert_eq!(Balances::<T, I>::free_balance(&source), Zero::zero()); + if cfg!(feature = "insecure_zero_ed") { + assert_eq!(Balances::<T, I>::free_balance(&source), balance - transfer_amount); + } else { + assert_eq!(Balances::<T, I>::free_balance(&source), Zero::zero()); + } + assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount); } @@ -208,7 +218,12 @@ mod benchmarks { #[extrinsic_call] transfer_allow_death(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount); - assert_eq!(Balances::<T, I>::free_balance(&caller), Zero::zero()); + if cfg!(feature = "insecure_zero_ed") { + assert_eq!(Balances::<T, I>::free_balance(&caller), balance - transfer_amount); + } else { + assert_eq!(Balances::<T, I>::free_balance(&caller), Zero::zero()); + } + assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount); } @@ -308,7 +323,7 @@ mod benchmarks { /// Benchmark `burn` extrinsic with the worst possible condition - burn kills the account. #[benchmark] fn burn_allow_death() { - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::<T, I>(); let caller = whitelisted_caller(); // Give some multiple of the existential deposit @@ -321,13 +336,17 @@ mod benchmarks { #[extrinsic_call] burn(RawOrigin::Signed(caller.clone()), burn_amount, false); - assert_eq!(Balances::<T, I>::free_balance(&caller), Zero::zero()); + if cfg!(feature = "insecure_zero_ed") { + assert_eq!(Balances::<T, I>::free_balance(&caller), balance - burn_amount); + } else { + assert_eq!(Balances::<T, I>::free_balance(&caller), Zero::zero()); + } } // Benchmark `burn` extrinsic with the case where account is kept alive. #[benchmark] fn burn_keep_alive() { - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::<T, I>(); let caller = whitelisted_caller(); // Give some multiple of the existential deposit diff --git a/substrate/frame/balances/src/impl_currency.rs b/substrate/frame/balances/src/impl_currency.rs index 23feb46b72ca02364ae05f1605cff5477b4234c7..f453b23420c400dddd01fe1013f358c3199c1ff2 100644 --- a/substrate/frame/balances/src/impl_currency.rs +++ b/substrate/frame/balances/src/impl_currency.rs @@ -632,7 +632,7 @@ where /// /// This is `Polite` and thus will not repatriate any funds which would lead the total balance /// to be less than the frozen amount. Returns `Ok` with the actual amount of funds moved, - /// which may be less than `value` since the operation is done an a `BestEffort` basis. + /// which may be less than `value` since the operation is done on a `BestEffort` basis. fn repatriate_reserved( slashed: &T::AccountId, beneficiary: &T::AccountId, @@ -674,8 +674,10 @@ where Reserves::<T, I>::try_mutate(who, |reserves| -> DispatchResult { match reserves.binary_search_by_key(id, |data| data.id) { Ok(index) => { - // this add can't overflow but just to be defensive. - reserves[index].amount = reserves[index].amount.defensive_saturating_add(value); + reserves[index].amount = reserves[index] + .amount + .checked_add(&value) + .ok_or(ArithmeticError::Overflow)?; }, Err(index) => { reserves diff --git a/substrate/frame/balances/src/lib.rs b/substrate/frame/balances/src/lib.rs index 9d74014521010e5aa6f9392eeb0a390f48999e16..e994f05a77c51dc97043700061835f9f8b1e2ca7 100644 --- a/substrate/frame/balances/src/lib.rs +++ b/substrate/frame/balances/src/lib.rs @@ -152,7 +152,11 @@ pub mod weights; extern crate alloc; -use alloc::vec::Vec; +use alloc::{ + format, + string::{String, ToString}, + vec::Vec, +}; use codec::{Codec, MaxEncodedLen}; use core::{cmp, fmt::Debug, mem, result}; use frame_support::{ @@ -173,6 +177,7 @@ use frame_support::{ use frame_system as system; pub use impl_currency::{NegativeImbalance, PositiveImbalance}; use scale_info::TypeInfo; +use sp_core::{sr25519::Pair as SrPair, Pair}; use sp_runtime::{ traits::{ AtLeast32BitUnsigned, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Saturating, @@ -180,6 +185,7 @@ use sp_runtime::{ }, ArithmeticError, DispatchError, FixedPointOperand, Perbill, RuntimeDebug, TokenError, }; + pub use types::{ AccountData, AdjustmentDirection, BalanceLock, DustCleaner, ExtraFlags, Reasons, ReserveData, }; @@ -189,6 +195,9 @@ pub use pallet::*; const LOG_TARGET: &str = "runtime::balances"; +// Default derivation(hard) for development accounts. +const DEFAULT_ADDRESS_URI: &str = "//Sender//{}"; + type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source; #[frame_support::pallet] @@ -505,11 +514,18 @@ pub mod pallet { #[pallet::genesis_config] pub struct GenesisConfig<T: Config<I>, I: 'static = ()> { pub balances: Vec<(T::AccountId, T::Balance)>, + /// Derived development accounts(Optional): + /// - `u32`: The number of development accounts to generate. + /// - `T::Balance`: The initial balance assigned to each development account. + /// - `String`: An optional derivation(hard) string template. + /// - Must include `{}` as a placeholder for account indices. + /// - Defaults to `"//Sender//{}`" if `None`. + pub dev_accounts: Option<(u32, T::Balance, Option<String>)>, } impl<T: Config<I>, I: 'static> Default for GenesisConfig<T, I> { fn default() -> Self { - Self { balances: Default::default() } + Self { balances: Default::default(), dev_accounts: None } } } @@ -540,6 +556,15 @@ pub mod pallet { "duplicate balances in genesis." ); + // Generate additional dev accounts. + if let Some((num_accounts, balance, ref derivation)) = self.dev_accounts { + // Using the provided derivation string or default to `"//Sender//{}`". + Pallet::<T, I>::derive_dev_account( + num_accounts, + balance, + derivation.as_deref().unwrap_or(DEFAULT_ADDRESS_URI), + ); + } for &(ref who, free) in self.balances.iter() { frame_system::Pallet::<T>::inc_providers(who); assert!(T::AccountStore::insert(who, AccountData { free, ..Default::default() }) @@ -1248,5 +1273,40 @@ pub mod pallet { }); Ok(actual) } + + /// Generate dev account from derivation(hard) string. + pub fn derive_dev_account(num_accounts: u32, balance: T::Balance, derivation: &str) { + // Ensure that the number of accounts is not zero. + assert!(num_accounts > 0, "num_accounts must be greater than zero"); + + assert!( + balance >= <T as Config<I>>::ExistentialDeposit::get(), + "the balance of any account should always be at least the existential deposit.", + ); + + assert!( + derivation.contains("{}"), + "Invalid derivation, expected `{{}}` as part of the derivation" + ); + + for index in 0..num_accounts { + // Replace "{}" in the derivation string with the index. + let derivation_string = derivation.replace("{}", &index.to_string()); + + // Generate the key pair from the derivation string using sr25519. + let pair: SrPair = Pair::from_string(&derivation_string, None) + .expect(&format!("Failed to parse derivation string: {derivation_string}")); + + // Convert the public key to AccountId. + let who = T::AccountId::decode(&mut &pair.public().encode()[..]) + .expect(&format!("Failed to decode public key from pair: {:?}", pair.public())); + + // Set the balance for the generated account. + Self::mutate_account_handling_dust(&who, |account| { + account.free = balance; + }) + .expect(&format!("Failed to add account to keystore: {:?}", who)); + } + } } } diff --git a/substrate/frame/balances/src/tests/currency_tests.rs b/substrate/frame/balances/src/tests/currency_tests.rs index 5ad818e5bfa2128292343af29b45339b3b32c104..0e5d7ccb46dee87d82bc3183d4c34922e00b0f7f 100644 --- a/substrate/frame/balances/src/tests/currency_tests.rs +++ b/substrate/frame/balances/src/tests/currency_tests.rs @@ -24,7 +24,7 @@ use frame_support::{ BalanceStatus::{Free, Reserved}, Currency, ExistenceRequirement::{self, AllowDeath, KeepAlive}, - Hooks, InspectLockableCurrency, LockIdentifier, LockableCurrency, NamedReservableCurrency, + InspectLockableCurrency, LockIdentifier, LockableCurrency, NamedReservableCurrency, ReservableCurrency, WithdrawReasons, }, StorageNoopGuard, @@ -721,7 +721,7 @@ fn burn_must_work() { fn cannot_set_genesis_value_below_ed() { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = 11); let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); - let _ = crate::GenesisConfig::<Test> { balances: vec![(1, 10)] } + let _ = crate::GenesisConfig::<Test> { balances: vec![(1, 10)], ..Default::default() } .assimilate_storage(&mut t) .unwrap(); } @@ -730,9 +730,12 @@ fn cannot_set_genesis_value_below_ed() { #[should_panic = "duplicate balances in genesis."] fn cannot_set_genesis_value_twice() { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); - let _ = crate::GenesisConfig::<Test> { balances: vec![(1, 10), (2, 20), (1, 15)] } - .assimilate_storage(&mut t) - .unwrap(); + let _ = crate::GenesisConfig::<Test> { + balances: vec![(1, 10), (2, 20), (1, 15)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); } #[test] @@ -1133,7 +1136,9 @@ fn operations_on_dead_account_should_not_change_state() { #[test] #[should_panic = "The existential deposit must be greater than zero!"] +#[cfg(not(feature = "insecure_zero_ed"))] fn zero_ed_is_prohibited() { + use frame_support::traits::Hooks; // These functions all use `mutate_account` which may introduce a storage change when // the account never existed to begin with, and shouldn't exist in the end. ExtBuilder::default().existential_deposit(0).build_and_execute_with(|| { diff --git a/substrate/frame/balances/src/tests/mod.rs b/substrate/frame/balances/src/tests/mod.rs index bf49ad9f0a1f08f12f16236d36b237c345c70842..ceb8e8134f0a29cbaae72291c194f241246880aa 100644 --- a/substrate/frame/balances/src/tests/mod.rs +++ b/substrate/frame/balances/src/tests/mod.rs @@ -19,7 +19,10 @@ #![cfg(test)] -use crate::{self as pallet_balances, AccountData, Config, CreditOf, Error, Pallet, TotalIssuance}; +use crate::{ + self as pallet_balances, AccountData, Config, CreditOf, Error, Pallet, TotalIssuance, + DEFAULT_ADDRESS_URI, +}; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ assert_err, assert_noop, assert_ok, assert_storage_noop, derive_impl, @@ -34,7 +37,7 @@ use frame_support::{ use frame_system::{self as system, RawOrigin}; use pallet_transaction_payment::{ChargeTransactionPayment, FungibleAdapter, Multiplier}; use scale_info::TypeInfo; -use sp_core::hexdisplay::HexDisplay; +use sp_core::{hexdisplay::HexDisplay, sr25519::Pair as SrPair, Pair}; use sp_io; use sp_runtime::{ traits::{BadOrigin, Zero}, @@ -169,6 +172,11 @@ impl ExtBuilder { } else { vec![] }, + dev_accounts: Some(( + 1000, + self.existential_deposit, + Some(DEFAULT_ADDRESS_URI.to_string()), + )), } .assimilate_storage(&mut t) .unwrap(); @@ -281,7 +289,32 @@ pub fn info_from_weight(w: Weight) -> DispatchInfo { pub fn ensure_ti_valid() { let mut sum = 0; + // Fetch the dev accounts from Account Storage. + let dev_accounts = (1000, EXISTENTIAL_DEPOSIT, DEFAULT_ADDRESS_URI.to_string()); + let (num_accounts, _balance, ref derivation) = dev_accounts; + + // Generate the dev account public keys. + let dev_account_ids: Vec<_> = (0..num_accounts) + .map(|index| { + let derivation_string = derivation.replace("{}", &index.to_string()); + let pair: SrPair = + Pair::from_string(&derivation_string, None).expect("Invalid derivation string"); + <crate::tests::Test as frame_system::Config>::AccountId::decode( + &mut &pair.public().encode()[..], + ) + .unwrap() + }) + .collect(); + + // Iterate over all account keys (i.e., the account IDs). for acc in frame_system::Account::<Test>::iter_keys() { + // Skip dev accounts by checking if the account is in the dev_account_ids list. + // This also proves dev_accounts exists in storage. + if dev_account_ids.contains(&acc) { + continue; + } + + // Check if we are using the system pallet or some other custom storage for accounts. if UseSystem::get() { let data = frame_system::Pallet::<Test>::account(acc); sum += data.data.total(); @@ -291,7 +324,8 @@ pub fn ensure_ti_valid() { } } - assert_eq!(TotalIssuance::<Test>::get(), sum, "Total Issuance wrong"); + // Ensure the total issuance matches the sum of the account balances + assert_eq!(TotalIssuance::<Test>::get(), sum, "Total Issuance is incorrect"); } #[test] diff --git a/substrate/frame/beefy-mmr/Cargo.toml b/substrate/frame/beefy-mmr/Cargo.toml index d67ac20ee922bc299956d8201a1552b2651c9527..54343bb9ce51a9695f4dd489d8eaf596fd6c8d8e 100644 --- a/substrate/frame/beefy-mmr/Cargo.toml +++ b/substrate/frame/beefy-mmr/Cargo.toml @@ -13,22 +13,22 @@ workspace = true [dependencies] array-bytes = { optional = true, workspace = true, default-features = true } -codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { optional = true, workspace = true, default-features = true } binary-merkle-tree = { workspace = true } +codec = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-beefy = { workspace = true } pallet-mmr = { workspace = true } pallet-session = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } +sp-api = { workspace = true } sp-consensus-beefy = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -sp-api = { workspace = true } sp-state-machine = { workspace = true } [dev-dependencies] diff --git a/substrate/frame/beefy-mmr/src/benchmarking.rs b/substrate/frame/beefy-mmr/src/benchmarking.rs index fea6a2078f0f1b1fd50b4e29e9d14abcb2c5b5fa..4fddd1bccf115c351a9ffe52519f2828fdcad4e9 100644 --- a/substrate/frame/beefy-mmr/src/benchmarking.rs +++ b/substrate/frame/beefy-mmr/src/benchmarking.rs @@ -49,6 +49,24 @@ fn init_block<T: Config>(block_num: u32) { mod benchmarks { use super::*; + /// Generate ancestry proofs with `n` leafs and benchmark the logic that checks + /// if the proof is optimal. + #[benchmark] + fn n_leafs_proof_is_optimal(n: Linear<2, 512>) { + pallet_mmr::UseLocalStorage::<T>::set(true); + + for block_num in 1..=n { + init_block::<T>(block_num); + } + let proof = Mmr::<T>::generate_mock_ancestry_proof().unwrap(); + assert_eq!(proof.leaf_count, n as u64); + + #[block] + { + <BeefyMmr<T> as AncestryHelper<HeaderFor<T>>>::is_proof_optimal(&proof); + }; + } + #[benchmark] fn extract_validation_context() { pallet_mmr::UseLocalStorage::<T>::set(true); diff --git a/substrate/frame/beefy-mmr/src/lib.rs b/substrate/frame/beefy-mmr/src/lib.rs index ef99bc1e9cf11821718085804b50f25b80e5e8f0..c7fcdeff87999798fac8b2f4005725b853aa0e47 100644 --- a/substrate/frame/beefy-mmr/src/lib.rs +++ b/substrate/frame/beefy-mmr/src/lib.rs @@ -210,6 +210,18 @@ where .ok() } + fn is_proof_optimal(proof: &Self::Proof) -> bool { + let is_proof_optimal = pallet_mmr::Pallet::<T>::is_ancestry_proof_optimal(proof); + + // We don't check the proof size when running benchmarks, since we use mock proofs + // which would cause the test to fail. + if cfg!(feature = "runtime-benchmarks") { + return true + } + + is_proof_optimal + } + fn extract_validation_context(header: HeaderFor<T>) -> Option<Self::ValidationContext> { // Check if the provided header is canonical. let expected_hash = frame_system::Pallet::<T>::block_hash(header.number()); @@ -292,6 +304,10 @@ impl<T: Config> AncestryHelperWeightInfo<HeaderFor<T>> for Pallet<T> where T: pallet_mmr::Config<Hashing = sp_consensus_beefy::MmrHashing>, { + fn is_proof_optimal(proof: &<Self as AncestryHelper<HeaderFor<T>>>::Proof) -> Weight { + <T as Config>::WeightInfo::n_leafs_proof_is_optimal(proof.leaf_count.saturated_into()) + } + fn extract_validation_context() -> Weight { <T as Config>::WeightInfo::extract_validation_context() } diff --git a/substrate/frame/beefy-mmr/src/weights.rs b/substrate/frame/beefy-mmr/src/weights.rs index dcfdb560ee94983ab95f8e74d1433e739403bf9e..5f7f7055311cdecda3f2917141cf1abbad98d7da 100644 --- a/substrate/frame/beefy-mmr/src/weights.rs +++ b/substrate/frame/beefy-mmr/src/weights.rs @@ -51,6 +51,7 @@ use core::marker::PhantomData; /// Weight functions needed for `pallet_beefy_mmr`. pub trait WeightInfo { + fn n_leafs_proof_is_optimal(n: u32, ) -> Weight; fn extract_validation_context() -> Weight; fn read_peak() -> Weight; fn n_items_proof_is_non_canonical(n: u32, ) -> Weight; @@ -59,25 +60,38 @@ pub trait WeightInfo { /// Weights for `pallet_beefy_mmr` using the Substrate node and recommended hardware. pub struct SubstrateWeight<T>(PhantomData<T>); impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { + /// The range of component `n` is `[2, 512]`. + fn n_leafs_proof_is_optimal(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 622_000 picoseconds. + Weight::from_parts(1_166_954, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 65 + .saturating_add(Weight::from_parts(1_356, 0).saturating_mul(n.into())) + } /// 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: `68` // Estimated: `3509` - // Minimum execution time: 6_687_000 picoseconds. - Weight::from_parts(6_939_000, 3509) - .saturating_add(T::DbWeight::get().reads(1_u64)) + // Minimum execution time: 6_272_000 picoseconds. + Weight::from_parts(6_452_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: `386` + // Measured: `254` // Estimated: `3505` - // Minimum execution time: 10_409_000 picoseconds. - Weight::from_parts(10_795_000, 3505) - .saturating_add(T::DbWeight::get().reads(1_u64)) + // Minimum execution time: 6_576_000 picoseconds. + Weight::from_parts(6_760_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`) @@ -86,37 +100,51 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `n` is `[2, 512]`. fn n_items_proof_is_non_canonical(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `378` + // Measured: `246` // Estimated: `1517` - // Minimum execution time: 15_459_000 picoseconds. - Weight::from_parts(21_963_366, 1517) - // Standard Error: 1_528 - .saturating_add(Weight::from_parts(984_907, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Minimum execution time: 12_538_000 picoseconds. + Weight::from_parts(24_516_023, 0) + .saturating_add(Weight::from_parts(0, 1517)) + // Standard Error: 1_923 + .saturating_add(Weight::from_parts(1_426_781, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(2)) } } // For backwards compatibility and tests. impl WeightInfo for () { + /// The range of component `n` is `[2, 512]`. + fn n_leafs_proof_is_optimal(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 622_000 picoseconds. + Weight::from_parts(1_166_954, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 65 + .saturating_add(Weight::from_parts(1_356, 0).saturating_mul(n.into())) + } /// 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: `68` // Estimated: `3509` - // Minimum execution time: 6_687_000 picoseconds. - Weight::from_parts(6_939_000, 3509) - .saturating_add(RocksDbWeight::get().reads(1_u64)) + // Minimum execution time: 6_272_000 picoseconds. + Weight::from_parts(6_452_000, 0) + .saturating_add(Weight::from_parts(0, 3509)) + .saturating_add(RocksDbWeight::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: `386` + // Measured: `254` // Estimated: `3505` - // Minimum execution time: 10_409_000 picoseconds. - Weight::from_parts(10_795_000, 3505) - .saturating_add(RocksDbWeight::get().reads(1_u64)) + // Minimum execution time: 6_576_000 picoseconds. + Weight::from_parts(6_760_000, 0) + .saturating_add(Weight::from_parts(0, 3505)) + .saturating_add(RocksDbWeight::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`) @@ -125,12 +153,13 @@ impl WeightInfo for () { /// The range of component `n` is `[2, 512]`. fn n_items_proof_is_non_canonical(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `378` + // Measured: `246` // Estimated: `1517` - // Minimum execution time: 15_459_000 picoseconds. - Weight::from_parts(21_963_366, 1517) - // Standard Error: 1_528 - .saturating_add(Weight::from_parts(984_907, 0).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(2_u64)) + // Minimum execution time: 12_538_000 picoseconds. + Weight::from_parts(24_516_023, 0) + .saturating_add(Weight::from_parts(0, 1517)) + // Standard Error: 1_923 + .saturating_add(Weight::from_parts(1_426_781, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(2)) } } diff --git a/substrate/frame/beefy/Cargo.toml b/substrate/frame/beefy/Cargo.toml index 05af974e89a703408fead24adb16a00da272fec9..b8e952dfbd66db87bb49579ba6dcebc0d779ee72 100644 --- a/substrate/frame/beefy/Cargo.toml +++ b/substrate/frame/beefy/Cargo.toml @@ -13,13 +13,13 @@ workspace = true [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } -serde = { optional = true, workspace = true, default-features = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-authorship = { workspace = true } pallet-session = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } sp-consensus-beefy = { features = ["serde"], workspace = true } sp-runtime = { features = ["serde"], workspace = true } sp-session = { workspace = true } diff --git a/substrate/frame/beefy/src/equivocation.rs b/substrate/frame/beefy/src/equivocation.rs index 3a49b9e169ce904d0185408fa8095d933ac730ad..294d64427ef8a62ba02e55ab62e9c0af9f2486d4 100644 --- a/substrate/frame/beefy/src/equivocation.rs +++ b/substrate/frame/beefy/src/equivocation.rs @@ -207,11 +207,17 @@ impl<T: Config> EquivocationEvidenceFor<T> { return Err(Error::<T>::InvalidDoubleVotingProof); } - return Ok(()) + Ok(()) }, EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, _) => { let ForkVotingProof { vote, ancestry_proof, header } = equivocation_proof; + if !<T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::is_proof_optimal( + &ancestry_proof, + ) { + return Err(Error::<T>::InvalidForkVotingProof); + } + let maybe_validation_context = <T::AncestryHelper as AncestryHelper< HeaderFor<T>, >>::extract_validation_context(header); diff --git a/substrate/frame/beefy/src/lib.rs b/substrate/frame/beefy/src/lib.rs index cf690a9df339dcd3e93ad3886a84ba3da2085f8c..e57fc0e21bc1eb99d594d3df9a24c1d33e0a82ff 100644 --- a/substrate/frame/beefy/src/lib.rs +++ b/substrate/frame/beefy/src/lib.rs @@ -755,7 +755,8 @@ pub(crate) trait WeightInfoExt: WeightInfo { max_nominators_per_validator: u32, ancestry_proof: &<T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof, ) -> Weight { - let _weight = <T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::extract_validation_context() + <T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::is_proof_optimal(&ancestry_proof) + .saturating_add(<T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::extract_validation_context()) .saturating_add( <T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::is_non_canonical( ancestry_proof, @@ -765,12 +766,7 @@ pub(crate) trait WeightInfoExt: WeightInfo { 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( diff --git a/substrate/frame/beefy/src/mock.rs b/substrate/frame/beefy/src/mock.rs index 7ae41c609180e4741c668372c2c2392bbb140808..fc731e3bc50e9957d287593db923759158e9ac49 100644 --- a/substrate/frame/beefy/src/mock.rs +++ b/substrate/frame/beefy/src/mock.rs @@ -99,6 +99,7 @@ pub struct MockAncestryProofContext { #[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)] pub struct MockAncestryProof { + pub is_optimal: bool, pub is_non_canonical: bool, } @@ -128,6 +129,10 @@ impl<Header: HeaderT> AncestryHelper<Header> for MockAncestryHelper { unimplemented!() } + fn is_proof_optimal(proof: &Self::Proof) -> bool { + proof.is_optimal + } + fn extract_validation_context(_header: Header) -> Option<Self::ValidationContext> { AncestryProofContext::get() } @@ -142,6 +147,10 @@ impl<Header: HeaderT> AncestryHelper<Header> for MockAncestryHelper { } impl<Header: HeaderT> AncestryHelperWeightInfo<Header> for MockAncestryHelper { + fn is_proof_optimal(_proof: &<Self as AncestryHelper<HeaderFor<Test>>>::Proof) -> Weight { + unimplemented!() + } + fn extract_validation_context() -> Weight { unimplemented!() } @@ -235,6 +244,7 @@ impl onchain::Config for OnChainSeqPhragmen { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Test { type RuntimeEvent = RuntimeEvent; + type OldCurrency = Balances; type Currency = Balances; type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>; type SessionInterface = Self; @@ -272,7 +282,7 @@ impl ExtBuilder { let balances: Vec<_> = (0..self.authorities.len()).map(|i| (i as u64, 10_000_000)).collect(); - pallet_balances::GenesisConfig::<Test> { balances } + pallet_balances::GenesisConfig::<Test> { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/beefy/src/tests.rs b/substrate/frame/beefy/src/tests.rs index 89645d21f6baaa64c08a560668e1ef30f745b4e7..1bd0a72b25ecd72cd4477432e025b6bb5fce3870 100644 --- a/substrate/frame/beefy/src/tests.rs +++ b/substrate/frame/beefy/src/tests.rs @@ -799,7 +799,7 @@ fn report_fork_voting( let payload = Payload::from_single_entry(MMR_ROOT_ID, vec![42]); let equivocation_proof = generate_fork_voting_proof( (block_num, payload, set_id, &equivocation_keyring), - MockAncestryProof { is_non_canonical: true }, + MockAncestryProof { is_optimal: true, is_non_canonical: true }, System::finalize(), ); @@ -835,6 +835,54 @@ fn report_fork_voting_invalid_key_owner_proof() { report_equivocation_invalid_key_owner_proof(report_fork_voting); } +#[test] +fn report_fork_voting_non_optimal_equivocation_proof() { + let authorities = test_authorities(); + + let mut ext = ExtBuilder::default().add_authorities(authorities).build(); + + let mut era = 1; + let (block_num, set_id, equivocation_keyring, key_owner_proof) = ext.execute_with(|| { + start_era(era); + let block_num = System::block_number(); + + let validator_set = Beefy::validator_set().unwrap(); + let authorities = validator_set.validators(); + let set_id = validator_set.id(); + + let equivocation_authority_index = 0; + let equivocation_key = &authorities[equivocation_authority_index]; + let equivocation_keyring = BeefyKeyring::from_public(equivocation_key).unwrap(); + + // generate a key ownership proof at set id in era 1 + let key_owner_proof = Historical::prove((BEEFY_KEY_TYPE, &equivocation_key)).unwrap(); + + era += 1; + start_era(era); + (block_num, set_id, equivocation_keyring, key_owner_proof) + }); + ext.persist_offchain_overlay(); + + ext.execute_with(|| { + let payload = Payload::from_single_entry(MMR_ROOT_ID, vec![42]); + + // Simulate non optimal equivocation proof. + let equivocation_proof = generate_fork_voting_proof( + (block_num + 1, payload.clone(), set_id, &equivocation_keyring), + MockAncestryProof { is_optimal: false, is_non_canonical: true }, + System::finalize(), + ); + assert_err!( + Beefy::report_fork_voting_unsigned( + RuntimeOrigin::none(), + Box::new(equivocation_proof), + key_owner_proof.clone(), + ), + Error::<Test>::InvalidForkVotingProof, + ); + }); +} + #[test] fn report_fork_voting_invalid_equivocation_proof() { let authorities = test_authorities(); @@ -869,7 +917,7 @@ fn report_fork_voting_invalid_equivocation_proof() { // vote signed with a key that isn't part of the authority set let equivocation_proof = generate_fork_voting_proof( (block_num, payload.clone(), set_id, &BeefyKeyring::Dave), - MockAncestryProof { is_non_canonical: true }, + MockAncestryProof { is_optimal: true, is_non_canonical: true }, System::finalize(), ); assert_err!( @@ -884,7 +932,7 @@ fn report_fork_voting_invalid_equivocation_proof() { // Simulate InvalidForkVotingProof error. let equivocation_proof = generate_fork_voting_proof( (block_num + 1, payload.clone(), set_id, &equivocation_keyring), - MockAncestryProof { is_non_canonical: false }, + MockAncestryProof { is_optimal: true, is_non_canonical: false }, System::finalize(), ); assert_err!( @@ -945,7 +993,7 @@ fn report_fork_voting_invalid_context() { // different payload than finalized let equivocation_proof = generate_fork_voting_proof( (block_num, payload, set_id, &equivocation_keyring), - MockAncestryProof { is_non_canonical: true }, + MockAncestryProof { is_optimal: true, is_non_canonical: true }, System::finalize(), ); diff --git a/substrate/frame/benchmarking/Cargo.toml b/substrate/frame/benchmarking/Cargo.toml index 0c74d94b33b8938e0594c21d52631a75f99e895f..fabeb9a03195a36f7912850bce08022755b7ed98 100644 --- a/substrate/frame/benchmarking/Cargo.toml +++ b/substrate/frame/benchmarking/Cargo.toml @@ -17,14 +17,14 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } +frame-support = { workspace = true } +frame-support-procedural = { workspace = true } +frame-system = { workspace = true } linregress = { optional = true, workspace = true } log = { workspace = true } paste = { workspace = true, default-features = true } scale-info = { features = ["derive"], workspace = true } serde = { optional = true, workspace = true, default-features = true } -frame-support = { workspace = true } -frame-support-procedural = { workspace = true } -frame-system = { workspace = true } sp-api = { workspace = true } sp-application-crypto = { workspace = true } sp-core = { workspace = true } @@ -37,10 +37,10 @@ static_assertions = { workspace = true, default-features = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } rusty-fork = { workspace = true } -sp-keystore = { workspace = true, default-features = true } sc-client-db = { workspace = true } -sp-state-machine = { workspace = true } sp-externalities = { workspace = true } +sp-keystore = { workspace = true, default-features = true } +sp-state-machine = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/benchmarking/pov/Cargo.toml b/substrate/frame/benchmarking/pov/Cargo.toml index ce89dceed3c3f2d6ee89e2c4e9ab5389b5c6f19b..47c6d6e5e4bc8109d6088eb80c44377bb4cc772d 100644 --- a/substrate/frame/benchmarking/pov/Cargo.toml +++ b/substrate/frame/benchmarking/pov/Cargo.toml @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/benchmarking/src/v1.rs b/substrate/frame/benchmarking/src/v1.rs index 64f93b22cf1b50c1f463b5e48f5c51cc3e3b719b..99aad0301c12760c8c75881b7c2f2dac84c0767d 100644 --- a/substrate/frame/benchmarking/src/v1.rs +++ b/substrate/frame/benchmarking/src/v1.rs @@ -1894,7 +1894,7 @@ macro_rules! add_benchmark { /// This macro allows users to easily generate a list of benchmarks for the pallets configured /// in the runtime. /// -/// To use this macro, first create a an object to store the list: +/// To use this macro, first create an object to store the list: /// /// ```ignore /// let mut list = Vec::<BenchmarkList>::new(); diff --git a/substrate/frame/bounties/Cargo.toml b/substrate/frame/bounties/Cargo.toml index a272153fed07d46c5209eeac8def20333aa46a42..926af60d1acb3283317f429b3cd55d5346bd226c 100644 --- a/substrate/frame/bounties/Cargo.toml +++ b/substrate/frame/bounties/Cargo.toml @@ -19,12 +19,12 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-treasury = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/bounties/README.md b/substrate/frame/bounties/README.md index 232334cb1edd64ea8aab141055457c494a352073..2293ae161e28e947daab9685ef35d35ebdacb1f5 100644 --- a/substrate/frame/bounties/README.md +++ b/substrate/frame/bounties/README.md @@ -19,7 +19,7 @@ curator or once the bounty is active or payout is pending, resulting in the slas curator's deposit. This pallet may opt into using a [`ChildBountyManager`] that enables bounties to be split into -sub-bounties, as children of anh established bounty (called the parent in the context of it's +sub-bounties, as children of an established bounty (called the parent in the context of it's children). > NOTE: The parent bounty cannot be closed if it has a non-zero number of it has active child diff --git a/substrate/frame/bounties/src/benchmarking.rs b/substrate/frame/bounties/src/benchmarking.rs index 1e931958898da8820a590fcb76014f1534620f32..b5155909e3cde0f6702bdb2174d7fea4d06cdede 100644 --- a/substrate/frame/bounties/src/benchmarking.rs +++ b/substrate/frame/bounties/src/benchmarking.rs @@ -15,9 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! bounties pallet benchmarking. - -#![cfg(feature = "runtime-benchmarks")] +//! Bounties pallet benchmarking. use super::*; @@ -37,6 +35,16 @@ fn set_block_number<T: Config<I>, I: 'static>(n: BlockNumberFor<T, I>) { <T as pallet_treasury::Config<I>>::BlockNumberProvider::set_block_number(n); } +fn minimum_balance<T: Config<I>, I: 'static>() -> BalanceOf<T, I> { + let minimum_balance = T::Currency::minimum_balance(); + + if minimum_balance.is_zero() { + 1u32.into() + } else { + minimum_balance + } +} + // Create bounties that are approved for use in `on_initialize`. fn create_approved_bounties<T: Config<I>, I: 'static>(n: u32) -> Result<(), BenchmarkError> { for i in 0..n { @@ -62,12 +70,10 @@ fn setup_bounty<T: Config<I>, I: 'static>( let fee = value / 2u32.into(); let deposit = T::BountyDepositBase::get() + T::DataDepositPerByte::get() * T::MaximumReasonLength::get().into(); - let _ = T::Currency::make_free_balance_be(&caller, deposit + T::Currency::minimum_balance()); + let _ = T::Currency::make_free_balance_be(&caller, deposit + minimum_balance::<T, I>()); let curator = account("curator", u, SEED); - let _ = T::Currency::make_free_balance_be( - &curator, - fee / 2u32.into() + T::Currency::minimum_balance(), - ); + let _ = + T::Currency::make_free_balance_be(&curator, fee / 2u32.into() + minimum_balance::<T, I>()); let reason = vec![0; d as usize]; (caller, curator, fee, value, reason) } @@ -91,7 +97,7 @@ fn create_bounty<T: Config<I>, I: 'static>( fn setup_pot_account<T: Config<I>, I: 'static>() { let pot_account = Bounties::<T, I>::account_id(); - let value = T::Currency::minimum_balance().saturating_mul(1_000_000_000u32.into()); + let value = minimum_balance::<T, I>().saturating_mul(1_000_000_000u32.into()); let _ = T::Currency::make_free_balance_be(&pot_account, value); } diff --git a/substrate/frame/bounties/src/lib.rs b/substrate/frame/bounties/src/lib.rs index 729c76b5cc750752ee6cd9d9f1906471fa29e7f8..9b6e3c06e91419f3b584ff4e87dfcbf087a2857a 100644 --- a/substrate/frame/bounties/src/lib.rs +++ b/substrate/frame/bounties/src/lib.rs @@ -36,7 +36,7 @@ //! curator's deposit. //! //! This pallet may opt into using a [`ChildBountyManager`] that enables bounties to be split into -//! sub-bounties, as children of anh established bounty (called the parent in the context of it's +//! sub-bounties, as children of an established bounty (called the parent in the context of it's //! children). //! //! > NOTE: The parent bounty cannot be closed if it has a non-zero number of it has active child @@ -84,6 +84,7 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "runtime-benchmarks")] mod benchmarking; pub mod migrations; mod tests; diff --git a/substrate/frame/bounties/src/tests.rs b/substrate/frame/bounties/src/tests.rs index 447d0edb4122d528a7f002e52befe786782d9f1d..c9f6c1319ed1ef2035bfcab4b7b2cd74c505c5d2 100644 --- a/substrate/frame/bounties/src/tests.rs +++ b/substrate/frame/bounties/src/tests.rs @@ -187,7 +187,10 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { system: frame_system::GenesisConfig::default(), - balances: pallet_balances::GenesisConfig { balances: vec![(0, 100), (1, 98), (2, 1)] }, + balances: pallet_balances::GenesisConfig { + balances: vec![(0, 100), (1, 98), (2, 1)], + ..Default::default() + }, treasury: Default::default(), treasury_1: Default::default(), } @@ -338,9 +341,12 @@ fn treasury_account_doesnt_get_deleted() { #[allow(deprecated)] fn inexistent_account_works() { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); - pallet_balances::GenesisConfig::<Test> { balances: vec![(0, 100), (1, 99), (2, 1)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig::<Test> { + balances: vec![(0, 100), (1, 99), (2, 1)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); // Treasury genesis config is not build thus treasury account does not exist let mut t: sp_io::TestExternalities = t.into(); @@ -977,6 +983,7 @@ fn genesis_funding_works() { pallet_balances::GenesisConfig::<Test> { // Total issuance will be 200 with treasury account initialized with 100. balances: vec![(0, 100), (Treasury::account_id(), initial_funding)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/broker/Cargo.toml b/substrate/frame/broker/Cargo.toml index aead49013ef068a3ec1b31404bab06ca07ede63a..a4cfe49d3b354a6300b5ec9a1a78b6f958757e24 100644 --- a/substrate/frame/broker/Cargo.toml +++ b/substrate/frame/broker/Cargo.toml @@ -15,22 +15,22 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { workspace = true } +bitvec = { workspace = true } codec = { features = ["derive"], workspace = true } +frame-benchmarking = { optional = true, workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +log = { workspace = true } scale-info = { features = ["derive"], workspace = true } -bitvec = { workspace = true } sp-api = { workspace = true } sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } [dev-dependencies] +pretty_assertions = { workspace = true } sp-io = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -pretty_assertions = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/broker/src/benchmarking.rs b/substrate/frame/broker/src/benchmarking.rs index 9ef9b1254435fa5427bddbafc8d11de62f3d29ce..516518740f7d03db946e8aec1f33595f8b894923 100644 --- a/substrate/frame/broker/src/benchmarking.rs +++ b/substrate/frame/broker/src/benchmarking.rs @@ -30,11 +30,11 @@ use frame_support::{ }, }; use frame_system::{Pallet as System, RawOrigin}; -use sp_arithmetic::{traits::Zero, Perbill}; +use sp_arithmetic::Perbill; use sp_core::Get; use sp_runtime::{ traits::{BlockNumberProvider, MaybeConvert}, - SaturatedConversion, Saturating, + Saturating, }; const SEED: u32 = 0; @@ -287,7 +287,7 @@ mod benches { ); let region = Broker::<T>::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); Broker::<T>::do_assign(region, None, 1001, Final) .map_err(|_| BenchmarkError::Weightless)?; @@ -316,7 +316,7 @@ mod benches { ); let region = Broker::<T>::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); let recipient: T::AccountId = account("recipient", 0, SEED); @@ -349,7 +349,7 @@ mod benches { ); let region = Broker::<T>::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); #[extrinsic_call] _(RawOrigin::Signed(caller), region, 2); @@ -381,7 +381,7 @@ mod benches { ); let region = Broker::<T>::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); #[extrinsic_call] _(RawOrigin::Signed(caller), region, 0x00000_fffff_fffff_00000.into()); @@ -417,7 +417,7 @@ mod benches { ); let region = Broker::<T>::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); #[extrinsic_call] _(RawOrigin::Signed(caller), region, 1000, Provisional); @@ -452,7 +452,7 @@ mod benches { ); let region = Broker::<T>::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); let recipient: T::AccountId = account("recipient", 0, SEED); @@ -492,7 +492,7 @@ mod benches { ); let region = Broker::<T>::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); let recipient: T::AccountId = account("recipient", 0, SEED); T::Currency::set_balance(&recipient.clone(), T::Currency::minimum_balance()); @@ -548,7 +548,7 @@ mod benches { T::Currency::set_balance(&Broker::<T>::account_id(), T::Currency::minimum_balance()); let region = Broker::<T>::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); let recipient: T::AccountId = account("recipient", 0, SEED); @@ -582,7 +582,7 @@ mod benches { ); let region = Broker::<T>::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); advance_to::<T>( (T::TimeslicePeriod::get() * (region_len * 4).into()).try_into().ok().unwrap(), @@ -616,7 +616,7 @@ mod benches { ); let region = Broker::<T>::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); let recipient: T::AccountId = account("recipient", 0, SEED); @@ -786,80 +786,97 @@ mod benches { #[benchmark] fn rotate_sale(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> { - let core_count = n.try_into().unwrap(); let config = new_config_record::<T>(); + Configuration::<T>::put(config.clone()); - let now = RCBlockNumberProviderOf::<T::Coretime>::current_block_number(); - let end_price = 10_000_000u32.into(); - let commit_timeslice = Broker::<T>::latest_timeslice_ready_to_commit(&config); - let sale = SaleInfoRecordOf::<T> { - sale_start: now, - leadin_length: Zero::zero(), - end_price, - sellout_price: None, - region_begin: commit_timeslice, - region_end: commit_timeslice.saturating_add(config.region_length), - first_core: 0, - ideal_cores_sold: 0, - cores_offered: 0, - cores_sold: 0, - }; - - let status = StatusRecord { - core_count, - private_pool_size: 0, - system_pool_size: 0, - last_committed_timeslice: commit_timeslice.saturating_sub(1), - last_timeslice: Broker::<T>::current_timeslice(), - }; + // Ensure there is one buyable core then use the rest to max out reservations and leases, if + // possible for worst case. + + // First allocate up to MaxReservedCores for reservations + let n_reservations = T::MaxReservedCores::get().min(n.saturating_sub(1)); + setup_reservations::<T>(n_reservations); + // Then allocate remaining cores to leases, up to MaxLeasedCores + let n_leases = + T::MaxLeasedCores::get().min(n.saturating_sub(1).saturating_sub(n_reservations)); + setup_leases::<T>(n_leases, 1, 20); + + // Start sales so we can test the auto-renewals. + Broker::<T>::do_start_sales( + 10_000_000u32.into(), + n.saturating_sub(n_reservations) + .saturating_sub(n_leases) + .try_into() + .expect("Upper limit of n is a u16."), + ) + .expect("Configuration was initialized before; qed"); + + // Advance to the fixed price period. + advance_to::<T>(2); - // Assume Reservations to be filled for worst case - setup_reservations::<T>(T::MaxReservedCores::get()); + // Assume max auto renewals for worst case. This is between 1 and the value of + // MaxAutoRenewals. + let n_renewable = T::MaxAutoRenewals::get() + .min(n.saturating_sub(n_leases).saturating_sub(n_reservations)); - // Assume Leases to be filled for worst case - setup_leases::<T>(T::MaxLeasedCores::get(), 1, 10); + let timeslice_period: u32 = T::TimeslicePeriod::get().try_into().ok().unwrap(); + let sale = SaleInfo::<T>::get().expect("Sale has started."); - // Assume max auto renewals for worst case. - (0..T::MaxAutoRenewals::get()).try_for_each(|indx| -> Result<(), BenchmarkError> { + (0..n_renewable.into()).try_for_each(|indx| -> Result<(), BenchmarkError> { let task = 1000 + indx; let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task) .expect("Failed to get sovereign account"); T::Currency::set_balance( &caller.clone(), - T::Currency::minimum_balance().saturating_add(100u32.into()), + T::Currency::minimum_balance().saturating_add(100_000_000u32.into()), ); - let region = Broker::<T>::do_purchase(caller.clone(), 10u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + let region = Broker::<T>::do_purchase(caller.clone(), 10_000_000u32.into()) + .expect("Offer not high enough for configuration."); Broker::<T>::do_assign(region, None, task, Final) .map_err(|_| BenchmarkError::Weightless)?; - Broker::<T>::do_enable_auto_renew(caller, region.core, task, None)?; + Broker::<T>::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?; Ok(()) })?; + // Advance to the block before the rotate_sale in which the auto-renewals will take place. + let rotate_block = timeslice_period.saturating_mul(config.region_length) - 2; + advance_to::<T>(rotate_block - 1); + + // Advance one block and manually tick so we can isolate the `rotate_sale` call. + System::<T>::set_block_number(rotate_block.into()); + RCBlockNumberProviderOf::<T::Coretime>::set_block_number(rotate_block.into()); + let mut status = Status::<T>::get().expect("Sale has started."); + let sale = SaleInfo::<T>::get().expect("Sale has started."); + Broker::<T>::process_core_count(&mut status); + Broker::<T>::process_revenue(); + status.last_committed_timeslice = config.region_length; + #[block] { Broker::<T>::rotate_sale(sale.clone(), &config, &status); } - assert!(SaleInfo::<T>::get().is_some()); - let sale_start = RCBlockNumberProviderOf::<T::Coretime>::current_block_number() + - config.interlude_length; - assert_last_event::<T>( + // Get prices from the actual price adapter. + let new_prices = T::PriceAdapter::adapt_price(SalePerformance::from_sale(&sale)); + let new_sale = SaleInfo::<T>::get().expect("Sale has started."); + let now = RCBlockNumberProviderOf::<T::Coretime>::current_block_number(); + let sale_start = config.interlude_length.saturating_add(rotate_block.into()); + + assert_has_event::<T>( Event::SaleInitialized { sale_start, leadin_length: 1u32.into(), - start_price: 1_000_000_000u32.into(), - end_price: 10_000_000u32.into(), + start_price: Broker::<T>::sale_price(&new_sale, now), + end_price: new_prices.end_price, region_begin: sale.region_begin + config.region_length, region_end: sale.region_end + config.region_length, ideal_cores_sold: 0, cores_offered: n - .saturating_sub(T::MaxReservedCores::get()) - .saturating_sub(T::MaxLeasedCores::get()) + .saturating_sub(n_reservations) + .saturating_sub(n_leases) .try_into() .unwrap(), } @@ -867,18 +884,18 @@ mod benches { ); // Make sure all cores got renewed: - (0..T::MaxAutoRenewals::get()).for_each(|indx| { + (0..n_renewable).for_each(|indx| { let task = 1000 + indx; let who = T::SovereignAccountOf::maybe_convert(task) .expect("Failed to get sovereign account"); assert_has_event::<T>( Event::Renewed { who, - old_core: 10 + indx as u16, // first ten cores are allocated to leases. - core: 10 + indx as u16, - price: 10u32.saturated_into(), - begin: 7, - duration: 3, + old_core: n_reservations as u16 + n_leases as u16 + indx as u16, + core: n_reservations as u16 + n_leases as u16 + indx as u16, + price: 10_000_000u32.into(), + begin: new_sale.region_begin, + duration: config.region_length, workload: Schedule::truncate_from(vec![ScheduleItem { assignment: Task(task), mask: CoreMask::complete(), @@ -999,6 +1016,47 @@ mod benches { Ok(()) } + #[benchmark] + fn force_reserve() -> Result<(), BenchmarkError> { + Configuration::<T>::put(new_config_record::<T>()); + // Assume Reservations to be almost filled for worst case. + let reservation_count = T::MaxReservedCores::get().saturating_sub(1); + setup_reservations::<T>(reservation_count); + + // Assume leases to be filled for worst case + setup_leases::<T>(T::MaxLeasedCores::get(), 1, 10); + + let origin = + T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + + // Sales must be started. + Broker::<T>::do_start_sales(100u32.into(), CoreIndex::try_from(reservation_count).unwrap()) + .map_err(|_| BenchmarkError::Weightless)?; + + // Add a core. + let status = Status::<T>::get().unwrap(); + Broker::<T>::do_request_core_count(status.core_count + 1).unwrap(); + + advance_to::<T>(T::TimeslicePeriod::get().try_into().ok().unwrap()); + let schedule = new_schedule(); + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, schedule.clone(), status.core_count); + + assert_eq!(Reservations::<T>::decode_len().unwrap(), T::MaxReservedCores::get() as usize); + + let sale_info = SaleInfo::<T>::get().unwrap(); + assert_eq!( + Workplan::<T>::get((sale_info.region_begin, status.core_count)), + Some(schedule.clone()) + ); + // We called at timeslice 1, therefore 2 was already processed and 3 is the next possible + // assignment point. + assert_eq!(Workplan::<T>::get((3, status.core_count)), Some(schedule)); + + Ok(()) + } + #[benchmark] fn swap_leases() -> Result<(), BenchmarkError> { let admin_origin = @@ -1018,56 +1076,62 @@ mod benches { #[benchmark] fn enable_auto_renew() -> Result<(), BenchmarkError> { - let _core = setup_and_start_sale::<T>()?; + let _core_id = setup_and_start_sale::<T>()?; advance_to::<T>(2); + let sale = SaleInfo::<T>::get().expect("Sale has already started."); // We assume max auto renewals for worst case. (0..T::MaxAutoRenewals::get() - 1).try_for_each(|indx| -> Result<(), BenchmarkError> { let task = 1000 + indx; let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task) .expect("Failed to get sovereign account"); + // Sovereign account needs sufficient funds to purchase and renew. T::Currency::set_balance( &caller.clone(), - T::Currency::minimum_balance().saturating_add(100u32.into()), + T::Currency::minimum_balance().saturating_add(100_000_000u32.into()), ); - let region = Broker::<T>::do_purchase(caller.clone(), 10u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + let region = Broker::<T>::do_purchase(caller.clone(), 10_000_000u32.into()) + .expect("Offer not high enough for configuration."); Broker::<T>::do_assign(region, None, task, Final) .map_err(|_| BenchmarkError::Weightless)?; - Broker::<T>::do_enable_auto_renew(caller, region.core, task, Some(7))?; + Broker::<T>::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?; Ok(()) })?; let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(2001).expect("Failed to get sovereign account"); + // Sovereign account needs sufficient funds to purchase and renew. T::Currency::set_balance( &caller.clone(), - T::Currency::minimum_balance().saturating_add(100u32.into()), + T::Currency::minimum_balance().saturating_add(100_000_000u32.into()), ); // The region for which we benchmark enable auto renew. - let region = Broker::<T>::do_purchase(caller.clone(), 10u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + let region = Broker::<T>::do_purchase(caller.clone(), 10_000_000u32.into()) + .expect("Offer not high enough for configuration."); Broker::<T>::do_assign(region, None, 2001, Final) .map_err(|_| BenchmarkError::Weightless)?; // The most 'intensive' path is when we renew the core upon enabling auto-renewal. // Therefore, we advance to next bulk sale: - advance_to::<T>(6); + let timeslice_period: u32 = T::TimeslicePeriod::get().try_into().ok().unwrap(); + let config = Configuration::<T>::get().expect("Already configured."); + advance_to::<T>(config.region_length * timeslice_period); #[extrinsic_call] _(RawOrigin::Signed(caller), region.core, 2001, None); assert_last_event::<T>(Event::AutoRenewalEnabled { core: region.core, task: 2001 }.into()); // Make sure we indeed renewed: + let sale = SaleInfo::<T>::get().expect("Sales have started."); assert!(PotentialRenewals::<T>::get(PotentialRenewalId { core: region.core, - when: 10 // region end after renewal + when: sale.region_end, }) .is_some()); @@ -1076,37 +1140,41 @@ mod benches { #[benchmark] fn disable_auto_renew() -> Result<(), BenchmarkError> { - let _core = setup_and_start_sale::<T>()?; + let core_id = setup_and_start_sale::<T>()?; advance_to::<T>(2); + let sale = SaleInfo::<T>::get().expect("Sale has already started."); // We assume max auto renewals for worst case. - (0..T::MaxAutoRenewals::get() - 1).try_for_each(|indx| -> Result<(), BenchmarkError> { + (0..T::MaxAutoRenewals::get()).try_for_each(|indx| -> Result<(), BenchmarkError> { let task = 1000 + indx; let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task) .expect("Failed to get sovereign account"); T::Currency::set_balance( &caller.clone(), - T::Currency::minimum_balance().saturating_add(100u32.into()), + T::Currency::minimum_balance().saturating_add(10_000_000u32.into()), ); - let region = Broker::<T>::do_purchase(caller.clone(), 10u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + let region = Broker::<T>::do_purchase(caller.clone(), 10_000_000u32.into()) + .expect("Offer not high enough for configuration."); Broker::<T>::do_assign(region, None, task, Final) .map_err(|_| BenchmarkError::Weightless)?; - Broker::<T>::do_enable_auto_renew(caller, region.core, task, Some(7))?; + Broker::<T>::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?; Ok(()) })?; + let task = 1000; + let caller: T::AccountId = - T::SovereignAccountOf::maybe_convert(1000).expect("Failed to get sovereign account"); + T::SovereignAccountOf::maybe_convert(task).expect("Failed to get sovereign account"); + #[extrinsic_call] - _(RawOrigin::Signed(caller), _core, 1000); + _(RawOrigin::Signed(caller), core_id, task); - assert_last_event::<T>(Event::AutoRenewalDisabled { core: _core, task: 1000 }.into()); + assert_last_event::<T>(Event::AutoRenewalDisabled { core: core_id, task }.into()); Ok(()) } @@ -1120,11 +1188,11 @@ mod benches { let caller: T::AccountId = whitelisted_caller(); T::Currency::set_balance( &caller.clone(), - T::Currency::minimum_balance().saturating_add(u32::MAX.into()), + T::Currency::minimum_balance().saturating_add(10_000_000u32.into()), ); - let _region = Broker::<T>::do_purchase(caller.clone(), (u32::MAX / 2).into()) - .map_err(|_| BenchmarkError::Weightless)?; + let _region = Broker::<T>::do_purchase(caller.clone(), 10_000_000u32.into()) + .expect("Offer not high enough for configuration."); let timeslice = Broker::<T>::current_timeslice(); diff --git a/substrate/frame/broker/src/dispatchable_impls.rs b/substrate/frame/broker/src/dispatchable_impls.rs index 733d96625da0c1ee6b55f96501c845dc05c6c295..489be12bdd1545d5deb731ca2b29ee9549f8fd1d 100644 --- a/substrate/frame/broker/src/dispatchable_impls.rs +++ b/substrate/frame/broker/src/dispatchable_impls.rs @@ -60,6 +60,27 @@ impl<T: Config> Pallet<T> { Ok(()) } + pub(crate) fn do_force_reserve(workload: Schedule, core: CoreIndex) -> DispatchResult { + // Sales must have started, otherwise reserve is equivalent. + let sale = SaleInfo::<T>::get().ok_or(Error::<T>::NoSales)?; + + // Reserve - starts at second sale period boundary from now. + Self::do_reserve(workload.clone())?; + + // Add to workload - grants one region from the next sale boundary. + Workplan::<T>::insert((sale.region_begin, core), &workload); + + // Assign now until the next sale boundary unless the next timeslice is already the sale + // boundary. + let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?; + let timeslice = status.last_committed_timeslice.saturating_add(1); + if timeslice < sale.region_begin { + Workplan::<T>::insert((timeslice, core), &workload); + } + + Ok(()) + } + pub(crate) fn do_set_lease(task: TaskId, until: Timeslice) -> DispatchResult { let mut r = Leases::<T>::get(); ensure!(until > Self::current_timeslice(), Error::<T>::AlreadyExpired); diff --git a/substrate/frame/broker/src/lib.rs b/substrate/frame/broker/src/lib.rs index ed16b98d26ccb4db1c4f5bff050f2ef3d1b7cd26..01368fd6404da182f1054a9b29c80410afd96e5d 100644 --- a/substrate/frame/broker/src/lib.rs +++ b/substrate/frame/broker/src/lib.rs @@ -585,6 +585,9 @@ pub mod pallet { /// Reserve a core for a workload. /// + /// The workload will be given a reservation, but two sale period boundaries must pass + /// before the core is actually assigned. + /// /// - `origin`: Must be Root or pass `AdminOrigin`. /// - `workload`: The workload which should be permanently placed on a core. #[pallet::call_index(1)] @@ -943,6 +946,29 @@ pub mod pallet { Ok(()) } + /// Reserve a core for a workload immediately. + /// + /// - `origin`: Must be Root or pass `AdminOrigin`. + /// - `workload`: The workload which should be permanently placed on a core starting + /// immediately. + /// - `core`: The core to which the assignment should be made until the reservation takes + /// effect. It is left to the caller to either add this new core or reassign any other + /// tasks to this existing core. + /// + /// This reserves the workload and then injects the workload into the Workplan for the next + /// two sale periods. This overwrites any existing assignments for this core at the start of + /// the next sale period. + #[pallet::call_index(23)] + pub fn force_reserve( + origin: OriginFor<T>, + workload: Schedule, + core: CoreIndex, + ) -> DispatchResultWithPostInfo { + T::AdminOrigin::ensure_origin_or_root(origin)?; + Self::do_force_reserve(workload, core)?; + Ok(Pays::No.into()) + } + #[pallet::call_index(99)] #[pallet::weight(T::WeightInfo::swap_leases())] pub fn swap_leases(origin: OriginFor<T>, id: TaskId, other: TaskId) -> DispatchResult { diff --git a/substrate/frame/broker/src/tests.rs b/substrate/frame/broker/src/tests.rs index f3fd5234e4ca9eadd4b8c40317103c0364bb979c..a130a2050d9a1aba164e586890a50e98c714cc02 100644 --- a/substrate/frame/broker/src/tests.rs +++ b/substrate/frame/broker/src/tests.rs @@ -1837,3 +1837,306 @@ fn start_sales_sets_correct_core_count() { System::assert_has_event(Event::<Test>::CoreCountRequested { core_count: 9 }.into()); }) } + +// Reservations currently need two sale period boundaries to pass before coming into effect. +#[test] +fn reserve_works() { + TestExt::new().execute_with(|| { + assert_ok!(Broker::do_start_sales(100, 0)); + // Advance forward from start_sales, but not into the first sale. + advance_to(1); + + let system_workload = Schedule::truncate_from(vec![ScheduleItem { + mask: CoreMask::complete(), + assignment: Task(1004), + }]); + + // This shouldn't work, as the reservation will never be assigned a core unless one is + // available. + // assert_noop!(Broker::do_reserve(system_workload.clone()), Error::<Test>::Unavailable); + + // Add another core and create the reservation. + let status = Status::<Test>::get().unwrap(); + assert_ok!(Broker::request_core_count(RuntimeOrigin::root(), status.core_count + 1)); + assert_ok!(Broker::reserve(RuntimeOrigin::root(), system_workload.clone())); + + // This is added to reservations. + System::assert_last_event( + Event::ReservationMade { index: 0, workload: system_workload.clone() }.into(), + ); + assert_eq!(Reservations::<Test>::get(), vec![system_workload.clone()]); + + // But not yet in workplan for any of the next few regions. + for i in 0..20 { + assert_eq!(Workplan::<Test>::get((i, 0)), None); + } + // And it hasn't been assigned a core. + assert_eq!(CoretimeTrace::get(), vec![]); + + // Go to next sale. Rotate sale puts it in the workplan. + advance_sale_period(); + assert_eq!(Workplan::<Test>::get((7, 0)), Some(system_workload.clone())); + // But it still hasn't been assigned a core. + assert_eq!(CoretimeTrace::get(), vec![]); + + // Go to the second sale after reserving. + advance_sale_period(); + // Core is assigned at block 14 (timeslice 7) after being reserved all the way back at + // timeslice 1! Since the mock periods are 3 timeslices long, this means that reservations + // made in period 0 will only come into effect in period 2. + assert_eq!( + CoretimeTrace::get(), + vec![( + 12, + AssignCore { + core: 0, + begin: 14, + assignment: vec![(Task(1004), 57600)], + end_hint: None + } + )] + ); + System::assert_has_event( + Event::CoreAssigned { + core: 0, + when: 14, + assignment: vec![(CoreAssignment::Task(1004), 57600)], + } + .into(), + ); + + // And it's in the workplan for the next period. + assert_eq!(Workplan::<Test>::get((10, 0)), Some(system_workload.clone())); + }); +} + +// We can use a hack to accelerate this by injecting it into the workplan. +#[test] +fn can_reserve_workloads_quickly() { + TestExt::new().execute_with(|| { + // Start sales. + assert_ok!(Broker::do_start_sales(100, 0)); + advance_to(2); + + let system_workload = Schedule::truncate_from(vec![ScheduleItem { + mask: CoreMask::complete(), + assignment: Task(1004), + }]); + + // This shouldn't work, as the reservation will never be assigned a core unless one is + // available. + // assert_noop!(Broker::do_reserve(system_workload.clone()), Error::<Test>::Unavailable); + + // Add another core and create the reservation. + let core_count = Status::<Test>::get().unwrap().core_count; + assert_ok!(Broker::request_core_count(RuntimeOrigin::root(), core_count + 1)); + assert_ok!(Broker::reserve(RuntimeOrigin::root(), system_workload.clone())); + + // These are the additional steps to onboard this immediately. + let core_index = core_count; + // In a real network this would call the relay chain + // `assigner_coretime::assign_core` extrinsic directly. + <TestCoretimeProvider as CoretimeInterface>::assign_core( + core_index, + 2, + vec![(Task(1004), 57600)], + None, + ); + // Inject into the workplan to ensure it's scheduled in the next rotate_sale. + Workplan::<Test>::insert((4, core_index), system_workload.clone()); + + // Reservation is added for the workload. + System::assert_has_event( + Event::ReservationMade { index: 0, workload: system_workload.clone() }.into(), + ); + System::assert_has_event(Event::CoreCountRequested { core_count: 1 }.into()); + + // It is also in the workplan for the next region. + assert_eq!(Workplan::<Test>::get((4, 0)), Some(system_workload.clone())); + + // Go to next sale. Rotate sale puts it in the workplan. + advance_sale_period(); + assert_eq!(Workplan::<Test>::get((7, 0)), Some(system_workload.clone())); + + // Go to the second sale after reserving. + advance_sale_period(); + + // Check the trace to ensure it has a core in every region. + assert_eq!( + CoretimeTrace::get(), + vec![ + ( + 2, + AssignCore { + core: 0, + begin: 2, + assignment: vec![(Task(1004), 57600)], + end_hint: None + } + ), + ( + 6, + AssignCore { + core: 0, + begin: 8, + assignment: vec![(Task(1004), 57600)], + end_hint: None + } + ), + ( + 12, + AssignCore { + core: 0, + begin: 14, + assignment: vec![(Task(1004), 57600)], + end_hint: None + } + ) + ] + ); + System::assert_has_event( + Event::CoreAssigned { + core: 0, + when: 8, + assignment: vec![(CoreAssignment::Task(1004), 57600)], + } + .into(), + ); + System::assert_has_event( + Event::CoreAssigned { + core: 0, + when: 14, + assignment: vec![(CoreAssignment::Task(1004), 57600)], + } + .into(), + ); + System::assert_has_event( + Event::CoreAssigned { + core: 0, + when: 14, + assignment: vec![(CoreAssignment::Task(1004), 57600)], + } + .into(), + ); + + // And it's in the workplan for the next period. + assert_eq!(Workplan::<Test>::get((10, 0)), Some(system_workload.clone())); + }); +} + +// Add an extrinsic to do it properly. +#[test] +fn force_reserve_works() { + TestExt::new().execute_with(|| { + let system_workload = Schedule::truncate_from(vec![ScheduleItem { + mask: CoreMask::complete(), + assignment: Task(1004), + }]); + + // Not intended to work before sales are started. + assert_noop!( + Broker::force_reserve(RuntimeOrigin::root(), system_workload.clone(), 0), + Error::<Test>::NoSales + ); + + // Start sales. + assert_ok!(Broker::do_start_sales(100, 0)); + advance_to(1); + + // Add a new core. With the mock this is instant, with current relay implementation it + // takes two sessions to come into effect. + assert_ok!(Broker::do_request_core_count(1)); + + // Force reserve should now work. + assert_ok!(Broker::force_reserve(RuntimeOrigin::root(), system_workload.clone(), 0)); + + // Reservation is added for the workload. + System::assert_has_event( + Event::ReservationMade { index: 0, workload: system_workload.clone() }.into(), + ); + System::assert_has_event(Event::CoreCountRequested { core_count: 1 }.into()); + assert_eq!(Reservations::<Test>::get(), vec![system_workload.clone()]); + + // Advance to where that timeslice will be committed. + advance_to(3); + System::assert_has_event( + Event::CoreAssigned { + core: 0, + when: 4, + assignment: vec![(CoreAssignment::Task(1004), 57600)], + } + .into(), + ); + + // It is also in the workplan for the next region. + assert_eq!(Workplan::<Test>::get((4, 0)), Some(system_workload.clone())); + + // Go to next sale. Rotate sale puts it in the workplan. + advance_sale_period(); + assert_eq!(Workplan::<Test>::get((7, 0)), Some(system_workload.clone())); + + // Go to the second sale after reserving. + advance_sale_period(); + + // Check the trace to ensure it has a core in every region. + assert_eq!( + CoretimeTrace::get(), + vec![ + ( + 2, + AssignCore { + core: 0, + begin: 4, + assignment: vec![(Task(1004), 57600)], + end_hint: None + } + ), + ( + 6, + AssignCore { + core: 0, + begin: 8, + assignment: vec![(Task(1004), 57600)], + end_hint: None + } + ), + ( + 12, + AssignCore { + core: 0, + begin: 14, + assignment: vec![(Task(1004), 57600)], + end_hint: None + } + ) + ] + ); + System::assert_has_event( + Event::CoreAssigned { + core: 0, + when: 8, + assignment: vec![(CoreAssignment::Task(1004), 57600)], + } + .into(), + ); + System::assert_has_event( + Event::CoreAssigned { + core: 0, + when: 14, + assignment: vec![(CoreAssignment::Task(1004), 57600)], + } + .into(), + ); + System::assert_has_event( + Event::CoreAssigned { + core: 0, + when: 14, + assignment: vec![(CoreAssignment::Task(1004), 57600)], + } + .into(), + ); + + // And it's in the workplan for the next period. + assert_eq!(Workplan::<Test>::get((10, 0)), Some(system_workload.clone())); + }); +} diff --git a/substrate/frame/broker/src/weights.rs b/substrate/frame/broker/src/weights.rs index 2f25fddc20509402ce13a98dc763dbeef539a7d4..87e588551661b335d1bb2aaa20f6b4369e90b2e9 100644 --- a/substrate/frame/broker/src/weights.rs +++ b/substrate/frame/broker/src/weights.rs @@ -18,27 +18,25 @@ //! Autogenerated weights for `pallet_broker` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `sergej-B650-AORUS-ELITE-AX`, CPU: `AMD Ryzen 9 7900X3D 12-Core Processor` +//! HOSTNAME: `runner-acd6uxux-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/release/substrate-node +// target/production/substrate-node // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_broker -// --no-storage-info -// --no-median-slopes -// --no-min-squares // --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --output=./substrate/frame/broker/src/weights.rs +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_broker +// --chain=dev // --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/broker/src/weights.rs // --template=./substrate/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -79,10 +77,11 @@ pub trait WeightInfo { fn notify_core_count() -> Weight; fn notify_revenue() -> Weight; fn do_tick_base() -> Weight; + fn force_reserve() -> Weight; fn swap_leases() -> Weight; - fn on_new_timeslice() -> Weight; fn enable_auto_renew() -> Weight; fn disable_auto_renew() -> Weight; + fn on_new_timeslice() -> Weight; } /// Weights for `pallet_broker` using the Substrate node and recommended hardware. @@ -94,8 +93,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_593_000 picoseconds. - Weight::from_parts(1_703_000, 0) + // Minimum execution time: 2_498_000 picoseconds. + Weight::from_parts(2_660_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Broker::Reservations` (r:1 w:1) @@ -104,8 +103,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `5016` // Estimated: `7496` - // Minimum execution time: 12_864_000 picoseconds. - Weight::from_parts(13_174_000, 7496) + // Minimum execution time: 23_090_000 picoseconds. + Weight::from_parts(23_664_000, 7496) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -115,8 +114,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `6218` // Estimated: `7496` - // Minimum execution time: 12_284_000 picoseconds. - Weight::from_parts(13_566_000, 7496) + // Minimum execution time: 21_782_000 picoseconds. + Weight::from_parts(22_708_000, 7496) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -126,8 +125,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `239` // Estimated: `1526` - // Minimum execution time: 6_743_000 picoseconds. - Weight::from_parts(7_094_000, 1526) + // Minimum execution time: 14_966_000 picoseconds. + Weight::from_parts(15_592_000, 1526) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -152,10 +151,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `6330` // Estimated: `8499` - // Minimum execution time: 21_120_000 picoseconds. - Weight::from_parts(40_929_422, 8499) - // Standard Error: 471 - .saturating_add(Weight::from_parts(1_004, 0).saturating_mul(n.into())) + // Minimum execution time: 31_757_000 picoseconds. + Weight::from_parts(57_977_268, 8499) + // Standard Error: 576 + .saturating_add(Weight::from_parts(3_102, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(16_u64)) } @@ -163,19 +162,15 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) /// Storage: `Broker::SaleInfo` (r:1 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::Regions` (r:0 w:1) /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(86), added: 2561, mode: `MaxEncodedLen`) fn purchase() -> Weight { // Proof Size summary in bytes: - // Measured: `651` - // Estimated: `2136` - // Minimum execution time: 31_169_000 picoseconds. - Weight::from_parts(32_271_000, 2136) - .saturating_add(T::DbWeight::get().reads(4_u64)) + // Measured: `470` + // Estimated: `1542` + // Minimum execution time: 40_469_000 picoseconds. + Weight::from_parts(41_360_000, 1542) + .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `Broker::Configuration` (r:1 w:0) @@ -186,19 +181,15 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) /// Storage: `Broker::PotentialRenewals` (r:1 w:2) /// Proof: `Broker::PotentialRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::Workplan` (r:0 w:1) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) fn renew() -> Weight { // Proof Size summary in bytes: - // Measured: `769` + // Measured: `588` // Estimated: `4698` - // Minimum execution time: 44_945_000 picoseconds. - Weight::from_parts(47_119_000, 4698) - .saturating_add(T::DbWeight::get().reads(6_u64)) + // Minimum execution time: 60_724_000 picoseconds. + Weight::from_parts(63_445_000, 4698) + .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Broker::Regions` (r:1 w:1) @@ -207,8 +198,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `496` // Estimated: `3551` - // Minimum execution time: 11_562_000 picoseconds. - Weight::from_parts(11_943_000, 3551) + // Minimum execution time: 23_734_000 picoseconds. + Weight::from_parts(25_080_000, 3551) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -218,8 +209,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `496` // Estimated: `3551` - // Minimum execution time: 13_075_000 picoseconds. - Weight::from_parts(13_616_000, 3551) + // Minimum execution time: 25_917_000 picoseconds. + Weight::from_parts(26_715_000, 3551) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -229,8 +220,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `496` // Estimated: `3551` - // Minimum execution time: 13_695_000 picoseconds. - Weight::from_parts(14_658_000, 3551) + // Minimum execution time: 26_764_000 picoseconds. + Weight::from_parts(27_770_000, 3551) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -246,8 +237,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `741` // Estimated: `4681` - // Minimum execution time: 22_623_000 picoseconds. - Weight::from_parts(23_233_000, 4681) + // Minimum execution time: 37_617_000 picoseconds. + Weight::from_parts(39_333_000, 4681) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -265,8 +256,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `776` // Estimated: `5996` - // Minimum execution time: 26_901_000 picoseconds. - Weight::from_parts(27_472_000, 5996) + // Minimum execution time: 43_168_000 picoseconds. + Weight::from_parts(44_741_000, 5996) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -281,10 +272,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `878` // Estimated: `6196 + m * (2520 ±0)` - // Minimum execution time: 51_778_000 picoseconds. - Weight::from_parts(53_726_731, 6196) - // Standard Error: 45_279 - .saturating_add(Weight::from_parts(677_769, 0).saturating_mul(m.into())) + // Minimum execution time: 75_317_000 picoseconds. + Weight::from_parts(76_792_860, 6196) + // Standard Error: 55_267 + .saturating_add(Weight::from_parts(1_878_133, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(m.into()))) .saturating_add(T::DbWeight::get().writes(5_u64)) @@ -296,8 +287,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 31_790_000 picoseconds. - Weight::from_parts(32_601_000, 3593) + // Minimum execution time: 44_248_000 picoseconds. + Weight::from_parts(45_201_000, 3593) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -309,8 +300,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `604` // Estimated: `3551` - // Minimum execution time: 18_465_000 picoseconds. - Weight::from_parts(21_050_000, 3551) + // Minimum execution time: 39_853_000 picoseconds. + Weight::from_parts(44_136_000, 3551) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -324,8 +315,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `601` // Estimated: `3533` - // Minimum execution time: 23_825_000 picoseconds. - Weight::from_parts(26_250_000, 3533) + // Minimum execution time: 46_452_000 picoseconds. + Weight::from_parts(52_780_000, 3533) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -339,10 +330,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn drop_history() -> Weight { // Proof Size summary in bytes: - // Measured: `1014` + // Measured: `1117` // Estimated: `3593` - // Minimum execution time: 28_103_000 picoseconds. - Weight::from_parts(32_622_000, 3593) + // Minimum execution time: 64_905_000 picoseconds. + Weight::from_parts(72_914_000, 3593) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -354,8 +345,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `661` // Estimated: `4698` - // Minimum execution time: 16_751_000 picoseconds. - Weight::from_parts(17_373_000, 4698) + // Minimum execution time: 38_831_000 picoseconds. + Weight::from_parts(41_420_000, 4698) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -364,8 +355,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_705_000 picoseconds. - Weight::from_parts(2_991_768, 0) + // Minimum execution time: 4_595_000 picoseconds. + Weight::from_parts(4_964_606, 0) } /// Storage: `Broker::CoreCountInbox` (r:1 w:1) /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -374,37 +365,58 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `404` // Estimated: `1487` - // Minimum execution time: 4_598_000 picoseconds. - Weight::from_parts(4_937_302, 1487) + // Minimum execution time: 8_640_000 picoseconds. + Weight::from_parts(9_153_332, 1487) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) - /// Proof: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Storage: `Broker::RevenueInbox` (r:1 w:1) + /// Proof: `Broker::RevenueInbox` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) /// Storage: `Broker::InstaPoolHistory` (r:1 w:1) /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn process_revenue() -> Weight { // Proof Size summary in bytes: - // Measured: `991` - // Estimated: `4456` - // Minimum execution time: 37_601_000 picoseconds. - Weight::from_parts(38_262_000, 4456) - .saturating_add(T::DbWeight::get().reads(5_u64)) + // Measured: `667` + // Estimated: `3593` + // Minimum execution time: 40_570_000 picoseconds. + Weight::from_parts(41_402_000, 3593) + .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } + /// Storage: `Broker::InstaPoolIo` (r:3 w:3) + /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:0) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(6011), added: 6506, mode: `MaxEncodedLen`) + /// Storage: `Broker::Leases` (r:1 w:1) + /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(41), added: 536, mode: `MaxEncodedLen`) + /// Storage: `Broker::AutoRenewals` (r:1 w:1) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(101), added: 596, mode: `MaxEncodedLen`) + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::PotentialRenewals` (r:10 w:20) + /// Proof: `Broker::PotentialRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:10 w:10) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Broker::SaleInfo` (r:0 w:1) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:1000) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 1000]`. - fn rotate_sale(_n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 0_000 picoseconds. - Weight::from_parts(0, 0) + fn rotate_sale(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `8548` + // Estimated: `38070` + // Minimum execution time: 29_370_000 picoseconds. + Weight::from_parts(334_030_189, 38070) + // Standard Error: 6_912 + .saturating_add(Weight::from_parts(1_268_750, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(26_u64)) + .saturating_add(T::DbWeight::get().writes(34_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) } /// Storage: `Broker::InstaPoolIo` (r:1 w:0) /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) @@ -414,8 +426,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `180` // Estimated: `3493` - // Minimum execution time: 5_391_000 picoseconds. - Weight::from_parts(5_630_000, 3493) + // Minimum execution time: 9_005_000 picoseconds. + Weight::from_parts(9_392_000, 3493) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -427,8 +439,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `1423` // Estimated: `4681` - // Minimum execution time: 10_249_000 picoseconds. - Weight::from_parts(10_529_000, 4681) + // Minimum execution time: 19_043_000 picoseconds. + Weight::from_parts(20_089_000, 4681) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -436,8 +448,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 120_000 picoseconds. - Weight::from_parts(140_000, 0) + // Minimum execution time: 149_000 picoseconds. + Weight::from_parts(183_000, 0) } /// Storage: `Broker::CoreCountInbox` (r:0 w:1) /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -445,8 +457,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_402_000 picoseconds. - Weight::from_parts(1_513_000, 0) + // Minimum execution time: 2_248_000 picoseconds. + Weight::from_parts(2_425_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Broker::RevenueInbox` (r:0 w:1) @@ -455,8 +467,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_902_000 picoseconds. - Weight::from_parts(2_116_000, 0) + // Minimum execution time: 2_413_000 picoseconds. + Weight::from_parts(2_640_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Broker::Status` (r:1 w:1) @@ -465,16 +477,33 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) /// Storage: `Broker::CoreCountInbox` (r:1 w:0) /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) - /// Storage: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) - /// Proof: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Storage: `Broker::RevenueInbox` (r:1 w:0) + /// Proof: `Broker::RevenueInbox` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) fn do_tick_base() -> Weight { // Proof Size summary in bytes: - // Measured: `603` - // Estimated: `4068` - // Minimum execution time: 8_897_000 picoseconds. - Weight::from_parts(9_218_000, 4068) + // Measured: `441` + // Estimated: `1516` + // Minimum execution time: 17_083_000 picoseconds. + Weight::from_parts(18_077_000, 1516) .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Broker::SaleInfo` (r:1 w:0) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:1) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(6011), added: 6506, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:2) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + fn force_reserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `5253` + // Estimated: `7496` + // Minimum execution time: 28_363_000 picoseconds. + Weight::from_parts(29_243_000, 7496) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } /// Storage: `Broker::Leases` (r:1 w:1) /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(41), added: 536, mode: `MaxEncodedLen`) @@ -482,18 +511,11 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `239` // Estimated: `1526` - // Minimum execution time: 4_678_000 picoseconds. - Weight::from_parts(4_920_000, 1526) + // Minimum execution time: 11_620_000 picoseconds. + Weight::from_parts(12_063_000, 1526) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - fn on_new_timeslice() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 229_000 picoseconds. - Weight::from_parts(268_000, 0) - } /// Storage: `Broker::SaleInfo` (r:1 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) /// Storage: `Broker::PotentialRenewals` (r:1 w:2) @@ -504,34 +526,37 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::AutoRenewals` (r:1 w:1) /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(101), added: 596, mode: `MaxEncodedLen`) /// Storage: `Broker::Workplan` (r:0 w:1) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) fn enable_auto_renew() -> Weight { // Proof Size summary in bytes: - // Measured: `930` + // Measured: `1121` // Estimated: `4698` - // Minimum execution time: 51_597_000 picoseconds. - Weight::from_parts(52_609_000, 4698) - .saturating_add(T::DbWeight::get().reads(8_u64)) + // Minimum execution time: 85_270_000 picoseconds. + Weight::from_parts(90_457_000, 4698) + .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: `Broker::AutoRenewals` (r:1 w:1) /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(101), added: 596, mode: `MaxEncodedLen`) fn disable_auto_renew() -> Weight { // Proof Size summary in bytes: - // Measured: `484` + // Measured: `578` // Estimated: `1586` - // Minimum execution time: 8_907_000 picoseconds. - Weight::from_parts(9_167_000, 1586) + // Minimum execution time: 22_479_000 picoseconds. + Weight::from_parts(23_687_000, 1586) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } + fn on_new_timeslice() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 245_000 picoseconds. + Weight::from_parts(290_000, 0) + } } // For backwards compatibility and tests. @@ -542,8 +567,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_593_000 picoseconds. - Weight::from_parts(1_703_000, 0) + // Minimum execution time: 2_498_000 picoseconds. + Weight::from_parts(2_660_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Broker::Reservations` (r:1 w:1) @@ -552,8 +577,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `5016` // Estimated: `7496` - // Minimum execution time: 12_864_000 picoseconds. - Weight::from_parts(13_174_000, 7496) + // Minimum execution time: 23_090_000 picoseconds. + Weight::from_parts(23_664_000, 7496) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -563,8 +588,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `6218` // Estimated: `7496` - // Minimum execution time: 12_284_000 picoseconds. - Weight::from_parts(13_566_000, 7496) + // Minimum execution time: 21_782_000 picoseconds. + Weight::from_parts(22_708_000, 7496) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -574,8 +599,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `239` // Estimated: `1526` - // Minimum execution time: 6_743_000 picoseconds. - Weight::from_parts(7_094_000, 1526) + // Minimum execution time: 14_966_000 picoseconds. + Weight::from_parts(15_592_000, 1526) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -600,10 +625,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `6330` // Estimated: `8499` - // Minimum execution time: 21_120_000 picoseconds. - Weight::from_parts(40_929_422, 8499) - // Standard Error: 471 - .saturating_add(Weight::from_parts(1_004, 0).saturating_mul(n.into())) + // Minimum execution time: 31_757_000 picoseconds. + Weight::from_parts(57_977_268, 8499) + // Standard Error: 576 + .saturating_add(Weight::from_parts(3_102, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(16_u64)) } @@ -611,19 +636,15 @@ impl WeightInfo for () { /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) /// Storage: `Broker::SaleInfo` (r:1 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::Regions` (r:0 w:1) /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(86), added: 2561, mode: `MaxEncodedLen`) fn purchase() -> Weight { // Proof Size summary in bytes: - // Measured: `651` - // Estimated: `2136` - // Minimum execution time: 31_169_000 picoseconds. - Weight::from_parts(32_271_000, 2136) - .saturating_add(RocksDbWeight::get().reads(4_u64)) + // Measured: `470` + // Estimated: `1542` + // Minimum execution time: 40_469_000 picoseconds. + Weight::from_parts(41_360_000, 1542) + .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `Broker::Configuration` (r:1 w:0) @@ -634,19 +655,15 @@ impl WeightInfo for () { /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) /// Storage: `Broker::PotentialRenewals` (r:1 w:2) /// Proof: `Broker::PotentialRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::Workplan` (r:0 w:1) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) fn renew() -> Weight { // Proof Size summary in bytes: - // Measured: `769` + // Measured: `588` // Estimated: `4698` - // Minimum execution time: 44_945_000 picoseconds. - Weight::from_parts(47_119_000, 4698) - .saturating_add(RocksDbWeight::get().reads(6_u64)) + // Minimum execution time: 60_724_000 picoseconds. + Weight::from_parts(63_445_000, 4698) + .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Broker::Regions` (r:1 w:1) @@ -655,8 +672,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `496` // Estimated: `3551` - // Minimum execution time: 11_562_000 picoseconds. - Weight::from_parts(11_943_000, 3551) + // Minimum execution time: 23_734_000 picoseconds. + Weight::from_parts(25_080_000, 3551) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -666,8 +683,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `496` // Estimated: `3551` - // Minimum execution time: 13_075_000 picoseconds. - Weight::from_parts(13_616_000, 3551) + // Minimum execution time: 25_917_000 picoseconds. + Weight::from_parts(26_715_000, 3551) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -677,8 +694,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `496` // Estimated: `3551` - // Minimum execution time: 13_695_000 picoseconds. - Weight::from_parts(14_658_000, 3551) + // Minimum execution time: 26_764_000 picoseconds. + Weight::from_parts(27_770_000, 3551) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -694,8 +711,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `741` // Estimated: `4681` - // Minimum execution time: 22_623_000 picoseconds. - Weight::from_parts(23_233_000, 4681) + // Minimum execution time: 37_617_000 picoseconds. + Weight::from_parts(39_333_000, 4681) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -713,8 +730,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `776` // Estimated: `5996` - // Minimum execution time: 26_901_000 picoseconds. - Weight::from_parts(27_472_000, 5996) + // Minimum execution time: 43_168_000 picoseconds. + Weight::from_parts(44_741_000, 5996) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -729,10 +746,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `878` // Estimated: `6196 + m * (2520 ±0)` - // Minimum execution time: 51_778_000 picoseconds. - Weight::from_parts(53_726_731, 6196) - // Standard Error: 45_279 - .saturating_add(Weight::from_parts(677_769, 0).saturating_mul(m.into())) + // Minimum execution time: 75_317_000 picoseconds. + Weight::from_parts(76_792_860, 6196) + // Standard Error: 55_267 + .saturating_add(Weight::from_parts(1_878_133, 0).saturating_mul(m.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(m.into()))) .saturating_add(RocksDbWeight::get().writes(5_u64)) @@ -744,8 +761,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 31_790_000 picoseconds. - Weight::from_parts(32_601_000, 3593) + // Minimum execution time: 44_248_000 picoseconds. + Weight::from_parts(45_201_000, 3593) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -757,8 +774,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `604` // Estimated: `3551` - // Minimum execution time: 18_465_000 picoseconds. - Weight::from_parts(21_050_000, 3551) + // Minimum execution time: 39_853_000 picoseconds. + Weight::from_parts(44_136_000, 3551) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -772,8 +789,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `601` // Estimated: `3533` - // Minimum execution time: 23_825_000 picoseconds. - Weight::from_parts(26_250_000, 3533) + // Minimum execution time: 46_452_000 picoseconds. + Weight::from_parts(52_780_000, 3533) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -787,10 +804,10 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn drop_history() -> Weight { // Proof Size summary in bytes: - // Measured: `1014` + // Measured: `1117` // Estimated: `3593` - // Minimum execution time: 28_103_000 picoseconds. - Weight::from_parts(32_622_000, 3593) + // Minimum execution time: 64_905_000 picoseconds. + Weight::from_parts(72_914_000, 3593) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -802,8 +819,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `661` // Estimated: `4698` - // Minimum execution time: 16_751_000 picoseconds. - Weight::from_parts(17_373_000, 4698) + // Minimum execution time: 38_831_000 picoseconds. + Weight::from_parts(41_420_000, 4698) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -812,8 +829,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_705_000 picoseconds. - Weight::from_parts(2_991_768, 0) + // Minimum execution time: 4_595_000 picoseconds. + Weight::from_parts(4_964_606, 0) } /// Storage: `Broker::CoreCountInbox` (r:1 w:1) /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -822,37 +839,58 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `404` // Estimated: `1487` - // Minimum execution time: 4_598_000 picoseconds. - Weight::from_parts(4_937_302, 1487) + // Minimum execution time: 8_640_000 picoseconds. + Weight::from_parts(9_153_332, 1487) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) - /// Proof: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Storage: `Broker::RevenueInbox` (r:1 w:1) + /// Proof: `Broker::RevenueInbox` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) /// Storage: `Broker::InstaPoolHistory` (r:1 w:1) /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn process_revenue() -> Weight { // Proof Size summary in bytes: - // Measured: `991` - // Estimated: `4456` - // Minimum execution time: 37_601_000 picoseconds. - Weight::from_parts(38_262_000, 4456) - .saturating_add(RocksDbWeight::get().reads(5_u64)) + // Measured: `667` + // Estimated: `3593` + // Minimum execution time: 40_570_000 picoseconds. + Weight::from_parts(41_402_000, 3593) + .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } + /// Storage: `Broker::InstaPoolIo` (r:3 w:3) + /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:0) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(6011), added: 6506, mode: `MaxEncodedLen`) + /// Storage: `Broker::Leases` (r:1 w:1) + /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(41), added: 536, mode: `MaxEncodedLen`) + /// Storage: `Broker::AutoRenewals` (r:1 w:1) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(101), added: 596, mode: `MaxEncodedLen`) + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::PotentialRenewals` (r:10 w:20) + /// Proof: `Broker::PotentialRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:10 w:10) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Broker::SaleInfo` (r:0 w:1) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:1000) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 1000]`. - fn rotate_sale(_n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 0_000 picoseconds. - Weight::from_parts(0, 0) + fn rotate_sale(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `8548` + // Estimated: `38070` + // Minimum execution time: 29_370_000 picoseconds. + Weight::from_parts(334_030_189, 38070) + // Standard Error: 6_912 + .saturating_add(Weight::from_parts(1_268_750, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(26_u64)) + .saturating_add(RocksDbWeight::get().writes(34_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) } /// Storage: `Broker::InstaPoolIo` (r:1 w:0) /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) @@ -862,8 +900,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `180` // Estimated: `3493` - // Minimum execution time: 5_391_000 picoseconds. - Weight::from_parts(5_630_000, 3493) + // Minimum execution time: 9_005_000 picoseconds. + Weight::from_parts(9_392_000, 3493) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -875,8 +913,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1423` // Estimated: `4681` - // Minimum execution time: 10_249_000 picoseconds. - Weight::from_parts(10_529_000, 4681) + // Minimum execution time: 19_043_000 picoseconds. + Weight::from_parts(20_089_000, 4681) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -884,8 +922,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 120_000 picoseconds. - Weight::from_parts(140_000, 0) + // Minimum execution time: 149_000 picoseconds. + Weight::from_parts(183_000, 0) } /// Storage: `Broker::CoreCountInbox` (r:0 w:1) /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -893,8 +931,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_402_000 picoseconds. - Weight::from_parts(1_513_000, 0) + // Minimum execution time: 2_248_000 picoseconds. + Weight::from_parts(2_425_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Broker::RevenueInbox` (r:0 w:1) @@ -903,8 +941,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_902_000 picoseconds. - Weight::from_parts(2_116_000, 0) + // Minimum execution time: 2_413_000 picoseconds. + Weight::from_parts(2_640_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Broker::Status` (r:1 w:1) @@ -913,16 +951,33 @@ impl WeightInfo for () { /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) /// Storage: `Broker::CoreCountInbox` (r:1 w:0) /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) - /// Storage: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) - /// Proof: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Storage: `Broker::RevenueInbox` (r:1 w:0) + /// Proof: `Broker::RevenueInbox` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) fn do_tick_base() -> Weight { // Proof Size summary in bytes: - // Measured: `603` - // Estimated: `4068` - // Minimum execution time: 8_897_000 picoseconds. - Weight::from_parts(9_218_000, 4068) + // Measured: `441` + // Estimated: `1516` + // Minimum execution time: 17_083_000 picoseconds. + Weight::from_parts(18_077_000, 1516) .saturating_add(RocksDbWeight::get().reads(4_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Broker::SaleInfo` (r:1 w:0) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:1) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(6011), added: 6506, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:2) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + fn force_reserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `5253` + // Estimated: `7496` + // Minimum execution time: 28_363_000 picoseconds. + Weight::from_parts(29_243_000, 7496) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } /// Storage: `Broker::Leases` (r:1 w:1) /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(41), added: 536, mode: `MaxEncodedLen`) @@ -930,18 +985,11 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `239` // Estimated: `1526` - // Minimum execution time: 4_678_000 picoseconds. - Weight::from_parts(4_920_000, 1526) + // Minimum execution time: 11_620_000 picoseconds. + Weight::from_parts(12_063_000, 1526) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - fn on_new_timeslice() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 229_000 picoseconds. - Weight::from_parts(268_000, 0) - } /// Storage: `Broker::SaleInfo` (r:1 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) /// Storage: `Broker::PotentialRenewals` (r:1 w:2) @@ -952,32 +1000,35 @@ impl WeightInfo for () { /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::AutoRenewals` (r:1 w:1) /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(101), added: 596, mode: `MaxEncodedLen`) /// Storage: `Broker::Workplan` (r:0 w:1) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) fn enable_auto_renew() -> Weight { // Proof Size summary in bytes: - // Measured: `930` + // Measured: `1121` // Estimated: `4698` - // Minimum execution time: 51_597_000 picoseconds. - Weight::from_parts(52_609_000, 4698) - .saturating_add(RocksDbWeight::get().reads(8_u64)) + // Minimum execution time: 85_270_000 picoseconds. + Weight::from_parts(90_457_000, 4698) + .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `Broker::AutoRenewals` (r:1 w:1) /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(101), added: 596, mode: `MaxEncodedLen`) fn disable_auto_renew() -> Weight { // Proof Size summary in bytes: - // Measured: `484` + // Measured: `578` // Estimated: `1586` - // Minimum execution time: 8_907_000 picoseconds. - Weight::from_parts(9_167_000, 1586) + // Minimum execution time: 22_479_000 picoseconds. + Weight::from_parts(23_687_000, 1586) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } -} \ No newline at end of file + fn on_new_timeslice() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 245_000 picoseconds. + Weight::from_parts(290_000, 0) + } +} diff --git a/substrate/frame/child-bounties/Cargo.toml b/substrate/frame/child-bounties/Cargo.toml index a250886b5e3d45e679dd9cab41f94f92739d8b90..b7d9d245892ac51bdf7945080a7d8025588d3897 100644 --- a/substrate/frame/child-bounties/Cargo.toml +++ b/substrate/frame/child-bounties/Cargo.toml @@ -19,13 +19,13 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-bounties = { workspace = true } pallet-treasury = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/child-bounties/src/tests.rs b/substrate/frame/child-bounties/src/tests.rs index 939983054f667dfe73cd92ec8b6e59ae293373fb..50c8adb453e5e5b82b4f216152dcebeb37e766ee 100644 --- a/substrate/frame/child-bounties/src/tests.rs +++ b/substrate/frame/child-bounties/src/tests.rs @@ -148,6 +148,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig::<Test> { // Total issuance will be 200 with treasury account initialized at ED. balances: vec![(account_id(0), 100), (account_id(1), 98), (account_id(2), 1)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/collective/Cargo.toml b/substrate/frame/collective/Cargo.toml index 59a9d23f7b1958b4137ea6b8412a0d3bfe44e163..8e53000352aea22ecfd2a5d232ec590ff3198289 100644 --- a/substrate/frame/collective/Cargo.toml +++ b/substrate/frame/collective/Cargo.toml @@ -18,11 +18,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } docify = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { features = ["experimental"], workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/collective/src/tests.rs b/substrate/frame/collective/src/tests.rs index c4ed17821ae89e2b66246b6937e5f169c644bd3b..300d5ad3772a9d9ee2531f62609433385edf6116 100644 --- a/substrate/frame/collective/src/tests.rs +++ b/substrate/frame/collective/src/tests.rs @@ -203,7 +203,10 @@ impl ExtBuilder { let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { system: frame_system::GenesisConfig::default(), // balances: pallet_balances::GenesisConfig::default(), - balances: pallet_balances::GenesisConfig { balances: vec![(1, 100), (2, 200)] }, + balances: pallet_balances::GenesisConfig { + balances: vec![(1, 100), (2, 200)], + ..Default::default() + }, collective: pallet_collective::GenesisConfig { members: self.collective_members, phantom: Default::default(), diff --git a/substrate/frame/contracts/Cargo.toml b/substrate/frame/contracts/Cargo.toml index 316ea68130483777c73e8a2e62af855fbbd8031b..88404803fe0f3b805ba6f86e80bf34f944d5b612 100644 --- a/substrate/frame/contracts/Cargo.toml +++ b/substrate/frame/contracts/Cargo.toml @@ -18,25 +18,25 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -paste = { workspace = true } bitflags = { workspace = true } codec = { features = [ "derive", "max-encoded-len", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } +impl-trait-for-tuples = { workspace = true } log = { workspace = true } +paste = { workspace = true } +scale-info = { features = ["derive"], workspace = true } serde = { optional = true, features = ["derive"], workspace = true, default-features = true } smallvec = { features = [ "const_generics", ], workspace = true } wasmi = { workspace = true } -impl-trait-for-tuples = { workspace = true } # Only used in benchmarking to generate contract code -wasm-instrument = { optional = true, workspace = true } rand = { optional = true, workspace = true } rand_pcg = { optional = true, workspace = true } +wasm-instrument = { optional = true, workspace = true } # Substrate Dependencies environmental = { workspace = true } @@ -44,13 +44,12 @@ frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-balances = { optional = true, workspace = true } -pallet-contracts-uapi = { workspace = true, default-features = true } pallet-contracts-proc-macro = { workspace = true, default-features = true } +pallet-contracts-uapi = { 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 } @@ -58,21 +57,19 @@ xcm-builder = { workspace = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } assert_matches = { workspace = true } +pallet-contracts-fixtures = { workspace = true } pretty_assertions = { workspace = true } wat = { workspace = true } -pallet-contracts-fixtures = { workspace = 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-insecure-randomness-collective-flip = { 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 } +pallet-timestamp = { workspace = true, default-features = true } +pallet-utility = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } @@ -98,7 +95,6 @@ std = [ "sp-io/std", "sp-keystore/std", "sp-runtime/std", - "sp-std/std", "wasm-instrument?/std", "wasmi/std", "xcm-builder/std", @@ -108,9 +104,7 @@ 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", @@ -119,14 +113,13 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "wasm-instrument", "xcm-builder/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "pallet-assets/try-runtime", "pallet-balances/try-runtime", "pallet-insecure-randomness-collective-flip/try-runtime", - "pallet-message-queue/try-runtime", "pallet-proxy/try-runtime", "pallet-timestamp/try-runtime", "pallet-utility/try-runtime", diff --git a/substrate/frame/contracts/fixtures/Cargo.toml b/substrate/frame/contracts/fixtures/Cargo.toml index 4c01c1f061b7a8e70251a22e46add05b86971d60..cf31f9eccc9c6a6a404ba66316525f55b960aade 100644 --- a/substrate/frame/contracts/fixtures/Cargo.toml +++ b/substrate/frame/contracts/fixtures/Cargo.toml @@ -11,13 +11,13 @@ description = "Fixtures for testing contracts pallet." workspace = true [dependencies] +anyhow = { workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -anyhow = { workspace = true, default-features = true } [build-dependencies] +anyhow = { workspace = true, default-features = true } parity-wasm = { workspace = true } tempfile = { workspace = true } toml = { workspace = true } twox-hash = { workspace = true, default-features = true } -anyhow = { workspace = true, default-features = true } diff --git a/substrate/frame/contracts/fixtures/build/Cargo.toml b/substrate/frame/contracts/fixtures/build/Cargo.toml index ba487a2bb5ca68f8533dec7ae32541dd0f429756..18e8c2767d5f6f4e68c1dc4fb77cfab05eebd601 100644 --- a/substrate/frame/contracts/fixtures/build/Cargo.toml +++ b/substrate/frame/contracts/fixtures/build/Cargo.toml @@ -8,9 +8,9 @@ edition = "2021" # All paths or versions are injected dynamically by the build script. [dependencies] -uapi = { package = 'pallet-contracts-uapi', path = "", default-features = false } common = { package = 'pallet-contracts-fixtures-common', path = "" } polkavm-derive = { version = "" } +uapi = { package = 'pallet-contracts-uapi', path = "", default-features = false } [profile.release] opt-level = 3 diff --git a/substrate/frame/contracts/mock-network/Cargo.toml b/substrate/frame/contracts/mock-network/Cargo.toml index d6e2d51ef452309cf30e884d7fcb7165e990b366..84aa95694b5090fd7aeea408d858f5837639f625 100644 --- a/substrate/frame/contracts/mock-network/Cargo.toml +++ b/substrate/frame/contracts/mock-network/Cargo.toml @@ -19,13 +19,10 @@ frame-system = { workspace = true } pallet-assets = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-contracts = { workspace = true, default-features = true } -pallet-contracts-uapi = { workspace = true } pallet-contracts-proc-macro = { workspace = true, default-features = true } -pallet-insecure-randomness-collective-flip = { workspace = true, default-features = true } +pallet-contracts-uapi = { workspace = 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 } @@ -43,8 +40,6 @@ xcm-executor = { workspace = true } xcm-simulator = { workspace = true, default-features = true } [dev-dependencies] -assert_matches = { workspace = true } -pretty_assertions = { workspace = true } pallet-contracts-fixtures = { workspace = true } [features] @@ -55,10 +50,7 @@ std = [ "frame-system/std", "pallet-balances/std", "pallet-contracts/std", - "pallet-insecure-randomness-collective-flip/std", - "pallet-proxy/std", "pallet-timestamp/std", - "pallet-utility/std", "pallet-xcm/std", "scale-info/std", "sp-api/std", @@ -77,9 +69,7 @@ runtime-benchmarks = [ "pallet-balances/runtime-benchmarks", "pallet-contracts/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", - "pallet-proxy/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", - "pallet-utility/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", @@ -87,4 +77,5 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/substrate/frame/contracts/mock-network/src/lib.rs b/substrate/frame/contracts/mock-network/src/lib.rs index cb9e22439b76d824d6bed684ae01de37ebc26120..c918cd39ed915c4f4540f6b15d48810876f2d4fe 100644 --- a/substrate/frame/contracts/mock-network/src/lib.rs +++ b/substrate/frame/contracts/mock-network/src/lib.rs @@ -99,6 +99,7 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { (relay_sovereign_account_id(), INITIAL_BALANCE), (BOB, INITIAL_BALANCE), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -137,6 +138,7 @@ pub fn relay_ext() -> sp_io::TestExternalities { (parachain_sovereign_account_id(1), INITIAL_BALANCE), (parachain_account_sovereign_account_id(1, ALICE), INITIAL_BALANCE), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/contracts/proc-macro/src/lib.rs b/substrate/frame/contracts/proc-macro/src/lib.rs index 4aba1d24dbd5889e925fb9bbab53cf600642ab77..5c3c34e6ef32d06a567fc2b7f1373080c66a79ca 100644 --- a/substrate/frame/contracts/proc-macro/src/lib.rs +++ b/substrate/frame/contracts/proc-macro/src/lib.rs @@ -650,10 +650,9 @@ fn expand_functions(def: &EnvDef, expand_mode: ExpandMode) -> TokenStream2 { let result = #body; if ::log::log_enabled!(target: "runtime::contracts::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(); - ctx.ext().append_debug_buffer(msg); + let mut msg = alloc::string::String::default(); + let _ = core::write!(&mut msg, #trace_fmt_str, #( #trace_fmt_args, )* result); + ctx.ext().append_debug_buffer(&msg); } result } diff --git a/substrate/frame/contracts/src/schedule.rs b/substrate/frame/contracts/src/schedule.rs index 80b8c54b1e1d0dbeb655e0fedfa922da3792f21a..285184280fcba0cc006a6bb629a1b5bdc263109c 100644 --- a/substrate/frame/contracts/src/schedule.rs +++ b/substrate/frame/contracts/src/schedule.rs @@ -114,7 +114,7 @@ impl Limits { #[scale_info(skip_type_params(T))] pub struct InstructionWeights<T: Config> { /// Base instruction `ref_time` Weight. - /// Should match to wasmi's `1` fuel (see <https://github.com/paritytech/wasmi/issues/701>). + /// Should match to wasmi's `1` fuel (see <https://github.com/wasmi-labs/wasmi/issues/701>). pub base: u32, /// The type parameter is used in the default implementation. #[codec(skip)] diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index b01d0aa4fa48afbf478c93547803c8e7d1106010..9bba55f82b4e1dfa7dd9990e2759207555fcd819 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -553,7 +553,7 @@ impl ExtBuilder { sp_tracing::try_init_simple(); self.set_associated_consts(); let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); - pallet_balances::GenesisConfig::<Test> { balances: vec![] } + pallet_balances::GenesisConfig::<Test> { balances: vec![], ..Default::default() } .assimilate_storage(&mut t) .unwrap(); let mut ext = sp_io::TestExternalities::new(t); diff --git a/substrate/frame/contracts/src/transient_storage.rs b/substrate/frame/contracts/src/transient_storage.rs index c795a966385a92572958cf67202e4d7f5b36e85c..c9b1dac1ad75299ebb51bb3b8ca9cdbf7dc69442 100644 --- a/substrate/frame/contracts/src/transient_storage.rs +++ b/substrate/frame/contracts/src/transient_storage.rs @@ -22,11 +22,11 @@ use crate::{ storage::WriteOutcome, Config, Error, }; +use alloc::{collections::BTreeMap, vec::Vec}; use codec::Encode; -use core::marker::PhantomData; +use core::{marker::PhantomData, mem}; 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)] diff --git a/substrate/frame/contracts/uapi/Cargo.toml b/substrate/frame/contracts/uapi/Cargo.toml index 09c70c287899667735be6dfe5fe179425a499df8..8297c35b31db55e8bd72cfc5aa78322ae8ebbede 100644 --- a/substrate/frame/contracts/uapi/Cargo.toml +++ b/substrate/frame/contracts/uapi/Cargo.toml @@ -12,16 +12,16 @@ description = "Exposes all the host functions that a contract can import." 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 } +paste = { workspace = true } +scale-info = { features = ["derive"], optional = true, workspace = true } [package.metadata.docs.rs] -default-target = ["wasm32-unknown-unknown"] +targets = ["wasm32-unknown-unknown"] [features] default = ["scale"] diff --git a/substrate/frame/conviction-voting/Cargo.toml b/substrate/frame/conviction-voting/Cargo.toml index fdb4310610d935dd4ec4b210676638ab9e0a3850..e2d483609769d3f8a7b995d23402b2cb1a8ac38a 100644 --- a/substrate/frame/conviction-voting/Cargo.toml +++ b/substrate/frame/conviction-voting/Cargo.toml @@ -21,17 +21,16 @@ codec = { features = [ "derive", "max-encoded-len", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { features = ["derive"], optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } sp-io = { workspace = true } sp-runtime = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } -pallet-scheduler = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } [features] @@ -42,7 +41,6 @@ std = [ "frame-support/std", "frame-system/std", "pallet-balances/std", - "pallet-scheduler/std", "scale-info/std", "serde", "sp-core/std", @@ -54,13 +52,11 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-balances/runtime-benchmarks", - "pallet-scheduler/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "pallet-balances/try-runtime", - "pallet-scheduler/try-runtime", "sp-runtime/try-runtime", ] diff --git a/substrate/frame/conviction-voting/src/tests.rs b/substrate/frame/conviction-voting/src/tests.rs index c6c50dc5d964b0127b0705ecb340850bdbcdda97..8e8b41ba63eb49d60392fb2da8a458249731cf14 100644 --- a/substrate/frame/conviction-voting/src/tests.rs +++ b/substrate/frame/conviction-voting/src/tests.rs @@ -161,6 +161,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); pallet_balances::GenesisConfig::<Test> { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/core-fellowship/Cargo.toml b/substrate/frame/core-fellowship/Cargo.toml index 3d73ec58d6139610382320e62c526afc3ffc6bd6..c0017f477251445d4186fb53a0ad616632506251 100644 --- a/substrate/frame/core-fellowship/Cargo.toml +++ b/substrate/frame/core-fellowship/Cargo.toml @@ -17,16 +17,16 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +pallet-ranked-collective = { optional = true, workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -pallet-ranked-collective = { optional = true, workspace = true } [features] default = ["std"] diff --git a/substrate/frame/core-fellowship/src/benchmarking.rs b/substrate/frame/core-fellowship/src/benchmarking.rs index adb8a4a091b8dcf473277763d992a632e3fb539a..ac0d489953c1fc279d34ed1ca8c74ba7b8a9ea31 100644 --- a/substrate/frame/core-fellowship/src/benchmarking.rs +++ b/substrate/frame/core-fellowship/src/benchmarking.rs @@ -50,6 +50,7 @@ mod benchmarks { for _ in 0..rank { T::Members::promote(&member)?; } + #[allow(deprecated)] CoreFellowship::<T, I>::import(RawOrigin::Signed(member.clone()).into())?; Ok(member) } @@ -260,6 +261,23 @@ mod benchmarks { Ok(()) } + #[benchmark] + fn import_member() -> Result<(), BenchmarkError> { + let member = account("member", 0, SEED); + let sender = account("sender", 0, SEED); + + T::Members::induct(&member)?; + T::Members::promote(&member)?; + + assert!(!Member::<T, I>::contains_key(&member)); + + #[extrinsic_call] + _(RawOrigin::Signed(sender), member.clone()); + + assert!(Member::<T, I>::contains_key(&member)); + Ok(()) + } + #[benchmark] fn approve() -> Result<(), BenchmarkError> { let member = make_member::<T, I>(1)?; diff --git a/substrate/frame/core-fellowship/src/lib.rs b/substrate/frame/core-fellowship/src/lib.rs index c61447e36280a93e0ce553116c8b868d606061a3..22ba63b26161d45efea8189e60d24bf0cbb9e04b 100644 --- a/substrate/frame/core-fellowship/src/lib.rs +++ b/substrate/frame/core-fellowship/src/lib.rs @@ -21,6 +21,7 @@ //! This only handles members of non-zero rank. //! //! # Process Flow +//! //! - Begin with a call to `induct`, where some privileged origin (perhaps a pre-existing member of //! `rank > 1`) is able to make a candidate from an account and introduce it to be tracked in this //! pallet in order to allow evidence to be submitted and promotion voted on. @@ -36,8 +37,9 @@ //! `bump` to demote the candidate by one rank. //! - If a candidate fails to be promoted to a member within the `offboard_timeout` period, then //! anyone may call `bump` to remove the account's candidacy. -//! - Pre-existing members may call `import` to have their rank recognised and be inducted into this -//! pallet (to gain a salary and allow for eventual promotion). +//! - Pre-existing members may call `import_member` on themselves (formerly `import`) to have their +//! rank recognised and be inducted into this pallet (to gain a salary and allow for eventual +//! promotion). //! - If, externally to this pallet, a member or candidate has their rank removed completely, then //! `offboard` may be called to remove them entirely from this pallet. //! @@ -585,28 +587,44 @@ pub mod pallet { Ok(if replaced { Pays::Yes } else { Pays::No }.into()) } - /// Introduce an already-ranked individual of the collective into this pallet. The rank may - /// still be zero. + /// Introduce an already-ranked individual of the collective into this pallet. /// - /// This resets `last_proof` to the current block and `last_promotion` will be set to zero, - /// thereby delaying any automatic demotion but allowing immediate promotion. + /// The rank may still be zero. This resets `last_proof` to the current block and + /// `last_promotion` will be set to zero, thereby delaying any automatic demotion but + /// allowing immediate promotion. /// /// - `origin`: A signed origin of a ranked, but not tracked, account. #[pallet::weight(T::WeightInfo::import())] #[pallet::call_index(8)] + #[deprecated = "Use `import_member` instead"] + #[allow(deprecated)] // Otherwise FRAME will complain about using something deprecated. pub fn import(origin: OriginFor<T>) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - ensure!(!Member::<T, I>::contains_key(&who), Error::<T, I>::AlreadyInducted); - let rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?; + Self::do_import(who)?; - let now = frame_system::Pallet::<T>::block_number(); - Member::<T, I>::insert( - &who, - MemberStatus { is_active: true, last_promotion: 0u32.into(), last_proof: now }, - ); - Self::deposit_event(Event::<T, I>::Imported { who, rank }); + Ok(Pays::No.into()) // Successful imports are free + } - Ok(Pays::No.into()) + /// Introduce an already-ranked individual of the collective into this pallet. + /// + /// The rank may still be zero. Can be called by anyone on any collective member - including + /// the sender. + /// + /// This resets `last_proof` to the current block and `last_promotion` will be set to zero, + /// thereby delaying any automatic demotion but allowing immediate promotion. + /// + /// - `origin`: A signed origin of a ranked, but not tracked, account. + /// - `who`: The account ID of the collective member to be inducted. + #[pallet::weight(T::WeightInfo::set_partial_params())] + #[pallet::call_index(11)] + pub fn import_member( + origin: OriginFor<T>, + who: T::AccountId, + ) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + Self::do_import(who)?; + + Ok(Pays::No.into()) // Successful imports are free } /// Set the parameters partially. @@ -661,6 +679,24 @@ pub mod pallet { } } } + + /// Import `who` into the core-fellowship pallet. + /// + /// `who` must be a member of the collective but *not* already imported. + pub(crate) fn do_import(who: T::AccountId) -> DispatchResult { + ensure!(!Member::<T, I>::contains_key(&who), Error::<T, I>::AlreadyInducted); + let rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?; + + let now = frame_system::Pallet::<T>::block_number(); + Member::<T, I>::insert( + &who, + MemberStatus { is_active: true, last_promotion: 0u32.into(), last_proof: now }, + ); + Self::deposit_event(Event::<T, I>::Imported { who, rank }); + + Ok(()) + } + /// Convert a rank into a `0..RANK_COUNT` index suitable for the arrays in Params. /// /// Rank 1 becomes index 0, rank `RANK_COUNT` becomes index `RANK_COUNT - 1`. Any rank not @@ -766,6 +802,7 @@ impl<T: Config<I>, I: 'static> pallet_ranked_collective::BenchmarkSetup<<T as frame_system::Config>::AccountId> for Pallet<T, I> { fn ensure_member(who: &<T as frame_system::Config>::AccountId) { + #[allow(deprecated)] Self::import(frame_system::RawOrigin::Signed(who.clone()).into()).unwrap(); } } diff --git a/substrate/frame/core-fellowship/src/tests/integration.rs b/substrate/frame/core-fellowship/src/tests/integration.rs index 7a48ed9783e7ba646aaa979b0aa3c20635ee6a54..b2149336547d3d9c7c43d00dbe5e0f61071d28a1 100644 --- a/substrate/frame/core-fellowship/src/tests/integration.rs +++ b/substrate/frame/core-fellowship/src/tests/integration.rs @@ -17,8 +17,10 @@ //! Integration test together with the ranked-collective pallet. +#![allow(deprecated)] + use frame_support::{ - assert_noop, assert_ok, derive_impl, hypothetically, ord_parameter_types, + assert_noop, assert_ok, derive_impl, hypothetically, hypothetically_ok, ord_parameter_types, pallet_prelude::Weight, parameter_types, traits::{ConstU16, EitherOf, IsInVec, MapSuccess, NoOpPoll, TryMapSuccess}, @@ -170,6 +172,37 @@ fn evidence(e: u32) -> Evidence<Test, ()> { .expect("Static length matches") } +#[test] +fn import_simple_works() { + new_test_ext().execute_with(|| { + for i in 0u16..9 { + let acc = i as u64; + + // Does not work yet + assert_noop!(CoreFellowship::import(signed(acc)), Error::<Test>::Unranked); + assert_noop!( + CoreFellowship::import_member(signed(acc + 1), acc), + Error::<Test>::Unranked + ); + + assert_ok!(Club::add_member(RuntimeOrigin::root(), acc)); + promote_n_times(acc, i); + + hypothetically_ok!(CoreFellowship::import(signed(acc))); + hypothetically_ok!(CoreFellowship::import_member(signed(acc), acc)); + // Works from other accounts + assert_ok!(CoreFellowship::import_member(signed(acc + 1), acc)); + + // Does not work again + assert_noop!(CoreFellowship::import(signed(acc)), Error::<Test>::AlreadyInducted); + assert_noop!( + CoreFellowship::import_member(signed(acc + 1), acc), + Error::<Test>::AlreadyInducted + ); + } + }); +} + #[test] fn swap_simple_works() { new_test_ext().execute_with(|| { @@ -178,7 +211,8 @@ fn swap_simple_works() { assert_ok!(Club::add_member(RuntimeOrigin::root(), acc)); promote_n_times(acc, i); - assert_ok!(CoreFellowship::import(signed(acc))); + hypothetically_ok!(CoreFellowship::import(signed(acc))); + assert_ok!(CoreFellowship::import_member(signed(acc), acc)); // Swapping normally works: assert_ok!(Club::exchange_member(RuntimeOrigin::root(), acc, acc + 10)); diff --git a/substrate/frame/core-fellowship/src/tests/unit.rs b/substrate/frame/core-fellowship/src/tests/unit.rs index 11d1ea9fe5b7563f16d6d9c7118071ec2b1a2e92..f4418ed439d0c00f715b40a281596461ce421e20 100644 --- a/substrate/frame/core-fellowship/src/tests/unit.rs +++ b/substrate/frame/core-fellowship/src/tests/unit.rs @@ -17,6 +17,8 @@ //! The crate's tests. +#![allow(deprecated)] + use std::collections::BTreeMap; use core::cell::RefCell; @@ -222,6 +224,66 @@ fn set_partial_params_works() { }); } +#[test] +fn import_member_works() { + new_test_ext().execute_with(|| { + assert_noop!(CoreFellowship::import_member(signed(0), 0), Error::<Test>::Unranked); + assert_noop!(CoreFellowship::import(signed(0)), Error::<Test>::Unranked); + + // Make induction work: + set_rank(0, 1); + assert!(!Member::<Test>::contains_key(0), "not yet imported"); + + // `import_member` can be used to induct ourselves: + hypothetically!({ + assert_ok!(CoreFellowship::import_member(signed(0), 0)); + assert!(Member::<Test>::contains_key(0), "got imported"); + + // Twice does not work: + assert_noop!( + CoreFellowship::import_member(signed(0), 0), + Error::<Test>::AlreadyInducted + ); + assert_noop!(CoreFellowship::import(signed(0)), Error::<Test>::AlreadyInducted); + }); + + // But we could have also used `import`: + hypothetically!({ + assert_ok!(CoreFellowship::import(signed(0))); + assert!(Member::<Test>::contains_key(0), "got imported"); + + // Twice does not work: + assert_noop!( + CoreFellowship::import_member(signed(0), 0), + Error::<Test>::AlreadyInducted + ); + assert_noop!(CoreFellowship::import(signed(0)), Error::<Test>::AlreadyInducted); + }); + }); +} + +#[test] +fn import_member_same_as_import() { + new_test_ext().execute_with(|| { + for rank in 0..=9 { + set_rank(0, rank); + + let import_root = hypothetically!({ + assert_ok!(CoreFellowship::import(signed(0))); + sp_io::storage::root(sp_runtime::StateVersion::V1) + }); + + let import_member_root = hypothetically!({ + assert_ok!(CoreFellowship::import_member(signed(1), 0)); + sp_io::storage::root(sp_runtime::StateVersion::V1) + }); + + // `import` and `import_member` do exactly the same thing. + assert_eq!(import_root, import_member_root); + } + }); +} + #[test] fn induct_works() { new_test_ext().execute_with(|| { diff --git a/substrate/frame/core-fellowship/src/weights.rs b/substrate/frame/core-fellowship/src/weights.rs index 9bca8cb56094f7bbd03ce979f12b0fec2e8526ac..e6381c854d344a53dceb903da9ac3b1ed6e95c57 100644 --- a/substrate/frame/core-fellowship/src/weights.rs +++ b/substrate/frame/core-fellowship/src/weights.rs @@ -61,6 +61,7 @@ pub trait WeightInfo { fn promote_fast(r: u32, ) -> Weight; fn offboard() -> Weight; fn import() -> Weight; + fn import_member() -> Weight; fn approve() -> Weight; fn submit_evidence() -> Weight; } @@ -76,7 +77,7 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Estimated: `0` // Minimum execution time: 6_652_000 picoseconds. Weight::from_parts(7_082_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `CoreFellowship::Params` (r:1 w:1) /// Proof: `CoreFellowship::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) @@ -86,8 +87,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Estimated: `1853` // Minimum execution time: 12_485_000 picoseconds. Weight::from_parts(12_784_000, 1853) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `CoreFellowship::Member` (r:1 w:1) /// Proof: `CoreFellowship::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) @@ -109,8 +110,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Estimated: `19894` // Minimum execution time: 61_243_000 picoseconds. Weight::from_parts(63_033_000, 19894) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `CoreFellowship::Member` (r:1 w:1) /// Proof: `CoreFellowship::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) @@ -132,8 +133,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Estimated: `19894` // Minimum execution time: 65_063_000 picoseconds. Weight::from_parts(67_047_000, 19894) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `RankedCollective::Members` (r:1 w:0) /// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) @@ -145,8 +146,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Estimated: `3514` // Minimum execution time: 21_924_000 picoseconds. Weight::from_parts(22_691_000, 3514) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `CoreFellowship::Member` (r:1 w:1) /// Proof: `CoreFellowship::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) @@ -164,8 +165,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Estimated: `3514` // Minimum execution time: 24_720_000 picoseconds. Weight::from_parts(25_580_000, 3514) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().writes(5_u64)) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `RankedCollective::Members` (r:1 w:1) /// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) @@ -187,8 +188,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Estimated: `19894` // Minimum execution time: 58_481_000 picoseconds. Weight::from_parts(59_510_000, 19894) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `RankedCollective::Members` (r:1 w:1) /// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) @@ -211,10 +212,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { Weight::from_parts(42_220_685, 19894) // Standard Error: 18_061 .saturating_add(Weight::from_parts(13_858_309, 0).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(3_u64)) - .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 2489).saturating_mul(r.into())) } /// Storage: `RankedCollective::Members` (r:1 w:0) @@ -229,8 +230,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Estimated: `3514` // Minimum execution time: 17_492_000 picoseconds. Weight::from_parts(18_324_000, 3514) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `CoreFellowship::Member` (r:1 w:1) /// Proof: `CoreFellowship::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) @@ -242,8 +243,18 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Estimated: `3514` // Minimum execution time: 16_534_000 picoseconds. Weight::from_parts(17_046_000, 3514) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + fn import_member() -> Weight { + // Proof Size summary in bytes: + // Measured: `285` + // Estimated: `3514` + // Minimum execution time: 23_239_000 picoseconds. + Weight::from_parts(23_684_000, 0) + .saturating_add(Weight::from_parts(0, 3514)) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } /// Storage: `RankedCollective::Members` (r:1 w:0) /// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) @@ -257,8 +268,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Estimated: `19894` // Minimum execution time: 42_264_000 picoseconds. Weight::from_parts(43_281_000, 19894) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `CoreFellowship::Member` (r:1 w:0) /// Proof: `CoreFellowship::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) @@ -270,8 +281,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Estimated: `19894` // Minimum execution time: 25_461_000 picoseconds. Weight::from_parts(26_014_000, 19894) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } @@ -454,6 +465,16 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + fn import_member() -> Weight { + // Proof Size summary in bytes: + // Measured: `285` + // Estimated: `3514` + // Minimum execution time: 23_239_000 picoseconds. + Weight::from_parts(23_684_000, 0) + .saturating_add(Weight::from_parts(0, 3514)) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } /// Storage: `RankedCollective::Members` (r:1 w:0) /// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) /// Storage: `CoreFellowship::Member` (r:1 w:1) diff --git a/substrate/frame/delegated-staking/Cargo.toml b/substrate/frame/delegated-staking/Cargo.toml index 8d5ccd342b6b639ae0644a5e1a14f0c0ed14be3e..3a2498fb99128d68b18a62c79505a77516974d9e 100644 --- a/substrate/frame/delegated-staking/Cargo.toml +++ b/substrate/frame/delegated-staking/Cargo.toml @@ -15,23 +15,22 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } scale-info = { features = ["derive"], workspace = true } +sp-io = { workspace = true } sp-runtime = { workspace = true } sp-staking = { workspace = true } -sp-io = { workspace = true } -log = { workspace = true } [dev-dependencies] +frame-election-provider-support = { workspace = true } +pallet-balances = { workspace = true, default-features = true } +pallet-nomination-pools = { workspace = true, default-features = true } +pallet-staking = { workspace = true, default-features = true } +pallet-staking-reward-curve = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } sp-tracing = { workspace = true, default-features = true } -pallet-staking = { workspace = true, default-features = true } -pallet-nomination-pools = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } -pallet-staking-reward-curve = { workspace = true, default-features = true } -frame-election-provider-support = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/delegated-staking/src/lib.rs b/substrate/frame/delegated-staking/src/lib.rs index 1d181eb29cab7321d94b083149a8e58a17ecd924..0dacfe9c55792f53e07fe708323ed6e0f8d5c784 100644 --- a/substrate/frame/delegated-staking/src/lib.rs +++ b/substrate/frame/delegated-staking/src/lib.rs @@ -520,7 +520,7 @@ impl<T: Config> Pallet<T> { let stake = T::CoreStaking::stake(who)?; // release funds from core staking. - T::CoreStaking::migrate_to_virtual_staker(who); + T::CoreStaking::migrate_to_virtual_staker(who)?; // transfer just released staked amount plus any free amount. let amount_to_transfer = diff --git a/substrate/frame/delegated-staking/src/mock.rs b/substrate/frame/delegated-staking/src/mock.rs index 811d5739f4e98f24c30c7514455dac049fbbc421..a4546e57dab5e8ebde6bb35bf48b8493c8779757 100644 --- a/substrate/frame/delegated-staking/src/mock.rs +++ b/substrate/frame/delegated-staking/src/mock.rs @@ -102,6 +102,7 @@ impl onchain::Config for OnChainSeqPhragmen { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Runtime { + type OldCurrency = Balances; type Currency = Balances; type UnixTime = pallet_timestamp::Pallet<Self>; type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>; @@ -188,6 +189,7 @@ impl ExtBuilder { (GENESIS_NOMINATOR_ONE, 1000), (GENESIS_NOMINATOR_TWO, 2000), ], + ..Default::default() } .assimilate_storage(&mut storage); diff --git a/substrate/frame/delegated-staking/src/tests.rs b/substrate/frame/delegated-staking/src/tests.rs index b7b82a43771eb388ee1a5e0e11145e614f56930f..c764e2741a2a4a0149853f2279bd8b556ba4b20b 100644 --- a/substrate/frame/delegated-staking/src/tests.rs +++ b/substrate/frame/delegated-staking/src/tests.rs @@ -671,12 +671,14 @@ mod staking_integration { )); assert_ok!(Staking::nominate(RuntimeOrigin::signed(agent), vec![GENESIS_VALIDATOR],)); let init_stake = Staking::stake(&agent).unwrap(); + // no extra provider added. + assert_eq!(System::providers(&agent), 1); // 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!(pallet_staking::asset::stakeable_balance::<T>(&agent), agent_amount); + assert_eq!(pallet_staking::asset::total_balance::<T>(&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 @@ -685,8 +687,9 @@ mod staking_integration { DelegatedStaking::generate_proxy_delegator(Agent::from(agent)).get(); 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); + // after migration, no provider left since free balance is 0 and staking pallet released + // all funds. + assert_eq!(System::providers(&agent), 0); 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); @@ -798,8 +801,6 @@ mod staking_integration { 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); diff --git a/substrate/frame/delegated-staking/src/types.rs b/substrate/frame/delegated-staking/src/types.rs index a78aa3f559060080344b84002d099ca8cb18caaa..14f49466f0e2867fec4da4a360a53fe816d72d22 100644 --- a/substrate/frame/delegated-staking/src/types.rs +++ b/substrate/frame/delegated-staking/src/types.rs @@ -131,10 +131,6 @@ impl<T: Config> AgentLedger<T> { /// /// Increments provider count if this is a new agent. pub(crate) fn update(self, key: &T::AccountId) { - if !<Agents<T>>::contains_key(key) { - // This is a new agent. Provide for this account. - frame_system::Pallet::<T>::inc_providers(key); - } <Agents<T>>::insert(key, self) } @@ -142,8 +138,6 @@ impl<T: Config> AgentLedger<T> { pub(crate) fn remove(key: &T::AccountId) { debug_assert!(<Agents<T>>::contains_key(key), "Agent should exist in storage"); <Agents<T>>::remove(key); - // Remove provider reference. - let _ = frame_system::Pallet::<T>::dec_providers(key).defensive(); } /// Effective total balance of the `Agent`. diff --git a/substrate/frame/democracy/Cargo.toml b/substrate/frame/democracy/Cargo.toml index 3cfea8bb312921b606a1df79222f5e29772cc109..189d64ccaa74120063764c17f8a78d6df1a299a8 100644 --- a/substrate/frame/democracy/Cargo.toml +++ b/substrate/frame/democracy/Cargo.toml @@ -19,20 +19,20 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { features = ["derive"], optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } +sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -sp-core = { workspace = true } -log = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } -pallet-scheduler = { workspace = true, default-features = true } pallet-preimage = { workspace = true, default-features = true } +pallet-scheduler = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/democracy/README.md b/substrate/frame/democracy/README.md index bbc5f1c65586ae7054e24df183ad12c6ac6357f5..d9d21e62447768bc7cb33e2caa2d02c77f71e9c8 100644 --- a/substrate/frame/democracy/README.md +++ b/substrate/frame/democracy/README.md @@ -96,7 +96,7 @@ This call can only be made by the `CancellationOrigin`. This call can only be made by the `ExternalOrigin`. -- `external_propose` - Schedules a proposal to become a referendum once it is is legal +- `external_propose` - Schedules a proposal to become a referendum once it is legal for an externally proposed referendum. #### External Majority Origin diff --git a/substrate/frame/democracy/src/lib.rs b/substrate/frame/democracy/src/lib.rs index 27bc36a756e4b36f3d6f3afdc2eb351063f6df56..2c662fbad26a5a5973ca3c4986e0189d5ca8271f 100644 --- a/substrate/frame/democracy/src/lib.rs +++ b/substrate/frame/democracy/src/lib.rs @@ -113,7 +113,7 @@ //! //! This call can only be made by the `ExternalOrigin`. //! -//! - `external_propose` - Schedules a proposal to become a referendum once it is is legal for an +//! - `external_propose` - Schedules a proposal to become a referendum once it is legal for an //! externally proposed referendum. //! //! #### External Majority Origin diff --git a/substrate/frame/democracy/src/tests.rs b/substrate/frame/democracy/src/tests.rs index 10e5ee75611d5378a99ff2062d10f235f047861d..77774480068488d5af4aa9381a8faad7dfacb81b 100644 --- a/substrate/frame/democracy/src/tests.rs +++ b/substrate/frame/democracy/src/tests.rs @@ -169,6 +169,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); pallet_balances::GenesisConfig::<Test> { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/election-provider-multi-phase/Cargo.toml b/substrate/frame/election-provider-multi-phase/Cargo.toml index ff2a997fafe03ea883e9ae3b2124f8e4d80a6c13..9a4a2a839346449e3016dd70dc2fa78988adfa5d 100644 --- a/substrate/frame/election-provider-multi-phase/Cargo.toml +++ b/substrate/frame/election-provider-multi-phase/Cargo.toml @@ -18,20 +18,20 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } +log = { workspace = true } scale-info = { features = [ "derive", ], workspace = true } -log = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -sp-io = { workspace = true } +frame-election-provider-support = { workspace = true } +sp-arithmetic = { workspace = true } sp-core = { workspace = true } -sp-runtime = { workspace = true } +sp-io = { workspace = true } sp-npos-elections = { workspace = true } -sp-arithmetic = { workspace = true } -frame-election-provider-support = { workspace = true } +sp-runtime = { workspace = true } # Optional imports for benchmarking frame-benchmarking = { optional = true, workspace = true } @@ -40,14 +40,14 @@ rand = { features = ["alloc", "small_rng"], optional = true, workspace = true } strum = { features = ["derive"], optional = true, workspace = true } [dev-dependencies] +frame-benchmarking = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } sp-core = { workspace = true } sp-io = { workspace = true, default-features = true } sp-npos-elections = { workspace = true } sp-tracing = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } -frame-benchmarking = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/election-provider-multi-phase/src/mock.rs b/substrate/frame/election-provider-multi-phase/src/mock.rs index 2e5ac2527203fc9373883947a906b6f1aad966dc..d0797e100fcdf0c06e3ce3b853294802102e90b0 100644 --- a/substrate/frame/election-provider-multi-phase/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/src/mock.rs @@ -600,6 +600,7 @@ impl ExtBuilder { (999, 100), (9999, 100), ], + ..Default::default() } .assimilate_storage(&mut storage); diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml b/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml index 771376e06656a03c9fca0715b77a466cd6165593..f11f9c04dbf4a6b5da7a04ebb17a1af89826df36 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml @@ -16,30 +16,30 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dev-dependencies] -parking_lot = { workspace = true, default-features = true } codec = { features = ["derive"], workspace = true, default-features = true } -scale-info = { features = ["derive"], workspace = true, default-features = true } log = { workspace = true } +parking_lot = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } -sp-staking = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } sp-npos-elections = { workspace = true } +sp-runtime = { workspace = true, default-features = true } +sp-staking = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } -frame-support = { workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } +frame-support = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } -pallet-election-provider-multi-phase = { workspace = true, default-features = true } -pallet-staking = { workspace = true, default-features = true } -pallet-nomination-pools = { workspace = true, default-features = true } pallet-bags-list = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } +pallet-delegated-staking = { workspace = true, default-features = true } +pallet-election-provider-multi-phase = { workspace = true, default-features = true } +pallet-nomination-pools = { workspace = true, default-features = true } pallet-session = { workspace = true, default-features = true } +pallet-staking = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } [features] try-runtime = [ @@ -48,6 +48,7 @@ try-runtime = [ "frame-system/try-runtime", "pallet-bags-list/try-runtime", "pallet-balances/try-runtime", + "pallet-delegated-staking/try-runtime", "pallet-election-provider-multi-phase/try-runtime", "pallet-nomination-pools/try-runtime", "pallet-session/try-runtime", diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs index 26a6345e145ff1050581e32534d8b6fa09ba450d..b1029e89fe85f65650fb5406314241c220cd2b28 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs @@ -327,8 +327,8 @@ fn automatic_unbonding_pools() { assert_eq!(<Runtime as pallet_nomination_pools::Config>::MaxUnbonding::get(), 1); // init state of pool members. - let init_stakeable_balance_2 = pallet_staking::asset::stakeable_balance::<Runtime>(&2); - let init_stakeable_balance_3 = pallet_staking::asset::stakeable_balance::<Runtime>(&3); + let init_free_balance_2 = Balances::free_balance(2); + let init_free_balance_3 = Balances::free_balance(3); let pool_bonded_account = Pools::generate_bonded_account(1); @@ -378,7 +378,7 @@ fn automatic_unbonding_pools() { System::reset_events(); let staked_before_withdraw_pool = staked_amount_for(pool_bonded_account); - assert_eq!(pallet_staking::asset::stakeable_balance::<Runtime>(&pool_bonded_account), 26); + assert_eq!(delegated_balance_for(pool_bonded_account), 5 + 10 + 10); // now unbonding 3 will work, although the pool's ledger still has the unlocking chunks // filled up. @@ -390,13 +390,13 @@ fn automatic_unbonding_pools() { [ // auto-withdraw happened as expected to release 2's unbonding funds, but the funds // were not transferred to 2 and stay in the pool's transferrable balance instead. - pallet_staking::Event::Withdrawn { stash: 7939698191839293293, amount: 10 }, - pallet_staking::Event::Unbonded { stash: 7939698191839293293, amount: 10 } + pallet_staking::Event::Withdrawn { stash: pool_bonded_account, amount: 10 }, + pallet_staking::Event::Unbonded { stash: pool_bonded_account, amount: 10 } ] ); // balance of the pool remains the same, it hasn't withdraw explicitly from the pool yet. - assert_eq!(pallet_staking::asset::stakeable_balance::<Runtime>(&pool_bonded_account), 26); + assert_eq!(delegated_balance_for(pool_bonded_account), 25); // but the locked amount in the pool's account decreases due to the auto-withdraw: assert_eq!(staked_before_withdraw_pool - 10, staked_amount_for(pool_bonded_account)); @@ -405,12 +405,12 @@ fn automatic_unbonding_pools() { // however, note that the withdrawing from the pool still works for 2, the funds are taken // from the pool's non staked balance. - assert_eq!(pallet_staking::asset::stakeable_balance::<Runtime>(&pool_bonded_account), 26); - assert_eq!(pallet_staking::asset::staked::<Runtime>(&pool_bonded_account), 15); + assert_eq!(delegated_balance_for(pool_bonded_account), 25); + assert_eq!(staked_amount_for(pool_bonded_account), 15); assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(2), 2, 10)); - assert_eq!(pallet_staking::asset::stakeable_balance::<Runtime>(&pool_bonded_account), 16); + assert_eq!(delegated_balance_for(pool_bonded_account), 15); - assert_eq!(pallet_staking::asset::stakeable_balance::<Runtime>(&2), 20); + assert_eq!(Balances::free_balance(2), 20); assert_eq!(TotalValueLocked::<Runtime>::get(), 15); // 3 cannot withdraw yet. @@ -429,15 +429,9 @@ fn automatic_unbonding_pools() { assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(3), 3, 10)); // final conditions are the expected. - assert_eq!(pallet_staking::asset::stakeable_balance::<Runtime>(&pool_bonded_account), 6); // 5 init bonded + ED - assert_eq!( - pallet_staking::asset::stakeable_balance::<Runtime>(&2), - init_stakeable_balance_2 - ); - assert_eq!( - pallet_staking::asset::stakeable_balance::<Runtime>(&3), - init_stakeable_balance_3 - ); + assert_eq!(delegated_balance_for(pool_bonded_account), 5); // 5 init bonded + assert_eq!(Balances::free_balance(2), init_free_balance_2); + assert_eq!(Balances::free_balance(3), init_free_balance_3); assert_eq!(TotalValueLocked::<Runtime>::get(), init_tvl); }); diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs index eaab848c1694485c47e4274ea83f223be53cd183..3a6496436187008352e06f74a1960c5ec92690a2 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs @@ -21,6 +21,7 @@ use frame_support::{ assert_ok, parameter_types, traits, traits::{Hooks, UnfilteredDispatchable, VariantCountOf}, weights::constants, + PalletId, }; use frame_system::EnsureRoot; use sp_core::{ConstU32, Get}; @@ -36,7 +37,7 @@ use sp_runtime::{ }; use sp_staking::{ offence::{OffenceDetails, OnOffenceHandler}, - EraIndex, SessionIndex, + Agent, DelegationInterface, EraIndex, SessionIndex, StakingInterface, }; use std::collections::BTreeMap; @@ -68,6 +69,7 @@ frame_support::construct_runtime!( System: frame_system, ElectionProviderMultiPhase: pallet_election_provider_multi_phase, Staking: pallet_staking, + DelegatedStaking: pallet_delegated_staking, Pools: pallet_nomination_pools, Balances: pallet_balances, BagsList: pallet_bags_list, @@ -77,7 +79,7 @@ frame_support::construct_runtime!( } ); -pub(crate) type AccountId = u64; +pub(crate) type AccountId = u128; pub(crate) type AccountIndex = u32; pub(crate) type BlockNumber = u32; pub(crate) type Balance = u64; @@ -87,8 +89,10 @@ pub(crate) type Moment = u32; #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Runtime { + type AccountId = AccountId; type Block = Block; type AccountData = pallet_balances::AccountData<Balance>; + type Lookup = sp_runtime::traits::IdentityLookup<Self::AccountId>; } const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); @@ -265,7 +269,8 @@ impl pallet_nomination_pools::Config for Runtime { type RewardCounter = sp_runtime::FixedU128; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; - type StakeAdapter = pallet_nomination_pools::adapter::TransferStake<Self, Staking>; + type StakeAdapter = + pallet_nomination_pools::adapter::DelegateStake<Self, Staking, DelegatedStaking>; type PostUnbondingPoolsWindow = ConstU32<2>; type PalletId = PoolsPalletId; type MaxMetadataLen = ConstU32<256>; @@ -274,6 +279,21 @@ impl pallet_nomination_pools::Config for Runtime { type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>; } +parameter_types! { + pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk"); + pub const SlashRewardFraction: Perbill = Perbill::from_percent(1); +} + +impl pallet_delegated_staking::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PalletId = DelegatedStakingPalletId; + type Currency = Balances; + type OnSlash = (); + type SlashRewardFraction = SlashRewardFraction; + type RuntimeHoldReason = RuntimeHoldReason; + type CoreStaking = Staking; +} + parameter_types! { pub static MaxUnlockingChunks: u32 = 32; } @@ -285,6 +305,7 @@ pub(crate) const SLASHING_DISABLING_FACTOR: usize = 3; #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Runtime { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = Balance; type UnixTime = Timestamp; @@ -302,7 +323,7 @@ impl pallet_staking::Config for Runtime { type NominationsQuota = pallet_staking::FixedNominationsQuota<MAX_QUOTA_NOMINATIONS>; type TargetList = pallet_staking::UseValidatorsMap<Self>; type MaxUnlockingChunks = MaxUnlockingChunks; - type EventListeners = Pools; + type EventListeners = (Pools, DelegatedStaking); type WeightInfo = pallet_staking::weights::SubstrateWeight<Runtime>; type DisablingStrategy = pallet_staking::UpToLimitWithReEnablingDisablingStrategy<SLASHING_DISABLING_FACTOR>; @@ -502,7 +523,7 @@ impl Default for BalancesExtBuilder { (100, 100), (200, 100), // stashes - (11, 1000), + (11, 1100), (21, 2000), (31, 3000), (41, 4000), @@ -546,6 +567,7 @@ impl ExtBuilder { let _ = pallet_balances::GenesisConfig::<Runtime> { balances: self.balances_builder.balances.clone(), + ..Default::default() } .assimilate_storage(&mut storage); @@ -581,7 +603,7 @@ impl ExtBuilder { // set the keys for the first session. keys: stakers .into_iter() - .map(|(id, ..)| (id, id, SessionKeys { other: (id as u64).into() })) + .map(|(id, ..)| (id, id, SessionKeys { other: (id as AccountId as u64).into() })) .collect(), ..Default::default() } @@ -926,7 +948,11 @@ pub(crate) fn set_minimum_election_score( } pub(crate) fn staked_amount_for(account_id: AccountId) -> Balance { - pallet_staking::asset::staked::<Runtime>(&account_id) + Staking::total_stake(&account_id).expect("account must be staker") +} + +pub(crate) fn delegated_balance_for(account_id: AccountId) -> Balance { + DelegatedStaking::agent_balance(Agent::from(account_id)).unwrap_or_default() } pub(crate) fn staking_events() -> Vec<pallet_staking::Event<Runtime>> { diff --git a/substrate/frame/election-provider-support/Cargo.toml b/substrate/frame/election-provider-support/Cargo.toml index cae20d1b46a480a7cf0c3b542870425d67283363..32fa381e1d2745851dcea7a7b10ddac1dc8b1998 100644 --- a/substrate/frame/election-provider-support/Cargo.toml +++ b/substrate/frame/election-provider-support/Cargo.toml @@ -16,14 +16,14 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-election-provider-solution-type = { workspace = true, default-features = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-arithmetic = { workspace = true } +sp-core = { workspace = true } sp-npos-elections = { workspace = true } sp-runtime = { workspace = true } -sp-core = { workspace = true } [dev-dependencies] rand = { features = ["small_rng"], workspace = true, default-features = true } diff --git a/substrate/frame/election-provider-support/solution-type/Cargo.toml b/substrate/frame/election-provider-support/solution-type/Cargo.toml index e24ed7f079fe8f5ae44f1178df7d895cfd97ad75..c2f307016f6b9c1641aacb65dcd0cb093f826fd5 100644 --- a/substrate/frame/election-provider-support/solution-type/Cargo.toml +++ b/substrate/frame/election-provider-support/solution-type/Cargo.toml @@ -18,10 +18,10 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { features = ["full", "visit"], workspace = true } -quote = { workspace = true } -proc-macro2 = { workspace = true } proc-macro-crate = { workspace = true } +proc-macro2 = { workspace = true } +quote = { workspace = true } +syn = { features = ["full", "visit"], workspace = true } [dev-dependencies] codec = { workspace = true, default-features = true } diff --git a/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml b/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml index 86abbf9677e020669b362dd51b9972bc34beaab2..d82a8acb2f84af7b280b2ce8a348a4ac34a6db8c 100644 --- a/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml +++ b/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml @@ -21,14 +21,14 @@ honggfuzz = { workspace = true } rand = { features = ["small_rng", "std"], workspace = true, default-features = true } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-election-provider-solution-type = { workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } sp-arithmetic = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } # used by generate_solution_type: -sp-npos-elections = { workspace = true } frame-support = { workspace = true, default-features = true } +sp-npos-elections = { workspace = true } [[bin]] name = "compact" diff --git a/substrate/frame/elections-phragmen/Cargo.toml b/substrate/frame/elections-phragmen/Cargo.toml index c1b12b3da4d80c08e588d35f7fabb6a1085ff957..b24ec7bd637e21083de8e4f16109907f86347ca6 100644 --- a/substrate/frame/elections-phragmen/Cargo.toml +++ b/substrate/frame/elections-phragmen/Cargo.toml @@ -19,11 +19,11 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-npos-elections = { workspace = true } diff --git a/substrate/frame/elections-phragmen/src/lib.rs b/substrate/frame/elections-phragmen/src/lib.rs index effbb6e786c0d0b948c843656e72978bbc8eb3ec..4a40d44e407761d08b4e9c2741ced71153a80555 100644 --- a/substrate/frame/elections-phragmen/src/lib.rs +++ b/substrate/frame/elections-phragmen/src/lib.rs @@ -616,7 +616,7 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event<T: Config> { /// A new term with new_members. This indicates that enough candidates existed to run - /// the election, not that enough have has been elected. The inner value must be examined + /// the election, not that enough have been elected. The inner value must be examined /// for this purpose. A `NewTerm(\[\])` indicates that some candidates got their bond /// slashed and none were elected, whilst `EmptyTerm` means that no candidates existed to /// begin with. @@ -1476,6 +1476,7 @@ mod tests { (5, 50 * self.balance_factor), (6, 60 * self.balance_factor), ], + ..Default::default() }, elections: elections_phragmen::GenesisConfig::<Test> { members: self.genesis_members, diff --git a/substrate/frame/examples/Cargo.toml b/substrate/frame/examples/Cargo.toml index 0c6ad5ef0978eb05b2466bf063a219db274dc4c9..40d6959378b8799e3b1e6e655373bf4e5409652e 100644 --- a/substrate/frame/examples/Cargo.toml +++ b/substrate/frame/examples/Cargo.toml @@ -18,14 +18,15 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] pallet-default-config-example = { workspace = true } pallet-dev-mode = { workspace = true } +pallet-example-authorization-tx-extension = { workspace = true } pallet-example-basic = { workspace = true } pallet-example-frame-crate = { workspace = true } pallet-example-kitchensink = { workspace = true } pallet-example-offchain-worker = { workspace = true } -pallet-example-split = { workspace = true } pallet-example-single-block-migrations = { workspace = true } +pallet-example-split = { workspace = true } pallet-example-tasks = { workspace = true } -pallet-example-authorization-tx-extension = { workspace = true } +pallet-example-view-functions = { workspace = true } [features] default = ["std"] @@ -40,6 +41,7 @@ std = [ "pallet-example-single-block-migrations/std", "pallet-example-split/std", "pallet-example-tasks/std", + "pallet-example-view-functions/std", ] try-runtime = [ "pallet-default-config-example/try-runtime", @@ -51,4 +53,5 @@ try-runtime = [ "pallet-example-single-block-migrations/try-runtime", "pallet-example-split/try-runtime", "pallet-example-tasks/try-runtime", + "pallet-example-view-functions/try-runtime", ] diff --git a/substrate/frame/examples/authorization-tx-extension/src/tests.rs b/substrate/frame/examples/authorization-tx-extension/src/tests.rs index 8ca35c0995562dcccfb449cf4056042cbd3e1769..5579e7a98416a4df774cc769018821cdee0be5af 100644 --- a/substrate/frame/examples/authorization-tx-extension/src/tests.rs +++ b/substrate/frame/examples/authorization-tx-extension/src/tests.rs @@ -24,7 +24,7 @@ use frame_support::{ pallet_prelude::{InvalidTransaction, TransactionValidityError}, }; use pallet_verify_signature::VerifySignature; -use sp_keyring::AccountKeyring; +use sp_keyring::Sr25519Keyring; use sp_runtime::{ generic::ExtensionVersion, traits::{Applyable, Checkable, IdentityLookup, TransactionExtension}, @@ -36,7 +36,7 @@ use crate::{extensions::AuthorizeCoownership, mock::*, pallet_assets}; #[test] fn create_asset_works() { new_test_ext().execute_with(|| { - let alice_keyring = AccountKeyring::Alice; + let alice_keyring = Sr25519Keyring::Alice; let alice_account = AccountId::from(alice_keyring.public()); // Simple call to create asset with Id `42`. let create_asset_call = @@ -99,9 +99,9 @@ fn create_asset_works() { #[test] fn create_coowned_asset_works() { new_test_ext().execute_with(|| { - let alice_keyring = AccountKeyring::Alice; - let bob_keyring = AccountKeyring::Bob; - let charlie_keyring = AccountKeyring::Charlie; + let alice_keyring = Sr25519Keyring::Alice; + let bob_keyring = Sr25519Keyring::Bob; + let charlie_keyring = Sr25519Keyring::Charlie; let alice_account = AccountId::from(alice_keyring.public()); let bob_account = AccountId::from(bob_keyring.public()); let charlie_account = AccountId::from(charlie_keyring.public()); @@ -189,9 +189,9 @@ fn create_coowned_asset_works() { #[test] fn inner_authorization_works() { new_test_ext().execute_with(|| { - let alice_keyring = AccountKeyring::Alice; - let bob_keyring = AccountKeyring::Bob; - let charlie_keyring = AccountKeyring::Charlie; + let alice_keyring = Sr25519Keyring::Alice; + let bob_keyring = Sr25519Keyring::Bob; + let charlie_keyring = Sr25519Keyring::Charlie; let charlie_account = AccountId::from(charlie_keyring.public()); // Simple call to create asset with Id `42`. let create_asset_call = diff --git a/substrate/frame/examples/basic/Cargo.toml b/substrate/frame/examples/basic/Cargo.toml index f7e2b653c2d15f4688df88bea4c4b5a45c011a2e..1deb82cc6ea5cc2ed8539bb02ed1bb2b37717ae7 100644 --- a/substrate/frame/examples/basic/Cargo.toml +++ b/substrate/frame/examples/basic/Cargo.toml @@ -18,12 +18,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-balances = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/examples/default-config/Cargo.toml b/substrate/frame/examples/default-config/Cargo.toml index fa376b4f9136aae67cc23548db9ecf7d770994a3..87485aa08ef0e3bfe63376ee05922b3769d0efa4 100644 --- a/substrate/frame/examples/default-config/Cargo.toml +++ b/substrate/frame/examples/default-config/Cargo.toml @@ -18,10 +18,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/examples/default-config/src/lib.rs b/substrate/frame/examples/default-config/src/lib.rs index ccdcd4968598d243ca4d236736402170db5e90bd..f690bffe0998bdef2e8b17a24ad46d2aaf6e363b 100644 --- a/substrate/frame/examples/default-config/src/lib.rs +++ b/substrate/frame/examples/default-config/src/lib.rs @@ -62,10 +62,10 @@ pub mod pallet { type OverwrittenDefaultValue: Get<u32>; /// An input parameter that relies on `<Self as frame_system::Config>::AccountId`. This can - /// too have a default, as long as as it is present in `frame_system::DefaultConfig`. + /// too have a default, as long as it is present in `frame_system::DefaultConfig`. type CanDeriveDefaultFromSystem: Get<Self::AccountId>; - /// We might chose to declare as one that doesn't have a default, for whatever semantical + /// We might choose to declare as one that doesn't have a default, for whatever semantical /// reason. #[pallet::no_default] type HasNoDefault: Get<u32>; diff --git a/substrate/frame/examples/dev-mode/Cargo.toml b/substrate/frame/examples/dev-mode/Cargo.toml index 6625fb3a5851102d73286dfcdd5d5b5529e1d873..7589abb929d50344091ee9860e6d14e3426dfc24 100644 --- a/substrate/frame/examples/dev-mode/Cargo.toml +++ b/substrate/frame/examples/dev-mode/Cargo.toml @@ -17,11 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-balances = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/examples/frame-crate/Cargo.toml b/substrate/frame/examples/frame-crate/Cargo.toml index f174c6b9054b52d1f88d5d9853ffecd2df6f8014..46db1afc34643cef8b90a639e39aa2070a16919e 100644 --- a/substrate/frame/examples/frame-crate/Cargo.toml +++ b/substrate/frame/examples/frame-crate/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { workspace = true } scale-info = { features = ["derive"], workspace = true } -frame = { features = ["experimental", "runtime"], workspace = true } +frame = { features = ["runtime"], workspace = true } [features] diff --git a/substrate/frame/examples/multi-block-migrations/Cargo.toml b/substrate/frame/examples/multi-block-migrations/Cargo.toml index 98569964a9c9e5a2c68884e52b53dbceed90696c..6e8e8978426653fe5e270032d995fcf70ec65f3b 100644 --- a/substrate/frame/examples/multi-block-migrations/Cargo.toml +++ b/substrate/frame/examples/multi-block-migrations/Cargo.toml @@ -14,11 +14,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -pallet-migrations = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } log = { workspace = true } +pallet-migrations = { workspace = true } scale-info = { workspace = true } sp-io = { workspace = true } diff --git a/substrate/frame/examples/multi-block-migrations/src/mock.rs b/substrate/frame/examples/multi-block-migrations/src/mock.rs index b2a946e1c505c2bd71e6929ff031d0b365ad702e..64940db080c42872eaae9c24778185b582acd60b 100644 --- a/substrate/frame/examples/multi-block-migrations/src/mock.rs +++ b/substrate/frame/examples/multi-block-migrations/src/mock.rs @@ -25,10 +25,7 @@ //! using the [`Migrations`] type. use frame_support::{ - construct_runtime, derive_impl, - migrations::MultiStepMigrator, - pallet_prelude::Weight, - traits::{OnFinalize, OnInitialize}, + construct_runtime, derive_impl, migrations::MultiStepMigrator, pallet_prelude::Weight, }; type Block = frame_system::mocking::MockBlock<Runtime>; @@ -81,13 +78,11 @@ pub fn new_test_ext() -> sp_io::TestExternalities { #[allow(dead_code)] pub fn run_to_block(n: u64) { - assert!(System::block_number() < n); - while System::block_number() < n { - let b = System::block_number(); - AllPalletsWithSystem::on_finalize(b); - // Done by Executive: - <Runtime as frame_system::Config>::MultiBlockMigrator::step(); - System::set_block_number(b + 1); - AllPalletsWithSystem::on_initialize(b + 1); - } + System::run_to_block_with::<AllPalletsWithSystem>( + n, + frame_system::RunToBlockHooks::default().after_initialize(|_| { + // Done by Executive: + <Runtime as frame_system::Config>::MultiBlockMigrator::step(); + }), + ); } diff --git a/substrate/frame/examples/offchain-worker/Cargo.toml b/substrate/frame/examples/offchain-worker/Cargo.toml index a5664dd912d4b348c79f6123b265798b90856660..fabdfb0f9e0c36a9b50b3143be21383b835182a3 100644 --- a/substrate/frame/examples/offchain-worker/Cargo.toml +++ b/substrate/frame/examples/offchain-worker/Cargo.toml @@ -18,11 +18,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } lite-json = { workspace = true } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-keystore = { optional = true, workspace = true } diff --git a/substrate/frame/examples/single-block-migrations/Cargo.toml b/substrate/frame/examples/single-block-migrations/Cargo.toml index 26a3a9fff75317ed7af879ffebb1a849f3a26182..4df8693e0f37d71d72c27b5309a1a3507963c90c 100644 --- a/substrate/frame/examples/single-block-migrations/Cargo.toml +++ b/substrate/frame/examples/single-block-migrations/Cargo.toml @@ -13,18 +13,18 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -docify = { workspace = true } -log = { workspace = true } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } -frame-support = { workspace = true } +docify = { workspace = true } frame-executive = { workspace = true } +frame-support = { workspace = true } frame-system = { workspace = true } frame-try-runtime = { optional = true, workspace = true } +log = { workspace = true } pallet-balances = { workspace = true } -sp-runtime = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } +sp-runtime = { workspace = true } sp-version = { workspace = true } [features] diff --git a/substrate/frame/examples/src/lib.rs b/substrate/frame/examples/src/lib.rs index d0d30830f2f04d1bfd4c4afa5a39be91ee549a54..200e92112a3f3b4ba9552a8de68401fc290469e4 100644 --- a/substrate/frame/examples/src/lib.rs +++ b/substrate/frame/examples/src/lib.rs @@ -48,6 +48,9 @@ //! //! - [`pallet_example_tasks`]: This pallet demonstrates the use of `Tasks` to execute service work. //! +//! - [`pallet_example_view_functions`]: This pallet demonstrates the use of view functions to query +//! pallet state. +//! //! - [`pallet_example_authorization_tx_extension`]: An example `TransactionExtension` that //! authorizes a custom origin through signature validation, along with two support pallets to //! showcase the usage. diff --git a/substrate/frame/examples/tasks/Cargo.toml b/substrate/frame/examples/tasks/Cargo.toml index 00695ceddf1977aaccfeec31db93c248c8391677..48f4d9e66e9c2c4e6c1ef92b5cddfd9cb9d08923 100644 --- a/substrate/frame/examples/tasks/Cargo.toml +++ b/substrate/frame/examples/tasks/Cargo.toml @@ -22,9 +22,9 @@ scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -sp-core = { workspace = true } frame-benchmarking = { optional = true, workspace = true } diff --git a/substrate/frame/examples/view-functions/Cargo.toml b/substrate/frame/examples/view-functions/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..b52ad4e06e9fa9c32b3f0ad422198789825c4a4e --- /dev/null +++ b/substrate/frame/examples/view-functions/Cargo.toml @@ -0,0 +1,61 @@ +[package] +name = "pallet-example-view-functions" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "Pallet to demonstrate the usage of view functions to query pallet state" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", default-features = false, workspace = true } +frame-metadata = { features = ["current"], workspace = true } +log = { workspace = true } +scale-info = { default-features = false, features = ["derive"], workspace = true } + +frame-support = { default-features = false, workspace = true } +frame-system = { default-features = false, workspace = true } + +sp-core = { default-features = false, workspace = true } +sp-io = { default-features = false, workspace = true } +sp-metadata-ir = { default-features = false, workspace = true } +sp-runtime = { default-features = false, workspace = true } + +frame-benchmarking = { default-features = false, optional = true, workspace = true } + +[dev-dependencies] +pretty_assertions = { workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-metadata/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-metadata-ir/std", + "sp-runtime/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/examples/view-functions/src/lib.rs b/substrate/frame/examples/view-functions/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..e842a718ad33442b1f83a88963d637b2e6ca77bf --- /dev/null +++ b/substrate/frame/examples/view-functions/src/lib.rs @@ -0,0 +1,114 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This pallet demonstrates the use of the `pallet::view_functions_experimental` api for service +//! work. +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod tests; + +use frame_support::Parameter; +use scale_info::TypeInfo; + +pub struct SomeType1; +impl From<SomeType1> for u64 { + fn from(_t: SomeType1) -> Self { + 0u64 + } +} + +pub trait SomeAssociation1 { + type _1: Parameter + codec::MaxEncodedLen + TypeInfo; +} +impl SomeAssociation1 for u64 { + type _1 = u64; +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::error] + pub enum Error<T> {} + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet<T>(_); + + #[pallet::storage] + pub type SomeValue<T: Config> = StorageValue<_, u32>; + + #[pallet::storage] + pub type SomeMap<T: Config> = StorageMap<_, Twox64Concat, u32, u32, OptionQuery>; + + #[pallet::view_functions_experimental] + impl<T: Config> Pallet<T> + where + T::AccountId: From<SomeType1> + SomeAssociation1, + { + /// Query value no args. + pub fn get_value() -> Option<u32> { + SomeValue::<T>::get() + } + + /// Query value with args. + pub fn get_value_with_arg(key: u32) -> Option<u32> { + SomeMap::<T>::get(key) + } + } +} + +#[frame_support::pallet] +pub mod pallet2 { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::error] + pub enum Error<T, I = ()> {} + + #[pallet::config] + pub trait Config<I: 'static = ()>: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet<T, I = ()>(PhantomData<(T, I)>); + + #[pallet::storage] + pub type SomeValue<T: Config<I>, I: 'static = ()> = StorageValue<_, u32>; + + #[pallet::storage] + pub type SomeMap<T: Config<I>, I: 'static = ()> = + StorageMap<_, Twox64Concat, u32, u32, OptionQuery>; + + #[pallet::view_functions_experimental] + impl<T: Config<I>, I: 'static> Pallet<T, I> + where + T::AccountId: From<SomeType1> + SomeAssociation1, + { + /// Query value no args. + pub fn get_value() -> Option<u32> { + SomeValue::<T, I>::get() + } + + /// Query value with args. + pub fn get_value_with_arg(key: u32) -> Option<u32> { + SomeMap::<T, I>::get(key) + } + } +} diff --git a/substrate/frame/examples/view-functions/src/tests.rs b/substrate/frame/examples/view-functions/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..25f5f094651d6a085c041702118a8e69a5f7e4c1 --- /dev/null +++ b/substrate/frame/examples/view-functions/src/tests.rs @@ -0,0 +1,188 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for `pallet-example-view-functions`. +#![cfg(test)] + +use crate::{ + pallet::{self, Pallet}, + pallet2, +}; +use codec::{Decode, Encode}; +use scale_info::{form::PortableForm, meta_type}; + +use frame_support::{derive_impl, pallet_prelude::PalletInfoAccess, view_functions::ViewFunction}; +use sp_io::hashing::twox_128; +use sp_metadata_ir::{ViewFunctionArgMetadataIR, ViewFunctionGroupIR, ViewFunctionMetadataIR}; +use sp_runtime::testing::TestXt; + +pub type AccountId = u32; +pub type Balance = u32; + +type Block = frame_system::mocking::MockBlock<Runtime>; +frame_support::construct_runtime!( + pub enum Runtime { + System: frame_system, + ViewFunctionsExample: pallet, + ViewFunctionsInstance: pallet2, + ViewFunctionsInstance1: pallet2::<Instance1>, + } +); + +pub type Extrinsic = TestXt<RuntimeCall, ()>; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type Block = Block; +} + +impl pallet::Config for Runtime {} +impl pallet2::Config<pallet2::Instance1> for Runtime {} + +impl pallet2::Config for Runtime {} + +pub fn new_test_ext() -> sp_io::TestExternalities { + use sp_runtime::BuildStorage; + + let t = RuntimeGenesisConfig { system: Default::default() }.build_storage().unwrap(); + t.into() +} + +#[test] +fn pallet_get_value_query() { + new_test_ext().execute_with(|| { + let some_value = Some(99); + pallet::SomeValue::<Runtime>::set(some_value); + assert_eq!(some_value, Pallet::<Runtime>::get_value()); + + let query = pallet::GetValueViewFunction::<Runtime>::new(); + test_dispatch_view_function(&query, some_value); + }); +} + +#[test] +fn pallet_get_value_with_arg_query() { + new_test_ext().execute_with(|| { + let some_key = 1u32; + let some_value = Some(123); + pallet::SomeMap::<Runtime>::set(some_key, some_value); + assert_eq!(some_value, Pallet::<Runtime>::get_value_with_arg(some_key)); + + let query = pallet::GetValueWithArgViewFunction::<Runtime>::new(some_key); + test_dispatch_view_function(&query, some_value); + }); +} + +#[test] +fn pallet_multiple_instances() { + use pallet2::Instance1; + + new_test_ext().execute_with(|| { + let instance_value = Some(123); + let instance1_value = Some(456); + + pallet2::SomeValue::<Runtime>::set(instance_value); + pallet2::SomeValue::<Runtime, Instance1>::set(instance1_value); + + let query = pallet2::GetValueViewFunction::<Runtime>::new(); + test_dispatch_view_function(&query, instance_value); + + let query_instance1 = pallet2::GetValueViewFunction::<Runtime, Instance1>::new(); + test_dispatch_view_function(&query_instance1, instance1_value); + }); +} + +#[test] +fn metadata_ir_definitions() { + new_test_ext().execute_with(|| { + let metadata_ir = Runtime::metadata_ir(); + let pallet1 = metadata_ir + .view_functions + .groups + .iter() + .find(|pallet| pallet.name == "ViewFunctionsExample") + .unwrap(); + + fn view_fn_id(preifx_hash: [u8; 16], view_fn_signature: &str) -> [u8; 32] { + let mut id = [0u8; 32]; + id[..16].copy_from_slice(&preifx_hash); + id[16..].copy_from_slice(&twox_128(view_fn_signature.as_bytes())); + id + } + + let get_value_id = view_fn_id( + <ViewFunctionsExample as PalletInfoAccess>::name_hash(), + "get_value() -> Option<u32>", + ); + + let get_value_with_arg_id = view_fn_id( + <ViewFunctionsExample as PalletInfoAccess>::name_hash(), + "get_value_with_arg(u32) -> Option<u32>", + ); + + pretty_assertions::assert_eq!( + pallet1.view_functions, + vec![ + ViewFunctionMetadataIR { + name: "get_value", + id: get_value_id, + args: vec![], + output: meta_type::<Option<u32>>(), + docs: vec![" Query value no args."], + }, + ViewFunctionMetadataIR { + name: "get_value_with_arg", + id: get_value_with_arg_id, + args: vec![ViewFunctionArgMetadataIR { name: "key", ty: meta_type::<u32>() },], + output: meta_type::<Option<u32>>(), + docs: vec![" Query value with args."], + }, + ] + ); + }); +} + +#[test] +fn metadata_encoded_to_custom_value() { + new_test_ext().execute_with(|| { + let metadata = sp_metadata_ir::into_latest(Runtime::metadata_ir()); + // metadata is currently experimental so lives as a custom value. + let frame_metadata::RuntimeMetadata::V15(v15) = metadata.1 else { + panic!("Expected metadata v15") + }; + let custom_value = v15 + .custom + .map + .get("view_functions_experimental") + .expect("Expected custom value"); + let view_function_groups: Vec<ViewFunctionGroupIR<PortableForm>> = + Decode::decode(&mut &custom_value.value[..]).unwrap(); + assert_eq!(view_function_groups.len(), 4); + }); +} + +fn test_dispatch_view_function<Q, V>(query: &Q, expected: V) +where + Q: ViewFunction + Encode, + V: Decode + Eq + PartialEq + std::fmt::Debug, +{ + let input = query.encode(); + let output = Runtime::execute_view_function(Q::id(), input).unwrap(); + let query_result = V::decode(&mut &output[..]).unwrap(); + + assert_eq!(expected, query_result,); +} diff --git a/substrate/frame/executive/Cargo.toml b/substrate/frame/executive/Cargo.toml index 76d084f49d9fcc5615630a01d14c17b9804f60bd..ee24a9fef13d791d32f00dc21b427a114a3e15f6 100644 --- a/substrate/frame/executive/Cargo.toml +++ b/substrate/frame/executive/Cargo.toml @@ -20,11 +20,11 @@ aquamarine = { workspace = true } codec = { features = [ "derive", ], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } frame-try-runtime = { optional = true, workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/executive/src/tests.rs b/substrate/frame/executive/src/tests.rs index 3841b010325b28c832ed197ac55c479c724c9afc..dd12a85a1114c162d1ce9d41bc11a65075776c3b 100644 --- a/substrate/frame/executive/src/tests.rs +++ b/substrate/frame/executive/src/tests.rs @@ -335,6 +335,9 @@ impl frame_system::ExtensionsWeightInfo for MockExtensionsWeights { fn check_weight() -> Weight { Weight::from_parts(10, 0) } + fn weight_reclaim() -> Weight { + Weight::zero() + } } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] @@ -452,6 +455,7 @@ type TxExtension = ( frame_system::CheckNonce<Runtime>, frame_system::CheckWeight<Runtime>, pallet_transaction_payment::ChargeTransactionPayment<Runtime>, + frame_system::WeightReclaim<Runtime>, ); type UncheckedXt = sp_runtime::generic::UncheckedExtrinsic< u64, @@ -560,6 +564,7 @@ fn tx_ext(nonce: u64, fee: Balance) -> TxExtension { frame_system::CheckNonce::from(nonce), frame_system::CheckWeight::new(), pallet_transaction_payment::ChargeTransactionPayment::from(fee), + frame_system::WeightReclaim::new(), ) .into() } @@ -571,7 +576,7 @@ fn call_transfer(dest: u64, value: u64) -> RuntimeCall { #[test] fn balance_transfer_dispatch_works() { let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap(); - pallet_balances::GenesisConfig::<Runtime> { balances: vec![(1, 211)] } + pallet_balances::GenesisConfig::<Runtime> { balances: vec![(1, 211)], ..Default::default() } .assimilate_storage(&mut t) .unwrap(); let xt = UncheckedXt::new_signed(call_transfer(2, 69), 1, 1.into(), tx_ext(0, 0)); @@ -593,9 +598,12 @@ fn balance_transfer_dispatch_works() { fn new_test_ext(balance_factor: Balance) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap(); - pallet_balances::GenesisConfig::<Runtime> { balances: vec![(1, 111 * balance_factor)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig::<Runtime> { + balances: vec![(1, 111 * balance_factor)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); let mut ext: sp_io::TestExternalities = t.into(); ext.execute_with(|| { SystemCallbacksCalled::set(0); @@ -605,9 +613,12 @@ fn new_test_ext(balance_factor: Balance) -> sp_io::TestExternalities { fn new_test_ext_v0(balance_factor: Balance) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap(); - pallet_balances::GenesisConfig::<Runtime> { balances: vec![(1, 111 * balance_factor)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig::<Runtime> { + balances: vec![(1, 111 * balance_factor)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); (t, sp_runtime::StateVersion::V0).into() } diff --git a/substrate/frame/fast-unstake/Cargo.toml b/substrate/frame/fast-unstake/Cargo.toml index c1d0e80551c23b0eb8b8dc1b30f99a31619badc8..209406dc3f99ad411855d8c0824c2e4ff53ce96f 100644 --- a/substrate/frame/fast-unstake/Cargo.toml +++ b/substrate/frame/fast-unstake/Cargo.toml @@ -22,23 +22,22 @@ scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +frame-election-provider-support = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } sp-staking = { workspace = true } -frame-election-provider-support = { workspace = true } frame-benchmarking = { optional = true, workspace = true } docify = { workspace = true } [dev-dependencies] +pallet-balances = { workspace = true, default-features = true } +pallet-staking = { workspace = true, default-features = true } pallet-staking-reward-curve = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true } -substrate-test-utils = { workspace = true } sp-tracing = { workspace = true, default-features = true } -pallet-staking = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/fast-unstake/src/mock.rs b/substrate/frame/fast-unstake/src/mock.rs index 757052e230a187ed9ce712a2b30bc5a5f2e9febb..67f7ee21e61758ee25803b7e741e4d3a2eec7529 100644 --- a/substrate/frame/fast-unstake/src/mock.rs +++ b/substrate/frame/fast-unstake/src/mock.rs @@ -105,6 +105,7 @@ impl frame_election_provider_support::ElectionProvider for MockElection { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Runtime { + type OldCurrency = Balances; type Currency = Balances; type UnixTime = pallet_timestamp::Pallet<Self>; type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>; @@ -223,9 +224,11 @@ impl ExtBuilder { .clone() .into_iter() .map(|(stash, _, balance)| (stash, balance * 2)) - .chain(validators_range.clone().map(|x| (x, 7 + 100))) - .chain(nominators_range.clone().map(|x| (x, 7 + 100))) + // give stakers enough balance for stake, ed and fast unstake deposit. + .chain(validators_range.clone().map(|x| (x, 7 + 1 + 100))) + .chain(nominators_range.clone().map(|x| (x, 7 + 1 + 100))) .collect::<Vec<_>>(), + ..Default::default() } .assimilate_storage(&mut storage); @@ -266,22 +269,19 @@ impl ExtBuilder { } pub(crate) fn run_to_block(n: u64, on_idle: bool) { - let current_block = System::block_number(); - assert!(n > current_block); - while System::block_number() < n { - Balances::on_finalize(System::block_number()); - Staking::on_finalize(System::block_number()); - FastUnstake::on_finalize(System::block_number()); - - System::set_block_number(System::block_number() + 1); - - Balances::on_initialize(System::block_number()); - Staking::on_initialize(System::block_number()); - FastUnstake::on_initialize(System::block_number()); - if on_idle { - FastUnstake::on_idle(System::block_number(), BlockWeights::get().max_block); - } - } + System::run_to_block_with::<AllPalletsWithSystem>( + n, + frame_system::RunToBlockHooks::default() + .before_finalize(|_| { + // Satisfy the timestamp pallet. + Timestamp::set_timestamp(0); + }) + .after_initialize(|bn| { + if on_idle { + FastUnstake::on_idle(bn, BlockWeights::get().max_block); + } + }), + ); } pub(crate) fn next_block(on_idle: bool) { diff --git a/substrate/frame/fast-unstake/src/tests.rs b/substrate/frame/fast-unstake/src/tests.rs index 7c11f381ca102927e10a3b8241d87dfa28a07d5b..0fddb88e02b7bef231d5f9bb3961318c8656dc24 100644 --- a/substrate/frame/fast-unstake/src/tests.rs +++ b/substrate/frame/fast-unstake/src/tests.rs @@ -19,7 +19,15 @@ use super::*; use crate::{mock::*, types::*, Event}; -use frame_support::{pallet_prelude::*, testing_prelude::*, traits::Currency}; +use frame_support::{ + pallet_prelude::*, + testing_prelude::*, + traits::{ + fungible::Inspect, + tokens::{Fortitude::Polite, Preservation::Expendable}, + Currency, + }, +}; use pallet_staking::{CurrentEra, RewardDestination}; use sp_runtime::traits::BadOrigin; @@ -146,7 +154,7 @@ fn deregister_works() { // Controller then changes mind and deregisters. assert_ok!(FastUnstake::deregister(RuntimeOrigin::signed(1))); - assert_eq!(<T as Config>::Currency::reserved_balance(&1) - pre_reserved, 0); + assert_eq!(<T as Config>::Currency::reserved_balance(&1), pre_reserved); // Ensure stash no longer exists in the queue. assert_eq!(Queue::<T>::get(1), None); @@ -297,7 +305,7 @@ mod on_idle { ); assert_eq!(Queue::<T>::count(), 3); - assert_eq!(<T as Config>::Currency::reserved_balance(&1) - pre_reserved, 0); + assert_eq!(<T as Config>::Currency::reserved_balance(&1), pre_reserved); assert_eq!( fast_unstake_events_since_last_call(), @@ -793,6 +801,8 @@ mod on_idle { RuntimeOrigin::signed(VALIDATOR_PREFIX), vec![VALIDATOR_PREFIX] )); + + assert_eq!(Balances::reducible_balance(&VALIDATOR_PREFIX, Expendable, Polite), 7); assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(VALIDATOR_PREFIX))); // but they indeed are exposed! diff --git a/substrate/frame/glutton/Cargo.toml b/substrate/frame/glutton/Cargo.toml index 6717176ffc9547e733aca024881651ef1ca4d998..7f7b24c12117bdfd34f4e91cfd27b0d0a67bedaf 100644 --- a/substrate/frame/glutton/Cargo.toml +++ b/substrate/frame/glutton/Cargo.toml @@ -18,18 +18,15 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] blake2 = { workspace = true } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } -log = { workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } +sp-inherents = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -sp-inherents = { workspace = true } - -[dev-dependencies] -pallet-balances = { workspace = true, default-features = true } [features] default = ["std"] @@ -40,7 +37,6 @@ std = [ "frame-support/std", "frame-system/std", "log/std", - "pallet-balances/std", "scale-info/std", "sp-core/std", "sp-inherents/std", @@ -50,7 +46,6 @@ std = [ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "pallet-balances/try-runtime", "sp-runtime/try-runtime", ] @@ -58,6 +53,5 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] diff --git a/substrate/frame/grandpa/Cargo.toml b/substrate/frame/grandpa/Cargo.toml index 86ace358d05d7d7abbab7d8a38db77637d422078..4072d65b6267b291c7217226e5e8fb30365d991e 100644 --- a/substrate/frame/grandpa/Cargo.toml +++ b/substrate/frame/grandpa/Cargo.toml @@ -17,13 +17,13 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-authorship = { workspace = true } pallet-session = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } sp-application-crypto = { features = ["serde"], workspace = true } sp-consensus-grandpa = { features = ["serde"], workspace = true } sp-core = { features = ["serde"], workspace = true } diff --git a/substrate/frame/grandpa/src/benchmarking.rs b/substrate/frame/grandpa/src/benchmarking.rs index 0a10e588277615fd73cd80a25b1167aa02faeae3..56048efa22cae6077e6d6f0e18fb772dff7777b4 100644 --- a/substrate/frame/grandpa/src/benchmarking.rs +++ b/substrate/frame/grandpa/src/benchmarking.rs @@ -17,7 +17,7 @@ //! Benchmarks for the GRANDPA pallet. -use super::{Pallet as Grandpa, *}; +use super::*; use frame_benchmarking::v2::*; use frame_system::RawOrigin; use sp_core::H256; @@ -69,7 +69,7 @@ mod benchmarks { #[extrinsic_call] _(RawOrigin::Root, delay, best_finalized_block_number); - assert!(Grandpa::<T>::stalled().is_some()); + assert!(Stalled::<T>::get().is_some()); } impl_benchmark_test_suite!( diff --git a/substrate/frame/grandpa/src/equivocation.rs b/substrate/frame/grandpa/src/equivocation.rs index 2366c957e9ab17e335ed5c830af64134ad39f033..4ebdbc1eecd300fb98a3dda0ac4035505c3454c9 100644 --- a/substrate/frame/grandpa/src/equivocation.rs +++ b/substrate/frame/grandpa/src/equivocation.rs @@ -177,7 +177,7 @@ where evidence: (EquivocationProof<T::Hash, BlockNumberFor<T>>, T::KeyOwnerProof), ) -> Result<(), DispatchError> { let (equivocation_proof, key_owner_proof) = evidence; - let reporter = reporter.or_else(|| <pallet_authorship::Pallet<T>>::author()); + let reporter = reporter.or_else(|| pallet_authorship::Pallet::<T>::author()); let offender = equivocation_proof.offender().clone(); // We check the equivocation within the context of its set id (and diff --git a/substrate/frame/grandpa/src/lib.rs b/substrate/frame/grandpa/src/lib.rs index 4f69aeaef523671969e961b24b4b87b59115bae3..9017eec2ca8f8139a036ed0b59e61f3fb8277a9f 100644 --- a/substrate/frame/grandpa/src/lib.rs +++ b/substrate/frame/grandpa/src/lib.rs @@ -127,7 +127,7 @@ pub mod pallet { impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { fn on_finalize(block_number: BlockNumberFor<T>) { // check for scheduled pending authority set changes - if let Some(pending_change) = <PendingChange<T>>::get() { + if let Some(pending_change) = PendingChange::<T>::get() { // emit signal if we're at the block that scheduled the change if block_number == pending_change.scheduled_at { let next_authorities = pending_change.next_authorities.to_vec(); @@ -150,12 +150,12 @@ pub mod pallet { Self::deposit_event(Event::NewAuthorities { authority_set: pending_change.next_authorities.into_inner(), }); - <PendingChange<T>>::kill(); + PendingChange::<T>::kill(); } } // check for scheduled pending state changes - match <State<T>>::get() { + match State::<T>::get() { StoredState::PendingPause { scheduled_at, delay } => { // signal change to pause if block_number == scheduled_at { @@ -164,7 +164,7 @@ pub mod pallet { // enact change to paused state if block_number == scheduled_at + delay { - <State<T>>::put(StoredState::Paused); + State::<T>::put(StoredState::Paused); Self::deposit_event(Event::Paused); } }, @@ -176,7 +176,7 @@ pub mod pallet { // enact change to live state if block_number == scheduled_at + delay { - <State<T>>::put(StoredState::Live); + State::<T>::put(StoredState::Live); Self::deposit_event(Event::Resumed); } }, @@ -297,37 +297,32 @@ pub mod pallet { } #[pallet::type_value] - pub(super) fn DefaultForState<T: Config>() -> StoredState<BlockNumberFor<T>> { + pub fn DefaultForState<T: Config>() -> StoredState<BlockNumberFor<T>> { StoredState::Live } /// State of the current authority set. #[pallet::storage] - #[pallet::getter(fn state)] - pub(super) type State<T: Config> = + pub type State<T: Config> = StorageValue<_, StoredState<BlockNumberFor<T>>, ValueQuery, DefaultForState<T>>; /// Pending change: (signaled at, scheduled change). #[pallet::storage] - #[pallet::getter(fn pending_change)] - pub(super) type PendingChange<T: Config> = + pub type PendingChange<T: Config> = StorageValue<_, StoredPendingChange<BlockNumberFor<T>, T::MaxAuthorities>>; /// next block number where we can force a change. #[pallet::storage] - #[pallet::getter(fn next_forced)] - pub(super) type NextForced<T: Config> = StorageValue<_, BlockNumberFor<T>>; + pub type NextForced<T: Config> = StorageValue<_, BlockNumberFor<T>>; /// `true` if we are currently stalled. #[pallet::storage] - #[pallet::getter(fn stalled)] - pub(super) type Stalled<T: Config> = StorageValue<_, (BlockNumberFor<T>, BlockNumberFor<T>)>; + pub type Stalled<T: Config> = StorageValue<_, (BlockNumberFor<T>, BlockNumberFor<T>)>; /// The number of changes (both in terms of keys and underlying economic responsibilities) /// in the "set" of Grandpa validators from genesis. #[pallet::storage] - #[pallet::getter(fn current_set_id)] - pub(super) type CurrentSetId<T: Config> = StorageValue<_, SetId, ValueQuery>; + pub type CurrentSetId<T: Config> = StorageValue<_, SetId, ValueQuery>; /// A mapping from grandpa set ID to the index of the *most recent* session for which its /// members were responsible. @@ -340,12 +335,11 @@ pub mod pallet { /// /// TWOX-NOTE: `SetId` is not under user control. #[pallet::storage] - #[pallet::getter(fn session_for_set)] - pub(super) type SetIdSession<T: Config> = StorageMap<_, Twox64Concat, SetId, SessionIndex>; + pub type SetIdSession<T: Config> = StorageMap<_, Twox64Concat, SetId, SessionIndex>; /// The current list of authorities. #[pallet::storage] - pub(crate) type Authorities<T: Config> = + pub type Authorities<T: Config> = StorageValue<_, BoundedAuthorityList<T::MaxAuthorities>, ValueQuery>; #[derive(frame_support::DefaultNoBound)] @@ -432,6 +426,44 @@ pub enum StoredState<N> { } impl<T: Config> Pallet<T> { + /// State of the current authority set. + pub fn state() -> StoredState<BlockNumberFor<T>> { + State::<T>::get() + } + + /// Pending change: (signaled at, scheduled change). + pub fn pending_change() -> Option<StoredPendingChange<BlockNumberFor<T>, T::MaxAuthorities>> { + PendingChange::<T>::get() + } + + /// next block number where we can force a change. + pub fn next_forced() -> Option<BlockNumberFor<T>> { + NextForced::<T>::get() + } + + /// `true` if we are currently stalled. + pub fn stalled() -> Option<(BlockNumberFor<T>, BlockNumberFor<T>)> { + Stalled::<T>::get() + } + + /// The number of changes (both in terms of keys and underlying economic responsibilities) + /// in the "set" of Grandpa validators from genesis. + pub fn current_set_id() -> SetId { + CurrentSetId::<T>::get() + } + + /// A mapping from grandpa set ID to the index of the *most recent* session for which its + /// members were responsible. + /// + /// This is only used for validating equivocation proofs. An equivocation proof must + /// contains a key-ownership proof for a given session, therefore we need a way to tie + /// together sessions and GRANDPA set ids, i.e. we need to validate that a validator + /// was the owner of a given key on a given session, and what the active set ID was + /// during that session. + pub fn session_for_set(set_id: SetId) -> Option<SessionIndex> { + SetIdSession::<T>::get(set_id) + } + /// Get the current set of authorities, along with their respective weights. pub fn grandpa_authorities() -> AuthorityList { Authorities::<T>::get().into_inner() @@ -440,9 +472,9 @@ impl<T: Config> Pallet<T> { /// Schedule GRANDPA to pause starting in the given number of blocks. /// Cannot be done when already paused. pub fn schedule_pause(in_blocks: BlockNumberFor<T>) -> DispatchResult { - if let StoredState::Live = <State<T>>::get() { - let scheduled_at = <frame_system::Pallet<T>>::block_number(); - <State<T>>::put(StoredState::PendingPause { delay: in_blocks, scheduled_at }); + if let StoredState::Live = State::<T>::get() { + let scheduled_at = frame_system::Pallet::<T>::block_number(); + State::<T>::put(StoredState::PendingPause { delay: in_blocks, scheduled_at }); Ok(()) } else { @@ -452,9 +484,9 @@ impl<T: Config> Pallet<T> { /// Schedule a resume of GRANDPA after pausing. pub fn schedule_resume(in_blocks: BlockNumberFor<T>) -> DispatchResult { - if let StoredState::Paused = <State<T>>::get() { - let scheduled_at = <frame_system::Pallet<T>>::block_number(); - <State<T>>::put(StoredState::PendingResume { delay: in_blocks, scheduled_at }); + if let StoredState::Paused = State::<T>::get() { + let scheduled_at = frame_system::Pallet::<T>::block_number(); + State::<T>::put(StoredState::PendingResume { delay: in_blocks, scheduled_at }); Ok(()) } else { @@ -481,17 +513,17 @@ impl<T: Config> Pallet<T> { in_blocks: BlockNumberFor<T>, forced: Option<BlockNumberFor<T>>, ) -> DispatchResult { - if !<PendingChange<T>>::exists() { - let scheduled_at = <frame_system::Pallet<T>>::block_number(); + if !PendingChange::<T>::exists() { + let scheduled_at = frame_system::Pallet::<T>::block_number(); if forced.is_some() { - if Self::next_forced().map_or(false, |next| next > scheduled_at) { + if NextForced::<T>::get().map_or(false, |next| next > scheduled_at) { return Err(Error::<T>::TooSoon.into()) } // only allow the next forced change when twice the window has passed since // this one. - <NextForced<T>>::put(scheduled_at + in_blocks * 2u32.into()); + NextForced::<T>::put(scheduled_at + in_blocks * 2u32.into()); } let next_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::force_from( @@ -502,7 +534,7 @@ impl<T: Config> Pallet<T> { ), ); - <PendingChange<T>>::put(StoredPendingChange { + PendingChange::<T>::put(StoredPendingChange { delay: in_blocks, scheduled_at, next_authorities, @@ -518,7 +550,7 @@ impl<T: Config> Pallet<T> { /// Deposit one of this module's logs. fn deposit_log(log: ConsensusLog<BlockNumberFor<T>>) { let log = DigestItem::Consensus(GRANDPA_ENGINE_ID, log.encode()); - <frame_system::Pallet<T>>::deposit_log(log); + frame_system::Pallet::<T>::deposit_log(log); } // Perform module initialization, abstracted so that it can be called either through genesis @@ -554,7 +586,7 @@ impl<T: Config> Pallet<T> { // when we record old authority sets we could try to figure out _who_ // failed. until then, we can't meaningfully guard against // `next == last` the way that normal session changes do. - <Stalled<T>>::put((further_wait, median)); + Stalled::<T>::put((further_wait, median)); } } @@ -583,10 +615,10 @@ where // Always issue a change if `session` says that the validators have changed. // Even if their session keys are the same as before, the underlying economic // identities have changed. - let current_set_id = if changed || <Stalled<T>>::exists() { + let current_set_id = if changed || Stalled::<T>::exists() { let next_authorities = validators.map(|(_, k)| (k, 1)).collect::<Vec<_>>(); - let res = if let Some((further_wait, median)) = <Stalled<T>>::take() { + let res = if let Some((further_wait, median)) = Stalled::<T>::take() { Self::schedule_change(next_authorities, further_wait, Some(median)) } else { Self::schedule_change(next_authorities, Zero::zero(), None) @@ -608,17 +640,17 @@ where // either the session module signalled that the validators have changed // or the set was stalled. but since we didn't successfully schedule // an authority set change we do not increment the set id. - Self::current_set_id() + CurrentSetId::<T>::get() } } else { // nothing's changed, neither economic conditions nor session keys. update the pointer // of the current set. - Self::current_set_id() + CurrentSetId::<T>::get() }; // update the mapping to note that the current set corresponds to the // latest equivalent session (i.e. now). - let session_index = <pallet_session::Pallet<T>>::current_index(); + let session_index = pallet_session::Pallet::<T>::current_index(); SetIdSession::<T>::insert(current_set_id, &session_index); } diff --git a/substrate/frame/grandpa/src/mock.rs b/substrate/frame/grandpa/src/mock.rs index 87369c23948ca0993fc986f555782c48397549b2..cb754fb7955b5f1e35126dcae498c481e27728f4 100644 --- a/substrate/frame/grandpa/src/mock.rs +++ b/substrate/frame/grandpa/src/mock.rs @@ -161,6 +161,7 @@ impl onchain::Config for OnChainSeqPhragmen { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Test { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = <Self as pallet_balances::Config>::Balance; type SessionsPerEra = SessionsPerEra; @@ -225,7 +226,7 @@ pub fn new_test_ext_raw_authorities(authorities: AuthorityList) -> sp_io::TestEx let balances: Vec<_> = (0..authorities.len()).map(|i| (i as u64, 10_000_000)).collect(); - pallet_balances::GenesisConfig::<Test> { balances } + pallet_balances::GenesisConfig::<Test> { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/grandpa/src/tests.rs b/substrate/frame/grandpa/src/tests.rs index 383f77f00de71d29cf6c7201f44ea628af2ce7fd..f4720966b17974ed2d10456310dc84c085d1757a 100644 --- a/substrate/frame/grandpa/src/tests.rs +++ b/substrate/frame/grandpa/src/tests.rs @@ -110,7 +110,7 @@ fn cannot_schedule_change_when_one_pending() { new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| { initialize_block(1, Default::default()); Grandpa::schedule_change(to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 1, None).unwrap(); - assert!(<PendingChange<Test>>::exists()); + assert!(PendingChange::<Test>::exists()); assert_noop!( Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None), Error::<Test>::ChangePending @@ -120,7 +120,7 @@ fn cannot_schedule_change_when_one_pending() { let header = System::finalize(); initialize_block(2, header.hash()); - assert!(<PendingChange<Test>>::exists()); + assert!(PendingChange::<Test>::exists()); assert_noop!( Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None), Error::<Test>::ChangePending @@ -130,7 +130,7 @@ fn cannot_schedule_change_when_one_pending() { let header = System::finalize(); initialize_block(3, header.hash()); - assert!(!<PendingChange<Test>>::exists()); + assert!(!PendingChange::<Test>::exists()); assert_ok!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None)); Grandpa::on_finalize(3); @@ -144,7 +144,7 @@ fn dispatch_forced_change() { initialize_block(1, Default::default()); Grandpa::schedule_change(to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 5, Some(0)).unwrap(); - assert!(<PendingChange<Test>>::exists()); + assert!(PendingChange::<Test>::exists()); assert_noop!( Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, Some(0)), Error::<Test>::ChangePending @@ -155,8 +155,8 @@ fn dispatch_forced_change() { for i in 2..7 { initialize_block(i, header.hash()); - assert!(<PendingChange<Test>>::get().unwrap().forced.is_some()); - assert_eq!(Grandpa::next_forced(), Some(11)); + assert!(PendingChange::<Test>::get().unwrap().forced.is_some()); + assert_eq!(NextForced::<Test>::get(), Some(11)); assert_noop!( Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None), Error::<Test>::ChangePending @@ -174,7 +174,7 @@ fn dispatch_forced_change() { // add a normal change. { initialize_block(7, header.hash()); - assert!(!<PendingChange<Test>>::exists()); + assert!(!PendingChange::<Test>::exists()); assert_eq!( Grandpa::grandpa_authorities(), to_authorities(vec![(4, 1), (5, 1), (6, 1)]) @@ -187,7 +187,7 @@ fn dispatch_forced_change() { // run the normal change. { initialize_block(8, header.hash()); - assert!(<PendingChange<Test>>::exists()); + assert!(PendingChange::<Test>::exists()); assert_eq!( Grandpa::grandpa_authorities(), to_authorities(vec![(4, 1), (5, 1), (6, 1)]) @@ -204,9 +204,9 @@ fn dispatch_forced_change() { // time. for i in 9..11 { initialize_block(i, header.hash()); - assert!(!<PendingChange<Test>>::exists()); + assert!(!PendingChange::<Test>::exists()); assert_eq!(Grandpa::grandpa_authorities(), to_authorities(vec![(5, 1)])); - assert_eq!(Grandpa::next_forced(), Some(11)); + assert_eq!(NextForced::<Test>::get(), Some(11)); assert_noop!( Grandpa::schedule_change(to_authorities(vec![(5, 1), (6, 1)]), 5, Some(0)), Error::<Test>::TooSoon @@ -217,13 +217,13 @@ fn dispatch_forced_change() { { initialize_block(11, header.hash()); - assert!(!<PendingChange<Test>>::exists()); + assert!(!PendingChange::<Test>::exists()); assert_ok!(Grandpa::schedule_change( to_authorities(vec![(5, 1), (6, 1), (7, 1)]), 5, Some(0) )); - assert_eq!(Grandpa::next_forced(), Some(21)); + assert_eq!(NextForced::<Test>::get(), Some(21)); Grandpa::on_finalize(11); header = System::finalize(); } @@ -239,7 +239,10 @@ fn schedule_pause_only_when_live() { Grandpa::schedule_pause(1).unwrap(); // we've switched to the pending pause state - assert_eq!(Grandpa::state(), StoredState::PendingPause { scheduled_at: 1u64, delay: 1 }); + assert_eq!( + State::<Test>::get(), + StoredState::PendingPause { scheduled_at: 1u64, delay: 1 } + ); Grandpa::on_finalize(1); let _ = System::finalize(); @@ -253,7 +256,7 @@ fn schedule_pause_only_when_live() { let _ = System::finalize(); // after finalizing block 2 the set should have switched to paused state - assert_eq!(Grandpa::state(), StoredState::Paused); + assert_eq!(State::<Test>::get(), StoredState::Paused); }); } @@ -265,14 +268,14 @@ fn schedule_resume_only_when_paused() { // the set is currently live, resuming it is an error assert_noop!(Grandpa::schedule_resume(1), Error::<Test>::ResumeFailed); - assert_eq!(Grandpa::state(), StoredState::Live); + assert_eq!(State::<Test>::get(), StoredState::Live); // we schedule a pause to be applied instantly Grandpa::schedule_pause(0).unwrap(); Grandpa::on_finalize(1); let _ = System::finalize(); - assert_eq!(Grandpa::state(), StoredState::Paused); + assert_eq!(State::<Test>::get(), StoredState::Paused); // we schedule the set to go back live in 2 blocks initialize_block(2, Default::default()); @@ -289,7 +292,7 @@ fn schedule_resume_only_when_paused() { let _ = System::finalize(); // it should be live at block 4 - assert_eq!(Grandpa::state(), StoredState::Live); + assert_eq!(State::<Test>::get(), StoredState::Live); }); } @@ -342,7 +345,7 @@ fn report_equivocation_current_set_works() { let equivocation_key = &authorities[equivocation_authority_index].0; let equivocation_keyring = extract_keyring(equivocation_key); - let set_id = Grandpa::current_set_id(); + let set_id = CurrentSetId::<Test>::get(); // generate an equivocation proof, with two votes in the same round for // different block hashes signed by the same key @@ -424,7 +427,7 @@ fn report_equivocation_old_set_works() { let equivocation_keyring = extract_keyring(equivocation_key); - let set_id = Grandpa::current_set_id(); + let set_id = CurrentSetId::<Test>::get(); // generate an equivocation proof for the old set, let equivocation_proof = generate_equivocation_proof( @@ -487,7 +490,7 @@ fn report_equivocation_invalid_set_id() { let key_owner_proof = Historical::prove((sp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); - let set_id = Grandpa::current_set_id(); + let set_id = CurrentSetId::<Test>::get(); // generate an equivocation for a future set let equivocation_proof = generate_equivocation_proof( @@ -527,7 +530,7 @@ fn report_equivocation_invalid_session() { start_era(2); - let set_id = Grandpa::current_set_id(); + let set_id = CurrentSetId::<Test>::get(); // generate an equivocation proof at set id = 2 let equivocation_proof = generate_equivocation_proof( @@ -568,7 +571,7 @@ fn report_equivocation_invalid_key_owner_proof() { let equivocation_key = &authorities[equivocation_authority_index].0; let equivocation_keyring = extract_keyring(equivocation_key); - let set_id = Grandpa::current_set_id(); + let set_id = CurrentSetId::<Test>::get(); // generate an equivocation proof for the authority at index 0 let equivocation_proof = generate_equivocation_proof( @@ -611,7 +614,7 @@ fn report_equivocation_invalid_equivocation_proof() { let key_owner_proof = Historical::prove((sp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); - let set_id = Grandpa::current_set_id(); + let set_id = CurrentSetId::<Test>::get(); let assert_invalid_equivocation_proof = |equivocation_proof| { assert_err!( @@ -675,7 +678,7 @@ fn report_equivocation_validate_unsigned_prevents_duplicates() { let equivocation_authority_index = 0; let equivocation_key = &authorities[equivocation_authority_index].0; let equivocation_keyring = extract_keyring(equivocation_key); - let set_id = Grandpa::current_set_id(); + let set_id = CurrentSetId::<Test>::get(); let equivocation_proof = generate_equivocation_proof( set_id, @@ -748,12 +751,12 @@ fn report_equivocation_validate_unsigned_prevents_duplicates() { #[test] fn on_new_session_doesnt_start_new_set_if_schedule_change_failed() { new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| { - assert_eq!(Grandpa::current_set_id(), 0); + assert_eq!(CurrentSetId::<Test>::get(), 0); // starting a new era should lead to a change in the session // validators and trigger a new set start_era(1); - assert_eq!(Grandpa::current_set_id(), 1); + assert_eq!(CurrentSetId::<Test>::get(), 1); // we schedule a change delayed by 2 blocks, this should make it so that // when we try to rotate the session at the beginning of the era we will @@ -761,22 +764,22 @@ fn on_new_session_doesnt_start_new_set_if_schedule_change_failed() { // not increment the set id. Grandpa::schedule_change(to_authorities(vec![(1, 1)]), 2, None).unwrap(); start_era(2); - assert_eq!(Grandpa::current_set_id(), 1); + assert_eq!(CurrentSetId::<Test>::get(), 1); // everything should go back to normal after. start_era(3); - assert_eq!(Grandpa::current_set_id(), 2); + assert_eq!(CurrentSetId::<Test>::get(), 2); // session rotation might also fail to schedule a change if it's for a // forced change (i.e. grandpa is stalled) and it is too soon. - <NextForced<Test>>::put(1000); - <Stalled<Test>>::put((30, 1)); + NextForced::<Test>::put(1000); + Stalled::<Test>::put((30, 1)); // NOTE: we cannot go through normal era rotation since having `Stalled` // defined will also trigger a new set (regardless of whether the // session validators changed) Grandpa::on_new_session(true, std::iter::empty(), std::iter::empty()); - assert_eq!(Grandpa::current_set_id(), 2); + assert_eq!(CurrentSetId::<Test>::get(), 2); }); } @@ -790,19 +793,19 @@ fn cleans_up_old_set_id_session_mappings() { // we should have a session id mapping for all the set ids from // `max_set_id_session_entries` eras we have observed for i in 1..=max_set_id_session_entries { - assert!(Grandpa::session_for_set(i as u64).is_some()); + assert!(SetIdSession::<Test>::get(i as u64).is_some()); } start_era(max_set_id_session_entries * 2); // we should keep tracking the new mappings for new eras for i in max_set_id_session_entries + 1..=max_set_id_session_entries * 2 { - assert!(Grandpa::session_for_set(i as u64).is_some()); + assert!(SetIdSession::<Test>::get(i as u64).is_some()); } // but the old ones should have been pruned by now for i in 1..=max_set_id_session_entries { - assert!(Grandpa::session_for_set(i as u64).is_none()); + assert!(SetIdSession::<Test>::get(i as u64).is_none()); } }); } @@ -812,24 +815,24 @@ fn always_schedules_a_change_on_new_session_when_stalled() { new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| { start_era(1); - assert!(Grandpa::pending_change().is_none()); - assert_eq!(Grandpa::current_set_id(), 1); + assert!(PendingChange::<Test>::get().is_none()); + assert_eq!(CurrentSetId::<Test>::get(), 1); // if the session handler reports no change then we should not schedule // any pending change Grandpa::on_new_session(false, std::iter::empty(), std::iter::empty()); - assert!(Grandpa::pending_change().is_none()); - assert_eq!(Grandpa::current_set_id(), 1); + assert!(PendingChange::<Test>::get().is_none()); + assert_eq!(CurrentSetId::<Test>::get(), 1); // if grandpa is stalled then we should **always** schedule a forced // change on a new session - <Stalled<Test>>::put((10, 1)); + Stalled::<Test>::put((10, 1)); Grandpa::on_new_session(false, std::iter::empty(), std::iter::empty()); - assert!(Grandpa::pending_change().is_some()); - assert!(Grandpa::pending_change().unwrap().forced.is_some()); - assert_eq!(Grandpa::current_set_id(), 2); + assert!(PendingChange::<Test>::get().is_some()); + assert!(PendingChange::<Test>::get().unwrap().forced.is_some()); + assert_eq!(CurrentSetId::<Test>::get(), 2); }); } @@ -861,7 +864,7 @@ fn valid_equivocation_reports_dont_pay_fees() { let equivocation_key = &Grandpa::grandpa_authorities()[0].0; let equivocation_keyring = extract_keyring(equivocation_key); - let set_id = Grandpa::current_set_id(); + let set_id = CurrentSetId::<Test>::get(); // generate an equivocation proof. let equivocation_proof = generate_equivocation_proof( diff --git a/substrate/frame/identity/Cargo.toml b/substrate/frame/identity/Cargo.toml index bf974221b85739b80c79f759f2ed03486fb76544..4ea7f797d9ee5d73e24316d481a9625c54439d5a 100644 --- a/substrate/frame/identity/Cargo.toml +++ b/substrate/frame/identity/Cargo.toml @@ -18,11 +18,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive", "max-encoded-len"], workspace = true } enumflags2 = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/identity/src/tests.rs b/substrate/frame/identity/src/tests.rs index 7bf5b2a727607e910bc7a2470c383aa236a18e35..c4c02a2834ac486b8f79b9066d37d85973329494 100644 --- a/substrate/frame/identity/src/tests.rs +++ b/substrate/frame/identity/src/tests.rs @@ -26,7 +26,7 @@ use crate::{ use codec::{Decode, Encode}; use frame_support::{ assert_err, assert_noop, assert_ok, derive_impl, parameter_types, - traits::{ConstU32, ConstU64, Get, OnFinalize, OnInitialize}, + traits::{ConstU32, ConstU64, Get}, BoundedVec, }; use frame_system::EnsureRoot; @@ -105,6 +105,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { (account(20), 1000), (account(30), 1000), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -114,18 +115,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { ext } -fn run_to_block(n: u64) { - while System::block_number() < n { - Identity::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); - Identity::on_initialize(System::block_number()); - } -} - fn account(id: u8) -> AccountIdOf<Test> { [id; 32].into() } @@ -1714,7 +1703,7 @@ fn unaccepted_usernames_through_grant_should_expire() { Some((who.clone(), expiration, Provider::Allocation)) ); - run_to_block(now + expiration - 1); + System::run_to_block::<AllPalletsWithSystem>(now + expiration - 1); // Cannot be removed assert_noop!( @@ -1722,7 +1711,7 @@ fn unaccepted_usernames_through_grant_should_expire() { Error::<Test>::NotExpired ); - run_to_block(now + expiration); + System::run_to_block::<AllPalletsWithSystem>(now + expiration); // Anyone can remove assert_ok!(Identity::remove_expired_approval( @@ -1782,7 +1771,7 @@ fn unaccepted_usernames_through_deposit_should_expire() { Some((who.clone(), expiration, Provider::AuthorityDeposit(username_deposit))) ); - run_to_block(now + expiration - 1); + System::run_to_block::<AllPalletsWithSystem>(now + expiration - 1); // Cannot be removed assert_noop!( @@ -1790,7 +1779,7 @@ fn unaccepted_usernames_through_deposit_should_expire() { Error::<Test>::NotExpired ); - run_to_block(now + expiration); + System::run_to_block::<AllPalletsWithSystem>(now + expiration); // Anyone can remove assert_eq!( diff --git a/substrate/frame/im-online/Cargo.toml b/substrate/frame/im-online/Cargo.toml index 6c32c8ae898ee9495b5b2699a0d52354629cae0d..179c4c3ce3b18062c93eda51e54a00eea5531051 100644 --- a/substrate/frame/im-online/Cargo.toml +++ b/substrate/frame/im-online/Cargo.toml @@ -17,12 +17,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-authorship = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } sp-application-crypto = { features = ["serde"], workspace = true } sp-core = { features = ["serde"], workspace = true } sp-io = { workspace = true } diff --git a/substrate/frame/indices/Cargo.toml b/substrate/frame/indices/Cargo.toml index d81b2d5cabf178b44584c4f9e58a4e3ed0800c99..fdc1753e44fcb988df639c81356e38d4da84d5a0 100644 --- a/substrate/frame/indices/Cargo.toml +++ b/substrate/frame/indices/Cargo.toml @@ -17,13 +17,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } -sp-keyring = { optional = true, workspace = true } sp-runtime = { workspace = true } [dev-dependencies] @@ -40,8 +39,6 @@ std = [ "scale-info/std", "sp-core/std", "sp-io/std", - "sp-keyring", - "sp-keyring?/std", "sp-runtime/std", ] runtime-benchmarks = [ diff --git a/substrate/frame/indices/src/mock.rs b/substrate/frame/indices/src/mock.rs index 72bbc6dab4a42b7c3556aa986a770ff3fbf24bdc..80d0a88881f97cf284959b54b4b47adc05e38d56 100644 --- a/substrate/frame/indices/src/mock.rs +++ b/substrate/frame/indices/src/mock.rs @@ -59,6 +59,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); pallet_balances::GenesisConfig::<Test> { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/insecure-randomness-collective-flip/Cargo.toml b/substrate/frame/insecure-randomness-collective-flip/Cargo.toml index 1a47030812da7ce691ebd62469c1ffbeeb5a8953..789f130423a4bf7eefee34460ad4e2e0a3fc4598 100644 --- a/substrate/frame/insecure-randomness-collective-flip/Cargo.toml +++ b/substrate/frame/insecure-randomness-collective-flip/Cargo.toml @@ -17,30 +17,18 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } +frame = { workspace = true, features = ["runtime"] } safe-mix = { workspace = true } scale-info = { features = ["derive"], workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } -sp-runtime = { workspace = true } - -[dev-dependencies] -sp-core = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } [features] default = ["std"] std = [ "codec/std", - "frame-support/std", - "frame-system/std", + "frame/std", "safe-mix/std", "scale-info/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "sp-runtime/try-runtime", + "frame/try-runtime", ] diff --git a/substrate/frame/insecure-randomness-collective-flip/src/lib.rs b/substrate/frame/insecure-randomness-collective-flip/src/lib.rs index b605b4d08582be2613bad2662fdc4de30c5dfe4c..0e7e8001d5df7de0d94d8dff8cca4a57d4c6215b 100644 --- a/substrate/frame/insecure-randomness-collective-flip/src/lib.rs +++ b/substrate/frame/insecure-randomness-collective-flip/src/lib.rs @@ -42,13 +42,11 @@ //! ### Example - Get random seed for the current block //! //! ``` -//! use frame_support::traits::Randomness; +//! use frame::{prelude::*, traits::Randomness}; //! -//! #[frame_support::pallet] +//! #[frame::pallet] //! pub mod pallet { //! use super::*; -//! use frame_support::pallet_prelude::*; -//! use frame_system::pallet_prelude::*; //! //! #[pallet::pallet] //! pub struct Pallet<T>(_); @@ -73,9 +71,7 @@ use safe_mix::TripletMix; use codec::Encode; -use frame_support::{pallet_prelude::Weight, traits::Randomness}; -use frame_system::pallet_prelude::BlockNumberFor; -use sp_runtime::traits::{Hash, Saturating}; +use frame::{prelude::*, traits::Randomness}; const RANDOM_MATERIAL_LEN: u32 = 81; @@ -87,10 +83,9 @@ fn block_number_to_index<T: Config>(block_number: BlockNumberFor<T>) -> usize { pub use pallet::*; -#[frame_support::pallet] +#[frame::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; #[pallet::pallet] pub struct Pallet<T>(_); @@ -167,19 +162,14 @@ impl<T: Config> Randomness<T::Hash, BlockNumberFor<T>> for Pallet<T> { mod tests { use super::*; use crate as pallet_insecure_randomness_collective_flip; - - use sp_core::H256; - use sp_runtime::{traits::Header as _, BuildStorage}; - - use frame_support::{ - derive_impl, parameter_types, - traits::{OnInitialize, Randomness}, + use frame::{ + testing_prelude::{frame_system::limits, *}, + traits::Header as _, }; - use frame_system::limits; type Block = frame_system::mocking::MockBlock<Test>; - frame_support::construct_runtime!( + construct_runtime!( pub enum Test { System: frame_system, @@ -199,7 +189,7 @@ mod tests { impl pallet_insecure_randomness_collective_flip::Config for Test {} - fn new_test_ext() -> sp_io::TestExternalities { + fn new_test_ext() -> TestExternalities { let t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); t.into() } diff --git a/substrate/frame/lottery/Cargo.toml b/substrate/frame/lottery/Cargo.toml index eb6e0b703d08676111d0d3904ac8b0887b35392d..23eb19c7ffa7d29634a5748e22ed8d376be87e2a 100644 --- a/substrate/frame/lottery/Cargo.toml +++ b/substrate/frame/lottery/Cargo.toml @@ -18,10 +18,10 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } [dev-dependencies] diff --git a/substrate/frame/lottery/src/mock.rs b/substrate/frame/lottery/src/mock.rs index d2c442e2ac6e5acd0b096308ebed02ad9fe0f78e..ea3f69b6cfc5d8eba9904eb7f554cbb3c02787b9 100644 --- a/substrate/frame/lottery/src/mock.rs +++ b/substrate/frame/lottery/src/mock.rs @@ -20,10 +20,7 @@ use super::*; use crate as pallet_lottery; -use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU32, OnFinalize, OnInitialize}, -}; +use frame_support::{derive_impl, parameter_types, traits::ConstU32}; use frame_support_test::TestRandomness; use frame_system::EnsureRoot; use sp_runtime::{BuildStorage, Perbill}; @@ -78,21 +75,9 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); pallet_balances::GenesisConfig::<Test> { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); t.into() } - -/// Run until a particular block. -pub fn run_to_block(n: u64) { - while System::block_number() < n { - if System::block_number() > 1 { - Lottery::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - } - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Lottery::on_initialize(System::block_number()); - } -} diff --git a/substrate/frame/lottery/src/tests.rs b/substrate/frame/lottery/src/tests.rs index ae3a6c858f2426e2119f090a5c3e4f53b51a333b..119be5df49250989835a71081e280d31f8837c2b 100644 --- a/substrate/frame/lottery/src/tests.rs +++ b/substrate/frame/lottery/src/tests.rs @@ -17,12 +17,11 @@ //! Tests for the module. -use super::*; -use frame_support::{assert_noop, assert_ok, assert_storage_noop}; -use mock::{ - new_test_ext, run_to_block, Balances, BalancesCall, Lottery, RuntimeCall, RuntimeOrigin, - SystemCall, Test, +use crate::{ + mock::{Lottery, *}, + *, }; +use frame_support::{assert_noop, assert_ok, assert_storage_noop}; use sp_runtime::{traits::BadOrigin, TokenError}; #[test] @@ -74,13 +73,13 @@ fn basic_end_to_end_works() { assert_eq!(TicketsCount::<Test>::get(), 4); // Go to end - run_to_block(20); + System::run_to_block::<AllPalletsWithSystem>(20); assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(5), call.clone())); // Ticket isn't bought assert_eq!(TicketsCount::<Test>::get(), 4); // Go to payout - run_to_block(25); + System::run_to_block::<AllPalletsWithSystem>(25); // User 1 wins assert_eq!(Balances::free_balance(&1), 70 + 40); // Lottery is reset and restarted @@ -115,11 +114,11 @@ fn stop_repeat_works() { // Lottery still exists. assert!(crate::Lottery::<Test>::get().is_some()); // End and pick a winner. - run_to_block(length + delay); + System::run_to_block::<AllPalletsWithSystem>(length + delay); // Lottery stays dead and does not repeat. assert!(crate::Lottery::<Test>::get().is_none()); - run_to_block(length + delay + 1); + System::run_to_block::<AllPalletsWithSystem>(length + delay + 1); assert!(crate::Lottery::<Test>::get().is_none()); }); } @@ -281,7 +280,7 @@ fn buy_ticket_works() { assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 1, 20, 5, false)); // Go to start, buy ticket for transfer - run_to_block(5); + System::run_to_block::<AllPalletsWithSystem>(5); assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call)); assert_eq!(TicketsCount::<Test>::get(), 1); @@ -300,12 +299,12 @@ fn buy_ticket_works() { assert_eq!(TicketsCount::<Test>::get(), 2); // Go to end, can't buy tickets anymore - run_to_block(20); + System::run_to_block::<AllPalletsWithSystem>(20); assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(2), call.clone())); assert_eq!(TicketsCount::<Test>::get(), 2); // Go to payout, can't buy tickets when there is no lottery open - run_to_block(25); + System::run_to_block::<AllPalletsWithSystem>(25); assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(2), call.clone())); assert_eq!(TicketsCount::<Test>::get(), 0); assert_eq!(LotteryIndex::<Test>::get(), 1); @@ -409,7 +408,7 @@ fn no_participants_works() { assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 10, length, delay, false)); // End the lottery, no one wins. - run_to_block(length + delay); + System::run_to_block::<AllPalletsWithSystem>(length + delay); }); } diff --git a/substrate/frame/membership/Cargo.toml b/substrate/frame/membership/Cargo.toml index 67aa3503ac0af7fae50a082ff5c381460bc6c4e1..738d09b4b354d73a283e9842abf12fff100b3273 100644 --- a/substrate/frame/membership/Cargo.toml +++ b/substrate/frame/membership/Cargo.toml @@ -17,11 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } sp-core = { features = ["serde"], workspace = true } sp-io = { workspace = true } sp-runtime = { features = ["serde"], workspace = true } diff --git a/substrate/frame/merkle-mountain-range/Cargo.toml b/substrate/frame/merkle-mountain-range/Cargo.toml index 4daa394a82d7a16174622f4b1768981b3c12f329..ecbef01a9205c89badf63ad6d577a209dfe48e7f 100644 --- a/substrate/frame/merkle-mountain-range/Cargo.toml +++ b/substrate/frame/merkle-mountain-range/Cargo.toml @@ -16,43 +16,27 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } -frame-benchmarking = { optional = true, workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } -sp-core = { workspace = true } -sp-io = { workspace = true } sp-mmr-primitives = { workspace = true } -sp-runtime = { workspace = true } [dev-dependencies] -array-bytes = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } itertools = { workspace = true } +sp-tracing = { workspace = true, default-features = true } [features] default = ["std"] std = [ "codec/std", - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", + "frame/std", "log/std", "scale-info/std", - "sp-core/std", - "sp-io/std", "sp-mmr-primitives/std", - "sp-runtime/std", ] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", + "frame/runtime-benchmarks", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "sp-runtime/try-runtime", + "frame/try-runtime", ] diff --git a/substrate/frame/merkle-mountain-range/src/benchmarking.rs b/substrate/frame/merkle-mountain-range/src/benchmarking.rs index 07afd9529eb2608941881cf01c7d9a8e341bae7c..407f1f7ead60aa299234ffd3fa324051f41de950 100644 --- a/substrate/frame/merkle-mountain-range/src/benchmarking.rs +++ b/substrate/frame/merkle-mountain-range/src/benchmarking.rs @@ -20,8 +20,10 @@ #![cfg(feature = "runtime-benchmarks")] use crate::*; -use frame_benchmarking::v1::benchmarks_instance_pallet; -use frame_support::traits::OnInitialize; +use frame::{ + benchmarking::prelude::v1::benchmarks_instance_pallet, + deps::frame_support::traits::OnInitialize, +}; benchmarks_instance_pallet! { on_initialize { @@ -31,10 +33,10 @@ benchmarks_instance_pallet! { <<T as pallet::Config::<I>>::BenchmarkHelper as BenchmarkHelper>::setup(); for leaf in 0..(leaves - 1) { - Pallet::<T, I>::on_initialize((leaf as u32).into()); + <Pallet::<T, I> as OnInitialize<BlockNumberFor<T>>>::on_initialize((leaf as u32).into()); } }: { - Pallet::<T, I>::on_initialize((leaves as u32 - 1).into()); + <Pallet::<T, I> as OnInitialize<BlockNumberFor<T>>>::on_initialize((leaves as u32 - 1).into()); } verify { assert_eq!(crate::NumberOfLeaves::<T, I>::get(), leaves); } diff --git a/substrate/frame/merkle-mountain-range/src/default_weights.rs b/substrate/frame/merkle-mountain-range/src/default_weights.rs index b0ef0539018cdf8814223862a5508b81ad2863b1..d1ed12edd062847df6d3014c1dc41bfd8e62512f 100644 --- a/substrate/frame/merkle-mountain-range/src/default_weights.rs +++ b/substrate/frame/merkle-mountain-range/src/default_weights.rs @@ -18,16 +18,13 @@ //! Default weights for the MMR Pallet //! This file was not auto-generated. -use frame_support::weights::{ - constants::{RocksDbWeight as DbWeight, WEIGHT_REF_TIME_PER_NANOS}, - Weight, -}; +use frame::{deps::frame_support::weights::constants::*, weights_prelude::*}; impl crate::WeightInfo for () { fn on_initialize(peaks: u32) -> Weight { let peaks = u64::from(peaks); // Reading the parent hash. - let leaf_weight = DbWeight::get().reads(1); + let leaf_weight = RocksDbWeight::get().reads(1); // Blake2 hash cost. let hash_weight = Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_NANOS, 0); // No-op hook. @@ -36,6 +33,6 @@ impl crate::WeightInfo for () { leaf_weight .saturating_add(hash_weight) .saturating_add(hook_weight) - .saturating_add(DbWeight::get().reads_writes(2 + peaks, 2 + peaks)) + .saturating_add(RocksDbWeight::get().reads_writes(2 + peaks, 2 + peaks)) } } diff --git a/substrate/frame/merkle-mountain-range/src/lib.rs b/substrate/frame/merkle-mountain-range/src/lib.rs index 7dfe95c83361c6f6e0d70ac5bfcd43bf7d87788b..cc64dfcb7de88aad56c9a941c5d0a01d04b4d0a1 100644 --- a/substrate/frame/merkle-mountain-range/src/lib.rs +++ b/substrate/frame/merkle-mountain-range/src/lib.rs @@ -59,20 +59,17 @@ extern crate alloc; use alloc::vec::Vec; -use frame_support::weights::Weight; -use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor}; use log; -use sp_mmr_primitives::utils; -use sp_runtime::{ - traits::{self, One, Saturating}, - SaturatedConversion, -}; -pub use pallet::*; +use frame::prelude::*; + pub use sp_mmr_primitives::{ - self as primitives, utils::NodesUtils, Error, LeafDataProvider, LeafIndex, NodeIndex, + self as primitives, utils, utils::NodesUtils, AncestryProof, Error, FullLeaf, LeafDataProvider, + LeafIndex, LeafProof, NodeIndex, OnNewRoot, }; +pub use pallet::*; + #[cfg(feature = "runtime-benchmarks")] mod benchmarking; mod default_weights; @@ -90,11 +87,11 @@ mod tests; /// crate-local wrapper over [frame_system::Pallet]. Since the current block hash /// is not available (since the block is not finished yet), /// we use the `parent_hash` here along with parent block number. -pub struct ParentNumberAndHash<T: frame_system::Config> { - _phantom: core::marker::PhantomData<T>, +pub struct ParentNumberAndHash<T: Config> { + _phantom: PhantomData<T>, } -impl<T: frame_system::Config> LeafDataProvider for ParentNumberAndHash<T> { +impl<T: Config> LeafDataProvider for ParentNumberAndHash<T> { type LeafData = (BlockNumberFor<T>, <T as frame_system::Config>::Hash); fn leaf_data() -> Self::LeafData { @@ -111,13 +108,11 @@ pub trait BlockHashProvider<BlockNumber, BlockHash> { } /// Default implementation of BlockHashProvider using frame_system. -pub struct DefaultBlockHashProvider<T: frame_system::Config> { +pub struct DefaultBlockHashProvider<T: Config> { _phantom: core::marker::PhantomData<T>, } -impl<T: frame_system::Config> BlockHashProvider<BlockNumberFor<T>, T::Hash> - for DefaultBlockHashProvider<T> -{ +impl<T: Config> BlockHashProvider<BlockNumberFor<T>, T::Hash> for DefaultBlockHashProvider<T> { fn block_hash(block_number: BlockNumberFor<T>) -> T::Hash { frame_system::Pallet::<T>::block_hash(block_number) } @@ -142,17 +137,16 @@ impl BenchmarkHelper for () { type ModuleMmr<StorageType, T, I> = mmr::Mmr<StorageType, T, I, LeafOf<T, I>>; /// Leaf data. -type LeafOf<T, I> = <<T as Config<I>>::LeafData as primitives::LeafDataProvider>::LeafData; +type LeafOf<T, I> = <<T as Config<I>>::LeafData as LeafDataProvider>::LeafData; /// Hashing used for the pallet. pub(crate) type HashingOf<T, I> = <T as Config<I>>::Hashing; /// Hash type used for the pallet. -pub(crate) type HashOf<T, I> = <<T as Config<I>>::Hashing as traits::Hash>::Output; +pub(crate) type HashOf<T, I> = <<T as Config<I>>::Hashing as Hash>::Output; -#[frame_support::pallet] +#[frame::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; #[pallet::pallet] pub struct Pallet<T, I = ()>(PhantomData<(T, I)>); @@ -180,7 +174,7 @@ pub mod pallet { /// /// Then we create a tuple of these two hashes, SCALE-encode it (concatenate) and /// hash, to obtain a new MMR inner node - the new peak. - type Hashing: traits::Hash; + type Hashing: Hash; /// Data stored in the leaf nodes. /// @@ -198,7 +192,7 @@ pub mod pallet { /// two forks with identical line of ancestors compete to write the same offchain key, but /// that's fine as long as leaves only contain data coming from ancestors - conflicting /// writes are identical). - type LeafData: primitives::LeafDataProvider; + type LeafData: LeafDataProvider; /// A hook to act on the new MMR root. /// @@ -206,7 +200,7 @@ pub mod pallet { /// apart from having it in the storage. For instance you might output it in the header /// digest (see [`frame_system::Pallet::deposit_log`]) to make it available for Light /// Clients. Hook complexity should be `O(1)`. - type OnNewRoot: primitives::OnNewRoot<HashOf<Self, I>>; + type OnNewRoot: OnNewRoot<HashOf<Self, I>>; /// Block hash provider for a given block number. type BlockHashProvider: BlockHashProvider< @@ -248,9 +242,8 @@ pub mod pallet { #[pallet::hooks] impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> { fn on_initialize(_n: BlockNumberFor<T>) -> Weight { - use primitives::LeafDataProvider; let leaves = NumberOfLeaves::<T, I>::get(); - let peaks_before = sp_mmr_primitives::utils::NodesUtils::new(leaves).number_of_peaks(); + let peaks_before = NodesUtils::new(leaves).number_of_peaks(); let data = T::LeafData::leaf_data(); // append new leaf to MMR @@ -268,12 +261,12 @@ pub mod pallet { return T::WeightInfo::on_initialize(peaks_before as u32) }, }; - <T::OnNewRoot as primitives::OnNewRoot<_>>::on_new_root(&root); + <T::OnNewRoot as OnNewRoot<_>>::on_new_root(&root); NumberOfLeaves::<T, I>::put(leaves); RootHash::<T, I>::put(root); - let peaks_after = sp_mmr_primitives::utils::NodesUtils::new(leaves).number_of_peaks(); + let peaks_after = NodesUtils::new(leaves).number_of_peaks(); T::WeightInfo::on_initialize(peaks_before.max(peaks_after) as u32) } @@ -290,28 +283,28 @@ pub mod pallet { pub fn verify_leaves_proof<H, L>( root: H::Output, leaves: Vec<mmr::Node<H, L>>, - proof: primitives::LeafProof<H::Output>, -) -> Result<(), primitives::Error> + proof: LeafProof<H::Output>, +) -> Result<(), Error> where - H: traits::Hash, - L: primitives::FullLeaf, + H: Hash, + L: FullLeaf, { let is_valid = mmr::verify_leaves_proof::<H, L>(root, leaves, proof)?; if is_valid { Ok(()) } else { - Err(primitives::Error::Verify.log_debug(("The proof is incorrect.", root))) + Err(Error::Verify.log_debug(("The proof is incorrect.", root))) } } /// Stateless ancestry proof verification. pub fn verify_ancestry_proof<H, L>( root: H::Output, - ancestry_proof: primitives::AncestryProof<H::Output>, + ancestry_proof: AncestryProof<H::Output>, ) -> Result<H::Output, Error> where - H: traits::Hash, - L: primitives::FullLeaf, + H: Hash, + L: FullLeaf, { mmr::verify_ancestry_proof::<H, L>(root, ancestry_proof) .map_err(|_| Error::Verify.log_debug(("The ancestry proof is incorrect.", root))) @@ -383,7 +376,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> { pub fn generate_proof( block_numbers: Vec<BlockNumberFor<T>>, best_known_block_number: Option<BlockNumberFor<T>>, - ) -> Result<(Vec<LeafOf<T, I>>, primitives::LeafProof<HashOf<T, I>>), primitives::Error> { + ) -> Result<(Vec<LeafOf<T, I>>, LeafProof<HashOf<T, I>>), Error> { // check whether best_known_block_number provided, else use current best block let best_known_block_number = best_known_block_number.unwrap_or_else(|| <frame_system::Pallet<T>>::block_number()); @@ -393,7 +386,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> { // we need to translate the block_numbers into leaf indices. let leaf_indices = block_numbers .iter() - .map(|block_num| -> Result<LeafIndex, primitives::Error> { + .map(|block_num| -> Result<LeafIndex, Error> { Self::block_num_to_leaf_index(*block_num) }) .collect::<Result<Vec<LeafIndex>, _>>()?; @@ -410,14 +403,15 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> { /// or the proof is invalid. pub fn verify_leaves( leaves: Vec<LeafOf<T, I>>, - proof: primitives::LeafProof<HashOf<T, I>>, - ) -> Result<(), primitives::Error> { + proof: LeafProof<HashOf<T, I>>, + ) -> Result<(), Error> { if proof.leaf_count > NumberOfLeaves::<T, I>::get() || proof.leaf_count == 0 || proof.items.len().saturating_add(leaves.len()) as u64 > proof.leaf_count { - return Err(primitives::Error::Verify - .log_debug("The proof has incorrect number of leaves or proof items.")) + return Err( + Error::Verify.log_debug("The proof has incorrect number of leaves or proof items.") + ) } let mmr: ModuleMmr<mmr::storage::OffchainStorage, T, I> = mmr::Mmr::new(proof.leaf_count); @@ -425,14 +419,14 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> { if is_valid { Ok(()) } else { - Err(primitives::Error::Verify.log_debug("The proof is incorrect.")) + Err(Error::Verify.log_debug("The proof is incorrect.")) } } pub fn generate_ancestry_proof( prev_block_number: BlockNumberFor<T>, best_known_block_number: Option<BlockNumberFor<T>>, - ) -> Result<primitives::AncestryProof<HashOf<T, I>>, Error> { + ) -> Result<AncestryProof<HashOf<T, I>>, Error> { // check whether best_known_block_number provided, else use current best block let best_known_block_number = best_known_block_number.unwrap_or_else(|| <frame_system::Pallet<T>>::block_number()); @@ -445,16 +439,21 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> { } #[cfg(feature = "runtime-benchmarks")] - pub fn generate_mock_ancestry_proof() -> Result<primitives::AncestryProof<HashOf<T, I>>, Error> - { + pub fn generate_mock_ancestry_proof() -> Result<AncestryProof<HashOf<T, I>>, Error> { let leaf_count = Self::block_num_to_leaf_count(<frame_system::Pallet<T>>::block_number())?; let mmr: ModuleMmr<mmr::storage::OffchainStorage, T, I> = mmr::Mmr::new(leaf_count); mmr.generate_mock_ancestry_proof() } + pub fn is_ancestry_proof_optimal( + ancestry_proof: &primitives::AncestryProof<HashOf<T, I>>, + ) -> bool { + mmr::is_ancestry_proof_optimal::<HashingOf<T, I>>(ancestry_proof) + } + pub fn verify_ancestry_proof( root: HashOf<T, I>, - ancestry_proof: primitives::AncestryProof<HashOf<T, I>>, + ancestry_proof: AncestryProof<HashOf<T, I>>, ) -> Result<HashOf<T, I>, Error> { verify_ancestry_proof::<HashingOf<T, I>, LeafOf<T, I>>(root, ancestry_proof) } diff --git a/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs b/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs index f9a4580b9bb30b34d6d5c18a0ddcd4fee7100e52..69a08a8b2d6af916efb8d92838da0da2926fcfe8 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs @@ -20,11 +20,14 @@ use crate::{ storage::{OffchainStorage, RuntimeStorage, Storage}, Hasher, Node, NodeOf, }, - primitives::{self, Error, NodeIndex}, + primitives::{ + mmr_lib, mmr_lib::MMRStoreReadOps, utils::NodesUtils, AncestryProof, Error, FullLeaf, + LeafIndex, LeafProof, NodeIndex, + }, Config, HashOf, HashingOf, }; use alloc::vec::Vec; -use sp_mmr_primitives::{mmr_lib, mmr_lib::MMRStoreReadOps, utils::NodesUtils, LeafIndex}; +use frame::prelude::*; /// Stateless verification of the proof for a batch of leaves. /// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the @@ -33,11 +36,11 @@ use sp_mmr_primitives::{mmr_lib, mmr_lib::MMRStoreReadOps, utils::NodesUtils, Le pub fn verify_leaves_proof<H, L>( root: H::Output, leaves: Vec<Node<H, L>>, - proof: primitives::LeafProof<H::Output>, + proof: LeafProof<H::Output>, ) -> Result<bool, Error> where - H: sp_runtime::traits::Hash, - L: primitives::FullLeaf, + H: Hash, + L: FullLeaf, { let size = NodesUtils::new(proof.leaf_count).size(); @@ -60,13 +63,25 @@ where .map_err(|e| Error::Verify.log_debug(e)) } +pub fn is_ancestry_proof_optimal<H>(ancestry_proof: &AncestryProof<H::Output>) -> bool +where + H: frame::traits::Hash, +{ + let prev_mmr_size = NodesUtils::new(ancestry_proof.prev_leaf_count).size(); + let mmr_size = NodesUtils::new(ancestry_proof.leaf_count).size(); + + let expected_proof_size = + mmr_lib::ancestry_proof::expected_ancestry_proof_size(prev_mmr_size, mmr_size); + ancestry_proof.items.len() == expected_proof_size +} + pub fn verify_ancestry_proof<H, L>( root: H::Output, - ancestry_proof: primitives::AncestryProof<H::Output>, + ancestry_proof: AncestryProof<H::Output>, ) -> Result<H::Output, Error> where - H: sp_runtime::traits::Hash, - L: primitives::FullLeaf, + H: Hash, + L: FullLeaf, { let mmr_size = NodesUtils::new(ancestry_proof.leaf_count).size(); @@ -80,9 +95,9 @@ where ); let raw_ancestry_proof = mmr_lib::AncestryProof::<Node<H, L>, Hasher<H, L>> { + prev_mmr_size: mmr_lib::helper::leaf_index_to_mmr_size(ancestry_proof.prev_leaf_count - 1), prev_peaks: ancestry_proof.prev_peaks.into_iter().map(|hash| Node::Hash(hash)).collect(), - prev_size: mmr_lib::helper::leaf_index_to_mmr_size(ancestry_proof.prev_leaf_count - 1), - proof: prev_peaks_proof, + prev_peaks_proof, }; let prev_root = mmr_lib::ancestry_proof::bagging_peaks_hashes::<Node<H, L>, Hasher<H, L>>( @@ -104,7 +119,7 @@ pub struct Mmr<StorageType, T, I, L> where T: Config<I>, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, Storage<StorageType, T, I, L>: MMRStoreReadOps<NodeOf<T, I, L>> + mmr_lib::MMRStoreWriteOps<NodeOf<T, I, L>>, { @@ -116,7 +131,7 @@ impl<StorageType, T, I, L> Mmr<StorageType, T, I, L> where T: Config<I>, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, Storage<StorageType, T, I, L>: MMRStoreReadOps<NodeOf<T, I, L>> + mmr_lib::MMRStoreWriteOps<NodeOf<T, I, L>>, { @@ -133,7 +148,7 @@ where pub fn verify_leaves_proof( &self, leaves: Vec<L>, - proof: primitives::LeafProof<HashOf<T, I>>, + proof: LeafProof<HashOf<T, I>>, ) -> Result<bool, Error> { let p = mmr_lib::MerkleProof::<NodeOf<T, I, L>, Hasher<HashingOf<T, I>, L>>::new( self.mmr.mmr_size(), @@ -167,7 +182,7 @@ impl<T, I, L> Mmr<RuntimeStorage, T, I, L> where T: Config<I>, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, { /// Push another item to the MMR. /// @@ -195,7 +210,7 @@ impl<T, I, L> Mmr<OffchainStorage, T, I, L> where T: Config<I>, I: 'static, - L: primitives::FullLeaf + codec::Decode, + L: FullLeaf + codec::Decode, { /// Generate a proof for given leaf indices. /// @@ -204,7 +219,7 @@ where pub fn generate_proof( &self, leaf_indices: Vec<NodeIndex>, - ) -> Result<(Vec<L>, primitives::LeafProof<HashOf<T, I>>), Error> { + ) -> Result<(Vec<L>, LeafProof<HashOf<T, I>>), Error> { let positions = leaf_indices .iter() .map(|index| mmr_lib::leaf_index_to_pos(*index)) @@ -222,7 +237,7 @@ where self.mmr .gen_proof(positions) .map_err(|e| Error::GenerateProof.log_error(e)) - .map(|p| primitives::LeafProof { + .map(|p| LeafProof { leaf_indices, leaf_count, items: p.proof_items().iter().map(|x| x.hash()).collect(), @@ -233,19 +248,19 @@ where pub fn generate_ancestry_proof( &self, prev_leaf_count: LeafIndex, - ) -> Result<primitives::AncestryProof<HashOf<T, I>>, Error> { + ) -> Result<AncestryProof<HashOf<T, I>>, Error> { let prev_mmr_size = NodesUtils::new(prev_leaf_count).size(); let raw_ancestry_proof = self .mmr .gen_ancestry_proof(prev_mmr_size) .map_err(|e| Error::GenerateProof.log_error(e))?; - Ok(primitives::AncestryProof { + Ok(AncestryProof { prev_peaks: raw_ancestry_proof.prev_peaks.into_iter().map(|p| p.hash()).collect(), prev_leaf_count, leaf_count: self.leaves, items: raw_ancestry_proof - .proof + .prev_peaks_proof .proof_items() .iter() .map(|(index, item)| (*index, item.hash())) @@ -258,12 +273,10 @@ where /// 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<sp_mmr_primitives::AncestryProof<HashOf<T, I>>, Error> { + pub fn generate_mock_ancestry_proof(&self) -> Result<AncestryProof<HashOf<T, I>>, Error> { use crate::ModuleMmr; use alloc::vec; - use sp_mmr_primitives::mmr_lib::helper; + use mmr_lib::helper; let mmr: ModuleMmr<OffchainStorage, T, I> = Mmr::new(self.leaves); let store = <Storage<OffchainStorage, T, I, L>>::default(); @@ -289,7 +302,7 @@ where proof_items.push((leaf_pos, leaf)); } - Ok(sp_mmr_primitives::AncestryProof { + Ok(AncestryProof { prev_peaks, prev_leaf_count: self.leaves, leaf_count: self.leaves, diff --git a/substrate/frame/merkle-mountain-range/src/mmr/mod.rs b/substrate/frame/merkle-mountain-range/src/mmr/mod.rs index 5b73f53506e92f3c0d8b8ffbd876cc6fafc987b9..d3232f23bce1cd3352775fb8019f910babdb8a02 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/mod.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/mod.rs @@ -18,10 +18,9 @@ mod mmr; pub mod storage; -use sp_mmr_primitives::{mmr_lib, DataOrHash, FullLeaf}; -use sp_runtime::traits; - -pub use self::mmr::{verify_ancestry_proof, verify_leaves_proof, Mmr}; +pub use self::mmr::{is_ancestry_proof_optimal, verify_ancestry_proof, verify_leaves_proof, Mmr}; +use crate::primitives::{mmr_lib, DataOrHash, FullLeaf}; +use frame::traits; /// Node type for runtime `T`. pub type NodeOf<T, I, L> = Node<<T as crate::Config<I>>::Hashing, L>; diff --git a/substrate/frame/merkle-mountain-range/src/mmr/storage.rs b/substrate/frame/merkle-mountain-range/src/mmr/storage.rs index 02852388b41715cd0b5272c4609df7b8432ede9b..c201c0ea846d33f5a820f88756f2f843377ea2d6 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/storage.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/storage.rs @@ -17,18 +17,22 @@ //! An MMR storage implementation. -use alloc::{vec, vec::Vec}; -use codec::Encode; -use core::iter::Peekable; -use log::{debug, trace}; -use sp_core::offchain::StorageKind; -use sp_mmr_primitives::{mmr_lib, mmr_lib::helper, utils::NodesUtils}; - use crate::{ mmr::{Node, NodeOf}, - primitives::{self, NodeIndex}, + primitives::{mmr_lib, mmr_lib::helper, utils::NodesUtils, FullLeaf, NodeIndex}, BlockHashProvider, Config, Nodes, NumberOfLeaves, Pallet, }; +use alloc::{vec, vec::Vec}; +use codec::Encode; +use core::iter::Peekable; +use frame::{ + deps::{ + sp_core::offchain::StorageKind, + sp_io::{offchain, offchain_index}, + }, + prelude::*, +}; +use log::{debug, trace}; /// A marker type for runtime-specific storage implementation. /// @@ -48,20 +52,20 @@ pub struct OffchainStorage; impl OffchainStorage { fn get(key: &[u8]) -> Option<Vec<u8>> { - sp_io::offchain::local_storage_get(StorageKind::PERSISTENT, &key) + offchain::local_storage_get(StorageKind::PERSISTENT, &key) } #[cfg(not(feature = "runtime-benchmarks"))] fn set<T: Config<I>, I: 'static>(key: &[u8], value: &[u8]) { - sp_io::offchain_index::set(key, value); + offchain_index::set(key, value); } #[cfg(feature = "runtime-benchmarks")] fn set<T: Config<I>, I: 'static>(key: &[u8], value: &[u8]) { if crate::pallet::UseLocalStorage::<T, I>::get() { - sp_io::offchain::local_storage_set(StorageKind::PERSISTENT, key, value); + offchain::local_storage_set(StorageKind::PERSISTENT, key, value); } else { - sp_io::offchain_index::set(key, value); + offchain_index::set(key, value); } } } @@ -82,7 +86,7 @@ impl<T, I, L> mmr_lib::MMRStoreReadOps<NodeOf<T, I, L>> for Storage<OffchainStor where T: Config<I>, I: 'static, - L: primitives::FullLeaf + codec::Decode, + L: FullLeaf + Decode, { fn get_elem(&self, pos: NodeIndex) -> mmr_lib::Result<Option<NodeOf<T, I, L>>> { // Find out which leaf added node `pos` in the MMR. @@ -120,7 +124,7 @@ impl<T, I, L> mmr_lib::MMRStoreWriteOps<NodeOf<T, I, L>> for Storage<OffchainSto where T: Config<I>, I: 'static, - L: primitives::FullLeaf + codec::Decode, + L: FullLeaf + Decode, { fn append(&mut self, _: NodeIndex, _: Vec<NodeOf<T, I, L>>) -> mmr_lib::Result<()> { panic!("MMR must not be altered in the off-chain context.") @@ -131,7 +135,7 @@ impl<T, I, L> mmr_lib::MMRStoreReadOps<NodeOf<T, I, L>> for Storage<RuntimeStora where T: Config<I>, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, { fn get_elem(&self, pos: NodeIndex) -> mmr_lib::Result<Option<NodeOf<T, I, L>>> { Ok(Nodes::<T, I>::get(pos).map(Node::Hash)) @@ -142,7 +146,7 @@ impl<T, I, L> mmr_lib::MMRStoreWriteOps<NodeOf<T, I, L>> for Storage<RuntimeStor where T: Config<I>, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, { fn append(&mut self, pos: NodeIndex, elems: Vec<NodeOf<T, I, L>>) -> mmr_lib::Result<()> { if elems.is_empty() { @@ -205,7 +209,7 @@ impl<T, I, L> Storage<RuntimeStorage, T, I, L> where T: Config<I>, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, { fn store_to_offchain( pos: NodeIndex, diff --git a/substrate/frame/merkle-mountain-range/src/mock.rs b/substrate/frame/merkle-mountain-range/src/mock.rs index 606719c6deba121f693250694894d104bae030f7..4c234e0d94aaf0d478dde14fef517acb70c6245a 100644 --- a/substrate/frame/merkle-mountain-range/src/mock.rs +++ b/substrate/frame/merkle-mountain-range/src/mock.rs @@ -18,14 +18,20 @@ use crate as pallet_mmr; use crate::*; +use crate::{ + frame_system::DefaultConfig, + primitives::{Compact, LeafDataProvider}, +}; use codec::{Decode, Encode}; -use frame_support::{derive_impl, parameter_types}; -use sp_mmr_primitives::{Compact, LeafDataProvider}; -use sp_runtime::traits::Keccak256; +use frame::{ + deps::frame_support::derive_impl, + prelude::{frame_system, frame_system::config_preludes::TestDefaultConfig}, + testing_prelude::*, +}; -type Block = frame_system::mocking::MockBlock<Test>; +type Block = MockBlock<Test>; -frame_support::construct_runtime!( +construct_runtime!( pub enum Test { System: frame_system, @@ -33,7 +39,7 @@ frame_support::construct_runtime!( } ); -#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +#[derive_impl(TestDefaultConfig)] impl frame_system::Config for Test { type Block = Block; } diff --git a/substrate/frame/merkle-mountain-range/src/tests.rs b/substrate/frame/merkle-mountain-range/src/tests.rs index 93e3d06eaa0af06d8445e477c31702e75b51e46c..03b08e51c32aea952ab21f040e92d3e22b44b160 100644 --- a/substrate/frame/merkle-mountain-range/src/tests.rs +++ b/substrate/frame/merkle-mountain-range/src/tests.rs @@ -17,19 +17,21 @@ use crate::{mock::*, *}; -use frame_support::traits::{Get, OnInitialize}; -use sp_core::{ - offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt}, - H256, +use crate::primitives::{mmr_lib::helper, utils, Compact, LeafProof}; + +use frame::{ + deps::sp_core::{ + offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt}, + H256, + }, + testing_prelude::*, }; -use sp_mmr_primitives::{mmr_lib::helper, utils, Compact, LeafProof}; -use sp_runtime::BuildStorage; -pub(crate) fn new_test_ext() -> sp_io::TestExternalities { +pub(crate) fn new_test_ext() -> TestState { frame_system::GenesisConfig::<Test>::default().build_storage().unwrap().into() } -fn register_offchain_ext(ext: &mut sp_io::TestExternalities) { +fn register_offchain_ext(ext: &mut TestState) { let (offchain, _offchain_state) = TestOffchainExt::with_offchain_db(ext.offchain_db()); ext.register_extension(OffchainDbExt::new(offchain.clone())); ext.register_extension(OffchainWorkerExt::new(offchain)); @@ -54,7 +56,7 @@ pub(crate) fn hex(s: &str) -> H256 { s.parse().unwrap() } -type BlockNumber = frame_system::pallet_prelude::BlockNumberFor<Test>; +type BlockNumber = BlockNumberFor<Test>; fn decode_node( v: Vec<u8>, @@ -517,7 +519,7 @@ fn should_verify() { } fn generate_and_verify_batch_proof( - ext: &mut sp_io::TestExternalities, + ext: &mut TestExternalities, block_numbers: &Vec<u64>, blocks_to_add: usize, ) { @@ -719,7 +721,6 @@ fn should_verify_on_the_next_block_since_there_is_no_pruning_yet() { #[test] fn should_verify_canonicalized() { - use frame_support::traits::Hooks; sp_tracing::init_for_tests(); // How deep is our fork-aware storage (in terms of blocks/leaves, nodes will be more). @@ -810,6 +811,7 @@ fn generating_and_verifying_ancestry_proofs_works_correctly() { for prev_block_number in 1usize..=500 { let proof = Pallet::<Test>::generate_ancestry_proof(prev_block_number as u64, None).unwrap(); + assert!(Pallet::<Test>::is_ancestry_proof_optimal(&proof)); assert_eq!( Pallet::<Test>::verify_ancestry_proof(root, proof), Ok(prev_roots[prev_block_number - 1]) diff --git a/substrate/frame/message-queue/Cargo.toml b/substrate/frame/message-queue/Cargo.toml index a6de61d70abf3a79f6e675f6b6e550c25301b11f..7b0de7c1e4ff867bd0d19a04428a02489e34dad4 100644 --- a/substrate/frame/message-queue/Cargo.toml +++ b/substrate/frame/message-queue/Cargo.toml @@ -13,15 +13,15 @@ workspace = true [dependencies] codec = { features = ["derive"], workspace = true } +environmental = { workspace = true } +log = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { optional = true, features = ["derive"], workspace = true, default-features = true } -log = { workspace = true } -environmental = { workspace = true } +sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -sp-arithmetic = { workspace = true } sp-weights = { workspace = true } frame-benchmarking = { optional = true, workspace = true } @@ -29,10 +29,10 @@ frame-support = { workspace = true } frame-system = { workspace = true } [dev-dependencies] -sp-crypto-hashing = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } rand_distr = { workspace = true } +sp-crypto-hashing = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/metadata-hash-extension/Cargo.toml b/substrate/frame/metadata-hash-extension/Cargo.toml index bca2c3ffb198692608d3844d92d873a38430dc8a..c7a417795ffee04eb4144749dcca595f8fed44ba 100644 --- a/substrate/frame/metadata-hash-extension/Cargo.toml +++ b/substrate/frame/metadata-hash-extension/Cargo.toml @@ -11,22 +11,22 @@ description = "FRAME signed extension for verifying the metadata hash" [dependencies] array-bytes = { workspace = true, default-features = true } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } -sp-runtime = { features = ["serde"], workspace = true } +const-hex = { workspace = true } +docify = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } log = { workspace = true } -docify = { workspace = true } -const-hex = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } +sp-runtime = { features = ["serde"], workspace = true } [dev-dependencies] -substrate-wasm-builder = { features = ["metadata-hash"], workspace = true, default-features = true } -substrate-test-runtime-client = { workspace = true } -sp-api = { workspace = true, default-features = true } -sp-transaction-pool = { workspace = true, default-features = true } +frame-metadata = { features = ["current", "unstable"], workspace = true, default-features = true } merkleized-metadata = { workspace = true } -frame-metadata = { features = ["current"], workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } +sp-transaction-pool = { workspace = true, default-features = true } +substrate-test-runtime-client = { workspace = true } +substrate-wasm-builder = { features = ["metadata-hash"], workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/metadata-hash-extension/src/tests.rs b/substrate/frame/metadata-hash-extension/src/tests.rs index 11a3345ee15ce2b7217f6070704f6bfbdabf78b9..7a6966f4629027dfe25cc65a866b415a3d9d45ea 100644 --- a/substrate/frame/metadata-hash-extension/src/tests.rs +++ b/substrate/frame/metadata-hash-extension/src/tests.rs @@ -144,6 +144,7 @@ mod docs { // Add the `CheckMetadataHash` extension. // The position in this list is not important, so we could also add it to beginning. frame_metadata_hash_extension::CheckMetadataHash<Runtime>, + frame_system::WeightReclaim<Runtime>, ); /// In your runtime this will be your real address type. diff --git a/substrate/frame/migrations/Cargo.toml b/substrate/frame/migrations/Cargo.toml index a32e48e652805f91fabf340916351f6c27d9b79a..f05db314ae57ee201a7d715c4e642a3d06c9e5cf 100644 --- a/substrate/frame/migrations/Cargo.toml +++ b/substrate/frame/migrations/Cargo.toml @@ -11,24 +11,25 @@ repository.workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { features = ["derive"], workspace = true } cfg-if = { workspace = true } +codec = { features = ["derive"], workspace = true } docify = { workspace = true } impl-trait-for-tuples = { workspace = true } log = { workspace = true, default-features = true } scale-info = { features = ["derive"], workspace = true } +frame = { workspace = true, features = ["runtime"] } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } sp-core = { workspace = true } +sp-io = { workspace = true } sp-runtime = { workspace = true } [dev-dependencies] frame-executive = { workspace = true, default-features = true } sp-api = { features = ["std"], workspace = true, default-features = true } sp-block-builder = { features = ["std"], workspace = true, default-features = true } -sp-io = { features = ["std"], workspace = true, default-features = true } sp-tracing = { features = ["std"], workspace = true, default-features = true } sp-version = { features = ["std"], workspace = true, default-features = true } @@ -42,9 +43,11 @@ std = [ "frame-benchmarking?/std", "frame-support/std", "frame-system/std", + "frame/std", "log/std", "scale-info/std", "sp-core/std", + "sp-io/std", "sp-runtime/std", ] @@ -52,6 +55,7 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "frame/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] @@ -59,5 +63,6 @@ try-runtime = [ "frame-executive/try-runtime", "frame-support/try-runtime", "frame-system/try-runtime", + "frame/try-runtime", "sp-runtime/try-runtime", ] diff --git a/substrate/frame/migrations/src/benchmarking.rs b/substrate/frame/migrations/src/benchmarking.rs index c076d40bb05cddebef31bfed7e597ba607b0de86..f06870fa9502c86c892cff4fdcad5539062ec9ae 100644 --- a/substrate/frame/migrations/src/benchmarking.rs +++ b/substrate/frame/migrations/src/benchmarking.rs @@ -19,8 +19,11 @@ use super::*; +use core::array; use frame_benchmarking::{v2::*, BenchmarkError}; use frame_system::{Pallet as System, RawOrigin}; +use sp_core::{twox_128, Get}; +use sp_io::{storage, KillStorageResult}; use sp_runtime::traits::One; fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) { @@ -204,6 +207,40 @@ mod benches { ); } + #[benchmark(skip_meta, pov_mode = Measured)] + fn reset_pallet_migration(n: Linear<0, 2048>) -> Result<(), BenchmarkError> { + let prefix: [u8; 16] = twox_128(b"__ResetPalletBenchmarkPrefix__"); + + for i in 0..n { + // we need to avoid allocations here + let mut iter = prefix.into_iter().chain(i.to_le_bytes()); + let key: [u8; 20] = array::from_fn(|_| iter.next().unwrap()); + // 32 byte will trigger the worst case where the value is + // no longer stored inline + storage::set(&key, &[0u8; 32]); + } + + let result; + #[block] + { + result = storage::clear_prefix(&prefix, None); + } + + // It will always reports no keys removed because they are still in the overlay. + // However, the benchmarking PoV results are correctly dependent on the amount of + // keys removed. + match result { + KillStorageResult::AllRemoved(_i) => { + // during the test the storage is not comitted and `i` will always be 0 + #[cfg(not(test))] + ensure!(_i == n, "Not all keys are removed"); + }, + _ => Err("Not all keys were removed")?, + } + + Ok(()) + } + fn cursor<T: Config>() -> CursorOf<T> { // Note: The weight of a function can depend on the weight of reading the `inner_cursor`. // `Cursor` is a user provided type. Now instead of requiring something like `Cursor: diff --git a/substrate/frame/migrations/src/lib.rs b/substrate/frame/migrations/src/lib.rs index d9490e7dcfe99fed3cc70560f6f25e61f4fc190f..fef61468e6e4ebe6c70626d13e23800f3e65f84e 100644 --- a/substrate/frame/migrations/src/lib.rs +++ b/substrate/frame/migrations/src/lib.rs @@ -145,6 +145,7 @@ #![cfg_attr(not(feature = "std"), no_std)] mod benchmarking; +pub mod migrations; mod mock; pub mod mock_helpers; mod tests; @@ -298,7 +299,11 @@ type PreUpgradeBytes<T: Config> = pub mod pallet { use super::*; + /// The in-code storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet<T>(_); #[pallet::config(with_default)] diff --git a/substrate/frame/migrations/src/migrations.rs b/substrate/frame/migrations/src/migrations.rs new file mode 100644 index 0000000000000000000000000000000000000000..796ff0f956599d959a78dadcbf65e068ff95b996 --- /dev/null +++ b/substrate/frame/migrations/src/migrations.rs @@ -0,0 +1,135 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Generic multi block migrations not specific to any pallet. + +use crate::{weights::WeightInfo, Config}; +use codec::Encode; +use core::marker::PhantomData; +use frame_support::{ + migrations::{SteppedMigration, SteppedMigrationError, StoreInCodeStorageVersion}, + traits::{GetStorageVersion, PalletInfoAccess}, + weights::WeightMeter, +}; +use sp_core::{twox_128, Get}; +use sp_io::{storage::clear_prefix, KillStorageResult}; +use sp_runtime::SaturatedConversion; + +/// Remove all of a pallet's state and re-initializes it to the current in-code storage version. +/// +/// It uses the multi block migration frame. Hence it is safe to use even on +/// pallets that contain a lot of storage. +/// +/// # Parameters +/// +/// - T: The runtime. Used to access the weight definition. +/// - P: The pallet to resetted as defined in construct runtime +/// +/// # Note +/// +/// If your pallet does rely of some state in genesis you need to take care of that +/// separately. This migration only sets the storage version after wiping. +pub struct ResetPallet<T, P>(PhantomData<(T, P)>); + +impl<T, P> ResetPallet<T, P> +where + P: PalletInfoAccess, +{ + #[cfg(feature = "try-runtime")] + fn num_keys() -> u64 { + let prefix = P::name_hash().to_vec(); + crate::storage::KeyPrefixIterator::new(prefix.clone(), prefix, |_| Ok(())).count() as _ + } +} + +impl<T, P, V> SteppedMigration for ResetPallet<T, P> +where + T: Config, + P: PalletInfoAccess + GetStorageVersion<InCodeStorageVersion = V>, + V: StoreInCodeStorageVersion<P>, +{ + type Cursor = bool; + type Identifier = [u8; 16]; + + fn id() -> Self::Identifier { + ("RemovePallet::", P::name()).using_encoded(twox_128) + } + + fn step( + cursor: Option<Self::Cursor>, + meter: &mut WeightMeter, + ) -> Result<Option<Self::Cursor>, SteppedMigrationError> { + // we write the storage version in a seperate block + if cursor.unwrap_or(false) { + let required = T::DbWeight::get().writes(1); + meter + .try_consume(required) + .map_err(|_| SteppedMigrationError::InsufficientWeight { required })?; + V::store_in_code_storage_version(); + return Ok(None); + } + + let base_weight = T::WeightInfo::reset_pallet_migration(0); + let weight_per_key = T::WeightInfo::reset_pallet_migration(1).saturating_sub(base_weight); + let key_budget = meter + .remaining() + .saturating_sub(base_weight) + .checked_div_per_component(&weight_per_key) + .unwrap_or_default() + .saturated_into(); + + if key_budget == 0 { + return Err(SteppedMigrationError::InsufficientWeight { + required: T::WeightInfo::reset_pallet_migration(1), + }) + } + + let (keys_removed, is_done) = match clear_prefix(&P::name_hash(), Some(key_budget)) { + KillStorageResult::AllRemoved(value) => (value, true), + KillStorageResult::SomeRemaining(value) => (value, false), + }; + + meter.consume(T::WeightInfo::reset_pallet_migration(keys_removed)); + + Ok(Some(is_done)) + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<alloc::vec::Vec<u8>, sp_runtime::TryRuntimeError> { + let num_keys: u64 = Self::num_keys(); + log::info!("ResetPallet<{}>: Trying to remove {num_keys} keys.", P::name()); + Ok(num_keys.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: alloc::vec::Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> { + use codec::Decode; + let keys_before = u64::decode(&mut state.as_ref()).expect("We encoded as u64 above; qed"); + let keys_now = Self::num_keys(); + log::info!("ResetPallet<{}>: Keys remaining after migration: {keys_now}", P::name()); + + if keys_before <= keys_now { + log::error!("ResetPallet<{}>: Did not remove any keys.", P::name()); + Err("ResetPallet failed")?; + } + + if keys_now != 1 { + log::error!("ResetPallet<{}>: Should have a single key after reset", P::name()); + Err("ResetPallet failed")?; + } + + Ok(()) + } +} diff --git a/substrate/frame/migrations/src/mock.rs b/substrate/frame/migrations/src/mock.rs index 48ff175f8137860823589122060f158f53e4c4fd..ea86899cad83ce5dee779ee24aee088e60e17894 100644 --- a/substrate/frame/migrations/src/mock.rs +++ b/substrate/frame/migrations/src/mock.rs @@ -21,12 +21,7 @@ use crate::{mock_helpers::*, Event, Historic}; -use frame_support::{ - derive_impl, - migrations::*, - traits::{OnFinalize, OnInitialize}, - weights::Weight, -}; +use frame_support::{derive_impl, migrations::*, weights::Weight}; use frame_system::EventRecord; use sp_core::H256; @@ -113,18 +108,18 @@ pub fn test_closure<R>(f: impl FnOnce() -> R) -> R { ext.execute_with(f) } -pub fn run_to_block(n: u32) { - while System::block_number() < n as u64 { - log::debug!("Block {}", System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Migrations::on_initialize(System::block_number()); - // Executive calls this: - <Migrations as MultiStepMigrator>::step(); - - Migrations::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - } +pub fn run_to_block(n: u64) { + System::run_to_block_with::<AllPalletsWithSystem>( + n, + frame_system::RunToBlockHooks::default() + .before_initialize(|bn| { + log::debug!("Block {bn}"); + }) + .after_initialize(|_| { + // Executive calls this: + <Migrations as MultiStepMigrator>::step(); + }), + ); } /// Returns the historic migrations, sorted by their identifier. diff --git a/substrate/frame/migrations/src/weights.rs b/substrate/frame/migrations/src/weights.rs index 49ae379dba020a39d12c6252c032e8f74ab2f494..10dfd82cbd8132358f82baf17340c62cbd26a5d3 100644 --- a/substrate/frame/migrations/src/weights.rs +++ b/substrate/frame/migrations/src/weights.rs @@ -18,36 +18,38 @@ //! Autogenerated weights for `pallet_migrations` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-11-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-wiukf8gn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` +//! HOSTNAME: `17938671047b`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate-node +// frame-omni-bencher +// v1 // benchmark // pallet -// --chain=dev +// --extrinsic=* +// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm +// --pallet=pallet_migrations +// --header=/__w/polkadot-sdk/polkadot-sdk/substrate/HEADER-APACHE2 +// --output=/__w/polkadot-sdk/polkadot-sdk/substrate/frame/migrations/src/weights.rs +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --pallet=pallet_migrations +// --heap-pages=4096 +// --template=substrate/.maintain/frame-weight-template.hbs // --no-storage-info -// --no-median-slopes // --no-min-squares -// --extrinsic=* -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./substrate/frame/migrations/src/weights.rs -// --header=./substrate/HEADER-APACHE2 -// --template=./substrate/.maintain/frame-weight-template.hbs +// --no-median-slopes +// --genesis-builder-policy=none +// --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic,pallet_nomination_pools,pallet_remark,pallet_transaction_storage #![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; +use frame::weights_prelude::*; /// Weight functions needed for `pallet_migrations`. pub trait WeightInfo { @@ -63,6 +65,7 @@ pub trait WeightInfo { fn force_set_active_cursor() -> Weight; fn force_onboard_mbms() -> Weight; fn clear_historic(n: u32, ) -> Weight; + fn reset_pallet_migration(n: u32, ) -> Weight; } /// Weights for `pallet_migrations` using the Substrate node and recommended hardware. @@ -74,10 +77,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) fn onboard_new_mbms() -> Weight { // Proof Size summary in bytes: - // Measured: `309` + // Measured: `0` // Estimated: `67035` - // Minimum execution time: 9_520_000 picoseconds. - Weight::from_parts(9_934_000, 67035) + // Minimum execution time: 4_422_000 picoseconds. + Weight::from_parts(4_560_000, 67035) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -85,10 +88,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn progress_mbms_none() -> Weight { // Proof Size summary in bytes: - // Measured: `142` + // Measured: `0` // Estimated: `67035` - // Minimum execution time: 2_993_000 picoseconds. - Weight::from_parts(3_088_000, 67035) + // Minimum execution time: 678_000 picoseconds. + Weight::from_parts(751_000, 67035) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -97,10 +100,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn exec_migration_completed() -> Weight { // Proof Size summary in bytes: - // Measured: `167` - // Estimated: `3632` - // Minimum execution time: 7_042_000 picoseconds. - Weight::from_parts(7_272_000, 3632) + // Measured: `0` + // Estimated: `3465` + // Minimum execution time: 3_791_000 picoseconds. + Weight::from_parts(3_930_000, 3465) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -110,10 +113,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_skipped_historic() -> Weight { // Proof Size summary in bytes: - // Measured: `363` - // Estimated: `3828` - // Minimum execution time: 16_522_000 picoseconds. - Weight::from_parts(17_082_000, 3828) + // Measured: `34` + // Estimated: `3731` + // Minimum execution time: 7_375_000 picoseconds. + Weight::from_parts(7_630_000, 3731) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -122,10 +125,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_advance() -> Weight { // Proof Size summary in bytes: - // Measured: `309` - // Estimated: `3774` - // Minimum execution time: 12_445_000 picoseconds. - Weight::from_parts(12_797_000, 3774) + // Measured: `0` + // Estimated: `3731` + // Minimum execution time: 6_771_000 picoseconds. + Weight::from_parts(6_894_000, 3731) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -134,10 +137,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_complete() -> Weight { // Proof Size summary in bytes: - // Measured: `309` - // Estimated: `3774` - // Minimum execution time: 14_057_000 picoseconds. - Weight::from_parts(14_254_000, 3774) + // Measured: `0` + // Estimated: `3731` + // Minimum execution time: 8_223_000 picoseconds. + Weight::from_parts(8_406_000, 3731) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -149,10 +152,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn exec_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `309` - // Estimated: `3774` - // Minimum execution time: 14_578_000 picoseconds. - Weight::from_parts(14_825_000, 3774) + // Measured: `0` + // Estimated: `3731` + // Minimum execution time: 8_907_000 picoseconds. + Weight::from_parts(9_168_000, 3731) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -160,8 +163,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 169_000 picoseconds. - Weight::from_parts(197_000, 0) + // Minimum execution time: 143_000 picoseconds. + Weight::from_parts(174_000, 0) } /// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) @@ -169,8 +172,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_634_000 picoseconds. - Weight::from_parts(2_798_000, 0) + // Minimum execution time: 2_172_000 picoseconds. + Weight::from_parts(2_259_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1) @@ -179,8 +182,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_069_000 picoseconds. - Weight::from_parts(3_293_000, 0) + // Minimum execution time: 2_600_000 picoseconds. + Weight::from_parts(2_728_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `MultiBlockMigrations::Cursor` (r:1 w:0) @@ -189,10 +192,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) fn force_onboard_mbms() -> Weight { // Proof Size summary in bytes: - // Measured: `284` + // Measured: `0` // Estimated: `67035` - // Minimum execution time: 7_674_000 picoseconds. - Weight::from_parts(8_000_000, 67035) + // Minimum execution time: 2_949_000 picoseconds. + Weight::from_parts(3_106_000, 67035) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `MultiBlockMigrations::Historic` (r:256 w:256) @@ -200,17 +203,32 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `n` is `[0, 256]`. fn clear_historic(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1122 + n * (271 ±0)` + // Measured: `960 + n * (271 ±0)` // Estimated: `3834 + n * (2740 ±0)` - // Minimum execution time: 16_937_000 picoseconds. - Weight::from_parts(15_713_121, 3834) - // Standard Error: 2_580 - .saturating_add(Weight::from_parts(1_424_239, 0).saturating_mul(n.into())) + // Minimum execution time: 15_122_000 picoseconds. + Weight::from_parts(27_397_644, 3834) + // Standard Error: 6_050 + .saturating_add(Weight::from_parts(1_454_904, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2740).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, 2048]`. + fn reset_pallet_migration(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1605 + n * (38 ±0)` + // Estimated: `686 + n * (39 ±0)` + // Minimum execution time: 1_128_000 picoseconds. + Weight::from_parts(1_180_000, 686) + // Standard Error: 2_597 + .saturating_add(Weight::from_parts(916_593, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 39).saturating_mul(n.into())) + } } // For backwards compatibility and tests. @@ -221,10 +239,10 @@ impl WeightInfo for () { /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) fn onboard_new_mbms() -> Weight { // Proof Size summary in bytes: - // Measured: `309` + // Measured: `0` // Estimated: `67035` - // Minimum execution time: 9_520_000 picoseconds. - Weight::from_parts(9_934_000, 67035) + // Minimum execution time: 4_422_000 picoseconds. + Weight::from_parts(4_560_000, 67035) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -232,10 +250,10 @@ impl WeightInfo for () { /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn progress_mbms_none() -> Weight { // Proof Size summary in bytes: - // Measured: `142` + // Measured: `0` // Estimated: `67035` - // Minimum execution time: 2_993_000 picoseconds. - Weight::from_parts(3_088_000, 67035) + // Minimum execution time: 678_000 picoseconds. + Weight::from_parts(751_000, 67035) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -244,10 +262,10 @@ impl WeightInfo for () { /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn exec_migration_completed() -> Weight { // Proof Size summary in bytes: - // Measured: `167` - // Estimated: `3632` - // Minimum execution time: 7_042_000 picoseconds. - Weight::from_parts(7_272_000, 3632) + // Measured: `0` + // Estimated: `3465` + // Minimum execution time: 3_791_000 picoseconds. + Weight::from_parts(3_930_000, 3465) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -257,10 +275,10 @@ impl WeightInfo for () { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_skipped_historic() -> Weight { // Proof Size summary in bytes: - // Measured: `363` - // Estimated: `3828` - // Minimum execution time: 16_522_000 picoseconds. - Weight::from_parts(17_082_000, 3828) + // Measured: `34` + // Estimated: `3731` + // Minimum execution time: 7_375_000 picoseconds. + Weight::from_parts(7_630_000, 3731) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -269,10 +287,10 @@ impl WeightInfo for () { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_advance() -> Weight { // Proof Size summary in bytes: - // Measured: `309` - // Estimated: `3774` - // Minimum execution time: 12_445_000 picoseconds. - Weight::from_parts(12_797_000, 3774) + // Measured: `0` + // Estimated: `3731` + // Minimum execution time: 6_771_000 picoseconds. + Weight::from_parts(6_894_000, 3731) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) @@ -281,10 +299,10 @@ impl WeightInfo for () { /// Proof: `MultiBlockMigrations::Historic` (`max_values`: None, `max_size`: Some(266), added: 2741, mode: `MaxEncodedLen`) fn exec_migration_complete() -> Weight { // Proof Size summary in bytes: - // Measured: `309` - // Estimated: `3774` - // Minimum execution time: 14_057_000 picoseconds. - Weight::from_parts(14_254_000, 3774) + // Measured: `0` + // Estimated: `3731` + // Minimum execution time: 8_223_000 picoseconds. + Weight::from_parts(8_406_000, 3731) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -296,10 +314,10 @@ impl WeightInfo for () { /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) fn exec_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `309` - // Estimated: `3774` - // Minimum execution time: 14_578_000 picoseconds. - Weight::from_parts(14_825_000, 3774) + // Measured: `0` + // Estimated: `3731` + // Minimum execution time: 8_907_000 picoseconds. + Weight::from_parts(9_168_000, 3731) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -307,8 +325,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 169_000 picoseconds. - Weight::from_parts(197_000, 0) + // Minimum execution time: 143_000 picoseconds. + Weight::from_parts(174_000, 0) } /// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1) /// Proof: `MultiBlockMigrations::Cursor` (`max_values`: Some(1), `max_size`: Some(65550), added: 66045, mode: `MaxEncodedLen`) @@ -316,8 +334,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_634_000 picoseconds. - Weight::from_parts(2_798_000, 0) + // Minimum execution time: 2_172_000 picoseconds. + Weight::from_parts(2_259_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `MultiBlockMigrations::Cursor` (r:0 w:1) @@ -326,8 +344,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_069_000 picoseconds. - Weight::from_parts(3_293_000, 0) + // Minimum execution time: 2_600_000 picoseconds. + Weight::from_parts(2_728_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `MultiBlockMigrations::Cursor` (r:1 w:0) @@ -336,10 +354,10 @@ impl WeightInfo for () { /// Proof: UNKNOWN KEY `0x583359fe0e84d953a9dd84e8addb08a5` (r:1 w:0) fn force_onboard_mbms() -> Weight { // Proof Size summary in bytes: - // Measured: `284` + // Measured: `0` // Estimated: `67035` - // Minimum execution time: 7_674_000 picoseconds. - Weight::from_parts(8_000_000, 67035) + // Minimum execution time: 2_949_000 picoseconds. + Weight::from_parts(3_106_000, 67035) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `MultiBlockMigrations::Historic` (r:256 w:256) @@ -347,15 +365,30 @@ impl WeightInfo for () { /// The range of component `n` is `[0, 256]`. fn clear_historic(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1122 + n * (271 ±0)` + // Measured: `960 + n * (271 ±0)` // Estimated: `3834 + n * (2740 ±0)` - // Minimum execution time: 16_937_000 picoseconds. - Weight::from_parts(15_713_121, 3834) - // Standard Error: 2_580 - .saturating_add(Weight::from_parts(1_424_239, 0).saturating_mul(n.into())) + // Minimum execution time: 15_122_000 picoseconds. + Weight::from_parts(27_397_644, 3834) + // Standard Error: 6_050 + .saturating_add(Weight::from_parts(1_454_904, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2740).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, 2048]`. + fn reset_pallet_migration(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1605 + n * (38 ±0)` + // Estimated: `686 + n * (39 ±0)` + // Minimum execution time: 1_128_000 picoseconds. + Weight::from_parts(1_180_000, 686) + // Standard Error: 2_597 + .saturating_add(Weight::from_parts(916_593, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 39).saturating_mul(n.into())) + } } diff --git a/substrate/frame/mixnet/Cargo.toml b/substrate/frame/mixnet/Cargo.toml index bb5e8486456658bf27835d56d5663e5cce0855a9..33bf7146980d5e32a46093483494b73279f012e4 100644 --- a/substrate/frame/mixnet/Cargo.toml +++ b/substrate/frame/mixnet/Cargo.toml @@ -17,42 +17,24 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive", "max-encoded-len"], workspace = true } -frame-benchmarking = { optional = true, workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { features = ["derive"], workspace = true } sp-application-crypto = { workspace = true } -sp-arithmetic = { workspace = true } -sp-io = { workspace = true } sp-mixnet = { workspace = true } -sp-runtime = { workspace = true } [features] default = ["std"] std = [ "codec/std", - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", + "frame/std", "log/std", "scale-info/std", "serde/std", "sp-application-crypto/std", - "sp-arithmetic/std", - "sp-io/std", "sp-mixnet/std", - "sp-runtime/std", -] -runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "sp-runtime/try-runtime", + "frame/try-runtime", ] diff --git a/substrate/frame/mixnet/src/lib.rs b/substrate/frame/mixnet/src/lib.rs index 6579ed678ae7a6a10200c80284a749acdb4bfbae..9849818176760cf9dfadb0a72d94b8c0206cc758 100644 --- a/substrate/frame/mixnet/src/lib.rs +++ b/substrate/frame/mixnet/src/lib.rs @@ -23,28 +23,23 @@ extern crate alloc; +pub use pallet::*; + use alloc::vec::Vec; -use codec::{Decode, Encode, MaxEncodedLen}; use core::cmp::Ordering; -use frame_support::{ - traits::{EstimateNextSessionRotation, Get, OneSessionHandler}, - BoundedVec, +use frame::{ + deps::{ + sp_io::{self, MultiRemovalResults}, + sp_runtime, + }, + prelude::*, }; -use frame_system::{ - offchain::{CreateInherent, SubmitTransaction}, - pallet_prelude::BlockNumberFor, -}; -pub use pallet::*; -use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_application_crypto::RuntimeAppPublic; -use sp_arithmetic::traits::{CheckedSub, Saturating, UniqueSaturatedInto, Zero}; -use sp_io::MultiRemovalResults; use sp_mixnet::types::{ AuthorityId, AuthoritySignature, KxPublic, Mixnode, MixnodesErr, PeerId, SessionIndex, SessionPhase, SessionStatus, KX_PUBLIC_SIZE, }; -use sp_runtime::RuntimeDebug; const LOG_TARGET: &str = "runtime::mixnet"; @@ -168,12 +163,9 @@ fn twox<BlockNumber: UniqueSaturatedInto<u64>>( // The pallet //////////////////////////////////////////////////////////////////////////////// -#[frame_support::pallet(dev_mode)] +#[frame::pallet(dev_mode)] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; - #[pallet::pallet] pub struct Pallet<T>(_); @@ -254,7 +246,7 @@ pub mod pallet { StorageDoubleMap<_, Identity, SessionIndex, Identity, AuthorityIndex, BoundedMixnodeFor<T>>; #[pallet::genesis_config] - #[derive(frame_support::DefaultNoBound)] + #[derive(DefaultNoBound)] pub struct GenesisConfig<T: Config> { /// The mixnode set for the very first session. pub mixnodes: BoundedVec<BoundedMixnodeFor<T>, T::MaxAuthorities>, @@ -308,7 +300,7 @@ pub mod pallet { fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { let Self::Call::register { registration, signature } = call else { - return InvalidTransaction::Call.into() + return InvalidTransaction::Call.into(); }; // Check session index matches @@ -320,16 +312,16 @@ pub mod pallet { // Check authority index is valid if registration.authority_index >= T::MaxAuthorities::get() { - return InvalidTransaction::BadProof.into() + return InvalidTransaction::BadProof.into(); } let Some(authority_id) = NextAuthorityIds::<T>::get(registration.authority_index) else { - return InvalidTransaction::BadProof.into() + return InvalidTransaction::BadProof.into(); }; // Check the authority hasn't registered a mixnode yet if Self::already_registered(registration.session_index, registration.authority_index) { - return InvalidTransaction::Stale.into() + return InvalidTransaction::Stale.into(); } // Check signature. Note that we don't use regular signed transactions for registration @@ -339,7 +331,7 @@ pub mod pallet { authority_id.verify(&encoded_registration, signature) }); if !signature_ok { - return InvalidTransaction::BadProof.into() + return InvalidTransaction::BadProof.into(); } ValidTransaction::with_tag_prefix("MixnetRegistration") @@ -368,12 +360,12 @@ impl<T: Config> Pallet<T> { .saturating_sub(CurrentSessionStartBlock::<T>::get()); let Some(block_in_phase) = block_in_phase.checked_sub(&T::NumCoverToCurrentBlocks::get()) else { - return SessionPhase::CoverToCurrent + return SessionPhase::CoverToCurrent; }; let Some(block_in_phase) = block_in_phase.checked_sub(&T::NumRequestsToCurrentBlocks::get()) else { - return SessionPhase::RequestsToCurrent + return SessionPhase::RequestsToCurrent; }; if block_in_phase < T::NumCoverToPrevBlocks::get() { SessionPhase::CoverToPrev @@ -411,7 +403,7 @@ impl<T: Config> Pallet<T> { return Err(MixnodesErr::InsufficientRegistrations { num: 0, min: T::MinMixnodes::get(), - }) + }); }; Self::mixnodes(prev_session_index) } @@ -430,7 +422,7 @@ impl<T: Config> Pallet<T> { // registering let block_in_session = block_number.saturating_sub(CurrentSessionStartBlock::<T>::get()); if block_in_session < T::NumRegisterStartSlackBlocks::get() { - return false + return false; } let (Some(end_block), _weight) = @@ -438,7 +430,7 @@ impl<T: Config> Pallet<T> { else { // Things aren't going to work terribly well in this case as all the authorities will // just pile in after the slack period... - return true + return true; }; let remaining_blocks = end_block @@ -447,7 +439,7 @@ impl<T: Config> Pallet<T> { if remaining_blocks.is_zero() { // Into the slack time at the end of the session. Not necessarily too late; // registrations are accepted right up until the session ends. - return true + return true; } // Want uniform distribution over the remaining blocks, so pick this block with probability @@ -496,7 +488,7 @@ impl<T: Config> Pallet<T> { "Session {session_index} registration attempted, \ but current session is {current_session_index}", ); - return false + return false; } let block_number = frame_system::Pallet::<T>::block_number(); @@ -505,7 +497,7 @@ impl<T: Config> Pallet<T> { target: LOG_TARGET, "Waiting for the session to progress further before registering", ); - return false + return false; } let Some((authority_index, authority_id)) = Self::next_local_authority() else { @@ -513,7 +505,7 @@ impl<T: Config> Pallet<T> { target: LOG_TARGET, "Not an authority in the next session; cannot register a mixnode", ); - return false + return false; }; if Self::already_registered(session_index, authority_index) { @@ -521,14 +513,14 @@ impl<T: Config> Pallet<T> { target: LOG_TARGET, "Already registered a mixnode for the next session", ); - return false + return false; } let registration = Registration { block_number, session_index, authority_index, mixnode: mixnode.into() }; let Some(signature) = authority_id.sign(®istration.encode()) else { log::debug!(target: LOG_TARGET, "Failed to sign registration"); - return false + return false; }; let call = Call::register { registration, signature }; let xt = T::create_inherent(call.into()); diff --git a/substrate/frame/multisig/Cargo.toml b/substrate/frame/multisig/Cargo.toml index c96be908faef08ae6d7a6e0dfb892ccfeba45614..e18e14f2626bfcae1ed509005e1108913e42568e 100644 --- a/substrate/frame/multisig/Cargo.toml +++ b/substrate/frame/multisig/Cargo.toml @@ -17,8 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } +frame = { workspace = true, features = ["runtime"] } scale-info = { features = ["derive"], workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } # third party log = { workspace = true } diff --git a/substrate/frame/multisig/src/benchmarking.rs b/substrate/frame/multisig/src/benchmarking.rs index ccaa1ceab66e54cd68774bb7ea244563c3f6fe59..3f75d92fe0ed378d2e76e87dbfba4c6a21048727 100644 --- a/substrate/frame/multisig/src/benchmarking.rs +++ b/substrate/frame/multisig/src/benchmarking.rs @@ -194,14 +194,14 @@ mod benchmarks { Ok(()) } - /// `z`: Transaction Length, not a component /// `s`: Signatories, need at least 2 people #[benchmark] fn approve_as_multi_create( s: Linear<2, { T::MaxSignatories::get() }>, - z: Linear<0, 10_000>, ) -> Result<(), BenchmarkError> { - let (mut signatories, call) = setup_multi::<T>(s, z)?; + // The call is neither in storage or an argument, so just use any: + let call_len = 10_000; + let (mut signatories, call) = setup_multi::<T>(s, call_len)?; let multi_account_id = Multisig::<T>::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; let call_hash = call.using_encoded(blake2_256); @@ -225,14 +225,14 @@ mod benchmarks { Ok(()) } - /// `z`: Transaction Length, not a component /// `s`: Signatories, need at least 2 people #[benchmark] fn approve_as_multi_approve( s: Linear<2, { T::MaxSignatories::get() }>, - z: Linear<0, 10_000>, ) -> Result<(), BenchmarkError> { - let (mut signatories, call) = setup_multi::<T>(s, z)?; + // The call is neither in storage or an argument, so just use any: + let call_len = 10_000; + let (mut signatories, call) = setup_multi::<T>(s, call_len)?; let mut signatories2 = signatories.clone(); let multi_account_id = Multisig::<T>::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; @@ -270,14 +270,12 @@ mod benchmarks { Ok(()) } - /// `z`: Transaction Length, not a component /// `s`: Signatories, need at least 2 people #[benchmark] - fn cancel_as_multi( - s: Linear<2, { T::MaxSignatories::get() }>, - z: Linear<0, 10_000>, - ) -> Result<(), BenchmarkError> { - let (mut signatories, call) = setup_multi::<T>(s, z)?; + fn cancel_as_multi(s: Linear<2, { T::MaxSignatories::get() }>) -> Result<(), BenchmarkError> { + // The call is neither in storage or an argument, so just use any: + let call_len = 10_000; + let (mut signatories, call) = setup_multi::<T>(s, call_len)?; let multi_account_id = Multisig::<T>::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; let call_hash = call.using_encoded(blake2_256); diff --git a/substrate/frame/multisig/src/tests.rs b/substrate/frame/multisig/src/tests.rs index 4065ce73f90556d00e26c4a2e0fc493f847f6dc8..8a389314256bec1aca8c3cbe00fe6b005d19c554 100644 --- a/substrate/frame/multisig/src/tests.rs +++ b/substrate/frame/multisig/src/tests.rs @@ -75,6 +75,7 @@ pub fn new_test_ext() -> TestState { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); pallet_balances::GenesisConfig::<Test> { balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 2)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/multisig/src/weights.rs b/substrate/frame/multisig/src/weights.rs index 5c14922e0ef00d2d0e05a1b13af7bb469d63f728..0f8167a07a1c8229016cd2ba5f087d6f8a7d5684 100644 --- a/substrate/frame/multisig/src/weights.rs +++ b/substrate/frame/multisig/src/weights.rs @@ -18,36 +18,39 @@ //! Autogenerated weights for `pallet_multisig` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` +//! HOSTNAME: `fff8f38555b9`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate-node +// frame-omni-bencher +// v1 // benchmark // pallet -// --chain=dev +// --extrinsic=* +// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/substrate/HEADER-APACHE2 +// --output=/__w/polkadot-sdk/polkadot-sdk/substrate/frame/multisig/src/weights.rs +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --pallet=pallet_multisig +// --heap-pages=4096 +// --template=substrate/.maintain/frame-umbrella-weight-template.hbs // --no-storage-info -// --no-median-slopes // --no-min-squares -// --extrinsic=* -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./substrate/frame/multisig/src/weights.rs -// --header=./substrate/HEADER-APACHE2 -// --template=./substrate/.maintain/frame-weight-template.hbs +// --no-median-slopes +// --genesis-builder-policy=none +// --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic,pallet_nomination_pools,pallet_remark,pallet_transaction_storage #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] #![allow(missing_docs)] -// TODO update this in frame-weight-template.hbs use frame::weights_prelude::*; + /// Weight functions needed for `pallet_multisig`. pub trait WeightInfo { fn as_multi_threshold_1(z: u32, ) -> Weight; @@ -69,12 +72,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `z` is `[0, 10000]`. fn as_multi_threshold_1(z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `145` + // Measured: `0` // Estimated: `3997` - // Minimum execution time: 20_302_000 picoseconds. - Weight::from_parts(21_362_808, 3997) - // Standard Error: 4 - .saturating_add(Weight::from_parts(432, 0).saturating_mul(z.into())) + // Minimum execution time: 18_665_000 picoseconds. + Weight::from_parts(19_157_181, 3997) + // Standard Error: 6 + .saturating_add(Weight::from_parts(590, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) @@ -83,14 +86,14 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `301 + s * (2 ±0)` + // Measured: `229 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 41_140_000 picoseconds. - Weight::from_parts(31_518_927, 6811) - // Standard Error: 754 - .saturating_add(Weight::from_parts(115_804, 0).saturating_mul(s.into())) - // Standard Error: 7 - .saturating_add(Weight::from_parts(1_442, 0).saturating_mul(z.into())) + // Minimum execution time: 42_388_000 picoseconds. + Weight::from_parts(29_499_967, 6811) + // Standard Error: 1_563 + .saturating_add(Weight::from_parts(145_538, 0).saturating_mul(s.into())) + // Standard Error: 15 + .saturating_add(Weight::from_parts(2_016, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -100,14 +103,14 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `320` + // Measured: `185` // Estimated: `6811` - // Minimum execution time: 27_375_000 picoseconds. - Weight::from_parts(17_806_361, 6811) - // Standard Error: 501 - .saturating_add(Weight::from_parts(107_042, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_491, 0).saturating_mul(z.into())) + // Minimum execution time: 27_231_000 picoseconds. + Weight::from_parts(16_755_689, 6811) + // Standard Error: 866 + .saturating_add(Weight::from_parts(119_094, 0).saturating_mul(s.into())) + // Standard Error: 8 + .saturating_add(Weight::from_parts(1_927, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -123,14 +126,14 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `571 + s * (33 ±0)` + // Measured: `288 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 54_427_000 picoseconds. - Weight::from_parts(43_677_970, 6811) - // Standard Error: 1_342 - .saturating_add(Weight::from_parts(154_697, 0).saturating_mul(s.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(1_534, 0).saturating_mul(z.into())) + // Minimum execution time: 50_448_000 picoseconds. + Weight::from_parts(34_504_261, 6811) + // Standard Error: 2_070 + .saturating_add(Weight::from_parts(189_586, 0).saturating_mul(s.into())) + // Standard Error: 20 + .saturating_add(Weight::from_parts(2_116, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -139,12 +142,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `s` is `[2, 100]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `301 + s * (2 ±0)` + // Measured: `233 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 29_102_000 picoseconds. - Weight::from_parts(30_317_105, 6811) - // Standard Error: 903 - .saturating_add(Weight::from_parts(109_792, 0).saturating_mul(s.into())) + // Minimum execution time: 26_020_000 picoseconds. + Weight::from_parts(28_229_601, 6811) + // Standard Error: 1_282 + .saturating_add(Weight::from_parts(133_221, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -153,12 +156,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `s` is `[2, 100]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `320` + // Measured: `185` // Estimated: `6811` - // Minimum execution time: 16_300_000 picoseconds. - Weight::from_parts(17_358_877, 6811) - // Standard Error: 522 - .saturating_add(Weight::from_parts(99_194, 0).saturating_mul(s.into())) + // Minimum execution time: 13_660_000 picoseconds. + Weight::from_parts(14_317_629, 6811) + // Standard Error: 1_188 + .saturating_add(Weight::from_parts(125_599, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -167,12 +170,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `492 + s * (1 ±0)` + // Measured: `357 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 30_147_000 picoseconds. - Weight::from_parts(32_003_421, 6811) - // Standard Error: 1_077 - .saturating_add(Weight::from_parts(108_567, 0).saturating_mul(s.into())) + // Minimum execution time: 27_827_000 picoseconds. + Weight::from_parts(28_980_511, 6811) + // Standard Error: 822 + .saturating_add(Weight::from_parts(130_315, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -187,12 +190,12 @@ impl WeightInfo for () { /// The range of component `z` is `[0, 10000]`. fn as_multi_threshold_1(z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `145` + // Measured: `0` // Estimated: `3997` - // Minimum execution time: 20_302_000 picoseconds. - Weight::from_parts(21_362_808, 3997) - // Standard Error: 4 - .saturating_add(Weight::from_parts(432, 0).saturating_mul(z.into())) + // Minimum execution time: 18_665_000 picoseconds. + Weight::from_parts(19_157_181, 3997) + // Standard Error: 6 + .saturating_add(Weight::from_parts(590, 0).saturating_mul(z.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) @@ -201,14 +204,14 @@ impl WeightInfo for () { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `301 + s * (2 ±0)` + // Measured: `229 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 41_140_000 picoseconds. - Weight::from_parts(31_518_927, 6811) - // Standard Error: 754 - .saturating_add(Weight::from_parts(115_804, 0).saturating_mul(s.into())) - // Standard Error: 7 - .saturating_add(Weight::from_parts(1_442, 0).saturating_mul(z.into())) + // Minimum execution time: 42_388_000 picoseconds. + Weight::from_parts(29_499_967, 6811) + // Standard Error: 1_563 + .saturating_add(Weight::from_parts(145_538, 0).saturating_mul(s.into())) + // Standard Error: 15 + .saturating_add(Weight::from_parts(2_016, 0).saturating_mul(z.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -218,14 +221,14 @@ impl WeightInfo for () { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `320` + // Measured: `185` // Estimated: `6811` - // Minimum execution time: 27_375_000 picoseconds. - Weight::from_parts(17_806_361, 6811) - // Standard Error: 501 - .saturating_add(Weight::from_parts(107_042, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_491, 0).saturating_mul(z.into())) + // Minimum execution time: 27_231_000 picoseconds. + Weight::from_parts(16_755_689, 6811) + // Standard Error: 866 + .saturating_add(Weight::from_parts(119_094, 0).saturating_mul(s.into())) + // Standard Error: 8 + .saturating_add(Weight::from_parts(1_927, 0).saturating_mul(z.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -241,14 +244,14 @@ impl WeightInfo for () { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `571 + s * (33 ±0)` + // Measured: `288 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 54_427_000 picoseconds. - Weight::from_parts(43_677_970, 6811) - // Standard Error: 1_342 - .saturating_add(Weight::from_parts(154_697, 0).saturating_mul(s.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(1_534, 0).saturating_mul(z.into())) + // Minimum execution time: 50_448_000 picoseconds. + Weight::from_parts(34_504_261, 6811) + // Standard Error: 2_070 + .saturating_add(Weight::from_parts(189_586, 0).saturating_mul(s.into())) + // Standard Error: 20 + .saturating_add(Weight::from_parts(2_116, 0).saturating_mul(z.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -257,12 +260,12 @@ impl WeightInfo for () { /// The range of component `s` is `[2, 100]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `301 + s * (2 ±0)` + // Measured: `233 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 29_102_000 picoseconds. - Weight::from_parts(30_317_105, 6811) - // Standard Error: 903 - .saturating_add(Weight::from_parts(109_792, 0).saturating_mul(s.into())) + // Minimum execution time: 26_020_000 picoseconds. + Weight::from_parts(28_229_601, 6811) + // Standard Error: 1_282 + .saturating_add(Weight::from_parts(133_221, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -271,12 +274,12 @@ impl WeightInfo for () { /// The range of component `s` is `[2, 100]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `320` + // Measured: `185` // Estimated: `6811` - // Minimum execution time: 16_300_000 picoseconds. - Weight::from_parts(17_358_877, 6811) - // Standard Error: 522 - .saturating_add(Weight::from_parts(99_194, 0).saturating_mul(s.into())) + // Minimum execution time: 13_660_000 picoseconds. + Weight::from_parts(14_317_629, 6811) + // Standard Error: 1_188 + .saturating_add(Weight::from_parts(125_599, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -285,13 +288,13 @@ impl WeightInfo for () { /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `492 + s * (1 ±0)` + // Measured: `357 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 30_147_000 picoseconds. - Weight::from_parts(32_003_421, 6811) - // Standard Error: 1_077 - .saturating_add(Weight::from_parts(108_567, 0).saturating_mul(s.into())) + // Minimum execution time: 27_827_000 picoseconds. + Weight::from_parts(28_980_511, 6811) + // Standard Error: 822 + .saturating_add(Weight::from_parts(130_315, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } -} \ No newline at end of file +} diff --git a/substrate/frame/nft-fractionalization/Cargo.toml b/substrate/frame/nft-fractionalization/Cargo.toml index 6a064204b895a5553af93444e0b788f3309e8264..23537b2278933db7cbacaa9f490848738a5d0224 100644 --- a/substrate/frame/nft-fractionalization/Cargo.toml +++ b/substrate/frame/nft-fractionalization/Cargo.toml @@ -17,20 +17,19 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-assets = { workspace = true } pallet-nfts = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/nfts/Cargo.toml b/substrate/frame/nfts/Cargo.toml index a97b49e56524568e1ae50b67bd8c9e8f2effe63d..18895018e1c5cd5cf90e22047e68a25294769e3e 100644 --- a/substrate/frame/nfts/Cargo.toml +++ b/substrate/frame/nfts/Cargo.toml @@ -18,11 +18,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } enumflags2 = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/nfts/runtime-api/Cargo.toml b/substrate/frame/nfts/runtime-api/Cargo.toml index 4d004875468db1222289f2aab268c8ce58813e52..36f85fbf61128a8fcea6cea9374c3cf1d7e56883 100644 --- a/substrate/frame/nfts/runtime-api/Cargo.toml +++ b/substrate/frame/nfts/runtime-api/Cargo.toml @@ -17,9 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -pallet-nfts = { workspace = true } sp-api = { workspace = true } [features] default = ["std"] -std = ["codec/std", "pallet-nfts/std", "sp-api/std"] +std = ["codec/std", "sp-api/std"] diff --git a/substrate/frame/nis/Cargo.toml b/substrate/frame/nis/Cargo.toml index 78e086d0ed12a0c2fb951fe3684c17dd9e446668..ec1a5d93bcbaaaac93669eed46ffe211c751ff4b 100644 --- a/substrate/frame/nis/Cargo.toml +++ b/substrate/frame/nis/Cargo.toml @@ -17,10 +17,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/nis/src/mock.rs b/substrate/frame/nis/src/mock.rs index 2b008f8ec2a41e3580e61d4bd1c28ba044368273..82b9f55b919bea0160a0c98f605a37878b304136 100644 --- a/substrate/frame/nis/src/mock.rs +++ b/substrate/frame/nis/src/mock.rs @@ -21,7 +21,7 @@ use crate::{self as pallet_nis, Perquintill, WithMaximumOf}; use frame_support::{ derive_impl, ord_parameter_types, parameter_types, - traits::{fungible::Inspect, ConstU32, ConstU64, OnFinalize, OnInitialize, StorageMapShim}, + traits::{fungible::Inspect, ConstU32, ConstU64, StorageMapShim}, weights::Weight, PalletId, }; @@ -133,6 +133,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); pallet_balances::GenesisConfig::<Test, Instance1> { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -145,15 +146,3 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pub fn new_test_ext_empty() -> sp_io::TestExternalities { frame_system::GenesisConfig::<Test>::default().build_storage().unwrap().into() } - -pub fn run_to_block(n: u64) { - while System::block_number() < n { - Nis::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); - Nis::on_initialize(System::block_number()); - } -} diff --git a/substrate/frame/nis/src/tests.rs b/substrate/frame/nis/src/tests.rs index a17aaf421827f3aba79fa36065178922b0af6d93..10c39a0d48edb4f7c586d55d54e4ada7495aede4 100644 --- a/substrate/frame/nis/src/tests.rs +++ b/substrate/frame/nis/src/tests.rs @@ -55,7 +55,7 @@ fn enlarge(amount: Balance, max_bids: u32) { #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); for q in 0..3 { assert!(Queues::<Test>::get(q).is_empty()); @@ -76,7 +76,7 @@ fn basic_setup_works() { #[test] fn place_bid_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_noop!(Nis::place_bid(signed(1), 1, 2), Error::<Test>::AmountTooSmall); assert_noop!(Nis::place_bid(signed(1), 101, 2), FundsUnavailable); assert_noop!(Nis::place_bid(signed(1), 10, 4), Error::<Test>::DurationTooBig); @@ -90,7 +90,7 @@ fn place_bid_works() { #[test] fn place_bid_queuing_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Nis::place_bid(signed(1), 20, 2)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); assert_ok!(Nis::place_bid(signed(1), 5, 2)); @@ -116,7 +116,7 @@ fn place_bid_queuing_works() { #[test] fn place_bid_fails_when_queue_full() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Nis::place_bid(signed(1), 10, 2)); assert_ok!(Nis::place_bid(signed(2), 10, 2)); assert_ok!(Nis::place_bid(signed(3), 10, 2)); @@ -128,7 +128,7 @@ fn place_bid_fails_when_queue_full() { #[test] fn multiple_place_bids_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Nis::place_bid(signed(1), 10, 1)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); @@ -154,7 +154,7 @@ fn multiple_place_bids_works() { #[test] fn retract_single_item_queue_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Nis::place_bid(signed(1), 10, 1)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); assert_ok!(Nis::retract_bid(signed(1), 10, 1)); @@ -169,7 +169,7 @@ fn retract_single_item_queue_works() { #[test] fn retract_with_other_and_duplicate_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Nis::place_bid(signed(1), 10, 1)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); @@ -190,7 +190,7 @@ fn retract_with_other_and_duplicate_works() { #[test] fn retract_non_existent_item_fails() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_noop!(Nis::retract_bid(signed(1), 10, 1), Error::<Test>::UnknownBid); assert_ok!(Nis::place_bid(signed(1), 10, 1)); assert_noop!(Nis::retract_bid(signed(1), 20, 1), Error::<Test>::UnknownBid); @@ -202,7 +202,7 @@ fn retract_non_existent_item_fails() { #[test] fn basic_enlarge_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_ok!(Nis::place_bid(signed(2), 40, 2)); enlarge(40, 2); @@ -240,7 +240,7 @@ fn basic_enlarge_works() { #[test] fn enlarge_respects_bids_limit() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_ok!(Nis::place_bid(signed(2), 40, 2)); assert_ok!(Nis::place_bid(signed(3), 40, 2)); @@ -285,7 +285,7 @@ fn enlarge_respects_bids_limit() { #[test] fn enlarge_respects_amount_limit_and_will_split() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Nis::place_bid(signed(1), 80, 1)); enlarge(40, 2); @@ -317,7 +317,7 @@ fn enlarge_respects_amount_limit_and_will_split() { #[test] fn basic_thaw_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_eq!(Nis::issuance().effective, 400); assert_eq!(Balances::free_balance(1), 60); @@ -330,9 +330,9 @@ fn basic_thaw_works() { assert_eq!(Balances::reserved_balance(1), 40); assert_eq!(holdings(), 40); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert_noop!(Nis::thaw_private(signed(1), 0, None), Error::<Test>::NotExpired); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_noop!(Nis::thaw_private(signed(1), 1, None), Error::<Test>::UnknownReceipt); assert_noop!(Nis::thaw_private(signed(2), 0, None), Error::<Test>::NotOwner); @@ -359,12 +359,12 @@ fn basic_thaw_works() { #[test] fn partial_thaw_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Nis::place_bid(signed(1), 80, 1)); enlarge(80, 1); assert_eq!(holdings(), 80); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); let prop = Perquintill::from_rational(4_100_000, 21_000_000u64); assert_noop!(Nis::thaw_private(signed(1), 0, Some(prop)), Error::<Test>::MakesDust); let prop = Perquintill::from_rational(1_050_000, 21_000_000u64); @@ -402,10 +402,10 @@ fn partial_thaw_works() { #[test] fn thaw_respects_transfers() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); enlarge(40, 1); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_eq!(Nis::owner(&0), Some(1)); assert_eq!(Balances::reserved_balance(&1), 40); @@ -428,10 +428,10 @@ fn thaw_respects_transfers() { #[test] fn communify_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); enlarge(40, 1); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_eq!(Nis::owner(&0), Some(1)); assert_eq!(Balances::reserved_balance(&1), 40); @@ -479,10 +479,10 @@ fn communify_works() { #[test] fn privatize_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); enlarge(40, 1); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_noop!(Nis::privatize(signed(2), 0), Error::<Test>::AlreadyPrivate); assert_ok!(Nis::communify(signed(1), 0)); @@ -503,11 +503,11 @@ fn privatize_works() { #[test] fn privatize_and_thaw_with_another_receipt_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_ok!(Nis::place_bid(signed(2), 40, 1)); enlarge(80, 2); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_ok!(Nis::communify(signed(1), 0)); assert_ok!(Nis::communify(signed(2), 1)); @@ -535,7 +535,7 @@ fn privatize_and_thaw_with_another_receipt_works() { #[test] fn communal_thaw_when_issuance_higher_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 100, 1)); enlarge(100, 1); @@ -552,7 +552,7 @@ fn communal_thaw_when_issuance_higher_works() { assert_ok!(Balances::mint_into(&3, 50)); assert_ok!(Balances::mint_into(&4, 50)); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); // Unfunded initially... assert_noop!(Nis::thaw_communal(signed(1), 0), Error::<Test>::Unfunded); @@ -581,7 +581,7 @@ fn communal_thaw_when_issuance_higher_works() { #[test] fn private_thaw_when_issuance_higher_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 100, 1)); enlarge(100, 1); @@ -591,7 +591,7 @@ fn private_thaw_when_issuance_higher_works() { assert_ok!(Balances::mint_into(&3, 50)); assert_ok!(Balances::mint_into(&4, 50)); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); // Unfunded initially... assert_noop!(Nis::thaw_private(signed(1), 0, None), Error::<Test>::Unfunded); @@ -609,7 +609,7 @@ fn private_thaw_when_issuance_higher_works() { #[test] fn thaw_with_ignored_issuance_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); // Give account zero some balance. assert_ok!(Balances::mint_into(&0, 200)); @@ -622,7 +622,7 @@ fn thaw_with_ignored_issuance_works() { assert_ok!(Balances::transfer_allow_death(signed(0), 3, 50)); assert_ok!(Balances::transfer_allow_death(signed(0), 4, 50)); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); // Unfunded initially... assert_noop!(Nis::thaw_private(signed(1), 0, None), Error::<Test>::Unfunded); // ...so we fund... @@ -640,7 +640,7 @@ fn thaw_with_ignored_issuance_works() { #[test] fn thaw_when_issuance_lower_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 100, 1)); enlarge(100, 1); @@ -650,7 +650,7 @@ fn thaw_when_issuance_lower_works() { assert_ok!(Balances::burn_from(&3, 25, Expendable, Exact, Force)); assert_ok!(Balances::burn_from(&4, 25, Expendable, Exact, Force)); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_ok!(Nis::thaw_private(signed(1), 0, None)); assert_ok!(Balances::transfer_allow_death(signed(1), 2, 1)); @@ -662,7 +662,7 @@ fn thaw_when_issuance_lower_works() { #[test] fn multiple_thaws_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Balances::transfer_allow_death(signed(3), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_ok!(Nis::place_bid(signed(1), 60, 1)); @@ -675,11 +675,11 @@ fn multiple_thaws_works() { assert_ok!(Balances::mint_into(&4, 100)); assert_ok!(Nis::fund_deficit(signed(1))); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_ok!(Nis::thaw_private(signed(1), 0, None)); assert_ok!(Nis::thaw_private(signed(1), 1, None)); assert_noop!(Nis::thaw_private(signed(2), 2, None), Error::<Test>::Throttled); - run_to_block(5); + System::run_to_block::<AllPalletsWithSystem>(5); assert_ok!(Nis::thaw_private(signed(2), 2, None)); assert_ok!(Balances::transfer_allow_death(signed(1), 3, 1)); @@ -693,7 +693,7 @@ fn multiple_thaws_works() { #[test] fn multiple_thaws_works_in_alternative_thaw_order() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::<AllPalletsWithSystem>(1); assert_ok!(Balances::transfer_allow_death(signed(3), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_ok!(Nis::place_bid(signed(1), 60, 1)); @@ -706,12 +706,12 @@ fn multiple_thaws_works_in_alternative_thaw_order() { assert_ok!(Balances::mint_into(&4, 100)); assert_ok!(Nis::fund_deficit(signed(1))); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_ok!(Nis::thaw_private(signed(2), 2, None)); assert_noop!(Nis::thaw_private(signed(1), 1, None), Error::<Test>::Throttled); assert_ok!(Nis::thaw_private(signed(1), 0, None)); - run_to_block(5); + System::run_to_block::<AllPalletsWithSystem>(5); assert_ok!(Nis::thaw_private(signed(1), 1, None)); assert_ok!(Balances::transfer_allow_death(signed(1), 3, 1)); @@ -725,7 +725,7 @@ fn multiple_thaws_works_in_alternative_thaw_order() { #[test] fn enlargement_to_target_works() { new_test_ext().execute_with(|| { - run_to_block(2); + System::run_to_block::<AllPalletsWithSystem>(2); let w = <() as WeightInfo>::process_queues() + <() as WeightInfo>::process_queue() + (<() as WeightInfo>::process_bid() * 2); @@ -737,7 +737,7 @@ fn enlargement_to_target_works() { assert_ok!(Nis::place_bid(signed(3), 40, 3)); Target::set(Perquintill::from_percent(40)); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert_eq!(Queues::<Test>::get(1), vec![Bid { amount: 40, who: 1 },]); assert_eq!( Queues::<Test>::get(2), @@ -749,7 +749,7 @@ fn enlargement_to_target_works() { ); assert_eq!(QueueTotals::<Test>::get(), vec![(1, 40), (2, 80), (2, 80)]); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); // Two new items should have been issued to 2 & 3 for 40 each & duration of 3. assert_eq!( Receipts::<Test>::get(0).unwrap(), @@ -778,7 +778,7 @@ fn enlargement_to_target_works() { } ); - run_to_block(5); + System::run_to_block::<AllPalletsWithSystem>(5); // No change assert_eq!( Summary::<Test>::get(), @@ -791,7 +791,7 @@ fn enlargement_to_target_works() { } ); - run_to_block(6); + System::run_to_block::<AllPalletsWithSystem>(6); // Two new items should have been issued to 1 & 2 for 40 each & duration of 2. assert_eq!( Receipts::<Test>::get(2).unwrap(), @@ -820,7 +820,7 @@ fn enlargement_to_target_works() { } ); - run_to_block(8); + System::run_to_block::<AllPalletsWithSystem>(8); // No change now. assert_eq!( Summary::<Test>::get(), @@ -835,7 +835,7 @@ fn enlargement_to_target_works() { // Set target a bit higher to use up the remaining bid. Target::set(Perquintill::from_percent(60)); - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); // One new item should have been issued to 1 for 40 each & duration of 2. assert_eq!( diff --git a/substrate/frame/node-authorization/Cargo.toml b/substrate/frame/node-authorization/Cargo.toml index 82aecc21d0b533973e81613b9e59637805438e80..86a78e6e361535ef4556527e6a84890e50cd3a4f 100644 --- a/substrate/frame/node-authorization/Cargo.toml +++ b/substrate/frame/node-authorization/Cargo.toml @@ -16,28 +16,18 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } -sp-core = { workspace = true } -sp-io = { workspace = true } -sp-runtime = { workspace = true } [features] default = ["std"] std = [ "codec/std", - "frame-support/std", - "frame-system/std", + "frame/std", "log/std", "scale-info/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "sp-runtime/try-runtime", + "frame/try-runtime", ] diff --git a/substrate/frame/node-authorization/src/lib.rs b/substrate/frame/node-authorization/src/lib.rs index 7682b54ea0f242b9c09a72bd6f134aa584598350..3cec0d3bcb63d7ea6944e923a64163f3c1831ae0 100644 --- a/substrate/frame/node-authorization/src/lib.rs +++ b/substrate/frame/node-authorization/src/lib.rs @@ -47,18 +47,18 @@ pub mod weights; extern crate alloc; use alloc::{collections::btree_set::BTreeSet, vec::Vec}; +use frame::{ + deps::{sp_core::OpaquePeerId as PeerId, sp_io}, + prelude::*, +}; pub use pallet::*; -use sp_core::OpaquePeerId as PeerId; -use sp_runtime::traits::StaticLookup; pub use weights::WeightInfo; type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source; -#[frame_support::pallet] +#[frame::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; #[pallet::pallet] #[pallet::without_storage_info] @@ -111,7 +111,7 @@ pub mod pallet { StorageMap<_, Blake2_128Concat, PeerId, BTreeSet<PeerId>, ValueQuery>; #[pallet::genesis_config] - #[derive(frame_support::DefaultNoBound)] + #[derive(DefaultNoBound)] pub struct GenesisConfig<T: Config> { pub nodes: Vec<(PeerId, T::AccountId)>, } @@ -171,7 +171,7 @@ pub mod pallet { impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { /// Set reserved node every block. It may not be enabled depends on the offchain /// worker settings when starting the node. - fn offchain_worker(now: frame_system::pallet_prelude::BlockNumberFor<T>) { + fn offchain_worker(now: BlockNumberFor<T>) { let network_state = sp_io::offchain::network_state(); match network_state { Err(_) => log::error!( diff --git a/substrate/frame/node-authorization/src/mock.rs b/substrate/frame/node-authorization/src/mock.rs index 656d2bfa39ad3d4b2ac22c135691c66dd9079b4a..c6665a479e1147b8ce2a51232d705d0865f360d9 100644 --- a/substrate/frame/node-authorization/src/mock.rs +++ b/substrate/frame/node-authorization/src/mock.rs @@ -20,13 +20,11 @@ use super::*; use crate as pallet_node_authorization; -use frame_support::{derive_impl, ord_parameter_types, traits::ConstU32}; -use frame_system::EnsureSignedBy; -use sp_runtime::BuildStorage; +use frame::testing_prelude::*; type Block = frame_system::mocking::MockBlock<Test>; -frame_support::construct_runtime!( +construct_runtime!( pub enum Test { System: frame_system, @@ -61,7 +59,7 @@ pub fn test_node(id: u8) -> PeerId { PeerId(vec![id]) } -pub fn new_test_ext() -> sp_io::TestExternalities { +pub fn new_test_ext() -> TestState { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); pallet_node_authorization::GenesisConfig::<Test> { nodes: vec![(test_node(10), 10), (test_node(20), 20), (test_node(30), 30)], diff --git a/substrate/frame/node-authorization/src/tests.rs b/substrate/frame/node-authorization/src/tests.rs index 4704b5adf2690f842480eb86356ee9c77437b010..cf60ab6efbd88bbea8060390cb7f95413b23bc9b 100644 --- a/substrate/frame/node-authorization/src/tests.rs +++ b/substrate/frame/node-authorization/src/tests.rs @@ -19,8 +19,7 @@ use super::*; use crate::mock::*; -use frame_support::{assert_noop, assert_ok}; -use sp_runtime::traits::BadOrigin; +use frame::testing_prelude::*; #[test] fn add_well_known_node_works() { diff --git a/substrate/frame/node-authorization/src/weights.rs b/substrate/frame/node-authorization/src/weights.rs index 881eeaf7a4c090573eab463494fd9a6846ae4b40..cd2935458b9daeefa6df80c2ab4d394b97ff431f 100644 --- a/substrate/frame/node-authorization/src/weights.rs +++ b/substrate/frame/node-authorization/src/weights.rs @@ -21,8 +21,7 @@ #![allow(unused_parens)] #![allow(unused_imports)] -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use core::marker::PhantomData; +use frame::weights_prelude::*; pub trait WeightInfo { fn add_well_known_node() -> Weight; diff --git a/substrate/frame/nomination-pools/Cargo.toml b/substrate/frame/nomination-pools/Cargo.toml index aa90e4d81339e5af1b8656f3c0e6e4844f8b933c..a5e8da17eb23eea2af1cd578b8b6d0abcaac4121 100644 --- a/substrate/frame/nomination-pools/Cargo.toml +++ b/substrate/frame/nomination-pools/Cargo.toml @@ -26,11 +26,11 @@ scale-info = { features = [ # FRAME frame-support = { workspace = true } frame-system = { workspace = true } -sp-runtime = { workspace = true } -sp-staking = { workspace = true } +log = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } -log = { workspace = true } +sp-runtime = { workspace = true } +sp-staking = { workspace = true } # Optional: use for testing and/or fuzzing pallet-balances = { optional = true, workspace = true } diff --git a/substrate/frame/nomination-pools/benchmarking/Cargo.toml b/substrate/frame/nomination-pools/benchmarking/Cargo.toml index 7dd826a912240f2f1b19b3dce4eaab0b7821cf84..0b3ac228e86f0e2910f5410f2dac5d5de58365f7 100644 --- a/substrate/frame/nomination-pools/benchmarking/Cargo.toml +++ b/substrate/frame/nomination-pools/benchmarking/Cargo.toml @@ -26,9 +26,9 @@ frame-election-provider-support = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-bags-list = { workspace = true } -pallet-staking = { workspace = true } pallet-delegated-staking = { workspace = true } pallet-nomination-pools = { workspace = true } +pallet-staking = { workspace = true } # Substrate Primitives sp-runtime = { workspace = true } @@ -37,8 +37,8 @@ sp-staking = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true } -pallet-timestamp = { workspace = true, default-features = true } pallet-staking-reward-curve = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } diff --git a/substrate/frame/nomination-pools/benchmarking/src/inner.rs b/substrate/frame/nomination-pools/benchmarking/src/inner.rs index 7ddb78cca3f9b8d78293f37c2e894ed805b84ed3..20c5eafbcfc59dee4d2460224e83a70458804d6d 100644 --- a/substrate/frame/nomination-pools/benchmarking/src/inner.rs +++ b/substrate/frame/nomination-pools/benchmarking/src/inner.rs @@ -132,6 +132,10 @@ fn migrate_to_transfer_stake<T: Config>(pool_id: PoolId) { .expect("member should have enough balance to transfer"); }); + // Pool needs to have ED balance free to stake so give it some. + // Note: we didn't require ED until pallet-staking migrated from locks to holds. + let _ = CurrencyOf::<T>::mint_into(&pool_acc, CurrencyOf::<T>::minimum_balance()); + pallet_staking::Pallet::<T>::migrate_to_direct_staker(&pool_acc); } @@ -141,14 +145,6 @@ fn vote_to_balance<T: pallet_nomination_pools::Config>( vote.try_into().map_err(|_| "could not convert u64 to Balance") } -/// `assertion` should strictly be true if the adapter is using `Delegate` strategy and strictly -/// false if the adapter is not using `Delegate` strategy. -fn assert_if_delegate<T: pallet_nomination_pools::Config>(assertion: bool) { - let legacy_adapter_used = T::StakeAdapter::strategy_type() != StakeStrategyType::Delegate; - // one and only one of the two should be true. - assert!(assertion ^ legacy_adapter_used); -} - #[allow(unused)] struct ListScenario<T: pallet_nomination_pools::Config> { /// Stash/Controller that is expected to be moved. @@ -981,9 +977,6 @@ mod benchmarks { #[benchmark] fn apply_slash() { - // Note: With older `TransferStake` strategy, slashing is greedy and apply_slash should - // always fail. - // We want to fill member's unbonding pools. So let's bond with big enough amount. let deposit_amount = Pools::<T>::depositor_min_bond() * T::MaxUnbonding::get().into() * 4u32.into(); @@ -993,7 +986,7 @@ mod benchmarks { // verify user balance in the pool. assert_eq!(PoolMembers::<T>::get(&depositor).unwrap().total_balance(), deposit_amount); // verify delegated balance. - assert_if_delegate::<T>( + assert!( T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) == Some(deposit_amount), ); @@ -1017,7 +1010,7 @@ mod benchmarks { deposit_amount / 2u32.into() ); // verify delegated balance are not yet slashed. - assert_if_delegate::<T>( + assert!( T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) == Some(deposit_amount), ); @@ -1041,13 +1034,11 @@ mod benchmarks { #[block] { - assert_if_delegate::<T>( - Pools::<T>::apply_slash( - RuntimeOrigin::Signed(slash_reporter.clone()).into(), - depositor_lookup.clone(), - ) - .is_ok(), - ); + assert!(Pools::<T>::apply_slash( + RuntimeOrigin::Signed(slash_reporter.clone()).into(), + depositor_lookup.clone(), + ) + .is_ok(),); } // verify balances are correct and slash applied. @@ -1055,7 +1046,7 @@ mod benchmarks { PoolMembers::<T>::get(&depositor).unwrap().total_balance(), deposit_amount / 2u32.into() ); - assert_if_delegate::<T>( + assert!( T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) == Some(deposit_amount / 2u32.into()), ); @@ -1126,18 +1117,16 @@ mod benchmarks { let _ = migrate_to_transfer_stake::<T>(1); #[block] { - assert_if_delegate::<T>( - Pools::<T>::migrate_pool_to_delegate_stake( - RuntimeOrigin::Signed(depositor.clone()).into(), - 1u32.into(), - ) - .is_ok(), - ); + assert!(Pools::<T>::migrate_pool_to_delegate_stake( + RuntimeOrigin::Signed(depositor.clone()).into(), + 1u32.into(), + ) + .is_ok(),); } - // this queries agent balance if `DelegateStake` strategy. + // this queries agent balance. assert_eq!( T::StakeAdapter::total_balance(Pool::from(pool_account.clone())), - Some(deposit_amount) + Some(deposit_amount + CurrencyOf::<T>::minimum_balance()) ); } @@ -1152,13 +1141,11 @@ mod benchmarks { let _ = migrate_to_transfer_stake::<T>(1); // Now migrate pool to delegate stake keeping delegators unmigrated. - assert_if_delegate::<T>( - Pools::<T>::migrate_pool_to_delegate_stake( - RuntimeOrigin::Signed(depositor.clone()).into(), - 1u32.into(), - ) - .is_ok(), - ); + assert!(Pools::<T>::migrate_pool_to_delegate_stake( + RuntimeOrigin::Signed(depositor.clone()).into(), + 1u32.into(), + ) + .is_ok(),); // delegation does not exist. assert!( @@ -1171,16 +1158,14 @@ mod benchmarks { #[block] { - assert_if_delegate::<T>( - Pools::<T>::migrate_delegation( - RuntimeOrigin::Signed(depositor.clone()).into(), - depositor_lookup.clone(), - ) - .is_ok(), - ); + assert!(Pools::<T>::migrate_delegation( + RuntimeOrigin::Signed(depositor.clone()).into(), + depositor_lookup.clone(), + ) + .is_ok(),); } // verify balances once more. - assert_if_delegate::<T>( + assert!( T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) == Some(deposit_amount), ); diff --git a/substrate/frame/nomination-pools/benchmarking/src/mock.rs b/substrate/frame/nomination-pools/benchmarking/src/mock.rs index 15d9e2c56031fe1e47b7b0cef3139ca177ddeb67..7c09cf22ad51e9575780525718d4fdd830bb6ecd 100644 --- a/substrate/frame/nomination-pools/benchmarking/src/mock.rs +++ b/substrate/frame/nomination-pools/benchmarking/src/mock.rs @@ -78,6 +78,7 @@ parameter_types! { } #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Runtime { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = Balance; type UnixTime = pallet_timestamp::Pallet<Self>; diff --git a/substrate/frame/nomination-pools/fuzzer/Cargo.toml b/substrate/frame/nomination-pools/fuzzer/Cargo.toml index e1518ed099aee7a71aeda2e19868378beee65d36..2f84004ece94e8759c0c8ef907873bfeb4aabe2d 100644 --- a/substrate/frame/nomination-pools/fuzzer/Cargo.toml +++ b/substrate/frame/nomination-pools/fuzzer/Cargo.toml @@ -21,15 +21,15 @@ honggfuzz = { workspace = true } pallet-nomination-pools = { features = ["fuzzing"], workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } frame-support = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -rand = { features = ["small_rng"], workspace = true, default-features = true } log = { workspace = true, default-features = true } +rand = { features = ["small_rng"], workspace = true, default-features = true } [[bin]] name = "call" diff --git a/substrate/frame/nomination-pools/runtime-api/Cargo.toml b/substrate/frame/nomination-pools/runtime-api/Cargo.toml index 6de9fc8c88444fc0760bb11be320b7b961ffab7e..337cc31c7cbb8e7a4fc4b6823d1e27b0ab978729 100644 --- a/substrate/frame/nomination-pools/runtime-api/Cargo.toml +++ b/substrate/frame/nomination-pools/runtime-api/Cargo.toml @@ -17,8 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -sp-api = { workspace = true } pallet-nomination-pools = { workspace = true } +sp-api = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/nomination-pools/src/adapter.rs b/substrate/frame/nomination-pools/src/adapter.rs index f125919dabfa6bce68dea67dd4fd60ed26ec0e3c..f1c68af4ea6ad170bbd54af728cdfd68dcef3688 100644 --- a/substrate/frame/nomination-pools/src/adapter.rs +++ b/substrate/frame/nomination-pools/src/adapter.rs @@ -16,6 +16,7 @@ // limitations under the License. use crate::*; +use frame_support::traits::tokens::{Fortitude::Polite, Preservation::Expendable}; use sp_staking::{Agent, DelegationInterface, DelegationMigrator, Delegator}; /// Types of stake strategies. @@ -245,8 +246,10 @@ pub trait StakeStrategy { /// strategy in an existing runtime, storage migration is required. See /// [`migration::unversioned::DelegationStakeMigration`]. For new runtimes, it is highly recommended /// to use the [`DelegateStake`] strategy. +#[deprecated = "consider migrating to DelegateStake"] pub struct TransferStake<T: Config, Staking: StakingInterface>(PhantomData<(T, Staking)>); +#[allow(deprecated)] impl<T: Config, Staking: StakingInterface<Balance = BalanceOf<T>, AccountId = T::AccountId>> StakeStrategy for TransferStake<T, Staking> { @@ -262,7 +265,8 @@ impl<T: Config, Staking: StakingInterface<Balance = BalanceOf<T>, AccountId = T: pool_account: Pool<Self::AccountId>, _: Member<Self::AccountId>, ) -> BalanceOf<T> { - T::Currency::balance(&pool_account.0).saturating_sub(Self::active_stake(pool_account)) + // free/liquid balance of the pool account. + T::Currency::reducible_balance(&pool_account.get(), Expendable, Polite) } fn total_balance(pool_account: Pool<Self::AccountId>) -> Option<BalanceOf<T>> { diff --git a/substrate/frame/nomination-pools/src/lib.rs b/substrate/frame/nomination-pools/src/lib.rs index dc82bf3a37c6e06430b6be9a318e7dd12fce342e..04736e6c1aad1cb04fcfe51c470929bf93f2e307 100644 --- a/substrate/frame/nomination-pools/src/lib.rs +++ b/substrate/frame/nomination-pools/src/lib.rs @@ -18,7 +18,7 @@ //! # Nomination Pools for Staking Delegation //! //! A pallet that allows members to delegate their stake to nominating pools. A nomination pool acts -//! as nominator and nominates validators on the members behalf. +//! as nominator and nominates validators on the members' behalf. //! //! # Index //! @@ -178,7 +178,7 @@ //! //! ### Pool Members //! -//! * In general, whenever a pool member changes their total point, the chain will automatically +//! * In general, whenever a pool member changes their total points, the chain will automatically //! claim all their pending rewards for them. This is not optional, and MUST happen for the reward //! calculation to remain correct (see the documentation of `bond` as an example). So, make sure //! you are warning your users about it. They might be surprised if they see that they bonded an @@ -1865,6 +1865,24 @@ pub mod pallet { MinBalanceDeficitAdjusted { pool_id: PoolId, amount: BalanceOf<T> }, /// Claimed excess frozen ED of af the reward pool. MinBalanceExcessAdjusted { pool_id: PoolId, amount: BalanceOf<T> }, + /// A pool member's claim permission has been updated. + MemberClaimPermissionUpdated { member: T::AccountId, permission: ClaimPermission }, + /// A pool's metadata was updated. + MetadataUpdated { pool_id: PoolId, caller: T::AccountId }, + /// A pool's nominating account (or the pool's root account) has nominated a validator set + /// on behalf of the pool. + PoolNominationMade { pool_id: PoolId, caller: T::AccountId }, + /// The pool is chilled i.e. no longer nominating. + PoolNominatorChilled { pool_id: PoolId, caller: T::AccountId }, + /// Global parameters regulating nomination pools have been updated. + GlobalParamsUpdated { + min_join_bond: BalanceOf<T>, + min_create_bond: BalanceOf<T>, + max_pools: Option<u32>, + max_members: Option<u32>, + max_members_per_pool: Option<u32>, + global_max_commission: Option<Perbill>, + }, } #[pallet::error] @@ -2509,13 +2527,13 @@ pub mod pallet { /// The dispatch origin of this call must be signed by the pool nominator or the pool /// root role. /// - /// This directly forward the call to the staking pallet, on behalf of the pool bonded - /// account. + /// This directly forwards the call to an implementation of `StakingInterface` (e.g., + /// `pallet-staking`) through [`Config::StakeAdapter`], on behalf of the bonded pool. /// /// # Note /// - /// In addition to a `root` or `nominator` role of `origin`, pool's depositor needs to have - /// at least `depositor_min_bond` in the pool to start nominating. + /// In addition to a `root` or `nominator` role of `origin`, the pool's depositor needs to + /// have at least `depositor_min_bond` in the pool to start nominating. #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))] pub fn nominate( @@ -2538,7 +2556,9 @@ pub mod pallet { Error::<T>::MinimumBondNotMet ); - T::StakeAdapter::nominate(Pool::from(bonded_pool.bonded_account()), validators) + T::StakeAdapter::nominate(Pool::from(bonded_pool.bonded_account()), validators).map( + |_| Self::deposit_event(Event::<T>::PoolNominationMade { pool_id, caller: who }), + ) } /// Set a new state for the pool. @@ -2603,6 +2623,8 @@ pub mod pallet { Metadata::<T>::mutate(pool_id, |pool_meta| *pool_meta = metadata); + Self::deposit_event(Event::<T>::MetadataUpdated { pool_id, caller: who }); + Ok(()) } @@ -2646,6 +2668,16 @@ pub mod pallet { config_op_exp!(MaxPoolMembers::<T>, max_members); config_op_exp!(MaxPoolMembersPerPool::<T>, max_members_per_pool); config_op_exp!(GlobalMaxCommission::<T>, global_max_commission); + + Self::deposit_event(Event::<T>::GlobalParamsUpdated { + min_join_bond: MinJoinBond::<T>::get(), + min_create_bond: MinCreateBond::<T>::get(), + max_pools: MaxPools::<T>::get(), + max_members: MaxPoolMembers::<T>::get(), + max_members_per_pool: MaxPoolMembersPerPool::<T>::get(), + global_max_commission: GlobalMaxCommission::<T>::get(), + }); + Ok(()) } @@ -2710,17 +2742,18 @@ pub mod pallet { /// The dispatch origin of this call can be signed by the pool nominator or the pool /// root role, same as [`Pallet::nominate`]. /// + /// This directly forwards the call to an implementation of `StakingInterface` (e.g., + /// `pallet-staking`) through [`Config::StakeAdapter`], on behalf of the bonded pool. + /// /// Under certain conditions, this call can be dispatched permissionlessly (i.e. by any /// account). /// /// # Conditions for a permissionless dispatch: - /// * When pool depositor has less than `MinNominatorBond` staked, otherwise pool members + /// * When pool depositor has less than `MinNominatorBond` staked, otherwise pool members /// are unable to unbond. /// /// # Conditions for permissioned dispatch: - /// * The caller has a nominator or root role of the pool. - /// This directly forward the call to the staking pallet, on behalf of the pool bonded - /// account. + /// * The caller is the pool's nominator or root. #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::chill())] pub fn chill(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult { @@ -2739,7 +2772,9 @@ pub mod pallet { ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator); } - T::StakeAdapter::chill(Pool::from(bonded_pool.bonded_account())) + T::StakeAdapter::chill(Pool::from(bonded_pool.bonded_account())).map(|_| { + Self::deposit_event(Event::<T>::PoolNominatorChilled { pool_id, caller: who }) + }) } /// `origin` bonds funds from `extra` for some pool member `member` into their respective @@ -2794,10 +2829,15 @@ pub mod pallet { Error::<T>::NotMigrated ); - ClaimPermissions::<T>::mutate(who, |source| { + ClaimPermissions::<T>::mutate(who.clone(), |source| { *source = permission; }); + Self::deposit_event(Event::<T>::MemberClaimPermissionUpdated { + member: who, + permission, + }); + Ok(()) } @@ -2913,9 +2953,20 @@ pub mod pallet { /// Claim pending commission. /// - /// The dispatch origin of this call must be signed by the `root` role of the pool. Pending - /// commission is paid out and added to total claimed commission`. Total pending commission - /// is reset to zero. the current. + /// The `root` role of the pool is _always_ allowed to claim the pool's commission. + /// + /// If the pool has set `CommissionClaimPermission::Permissionless`, then any account can + /// trigger the process of claiming the pool's commission. + /// + /// If the pool has set its `CommissionClaimPermission` to `Account(acc)`, then only + /// accounts + /// * `acc`, and + /// * the pool's root account + /// + /// may call this extrinsic on behalf of the pool. + /// + /// Pending commissions are paid out and added to the total claimed commission. + /// The total pending commission is reset to zero. #[pallet::call_index(20)] #[pallet::weight(T::WeightInfo::claim_commission())] pub fn claim_commission(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult { diff --git a/substrate/frame/nomination-pools/src/mock.rs b/substrate/frame/nomination-pools/src/mock.rs index cc942039760c0208776fc2f045310a9cb501c73a..f4552389a267abaacb459ed3b4fea0a82daa1a80 100644 --- a/substrate/frame/nomination-pools/src/mock.rs +++ b/substrate/frame/nomination-pools/src/mock.rs @@ -23,8 +23,10 @@ use frame_support::{ PalletId, }; use frame_system::{EnsureSignedBy, RawOrigin}; -use sp_runtime::{BuildStorage, FixedU128}; -use sp_staking::{OnStakingUpdate, Stake}; +use sp_runtime::{BuildStorage, DispatchResult, FixedU128}; +use sp_staking::{ + Agent, DelegationInterface, DelegationMigrator, Delegator, OnStakingUpdate, Stake, +}; pub type BlockNumber = u64; pub type AccountId = u128; @@ -76,6 +78,7 @@ impl StakingMock { let bonded = BondedBalanceMap::get(); let pre_total = bonded.get(&acc).unwrap(); Self::set_bonded_balance(acc, pre_total - amount); + DelegateMock::on_slash(acc, amount); Pools::on_slash(&acc, pre_total - amount, &Default::default(), amount); } } @@ -112,8 +115,8 @@ impl sp_staking::StakingInterface for StakingMock { .ok_or(DispatchError::Other("NotStash")) } - fn is_virtual_staker(_who: &Self::AccountId) -> bool { - false + fn is_virtual_staker(who: &Self::AccountId) -> bool { + AgentBalanceMap::get().contains_key(who) } fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { @@ -162,7 +165,9 @@ impl sp_staking::StakingInterface for StakingMock { staker_map.retain(|(unlocking_at, _amount)| *unlocking_at > current_era); // if there was a withdrawal, notify the pallet. - Pools::on_withdraw(&who, unlocking_before.saturating_sub(unlocking(&staker_map))); + let withdraw_amount = unlocking_before.saturating_sub(unlocking(&staker_map)); + Pools::on_withdraw(&who, withdraw_amount); + DelegateMock::on_withdraw(who, withdraw_amount); UnbondingBalanceMap::set(&unbonding_map); Ok(UnbondingBalanceMap::get().get(&who).unwrap().is_empty() && @@ -239,6 +244,176 @@ impl sp_staking::StakingInterface for StakingMock { } } +parameter_types! { + // Map of agent to their (delegated balance, unclaimed withdrawal, pending slash). + pub storage AgentBalanceMap: BTreeMap<AccountId, (Balance, Balance, Balance)> = Default::default(); + pub storage DelegatorBalanceMap: BTreeMap<AccountId, Balance> = Default::default(); +} +pub struct DelegateMock; +impl DelegationInterface for DelegateMock { + type Balance = Balance; + type AccountId = AccountId; + fn agent_balance(agent: Agent<Self::AccountId>) -> Option<Self::Balance> { + AgentBalanceMap::get() + .get(&agent.get()) + .copied() + .map(|(delegated, _, pending)| delegated - pending) + } + + fn agent_transferable_balance(agent: Agent<Self::AccountId>) -> Option<Self::Balance> { + AgentBalanceMap::get() + .get(&agent.get()) + .copied() + .map(|(_, unclaimed_withdrawals, _)| unclaimed_withdrawals) + } + + fn delegator_balance(delegator: Delegator<Self::AccountId>) -> Option<Self::Balance> { + DelegatorBalanceMap::get().get(&delegator.get()).copied() + } + + fn register_agent( + agent: Agent<Self::AccountId>, + _reward_account: &Self::AccountId, + ) -> DispatchResult { + let mut agents = AgentBalanceMap::get(); + agents.insert(agent.get(), (0, 0, 0)); + AgentBalanceMap::set(&agents); + Ok(()) + } + + fn remove_agent(agent: Agent<Self::AccountId>) -> DispatchResult { + let mut agents = AgentBalanceMap::get(); + let agent = agent.get(); + assert!(agents.contains_key(&agent)); + agents.remove(&agent); + AgentBalanceMap::set(&agents); + Ok(()) + } + + fn delegate( + delegator: Delegator<Self::AccountId>, + agent: Agent<Self::AccountId>, + amount: Self::Balance, + ) -> DispatchResult { + let delegator = delegator.get(); + let mut delegators = DelegatorBalanceMap::get(); + delegators.entry(delegator).and_modify(|b| *b += amount).or_insert(amount); + DelegatorBalanceMap::set(&delegators); + + let agent = agent.get(); + let mut agents = AgentBalanceMap::get(); + agents + .get_mut(&agent) + .map(|(d, _, _)| *d += amount) + .ok_or(DispatchError::Other("agent not registered"))?; + AgentBalanceMap::set(&agents); + + if BondedBalanceMap::get().contains_key(&agent) { + StakingMock::bond_extra(&agent, amount) + } else { + // reward account does not matter in this context. + StakingMock::bond(&agent, amount, &999) + } + } + + fn withdraw_delegation( + delegator: Delegator<Self::AccountId>, + agent: Agent<Self::AccountId>, + amount: Self::Balance, + _num_slashing_spans: u32, + ) -> DispatchResult { + let mut delegators = DelegatorBalanceMap::get(); + delegators.get_mut(&delegator.get()).map(|b| *b -= amount); + DelegatorBalanceMap::set(&delegators); + + let mut agents = AgentBalanceMap::get(); + agents.get_mut(&agent.get()).map(|(d, u, _)| { + *d -= amount; + *u -= amount; + }); + AgentBalanceMap::set(&agents); + + Ok(()) + } + + fn pending_slash(agent: Agent<Self::AccountId>) -> Option<Self::Balance> { + AgentBalanceMap::get() + .get(&agent.get()) + .copied() + .map(|(_, _, pending_slash)| pending_slash) + } + + fn delegator_slash( + agent: Agent<Self::AccountId>, + delegator: Delegator<Self::AccountId>, + value: Self::Balance, + _maybe_reporter: Option<Self::AccountId>, + ) -> DispatchResult { + let mut delegators = DelegatorBalanceMap::get(); + delegators.get_mut(&delegator.get()).map(|b| *b -= value); + DelegatorBalanceMap::set(&delegators); + + let mut agents = AgentBalanceMap::get(); + agents.get_mut(&agent.get()).map(|(_, _, p)| { + p.saturating_reduce(value); + }); + AgentBalanceMap::set(&agents); + + Ok(()) + } +} + +impl DelegateMock { + pub fn set_agent_balance(who: AccountId, delegated: Balance) { + let mut agents = AgentBalanceMap::get(); + agents.insert(who, (delegated, 0, 0)); + AgentBalanceMap::set(&agents); + } + + pub fn set_delegator_balance(who: AccountId, amount: Balance) { + let mut delegators = DelegatorBalanceMap::get(); + delegators.insert(who, amount); + DelegatorBalanceMap::set(&delegators); + } + + pub fn on_slash(agent: AccountId, amount: Balance) { + let mut agents = AgentBalanceMap::get(); + agents.get_mut(&agent).map(|(_, _, p)| *p += amount); + AgentBalanceMap::set(&agents); + } + + fn on_withdraw(agent: AccountId, amount: Balance) { + let mut agents = AgentBalanceMap::get(); + // if agent exists, add the amount to unclaimed withdrawals. + agents.get_mut(&agent).map(|(_, u, _)| *u += amount); + AgentBalanceMap::set(&agents); + } +} + +impl DelegationMigrator for DelegateMock { + type Balance = Balance; + type AccountId = AccountId; + fn migrate_nominator_to_agent( + _agent: Agent<Self::AccountId>, + _reward_account: &Self::AccountId, + ) -> DispatchResult { + unimplemented!("not used in current unit tests") + } + + fn migrate_delegation( + _agent: Agent<Self::AccountId>, + _delegator: Delegator<Self::AccountId>, + _value: Self::Balance, + ) -> DispatchResult { + unimplemented!("not used in current unit tests") + } + + #[cfg(feature = "runtime-benchmarks")] + fn force_kill_agent(_agent: Agent<Self::AccountId>) { + unimplemented!("not used in current unit tests") + } +} + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Runtime { type Nonce = u64; @@ -295,7 +470,7 @@ impl pools::Config for Runtime { type RewardCounter = RewardCounter; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; - type StakeAdapter = adapter::TransferStake<Self, StakingMock>; + type StakeAdapter = adapter::DelegateStake<Self, StakingMock, DelegateMock>; type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; type PalletId = PoolsPalletId; type MaxMetadataLen = MaxMetadataLen; @@ -435,18 +610,7 @@ parameter_types! { /// Helper to run a specified amount of blocks. pub fn run_blocks(n: u64) { let current_block = System::block_number(); - run_to_block(n + current_block); -} - -/// Helper to run to a specific block. -pub fn run_to_block(n: u64) { - let current_block = System::block_number(); - assert!(n > current_block); - while System::block_number() < n { - Pools::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - Pools::on_initialize(System::block_number()); - } + System::run_to_block::<AllPalletsWithSystem>(n + current_block); } /// All events of this pallet. @@ -533,6 +697,21 @@ pub fn reward_imbalance(pool: PoolId) -> RewardImbalance { } } +pub fn set_pool_balance(who: AccountId, amount: Balance) { + StakingMock::set_bonded_balance(who, amount); + DelegateMock::set_agent_balance(who, amount); +} + +pub fn member_delegation(who: AccountId) -> Balance { + <T as Config>::StakeAdapter::member_delegation_balance(Member::from(who)) + .expect("who must be a pool member") +} + +pub fn pool_balance(id: PoolId) -> Balance { + <T as Config>::StakeAdapter::total_balance(Pool::from(Pools::generate_bonded_account(id))) + .expect("who must be a bonded pool account") +} + #[cfg(test)] mod test { use super::*; diff --git a/substrate/frame/nomination-pools/src/tests.rs b/substrate/frame/nomination-pools/src/tests.rs index 06261699a5b23ff58c227875b143a1eaba08e439..e2922e22fa98949c219d377b53cd2f0aba3c4964 100644 --- a/substrate/frame/nomination-pools/src/tests.rs +++ b/substrate/frame/nomination-pools/src/tests.rs @@ -17,13 +17,14 @@ use super::*; use crate::{mock::*, Event}; -use frame_support::{assert_err, assert_noop, assert_ok, assert_storage_noop}; +use frame_support::{assert_err, assert_noop, assert_ok}; use pallet_balances::Event as BEvent; use sp_runtime::{ bounded_btree_map, traits::{BadOrigin, Dispatchable}, FixedU128, }; +use sp_staking::{Agent, DelegationInterface}; macro_rules! unbonding_pools_with_era { ($($k:expr => $v:expr),* $(,)?) => {{ @@ -127,41 +128,41 @@ mod bonded_pool { }; // 1 points : 1 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + set_pool_balance(bonded_pool.bonded_account(), 100); assert_eq!(bonded_pool.balance_to_point(10), 10); assert_eq!(bonded_pool.balance_to_point(0), 0); // 2 points : 1 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 50); + set_pool_balance(bonded_pool.bonded_account(), 50); assert_eq!(bonded_pool.balance_to_point(10), 20); // 1 points : 2 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + set_pool_balance(bonded_pool.bonded_account(), 100); bonded_pool.points = 50; assert_eq!(bonded_pool.balance_to_point(10), 5); // 100 points : 0 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); + set_pool_balance(bonded_pool.bonded_account(), 0); bonded_pool.points = 100; assert_eq!(bonded_pool.balance_to_point(10), 100 * 10); // 0 points : 100 balance - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + set_pool_balance(bonded_pool.bonded_account(), 100); bonded_pool.points = 0; assert_eq!(bonded_pool.balance_to_point(10), 10); // 10 points : 3 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 30); + set_pool_balance(bonded_pool.bonded_account(), 30); bonded_pool.points = 100; assert_eq!(bonded_pool.balance_to_point(10), 33); // 2 points : 3 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 300); + set_pool_balance(bonded_pool.bonded_account(), 300); bonded_pool.points = 200; assert_eq!(bonded_pool.balance_to_point(10), 6); // 4 points : 9 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 900); + set_pool_balance(bonded_pool.bonded_account(), 900); bonded_pool.points = 400; assert_eq!(bonded_pool.balance_to_point(90), 40); }) @@ -182,7 +183,7 @@ mod bonded_pool { }, }; - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + set_pool_balance(bonded_pool.bonded_account(), 100); assert_eq!(bonded_pool.points_to_balance(10), 10); assert_eq!(bonded_pool.points_to_balance(0), 0); @@ -191,27 +192,27 @@ mod bonded_pool { assert_eq!(bonded_pool.points_to_balance(10), 20); // 100 balance : 0 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + set_pool_balance(bonded_pool.bonded_account(), 100); bonded_pool.points = 0; assert_eq!(bonded_pool.points_to_balance(10), 0); // 0 balance : 100 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); + set_pool_balance(bonded_pool.bonded_account(), 0); bonded_pool.points = 100; assert_eq!(bonded_pool.points_to_balance(10), 0); // 10 balance : 3 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + set_pool_balance(bonded_pool.bonded_account(), 100); bonded_pool.points = 30; assert_eq!(bonded_pool.points_to_balance(10), 33); // 2 balance : 3 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 200); + set_pool_balance(bonded_pool.bonded_account(), 200); bonded_pool.points = 300; assert_eq!(bonded_pool.points_to_balance(10), 6); // 4 balance : 9 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 400); + set_pool_balance(bonded_pool.bonded_account(), 400); bonded_pool.points = 900; assert_eq!(bonded_pool.points_to_balance(90), 40); }) @@ -269,30 +270,21 @@ mod bonded_pool { <<Runtime as Config>::MaxPointsToBalance as Get<u8>>::get().into(); // Simulate a 100% slashed pool - StakingMock::set_bonded_balance(pool.bonded_account(), 0); + set_pool_balance(pool.bonded_account(), 0); assert_noop!(pool.ok_to_join(), Error::<Runtime>::OverflowRisk); // Simulate a slashed pool at `MaxPointsToBalance` + 1 slashed pool - StakingMock::set_bonded_balance( - pool.bonded_account(), - max_points_to_balance.saturating_add(1), - ); + set_pool_balance(pool.bonded_account(), max_points_to_balance.saturating_add(1)); assert_ok!(pool.ok_to_join()); // Simulate a slashed pool at `MaxPointsToBalance` - StakingMock::set_bonded_balance(pool.bonded_account(), max_points_to_balance); + set_pool_balance(pool.bonded_account(), max_points_to_balance); assert_noop!(pool.ok_to_join(), Error::<Runtime>::OverflowRisk); - StakingMock::set_bonded_balance( - pool.bonded_account(), - Balance::MAX / max_points_to_balance, - ); + set_pool_balance(pool.bonded_account(), Balance::MAX / max_points_to_balance); // and a sanity check - StakingMock::set_bonded_balance( - pool.bonded_account(), - Balance::MAX / max_points_to_balance - 1, - ); + set_pool_balance(pool.bonded_account(), Balance::MAX / max_points_to_balance - 1); assert_ok!(pool.ok_to_join()); }); } @@ -310,7 +302,7 @@ mod bonded_pool { state: PoolState::Open, }, }; - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), u128::MAX); + set_pool_balance(bonded_pool.bonded_account(), u128::MAX); // Max out the points and balance of the pool and make sure the conversion works as // expected and does not overflow. @@ -640,8 +632,6 @@ mod sub_pools { } mod join { - use sp_runtime::TokenError; - use super::*; #[test] @@ -671,6 +661,7 @@ mod join { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true }, ] ); @@ -728,7 +719,7 @@ mod join { ); // Force the pools bonded balance to 0, simulating a 100% slash - StakingMock::set_bonded_balance(Pools::generate_bonded_account(1), 0); + set_pool_balance(Pools::generate_bonded_account(1), 0); assert_noop!( Pools::join(RuntimeOrigin::signed(11), 420, 1), Error::<Runtime>::OverflowRisk @@ -754,29 +745,13 @@ mod join { let max_points_to_balance: u128 = <<Runtime as Config>::MaxPointsToBalance as Get<u8>>::get().into(); - StakingMock::set_bonded_balance( - Pools::generate_bonded_account(123), - max_points_to_balance, - ); + set_pool_balance(Pools::generate_bonded_account(123), max_points_to_balance); assert_noop!( Pools::join(RuntimeOrigin::signed(11), 420, 123), Error::<Runtime>::OverflowRisk ); - StakingMock::set_bonded_balance( - Pools::generate_bonded_account(123), - Balance::MAX / max_points_to_balance, - ); - // Balance needs to be gt Balance::MAX / `MaxPointsToBalance` - assert_noop!( - Pools::join(RuntimeOrigin::signed(11), 5, 123), - TokenError::FundsUnavailable, - ); - - StakingMock::set_bonded_balance( - Pools::generate_bonded_account(1), - max_points_to_balance, - ); + set_pool_balance(Pools::generate_bonded_account(1), max_points_to_balance); // Cannot join a pool that isn't open unsafe_set_state(123, PoolState::Blocked); @@ -807,7 +782,7 @@ mod join { #[cfg_attr(not(debug_assertions), should_panic)] fn join_panics_when_reward_pool_not_found() { ExtBuilder::default().build_and_execute(|| { - StakingMock::set_bonded_balance(Pools::generate_bonded_account(123), 100); + set_pool_balance(Pools::generate_bonded_account(123), 100); BondedPool::<Runtime> { id: 123, inner: BondedPoolInner { @@ -843,6 +818,7 @@ mod join { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 101, pool_id: 1, bonded: 100, joined: true }, Event::Bonded { member: 102, pool_id: 1, bonded: 100, joined: true } ] @@ -1115,6 +1091,7 @@ mod claim_payout { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 11, pool_id: 1, bonded: 11, joined: true }, Event::Unbonded { member: 11, pool_id: 1, points: 11, balance: 11, era: 3 } ] @@ -1147,6 +1124,7 @@ mod claim_payout { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::PoolCommissionUpdated { pool_id: 1, current: Some((Perbill::from_percent(75), 2)) @@ -1210,6 +1188,7 @@ mod claim_payout { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::PaidOut { member: 10, pool_id: 1, payout: 5 } ] ); @@ -1285,6 +1264,7 @@ mod claim_payout { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, Event::Bonded { member: 50, pool_id: 1, bonded: 50, joined: true } ] @@ -1540,6 +1520,7 @@ mod claim_payout { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, Event::PaidOut { member: 10, pool_id: 1, payout: 20 }, Event::PaidOut { member: 20, pool_id: 1, payout: 10 }, @@ -1583,6 +1564,7 @@ mod claim_payout { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, Event::PaidOut { member: 10, pool_id: 1, payout: 3 + 3 }, Event::PaidOut { member: 20, pool_id: 1, payout: 3 }, @@ -1646,6 +1628,7 @@ mod claim_payout { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true }, Event::PaidOut { member: 10, pool_id: 1, payout: 30 + 100 / 2 + 60 / 3 }, @@ -1747,6 +1730,7 @@ mod claim_payout { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true }, Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, @@ -1796,6 +1780,7 @@ mod claim_payout { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, Event::PaidOut { member: 20, pool_id: 1, payout: 20 } @@ -1844,6 +1829,7 @@ mod claim_payout { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true }, Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, @@ -1910,6 +1896,7 @@ mod claim_payout { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::PaidOut { member: 10, pool_id: 1, payout: 10 } ] @@ -2009,6 +1996,7 @@ mod claim_payout { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Created { depositor: 20, pool_id: 2 }, Event::Bonded { member: 20, pool_id: 2, bonded: 10, joined: true }, Event::Created { depositor: 30, pool_id: 3 }, @@ -2078,6 +2066,7 @@ mod claim_payout { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 40, pool_id: 1, bonded: 10, joined: true } @@ -2158,6 +2147,7 @@ mod claim_payout { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false }, Event::PaidOut { member: 10, pool_id: 1, payout: 15 }, @@ -2303,6 +2293,7 @@ mod claim_payout { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::Bonded { member: 30, pool_id: 1, bonded: 20, joined: true }, Event::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 3 }, @@ -2321,8 +2312,8 @@ mod claim_payout { fn rewards_are_rounded_down_depositor_collects_them() { ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| { // initial balance of 10. - - assert_eq!(Currency::free_balance(&10), 35); + let init_balance_10 = Currency::free_balance(&10); + assert_eq!(member_delegation(10), 10); assert_eq!( Currency::free_balance(&default_reward_account()), Currency::minimum_balance() @@ -2341,6 +2332,7 @@ mod claim_payout { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::PaidOut { member: 10, pool_id: 1, payout: 13 }, Event::PaidOut { member: 20, pool_id: 1, payout: 26 } @@ -2373,8 +2365,10 @@ mod claim_payout { ); assert!(!Metadata::<T>::contains_key(1)); - // original ed + ed put into reward account + reward + bond + dust. - assert_eq!(Currency::free_balance(&10), 35 + 5 + 13 + 10 + 1); + // original ed + ed put into reward account + reward + dust. + assert_eq!(Currency::free_balance(&10), init_balance_10 + 5 + 13 + 1); + // delegation reduced from 10 to 0. + assert_eq!(member_delegation(10), 0); }) } @@ -2409,6 +2403,7 @@ mod claim_payout { bonded: 1000000000000000, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 20, pool_id: 1, @@ -2444,9 +2439,10 @@ mod claim_payout { let claimable_reward = 8 - ExistentialDeposit::get(); // NOTE: easier to read if we use 3, so let's use the number instead of variable. assert_eq!(claimable_reward, 3, "test is correct if rewards are divisible by 3"); + let init_balance = Currency::free_balance(&10); // given - assert_eq!(Currency::free_balance(&10), 35); + assert_eq!(member_delegation(10), 10); // when @@ -2455,7 +2451,10 @@ mod claim_payout { assert_ok!(Pools::claim_payout_other(RuntimeOrigin::signed(80), 10)); // then - assert_eq!(Currency::free_balance(&10), 36); + // delegated balance does not change. + assert_eq!(member_delegation(10), 10); + // reward of 1 is paid out to 10. + assert_eq!(Currency::free_balance(&10), init_balance + 1); assert_eq!(Currency::free_balance(&default_reward_account()), 7); }) } @@ -2604,6 +2603,7 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::PaidOut { member: 20, pool_id: 1, payout: 6 }, Event::Unbonded { member: 20, pool_id: 1, balance: 20, points: 20, era: 3 } @@ -2818,6 +2818,8 @@ mod unbond { ExtBuilder::default() .add_members(vec![(40, 40), (550, 550)]) .build_and_execute(|| { + let init_balance_40 = Currency::free_balance(&40); + let init_balance_550 = Currency::free_balance(&550); let ed = Currency::minimum_balance(); // Given a slash from 600 -> 500 StakingMock::slash_by(1, 500); @@ -2851,6 +2853,7 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, Event::PoolSlashed { pool_id: 1, balance: 100 }, @@ -2864,7 +2867,9 @@ mod unbond { PoolMembers::<Runtime>::get(40).unwrap().unbonding_eras, member_unbonding_eras!(3 => 6) ); - assert_eq!(Currency::free_balance(&40), 40 + 40); // We claim rewards when unbonding + assert_eq!(member_delegation(40), 40); + // We claim rewards when unbonding + assert_eq!(Currency::free_balance(&40), init_balance_40 + 40); // When unsafe_set_state(1, PoolState::Destroying); @@ -2893,7 +2898,8 @@ mod unbond { PoolMembers::<Runtime>::get(550).unwrap().unbonding_eras, member_unbonding_eras!(3 => 92) ); - assert_eq!(Currency::free_balance(&550), 550 + 550); + assert_eq!(member_delegation(550), 550); + assert_eq!(Currency::free_balance(&550), init_balance_550 + 550); assert_eq!( pool_events_since_last_call(), vec![ @@ -2934,7 +2940,8 @@ mod unbond { ); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0); - assert_eq!(Currency::free_balance(&550), 550 + 550 + 92); + // 550 is removed from pool. + assert_eq!(member_delegation(550), 0); assert_eq!( pool_events_since_last_call(), vec![ @@ -2989,6 +2996,7 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10, era: 9 } ] ); @@ -3023,6 +3031,7 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true }, Event::Unbonded { @@ -3116,6 +3125,7 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100, era: 3 } ] @@ -3272,6 +3282,7 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1, era: 3 } ] ); @@ -3404,6 +3415,7 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::Unbonded { member: 20, pool_id: 1, points: 2, balance: 2, era: 3 }, Event::Unbonded { member: 20, pool_id: 1, points: 3, balance: 3, era: 4 }, @@ -3440,6 +3452,7 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3, era: 3 } ] ); @@ -3481,6 +3494,7 @@ mod unbond { // 2/3 of ed, which is 20's share. Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::PaidOut { member: 20, pool_id: 1, payout: 10 }, Event::Unbonded { member: 20, pool_id: 1, balance: 2, points: 2, era: 3 } @@ -3532,7 +3546,7 @@ mod pool_withdraw_unbonded { assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15)); assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(20)); - assert_eq!(Balances::free_balance(&default_bonded_account()), 20); + assert_eq!(pool_balance(1), 20); // When CurrentEra::set(StakingMock::current_era() + StakingMock::bonding_duration() + 1); @@ -3541,7 +3555,7 @@ mod pool_withdraw_unbonded { // Then their unbonding balance is no longer locked assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15)); assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(15)); - assert_eq!(Balances::free_balance(&default_bonded_account()), 20); + assert_eq!(pool_balance(1), 20); }); } #[test] @@ -3552,7 +3566,7 @@ mod pool_withdraw_unbonded { assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15)); assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(20)); - assert_eq!(Balances::free_balance(&default_bonded_account()), 20); + assert_eq!(pool_balance(1), 20); assert_eq!(TotalValueLocked::<T>::get(), 20); // When @@ -3568,14 +3582,14 @@ mod pool_withdraw_unbonded { // Then their unbonding balance is no longer locked assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15)); assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(15)); - assert_eq!(Currency::free_balance(&default_bonded_account()), 20); + assert_eq!(pool_balance(1), 20); // The difference between TVL and member_balance is exactly the difference between - // `total_stake` and the `free_balance`. - // This relation is not guaranteed in the wild as arbitrary transfers towards - // `free_balance` can be made to the pool that are not accounted for. - let non_locked_balance = Balances::free_balance(&default_bonded_account()) - - StakingMock::total_stake(&default_bonded_account()).unwrap(); + // `pool balance` (sum of all balance delegated to pool) and the `staked balance`. + // This is the withdrawn funds from the pool stake that has not yet been claimed by the + // respective members. + let non_locked_balance = + pool_balance(1) - StakingMock::total_stake(&default_bonded_account()).unwrap(); assert_eq!(member_balance, TotalValueLocked::<T>::get() + non_locked_balance); }); } @@ -3597,7 +3611,7 @@ mod withdraw_unbonded { assert_eq!(StakingMock::bonding_duration(), 3); assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(550), 550)); assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(40), 40)); - assert_eq!(Currency::free_balance(&default_bonded_account()), 600); + assert_eq!(pool_balance(1), 600); let mut current_era = 1; CurrentEra::set(current_era); @@ -3626,10 +3640,7 @@ mod withdraw_unbonded { .1 /= 2; UnbondingBalanceMap::set(&x); - Currency::set_balance( - &default_bonded_account(), - Currency::free_balance(&default_bonded_account()) / 2, // 300 - ); + set_pool_balance(1, pool_balance(1) / 2); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 10); StakingMock::slash_by(1, 5); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 5); @@ -3658,6 +3669,7 @@ mod withdraw_unbonded { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, Event::Unbonded { @@ -3671,11 +3683,6 @@ mod withdraw_unbonded { Event::PoolSlashed { pool_id: 1, balance: 5 } ] ); - assert_eq!( - balances_events_since_last_call(), - vec![BEvent::Burned { who: default_bonded_account(), amount: 300 }] - ); - // When assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(550), 550, 0)); @@ -3691,10 +3698,9 @@ mod withdraw_unbonded { Event::MemberRemoved { pool_id: 1, member: 550, released_balance: 0 } ] ); - assert_eq!( - balances_events_since_last_call(), - vec![BEvent::Transfer { from: default_bonded_account(), to: 550, amount: 275 }] - ); + + // member has 40 tokens in delegation, but only 20 can be withdrawan. + assert_eq!(member_delegation(40), 40); // When assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(40), 40, 0)); @@ -3708,18 +3714,18 @@ mod withdraw_unbonded { assert_eq!( pool_events_since_last_call(), vec![ + // out of 40, 20 is withdrawn. Event::Withdrawn { member: 40, pool_id: 1, balance: 20, points: 40 }, - Event::MemberRemoved { pool_id: 1, member: 40, released_balance: 0 } + // member is removed and the dangling delegation of 20 tokens left in their + // account is released. + Event::MemberRemoved { pool_id: 1, member: 40, released_balance: 20 } ] ); - assert_eq!( - balances_events_since_last_call(), - vec![BEvent::Transfer { from: default_bonded_account(), to: 40, amount: 20 }] - ); // now, finally, the depositor can take out its share. unsafe_set_state(1, PoolState::Destroying); assert_ok!(fully_unbond_permissioned(10)); + assert_eq!(member_delegation(10), 10); current_era += 3; CurrentEra::set(current_era); @@ -3731,7 +3737,9 @@ 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, released_balance: 0 }, + // when member is removed, any leftover delegation is released. + Event::MemberRemoved { pool_id: 1, member: 10, released_balance: 5 }, + // when the last member leaves, the pool is destroyed. Event::Destroyed { pool_id: 1 } ] ); @@ -3739,7 +3747,6 @@ mod withdraw_unbonded { assert_eq!( balances_events_since_last_call(), vec![ - BEvent::Transfer { from: default_bonded_account(), to: 10, amount: 5 }, BEvent::Thawed { who: default_reward_account(), amount: 5 }, BEvent::Transfer { from: default_reward_account(), to: 10, amount: 5 } ] @@ -3753,11 +3760,9 @@ mod withdraw_unbonded { .add_members(vec![(40, 40), (550, 550)]) .build_and_execute(|| { let _ = balances_events_since_last_call(); - // Given // current bond is 600, we slash it all to 300. StakingMock::slash_by(1, 300); - Currency::set_balance(&default_bonded_account(), 300); assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(300)); assert_ok!(fully_unbond_permissioned(40)); @@ -3774,6 +3779,7 @@ mod withdraw_unbonded { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, Event::PoolSlashed { pool_id: 1, balance: 300 }, @@ -3787,10 +3793,6 @@ mod withdraw_unbonded { } ] ); - assert_eq!( - balances_events_since_last_call(), - vec![BEvent::Burned { who: default_bonded_account(), amount: 300 },] - ); CurrentEra::set(StakingMock::bonding_duration()); @@ -3798,10 +3800,6 @@ mod withdraw_unbonded { assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(40), 40, 0)); // Then - assert_eq!( - balances_events_since_last_call(), - vec![BEvent::Transfer { from: default_bonded_account(), to: 40, amount: 20 },] - ); assert_eq!( pool_events_since_last_call(), vec![ @@ -3819,10 +3817,6 @@ mod withdraw_unbonded { assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(550), 550, 0)); // Then - assert_eq!( - balances_events_since_last_call(), - vec![BEvent::Transfer { from: default_bonded_account(), to: 550, amount: 275 },] - ); assert_eq!( pool_events_since_last_call(), vec![ @@ -3852,15 +3846,18 @@ mod withdraw_unbonded { assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); // then - assert_eq!(Currency::free_balance(&10), 10 + 35); - assert_eq!(Currency::free_balance(&default_bonded_account()), 0); - + assert_eq!( + DelegateMock::agent_balance(Agent::from(default_bonded_account())), + None + ); + assert_eq!(StakingMock::stake(&default_bonded_account()).unwrap().total, 0); // in this test 10 also gets a fair share of the slash, because the slash was // applied to the bonded account. assert_eq!( pool_events_since_last_call(), vec![ Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5, era: 6 }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Withdrawn { member: 10, pool_id: 1, points: 5, balance: 5 }, Event::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, Event::Destroyed { pool_id: 1 } @@ -3870,7 +3867,6 @@ mod withdraw_unbonded { assert_eq!( balances_events_since_last_call(), vec![ - BEvent::Transfer { from: default_bonded_account(), to: 10, amount: 5 }, BEvent::Thawed { who: default_reward_account(), amount: 5 }, BEvent::Transfer { from: default_reward_account(), to: 10, amount: 5 } ] @@ -3878,35 +3874,6 @@ mod withdraw_unbonded { }); } - #[test] - fn withdraw_unbonded_handles_faulty_sub_pool_accounting() { - ExtBuilder::default().build_and_execute(|| { - // Given - assert_eq!(Currency::minimum_balance(), 5); - assert_eq!(Currency::free_balance(&10), 35); - assert_eq!(Currency::free_balance(&default_bonded_account()), 10); - unsafe_set_state(1, PoolState::Destroying); - assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(10), 10)); - - // Simulate a slash that is not accounted for in the sub pools. - Currency::set_balance(&default_bonded_account(), 5); - assert_eq!( - SubPoolsStorage::<Runtime>::get(1).unwrap().with_era, - //------------------------------balance decrease is not account for - unbonding_pools_with_era! { 3 => UnbondPool { points: 10, balance: 10 } } - ); - - CurrentEra::set(3); - - // When - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); - - // Then - assert_eq!(Currency::free_balance(&10), 10 + 35); - assert_eq!(Currency::free_balance(&default_bonded_account()), 0); - }); - } - #[test] fn withdraw_unbonded_errors_correctly() { ExtBuilder::default().with_check(0).build_and_execute(|| { @@ -3925,6 +3892,10 @@ mod withdraw_unbonded { let mut member = PoolMember { pool_id: 1, points: 10, ..Default::default() }; PoolMembers::<Runtime>::insert(11, member.clone()); + // set agent and delegator balance + DelegateMock::set_agent_balance(Pools::generate_bonded_account(1), 10); + DelegateMock::set_delegator_balance(11, 10); + // Simulate calling `unbond` member.unbonding_eras = member_unbonding_eras!(3 => 10); PoolMembers::<Runtime>::insert(11, member.clone()); @@ -3975,6 +3946,7 @@ mod withdraw_unbonded { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true }, Event::Unbonded { @@ -4045,7 +4017,7 @@ mod withdraw_unbonded { } ); CurrentEra::set(StakingMock::bonding_duration()); - assert_eq!(Currency::free_balance(&100), 100); + assert_eq!(member_delegation(100), 100); // Cannot permissionlessly withdraw assert_noop!( @@ -4061,12 +4033,14 @@ mod withdraw_unbonded { assert_eq!(SubPoolsStorage::<Runtime>::get(1).unwrap(), Default::default(),); assert_eq!(Currency::free_balance(&100), 100 + 100); + assert_eq!(member_delegation(100), 0); assert!(!PoolMembers::<Runtime>::contains_key(100)); assert_eq!( pool_events_since_last_call(), vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, 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 }, @@ -4107,6 +4081,7 @@ mod withdraw_unbonded { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false }, Event::Unbonded { member: 10, pool_id: 1, points: 6, balance: 6, era: 3 }, Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1, era: 4 } @@ -4194,6 +4169,7 @@ mod withdraw_unbonded { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 11, pool_id: 1, bonded: 10, joined: true }, Event::Unbonded { member: 11, pool_id: 1, points: 6, balance: 6, era: 3 }, Event::Unbonded { member: 11, pool_id: 1, points: 1, balance: 1, era: 4 } @@ -4290,6 +4266,7 @@ mod withdraw_unbonded { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, Event::Unbonded { member: 100, pool_id: 1, points: 75, balance: 75, era: 3 }, Event::Unbonded { member: 100, pool_id: 1, points: 25, balance: 25, era: 4 }, @@ -4528,6 +4505,7 @@ mod withdraw_unbonded { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false }, Event::Unbonded { member: 10, pool_id: 1, balance: 7, points: 7, era: 3 }, Event::Unbonded { member: 10, pool_id: 1, balance: 3, points: 3, era: 4 }, @@ -4574,6 +4552,7 @@ mod withdraw_unbonded { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::Unbonded { member: 20, pool_id: 1, balance: 20, points: 20, era: 4 }, ] @@ -4614,6 +4593,7 @@ mod withdraw_unbonded { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 4 }, ] ); @@ -4656,16 +4636,13 @@ mod withdraw_unbonded { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 4 }, ] ); // move to era when unbonded funds can be withdrawn. CurrentEra::set(4); - - // increment consumer by 1 reproducing the erroneous consumer bug. - // refer https://github.com/paritytech/polkadot-sdk/issues/4440. - assert_ok!(frame_system::Pallet::<T>::inc_consumers(&pool_one)); assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); assert_eq!( @@ -4712,7 +4689,7 @@ mod create { )); assert_eq!(TotalValueLocked::<T>::get(), 10 + StakingMock::minimum_nominator_bond()); - assert_eq!(Currency::free_balance(&11), 0); + assert_eq!(member_delegation(11), StakingMock::minimum_nominator_bond()); assert_eq!( PoolMembers::<Runtime>::get(11).unwrap(), PoolMember { @@ -4762,6 +4739,7 @@ mod create { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Created { depositor: 11, pool_id: 2 }, Event::Bonded { member: 11, pool_id: 2, bonded: 10, joined: true } ] @@ -4851,7 +4829,7 @@ mod create { 789 )); - assert_eq!(Currency::free_balance(&11), 0); + assert_eq!(member_delegation(11), StakingMock::minimum_nominator_bond()); // delete the initial pool created, then pool_Id `1` will be free assert_noop!( @@ -4892,6 +4870,7 @@ fn set_claim_permission_works() { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true }, ] ); @@ -4907,6 +4886,14 @@ fn set_claim_permission_works() { ClaimPermission::Permissioned )); + assert_eq!( + pool_events_since_last_call(), + vec![Event::MemberClaimPermissionUpdated { + member: 11, + permission: ClaimPermission::Permissioned + },] + ); + // then assert_eq!(ClaimPermissions::<Runtime>::get(11), ClaimPermission::Permissioned); }); @@ -4946,10 +4933,21 @@ mod nominate { assert_ok!(Pools::nominate(RuntimeOrigin::signed(900), 1, vec![21])); assert_eq!(Nominations::get().unwrap(), vec![21]); + // Check event + System::assert_last_event(tests::RuntimeEvent::Pools(Event::PoolNominationMade { + pool_id: 1, + caller: 900, + })); + // Nominator can nominate assert_ok!(Pools::nominate(RuntimeOrigin::signed(901), 1, vec![31])); assert_eq!(Nominations::get().unwrap(), vec![31]); + System::assert_last_event(tests::RuntimeEvent::Pools(Event::PoolNominationMade { + pool_id: 1, + caller: 901, + })); + // Can't nominate for a pool that doesn't exist assert_noop!( Pools::nominate(RuntimeOrigin::signed(902), 123, vec![21]), @@ -4986,6 +4984,7 @@ mod set_state { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::StateChanged { pool_id: 1, new_state: PoolState::Blocked } ] ); @@ -5014,16 +5013,9 @@ mod set_state { // surpassed. Making this pool destroyable by anyone. StakingMock::slash_by(1, 10); - // in mock we are using transfer stake which implies slash is greedy. Extrinsic to - // apply pending slash should fail. - assert_noop!( - Pools::apply_slash(RuntimeOrigin::signed(11), 10), - Error::<Runtime>::NotSupported - ); - - // pending slash api should return zero as well. - assert_eq!(Pools::api_pool_pending_slash(1), 0); - assert_eq!(Pools::api_member_pending_slash(10), 0); + // pending slash is correct. + assert_eq!(Pools::api_pool_pending_slash(1), 10); + assert_eq!(Pools::api_member_pending_slash(10), 10); // When assert_ok!(Pools::set_state(RuntimeOrigin::signed(11), 1, PoolState::Destroying)); @@ -5069,10 +5061,20 @@ mod set_metadata { assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(900), 1, vec![1, 1])); assert_eq!(Metadata::<Runtime>::get(1), vec![1, 1]); + System::assert_last_event(tests::RuntimeEvent::Pools(Event::MetadataUpdated { + pool_id: 1, + caller: 900, + })); + // bouncer can set metadata assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(902), 1, vec![2, 2])); assert_eq!(Metadata::<Runtime>::get(1), vec![2, 2]); + System::assert_last_event(tests::RuntimeEvent::Pools(Event::MetadataUpdated { + pool_id: 1, + caller: 902, + })); + // Depositor can't set metadata assert_noop!( Pools::set_metadata(RuntimeOrigin::signed(10), 1, vec![3, 3]), @@ -5131,8 +5133,18 @@ mod set_configs { assert_eq!(MaxPoolMembersPerPool::<Runtime>::get(), Some(5)); assert_eq!(GlobalMaxCommission::<Runtime>::get(), Some(Perbill::from_percent(6))); + // Check events + System::assert_last_event(tests::RuntimeEvent::Pools(Event::GlobalParamsUpdated { + min_join_bond: 1, + min_create_bond: 2, + max_pools: Some(3), + max_members: Some(4), + max_members_per_pool: Some(5), + global_max_commission: Some(Perbill::from_percent(6)), + })); + // Noop does nothing - assert_storage_noop!(assert_ok!(Pools::set_configs( + assert_ok!(Pools::set_configs( RuntimeOrigin::signed(42), ConfigOp::Noop, ConfigOp::Noop, @@ -5140,7 +5152,23 @@ mod set_configs { ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Noop, - ))); + )); + + assert_eq!(MinJoinBond::<Runtime>::get(), 1); + assert_eq!(MinCreateBond::<Runtime>::get(), 2); + assert_eq!(MaxPools::<Runtime>::get(), Some(3)); + assert_eq!(MaxPoolMembers::<Runtime>::get(), Some(4)); + assert_eq!(MaxPoolMembersPerPool::<Runtime>::get(), Some(5)); + assert_eq!(GlobalMaxCommission::<Runtime>::get(), Some(Perbill::from_percent(6))); + + System::assert_last_event(tests::RuntimeEvent::Pools(Event::GlobalParamsUpdated { + min_join_bond: 1, + min_create_bond: 2, + max_pools: Some(3), + max_members: Some(4), + max_members_per_pool: Some(5), + global_max_commission: Some(Perbill::from_percent(6)), + })); // Removing works assert_ok!(Pools::set_configs( @@ -5158,6 +5186,15 @@ mod set_configs { assert_eq!(MaxPoolMembers::<Runtime>::get(), None); assert_eq!(MaxPoolMembersPerPool::<Runtime>::get(), None); assert_eq!(GlobalMaxCommission::<Runtime>::get(), None); + + System::assert_last_event(tests::RuntimeEvent::Pools(Event::GlobalParamsUpdated { + min_join_bond: 0, + min_create_bond: 0, + max_pools: None, + max_members: None, + max_members_per_pool: None, + global_max_commission: None, + })); }); } } @@ -5175,13 +5212,13 @@ mod bond_extra { // given assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10); assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 10); - assert_eq!(Currency::free_balance(&10), 100); + assert_eq!(member_delegation(10), 10); // when assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10))); // then - assert_eq!(Currency::free_balance(&10), 90); + assert_eq!(member_delegation(10), 10 + 10); assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 20); assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 20); @@ -5190,6 +5227,7 @@ mod bond_extra { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false } ] ); @@ -5198,7 +5236,7 @@ mod bond_extra { assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(20))); // then - assert_eq!(Currency::free_balance(&10), 70); + assert_eq!(member_delegation(10), 20 + 20); assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 40); assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 40); @@ -5221,13 +5259,15 @@ mod bond_extra { // given assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10); assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 10); - assert_eq!(Currency::free_balance(&10), 35); + // 10 has delegated 10 tokens to the pool. + assert_eq!(member_delegation(10), 10); // when assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::Rewards)); // then - assert_eq!(Currency::free_balance(&10), 35); + // delegator balance is increased by the claimable reward. + assert_eq!(member_delegation(10), 10 + claimable_reward); assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10 + claimable_reward); assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 10 + claimable_reward); @@ -5236,6 +5276,7 @@ mod bond_extra { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::PaidOut { member: 10, pool_id: 1, payout: claimable_reward }, Event::Bonded { member: 10, @@ -5264,8 +5305,8 @@ mod bond_extra { assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 20); assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30); - assert_eq!(Currency::free_balance(&10), 35); - assert_eq!(Currency::free_balance(&20), 20); + assert_eq!(member_delegation(10), 10); + assert_eq!(member_delegation(20), 20); assert_eq!(TotalValueLocked::<T>::get(), 30); // when @@ -5273,7 +5314,7 @@ mod bond_extra { assert_eq!(Currency::free_balance(&default_reward_account()), 7); // then - assert_eq!(Currency::free_balance(&10), 35); + assert_eq!(member_delegation(10), 10 + 1); assert_eq!(TotalValueLocked::<T>::get(), 31); // 10's share of the reward is 1/3, since they gave 10/30 of the total shares. @@ -5284,11 +5325,11 @@ mod bond_extra { assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(20), BondExtra::Rewards)); // then - assert_eq!(Currency::free_balance(&20), 20); assert_eq!(TotalValueLocked::<T>::get(), 33); // 20's share of the rewards is the other 2/3 of the rewards, since they have 20/30 of // the shares + assert_eq!(member_delegation(20), 20 + 2); assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 20 + 2); assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30 + 3); @@ -5297,6 +5338,7 @@ mod bond_extra { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::PaidOut { member: 10, pool_id: 1, payout: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 1, joined: false }, @@ -5320,8 +5362,8 @@ mod bond_extra { assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10); assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 20); assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30); - assert_eq!(Currency::free_balance(&10), 35); - assert_eq!(Currency::free_balance(&20), 20); + assert_eq!(member_delegation(10), 10); + assert_eq!(member_delegation(20), 20); // Permissioned by default assert_noop!( @@ -5337,7 +5379,7 @@ mod bond_extra { assert_eq!(Currency::free_balance(&default_reward_account()), 7); // then - assert_eq!(Currency::free_balance(&10), 35); + assert_eq!(member_delegation(10), 10 + 1); assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10 + 1); assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30 + 1); @@ -5355,7 +5397,7 @@ mod bond_extra { )); // then - assert_eq!(Currency::free_balance(&20), 12); + assert_eq!(member_delegation(20), 20 + 10); assert_eq!(Currency::free_balance(&default_reward_account()), 5); assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 30); assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 41); @@ -5440,6 +5482,7 @@ mod update_roles { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::RolesUpdated { root: Some(5), bouncer: Some(7), nominator: Some(6) } ] ); @@ -5553,7 +5596,8 @@ mod reward_counter_precision { pool_id: 1, bonded: 1173908528796953165005, joined: true, - } + }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, ] ); @@ -5586,7 +5630,8 @@ mod reward_counter_precision { pool_events_since_last_call(), vec![ Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { member: 10, pool_id: 1, bonded: 10000000000000, joined: true } + Event::Bonded { member: 10, pool_id: 1, bonded: 10000000000000, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, ] ); @@ -5625,7 +5670,8 @@ mod reward_counter_precision { pool_id: 1, bonded: 12_968_712_300_500_000_000, joined: true, - } + }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, ] ); @@ -5691,7 +5737,8 @@ mod reward_counter_precision { pool_id: 1, bonded: 12_968_712_300_500_000_000, joined: true, - } + }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, ] ); @@ -5726,7 +5773,8 @@ mod reward_counter_precision { pool_id: 1, bonded: 2500000000000000000, joined: true, - } + }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, ] ); @@ -5794,7 +5842,8 @@ mod reward_counter_precision { pool_id: 1, bonded: 2500000000000000000, joined: true, - } + }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, ] ); @@ -5862,6 +5911,7 @@ mod commission { vec![ Event::Created { depositor: 10, pool_id }, Event::Bonded { member: 10, pool_id, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::PoolCommissionUpdated { pool_id, current: Some((Perbill::from_percent(50), root)) @@ -6155,6 +6205,7 @@ mod commission { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::PoolCommissionUpdated { pool_id: 1, current: Some((Perbill::from_percent(10), 900)) @@ -6177,6 +6228,7 @@ mod commission { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, ] ); @@ -6464,6 +6516,7 @@ mod commission { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::PoolMaxCommissionUpdated { pool_id: 1, max_commission: Perbill::from_percent(80) @@ -6531,6 +6584,7 @@ mod commission { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::PoolCommissionChangeRateUpdated { pool_id: 1, change_rate: CommissionChangeRate { @@ -6691,6 +6745,7 @@ mod commission { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::PoolCommissionUpdated { pool_id: 1, current: Some((Perbill::from_percent(10), 900)) @@ -6827,6 +6882,7 @@ mod commission { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::PoolCommissionChangeRateUpdated { pool_id: 1, change_rate: CommissionChangeRate { @@ -6860,6 +6916,7 @@ mod commission { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::PoolCommissionUpdated { pool_id: 1, current: Some((Perbill::from_percent(33), 2)) @@ -6950,6 +7007,7 @@ mod commission { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::PoolCommissionUpdated { pool_id: 1, current: Some((Perbill::from_percent(10), 2)) @@ -7016,6 +7074,7 @@ mod commission { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::PoolCommissionUpdated { pool_id: 1, current: Some((Perbill::from_percent(10), 2)) @@ -7075,6 +7134,7 @@ mod commission { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::PoolCommissionUpdated { pool_id: 1, current: Some((Perbill::from_percent(100), 2)) @@ -7120,6 +7180,7 @@ mod commission { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, ] ); @@ -7180,6 +7241,7 @@ mod commission { vec![ Event::Created { depositor: 10, pool_id }, Event::Bonded { member: 10, pool_id, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::PoolCommissionUpdated { pool_id, current: Some((Perbill::from_percent(50), 900)) @@ -7362,6 +7424,7 @@ mod commission { vec![ Event::Created { depositor: 10, pool_id }, Event::Bonded { member: 10, pool_id, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, ] ); @@ -7417,6 +7480,7 @@ mod slash { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::MetadataUpdated { pool_id: 1, caller: 900 }, Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true }, ] ); @@ -7472,11 +7536,28 @@ mod chill { // root can chill and re-nominate assert_ok!(Pools::chill(RuntimeOrigin::signed(900), 1)); + // Check that chill now emits an event + System::assert_last_event(tests::RuntimeEvent::Pools(Event::PoolNominatorChilled { + pool_id: 1, + caller: 900, + })); assert_ok!(Pools::nominate(RuntimeOrigin::signed(900), 1, vec![31])); + System::assert_last_event(tests::RuntimeEvent::Pools(Event::PoolNominationMade { + pool_id: 1, + caller: 900, + })); // nominator can chill and re-nominate assert_ok!(Pools::chill(RuntimeOrigin::signed(901), 1)); + System::assert_last_event(tests::RuntimeEvent::Pools(Event::PoolNominatorChilled { + pool_id: 1, + caller: 901, + })); assert_ok!(Pools::nominate(RuntimeOrigin::signed(901), 1, vec![31])); + System::assert_last_event(tests::RuntimeEvent::Pools(Event::PoolNominationMade { + pool_id: 1, + caller: 901, + })); // if `depositor` stake is less than the `MinimumNominatorBond`, then this call // becomes permissionless; @@ -7487,63 +7568,3 @@ mod chill { }) } } - -// the test mock is using `TransferStake` and so `DelegateStake` is not tested here. Extrinsics -// meant for `DelegateStake` should be gated. -// -// `DelegateStake` tests are in `pallet-nomination-pools-test-delegate-stake`. Since we support both -// strategies currently, we keep these tests as it is but in future we may remove `TransferStake` -// completely. -mod delegate_stake { - use super::*; - #[test] - fn delegation_specific_calls_are_gated() { - ExtBuilder::default().with_check(0).build_and_execute(|| { - // Given - Currency::set_balance(&11, ExistentialDeposit::get() + 2); - assert!(!PoolMembers::<Runtime>::contains_key(11)); - - // When - assert_ok!(Pools::join(RuntimeOrigin::signed(11), 2, 1)); - - // Then - assert_eq!( - pool_events_since_last_call(), - vec![ - Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true }, - ] - ); - - assert_eq!( - PoolMembers::<Runtime>::get(11).unwrap(), - PoolMember::<Runtime> { pool_id: 1, points: 2, ..Default::default() } - ); - - // ensure pool 1 cannot be migrated. - assert!(!Pools::api_pool_needs_delegate_migration(1)); - assert_noop!( - Pools::migrate_pool_to_delegate_stake(RuntimeOrigin::signed(10), 1), - Error::<Runtime>::NotSupported - ); - - // members cannot be migrated either. - assert!(!Pools::api_member_needs_delegate_migration(10)); - assert_noop!( - Pools::migrate_delegation(RuntimeOrigin::signed(10), 11), - Error::<Runtime>::NotSupported - ); - - // Given - // The bonded balance is slashed in half - StakingMock::slash_by(1, 6); - - // since slash is greedy with `TransferStake`, `apply_slash` should not work either. - assert_noop!( - Pools::apply_slash(RuntimeOrigin::signed(10), 11), - Error::<Runtime>::NotSupported - ); - }); - } -} diff --git a/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml b/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml index 70e1591409b8d90549fcc86b9b636ea31dbb80c0..62c2fb625fc4f3e1c4a7252efa7750ec263dd0e8 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml +++ b/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml @@ -19,23 +19,22 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = ["derive"], workspace = true, default-features = true } scale-info = { features = ["derive"], workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } sp-staking = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } -frame-support = { features = ["experimental"], workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } +frame-support = { features = ["experimental"], workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } +pallet-bags-list = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } -pallet-staking = { workspace = true, default-features = true } pallet-delegated-staking = { workspace = true, default-features = true } -pallet-bags-list = { workspace = true, default-features = true } -pallet-staking-reward-curve = { workspace = true, default-features = true } pallet-nomination-pools = { workspace = true, default-features = true } +pallet-staking = { workspace = true, default-features = true } +pallet-staking-reward-curve = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } log = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } 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 cc6335959ab738115c88c4878bc3dc2d8bb6db81..b43a41cd0f980dbe19916e104a8d3b51551e2154 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs @@ -21,7 +21,10 @@ mod mock; use frame_support::{ assert_noop, assert_ok, hypothetically, - traits::{fungible::InspectHold, Currency}, + traits::{ + fungible::{InspectHold, Mutate}, + Currency, + }, }; use mock::*; use pallet_nomination_pools::{ @@ -59,6 +62,7 @@ fn pool_lifecycle_e2e() { vec![ PoolsEvent::Created { depositor: 10, pool_id: 1 }, PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true }, + PoolsEvent::PoolNominationMade { pool_id: 1, caller: 10 }, ] ); @@ -177,7 +181,10 @@ fn pool_lifecycle_e2e() { ); assert_eq!( pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 50, balance: 50, era: 6 }] + vec![ + PoolsEvent::PoolNominatorChilled { pool_id: 1, caller: 10 }, + PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 50, balance: 50, era: 6 } + ] ); // waiting another bonding duration: @@ -222,6 +229,7 @@ fn pool_chill_e2e() { vec![ PoolsEvent::Created { depositor: 10, pool_id: 1 }, PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true }, + PoolsEvent::PoolNominationMade { pool_id: 1, caller: 10 }, ] ); @@ -942,9 +950,13 @@ fn pool_slash_non_proportional_bonded_pool_and_chunks() { fn pool_migration_e2e() { new_test_ext().execute_with(|| { LegacyAdapter::set(true); - assert_eq!(Balances::minimum_balance(), 5); assert_eq!(CurrentEra::<T>::get(), None); + // hack: mint ED to pool so that the deprecated `TransferStake` works correctly with + // staking. + assert_eq!(Balances::minimum_balance(), 5); + assert_ok!(Balances::mint_into(&POOL1_BONDED, 5)); + // create the pool with TransferStake strategy. assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10)); assert_eq!(LastPoolId::<Runtime>::get(), 1); @@ -961,6 +973,7 @@ fn pool_migration_e2e() { vec![ PoolsEvent::Created { depositor: 10, pool_id: 1 }, PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true }, + PoolsEvent::PoolNominationMade { pool_id: 1, caller: 10 } ] ); @@ -1050,10 +1063,11 @@ fn pool_migration_e2e() { assert_eq!( delegated_staking_events_since_last_call(), + // delegated also contains the extra ED that we minted when pool was `TransferStake` . vec![DelegatedStakingEvent::Delegated { agent: POOL1_BONDED, delegator: proxy_delegator_1, - amount: 50 + 10 * 3 + amount: 50 + 10 * 3 + 5 }] ); @@ -1223,6 +1237,11 @@ fn disable_pool_operations_on_non_migrated() { assert_eq!(Balances::minimum_balance(), 5); assert_eq!(CurrentEra::<T>::get(), None); + // hack: mint ED to pool so that the deprecated `TransferStake` works correctly with + // staking. + assert_eq!(Balances::minimum_balance(), 5); + assert_ok!(Balances::mint_into(&POOL1_BONDED, 5)); + // create the pool with TransferStake strategy. assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10)); assert_eq!(LastPoolId::<Runtime>::get(), 1); @@ -1239,6 +1258,7 @@ fn disable_pool_operations_on_non_migrated() { vec![ PoolsEvent::Created { depositor: 10, pool_id: 1 }, PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true }, + PoolsEvent::PoolNominationMade { pool_id: 1, caller: 10 } ] ); @@ -1331,11 +1351,12 @@ fn disable_pool_operations_on_non_migrated() { assert_ok!(Pools::migrate_pool_to_delegate_stake(RuntimeOrigin::signed(10), 1)); assert_eq!( delegated_staking_events_since_last_call(), + // delegated also contains the extra ED that we minted when pool was `TransferStake` . vec![DelegatedStakingEvent::Delegated { agent: POOL1_BONDED, delegator: DelegatedStaking::generate_proxy_delegator(Agent::from(POOL1_BONDED)) .get(), - amount: 50 + 10 + amount: 50 + 10 + 5 },] ); 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 d1bc4ef8ff281986532d069292b2fed7785e6ca4..7aa8019b9c42c957454806431663a41347151cfd 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs @@ -15,6 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Disable warnings for `TransferStake` being deprecated. +#![allow(deprecated)] + use frame_election_provider_support::VoteWeight; use frame_support::{ assert_ok, derive_impl, @@ -92,6 +95,7 @@ parameter_types! { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Runtime { + type OldCurrency = Balances; type Currency = Balances; type UnixTime = pallet_timestamp::Pallet<Self>; type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>; @@ -310,6 +314,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let _ = pallet_balances::GenesisConfig::<Runtime> { balances: vec![(10, 100), (20, 100), (21, 100), (22, 100)], + ..Default::default() } .assimilate_storage(&mut storage) .unwrap(); diff --git a/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml b/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml deleted file mode 100644 index 7398404c23515e763f061a9963797e57ece05772..0000000000000000000000000000000000000000 --- a/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "pallet-nomination-pools-test-transfer-stake" -version = "1.0.0" -authors.workspace = true -edition.workspace = true -license = "Apache-2.0" -homepage.workspace = true -repository.workspace = true -description = "FRAME nomination pools pallet tests with the staking pallet" -publish = false - -[lints] -workspace = true - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dev-dependencies] -codec = { features = ["derive"], workspace = true, default-features = true } -scale-info = { features = ["derive"], workspace = true, default-features = true } - -sp-runtime = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } -sp-staking = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } - -frame-system = { workspace = true, default-features = true } -frame-support = { workspace = true, default-features = true } -frame-election-provider-support = { workspace = true, default-features = true } - -pallet-timestamp = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } -pallet-staking = { workspace = true, default-features = true } -pallet-bags-list = { workspace = true, default-features = true } -pallet-staking-reward-curve = { workspace = true, default-features = true } -pallet-nomination-pools = { workspace = true, default-features = true } - -sp-tracing = { workspace = true, default-features = true } -log = { workspace = true, default-features = true } diff --git a/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs b/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs deleted file mode 100644 index cc39cfee91c80d95db2b069023991f7647eb3b79..0000000000000000000000000000000000000000 --- a/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs +++ /dev/null @@ -1,912 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#![cfg(test)] - -mod mock; - -use frame_support::{assert_noop, assert_ok, traits::Currency}; -use mock::*; -use pallet_nomination_pools::{ - BondExtra, BondedPools, Error as PoolsError, Event as PoolsEvent, LastPoolId, PoolMember, - PoolMembers, PoolState, -}; -use pallet_staking::{ - CurrentEra, Error as StakingError, Event as StakingEvent, Payee, RewardDestination, -}; -use sp_runtime::{bounded_btree_map, traits::Zero}; - -#[test] -fn pool_lifecycle_e2e() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::minimum_balance(), 5); - assert_eq!(CurrentEra::<T>::get(), None); - - // create the pool, we know this has id 1. - assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10)); - assert_eq!(LastPoolId::<Runtime>::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 }, - ] - ); - - // have two members join - assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); - assert_ok!(Pools::join(RuntimeOrigin::signed(21), 10, 1)); - - assert_eq!( - staking_events_since_last_call(), - vec![ - StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, - 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 }, - PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true }, - ] - ); - - // pool goes into destroying - assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying)); - - // depositor cannot unbond yet. - assert_noop!( - Pools::unbond(RuntimeOrigin::signed(10), 10, 50), - PoolsError::<Runtime>::MinimumBondNotMet, - ); - - // now the members want to unbond. - assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10)); - - assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras.len(), 1); - assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 0); - assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().unbonding_eras.len(), 1); - assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().points, 0); - - 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::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, - PoolsEvent::Unbonded { member: 20, pool_id: 1, points: 10, balance: 10, era: 3 }, - PoolsEvent::Unbonded { member: 21, pool_id: 1, points: 10, balance: 10, era: 3 }, - ] - ); - - // depositor cannot still unbond - assert_noop!( - Pools::unbond(RuntimeOrigin::signed(10), 10, 50), - PoolsError::<Runtime>::MinimumBondNotMet, - ); - - for e in 1..BondingDuration::get() { - CurrentEra::<Runtime>::set(Some(e)); - assert_noop!( - Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0), - PoolsError::<Runtime>::CannotWithdrawAny - ); - } - - // members are now unlocked. - CurrentEra::<Runtime>::set(Some(BondingDuration::get())); - - // depositor cannot still unbond - assert_noop!( - Pools::unbond(RuntimeOrigin::signed(10), 10, 50), - PoolsError::<Runtime>::MinimumBondNotMet, - ); - - // but members can now withdraw. - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0)); - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0)); - assert!(PoolMembers::<Runtime>::get(20).is_none()); - assert!(PoolMembers::<Runtime>::get(21).is_none()); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 20 },] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Withdrawn { member: 20, pool_id: 1, points: 10, balance: 10 }, - 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, released_balance: 0 }, - ] - ); - - // as soon as all members have left, the depositor can try to unbond, but since the - // min-nominator intention is set, they must chill first. - assert_noop!( - Pools::unbond(RuntimeOrigin::signed(10), 10, 50), - pallet_staking::Error::<Runtime>::InsufficientBond - ); - - assert_ok!(Pools::chill(RuntimeOrigin::signed(10), 1)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 50)); - - assert_eq!( - staking_events_since_last_call(), - vec![ - StakingEvent::Chilled { stash: POOL1_BONDED }, - StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 50 }, - ] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 50, balance: 50, era: 6 }] - ); - - // waiting another bonding duration: - CurrentEra::<Runtime>::set(Some(BondingDuration::get() * 2)); - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 1)); - - // pools is fully destroyed now. - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 50 },] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Withdrawn { member: 10, pool_id: 1, points: 50, balance: 50 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, - PoolsEvent::Destroyed { pool_id: 1 } - ] - ); - }) -} - -#[test] -fn destroy_pool_with_erroneous_consumer() { - new_test_ext().execute_with(|| { - // create the pool, we know this has id 1. - assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10)); - assert_eq!(LastPoolId::<Runtime>::get(), 1); - - // expect consumers on pool account to be 2 (staking lock and an explicit inc by staking). - assert_eq!(frame_system::Pallet::<T>::consumers(&POOL1_BONDED), 2); - - // increment consumer by 1 reproducing the erroneous consumer bug. - // refer https://github.com/paritytech/polkadot-sdk/issues/4440. - assert_ok!(frame_system::Pallet::<T>::inc_consumers(&POOL1_BONDED)); - assert_eq!(frame_system::Pallet::<T>::consumers(&POOL1_BONDED), 3); - - // 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 }, - ] - ); - - // pool goes into destroying - assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying)); - - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying },] - ); - - // move to era 1 - CurrentEra::<Runtime>::set(Some(1)); - - // depositor need to chill before unbonding - assert_noop!( - Pools::unbond(RuntimeOrigin::signed(10), 10, 50), - pallet_staking::Error::<Runtime>::InsufficientBond - ); - - assert_ok!(Pools::chill(RuntimeOrigin::signed(10), 1)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 50)); - - assert_eq!( - staking_events_since_last_call(), - vec![ - StakingEvent::Chilled { stash: POOL1_BONDED }, - StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 50 }, - ] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { - member: 10, - pool_id: 1, - points: 50, - balance: 50, - era: 1 + 3 - }] - ); - - // waiting bonding duration: - CurrentEra::<Runtime>::set(Some(1 + 3)); - // this should work even with an extra consumer count on pool account. - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 1)); - - // pools is fully destroyed now. - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 50 },] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Withdrawn { member: 10, pool_id: 1, points: 50, balance: 50 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, - PoolsEvent::Destroyed { pool_id: 1 } - ] - ); - }) -} - -#[test] -fn pool_chill_e2e() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::minimum_balance(), 5); - assert_eq!(CurrentEra::<T>::get(), None); - - // create the pool, we know this has id 1. - assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10)); - assert_eq!(LastPoolId::<Runtime>::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 }, - ] - ); - - // have two members join - assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); - assert_ok!(Pools::join(RuntimeOrigin::signed(21), 10, 1)); - - assert_eq!( - staking_events_since_last_call(), - vec![ - StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, - 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 }, - PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true }, - ] - ); - - // in case depositor does not have more than `MinNominatorBond` staked, we can end up in - // situation where a member unbonding would cause pool balance to drop below - // `MinNominatorBond` and hence not allowed. This can happen if the `MinNominatorBond` is - // increased after the pool is created. - assert_ok!(Staking::set_staking_configs( - RuntimeOrigin::root(), - pallet_staking::ConfigOp::Set(55), // minimum nominator bond - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - )); - - // members can unbond as long as total stake of the pool is above min nominator bond - assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10),); - assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras.len(), 1); - assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 0); - - // this member cannot unbond since it will cause `pool stake < MinNominatorBond` - assert_noop!( - Pools::unbond(RuntimeOrigin::signed(21), 21, 10), - StakingError::<Runtime>::InsufficientBond, - ); - - // members can call `chill` permissionlessly now - assert_ok!(Pools::chill(RuntimeOrigin::signed(20), 1)); - - // now another member can unbond. - assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10)); - assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().unbonding_eras.len(), 1); - assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().points, 0); - - // nominator can not resume nomination until depositor have enough stake - assert_noop!( - Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]), - PoolsError::<Runtime>::MinimumBondNotMet, - ); - - // other members joining pool does not affect the depositor's ability to resume nomination - assert_ok!(Pools::join(RuntimeOrigin::signed(22), 10, 1)); - - assert_noop!( - Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]), - PoolsError::<Runtime>::MinimumBondNotMet, - ); - - // depositor can bond extra stake - assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10))); - - // `chill` can not be called permissionlessly anymore - assert_noop!( - Pools::chill(RuntimeOrigin::signed(20), 1), - PoolsError::<Runtime>::NotNominator, - ); - - // now nominator can resume nomination - assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3])); - - // skip to make the unbonding period end. - CurrentEra::<Runtime>::set(Some(BondingDuration::get())); - - // members can now withdraw. - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0)); - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0)); - - assert_eq!( - staking_events_since_last_call(), - vec![ - StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, - StakingEvent::Chilled { stash: POOL1_BONDED }, - StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, - StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, // other member bonding - StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, // depositor bond extra - StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 20 }, - ] - ); - }) -} - -#[test] -fn pool_slash_e2e() { - new_test_ext().execute_with(|| { - ExistentialDeposit::set(1); - assert_eq!(Balances::minimum_balance(), 1); - assert_eq!(CurrentEra::<T>::get(), None); - - // create the pool, we know this has id 1. - assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10)); - assert_eq!(LastPoolId::<Runtime>::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: 10, pool_id: 1 }, - PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, - ] - ); - - assert_eq!( - Payee::<Runtime>::get(POOL1_BONDED), - Some(RewardDestination::Account(POOL1_REWARD)) - ); - - // have two members join - assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1)); - assert_ok!(Pools::join(RuntimeOrigin::signed(21), 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: 20, pool_id: 1, bonded: 20, joined: true }, - PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 20, joined: true }, - ] - ); - - // now let's progress a bit. - CurrentEra::<Runtime>::set(Some(1)); - - // 20 / 80 of the total funds are unlocked, and safe from any further slash. - assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 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: 10, pool_id: 1, balance: 10, points: 10, era: 4 }, - PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 4 } - ] - ); - - CurrentEra::<Runtime>::set(Some(2)); - - // note: depositor cannot fully unbond at this point. - // these funds will still get slashed. - assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10)); - - assert_eq!( - staking_events_since_last_call(), - vec![ - StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, - 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: 10, pool_id: 1, balance: 10, points: 10, era: 5 }, - PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 5 }, - PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 10, points: 10, era: 5 }, - ] - ); - - // At this point, 20 are safe from slash, 30 are unlocking but vulnerable to slash, and - // another 30 are active and vulnerable to slash. Let's slash half of them. - pallet_staking::slashing::do_slash::<Runtime>( - &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![ - // 30 has been slashed to 15 (15 slash) - PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 5, balance: 15 }, - // 30 has been slashed to 15 (15 slash) - PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 } - ] - ); - - CurrentEra::<Runtime>::set(Some(3)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10)); - - assert_eq!( - PoolMembers::<Runtime>::get(21).unwrap(), - PoolMember { - pool_id: 1, - points: 0, - last_recorded_reward_counter: Zero::zero(), - // the 10 points unlocked just now correspond to 5 points in the unbond pool. - unbonding_eras: bounded_btree_map!(5 => 10, 6 => 5) - } - ); - 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: 21, pool_id: 1, balance: 5, points: 5, era: 6 }] - ); - - // now we start withdrawing. we do it all at once, at era 6 where 20 and 21 are fully free. - CurrentEra::<Runtime>::set(Some(6)); - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0)); - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0)); - - assert_eq!( - pool_events_since_last_call(), - 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, 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, released_balance: 0 } - ] - ); - assert_eq!( - staking_events_since_last_call(), - // a 10 (un-slashed) + 10/2 (slashed) balance from 10 has also been unlocked - vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 15 + 10 + 15 }] - ); - - // now, finally, we can unbond the depositor further than their current limit. - assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 20)); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, - PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10, era: 9 } - ] - ); - - CurrentEra::<Runtime>::set(Some(9)); - assert_eq!( - PoolMembers::<Runtime>::get(10).unwrap(), - PoolMember { - pool_id: 1, - points: 0, - last_recorded_reward_counter: Zero::zero(), - unbonding_eras: bounded_btree_map!(4 => 10, 5 => 10, 9 => 10) - } - ); - // withdraw the depositor, they should lose 12 balance in total due to slash. - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 10 }] - ); - assert_eq!( - 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, released_balance: 0 }, - PoolsEvent::Destroyed { pool_id: 1 } - ] - ); - }); -} - -#[test] -fn pool_slash_proportional() { - // a typical example where 3 pool members unbond in era 99, 100, and 101, and a slash that - // happened in era 100 should only affect the latter two. - new_test_ext().execute_with(|| { - ExistentialDeposit::set(1); - BondingDuration::set(28); - assert_eq!(Balances::minimum_balance(), 1); - assert_eq!(CurrentEra::<T>::get(), None); - - // create the pool, we know this has id 1. - assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10)); - assert_eq!(LastPoolId::<T>::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: 10, pool_id: 1 }, - PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, - ] - ); - - // have two members join - let bond = 20; - assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1)); - assert_ok!(Pools::join(RuntimeOrigin::signed(21), bond, 1)); - assert_ok!(Pools::join(RuntimeOrigin::signed(22), bond, 1)); - - assert_eq!( - staking_events_since_last_call(), - vec![ - StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }, - StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }, - StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }, - ] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }, - PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: bond, joined: true }, - PoolsEvent::Bonded { member: 22, pool_id: 1, bonded: bond, joined: true }, - ] - ); - - // now let's progress a lot. - CurrentEra::<T>::set(Some(99)); - - // and unbond - assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond)); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { - member: 20, - pool_id: 1, - balance: bond, - points: bond, - era: 127 - }] - ); - - CurrentEra::<T>::set(Some(100)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, bond)); - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { - member: 21, - pool_id: 1, - balance: bond, - points: bond, - era: 128 - }] - ); - - CurrentEra::<T>::set(Some(101)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(22), 22, bond)); - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { - member: 22, - pool_id: 1, - balance: bond, - points: bond, - era: 129 - }] - ); - - // Apply a slash that happened in era 100. This is typically applied with a delay. - // Of the total 100, 50 is slashed. - assert_eq!(BondedPools::<T>::get(1).unwrap().points, 40); - pallet_staking::slashing::do_slash::<Runtime>( - &POOL1_BONDED, - 50, - &mut Default::default(), - &mut Default::default(), - 100, - ); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 50 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - // This era got slashed 12.5, which rounded up to 13. - PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 128, balance: 7 }, - // This era got slashed 12 instead of 12.5 because an earlier chunk got 0.5 more - // slashed, and 12 is all the remaining slash - PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 129, balance: 8 }, - // Bonded pool got slashed for 25, remaining 15 in it. - PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 } - ] - ); - }); -} - -#[test] -fn pool_slash_non_proportional_only_bonded_pool() { - // A typical example where a pool member unbonds in era 99, and they can get away with a slash - // that happened in era 100, as long as the pool has enough active bond to cover the slash. If - // everything else in the slashing/staking system works, this should always be the case. - // Nonetheless, `ledger.slash` has been written such that it will slash greedily from any chunk - // if it runs out of chunks that it thinks should be affected by the slash. - new_test_ext().execute_with(|| { - ExistentialDeposit::set(1); - BondingDuration::set(28); - assert_eq!(Balances::minimum_balance(), 1); - assert_eq!(CurrentEra::<T>::get(), None); - - // create the pool, we know this has id 1. - assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10)); - 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: 10, pool_id: 1 }, - PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, - ] - ); - - // have two members join - let bond = 20; - assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1)); - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }] - ); - - // progress and unbond. - CurrentEra::<T>::set(Some(99)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond)); - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { - member: 20, - pool_id: 1, - balance: bond, - points: bond, - era: 127 - }] - ); - - // slash for 30. This will be deducted only from the bonded pool. - CurrentEra::<T>::set(Some(100)); - assert_eq!(BondedPools::<T>::get(1).unwrap().points, 40); - pallet_staking::slashing::do_slash::<Runtime>( - &POOL1_BONDED, - 30, - &mut Default::default(), - &mut Default::default(), - 100, - ); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 30 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::PoolSlashed { pool_id: 1, balance: 10 }] - ); - }); -} - -#[test] -fn pool_slash_non_proportional_bonded_pool_and_chunks() { - // An uncommon example where even though some funds are unlocked such that they should not be - // affected by a slash, we still slash out of them. This should not happen at all. If a - // nomination has unbonded, from the next era onwards, their exposure will drop, so if an era - // happens in that era, then their share of that slash should naturally be less, such that only - // their active ledger stake is enough to compensate it. - new_test_ext().execute_with(|| { - ExistentialDeposit::set(1); - BondingDuration::set(28); - assert_eq!(Balances::minimum_balance(), 1); - assert_eq!(CurrentEra::<T>::get(), None); - - // create the pool, we know this has id 1. - assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10)); - 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: 10, pool_id: 1 }, - PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, - ] - ); - - // have two members join - let bond = 20; - assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1)); - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }] - ); - - // progress and unbond. - CurrentEra::<T>::set(Some(99)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond)); - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { - member: 20, - pool_id: 1, - balance: bond, - points: bond, - era: 127 - }] - ); - - // slash 50. This will be deducted only from the bonded pool and one of the unbonding pools. - CurrentEra::<T>::set(Some(100)); - assert_eq!(BondedPools::<T>::get(1).unwrap().points, 40); - pallet_staking::slashing::do_slash::<Runtime>( - &POOL1_BONDED, - 50, - &mut Default::default(), - &mut Default::default(), - 100, - ); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 50 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - // out of 20, 10 was taken. - PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 127, balance: 10 }, - // out of 40, all was taken. - PoolsEvent::PoolSlashed { pool_id: 1, balance: 0 } - ] - ); - }); -} diff --git a/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs b/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs deleted file mode 100644 index d913c5fe6948cb5a0fae1d5ccf8e533e5ddb8cc7..0000000000000000000000000000000000000000 --- a/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs +++ /dev/null @@ -1,231 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use frame_election_provider_support::VoteWeight; -use frame_support::{ - assert_ok, derive_impl, - pallet_prelude::*, - parameter_types, - traits::{ConstU64, ConstU8, VariantCountOf}, - PalletId, -}; -use sp_runtime::{ - traits::{Convert, IdentityLookup}, - BuildStorage, FixedU128, Perbill, -}; - -type AccountId = u128; -type BlockNumber = u64; -type Balance = u128; - -pub(crate) type T = Runtime; - -pub(crate) const POOL1_BONDED: AccountId = 20318131474730217858575332831085u128; -pub(crate) const POOL1_REWARD: AccountId = 20397359637244482196168876781421u128; - -#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] -impl frame_system::Config for Runtime { - type AccountId = AccountId; - type Lookup = IdentityLookup<Self::AccountId>; - type Block = Block; - type AccountData = pallet_balances::AccountData<Balance>; -} - -impl pallet_timestamp::Config for Runtime { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = ConstU64<5>; - type WeightInfo = (); -} - -parameter_types! { - pub static ExistentialDeposit: Balance = 5; -} - -#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] -impl pallet_balances::Config for Runtime { - type Balance = Balance; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type FreezeIdentifier = RuntimeFreezeReason; - type MaxFreezes = VariantCountOf<RuntimeFreezeReason>; - type RuntimeFreezeReason = RuntimeFreezeReason; -} - -pallet_staking_reward_curve::build! { - const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!( - min_inflation: 0_025_000, - max_inflation: 0_100_000, - ideal_stake: 0_500_000, - falloff: 0_050_000, - max_piece_count: 40, - test_precision: 0_005_000, - ); -} - -parameter_types! { - pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; - pub static BondingDuration: u32 = 3; -} - -#[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] -impl pallet_staking::Config for Runtime { - type Currency = Balances; - type UnixTime = pallet_timestamp::Pallet<Self>; - type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>; - type BondingDuration = BondingDuration; - type EraPayout = pallet_staking::ConvertCurve<RewardCurve>; - type ElectionProvider = - frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; - type GenesisElectionProvider = Self::ElectionProvider; - type VoterList = VoterList; - type TargetList = pallet_staking::UseValidatorsMap<Self>; - type EventListeners = Pools; - type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; -} - -parameter_types! { - pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; -} - -type VoterBagsListInstance = pallet_bags_list::Instance1; -impl pallet_bags_list::Config<VoterBagsListInstance> for Runtime { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = (); - type BagThresholds = BagThresholds; - type ScoreProvider = Staking; - type Score = VoteWeight; -} - -pub struct BalanceToU256; -impl Convert<Balance, sp_core::U256> for BalanceToU256 { - fn convert(n: Balance) -> sp_core::U256 { - n.into() - } -} - -pub struct U256ToBalance; -impl Convert<sp_core::U256, Balance> for U256ToBalance { - fn convert(n: sp_core::U256) -> Balance { - n.try_into().unwrap() - } -} - -parameter_types! { - pub const PostUnbondingPoolsWindow: u32 = 10; - pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); -} - -impl pallet_nomination_pools::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = (); - type Currency = Balances; - type RuntimeFreezeReason = RuntimeFreezeReason; - type RewardCounter = FixedU128; - type BalanceToU256 = BalanceToU256; - type U256ToBalance = U256ToBalance; - type StakeAdapter = pallet_nomination_pools::adapter::TransferStake<Self, Staking>; - type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; - type MaxMetadataLen = ConstU32<256>; - type MaxUnbonding = ConstU32<8>; - type MaxPointsToBalance = ConstU8<10>; - type PalletId = PoolsPalletId; - type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>; -} - -type Block = frame_system::mocking::MockBlock<Runtime>; - -frame_support::construct_runtime!( - pub enum Runtime { - System: frame_system, - Timestamp: pallet_timestamp, - Balances: pallet_balances, - Staking: pallet_staking, - VoterList: pallet_bags_list::<Instance1>, - Pools: pallet_nomination_pools, - } -); - -pub fn new_test_ext() -> sp_io::TestExternalities { - sp_tracing::try_init_simple(); - let mut storage = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap(); - let _ = pallet_nomination_pools::GenesisConfig::<Runtime> { - min_join_bond: 2, - min_create_bond: 2, - max_pools: Some(3), - max_members_per_pool: Some(5), - max_members: Some(3 * 5), - global_max_commission: Some(Perbill::from_percent(90)), - } - .assimilate_storage(&mut storage) - .unwrap(); - - let _ = pallet_balances::GenesisConfig::<Runtime> { - balances: vec![(10, 100), (20, 100), (21, 100), (22, 100)], - } - .assimilate_storage(&mut storage) - .unwrap(); - - let mut ext = sp_io::TestExternalities::from(storage); - - ext.execute_with(|| { - // for events to be deposited. - frame_system::Pallet::<Runtime>::set_block_number(1); - - // set some limit for nominations. - assert_ok!(Staking::set_staking_configs( - RuntimeOrigin::root(), - pallet_staking::ConfigOp::Set(10), // minimum nominator bond - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - )); - }); - - ext -} - -parameter_types! { - static ObservedEventsPools: usize = 0; - static ObservedEventsStaking: usize = 0; - static ObservedEventsBalances: usize = 0; -} - -pub(crate) fn pool_events_since_last_call() -> Vec<pallet_nomination_pools::Event<Runtime>> { - let events = System::events() - .into_iter() - .map(|r| r.event) - .filter_map(|e| if let RuntimeEvent::Pools(inner) = e { Some(inner) } else { None }) - .collect::<Vec<_>>(); - let already_seen = ObservedEventsPools::get(); - ObservedEventsPools::set(events.len()); - events.into_iter().skip(already_seen).collect() -} - -pub(crate) fn staking_events_since_last_call() -> Vec<pallet_staking::Event<Runtime>> { - let events = System::events() - .into_iter() - .map(|r| r.event) - .filter_map(|e| if let RuntimeEvent::Staking(inner) = e { Some(inner) } else { None }) - .collect::<Vec<_>>(); - let already_seen = ObservedEventsStaking::get(); - ObservedEventsStaking::set(events.len()); - events.into_iter().skip(already_seen).collect() -} diff --git a/substrate/frame/offences/Cargo.toml b/substrate/frame/offences/Cargo.toml index 98c320e1f8088d824793501023dc0fad57f347f4..221a4918a511f21bcf865e4de0295ea0c26c712a 100644 --- a/substrate/frame/offences/Cargo.toml +++ b/substrate/frame/offences/Cargo.toml @@ -17,12 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { optional = true, workspace = true, default-features = true } -frame-support = { workspace = true } -frame-system = { workspace = true } -pallet-balances = { workspace = true } sp-runtime = { workspace = true } sp-staking = { workspace = true } @@ -37,7 +36,6 @@ std = [ "frame-support/std", "frame-system/std", "log/std", - "pallet-balances/std", "scale-info/std", "serde", "sp-core/std", @@ -48,13 +46,11 @@ std = [ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "sp-staking/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "pallet-balances/try-runtime", "sp-runtime/try-runtime", ] diff --git a/substrate/frame/offences/benchmarking/Cargo.toml b/substrate/frame/offences/benchmarking/Cargo.toml index 28c7895180c4b4ee3516c9c5a20059608844e596..76b167ebdb332f907c342c115e86961625070197 100644 --- a/substrate/frame/offences/benchmarking/Cargo.toml +++ b/substrate/frame/offences/benchmarking/Cargo.toml @@ -17,11 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { workspace = true } frame-election-provider-support = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-babe = { workspace = true } pallet-balances = { workspace = true } pallet-grandpa = { workspace = true } @@ -29,9 +29,9 @@ pallet-im-online = { workspace = true } pallet-offences = { workspace = true } pallet-session = { workspace = true } pallet-staking = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } sp-staking = { workspace = true } -log = { workspace = true } [dev-dependencies] pallet-staking-reward-curve = { workspace = true, default-features = true } diff --git a/substrate/frame/offences/benchmarking/src/inner.rs b/substrate/frame/offences/benchmarking/src/inner.rs index 573114de074224db6e0e8d562d069852ca066092..3d3cd470bc24cc3fd63eaf09a52b99ee5eb7b33e 100644 --- a/substrate/frame/offences/benchmarking/src/inner.rs +++ b/substrate/frame/offences/benchmarking/src/inner.rs @@ -19,7 +19,7 @@ use alloc::{vec, vec::Vec}; -use frame_benchmarking::v1::{account, benchmarks}; +use frame_benchmarking::v2::*; use frame_support::traits::Get; use frame_system::{Config as SystemConfig, Pallet as System, RawOrigin}; @@ -144,7 +144,7 @@ fn create_offender<T: Config>(n: u32, nominators: u32) -> Result<Offender<T>, &' fn make_offenders<T: Config>( num_offenders: u32, num_nominators: u32, -) -> Result<(Vec<IdentificationTuple<T>>, Vec<Offender<T>>), &'static str> { +) -> Result<Vec<IdentificationTuple<T>>, &'static str> { Staking::<T>::new_session(0); let mut offenders = vec![]; @@ -167,21 +167,46 @@ fn make_offenders<T: Config>( .expect("failed to convert validator id to full identification") }) .collect::<Vec<IdentificationTuple<T>>>(); - Ok((id_tuples, offenders)) + Ok(id_tuples) } -benchmarks! { - where_clause { - where +#[cfg(test)] +fn assert_all_slashes_applied<T>(offender_count: usize) +where + T: Config, + <T as frame_system::Config>::RuntimeEvent: TryInto<pallet_staking::Event<T>>, + <T as frame_system::Config>::RuntimeEvent: TryInto<pallet_balances::Event<T>>, + <T as frame_system::Config>::RuntimeEvent: TryInto<pallet_offences::Event>, + <T as frame_system::Config>::RuntimeEvent: TryInto<frame_system::Event<T>>, +{ + // make sure that all slashes have been applied + // deposit to reporter + reporter account endowed. + assert_eq!(System::<T>::read_events_for_pallet::<pallet_balances::Event<T>>().len(), 2); + // (n nominators + one validator) * slashed + Slash Reported + assert_eq!( + System::<T>::read_events_for_pallet::<pallet_staking::Event<T>>().len(), + 1 * (offender_count + 1) as usize + 1 + ); + // offence + assert_eq!(System::<T>::read_events_for_pallet::<pallet_offences::Event>().len(), 1); + // reporter new account + assert_eq!(System::<T>::read_events_for_pallet::<frame_system::Event<T>>().len(), 1); +} + +#[benchmarks( + where <T as frame_system::Config>::RuntimeEvent: TryInto<pallet_staking::Event<T>>, <T as frame_system::Config>::RuntimeEvent: TryInto<pallet_balances::Event<T>>, <T as frame_system::Config>::RuntimeEvent: TryInto<pallet_offences::Event>, <T as frame_system::Config>::RuntimeEvent: TryInto<frame_system::Event<T>>, - } - - report_offence_grandpa { - let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::<T>::get()); - +)] +mod benchmarks { + use super::*; + + #[benchmark] + pub fn report_offence_grandpa( + n: Linear<0, { MAX_NOMINATORS.min(MaxNominationsOf::<T>::get()) }>, + ) -> Result<(), BenchmarkError> { // for grandpa equivocation reports the number of reporters // and offenders is always 1 let reporters = vec![account("reporter", 1, SEED)]; @@ -189,7 +214,7 @@ benchmarks! { // make sure reporters actually get rewarded Staking::<T>::set_slash_reward_fraction(Perbill::one()); - let (mut offenders, raw_offenders) = make_offenders::<T>(1, n)?; + let mut offenders = make_offenders::<T>(1, n)?; let validator_set_count = Session::<T>::validators().len() as u32; let offence = GrandpaEquivocationOffence { @@ -199,28 +224,24 @@ benchmarks! { offender: T::convert(offenders.pop().unwrap()), }; assert_eq!(System::<T>::event_count(), 0); - }: { - let _ = Offences::<T>::report_offence(reporters, offence); - } - verify { + + #[block] + { + let _ = Offences::<T>::report_offence(reporters, offence); + } + #[cfg(test)] { - // make sure that all slashes have been applied - // (n nominators + one validator) * (slashed + unlocked) + deposit to reporter + reporter - // account endowed + some funds rescinded from issuance. - assert_eq!(System::<T>::read_events_for_pallet::<pallet_balances::Event<T>>().len(), 2 * (n + 1) as usize + 3); - // (n nominators + one validator) * slashed + Slash Reported - assert_eq!(System::<T>::read_events_for_pallet::<pallet_staking::Event<T>>().len(), 1 * (n + 1) as usize + 1); - // offence - assert_eq!(System::<T>::read_events_for_pallet::<pallet_offences::Event>().len(), 1); - // reporter new account - assert_eq!(System::<T>::read_events_for_pallet::<frame_system::Event<T>>().len(), 1); + assert_all_slashes_applied::<T>(n as usize); } - } - report_offence_babe { - let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::<T>::get()); + Ok(()) + } + #[benchmark] + fn report_offence_babe( + n: Linear<0, { MAX_NOMINATORS.min(MaxNominationsOf::<T>::get()) }>, + ) -> Result<(), BenchmarkError> { // for babe equivocation reports the number of reporters // and offenders is always 1 let reporters = vec![account("reporter", 1, SEED)]; @@ -228,7 +249,7 @@ benchmarks! { // make sure reporters actually get rewarded Staking::<T>::set_slash_reward_fraction(Perbill::one()); - let (mut offenders, raw_offenders) = make_offenders::<T>(1, n)?; + let mut offenders = make_offenders::<T>(1, n)?; let validator_set_count = Session::<T>::validators().len() as u32; let offence = BabeEquivocationOffence { @@ -238,23 +259,17 @@ benchmarks! { offender: T::convert(offenders.pop().unwrap()), }; assert_eq!(System::<T>::event_count(), 0); - }: { - let _ = Offences::<T>::report_offence(reporters, offence); - } - verify { + + #[block] + { + let _ = Offences::<T>::report_offence(reporters, offence); + } #[cfg(test)] { - // make sure that all slashes have been applied - // (n nominators + one validator) * (slashed + unlocked) + deposit to reporter + reporter - // account endowed + some funds rescinded from issuance. - assert_eq!(System::<T>::read_events_for_pallet::<pallet_balances::Event<T>>().len(), 2 * (n + 1) as usize + 3); - // (n nominators + one validator) * slashed + Slash Reported - assert_eq!(System::<T>::read_events_for_pallet::<pallet_staking::Event<T>>().len(), 1 * (n + 1) as usize + 1); - // offence - assert_eq!(System::<T>::read_events_for_pallet::<pallet_offences::Event>().len(), 1); - // reporter new account - assert_eq!(System::<T>::read_events_for_pallet::<frame_system::Event<T>>().len(), 1); + assert_all_slashes_applied::<T>(n as usize); } + + Ok(()) } impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); diff --git a/substrate/frame/offences/benchmarking/src/mock.rs b/substrate/frame/offences/benchmarking/src/mock.rs index efaec49a65b3d2e905deb5891d127ec6934287db..3c81f2a664e32138f5fee5f43aab30e1f7ce6819 100644 --- a/substrate/frame/offences/benchmarking/src/mock.rs +++ b/substrate/frame/offences/benchmarking/src/mock.rs @@ -29,7 +29,7 @@ use frame_system as system; use pallet_session::historical as pallet_session_historical; use sp_runtime::{ testing::{Header, UintAuthorityId}, - BuildStorage, Perbill, + BuildStorage, KeyTypeId, Perbill, }; type AccountId = u64; @@ -66,7 +66,8 @@ sp_runtime::impl_opaque_keys! { pub struct TestSessionHandler; impl pallet_session::SessionHandler<AccountId> for TestSessionHandler { - const KEY_TYPE_IDS: &'static [sp_runtime::KeyTypeId] = &[]; + // corresponds to the opaque key id above + const KEY_TYPE_IDS: &'static [KeyTypeId] = &[KeyTypeId([100u8, 117u8, 109u8, 121u8])]; fn on_genesis_session<Ks: sp_runtime::traits::OpaqueKeys>(_validators: &[(AccountId, Ks)]) {} @@ -124,6 +125,7 @@ impl onchain::Config for OnChainSeqPhragmen { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Test { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = <Self as pallet_balances::Config>::Balance; type UnixTime = pallet_timestamp::Pallet<Self>; diff --git a/substrate/frame/paged-list/Cargo.toml b/substrate/frame/paged-list/Cargo.toml index a680139c5fdc2011b37fccaf33eb1bb4c12d1470..07755c351e288206380b0e3192041717fd32be29 100644 --- a/substrate/frame/paged-list/Cargo.toml +++ b/substrate/frame/paged-list/Cargo.toml @@ -19,21 +19,19 @@ codec = { features = ["derive"], workspace = true } docify = { workspace = true } scale-info = { features = ["derive"], workspace = true } -frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -sp-runtime = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-metadata-ir = { optional = true, workspace = true } +sp-runtime = { workspace = true } [features] default = ["std"] std = [ "codec/std", - "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "scale-info/std", @@ -44,7 +42,6 @@ std = [ ] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", diff --git a/substrate/frame/paged-list/fuzzer/Cargo.toml b/substrate/frame/paged-list/fuzzer/Cargo.toml index d0108254ed2dfb958fa887aaf7a24cd130ce7a67..32535093b59805859a2b8d49f18412e4216f0d89 100644 --- a/substrate/frame/paged-list/fuzzer/Cargo.toml +++ b/substrate/frame/paged-list/fuzzer/Cargo.toml @@ -18,8 +18,13 @@ path = "src/paged_list.rs" [dependencies] arbitrary = { workspace = true } +frame = { workspace = true, features = ["runtime"] } honggfuzz = { workspace = true } - -frame-support = { features = ["std"], workspace = true } -sp-io = { features = ["std"], workspace = true } pallet-paged-list = { features = ["std"], workspace = true } + +[features] +default = ["std"] +std = [ + "frame/std", + "pallet-paged-list/std", +] diff --git a/substrate/frame/paged-list/fuzzer/src/paged_list.rs b/substrate/frame/paged-list/fuzzer/src/paged_list.rs index 43b797eee6bfb088412c90964d8f3332b0c039a7..f0f914de14229cf721cb8abaedebdf99a4578449 100644 --- a/substrate/frame/paged-list/fuzzer/src/paged_list.rs +++ b/substrate/frame/paged-list/fuzzer/src/paged_list.rs @@ -30,9 +30,12 @@ use arbitrary::Arbitrary; use honggfuzz::fuzz; -use frame_support::{storage::StorageList, StorageNoopGuard}; +use frame::{ + prelude::*, runtime::prelude::storage::storage_noop_guard::StorageNoopGuard, + testing_prelude::TestExternalities, +}; + use pallet_paged_list::mock::{PagedList as List, *}; -use sp_io::TestExternalities; type Meta = MetaOf<Test, ()>; fn main() { diff --git a/substrate/frame/parameters/Cargo.toml b/substrate/frame/parameters/Cargo.toml index a97ba1172a503df2efb2f820ef8995f8ff9f4a65..dda218b618c458fe2f06ce2d76fa6af3551564c4 100644 --- a/substrate/frame/parameters/Cargo.toml +++ b/substrate/frame/parameters/Cargo.toml @@ -9,22 +9,22 @@ edition.workspace = true [dependencies] codec = { features = ["max-encoded-len"], workspace = true } -scale-info = { features = ["derive"], workspace = true } +docify = { workspace = true } paste = { workspace = true } +scale-info = { features = ["derive"], workspace = true } serde = { features = ["derive"], optional = true, workspace = true, default-features = true } -docify = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } frame-support = { features = ["experimental"], workspace = true } frame-system = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } [dev-dependencies] +pallet-balances = { features = ["std"], workspace = true, default-features = true } +pallet-example-basic = { features = ["std"], workspace = true, default-features = true } sp-core = { features = ["std"], workspace = true, default-features = true } sp-io = { features = ["std"], workspace = true, default-features = true } -pallet-example-basic = { features = ["std"], workspace = true, default-features = true } -pallet-balances = { features = ["std"], workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/parameters/src/tests/mock.rs b/substrate/frame/parameters/src/tests/mock.rs index 53a3b3e394c4b7c5d8410ba6e1af758d78cbf8ae..8d6f7d25ceac5e02ee4a067d77e4b8a5f88c1599 100644 --- a/substrate/frame/parameters/src/tests/mock.rs +++ b/substrate/frame/parameters/src/tests/mock.rs @@ -75,7 +75,7 @@ pub mod dynamic_params { } #[dynamic_pallet_params] - #[codec(index = 3)] + #[codec(index = 4)] pub mod somE_weird_SPElLInG_s { #[codec(index = 0)] pub static V: u64 = 0; diff --git a/substrate/frame/preimage/Cargo.toml b/substrate/frame/preimage/Cargo.toml index 1356ac403d38c1a4adc410ab8e792051c00c3c0c..fae6627b6315e36538443f57be5531e244c8470a 100644 --- a/substrate/frame/preimage/Cargo.toml +++ b/substrate/frame/preimage/Cargo.toml @@ -13,14 +13,14 @@ workspace = true [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { optional = true, workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -log = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } diff --git a/substrate/frame/preimage/src/benchmarking.rs b/substrate/frame/preimage/src/benchmarking.rs index 3d0c5b900579d7f84d887d1ca8fdc686adb1f3a9..ea635bf3ef772d6efa63f240733d81b3ca719fb3 100644 --- a/substrate/frame/preimage/src/benchmarking.rs +++ b/substrate/frame/preimage/src/benchmarking.rs @@ -17,14 +17,13 @@ //! Preimage pallet benchmarking. -use super::*; use alloc::vec; -use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller, BenchmarkError}; +use frame_benchmarking::v2::*; use frame_support::assert_ok; use frame_system::RawOrigin; use sp_runtime::traits::Bounded; -use crate::Pallet as Preimage; +use crate::*; fn funded_account<T: Config>() -> T::AccountId { let caller: T::AccountId = whitelisted_caller(); @@ -43,206 +42,225 @@ fn sized_preimage_and_hash<T: Config>(size: u32) -> (Vec<u8>, T::Hash) { (preimage, hash) } -benchmarks! { +fn insert_old_unrequested<T: Config>(s: u32) -> <T as frame_system::Config>::Hash { + let acc = account("old", s, 0); + T::Currency::make_free_balance_be(&acc, BalanceOf::<T>::max_value() / 2u32.into()); + + // The preimage size does not matter here as it is not touched. + let preimage = s.to_le_bytes(); + let hash = <T as frame_system::Config>::Hashing::hash(&preimage[..]); + + #[allow(deprecated)] + StatusFor::<T>::insert( + &hash, + OldRequestStatus::Unrequested { deposit: (acc, 123u32.into()), len: preimage.len() as u32 }, + ); + hash +} + +#[benchmarks] +mod benchmarks { + use super::*; + // Expensive note - will reserve. - note_preimage { - let s in 0 .. MAX_SIZE; + #[benchmark] + fn note_preimage(s: Linear<0, MAX_SIZE>) { let caller = funded_account::<T>(); let (preimage, hash) = sized_preimage_and_hash::<T>(s); - }: _(RawOrigin::Signed(caller), preimage) - verify { - assert!(Preimage::<T>::have_preimage(&hash)); + + #[extrinsic_call] + _(RawOrigin::Signed(caller), preimage); + + assert!(Pallet::<T>::have_preimage(&hash)); } + // Cheap note - will not reserve since it was requested. - note_requested_preimage { - let s in 0 .. MAX_SIZE; + #[benchmark] + fn note_requested_preimage(s: Linear<0, MAX_SIZE>) { let caller = funded_account::<T>(); let (preimage, hash) = sized_preimage_and_hash::<T>(s); - assert_ok!(Preimage::<T>::request_preimage( + assert_ok!(Pallet::<T>::request_preimage( T::ManagerOrigin::try_successful_origin() .expect("ManagerOrigin has no successful origin required for the benchmark"), hash, )); - }: note_preimage(RawOrigin::Signed(caller), preimage) - verify { - assert!(Preimage::<T>::have_preimage(&hash)); + + #[extrinsic_call] + note_preimage(RawOrigin::Signed(caller), preimage); + + assert!(Pallet::<T>::have_preimage(&hash)); } + // Cheap note - will not reserve since it's the manager. - note_no_deposit_preimage { - let s in 0 .. MAX_SIZE; + #[benchmark] + fn note_no_deposit_preimage(s: Linear<0, MAX_SIZE>) { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (preimage, hash) = sized_preimage_and_hash::<T>(s); - assert_ok!(Preimage::<T>::request_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - hash, - )); - }: note_preimage<T::RuntimeOrigin>( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - preimage - ) verify { - assert!(Preimage::<T>::have_preimage(&hash)); + assert_ok!(Pallet::<T>::request_preimage(o.clone(), hash,)); + + #[extrinsic_call] + note_preimage(o as T::RuntimeOrigin, preimage); + + assert!(Pallet::<T>::have_preimage(&hash)); } // Expensive unnote - will unreserve. - unnote_preimage { + #[benchmark] + fn unnote_preimage() { let caller = funded_account::<T>(); let (preimage, hash) = preimage_and_hash::<T>(); - assert_ok!(Preimage::<T>::note_preimage(RawOrigin::Signed(caller.clone()).into(), preimage)); - }: _(RawOrigin::Signed(caller), hash) - verify { - assert!(!Preimage::<T>::have_preimage(&hash)); + assert_ok!(Pallet::<T>::note_preimage(RawOrigin::Signed(caller.clone()).into(), preimage)); + + #[extrinsic_call] + _(RawOrigin::Signed(caller), hash); + + assert!(!Pallet::<T>::have_preimage(&hash)); } + // Cheap unnote - will not unreserve since there's no deposit held. - unnote_no_deposit_preimage { + #[benchmark] + fn unnote_no_deposit_preimage() { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (preimage, hash) = preimage_and_hash::<T>(); - assert_ok!(Preimage::<T>::note_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - preimage, - )); - }: unnote_preimage<T::RuntimeOrigin>( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - hash - ) verify { - assert!(!Preimage::<T>::have_preimage(&hash)); + assert_ok!(Pallet::<T>::note_preimage(o.clone(), preimage,)); + + #[extrinsic_call] + unnote_preimage(o as T::RuntimeOrigin, hash); + + assert!(!Pallet::<T>::have_preimage(&hash)); } // Expensive request - will unreserve the noter's deposit. - request_preimage { + #[benchmark] + fn request_preimage() { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (preimage, hash) = preimage_and_hash::<T>(); let noter = funded_account::<T>(); - assert_ok!(Preimage::<T>::note_preimage(RawOrigin::Signed(noter.clone()).into(), preimage)); - }: _<T::RuntimeOrigin>( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - hash - ) verify { - let ticket = TicketOf::<T>::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_ok!(Pallet::<T>::note_preimage(RawOrigin::Signed(noter.clone()).into(), preimage)); + + #[extrinsic_call] + _(o as T::RuntimeOrigin, hash); + + let ticket = + TicketOf::<T>::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::<T>::get(&hash), Some(s)); } + // Cheap request - would unreserve the deposit but none was held. - request_no_deposit_preimage { + #[benchmark] + fn request_no_deposit_preimage() { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (preimage, hash) = preimage_and_hash::<T>(); - assert_ok!(Preimage::<T>::note_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - preimage, - )); - }: request_preimage<T::RuntimeOrigin>( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - hash - ) verify { - let s = RequestStatus::Requested { maybe_ticket: None, count: 2, maybe_len: Some(MAX_SIZE) }; + assert_ok!(Pallet::<T>::note_preimage(o.clone(), preimage,)); + + #[extrinsic_call] + request_preimage(o as T::RuntimeOrigin, hash); + + let s = + RequestStatus::Requested { maybe_ticket: None, count: 2, maybe_len: Some(MAX_SIZE) }; assert_eq!(RequestStatusFor::<T>::get(&hash), Some(s)); } + // Cheap request - the preimage is not yet noted, so deposit to unreserve. - request_unnoted_preimage { + #[benchmark] + fn request_unnoted_preimage() { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (_, hash) = preimage_and_hash::<T>(); - }: request_preimage<T::RuntimeOrigin>( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - hash - ) verify { + + #[extrinsic_call] + request_preimage(o as T::RuntimeOrigin, hash); + let s = RequestStatus::Requested { maybe_ticket: None, count: 1, maybe_len: None }; assert_eq!(RequestStatusFor::<T>::get(&hash), Some(s)); } + // Cheap request - the preimage is already requested, so just a counter bump. - request_requested_preimage { + #[benchmark] + fn request_requested_preimage() { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (_, hash) = preimage_and_hash::<T>(); - assert_ok!(Preimage::<T>::request_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - hash, - )); - }: request_preimage<T::RuntimeOrigin>( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - hash - ) verify { + assert_ok!(Pallet::<T>::request_preimage(o.clone(), hash,)); + + #[extrinsic_call] + request_preimage(o as T::RuntimeOrigin, hash); + let s = RequestStatus::Requested { maybe_ticket: None, count: 2, maybe_len: None }; assert_eq!(RequestStatusFor::<T>::get(&hash), Some(s)); } // Expensive unrequest - last reference and it's noted, so will destroy the preimage. - unrequest_preimage { + #[benchmark] + fn unrequest_preimage() { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (preimage, hash) = preimage_and_hash::<T>(); - assert_ok!(Preimage::<T>::request_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - hash, - )); - assert_ok!(Preimage::<T>::note_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - preimage, - )); - }: _<T::RuntimeOrigin>( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - hash - ) verify { + assert_ok!(Pallet::<T>::request_preimage(o.clone(), hash,)); + assert_ok!(Pallet::<T>::note_preimage(o.clone(), preimage)); + + #[extrinsic_call] + _(o as T::RuntimeOrigin, hash); + assert_eq!(RequestStatusFor::<T>::get(&hash), None); } + // Cheap unrequest - last reference, but it's not noted. - unrequest_unnoted_preimage { + #[benchmark] + fn unrequest_unnoted_preimage() { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (_, hash) = preimage_and_hash::<T>(); - assert_ok!(Preimage::<T>::request_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - hash, - )); - }: unrequest_preimage<T::RuntimeOrigin>( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - hash - ) verify { + assert_ok!(Pallet::<T>::request_preimage(o.clone(), hash,)); + + #[extrinsic_call] + unrequest_preimage(o as T::RuntimeOrigin, hash); + assert_eq!(RequestStatusFor::<T>::get(&hash), None); } + // Cheap unrequest - not the last reference. - unrequest_multi_referenced_preimage { + #[benchmark] + fn unrequest_multi_referenced_preimage() { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (_, hash) = preimage_and_hash::<T>(); - assert_ok!(Preimage::<T>::request_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - hash, - )); - assert_ok!(Preimage::<T>::request_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - hash, - )); - }: unrequest_preimage<T::RuntimeOrigin>( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - hash - ) verify { + assert_ok!(Pallet::<T>::request_preimage(o.clone(), hash,)); + assert_ok!(Pallet::<T>::request_preimage(o.clone(), hash,)); + + #[extrinsic_call] + unrequest_preimage(o as T::RuntimeOrigin, hash); + let s = RequestStatus::Requested { maybe_ticket: None, count: 1, maybe_len: None }; assert_eq!(RequestStatusFor::<T>::get(&hash), Some(s)); } - ensure_updated { - let n in 1..MAX_HASH_UPGRADE_BULK_COUNT; - + #[benchmark] + fn ensure_updated(n: Linear<1, MAX_HASH_UPGRADE_BULK_COUNT>) { let caller = funded_account::<T>(); let hashes = (0..n).map(|i| insert_old_unrequested::<T>(i)).collect::<Vec<_>>(); - }: _(RawOrigin::Signed(caller), hashes) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(caller), hashes); + assert_eq!(RequestStatusFor::<T>::iter_keys().count(), n as usize); #[allow(deprecated)] let c = StatusFor::<T>::iter_keys().count(); assert_eq!(c, 0); } - impl_benchmark_test_suite!(Preimage, crate::mock::new_test_ext(), crate::mock::Test); -} - -fn insert_old_unrequested<T: Config>(s: u32) -> <T as frame_system::Config>::Hash { - let acc = account("old", s, 0); - T::Currency::make_free_balance_be(&acc, BalanceOf::<T>::max_value() / 2u32.into()); - - // The preimage size does not matter here as it is not touched. - let preimage = s.to_le_bytes(); - let hash = <T as frame_system::Config>::Hashing::hash(&preimage[..]); - - #[allow(deprecated)] - StatusFor::<T>::insert( - &hash, - OldRequestStatus::Unrequested { deposit: (acc, 123u32.into()), len: preimage.len() as u32 }, - ); - hash + impl_benchmark_test_suite! { + Pallet, + mock::new_test_ext(), + mock::Test + } } diff --git a/substrate/frame/preimage/src/lib.rs b/substrate/frame/preimage/src/lib.rs index 658e7fec534827a9f82f0756840303d20a725233..849ffddf4fb3c0c4e88fbf534a33312d0e2a83b9 100644 --- a/substrate/frame/preimage/src/lib.rs +++ b/substrate/frame/preimage/src/lib.rs @@ -236,7 +236,7 @@ pub mod pallet { Self::do_unrequest_preimage(&hash) } - /// Ensure that the a bulk of pre-images is upgraded. + /// Ensure that the bulk of pre-images is upgraded. /// /// The caller pays no fee if at least 90% of pre-images were successfully updated. #[pallet::call_index(4)] diff --git a/substrate/frame/preimage/src/mock.rs b/substrate/frame/preimage/src/mock.rs index 9c72d09cae1463395aaebe1e06f95d5e22cced9f..dec590c6a197bb40106eea78860cab62142e5bce 100644 --- a/substrate/frame/preimage/src/mock.rs +++ b/substrate/frame/preimage/src/mock.rs @@ -81,6 +81,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); let balances = pallet_balances::GenesisConfig::<Test> { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)], + ..Default::default() }; balances.assimilate_storage(&mut t).unwrap(); t.into() diff --git a/substrate/frame/preimage/src/weights.rs b/substrate/frame/preimage/src/weights.rs index edb2eed9c75a00bbfc51015110f7e1727b9c1aaf..a3aec7e7546e46d1239ef95536db961680503c40 100644 --- a/substrate/frame/preimage/src/weights.rs +++ b/substrate/frame/preimage/src/weights.rs @@ -18,27 +18,25 @@ //! Autogenerated weights for `pallet_preimage` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-11-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-11-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `runner-wiukf8gn-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 +// target/production/substrate-node // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_preimage -// --no-storage-info -// --no-median-slopes -// --no-min-squares // --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --output=./substrate/frame/preimage/src/weights.rs +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_preimage +// --chain=dev // --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/preimage/src/weights.rs // --template=./substrate/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -84,10 +82,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `7` // Estimated: `6012` - // Minimum execution time: 51_981_000 picoseconds. - Weight::from_parts(52_228_000, 6012) - // Standard Error: 6 - .saturating_add(Weight::from_parts(2_392, 0).saturating_mul(s.into())) + // Minimum execution time: 51_305_000 picoseconds. + Weight::from_parts(51_670_000, 6012) + // Standard Error: 5 + .saturating_add(Weight::from_parts(2_337, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -102,10 +100,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 15_835_000 picoseconds. - Weight::from_parts(16_429_000, 3556) - // Standard Error: 8 - .saturating_add(Weight::from_parts(2_647, 0).saturating_mul(s.into())) + // Minimum execution time: 16_204_000 picoseconds. + Weight::from_parts(16_613_000, 3556) + // Standard Error: 6 + .saturating_add(Weight::from_parts(2_503, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -120,10 +118,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 15_263_000 picoseconds. - Weight::from_parts(15_578_000, 3556) - // Standard Error: 7 - .saturating_add(Weight::from_parts(2_598, 0).saturating_mul(s.into())) + // Minimum execution time: 15_118_000 picoseconds. + Weight::from_parts(15_412_000, 3556) + // Standard Error: 6 + .saturating_add(Weight::from_parts(2_411, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -139,8 +137,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `206` // Estimated: `3820` - // Minimum execution time: 64_189_000 picoseconds. - Weight::from_parts(70_371_000, 3820) + // Minimum execution time: 57_218_000 picoseconds. + Weight::from_parts(61_242_000, 3820) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -154,8 +152,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3556` - // Minimum execution time: 27_582_000 picoseconds. - Weight::from_parts(31_256_000, 3556) + // Minimum execution time: 25_140_000 picoseconds. + Weight::from_parts(27_682_000, 3556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -167,8 +165,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `150` // Estimated: `3556` - // Minimum execution time: 27_667_000 picoseconds. - Weight::from_parts(32_088_000, 3556) + // Minimum execution time: 25_296_000 picoseconds. + Weight::from_parts(27_413_000, 3556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -180,8 +178,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3556` - // Minimum execution time: 16_065_000 picoseconds. - Weight::from_parts(20_550_000, 3556) + // Minimum execution time: 15_011_000 picoseconds. + Weight::from_parts(16_524_000, 3556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -193,8 +191,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `4` // Estimated: `3556` - // Minimum execution time: 13_638_000 picoseconds. - Weight::from_parts(16_979_000, 3556) + // Minimum execution time: 14_649_000 picoseconds. + Weight::from_parts(15_439_000, 3556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -206,8 +204,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 11_383_000 picoseconds. - Weight::from_parts(12_154_000, 3556) + // Minimum execution time: 10_914_000 picoseconds. + Weight::from_parts(11_137_000, 3556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -221,8 +219,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3556` - // Minimum execution time: 22_832_000 picoseconds. - Weight::from_parts(30_716_000, 3556) + // Minimum execution time: 22_512_000 picoseconds. + Weight::from_parts(24_376_000, 3556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -234,8 +232,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 10_685_000 picoseconds. - Weight::from_parts(12_129_000, 3556) + // Minimum execution time: 10_571_000 picoseconds. + Weight::from_parts(10_855_000, 3556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -247,8 +245,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 10_394_000 picoseconds. - Weight::from_parts(10_951_000, 3556) + // Minimum execution time: 10_312_000 picoseconds. + Weight::from_parts(10_653_000, 3556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -267,10 +265,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0 + n * (227 ±0)` // Estimated: `6012 + n * (2830 ±0)` - // Minimum execution time: 62_203_000 picoseconds. - Weight::from_parts(63_735_000, 6012) - // Standard Error: 59_589 - .saturating_add(Weight::from_parts(59_482_352, 0).saturating_mul(n.into())) + // Minimum execution time: 61_990_000 picoseconds. + Weight::from_parts(62_751_000, 6012) + // Standard Error: 44_079 + .saturating_add(Weight::from_parts(57_343_378, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes((4_u64).saturating_mul(n.into()))) @@ -295,10 +293,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `7` // Estimated: `6012` - // Minimum execution time: 51_981_000 picoseconds. - Weight::from_parts(52_228_000, 6012) - // Standard Error: 6 - .saturating_add(Weight::from_parts(2_392, 0).saturating_mul(s.into())) + // Minimum execution time: 51_305_000 picoseconds. + Weight::from_parts(51_670_000, 6012) + // Standard Error: 5 + .saturating_add(Weight::from_parts(2_337, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -313,10 +311,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 15_835_000 picoseconds. - Weight::from_parts(16_429_000, 3556) - // Standard Error: 8 - .saturating_add(Weight::from_parts(2_647, 0).saturating_mul(s.into())) + // Minimum execution time: 16_204_000 picoseconds. + Weight::from_parts(16_613_000, 3556) + // Standard Error: 6 + .saturating_add(Weight::from_parts(2_503, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -331,10 +329,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 15_263_000 picoseconds. - Weight::from_parts(15_578_000, 3556) - // Standard Error: 7 - .saturating_add(Weight::from_parts(2_598, 0).saturating_mul(s.into())) + // Minimum execution time: 15_118_000 picoseconds. + Weight::from_parts(15_412_000, 3556) + // Standard Error: 6 + .saturating_add(Weight::from_parts(2_411, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -350,8 +348,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `206` // Estimated: `3820` - // Minimum execution time: 64_189_000 picoseconds. - Weight::from_parts(70_371_000, 3820) + // Minimum execution time: 57_218_000 picoseconds. + Weight::from_parts(61_242_000, 3820) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -365,8 +363,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3556` - // Minimum execution time: 27_582_000 picoseconds. - Weight::from_parts(31_256_000, 3556) + // Minimum execution time: 25_140_000 picoseconds. + Weight::from_parts(27_682_000, 3556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -378,8 +376,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `150` // Estimated: `3556` - // Minimum execution time: 27_667_000 picoseconds. - Weight::from_parts(32_088_000, 3556) + // Minimum execution time: 25_296_000 picoseconds. + Weight::from_parts(27_413_000, 3556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -391,8 +389,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3556` - // Minimum execution time: 16_065_000 picoseconds. - Weight::from_parts(20_550_000, 3556) + // Minimum execution time: 15_011_000 picoseconds. + Weight::from_parts(16_524_000, 3556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -404,8 +402,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `4` // Estimated: `3556` - // Minimum execution time: 13_638_000 picoseconds. - Weight::from_parts(16_979_000, 3556) + // Minimum execution time: 14_649_000 picoseconds. + Weight::from_parts(15_439_000, 3556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -417,8 +415,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 11_383_000 picoseconds. - Weight::from_parts(12_154_000, 3556) + // Minimum execution time: 10_914_000 picoseconds. + Weight::from_parts(11_137_000, 3556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -432,8 +430,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3556` - // Minimum execution time: 22_832_000 picoseconds. - Weight::from_parts(30_716_000, 3556) + // Minimum execution time: 22_512_000 picoseconds. + Weight::from_parts(24_376_000, 3556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -445,8 +443,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 10_685_000 picoseconds. - Weight::from_parts(12_129_000, 3556) + // Minimum execution time: 10_571_000 picoseconds. + Weight::from_parts(10_855_000, 3556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -458,8 +456,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 10_394_000 picoseconds. - Weight::from_parts(10_951_000, 3556) + // Minimum execution time: 10_312_000 picoseconds. + Weight::from_parts(10_653_000, 3556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -478,10 +476,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0 + n * (227 ±0)` // Estimated: `6012 + n * (2830 ±0)` - // Minimum execution time: 62_203_000 picoseconds. - Weight::from_parts(63_735_000, 6012) - // Standard Error: 59_589 - .saturating_add(Weight::from_parts(59_482_352, 0).saturating_mul(n.into())) + // Minimum execution time: 61_990_000 picoseconds. + Weight::from_parts(62_751_000, 6012) + // Standard Error: 44_079 + .saturating_add(Weight::from_parts(57_343_378, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes((4_u64).saturating_mul(n.into()))) diff --git a/substrate/frame/proxy/Cargo.toml b/substrate/frame/proxy/Cargo.toml index 8897c66419c7a59507ceca79cb7fe3eaffd1dfb9..3f2565abac88d2653781046d111c1f50fb757393 100644 --- a/substrate/frame/proxy/Cargo.toml +++ b/substrate/frame/proxy/Cargo.toml @@ -17,8 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["max-encoded-len"], workspace = true } +frame = { workspace = true, features = ["runtime"] } scale-info = { features = ["derive"], workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } diff --git a/substrate/frame/proxy/src/tests.rs b/substrate/frame/proxy/src/tests.rs index afc668188e6cbdff2c03bc43def84ad59e93d47f..14389b03ac7e2593bcc37188d21a8a44974cac2f 100644 --- a/substrate/frame/proxy/src/tests.rs +++ b/substrate/frame/proxy/src/tests.rs @@ -133,6 +133,7 @@ pub fn new_test_ext() -> TestState { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); pallet_balances::GenesisConfig::<Test> { balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 3)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/ranked-collective/Cargo.toml b/substrate/frame/ranked-collective/Cargo.toml index eca59cf7fc22345f723460b78fa325eff1a303fe..78a02bec8e97618f723b31560c1618ed931836e1 100644 --- a/substrate/frame/ranked-collective/Cargo.toml +++ b/substrate/frame/ranked-collective/Cargo.toml @@ -17,16 +17,16 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +impl-trait-for-tuples = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -impl-trait-for-tuples = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/recovery/Cargo.toml b/substrate/frame/recovery/Cargo.toml index 44335e8f575c879a4bd316c47507e1168221e316..4f3a734d9868f6b67334da6fd90d0ef7d9b65694 100644 --- a/substrate/frame/recovery/Cargo.toml +++ b/substrate/frame/recovery/Cargo.toml @@ -17,10 +17,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/recovery/README.md b/substrate/frame/recovery/README.md index 7e2dd7a23619ac893061d0730f7813bdc7e33e98..39f6914070464df82aaea3d69b975319a0670a33 100644 --- a/substrate/frame/recovery/README.md +++ b/substrate/frame/recovery/README.md @@ -62,12 +62,12 @@ The intended life cycle of a successful recovery takes the following steps: ### Malicious Recovery Attempts -Initializing a the recovery process for a recoverable account is open and +Initializing the recovery process for a recoverable account is open and permissionless. However, the recovery deposit is an economic deterrent that should disincentivize would-be attackers from trying to maliciously recover accounts. -The recovery deposit can always be claimed by the account which is trying to +The recovery deposit can always be claimed by the account which is trying to be recovered. In the case of a malicious recovery attempt, the account owner who still has access to their account can claim the deposit and essentially punish the malicious user. diff --git a/substrate/frame/recovery/src/lib.rs b/substrate/frame/recovery/src/lib.rs index f8622880538efceb7b377e64b9dfe26b994bd7fb..42fb641983f6308a9809ca8ec856ee2d019ab569 100644 --- a/substrate/frame/recovery/src/lib.rs +++ b/substrate/frame/recovery/src/lib.rs @@ -75,12 +75,12 @@ //! //! ### Malicious Recovery Attempts //! -//! Initializing a the recovery process for a recoverable account is open and +//! Initializing the recovery process for a recoverable account is open and //! permissionless. However, the recovery deposit is an economic deterrent that //! should disincentivize would-be attackers from trying to maliciously recover //! accounts. //! -//! The recovery deposit can always be claimed by the account which is trying to +//! The recovery deposit can always be claimed by the account which is trying //! to be recovered. In the case of a malicious recovery attempt, the account //! owner who still has access to their account can claim the deposit and //! essentially punish the malicious user. @@ -156,7 +156,10 @@ use alloc::{boxed::Box, vec::Vec}; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ - traits::{CheckedAdd, CheckedMul, Dispatchable, SaturatedConversion, StaticLookup}, + traits::{ + BlockNumberProvider, CheckedAdd, CheckedMul, Dispatchable, SaturatedConversion, + StaticLookup, + }, RuntimeDebug, }; @@ -178,11 +181,12 @@ mod mock; mod tests; pub mod weights; +type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source; type BalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance; - +type BlockNumberFromProviderOf<T> = + <<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber; type FriendsOf<T> = BoundedVec<<T as frame_system::Config>::AccountId, <T as Config>::MaxFriends>; -type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source; /// An active recovery process. #[derive(Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen)] @@ -190,7 +194,7 @@ pub struct ActiveRecovery<BlockNumber, Balance, Friends> { /// The block number when the recovery process started. created: BlockNumber, /// The amount held in reserve of the `depositor`, - /// To be returned once this recovery process is closed. + /// to be returned once this recovery process is closed. deposit: Balance, /// The friends which have vouched so far. Always sorted. friends: Friends, @@ -236,6 +240,9 @@ pub mod pallet { + GetDispatchInfo + From<frame_system::Call<Self>>; + /// Provider for the block number. Normally this is the `frame_system` pallet. + type BlockNumberProvider: BlockNumberProvider; + /// The currency mechanism. type Currency: ReservableCurrency<Self::AccountId>; @@ -339,7 +346,7 @@ pub mod pallet { _, Twox64Concat, T::AccountId, - RecoveryConfig<BlockNumberFor<T>, BalanceOf<T>, FriendsOf<T>>, + RecoveryConfig<BlockNumberFromProviderOf<T>, BalanceOf<T>, FriendsOf<T>>, >; /// Active recovery attempts. @@ -354,7 +361,7 @@ pub mod pallet { T::AccountId, Twox64Concat, T::AccountId, - ActiveRecovery<BlockNumberFor<T>, BalanceOf<T>, FriendsOf<T>>, + ActiveRecovery<BlockNumberFromProviderOf<T>, BalanceOf<T>, FriendsOf<T>>, >; /// The list of allowed proxy accounts. @@ -396,7 +403,7 @@ pub mod pallet { .map_err(|e| e.error) } - /// Allow ROOT to bypass the recovery process and set an a rescuer account + /// Allow ROOT to bypass the recovery process and set a rescuer account /// for a lost account directly. /// /// The dispatch origin for this call must be _ROOT_. @@ -445,7 +452,7 @@ pub mod pallet { origin: OriginFor<T>, friends: Vec<T::AccountId>, threshold: u16, - delay_period: BlockNumberFor<T>, + delay_period: BlockNumberFromProviderOf<T>, ) -> DispatchResult { let who = ensure_signed(origin)?; // Check account is not already set up for recovery @@ -511,7 +518,7 @@ pub mod pallet { T::Currency::reserve(&who, recovery_deposit)?; // Create an active recovery status let recovery_status = ActiveRecovery { - created: <frame_system::Pallet<T>>::block_number(), + created: T::BlockNumberProvider::current_block_number(), deposit: recovery_deposit, friends: Default::default(), }; @@ -596,7 +603,7 @@ pub mod pallet { Self::active_recovery(&account, &who).ok_or(Error::<T>::NotStarted)?; ensure!(!Proxy::<T>::contains_key(&who), Error::<T>::AlreadyProxy); // Make sure the delay period has passed - let current_block_number = <frame_system::Pallet<T>>::block_number(); + let current_block_number = T::BlockNumberProvider::current_block_number(); let recoverable_block_number = active_recovery .created .checked_add(&recovery_config.delay_period) diff --git a/substrate/frame/recovery/src/mock.rs b/substrate/frame/recovery/src/mock.rs index 8e30cbe997e17b80e8ef74a598b15343e6affdef..446d507a414c5c0f5a9178e4d90eb60c1b841dd0 100644 --- a/substrate/frame/recovery/src/mock.rs +++ b/substrate/frame/recovery/src/mock.rs @@ -20,10 +20,7 @@ use super::*; use crate as recovery; -use frame_support::{ - derive_impl, parameter_types, - traits::{OnFinalize, OnInitialize}, -}; +use frame_support::{derive_impl, parameter_types}; use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock<Test>; @@ -66,6 +63,7 @@ impl Config for Test { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type RuntimeCall = RuntimeCall; + type BlockNumberProvider = System; type Currency = Balances; type ConfigDepositBase = ConfigDepositBase; type FriendDepositFactor = FriendDepositFactor; @@ -80,19 +78,9 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); pallet_balances::GenesisConfig::<Test> { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); t.into() } - -/// Run until a particular block. -pub fn run_to_block(n: u64) { - while System::block_number() < n { - if System::block_number() > 1 { - System::on_finalize(System::block_number()); - } - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - } -} diff --git a/substrate/frame/recovery/src/tests.rs b/substrate/frame/recovery/src/tests.rs index 93df07015852e689db0ec02114642baa303ee349..97085df2ae788f34ee43a1c705563e5dd0a46cea 100644 --- a/substrate/frame/recovery/src/tests.rs +++ b/substrate/frame/recovery/src/tests.rs @@ -17,12 +17,8 @@ //! Tests for the module. -use super::*; +use crate::{mock::*, *}; use frame_support::{assert_noop, assert_ok, traits::Currency}; -use mock::{ - new_test_ext, run_to_block, Balances, BalancesCall, MaxFriends, Recovery, RecoveryCall, - RuntimeCall, RuntimeOrigin, Test, -}; use sp_runtime::{bounded_vec, traits::BadOrigin}; #[test] @@ -70,7 +66,7 @@ fn recovery_life_cycle_works() { delay_period )); // Some time has passed, and the user lost their keys! - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); // Using account 1, the user begins the recovery process to recover the lost account assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5)); // Off chain, the user contacts their friends and asks them to vouch for the recovery @@ -84,7 +80,7 @@ fn recovery_life_cycle_works() { Error::<Test>::DelayPeriod ); // We need to wait at least the delay_period number of blocks before we can recover - run_to_block(20); + System::run_to_block::<AllPalletsWithSystem>(20); assert_ok!(Recovery::claim_recovery(RuntimeOrigin::signed(1), 5)); // Account 1 can use account 5 to close the active recovery process, claiming the deposited // funds used to initiate the recovery process into account 5. @@ -128,7 +124,7 @@ fn malicious_recovery_fails() { delay_period )); // Some time has passed, and account 1 wants to try and attack this account! - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); // Using account 1, the malicious user begins the recovery process on account 5 assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5)); // Off chain, the user **tricks** their friends and asks them to vouch for the recovery @@ -144,7 +140,7 @@ fn malicious_recovery_fails() { Error::<Test>::DelayPeriod ); // Account 1 needs to wait... - run_to_block(19); + System::run_to_block::<AllPalletsWithSystem>(19); // One more block to wait! assert_noop!( Recovery::claim_recovery(RuntimeOrigin::signed(1), 5), @@ -158,7 +154,7 @@ fn malicious_recovery_fails() { // Thanks for the free money! assert_eq!(Balances::total_balance(&5), 110); // The recovery process has been closed, so account 1 can't make the claim - run_to_block(20); + System::run_to_block::<AllPalletsWithSystem>(20); assert_noop!( Recovery::claim_recovery(RuntimeOrigin::signed(1), 5), Error::<Test>::NotStarted @@ -397,7 +393,7 @@ fn claim_recovery_handles_basic_errors() { Recovery::claim_recovery(RuntimeOrigin::signed(1), 5), Error::<Test>::DelayPeriod ); - run_to_block(11); + System::run_to_block::<AllPalletsWithSystem>(11); // Cannot claim an account which has not passed the threshold number of votes assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1)); assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 1)); @@ -427,7 +423,7 @@ fn claim_recovery_works() { assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 1)); assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(4), 5, 1)); - run_to_block(11); + System::run_to_block::<AllPalletsWithSystem>(11); // Account can be recovered. assert_ok!(Recovery::claim_recovery(RuntimeOrigin::signed(1), 5)); @@ -439,7 +435,7 @@ fn claim_recovery_works() { assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 4)); assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(4), 5, 4)); - run_to_block(21); + System::run_to_block::<AllPalletsWithSystem>(21); // Account is re-recovered. assert_ok!(Recovery::claim_recovery(RuntimeOrigin::signed(4), 5)); diff --git a/substrate/frame/referenda/Cargo.toml b/substrate/frame/referenda/Cargo.toml index e9b4eb04ed51cf9ae86ef55b6f978c2aa90eac1e..0f35dc74382e3916b5a60f62f173ff6b383e82fc 100644 --- a/substrate/frame/referenda/Cargo.toml +++ b/substrate/frame/referenda/Cargo.toml @@ -20,15 +20,15 @@ assert_matches = { optional = true, workspace = true } codec = { features = [ "derive", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { features = ["derive"], optional = true, workspace = true, default-features = true } -sp-arithmetic = { workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } +sp-arithmetic = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -log = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } diff --git a/substrate/frame/referenda/src/mock.rs b/substrate/frame/referenda/src/mock.rs index c96a50af86581978e2e6ffe359a2417e4796665b..5d36ce137d46d2308166ed131cdfb8a464a1c661 100644 --- a/substrate/frame/referenda/src/mock.rs +++ b/substrate/frame/referenda/src/mock.rs @@ -219,7 +219,7 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), (6, 100)]; - pallet_balances::GenesisConfig::<Test> { balances } + pallet_balances::GenesisConfig::<Test> { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); let mut ext = sp_io::TestExternalities::new(t); diff --git a/substrate/frame/remark/Cargo.toml b/substrate/frame/remark/Cargo.toml index 487bada593cd7ebdb520ea70f535114b6467bcd3..a40b577b52ea448c707a905305998187860bd3c5 100644 --- a/substrate/frame/remark/Cargo.toml +++ b/substrate/frame/remark/Cargo.toml @@ -17,11 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 81fbbc8cf38e1a384aac69e1676788f13c4a416f..4faa9205378fed4e0f4eb550b09539951ac3989b 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -17,62 +17,59 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { features = ["derive", "max-encoded-len"], workspace = true } +derive_more = { workspace = true } environmental = { workspace = true } +ethabi = { workspace = true } +ethereum-types = { workspace = true, features = ["codec", "rlp", "serialize"] } +hex = { workspace = true } +impl-trait-for-tuples = { workspace = true } +log = { workspace = true } paste = { workspace = true } -polkavm = { version = "0.13.0", default-features = false } -bitflags = { workspace = true } -codec = { features = ["derive", "max-encoded-len"], workspace = true } +polkavm = { version = "0.19.0", default-features = false } +rlp = { workspace = true } scale-info = { features = ["derive"], workspace = true } -log = { workspace = true } serde = { features = [ "alloc", "derive", ], workspace = true, default-features = false } -impl-trait-for-tuples = { workspace = true } -rlp = { workspace = true } -derive_more = { workspace = true } -hex = { workspace = true } -jsonrpsee = { workspace = true, features = ["full"], optional = true } -ethereum-types = { workspace = true, features = ["codec", "rlp", "serialize"] } # Polkadot SDK Dependencies 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, optional = true } -pallet-revive-uapi = { workspace = true, default-features = true } -pallet-revive-proc-macro = { workspace = true, default-features = true } +pallet-revive-fixtures = { workspace = true, optional = true } +pallet-revive-proc-macro = { workspace = true } +pallet-revive-uapi = { workspace = true, features = ["scale"] } pallet-transaction-payment = { workspace = true } sp-api = { workspace = true } sp-arithmetic = { workspace = true } +sp-consensus-aura = { workspace = true, optional = true } +sp-consensus-babe = { workspace = true, optional = true } +sp-consensus-slots = { workspace = true, optional = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -sp-std = { workspace = true } -sp-weights = { workspace = true } -xcm = { workspace = true } -xcm-builder = { workspace = true } subxt-signer = { workspace = true, optional = true, features = [ "unstable-eth", ] } +xcm = { workspace = true } +xcm-builder = { workspace = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } assert_matches = { workspace = true } +hex-literal = { workspace = true } pretty_assertions = { workspace = true } -pallet-revive-fixtures = { workspace = true, default-features = true } secp256k1 = { workspace = true, features = ["recovery"] } serde_json = { workspace = true } -hex-literal = { workspace = true } # Polkadot SDK Dependencies pallet-balances = { workspace = true, default-features = true } +pallet-proxy = { workspace = true, default-features = true } +pallet-revive-fixtures = { 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 } xcm-builder = { workspace = true, default-features = true } @@ -82,14 +79,13 @@ default = ["std"] std = [ "codec/std", "environmental/std", + "ethabi/std", "ethereum-types/std", "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "hex/std", - "jsonrpsee", "log/std", - "pallet-balances?/std", "pallet-proxy/std", "pallet-revive-fixtures?/std", "pallet-timestamp/std", @@ -103,12 +99,13 @@ std = [ "serde_json/std", "sp-api/std", "sp-arithmetic/std", + "sp-consensus-aura/std", + "sp-consensus-babe/std", + "sp-consensus-slots/std", "sp-core/std", "sp-io/std", "sp-keystore/std", "sp-runtime/std", - "sp-std/std", - "sp-weights/std", "subxt-signer", "xcm-builder/std", "xcm/std", @@ -117,23 +114,23 @@ 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-revive-fixtures", "pallet-timestamp/runtime-benchmarks", "pallet-transaction-payment/runtime-benchmarks", "pallet-utility/runtime-benchmarks", + "sp-consensus-aura", + "sp-consensus-babe", + "sp-consensus-slots", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", + "xcm/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-transaction-payment/try-runtime", diff --git a/substrate/frame/revive/README.md b/substrate/frame/revive/README.md index 5352e636c2524c4fbb477880f61c64c37a640f5d..7538f77d10bc07cbed79dbc6eff82869f1a73673 100644 --- a/substrate/frame/revive/README.md +++ b/substrate/frame/revive/README.md @@ -49,29 +49,6 @@ This module executes PolkaVM smart contracts. These can potentially be written i 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, @@ -92,7 +69,7 @@ Driven by the desire to have an iterative approach in developing new contract in 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) +In order to access interfaces which don't have a stable `#[stable]` in [`runtime.rs`](src/wasm/runtime.rs) one need to set `pallet_revive::Config::UnsafeUnstableInterface` to `ConstU32<true>`. **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 diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml index 7a5452853d656d61b1950830150511c608180876..a6f25cc26f3c040ed9db6b56b9b402d8319ee3b1 100644 --- a/substrate/frame/revive/fixtures/Cargo.toml +++ b/substrate/frame/revive/fixtures/Cargo.toml @@ -5,26 +5,26 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Fixtures for testing and benchmarking" +homepage.workspace = true +repository.workspace = true + +[package.metadata.polkadot-sdk] +exclude-from-umbrella = true [lints] workspace = true [dependencies] -frame-system = { workspace = true, default-features = true, optional = true } +anyhow = { workspace = true, default-features = true, optional = true } sp-core = { workspace = true, default-features = true, optional = true } sp-io = { workspace = true, default-features = true, optional = true } -sp-runtime = { workspace = true, default-features = true, optional = true } -anyhow = { workspace = true, default-features = true, optional = true } -log = { workspace = true } [build-dependencies] -parity-wasm = { workspace = true } -tempfile = { workspace = true } -toml = { workspace = true } -polkavm-linker = { version = "0.14.0" } anyhow = { workspace = true, default-features = true } +polkavm-linker = { version = "0.19.0" } +toml = { workspace = true } [features] default = ["std"] # only when std is enabled all fixtures are available -std = ["anyhow", "frame-system", "log/std", "sp-core", "sp-io", "sp-runtime"] +std = ["anyhow", "sp-core", "sp-io"] diff --git a/substrate/frame/revive/fixtures/build.rs b/substrate/frame/revive/fixtures/build.rs index 3472e0846efddcd8857a3c20e4e96fc55857158a..eca547bc6ddd0ac6f7856a1ae60804b9013b4dda 100644 --- a/substrate/frame/revive/fixtures/build.rs +++ b/substrate/frame/revive/fixtures/build.rs @@ -20,7 +20,8 @@ use anyhow::Result; use anyhow::{bail, Context}; use std::{ - cfg, env, fs, + env, fs, + io::Write, path::{Path, PathBuf}, process::Command, }; @@ -82,7 +83,7 @@ fn create_cargo_toml<'a>( entries: impl Iterator<Item = &'a Entry>, output_dir: &Path, ) -> Result<()> { - let mut cargo_toml: toml::Value = toml::from_str(include_str!("./build/Cargo.toml"))?; + 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(), @@ -108,21 +109,24 @@ fn create_cargo_toml<'a>( let cargo_toml = toml::to_string_pretty(&cargo_toml)?; fs::write(output_dir.join("Cargo.toml"), cargo_toml.clone()) .with_context(|| format!("Failed to write {cargo_toml:?}"))?; + fs::copy( + fixtures_dir.join("build/_rust-toolchain.toml"), + output_dir.join("rust-toolchain.toml"), + ) + .context("Failed to write toolchain file")?; Ok(()) } -fn invoke_build(target: &Path, current_dir: &Path) -> Result<()> { +fn invoke_build(current_dir: &Path) -> Result<()> { let encoded_rustflags = ["-Dwarnings"].join("\x1f"); - let mut build_command = Command::new(env::var("CARGO")?); + let mut build_command = Command::new("cargo"); build_command .current_dir(current_dir) .env_clear() .env("PATH", env::var("PATH").unwrap_or_default()) .env("CARGO_ENCODED_RUSTFLAGS", encoded_rustflags) - .env("RUSTC_BOOTSTRAP", "1") .env("RUSTUP_HOME", env::var("RUSTUP_HOME").unwrap_or_default()) - .env("RUSTUP_TOOLCHAIN", env::var("RUSTUP_TOOLCHAIN").unwrap_or_default()) .args([ "build", "--release", @@ -130,7 +134,7 @@ fn invoke_build(target: &Path, current_dir: &Path) -> Result<()> { "-Zbuild-std-features=panic_immediate_abort", ]) .arg("--target") - .arg(target); + .arg(polkavm_linker::target_json_64_path().unwrap()); if let Ok(toolchain) = env::var(OVERRIDE_RUSTUP_TOOLCHAIN_ENV_VAR) { build_command.env("RUSTUP_TOOLCHAIN", &toolchain); @@ -168,7 +172,7 @@ fn write_output(build_dir: &Path, out_dir: &Path, entries: Vec<Entry>) -> Result for entry in entries { post_process( &build_dir - .join("target/riscv32emac-unknown-none-polkavm/release") + .join("target/riscv64emac-unknown-none-polkavm/release") .join(entry.name()), &out_dir.join(entry.out_filename()), )?; @@ -177,11 +181,66 @@ fn write_output(build_dir: &Path, out_dir: &Path, entries: Vec<Entry>) -> Result Ok(()) } +/// Create a directory in the `target` as output directory +fn create_out_dir() -> Result<PathBuf> { + let temp_dir: PathBuf = env::var("OUT_DIR")?.into(); + + // this is set in case the user has overriden the target directory + let out_dir = if let Ok(path) = env::var("CARGO_TARGET_DIR") { + path.into() + } else { + // otherwise just traverse up from the out dir + let mut out_dir: PathBuf = temp_dir.clone(); + loop { + if !out_dir.pop() { + bail!("Cannot find project root.") + } + if out_dir.join("Cargo.lock").exists() { + break; + } + } + out_dir.join("target") + } + .join("pallet-revive-fixtures"); + + // clean up some leftover symlink from previous versions of this script + let mut out_exists = out_dir.exists(); + if out_exists && !out_dir.is_dir() { + fs::remove_file(&out_dir)?; + out_exists = false; + } + + if !out_exists { + fs::create_dir(&out_dir).context("Failed to create output directory")?; + } + + // write the location of the out dir so it can be found later + let mut file = fs::File::create(temp_dir.join("fixture_location.rs")) + .context("Failed to create fixture_location.rs")?; + write!( + file, + r#" + #[allow(dead_code)] + const FIXTURE_DIR: &str = "{0}"; + macro_rules! fixture {{ + ($name: literal) => {{ + include_bytes!(concat!("{0}", "/", $name, ".polkavm")) + }}; + }} + "#, + out_dir.display() + ) + .context("Failed to write to fixture_location.rs")?; + + Ok(out_dir) +} + pub fn main() -> Result<()> { let fixtures_dir: PathBuf = env::var("CARGO_MANIFEST_DIR")?.into(); let contracts_dir = fixtures_dir.join("contracts"); - let out_dir: PathBuf = env::var("OUT_DIR")?.into(); - let target = fixtures_dir.join("riscv32emac-unknown-none-polkavm.json"); + let out_dir = create_out_dir().context("Cannot determine output directory")?; + let build_dir = out_dir.join("build"); + fs::create_dir_all(&build_dir).context("Failed to create build directory")?; println!("cargo::rerun-if-env-changed={OVERRIDE_RUSTUP_TOOLCHAIN_ENV_VAR}"); println!("cargo::rerun-if-env-changed={OVERRIDE_STRIP_ENV_VAR}"); @@ -199,25 +258,9 @@ pub fn main() -> Result<()> { 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(&target, tmp_dir_path)?; - - write_output(tmp_dir_path, &out_dir, entries)?; - - #[cfg(unix)] - if let Ok(symlink_dir) = env::var("CARGO_WORKSPACE_ROOT_DIR") { - let symlink_dir: PathBuf = symlink_dir.into(); - let symlink_dir: PathBuf = symlink_dir.join("target").join("pallet-revive-fixtures"); - if symlink_dir.is_symlink() { - fs::remove_file(&symlink_dir) - .with_context(|| format!("Failed to remove_file {symlink_dir:?}"))?; - } - std::os::unix::fs::symlink(&out_dir, &symlink_dir) - .with_context(|| format!("Failed to symlink {out_dir:?} -> {symlink_dir:?}"))?; - } + create_cargo_toml(&fixtures_dir, entries.iter(), &build_dir)?; + invoke_build(&build_dir)?; + write_output(&build_dir, &out_dir, entries)?; Ok(()) } diff --git a/substrate/frame/revive/fixtures/build/Cargo.toml b/substrate/frame/revive/fixtures/build/_Cargo.toml similarity index 56% rename from substrate/frame/revive/fixtures/build/Cargo.toml rename to substrate/frame/revive/fixtures/build/_Cargo.toml index 5d0e256e2e73c96f9e6e17fe30022171de544cf4..1a0a635420ad5acf02c55302f26c904cceb0f1b4 100644 --- a/substrate/frame/revive/fixtures/build/Cargo.toml +++ b/substrate/frame/revive/fixtures/build/_Cargo.toml @@ -4,14 +4,18 @@ publish = false version = "1.0.0" edition = "2021" +# Make sure this is not included into the workspace +[workspace] + # 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 } +uapi = { package = 'pallet-revive-uapi', path = "", features = ["unstable-hostfn"], default-features = false } common = { package = 'pallet-revive-fixtures-common', path = "" } -polkavm-derive = { version = "0.14.0" } +hex-literal = { version = "0.4.1", default-features = false } +polkavm-derive = { version = "0.19.0" } [profile.release] opt-level = 3 diff --git a/substrate/frame/revive/fixtures/build/_rust-toolchain.toml b/substrate/frame/revive/fixtures/build/_rust-toolchain.toml new file mode 100644 index 0000000000000000000000000000000000000000..4c757c708d58bbd18c54f1be50210a34cb525022 --- /dev/null +++ b/substrate/frame/revive/fixtures/build/_rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly-2024-11-19" +components = ["rust-src"] +profile = "minimal" diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs b/substrate/frame/revive/fixtures/contracts/base_fee.rs similarity index 79% rename from substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs rename to substrate/frame/revive/fixtures/contracts/base_fee.rs index 0ce2b6b5628da348415e5a39161576161b39146b..157909463ee41707387e4da2d8e912806b0f5f3f 100644 --- a/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs +++ b/substrate/frame/revive/fixtures/contracts/base_fee.rs @@ -15,12 +15,13 @@ // 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. +//! Returns the base fee back to the caller. + #![no_std] #![no_main] extern crate common; -use uapi::{HostFn, HostFnImpl as api, ReturnErrorCode}; +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; #[no_mangle] #[polkavm_derive::polkavm_export] @@ -29,5 +30,7 @@ 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)); + let mut buf = [0; 32]; + api::base_fee(&mut buf); + api::return_value(ReturnFlags::empty(), &buf); } diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_works.rs b/substrate/frame/revive/fixtures/contracts/block_author.rs similarity index 85% rename from substrate/frame/revive/fixtures/contracts/debug_message_works.rs rename to substrate/frame/revive/fixtures/contracts/block_author.rs index 3a2509509d8f156300a256d1a5b0494ea961f5ef..59886a19cc6198756248a40b6779216f29ec4906 100644 --- a/substrate/frame/revive/fixtures/contracts/debug_message_works.rs +++ b/substrate/frame/revive/fixtures/contracts/block_author.rs @@ -15,11 +15,10 @@ // 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 common::input; use uapi::{HostFn, HostFnImpl as api}; #[no_mangle] @@ -29,5 +28,10 @@ pub extern "C" fn deploy() {} #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { - api::debug_message(b"Hello World!").unwrap(); + input!(expected: &[u8; 20],); + + let mut received = [0; 20]; + api::block_author(&mut received); + + assert_eq!(expected, &received); } diff --git a/substrate/frame/revive/fixtures/contracts/call.rs b/substrate/frame/revive/fixtures/contracts/call.rs index ee51548879d9de9479d5d9f1c695f6c6d4bc5889..7c4c0882c6b87c526b4e90d4d00c399f584fab49 100644 --- a/substrate/frame/revive/fixtures/contracts/call.rs +++ b/substrate/frame/revive/fixtures/contracts/call.rs @@ -38,10 +38,10 @@ pub extern "C" fn call() { 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. - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. callee_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/call_data_copy.rs b/substrate/frame/revive/fixtures/contracts/call_data_copy.rs new file mode 100644 index 0000000000000000000000000000000000000000..ccf1664058e83a28a22c09dcd28bed0ca3ab5e46 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_data_copy.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. + +//! Expects a call data of [0xFF; 32] and executes the test vectors from +//! [https://www.evm.codes/?fork=cancun#37] and some additional tests. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api}; + +const TEST_DATA: [u8; 32] = [ + 255, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + let mut buf = [0; 32]; + + api::call_data_copy(&mut &mut buf[..], 0); + assert_eq!(buf, [255; 32]); + + api::call_data_copy(&mut &mut buf[..8], 31); + assert_eq!(buf, TEST_DATA); + + api::call_data_copy(&mut &mut buf[..], 32); + assert_eq!(buf, [0; 32]); + + let mut buf = [255; 32]; + api::call_data_copy(&mut &mut buf[..], u32::MAX); + assert_eq!(buf, [0; 32]); +} diff --git a/substrate/frame/revive/fixtures/contracts/call_data_load.rs b/substrate/frame/revive/fixtures/contracts/call_data_load.rs new file mode 100644 index 0000000000000000000000000000000000000000..d3df9433f5d104eaa99813062e7b7808bb8b920d --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_data_load.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. + +//! This uses the call data load API to first the first input byte. +//! This single input byte is used as the offset for a second call +//! to the call data load API. +//! The output of the second API call is returned. + +#![no_std] +#![no_main] + +extern crate common; +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() { + let mut buf = [0; 32]; + api::call_data_load(&mut buf, 0); + + let offset = buf[31] as u32; + let mut buf = [0; 32]; + api::call_data_load(&mut buf, offset); + + api::return_value(ReturnFlags::empty(), &buf); +} diff --git a/substrate/frame/revive/fixtures/contracts/call_data_size.rs b/substrate/frame/revive/fixtures/contracts/call_data_size.rs new file mode 100644 index 0000000000000000000000000000000000000000..7caf18d440b883ed31d623ddd74701baefef8cf9 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_data_size.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. + +//! Returns the call data size back to the caller. + +#![no_std] +#![no_main] + +extern crate common; +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() { + api::return_value(ReturnFlags::empty(), &api::call_data_size().to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs b/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs index 129adde2cec915adbf2f3327ab6b536466b607e9..d084c4aed6df750152c417f3c4a6c3d06b6c09a1 100644 --- a/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs +++ b/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs @@ -42,9 +42,9 @@ fn assert_call<const N: usize>(callee_address: &[u8; 20], expected_output: [u8; api::call( uapi::CallFlags::ALLOW_REENTRY, callee_address, - 0u64, - 0u64, - None, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], &[0u8; 32], &[], Some(output_buf_capped), @@ -66,12 +66,11 @@ fn assert_instantiate<const N: usize>(expected_output: [u8; BUF_SIZE]) { let output_buf_capped = &mut &mut output_buf[..N]; api::instantiate( - &code_hash, - 0u64, - 0u64, - None, - &[0; 32], + u64::MAX, + u64::MAX, + &[u8::MAX; 32], &[0; 32], + &code_hash, None, Some(output_buf_capped), None, diff --git a/substrate/frame/revive/fixtures/contracts/call_return_code.rs b/substrate/frame/revive/fixtures/contracts/call_return_code.rs index 2d13b9f7095638a29f523d15b7d1c94dd2012946..19b3ae3fdb2624f341df5c781bf03d7fa77123f1 100644 --- a/substrate/frame/revive/fixtures/contracts/call_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/call_return_code.rs @@ -42,10 +42,10 @@ pub extern "C" fn call() { 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. - value, // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + value, // Value transferred to the contract. input, None, ) { diff --git a/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs b/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs index 8c8aee9628498e4cbc67e8e9f304106c717a486f..78b275459f0e005841ee5f96d0b03693a137efb3 100644 --- a/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs +++ b/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs @@ -42,10 +42,10 @@ pub extern "C" fn call() { 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. - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. callee_input, None, ) 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 index 330393e706e98fea7f7e6b04816885f37ac21073..155a4b41bd95f330be6f3af261fc6db7f3acb044 100644 --- a/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs +++ b/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs @@ -40,10 +40,10 @@ pub extern "C" fn call() { 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. - &u256_bytes(value), // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &u256_bytes(value), // Value transferred to the contract. forwarded_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/call_with_limit.rs b/substrate/frame/revive/fixtures/contracts/call_with_limit.rs index 6ab892a6b7ae8416d923215df0a13b9ee13faf0c..af5c301a353c94cc240c39a73c9ffeffed906152 100644 --- a/substrate/frame/revive/fixtures/contracts/call_with_limit.rs +++ b/substrate/frame/revive/fixtures/contracts/call_with_limit.rs @@ -43,8 +43,8 @@ pub extern "C" fn call() { callee_addr, ref_time, proof_size, - None, // No deposit limit. - &[0u8; 32], // value transferred to the contract. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // value transferred to the contract. forwarded_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/caller_contract.rs b/substrate/frame/revive/fixtures/contracts/caller_contract.rs index f9a30b87df47e071ae808b4530caed8e4eebea55..b6a9bf2895fa6d48b3032c5b4e9b3839e93bb6ae 100644 --- a/substrate/frame/revive/fixtures/contracts/caller_contract.rs +++ b/substrate/frame/revive/fixtures/contracts/caller_contract.rs @@ -21,6 +21,9 @@ use common::{input, u256_bytes}; use uapi::{HostFn, HostFnImpl as api, ReturnErrorCode}; +const INPUT: [u8; 8] = [0u8, 1, 34, 51, 68, 85, 102, 119]; +const REVERTED_INPUT: [u8; 7] = [1u8, 34, 51, 68, 85, 102, 119]; + #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn deploy() {} @@ -36,17 +39,21 @@ pub extern "C" fn call() { let salt = [0u8; 32]; // 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]; + let mut input_deploy = [0; 32 + INPUT.len()]; + input_deploy[..32].copy_from_slice(code_hash); + input_deploy[32..].copy_from_slice(&INPUT); + + let mut reverted_input_deploy = [0; 32 + REVERTED_INPUT.len()]; + reverted_input_deploy[..32].copy_from_slice(code_hash); + reverted_input_deploy[32..].copy_from_slice(&REVERTED_INPUT); // 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. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, - &reverted_input, + &reverted_input_deploy, None, None, Some(&salt), @@ -55,42 +62,39 @@ pub extern "C" fn call() { // 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. + 1u64, // too little ref_time weight + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, - &input, + &input_deploy, None, None, Some(&salt), ); - assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); + assert!(matches!(res, Err(ReturnErrorCode::OutOfResources))); // 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. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + 1u64, // Too little proof_size weight + &[u8::MAX; 32], // No deposit limit. &value, - &input, + &input_deploy, None, None, Some(&salt), ); - assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); + assert!(matches!(res, Err(ReturnErrorCode::OutOfResources))); // Deploy the contract successfully. let mut callee = [0u8; 20]; 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. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, - &input, + &input_deploy, Some(&mut callee), None, Some(&salt), @@ -101,11 +105,11 @@ pub extern "C" fn call() { 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. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, - &reverted_input, + &REVERTED_INPUT, None, ); assert!(matches!(res, Err(ReturnErrorCode::CalleeReverted))); @@ -114,40 +118,40 @@ pub extern "C" fn call() { 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. + 1u64, // Too little ref_time weight. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, - &input, + &INPUT, None, ); - assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); + assert!(matches!(res, Err(ReturnErrorCode::OutOfResources))); // 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. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + 1u64, // too little proof_size weight + &[u8::MAX; 32], // No deposit limit. &value, - &input, + &INPUT, None, ); - assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); + assert!(matches!(res, Err(ReturnErrorCode::OutOfResources))); // 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. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, - &input, + &INPUT, Some(&mut &mut output[..]), ) .unwrap(); - assert_eq!(&output, &input[4..]) + assert_eq!(&output, &INPUT[4..]) } diff --git a/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs b/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs index 22d6c5b548d87acd2bffc17401f42a682d328dd7..9b76b9d39ee946b20d07c9ff2458123c9b0c9968 100644 --- a/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs +++ b/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs @@ -54,10 +54,10 @@ pub extern "C" fn call() { 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. - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/common/src/lib.rs b/substrate/frame/revive/fixtures/contracts/common/src/lib.rs index abfba282bec14254f76797f776bc83b0a7f5e315..302608ccf87c1d3ca703ec73c461b15bf06393b5 100644 --- a/substrate/frame/revive/fixtures/contracts/common/src/lib.rs +++ b/substrate/frame/revive/fixtures/contracts/common/src/lib.rs @@ -121,8 +121,9 @@ macro_rules! input { // 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); + let input_size = $crate::api::call_data_size(); + let $buffer = &mut &mut $buffer[..$size.min(input_size as usize)]; + $crate::api::call_data_copy($buffer, 0); input!(@inner $buffer, 0, $($rest)*); }; diff --git a/substrate/frame/revive/fixtures/contracts/create1_with_value.rs b/substrate/frame/revive/fixtures/contracts/create1_with_value.rs index c6adab82886076484b57972c943d676c443d41e9..a694a9b09189872ec54bf511548685979f20706a 100644 --- a/substrate/frame/revive/fixtures/contracts/create1_with_value.rs +++ b/substrate/frame/revive/fixtures/contracts/create1_with_value.rs @@ -34,6 +34,6 @@ pub extern "C" fn call() { api::value_transferred(&mut value); // Deploy the contract with no salt (equivalent to create1). - let ret = api::instantiate(code_hash, 0u64, 0u64, None, &value, &[], None, None, None); - assert!(ret.is_ok()); + api::instantiate(u64::MAX, u64::MAX, &[u8::MAX; 32], &value, code_hash, None, None, None) + .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 index 4fa2db0c8c1cf1a8758426622a5f53c46f4c9fd7..5bb11e27903e7ff15eebc0f6abad80319cd83677 100644 --- a/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs @@ -40,17 +40,19 @@ pub extern "C" fn call() { api::set_storage(StorageFlags::empty(), buffer, &[1u8; 4]); // Call the callee - api::call( + let ret = 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), - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all resources. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all resources. + deposit_limit, + &[0u8; 32], // Value transferred to the contract. input, None, - ) - .unwrap(); + ); + if let Err(code) = ret { + api::return_value(uapi::ReturnFlags::REVERT, &(code as u32).to_le_bytes()); + }; // create 8 byte of storage after calling // item of 12 bytes because we override 4 bytes diff --git a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs index 463706457a15c8a558a3294002d0a018998c898b..0ee0bd70db97c0b85fcd52070b20ec992db124da 100644 --- a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs @@ -20,7 +20,9 @@ #![no_main] use common::{input, u256_bytes}; -use uapi::{HostFn, HostFnImpl as api}; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +static BUFFER: [u8; 16 * 1024 + 1] = [0u8; 16 * 1024 + 1]; #[no_mangle] #[polkavm_derive::polkavm_export] @@ -30,27 +32,37 @@ pub extern "C" fn deploy() {} #[polkavm_derive::polkavm_export] pub extern "C" fn call() { input!( - input: [u8; 4], code_hash: &[u8; 32], + input: [u8; 4], deposit_limit: &[u8; 32], ); + let len = u32::from_le_bytes(input.try_into().unwrap()); + let data = &BUFFER[..len as usize]; + let mut key = [0u8; 32]; + key[0] = 1; + api::set_storage(StorageFlags::empty(), &key, data); + let value = u256_bytes(10_000u64); let salt = [0u8; 32]; let mut address = [0u8; 20]; + let mut deploy_input = [0; 32 + 4]; + deploy_input[..32].copy_from_slice(code_hash); + deploy_input[32..].copy_from_slice(&input); - 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), + let ret = api::instantiate( + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + deposit_limit, &value, - input, + &deploy_input, Some(&mut address), None, Some(&salt), - ) - .unwrap(); + ); + if let Err(code) = ret { + api::return_value(uapi::ReturnFlags::REVERT, &(code as u32).to_le_bytes()); + }; // 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 index d2efb26e5cebcc41febe22822c7a18036ae2b331..0244967a0556531f2579b41ffb63fa489f5ded82 100644 --- a/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs +++ b/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs @@ -22,7 +22,7 @@ use common::input; use uapi::{HostFn, HostFnImpl as api, StorageFlags}; -static BUFFER: [u8; 512] = [0u8; 512]; +static BUFFER: [u8; 416] = [0u8; 416]; #[no_mangle] #[polkavm_derive::polkavm_export] @@ -49,10 +49,10 @@ pub extern "C" fn call() { 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, - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call.rs b/substrate/frame/revive/fixtures/contracts/delegate_call.rs index 3cf74acf1321a390e437edf497495881b1a8012c..0dedd5f704cb9a84d8d2387a8b407d91047ac34b 100644 --- a/substrate/frame/revive/fixtures/contracts/delegate_call.rs +++ b/substrate/frame/revive/fixtures/contracts/delegate_call.rs @@ -46,7 +46,15 @@ pub extern "C" fn call() { assert!(value[0] == 2u8); let input = [0u8; 0]; - api::delegate_call(uapi::CallFlags::empty(), address, ref_time, proof_size, None, &input, None).unwrap(); + api::delegate_call( + uapi::CallFlags::empty(), + address, + ref_time, + proof_size, + &[u8::MAX; 32], + &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_deposit_limit.rs b/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs index 55203d534c9bb756e0cb5d89eb886b06442cf524..0c503aa93c565bc5c6b593a77a93d0f51ace4498 100644 --- a/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs +++ b/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs @@ -34,7 +34,19 @@ pub extern "C" fn call() { ); let input = [0u8; 0]; - api::delegate_call(uapi::CallFlags::empty(), address, 0, 0, Some(&u256_bytes(deposit_limit)), &input, None).unwrap(); + let ret = api::delegate_call( + uapi::CallFlags::empty(), + address, + u64::MAX, + u64::MAX, + &u256_bytes(deposit_limit), + &input, + None + ); + + if let Err(code) = ret { + api::return_value(uapi::ReturnFlags::REVERT, &(code as u32).to_le_bytes()); + }; let mut key = [0u8; 32]; key[0] = 1u8; diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs b/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs index a8501dad4692d51477db7a5027107249393be0b2..b7bdb792c76c5c30d39fe1d62daa426a837fb945 100644 --- a/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs +++ b/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs @@ -32,5 +32,13 @@ pub extern "C" fn call() { // Delegate call into passed address. let input = [0u8; 0]; - api::delegate_call(uapi::CallFlags::empty(), address, 0, 0, None, &input, None).unwrap(); + api::delegate_call( + uapi::CallFlags::empty(), + address, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], + &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 index 8342f4acf95296d621c855408aacc5b8457a4234..b5face97e236048190cc5a0b2e3a6d964fbd352f 100644 --- a/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs +++ b/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs @@ -29,17 +29,15 @@ const VALUE: [u8; 32] = u256_bytes(65536); pub extern "C" fn deploy() { input!(code_hash: &[u8; 32],); - let input = [0u8; 0]; let mut address = [0u8; 20]; let salt = [47u8; 32]; 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. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &VALUE, - &input, + code_hash, Some(&mut address), None, Some(&salt), @@ -62,9 +60,9 @@ pub extern "C" fn call() { 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. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &VALUE, &[0u8; 1], None, @@ -75,9 +73,9 @@ pub extern "C" fn call() { 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. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &VALUE, &[0u8; 0], None, diff --git a/substrate/frame/revive/fixtures/contracts/drain.rs b/substrate/frame/revive/fixtures/contracts/drain.rs index 6e3e708a6b3d82917e7b2cf09b195fd4c80c1743..53fb213143c489e96b59058292030135ed3a7db8 100644 --- a/substrate/frame/revive/fixtures/contracts/drain.rs +++ b/substrate/frame/revive/fixtures/contracts/drain.rs @@ -41,7 +41,7 @@ pub extern "C" fn call() { &[0u8; 20], 0, 0, - None, + &[u8::MAX; 32], &u256_bytes(balance), &[], None, diff --git a/substrate/frame/revive/fixtures/contracts/extcodesize.rs b/substrate/frame/revive/fixtures/contracts/extcodesize.rs index 0a1171be30e9c89dcdb8fec6f317cecf2d18d47f..3f51b69b46db2653ba208bcb2b68afd0e8b15664 100644 --- a/substrate/frame/revive/fixtures/contracts/extcodesize.rs +++ b/substrate/frame/revive/fixtures/contracts/extcodesize.rs @@ -18,7 +18,7 @@ #![no_std] #![no_main] -use common::{input, u64_output}; +use common::input; use uapi::{HostFn, HostFnImpl as api}; #[no_mangle] @@ -30,7 +30,7 @@ pub extern "C" fn deploy() {} pub extern "C" fn call() { input!(address: &[u8; 20], expected: u64,); - let received = u64_output!(api::code_size, address); + let received = api::code_size(address); assert_eq!(expected, received); } diff --git a/substrate/frame/revive/fixtures/contracts/gas_limit.rs b/substrate/frame/revive/fixtures/contracts/gas_limit.rs new file mode 100644 index 0000000000000000000000000000000000000000..9ce82227b64da38937513678620496634bf5fe94 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/gas_limit.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. + +//! Returns the block ref_time limit back to the caller. + +#![no_std] +#![no_main] + +extern crate common; +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() { + api::return_value(ReturnFlags::empty(), &api::gas_limit().to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs b/substrate/frame/revive/fixtures/contracts/gas_price.rs similarity index 83% rename from substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs rename to substrate/frame/revive/fixtures/contracts/gas_price.rs index 6c850a9ec66312a65e183ce04e7acdc6166093fb..c1c8109fafbee3965641f04d409ab9190eb5f882 100644 --- a/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs +++ b/substrate/frame/revive/fixtures/contracts/gas_price.rs @@ -15,12 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Emit a debug message with an invalid utf-8 code. +//! Returns the gas price back to the caller. + #![no_std] #![no_main] extern crate common; -use uapi::{HostFn, HostFnImpl as api}; +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; #[no_mangle] #[polkavm_derive::polkavm_export] @@ -29,5 +30,5 @@ pub extern "C" fn deploy() {} #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { - api::debug_message(b"\xFC").unwrap(); + api::return_value(ReturnFlags::empty(), &api::gas_price().to_le_bytes()); } diff --git a/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs index 9764859c619b90452eeacbb337a7b94a2cd7d86c..a3643bdedbdbd90eb1e6862ffe1f0c74b760df54 100644 --- a/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs @@ -28,17 +28,14 @@ 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..]; + input!(buffer: &[u8; 36],); 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. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &u256_bytes(10_000u64), // Value to transfer. - input, + buffer, None, None, Some(&[0u8; 32]), // Salt. diff --git a/substrate/frame/revive/fixtures/contracts/origin.rs b/substrate/frame/revive/fixtures/contracts/origin.rs index 8e9afd8e80526b8ba272c8cbe8007d19b76afe5b..151ca3da77cd05349ef03db4f0579f73a2230499 100644 --- a/substrate/frame/revive/fixtures/contracts/origin.rs +++ b/substrate/frame/revive/fixtures/contracts/origin.rs @@ -49,9 +49,9 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::ALLOW_REENTRY, &addr, - 0u64, - 0u64, - None, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], &[0; 32], &[], Some(&mut &mut buf[..]), diff --git a/substrate/frame/revive/fixtures/contracts/read_only_call.rs b/substrate/frame/revive/fixtures/contracts/read_only_call.rs index ea74d56867f5efd1dd2f671a7b015418f7b1d7b7..0a87ecbb9b140a6e4d797a83a174e565b6954d7d 100644 --- a/substrate/frame/revive/fixtures/contracts/read_only_call.rs +++ b/substrate/frame/revive/fixtures/contracts/read_only_call.rs @@ -39,10 +39,10 @@ pub extern "C" fn call() { 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. - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. callee_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/recurse.rs b/substrate/frame/revive/fixtures/contracts/recurse.rs index 2e70d67d8c738c700f35d281a51605e7b9c76170..ead565c01459e6848c846611abe1c6132b751940 100644 --- a/substrate/frame/revive/fixtures/contracts/recurse.rs +++ b/substrate/frame/revive/fixtures/contracts/recurse.rs @@ -43,10 +43,10 @@ pub extern "C" fn call() { 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. - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all resources. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all resources. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. &(calls_left - 1).to_le_bytes(), None, ) diff --git a/substrate/frame/revive/fixtures/contracts/ref_time_left.rs b/substrate/frame/revive/fixtures/contracts/ref_time_left.rs new file mode 100644 index 0000000000000000000000000000000000000000..aa892a8ba440efb570dd2e124163237eb63f5896 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/ref_time_left.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; +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + assert!(api::ref_time_left() > api::ref_time_left()); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + api::return_value(ReturnFlags::empty(), &api::ref_time_left().to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/return_data_api.rs b/substrate/frame/revive/fixtures/contracts/return_data_api.rs index 2a390296a419035f5ff840c18c744918b1f347ea..e8aeeea44bde77e0ceac70910dfad40055cda960 100644 --- a/substrate/frame/revive/fixtures/contracts/return_data_api.rs +++ b/substrate/frame/revive/fixtures/contracts/return_data_api.rs @@ -75,15 +75,22 @@ fn recursion_guard() -> [u8; 20] { /// Assert [api::return_data_size] to match the `expected` value. fn assert_return_data_size_of(expected: u64) { - let mut return_data_size = [0xff; 32]; - api::return_data_size(&mut return_data_size); - assert_eq!(return_data_size, u256_bytes(expected)); + assert_eq!(api::return_data_size(), expected); } /// Assert the return data to be reset after a balance transfer. fn assert_balance_transfer_does_reset() { - api::call(uapi::CallFlags::empty(), &[0u8; 20], 0, 0, None, &u256_bytes(128), &[], None) - .unwrap(); + api::call( + uapi::CallFlags::empty(), + &[0u8; 20], + u64::MAX, + u64::MAX, + &[u8::MAX; 32], + &u256_bytes(128), + &[], + None, + ) + .unwrap(); assert_return_data_size_of(0); } @@ -111,13 +118,16 @@ pub extern "C" fn call() { input }; let mut instantiate = |exit_flag| { + let input = construct_input(exit_flag); + let mut deploy_input = [0; 32 + INPUT_BUF_SIZE]; + deploy_input[..32].copy_from_slice(code_hash); + deploy_input[32..].copy_from_slice(&input); api::instantiate( - code_hash, - 0u64, - 0u64, - None, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], &[0; 32], - &construct_input(exit_flag), + &deploy_input, Some(&mut address_buf), None, None, @@ -127,9 +137,9 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::empty(), address_buf, - 0u64, - 0u64, - None, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], &[0; 32], &construct_input(exit_flag), None, diff --git a/substrate/frame/revive/fixtures/contracts/self_destruct.rs b/substrate/frame/revive/fixtures/contracts/self_destruct.rs index 2f37706634bd126f465ce6ea579cc0878d951bdd..eed7f40ddfed7b3518fad29e058c90f457fd2a9e 100644 --- a/substrate/frame/revive/fixtures/contracts/self_destruct.rs +++ b/substrate/frame/revive/fixtures/contracts/self_destruct.rs @@ -25,7 +25,10 @@ const DJANGO_FALLBACK: [u8; 20] = [4u8; 20]; #[no_mangle] #[polkavm_derive::polkavm_export] -pub extern "C" fn deploy() {} +pub extern "C" fn deploy() { + // make sure that the deposit for the immutable data is refunded + api::set_immutable_data(&[1, 2, 3, 4, 5]) +} #[no_mangle] #[polkavm_derive::polkavm_export] @@ -42,10 +45,10 @@ pub extern "C" fn call() { 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. - &[0u8; 32], // Value to transfer. + u64::MAX, // How much ref_time to devote for the execution. u64 = all. + u64::MAX, // How much proof_size to devote for the execution. u64 = all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value to transfer. &[0u8; 0], None, ) diff --git a/substrate/frame/revive/fixtures/contracts/set_code_hash.rs b/substrate/frame/revive/fixtures/contracts/set_code_hash.rs index 75995d7bb8a2809d02d4796576efc1d0e29a79d0..7292c6fd10ae7140e345942b152f6e325738ab9f 100644 --- a/substrate/frame/revive/fixtures/contracts/set_code_hash.rs +++ b/substrate/frame/revive/fixtures/contracts/set_code_hash.rs @@ -29,7 +29,7 @@ pub extern "C" fn deploy() {} #[polkavm_derive::polkavm_export] pub extern "C" fn call() { input!(addr: &[u8; 32],); - api::set_code_hash(addr).unwrap(); + api::set_code_hash(addr); // we return 1 after setting new code_hash // next `call` will NOT return this value, because contract code has been changed diff --git a/substrate/frame/revive/fixtures/contracts/to_account_id.rs b/substrate/frame/revive/fixtures/contracts/to_account_id.rs new file mode 100644 index 0000000000000000000000000000000000000000..c2a8fce3ec995c9cd28aa31584f6dd66948d6a25 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/to_account_id.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!( + address: &[u8; 20], + expected_account_id: &[u8; 32], + ); + + let mut account_id = [0u8; 32]; + api::to_account_id(address, &mut account_id); + + assert!(&account_id == expected_account_id); +} diff --git a/substrate/frame/revive/fixtures/contracts/tracing.rs b/substrate/frame/revive/fixtures/contracts/tracing.rs new file mode 100644 index 0000000000000000000000000000000000000000..9cbef3bbc84355dc89968347248d4daed68aedad --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/tracing.rs @@ -0,0 +1,75 @@ +// 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; +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, callee_addr: &[u8; 20],); + if calls_left == 0 { + return + } + + let next_input = (calls_left - 1).to_le_bytes(); + api::deposit_event(&[], b"before"); + + // Call the callee, ignore revert. + let _ = api::call( + uapi::CallFlags::empty(), + callee_addr, + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. + &next_input, + None, + ); + + api::deposit_event(&[], b"after"); + + // own address + let mut addr = [0u8; 20]; + api::address(&mut addr); + let mut input = [0u8; 24]; + + input[..4].copy_from_slice(&next_input); + input[4..24].copy_from_slice(&callee_addr[..20]); + + // recurse + api::call( + uapi::CallFlags::ALLOW_REENTRY, + &addr, + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. + &input, + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs b/substrate/frame/revive/fixtures/contracts/tracing_callee.rs similarity index 50% rename from substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs rename to substrate/frame/revive/fixtures/contracts/tracing_callee.rs index 3d7702c6537adc3528eeb23443347185f144c172..d44771e417f9df5fd8c13277a2b4f5c0d7afe8f3 100644 --- a/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs +++ b/substrate/frame/revive/fixtures/contracts/tracing_callee.rs @@ -14,56 +14,32 @@ // WITHOUT 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_FALLBACK: [u8; 20] = [1u8; 20]; - -/// 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, - address: &[u8; 20], - 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_FALLBACK); - }, - // Everything else is a noop - _ => {}, - } - - if delegate_call { - api::delegate_call(uapi::CallFlags::empty(), address, 0, 0, None, &[], None).unwrap(); - } -} - #[no_mangle] #[polkavm_derive::polkavm_export] -pub extern "C" fn deploy() { - load_input(false); -} +pub extern "C" fn deploy() {} #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { - load_input(true); + input!(id: u32, ); + + match id { + // Revert with message "This function always fails" + 2 => { + let data = hex_literal::hex!( + "08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a546869732066756e6374696f6e20616c77617973206661696c73000000000000" + ); + api::return_value(uapi::ReturnFlags::REVERT, &data) + }, + 1 => { + panic!("booum"); + }, + _ => api::return_value(uapi::ReturnFlags::empty(), &id.to_le_bytes()), + }; } diff --git a/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs b/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs index 09d45d0a8411a3a9fca37a8034aa880317ad1834..053f97feda4a8dcc8dbd302aed6dbc952d743874 100644 --- a/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs @@ -33,7 +33,7 @@ pub extern "C" fn call() { &[0u8; 20], 0, 0, - None, + &[u8::MAX; 32], &u256_bytes(100u64), &[], None, diff --git a/substrate/frame/revive/fixtures/contracts/unknown_syscall.rs b/substrate/frame/revive/fixtures/contracts/unknown_syscall.rs new file mode 100644 index 0000000000000000000000000000000000000000..93ea86754f55a93981686c61c8682660316d72c2 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/unknown_syscall.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; + +#[polkavm_derive::polkavm_import] +extern "C" { + pub fn __this_syscall_does_not_exist__(); +} + +// 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 it is not optimized away + unsafe { + __this_syscall_does_not_exist__(); + } +} + +#[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/unstable_interface.rs b/substrate/frame/revive/fixtures/contracts/unstable_interface.rs new file mode 100644 index 0000000000000000000000000000000000000000..d73ae041dc068a9e612f4cb46ae56fd26ecb1695 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/unstable_interface.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; + +#[polkavm_derive::polkavm_import] +extern "C" { + pub fn set_code_hash(); +} + +// 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 it is not optimized away + unsafe { + set_code_hash(); + } +} + +#[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/riscv32emac-unknown-none-polkavm.json b/substrate/frame/revive/fixtures/riscv32emac-unknown-none-polkavm.json deleted file mode 100644 index bbd54cdefbac547021f606a0871b55edc4dca0cb..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/fixtures/riscv32emac-unknown-none-polkavm.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "arch": "riscv32", - "cpu": "generic-rv32", - "crt-objects-fallback": "false", - "data-layout": "e-m:e-p:32:32-i64:64-n32-S32", - "eh-frame-header": false, - "emit-debug-gdb-scripts": false, - "features": "+e,+m,+a,+c,+lui-addi-fusion,+fast-unaligned-access,+xtheadcondmov", - "linker": "rust-lld", - "linker-flavor": "ld.lld", - "llvm-abiname": "ilp32e", - "llvm-target": "riscv32", - "max-atomic-width": 32, - "panic-strategy": "abort", - "relocation-model": "pie", - "target-pointer-width": "32", - "singlethread": true, - "pre-link-args": { - "ld": [ - "--emit-relocs", - "--unique", - "--relocatable" - ] - }, - "env": "polkavm" -} diff --git a/substrate/frame/revive/fixtures/src/lib.rs b/substrate/frame/revive/fixtures/src/lib.rs index cc84daec9b598bc758c70f73b9f3300c57266e95..7685253d1ea2cc227a6ef13f20330c711ada4502 100644 --- a/substrate/frame/revive/fixtures/src/lib.rs +++ b/substrate/frame/revive/fixtures/src/lib.rs @@ -19,12 +19,14 @@ extern crate alloc; -/// Load a given wasm module and returns a wasm binary contents along with it's hash. +// generated file that tells us where to find the fixtures +include!(concat!(env!("OUT_DIR"), "/fixture_location.rs")); + +/// Load a given wasm module and returns a wasm binary contents along with its hash. #[cfg(feature = "std")] pub fn compile_module(fixture_name: &str) -> anyhow::Result<(Vec<u8>, sp_core::H256)> { - let out_dir: std::path::PathBuf = env!("OUT_DIR").into(); + let out_dir: std::path::PathBuf = FIXTURE_DIR.into(); let fixture_path = out_dir.join(format!("{fixture_name}.polkavm")); - log::debug!("Loading fixture from {fixture_path:?}"); let binary = std::fs::read(fixture_path)?; let code_hash = sp_io::hashing::keccak_256(&binary); Ok((binary, sp_core::H256(code_hash))) @@ -36,12 +38,6 @@ pub fn compile_module(fixture_name: &str) -> anyhow::Result<(Vec<u8>, sp_core::H /// available in no-std environments (runtime benchmarks). pub mod bench { use alloc::vec::Vec; - - macro_rules! fixture { - ($name: literal) => { - include_bytes!(concat!(env!("OUT_DIR"), "/", $name, ".polkavm")) - }; - } pub const DUMMY: &[u8] = fixture!("dummy"); pub const NOOP: &[u8] = fixture!("noop"); pub const INSTR: &[u8] = fixture!("instr_benchmark"); @@ -61,7 +57,7 @@ pub mod bench { mod test { #[test] fn out_dir_should_have_compiled_mocks() { - let out_dir: std::path::PathBuf = env!("OUT_DIR").into(); + let out_dir: std::path::PathBuf = crate::FIXTURE_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 index c5b18b3fa290ea325217e435e0fe92d560ab1e71..1ebeb2c95db7f42db9cd88bdb904607ef1639e6b 100644 --- a/substrate/frame/revive/mock-network/Cargo.toml +++ b/substrate/frame/revive/mock-network/Cargo.toml @@ -18,22 +18,17 @@ frame-support = { workspace = true } frame-system = { workspace = true } pallet-assets = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } +pallet-message-queue = { 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 } @@ -43,8 +38,8 @@ xcm-simulator = { workspace = true, default-features = true } [dev-dependencies] assert_matches = { workspace = true } -pretty_assertions = { workspace = true } pallet-revive-fixtures = { workspace = true } +pretty_assertions = { workspace = true } [features] default = ["std"] @@ -53,17 +48,13 @@ 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", @@ -74,10 +65,8 @@ 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", @@ -85,6 +74,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", @@ -92,10 +82,8 @@ try-runtime = [ "pallet-assets/try-runtime", "pallet-balances/try-runtime", "pallet-message-queue/try-runtime", - "pallet-proxy/try-runtime", "pallet-revive/try-runtime", "pallet-timestamp/try-runtime", - "pallet-utility/try-runtime", "pallet-xcm/try-runtime", "polkadot-runtime-parachains/try-runtime", "sp-runtime/try-runtime", diff --git a/substrate/frame/revive/mock-network/src/lib.rs b/substrate/frame/revive/mock-network/src/lib.rs index adfd0016b4dd918497ae49b8a5b04aed12176e80..b8c9bc13aa79629d9f38358fc9435aae6031bd02 100644 --- a/substrate/frame/revive/mock-network/src/lib.rs +++ b/substrate/frame/revive/mock-network/src/lib.rs @@ -99,6 +99,7 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { (relay_sovereign_account_id(), INITIAL_BALANCE), (BOB, INITIAL_BALANCE), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -137,6 +138,7 @@ pub fn relay_ext() -> sp_io::TestExternalities { (parachain_sovereign_account_id(1), INITIAL_BALANCE), (parachain_account_sovereign_account_id(1, ALICE), INITIAL_BALANCE), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/revive/mock-network/src/tests.rs b/substrate/frame/revive/mock-network/src/tests.rs index bd05726a1a45f505e5105c673a7d0944f0cb26cb..34f797c2b530f44aa67286fcdccf6b2a7d6b1cd7 100644 --- a/substrate/frame/revive/mock-network/src/tests.rs +++ b/substrate/frame/revive/mock-network/src/tests.rs @@ -24,7 +24,7 @@ use frame_support::traits::{fungibles::Mutate, Currency}; use frame_system::RawOrigin; use pallet_revive::{ test_utils::{self, builder::*}, - Code, + Code, DepositLimit, }; use pallet_revive_fixtures::compile_module; use pallet_revive_uapi::ReturnErrorCode; @@ -52,7 +52,7 @@ fn instantiate_test_contract(name: &str) -> Contract<parachain::Runtime> { RawOrigin::Signed(ALICE).into(), Code::Upload(wasm), ) - .storage_deposit_limit(1_000_000_000_000) + .storage_deposit_limit(DepositLimit::Balance(1_000_000_000_000)) .build_and_unwrap_contract() }); diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index 012b4bfab9a9b129cfaa590dd91642a00a7533c4..6f087c86b5ffdc89604daf6d1dcd6e6d100e874f 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -25,6 +25,17 @@ use proc_macro2::{Literal, Span, TokenStream as TokenStream2}; use quote::{quote, ToTokens}; use syn::{parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, FnArg, Ident}; +#[proc_macro_attribute] +pub fn unstable_hostfn(_attr: TokenStream, item: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(item as syn::Item); + let expanded = quote! { + #[cfg(feature = "unstable-hostfn")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-hostfn")))] + #input + }; + expanded.into() +} + /// Defines a host functions set that can be imported by contract wasm code. /// /// **NB**: Be advised that all functions defined by this macro @@ -79,6 +90,7 @@ use syn::{parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, F /// - `Result<(), TrapReason>`, /// - `Result<ReturnErrorCode, TrapReason>`, /// - `Result<u32, TrapReason>`. +/// - `Result<u64, TrapReason>`. /// /// The macro expands to `pub struct Env` declaration, with the following traits implementations: /// - `pallet_revive::wasm::Environment<Runtime<E>> where E: Ext` @@ -118,7 +130,7 @@ struct EnvDef { /// Parsed host function definition. struct HostFn { item: syn::ItemFn, - api_version: Option<u16>, + is_stable: bool, name: String, returns: HostFnReturn, cfg: Option<syn::Attribute>, @@ -127,6 +139,7 @@ struct HostFn { enum HostFnReturn { Unit, U32, + U64, ReturnCode, } @@ -134,8 +147,7 @@ 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()) }, + _ => quote! { |ret_val| Some(ret_val.into()) }, } } @@ -143,6 +155,7 @@ impl HostFnReturn { match self { Self::Unit => syn::ReturnType::Default, Self::U32 => parse_quote! { -> u32 }, + Self::U64 => parse_quote! { -> u64 }, Self::ReturnCode => parse_quote! { -> ReturnErrorCode }, } } @@ -181,22 +194,21 @@ impl HostFn { }; // process attributes - let msg = "Only #[api_version(<u16>)], #[cfg] and #[mutating] attributes are allowed."; + let msg = "Only #[stable], #[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 is_stable = false; 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")) + "stable" => { + if is_stable { + return Err(err(span, "#[stable] can only be specified once")) } - api_version = - Some(attr.parse_args::<syn::LitInt>().and_then(|lit| lit.base10_parse())?); + is_stable = true; }, "mutating" => { if mutating { @@ -243,7 +255,8 @@ impl HostFn { let msg = r#"Should return one of the following: - Result<(), TrapReason>, - Result<ReturnErrorCode, TrapReason>, - - Result<u32, TrapReason>"#; + - Result<u32, TrapReason>, + - Result<u64, TrapReason>"#; let ret_ty = match item.clone().sig.output { syn::ReturnType::Type(_, ty) => Ok(ty.clone()), _ => Err(err(span, &msg)), @@ -305,11 +318,12 @@ impl HostFn { let returns = match ok_ty_str.as_str() { "()" => Ok(HostFnReturn::Unit), "u32" => Ok(HostFnReturn::U32), + "u64" => Ok(HostFnReturn::U64), "ReturnErrorCode" => Ok(HostFnReturn::ReturnCode), _ => Err(err(arg1.span(), &msg)), }?; - Ok(Self { item, api_version, name, returns, cfg }) + Ok(Self { item, is_stable, name, returns, cfg }) }, _ => Err(err(span, &msg)), } @@ -339,48 +353,34 @@ where P: Iterator<Item = &'a std::boxed::Box<syn::Pat>> + Clone, I: Iterator<Item = &'a std::boxed::Box<syn::Type>> + 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() { + const ALLOWED_REGISTERS: usize = 6; + + // too many arguments + if param_names.clone().count() > ALLOWED_REGISTERS { + panic!("Syscalls take a maximum of {ALLOWED_REGISTERS} arguments"); + } + + // all of them take one register but we truncate them before passing into the function + // it is important to not allow any type which has illegal bit patterns like 'bool' + if !param_types.clone().all(|ty| { 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); + matches!(ident.to_string().as_ref(), "u8" | "u16" | "u32" | "u64") + }) { + panic!("Only primitive unsigned integers are allowed as arguments to syscalls"); } + + // one argument per register + let bindings = param_names.zip(param_types).enumerate().map(|(idx, (name, ty))| { + let reg = quote::format_ident!("__a{}__", idx); + quote! { + let #name = #reg as #ty; + } + }); quote! { #( #bindings )* } @@ -394,20 +394,24 @@ 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(); + let stable_syscalls = expand_func_list(def, false); + let all_syscalls = expand_func_list(def, true); quote! { - #[cfg(test)] - pub const HIGHEST_API_VERSION: u16 = #highest_api_version; + pub fn list_syscalls(include_unstable: bool) -> &'static [&'static [u8]] { + if include_unstable { + #all_syscalls + } else { + #stable_syscalls + } + } impl<'a, E: Ext, M: PolkaVmInstance<E::T>> Runtime<'a, E, M> { fn handle_ecall( &mut self, memory: &mut M, __syscall_symbol__: &[u8], - __available_api_version__: ApiVersion, - ) -> Result<Option<u32>, TrapReason> + ) -> Result<Option<u64>, TrapReason> { #impls } @@ -457,10 +461,6 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { 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 @@ -483,20 +483,14 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { 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); - } + ::log::trace!(target: "runtime::revive::strace", #trace_fmt_str, #( #trace_fmt_args, )* result); result } }; quote! { #cfg - #syscall_symbol if __is_available__(#api_version) => { + #syscall_symbol => { // closure is needed so that "?" can infere the correct type (|| #output { #arg_decoder @@ -517,18 +511,6 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { // 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<u16>| { - 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(); @@ -590,10 +572,8 @@ fn expand_func_doc(def: &EnvDef) -> TokenStream2 { }); 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}**.", - ); + let availability = if func.is_stable { + let info = "\n# Stable API\nThis API is stable and will never change."; quote! { #[doc = #info] } } else { let info = @@ -615,3 +595,20 @@ fn expand_func_doc(def: &EnvDef) -> TokenStream2 { #( #docs )* } } + +fn expand_func_list(def: &EnvDef, include_unstable: bool) -> TokenStream2 { + let docs = def.host_funcs.iter().filter(|f| include_unstable || f.is_stable).map(|f| { + let name = Literal::byte_string(f.name.as_bytes()); + quote! { + #name.as_slice() + } + }); + let len = docs.clone().count(); + + quote! { + { + static FUNCS: [&[u8]; #len] = [#(#docs),*]; + FUNCS.as_slice() + } + } +} diff --git a/substrate/frame/revive/rpc/.sqlx/query-027a434a38822c2ba4439e8f9f9c1135227c1150f2c5083d1c7c6086b717ada0.json b/substrate/frame/revive/rpc/.sqlx/query-027a434a38822c2ba4439e8f9f9c1135227c1150f2c5083d1c7c6086b717ada0.json new file mode 100644 index 0000000000000000000000000000000000000000..016276144901a3a5d85aa1fde5cf6976aa3d2a74 --- /dev/null +++ b/substrate/frame/revive/rpc/.sqlx/query-027a434a38822c2ba4439e8f9f9c1135227c1150f2c5083d1c7c6086b717ada0.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n\t\t\t\tINSERT INTO transaction_hashes (transaction_hash, block_hash, transaction_index)\n\t\t\t\tVALUES ($1, $2, $3)\n\n\t\t\t\tON CONFLICT(transaction_hash) DO UPDATE SET\n\t\t\t\tblock_hash = EXCLUDED.block_hash,\n\t\t\t\ttransaction_index = EXCLUDED.transaction_index\n\t\t\t\t", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "027a434a38822c2ba4439e8f9f9c1135227c1150f2c5083d1c7c6086b717ada0" +} diff --git a/substrate/frame/revive/rpc/.sqlx/query-2348bd412ca114197996e4395fd68c427245f94b80d37ec3aef04cd96fb36298.json b/substrate/frame/revive/rpc/.sqlx/query-2348bd412ca114197996e4395fd68c427245f94b80d37ec3aef04cd96fb36298.json new file mode 100644 index 0000000000000000000000000000000000000000..507564cd05c57efe2cc60d76d6a0bf3a361ae77f --- /dev/null +++ b/substrate/frame/revive/rpc/.sqlx/query-2348bd412ca114197996e4395fd68c427245f94b80d37ec3aef04cd96fb36298.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT COUNT(*) as count\n FROM transaction_hashes\n WHERE block_hash = $1\n ", + "describe": { + "columns": [ + { + "name": "count", + "ordinal": 0, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "2348bd412ca114197996e4395fd68c427245f94b80d37ec3aef04cd96fb36298" +} diff --git a/substrate/frame/revive/rpc/.sqlx/query-29af64347f700919dc2ee12463f332be50096d4e37be04ed8b6f46ac5c242043.json b/substrate/frame/revive/rpc/.sqlx/query-29af64347f700919dc2ee12463f332be50096d4e37be04ed8b6f46ac5c242043.json new file mode 100644 index 0000000000000000000000000000000000000000..2443035c433d782aee91f404eb142086586871fa --- /dev/null +++ b/substrate/frame/revive/rpc/.sqlx/query-29af64347f700919dc2ee12463f332be50096d4e37be04ed8b6f46ac5c242043.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "\n\t\t\tSELECT block_hash, transaction_index\n\t\t\tFROM transaction_hashes\n\t\t\tWHERE transaction_hash = $1\n\t\t\t", + "describe": { + "columns": [ + { + "name": "block_hash", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "transaction_index", + "ordinal": 1, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false + ] + }, + "hash": "29af64347f700919dc2ee12463f332be50096d4e37be04ed8b6f46ac5c242043" +} diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 9f89b74c668f8ac2122c221cce0e54e7cab293d2..014231f7f3e55c9a296e4519e6d9ea95215061bd 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -7,75 +7,73 @@ license = "Apache-2.0" homepage.workspace = true repository.workspace = true description = "An Ethereum JSON-RPC server for pallet-revive." +default-run = "eth-rpc" [[bin]] name = "eth-rpc" path = "src/main.rs" +[[bin]] +name = "eth-indexer" +path = "src/eth-indexer.rs" + +[[bin]] +name = "eth-rpc-tester" +path = "src/eth-rpc-tester.rs" + [[example]] name = "deploy" path = "examples/rust/deploy.rs" -required-features = ["example"] [[example]] name = "transfer" path = "examples/rust/transfer.rs" -required-features = ["example"] [[example]] name = "rpc-playground" path = "examples/rust/rpc-playground.rs" -required-features = ["example"] [[example]] name = "extrinsic" path = "examples/rust/extrinsic.rs" -required-features = ["example"] [[example]] name = "remark-extrinsic" path = "examples/rust/remark-extrinsic.rs" -required-features = ["example"] [dependencies] -clap = { workspace = true, features = ["derive"] } anyhow = { workspace = true } +clap = { workspace = true, features = ["derive", "env"] } +codec = { workspace = true, features = ["derive"] } +ethabi = { version = "18.0.0" } futures = { workspace = true, features = ["thread-pool"] } +hex = { workspace = true } jsonrpsee = { workspace = true, features = ["full"] } -serde_json = { workspace = true } -thiserror = { workspace = true } -sp-crypto-hashing = { workspace = true } -subxt = { workspace = true, default-features = true, features = ["reconnecting-rpc-client"] } -tokio = { workspace = true, features = ["full"] } -codec = { workspace = true, features = ["derive"] } -log.workspace = true +log = { workspace = true } pallet-revive = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-weights = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } +pallet-revive-fixtures = { workspace = true, default-features = true } +prometheus-endpoint = { workspace = true, default-features = true } +rlp = { workspace = true } +sc-cli = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } -sc-cli = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } - -rlp = { workspace = true, optional = true } -subxt-signer = { workspace = true, optional = true, features = [ +sp-arithmetic = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true } +sp-weights = { workspace = true, default-features = true } +sqlx = { version = "0.8.2", features = ["macros", "runtime-tokio", "sqlite"] } +subxt = { workspace = true, default-features = true, features = [ + "reconnecting-rpc-client", +] } +subxt-signer = { workspace = true, features = [ "unstable-eth", ] } -hex = { workspace = true } -hex-literal = { workspace = true, optional = true } -scale-info = { workspace = true } -secp256k1 = { workspace = true, optional = true, features = ["recovery"] } -env_logger = { workspace = true } -ethabi = { version = "18.0.0" } - -[features] -example = ["hex-literal", "rlp", "secp256k1", "subxt-signer"] +thiserror = { workspace = true } +tokio = { workspace = true, features = ["full"] } [dev-dependencies] +env_logger = { workspace = true } static_init = { workspace = true } -hex-literal = { workspace = true } -pallet-revive-fixtures = { workspace = true } substrate-cli-test-utils = { workspace = true } subxt-signer = { workspace = true, features = ["unstable-eth"] } diff --git a/substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile b/substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..77fa846a145cec4633fabd43a5a53467f8b77d2c --- /dev/null +++ b/substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile @@ -0,0 +1,28 @@ +FROM rust AS builder + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + protobuf-compiler \ + clang libclang-dev + +WORKDIR /polkadot +COPY . /polkadot +RUN rustup component add rust-src +RUN cargo build --locked --profile production -p pallet-revive-eth-rpc --bin eth-indexer + +FROM docker.io/parity/base-bin:latest +COPY --from=builder /polkadot/target/production/eth-indexer /usr/local/bin + +USER root +RUN useradd -m -u 1001 -U -s /bin/sh -d /polkadot polkadot && \ +# unclutter and minimize the attack surface + rm -rf /usr/bin /usr/sbin && \ +# check if executable works in this container + /usr/local/bin/eth-indexer --help + +USER polkadot + +ENTRYPOINT ["/usr/local/bin/eth-indexer"] + +# We call the help by default +CMD ["--help"] diff --git a/substrate/frame/revive/rpc/Dockerfile b/substrate/frame/revive/rpc/dockerfiles/eth-rpc/Dockerfile similarity index 100% rename from substrate/frame/revive/rpc/Dockerfile rename to substrate/frame/revive/rpc/dockerfiles/eth-rpc/Dockerfile diff --git a/substrate/frame/revive/rpc/examples/README.md b/substrate/frame/revive/rpc/examples/README.md index b9a2756b381d26cb7155b11c0aa1968eb2836256..1079c254b9c2070fb798c7b8362aa8788d89ab79 100644 --- a/substrate/frame/revive/rpc/examples/README.md +++ b/substrate/frame/revive/rpc/examples/README.md @@ -42,7 +42,7 @@ RUST_LOG="info,eth-rpc=debug" cargo run -p pallet-revive-eth-rpc -- --dev Run one of the examples from the `examples` directory to send a transaction to the node: ```bash -RUST_LOG="info,eth-rpc=debug" cargo run -p pallet-revive-eth-rpc --features example --example deploy +RUST_LOG="info,eth-rpc=debug" cargo run -p pallet-revive-eth-rpc --example deploy ``` ## JS examples diff --git a/substrate/frame/revive/rpc/examples/js/abi/Errors.json b/substrate/frame/revive/rpc/examples/js/abi/Errors.json new file mode 100644 index 0000000000000000000000000000000000000000..2d8dccc771e8b6eef12fde6a27556d9b52899ed0 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/Errors.json @@ -0,0 +1,106 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "CustomError", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "newState", + "type": "bool" + } + ], + "name": "setState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "state", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "triggerAssertError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerCustomError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerDivisionByZero", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerOutOfBoundsError", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerRequireError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerRevertError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "valueMatch", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/Errors.ts b/substrate/frame/revive/rpc/examples/js/abi/Errors.ts new file mode 100644 index 0000000000000000000000000000000000000000..b39567531c6d3d2cf979234d2d30fcf31b305852 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/Errors.ts @@ -0,0 +1,106 @@ +export const ErrorsAbi = [ + { + inputs: [ + { + internalType: "string", + name: "message", + type: "string", + }, + ], + name: "CustomError", + type: "error", + }, + { + inputs: [ + { + internalType: "bool", + name: "newState", + type: "bool", + }, + ], + name: "setState", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "state", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "triggerAssertError", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerCustomError", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerDivisionByZero", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerOutOfBoundsError", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerRequireError", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerRevertError", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "valueMatch", + outputs: [], + stateMutability: "payable", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/EventExample.json b/substrate/frame/revive/rpc/examples/js/abi/EventExample.json new file mode 100644 index 0000000000000000000000000000000000000000..a64c920c40687eda7e6e856d3d6e959b87d8bdf6 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/EventExample.json @@ -0,0 +1,34 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "ExampleEvent", + "type": "event" + }, + { + "inputs": [], + "name": "triggerEvent", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/EventExample.ts b/substrate/frame/revive/rpc/examples/js/abi/EventExample.ts new file mode 100644 index 0000000000000000000000000000000000000000..efb0d741b48fd82c7899e8e5848142203baadcf9 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/EventExample.ts @@ -0,0 +1,34 @@ +export const EventExampleAbi = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + indexed: false, + internalType: "string", + name: "message", + type: "string", + }, + ], + name: "ExampleEvent", + type: "event", + }, + { + inputs: [], + name: "triggerEvent", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/Flipper.json b/substrate/frame/revive/rpc/examples/js/abi/Flipper.json new file mode 100644 index 0000000000000000000000000000000000000000..4c1b163d2943d561d07086ce939b732e916dd8c1 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/Flipper.json @@ -0,0 +1,35 @@ +[ + { + "inputs": [], + "name": "flip", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getValue", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "value", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/Flipper.ts b/substrate/frame/revive/rpc/examples/js/abi/Flipper.ts new file mode 100644 index 0000000000000000000000000000000000000000..d7428beb6aa961b07c5a0d2dc55ab13bd783f42e --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/Flipper.ts @@ -0,0 +1,35 @@ +export const FlipperAbi = [ + { + inputs: [], + name: "flip", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "getValue", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "value", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/FlipperCaller.json b/substrate/frame/revive/rpc/examples/js/abi/FlipperCaller.json new file mode 100644 index 0000000000000000000000000000000000000000..c4ed4228f47dcd974274d7fda6bf963606a8ff6d --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/FlipperCaller.json @@ -0,0 +1,46 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_flipperAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "callFlip", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "callGetValue", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "flipperAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/FlipperCaller.ts b/substrate/frame/revive/rpc/examples/js/abi/FlipperCaller.ts new file mode 100644 index 0000000000000000000000000000000000000000..2d695886d9602fe1df5de42e54f39fe8549ea780 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/FlipperCaller.ts @@ -0,0 +1,46 @@ +export const FlipperCallerAbi = [ + { + inputs: [ + { + internalType: "address", + name: "_flipperAddress", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "callFlip", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "callGetValue", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "flipperAddress", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/PiggyBank.json b/substrate/frame/revive/rpc/examples/js/abi/PiggyBank.json new file mode 100644 index 0000000000000000000000000000000000000000..e6655889e21aac45a3152854808b08b19d4625bb --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/PiggyBank.json @@ -0,0 +1,65 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "withdrawAmount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "remainingBal", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/RevertExample.ts b/substrate/frame/revive/rpc/examples/js/abi/RevertExample.ts new file mode 100644 index 0000000000000000000000000000000000000000..ab483b1811c4a930e05dd5642d8df0daa762052f --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/RevertExample.ts @@ -0,0 +1,14 @@ +export const RevertExampleAbi = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "doRevert", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/event.json b/substrate/frame/revive/rpc/examples/js/abi/event.json deleted file mode 100644 index d36089fbc84ea25fc4cfcc757222526aaa14f1fb..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/rpc/examples/js/abi/event.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "string", - "name": "message", - "type": "string" - } - ], - "name": "ExampleEvent", - "type": "event" - }, - { - "inputs": [], - "name": "triggerEvent", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json deleted file mode 100644 index 2c2cfd5f753377371f4fb6d111fda23aabf863e7..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json +++ /dev/null @@ -1,65 +0,0 @@ -[ - { - "inputs": [], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "deposit", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "getDeposit", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "withdrawAmount", - "type": "uint256" - } - ], - "name": "withdraw", - "outputs": [ - { - "internalType": "uint256", - "name": "remainingBal", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts new file mode 100644 index 0000000000000000000000000000000000000000..a6b8c1b0be56fb4d72fdb00dad93d16fbd93bc70 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts @@ -0,0 +1,65 @@ +export const PiggyBankAbi = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "deposit", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "getDeposit", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "withdrawAmount", + type: "uint256", + }, + ], + name: "withdraw", + outputs: [ + { + internalType: "uint256", + name: "remainingBal", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/revert.json b/substrate/frame/revive/rpc/examples/js/abi/revert.json deleted file mode 100644 index be2945fcc0a59e28ea15ce24bb25badad71cd993..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/rpc/examples/js/abi/revert.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "inputs": [], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "doRevert", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/substrate/frame/revive/rpc/examples/js/bun.lockb b/substrate/frame/revive/rpc/examples/js/bun.lockb index 700dca51da2ad3f843e890258b59c16fd4df6457..39a1d0906b70e1c5cecc7df65976f4db77f41d17 100755 Binary files a/substrate/frame/revive/rpc/examples/js/bun.lockb and b/substrate/frame/revive/rpc/examples/js/bun.lockb differ diff --git a/substrate/frame/revive/rpc/examples/js/contracts/.solhint.json b/substrate/frame/revive/rpc/examples/js/contracts/.solhint.json new file mode 100644 index 0000000000000000000000000000000000000000..ce2220e0b7560eedc6b32ab9f629dbf077c9944c --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/contracts/.solhint.json @@ -0,0 +1,3 @@ +{ + "extends": "solhint:recommended" +} diff --git a/substrate/frame/revive/rpc/examples/js/contracts/Errors.sol b/substrate/frame/revive/rpc/examples/js/contracts/Errors.sol new file mode 100644 index 0000000000000000000000000000000000000000..abbdba8d32eb4a2eb3abda6f39589cca2b14f168 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/contracts/Errors.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract Errors { + bool public state; + + // Payable function that can be used to test insufficient funds errors + function valueMatch(uint256 value) public payable { + require(msg.value == value , "msg.value does not match value"); + } + + function setState(bool newState) public { + state = newState; + } + + // Trigger a require statement failure with a custom error message + function triggerRequireError() public pure { + require(false, "This is a require error"); + } + + // Trigger an assert statement failure + function triggerAssertError() public pure { + assert(false); + } + + // Trigger a revert statement with a custom error message + function triggerRevertError() public pure { + revert("This is a revert error"); + } + + // Trigger a division by zero error + function triggerDivisionByZero() public pure returns (uint256) { + uint256 a = 1; + uint256 b = 0; + return a / b; + } + + // Trigger an out-of-bounds array access + function triggerOutOfBoundsError() public pure returns (uint256) { + uint256[] memory arr = new uint256[](1); + return arr[2]; + } + + // Trigger a custom error + error CustomError(string message); + + function triggerCustomError() public pure { + revert CustomError("This is a custom error"); + } +} + diff --git a/substrate/frame/revive/rpc/examples/js/contracts/Flipper.sol b/substrate/frame/revive/rpc/examples/js/contracts/Flipper.sol new file mode 100644 index 0000000000000000000000000000000000000000..51aaafcae428848557d20c8848ac8cc7a036408c --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/contracts/Flipper.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Flipper - Stores and toggles a boolean value +contract Flipper { + bool public value; + + function flip() external { + value = !value; + } + + function getValue() external view returns (bool) { + return value; + } +} + +// FlipperCaller - Interacts with the Flipper contract +contract FlipperCaller { + // Address of the Flipper contract + address public flipperAddress; + + // Constructor to initialize Flipper's address + constructor(address _flipperAddress) { + flipperAddress = _flipperAddress; + } + + function callFlip() external { + Flipper(flipperAddress).flip(); + } + + function callGetValue() external view returns (bool) { + return Flipper(flipperAddress).getValue(); + } +} + diff --git a/substrate/frame/revive/rpc/examples/js/contracts/PiggyBank.sol b/substrate/frame/revive/rpc/examples/js/contracts/PiggyBank.sol index 1906c46588895567b7edb8cd48a459b39cd2dd22..0c8a4d26f4dc224219ac3507ff7a541b16676241 100644 --- a/substrate/frame/revive/rpc/examples/js/contracts/PiggyBank.sol +++ b/substrate/frame/revive/rpc/examples/js/contracts/PiggyBank.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; contract PiggyBank { - uint private balance; + uint256 private balance; address public owner; constructor() { @@ -11,16 +11,16 @@ contract PiggyBank { balance = 0; } - function deposit() public payable returns (uint) { + function deposit() public payable returns (uint256) { balance += msg.value; return balance; } - function getDeposit() public view returns (uint) { + function getDeposit() public view returns (uint256) { return balance; } - function withdraw(uint withdrawAmount) public returns (uint remainingBal) { + function withdraw(uint256 withdrawAmount) public returns (uint256 remainingBal) { require(msg.sender == owner); balance -= withdrawAmount; (bool success, ) = payable(msg.sender).call{value: withdrawAmount}(""); diff --git a/substrate/frame/revive/rpc/examples/js/contracts/Revert.sol b/substrate/frame/revive/rpc/examples/js/contracts/Revert.sol deleted file mode 100644 index 53f1f8994256edd5dd40945a61bdb89e3d0dd244..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/rpc/examples/js/contracts/Revert.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -contract RevertExample { - constructor() { - } - - function doRevert() public { - revert("revert message"); - } -} diff --git a/substrate/frame/revive/rpc/examples/js/evm/.gitkeep b/substrate/frame/revive/rpc/examples/js/evm/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/substrate/frame/revive/rpc/examples/js/package.json b/substrate/frame/revive/rpc/examples/js/package.json index 3ae1f0fbd799a9e6be03292c0599e47b8e0f061a..f2c4b8d780932a2b55d7ff7976d814c2f6d47364 100644 --- a/substrate/frame/revive/rpc/examples/js/package.json +++ b/substrate/frame/revive/rpc/examples/js/package.json @@ -1,22 +1,23 @@ { - "name": "demo", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "generate-types": "typechain --target=ethers-v6 'abi/*.json'" - }, - "dependencies": { - "@typechain/ethers-v6": "^0.5.1", - "ethers": "^6.13.4", - "solc": "^0.8.28", - "typechain": "^8.3.2" - }, - "devDependencies": { - "typescript": "^5.5.3", - "vite": "^5.4.8" - } + "name": "demo", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@parity/revive": "^0.0.9", + "ethers": "^6.13.5", + "solc": "^0.8.28", + "viem": "^2.22.4" + }, + "devDependencies": { + "prettier": "^3.4.2", + "@types/bun": "^1.1.15", + "typescript": "^5.7.2", + "vite": "^5.4.11" + } } diff --git a/substrate/frame/revive/rpc/examples/js/pvm/Errors.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/Errors.polkavm new file mode 100644 index 0000000000000000000000000000000000000000..48de6e0aa0c6cc1604008ba4e65c237dd557675b Binary files /dev/null and b/substrate/frame/revive/rpc/examples/js/pvm/Errors.polkavm differ diff --git a/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm new file mode 100644 index 0000000000000000000000000000000000000000..cea22e46adcad0dc9bf6375e676dd4923c624c89 Binary files /dev/null and b/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm differ diff --git a/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm new file mode 100644 index 0000000000000000000000000000000000000000..67f11e68f117309169a317d415ba547020c1f568 Binary files /dev/null and b/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm differ diff --git a/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm new file mode 100644 index 0000000000000000000000000000000000000000..b7b037c1c7b31018fccc0d7bfc0e058d05b68588 Binary files /dev/null and b/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm differ diff --git a/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm new file mode 100644 index 0000000000000000000000000000000000000000..2fc5e139825aa174d85127c3f29543fd7ebe05fc Binary files /dev/null and b/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm differ diff --git a/substrate/frame/revive/rpc/examples/js/pvm/event.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/event.polkavm deleted file mode 100644 index 859c1cdf5d3e97c7df8cbad6a0dc0cbd5f8d6388..0000000000000000000000000000000000000000 Binary files a/substrate/frame/revive/rpc/examples/js/pvm/event.polkavm and /dev/null differ diff --git a/substrate/frame/revive/rpc/examples/js/pvm/piggyBank.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/piggyBank.polkavm deleted file mode 100644 index 1a45c15d53f68431b7e5c1dff297c768e1160e97..0000000000000000000000000000000000000000 Binary files a/substrate/frame/revive/rpc/examples/js/pvm/piggyBank.polkavm and /dev/null differ diff --git a/substrate/frame/revive/rpc/examples/js/pvm/revert.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/revert.polkavm deleted file mode 100644 index 7505c402f21b7f8a16914f1f067f94eebb9b3c96..0000000000000000000000000000000000000000 Binary files a/substrate/frame/revive/rpc/examples/js/pvm/revert.polkavm and /dev/null differ diff --git a/substrate/frame/revive/rpc/examples/js/src/balance.ts b/substrate/frame/revive/rpc/examples/js/src/balance.ts new file mode 100644 index 0000000000000000000000000000000000000000..1261dcab7812f9248c2cce86afb2874c15174507 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/src/balance.ts @@ -0,0 +1,8 @@ +import { walletClient } from './lib.ts' + +const recipient = '0x8D97689C9818892B700e27F316cc3E41e17fBeb9' +try { + console.log(`Recipient balance: ${await walletClient.getBalance({ address: recipient })}`) +} catch (err) { + console.error(err) +} diff --git a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts index c6b7700d1ccf44a4e7f38e609abab71749559cbb..b162b8be0adfed696d30892247a10ce234482498 100644 --- a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts +++ b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts @@ -1,11 +1,23 @@ import { compile } from '@parity/revive' +import { format } from 'prettier' +import { parseArgs } from 'node:util' import solc from 'solc' -import { readFileSync, writeFileSync } from 'fs' -import { join } from 'path' +import { readdirSync, readFileSync, writeFileSync } from 'fs' +import { basename, join } from 'path' type CompileInput = Parameters<typeof compile>[0] -type CompileOutput = Awaited<ReturnType<typeof compile>> -type Abi = CompileOutput['contracts'][string][string]['abi'] + +const { + values: { filter }, +} = parseArgs({ + args: process.argv.slice(2), + options: { + filter: { + type: 'string', + short: 'f', + }, + }, +}) function evmCompile(sources: CompileInput) { const input = { @@ -25,32 +37,57 @@ function evmCompile(sources: CompileInput) { console.log('Compiling contracts...') -const input = [ - { file: 'Event.sol', contract: 'EventExample', keypath: 'event' }, - { file: 'Revert.sol', contract: 'RevertExample', keypath: 'revert' }, - { file: 'PiggyBank.sol', contract: 'PiggyBank', keypath: 'piggyBank' }, -] +const rootDir = join(__dirname, '..') +const contractsDir = join(rootDir, 'contracts') +const abiDir = join(rootDir, 'abi') +const pvmDir = join(rootDir, 'pvm') +const evmDir = join(rootDir, 'evm') -for (const { keypath, contract, file } of input) { +const input = readdirSync(contractsDir) + .filter((f) => f.endsWith('.sol')) + .filter((f) => !filter || f.includes(filter)) + +for (const file of input) { + console.log(`🔨 Compiling ${file}...`) + const name = basename(file, '.sol') const input = { - [file]: { content: readFileSync(join('contracts', file), 'utf8') }, + [name]: { content: readFileSync(join(contractsDir, file), 'utf8') }, } - { - console.log(`Compile with solc ${file}`) - const out = JSON.parse(evmCompile(input)) - const entry = out.contracts[file][contract] - writeFileSync(join('evm', `${keypath}.bin`), Buffer.from(entry.evm.bytecode.object, 'hex')) - writeFileSync(join('abi', `${keypath}.json`), JSON.stringify(entry.abi, null, 2)) + console.log('Compiling with revive...') + const reviveOut = await compile(input) + + for (const contracts of Object.values(reviveOut.contracts)) { + for (const [name, contract] of Object.entries(contracts)) { + console.log(`📜 Add PVM contract ${name}`) + const abi = contract.abi + const abiName = `${name}Abi` + writeFileSync(join(abiDir, `${name}.json`), JSON.stringify(abi, null, 2)) + + writeFileSync( + join(abiDir, `${name}.ts`), + await format(`export const ${abiName} = ${JSON.stringify(abi, null, 2)} as const`, { + parser: 'typescript', + }) + ) + + writeFileSync( + join(pvmDir, `${name}.polkavm`), + Buffer.from(contract.evm.bytecode.object, 'hex') + ) + } } - { - console.log(`Compile with revive ${file}`) - const out = await compile(input) - const entry = out.contracts[file][contract] - writeFileSync( - join('pvm', `${keypath}.polkavm`), - Buffer.from(entry.evm.bytecode.object, 'hex') - ) + console.log(`Compile with solc ${file}`) + const evmOut = JSON.parse(evmCompile(input)) as typeof reviveOut + + for (const contracts of Object.values(evmOut.contracts)) { + for (const [name, contract] of Object.entries(contracts)) { + console.log(`📜 Add EVM contract ${name}`) + writeFileSync( + join(evmDir, `${name}.bin`), + Buffer.from(contract.evm.bytecode.object, 'hex') + ) + } } } diff --git a/substrate/frame/revive/rpc/examples/js/src/event.ts b/substrate/frame/revive/rpc/examples/js/src/event.ts index 94cc2560272e6813a17880d6fec2eee3027c9f25..2e672a9772ff412ae6f8e8c5bd36c41f5c9105a5 100644 --- a/substrate/frame/revive/rpc/examples/js/src/event.ts +++ b/substrate/frame/revive/rpc/examples/js/src/event.ts @@ -1,15 +1,29 @@ //! Run with bun run script-event.ts -import { call, getContract, deploy } from './lib.ts' - -try { - const { abi, bytecode } = getContract('event') - const contract = await deploy(bytecode, abi) - const receipt = await call('triggerEvent', await contract.getAddress(), abi) - if (receipt) { - for (const log of receipt.logs) { - console.log('Event log:', JSON.stringify(log, null, 2)) - } - } -} catch (err) { - console.error(err) + +import { abi } from '../abi/event.ts' +import { assert, getByteCode, walletClient } from './lib.ts' + +const deployHash = await walletClient.deployContract({ + abi, + bytecode: getByteCode('event'), +}) +const deployReceipt = await walletClient.waitForTransactionReceipt({ hash: deployHash }) +const contractAddress = deployReceipt.contractAddress +console.log('Contract deployed:', contractAddress) +assert(contractAddress, 'Contract address should be set') + +const { request } = await walletClient.simulateContract({ + account: walletClient.account, + address: contractAddress, + abi, + functionName: 'triggerEvent', +}) + +const hash = await walletClient.writeContract(request) +const receipt = await walletClient.waitForTransactionReceipt({ hash }) +console.log(`Receipt: ${receipt.status}`) +console.log(`Logs receipt: ${receipt.status}`) + +for (const log of receipt.logs) { + console.log('Event log:', log) } diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..2a4ff2edcdf521e36a3d97ec2f3541affd5a76b6 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts @@ -0,0 +1,361 @@ +import { + jsonRpcErrors, + createEnv, + getByteCode, + killProcessOnPort, + waitForHealth, + polkadotSdkPath, +} from './util.ts' +import { afterAll, afterEach, beforeAll, describe, expect, test } from 'bun:test' +import { encodeFunctionData, Hex, parseEther } from 'viem' +import { ErrorsAbi } from '../abi/Errors' +import { FlipperCallerAbi } from '../abi/FlipperCaller' +import { FlipperAbi } from '../abi/Flipper' +import { Subprocess, spawn } from 'bun' +import { fail } from 'node:assert' + +const procs: Subprocess[] = [] +if (!process.env.USE_LIVE_SERVERS) { + procs.push( + // Run geth on port 8546 + await (async () => { + killProcessOnPort(8546) + console.log('Starting geth') + const proc = spawn( + 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( + ' ' + ), + { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } + ) + + await waitForHealth('http://localhost:8546').catch() + return proc + })(), + //Run the substate node + (() => { + killProcessOnPort(9944) + console.log('Starting substrate node') + return spawn( + [ + './target/debug/substrate-node', + '--dev', + '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', + ], + { + stdout: Bun.file('/tmp/kitchensink.out.log'), + stderr: Bun.file('/tmp/kitchensink.err.log'), + cwd: polkadotSdkPath, + } + ) + })(), + // Run eth-rpc on 8545 + await (async () => { + killProcessOnPort(8545) + console.log('Starting eth-rpc') + const proc = spawn( + [ + './target/debug/eth-rpc', + '--dev', + '--node-rpc-url=ws://localhost:9944', + '-l=rpc-metrics=debug,eth-rpc=debug', + ], + { + stdout: Bun.file('/tmp/eth-rpc.out.log'), + stderr: Bun.file('/tmp/eth-rpc.err.log'), + cwd: polkadotSdkPath, + } + ) + await waitForHealth('http://localhost:8545').catch() + return proc + })() + ) +} + +afterEach(() => { + jsonRpcErrors.length = 0 +}) + +afterAll(async () => { + procs.forEach((proc) => proc.kill()) +}) + +const envs = await Promise.all([createEnv('geth'), createEnv('kitchensink')]) + +for (const env of envs) { + describe(env.serverWallet.chain.name, () => { + let errorsAddr: Hex = '0x' + let flipperAddr: Hex = '0x' + let flipperCallerAddr: Hex = '0x' + beforeAll(async () => { + { + const hash = await env.serverWallet.deployContract({ + abi: ErrorsAbi, + bytecode: getByteCode('Errors', env.evm), + }) + const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) + if (!deployReceipt.contractAddress) + throw new Error('Contract address should be set') + errorsAddr = deployReceipt.contractAddress + } + + { + const hash = await env.serverWallet.deployContract({ + abi: FlipperAbi, + bytecode: getByteCode('Flipper', env.evm), + }) + const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) + if (!deployReceipt.contractAddress) + throw new Error('Contract address should be set') + flipperAddr = deployReceipt.contractAddress + } + + { + const hash = await env.serverWallet.deployContract({ + abi: FlipperCallerAbi, + args: [flipperAddr], + bytecode: getByteCode('FlipperCaller', env.evm), + }) + const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) + if (!deployReceipt.contractAddress) + throw new Error('Contract address should be set') + flipperCallerAddr = deployReceipt.contractAddress + } + }) + + test('triggerAssertError', async () => { + try { + await env.accountWallet.readContract({ + address: errorsAddr, + abi: ErrorsAbi, + functionName: 'triggerAssertError', + }) + fail('Expect call to fail') + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.data).toBe( + '0x4e487b710000000000000000000000000000000000000000000000000000000000000001' + ) + expect(lastJsonRpcError?.message).toBe('execution reverted: assert(false)') + } + }) + + test('triggerRevertError', async () => { + try { + await env.accountWallet.readContract({ + address: errorsAddr, + abi: ErrorsAbi, + functionName: 'triggerRevertError', + }) + fail('Expect call to fail') + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.message).toBe('execution reverted: This is a revert error') + expect(lastJsonRpcError?.data).toBe( + '0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001654686973206973206120726576657274206572726f7200000000000000000000' + ) + } + }) + + test('triggerDivisionByZero', async () => { + try { + await env.accountWallet.readContract({ + address: errorsAddr, + abi: ErrorsAbi, + functionName: 'triggerDivisionByZero', + }) + expect.assertions(3) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.data).toBe( + '0x4e487b710000000000000000000000000000000000000000000000000000000000000012' + ) + expect(lastJsonRpcError?.message).toBe( + 'execution reverted: division or modulo by zero' + ) + } + }) + + test('triggerOutOfBoundsError', async () => { + try { + await env.accountWallet.readContract({ + address: errorsAddr, + abi: ErrorsAbi, + functionName: 'triggerOutOfBoundsError', + }) + fail('Expect call to fail') + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.data).toBe( + '0x4e487b710000000000000000000000000000000000000000000000000000000000000032' + ) + expect(lastJsonRpcError?.message).toBe( + 'execution reverted: out-of-bounds access of an array or bytesN' + ) + } + }) + + test('triggerCustomError', async () => { + try { + await env.accountWallet.readContract({ + address: errorsAddr, + abi: ErrorsAbi, + functionName: 'triggerCustomError', + }) + fail('Expect call to fail') + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.data).toBe( + '0x8d6ea8be0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001654686973206973206120637573746f6d206572726f7200000000000000000000' + ) + expect(lastJsonRpcError?.message).toBe('execution reverted') + } + }) + + test('eth_call (not enough funds)', async () => { + try { + await env.emptyWallet.simulateContract({ + address: errorsAddr, + abi: ErrorsAbi, + functionName: 'valueMatch', + value: parseEther('10'), + args: [parseEther('10')], + }) + fail('Expect call to fail') + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(-32000) + expect(lastJsonRpcError?.message).toInclude('insufficient funds') + expect(lastJsonRpcError?.data).toBeUndefined() + } + }) + + test('eth_call transfer (not enough funds)', async () => { + const value = parseEther('10') + const balance = await env.emptyWallet.getBalance(env.emptyWallet.account) + expect(balance, 'Balance should be less than 10').toBeLessThan(value) + try { + await env.emptyWallet.sendTransaction({ + to: '0x75E480dB528101a381Ce68544611C169Ad7EB342', + value, + }) + fail('Expect call to fail') + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(-32000) + expect(lastJsonRpcError?.message).toInclude('insufficient funds') + expect(lastJsonRpcError?.data).toBeUndefined() + } + }) + + test('eth_estimate (not enough funds)', async () => { + try { + await env.emptyWallet.estimateContractGas({ + address: errorsAddr, + abi: ErrorsAbi, + functionName: 'valueMatch', + value: parseEther('10'), + args: [parseEther('10')], + }) + fail('Expect call to fail') + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(-32000) + expect(lastJsonRpcError?.message).toInclude('insufficient funds') + expect(lastJsonRpcError?.data).toBeUndefined() + } + }) + + test('eth_estimate call caller (not enough funds)', async () => { + try { + await env.emptyWallet.estimateContractGas({ + address: errorsAddr, + abi: ErrorsAbi, + functionName: 'valueMatch', + value: parseEther('10'), + args: [parseEther('10')], + }) + fail('Expect call to fail') + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(-32000) + expect(lastJsonRpcError?.message).toInclude('insufficient funds') + expect(lastJsonRpcError?.data).toBeUndefined() + } + }) + + test('eth_estimate (revert)', async () => { + try { + await env.serverWallet.estimateContractGas({ + address: errorsAddr, + abi: ErrorsAbi, + functionName: 'valueMatch', + value: parseEther('11'), + args: [parseEther('10')], + }) + fail('Expect call to fail') + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.message).toBe( + 'execution reverted: msg.value does not match value' + ) + expect(lastJsonRpcError?.data).toBe( + '0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001e6d73672e76616c756520646f6573206e6f74206d617463682076616c75650000' + ) + } + }) + + test('eth_get_balance (no account)', async () => { + const balance = await env.serverWallet.getBalance({ + address: '0x0000000000000000000000000000000000000123', + }) + expect(balance).toBe(0n) + }) + + test('eth_estimate (not enough funds to cover gas specified)', async () => { + let balance = await env.serverWallet.getBalance(env.emptyWallet.account) + expect(balance).toBe(0n) + try { + await env.emptyWallet.estimateContractGas({ + address: errorsAddr, + abi: ErrorsAbi, + functionName: 'setState', + args: [true], + }) + fail('Expect call to fail') + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(-32000) + expect(lastJsonRpcError?.message).toInclude('insufficient funds') + expect(lastJsonRpcError?.data).toBeUndefined() + } + }) + + test('eth_estimate (no gas specified)', async () => { + let balance = await env.serverWallet.getBalance(env.emptyWallet.account) + expect(balance).toBe(0n) + + const data = encodeFunctionData({ + abi: ErrorsAbi, + functionName: 'setState', + args: [true], + }) + + await env.emptyWallet.request({ + method: 'eth_estimateGas', + params: [ + { + data, + from: env.emptyWallet.account.address, + to: errorsAddr, + }, + ], + }) + }) + }) +} diff --git a/substrate/frame/revive/rpc/examples/js/src/lib.ts b/substrate/frame/revive/rpc/examples/js/src/lib.ts index 975d8faf15b31327783fb4aecc63f57895dc92fb..1470f492e34d6d451f90ddc161c054701f125863 100644 --- a/substrate/frame/revive/rpc/examples/js/src/lib.ts +++ b/substrate/frame/revive/rpc/examples/js/src/lib.ts @@ -1,22 +1,11 @@ -import { - Contract, - ContractFactory, - JsonRpcProvider, - TransactionReceipt, - TransactionResponse, - Wallet, -} from 'ethers' import { readFileSync } from 'node:fs' -import type { compile } from '@parity/revive' import { spawn } from 'node:child_process' import { parseArgs } from 'node:util' -import { BaseContract } from 'ethers' - -type CompileOutput = Awaited<ReturnType<typeof compile>> -type Abi = CompileOutput['contracts'][string][string]['abi'] +import { createWalletClient, defineChain, Hex, http, parseEther, publicActions } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' const { - values: { geth, westend, ['private-key']: privateKey }, + values: { geth, proxy, westend, endowment, ['private-key']: privateKey }, } = parseArgs({ args: process.argv.slice(2), options: { @@ -24,6 +13,13 @@ const { type: 'string', short: 'k', }, + endowment: { + type: 'string', + short: 'e', + }, + proxy: { + type: 'boolean', + }, geth: { type: 'boolean', }, @@ -42,7 +38,7 @@ if (geth) { '--http.api', 'web3,eth,debug,personal,net', '--http.port', - '8546', + process.env.GETH_PORT ?? '8546', '--dev', '--verbosity', '0', @@ -54,57 +50,78 @@ if (geth) { child.unref() await new Promise((resolve) => setTimeout(resolve, 500)) } - -export const provider = new JsonRpcProvider( - westend +const rpcUrl = proxy + ? 'http://localhost:8080' + : westend ? 'https://westend-asset-hub-eth-rpc.polkadot.io' : geth ? 'http://localhost:8546' : 'http://localhost:8545' -) -export const signer = privateKey ? new Wallet(privateKey, provider) : await provider.getSigner() -console.log(`Signer address: ${await signer.getAddress()}, Nonce: ${await signer.getNonce()}`) +export const chain = defineChain({ + id: geth ? 1337 : 420420420, + name: 'Asset Hub Westend', + network: 'asset-hub', + nativeCurrency: { + name: 'Westie', + symbol: 'WST', + decimals: 18, + }, + rpcUrls: { + default: { + http: [rpcUrl], + }, + }, + testnet: true, +}) + +const wallet = createWalletClient({ + transport: http(), + chain, +}) +const [account] = await wallet.getAddresses() +export const serverWalletClient = createWalletClient({ + account, + transport: http(), + chain, +}) + +export const walletClient = await (async () => { + if (privateKey) { + const account = privateKeyToAccount(`0x${privateKey}`) + console.log(`Wallet address ${account.address}`) + + const wallet = createWalletClient({ + account, + transport: http(), + chain, + }) + + if (endowment) { + await serverWalletClient.sendTransaction({ + to: account.address, + value: parseEther(endowment), + }) + console.log(`Endowed address ${account.address} with: ${endowment}`) + } + + return wallet.extend(publicActions) + } else { + return serverWalletClient.extend(publicActions) + } +})() /** * Get one of the pre-built contracts * @param name - the contract name */ -export function getContract(name: string): { abi: Abi; bytecode: string } { +export function getByteCode(name: string): Hex { const bytecode = geth ? readFileSync(`evm/${name}.bin`) : readFileSync(`pvm/${name}.polkavm`) - const abi = JSON.parse(readFileSync(`abi/${name}.json`, 'utf8')) as Abi - return { abi, bytecode: Buffer.from(bytecode).toString('hex') } + return `0x${Buffer.from(bytecode).toString('hex')}` } -/** - * Deploy a contract - * @returns the contract address - **/ -export async function deploy(bytecode: string, abi: Abi, args: any[] = []): Promise<BaseContract> { - console.log('Deploying contract with', args) - const contractFactory = new ContractFactory(abi, bytecode, signer) - - const contract = await contractFactory.deploy(args) - await contract.waitForDeployment() - const address = await contract.getAddress() - console.log(`Contract deployed: ${address}`) - - return contract -} - -/** - * Call a contract - **/ -export async function call( - method: string, - address: string, - abi: Abi, - args: any[] = [], - opts: { value?: bigint } = {} -): Promise<null | TransactionReceipt> { - console.log(`Calling ${method} at ${address} with`, args, opts) - const contract = new Contract(address, abi, signer) - const tx = (await contract[method](...args, opts)) as TransactionResponse - console.log('Call transaction hash:', tx.hash) - return tx.wait() +export function assert(condition: any, message: string): asserts condition { + if (!condition) { + throw new Error(message) + } } diff --git a/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts b/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts index 7a8edbde36626f097b7e87ef0ec0e5d5812d20f8..4983a6f3b301ea311fa7b5ff00f60a46eda3e607 100644 --- a/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts +++ b/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts @@ -1,24 +1,64 @@ -import { provider, call, getContract, deploy } from './lib.ts' -import { parseEther } from 'ethers' -import { PiggyBank } from '../types/ethers-contracts/PiggyBank' +import { assert, getByteCode, walletClient } from './lib.ts' +import { PiggyBankAbi } from '../abi/piggyBank.ts' +import { parseEther } from 'viem' -try { - const { abi, bytecode } = getContract('piggyBank') - const contract = (await deploy(bytecode, abi)) as PiggyBank - const address = await contract.getAddress() +const hash = await walletClient.deployContract({ + abi: PiggyBankAbi, + bytecode: getByteCode('PiggyBank'), +}) +const deployReceipt = await walletClient.waitForTransactionReceipt({ hash }) +const contractAddress = deployReceipt.contractAddress +console.log('Contract deployed:', contractAddress) +assert(contractAddress, 'Contract address should be set') - let receipt = await call('deposit', address, abi, [], { - value: parseEther('10.0'), +// Deposit 10 WST +{ + const result = await walletClient.estimateContractGas({ + account: walletClient.account, + address: contractAddress, + abi: PiggyBankAbi, + functionName: 'deposit', + value: parseEther('10'), }) - console.log('Deposit receipt:', receipt?.status) - console.log(`Contract balance: ${await provider.getBalance(address)}`) - console.log('deposit: ', await contract.getDeposit()) + console.log(`Gas estimate: ${result}`) - receipt = await call('withdraw', address, abi, [parseEther('5.0')]) - console.log('Withdraw receipt:', receipt?.status) - console.log(`Contract balance: ${await provider.getBalance(address)}`) - console.log('deposit: ', await contract.getDeposit()) -} catch (err) { - console.error(err) + const { request } = await walletClient.simulateContract({ + account: walletClient.account, + address: contractAddress, + abi: PiggyBankAbi, + functionName: 'deposit', + value: parseEther('10'), + }) + + const hash = await walletClient.writeContract(request) + const receipt = await walletClient.waitForTransactionReceipt({ hash }) + console.log(`Deposit receipt: ${receipt.status}`) +} + +// Withdraw 5 WST +{ + const { request } = await walletClient.simulateContract({ + account: walletClient.account, + address: contractAddress, + abi: PiggyBankAbi, + functionName: 'withdraw', + args: [parseEther('5')], + }) + + const hash = await walletClient.writeContract(request) + const receipt = await walletClient.waitForTransactionReceipt({ hash }) + console.log(`Withdraw receipt: ${receipt.status}`) + + // Check remaining balance + const balance = await walletClient.readContract({ + address: contractAddress, + abi: PiggyBankAbi, + functionName: 'getDeposit', + }) + + console.log(`Get deposit: ${balance}`) + console.log( + `Get contract balance: ${await walletClient.getBalance({ address: contractAddress })}` + ) } diff --git a/substrate/frame/revive/rpc/examples/js/src/revert.ts b/substrate/frame/revive/rpc/examples/js/src/revert.ts deleted file mode 100644 index ea1bf4eceeb9c3991412b0d9de9c9c0513b45815..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/rpc/examples/js/src/revert.ts +++ /dev/null @@ -1,10 +0,0 @@ -//! Run with bun run script-revert.ts -import { call, getContract, deploy } from './lib.ts' - -try { - const { abi, bytecode } = getContract('revert') - const contract = await deploy(bytecode, abi) - await call('doRevert', await contract.getAddress(), abi) -} catch (err) { - console.error(err) -} diff --git a/substrate/frame/revive/rpc/examples/js/src/spammer.ts b/substrate/frame/revive/rpc/examples/js/src/spammer.ts new file mode 100644 index 0000000000000000000000000000000000000000..c038afa71f0aa5bb9227a17578e7af68409efe4a --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/src/spammer.ts @@ -0,0 +1,104 @@ +import { spawn } from 'bun' +import { + createEnv, + getByteCode, + killProcessOnPort, + polkadotSdkPath, + timeout, + wait, + waitForHealth, +} from './util' +import { FlipperAbi } from '../abi/Flipper' + +//Run the substate node +console.log('🚀 Start kitchensink...') +killProcessOnPort(9944) +spawn( + [ + './target/debug/substrate-node', + '--dev', + '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', + ], + { + stdout: Bun.file('/tmp/kitchensink.out.log'), + stderr: Bun.file('/tmp/kitchensink.err.log'), + cwd: polkadotSdkPath, + } +) + +// Run eth-indexer +console.log('🔠Start indexer...') +spawn( + [ + './target/debug/eth-indexer', + '--node-rpc-url=ws://localhost:9944', + '-l=eth-rpc=debug', + '--database-url ${polkadotSdkPath}/substrate/frame/revive/rpc/tx_hashes.db', + ], + { + stdout: Bun.file('/tmp/eth-indexer.out.log'), + stderr: Bun.file('/tmp/eth-indexer.err.log'), + cwd: polkadotSdkPath, + } +) + +// Run eth-rpc on 8545 +console.log('💻 Start eth-rpc...') +killProcessOnPort(8545) +spawn( + [ + './target/debug/eth-rpc', + '--dev', + '--node-rpc-url=ws://localhost:9944', + '-l=rpc-metrics=debug,eth-rpc=debug', + ], + { + stdout: Bun.file('/tmp/eth-rpc.out.log'), + stderr: Bun.file('/tmp/eth-rpc.err.log'), + cwd: polkadotSdkPath, + } +) +await waitForHealth('http://localhost:8545').catch() + +const env = await createEnv('kitchensink') +const wallet = env.accountWallet + +console.log('🚀 Deploy flipper...') +const hash = await wallet.deployContract({ + abi: FlipperAbi, + bytecode: getByteCode('Flipper'), +}) + +const deployReceipt = await wallet.waitForTransactionReceipt({ hash }) +if (!deployReceipt.contractAddress) throw new Error('Contract address should be set') +const flipperAddr = deployReceipt.contractAddress + +let nonce = await wallet.getTransactionCount(wallet.account) +let callCount = 0 + +console.log('🔄 Starting nonce:', nonce) +console.log('🔄 Starting loop...') +try { + while (true) { + callCount++ + console.log(`🔄 Call flip (${callCount})...`) + const { request } = await wallet.simulateContract({ + account: wallet.account, + address: flipperAddr, + abi: FlipperAbi, + functionName: 'flip', + }) + + console.log(`🔄 Submit flip (call ${callCount}...`) + + await Promise.race([ + (async () => { + const hash = await wallet.writeContract(request) + await wallet.waitForTransactionReceipt({ hash }) + })(), + timeout(15_000), + ]) + } +} catch (err) { + console.error('Failed with error:', err) +} diff --git a/substrate/frame/revive/rpc/examples/js/src/transfer.ts b/substrate/frame/revive/rpc/examples/js/src/transfer.ts index ae2dd50f2af868b84b5b29ad70b6da5df78ebb60..aef9a487b0c014c72b2d1b8f6c44796268ec1b00 100644 --- a/substrate/frame/revive/rpc/examples/js/src/transfer.ts +++ b/substrate/frame/revive/rpc/examples/js/src/transfer.ts @@ -1,17 +1,18 @@ -import { parseEther } from 'ethers' -import { provider, signer } from './lib.ts' +import { parseEther } from 'viem' +import { walletClient } from './lib.ts' const recipient = '0x75E480dB528101a381Ce68544611C169Ad7EB342' try { - console.log(`Signer balance: ${await provider.getBalance(signer.address)}`) - console.log(`Recipient balance: ${await provider.getBalance(recipient)}`) - await signer.sendTransaction({ + console.log(`Signer balance: ${await walletClient.getBalance(walletClient.account)}`) + console.log(`Recipient balance: ${await walletClient.getBalance({ address: recipient })}`) + + await walletClient.sendTransaction({ to: recipient, value: parseEther('1.0'), }) console.log(`Sent: ${parseEther('1.0')}`) - console.log(`Signer balance: ${await provider.getBalance(signer.address)}`) - console.log(`Recipient balance: ${await provider.getBalance(recipient)}`) + console.log(`Signer balance: ${await walletClient.getBalance(walletClient.account)}`) + console.log(`Recipient balance: ${await walletClient.getBalance({ address: recipient })}`) } catch (err) { console.error(err) } diff --git a/substrate/frame/revive/rpc/examples/js/src/util.ts b/substrate/frame/revive/rpc/examples/js/src/util.ts new file mode 100644 index 0000000000000000000000000000000000000000..2991bdfe6367be7975d337c44e014372294824fb --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/src/util.ts @@ -0,0 +1,142 @@ +import { spawnSync } from 'bun' +import { resolve } from 'path' +import { readFileSync } from 'fs' +import { createWalletClient, defineChain, Hex, http, publicActions } from 'viem' +import { privateKeyToAccount, nonceManager } from 'viem/accounts' + +export function getByteCode(name: string, evm: boolean = false): Hex { + const bytecode = evm ? readFileSync(`evm/${name}.bin`) : readFileSync(`pvm/${name}.polkavm`) + return `0x${Buffer.from(bytecode).toString('hex')}` +} + +export type JsonRpcError = { + code: number + message: string + data: Hex +} + +export const polkadotSdkPath = resolve(__dirname, '../../../../../../..') + +export function killProcessOnPort(port: number) { + // Check which process is using the specified port + const result = spawnSync(['lsof', '-ti', `:${port}`]) + const output = result.stdout.toString().trim() + + if (output) { + console.log(`Port ${port} is in use. Killing process...`) + const pids = output.split('\n') + + // Kill each process using the port + for (const pid of pids) { + spawnSync(['kill', '-9', pid]) + console.log(`Killed process with PID: ${pid}`) + } + } +} + +export let jsonRpcErrors: JsonRpcError[] = [] +export async function createEnv(name: 'geth' | 'kitchensink') { + const gethPort = process.env.GETH_PORT || '8546' + const kitchensinkPort = process.env.KITCHENSINK_PORT || '8545' + const url = `http://localhost:${name == 'geth' ? gethPort : kitchensinkPort}` + const chain = defineChain({ + id: name == 'geth' ? 1337 : 420420420, + name, + nativeCurrency: { + name: 'Westie', + symbol: 'WST', + decimals: 18, + }, + rpcUrls: { + default: { + http: [url], + }, + }, + testnet: true, + }) + + const transport = http(url, { + onFetchResponse: async (response) => { + const raw = await response.clone().json() + if (raw.error) { + jsonRpcErrors.push(raw.error as JsonRpcError) + } + }, + }) + + const wallet = createWalletClient({ + transport, + chain, + }) + + const [account] = await wallet.getAddresses() + const serverWallet = createWalletClient({ + account, + transport, + chain, + }).extend(publicActions) + + const accountWallet = createWalletClient({ + account: privateKeyToAccount( + '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133', + { nonceManager } + ), + transport, + chain, + }).extend(publicActions) + + const emptyWallet = createWalletClient({ + account: privateKeyToAccount( + '0x4450c571bae82da0528ecf76fcf7079e12ecc46dc873c9cacb6db8b75ed22f41', + { nonceManager } + ), + transport, + chain, + }).extend(publicActions) + + return { serverWallet, emptyWallet, accountWallet, evm: name == 'geth' } +} + +export function wait(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +export function timeout(ms: number) { + return new Promise((_resolve, reject) => setTimeout(() => reject(new Error('timeout hit')), ms)) +} + +// wait for http request to return 200 +export function waitForHealth(url: string) { + return new Promise<void>((resolve, reject) => { + const start = Date.now() + const interval = setInterval(async () => { + try { + const res = await fetch(url, { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify({ + jsonrpc: '2.0', + method: 'eth_syncing', + params: [], + id: 1, + }), + }) + + if (res.status !== 200) { + return + } + + clearInterval(interval) + resolve() + } catch (_err) { + const elapsed = Date.now() - start + if (elapsed > 30_000) { + clearInterval(interval) + reject(new Error('hit timeout')) + } + } + }, 1000) + }) +} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Event.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Event.ts deleted file mode 100644 index d65f953969f0c3158fb527103021f59dd960598c..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Event.ts +++ /dev/null @@ -1,117 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -import type { - BaseContract, - BigNumberish, - BytesLike, - FunctionFragment, - Result, - Interface, - EventFragment, - AddressLike, - ContractRunner, - ContractMethod, - Listener, -} from 'ethers' -import type { - TypedContractEvent, - TypedDeferredTopicFilter, - TypedEventLog, - TypedLogDescription, - TypedListener, - TypedContractMethod, -} from './common' - -export interface EventInterface extends Interface { - getFunction(nameOrSignature: 'triggerEvent'): FunctionFragment - - getEvent(nameOrSignatureOrTopic: 'ExampleEvent'): EventFragment - - encodeFunctionData(functionFragment: 'triggerEvent', values?: undefined): string - - decodeFunctionResult(functionFragment: 'triggerEvent', data: BytesLike): Result -} - -export namespace ExampleEventEvent { - export type InputTuple = [sender: AddressLike, value: BigNumberish, message: string] - export type OutputTuple = [sender: string, value: bigint, message: string] - export interface OutputObject { - sender: string - value: bigint - message: string - } - export type Event = TypedContractEvent<InputTuple, OutputTuple, OutputObject> - export type Filter = TypedDeferredTopicFilter<Event> - export type Log = TypedEventLog<Event> - export type LogDescription = TypedLogDescription<Event> -} - -export interface Event extends BaseContract { - connect(runner?: ContractRunner | null): Event - waitForDeployment(): Promise<this> - - interface: EventInterface - - queryFilter<TCEvent extends TypedContractEvent>( - event: TCEvent, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise<Array<TypedEventLog<TCEvent>>> - queryFilter<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise<Array<TypedEventLog<TCEvent>>> - - on<TCEvent extends TypedContractEvent>( - event: TCEvent, - listener: TypedListener<TCEvent> - ): Promise<this> - on<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - listener: TypedListener<TCEvent> - ): Promise<this> - - once<TCEvent extends TypedContractEvent>( - event: TCEvent, - listener: TypedListener<TCEvent> - ): Promise<this> - once<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - listener: TypedListener<TCEvent> - ): Promise<this> - - listeners<TCEvent extends TypedContractEvent>( - event: TCEvent - ): Promise<Array<TypedListener<TCEvent>>> - listeners(eventName?: string): Promise<Array<Listener>> - removeAllListeners<TCEvent extends TypedContractEvent>(event?: TCEvent): Promise<this> - - triggerEvent: TypedContractMethod<[], [void], 'nonpayable'> - - getFunction<T extends ContractMethod = ContractMethod>(key: string | FunctionFragment): T - - getFunction(nameOrSignature: 'triggerEvent'): TypedContractMethod<[], [void], 'nonpayable'> - - getEvent( - key: 'ExampleEvent' - ): TypedContractEvent< - ExampleEventEvent.InputTuple, - ExampleEventEvent.OutputTuple, - ExampleEventEvent.OutputObject - > - - filters: { - 'ExampleEvent(address,uint256,string)': TypedContractEvent< - ExampleEventEvent.InputTuple, - ExampleEventEvent.OutputTuple, - ExampleEventEvent.OutputObject - > - ExampleEvent: TypedContractEvent< - ExampleEventEvent.InputTuple, - ExampleEventEvent.OutputTuple, - ExampleEventEvent.OutputObject - > - } -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/PiggyBank.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/PiggyBank.ts deleted file mode 100644 index ca137fcc8b30aec6ebf9fda80248f890ed5668d7..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/PiggyBank.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -import type { - BaseContract, - BigNumberish, - BytesLike, - FunctionFragment, - Result, - Interface, - ContractRunner, - ContractMethod, - Listener, -} from 'ethers' -import type { - TypedContractEvent, - TypedDeferredTopicFilter, - TypedEventLog, - TypedListener, - TypedContractMethod, -} from './common' - -export interface PiggyBankInterface extends Interface { - getFunction(nameOrSignature: 'deposit' | 'getDeposit' | 'owner' | 'withdraw'): FunctionFragment - - encodeFunctionData(functionFragment: 'deposit', values?: undefined): string - encodeFunctionData(functionFragment: 'getDeposit', values?: undefined): string - encodeFunctionData(functionFragment: 'owner', values?: undefined): string - encodeFunctionData(functionFragment: 'withdraw', values: [BigNumberish]): string - - decodeFunctionResult(functionFragment: 'deposit', data: BytesLike): Result - decodeFunctionResult(functionFragment: 'getDeposit', data: BytesLike): Result - decodeFunctionResult(functionFragment: 'owner', data: BytesLike): Result - decodeFunctionResult(functionFragment: 'withdraw', data: BytesLike): Result -} - -export interface PiggyBank extends BaseContract { - connect(runner?: ContractRunner | null): PiggyBank - waitForDeployment(): Promise<this> - - interface: PiggyBankInterface - - queryFilter<TCEvent extends TypedContractEvent>( - event: TCEvent, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise<Array<TypedEventLog<TCEvent>>> - queryFilter<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise<Array<TypedEventLog<TCEvent>>> - - on<TCEvent extends TypedContractEvent>( - event: TCEvent, - listener: TypedListener<TCEvent> - ): Promise<this> - on<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - listener: TypedListener<TCEvent> - ): Promise<this> - - once<TCEvent extends TypedContractEvent>( - event: TCEvent, - listener: TypedListener<TCEvent> - ): Promise<this> - once<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - listener: TypedListener<TCEvent> - ): Promise<this> - - listeners<TCEvent extends TypedContractEvent>( - event: TCEvent - ): Promise<Array<TypedListener<TCEvent>>> - listeners(eventName?: string): Promise<Array<Listener>> - removeAllListeners<TCEvent extends TypedContractEvent>(event?: TCEvent): Promise<this> - - deposit: TypedContractMethod<[], [bigint], 'payable'> - - getDeposit: TypedContractMethod<[], [bigint], 'view'> - - owner: TypedContractMethod<[], [string], 'view'> - - withdraw: TypedContractMethod<[withdrawAmount: BigNumberish], [bigint], 'nonpayable'> - - getFunction<T extends ContractMethod = ContractMethod>(key: string | FunctionFragment): T - - getFunction(nameOrSignature: 'deposit'): TypedContractMethod<[], [bigint], 'payable'> - getFunction(nameOrSignature: 'getDeposit'): TypedContractMethod<[], [bigint], 'view'> - getFunction(nameOrSignature: 'owner'): TypedContractMethod<[], [string], 'view'> - getFunction( - nameOrSignature: 'withdraw' - ): TypedContractMethod<[withdrawAmount: BigNumberish], [bigint], 'nonpayable'> - - filters: {} -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Revert.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Revert.ts deleted file mode 100644 index ad6e23b38a65dbd4859d821c00e90f8e08efe1e0..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Revert.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -import type { - BaseContract, - BytesLike, - FunctionFragment, - Result, - Interface, - ContractRunner, - ContractMethod, - Listener, -} from 'ethers' -import type { - TypedContractEvent, - TypedDeferredTopicFilter, - TypedEventLog, - TypedListener, - TypedContractMethod, -} from './common' - -export interface RevertInterface extends Interface { - getFunction(nameOrSignature: 'doRevert'): FunctionFragment - - encodeFunctionData(functionFragment: 'doRevert', values?: undefined): string - - decodeFunctionResult(functionFragment: 'doRevert', data: BytesLike): Result -} - -export interface Revert extends BaseContract { - connect(runner?: ContractRunner | null): Revert - waitForDeployment(): Promise<this> - - interface: RevertInterface - - queryFilter<TCEvent extends TypedContractEvent>( - event: TCEvent, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise<Array<TypedEventLog<TCEvent>>> - queryFilter<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise<Array<TypedEventLog<TCEvent>>> - - on<TCEvent extends TypedContractEvent>( - event: TCEvent, - listener: TypedListener<TCEvent> - ): Promise<this> - on<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - listener: TypedListener<TCEvent> - ): Promise<this> - - once<TCEvent extends TypedContractEvent>( - event: TCEvent, - listener: TypedListener<TCEvent> - ): Promise<this> - once<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - listener: TypedListener<TCEvent> - ): Promise<this> - - listeners<TCEvent extends TypedContractEvent>( - event: TCEvent - ): Promise<Array<TypedListener<TCEvent>>> - listeners(eventName?: string): Promise<Array<Listener>> - removeAllListeners<TCEvent extends TypedContractEvent>(event?: TCEvent): Promise<this> - - doRevert: TypedContractMethod<[], [void], 'nonpayable'> - - getFunction<T extends ContractMethod = ContractMethod>(key: string | FunctionFragment): T - - getFunction(nameOrSignature: 'doRevert'): TypedContractMethod<[], [void], 'nonpayable'> - - filters: {} -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/common.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/common.ts deleted file mode 100644 index 247b9468ece25709be8181295f8a322d7e754fa4..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/common.ts +++ /dev/null @@ -1,100 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -import type { - FunctionFragment, - Typed, - EventFragment, - ContractTransaction, - ContractTransactionResponse, - DeferredTopicFilter, - EventLog, - TransactionRequest, - LogDescription, -} from 'ethers' - -export interface TypedDeferredTopicFilter<_TCEvent extends TypedContractEvent> - extends DeferredTopicFilter {} - -export interface TypedContractEvent< - InputTuple extends Array<any> = any, - OutputTuple extends Array<any> = any, - OutputObject = any, -> { - ( - ...args: Partial<InputTuple> - ): TypedDeferredTopicFilter<TypedContractEvent<InputTuple, OutputTuple, OutputObject>> - name: string - fragment: EventFragment - getFragment(...args: Partial<InputTuple>): EventFragment -} - -type __TypechainAOutputTuple<T> = T extends TypedContractEvent<infer _U, infer W> ? W : never -type __TypechainOutputObject<T> = - T extends TypedContractEvent<infer _U, infer _W, infer V> ? V : never - -export interface TypedEventLog<TCEvent extends TypedContractEvent> extends Omit<EventLog, 'args'> { - args: __TypechainAOutputTuple<TCEvent> & __TypechainOutputObject<TCEvent> -} - -export interface TypedLogDescription<TCEvent extends TypedContractEvent> - extends Omit<LogDescription, 'args'> { - args: __TypechainAOutputTuple<TCEvent> & __TypechainOutputObject<TCEvent> -} - -export type TypedListener<TCEvent extends TypedContractEvent> = ( - ...listenerArg: [...__TypechainAOutputTuple<TCEvent>, TypedEventLog<TCEvent>, ...undefined[]] -) => void - -export type MinEthersFactory<C, ARGS> = { - deploy(...a: ARGS[]): Promise<C> -} - -export type GetContractTypeFromFactory<F> = F extends MinEthersFactory<infer C, any> ? C : never -export type GetARGsTypeFromFactory<F> = - F extends MinEthersFactory<any, any> ? Parameters<F['deploy']> : never - -export type StateMutability = 'nonpayable' | 'payable' | 'view' - -export type BaseOverrides = Omit<TransactionRequest, 'to' | 'data'> -export type NonPayableOverrides = Omit<BaseOverrides, 'value' | 'blockTag' | 'enableCcipRead'> -export type PayableOverrides = Omit<BaseOverrides, 'blockTag' | 'enableCcipRead'> -export type ViewOverrides = Omit<TransactionRequest, 'to' | 'data'> -export type Overrides<S extends StateMutability> = S extends 'nonpayable' - ? NonPayableOverrides - : S extends 'payable' - ? PayableOverrides - : ViewOverrides - -export type PostfixOverrides<A extends Array<any>, S extends StateMutability> = - | A - | [...A, Overrides<S>] -export type ContractMethodArgs<A extends Array<any>, S extends StateMutability> = PostfixOverrides< - { [I in keyof A]-?: A[I] | Typed }, - S -> - -export type DefaultReturnType<R> = R extends Array<any> ? R[0] : R - -// export interface ContractMethod<A extends Array<any> = Array<any>, R = any, D extends R | ContractTransactionResponse = R | ContractTransactionResponse> { -export interface TypedContractMethod< - A extends Array<any> = Array<any>, - R = any, - S extends StateMutability = 'payable', -> { - ( - ...args: ContractMethodArgs<A, S> - ): S extends 'view' ? Promise<DefaultReturnType<R>> : Promise<ContractTransactionResponse> - - name: string - - fragment: FunctionFragment - - getFragment(...args: ContractMethodArgs<A, S>): FunctionFragment - - populateTransaction(...args: ContractMethodArgs<A, S>): Promise<ContractTransaction> - staticCall(...args: ContractMethodArgs<A, 'view'>): Promise<DefaultReturnType<R>> - send(...args: ContractMethodArgs<A, S>): Promise<ContractTransactionResponse> - estimateGas(...args: ContractMethodArgs<A, S>): Promise<bigint> - staticCallResult(...args: ContractMethodArgs<A, 'view'>): Promise<R> -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Event__factory.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Event__factory.ts deleted file mode 100644 index 2e16b18a7ed8c5e4d37abe614ed118388ffb1be3..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Event__factory.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ - -import { Contract, Interface, type ContractRunner } from 'ethers' -import type { Event, EventInterface } from '../Event' - -const _abi = [ - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'sender', - type: 'address', - }, - { - indexed: false, - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - { - indexed: false, - internalType: 'string', - name: 'message', - type: 'string', - }, - ], - name: 'ExampleEvent', - type: 'event', - }, - { - inputs: [], - name: 'triggerEvent', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const - -export class Event__factory { - static readonly abi = _abi - static createInterface(): EventInterface { - return new Interface(_abi) as EventInterface - } - static connect(address: string, runner?: ContractRunner | null): Event { - return new Contract(address, _abi, runner) as unknown as Event - } -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/PiggyBank__factory.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/PiggyBank__factory.ts deleted file mode 100644 index 0efea80ed2dc88c637bacd0eb9dda91858426a23..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/PiggyBank__factory.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ - -import { Contract, Interface, type ContractRunner } from 'ethers' -import type { PiggyBank, PiggyBankInterface } from '../PiggyBank' - -const _abi = [ - { - inputs: [], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - inputs: [], - name: 'deposit', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [], - name: 'getDeposit', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'owner', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'withdrawAmount', - type: 'uint256', - }, - ], - name: 'withdraw', - outputs: [ - { - internalType: 'uint256', - name: 'remainingBal', - type: 'uint256', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const - -export class PiggyBank__factory { - static readonly abi = _abi - static createInterface(): PiggyBankInterface { - return new Interface(_abi) as PiggyBankInterface - } - static connect(address: string, runner?: ContractRunner | null): PiggyBank { - return new Contract(address, _abi, runner) as unknown as PiggyBank - } -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Revert__factory.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Revert__factory.ts deleted file mode 100644 index ece1c6b5426ef7e31547d3cd736b06248d603746..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Revert__factory.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ - -import { Contract, Interface, type ContractRunner } from 'ethers' -import type { Revert, RevertInterface } from '../Revert' - -const _abi = [ - { - inputs: [], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - inputs: [], - name: 'doRevert', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const - -export class Revert__factory { - static readonly abi = _abi - static createInterface(): RevertInterface { - return new Interface(_abi) as RevertInterface - } - static connect(address: string, runner?: ContractRunner | null): Revert { - return new Contract(address, _abi, runner) as unknown as Revert - } -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/index.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/index.ts deleted file mode 100644 index 67370dba411c3d9e5908cccdbee1a858944258f5..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -export { Event__factory } from './Event__factory' -export { PiggyBank__factory } from './PiggyBank__factory' -export { Revert__factory } from './Revert__factory' diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/index.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/index.ts deleted file mode 100644 index 3e324e80dcb1c04a0a34c2abaaad2afbec620278..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -export type { Event } from './Event' -export type { PiggyBank } from './PiggyBank' -export type { Revert } from './Revert' -export * as factories from './factories' -export { Event__factory } from './factories/Event__factory' -export { PiggyBank__factory } from './factories/PiggyBank__factory' -export { Revert__factory } from './factories/Revert__factory' diff --git a/substrate/frame/revive/rpc/examples/westend_local_network.toml b/substrate/frame/revive/rpc/examples/westend_local_network.toml index 28295db76133c80e3a1f7bf9a8739b45df1c56e1..76561be814ecece1c1718a1d886e685dcb6df1e2 100644 --- a/substrate/frame/revive/rpc/examples/westend_local_network.toml +++ b/substrate/frame/revive/rpc/examples/westend_local_network.toml @@ -29,13 +29,9 @@ name = "asset-hub-westend-collator1" rpc_port = 9011 ws_port = 9944 command = "{{POLKADOT_PARACHAIN_BINARY}}" -args = [ - "-lparachain=debug,runtime::revive=debug", -] +args = ["-lparachain=debug,runtime::revive=debug"] [[parachains.collators]] name = "asset-hub-westend-collator2" command = "{{POLKADOT_PARACHAIN_BINARY}}" -args = [ - "-lparachain=debug,runtime::revive=debug", -] +args = ["-lparachain=debug,runtime::revive=debug"] diff --git a/substrate/frame/revive/rpc/migrations/20241205165418_create_transaction_hashes.sql b/substrate/frame/revive/rpc/migrations/20241205165418_create_transaction_hashes.sql new file mode 100644 index 0000000000000000000000000000000000000000..43405bea9d0468c45a7dc5a10ed0571cec9fa163 --- /dev/null +++ b/substrate/frame/revive/rpc/migrations/20241205165418_create_transaction_hashes.sql @@ -0,0 +1,15 @@ +-- Create DB: +-- DATABASE_URL="..." cargo sqlx database create +-- +-- Run migration: +-- DATABASE_URL="..." cargo sqlx migrate run +-- +-- Update compile time artifacts: +-- DATABASE_URL="..." cargo sqlx prepare +CREATE TABLE transaction_hashes ( + transaction_hash CHAR(64) NOT NULL PRIMARY KEY, + transaction_index INTEGER NOT NULL, + block_hash CHAR(64) NOT NULL +); + +CREATE INDEX idx_block_hash ON transaction_hashes (block_hash); diff --git a/substrate/frame/revive/rpc/revive_chain.metadata b/substrate/frame/revive/rpc/revive_chain.metadata index 3560b3b90407acce7f602ce91ac089843be8dea8..29d91486afe004960ba4a1eee5d22c5ef3297876 100644 Binary files a/substrate/frame/revive/rpc/revive_chain.metadata and b/substrate/frame/revive/rpc/revive_chain.metadata differ diff --git a/substrate/frame/revive/rpc/src/block_info_provider.rs b/substrate/frame/revive/rpc/src/block_info_provider.rs new file mode 100644 index 0000000000000000000000000000000000000000..0e91869cddaa24569fd356cf192a14af9745eb9e --- /dev/null +++ b/substrate/frame/revive/rpc/src/block_info_provider.rs @@ -0,0 +1,250 @@ +// 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::{ + client::{SubstrateBlock, SubstrateBlockNumber}, + subxt_client::SrcChainConfig, + ClientError, +}; +use jsonrpsee::core::async_trait; +use sp_core::H256; +use std::{ + collections::{HashMap, VecDeque}, + sync::Arc, +}; +use subxt::{backend::legacy::LegacyRpcMethods, OnlineClient}; +use tokio::sync::RwLock; + +/// BlockInfoProvider cache and retrieves information about blocks. +#[async_trait] +pub trait BlockInfoProvider: Send + Sync { + /// Cache a new block and return the pruned block hash. + async fn cache_block(&self, block: SubstrateBlock) -> Option<H256>; + + /// Return the latest ingested block. + async fn latest_block(&self) -> Option<Arc<SubstrateBlock>>; + + /// Get block by block_number. + async fn block_by_number( + &self, + block_number: SubstrateBlockNumber, + ) -> Result<Option<Arc<SubstrateBlock>>, ClientError>; + + /// Get block by block hash. + async fn block_by_hash(&self, hash: &H256) -> Result<Option<Arc<SubstrateBlock>>, ClientError>; +} + +/// Provides information about blocks. +#[derive(Clone)] +pub struct BlockInfoProviderImpl { + /// The shared in memory cache. + cache: Arc<RwLock<BlockCache<SubstrateBlock>>>, + + /// The rpc client, used to fetch blocks not in the cache. + rpc: LegacyRpcMethods<SrcChainConfig>, + + /// The api client, used to fetch blocks not in the cache. + api: OnlineClient<SrcChainConfig>, +} + +impl BlockInfoProviderImpl { + pub fn new( + cache_size: usize, + api: OnlineClient<SrcChainConfig>, + rpc: LegacyRpcMethods<SrcChainConfig>, + ) -> Self { + Self { api, rpc, cache: Arc::new(RwLock::new(BlockCache::new(cache_size))) } + } + + async fn cache(&self) -> tokio::sync::RwLockReadGuard<'_, BlockCache<SubstrateBlock>> { + self.cache.read().await + } +} + +#[async_trait] +impl BlockInfoProvider for BlockInfoProviderImpl { + async fn cache_block(&self, block: SubstrateBlock) -> Option<H256> { + let mut cache = self.cache.write().await; + cache.insert(block) + } + + async fn latest_block(&self) -> Option<Arc<SubstrateBlock>> { + let cache = self.cache().await; + cache.buffer.back().cloned() + } + + async fn block_by_number( + &self, + block_number: SubstrateBlockNumber, + ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> { + let cache = self.cache().await; + if let Some(block) = cache.blocks_by_number.get(&block_number).cloned() { + return Ok(Some(block)); + } + + let Some(hash) = self.rpc.chain_get_block_hash(Some(block_number.into())).await? else { + return Ok(None); + }; + + self.block_by_hash(&hash).await + } + + async fn block_by_hash(&self, hash: &H256) -> Result<Option<Arc<SubstrateBlock>>, ClientError> { + let cache = self.cache().await; + if let Some(block) = cache.blocks_by_hash.get(hash).cloned() { + return Ok(Some(block)); + } + + match self.api.blocks().at(*hash).await { + Ok(block) => Ok(Some(Arc::new(block))), + Err(subxt::Error::Block(subxt::error::BlockError::NotFound(_))) => Ok(None), + Err(err) => Err(err.into()), + } + } +} + +/// The cache maintains a buffer of the last N blocks, +struct BlockCache<Block> { + /// The maximum buffer's size. + max_cache_size: usize, + + /// A double-ended queue of the last N blocks. + /// The most recent block is at the back of the queue, and the oldest block is at the front. + buffer: VecDeque<Arc<Block>>, + + /// A map of blocks by block number. + blocks_by_number: HashMap<SubstrateBlockNumber, Arc<Block>>, + + /// A map of blocks by block hash. + blocks_by_hash: HashMap<H256, Arc<Block>>, +} + +/// Provides information about a block, +/// This is an abstratction on top of [`SubstrateBlock`] used to test the [`BlockCache`]. +/// Can be removed once https://github.com/paritytech/subxt/issues/1883 is fixed. +trait BlockInfo { + /// Returns the block hash. + fn hash(&self) -> H256; + /// Returns the block number. + fn number(&self) -> SubstrateBlockNumber; +} + +impl BlockInfo for SubstrateBlock { + fn hash(&self) -> H256 { + SubstrateBlock::hash(self) + } + fn number(&self) -> u32 { + SubstrateBlock::number(self) + } +} + +impl<B: BlockInfo> BlockCache<B> { + /// Create a new cache with the given maximum buffer size. + pub fn new(max_cache_size: usize) -> Self { + Self { + max_cache_size, + buffer: Default::default(), + blocks_by_number: Default::default(), + blocks_by_hash: Default::default(), + } + } + + /// Insert an entry into the cache, and prune the oldest entry if the cache is full. + pub fn insert(&mut self, block: B) -> Option<H256> { + let mut pruned_block_hash = None; + if self.buffer.len() >= self.max_cache_size { + if let Some(block) = self.buffer.pop_front() { + let hash = block.hash(); + self.blocks_by_hash.remove(&hash); + self.blocks_by_number.remove(&block.number()); + pruned_block_hash = Some(hash); + } + } + + let block = Arc::new(block); + self.buffer.push_back(block.clone()); + self.blocks_by_number.insert(block.number(), block.clone()); + self.blocks_by_hash.insert(block.hash(), block); + pruned_block_hash + } +} + +#[cfg(test)] +pub mod test { + use super::*; + + struct MockBlock { + block_number: SubstrateBlockNumber, + block_hash: H256, + } + + impl BlockInfo for MockBlock { + fn hash(&self) -> H256 { + self.block_hash + } + + fn number(&self) -> u32 { + self.block_number + } + } + + #[test] + fn cache_insert_works() { + let mut cache = BlockCache::<MockBlock>::new(2); + + let pruned = cache.insert(MockBlock { block_number: 1, block_hash: H256::from([1; 32]) }); + assert_eq!(pruned, None); + + let pruned = cache.insert(MockBlock { block_number: 2, block_hash: H256::from([2; 32]) }); + assert_eq!(pruned, None); + + let pruned = cache.insert(MockBlock { block_number: 3, block_hash: H256::from([3; 32]) }); + assert_eq!(pruned, Some(H256::from([1; 32]))); + + assert_eq!(cache.buffer.len(), 2); + assert_eq!(cache.blocks_by_number.len(), 2); + assert_eq!(cache.blocks_by_hash.len(), 2); + } + + /// A Noop BlockInfoProvider used to test [`db::DBReceiptProvider`]. + pub struct MockBlockInfoProvider; + + #[async_trait] + impl BlockInfoProvider for MockBlockInfoProvider { + async fn cache_block(&self, _block: SubstrateBlock) -> Option<H256> { + None + } + + async fn latest_block(&self) -> Option<Arc<SubstrateBlock>> { + None + } + + async fn block_by_number( + &self, + _block_number: SubstrateBlockNumber, + ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> { + Ok(None) + } + + async fn block_by_hash( + &self, + _hash: &H256, + ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> { + Ok(None) + } + } +} diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index c0f81fcafd771377a31e7ae75fc29a75ceec60b8..b6c57d2c3b0bfcb50f024aee5521d9b1edf4d1c2 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -16,8 +16,10 @@ // limitations under the License. //! The Ethereum JSON-RPC server. use crate::{ - client::Client, EthRpcServer, EthRpcServerImpl, SystemHealthRpcServer, - SystemHealthRpcServerImpl, + client::{connect, Client}, + BlockInfoProvider, BlockInfoProviderImpl, CacheReceiptProvider, DBReceiptProvider, + EthRpcServer, EthRpcServerImpl, ReceiptProvider, SystemHealthRpcServer, + SystemHealthRpcServerImpl, LOG_TARGET, }; use clap::Parser; use futures::{pin_mut, FutureExt}; @@ -27,6 +29,7 @@ use sc_service::{ config::{PrometheusConfig, RpcConfiguration}, start_rpc_servers, TaskManager, }; +use std::sync::Arc; // Default port if --prometheus-port is not specified const DEFAULT_PROMETHEUS_PORT: u16 = 9616; @@ -42,6 +45,21 @@ pub struct CliCommand { #[clap(long, default_value = "ws://127.0.0.1:9944")] pub node_rpc_url: String, + /// The maximum number of blocks to cache in memory. + #[clap(long, default_value = "256")] + pub cache_size: usize, + + /// The database used to store Ethereum transaction hashes. + /// This is only useful if the node needs to act as an archive node and respond to Ethereum RPC + /// queries for transactions that are not in the in memory cache. + #[clap(long, env = "DATABASE_URL")] + pub database_url: Option<String>, + + /// If true, we will only read from the database and not write to it. + /// Only useful if `--database-url` is specified. + #[clap(long, default_value = "true")] + pub database_read_only: bool, + #[allow(missing_docs)] #[clap(flatten)] pub shared_params: SharedParams, @@ -78,7 +96,16 @@ fn init_logger(params: &SharedParams) -> anyhow::Result<()> { /// Start the JSON-RPC server using the given command line arguments. pub fn run(cmd: CliCommand) -> anyhow::Result<()> { - let CliCommand { rpc_params, prometheus_params, node_rpc_url, shared_params, .. } = cmd; + let CliCommand { + rpc_params, + prometheus_params, + node_rpc_url, + cache_size, + database_url, + database_read_only, + shared_params, + .. + } = cmd; #[cfg(not(test))] init_logger(&shared_params)?; @@ -110,19 +137,44 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { let tokio_runtime = sc_cli::build_runtime()?; let tokio_handle = tokio_runtime.handle(); - let signals = tokio_runtime.block_on(async { Signals::capture() })?; let mut task_manager = TaskManager::new(tokio_handle.clone(), prometheus_registry)?; let essential_spawn_handle = task_manager.spawn_essential_handle(); let gen_rpc_module = || { let signals = tokio_runtime.block_on(async { Signals::capture() })?; - let fut = Client::from_url(&node_rpc_url, &essential_spawn_handle).fuse(); + let fut = async { + let (api, rpc_client, rpc) = connect(&node_rpc_url).await?; + let block_provider: Arc<dyn BlockInfoProvider> = + Arc::new(BlockInfoProviderImpl::new(cache_size, api.clone(), rpc.clone())); + let receipt_provider: Arc<dyn ReceiptProvider> = + if let Some(database_url) = database_url.as_ref() { + log::info!(target: LOG_TARGET, "🔗 Connecting to provided database"); + Arc::new(( + CacheReceiptProvider::default(), + DBReceiptProvider::new( + database_url, + database_read_only, + block_provider.clone(), + ) + .await?, + )) + } else { + log::info!(target: LOG_TARGET, "🔌 No database provided, using in-memory cache"); + Arc::new(CacheReceiptProvider::default()) + }; + + let client = + Client::new(api, rpc_client, rpc, block_provider, receipt_provider).await?; + client.subscribe_and_cache_blocks(&essential_spawn_handle); + Ok::<_, crate::ClientError>(client) + } + .fuse(); pin_mut!(fut); match tokio_handle.block_on(signals.try_until_signal(fut)) { Ok(Ok(client)) => rpc_module(is_dev, client), Ok(Err(err)) => { - log::error!("Error connecting to the node at {node_rpc_url}: {err}"); + log::error!("Error initializing: {err:?}"); Err(sc_service::Error::Application(err.into())) }, Err(_) => Err(sc_service::Error::Application("Client connection interrupted".into())), @@ -142,6 +194,7 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { start_rpc_servers(&rpc_config, prometheus_registry, tokio_handle, gen_rpc_module, None)?; task_manager.keep_alive(rpc_server_handle); + let signals = tokio_runtime.block_on(async { Signals::capture() })?; tokio_runtime.block_on(signals.run_until_signal(task_manager.future().fuse()))?; Ok(()) } diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index d37f1d760065314afe2a96ffe166109b77d572a7..7f45a27a05a356bcaaae0d965eb1c9cbad52feea 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -17,30 +17,23 @@ //! The client connects to the source substrate chain //! and is used by the rpc server to query and send transactions to the substrate chain. use crate::{ - runtime::GAS_PRICE, + extract_receipts_from_block, subxt_client::{ - revive::{calls::types::EthTransact, events::ContractEmitted}, - runtime_types::pallet_revive::storage::ContractInfo, + revive::calls::types::EthTransact, runtime_types::pallet_revive::storage::ContractInfo, }, - LOG_TARGET, + BlockInfoProvider, ReceiptProvider, TransactionInfo, LOG_TARGET, }; -use futures::{stream, StreamExt}; use jsonrpsee::types::{error::CALL_EXECUTION_FAILED_CODE, ErrorObjectOwned}; use pallet_revive::{ - create1, evm::{ - Block, BlockNumberOrTag, BlockNumberOrTagOrHash, Bytes256, GenericTransaction, Log, - ReceiptInfo, SyncingProgress, SyncingStatus, TransactionSigned, H160, H256, U256, + extract_revert_message, Block, BlockNumberOrTag, BlockNumberOrTagOrHash, + GenericTransaction, ReceiptInfo, SyncingProgress, SyncingStatus, TransactionSigned, H160, + H256, U256, }, - EthContractResult, + EthTransactError, EthTransactInfo, }; -use sp_core::keccak_256; use sp_weights::Weight; -use std::{ - collections::{HashMap, VecDeque}, - sync::Arc, - time::Duration, -}; +use std::{ops::ControlFlow, sync::Arc, time::Duration}; use subxt::{ backend::{ legacy::{rpc_methods::SystemHealth, LegacyRpcMethods}, @@ -54,11 +47,10 @@ use subxt::{ storage::Storage, Config, OnlineClient, }; -use subxt_client::transaction_payment::events::TransactionFeePaid; use thiserror::Error; -use tokio::sync::{watch::Sender, RwLock}; +use tokio::{sync::RwLock, try_join}; -use crate::subxt_client::{self, system::events::ExtrinsicSuccess, SrcChainConfig}; +use crate::subxt_client::{self, SrcChainConfig}; /// The substrate block type. pub type SubstrateBlock = subxt::blocks::Block<SrcChainConfig, OnlineClient<SrcChainConfig>>; @@ -75,27 +67,12 @@ pub type Shared<T> = Arc<RwLock<T>>; /// The runtime balance type. pub type Balance = u128; -/// The cache maintains a buffer of the last N blocks, -#[derive(Default)] -struct BlockCache<const N: usize> { - /// A double-ended queue of the last N blocks. - /// The most recent block is at the back of the queue, and the oldest block is at the front. - buffer: VecDeque<Arc<SubstrateBlock>>, - - /// A map of blocks by block number. - blocks_by_number: HashMap<SubstrateBlockNumber, Arc<SubstrateBlock>>, - - /// A map of blocks by block hash. - blocks_by_hash: HashMap<H256, Arc<SubstrateBlock>>, - - /// A map of receipts by hash. - receipts_by_hash: HashMap<H256, ReceiptInfo>, - - /// A map of Signed transaction by hash. - signed_tx_by_hash: HashMap<H256, TransactionSigned>, - - /// A map of receipt hashes by block hash. - tx_hashes_by_block_and_index: HashMap<H256, HashMap<U256, H256>>, +/// The subscription type used to listen to new blocks. +pub enum SubscriptionType { + /// Subscribe to the best blocks. + BestBlocks, + /// Subscribe to the finalized blocks. + FinalizedBlocks, } /// Unwrap the original `jsonrpsee::core::client::Error::Call` error. @@ -114,23 +91,6 @@ fn unwrap_call_err(err: &subxt::error::RpcError) -> Option<ErrorObjectOwned> { } } -/// Extract the revert message from a revert("msg") solidity statement. -fn extract_revert_message(exec_data: &[u8]) -> Option<String> { - let function_selector = exec_data.get(0..4)?; - - // keccak256("Error(string)") - let expected_selector = [0x08, 0xC3, 0x79, 0xA0]; - if function_selector != expected_selector { - return None; - } - - let decoded = ethabi::decode(&[ethabi::ParamType::String], &exec_data[4..]).ok()?; - match decoded.first()? { - ethabi::Token::String(msg) => Some(msg.to_string()), - _ => None, - } -} - /// The error type for the client. #[derive(Error, Debug)] pub enum ClientError { @@ -143,206 +103,77 @@ pub enum ClientError { /// A [`RpcError`] wrapper error. #[error(transparent)] RpcError(#[from] RpcError), + /// A [`sqlx::Error`] wrapper error. + #[error(transparent)] + SqlxError(#[from] sqlx::Error), /// A [`codec::Error`] wrapper error. #[error(transparent)] CodecError(#[from] codec::Error), - /// The dry run failed. - #[error("Dry run failed: {0}")] - DryRunFailed(String), /// Contract reverted - #[error("Execution reverted: {}", extract_revert_message(.0).unwrap_or_default())] - Reverted(Vec<u8>), + #[error("contract reverted")] + Reverted(EthTransactError), /// A decimal conversion failed. - #[error("Conversion failed")] + #[error("conversion failed")] ConversionFailed, /// The block hash was not found. - #[error("Hash not found")] + #[error("hash not found")] BlockNotFound, + + #[error("No Ethereum extrinsic found")] + EthExtrinsicNotFound, /// The transaction fee could not be found - #[error("TransactionFeePaid event not found")] + #[error("transactionFeePaid event not found")] TxFeeNotFound, + /// Failed to decode a raw payload into a signed transaction. + #[error("Failed to decode a raw payload into a signed transaction")] + TxDecodingFailed, + /// Failed to recover eth address. + #[error("failed to recover eth address")] + RecoverEthAddressFailed, /// The cache is empty. - #[error("Cache is empty")] + #[error("cache is empty")] CacheEmpty, } -// TODO convert error code to https://eips.ethereum.org/EIPS/eip-1474#error-codes +const REVERT_CODE: i32 = 3; impl From<ClientError> for ErrorObjectOwned { fn from(err: ClientError) -> Self { - let msg = err.to_string(); match err { ClientError::SubxtError(subxt::Error::Rpc(err)) | ClientError::RpcError(err) => { if let Some(err) = unwrap_call_err(&err) { return err; } - ErrorObjectOwned::owned::<Vec<u8>>(CALL_EXECUTION_FAILED_CODE, msg, None) + ErrorObjectOwned::owned::<Vec<u8>>( + CALL_EXECUTION_FAILED_CODE, + err.to_string(), + None, + ) }, - ClientError::Reverted(data) => { + ClientError::Reverted(EthTransactError::Data(data)) => { + let msg = extract_revert_message(&data).unwrap_or_default(); let data = format!("0x{}", hex::encode(data)); - ErrorObjectOwned::owned::<String>(CALL_EXECUTION_FAILED_CODE, msg, Some(data)) + ErrorObjectOwned::owned::<String>(REVERT_CODE, msg, Some(data)) }, - _ => ErrorObjectOwned::owned::<String>(CALL_EXECUTION_FAILED_CODE, msg, None), + ClientError::Reverted(EthTransactError::Message(msg)) => + ErrorObjectOwned::owned::<String>(CALL_EXECUTION_FAILED_CODE, msg, None), + _ => + ErrorObjectOwned::owned::<String>(CALL_EXECUTION_FAILED_CODE, err.to_string(), None), } } } -/// The number of recent blocks maintained by the cache. -/// For each block in the cache, we also store the EVM transaction receipts. -pub const CACHE_SIZE: usize = 256; - -impl<const N: usize> BlockCache<N> { - fn latest_block(&self) -> Option<&Arc<SubstrateBlock>> { - self.buffer.back() - } - - /// Insert an entry into the cache, and prune the oldest entry if the cache is full. - fn insert(&mut self, block: SubstrateBlock) { - if self.buffer.len() >= N { - if let Some(block) = self.buffer.pop_front() { - log::trace!(target: LOG_TARGET, "Pruning block: {}", block.number()); - let hash = block.hash(); - self.blocks_by_hash.remove(&hash); - self.blocks_by_number.remove(&block.number()); - if let Some(entries) = self.tx_hashes_by_block_and_index.remove(&hash) { - for hash in entries.values() { - self.receipts_by_hash.remove(hash); - } - } - } - } - - let block = Arc::new(block); - self.buffer.push_back(block.clone()); - self.blocks_by_number.insert(block.number(), block.clone()); - self.blocks_by_hash.insert(block.hash(), block); - } -} - /// A client connect to a node and maintains a cache of the last `CACHE_SIZE` blocks. #[derive(Clone)] pub struct Client { - /// The inner state of the client. - inner: Arc<ClientInner>, - /// A watch channel to signal cache updates. - pub updates: tokio::sync::watch::Receiver<()>, -} - -/// The inner state of the client. -struct ClientInner { api: OnlineClient<SrcChainConfig>, rpc_client: ReconnectingRpcClient, rpc: LegacyRpcMethods<SrcChainConfig>, - cache: Shared<BlockCache<CACHE_SIZE>>, + receipt_provider: Arc<dyn ReceiptProvider>, + block_provider: Arc<dyn BlockInfoProvider>, chain_id: u64, max_block_weight: Weight, } -impl ClientInner { - /// Create a new client instance connecting to the substrate node at the given URL. - async fn from_url(url: &str) -> Result<Self, ClientError> { - let rpc_client = ReconnectingRpcClient::builder() - .retry_policy(ExponentialBackoff::from_millis(100).max_delay(Duration::from_secs(10))) - .build(url.to_string()) - .await?; - - let api = OnlineClient::<SrcChainConfig>::from_rpc_client(rpc_client.clone()).await?; - let cache = Arc::new(RwLock::new(BlockCache::<CACHE_SIZE>::default())); - - let rpc = LegacyRpcMethods::<SrcChainConfig>::new(RpcClient::new(rpc_client.clone())); - - let (chain_id, max_block_weight) = - tokio::try_join!(chain_id(&api), max_block_weight(&api))?; - - Ok(Self { api, rpc_client, rpc, cache, chain_id, max_block_weight }) - } - - /// Get the receipt infos from the extrinsics in a block. - async fn receipt_infos( - &self, - block: &SubstrateBlock, - ) -> Result<HashMap<H256, (TransactionSigned, ReceiptInfo)>, ClientError> { - // Get extrinsics from the block - let extrinsics = block.extrinsics().await?; - - // Filter extrinsics from pallet_revive - let extrinsics = extrinsics.iter().flat_map(|ext| { - let call = ext.as_extrinsic::<EthTransact>().ok()??; - let transaction_hash = H256(keccak_256(&call.payload)); - let signed_tx = TransactionSigned::decode(&call.payload).ok()?; - let from = signed_tx.recover_eth_address().ok()?; - let tx_info = GenericTransaction::from_signed(signed_tx.clone(), Some(from)); - let contract_address = if tx_info.to.is_none() { - Some(create1(&from, tx_info.nonce.unwrap_or_default().try_into().ok()?)) - } else { - None - }; - - Some((from, signed_tx, tx_info, transaction_hash, contract_address, ext)) - }); - - // Map each extrinsic to a receipt - stream::iter(extrinsics) - .map(|(from, signed_tx, tx_info, transaction_hash, contract_address, ext)| async move { - let events = ext.events().await?; - let tx_fees = - events.find_first::<TransactionFeePaid>()?.ok_or(ClientError::TxFeeNotFound)?; - - let gas_price = tx_info.gas_price.unwrap_or_default(); - let gas_used = (tx_fees.tip.saturating_add(tx_fees.actual_fee)) - .checked_div(gas_price.as_u128()) - .unwrap_or_default(); - - let success = events.has::<ExtrinsicSuccess>()?; - let transaction_index = ext.index(); - let block_hash = block.hash(); - let block_number = block.number().into(); - - // get logs from ContractEmitted event - let logs = events.iter() - .filter_map(|event_details| { - let event_details = event_details.ok()?; - let event = event_details.as_event::<ContractEmitted>().ok()??; - - Some(Log { - address: event.contract, - topics: event.topics, - data: Some(event.data.into()), - block_number: Some(block_number), - transaction_hash, - transaction_index: Some(transaction_index.into()), - block_hash: Some(block_hash), - log_index: Some(event_details.index().into()), - ..Default::default() - }) - }).collect(); - - - log::debug!(target: LOG_TARGET, "Adding receipt for tx hash: {transaction_hash:?} - block: {block_number:?}"); - let receipt = ReceiptInfo::new( - block_hash, - block_number, - contract_address, - from, - logs, - tx_info.to, - gas_price, - gas_used.into(), - success, - transaction_hash, - transaction_index.into(), - tx_info.r#type.unwrap_or_default() - ); - - Ok::<_, ClientError>((receipt.transaction_hash, (signed_tx, receipt))) - }) - .buffer_unordered(10) - .collect::<Vec<Result<_, _>>>() - .await - .into_iter() - .collect::<Result<HashMap<_, _>, _>>() - } -} - /// Fetch the chain ID from the substrate chain. async fn chain_id(api: &OnlineClient<SrcChainConfig>) -> Result<u64, ClientError> { let query = subxt_client::constants().revive().chain_id(); @@ -367,23 +198,188 @@ async fn extract_block_timestamp(block: &SubstrateBlock) -> Option<u64> { Some(ext.value.now / 1000) } +/// Connect to a node at the given URL, and return the underlying API, RPC client, and legacy RPC +/// clients. +pub async fn connect( + node_rpc_url: &str, +) -> Result< + (OnlineClient<SrcChainConfig>, ReconnectingRpcClient, LegacyRpcMethods<SrcChainConfig>), + ClientError, +> { + log::info!(target: LOG_TARGET, "Connecting to node at: {node_rpc_url} ..."); + let rpc_client = ReconnectingRpcClient::builder() + .retry_policy(ExponentialBackoff::from_millis(100).max_delay(Duration::from_secs(10))) + .build(node_rpc_url.to_string()) + .await?; + log::info!(target: LOG_TARGET, "Connected to node at: {node_rpc_url}"); + + let api = OnlineClient::<SrcChainConfig>::from_rpc_client(rpc_client.clone()).await?; + let rpc = LegacyRpcMethods::<SrcChainConfig>::new(RpcClient::new(rpc_client.clone())); + Ok((api, rpc_client, rpc)) +} + impl Client { /// Create a new client instance. - /// The client will subscribe to new blocks and maintain a cache of [`CACHE_SIZE`] blocks. - pub async fn from_url( - url: &str, - spawn_handle: &sc_service::SpawnEssentialTaskHandle, + pub async fn new( + api: OnlineClient<SrcChainConfig>, + rpc_client: ReconnectingRpcClient, + rpc: LegacyRpcMethods<SrcChainConfig>, + block_provider: Arc<dyn BlockInfoProvider>, + receipt_provider: Arc<dyn ReceiptProvider>, ) -> Result<Self, ClientError> { - log::info!(target: LOG_TARGET, "Connecting to node at: {url} ..."); - let inner: Arc<ClientInner> = Arc::new(ClientInner::from_url(url).await?); - log::info!(target: LOG_TARGET, "Connected to node at: {url}"); + let (chain_id, max_block_weight) = + tokio::try_join!(chain_id(&api), max_block_weight(&api))?; + + Ok(Self { + api, + rpc_client, + rpc, + receipt_provider, + block_provider, + chain_id, + max_block_weight, + }) + } + + /// Subscribe to past blocks executing the callback for each block. + /// The subscription continues iterating past blocks until the closure returns + /// `ControlFlow::Break`. Blocks are iterated starting from the latest block and moving + /// backward. + #[allow(dead_code)] + async fn subscribe_past_blocks<F, Fut>(&self, callback: F) -> Result<(), ClientError> + where + F: Fn(SubstrateBlock) -> Fut + Send + Sync, + Fut: std::future::Future<Output = Result<ControlFlow<()>, ClientError>> + Send, + { + log::info!(target: LOG_TARGET, "Subscribing to past blocks"); + let mut block = self.api.blocks().at_latest().await.inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to fetch latest block: {err:?}"); + })?; + + loop { + let block_number = block.number(); + log::debug!(target: LOG_TARGET, "Processing block {block_number}"); + + let parent_hash = block.header().parent_hash; + let control_flow = callback(block).await.inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to process block {block_number}: {err:?}"); + })?; + + match control_flow { + ControlFlow::Continue(_) => { + if block_number == 0 { + log::info!(target: LOG_TARGET, "All past blocks processed"); + return Ok(()); + } + block = self.api.blocks().at(parent_hash).await.inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to fetch block at {parent_hash:?}: {err:?}"); + })?; + }, + ControlFlow::Break(_) => { + log::info!(target: LOG_TARGET, "Stopping past block subscription at {block_number}"); + return Ok(()); + }, + } + } + } + + /// Subscribe to new best blocks, and execute the async closure with + /// the extracted block and ethereum transactions + async fn subscribe_new_blocks<F, Fut>( + &self, + subscription_type: SubscriptionType, + callback: F, + ) -> Result<(), ClientError> + where + F: Fn(SubstrateBlock) -> Fut + Send + Sync, + Fut: std::future::Future<Output = Result<(), ClientError>> + Send, + { + log::info!(target: LOG_TARGET, "Subscribing to new blocks"); + let mut block_stream = match subscription_type { + SubscriptionType::BestBlocks => self.api.blocks().subscribe_best().await, + SubscriptionType::FinalizedBlocks => self.api.blocks().subscribe_finalized().await, + } + .inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to subscribe to blocks: {err:?}"); + })?; + + while let Some(block) = block_stream.next().await { + let block = match block { + Ok(block) => block, + Err(err) => { + if err.is_disconnected_will_reconnect() { + log::warn!( + target: LOG_TARGET, + "The RPC connection was lost and we may have missed a few blocks" + ); + continue; + } - let (tx, mut updates) = tokio::sync::watch::channel(()); + log::error!(target: LOG_TARGET, "Failed to fetch block: {err:?}"); + return Err(err.into()); + }, + }; - spawn_handle.spawn("subscribe-blocks", None, Self::subscribe_blocks(inner.clone(), tx)); + log::debug!(target: LOG_TARGET, "Pushing block: {}", block.number()); + if let Err(err) = callback(block).await { + log::error!(target: LOG_TARGET, "Failed to process block: {err:?}"); + } + } - updates.changed().await.expect("tx is not dropped"); - Ok(Self { inner, updates }) + log::info!(target: LOG_TARGET, "Block subscription ended"); + Ok(()) + } + + /// Start the block subscription, and populate the block cache. + pub fn subscribe_and_cache_blocks(&self, spawn_handle: &sc_service::SpawnEssentialTaskHandle) { + let client = self.clone(); + spawn_handle.spawn("subscribe-blocks", None, async move { + let res = client + .subscribe_new_blocks(SubscriptionType::BestBlocks, |block| async { + let receipts = extract_receipts_from_block(&block).await?; + + client.receipt_provider.insert(&block.hash(), &receipts).await; + if let Some(pruned) = client.block_provider.cache_block(block).await { + client.receipt_provider.remove(&pruned).await; + } + + Ok(()) + }) + .await; + + if let Err(err) = res { + log::error!(target: LOG_TARGET, "Block subscription error: {err:?}"); + } + }); + } + + /// Start the block subscription, and populate the block cache. + pub async fn subscribe_and_cache_receipts( + &self, + oldest_block: Option<SubstrateBlockNumber>, + ) -> Result<(), ClientError> { + let new_blocks_fut = + self.subscribe_new_blocks(SubscriptionType::FinalizedBlocks, |block| async move { + let receipts = extract_receipts_from_block(&block).await.inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to extract receipts from block: {err:?}"); + })?; + self.receipt_provider.insert(&block.hash(), &receipts).await; + Ok(()) + }); + + let Some(oldest_block) = oldest_block else { return new_blocks_fut.await }; + + let old_blocks_fut = self.subscribe_past_blocks(|block| async move { + let receipts = extract_receipts_from_block(&block).await?; + self.receipt_provider.insert(&block.hash(), &receipts).await; + if block.number() == oldest_block { + Ok(ControlFlow::Break(())) + } else { + Ok(ControlFlow::Continue(())) + } + }); + + try_join!(new_blocks_fut, old_blocks_fut).map(|_| ()) } /// Expose the storage API. @@ -397,14 +393,14 @@ impl Client { (*block_number).try_into().map_err(|_| ClientError::ConversionFailed)?; let hash = self.get_block_hash(n).await?.ok_or(ClientError::BlockNotFound)?; - Ok(self.inner.api.storage().at(hash)) + Ok(self.api.storage().at(hash)) }, - BlockNumberOrTagOrHash::H256(hash) => Ok(self.inner.api.storage().at(*hash)), + BlockNumberOrTagOrHash::H256(hash) => Ok(self.api.storage().at(*hash)), BlockNumberOrTagOrHash::BlockTag(_) => { if let Some(block) = self.latest_block().await { - return Ok(self.inner.api.storage().at(block.hash())); + return Ok(self.api.storage().at(block.hash())); } - let storage = self.inner.api.storage().at_latest().await?; + let storage = self.api.storage().at_latest().await?; Ok(storage) }, } @@ -424,90 +420,24 @@ impl Client { (*block_number).try_into().map_err(|_| ClientError::ConversionFailed)?; let hash = self.get_block_hash(n).await?.ok_or(ClientError::BlockNotFound)?; - Ok(self.inner.api.runtime_api().at(hash)) + Ok(self.api.runtime_api().at(hash)) }, - BlockNumberOrTagOrHash::H256(hash) => Ok(self.inner.api.runtime_api().at(*hash)), + BlockNumberOrTagOrHash::H256(hash) => Ok(self.api.runtime_api().at(*hash)), BlockNumberOrTagOrHash::BlockTag(_) => { if let Some(block) = self.latest_block().await { - return Ok(self.inner.api.runtime_api().at(block.hash())); + return Ok(self.api.runtime_api().at(block.hash())); } - let api = self.inner.api.runtime_api().at_latest().await?; + let api = self.api.runtime_api().at_latest().await?; Ok(api) }, } } - /// Subscribe to new blocks and update the cache. - async fn subscribe_blocks(inner: Arc<ClientInner>, tx: Sender<()>) { - log::info!(target: LOG_TARGET, "Subscribing to new blocks"); - let mut block_stream = match inner.as_ref().api.blocks().subscribe_best().await { - Ok(s) => s, - Err(err) => { - log::error!(target: LOG_TARGET, "Failed to subscribe to blocks: {err:?}"); - return; - }, - }; - - while let Some(block) = block_stream.next().await { - let block = match block { - Ok(block) => block, - Err(err) => { - if err.is_disconnected_will_reconnect() { - log::warn!( - target: LOG_TARGET, - "The RPC connection was lost and we may have missed a few blocks" - ); - continue; - } - - log::error!(target: LOG_TARGET, "Failed to fetch block: {err:?}"); - return; - }, - }; - - log::trace!(target: LOG_TARGET, "Pushing block: {}", block.number()); - let mut cache = inner.cache.write().await; - - let receipts = inner - .receipt_infos(&block) - .await - .inspect_err(|err| { - log::error!(target: LOG_TARGET, "Failed to get receipts: {err:?}"); - }) - .unwrap_or_default(); - - if !receipts.is_empty() { - let values = receipts - .iter() - .map(|(hash, (_, receipt))| (receipt.transaction_index, *hash)) - .collect::<HashMap<_, _>>(); - - cache.tx_hashes_by_block_and_index.insert(block.hash(), values); - - cache - .receipts_by_hash - .extend(receipts.iter().map(|(hash, (_, receipt))| (*hash, receipt.clone()))); - - cache.signed_tx_by_hash.extend( - receipts.iter().map(|(hash, (signed_tx, _))| (*hash, signed_tx.clone())), - ) - } - - cache.insert(block); - tx.send_replace(()); - } - - log::info!(target: LOG_TARGET, "Block subscription ended"); - } -} - -impl Client { /// Get the most recent block stored in the cache. pub async fn latest_block(&self) -> Option<Arc<SubstrateBlock>> { - let cache = self.inner.cache.read().await; - let block = cache.latest_block()?; - Some(block.clone()) + let block = self.block_provider.latest_block().await?; + Some(block) } /// Expose the transaction API. @@ -515,23 +445,22 @@ impl Client { &self, call: subxt::tx::DefaultPayload<EthTransact>, ) -> Result<H256, ClientError> { - let ext = self.inner.api.tx().create_unsigned(&call).map_err(ClientError::from)?; + let ext = self.api.tx().create_unsigned(&call).map_err(ClientError::from)?; let hash = ext.submit().await?; Ok(hash) } /// Get an EVM transaction receipt by hash. pub async fn receipt(&self, tx_hash: &H256) -> Option<ReceiptInfo> { - let cache = self.inner.cache.read().await; - cache.receipts_by_hash.get(tx_hash).cloned() + self.receipt_provider.receipt_by_hash(tx_hash).await } /// Get the syncing status of the chain. pub async fn syncing(&self) -> Result<SyncingStatus, ClientError> { - let health = self.inner.rpc.system_health().await?; + let health = self.rpc.system_health().await?; let status = if health.is_syncing { - let client = RpcClient::new(self.inner.rpc_client.clone()); + let client = RpcClient::new(self.rpc_client.clone()); let sync_state: sc_rpc::system::SyncState<SubstrateBlockNumber> = client.request("system_syncState", Default::default()).await?; @@ -554,27 +483,23 @@ impl Client { block_hash: &H256, transaction_index: &U256, ) -> Option<ReceiptInfo> { - let cache = self.inner.cache.read().await; - let receipt_hash = - cache.tx_hashes_by_block_and_index.get(block_hash)?.get(transaction_index)?; - let receipt = cache.receipts_by_hash.get(receipt_hash)?; - Some(receipt.clone()) + self.receipt_provider + .receipt_by_block_hash_and_index(block_hash, transaction_index) + .await } pub async fn signed_tx_by_hash(&self, tx_hash: &H256) -> Option<TransactionSigned> { - let cache = self.inner.cache.read().await; - cache.signed_tx_by_hash.get(tx_hash).cloned() + self.receipt_provider.signed_tx_by_hash(tx_hash).await } /// Get receipts count per block. pub async fn receipts_count_per_block(&self, block_hash: &SubstrateBlockHash) -> Option<usize> { - let cache = self.inner.cache.read().await; - cache.tx_hashes_by_block_and_index.get(block_hash).map(|v| v.len()) + self.receipt_provider.receipts_count_per_block(block_hash).await } /// Get the system health. pub async fn system_health(&self) -> Result<SystemHealth, ClientError> { - let health = self.inner.rpc.system_health().await?; + let health = self.rpc.system_health().await?; Ok(health) } @@ -634,54 +559,25 @@ impl Client { Ok(result) } - /// Dry run a transaction and returns the [`EthContractResult`] for the transaction. + /// Dry run a transaction and returns the [`EthTransactInfo`] for the transaction. pub async fn dry_run( &self, - tx: &GenericTransaction, + tx: GenericTransaction, block: BlockNumberOrTagOrHash, - ) -> Result<EthContractResult<Balance, Vec<u8>>, ClientError> { + ) -> Result<EthTransactInfo<Balance>, ClientError> { let runtime_api = self.runtime_api(&block).await?; + let payload = subxt_client::apis().revive_api().eth_transact(tx.into()); - // TODO: remove once subxt is updated - let value = subxt::utils::Static(tx.value.unwrap_or_default()); - let from = tx.from.map(|v| v.0.into()); - let to = tx.to.map(|v| v.0.into()); - - let payload = subxt_client::apis().revive_api().eth_transact( - from.unwrap_or_default(), - to, - value, - tx.input.clone().unwrap_or_default().0, - None, - None, - ); - - let EthContractResult { fee, gas_required, storage_deposit, result } = - runtime_api.call(payload).await?.0; + let result = runtime_api.call(payload).await?; match result { Err(err) => { log::debug!(target: LOG_TARGET, "Dry run failed {err:?}"); - Err(ClientError::DryRunFailed(format!("{err:?}"))) - }, - Ok(result) if result.did_revert() => { - log::debug!(target: LOG_TARGET, "Dry run reverted"); - Err(ClientError::Reverted(result.0.data)) + Err(ClientError::Reverted(err.0)) }, - Ok(result) => - Ok(EthContractResult { fee, gas_required, storage_deposit, result: result.0.data }), + Ok(result) => Ok(result.0), } } - /// Dry run a transaction and returns the gas estimate for the transaction. - pub async fn estimate_gas( - &self, - tx: &GenericTransaction, - block: BlockNumberOrTagOrHash, - ) -> Result<U256, ClientError> { - let dry_run = self.dry_run(tx, block).await?; - Ok(U256::from(dry_run.fee / GAS_PRICE as u128) + GAS_PRICE) - } - /// Get the nonce of the given address. pub async fn nonce( &self, @@ -698,8 +594,8 @@ impl Client { /// Get the block number of the latest block. pub async fn block_number(&self) -> Result<SubstrateBlockNumber, ClientError> { - let cache = self.inner.cache.read().await; - let latest_block = cache.buffer.back().ok_or(ClientError::CacheEmpty)?; + let latest_block = + self.block_provider.latest_block().await.ok_or(ClientError::CacheEmpty)?; Ok(latest_block.number()) } @@ -708,13 +604,8 @@ impl Client { &self, block_number: SubstrateBlockNumber, ) -> Result<Option<SubstrateBlockHash>, ClientError> { - let cache = self.inner.cache.read().await; - if let Some(block) = cache.blocks_by_number.get(&block_number) { - return Ok(Some(block.hash())); - } - - let hash = self.inner.rpc.chain_get_block_hash(Some(block_number.into())).await?; - Ok(hash) + let maybe_block = self.block_provider.block_by_number(block_number).await?; + Ok(maybe_block.map(|block| block.hash())) } /// Get a block for the specified hash or number. @@ -728,8 +619,8 @@ impl Client { self.block_by_number(n).await }, BlockNumberOrTag::BlockTag(_) => { - let cache = self.inner.cache.read().await; - Ok(cache.buffer.back().cloned()) + let block = self.block_provider.latest_block().await; + Ok(block) }, } } @@ -739,16 +630,7 @@ impl Client { &self, hash: &SubstrateBlockHash, ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> { - let cache = self.inner.cache.read().await; - if let Some(block) = cache.blocks_by_hash.get(hash) { - return Ok(Some(block.clone())); - } - - match self.inner.api.blocks().at(*hash).await { - Ok(block) => Ok(Some(Arc::new(block))), - Err(subxt::Error::Block(subxt::error::BlockError::NotFound(_))) => Ok(None), - Err(err) => Err(err.into()), - } + self.block_provider.block_by_hash(hash).await } /// Get a block by number @@ -756,23 +638,24 @@ impl Client { &self, block_number: SubstrateBlockNumber, ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> { - let cache = self.inner.cache.read().await; - if let Some(block) = cache.blocks_by_number.get(&block_number) { - return Ok(Some(block.clone())); - } - - let Some(hash) = self.get_block_hash(block_number).await? else { - return Ok(None); - }; + self.block_provider.block_by_number(block_number).await + } - self.block_by_hash(&hash).await + pub async fn gas_price(&self, at: &BlockNumberOrTagOrHash) -> Result<U256, ClientError> { + let runtime_api = self.runtime_api(at).await?; + let payload = subxt_client::apis().revive_api().gas_price(); + let gas_price = runtime_api.call(payload).await?; + Ok(*gas_price) } /// Get the EVM block for the given hash. - pub async fn evm_block(&self, block: Arc<SubstrateBlock>) -> Result<Block, ClientError> { - let runtime_api = self.inner.api.runtime_api().at(block.hash()); - let max_fee = Self::weight_to_fee(&runtime_api, self.max_block_weight()).await?; - let gas_limit = U256::from(max_fee / GAS_PRICE as u128); + pub async fn evm_block( + &self, + block: Arc<SubstrateBlock>, + hydrated_transactions: bool, + ) -> Block { + let runtime_api = self.api.runtime_api().at(block.hash()); + let gas_limit = Self::block_gas_limit(&runtime_api).await.unwrap_or_default(); let header = block.header(); let timestamp = extract_block_timestamp(&block).await.unwrap_or_default(); @@ -782,7 +665,24 @@ impl Client { let state_root = header.state_root.0.into(); let extrinsics_root = header.extrinsics_root.0.into(); - Ok(Block { + let receipts = extract_receipts_from_block(&block).await.unwrap_or_default(); + let gas_used = + receipts.iter().fold(U256::zero(), |acc, (_, receipt)| acc + receipt.gas_used); + let transactions = if hydrated_transactions { + receipts + .into_iter() + .map(|(signed_tx, receipt)| TransactionInfo::new(receipt, signed_tx)) + .collect::<Vec<TransactionInfo>>() + .into() + } else { + receipts + .into_iter() + .map(|(_, receipt)| receipt.transaction_hash) + .collect::<Vec<_>>() + .into() + }; + + Block { hash: block.hash(), parent_hash, state_root, @@ -790,33 +690,31 @@ impl Client { number: header.number.into(), timestamp: timestamp.into(), difficulty: Some(0u32.into()), + base_fee_per_gas: self.gas_price(&block.hash().into()).await.ok(), gas_limit, - logs_bloom: Bytes256([0u8; 256]), + gas_used, receipts_root: extrinsics_root, + transactions, ..Default::default() - }) + } } /// Convert a weight to a fee. - async fn weight_to_fee( + async fn block_gas_limit( runtime_api: &subxt::runtime_api::RuntimeApi<SrcChainConfig, OnlineClient<SrcChainConfig>>, - weight: Weight, - ) -> Result<Balance, ClientError> { - let payload = subxt_client::apis() - .transaction_payment_api() - .query_weight_to_fee(weight.into()); - - let fee = runtime_api.call(payload).await?; - Ok(fee) + ) -> Result<U256, ClientError> { + let payload = subxt_client::apis().revive_api().block_gas_limit(); + let gas_limit = runtime_api.call(payload).await?; + Ok(*gas_limit) } /// Get the chain ID. pub fn chain_id(&self) -> u64 { - self.inner.chain_id + self.chain_id } /// Get the Max Block Weight. pub fn max_block_weight(&self) -> Weight { - self.inner.max_block_weight + self.max_block_weight } } diff --git a/substrate/frame/revive/rpc/src/eth-indexer.rs b/substrate/frame/revive/rpc/src/eth-indexer.rs new file mode 100644 index 0000000000000000000000000000000000000000..894143be0a525a581a32301dc12070b3bbc9143b --- /dev/null +++ b/substrate/frame/revive/rpc/src/eth-indexer.rs @@ -0,0 +1,88 @@ +// 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. +//! The Ethereum JSON-RPC server. +use clap::Parser; +use pallet_revive_eth_rpc::{ + client::{connect, Client, SubstrateBlockNumber}, + BlockInfoProvider, BlockInfoProviderImpl, DBReceiptProvider, ReceiptProvider, +}; +use sc_cli::SharedParams; +use std::sync::Arc; + +// Parsed command instructions from the command line +#[derive(Parser, Debug)] +#[clap(author, about, version)] +pub struct CliCommand { + /// The node url to connect to + #[clap(long, default_value = "ws://127.0.0.1:9944")] + pub node_rpc_url: String, + + /// Specifies the block number to start indexing from, going backwards from the current block. + /// If not provided, only new blocks will be indexed + #[clap(long)] + pub oldest_block: Option<SubstrateBlockNumber>, + + /// The database used to store Ethereum transaction hashes. + #[clap(long, env = "DATABASE_URL")] + pub database_url: String, + + #[allow(missing_docs)] + #[clap(flatten)] + pub shared_params: SharedParams, +} + +/// Initialize the logger +#[cfg(not(test))] +fn init_logger(params: &SharedParams) -> anyhow::Result<()> { + let mut logger = sc_cli::LoggerBuilder::new(params.log_filters().join(",")); + logger + .with_log_reloading(params.enable_log_reloading) + .with_detailed_output(params.detailed_log_output); + + if let Some(tracing_targets) = ¶ms.tracing_targets { + let tracing_receiver = params.tracing_receiver.into(); + logger.with_profiling(tracing_receiver, tracing_targets); + } + + if params.disable_log_color { + logger.with_colors(false); + } + + logger.init()?; + Ok(()) +} + +#[tokio::main] +pub async fn main() -> anyhow::Result<()> { + let CliCommand { + node_rpc_url, database_url, shared_params: _shared_params, oldest_block, .. + } = CliCommand::parse(); + + #[cfg(not(test))] + init_logger(&_shared_params)?; + + let (api, rpc_client, rpc) = connect(&node_rpc_url).await?; + let block_provider: Arc<dyn BlockInfoProvider> = + Arc::new(BlockInfoProviderImpl::new(0, api.clone(), rpc.clone())); + let receipt_provider: Arc<dyn ReceiptProvider> = + Arc::new(DBReceiptProvider::new(&database_url, false, block_provider.clone()).await?); + + let client = Client::new(api, rpc_client, rpc, block_provider, receipt_provider).await?; + client.subscribe_and_cache_receipts(oldest_block).await?; + + Ok(()) +} diff --git a/substrate/frame/revive/rpc/src/eth-rpc-tester.rs b/substrate/frame/revive/rpc/src/eth-rpc-tester.rs new file mode 100644 index 0000000000000000000000000000000000000000..0ddad6874dfd5b188508d59a4dacf31ee40f85ca --- /dev/null +++ b/substrate/frame/revive/rpc/src/eth-rpc-tester.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. +use clap::Parser; +use jsonrpsee::http_client::HttpClientBuilder; +use pallet_revive::evm::{Account, BlockTag, ReceiptInfo}; +use pallet_revive_eth_rpc::{ + example::{wait_for_receipt, TransactionBuilder}, + EthRpcClient, +}; +use tokio::{ + io::{AsyncBufReadExt, BufReader}, + process::{Child, ChildStderr, Command}, + signal::unix::{signal, SignalKind}, +}; + +const DOCKER_CONTAINER_NAME: &str = "eth-rpc-test"; + +#[derive(Parser, Debug)] +#[clap(author, about, version)] +pub struct CliCommand { + /// The parity docker image e.g eth-rpc:master-fb2e414f + #[clap(long, default_value = "eth-rpc:master-fb2e414f")] + docker_image: String, + + /// The docker binary + /// Either docker or podman + #[clap(long, default_value = "docker")] + docker_bin: String, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let CliCommand { docker_bin, docker_image, .. } = CliCommand::parse(); + + let mut docker_process = start_docker(&docker_bin, &docker_image)?; + let stderr = docker_process.stderr.take().unwrap(); + + tokio::select! { + result = docker_process.wait() => { + println!("docker failed: {result:?}"); + } + _ = interrupt() => { + kill_docker().await?; + } + _ = test_eth_rpc(stderr) => { + kill_docker().await?; + } + } + + Ok(()) +} + +async fn interrupt() { + let mut sigint = signal(SignalKind::interrupt()).expect("failed to listen for SIGINT"); + let mut sigterm = signal(SignalKind::terminate()).expect("failed to listen for SIGTERM"); + + tokio::select! { + _ = sigint.recv() => {}, + _ = sigterm.recv() => {}, + } +} + +fn start_docker(docker_bin: &str, docker_image: &str) -> anyhow::Result<Child> { + let docker_process = Command::new(docker_bin) + .args([ + "run", + "--name", + DOCKER_CONTAINER_NAME, + "--rm", + "-p", + "8545:8545", + &format!("docker.io/paritypr/{docker_image}"), + "--node-rpc-url", + "wss://westend-asset-hub-rpc.polkadot.io", + "--rpc-cors", + "all", + "--unsafe-rpc-external", + "--log=sc_rpc_server:info", + ]) + .stderr(std::process::Stdio::piped()) + .kill_on_drop(true) + .spawn()?; + + Ok(docker_process) +} + +async fn kill_docker() -> anyhow::Result<()> { + Command::new("docker").args(["kill", DOCKER_CONTAINER_NAME]).output().await?; + Ok(()) +} + +async fn test_eth_rpc(stderr: ChildStderr) -> anyhow::Result<()> { + let mut reader = BufReader::new(stderr).lines(); + while let Some(line) = reader.next_line().await? { + println!("{line}"); + if line.contains("Running JSON-RPC server") { + break; + } + } + + let account = Account::default(); + let data = vec![]; + let (bytes, _) = pallet_revive_fixtures::compile_module("dummy")?; + let input = bytes.into_iter().chain(data).collect::<Vec<u8>>(); + + println!("Account:"); + println!("- address: {:?}", account.address()); + let client = HttpClientBuilder::default().build("http://localhost:8545")?; + + let nonce = client.get_transaction_count(account.address(), BlockTag::Latest.into()).await?; + let balance = client.get_balance(account.address(), BlockTag::Latest.into()).await?; + println!("- nonce: {nonce:?}"); + println!("- balance: {balance:?}"); + + println!("\n\n=== Deploying dummy contract ===\n\n"); + let hash = TransactionBuilder::default().input(input).send(&client).await?; + + println!("Hash: {hash:?}"); + println!("Waiting for receipt..."); + let ReceiptInfo { block_number, gas_used, contract_address, .. } = + wait_for_receipt(&client, hash).await?; + + let contract_address = contract_address.unwrap(); + println!("\nReceipt:"); + println!("Block explorer: https://westend-asset-hub-eth-explorer.parity.io/{hash:?}"); + println!("- Block number: {block_number}"); + println!("- Gas used: {gas_used}"); + println!("- Address: {contract_address:?}"); + + println!("\n\n=== Calling dummy contract ===\n\n"); + let hash = TransactionBuilder::default().to(contract_address).send(&client).await?; + + println!("Hash: {hash:?}"); + println!("Waiting for receipt..."); + + let ReceiptInfo { block_number, gas_used, to, .. } = wait_for_receipt(&client, hash).await?; + println!("\nReceipt:"); + println!("Block explorer: https://westend-asset-hub-eth-explorer.parity.io/{hash:?}"); + println!("- Block number: {block_number}"); + println!("- Gas used: {gas_used}"); + println!("- To: {to:?}"); + Ok(()) +} diff --git a/substrate/frame/revive/rpc/src/example.rs b/substrate/frame/revive/rpc/src/example.rs index 3b9a33296ef4d9073c52e44bdb895cbe57875dc5..aad5b4fbc344d511088d0a73ee99fab3de042749 100644 --- a/substrate/frame/revive/rpc/src/example.rs +++ b/substrate/frame/revive/rpc/src/example.rs @@ -15,8 +15,6 @@ // See the License for the specific language governing permissions and // limitations under the License. //! Example utilities -#![cfg(any(feature = "example", test))] - use crate::{EthRpcClient, ReceiptInfo}; use anyhow::Context; use pallet_revive::evm::{ diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 6a324e63a8573eda22dec3f4c865fd7576568def..f2567db8a330d42f226cf82fb23c72dc810f7a13 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -17,13 +17,13 @@ //! The [`EthRpcServer`] RPC server implementation #![cfg_attr(docsrs, feature(doc_cfg))] -use crate::runtime::GAS_PRICE; use client::ClientError; use jsonrpsee::{ core::{async_trait, RpcResult}, types::{ErrorCode, ErrorObjectOwned}, }; -use pallet_revive::{evm::*, EthContractResult}; +use pallet_revive::evm::*; +use sp_arithmetic::Permill; use sp_core::{keccak_256, H160, H256, U256}; use thiserror::Error; @@ -35,6 +35,12 @@ pub mod subxt_client; #[cfg(test)] mod tests; +mod block_info_provider; +pub use block_info_provider::*; + +mod receipt_provider; +pub use receipt_provider::*; + mod rpc_health; pub use rpc_health::*; @@ -121,44 +127,44 @@ impl EthRpcServer for EthRpcServerImpl { transaction_hash: H256, ) -> RpcResult<Option<ReceiptInfo>> { let receipt = self.client.receipt(&transaction_hash).await; - log::debug!(target: LOG_TARGET, "transaction_receipt for {transaction_hash:?}: {}", receipt.is_some()); + log::debug!( + target: LOG_TARGET, + "transaction_receipt for {transaction_hash:?}: received: {received} - success: {success:?}", + received = receipt.is_some(), + success = receipt.as_ref().map(|r| r.status == Some(U256::one())) + ); Ok(receipt) } async fn estimate_gas( &self, transaction: GenericTransaction, - _block: Option<BlockNumberOrTag>, + block: Option<BlockNumberOrTag>, ) -> RpcResult<U256> { - let result = self.client.estimate_gas(&transaction, BlockTag::Latest.into()).await?; - Ok(result) + let dry_run = self.client.dry_run(transaction, block.unwrap_or_default().into()).await?; + Ok(dry_run.eth_gas) + } + + async fn call( + &self, + transaction: GenericTransaction, + block: Option<BlockNumberOrTagOrHash>, + ) -> RpcResult<Bytes> { + let dry_run = self + .client + .dry_run(transaction, block.unwrap_or_else(|| BlockTag::Latest.into())) + .await?; + Ok(dry_run.data.into()) } async fn send_raw_transaction(&self, transaction: Bytes) -> RpcResult<H256> { let hash = H256(keccak_256(&transaction.0)); - - let tx = TransactionSigned::decode(&transaction.0).map_err(|err| { - log::debug!(target: LOG_TARGET, "Failed to decode transaction: {err:?}"); - EthRpcError::from(err) - })?; - - let eth_addr = tx.recover_eth_address().map_err(|err| { - log::debug!(target: LOG_TARGET, "Failed to recover eth address: {err:?}"); - EthRpcError::InvalidSignature + let call = subxt_client::tx().revive().eth_transact(transaction.0); + self.client.submit(call).await.map_err(|err| { + log::debug!(target: LOG_TARGET, "submit call failed: {err:?}"); + err })?; - let tx = GenericTransaction::from_signed(tx, Some(eth_addr)); - - // Dry run the transaction to get the weight limit and storage deposit limit - let dry_run = self.client.dry_run(&tx, BlockTag::Latest.into()).await?; - - let EthContractResult { gas_required, storage_deposit, .. } = dry_run; - let call = subxt_client::tx().revive().eth_transact( - transaction.0, - gas_required.into(), - storage_deposit, - ); - self.client.submit(call).await?; log::debug!(target: LOG_TARGET, "send_raw_transaction hash: {hash:?}"); Ok(hash) } @@ -202,12 +208,12 @@ impl EthRpcServer for EthRpcServerImpl { async fn get_block_by_hash( &self, block_hash: H256, - _hydrated_transactions: bool, + hydrated_transactions: bool, ) -> RpcResult<Option<Block>> { let Some(block) = self.client.block_by_hash(&block_hash).await? else { return Ok(None); }; - let block = self.client.evm_block(block).await?; + let block = self.client.evm_block(block, hydrated_transactions).await; Ok(Some(block)) } @@ -222,7 +228,13 @@ impl EthRpcServer for EthRpcServerImpl { } async fn gas_price(&self) -> RpcResult<U256> { - Ok(U256::from(GAS_PRICE)) + Ok(self.client.gas_price(&BlockTag::Latest.into()).await?) + } + + async fn max_priority_fee_per_gas(&self) -> RpcResult<U256> { + // TODO: Provide better estimation + let gas_price = self.gas_price().await?; + Ok(Permill::from_percent(20).mul_ceil(gas_price)) } async fn get_code(&self, address: H160, block: BlockNumberOrTagOrHash) -> RpcResult<Bytes> { @@ -234,27 +246,15 @@ impl EthRpcServer for EthRpcServerImpl { Ok(self.accounts.iter().map(|account| account.address()).collect()) } - async fn call( - &self, - transaction: GenericTransaction, - block: Option<BlockNumberOrTagOrHash>, - ) -> RpcResult<Bytes> { - let dry_run = self - .client - .dry_run(&transaction, block.unwrap_or_else(|| BlockTag::Latest.into())) - .await?; - Ok(dry_run.result.into()) - } - async fn get_block_by_number( &self, block: BlockNumberOrTag, - _hydrated_transactions: bool, + hydrated_transactions: bool, ) -> RpcResult<Option<Block>> { let Some(block) = self.client.block_by_number_or_tag(&block).await? else { return Ok(None); }; - let block = self.client.evm_block(block).await?; + let block = self.client.evm_block(block, hydrated_transactions).await; Ok(Some(block)) } diff --git a/substrate/frame/revive/rpc/src/receipt_provider.rs b/substrate/frame/revive/rpc/src/receipt_provider.rs new file mode 100644 index 0000000000000000000000000000000000000000..5c102b3d3d41a8e53d0bfbf5b9a3f7a629c5f896 --- /dev/null +++ b/substrate/frame/revive/rpc/src/receipt_provider.rs @@ -0,0 +1,240 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + client::SubstrateBlock, + subxt_client::{ + revive::{calls::types::EthTransact, events::ContractEmitted}, + system::events::ExtrinsicSuccess, + transaction_payment::events::TransactionFeePaid, + SrcChainConfig, + }, + ClientError, LOG_TARGET, +}; +use futures::{stream, StreamExt}; +use jsonrpsee::core::async_trait; +use pallet_revive::{ + create1, + evm::{GenericTransaction, Log, ReceiptInfo, TransactionSigned, H256, U256}, +}; +use sp_core::keccak_256; +use tokio::join; + +mod cache; +pub use cache::CacheReceiptProvider; + +mod db; +pub use db::DBReceiptProvider; + +/// Provide means to store and retrieve receipts. +#[async_trait] +pub trait ReceiptProvider: Send + Sync { + /// Insert receipts into the provider. + async fn insert(&self, block_hash: &H256, receipts: &[(TransactionSigned, ReceiptInfo)]); + + /// Remove receipts with the given block hash. + async fn remove(&self, block_hash: &H256); + + /// Get the receipt for the given block hash and transaction index. + async fn receipt_by_block_hash_and_index( + &self, + block_hash: &H256, + transaction_index: &U256, + ) -> Option<ReceiptInfo>; + + /// Get the number of receipts per block. + async fn receipts_count_per_block(&self, block_hash: &H256) -> Option<usize>; + + /// Get the receipt for the given transaction hash. + async fn receipt_by_hash(&self, transaction_hash: &H256) -> Option<ReceiptInfo>; + + /// Get the signed transaction for the given transaction hash. + async fn signed_tx_by_hash(&self, transaction_hash: &H256) -> Option<TransactionSigned>; +} + +#[async_trait] +impl<Main: ReceiptProvider, Fallback: ReceiptProvider> ReceiptProvider for (Main, Fallback) { + async fn insert(&self, block_hash: &H256, receipts: &[(TransactionSigned, ReceiptInfo)]) { + join!(self.0.insert(block_hash, receipts), self.1.insert(block_hash, receipts)); + } + + async fn remove(&self, block_hash: &H256) { + join!(self.0.remove(block_hash), self.1.remove(block_hash)); + } + + async fn receipt_by_block_hash_and_index( + &self, + block_hash: &H256, + transaction_index: &U256, + ) -> Option<ReceiptInfo> { + if let Some(receipt) = + self.0.receipt_by_block_hash_and_index(block_hash, transaction_index).await + { + return Some(receipt); + } + + self.1.receipt_by_block_hash_and_index(block_hash, transaction_index).await + } + + async fn receipts_count_per_block(&self, block_hash: &H256) -> Option<usize> { + if let Some(count) = self.0.receipts_count_per_block(block_hash).await { + return Some(count); + } + self.1.receipts_count_per_block(block_hash).await + } + + async fn receipt_by_hash(&self, hash: &H256) -> Option<ReceiptInfo> { + if let Some(receipt) = self.0.receipt_by_hash(hash).await { + return Some(receipt); + } + self.1.receipt_by_hash(hash).await + } + + async fn signed_tx_by_hash(&self, hash: &H256) -> Option<TransactionSigned> { + if let Some(tx) = self.0.signed_tx_by_hash(hash).await { + return Some(tx); + } + self.1.signed_tx_by_hash(hash).await + } +} + +/// Extract a [`TransactionSigned`] and a [`ReceiptInfo`] and from an extrinsic. +pub async fn extract_receipt_from_extrinsic( + block: &SubstrateBlock, + ext: subxt::blocks::ExtrinsicDetails<SrcChainConfig, subxt::OnlineClient<SrcChainConfig>>, + call: EthTransact, +) -> Result<(TransactionSigned, ReceiptInfo), ClientError> { + let transaction_index = ext.index(); + let block_number = U256::from(block.number()); + let block_hash = block.hash(); + let events = ext.events().await?; + + let success = events.has::<ExtrinsicSuccess>().inspect_err(|err| { + log::debug!(target: LOG_TARGET, "Failed to lookup for ExtrinsicSuccess event in block {block_number}: {err:?}") + })?; + let tx_fees = events + .find_first::<TransactionFeePaid>()? + .ok_or(ClientError::TxFeeNotFound) + .inspect_err( + |err| log::debug!(target: LOG_TARGET, "TransactionFeePaid not found in events for block {block_number}\n{err:?}") + )?; + let transaction_hash = H256(keccak_256(&call.payload)); + + let signed_tx = + TransactionSigned::decode(&call.payload).map_err(|_| ClientError::TxDecodingFailed)?; + let from = signed_tx.recover_eth_address().map_err(|_| { + log::error!(target: LOG_TARGET, "Failed to recover eth address from signed tx"); + ClientError::RecoverEthAddressFailed + })?; + + let tx_info = GenericTransaction::from_signed(signed_tx.clone(), Some(from)); + let gas_price = tx_info.gas_price.unwrap_or_default(); + let gas_used = (tx_fees.tip.saturating_add(tx_fees.actual_fee)) + .checked_div(gas_price.as_u128()) + .unwrap_or_default(); + + // get logs from ContractEmitted event + let logs = events + .iter() + .filter_map(|event_details| { + let event_details = event_details.ok()?; + let event = event_details.as_event::<ContractEmitted>().ok()??; + + Some(Log { + address: event.contract, + topics: event.topics, + data: Some(event.data.into()), + block_number: Some(block_number), + transaction_hash, + transaction_index: Some(transaction_index.into()), + block_hash: Some(block_hash), + log_index: Some(event_details.index().into()), + ..Default::default() + }) + }) + .collect(); + + let contract_address = if tx_info.to.is_none() { + Some(create1( + &from, + tx_info + .nonce + .unwrap_or_default() + .try_into() + .map_err(|_| ClientError::ConversionFailed)?, + )) + } else { + None + }; + + log::debug!(target: LOG_TARGET, "Adding receipt for tx hash: {transaction_hash:?} - block: {block_number:?}"); + let receipt = ReceiptInfo::new( + block_hash, + block_number, + contract_address, + from, + logs, + tx_info.to, + gas_price, + gas_used.into(), + success, + transaction_hash, + transaction_index.into(), + tx_info.r#type.unwrap_or_default(), + ); + Ok((signed_tx, receipt)) +} + +/// Extract receipts from block. +pub async fn extract_receipts_from_block( + block: &SubstrateBlock, +) -> Result<Vec<(TransactionSigned, ReceiptInfo)>, ClientError> { + // Filter extrinsics from pallet_revive + let extrinsics = block.extrinsics().await.inspect_err(|err| { + log::debug!(target: LOG_TARGET, "Error fetching for #{:?} extrinsics: {err:?}", block.number()); + })?; + + let extrinsics = extrinsics.iter().flat_map(|ext| { + let call = ext.as_extrinsic::<EthTransact>().ok()??; + Some((ext, call)) + }); + + stream::iter(extrinsics) + .map(|(ext, call)| async move { extract_receipt_from_extrinsic(block, ext, call).await }) + .buffer_unordered(10) + .collect::<Vec<Result<_, _>>>() + .await + .into_iter() + .collect::<Result<Vec<_>, _>>() +} + +/// Extract receipt from transaction +pub async fn extract_receipts_from_transaction( + block: &SubstrateBlock, + transaction_index: usize, +) -> Result<(TransactionSigned, ReceiptInfo), ClientError> { + let extrinsics = block.extrinsics().await?; + let ext = extrinsics + .iter() + .nth(transaction_index) + .ok_or(ClientError::EthExtrinsicNotFound)?; + + let call = ext + .as_extrinsic::<EthTransact>()? + .ok_or_else(|| ClientError::EthExtrinsicNotFound)?; + extract_receipt_from_extrinsic(block, ext, call).await +} diff --git a/substrate/frame/revive/rpc/src/receipt_provider/cache.rs b/substrate/frame/revive/rpc/src/receipt_provider/cache.rs new file mode 100644 index 0000000000000000000000000000000000000000..39124929ec07dba8babe69dffd37e78d59eeb744 --- /dev/null +++ b/substrate/frame/revive/rpc/src/receipt_provider/cache.rs @@ -0,0 +1,148 @@ +// 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::ReceiptProvider; +use jsonrpsee::core::async_trait; +use pallet_revive::evm::{ReceiptInfo, TransactionSigned, H256, U256}; +use std::{collections::HashMap, sync::Arc}; +use tokio::sync::RwLock; + +/// A `[ReceiptProvider]` that caches receipts in memory. +#[derive(Clone, Default)] +pub struct CacheReceiptProvider { + cache: Arc<RwLock<ReceiptCache>>, +} + +impl CacheReceiptProvider { + /// Get a read access on the shared cache. + async fn cache(&self) -> tokio::sync::RwLockReadGuard<'_, ReceiptCache> { + self.cache.read().await + } +} + +#[async_trait] +impl ReceiptProvider for CacheReceiptProvider { + async fn insert(&self, block_hash: &H256, receipts: &[(TransactionSigned, ReceiptInfo)]) { + let mut cache = self.cache.write().await; + cache.insert(block_hash, receipts); + } + + async fn remove(&self, block_hash: &H256) { + let mut cache = self.cache.write().await; + cache.remove(block_hash); + } + + async fn receipt_by_block_hash_and_index( + &self, + block_hash: &H256, + transaction_index: &U256, + ) -> Option<ReceiptInfo> { + let cache = self.cache().await; + let receipt_hash = cache + .transaction_hashes_by_block_and_index + .get(block_hash)? + .get(transaction_index)?; + let receipt = cache.receipts_by_hash.get(receipt_hash)?; + Some(receipt.clone()) + } + + async fn receipts_count_per_block(&self, block_hash: &H256) -> Option<usize> { + let cache = self.cache().await; + cache.transaction_hashes_by_block_and_index.get(block_hash).map(|v| v.len()) + } + + async fn receipt_by_hash(&self, hash: &H256) -> Option<ReceiptInfo> { + let cache = self.cache().await; + cache.receipts_by_hash.get(hash).cloned() + } + + async fn signed_tx_by_hash(&self, hash: &H256) -> Option<TransactionSigned> { + let cache = self.cache().await; + cache.signed_tx_by_hash.get(hash).cloned() + } +} + +#[derive(Default)] +struct ReceiptCache { + /// A map of receipts by transaction hash. + receipts_by_hash: HashMap<H256, ReceiptInfo>, + + /// A map of Signed transaction by transaction hash. + signed_tx_by_hash: HashMap<H256, TransactionSigned>, + + /// A map of receipt hashes by block hash. + transaction_hashes_by_block_and_index: HashMap<H256, HashMap<U256, H256>>, +} + +impl ReceiptCache { + /// Insert new receipts into the cache. + pub fn insert(&mut self, block_hash: &H256, receipts: &[(TransactionSigned, ReceiptInfo)]) { + if !receipts.is_empty() { + let values = receipts + .iter() + .map(|(_, receipt)| (receipt.transaction_index, receipt.transaction_hash)) + .collect::<HashMap<_, _>>(); + + self.transaction_hashes_by_block_and_index.insert(*block_hash, values); + + self.receipts_by_hash.extend( + receipts.iter().map(|(_, receipt)| (receipt.transaction_hash, receipt.clone())), + ); + + self.signed_tx_by_hash.extend( + receipts + .iter() + .map(|(signed_tx, receipt)| (receipt.transaction_hash, signed_tx.clone())), + ) + } + } + + /// Remove entry from the cache. + pub fn remove(&mut self, hash: &H256) { + if let Some(entries) = self.transaction_hashes_by_block_and_index.remove(hash) { + for hash in entries.values() { + self.receipts_by_hash.remove(hash); + self.signed_tx_by_hash.remove(hash); + } + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn cache_insert_and_remove_works() { + let mut cache = ReceiptCache::default(); + + for i in 1u8..=3 { + let hash = H256::from([i; 32]); + cache.insert( + &hash, + &[( + TransactionSigned::default(), + ReceiptInfo { transaction_hash: hash, ..Default::default() }, + )], + ); + } + + cache.remove(&H256::from([1u8; 32])); + assert_eq!(cache.transaction_hashes_by_block_and_index.len(), 2); + assert_eq!(cache.receipts_by_hash.len(), 2); + assert_eq!(cache.signed_tx_by_hash.len(), 2); + } +} diff --git a/substrate/frame/revive/rpc/src/receipt_provider/db.rs b/substrate/frame/revive/rpc/src/receipt_provider/db.rs new file mode 100644 index 0000000000000000000000000000000000000000..63917d6193ea7e7bbf55e3e8958b8ab9a7b2bd54 --- /dev/null +++ b/substrate/frame/revive/rpc/src/receipt_provider/db.rs @@ -0,0 +1,216 @@ +// 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::BlockInfoProvider; +use jsonrpsee::core::async_trait; +use pallet_revive::evm::{ReceiptInfo, TransactionSigned}; +use sp_core::{H256, U256}; +use sqlx::{query, SqlitePool}; +use std::sync::Arc; + +/// A `[ReceiptProvider]` that stores receipts in a SQLite database. +#[derive(Clone)] +pub struct DBReceiptProvider { + /// The database pool. + pool: SqlitePool, + /// The block provider used to fetch blocks, and reconstruct receipts. + block_provider: Arc<dyn BlockInfoProvider>, + /// weather or not we should write to the DB. + read_only: bool, +} + +impl DBReceiptProvider { + /// Create a new `DBReceiptProvider` with the given database URL and block provider. + pub async fn new( + database_url: &str, + read_only: bool, + block_provider: Arc<dyn BlockInfoProvider>, + ) -> Result<Self, sqlx::Error> { + let pool = SqlitePool::connect(database_url).await?; + Ok(Self { pool, block_provider, read_only }) + } + + async fn fetch_row(&self, transaction_hash: &H256) -> Option<(H256, usize)> { + let transaction_hash = hex::encode(transaction_hash); + let result = query!( + r#" + SELECT block_hash, transaction_index + FROM transaction_hashes + WHERE transaction_hash = $1 + "#, + transaction_hash + ) + .fetch_optional(&self.pool) + .await + .ok()??; + + let block_hash = result.block_hash.parse::<H256>().ok()?; + let transaction_index = result.transaction_index.try_into().ok()?; + Some((block_hash, transaction_index)) + } +} + +#[async_trait] +impl ReceiptProvider for DBReceiptProvider { + async fn insert(&self, block_hash: &H256, receipts: &[(TransactionSigned, ReceiptInfo)]) { + if self.read_only { + return + } + + let block_hash_str = hex::encode(block_hash); + for (_, receipt) in receipts { + let transaction_hash = hex::encode(receipt.transaction_hash); + let transaction_index = receipt.transaction_index.as_u32() as i32; + + let result = query!( + r#" + INSERT INTO transaction_hashes (transaction_hash, block_hash, transaction_index) + VALUES ($1, $2, $3) + + ON CONFLICT(transaction_hash) DO UPDATE SET + block_hash = EXCLUDED.block_hash, + transaction_index = EXCLUDED.transaction_index + "#, + transaction_hash, + block_hash_str, + transaction_index + ) + .execute(&self.pool) + .await; + + if let Err(err) = result { + log::error!( + "Error inserting transaction for block hash {block_hash:?}: {:?}", + err + ); + } + } + } + + async fn remove(&self, _block_hash: &H256) {} + + async fn receipts_count_per_block(&self, block_hash: &H256) -> Option<usize> { + let block_hash = hex::encode(block_hash); + let row = query!( + r#" + SELECT COUNT(*) as count + FROM transaction_hashes + WHERE block_hash = $1 + "#, + block_hash + ) + .fetch_one(&self.pool) + .await + .ok()?; + + let count = row.count as usize; + Some(count) + } + + async fn receipt_by_block_hash_and_index( + &self, + block_hash: &H256, + transaction_index: &U256, + ) -> Option<ReceiptInfo> { + let block = self.block_provider.block_by_hash(block_hash).await.ok()??; + let transaction_index: usize = transaction_index.as_usize(); // TODO: check for overflow + let (_, receipt) = + extract_receipts_from_transaction(&block, transaction_index).await.ok()?; + Some(receipt) + } + + async fn receipt_by_hash(&self, transaction_hash: &H256) -> Option<ReceiptInfo> { + let (block_hash, transaction_index) = self.fetch_row(transaction_hash).await?; + + let block = self.block_provider.block_by_hash(&block_hash).await.ok()??; + let (_, receipt) = + extract_receipts_from_transaction(&block, transaction_index).await.ok()?; + Some(receipt) + } + + async fn signed_tx_by_hash(&self, transaction_hash: &H256) -> Option<TransactionSigned> { + let transaction_hash = hex::encode(transaction_hash); + let result = query!( + r#" + SELECT block_hash, transaction_index + FROM transaction_hashes + WHERE transaction_hash = $1 + "#, + transaction_hash + ) + .fetch_optional(&self.pool) + .await + .ok()??; + + let block_hash = result.block_hash.parse::<H256>().ok()?; + let transaction_index = result.transaction_index.try_into().ok()?; + + let block = self.block_provider.block_by_hash(&block_hash).await.ok()??; + let (signed_tx, _) = + extract_receipts_from_transaction(&block, transaction_index).await.ok()?; + Some(signed_tx) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::MockBlockInfoProvider; + use pallet_revive::evm::{ReceiptInfo, TransactionSigned}; + use sp_core::H256; + use sqlx::SqlitePool; + + async fn setup_sqlite_provider(pool: SqlitePool) -> DBReceiptProvider { + DBReceiptProvider { + pool, + block_provider: Arc::new(MockBlockInfoProvider {}), + read_only: false, + } + } + + #[sqlx::test] + async fn test_insert(pool: SqlitePool) { + let provider = setup_sqlite_provider(pool).await; + let block_hash = H256::default(); + let receipts = vec![(TransactionSigned::default(), ReceiptInfo::default())]; + + provider.insert(&block_hash, &receipts).await; + let row = provider.fetch_row(&receipts[0].1.transaction_hash).await; + assert_eq!(row, Some((block_hash, 0))); + } + + #[sqlx::test] + async fn test_receipts_count_per_block(pool: SqlitePool) { + let provider = setup_sqlite_provider(pool).await; + let block_hash = H256::default(); + let receipts = vec![ + ( + TransactionSigned::default(), + ReceiptInfo { transaction_hash: H256::from([0u8; 32]), ..Default::default() }, + ), + ( + TransactionSigned::default(), + ReceiptInfo { transaction_hash: H256::from([1u8; 32]), ..Default::default() }, + ), + ]; + + provider.insert(&block_hash, &receipts).await; + let count = provider.receipts_count_per_block(&block_hash).await; + assert_eq!(count, Some(2)); + } +} diff --git a/substrate/frame/revive/rpc/src/rpc_health.rs b/substrate/frame/revive/rpc/src/rpc_health.rs index f94d4b82a80fbbb5df7f29f4963f7dfa4e9daa6a..35c5a588f284dfc9cb3c6ae5b589cbf30f74e0c1 100644 --- a/substrate/frame/revive/rpc/src/rpc_health.rs +++ b/substrate/frame/revive/rpc/src/rpc_health.rs @@ -25,6 +25,10 @@ pub trait SystemHealthRpc { /// Proxy the substrate chain system_health RPC call. #[method(name = "system_health")] async fn system_health(&self) -> RpcResult<Health>; + + ///Returns the number of peers currently connected to the client. + #[method(name = "net_peerCount")] + async fn net_peer_count(&self) -> RpcResult<U64>; } pub struct SystemHealthRpcServerImpl { @@ -47,4 +51,9 @@ impl SystemHealthRpcServer for SystemHealthRpcServerImpl { should_have_peers: health.should_have_peers, }) } + + async fn net_peer_count(&self) -> RpcResult<U64> { + let health = self.client.system_health().await?; + Ok((health.peers as u64).into()) + } } diff --git a/substrate/frame/revive/rpc/src/rpc_methods_gen.rs b/substrate/frame/revive/rpc/src/rpc_methods_gen.rs index 33908036896989593caec66f32db80c4df8ed017..da60360d9e61b70241d08c987bfb8683719d137f 100644 --- a/substrate/frame/revive/rpc/src/rpc_methods_gen.rs +++ b/substrate/frame/revive/rpc/src/rpc_methods_gen.rs @@ -14,6 +14,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + //! Generated JSON-RPC methods. #![allow(missing_docs)] @@ -141,6 +142,10 @@ pub trait EthRpc { transaction_hash: H256, ) -> RpcResult<Option<ReceiptInfo>>; + /// Returns the current maxPriorityFeePerGas per gas in wei. + #[method(name = "eth_maxPriorityFeePerGas")] + async fn max_priority_fee_per_gas(&self) -> RpcResult<U256>; + /// Submits a raw transaction. For EIP-4844 transactions, the raw form must be the network form. /// This means it includes the blobs, KZG commitments, and KZG proofs. #[method(name = "eth_sendRawTransaction")] diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs index a232b231bc7c0337c71e7e45de462573ffc63e25..1e1c395028a457461987056528d837dbdf87a057 100644 --- a/substrate/frame/revive/rpc/src/subxt_client.rs +++ b/substrate/frame/revive/rpc/src/subxt_client.rs @@ -27,8 +27,16 @@ use subxt::config::{signed_extensions, Config, PolkadotConfig}; with = "::subxt::utils::Static<::sp_core::U256>" ), substitute_type( - path = "pallet_revive::primitives::EthContractResult<A, B>", - with = "::subxt::utils::Static<::pallet_revive::EthContractResult<A, B>>" + path = "pallet_revive::evm::api::rpc_types_gen::GenericTransaction", + with = "::subxt::utils::Static<::pallet_revive::evm::GenericTransaction>" + ), + substitute_type( + path = "pallet_revive::primitives::EthTransactInfo<B>", + with = "::subxt::utils::Static<::pallet_revive::EthTransactInfo<B>>" + ), + substitute_type( + path = "pallet_revive::primitives::EthTransactError", + with = "::subxt::utils::Static<::pallet_revive::EthTransactError>" ), substitute_type( path = "pallet_revive::primitives::ExecReturnValue", diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 7734c8c572090d3b43cffbce77d0002b7975336b..e64e16d45b2aec2a9c2a2e972684ab03b64df8d2 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -222,21 +222,22 @@ async fn deploy_and_call() -> anyhow::Result<()> { async fn revert_call() -> anyhow::Result<()> { let _lock = SHARED_RESOURCES.write(); let client = SharedResources::client().await; - let (bytecode, contract) = get_contract("revert")?; + let (bytecode, contract) = get_contract("Errors")?; let receipt = TransactionBuilder::default() - .input(contract.constructor.clone().unwrap().encode_input(bytecode, &[]).unwrap()) + .input(bytecode) .send_and_wait_for_receipt(&client) .await?; let err = TransactionBuilder::default() .to(receipt.contract_address.unwrap()) - .input(contract.function("doRevert")?.encode_input(&[])?.to_vec()) + .input(contract.function("triggerRequireError")?.encode_input(&[])?.to_vec()) .send(&client) .await .unwrap_err(); let call_err = unwrap_call_err!(err.source().unwrap()); - assert_eq!(call_err.message(), "Execution reverted: revert message"); + assert_eq!(call_err.message(), "execution reverted: This is a require error"); + assert_eq!(call_err.code(), 3); Ok(()) } @@ -244,7 +245,7 @@ async fn revert_call() -> anyhow::Result<()> { async fn event_logs() -> anyhow::Result<()> { let _lock = SHARED_RESOURCES.write(); let client = SharedResources::client().await; - let (bytecode, contract) = get_contract("event")?; + let (bytecode, contract) = get_contract("EventExample")?; let receipt = TransactionBuilder::default() .input(bytecode) .send_and_wait_for_receipt(&client) @@ -283,7 +284,7 @@ async fn invalid_transaction() -> anyhow::Result<()> { async fn native_evm_ratio_works() -> anyhow::Result<()> { let _lock = SHARED_RESOURCES.write(); let client = SharedResources::client().await; - let (bytecode, contract) = get_contract("piggyBank")?; + let (bytecode, contract) = get_contract("PiggyBank")?; let contract_address = TransactionBuilder::default() .input(bytecode) .send_and_wait_for_receipt(&client) diff --git a/substrate/frame/revive/src/benchmarking/call_builder.rs b/substrate/frame/revive/src/benchmarking/call_builder.rs index c666383abb2f82bc09f3198c12e327f0e9308efd..077e18ff5f0b805654d91c86f040e5269819eb67 100644 --- a/substrate/frame/revive/src/benchmarking/call_builder.rs +++ b/substrate/frame/revive/src/benchmarking/call_builder.rs @@ -21,8 +21,8 @@ use crate::{ exec::{ExportedFunction, Ext, Key, Stack}, storage::meter::Meter, transient_storage::MeterEntry, - wasm::{ApiVersion, PreparedCall, Runtime}, - BalanceOf, Config, DebugBuffer, Error, GasMeter, MomentOf, Origin, WasmBlob, Weight, + wasm::{PreparedCall, Runtime}, + BalanceOf, Config, Error, GasMeter, MomentOf, Origin, WasmBlob, Weight, }; use alloc::{vec, vec::Vec}; use frame_benchmarking::benchmarking; @@ -38,14 +38,13 @@ pub struct CallSetup<T: Config> { gas_meter: GasMeter<T>, storage_meter: Meter<T>, value: BalanceOf<T>, - debug_message: Option<DebugBuffer>, data: Vec<u8>, transient_storage_size: u32, } impl<T> Default for CallSetup<T> where - T: Config + pallet_balances::Config, + T: Config, BalanceOf<T>: Into<U256> + TryFrom<U256>, MomentOf<T>: Into<U256>, T::Hash: frame_support::traits::IsType<H256>, @@ -57,7 +56,7 @@ where impl<T> CallSetup<T> where - T: Config + pallet_balances::Config, + T: Config, BalanceOf<T>: Into<U256> + TryFrom<U256>, MomentOf<T>: Into<U256>, T::Hash: frame_support::traits::IsType<H256>, @@ -91,7 +90,6 @@ where gas_meter: GasMeter::new(Weight::MAX), storage_meter, value: 0u32.into(), - debug_message: None, data: vec![], transient_storage_size: 0, } @@ -122,16 +120,6 @@ where 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<DebugBuffer> { - self.debug_message.clone() - } - /// Get the call's input data. pub fn data(&self) -> Vec<u8> { self.data.clone() @@ -150,7 +138,6 @@ where &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(); @@ -164,13 +151,7 @@ where module: WasmBlob<T>, input: Vec<u8>, ) -> PreparedCall<'a, StackExt<'a, T>> { - module - .prepare_call( - Runtime::new(ext, input), - ExportedFunction::Call, - ApiVersion::UnsafeNewest, - ) - .unwrap() + module.prepare_call(Runtime::new(ext, input), ExportedFunction::Call).unwrap() } /// Add transient_storage diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 9c4d817a07dee527f1377ac7786e4d82957e76f6..94d8edef7772ba1e2a8156ffea196fd30394008a 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -23,10 +23,11 @@ mod call_builder; mod code; use self::{call_builder::CallSetup, code::WasmModule}; use crate::{ - exec::{Key, MomentOf}, + evm::runtime::GAS_PRICE, + exec::{Ext, Key, MomentOf}, limits, storage::WriteOutcome, - Pallet as Contracts, *, + ConversionPrecision, Pallet as Contracts, *, }; use alloc::{vec, vec::Vec}; use codec::{Encode, MaxEncodedLen}; @@ -34,13 +35,21 @@ use frame_benchmarking::v2::*; use frame_support::{ self, assert_ok, storage::child, - traits::{fungible::InspectHold, Currency}, + traits::fungible::InspectHold, weights::{Weight, WeightMeter}, }; use frame_system::RawOrigin; -use pallet_balances; -use pallet_revive_uapi::{CallFlags, ReturnErrorCode, StorageFlags}; -use sp_runtime::traits::{Bounded, Hash}; +use pallet_revive_uapi::{pack_hi_lo, CallFlags, ReturnErrorCode, StorageFlags}; +use sp_consensus_aura::AURA_ENGINE_ID; +use sp_consensus_babe::{ + digests::{PreDigest, PrimaryPreDigest}, + BABE_ENGINE_ID, +}; +use sp_consensus_slots::Slot; +use sp_runtime::{ + generic::{Digest, DigestItem}, + traits::{Bounded, Hash}, +}; /// How many runs we do per API benchmark. /// @@ -68,7 +77,7 @@ struct Contract<T: Config> { impl<T> Contract<T> where - T: Config + pallet_balances::Config, + T: Config, BalanceOf<T>: Into<U256> + TryFrom<U256>, MomentOf<T>: Into<U256>, T::Hash: frame_support::traits::IsType<H256>, @@ -103,12 +112,10 @@ where origin, 0u32.into(), Weight::MAX, - default_deposit_limit::<T>(), + DepositLimit::Balance(default_deposit_limit::<T>()), Code::Upload(module.code), data, salt, - DebugInfo::Skip, - CollectEvents::Skip, ); let address = outcome.result?.addr; @@ -220,11 +227,10 @@ fn default_deposit_limit<T: Config>() -> BalanceOf<T> { #[benchmarks( where BalanceOf<T>: Into<U256> + TryFrom<U256>, - T: Config + pallet_balances::Config, + T: Config, MomentOf<T>: Into<U256>, <T as frame_system::Config>::RuntimeEvent: From<pallet::Event<T>>, <T as Config>::RuntimeCall: From<frame_system::Call<T>>, - <pallet_balances::Pallet<T> as Currency<T::AccountId>>::Balance: From<BalanceOf<T>>, <T as frame_system::Config>::Hash: frame_support::traits::IsType<H256>, )] mod benchmarks { @@ -364,10 +370,10 @@ mod benchmarks { // 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. + // won't call `seal_call_data_copy` 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 + // part of `seal_call_data_copy`. 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> { @@ -559,6 +565,38 @@ mod benchmarks { assert_eq!(result.unwrap(), 1); } + #[benchmark(pov_mode = Measured)] + fn seal_to_account_id() { + // use a mapped address for the benchmark, to ensure that we bench the worst + // case (and not the fallback case). + let address = { + let caller = account("seal_to_account_id", 0, 0); + T::Currency::set_balance(&caller, caller_funding::<T>()); + T::AddressMapper::map(&caller).unwrap(); + T::AddressMapper::to_address(&caller) + }; + + let len = <T::AccountId as MaxEncodedLen>::max_encoded_len(); + build_runtime!(runtime, memory: [vec![0u8; len], address.0, ]); + + let result; + #[block] + { + result = runtime.bench_to_account_id(memory.as_mut_slice(), len as u32, 0); + } + + assert_ok!(result); + assert_ne!( + memory.as_slice()[20..32], + [0xEE; 12], + "fallback suffix found where none should be" + ); + assert_eq!( + T::AccountId::decode(&mut memory.as_slice()), + Ok(runtime.ext().to_account_id(&address)) + ); + } + #[benchmark(pov_mode = Measured)] fn seal_code_hash() { let contract = Contract::<T>::with_index(1, WasmModule::dummy(), vec![]).unwrap(); @@ -598,19 +636,15 @@ mod benchmarks { #[benchmark(pov_mode = Measured)] fn seal_code_size() { let contract = Contract::<T>::with_index(1, WasmModule::dummy(), vec![]).unwrap(); - build_runtime!(runtime, memory: [contract.address.encode(), vec![0u8; 32], ]); + build_runtime!(runtime, memory: [contract.address.encode(),]); let result; #[block] { - result = runtime.bench_code_size(memory.as_mut_slice(), 0, 20); + result = runtime.bench_code_size(memory.as_mut_slice(), 0); } - assert_ok!(result); - assert_eq!( - U256::from_little_endian(&memory[20..]), - U256::from(WasmModule::dummy().code.len()) - ); + assert_eq!(result.unwrap(), WasmModule::dummy().code.len() as u64); } #[benchmark(pov_mode = Measured)] @@ -673,6 +707,18 @@ mod benchmarks { ); } + #[benchmark(pov_mode = Measured)] + fn seal_ref_time_left() { + build_runtime!(runtime, memory: [vec![], ]); + + let result; + #[block] + { + result = runtime.bench_ref_time_left(memory.as_mut_slice()); + } + assert_eq!(result.unwrap(), runtime.ext().gas_meter().gas_left().ref_time()); + } + #[benchmark(pov_mode = Measured)] fn seal_balance() { build_runtime!(runtime, memory: [[0u8;32], ]); @@ -735,7 +781,7 @@ mod benchmarks { let mut setup = CallSetup::<T>::default(); let input = setup.data(); let (mut ext, _) = setup.ext(); - ext.override_export(crate::debug::ExportedFunction::Constructor); + ext.override_export(crate::exec::ExportedFunction::Constructor); let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, input); @@ -773,6 +819,70 @@ mod benchmarks { assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().minimum_balance()); } + #[benchmark(pov_mode = Measured)] + fn seal_return_data_size() { + let mut setup = CallSetup::<T>::default(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::new(&mut ext, vec![]); + let mut memory = memory!(vec![],); + *runtime.ext().last_frame_output_mut() = + ExecReturnValue { data: vec![42; 256], ..Default::default() }; + let result; + #[block] + { + result = runtime.bench_return_data_size(memory.as_mut_slice()); + } + assert_eq!(result.unwrap(), 256); + } + + #[benchmark(pov_mode = Measured)] + fn seal_call_data_size() { + let mut setup = CallSetup::<T>::default(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::new(&mut ext, vec![42u8; 128 as usize]); + let mut memory = memory!(vec![0u8; 4],); + let result; + #[block] + { + result = runtime.bench_call_data_size(memory.as_mut_slice()); + } + assert_eq!(result.unwrap(), 128); + } + + #[benchmark(pov_mode = Measured)] + fn seal_gas_limit() { + build_runtime!(runtime, memory: []); + let result; + #[block] + { + result = runtime.bench_gas_limit(&mut memory); + } + assert_eq!(result.unwrap(), T::BlockWeights::get().max_block.ref_time()); + } + + #[benchmark(pov_mode = Measured)] + fn seal_gas_price() { + build_runtime!(runtime, memory: []); + let result; + #[block] + { + result = runtime.bench_gas_price(memory.as_mut_slice()); + } + assert_eq!(result.unwrap(), u64::from(GAS_PRICE)); + } + + #[benchmark(pov_mode = Measured)] + fn seal_base_fee() { + build_runtime!(runtime, memory: [[1u8;32], ]); + let result; + #[block] + { + result = runtime.bench_base_fee(memory.as_mut_slice(), 0); + } + assert_ok!(result); + assert_eq!(U256::from_little_endian(&memory[..]), U256::zero()); + } + #[benchmark(pov_mode = Measured)] fn seal_block_number() { build_runtime!(runtime, memory: [[0u8;32], ]); @@ -785,6 +895,59 @@ mod benchmarks { assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().block_number()); } + #[benchmark(pov_mode = Measured)] + fn seal_block_author() { + build_runtime!(runtime, memory: [[123u8; 20], ]); + + let mut digest = Digest::default(); + + // The pre-runtime digest log is unbounded; usually around 3 items but it can vary. + // To get safe benchmark results despite that, populate it with a bunch of random logs to + // ensure iteration over many items (we just overestimate the cost of the API). + for i in 0..16 { + digest.push(DigestItem::PreRuntime([i, i, i, i], vec![i; 128])); + digest.push(DigestItem::Consensus([i, i, i, i], vec![i; 128])); + digest.push(DigestItem::Seal([i, i, i, i], vec![i; 128])); + digest.push(DigestItem::Other(vec![i; 128])); + } + + // The content of the pre-runtime digest log depends on the configured consensus. + // However, mismatching logs are simply ignored. Thus we construct fixtures which will + // let the API to return a value in both BABE and AURA consensus. + + // Construct a `Digest` log fixture returning some value in BABE + let primary_pre_digest = vec![0; <PrimaryPreDigest as MaxEncodedLen>::max_encoded_len()]; + let pre_digest = + PreDigest::Primary(PrimaryPreDigest::decode(&mut &primary_pre_digest[..]).unwrap()); + digest.push(DigestItem::PreRuntime(BABE_ENGINE_ID, pre_digest.encode())); + digest.push(DigestItem::Seal(BABE_ENGINE_ID, pre_digest.encode())); + + // Construct a `Digest` log fixture returning some value in AURA + let slot = Slot::default(); + digest.push(DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())); + digest.push(DigestItem::Seal(AURA_ENGINE_ID, slot.encode())); + + frame_system::Pallet::<T>::initialize( + &BlockNumberFor::<T>::from(1u32), + &Default::default(), + &digest, + ); + + let result; + #[block] + { + result = runtime.bench_block_author(memory.as_mut_slice(), 0); + } + assert_ok!(result); + + let block_author = runtime + .ext() + .block_author() + .map(|account| T::AddressMapper::to_address(&account)) + .unwrap_or(H160::zero()); + assert_eq!(&memory[..], block_author.as_bytes()); + } + #[benchmark(pov_mode = Measured)] fn seal_block_hash() { let mut memory = vec![0u8; 64]; @@ -841,20 +1004,58 @@ mod benchmarks { } #[benchmark(pov_mode = Measured)] - fn seal_input(n: Linear<0, { limits::code::BLOB_BYTES - 4 }>) { + fn seal_copy_to_contract(n: Linear<0, { limits::code::BLOB_BYTES - 4 }>) { let mut setup = CallSetup::<T>::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 mut runtime = crate::wasm::Runtime::new(&mut ext, vec![]); + let mut memory = memory!(n.encode(), vec![0u8; n as usize],); let result; #[block] { - result = runtime.bench_input(memory.as_mut_slice(), 4, 0); + result = runtime.write_sandbox_output( + memory.as_mut_slice(), + 4, + 0, + &vec![42u8; n as usize], + false, + |_| None, + ); } assert_ok!(result); + assert_eq!(&memory[..4], &n.encode()); assert_eq!(&memory[4..], &vec![42u8; n as usize]); } + #[benchmark(pov_mode = Measured)] + fn seal_call_data_load() { + let mut setup = CallSetup::<T>::default(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::new(&mut ext, vec![42u8; 32]); + let mut memory = memory!(vec![0u8; 32],); + let result; + #[block] + { + result = runtime.bench_call_data_load(memory.as_mut_slice(), 0, 0); + } + assert_ok!(result); + assert_eq!(&memory[..], &vec![42u8; 32]); + } + + #[benchmark(pov_mode = Measured)] + fn seal_call_data_copy(n: Linear<0, { limits::code::BLOB_BYTES }>) { + let mut setup = CallSetup::<T>::default(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::new(&mut ext, vec![42u8; n as usize]); + let mut memory = memory!(vec![0u8; n as usize],); + let result; + #[block] + { + result = runtime.bench_call_data_copy(memory.as_mut_slice(), 0, n, 0); + } + assert_ok!(result); + assert_eq!(&memory[..], &vec![42u8; n as usize]); + } + #[benchmark(pov_mode = Measured)] fn seal_return(n: Linear<0, { limits::code::BLOB_BYTES - 4 }>) { build_runtime!(runtime, memory: [n.to_le_bytes(), vec![42u8; n as usize], ]); @@ -872,24 +1073,11 @@ mod benchmarks { } #[benchmark(pov_mode = Measured)] - fn seal_terminate( - n: Linear<0, { limits::DELEGATE_DEPENDENCIES }>, - ) -> Result<(), BenchmarkError> { + fn seal_terminate() -> Result<(), BenchmarkError> { let beneficiary = account::<T::AccountId>("beneficiary", 0, 0); - let caller = whitelisted_caller(); - T::Currency::set_balance(&caller, caller_funding::<T>()); - let origin = RawOrigin::Signed(caller); - let storage_deposit = default_deposit_limit::<T>(); build_runtime!(runtime, memory: [beneficiary.encode(),]); - (0..n).for_each(|i| { - let new_code = WasmModule::dummy_unique(65 + i); - Contracts::<T>::bare_upload_code(origin.clone().into(), new_code.code, storage_deposit) - .unwrap(); - runtime.ext().lock_delegate_dependency(new_code.hash).unwrap(); - }); - let result; #[block] { @@ -938,32 +1126,6 @@ mod benchmarks { ); } - // 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::code::BLOB_BYTES).min(limits::DEBUG_BUFFER_BYTES) }>, - ) { - let mut setup = CallSetup::<T>::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::<Vec<_>>(); - - 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; @@ -1537,16 +1699,12 @@ mod benchmarks { { 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 + pack_hi_lo(CallFlags::CLONE_INPUT.bits(), 0), // flags + callee + u64::MAX, // ref_time_limit + u64::MAX, // proof_size_limit + pack_hi_lo(callee_len, callee_len + deposit_len), // deposit_ptr + value_pr + pack_hi_lo(0, 0), // input len + data ptr + pack_hi_lo(0, SENTINEL), // output len + data ptr ); } @@ -1577,15 +1735,12 @@ mod benchmarks { { result = runtime.bench_delegate_call( memory.as_mut_slice(), - 0, // flags - 0, // address_ptr - 0, // ref_time_limit - 0, // proof_size_limit - address_len, // deposit_ptr - 0, // input_data_ptr - 0, // input_data_len - SENTINEL, // output_ptr - 0, + pack_hi_lo(0, 0), // flags + address ptr + u64::MAX, // ref_time_limit + u64::MAX, // proof_size_limit + address_len, // deposit_ptr + pack_hi_lo(0, 0), // input len + data ptr + pack_hi_lo(0, SENTINEL), // output len + ptr ); } @@ -1600,13 +1755,12 @@ mod benchmarks { let code = WasmModule::dummy(); let hash = Contract::<T>::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<T> = 1_000_000u32.into(); let value_bytes = Into::<U256>::into(value).encode(); let value_len = value_bytes.len() as u32; - let deposit: BalanceOf<T> = 0u32.into(); + let deposit: BalanceOf<T> = BalanceOf::<T>::max_value(); let deposit_bytes = Into::<U256>::into(deposit).encode(); let deposit_len = deposit_bytes.len() as u32; @@ -1619,11 +1773,12 @@ mod benchmarks { let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); let input = vec![42u8; i as _]; + let input_len = hash_bytes.len() as u32 + input.len() as u32; let salt = [42u8; 32]; let deployer = T::AddressMapper::to_address(&account_id); let addr = crate::address::create2(&deployer, &code.code, &input, &salt); let account_id = T::AddressMapper::to_fallback_account_id(&addr); - let mut memory = memory!(hash_bytes, deposit_bytes, value_bytes, input, salt,); + let mut memory = memory!(hash_bytes, input, deposit_bytes, value_bytes, salt,); let mut offset = { let mut current = 0u32; @@ -1640,17 +1795,12 @@ mod benchmarks { { 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 - SENTINEL, // output_ptr - 0, // output_len_ptr - offset(i), // salt_ptr + u64::MAX, // ref_time_limit + u64::MAX, // proof_size_limit + pack_hi_lo(offset(input_len), offset(deposit_len)), // deopsit_ptr + value_ptr + pack_hi_lo(input_len, 0), // input_data_len + input_data + pack_hi_lo(0, SENTINEL), // output_len_ptr + output_ptr + pack_hi_lo(SENTINEL, offset(value_len)), // address_ptr + salt_ptr ); } @@ -1658,7 +1808,9 @@ mod benchmarks { assert!(ContractInfoOf::<T>::get(&addr).is_some()); assert_eq!( T::Currency::balance(&account_id), - Pallet::<T>::min_balance() + Pallet::<T>::convert_evm_to_native(value.into()).unwrap() + Pallet::<T>::min_balance() + + Pallet::<T>::convert_evm_to_native(value.into(), ConversionPrecision::Exact) + .unwrap() ); Ok(()) } @@ -1817,43 +1969,6 @@ mod benchmarks { Ok(()) } - #[benchmark(pov_mode = Measured)] - fn lock_delegate_dependency() -> Result<(), BenchmarkError> { - let code_hash = Contract::<T>::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::<T>::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>) { diff --git a/substrate/frame/revive/src/chain_extension.rs b/substrate/frame/revive/src/chain_extension.rs index ccea1294505418f4930524c22de216071361baed..5b3e886a56281f0ed5cd10878f4fe4a514a641db 100644 --- a/substrate/frame/revive/src/chain_extension.rs +++ b/substrate/frame/revive/src/chain_extension.rs @@ -75,7 +75,7 @@ use crate::{ Error, }; use alloc::vec::Vec; -use codec::{Decode, MaxEncodedLen}; +use codec::Decode; use frame_support::weights::Weight; use sp_runtime::DispatchError; @@ -304,16 +304,6 @@ impl<'a, 'b, E: Ext, M: ?Sized + Memory<E::T>> Environment<'a, 'b, E, M> { 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<T: Decode + MaxEncodedLen>(&mut self) -> Result<T> { - 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. diff --git a/substrate/frame/revive/src/debug.rs b/substrate/frame/revive/src/debug.rs deleted file mode 100644 index d1fc0823e03dff6719018ee60f0c0668aca37d7f..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/src/debug.rs +++ /dev/null @@ -1,109 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pub use crate::{ - exec::{ExecResult, ExportedFunction}, - primitives::ExecReturnValue, -}; -use crate::{Config, LOG_TARGET}; -use sp_core::H160; - -/// Umbrella trait for all interfaces that serves for debugging. -pub trait Debugger<T: Config>: Tracing<T> + CallInterceptor<T> {} - -impl<T: Config, V> Debugger<T> for V where V: Tracing<T> + CallInterceptor<T> {} - -/// Defines methods to capture contract calls, enabling external observers to -/// measure, trace, and react to contract interactions. -pub trait Tracing<T: Config> { - /// 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: &H160, - 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<T: Config> Tracing<T> for () { - type CallSpan = (); - - fn new_call_span(contract_address: &H160, entry_point: ExportedFunction, input_data: &[u8]) { - log::trace!(target: LOG_TARGET, "call {entry_point:?} address: {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<T: Config> { - /// 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: &H160, - entry_point: ExportedFunction, - input_data: &[u8], - ) -> Option<ExecResult>; -} - -impl<T: Config> CallInterceptor<T> for () { - fn intercept_call( - _contract_address: &H160, - _entry_point: ExportedFunction, - _input_data: &[u8], - ) -> Option<ExecResult> { - None - } -} diff --git a/substrate/frame/revive/src/evm.rs b/substrate/frame/revive/src/evm.rs index c3495fc0559d220a7ccaaeaac488a12963b98a79..33660a36aa6ea57f713ccf57a62f8ba013ac4ef9 100644 --- a/substrate/frame/revive/src/evm.rs +++ b/substrate/frame/revive/src/evm.rs @@ -19,4 +19,51 @@ mod api; pub use api::*; +mod tracing; +pub use tracing::*; +mod gas_encoder; +pub use gas_encoder::*; pub mod runtime; + +use crate::alloc::{format, string::*}; + +/// Extract the revert message from a revert("msg") solidity statement. +pub fn extract_revert_message(exec_data: &[u8]) -> Option<String> { + let error_selector = exec_data.get(0..4)?; + + match error_selector { + // assert(false) + [0x4E, 0x48, 0x7B, 0x71] => { + let panic_code: u32 = U256::from_big_endian(exec_data.get(4..36)?).try_into().ok()?; + + // See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require + let msg = match panic_code { + 0x00 => "generic panic", + 0x01 => "assert(false)", + 0x11 => "arithmetic underflow or overflow", + 0x12 => "division or modulo by zero", + 0x21 => "enum overflow", + 0x22 => "invalid encoded storage byte array accessed", + 0x31 => "out-of-bounds array access; popping on an empty array", + 0x32 => "out-of-bounds access of an array or bytesN", + 0x41 => "out of memory", + 0x51 => "uninitialized function", + code => return Some(format!("execution reverted: unknown panic code: {code:#x}")), + }; + + Some(format!("execution reverted: {msg}")) + }, + // revert(string) + [0x08, 0xC3, 0x79, 0xA0] => { + let decoded = ethabi::decode(&[ethabi::ParamKind::String], &exec_data[4..]).ok()?; + if let Some(ethabi::Token::String(msg)) = decoded.first() { + return Some(format!("execution reverted: {}", String::from_utf8_lossy(msg))) + } + Some("execution reverted".to_string()) + }, + _ => { + log::debug!(target: crate::LOG_TARGET, "Unknown revert function selector: {error_selector:?}"); + Some("execution reverted".to_string()) + }, + } +} diff --git a/substrate/frame/revive/src/evm/api.rs b/substrate/frame/revive/src/evm/api.rs index fe18c8735bed4d8dc435f8517d86f67aa9c70393..7a34fdc83f9a5140cc459f4be0da188bd81b5b7b 100644 --- a/substrate/frame/revive/src/evm/api.rs +++ b/substrate/frame/revive/src/evm/api.rs @@ -16,6 +16,8 @@ // limitations under the License. //! JSON-RPC methods and types, for Ethereum. +mod hex_serde; + mod byte; pub use byte::*; @@ -25,6 +27,9 @@ pub use rlp; mod type_id; pub use type_id::*; +mod debug_rpc_types; +pub use debug_rpc_types::*; + mod rpc_types; mod rpc_types_gen; pub use rpc_types_gen::*; diff --git a/substrate/frame/revive/src/evm/api/byte.rs b/substrate/frame/revive/src/evm/api/byte.rs index df4ed1740ecdb75a8a027fc17fdc6b6f222b0ae9..f11966d0072cf64d674bd2925862720192c43924 100644 --- a/substrate/frame/revive/src/evm/api/byte.rs +++ b/substrate/frame/revive/src/evm/api/byte.rs @@ -15,79 +15,16 @@ // See the License for the specific language governing permissions and // limitations under the License. //! Define Byte wrapper types for encoding and decoding hex strings +use super::hex_serde::HexCodec; use alloc::{vec, vec::Vec}; use codec::{Decode, Encode}; use core::{ fmt::{Debug, Display, Formatter, Result as FmtResult}, str::FromStr, }; -use hex_serde::HexCodec; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; -mod hex_serde { - #[cfg(not(feature = "std"))] - use alloc::{format, string::String, vec::Vec}; - use serde::{Deserialize, Deserializer, Serializer}; - - pub trait HexCodec: Sized { - type Error; - fn to_hex(&self) -> String; - fn from_hex(s: String) -> Result<Self, Self::Error>; - } - - impl HexCodec for u8 { - type Error = core::num::ParseIntError; - fn to_hex(&self) -> String { - format!("0x{:x}", self) - } - fn from_hex(s: String) -> Result<Self, Self::Error> { - u8::from_str_radix(s.trim_start_matches("0x"), 16) - } - } - - impl<const T: usize> HexCodec for [u8; T] { - type Error = hex::FromHexError; - fn to_hex(&self) -> String { - format!("0x{}", hex::encode(self)) - } - fn from_hex(s: String) -> Result<Self, Self::Error> { - let data = hex::decode(s.trim_start_matches("0x"))?; - data.try_into().map_err(|_| hex::FromHexError::InvalidStringLength) - } - } - - impl HexCodec for Vec<u8> { - type Error = hex::FromHexError; - fn to_hex(&self) -> String { - format!("0x{}", hex::encode(self)) - } - fn from_hex(s: String) -> Result<Self, Self::Error> { - hex::decode(s.trim_start_matches("0x")) - } - } - - pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - T: HexCodec, - { - let s = value.to_hex(); - serializer.serialize_str(&s) - } - - pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error> - where - D: Deserializer<'de>, - T: HexCodec, - <T as HexCodec>::Error: core::fmt::Debug, - { - let s = String::deserialize(deserializer)?; - let value = T::from_hex(s).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?; - Ok(value) - } -} - impl FromStr for Bytes { type Err = hex::FromHexError; fn from_str(s: &str) -> Result<Self, Self::Err> { @@ -100,7 +37,7 @@ macro_rules! impl_hex { ($type:ident, $inner:ty, $default:expr) => { #[derive(Encode, Decode, Eq, PartialEq, TypeInfo, Clone, Serialize, Deserialize)] #[doc = concat!("`", stringify!($inner), "`", " wrapper type for encoding and decoding hex strings")] - pub struct $type(#[serde(with = "hex_serde")] pub $inner); + pub struct $type(#[serde(with = "crate::evm::api::hex_serde")] pub $inner); impl Default for $type { fn default() -> Self { @@ -116,7 +53,10 @@ macro_rules! impl_hex { impl Debug for $type { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!(f, concat!(stringify!($type), "({})"), self.0.to_hex()) + let hex_str = self.0.to_hex(); + let truncated = &hex_str[..hex_str.len().min(100)]; + let ellipsis = if hex_str.len() > 100 { "..." } else { "" }; + write!(f, concat!(stringify!($type), "({}{})"), truncated,ellipsis) } } @@ -128,6 +68,13 @@ macro_rules! impl_hex { }; } +impl Bytes { + /// See `Vec::is_empty` + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + impl_hex!(Byte, u8, 0u8); impl_hex!(Bytes, Vec<u8>, vec![]); impl_hex!(Bytes8, [u8; 8], [0u8; 8]); diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs new file mode 100644 index 0000000000000000000000000000000000000000..0857a59fbf3b650707069e1a0a8387acad49fe99 --- /dev/null +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -0,0 +1,219 @@ +// 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::evm::{Bytes, CallTracer}; +use alloc::{fmt, string::String, vec::Vec}; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use serde::{ + de::{self, MapAccess, Visitor}, + Deserialize, Deserializer, Serialize, +}; +use sp_core::{H160, H256, U256}; + +/// Tracer configuration used to trace calls. +#[derive(TypeInfo, Debug, Clone, Encode, Decode, Serialize, PartialEq)] +#[serde(tag = "tracer", content = "tracerConfig")] +pub enum TracerConfig { + /// A tracer that captures call traces. + #[serde(rename = "callTracer")] + CallTracer { + /// Whether or not to capture logs. + #[serde(rename = "withLog")] + with_logs: bool, + }, +} + +impl TracerConfig { + /// Build the tracer associated to this config. + pub fn build<G>(self, gas_mapper: G) -> CallTracer<U256, G> { + match self { + Self::CallTracer { with_logs } => CallTracer::new(with_logs, gas_mapper), + } + } +} + +/// Custom deserializer to support the following JSON format: +/// +/// ```json +/// { "tracer": "callTracer", "tracerConfig": { "withLogs": false } } +/// ``` +/// +/// ```json +/// { "tracer": "callTracer" } +/// ``` +impl<'de> Deserialize<'de> for TracerConfig { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + struct TracerConfigVisitor; + + impl<'de> Visitor<'de> for TracerConfigVisitor { + type Value = TracerConfig; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map with tracer and optional tracerConfig") + } + + fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error> + where + M: MapAccess<'de>, + { + let mut tracer_type: Option<String> = None; + let mut with_logs = None; + + while let Some(key) = map.next_key::<String>()? { + match key.as_str() { + "tracer" => { + tracer_type = map.next_value()?; + }, + "tracerConfig" => { + #[derive(Deserialize)] + struct CallTracerConfig { + #[serde(rename = "withLogs")] + with_logs: Option<bool>, + } + let inner: CallTracerConfig = map.next_value()?; + with_logs = inner.with_logs; + }, + _ => {}, + } + } + + match tracer_type.as_deref() { + Some("callTracer") => + Ok(TracerConfig::CallTracer { with_logs: with_logs.unwrap_or(true) }), + _ => Err(de::Error::custom("Unsupported or missing tracer type")), + } + } + } + + deserializer.deserialize_map(TracerConfigVisitor) + } +} + +#[test] +fn test_tracer_config_serialization() { + let tracers = vec![ + (r#"{"tracer": "callTracer"}"#, TracerConfig::CallTracer { with_logs: true }), + ( + r#"{"tracer": "callTracer", "tracerConfig": { "withLogs": true }}"#, + TracerConfig::CallTracer { with_logs: true }, + ), + ( + r#"{"tracer": "callTracer", "tracerConfig": { "withLogs": false }}"#, + TracerConfig::CallTracer { with_logs: false }, + ), + ]; + + for (json_data, expected) in tracers { + let result: TracerConfig = + serde_json::from_str(json_data).expect("Deserialization should succeed"); + assert_eq!(result, expected); + } +} + +impl Default for TracerConfig { + fn default() -> Self { + TracerConfig::CallTracer { with_logs: false } + } +} + +/// The type of call that was executed. +#[derive( + Default, TypeInfo, Encode, Decode, Serialize, Deserialize, Eq, PartialEq, Clone, Debug, +)] +#[serde(rename_all = "UPPERCASE")] +pub enum CallType { + /// A regular call. + #[default] + Call, + /// A read-only call. + StaticCall, + /// A delegate call. + DelegateCall, +} + +/// A smart contract execution call trace. +#[derive( + TypeInfo, Default, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, +)] +pub struct CallTrace<Gas = U256> { + /// Address of the sender. + pub from: H160, + /// Address of the receiver. + pub to: H160, + /// Call input data. + pub input: Vec<u8>, + /// Amount of value transferred. + #[serde(skip_serializing_if = "U256::is_zero")] + pub value: U256, + /// Type of call. + #[serde(rename = "type")] + pub call_type: CallType, + /// Amount of gas provided for the call. + pub gas: Gas, + /// Amount of gas used. + #[serde(rename = "gasUsed")] + pub gas_used: Gas, + /// Return data. + #[serde(flatten, skip_serializing_if = "Bytes::is_empty")] + pub output: Bytes, + /// The error message if the call failed. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option<String>, + /// The revert reason, if the call reverted. + #[serde(rename = "revertReason")] + pub revert_reason: Option<String>, + /// List of sub-calls. + #[serde(skip_serializing_if = "Vec::is_empty")] + pub calls: Vec<CallTrace<Gas>>, + /// List of logs emitted during the call. + #[serde(skip_serializing_if = "Vec::is_empty")] + pub logs: Vec<CallLog>, +} + +/// A log emitted during a call. +#[derive( + Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq, +)] +pub struct CallLog { + /// The address of the contract that emitted the log. + pub address: H160, + /// The log's data. + #[serde(skip_serializing_if = "Bytes::is_empty")] + pub data: Bytes, + /// The topics used to index the log. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub topics: Vec<H256>, + /// Position of the log relative to subcalls within the same trace + /// See <https://github.com/ethereum/go-ethereum/pull/28389> for details + #[serde(with = "super::hex_serde")] + pub position: u32, +} + +/// A transaction trace +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct TransactionTrace { + /// The transaction hash. + #[serde(rename = "txHash")] + pub tx_hash: H256, + /// The trace of the transaction. + #[serde(rename = "result")] + pub trace: CallTrace, +} diff --git a/substrate/frame/revive/src/evm/api/hex_serde.rs b/substrate/frame/revive/src/evm/api/hex_serde.rs new file mode 100644 index 0000000000000000000000000000000000000000..ba07b36fa4be6e5cba82bba719c3893cd32aafc5 --- /dev/null +++ b/substrate/frame/revive/src/evm/api/hex_serde.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. + +use alloc::{format, string::String, vec::Vec}; +use serde::{Deserialize, Deserializer, Serializer}; + +pub trait HexCodec: Sized { + type Error; + fn to_hex(&self) -> String; + fn from_hex(s: String) -> Result<Self, Self::Error>; +} + +macro_rules! impl_hex_codec { + ($($t:ty),*) => { + $( + impl HexCodec for $t { + type Error = core::num::ParseIntError; + fn to_hex(&self) -> String { + format!("0x{:x}", self) + } + fn from_hex(s: String) -> Result<Self, Self::Error> { + <$t>::from_str_radix(s.trim_start_matches("0x"), 16) + } + } + )* + }; +} + +impl_hex_codec!(u8, u32); + +impl<const T: usize> HexCodec for [u8; T] { + type Error = hex::FromHexError; + fn to_hex(&self) -> String { + format!("0x{}", hex::encode(self)) + } + fn from_hex(s: String) -> Result<Self, Self::Error> { + let data = hex::decode(s.trim_start_matches("0x"))?; + data.try_into().map_err(|_| hex::FromHexError::InvalidStringLength) + } +} + +impl HexCodec for Vec<u8> { + type Error = hex::FromHexError; + fn to_hex(&self) -> String { + format!("0x{}", hex::encode(self)) + } + fn from_hex(s: String) -> Result<Self, Self::Error> { + hex::decode(s.trim_start_matches("0x")) + } +} + +pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error> +where + S: Serializer, + T: HexCodec, +{ + let s = value.to_hex(); + serializer.serialize_str(&s) +} + +pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error> +where + D: Deserializer<'de>, + T: HexCodec, + <T as HexCodec>::Error: core::fmt::Debug, +{ + let s = String::deserialize(deserializer)?; + let value = T::from_hex(s).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?; + Ok(value) +} diff --git a/substrate/frame/revive/src/evm/api/rlp_codec.rs b/substrate/frame/revive/src/evm/api/rlp_codec.rs index 3442ed73accab7be5caff8f884a336c4be03b252..9b61cd042ec5433655c1002bcab62b6d67bd2fd7 100644 --- a/substrate/frame/revive/src/evm/api/rlp_codec.rs +++ b/substrate/frame/revive/src/evm/api/rlp_codec.rs @@ -88,14 +88,14 @@ impl TransactionSigned { } } -impl TransactionLegacyUnsigned { - /// Get the rlp encoded bytes of a signed transaction with a dummy 65 bytes signature. +impl TransactionUnsigned { + /// Get a signed transaction payload with a dummy 65 bytes signature. pub fn dummy_signed_payload(&self) -> Vec<u8> { - let mut s = rlp::RlpStream::new(); - s.append(self); const DUMMY_SIGNATURE: [u8; 65] = [0u8; 65]; - s.append_raw(&DUMMY_SIGNATURE.as_ref(), 1); - s.out().to_vec() + self.unsigned_payload() + .into_iter() + .chain(DUMMY_SIGNATURE.iter().copied()) + .collect::<Vec<_>>() } } @@ -567,7 +567,7 @@ mod test { #[test] fn dummy_signed_payload_works() { - let tx = TransactionLegacyUnsigned { + let tx: TransactionUnsigned = TransactionLegacyUnsigned { chain_id: Some(596.into()), gas: U256::from(21000), nonce: U256::from(1), @@ -576,10 +576,10 @@ mod test { value: U256::from(123123), input: Bytes(vec![]), r#type: TypeLegacy, - }; + } + .into(); let dummy_signed_payload = tx.dummy_signed_payload(); - let tx: TransactionUnsigned = tx.into(); let payload = Account::default().sign_transaction(tx).signed_payload(); assert_eq!(dummy_signed_payload.len(), payload.len()); } diff --git a/substrate/frame/revive/src/evm/api/rpc_types.rs b/substrate/frame/revive/src/evm/api/rpc_types.rs index 1cf8d984b68b847d512fba8dd9483745ecb507e8..b4b2c6ffcf17eed8bfc57500147d4dda873376fe 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types.rs @@ -19,6 +19,27 @@ use super::*; use alloc::vec::Vec; use sp_core::{H160, U256}; +impl From<BlockNumberOrTag> for BlockNumberOrTagOrHash { + fn from(b: BlockNumberOrTag) -> Self { + match b { + BlockNumberOrTag::U256(n) => BlockNumberOrTagOrHash::U256(n), + BlockNumberOrTag::BlockTag(t) => BlockNumberOrTagOrHash::BlockTag(t), + } + } +} + +impl From<TransactionSigned> for TransactionUnsigned { + fn from(tx: TransactionSigned) -> Self { + use TransactionSigned::*; + match tx { + Transaction4844Signed(tx) => tx.transaction_4844_unsigned.into(), + Transaction1559Signed(tx) => tx.transaction_1559_unsigned.into(), + Transaction2930Signed(tx) => tx.transaction_2930_unsigned.into(), + TransactionLegacySigned(tx) => tx.transaction_legacy_unsigned.into(), + } + } +} + impl TransactionInfo { /// Create a new [`TransactionInfo`] from a receipt and a signed transaction. pub fn new(receipt: ReceiptInfo, transaction_signed: TransactionSigned) -> Self { @@ -143,76 +164,77 @@ fn logs_bloom_works() { impl GenericTransaction { /// Create a new [`GenericTransaction`] from a signed transaction. pub fn from_signed(tx: TransactionSigned, from: Option<H160>) -> Self { - use TransactionSigned::*; + Self::from_unsigned(tx.into(), from) + } + + /// Create a new [`GenericTransaction`] from a unsigned transaction. + pub fn from_unsigned(tx: TransactionUnsigned, from: Option<H160>) -> Self { + use TransactionUnsigned::*; match tx { - TransactionLegacySigned(tx) => { - let tx = tx.transaction_legacy_unsigned; - GenericTransaction { - from, - r#type: Some(tx.r#type.as_byte()), - chain_id: tx.chain_id, - input: Some(tx.input), - nonce: Some(tx.nonce), - value: Some(tx.value), - to: tx.to, - gas: Some(tx.gas), - gas_price: Some(tx.gas_price), - ..Default::default() - } + TransactionLegacyUnsigned(tx) => GenericTransaction { + from, + r#type: Some(tx.r#type.as_byte()), + chain_id: tx.chain_id, + input: Some(tx.input), + nonce: Some(tx.nonce), + value: Some(tx.value), + to: tx.to, + gas: Some(tx.gas), + gas_price: Some(tx.gas_price), + ..Default::default() }, - Transaction4844Signed(tx) => { - let tx = tx.transaction_4844_unsigned; - GenericTransaction { - from, - r#type: Some(tx.r#type.as_byte()), - chain_id: Some(tx.chain_id), - input: Some(tx.input), - nonce: Some(tx.nonce), - value: Some(tx.value), - to: Some(tx.to), - gas: Some(tx.gas), - gas_price: Some(tx.max_fee_per_blob_gas), - access_list: Some(tx.access_list), - blob_versioned_hashes: Some(tx.blob_versioned_hashes), - max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas), - max_fee_per_gas: Some(tx.max_fee_per_gas), - max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), - ..Default::default() - } + Transaction4844Unsigned(tx) => GenericTransaction { + from, + r#type: Some(tx.r#type.as_byte()), + chain_id: Some(tx.chain_id), + input: Some(tx.input), + nonce: Some(tx.nonce), + value: Some(tx.value), + to: Some(tx.to), + gas: Some(tx.gas), + gas_price: Some( + U256::from(crate::GAS_PRICE) + .saturating_add(tx.max_priority_fee_per_gas) + .max(tx.max_fee_per_blob_gas), + ), + access_list: Some(tx.access_list), + blob_versioned_hashes: tx.blob_versioned_hashes, + max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas), + max_fee_per_gas: Some(tx.max_fee_per_gas), + max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), + ..Default::default() }, - Transaction1559Signed(tx) => { - let tx = tx.transaction_1559_unsigned; - GenericTransaction { - from, - r#type: Some(tx.r#type.as_byte()), - chain_id: Some(tx.chain_id), - input: Some(tx.input), - nonce: Some(tx.nonce), - value: Some(tx.value), - to: tx.to, - gas: Some(tx.gas), - gas_price: Some(tx.gas_price), - access_list: Some(tx.access_list), - max_fee_per_gas: Some(tx.max_fee_per_gas), - max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), - ..Default::default() - } + Transaction1559Unsigned(tx) => GenericTransaction { + from, + r#type: Some(tx.r#type.as_byte()), + chain_id: Some(tx.chain_id), + input: Some(tx.input), + nonce: Some(tx.nonce), + value: Some(tx.value), + to: tx.to, + gas: Some(tx.gas), + gas_price: Some( + U256::from(crate::GAS_PRICE) + .saturating_add(tx.max_priority_fee_per_gas) + .max(tx.max_fee_per_gas), + ), + access_list: Some(tx.access_list), + max_fee_per_gas: Some(tx.max_fee_per_gas), + max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), + ..Default::default() }, - Transaction2930Signed(tx) => { - let tx = tx.transaction_2930_unsigned; - GenericTransaction { - from, - r#type: Some(tx.r#type.as_byte()), - chain_id: Some(tx.chain_id), - input: Some(tx.input), - nonce: Some(tx.nonce), - value: Some(tx.value), - to: tx.to, - gas: Some(tx.gas), - gas_price: Some(tx.gas_price), - access_list: Some(tx.access_list), - ..Default::default() - } + Transaction2930Unsigned(tx) => GenericTransaction { + from, + r#type: Some(tx.r#type.as_byte()), + chain_id: Some(tx.chain_id), + input: Some(tx.input), + nonce: Some(tx.nonce), + value: Some(tx.value), + to: tx.to, + gas: Some(tx.gas), + gas_price: Some(tx.gas_price), + access_list: Some(tx.access_list), + ..Default::default() }, } } @@ -269,7 +291,7 @@ impl GenericTransaction { max_fee_per_blob_gas: self.max_fee_per_blob_gas.unwrap_or_default(), max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(), access_list: self.access_list.unwrap_or_default(), - blob_versioned_hashes: self.blob_versioned_hashes.unwrap_or_default(), + blob_versioned_hashes: self.blob_versioned_hashes, } .into()), _ => Err(()), diff --git a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs index 5037ec05d881c94f33cc8e0b798b9c2ef7d4c1c9..5d31613ca314baa057fdc52e88fe66be16fc5c58 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs @@ -87,15 +87,15 @@ pub struct Block { /// Total difficulty #[serde(rename = "totalDifficulty", skip_serializing_if = "Option::is_none")] pub total_difficulty: Option<U256>, - pub transactions: H256OrTransactionInfo, + pub transactions: HashesOrTransactionInfos, /// Transactions root #[serde(rename = "transactionsRoot")] pub transactions_root: H256, /// Uncles pub uncles: Vec<H256>, /// Withdrawals - #[serde(skip_serializing_if = "Option::is_none")] - pub withdrawals: Option<Vec<Withdrawal>>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub withdrawals: Vec<Withdrawal>, /// Withdrawals root #[serde(rename = "withdrawalsRoot", skip_serializing_if = "Option::is_none")] pub withdrawals_root: Option<H256>, @@ -114,7 +114,7 @@ pub enum BlockNumberOrTag { } impl Default for BlockNumberOrTag { fn default() -> Self { - BlockNumberOrTag::U256(Default::default()) + BlockNumberOrTag::BlockTag(Default::default()) } } @@ -133,7 +133,7 @@ pub enum BlockNumberOrTagOrHash { } impl Default for BlockNumberOrTagOrHash { fn default() -> Self { - BlockNumberOrTagOrHash::U256(Default::default()) + BlockNumberOrTagOrHash::BlockTag(Default::default()) } } @@ -148,12 +148,12 @@ pub struct GenericTransaction { pub access_list: Option<AccessList>, /// blobVersionedHashes /// List of versioned blob hashes associated with the transaction's EIP-4844 data blobs. - #[serde(rename = "blobVersionedHashes", skip_serializing_if = "Option::is_none")] - pub blob_versioned_hashes: Option<Vec<H256>>, + #[serde(rename = "blobVersionedHashes", default, skip_serializing_if = "Vec::is_empty")] + pub blob_versioned_hashes: Vec<H256>, /// blobs /// Raw blob data. - #[serde(skip_serializing_if = "Option::is_none")] - pub blobs: Option<Vec<Bytes>>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub blobs: Vec<Bytes>, /// chainId /// Chain ID that this transaction is valid on. #[serde(rename = "chainId", skip_serializing_if = "Option::is_none")] @@ -319,7 +319,7 @@ pub enum TransactionUnsigned { } impl Default for TransactionUnsigned { fn default() -> Self { - TransactionUnsigned::Transaction4844Unsigned(Default::default()) + TransactionUnsigned::TransactionLegacyUnsigned(Default::default()) } } @@ -341,13 +341,13 @@ pub type AccessList = Vec<AccessListEntry>; )] pub enum BlockTag { #[serde(rename = "earliest")] - #[default] Earliest, #[serde(rename = "finalized")] Finalized, #[serde(rename = "safe")] Safe, #[serde(rename = "latest")] + #[default] Latest, #[serde(rename = "pending")] Pending, @@ -357,15 +357,15 @@ pub enum BlockTag { Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, From, TryInto, Eq, PartialEq, )] #[serde(untagged)] -pub enum H256OrTransactionInfo { +pub enum HashesOrTransactionInfos { /// Transaction hashes - H256s(Vec<H256>), + Hashes(Vec<H256>), /// Full transactions TransactionInfos(Vec<TransactionInfo>), } -impl Default for H256OrTransactionInfo { +impl Default for HashesOrTransactionInfos { fn default() -> Self { - H256OrTransactionInfo::H256s(Default::default()) + HashesOrTransactionInfos::Hashes(Default::default()) } } @@ -392,7 +392,7 @@ pub struct Log { #[serde(skip_serializing_if = "Option::is_none")] pub removed: Option<bool>, /// topics - #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub topics: Vec<H256>, /// transaction hash #[serde(rename = "transactionHash")] @@ -574,7 +574,7 @@ pub enum TransactionSigned { } impl Default for TransactionSigned { fn default() -> Self { - TransactionSigned::Transaction4844Signed(Default::default()) + TransactionSigned::TransactionLegacySigned(Default::default()) } } diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs new file mode 100644 index 0000000000000000000000000000000000000000..4ea822f30696238968571bad299becda65487999 --- /dev/null +++ b/substrate/frame/revive/src/evm/gas_encoder.rs @@ -0,0 +1,216 @@ +// 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. +//! Encodes/Decodes EVM gas values. + +use crate::Weight; +use core::ops::{Div, Rem}; +use frame_support::pallet_prelude::CheckedShl; +use sp_arithmetic::traits::{One, Zero}; +use sp_core::U256; + +// We use 3 digits to store each component. +const SCALE: u128 = 100; + +/// Rounds up the given value to the nearest multiple of the mask. +/// +/// # Panics +/// Panics if the `mask` is zero. +fn round_up<T>(value: T, mask: T) -> T +where + T: One + Zero + Copy + Rem<Output = T> + Div<Output = T>, + <T as Rem>::Output: PartialEq, +{ + let rest = if value % mask == T::zero() { T::zero() } else { T::one() }; + value / mask + rest +} + +/// Rounds up the log2 of the given value to the nearest integer. +fn log2_round_up<T>(val: T) -> u128 +where + T: Into<u128>, +{ + let val = val.into(); + val.checked_ilog2() + .map(|v| if 1u128 << v == val { v } else { v + 1 }) + .unwrap_or(0) as u128 +} + +mod private { + pub trait Sealed {} + impl Sealed for () {} +} + +/// Encodes/Decodes EVM gas values. +/// +/// # Note +/// +/// This is defined as a trait rather than standalone functions to allow +/// it to be added as an associated type to [`crate::Config`]. This way, +/// it can be invoked without requiring the implementation bounds to be +/// explicitly specified. +/// +/// This trait is sealed and cannot be implemented by downstream crates. +pub trait GasEncoder<Balance>: private::Sealed { + /// Encodes all components (deposit limit, weight reference time, and proof size) into a single + /// gas value. + fn encode(gas_limit: U256, weight: Weight, deposit: Balance) -> U256; + + /// Decodes the weight and deposit from the encoded gas value. + /// Returns `None` if the gas value is invalid + fn decode(gas: U256) -> Option<(Weight, Balance)>; + + /// Returns the encoded values of the specified weight and deposit. + fn as_encoded_values(weight: Weight, deposit: Balance) -> (Weight, Balance) { + let encoded = Self::encode(U256::zero(), weight, deposit); + Self::decode(encoded).expect("encoded values should be decodable; qed") + } +} + +impl<Balance> GasEncoder<Balance> for () +where + Balance: Zero + One + CheckedShl + Into<u128>, +{ + /// The encoding follows the pattern `g...grrppdd`, where: + /// - `dd`: log2 Deposit value, encoded in the lowest 2 digits. + /// - `pp`: log2 Proof size, encoded in the next 2 digits. + /// - `rr`: log2 Reference time, encoded in the next 2 digits. + /// - `g...g`: Gas limit, encoded in the highest digits. + /// + /// # Note + /// - The deposit value is maxed by 2^99 for u128 balance, and 2^63 for u64 balance. + fn encode(gas_limit: U256, weight: Weight, deposit: Balance) -> U256 { + let deposit: u128 = deposit.into(); + let deposit_component = log2_round_up(deposit); + + let proof_size = weight.proof_size(); + let proof_size_component = SCALE * log2_round_up(proof_size); + + let ref_time = weight.ref_time(); + let ref_time_component = SCALE.pow(2) * log2_round_up(ref_time); + + let components = U256::from(deposit_component + proof_size_component + ref_time_component); + + let raw_gas_mask = U256::from(SCALE).pow(3.into()); + let raw_gas_component = if gas_limit <= components { + U256::zero() + } else { + round_up(gas_limit, raw_gas_mask).saturating_mul(raw_gas_mask) + }; + + components.saturating_add(raw_gas_component) + } + + fn decode(gas: U256) -> Option<(Weight, Balance)> { + let deposit = gas % SCALE; + + // Casting with as_u32 is safe since all values are maxed by `SCALE`. + let deposit = deposit.as_u32(); + let proof_time = ((gas / SCALE) % SCALE).as_u32(); + let ref_time = ((gas / SCALE.pow(2)) % SCALE).as_u32(); + + let ref_weight = match ref_time { + 0 => 0, + 64 => u64::MAX, + _ => 1u64.checked_shl(ref_time)?, + }; + + let proof_weight = match proof_time { + 0 => 0, + 64 => u64::MAX, + _ => 1u64.checked_shl(proof_time)?, + }; + + let weight = Weight::from_parts(ref_weight, proof_weight); + + let deposit = match deposit { + 0 => Balance::zero(), + _ => Balance::one().checked_shl(deposit)?, + }; + + Some((weight, deposit)) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_gas_encoding_decoding_works() { + let raw_gas_limit = 111_111_999_999_999u128; + let weight = Weight::from_parts(222_999_999, 333_999_999); + let deposit = 444_999_999u64; + + let encoded_gas = <() as GasEncoder<u64>>::encode(raw_gas_limit.into(), weight, deposit); + assert_eq!(encoded_gas, U256::from(111_112_000_282_929u128)); + assert!(encoded_gas > raw_gas_limit.into()); + + let (decoded_weight, decoded_deposit) = + <() as GasEncoder<u64>>::decode(encoded_gas).unwrap(); + assert!(decoded_weight.all_gte(weight)); + assert!(weight.mul(2).all_gte(weight)); + + assert!(decoded_deposit >= deposit); + assert!(deposit * 2 >= decoded_deposit); + + assert_eq!( + (decoded_weight, decoded_deposit), + <() as GasEncoder<u64>>::as_encoded_values(weight, deposit) + ); + } + + #[test] + fn test_encoding_zero_values_work() { + let encoded_gas = <() as GasEncoder<u64>>::encode( + Default::default(), + Default::default(), + Default::default(), + ); + + assert_eq!(encoded_gas, U256::from(0)); + + let (decoded_weight, decoded_deposit) = + <() as GasEncoder<u64>>::decode(encoded_gas).unwrap(); + assert_eq!(Weight::default(), decoded_weight); + assert_eq!(0u64, decoded_deposit); + + let encoded_gas = + <() as GasEncoder<u64>>::encode(U256::from(1), Default::default(), Default::default()); + assert_eq!(encoded_gas, U256::from(1000000)); + } + + #[test] + fn test_encoding_max_values_work() { + let max_weight = Weight::from_parts(u64::MAX, u64::MAX); + let max_deposit = 1u64 << 63; + let encoded_gas = + <() as GasEncoder<u64>>::encode(Default::default(), max_weight, max_deposit); + + assert_eq!(encoded_gas, U256::from(646463)); + + let (decoded_weight, decoded_deposit) = + <() as GasEncoder<u64>>::decode(encoded_gas).unwrap(); + assert_eq!(max_weight, decoded_weight); + assert_eq!(max_deposit, decoded_deposit); + } + + #[test] + fn test_overflow() { + assert_eq!(None, <() as GasEncoder<u64>>::decode(65_00u128.into()), "Invalid proof size"); + assert_eq!(None, <() as GasEncoder<u64>>::decode(65_00_00u128.into()), "Invalid ref_time"); + } +} diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 40c210304ca2fe84641bb1349a4b4b45527e1b19..e593222239e5cbad32932198cc4c99f09ffc26f6 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -16,9 +16,14 @@ // limitations under the License. //! Runtime types for integrating `pallet-revive` with the EVM. use crate::{ - evm::api::{GenericTransaction, TransactionSigned}, - AccountIdOf, AddressMapper, BalanceOf, MomentOf, Weight, LOG_TARGET, + evm::{ + api::{GenericTransaction, TransactionSigned}, + GasEncoder, + }, + AccountIdOf, AddressMapper, BalanceOf, Config, ConversionPrecision, MomentOf, Pallet, + LOG_TARGET, }; +use alloc::vec::Vec; use codec::{Decode, Encode}; use frame_support::{ dispatch::{DispatchInfo, GetDispatchInfo}, @@ -26,7 +31,6 @@ use frame_support::{ }; use pallet_transaction_payment::OnChargeTransaction; use scale_info::{StaticTypeInfo, TypeInfo}; -use sp_arithmetic::Percent; use sp_core::{Get, H256, U256}; use sp_runtime::{ generic::{self, CheckedExtrinsic, ExtrinsicFormat}, @@ -35,11 +39,9 @@ use sp_runtime::{ TransactionExtension, }, transaction_validity::{InvalidTransaction, TransactionValidityError}, - OpaqueExtrinsic, RuntimeDebug, Saturating, + OpaqueExtrinsic, RuntimeDebug, }; -use alloc::vec::Vec; - type CallOf<T> = <T as frame_system::Config>::RuntimeCall; /// The EVM gas price. @@ -48,7 +50,12 @@ type CallOf<T> = <T as frame_system::Config>::RuntimeCall; /// We use a fixed value for the gas price. /// This let us calculate the gas estimate for a transaction with the formula: /// `estimate_gas = substrate_fee / gas_price`. -pub const GAS_PRICE: u32 = 1u32; +/// +/// The chosen constant value is: +/// - Not too high, ensuring the gas value is large enough (at least 7 digits) to encode the +/// ref_time, proof_size, and deposit into the less significant (6 lower) digits of the gas value. +/// - Not too low, enabling users to adjust the gas price to define a tip. +pub(crate) const GAS_PRICE: u64 = 1_000u64; /// Wraps [`generic::UncheckedExtrinsic`] to support checking unsigned /// [`crate::Call::eth_transact`] extrinsic. @@ -92,8 +99,12 @@ impl<Address: TypeInfo, Signature: TypeInfo, E: EthExtra> ExtrinsicLike impl<Address, Signature, E: EthExtra> ExtrinsicMetadata for UncheckedExtrinsic<Address, Signature, E> { - const VERSION: u8 = - generic::UncheckedExtrinsic::<Address, CallOf<E::Config>, Signature, E::Extension>::VERSION; + const VERSIONS: &'static [u8] = generic::UncheckedExtrinsic::< + Address, + CallOf<E::Config>, + Signature, + E::Extension, + >::VERSIONS; type TransactionExtensions = E::Extension; } @@ -136,15 +147,8 @@ where fn check(self, lookup: &Lookup) -> Result<Self::Checked, TransactionValidityError> { if !self.0.is_signed() { if let Ok(call) = self.0.function.clone().try_into() { - if let crate::Call::eth_transact { payload, gas_limit, storage_deposit_limit } = - call - { - let checked = E::try_into_checked_extrinsic( - payload, - gas_limit, - storage_deposit_limit, - self.encoded_size(), - )?; + if let crate::Call::eth_transact { payload } = call { + let checked = E::try_into_checked_extrinsic(payload, self.encoded_size())?; return Ok(checked) }; } @@ -247,7 +251,7 @@ where /// EthExtra convert an unsigned [`crate::Call::eth_transact`] into a [`CheckedExtrinsic`]. pub trait EthExtra { /// The Runtime configuration. - type Config: crate::Config + pallet_transaction_payment::Config; + type Config: Config + pallet_transaction_payment::Config; /// The Runtime's transaction extension. /// It should include at least: @@ -277,8 +281,6 @@ pub trait EthExtra { /// - `encoded_len`: The encoded length of the extrinsic. fn try_into_checked_extrinsic( payload: Vec<u8>, - gas_limit: Weight, - storage_deposit_limit: BalanceOf<Self::Config>, encoded_len: usize, ) -> Result< CheckedExtrinsic<AccountIdOf<Self::Config>, CallOf<Self::Config>, Self::Extension>, @@ -303,23 +305,37 @@ pub trait EthExtra { InvalidTransaction::BadProof })?; - let signer = - <Self::Config as crate::Config>::AddressMapper::to_fallback_account_id(&signer); + let signer = <Self::Config as Config>::AddressMapper::to_fallback_account_id(&signer); let GenericTransaction { nonce, chain_id, to, value, input, gas, gas_price, .. } = GenericTransaction::from_signed(tx, None); - if chain_id.unwrap_or_default() != <Self::Config as crate::Config>::ChainId::get().into() { + let Some(gas) = gas else { + log::debug!(target: LOG_TARGET, "No gas provided"); + return Err(InvalidTransaction::Call); + }; + + if chain_id.unwrap_or_default() != <Self::Config as Config>::ChainId::get().into() { log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}"); return Err(InvalidTransaction::Call); } - let value = crate::Pallet::<Self::Config>::convert_evm_to_native(value.unwrap_or_default()) - .map_err(|err| { - log::debug!(target: LOG_TARGET, "Failed to convert value to native: {err:?}"); + let value = crate::Pallet::<Self::Config>::convert_evm_to_native( + value.unwrap_or_default(), + ConversionPrecision::Exact, + ) + .map_err(|err| { + log::debug!(target: LOG_TARGET, "Failed to convert value to native: {err:?}"); + InvalidTransaction::Call + })?; + + let data = input.unwrap_or_default().0; + + let (gas_limit, storage_deposit_limit) = + <Self::Config as Config>::EthGasEncoder::decode(gas).ok_or_else(|| { + log::debug!(target: LOG_TARGET, "Failed to decode gas: {gas:?}"); InvalidTransaction::Call })?; - let data = input.unwrap_or_default().0; let call = if let Some(dest) = to { crate::Call::call::<Self::Config> { dest, @@ -350,25 +366,16 @@ pub trait EthExtra { } }; + let mut info = call.get_dispatch_info(); + let function: CallOf<Self::Config> = call.into(); let nonce = nonce.unwrap_or_default().try_into().map_err(|_| InvalidTransaction::Call)?; + let gas_price = gas_price.unwrap_or_default(); - // Fees calculated with the fixed `GAS_PRICE` - // When we dry-run the transaction, we set the gas to `Fee / GAS_PRICE` - let eth_fee_no_tip = U256::from(GAS_PRICE) - .saturating_mul(gas.unwrap_or_default()) - .try_into() - .map_err(|_| InvalidTransaction::Call)?; - - // Fees with the actual gas_price from the transaction. - let eth_fee: BalanceOf<Self::Config> = U256::from(gas_price.unwrap_or_default()) - .saturating_mul(gas.unwrap_or_default()) - .try_into() + let eth_fee = Pallet::<Self::Config>::evm_gas_to_fee(gas, gas_price) .map_err(|_| InvalidTransaction::Call)?; - let info = call.get_dispatch_info(); - let function: CallOf<Self::Config> = call.into(); - // Fees calculated from the extrinsic, without the tip. + info.extension_weight = Self::get_eth_extension(nonce, 0u32.into()).weight(&function); let actual_fee: BalanceOf<Self::Config> = pallet_transaction_payment::Pallet::<Self::Config>::compute_fee( encoded_len as u32, @@ -376,27 +383,21 @@ pub trait EthExtra { Default::default(), ) .into(); - log::trace!(target: LOG_TARGET, "try_into_checked_extrinsic: encoded_len: {encoded_len:?} actual_fee: {actual_fee:?} eth_fee: {eth_fee:?}"); + log::debug!(target: LOG_TARGET, "try_into_checked_extrinsic: gas_price: {gas_price:?}, encoded_len: {encoded_len:?} actual_fee: {actual_fee:?} eth_fee: {eth_fee:?}"); // The fees from the Ethereum transaction should be greater or equal to the actual fees paid // by the account. if eth_fee < actual_fee { - log::debug!(target: LOG_TARGET, "fees {eth_fee:?} too low for the extrinsic {actual_fee:?}"); + log::debug!(target: LOG_TARGET, "eth fees {eth_fee:?} too low, actual fees: {actual_fee:?}"); return Err(InvalidTransaction::Payment.into()) } - let min = actual_fee.min(eth_fee_no_tip); - let max = actual_fee.max(eth_fee_no_tip); - let diff = Percent::from_rational(max - min, min); - if diff > Percent::from_percent(10) { - log::trace!(target: LOG_TARGET, "Difference between the extrinsic fees {actual_fee:?} and the Ethereum gas fees {eth_fee_no_tip:?} should be no more than 10% got {diff:?}"); - return Err(InvalidTransaction::Call.into()) - } else { - log::trace!(target: LOG_TARGET, "Difference between the extrinsic fees {actual_fee:?} and the Ethereum gas fees {eth_fee_no_tip:?}: {diff:?}"); - } + let tip = + Pallet::<Self::Config>::evm_gas_to_fee(gas, gas_price.saturating_sub(GAS_PRICE.into())) + .unwrap_or_default() + .min(actual_fee); - let tip = eth_fee.saturating_sub(eth_fee_no_tip); - log::debug!(target: LOG_TARGET, "Created checked Ethereum transaction with nonce {nonce:?} and tip: {tip:?}"); + log::debug!(target: LOG_TARGET, "Created checked Ethereum transaction with nonce: {nonce:?} and tip: {tip:?}"); Ok(CheckedExtrinsic { format: ExtrinsicFormat::Signed(signer.into(), Self::get_eth_extension(nonce, tip)), function, @@ -411,6 +412,7 @@ mod test { evm::*, test_utils::*, tests::{ExtBuilder, RuntimeCall, RuntimeOrigin, Test}, + Weight, }; use frame_support::{error::LookupError, traits::fungible::Mutate}; use pallet_revive_fixtures::compile_module; @@ -451,236 +453,247 @@ mod test { /// A builder for creating an unchecked extrinsic, and test that the check function works. #[derive(Clone)] struct UncheckedExtrinsicBuilder { - tx: TransactionLegacyUnsigned, - gas_limit: Weight, - storage_deposit_limit: BalanceOf<Test>, + tx: GenericTransaction, + before_validate: Option<std::sync::Arc<dyn Fn() + Send + Sync>>, } impl UncheckedExtrinsicBuilder { /// Create a new builder with default values. fn new() -> Self { Self { - tx: TransactionLegacyUnsigned { - chain_id: Some(<Test as crate::Config>::ChainId::get().into()), - gas_price: U256::from(GAS_PRICE), + tx: GenericTransaction { + from: Some(Account::default().address()), + chain_id: Some(<Test as Config>::ChainId::get().into()), + gas_price: Some(U256::from(GAS_PRICE)), ..Default::default() }, - gas_limit: Weight::zero(), - storage_deposit_limit: 0, + before_validate: None, } } fn estimate_gas(&mut self) { let dry_run = crate::Pallet::<Test>::bare_eth_transact( - Account::default().substrate_account(), - self.tx.to, - self.tx.value.try_into().unwrap(), - self.tx.input.clone().0, + self.tx.clone(), Weight::MAX, - u64::MAX, - |call| { + |call, mut info| { let call = RuntimeCall::Contracts(call); + info.extension_weight = Extra::get_eth_extension(0, 0u32.into()).weight(&call); let uxt: Ex = sp_runtime::generic::UncheckedExtrinsic::new_bare(call).into(); - uxt.encoded_size() as u32 + pallet_transaction_payment::Pallet::<Test>::compute_fee( + uxt.encoded_size() as u32, + &info, + Default::default(), + ) }, - crate::DebugInfo::Skip, - crate::CollectEvents::Skip, ); - self.tx.gas = ((dry_run.fee + GAS_PRICE as u64) / (GAS_PRICE as u64)).into(); + + match dry_run { + Ok(dry_run) => { + log::debug!(target: LOG_TARGET, "Estimated gas: {:?}", dry_run.eth_gas); + self.tx.gas = Some(dry_run.eth_gas); + }, + Err(err) => { + log::debug!(target: LOG_TARGET, "Failed to estimate gas: {:?}", err); + }, + } } /// Create a new builder with a call to the given address. fn call_with(dest: H160) -> Self { let mut builder = Self::new(); builder.tx.to = Some(dest); - builder.estimate_gas(); builder } /// Create a new builder with an instantiate call. fn instantiate_with(code: Vec<u8>, data: Vec<u8>) -> Self { let mut builder = Self::new(); - builder.tx.input = Bytes(code.into_iter().chain(data.into_iter()).collect()); - builder.estimate_gas(); + builder.tx.input = Some(Bytes(code.into_iter().chain(data.into_iter()).collect())); builder } - /// Update the transaction with the given function. - fn update(mut self, f: impl FnOnce(&mut TransactionLegacyUnsigned) -> ()) -> Self { - f(&mut self.tx); + /// Set before_validate function. + fn before_validate(mut self, f: impl Fn() + Send + Sync + 'static) -> Self { + self.before_validate = Some(std::sync::Arc::new(f)); self } - /// Call `check` on the unchecked extrinsic, and `pre_dispatch` on the signed extension. - fn check(&self) -> Result<(RuntimeCall, SignedExtra), TransactionValidityError> { - let UncheckedExtrinsicBuilder { tx, gas_limit, storage_deposit_limit } = self.clone(); - - // Fund the account. - let account = Account::default(); - let _ = <Test as crate::Config>::Currency::set_balance( - &account.substrate_account(), - 100_000_000_000_000, - ); - - let payload = account.sign_transaction(tx.into()).signed_payload(); - let call = RuntimeCall::Contracts(crate::Call::eth_transact { - payload, - gas_limit, - storage_deposit_limit, - }); - - let encoded_len = call.encoded_size(); - let uxt: Ex = generic::UncheckedExtrinsic::new_bare(call).into(); - let result: CheckedExtrinsic<_, _, _> = uxt.check(&TestContext {})?; - let (account_id, extra): (AccountId32, SignedExtra) = match result.format { - ExtrinsicFormat::Signed(signer, extra) => (signer, extra), - _ => unreachable!(), - }; + fn check( + self, + ) -> Result<(RuntimeCall, SignedExtra, GenericTransaction), TransactionValidityError> { + self.mutate_estimate_and_check(Box::new(|_| ())) + } - extra.clone().validate_and_prepare( - RuntimeOrigin::signed(account_id), - &result.function, - &result.function.get_dispatch_info(), - encoded_len, - 0, - )?; + /// Call `check` on the unchecked extrinsic, and `pre_dispatch` on the signed extension. + fn mutate_estimate_and_check( + mut self, + f: Box<dyn FnOnce(&mut GenericTransaction) -> ()>, + ) -> Result<(RuntimeCall, SignedExtra, GenericTransaction), TransactionValidityError> { + ExtBuilder::default().build().execute_with(|| self.estimate_gas()); + f(&mut self.tx); + ExtBuilder::default().build().execute_with(|| { + let UncheckedExtrinsicBuilder { tx, before_validate, .. } = self.clone(); + + // Fund the account. + let account = Account::default(); + let _ = <Test as Config>::Currency::set_balance( + &account.substrate_account(), + 100_000_000_000_000, + ); + + let payload = account + .sign_transaction(tx.clone().try_into_unsigned().unwrap()) + .signed_payload(); + let call = RuntimeCall::Contracts(crate::Call::eth_transact { payload }); + + let encoded_len = call.encoded_size(); + let uxt: Ex = generic::UncheckedExtrinsic::new_bare(call).into(); + let result: CheckedExtrinsic<_, _, _> = uxt.check(&TestContext {})?; + let (account_id, extra): (AccountId32, SignedExtra) = match result.format { + ExtrinsicFormat::Signed(signer, extra) => (signer, extra), + _ => unreachable!(), + }; - Ok((result.function, extra)) + before_validate.map(|f| f()); + extra.clone().validate_and_prepare( + RuntimeOrigin::signed(account_id), + &result.function, + &result.function.get_dispatch_info(), + encoded_len, + 0, + )?; + + Ok((result.function, extra, tx)) + }) } } #[test] fn check_eth_transact_call_works() { - ExtBuilder::default().build().execute_with(|| { - let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); - assert_eq!( - builder.check().unwrap().0, - crate::Call::call::<Test> { - dest: builder.tx.to.unwrap(), - value: builder.tx.value.as_u64(), - gas_limit: builder.gas_limit, - storage_deposit_limit: builder.storage_deposit_limit, - data: builder.tx.input.0 - } - .into() - ); - }); + let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); + let (call, _, tx) = builder.check().unwrap(); + let (gas_limit, storage_deposit_limit) = + <<Test as Config>::EthGasEncoder as GasEncoder<_>>::decode(tx.gas.unwrap()).unwrap(); + + assert_eq!( + call, + crate::Call::call::<Test> { + dest: tx.to.unwrap(), + value: tx.value.unwrap_or_default().as_u64(), + data: tx.input.unwrap_or_default().0, + gas_limit, + storage_deposit_limit + } + .into() + ); } #[test] fn check_eth_transact_instantiate_works() { - ExtBuilder::default().build().execute_with(|| { - let (code, _) = compile_module("dummy").unwrap(); - let data = vec![]; - let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()); - - assert_eq!( - builder.check().unwrap().0, - crate::Call::instantiate_with_code::<Test> { - value: builder.tx.value.as_u64(), - gas_limit: builder.gas_limit, - storage_deposit_limit: builder.storage_deposit_limit, - code, - data, - salt: None - } - .into() - ); - }); + let (code, _) = compile_module("dummy").unwrap(); + let data = vec![]; + let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()); + let (call, _, tx) = builder.check().unwrap(); + let (gas_limit, storage_deposit_limit) = + <<Test as Config>::EthGasEncoder as GasEncoder<_>>::decode(tx.gas.unwrap()).unwrap(); + + assert_eq!( + call, + crate::Call::instantiate_with_code::<Test> { + value: tx.value.unwrap_or_default().as_u64(), + code, + data, + salt: None, + gas_limit, + storage_deposit_limit + } + .into() + ); } #[test] fn check_eth_transact_nonce_works() { - ExtBuilder::default().build().execute_with(|| { - let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) - .update(|tx| tx.nonce = 1u32.into()); + let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); - assert_eq!( - builder.check(), - Err(TransactionValidityError::Invalid(InvalidTransaction::Future)) - ); + assert_eq!( + builder.mutate_estimate_and_check(Box::new(|tx| tx.nonce = Some(1u32.into()))), + Err(TransactionValidityError::Invalid(InvalidTransaction::Future)) + ); - <crate::System<Test>>::inc_account_nonce(Account::default().substrate_account()); + let builder = + UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])).before_validate(|| { + <crate::System<Test>>::inc_account_nonce(Account::default().substrate_account()); + }); - let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); - assert_eq!( - builder.check(), - Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)) - ); - }); + assert_eq!( + builder.check(), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)) + ); } #[test] fn check_eth_transact_chain_id_works() { - ExtBuilder::default().build().execute_with(|| { - let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) - .update(|tx| tx.chain_id = Some(42.into())); + let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); - assert_eq!( - builder.check(), - Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) - ); - }); + assert_eq!( + builder.mutate_estimate_and_check(Box::new(|tx| tx.chain_id = Some(42.into()))), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) + ); } #[test] fn check_instantiate_data() { - ExtBuilder::default().build().execute_with(|| { - let code = b"invalid code".to_vec(); - let data = vec![1]; - let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()); - - // Fail because the tx input fail to get the blob length - assert_eq!( - builder.clone().update(|tx| tx.input = Bytes(vec![1, 2, 3])).check(), - Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) - ); - }); + let code = b"invalid code".to_vec(); + let data = vec![1]; + let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()); + + // Fail because the tx input fail to get the blob length + assert_eq!( + builder.mutate_estimate_and_check(Box::new(|tx| tx.input = Some(Bytes(vec![1, 2, 3])))), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) + ); } #[test] fn check_transaction_fees() { - ExtBuilder::default().build().execute_with(|| { - let scenarios: [(_, Box<dyn FnOnce(&mut TransactionLegacyUnsigned)>, _); 5] = [ - ("Eth fees too low", Box::new(|tx| tx.gas_price /= 2), InvalidTransaction::Payment), - ("Gas fees too high", Box::new(|tx| tx.gas *= 2), InvalidTransaction::Call), - ("Gas fees too low", Box::new(|tx| tx.gas *= 2), InvalidTransaction::Call), - ( - "Diff > 10%", - Box::new(|tx| tx.gas = tx.gas * 111 / 100), - InvalidTransaction::Call, - ), - ( - "Diff < 10%", - Box::new(|tx| { - tx.gas_price *= 2; - tx.gas = tx.gas * 89 / 100 - }), - InvalidTransaction::Call, - ), - ]; - - for (msg, update_tx, err) in scenarios { - let builder = - UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])).update(update_tx); - - assert_eq!(builder.check(), Err(TransactionValidityError::Invalid(err)), "{}", msg); - } - }); + let scenarios: Vec<(_, Box<dyn FnOnce(&mut GenericTransaction)>, _)> = vec![ + ( + "Eth fees too low", + Box::new(|tx| { + tx.gas_price = Some(tx.gas_price.unwrap() / 2); + }), + InvalidTransaction::Payment, + ), + ( + "Gas fees too low", + Box::new(|tx| { + tx.gas = Some(tx.gas.unwrap() / 2); + }), + InvalidTransaction::Payment, + ), + ]; + + for (msg, update_tx, err) in scenarios { + let res = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) + .mutate_estimate_and_check(update_tx); + + assert_eq!(res, Err(TransactionValidityError::Invalid(err)), "{}", msg); + } } #[test] fn check_transaction_tip() { - ExtBuilder::default().build().execute_with(|| { - let (code, _) = compile_module("dummy").unwrap(); - let data = vec![]; - let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()) - .update(|tx| tx.gas_price = tx.gas_price * 103 / 100); - - let tx = &builder.tx; - let expected_tip = tx.gas_price * tx.gas - U256::from(GAS_PRICE) * tx.gas; - let (_, extra) = builder.check().unwrap(); - assert_eq!(U256::from(extra.1.tip()), expected_tip); - }); + let (code, _) = compile_module("dummy").unwrap(); + let data = vec![]; + let (_, extra, tx) = + UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()) + .mutate_estimate_and_check(Box::new(|tx| { + tx.gas_price = Some(tx.gas_price.unwrap() * 103 / 100); + log::debug!(target: LOG_TARGET, "Gas price: {:?}", tx.gas_price); + })) + .unwrap(); + let diff = tx.gas_price.unwrap() - U256::from(GAS_PRICE); + let expected_tip = crate::Pallet::<Test>::evm_gas_to_fee(tx.gas.unwrap(), diff).unwrap(); + assert_eq!(extra.1.tip(), expected_tip); } } diff --git a/substrate/frame/revive/src/evm/tracing.rs b/substrate/frame/revive/src/evm/tracing.rs new file mode 100644 index 0000000000000000000000000000000000000000..7466ec1de487738bcb662e92b9127852098e304d --- /dev/null +++ b/substrate/frame/revive/src/evm/tracing.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. +use crate::{ + evm::{extract_revert_message, CallLog, CallTrace, CallType}, + primitives::ExecReturnValue, + tracing::Tracer, + DispatchError, Weight, +}; +use alloc::{format, string::ToString, vec::Vec}; +use sp_core::{H160, H256, U256}; + +/// A Tracer that reports logs and nested call traces transactions. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct CallTracer<Gas, GasMapper> { + /// Map Weight to Gas equivalent. + gas_mapper: GasMapper, + /// Store all in-progress CallTrace instances. + traces: Vec<CallTrace<Gas>>, + /// Stack of indices to the current active traces. + current_stack: Vec<usize>, + /// whether or not to capture logs. + with_log: bool, +} + +impl<Gas, GasMapper> CallTracer<Gas, GasMapper> { + /// Create a new [`CallTracer`] instance. + pub fn new(with_log: bool, gas_mapper: GasMapper) -> Self { + Self { gas_mapper, traces: Vec::new(), current_stack: Vec::new(), with_log } + } + + /// Collect the traces and return them. + pub fn collect_traces(&mut self) -> Vec<CallTrace<Gas>> { + core::mem::take(&mut self.traces) + } +} + +impl<Gas: Default, GasMapper: Fn(Weight) -> Gas> Tracer for CallTracer<Gas, GasMapper> { + fn enter_child_span( + &mut self, + from: H160, + to: H160, + is_delegate_call: bool, + is_read_only: bool, + value: U256, + input: &[u8], + gas_left: Weight, + ) { + let call_type = if is_read_only { + CallType::StaticCall + } else if is_delegate_call { + CallType::DelegateCall + } else { + CallType::Call + }; + + self.traces.push(CallTrace { + from, + to, + value, + call_type, + input: input.to_vec(), + gas: (self.gas_mapper)(gas_left), + ..Default::default() + }); + + // Push the index onto the stack of the current active trace + self.current_stack.push(self.traces.len() - 1); + } + + fn log_event(&mut self, address: H160, topics: &[H256], data: &[u8]) { + if !self.with_log { + return; + } + + let current_index = self.current_stack.last().unwrap(); + let position = self.traces[*current_index].calls.len() as u32; + let log = + CallLog { address, topics: topics.to_vec(), data: data.to_vec().into(), position }; + + let current_index = *self.current_stack.last().unwrap(); + self.traces[current_index].logs.push(log); + } + + fn exit_child_span(&mut self, output: &ExecReturnValue, gas_used: Weight) { + // Set the output of the current trace + let current_index = self.current_stack.pop().unwrap(); + let trace = &mut self.traces[current_index]; + trace.output = output.data.clone().into(); + trace.gas_used = (self.gas_mapper)(gas_used); + + if output.did_revert() { + trace.revert_reason = extract_revert_message(&output.data); + trace.error = Some("execution reverted".to_string()); + } + + // Move the current trace into its parent + if let Some(parent_index) = self.current_stack.last() { + let child_trace = self.traces.remove(current_index); + self.traces[*parent_index].calls.push(child_trace); + } + } + fn exit_child_span_with_error(&mut self, error: DispatchError, gas_used: Weight) { + // Set the output of the current trace + let current_index = self.current_stack.pop().unwrap(); + let trace = &mut self.traces[current_index]; + trace.gas_used = (self.gas_mapper)(gas_used); + + trace.error = match error { + DispatchError::Module(sp_runtime::ModuleError { message, .. }) => + Some(message.unwrap_or_default().to_string()), + _ => Some(format!("{:?}", error)), + }; + + // Move the current trace into its parent + if let Some(parent_index) = self.current_stack.last() { + let child_trace = self.traces.remove(current_index); + self.traces[*parent_index].calls.push(child_trace); + } + } +} diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 49c08166483e50508b8be6b385959edf663b958a..dc91c6f301009da44c5c07a99a1d0c4c25418df8 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -17,27 +17,26 @@ use crate::{ address::{self, AddressMapper}, - debug::{CallInterceptor, CallSpan, Tracing}, gas::GasMeter, limits, primitives::{ExecReturnValue, StorageDeposit}, runtime_decl_for_revive_api::{Decode, Encode, RuntimeDebugNoBound, TypeInfo}, storage::{self, meter::Diff, WriteOutcome}, + tracing::if_tracing, transient_storage::TransientStorage, - BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, DebugBuffer, Error, - Event, ImmutableData, ImmutableDataOf, Pallet as Contracts, LOG_TARGET, + BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, ConversionPrecision, + Error, Event, ImmutableData, ImmutableDataOf, Pallet as Contracts, }; 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, + Contains, FindAuthor, OriginTrait, Time, }, weights::Weight, Blake2_128Concat, BoundedVec, StorageHasher, @@ -49,11 +48,11 @@ use frame_system::{ use sp_core::{ ecdsa::Public as ECDSAPublic, sr25519::{Public as SR25519Public, Signature as SR25519Signature}, - ConstU32, Get, H160, H256, U256, + ConstU32, H160, H256, U256, }; use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256}; use sp_runtime::{ - traits::{BadOrigin, Convert, Dispatchable, Saturating, Zero}, + traits::{BadOrigin, Bounded, Convert, Dispatchable, Saturating, Zero}, DispatchError, SaturatedConversion, }; @@ -293,12 +292,15 @@ pub trait Ext: sealing::Sealed { /// Check if a contract lives at the specified `address`. fn is_contract(&self, address: &H160) -> bool; + /// Returns the account id for the given `address`. + fn to_account_id(&self, address: &H160) -> AccountIdOf<Self::T>; + /// Returns the code hash of the contract for the given `address`. /// If not a contract but account exists then `keccak_256([])` is returned, otherwise `zero`. fn code_hash(&self, address: &H160) -> H256; /// Returns the code size of the contract at the given `address` or zero. - fn code_size(&self, address: &H160) -> U256; + fn code_size(&self, address: &H160) -> u64; /// Returns the code hash of the contract being executed. fn own_code_hash(&mut self) -> &H256; @@ -320,12 +322,18 @@ pub trait Ext: sealing::Sealed { <Self::T as Config>::AddressMapper::to_address(self.account_id()) } + /// Get the length of the immutable data. + /// + /// This query is free as it does not need to load the immutable data from storage. + /// Useful when we need a constant time lookup of the length. + fn immutable_data_len(&mut self) -> u32; + /// Returns the immutable data of the current contract. /// /// Returns `Err(InvalidImmutableAccess)` if called from a constructor. fn get_immutable_data(&mut self) -> Result<ImmutableData, DispatchError>; - /// Set the the immutable data of the current contract. + /// Set the immutable data of the current contract. /// /// Returns `Err(InvalidImmutableAccess)` if not called from a constructor. /// @@ -363,6 +371,9 @@ pub trait Ext: sealing::Sealed { /// `block_number` isn't within the range of the previous 256 blocks. fn block_hash(&self, block_number: U256) -> Option<H256>; + /// Returns the author of the current block. + fn block_author(&self) -> Option<AccountIdOf<Self::T>>; + /// Returns the maximum allowed size of a storage item. fn max_value_size(&self) -> u32; @@ -378,19 +389,6 @@ pub trait Ext: sealing::Sealed { /// 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: <Self::T as Config>::RuntimeCall) -> DispatchResultWithPostInfo; @@ -416,51 +414,6 @@ pub trait Ext: sealing::Sealed { /// Sets new code hash and immutable data for an existing contract. fn set_code_hash(&mut self, hash: H256) -> 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: H256) -> 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: H256); - - /// 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: H256) -> 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: &H256) -> 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; @@ -555,13 +508,11 @@ pub struct Stack<'a, T: Config, E> { frames: BoundedVec<Frame<T>, ConstU32<{ limits::CALL_STACK_DEPTH }>>, /// Statically guarantee that each call stack has at least one frame. first_frame: Frame<T>, - /// 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<T>, + /// Whether or not actual transfer of funds should be performed. + /// This is set to `true` exclusively when we simulate a call through eth_transact. + skip_transfer: bool, /// No executable is held by the struct but influences its behaviour. _phantom: PhantomData<E>, } @@ -762,11 +713,6 @@ where { /// 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)> @@ -777,7 +723,7 @@ where storage_meter: &'a mut storage::meter::Meter<T>, value: U256, input_data: Vec<u8>, - debug_message: Option<&'a mut DebugBuffer>, + skip_transfer: bool, ) -> ExecResult { let dest = T::AddressMapper::to_account_id(&dest); if let Some((mut stack, executable)) = Self::new( @@ -786,21 +732,34 @@ where gas_meter, storage_meter, value, - debug_message, + skip_transfer, )? { stack.run(executable, input_data).map(|_| stack.first_frame.last_frame_output) } else { - Self::transfer_from_origin(&origin, &origin, &dest, value) + if_tracing(|t| { + let address = + origin.account_id().map(T::AddressMapper::to_address).unwrap_or_default(); + let dest = T::AddressMapper::to_address(&dest); + t.enter_child_span(address, dest, false, false, value, &input_data, Weight::zero()); + }); + + let result = Self::transfer_from_origin(&origin, &origin, &dest, value); + match result { + Ok(ref output) => { + if_tracing(|t| { + t.exit_child_span(&output, Weight::zero()); + }); + }, + Err(e) => { + if_tracing(|t| t.exit_child_span_with_error(e.error.into(), Weight::zero())); + }, + } + result } } /// 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)> @@ -812,7 +771,7 @@ where value: U256, input_data: Vec<u8>, salt: Option<&[u8; 32]>, - debug_message: Option<&'a mut DebugBuffer>, + skip_transfer: bool, ) -> Result<(H160, ExecReturnValue), ExecError> { let (mut stack, executable) = Self::new( FrameArgs::Instantiate { @@ -825,7 +784,7 @@ where gas_meter, storage_meter, value, - debug_message, + skip_transfer, )? .expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE); let address = T::AddressMapper::to_address(&stack.top_frame().account_id); @@ -841,7 +800,6 @@ where gas_meter: &'a mut GasMeter<T>, storage_meter: &'a mut storage::meter::Meter<T>, value: BalanceOf<T>, - debug_message: Option<&'a mut DebugBuffer>, ) -> (Self, E) { Self::new( FrameArgs::Call { @@ -853,7 +811,7 @@ where gas_meter, storage_meter, value.into(), - debug_message, + false, ) .unwrap() .unwrap() @@ -869,16 +827,16 @@ where gas_meter: &'a mut GasMeter<T>, storage_meter: &'a mut storage::meter::Meter<T>, value: U256, - debug_message: Option<&'a mut DebugBuffer>, + skip_transfer: bool, ) -> Result<Option<(Self, E)>, ExecError> { origin.ensure_mapped()?; let Some((first_frame, executable)) = Self::new_frame( args, value, gas_meter, - Weight::zero(), + Weight::max_value(), storage_meter, - BalanceOf::<T>::zero(), + BalanceOf::<T>::max_value(), false, true, )? @@ -894,8 +852,8 @@ where block_number: <frame_system::Pallet<T>>::block_number(), first_frame, frames: Default::default(), - debug_message, transient_storage: TransientStorage::new(limits::TRANSIENT_STORAGE_BYTES), + skip_transfer, _phantom: Default::default(), }; @@ -1041,6 +999,7 @@ where fn run(&mut self, executable: E, input_data: Vec<u8>) -> Result<(), ExecError> { let frame = self.top_frame(); let entry_point = frame.entry_point; + let is_delegate_call = frame.delegate.is_some(); let delegated_code_hash = if frame.delegate.is_some() { Some(*executable.code_hash()) } else { None }; @@ -1058,25 +1017,40 @@ where self.transient_storage.start_transaction(); - let do_transaction = || { + let do_transaction = || -> ExecResult { let caller = self.caller(); let frame = top_frame_mut!(self); + let read_only = frame.read_only; + let value_transferred = frame.value_transferred; + let account_id = &frame.account_id.clone(); - // 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. + // We need to make sure that the contract's account exists before calling its + // constructor. 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()?; - frame.nested_storage.charge_instantiate( - origin, - &frame.account_id, - frame.contract_info.get(&frame.account_id), - executable.code_info(), - )?; + + let ed = <Contracts<T>>::min_balance(); + frame.nested_storage.record_charge(&StorageDeposit::Charge(ed)); + if self.skip_transfer { + T::Currency::set_balance(account_id, ed); + } else { + T::Currency::transfer(origin, account_id, 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<T>>::inc_consumers(account_id)?; + // Needs to be incremented before calling into the code so that it is visible // in case of recursion. <System<T>>::inc_account_nonce(caller.account_id()?); + + // The incremented refcount should be visible to the constructor. + <CodeInfo<T>>::increment_refcount(*executable.code_hash())?; } // Every non delegate call or instantiate also optionally transfers the balance. @@ -1091,72 +1065,61 @@ where )?; } - let contract_address = T::AddressMapper::to_address(&top_frame!(self).account_id); - - let call_span = T::Debug::new_call_span(&contract_address, entry_point, &input_data); + let contract_address = T::AddressMapper::to_address(account_id); + let maybe_caller_address = caller.account_id().map(T::AddressMapper::to_address); + let code_deposit = executable.code_info().deposit(); + + if_tracing(|tracer| { + tracer.enter_child_span( + maybe_caller_address.unwrap_or_default(), + contract_address, + is_delegate_call, + read_only, + value_transferred, + &input_data, + frame.nested_gas.gas_left(), + ); + }); - 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 }) - })?; + let output = executable.execute(self, entry_point, input_data).map_err(|e| { + if_tracing(|tracer| { + tracer.exit_child_span_with_error( + e.error, + top_frame_mut!(self).nested_gas.gas_consumed(), + ); + }); + ExecError { error: e.error, origin: ErrorOrigin::Callee } + })?; - call_span.after_call(&output); + if_tracing(|tracer| { + tracer.exit_child_span(&output, top_frame_mut!(self).nested_gas.gas_consumed()); + }); // 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_mut(); - // 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 contract = frame.contract_info.as_contract(); - frame.nested_storage.enforce_subcall_limit(contract)?; - - let account_id = T::AddressMapper::to_address(&frame.account_id); - 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::<T>::TerminatedInConstructor.into()); - } - - let caller = T::AddressMapper::to_address(self.caller().account_id()?); - // Deposit an instantiation event. - Contracts::<T>::deposit_event(Event::Instantiated { - deployer: caller, - contract: account_id, - }); - }, - (ExportedFunction::Call, Some(code_hash)) => { - Contracts::<T>::deposit_event(Event::DelegateCalled { - contract: account_id, - code_hash, - }); - }, - (ExportedFunction::Call, None) => { - let caller = self.caller(); - Contracts::<T>::deposit_event(Event::Called { - caller: caller.clone(), - contract: account_id, - }); - }, + // The deposit we charge for a contract depends on the size of the immutable data. + // Hence we need to delay charging the base deposit after execution. + if entry_point == ExportedFunction::Constructor { + let deposit = frame.contract_info().update_base_deposit(code_deposit); + frame + .nested_storage + .charge_deposit(frame.account_id.clone(), StorageDeposit::Charge(deposit)); } + // The storage deposit is only charged at the end of every call stack. + // To make sure that no sub call uses more than it is allowed to, + // the limit is manually enforced here. + let contract = frame.contract_info.as_contract(); + frame + .nested_storage + .enforce_limit(contract) + .map_err(|e| ExecError { error: e, origin: ErrorOrigin::Callee })?; + Ok(output) }; @@ -1249,13 +1212,6 @@ where } } } 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("<Invalid UTF8>"), - ); - } self.gas_meter.absorb_nested(mem::take(&mut self.first_frame.nested_gas)); if !persist { return; @@ -1293,7 +1249,7 @@ where to: &T::AccountId, value: U256, ) -> ExecResult { - let value = crate::Pallet::<T>::convert_evm_to_native(value)?; + let value = crate::Pallet::<T>::convert_evm_to_native(value, ConversionPrecision::Exact)?; if value.is_zero() { return Ok(Default::default()); } @@ -1415,7 +1371,7 @@ where &mut self, gas_limit: Weight, deposit_limit: U256, - dest: &H160, + dest_addr: &H160, value: U256, input_data: Vec<u8>, allows_reentry: bool, @@ -1431,7 +1387,7 @@ where *self.last_frame_output_mut() = Default::default(); let try_call = || { - let dest = T::AddressMapper::to_account_id(dest); + let dest = T::AddressMapper::to_account_id(dest_addr); if !self.allows_reentry(&dest) { return Err(<Error<T>>::ReentranceDenied.into()); } @@ -1452,7 +1408,7 @@ where FrameArgs::Call { dest: dest.clone(), cached_info, delegated_call: None }, value, gas_limit, - deposit_limit.try_into().map_err(|_| Error::<T>::BalanceConversionFailed)?, + deposit_limit.saturated_into::<BalanceOf<T>>(), // Enable read-only access if requested; cannot disable it if already set. read_only || self.is_read_only(), )? { @@ -1508,7 +1464,7 @@ where }, value, gas_limit, - deposit_limit.try_into().map_err(|_| Error::<T>::BalanceConversionFailed)?, + deposit_limit.saturated_into::<BalanceOf<T>>(), self.is_read_only(), )?; self.run(executable.expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE), input_data) @@ -1538,7 +1494,7 @@ where }, value.try_into().map_err(|_| Error::<T>::BalanceConversionFailed)?, gas_limit, - deposit_limit.try_into().map_err(|_| Error::<T>::BalanceConversionFailed)?, + deposit_limit.saturated_into::<BalanceOf<T>>(), self.is_read_only(), )?; let address = T::AddressMapper::to_address(&self.top_frame().account_id); @@ -1551,6 +1507,9 @@ where return Err(Error::<T>::TerminatedWhileReentrant.into()); } let frame = self.top_frame_mut(); + if frame.entry_point == ExportedFunction::Constructor { + return Err(Error::<T>::TerminatedInConstructor.into()); + } let info = frame.terminate(); let beneficiary_account = T::AddressMapper::to_account_id(beneficiary); frame.nested_storage.terminate(&info, beneficiary_account); @@ -1559,19 +1518,8 @@ where let account_address = T::AddressMapper::to_address(&frame.account_id); ContractInfoOf::<T>::remove(&account_address); ImmutableDataOf::<T>::remove(&account_address); - Self::decrement_refcount(info.code_hash); + <CodeInfo<T>>::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::<T>::deposit_event(Event::Terminated { - contract: account_address, - beneficiary: *beneficiary, - }); Ok(()) } @@ -1641,6 +1589,10 @@ where ContractInfoOf::<T>::contains_key(&address) } + fn to_account_id(&self, address: &H160) -> T::AccountId { + T::AddressMapper::to_account_id(address) + } + fn code_hash(&self, address: &H160) -> H256 { <ContractInfoOf<T>>::get(&address) .map(|contract| contract.code_hash) @@ -1652,7 +1604,7 @@ where }) } - fn code_size(&self, address: &H160) -> U256 { + fn code_size(&self, address: &H160) -> u64 { <ContractInfoOf<T>>::get(&address) .and_then(|contract| CodeInfoOf::<T>::get(contract.code_hash)) .map(|info| info.code_len()) @@ -1672,6 +1624,10 @@ where self.caller_is_origin() && self.origin == Origin::Root } + fn immutable_data_len(&mut self) -> u32 { + self.top_frame_mut().contract_info().immutable_data_len() + } + fn get_immutable_data(&mut self) -> Result<ImmutableData, DispatchError> { if self.top_frame().entry_point == ExportedFunction::Constructor { return Err(Error::<T>::InvalidImmutableAccess.into()); @@ -1688,17 +1644,12 @@ where } fn set_immutable_data(&mut self, data: ImmutableData) -> Result<(), DispatchError> { - if self.top_frame().entry_point == ExportedFunction::Call { + let frame = self.top_frame_mut(); + if frame.entry_point == ExportedFunction::Call || data.is_empty() { return Err(Error::<T>::InvalidImmutableAccess.into()); } - - let account_id = self.account_id().clone(); - let len = data.len() as u32; - let amount = self.top_frame_mut().contract_info().set_immutable_data_len(len)?; - self.top_frame_mut().nested_storage.charge_deposit(account_id.clone(), amount); - - <ImmutableDataOf<T>>::insert(T::AddressMapper::to_address(&account_id), &data); - + frame.contract_info().set_immutable_data_len(data.len() as u32); + <ImmutableDataOf<T>>::insert(T::AddressMapper::to_address(&frame.account_id), &data); Ok(()) } @@ -1723,11 +1674,11 @@ where } fn deposit_event(&mut self, topics: Vec<H256>, data: Vec<u8>) { - Contracts::<Self::T>::deposit_event(Event::ContractEmitted { - contract: T::AddressMapper::to_address(self.account_id()), - data, - topics, + let contract = T::AddressMapper::to_address(self.account_id()); + if_tracing(|tracer| { + tracer.log_event(contract, &topics, &data); }); + Contracts::<Self::T>::deposit_event(Event::ContractEmitted { contract, data, topics }); } fn block_number(&self) -> U256 { @@ -1738,6 +1689,13 @@ where self.block_hash(block_number) } + fn block_author(&self) -> Option<AccountIdOf<Self::T>> { + let digest = <frame_system::Pallet<T>>::digest(); + let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime()); + + T::FindAuthor::find_author(pre_runtime_digests) + } + fn max_value_size(&self) -> u32 { limits::PAYLOAD_BYTES } @@ -1758,28 +1716,6 @@ where self.top_frame_mut().nested_storage.charge(diff) } - fn debug_buffer_enabled(&self) -> bool { - self.debug_message.is_some() - } - - fn append_debug_buffer(&mut self, msg: &str) -> bool { - if let Some(buffer) = &mut self.debug_message { - buffer - .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: <Self::T as Config>::RuntimeCall) -> DispatchResultWithPostInfo { let mut origin: T::RuntimeOrigin = RawOrigin::Signed(self.account_id().clone()).into(); origin.add_filter(T::CallFilter::contains); @@ -1838,73 +1774,17 @@ where let code_info = CodeInfoOf::<T>::get(hash).ok_or(Error::<T>::CodeNotFound)?; let old_base_deposit = info.storage_base_deposit(); - let new_base_deposit = info.update_base_deposit(&code_info); + let new_base_deposit = info.update_base_deposit(code_info.deposit()); 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::<Self::T>::deposit_event(Event::ContractCodeUpdated { - contract: T::AddressMapper::to_address(&frame.account_id), - new_code_hash: hash, - old_code_hash: prev_hash, - }); + <CodeInfo<T>>::increment_refcount(hash)?; + <CodeInfo<T>>::decrement_refcount(prev_hash)?; Ok(()) } - fn increment_refcount(code_hash: H256) -> DispatchResult { - <CodeInfoOf<Self::T>>::mutate(code_hash, |existing| -> Result<(), DispatchError> { - if let Some(info) = existing { - *info.refcount_mut() = info.refcount().saturating_add(1); - Ok(()) - } else { - Err(Error::<T>::CodeNotFound.into()) - } - }) - } - - fn decrement_refcount(code_hash: H256) { - <CodeInfoOf<T>>::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: H256) -> DispatchResult { - let frame = self.top_frame_mut(); - let info = frame.contract_info.get(&frame.account_id); - ensure!(code_hash != info.code_hash, Error::<T>::CannotAddSelfAsDelegateDependency); - - let code_info = CodeInfoOf::<T>::get(code_hash).ok_or(Error::<T>::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: &H256) -> 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 } @@ -1979,7 +1859,7 @@ mod tests { #[derive(Clone)] struct MockExecutable { func: Rc<dyn for<'a> Fn(MockCtx<'a>, &Self) -> ExecResult + 'static>, - func_type: ExportedFunction, + constructor: Rc<dyn for<'a> Fn(MockCtx<'a>, &Self) -> ExecResult + 'static>, code_hash: H256, code_info: CodeInfo<Test>, } @@ -1998,6 +1878,39 @@ mod tests { fn insert( func_type: ExportedFunction, f: impl Fn(MockCtx, &MockExecutable) -> ExecResult + 'static, + ) -> H256 { + Loader::mutate(|loader| { + // Generate code hashes from contract index value. + let hash = H256(keccak_256(&loader.counter.to_le_bytes())); + loader.counter += 1; + if func_type == ExportedFunction::Constructor { + loader.map.insert( + hash, + MockExecutable { + func: Rc::new(|_, _| exec_success()), + constructor: Rc::new(f), + code_hash: hash, + code_info: CodeInfo::<Test>::new(ALICE), + }, + ); + } else { + loader.map.insert( + hash, + MockExecutable { + func: Rc::new(f), + constructor: Rc::new(|_, _| exec_success()), + code_hash: hash, + code_info: CodeInfo::<Test>::new(ALICE), + }, + ); + } + hash + }) + } + + fn insert_both( + constructor: impl Fn(MockCtx, &MockExecutable) -> ExecResult + 'static, + call: impl Fn(MockCtx, &MockExecutable) -> ExecResult + 'static, ) -> H256 { Loader::mutate(|loader| { // Generate code hashes from contract index value. @@ -2006,8 +1919,8 @@ mod tests { loader.map.insert( hash, MockExecutable { - func: Rc::new(f), - func_type, + func: Rc::new(call), + constructor: Rc::new(constructor), code_hash: hash, code_info: CodeInfo::<Test>::new(ALICE), }, @@ -2033,9 +1946,6 @@ mod tests { function: ExportedFunction, input_data: Vec<u8>, ) -> 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. @@ -2046,10 +1956,10 @@ mod tests { // `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) + if function == ExportedFunction::Constructor { + (self.constructor)(MockCtx { ext, input_data }, &self) } else { - exec_success() + (self.func)(MockCtx { ext, input_data }, &self) } } @@ -2101,7 +2011,7 @@ mod tests { &mut storage_meter, value.into(), vec![], - None, + false, ), Ok(_) ); @@ -2193,7 +2103,7 @@ mod tests { &mut storage_meter, value.into(), vec![], - None, + false, ) .unwrap(); @@ -2233,7 +2143,7 @@ mod tests { &mut storage_meter, value.into(), vec![], - None, + false, )); assert_eq!(get_balance(&ALICE), 100 - value); @@ -2269,7 +2179,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, ), ExecError { error: Error::<Test>::CodeNotFound.into(), @@ -2286,7 +2196,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, )); }); } @@ -2314,7 +2224,7 @@ mod tests { &mut storage_meter, 55u64.into(), vec![], - None, + false, ) .unwrap(); @@ -2363,7 +2273,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, ); let output = result.unwrap(); @@ -2392,7 +2302,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, ); let output = result.unwrap(); @@ -2421,7 +2331,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![1, 2, 3, 4], - None, + false, ); assert_matches!(result, Ok(_)); }); @@ -2457,7 +2367,7 @@ mod tests { min_balance.into(), vec![1, 2, 3, 4], Some(&[0; 32]), - None, + false, ); assert_matches!(result, Ok(_)); }); @@ -2511,7 +2421,7 @@ mod tests { &mut storage_meter, value.into(), vec![], - None, + false, ); assert_matches!(result, Ok(_)); @@ -2575,7 +2485,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, ); assert_matches!(result, Ok(_)); @@ -2640,7 +2550,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, ); assert_matches!(result, Ok(_)); @@ -2672,7 +2582,41 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn to_account_id_returns_proper_values() { + let bob_code_hash = MockLoader::insert(Call, |ctx, _| { + let alice_account_id = <Test as Config>::AddressMapper::to_account_id(&ALICE_ADDR); + assert_eq!(ctx.ext.to_account_id(&ALICE_ADDR), alice_account_id); + + const UNMAPPED_ADDR: H160 = H160([99u8; 20]); + let mut unmapped_fallback_account_id = [0xEE; 32]; + unmapped_fallback_account_id[..20].copy_from_slice(UNMAPPED_ADDR.as_bytes()); + assert_eq!( + ctx.ext.to_account_id(&UNMAPPED_ADDR), + AccountId32::new(unmapped_fallback_account_id) + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, bob_code_hash); + let origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); + let result = MockStack::run_call( + origin, + BOB_ADDR, + &mut GasMeter::<Test>::new(GAS_LIMIT), + &mut storage_meter, + U256::zero(), + vec![0], + false, ); assert_matches!(result, Ok(_)); }); @@ -2709,7 +2653,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], - None, + false, ); assert_matches!(result, Ok(_)); }); @@ -2735,7 +2679,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], - None, + false, ); assert_matches!(result, Ok(_)); }); @@ -2779,7 +2723,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], - None, + false, ); assert_matches!(result, Ok(_)); }); @@ -2805,7 +2749,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], - None, + false, ); assert_matches!(result, Ok(_)); }); @@ -2831,7 +2775,7 @@ mod tests { &mut storage_meter, 1u64.into(), vec![0], - None, + false, ); assert_matches!(result, Err(_)); }); @@ -2875,7 +2819,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], - None, + false, ); assert_matches!(result, Ok(_)); }); @@ -2920,7 +2864,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, ); assert_matches!(result, Ok(_)); @@ -2946,7 +2890,7 @@ mod tests { U256::zero(), // <- zero value vec![], Some(&[0; 32]), - None, + false, ), Err(_) ); @@ -2981,7 +2925,7 @@ mod tests { min_balance.into(), vec![], Some(&[0 ;32]), - None, + false, ), Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address ); @@ -2997,13 +2941,6 @@ mod tests { ContractInfo::<Test>::load_code_hash(&instantiated_contract_id).unwrap(), dummy_ch ); - assert_eq!( - &events(), - &[Event::Instantiated { - deployer: ALICE_ADDR, - contract: instantiated_contract_address - }] - ); }); } @@ -3032,11 +2969,10 @@ mod tests { executable, &mut gas_meter, &mut storage_meter, - min_balance.into(), vec![], Some(&[0; 32]), - None, + false, ), Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address ); @@ -3064,8 +3000,8 @@ mod tests { let (address, output) = ctx .ext .instantiate( - Weight::zero(), - U256::zero(), + Weight::MAX, + U256::MAX, dummy_ch, <Test as Config>::Currency::minimum_balance().into(), vec![], @@ -3100,7 +3036,7 @@ mod tests { &mut storage_meter, (min_balance * 10).into(), vec![], - None, + false, ), Ok(_) ); @@ -3120,19 +3056,6 @@ mod tests { ContractInfo::<Test>::load_code_hash(&instantiated_contract_id).unwrap(), dummy_ch ); - assert_eq!( - &events(), - &[ - Event::Instantiated { - deployer: BOB_ADDR, - contract: instantiated_contract_address - }, - Event::Called { - caller: Origin::from_account_id(ALICE), - contract: BOB_ADDR - }, - ] - ); }); } @@ -3180,24 +3103,17 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, ), 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_ADDR },] - ); }); } #[test] fn termination_from_instantiate_fails() { let terminate_ch = MockLoader::insert(Constructor, |ctx, _| { - ctx.ext.terminate(&ALICE_ADDR).unwrap(); + ctx.ext.terminate(&ALICE_ADDR)?; exec_success() }); @@ -3223,9 +3139,12 @@ mod tests { 100u64.into(), vec![], Some(&[0; 32]), - None, + false, ), - Err(Error::<Test>::TerminatedInConstructor.into()) + Err(ExecError { + error: Error::<Test>::TerminatedInConstructor.into(), + origin: ErrorOrigin::Callee + }) ); assert_eq!(&events(), &[]); @@ -3287,7 +3206,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], - None, + false, ); assert_matches!(result, Ok(_)); }); @@ -3349,7 +3268,7 @@ mod tests { 10u64.into(), vec![], Some(&[0; 32]), - None, + false, ); assert_matches!(result, Ok(_)); }); @@ -3373,7 +3292,7 @@ mod tests { true, false ), - <Error<Test>>::TransferFailed + <Error<Test>>::TransferFailed, ); exec_success() }); @@ -3395,110 +3314,12 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, ) .unwrap(); }); } - #[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 = <Test as Config>::Currency::minimum_balance(); - - let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT); - set_balance(&ALICE, min_balance * 10); - place_contract(&BOB, code_hash); - let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); - MockStack::run_call( - origin, - BOB_ADDR, - &mut gas_meter, - &mut storage_meter, - U256::zero(), - 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 = <Test as Config>::Currency::minimum_balance(); - - let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT); - set_balance(&ALICE, min_balance * 10); - place_contract(&BOB, code_hash); - let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); - let result = MockStack::run_call( - origin, - BOB_ADDR, - &mut gas_meter, - &mut storage_meter, - U256::zero(), - 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 = <Test as Config>::Currency::minimum_balance(); - let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT); - set_balance(&ALICE, min_balance * 10); - place_contract(&BOB, code_hash); - let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); - MockStack::run_call( - origin, - BOB_ADDR, - &mut gas_meter, - &mut storage_meter, - U256::zero(), - 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 @@ -3525,7 +3346,7 @@ mod tests { &mut storage_meter, U256::zero(), CHARLIE_ADDR.as_bytes().to_vec(), - None, + false, )); // Calling into oneself fails @@ -3537,7 +3358,7 @@ mod tests { &mut storage_meter, U256::zero(), BOB_ADDR.as_bytes().to_vec(), - None, + false, ) .map_err(|e| e.error), <Error<Test>>::ReentranceDenied, @@ -3587,7 +3408,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], - None, + false, ) .map_err(|e| e.error), <Error<Test>>::ReentranceDenied, @@ -3621,31 +3442,21 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, ) .unwrap(); let remark_hash = <Test as frame_system::Config>::Hashing::hash(b"Hello World"); assert_eq!( System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::System(frame_system::Event::Remarked { - sender: BOB_FALLBACK, - hash: remark_hash - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: BOB_ADDR, - }), - topics: vec![], - }, - ] + vec![EventRecord { + phase: Phase::Initialization, + event: MetaEvent::System(frame_system::Event::Remarked { + sender: BOB_FALLBACK, + hash: remark_hash + }), + topics: vec![], + },] ); }); } @@ -3705,7 +3516,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, ) .unwrap(); @@ -3734,14 +3545,6 @@ mod tests { },), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: BOB_ADDR, - }), - topics: vec![], - }, ] ); }); @@ -3754,8 +3557,8 @@ mod tests { let succ_fail_code = MockLoader::insert(Constructor, move |ctx, _| { ctx.ext .instantiate( - Weight::zero(), - U256::zero(), + Weight::MAX, + U256::MAX, fail_code, ctx.ext.minimum_balance() * 100, vec![], @@ -3771,8 +3574,8 @@ mod tests { let addr = ctx .ext .instantiate( - Weight::zero(), - U256::zero(), + Weight::MAX, + U256::MAX, success_code, ctx.ext.minimum_balance() * 100, vec![], @@ -3831,7 +3634,7 @@ mod tests { (min_balance * 100).into(), vec![], Some(&[0; 32]), - None, + false, ) .ok(); assert_eq!(System::account_nonce(&ALICE), 0); @@ -3844,7 +3647,7 @@ mod tests { (min_balance * 100).into(), vec![], Some(&[0; 32]), - None, + false, )); assert_eq!(System::account_nonce(&ALICE), 1); @@ -3856,7 +3659,7 @@ mod tests { (min_balance * 200).into(), vec![], Some(&[0; 32]), - None, + false, )); assert_eq!(System::account_nonce(&ALICE), 2); @@ -3868,7 +3671,7 @@ mod tests { (min_balance * 200).into(), vec![], Some(&[0; 32]), - None, + false, )); assert_eq!(System::account_nonce(&ALICE), 3); }); @@ -3936,7 +3739,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, )); }); } @@ -4047,7 +3850,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, )); }); } @@ -4086,7 +3889,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, )); }); } @@ -4125,7 +3928,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, )); }); } @@ -4178,7 +3981,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, )); }); } @@ -4234,7 +4037,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, )); }); } @@ -4309,7 +4112,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, )); }); } @@ -4379,7 +4182,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], - None, + false, ); assert_matches!(result, Ok(_)); }); @@ -4417,7 +4220,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, )); }); } @@ -4479,7 +4282,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], - None, + false, ); assert_matches!(result, Ok(_)); }); @@ -4512,7 +4315,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, ); assert_matches!(result, Ok(_)); }); @@ -4534,7 +4337,7 @@ mod tests { // Successful instantiation should set the output let address = ctx .ext - .instantiate(Weight::zero(), U256::zero(), ok_ch, value, vec![], None) + .instantiate(Weight::MAX, U256::MAX, ok_ch, value, vec![], None) .unwrap(); assert_eq!( ctx.ext.last_frame_output(), @@ -4543,15 +4346,7 @@ mod tests { // Balance transfers should reset the output ctx.ext - .call( - Weight::zero(), - U256::zero(), - &address, - U256::from(1), - vec![], - true, - false, - ) + .call(Weight::MAX, U256::MAX, &address, U256::from(1), vec![], true, false) .unwrap(); assert_eq!(ctx.ext.last_frame_output(), &Default::default()); @@ -4595,7 +4390,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, ) .unwrap() }); @@ -4663,7 +4458,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], - None, + false, ); assert_matches!(result, Ok(_)); }); @@ -4734,7 +4529,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, ); assert_matches!(result, Ok(_)); }); @@ -4761,7 +4556,7 @@ mod tests { // Constructors can not access the immutable data ctx.ext - .instantiate(Weight::zero(), U256::zero(), dummy_ch, value, vec![], None) + .instantiate(Weight::MAX, U256::MAX, dummy_ch, value, vec![], None) .unwrap(); exec_success() @@ -4785,7 +4580,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, ) .unwrap() }); @@ -4854,53 +4649,58 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, ) .unwrap() }); } #[test] - fn immutable_data_set_works_only_once() { - let dummy_ch = MockLoader::insert(Constructor, move |ctx, _| { - // Calling `set_immutable_data` the first time should work - assert_ok!(ctx.ext.set_immutable_data(vec![0, 1, 2, 3].try_into().unwrap())); - // Calling `set_immutable_data` the second time should error out - assert_eq!( - ctx.ext.set_immutable_data(vec![0, 1, 2, 3].try_into().unwrap()), - Err(Error::<Test>::InvalidImmutableAccess.into()) - ); - exec_success() - }); - let instantiator_ch = MockLoader::insert(Call, { + fn immutable_data_set_overrides() { + let hash = MockLoader::insert_both( move |ctx, _| { - let value = <Test as Config>::Currency::minimum_balance().into(); - ctx.ext - .instantiate(Weight::zero(), U256::zero(), dummy_ch, value, vec![], None) - .unwrap(); - + // Calling `set_immutable_data` the first time should work + assert_ok!(ctx.ext.set_immutable_data(vec![0, 1, 2, 3].try_into().unwrap())); + // Calling `set_immutable_data` the second time overrides the original one + assert_ok!(ctx.ext.set_immutable_data(vec![7, 5].try_into().unwrap())); exec_success() - } - }); + }, + move |ctx, _| { + assert_eq!(ctx.ext.get_immutable_data().unwrap().into_inner(), vec![7, 5]); + 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 origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&origin, 200, 0).unwrap(); + let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT); + + let addr = MockStack::run_instantiate( + ALICE, + MockExecutable::from_storage(hash, &mut gas_meter).unwrap(), + &mut gas_meter, + &mut storage_meter, + U256::zero(), + vec![], + None, + false, + ) + .unwrap() + .0; MockStack::run_call( origin, - BOB_ADDR, + addr, &mut GasMeter::<Test>::new(GAS_LIMIT), &mut storage_meter, U256::zero(), vec![], - None, + false, ) .unwrap() }); @@ -4920,7 +4720,7 @@ mod tests { move |ctx, _| { let value = <Test as Config>::Currency::minimum_balance().into(); ctx.ext - .instantiate(Weight::zero(), U256::zero(), dummy_ch, value, vec![], None) + .instantiate(Weight::MAX, U256::MAX, dummy_ch, value, vec![], None) .unwrap(); exec_success() @@ -4944,7 +4744,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], - None, + false, ) .unwrap() }); @@ -4999,7 +4799,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], - None, + false, ), Ok(_) ); diff --git a/substrate/frame/revive/src/gas.rs b/substrate/frame/revive/src/gas.rs index 9aad84e69201e4991679f7b1e57590598760415e..e8338db12192bfe5ba698f191b3469be12d5a511 100644 --- a/substrate/frame/revive/src/gas.rs +++ b/substrate/frame/revive/src/gas.rs @@ -22,7 +22,7 @@ use frame_support::{ weights::Weight, DefaultNoBound, }; -use sp_runtime::{traits::Zero, DispatchError}; +use sp_runtime::DispatchError; #[cfg(test)] use std::{any::Any, fmt::Debug}; @@ -89,7 +89,7 @@ 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. +/// Wrapped to make sure that the resource will be synced back to the executor. #[must_use] pub struct Syncable(polkavm::Gas); @@ -168,25 +168,19 @@ impl<T: Config> GasMeter<T> { } } - /// Create a new gas meter by removing gas from the current meter. + /// Create a new gas meter by removing *all* the gas from the current meter. /// - /// # Note - /// - /// Passing `0` as amount is interpreted as "all remaining gas". + /// This should only be used by the primordial frame in a sequence of calls - every subsequent + /// frame should use [`nested`](Self::nested). + pub fn nested_take_all(&mut self) -> Self { + let gas_left = self.gas_left; + self.gas_left -= gas_left; + GasMeter::new(gas_left) + } + + /// Create a new gas meter for a nested call by removing gas from the current meter. 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); + let amount = amount.min(self.gas_left); self.gas_left -= amount; GasMeter::new(amount) } @@ -392,6 +386,50 @@ mod tests { assert!(gas_meter.charge(SimpleToken(1)).is_err()); } + /// Previously, passing a `Weight` of 0 to `nested` would consume all of the meter's current + /// gas. + /// + /// Now, a `Weight` of 0 means no gas for the nested call. + #[test] + fn nested_zero_gas_requested() { + let test_weight = 50000.into(); + let mut gas_meter = GasMeter::<Test>::new(test_weight); + let gas_for_nested_call = gas_meter.nested(0.into()); + + assert_eq!(gas_meter.gas_left(), 50000.into()); + assert_eq!(gas_for_nested_call.gas_left(), 0.into()) + } + + #[test] + fn nested_some_gas_requested() { + let test_weight = 50000.into(); + let mut gas_meter = GasMeter::<Test>::new(test_weight); + let gas_for_nested_call = gas_meter.nested(10000.into()); + + assert_eq!(gas_meter.gas_left(), 40000.into()); + assert_eq!(gas_for_nested_call.gas_left(), 10000.into()) + } + + #[test] + fn nested_all_gas_requested() { + let test_weight = Weight::from_parts(50000, 50000); + let mut gas_meter = GasMeter::<Test>::new(test_weight); + let gas_for_nested_call = gas_meter.nested(test_weight); + + assert_eq!(gas_meter.gas_left(), Weight::from_parts(0, 0)); + assert_eq!(gas_for_nested_call.gas_left(), 50_000.into()) + } + + #[test] + fn nested_excess_gas_requested() { + let test_weight = Weight::from_parts(50000, 50000); + let mut gas_meter = GasMeter::<Test>::new(test_weight); + let gas_for_nested_call = gas_meter.nested(test_weight + 10000.into()); + + assert_eq!(gas_meter.gas_left(), Weight::from_parts(0, 0)); + assert_eq!(gas_for_nested_call.gas_left(), 50_000.into()) + } + // Make sure that the gas meter does not charge in case of overcharge #[test] fn overcharge_does_not_charge() { diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index b55854e2eec583ef954ac8a9df7c796cc69f4c4d..69d60c5d6a9523e5727e33bd34d06fb1e489f92f 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -35,24 +35,24 @@ mod wasm; mod tests; pub mod chain_extension; -pub mod debug; pub mod evm; pub mod test_utils; +pub mod tracing; pub mod weights; use crate::{ - evm::{runtime::GAS_PRICE, TransactionLegacyUnsigned}, - exec::{AccountIdOf, ExecError, Executable, Ext, Key, Origin, Stack as ExecStack}, + evm::{runtime::GAS_PRICE, GasEncoder, GenericTransaction}, + exec::{AccountIdOf, ExecError, Executable, Key, Stack as ExecStack}, gas::GasMeter, storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager}, wasm::{CodeInfo, RuntimeCosts, WasmBlob}, }; -use alloc::boxed::Box; +use alloc::{boxed::Box, format, vec}; use codec::{Codec, Decode, Encode}; use environmental::*; use frame_support::{ dispatch::{ - DispatchErrorWithPostInfo, DispatchResultWithPostInfo, GetDispatchInfo, Pays, + DispatchErrorWithPostInfo, DispatchInfo, DispatchResultWithPostInfo, GetDispatchInfo, Pays, PostDispatchInfo, RawOrigin, }, ensure, @@ -68,20 +68,18 @@ use frame_support::{ use frame_system::{ ensure_signed, pallet_prelude::{BlockNumberFor, OriginFor}, - EventRecord, Pallet as System, + Pallet as System, }; -use pallet_transaction_payment::OnChargeTransaction; use scale_info::TypeInfo; use sp_core::{H160, H256, U256}; use sp_runtime::{ - traits::{BadOrigin, Convert, Dispatchable, Saturating, Zero}, + traits::{BadOrigin, Bounded, Convert, Dispatchable, Saturating, Zero}, DispatchError, }; pub use crate::{ address::{create1, create2, AccountId32Mapper, AddressMapper}, - debug::Tracing, - exec::MomentOf, + exec::{MomentOf, Origin}, pallet::*, }; pub use primitives::*; @@ -93,11 +91,7 @@ pub use crate::wasm::SyscallDoc; type TrieId = BoundedVec<u8, ConstU32<128>>; type BalanceOf<T> = <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance; -type OnChargeTransactionBalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<T>>::Balance; type CodeVec = BoundedVec<u8, ConstU32<{ limits::code::BLOB_BYTES }>>; -type EventRecordOf<T> = - EventRecord<<T as frame_system::Config>::RuntimeEvent, <T as frame_system::Config>::Hash>; -type DebugBuffer = BoundedVec<u8, ConstU32<{ limits::DEBUG_BUFFER_BYTES }>>; type ImmutableData = BoundedVec<u8, ConstU32<{ limits::IMMUTABLE_BYTES }>>; /// Used as a sentinel value when reading and writing contract memory. @@ -115,24 +109,10 @@ const SENTINEL: u32 = u32::MAX; /// 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_support::{pallet_prelude::*, traits::FindAuthor}; use frame_system::pallet_prelude::*; use sp_core::U256; use sp_runtime::Perbill; @@ -207,6 +187,9 @@ pub mod pallet { #[pallet::no_default_bounds] type ChainExtension: chain_extension::ChainExtension<Self> + Default; + /// Find the author of the current block. + type FindAuthor: FindAuthor<Self::AccountId>; + /// The amount of balance a caller has to pay for each byte of storage. /// /// # Note @@ -226,9 +209,8 @@ pub mod pallet { type DepositPerItem: Get<BalanceOf<Self>>; /// 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. + /// Instantiating a contract, 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<Perbill>; @@ -268,12 +250,6 @@ pub mod pallet { #[pallet::no_default_bounds] type InstantiateOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>; - /// 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<Self>; - /// A type that exposes XCM APIs, allowing contracts to interact with other parachains, and /// execute XCM programs. #[pallet::no_default_bounds] @@ -308,6 +284,11 @@ pub mod pallet { /// The ratio between the decimal representation of the native token and the ETH token. #[pallet::constant] type NativeToEthRatio: Get<u32>; + + /// Encode and decode Ethereum gas values. + /// Only valid value is `()`. See [`GasEncoder`]. + #[pallet::no_default_bounds] + type EthGasEncoder: GasEncoder<BalanceOf<Self>>; } /// Container for different types that implement [`DefaultConfig`]` of this pallet. @@ -375,36 +356,18 @@ pub mod pallet { type InstantiateOrigin = EnsureSigned<AccountId>; type WeightInfo = (); type WeightPrice = Self; - type Debug = (); type Xcm = (); type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; type ChainId = ConstU64<0>; type NativeToEthRatio = ConstU32<1>; + type EthGasEncoder = (); + type FindAuthor = (); } } #[pallet::event] pub enum Event<T: Config> { - /// Contract deployed by address at the specified address. - Instantiated { deployer: H160, contract: H160 }, - - /// 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: H160, - /// The account that received the contracts remaining balance - beneficiary: H160, - }, - - /// Code with the specified hash has been stored. - CodeStored { code_hash: H256, deposit_held: BalanceOf<T>, uploader: H160 }, - /// A custom event emitted by the contract. ContractEmitted { /// The contract that emitted the event. @@ -416,54 +379,6 @@ pub mod pallet { /// Number of topics is capped by [`limits::NUM_EVENT_TOPICS`]. topics: Vec<H256>, }, - - /// A code with the specified hash was removed. - CodeRemoved { code_hash: H256, deposit_released: BalanceOf<T>, remover: H160 }, - - /// A contract's code was updated. - ContractCodeUpdated { - /// The contract that has been updated. - contract: H160, - /// New code hash that was set for the contract. - new_code_hash: H256, - /// Previous code hash of the contract. - old_code_hash: H256, - }, - - /// 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<T>, - /// The contract that was called. - contract: H160, - }, - - /// 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: H160, - /// The code hash that was delegate called. - code_hash: H256, - }, - - /// Some funds have been transferred and held as storage deposit. - StorageDepositTransferredAndHeld { from: H160, to: H160, amount: BalanceOf<T> }, - - /// Some storage deposit funds have been transferred and released. - StorageDepositTransferredAndReleased { from: H160, to: H160, amount: BalanceOf<T> }, } #[pallet::error] @@ -573,6 +488,10 @@ pub mod pallet { AccountUnmapped, /// Tried to map an account that is already mapped. AccountAlreadyMapped, + /// The transaction used to dry-run a contract is invalid. + InvalidGenericTransaction, + /// The refcount of a code either over or underflowed. + RefcountOverOrUnderflow, } /// A reason for the pallet contracts placing a hold on funds. @@ -623,14 +542,6 @@ pub mod pallet { #[pallet::storage] pub(crate) type AddressSuffix<T: Config> = StorageMap<_, Identity, H160, [u8; 12]>; - #[pallet::extra_constants] - impl<T: Config> Pallet<T> { - #[pallet::constant_name(ApiVersion)] - fn api_version() -> u16 { - API_VERSION - } - } - #[pallet::hooks] impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { fn on_idle(_block: BlockNumberFor<T>, limit: Weight) -> Weight { @@ -782,12 +693,7 @@ pub mod pallet { #[allow(unused_variables)] #[pallet::call_index(0)] #[pallet::weight(Weight::MAX)] - pub fn eth_transact( - origin: OriginFor<T>, - payload: Vec<u8>, - gas_limit: Weight, - #[pallet::compact] storage_deposit_limit: BalanceOf<T>, - ) -> DispatchResultWithPostInfo { + pub fn eth_transact(origin: OriginFor<T>, payload: Vec<u8>) -> DispatchResultWithPostInfo { Err(frame_system::Error::CallFiltered::<T>.into()) } @@ -823,11 +729,10 @@ pub mod pallet { dest, value, gas_limit, - storage_deposit_limit, + DepositLimit::Balance(storage_deposit_limit), data, - DebugInfo::Skip, - CollectEvents::Skip, ); + if let Ok(return_value) = &output.result { if return_value.did_revert() { output.result = Err(<Error<T>>::ContractReverted.into()); @@ -859,12 +764,10 @@ pub mod pallet { origin, value, gas_limit, - storage_deposit_limit, + DepositLimit::Balance(storage_deposit_limit), Code::Existing(code_hash), data, salt, - DebugInfo::Skip, - CollectEvents::Skip, ); if let Ok(retval) = &output.result { if retval.result.did_revert() { @@ -925,12 +828,10 @@ pub mod pallet { origin, value, gas_limit, - storage_deposit_limit, + DepositLimit::Balance(storage_deposit_limit), Code::Upload(code), data, salt, - DebugInfo::Skip, - CollectEvents::Skip, ); if let Ok(retval) = &output.result { if retval.result.did_revert() { @@ -1006,13 +907,8 @@ pub mod pallet { } else { return Err(<Error<T>>::ContractNotFound.into()); }; - <ExecStack<T, WasmBlob<T>>>::increment_refcount(code_hash)?; - <ExecStack<T, WasmBlob<T>>>::decrement_refcount(contract.code_hash); - Self::deposit_event(Event::ContractCodeUpdated { - contract: dest, - new_code_hash: code_hash, - old_code_hash: contract.code_hash, - }); + <CodeInfo<T>>::increment_refcount(code_hash)?; + <CodeInfo<T>>::decrement_refcount(contract.code_hash)?; contract.code_hash = code_hash; Ok(()) }) @@ -1083,7 +979,7 @@ fn dispatch_result<R>( impl<T: Config> Pallet<T> where - BalanceOf<T>: Into<U256> + TryFrom<U256>, + BalanceOf<T>: Into<U256> + TryFrom<U256> + Bounded, MomentOf<T>: Into<U256>, T::Hash: frame_support::traits::IsType<H256>, { @@ -1098,21 +994,18 @@ where dest: H160, value: BalanceOf<T>, gas_limit: Weight, - storage_deposit_limit: BalanceOf<T>, + storage_deposit_limit: DepositLimit<BalanceOf<T>>, data: Vec<u8>, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> ContractResult<ExecReturnValue, BalanceOf<T>, EventRecordOf<T>> { + ) -> ContractResult<ExecReturnValue, BalanceOf<T>> { 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 = || { let origin = Origin::from_runtime_origin(origin)?; - let mut storage_meter = StorageMeter::new(&origin, storage_deposit_limit, value)?; + let mut storage_meter = match storage_deposit_limit { + DepositLimit::Balance(limit) => StorageMeter::new(&origin, limit, value)?, + DepositLimit::Unchecked => StorageMeter::new_unchecked(BalanceOf::<T>::max_value()), + }; let result = ExecStack::<T, WasmBlob<T>>::run_call( origin.clone(), dest, @@ -1120,24 +1013,21 @@ where &mut storage_meter, Self::convert_native_to_evm(value), data, - debug_message.as_mut(), + storage_deposit_limit.is_unchecked(), )?; - storage_deposit = storage_meter.try_into_deposit(&origin)?; + storage_deposit = storage_meter + .try_into_deposit(&origin, storage_deposit_limit.is_unchecked()) + .inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to transfer deposit: {err:?}"); + })?; Ok(result) }; let result = Self::run_guarded(try_call); - let events = if matches!(collect_events, CollectEvents::UnsafeCollect) { - Some(System::<T>::read_events_no_consensus().map(|e| *e).collect()) - } else { - None - }; ContractResult { 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, } } @@ -1145,30 +1035,35 @@ where /// /// 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). + /// more information to the caller useful to estimate the cost of the operation. pub fn bare_instantiate( origin: OriginFor<T>, value: BalanceOf<T>, gas_limit: Weight, - mut storage_deposit_limit: BalanceOf<T>, + storage_deposit_limit: DepositLimit<BalanceOf<T>>, code: Code, data: Vec<u8>, salt: Option<[u8; 32]>, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> ContractResult<InstantiateReturnValue, BalanceOf<T>, EventRecordOf<T>> { + ) -> ContractResult<InstantiateReturnValue, BalanceOf<T>> { 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 unchecked_deposit_limit = storage_deposit_limit.is_unchecked(); + let mut storage_deposit_limit = match storage_deposit_limit { + DepositLimit::Balance(limit) => limit, + DepositLimit::Unchecked => BalanceOf::<T>::max_value(), + }; + let try_instantiate = || { 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)?; + let (executable, upload_deposit) = Self::try_upload_code( + upload_account, + code, + storage_deposit_limit, + unchecked_deposit_limit, + )?; storage_deposit_limit.saturating_reduce(upload_deposit); (executable, upload_deposit) }, @@ -1176,8 +1071,12 @@ where (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 mut storage_meter = if unchecked_deposit_limit { + StorageMeter::new_unchecked(storage_deposit_limit) + } else { + StorageMeter::new(&instantiate_origin, storage_deposit_limit, value)? + }; + let result = ExecStack::<T, WasmBlob<T>>::run_instantiate( instantiate_account, executable, @@ -1186,19 +1085,14 @@ where Self::convert_native_to_evm(value), data, salt.as_ref(), - debug_message.as_mut(), + unchecked_deposit_limit, ); storage_deposit = storage_meter - .try_into_deposit(&instantiate_origin)? + .try_into_deposit(&instantiate_origin, unchecked_deposit_limit)? .saturating_add(&StorageDeposit::Charge(upload_deposit)); result }; let output = Self::run_guarded(try_instantiate); - let events = if matches!(collect_events, CollectEvents::UnsafeCollect) { - Some(System::<T>::read_events_no_consensus().map(|e| *e).collect()) - } else { - None - }; ContractResult { result: output .map(|(addr, result)| InstantiateReturnValue { result, addr }) @@ -1206,8 +1100,6 @@ where gas_consumed: gas_meter.gas_consumed(), gas_required: gas_meter.gas_required(), storage_deposit, - debug_message: debug_message.unwrap_or_default().to_vec(), - events, } } @@ -1215,58 +1107,75 @@ where /// /// # Parameters /// - /// - `origin`: The origin of the call. - /// - `dest`: The destination address of the call. - /// - `value`: The EVM value to transfer. - /// - `input`: The input data. + /// - `tx`: The Ethereum transaction to simulate. /// - `gas_limit`: The gas limit enforced during contract execution. - /// - `storage_deposit_limit`: The maximum balance that can be charged to the caller for storage - /// usage. - /// - `utx_encoded_size`: A function that takes a call and returns the encoded size of the - /// unchecked extrinsic. - /// - `debug`: Debugging configuration. - /// - `collect_events`: Event collection configuration. + /// - `tx_fee`: A function that returns the fee for the given call and dispatch info. pub fn bare_eth_transact( - origin: T::AccountId, - dest: Option<H160>, - value: U256, - input: Vec<u8>, + mut tx: GenericTransaction, gas_limit: Weight, - storage_deposit_limit: BalanceOf<T>, - utx_encoded_size: impl Fn(Call<T>) -> u32, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> EthContractResult<BalanceOf<T>> + tx_fee: impl Fn(Call<T>, DispatchInfo) -> BalanceOf<T>, + ) -> Result<EthTransactInfo<BalanceOf<T>>, EthTransactError> where - T: pallet_transaction_payment::Config, <T as frame_system::Config>::RuntimeCall: Dispatchable<Info = frame_support::dispatch::DispatchInfo>, <T as Config>::RuntimeCall: From<crate::Call<T>>, <T as Config>::RuntimeCall: Encode, - OnChargeTransactionBalanceOf<T>: Into<BalanceOf<T>>, T::Nonce: Into<U256>, T::Hash: frame_support::traits::IsType<H256>, { - log::debug!(target: LOG_TARGET, "bare_eth_transact: dest: {dest:?} value: {value:?} - gas_limit: {gas_limit:?} storage_deposit_limit: {storage_deposit_limit:?}"); + log::trace!(target: LOG_TARGET, "bare_eth_transact: tx: {tx:?} gas_limit: {gas_limit:?}"); + + let from = tx.from.unwrap_or_default(); + let origin = T::AddressMapper::to_account_id(&from); + + let storage_deposit_limit = if tx.gas.is_some() { + DepositLimit::Balance(BalanceOf::<T>::max_value()) + } else { + DepositLimit::Unchecked + }; - // Get the nonce to encode in the tx. - let nonce: T::Nonce = <System<T>>::account_nonce(&origin); + if tx.nonce.is_none() { + tx.nonce = Some(<System<T>>::account_nonce(&origin).into()); + } + if tx.chain_id.is_none() { + tx.chain_id = Some(T::ChainId::get().into()); + } + if tx.gas_price.is_none() { + tx.gas_price = Some(GAS_PRICE.into()); + } + if tx.gas.is_none() { + tx.gas = Some(Self::evm_block_gas_limit()); + } // Convert the value to the native balance type. - let native_value = match Self::convert_evm_to_native(value) { + let evm_value = tx.value.unwrap_or_default(); + let native_value = match Self::convert_evm_to_native(evm_value, ConversionPrecision::Exact) + { Ok(v) => v, - Err(err) => - return EthContractResult { - gas_required: Default::default(), - storage_deposit: Default::default(), - fee: Default::default(), - result: Err(err.into()), - }, + Err(_) => return Err(EthTransactError::Message("Failed to convert value".into())), + }; + + let input = tx.input.clone().unwrap_or_default().0; + + let extract_error = |err| { + if err == Error::<T>::TransferFailed.into() || + err == Error::<T>::StorageDepositNotEnoughFunds.into() || + err == Error::<T>::StorageDepositLimitExhausted.into() + { + let balance = Self::evm_balance(&from); + return Err(EthTransactError::Message( + format!("insufficient funds for gas * price + value: address {from:?} have {balance} (supplied gas {})", + tx.gas.unwrap_or_default())) + ); + } + + return Err(EthTransactError::Message(format!( + "Failed to instantiate contract: {err:?}" + ))); }; // Dry run the call - let (mut result, dispatch_info) = match dest { + let (mut result, dispatch_info) = match tx.to { // A contract call. Some(dest) => { // Dry run the call. @@ -1277,22 +1186,37 @@ where gas_limit, storage_deposit_limit, input.clone(), - debug, - collect_events, ); - let result = EthContractResult { + let data = match result.result { + Ok(return_value) => { + if return_value.did_revert() { + return Err(EthTransactError::Data(return_value.data)); + } + return_value.data + }, + Err(err) => { + log::debug!(target: LOG_TARGET, "Failed to execute call: {err:?}"); + return extract_error(err) + }, + }; + + let result = EthTransactInfo { gas_required: result.gas_required, storage_deposit: result.storage_deposit.charge_or_zero(), - result: result.result, - fee: Default::default(), + data, + eth_gas: Default::default(), }; - // Get the dispatch info of the call. + + let (gas_limit, storage_deposit_limit) = T::EthGasEncoder::as_encoded_values( + result.gas_required, + result.storage_deposit, + ); let dispatch_call: <T as Config>::RuntimeCall = crate::Call::<T>::call { dest, value: native_value, - gas_limit: result.gas_required, - storage_deposit_limit: result.storage_deposit, + gas_limit, + storage_deposit_limit, data: input.clone(), } .into(); @@ -1322,23 +1246,38 @@ where Code::Upload(code.to_vec()), data.to_vec(), None, - debug, - collect_events, ); - let result = EthContractResult { + let returned_data = match result.result { + Ok(return_value) => { + if return_value.result.did_revert() { + return Err(EthTransactError::Data(return_value.result.data)); + } + return_value.result.data + }, + Err(err) => { + log::debug!(target: LOG_TARGET, "Failed to instantiate: {err:?}"); + return extract_error(err) + }, + }; + + let result = EthTransactInfo { gas_required: result.gas_required, storage_deposit: result.storage_deposit.charge_or_zero(), - result: result.result.map(|v| v.result), - fee: Default::default(), + data: returned_data, + eth_gas: Default::default(), }; // Get the dispatch info of the call. + let (gas_limit, storage_deposit_limit) = T::EthGasEncoder::as_encoded_values( + result.gas_required, + result.storage_deposit, + ); let dispatch_call: <T as Config>::RuntimeCall = crate::Call::<T>::instantiate_with_code { value: native_value, - gas_limit: result.gas_required, - storage_deposit_limit: result.storage_deposit, + gas_limit, + storage_deposit_limit, code: code.to_vec(), data: data.to_vec(), salt: None, @@ -1348,44 +1287,20 @@ where }, }; - let mut tx = TransactionLegacyUnsigned { - value, - input: input.into(), - nonce: nonce.into(), - chain_id: Some(T::ChainId::get().into()), - gas_price: GAS_PRICE.into(), - to: dest, - ..Default::default() + let Ok(unsigned_tx) = tx.clone().try_into_unsigned() else { + return Err(EthTransactError::Message("Invalid transaction".into())); }; - // The transaction fees depend on the extrinsic's length, which in turn is influenced by - // the encoded length of the gas limit specified in the transaction (tx.gas). - // We iteratively compute the fee by adjusting tx.gas until the fee stabilizes. - // with a maximum of 3 iterations to avoid an infinite loop. - for _ in 0..3 { - let eth_dispatch_call = crate::Call::<T>::eth_transact { - payload: tx.dummy_signed_payload(), - gas_limit: result.gas_required, - storage_deposit_limit: result.storage_deposit, - }; - let encoded_len = utx_encoded_size(eth_dispatch_call); - let fee = pallet_transaction_payment::Pallet::<T>::compute_fee( - encoded_len, - &dispatch_info, - 0u32.into(), - ) - .into(); - - if fee == result.fee { - log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} fee: {fee:?}"); - break; - } - result.fee = fee; - tx.gas = (fee / GAS_PRICE.into()).into(); - log::debug!(target: LOG_TARGET, "Adjusting Eth gas to: {:?}", tx.gas); - } + let eth_dispatch_call = + crate::Call::<T>::eth_transact { payload: unsigned_tx.dummy_signed_payload() }; + let fee = tx_fee(eth_dispatch_call, dispatch_info); + let raw_gas = Self::evm_fee_to_gas(fee); + let eth_gas = + T::EthGasEncoder::encode(raw_gas, result.gas_required, result.storage_deposit); - result + log::trace!(target: LOG_TARGET, "bare_eth_call: raw_gas: {raw_gas:?} eth_gas: {eth_gas:?}"); + result.eth_gas = eth_gas; + Ok(result) } /// Get the balance with EVM decimals of the given `address`. @@ -1394,6 +1309,41 @@ where Self::convert_native_to_evm(T::Currency::reducible_balance(&account, Preserve, Polite)) } + /// Convert a substrate fee into a gas value, using the fixed `GAS_PRICE`. + /// The gas is calculated as `fee / GAS_PRICE`, rounded up to the nearest integer. + pub fn evm_fee_to_gas(fee: BalanceOf<T>) -> U256 { + let fee = Self::convert_native_to_evm(fee); + let gas_price = GAS_PRICE.into(); + let (quotient, remainder) = fee.div_mod(gas_price); + if remainder.is_zero() { + quotient + } else { + quotient + U256::one() + } + } + + /// Convert a gas value into a substrate fee + fn evm_gas_to_fee(gas: U256, gas_price: U256) -> Result<BalanceOf<T>, Error<T>> { + let fee = gas.saturating_mul(gas_price); + Self::convert_evm_to_native(fee, ConversionPrecision::RoundUp) + } + + /// Get the block gas limit. + pub fn evm_block_gas_limit() -> U256 { + let max_block_weight = T::BlockWeights::get() + .get(DispatchClass::Normal) + .max_total + .unwrap_or_else(|| T::BlockWeights::get().max_block); + + let fee = T::WeightPrice::convert(max_block_weight); + Self::evm_fee_to_gas(fee) + } + + /// Get the gas price. + pub fn evm_gas_price() -> U256 { + GAS_PRICE.into() + } + /// A generalized version of [`Self::upload_code`]. /// /// It is identical to [`Self::upload_code`] and only differs in the information it returns. @@ -1403,7 +1353,7 @@ where storage_deposit_limit: BalanceOf<T>, ) -> CodeUploadResult<BalanceOf<T>> { let origin = T::UploadOrigin::ensure_origin(origin)?; - let (module, deposit) = Self::try_upload_code(origin, code, storage_deposit_limit)?; + let (module, deposit) = Self::try_upload_code(origin, code, storage_deposit_limit, false)?; Ok(CodeUploadReturnValue { code_hash: *module.code_hash(), deposit }) } @@ -1421,9 +1371,10 @@ where origin: T::AccountId, code: Vec<u8>, storage_deposit_limit: BalanceOf<T>, + skip_transfer: bool, ) -> Result<(WasmBlob<T>, BalanceOf<T>), DispatchError> { let mut module = WasmBlob::from_code(code, origin)?; - let deposit = module.store_code()?; + let deposit = module.store_code(skip_transfer)?; ensure!(storage_deposit_limit >= deposit, <Error<T>>::StorageDepositLimitExhausted); Ok((module, deposit)) } @@ -1453,16 +1404,22 @@ where } /// Convert an EVM balance to a native balance. - fn convert_evm_to_native(value: U256) -> Result<BalanceOf<T>, Error<T>> { + fn convert_evm_to_native( + value: U256, + precision: ConversionPrecision, + ) -> Result<BalanceOf<T>, Error<T>> { if value.is_zero() { return Ok(Zero::zero()) } - let ratio = T::NativeToEthRatio::get().into(); - let res = value.checked_div(ratio).expect("divisor is non-zero; qed"); - if res.saturating_mul(ratio) == value { - res.try_into().map_err(|_| Error::<T>::BalanceConversionFailed) - } else { - Err(Error::<T>::DecimalPrecisionLoss) + + let (quotient, remainder) = value.div_mod(T::NativeToEthRatio::get().into()); + match (precision, remainder.is_zero()) { + (ConversionPrecision::Exact, false) => Err(Error::<T>::DecimalPrecisionLoss), + (_, true) => quotient.try_into().map_err(|_| Error::<T>::BalanceConversionFailed), + (_, false) => quotient + .saturating_add(U256::one()) + .try_into() + .map_err(|_| Error::<T>::BalanceConversionFailed), } } } @@ -1485,16 +1442,21 @@ environmental!(executing_contract: bool); sp_api::decl_runtime_apis! { /// The API used to dry-run contract interactions. #[api_version(1)] - pub trait ReviveApi<AccountId, Balance, Nonce, BlockNumber, EventRecord> where + pub trait ReviveApi<AccountId, Balance, Nonce, BlockNumber> where AccountId: Codec, Balance: Codec, Nonce: Codec, BlockNumber: Codec, - EventRecord: Codec, { + /// Returns the block gas limit. + fn block_gas_limit() -> U256; + /// Returns the free balance of the given `[H160]` address, using EVM decimals. fn balance(address: H160) -> U256; + /// Returns the gas price. + fn gas_price() -> U256; + /// Returns the nonce of the given `[H160]` address. fn nonce(address: H160) -> Nonce; @@ -1508,7 +1470,7 @@ sp_api::decl_runtime_apis! { gas_limit: Option<Weight>, storage_deposit_limit: Option<Balance>, input_data: Vec<u8>, - ) -> ContractResult<ExecReturnValue, Balance, EventRecord>; + ) -> ContractResult<ExecReturnValue, Balance>; /// Instantiate a new contract. /// @@ -1521,20 +1483,13 @@ sp_api::decl_runtime_apis! { code: Code, data: Vec<u8>, salt: Option<[u8; 32]>, - ) -> ContractResult<InstantiateReturnValue, Balance, EventRecord>; + ) -> ContractResult<InstantiateReturnValue, Balance>; /// Perform an Ethereum call. /// /// See [`crate::Pallet::bare_eth_transact`] - fn eth_transact( - origin: H160, - dest: Option<H160>, - value: U256, - input: Vec<u8>, - gas_limit: Option<Weight>, - storage_deposit_limit: Option<Balance>, - ) -> EthContractResult<Balance>; + fn eth_transact(tx: GenericTransaction) -> Result<EthTransactInfo<Balance>, EthTransactError>; /// Upload new code without instantiating a contract from it. /// diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs index 64e66382b9ab26366cc594814b53501f45fc006e..a4060cf6cc91cf030a869af8abd0bc608b2055ac 100644 --- a/substrate/frame/revive/src/limits.rs +++ b/substrate/frame/revive/src/limits.rs @@ -43,11 +43,8 @@ pub const CALL_STACK_DEPTH: u32 = 5; /// 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; - /// Maximum size of events (including topics) and storage values. -pub const PAYLOAD_BYTES: u32 = 512; +pub const PAYLOAD_BYTES: u32 = 416; /// The maximum size of the transient storage in bytes. /// @@ -57,11 +54,6 @@ 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; - /// The page size in which PolkaVM should allocate memory chunks. pub const PAGE_SIZE: u32 = 4 * 1024; @@ -116,7 +108,10 @@ pub mod code { const BASIC_BLOCK_SIZE: u32 = 1000; /// Make sure that the various program parts are within the defined limits. - pub fn enforce<T: Config>(blob: Vec<u8>) -> Result<CodeVec, DispatchError> { + pub fn enforce<T: Config>( + blob: Vec<u8>, + available_syscalls: &[&[u8]], + ) -> Result<CodeVec, DispatchError> { fn round_page(n: u32) -> u64 { // performing the rounding in u64 in order to prevent overflow u64::from(n).next_multiple_of(PAGE_SIZE.into()) @@ -129,23 +124,56 @@ pub mod code { Error::<T>::CodeRejected })?; + if !program.is_64_bit() { + log::debug!(target: LOG_TARGET, "32bit programs are not supported."); + Err(Error::<T>::CodeRejected)?; + } + + // Need to check that no non-existent syscalls are used. This allows us to add + // new syscalls later without affecting already deployed code. + for (idx, import) in program.imports().iter().enumerate() { + // We are being defensive in case an attacker is able to somehow include + // a lot of imports. This is important because we search the array of host + // functions for every import. + if idx == available_syscalls.len() { + log::debug!(target: LOG_TARGET, "Program contains too many imports."); + Err(Error::<T>::CodeRejected)?; + } + let Some(import) = import else { + log::debug!(target: LOG_TARGET, "Program contains malformed import."); + return Err(Error::<T>::CodeRejected.into()); + }; + if !available_syscalls.contains(&import.as_bytes()) { + log::debug!(target: LOG_TARGET, "Program references unknown syscall: {}", import); + Err(Error::<T>::CodeRejected)?; + } + } + // This scans the whole program but we only do it once on code deployment. // It is safe to do unchecked math in u32 because the size of the program // was already checked above. - use polkavm::program::ISA32_V1_NoSbrk as ISA; + use polkavm::program::ISA64_V1 as ISA; let mut num_instructions: u32 = 0; let mut max_basic_block_size: u32 = 0; let mut basic_block_size: u32 = 0; for inst in program.instructions(ISA) { + use polkavm::program::Instruction; num_instructions += 1; basic_block_size += 1; if inst.kind.opcode().starts_new_basic_block() { max_basic_block_size = max_basic_block_size.max(basic_block_size); basic_block_size = 0; } - if matches!(inst.kind, polkavm::program::Instruction::invalid) { - log::debug!(target: LOG_TARGET, "invalid instruction at offset {}", inst.offset); - return Err(<Error<T>>::InvalidInstruction.into()) + match inst.kind { + Instruction::invalid => { + log::debug!(target: LOG_TARGET, "invalid instruction at offset {}", inst.offset); + return Err(<Error<T>>::InvalidInstruction.into()) + }, + Instruction::sbrk(_, _) => { + log::debug!(target: LOG_TARGET, "sbrk instruction is not allowed. offset {}", inst.offset); + return Err(<Error<T>>::InvalidInstruction.into()) + }, + _ => (), } } diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 024b1f3448e12802f22a08ca66a72c971e496178..e2900bd027b694fac01e76f2d277a1166663783e 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -17,8 +17,8 @@ //! A crate that hosts a common definitions that are relevant for the pallet-revive. -use crate::H160; -use alloc::vec::Vec; +use crate::{H160, U256}; +use alloc::{string::String, vec::Vec}; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::weights::Weight; use pallet_revive_uapi::ReturnFlags; @@ -28,6 +28,30 @@ use sp_runtime::{ DispatchError, RuntimeDebug, }; +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum DepositLimit<Balance> { + /// Allows bypassing all balance transfer checks. + Unchecked, + + /// Specifies a maximum allowable balance for a deposit. + Balance(Balance), +} + +impl<T> DepositLimit<T> { + pub fn is_unchecked(&self) -> bool { + match self { + Self::Unchecked => true, + _ => false, + } + } +} + +impl<T> From<T> for DepositLimit<T> { + fn from(value: T) -> Self { + Self::Balance(value) + } +} + /// Result type of a `bare_call` or `bare_instantiate` call as well as `ContractsApi::call` and /// `ContractsApi::instantiate`. /// @@ -39,7 +63,7 @@ use sp_runtime::{ /// `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<R, Balance, EventRecord> { +pub struct ContractResult<R, Balance> { /// 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. @@ -48,7 +72,7 @@ pub struct ContractResult<R, Balance, EventRecord> { /// /// # Note /// - /// This can only different from [`Self::gas_consumed`] when weight pre charging + /// This can only be 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. @@ -60,39 +84,36 @@ pub struct ContractResult<R, Balance, EventRecord> { /// is `Err`. This is because on error all storage changes are rolled back including the /// payment of the deposit. pub storage_deposit: StorageDeposit<Balance>, - /// 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<u8>, /// The execution result of the wasm code. pub result: Result<R, DispatchError>, - /// The events that were emitted during execution. It is an option as event collection is - /// optional. - pub events: Option<Vec<EventRecord>>, } /// The result of the execution of a `eth_transact` call. #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct EthContractResult<Balance, R = Result<ExecReturnValue, DispatchError>> { - /// The fee charged for the execution. - pub fee: Balance, +pub struct EthTransactInfo<Balance> { /// The amount of gas that was necessary to execute the transaction. pub gas_required: Weight, /// Storage deposit charged. pub storage_deposit: Balance, - /// The execution result. - pub result: R, + /// The weight and deposit equivalent in EVM Gas. + pub eth_gas: U256, + /// The execution return value. + pub data: Vec<u8>, +} + +/// Error type of a `eth_transact` call. +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum EthTransactError { + Data(Vec<u8>), + Message(String), +} + +/// Precision used for converting between Native and EVM balances. +pub enum ConversionPrecision { + /// Exact conversion without any rounding. + Exact, + /// Conversion that rounds up to the nearest whole number. + RoundUp, } /// Result type of a `bare_code_upload` call. @@ -253,36 +274,3 @@ where } } } - -/// 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 index b7156588d44c659243e3bde3a2b8d19968ebe926..a761223aadfdde5e9f8936860d2aa25ba04cfb40 100644 --- a/substrate/frame/revive/src/storage.rs +++ b/substrate/frame/revive/src/storage.rs @@ -22,11 +22,10 @@ pub mod meter; use crate::{ address::AddressMapper, exec::{AccountIdOf, Key}, - limits, storage::meter::Diff, weights::WeightInfo, - BalanceOf, CodeInfo, Config, ContractInfoOf, DeletionQueue, DeletionQueueCounter, Error, - StorageDeposit, TrieId, SENTINEL, + BalanceOf, Config, ContractInfoOf, DeletionQueue, DeletionQueueCounter, Error, TrieId, + SENTINEL, }; use alloc::vec::Vec; use codec::{Decode, Encode, MaxEncodedLen}; @@ -36,18 +35,14 @@ use frame_support::{ weights::{Weight, WeightMeter}, CloneNoBound, DefaultNoBound, }; -use meter::DepositOf; use scale_info::TypeInfo; -use sp_core::{ConstU32, Get, H160}; +use sp_core::{Get, H160}; use sp_io::KillStorageResult; use sp_runtime::{ traits::{Hash, Saturating, Zero}, - BoundedBTreeMap, DispatchError, DispatchResult, RuntimeDebug, + DispatchError, RuntimeDebug, }; -type DelegateDependencyMap<T> = - BoundedBTreeMap<sp_core::H256, BalanceOf<T>, 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)] @@ -70,12 +65,6 @@ pub struct ContractInfo<T: Config> { /// 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<T>, - /// 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<T>, /// The size of the immutable data of this contract. immutable_data_len: u32, } @@ -110,18 +99,12 @@ impl<T: Config> ContractInfo<T> { storage_byte_deposit: Zero::zero(), storage_item_deposit: Zero::zero(), storage_base_deposit: Zero::zero(), - delegate_dependencies: Default::default(), immutable_data_len: 0, }; 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()) @@ -240,58 +223,27 @@ impl<T: Config> ContractInfo<T> { /// 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<T>) -> BalanceOf<T> { - let info_deposit = - Diff { bytes_added: self.encoded_size() as u32, items_added: 1, ..Default::default() } - .update_contract::<T>(None) - .charge_or_zero(); + /// the deposit paid to upload the contract's code. It also depends on the size of immutable + /// storage which is also changed when the code hash of a contract is changed. + pub fn update_base_deposit(&mut self, code_deposit: BalanceOf<T>) -> BalanceOf<T> { + let contract_deposit = Diff { + bytes_added: (self.encoded_size() as u32).saturating_add(self.immutable_data_len), + items_added: if self.immutable_data_len == 0 { 1 } else { 2 }, + ..Default::default() + } + .update_contract::<T>(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 code_deposit = T::CodeHashLockupDepositPercent::get().mul_ceil(code_deposit); - let deposit = info_deposit.saturating_add(upload_deposit); + let deposit = contract_deposit.saturating_add(code_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: sp_core::H256, - amount: BalanceOf<T>, - ) -> DispatchResult { - self.delegate_dependencies - .try_insert(code_hash, amount) - .map_err(|_| Error::<T>::MaxDelegateDependenciesReached)? - .map_or(Ok(()), |_| Err(Error::<T>::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: &sp_core::H256, - ) -> Result<BalanceOf<T>, DispatchError> { - self.delegate_dependencies - .remove(code_hash) - .ok_or(Error::<T>::DelegateDependencyNotFound.into()) - } - - /// Returns the delegate_dependencies of the contract. - pub fn delegate_dependencies(&self) -> &DelegateDependencyMap<T> { - &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. @@ -367,27 +319,8 @@ impl<T: Config> ContractInfo<T> { } /// Set the number of immutable bytes of this contract. - /// - /// On success, returns the storage deposit to be charged. - /// - /// Returns `Err(InvalidImmutableAccess)` if: - /// - The immutable bytes of this contract are not 0. This indicates that the immutable data - /// have already been set; it is only valid to set the immutable data exactly once. - /// - The provided `immutable_data_len` value was 0; it is invalid to set empty immutable data. - pub fn set_immutable_data_len( - &mut self, - immutable_data_len: u32, - ) -> Result<DepositOf<T>, DispatchError> { - if self.immutable_data_len != 0 || immutable_data_len == 0 { - return Err(Error::<T>::InvalidImmutableAccess.into()); - } - + pub fn set_immutable_data_len(&mut self, immutable_data_len: u32) { self.immutable_data_len = immutable_data_len; - - let amount = T::DepositPerByte::get() - .saturating_mul(immutable_data_len.into()) - .saturating_add(T::DepositPerItem::get()); - Ok(StorageDeposit::Charge(amount)) } } diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs index 712010bc8257454defa0ad81f466fbddc5822a9a..ddd4a3bae87f0d65614132fde12cd5ee4f301505 100644 --- a/substrate/frame/revive/src/storage/meter.rs +++ b/substrate/frame/revive/src/storage/meter.rs @@ -18,8 +18,8 @@ //! This module contains functions to meter the storage deposit. use crate::{ - address::AddressMapper, storage::ContractInfo, AccountIdOf, BalanceOf, CodeInfo, Config, Error, - Event, HoldReason, Inspect, Origin, Pallet, StorageDeposit as Deposit, System, LOG_TARGET, + storage::ContractInfo, AccountIdOf, BalanceOf, Config, Error, HoldReason, Inspect, Origin, + StorageDeposit as Deposit, System, LOG_TARGET, }; use alloc::vec::Vec; use core::{fmt::Debug, marker::PhantomData}; @@ -101,12 +101,8 @@ 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, -} +#[derive(Default, Debug)] +pub struct Nested; impl State for Root {} impl State for Nested {} @@ -125,10 +121,8 @@ pub struct RawMeter<T: Config, E, S: State + Default + Debug> { /// We only have one charge per contract hence the size of this vector is /// limited by the maximum call depth. charges: Vec<Charge<T>>, - /// 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<E>, + _phantom: PhantomData<(E, S)>, } /// This type is used to describe a storage change when charging from the meter. @@ -281,21 +275,14 @@ where 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<T>) -> RawMeter<T, E, Nested> { 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() } - } + + RawMeter { limit: self.available().min(limit), ..Default::default() } } /// Absorb a child that was spawned to handle a sub call. @@ -373,24 +360,36 @@ where } } + /// Create new storage meter without checking the limit. + pub fn new_unchecked(limit: BalanceOf<T>) -> Self { + return 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<T>) -> Result<DepositOf<T>, 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)?; + pub fn try_into_deposit( + self, + origin: &Origin<T>, + skip_transfer: bool, + ) -> Result<DepositOf<T>, DispatchError> { + if !skip_transfer { + // 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) } } @@ -405,44 +404,26 @@ impl<T: Config, E: Ext<T>> RawMeter<T, E, Nested> { }; } - /// Adds a deposit charge. + /// Adds a charge without recording it in the contract info. /// /// 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. + /// change within the contract's child trie. This is the case when 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. + /// + /// If this functions is used the amount of the charge has to be stored by the caller somewhere + /// alese in order to be able to refund it. pub fn charge_deposit(&mut self, contract: T::AccountId, amount: DepositOf<T>) { - self.total_deposit = self.total_deposit.saturating_add(&amount); + self.record_charge(&amount); self.charges.push(Charge { contract, amount, state: ContractState::Alive }); } - /// Charges from `origin` a storage deposit for contract instantiation. + /// Record a charge that has taken place externally. /// - /// 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<T>, - code_info: &CodeInfo<T>, - ) -> Result<(), DispatchError> { - debug_assert!(matches!(self.contract_state(), ContractState::Alive)); - - // We need to make sure that the contract's account exists. - let ed = Pallet::<T>::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::<T>::inc_consumers(contract)?; - - let deposit = contract_info.update_base_deposit(&code_info); - let deposit = Deposit::Charge(deposit); - - self.charge_deposit(contract.clone(), deposit); - Ok(()) + /// This will not perform a charge. It just records it to reflect it in the + /// total amount of storage required for a transaction. + pub fn record_charge(&mut self, amount: &DepositOf<T>) { + self.total_deposit = self.total_deposit.saturating_add(&amount); } /// Call to tell the meter that the currently executing contract was terminated. @@ -460,13 +441,6 @@ impl<T: Config, E: Ext<T>> RawMeter<T, E, Nested> { /// [`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<T>>, @@ -479,23 +453,12 @@ impl<T: Config, E: Ext<T>> RawMeter<T, E, Nested> { } if let Deposit::Charge(amount) = total_deposit { if amount > self.limit { + log::debug!( target: LOG_TARGET, "Storage deposit limit exhausted: {:?} > {:?}", amount, self.limit); return Err(<Error<T>>::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<T>>, - ) -> Result<(), DispatchError> { - match self.nested { - Nested::OwnLimit => self.enforce_limit(info), - Nested::DerivedLimit => Ok(()), - } - } } impl<T: Config> Ext<T> for ReservingExt { @@ -530,12 +493,6 @@ impl<T: Config> Ext<T> for ReservingExt { Preservation::Preserve, Fortitude::Polite, )?; - - Pallet::<T>::deposit_event(Event::StorageDepositTransferredAndHeld { - from: T::AddressMapper::to_address(origin), - to: T::AddressMapper::to_address(contract), - amount: *amount, - }); }, Deposit::Refund(amount) => { let transferred = T::Currency::transfer_on_hold( @@ -548,12 +505,6 @@ impl<T: Config> Ext<T> for ReservingExt { Fortitude::Polite, )?; - Pallet::<T>::deposit_event(Event::StorageDepositTransferredAndReleased { - from: T::AddressMapper::to_address(contract), - to: T::AddressMapper::to_address(origin), - 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 @@ -686,7 +637,6 @@ mod tests { storage_byte_deposit: info.bytes_deposit, storage_item_deposit: info.items_deposit, storage_base_deposit: Default::default(), - delegate_dependencies: Default::default(), immutable_data_len: info.immutable_data_len, } } @@ -706,6 +656,49 @@ mod tests { ) } + /// Previously, passing a limit of 0 meant unlimited storage for a nested call. + /// + /// Now, a limit of 0 means the subcall will not be able to use any storage. + #[test] + fn nested_zero_limit_requested() { + clear_ext(); + + let meter = TestMeter::new(&Origin::from_account_id(ALICE), 1_000, 0).unwrap(); + assert_eq!(meter.available(), 1_000); + let nested0 = meter.nested(BalanceOf::<Test>::zero()); + assert_eq!(nested0.available(), 0); + } + + #[test] + fn nested_some_limit_requested() { + clear_ext(); + + let meter = TestMeter::new(&Origin::from_account_id(ALICE), 1_000, 0).unwrap(); + assert_eq!(meter.available(), 1_000); + let nested0 = meter.nested(500); + assert_eq!(nested0.available(), 500); + } + + #[test] + fn nested_all_limit_requested() { + clear_ext(); + + let meter = TestMeter::new(&Origin::from_account_id(ALICE), 1_000, 0).unwrap(); + assert_eq!(meter.available(), 1_000); + let nested0 = meter.nested(1_000); + assert_eq!(nested0.available(), 1_000); + } + + #[test] + fn nested_over_limit_requested() { + clear_ext(); + + let meter = TestMeter::new(&Origin::from_account_id(ALICE), 1_000, 0).unwrap(); + assert_eq!(meter.available(), 1_000); + let nested0 = meter.nested(2_000); + assert_eq!(nested0.available(), 1_000); + } + #[test] fn empty_charge_works() { clear_ext(); @@ -811,7 +804,10 @@ mod tests { 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!( + meter.try_into_deposit(&test_case.origin, false).unwrap(), + test_case.deposit + ); assert_eq!(nested0_info.extra_deposit(), 112); assert_eq!(nested1_info.extra_deposit(), 110); @@ -858,7 +854,7 @@ mod tests { let mut meter = TestMeter::new(&test_case.origin, 1_000, 0).unwrap(); assert_eq!(meter.available(), 1_000); - let mut nested0 = meter.nested(BalanceOf::<Test>::zero()); + let mut nested0 = meter.nested(BalanceOf::<Test>::max_value()); nested0.charge(&Diff { bytes_added: 5, bytes_removed: 1, @@ -874,7 +870,7 @@ mod tests { items_deposit: 20, immutable_data_len: 0, }); - let mut nested1 = nested0.nested(BalanceOf::<Test>::zero()); + let mut nested1 = nested0.nested(BalanceOf::<Test>::max_value()); nested1.charge(&Diff { items_removed: 5, ..Default::default() }); nested1.charge(&Diff { bytes_added: 20, ..Default::default() }); nested1.terminate(&nested1_info, CHARLIE); @@ -882,7 +878,10 @@ mod tests { 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!( + meter.try_into_deposit(&test_case.origin, false).unwrap(), + test_case.deposit + ); assert_eq!(TestExtTestValue::get(), test_case.expected) } } diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs index e64f588944326720af153920c86baa1e59961790..7fbb5b676439e6c378e3b51683bac449513faca5 100644 --- a/substrate/frame/revive/src/test_utils/builder.rs +++ b/substrate/frame/revive/src/test_utils/builder.rs @@ -17,8 +17,8 @@ use super::{deposit_limit, GAS_LIMIT}; use crate::{ - address::AddressMapper, AccountIdOf, BalanceOf, Code, CollectEvents, Config, ContractResult, - DebugInfo, EventRecordOf, ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight, + address::AddressMapper, AccountIdOf, BalanceOf, Code, Config, ContractResult, DepositLimit, + ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight, }; use frame_support::pallet_prelude::DispatchResultWithPostInfo; use paste::paste; @@ -133,13 +133,11 @@ builder!( origin: OriginFor<T>, value: BalanceOf<T>, gas_limit: Weight, - storage_deposit_limit: BalanceOf<T>, + storage_deposit_limit: DepositLimit<BalanceOf<T>>, code: Code, data: Vec<u8>, salt: Option<[u8; 32]>, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> ContractResult<InstantiateReturnValue, BalanceOf<T>, EventRecordOf<T>>; + ) -> ContractResult<InstantiateReturnValue, BalanceOf<T>>; /// Build the instantiate call and unwrap the result. pub fn build_and_unwrap_result(self) -> InstantiateReturnValue { @@ -159,12 +157,10 @@ builder!( origin, value: 0u32.into(), gas_limit: GAS_LIMIT, - storage_deposit_limit: deposit_limit::<T>(), + storage_deposit_limit: DepositLimit::Balance(deposit_limit::<T>()), code, data: vec![], salt: Some([0; 32]), - debug: DebugInfo::UnsafeDebug, - collect_events: CollectEvents::Skip, } } ); @@ -198,11 +194,9 @@ builder!( dest: H160, value: BalanceOf<T>, gas_limit: Weight, - storage_deposit_limit: BalanceOf<T>, + storage_deposit_limit: DepositLimit<BalanceOf<T>>, data: Vec<u8>, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> ContractResult<ExecReturnValue, BalanceOf<T>, EventRecordOf<T>>; + ) -> ContractResult<ExecReturnValue, BalanceOf<T>>; /// Build the call and unwrap the result. pub fn build_and_unwrap_result(self) -> ExecReturnValue { @@ -216,10 +210,8 @@ builder!( dest, value: 0u32.into(), gas_limit: GAS_LIMIT, - storage_deposit_limit: deposit_limit::<T>(), + storage_deposit_limit: DepositLimit::Balance(deposit_limit::<T>()), 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 index 34afe8aabfe6f7aee9f3aed510687d2e36a2a8f0..319b35d1916bd21369664ce72ce74ae0ffcff37d 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -16,12 +16,8 @@ // limitations under the License. mod pallet_dummy; -mod test_debug; -use self::{ - test_debug::TestDebug, - test_utils::{ensure_stored, expected_deposit}, -}; +use self::test_utils::{ensure_stored, expected_deposit}; use crate::{ self as pallet_revive, address::{create1, create2, AddressMapper}, @@ -29,16 +25,17 @@ use crate::{ ChainExtension, Environment, Ext, RegisteredChainExtension, Result as ExtensionResult, RetVal, ReturnFlags, }, + evm::{runtime::GAS_PRICE, CallTrace, CallTracer, CallType, GenericTransaction}, exec::Key, limits, - primitives::CodeUploadReturnValue, storage::DeletionQueueManager, test_utils::*, tests::test_utils::{get_contract, get_contract_checked}, + tracing::trace, wasm::Memory, weights::WeightInfo, - AccountId32Mapper, BalanceOf, Code, CodeInfoOf, CollectEvents, Config, ContractInfo, - ContractInfoOf, DebugInfo, DeletionQueueCounter, Error, HoldReason, Origin, Pallet, + AccountId32Mapper, BalanceOf, Code, CodeInfoOf, Config, ContractInfo, ContractInfoOf, + DeletionQueueCounter, DepositLimit, Error, EthTransactError, HoldReason, Origin, Pallet, PristineCode, H160, }; @@ -54,12 +51,12 @@ use frame_support::{ traits::{ fungible::{BalancedHold, Inspect, Mutate, MutateHold}, tokens::Preservation, - ConstU32, ConstU64, Contains, OnIdle, OnInitialize, StorageVersion, + ConstU32, ConstU64, Contains, FindAuthor, OnIdle, OnInitialize, StorageVersion, }, weights::{constants::WEIGHT_REF_TIME_PER_SECOND, FixedFee, IdentityFee, Weight, WeightMeter}, }; use frame_system::{EventRecord, Phase}; -use pallet_revive_fixtures::{bench::dummy_unique, compile_module}; +use pallet_revive_fixtures::compile_module; use pallet_revive_uapi::ReturnErrorCode as RuntimeReturnCode; use pallet_transaction_payment::{ConstFeeMultiplier, Multiplier}; use pretty_assertions::{assert_eq, assert_ne}; @@ -102,7 +99,7 @@ macro_rules! assert_refcount { } pub mod test_utils { - use super::{Contracts, DepositPerByte, DepositPerItem, Test}; + use super::{CodeHashLockupDepositPercent, Contracts, DepositPerByte, DepositPerItem, Test}; use crate::{ address::AddressMapper, exec::AccountIdOf, BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, PristineCode, @@ -140,20 +137,26 @@ pub mod test_utils { pub fn get_code_deposit(code_hash: &sp_core::H256) -> BalanceOf<Test> { crate::CodeInfoOf::<Test>::get(code_hash).unwrap().deposit() } - pub fn contract_info_storage_deposit(addr: &H160) -> BalanceOf<Test> { + pub fn lockup_deposit(code_hash: &sp_core::H256) -> BalanceOf<Test> { + CodeHashLockupDepositPercent::get().mul_ceil(get_code_deposit(code_hash)).into() + } + pub fn contract_base_deposit(addr: &H160) -> BalanceOf<Test> { let contract_info = self::get_contract(&addr); let info_size = contract_info.encoded_size() as u64; - let info_deposit = DepositPerByte::get() + let code_deposit = CodeHashLockupDepositPercent::get() + .mul_ceil(get_code_deposit(&contract_info.code_hash)); + let deposit = DepositPerByte::get() .saturating_mul(info_size) - .saturating_add(DepositPerItem::get()); + .saturating_add(DepositPerItem::get()) + .saturating_add(code_deposit); let immutable_size = contract_info.immutable_data_len() as u64; if immutable_size > 0 { let immutable_deposit = DepositPerByte::get() .saturating_mul(immutable_size) .saturating_add(DepositPerItem::get()); - info_deposit.saturating_add(immutable_deposit) + deposit.saturating_add(immutable_deposit) } else { - info_deposit + deposit } } pub fn expected_deposit(code_len: usize) -> u64 { @@ -373,7 +376,7 @@ impl RegisteredChainExtension<Test> for TempStorageExtension { 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), + Weight::from_parts(2 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), ); pub static ExistentialDeposit: u64 = 1; } @@ -381,6 +384,7 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type Block = Block; + type BlockWeights = BlockWeights; type AccountId = AccountId32; type Lookup = IdentityLookup<Self::AccountId>; type AccountData = pallet_balances::AccountData<u64>; @@ -436,8 +440,8 @@ impl pallet_dummy::Config for Test {} parameter_types! { pub static DepositPerByte: BalanceOf<Test> = 1; pub const DepositPerItem: BalanceOf<Test> = 2; - pub static CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0); - pub static ChainId: u64 = 384; + pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30); + pub static ChainId: u64 = 448; } impl Convert<Weight, BalanceOf<Self>> for Test { @@ -507,6 +511,15 @@ parameter_types! { pub static UnstableInterface: bool = true; } +impl FindAuthor<<Test as frame_system::Config>::AccountId> for Test { + fn find_author<'a, I>(_digests: I) -> Option<<Test as frame_system::Config>::AccountId> + where + I: 'a + IntoIterator<Item = (frame_support::ConsensusEngineId, &'a [u8])>, + { + Some(EVE) + } +} + #[derive_impl(crate::config_preludes::TestDefaultConfig)] impl Config for Test { type Time = Timestamp; @@ -521,8 +534,8 @@ impl Config for Test { type UploadOrigin = EnsureAccount<Self, UploadAccount>; type InstantiateOrigin = EnsureAccount<Self, InstantiateAccount>; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Debug = TestDebug; type ChainId = ChainId; + type FindAuthor = Test; } impl TryFrom<RuntimeCall> for crate::Call<Test> { @@ -568,7 +581,7 @@ impl ExtBuilder { sp_tracing::try_init_simple(); self.set_associated_consts(); let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); - pallet_balances::GenesisConfig::<Test> { balances: vec![] } + pallet_balances::GenesisConfig::<Test> { balances: vec![], ..Default::default() } .assimilate_storage(&mut t) .unwrap(); let mut ext = sp_io::TestExternalities::new(t); @@ -711,25 +724,6 @@ fn instantiate_and_call_and_deposit_event() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: test_utils::contract_info_storage_deposit(&addr), - } - ), - topics: vec![], - }, ] ); }); @@ -1076,14 +1070,6 @@ fn deploy_and_call_other_contract() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: caller_addr, - contract: callee_addr, - }), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { @@ -1093,33 +1079,6 @@ fn deploy_and_call_other_contract() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(caller_account.clone()), - contract: callee_addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: caller_addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: callee_addr, - amount: test_utils::contract_info_storage_deposit(&callee_addr), - } - ), - topics: vec![], - }, ] ); }); @@ -1147,7 +1106,7 @@ fn delegate_call() { assert_ok!(builder::call(caller_addr) .value(1337) - .data((callee_addr, 0u64, 0u64).encode()) + .data((callee_addr, u64::MAX, u64::MAX).encode()) .build()); }); } @@ -1211,14 +1170,11 @@ fn delegate_call_with_deposit_limit() { // Delegate call will write 1 storage and deposit of 2 (1 item) + 32 (bytes) is required. // Fails, not enough deposit - assert_err!( - builder::bare_call(caller_addr) - .value(1337) - .data((callee_addr, 33u64).encode()) - .build() - .result, - Error::<Test>::StorageDepositLimitExhausted, - ); + let ret = builder::bare_call(caller_addr) + .value(1337) + .data((callee_addr, 33u64).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); assert_ok!(builder::call(caller_addr) .value(1337) @@ -1246,10 +1202,10 @@ fn transfer_expendable_cannot_kill_account() { assert_eq!( test_utils::get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account), - test_utils::contract_info_storage_deposit(&addr) + test_utils::contract_base_deposit(&addr) ); - // Some ot the total balance is held, so it can't be transferred. + // Some or the total balance is held, so it can't be transferred. assert_err!( <<Test as Config>::Currency as Mutate<AccountId32>>::transfer( &account, @@ -1288,7 +1244,7 @@ fn cannot_self_destruct_through_draining() { // Make sure the account wasn't remove by sending all free balance away. assert_eq!( <Test as Config>::Currency::total_balance(&account), - value + test_utils::contract_info_storage_deposit(&addr) + min_balance, + value + test_utils::contract_base_deposit(&addr) + min_balance, ); }); } @@ -1302,7 +1258,7 @@ fn cannot_self_destruct_through_storage_refund_after_price_change() { // Instantiate the BOB contract. let contract = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); - let info_deposit = test_utils::contract_info_storage_deposit(&contract.addr); + let info_deposit = test_utils::contract_base_deposit(&contract.addr); // Check that the contract has been instantiated and has the minimum balance assert_eq!(get_contract(&contract.addr).total_deposit(), info_deposit); @@ -1374,8 +1330,6 @@ fn self_destruct_works() { // Check that the BOB contract has been instantiated. let _ = get_contract(&contract.addr); - let info_deposit = test_utils::contract_info_storage_deposit(&contract.addr); - // Drop all previous events initialize_block(2); @@ -1405,33 +1359,6 @@ fn self_destruct_works() { pretty_assertions::assert_eq!( System::events(), vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Terminated { - contract: contract.addr, - beneficiary: DJANGO_ADDR, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: contract.addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndReleased { - from: contract.addr, - to: ALICE_ADDR, - amount: info_deposit, - } - ), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::KilledAccount { @@ -1672,14 +1599,14 @@ fn instantiate_return_code() { // Contract has only the minimal balance so any transfer will fail. <Test as Config>::Currency::set_balance(&contract.account_id, min_balance); let result = builder::bare_call(contract.addr) - .data(callee_hash.clone()) + .data(callee_hash.iter().chain(&0u32.to_le_bytes()).cloned().collect()) .build_and_unwrap_result(); assert_return_code!(result, RuntimeReturnCode::TransferFailed); // Contract has enough balance but the passed code hash is invalid <Test as Config>::Currency::set_balance(&contract.account_id, min_balance + 10_000); - let result = builder::bare_call(contract.addr).data(vec![0; 33]).build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::CodeNotFound); + let result = builder::bare_call(contract.addr).data(vec![0; 36]).build(); + assert_err!(result.result, <Error<Test>>::CodeNotFound); // Contract has enough balance but callee reverts because "1" is passed. let result = builder::bare_call(contract.addr) @@ -1692,6 +1619,18 @@ fn instantiate_return_code() { .data(callee_hash.iter().chain(&2u32.to_le_bytes()).cloned().collect()) .build_and_unwrap_result(); assert_return_code!(result, RuntimeReturnCode::CalleeTrapped); + + // Contract instantiation succeeds + let result = builder::bare_call(contract.addr) + .data(callee_hash.iter().chain(&0u32.to_le_bytes()).cloned().collect()) + .build_and_unwrap_result(); + assert_return_code!(result, 0); + + // Contract instantiation fails because the same salt is being used again. + let result = builder::bare_call(contract.addr) + .data(callee_hash.iter().chain(&0u32.to_le_bytes()).cloned().collect()) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::DuplicateContractAddress); }); } @@ -1876,6 +1815,27 @@ fn lazy_batch_removal_works() { }); } +#[test] +fn ref_time_left_api_works() { + let (code, _) = compile_module("ref_time_left").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor calls ref_time_left twice and asserts it to decrease + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It echoes back the ref_time returned by the ref_time_left API. + let received = builder::bare_call(addr).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + + let returned_value = u64::from_le_bytes(received.data[..8].try_into().unwrap()); + assert!(returned_value > 0); + assert!(returned_value < GAS_LIMIT.ref_time()); + }); +} + #[test] fn lazy_removal_partial_remove_works() { let (code, _hash) = compile_module("self_destruct").unwrap(); @@ -2164,58 +2124,6 @@ fn refcounter() { }); } -#[test] -fn debug_message_works() { - let (wasm, _code_hash) = compile_module("debug_message_works").unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(30_000) - .build_and_unwrap_contract(); - 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 _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(30_000) - .build_and_unwrap_contract(); - // the dispatchables always run without debugging - assert_ok!(Contracts::call( - RuntimeOrigin::signed(ALICE), - addr, - 0, - GAS_LIMIT, - deposit_limit::<Test>(), - 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 _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(30_000) - .build_and_unwrap_contract(); - 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(); @@ -2241,12 +2149,12 @@ fn gas_estimation_for_subcalls() { // Run the test for all of those weight limits for the subcall let weights = [ - Weight::zero(), + Weight::MAX, GAS_LIMIT, GAS_LIMIT * 2, GAS_LIMIT / 5, - Weight::from_parts(0, GAS_LIMIT.proof_size()), - Weight::from_parts(GAS_LIMIT.ref_time(), 0), + Weight::from_parts(u64::MAX, GAS_LIMIT.proof_size()), + Weight::from_parts(GAS_LIMIT.ref_time(), u64::MAX), ]; // This call is passed to the sub call in order to create a large `required_weight` @@ -2290,7 +2198,7 @@ fn gas_estimation_for_subcalls() { // Make the same call using the estimated gas. Should succeed. let result = builder::bare_call(addr_caller) .gas_limit(result_orig.gas_required) - .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero()) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) .data(input.clone()) .build(); assert_ok!(&result.result); @@ -2298,7 +2206,7 @@ fn gas_estimation_for_subcalls() { // Check that it fails with too little ref_time let result = builder::bare_call(addr_caller) .gas_limit(result_orig.gas_required.sub_ref_time(1)) - .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero()) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) .data(input.clone()) .build(); assert_err!(result.result, error); @@ -2306,7 +2214,7 @@ fn gas_estimation_for_subcalls() { // Check that it fails with too little proof_size let result = builder::bare_call(addr_caller) .gas_limit(result_orig.gas_required.sub_proof_size(1)) - .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero()) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) .data(input.clone()) .build(); assert_err!(result.result, error); @@ -2431,79 +2339,6 @@ fn ecdsa_recover() { }) } -#[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 _ = <Test as Config>::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 _ = <Test as Config>::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 _ = <Test as Config>::Currency::set_balance(&ALICE, 1000 * min_balance); - - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(min_balance * 100) - .build_and_unwrap_contract(); - - let result = builder::bare_call(addr).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 _ = <Test as Config>::Currency::set_balance(&ALICE, 1000 * min_balance); - - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(min_balance * 100) - .build_and_unwrap_contract(); - - let result = builder::bare_call(addr).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(); @@ -2617,23 +2452,9 @@ fn upload_code_works() { initialize_block(2); assert!(!PristineCode::<Test>::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_ADDR - }), - topics: vec![], - },] - ); + expected_deposit(ensure_stored(code_hash)); }); } @@ -2691,32 +2512,8 @@ fn remove_code_works() { 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)); - + 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_ADDR - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeRemoved { - code_hash, - deposit_released: deposit_expected, - remover: ALICE_ADDR - }), - topics: vec![], - }, - ] - ); }); } @@ -2732,25 +2529,12 @@ fn remove_code_wrong_origin() { 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)); + 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_ADDR - }), - topics: vec![], - },] - ); }); } @@ -2809,27 +2593,18 @@ fn instantiate_with_zero_balance_works() { builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); + expected_deposit(ensure_stored(code_hash)); // Make sure the account exists even though no free balance was send assert_eq!(<Test as Config>::Currency::free_balance(&account_id), min_balance); assert_eq!( <Test as Config>::Currency::total_balance(&account_id), - min_balance + test_utils::contract_info_storage_deposit(&addr) + min_balance + test_utils::contract_base_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_ADDR - }), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::NewAccount { @@ -2854,25 +2629,6 @@ fn instantiate_with_zero_balance_works() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: test_utils::contract_info_storage_deposit(&addr), - } - ), - topics: vec![], - }, ] ); }); @@ -2895,26 +2651,17 @@ fn instantiate_with_below_existential_deposit_works() { .build_and_unwrap_contract(); // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); + expected_deposit(ensure_stored(code_hash)); // Make sure the account exists even though not enough free balance was send assert_eq!(<Test as Config>::Currency::free_balance(&account_id), min_balance + value); assert_eq!( <Test as Config>::Currency::total_balance(&account_id), - min_balance + value + test_utils::contract_info_storage_deposit(&addr) + min_balance + value + test_utils::contract_base_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_ADDR - }), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::NewAccount { @@ -2948,25 +2695,6 @@ fn instantiate_with_below_existential_deposit_works() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: test_utils::contract_info_storage_deposit(&addr), - } - ), - topics: vec![], - }, ] ); }); @@ -2981,7 +2709,7 @@ fn storage_deposit_works() { let Contract { addr, account_id } = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); - let mut deposit = test_utils::contract_info_storage_deposit(&addr); + let mut deposit = test_utils::contract_base_deposit(&addr); // Drop previous events initialize_block(2); @@ -3008,74 +2736,15 @@ fn storage_deposit_works() { assert_eq!( System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: account_id.clone(), - amount: 42, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: charged0, - } - ), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: charged1, - } - ), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndReleased { - from: addr, - to: ALICE_ADDR, - amount: refunded0, - } - ), - topics: vec![], - }, - ] + vec![EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: account_id.clone(), + amount: 42, + }), + topics: vec![], + },] ); }); } @@ -3102,7 +2771,7 @@ fn storage_deposit_callee_works() { assert_eq!(test_utils::get_balance(&account_id), min_balance); assert_eq!( callee.total_deposit(), - deposit + test_utils::contract_info_storage_deposit(&addr_callee) + deposit + test_utils::contract_base_deposit(&addr_callee) ); }); } @@ -3168,18 +2837,6 @@ fn set_code_extrinsic() { 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, - new_code_hash, - old_code_hash: code_hash, - }), - topics: vec![], - },] - ); }); } @@ -3198,7 +2855,7 @@ fn slash_cannot_kill_account() { // Drop previous events initialize_block(2); - let info_deposit = test_utils::contract_info_storage_deposit(&addr); + let info_deposit = test_utils::contract_base_deposit(&addr); assert_eq!( test_utils::get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account_id), @@ -3285,7 +2942,7 @@ fn contract_reverted() { #[test] fn set_code_hash() { - let (wasm, code_hash) = compile_module("set_code_hash").unwrap(); + let (wasm, _) = 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(|| { @@ -3307,47 +2964,12 @@ fn set_code_hash() { // First call sets new code_hash and returns 1 let result = builder::bare_call(contract_addr) .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) - .debug(DebugInfo::UnsafeDebug) - .build_and_unwrap_result(); + let result = builder::bare_call(contract_addr).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, - 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, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: contract_addr, - }), - topics: vec![], - }, - ], - ); }); } @@ -3372,7 +2994,7 @@ fn storage_deposit_limit_is_enforced() { let Contract { addr, account_id } = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); - let info_deposit = test_utils::contract_info_storage_deposit(&addr); + let info_deposit = test_utils::contract_base_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!( @@ -3433,13 +3055,13 @@ fn deposit_limit_in_nested_calls() { // 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. + // The nested call doesn't get a special limit, which is set by passing `u64::MAX` 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) .storage_deposit_limit(13) - .data((100u32, &addr_callee, U256::from(0u64)).encode()) + .data((100u32, &addr_callee, U256::MAX).encode()) .build(), <Error<Test>>::StorageDepositLimitExhausted, ); @@ -3447,13 +3069,13 @@ fn deposit_limit_in_nested_calls() { // 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. + // The nested call doesn't get a special limit, which is set by passing `u64::MAX` 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) .storage_deposit_limit(14) - .data((101u32, &addr_callee, U256::from(0u64)).encode()) + .data((101u32, &addr_callee, &U256::MAX).encode()) .build(), <Error<Test>>::StorageDepositLimitExhausted, ); @@ -3463,13 +3085,11 @@ fn deposit_limit_in_nested_calls() { // 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) - .storage_deposit_limit(16) - .data((102u32, &addr_callee, U256::from(1u64)).encode()) - .build(), - <Error<Test>>::StorageDepositLimitExhausted, - ); + let ret = builder::bare_call(addr_caller) + .storage_deposit_limit(DepositLimit::Balance(16)) + .data((102u32, &addr_callee, U256::from(1u64)).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); // 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 @@ -3477,7 +3097,7 @@ fn deposit_limit_in_nested_calls() { assert_err_ignore_postinfo!( builder::call(addr_caller) .storage_deposit_limit(0) - .data((87u32, &addr_callee, U256::from(0u64)).encode()) + .data((87u32, &addr_callee, &U256::MAX.to_little_endian()).encode()) .build(), <Error<Test>>::StorageDepositLimitExhausted, ); @@ -3485,13 +3105,11 @@ fn deposit_limit_in_nested_calls() { let _ = <Test as Config>::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) - .data((512u32, &addr_callee, U256::from(1u64)).encode()) - .build(), - <Error<Test>>::StorageDepositLimitExhausted, - ); + // Limit the sub call to little balance so it should fail in there + let ret = builder::bare_call(addr_caller) + .data((416, &addr_callee, U256::from(1u64)).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); // 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 @@ -3522,81 +3140,82 @@ fn deposit_limit_in_nested_instantiate() { .data(vec![0, 0, 0, 0]) .build_and_unwrap_contract(); - let callee_info_len = ContractInfoOf::<Test>::get(&addr).unwrap().encoded_size() as u64; - - // We don't set a special deposit limit for the nested instantiation. + // This is the deposit we expect to be charged just for instantiatiting the callee. // - // 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, + // - callee_info_len + 2 for storing the new contract info + // - the deposit for depending on a code hash + // - 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. + let callee_min_deposit = { + let callee_info_len = ContractInfoOf::<Test>::get(&addr).unwrap().encoded_size() as u64; + let code_deposit = test_utils::lockup_deposit(&code_hash_callee); + callee_info_len + code_deposit + 2 + ED + 2 + }; + + // The parent just stores an item of the passed size so at least + // we need to pay for the item itself. + let caller_min_deposit = callee_min_deposit + 2; + + // Fail in callee. // - // 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) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 1) - .data((0u32, &code_hash_callee, U256::from(0u64)).encode()) - .build(), - <Error<Test>>::StorageDepositLimitExhausted, - ); + // We still fail in the sub call because we enforce limits on return from a contract. + // Sub calls return first to they are checked first. + let ret = builder::bare_call(addr_caller) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(DepositLimit::Balance(0)) + .data((&code_hash_callee, 100u32, &U256::MAX.to_little_endian()).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); // The charges made on instantiation should be rolled back. assert_eq!(<Test as Config>::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) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 2) - .data((1u32, &code_hash_callee, U256::from(0u64)).encode()) - .build(), - <Error<Test>>::StorageDepositLimitExhausted, - ); + // Fail in the caller. + // + // For that we need to supply enough storage deposit so that the sub call + // succeeds but the parent call runs out of storage. + let ret = builder::bare_call(addr_caller) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(DepositLimit::Balance(callee_min_deposit)) + .data((&code_hash_callee, 0u32, &U256::MAX.to_little_endian()).encode()) + .build(); + assert_err!(ret.result, <Error<Test>>::StorageDepositLimitExhausted); // The charges made on the instantiation should be rolled back. assert_eq!(<Test as Config>::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) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 2) - .data((0u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 1)).encode()) - .build(), - <Error<Test>>::StorageDepositLimitExhausted, - ); + // Fail in the callee with bytes. + // + // Same as above but stores one byte in both caller and callee. + let ret = builder::bare_call(addr_caller) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(DepositLimit::Balance(caller_min_deposit + 1)) + .data((&code_hash_callee, 1u32, U256::from(callee_min_deposit)).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); // The charges made on the instantiation should be rolled back. assert_eq!(<Test as Config>::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) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 3) // enough parent limit - .data((1u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 2)).encode()) - .build(), - <Error<Test>>::StorageDepositLimitExhausted, - ); + // Fail in the caller with bytes. + // + // Same as above but stores one byte in both caller and callee. + let ret = builder::bare_call(addr_caller) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(DepositLimit::Balance(callee_min_deposit + 1)) + .data((&code_hash_callee, 1u32, U256::from(callee_min_deposit + 1)).encode()) + .build(); + assert_err!(ret.result, <Error<Test>>::StorageDepositLimitExhausted); // The charges made on the instantiation should be rolled back. assert_eq!(<Test as Config>::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) .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 4 + 2) - .data((1u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 3 + 2)).encode()) + .storage_deposit_limit((caller_min_deposit + 2).into()) + .data((&code_hash_callee, 1u32, U256::from(callee_min_deposit + 1)).encode()) .build(); let returned = result.result.unwrap(); + assert!(!returned.did_revert()); + // All balance of the caller except ED has been transferred to the callee. // No deposit has been taken from it. assert_eq!(<Test as Config>::Currency::free_balance(&caller_id), ED); @@ -3606,17 +3225,12 @@ fn deposit_limit_in_nested_instantiate() { // 10_000 should be sent to callee from the caller contract, plus ED to be sent from the // origin. assert_eq!(<Test as Config>::Currency::free_balance(&callee_account_id), 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 - // - Immutable data storage item deposit + // The origin should be charged with what the outer call consumed assert_eq!( <Test as Config>::Currency::free_balance(&BOB), - 1_000_000 - (callee_info_len + 2 + ED + 3) + 1_000_000 - (caller_min_deposit + 2), ); - // 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)) + assert_eq!(result.storage_deposit.charge_or_zero(), (caller_min_deposit + 2)) }); } @@ -3633,7 +3247,7 @@ fn deposit_limit_honors_liquidity_restrictions() { let Contract { addr, account_id } = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); - let info_deposit = test_utils::contract_info_storage_deposit(&addr); + let info_deposit = test_utils::contract_base_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!( @@ -3672,7 +3286,7 @@ fn deposit_limit_honors_existential_deposit() { let Contract { addr, account_id } = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); - let info_deposit = test_utils::contract_info_storage_deposit(&addr); + let info_deposit = test_utils::contract_base_deposit(&addr); // Check that the contract has been instantiated and has the minimum balance assert_eq!(get_contract(&addr).total_deposit(), info_deposit); @@ -3706,7 +3320,7 @@ fn deposit_limit_honors_min_leftover() { let Contract { addr, account_id } = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); - let info_deposit = test_utils::contract_info_storage_deposit(&addr); + let info_deposit = test_utils::contract_base_deposit(&addr); // Check that the contract has been instantiated and has the minimum balance and the // storage deposit @@ -3733,195 +3347,11 @@ fn deposit_limit_honors_min_leftover() { }); } -#[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| sp_core::H256(sp_io::hashing::keccak_256(c))) - .collect(); - - let hash2addr = |code_hash: &H256| { - let mut addr = H160::zero(); - addr.as_bytes_mut().copy_from_slice(&code_hash.as_ref()[..20]); - addr - }; - - // 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)| { - let (action, code_hash) = input; - builder::bare_instantiate(Code::Upload(wasm_caller.clone())) - .origin(RuntimeOrigin::signed(ALICE_FALLBACK)) - .data((action, hash2addr(code_hash), code_hash).encode()) - .build() - }; - - // Call contract with the given input. - let call = |addr_caller: &H160, input: &(u32, H256)| { - let (action, code_hash) = input; - builder::bare_call(*addr_caller) - .origin(RuntimeOrigin::signed(ALICE_FALLBACK)) - .data((action, hash2addr(code_hash), code_hash).encode()) - .build() - }; - const ED: u64 = 2000; - ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { - let _ = Balances::set_balance(&ALICE_FALLBACK, 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::<Test>::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, code_hash } = - Contracts::bare_upload_code( - RuntimeOrigin::signed(ALICE_FALLBACK), - code.clone(), - deposit_limit::<Test>(), - ) - .unwrap(); - deposit = deposit_per_code; - // Mock contract info by using first 20 bytes of code_hash as address. - let addr = hash2addr(&code_hash); - ContractInfoOf::<Test>::set(&addr, ContractInfo::new(&addr, 0, code_hash).ok()); - } - - // Instantiate should now work. - let addr_caller = instantiate(&lock_delegate_dependency_input).result.unwrap().addr; - let caller_account_id = <Test as Config>::AddressMapper::to_account_id(&addr_caller); - - // 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(), - &caller_account_id - ), - 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_FALLBACK), callee_hashes[0]), - <Error<Test>>::CodeInUse - ); - - // Locking an already existing dependency should fail. - assert_err!( - call(&addr_caller, &lock_delegate_dependency_input).result, - Error::<Test>::DelegateDependencyAlreadyExists - ); - - // Locking self should fail. - assert_err!( - builder::bare_call(addr_caller) - .origin(RuntimeOrigin::signed(ALICE_FALLBACK)) - .data((1u32, &addr_caller, self_code_hash).encode()) - .build() - .result, - Error::<Test>::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::<Test>::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(), - &caller_account_id - ), - contract.storage_base_deposit() - ); - - // Removing a nonexistent dependency should fail. - assert_err!( - call(&addr_caller, &unlock_delegate_dependency_input).result, - Error::<Test>::DelegateDependencyNotFound - ); - - // Locking a dependency with a storage limit too low should fail. - assert_err!( - builder::bare_call(addr_caller) - .storage_deposit_limit(dependency_deposit - 1) - .data((1u32, hash2addr(&callee_hashes[0]), callee_hashes[0]).encode()) - .build() - .result, - Error::<Test>::StorageDepositLimitExhausted - ); - - // Since we unlocked the dependency we should now be able to remove the code. - assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE_FALLBACK), callee_hashes[0])); - - // Calling should fail since the delegated contract is not on chain anymore. - assert_err!(call(&addr_caller, &noop_input).result, Error::<Test>::ContractTrapped); - - // Add the dependency back. - Contracts::upload_code( - RuntimeOrigin::signed(ALICE_FALLBACK), - callee_codes[0].clone(), - deposit_limit::<Test>(), - ) - .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_FALLBACK); - assert_ok!(call(&addr_caller, &terminate_input).result); - assert_eq!( - test_utils::get_balance(&ALICE_FALLBACK), - 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_FALLBACK), 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(|| { @@ -3955,33 +3385,34 @@ fn native_dependency_deposit_works() { let addr = res.result.unwrap().addr; let account_id = <Test as Config>::AddressMapper::to_account_id(&addr); - let base_deposit = test_utils::contract_info_storage_deposit(&addr); + let base_deposit = test_utils::contract_base_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()); + assert_eq!( + res.storage_deposit.charge_or_zero(), + extra_deposit + base_deposit + Contracts::min_balance() + ); // call set_code_hash builder::bare_call(addr) .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); + // Check updated storage_deposit due to code size changes + let deposit_diff = lockup_deposit_percent + .mul_ceil(test_utils::get_code_deposit(&code_hash)) - + lockup_deposit_percent.mul_ceil(test_utils::get_code_deposit(&dummy_code_hash)); + let new_base_deposit = test_utils::contract_base_deposit(&addr); + assert_ne!(deposit_diff, 0); + assert_eq!(base_deposit - new_base_deposit, deposit_diff); assert_eq!( test_utils::get_balance_on_hold( &HoldReason::StorageDepositReserve.into(), &account_id ), - deposit + new_base_deposit ); }); } @@ -4012,6 +3443,21 @@ fn block_hash_works() { }); } +#[test] +fn block_author_works() { + let (code, _) = compile_module("block_author").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // The fixture asserts the input to match the find_author API method output. + assert_ok!(builder::call(addr).data(EVE_ADDR.encode()).build()); + }); +} + #[test] fn root_cannot_upload_code() { let (wasm, _) = compile_module("dummy").unwrap(); @@ -4352,6 +3798,80 @@ fn create1_with_value_works() { }); } +#[test] +fn gas_price_api_works() { + let (code, _) = compile_module("gas_price").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It echoes back the value returned by the gas price API. + let received = builder::bare_call(addr).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(u64::from_le_bytes(received.data[..].try_into().unwrap()), u64::from(GAS_PRICE)); + }); +} + +#[test] +fn base_fee_api_works() { + let (code, _) = compile_module("base_fee").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It echoes back the value returned by the base fee API. + let received = builder::bare_call(addr).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(received.data[..].try_into().unwrap()), U256::zero()); + }); +} + +#[test] +fn call_data_size_api_works() { + let (code, _) = compile_module("call_data_size").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It echoes back the value returned by the call data size API. + let received = builder::bare_call(addr).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(u64::from_le_bytes(received.data.try_into().unwrap()), 0); + + let received = builder::bare_call(addr).data(vec![1; 256]).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(u64::from_le_bytes(received.data.try_into().unwrap()), 256); + }); +} + +#[test] +fn call_data_copy_api_works() { + let (code, _) = compile_module("call_data_copy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call fixture: Expects an input of [255; 32] and executes tests. + assert_ok!(builder::call(addr).data(vec![255; 32]).build()); + }); +} + #[test] fn static_data_limit_is_enforced() { let (oom_rw_trailing, _) = compile_module("oom_rw_trailing").unwrap(); @@ -4417,6 +3937,48 @@ fn chain_id_works() { }); } +#[test] +fn call_data_load_api_works() { + let (code, _) = compile_module("call_data_load").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It reads a byte for the offset and then returns + // what call data load returned using this byte as the offset. + let input = (3u8, U256::max_value(), U256::max_value()).encode(); + let received = builder::bare_call(addr).data(input).build().result.unwrap(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(&received.data), U256::max_value()); + + // Edge case + let input = (2u8, U256::from(255).to_big_endian()).encode(); + let received = builder::bare_call(addr).data(input).build().result.unwrap(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(&received.data), U256::from(65280)); + + // Edge case + let received = builder::bare_call(addr).data(vec![1]).build().result.unwrap(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(&received.data), U256::zero()); + + // OOB case + let input = (42u8).encode(); + let received = builder::bare_call(addr).data(input).build().result.unwrap(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(&received.data), U256::zero()); + + // No calldata should return the zero value + let received = builder::bare_call(addr).build().result.unwrap(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(&received.data), U256::zero()); + }); +} + #[test] fn return_data_api_works() { let (code_return_data_api, _) = compile_module("return_data_api").unwrap(); @@ -4459,15 +4021,21 @@ fn immutable_data_works() { .data(data.to_vec()) .build_and_unwrap_contract(); + let contract = test_utils::get_contract(&addr); + let account = <Test as Config>::AddressMapper::to_account_id(&addr); + let actual_deposit = + test_utils::get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account); + + assert_eq!(contract.immutable_data_len(), data.len() as u32); + // Storing immmutable data charges storage deposit; verify it explicitly. + assert_eq!(actual_deposit, test_utils::contract_base_deposit(&addr)); + + // make sure it is also recorded in the base deposit assert_eq!( - test_utils::get_balance_on_hold( - &HoldReason::StorageDepositReserve.into(), - &<Test as Config>::AddressMapper::to_account_id(&addr) - ), - test_utils::contract_info_storage_deposit(&addr) + test_utils::get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account), + contract.storage_base_deposit(), ); - assert_eq!(test_utils::get_contract(&addr).immutable_data_len(), data.len() as u32); // Call the contract: Asserts the input to equal the immutable data assert_ok!(builder::call(addr).data(data.to_vec()).build()); @@ -4536,6 +4104,43 @@ fn origin_api_works() { }); } +#[test] +fn to_account_id_works() { + let (code_hash_code, _) = compile_module("to_account_id").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); + let _ = <Test as Config>::Currency::set_balance(&EVE, 1_000_000); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code_hash_code)).build_and_unwrap_contract(); + + // mapped account + <Pallet<Test>>::map_account(RuntimeOrigin::signed(EVE)).unwrap(); + let expected_mapped_account_id = &<Test as Config>::AddressMapper::to_account_id(&EVE_ADDR); + assert_ne!( + expected_mapped_account_id.encode()[20..32], + [0xEE; 12], + "fallback suffix found where none should be" + ); + assert_ok!(builder::call(addr) + .data((EVE_ADDR, expected_mapped_account_id).encode()) + .build()); + + // fallback for unmapped accounts + let expected_fallback_account_id = + &<Test as Config>::AddressMapper::to_account_id(&BOB_ADDR); + assert_eq!( + expected_fallback_account_id.encode()[20..32], + [0xEE; 12], + "no fallback suffix found where one should be" + ); + assert_ok!(builder::call(addr) + .data((BOB_ADDR, expected_fallback_account_id).encode()) + .build()); + }); +} + #[test] fn code_hash_works() { let (code_hash_code, self_code_hash) = compile_module("code_hash").unwrap(); @@ -4666,3 +4271,300 @@ fn mapped_address_works() { assert_eq!(<Test as Config>::Currency::total_balance(&EVE), 1_100); }); } + +#[test] +fn skip_transfer_works() { + let (code_caller, _) = compile_module("call").unwrap(); + let (code, _) = compile_module("set_empty_storage").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); + <Test as Config>::Currency::set_balance(&BOB, 0); + + // fails to instantiate when gas is specified. + assert_err!( + Pallet::<Test>::bare_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + input: Some(code.clone().into()), + gas: Some(1u32.into()), + ..Default::default() + }, + Weight::MAX, + |_, _| 0u64, + ), + EthTransactError::Message(format!( + "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" + )) + ); + + // works when no gas is specified. + assert_ok!(Pallet::<Test>::bare_eth_transact( + GenericTransaction { + from: Some(ALICE_ADDR), + input: Some(code.clone().into()), + ..Default::default() + }, + Weight::MAX, + |_, _| 0u64, + )); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(code_caller)).build_and_unwrap_contract(); + + // fails to call when gas is specified. + assert_err!( + Pallet::<Test>::bare_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + to: Some(addr), + gas: Some(1u32.into()), + ..Default::default() + }, + Weight::MAX, + |_, _| 0u64, + ), + EthTransactError::Message(format!( + "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" + )) + ); + + // fails when calling from a contract when gas is specified. + assert!(Pallet::<Test>::bare_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + to: Some(caller_addr), + input: Some((0u32, &addr).encode().into()), + gas: Some(1u32.into()), + ..Default::default() + }, + Weight::MAX, + |_, _| 0u64, + ) + .is_err(),); + + // works when no gas is specified. + assert_ok!(Pallet::<Test>::bare_eth_transact( + GenericTransaction { from: Some(BOB_ADDR), to: Some(addr), ..Default::default() }, + Weight::MAX, + |_, _| 0u64, + )); + + // works when calling from a contract when no gas is specified. + assert_ok!(Pallet::<Test>::bare_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + to: Some(caller_addr), + input: Some((0u32, &addr).encode().into()), + ..Default::default() + }, + Weight::MAX, + |_, _| 0u64, + )); + }); +} + +#[test] +fn gas_limit_api_works() { + let (code, _) = compile_module("gas_limit").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It echoes back the value returned by the gas limit API. + let received = builder::bare_call(addr).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!( + u64::from_le_bytes(received.data[..].try_into().unwrap()), + <Test as frame_system::Config>::BlockWeights::get().max_block.ref_time() + ); + }); +} + +#[test] +fn unknown_syscall_rejected() { + let (code, _) = compile_module("unknown_syscall").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); + + assert_err!( + builder::bare_instantiate(Code::Upload(code)).build().result, + <Error<Test>>::CodeRejected, + ) + }); +} + +#[test] +fn unstable_interface_rejected() { + let (code, _) = compile_module("unstable_interface").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); + + Test::set_unstable_interface(false); + assert_err!( + builder::bare_instantiate(Code::Upload(code.clone())).build().result, + <Error<Test>>::CodeRejected, + ); + + Test::set_unstable_interface(true); + assert_ok!(builder::bare_instantiate(Code::Upload(code)).build().result); + }); +} + +#[test] +fn tracing_works_for_transfers() { + ExtBuilder::default().build().execute_with(|| { + let _ = <Test as Config>::Currency::set_balance(&ALICE, 100_000_000); + let mut tracer = CallTracer::new(false, |_| U256::zero()); + trace(&mut tracer, || { + builder::bare_call(BOB_ADDR).value(10_000_000).build_and_unwrap_result(); + }); + assert_eq!( + tracer.collect_traces(), + vec![CallTrace { + from: ALICE_ADDR, + to: BOB_ADDR, + value: U256::from(10_000_000), + call_type: CallType::Call, + ..Default::default() + },] + ) + }); +} + +#[test] +#[ignore = "does not collect the gas_used properly"] +fn tracing_works() { + use crate::evm::*; + use CallType::*; + let (code, _code_hash) = compile_module("tracing").unwrap(); + let (wasm_callee, _) = compile_module("tracing_callee").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let tracer_options = vec![ + ( false , vec![]), + ( + true , + vec![ + CallLog { + address: addr, + topics: Default::default(), + data: b"before".to_vec().into(), + position: 0, + }, + CallLog { + address: addr, + topics: Default::default(), + data: b"after".to_vec().into(), + position: 1, + }, + ], + ), + ]; + + // Verify that the first trace report the same weight reported by bare_call + let mut tracer = CallTracer::new(false, |w| w); + let gas_used = trace(&mut tracer, || { + builder::bare_call(addr).data((3u32, addr_callee).encode()).build().gas_consumed + }); + let traces = tracer.collect_traces(); + assert_eq!(&traces[0].gas_used, &gas_used); + + // Discarding gas usage, check that traces reported are correct + for (with_logs, logs) in tracer_options { + let mut tracer = CallTracer::new(with_logs, |_| U256::zero()); + trace(&mut tracer, || { + builder::bare_call(addr).data((3u32, addr_callee).encode()).build() + }); + + + assert_eq!( + tracer.collect_traces(), + vec![CallTrace { + from: ALICE_ADDR, + to: addr, + input: (3u32, addr_callee).encode(), + call_type: Call, + logs: logs.clone(), + calls: vec![ + CallTrace { + from: addr, + to: addr_callee, + input: 2u32.encode(), + output: hex_literal::hex!( + "08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a546869732066756e6374696f6e20616c77617973206661696c73000000000000" + ).to_vec().into(), + revert_reason: Some( + "execution reverted: This function always fails".to_string() + ), + error: Some("execution reverted".to_string()), + call_type: Call, + ..Default::default() + }, + CallTrace { + from: addr, + to: addr, + input: (2u32, addr_callee).encode(), + call_type: Call, + logs: logs.clone(), + calls: vec![ + CallTrace { + from: addr, + to: addr_callee, + input: 1u32.encode(), + output: Default::default(), + error: Some("ContractTrapped".to_string()), + call_type: Call, + ..Default::default() + }, + CallTrace { + from: addr, + to: addr, + input: (1u32, addr_callee).encode(), + call_type: Call, + logs: logs.clone(), + calls: vec![ + CallTrace { + from: addr, + to: addr_callee, + input: 0u32.encode(), + output: 0u32.to_le_bytes().to_vec().into(), + call_type: Call, + ..Default::default() + }, + CallTrace { + from: addr, + to: addr, + input: (0u32, addr_callee).encode(), + call_type: Call, + ..Default::default() + }, + ], + ..Default::default() + }, + ], + ..Default::default() + }, + ], + ..Default::default() + },] + ); + } + }); +} diff --git a/substrate/frame/revive/src/tests/test_debug.rs b/substrate/frame/revive/src/tests/test_debug.rs deleted file mode 100644 index 7c4fbba71f656616ab82bdb860dd22f54c6c0db9..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/src/tests/test_debug.rs +++ /dev/null @@ -1,238 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::*; - -use crate::{ - debug::{CallInterceptor, CallSpan, ExecResult, ExportedFunction, Tracing}, - primitives::ExecReturnValue, - test_utils::*, -}; -use frame_support::traits::Currency; -use pretty_assertions::assert_eq; -use sp_core::H160; -use std::cell::RefCell; - -#[derive(Clone, PartialEq, Eq, Debug)] -struct DebugFrame { - contract_address: sp_core::H160, - call: ExportedFunction, - input: Vec<u8>, - result: Option<Vec<u8>>, -} - -thread_local! { - static DEBUG_EXECUTION_TRACE: RefCell<Vec<DebugFrame>> = RefCell::new(Vec::new()); - static INTERCEPTED_ADDRESS: RefCell<Option<sp_core::H160>> = RefCell::new(None); -} - -pub struct TestDebug; -pub struct TestCallSpan { - contract_address: sp_core::H160, - call: ExportedFunction, - input: Vec<u8>, -} - -impl Tracing<Test> for TestDebug { - type CallSpan = TestCallSpan; - - fn new_call_span( - contract_address: &crate::H160, - entry_point: ExportedFunction, - input_data: &[u8], - ) -> TestCallSpan { - DEBUG_EXECUTION_TRACE.with(|d| { - d.borrow_mut().push(DebugFrame { - contract_address: *contract_address, - call: entry_point, - input: input_data.to_vec(), - result: None, - }) - }); - TestCallSpan { - contract_address: *contract_address, - call: entry_point, - input: input_data.to_vec(), - } - } -} - -impl CallInterceptor<Test> for TestDebug { - fn intercept_call( - contract_address: &sp_core::H160, - _entry_point: ExportedFunction, - _input_data: &[u8], - ) -> Option<ExecResult> { - 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_address: self.contract_address, - call: self.call, - input: self.input, - result: Some(output.data.clone()), - }) - }); - } -} - -#[test] -fn debugging_works() { - let (wasm_caller, _) = compile_module("call").unwrap(); - let (wasm_callee, _) = compile_module("store_call").unwrap(); - - fn current_stack() -> Vec<DebugFrame> { - DEBUG_EXECUTION_TRACE.with(|stack| stack.borrow().clone()) - } - - fn deploy(wasm: Vec<u8>) -> H160 { - Contracts::bare_instantiate( - RuntimeOrigin::signed(ALICE), - 0, - GAS_LIMIT, - deposit_limit::<Test>(), - Code::Upload(wasm), - vec![], - Some([0u8; 32]), - DebugInfo::Skip, - CollectEvents::Skip, - ) - .result - .unwrap() - .addr - } - - fn constructor_frame(contract_address: &H160, after: bool) -> DebugFrame { - DebugFrame { - contract_address: *contract_address, - call: ExportedFunction::Constructor, - input: vec![], - result: if after { Some(vec![]) } else { None }, - } - } - - fn call_frame(contract_address: &H160, args: Vec<u8>, after: bool) -> DebugFrame { - DebugFrame { - contract_address: *contract_address, - 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, - 0, - GAS_LIMIT, - deposit_limit::<Test>(), - 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::<Test>(), - Code::Upload(wasm), - vec![], - // some salt to ensure that the address of this contract is unique among all tests - Some([0x41; 32]), - DebugInfo::Skip, - CollectEvents::Skip, - ) - .result - .unwrap() - .addr; - - // no interception yet - assert_ok!(Contracts::call( - RuntimeOrigin::signed(ALICE), - account_id, - 0, - GAS_LIMIT, - deposit_limit::<Test>(), - vec![], - )); - - // intercept calls to this contract - INTERCEPTED_ADDRESS.with(|i| *i.borrow_mut() = Some(account_id)); - - assert_err_ignore_postinfo!( - Contracts::call( - RuntimeOrigin::signed(ALICE), - account_id, - 0, - GAS_LIMIT, - deposit_limit::<Test>(), - vec![], - ), - <Error<Test>>::ContractReverted, - ); - }); -} diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs new file mode 100644 index 0000000000000000000000000000000000000000..e9c05f8cb5058ee74b4fbb29cb9fe5aea30faac2 --- /dev/null +++ b/substrate/frame/revive/src/tracing.rs @@ -0,0 +1,64 @@ +// 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::{primitives::ExecReturnValue, DispatchError, Weight}; +use environmental::environmental; +use sp_core::{H160, H256, U256}; + +environmental!(tracer: dyn Tracer + 'static); + +/// Trace the execution of the given closure. +/// +/// # Warning +/// +/// Only meant to be called from off-chain code as its additional resource usage is +/// not accounted for in the weights or memory envelope. +pub fn trace<R, F: FnOnce() -> R>(tracer: &mut (dyn Tracer + 'static), f: F) -> R { + tracer::using_once(tracer, f) +} + +/// Run the closure when tracing is enabled. +/// +/// This is safe to be called from on-chain code as tracing will never be activated +/// there. Hence the closure is not executed in this case. +pub(crate) fn if_tracing<F: FnOnce(&mut (dyn Tracer + 'static))>(f: F) { + tracer::with(f); +} + +/// Defines methods to trace contract interactions. +pub trait Tracer { + /// Called before a contract call is executed + fn enter_child_span( + &mut self, + from: H160, + to: H160, + is_delegate_call: bool, + is_read_only: bool, + value: U256, + input: &[u8], + gas: Weight, + ); + + /// Record a log event + fn log_event(&mut self, event: H160, topics: &[H256], data: &[u8]); + + /// Called after a contract call is executed + fn exit_child_span(&mut self, output: &ExecReturnValue, gas_left: Weight); + + /// Called when a contract call terminates with an error + fn exit_child_span_with_error(&mut self, error: DispatchError, gas_left: Weight); +} diff --git a/substrate/frame/revive/src/transient_storage.rs b/substrate/frame/revive/src/transient_storage.rs index 298e0296fe69e383567c951408fbf68155fc5810..d88adc4373590847a28a9ef18aaf4eba91fc38b3 100644 --- a/substrate/frame/revive/src/transient_storage.rs +++ b/substrate/frame/revive/src/transient_storage.rs @@ -22,11 +22,11 @@ use crate::{ storage::WriteOutcome, Config, Error, }; +use alloc::{collections::BTreeMap, vec::Vec}; use codec::Encode; -use core::marker::PhantomData; +use core::{marker::PhantomData, mem}; 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)] diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index f10c4f5fddf8a43eeb768fc24fbb277b1fefa469..dc49fae26fdaa36509bdc1e9beffeb7188ef027e 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -23,23 +23,19 @@ mod runtime; #[cfg(doc)] pub use crate::wasm::runtime::SyscallDoc; -#[cfg(test)] -pub use runtime::HIGHEST_API_VERSION; - #[cfg(feature = "runtime-benchmarks")] pub use crate::wasm::runtime::{ReturnData, TrapReason}; -pub use crate::wasm::runtime::{ApiVersion, Memory, Runtime, RuntimeCosts}; +pub use crate::wasm::runtime::{Memory, Runtime, RuntimeCosts}; use crate::{ - address::AddressMapper, exec::{ExecResult, Executable, ExportedFunction, Ext}, gas::{GasMeter, Token}, limits, storage::meter::Diff, weights::WeightInfo, - AccountIdOf, BadOrigin, BalanceOf, CodeInfoOf, CodeVec, Config, Error, Event, ExecError, - HoldReason, Pallet, PristineCode, Weight, API_VERSION, LOG_TARGET, + AccountIdOf, BadOrigin, BalanceOf, CodeInfoOf, CodeVec, Config, Error, ExecError, HoldReason, + PristineCode, Weight, LOG_TARGET, }; use alloc::vec::Vec; use codec::{Decode, Encode, MaxEncodedLen}; @@ -87,11 +83,6 @@ pub struct CodeInfo<T: Config> { 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 @@ -99,7 +90,7 @@ pub struct CodeInfo<T: Config> { /// 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, + behaviour_version: u32, } impl ExportedFunction { @@ -130,9 +121,10 @@ where { /// We only check for size and nothing else when the code is uploaded. pub fn from_code(code: Vec<u8>, owner: AccountIdOf<T>) -> Result<Self, DispatchError> { - // We do size checks when new code is deployed. This allows us to increase + // We do validation only when new code is deployed. This allows us to increase // the limits later without affecting already deployed code. - let code = limits::code::enforce::<T>(code)?; + let available_syscalls = runtime::list_syscalls(T::UnsafeUnstableInterface::get()); + let code = limits::code::enforce::<T>(code, available_syscalls)?; let code_len = code.len() as u32; let bytes_added = code_len.saturating_add(<CodeInfo<T>>::max_encoded_len() as u32); @@ -144,7 +136,6 @@ where deposit, refcount: 0, code_len, - api_version: API_VERSION, behaviour_version: Default::default(), }; let code_hash = H256(sp_io::hashing::keccak_256(&code)); @@ -165,16 +156,9 @@ where code_info.deposit, BestEffort, ); - let deposit_released = code_info.deposit; - let remover = T::AddressMapper::to_address(&code_info.owner); *existing = None; <PristineCode<T>>::remove(&code_hash); - <Pallet<T>>::deposit_event(Event::CodeRemoved { - code_hash, - deposit_released, - remover, - }); Ok(()) } else { Err(<Error<T>>::CodeNotFound.into()) @@ -183,7 +167,7 @@ where } /// Puts the module blob into storage, and returns the deposit collected for the storage. - pub fn store_code(&mut self) -> Result<BalanceOf<T>, Error<T>> { + pub fn store_code(&mut self, skip_transfer: bool) -> Result<BalanceOf<T>, Error<T>> { let code_hash = *self.code_hash(); <CodeInfoOf<T>>::mutate(code_hash, |stored_code_info| { match stored_code_info { @@ -195,25 +179,21 @@ where // the `owner` is always the origin of the current transaction. None => { let deposit = self.code_info.deposit; - T::Currency::hold( + + if !skip_transfer { + T::Currency::hold( &HoldReason::CodeUploadDepositReserve.into(), &self.code_info.owner, deposit, - ) - .map_err(|err| { - log::debug!(target: LOG_TARGET, "failed to store code for owner: {:?}: {err:?}", self.code_info.owner); - <Error<T>>::StorageDepositNotEnoughFunds + ) .map_err(|err| { + log::debug!(target: LOG_TARGET, "failed to hold store code deposit {deposit:?} for owner: {:?}: {err:?}", self.code_info.owner); + <Error<T>>::StorageDepositNotEnoughFunds })?; + } self.code_info.refcount = 0; <PristineCode<T>>::insert(code_hash, &self.code); *stored_code_info = Some(self.code_info.clone()); - let uploader = T::AddressMapper::to_address(&self.code_info.owner); - <Pallet<T>>::deposit_event(Event::CodeStored { - code_hash, - deposit_held: deposit, - uploader, - }); Ok(deposit) }, } @@ -229,37 +209,72 @@ impl<T: Config> CodeInfo<T> { deposit: Default::default(), refcount: 0, code_len: 0, - api_version: API_VERSION, behaviour_version: Default::default(), } } /// Returns reference count of the module. + #[cfg(test)] 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<T> { self.deposit } /// Returns the code length. - pub fn code_len(&self) -> U256 { + pub fn code_len(&self) -> u64 { self.code_len.into() } + + /// Returns the number of times the specified contract exists on the call stack. Delegated calls + /// Increment the reference count of a stored code by one. + /// + /// # Errors + /// + /// [`Error::CodeNotFound`] is returned if no stored code found having the specified + /// `code_hash`. + pub fn increment_refcount(code_hash: H256) -> DispatchResult { + <CodeInfoOf<T>>::mutate(code_hash, |existing| -> Result<(), DispatchError> { + if let Some(info) = existing { + info.refcount = info + .refcount + .checked_add(1) + .ok_or_else(|| <Error<T>>::RefcountOverOrUnderflow)?; + Ok(()) + } else { + Err(Error::<T>::CodeNotFound.into()) + } + }) + } + + /// 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. + pub fn decrement_refcount(code_hash: H256) -> DispatchResult { + <CodeInfoOf<T>>::mutate(code_hash, |existing| { + if let Some(info) = existing { + info.refcount = info + .refcount + .checked_sub(1) + .ok_or_else(|| <Error<T>>::RefcountOverOrUnderflow)?; + Ok(()) + } else { + Err(Error::<T>::CodeNotFound.into()) + } + }) + } } 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> @@ -270,12 +285,9 @@ where 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, - ) { + if let Some(exec_result) = + self.runtime.handle_interrupt(interrupt, &self.module, &mut self.instance) + { break exec_result } }; @@ -289,12 +301,18 @@ impl<T: Config> WasmBlob<T> { self, mut runtime: Runtime<E, polkavm::RawInstance>, entry_point: ExportedFunction, - api_version: ApiVersion, ) -> Result<PreparedCall<E>, ExecError> { 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"); + config.set_cache_enabled(false); + #[cfg(feature = "std")] + if std::env::var_os("REVIVE_USE_COMPILER").is_some() { + config.set_backend(Some(polkavm::BackendKind::Compiler)); + } + let engine = polkavm::Engine::new(&config).expect( + "on-chain (no_std) use of interpreter is hard coded. + interpreter is available on all platforms; qed", + ); let mut module_config = polkavm::ModuleConfig::new(); module_config.set_page_size(limits::PAGE_SIZE); @@ -319,15 +337,10 @@ impl<T: Config> WasmBlob<T> { Error::<T>::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 }) + Ok(PreparedCall { module, instance, runtime }) } } @@ -348,13 +361,7 @@ where function: ExportedFunction, input_data: Vec<u8>, ) -> ExecResult { - let api_version = if <E::T as Config>::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)?; + let prepared_call = self.prepare_call(Runtime::new(ext, input_data), function)?; prepared_call.call() } diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 3e2c83db1ebda2cc3e2611e501093c2d7325de95..279d72b97ee1a546bed2c517ac3a5a0f4ba9229f 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -19,6 +19,7 @@ use crate::{ address::AddressMapper, + evm::runtime::GAS_PRICE, exec::{ExecError, ExecResult, Ext, Key}, gas::{ChargedAmount, Token}, limits, @@ -27,7 +28,7 @@ use crate::{ Config, Error, LOG_TARGET, SENTINEL, }; use alloc::{boxed::Box, vec, vec::Vec}; -use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen}; +use codec::{Decode, DecodeLimit, Encode}; use core::{fmt, marker::PhantomData, mem}; use frame_support::{ dispatch::DispatchInfo, ensure, pallet_prelude::DispatchResultWithPostInfo, parameter_types, @@ -44,14 +45,6 @@ type CallOf<T> = <T as frame_system::Config>::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 @@ -73,6 +66,13 @@ pub trait Memory<T: Config> { /// - designated area is not within the bounds of the sandbox memory. fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError>; + /// Zero 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 zero(&mut self, ptr: u32, len: u32) -> Result<(), DispatchError>; + /// Read designated chunk from the sandbox memory. /// /// Returns `Err` if one of the following conditions occurs: @@ -126,34 +126,13 @@ pub trait Memory<T: Config> { /// /// # Note /// - /// There must be an extra benchmark for determining the influence of `len` with - /// regard to the overall weight. + /// Make sure to charge a proportional amount of weight if `len` is not fixed. fn read_as_unbounded<D: Decode>(&self, ptr: u32, len: u32) -> Result<D, DispatchError> { 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::<T>::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<D: Decode + MaxEncodedLen>(&self, ptr: u32) -> Result<D, DispatchError> { - 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::<T>::DecodingFailed))?; - Ok(decoded) - } } /// Allows syscalls access to the PolkaVM instance they are executing in. @@ -164,8 +143,8 @@ pub trait Memory<T: Config> { pub trait PolkaVmInstance<T: Config>: Memory<T> { 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); + fn read_input_regs(&self) -> (u64, u64, u64, u64, u64, u64); + fn write_output(&mut self, output: u64); } // Memory implementation used in benchmarking where guest memory is mapped into the host. @@ -191,6 +170,10 @@ impl<T: Config> Memory<T> for [u8] { bound_checked.copy_from_slice(buf); Ok(()) } + + fn zero(&mut self, ptr: u32, len: u32) -> Result<(), DispatchError> { + <[u8] as Memory<T>>::write(self, ptr, &vec![0; len as usize]) + } } impl<T: Config> Memory<T> for polkavm::RawInstance { @@ -203,6 +186,10 @@ impl<T: Config> Memory<T> for polkavm::RawInstance { fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> { self.write_memory(ptr, buf).map_err(|_| Error::<T>::OutOfBounds.into()) } + + fn zero(&mut self, ptr: u32, len: u32) -> Result<(), DispatchError> { + self.zero_memory(ptr, len).map_err(|_| Error::<T>::OutOfBounds.into()) + } } impl<T: Config> PolkaVmInstance<T> for polkavm::RawInstance { @@ -214,7 +201,7 @@ impl<T: Config> PolkaVmInstance<T> for polkavm::RawInstance { self.set_gas(gas) } - fn read_input_regs(&self) -> (u32, u32, u32, u32, u32, u32) { + fn read_input_regs(&self) -> (u64, u64, u64, u64, u64, u64) { ( self.reg(polkavm::Reg::A0), self.reg(polkavm::Reg::A1), @@ -225,7 +212,7 @@ impl<T: Config> PolkaVmInstance<T> for polkavm::RawInstance { ) } - fn write_output(&mut self, output: u32) { + fn write_output(&mut self, output: u64) { self.set_reg(polkavm::Reg::A0, output); } } @@ -296,8 +283,18 @@ pub enum RuntimeCosts { CopyFromContract(u32), /// Weight charged for copying data to the sandbox. CopyToContract(u32), + /// Weight of calling `seal_call_data_load``. + CallDataLoad, + /// Weight of calling `seal_call_data_copy`. + CallDataCopy(u32), /// Weight of calling `seal_caller`. Caller, + /// Weight of calling `seal_call_data_size`. + CallDataSize, + /// Weight of calling `seal_return_data_size`. + ReturnDataSize, + /// Weight of calling `seal_to_account_id`. + ToAccountId, /// Weight of calling `seal_origin`. Origin, /// Weight of calling `seal_is_contract`. @@ -314,6 +311,8 @@ pub enum RuntimeCosts { CallerIsRoot, /// Weight of calling `seal_address`. Address, + /// Weight of calling `seal_ref_time_left`. + RefTimeLeft, /// Weight of calling `seal_weight_left`. WeightLeft, /// Weight of calling `seal_balance`. @@ -328,16 +327,22 @@ pub enum RuntimeCosts { BlockNumber, /// Weight of calling `seal_block_hash`. BlockHash, + /// Weight of calling `seal_block_author`. + BlockAuthor, + /// Weight of calling `seal_gas_price`. + GasPrice, + /// Weight of calling `seal_base_fee`. + BaseFee, /// Weight of calling `seal_now`. Now, + /// Weight of calling `seal_gas_limit`. + GasLimit, /// 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_terminate`. + Terminate, /// 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. @@ -390,10 +395,6 @@ pub enum RuntimeCosts { SetCodeHash, /// Weight of calling `ecdsa_to_eth_address` EcdsaToEthAddress, - /// Weight of calling `lock_delegate_dependency` - LockDelegateDependency, - /// Weight of calling `unlock_delegate_dependency` - UnlockDelegateDependency, /// Weight of calling `get_immutable_dependency` GetImmutableData(u32), /// Weight of calling `set_immutable_dependency` @@ -456,17 +457,23 @@ impl<T: Config> Token<T> for RuntimeCosts { use self::RuntimeCosts::*; match *self { HostFn => cost_args!(noop_host_fn, 1), - CopyToContract(len) => T::WeightInfo::seal_input(len), + CopyToContract(len) => T::WeightInfo::seal_copy_to_contract(len), CopyFromContract(len) => T::WeightInfo::seal_return(len), + CallDataSize => T::WeightInfo::seal_call_data_size(), + ReturnDataSize => T::WeightInfo::seal_return_data_size(), + CallDataLoad => T::WeightInfo::seal_call_data_load(), + CallDataCopy(len) => T::WeightInfo::seal_call_data_copy(len), Caller => T::WeightInfo::seal_caller(), Origin => T::WeightInfo::seal_origin(), IsContract => T::WeightInfo::seal_is_contract(), + ToAccountId => T::WeightInfo::seal_to_account_id(), CodeHash => T::WeightInfo::seal_code_hash(), CodeSize => T::WeightInfo::seal_code_size(), 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(), + RefTimeLeft => T::WeightInfo::seal_ref_time_left(), WeightLeft => T::WeightInfo::seal_weight_left(), Balance => T::WeightInfo::seal_balance(), BalanceOf => T::WeightInfo::seal_balance_of(), @@ -474,11 +481,14 @@ impl<T: Config> Token<T> for RuntimeCosts { MinimumBalance => T::WeightInfo::seal_minimum_balance(), BlockNumber => T::WeightInfo::seal_block_number(), BlockHash => T::WeightInfo::seal_block_hash(), + BlockAuthor => T::WeightInfo::seal_block_author(), + GasPrice => T::WeightInfo::seal_gas_price(), + BaseFee => T::WeightInfo::seal_base_fee(), Now => T::WeightInfo::seal_now(), + GasLimit => T::WeightInfo::seal_gas_limit(), WeightToFee => T::WeightInfo::seal_weight_to_fee(), - Terminate(locked_dependencies) => T::WeightInfo::seal_terminate(locked_dependencies), + Terminate => T::WeightInfo::seal_terminate(), 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) }, @@ -515,8 +525,6 @@ impl<T: Config> Token<T> for RuntimeCosts { 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(), GetImmutableData(len) => T::WeightInfo::seal_get_immutable_data(len), SetImmutableData(len) => T::WeightInfo::seal_set_immutable_data(len), } @@ -558,6 +566,11 @@ fn already_charged(_: u32) -> Option<RuntimeCosts> { None } +/// Helper to extract two `u32` values from a given `u64` register. +fn extract_hi_lo(reg: u64) -> (u32, u32) { + ((reg >> 32) as u32, reg as u32) +} + /// Can only be used for one call. pub struct Runtime<'a, E: Ext, M: ?Sized> { ext: &'a mut E, @@ -572,7 +585,6 @@ impl<'a, E: Ext, M: PolkaVmInstance<E::T>> Runtime<'a, E, M> { interrupt: Result<polkavm::InterruptKind, polkavm::Error>, module: &polkavm::Module, instance: &mut M, - api_version: ApiVersion, ) -> Option<ExecResult> { use polkavm::InterruptKind::*; @@ -592,7 +604,7 @@ impl<'a, E: Ext, M: PolkaVmInstance<E::T>> Runtime<'a, E, M> { let Some(syscall_symbol) = module.imports().get(idx) else { return Some(Err(<Error<E::T>>::InvalidSyscall.into())); }; - match self.handle_ecall(instance, syscall_symbol.as_bytes(), api_version) { + match self.handle_ecall(instance, syscall_symbol.as_bytes()) { Ok(None) => None, Ok(Some(return_value)) => { instance.write_output(return_value); @@ -659,10 +671,7 @@ impl<'a, E: Ext, M: ?Sized + Memory<E::T>> Runtime<'a, E, M> { 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()); - }; + log::debug!(target: LOG_TARGET, "call failed with: {e:?}"); Ok(ErrorReturnCode::get()) }, } @@ -766,29 +775,26 @@ impl<'a, E: Ext, M: ?Sized + Memory<E::T>> Runtime<'a, E, M> { Ok(()) } - /// Fallible conversion of `DispatchError` to `ReturnErrorCode`. - fn err_into_return_code(from: DispatchError) -> Result<ReturnErrorCode, DispatchError> { - use ReturnErrorCode::*; - - let transfer_failed = Error::<E::T>::TransferFailed.into(); - let no_code = Error::<E::T>::CodeNotFound.into(); - let not_found = Error::<E::T>::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 `ExecError` to `ReturnErrorCode`. + /// + /// This is used when converting the error returned from a subcall in order to decide + /// whether to trap the caller or allow handling of the error. fn exec_error_into_return_code(from: ExecError) -> Result<ReturnErrorCode, DispatchError> { use crate::exec::ErrorOrigin::Callee; + use ReturnErrorCode::*; + + let transfer_failed = Error::<E::T>::TransferFailed.into(); + let out_of_gas = Error::<E::T>::OutOfGas.into(); + let out_of_deposit = Error::<E::T>::StorageDepositLimitExhausted.into(); + let duplicate_contract = Error::<E::T>::DuplicateContract.into(); + // errors in the callee do not trap the caller match (from.error, from.origin) { - (_, Callee) => Ok(ReturnErrorCode::CalleeTrapped), - (err, _) => Self::err_into_return_code(err), + (err, _) if err == transfer_failed => Ok(TransferFailed), + (err, _) if err == duplicate_contract => Ok(DuplicateContractAddress), + (err, Callee) if err == out_of_gas || err == out_of_deposit => Ok(OutOfResources), + (_, Callee) => Ok(CalleeTrapped), + (err, _) => Err(err), } } @@ -999,8 +1005,7 @@ impl<'a, E: Ext, M: ?Sized + Memory<E::T>> Runtime<'a, E, M> { self.charge_gas(call_type.cost())?; let callee = memory.read_h160(callee_ptr)?; - let deposit_limit = - if deposit_ptr == SENTINEL { U256::zero() } else { memory.read_u256(deposit_ptr)? }; + let deposit_limit = memory.read_u256(deposit_ptr)?; let input_data = if flags.contains(CallFlags::CLONE_INPUT) { let input = self.input_data.as_ref().ok_or(Error::<E::T>::InputForwarded)?; @@ -1086,8 +1091,7 @@ impl<'a, E: Ext, M: ?Sized + Memory<E::T>> Runtime<'a, E, M> { salt_ptr: u32, ) -> Result<ReturnErrorCode, TrapReason> { self.charge_gas(RuntimeCosts::Instantiate { input_data_len })?; - let deposit_limit: U256 = - if deposit_ptr == SENTINEL { U256::zero() } else { memory.read_u256(deposit_ptr)? }; + let deposit_limit: U256 = memory.read_u256(deposit_ptr)?; let value = memory.read_u256(value_ptr)?; let code_hash = memory.read_h256(code_hash_ptr)?; let input_data = memory.read(input_data_ptr, input_data_len)?; @@ -1132,15 +1136,6 @@ impl<'a, E: Ext, M: ?Sized + Memory<E::T>> Runtime<'a, E, M> { Err(err) => Ok(Self::exec_error_into_return_code(err)?), } } - - 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 = memory.read_h160(beneficiary_ptr)?; - self.ext.terminate(&beneficiary)?; - Err(TrapReason::Termination) - } } // This is the API exposed to contracts. @@ -1153,14 +1148,18 @@ impl<'a, E: Ext, M: ?Sized + Memory<E::T>> Runtime<'a, E, M> { #[define_env] pub mod env { /// Noop function used to benchmark the time it takes to execute an empty function. + /// + /// Marked as stable because it needs to be called from benchmarks even when the benchmarked + /// parachain has unstable functions disabled. #[cfg(feature = "runtime-benchmarks")] + #[stable] 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)] + #[stable] #[mutating] fn set_storage( &mut self, @@ -1174,23 +1173,9 @@ pub mod env { 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<u32, TrapReason> { - 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)] + #[stable] fn get_storage( &mut self, memory: &mut M, @@ -1203,52 +1188,24 @@ pub mod env { 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<u32, TrapReason> { - 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<ReturnErrorCode, TrapReason> { - self.take_storage(memory, flags, key_ptr, key_len, out_ptr, out_len_ptr) - } - /// Make a call to another contract. /// See [`pallet_revive_uapi::HostFn::call`]. - #[api_version(0)] + #[stable] fn call( &mut self, memory: &mut M, - flags: u32, - callee_ptr: u32, + flags_and_callee: u64, 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, + deposit_and_value: u64, + input_data: u64, + output_data: u64, ) -> Result<ReturnErrorCode, TrapReason> { + let (flags, callee_ptr) = extract_hi_lo(flags_and_callee); + let (deposit_ptr, value_ptr) = extract_hi_lo(deposit_and_value); + let (input_data_len, input_data_ptr) = extract_hi_lo(input_data); + let (output_len_ptr, output_ptr) = extract_hi_lo(output_data); + self.call( memory, CallFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?, @@ -1265,20 +1222,21 @@ pub mod env { /// Execute code in the context (storage, caller, value) of the current contract. /// See [`pallet_revive_uapi::HostFn::delegate_call`]. - #[api_version(0)] + #[stable] fn delegate_call( &mut self, memory: &mut M, - flags: u32, - address_ptr: u32, + flags_and_callee: u64, ref_time_limit: u64, proof_size_limit: u64, deposit_ptr: u32, - input_data_ptr: u32, - input_data_len: u32, - output_ptr: u32, - output_len_ptr: u32, + input_data: u64, + output_data: u64, ) -> Result<ReturnErrorCode, TrapReason> { + let (flags, address_ptr) = extract_hi_lo(flags_and_callee); + let (input_data_len, input_data_ptr) = extract_hi_lo(input_data); + let (output_len_ptr, output_ptr) = extract_hi_lo(output_data); + self.call( memory, CallFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?, @@ -1295,23 +1253,29 @@ pub mod env { /// Instantiate a contract with the specified code hash. /// See [`pallet_revive_uapi::HostFn::instantiate`]. - #[api_version(0)] + #[stable] #[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, - output_ptr: u32, - output_len_ptr: u32, - salt_ptr: u32, + deposit_and_value: u64, + input_data: u64, + output_data: u64, + address_and_salt: u64, ) -> Result<ReturnErrorCode, TrapReason> { + let (deposit_ptr, value_ptr) = extract_hi_lo(deposit_and_value); + let (input_data_len, code_hash_ptr) = extract_hi_lo(input_data); + let (output_len_ptr, output_ptr) = extract_hi_lo(output_data); + let (address_ptr, salt_ptr) = extract_hi_lo(address_and_salt); + let Some(input_data_ptr) = code_hash_ptr.checked_add(32) else { + return Err(Error::<E::T>::OutOfBounds.into()); + }; + let Some(input_data_len) = input_data_len.checked_sub(32) else { + return Err(Error::<E::T>::OutOfBounds.into()); + }; + self.instantiate( memory, code_hash_ptr, @@ -1327,32 +1291,83 @@ pub mod env { ) } - /// 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) + /// Returns the total size of the contract call input data. + /// See [`pallet_revive_uapi::HostFn::call_data_size `]. + #[stable] + fn call_data_size(&mut self, memory: &mut M) -> Result<u64, TrapReason> { + self.charge_gas(RuntimeCosts::CallDataSize)?; + Ok(self + .input_data + .as_ref() + .map(|input| input.len().try_into().expect("usize fits into u64; qed")) + .unwrap_or_default()) } /// 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::<E::T>::InputForwarded.into()) + /// See [`pallet_revive_uapi::HostFn::call_data_copy`]. + #[stable] + fn call_data_copy( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len: u32, + offset: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::CallDataCopy(out_len))?; + + let Some(input) = self.input_data.as_ref() else { + return Err(Error::<E::T>::InputForwarded.into()); + }; + + let start = offset as usize; + if start >= input.len() { + memory.zero(out_ptr, out_len)?; + return Ok(()); } + + let end = start.saturating_add(out_len as usize).min(input.len()); + memory.write(out_ptr, &input[start..end])?; + + let bytes_written = (end - start) as u32; + memory.zero(out_ptr.saturating_add(bytes_written), out_len - bytes_written)?; + + Ok(()) + } + + /// Stores the U256 value at given call input `offset` into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::call_data_load`]. + #[stable] + fn call_data_load( + &mut self, + memory: &mut M, + out_ptr: u32, + offset: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::CallDataLoad)?; + + let Some(input) = self.input_data.as_ref() else { + return Err(Error::<E::T>::InputForwarded.into()); + }; + + let mut data = [0; 32]; + let start = offset as usize; + let data = if start >= input.len() { + data // Any index is valid to request; OOB offsets return zero. + } else { + let end = start.saturating_add(32).min(input.len()); + data[..end - start].copy_from_slice(&input[start..end]); + data.reverse(); + data // Solidity expects right-padded data + }; + + self.write_fixed_sandbox_output(memory, out_ptr, &data, false, already_charged)?; + + Ok(()) } /// Cease contract execution and save a data buffer as a result of the execution. /// See [`pallet_revive_uapi::HostFn::return_value`]. - #[api_version(0)] + #[stable] fn seal_return( &mut self, memory: &mut M, @@ -1366,7 +1381,7 @@ pub mod env { /// Stores the address of the caller into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::caller`]. - #[api_version(0)] + #[stable] fn caller(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::Caller)?; let caller = <E::T as Config>::AddressMapper::to_address(self.ext.caller().account_id()?); @@ -1381,7 +1396,7 @@ pub mod env { /// Stores the address of the call stack origin into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::origin`]. - #[api_version(0)] + #[stable] fn origin(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::Origin)?; let origin = <E::T as Config>::AddressMapper::to_address(self.ext.origin().account_id()?); @@ -1394,18 +1409,9 @@ pub mod env { )?) } - /// 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<u32, TrapReason> { - self.charge_gas(RuntimeCosts::IsContract)?; - let address = memory.read_h160(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)] + #[stable] fn code_hash(&mut self, memory: &mut M, addr_ptr: u32, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::CodeHash)?; let address = memory.read_h160(addr_ptr)?; @@ -1420,53 +1426,16 @@ pub mod env { /// Retrieve the code size for a given contract address. /// See [`pallet_revive_uapi::HostFn::code_size`]. - #[api_version(0)] - fn code_size(&mut self, memory: &mut M, addr_ptr: u32, out_ptr: u32) -> Result<(), TrapReason> { + #[stable] + fn code_size(&mut self, memory: &mut M, addr_ptr: u32) -> Result<u64, TrapReason> { self.charge_gas(RuntimeCosts::CodeSize)?; let address = memory.read_h160(addr_ptr)?; - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &self.ext.code_size(&address).to_little_endian(), - false, - already_charged, - )?) - } - - /// 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) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::OwnCodeHash)?; - let code_hash = *self.ext.own_code_hash(); - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - code_hash.as_bytes(), - 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<u32, TrapReason> { - 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<u32, TrapReason> { - self.charge_gas(RuntimeCosts::CallerIsRoot)?; - Ok(self.ext.caller_is_root() as u32) + Ok(self.ext.code_size(&address)) } /// Stores the address of the current contract into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::address`]. - #[api_version(0)] + #[stable] fn address(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::Address)?; let address = self.ext.address(); @@ -1481,7 +1450,7 @@ pub mod env { /// Stores the price for the specified amount of weight into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::weight_to_fee`]. - #[api_version(0)] + #[stable] fn weight_to_fee( &mut self, memory: &mut M, @@ -1500,46 +1469,26 @@ pub mod env { )?) } - /// 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::WeightLeft)?; - 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 immutable data into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::get_immutable_data`]. - #[api_version(0)] + #[stable] fn get_immutable_data( &mut self, memory: &mut M, out_ptr: u32, out_len_ptr: u32, ) -> Result<(), TrapReason> { - let charged = self.charge_gas(RuntimeCosts::GetImmutableData(limits::IMMUTABLE_BYTES))?; + // quering the length is free as it is stored with the contract metadata + let len = self.ext.immutable_data_len(); + self.charge_gas(RuntimeCosts::GetImmutableData(len))?; let data = self.ext.get_immutable_data()?; - self.adjust_gas(charged, RuntimeCosts::GetImmutableData(data.len() as u32)); self.write_sandbox_output(memory, out_ptr, out_len_ptr, &data, false, already_charged)?; Ok(()) } /// Attaches the supplied immutable data to the currently executing contract. /// See [`pallet_revive_uapi::HostFn::set_immutable_data`]. - #[api_version(0)] + #[stable] fn set_immutable_data(&mut self, memory: &mut M, ptr: u32, len: u32) -> Result<(), TrapReason> { if len > limits::IMMUTABLE_BYTES { return Err(Error::<E::T>::OutOfBounds.into()); @@ -1553,7 +1502,7 @@ pub mod env { /// Stores the *free* balance of the current account into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::balance`]. - #[api_version(0)] + #[stable] fn balance(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::Balance)?; Ok(self.write_fixed_sandbox_output( @@ -1567,7 +1516,7 @@ pub mod env { /// Stores the *free* balance of the supplied address into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::balance`]. - #[api_version(0)] + #[stable] fn balance_of( &mut self, memory: &mut M, @@ -1587,7 +1536,7 @@ pub mod env { /// Returns the chain ID. /// See [`pallet_revive_uapi::HostFn::chain_id`]. - #[api_version(0)] + #[stable] fn chain_id(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { Ok(self.write_fixed_sandbox_output( memory, @@ -1598,9 +1547,17 @@ pub mod env { )?) } + /// Returns the block ref_time limit. + /// See [`pallet_revive_uapi::HostFn::gas_limit`]. + #[stable] + fn gas_limit(&mut self, memory: &mut M) -> Result<u64, TrapReason> { + self.charge_gas(RuntimeCosts::GasLimit)?; + Ok(<E::T as frame_system::Config>::BlockWeights::get().max_block.ref_time()) + } + /// Stores the value transferred along with this call/instantiate into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::value_transferred`]. - #[api_version(0)] + #[stable] fn value_transferred(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::ValueTransferred)?; Ok(self.write_fixed_sandbox_output( @@ -1612,29 +1569,37 @@ pub mod env { )?) } - /// 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) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::Now)?; + /// Returns the simulated ethereum `GASPRICE` value. + /// See [`pallet_revive_uapi::HostFn::gas_price`]. + #[stable] + fn gas_price(&mut self, memory: &mut M) -> Result<u64, TrapReason> { + self.charge_gas(RuntimeCosts::GasPrice)?; + Ok(GAS_PRICE.into()) + } + + /// Returns the simulated ethereum `BASEFEE` value. + /// See [`pallet_revive_uapi::HostFn::base_fee`]. + #[stable] + fn base_fee(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::BaseFee)?; Ok(self.write_fixed_sandbox_output( memory, out_ptr, - &self.ext.now().to_little_endian(), + &U256::zero().to_little_endian(), 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) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::MinimumBalance)?; + /// Load the latest block timestamp into the supplied buffer + /// See [`pallet_revive_uapi::HostFn::now`]. + #[stable] + fn now(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::Now)?; Ok(self.write_fixed_sandbox_output( memory, out_ptr, - &self.ext.minimum_balance().to_little_endian(), + &self.ext.now().to_little_endian(), false, already_charged, )?) @@ -1642,7 +1607,7 @@ pub mod env { /// Deposit a contract event with the data buffer and optional list of topics. /// See [pallet_revive_uapi::HostFn::deposit_event] - #[api_version(0)] + #[stable] #[mutating] fn deposit_event( &mut self, @@ -1682,7 +1647,7 @@ pub mod env { /// Stores the current block number of the current contract into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::block_number`]. - #[api_version(0)] + #[stable] fn block_number(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::BlockNumber)?; Ok(self.write_fixed_sandbox_output( @@ -1696,7 +1661,7 @@ pub mod env { /// Stores the block hash at given block height into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::block_hash`]. - #[api_version(0)] + #[stable] fn block_hash( &mut self, memory: &mut M, @@ -1715,25 +1680,28 @@ pub mod env { )?) } - /// 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, + /// Stores the current block author into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::block_author`]. + #[stable] + fn block_author(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::BlockAuthor)?; + let block_author = self + .ext + .block_author() + .map(|account| <E::T as Config>::AddressMapper::to_address(&account)) + .unwrap_or(H160::zero()); + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &block_author.as_bytes(), + false, + already_charged, )?) } /// Computes the KECCAK 256-bit hash on the given input buffer. /// See [`pallet_revive_uapi::HostFn::hash_keccak_256`]. - #[api_version(0)] + #[stable] fn hash_keccak_256( &mut self, memory: &mut M, @@ -1747,36 +1715,53 @@ pub mod env { )?) } - /// 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, - )?) + /// Stores the length of the data returned by the last call into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::return_data_size`]. + #[stable] + fn return_data_size(&mut self, memory: &mut M) -> Result<u64, TrapReason> { + self.charge_gas(RuntimeCosts::ReturnDataSize)?; + Ok(self + .ext + .last_frame_output() + .data + .len() + .try_into() + .expect("usize fits into u64; qed")) } - /// 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( + /// Stores data returned by the last call, starting from `offset`, into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::return_data`]. + #[stable] + fn return_data_copy( &mut self, memory: &mut M, - input_ptr: u32, - input_len: u32, - output_ptr: u32, + out_ptr: u32, + out_len_ptr: u32, + offset: 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, - )?) + let output = mem::take(self.ext.last_frame_output_mut()); + let result = if offset as usize > output.data.len() { + Err(Error::<E::T>::OutOfBounds.into()) + } else { + self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &output.data[offset as usize..], + false, + |len| Some(RuntimeCosts::CopyToContract(len)), + ) + }; + *self.ext.last_frame_output_mut() = output; + Ok(result?) + } + + /// Returns the amount of ref_time left. + /// See [`pallet_revive_uapi::HostFn::ref_time_left`]. + #[stable] + fn ref_time_left(&mut self, memory: &mut M) -> Result<u64, TrapReason> { + self.charge_gas(RuntimeCosts::RefTimeLeft)?; + Ok(self.ext.gas_meter().gas_left().ref_time()) } /// Call into the chain extension provided by the chain if any. @@ -1809,28 +1794,6 @@ pub mod env { 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<ReturnErrorCode, TrapReason> { - 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] @@ -1850,86 +1813,47 @@ pub mod env { ) } - /// 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<ReturnErrorCode, TrapReason> { - use frame_support::dispatch::DispatchInfo; - use xcm::VersionedXcm; - use xcm_builder::{ExecuteController, ExecuteControllerWeightInfo}; - - self.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; - let message: VersionedXcm<CallOf<E::T>> = memory.read_as_unbounded(msg_ptr, msg_len)?; - - let execute_weight = - <<E::T as Config>::Xcm as ExecuteController<_, _>>::WeightInfo::execute(); - let weight = self.ext.gas_meter().gas_left().max(execute_weight); - let dispatch_info = DispatchInfo { call_weight: weight, ..Default::default() }; - - self.call_dispatchable::<XcmExecutionFailed>( - dispatch_info, - RuntimeCosts::CallXcmExecute, - |runtime| { - let origin = crate::RawOrigin::Signed(runtime.ext.account_id().clone()).into(); - let weight_used = <<E::T as Config>::Xcm>::execute( - origin, - Box::new(message), - weight.saturating_sub(execute_weight), - )?; + /// Checks whether the caller of the current contract is the origin of the whole call stack. + /// See [`pallet_revive_uapi::HostFn::caller_is_origin`]. + fn caller_is_origin(&mut self, _memory: &mut M) -> Result<u32, TrapReason> { + self.charge_gas(RuntimeCosts::CallerIsOrigin)?; + Ok(self.ext.caller_is_origin() as u32) + } - Ok(Some(weight_used.saturating_add(execute_weight)).into()) - }, - ) + /// Checks whether the caller of the current contract is root. + /// See [`pallet_revive_uapi::HostFn::caller_is_root`]. + fn caller_is_root(&mut self, _memory: &mut M) -> Result<u32, TrapReason> { + self.charge_gas(RuntimeCosts::CallerIsRoot)?; + Ok(self.ext.caller_is_root() as u32) } - /// Send an XCM program from the contract to the specified destination. - /// See [`pallet_revive_uapi::HostFn::send_xcm`]. + /// Clear the value at the given key in the contract storage. + /// See [`pallet_revive_uapi::HostFn::clear_storage`] #[mutating] - fn xcm_send( + fn clear_storage( &mut self, memory: &mut M, - dest_ptr: u32, - dest_len: u32, - msg_ptr: u32, - msg_len: u32, - output_ptr: u32, - ) -> Result<ReturnErrorCode, TrapReason> { - use xcm::{VersionedLocation, VersionedXcm}; - use xcm_builder::{SendController, SendControllerWeightInfo}; - - self.charge_gas(RuntimeCosts::CopyFromContract(dest_len))?; - let dest: VersionedLocation = memory.read_as_unbounded(dest_ptr, dest_len)?; - - self.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; - let message: VersionedXcm<()> = memory.read_as_unbounded(msg_ptr, msg_len)?; - - let weight = <<E::T as Config>::Xcm as SendController<_>>::WeightInfo::send(); - self.charge_gas(RuntimeCosts::CallRuntime(weight))?; - let origin = crate::RawOrigin::Signed(self.ext.account_id().clone()).into(); + flags: u32, + key_ptr: u32, + key_len: u32, + ) -> Result<u32, TrapReason> { + self.clear_storage(memory, flags, key_ptr, key_len) + } - match <<E::T as Config>::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) - }, - } + /// Checks whether there is a value stored under the given key. + /// See [`pallet_revive_uapi::HostFn::contains_storage`] + fn contains_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + ) -> Result<u32, TrapReason> { + self.contains_storage(memory, flags, key_ptr, key_len) } /// 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, @@ -1958,9 +1882,122 @@ pub mod env { } } + /// Calculates Ethereum address from the ECDSA compressed public key and stores + /// See [`pallet_revive_uapi::HostFn::ecdsa_to_eth_address`]. + fn ecdsa_to_eth_address( + &mut self, + memory: &mut M, + key_ptr: u32, + out_ptr: u32, + ) -> Result<ReturnErrorCode, TrapReason> { + 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), + } + } + + /// Computes the BLAKE2 128-bit hash on the given input buffer. + /// See [`pallet_revive_uapi::HostFn::hash_blake2_128`]. + 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, + )?) + } + + /// Computes the BLAKE2 256-bit hash on the given input buffer. + /// See [`pallet_revive_uapi::HostFn::hash_blake2_256`]. + 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 SHA2 256-bit hash on the given input buffer. + /// See [`pallet_revive_uapi::HostFn::hash_sha2_256`]. + 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, + )?) + } + + /// Checks whether a specified address belongs to a contract. + /// See [`pallet_revive_uapi::HostFn::is_contract`]. + fn is_contract(&mut self, memory: &mut M, account_ptr: u32) -> Result<u32, TrapReason> { + self.charge_gas(RuntimeCosts::IsContract)?; + let address = memory.read_h160(account_ptr)?; + Ok(self.ext.is_contract(&address) as u32) + } + + /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::minimum_balance`]. + fn minimum_balance(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::MinimumBalance)?; + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &self.ext.minimum_balance().to_little_endian(), + false, + already_charged, + )?) + } + + /// Retrieve the code hash of the currently executing contract. + /// See [`pallet_revive_uapi::HostFn::own_code_hash`]. + fn own_code_hash(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::OwnCodeHash)?; + let code_hash = *self.ext.own_code_hash(); + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + code_hash.as_bytes(), + false, + already_charged, + )?) + } + + /// Replace the contract code at the specified address with new code. + /// See [`pallet_revive_uapi::HostFn::set_code_hash`]. + /// + /// Disabled until the internal implementation takes care of collecting + /// the immutable data of the new code hash. + #[mutating] + fn set_code_hash(&mut self, memory: &mut M, code_hash_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::SetCodeHash)?; + let code_hash: H256 = memory.read_h256(code_hash_ptr)?; + self.ext.set_code_hash(code_hash)?; + Ok(()) + } + /// Verify a sr25519 signature /// See [`pallet_revive_uapi::HostFn::sr25519_verify`]. - #[api_version(0)] fn sr25519_verify( &mut self, memory: &mut M, @@ -1986,117 +2023,143 @@ pub mod env { } } - /// Replace the contract code at the specified address with new code. - /// See [`pallet_revive_uapi::HostFn::set_code_hash`]. - /// - /// Disabled until the internal implementation takes care of collecting - /// the immutable data of the new code hash. + /// Retrieve and remove the value under the given key from storage. + /// See [`pallet_revive_uapi::HostFn::take_storage`] #[mutating] - fn set_code_hash( + fn take_storage( &mut self, memory: &mut M, - code_hash_ptr: u32, + flags: u32, + key_ptr: u32, + key_len: u32, + out_ptr: u32, + out_len_ptr: u32, ) -> Result<ReturnErrorCode, TrapReason> { - self.charge_gas(RuntimeCosts::SetCodeHash)?; - let code_hash: H256 = memory.read_h256(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), - } + self.take_storage(memory, flags, key_ptr, key_len, out_ptr, out_len_ptr) } - /// 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( + /// Remove the calling account and transfer remaining **free** balance. + /// See [`pallet_revive_uapi::HostFn::terminate`]. + #[mutating] + fn terminate(&mut self, memory: &mut M, beneficiary_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::Terminate)?; + let beneficiary = memory.read_h160(beneficiary_ptr)?; + self.ext.terminate(&beneficiary)?; + Err(TrapReason::Termination) + } + + /// Stores the amount of weight left into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::weight_left`]. + fn weight_left( &mut self, memory: &mut M, - key_ptr: u32, out_ptr: u32, - ) -> Result<ReturnErrorCode, TrapReason> { - 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), - } + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::WeightLeft)?; + 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, + )?) } - /// Adds a new delegate dependency to the contract. - /// See [`pallet_revive_uapi::HostFn::lock_delegate_dependency`]. - #[api_version(0)] + /// Execute an XCM program locally, using the contract's address as the origin. + /// See [`pallet_revive_uapi::HostFn::execute_xcm`]. #[mutating] - fn lock_delegate_dependency( + fn xcm_execute( &mut self, memory: &mut M, - code_hash_ptr: u32, - ) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::LockDelegateDependency)?; - let code_hash = memory.read_h256(code_hash_ptr)?; - self.ext.lock_delegate_dependency(code_hash)?; - Ok(()) + msg_ptr: u32, + msg_len: u32, + ) -> Result<ReturnErrorCode, TrapReason> { + use frame_support::dispatch::DispatchInfo; + use xcm::VersionedXcm; + use xcm_builder::{ExecuteController, ExecuteControllerWeightInfo}; + + self.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; + let message: VersionedXcm<CallOf<E::T>> = memory.read_as_unbounded(msg_ptr, msg_len)?; + + let execute_weight = + <<E::T as Config>::Xcm as ExecuteController<_, _>>::WeightInfo::execute(); + let weight = self.ext.gas_meter().gas_left().max(execute_weight); + let dispatch_info = DispatchInfo { call_weight: weight, ..Default::default() }; + + self.call_dispatchable::<XcmExecutionFailed>( + dispatch_info, + RuntimeCosts::CallXcmExecute, + |runtime| { + let origin = crate::RawOrigin::Signed(runtime.ext.account_id().clone()).into(); + let weight_used = <<E::T as Config>::Xcm>::execute( + origin, + Box::new(message), + weight.saturating_sub(execute_weight), + )?; + + Ok(Some(weight_used.saturating_add(execute_weight)).into()) + }, + ) } - /// Removes the delegate dependency from the contract. - /// see [`pallet_revive_uapi::HostFn::unlock_delegate_dependency`]. - #[api_version(0)] + /// Send an XCM program from the contract to the specified destination. + /// See [`pallet_revive_uapi::HostFn::send_xcm`]. #[mutating] - fn unlock_delegate_dependency( + fn xcm_send( &mut self, memory: &mut M, - code_hash_ptr: u32, - ) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::UnlockDelegateDependency)?; - let code_hash = memory.read_h256(code_hash_ptr)?; - self.ext.unlock_delegate_dependency(&code_hash)?; - Ok(()) - } + dest_ptr: u32, + dest_len: u32, + msg_ptr: u32, + msg_len: u32, + output_ptr: u32, + ) -> Result<ReturnErrorCode, TrapReason> { + use xcm::{VersionedLocation, VersionedXcm}; + use xcm_builder::{SendController, SendControllerWeightInfo}; - /// Stores the length of the data returned by the last call into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::return_data_size`]. - #[api_version(0)] - fn return_data_size(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &U256::from(self.ext.last_frame_output().data.len()).to_little_endian(), - false, - |len| Some(RuntimeCosts::CopyToContract(len)), - )?) + self.charge_gas(RuntimeCosts::CopyFromContract(dest_len))?; + let dest: VersionedLocation = memory.read_as_unbounded(dest_ptr, dest_len)?; + + self.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; + let message: VersionedXcm<()> = memory.read_as_unbounded(msg_ptr, msg_len)?; + + let weight = <<E::T as Config>::Xcm as SendController<_>>::WeightInfo::send(); + self.charge_gas(RuntimeCosts::CallRuntime(weight))?; + let origin = crate::RawOrigin::Signed(self.ext.account_id().clone()).into(); + + match <<E::T as Config>::Xcm>::send(origin, dest.into(), message.into()) { + Ok(message_id) => { + memory.write(output_ptr, &message_id.encode())?; + Ok(ReturnErrorCode::Success) + }, + Err(e) => { + log::debug!(target: LOG_TARGET, "seal0::xcm_send failed with: {e:?}"); + Ok(ReturnErrorCode::XcmSendFailed) + }, + } } - /// Stores data returned by the last call, starting from `offset`, into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::return_data`]. - #[api_version(0)] - fn return_data_copy( + /// Retrieves the account id for a specified contract address. + /// + /// See [`pallet_revive_uapi::HostFn::to_account_id`]. + fn to_account_id( &mut self, memory: &mut M, + addr_ptr: u32, out_ptr: u32, - out_len_ptr: u32, - offset: u32, ) -> Result<(), TrapReason> { - let output = mem::take(self.ext.last_frame_output_mut()); - let result = if offset as usize > output.data.len() { - Err(Error::<E::T>::OutOfBounds.into()) - } else { - self.write_sandbox_output( - memory, - out_ptr, - out_len_ptr, - &output.data[offset as usize..], - false, - |len| Some(RuntimeCosts::CopyToContract(len)), - ) - }; - *self.ext.last_frame_output_mut() = output; - Ok(result?) + self.charge_gas(RuntimeCosts::ToAccountId)?; + let address = memory.read_h160(addr_ptr)?; + let account_id = self.ext.to_account_id(&address); + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &account_id.encode(), + false, + already_charged, + )?) } } diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 96654432a5cce7b5e514036a820b79d82f8495ef..42b8a9e5e722f8aaaf2d8302d870bba0ba9d9da2 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -18,33 +18,37 @@ //! Autogenerated weights for `pallet_revive` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-11-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-02-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-wiukf8gn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` +//! HOSTNAME: `11670a4f427b`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate-node +// frame-omni-bencher +// v1 // benchmark // pallet -// --chain=dev +// --extrinsic=* +// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm +// --pallet=pallet_revive +// --header=/__w/polkadot-sdk/polkadot-sdk/substrate/HEADER-APACHE2 +// --output=/__w/polkadot-sdk/polkadot-sdk/substrate/frame/revive/src/weights.rs +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --pallet=pallet_revive +// --heap-pages=4096 +// --template=substrate/.maintain/frame-weight-template.hbs // --no-storage-info -// --no-median-slopes // --no-min-squares -// --extrinsic=* -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./substrate/frame/revive/src/weights.rs -// --header=./substrate/HEADER-APACHE2 -// --template=./substrate/.maintain/frame-weight-template.hbs +// --no-median-slopes +// --genesis-builder-policy=none +// --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic,pallet_nomination_pools,pallet_remark,pallet_transaction_storage #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] #![allow(missing_docs)] +#[allow(dead_code)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use core::marker::PhantomData; @@ -67,6 +71,7 @@ pub trait WeightInfo { fn seal_caller() -> Weight; fn seal_origin() -> Weight; fn seal_is_contract() -> Weight; + fn seal_to_account_id() -> Weight; fn seal_code_hash() -> Weight; fn seal_own_code_hash() -> Weight; fn seal_code_size() -> Weight; @@ -74,21 +79,29 @@ pub trait WeightInfo { fn seal_caller_is_root() -> Weight; fn seal_address() -> Weight; fn seal_weight_left() -> Weight; + fn seal_ref_time_left() -> Weight; fn seal_balance() -> Weight; fn seal_balance_of() -> Weight; fn seal_get_immutable_data(n: u32, ) -> Weight; fn seal_set_immutable_data(n: u32, ) -> Weight; fn seal_value_transferred() -> Weight; fn seal_minimum_balance() -> Weight; + fn seal_return_data_size() -> Weight; + fn seal_call_data_size() -> Weight; + fn seal_gas_limit() -> Weight; + fn seal_gas_price() -> Weight; + fn seal_base_fee() -> Weight; fn seal_block_number() -> Weight; + fn seal_block_author() -> Weight; fn seal_block_hash() -> Weight; fn seal_now() -> Weight; fn seal_weight_to_fee() -> Weight; - fn seal_input(n: u32, ) -> Weight; + fn seal_copy_to_contract(n: u32, ) -> Weight; + fn seal_call_data_load() -> Weight; + fn seal_call_data_copy(n: u32, ) -> Weight; fn seal_return(n: u32, ) -> Weight; - fn seal_terminate(n: u32, ) -> Weight; + fn seal_terminate() -> 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; @@ -119,8 +132,6 @@ pub trait WeightInfo { 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 instr(r: u32, ) -> Weight; } @@ -131,10 +142,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Revive::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: `109` - // Estimated: `1594` - // Minimum execution time: 2_818_000 picoseconds. - Weight::from_parts(3_058_000, 1594) + // Measured: `0` + // Estimated: `1485` + // Minimum execution time: 695_000 picoseconds. + Weight::from_parts(750_000, 1485) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -142,12 +153,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `k` is `[0, 1024]`. fn on_initialize_per_trie_key(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `425 + k * (69 ±0)` - // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 15_916_000 picoseconds. - Weight::from_parts(16_132_000, 415) - // Standard Error: 1_482 - .saturating_add(Weight::from_parts(1_185_583, 0).saturating_mul(k.into())) + // Measured: `230 + k * (69 ±0)` + // Estimated: `222 + k * (70 ±0)` + // Minimum execution time: 10_509_000 picoseconds. + Weight::from_parts(10_896_000, 222) + // Standard Error: 2_549 + .saturating_add(Weight::from_parts(1_264_033, 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)) @@ -157,7 +168,7 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Storage: `Revive::AddressSuffix` (r:2 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) @@ -169,21 +180,21 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `c` is `[0, 262144]`. fn call_with_code_per_byte(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1502` - // Estimated: `7442` - // Minimum execution time: 88_115_000 picoseconds. - Weight::from_parts(92_075_651, 7442) + // Measured: `1194` + // Estimated: `7134` + // Minimum execution time: 84_008_000 picoseconds. + Weight::from_parts(91_138_296, 7134) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:2 w:2) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, 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) @@ -192,16 +203,14 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. /// The range of component `i` is `[0, 262144]`. - fn instantiate_with_code(c: u32, i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `403` - // Estimated: `6326` - // Minimum execution time: 188_274_000 picoseconds. - Weight::from_parts(157_773_869, 6326) - // Standard Error: 11 - .saturating_add(Weight::from_parts(16, 0).saturating_mul(c.into())) - // Standard Error: 11 - .saturating_add(Weight::from_parts(4_464, 0).saturating_mul(i.into())) + fn instantiate_with_code(_c: u32, i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `93` + // Estimated: `6033` + // Minimum execution time: 172_907_000 picoseconds. + Weight::from_parts(153_592_465, 6033) + // Standard Error: 12 + .saturating_add(Weight::from_parts(4_544, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -212,29 +221,29 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, 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(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// The range of component `i` is `[0, 262144]`. fn instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1296` - // Estimated: `4739` - // Minimum execution time: 158_616_000 picoseconds. - Weight::from_parts(134_329_076, 4739) - // Standard Error: 15 - .saturating_add(Weight::from_parts(4_358, 0).saturating_mul(i.into())) + // Measured: `987` + // Estimated: `4452` + // Minimum execution time: 143_169_000 picoseconds. + Weight::from_parts(120_653_436, 4452) + // Standard Error: 16 + .saturating_add(Weight::from_parts(4_444, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Revive::AddressSuffix` (r:2 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) @@ -245,80 +254,80 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `1502` - // Estimated: `7442` - // Minimum execution time: 134_935_000 picoseconds. - Weight::from_parts(141_040_000, 7442) + // Measured: `1194` + // Estimated: `7134` + // Minimum execution time: 138_392_000 picoseconds. + Weight::from_parts(143_329_000, 7134) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:0 w:1) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. fn upload_code(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `109` - // Estimated: `3574` - // Minimum execution time: 51_026_000 picoseconds. - Weight::from_parts(53_309_143, 3574) + // Measured: `0` + // Estimated: `3465` + // Minimum execution time: 43_420_000 picoseconds. + Weight::from_parts(45_143_767, 3465) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:0 w:1) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) fn remove_code() -> Weight { // Proof Size summary in bytes: - // Measured: `285` - // Estimated: `3750` - // Minimum execution time: 44_338_000 picoseconds. - Weight::from_parts(45_398_000, 3750) + // Measured: `181` + // Estimated: `3646` + // Minimum execution time: 35_828_000 picoseconds. + Weight::from_parts(36_853_000, 3646) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:2 w:2) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn set_code() -> Weight { // Proof Size summary in bytes: - // Measured: `529` - // Estimated: `6469` - // Minimum execution time: 26_420_000 picoseconds. - Weight::from_parts(27_141_000, 6469) + // Measured: `424` + // Estimated: `6364` + // Minimum execution time: 19_678_000 picoseconds. + Weight::from_parts(21_266_000, 6364) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } /// Storage: `Revive::AddressSuffix` (r:1 w:1) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) fn map_account() -> Weight { // Proof Size summary in bytes: - // Measured: `109` - // Estimated: `3574` - // Minimum execution time: 39_735_000 picoseconds. - Weight::from_parts(41_260_000, 3574) + // Measured: `0` + // Estimated: `3465` + // Minimum execution time: 37_024_000 picoseconds. + Weight::from_parts(37_440_000, 3465) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// Storage: `Revive::AddressSuffix` (r:0 w:1) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) fn unmap_account() -> Weight { // Proof Size summary in bytes: // Measured: `56` // Estimated: `3521` - // Minimum execution time: 32_059_000 picoseconds. - Weight::from_parts(32_776_000, 3521) + // Minimum execution time: 31_228_000 picoseconds. + Weight::from_parts(32_183_000, 3521) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -328,10 +337,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `Measured`) fn dispatch_as_fallback_account() -> Weight { // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3610` - // Minimum execution time: 13_553_000 picoseconds. - Weight::from_parts(14_121_000, 3610) + // Measured: `0` + // Estimated: `3465` + // Minimum execution time: 6_241_000 picoseconds. + Weight::from_parts(6_467_000, 3465) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -339,98 +348,115 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_392_000 picoseconds. - Weight::from_parts(7_692_248, 0) - // Standard Error: 105 - .saturating_add(Weight::from_parts(180_036, 0).saturating_mul(r.into())) + // Minimum execution time: 6_397_000 picoseconds. + Weight::from_parts(7_159_300, 0) + // Standard Error: 173 + .saturating_add(Weight::from_parts(167_265, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 287_000 picoseconds. - Weight::from_parts(317_000, 0) + // Minimum execution time: 267_000 picoseconds. + Weight::from_parts(296_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 235_000 picoseconds. - Weight::from_parts(288_000, 0) + // Minimum execution time: 227_000 picoseconds. + Weight::from_parts(252_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, mode: `Measured`) fn seal_is_contract() -> Weight { // Proof Size summary in bytes: - // Measured: `306` - // Estimated: `3771` - // Minimum execution time: 10_101_000 picoseconds. - Weight::from_parts(10_420_000, 3771) + // Measured: `202` + // Estimated: `3667` + // Minimum execution time: 6_591_000 picoseconds. + Weight::from_parts(6_770_000, 3667) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Revive::AddressSuffix` (r:1 w:0) + /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) + fn seal_to_account_id() -> Weight { + // Proof Size summary in bytes: + // Measured: `144` + // Estimated: `3609` + // Minimum execution time: 6_182_000 picoseconds. + Weight::from_parts(6_372_000, 3609) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, mode: `Measured`) fn seal_code_hash() -> Weight { // Proof Size summary in bytes: - // Measured: `403` - // Estimated: `3868` - // Minimum execution time: 11_422_000 picoseconds. - Weight::from_parts(11_829_000, 3868) + // Measured: `298` + // Estimated: `3763` + // Minimum execution time: 7_327_000 picoseconds. + Weight::from_parts(7_612_000, 3763) .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: 247_000 picoseconds. - Weight::from_parts(282_000, 0) + // Minimum execution time: 232_000 picoseconds. + Weight::from_parts(287_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn seal_code_size() -> Weight { // Proof Size summary in bytes: - // Measured: `473` - // Estimated: `3938` - // Minimum execution time: 14_856_000 picoseconds. - Weight::from_parts(15_528_000, 3938) + // Measured: `368` + // Estimated: `3833` + // Minimum execution time: 10_918_000 picoseconds. + Weight::from_parts(11_323_000, 3833) .saturating_add(T::DbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 303_000 picoseconds. - Weight::from_parts(361_000, 0) + // Minimum execution time: 310_000 picoseconds. + Weight::from_parts(340_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 253_000 picoseconds. - Weight::from_parts(287_000, 0) + // Minimum execution time: 257_000 picoseconds. + Weight::from_parts(292_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 231_000 picoseconds. - Weight::from_parts(263_000, 0) + // Minimum execution time: 240_000 picoseconds. + Weight::from_parts(249_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 628_000 picoseconds. - Weight::from_parts(697_000, 0) + // Minimum execution time: 599_000 picoseconds. + Weight::from_parts(645_000, 0) + } + fn seal_ref_time_left() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 208_000 picoseconds. + Weight::from_parts(244_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: - // Measured: `103` + // Measured: `102` // Estimated: `0` - // Minimum execution time: 4_531_000 picoseconds. - Weight::from_parts(4_726_000, 0) + // Minimum execution time: 4_534_000 picoseconds. + Weight::from_parts(4_689_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -438,10 +464,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn seal_balance_of() -> Weight { // Proof Size summary in bytes: - // Measured: `264` - // Estimated: `3729` - // Minimum execution time: 8_787_000 picoseconds. - Weight::from_parts(9_175_000, 3729) + // Measured: `160` + // Estimated: `3625` + // Minimum execution time: 8_640_000 picoseconds. + Weight::from_parts(8_971_000, 3625) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -449,12 +475,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `n` is `[1, 4096]`. fn seal_get_immutable_data(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `238 + n * (1 ±0)` - // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 5_760_000 picoseconds. - Weight::from_parts(6_591_336, 3703) - // Standard Error: 4 - .saturating_add(Weight::from_parts(628, 0).saturating_mul(n.into())) + // Measured: `134 + n * (1 ±0)` + // Estimated: `3599 + n * (1 ±0)` + // Minimum execution time: 4_875_000 picoseconds. + Weight::from_parts(6_212_863, 3599) + // Standard Error: 7 + .saturating_add(Weight::from_parts(671, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -465,153 +491,199 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_971_000 picoseconds. - Weight::from_parts(2_206_252, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(529, 0).saturating_mul(n.into())) + // Minimum execution time: 1_678_000 picoseconds. + Weight::from_parts(1_883_150, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(579, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 246_000 picoseconds. - Weight::from_parts(279_000, 0) + // Minimum execution time: 238_000 picoseconds. + Weight::from_parts(273_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 223_000 picoseconds. - Weight::from_parts(274_000, 0) + // Minimum execution time: 244_000 picoseconds. + Weight::from_parts(260_000, 0) + } + fn seal_return_data_size() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 249_000 picoseconds. + Weight::from_parts(265_000, 0) + } + fn seal_call_data_size() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 243_000 picoseconds. + Weight::from_parts(269_000, 0) + } + fn seal_gas_limit() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 228_000 picoseconds. + Weight::from_parts(268_000, 0) + } + fn seal_gas_price() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 222_000 picoseconds. + Weight::from_parts(251_000, 0) + } + fn seal_base_fee() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 226_000 picoseconds. + Weight::from_parts(250_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 213_000 picoseconds. + // Minimum execution time: 228_000 picoseconds. Weight::from_parts(270_000, 0) } + /// Storage: `Session::Validators` (r:1 w:0) + /// Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn seal_block_author() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1485` + // Minimum execution time: 13_597_000 picoseconds. + Weight::from_parts(13_770_000, 1485) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) fn seal_block_hash() -> Weight { // Proof Size summary in bytes: - // Measured: `30` - // Estimated: `3495` - // Minimum execution time: 3_502_000 picoseconds. - Weight::from_parts(3_777_000, 3495) + // Measured: `0` + // Estimated: `3465` + // Minimum execution time: 2_199_000 picoseconds. + Weight::from_parts(2_402_000, 3465) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 232_000 picoseconds. - Weight::from_parts(277_000, 0) + // Minimum execution time: 230_000 picoseconds. + Weight::from_parts(256_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_293_000 picoseconds. - Weight::from_parts(1_426_000, 0) + // Minimum execution time: 1_214_000 picoseconds. + Weight::from_parts(1_283_000, 0) } /// The range of component `n` is `[0, 262140]`. - fn seal_input(n: u32, ) -> Weight { + fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 449_000 picoseconds. - Weight::from_parts(446_268, 0) + // Minimum execution time: 376_000 picoseconds. + Weight::from_parts(569_136, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(113, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(236, 0).saturating_mul(n.into())) + } + fn seal_call_data_load() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 243_000 picoseconds. + Weight::from_parts(260_000, 0) + } + /// The range of component `n` is `[0, 262144]`. + fn seal_call_data_copy(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 231_000 picoseconds. + Weight::from_parts(379_088, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_000 picoseconds. - Weight::from_parts(612_733, 0) + // Minimum execution time: 227_000 picoseconds. + Weight::from_parts(400_572, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(200, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(237, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Revive::DeletionQueueCounter` (r:1 w:1) /// Proof: `Revive::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) - /// Storage: `Revive::CodeInfoOf` (r:33 w:33) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::DeletionQueue` (r:0 w:1) /// Proof: `Revive::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) /// Storage: `Revive::ImmutableDataOf` (r:0 w:1) /// Proof: `Revive::ImmutableDataOf` (`max_values`: None, `max_size`: Some(4118), added: 6593, mode: `Measured`) - /// The range of component `n` is `[0, 32]`. - fn seal_terminate(n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `324 + n * (88 ±0)` - // Estimated: `3789 + n * (2563 ±0)` - // Minimum execution time: 21_822_000 picoseconds. - Weight::from_parts(22_468_601, 3789) - // Standard Error: 7_303 - .saturating_add(Weight::from_parts(4_138_073, 0).saturating_mul(n.into())) + fn seal_terminate() -> Weight { + // Proof Size summary in bytes: + // Measured: `215` + // Estimated: `3680` + // Minimum execution time: 14_216_000 picoseconds. + Weight::from_parts(14_533_000, 3680) .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4_u64)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2563).saturating_mul(n.into())) } /// The range of component `t` is `[0, 4]`. - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 416]`. fn seal_deposit_event(t: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_127_000 picoseconds. - Weight::from_parts(4_043_097, 0) - // Standard Error: 3_136 - .saturating_add(Weight::from_parts(209_603, 0).saturating_mul(t.into())) + // Minimum execution time: 3_877_000 picoseconds. + Weight::from_parts(3_856_832, 0) + // Standard Error: 2_622 + .saturating_add(Weight::from_parts(201_206, 0).saturating_mul(t.into())) // Standard Error: 28 - .saturating_add(Weight::from_parts(988, 0).saturating_mul(n.into())) - } - /// The range of component `i` is `[0, 262144]`. - fn seal_debug_message(i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 276_000 picoseconds. - Weight::from_parts(1_111_301, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(706, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(1_128, 0).saturating_mul(n.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: `744` - // Estimated: `744` - // Minimum execution time: 7_869_000 picoseconds. - Weight::from_parts(8_190_000, 744) + // Measured: `552` + // Estimated: `552` + // Minimum execution time: 5_806_000 picoseconds. + Weight::from_parts(6_037_000, 552) .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: `10754` - // Estimated: `10754` - // Minimum execution time: 42_793_000 picoseconds. - Weight::from_parts(43_861_000, 10754) + // Measured: `10562` + // Estimated: `10562` + // Minimum execution time: 39_517_000 picoseconds. + Weight::from_parts(40_698_000, 10562) .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: `744` - // Estimated: `744` - // Minimum execution time: 8_753_000 picoseconds. - Weight::from_parts(9_235_000, 744) + // Measured: `552` + // Estimated: `552` + // Minimum execution time: 6_747_000 picoseconds. + Weight::from_parts(7_003_000, 552) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -619,85 +691,85 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_storage_full() -> Weight { // Proof Size summary in bytes: - // Measured: `10754` - // Estimated: `10754` - // Minimum execution time: 44_446_000 picoseconds. - Weight::from_parts(45_586_000, 10754) + // Measured: `10562` + // Estimated: `10562` + // Minimum execution time: 40_158_000 picoseconds. + Weight::from_parts(41_394_000, 10562) .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, 512]`. - /// The range of component `o` is `[0, 512]`. + /// The range of component `n` is `[0, 416]`. + /// The range of component `o` is `[0, 416]`. fn seal_set_storage(n: u32, o: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `248 + o * (1 ±0)` - // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_214_000 picoseconds. - Weight::from_parts(9_888_060, 247) - // Standard Error: 41 - .saturating_add(Weight::from_parts(151, 0).saturating_mul(n.into())) - // Standard Error: 41 - .saturating_add(Weight::from_parts(315, 0).saturating_mul(o.into())) + // Measured: `152 + o * (1 ±0)` + // Estimated: `151 + o * (1 ±0)` + // Minimum execution time: 6_360_000 picoseconds. + Weight::from_parts(7_335_152, 151) + // Standard Error: 80 + .saturating_add(Weight::from_parts(716, 0).saturating_mul(n.into())) + // Standard Error: 80 + .saturating_add(Weight::from_parts(1_127, 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, 512]`. + /// The range of component `n` is `[0, 416]`. fn seal_clear_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `248 + n * (1 ±0)` - // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_647_000 picoseconds. - Weight::from_parts(9_553_009, 247) - // Standard Error: 48 - .saturating_add(Weight::from_parts(651, 0).saturating_mul(n.into())) + // Measured: `152 + n * (1 ±0)` + // Estimated: `151 + n * (1 ±0)` + // Minimum execution time: 5_980_000 picoseconds. + Weight::from_parts(7_164_266, 151) + // Standard Error: 130 + .saturating_add(Weight::from_parts(1_893, 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, 512]`. + /// The range of component `n` is `[0, 416]`. fn seal_get_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `248 + n * (1 ±0)` - // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_457_000 picoseconds. - Weight::from_parts(9_199_745, 247) - // Standard Error: 59 - .saturating_add(Weight::from_parts(1_562, 0).saturating_mul(n.into())) + // Measured: `152 + n * (1 ±0)` + // Estimated: `151 + n * (1 ±0)` + // Minimum execution time: 5_823_000 picoseconds. + Weight::from_parts(7_045_557, 151) + // Standard Error: 123 + .saturating_add(Weight::from_parts(2_222, 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, 512]`. + /// The range of component `n` is `[0, 416]`. fn seal_contains_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `248 + n * (1 ±0)` - // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_025_000 picoseconds. - Weight::from_parts(8_700_911, 247) - // Standard Error: 49 - .saturating_add(Weight::from_parts(635, 0).saturating_mul(n.into())) + // Measured: `152 + n * (1 ±0)` + // Estimated: `151 + n * (1 ±0)` + // Minimum execution time: 5_349_000 picoseconds. + Weight::from_parts(6_506_216, 151) + // Standard Error: 127 + .saturating_add(Weight::from_parts(1_605, 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, 512]`. + /// The range of component `n` is `[0, 416]`. fn seal_take_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `248 + n * (1 ±0)` - // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_346_000 picoseconds. - Weight::from_parts(10_297_284, 247) - // Standard Error: 62 - .saturating_add(Weight::from_parts(1_396, 0).saturating_mul(n.into())) + // Measured: `152 + n * (1 ±0)` + // Estimated: `151 + n * (1 ±0)` + // Minimum execution time: 6_151_000 picoseconds. + Weight::from_parts(7_812_180, 151) + // Standard Error: 159 + .saturating_add(Weight::from_parts(2_277, 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())) @@ -706,92 +778,94 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_428_000 picoseconds. - Weight::from_parts(1_517_000, 0) + // Minimum execution time: 1_344_000 picoseconds. + Weight::from_parts(1_462_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_868_000 picoseconds. - Weight::from_parts(1_942_000, 0) + // Minimum execution time: 1_680_000 picoseconds. + Weight::from_parts(1_785_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_403_000 picoseconds. - Weight::from_parts(1_539_000, 0) + // Minimum execution time: 1_380_000 picoseconds. + Weight::from_parts(1_502_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_676_000 picoseconds. - Weight::from_parts(1_760_000, 0) + // Minimum execution time: 1_506_000 picoseconds. + Weight::from_parts(1_604_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_119_000 picoseconds. - Weight::from_parts(1_205_000, 0) + // Minimum execution time: 972_000 picoseconds. + Weight::from_parts(1_054_000, 0) } - /// The range of component `n` is `[0, 512]`. - /// The range of component `o` is `[0, 512]`. + /// The range of component `n` is `[0, 416]`. + /// The range of component `o` is `[0, 416]`. fn seal_set_transient_storage(n: u32, o: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_146_000 picoseconds. - Weight::from_parts(2_315_339, 0) - // Standard Error: 13 - .saturating_add(Weight::from_parts(327, 0).saturating_mul(n.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(366, 0).saturating_mul(o.into())) + // Minimum execution time: 2_048_000 picoseconds. + Weight::from_parts(2_304_120, 0) + // Standard Error: 17 + .saturating_add(Weight::from_parts(254, 0).saturating_mul(n.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(321, 0).saturating_mul(o.into())) } - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 416]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_950_000 picoseconds. - Weight::from_parts(2_271_073, 0) - // Standard Error: 15 - .saturating_add(Weight::from_parts(373, 0).saturating_mul(n.into())) + // Minimum execution time: 1_790_000 picoseconds. + Weight::from_parts(2_141_874, 0) + // Standard Error: 31 + .saturating_add(Weight::from_parts(378, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 416]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_839_000 picoseconds. - Weight::from_parts(2_049_659, 0) + // Minimum execution time: 1_662_000 picoseconds. + Weight::from_parts(1_938_172, 0) // Standard Error: 14 - .saturating_add(Weight::from_parts(291, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(316, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 416]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_716_000 picoseconds. - Weight::from_parts(1_893_932, 0) - // Standard Error: 12 - .saturating_add(Weight::from_parts(172, 0).saturating_mul(n.into())) + // Minimum execution time: 1_570_000 picoseconds. + Weight::from_parts(1_769_617, 0) + // Standard Error: 13 + .saturating_add(Weight::from_parts(152, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 512]`. - fn seal_take_transient_storage(_n: u32, ) -> Weight { + /// The range of component `n` is `[0, 416]`. + fn seal_take_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_448_000 picoseconds. - Weight::from_parts(2_676_764, 0) + // Minimum execution time: 2_266_000 picoseconds. + Weight::from_parts(2_497_430, 0) + // Standard Error: 21 + .saturating_add(Weight::from_parts(38, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:0) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) @@ -802,48 +876,50 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `i` is `[0, 262144]`. fn seal_call(t: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1294 + t * (242 ±0)` - // Estimated: `4759 + t * (2501 ±0)` - // Minimum execution time: 39_786_000 picoseconds. - Weight::from_parts(41_175_457, 4759) - // Standard Error: 45_251 - .saturating_add(Weight::from_parts(2_375_617, 0).saturating_mul(t.into())) + // Measured: `1163 + t * (206 ±0)` + // Estimated: `4628 + t * (2417 ±0)` + // Minimum execution time: 30_368_000 picoseconds. + Weight::from_parts(31_023_429, 4628) + // Standard Error: 43_250 + .saturating_add(Weight::from_parts(5_949_452, 0).saturating_mul(t.into())) // Standard Error: 0 .saturating_add(Weight::from_parts(2, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(1_u64)) - .saturating_add(Weight::from_parts(0, 2501).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 2417).saturating_mul(t.into())) } + /// Storage: `Revive::ContractInfoOf` (r:1 w:0) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) fn seal_delegate_call() -> Weight { // Proof Size summary in bytes: - // Measured: `1064` - // Estimated: `4529` - // Minimum execution time: 29_762_000 picoseconds. - Weight::from_parts(31_345_000, 4529) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Measured: `1108` + // Estimated: `4573` + // Minimum execution time: 24_707_000 picoseconds. + Weight::from_parts(25_410_000, 4573) + .saturating_add(T::DbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, 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, 262144]`. fn seal_instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1310` - // Estimated: `4748` - // Minimum execution time: 117_791_000 picoseconds. - Weight::from_parts(105_413_907, 4748) - // Standard Error: 11 - .saturating_add(Weight::from_parts(4_038, 0).saturating_mul(i.into())) + // Measured: `1094` + // Estimated: `4579` + // Minimum execution time: 107_232_000 picoseconds. + Weight::from_parts(94_844_854, 4579) + // Standard Error: 10 + .saturating_add(Weight::from_parts(4_159, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -852,95 +928,73 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 638_000 picoseconds. - Weight::from_parts(4_703_710, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(1_349, 0).saturating_mul(n.into())) + // Minimum execution time: 617_000 picoseconds. + Weight::from_parts(3_460_054, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_374, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_085_000 picoseconds. - Weight::from_parts(3_630_716, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(3_567, 0).saturating_mul(n.into())) + // Minimum execution time: 1_040_000 picoseconds. + Weight::from_parts(3_026_644, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(3_607, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 643_000 picoseconds. - Weight::from_parts(3_733_026, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(1_492, 0).saturating_mul(n.into())) + // Minimum execution time: 633_000 picoseconds. + Weight::from_parts(3_375_104, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_494, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 653_000 picoseconds. - Weight::from_parts(4_627_285, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(1_478, 0).saturating_mul(n.into())) + // Minimum execution time: 601_000 picoseconds. + Weight::from_parts(3_802_060, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_493, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 45_786_000 picoseconds. - Weight::from_parts(36_383_470, 0) - // Standard Error: 10 - .saturating_add(Weight::from_parts(5_396, 0).saturating_mul(n.into())) + // Minimum execution time: 42_419_000 picoseconds. + Weight::from_parts(26_760_986, 0) + // Standard Error: 12 + .saturating_add(Weight::from_parts(5_421, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 48_140_000 picoseconds. - Weight::from_parts(49_720_000, 0) + // Minimum execution time: 48_672_000 picoseconds. + Weight::from_parts(49_840_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_565_000 picoseconds. - Weight::from_parts(12_704_000, 0) + // Minimum execution time: 12_307_000 picoseconds. + Weight::from_parts(12_500_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn seal_set_code_hash() -> Weight { // Proof Size summary in bytes: - // Measured: `300` - // Estimated: `3765` - // Minimum execution time: 17_208_000 picoseconds. - Weight::from_parts(18_307_000, 3765) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: `Revive::CodeInfoOf` (r:1 w:1) - /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) - fn lock_delegate_dependency() -> Weight { - // Proof Size summary in bytes: - // Measured: `338` - // Estimated: `3803` - // Minimum execution time: 13_686_000 picoseconds. - Weight::from_parts(14_186_000, 3803) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: `Revive::CodeInfoOf` (r:1 w:1) - /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) - fn unlock_delegate_dependency() -> Weight { - // Proof Size summary in bytes: - // Measured: `338` - // Estimated: `3561` - // Minimum execution time: 12_381_000 picoseconds. - Weight::from_parts(13_208_000, 3561) + // Measured: `196` + // Estimated: `3661` + // Minimum execution time: 10_142_000 picoseconds. + Weight::from_parts(10_458_000, 3661) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -949,10 +1003,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_118_000 picoseconds. - Weight::from_parts(9_813_514, 0) - // Standard Error: 40 - .saturating_add(Weight::from_parts(71_154, 0).saturating_mul(r.into())) + // Minimum execution time: 7_893_000 picoseconds. + Weight::from_parts(9_362_667, 0) + // Standard Error: 84 + .saturating_add(Weight::from_parts(74_272, 0).saturating_mul(r.into())) } } @@ -962,10 +1016,10 @@ impl WeightInfo for () { /// Proof: `Revive::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: `109` - // Estimated: `1594` - // Minimum execution time: 2_818_000 picoseconds. - Weight::from_parts(3_058_000, 1594) + // Measured: `0` + // Estimated: `1485` + // Minimum execution time: 695_000 picoseconds. + Weight::from_parts(750_000, 1485) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -973,12 +1027,12 @@ impl WeightInfo for () { /// The range of component `k` is `[0, 1024]`. fn on_initialize_per_trie_key(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `425 + k * (69 ±0)` - // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 15_916_000 picoseconds. - Weight::from_parts(16_132_000, 415) - // Standard Error: 1_482 - .saturating_add(Weight::from_parts(1_185_583, 0).saturating_mul(k.into())) + // Measured: `230 + k * (69 ±0)` + // Estimated: `222 + k * (70 ±0)` + // Minimum execution time: 10_509_000 picoseconds. + Weight::from_parts(10_896_000, 222) + // Standard Error: 2_549 + .saturating_add(Weight::from_parts(1_264_033, 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)) @@ -988,7 +1042,7 @@ impl WeightInfo for () { /// Storage: `Revive::AddressSuffix` (r:2 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) @@ -1000,21 +1054,21 @@ impl WeightInfo for () { /// The range of component `c` is `[0, 262144]`. fn call_with_code_per_byte(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1502` - // Estimated: `7442` - // Minimum execution time: 88_115_000 picoseconds. - Weight::from_parts(92_075_651, 7442) + // Measured: `1194` + // Estimated: `7134` + // Minimum execution time: 84_008_000 picoseconds. + Weight::from_parts(91_138_296, 7134) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:2 w:2) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, 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) @@ -1023,16 +1077,14 @@ impl WeightInfo for () { /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. /// The range of component `i` is `[0, 262144]`. - fn instantiate_with_code(c: u32, i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `403` - // Estimated: `6326` - // Minimum execution time: 188_274_000 picoseconds. - Weight::from_parts(157_773_869, 6326) - // Standard Error: 11 - .saturating_add(Weight::from_parts(16, 0).saturating_mul(c.into())) - // Standard Error: 11 - .saturating_add(Weight::from_parts(4_464, 0).saturating_mul(i.into())) + fn instantiate_with_code(_c: u32, i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `93` + // Estimated: `6033` + // Minimum execution time: 172_907_000 picoseconds. + Weight::from_parts(153_592_465, 6033) + // Standard Error: 12 + .saturating_add(Weight::from_parts(4_544, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1043,29 +1095,29 @@ impl WeightInfo for () { /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, 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(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// The range of component `i` is `[0, 262144]`. fn instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1296` - // Estimated: `4739` - // Minimum execution time: 158_616_000 picoseconds. - Weight::from_parts(134_329_076, 4739) - // Standard Error: 15 - .saturating_add(Weight::from_parts(4_358, 0).saturating_mul(i.into())) + // Measured: `987` + // Estimated: `4452` + // Minimum execution time: 143_169_000 picoseconds. + Weight::from_parts(120_653_436, 4452) + // Standard Error: 16 + .saturating_add(Weight::from_parts(4_444, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Revive::AddressSuffix` (r:2 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) @@ -1076,80 +1128,80 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `1502` - // Estimated: `7442` - // Minimum execution time: 134_935_000 picoseconds. - Weight::from_parts(141_040_000, 7442) + // Measured: `1194` + // Estimated: `7134` + // Minimum execution time: 138_392_000 picoseconds. + Weight::from_parts(143_329_000, 7134) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:0 w:1) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. fn upload_code(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `109` - // Estimated: `3574` - // Minimum execution time: 51_026_000 picoseconds. - Weight::from_parts(53_309_143, 3574) + // Measured: `0` + // Estimated: `3465` + // Minimum execution time: 43_420_000 picoseconds. + Weight::from_parts(45_143_767, 3465) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:0 w:1) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) fn remove_code() -> Weight { // Proof Size summary in bytes: - // Measured: `285` - // Estimated: `3750` - // Minimum execution time: 44_338_000 picoseconds. - Weight::from_parts(45_398_000, 3750) + // Measured: `181` + // Estimated: `3646` + // Minimum execution time: 35_828_000 picoseconds. + Weight::from_parts(36_853_000, 3646) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:2 w:2) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn set_code() -> Weight { // Proof Size summary in bytes: - // Measured: `529` - // Estimated: `6469` - // Minimum execution time: 26_420_000 picoseconds. - Weight::from_parts(27_141_000, 6469) + // Measured: `424` + // Estimated: `6364` + // Minimum execution time: 19_678_000 picoseconds. + Weight::from_parts(21_266_000, 6364) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } /// Storage: `Revive::AddressSuffix` (r:1 w:1) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) fn map_account() -> Weight { // Proof Size summary in bytes: - // Measured: `109` - // Estimated: `3574` - // Minimum execution time: 39_735_000 picoseconds. - Weight::from_parts(41_260_000, 3574) + // Measured: `0` + // Estimated: `3465` + // Minimum execution time: 37_024_000 picoseconds. + Weight::from_parts(37_440_000, 3465) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// Storage: `Revive::AddressSuffix` (r:0 w:1) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) fn unmap_account() -> Weight { // Proof Size summary in bytes: // Measured: `56` // Estimated: `3521` - // Minimum execution time: 32_059_000 picoseconds. - Weight::from_parts(32_776_000, 3521) + // Minimum execution time: 31_228_000 picoseconds. + Weight::from_parts(32_183_000, 3521) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1159,10 +1211,10 @@ impl WeightInfo for () { /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `Measured`) fn dispatch_as_fallback_account() -> Weight { // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3610` - // Minimum execution time: 13_553_000 picoseconds. - Weight::from_parts(14_121_000, 3610) + // Measured: `0` + // Estimated: `3465` + // Minimum execution time: 6_241_000 picoseconds. + Weight::from_parts(6_467_000, 3465) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -1170,98 +1222,115 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_392_000 picoseconds. - Weight::from_parts(7_692_248, 0) - // Standard Error: 105 - .saturating_add(Weight::from_parts(180_036, 0).saturating_mul(r.into())) + // Minimum execution time: 6_397_000 picoseconds. + Weight::from_parts(7_159_300, 0) + // Standard Error: 173 + .saturating_add(Weight::from_parts(167_265, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 287_000 picoseconds. - Weight::from_parts(317_000, 0) + // Minimum execution time: 267_000 picoseconds. + Weight::from_parts(296_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 235_000 picoseconds. - Weight::from_parts(288_000, 0) + // Minimum execution time: 227_000 picoseconds. + Weight::from_parts(252_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, mode: `Measured`) fn seal_is_contract() -> Weight { // Proof Size summary in bytes: - // Measured: `306` - // Estimated: `3771` - // Minimum execution time: 10_101_000 picoseconds. - Weight::from_parts(10_420_000, 3771) + // Measured: `202` + // Estimated: `3667` + // Minimum execution time: 6_591_000 picoseconds. + Weight::from_parts(6_770_000, 3667) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Revive::AddressSuffix` (r:1 w:0) + /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) + fn seal_to_account_id() -> Weight { + // Proof Size summary in bytes: + // Measured: `144` + // Estimated: `3609` + // Minimum execution time: 6_182_000 picoseconds. + Weight::from_parts(6_372_000, 3609) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, mode: `Measured`) fn seal_code_hash() -> Weight { // Proof Size summary in bytes: - // Measured: `403` - // Estimated: `3868` - // Minimum execution time: 11_422_000 picoseconds. - Weight::from_parts(11_829_000, 3868) + // Measured: `298` + // Estimated: `3763` + // Minimum execution time: 7_327_000 picoseconds. + Weight::from_parts(7_612_000, 3763) .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: 247_000 picoseconds. - Weight::from_parts(282_000, 0) + // Minimum execution time: 232_000 picoseconds. + Weight::from_parts(287_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn seal_code_size() -> Weight { // Proof Size summary in bytes: - // Measured: `473` - // Estimated: `3938` - // Minimum execution time: 14_856_000 picoseconds. - Weight::from_parts(15_528_000, 3938) + // Measured: `368` + // Estimated: `3833` + // Minimum execution time: 10_918_000 picoseconds. + Weight::from_parts(11_323_000, 3833) .saturating_add(RocksDbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 303_000 picoseconds. - Weight::from_parts(361_000, 0) + // Minimum execution time: 310_000 picoseconds. + Weight::from_parts(340_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 253_000 picoseconds. - Weight::from_parts(287_000, 0) + // Minimum execution time: 257_000 picoseconds. + Weight::from_parts(292_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 231_000 picoseconds. - Weight::from_parts(263_000, 0) + // Minimum execution time: 240_000 picoseconds. + Weight::from_parts(249_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 628_000 picoseconds. - Weight::from_parts(697_000, 0) + // Minimum execution time: 599_000 picoseconds. + Weight::from_parts(645_000, 0) + } + fn seal_ref_time_left() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 208_000 picoseconds. + Weight::from_parts(244_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: - // Measured: `103` + // Measured: `102` // Estimated: `0` - // Minimum execution time: 4_531_000 picoseconds. - Weight::from_parts(4_726_000, 0) + // Minimum execution time: 4_534_000 picoseconds. + Weight::from_parts(4_689_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1269,10 +1338,10 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn seal_balance_of() -> Weight { // Proof Size summary in bytes: - // Measured: `264` - // Estimated: `3729` - // Minimum execution time: 8_787_000 picoseconds. - Weight::from_parts(9_175_000, 3729) + // Measured: `160` + // Estimated: `3625` + // Minimum execution time: 8_640_000 picoseconds. + Weight::from_parts(8_971_000, 3625) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -1280,12 +1349,12 @@ impl WeightInfo for () { /// The range of component `n` is `[1, 4096]`. fn seal_get_immutable_data(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `238 + n * (1 ±0)` - // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 5_760_000 picoseconds. - Weight::from_parts(6_591_336, 3703) - // Standard Error: 4 - .saturating_add(Weight::from_parts(628, 0).saturating_mul(n.into())) + // Measured: `134 + n * (1 ±0)` + // Estimated: `3599 + n * (1 ±0)` + // Minimum execution time: 4_875_000 picoseconds. + Weight::from_parts(6_212_863, 3599) + // Standard Error: 7 + .saturating_add(Weight::from_parts(671, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1296,153 +1365,199 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_971_000 picoseconds. - Weight::from_parts(2_206_252, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(529, 0).saturating_mul(n.into())) + // Minimum execution time: 1_678_000 picoseconds. + Weight::from_parts(1_883_150, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(579, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 246_000 picoseconds. - Weight::from_parts(279_000, 0) + // Minimum execution time: 238_000 picoseconds. + Weight::from_parts(273_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 223_000 picoseconds. - Weight::from_parts(274_000, 0) + // Minimum execution time: 244_000 picoseconds. + Weight::from_parts(260_000, 0) + } + fn seal_return_data_size() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 249_000 picoseconds. + Weight::from_parts(265_000, 0) + } + fn seal_call_data_size() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 243_000 picoseconds. + Weight::from_parts(269_000, 0) + } + fn seal_gas_limit() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 228_000 picoseconds. + Weight::from_parts(268_000, 0) + } + fn seal_gas_price() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 222_000 picoseconds. + Weight::from_parts(251_000, 0) + } + fn seal_base_fee() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 226_000 picoseconds. + Weight::from_parts(250_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 213_000 picoseconds. + // Minimum execution time: 228_000 picoseconds. Weight::from_parts(270_000, 0) } + /// Storage: `Session::Validators` (r:1 w:0) + /// Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn seal_block_author() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1485` + // Minimum execution time: 13_597_000 picoseconds. + Weight::from_parts(13_770_000, 1485) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) fn seal_block_hash() -> Weight { // Proof Size summary in bytes: - // Measured: `30` - // Estimated: `3495` - // Minimum execution time: 3_502_000 picoseconds. - Weight::from_parts(3_777_000, 3495) + // Measured: `0` + // Estimated: `3465` + // Minimum execution time: 2_199_000 picoseconds. + Weight::from_parts(2_402_000, 3465) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 232_000 picoseconds. - Weight::from_parts(277_000, 0) + // Minimum execution time: 230_000 picoseconds. + Weight::from_parts(256_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_293_000 picoseconds. - Weight::from_parts(1_426_000, 0) + // Minimum execution time: 1_214_000 picoseconds. + Weight::from_parts(1_283_000, 0) } /// The range of component `n` is `[0, 262140]`. - fn seal_input(n: u32, ) -> Weight { + fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 449_000 picoseconds. - Weight::from_parts(446_268, 0) + // Minimum execution time: 376_000 picoseconds. + Weight::from_parts(569_136, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(113, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(236, 0).saturating_mul(n.into())) + } + fn seal_call_data_load() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 243_000 picoseconds. + Weight::from_parts(260_000, 0) + } + /// The range of component `n` is `[0, 262144]`. + fn seal_call_data_copy(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 231_000 picoseconds. + Weight::from_parts(379_088, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_000 picoseconds. - Weight::from_parts(612_733, 0) + // Minimum execution time: 227_000 picoseconds. + Weight::from_parts(400_572, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(200, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(237, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Revive::DeletionQueueCounter` (r:1 w:1) /// Proof: `Revive::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) - /// Storage: `Revive::CodeInfoOf` (r:33 w:33) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::DeletionQueue` (r:0 w:1) /// Proof: `Revive::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) /// Storage: `Revive::ImmutableDataOf` (r:0 w:1) /// Proof: `Revive::ImmutableDataOf` (`max_values`: None, `max_size`: Some(4118), added: 6593, mode: `Measured`) - /// The range of component `n` is `[0, 32]`. - fn seal_terminate(n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `324 + n * (88 ±0)` - // Estimated: `3789 + n * (2563 ±0)` - // Minimum execution time: 21_822_000 picoseconds. - Weight::from_parts(22_468_601, 3789) - // Standard Error: 7_303 - .saturating_add(Weight::from_parts(4_138_073, 0).saturating_mul(n.into())) + fn seal_terminate() -> Weight { + // Proof Size summary in bytes: + // Measured: `215` + // Estimated: `3680` + // Minimum execution time: 14_216_000 picoseconds. + Weight::from_parts(14_533_000, 3680) .saturating_add(RocksDbWeight::get().reads(3_u64)) - .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(4_u64)) - .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2563).saturating_mul(n.into())) } /// The range of component `t` is `[0, 4]`. - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 416]`. fn seal_deposit_event(t: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_127_000 picoseconds. - Weight::from_parts(4_043_097, 0) - // Standard Error: 3_136 - .saturating_add(Weight::from_parts(209_603, 0).saturating_mul(t.into())) + // Minimum execution time: 3_877_000 picoseconds. + Weight::from_parts(3_856_832, 0) + // Standard Error: 2_622 + .saturating_add(Weight::from_parts(201_206, 0).saturating_mul(t.into())) // Standard Error: 28 - .saturating_add(Weight::from_parts(988, 0).saturating_mul(n.into())) - } - /// The range of component `i` is `[0, 262144]`. - fn seal_debug_message(i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 276_000 picoseconds. - Weight::from_parts(1_111_301, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(706, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(1_128, 0).saturating_mul(n.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: `744` - // Estimated: `744` - // Minimum execution time: 7_869_000 picoseconds. - Weight::from_parts(8_190_000, 744) + // Measured: `552` + // Estimated: `552` + // Minimum execution time: 5_806_000 picoseconds. + Weight::from_parts(6_037_000, 552) .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: `10754` - // Estimated: `10754` - // Minimum execution time: 42_793_000 picoseconds. - Weight::from_parts(43_861_000, 10754) + // Measured: `10562` + // Estimated: `10562` + // Minimum execution time: 39_517_000 picoseconds. + Weight::from_parts(40_698_000, 10562) .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: `744` - // Estimated: `744` - // Minimum execution time: 8_753_000 picoseconds. - Weight::from_parts(9_235_000, 744) + // Measured: `552` + // Estimated: `552` + // Minimum execution time: 6_747_000 picoseconds. + Weight::from_parts(7_003_000, 552) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1450,85 +1565,85 @@ impl WeightInfo for () { /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_storage_full() -> Weight { // Proof Size summary in bytes: - // Measured: `10754` - // Estimated: `10754` - // Minimum execution time: 44_446_000 picoseconds. - Weight::from_parts(45_586_000, 10754) + // Measured: `10562` + // Estimated: `10562` + // Minimum execution time: 40_158_000 picoseconds. + Weight::from_parts(41_394_000, 10562) .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, 512]`. - /// The range of component `o` is `[0, 512]`. + /// The range of component `n` is `[0, 416]`. + /// The range of component `o` is `[0, 416]`. fn seal_set_storage(n: u32, o: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `248 + o * (1 ±0)` - // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_214_000 picoseconds. - Weight::from_parts(9_888_060, 247) - // Standard Error: 41 - .saturating_add(Weight::from_parts(151, 0).saturating_mul(n.into())) - // Standard Error: 41 - .saturating_add(Weight::from_parts(315, 0).saturating_mul(o.into())) + // Measured: `152 + o * (1 ±0)` + // Estimated: `151 + o * (1 ±0)` + // Minimum execution time: 6_360_000 picoseconds. + Weight::from_parts(7_335_152, 151) + // Standard Error: 80 + .saturating_add(Weight::from_parts(716, 0).saturating_mul(n.into())) + // Standard Error: 80 + .saturating_add(Weight::from_parts(1_127, 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, 512]`. + /// The range of component `n` is `[0, 416]`. fn seal_clear_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `248 + n * (1 ±0)` - // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_647_000 picoseconds. - Weight::from_parts(9_553_009, 247) - // Standard Error: 48 - .saturating_add(Weight::from_parts(651, 0).saturating_mul(n.into())) + // Measured: `152 + n * (1 ±0)` + // Estimated: `151 + n * (1 ±0)` + // Minimum execution time: 5_980_000 picoseconds. + Weight::from_parts(7_164_266, 151) + // Standard Error: 130 + .saturating_add(Weight::from_parts(1_893, 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, 512]`. + /// The range of component `n` is `[0, 416]`. fn seal_get_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `248 + n * (1 ±0)` - // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_457_000 picoseconds. - Weight::from_parts(9_199_745, 247) - // Standard Error: 59 - .saturating_add(Weight::from_parts(1_562, 0).saturating_mul(n.into())) + // Measured: `152 + n * (1 ±0)` + // Estimated: `151 + n * (1 ±0)` + // Minimum execution time: 5_823_000 picoseconds. + Weight::from_parts(7_045_557, 151) + // Standard Error: 123 + .saturating_add(Weight::from_parts(2_222, 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, 512]`. + /// The range of component `n` is `[0, 416]`. fn seal_contains_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `248 + n * (1 ±0)` - // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_025_000 picoseconds. - Weight::from_parts(8_700_911, 247) - // Standard Error: 49 - .saturating_add(Weight::from_parts(635, 0).saturating_mul(n.into())) + // Measured: `152 + n * (1 ±0)` + // Estimated: `151 + n * (1 ±0)` + // Minimum execution time: 5_349_000 picoseconds. + Weight::from_parts(6_506_216, 151) + // Standard Error: 127 + .saturating_add(Weight::from_parts(1_605, 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, 512]`. + /// The range of component `n` is `[0, 416]`. fn seal_take_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `248 + n * (1 ±0)` - // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_346_000 picoseconds. - Weight::from_parts(10_297_284, 247) - // Standard Error: 62 - .saturating_add(Weight::from_parts(1_396, 0).saturating_mul(n.into())) + // Measured: `152 + n * (1 ±0)` + // Estimated: `151 + n * (1 ±0)` + // Minimum execution time: 6_151_000 picoseconds. + Weight::from_parts(7_812_180, 151) + // Standard Error: 159 + .saturating_add(Weight::from_parts(2_277, 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())) @@ -1537,92 +1652,94 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_428_000 picoseconds. - Weight::from_parts(1_517_000, 0) + // Minimum execution time: 1_344_000 picoseconds. + Weight::from_parts(1_462_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_868_000 picoseconds. - Weight::from_parts(1_942_000, 0) + // Minimum execution time: 1_680_000 picoseconds. + Weight::from_parts(1_785_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_403_000 picoseconds. - Weight::from_parts(1_539_000, 0) + // Minimum execution time: 1_380_000 picoseconds. + Weight::from_parts(1_502_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_676_000 picoseconds. - Weight::from_parts(1_760_000, 0) + // Minimum execution time: 1_506_000 picoseconds. + Weight::from_parts(1_604_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_119_000 picoseconds. - Weight::from_parts(1_205_000, 0) + // Minimum execution time: 972_000 picoseconds. + Weight::from_parts(1_054_000, 0) } - /// The range of component `n` is `[0, 512]`. - /// The range of component `o` is `[0, 512]`. + /// The range of component `n` is `[0, 416]`. + /// The range of component `o` is `[0, 416]`. fn seal_set_transient_storage(n: u32, o: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_146_000 picoseconds. - Weight::from_parts(2_315_339, 0) - // Standard Error: 13 - .saturating_add(Weight::from_parts(327, 0).saturating_mul(n.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(366, 0).saturating_mul(o.into())) + // Minimum execution time: 2_048_000 picoseconds. + Weight::from_parts(2_304_120, 0) + // Standard Error: 17 + .saturating_add(Weight::from_parts(254, 0).saturating_mul(n.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(321, 0).saturating_mul(o.into())) } - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 416]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_950_000 picoseconds. - Weight::from_parts(2_271_073, 0) - // Standard Error: 15 - .saturating_add(Weight::from_parts(373, 0).saturating_mul(n.into())) + // Minimum execution time: 1_790_000 picoseconds. + Weight::from_parts(2_141_874, 0) + // Standard Error: 31 + .saturating_add(Weight::from_parts(378, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 416]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_839_000 picoseconds. - Weight::from_parts(2_049_659, 0) + // Minimum execution time: 1_662_000 picoseconds. + Weight::from_parts(1_938_172, 0) // Standard Error: 14 - .saturating_add(Weight::from_parts(291, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(316, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 416]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_716_000 picoseconds. - Weight::from_parts(1_893_932, 0) - // Standard Error: 12 - .saturating_add(Weight::from_parts(172, 0).saturating_mul(n.into())) + // Minimum execution time: 1_570_000 picoseconds. + Weight::from_parts(1_769_617, 0) + // Standard Error: 13 + .saturating_add(Weight::from_parts(152, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 512]`. - fn seal_take_transient_storage(_n: u32, ) -> Weight { + /// The range of component `n` is `[0, 416]`. + fn seal_take_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_448_000 picoseconds. - Weight::from_parts(2_676_764, 0) + // Minimum execution time: 2_266_000 picoseconds. + Weight::from_parts(2_497_430, 0) + // Standard Error: 21 + .saturating_add(Weight::from_parts(38, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:0) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) @@ -1633,48 +1750,50 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 262144]`. fn seal_call(t: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1294 + t * (242 ±0)` - // Estimated: `4759 + t * (2501 ±0)` - // Minimum execution time: 39_786_000 picoseconds. - Weight::from_parts(41_175_457, 4759) - // Standard Error: 45_251 - .saturating_add(Weight::from_parts(2_375_617, 0).saturating_mul(t.into())) + // Measured: `1163 + t * (206 ±0)` + // Estimated: `4628 + t * (2417 ±0)` + // Minimum execution time: 30_368_000 picoseconds. + Weight::from_parts(31_023_429, 4628) + // Standard Error: 43_250 + .saturating_add(Weight::from_parts(5_949_452, 0).saturating_mul(t.into())) // Standard Error: 0 .saturating_add(Weight::from_parts(2, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(RocksDbWeight::get().writes(1_u64)) - .saturating_add(Weight::from_parts(0, 2501).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 2417).saturating_mul(t.into())) } + /// Storage: `Revive::ContractInfoOf` (r:1 w:0) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) fn seal_delegate_call() -> Weight { // Proof Size summary in bytes: - // Measured: `1064` - // Estimated: `4529` - // Minimum execution time: 29_762_000 picoseconds. - Weight::from_parts(31_345_000, 4529) - .saturating_add(RocksDbWeight::get().reads(2_u64)) + // Measured: `1108` + // Estimated: `4573` + // Minimum execution time: 24_707_000 picoseconds. + Weight::from_parts(25_410_000, 4573) + .saturating_add(RocksDbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(242), added: 2717, 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, 262144]`. fn seal_instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1310` - // Estimated: `4748` - // Minimum execution time: 117_791_000 picoseconds. - Weight::from_parts(105_413_907, 4748) - // Standard Error: 11 - .saturating_add(Weight::from_parts(4_038, 0).saturating_mul(i.into())) + // Measured: `1094` + // Estimated: `4579` + // Minimum execution time: 107_232_000 picoseconds. + Weight::from_parts(94_844_854, 4579) + // Standard Error: 10 + .saturating_add(Weight::from_parts(4_159, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1683,95 +1802,73 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 638_000 picoseconds. - Weight::from_parts(4_703_710, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(1_349, 0).saturating_mul(n.into())) + // Minimum execution time: 617_000 picoseconds. + Weight::from_parts(3_460_054, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_374, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_085_000 picoseconds. - Weight::from_parts(3_630_716, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(3_567, 0).saturating_mul(n.into())) + // Minimum execution time: 1_040_000 picoseconds. + Weight::from_parts(3_026_644, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(3_607, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 643_000 picoseconds. - Weight::from_parts(3_733_026, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(1_492, 0).saturating_mul(n.into())) + // Minimum execution time: 633_000 picoseconds. + Weight::from_parts(3_375_104, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_494, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 653_000 picoseconds. - Weight::from_parts(4_627_285, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(1_478, 0).saturating_mul(n.into())) + // Minimum execution time: 601_000 picoseconds. + Weight::from_parts(3_802_060, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_493, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 45_786_000 picoseconds. - Weight::from_parts(36_383_470, 0) - // Standard Error: 10 - .saturating_add(Weight::from_parts(5_396, 0).saturating_mul(n.into())) + // Minimum execution time: 42_419_000 picoseconds. + Weight::from_parts(26_760_986, 0) + // Standard Error: 12 + .saturating_add(Weight::from_parts(5_421, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 48_140_000 picoseconds. - Weight::from_parts(49_720_000, 0) + // Minimum execution time: 48_672_000 picoseconds. + Weight::from_parts(49_840_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_565_000 picoseconds. - Weight::from_parts(12_704_000, 0) + // Minimum execution time: 12_307_000 picoseconds. + Weight::from_parts(12_500_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn seal_set_code_hash() -> Weight { // Proof Size summary in bytes: - // Measured: `300` - // Estimated: `3765` - // Minimum execution time: 17_208_000 picoseconds. - Weight::from_parts(18_307_000, 3765) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: `Revive::CodeInfoOf` (r:1 w:1) - /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) - fn lock_delegate_dependency() -> Weight { - // Proof Size summary in bytes: - // Measured: `338` - // Estimated: `3803` - // Minimum execution time: 13_686_000 picoseconds. - Weight::from_parts(14_186_000, 3803) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: `Revive::CodeInfoOf` (r:1 w:1) - /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) - fn unlock_delegate_dependency() -> Weight { - // Proof Size summary in bytes: - // Measured: `338` - // Estimated: `3561` - // Minimum execution time: 12_381_000 picoseconds. - Weight::from_parts(13_208_000, 3561) + // Measured: `196` + // Estimated: `3661` + // Minimum execution time: 10_142_000 picoseconds. + Weight::from_parts(10_458_000, 3661) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1780,9 +1877,9 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_118_000 picoseconds. - Weight::from_parts(9_813_514, 0) - // Standard Error: 40 - .saturating_add(Weight::from_parts(71_154, 0).saturating_mul(r.into())) + // Minimum execution time: 7_893_000 picoseconds. + Weight::from_parts(9_362_667, 0) + // Standard Error: 84 + .saturating_add(Weight::from_parts(74_272, 0).saturating_mul(r.into())) } } diff --git a/substrate/frame/revive/uapi/Cargo.toml b/substrate/frame/revive/uapi/Cargo.toml index 0c7461a35d691f259339a1076dca65a5b7670f12..cf006941cfd0a6477d35b9b0d992060105f6f3c1 100644 --- a/substrate/frame/revive/uapi/Cargo.toml +++ b/substrate/frame/revive/uapi/Cargo.toml @@ -12,20 +12,23 @@ description = "Exposes all the host functions that a contract can import." 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 } +pallet-revive-proc-macro = { workspace = true } +paste = { workspace = true } +scale-info = { features = ["derive"], optional = true, workspace = true } -[target.'cfg(target_arch = "riscv32")'.dependencies] -polkavm-derive = { version = "0.14.0" } +[target.'cfg(target_arch = "riscv64")'.dependencies] +polkavm-derive = { version = "0.19.0" } [package.metadata.docs.rs] -default-target = ["wasm32-unknown-unknown"] +features = ["unstable-hostfn"] +targets = ["riscv64imac-unknown-none-elf"] [features] default = ["scale"] scale = ["dep:codec", "scale-info"] +unstable-hostfn = [] diff --git a/substrate/frame/revive/uapi/src/flags.rs b/substrate/frame/revive/uapi/src/flags.rs index 763a89d6c030489b3867417357c8310cbfa686f3..6a0f47c38c2c8aa478084019a2a0ce88a2599957 100644 --- a/substrate/frame/revive/uapi/src/flags.rs +++ b/substrate/frame/revive/uapi/src/flags.rs @@ -38,7 +38,7 @@ bitflags! { /// /// 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 + /// It does not matter if this is due to calling `call_data_copy` or trying another /// forwarding call. Consider using [`Self::CLONE_INPUT`] in order to preserve /// the input. const FORWARD_INPUT = 0b0000_0001; diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 6b3a8b07f040e3ca685704789cca9d5ae84366ea..8e14eefc636455c6280208b1ec94586bd5ab7cbb 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -12,26 +12,10 @@ // 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 [<hash_ $name>](input: &[u8], output: &mut [u8; $bytes]); - } - }; -} +use pallet_revive_proc_macro::unstable_hostfn; + +#[cfg(target_arch = "riscv64")] +mod riscv64; /// Implements [`HostFn`] when compiled on supported architectures (RISC-V). pub enum HostFnImpl {} @@ -45,17 +29,6 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the address. fn address(output: &mut [u8; 20]); - /// 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; 32]); - /// Get the contract immutable data. /// /// Traps if: @@ -98,20 +71,16 @@ pub trait HostFn: private::Sealed { /// Returns the [EIP-155](https://eips.ethereum.org/EIPS/eip-155) chain ID. fn chain_id(output: &mut [u8; 32]); - /// Stores the current block number of the current contract into the supplied buffer. - /// - /// # Parameters - /// - /// - `output`: A reference to the output data buffer to write the block number. - fn block_number(output: &mut [u8; 32]); + /// Returns the price per ref_time, akin to the EVM + /// [GASPRICE](https://www.evm.codes/?fork=cancun#3a) opcode. + fn gas_price() -> u64; - /// Stores the block hash of the given block number into the supplied buffer. - /// - /// # Parameters - /// - /// - `block_number`: A reference to the block number buffer. - /// - `output`: A reference to the output data buffer to write the block number. - fn block_hash(block_number: &[u8; 32], output: &mut [u8; 32]); + /// Returns the base fee, akin to the EVM + /// [BASEFEE](https://www.evm.codes/?fork=cancun#48) opcode. + fn base_fee(output: &mut [u8; 32]); + + /// Returns the call data size. + fn call_data_size() -> u64; /// Call (possibly transferring some amount of funds) into the specified account. /// @@ -138,68 +107,18 @@ pub trait HostFn: private::Sealed { /// - [CalleeReverted][`crate::ReturnErrorCode::CalleeReverted]: Output buffer is returned. /// - [CalleeTrapped][`crate::ReturnErrorCode::CalleeTrapped] /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] - /// - [NotCallable][`crate::ReturnErrorCode::NotCallable] + /// - [OutOfResources][`crate::ReturnErrorCode::OutOfResources] fn call( flags: CallFlags, callee: &[u8; 20], ref_time_limit: u64, proof_size_limit: u64, - deposit: Option<&[u8; 32]>, + deposit: &[u8; 32], value: &[u8; 32], 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 this is a top-level call (i.e. initiated by an extrinsic) the origin address of the @@ -225,38 +144,6 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the origin's address. fn origin(output: &mut [u8; 20]); - /// 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<u32>; - /// Retrieve the code hash for a specified contract address. /// /// # Parameters @@ -271,48 +158,16 @@ pub trait HostFn: private::Sealed { /// otherwise `zero`. fn code_hash(addr: &[u8; 20], output: &mut [u8; 32]); - /// Retrieve the code size for a specified contract address. + /// Returns the code size for a specified contract address. /// /// # Parameters /// /// - `addr`: The address of the contract. - /// - `output`: A reference to the output data buffer to write the code size. /// /// # Note /// /// If `addr` is not a contract the `output` will be zero. - fn code_size(addr: &[u8; 20], output: &mut [u8; 32]); - - /// 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<u32>; - - /// 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; + fn code_size(addr: &[u8; 20]) -> u64; /// Execute code in the context (storage, caller, value) of the current contract. /// @@ -341,13 +196,13 @@ pub trait HostFn: private::Sealed { /// /// - [CalleeReverted][`crate::ReturnErrorCode::CalleeReverted]: Output buffer is returned. /// - [CalleeTrapped][`crate::ReturnErrorCode::CalleeTrapped] - /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] + /// - [OutOfResources][`crate::ReturnErrorCode::OutOfResources] fn delegate_call( flags: CallFlags, address: &[u8; 20], ref_time_limit: u64, proof_size_limit: u64, - deposit_limit: Option<&[u8; 32]>, + deposit_limit: &[u8; 32], input_data: &[u8], output: Option<&mut &mut [u8]>, ) -> Result; @@ -362,79 +217,69 @@ pub trait HostFn: private::Sealed { /// - `topics`: The topics list. It can't contain duplicates. fn deposit_event(topics: &[[u8; 32]], data: &[u8]); - /// Recovers the ECDSA public key from the given message hash and signature. + /// Retrieve the value under the given key from storage. /// - /// Writes the public key into the given output buffer. - /// Assumes the secp256k1 curve. + /// The key length must not exceed the maximum defined by the contracts module parameter. /// /// # Parameters - /// - /// - `signature`: The signature bytes. - /// - `message_hash`: The message hash bytes. - /// - `output`: A reference to the output data buffer to write the public key. + /// - `key`: The storage key. + /// - `output`: A reference to the output data buffer to write the storage entry. /// /// # Errors /// - /// - [EcdsaRecoveryFailed][`crate::ReturnErrorCode::EcdsaRecoveryFailed] - fn ecdsa_recover( - signature: &[u8; 65], - message_hash: &[u8; 32], - output: &mut [u8; 33], - ) -> Result; + /// [KeyNotFound][`crate::ReturnErrorCode::KeyNotFound] + fn get_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result; - /// Calculates Ethereum address from the ECDSA compressed public key and stores - /// it into the supplied buffer. + /// Computes the keccak_256 32-bit hash on the given input buffer. /// - /// # Parameters - /// - /// - `pubkey`: The public key bytes. - /// - `output`: A reference to the output data buffer to write the address. + /// - The `input` and `output` buffer may overlap. + /// - The output buffer is expected to hold at least 32 bits. + /// - 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. /// - /// # Errors + /// # Parameters /// - /// - [EcdsaRecoveryFailed][`crate::ReturnErrorCode::EcdsaRecoveryFailed] - fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result; + /// - `input`: The input data buffer. + /// - `output`: The output buffer to write the hash result to. + fn hash_keccak_256(input: &[u8], output: &mut [u8; 32]); - /// Stores the amount of weight left into the supplied buffer. - /// The data is encoded as Weight. + /// Stores the input data passed by the caller into the supplied `output` buffer, + /// starting from the given input data `offset`. /// - /// If the available space in `output` is less than the size of the value a trap is triggered. - /// - /// # Parameters + /// The `output` buffer is guaranteed to always be fully populated: + /// - If the call data (starting from the given `offset`) is larger than the `output` buffer, + /// only what fits into the `output` buffer is written. + /// - If the `output` buffer size exceeds the call data size (starting from `offset`), remaining + /// bytes in the `output` buffer are zeroed out. + /// - If the provided call data `offset` is out-of-bounds, the whole `output` buffer is zeroed + /// out. /// - /// - `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. + /// # Note /// - /// The key length must not exceed the maximum defined by the contracts module parameter. + /// This function traps if: + /// - the input was previously forwarded by a [`call()`][`Self::call()`]. + /// - the `output` buffer is located in an PolkaVM invalid memory range. /// /// # 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); + /// - `output`: A reference to the output data buffer to write the call data. + /// - `offset`: The offset index into the call data from where to start copying. + fn call_data_copy(output: &mut [u8], offset: u32); - /// Stores the input passed by the caller into the supplied buffer. + /// Stores the U256 value at given `offset` from 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()`]. + /// - If `offset` is out of bounds, a value of zero will be returned. + /// - If `offset` is in bounds but there is not enough call data, the available data + /// is right-padded in order to fill a whole U256 value. + /// - The data written to `output` is a little endian U256 integer value. /// /// # Parameters /// - /// - `output`: A reference to the output data buffer to write the input data. - fn input(output: &mut &mut [u8]); + /// - `output`: A reference to the fixed output data buffer to write the value. + /// - `offset`: The offset (index) into the call data. + fn call_data_load(output: &mut [u8; 32], offset: u32); /// Instantiate a contract with the specified code hash. /// @@ -443,14 +288,14 @@ pub trait HostFn: private::Sealed { /// /// # 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. 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. - /// - `input`: The input data buffer. + /// - `input`: The code hash and constructor input data buffer. The first 32 bytes are the code + /// hash of the code to be instantiated. The remaining bytes are the constructor call data. /// - `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. @@ -468,12 +313,11 @@ pub trait HostFn: private::Sealed { /// - [CalleeReverted][`crate::ReturnErrorCode::CalleeReverted]: Output buffer is returned. /// - [CalleeTrapped][`crate::ReturnErrorCode::CalleeTrapped] /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] - /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] + /// - [OutOfResources][`crate::ReturnErrorCode::OutOfResources] fn instantiate( - code_hash: &[u8; 32], ref_time_limit: u64, proof_size_limit: u64, - deposit: Option<&[u8; 32]>, + deposit: &[u8; 32], value: &[u8; 32], input: &[u8], address: Option<&mut [u8; 20]>, @@ -481,65 +325,317 @@ pub trait HostFn: private::Sealed { salt: Option<&[u8; 32]>, ) -> Result; - /// Checks whether a specified address belongs to a contract. + /// Load the latest block timestamp into the supplied buffer /// /// # Parameters /// - /// - `address`: The address to check + /// - `output`: A reference to the output data buffer to write the timestamp. + fn now(output: &mut [u8; 32]); + + /// Returns the block ref_time limit. + fn gas_limit() -> u64; + + /// 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]) -> !; + + /// 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 `true` if the address belongs to a contract. - fn is_contract(address: &[u8; 20]) -> bool; + /// Returns the size of the pre-existing value at the specified key if any. + fn set_storage(flags: StorageFlags, key: &[u8], value: &[u8]) -> Option<u32>; - /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. + /// Stores the value transferred along with this call/instantiate into the supplied buffer. /// /// # Parameters /// - /// - `output`: A reference to the output data buffer to write the minimum balance. - fn minimum_balance(output: &mut [u8; 32]); + /// - `output`: A reference to the output data buffer to write the transferred value. + fn value_transferred(output: &mut [u8; 32]); - /// Retrieve the code hash of the currently executing contract. + /// Stores the price for the specified amount of gas into the supplied buffer. /// /// # Parameters /// - /// - `output`: A reference to the output data buffer to write the code hash. - fn own_code_hash(output: &mut [u8; 32]); + /// - `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 [u8; 32]); - /// Load the latest block timestamp into the supplied buffer + /// Returns the size of the returned data of the last contract call or instantiation. + fn return_data_size() -> u64; + + /// Stores the returned data of the last contract call or contract instantiation. /// /// # Parameters + /// - `output`: A reference to the output buffer to write the data. + /// - `offset`: Byte offset into the returned data + fn return_data_copy(output: &mut &mut [u8], offset: u32); + + /// Returns the amount of ref_time left. + fn ref_time_left() -> u64; + + /// Stores the current block author of into the supplied buffer. /// - /// - `output`: A reference to the output data buffer to write the timestamp. - fn now(output: &mut [u8; 32]); + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the block author. + fn block_author(output: &mut [u8; 20]); - /// Removes the delegate dependency from the contract. + /// Stores the current block number of the current contract into the supplied buffer. + /// + /// # Parameters /// - /// Traps if the delegate dependency does not exist. + /// - `output`: A reference to the output data buffer to write the block number. + fn block_number(output: &mut [u8; 32]); + + /// Retrieve the account id for a specified address. /// /// # 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; 32]); + /// - `addr`: A `H160` address. + /// - `output`: A reference to the output data buffer to write the account id. + /// + /// # Note + /// + /// If no mapping exists for `addr`, the fallback account id will be returned. + #[unstable_hostfn] + fn to_account_id(addr: &[u8; 20], output: &mut [u8]); - /// Cease contract execution and save a data buffer as a result of the execution. + /// Stores the block hash of the given block number into the supplied buffer. /// - /// 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(), &[]) - /// ``` + /// # Parameters /// - /// Using an unnamed non empty `ReturnFlags` triggers a trap. + /// - `block_number`: A reference to the block number buffer. + /// - `output`: A reference to the output data buffer to write the block number. + #[unstable_hostfn] + fn block_hash(block_number: &[u8; 32], output: &mut [u8; 32]); + + /// 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 /// - /// - `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]) -> !; + /// - `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. + #[unstable_hostfn] + 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. + #[unstable_hostfn] + fn call_runtime(call: &[u8]) -> Result; + + /// 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. + #[unstable_hostfn] + 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. + #[unstable_hostfn] + fn caller_is_root() -> bool; + + /// 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. + #[unstable_hostfn] + fn clear_storage(flags: StorageFlags, key: &[u8]) -> Option<u32>; + + /// 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. + #[unstable_hostfn] + fn contains_storage(flags: StorageFlags, key: &[u8]) -> Option<u32>; + + /// 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] + #[unstable_hostfn] + 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] + #[unstable_hostfn] + fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result; + + /// Computes the sha2_256 32-bit hash on the given input buffer. + /// + /// - The `input` and `output` buffer may overlap. + /// - The output buffer is expected to hold at least 32 bits. + /// - 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. + /// + /// # Parameters + /// + /// - `input`: The input data buffer. + /// - `output`: The output buffer to write the hash result to. + #[unstable_hostfn] + fn hash_sha2_256(input: &[u8], output: &mut [u8; 32]); + + /// Computes the blake2_256 32-bit hash on the given input buffer. + /// + /// - The `input` and `output` buffer may overlap. + /// - The output buffer is expected to hold at least 32 bits. + /// - 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. + /// + /// # Parameters + /// */ + /// - `input`: The input data buffer. + /// - `output`: The output buffer to write the hash result to. + #[unstable_hostfn] + fn hash_blake2_256(input: &[u8], output: &mut [u8; 32]); + + /// Computes the blake2_128 16-bit hash on the given input buffer. + /// + /// - The `input` and `output` buffer may overlap. + /// - The output buffer is expected to hold at least 16 bits. + /// - 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. + /// # Parameters + /// + /// - `input`: The input data buffer. + /// - `output`: The output buffer to write the hash result to. + #[unstable_hostfn] + fn hash_blake2_128(input: &[u8], output: &mut [u8; 16]); + + /// Checks whether a specified address belongs to a contract. + /// + /// # Parameters + /// + /// - `address`: The address to check + /// + /// # Return + /// + /// Returns `true` if the address belongs to a contract. + #[unstable_hostfn] + fn is_contract(address: &[u8; 20]) -> bool; + + /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the minimum balance. + #[unstable_hostfn] + fn minimum_balance(output: &mut [u8; 32]); + + /// Retrieve the code hash of the currently executing contract. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the code hash. + #[unstable_hostfn] + fn own_code_hash(output: &mut [u8; 32]); /// Replace the contract code at the specified address with new code. /// @@ -566,25 +662,11 @@ pub trait HostFn: private::Sealed { /// - `code_hash`: The hash of the new code. Should be decodable as an `T::Hash`. Traps /// otherwise. /// - /// # Errors + /// # Panics /// - /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] - fn set_code_hash(code_hash: &[u8; 32]) -> 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<u32>; + /// Panics if there is no code on-chain with the specified hash. + #[unstable_hostfn] + fn set_code_hash(code_hash: &[u8; 32]); /// Verify a sr25519 signature /// @@ -596,6 +678,7 @@ pub trait HostFn: private::Sealed { /// # Errors /// /// - [Sr25519VerifyFailed][`crate::ReturnErrorCode::Sr25519VerifyFailed] + #[unstable_hostfn] fn sr25519_verify(signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> Result; /// Retrieve and remove the value under the given key from storage. @@ -607,6 +690,7 @@ pub trait HostFn: private::Sealed { /// # Errors /// /// [KeyNotFound][`crate::ReturnErrorCode::KeyNotFound] + #[unstable_hostfn] fn take_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result; /// Remove the calling account and transfer remaining **free** balance. @@ -624,23 +708,19 @@ pub trait HostFn: private::Sealed { /// - 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. + #[unstable_hostfn] fn terminate(beneficiary: &[u8; 20]) -> !; - /// Stores the value transferred along with this call/instantiate into the supplied buffer. - /// - /// # Parameters + /// Stores the amount of weight left into the supplied buffer. + /// The data is encoded as Weight. /// - /// - `output`: A reference to the output data buffer to write the transferred value. - fn value_transferred(output: &mut [u8; 32]); - - /// Stores the price for the specified amount of gas into the supplied buffer. + /// 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 [u8; 32]); + /// - `output`: A reference to the output data buffer to write the weight left. + #[unstable_hostfn] + fn weight_left(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 @@ -656,6 +736,7 @@ pub trait HostFn: private::Sealed { /// /// Returns `Error::Success` when the XCM execution attempt is successful. When the XCM /// execution fails, `ReturnCode::XcmExecutionFailed` is returned + #[unstable_hostfn] fn xcm_execute(msg: &[u8]) -> Result; /// Send an XCM program from the contract to the specified destination. @@ -673,21 +754,8 @@ pub trait HostFn: private::Sealed { /// /// Returns `ReturnCode::Success` when the message was successfully sent. When the XCM /// execution fails, `ReturnErrorCode::XcmSendFailed` is returned. + #[unstable_hostfn] fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result; - - /// Stores the size of the returned data of the last contract call or instantiation. - /// - /// # Parameters - /// - /// - `output`: A reference to the output buffer to write the size. - fn return_data_size(output: &mut [u8; 32]); - - /// Stores the returned data of the last contract call or contract instantiation. - /// - /// # Parameters - /// - `output`: A reference to the output buffer to write the data. - /// - `offset`: Byte offset into the returned data - fn return_data_copy(output: &mut &mut [u8], offset: u32); } mod private { diff --git a/substrate/frame/revive/uapi/src/host/riscv32.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs similarity index 66% rename from substrate/frame/revive/uapi/src/host/riscv32.rs rename to substrate/frame/revive/uapi/src/host/riscv64.rs index e8b27057ed18dc10a793508e931aeec090ec467e..588579dc83ebfb7287f516e324a0e792f9c4d181 100644 --- a/substrate/frame/revive/uapi/src/host/riscv32.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -16,8 +16,9 @@ use crate::{ host::{CallFlags, HostFn, HostFnImpl, Result, StorageFlags}, - ReturnFlags, + pack_hi_lo, ReturnFlags, }; +use pallet_revive_proc_macro::unstable_hostfn; mod sys { use crate::ReturnCode; @@ -26,10 +27,10 @@ mod sys { mod abi {} impl abi::FromHost for ReturnCode { - type Regs = (u32,); + type Regs = (u64,); fn from_host((a0,): Self::Regs) -> Self { - ReturnCode(a0) + ReturnCode(a0 as _) } } @@ -58,23 +59,47 @@ mod sys { out_ptr: *mut u8, out_len_ptr: *mut u32, ) -> ReturnCode; - pub fn call(ptr: *const u8) -> ReturnCode; - pub fn delegate_call(ptr: *const u8) -> ReturnCode; - pub fn instantiate(ptr: *const u8) -> ReturnCode; + pub fn call( + flags_and_callee: u64, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_and_value: u64, + input_data: u64, + output_data: u64, + ) -> ReturnCode; + pub fn delegate_call( + flags_and_callee: u64, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_ptr: *const u8, + input_data: u64, + output_data: u64, + ) -> ReturnCode; + pub fn instantiate( + ref_time_limit: u64, + proof_size_limit: u64, + deposit_and_value: u64, + input_data: u64, + output_data: u64, + address_and_salt: u64, + ) -> ReturnCode; pub fn terminate(beneficiary_ptr: *const u8); - pub fn input(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn call_data_copy(out_ptr: *mut u8, out_len: u32, offset: u32); + pub fn call_data_load(out_ptr: *mut u8, offset: u32); pub fn seal_return(flags: u32, data_ptr: *const u8, data_len: u32); pub fn caller(out_ptr: *mut u8); pub fn origin(out_ptr: *mut u8); pub fn is_contract(account_ptr: *const u8) -> ReturnCode; + pub fn to_account_id(address_ptr: *const u8, out_ptr: *mut u8); pub fn code_hash(address_ptr: *const u8, out_ptr: *mut u8); - pub fn code_size(address_ptr: *const u8, out_ptr: *mut u8); + pub fn code_size(address_ptr: *const u8) -> u64; pub fn own_code_hash(out_ptr: *mut u8); pub fn caller_is_origin() -> ReturnCode; pub fn caller_is_root() -> ReturnCode; pub fn address(out_ptr: *mut u8); pub fn weight_to_fee(ref_time: u64, proof_size: u64, out_ptr: *mut u8); pub fn weight_left(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn ref_time_left() -> u64; pub fn get_immutable_data(out_ptr: *mut u8, out_len_ptr: *mut u32); pub fn set_immutable_data(ptr: *const u8, len: u32); pub fn balance(out_ptr: *mut u8); @@ -82,6 +107,7 @@ mod sys { pub fn chain_id(out_ptr: *mut u8); pub fn value_transferred(out_ptr: *mut u8); pub fn now(out_ptr: *mut u8); + pub fn gas_limit() -> u64; pub fn minimum_balance(out_ptr: *mut u8); pub fn deposit_event( topics_ptr: *const [u8; 32], @@ -89,8 +115,12 @@ mod sys { data_ptr: *const u8, data_len: u32, ); + pub fn gas_price() -> u64; + pub fn base_fee(out_ptr: *mut u8); + pub fn call_data_size() -> u64; pub fn block_number(out_ptr: *mut u8); pub fn block_hash(block_number_ptr: *const u8, out_ptr: *mut u8); + pub fn block_author(out_ptr: *mut u8); 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); @@ -102,7 +132,6 @@ mod sys { 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, @@ -115,11 +144,9 @@ mod sys { message_len: u32, message_ptr: *const u8, ) -> ReturnCode; - pub fn set_code_hash(code_hash_ptr: *const u8) -> ReturnCode; + pub fn set_code_hash(code_hash_ptr: *const u8); 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, @@ -128,43 +155,11 @@ mod sys { msg_len: u32, out_ptr: *mut u8, ) -> ReturnCode; - pub fn return_data_size(out_ptr: *mut u8); + pub fn return_data_size() -> u64; pub fn return_data_copy(out_ptr: *mut u8, out_len_ptr: *mut u32, offset: u32); } } -/// A macro to implement all Host functions with a signature of `fn(&mut [u8; n])`. -macro_rules! impl_wrapper_for { - (@impl_fn $name:ident, $n: literal) => { - fn $name(output: &mut [u8; $n]) { - unsafe { sys::$name(output.as_mut_ptr()) } - } - }; - - () => {}; - - ([u8; $n: literal] => $($name:ident),*; $($tail:tt)*) => { - $(impl_wrapper_for!(@impl_fn $name, $n);)* - impl_wrapper_for!($($tail)*); - }; -} - -macro_rules! impl_hash_fn { - ( $name:ident, $bytes_result:literal ) => { - paste::item! { - fn [<hash_ $name>](input: &[u8], output: &mut [u8; $bytes_result]) { - unsafe { - sys::[<hash_ $name>]( - 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()); @@ -190,10 +185,9 @@ fn ptr_or_sentinel(data: &Option<&[u8; 32]>) -> *const u8 { impl HostFn for HostFnImpl { fn instantiate( - code_hash: &[u8; 32], ref_time_limit: u64, proof_size_limit: u64, - deposit_limit: Option<&[u8; 32]>, + deposit_limit: &[u8; 32], value: &[u8; 32], input: &[u8], mut address: Option<&mut [u8; 20]>, @@ -204,42 +198,28 @@ impl HostFn for HostFnImpl { Some(ref mut data) => data.as_mut_ptr(), None => crate::SENTINEL as _, }; - let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); - let deposit_limit_ptr = ptr_or_sentinel(&deposit_limit); + let (output_ptr, mut output_len_ptr) = ptr_len_or_sentinel(&mut output); + let deposit_limit_ptr = deposit_limit.as_ptr(); let salt_ptr = ptr_or_sentinel(&salt); - #[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: u32, - address: *const u8, - output: *mut u8, - output_len: *mut u32, - salt: *const u8, - } - 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() as _, - address, - output: output_ptr, - output_len: &mut output_len as *mut _, - salt: salt_ptr, - }; - let ret_code = { unsafe { sys::instantiate(&args as *const Args as *const _) } }; + let deposit_and_value = pack_hi_lo(deposit_limit_ptr as _, value.as_ptr() as _); + let address_and_salt = pack_hi_lo(address as _, salt_ptr as _); + let input_data = pack_hi_lo(input.len() as _, input.as_ptr() as _); + let output_data = pack_hi_lo(&mut output_len_ptr as *mut _ as _, output_ptr as _); + + let ret_code = unsafe { + sys::instantiate( + ref_time_limit, + proof_size_limit, + deposit_and_value, + input_data, + output_data, + address_and_salt, + ) + }; if let Some(ref mut output) = output { - extract_from_slice(output, output_len as usize); + extract_from_slice(output, output_len_ptr as usize); } ret_code.into() @@ -250,41 +230,29 @@ impl HostFn for HostFnImpl { callee: &[u8; 20], ref_time_limit: u64, proof_size_limit: u64, - deposit_limit: Option<&[u8; 32]>, + deposit_limit: &[u8; 32], value: &[u8; 32], 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: u32, - 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() as _, - output: output_ptr, - output_len: &mut output_len as *mut _, - }; + let deposit_limit_ptr = deposit_limit.as_ptr(); + + let flags_and_callee = pack_hi_lo(flags.bits(), callee.as_ptr() as _); + let deposit_and_value = pack_hi_lo(deposit_limit_ptr as _, value.as_ptr() as _); + let input_data = pack_hi_lo(input.len() as _, input.as_ptr() as _); + let output_data = pack_hi_lo(&mut output_len as *mut _ as _, output_ptr as _); - let ret_code = { unsafe { sys::call(&args as *const Args as *const _) } }; + let ret_code = unsafe { + sys::call( + flags_and_callee, + ref_time_limit, + proof_size_limit, + deposit_and_value, + input_data, + output_data, + ) + }; if let Some(ref mut output) = output { extract_from_slice(output, output_len as usize); @@ -293,47 +261,32 @@ impl HostFn for HostFnImpl { ret_code.into() } - fn caller_is_root() -> u32 { - unsafe { sys::caller_is_root() }.into_u32() - } - fn delegate_call( flags: CallFlags, address: &[u8; 20], ref_time_limit: u64, proof_size_limit: u64, - deposit_limit: Option<&[u8; 32]>, + deposit_limit: &[u8; 32], 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, - address: *const u8, - ref_time_limit: u64, - proof_size_limit: u64, - deposit_limit: *const u8, - input: *const u8, - input_len: u32, - output: *mut u8, - output_len: *mut u32, - } - let args = Args { - flags: flags.bits(), - address: address.as_ptr(), - ref_time_limit, - proof_size_limit, - deposit_limit: deposit_limit_ptr, - input: input.as_ptr(), - input_len: input.len() as _, - output: output_ptr, - output_len: &mut output_len as *mut _, - }; + let deposit_limit_ptr = deposit_limit.as_ptr(); + + let flags_and_callee = pack_hi_lo(flags.bits(), address.as_ptr() as u32); + let input_data = pack_hi_lo(input.len() as u32, input.as_ptr() as u32); + let output_data = pack_hi_lo(&mut output_len as *mut _ as u32, output_ptr as u32); - let ret_code = { unsafe { sys::delegate_call(&args as *const Args as *const _) } }; + let ret_code = unsafe { + sys::delegate_call( + flags_and_callee, + ref_time_limit, + proof_size_limit, + deposit_limit_ptr as _, + input_data, + output_data, + ) + }; if let Some(ref mut output) = output { extract_from_slice(output, output_len as usize); @@ -366,17 +319,6 @@ impl HostFn for HostFnImpl { ret_code.into() } - fn clear_storage(flags: StorageFlags, key: &[u8]) -> Option<u32> { - 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<u32> { - 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 = { @@ -394,33 +336,124 @@ impl HostFn for HostFnImpl { ret_code.into() } - fn take_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result { + fn call_data_load(out_ptr: &mut [u8; 32], offset: u32) { + unsafe { sys::call_data_load(out_ptr.as_mut_ptr(), offset) }; + } + + fn gas_limit() -> u64 { + unsafe { sys::gas_limit() } + } + + fn call_data_size() -> u64 { + unsafe { sys::call_data_size() } + } + + 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 gas_price() -> u64 { + unsafe { sys::gas_price() } + } + + fn base_fee(output: &mut [u8; 32]) { + unsafe { sys::base_fee(output.as_mut_ptr()) } + } + + fn balance(output: &mut [u8; 32]) { + unsafe { sys::balance(output.as_mut_ptr()) } + } + + fn value_transferred(output: &mut [u8; 32]) { + unsafe { sys::value_transferred(output.as_mut_ptr()) } + } + + fn now(output: &mut [u8; 32]) { + unsafe { sys::now(output.as_mut_ptr()) } + } + + fn chain_id(output: &mut [u8; 32]) { + unsafe { sys::chain_id(output.as_mut_ptr()) } + } + + fn address(output: &mut [u8; 20]) { + unsafe { sys::address(output.as_mut_ptr()) } + } + + fn caller(output: &mut [u8; 20]) { + unsafe { sys::caller(output.as_mut_ptr()) } + } + + fn origin(output: &mut [u8; 20]) { + unsafe { sys::origin(output.as_mut_ptr()) } + } + + fn block_number(output: &mut [u8; 32]) { + unsafe { sys::block_number(output.as_mut_ptr()) } + } + + fn block_author(output: &mut [u8; 20]) { + unsafe { sys::block_author(output.as_mut_ptr()) } + } + + fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut [u8; 32]) { + unsafe { sys::weight_to_fee(ref_time_limit, proof_size_limit, output.as_mut_ptr()) }; + } + + fn hash_keccak_256(input: &[u8], output: &mut [u8; 32]) { + unsafe { sys::hash_keccak_256(input.as_ptr(), input.len() as u32, output.as_mut_ptr()) } + } + + fn get_immutable_data(output: &mut &mut [u8]) { 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, - ) - } - }; + unsafe { sys::get_immutable_data(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 set_immutable_data(data: &[u8]) { + unsafe { sys::set_immutable_data(data.as_ptr(), data.len() as u32) } } - fn terminate(beneficiary: &[u8; 20]) -> ! { - unsafe { sys::terminate(beneficiary.as_ptr()) } - panic!("terminate does not return"); + fn balance_of(address: &[u8; 20], output: &mut [u8; 32]) { + unsafe { sys::balance_of(address.as_ptr(), output.as_mut_ptr()) }; + } + + fn code_hash(address: &[u8; 20], output: &mut [u8; 32]) { + unsafe { sys::code_hash(address.as_ptr(), output.as_mut_ptr()) } } + fn code_size(address: &[u8; 20]) -> u64 { + unsafe { sys::code_size(address.as_ptr()) } + } + + fn return_data_size() -> u64 { + unsafe { sys::return_data_size() } + } + + fn return_data_copy(output: &mut &mut [u8], offset: u32) { + let mut output_len = output.len() as u32; + { + unsafe { sys::return_data_copy(output.as_mut_ptr(), &mut output_len, offset) }; + } + extract_from_slice(output, output_len as usize); + } + + fn ref_time_left() -> u64 { + unsafe { sys::ref_time_left() } + } + + #[unstable_hostfn] + fn to_account_id(address: &[u8; 20], output: &mut [u8]) { + unsafe { sys::to_account_id(address.as_ptr(), output.as_mut_ptr()) } + } + + #[unstable_hostfn] + fn block_hash(block_number_ptr: &[u8; 32], output: &mut [u8; 32]) { + unsafe { sys::block_hash(block_number_ptr.as_ptr(), output.as_mut_ptr()) }; + } + + #[unstable_hostfn] 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 = { @@ -441,44 +474,43 @@ impl HostFn for HostFnImpl { 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_data_copy(output: &mut [u8], offset: u32) { + let len = output.len() as u32; + unsafe { sys::call_data_copy(output.as_mut_ptr(), len, offset) }; } + #[unstable_hostfn] 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! { - [u8; 32] => block_number, balance, value_transferred, now, minimum_balance, chain_id; - [u8; 20] => address, caller, origin; + #[unstable_hostfn] + fn caller_is_origin() -> bool { + let ret_val = unsafe { sys::caller_is_origin() }; + ret_val.into_bool() } - fn weight_left(output: &mut &mut [u8]) { - let mut output_len = output.len() as u32; - unsafe { sys::weight_left(output.as_mut_ptr(), &mut output_len) } - extract_from_slice(output, output_len as usize) + #[unstable_hostfn] + fn caller_is_root() -> bool { + let ret_val = unsafe { sys::caller_is_root() }; + ret_val.into_bool() } - fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut [u8; 32]) { - unsafe { sys::weight_to_fee(ref_time_limit, proof_size_limit, output.as_mut_ptr()) }; + #[unstable_hostfn] + fn clear_storage(flags: StorageFlags, key: &[u8]) -> Option<u32> { + let ret_code = unsafe { sys::clear_storage(flags.bits(), key.as_ptr(), key.len() as u32) }; + ret_code.into() } - impl_hash_fn!(sha2_256, 32); - impl_hash_fn!(keccak_256, 32); - impl_hash_fn!(blake2_256, 32); - impl_hash_fn!(blake2_128, 16); + #[unstable_hostfn] + fn contains_storage(flags: StorageFlags, key: &[u8]) -> Option<u32> { + let ret_code = + unsafe { sys::contains_storage(flags.bits(), key.as_ptr(), key.len() as u32) }; + ret_code.into() + } + #[unstable_hostfn] fn ecdsa_recover( signature: &[u8; 65], message_hash: &[u8; 32], @@ -490,77 +522,99 @@ impl HostFn for HostFnImpl { ret_code.into() } + #[unstable_hostfn] 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(address: &[u8; 20]) -> bool { - let ret_val = unsafe { sys::is_contract(address.as_ptr()) }; - ret_val.into_bool() + #[unstable_hostfn] + fn hash_sha2_256(input: &[u8], output: &mut [u8; 32]) { + unsafe { sys::hash_sha2_256(input.as_ptr(), input.len() as u32, output.as_mut_ptr()) } } - fn get_immutable_data(output: &mut &mut [u8]) { - let mut output_len = output.len() as u32; - unsafe { sys::get_immutable_data(output.as_mut_ptr(), &mut output_len) }; - extract_from_slice(output, output_len as usize); + #[unstable_hostfn] + fn hash_blake2_256(input: &[u8], output: &mut [u8; 32]) { + unsafe { sys::hash_blake2_256(input.as_ptr(), input.len() as u32, output.as_mut_ptr()) } } - fn set_immutable_data(data: &[u8]) { - unsafe { sys::set_immutable_data(data.as_ptr(), data.len() as u32) } + #[unstable_hostfn] + fn hash_blake2_128(input: &[u8], output: &mut [u8; 16]) { + unsafe { sys::hash_blake2_128(input.as_ptr(), input.len() as u32, output.as_mut_ptr()) } } - fn balance_of(address: &[u8; 20], output: &mut [u8; 32]) { - unsafe { sys::balance_of(address.as_ptr(), output.as_mut_ptr()) }; + #[unstable_hostfn] + fn is_contract(address: &[u8; 20]) -> bool { + let ret_val = unsafe { sys::is_contract(address.as_ptr()) }; + ret_val.into_bool() } - fn caller_is_origin() -> bool { - let ret_val = unsafe { sys::caller_is_origin() }; - ret_val.into_bool() + #[unstable_hostfn] + fn minimum_balance(output: &mut [u8; 32]) { + unsafe { sys::minimum_balance(output.as_mut_ptr()) } } - fn set_code_hash(code_hash: &[u8; 32]) -> Result { - let ret_val = unsafe { sys::set_code_hash(code_hash.as_ptr()) }; - ret_val.into() + #[unstable_hostfn] + fn own_code_hash(output: &mut [u8; 32]) { + unsafe { sys::own_code_hash(output.as_mut_ptr()) } } - fn code_hash(address: &[u8; 20], output: &mut [u8; 32]) { - unsafe { sys::code_hash(address.as_ptr(), output.as_mut_ptr()) } + #[unstable_hostfn] + fn set_code_hash(code_hash: &[u8; 32]) { + unsafe { sys::set_code_hash(code_hash.as_ptr()) } } - fn code_size(address: &[u8; 20], output: &mut [u8; 32]) { - unsafe { sys::code_size(address.as_ptr(), output.as_mut_ptr()) } + #[unstable_hostfn] + 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 own_code_hash(output: &mut [u8; 32]) { - unsafe { sys::own_code_hash(output.as_mut_ptr()) } + #[unstable_hostfn] + 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 lock_delegate_dependency(code_hash: &[u8; 32]) { - unsafe { sys::lock_delegate_dependency(code_hash.as_ptr()) } + #[unstable_hostfn] + fn terminate(beneficiary: &[u8; 20]) -> ! { + unsafe { sys::terminate(beneficiary.as_ptr()) } + panic!("terminate does not return"); } - fn unlock_delegate_dependency(code_hash: &[u8; 32]) { - unsafe { sys::unlock_delegate_dependency(code_hash.as_ptr()) } + #[unstable_hostfn] + fn weight_left(output: &mut &mut [u8]) { + let mut output_len = output.len() as u32; + unsafe { sys::weight_left(output.as_mut_ptr(), &mut output_len) } + extract_from_slice(output, output_len as usize) } + #[unstable_hostfn] fn xcm_execute(msg: &[u8]) -> Result { let ret_code = unsafe { sys::xcm_execute(msg.as_ptr(), msg.len() as _) }; ret_code.into() } + #[unstable_hostfn] fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result { let ret_code = unsafe { sys::xcm_send( @@ -573,20 +627,4 @@ impl HostFn for HostFnImpl { }; ret_code.into() } - - fn return_data_size(output: &mut [u8; 32]) { - unsafe { sys::return_data_size(output.as_mut_ptr()) }; - } - - fn return_data_copy(output: &mut &mut [u8], offset: u32) { - let mut output_len = output.len() as u32; - { - unsafe { sys::return_data_copy(output.as_mut_ptr(), &mut output_len, offset) }; - } - extract_from_slice(output, output_len as usize); - } - - fn block_hash(block_number_ptr: &[u8; 32], output: &mut [u8; 32]) { - unsafe { sys::block_hash(block_number_ptr.as_ptr(), output.as_mut_ptr()) }; - } } diff --git a/substrate/frame/revive/uapi/src/lib.rs b/substrate/frame/revive/uapi/src/lib.rs index e660ce36ef75eb846155a3dd08db28f3f980f697..744a2f0bca5d194c1b78c9369b36aa57bbf3321f 100644 --- a/substrate/frame/revive/uapi/src/lib.rs +++ b/substrate/frame/revive/uapi/src/lib.rs @@ -17,6 +17,7 @@ //! Refer to substrate FRAME contract module for more documentation. #![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] mod flags; pub use flags::*; @@ -65,6 +66,12 @@ impl From<ReturnErrorCode> for u32 { } } +impl From<ReturnErrorCode> for u64 { + fn from(error: ReturnErrorCode) -> Self { + u32::from(error).into() + } +} + define_error_codes! { /// The called function trapped and has its state changes reverted. /// In this case no output buffer is returned. @@ -79,23 +86,21 @@ define_error_codes! { /// 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 subcall ran out of weight or storage deposit. + OutOfResources = 5, /// The call dispatched by `call_runtime` was executed but returned an error. - CallRuntimeFailed = 8, + CallRuntimeFailed = 6, /// ECDSA public key recovery failed. Most probably wrong recovery id or signature. - EcdsaRecoveryFailed = 9, + EcdsaRecoveryFailed = 7, /// sr25519 signature verification failed. - Sr25519VerifyFailed = 10, + Sr25519VerifyFailed = 8, /// The `xcm_execute` call failed. - XcmExecutionFailed = 11, + XcmExecutionFailed = 9, /// The `xcm_send` call failed. - XcmSendFailed = 12, + XcmSendFailed = 10, + /// Contract instantiation failed because the address already exists. + /// Occurs when instantiating the same contract with the same salt more than once. + DuplicateContractAddress = 11, } /// The raw return code returned by the host side. @@ -129,3 +134,14 @@ impl ReturnCode { } type Result = core::result::Result<(), ReturnErrorCode>; + +/// Helper to pack two `u32` values into a `u64` register. +/// +/// Pointers to PVM memory are always 32 bit in size. Thus contracts can pack two +/// pointers into a single register when calling a syscall API method. +/// +/// This is done in syscall API methods where the number of arguments is exceeding +/// the available registers. +pub fn pack_hi_lo(hi: u32, lo: u32) -> u64 { + ((hi as u64) << 32) | lo as u64 +} diff --git a/substrate/frame/root-offences/Cargo.toml b/substrate/frame/root-offences/Cargo.toml index f80fed11b97101179e07695b3bb5a4e93017f7c1..c539f1dc4dc10a821c4349cf539f95340a330e1e 100644 --- a/substrate/frame/root-offences/Cargo.toml +++ b/substrate/frame/root-offences/Cargo.toml @@ -29,12 +29,11 @@ sp-staking = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } pallet-staking-reward-curve = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true } -sp-std = { workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } diff --git a/substrate/frame/root-offences/src/mock.rs b/substrate/frame/root-offences/src/mock.rs index a27fb36f64a6478f00a1379160bbcd7d63c59d6b..9b319cabb09ed78ddbd4bacd76f0896554eb098a 100644 --- a/substrate/frame/root-offences/src/mock.rs +++ b/substrate/frame/root-offences/src/mock.rs @@ -25,7 +25,7 @@ use frame_election_provider_support::{ }; use frame_support::{ derive_impl, parameter_types, - traits::{ConstU32, ConstU64, Hooks, OneSessionHandler}, + traits::{ConstU32, ConstU64, OneSessionHandler}, }; use pallet_staking::StakerStatus; use sp_runtime::{curve::PiecewiseLinear, testing::UintAuthorityId, traits::Zero, BuildStorage}; @@ -126,6 +126,7 @@ parameter_types! { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Test { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = <Self as pallet_balances::Config>::Balance; type UnixTime = Timestamp; @@ -206,11 +207,12 @@ impl ExtBuilder { (30, self.balance_factor * 50), (40, self.balance_factor * 50), // stashes - (11, self.balance_factor * 1000), - (21, self.balance_factor * 1000), - (31, self.balance_factor * 500), - (41, self.balance_factor * 1000), + (11, self.balance_factor * 1500), + (21, self.balance_factor * 1500), + (31, self.balance_factor * 1000), + (41, self.balance_factor * 2000), ], + ..Default::default() } .assimilate_storage(&mut storage) .unwrap(); @@ -283,16 +285,12 @@ pub(crate) fn start_session(session_index: SessionIndex) { /// a block import/propose process where we first initialize the block, then execute some stuff (not /// in the function), and then finalize the block. pub(crate) fn run_to_block(n: BlockNumber) { - Staking::on_finalize(System::block_number()); - for b in (System::block_number() + 1)..=n { - System::set_block_number(b); - Session::on_initialize(b); - <Staking as Hooks<u64>>::on_initialize(b); - Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP); - if b != n { - Staking::on_finalize(System::block_number()); - } - } + System::run_to_block_with::<AllPalletsWithSystem>( + n, + frame_system::RunToBlockHooks::default().after_initialize(|bn| { + Timestamp::set_timestamp(bn * BLOCK_TIME + INIT_TIMESTAMP); + }), + ); } pub(crate) fn active_era() -> EraIndex { diff --git a/substrate/frame/root-testing/Cargo.toml b/substrate/frame/root-testing/Cargo.toml index ee3ce80110090e8cd0fdef08b223ff5e6011337c..fd0f4da2e80c3eb590c0404cbf9c86b31c437608 100644 --- a/substrate/frame/root-testing/Cargo.toml +++ b/substrate/frame/root-testing/Cargo.toml @@ -17,9 +17,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/safe-mode/Cargo.toml b/substrate/frame/safe-mode/Cargo.toml index e7f165ae67d8c08287db91c95c774feaf332df5e..3f1f6bc1f1d649e06febc12c1c2b248600806bcf 100644 --- a/substrate/frame/safe-mode/Cargo.toml +++ b/substrate/frame/safe-mode/Cargo.toml @@ -20,20 +20,20 @@ docify = { workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +pallet-balances = { optional = true, workspace = true } +pallet-proxy = { optional = true, workspace = true } +pallet-utility = { optional = true, workspace = true } scale-info = { features = ["derive"], workspace = true } sp-arithmetic = { workspace = true } sp-runtime = { workspace = true } -pallet-balances = { optional = true, workspace = true } -pallet-utility = { optional = true, workspace = true } -pallet-proxy = { optional = true, workspace = true } [dev-dependencies] -sp-core = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } +frame-support = { features = ["experimental"], workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } -pallet-utility = { workspace = true, default-features = true } pallet-proxy = { workspace = true, default-features = true } -frame-support = { features = ["experimental"], workspace = true, default-features = true } +pallet-utility = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/safe-mode/src/mock.rs b/substrate/frame/safe-mode/src/mock.rs index aaf3456272fa07316a412b4a17448b5ba84f4e64..2980f86abc2811143b137ce669c856c257d6760c 100644 --- a/substrate/frame/safe-mode/src/mock.rs +++ b/substrate/frame/safe-mode/src/mock.rs @@ -233,6 +233,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig::<Test> { // The 0 account is NOT a special origin, the rest may be. balances: vec![(0, BAL_ACC0), (1, BAL_ACC1), (2, 5678), (3, 5678), (4, 5678)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/salary/Cargo.toml b/substrate/frame/salary/Cargo.toml index 9e4cf06288dd5e624119541d2e58c8b09acfabc9..84c55b110c8c2b3ebc1b948791311e551094dadf 100644 --- a/substrate/frame/salary/Cargo.toml +++ b/substrate/frame/salary/Cargo.toml @@ -17,43 +17,25 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -frame-benchmarking = { optional = true, workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } -sp-arithmetic = { workspace = true } -sp-core = { workspace = true } -sp-io = { workspace = true } -sp-runtime = { workspace = true } pallet-ranked-collective = { optional = true, workspace = true } +scale-info = { features = ["derive"], workspace = true } [features] default = ["std"] std = [ "codec/std", - "frame-benchmarking?/std", - "frame-support/experimental", - "frame-support/std", - "frame-system/std", + "frame/std", "log/std", "pallet-ranked-collective/std", "scale-info/std", - "sp-arithmetic/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", ] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", + "frame/runtime-benchmarks", "pallet-ranked-collective/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", + "frame/try-runtime", "pallet-ranked-collective?/try-runtime", - "sp-runtime/try-runtime", ] diff --git a/substrate/frame/salary/src/benchmarking.rs b/substrate/frame/salary/src/benchmarking.rs index aeae8d2d67f88ba8509f0aed7031b7983233a1d3..6dfd6f6dd488933eaf65f93820c9ed1f2f2f21aa 100644 --- a/substrate/frame/salary/src/benchmarking.rs +++ b/substrate/frame/salary/src/benchmarking.rs @@ -22,10 +22,7 @@ use super::*; use crate::Pallet as Salary; -use frame_benchmarking::v2::*; -use frame_system::{Pallet as System, RawOrigin}; -use sp_core::Get; - +use frame::benchmarking::prelude::*; const SEED: u32 = 0; fn ensure_member_with_salary<T: Config<I>, I: 'static>(who: &T::AccountId) { @@ -37,7 +34,7 @@ fn ensure_member_with_salary<T: Config<I>, I: 'static>(who: &T::AccountId) { for _ in 0..255 { let r = T::Members::rank_of(who).expect("prior guard ensures `who` is a member; qed"); if !T::Salary::get_salary(r, &who).is_zero() { - break + break; } T::Members::promote(who).unwrap(); } diff --git a/substrate/frame/salary/src/lib.rs b/substrate/frame/salary/src/lib.rs index efb4f5d3c5422d96b2b2038122de8c2d391cc855..6a843625f4a7bc61901b890f79135c183188d354 100644 --- a/substrate/frame/salary/src/lib.rs +++ b/substrate/frame/salary/src/lib.rs @@ -19,20 +19,10 @@ #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, Encode, MaxEncodedLen}; use core::marker::PhantomData; -use scale_info::TypeInfo; -use sp_arithmetic::traits::{Saturating, Zero}; -use sp_runtime::{Perbill, RuntimeDebug}; - -use frame_support::{ - defensive, - dispatch::DispatchResultWithPostInfo, - ensure, - traits::{ - tokens::{GetSalary, Pay, PaymentStatus}, - RankedMembers, RankedMembersSwapHandler, - }, +use frame::{ + prelude::*, + traits::tokens::{GetSalary, Pay, PaymentStatus}, }; #[cfg(test)] @@ -85,12 +75,9 @@ pub struct ClaimantStatus<CycleIndex, Balance, Id> { status: ClaimState<Balance, Id>, } -#[frame_support::pallet] +#[frame::pallet] pub mod pallet { use super::*; - use frame_support::{dispatch::Pays, pallet_prelude::*}; - use frame_system::pallet_prelude::*; - #[pallet::pallet] pub struct Pallet<T, I = ()>(PhantomData<(T, I)>); @@ -460,15 +447,15 @@ impl<T: Config<I>, I: 'static> ) { if who == new_who { defensive!("Should not try to swap with self"); - return + return; } if Claimant::<T, I>::contains_key(new_who) { defensive!("Should not try to overwrite existing claimant"); - return + return; } let Some(claimant) = Claimant::<T, I>::take(who) else { - frame_support::defensive!("Claimant should exist when swapping"); + defensive!("Claimant should exist when swapping"); return; }; diff --git a/substrate/frame/salary/src/tests/integration.rs b/substrate/frame/salary/src/tests/integration.rs index 0c1fb8bbdcba08f9c171b4c0f78f34a5b091d5f4..e4e9c8f6a31b5e8adfd15d7ab6b984193eef3939 100644 --- a/substrate/frame/salary/src/tests/integration.rs +++ b/substrate/frame/salary/src/tests/integration.rs @@ -19,25 +19,14 @@ use crate as pallet_salary; use crate::*; -use frame_support::{ - assert_noop, assert_ok, derive_impl, hypothetically, - pallet_prelude::Weight, - parameter_types, - traits::{ConstU64, EitherOf, MapSuccess, NoOpPoll}, -}; +use frame::{deps::sp_io, testing_prelude::*}; use pallet_ranked_collective::{EnsureRanked, Geometric}; -use sp_core::{ConstU16, Get}; -use sp_runtime::{ - traits::{Convert, ReduceBy, ReplaceWithDefault}, - BuildStorage, -}; type Rank = u16; type Block = frame_system::mocking::MockBlock<Test>; -frame_support::construct_runtime!( - pub enum Test - { +construct_runtime!( + pub struct Test { System: frame_system, Salary: pallet_salary, Club: pallet_ranked_collective, @@ -145,9 +134,9 @@ impl pallet_ranked_collective::Config for Test { type BenchmarkSetup = Salary; } -pub fn new_test_ext() -> sp_io::TestExternalities { +pub fn new_test_ext() -> TestState { let t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); - let mut ext = sp_io::TestExternalities::new(t); + let mut ext = TestState::new(t); ext.execute_with(|| System::set_block_number(1)); ext } @@ -194,7 +183,7 @@ fn swap_exhaustive_works() { // The events mess up the storage root: System::reset_events(); - sp_io::storage::root(sp_runtime::StateVersion::V1) + sp_io::storage::root(StateVersion::V1) }); let root_swap = hypothetically!({ @@ -207,7 +196,7 @@ fn swap_exhaustive_works() { // The events mess up the storage root: System::reset_events(); - sp_io::storage::root(sp_runtime::StateVersion::V1) + sp_io::storage::root(StateVersion::V1) }); assert_eq!(root_add, root_swap); diff --git a/substrate/frame/salary/src/tests/unit.rs b/substrate/frame/salary/src/tests/unit.rs index db1c8b947ef57c7c1919568f5bf0545f0449994d..3bb7bc4adf1ea5c04626cc774f416e1494950dae 100644 --- a/substrate/frame/salary/src/tests/unit.rs +++ b/substrate/frame/salary/src/tests/unit.rs @@ -17,23 +17,15 @@ //! The crate's tests. -use std::collections::BTreeMap; - -use core::cell::RefCell; -use frame_support::{ - assert_noop, assert_ok, derive_impl, - pallet_prelude::Weight, - parameter_types, - traits::{tokens::ConvertRank, ConstU64}, -}; -use sp_runtime::{traits::Identity, BuildStorage, DispatchResult}; - use crate as pallet_salary; use crate::*; +use core::cell::RefCell; +use frame::{deps::sp_runtime::traits::Identity, testing_prelude::*, traits::tokens::ConvertRank}; +use std::collections::BTreeMap; -type Block = frame_system::mocking::MockBlock<Test>; +type Block = MockBlock<Test>; -frame_support::construct_runtime!( +construct_runtime!( pub enum Test { System: frame_system, @@ -124,7 +116,7 @@ impl RankedMembers for TestClub { } fn demote(who: &Self::AccountId) -> DispatchResult { CLUB.with(|club| match club.borrow().get(who) { - None => Err(sp_runtime::DispatchError::Unavailable), + None => Err(DispatchError::Unavailable), Some(&0) => { club.borrow_mut().remove(&who); Ok(()) @@ -156,9 +148,9 @@ impl Config for Test { type Budget = Budget; } -pub fn new_test_ext() -> sp_io::TestExternalities { +pub fn new_test_ext() -> TestState { let t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); - let mut ext = sp_io::TestExternalities::new(t); + let mut ext = TestState::new(t); ext.execute_with(|| System::set_block_number(1)); ext } diff --git a/substrate/frame/salary/src/weights.rs b/substrate/frame/salary/src/weights.rs index f1cdaaa225a44e7c7854780cca93ad04f4a59ca8..43c001b30d336d54fef9a88b07ff182bfa5e9cd9 100644 --- a/substrate/frame/salary/src/weights.rs +++ b/substrate/frame/salary/src/weights.rs @@ -46,8 +46,8 @@ #![allow(unused_imports)] #![allow(missing_docs)] -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use core::marker::PhantomData; +use frame::weights_prelude::*; /// Weight functions needed for `pallet_salary`. pub trait WeightInfo { diff --git a/substrate/frame/sassafras/Cargo.toml b/substrate/frame/sassafras/Cargo.toml index 7eb2bda96ffc3fb707772cb2c162c873bedfd087..dd091b6f8ed79c4686ad9643be4c5da14a526530 100644 --- a/substrate/frame/sassafras/Cargo.toml +++ b/substrate/frame/sassafras/Cargo.toml @@ -18,11 +18,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-consensus-sassafras = { features = ["serde"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/scheduler/Cargo.toml b/substrate/frame/scheduler/Cargo.toml index 1432ada913350e9715e3e854a324fde771aaaee8..0506470e72c32fafe01c906ffc8e0fe2a3ba389c 100644 --- a/substrate/frame/scheduler/Cargo.toml +++ b/substrate/frame/scheduler/Cargo.toml @@ -14,15 +14,15 @@ workspace = true [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } +docify = { workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } sp-weights = { workspace = true } -docify = { workspace = true } [dev-dependencies] pallet-preimage = { workspace = true, default-features = true } diff --git a/substrate/frame/scheduler/src/mock.rs b/substrate/frame/scheduler/src/mock.rs index 8d36ca1c42e3ad916a5cd38e2ef3fa7bcfea5483..43a964bcf149731d7df03ad80d6247a82b2a8ec0 100644 --- a/substrate/frame/scheduler/src/mock.rs +++ b/substrate/frame/scheduler/src/mock.rs @@ -22,7 +22,7 @@ use super::*; use crate as scheduler; use frame_support::{ derive_impl, ord_parameter_types, parameter_types, - traits::{ConstU32, Contains, EitherOfDiverse, EqualPrivilegeOnly, OnFinalize, OnInitialize}, + traits::{ConstU32, Contains, EitherOfDiverse, EqualPrivilegeOnly}, }; use frame_system::{EnsureRoot, EnsureSignedBy}; use sp_runtime::{BuildStorage, Perbill}; @@ -236,14 +236,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { t.into() } -pub fn run_to_block(n: u64) { - while System::block_number() < n { - Scheduler::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - Scheduler::on_initialize(System::block_number()); - } -} - pub fn root() -> OriginCaller { system::RawOrigin::Root.into() } diff --git a/substrate/frame/scheduler/src/tests.rs b/substrate/frame/scheduler/src/tests.rs index 3023a370a4b6034e4ec9366fcaa065035edccec3..7552239341088d7ad46c287edc23f1dd5b417088 100644 --- a/substrate/frame/scheduler/src/tests.rs +++ b/substrate/frame/scheduler/src/tests.rs @@ -20,7 +20,7 @@ use super::*; use crate::mock::{ logger::{self, Threshold}, - new_test_ext, root, run_to_block, LoggerCall, RuntimeCall, Scheduler, Test, *, + new_test_ext, root, LoggerCall, RuntimeCall, Scheduler, Test, *, }; use frame_support::{ assert_err, assert_noop, assert_ok, @@ -52,14 +52,14 @@ fn basic_scheduling_works() { )); // `log` runtime call should not have executed yet - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert!(logger::log().is_empty()); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); // `log` runtime call should have executed at block 4 assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -87,17 +87,17 @@ fn scheduling_with_preimages_works() { assert!(Preimage::is_requested(&hash)); // `log` runtime call should not have executed yet - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert!(logger::log().is_empty()); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); // preimage should not have been removed when executed by the scheduler assert!(!Preimage::len(&hash).is_some()); assert!(!Preimage::is_requested(&hash)); // `log` runtime call should have executed at block 4 assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -105,7 +105,7 @@ fn scheduling_with_preimages_works() { #[test] fn schedule_after_works() { new_test_ext().execute_with(|| { - run_to_block(2); + System::run_to_block::<AllPalletsWithSystem>(2); let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); assert!(!<Test as frame_system::Config>::BaseCallFilter::contains(&call)); @@ -117,11 +117,11 @@ fn schedule_after_works() { root(), Preimage::bound(call).unwrap() )); - run_to_block(5); + System::run_to_block::<AllPalletsWithSystem>(5); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::<AllPalletsWithSystem>(6); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -129,7 +129,7 @@ fn schedule_after_works() { #[test] fn schedule_after_zero_works() { new_test_ext().execute_with(|| { - run_to_block(2); + System::run_to_block::<AllPalletsWithSystem>(2); let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); assert!(!<Test as frame_system::Config>::BaseCallFilter::contains(&call)); @@ -141,9 +141,9 @@ fn schedule_after_zero_works() { Preimage::bound(call).unwrap() )); // Will trigger on the next block. - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -163,19 +163,19 @@ fn periodic_scheduling_works() { })) .unwrap() )); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert!(logger::log().is_empty()); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(6); + System::run_to_block::<AllPalletsWithSystem>(6); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(7); + System::run_to_block::<AllPalletsWithSystem>(7); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(9); + System::run_to_block::<AllPalletsWithSystem>(9); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); }); } @@ -201,37 +201,37 @@ fn retry_scheduling_works() { // retry 10 times every 3 blocks assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 3)); assert_eq!(Retries::<Test>::iter().count(), 1); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert!(logger::log().is_empty()); assert!(Agenda::<Test>::get(4)[0].is_some()); // task should be retried in block 7 - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert!(Agenda::<Test>::get(4).is_empty()); assert!(Agenda::<Test>::get(7)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::<AllPalletsWithSystem>(6); assert!(Agenda::<Test>::get(7)[0].is_some()); assert!(logger::log().is_empty()); // task still fails, should be retried in block 10 - run_to_block(7); + System::run_to_block::<AllPalletsWithSystem>(7); assert!(Agenda::<Test>::get(7).is_empty()); assert!(Agenda::<Test>::get(10)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(8); + System::run_to_block::<AllPalletsWithSystem>(8); assert!(Agenda::<Test>::get(10)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(9); + System::run_to_block::<AllPalletsWithSystem>(9); assert!(logger::log().is_empty()); assert_eq!(Retries::<Test>::iter().count(), 1); // finally it should succeed - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); assert_eq!(logger::log(), vec![(root(), 42u32)]); assert_eq!(Retries::<Test>::iter().count(), 0); - run_to_block(11); + System::run_to_block::<AllPalletsWithSystem>(11); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(12); + System::run_to_block::<AllPalletsWithSystem>(12); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -262,37 +262,37 @@ fn named_retry_scheduling_works() { // retry 10 times every 3 blocks assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 3)); assert_eq!(Retries::<Test>::iter().count(), 1); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert!(logger::log().is_empty()); assert!(Agenda::<Test>::get(4)[0].is_some()); // task should be retried in block 7 - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert!(Agenda::<Test>::get(4).is_empty()); assert!(Agenda::<Test>::get(7)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::<AllPalletsWithSystem>(6); assert!(Agenda::<Test>::get(7)[0].is_some()); assert!(logger::log().is_empty()); // task still fails, should be retried in block 10 - run_to_block(7); + System::run_to_block::<AllPalletsWithSystem>(7); assert!(Agenda::<Test>::get(7).is_empty()); assert!(Agenda::<Test>::get(10)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(8); + System::run_to_block::<AllPalletsWithSystem>(8); assert!(Agenda::<Test>::get(10)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(9); + System::run_to_block::<AllPalletsWithSystem>(9); assert!(logger::log().is_empty()); assert_eq!(Retries::<Test>::iter().count(), 1); // finally it should succeed - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); assert_eq!(logger::log(), vec![(root(), 42u32)]); assert_eq!(Retries::<Test>::iter().count(), 0); - run_to_block(11); + System::run_to_block::<AllPalletsWithSystem>(11); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(12); + System::run_to_block::<AllPalletsWithSystem>(12); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -333,11 +333,11 @@ fn retry_scheduling_multiple_tasks_works() { // task 42 will be retried 10 times every 3 blocks assert_ok!(Scheduler::set_retry(root().into(), (4, 1), 10, 3)); assert_eq!(Retries::<Test>::iter().count(), 2); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert!(logger::log().is_empty()); assert_eq!(Agenda::<Test>::get(4).len(), 2); // both tasks fail - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert!(Agenda::<Test>::get(4).is_empty()); // 20 is rescheduled for next block assert_eq!(Agenda::<Test>::get(5).len(), 1); @@ -345,41 +345,41 @@ fn retry_scheduling_multiple_tasks_works() { assert_eq!(Agenda::<Test>::get(7).len(), 1); assert!(logger::log().is_empty()); // 20 still fails - run_to_block(5); + System::run_to_block::<AllPalletsWithSystem>(5); // 20 rescheduled for next block assert_eq!(Agenda::<Test>::get(6).len(), 1); assert_eq!(Agenda::<Test>::get(7).len(), 1); assert_eq!(Retries::<Test>::iter().count(), 2); assert!(logger::log().is_empty()); // 20 still fails - run_to_block(6); + System::run_to_block::<AllPalletsWithSystem>(6); // rescheduled for next block together with 42 assert_eq!(Agenda::<Test>::get(7).len(), 2); assert_eq!(Retries::<Test>::iter().count(), 2); assert!(logger::log().is_empty()); // both tasks will fail, for 20 it was the last retry so it's dropped - run_to_block(7); + System::run_to_block::<AllPalletsWithSystem>(7); assert!(Agenda::<Test>::get(7).is_empty()); assert!(Agenda::<Test>::get(8).is_empty()); // 42 is rescheduled for block 10 assert_eq!(Agenda::<Test>::get(10).len(), 1); assert_eq!(Retries::<Test>::iter().count(), 1); assert!(logger::log().is_empty()); - run_to_block(8); + System::run_to_block::<AllPalletsWithSystem>(8); assert_eq!(Agenda::<Test>::get(10).len(), 1); assert!(logger::log().is_empty()); - run_to_block(9); + System::run_to_block::<AllPalletsWithSystem>(9); assert!(logger::log().is_empty()); assert_eq!(Retries::<Test>::iter().count(), 1); // 42 runs successfully - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); assert_eq!(logger::log(), vec![(root(), 42u32)]); assert_eq!(Retries::<Test>::iter().count(), 0); - run_to_block(11); + System::run_to_block::<AllPalletsWithSystem>(11); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(12); + System::run_to_block::<AllPalletsWithSystem>(12); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -422,11 +422,11 @@ fn retry_scheduling_multiple_named_tasks_works() { // task 42 will be retried 10 times every 3 block assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 10, 3)); assert_eq!(Retries::<Test>::iter().count(), 2); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert!(logger::log().is_empty()); assert_eq!(Agenda::<Test>::get(4).len(), 2); // both tasks fail - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert!(Agenda::<Test>::get(4).is_empty()); // 42 is rescheduled for block 7 assert_eq!(Agenda::<Test>::get(7).len(), 1); @@ -434,41 +434,41 @@ fn retry_scheduling_multiple_named_tasks_works() { assert_eq!(Agenda::<Test>::get(5).len(), 1); assert!(logger::log().is_empty()); // 20 still fails - run_to_block(5); + System::run_to_block::<AllPalletsWithSystem>(5); // 20 rescheduled for next block assert_eq!(Agenda::<Test>::get(6).len(), 1); assert_eq!(Agenda::<Test>::get(7).len(), 1); assert_eq!(Retries::<Test>::iter().count(), 2); assert!(logger::log().is_empty()); // 20 still fails - run_to_block(6); + System::run_to_block::<AllPalletsWithSystem>(6); // 20 rescheduled for next block together with 42 assert_eq!(Agenda::<Test>::get(7).len(), 2); assert_eq!(Retries::<Test>::iter().count(), 2); assert!(logger::log().is_empty()); // both tasks will fail, for 20 it was the last retry so it's dropped - run_to_block(7); + System::run_to_block::<AllPalletsWithSystem>(7); assert!(Agenda::<Test>::get(7).is_empty()); assert!(Agenda::<Test>::get(8).is_empty()); // 42 is rescheduled for block 10 assert_eq!(Agenda::<Test>::get(10).len(), 1); assert_eq!(Retries::<Test>::iter().count(), 1); assert!(logger::log().is_empty()); - run_to_block(8); + System::run_to_block::<AllPalletsWithSystem>(8); assert_eq!(Agenda::<Test>::get(10).len(), 1); assert!(logger::log().is_empty()); - run_to_block(9); + System::run_to_block::<AllPalletsWithSystem>(9); assert!(logger::log().is_empty()); assert_eq!(Retries::<Test>::iter().count(), 1); // 42 runs successfully - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); assert_eq!(logger::log(), vec![(root(), 42u32)]); assert_eq!(Retries::<Test>::iter().count(), 0); - run_to_block(11); + System::run_to_block::<AllPalletsWithSystem>(11); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(12); + System::run_to_block::<AllPalletsWithSystem>(12); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -495,33 +495,33 @@ fn retry_scheduling_with_period_works() { // 42 will be retried 10 times every 2 blocks assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 2)); assert_eq!(Retries::<Test>::iter().count(), 1); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert!(logger::log().is_empty()); assert!(Agenda::<Test>::get(4)[0].is_some()); // 42 runs successfully once, it will run again at block 7 - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert!(Agenda::<Test>::get(4).is_empty()); assert!(Agenda::<Test>::get(7)[0].is_some()); assert_eq!(Retries::<Test>::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32)]); // nothing changed - run_to_block(6); + System::run_to_block::<AllPalletsWithSystem>(6); assert!(Agenda::<Test>::get(7)[0].is_some()); assert_eq!(logger::log(), vec![(root(), 42u32)]); // 42 runs successfully again, it will run again at block 10 - run_to_block(7); + System::run_to_block::<AllPalletsWithSystem>(7); assert!(Agenda::<Test>::get(7).is_empty()); assert!(Agenda::<Test>::get(10)[0].is_some()); assert_eq!(Retries::<Test>::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(9); + System::run_to_block::<AllPalletsWithSystem>(9); assert!(Agenda::<Test>::get(10)[0].is_some()); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 has 10 retries left out of a total of 10 assert_eq!(Retries::<Test>::get((10, 0)).unwrap().remaining, 10); // 42 will fail because we're outside the set threshold (block number in `4..8`), so it // should be retried in 2 blocks (at block 12) - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); // should be queued for the normal period of 3 blocks assert!(Agenda::<Test>::get(13)[0].is_some()); // should also be queued to be retried in 2 blocks @@ -532,7 +532,7 @@ fn retry_scheduling_with_period_works() { assert_eq!(Retries::<Test>::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 will fail again - run_to_block(12); + System::run_to_block::<AllPalletsWithSystem>(12); // should still be queued for the normal period assert!(Agenda::<Test>::get(13)[0].is_some()); // should be queued to be retried in 2 blocks @@ -543,7 +543,7 @@ fn retry_scheduling_with_period_works() { assert_eq!(Retries::<Test>::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 will fail for the regular periodic run - run_to_block(13); + System::run_to_block::<AllPalletsWithSystem>(13); // should still be queued for the normal period assert!(Agenda::<Test>::get(16)[0].is_some()); // should still be queued to be retried next block @@ -560,7 +560,7 @@ fn retry_scheduling_with_period_works() { // change the threshold to allow the task to succeed Threshold::<Test>::put((14, 100)); // first retry should now succeed - run_to_block(14); + System::run_to_block::<AllPalletsWithSystem>(14); assert!(Agenda::<Test>::get(15)[0].as_ref().unwrap().maybe_periodic.is_none()); assert_eq!(Agenda::<Test>::get(16).iter().filter(|entry| entry.is_some()).count(), 1); assert!(Agenda::<Test>::get(16)[0].is_some()); @@ -569,7 +569,7 @@ fn retry_scheduling_with_period_works() { assert_eq!(Retries::<Test>::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); // second retry should also succeed - run_to_block(15); + System::run_to_block::<AllPalletsWithSystem>(15); assert_eq!(Agenda::<Test>::get(16).iter().filter(|entry| entry.is_some()).count(), 1); assert!(Agenda::<Test>::get(16)[0].is_some()); assert!(Agenda::<Test>::get(17).is_empty()); @@ -580,7 +580,7 @@ fn retry_scheduling_with_period_works() { vec![(root(), 42u32), (root(), 42u32), (root(), 42u32), (root(), 42u32)] ); // normal periodic run on block 16 will succeed - run_to_block(16); + System::run_to_block::<AllPalletsWithSystem>(16); // next periodic run at block 19 assert!(Agenda::<Test>::get(19)[0].is_some()); assert!(Agenda::<Test>::get(18).is_empty()); @@ -598,7 +598,7 @@ fn retry_scheduling_with_period_works() { ] ); // final periodic run on block 19 will succeed - run_to_block(19); + System::run_to_block::<AllPalletsWithSystem>(19); // next periodic run at block 19 assert_eq!(Agenda::<Test>::iter().count(), 0); assert_eq!(Retries::<Test>::iter().count(), 0); @@ -639,33 +639,33 @@ fn named_retry_scheduling_with_period_works() { // 42 will be retried 10 times every 2 blocks assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 10, 2)); assert_eq!(Retries::<Test>::iter().count(), 1); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert!(logger::log().is_empty()); assert!(Agenda::<Test>::get(4)[0].is_some()); // 42 runs successfully once, it will run again at block 7 - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert!(Agenda::<Test>::get(4).is_empty()); assert!(Agenda::<Test>::get(7)[0].is_some()); assert_eq!(Retries::<Test>::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32)]); // nothing changed - run_to_block(6); + System::run_to_block::<AllPalletsWithSystem>(6); assert!(Agenda::<Test>::get(7)[0].is_some()); assert_eq!(logger::log(), vec![(root(), 42u32)]); // 42 runs successfully again, it will run again at block 10 - run_to_block(7); + System::run_to_block::<AllPalletsWithSystem>(7); assert!(Agenda::<Test>::get(7).is_empty()); assert!(Agenda::<Test>::get(10)[0].is_some()); assert_eq!(Retries::<Test>::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(9); + System::run_to_block::<AllPalletsWithSystem>(9); assert!(Agenda::<Test>::get(10)[0].is_some()); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 has 10 retries left out of a total of 10 assert_eq!(Retries::<Test>::get((10, 0)).unwrap().remaining, 10); // 42 will fail because we're outside the set threshold (block number in `4..8`), so it // should be retried in 2 blocks (at block 12) - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); // should be queued for the normal period of 3 blocks assert!(Agenda::<Test>::get(13)[0].is_some()); // should also be queued to be retried in 2 blocks @@ -677,7 +677,7 @@ fn named_retry_scheduling_with_period_works() { assert_eq!(Lookup::<Test>::get([42u8; 32]).unwrap(), (13, 0)); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 will fail again - run_to_block(12); + System::run_to_block::<AllPalletsWithSystem>(12); // should still be queued for the normal period assert!(Agenda::<Test>::get(13)[0].is_some()); // should be queued to be retried in 2 blocks @@ -688,7 +688,7 @@ fn named_retry_scheduling_with_period_works() { assert_eq!(Retries::<Test>::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 will fail for the regular periodic run - run_to_block(13); + System::run_to_block::<AllPalletsWithSystem>(13); // should still be queued for the normal period assert!(Agenda::<Test>::get(16)[0].is_some()); // should still be queued to be retried next block @@ -706,7 +706,7 @@ fn named_retry_scheduling_with_period_works() { // change the threshold to allow the task to succeed Threshold::<Test>::put((14, 100)); // first retry should now succeed - run_to_block(14); + System::run_to_block::<AllPalletsWithSystem>(14); assert!(Agenda::<Test>::get(15)[0].as_ref().unwrap().maybe_periodic.is_none()); assert_eq!(Agenda::<Test>::get(16).iter().filter(|entry| entry.is_some()).count(), 1); assert!(Agenda::<Test>::get(16)[0].is_some()); @@ -715,7 +715,7 @@ fn named_retry_scheduling_with_period_works() { assert_eq!(Retries::<Test>::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); // second retry should also succeed - run_to_block(15); + System::run_to_block::<AllPalletsWithSystem>(15); assert_eq!(Agenda::<Test>::get(16).iter().filter(|entry| entry.is_some()).count(), 1); assert!(Agenda::<Test>::get(16)[0].is_some()); assert!(Agenda::<Test>::get(17).is_empty()); @@ -727,7 +727,7 @@ fn named_retry_scheduling_with_period_works() { vec![(root(), 42u32), (root(), 42u32), (root(), 42u32), (root(), 42u32)] ); // normal periodic run on block 16 will succeed - run_to_block(16); + System::run_to_block::<AllPalletsWithSystem>(16); // next periodic run at block 19 assert!(Agenda::<Test>::get(19)[0].is_some()); assert!(Agenda::<Test>::get(18).is_empty()); @@ -746,7 +746,7 @@ fn named_retry_scheduling_with_period_works() { ] ); // final periodic run on block 19 will succeed - run_to_block(19); + System::run_to_block::<AllPalletsWithSystem>(19); // next periodic run at block 19 assert_eq!(Agenda::<Test>::iter().count(), 0); assert_eq!(Retries::<Test>::iter().count(), 0); @@ -786,12 +786,12 @@ fn retry_scheduling_expires() { // task 42 will be retried 3 times every block assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 3, 1)); assert_eq!(Retries::<Test>::iter().count(), 1); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert!(logger::log().is_empty()); // task 42 is scheduled for next block assert!(Agenda::<Test>::get(4)[0].is_some()); // task fails because we're past block 3 - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); // task is scheduled for next block assert!(Agenda::<Test>::get(4).is_empty()); assert!(Agenda::<Test>::get(5)[0].is_some()); @@ -799,7 +799,7 @@ fn retry_scheduling_expires() { assert_eq!(Retries::<Test>::get((5, 0)).unwrap().remaining, 2); assert!(logger::log().is_empty()); // task fails again - run_to_block(5); + System::run_to_block::<AllPalletsWithSystem>(5); // task is scheduled for next block assert!(Agenda::<Test>::get(5).is_empty()); assert!(Agenda::<Test>::get(6)[0].is_some()); @@ -807,7 +807,7 @@ fn retry_scheduling_expires() { assert_eq!(Retries::<Test>::get((6, 0)).unwrap().remaining, 1); assert!(logger::log().is_empty()); // task fails again - run_to_block(6); + System::run_to_block::<AllPalletsWithSystem>(6); // task is scheduled for next block assert!(Agenda::<Test>::get(6).is_empty()); assert!(Agenda::<Test>::get(7)[0].is_some()); @@ -815,7 +815,7 @@ fn retry_scheduling_expires() { assert_eq!(Retries::<Test>::get((7, 0)).unwrap().remaining, 0); assert!(logger::log().is_empty()); // task fails again - run_to_block(7); + System::run_to_block::<AllPalletsWithSystem>(7); // task ran out of retries so it gets dropped assert_eq!(Agenda::<Test>::iter().count(), 0); assert_eq!(Retries::<Test>::iter().count(), 0); @@ -949,17 +949,17 @@ fn retry_periodic_full_cycle() { // 42 will be retried 2 times every block assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 2, 1)); assert_eq!(Retries::<Test>::iter().count(), 1); - run_to_block(9); + System::run_to_block::<AllPalletsWithSystem>(9); assert!(logger::log().is_empty()); assert!(Agenda::<Test>::get(10)[0].is_some()); // 42 runs successfully once, it will run again at block 110 - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); assert!(Agenda::<Test>::get(10).is_empty()); assert!(Agenda::<Test>::get(110)[0].is_some()); assert_eq!(Retries::<Test>::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32)]); // nothing changed - run_to_block(109); + System::run_to_block::<AllPalletsWithSystem>(109); assert!(Agenda::<Test>::get(110)[0].is_some()); // original task still has 2 remaining retries assert_eq!(Retries::<Test>::get((110, 0)).unwrap().remaining, 2); @@ -968,7 +968,7 @@ fn retry_periodic_full_cycle() { Threshold::<Test>::put((1, 2)); // 42 will fail because we're outside the set threshold (block number in `1..2`), so it // should be retried next block (at block 111) - run_to_block(110); + System::run_to_block::<AllPalletsWithSystem>(110); // should be queued for the normal period of 100 blocks assert!(Agenda::<Test>::get(210)[0].is_some()); // should also be queued to be retried next block @@ -980,7 +980,7 @@ fn retry_periodic_full_cycle() { assert_eq!(Retries::<Test>::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32)]); // 42 retry will fail again - run_to_block(111); + System::run_to_block::<AllPalletsWithSystem>(111); // should still be queued for the normal period assert!(Agenda::<Test>::get(210)[0].is_some()); // should be queued to be retried next block @@ -991,20 +991,20 @@ fn retry_periodic_full_cycle() { assert_eq!(Retries::<Test>::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32)]); // 42 retry will fail again - run_to_block(112); + System::run_to_block::<AllPalletsWithSystem>(112); // should still be queued for the normal period assert!(Agenda::<Test>::get(210)[0].is_some()); // 42 retry clone ran out of retries, must have been evicted assert_eq!(Agenda::<Test>::iter().count(), 1); // advance - run_to_block(209); + System::run_to_block::<AllPalletsWithSystem>(209); // should still be queued for the normal period assert!(Agenda::<Test>::get(210)[0].is_some()); // 42 retry clone ran out of retries, must have been evicted assert_eq!(Agenda::<Test>::iter().count(), 1); // 42 should fail again and should spawn another retry clone - run_to_block(210); + System::run_to_block::<AllPalletsWithSystem>(210); // should be queued for the normal period of 100 blocks assert!(Agenda::<Test>::get(310)[0].is_some()); // should also be queued to be retried next block @@ -1018,7 +1018,7 @@ fn retry_periodic_full_cycle() { // make 42 run successfully again Threshold::<Test>::put((1, 1000)); // 42 retry clone should now succeed - run_to_block(211); + System::run_to_block::<AllPalletsWithSystem>(211); // should be queued for the normal period of 100 blocks assert!(Agenda::<Test>::get(310)[0].is_some()); // retry was successful, retry task should have been discarded @@ -1029,7 +1029,7 @@ fn retry_periodic_full_cycle() { assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // fast forward to the last periodic run of 42 - run_to_block(310); + System::run_to_block::<AllPalletsWithSystem>(310); // 42 was successful, the period ended as this was the 4th scheduled periodic run so 42 must // have been discarded assert_eq!(Agenda::<Test>::iter().count(), 0); @@ -1057,7 +1057,7 @@ fn reschedule_works() { (4, 0) ); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert!(logger::log().is_empty()); assert_eq!(Scheduler::do_reschedule((4, 0), DispatchTime::At(6)).unwrap(), (6, 0)); @@ -1067,13 +1067,13 @@ fn reschedule_works() { Error::<Test>::RescheduleNoChange ); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::<AllPalletsWithSystem>(6); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -1097,7 +1097,7 @@ fn reschedule_named_works() { (4, 0) ); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert!(logger::log().is_empty()); assert_eq!(Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(6)).unwrap(), (6, 0)); @@ -1107,13 +1107,13 @@ fn reschedule_named_works() { Error::<Test>::RescheduleNoChange ); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::<AllPalletsWithSystem>(6); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -1137,16 +1137,16 @@ fn reschedule_named_periodic_works() { (4, 0) ); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert!(logger::log().is_empty()); assert_eq!(Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(5)).unwrap(), (5, 0)); assert_eq!(Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(6)).unwrap(), (6, 0)); - run_to_block(5); + System::run_to_block::<AllPalletsWithSystem>(5); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::<AllPalletsWithSystem>(6); assert_eq!(logger::log(), vec![(root(), 42u32)]); assert_eq!( @@ -1154,16 +1154,16 @@ fn reschedule_named_periodic_works() { (10, 0) ); - run_to_block(9); + System::run_to_block::<AllPalletsWithSystem>(9); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(13); + System::run_to_block::<AllPalletsWithSystem>(13); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); }); } @@ -1197,11 +1197,11 @@ fn cancel_named_scheduling_works_with_normal_cancel() { .unwrap(), ) .unwrap(); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert!(logger::log().is_empty()); assert_ok!(Scheduler::do_cancel_named(None, [1u8; 32])); assert_ok!(Scheduler::do_cancel(None, i)); - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert!(logger::log().is_empty()); }); } @@ -1251,13 +1251,13 @@ fn cancel_named_periodic_scheduling_works() { .unwrap(), ) .unwrap(); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert!(logger::log().is_empty()); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(6); + System::run_to_block::<AllPalletsWithSystem>(6); assert_ok!(Scheduler::do_cancel_named(None, [1u8; 32])); - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); }); } @@ -1283,9 +1283,9 @@ fn scheduler_respects_weight_limits() { Preimage::bound(call).unwrap(), )); // 69 and 42 do not fit together - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(5); + System::run_to_block::<AllPalletsWithSystem>(5); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); }); } @@ -1316,26 +1316,26 @@ fn retry_respects_weight_limits() { // set a retry config for 20 for 10 retries every block assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1)); // 20 should fail and be retried later - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert!(Agenda::<Test>::get(5)[0].is_some()); assert!(Agenda::<Test>::get(8)[0].is_some()); assert_eq!(Retries::<Test>::iter().count(), 1); assert!(logger::log().is_empty()); // 20 still fails but is scheduled next block together with 42 - run_to_block(7); + System::run_to_block::<AllPalletsWithSystem>(7); assert_eq!(Agenda::<Test>::get(8).len(), 2); assert_eq!(Retries::<Test>::iter().count(), 1); assert!(logger::log().is_empty()); // 20 and 42 do not fit together // 42 is executed as it was first in the queue // 20 is still on the 8th block's agenda - run_to_block(8); + System::run_to_block::<AllPalletsWithSystem>(8); assert!(Agenda::<Test>::get(8)[0].is_none()); assert!(Agenda::<Test>::get(8)[1].is_some()); assert_eq!(Retries::<Test>::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32)]); // 20 is executed and the schedule is cleared - run_to_block(9); + System::run_to_block::<AllPalletsWithSystem>(9); assert_eq!(Agenda::<Test>::iter().count(), 0); assert_eq!(Retries::<Test>::iter().count(), 0); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 20u32)]); @@ -1386,7 +1386,7 @@ fn try_schedule_retry_respects_weight_limits() { // set a retry config for 20 for 10 retries every block assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1)); // 20 should fail and, because of insufficient weight, it should not be scheduled again - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); // nothing else should be scheduled assert_eq!(Agenda::<Test>::iter().count(), 0); assert_eq!(Retries::<Test>::iter().count(), 0); @@ -1415,7 +1415,7 @@ fn scheduler_does_not_delete_permanently_overweight_call() { Preimage::bound(call).unwrap(), )); // Never executes. - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert_eq!(logger::log(), vec![]); // Assert the `PermanentlyOverweight` event. @@ -1445,7 +1445,7 @@ fn scheduler_handles_periodic_failure() { bound.clone(), )); // Executes 5 times till block 20. - run_to_block(20); + System::run_to_block::<AllPalletsWithSystem>(20); assert_eq!(logger::log().len(), 5); // Block 28 will already be full. @@ -1460,7 +1460,7 @@ fn scheduler_handles_periodic_failure() { } // Going to block 24 will emit a `PeriodicFailed` event. - run_to_block(24); + System::run_to_block::<AllPalletsWithSystem>(24); assert_eq!(logger::log().len(), 6); assert_eq!( @@ -1498,7 +1498,7 @@ fn scheduler_handles_periodic_unavailable_preimage() { assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(1), call.encode())); // Executes 1 times till block 4. - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_eq!(logger::log().len(), 1); // As the public api doesn't support to remove a noted preimage, we need to first unnote it @@ -1508,7 +1508,7 @@ fn scheduler_handles_periodic_unavailable_preimage() { Preimage::request(&hash); // Does not ever execute again. - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert_eq!(logger::log().len(), 1); // The preimage is not requested anymore. @@ -1536,7 +1536,7 @@ fn scheduler_respects_priority_ordering() { root(), Preimage::bound(call).unwrap(), )); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_eq!(logger::log(), vec![(root(), 69u32), (root(), 42u32)]); }); } @@ -1571,10 +1571,10 @@ fn scheduler_respects_priority_ordering_with_soft_deadlines() { )); // 2600 does not fit with 69 or 42, but has higher priority, so will go through - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_eq!(logger::log(), vec![(root(), 2600u32)]); // 69 and 42 fit together - run_to_block(5); + System::run_to_block::<AllPalletsWithSystem>(5); assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]); }); } @@ -1701,14 +1701,14 @@ fn root_calls_works() { Scheduler::schedule_named(RuntimeOrigin::root(), [1u8; 32], 4, None, 127, call,) ); assert_ok!(Scheduler::schedule(RuntimeOrigin::root(), 4, None, 127, call2)); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); // Scheduled calls are in the agenda. assert_eq!(Agenda::<Test>::get(4).len(), 2); assert!(logger::log().is_empty()); assert_ok!(Scheduler::cancel_named(RuntimeOrigin::root(), [1u8; 32])); assert_ok!(Scheduler::cancel(RuntimeOrigin::root(), 4, 1)); // Scheduled calls are made NONE, so should not effect state - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert!(logger::log().is_empty()); }); } @@ -1716,7 +1716,7 @@ fn root_calls_works() { #[test] fn fails_to_schedule_task_in_the_past() { new_test_ext().execute_with(|| { - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); let call1 = Box::new(RuntimeCall::Logger(LoggerCall::log { i: 69, @@ -1768,14 +1768,14 @@ fn should_use_origin() { call, )); assert_ok!(Scheduler::schedule(system::RawOrigin::Signed(1).into(), 4, None, 127, call2,)); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); // Scheduled calls are in the agenda. assert_eq!(Agenda::<Test>::get(4).len(), 2); assert!(logger::log().is_empty()); assert_ok!(Scheduler::cancel_named(system::RawOrigin::Signed(1).into(), [1u8; 32])); assert_ok!(Scheduler::cancel(system::RawOrigin::Signed(1).into(), 4, 1)); // Scheduled calls are made NONE, so should not effect state - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert!(logger::log().is_empty()); }); } @@ -1829,7 +1829,7 @@ fn should_check_origin_for_cancel() { call, )); assert_ok!(Scheduler::schedule(system::RawOrigin::Signed(1).into(), 4, None, 127, call2,)); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); // Scheduled calls are in the agenda. assert_eq!(Agenda::<Test>::get(4).len(), 2); assert!(logger::log().is_empty()); @@ -1840,7 +1840,7 @@ fn should_check_origin_for_cancel() { assert_noop!(Scheduler::cancel(system::RawOrigin::Signed(2).into(), 4, 1), BadOrigin); assert_noop!(Scheduler::cancel_named(system::RawOrigin::Root.into(), [1u8; 32]), BadOrigin); assert_noop!(Scheduler::cancel(system::RawOrigin::Root.into(), 4, 1), BadOrigin); - run_to_block(5); + System::run_to_block::<AllPalletsWithSystem>(5); assert_eq!( logger::log(), vec![ @@ -1888,17 +1888,17 @@ fn cancel_removes_retry_entry() { // task 42 will be retried 10 times every 3 blocks assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 1)); assert_eq!(Retries::<Test>::iter().count(), 2); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert!(logger::log().is_empty()); assert_eq!(Agenda::<Test>::get(4).len(), 2); // both tasks fail - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert!(Agenda::<Test>::get(4).is_empty()); // 42 and 20 are rescheduled for next block assert_eq!(Agenda::<Test>::get(5).len(), 2); assert!(logger::log().is_empty()); // 42 and 20 still fail - run_to_block(5); + System::run_to_block::<AllPalletsWithSystem>(5); // 42 and 20 rescheduled for next block assert_eq!(Agenda::<Test>::get(6).len(), 2); assert_eq!(Retries::<Test>::iter().count(), 2); @@ -1909,7 +1909,7 @@ fn cancel_removes_retry_entry() { assert!(Scheduler::cancel(root().into(), 6, 0).is_ok()); // 20 is removed, 42 still fails - run_to_block(6); + System::run_to_block::<AllPalletsWithSystem>(6); // 42 rescheduled for next block assert_eq!(Agenda::<Test>::get(7).len(), 1); // 20's retry entry is removed @@ -1920,7 +1920,7 @@ fn cancel_removes_retry_entry() { assert!(Scheduler::cancel(root().into(), 7, 0).is_ok()); // both tasks are canceled, everything is removed now - run_to_block(7); + System::run_to_block::<AllPalletsWithSystem>(7); assert!(Agenda::<Test>::get(8).is_empty()); assert_eq!(Retries::<Test>::iter().count(), 0); }); @@ -1963,7 +1963,7 @@ fn cancel_retries_works() { // task 42 will be retried 10 times every 3 blocks assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 1)); assert_eq!(Retries::<Test>::iter().count(), 2); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); assert!(logger::log().is_empty()); assert_eq!(Agenda::<Test>::get(4).len(), 2); // cancel the retry config for 20 @@ -1972,7 +1972,7 @@ fn cancel_retries_works() { // cancel the retry config for 42 assert_ok!(Scheduler::cancel_retry_named(root().into(), [1u8; 32])); assert_eq!(Retries::<Test>::iter().count(), 0); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); // both tasks failed and there are no more retries, so they are evicted assert_eq!(Agenda::<Test>::get(4).len(), 0); assert_eq!(Retries::<Test>::iter().count(), 0); @@ -2287,7 +2287,7 @@ fn postponed_named_task_cannot_be_rescheduled() { assert!(Lookup::<Test>::contains_key(name)); // Run to a very large block. - run_to_block(10); + System::run_to_block::<AllPalletsWithSystem>(10); // It was not executed. assert!(logger::log().is_empty()); @@ -2321,7 +2321,7 @@ fn postponed_named_task_cannot_be_rescheduled() { // Finally add the preimage. assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(0), call.encode())); - run_to_block(1000); + System::run_to_block::<AllPalletsWithSystem>(1000); // It did not execute. assert!(logger::log().is_empty()); assert!(!Preimage::is_requested(&hash)); @@ -2357,14 +2357,14 @@ fn scheduler_v3_anon_basic_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); // Did not execute till block 3. assert!(logger::log().is_empty()); // Executes in block 4. - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_eq!(logger::log(), vec![(root(), 42u32)]); // ... but not again. - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -2389,7 +2389,7 @@ fn scheduler_v3_anon_cancel_works() { // Cancel the call. assert_ok!(<Scheduler as Anon<_, _, _>>::cancel(address)); // It did not get executed. - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert!(logger::log().is_empty()); // Cannot cancel again. assert_err!(<Scheduler as Anon<_, _, _>>::cancel(address), DispatchError::Unavailable); @@ -2413,7 +2413,7 @@ fn scheduler_v3_anon_reschedule_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); // Did not execute till block 3. assert!(logger::log().is_empty()); @@ -2430,9 +2430,9 @@ fn scheduler_v3_anon_reschedule_works() { // Re-schedule to block 5. assert_ok!(<Scheduler as Anon<_, _, _>>::reschedule(address, DispatchTime::At(5))); // Scheduled for block 5. - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert!(logger::log().is_empty()); - run_to_block(5); + System::run_to_block::<AllPalletsWithSystem>(5); // Does execute in block 5. assert_eq!(logger::log(), vec![(root(), 42)]); // Cannot re-schedule executed task. @@ -2461,14 +2461,14 @@ fn scheduler_v3_anon_next_schedule_time_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); // Did not execute till block 3. assert!(logger::log().is_empty()); // Scheduled for block 4. assert_eq!(<Scheduler as Anon<_, _, _>>::next_dispatch_time(address), Ok(4)); // Block 4 executes it. - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_eq!(logger::log(), vec![(root(), 42)]); // It has no dispatch time anymore. @@ -2498,7 +2498,7 @@ fn scheduler_v3_anon_reschedule_and_next_schedule_time_work() { ) .unwrap(); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); // Did not execute till block 3. assert!(logger::log().is_empty()); @@ -2512,10 +2512,10 @@ fn scheduler_v3_anon_reschedule_and_next_schedule_time_work() { assert_eq!(<Scheduler as Anon<_, _, _>>::next_dispatch_time(address), Ok(5)); // Block 4 does nothing. - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert!(logger::log().is_empty()); // Block 5 executes it. - run_to_block(5); + System::run_to_block::<AllPalletsWithSystem>(5); assert_eq!(logger::log(), vec![(root(), 42)]); }); } @@ -2548,7 +2548,7 @@ fn scheduler_v3_anon_schedule_agenda_overflows() { DispatchError::Exhausted ); - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); // All scheduled calls are executed. assert_eq!(logger::log().len() as u32, max); }); @@ -2597,7 +2597,7 @@ fn scheduler_v3_anon_cancel_and_schedule_fills_holes() { assert_eq!(i, index); } - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); // Maximum number of calls are executed. assert_eq!(logger::log().len() as u32, max); }); @@ -2643,7 +2643,7 @@ fn scheduler_v3_anon_reschedule_fills_holes() { assert_eq!(new, want); } - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); // Maximum number of calls are executed. assert_eq!(logger::log().len() as u32, max); }); @@ -2670,14 +2670,14 @@ fn scheduler_v3_named_basic_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); // Did not execute till block 3. assert!(logger::log().is_empty()); // Executes in block 4. - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_eq!(logger::log(), vec![(root(), 42u32)]); // ... but not again. - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -2705,7 +2705,7 @@ fn scheduler_v3_named_cancel_named_works() { // Cancel the call by name. assert_ok!(<Scheduler as Named<_, _, _>>::cancel_named(name)); // It did not get executed. - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert!(logger::log().is_empty()); // Cannot cancel again. assert_noop!(<Scheduler as Named<_, _, _>>::cancel_named(name), DispatchError::Unavailable); @@ -2735,7 +2735,7 @@ fn scheduler_v3_named_cancel_without_name_works() { // Cancel the call by address. assert_ok!(<Scheduler as Anon<_, _, _>>::cancel(address)); // It did not get executed. - run_to_block(100); + System::run_to_block::<AllPalletsWithSystem>(100); assert!(logger::log().is_empty()); // Cannot cancel again. assert_err!(<Scheduler as Anon<_, _, _>>::cancel(address), DispatchError::Unavailable); @@ -2762,7 +2762,7 @@ fn scheduler_v3_named_reschedule_named_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); // Did not execute till block 3. assert!(logger::log().is_empty()); @@ -2784,9 +2784,9 @@ fn scheduler_v3_named_reschedule_named_works() { // Re-schedule to block 5. assert_ok!(<Scheduler as Named<_, _, _>>::reschedule_named(name, DispatchTime::At(5))); // Scheduled for block 5. - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert!(logger::log().is_empty()); - run_to_block(5); + System::run_to_block::<AllPalletsWithSystem>(5); // Does execute in block 5. assert_eq!(logger::log(), vec![(root(), 42)]); // Cannot re-schedule executed task. @@ -2822,7 +2822,7 @@ fn scheduler_v3_named_next_schedule_time_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::<AllPalletsWithSystem>(3); // Did not execute till block 3. assert!(logger::log().is_empty()); @@ -2831,7 +2831,7 @@ fn scheduler_v3_named_next_schedule_time_works() { // Also works by address. assert_eq!(<Scheduler as Anon<_, _, _>>::next_dispatch_time(address), Ok(4)); // Block 4 executes it. - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_eq!(logger::log(), vec![(root(), 42)]); // It has no dispatch time anymore. @@ -3025,7 +3025,7 @@ fn unavailable_call_is_detected() { assert!(Preimage::is_requested(&hash)); // Executes in block 4. - run_to_block(4); + System::run_to_block::<AllPalletsWithSystem>(4); assert_eq!( System::events().last().unwrap().event, diff --git a/substrate/frame/scored-pool/Cargo.toml b/substrate/frame/scored-pool/Cargo.toml index d945ef42a47b0ba9419782881d0f9de2c372120d..227868fa2a4f52344dcd059df00283cff4776c93 100644 --- a/substrate/frame/scored-pool/Cargo.toml +++ b/substrate/frame/scored-pool/Cargo.toml @@ -17,9 +17,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/scored-pool/src/mock.rs b/substrate/frame/scored-pool/src/mock.rs index 7708c06e56bd8056b0b2ff89f5b383d3b4c180bf..5eb9df5289240a488123c234cc864b4f2926c894 100644 --- a/substrate/frame/scored-pool/src/mock.rs +++ b/substrate/frame/scored-pool/src/mock.rs @@ -109,7 +109,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { balances.push((40, 500_000)); balances.push((99, 1)); - pallet_balances::GenesisConfig::<Test> { balances } + pallet_balances::GenesisConfig::<Test> { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); pallet_scored_pool::GenesisConfig::<Test> { diff --git a/substrate/frame/session/Cargo.toml b/substrate/frame/session/Cargo.toml index b82112681e679ad0fc0ddbcf32f8f8944ca4c766..737678bea8a3b53b01321e544d14a8b7aaa261bc 100644 --- a/substrate/frame/session/Cargo.toml +++ b/substrate/frame/session/Cargo.toml @@ -17,19 +17,19 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -impl-trait-for-tuples = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +impl-trait-for-tuples = { workspace = true } +log = { workspace = true } pallet-timestamp = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } sp-core = { features = ["serde"], workspace = true } sp-io = { workspace = true } sp-runtime = { features = ["serde"], workspace = true } sp-session = { workspace = true } sp-staking = { features = ["serde"], workspace = true } -sp-trie = { optional = true, workspace = true } sp-state-machine = { workspace = true } +sp-trie = { optional = true, workspace = true } [features] default = ["historical", "std"] diff --git a/substrate/frame/session/benchmarking/Cargo.toml b/substrate/frame/session/benchmarking/Cargo.toml index 264bc10a33f6be356d7b745dd48b287f331b94f4..72e4b3deabfd0f7cec95b9aaf44242ee39e13643 100644 --- a/substrate/frame/session/benchmarking/Cargo.toml +++ b/substrate/frame/session/benchmarking/Cargo.toml @@ -17,22 +17,22 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -rand = { features = ["std_rng"], workspace = true } frame-benchmarking = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-session = { workspace = true } pallet-staking = { workspace = true } +rand = { features = ["std_rng"], workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } [dev-dependencies] codec = { features = ["derive"], workspace = true, default-features = true } -scale-info = { workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-staking-reward-curve = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } +scale-info = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } diff --git a/substrate/frame/session/benchmarking/src/inner.rs b/substrate/frame/session/benchmarking/src/inner.rs index 9ba47b34ed7a3d57f065884f43be4707340c4b35..9789b6bb593d0fa9d83b0d780154d8668700a717 100644 --- a/substrate/frame/session/benchmarking/src/inner.rs +++ b/substrate/frame/session/benchmarking/src/inner.rs @@ -22,7 +22,7 @@ use alloc::{vec, vec::Vec}; use sp_runtime::traits::{One, StaticLookup, TrailingZeroInput}; use codec::Decode; -use frame_benchmarking::v1::benchmarks; +use frame_benchmarking::v2::*; use frame_support::traits::{Get, KeyOwnerProofSystem, OnInitialize}; use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use pallet_session::{historical::Pallet as Historical, Pallet as Session, *}; @@ -45,8 +45,12 @@ impl<T: Config> OnInitialize<BlockNumberFor<T>> for Pallet<T> { } } -benchmarks! { - set_keys { +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn set_keys() -> Result<(), BenchmarkError> { let n = MaxNominationsOf::<T>::get(); let (v_stash, _) = create_validator_with_nominators::<T>( n, @@ -58,13 +62,19 @@ benchmarks! { let v_controller = pallet_staking::Pallet::<T>::bonded(&v_stash).ok_or("not stash")?; let keys = T::Keys::decode(&mut TrailingZeroInput::zeroes()).unwrap(); - let proof: Vec<u8> = vec![0,1,2,3]; + let proof: Vec<u8> = vec![0, 1, 2, 3]; // Whitelist controller account from further DB operations. let v_controller_key = frame_system::Account::<T>::hashed_key_for(&v_controller); frame_benchmarking::benchmarking::add_to_whitelist(v_controller_key.into()); - }: _(RawOrigin::Signed(v_controller), keys, proof) - purge_keys { + #[extrinsic_call] + _(RawOrigin::Signed(v_controller), keys, proof); + + Ok(()) + } + + #[benchmark] + fn purge_keys() -> Result<(), BenchmarkError> { let n = MaxNominationsOf::<T>::get(); let (v_stash, _) = create_validator_with_nominators::<T>( n, @@ -75,30 +85,33 @@ benchmarks! { )?; let v_controller = pallet_staking::Pallet::<T>::bonded(&v_stash).ok_or("not stash")?; let keys = T::Keys::decode(&mut TrailingZeroInput::zeroes()).unwrap(); - let proof: Vec<u8> = vec![0,1,2,3]; + let proof: Vec<u8> = vec![0, 1, 2, 3]; Session::<T>::set_keys(RawOrigin::Signed(v_controller.clone()).into(), keys, proof)?; // Whitelist controller account from further DB operations. let v_controller_key = frame_system::Account::<T>::hashed_key_for(&v_controller); frame_benchmarking::benchmarking::add_to_whitelist(v_controller_key.into()); - }: _(RawOrigin::Signed(v_controller)) - #[extra] - check_membership_proof_current_session { - let n in 2 .. MAX_VALIDATORS as u32; + #[extrinsic_call] + _(RawOrigin::Signed(v_controller)); + Ok(()) + } + + #[benchmark(extra)] + fn check_membership_proof_current_session(n: Linear<2, MAX_VALIDATORS>) { let (key, key_owner_proof1) = check_membership_proof_setup::<T>(n); let key_owner_proof2 = key_owner_proof1.clone(); - }: { - Historical::<T>::check_proof(key, key_owner_proof1); - } - verify { + + #[block] + { + Historical::<T>::check_proof(key, key_owner_proof1); + } + assert!(Historical::<T>::check_proof(key, key_owner_proof2).is_some()); } - #[extra] - check_membership_proof_historical_session { - let n in 2 .. MAX_VALIDATORS as u32; - + #[benchmark(extra)] + fn check_membership_proof_historical_session(n: Linear<2, MAX_VALIDATORS>) { let (key, key_owner_proof1) = check_membership_proof_setup::<T>(n); // skip to the next session so that the session is historical @@ -106,14 +119,21 @@ benchmarks! { Session::<T>::rotate_session(); let key_owner_proof2 = key_owner_proof1.clone(); - }: { - Historical::<T>::check_proof(key, key_owner_proof1); - } - verify { + + #[block] + { + Historical::<T>::check_proof(key, key_owner_proof1); + } + assert!(Historical::<T>::check_proof(key, key_owner_proof2).is_some()); } - impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test, extra = false); + impl_benchmark_test_suite!( + Pallet, + crate::mock::new_test_ext(), + crate::mock::Test, + extra = false + ); } /// Sets up the benchmark for checking a membership proof. It creates the given diff --git a/substrate/frame/session/benchmarking/src/mock.rs b/substrate/frame/session/benchmarking/src/mock.rs index 2aec58cceded2d186d9ead2ca2c4768e7cce1b90..74201da3d2f310f58593d8566eb650086245c76f 100644 --- a/substrate/frame/session/benchmarking/src/mock.rs +++ b/substrate/frame/session/benchmarking/src/mock.rs @@ -27,7 +27,7 @@ use frame_support::{ derive_impl, parameter_types, traits::{ConstU32, ConstU64}, }; -use sp_runtime::{traits::IdentityLookup, BuildStorage}; +use sp_runtime::{traits::IdentityLookup, BuildStorage, KeyTypeId}; type AccountId = u64; type Nonce = u32; @@ -42,6 +42,7 @@ frame_support::construct_runtime!( Balances: pallet_balances, Staking: pallet_staking, Session: pallet_session, + Historical: pallet_session::historical } ); @@ -79,7 +80,8 @@ sp_runtime::impl_opaque_keys! { pub struct TestSessionHandler; impl pallet_session::SessionHandler<AccountId> for TestSessionHandler { - const KEY_TYPE_IDS: &'static [sp_runtime::KeyTypeId] = &[]; + // corresponds to the opaque key id above + const KEY_TYPE_IDS: &'static [KeyTypeId] = &[KeyTypeId([100u8, 117u8, 109u8, 121u8])]; fn on_genesis_session<Ks: sp_runtime::traits::OpaqueKeys>(_validators: &[(AccountId, Ks)]) {} @@ -131,6 +133,7 @@ impl onchain::Config for OnChainSeqPhragmen { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Test { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = <Self as pallet_balances::Config>::Balance; type UnixTime = pallet_timestamp::Pallet<Self>; diff --git a/substrate/frame/society/Cargo.toml b/substrate/frame/society/Cargo.toml index 555dee68ba01ee8eea4fcc789c1660b7c61078e2..d5860518fdda84c7ea6bd602ddead60f80bb20e9 100644 --- a/substrate/frame/society/Cargo.toml +++ b/substrate/frame/society/Cargo.toml @@ -16,17 +16,17 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { features = ["derive"], workspace = true } log = { workspace = true } rand_chacha = { workspace = true } scale-info = { features = ["derive"], workspace = true } -codec = { features = ["derive"], workspace = true } -sp-io = { workspace = true } -sp-arithmetic = { workspace = true } -sp-runtime = { workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +sp-arithmetic = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } [dev-dependencies] frame-support-test = { workspace = true } diff --git a/substrate/frame/society/src/mock.rs b/substrate/frame/society/src/mock.rs index 3c27c08a10610f44ebaf2fc72678f52be2f06507..63fc5059279b15625447a3d38b643c897e63df5d 100644 --- a/substrate/frame/society/src/mock.rs +++ b/substrate/frame/society/src/mock.rs @@ -115,7 +115,7 @@ impl EnvBuilder { pub fn execute<R, F: FnOnce() -> R>(mut self, f: F) -> R { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); self.balances.push((Society::account_id(), self.balance.max(self.pot))); - pallet_balances::GenesisConfig::<Test> { balances: self.balances } + pallet_balances::GenesisConfig::<Test> { balances: self.balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); pallet_society::GenesisConfig::<Test> { pot: self.pot } @@ -138,18 +138,6 @@ impl EnvBuilder { } } -/// Run until a particular block. -pub fn run_to_block(n: u64) { - while System::block_number() < n { - if System::block_number() > 1 { - System::on_finalize(System::block_number()); - } - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Society::on_initialize(System::block_number()); - } -} - /// Creates a bid struct using input parameters. pub fn bid<AccountId, Balance>( who: AccountId, @@ -173,12 +161,12 @@ pub fn candidacy<AccountId, Balance>( pub fn next_challenge() { let challenge_period: u64 = <Test as Config>::ChallengePeriod::get(); let now = System::block_number(); - run_to_block(now + challenge_period - now % challenge_period); + System::run_to_block::<AllPalletsWithSystem>(now + challenge_period - now % challenge_period); } pub fn next_voting() { if let Period::Voting { more, .. } = Society::period() { - run_to_block(System::block_number() + more); + System::run_to_block::<AllPalletsWithSystem>(System::block_number() + more); } } @@ -235,8 +223,11 @@ pub fn conclude_intake(allow_resignation: bool, judge_intake: Option<bool>) { pub fn next_intake() { let claim_period: u64 = <Test as Config>::ClaimPeriod::get(); match Society::period() { - Period::Voting { more, .. } => run_to_block(System::block_number() + more + claim_period), - Period::Claim { more, .. } => run_to_block(System::block_number() + more), + Period::Voting { more, .. } => System::run_to_block::<AllPalletsWithSystem>( + System::block_number() + more + claim_period, + ), + Period::Claim { more, .. } => + System::run_to_block::<AllPalletsWithSystem>(System::block_number() + more), } } diff --git a/substrate/frame/society/src/tests.rs b/substrate/frame/society/src/tests.rs index 2a13f99855b59f66764ffc2fd93fe3479df47c46..22832f18b6fe0305e3a4fa025776ce9d36730b22 100644 --- a/substrate/frame/society/src/tests.rs +++ b/substrate/frame/society/src/tests.rs @@ -272,7 +272,7 @@ fn bidding_works() { // 40, now a member, can vote for 50 assert_ok!(Society::vote(Origin::signed(40), 50, true)); conclude_intake(true, None); - run_to_block(12); + System::run_to_block::<AllPalletsWithSystem>(12); // 50 is now a member assert_eq!(members(), vec![10, 30, 40, 50]); // Pot is increased by 1000, and 500 is paid out. Total payout so far is 1200. @@ -282,7 +282,7 @@ fn bidding_works() { assert_eq!(candidacies(), vec![]); assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around // Next period - run_to_block(16); + System::run_to_block::<AllPalletsWithSystem>(16); // Same members assert_eq!(members(), vec![10, 30, 40, 50]); // Pot is increased by 1000 again @@ -294,7 +294,7 @@ fn bidding_works() { // Candidate 60 is voted in. assert_ok!(Society::vote(Origin::signed(50), 60, true)); conclude_intake(true, None); - run_to_block(20); + System::run_to_block::<AllPalletsWithSystem>(20); // 60 joins as a member assert_eq!(members(), vec![10, 30, 40, 50, 60]); // Pay them @@ -368,7 +368,7 @@ fn rejecting_skeptic_on_approved_is_punished() { } conclude_intake(true, None); assert_eq!(Members::<Test>::get(10).unwrap().strikes, 0); - run_to_block(12); + System::run_to_block::<AllPalletsWithSystem>(12); assert_eq!(members(), vec![10, 20, 30, 40]); assert_eq!(Members::<Test>::get(skeptic).unwrap().strikes, 1); }); @@ -418,7 +418,7 @@ fn slash_payout_works() { Payouts::<Test>::get(20), PayoutRecord { paid: 0, payouts: vec![(8, 500)].try_into().unwrap() } ); - run_to_block(8); + System::run_to_block::<AllPalletsWithSystem>(8); // payout should be here, but 500 less assert_ok!(Society::payout(RuntimeOrigin::signed(20))); assert_eq!(Balances::free_balance(20), 550); @@ -1315,7 +1315,7 @@ fn drop_candidate_works() { assert_ok!(Society::vote(Origin::signed(10), 40, false)); assert_ok!(Society::vote(Origin::signed(20), 40, false)); assert_ok!(Society::vote(Origin::signed(30), 40, false)); - run_to_block(12); + System::run_to_block::<AllPalletsWithSystem>(12); assert_ok!(Society::drop_candidate(Origin::signed(50), 40)); // 40 candidacy has gone. assert_eq!(candidates(), vec![]); diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index 03d815e349df896bbbc6fbbda4ee87dbf2b573f1..1c4b2ed5b821d56a4e320cbc7ae272561c6ee904 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -106,7 +106,7 @@ //! [dependencies] //! codec = { features = ["max-encoded-len"], workspace = true } //! scale-info = { features = ["derive"], workspace = true } -//! frame = { workspace = true, features = ["experimental", "runtime"] } +//! frame = { workspace = true, features = ["runtime"] } //! //! [features] //! default = ["std"] @@ -150,7 +150,6 @@ //! * `runtime::apis` should expose all common runtime APIs that all FRAME-based runtimes need. #![cfg_attr(not(feature = "std"), no_std)] -#![cfg(feature = "experimental")] #[doc(no_inline)] pub use frame_support::pallet; @@ -203,12 +202,20 @@ pub mod prelude { /// Dispatch types from `frame-support`, other fundamental traits #[doc(no_inline)] pub use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; - pub use frame_support::traits::{Contains, IsSubType, OnRuntimeUpgrade}; + pub use frame_support::traits::{ + Contains, EitherOf, EstimateNextSessionRotation, Everything, IsSubType, MapSuccess, + NoOpPoll, OnRuntimeUpgrade, OneSessionHandler, RankedMembers, RankedMembersSwapHandler, + VariantCount, VariantCountOf, + }; /// Pallet prelude of `frame-system`. #[doc(no_inline)] pub use frame_system::pallet_prelude::*; + /// Transaction related helpers to submit transactions. + #[doc(no_inline)] + pub use frame_system::offchain::*; + /// All FRAME-relevant derive macros. #[doc(no_inline)] pub use super::derive::*; @@ -216,16 +223,27 @@ pub mod prelude { /// All hashing related things pub use super::hashing::*; + /// All account related things. + pub use super::account::*; + + /// All arithmetic types and traits used for safe math. + pub use super::arithmetic::*; + /// Runtime traits #[doc(no_inline)] pub use sp_runtime::traits::{ - BlockNumberProvider, Bounded, DispatchInfoOf, Dispatchable, SaturatedConversion, - Saturating, StaticLookup, TrailingZeroInput, + BlockNumberProvider, Bounded, Convert, DispatchInfoOf, Dispatchable, ReduceBy, + ReplaceWithDefault, SaturatedConversion, Saturating, StaticLookup, TrailingZeroInput, }; + /// Bounded storage related types. + pub use sp_runtime::{BoundedSlice, BoundedVec}; + /// Other error/result types for runtime #[doc(no_inline)] - pub use sp_runtime::{DispatchErrorWithPostInfo, DispatchResultWithInfo, TokenError}; + pub use sp_runtime::{ + BoundToRuntimeAppPublic, DispatchErrorWithPostInfo, DispatchResultWithInfo, TokenError, + }; } #[cfg(any(feature = "try-runtime", test))] @@ -251,7 +269,7 @@ pub mod benchmarking { pub use frame_benchmarking::benchmarking::*; // The system origin, which is very often needed in benchmarking code. Might be tricky only // if the pallet defines its own `#[pallet::origin]` and call it `RawOrigin`. - pub use frame_system::RawOrigin; + pub use frame_system::{Pallet as System, RawOrigin}; } #[deprecated( @@ -308,15 +326,18 @@ pub mod testing_prelude { /// Other helper macros from `frame_support` that help with asserting in tests. pub use frame_support::{ assert_err, assert_err_ignore_postinfo, assert_error_encoded_size, assert_noop, assert_ok, - assert_storage_noop, storage_alias, + assert_storage_noop, ensure, hypothetically, storage_alias, }; - pub use frame_system::{self, mocking::*}; + pub use frame_system::{self, mocking::*, RunToBlockHooks}; - #[deprecated(note = "Use `frame::testing_prelude::TestExternalities` instead.")] + #[deprecated(note = "Use `frame::testing_prelude::TestState` instead.")] pub use sp_io::TestExternalities; pub use sp_io::TestExternalities as TestState; + + /// Commonly used runtime traits for testing. + pub use sp_runtime::{traits::BadOrigin, StateVersion}; } /// All of the types and tools needed to build FRAME-based runtimes. @@ -391,7 +412,7 @@ pub mod runtime { LOCAL_TESTNET_RUNTIME_PRESET, }; pub use sp_inherents::{CheckInherentsResult, InherentData}; - pub use sp_keyring::AccountKeyring; + pub use sp_keyring::Sr25519Keyring; pub use sp_runtime::{ApplyExtrinsicResult, ExtrinsicInclusionMode}; } @@ -484,6 +505,7 @@ pub mod runtime { frame_system::CheckEra<T>, frame_system::CheckNonce<T>, frame_system::CheckWeight<T>, + frame_system::WeightReclaim<T>, ); } @@ -493,7 +515,7 @@ pub mod runtime { #[cfg(feature = "std")] pub mod testing_prelude { pub use sp_core::storage::Storage; - pub use sp_runtime::BuildStorage; + pub use sp_runtime::{BuildStorage, DispatchError}; } } @@ -509,6 +531,8 @@ pub mod traits { } /// The arithmetic types used for safe math. +/// +/// This is already part of the [`prelude`]. pub mod arithmetic { pub use sp_arithmetic::{traits::*, *}; } @@ -532,6 +556,16 @@ pub mod hashing { pub use sp_runtime::traits::{BlakeTwo256, Hash, Keccak256}; } +/// All account management related traits. +/// +/// This is already part of the [`prelude`]. +pub mod account { + pub use frame_support::traits::{ + AsEnsureOriginWithArg, ChangeMembers, EitherOfDiverse, InitializeMembers, + }; + pub use sp_runtime::traits::{IdentifyAccount, IdentityLookup}; +} + /// Access to all of the dependencies of this crate. In case the prelude re-exports are not enough, /// this module can be used. /// diff --git a/substrate/frame/staking/Cargo.toml b/substrate/frame/staking/Cargo.toml index a6a0ccd3b0a730e3ef267da349d7912c04c717c0..74b1c78e9cbee9661978ee2111ec9d3410a7f56c 100644 --- a/substrate/frame/staking/Cargo.toml +++ b/substrate/frame/staking/Cargo.toml @@ -16,40 +16,41 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { features = ["alloc", "derive"], workspace = true } codec = { features = [ "derive", ], workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } -sp-io = { workspace = true } -sp-runtime = { features = ["serde"], workspace = true } -sp-staking = { features = ["serde"], workspace = true } +frame-election-provider-support = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +pallet-authorship = { workspace = true } pallet-session = { features = [ "historical", ], workspace = true } -pallet-authorship = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } +serde = { features = ["alloc", "derive"], workspace = true } sp-application-crypto = { features = ["serde"], workspace = true } -frame-election-provider-support = { workspace = true } -log = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { features = ["serde"], workspace = true } +sp-staking = { features = ["serde"], workspace = true } # Optional imports for benchmarking frame-benchmarking = { optional = true, workspace = true } rand_chacha = { optional = true, workspace = true } [dev-dependencies] +frame-benchmarking = { workspace = true, default-features = true } +frame-election-provider-support = { workspace = true, default-features = true } +frame-support = { features = ["experimental"], workspace = true, default-features = true } +pallet-bags-list = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } +pallet-staking-reward-curve = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } +rand_chacha = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-npos-elections = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } -pallet-staking-reward-curve = { workspace = true, default-features = true } -pallet-bags-list = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } substrate-test-utils = { workspace = true } -frame-benchmarking = { workspace = true, default-features = true } -frame-election-provider-support = { workspace = true, default-features = true } -rand_chacha = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/staking/src/asset.rs b/substrate/frame/staking/src/asset.rs index 23368b1f8fca713f78405165c00ec09d54a8d257..a1140d317c204957e5f7cc9178f704cffe5965c1 100644 --- a/substrate/frame/staking/src/asset.rs +++ b/substrate/frame/staking/src/asset.rs @@ -18,9 +18,15 @@ //! Contains all the interactions with [`Config::Currency`] to manipulate the underlying staking //! asset. -use frame_support::traits::{Currency, InspectLockableCurrency, LockableCurrency}; - -use crate::{BalanceOf, Config, NegativeImbalanceOf, PositiveImbalanceOf}; +use crate::{BalanceOf, Config, HoldReason, NegativeImbalanceOf, PositiveImbalanceOf}; +use frame_support::traits::{ + fungible::{ + hold::{Balanced as FunHoldBalanced, Inspect as FunHoldInspect, Mutate as FunHoldMutate}, + Balanced, Inspect as FunInspect, + }, + tokens::{Fortitude, Precision, Preservation}, +}; +use sp_runtime::{DispatchResult, Saturating}; /// Existential deposit for the chain. pub fn existential_deposit<T: Config>() -> BalanceOf<T> { @@ -32,7 +38,7 @@ pub fn total_issuance<T: Config>() -> BalanceOf<T> { T::Currency::total_issuance() } -/// Total balance of `who`. Includes both, free and reserved. +/// Total balance of `who`. Includes both free and staked. pub fn total_balance<T: Config>(who: &T::AccountId) -> BalanceOf<T> { T::Currency::total_balance(who) } @@ -41,42 +47,65 @@ pub fn total_balance<T: Config>(who: &T::AccountId) -> BalanceOf<T> { /// /// This includes balance free to stake along with any balance that is already staked. pub fn stakeable_balance<T: Config>(who: &T::AccountId) -> BalanceOf<T> { - T::Currency::free_balance(who) + free_to_stake::<T>(who).saturating_add(staked::<T>(who)) } /// Balance of `who` that is currently at stake. /// -/// The staked amount is locked and cannot be transferred out of `who`s account. +/// The staked amount is on hold and cannot be transferred out of `who`s account. pub fn staked<T: Config>(who: &T::AccountId) -> BalanceOf<T> { - T::Currency::balance_locked(crate::STAKING_ID, who) + T::Currency::balance_on_hold(&HoldReason::Staking.into(), who) +} + +/// Balance of who that can be staked additionally. +/// +/// Does not include the current stake. +pub fn free_to_stake<T: Config>(who: &T::AccountId) -> BalanceOf<T> { + // since we want to be able to use frozen funds for staking, we force the reduction. + T::Currency::reducible_balance(who, Preservation::Preserve, Fortitude::Force) } /// Set balance that can be staked for `who`. /// -/// This includes any balance that is already staked. +/// If `Value` is lower than the current staked balance, the difference is unlocked. +/// +/// Should only be used with test. #[cfg(any(test, feature = "runtime-benchmarks"))] pub fn set_stakeable_balance<T: Config>(who: &T::AccountId, value: BalanceOf<T>) { - T::Currency::make_free_balance_be(who, value); + use frame_support::traits::fungible::Mutate; + + // minimum free balance (non-staked) required to keep the account alive. + let ed = existential_deposit::<T>(); + // currently on stake + let staked_balance = staked::<T>(who); + + // if new value is greater than staked balance, mint some free balance. + if value > staked_balance { + let _ = T::Currency::set_balance(who, value - staked_balance + ed); + } else { + // else reduce the staked balance. + update_stake::<T>(who, value).expect("can remove from what is staked"); + // burn all free, only leaving ED. + let _ = T::Currency::set_balance(who, ed); + } + + // ensure new stakeable balance same as desired `value`. + assert_eq!(stakeable_balance::<T>(who), value); } /// Update `amount` at stake for `who`. /// /// Overwrites the existing stake amount. If passed amount is lower than the existing stake, the /// difference is unlocked. -pub fn update_stake<T: Config>(who: &T::AccountId, amount: BalanceOf<T>) { - T::Currency::set_lock( - crate::STAKING_ID, - who, - amount, - frame_support::traits::WithdrawReasons::all(), - ); +pub fn update_stake<T: Config>(who: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult { + T::Currency::set_on_hold(&HoldReason::Staking.into(), who, amount) } -/// Kill the stake of `who`. +/// Release all staked amount to `who`. /// -/// All locked amount is unlocked. -pub fn kill_stake<T: Config>(who: &T::AccountId) { - T::Currency::remove_lock(crate::STAKING_ID, who); +/// Fails if there are consumers left on `who` that restricts it from being reaped. +pub fn kill_stake<T: Config>(who: &T::AccountId) -> DispatchResult { + T::Currency::release_all(&HoldReason::Staking.into(), who, Precision::BestEffort).map(|_| ()) } /// Slash the value from `who`. @@ -86,29 +115,32 @@ pub fn slash<T: Config>( who: &T::AccountId, value: BalanceOf<T>, ) -> (NegativeImbalanceOf<T>, BalanceOf<T>) { - T::Currency::slash(who, value) + T::Currency::slash(&HoldReason::Staking.into(), who, value) } /// Mint `value` into an existing account `who`. /// /// This does not increase the total issuance. -pub fn mint_existing<T: Config>( +pub fn mint_into_existing<T: Config>( who: &T::AccountId, value: BalanceOf<T>, ) -> Option<PositiveImbalanceOf<T>> { - T::Currency::deposit_into_existing(who, value).ok() + // since the account already exists, we mint exact value even if value is below ED. + T::Currency::deposit(who, value, Precision::Exact).ok() } -/// Mint reward and create account for `who` if it does not exist. +/// Mint `value` and create account for `who` if it does not exist. /// -/// This does not increase the total issuance. +/// If value is below existential deposit, the account is not created. +/// +/// Note: This does not increase the total issuance. pub fn mint_creating<T: Config>(who: &T::AccountId, value: BalanceOf<T>) -> PositiveImbalanceOf<T> { - T::Currency::deposit_creating(who, value) + T::Currency::deposit(who, value, Precision::BestEffort).unwrap_or_default() } /// Deposit newly issued or slashed `value` into `who`. pub fn deposit_slashed<T: Config>(who: &T::AccountId, value: NegativeImbalanceOf<T>) { - T::Currency::resolve_creating(who, value) + let _ = T::Currency::resolve(who, value); } /// Issue `value` increasing total issuance. @@ -121,5 +153,5 @@ pub fn issue<T: Config>(value: BalanceOf<T>) -> NegativeImbalanceOf<T> { /// Burn the amount from the total issuance. #[cfg(feature = "runtime-benchmarks")] pub fn burn<T: Config>(amount: BalanceOf<T>) -> PositiveImbalanceOf<T> { - T::Currency::burn(amount) + T::Currency::rescind(amount) } diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index 79d8dd3fbc30365e4a81a2f708383754f67a49cc..59d272168d68bc06f73e4e85ce7fc5a407ca3daf 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -257,7 +257,11 @@ mod benchmarks { .map(|l| l.active) .ok_or("ledger not created after")?; - let _ = asset::mint_existing::<T>(&stash, max_additional).unwrap(); + let _ = asset::mint_into_existing::<T>( + &stash, + max_additional + asset::existential_deposit::<T>(), + ) + .unwrap(); whitelist_account!(stash); @@ -1133,6 +1137,23 @@ mod benchmarks { Ok(()) } + #[benchmark] + fn migrate_currency() -> Result<(), BenchmarkError> { + let (stash, _ctrl) = + create_stash_controller::<T>(USER_SEED, 100, RewardDestination::Staked)?; + let stake = asset::staked::<T>(&stash); + migrate_to_old_currency::<T>(stash.clone()); + // no holds + assert!(asset::staked::<T>(&stash).is_zero()); + whitelist_account!(stash); + + #[extrinsic_call] + _(RawOrigin::Signed(stash.clone()), stash.clone()); + + assert_eq!(asset::staked::<T>(&stash), stake); + Ok(()) + } + impl_benchmark_test_suite!( Staking, crate::mock::ExtBuilder::default().has_stakers(true), diff --git a/substrate/frame/staking/src/ledger.rs b/substrate/frame/staking/src/ledger.rs index ac3be04cf607176d6aee437a7b49bc3f4afffd55..1d66ebd27e9f729e8a42a93960435ccf78b79769 100644 --- a/substrate/frame/staking/src/ledger.rs +++ b/substrate/frame/staking/src/ledger.rs @@ -32,6 +32,7 @@ //! state consistency. use frame_support::{defensive, ensure, traits::Defensive}; +use sp_runtime::DispatchResult; use sp_staking::{StakingAccount, StakingInterface}; use crate::{ @@ -187,7 +188,8 @@ impl<T: Config> StakingLedger<T> { // We skip locking virtual stakers. if !Pallet::<T>::is_virtual_staker(&self.stash) { // for direct stakers, update lock on stash based on ledger. - asset::update_stake::<T>(&self.stash, self.total); + asset::update_stake::<T>(&self.stash, self.total) + .map_err(|_| Error::<T>::NotEnoughFunds)?; } Ledger::<T>::insert( @@ -250,7 +252,7 @@ impl<T: Config> StakingLedger<T> { /// Clears all data related to a staking ledger and its bond in both [`Ledger`] and [`Bonded`] /// storage items and updates the stash staking lock. - pub(crate) fn kill(stash: &T::AccountId) -> Result<(), Error<T>> { + pub(crate) fn kill(stash: &T::AccountId) -> DispatchResult { let controller = <Bonded<T>>::get(stash).ok_or(Error::<T>::NotStash)?; <Ledger<T>>::get(&controller).ok_or(Error::<T>::NotController).map(|ledger| { @@ -259,9 +261,9 @@ impl<T: Config> StakingLedger<T> { <Payee<T>>::remove(&stash); // kill virtual staker if it exists. - if <VirtualStakers<T>>::take(&stash).is_none() { + if <VirtualStakers<T>>::take(&ledger.stash).is_none() { // if not virtual staker, clear locks. - asset::kill_stake::<T>(&ledger.stash); + asset::kill_stake::<T>(&ledger.stash)?; } Ok(()) diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 6361663b2b1c05939ba24f572c171c00af2f77e9..42230cb27b756306446cf23cea536a054b11e119 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -312,7 +312,8 @@ use codec::{Decode, Encode, HasCompact, MaxEncodedLen}; use frame_support::{ defensive, defensive_assert, traits::{ - ConstU32, Currency, Defensive, DefensiveMax, DefensiveSaturating, Get, LockIdentifier, + tokens::fungible::{Credit, Debt}, + ConstU32, Defensive, DefensiveMax, DefensiveSaturating, Get, LockIdentifier, }, weights::Weight, BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, @@ -361,12 +362,9 @@ pub type RewardPoint = u32; /// The balance type of this pallet. pub type BalanceOf<T> = <T as Config>::CurrencyBalance; -type PositiveImbalanceOf<T> = <<T as Config>::Currency as Currency< - <T as frame_system::Config>::AccountId, ->>::PositiveImbalance; -pub type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency< - <T as frame_system::Config>::AccountId, ->>::NegativeImbalance; +type PositiveImbalanceOf<T> = Debt<<T as frame_system::Config>::AccountId, <T as Config>::Currency>; +pub type NegativeImbalanceOf<T> = + Credit<<T as frame_system::Config>::AccountId, <T as Config>::Currency>; type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source; diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index df8cb38e8b371be821c9843255ce30ffb47ae647..41fb3a31d52ede0e8e1e84c8e5d954f8c2de4d61 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -25,8 +25,7 @@ use frame_election_provider_support::{ use frame_support::{ assert_ok, derive_impl, ord_parameter_types, parameter_types, traits::{ - ConstU64, Currency, EitherOfDiverse, FindAuthor, Get, Hooks, Imbalance, LockableCurrency, - OnUnbalanced, OneSessionHandler, WithdrawReasons, + ConstU64, EitherOfDiverse, FindAuthor, Get, Imbalance, OnUnbalanced, OneSessionHandler, }, weights::constants::RocksDbWeight, }; @@ -155,7 +154,7 @@ impl pallet_session::historical::Config for Test { } impl pallet_authorship::Config for Test { type FindAuthor = Author11; - type EventHandler = Pallet<Test>; + type EventHandler = (); } impl pallet_timestamp::Config for Test { @@ -264,6 +263,7 @@ pub(crate) const DISABLING_LIMIT_FACTOR: usize = 3; #[derive_impl(crate::config_preludes::TestDefaultConfig)] impl crate::pallet::pallet::Config for Test { + type OldCurrency = Balances; type Currency = Balances; type UnixTime = Timestamp; type RewardRemainder = RewardRemainderMock; @@ -432,6 +432,7 @@ impl ExtBuilder { fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); let mut storage = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); + let ed = ExistentialDeposit::get(); let _ = pallet_balances::GenesisConfig::<Test> { balances: vec![ @@ -446,19 +447,23 @@ impl ExtBuilder { (40, self.balance_factor), (50, self.balance_factor), // stashes - (11, self.balance_factor * 1000), - (21, self.balance_factor * 2000), - (31, self.balance_factor * 2000), - (41, self.balance_factor * 2000), - (51, self.balance_factor * 2000), - (201, self.balance_factor * 2000), - (202, self.balance_factor * 2000), + // Note: Previously this pallet used locks and stakers could stake all their + // balance including ED. Now with holds, stakers are required to maintain + // (non-staked) ED in their accounts. Therefore, we drop an additional existential + // deposit to genesis stakers. + (11, self.balance_factor * 1000 + ed), + (21, self.balance_factor * 2000 + ed), + (31, self.balance_factor * 2000 + ed), + (41, self.balance_factor * 2000 + ed), + (51, self.balance_factor * 2000 + ed), + (201, self.balance_factor * 2000 + ed), + (202, self.balance_factor * 2000 + ed), // optional nominator - (100, self.balance_factor * 2000), - (101, self.balance_factor * 2000), + (100, self.balance_factor * 2000 + ed), + (101, self.balance_factor * 2000 + ed), // aux accounts (60, self.balance_factor), - (61, self.balance_factor * 2000), + (61, self.balance_factor * 2000 + ed), (70, self.balance_factor), (71, self.balance_factor * 2000), (80, self.balance_factor), @@ -466,6 +471,7 @@ impl ExtBuilder { // This allows us to have a total_payout different from 0. (999, 1_000_000_000_000), ], + ..Default::default() } .assimilate_storage(&mut storage); @@ -544,13 +550,10 @@ impl ExtBuilder { let mut ext = sp_io::TestExternalities::from(storage); if self.initialize_first_session { - // We consider all test to start after timestamp is initialized This must be ensured by - // having `timestamp::on_initialize` called before `staking::on_initialize`. Also, if - // session length is 1, then it is already triggered. ext.execute_with(|| { - System::set_block_number(1); - Session::on_initialize(1); - <Staking as Hooks<u64>>::on_initialize(1); + run_to_block(1); + + // Force reset the timestamp to the initial timestamp for easy testing. Timestamp::set_timestamp(INIT_TIMESTAMP); }); } @@ -578,7 +581,7 @@ pub(crate) fn current_era() -> EraIndex { } pub(crate) fn bond(who: AccountId, val: Balance) { - let _ = Balances::make_free_balance_be(&who, val); + let _ = asset::set_stakeable_balance::<Test>(&who, val); assert_ok!(Staking::bond(RuntimeOrigin::signed(who), val, RewardDestination::Stash)); } @@ -603,10 +606,6 @@ pub(crate) fn bond_virtual_nominator( val: Balance, target: Vec<AccountId>, ) { - // In a real scenario, `who` is a keyless account managed by another pallet which provides for - // it. - System::inc_providers(&who); - // Bond who virtually. assert_ok!(<Staking as sp_staking::StakingUnchecked>::virtual_bond(&who, val, &payee)); assert_ok!(Staking::nominate(RuntimeOrigin::signed(who), target)); @@ -618,33 +617,31 @@ pub(crate) fn bond_virtual_nominator( /// a block import/propose process where we first initialize the block, then execute some stuff (not /// in the function), and then finalize the block. pub(crate) fn run_to_block(n: BlockNumber) { - Staking::on_finalize(System::block_number()); - for b in (System::block_number() + 1)..=n { - System::set_block_number(b); - Session::on_initialize(b); - <Staking as Hooks<u64>>::on_initialize(b); - Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP); - if b != n { - Staking::on_finalize(System::block_number()); - } - } + System::run_to_block_with::<AllPalletsWithSystem>( + n, + frame_system::RunToBlockHooks::default().after_initialize(|bn| { + Timestamp::set_timestamp(bn * BLOCK_TIME + INIT_TIMESTAMP); + }), + ); } /// Progresses from the current block number (whatever that may be) to the `P * session_index + 1`. -pub(crate) fn start_session(session_index: SessionIndex) { +pub(crate) fn start_session(end_session_idx: SessionIndex) { + let period = Period::get(); let end: u64 = if Offset::get().is_zero() { - (session_index as u64) * Period::get() + (end_session_idx as u64) * period } else { - Offset::get() + (session_index.saturating_sub(1) as u64) * Period::get() + Offset::get() + (end_session_idx.saturating_sub(1) as u64) * period }; + run_to_block(end); + + let curr_session_idx = Session::current_index(); + // session must have progressed properly. assert_eq!( - Session::current_index(), - session_index, - "current session index = {}, expected = {}", - Session::current_index(), - session_index, + curr_session_idx, end_session_idx, + "current session index = {curr_session_idx}, expected = {end_session_idx}", ); } @@ -814,7 +811,7 @@ pub(crate) fn bond_extra_no_checks(stash: &AccountId, amount: Balance) { let mut ledger = Ledger::<Test>::get(&controller).expect("ledger must exist to bond_extra"); let new_total = ledger.total + amount; - Balances::set_lock(crate::STAKING_ID, stash, new_total, WithdrawReasons::all()); + let _ = asset::update_stake::<Test>(stash, new_total); ledger.total = new_total; ledger.active = new_total; Ledger::<Test>::insert(controller, ledger); @@ -823,10 +820,10 @@ pub(crate) fn bond_extra_no_checks(stash: &AccountId, amount: Balance) { pub(crate) fn setup_double_bonded_ledgers() { let init_ledgers = Ledger::<Test>::iter().count(); - let _ = Balances::make_free_balance_be(&333, 2000); - let _ = Balances::make_free_balance_be(&444, 2000); - let _ = Balances::make_free_balance_be(&555, 2000); - let _ = Balances::make_free_balance_be(&777, 2000); + let _ = asset::set_stakeable_balance::<Test>(&333, 2000); + let _ = asset::set_stakeable_balance::<Test>(&444, 2000); + let _ = asset::set_stakeable_balance::<Test>(&555, 2000); + let _ = asset::set_stakeable_balance::<Test>(&777, 2000); assert_ok!(Staking::bond(RuntimeOrigin::signed(333), 10, RewardDestination::Staked)); assert_ok!(Staking::bond(RuntimeOrigin::signed(444), 20, RewardDestination::Staked)); @@ -928,5 +925,5 @@ pub(crate) fn staking_events_since_last_call() -> Vec<crate::Event<Test>> { } pub(crate) fn balances(who: &AccountId) -> (Balance, Balance) { - (Balances::free_balance(who), Balances::reserved_balance(who)) + (asset::stakeable_balance::<Test>(who), Balances::reserved_balance(who)) } diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 2ae925d036435976c2f58d49e27f81dad1bfc1d1..8c3ff23315a42d94803f2324cebf4c4930951fe0 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -27,8 +27,8 @@ use frame_support::{ dispatch::WithPostDispatchInfo, pallet_prelude::*, traits::{ - Defensive, DefensiveSaturating, EstimateNextNewSession, Get, Imbalance, Len, OnUnbalanced, - TryCollect, UnixTime, + Defensive, DefensiveSaturating, EstimateNextNewSession, Get, Imbalance, + InspectLockableCurrency, Len, LockableCurrency, OnUnbalanced, TryCollect, UnixTime, }, weights::Weight, }; @@ -36,10 +36,9 @@ use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use pallet_session::historical; use sp_runtime::{ traits::{ - Bounded, CheckedAdd, CheckedSub, Convert, One, SaturatedConversion, Saturating, - StaticLookup, Zero, + Bounded, CheckedAdd, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero, }, - ArithmeticError, Perbill, Percent, + ArithmeticError, DispatchResult, Perbill, Percent, }; use sp_staking::{ currency_to_vote::CurrencyToVote, @@ -54,6 +53,7 @@ use crate::{ BalanceOf, EraInfo, EraPayout, Exposure, ExposureOf, Forcing, IndividualExposure, LedgerIntegrityState, MaxNominationsOf, MaxWinnersOf, Nominations, NominationsQuota, PositiveImbalanceOf, RewardDestination, SessionInterface, StakingLedger, ValidatorPrefs, + STAKING_ID, }; use alloc::{boxed::Box, vec, vec::Vec}; @@ -96,10 +96,12 @@ impl<T: Config> Pallet<T> { pub(crate) fn inspect_bond_state( stash: &T::AccountId, ) -> Result<LedgerIntegrityState, Error<T>> { - let lock = asset::staked::<T>(&stash); + // look at any old unmigrated lock as well. + let hold_or_lock = asset::staked::<T>(&stash) + .max(T::OldCurrency::balance_locked(STAKING_ID, &stash).into()); let controller = <Bonded<T>>::get(stash).ok_or_else(|| { - if lock == Zero::zero() { + if hold_or_lock == Zero::zero() { Error::<T>::NotStash } else { Error::<T>::BadState @@ -111,7 +113,7 @@ impl<T: Config> Pallet<T> { if ledger.stash != *stash { Ok(LedgerIntegrityState::Corrupted) } else { - if lock != ledger.total { + if hold_or_lock != ledger.total { Ok(LedgerIntegrityState::LockCorrupted) } else { Ok(LedgerIntegrityState::Ok) @@ -163,11 +165,7 @@ impl<T: Config> Pallet<T> { additional } else { // additional amount or actual balance of stash whichever is lower. - additional.min( - asset::stakeable_balance::<T>(stash) - .checked_sub(&ledger.total) - .ok_or(ArithmeticError::Overflow)?, - ) + additional.min(asset::free_to_stake::<T>(stash)) }; ledger.total = ledger.total.checked_add(&extra).ok_or(ArithmeticError::Overflow)?; @@ -416,12 +414,12 @@ impl<T: Config> Pallet<T> { let dest = Self::payee(StakingAccount::Stash(stash.clone()))?; let maybe_imbalance = match dest { - RewardDestination::Stash => asset::mint_existing::<T>(stash, amount), + RewardDestination::Stash => asset::mint_into_existing::<T>(stash, amount), RewardDestination::Staked => Self::ledger(Stash(stash.clone())) .and_then(|mut ledger| { ledger.active += amount; ledger.total += amount; - let r = asset::mint_existing::<T>(stash, amount); + let r = asset::mint_into_existing::<T>(stash, amount); let _ = ledger .update() @@ -799,8 +797,6 @@ impl<T: Config> Pallet<T> { Self::do_remove_validator(&stash); Self::do_remove_nominator(&stash); - frame_system::Pallet::<T>::dec_consumers(&stash); - Ok(()) } @@ -1163,6 +1159,81 @@ impl<T: Config> Pallet<T> { ) -> Exposure<T::AccountId, BalanceOf<T>> { EraInfo::<T>::get_full_exposure(era, account) } + + pub(super) fn do_migrate_currency(stash: &T::AccountId) -> DispatchResult { + if Self::is_virtual_staker(stash) { + return Self::do_migrate_virtual_staker(stash); + } + + let ledger = Self::ledger(Stash(stash.clone()))?; + let staked: BalanceOf<T> = T::OldCurrency::balance_locked(STAKING_ID, stash).into(); + ensure!(!staked.is_zero(), Error::<T>::AlreadyMigrated); + ensure!(ledger.total == staked, Error::<T>::BadState); + + // remove old staking lock + T::OldCurrency::remove_lock(STAKING_ID, &stash); + + // check if we can hold all stake. + let max_hold = asset::free_to_stake::<T>(&stash); + let force_withdraw = if max_hold >= staked { + // this means we can hold all stake. yay! + asset::update_stake::<T>(&stash, staked)?; + Zero::zero() + } else { + // if we are here, it means we cannot hold all user stake. We will do a force withdraw + // from ledger, but that's okay since anyways user do not have funds for it. + let force_withdraw = staked.saturating_sub(max_hold); + + // we ignore if active is 0. It implies the locked amount is not actively staked. The + // account can still get away from potential slash but we can't do much better here. + StakingLedger { + total: max_hold, + active: ledger.active.saturating_sub(force_withdraw), + // we are not changing the stash, so we can keep the stash. + ..ledger + } + .update()?; + force_withdraw + }; + + // Get rid of the extra consumer we used to have with OldCurrency. + frame_system::Pallet::<T>::dec_consumers(&stash); + + Self::deposit_event(Event::<T>::CurrencyMigrated { stash: stash.clone(), force_withdraw }); + Ok(()) + } + + fn do_migrate_virtual_staker(stash: &T::AccountId) -> DispatchResult { + // Funds for virtual stakers not managed/held by this pallet. We only need to clear + // the extra consumer we used to have with OldCurrency. + frame_system::Pallet::<T>::dec_consumers(&stash); + + // The delegation system that manages the virtual staker needed to increment provider + // previously because of the consumer needed by this pallet. In reality, this stash + // is just a key for managing the ledger and the account does not need to hold any + // balance or exist. We decrement this provider. + let actual_providers = frame_system::Pallet::<T>::providers(stash); + + let expected_providers = + // provider is expected to be 1 but someone can always transfer some free funds to + // these accounts, increasing the provider. + if asset::free_to_stake::<T>(&stash) >= asset::existential_deposit::<T>() { + 2 + } else { + 1 + }; + + // We should never have more than expected providers. + ensure!(actual_providers <= expected_providers, Error::<T>::BadState); + + // if actual provider is less than expected, it is already migrated. + ensure!(actual_providers == expected_providers, Error::<T>::AlreadyMigrated); + + // dec provider + let _ = frame_system::Pallet::<T>::dec_providers(&stash)?; + + return Ok(()) + } } impl<T: Config> Pallet<T> { @@ -1925,9 +1996,10 @@ impl<T: Config> StakingInterface for Pallet<T> { } impl<T: Config> sp_staking::StakingUnchecked for Pallet<T> { - fn migrate_to_virtual_staker(who: &Self::AccountId) { - asset::kill_stake::<T>(who); + fn migrate_to_virtual_staker(who: &Self::AccountId) -> DispatchResult { + asset::kill_stake::<T>(who)?; VirtualStakers::<T>::insert(who, ()); + Ok(()) } /// Virtually bonds `keyless_who` to `payee` with `value`. @@ -1945,9 +2017,6 @@ impl<T: Config> sp_staking::StakingUnchecked for Pallet<T> { // check if payee not same as who. ensure!(keyless_who != payee, Error::<T>::RewardDestinationRestricted); - // mark this pallet as consumer of `who`. - frame_system::Pallet::<T>::inc_consumers(&keyless_who).map_err(|_| Error::<T>::BadState)?; - // mark who as a virtual staker. VirtualStakers::<T>::insert(keyless_who, ()); @@ -1959,11 +2028,13 @@ impl<T: Config> sp_staking::StakingUnchecked for Pallet<T> { Ok(()) } + /// Only meant to be used in tests. #[cfg(feature = "runtime-benchmarks")] fn migrate_to_direct_staker(who: &Self::AccountId) { assert!(VirtualStakers::<T>::contains_key(who)); let ledger = StakingLedger::<T>::get(Stash(who.clone())).unwrap(); - asset::update_stake::<T>(who, ledger.total); + let _ = asset::update_stake::<T>(who, ledger.total) + .expect("funds must be transferred to stash"); VirtualStakers::<T>::remove(who); } } @@ -2100,7 +2171,7 @@ impl<T: Config> Pallet<T> { if VirtualStakers::<T>::contains_key(stash.clone()) { ensure!( asset::staked::<T>(&stash) == Zero::zero(), - "virtual stakers should not have any locked balance" + "virtual stakers should not have any staked balance" ); ensure!( <Bonded<T>>::get(stash.clone()).unwrap() == stash.clone(), @@ -2128,7 +2199,7 @@ impl<T: Config> Pallet<T> { } else { ensure!( Self::inspect_bond_state(&stash) == Ok(LedgerIntegrityState::Ok), - "bond, ledger and/or staking lock inconsistent for a bonded stash." + "bond, ledger and/or staking hold inconsistent for a bonded stash." ); } diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index b3f8c18f704cd7e5158c89f0e432f50d7bcb8c04..9d8914627397fcc1bc19249e49f65b81b8e4d7e9 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -25,8 +25,12 @@ use frame_election_provider_support::{ use frame_support::{ pallet_prelude::*, traits::{ + fungible::{ + hold::{Balanced as FunHoldBalanced, Mutate as FunHoldMutate}, + Mutate as FunMutate, + }, Defensive, DefensiveSaturating, EnsureOrigin, EstimateNextNewSession, Get, - InspectLockableCurrency, LockableCurrency, OnUnbalanced, UnixTime, + InspectLockableCurrency, OnUnbalanced, UnixTime, }, weights::Weight, BoundedVec, @@ -89,13 +93,27 @@ pub mod pallet { #[pallet::config(with_default)] pub trait Config: frame_system::Config { + /// The old trait for staking balance. Deprecated and only used for migrating old ledgers. + #[pallet::no_default] + type OldCurrency: InspectLockableCurrency< + Self::AccountId, + Moment = BlockNumberFor<Self>, + Balance = Self::CurrencyBalance, + >; + /// The staking balance. #[pallet::no_default] - type Currency: LockableCurrency< + type Currency: FunHoldMutate< Self::AccountId, - Moment = BlockNumberFor<Self>, + Reason = Self::RuntimeHoldReason, Balance = Self::CurrencyBalance, - > + InspectLockableCurrency<Self::AccountId>; + > + FunMutate<Self::AccountId, Balance = Self::CurrencyBalance> + + FunHoldBalanced<Self::AccountId, Balance = Self::CurrencyBalance>; + + /// Overarching hold reason. + #[pallet::no_default_bounds] + type RuntimeHoldReason: From<HoldReason>; + /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to /// `From<u64>`. type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned @@ -106,6 +124,8 @@ pub mod pallet { + Default + From<u64> + TypeInfo + + Send + + Sync + MaxEncodedLen; /// Time used for computing era duration. /// @@ -309,6 +329,14 @@ pub mod pallet { type WeightInfo: WeightInfo; } + /// A reason for placing a hold on funds. + #[pallet::composite_enum] + pub enum HoldReason { + /// Funds on stake by a nominator or a validator. + #[codec(index = 0)] + Staking, + } + /// Default implementations of [`DefaultConfig`], which can be used to implement [`Config`]. pub mod config_preludes { use super::*; @@ -327,6 +355,8 @@ pub mod pallet { impl DefaultConfig for TestDefaultConfig { #[inject_runtime_type] type RuntimeEvent = (); + #[inject_runtime_type] + type RuntimeHoldReason = (); type CurrencyBalance = u128; type CurrencyToVote = (); type NominationsQuota = crate::FixedNominationsQuota<16>; @@ -765,7 +795,7 @@ pub mod pallet { status ); assert!( - asset::stakeable_balance::<T>(stash) >= balance, + asset::free_to_stake::<T>(stash) >= balance, "Stash does not have enough balance to bond." ); frame_support::assert_ok!(<Pallet<T>>::bond( @@ -858,6 +888,9 @@ pub mod pallet { ValidatorDisabled { stash: T::AccountId }, /// Validator has been re-enabled. ValidatorReenabled { stash: T::AccountId }, + /// Staking balance migrated from locks to holds, with any balance that could not be held + /// is force withdrawn. + CurrencyMigrated { stash: T::AccountId, force_withdraw: BalanceOf<T> }, } #[pallet::error] @@ -929,6 +962,10 @@ pub mod pallet { NotEnoughFunds, /// Operation not allowed for virtual stakers. VirtualStakerNotAllowed, + /// Stash could not be reaped as other pallet might depend on it. + CannotReapStash, + /// The stake of this account is already migrated to `Fungible` holds. + AlreadyMigrated, } #[pallet::hooks] @@ -1172,10 +1209,7 @@ pub mod pallet { return Err(Error::<T>::InsufficientBond.into()) } - // Would fail if account has no provider. - frame_system::Pallet::<T>::inc_consumers(&stash)?; - - let stash_balance = asset::stakeable_balance::<T>(&stash); + let stash_balance = asset::free_to_stake::<T>(&stash); let value = value.min(stash_balance); Self::deposit_event(Event::<T>::Bonded { stash: stash.clone(), amount: value }); let ledger = StakingLedger::<T>::new(stash.clone(), value); @@ -1518,7 +1552,7 @@ pub mod pallet { let _ = ledger .set_payee(payee) - .defensive_proof("ledger was retrieved from storage, thus its bonded; qed.")?; + .defensive_proof("ledger was retrieved from storage, thus it's bonded; qed.")?; Ok(()) } @@ -2231,8 +2265,8 @@ pub mod pallet { let new_total = if let Some(total) = maybe_total { let new_total = total.min(stash_balance); - // enforce lock == ledger.amount. - asset::update_stake::<T>(&stash, new_total); + // enforce hold == ledger.amount. + asset::update_stake::<T>(&stash, new_total)?; new_total } else { current_lock @@ -2259,13 +2293,13 @@ pub mod pallet { // to enforce a new ledger.total and staking lock for this stash. let new_total = maybe_total.ok_or(Error::<T>::CannotRestoreLedger)?.min(stash_balance); - asset::update_stake::<T>(&stash, new_total); + asset::update_stake::<T>(&stash, new_total)?; Ok((stash.clone(), new_total)) }, Err(Error::<T>::BadState) => { // the stash and ledger do not exist but lock is lingering. - asset::kill_stake::<T>(&stash); + asset::kill_stake::<T>(&stash)?; ensure!( Self::inspect_bond_state(&stash) == Err(Error::<T>::NotStash), Error::<T>::BadState @@ -2291,6 +2325,26 @@ pub mod pallet { ); Ok(()) } + + /// Migrates permissionlessly a stash from locks to holds. + /// + /// This removes the old lock on the stake and creates a hold on it atomically. If all + /// stake cannot be held, the best effort is made to hold as much as possible. The remaining + /// stake is removed from the ledger. + /// + /// The fee is waived if the migration is successful. + #[pallet::call_index(30)] + #[pallet::weight(T::WeightInfo::migrate_currency())] + pub fn migrate_currency( + origin: OriginFor<T>, + stash: T::AccountId, + ) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + Self::do_migrate_currency(&stash)?; + + // Refund the transaction fee if successful. + Ok(Pays::No.into()) + } } } diff --git a/substrate/frame/staking/src/testing_utils.rs b/substrate/frame/staking/src/testing_utils.rs index 81337710aa9043bf975a95c3d5377b0dea0738f8..dfd5422106c081784c95985fa64477b596e93064 100644 --- a/substrate/frame/staking/src/testing_utils.rs +++ b/substrate/frame/staking/src/testing_utils.rs @@ -238,3 +238,21 @@ pub fn create_validators_with_nominators_for_era<T: Config>( pub fn current_era<T: Config>() -> EraIndex { CurrentEra::<T>::get().unwrap_or(0) } + +pub fn migrate_to_old_currency<T: Config>(who: T::AccountId) { + use frame_support::traits::LockableCurrency; + let staked = asset::staked::<T>(&who); + + // apply locks (this also adds a consumer). + T::OldCurrency::set_lock( + STAKING_ID, + &who, + staked, + frame_support::traits::WithdrawReasons::all(), + ); + // remove holds. + asset::kill_stake::<T>(&who).expect("remove hold failed"); + + // replicate old behaviour of explicit increment of consumer. + frame_system::Pallet::<T>::inc_consumers(&who).expect("increment consumer failed"); +} diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 6c2335e1aac8a0f32beba70ac6974729e70af441..90841514399403301be4eb3a0d2ba87da543ddc2 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -26,8 +26,12 @@ use frame_election_provider_support::{ use frame_support::{ assert_noop, assert_ok, assert_storage_noop, dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}, + hypothetically, pallet_prelude::*, - traits::{Currency, Get, ReservableCurrency}, + traits::{ + fungible::Inspect, Currency, Get, InspectLockableCurrency, LockableCurrency, + ReservableCurrency, WithdrawReasons, + }, }; use mock::*; @@ -108,7 +112,7 @@ fn force_unstake_works() { // Cant transfer assert_noop!( Balances::transfer_allow_death(RuntimeOrigin::signed(11), 1, 10), - TokenError::Frozen, + TokenError::FundsUnavailable, ); // Force unstake requires root. assert_noop!(Staking::force_unstake(RuntimeOrigin::signed(11), 11, 2), BadOrigin); @@ -229,8 +233,7 @@ fn basic_setup_works() { assert_eq!(active_era(), 0); // Account 10 has `balance_factor` free balance - assert_eq!(asset::stakeable_balance::<Test>(&10), 1); - assert_eq!(asset::stakeable_balance::<Test>(&10), 1); + assert_eq!(Balances::balance(&10), 1); // New era is not being forced assert_eq!(ForceEra::<Test>::get(), Forcing::NotForcing); @@ -360,8 +363,16 @@ fn rewards_should_work() { remainder: maximum_payout - total_payout_0 } ); + + // make note of total issuance before rewards. + let total_issuance_0 = asset::total_issuance::<Test>(); + mock::make_all_reward_payment(0); + // total issuance should have increased + let total_issuance_1 = asset::total_issuance::<Test>(); + assert_eq!(total_issuance_1, total_issuance_0 + total_payout_0); + assert_eq_error_rate!( asset::total_balance::<Test>(&11), init_balance_11 + part_for_11 * total_payout_0 * 2 / 3, @@ -401,6 +412,7 @@ fn rewards_should_work() { ); mock::make_all_reward_payment(1); + assert_eq!(asset::total_issuance::<Test>(), total_issuance_1 + total_payout_1); assert_eq_error_rate!( asset::total_balance::<Test>(&11), init_balance_11 + part_for_11 * (total_payout_0 * 2 / 3 + total_payout_1), @@ -490,7 +502,7 @@ fn staking_should_work() { } ); // e.g. it cannot reserve more than 500 that it has free from the total 2000 - assert_noop!(Balances::reserve(&3, 501), BalancesError::<Test, _>::LiquidityRestrictions); + assert_noop!(Balances::reserve(&3, 501), DispatchError::ConsumerRemaining); assert_ok!(Balances::reserve(&3, 409)); }); } @@ -689,7 +701,7 @@ fn nominating_and_rewards_should_work() { ); // Nominator 3: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 21]'s reward. ==> // 2/9 + 3/11 - assert_eq!(asset::total_balance::<Test>(&3), initial_balance); + assert_eq!(asset::stakeable_balance::<Test>(&3), initial_balance); // 333 is the reward destination for 3. assert_eq_error_rate!( asset::total_balance::<Test>(&333), @@ -992,9 +1004,9 @@ fn cannot_transfer_staked_balance() { ExtBuilder::default().nominate(false).build_and_execute(|| { // Confirm account 11 is stashed assert_eq!(Staking::bonded(&11), Some(11)); - // Confirm account 11 has some free balance + // Confirm account 11 has some stakeable balance assert_eq!(asset::stakeable_balance::<Test>(&11), 1000); - // Confirm account 11 (via controller) is totally staked + // Confirm account 11 is totally staked assert_eq!(Staking::eras_stakers(active_era(), &11).total, 1000); // Confirm account 11 cannot transfer as a result assert_noop!( @@ -1021,11 +1033,12 @@ fn cannot_transfer_staked_balance_2() { assert_eq!(asset::stakeable_balance::<Test>(&21), 2000); // Confirm account 21 (via controller) is totally staked assert_eq!(Staking::eras_stakers(active_era(), &21).total, 1000); - // Confirm account 21 can transfer at most 1000 + // Confirm account 21 cannot transfer more than 1000 assert_noop!( Balances::transfer_allow_death(RuntimeOrigin::signed(21), 21, 1001), TokenError::Frozen, ); + // Confirm account 21 needs to leave at least ED in free balance to be able to transfer assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(21), 21, 1000)); }); } @@ -1036,17 +1049,61 @@ fn cannot_reserve_staked_balance() { ExtBuilder::default().build_and_execute(|| { // Confirm account 11 is stashed assert_eq!(Staking::bonded(&11), Some(11)); - // Confirm account 11 has some free balance - assert_eq!(asset::stakeable_balance::<Test>(&11), 1000); - // Confirm account 11 (via controller 10) is totally staked - assert_eq!(Staking::eras_stakers(active_era(), &11).own, 1000); + // Confirm account 11 is totally staked + assert_eq!(asset::staked::<Test>(&11), 1000); + // Confirm account 11 cannot reserve as a result - assert_noop!(Balances::reserve(&11, 1), BalancesError::<Test, _>::LiquidityRestrictions); + assert_noop!(Balances::reserve(&11, 2), BalancesError::<Test, _>::InsufficientBalance); + assert_noop!(Balances::reserve(&11, 1), DispatchError::ConsumerRemaining); // Give account 11 extra free balance - let _ = asset::set_stakeable_balance::<Test>(&11, 10000); + let _ = asset::set_stakeable_balance::<Test>(&11, 1000 + 1000); + assert_eq!(asset::free_to_stake::<Test>(&11), 1000); + // Confirm account 11 can now reserve balance - assert_ok!(Balances::reserve(&11, 1)); + assert_ok!(Balances::reserve(&11, 500)); + + // free to stake balance has reduced + assert_eq!(asset::free_to_stake::<Test>(&11), 500); + }); +} + +#[test] +fn locked_balance_can_be_staked() { + // Checks that a bonded account cannot reserve balance from free balance + ExtBuilder::default().build_and_execute(|| { + // Confirm account 11 is stashed + assert_eq!(Staking::bonded(&11), Some(11)); + assert_eq!(asset::staked::<Test>(&11), 1000); + assert_eq!(asset::free_to_stake::<Test>(&11), 0); + + // add some staking balance to 11 + let _ = asset::set_stakeable_balance::<Test>(&11, 1000 + 1000); + // free to stake is 1000 + assert_eq!(asset::free_to_stake::<Test>(&11), 1000); + + // lock some balance + Balances::set_lock(*b"somelock", &11, 500, WithdrawReasons::all()); + + // locked balance still available for staking + assert_eq!(asset::free_to_stake::<Test>(&11), 1000); + + // can stake free balance + assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), 500)); + assert_eq!(asset::staked::<Test>(&11), 1500); + + // Can stake the locked balance + assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), 500)); + assert_eq!(asset::staked::<Test>(&11), 2000); + // no balance left to stake + assert_eq!(asset::free_to_stake::<Test>(&11), 0); + + // this does not fail if someone tries to stake more than free balance but just stakes + // whatever is available. (not sure if that is the best way, but we keep it backward + // compatible) + assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), 10)); + // no extra balance staked. + assert_eq!(asset::staked::<Test>(&11), 2000); }); } @@ -1057,9 +1114,9 @@ fn reward_destination_works() { // Check that account 11 is a validator assert!(Session::validators().contains(&11)); // Check the balance of the validator account - assert_eq!(asset::stakeable_balance::<Test>(&10), 1); + assert_eq!(asset::total_balance::<Test>(&10), 1); // Check the balance of the stash account - assert_eq!(asset::stakeable_balance::<Test>(&11), 1000); + assert_eq!(asset::total_balance::<Test>(&11), 1001); // Check how much is at stake assert_eq!( Staking::ledger(11.into()).unwrap(), @@ -1294,12 +1351,12 @@ fn bond_extra_and_withdraw_unbonded_works() { // Give account 11 some large free balance greater than total let _ = asset::set_stakeable_balance::<Test>(&11, 1000000); + // ensure it has the correct balance. + assert_eq!(asset::stakeable_balance::<Test>(&11), 1000000); + // Initial config should be correct assert_eq!(active_era(), 0); - // check the balance of a validator accounts. - assert_eq!(asset::total_balance::<Test>(&11), 1000000); - // confirm that 10 is a normal validator and gets paid at the end of the era. mock::start_active_era(1); @@ -2077,7 +2134,7 @@ fn bond_with_no_staked_value() { ); // bonded with absolute minimum value possible. assert_ok!(Staking::bond(RuntimeOrigin::signed(1), 5, RewardDestination::Account(1))); - assert_eq!(pallet_balances::Locks::<Test>::get(&1)[0].amount, 5); + assert_eq!(pallet_balances::Holds::<Test>::get(&1)[0].amount, 5); // unbonding even 1 will cause all to be unbonded. assert_ok!(Staking::unbond(RuntimeOrigin::signed(1), 1)); @@ -2098,14 +2155,14 @@ fn bond_with_no_staked_value() { // not yet removed. assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(1), 0)); assert!(Staking::ledger(1.into()).is_ok()); - assert_eq!(pallet_balances::Locks::<Test>::get(&1)[0].amount, 5); + assert_eq!(pallet_balances::Holds::<Test>::get(&1)[0].amount, 5); mock::start_active_era(3); // poof. Account 1 is removed from the staking system. assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(1), 0)); assert!(Staking::ledger(1.into()).is_err()); - assert_eq!(pallet_balances::Locks::<Test>::get(&1).len(), 0); + assert_eq!(pallet_balances::Holds::<Test>::get(&1).len(), 0); }); } @@ -2338,9 +2395,20 @@ fn reward_validator_slashing_validator_does_not_overflow() { EraInfo::<Test>::set_exposure(0, &11, exposure); ErasValidatorReward::<Test>::insert(0, stake); assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 0, 0)); - assert_eq!(asset::total_balance::<Test>(&11), stake * 2); + assert_eq!(asset::stakeable_balance::<Test>(&11), stake * 2); - // Set staker + // ensure ledger has `stake` and no more. + Ledger::<Test>::insert( + 11, + StakingLedgerInspect { + stash: 11, + total: stake, + active: stake, + unlocking: Default::default(), + legacy_claimed_rewards: bounded_vec![1], + }, + ); + // Set staker (unsafe, can reduce balance below actual stake) let _ = asset::set_stakeable_balance::<Test>(&11, stake); let _ = asset::set_stakeable_balance::<Test>(&2, stake); @@ -2366,8 +2434,8 @@ fn reward_validator_slashing_validator_does_not_overflow() { &[Perbill::from_percent(100)], ); - assert_eq!(asset::total_balance::<Test>(&11), stake - 1); - assert_eq!(asset::total_balance::<Test>(&2), 1); + assert_eq!(asset::stakeable_balance::<Test>(&11), stake - 1); + assert_eq!(asset::stakeable_balance::<Test>(&2), 1); }) } @@ -2627,8 +2695,8 @@ fn reporters_receive_their_slice() { // 50% * (10% * initial_balance / 2) let reward = (initial_balance / 20) / 2; let reward_each = reward / 2; // split into two pieces. - assert_eq!(asset::stakeable_balance::<Test>(&1), 10 + reward_each); - assert_eq!(asset::stakeable_balance::<Test>(&2), 20 + reward_each); + assert_eq!(asset::total_balance::<Test>(&1), 10 + reward_each); + assert_eq!(asset::total_balance::<Test>(&2), 20 + reward_each); }); } @@ -2653,7 +2721,7 @@ fn subsequent_reports_in_same_span_pay_out_less() { // F1 * (reward_proportion * slash - 0) // 50% * (10% * initial_balance * 20%) let reward = (initial_balance / 5) / 20; - assert_eq!(asset::stakeable_balance::<Test>(&1), 10 + reward); + assert_eq!(asset::total_balance::<Test>(&1), 10 + reward); on_offence_now( &[OffenceDetails { @@ -2668,7 +2736,7 @@ fn subsequent_reports_in_same_span_pay_out_less() { // F1 * (reward_proportion * slash - prior_payout) // 50% * (10% * (initial_balance / 2) - prior_payout) let reward = ((initial_balance / 20) - prior_payout) / 2; - assert_eq!(asset::stakeable_balance::<Test>(&1), 10 + prior_payout + reward); + assert_eq!(asset::total_balance::<Test>(&1), 10 + prior_payout + reward); }); } @@ -2812,8 +2880,9 @@ fn garbage_collection_after_slashing() { // validator and nominator slash in era are garbage-collected by era change, // so we don't test those here. - assert_eq!(asset::stakeable_balance::<Test>(&11), 2); - assert_eq!(asset::total_balance::<Test>(&11), 2); + assert_eq!(asset::stakeable_balance::<Test>(&11), 0); + // Non staked balance is not touched. + assert_eq!(asset::total_balance::<Test>(&11), ExistentialDeposit::get()); let slashing_spans = SlashingSpans::<Test>::get(&11).unwrap(); assert_eq!(slashing_spans.iter().count(), 2); @@ -6092,7 +6161,7 @@ fn nomination_quota_max_changes_decoding() { .add_staker(70, 71, 10, StakerStatus::Nominator(vec![1, 2, 3])) .add_staker(30, 330, 10, StakerStatus::Nominator(vec![1, 2, 3, 4])) .add_staker(50, 550, 10, StakerStatus::Nominator(vec![1, 2, 3, 4])) - .balance_factor(10) + .balance_factor(11) .build_and_execute(|| { // pre-condition. assert_eq!(MaxNominationsOf::<Test>::get(), 16); @@ -6208,240 +6277,248 @@ fn force_apply_min_commission_works() { #[test] fn proportional_slash_stop_slashing_if_remaining_zero() { - let c = |era, value| UnlockChunk::<Balance> { era, value }; + ExtBuilder::default().nominate(true).build_and_execute(|| { + let c = |era, value| UnlockChunk::<Balance> { era, value }; - // we have some chunks, but they are not affected. - let unlocking = bounded_vec![c(1, 10), c(2, 10)]; + // we have some chunks, but they are not affected. + let unlocking = bounded_vec![c(1, 10), c(2, 10)]; - // Given - let mut ledger = StakingLedger::<Test>::new(123, 20); - ledger.total = 40; - ledger.unlocking = unlocking; + // Given + let mut ledger = StakingLedger::<Test>::new(123, 20); + ledger.total = 40; + ledger.unlocking = unlocking; - assert_eq!(BondingDuration::get(), 3); + assert_eq!(BondingDuration::get(), 3); - // should not slash more than the amount requested, by accidentally slashing the first chunk. - assert_eq!(ledger.slash(18, 1, 0), 18); + // should not slash more than the amount requested, by accidentally slashing the first + // chunk. + assert_eq!(ledger.slash(18, 1, 0), 18); + }); } #[test] fn proportional_ledger_slash_works() { - let c = |era, value| UnlockChunk::<Balance> { era, value }; - // Given - let mut ledger = StakingLedger::<Test>::new(123, 10); - assert_eq!(BondingDuration::get(), 3); - - // When we slash a ledger with no unlocking chunks - assert_eq!(ledger.slash(5, 1, 0), 5); - // Then - assert_eq!(ledger.total, 5); - assert_eq!(ledger.active, 5); - assert_eq!(LedgerSlashPerEra::get().0, 5); - assert_eq!(LedgerSlashPerEra::get().1, Default::default()); - - // When we slash a ledger with no unlocking chunks and the slash amount is greater then the - // total - assert_eq!(ledger.slash(11, 1, 0), 5); - // Then - assert_eq!(ledger.total, 0); - assert_eq!(ledger.active, 0); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, Default::default()); - - // Given - ledger.unlocking = bounded_vec![c(4, 10), c(5, 10)]; - ledger.total = 2 * 10; - ledger.active = 0; - // When all the chunks overlap with the slash eras - assert_eq!(ledger.slash(20, 0, 0), 20); - // Then - assert_eq!(ledger.unlocking, vec![]); - assert_eq!(ledger.total, 0); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0)])); - - // Given - ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; - ledger.total = 4 * 100; - ledger.active = 0; - // When the first 2 chunks don't overlap with the affected range of unlock eras. - assert_eq!(ledger.slash(140, 0, 3), 140); - // Then - assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 30), c(7, 30)]); - assert_eq!(ledger.total, 4 * 100 - 140); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 30), (7, 30)])); - - // Given - ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; - ledger.total = 4 * 100; - ledger.active = 0; - // When the first 2 chunks don't overlap with the affected range of unlock eras. - assert_eq!(ledger.slash(15, 0, 3), 15); - // Then - assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 100 - 8), c(7, 100 - 7)]); - assert_eq!(ledger.total, 4 * 100 - 15); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 92), (7, 93)])); - - // Given - ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; - ledger.active = 500; - // 900 - ledger.total = 40 + 10 + 100 + 250 + 500; - // When we have a partial slash that touches all chunks - assert_eq!(ledger.slash(900 / 2, 0, 0), 450); - // Then - assert_eq!(ledger.active, 500 / 2); - assert_eq!(ledger.unlocking, vec![c(4, 40 / 2), c(5, 100 / 2), c(6, 10 / 2), c(7, 250 / 2)]); - assert_eq!(ledger.total, 900 / 2); - assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); - assert_eq!( - LedgerSlashPerEra::get().1, - BTreeMap::from([(4, 40 / 2), (5, 100 / 2), (6, 10 / 2), (7, 250 / 2)]) - ); + ExtBuilder::default().nominate(true).build_and_execute(|| { + let c = |era, value| UnlockChunk::<Balance> { era, value }; + // Given + let mut ledger = StakingLedger::<Test>::new(123, 10); + assert_eq!(BondingDuration::get(), 3); - // slash 1/4th with not chunk. - ledger.unlocking = bounded_vec![]; - ledger.active = 500; - ledger.total = 500; - // When we have a partial slash that touches all chunks - assert_eq!(ledger.slash(500 / 4, 0, 0), 500 / 4); - // Then - assert_eq!(ledger.active, 3 * 500 / 4); - assert_eq!(ledger.unlocking, vec![]); - assert_eq!(ledger.total, ledger.active); - assert_eq!(LedgerSlashPerEra::get().0, 3 * 500 / 4); - assert_eq!(LedgerSlashPerEra::get().1, Default::default()); - - // Given we have the same as above, - ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; - ledger.active = 500; - ledger.total = 40 + 10 + 100 + 250 + 500; // 900 - assert_eq!(ledger.total, 900); - // When we have a higher min balance - assert_eq!( - ledger.slash( - 900 / 2, - 25, /* min balance - chunks with era 0 & 2 will be slashed to <=25, causing it to - * get swept */ - 0 - ), - 450 - ); - assert_eq!(ledger.active, 500 / 2); - // the last chunk was not slashed 50% like all the rest, because some other earlier chunks got - // dusted. - assert_eq!(ledger.unlocking, vec![c(5, 100 / 2), c(7, 150)]); - assert_eq!(ledger.total, 900 / 2); - assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); - assert_eq!( - LedgerSlashPerEra::get().1, - BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 150)]) - ); + // When we slash a ledger with no unlocking chunks + assert_eq!(ledger.slash(5, 1, 0), 5); + // Then + assert_eq!(ledger.total, 5); + assert_eq!(ledger.active, 5); + assert_eq!(LedgerSlashPerEra::get().0, 5); + assert_eq!(LedgerSlashPerEra::get().1, Default::default()); + + // When we slash a ledger with no unlocking chunks and the slash amount is greater then the + // total + assert_eq!(ledger.slash(11, 1, 0), 5); + // Then + assert_eq!(ledger.total, 0); + assert_eq!(ledger.active, 0); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, Default::default()); - // Given - // slash order --------------------NA--------2----------0----------1---- - ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; - ledger.active = 500; - ledger.total = 40 + 10 + 100 + 250 + 500; // 900 - assert_eq!( - ledger.slash( - 500 + 10 + 250 + 100 / 2, // active + era 6 + era 7 + era 5 / 2 - 0, - 3 /* slash era 6 first, so the affected parts are era 6, era 7 and - * ledge.active. This will cause the affected to go to zero, and then we will - * start slashing older chunks */ - ), - 500 + 250 + 10 + 100 / 2 - ); - // Then - assert_eq!(ledger.active, 0); - assert_eq!(ledger.unlocking, vec![c(4, 40), c(5, 100 / 2)]); - assert_eq!(ledger.total, 90); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 100 / 2), (6, 0), (7, 0)])); - - // Given - // iteration order------------------NA---------2----------0----------1---- - ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; - ledger.active = 100; - ledger.total = 5 * 100; - // When - assert_eq!( - ledger.slash( - 351, // active + era 6 + era 7 + era 5 / 2 + 1 - 50, // min balance - everything slashed below 50 will get dusted - 3 /* slash era 3+3 first, so the affected parts are era 6, era 7 and - * ledge.active. This will cause the affected to go to zero, and then we will - * start slashing older chunks */ - ), - 400 - ); - // Then - assert_eq!(ledger.active, 0); - assert_eq!(ledger.unlocking, vec![c(4, 100)]); - assert_eq!(ledger.total, 100); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 0), (6, 0), (7, 0)])); - - // Tests for saturating arithmetic - - // Given - let slash = u64::MAX as Balance * 2; - // The value of the other parts of ledger that will get slashed - let value = slash - (10 * 4); - - ledger.active = 10; - ledger.unlocking = bounded_vec![c(4, 10), c(5, 10), c(6, 10), c(7, value)]; - ledger.total = value + 40; - // When - let slash_amount = ledger.slash(slash, 0, 0); - assert_eq_error_rate!(slash_amount, slash, 5); - // Then - assert_eq!(ledger.active, 0); // slash of 9 - assert_eq!(ledger.unlocking, vec![]); - assert_eq!(ledger.total, 0); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0), (6, 0), (7, 0)])); - - // Given - use sp_runtime::PerThing as _; - let slash = u64::MAX as Balance * 2; - let value = u64::MAX as Balance * 2; - let unit = 100; - // slash * value that will saturate - assert!(slash.checked_mul(value).is_none()); - // but slash * unit won't. - assert!(slash.checked_mul(unit).is_some()); - ledger.unlocking = bounded_vec![c(4, unit), c(5, value), c(6, unit), c(7, unit)]; - //--------------------------------------note value^^^ - ledger.active = unit; - ledger.total = unit * 4 + value; - // When - assert_eq!(ledger.slash(slash, 0, 0), slash); - // Then - // The amount slashed out of `unit` - let affected_balance = value + unit * 4; - let ratio = - Perquintill::from_rational_with_rounding(slash, affected_balance, Rounding::Up).unwrap(); - // `unit` after the slash is applied - let unit_slashed = { - let unit_slash = ratio.mul_ceil(unit); - unit - unit_slash - }; - let value_slashed = { - let value_slash = ratio.mul_ceil(value); - value - value_slash - }; - assert_eq!(ledger.active, unit_slashed); - assert_eq!(ledger.unlocking, vec![c(5, value_slashed), c(7, 32)]); - assert_eq!(ledger.total, value_slashed + 32); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!( - LedgerSlashPerEra::get().1, - BTreeMap::from([(4, 0), (5, value_slashed), (6, 0), (7, 32)]) - ); + // Given + ledger.unlocking = bounded_vec![c(4, 10), c(5, 10)]; + ledger.total = 2 * 10; + ledger.active = 0; + // When all the chunks overlap with the slash eras + assert_eq!(ledger.slash(20, 0, 0), 20); + // Then + assert_eq!(ledger.unlocking, vec![]); + assert_eq!(ledger.total, 0); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0)])); + + // Given + ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; + ledger.total = 4 * 100; + ledger.active = 0; + // When the first 2 chunks don't overlap with the affected range of unlock eras. + assert_eq!(ledger.slash(140, 0, 3), 140); + // Then + assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 30), c(7, 30)]); + assert_eq!(ledger.total, 4 * 100 - 140); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 30), (7, 30)])); + + // Given + ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; + ledger.total = 4 * 100; + ledger.active = 0; + // When the first 2 chunks don't overlap with the affected range of unlock eras. + assert_eq!(ledger.slash(15, 0, 3), 15); + // Then + assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 100 - 8), c(7, 100 - 7)]); + assert_eq!(ledger.total, 4 * 100 - 15); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 92), (7, 93)])); + + // Given + ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; + ledger.active = 500; + // 900 + ledger.total = 40 + 10 + 100 + 250 + 500; + // When we have a partial slash that touches all chunks + assert_eq!(ledger.slash(900 / 2, 0, 0), 450); + // Then + assert_eq!(ledger.active, 500 / 2); + assert_eq!( + ledger.unlocking, + vec![c(4, 40 / 2), c(5, 100 / 2), c(6, 10 / 2), c(7, 250 / 2)] + ); + assert_eq!(ledger.total, 900 / 2); + assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); + assert_eq!( + LedgerSlashPerEra::get().1, + BTreeMap::from([(4, 40 / 2), (5, 100 / 2), (6, 10 / 2), (7, 250 / 2)]) + ); + + // slash 1/4th with not chunk. + ledger.unlocking = bounded_vec![]; + ledger.active = 500; + ledger.total = 500; + // When we have a partial slash that touches all chunks + assert_eq!(ledger.slash(500 / 4, 0, 0), 500 / 4); + // Then + assert_eq!(ledger.active, 3 * 500 / 4); + assert_eq!(ledger.unlocking, vec![]); + assert_eq!(ledger.total, ledger.active); + assert_eq!(LedgerSlashPerEra::get().0, 3 * 500 / 4); + assert_eq!(LedgerSlashPerEra::get().1, Default::default()); + + // Given we have the same as above, + ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; + ledger.active = 500; + ledger.total = 40 + 10 + 100 + 250 + 500; // 900 + assert_eq!(ledger.total, 900); + // When we have a higher min balance + assert_eq!( + ledger.slash( + 900 / 2, + 25, /* min balance - chunks with era 0 & 2 will be slashed to <=25, causing it + * to get swept */ + 0 + ), + 450 + ); + assert_eq!(ledger.active, 500 / 2); + // the last chunk was not slashed 50% like all the rest, because some other earlier chunks + // got dusted. + assert_eq!(ledger.unlocking, vec![c(5, 100 / 2), c(7, 150)]); + assert_eq!(ledger.total, 900 / 2); + assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); + assert_eq!( + LedgerSlashPerEra::get().1, + BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 150)]) + ); + + // Given + // slash order --------------------NA--------2----------0----------1---- + ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; + ledger.active = 500; + ledger.total = 40 + 10 + 100 + 250 + 500; // 900 + assert_eq!( + ledger.slash( + 500 + 10 + 250 + 100 / 2, // active + era 6 + era 7 + era 5 / 2 + 0, + 3 /* slash era 6 first, so the affected parts are era 6, era 7 and + * ledge.active. This will cause the affected to go to zero, and then we will + * start slashing older chunks */ + ), + 500 + 250 + 10 + 100 / 2 + ); + // Then + assert_eq!(ledger.active, 0); + assert_eq!(ledger.unlocking, vec![c(4, 40), c(5, 100 / 2)]); + assert_eq!(ledger.total, 90); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 100 / 2), (6, 0), (7, 0)])); + + // Given + // iteration order------------------NA---------2----------0----------1---- + ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; + ledger.active = 100; + ledger.total = 5 * 100; + // When + assert_eq!( + ledger.slash( + 351, // active + era 6 + era 7 + era 5 / 2 + 1 + 50, // min balance - everything slashed below 50 will get dusted + 3 /* slash era 3+3 first, so the affected parts are era 6, era 7 and + * ledge.active. This will cause the affected to go to zero, and then we + * will start slashing older chunks */ + ), + 400 + ); + // Then + assert_eq!(ledger.active, 0); + assert_eq!(ledger.unlocking, vec![c(4, 100)]); + assert_eq!(ledger.total, 100); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 0), (6, 0), (7, 0)])); + + // Tests for saturating arithmetic + + // Given + let slash = u64::MAX as Balance * 2; + // The value of the other parts of ledger that will get slashed + let value = slash - (10 * 4); + + ledger.active = 10; + ledger.unlocking = bounded_vec![c(4, 10), c(5, 10), c(6, 10), c(7, value)]; + ledger.total = value + 40; + // When + let slash_amount = ledger.slash(slash, 0, 0); + assert_eq_error_rate!(slash_amount, slash, 5); + // Then + assert_eq!(ledger.active, 0); // slash of 9 + assert_eq!(ledger.unlocking, vec![]); + assert_eq!(ledger.total, 0); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0), (6, 0), (7, 0)])); + + // Given + use sp_runtime::PerThing as _; + let slash = u64::MAX as Balance * 2; + let value = u64::MAX as Balance * 2; + let unit = 100; + // slash * value that will saturate + assert!(slash.checked_mul(value).is_none()); + // but slash * unit won't. + assert!(slash.checked_mul(unit).is_some()); + ledger.unlocking = bounded_vec![c(4, unit), c(5, value), c(6, unit), c(7, unit)]; + //--------------------------------------note value^^^ + ledger.active = unit; + ledger.total = unit * 4 + value; + // When + assert_eq!(ledger.slash(slash, 0, 0), slash); + // Then + // The amount slashed out of `unit` + let affected_balance = value + unit * 4; + let ratio = Perquintill::from_rational_with_rounding(slash, affected_balance, Rounding::Up) + .unwrap(); + // `unit` after the slash is applied + let unit_slashed = { + let unit_slash = ratio.mul_ceil(unit); + unit - unit_slash + }; + let value_slashed = { + let value_slash = ratio.mul_ceil(value); + value - value_slash + }; + assert_eq!(ledger.active, unit_slashed); + assert_eq!(ledger.unlocking, vec![c(5, value_slashed), c(7, 32)]); + assert_eq!(ledger.total, value_slashed + 32); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!( + LedgerSlashPerEra::get().1, + BTreeMap::from([(4, 0), (5, value_slashed), (6, 0), (7, 32)]) + ); + }); } #[test] @@ -7126,7 +7203,7 @@ mod staking_unchecked { fn virtual_bond_does_not_lock() { ExtBuilder::default().build_and_execute(|| { mock::start_active_era(1); - assert_eq!(asset::stakeable_balance::<Test>(&10), 1); + assert_eq!(asset::total_balance::<Test>(&10), 1); // 10 can bond more than its balance amount since we do not require lock for virtual // bonding. assert_ok!(<Staking as StakingUnchecked>::virtual_bond(&10, 100, &15)); @@ -7265,7 +7342,7 @@ mod staking_unchecked { assert_eq!(asset::staked::<Test>(&200), 1000); // migrate them to virtual staker - <Staking as StakingUnchecked>::migrate_to_virtual_staker(&200); + assert_ok!(<Staking as StakingUnchecked>::migrate_to_virtual_staker(&200)); // payee needs to be updated to a non-stash account. assert_ok!(<Staking as StakingInterface>::set_payee(&200, &201)); @@ -7292,7 +7369,7 @@ mod staking_unchecked { // 101 is a nominator for 11 assert_eq!(initial_exposure.others.first().unwrap().who, 101); // make 101 a virtual nominator - <Staking as StakingUnchecked>::migrate_to_virtual_staker(&101); + assert_ok!(<Staking as StakingUnchecked>::migrate_to_virtual_staker(&101)); // set payee different to self. assert_ok!(<Staking as StakingInterface>::set_payee(&101, &102)); @@ -7367,7 +7444,7 @@ mod staking_unchecked { // 101 is a nominator for 11 assert_eq!(initial_exposure.others.first().unwrap().who, 101); // make 101 a virtual nominator - <Staking as StakingUnchecked>::migrate_to_virtual_staker(&101); + assert_ok!(<Staking as StakingUnchecked>::migrate_to_virtual_staker(&101)); // set payee different to self. assert_ok!(<Staking as StakingInterface>::set_payee(&101, &102)); @@ -7423,7 +7500,7 @@ mod staking_unchecked { // 333 is corrupted assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Corrupted); // migrate to virtual staker. - <Staking as StakingUnchecked>::migrate_to_virtual_staker(&333); + assert_ok!(<Staking as StakingUnchecked>::migrate_to_virtual_staker(&333)); // recover the ledger won't work for virtual staker assert_noop!( @@ -8034,8 +8111,7 @@ mod ledger_recovery { // side effects on 333 - ledger, bonded, payee, lock should be intact. assert_eq!(asset::staked::<Test>(&333), lock_333_before); // OK assert_eq!(Bonded::<Test>::get(&333), Some(444)); // OK - assert!(Payee::<Test>::get(&333).is_some()); // OK - + assert!(Payee::<Test>::get(&333).is_some()); // however, ledger associated with its controller was killed. assert!(Ledger::<Test>::get(&444).is_none()); // NOK @@ -9081,3 +9157,249 @@ mod getters { }); } } + +mod hold_migration { + use super::*; + use sp_staking::{Stake, StakingInterface}; + + #[test] + fn ledger_update_creates_hold() { + ExtBuilder::default().has_stakers(true).build_and_execute(|| { + // GIVEN alice who is a nominator with old currency + let alice = 300; + bond_nominator(alice, 1000, vec![11]); + assert_eq!(asset::staked::<Test>(&alice), 1000); + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 0); + // migrate alice currency to legacy locks + testing_utils::migrate_to_old_currency::<Test>(alice); + // no more holds + assert_eq!(asset::staked::<Test>(&alice), 0); + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 1000); + assert_eq!( + <Staking as StakingInterface>::stake(&alice), + Ok(Stake { total: 1000, active: 1000 }) + ); + + // any ledger mutation should create a hold + hypothetically!({ + // give some extra balance to alice. + let _ = asset::mint_into_existing::<Test>(&alice, 100); + + // WHEN new fund is bonded to ledger. + assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(alice), 100)); + + // THEN new hold is created + assert_eq!(asset::staked::<Test>(&alice), 1000 + 100); + assert_eq!( + <Staking as StakingInterface>::stake(&alice), + Ok(Stake { total: 1100, active: 1100 }) + ); + + // old locked balance is untouched + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 1000); + }); + + hypothetically!({ + // WHEN new fund is unbonded from ledger. + assert_ok!(Staking::unbond(RuntimeOrigin::signed(alice), 100)); + + // THEN hold is updated. + assert_eq!(asset::staked::<Test>(&alice), 1000); + assert_eq!( + <Staking as StakingInterface>::stake(&alice), + Ok(Stake { total: 1000, active: 900 }) + ); + + // old locked balance is untouched + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 1000); + }); + + // WHEN alice currency is migrated. + assert_ok!(Staking::migrate_currency(RuntimeOrigin::signed(1), alice)); + + // THEN hold is updated. + assert_eq!(asset::staked::<Test>(&alice), 1000); + assert_eq!( + <Staking as StakingInterface>::stake(&alice), + Ok(Stake { total: 1000, active: 1000 }) + ); + + // ensure cannot migrate again. + assert_noop!( + Staking::migrate_currency(RuntimeOrigin::signed(1), alice), + Error::<Test>::AlreadyMigrated + ); + + // locked balance is removed + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 0); + }); + } + + #[test] + fn migrate_removes_old_lock() { + ExtBuilder::default().has_stakers(true).build_and_execute(|| { + // GIVEN alice who is a nominator with old currency + let alice = 300; + bond_nominator(alice, 1000, vec![11]); + testing_utils::migrate_to_old_currency::<Test>(alice); + assert_eq!(asset::staked::<Test>(&alice), 0); + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 1000); + let pre_migrate_consumer = System::consumers(&alice); + System::reset_events(); + + // WHEN alice currency is migrated. + assert_ok!(Staking::migrate_currency(RuntimeOrigin::signed(1), alice)); + + // THEN + // the extra consumer from old code is removed. + assert_eq!(System::consumers(&alice), pre_migrate_consumer - 1); + // ensure no lock + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 0); + // ensure stake and hold are same. + assert_eq!( + <Staking as StakingInterface>::stake(&alice), + Ok(Stake { total: 1000, active: 1000 }) + ); + assert_eq!(asset::staked::<Test>(&alice), 1000); + // ensure events are emitted. + assert_eq!( + staking_events_since_last_call(), + vec![Event::CurrencyMigrated { stash: alice, force_withdraw: 0 }] + ); + + // ensure cannot migrate again. + assert_noop!( + Staking::migrate_currency(RuntimeOrigin::signed(1), alice), + Error::<Test>::AlreadyMigrated + ); + }); + } + #[test] + fn cannot_hold_all_stake() { + // When there is not enough funds to hold all stake, part of the stake if force withdrawn. + // At end of the migration, the stake and hold should be same. + ExtBuilder::default().has_stakers(true).build_and_execute(|| { + // GIVEN alice who is a nominator with old currency. + let alice = 300; + let stake = 1000; + bond_nominator(alice, stake, vec![11]); + testing_utils::migrate_to_old_currency::<Test>(alice); + assert_eq!(asset::staked::<Test>(&alice), 0); + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), stake); + // ledger has 1000 staked. + assert_eq!( + <Staking as StakingInterface>::stake(&alice), + Ok(Stake { total: stake, active: stake }) + ); + + // Get rid of the extra ED to emulate all their balance including ED is staked. + assert_ok!(Balances::transfer_allow_death( + RuntimeOrigin::signed(alice), + 10, + ExistentialDeposit::get() + )); + + let expected_force_withdraw = ExistentialDeposit::get(); + + // ledger mutation would fail in this case before migration because of failing hold. + assert_noop!( + Staking::unbond(RuntimeOrigin::signed(alice), 100), + Error::<Test>::NotEnoughFunds + ); + + // clear events + System::reset_events(); + + // WHEN alice currency is migrated. + assert_ok!(Staking::migrate_currency(RuntimeOrigin::signed(1), alice)); + + // THEN + let expected_hold = stake - expected_force_withdraw; + // ensure no lock + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 0); + // ensure stake and hold are same. + assert_eq!( + <Staking as StakingInterface>::stake(&alice), + Ok(Stake { total: expected_hold, active: expected_hold }) + ); + assert_eq!(asset::staked::<Test>(&alice), expected_hold); + // ensure events are emitted. + assert_eq!( + staking_events_since_last_call(), + vec![Event::CurrencyMigrated { + stash: alice, + force_withdraw: expected_force_withdraw + }] + ); + + // ensure cannot migrate again. + assert_noop!( + Staking::migrate_currency(RuntimeOrigin::signed(1), alice), + Error::<Test>::AlreadyMigrated + ); + + // unbond works after migration. + assert_ok!(Staking::unbond(RuntimeOrigin::signed(alice), 100)); + }); + } + + #[test] + fn virtual_staker_consumer_provider_dec() { + // Ensure virtual stakers consumer and provider count is decremented. + ExtBuilder::default().has_stakers(true).build_and_execute(|| { + // 200 virtual bonds + bond_virtual_nominator(200, 201, 500, vec![11, 21]); + + // previously the virtual nominator had a provider inc by the delegation system as + // well as a consumer by this pallet. + System::inc_providers(&200); + System::inc_consumers(&200).expect("has provider, can consume"); + + hypothetically!({ + // migrate 200 + assert_ok!(Staking::migrate_currency(RuntimeOrigin::signed(1), 200)); + + // ensure account does not exist in system anymore. + assert_eq!(System::consumers(&200), 0); + assert_eq!(System::providers(&200), 0); + assert!(!System::account_exists(&200)); + + // ensure cannot migrate again. + assert_noop!( + Staking::migrate_currency(RuntimeOrigin::signed(1), 200), + Error::<Test>::AlreadyMigrated + ); + }); + + hypothetically!({ + // 200 has an erroneously extra provider + System::inc_providers(&200); + + // causes migration to fail. + assert_noop!( + Staking::migrate_currency(RuntimeOrigin::signed(1), 200), + Error::<Test>::BadState + ); + }); + + // 200 is funded for more than ED by a random account. + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(999), 200, 10)); + + // it has an extra provider now. + assert_eq!(System::providers(&200), 2); + + // migrate 200 + assert_ok!(Staking::migrate_currency(RuntimeOrigin::signed(1), 200)); + + // 1 provider is left, consumers is 0. + assert_eq!(System::providers(&200), 1); + assert_eq!(System::consumers(&200), 0); + + // ensure cannot migrate again. + assert_noop!( + Staking::migrate_currency(RuntimeOrigin::signed(1), 200), + Error::<Test>::AlreadyMigrated + ); + }); + } +} diff --git a/substrate/frame/staking/src/weights.rs b/substrate/frame/staking/src/weights.rs index 56f561679cfc78aee1cca7f1a5b759e17b2f93d9..02ccdacb01c4295f29e4735cda32704e2438f828 100644 --- a/substrate/frame/staking/src/weights.rs +++ b/substrate/frame/staking/src/weights.rs @@ -18,27 +18,25 @@ //! Autogenerated weights for `pallet_staking` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-09-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-obbyq9g6-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 +// target/production/substrate-node // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_staking -// --no-storage-info -// --no-median-slopes -// --no-min-squares // --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --output=./substrate/frame/staking/src/weights.rs +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_staking +// --chain=dev // --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/staking/src/weights.rs // --template=./substrate/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -83,6 +81,7 @@ pub trait WeightInfo { fn force_apply_min_commission() -> Weight; fn set_min_commission() -> Weight; fn restore_ledger() -> Weight; + fn migrate_currency() -> Weight; } /// Weights for `pallet_staking` using the Substrate node and recommended hardware. @@ -92,18 +91,18 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:0 w:1) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn bond() -> Weight { // Proof Size summary in bytes: - // Measured: `1042` - // Estimated: `4764` - // Minimum execution time: 46_504_000 picoseconds. - Weight::from_parts(48_459_000, 4764) + // Measured: `1068` + // Estimated: `4556` + // Minimum execution time: 71_854_000 picoseconds. + Weight::from_parts(73_408_000, 4556) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -111,20 +110,20 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn bond_extra() -> Weight { // Proof Size summary in bytes: - // Measured: `1990` + // Measured: `2049` // Estimated: `8877` - // Minimum execution time: 90_475_000 picoseconds. - Weight::from_parts(93_619_000, 8877) + // Minimum execution time: 127_442_000 picoseconds. + Weight::from_parts(130_845_000, 8877) .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -138,22 +137,22 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn unbond() -> Weight { // Proof Size summary in bytes: - // Measured: `2195` + // Measured: `2151` // Estimated: `8877` - // Minimum execution time: 99_335_000 picoseconds. - Weight::from_parts(101_440_000, 8877) + // Minimum execution time: 105_259_000 picoseconds. + Weight::from_parts(107_112_000, 8877) .saturating_add(T::DbWeight::get().reads(12_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) @@ -161,21 +160,21 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:0) /// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1297` - // Estimated: `4764` - // Minimum execution time: 50_067_000 picoseconds. - Weight::from_parts(52_396_327, 4764) - // Standard Error: 1_419 - .saturating_add(Weight::from_parts(51_406, 0).saturating_mul(s.into())) + // Measured: `1393` + // Estimated: `4556` + // Minimum execution time: 77_158_000 picoseconds. + Weight::from_parts(79_140_122, 4556) + // Standard Error: 1_688 + .saturating_add(Weight::from_parts(62_663, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -187,10 +186,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashingSpans` (r:1 w:1) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -210,14 +209,14 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2196 + s * (4 ±0)` + // Measured: `2255 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 92_931_000 picoseconds. - Weight::from_parts(101_398_156, 6248) - // Standard Error: 4_180 - .saturating_add(Weight::from_parts(1_377_850, 0).saturating_mul(s.into())) + // Minimum execution time: 125_396_000 picoseconds. + Weight::from_parts(134_915_543, 6248) + // Standard Error: 3_660 + .saturating_add(Weight::from_parts(1_324_736, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13_u64)) - .saturating_add(T::DbWeight::get().writes(11_u64)) + .saturating_add(T::DbWeight::get().writes(12_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -245,10 +244,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Staking::CounterForValidators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn validate() -> Weight { // Proof Size summary in bytes: - // Measured: `1372` + // Measured: `1438` // Estimated: `4556` - // Minimum execution time: 56_291_000 picoseconds. - Weight::from_parts(58_372_000, 4556) + // Minimum execution time: 68_826_000 picoseconds. + Weight::from_parts(71_261_000, 4556) .saturating_add(T::DbWeight::get().reads(11_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -261,12 +260,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1815 + k * (572 ±0)` + // Measured: `1848 + k * (572 ±0)` // Estimated: `4556 + k * (3033 ±0)` - // Minimum execution time: 36_218_000 picoseconds. - Weight::from_parts(38_811_308, 4556) - // Standard Error: 8_352 - .saturating_add(Weight::from_parts(6_527_398, 0).saturating_mul(k.into())) + // Minimum execution time: 46_082_000 picoseconds. + Weight::from_parts(49_541_374, 4556) + // Standard Error: 7_218 + .saturating_add(Weight::from_parts(7_281_079, 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((1_u64).saturating_mul(k.into()))) @@ -297,12 +296,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1866 + n * (102 ±0)` + // Measured: `1932 + n * (102 ±0)` // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 68_607_000 picoseconds. - Weight::from_parts(66_831_185, 6248) - // Standard Error: 14_014 - .saturating_add(Weight::from_parts(4_031_635, 0).saturating_mul(n.into())) + // Minimum execution time: 83_854_000 picoseconds. + Weight::from_parts(81_387_241, 6248) + // Standard Error: 16_811 + .saturating_add(Weight::from_parts(4_900_554, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(6_u64)) @@ -326,10 +325,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill() -> Weight { // Proof Size summary in bytes: - // Measured: `1816` + // Measured: `1882` // Estimated: `6248` - // Minimum execution time: 60_088_000 picoseconds. - Weight::from_parts(62_471_000, 6248) + // Minimum execution time: 73_939_000 picoseconds. + Weight::from_parts(75_639_000, 6248) .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -341,10 +340,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn set_payee() -> Weight { // Proof Size summary in bytes: - // Measured: `902` + // Measured: `935` // Estimated: `4556` - // Minimum execution time: 19_777_000 picoseconds. - Weight::from_parts(20_690_000, 4556) + // Minimum execution time: 24_592_000 picoseconds. + Weight::from_parts(25_092_000, 4556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -356,10 +355,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn update_payee() -> Weight { // Proof Size summary in bytes: - // Measured: `969` + // Measured: `1002` // Estimated: `4556` - // Minimum execution time: 23_705_000 picoseconds. - Weight::from_parts(24_409_000, 4556) + // Minimum execution time: 29_735_000 picoseconds. + Weight::from_parts(30_546_000, 4556) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -369,10 +368,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) fn set_controller() -> Weight { // Proof Size summary in bytes: - // Measured: `902` + // Measured: `935` // Estimated: `8122` - // Minimum execution time: 23_479_000 picoseconds. - Weight::from_parts(24_502_000, 8122) + // Minimum execution time: 28_728_000 picoseconds. + Weight::from_parts(29_709_000, 8122) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -382,8 +381,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_675_000 picoseconds. - Weight::from_parts(2_802_000, 0) + // Minimum execution time: 2_519_000 picoseconds. + Weight::from_parts(2_673_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -392,8 +391,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_067_000 picoseconds. - Weight::from_parts(7_413_000, 0) + // Minimum execution time: 8_050_000 picoseconds. + Weight::from_parts(8_268_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -402,8 +401,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_977_000 picoseconds. - Weight::from_parts(7_353_000, 0) + // Minimum execution time: 8_131_000 picoseconds. + Weight::from_parts(8_349_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -412,8 +411,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_071_000 picoseconds. - Weight::from_parts(7_463_000, 0) + // Minimum execution time: 8_104_000 picoseconds. + Weight::from_parts(8_317_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::Invulnerables` (r:0 w:1) @@ -423,10 +422,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_833_000 picoseconds. - Weight::from_parts(3_328_130, 0) - // Standard Error: 30 - .saturating_add(Weight::from_parts(10_058, 0).saturating_mul(v.into())) + // Minimum execution time: 2_669_000 picoseconds. + Weight::from_parts(3_013_436, 0) + // Standard Error: 31 + .saturating_add(Weight::from_parts(10_704, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::Ledger` (r:11800 w:11800) @@ -438,12 +437,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `i` is `[0, 5900]`. fn deprecate_controller_batch(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1746 + i * (229 ±0)` + // Measured: `1779 + i * (229 ±0)` // Estimated: `990 + i * (7132 ±0)` - // Minimum execution time: 5_300_000 picoseconds. - Weight::from_parts(5_437_000, 990) - // Standard Error: 66_261 - .saturating_add(Weight::from_parts(30_172_457, 0).saturating_mul(i.into())) + // Minimum execution time: 5_101_000 picoseconds. + Weight::from_parts(5_368_000, 990) + // Standard Error: 75_180 + .saturating_add(Weight::from_parts(33_781_643, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(i.into()))) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(i.into()))) .saturating_add(Weight::from_parts(0, 7132).saturating_mul(i.into())) @@ -454,10 +453,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) @@ -479,14 +478,14 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2196 + s * (4 ±0)` + // Measured: `2255 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 87_677_000 picoseconds. - Weight::from_parts(96_386_462, 6248) - // Standard Error: 3_717 - .saturating_add(Weight::from_parts(1_370_585, 0).saturating_mul(s.into())) + // Minimum execution time: 119_955_000 picoseconds. + Weight::from_parts(128_392_032, 6248) + // Standard Error: 3_773 + .saturating_add(Weight::from_parts(1_302_488, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13_u64)) - .saturating_add(T::DbWeight::get().writes(12_u64)) + .saturating_add(T::DbWeight::get().writes(13_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -495,12 +494,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `66672` - // Estimated: `70137` - // Minimum execution time: 105_086_000 picoseconds. - Weight::from_parts(1_167_895_222, 70137) - // Standard Error: 77_022 - .saturating_add(Weight::from_parts(6_487_305, 0).saturating_mul(s.into())) + // Measured: `66705` + // Estimated: `70170` + // Minimum execution time: 139_290_000 picoseconds. + Weight::from_parts(959_667_494, 70170) + // Standard Error: 56_271 + .saturating_add(Weight::from_parts(4_798_293, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -518,12 +517,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasValidatorReward` (r:1 w:0) /// Proof: `Staking::ErasValidatorReward` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:257 w:257) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:257 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:257 w:257) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:257 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:257 w:257) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasStakersPaged` (r:1 w:0) /// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Staking::ErasRewardPoints` (r:1 w:0) @@ -532,29 +529,31 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:257 w:0) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:257 w:257) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `33297 + n * (377 ±0)` - // Estimated: `30944 + n * (3774 ±3)` - // Minimum execution time: 154_210_000 picoseconds. - Weight::from_parts(192_836_012, 30944) - // Standard Error: 40_441 - .saturating_add(Weight::from_parts(47_646_642, 0).saturating_mul(n.into())) + // Measured: `33283 + n * (370 ±0)` + // Estimated: `30958 + n * (3566 ±0)` + // Minimum execution time: 193_068_000 picoseconds. + Weight::from_parts(252_762_568, 30958) + // Standard Error: 22_743 + .saturating_add(Weight::from_parts(81_185_306, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(14_u64)) .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4_u64)) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 3774).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) } /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) @@ -562,25 +561,25 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1991 + l * (7 ±0)` + // Measured: `1947 + l * (7 ±0)` // Estimated: `8877` - // Minimum execution time: 88_337_000 picoseconds. - Weight::from_parts(91_391_254, 8877) - // Standard Error: 4_485 - .saturating_add(Weight::from_parts(103_443, 0).saturating_mul(l.into())) + // Minimum execution time: 91_151_000 picoseconds. + Weight::from_parts(93_596_096, 8877) + // Standard Error: 5_313 + .saturating_add(Weight::from_parts(124_684, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(9_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) } + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashingSpans` (r:1 w:1) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -600,14 +599,14 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2196 + s * (4 ±0)` + // Measured: `2255 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 98_014_000 picoseconds. - Weight::from_parts(102_537_670, 6248) - // Standard Error: 3_324 - .saturating_add(Weight::from_parts(1_353_142, 0).saturating_mul(s.into())) + // Minimum execution time: 133_214_000 picoseconds. + Weight::from_parts(137_290_527, 6248) + // Standard Error: 4_153 + .saturating_add(Weight::from_parts(1_291_007, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(12_u64)) - .saturating_add(T::DbWeight::get().writes(11_u64)) + .saturating_add(T::DbWeight::get().writes(12_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -651,12 +650,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0 + n * (720 ±0) + v * (3598 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 608_575_000 picoseconds. - Weight::from_parts(613_663_000, 512390) - // Standard Error: 2_286_521 - .saturating_add(Weight::from_parts(72_108_001, 0).saturating_mul(v.into())) - // Standard Error: 227_839 - .saturating_add(Weight::from_parts(20_314_085, 0).saturating_mul(n.into())) + // Minimum execution time: 692_301_000 picoseconds. + Weight::from_parts(708_732_000, 512390) + // Standard Error: 2_117_299 + .saturating_add(Weight::from_parts(70_087_600, 0).saturating_mul(v.into())) + // Standard Error: 210_977 + .saturating_add(Weight::from_parts(22_953_405, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(206_u64)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -685,14 +684,14 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `n` is `[500, 1000]`. fn get_npos_voters(v: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `3175 + n * (911 ±0) + v * (395 ±0)` + // Measured: `3241 + n * (911 ±0) + v * (395 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 37_173_756_000 picoseconds. - Weight::from_parts(37_488_937_000, 512390) - // Standard Error: 467_413 - .saturating_add(Weight::from_parts(8_086_367, 0).saturating_mul(v.into())) - // Standard Error: 467_413 - .saturating_add(Weight::from_parts(3_108_193, 0).saturating_mul(n.into())) + // Minimum execution time: 43_708_472_000 picoseconds. + Weight::from_parts(44_048_436_000, 512390) + // Standard Error: 493_244 + .saturating_add(Weight::from_parts(6_697_278, 0).saturating_mul(v.into())) + // Standard Error: 493_244 + .saturating_add(Weight::from_parts(4_559_779, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(201_u64)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -707,12 +706,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `979 + v * (50 ±0)` + // Measured: `1012 + v * (50 ±0)` // Estimated: `3510 + v * (2520 ±0)` - // Minimum execution time: 2_641_258_000 picoseconds. - Weight::from_parts(382_882_595, 3510) - // Standard Error: 11_991 - .saturating_add(Weight::from_parts(4_695_820, 0).saturating_mul(v.into())) + // Minimum execution time: 2_917_165_000 picoseconds. + Weight::from_parts(2_948_999_000, 3510) + // Standard Error: 33_372 + .saturating_add(Weight::from_parts(2_126_909, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) .saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into())) @@ -735,8 +734,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_753_000 picoseconds. - Weight::from_parts(6_529_000, 0) + // Minimum execution time: 4_748_000 picoseconds. + Weight::from_parts(5_052_000, 0) .saturating_add(T::DbWeight::get().writes(7_u64)) } /// Storage: `Staking::MinCommission` (r:0 w:1) @@ -757,8 +756,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_212_000 picoseconds. - Weight::from_parts(5_451_000, 0) + // Minimum execution time: 4_316_000 picoseconds. + Weight::from_parts(4_526_000, 0) .saturating_add(T::DbWeight::get().writes(7_u64)) } /// Storage: `Staking::Bonded` (r:1 w:0) @@ -785,10 +784,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill_other() -> Weight { // Proof Size summary in bytes: - // Measured: `1939` + // Measured: `2005` // Estimated: `6248` - // Minimum execution time: 73_000_000 picoseconds. - Weight::from_parts(75_184_000, 6248) + // Minimum execution time: 87_374_000 picoseconds. + Weight::from_parts(89_848_000, 6248) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -798,10 +797,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) fn force_apply_min_commission() -> Weight { // Proof Size summary in bytes: - // Measured: `691` + // Measured: `724` // Estimated: `3510` - // Minimum execution time: 13_056_000 picoseconds. - Weight::from_parts(13_517_000, 3510) + // Minimum execution time: 15_529_000 picoseconds. + Weight::from_parts(16_094_000, 3510) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -811,28 +810,51 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_201_000 picoseconds. - Weight::from_parts(3_442_000, 0) + // Minimum execution time: 2_533_000 picoseconds. + Weight::from_parts(2_817_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:0) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + fn restore_ledger() -> Weight { + // Proof Size summary in bytes: + // Measured: `1110` + // Estimated: `4764` + // Minimum execution time: 50_105_000 picoseconds. + Weight::from_parts(50_966_000, 4764) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) - fn restore_ledger() -> Weight { + fn migrate_currency() -> Weight { // Proof Size summary in bytes: - // Measured: `1047` + // Measured: `1246` // Estimated: `4764` - // Minimum execution time: 44_671_000 picoseconds. - Weight::from_parts(45_611_000, 4764) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + // Minimum execution time: 94_054_000 picoseconds. + Weight::from_parts(96_272_000, 4764) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } } @@ -842,18 +864,18 @@ impl WeightInfo for () { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:0 w:1) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn bond() -> Weight { // Proof Size summary in bytes: - // Measured: `1042` - // Estimated: `4764` - // Minimum execution time: 46_504_000 picoseconds. - Weight::from_parts(48_459_000, 4764) + // Measured: `1068` + // Estimated: `4556` + // Minimum execution time: 71_854_000 picoseconds. + Weight::from_parts(73_408_000, 4556) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -861,20 +883,20 @@ impl WeightInfo for () { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn bond_extra() -> Weight { // Proof Size summary in bytes: - // Measured: `1990` + // Measured: `2049` // Estimated: `8877` - // Minimum execution time: 90_475_000 picoseconds. - Weight::from_parts(93_619_000, 8877) + // Minimum execution time: 127_442_000 picoseconds. + Weight::from_parts(130_845_000, 8877) .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -888,22 +910,22 @@ impl WeightInfo for () { /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn unbond() -> Weight { // Proof Size summary in bytes: - // Measured: `2195` + // Measured: `2151` // Estimated: `8877` - // Minimum execution time: 99_335_000 picoseconds. - Weight::from_parts(101_440_000, 8877) + // Minimum execution time: 105_259_000 picoseconds. + Weight::from_parts(107_112_000, 8877) .saturating_add(RocksDbWeight::get().reads(12_u64)) - .saturating_add(RocksDbWeight::get().writes(7_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) @@ -911,21 +933,21 @@ impl WeightInfo for () { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:0) /// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1297` - // Estimated: `4764` - // Minimum execution time: 50_067_000 picoseconds. - Weight::from_parts(52_396_327, 4764) - // Standard Error: 1_419 - .saturating_add(Weight::from_parts(51_406, 0).saturating_mul(s.into())) + // Measured: `1393` + // Estimated: `4556` + // Minimum execution time: 77_158_000 picoseconds. + Weight::from_parts(79_140_122, 4556) + // Standard Error: 1_688 + .saturating_add(Weight::from_parts(62_663, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -937,10 +959,10 @@ impl WeightInfo for () { /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashingSpans` (r:1 w:1) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -960,14 +982,14 @@ impl WeightInfo for () { /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2196 + s * (4 ±0)` + // Measured: `2255 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 92_931_000 picoseconds. - Weight::from_parts(101_398_156, 6248) - // Standard Error: 4_180 - .saturating_add(Weight::from_parts(1_377_850, 0).saturating_mul(s.into())) + // Minimum execution time: 125_396_000 picoseconds. + Weight::from_parts(134_915_543, 6248) + // Standard Error: 3_660 + .saturating_add(Weight::from_parts(1_324_736, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(13_u64)) - .saturating_add(RocksDbWeight::get().writes(11_u64)) + .saturating_add(RocksDbWeight::get().writes(12_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -995,10 +1017,10 @@ impl WeightInfo for () { /// Proof: `Staking::CounterForValidators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn validate() -> Weight { // Proof Size summary in bytes: - // Measured: `1372` + // Measured: `1438` // Estimated: `4556` - // Minimum execution time: 56_291_000 picoseconds. - Weight::from_parts(58_372_000, 4556) + // Minimum execution time: 68_826_000 picoseconds. + Weight::from_parts(71_261_000, 4556) .saturating_add(RocksDbWeight::get().reads(11_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -1011,12 +1033,12 @@ impl WeightInfo for () { /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1815 + k * (572 ±0)` + // Measured: `1848 + k * (572 ±0)` // Estimated: `4556 + k * (3033 ±0)` - // Minimum execution time: 36_218_000 picoseconds. - Weight::from_parts(38_811_308, 4556) - // Standard Error: 8_352 - .saturating_add(Weight::from_parts(6_527_398, 0).saturating_mul(k.into())) + // Minimum execution time: 46_082_000 picoseconds. + Weight::from_parts(49_541_374, 4556) + // Standard Error: 7_218 + .saturating_add(Weight::from_parts(7_281_079, 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((1_u64).saturating_mul(k.into()))) @@ -1047,12 +1069,12 @@ impl WeightInfo for () { /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1866 + n * (102 ±0)` + // Measured: `1932 + n * (102 ±0)` // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 68_607_000 picoseconds. - Weight::from_parts(66_831_185, 6248) - // Standard Error: 14_014 - .saturating_add(Weight::from_parts(4_031_635, 0).saturating_mul(n.into())) + // Minimum execution time: 83_854_000 picoseconds. + Weight::from_parts(81_387_241, 6248) + // Standard Error: 16_811 + .saturating_add(Weight::from_parts(4_900_554, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(6_u64)) @@ -1076,10 +1098,10 @@ impl WeightInfo for () { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill() -> Weight { // Proof Size summary in bytes: - // Measured: `1816` + // Measured: `1882` // Estimated: `6248` - // Minimum execution time: 60_088_000 picoseconds. - Weight::from_parts(62_471_000, 6248) + // Minimum execution time: 73_939_000 picoseconds. + Weight::from_parts(75_639_000, 6248) .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1091,10 +1113,10 @@ impl WeightInfo for () { /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn set_payee() -> Weight { // Proof Size summary in bytes: - // Measured: `902` + // Measured: `935` // Estimated: `4556` - // Minimum execution time: 19_777_000 picoseconds. - Weight::from_parts(20_690_000, 4556) + // Minimum execution time: 24_592_000 picoseconds. + Weight::from_parts(25_092_000, 4556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1106,10 +1128,10 @@ impl WeightInfo for () { /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn update_payee() -> Weight { // Proof Size summary in bytes: - // Measured: `969` + // Measured: `1002` // Estimated: `4556` - // Minimum execution time: 23_705_000 picoseconds. - Weight::from_parts(24_409_000, 4556) + // Minimum execution time: 29_735_000 picoseconds. + Weight::from_parts(30_546_000, 4556) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1119,10 +1141,10 @@ impl WeightInfo for () { /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) fn set_controller() -> Weight { // Proof Size summary in bytes: - // Measured: `902` + // Measured: `935` // Estimated: `8122` - // Minimum execution time: 23_479_000 picoseconds. - Weight::from_parts(24_502_000, 8122) + // Minimum execution time: 28_728_000 picoseconds. + Weight::from_parts(29_709_000, 8122) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1132,8 +1154,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_675_000 picoseconds. - Weight::from_parts(2_802_000, 0) + // Minimum execution time: 2_519_000 picoseconds. + Weight::from_parts(2_673_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1142,8 +1164,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_067_000 picoseconds. - Weight::from_parts(7_413_000, 0) + // Minimum execution time: 8_050_000 picoseconds. + Weight::from_parts(8_268_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1152,8 +1174,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_977_000 picoseconds. - Weight::from_parts(7_353_000, 0) + // Minimum execution time: 8_131_000 picoseconds. + Weight::from_parts(8_349_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1162,8 +1184,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_071_000 picoseconds. - Weight::from_parts(7_463_000, 0) + // Minimum execution time: 8_104_000 picoseconds. + Weight::from_parts(8_317_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::Invulnerables` (r:0 w:1) @@ -1173,10 +1195,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_833_000 picoseconds. - Weight::from_parts(3_328_130, 0) - // Standard Error: 30 - .saturating_add(Weight::from_parts(10_058, 0).saturating_mul(v.into())) + // Minimum execution time: 2_669_000 picoseconds. + Weight::from_parts(3_013_436, 0) + // Standard Error: 31 + .saturating_add(Weight::from_parts(10_704, 0).saturating_mul(v.into())) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::Ledger` (r:11800 w:11800) @@ -1188,12 +1210,12 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 5900]`. fn deprecate_controller_batch(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1746 + i * (229 ±0)` + // Measured: `1779 + i * (229 ±0)` // Estimated: `990 + i * (7132 ±0)` - // Minimum execution time: 5_300_000 picoseconds. - Weight::from_parts(5_437_000, 990) - // Standard Error: 66_261 - .saturating_add(Weight::from_parts(30_172_457, 0).saturating_mul(i.into())) + // Minimum execution time: 5_101_000 picoseconds. + Weight::from_parts(5_368_000, 990) + // Standard Error: 75_180 + .saturating_add(Weight::from_parts(33_781_643, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(i.into()))) .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(i.into()))) .saturating_add(Weight::from_parts(0, 7132).saturating_mul(i.into())) @@ -1204,10 +1226,10 @@ impl WeightInfo for () { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) @@ -1229,14 +1251,14 @@ impl WeightInfo for () { /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2196 + s * (4 ±0)` + // Measured: `2255 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 87_677_000 picoseconds. - Weight::from_parts(96_386_462, 6248) - // Standard Error: 3_717 - .saturating_add(Weight::from_parts(1_370_585, 0).saturating_mul(s.into())) + // Minimum execution time: 119_955_000 picoseconds. + Weight::from_parts(128_392_032, 6248) + // Standard Error: 3_773 + .saturating_add(Weight::from_parts(1_302_488, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(13_u64)) - .saturating_add(RocksDbWeight::get().writes(12_u64)) + .saturating_add(RocksDbWeight::get().writes(13_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -1245,12 +1267,12 @@ impl WeightInfo for () { /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `66672` - // Estimated: `70137` - // Minimum execution time: 105_086_000 picoseconds. - Weight::from_parts(1_167_895_222, 70137) - // Standard Error: 77_022 - .saturating_add(Weight::from_parts(6_487_305, 0).saturating_mul(s.into())) + // Measured: `66705` + // Estimated: `70170` + // Minimum execution time: 139_290_000 picoseconds. + Weight::from_parts(959_667_494, 70170) + // Standard Error: 56_271 + .saturating_add(Weight::from_parts(4_798_293, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1268,12 +1290,10 @@ impl WeightInfo for () { /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasValidatorReward` (r:1 w:0) /// Proof: `Staking::ErasValidatorReward` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:257 w:257) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:257 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:257 w:257) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:257 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:257 w:257) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasStakersPaged` (r:1 w:0) /// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Staking::ErasRewardPoints` (r:1 w:0) @@ -1282,29 +1302,31 @@ impl WeightInfo for () { /// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:257 w:0) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:257 w:257) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `33297 + n * (377 ±0)` - // Estimated: `30944 + n * (3774 ±3)` - // Minimum execution time: 154_210_000 picoseconds. - Weight::from_parts(192_836_012, 30944) - // Standard Error: 40_441 - .saturating_add(Weight::from_parts(47_646_642, 0).saturating_mul(n.into())) + // Measured: `33283 + n * (370 ±0)` + // Estimated: `30958 + n * (3566 ±0)` + // Minimum execution time: 193_068_000 picoseconds. + Weight::from_parts(252_762_568, 30958) + // Standard Error: 22_743 + .saturating_add(Weight::from_parts(81_185_306, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(14_u64)) .saturating_add(RocksDbWeight::get().reads((6_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(4_u64)) .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 3774).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) } /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) @@ -1312,25 +1334,25 @@ impl WeightInfo for () { /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1991 + l * (7 ±0)` + // Measured: `1947 + l * (7 ±0)` // Estimated: `8877` - // Minimum execution time: 88_337_000 picoseconds. - Weight::from_parts(91_391_254, 8877) - // Standard Error: 4_485 - .saturating_add(Weight::from_parts(103_443, 0).saturating_mul(l.into())) + // Minimum execution time: 91_151_000 picoseconds. + Weight::from_parts(93_596_096, 8877) + // Standard Error: 5_313 + .saturating_add(Weight::from_parts(124_684, 0).saturating_mul(l.into())) .saturating_add(RocksDbWeight::get().reads(9_u64)) - .saturating_add(RocksDbWeight::get().writes(7_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashingSpans` (r:1 w:1) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -1350,14 +1372,14 @@ impl WeightInfo for () { /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2196 + s * (4 ±0)` + // Measured: `2255 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 98_014_000 picoseconds. - Weight::from_parts(102_537_670, 6248) - // Standard Error: 3_324 - .saturating_add(Weight::from_parts(1_353_142, 0).saturating_mul(s.into())) + // Minimum execution time: 133_214_000 picoseconds. + Weight::from_parts(137_290_527, 6248) + // Standard Error: 4_153 + .saturating_add(Weight::from_parts(1_291_007, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(12_u64)) - .saturating_add(RocksDbWeight::get().writes(11_u64)) + .saturating_add(RocksDbWeight::get().writes(12_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -1401,12 +1423,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0 + n * (720 ±0) + v * (3598 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 608_575_000 picoseconds. - Weight::from_parts(613_663_000, 512390) - // Standard Error: 2_286_521 - .saturating_add(Weight::from_parts(72_108_001, 0).saturating_mul(v.into())) - // Standard Error: 227_839 - .saturating_add(Weight::from_parts(20_314_085, 0).saturating_mul(n.into())) + // Minimum execution time: 692_301_000 picoseconds. + Weight::from_parts(708_732_000, 512390) + // Standard Error: 2_117_299 + .saturating_add(Weight::from_parts(70_087_600, 0).saturating_mul(v.into())) + // Standard Error: 210_977 + .saturating_add(Weight::from_parts(22_953_405, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(206_u64)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -1435,14 +1457,14 @@ impl WeightInfo for () { /// The range of component `n` is `[500, 1000]`. fn get_npos_voters(v: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `3175 + n * (911 ±0) + v * (395 ±0)` + // Measured: `3241 + n * (911 ±0) + v * (395 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 37_173_756_000 picoseconds. - Weight::from_parts(37_488_937_000, 512390) - // Standard Error: 467_413 - .saturating_add(Weight::from_parts(8_086_367, 0).saturating_mul(v.into())) - // Standard Error: 467_413 - .saturating_add(Weight::from_parts(3_108_193, 0).saturating_mul(n.into())) + // Minimum execution time: 43_708_472_000 picoseconds. + Weight::from_parts(44_048_436_000, 512390) + // Standard Error: 493_244 + .saturating_add(Weight::from_parts(6_697_278, 0).saturating_mul(v.into())) + // Standard Error: 493_244 + .saturating_add(Weight::from_parts(4_559_779, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(201_u64)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -1457,12 +1479,12 @@ impl WeightInfo for () { /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `979 + v * (50 ±0)` + // Measured: `1012 + v * (50 ±0)` // Estimated: `3510 + v * (2520 ±0)` - // Minimum execution time: 2_641_258_000 picoseconds. - Weight::from_parts(382_882_595, 3510) - // Standard Error: 11_991 - .saturating_add(Weight::from_parts(4_695_820, 0).saturating_mul(v.into())) + // Minimum execution time: 2_917_165_000 picoseconds. + Weight::from_parts(2_948_999_000, 3510) + // Standard Error: 33_372 + .saturating_add(Weight::from_parts(2_126_909, 0).saturating_mul(v.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(v.into()))) .saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into())) @@ -1485,8 +1507,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_753_000 picoseconds. - Weight::from_parts(6_529_000, 0) + // Minimum execution time: 4_748_000 picoseconds. + Weight::from_parts(5_052_000, 0) .saturating_add(RocksDbWeight::get().writes(7_u64)) } /// Storage: `Staking::MinCommission` (r:0 w:1) @@ -1507,8 +1529,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_212_000 picoseconds. - Weight::from_parts(5_451_000, 0) + // Minimum execution time: 4_316_000 picoseconds. + Weight::from_parts(4_526_000, 0) .saturating_add(RocksDbWeight::get().writes(7_u64)) } /// Storage: `Staking::Bonded` (r:1 w:0) @@ -1535,10 +1557,10 @@ impl WeightInfo for () { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill_other() -> Weight { // Proof Size summary in bytes: - // Measured: `1939` + // Measured: `2005` // Estimated: `6248` - // Minimum execution time: 73_000_000 picoseconds. - Weight::from_parts(75_184_000, 6248) + // Minimum execution time: 87_374_000 picoseconds. + Weight::from_parts(89_848_000, 6248) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1548,10 +1570,10 @@ impl WeightInfo for () { /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) fn force_apply_min_commission() -> Weight { // Proof Size summary in bytes: - // Measured: `691` + // Measured: `724` // Estimated: `3510` - // Minimum execution time: 13_056_000 picoseconds. - Weight::from_parts(13_517_000, 3510) + // Minimum execution time: 15_529_000 picoseconds. + Weight::from_parts(16_094_000, 3510) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1561,27 +1583,50 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_201_000 picoseconds. - Weight::from_parts(3_442_000, 0) + // Minimum execution time: 2_533_000 picoseconds. + Weight::from_parts(2_817_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:0) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + fn restore_ledger() -> Weight { + // Proof Size summary in bytes: + // Measured: `1110` + // Estimated: `4764` + // Minimum execution time: 50_105_000 picoseconds. + Weight::from_parts(50_966_000, 4764) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) - fn restore_ledger() -> Weight { + fn migrate_currency() -> Weight { // Proof Size summary in bytes: - // Measured: `1047` + // Measured: `1246` // Estimated: `4764` - // Minimum execution time: 44_671_000 picoseconds. - Weight::from_parts(45_611_000, 4764) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) + // Minimum execution time: 94_054_000 picoseconds. + Weight::from_parts(96_272_000, 4764) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } } \ No newline at end of file diff --git a/substrate/frame/state-trie-migration/Cargo.toml b/substrate/frame/state-trie-migration/Cargo.toml index 8c82bc38da97d82a0e9814b30b7d8a1bb9cfe4ac..1f1f6fc5be3ae5cf152912ccbac64a7685f09ce4 100644 --- a/substrate/frame/state-trie-migration/Cargo.toml +++ b/substrate/frame/state-trie-migration/Cargo.toml @@ -16,25 +16,25 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { optional = true, workspace = true, default-features = true } -thousands = { optional = true, workspace = true } -zstd = { optional = true, workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } remote-externalities = { optional = true, workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } substrate-state-trie-migration-rpc = { optional = true, workspace = true, default-features = true } +thousands = { optional = true, workspace = true } +zstd = { optional = true, workspace = true } [dev-dependencies] -parking_lot = { workspace = true, default-features = true } -tokio = { features = ["macros"], workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } +parking_lot = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } +tokio = { features = ["macros"], workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/state-trie-migration/src/lib.rs b/substrate/frame/state-trie-migration/src/lib.rs index 3fe5abb81031345904b38d1a006a5fa208fb4a84..6e475b7067e16e8d22eddc1861db63aac470d0ce 100644 --- a/substrate/frame/state-trie-migration/src/lib.rs +++ b/substrate/frame/state-trie-migration/src/lib.rs @@ -249,13 +249,13 @@ pub mod pallet { if limits.item.is_zero() || limits.size.is_zero() { // handle this minor edge case, else we would call `migrate_tick` at least once. log!(warn, "limits are zero. stopping"); - return Ok(()) + return Ok(()); } while !self.exhausted(limits) && !self.finished() { if let Err(e) = self.migrate_tick() { log!(error, "migrate_until_exhaustion failed: {:?}", e); - return Err(e) + return Err(e); } } @@ -332,7 +332,7 @@ pub mod pallet { _ => { // defensive: there must be an ongoing top migration. frame_support::defensive!("cannot migrate child key."); - return Ok(()) + return Ok(()); }, }; @@ -374,7 +374,7 @@ pub mod pallet { Progress::Complete => { // defensive: there must be an ongoing top migration. frame_support::defensive!("cannot migrate top key."); - return Ok(()) + return Ok(()); }, }; @@ -669,7 +669,7 @@ pub mod pallet { // ensure that the migration witness data was correct. if real_size_upper < task.dyn_size { Self::slash(who, deposit)?; - return Ok(().into()) + return Ok(().into()); } Self::deposit_event(Event::<T>::Migrated { @@ -957,6 +957,7 @@ pub mod pallet { mod benchmarks { use super::{pallet::Pallet as StateTrieMigration, *}; use alloc::vec; + use frame_benchmarking::v2::*; use frame_support::traits::fungible::{Inspect, Mutate}; // The size of the key seemingly makes no difference in the read/write time, so we make it @@ -970,8 +971,12 @@ mod benchmarks { stash } - frame_benchmarking::benchmarks! { - continue_migrate { + #[benchmarks] + mod inner_benchmarks { + use super::*; + + #[benchmark] + fn continue_migrate() -> Result<(), BenchmarkError> { // note that this benchmark should migrate nothing, as we only want the overhead weight // of the bookkeeping, and the migration cost itself is noted via the `dynamic_weight` // function. @@ -980,116 +985,151 @@ mod benchmarks { let stash = set_balance_for_deposit::<T>(&caller, null.item); // Allow signed migrations. SignedMigrationMaxLimits::<T>::put(MigrationLimits { size: 1024, item: 5 }); - }: _(frame_system::RawOrigin::Signed(caller.clone()), null, 0, StateTrieMigration::<T>::migration_process()) - verify { + + #[extrinsic_call] + _( + frame_system::RawOrigin::Signed(caller.clone()), + null, + 0, + StateTrieMigration::<T>::migration_process(), + ); + assert_eq!(StateTrieMigration::<T>::migration_process(), Default::default()); - assert_eq!(T::Currency::balance(&caller), stash) + assert_eq!(T::Currency::balance(&caller), stash); + + Ok(()) } - continue_migrate_wrong_witness { + #[benchmark] + fn continue_migrate_wrong_witness() -> Result<(), BenchmarkError> { let null = MigrationLimits::default(); let caller = frame_benchmarking::whitelisted_caller(); - let bad_witness = MigrationTask { progress_top: Progress::LastKey(vec![1u8].try_into().unwrap()), ..Default::default() }; - }: { - assert!( - StateTrieMigration::<T>::continue_migrate( + let bad_witness = MigrationTask { + progress_top: Progress::LastKey(vec![1u8].try_into().unwrap()), + ..Default::default() + }; + #[block] + { + assert!(StateTrieMigration::<T>::continue_migrate( frame_system::RawOrigin::Signed(caller).into(), null, 0, bad_witness, ) - .is_err() - ) - } - verify { - assert_eq!(StateTrieMigration::<T>::migration_process(), Default::default()) + .is_err()); + } + + assert_eq!(StateTrieMigration::<T>::migration_process(), Default::default()); + + Ok(()) } - migrate_custom_top_success { + #[benchmark] + fn migrate_custom_top_success() -> Result<(), BenchmarkError> { let null = MigrationLimits::default(); let caller: T::AccountId = frame_benchmarking::whitelisted_caller(); let stash = set_balance_for_deposit::<T>(&caller, null.item); - }: migrate_custom_top(frame_system::RawOrigin::Signed(caller.clone()), Default::default(), 0) - verify { + #[extrinsic_call] + migrate_custom_top( + frame_system::RawOrigin::Signed(caller.clone()), + Default::default(), + 0, + ); + assert_eq!(StateTrieMigration::<T>::migration_process(), Default::default()); - assert_eq!(T::Currency::balance(&caller), stash) + assert_eq!(T::Currency::balance(&caller), stash); + Ok(()) } - migrate_custom_top_fail { + #[benchmark] + fn migrate_custom_top_fail() -> Result<(), BenchmarkError> { let null = MigrationLimits::default(); let caller: T::AccountId = frame_benchmarking::whitelisted_caller(); let stash = set_balance_for_deposit::<T>(&caller, null.item); // for tests, we need to make sure there is _something_ in storage that is being // migrated. - sp_io::storage::set(b"foo", vec![1u8;33].as_ref()); - }: { - assert!( - StateTrieMigration::<T>::migrate_custom_top( + sp_io::storage::set(b"foo", vec![1u8; 33].as_ref()); + #[block] + { + assert!(StateTrieMigration::<T>::migrate_custom_top( frame_system::RawOrigin::Signed(caller.clone()).into(), vec![b"foo".to_vec()], 1, - ).is_ok() - ); + ) + .is_ok()); + + frame_system::Pallet::<T>::assert_last_event( + <T as Config>::RuntimeEvent::from(crate::Event::Slashed { + who: caller.clone(), + amount: StateTrieMigration::<T>::calculate_deposit_for(1u32), + }) + .into(), + ); + } - frame_system::Pallet::<T>::assert_last_event( - <T as Config>::RuntimeEvent::from(crate::Event::Slashed { - who: caller.clone(), - amount: StateTrieMigration::<T>::calculate_deposit_for(1u32), - }).into(), - ); - } - verify { assert_eq!(StateTrieMigration::<T>::migration_process(), Default::default()); // must have gotten slashed - assert!(T::Currency::balance(&caller) < stash) + assert!(T::Currency::balance(&caller) < stash); + + Ok(()) } - migrate_custom_child_success { + #[benchmark] + fn migrate_custom_child_success() -> Result<(), BenchmarkError> { let caller: T::AccountId = frame_benchmarking::whitelisted_caller(); let stash = set_balance_for_deposit::<T>(&caller, 0); - }: migrate_custom_child( - frame_system::RawOrigin::Signed(caller.clone()), - StateTrieMigration::<T>::childify(Default::default()), - Default::default(), - 0 - ) - verify { + + #[extrinsic_call] + migrate_custom_child( + frame_system::RawOrigin::Signed(caller.clone()), + StateTrieMigration::<T>::childify(Default::default()), + Default::default(), + 0, + ); + assert_eq!(StateTrieMigration::<T>::migration_process(), Default::default()); assert_eq!(T::Currency::balance(&caller), stash); + + Ok(()) } - migrate_custom_child_fail { + #[benchmark] + fn migrate_custom_child_fail() -> Result<(), BenchmarkError> { let caller: T::AccountId = frame_benchmarking::whitelisted_caller(); let stash = set_balance_for_deposit::<T>(&caller, 1); // for tests, we need to make sure there is _something_ in storage that is being // migrated. - sp_io::default_child_storage::set(b"top", b"foo", vec![1u8;33].as_ref()); - }: { - assert!( - StateTrieMigration::<T>::migrate_custom_child( + sp_io::default_child_storage::set(b"top", b"foo", vec![1u8; 33].as_ref()); + + #[block] + { + assert!(StateTrieMigration::<T>::migrate_custom_child( frame_system::RawOrigin::Signed(caller.clone()).into(), StateTrieMigration::<T>::childify("top"), vec![b"foo".to_vec()], 1, - ).is_ok() - ) - } - verify { + ) + .is_ok()); + } assert_eq!(StateTrieMigration::<T>::migration_process(), Default::default()); // must have gotten slashed - assert!(T::Currency::balance(&caller) < stash) + assert!(T::Currency::balance(&caller) < stash); + Ok(()) } - process_top_key { - let v in 1 .. (4 * 1024 * 1024); - + #[benchmark] + fn process_top_key(v: Linear<1, { 4 * 1024 * 1024 }>) -> Result<(), BenchmarkError> { let value = alloc::vec![1u8; v as usize]; sp_io::storage::set(KEY, &value); - }: { - let data = sp_io::storage::get(KEY).unwrap(); - sp_io::storage::set(KEY, &data); - let _next = sp_io::storage::next_key(KEY); - assert_eq!(data, value); + #[block] + { + let data = sp_io::storage::get(KEY).unwrap(); + sp_io::storage::set(KEY, &data); + let _next = sp_io::storage::next_key(KEY); + assert_eq!(data, value); + } + + Ok(()) } impl_benchmark_test_suite!( @@ -1257,9 +1297,12 @@ mod mock { frame_system::GenesisConfig::<Test>::default() .assimilate_storage(&mut custom_storage) .unwrap(); - pallet_balances::GenesisConfig::<Test> { balances: vec![(1, 1000)] } - .assimilate_storage(&mut custom_storage) - .unwrap(); + pallet_balances::GenesisConfig::<Test> { + balances: vec![(1, 1000)], + ..Default::default() + } + .assimilate_storage(&mut custom_storage) + .unwrap(); } sp_tracing::try_init_simple(); @@ -1269,16 +1312,17 @@ mod mock { pub(crate) fn run_to_block(n: u32) -> (H256, Weight) { let mut root = Default::default(); let mut weight_sum = Weight::zero(); + log::trace!(target: LOG_TARGET, "running from {:?} to {:?}", System::block_number(), n); - while System::block_number() < n { - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - weight_sum += StateTrieMigration::on_initialize(System::block_number()); + System::run_to_block_with::<AllPalletsWithSystem>( + n, + frame_system::RunToBlockHooks::default().after_initialize(|bn| { + weight_sum += StateTrieMigration::on_initialize(bn); + root = *System::finalize().state_root(); + }), + ); - root = *System::finalize().state_root(); - System::on_finalize(System::block_number()); - } (root, weight_sum) } } @@ -1741,7 +1785,7 @@ pub(crate) mod remote_tests { let ((finished, weight), proof) = ext.execute_and_prove(|| { let weight = run_to_block::<Runtime>(now + One::one()).1; if StateTrieMigration::<Runtime>::migration_process().finished() { - return (true, weight) + return (true, weight); } duration += One::one(); now += One::one(); @@ -1768,7 +1812,7 @@ pub(crate) mod remote_tests { ext.commit_all().unwrap(); if finished { - break + break; } } diff --git a/substrate/frame/statement/Cargo.toml b/substrate/frame/statement/Cargo.toml index e601881cd7202bc49fd7dbda74f52b292008213c..b1449fa244162894218e1106b1546abf430c78a1 100644 --- a/substrate/frame/statement/Cargo.toml +++ b/substrate/frame/statement/Cargo.toml @@ -16,15 +16,15 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -sp-statement-store = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-api = { workspace = true } -sp-runtime = { workspace = true } -sp-io = { workspace = true } sp-core = { workspace = true } -log = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-statement-store = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } diff --git a/substrate/frame/statement/src/mock.rs b/substrate/frame/statement/src/mock.rs index 34afd332c083ddb4baefd367349a9e37f806838f..db9d19dbbab7378e57fff6c023cbfae49bc272c7 100644 --- a/substrate/frame/statement/src/mock.rs +++ b/substrate/frame/statement/src/mock.rs @@ -82,6 +82,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { 500000, ), ], + ..Default::default() }; balances.assimilate_storage(&mut t).unwrap(); t.into() diff --git a/substrate/frame/sudo/Cargo.toml b/substrate/frame/sudo/Cargo.toml index 9b362019b29bc6890d44543f75f8f531311d085a..e2096bf0668add3abb8227a5c89e83449f3eea65 100644 --- a/substrate/frame/sudo/Cargo.toml +++ b/substrate/frame/sudo/Cargo.toml @@ -18,9 +18,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/support/Cargo.toml b/substrate/frame/support/Cargo.toml index d7da034b3492aabdee969e211a0d505c92c96c63..1f4fdd5d46cd99f590745c97679ac85fb2c621dd 100644 --- a/substrate/frame/support/Cargo.toml +++ b/substrate/frame/support/Cargo.toml @@ -18,60 +18,59 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = { workspace = true } binary-merkle-tree.workspace = true -serde = { features = ["alloc", "derive"], workspace = true } +bitflags = { workspace = true } codec = { features = [ "derive", "max-encoded-len", ], workspace = true } -scale-info = { features = [ - "derive", -], workspace = true } +docify = { workspace = true } +environmental = { workspace = true } frame-metadata = { features = [ "current", + "unstable", +], workspace = true } +frame-support-procedural = { workspace = true } +impl-trait-for-tuples = { workspace = true } +k256 = { features = ["ecdsa"], workspace = true } +log = { workspace = true } +macro_magic = { workspace = true } +paste = { workspace = true, default-features = true } +scale-info = { features = [ + "derive", ], workspace = true } +serde = { features = ["alloc", "derive"], workspace = true } +serde_json = { features = ["alloc"], workspace = true } +smallvec = { workspace = true, default-features = true } sp-api = { features = [ "frame-metadata", ], workspace = true } -sp-std = { workspace = true } -sp-io = { workspace = true } -sp-runtime = { features = [ - "serde", -], workspace = true } -sp-tracing = { workspace = true } -sp-core = { workspace = true } sp-arithmetic = { workspace = true } -sp-inherents = { workspace = true } -sp-staking = { workspace = true } -sp-weights = { workspace = true } +sp-core = { workspace = true } +sp-crypto-hashing-proc-macro = { workspace = true, default-features = true } sp-debug-derive = { workspace = true } +sp-genesis-builder = { workspace = true } +sp-inherents = { workspace = true } +sp-io = { workspace = true } sp-metadata-ir = { workspace = true } -sp-trie = { workspace = true } -tt-call = { workspace = true } -macro_magic = { workspace = true } -frame-support-procedural = { workspace = true } -paste = { workspace = true, default-features = true } +sp-runtime = { features = ["serde"], workspace = true } +sp-staking = { workspace = true } sp-state-machine = { optional = true, workspace = true } -bitflags = { workspace = true } -impl-trait-for-tuples = { workspace = true } -smallvec = { workspace = true, default-features = true } -log = { workspace = true } -sp-crypto-hashing-proc-macro = { workspace = true, default-features = true } -k256 = { features = ["ecdsa"], workspace = true } -environmental = { workspace = true } -sp-genesis-builder = { workspace = true } -serde_json = { features = ["alloc"], workspace = true } -docify = { workspace = true } +sp-std = { workspace = true } +sp-tracing = { workspace = true } +sp-trie = { workspace = true } +sp-weights = { workspace = true } static_assertions = { workspace = true, default-features = true } +tt-call = { workspace = true } aquamarine = { workspace = true } [dev-dependencies] +Inflector = { workspace = true } assert_matches = { workspace = true } -pretty_assertions = { workspace = true } -sp-timestamp = { workspace = true } frame-system = { workspace = true, default-features = true } +pretty_assertions = { workspace = true } sp-crypto-hashing = { workspace = true, default-features = true } -Inflector = { workspace = true } +sp-timestamp = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/support/procedural/Cargo.toml b/substrate/frame/support/procedural/Cargo.toml index 51790062b2c2a8cfd181f7b05563073b81329ceb..624562187617dc901e85dadcea5358ecdfeb8ab6 100644 --- a/substrate/frame/support/procedural/Cargo.toml +++ b/substrate/frame/support/procedural/Cargo.toml @@ -18,36 +18,36 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -derive-syn-parse = { workspace = true } -docify = { workspace = true } Inflector = { workspace = true } cfg-expr = { workspace = true } -itertools = { workspace = true } -proc-macro2 = { workspace = true } -quote = { workspace = true } -syn = { features = ["full", "parsing", "visit-mut"], workspace = true } +derive-syn-parse = { workspace = true } +docify = { workspace = true } +expander = { workspace = true } frame-support-procedural-tools = { workspace = true, default-features = true } +itertools = { workspace = true } macro_magic = { features = ["proc_support"], workspace = true } proc-macro-warning = { workspace = true } -expander = { workspace = true } +proc-macro2 = { workspace = true } +quote = { workspace = true } sp-crypto-hashing = { workspace = true } +syn = { features = ["full", "parsing", "visit-mut"], workspace = true } [dev-dependencies] codec = { features = [ "derive", "max-encoded-len", ], workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +pretty_assertions = { workspace = true } regex = { workspace = true } -sp-metadata-ir = { workspace = true } scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } +sp-metadata-ir = { workspace = true } sp-runtime = { features = [ "serde", ], workspace = true } -frame-system = { workspace = true } -frame-support = { workspace = true } -pretty_assertions = { workspace = true } static_assertions = { workspace = true } [features] diff --git a/substrate/frame/support/procedural/examples/proc_main/main.rs b/substrate/frame/support/procedural/examples/proc_main/main.rs index 4bdfc76dd92f0821865fbe7a74fbf0bea77f35e3..946bd5ff03ed298824ec7edd0ba31c8885d5df0f 100644 --- a/substrate/frame/support/procedural/examples/proc_main/main.rs +++ b/substrate/frame/support/procedural/examples/proc_main/main.rs @@ -234,7 +234,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/substrate/frame/support/procedural/examples/proc_main/runtime.rs b/substrate/frame/support/procedural/examples/proc_main/runtime.rs index 109ca4f6dc488228cc596c2d3ff2447aa4cacff4..8de560555895bf64b3e21d8c0e892b77d6e77d4a 100644 --- a/substrate/frame/support/procedural/examples/proc_main/runtime.rs +++ b/substrate/frame/support/procedural/examples/proc_main/runtime.rs @@ -99,7 +99,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs index f055e8ce28e904639dba11f9f7639bf64c934d95..411d74ecbb3d23c84e5b62c14e10a1b047e49248 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; -use std::str::FromStr; use syn::Ident; pub fn expand_outer_dispatch( @@ -40,15 +39,7 @@ pub fn expand_outer_dispatch( let name = &pallet_declaration.name; let path = &pallet_declaration.path; let index = pallet_declaration.index; - let attr = - pallet_declaration.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet_declaration.get_attributes(); variant_defs.extend(quote! { #attr diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs index dbbe6ba6e6c32ec09a8514033108617883075243..7a51ba6ecf1da9c9f9af5abe83f85fee59323995 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs @@ -19,7 +19,6 @@ use crate::construct_runtime::Pallet; use inflector::Inflector; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; -use std::str::FromStr; use syn::Ident; pub fn expand_outer_config( @@ -41,14 +40,7 @@ pub fn expand_outer_config( let field_name = &Ident::new(&pallet_name.to_string().to_snake_case(), decl.name.span()); let part_is_generic = !pallet_entry.generics.params.is_empty(); - let attr = &decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = &decl.get_attributes(); types.extend(expand_config_types(attr, runtime, decl, &config, part_is_generic)); fields.extend(quote!(#attr pub #field_name: #config,)); diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs index e34c6ac5016a9f8597fae03f6012f1e7d2f79659..e25492802c3293d02321b2b1fcc0f26aabe8dce4 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; -use std::str::FromStr; use syn::Ident; pub fn expand_outer_inherent( @@ -36,14 +35,7 @@ pub fn expand_outer_inherent( if pallet_decl.exists_part("Inherent") { let name = &pallet_decl.name; let path = &pallet_decl.path; - let attr = pallet_decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet_decl.get_attributes(); pallet_names.push(name); pallet_attrs.push(attr); diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs index c12fc20bc8b89e05c4aa8d1f291d81b094a0acbc..d246c00628640d8dfd27a133a2c06637b3953eb8 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::{parse::PalletPath, Pallet}; use proc_macro2::TokenStream; use quote::quote; -use std::str::FromStr; use syn::Ident; pub fn expand_runtime_metadata( @@ -51,14 +50,7 @@ pub fn expand_runtime_metadata( let errors = expand_pallet_metadata_errors(runtime, decl); let associated_types = expand_pallet_metadata_associated_types(runtime, decl); let docs = expand_pallet_metadata_docs(runtime, decl); - let attr = decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = decl.get_attributes(); let deprecation_info = expand_pallet_metadata_deprecation(runtime, decl); quote! { #attr @@ -78,6 +70,20 @@ pub fn expand_runtime_metadata( }) .collect::<Vec<_>>(); + let view_functions = pallet_declarations.iter().map(|decl| { + let name = &decl.name; + let path = &decl.path; + let instance = decl.instance.as_ref().into_iter(); + let attr = decl.get_attributes(); + + quote! { + #attr + #path::Pallet::<#runtime #(, #path::#instance)*>::pallet_view_functions_metadata( + ::core::stringify!(#name) + ) + } + }); + quote! { impl #runtime { fn metadata_ir() -> #scrate::__private::metadata_ir::MetadataIR { @@ -113,11 +119,13 @@ pub fn expand_runtime_metadata( <#extrinsic as #scrate::traits::SignedTransactionBuilder>::Extension >(); + use #scrate::__private::metadata_ir::InternalImplRuntimeApis; + #scrate::__private::metadata_ir::MetadataIR { pallets: #scrate::__private::vec![ #(#pallets),* ], extrinsic: #scrate::__private::metadata_ir::ExtrinsicMetadataIR { ty, - version: <#extrinsic as #scrate::sp_runtime::traits::ExtrinsicMetadata>::VERSION, + versions: <#extrinsic as #scrate::sp_runtime::traits::ExtrinsicMetadata>::VERSIONS.into_iter().map(|ref_version| *ref_version).collect(), address_ty, call_ty, signature_ty, @@ -147,6 +155,10 @@ pub fn expand_runtime_metadata( >(), event_enum_ty: #scrate::__private::scale_info::meta_type::<RuntimeEvent>(), error_enum_ty: #scrate::__private::scale_info::meta_type::<RuntimeError>(), + }, + view_functions: #scrate::__private::metadata_ir::RuntimeViewFunctionsIR { + ty: #scrate::__private::scale_info::meta_type::<RuntimeViewFunction>(), + groups: #scrate::__private::sp_std::vec![ #(#view_functions),* ], } } } diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs index 88f9a3c6e33fd3fc99b2f4e511d5a6c0afd9263a..823aa69dbdf2b1a4d725f8b33c28a231a1699977 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs @@ -28,6 +28,7 @@ mod outer_enums; mod slash_reason; mod task; mod unsigned; +mod view_function; pub use call::expand_outer_dispatch; pub use config::expand_outer_config; @@ -41,3 +42,4 @@ pub use outer_enums::{expand_outer_enum, OuterEnumType}; pub use slash_reason::expand_outer_slash_reason; pub use task::expand_outer_task; pub use unsigned::expand_outer_validate_unsigned; +pub use view_function::expand_outer_query; diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs index 1c4ab436ad92aaee824a7f2916c32483fe443f6f..aada9f7af75b73a0d4b8166e0cbb47f899f8045e 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::{Pallet, SYSTEM_PALLET_NAME}; use proc_macro2::TokenStream; use quote::quote; -use std::str::FromStr; use syn::{Generics, Ident}; pub fn expand_outer_origin( @@ -210,6 +209,7 @@ pub fn expand_outer_origin( system(#system_path::Origin<#runtime>), #caller_variants #[allow(dead_code)] + #[codec(skip)] Void(#scrate::__private::Void) } @@ -335,14 +335,7 @@ fn expand_origin_caller_variant( let part_is_generic = !generics.params.is_empty(); let variant_name = &pallet.name; let path = &pallet.path; - let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet.get_attributes(); match instance { Some(inst) if part_is_generic => quote! { @@ -387,14 +380,7 @@ fn expand_origin_pallet_conversions( }; let doc_string = get_intra_doc_string(" Convert to runtime origin using", &path.module_name()); - let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet.get_attributes(); quote! { #attr diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs index 80b242ccbe493607a59e672b7f0958fc9d0a213c..80d3a5af26627f9cdcdb58cd1ef6645c523dbf86 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::Pallet; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; -use std::str::FromStr; use syn::{Generics, Ident}; /// Represents the types supported for creating an outer enum. @@ -185,14 +184,7 @@ fn expand_enum_variant( let path = &pallet.path; let variant_name = &pallet.name; let part_is_generic = !generics.params.is_empty(); - let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet.get_attributes(); match instance { Some(inst) if part_is_generic => quote! { @@ -224,14 +216,7 @@ fn expand_enum_conversion( enum_name_ident: &Ident, ) -> TokenStream { let variant_name = &pallet.name; - let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet.get_attributes(); quote! { #attr diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs index 1302f86455f2ceeb7af58e46e30b05afccbc44cf..b9b8efb8c006364dc48e25926c158664066a9d65 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs @@ -16,7 +16,6 @@ // limitations under the License use crate::construct_runtime::Pallet; -use core::str::FromStr; use proc_macro2::{Ident, TokenStream as TokenStream2}; use quote::quote; @@ -42,14 +41,7 @@ pub fn expand_outer_task( let instance = decl.instance.as_ref().map(|instance| quote!(, #path::#instance)); let task_type = quote!(#path::Task<#runtime_name #instance>); - let attr = decl.cfg_pattern.iter().fold(TokenStream2::new(), |acc, pattern| { - let attr = TokenStream2::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = decl.get_attributes(); from_impls.push(quote! { #attr diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs index 33aadba0d1f1c522aae13fb647b9fe8a918d8e24..737a39ea681e0d1c09c458c4b59229f5159136d4 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; -use std::str::FromStr; use syn::Ident; pub fn expand_outer_validate_unsigned( @@ -34,14 +33,7 @@ pub fn expand_outer_validate_unsigned( if pallet_decl.exists_part("ValidateUnsigned") { let name = &pallet_decl.name; let path = &pallet_decl.path; - let attr = pallet_decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet_decl.get_attributes(); pallet_names.push(name); pallet_attrs.push(attr); diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/view_function.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/view_function.rs new file mode 100644 index 0000000000000000000000000000000000000000..094dcca4a5b5249cfe3026790d9938a84f5649fe --- /dev/null +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/view_function.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 crate::construct_runtime::Pallet; +use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; + +/// Expands implementation of runtime level `DispatchViewFunction`. +pub fn expand_outer_query( + runtime_name: &Ident, + pallet_decls: &[Pallet], + scrate: &TokenStream2, +) -> TokenStream2 { + let runtime_view_function = syn::Ident::new("RuntimeViewFunction", Span::call_site()); + + let prefix_conditionals = pallet_decls.iter().map(|pallet| { + let pallet_name = &pallet.name; + let attr = pallet.get_attributes(); + quote::quote! { + #attr + if id.prefix == <#pallet_name as #scrate::view_functions::ViewFunctionIdPrefix>::prefix() { + return <#pallet_name as #scrate::view_functions::DispatchViewFunction>::dispatch_view_function(id, input, output) + } + } + }); + + quote::quote! { + /// Runtime query type. + #[derive( + Clone, PartialEq, Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::scale_info::TypeInfo, + #scrate::__private::RuntimeDebug, + )] + pub enum #runtime_view_function {} + + const _: () = { + impl #scrate::view_functions::DispatchViewFunction for #runtime_view_function { + fn dispatch_view_function<O: #scrate::__private::codec::Output>( + id: & #scrate::view_functions::ViewFunctionId, + input: &mut &[u8], + output: &mut O + ) -> Result<(), #scrate::view_functions::ViewFunctionDispatchError> + { + #( #prefix_conditionals )* + Err(#scrate::view_functions::ViewFunctionDispatchError::NotFound(id.clone())) + } + } + + impl #runtime_name { + /// Convenience function for query execution from the runtime API. + pub fn execute_view_function( + id: #scrate::view_functions::ViewFunctionId, + input: #scrate::__private::Vec<::core::primitive::u8>, + ) -> Result<#scrate::__private::Vec<::core::primitive::u8>, #scrate::view_functions::ViewFunctionDispatchError> + { + let mut output = #scrate::__private::vec![]; + <#runtime_view_function as #scrate::view_functions::DispatchViewFunction>::dispatch_view_function(&id, &mut &input[..], &mut output)?; + Ok(output) + } + } + }; + } +} diff --git a/substrate/frame/support/procedural/src/construct_runtime/mod.rs b/substrate/frame/support/procedural/src/construct_runtime/mod.rs index 17042c2487803eb102ac1c4db4470b9ee232d766..c6018e048f2f8c15acdc5264a52c35df52c6aa7a 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/mod.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/mod.rs @@ -400,6 +400,7 @@ fn construct_runtime_final_expansion( let dispatch = expand::expand_outer_dispatch(&name, system_pallet, &pallets, &scrate); let tasks = expand::expand_outer_task(&name, &pallets, &scrate); + let query = expand::expand_outer_query(&name, &pallets, &scrate); let metadata = expand::expand_runtime_metadata( &name, &pallets, @@ -466,7 +467,6 @@ fn construct_runtime_final_expansion( // Therefore, the `Deref` trait will resolve the `runtime_metadata` from `impl_runtime_apis!` // when both macros are called; and will resolve an empty `runtime_metadata` when only the `construct_runtime!` // is called. - #[doc(hidden)] trait InternalConstructRuntime { #[inline(always)] @@ -477,6 +477,8 @@ fn construct_runtime_final_expansion( #[doc(hidden)] impl InternalConstructRuntime for &#name {} + use #scrate::__private::metadata_ir::InternalImplRuntimeApis; + #outer_event #outer_error @@ -491,6 +493,8 @@ fn construct_runtime_final_expansion( #tasks + #query + #metadata #outer_config @@ -649,16 +653,7 @@ pub(crate) fn decl_pallet_runtime_setup( .collect::<Vec<_>>(); let pallet_attrs = pallet_declarations .iter() - .map(|pallet| { - pallet.cfg_pattern.iter().fold(TokenStream2::new(), |acc, pattern| { - let attr = TokenStream2::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }) - }) + .map(|pallet| pallet.get_attributes()) .collect::<Vec<_>>(); quote!( diff --git a/substrate/frame/support/procedural/src/construct_runtime/parse.rs b/substrate/frame/support/procedural/src/construct_runtime/parse.rs index 729a803a302ed7451f890ab6a81b666379d68d07..2df08123821a3972d5053aeb0ce9e6a7e3477a79 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/parse.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/parse.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use core::str::FromStr; use frame_support_procedural_tools::syn_ext as ext; use proc_macro2::{Span, TokenStream}; use quote::ToTokens; @@ -609,6 +610,18 @@ impl Pallet { pub fn exists_part(&self, name: &str) -> bool { self.find_part(name).is_some() } + + // Get runtime attributes for the pallet, mostly used for macros + pub fn get_attributes(&self) -> TokenStream { + self.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote::quote! { + #acc + #attr + } + }) + } } /// Result of a conversion of a declaration of pallets. diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index c2f546d92048ac2bc259138b27ac5820227fefd6..26703a2438ef9ad368c3718bc497c9563bb407fe 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -817,6 +817,7 @@ pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream { if item.ident != "RuntimeCall" && item.ident != "RuntimeEvent" && item.ident != "RuntimeTask" && + item.ident != "RuntimeViewFunction" && item.ident != "RuntimeOrigin" && item.ident != "RuntimeHoldReason" && item.ident != "RuntimeFreezeReason" && @@ -826,7 +827,7 @@ pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream { return syn::Error::new_spanned( item, "`#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, \ - `RuntimeTask`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo`", + `RuntimeTask`, `RuntimeViewFunction`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo`", ) .to_compile_error() .into(); diff --git a/substrate/frame/support/procedural/src/pallet/expand/config.rs b/substrate/frame/support/procedural/src/pallet/expand/config.rs index 0a583f1359bac9dcd7682e3c8c6a4de4eaca9ba9..d39f276723600fe7ff5ca36306800bee87c68766 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/config.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/config.rs @@ -126,7 +126,7 @@ pub fn expand_config_metadata(def: &Def) -> proc_macro2::TokenStream { ty: #frame_support::__private::scale_info::meta_type::< <T as Config #trait_use_gen>::#ident >(), - docs: #frame_support::__private::sp_std::vec![ #( #doc ),* ], + docs: #frame_support::__private::vec![ #( #doc ),* ], } }) }); @@ -136,9 +136,9 @@ pub fn expand_config_metadata(def: &Def) -> proc_macro2::TokenStream { #[doc(hidden)] pub fn pallet_associated_types_metadata() - -> #frame_support::__private::sp_std::vec::Vec<#frame_support::__private::metadata_ir::PalletAssociatedTypeMetadataIR> + -> #frame_support::__private::vec::Vec<#frame_support::__private::metadata_ir::PalletAssociatedTypeMetadataIR> { - #frame_support::__private::sp_std::vec![ #( #types ),* ] + #frame_support::__private::vec![ #( #types ),* ] } } ) diff --git a/substrate/frame/support/procedural/src/pallet/expand/mod.rs b/substrate/frame/support/procedural/src/pallet/expand/mod.rs index 3f9b50f79c0ccf8ea37e23cd79d1bd1aead2064f..439ec55e269d43aaab8ec91296c48df970ca6985 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/mod.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/mod.rs @@ -35,6 +35,7 @@ mod tasks; mod tt_default_parts; mod type_value; mod validate_unsigned; +mod view_functions; mod warnings; use crate::pallet::Def; @@ -66,6 +67,7 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream { let error = error::expand_error(&mut def); let event = event::expand_event(&mut def); let storages = storage::expand_storages(&mut def); + let view_functions = view_functions::expand_view_functions(&def); let inherents = inherent::expand_inherents(&mut def); let instances = instances::expand_instances(&mut def); let hooks = hooks::expand_hooks(&mut def); @@ -108,6 +110,7 @@ storage item. Otherwise, all storage items are listed among [*Type Definitions*] #error #event #storages + #view_functions #inherents #instances #hooks diff --git a/substrate/frame/support/procedural/src/pallet/expand/view_functions.rs b/substrate/frame/support/procedural/src/pallet/expand/view_functions.rs new file mode 100644 index 0000000000000000000000000000000000000000..587e74a2ac182f2dc817db9bb6058e2c93611bc2 --- /dev/null +++ b/substrate/frame/support/procedural/src/pallet/expand/view_functions.rs @@ -0,0 +1,263 @@ +// 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::pallet::{parse::view_functions::ViewFunctionDef, Def}; +use proc_macro2::{Span, TokenStream}; +use syn::spanned::Spanned; + +pub fn expand_view_functions(def: &Def) -> TokenStream { + let (span, where_clause, view_fns, docs) = match def.view_functions.as_ref() { + Some(view_fns) => ( + view_fns.attr_span, + view_fns.where_clause.clone(), + view_fns.view_functions.clone(), + view_fns.docs.clone(), + ), + None => (def.item.span(), def.config.where_clause.clone(), Vec::new(), Vec::new()), + }; + + let view_function_prefix_impl = + expand_view_function_prefix_impl(def, span, where_clause.as_ref()); + + let view_fn_impls = view_fns + .iter() + .map(|view_fn| expand_view_function(def, span, where_clause.as_ref(), view_fn)); + let impl_dispatch_view_function = + impl_dispatch_view_function(def, span, where_clause.as_ref(), &view_fns); + let impl_view_function_metadata = + impl_view_function_metadata(def, span, where_clause.as_ref(), &view_fns, &docs); + + quote::quote! { + #view_function_prefix_impl + #( #view_fn_impls )* + #impl_dispatch_view_function + #impl_view_function_metadata + } +} + +fn expand_view_function_prefix_impl( + def: &Def, + span: Span, + where_clause: Option<&syn::WhereClause>, +) -> TokenStream { + let pallet_ident = &def.pallet_struct.pallet; + let frame_support = &def.frame_support; + let frame_system = &def.frame_system; + let type_impl_gen = &def.type_impl_generics(span); + let type_use_gen = &def.type_use_generics(span); + + quote::quote! { + impl<#type_impl_gen> #frame_support::view_functions::ViewFunctionIdPrefix for #pallet_ident<#type_use_gen> #where_clause { + fn prefix() -> [::core::primitive::u8; 16usize] { + < + <T as #frame_system::Config>::PalletInfo + as #frame_support::traits::PalletInfo + >::name_hash::<Pallet<#type_use_gen>>() + .expect("No name_hash found for the pallet in the runtime! This usually means that the pallet wasn't added to `construct_runtime!`.") + } + } + } +} + +fn expand_view_function( + def: &Def, + span: Span, + where_clause: Option<&syn::WhereClause>, + view_fn: &ViewFunctionDef, +) -> TokenStream { + let frame_support = &def.frame_support; + let pallet_ident = &def.pallet_struct.pallet; + let type_impl_gen = &def.type_impl_generics(span); + let type_decl_bounded_gen = &def.type_decl_bounded_generics(span); + let type_use_gen = &def.type_use_generics(span); + let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" }; + + let view_function_struct_ident = view_fn.view_function_struct_ident(); + let view_fn_name = &view_fn.name; + let (arg_names, arg_types) = match view_fn.args_names_types() { + Ok((arg_names, arg_types)) => (arg_names, arg_types), + Err(e) => return e.into_compile_error(), + }; + let return_type = &view_fn.return_type; + let docs = &view_fn.docs; + + let view_function_id_suffix_bytes_raw = match view_fn.view_function_id_suffix_bytes() { + Ok(view_function_id_suffix_bytes_raw) => view_function_id_suffix_bytes_raw, + Err(e) => return e.into_compile_error(), + }; + let view_function_id_suffix_bytes = view_function_id_suffix_bytes_raw + .map(|byte| syn::LitInt::new(&format!("0x{:X}_u8", byte), Span::call_site())); + + quote::quote! { + #( #[doc = #docs] )* + #[allow(missing_docs)] + #[derive( + #frame_support::RuntimeDebugNoBound, + #frame_support::CloneNoBound, + #frame_support::EqNoBound, + #frame_support::PartialEqNoBound, + #frame_support::__private::codec::Encode, + #frame_support::__private::codec::Decode, + #frame_support::__private::scale_info::TypeInfo, + )] + #[codec(encode_bound())] + #[codec(decode_bound())] + #[scale_info(skip_type_params(#type_use_gen), capture_docs = #capture_docs)] + pub struct #view_function_struct_ident<#type_decl_bounded_gen> #where_clause { + #( + pub #arg_names: #arg_types, + )* + _marker: ::core::marker::PhantomData<(#type_use_gen,)>, + } + + impl<#type_impl_gen> #view_function_struct_ident<#type_use_gen> #where_clause { + /// Create a new [`#view_function_struct_ident`] instance. + pub fn new(#( #arg_names: #arg_types, )*) -> Self { + Self { + #( #arg_names, )* + _marker: ::core::default::Default::default() + } + } + } + + impl<#type_impl_gen> #frame_support::view_functions::ViewFunctionIdSuffix for #view_function_struct_ident<#type_use_gen> #where_clause { + const SUFFIX: [::core::primitive::u8; 16usize] = [ #( #view_function_id_suffix_bytes ),* ]; + } + + impl<#type_impl_gen> #frame_support::view_functions::ViewFunction for #view_function_struct_ident<#type_use_gen> #where_clause { + fn id() -> #frame_support::view_functions::ViewFunctionId { + #frame_support::view_functions::ViewFunctionId { + prefix: <#pallet_ident<#type_use_gen> as #frame_support::view_functions::ViewFunctionIdPrefix>::prefix(), + suffix: <Self as #frame_support::view_functions::ViewFunctionIdSuffix>::SUFFIX, + } + } + + type ReturnType = #return_type; + + fn invoke(self) -> Self::ReturnType { + let Self { #( #arg_names, )* _marker } = self; + #pallet_ident::<#type_use_gen> :: #view_fn_name( #( #arg_names, )* ) + } + } + } +} + +fn impl_dispatch_view_function( + def: &Def, + span: Span, + where_clause: Option<&syn::WhereClause>, + view_fns: &[ViewFunctionDef], +) -> TokenStream { + let frame_support = &def.frame_support; + let pallet_ident = &def.pallet_struct.pallet; + let type_impl_gen = &def.type_impl_generics(span); + let type_use_gen = &def.type_use_generics(span); + + let query_match_arms = view_fns.iter().map(|view_fn| { + let view_function_struct_ident = view_fn.view_function_struct_ident(); + quote::quote! { + <#view_function_struct_ident<#type_use_gen> as #frame_support::view_functions::ViewFunctionIdSuffix>::SUFFIX => { + <#view_function_struct_ident<#type_use_gen> as #frame_support::view_functions::ViewFunction>::execute(input, output) + } + } + }); + + quote::quote! { + impl<#type_impl_gen> #frame_support::view_functions::DispatchViewFunction + for #pallet_ident<#type_use_gen> #where_clause + { + #[deny(unreachable_patterns)] + fn dispatch_view_function<O: #frame_support::__private::codec::Output>( + id: & #frame_support::view_functions::ViewFunctionId, + input: &mut &[u8], + output: &mut O + ) -> Result<(), #frame_support::view_functions::ViewFunctionDispatchError> + { + match id.suffix { + #( #query_match_arms )* + _ => Err(#frame_support::view_functions::ViewFunctionDispatchError::NotFound(id.clone())), + } + } + } + } +} + +fn impl_view_function_metadata( + def: &Def, + span: Span, + where_clause: Option<&syn::WhereClause>, + view_fns: &[ViewFunctionDef], + docs: &[syn::Expr], +) -> TokenStream { + let frame_support = &def.frame_support; + let pallet_ident = &def.pallet_struct.pallet; + let type_impl_gen = &def.type_impl_generics(span); + let type_use_gen = &def.type_use_generics(span); + + let view_functions = view_fns.iter().map(|view_fn| { + let view_function_struct_ident = view_fn.view_function_struct_ident(); + let name = &view_fn.name; + let args = view_fn.args.iter().filter_map(|fn_arg| { + match fn_arg { + syn::FnArg::Receiver(_) => None, + syn::FnArg::Typed(typed) => { + let pat = &typed.pat; + let ty = &typed.ty; + Some(quote::quote! { + #frame_support::__private::metadata_ir::ViewFunctionArgMetadataIR { + name: ::core::stringify!(#pat), + ty: #frame_support::__private::scale_info::meta_type::<#ty>(), + } + }) + } + } + }); + + let no_docs = vec![]; + let doc = if cfg!(feature = "no-metadata-docs") { &no_docs } else { &view_fn.docs }; + + quote::quote! { + #frame_support::__private::metadata_ir::ViewFunctionMetadataIR { + name: ::core::stringify!(#name), + id: <#view_function_struct_ident<#type_use_gen> as #frame_support::view_functions::ViewFunction>::id().into(), + args: #frame_support::__private::sp_std::vec![ #( #args ),* ], + output: #frame_support::__private::scale_info::meta_type::< + <#view_function_struct_ident<#type_use_gen> as #frame_support::view_functions::ViewFunction>::ReturnType + >(), + docs: #frame_support::__private::sp_std::vec![ #( #doc ),* ], + } + } + }); + + let no_docs = vec![]; + let doc = if cfg!(feature = "no-metadata-docs") { &no_docs } else { docs }; + + quote::quote! { + impl<#type_impl_gen> #pallet_ident<#type_use_gen> #where_clause { + #[doc(hidden)] + pub fn pallet_view_functions_metadata(name: &'static ::core::primitive::str) + -> #frame_support::__private::metadata_ir::ViewFunctionGroupIR + { + #frame_support::__private::metadata_ir::ViewFunctionGroupIR { + name, + view_functions: #frame_support::__private::sp_std::vec![ #( #view_functions ),* ], + docs: #frame_support::__private::sp_std::vec![ #( #doc ),* ], + } + } + } + } +} diff --git a/substrate/frame/support/procedural/src/pallet/parse/mod.rs b/substrate/frame/support/procedural/src/pallet/parse/mod.rs index c9a150effccbee6ecf7ebb308837556828c1c071..89875974b8b5d023c161600a96518ad65cb855fd 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/mod.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/mod.rs @@ -36,6 +36,7 @@ pub mod storage; pub mod tasks; pub mod type_value; pub mod validate_unsigned; +pub mod view_functions; #[cfg(test)] pub mod tests; @@ -70,6 +71,7 @@ pub struct Def { pub frame_system: syn::Path, pub frame_support: syn::Path, pub dev_mode: bool, + pub view_functions: Option<view_functions::ViewFunctionsImplDef>, } impl Def { @@ -103,6 +105,7 @@ impl Def { let mut storages = vec![]; let mut type_values = vec![]; let mut composites: Vec<CompositeDef> = vec![]; + let mut view_functions = None; for (index, item) in items.iter_mut().enumerate() { let pallet_attr: Option<PalletAttr> = helper::take_first_item_pallet_attr(item)?; @@ -205,6 +208,9 @@ impl Def { } composites.push(composite); }, + Some(PalletAttr::ViewFunctions(span)) => { + view_functions = Some(view_functions::ViewFunctionsImplDef::try_from(span, item)?); + } Some(attr) => { let msg = "Invalid duplicated attribute"; return Err(syn::Error::new(attr.span(), msg)) @@ -250,6 +256,7 @@ impl Def { frame_system, frame_support, dev_mode, + view_functions, }; def.check_instance_usage()?; @@ -563,6 +570,7 @@ mod keyword { syn::custom_keyword!(pallet); syn::custom_keyword!(extra_constants); syn::custom_keyword!(composite_enum); + syn::custom_keyword!(view_functions_experimental); } /// The possible values for the `#[pallet::config]` attribute. @@ -652,6 +660,7 @@ enum PalletAttr { TypeValue(proc_macro2::Span), ExtraConstants(proc_macro2::Span), Composite(proc_macro2::Span), + ViewFunctions(proc_macro2::Span), } impl PalletAttr { @@ -677,6 +686,7 @@ impl PalletAttr { Self::TypeValue(span) => *span, Self::ExtraConstants(span) => *span, Self::Composite(span) => *span, + Self::ViewFunctions(span) => *span, } } } @@ -778,6 +788,10 @@ impl syn::parse::Parse for PalletAttr { Ok(PalletAttr::ExtraConstants(content.parse::<keyword::extra_constants>()?.span())) } else if lookahead.peek(keyword::composite_enum) { Ok(PalletAttr::Composite(content.parse::<keyword::composite_enum>()?.span())) + } else if lookahead.peek(keyword::view_functions_experimental) { + Ok(PalletAttr::ViewFunctions( + content.parse::<keyword::view_functions_experimental>()?.span(), + )) } else { Err(lookahead.error()) } diff --git a/substrate/frame/support/procedural/src/pallet/parse/view_functions.rs b/substrate/frame/support/procedural/src/pallet/parse/view_functions.rs new file mode 100644 index 0000000000000000000000000000000000000000..766bcb13da8b3cbddc164a8fdd2a2ecab066f6d4 --- /dev/null +++ b/substrate/frame/support/procedural/src/pallet/parse/view_functions.rs @@ -0,0 +1,155 @@ +// 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 governsing permissions and +// limitations under the License. + +use frame_support_procedural_tools::get_doc_literals; +use inflector::Inflector; +use syn::spanned::Spanned; + +/// Parsed representation of an impl block annotated with `pallet::view_functions_experimental`. +pub struct ViewFunctionsImplDef { + /// The where_clause used. + pub where_clause: Option<syn::WhereClause>, + /// The span of the pallet::view_functions_experimental attribute. + pub attr_span: proc_macro2::Span, + /// Docs, specified on the impl Block. + pub docs: Vec<syn::Expr>, + /// The view function definitions. + pub view_functions: Vec<ViewFunctionDef>, +} + +impl ViewFunctionsImplDef { + pub fn try_from(attr_span: proc_macro2::Span, item: &mut syn::Item) -> syn::Result<Self> { + let syn::Item::Impl(item_impl) = item else { + return Err(syn::Error::new( + item.span(), + "Invalid pallet::view_functions_experimental, expected item impl", + )) + }; + let mut view_functions = Vec::new(); + for item in &mut item_impl.items { + if let syn::ImplItem::Fn(method) = item { + if !matches!(method.vis, syn::Visibility::Public(_)) { + let msg = "Invalid pallet::view_functions_experimental, view function must be public: \ + `pub fn`"; + + let span = match method.vis { + syn::Visibility::Inherited => method.sig.span(), + _ => method.vis.span(), + }; + + return Err(syn::Error::new(span, msg)) + } + + let view_fn_def = ViewFunctionDef::try_from(method.clone())?; + view_functions.push(view_fn_def) + } else { + return Err(syn::Error::new( + item.span(), + "Invalid pallet::view_functions_experimental, expected a function", + )) + } + } + Ok(Self { + view_functions, + attr_span, + where_clause: item_impl.generics.where_clause.clone(), + docs: get_doc_literals(&item_impl.attrs), + }) + } +} + +/// Parsed representation of a view function definition. +#[derive(Clone)] +pub struct ViewFunctionDef { + pub name: syn::Ident, + pub docs: Vec<syn::Expr>, + pub args: Vec<syn::FnArg>, + pub return_type: syn::Type, +} + +impl TryFrom<syn::ImplItemFn> for ViewFunctionDef { + type Error = syn::Error; + fn try_from(method: syn::ImplItemFn) -> Result<Self, Self::Error> { + let syn::ReturnType::Type(_, type_) = method.sig.output else { + return Err(syn::Error::new(method.sig.span(), "view functions must return a value")) + }; + + Ok(Self { + name: method.sig.ident.clone(), + docs: get_doc_literals(&method.attrs), + args: method.sig.inputs.iter().cloned().collect::<Vec<_>>(), + return_type: *type_.clone(), + }) + } +} + +impl ViewFunctionDef { + pub fn view_function_struct_ident(&self) -> syn::Ident { + syn::Ident::new( + &format!("{}ViewFunction", self.name.to_string().to_pascal_case()), + self.name.span(), + ) + } + + pub fn view_function_id_suffix_bytes(&self) -> Result<[u8; 16], syn::Error> { + let mut output = [0u8; 16]; + + // concatenate the signature string + let arg_types = self + .args_names_types()? + .1 + .iter() + .map(|ty| quote::quote!(#ty).to_string().replace(" ", "")) + .collect::<Vec<_>>() + .join(","); + let return_type = &self.return_type; + let return_type = quote::quote!(#return_type).to_string().replace(" ", ""); + let view_fn_signature = format!( + "{view_function_name}({arg_types}) -> {return_type}", + view_function_name = &self.name, + ); + + // hash the signature string + let hash = sp_crypto_hashing::twox_128(view_fn_signature.as_bytes()); + output.copy_from_slice(&hash[..]); + Ok(output) + } + + pub fn args_names_types(&self) -> Result<(Vec<syn::Ident>, Vec<syn::Type>), syn::Error> { + Ok(self + .args + .iter() + .map(|arg| { + let syn::FnArg::Typed(pat_type) = arg else { + return Err(syn::Error::new( + arg.span(), + "Unsupported argument in view function", + )); + }; + let syn::Pat::Ident(ident) = &*pat_type.pat else { + return Err(syn::Error::new( + pat_type.pat.span(), + "Unsupported pattern in view function argument", + )); + }; + Ok((ident.ident.clone(), *pat_type.ty.clone())) + }) + .collect::<Result<Vec<(syn::Ident, syn::Type)>, syn::Error>>()? + .into_iter() + .unzip()) + } +} diff --git a/substrate/frame/support/procedural/src/runtime/expand/mod.rs b/substrate/frame/support/procedural/src/runtime/expand/mod.rs index 666bc03aa415df6b495cd479e62cb929f8767cd8..005b109c0eb5fe2a1c79f44f664d2dad47a5bf84 100644 --- a/substrate/frame/support/procedural/src/runtime/expand/mod.rs +++ b/substrate/frame/support/procedural/src/runtime/expand/mod.rs @@ -182,6 +182,7 @@ fn construct_runtime_final_expansion( let mut slash_reason = None; let mut lock_id = None; let mut task = None; + let mut query = None; for runtime_type in runtime_types.iter() { match runtime_type { @@ -224,6 +225,9 @@ fn construct_runtime_final_expansion( RuntimeType::RuntimeTask(_) => { task = Some(expand::expand_outer_task(&name, &pallets, &scrate)); }, + RuntimeType::RuntimeViewFunction(_) => { + query = Some(expand::expand_outer_query(&name, &pallets, &scrate)); + }, } } @@ -301,6 +305,8 @@ fn construct_runtime_final_expansion( #task + #query + #metadata #outer_config diff --git a/substrate/frame/support/procedural/src/runtime/parse/pallet.rs b/substrate/frame/support/procedural/src/runtime/parse/pallet.rs index 52f57cd2cd8b66fd574a48663ebc5bd832b642e5..1397b7266a18a3296b326b085e8b84245a719c59 100644 --- a/substrate/frame/support/procedural/src/runtime/parse/pallet.rs +++ b/substrate/frame/support/procedural/src/runtime/parse/pallet.rs @@ -21,7 +21,7 @@ use crate::{ }; use frame_support_procedural_tools::get_doc_literals; use quote::ToTokens; -use syn::{punctuated::Punctuated, token, Error}; +use syn::{punctuated::Punctuated, spanned::Spanned, token, Error}; impl Pallet { pub fn try_from( @@ -78,7 +78,18 @@ impl Pallet { }) .collect(); - let cfg_pattern = vec![]; + let cfg_pattern = item + .attrs + .iter() + .filter(|attr| attr.path().segments.first().map_or(false, |s| s.ident == "cfg")) + .map(|attr| { + attr.parse_args_with(|input: syn::parse::ParseStream| { + let input = input.parse::<proc_macro2::TokenStream>()?; + cfg_expr::Expression::parse(&input.to_string()) + .map_err(|e| syn::Error::new(attr.span(), e.to_string())) + }) + }) + .collect::<syn::Result<Vec<_>>>()?; let docs = get_doc_literals(&item.attrs); diff --git a/substrate/frame/support/procedural/src/runtime/parse/runtime_types.rs b/substrate/frame/support/procedural/src/runtime/parse/runtime_types.rs index a4480e2a1fd32622bea3a7f20294b4c2bee88309..9a385146a811e85211aa0bf6791154ac76276687 100644 --- a/substrate/frame/support/procedural/src/runtime/parse/runtime_types.rs +++ b/substrate/frame/support/procedural/src/runtime/parse/runtime_types.rs @@ -32,6 +32,7 @@ mod keyword { custom_keyword!(RuntimeSlashReason); custom_keyword!(RuntimeLockId); custom_keyword!(RuntimeTask); + custom_keyword!(RuntimeViewFunction); } #[derive(Debug, Clone, PartialEq)] @@ -45,6 +46,7 @@ pub enum RuntimeType { RuntimeSlashReason(keyword::RuntimeSlashReason), RuntimeLockId(keyword::RuntimeLockId), RuntimeTask(keyword::RuntimeTask), + RuntimeViewFunction(keyword::RuntimeViewFunction), } impl Parse for RuntimeType { @@ -69,6 +71,8 @@ impl Parse for RuntimeType { Ok(Self::RuntimeLockId(input.parse()?)) } else if lookahead.peek(keyword::RuntimeTask) { Ok(Self::RuntimeTask(input.parse()?)) + } else if lookahead.peek(keyword::RuntimeViewFunction) { + Ok(Self::RuntimeViewFunction(input.parse()?)) } else { Err(lookahead.error()) } diff --git a/substrate/frame/support/procedural/tools/Cargo.toml b/substrate/frame/support/procedural/tools/Cargo.toml index e61e17e8ac75cbd36a4aebebb7490fb2e0f4cc6e..cbb2fde9e816afe536bfa70ea2a4f08e42e65261 100644 --- a/substrate/frame/support/procedural/tools/Cargo.toml +++ b/substrate/frame/support/procedural/tools/Cargo.toml @@ -15,8 +15,8 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +frame-support-procedural-tools-derive = { workspace = true, default-features = true } proc-macro-crate = { workspace = true } proc-macro2 = { workspace = true } quote = { workspace = true } syn = { features = ["extra-traits", "full", "visit"], workspace = true } -frame-support-procedural-tools-derive = { workspace = true, default-features = true } diff --git a/substrate/frame/support/src/dispatch.rs b/substrate/frame/support/src/dispatch.rs index 483a3dce77f6a8b510f9c36b7d74805103ae8d8a..14bc2667def179cc0d403287acca811939824bbe 100644 --- a/substrate/frame/support/src/dispatch.rs +++ b/substrate/frame/support/src/dispatch.rs @@ -308,6 +308,17 @@ impl PostDispatchInfo { /// Calculate how much weight was actually spent by the `Dispatchable`. pub fn calc_actual_weight(&self, info: &DispatchInfo) -> Weight { if let Some(actual_weight) = self.actual_weight { + let info_total_weight = info.total_weight(); + if actual_weight.any_gt(info_total_weight) { + log::error!( + target: crate::LOG_TARGET, + "Post dispatch weight is greater than pre dispatch weight. \ + Pre dispatch weight may underestimating the actual weight. \ + Greater post dispatch weight components are ignored. + Pre dispatch weight: {info_total_weight:?}, + Post dispatch weight: {actual_weight:?}", + ); + } actual_weight.min(info.total_weight()) } else { info.total_weight() diff --git a/substrate/frame/support/src/dispatch_context.rs b/substrate/frame/support/src/dispatch_context.rs index b34c6bdada3d484b00745dd02154af849c124c0a..42776e71cb883db750ade8ce5a692e0f84b37e9a 100644 --- a/substrate/frame/support/src/dispatch_context.rs +++ b/substrate/frame/support/src/dispatch_context.rs @@ -140,7 +140,7 @@ impl<T> Value<'_, T> { /// Runs the given `callback` in the dispatch context and gives access to some user defined value. /// -/// Passes the a mutable reference of [`Value`] to the callback. The value will be of type `T` and +/// Passes a mutable reference of [`Value`] to the callback. The value will be of type `T` and /// is identified using the [`TypeId`] of `T`. This means that `T` should be some unique type to /// make the value unique. If no value is set yet [`Value::get()`] and [`Value::get_mut()`] will /// return `None`. It is totally valid to have some `T` that is shared between different callers to diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index c64987b17d35c3994d8eb97fe6c841c09cbf7ea8..97d16e2a06d23349fe4ea653be73428dde976a68 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -44,6 +44,7 @@ pub mod __private { pub use alloc::{ boxed::Box, rc::Rc, + string::String, vec, vec::{IntoIter, Vec}, }; @@ -86,6 +87,7 @@ pub mod storage; #[cfg(test)] mod tests; pub mod traits; +pub mod view_functions; pub mod weights; #[doc(hidden)] pub mod unsigned { @@ -502,9 +504,9 @@ macro_rules! runtime_print { ($($arg:tt)+) => { { use core::fmt::Write; - let mut w = $crate::__private::sp_std::Writer::default(); - let _ = core::write!(&mut w, $($arg)+); - $crate::__private::sp_io::misc::print_utf8(&w.inner()) + let mut msg = $crate::__private::String::default(); + let _ = core::write!(&mut msg, $($arg)+); + $crate::__private::sp_io::misc::print_utf8(msg.as_bytes()) } } } diff --git a/substrate/frame/support/src/storage/child.rs b/substrate/frame/support/src/storage/child.rs index 5ebba269365851b488b8af4eacc3d498c874c2b2..7109e9213b0f6dc46c7164c28868695404e1bb49 100644 --- a/substrate/frame/support/src/storage/child.rs +++ b/substrate/frame/support/src/storage/child.rs @@ -163,7 +163,7 @@ pub fn kill_storage(child_info: &ChildInfo, limit: Option<u32>) -> KillStorageRe /// operating on the same prefix should pass `Some` and this value should be equal to the /// previous call result's `maybe_cursor` field. The only exception to this is when you can /// guarantee that the subsequent call is in a new block; in this case the previous call's result -/// cursor need not be passed in an a `None` may be passed instead. This exception may be useful +/// cursor need not be passed in and a `None` may be passed instead. This exception may be useful /// then making this call solely from a block-hook such as `on_initialize`. /// Returns [`MultiRemovalResults`] to inform about the result. Once the resultant `maybe_cursor` diff --git a/substrate/frame/support/src/storage/unhashed.rs b/substrate/frame/support/src/storage/unhashed.rs index 7f9bc93d7d818ff5f025617e721c3f79587347cb..495c50caa2d6c121331112cd9a96d8c02f69a69b 100644 --- a/substrate/frame/support/src/storage/unhashed.rs +++ b/substrate/frame/support/src/storage/unhashed.rs @@ -124,7 +124,7 @@ pub fn kill_prefix(prefix: &[u8], limit: Option<u32>) -> sp_io::KillStorageResul /// operating on the same prefix should pass `Some` and this value should be equal to the /// previous call result's `maybe_cursor` field. The only exception to this is when you can /// guarantee that the subsequent call is in a new block; in this case the previous call's result -/// cursor need not be passed in an a `None` may be passed instead. This exception may be useful +/// cursor need not be passed in and a `None` may be passed instead. This exception may be useful /// then making this call solely from a block-hook such as `on_initialize`. /// /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once the diff --git a/substrate/frame/support/src/tests/mod.rs b/substrate/frame/support/src/tests/mod.rs index 7c90a12d4167e376841cb0c5a683d56d36559c5c..b10e719b9ac36caaa7e283de1c9e415d00c9edef 100644 --- a/substrate/frame/support/src/tests/mod.rs +++ b/substrate/frame/support/src/tests/mod.rs @@ -237,7 +237,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index 728426cc84c71087015cea70dd5a23a50d49c982..4a83c809a6a5ece39a9818f7111c92e6e4b7b100 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -96,8 +96,9 @@ mod storage; #[cfg(feature = "experimental")] pub use storage::MaybeConsideration; pub use storage::{ - Consideration, Footprint, Incrementable, Instance, LinearStoragePrice, PartialStorageInfoTrait, - StorageInfo, StorageInfoTrait, StorageInstance, TrackedStorageKey, WhitelistedStorageKeys, + Consideration, ConstantStoragePrice, Footprint, Incrementable, Instance, LinearStoragePrice, + PartialStorageInfoTrait, StorageInfo, StorageInfoTrait, StorageInstance, TrackedStorageKey, + WhitelistedStorageKeys, }; mod dispatch; diff --git a/substrate/frame/support/src/traits/hooks.rs b/substrate/frame/support/src/traits/hooks.rs index 012a74d0ae92f1dcc9c3797f62d2ffc9d137485c..51209cb542467822c51465b6fe4c779aedaf4246 100644 --- a/substrate/frame/support/src/traits/hooks.rs +++ b/substrate/frame/support/src/traits/hooks.rs @@ -584,6 +584,10 @@ pub trait BuildGenesisConfig: sp_runtime::traits::MaybeSerializeDeserialize { fn build(&self); } +impl BuildGenesisConfig for () { + fn build(&self) {} +} + /// A trait to define the build function of a genesis config, T and I are placeholder for pallet /// trait and pallet instance. #[deprecated( diff --git a/substrate/frame/support/src/traits/misc.rs b/substrate/frame/support/src/traits/misc.rs index 0dc3abdce956c1653e3bdc081585e2e6549ace5d..9fef4383ad67c5930db3d299889bd59a8f0336d1 100644 --- a/substrate/frame/support/src/traits/misc.rs +++ b/substrate/frame/support/src/traits/misc.rs @@ -66,7 +66,7 @@ impl<T: VariantCount> Get<u32> for VariantCountOf<T> { #[macro_export] macro_rules! defensive { () => { - frame_support::__private::log::error!( + $crate::__private::log::error!( target: "runtime::defensive", "{}", $crate::traits::DEFENSIVE_OP_PUBLIC_ERROR @@ -74,7 +74,7 @@ macro_rules! defensive { debug_assert!(false, "{}", $crate::traits::DEFENSIVE_OP_INTERNAL_ERROR); }; ($error:expr $(,)?) => { - frame_support::__private::log::error!( + $crate::__private::log::error!( target: "runtime::defensive", "{}: {:?}", $crate::traits::DEFENSIVE_OP_PUBLIC_ERROR, @@ -83,7 +83,7 @@ macro_rules! defensive { debug_assert!(false, "{}: {:?}", $crate::traits::DEFENSIVE_OP_INTERNAL_ERROR, $error); }; ($error:expr, $proof:expr $(,)?) => { - frame_support::__private::log::error!( + $crate::__private::log::error!( target: "runtime::defensive", "{}: {:?}: {:?}", $crate::traits::DEFENSIVE_OP_PUBLIC_ERROR, diff --git a/substrate/frame/support/src/traits/preimages.rs b/substrate/frame/support/src/traits/preimages.rs index 80020d8d00809af048334d8614e8d7f8953c6525..6e46a7489654ee61b76bf3da219fcafec4333acd 100644 --- a/substrate/frame/support/src/traits/preimages.rs +++ b/substrate/frame/support/src/traits/preimages.rs @@ -38,7 +38,7 @@ pub enum Bounded<T, H: Hash> { /// for transitioning from legacy state. In the future we will make this a pure /// `Dummy` item storing only the final `dummy` field. Legacy { hash: H::Output, dummy: core::marker::PhantomData<T> }, - /// A an bounded `Call`. Its encoding must be at most 128 bytes. + /// A bounded `Call`. Its encoding must be at most 128 bytes. Inline(BoundedInline), /// A hash of the call together with an upper limit for its size.` Lookup { hash: H::Output, len: u32 }, diff --git a/substrate/frame/support/src/traits/storage.rs b/substrate/frame/support/src/traits/storage.rs index 2b8e437073894cc03634bbd950313f541b59fe76..676b73e03d3c4472ebdb1ab024b6c9accb430ac1 100644 --- a/substrate/frame/support/src/traits/storage.rs +++ b/substrate/frame/support/src/traits/storage.rs @@ -200,6 +200,18 @@ where } } +/// Constant `Price` regardless of the given [`Footprint`]. +pub struct ConstantStoragePrice<Price, Balance>(PhantomData<(Price, Balance)>); +impl<Price, Balance> Convert<Footprint, Balance> for ConstantStoragePrice<Price, Balance> +where + Price: Get<Balance>, + Balance: From<u64> + sp_runtime::Saturating, +{ + fn convert(_: Footprint) -> Balance { + Price::get() + } +} + /// Some sort of cost taken from account temporarily in order to offset the cost to the chain of /// holding some data [`Footprint`] in state. /// diff --git a/substrate/frame/support/src/view_functions.rs b/substrate/frame/support/src/view_functions.rs new file mode 100644 index 0000000000000000000000000000000000000000..dd23fad94a4fd578bdc3d63f86bcb1a1750fe064 --- /dev/null +++ b/substrate/frame/support/src/view_functions.rs @@ -0,0 +1,128 @@ +// 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 fsor the specific language governing permissions and +// limitations under the License. + +//! Traits for querying pallet view functions. + +use alloc::vec::Vec; +use codec::{Decode, DecodeAll, Encode, Output}; +use scale_info::TypeInfo; +use sp_runtime::RuntimeDebug; + +/// The unique identifier for a view function. +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ViewFunctionId { + /// The part of the id for dispatching view functions from the top level of the runtime. + /// + /// Specifies which view function grouping this view function belongs to. This could be a group + /// of view functions associated with a pallet, or a pallet agnostic group of view functions. + pub prefix: [u8; 16], + /// The part of the id for dispatching to a view function within a group. + pub suffix: [u8; 16], +} + +impl From<ViewFunctionId> for [u8; 32] { + fn from(value: ViewFunctionId) -> Self { + let mut output = [0u8; 32]; + output[..16].copy_from_slice(&value.prefix); + output[16..].copy_from_slice(&value.suffix); + output + } +} + +/// Error type for view function dispatching. +#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum ViewFunctionDispatchError { + /// View functions are not implemented for this runtime. + NotImplemented, + /// A view function with the given `ViewFunctionId` was not found + NotFound(ViewFunctionId), + /// Failed to decode the view function input. + Codec, +} + +impl From<codec::Error> for ViewFunctionDispatchError { + fn from(_: codec::Error) -> Self { + ViewFunctionDispatchError::Codec + } +} + +/// Implemented by both pallets and the runtime. The runtime is dispatching by prefix using the +/// pallet implementation of `ViewFunctionIdPrefix` then the pallet is dispatching by suffix using +/// the methods implementation of `ViewFunctionIdSuffix`. +pub trait DispatchViewFunction { + fn dispatch_view_function<O: Output>( + id: &ViewFunctionId, + input: &mut &[u8], + output: &mut O, + ) -> Result<(), ViewFunctionDispatchError>; +} + +impl DispatchViewFunction for () { + fn dispatch_view_function<O: Output>( + _id: &ViewFunctionId, + _input: &mut &[u8], + _output: &mut O, + ) -> Result<(), ViewFunctionDispatchError> { + Err(ViewFunctionDispatchError::NotImplemented) + } +} + +/// Automatically implemented for each pallet by the macro [`pallet`](crate::pallet). +pub trait ViewFunctionIdPrefix { + fn prefix() -> [u8; 16]; +} + +/// Automatically implemented for each pallet view function method by the macro +/// [`pallet`](crate::pallet). +pub trait ViewFunctionIdSuffix { + const SUFFIX: [u8; 16]; +} + +/// Automatically implemented for each pallet view function method by the macro +/// [`pallet`](crate::pallet). +pub trait ViewFunction: DecodeAll { + fn id() -> ViewFunctionId; + type ReturnType: Encode; + + fn invoke(self) -> Self::ReturnType; + + fn execute<O: Output>( + input: &mut &[u8], + output: &mut O, + ) -> Result<(), ViewFunctionDispatchError> { + let view_function = Self::decode_all(input)?; + let result = view_function.invoke(); + Encode::encode_to(&result, output); + Ok(()) + } +} + +pub mod runtime_api { + use super::*; + + sp_api::decl_runtime_apis! { + #[api_version(1)] + /// Runtime API for executing view functions + pub trait RuntimeViewFunction { + /// Execute a view function query. + fn execute_view_function( + query_id: ViewFunctionId, + input: Vec<u8>, + ) -> Result<Vec<u8>, ViewFunctionDispatchError>; + } + } +} diff --git a/substrate/frame/support/test/Cargo.toml b/substrate/frame/support/test/Cargo.toml index 2187ee22b395c39d2becf8070355e54387f6ef82..ca122e6bd5446bc0d45e526fd7ba588a9ceda9be 100644 --- a/substrate/frame/support/test/Cargo.toml +++ b/substrate/frame/support/test/Cargo.toml @@ -15,26 +15,26 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -static_assertions = { workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true } codec = { features = ["derive"], workspace = true } +frame-benchmarking = { workspace = true } +frame-executive = { workspace = true } +frame-metadata = { features = ["current", "unstable"], workspace = true } +frame-support = { features = ["experimental"], workspace = true } +frame-system = { workspace = true } +pretty_assertions = { workspace = true } +rustversion = { workspace = true } scale-info = { features = ["derive"], workspace = true } -frame-metadata = { features = ["current"], workspace = true } +serde = { features = ["derive"], workspace = true } sp-api = { workspace = true } sp-arithmetic = { workspace = true } +sp-core = { workspace = true } sp-io = { workspace = true } -sp-state-machine = { optional = true, workspace = true, default-features = true } -frame-support = { features = ["experimental"], workspace = true } -frame-benchmarking = { workspace = true } +sp-metadata-ir = { workspace = true } sp-runtime = { workspace = true } -sp-core = { workspace = true } +sp-state-machine = { optional = true, workspace = true, default-features = true } sp-version = { workspace = true } -sp-metadata-ir = { workspace = true } +static_assertions = { workspace = true, default-features = true } trybuild = { features = ["diff"], workspace = true } -pretty_assertions = { workspace = true } -rustversion = { workspace = true } -frame-system = { workspace = true } -frame-executive = { workspace = true } # The "std" feature for this pallet is never activated on purpose, in order to test construct_runtime error message test-pallet = { workspace = true } diff --git a/substrate/frame/support/test/compile_pass/Cargo.toml b/substrate/frame/support/test/compile_pass/Cargo.toml index 9e0a7ff7c6756fa39b92d8601198610a940a9f6e..988135d64dbf6ea5405343d22299c25dc3de1024 100644 --- a/substrate/frame/support/test/compile_pass/Cargo.toml +++ b/substrate/frame/support/test/compile_pass/Cargo.toml @@ -16,9 +16,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } sp-version = { workspace = true } diff --git a/substrate/frame/support/test/pallet/Cargo.toml b/substrate/frame/support/test/pallet/Cargo.toml index f03377dc21eb4166c0575d1dd010275061cc7590..dc5558b1d4b8e45ae421eedfbfcd8fe3af4ceeff 100644 --- a/substrate/frame/support/test/pallet/Cargo.toml +++ b/substrate/frame/support/test/pallet/Cargo.toml @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } [features] diff --git a/substrate/frame/support/test/stg_frame_crate/Cargo.toml b/substrate/frame/support/test/stg_frame_crate/Cargo.toml index f627d29cd563067cdad9b48e4dc5a7ac9bdefbd8..157361dbd5d6dc1df062ebd2e4632da830b7362b 100644 --- a/substrate/frame/support/test/stg_frame_crate/Cargo.toml +++ b/substrate/frame/support/test/stg_frame_crate/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -frame = { features = ["experimental", "runtime"], workspace = true } +frame = { features = ["runtime"], workspace = true } scale-info = { features = ["derive"], workspace = true } [features] diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr index 726b09cf54c997b04ffa17a6256cae25e4c17886..faa9cb558c262f09111a488a4e2f4bf114a74375 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr @@ -561,6 +561,15 @@ note: the trait `Config` must be implemented | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:26:3 + | +26 | System: frame_system::{Pallet, Call, Storage, Config<T>, Event<T>}, + | ^^^^^^ the trait `Config` is not implemented for `Runtime`, which is required by `Pallet<Runtime>: ViewFunctionIdPrefix` + | + = help: the trait `ViewFunctionIdPrefix` is implemented for `Pallet<T>` + = note: required for `Pallet<Runtime>` to implement `ViewFunctionIdPrefix` + error[E0599]: the function or associated item `storage_metadata` exists for struct `Pallet<Runtime>`, but its trait bounds were not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | @@ -736,6 +745,31 @@ note: the trait `Config` must be implemented | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) +error[E0599]: the function or associated item `pallet_view_functions_metadata` exists for struct `Pallet<Runtime>`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ function or associated item cannot be called on `Pallet<Runtime>` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | diff --git a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr index c7159b34afb3d22737fb5d8ed6662027f04a3bd6..aafc6b5a2c874a7a1ba219fa4b442d5da6114bb3 100644 --- a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr +++ b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr @@ -1,4 +1,4 @@ -error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeTask`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo` +error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeTask`, `RuntimeViewFunction`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo` --> tests/derive_impl_ui/inject_runtime_type_invalid.rs:32:5 | 32 | type RuntimeInfo = (); diff --git a/substrate/frame/support/test/tests/enum_deprecation.rs b/substrate/frame/support/test/tests/enum_deprecation.rs index c1167dfe339ce0a013c0d9e7cb742a8161ddab75..72b14dad962917ce0ea0fe21530aaa66cf769bb7 100644 --- a/substrate/frame/support/test/tests/enum_deprecation.rs +++ b/substrate/frame/support/test/tests/enum_deprecation.rs @@ -85,6 +85,7 @@ pub mod pallet { T::AccountId: SomeAssociation1 + From<SomeType1>, { #[deprecated = "second"] + #[codec(index = 1)] A, #[deprecated = "first"] #[codec(index = 0)] @@ -157,20 +158,13 @@ fn pallet_metadata() { // Example pallet events are partially and fully deprecated let meta = example.event.unwrap(); assert_eq!( - // Result should be this, but instead we get the result below - // see: https://github.com/paritytech/parity-scale-codec/issues/507 - // - // DeprecationInfoIR::VariantsDeprecated(BTreeMap::from([ - // (codec::Compact(0), DeprecationStatusIR::Deprecated { note: "first", since: None - // }), ( - // codec::Compact(1), - // DeprecationStatusIR::Deprecated { note: "second", since: None } - // ) - // ])), - DeprecationInfoIR::VariantsDeprecated(BTreeMap::from([( - codec::Compact(0), - DeprecationStatusIR::Deprecated { note: "first", since: None } - ),])), + DeprecationInfoIR::VariantsDeprecated(BTreeMap::from([ + (codec::Compact(0), DeprecationStatusIR::Deprecated { note: "first", since: None }), + ( + codec::Compact(1), + DeprecationStatusIR::Deprecated { note: "second", since: None } + ) + ])), meta.deprecation_info ); } diff --git a/substrate/frame/support/test/tests/pallet.rs b/substrate/frame/support/test/tests/pallet.rs index b0b83f7724997d2f8d0e489d761d7dc523d63f5a..e45ff64e4c26eda45147fb9d65b688914735bfca 100644 --- a/substrate/frame/support/test/tests/pallet.rs +++ b/substrate/frame/support/test/tests/pallet.rs @@ -53,6 +53,9 @@ parameter_types! { /// Latest stable metadata version used for testing. const LATEST_METADATA_VERSION: u32 = 15; +/// Unstable metadata version. +const UNSTABLE_METADATA_VERSION: u32 = u32::MAX; + pub struct SomeType1; impl From<SomeType1> for u64 { fn from(_t: SomeType1) -> Self { @@ -458,6 +461,22 @@ pub mod pallet { _myfield: u32, } + #[pallet::view_functions_experimental] + impl<T: Config> Pallet<T> + where + T::AccountId: From<SomeType1> + SomeAssociation1, + { + /// Query value no args. + pub fn get_value() -> Option<u32> { + Value::<T>::get() + } + + /// Query value with args. + pub fn get_value_with_arg(key: u16) -> Option<u32> { + Map2::<T>::get(key) + } + } + #[pallet::genesis_build] impl<T: Config> BuildGenesisConfig for GenesisConfig<T> where @@ -799,20 +818,44 @@ where } } -frame_support::construct_runtime!( - pub struct Runtime { - // Exclude part `Storage` in order not to check its metadata in tests. - System: frame_system exclude_parts { Pallet, Storage }, - Example: pallet, - Example2: pallet2 exclude_parts { Call }, - #[cfg(feature = "frame-feature-testing")] - Example3: pallet3, - Example4: pallet4 use_parts { Call }, +#[frame_support::runtime] +mod runtime { + #[runtime::runtime] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeFreezeReason, + RuntimeHoldReason, + RuntimeSlashReason, + RuntimeLockId, + RuntimeTask, + RuntimeViewFunction + )] + pub struct Runtime; - #[cfg(feature = "frame-feature-testing-2")] - Example5: pallet5, - } -); + #[runtime::pallet_index(0)] + pub type System = frame_system + Call + Event<T>; + + #[runtime::pallet_index(1)] + pub type Example = pallet; + + #[runtime::pallet_index(2)] + #[runtime::disable_call] + pub type Example2 = pallet2; + + #[cfg(feature = "frame-feature-testing")] + #[runtime::pallet_index(3)] + pub type Example3 = pallet3; + + #[runtime::pallet_index(4)] + pub type Example4 = pallet4; + + #[cfg(feature = "frame-feature-testing-2")] + #[runtime::pallet_index(5)] + pub type Example5 = pallet5; +} // Test that the part `RuntimeCall` is excluded from Example2 and included in Example4. fn _ensure_call_is_correctly_excluded_and_included(call: RuntimeCall) { @@ -1847,6 +1890,16 @@ fn metadata() { error: None, docs: vec![" Test that the supertrait check works when we pass some parameter to the `frame_system::Config`."], }, + PalletMetadata { + index: 4, + name: "Example4", + storage: None, + calls: Some(meta_type::<pallet4::Call<Runtime>>().into()), + event: None, + constants: vec![], + error: None, + docs: vec![], + }, #[cfg(feature = "frame-feature-testing-2")] PalletMetadata { index: 5, @@ -1944,7 +1997,10 @@ fn metadata_at_version() { #[test] fn metadata_versions() { - assert_eq!(vec![14, LATEST_METADATA_VERSION], Runtime::metadata_versions()); + assert_eq!( + vec![14, LATEST_METADATA_VERSION, UNSTABLE_METADATA_VERSION], + Runtime::metadata_versions() + ); } #[test] diff --git a/substrate/frame/support/test/tests/runtime.rs b/substrate/frame/support/test/tests/runtime.rs index 5335e08837e4adb2735fd3beecd21f8023be7778..cbcdf8d27b39a6e3f48f7cbef5e02c350d56a943 100644 --- a/substrate/frame/support/test/tests/runtime.rs +++ b/substrate/frame/support/test/tests/runtime.rs @@ -296,7 +296,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/substrate/frame/support/test/tests/runtime_legacy_ordering.rs b/substrate/frame/support/test/tests/runtime_legacy_ordering.rs index 7b92073a82b1a789ca2b33296893fcb8d62a3a62..1594356ad8fe846b329818634c2a235fd2cae77e 100644 --- a/substrate/frame/support/test/tests/runtime_legacy_ordering.rs +++ b/substrate/frame/support/test/tests/runtime_legacy_ordering.rs @@ -296,7 +296,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/substrate/frame/support/test/tests/runtime_metadata.rs b/substrate/frame/support/test/tests/runtime_metadata.rs index 7523a415d458e4f03c704972cca50d03fdf8fb93..a098643abb911a0560e82a31a91ff9d2b6dc4f3e 100644 --- a/substrate/frame/support/test/tests/runtime_metadata.rs +++ b/substrate/frame/support/test/tests/runtime_metadata.rs @@ -80,34 +80,39 @@ sp_api::decl_runtime_apis! { } } -sp_api::impl_runtime_apis! { - impl self::Api<Block> for Runtime { - fn test(_data: u64) { - unimplemented!() - } +// Module to emulate having the implementation in a different file. +mod apis { + use super::{Block, BlockT, Runtime}; - fn something_with_block(_: Block) -> Block { - unimplemented!() - } + sp_api::impl_runtime_apis! { + impl crate::Api<Block> for Runtime { + fn test(_data: u64) { + unimplemented!() + } - fn function_with_two_args(_: u64, _: Block) { - unimplemented!() - } + fn something_with_block(_: Block) -> Block { + unimplemented!() + } - fn same_name() {} + fn function_with_two_args(_: u64, _: Block) { + unimplemented!() + } - fn wild_card(_: u32) {} - } + fn same_name() {} - impl sp_api::Core<Block> for Runtime { - fn version() -> sp_version::RuntimeVersion { - unimplemented!() - } - fn execute_block(_: Block) { - unimplemented!() + fn wild_card(_: u32) {} } - fn initialize_block(_: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode { - unimplemented!() + + 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!() + } } } } diff --git a/substrate/frame/support/test/tests/runtime_ui/invalid_runtime_type_derive.stderr b/substrate/frame/support/test/tests/runtime_ui/invalid_runtime_type_derive.stderr index 0b128c3dd4579ba93f7950f42f0ff97440eadd38..daa6721ff051dac23d66cde5f56c4b1e023b5e97 100644 --- a/substrate/frame/support/test/tests/runtime_ui/invalid_runtime_type_derive.stderr +++ b/substrate/frame/support/test/tests/runtime_ui/invalid_runtime_type_derive.stderr @@ -1,4 +1,4 @@ -error: expected one of: `RuntimeCall`, `RuntimeEvent`, `RuntimeError`, `RuntimeOrigin`, `RuntimeFreezeReason`, `RuntimeHoldReason`, `RuntimeSlashReason`, `RuntimeLockId`, `RuntimeTask` +error: expected one of: `RuntimeCall`, `RuntimeEvent`, `RuntimeError`, `RuntimeOrigin`, `RuntimeFreezeReason`, `RuntimeHoldReason`, `RuntimeSlashReason`, `RuntimeLockId`, `RuntimeTask`, `RuntimeViewFunction` --> tests/runtime_ui/invalid_runtime_type_derive.rs:21:23 | 21 | #[runtime::derive(RuntimeInfo)] diff --git a/substrate/frame/support/test/tests/runtime_ui/pass/basic.rs b/substrate/frame/support/test/tests/runtime_ui/pass/basic.rs index 514f150180153692caf55ba9b3ecb171ca4e1a2a..8350211335a5251ec7ef1356dd491dbe7034cc18 100644 --- a/substrate/frame/support/test/tests/runtime_ui/pass/basic.rs +++ b/substrate/frame/support/test/tests/runtime_ui/pass/basic.rs @@ -27,7 +27,7 @@ impl frame_system::Config for Runtime { #[frame_support::runtime] mod runtime { #[runtime::runtime] - #[runtime::derive(RuntimeCall, RuntimeEvent, RuntimeOrigin, RuntimeError, RuntimeTask)] + #[runtime::derive(RuntimeCall, RuntimeEvent, RuntimeOrigin, RuntimeError, RuntimeTask, RuntimeViewFunction)] pub struct Runtime; #[runtime::pallet_index(0)] diff --git a/substrate/frame/system/Cargo.toml b/substrate/frame/system/Cargo.toml index 38349c7edbd9499c22db57ef7da07ea3b9b3f23c..8883ebd4c41df9cfd9809253ef659ae584fe5e5e 100644 --- a/substrate/frame/system/Cargo.toml +++ b/substrate/frame/system/Cargo.toml @@ -18,17 +18,16 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] cfg-if = { workspace = true } codec = { features = ["derive"], workspace = true } +docify = { workspace = true } +frame-support = { workspace = true } log = { workspace = true } scale-info = { features = ["derive", "serde"], workspace = true } serde = { features = ["alloc", "derive"], workspace = true } -frame-support = { workspace = true } sp-core = { features = ["serde"], workspace = true } sp-io = { workspace = true } sp-runtime = { features = ["serde"], workspace = true } -sp-std = { workspace = true } sp-version = { features = ["serde"], workspace = true } sp-weights = { features = ["serde"], workspace = true } -docify = { workspace = true } [dev-dependencies] criterion = { workspace = true, default-features = true } @@ -47,7 +46,6 @@ std = [ "sp-externalities/std", "sp-io/std", "sp-runtime/std", - "sp-std/std", "sp-version/std", "sp-weights/std", ] diff --git a/substrate/frame/system/benchmarking/Cargo.toml b/substrate/frame/system/benchmarking/Cargo.toml index d9b5e7083bd21a25a24777c53dc6dd406a79c411..e9aac6e519f37c4c45189585327bc53c1fd84c93 100644 --- a/substrate/frame/system/benchmarking/Cargo.toml +++ b/substrate/frame/system/benchmarking/Cargo.toml @@ -17,16 +17,16 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } [dev-dependencies] -sp-io = { workspace = true, default-features = true } sp-externalities = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } [features] diff --git a/substrate/frame/system/benchmarking/src/extensions.rs b/substrate/frame/system/benchmarking/src/extensions.rs index 01e4687bc4bceecd21f9d9c097e65db4f7305105..25d6ea03557887d1554a2a417d781aaa2c79bee7 100644 --- a/substrate/frame/system/benchmarking/src/extensions.rs +++ b/substrate/frame/system/benchmarking/src/extensions.rs @@ -29,7 +29,7 @@ use frame_support::{ use frame_system::{ pallet_prelude::*, CheckGenesis, CheckMortality, CheckNonZeroSender, CheckNonce, CheckSpecVersion, CheckTxVersion, CheckWeight, Config, ExtensionsWeightInfo, Pallet as System, - RawOrigin, + RawOrigin, WeightReclaim, }; use sp_runtime::{ generic::Era, @@ -254,5 +254,49 @@ mod benchmarks { Ok(()) } + #[benchmark] + fn weight_reclaim() -> Result<(), BenchmarkError> { + let caller = account("caller", 0, 0); + let base_extrinsic = <T as frame_system::Config>::BlockWeights::get() + .get(DispatchClass::Normal) + .base_extrinsic; + let extension_weight = <T as frame_system::Config>::ExtensionsWeightInfo::weight_reclaim(); + let info = DispatchInfo { + call_weight: Weight::from_parts(base_extrinsic.ref_time() * 5, 0), + extension_weight, + class: DispatchClass::Normal, + ..Default::default() + }; + let call: T::RuntimeCall = frame_system::Call::remark { remark: vec![] }.into(); + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(base_extrinsic.ref_time() * 2, 0)), + pays_fee: Default::default(), + }; + let len = 0_usize; + let ext = WeightReclaim::<T>::new(); + + let initial_block_weight = Weight::from_parts(base_extrinsic.ref_time() * 2, 0); + frame_system::BlockWeight::<T>::mutate(|current_weight| { + current_weight.set(Weight::zero(), DispatchClass::Mandatory); + current_weight.set(initial_block_weight, DispatchClass::Normal); + current_weight.accrue(base_extrinsic + info.total_weight(), DispatchClass::Normal); + }); + + #[block] + { + ext.test_run(RawOrigin::Signed(caller).into(), &call, &info, len, 0, |_| Ok(post_info)) + .unwrap() + .unwrap(); + } + + assert_eq!( + System::<T>::block_weight().total(), + initial_block_weight + + base_extrinsic + + post_info.actual_weight.unwrap().saturating_add(extension_weight), + ); + Ok(()) + } + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test,); } diff --git a/substrate/frame/system/benchmarking/src/mock.rs b/substrate/frame/system/benchmarking/src/mock.rs index 6b126619ce5bfab57882868e19c936cbdec0eb27..61b5b885ec623f190c4bc618e033517b1e74f2ac 100644 --- a/substrate/frame/system/benchmarking/src/mock.rs +++ b/substrate/frame/system/benchmarking/src/mock.rs @@ -65,6 +65,10 @@ impl frame_system::ExtensionsWeightInfo for MockWeights { fn check_weight() -> Weight { Weight::from_parts(10, 0) } + + fn weight_reclaim() -> Weight { + Weight::from_parts(10, 0) + } } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] diff --git a/substrate/frame/system/rpc/runtime-api/Cargo.toml b/substrate/frame/system/rpc/runtime-api/Cargo.toml index 8e968a536756d32830d83a8fc6b92db991b5732c..3fd1985619bd4e7640de5bb9e26cf2a9b16fdca8 100644 --- a/substrate/frame/system/rpc/runtime-api/Cargo.toml +++ b/substrate/frame/system/rpc/runtime-api/Cargo.toml @@ -17,8 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -sp-api = { workspace = true } docify = { workspace = true } +sp-api = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/system/src/extensions/check_non_zero_sender.rs b/substrate/frame/system/src/extensions/check_non_zero_sender.rs index 577e2b324fca5b981b58dd794aad28c7d67cdc3b..978eebaf3dace5542991573c942f222015148d51 100644 --- a/substrate/frame/system/src/extensions/check_non_zero_sender.rs +++ b/substrate/frame/system/src/extensions/check_non_zero_sender.rs @@ -86,7 +86,7 @@ mod tests { use crate::mock::{new_test_ext, Test, CALL}; use frame_support::{assert_ok, dispatch::DispatchInfo}; use sp_runtime::{ - traits::{AsTransactionAuthorizedOrigin, DispatchTransaction}, + traits::{AsTransactionAuthorizedOrigin, DispatchTransaction, TxBaseImplication}, transaction_validity::{TransactionSource::External, TransactionValidityError}, }; @@ -118,7 +118,7 @@ mod tests { let info = DispatchInfo::default(); let len = 0_usize; let (_, _, origin) = CheckNonZeroSender::<Test>::new() - .validate(None.into(), CALL, &info, len, (), CALL, External) + .validate(None.into(), CALL, &info, len, (), &TxBaseImplication(CALL), External) .unwrap(); assert!(!origin.is_transaction_authorized()); }) diff --git a/substrate/frame/system/src/extensions/check_nonce.rs b/substrate/frame/system/src/extensions/check_nonce.rs index 004ec08a26f26f1b480d11f232a532bc4d8d269c..bc19a09e06a9e0d567441d066218b69377401c01 100644 --- a/substrate/frame/system/src/extensions/check_nonce.rs +++ b/substrate/frame/system/src/extensions/check_nonce.rs @@ -186,7 +186,7 @@ mod tests { assert_ok, assert_storage_noop, dispatch::GetDispatchInfo, traits::OriginTrait, }; use sp_runtime::{ - traits::{AsTransactionAuthorizedOrigin, DispatchTransaction}, + traits::{AsTransactionAuthorizedOrigin, DispatchTransaction, TxBaseImplication}, transaction_validity::TransactionSource::External, }; @@ -335,7 +335,7 @@ mod tests { let info = DispatchInfo::default(); let len = 0_usize; let (_, val, origin) = CheckNonce::<Test>(1u64.into()) - .validate(None.into(), CALL, &info, len, (), CALL, External) + .validate(None.into(), CALL, &info, len, (), &TxBaseImplication(CALL), External) .unwrap(); assert!(!origin.is_transaction_authorized()); assert_ok!(CheckNonce::<Test>(1u64.into()).prepare(val, &origin, CALL, &info, len)); @@ -359,7 +359,7 @@ mod tests { let len = 0_usize; // run the validation step let (_, val, origin) = CheckNonce::<Test>(1u64.into()) - .validate(Some(1).into(), CALL, &info, len, (), CALL, External) + .validate(Some(1).into(), CALL, &info, len, (), &TxBaseImplication(CALL), External) .unwrap(); // mutate `AccountData` for the caller crate::Account::<Test>::mutate(1, |info| { diff --git a/substrate/frame/system/src/extensions/check_weight.rs b/substrate/frame/system/src/extensions/check_weight.rs index ee91478b90f314ddce0c7afffe5ab292abebc2ce..de0303defd0c3e8c9fc502d3017127e1c08c54ac 100644 --- a/substrate/frame/system/src/extensions/check_weight.rs +++ b/substrate/frame/system/src/extensions/check_weight.rs @@ -135,30 +135,12 @@ where Ok(()) } + #[deprecated(note = "Use `frame_system::Pallet::reclaim_weight` instead.")] pub fn do_post_dispatch( info: &DispatchInfoOf<T::RuntimeCall>, post_info: &PostDispatchInfoOf<T::RuntimeCall>, ) -> Result<(), TransactionValidityError> { - let unspent = post_info.calc_unspent(info); - if unspent.any_gt(Weight::zero()) { - crate::BlockWeight::<T>::mutate(|current_weight| { - current_weight.reduce(unspent, info.class); - }) - } - - log::trace!( - target: LOG_TARGET, - "Used block weight: {:?}", - crate::BlockWeight::<T>::get(), - ); - - log::trace!( - target: LOG_TARGET, - "Used block length: {:?}", - Pallet::<T>::all_extrinsics_len(), - ); - - Ok(()) + crate::Pallet::<T>::reclaim_weight(info, post_info) } } @@ -279,8 +261,7 @@ where _len: usize, _result: &DispatchResult, ) -> Result<Weight, TransactionValidityError> { - Self::do_post_dispatch(info, post_info)?; - Ok(Weight::zero()) + crate::Pallet::<T>::reclaim_weight(info, post_info).map(|()| Weight::zero()) } fn bare_validate( @@ -306,7 +287,7 @@ where _len: usize, _result: &DispatchResult, ) -> Result<(), TransactionValidityError> { - Self::do_post_dispatch(info, post_info) + crate::Pallet::<T>::reclaim_weight(info, post_info) } } @@ -744,6 +725,121 @@ mod tests { }) } + #[test] + fn extrinsic_already_refunded_more_precisely() { + new_test_ext().execute_with(|| { + // This is half of the max block weight + let info = + DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(128, 0)), + pays_fee: Default::default(), + }; + let prior_block_weight = Weight::from_parts(64, 0); + let accurate_refund = Weight::from_parts(510, 0); + let len = 0_usize; + let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic; + + // Set initial info + BlockWeight::<Test>::mutate(|current_weight| { + current_weight.set(Weight::zero(), DispatchClass::Mandatory); + current_weight.set(prior_block_weight, DispatchClass::Normal); + }); + + // Validate and prepare extrinsic + let pre = CheckWeight::<Test>(PhantomData) + .validate_and_prepare(Some(1).into(), CALL, &info, len, 0) + .unwrap() + .0; + + assert_eq!( + BlockWeight::<Test>::get().total(), + info.total_weight() + prior_block_weight + base_extrinsic + ); + + // Refund more accurately than the benchmark + BlockWeight::<Test>::mutate(|current_weight| { + current_weight.reduce(accurate_refund, DispatchClass::Normal); + }); + crate::ExtrinsicWeightReclaimed::<Test>::put(accurate_refund); + + // Do the post dispatch + assert_ok!(CheckWeight::<Test>::post_dispatch_details( + pre, + &info, + &post_info, + len, + &Ok(()) + )); + + // Ensure the accurate refund is used + assert_eq!(crate::ExtrinsicWeightReclaimed::<Test>::get(), accurate_refund); + assert_eq!( + BlockWeight::<Test>::get().total(), + info.total_weight() - accurate_refund + prior_block_weight + base_extrinsic + ); + }) + } + + #[test] + fn extrinsic_already_refunded_less_precisely() { + new_test_ext().execute_with(|| { + // This is half of the max block weight + let info = + DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(128, 0)), + pays_fee: Default::default(), + }; + let prior_block_weight = Weight::from_parts(64, 0); + let inaccurate_refund = Weight::from_parts(110, 0); + let len = 0_usize; + let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic; + + // Set initial info + BlockWeight::<Test>::mutate(|current_weight| { + current_weight.set(Weight::zero(), DispatchClass::Mandatory); + current_weight.set(prior_block_weight, DispatchClass::Normal); + }); + + // Validate and prepare extrinsic + let pre = CheckWeight::<Test>(PhantomData) + .validate_and_prepare(Some(1).into(), CALL, &info, len, 0) + .unwrap() + .0; + + assert_eq!( + BlockWeight::<Test>::get().total(), + info.total_weight() + prior_block_weight + base_extrinsic + ); + + // Refund less accurately than the benchmark + BlockWeight::<Test>::mutate(|current_weight| { + current_weight.reduce(inaccurate_refund, DispatchClass::Normal); + }); + crate::ExtrinsicWeightReclaimed::<Test>::put(inaccurate_refund); + + // Do the post dispatch + assert_ok!(CheckWeight::<Test>::post_dispatch_details( + pre, + &info, + &post_info, + len, + &Ok(()) + )); + + // Ensure the accurate refund from benchmark is used + assert_eq!( + crate::ExtrinsicWeightReclaimed::<Test>::get(), + post_info.calc_unspent(&info) + ); + assert_eq!( + BlockWeight::<Test>::get().total(), + post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic + ); + }) + } + #[test] fn zero_weight_extrinsic_still_has_base_weight() { new_test_ext().execute_with(|| { diff --git a/substrate/frame/system/src/extensions/mod.rs b/substrate/frame/system/src/extensions/mod.rs index d79104d224035450f9ca69ba7d4b502b9ff0289d..66a8b17d30ae15b09a4af20a9dd703a52d18de9a 100644 --- a/substrate/frame/system/src/extensions/mod.rs +++ b/substrate/frame/system/src/extensions/mod.rs @@ -22,6 +22,7 @@ pub mod check_nonce; pub mod check_spec_version; pub mod check_tx_version; pub mod check_weight; +pub mod weight_reclaim; pub mod weights; pub use weights::WeightInfo; diff --git a/substrate/frame/system/src/extensions/weight_reclaim.rs b/substrate/frame/system/src/extensions/weight_reclaim.rs new file mode 100644 index 0000000000000000000000000000000000000000..0c37422a843bb7595497e8c2cc939ac39dee1c5e --- /dev/null +++ b/substrate/frame/system/src/extensions/weight_reclaim.rs @@ -0,0 +1,401 @@ +// 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::Config; +use codec::{Decode, Encode}; +use frame_support::dispatch::{DispatchInfo, PostDispatchInfo}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{ + DispatchInfoOf, Dispatchable, PostDispatchInfoOf, TransactionExtension, ValidateResult, + }, + transaction_validity::{TransactionSource, TransactionValidityError, ValidTransaction}, + DispatchResult, +}; +use sp_weights::Weight; + +/// Reclaim the unused weight using the post dispatch information +/// +/// After the dispatch of the extrinsic, calculate the unused weight using the post dispatch +/// information and update the block consumed weight according to the new calculated extrinsic +/// weight. +#[derive(Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct WeightReclaim<T: Config + Send + Sync>(core::marker::PhantomData<T>); + +impl<T: Config + Send + Sync> WeightReclaim<T> +where + T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>, +{ + /// Creates new `TransactionExtension` to recalculate the extrinsic weight after dispatch. + pub fn new() -> Self { + Self(Default::default()) + } +} + +impl<T: Config + Send + Sync> TransactionExtension<T::RuntimeCall> for WeightReclaim<T> +where + T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>, +{ + const IDENTIFIER: &'static str = "WeightReclaim"; + type Implicit = (); + type Pre = (); + type Val = (); + + fn weight(&self, _: &T::RuntimeCall) -> Weight { + <T::ExtensionsWeightInfo as super::WeightInfo>::weight_reclaim() + } + + fn validate( + &self, + origin: T::RuntimeOrigin, + _call: &T::RuntimeCall, + _info: &DispatchInfoOf<T::RuntimeCall>, + _len: usize, + _self_implicit: Self::Implicit, + _inherited_implication: &impl Encode, + _source: TransactionSource, + ) -> ValidateResult<Self::Val, T::RuntimeCall> { + Ok((ValidTransaction::default(), (), origin)) + } + + fn prepare( + self, + _val: Self::Val, + _origin: &T::RuntimeOrigin, + _call: &T::RuntimeCall, + _info: &DispatchInfoOf<T::RuntimeCall>, + _len: usize, + ) -> Result<Self::Pre, TransactionValidityError> { + Ok(()) + } + + fn post_dispatch_details( + _pre: Self::Pre, + info: &DispatchInfoOf<T::RuntimeCall>, + post_info: &PostDispatchInfoOf<T::RuntimeCall>, + _len: usize, + _result: &DispatchResult, + ) -> Result<Weight, TransactionValidityError> { + crate::Pallet::<T>::reclaim_weight(info, post_info).map(|()| Weight::zero()) + } + + fn bare_validate( + _call: &T::RuntimeCall, + _info: &DispatchInfoOf<T::RuntimeCall>, + _len: usize, + ) -> frame_support::pallet_prelude::TransactionValidity { + Ok(ValidTransaction::default()) + } + + fn bare_validate_and_prepare( + _call: &T::RuntimeCall, + _info: &DispatchInfoOf<T::RuntimeCall>, + _len: usize, + ) -> Result<(), TransactionValidityError> { + Ok(()) + } + + fn bare_post_dispatch( + info: &DispatchInfoOf<T::RuntimeCall>, + post_info: &mut PostDispatchInfoOf<T::RuntimeCall>, + _len: usize, + _result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + crate::Pallet::<T>::reclaim_weight(info, post_info) + } +} + +impl<T: Config + Send + Sync> core::fmt::Debug for WeightReclaim<T> +where + T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>, +{ + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", Self::IDENTIFIER) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + mock::{new_test_ext, Test}, + BlockWeight, DispatchClass, + }; + use frame_support::{assert_ok, weights::Weight}; + + fn block_weights() -> crate::limits::BlockWeights { + <Test as crate::Config>::BlockWeights::get() + } + + #[test] + fn extrinsic_already_refunded_more_precisely() { + new_test_ext().execute_with(|| { + // This is half of the max block weight + let info = + DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(128, 0)), + pays_fee: Default::default(), + }; + let prior_block_weight = Weight::from_parts(64, 0); + let accurate_refund = Weight::from_parts(510, 0); + let len = 0_usize; + let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic; + + // Set initial info + BlockWeight::<Test>::mutate(|current_weight| { + current_weight.set(prior_block_weight, DispatchClass::Normal); + current_weight.accrue( + base_extrinsic + info.total_weight() - accurate_refund, + DispatchClass::Normal, + ); + }); + crate::ExtrinsicWeightReclaimed::<Test>::put(accurate_refund); + + // Do the post dispatch + assert_ok!(WeightReclaim::<Test>::post_dispatch_details( + (), + &info, + &post_info, + len, + &Ok(()) + )); + + // Ensure the accurate refund is used + assert_eq!(crate::ExtrinsicWeightReclaimed::<Test>::get(), accurate_refund); + assert_eq!( + *BlockWeight::<Test>::get().get(DispatchClass::Normal), + info.total_weight() - accurate_refund + prior_block_weight + base_extrinsic + ); + }) + } + + #[test] + fn extrinsic_already_refunded_less_precisely() { + new_test_ext().execute_with(|| { + // This is half of the max block weight + let info = + DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(128, 0)), + pays_fee: Default::default(), + }; + let prior_block_weight = Weight::from_parts(64, 0); + let inaccurate_refund = Weight::from_parts(110, 0); + let len = 0_usize; + let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic; + + // Set initial info + BlockWeight::<Test>::mutate(|current_weight| { + current_weight.set(prior_block_weight, DispatchClass::Normal); + current_weight.accrue( + base_extrinsic + info.total_weight() - inaccurate_refund, + DispatchClass::Normal, + ); + }); + crate::ExtrinsicWeightReclaimed::<Test>::put(inaccurate_refund); + + // Do the post dispatch + assert_ok!(WeightReclaim::<Test>::post_dispatch_details( + (), + &info, + &post_info, + len, + &Ok(()) + )); + + // Ensure the accurate refund from benchmark is used + assert_eq!( + crate::ExtrinsicWeightReclaimed::<Test>::get(), + post_info.calc_unspent(&info) + ); + assert_eq!( + *BlockWeight::<Test>::get().get(DispatchClass::Normal), + post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic + ); + }) + } + + #[test] + fn extrinsic_not_refunded_before() { + new_test_ext().execute_with(|| { + // This is half of the max block weight + let info = + DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(128, 0)), + pays_fee: Default::default(), + }; + let prior_block_weight = Weight::from_parts(64, 0); + let len = 0_usize; + let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic; + + // Set initial info + BlockWeight::<Test>::mutate(|current_weight| { + current_weight.set(prior_block_weight, DispatchClass::Normal); + current_weight.accrue(base_extrinsic + info.total_weight(), DispatchClass::Normal); + }); + + // Do the post dispatch + assert_ok!(WeightReclaim::<Test>::post_dispatch_details( + (), + &info, + &post_info, + len, + &Ok(()) + )); + + // Ensure the accurate refund from benchmark is used + assert_eq!( + crate::ExtrinsicWeightReclaimed::<Test>::get(), + post_info.calc_unspent(&info) + ); + assert_eq!( + *BlockWeight::<Test>::get().get(DispatchClass::Normal), + post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic + ); + }) + } + + #[test] + fn no_actual_post_dispatch_weight() { + new_test_ext().execute_with(|| { + // This is half of the max block weight + let info = + DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() }; + let post_info = PostDispatchInfo { actual_weight: None, pays_fee: Default::default() }; + let prior_block_weight = Weight::from_parts(64, 0); + let len = 0_usize; + let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic; + + // Set initial info + BlockWeight::<Test>::mutate(|current_weight| { + current_weight.set(prior_block_weight, DispatchClass::Normal); + current_weight.accrue(base_extrinsic + info.total_weight(), DispatchClass::Normal); + }); + + // Do the post dispatch + assert_ok!(WeightReclaim::<Test>::post_dispatch_details( + (), + &info, + &post_info, + len, + &Ok(()) + )); + + // Ensure the accurate refund from benchmark is used + assert_eq!( + crate::ExtrinsicWeightReclaimed::<Test>::get(), + post_info.calc_unspent(&info) + ); + assert_eq!( + *BlockWeight::<Test>::get().get(DispatchClass::Normal), + info.total_weight() + prior_block_weight + base_extrinsic + ); + }) + } + + #[test] + fn different_dispatch_class() { + new_test_ext().execute_with(|| { + // This is half of the max block weight + let info = DispatchInfo { + call_weight: Weight::from_parts(512, 0), + class: DispatchClass::Operational, + ..Default::default() + }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(128, 0)), + pays_fee: Default::default(), + }; + let prior_block_weight = Weight::from_parts(64, 0); + let len = 0_usize; + let base_extrinsic = block_weights().get(DispatchClass::Operational).base_extrinsic; + + // Set initial info + BlockWeight::<Test>::mutate(|current_weight| { + current_weight.set(prior_block_weight, DispatchClass::Operational); + current_weight + .accrue(base_extrinsic + info.total_weight(), DispatchClass::Operational); + }); + + // Do the post dispatch + assert_ok!(WeightReclaim::<Test>::post_dispatch_details( + (), + &info, + &post_info, + len, + &Ok(()) + )); + + // Ensure the accurate refund from benchmark is used + assert_eq!( + crate::ExtrinsicWeightReclaimed::<Test>::get(), + post_info.calc_unspent(&info) + ); + assert_eq!( + *BlockWeight::<Test>::get().get(DispatchClass::Operational), + post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic + ); + }) + } + + #[test] + fn bare_also_works() { + new_test_ext().execute_with(|| { + // This is half of the max block weight + let info = DispatchInfo { + call_weight: Weight::from_parts(512, 0), + class: DispatchClass::Operational, + ..Default::default() + }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(128, 0)), + pays_fee: Default::default(), + }; + let prior_block_weight = Weight::from_parts(64, 0); + let len = 0_usize; + let base_extrinsic = block_weights().get(DispatchClass::Operational).base_extrinsic; + + // Set initial info + BlockWeight::<Test>::mutate(|current_weight| { + current_weight.set(prior_block_weight, DispatchClass::Operational); + current_weight + .accrue(base_extrinsic + info.total_weight(), DispatchClass::Operational); + }); + + // Do the bare post dispatch + assert_ok!(WeightReclaim::<Test>::bare_post_dispatch( + &info, + &mut post_info.clone(), + len, + &Ok(()) + )); + + // Ensure the accurate refund from benchmark is used + assert_eq!( + crate::ExtrinsicWeightReclaimed::<Test>::get(), + post_info.calc_unspent(&info) + ); + assert_eq!( + *BlockWeight::<Test>::get().get(DispatchClass::Operational), + post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic + ); + }) + } +} diff --git a/substrate/frame/system/src/extensions/weights.rs b/substrate/frame/system/src/extensions/weights.rs index b3c296899be52e8d308dd48fdeb662327be26dfb..670bb9a0e6fab2165cc29e04d757300a2eeae4a4 100644 --- a/substrate/frame/system/src/extensions/weights.rs +++ b/substrate/frame/system/src/extensions/weights.rs @@ -59,6 +59,7 @@ pub trait WeightInfo { fn check_spec_version() -> Weight; fn check_tx_version() -> Weight; fn check_weight() -> Weight; + fn weight_reclaim() -> Weight; } /// Weights for `frame_system_extensions` using the Substrate node and recommended hardware. @@ -133,6 +134,17 @@ impl<T: crate::Config> WeightInfo for SubstrateWeight<T> { // Minimum execution time: 2_887_000 picoseconds. Weight::from_parts(3_006_000, 0) } + /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1489` + // Minimum execution time: 4_375_000 picoseconds. + Weight::from_parts(4_747_000, 1489) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } } // For backwards compatibility and tests. @@ -206,4 +218,15 @@ impl WeightInfo for () { // Minimum execution time: 2_887_000 picoseconds. Weight::from_parts(3_006_000, 0) } + /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1489` + // Minimum execution time: 4_375_000 picoseconds. + Weight::from_parts(4_747_000, 1489) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } } diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index 862fb4cf9faf8c23c57bbbc713894d6e31325155..8980c6d6c8f427eedd924a8d2cfb555635b18cf7 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -120,8 +120,6 @@ use sp_runtime::{ }, DispatchError, RuntimeDebug, }; -#[cfg(any(feature = "std", test))] -use sp_std::map; use sp_version::RuntimeVersion; use codec::{Decode, Encode, EncodeLike, FullCodec, MaxEncodedLen}; @@ -146,6 +144,10 @@ use frame_support::{ }; use scale_info::TypeInfo; use sp_core::storage::well_known_keys; +use sp_runtime::{ + traits::{DispatchInfoOf, PostDispatchInfoOf}, + transaction_validity::TransactionValidityError, +}; use sp_weights::{RuntimeDbWeight, Weight}; #[cfg(any(feature = "std", test))] @@ -170,7 +172,7 @@ pub use extensions::{ check_genesis::CheckGenesis, check_mortality::CheckMortality, check_non_zero_sender::CheckNonZeroSender, check_nonce::CheckNonce, check_spec_version::CheckSpecVersion, check_tx_version::CheckTxVersion, - check_weight::CheckWeight, WeightInfo as ExtensionsWeightInfo, + check_weight::CheckWeight, weight_reclaim::WeightReclaim, WeightInfo as ExtensionsWeightInfo, }; // Backward compatible re-export. pub use extensions::check_mortality::CheckMortality as CheckEra; @@ -1039,6 +1041,17 @@ pub mod pallet { pub(super) type AuthorizedUpgrade<T: Config> = StorageValue<_, CodeUpgradeAuthorization<T>, OptionQuery>; + /// The weight reclaimed for the extrinsic. + /// + /// This information is available until the end of the extrinsic execution. + /// More precisely this information is removed in `note_applied_extrinsic`. + /// + /// Logic doing some post dispatch weight reduction must update this storage to avoid duplicate + /// reduction. + #[pallet::storage] + #[pallet::whitelist_storage] + pub type ExtrinsicWeightReclaimed<T: Config> = StorageValue<_, Weight, ValueQuery>; + #[derive(frame_support::DefaultNoBound)] #[pallet::genesis_config] pub struct GenesisConfig<T: Config> { @@ -1905,12 +1918,14 @@ impl<T: Config> Pallet<T> { #[cfg(any(feature = "std", test))] pub fn externalities() -> TestExternalities { TestExternalities::new(sp_core::storage::Storage { - top: map![ - <BlockHash<T>>::hashed_key_for(BlockNumberFor::<T>::zero()) => [69u8; 32].encode(), - <Number<T>>::hashed_key().to_vec() => BlockNumberFor::<T>::one().encode(), - <ParentHash<T>>::hashed_key().to_vec() => [69u8; 32].encode() - ], - children_default: map![], + top: [ + (<BlockHash<T>>::hashed_key_for(BlockNumberFor::<T>::zero()), [69u8; 32].encode()), + (<Number<T>>::hashed_key().to_vec(), BlockNumberFor::<T>::one().encode()), + (<ParentHash<T>>::hashed_key().to_vec(), [69u8; 32].encode()), + ] + .into_iter() + .collect(), + children_default: Default::default(), }) } @@ -1959,6 +1974,51 @@ impl<T: Config> Pallet<T> { .collect::<_>() } + /// Simulate the execution of a block sequence up to a specified height, injecting the + /// provided hooks at each block. + /// + /// `on_finalize` is always called before `on_initialize` with the current block number. + /// `on_initalize` is always called with the next block number. + /// + /// These hooks allows custom logic to be executed at each block at specific location. + /// For example, you might use one of them to set a timestamp for each block. + #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] + pub fn run_to_block_with<AllPalletsWithSystem>( + n: BlockNumberFor<T>, + mut hooks: RunToBlockHooks<T>, + ) where + AllPalletsWithSystem: frame_support::traits::OnInitialize<BlockNumberFor<T>> + + frame_support::traits::OnFinalize<BlockNumberFor<T>>, + { + let mut bn = Self::block_number(); + + while bn < n { + // Skip block 0. + if !bn.is_zero() { + (hooks.before_finalize)(bn); + AllPalletsWithSystem::on_finalize(bn); + (hooks.after_finalize)(bn); + } + + bn += One::one(); + + Self::set_block_number(bn); + (hooks.before_initialize)(bn); + AllPalletsWithSystem::on_initialize(bn); + (hooks.after_initialize)(bn); + } + } + + /// Simulate the execution of a block sequence up to a specified height. + #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] + pub fn run_to_block<AllPalletsWithSystem>(n: BlockNumberFor<T>) + where + AllPalletsWithSystem: frame_support::traits::OnInitialize<BlockNumberFor<T>> + + frame_support::traits::OnFinalize<BlockNumberFor<T>>, + { + Self::run_to_block_with::<AllPalletsWithSystem>(n, Default::default()); + } + /// Set the block number to something in particular. Can be used as an alternative to /// `initialize` for tests that don't need to bother with the other environment entries. #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] @@ -2002,11 +2062,18 @@ impl<T: Config> Pallet<T> { /// /// NOTE: Events not registered at the genesis block and quietly omitted. #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] + #[track_caller] pub fn assert_has_event(event: T::RuntimeEvent) { + let warn = if Self::block_number().is_zero() { + "WARNING: block number is zero, and events are not registered at block number zero.\n" + } else { + "" + }; + let events = Self::events(); assert!( events.iter().any(|record| record.event == event), - "expected event {event:?} not found in events {events:?}", + "{warn}expected event {event:?} not found in events {events:?}", ); } @@ -2014,11 +2081,22 @@ impl<T: Config> Pallet<T> { /// /// NOTE: Events not registered at the genesis block and quietly omitted. #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] + #[track_caller] pub fn assert_last_event(event: T::RuntimeEvent) { - let last_event = Self::events().last().expect("events expected").event.clone(); + let warn = if Self::block_number().is_zero() { + "WARNING: block number is zero, and events are not registered at block number zero.\n" + } else { + "" + }; + + let last_event = Self::events() + .last() + .expect(&alloc::format!("{warn}events expected")) + .event + .clone(); assert_eq!( last_event, event, - "expected event {event:?} is not equal to the last event {last_event:?}", + "{warn}expected event {event:?} is not equal to the last event {last_event:?}", ); } @@ -2073,10 +2151,23 @@ impl<T: Config> Pallet<T> { }, }); + log::trace!( + target: LOG_TARGET, + "Used block weight: {:?}", + BlockWeight::<T>::get(), + ); + + log::trace!( + target: LOG_TARGET, + "Used block length: {:?}", + Pallet::<T>::all_extrinsics_len(), + ); + let next_extrinsic_index = Self::extrinsic_index().unwrap_or_default() + 1u32; storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &next_extrinsic_index); ExecutionPhase::<T>::put(Phase::ApplyExtrinsic(next_extrinsic_index)); + ExtrinsicWeightReclaimed::<T>::kill(); } /// To be called immediately after `note_applied_extrinsic` of the last extrinsic of the block @@ -2174,6 +2265,32 @@ impl<T: Config> Pallet<T> { } Ok(actual_hash) } + + /// Reclaim the weight for the extrinsic given info and post info. + /// + /// This function will check the already reclaimed weight, and reclaim more if the + /// difference between pre dispatch and post dispatch weight is higher. + pub fn reclaim_weight( + info: &DispatchInfoOf<T::RuntimeCall>, + post_info: &PostDispatchInfoOf<T::RuntimeCall>, + ) -> Result<(), TransactionValidityError> + where + T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>, + { + let already_reclaimed = crate::ExtrinsicWeightReclaimed::<T>::get(); + let unspent = post_info.calc_unspent(info); + let accurate_reclaim = already_reclaimed.max(unspent); + // Saturation never happens, we took the maximum above. + let to_reclaim_more = accurate_reclaim.saturating_sub(already_reclaimed); + if to_reclaim_more != Weight::zero() { + crate::BlockWeight::<T>::mutate(|current_weight| { + current_weight.reduce(to_reclaim_more, info.class); + }); + crate::ExtrinsicWeightReclaimed::<T>::put(accurate_reclaim); + } + + Ok(()) + } } /// Returns a 32 byte datum which is guaranteed to be universally unique. `entropy` is provided @@ -2293,6 +2410,72 @@ impl<T: Config> Lookup for ChainContext<T> { } } +/// Hooks for the [`Pallet::run_to_block_with`] function. +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +pub struct RunToBlockHooks<'a, T> +where + T: 'a + Config, +{ + before_initialize: Box<dyn 'a + FnMut(BlockNumberFor<T>)>, + after_initialize: Box<dyn 'a + FnMut(BlockNumberFor<T>)>, + before_finalize: Box<dyn 'a + FnMut(BlockNumberFor<T>)>, + after_finalize: Box<dyn 'a + FnMut(BlockNumberFor<T>)>, +} + +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +impl<'a, T> RunToBlockHooks<'a, T> +where + T: 'a + Config, +{ + /// Set the hook function logic before the initialization of the block. + pub fn before_initialize<F>(mut self, f: F) -> Self + where + F: 'a + FnMut(BlockNumberFor<T>), + { + self.before_initialize = Box::new(f); + self + } + /// Set the hook function logic after the initialization of the block. + pub fn after_initialize<F>(mut self, f: F) -> Self + where + F: 'a + FnMut(BlockNumberFor<T>), + { + self.after_initialize = Box::new(f); + self + } + /// Set the hook function logic before the finalization of the block. + pub fn before_finalize<F>(mut self, f: F) -> Self + where + F: 'a + FnMut(BlockNumberFor<T>), + { + self.before_finalize = Box::new(f); + self + } + /// Set the hook function logic after the finalization of the block. + pub fn after_finalize<F>(mut self, f: F) -> Self + where + F: 'a + FnMut(BlockNumberFor<T>), + { + self.after_finalize = Box::new(f); + self + } +} + +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +impl<'a, T> Default for RunToBlockHooks<'a, T> +where + T: Config, +{ + fn default() -> Self { + Self { + before_initialize: Box::new(|_| {}), + after_initialize: Box::new(|_| {}), + before_finalize: Box::new(|_| {}), + after_finalize: Box::new(|_| {}), + } + } +} + /// Prelude to be used alongside pallet macro, for ease of use. pub mod pallet_prelude { pub use crate::{ensure_none, ensure_root, ensure_signed, ensure_signed_or_root}; diff --git a/substrate/frame/system/src/tests.rs b/substrate/frame/system/src/tests.rs index 6b903f5b7e79eccbeacc6180e2828ea74897f72d..6415380b2848564cf3c3ed4a95642d147db539c9 100644 --- a/substrate/frame/system/src/tests.rs +++ b/substrate/frame/system/src/tests.rs @@ -892,3 +892,67 @@ fn test_default_account_nonce() { assert_eq!(System::account_nonce(&1), 5u64.into()); }); } + +#[test] +fn extrinsic_weight_refunded_is_cleaned() { + new_test_ext().execute_with(|| { + crate::ExtrinsicWeightReclaimed::<Test>::put(Weight::from_parts(1, 2)); + assert_eq!(crate::ExtrinsicWeightReclaimed::<Test>::get(), Weight::from_parts(1, 2)); + System::note_applied_extrinsic(&Ok(().into()), Default::default()); + assert_eq!(crate::ExtrinsicWeightReclaimed::<Test>::get(), Weight::zero()); + + crate::ExtrinsicWeightReclaimed::<Test>::put(Weight::from_parts(1, 2)); + assert_eq!(crate::ExtrinsicWeightReclaimed::<Test>::get(), Weight::from_parts(1, 2)); + System::note_applied_extrinsic(&Err(DispatchError::BadOrigin.into()), Default::default()); + assert_eq!(crate::ExtrinsicWeightReclaimed::<Test>::get(), Weight::zero()); + }); +} + +#[test] +fn reclaim_works() { + new_test_ext().execute_with(|| { + let info = DispatchInfo { call_weight: Weight::from_parts(100, 200), ..Default::default() }; + crate::Pallet::<Test>::reclaim_weight( + &info, + &PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 100)), + ..Default::default() + }, + ) + .unwrap(); + assert_eq!(crate::ExtrinsicWeightReclaimed::<Test>::get(), Weight::from_parts(50, 100)); + + crate::Pallet::<Test>::reclaim_weight( + &info, + &PostDispatchInfo { + actual_weight: Some(Weight::from_parts(25, 200)), + ..Default::default() + }, + ) + .unwrap(); + assert_eq!(crate::ExtrinsicWeightReclaimed::<Test>::get(), Weight::from_parts(75, 100)); + + crate::Pallet::<Test>::reclaim_weight( + &info, + &PostDispatchInfo { + actual_weight: Some(Weight::from_parts(300, 50)), + ..Default::default() + }, + ) + .unwrap(); + assert_eq!(crate::ExtrinsicWeightReclaimed::<Test>::get(), Weight::from_parts(75, 150)); + + crate::Pallet::<Test>::reclaim_weight( + &info, + &PostDispatchInfo { + actual_weight: Some(Weight::from_parts(300, 300)), + ..Default::default() + }, + ) + .unwrap(); + assert_eq!(crate::ExtrinsicWeightReclaimed::<Test>::get(), Weight::from_parts(75, 150)); + + System::note_applied_extrinsic(&Ok(().into()), Default::default()); + assert_eq!(crate::ExtrinsicWeightReclaimed::<Test>::get(), Weight::zero()); + }); +} diff --git a/substrate/frame/timestamp/Cargo.toml b/substrate/frame/timestamp/Cargo.toml index 0eff0530c7e2444979e09f31f1f242a61c4c57db..75788aef348a34adccfed05dfcf1291c3affefaa 100644 --- a/substrate/frame/timestamp/Cargo.toml +++ b/substrate/frame/timestamp/Cargo.toml @@ -18,11 +18,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive", "max-encoded-len"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-inherents = { workspace = true } sp-io = { optional = true, workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/tips/Cargo.toml b/substrate/frame/tips/Cargo.toml index 7c7a2d6aa9098a3bbe79112757e3f53be784b53b..6b5b89e7a19723f4f2e10bdd3bf02db8e27189f8 100644 --- a/substrate/frame/tips/Cargo.toml +++ b/substrate/frame/tips/Cargo.toml @@ -17,13 +17,13 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { features = ["derive"], optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-treasury = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/tips/src/tests.rs b/substrate/frame/tips/src/tests.rs index 530efb708e41483e8fd339720f9daa81c195d7c2..b769ea5b3e753a3b6081496d2167aafb8f2db7f0 100644 --- a/substrate/frame/tips/src/tests.rs +++ b/substrate/frame/tips/src/tests.rs @@ -180,7 +180,10 @@ impl Config<Instance1> for Test { pub fn new_test_ext() -> sp_io::TestExternalities { let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { system: frame_system::GenesisConfig::default(), - balances: pallet_balances::GenesisConfig { balances: vec![(0, 100), (1, 98), (2, 1)] }, + balances: pallet_balances::GenesisConfig { + balances: vec![(0, 100), (1, 98), (2, 1)], + ..Default::default() + }, treasury: Default::default(), treasury_1: Default::default(), } @@ -583,6 +586,7 @@ fn genesis_funding_works() { pallet_balances::GenesisConfig::<Test> { // Total issuance will be 200 with treasury account initialized with 100. balances: vec![(0, 100), (Treasury::account_id(), initial_funding)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/transaction-payment/Cargo.toml b/substrate/frame/transaction-payment/Cargo.toml index afa03ceb12eb8942e540119c5f5a898bd6859566..2639bda18b6c2e8e1a179f96dd8bf876125e69c5 100644 --- a/substrate/frame/transaction-payment/Cargo.toml +++ b/substrate/frame/transaction-payment/Cargo.toml @@ -19,18 +19,18 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } [dev-dependencies] -serde_json = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml b/substrate/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml index 7c98d157f6ffd5b7e89645aae4d8a8af90c38c49..147859fdb26a4f76d3bbc05cde7b302c069eaf93 100644 --- a/substrate/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml +++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml @@ -17,21 +17,21 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # Substrate dependencies -sp-runtime = { workspace = true } +codec = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-asset-conversion = { workspace = true } pallet-transaction-payment = { workspace = true } -codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } +sp-runtime = { workspace = true } [dev-dependencies] +pallet-assets = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-storage = { workspace = true } -pallet-assets = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs index 6ce4652fd42f5e8d0c962e8033c350bb84bf7e0b..76d46aa164713afdf060fdbc44d52a51e7da21aa 100644 --- a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs +++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs @@ -86,6 +86,7 @@ impl ExtBuilder { } else { vec![] }, + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml b/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml index 89fe5bfe7a42fd32cfd6c46fa485636de7c63c01..2924860c520109bd0a66393e69460ff823f75fa3 100644 --- a/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml +++ b/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml @@ -21,10 +21,10 @@ sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-transaction-payment = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } # Other dependencies codec = { features = ["derive"], workspace = true } diff --git a/substrate/frame/transaction-payment/asset-tx-payment/src/lib.rs b/substrate/frame/transaction-payment/asset-tx-payment/src/lib.rs index dd752989c3662d2123c9d7783786bcb3d9de80d2..4a96cbcacb5804541aa6130702869b05cbd32637 100644 --- a/substrate/frame/transaction-payment/asset-tx-payment/src/lib.rs +++ b/substrate/frame/transaction-payment/asset-tx-payment/src/lib.rs @@ -202,7 +202,7 @@ where debug_assert!(self.tip <= fee, "tip should be included in the computed fee"); if fee.is_zero() { Ok((fee, InitialPayment::Nothing)) - } else if let Some(asset_id) = self.asset_id { + } else if let Some(asset_id) = self.asset_id.clone() { T::OnChargeAssetTransaction::withdraw_fee( who, call, @@ -233,7 +233,7 @@ where debug_assert!(self.tip <= fee, "tip should be included in the computed fee"); if fee.is_zero() { Ok(()) - } else if let Some(asset_id) = self.asset_id { + } else if let Some(asset_id) = self.asset_id.clone() { T::OnChargeAssetTransaction::can_withdraw_fee( who, call, @@ -358,7 +358,7 @@ where tip, who, initial_payment, - asset_id: self.asset_id, + asset_id: self.asset_id.clone(), weight: self.weight(call), }) }, diff --git a/substrate/frame/transaction-payment/asset-tx-payment/src/payment.rs b/substrate/frame/transaction-payment/asset-tx-payment/src/payment.rs index 2074b1476f45ae9187778a3c846e0b21a1097176..7b7ae855bf8f34bea492b98a912c2094fdd8343d 100644 --- a/substrate/frame/transaction-payment/asset-tx-payment/src/payment.rs +++ b/substrate/frame/transaction-payment/asset-tx-payment/src/payment.rs @@ -40,7 +40,7 @@ pub trait OnChargeAssetTransaction<T: Config> { /// The underlying integer type in which fees are calculated. type Balance: Balance; /// The type used to identify the assets used for transaction payment. - type AssetId: FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo; + type AssetId: FullCodec + Clone + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo; /// The type used to store the intermediate values between pre- and post-dispatch. type LiquidityInfo; @@ -112,7 +112,7 @@ where T: Config, CON: ConversionToAssetBalance<BalanceOf<T>, AssetIdOf<T>, AssetBalanceOf<T>>, HC: HandleCredit<T::AccountId, T::Fungibles>, - AssetIdOf<T>: FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo, + AssetIdOf<T>: FullCodec + Clone + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo, { type Balance = BalanceOf<T>; type AssetId = AssetIdOf<T>; @@ -133,11 +133,14 @@ where // less than one (e.g. 0.5) but gets rounded down by integer division we introduce a minimum // fee. let min_converted_fee = if fee.is_zero() { Zero::zero() } else { One::one() }; - let converted_fee = CON::to_asset_balance(fee, asset_id) + let converted_fee = CON::to_asset_balance(fee, asset_id.clone()) .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))? .max(min_converted_fee); - let can_withdraw = - <T::Fungibles as Inspect<T::AccountId>>::can_withdraw(asset_id, who, converted_fee); + let can_withdraw = <T::Fungibles as Inspect<T::AccountId>>::can_withdraw( + asset_id.clone(), + who, + converted_fee, + ); if can_withdraw != WithdrawConsequence::Success { return Err(InvalidTransaction::Payment.into()) } @@ -167,7 +170,7 @@ where // less than one (e.g. 0.5) but gets rounded down by integer division we introduce a minimum // fee. let min_converted_fee = if fee.is_zero() { Zero::zero() } else { One::one() }; - let converted_fee = CON::to_asset_balance(fee, asset_id) + let converted_fee = CON::to_asset_balance(fee, asset_id.clone()) .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))? .max(min_converted_fee); let can_withdraw = diff --git a/substrate/frame/transaction-payment/asset-tx-payment/src/tests.rs b/substrate/frame/transaction-payment/asset-tx-payment/src/tests.rs index 6de2e8e7da55ce286fe8041bb4a25fe398d8be15..2aa5d8ec7beedb545b9fbbf8942ffbf5360acbdb 100644 --- a/substrate/frame/transaction-payment/asset-tx-payment/src/tests.rs +++ b/substrate/frame/transaction-payment/asset-tx-payment/src/tests.rs @@ -81,6 +81,7 @@ impl ExtBuilder { } else { vec![] }, + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/transaction-payment/skip-feeless-payment/src/lib.rs b/substrate/frame/transaction-payment/skip-feeless-payment/src/lib.rs index dd907f6fcbb74eacfeced2a25222122177b37532..5ba1d1297679eb6cc6b0d905a70a98d57b99a563 100644 --- a/substrate/frame/transaction-payment/skip-feeless-payment/src/lib.rs +++ b/substrate/frame/transaction-payment/skip-feeless-payment/src/lib.rs @@ -46,7 +46,8 @@ use frame_support::{ use scale_info::{StaticTypeInfo, TypeInfo}; use sp_runtime::{ traits::{ - DispatchInfoOf, DispatchOriginOf, PostDispatchInfoOf, TransactionExtension, ValidateResult, + DispatchInfoOf, DispatchOriginOf, Implication, PostDispatchInfoOf, TransactionExtension, + ValidateResult, }, transaction_validity::TransactionValidityError, }; @@ -147,7 +148,7 @@ where info: &DispatchInfoOf<T::RuntimeCall>, len: usize, self_implicit: S::Implicit, - inherited_implication: &impl Encode, + inherited_implication: &impl Implication, source: TransactionSource, ) -> ValidateResult<Self::Val, T::RuntimeCall> { if call.is_feeless(&origin) { diff --git a/substrate/frame/transaction-payment/src/payment.rs b/substrate/frame/transaction-payment/src/payment.rs index 4b39cd3fe53bbc6a68017ae7ba2badf9f9b1a8d6..b8a047fee3e6525b241cf7c1bbba90d5b95c5217 100644 --- a/substrate/frame/transaction-payment/src/payment.rs +++ b/substrate/frame/transaction-payment/src/payment.rs @@ -155,14 +155,15 @@ where if let Some(paid) = already_withdrawn { // Calculate how much refund we should return let refund_amount = paid.peek().saturating_sub(corrected_fee); - // refund to the the account that paid the fees if it exists. otherwise, don't refind - // anything. - let refund_imbalance = if F::total_balance(who) > F::Balance::zero() { - F::deposit(who, refund_amount, Precision::BestEffort) - .unwrap_or_else(|_| Debt::<T::AccountId, F>::zero()) - } else { - Debt::<T::AccountId, F>::zero() - }; + // Refund to the the account that paid the fees if it exists & refund is non-zero. + // Otherwise, don't refund anything. + let refund_imbalance = + if refund_amount > Zero::zero() && F::total_balance(who) > F::Balance::zero() { + F::deposit(who, refund_amount, Precision::BestEffort) + .unwrap_or_else(|_| Debt::<T::AccountId, F>::zero()) + } else { + Debt::<T::AccountId, F>::zero() + }; // merge the imbalance caused by paying the fees and refunding parts of it again. let adjusted_paid: Credit<T::AccountId, F> = paid .offset(refund_imbalance) diff --git a/substrate/frame/transaction-payment/src/tests.rs b/substrate/frame/transaction-payment/src/tests.rs index 572c1d4961dd45ff47463d8d1c291ffb58f31202..8349df306675e1ba529cd3f2ad68c8253ce557fa 100644 --- a/substrate/frame/transaction-payment/src/tests.rs +++ b/substrate/frame/transaction-payment/src/tests.rs @@ -99,6 +99,7 @@ impl ExtBuilder { } else { vec![] }, + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -877,3 +878,40 @@ fn no_fee_and_no_weight_for_other_origins() { assert_eq!(post_info.actual_weight, Some(info.call_weight)); }) } + +#[test] +fn fungible_adapter_no_zero_refund_action() { + type FungibleAdapterT = payment::FungibleAdapter<Balances, DealWithFees>; + + ExtBuilder::default().balance_factor(10).build().execute_with(|| { + System::set_block_number(10); + + let dummy_acc = 1; + let (actual_fee, no_tip) = (10, 0); + let already_paid = <FungibleAdapterT as OnChargeTransaction<Runtime>>::withdraw_fee( + &dummy_acc, + CALL, + &CALL.get_dispatch_info(), + actual_fee, + no_tip, + ).expect("Account must have enough funds."); + + // Correction action with no expected side effect. + assert!(<FungibleAdapterT as OnChargeTransaction<Runtime>>::correct_and_deposit_fee( + &dummy_acc, + &CALL.get_dispatch_info(), + &default_post_info(), + actual_fee, + no_tip, + already_paid, + ).is_ok()); + + // Ensure no zero amount deposit event is emitted. + let events = System::events(); + assert!(!events + .iter() + .any(|record| matches!(record.event, RuntimeEvent::Balances(pallet_balances::Event::Deposit { amount, .. }) if amount.is_zero())), + "No zero amount deposit amount event should be emitted.", + ); + }); +} diff --git a/substrate/frame/transaction-storage/Cargo.toml b/substrate/frame/transaction-storage/Cargo.toml index f5d6bd1c364c43f2b5fe6bd067b9ddaa239aa833..0ca38e9dd60df3a1ef5b299ea3ee81818b5a304d 100644 --- a/substrate/frame/transaction-storage/Cargo.toml +++ b/substrate/frame/transaction-storage/Cargo.toml @@ -18,17 +18,17 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = { optional = true, workspace = true, default-features = true } codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-balances = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } sp-inherents = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } sp-transaction-storage-proof = { workspace = true } -log = { workspace = true } [dev-dependencies] sp-core = { workspace = true } diff --git a/substrate/frame/transaction-storage/src/mock.rs b/substrate/frame/transaction-storage/src/mock.rs index 73174b73dbacc7c803ad947c3108165688832767..25f44b953bfb249627416c78e4fdee92686a3994 100644 --- a/substrate/frame/transaction-storage/src/mock.rs +++ b/substrate/frame/transaction-storage/src/mock.rs @@ -21,10 +21,7 @@ use crate::{ self as pallet_transaction_storage, TransactionStorageProof, DEFAULT_MAX_BLOCK_TRANSACTIONS, DEFAULT_MAX_TRANSACTION_SIZE, }; -use frame_support::{ - derive_impl, - traits::{ConstU32, OnFinalize, OnInitialize}, -}; +use frame_support::{derive_impl, traits::ConstU32}; use sp_runtime::{traits::IdentityLookup, BuildStorage}; pub type Block = frame_system::mocking::MockBlock<Test>; @@ -68,6 +65,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { system: Default::default(), balances: pallet_balances::GenesisConfig::<Test> { balances: vec![(1, 1000000000), (2, 100), (3, 100), (4, 100)], + ..Default::default() }, transaction_storage: pallet_transaction_storage::GenesisConfig::<Test> { storage_period: 10, @@ -80,15 +78,13 @@ pub fn new_test_ext() -> sp_io::TestExternalities { t.into() } -pub fn run_to_block(n: u64, f: impl Fn() -> Option<TransactionStorageProof>) { - while System::block_number() < n { - if let Some(proof) = f() { - TransactionStorage::check_proof(RuntimeOrigin::none(), proof).unwrap(); - } - TransactionStorage::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - TransactionStorage::on_initialize(System::block_number()); - } +pub fn run_to_block(n: u64, f: impl Fn() -> Option<TransactionStorageProof> + 'static) { + System::run_to_block_with::<AllPalletsWithSystem>( + n, + frame_system::RunToBlockHooks::default().before_finalize(|_| { + if let Some(proof) = f() { + TransactionStorage::check_proof(RuntimeOrigin::none(), proof).unwrap(); + } + }), + ); } diff --git a/substrate/frame/treasury/Cargo.toml b/substrate/frame/treasury/Cargo.toml index 93a3d9bea93d1b78f01e19206a4b1b9d31e31ba4..c6f059f5fa034818802ad99aa03ce9b7438a1550 100644 --- a/substrate/frame/treasury/Cargo.toml +++ b/substrate/frame/treasury/Cargo.toml @@ -21,21 +21,21 @@ codec = { features = [ "max-encoded-len", ], workspace = true } docify = { workspace = true } -impl-trait-for-tuples = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { features = ["derive"], optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +impl-trait-for-tuples = { workspace = true } +log = { workspace = true } pallet-balances = { workspace = true } -sp-runtime = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } sp-core = { optional = true, workspace = true } -log = { workspace = true } +sp-runtime = { workspace = true } [dev-dependencies] -sp-io = { workspace = true, default-features = true } pallet-utility = { workspace = true, default-features = true } sp-core = { workspace = true } +sp-io = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/treasury/src/tests.rs b/substrate/frame/treasury/src/tests.rs index e9efb7c0956f196a53239b2ff6eec2a3a705fa57..2c2ceac586249449025cdd8cccc6ef69fca25849 100644 --- a/substrate/frame/treasury/src/tests.rs +++ b/substrate/frame/treasury/src/tests.rs @@ -221,6 +221,7 @@ impl ExtBuilder { pallet_balances::GenesisConfig::<Test> { // Total issuance will be 200 with treasury account initialized at ED. balances: vec![(0, 100), (1, 98), (2, 1)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -406,9 +407,12 @@ fn treasury_account_doesnt_get_deleted() { #[test] fn inexistent_account_works() { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); - pallet_balances::GenesisConfig::<Test> { balances: vec![(0, 100), (1, 99), (2, 1)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig::<Test> { + balances: vec![(0, 100), (1, 99), (2, 1)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); // Treasury genesis config is not build thus treasury account does not exist let mut t: sp_io::TestExternalities = t.into(); @@ -445,6 +449,7 @@ fn genesis_funding_works() { pallet_balances::GenesisConfig::<Test> { // Total issuance will be 200 with treasury account initialized with 100. balances: vec![(0, 100), (Treasury::account_id(), initial_funding)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/tx-pause/Cargo.toml b/substrate/frame/tx-pause/Cargo.toml index 03c700ec053cabf37586bdfa57a5e991946fb195..6298645fb2b3f086c38be8db8a60b84868050c61 100644 --- a/substrate/frame/tx-pause/Cargo.toml +++ b/substrate/frame/tx-pause/Cargo.toml @@ -20,18 +20,18 @@ docify = { workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -sp-runtime = { workspace = true } pallet-balances = { optional = true, workspace = true } -pallet-utility = { optional = true, workspace = true } pallet-proxy = { optional = true, workspace = true } +pallet-utility = { optional = true, workspace = true } +scale-info = { features = ["derive"], workspace = true } +sp-runtime = { workspace = true } [dev-dependencies] -sp-core = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } -pallet-utility = { workspace = true, default-features = true } pallet-proxy = { workspace = true, default-features = true } +pallet-utility = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/tx-pause/src/mock.rs b/substrate/frame/tx-pause/src/mock.rs index fd9b3b552ccdfd2703f1e97bd4afd6292de7fc11..d543f447ca7a541c579ad8c4e01e2c07eac5099a 100644 --- a/substrate/frame/tx-pause/src/mock.rs +++ b/substrate/frame/tx-pause/src/mock.rs @@ -157,6 +157,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig::<Test> { // The 0 account is NOT a special origin. The rest may be: balances: vec![(0, 1234), (1, 5678), (2, 5678), (3, 5678), (4, 5678)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/uniques/Cargo.toml b/substrate/frame/uniques/Cargo.toml index abd456d97556beca5497fe501b22d9c54ae0c0eb..a2473c51ee75cc687c4a8e8bed9f9aa7cf6fdfee 100644 --- a/substrate/frame/uniques/Cargo.toml +++ b/substrate/frame/uniques/Cargo.toml @@ -17,18 +17,17 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/utility/Cargo.toml b/substrate/frame/utility/Cargo.toml index e2d35fc1699f1b774b7f3c48c34bf2b15572f4ae..c9a4432648eaf94adb6c9482c5bbc9658a34d06c 100644 --- a/substrate/frame/utility/Cargo.toml +++ b/substrate/frame/utility/Cargo.toml @@ -17,18 +17,18 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } -pallet-root-testing = { workspace = true, default-features = true } pallet-collective = { workspace = true, default-features = true } +pallet-root-testing = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } diff --git a/substrate/frame/utility/src/tests.rs b/substrate/frame/utility/src/tests.rs index 274a90d77cf06c90fba30fe1b74c97e2f4530ee5..d075ec1ff82e3537abec09a8587e841702f908d0 100644 --- a/substrate/frame/utility/src/tests.rs +++ b/substrate/frame/utility/src/tests.rs @@ -237,6 +237,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); pallet_balances::GenesisConfig::<Test> { balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 2)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/verify-signature/Cargo.toml b/substrate/frame/verify-signature/Cargo.toml index 3c5fd5e651575fdc7fba0ebdced070be25f17589..453424bbec7a8696b1013d1d2ba847857c427179 100644 --- a/substrate/frame/verify-signature/Cargo.toml +++ b/substrate/frame/verify-signature/Cargo.toml @@ -17,20 +17,16 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } sp-weights = { features = ["serde"], workspace = true } [dev-dependencies] -pallet-balances = { workspace = true, default-features = true } -pallet-root-testing = { workspace = true, default-features = true } -pallet-collective = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } [features] @@ -40,10 +36,6 @@ std = [ "frame-benchmarking?/std", "frame-support/std", "frame-system/std", - "pallet-balances/std", - "pallet-collective/std", - "pallet-root-testing/std", - "pallet-timestamp/std", "scale-info/std", "sp-core/std", "sp-io/std", @@ -54,17 +46,10 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "pallet-collective/runtime-benchmarks", - "pallet-timestamp/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "pallet-balances/try-runtime", - "pallet-collective/try-runtime", - "pallet-root-testing/try-runtime", - "pallet-timestamp/try-runtime", "sp-runtime/try-runtime", ] diff --git a/substrate/frame/vesting/Cargo.toml b/substrate/frame/vesting/Cargo.toml index f896c3962eaacd28c397bbd373d3f7098ef9d4b1..882ce5f813730fb7bc73a171b2b4e071947f7bbd 100644 --- a/substrate/frame/vesting/Cargo.toml +++ b/substrate/frame/vesting/Cargo.toml @@ -19,11 +19,11 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } [dev-dependencies] diff --git a/substrate/frame/vesting/src/mock.rs b/substrate/frame/vesting/src/mock.rs index f0954a5b989c8a20d1fbb7feba9e49b20d61a27c..8fae9bbf7497445f67a4c8c5d810fa4e4786737f 100644 --- a/substrate/frame/vesting/src/mock.rs +++ b/substrate/frame/vesting/src/mock.rs @@ -94,6 +94,7 @@ impl ExtBuilder { (12, 10 * self.existential_deposit), (13, 9999 * self.existential_deposit), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/whitelist/Cargo.toml b/substrate/frame/whitelist/Cargo.toml index a347174ed2ebee4fc1cc02e545b4e57c90372e24..68ecc5d0d78e804f43e544f32be746490711dc1f 100644 --- a/substrate/frame/whitelist/Cargo.toml +++ b/substrate/frame/whitelist/Cargo.toml @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive", "max-encoded-len"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-api = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/primitives/api/Cargo.toml b/substrate/primitives/api/Cargo.toml index e0a4d06b2d81626ca95834217c3a109580bb7e74..7295adbc11caf681929bc2708f9c9610d9e54691 100644 --- a/substrate/primitives/api/Cargo.toml +++ b/substrate/primitives/api/Cargo.toml @@ -17,22 +17,22 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } +docify = { workspace = true } +hash-db = { optional = true, workspace = true, default-features = true } +log = { workspace = true } +scale-info = { features = [ + "derive", +], workspace = true } sp-api-proc-macro = { workspace = true } sp-core = { workspace = true } +sp-externalities = { optional = true, workspace = true } +sp-metadata-ir = { optional = true, workspace = true } sp-runtime = { workspace = true } sp-runtime-interface = { workspace = true } -sp-externalities = { optional = true, workspace = true } -sp-version = { workspace = true } sp-state-machine = { optional = true, workspace = true } sp-trie = { optional = true, workspace = true } -hash-db = { optional = true, workspace = true, default-features = true } +sp-version = { workspace = true } thiserror = { optional = true, workspace = true } -scale-info = { features = [ - "derive", -], workspace = true } -sp-metadata-ir = { optional = true, workspace = true } -log = { workspace = true } -docify = { workspace = true } [dev-dependencies] sp-test-primitives = { workspace = true } diff --git a/substrate/primitives/api/proc-macro/Cargo.toml b/substrate/primitives/api/proc-macro/Cargo.toml index 191578f432adb84f2570669dbfa26ae68dda5dbf..2f414597fb74461106db50c6a4351a8c44dea8f5 100644 --- a/substrate/primitives/api/proc-macro/Cargo.toml +++ b/substrate/primitives/api/proc-macro/Cargo.toml @@ -19,13 +19,13 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -quote = { workspace = true } -syn = { features = ["extra-traits", "fold", "full", "visit", "visit-mut"], workspace = true } -proc-macro2 = { workspace = true } +Inflector = { workspace = true } blake2 = { workspace = true } -proc-macro-crate = { workspace = true } expander = { workspace = true } -Inflector = { workspace = true } +proc-macro-crate = { workspace = true } +proc-macro2 = { workspace = true } +quote = { workspace = true } +syn = { features = ["extra-traits", "fold", "full", "visit", "visit-mut"], workspace = true } [dev-dependencies] assert_matches = { workspace = true } diff --git a/substrate/primitives/api/proc-macro/src/runtime_metadata.rs b/substrate/primitives/api/proc-macro/src/runtime_metadata.rs index 6be396339259866cb5ad8d9d63eab25bf2eb43f0..1706f8ca6fbb46fb71d8011a294c97f832c4d7c8 100644 --- a/substrate/primitives/api/proc-macro/src/runtime_metadata.rs +++ b/substrate/primitives/api/proc-macro/src/runtime_metadata.rs @@ -298,18 +298,14 @@ pub fn generate_impl_runtime_metadata(impls: &[ItemImpl]) -> Result<TokenStream2 // Therefore, the `Deref` trait will resolve the `runtime_metadata` from `impl_runtime_apis!` // when both macros are called; and will resolve an empty `runtime_metadata` when only the // `construct_runtime!` is called. - Ok(quote!( #crate_::frame_metadata_enabled! { #[doc(hidden)] - trait InternalImplRuntimeApis { - #[inline(always)] + impl #crate_::metadata_ir::InternalImplRuntimeApis for #runtime_name { fn runtime_metadata(&self) -> #crate_::vec::Vec<#crate_::metadata_ir::RuntimeApiMetadataIR> { #crate_::vec![ #( #metadata, )* ] } } - #[doc(hidden)] - impl InternalImplRuntimeApis for #runtime_name {} } )) } diff --git a/substrate/primitives/api/src/lib.rs b/substrate/primitives/api/src/lib.rs index b412d4b52fed5208d0d2fe8ace56977822b3b55f..8909d2b2e48617d48d9a8ebd39fc25a23e7d3d6f 100644 --- a/substrate/primitives/api/src/lib.rs +++ b/substrate/primitives/api/src/lib.rs @@ -666,7 +666,7 @@ pub struct CallApiAtParams<'a, Block: BlockT> { pub extensions: &'a RefCell<Extensions>, } -/// Something that can call into the an api at a given block. +/// Something that can call into an api at a given block. #[cfg(feature = "std")] pub trait CallApiAt<Block: BlockT> { /// The state backend that is used to store the block states. diff --git a/substrate/primitives/api/test/Cargo.toml b/substrate/primitives/api/test/Cargo.toml index 1d21f23eb8042200030c64295665d1b32c5730f0..9b02cf125eae4192e3fa8ca4ff459e24a667cc97 100644 --- a/substrate/primitives/api/test/Cargo.toml +++ b/substrate/primitives/api/test/Cargo.toml @@ -15,18 +15,19 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { workspace = true, default-features = true } +rustversion = { workspace = true } +sc-block-builder = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } sp-api = { workspace = true, default-features = true } -substrate-test-runtime-client = { workspace = true } -sp-version = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } -sc-block-builder = { workspace = true, default-features = true } -codec = { workspace = true, default-features = true } +sp-metadata-ir = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } +sp-version = { workspace = true, default-features = true } +substrate-test-runtime-client = { workspace = true } trybuild = { workspace = true } -rustversion = { workspace = true } -scale-info = { features = ["derive"], workspace = true } [dev-dependencies] criterion = { workspace = true, default-features = true } @@ -40,5 +41,5 @@ name = "bench" harness = false [features] -"enable-staging-api" = [] +enable-staging-api = [] disable-ui-tests = [] diff --git a/substrate/primitives/api/test/tests/decl_and_impl.rs b/substrate/primitives/api/test/tests/decl_and_impl.rs index 890cf6eccdbcb13bd23a789f89ccc6c430c18342..2e5a078cb3828abb15dbb5a463d978811df6c959 100644 --- a/substrate/primitives/api/test/tests/decl_and_impl.rs +++ b/substrate/primitives/api/test/tests/decl_and_impl.rs @@ -309,6 +309,8 @@ fn mock_runtime_api_works_with_advanced() { #[test] fn runtime_api_metadata_matches_version_implemented() { + use sp_metadata_ir::InternalImplRuntimeApis; + let rt = Runtime {}; let runtime_metadata = rt.runtime_metadata(); diff --git a/substrate/primitives/api/test/tests/runtime_calls.rs b/substrate/primitives/api/test/tests/runtime_calls.rs index 5a524d1c7f4d3c6e31b2fee19e2af57e1c6365f8..0470b8b72aa0462ff7c7beb2ee45e7afc695f375 100644 --- a/substrate/primitives/api/test/tests/runtime_calls.rs +++ b/substrate/primitives/api/test/tests/runtime_calls.rs @@ -99,8 +99,8 @@ fn record_proof_works() { let transaction = Transfer { amount: 1000, nonce: 0, - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Bob.into(), } .into_unchecked_extrinsic(); diff --git a/substrate/primitives/application-crypto/Cargo.toml b/substrate/primitives/application-crypto/Cargo.toml index 1161d43ded5a87ad0f6a647a940b4972b4156d5d..9589cce042f55fb07cbe6ef5bbffd82734d87524 100644 --- a/substrate/primitives/application-crypto/Cargo.toml +++ b/substrate/primitives/application-crypto/Cargo.toml @@ -18,10 +18,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-core = { workspace = true } codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { optional = true, features = ["alloc", "derive"], workspace = true } +sp-core = { workspace = true } sp-io = { workspace = true } [features] diff --git a/substrate/primitives/arithmetic/Cargo.toml b/substrate/primitives/arithmetic/Cargo.toml index 485656bf30bb40fc5a3c946c05d530e2a44c19d4..77b82fbe64686b4e043d5d5b2066a76d8d637e68 100644 --- a/substrate/primitives/arithmetic/Cargo.toml +++ b/substrate/primitives/arithmetic/Cargo.toml @@ -21,18 +21,18 @@ codec = { features = [ "derive", "max-encoded-len", ], workspace = true } +docify = { workspace = true } integer-sqrt = { workspace = true } num-traits = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { features = ["alloc", "derive"], optional = true, workspace = true } static_assertions = { workspace = true, default-features = true } -docify = { workspace = true } [dev-dependencies] criterion = { workspace = true, default-features = true } primitive-types = { workspace = true, default-features = true } -sp-crypto-hashing = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/primitives/blockchain/Cargo.toml b/substrate/primitives/blockchain/Cargo.toml index 93158274d98f95af1c73aed1a2e9d90670a49094..aed09a684bdae678effd3bc583d9a747a03f32c5 100644 --- a/substrate/primitives/blockchain/Cargo.toml +++ b/substrate/primitives/blockchain/Cargo.toml @@ -21,11 +21,11 @@ codec = { features = ["derive"], workspace = true } futures = { workspace = true } parking_lot = { workspace = true, default-features = true } schnellru = { workspace = true } -thiserror = { workspace = true } sp-api = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-database = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } +thiserror = { workspace = true } tracing = { workspace = true, default-features = true } diff --git a/substrate/primitives/consensus/beefy/Cargo.toml b/substrate/primitives/consensus/beefy/Cargo.toml index 13d80683c853da5db00efe896161158a2a06041a..572e46d8de8d85cffcb8880ce82c2666f00af5db 100644 --- a/substrate/primitives/consensus/beefy/Cargo.toml +++ b/substrate/primitives/consensus/beefy/Cargo.toml @@ -23,9 +23,9 @@ sp-application-crypto = { workspace = true } sp-core = { workspace = true } sp-crypto-hashing = { workspace = true } sp-io = { workspace = true } +sp-keystore = { workspace = true } sp-mmr-primitives = { workspace = true } sp-runtime = { workspace = true } -sp-keystore = { workspace = true } sp-weights = { workspace = true } strum = { features = ["derive"], workspace = true } diff --git a/substrate/primitives/consensus/beefy/src/lib.rs b/substrate/primitives/consensus/beefy/src/lib.rs index e977fb0ea25f6a2bcdd1d7fadb0364332a1f710d..0f57cdfc810420176c9354fd2bb29662f2a198cc 100644 --- a/substrate/primitives/consensus/beefy/src/lib.rs +++ b/substrate/primitives/consensus/beefy/src/lib.rs @@ -449,6 +449,9 @@ pub trait AncestryHelper<Header: HeaderT> { best_known_block_number: Option<Header::Number>, ) -> Option<Self::Proof>; + /// Check if the proof is optimal. + fn is_proof_optimal(proof: &Self::Proof) -> bool; + /// Extract the validation context from the provided header. fn extract_validation_context(header: Header) -> Option<Self::ValidationContext>; @@ -463,6 +466,9 @@ pub trait AncestryHelper<Header: HeaderT> { /// Weight information for the logic in `AncestryHelper`. pub trait AncestryHelperWeightInfo<Header: HeaderT>: AncestryHelper<Header> { + /// Weight info for the `AncestryHelper::is_proof_optimal()` method. + fn is_proof_optimal(proof: &<Self as AncestryHelper<Header>>::Proof) -> Weight; + /// Weight info for the `AncestryHelper::extract_validation_context()` method. fn extract_validation_context() -> Weight; diff --git a/substrate/primitives/consensus/common/Cargo.toml b/substrate/primitives/consensus/common/Cargo.toml index 764ef1d973460b499cc90eee19183747a11f37e3..3a6ffd031ec51d806c7188c32c90ee93800dba0c 100644 --- a/substrate/primitives/consensus/common/Cargo.toml +++ b/substrate/primitives/consensus/common/Cargo.toml @@ -20,11 +20,11 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = { workspace = true } futures = { features = ["thread-pool"], workspace = true } log = { workspace = true, default-features = true } -thiserror = { workspace = true } sp-core = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] futures = { workspace = true } diff --git a/substrate/primitives/core/Cargo.toml b/substrate/primitives/core/Cargo.toml index f6bc17bccacaf48354ad43dcd327c2577a35ad66..0ea885abd22d75e158eefd393f167c35971c333d 100644 --- a/substrate/primitives/core/Cargo.toml +++ b/substrate/primitives/core/Cargo.toml @@ -16,47 +16,47 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { features = ["derive", "max-encoded-len"], workspace = true } -scale-info = { features = ["derive"], workspace = true } -log = { workspace = true } -serde = { optional = true, features = ["alloc", "derive"], workspace = true } bounded-collections = { workspace = true } -primitive-types = { features = ["codec", "scale-info"], workspace = true } -impl-serde = { optional = true, workspace = true } +bs58 = { optional = true, workspace = true } +codec = { features = ["derive", "max-encoded-len"], workspace = true } hash-db = { workspace = true } hash256-std-hasher = { workspace = true } -bs58 = { optional = true, workspace = true } +impl-serde = { optional = true, workspace = true } +log = { workspace = true } +primitive-types = { features = ["codec", "scale-info"], workspace = true } rand = { features = [ "small_rng", ], optional = true, workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, features = ["alloc", "derive"], workspace = true } substrate-bip39 = { workspace = true } # personal fork here as workaround for: https://github.com/rust-bitcoin/rust-bip39/pull/64 bip39 = { package = "parity-bip39", version = "2.0.1", default-features = false, features = [ "alloc", ] } -zeroize = { workspace = true } -secrecy = { features = ["alloc"], workspace = true } +bitflags = { workspace = true } +dyn-clonable = { optional = true, workspace = true } +futures = { optional = true, workspace = true } +itertools = { optional = true, workspace = true } parking_lot = { optional = true, workspace = true, default-features = true } -ss58-registry = { workspace = true } -sp-std = { workspace = true } +paste = { workspace = true, default-features = true } +secrecy = { features = ["alloc"], workspace = true } sp-debug-derive = { workspace = true } -sp-storage = { workspace = true } sp-externalities = { optional = true, workspace = true } -futures = { optional = true, workspace = true } -dyn-clonable = { optional = true, workspace = true } +sp-std = { workspace = true } +sp-storage = { workspace = true } +ss58-registry = { workspace = true } thiserror = { optional = true, workspace = true } tracing = { optional = true, workspace = true, default-features = true } -bitflags = { workspace = true } -paste = { workspace = true, default-features = true } -itertools = { optional = true, workspace = true } +zeroize = { workspace = true } # full crypto array-bytes = { workspace = true, default-features = true } -ed25519-zebra = { workspace = true } blake2 = { optional = true, workspace = true } +ed25519-zebra = { workspace = true } libsecp256k1 = { features = ["static-context"], workspace = true } -schnorrkel = { features = ["preaudit_deprecated"], workspace = true } merlin = { workspace = true } +schnorrkel = { features = ["preaudit_deprecated"], workspace = true } sp-crypto-hashing = { workspace = true } sp-runtime-interface = { workspace = true } # k256 crate, better portability, intended to be used in substrate-runtimes (no-std) @@ -76,8 +76,8 @@ bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", rev = "0fef826", [dev-dependencies] criterion = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } regex = { workspace = true } +serde_json = { workspace = true, default-features = true } [[bench]] name = "bench" diff --git a/substrate/primitives/crypto/ec-utils/Cargo.toml b/substrate/primitives/crypto/ec-utils/Cargo.toml index 29e30133ebead7a32180aadaacc76fe5d6972323..1e5964f855758a818aa099bad2436b1af6709fb7 100644 --- a/substrate/primitives/crypto/ec-utils/Cargo.toml +++ b/substrate/primitives/crypto/ec-utils/Cargo.toml @@ -15,17 +15,17 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -ark-ec = { optional = true, workspace = true } -ark-bls12-377-ext = { optional = true, workspace = true } ark-bls12-377 = { features = ["curve"], optional = true, workspace = true } -ark-bls12-381-ext = { optional = true, workspace = true } +ark-bls12-377-ext = { optional = true, workspace = true } ark-bls12-381 = { features = ["curve"], optional = true, workspace = true } -ark-bw6-761-ext = { optional = true, workspace = true } +ark-bls12-381-ext = { optional = true, workspace = true } ark-bw6-761 = { optional = true, workspace = true } -ark-ed-on-bls12-381-bandersnatch-ext = { optional = true, workspace = true } -ark-ed-on-bls12-381-bandersnatch = { optional = true, workspace = true } -ark-ed-on-bls12-377-ext = { optional = true, workspace = true } +ark-bw6-761-ext = { optional = true, workspace = true } +ark-ec = { optional = true, workspace = true } ark-ed-on-bls12-377 = { optional = true, workspace = true } +ark-ed-on-bls12-377-ext = { optional = true, workspace = true } +ark-ed-on-bls12-381-bandersnatch = { optional = true, workspace = true } +ark-ed-on-bls12-381-bandersnatch-ext = { optional = true, workspace = true } ark-scale = { features = ["hazmat"], optional = true, workspace = true } sp-runtime-interface = { optional = true, workspace = true } diff --git a/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml b/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml index 6f974a3e2c8ab9edebb41bcd1c6278291c596766..e09661d41c1111d7b85cb818e1650b245cadbe9d 100644 --- a/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml +++ b/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml @@ -20,5 +20,5 @@ proc-macro = true [dependencies] quote = { workspace = true } -syn = { features = ["full", "parsing"], workspace = true } sp-crypto-hashing = { workspace = true } +syn = { features = ["full", "parsing"], workspace = true } diff --git a/substrate/primitives/debug-derive/Cargo.toml b/substrate/primitives/debug-derive/Cargo.toml index 4979b89155ab3e87e5fc9804f0a89ac0dab93d16..a26cbbf62adac467ed8e3a16b8c55fcff43ea0d8 100644 --- a/substrate/primitives/debug-derive/Cargo.toml +++ b/substrate/primitives/debug-derive/Cargo.toml @@ -19,9 +19,9 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] +proc-macro2 = { workspace = true } quote = { workspace = true } syn = { workspace = true } -proc-macro2 = { workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/genesis-builder/Cargo.toml b/substrate/primitives/genesis-builder/Cargo.toml index 285b214907ad5a892f5cc33bf38c4539a070fe41..f1fa60d023be4ed527fa81b3b6d24768cac21e6b 100644 --- a/substrate/primitives/genesis-builder/Cargo.toml +++ b/substrate/primitives/genesis-builder/Cargo.toml @@ -19,9 +19,9 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = ["bytes"], workspace = true } scale-info = { features = ["derive"], workspace = true } +serde_json = { features = ["alloc", "arbitrary_precision"], workspace = true } sp-api = { workspace = true } sp-runtime = { workspace = true } -serde_json = { features = ["alloc", "arbitrary_precision"], workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/inherents/Cargo.toml b/substrate/primitives/inherents/Cargo.toml index 271308c9cbf1cc60ba7cd44ab1873cbd61c8a424..19966919047f9b2fbcb0db25c000fb416566252d 100644 --- a/substrate/primitives/inherents/Cargo.toml +++ b/substrate/primitives/inherents/Cargo.toml @@ -19,10 +19,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = { optional = true, workspace = true } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } impl-trait-for-tuples = { workspace = true } -thiserror = { optional = true, workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { optional = true, workspace = true } +thiserror = { optional = true, workspace = true } [dev-dependencies] futures = { workspace = true } diff --git a/substrate/primitives/io/Cargo.toml b/substrate/primitives/io/Cargo.toml index 97940759a98701f637452ab9590ab6a511df19a2..b0c99002910b01868cf7eef69d292bebf28b98fa 100644 --- a/substrate/primitives/io/Cargo.toml +++ b/substrate/primitives/io/Cargo.toml @@ -22,20 +22,20 @@ bytes = { workspace = true } codec = { features = [ "bytes", ], workspace = true } -sp-core = { workspace = true } -sp-crypto-hashing = { workspace = true } -sp-keystore = { optional = true, workspace = true } libsecp256k1 = { optional = true, workspace = true, default-features = true } -sp-state-machine = { optional = true, workspace = true } -sp-runtime-interface = { workspace = true } -sp-trie = { optional = true, workspace = true } -sp-externalities = { workspace = true } -sp-tracing = { workspace = true } log = { optional = true, workspace = true, default-features = true } secp256k1 = { features = [ "global-context", "recovery", ], optional = true, workspace = true, default-features = true } +sp-core = { workspace = true } +sp-crypto-hashing = { workspace = true } +sp-externalities = { workspace = true } +sp-keystore = { optional = true, workspace = true } +sp-runtime-interface = { workspace = true } +sp-state-machine = { optional = true, workspace = true } +sp-tracing = { workspace = true } +sp-trie = { optional = true, workspace = true } tracing = { workspace = true } tracing-core = { workspace = true } diff --git a/substrate/primitives/keyring/Cargo.toml b/substrate/primitives/keyring/Cargo.toml index 27f7304a935811a3623f5f2303c4304044561458..9ffcf50c7b455f7dce392238c941ff4c50866c60 100644 --- a/substrate/primitives/keyring/Cargo.toml +++ b/substrate/primitives/keyring/Cargo.toml @@ -17,9 +17,9 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -strum = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } +strum = { features = ["derive"], workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/keyring/src/lib.rs b/substrate/primitives/keyring/src/lib.rs index 008c01b150f00fd0215d5db01064474303fb4655..36e77dabd601c2c57030f3cb4f37cee1526837f8 100644 --- a/substrate/primitives/keyring/src/lib.rs +++ b/substrate/primitives/keyring/src/lib.rs @@ -32,20 +32,11 @@ pub mod ed25519; #[cfg(feature = "bandersnatch-experimental")] pub mod bandersnatch; -/// Convenience export: Sr25519's Keyring is exposed as `AccountKeyring`, since it tends to be -/// used for accounts (although it may also be used by authorities). -pub use sr25519::Keyring as AccountKeyring; - #[cfg(feature = "bandersnatch-experimental")] pub use bandersnatch::Keyring as BandersnatchKeyring; pub use ed25519::Keyring as Ed25519Keyring; pub use sr25519::Keyring as Sr25519Keyring; -pub mod test { - /// The keyring for use with accounts when using the test runtime. - pub use super::ed25519::Keyring as AccountKeyring; -} - #[derive(Debug)] /// Represents an error that occurs when parsing a string into a `KeyRing`. pub struct ParseKeyringError; diff --git a/substrate/primitives/merkle-mountain-range/Cargo.toml b/substrate/primitives/merkle-mountain-range/Cargo.toml index 6f944a3f6a8da13d218731eff817cef9021c59f1..0d8a67da7cad1dc9a2f356ffbca4beb553b36042 100644 --- a/substrate/primitives/merkle-mountain-range/Cargo.toml +++ b/substrate/primitives/merkle-mountain-range/Cargo.toml @@ -16,9 +16,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } log = { workspace = true } -mmr-lib = { package = "polkadot-ckb-merkle-mountain-range", version = "0.7.0", default-features = false } +mmr-lib = { package = "polkadot-ckb-merkle-mountain-range", version = "0.8.1", default-features = false } +scale-info = { features = ["derive"], workspace = true } serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-api = { workspace = true } sp-core = { workspace = true } diff --git a/substrate/primitives/metadata-ir/Cargo.toml b/substrate/primitives/metadata-ir/Cargo.toml index d7786347dd02822658cbd633a69eaf90414db22f..046441104b88b7e7bcfac7c0e780b2653fc3d42f 100644 --- a/substrate/primitives/metadata-ir/Cargo.toml +++ b/substrate/primitives/metadata-ir/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -frame-metadata = { features = ["current"], workspace = true } +frame-metadata = { features = ["current", "unstable"], workspace = true } scale-info = { features = ["derive"], workspace = true } [features] diff --git a/substrate/primitives/metadata-ir/src/lib.rs b/substrate/primitives/metadata-ir/src/lib.rs index 4bd13b935afda483e40fe9898ecc5d84e18f95e6..e048010a34b75a7facb2cd0abe019eb305734096 100644 --- a/substrate/primitives/metadata-ir/src/lib.rs +++ b/substrate/primitives/metadata-ir/src/lib.rs @@ -30,6 +30,7 @@ mod types; use frame_metadata::RuntimeMetadataPrefixed; pub use types::*; +mod unstable; mod v14; mod v15; @@ -39,23 +40,33 @@ const V14: u32 = 14; /// Metadata V15. const V15: u32 = 15; +/// Unstable metadata V16. +const UNSTABLE_V16: u32 = u32::MAX; + /// Transform the IR to the specified version. /// /// Use [`supported_versions`] to find supported versions. pub fn into_version(metadata: MetadataIR, version: u32) -> Option<RuntimeMetadataPrefixed> { // Note: Unstable metadata version is `u32::MAX` until stabilized. match version { - // Latest stable version. + // Version V14. This needs to be around until the + // deprecation of the `Metadata_metadata` runtime call in favor of + // `Metadata_metadata_at_version. V14 => Some(into_v14(metadata)), - // Unstable metadata. + + // Version V15 - latest stable. V15 => Some(into_latest(metadata)), + + // Unstable metadata under `u32::MAX`. + UNSTABLE_V16 => Some(into_unstable(metadata)), + _ => None, } } /// Returns the supported metadata versions. pub fn supported_versions() -> alloc::vec::Vec<u32> { - alloc::vec![V14, V15] + alloc::vec![V14, V15, UNSTABLE_V16] } /// Transform the IR to the latest stable metadata version. @@ -70,6 +81,22 @@ pub fn into_v14(metadata: MetadataIR) -> RuntimeMetadataPrefixed { latest.into() } +/// Transform the IR to unstable metadata version 16. +pub fn into_unstable(metadata: MetadataIR) -> RuntimeMetadataPrefixed { + let latest: frame_metadata::v16::RuntimeMetadataV16 = metadata.into(); + latest.into() +} + +/// INTERNAL USE ONLY +/// +/// Special trait that is used together with `InternalConstructRuntime` by `construct_runtime!` to +/// fetch the runtime api metadata without exploding when there is no runtime api implementation +/// available. +#[doc(hidden)] +pub trait InternalImplRuntimeApis { + fn runtime_metadata(&self) -> alloc::vec::Vec<RuntimeApiMetadataIR>; +} + #[cfg(test)] mod test { use super::*; @@ -81,7 +108,7 @@ mod test { pallets: vec![], extrinsic: ExtrinsicMetadataIR { ty: meta_type::<()>(), - version: 0, + versions: vec![0], address_ty: meta_type::<()>(), call_ty: meta_type::<()>(), signature_ty: meta_type::<()>(), @@ -95,6 +122,7 @@ mod test { event_enum_ty: meta_type::<()>(), error_enum_ty: meta_type::<()>(), }, + view_functions: RuntimeViewFunctionsIR { ty: meta_type::<()>(), groups: vec![] }, } } diff --git a/substrate/primitives/metadata-ir/src/types.rs b/substrate/primitives/metadata-ir/src/types.rs index 199b692fbd8c4e01df23da24823e55acb16176e6..0617fc7dfb94f7d8c397d4ccbcd4219d400c488d 100644 --- a/substrate/primitives/metadata-ir/src/types.rs +++ b/substrate/primitives/metadata-ir/src/types.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use codec::{Compact, Encode}; +use codec::{Compact, Decode, Encode}; use scale_info::{ form::{Form, MetaForm, PortableForm}, prelude::{collections::BTreeMap, vec::Vec}, @@ -41,6 +41,8 @@ pub struct MetadataIR<T: Form = MetaForm> { pub apis: Vec<RuntimeApiMetadataIR<T>>, /// The outer enums types as found in the runtime. pub outer_enums: OuterEnumsIR<T>, + /// Metadata of view function queries + pub view_functions: RuntimeViewFunctionsIR<T>, } /// Metadata of a runtime trait. @@ -118,6 +120,89 @@ impl IntoPortable for RuntimeApiMethodParamMetadataIR { } } +/// Metadata of the top level runtime view function dispatch. +#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)] +pub struct RuntimeViewFunctionsIR<T: Form = MetaForm> { + /// The type implementing the runtime query dispatch. + pub ty: T::Type, + /// The view function groupings metadata. + pub groups: Vec<ViewFunctionGroupIR<T>>, +} + +/// Metadata of a runtime view function group. +/// +/// For example, view functions associated with a pallet would form a view function group. +#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)] +pub struct ViewFunctionGroupIR<T: Form = MetaForm> { + /// Name of the view function group. + pub name: T::String, + /// View functions belonging to the group. + pub view_functions: Vec<ViewFunctionMetadataIR<T>>, + /// View function group documentation. + pub docs: Vec<T::String>, +} + +impl IntoPortable for ViewFunctionGroupIR { + type Output = ViewFunctionGroupIR<PortableForm>; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + ViewFunctionGroupIR { + name: self.name.into_portable(registry), + view_functions: registry.map_into_portable(self.view_functions), + docs: registry.map_into_portable(self.docs), + } + } +} + +/// Metadata of a runtime view function. +#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)] +pub struct ViewFunctionMetadataIR<T: Form = MetaForm> { + /// Query name. + pub name: T::String, + /// Query id. + pub id: [u8; 32], + /// Query args. + pub args: Vec<ViewFunctionArgMetadataIR<T>>, + /// Query output. + pub output: T::Type, + /// Query documentation. + pub docs: Vec<T::String>, +} + +impl IntoPortable for ViewFunctionMetadataIR { + type Output = ViewFunctionMetadataIR<PortableForm>; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + ViewFunctionMetadataIR { + name: self.name.into_portable(registry), + id: self.id, + args: registry.map_into_portable(self.args), + output: registry.register_type(&self.output), + docs: registry.map_into_portable(self.docs), + } + } +} + +/// Metadata of a runtime method argument. +#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)] +pub struct ViewFunctionArgMetadataIR<T: Form = MetaForm> { + /// Query argument name. + pub name: T::String, + /// Query argument type. + pub ty: T::Type, +} + +impl IntoPortable for ViewFunctionArgMetadataIR { + type Output = ViewFunctionArgMetadataIR<PortableForm>; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + ViewFunctionArgMetadataIR { + name: self.name.into_portable(registry), + ty: registry.register_type(&self.ty), + } + } +} + /// The intermediate representation for a pallet metadata. #[derive(Clone, PartialEq, Eq, Encode, Debug)] pub struct PalletMetadataIR<T: Form = MetaForm> { @@ -170,8 +255,8 @@ pub struct ExtrinsicMetadataIR<T: Form = MetaForm> { /// /// Note: Field used for metadata V14 only. pub ty: T::Type, - /// Extrinsic version. - pub version: u8, + /// Extrinsic versions. + pub versions: Vec<u8>, /// The type of the address that signs the extrinsic pub address_ty: T::Type, /// The type of the outermost Call enum. @@ -191,7 +276,7 @@ impl IntoPortable for ExtrinsicMetadataIR { fn into_portable(self, registry: &mut Registry) -> Self::Output { ExtrinsicMetadataIR { ty: registry.register_type(&self.ty), - version: self.version, + versions: self.versions, address_ty: registry.register_type(&self.address_ty), call_ty: registry.register_type(&self.call_ty), signature_ty: registry.register_type(&self.signature_ty), diff --git a/substrate/primitives/metadata-ir/src/unstable.rs b/substrate/primitives/metadata-ir/src/unstable.rs new file mode 100644 index 0000000000000000000000000000000000000000..d46ce3ec6a7d470abca872501fad3c7d0632ad32 --- /dev/null +++ b/substrate/primitives/metadata-ir/src/unstable.rs @@ -0,0 +1,211 @@ +// 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. + +//! Convert the IR to V16 metadata. + +use crate::{ + DeprecationInfoIR, DeprecationStatusIR, OuterEnumsIR, PalletAssociatedTypeMetadataIR, + PalletCallMetadataIR, PalletConstantMetadataIR, PalletErrorMetadataIR, PalletEventMetadataIR, + PalletStorageMetadataIR, StorageEntryMetadataIR, +}; + +use super::types::{ + ExtrinsicMetadataIR, MetadataIR, PalletMetadataIR, RuntimeApiMetadataIR, + RuntimeApiMethodMetadataIR, RuntimeApiMethodParamMetadataIR, TransactionExtensionMetadataIR, +}; + +use frame_metadata::v16::{ + CustomMetadata, DeprecationInfo, DeprecationStatus, ExtrinsicMetadata, OuterEnums, + PalletAssociatedTypeMetadata, PalletCallMetadata, PalletConstantMetadata, PalletErrorMetadata, + PalletEventMetadata, PalletMetadata, PalletStorageMetadata, RuntimeApiMetadata, + RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata, RuntimeMetadataV16, + StorageEntryMetadata, TransactionExtensionMetadata, +}; + +impl From<MetadataIR> for RuntimeMetadataV16 { + fn from(ir: MetadataIR) -> Self { + RuntimeMetadataV16::new( + ir.pallets.into_iter().map(Into::into).collect(), + ir.extrinsic.into(), + ir.apis.into_iter().map(Into::into).collect(), + ir.outer_enums.into(), + // Substrate does not collect yet the custom metadata fields. + // This allows us to extend the V16 easily. + CustomMetadata { map: Default::default() }, + ) + } +} + +impl From<RuntimeApiMetadataIR> for RuntimeApiMetadata { + fn from(ir: RuntimeApiMetadataIR) -> Self { + RuntimeApiMetadata { + name: ir.name, + methods: ir.methods.into_iter().map(Into::into).collect(), + docs: ir.docs, + deprecation_info: ir.deprecation_info.into(), + } + } +} + +impl From<RuntimeApiMethodMetadataIR> for RuntimeApiMethodMetadata { + fn from(ir: RuntimeApiMethodMetadataIR) -> Self { + RuntimeApiMethodMetadata { + name: ir.name, + inputs: ir.inputs.into_iter().map(Into::into).collect(), + output: ir.output, + docs: ir.docs, + deprecation_info: ir.deprecation_info.into(), + } + } +} + +impl From<RuntimeApiMethodParamMetadataIR> for RuntimeApiMethodParamMetadata { + fn from(ir: RuntimeApiMethodParamMetadataIR) -> Self { + RuntimeApiMethodParamMetadata { name: ir.name, ty: ir.ty } + } +} + +impl From<PalletMetadataIR> for PalletMetadata { + fn from(ir: PalletMetadataIR) -> Self { + PalletMetadata { + name: ir.name, + storage: ir.storage.map(Into::into), + calls: ir.calls.map(Into::into), + event: ir.event.map(Into::into), + constants: ir.constants.into_iter().map(Into::into).collect(), + error: ir.error.map(Into::into), + index: ir.index, + docs: ir.docs, + associated_types: ir.associated_types.into_iter().map(Into::into).collect(), + deprecation_info: ir.deprecation_info.into(), + } + } +} + +impl From<PalletStorageMetadataIR> for PalletStorageMetadata { + fn from(ir: PalletStorageMetadataIR) -> Self { + PalletStorageMetadata { + prefix: ir.prefix, + entries: ir.entries.into_iter().map(Into::into).collect(), + } + } +} + +impl From<StorageEntryMetadataIR> for StorageEntryMetadata { + fn from(ir: StorageEntryMetadataIR) -> Self { + StorageEntryMetadata { + name: ir.name, + modifier: ir.modifier.into(), + ty: ir.ty.into(), + default: ir.default, + docs: ir.docs, + deprecation_info: ir.deprecation_info.into(), + } + } +} + +impl From<PalletAssociatedTypeMetadataIR> for PalletAssociatedTypeMetadata { + fn from(ir: PalletAssociatedTypeMetadataIR) -> Self { + PalletAssociatedTypeMetadata { name: ir.name, ty: ir.ty, docs: ir.docs } + } +} + +impl From<PalletErrorMetadataIR> for PalletErrorMetadata { + fn from(ir: PalletErrorMetadataIR) -> Self { + PalletErrorMetadata { ty: ir.ty, deprecation_info: ir.deprecation_info.into() } + } +} + +impl From<PalletEventMetadataIR> for PalletEventMetadata { + fn from(ir: PalletEventMetadataIR) -> Self { + PalletEventMetadata { ty: ir.ty, deprecation_info: ir.deprecation_info.into() } + } +} + +impl From<PalletCallMetadataIR> for PalletCallMetadata { + fn from(ir: PalletCallMetadataIR) -> Self { + PalletCallMetadata { ty: ir.ty, deprecation_info: ir.deprecation_info.into() } + } +} + +impl From<PalletConstantMetadataIR> for PalletConstantMetadata { + fn from(ir: PalletConstantMetadataIR) -> Self { + PalletConstantMetadata { + name: ir.name, + ty: ir.ty, + value: ir.value, + docs: ir.docs, + deprecation_info: ir.deprecation_info.into(), + } + } +} + +impl From<TransactionExtensionMetadataIR> for TransactionExtensionMetadata { + fn from(ir: TransactionExtensionMetadataIR) -> Self { + TransactionExtensionMetadata { identifier: ir.identifier, ty: ir.ty, implicit: ir.implicit } + } +} + +impl From<ExtrinsicMetadataIR> for ExtrinsicMetadata { + fn from(ir: ExtrinsicMetadataIR) -> Self { + // Assume version 0 for all extensions. + let indexes = (0..ir.extensions.len()).map(|index| index as u32).collect(); + let transaction_extensions_by_version = [(0, indexes)].iter().cloned().collect(); + + ExtrinsicMetadata { + versions: ir.versions, + address_ty: ir.address_ty, + signature_ty: ir.signature_ty, + transaction_extensions_by_version, + transaction_extensions: ir.extensions.into_iter().map(Into::into).collect(), + } + } +} + +impl From<OuterEnumsIR> for OuterEnums { + fn from(ir: OuterEnumsIR) -> Self { + OuterEnums { + call_enum_ty: ir.call_enum_ty, + event_enum_ty: ir.event_enum_ty, + error_enum_ty: ir.error_enum_ty, + } + } +} + +impl From<DeprecationStatusIR> for DeprecationStatus { + fn from(ir: DeprecationStatusIR) -> Self { + match ir { + DeprecationStatusIR::NotDeprecated => DeprecationStatus::NotDeprecated, + DeprecationStatusIR::DeprecatedWithoutNote => DeprecationStatus::DeprecatedWithoutNote, + DeprecationStatusIR::Deprecated { since, note } => + DeprecationStatus::Deprecated { since, note }, + } + } +} + +impl From<DeprecationInfoIR> for DeprecationInfo { + fn from(ir: DeprecationInfoIR) -> Self { + match ir { + DeprecationInfoIR::NotDeprecated => DeprecationInfo::NotDeprecated, + DeprecationInfoIR::ItemDeprecated(status) => + DeprecationInfo::ItemDeprecated(status.into()), + DeprecationInfoIR::VariantsDeprecated(btree) => DeprecationInfo::VariantsDeprecated( + btree.into_iter().map(|(key, value)| (key.0, value.into())).collect(), + ), + } + } +} diff --git a/substrate/primitives/metadata-ir/src/v14.rs b/substrate/primitives/metadata-ir/src/v14.rs index 70e84532add9d2e490638d7630e5c9498848c876..f3cb5973f5bdc79ff507058f00c867576dd5ed34 100644 --- a/substrate/primitives/metadata-ir/src/v14.rs +++ b/substrate/primitives/metadata-ir/src/v14.rs @@ -149,9 +149,12 @@ impl From<TransactionExtensionMetadataIR> for SignedExtensionMetadata { impl From<ExtrinsicMetadataIR> for ExtrinsicMetadata { fn from(ir: ExtrinsicMetadataIR) -> Self { + let lowest_supported_version = + ir.versions.iter().min().expect("Metadata V14 supports one version; qed"); + ExtrinsicMetadata { ty: ir.ty, - version: ir.version, + version: *lowest_supported_version, signed_extensions: ir.extensions.into_iter().map(Into::into).collect(), } } diff --git a/substrate/primitives/metadata-ir/src/v15.rs b/substrate/primitives/metadata-ir/src/v15.rs index 4b3b6106d27f94422d6122df255956434a33f955..7bc76f22b58d004507c900bd139c9c58f1e8dbf3 100644 --- a/substrate/primitives/metadata-ir/src/v15.rs +++ b/substrate/primitives/metadata-ir/src/v15.rs @@ -17,31 +17,39 @@ //! Convert the IR to V15 metadata. -use crate::OuterEnumsIR; - use super::types::{ - ExtrinsicMetadataIR, MetadataIR, PalletMetadataIR, RuntimeApiMetadataIR, + ExtrinsicMetadataIR, MetadataIR, OuterEnumsIR, PalletMetadataIR, RuntimeApiMetadataIR, RuntimeApiMethodMetadataIR, RuntimeApiMethodParamMetadataIR, TransactionExtensionMetadataIR, }; use frame_metadata::v15::{ - CustomMetadata, ExtrinsicMetadata, OuterEnums, PalletMetadata, RuntimeApiMetadata, - RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata, RuntimeMetadataV15, - SignedExtensionMetadata, + CustomMetadata, CustomValueMetadata, ExtrinsicMetadata, OuterEnums, PalletMetadata, + RuntimeApiMetadata, RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata, + RuntimeMetadataV15, SignedExtensionMetadata, }; +use scale_info::{IntoPortable, Registry}; impl From<MetadataIR> for RuntimeMetadataV15 { fn from(ir: MetadataIR) -> Self { - RuntimeMetadataV15::new( - ir.pallets.into_iter().map(Into::into).collect(), - ir.extrinsic.into(), - ir.ty, - ir.apis.into_iter().map(Into::into).collect(), - ir.outer_enums.into(), - // Substrate does not collect yet the custom metadata fields. - // This allows us to extend the V15 easily. - CustomMetadata { map: Default::default() }, - ) + let mut registry = Registry::new(); + let pallets = + registry.map_into_portable(ir.pallets.into_iter().map(Into::<PalletMetadata>::into)); + let extrinsic = Into::<ExtrinsicMetadata>::into(ir.extrinsic).into_portable(&mut registry); + let ty = registry.register_type(&ir.ty); + let apis = + registry.map_into_portable(ir.apis.into_iter().map(Into::<RuntimeApiMetadata>::into)); + let outer_enums = Into::<OuterEnums>::into(ir.outer_enums).into_portable(&mut registry); + + let view_function_groups = registry.map_into_portable(ir.view_functions.groups.into_iter()); + let view_functions_custom_metadata = CustomValueMetadata { + ty: ir.view_functions.ty, + value: codec::Encode::encode(&view_function_groups), + }; + let mut custom_map = alloc::collections::BTreeMap::new(); + custom_map.insert("view_functions_experimental", view_functions_custom_metadata); + let custom = CustomMetadata { map: custom_map }.into_portable(&mut registry); + + Self { types: registry.into(), pallets, extrinsic, ty, apis, outer_enums, custom } } } @@ -100,7 +108,7 @@ impl From<TransactionExtensionMetadataIR> for SignedExtensionMetadata { impl From<ExtrinsicMetadataIR> for ExtrinsicMetadata { fn from(ir: ExtrinsicMetadataIR) -> Self { ExtrinsicMetadata { - version: ir.version, + version: *ir.versions.iter().min().expect("Metadata V15 supports only one version"), address_ty: ir.address_ty, call_ty: ir.call_ty, signature_ty: ir.signature_ty, diff --git a/substrate/primitives/panic-handler/src/lib.rs b/substrate/primitives/panic-handler/src/lib.rs index c4a7eb8dc67c7cf8e4defcb5f60746a16d04cc40..81ccaaee828e4f97ade57f03bf07221557f6d4e0 100644 --- a/substrate/primitives/panic-handler/src/lib.rs +++ b/substrate/primitives/panic-handler/src/lib.rs @@ -30,7 +30,7 @@ use std::{ cell::Cell, io::{self, Write}, marker::PhantomData, - panic::{self, PanicInfo}, + panic::{self, PanicHookInfo}, sync::LazyLock, thread, }; @@ -149,7 +149,7 @@ fn strip_control_codes(input: &str) -> std::borrow::Cow<str> { } /// Function being called when a panic happens. -fn panic_hook(info: &PanicInfo, report_url: &str, version: &str) { +fn panic_hook(info: &PanicHookInfo, report_url: &str, version: &str) { let location = info.location(); let file = location.as_ref().map(|l| l.file()).unwrap_or("<unknown>"); let line = location.as_ref().map(|l| l.line()).unwrap_or(0); diff --git a/substrate/primitives/runtime-interface/Cargo.toml b/substrate/primitives/runtime-interface/Cargo.toml index ee44d90fa959d56e4242e22c7c0cf5f0b1c6067a..2d82838ca0b3b5d75e06a0161906f84f3cc53682 100644 --- a/substrate/primitives/runtime-interface/Cargo.toml +++ b/substrate/primitives/runtime-interface/Cargo.toml @@ -18,26 +18,26 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] bytes = { workspace = true } -sp-wasm-interface = { workspace = true } -sp-std = { workspace = true } -sp-tracing = { workspace = true } -sp-runtime-interface-proc-macro = { workspace = true, default-features = true } -sp-externalities = { workspace = true } codec = { features = ["bytes"], workspace = true } -static_assertions = { workspace = true, default-features = true } +impl-trait-for-tuples = { workspace = true } primitive-types = { workspace = true } +sp-externalities = { workspace = true } +sp-runtime-interface-proc-macro = { workspace = true, default-features = true } +sp-std = { workspace = true } sp-storage = { workspace = true } -impl-trait-for-tuples = { workspace = true } +sp-tracing = { workspace = true } +sp-wasm-interface = { workspace = true } +static_assertions = { workspace = true, default-features = true } [target.'cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), substrate_runtime))'.dependencies] polkavm-derive = { workspace = true } [dev-dependencies] -sp-runtime-interface-test-wasm = { workspace = true } -sp-state-machine = { workspace = true, default-features = true } +rustversion = { workspace = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -rustversion = { workspace = true } +sp-runtime-interface-test-wasm = { workspace = true } +sp-state-machine = { workspace = true, default-features = true } trybuild = { workspace = true } [features] diff --git a/substrate/primitives/runtime-interface/proc-macro/Cargo.toml b/substrate/primitives/runtime-interface/proc-macro/Cargo.toml index 3fd5f073f0254336ae2c7ee12c2e1010a766f3a5..2112d5bc06933e599b02712f16d778518c236940 100644 --- a/substrate/primitives/runtime-interface/proc-macro/Cargo.toml +++ b/substrate/primitives/runtime-interface/proc-macro/Cargo.toml @@ -20,8 +20,8 @@ proc-macro = true [dependencies] Inflector = { workspace = true } +expander = { workspace = true } proc-macro-crate = { workspace = true } proc-macro2 = { workspace = true } quote = { workspace = true } -expander = { workspace = true } syn = { features = ["extra-traits", "fold", "full", "visit"], workspace = true } diff --git a/substrate/primitives/runtime-interface/test/Cargo.toml b/substrate/primitives/runtime-interface/test/Cargo.toml index 29ef0f6b4892d4576a62652e0bd9d47a25d0e1ba..ebcf4222bda39aa983c49271ebefe2e1e24ff2b2 100644 --- a/substrate/primitives/runtime-interface/test/Cargo.toml +++ b/substrate/primitives/runtime-interface/test/Cargo.toml @@ -15,8 +15,6 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -tracing = { workspace = true, default-features = true } -tracing-core = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } sc-executor-common = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } @@ -25,3 +23,5 @@ sp-runtime-interface = { workspace = true, default-features = true } sp-runtime-interface-test-wasm = { workspace = true } sp-runtime-interface-test-wasm-deprecated = { workspace = true } sp-state-machine = { workspace = true, default-features = true } +tracing = { workspace = true, default-features = true } +tracing-core = { workspace = true, default-features = true } diff --git a/substrate/primitives/runtime/Cargo.toml b/substrate/primitives/runtime/Cargo.toml index 8a812c3a5772a4929048ec590440cd0ec27a06db..89c221d574fca3591a03be9d31568d83fbd43a71 100644 --- a/substrate/primitives/runtime/Cargo.toml +++ b/substrate/primitives/runtime/Cargo.toml @@ -17,7 +17,9 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +binary-merkle-tree = { workspace = true } codec = { features = ["derive", "max-encoded-len"], workspace = true } +docify = { workspace = true } either = { workspace = true } hash256-std-hasher = { workspace = true } impl-trait-for-tuples = { workspace = true } @@ -34,9 +36,7 @@ sp-io = { workspace = true } sp-std = { workspace = true } sp-trie = { workspace = true } sp-weights = { workspace = true } -docify = { workspace = true } tracing = { workspace = true, features = ["log"], default-features = false } -binary-merkle-tree = { workspace = true } simple-mermaid = { version = "0.1.1", optional = true } tuplex = { version = "0.1.2", default-features = false } @@ -44,11 +44,11 @@ tuplex = { version = "0.1.2", default-features = false } [dev-dependencies] rand = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } -zstd = { workspace = true } sp-api = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } +zstd = { workspace = true } [features] runtime-benchmarks = [] diff --git a/substrate/primitives/runtime/src/generic/checked_extrinsic.rs b/substrate/primitives/runtime/src/generic/checked_extrinsic.rs index 1842b1631621a9158f09422cb744dc54d27df935..dec818598472334214ba7e8b228530d5259583d0 100644 --- a/substrate/primitives/runtime/src/generic/checked_extrinsic.rs +++ b/substrate/primitives/runtime/src/generic/checked_extrinsic.rs @@ -85,7 +85,6 @@ where match self.format { ExtrinsicFormat::Bare => { let inherent_validation = I::validate_unsigned(source, &self.function)?; - #[allow(deprecated)] let legacy_validation = Extension::bare_validate(&self.function, info, len)?; Ok(legacy_validation.combine_with(inherent_validation)) }, diff --git a/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs b/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs index 91ba37451909b365a138524a475ac9ef96c4041c..6b8471f848466e7c1d7efa8e534c3a9af3b1887b 100644 --- a/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs +++ b/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -389,8 +389,7 @@ where impl<Address, Call: Dispatchable, Signature, Extension: TransactionExtension<Call>> ExtrinsicMetadata for UncheckedExtrinsic<Address, Call, Signature, Extension> { - // TODO: Expose both version 4 and version 5 in metadata v16. - const VERSION: u8 = LEGACY_EXTRINSIC_FORMAT_VERSION; + const VERSIONS: &'static [u8] = &[LEGACY_EXTRINSIC_FORMAT_VERSION, EXTRINSIC_FORMAT_VERSION]; type TransactionExtensions = Extension; } @@ -684,7 +683,7 @@ mod legacy { Extra: Encode, { fn encode(&self) -> Vec<u8> { - let mut tmp = Vec::with_capacity(sp_std::mem::size_of::<Self>()); + let mut tmp = Vec::with_capacity(core::mem::size_of::<Self>()); // 1 byte version id. match self.signature.as_ref() { diff --git a/substrate/primitives/runtime/src/proving_trie/base16.rs b/substrate/primitives/runtime/src/proving_trie/base16.rs index da05c551c6d9478fd393a9daefdc2398c662cbae..abdf6ed178bb8fbd7f4b89c24cf638ddf364515d 100644 --- a/substrate/primitives/runtime/src/proving_trie/base16.rs +++ b/substrate/primitives/runtime/src/proving_trie/base16.rs @@ -26,8 +26,8 @@ use super::{ProofToHashes, ProvingTrie, TrieError}; use crate::{Decode, DispatchError, Encode}; +use alloc::vec::Vec; use codec::MaxEncodedLen; -use sp_std::vec::Vec; use sp_trie::{ trie_types::{TrieDBBuilder, TrieDBMutBuilderV1}, LayoutV1, MemoryDB, Trie, TrieMut, @@ -197,7 +197,7 @@ mod tests { use super::*; use crate::traits::BlakeTwo256; use sp_core::H256; - use sp_std::collections::btree_map::BTreeMap; + use std::collections::BTreeMap; // A trie which simulates a trie of accounts (u32) and balances (u128). type BalanceTrie = BasicProvingTrie<BlakeTwo256, u32, u128>; diff --git a/substrate/primitives/runtime/src/proving_trie/base2.rs b/substrate/primitives/runtime/src/proving_trie/base2.rs index 2b14a59ab056c87dca2352a7824b9f740945e897..8a7cfaa5149df9e5a78fd60f285eecb7022141cf 100644 --- a/substrate/primitives/runtime/src/proving_trie/base2.rs +++ b/substrate/primitives/runtime/src/proving_trie/base2.rs @@ -22,9 +22,9 @@ use super::{ProofToHashes, ProvingTrie, TrieError}; use crate::{Decode, DispatchError, Encode}; +use alloc::{collections::BTreeMap, vec::Vec}; use binary_merkle_tree::{merkle_proof, merkle_root, MerkleProof}; use codec::MaxEncodedLen; -use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; /// A helper structure for building a basic base-2 merkle trie and creating compact proofs for that /// trie. @@ -161,7 +161,7 @@ mod tests { use super::*; use crate::traits::BlakeTwo256; use sp_core::H256; - use sp_std::collections::btree_map::BTreeMap; + use std::collections::BTreeMap; // A trie which simulates a trie of accounts (u32) and balances (u128). type BalanceTrie = BasicProvingTrie<BlakeTwo256, u32, u128>; diff --git a/substrate/primitives/runtime/src/proving_trie/mod.rs b/substrate/primitives/runtime/src/proving_trie/mod.rs index 009aa6d4935fd9231c57a5425365bebdc72411c1..32b2284b4d79d9e7481e8231cda3dfa454c84ffc 100644 --- a/substrate/primitives/runtime/src/proving_trie/mod.rs +++ b/substrate/primitives/runtime/src/proving_trie/mod.rs @@ -23,7 +23,7 @@ pub mod base2; use crate::{Decode, DispatchError, Encode, MaxEncodedLen, TypeInfo}; #[cfg(feature = "serde")] use crate::{Deserialize, Serialize}; -use sp_std::vec::Vec; +use alloc::vec::Vec; use sp_trie::{trie_types::TrieError as SpTrieError, VerifyError}; /// A runtime friendly error type for tries. diff --git a/substrate/primitives/runtime/src/runtime_logger.rs b/substrate/primitives/runtime/src/runtime_logger.rs index 79984b13567250fe7c513c84f28e1276d1cc7ec8..ec5251d978f13a6f5dc39086bf875b1a96fcb85a 100644 --- a/substrate/primitives/runtime/src/runtime_logger.rs +++ b/substrate/primitives/runtime/src/runtime_logger.rs @@ -54,10 +54,10 @@ impl log::Log for RuntimeLogger { fn log(&self, record: &log::Record) { use core::fmt::Write; - let mut w = sp_std::Writer::default(); - let _ = ::core::write!(&mut w, "{}", record.args()); + let mut msg = alloc::string::String::default(); + let _ = ::core::write!(&mut msg, "{}", record.args()); - sp_io::logging::log(record.level().into(), record.target(), w.inner()); + sp_io::logging::log(record.level().into(), record.target(), msg.as_bytes()); } fn flush(&self) {} diff --git a/substrate/primitives/runtime/src/traits/mod.rs b/substrate/primitives/runtime/src/traits/mod.rs index 10a2c9ccb0f7b6ade844ea2751ac92a8f2d5513e..46f17a0fcc6337a9973b13ca03604c9c115f441e 100644 --- a/substrate/primitives/runtime/src/traits/mod.rs +++ b/substrate/primitives/runtime/src/traits/mod.rs @@ -55,7 +55,8 @@ use std::str::FromStr; pub mod transaction_extension; pub use transaction_extension::{ - DispatchTransaction, TransactionExtension, TransactionExtensionMetadata, ValidateResult, + DispatchTransaction, Implication, ImplicationParts, TransactionExtension, + TransactionExtensionMetadata, TxBaseImplication, ValidateResult, }; /// A lazy value. @@ -1410,10 +1411,10 @@ impl SignaturePayload for () { /// Implementor is an [`Extrinsic`] and provides metadata about this extrinsic. pub trait ExtrinsicMetadata { - /// The format version of the `Extrinsic`. + /// The format versions of the `Extrinsic`. /// - /// By format is meant the encoded representation of the `Extrinsic`. - const VERSION: u8; + /// By format we mean the encoded representation of the `Extrinsic`. + const VERSIONS: &'static [u8]; /// Transaction extensions attached to this `Extrinsic`. type TransactionExtensions; @@ -1709,7 +1710,7 @@ pub trait SignedExtension: /// This method provides a default implementation that returns a vec containing a single /// [`TransactionExtensionMetadata`]. fn metadata() -> Vec<TransactionExtensionMetadata> { - sp_std::vec![TransactionExtensionMetadata { + alloc::vec![TransactionExtensionMetadata { identifier: Self::IDENTIFIER, ty: scale_info::meta_type::<Self>(), implicit: scale_info::meta_type::<Self::AdditionalSigned>() @@ -1962,7 +1963,7 @@ pub trait AccountIdConversion<AccountId>: Sized { Self::try_from_sub_account::<()>(a).map(|x| x.0) } - /// Convert this value amalgamated with the a secondary "sub" value into an account ID, + /// Convert this value amalgamated with a secondary "sub" value into an account ID, /// truncating any unused bytes. This is infallible. /// /// NOTE: The account IDs from this and from `into_account` are *not* guaranteed to be distinct diff --git a/substrate/primitives/runtime/src/traits/transaction_extension/dispatch_transaction.rs b/substrate/primitives/runtime/src/traits/transaction_extension/dispatch_transaction.rs index 28030d12fc9f3c390eddb492c2c88c37a5a2ed77..1fbaab0d45ac13bac0192383dcfcb7c942346ba7 100644 --- a/substrate/primitives/runtime/src/traits/transaction_extension/dispatch_transaction.rs +++ b/substrate/primitives/runtime/src/traits/transaction_extension/dispatch_transaction.rs @@ -111,7 +111,7 @@ where info, len, self.implicit()?, - &(extension_version, call), + &TxBaseImplication((extension_version, call)), source, ) { // After validation, some origin must have been authorized. diff --git a/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs b/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs index f8c5dc6a724eb9e6f61935eff675af1e3096eb01..15be1e4c8e0a23bdd8f2dec892a3ad3f07808222 100644 --- a/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs +++ b/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs @@ -24,11 +24,12 @@ use crate::{ }, DispatchResult, }; +use alloc::vec::Vec; use codec::{Codec, Decode, Encode}; -use impl_trait_for_tuples::impl_for_tuples; +use core::fmt::Debug; #[doc(hidden)] -pub use sp_std::marker::PhantomData; -use sp_std::{self, fmt::Debug, prelude::*}; +pub use core::marker::PhantomData; +use impl_trait_for_tuples::impl_for_tuples; use sp_weights::Weight; use tuplex::{PopFront, PushBack}; @@ -43,6 +44,72 @@ mod dispatch_transaction; pub use as_transaction_extension::AsTransactionExtension; pub use dispatch_transaction::DispatchTransaction; +/// Provides `Sealed` trait. +mod private { + /// Special trait that prevents the implementation of some traits outside of this crate. + pub trait Sealed {} +} + +/// The base implication in a transaction. +/// +/// This struct is used to represent the base implication in the transaction, that is +/// the implication not part of any transaction extensions. It usually comprises of the call and +/// the transaction extension version. +/// +/// The concept of implication in the transaction extension pipeline is explained in the trait +/// documentation: [`TransactionExtension`]. +#[derive(Encode)] +pub struct TxBaseImplication<T>(pub T); + +impl<T: Encode> Implication for TxBaseImplication<T> { + fn parts(&self) -> ImplicationParts<&impl Encode, &impl Encode, &impl Encode> { + ImplicationParts { base: self, explicit: &(), implicit: &() } + } +} + +impl<T> private::Sealed for TxBaseImplication<T> {} + +/// The implication in a transaction. +/// +/// The concept of implication in the transaction extension pipeline is explained in the trait +/// documentation: [`TransactionExtension`]. +#[derive(Encode)] +pub struct ImplicationParts<Base, Explicit, Implicit> { + /// The base implication, that is implication not part of any transaction extension, usually + /// the call and the transaction extension version. + pub base: Base, + /// The explicit implication in transaction extensions. + pub explicit: Explicit, + /// The implicit implication in transaction extensions. + pub implicit: Implicit, +} + +impl<Base: Encode, Explicit: Encode, Implicit: Encode> Implication + for ImplicationParts<Base, Explicit, Implicit> +{ + fn parts(&self) -> ImplicationParts<&impl Encode, &impl Encode, &impl Encode> { + ImplicationParts { base: &self.base, explicit: &self.explicit, implicit: &self.implicit } + } +} + +impl<Base, Explicit, Implicit> private::Sealed for ImplicationParts<Base, Explicit, Implicit> {} + +/// Interface of implications in the transaction extension pipeline. +/// +/// Implications can be encoded, this is useful for checking signature on the implications. +/// Implications can be split into parts, this allow to destructure and restructure the +/// implications, this is useful for nested pipeline. +/// +/// This trait is sealed, consider using [`TxBaseImplication`] and [`ImplicationParts`] +/// implementations. +/// +/// The concept of implication in the transaction extension pipeline is explained in the trait +/// documentation: [`TransactionExtension`]. +pub trait Implication: Encode + private::Sealed { + /// Destructure the implication into its parts. + fn parts(&self) -> ImplicationParts<&impl Encode, &impl Encode, &impl Encode>; +} + /// Shortcut for the result value of the `validate` function. pub type ValidateResult<Val, Call> = Result<(ValidTransaction, Val, DispatchOriginOf<Call>), TransactionValidityError>; @@ -192,7 +259,7 @@ pub trait TransactionExtension<Call: Dispatchable>: /// This method provides a default implementation that returns a vec containing a single /// [`TransactionExtensionMetadata`]. fn metadata() -> Vec<TransactionExtensionMetadata> { - sp_std::vec![TransactionExtensionMetadata { + alloc::vec![TransactionExtensionMetadata { identifier: Self::IDENTIFIER, ty: scale_info::meta_type::<Self>(), implicit: scale_info::meta_type::<Self::Implicit>() @@ -244,7 +311,7 @@ pub trait TransactionExtension<Call: Dispatchable>: info: &DispatchInfoOf<Call>, len: usize, self_implicit: Self::Implicit, - inherited_implication: &impl Encode, + inherited_implication: &impl Implication, source: TransactionSource, ) -> ValidateResult<Self::Val, Call>; @@ -421,7 +488,7 @@ pub trait TransactionExtension<Call: Dispatchable>: #[macro_export] macro_rules! impl_tx_ext_default { ($call:ty ; , $( $rest:tt )*) => { - impl_tx_ext_default!{$call ; $( $rest )*} + $crate::impl_tx_ext_default!{$call ; $( $rest )*} }; ($call:ty ; validate $( $rest:tt )*) => { fn validate( @@ -436,7 +503,7 @@ macro_rules! impl_tx_ext_default { ) -> $crate::traits::ValidateResult<Self::Val, $call> { Ok((Default::default(), Default::default(), origin)) } - impl_tx_ext_default!{$call ; $( $rest )*} + $crate::impl_tx_ext_default!{$call ; $( $rest )*} }; ($call:ty ; prepare $( $rest:tt )*) => { fn prepare( @@ -449,13 +516,13 @@ macro_rules! impl_tx_ext_default { ) -> Result<Self::Pre, $crate::transaction_validity::TransactionValidityError> { Ok(Default::default()) } - impl_tx_ext_default!{$call ; $( $rest )*} + $crate::impl_tx_ext_default!{$call ; $( $rest )*} }; ($call:ty ; weight $( $rest:tt )*) => { fn weight(&self, _call: &$call) -> $crate::Weight { $crate::Weight::zero() } - impl_tx_ext_default!{$call ; $( $rest )*} + $crate::impl_tx_ext_default!{$call ; $( $rest )*} }; ($call:ty ;) => {}; } @@ -499,7 +566,7 @@ impl<Call: Dispatchable> TransactionExtension<Call> for Tuple { info: &DispatchInfoOf<Call>, len: usize, self_implicit: Self::Implicit, - inherited_implication: &impl Encode, + inherited_implication: &impl Implication, source: TransactionSource, ) -> Result< (ValidTransaction, Self::Val, <Call as Dispatchable>::RuntimeOrigin), @@ -510,23 +577,20 @@ impl<Call: Dispatchable> TransactionExtension<Call> for Tuple { let following_explicit_implications = for_tuples!( ( #( &self.Tuple ),* ) ); let following_implicit_implications = self_implicit; + let implication_parts = inherited_implication.parts(); + for_tuples!(#( // Implication of this pipeline element not relevant for later items, so we pop it. let (_item, following_explicit_implications) = following_explicit_implications.pop_front(); let (item_implicit, following_implicit_implications) = following_implicit_implications.pop_front(); let (item_valid, item_val, origin) = { - let implications = ( - // The first is the implications born of the fact we return the mutated - // origin. - inherited_implication, - // This is the explicitly made implication born of the fact the new origin is - // passed into the next items in this pipeline-tuple. - &following_explicit_implications, - // This is the implicitly made implication born of the fact the new origin is - // passed into the next items in this pipeline-tuple. - &following_implicit_implications, - ); - Tuple.validate(origin, call, info, len, item_implicit, &implications, source)? + Tuple.validate(origin, call, info, len, item_implicit, + &ImplicationParts { + base: implication_parts.base, + explicit: (&following_explicit_implications, implication_parts.explicit), + implicit: (&following_implicit_implications, implication_parts.implicit), + }, + source)? }; let valid = valid.combine_with(item_valid); let val = val.push_back(item_val); @@ -605,7 +669,7 @@ impl<Call: Dispatchable> TransactionExtension<Call> for Tuple { impl<Call: Dispatchable> TransactionExtension<Call> for () { const IDENTIFIER: &'static str = "UnitTransactionExtension"; type Implicit = (); - fn implicit(&self) -> sp_std::result::Result<Self::Implicit, TransactionValidityError> { + fn implicit(&self) -> core::result::Result<Self::Implicit, TransactionValidityError> { Ok(()) } type Val = (); @@ -620,7 +684,7 @@ impl<Call: Dispatchable> TransactionExtension<Call> for () { _info: &DispatchInfoOf<Call>, _len: usize, _self_implicit: Self::Implicit, - _inherited_implication: &impl Encode, + _inherited_implication: &impl Implication, _source: TransactionSource, ) -> Result< (ValidTransaction, (), <Call as Dispatchable>::RuntimeOrigin), @@ -639,3 +703,168 @@ impl<Call: Dispatchable> TransactionExtension<Call> for () { Ok(()) } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_implications_on_nested_structure() { + use scale_info::TypeInfo; + use std::cell::RefCell; + + #[derive(Clone, Debug, Eq, PartialEq, Encode, Decode, TypeInfo)] + struct MockExtension { + also_implicit: u8, + explicit: u8, + } + + const CALL_IMPLICIT: u8 = 23; + + thread_local! { + static COUNTER: RefCell<u8> = RefCell::new(1); + } + + impl TransactionExtension<()> for MockExtension { + const IDENTIFIER: &'static str = "MockExtension"; + type Implicit = u8; + fn implicit(&self) -> Result<Self::Implicit, TransactionValidityError> { + Ok(self.also_implicit) + } + type Val = (); + type Pre = (); + fn weight(&self, _call: &()) -> Weight { + Weight::zero() + } + fn prepare( + self, + _val: Self::Val, + _origin: &DispatchOriginOf<()>, + _call: &(), + _info: &DispatchInfoOf<()>, + _len: usize, + ) -> Result<Self::Pre, TransactionValidityError> { + Ok(()) + } + fn validate( + &self, + origin: DispatchOriginOf<()>, + _call: &(), + _info: &DispatchInfoOf<()>, + _len: usize, + self_implicit: Self::Implicit, + inherited_implication: &impl Implication, + _source: TransactionSource, + ) -> ValidateResult<Self::Val, ()> { + COUNTER.with(|c| { + let mut counter = c.borrow_mut(); + + assert_eq!(self_implicit, *counter); + assert_eq!( + self, + &MockExtension { also_implicit: *counter, explicit: *counter + 1 } + ); + + // Implications must be call then 1 to 22 then 1 to 22 odd. + let mut assert_implications = Vec::new(); + assert_implications.push(CALL_IMPLICIT); + for i in *counter + 2..23 { + assert_implications.push(i); + } + for i in *counter + 2..23 { + if i % 2 == 1 { + assert_implications.push(i); + } + } + assert_eq!(inherited_implication.encode(), assert_implications); + + *counter += 2; + }); + Ok((ValidTransaction::default(), (), origin)) + } + fn post_dispatch_details( + _pre: Self::Pre, + _info: &DispatchInfoOf<()>, + _post_info: &PostDispatchInfoOf<()>, + _len: usize, + _result: &DispatchResult, + ) -> Result<Weight, TransactionValidityError> { + Ok(Weight::zero()) + } + } + + // Test for one nested structure + + let ext = ( + MockExtension { also_implicit: 1, explicit: 2 }, + MockExtension { also_implicit: 3, explicit: 4 }, + ( + MockExtension { also_implicit: 5, explicit: 6 }, + MockExtension { also_implicit: 7, explicit: 8 }, + ( + MockExtension { also_implicit: 9, explicit: 10 }, + MockExtension { also_implicit: 11, explicit: 12 }, + ), + MockExtension { also_implicit: 13, explicit: 14 }, + MockExtension { also_implicit: 15, explicit: 16 }, + ), + MockExtension { also_implicit: 17, explicit: 18 }, + (MockExtension { also_implicit: 19, explicit: 20 },), + MockExtension { also_implicit: 21, explicit: 22 }, + ); + + let implicit = ext.implicit().unwrap(); + + let res = ext + .validate( + (), + &(), + &DispatchInfoOf::<()>::default(), + 0, + implicit, + &TxBaseImplication(CALL_IMPLICIT), + TransactionSource::Local, + ) + .expect("valid"); + + assert_eq!(res.0, ValidTransaction::default()); + + // Test for another nested structure + + COUNTER.with(|c| { + *c.borrow_mut() = 1; + }); + + let ext = ( + MockExtension { also_implicit: 1, explicit: 2 }, + MockExtension { also_implicit: 3, explicit: 4 }, + MockExtension { also_implicit: 5, explicit: 6 }, + MockExtension { also_implicit: 7, explicit: 8 }, + MockExtension { also_implicit: 9, explicit: 10 }, + MockExtension { also_implicit: 11, explicit: 12 }, + ( + MockExtension { also_implicit: 13, explicit: 14 }, + MockExtension { also_implicit: 15, explicit: 16 }, + MockExtension { also_implicit: 17, explicit: 18 }, + MockExtension { also_implicit: 19, explicit: 20 }, + MockExtension { also_implicit: 21, explicit: 22 }, + ), + ); + + let implicit = ext.implicit().unwrap(); + + let res = ext + .validate( + (), + &(), + &DispatchInfoOf::<()>::default(), + 0, + implicit, + &TxBaseImplication(CALL_IMPLICIT), + TransactionSource::Local, + ) + .expect("valid"); + + assert_eq!(res.0, ValidTransaction::default()); + } +} diff --git a/substrate/primitives/runtime/src/type_with_default.rs b/substrate/primitives/runtime/src/type_with_default.rs index 5790e3ab6bf68a2380943f66d98ae48793777508..b0eca22e5c1a422cb1fe056997f716d023f5cce6 100644 --- a/substrate/primitives/runtime/src/type_with_default.rs +++ b/substrate/primitives/runtime/src/type_with_default.rs @@ -31,7 +31,7 @@ use num_traits::{ CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, Num, NumCast, PrimInt, Saturating, ToPrimitive, }; -use scale_info::TypeInfo; +use scale_info::{StaticTypeInfo, TypeInfo}; use sp_core::Get; #[cfg(feature = "serde")] @@ -40,7 +40,8 @@ use serde::{Deserialize, Serialize}; /// A type that wraps another type and provides a default value. /// /// Passes through arithmetical and many other operations to the inner value. -#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen)] +/// Type information for metadata is the same as the inner value's type. +#[derive(Encode, Decode, Debug, MaxEncodedLen)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct TypeWithDefault<T, D: Get<T>>(T, PhantomData<D>); @@ -50,6 +51,17 @@ impl<T, D: Get<T>> TypeWithDefault<T, D> { } } +// Hides implementation details from the outside (for metadata type information). +// +// The type info showed in metadata is the one of the inner value's type. +impl<T: StaticTypeInfo, D: Get<T> + 'static> TypeInfo for TypeWithDefault<T, D> { + type Identity = Self; + + fn type_info() -> scale_info::Type { + T::type_info() + } +} + impl<T: Clone, D: Get<T>> Clone for TypeWithDefault<T, D> { fn clone(&self) -> Self { Self(self.0.clone(), PhantomData) @@ -511,6 +523,7 @@ impl<T: HasCompact, D: Get<T>> CompactAs for TypeWithDefault<T, D> { #[cfg(test)] mod tests { use super::TypeWithDefault; + use scale_info::TypeInfo; use sp_arithmetic::traits::{AtLeast16Bit, AtLeast32Bit, AtLeast8Bit}; use sp_core::Get; @@ -565,5 +578,11 @@ mod tests { } type U128WithDefault = TypeWithDefault<u128, Getu128>; impl WrapAtLeast32Bit for U128WithDefault {} + + assert_eq!(U8WithDefault::type_info(), <u8 as TypeInfo>::type_info()); + assert_eq!(U16WithDefault::type_info(), <u16 as TypeInfo>::type_info()); + assert_eq!(U32WithDefault::type_info(), <u32 as TypeInfo>::type_info()); + assert_eq!(U64WithDefault::type_info(), <u64 as TypeInfo>::type_info()); + assert_eq!(U128WithDefault::type_info(), <u128 as TypeInfo>::type_info()); } } diff --git a/substrate/primitives/session/Cargo.toml b/substrate/primitives/session/Cargo.toml index 6abf83505530503f46e0c6ff3dd3db0b55fce953..72be81c1222ef6c764b34f29bc698973ad854d05 100644 --- a/substrate/primitives/session/Cargo.toml +++ b/substrate/primitives/session/Cargo.toml @@ -20,9 +20,9 @@ codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } sp-api = { workspace = true } sp-core = { workspace = true } +sp-keystore = { optional = true, workspace = true } sp-runtime = { optional = true, workspace = true } sp-staking = { workspace = true } -sp-keystore = { optional = true, workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/staking/Cargo.toml b/substrate/primitives/staking/Cargo.toml index 35e7e4f604136df10e65673681b4b1abe95625de..42694cdbb67433241e970626185edac87ecc9a3d 100644 --- a/substrate/primitives/staking/Cargo.toml +++ b/substrate/primitives/staking/Cargo.toml @@ -16,10 +16,10 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { features = ["alloc", "derive"], optional = true, workspace = true } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } impl-trait-for-tuples = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 17010a8907fc23325d2726e59285313f5fea53a6..8e23c6800a9d53a240261b51a7f87dd20017b9e0 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -325,7 +325,7 @@ pub trait StakingUnchecked: StakingInterface { /// Migrate an existing staker to a virtual staker. /// /// It would release all funds held by the implementation pallet. - fn migrate_to_virtual_staker(who: &Self::AccountId); + fn migrate_to_virtual_staker(who: &Self::AccountId) -> DispatchResult; /// Book-keep a new bond for `keyless_who` without applying any locks (hence virtual). /// diff --git a/substrate/primitives/state-machine/Cargo.toml b/substrate/primitives/state-machine/Cargo.toml index e1c67feb7ac59aa45f3893da9c928656c9759ab8..5bc06b8cb509a3b21823f3aff557e39101340954 100644 --- a/substrate/primitives/state-machine/Cargo.toml +++ b/substrate/primitives/state-machine/Cargo.toml @@ -17,28 +17,28 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +arbitrary = { features = ["derive"], optional = true, workspace = true } codec = { workspace = true } hash-db = { workspace = true } log = { workspace = true } parking_lot = { optional = true, workspace = true, default-features = true } rand = { optional = true, workspace = true, default-features = true } smallvec = { workspace = true, default-features = true } -thiserror = { optional = true, workspace = true } -tracing = { optional = true, workspace = true, default-features = true } sp-core = { workspace = true } sp-externalities = { workspace = true } sp-panic-handler = { optional = true, workspace = true, default-features = true } sp-trie = { workspace = true } +thiserror = { optional = true, workspace = true } +tracing = { optional = true, workspace = true, default-features = true } trie-db = { workspace = true } -arbitrary = { features = ["derive"], optional = true, workspace = true } [dev-dependencies] +arbitrary = { features = ["derive"], workspace = true } array-bytes = { workspace = true, default-features = true } +assert_matches = { workspace = true } pretty_assertions = { workspace = true } rand = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -assert_matches = { workspace = true } -arbitrary = { features = ["derive"], workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/state-machine/fuzz/Cargo.toml b/substrate/primitives/state-machine/fuzz/Cargo.toml index 416c00c34fda4e8e1853f2b8d414e40f7d1ff808..16bf5b92025f0d357588fc7704b23544d27e2fd5 100644 --- a/substrate/primitives/state-machine/fuzz/Cargo.toml +++ b/substrate/primitives/state-machine/fuzz/Cargo.toml @@ -13,8 +13,8 @@ libfuzzer-sys = "0.4" sp-runtime = { path = "../../runtime" } [dependencies.sp-state-machine] -path = ".." features = ["fuzzing"] +path = ".." # Prevent this from interfering with workspaces [workspace] diff --git a/substrate/primitives/statement-store/Cargo.toml b/substrate/primitives/statement-store/Cargo.toml index aac676caedc9aef96ab994d64a09928d7f61eff6..df66cfcfc2e655f95566af0880930e39f47633d2 100644 --- a/substrate/primitives/statement-store/Cargo.toml +++ b/substrate/primitives/statement-store/Cargo.toml @@ -18,23 +18,23 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } +sp-api = { workspace = true } +sp-application-crypto = { workspace = true } sp-core = { workspace = true } sp-crypto-hashing = { workspace = true } +sp-externalities = { workspace = true } sp-runtime = { workspace = true } -sp-api = { workspace = true } -sp-application-crypto = { workspace = true } sp-runtime-interface = { workspace = true } -sp-externalities = { workspace = true } thiserror = { optional = true, workspace = true } # ECIES dependencies -ed25519-dalek = { optional = true, workspace = true, default-features = true } -x25519-dalek = { optional = true, features = ["static_secrets"], workspace = true } -curve25519-dalek = { optional = true, workspace = true } aes-gcm = { optional = true, workspace = true } +curve25519-dalek = { optional = true, workspace = true } +ed25519-dalek = { optional = true, workspace = true, default-features = true } hkdf = { optional = true, workspace = true } -sha2 = { optional = true, workspace = true, default-features = true } rand = { features = ["small_rng"], optional = true, workspace = true, default-features = true } +sha2 = { optional = true, workspace = true, default-features = true } +x25519-dalek = { optional = true, features = ["static_secrets"], workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/storage/src/lib.rs b/substrate/primitives/storage/src/lib.rs index 4b25f85fba68bd80a3c1040b45b213cfdad72563..df7570a185481c7c489b4dd07f027654410d509c 100644 --- a/substrate/primitives/storage/src/lib.rs +++ b/substrate/primitives/storage/src/lib.rs @@ -191,11 +191,15 @@ pub mod well_known_keys { /// Wasm code of the runtime. /// /// Stored as a raw byte vector. Required by substrate. + /// + /// Encodes to `0x3A636F6465`. pub const CODE: &[u8] = b":code"; /// Number of wasm linear memory pages required for execution of the runtime. /// /// The type of this value is encoded `u64`. + /// + /// Encodes to `0x307833413633364636343635` pub const HEAP_PAGES: &[u8] = b":heappages"; /// Current extrinsic index (u32) is stored under this key. diff --git a/substrate/primitives/timestamp/Cargo.toml b/substrate/primitives/timestamp/Cargo.toml index 0fcd5be98e6f6782f1c1f8d01c1a9d574bdc7f39..619f1eaa142b8974815b5353c88ee77e42f88b94 100644 --- a/substrate/primitives/timestamp/Cargo.toml +++ b/substrate/primitives/timestamp/Cargo.toml @@ -18,9 +18,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = { optional = true, workspace = true } codec = { features = ["derive"], workspace = true } -thiserror = { optional = true, workspace = true } sp-inherents = { workspace = true } sp-runtime = { workspace = true } +thiserror = { optional = true, workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/trie/Cargo.toml b/substrate/primitives/trie/Cargo.toml index 7f27bb097290e06cee472a3ddfbfff5eb0783c09..65a9727ed2ae8717ed6de3387a35f10db777e521 100644 --- a/substrate/primitives/trie/Cargo.toml +++ b/substrate/primitives/trie/Cargo.toml @@ -29,20 +29,20 @@ nohash-hasher = { optional = true, workspace = true } parking_lot = { optional = true, workspace = true, default-features = true } rand = { optional = true, workspace = true, default-features = true } scale-info = { features = ["derive"], workspace = true } +schnellru = { optional = true, workspace = true } +sp-core = { workspace = true } +sp-externalities = { workspace = true } thiserror = { optional = true, workspace = true } tracing = { optional = true, workspace = true, default-features = true } trie-db = { workspace = true } trie-root = { workspace = true } -sp-core = { workspace = true } -sp-externalities = { workspace = true } -schnellru = { optional = true, workspace = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } criterion = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } trie-bench = { workspace = true } trie-standardmap = { workspace = true } -sp-runtime = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/primitives/version/Cargo.toml b/substrate/primitives/version/Cargo.toml index 0424304989b7d808bda4213e1c83f3d520cfc06b..7fa983d0282384fa418f5145c053f05703fa1b4f 100644 --- a/substrate/primitives/version/Cargo.toml +++ b/substrate/primitives/version/Cargo.toml @@ -22,11 +22,11 @@ impl-serde = { optional = true, workspace = true } parity-wasm = { optional = true, workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { features = ["alloc", "derive"], optional = true, workspace = true } -thiserror = { optional = true, workspace = true } sp-crypto-hashing-proc-macro = { workspace = true, default-features = true } sp-runtime = { workspace = true } sp-std = { workspace = true } sp-version-proc-macro = { workspace = true } +thiserror = { optional = true, workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/wasm-interface/Cargo.toml b/substrate/primitives/wasm-interface/Cargo.toml index 9d0310fd22e8ea675ade50110d002afa8f3adeb2..9f8eea5102d63a91ad6bbd091347920f319763f6 100644 --- a/substrate/primitives/wasm-interface/Cargo.toml +++ b/substrate/primitives/wasm-interface/Cargo.toml @@ -17,11 +17,11 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +anyhow = { optional = true, workspace = true } codec = { features = ["derive"], workspace = true } impl-trait-for-tuples = { workspace = true } log = { optional = true, workspace = true, default-features = true } wasmtime = { optional = true, workspace = true } -anyhow = { optional = true, workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/weights/Cargo.toml b/substrate/primitives/weights/Cargo.toml index c4e1897dbb8ec02d8d37f9964cf2de11ee6bbf28..9cd0d9ac2e2064b73533db625f65aa6bfd3f025a 100644 --- a/substrate/primitives/weights/Cargo.toml +++ b/substrate/primitives/weights/Cargo.toml @@ -19,11 +19,11 @@ targets = ["x86_64-unknown-linux-gnu"] bounded-collections = { workspace = true } codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } +schemars = { optional = true, workspace = true } serde = { optional = true, features = ["alloc", "derive"], workspace = true } smallvec = { workspace = true, default-features = true } sp-arithmetic = { workspace = true } sp-debug-derive = { workspace = true } -schemars = { optional = true, workspace = true } [features] default = ["std"] diff --git a/substrate/scripts/ci/node-template-release/Cargo.toml b/substrate/scripts/ci/node-template-release/Cargo.toml index d335dbcf39713d3afd2d8d32ab3115f1b5815aae..5b90044d44dd14dd22a5e3607fa5f4c0622779b9 100644 --- a/substrate/scripts/ci/node-template-release/Cargo.toml +++ b/substrate/scripts/ci/node-template-release/Cargo.toml @@ -18,7 +18,7 @@ clap = { features = ["derive"], workspace = true } flate2 = { workspace = true } fs_extra = { workspace = true } glob = { workspace = true } +itertools = { workspace = true } tar = { workspace = true } tempfile = { workspace = true } toml_edit = { workspace = true } -itertools = { workspace = true } diff --git a/substrate/test-utils/Cargo.toml b/substrate/test-utils/Cargo.toml index 4f7a7090685983a8823883aa25411c30e812dd56..75eab46cb217acd60f2cf686302a1961d93905b1 100644 --- a/substrate/test-utils/Cargo.toml +++ b/substrate/test-utils/Cargo.toml @@ -14,11 +14,3 @@ workspace = true [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -futures = { workspace = true } -tokio = { features = ["macros", "time"], workspace = true, default-features = true } - -[dev-dependencies] -trybuild = { features = ["diff"], workspace = true } -sc-service = { workspace = true, default-features = true } diff --git a/substrate/test-utils/cli/Cargo.toml b/substrate/test-utils/cli/Cargo.toml index 3fbcf2090683cbc4243cd683cb777270b191d6c9..b11e67bc49bcab221af4ab7394c040017d28fe32 100644 --- a/substrate/test-utils/cli/Cargo.toml +++ b/substrate/test-utils/cli/Cargo.toml @@ -16,17 +16,17 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -substrate-rpc-client = { workspace = true, default-features = true } -sp-rpc = { workspace = true, default-features = true } assert_cmd = { workspace = true } +futures = { workspace = true } nix = { features = ["signal"], workspace = true } -regex = { workspace = true } -tokio = { features = ["full"], workspace = true, default-features = true } -node-primitives = { workspace = true, default-features = true } node-cli = { workspace = true } +node-primitives = { workspace = true, default-features = true } +regex = { workspace = true } sc-cli = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } -futures = { workspace = true } +sp-rpc = { workspace = true, default-features = true } +substrate-rpc-client = { workspace = true, default-features = true } +tokio = { features = ["full"], workspace = true, default-features = true } [features] try-runtime = ["node-cli/try-runtime"] diff --git a/substrate/test-utils/client/Cargo.toml b/substrate/test-utils/client/Cargo.toml index ebd1eab5980daf47b30a711f95e0256a364730da..b0709f4e244d96ef097848dde3ccd3bc86e02451 100644 --- a/substrate/test-utils/client/Cargo.toml +++ b/substrate/test-utils/client/Cargo.toml @@ -20,18 +20,15 @@ array-bytes = { workspace = true, default-features = true } async-trait = { workspace = true } codec = { workspace = true, default-features = true } futures = { workspace = true } -serde = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-client-db = { features = [ "test-helpers", ], workspace = true } sc-consensus = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } -sc-offchain = { workspace = true, default-features = true } -sc-service = { features = [ - "test-helpers", -], workspace = true } +sc-service = { workspace = true } +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } diff --git a/substrate/test-utils/client/src/lib.rs b/substrate/test-utils/client/src/lib.rs index c07640653d560b54658ced8dc466398b6f291f77..5a4e6c911694ec97c03b42206405a8a3b64d6da1 100644 --- a/substrate/test-utils/client/src/lib.rs +++ b/substrate/test-utils/client/src/lib.rs @@ -27,9 +27,7 @@ pub use sc_client_db::{self, Backend, BlocksPruning}; pub use sc_executor::{self, WasmExecutionMethod, WasmExecutor}; pub use sc_service::{client, RpcHandlers}; pub use sp_consensus; -pub use sp_keyring::{ - ed25519::Keyring as Ed25519Keyring, sr25519::Keyring as Sr25519Keyring, AccountKeyring, -}; +pub use sp_keyring::{Ed25519Keyring, Sr25519Keyring}; pub use sp_keystore::{Keystore, KeystorePtr}; pub use sp_runtime::{Storage, StorageChild}; diff --git a/substrate/test-utils/runtime/Cargo.toml b/substrate/test-utils/runtime/Cargo.toml index 1c82c73072bca20b14729ba860501d7677da68b2..7af692b437f639e023880117f3a47e8eaeea107c 100644 --- a/substrate/test-utils/runtime/Cargo.toml +++ b/substrate/test-utils/runtime/Cargo.toml @@ -16,43 +16,43 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { features = ["derive"], workspace = true } +frame-executive = { workspace = true } +frame-metadata-hash-extension = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +frame-system-rpc-runtime-api = { workspace = true } +pallet-babe = { workspace = true } +pallet-balances = { workspace = true } +pallet-timestamp = { workspace = true } +sc-service = { optional = true, workspace = true } +scale-info = { features = ["derive"], workspace = true } +sp-api = { workspace = true } sp-application-crypto = { features = ["serde"], workspace = true } +sp-block-builder = { workspace = true } sp-consensus-aura = { features = ["serde"], workspace = true } sp-consensus-babe = { features = ["serde"], workspace = true } +sp-consensus-grandpa = { features = ["serde"], workspace = true } +sp-core = { features = ["serde"], workspace = true } +sp-crypto-hashing = { workspace = true } +sp-externalities = { workspace = true } sp-genesis-builder = { workspace = true } -sp-block-builder = { workspace = true } -codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } sp-inherents = { workspace = true } +sp-io = { workspace = true } sp-keyring = { workspace = true } sp-offchain = { workspace = true } -sp-core = { features = ["serde"], workspace = true } -sp-crypto-hashing = { workspace = true } -sp-io = { workspace = true } -frame-support = { workspace = true } -sp-version = { workspace = true } -sp-session = { workspace = true } -sp-api = { workspace = true } sp-runtime = { features = ["serde"], workspace = true } -pallet-babe = { workspace = true } -pallet-balances = { workspace = true } -frame-executive = { workspace = true } -frame-metadata-hash-extension = { workspace = true } -frame-system = { workspace = true } -frame-system-rpc-runtime-api = { workspace = true } -pallet-timestamp = { workspace = true } -sp-consensus-grandpa = { features = ["serde"], workspace = true } -sp-trie = { workspace = true } +sp-session = { workspace = true } +sp-state-machine = { workspace = true } sp-transaction-pool = { workspace = true } +sp-trie = { workspace = true } +sp-version = { workspace = true } trie-db = { workspace = true } -sc-service = { features = ["test-helpers"], optional = true, workspace = true } -sp-state-machine = { workspace = true } -sp-externalities = { workspace = true } # 3rd party array-bytes = { optional = true, workspace = true, default-features = true } -serde_json = { workspace = true, features = ["alloc"] } log = { workspace = true } +serde_json = { workspace = true, features = ["alloc"] } tracing = { workspace = true, default-features = false } [dev-dependencies] @@ -61,11 +61,11 @@ sc-block-builder = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } sc-executor-common = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } -substrate-test-runtime-client = { workspace = true } -sp-tracing = { workspace = true, default-features = true } serde = { features = ["alloc", "derive"], workspace = true } serde_json = { features = ["alloc"], workspace = true } +sp-consensus = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } +substrate-test-runtime-client = { workspace = true } [build-dependencies] substrate-wasm-builder = { optional = true, features = ["metadata-hash"], workspace = true, default-features = true } diff --git a/substrate/test-utils/runtime/client/src/lib.rs b/substrate/test-utils/runtime/client/src/lib.rs index 435f3f5ebacb245ded18b6c258220744619e6d94..a5a37660660c70ce99d247c07884367168da7dfb 100644 --- a/substrate/test-utils/runtime/client/src/lib.rs +++ b/substrate/test-utils/runtime/client/src/lib.rs @@ -45,7 +45,7 @@ pub mod prelude { Backend, ExecutorDispatch, TestClient, TestClientBuilder, WasmExecutionMethod, }; // Keyring - pub use super::{AccountKeyring, Sr25519Keyring}; + pub use super::Sr25519Keyring; } /// Test client database backend. diff --git a/substrate/test-utils/runtime/client/src/trait_tests.rs b/substrate/test-utils/runtime/client/src/trait_tests.rs index c3a5f173d14ee419e67be40ebd847fdb954bdcb8..815e05163281092d8a965239659ff9951f55afc4 100644 --- a/substrate/test-utils/runtime/client/src/trait_tests.rs +++ b/substrate/test-utils/runtime/client/src/trait_tests.rs @@ -23,7 +23,7 @@ use std::sync::Arc; use crate::{ - AccountKeyring, BlockBuilderExt, ClientBlockImportExt, TestClientBuilder, TestClientBuilderExt, + BlockBuilderExt, ClientBlockImportExt, Sr25519Keyring, TestClientBuilder, TestClientBuilderExt, }; use futures::executor::block_on; use sc_block_builder::BlockBuilderBuilder; @@ -132,8 +132,8 @@ where // this push is required as otherwise B2 has the same hash as A2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -179,8 +179,8 @@ where // this push is required as otherwise C3 has the same hash as B3 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1, nonce: 1, }) @@ -199,8 +199,8 @@ where // this push is required as otherwise D2 has the same hash as B2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1, nonce: 0, }) @@ -295,8 +295,8 @@ where // this push is required as otherwise B2 has the same hash as A2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -338,8 +338,8 @@ where // this push is required as otherwise C3 has the same hash as B3 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1, nonce: 1, }) @@ -357,8 +357,8 @@ where // this push is required as otherwise D2 has the same hash as B2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1, nonce: 0, }) @@ -464,8 +464,8 @@ where // this push is required as otherwise B2 has the same hash as A2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -507,8 +507,8 @@ where // this push is required as otherwise C3 has the same hash as B3 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1, nonce: 1, }) @@ -526,8 +526,8 @@ where // this push is required as otherwise D2 has the same hash as B2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1, nonce: 0, }) diff --git a/substrate/test-utils/runtime/src/extrinsic.rs b/substrate/test-utils/runtime/src/extrinsic.rs index 4c884d4174ff7efe73fa3cd9c9defd28b80c6281..49dc6ba035c9e929d172d67d39b7ee7a97ac2f07 100644 --- a/substrate/test-utils/runtime/src/extrinsic.rs +++ b/substrate/test-utils/runtime/src/extrinsic.rs @@ -25,7 +25,7 @@ use codec::Encode; use frame_metadata_hash_extension::CheckMetadataHash; use frame_system::{CheckNonce, CheckWeight}; use sp_core::crypto::Pair as TraitPair; -use sp_keyring::AccountKeyring; +use sp_keyring::Sr25519Keyring; use sp_runtime::{ generic::Preamble, traits::TransactionExtension, transaction_validity::TransactionPriority, Perbill, @@ -54,8 +54,8 @@ impl Transfer { impl Default for TransferData { fn default() -> Self { Self { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Bob.into(), amount: 0, nonce: 0, } @@ -93,7 +93,7 @@ impl ExtrinsicBuilder { pub fn new(function: impl Into<RuntimeCall>) -> Self { Self { function: function.into(), - signer: Some(AccountKeyring::Alice.pair()), + signer: Some(Sr25519Keyring::Alice.pair()), nonce: None, metadata_hash: None, } @@ -212,6 +212,7 @@ impl ExtrinsicBuilder { self.metadata_hash .map(CheckMetadataHash::new_with_custom_hash) .unwrap_or_else(|| CheckMetadataHash::new(false)), + frame_system::WeightReclaim::new(), ); let raw_payload = SignedPayload::from_raw( self.function.clone(), diff --git a/substrate/test-utils/runtime/src/genesismap.rs b/substrate/test-utils/runtime/src/genesismap.rs index 9e972886b3771469284a7ee66e5f823623a75b07..e9a0e4815a2bf96c3ea9b08d22ecfefaccf6606b 100644 --- a/substrate/test-utils/runtime/src/genesismap.rs +++ b/substrate/test-utils/runtime/src/genesismap.rs @@ -27,7 +27,7 @@ use sp_core::{ storage::{well_known_keys, StateVersion, Storage}, Pair, }; -use sp_keyring::{AccountKeyring, Sr25519Keyring}; +use sp_keyring::Sr25519Keyring; use sp_runtime::{ traits::{Block as BlockT, Hash as HashT, Header as HeaderT}, BuildStorage, @@ -60,11 +60,11 @@ impl Default for GenesisStorageBuilder { ], (0..16_usize) .into_iter() - .map(|i| AccountKeyring::numeric(i).public()) + .map(|i| Sr25519Keyring::numeric(i).public()) .chain(vec![ - AccountKeyring::Alice.into(), - AccountKeyring::Bob.into(), - AccountKeyring::Charlie.into(), + Sr25519Keyring::Alice.into(), + Sr25519Keyring::Bob.into(), + Sr25519Keyring::Charlie.into(), ]) .collect(), 1000 * currency::DOLLARS, @@ -130,7 +130,10 @@ impl GenesisStorageBuilder { authorities: authorities_sr25519.clone(), ..Default::default() }, - balances: pallet_balances::GenesisConfig { balances: self.balances.clone() }, + balances: pallet_balances::GenesisConfig { + balances: self.balances.clone(), + ..Default::default() + }, } } diff --git a/substrate/test-utils/runtime/src/lib.rs b/substrate/test-utils/runtime/src/lib.rs index 461d583b5cc6070c339b3d92349fa4684a9190ff..7c092f2851663d2a2807807e5d812a3c0071a71e 100644 --- a/substrate/test-utils/runtime/src/lib.rs +++ b/substrate/test-utils/runtime/src/lib.rs @@ -47,7 +47,7 @@ use frame_system::{ }; use scale_info::TypeInfo; use sp_application_crypto::Ss58Codec; -use sp_keyring::AccountKeyring; +use sp_keyring::Sr25519Keyring; use sp_application_crypto::{ecdsa, ed25519, sr25519, RuntimeAppPublic}; use sp_core::{OpaqueMetadata, RuntimeDebug}; @@ -155,6 +155,7 @@ pub type TxExtension = ( (CheckNonce<Runtime>, CheckWeight<Runtime>), CheckSubstrateCall, frame_metadata_hash_extension::CheckMetadataHash<Runtime>, + frame_system::WeightReclaim<Runtime>, ); /// The payload being signed in transactions. pub type SignedPayload = sp_runtime::generic::SignedPayload<RuntimeCall, TxExtension>; @@ -731,8 +732,8 @@ impl_runtime_apis! { let patch = match name.as_ref() { "staging" => { let endowed_accounts: Vec<AccountId> = vec![ - AccountKeyring::Bob.public().into(), - AccountKeyring::Charlie.public().into(), + Sr25519Keyring::Bob.public().into(), + Sr25519Keyring::Charlie.public().into(), ]; json!({ @@ -741,8 +742,8 @@ impl_runtime_apis! { }, "substrateTest": { "authorities": [ - AccountKeyring::Alice.public().to_ss58check(), - AccountKeyring::Ferdie.public().to_ss58check() + Sr25519Keyring::Alice.public().to_ss58check(), + Sr25519Keyring::Ferdie.public().to_ss58check() ], } }) @@ -911,11 +912,11 @@ pub mod storage_key_generator { let balances_map_keys = (0..16_usize) .into_iter() - .map(|i| AccountKeyring::numeric(i).public().to_vec()) + .map(|i| Sr25519Keyring::numeric(i).public().to_vec()) .chain(vec![ - AccountKeyring::Alice.public().to_vec(), - AccountKeyring::Bob.public().to_vec(), - AccountKeyring::Charlie.public().to_vec(), + Sr25519Keyring::Alice.public().to_vec(), + Sr25519Keyring::Bob.public().to_vec(), + Sr25519Keyring::Charlie.public().to_vec(), ]) .map(|pubkey| { sp_crypto_hashing::blake2_128(&pubkey) @@ -1133,8 +1134,8 @@ mod tests { pub fn new_test_ext() -> sp_io::TestExternalities { genesismap::GenesisStorageBuilder::new( - vec![AccountKeyring::One.public().into(), AccountKeyring::Two.public().into()], - vec![AccountKeyring::One.into(), AccountKeyring::Two.into()], + vec![Sr25519Keyring::One.public().into(), Sr25519Keyring::Two.public().into()], + vec![Sr25519Keyring::One.into(), Sr25519Keyring::Two.into()], 1000 * currency::DOLLARS, ) .build() @@ -1202,7 +1203,7 @@ mod tests { fn check_substrate_check_signed_extension_works() { sp_tracing::try_init_simple(); new_test_ext().execute_with(|| { - let x = AccountKeyring::Alice.into(); + let x = Sr25519Keyring::Alice.into(); let info = DispatchInfo::default(); let len = 0_usize; assert_eq!( @@ -1328,7 +1329,7 @@ mod tests { .expect("default config is there"); let json = String::from_utf8(r.into()).expect("returned value is json. qed."); - let expected = r#"{"system":{},"babe":{"authorities":[],"epochConfig":{"c":[1,4],"allowed_slots":"PrimaryAndSecondaryVRFSlots"}},"substrateTest":{"authorities":[]},"balances":{"balances":[]}}"#; + let expected = r#"{"system":{},"babe":{"authorities":[],"epochConfig":{"c":[1,4],"allowed_slots":"PrimaryAndSecondaryVRFSlots"}},"substrateTest":{"authorities":[]},"balances":{"balances":[],"devAccounts":null}}"#; assert_eq!(expected.to_string(), json); } @@ -1472,8 +1473,8 @@ mod tests { }, "substrateTest": { "authorities": [ - AccountKeyring::Ferdie.public().to_ss58check(), - AccountKeyring::Alice.public().to_ss58check() + Sr25519Keyring::Ferdie.public().to_ss58check(), + Sr25519Keyring::Alice.public().to_ss58check() ], } }); @@ -1502,8 +1503,8 @@ mod tests { let authority_key_vec = Vec::<sp_core::sr25519::Public>::decode(&mut &value[..]).unwrap(); assert_eq!(authority_key_vec.len(), 2); - assert_eq!(authority_key_vec[0], AccountKeyring::Ferdie.public()); - assert_eq!(authority_key_vec[1], AccountKeyring::Alice.public()); + assert_eq!(authority_key_vec[0], Sr25519Keyring::Ferdie.public()); + assert_eq!(authority_key_vec[1], Sr25519Keyring::Alice.public()); //Babe|Authorities let value: Vec<u8> = get_from_storage( diff --git a/substrate/test-utils/runtime/transaction-pool/Cargo.toml b/substrate/test-utils/runtime/transaction-pool/Cargo.toml index 3cdaea642263e67f3f2594d2d14e2e5dbcad375f..501c9f99ebf11b2244554ea6346257c58b6cbe4b 100644 --- a/substrate/test-utils/runtime/transaction-pool/Cargo.toml +++ b/substrate/test-utils/runtime/transaction-pool/Cargo.toml @@ -19,9 +19,9 @@ codec = { workspace = true, default-features = true } futures = { workspace = true } log = { workspace = true } parking_lot = { workspace = true, default-features = true } -thiserror = { workspace = true } sc-transaction-pool = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } +thiserror = { workspace = true } diff --git a/substrate/test-utils/runtime/transaction-pool/src/lib.rs b/substrate/test-utils/runtime/transaction-pool/src/lib.rs index 6a4f38f63e8233a880b28586422f368054c65088..f88694fb1071e231ac5344156cca634a19d1614d 100644 --- a/substrate/test-utils/runtime/transaction-pool/src/lib.rs +++ b/substrate/test-utils/runtime/transaction-pool/src/lib.rs @@ -43,7 +43,7 @@ use substrate_test_runtime_client::{ AccountId, Block, BlockNumber, Extrinsic, ExtrinsicBuilder, Hash, Header, Nonce, Transfer, TransferData, }, - AccountKeyring::{self, *}, + Sr25519Keyring::{self, *}, }; /// Error type used by [`TestApi`]. @@ -338,7 +338,7 @@ trait TagFrom { impl TagFrom for AccountId { fn tag_from(&self) -> u8 { - let f = AccountKeyring::iter().enumerate().find(|k| AccountId::from(k.1) == *self); + let f = Sr25519Keyring::iter().enumerate().find(|k| AccountId::from(k.1) == *self); u8::try_from(f.unwrap().0).unwrap() } } @@ -352,9 +352,18 @@ impl ChainApi for TestApi { fn validate_transaction( &self, at: <Self::Block as BlockT>::Hash, - _source: TransactionSource, + source: TransactionSource, uxt: Arc<<Self::Block as BlockT>::Extrinsic>, ) -> Self::ValidationFuture { + ready(self.validate_transaction_blocking(at, source, uxt)) + } + + fn validate_transaction_blocking( + &self, + at: <Self::Block as BlockT>::Hash, + _source: TransactionSource, + uxt: Arc<<Self::Block as BlockT>::Extrinsic>, + ) -> Result<TransactionValidity, Error> { let uxt = (*uxt).clone(); self.validation_requests.write().push(uxt.clone()); let block_number; @@ -374,16 +383,12 @@ impl ChainApi for TestApi { // the transaction. (This is not required for this test function, but in real // environment it would fail because of this). if !found_best { - return ready(Ok(Err(TransactionValidityError::Invalid( - InvalidTransaction::Custom(1), - )))) + return Ok(Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(1)))) } }, Ok(None) => - return ready(Ok(Err(TransactionValidityError::Invalid( - InvalidTransaction::Custom(2), - )))), - Err(e) => return ready(Err(e)), + return Ok(Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(2)))), + Err(e) => return Err(e), } let (requires, provides) = if let Ok(transfer) = TransferData::try_from(&uxt) { @@ -423,7 +428,7 @@ impl ChainApi for TestApi { if self.enable_stale_check && transfer.nonce < chain_nonce { log::info!("test_api::validate_transaction: invalid_transaction(stale)...."); - return ready(Ok(Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)))) + return Ok(Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))) } (requires, provides) @@ -433,7 +438,7 @@ impl ChainApi for TestApi { if self.chain.read().invalid_hashes.contains(&self.hash_and_length(&uxt).0) { log::info!("test_api::validate_transaction: invalid_transaction...."); - return ready(Ok(Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(0))))) + return Ok(Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(0)))) } let priority = self.chain.read().priorities.get(&self.hash_and_length(&uxt).0).cloned(); @@ -447,16 +452,7 @@ impl ChainApi for TestApi { (self.valid_modifier.read())(&mut validity); - ready(Ok(Ok(validity))) - } - - fn validate_transaction_blocking( - &self, - _at: <Self::Block as BlockT>::Hash, - _source: TransactionSource, - _uxt: Arc<<Self::Block as BlockT>::Extrinsic>, - ) -> Result<TransactionValidity, Error> { - unimplemented!(); + Ok(Ok(validity)) } fn block_id_to_number( @@ -534,7 +530,7 @@ impl sp_blockchain::HeaderMetadata<Block> for TestApi { /// Generate transfer extrinsic with a given nonce. /// /// Part of the test api. -pub fn uxt(who: AccountKeyring, nonce: Nonce) -> Extrinsic { +pub fn uxt(who: Sr25519Keyring, nonce: Nonce) -> Extrinsic { let dummy = codec::Decode::decode(&mut TrailingZeroInput::zeroes()).unwrap(); let transfer = Transfer { from: who.into(), to: dummy, nonce, amount: 1 }; ExtrinsicBuilder::new_transfer(transfer).build() diff --git a/substrate/utils/binary-merkle-tree/Cargo.toml b/substrate/utils/binary-merkle-tree/Cargo.toml index 9577d94ef0bfa1e63262ffd267930cffa0ada9e9..86d64face80e01fdc7a86a1f1c9f615223fa936f 100644 --- a/substrate/utils/binary-merkle-tree/Cargo.toml +++ b/substrate/utils/binary-merkle-tree/Cargo.toml @@ -12,16 +12,16 @@ homepage.workspace = true workspace = true [dependencies] -codec = { workspace = true, features = ["derive"] } array-bytes = { optional = true, workspace = true, default-features = true } -log = { optional = true, workspace = true } +codec = { workspace = true, features = ["derive"] } hash-db = { workspace = true } +log = { optional = true, workspace = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } [features] debug = ["array-bytes", "log"] diff --git a/substrate/utils/frame/benchmarking-cli/Cargo.toml b/substrate/utils/frame/benchmarking-cli/Cargo.toml index 8a4a06b1b40abe43fa1df8573324e1dd173486e1..c38a7e4f77d812f2d22e7b9dcbff52d4459358cc 100644 --- a/substrate/utils/frame/benchmarking-cli/Cargo.toml +++ b/substrate/utils/frame/benchmarking-cli/Cargo.toml @@ -16,25 +16,27 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +Inflector = { workspace = true } array-bytes = { workspace = true, default-features = true } chrono = { workspace = true } clap = { features = ["derive"], workspace = true } codec = { workspace = true, default-features = true } comfy-table = { workspace = true } +cumulus-client-parachain-inherent = { workspace = true, default-features = true } +cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } +frame-benchmarking = { workspace = true, default-features = true } +frame-support = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } +gethostname = { workspace = true } handlebars = { workspace = true } -Inflector = { workspace = true } +hex = { workspace = true, default-features = true } itertools = { workspace = true } linked-hash-map = { workspace = true } log = { workspace = true, default-features = true } +polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } rand = { features = ["small_rng"], workspace = true, default-features = true } rand_pcg = { workspace = true } -serde = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -thiserror = { workspace = true } -thousands = { workspace = true } -frame-benchmarking = { workspace = true, default-features = true } -frame-support = { workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-chain-spec = { workspace = true } sc-cli = { workspace = true } @@ -42,35 +44,34 @@ sc-client-api = { workspace = true, default-features = true } sc-client-db = { workspace = true } sc-executor = { workspace = true, default-features = true } sc-executor-common = { workspace = true } +sc-runtime-utilities = { workspace = true, default-features = true } sc-service = { workspace = true } sc-sysinfo = { workspace = true, default-features = true } +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } +sp-block-builder = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true, default-features = true } sp-database = { workspace = true, default-features = true } sp-externalities = { workspace = true, default-features = true } sp-genesis-builder = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } -sp-crypto-hashing = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } sp-storage = { workspace = true, default-features = true } -sp-trie = { workspace = true, default-features = true } -sp-block-builder = { workspace = true, default-features = true } +sp-timestamp = { workspace = true, default-features = true } sp-transaction-pool = { workspace = true, default-features = true } +sp-trie = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } -sp-timestamp = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } sp-wasm-interface = { workspace = true, default-features = true } subxt = { workspace = true, features = ["native"] } subxt-signer = { workspace = true, features = ["unstable-eth"] } -cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } -cumulus-client-parachain-inherent = { workspace = true, default-features = true } -polkadot-parachain-primitives = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } -gethostname = { workspace = true } -hex = { workspace = true, default-features = true } +thiserror = { workspace = true } +thousands = { workspace = true } [dev-dependencies] cumulus-test-runtime = { workspace = true, default-features = true } diff --git a/substrate/utils/frame/benchmarking-cli/src/lib.rs b/substrate/utils/frame/benchmarking-cli/src/lib.rs index 1e8642e54d7015da52d134328a23e59cd7eb7137..e1c3c5fe3706f17e602c2b7ce260ac73720c7698 100644 --- a/substrate/utils/frame/benchmarking-cli/src/lib.rs +++ b/substrate/utils/frame/benchmarking-cli/src/lib.rs @@ -30,7 +30,6 @@ pub use extrinsic::{ExtrinsicBuilder, ExtrinsicCmd, ExtrinsicFactory}; pub use machine::{MachineCmd, SUBSTRATE_REFERENCE_HARDWARE}; pub use overhead::{ remark_builder::{DynamicRemarkBuilder, SubstrateRemarkBuilder}, - runtime_utilities::fetch_latest_metadata_from_code_blob, OpaqueBlock, OverheadCmd, }; pub use pallet::PalletCmd; diff --git a/substrate/utils/frame/benchmarking-cli/src/overhead/command.rs b/substrate/utils/frame/benchmarking-cli/src/overhead/command.rs index 8102f14b4f4b97a8f2e73651523e41597ad689e0..847f8e16c0df0f9e7a983e527591eb52b60a3000 100644 --- a/substrate/utils/frame/benchmarking-cli/src/overhead/command.rs +++ b/substrate/utils/frame/benchmarking-cli/src/overhead/command.rs @@ -18,7 +18,6 @@ //! Contains the [`OverheadCmd`] as entry point for the CLI to execute //! the *overhead* benchmarks. -use super::runtime_utilities::*; use crate::{ extrinsic::{ bench::{Benchmark, BenchmarkParams as ExtrinsicBenchmarkParams}, @@ -37,7 +36,7 @@ use crate::{ }, }; use clap::{error::ErrorKind, Args, CommandFactory, Parser}; -use codec::Encode; +use codec::{Decode, Encode}; use cumulus_client_parachain_inherent::MockValidationDataInherentDataProvider; use fake_runtime_api::RuntimeApi as FakeRuntimeApi; use frame_support::Deserialize; @@ -50,6 +49,7 @@ use sc_cli::{CliConfiguration, Database, ImportParams, Result, SharedParams}; use sc_client_api::{execution_extensions::ExecutionExtensions, UsageProvider}; use sc_client_db::{BlocksPruning, DatabaseSettings}; use sc_executor::WasmExecutor; +use sc_runtime_utilities::fetch_latest_metadata_from_code_blob; use sc_service::{new_client, new_db_backend, BasePath, ClientConfig, TFullClient, TaskManager}; use serde::Serialize; use serde_json::{json, Value}; @@ -317,7 +317,7 @@ impl OverheadCmd { Some(self.params.genesis_builder_preset.clone()), ), self.params.para_id, - )) + )); }; Err("Neither a runtime nor a chain-spec were specified".to_string().into()) @@ -335,7 +335,7 @@ impl OverheadCmd { ErrorKind::MissingRequiredArgument, "Provide either a runtime via `--runtime` or a chain spec via `--chain`" .to_string(), - )) + )); } match self.params.genesis_builder { @@ -344,7 +344,7 @@ impl OverheadCmd { return Err(( ErrorKind::MissingRequiredArgument, "Provide a chain spec via `--chain`.".to_string(), - )) + )); }, _ => {}, }; @@ -400,8 +400,12 @@ impl OverheadCmd { .with_allow_missing_host_functions(true) .build(); - let metadata = - fetch_latest_metadata_from_code_blob(&executor, state_handler.get_code_bytes()?)?; + let opaque_metadata = + fetch_latest_metadata_from_code_blob(&executor, state_handler.get_code_bytes()?) + .map_err(|_| { + <&str as Into<sc_cli::Error>>::into("Unable to fetch latest stable metadata") + })?; + let metadata = subxt::Metadata::decode(&mut (*opaque_metadata).as_slice())?; // At this point we know what kind of chain we are dealing with. let chain_type = identify_chain(&metadata, para_id); @@ -478,7 +482,7 @@ impl OverheadCmd { let database_source = self.database_config( &base_path.path().to_path_buf(), self.database_cache_size()?.unwrap_or(1024), - self.database()?.unwrap_or(Database::RocksDb), + self.database()?.unwrap_or(Database::Auto), )?; let backend = new_db_backend(DatabaseSettings { @@ -682,6 +686,7 @@ mod tests { OverheadCmd, }; use clap::Parser; + use codec::Decode; use sc_executor::WasmExecutor; #[test] @@ -690,8 +695,9 @@ mod tests { let code_bytes = westend_runtime::WASM_BINARY .expect("To run this test, build the wasm binary of westend-runtime") .to_vec(); - let metadata = + let opaque_metadata = super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()).unwrap(); + let metadata = subxt::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap(); let chain_type = identify_chain(&metadata, None); assert_eq!(chain_type, ChainType::Relaychain); assert_eq!(chain_type.requires_proof_recording(), false); @@ -703,8 +709,9 @@ mod tests { let code_bytes = cumulus_test_runtime::WASM_BINARY .expect("To run this test, build the wasm binary of cumulus-test-runtime") .to_vec(); - let metadata = + let opaque_metadata = super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()).unwrap(); + let metadata = subxt::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap(); let chain_type = identify_chain(&metadata, Some(100)); assert_eq!(chain_type, ChainType::Parachain(100)); assert!(chain_type.requires_proof_recording()); @@ -717,8 +724,9 @@ mod tests { let code_bytes = substrate_test_runtime::WASM_BINARY .expect("To run this test, build the wasm binary of substrate-test-runtime") .to_vec(); - let metadata = + let opaque_metadata = super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()).unwrap(); + let metadata = subxt::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap(); let chain_type = identify_chain(&metadata, None); assert_eq!(chain_type, ChainType::Unknown); assert_eq!(chain_type.requires_proof_recording(), false); diff --git a/substrate/utils/frame/benchmarking-cli/src/overhead/mod.rs b/substrate/utils/frame/benchmarking-cli/src/overhead/mod.rs index 89c23d1fb6c1928f47b9137dc734820387fa5303..de524d9ebc18efb702c9473bb846b22779652fb3 100644 --- a/substrate/utils/frame/benchmarking-cli/src/overhead/mod.rs +++ b/substrate/utils/frame/benchmarking-cli/src/overhead/mod.rs @@ -20,6 +20,5 @@ pub mod template; mod fake_runtime_api; pub mod remark_builder; -pub mod runtime_utilities; pub use command::{OpaqueBlock, OverheadCmd}; diff --git a/substrate/utils/frame/benchmarking-cli/src/overhead/remark_builder.rs b/substrate/utils/frame/benchmarking-cli/src/overhead/remark_builder.rs index a1d5f282d9f8854dec3dd5f1aa6c218320e37b4f..3a2d8776d1e1d055d6b5c716faebdb2be37bc2c9 100644 --- a/substrate/utils/frame/benchmarking-cli/src/overhead/remark_builder.rs +++ b/substrate/utils/frame/benchmarking-cli/src/overhead/remark_builder.rs @@ -54,13 +54,15 @@ impl<C: Config<Hash = subxt::utils::H256>> DynamicRemarkBuilder<C> { log::debug!("Found metadata API version {}.", metadata_api_version); let opaque_metadata = if metadata_api_version > 1 { - let Ok(mut supported_metadata_versions) = api.metadata_versions(genesis) else { + let Ok(supported_metadata_versions) = api.metadata_versions(genesis) else { return Err("Unable to fetch metadata versions".to_string().into()); }; let latest = supported_metadata_versions - .pop() - .ok_or("No metadata version supported".to_string())?; + .into_iter() + .filter(|v| *v != u32::MAX) + .max() + .ok_or("No stable metadata versions supported".to_string())?; api.metadata_at_version(genesis, latest) .map_err(|e| format!("Unable to fetch metadata: {:?}", e))? diff --git a/substrate/utils/frame/benchmarking-cli/src/storage/README.md b/substrate/utils/frame/benchmarking-cli/src/storage/README.md index 95c83d2edbc5c68a00589951b0b83acd7a4adbb3..955b52a248c6e05a42f2419327785e2bfe88f34a 100644 --- a/substrate/utils/frame/benchmarking-cli/src/storage/README.md +++ b/substrate/utils/frame/benchmarking-cli/src/storage/README.md @@ -13,7 +13,7 @@ Running the command on Substrate itself is not verify meaningful, since the gene used. The output for the Polkadot client with a recent chain snapshot will give you a better impression. A recent snapshot can -be downloaded from [Polkachu]. +be downloaded from [Polkadot Snapshots]. Then run (remove the `--db=paritydb` if you have a RocksDB snapshot): ```sh cargo run --profile=production -- benchmark storage --dev --state-version=0 --db=paritydb --weight-path runtime/polkadot/constants/src/weights @@ -106,6 +106,6 @@ write: 71_347 * constants::WEIGHT_REF_TIME_PER_NANOS, License: Apache-2.0 <!-- LINKS --> -[Polkachu]: https://polkachu.com/snapshots +[Polkadot Snapshots]: https://snapshots.polkadot.io [paritydb_weights.rs]: https://github.com/paritytech/polkadot/blob/c254e5975711a6497af256f6831e9a6c752d28f5/runtime/polkadot/constants/src/weights/paritydb_weights.rs#L60 diff --git a/substrate/utils/frame/generate-bags/Cargo.toml b/substrate/utils/frame/generate-bags/Cargo.toml index c37c426466992ffceb2aaba60b38fa10f61f1c67..c03f85ece05d873ba2c4477fba5bd639f3bbc538 100644 --- a/substrate/utils/frame/generate-bags/Cargo.toml +++ b/substrate/utils/frame/generate-bags/Cargo.toml @@ -13,8 +13,8 @@ workspace = true [dependencies] # FRAME -frame-support = { workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } +frame-support = { workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } pallet-staking = { workspace = true, default-features = true } sp-staking = { workspace = true, default-features = true } diff --git a/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml b/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml index 3d574864725729e52aeb2d6cbd98d9aebe5eac31..aace0f4ad23f11aa6c37454c103a4adc1a3ef51d 100644 --- a/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml +++ b/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml @@ -13,8 +13,8 @@ publish = false workspace = true [dependencies] -kitchensink-runtime = { workspace = true } generate-bags = { workspace = true, default-features = true } +kitchensink-runtime = { workspace = true } # third-party clap = { features = ["derive"], workspace = true } diff --git a/substrate/utils/frame/omni-bencher/Cargo.toml b/substrate/utils/frame/omni-bencher/Cargo.toml index 345a7288d45bf2ee6723789eb436b738d03cb1a4..d0d7f1a3428f8d5b2a2a130942e07fd3347587a0 100644 --- a/substrate/utils/frame/omni-bencher/Cargo.toml +++ b/substrate/utils/frame/omni-bencher/Cargo.toml @@ -15,16 +15,16 @@ workspace = true clap = { features = ["derive"], workspace = true } cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } frame-benchmarking-cli = { workspace = true } +log = { workspace = true } sc-cli = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-statement-store = { workspace = true, default-features = true } tracing-subscriber = { workspace = true } -log = { workspace = true } [dev-dependencies] -tempfile = { workspace = true } assert_cmd = { workspace = true } cumulus-test-runtime = { workspace = true } -sp-tracing = { workspace = true, default-features = true } -sp-genesis-builder = { workspace = true, default-features = true } sc-chain-spec = { workspace = true } +sp-genesis-builder = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } +tempfile = { workspace = true } diff --git a/substrate/utils/frame/omni-bencher/src/main.rs b/substrate/utils/frame/omni-bencher/src/main.rs index 7d8aa891dc4a0c77f6a04f81988701f31e19dd6d..f0f9ab753b074e7084a702441ee9fadf8c7969fe 100644 --- a/substrate/utils/frame/omni-bencher/src/main.rs +++ b/substrate/utils/frame/omni-bencher/src/main.rs @@ -24,8 +24,6 @@ use tracing_subscriber::EnvFilter; fn main() -> Result<()> { setup_logger(); - log::warn!("The FRAME omni-bencher is not yet battle tested - double check the results.",); - command::Command::parse().run() } diff --git a/substrate/utils/frame/remote-externalities/Cargo.toml b/substrate/utils/frame/remote-externalities/Cargo.toml index 41a0091027c13831bb4f7adaad30b2265602173f..4ed0e1edf3e4e6dc1e6f8c9453c3f1eca47d20a9 100644 --- a/substrate/utils/frame/remote-externalities/Cargo.toml +++ b/substrate/utils/frame/remote-externalities/Cargo.toml @@ -15,20 +15,20 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { features = ["http-client"], workspace = true } codec = { workspace = true, default-features = true } +futures = { workspace = true } +indicatif = { workspace = true } +jsonrpsee = { features = ["http-client"], workspace = true } log = { workspace = true, default-features = true } serde = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } -sp-state-machine = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -tokio = { features = ["macros", "rt-multi-thread"], workspace = true, default-features = true } -substrate-rpc-client = { workspace = true, default-features = true } -futures = { workspace = true } -indicatif = { workspace = true } +sp-state-machine = { workspace = true, default-features = true } spinners = { workspace = true } +substrate-rpc-client = { workspace = true, default-features = true } +tokio = { features = ["macros", "rt-multi-thread"], workspace = true, default-features = true } tokio-retry = { workspace = true } [dev-dependencies] diff --git a/substrate/utils/frame/remote-externalities/src/lib.rs b/substrate/utils/frame/remote-externalities/src/lib.rs index 75a2ac2aef41b7920a66107a96a654191d1d24f7..4c49663260bb6ec4f5bc27e55c47848dd3ce5eeb 100644 --- a/substrate/utils/frame/remote-externalities/src/lib.rs +++ b/substrate/utils/frame/remote-externalities/src/lib.rs @@ -20,6 +20,8 @@ //! An equivalent of `sp_io::TestExternalities` that can load its state from a remote substrate //! based chain, or a local state snapshot file. +mod logging; + use codec::{Compact, Decode, Encode}; use indicatif::{ProgressBar, ProgressStyle}; use jsonrpsee::{core::params::ArrayParams, http_client::HttpClient}; @@ -37,7 +39,6 @@ use sp_runtime::{ StateVersion, }; use sp_state_machine::TestExternalities; -use spinners::{Spinner, Spinners}; use std::{ cmp::{max, min}, fs, @@ -49,6 +50,8 @@ use std::{ use substrate_rpc_client::{rpc_params, BatchRequestBuilder, ChainApi, ClientT, StateApi}; use tokio_retry::{strategy::FixedInterval, Retry}; +type Result<T, E = &'static str> = std::result::Result<T, E>; + type KeyValue = (StorageKey, StorageData); type TopKeyValues = Vec<KeyValue>; type ChildKeyValues = Vec<(ChildInfo, Vec<KeyValue>)>; @@ -87,7 +90,7 @@ impl<B: BlockT> Snapshot<B> { } } - fn load(path: &PathBuf) -> Result<Snapshot<B>, &'static str> { + fn load(path: &PathBuf) -> Result<Snapshot<B>> { let bytes = fs::read(path).map_err(|_| "fs::read failed.")?; // The first item in the SCALE encoded struct bytes is the snapshot version. We decode and // check that first, before proceeding to decode the rest of the snapshot. @@ -168,9 +171,9 @@ impl Transport { } // Build an HttpClient from a URI. - async fn init(&mut self) -> Result<(), &'static str> { + async fn init(&mut self) -> Result<()> { if let Self::Uri(uri) = self { - log::debug!(target: LOG_TARGET, "initializing remote client to {:?}", uri); + debug!(target: LOG_TARGET, "initializing remote client to {uri:?}"); // If we have a ws uri, try to convert it to an http uri. // We use an HTTP client rather than WS because WS starts to choke with "accumulated @@ -178,11 +181,11 @@ impl Transport { // from a node running a default configuration. let uri = if uri.starts_with("ws://") { let uri = uri.replace("ws://", "http://"); - log::info!(target: LOG_TARGET, "replacing ws:// in uri with http://: {:?} (ws is currently unstable for fetching remote storage, for more see https://github.com/paritytech/jsonrpsee/issues/1086)", uri); + info!(target: LOG_TARGET, "replacing ws:// in uri with http://: {uri:?} (ws is currently unstable for fetching remote storage, for more see https://github.com/paritytech/jsonrpsee/issues/1086)"); uri } else if uri.starts_with("wss://") { let uri = uri.replace("wss://", "https://"); - log::info!(target: LOG_TARGET, "replacing wss:// in uri with https://: {:?} (ws is currently unstable for fetching remote storage, for more see https://github.com/paritytech/jsonrpsee/issues/1086)", uri); + info!(target: LOG_TARGET, "replacing wss:// in uri with https://: {uri:?} (ws is currently unstable for fetching remote storage, for more see https://github.com/paritytech/jsonrpsee/issues/1086)"); uri } else { uri.clone() @@ -193,7 +196,7 @@ impl Transport { .request_timeout(std::time::Duration::from_secs(60 * 5)) .build(uri) .map_err(|e| { - log::error!(target: LOG_TARGET, "error: {:?}", e); + error!(target: LOG_TARGET, "error: {e:?}"); "failed to build http client" })?; @@ -364,23 +367,23 @@ where &self, key: StorageKey, maybe_at: Option<B::Hash>, - ) -> Result<Option<StorageData>, &'static str> { + ) -> Result<Option<StorageData>> { trace!(target: LOG_TARGET, "rpc: get_storage"); self.as_online().rpc_client().storage(key, maybe_at).await.map_err(|e| { - error!(target: LOG_TARGET, "Error = {:?}", e); + error!(target: LOG_TARGET, "Error = {e:?}"); "rpc get_storage failed." }) } /// Get the latest finalized head. - async fn rpc_get_head(&self) -> Result<B::Hash, &'static str> { + async fn rpc_get_head(&self) -> Result<B::Hash> { trace!(target: LOG_TARGET, "rpc: finalized_head"); // sadly this pretty much unreadable... ChainApi::<(), _, B::Header, ()>::finalized_head(self.as_online().rpc_client()) .await .map_err(|e| { - error!(target: LOG_TARGET, "Error = {:?}", e); + error!(target: LOG_TARGET, "Error = {e:?}"); "rpc finalized_head failed." }) } @@ -390,13 +393,13 @@ where prefix: Option<StorageKey>, start_key: Option<StorageKey>, at: B::Hash, - ) -> Result<Vec<StorageKey>, &'static str> { + ) -> Result<Vec<StorageKey>> { self.as_online() .rpc_client() .storage_keys_paged(prefix, Self::DEFAULT_KEY_DOWNLOAD_PAGE, start_key, Some(at)) .await .map_err(|e| { - error!(target: LOG_TARGET, "Error = {:?}", e); + error!(target: LOG_TARGET, "Error = {e:?}"); "rpc get_keys failed" }) } @@ -407,7 +410,7 @@ where prefix: &StorageKey, block: B::Hash, parallel: usize, - ) -> Result<Vec<StorageKey>, &'static str> { + ) -> Result<Vec<StorageKey>> { /// Divide the workload and return the start key of each chunks. Guaranteed to return a /// non-empty list. fn gen_start_keys(prefix: &StorageKey) -> Vec<StorageKey> { @@ -491,7 +494,7 @@ where block: B::Hash, start_key: Option<&StorageKey>, end_key: Option<&StorageKey>, - ) -> Result<Vec<StorageKey>, &'static str> { + ) -> Result<Vec<StorageKey>> { let mut last_key: Option<&StorageKey> = start_key; let mut keys: Vec<StorageKey> = vec![]; @@ -518,11 +521,11 @@ where // scraping out of range or no more matches, // we are done either way if page_len < Self::DEFAULT_KEY_DOWNLOAD_PAGE as usize { - log::debug!(target: LOG_TARGET, "last page received: {}", page_len); + debug!(target: LOG_TARGET, "last page received: {page_len}"); break } - log::debug!( + debug!( target: LOG_TARGET, "new total = {}, full page received: {}", keys.len(), @@ -589,11 +592,10 @@ where let total_payloads = payloads.len(); while start_index < total_payloads { - log::debug!( + debug!( target: LOG_TARGET, - "Remaining payloads: {} Batch request size: {}", + "Remaining payloads: {} Batch request size: {batch_size}", total_payloads - start_index, - batch_size, ); let end_index = usize::min(start_index + batch_size, total_payloads); @@ -620,18 +622,16 @@ where retries += 1; let failure_log = format!( - "Batch request failed ({}/{} retries). Error: {}", - retries, - Self::MAX_RETRIES, - e + "Batch request failed ({retries}/{} retries). Error: {e}", + Self::MAX_RETRIES ); // after 2 subsequent failures something very wrong is happening. log a warning // and reset the batch size down to 1. if retries >= 2 { - log::warn!("{}", failure_log); + warn!("{failure_log}"); batch_size = 1; } else { - log::debug!("{}", failure_log); + debug!("{failure_log}"); // Decrease batch size by DECREASE_FACTOR batch_size = (batch_size as f32 * Self::BATCH_SIZE_DECREASE_FACTOR) as usize; @@ -655,13 +655,11 @@ where ) }; - log::debug!( + debug!( target: LOG_TARGET, - "Request duration: {:?} Target duration: {:?} Last batch size: {} Next batch size: {}", - request_duration, + "Request duration: {request_duration:?} Target duration: {:?} Last batch size: {} Next batch size: {batch_size}", Self::REQUEST_DURATION_TARGET, end_index - start_index, - batch_size ); let batch_response_len = batch_response.len(); @@ -689,21 +687,24 @@ where prefix: StorageKey, at: B::Hash, pending_ext: &mut TestExternalities<HashingFor<B>>, - ) -> Result<Vec<KeyValue>, &'static str> { - let start = Instant::now(); - let mut sp = Spinner::with_timer(Spinners::Dots, "Scraping keys...".into()); - // TODO We could start downloading when having collected the first batch of keys - // https://github.com/paritytech/polkadot-sdk/issues/2494 - let keys = self - .rpc_get_keys_parallel(&prefix, at, Self::PARALLEL_REQUESTS) - .await? - .into_iter() - .collect::<Vec<_>>(); - sp.stop_with_message(format!( - "✅ Found {} keys ({:.2}s)", - keys.len(), - start.elapsed().as_secs_f32() - )); + ) -> Result<Vec<KeyValue>> { + let keys = logging::with_elapsed_async( + || async { + // TODO: We could start downloading when having collected the first batch of keys. + // https://github.com/paritytech/polkadot-sdk/issues/2494 + let keys = self + .rpc_get_keys_parallel(&prefix, at, Self::PARALLEL_REQUESTS) + .await? + .into_iter() + .collect::<Vec<_>>(); + + Ok(keys) + }, + "Scraping keys...", + |keys| format!("Found {} keys", keys.len()), + ) + .await?; + if keys.is_empty() { return Ok(Default::default()) } @@ -735,7 +736,7 @@ where let storage_data = match storage_data_result { Ok(storage_data) => storage_data.into_iter().flatten().collect::<Vec<_>>(), Err(e) => { - log::error!(target: LOG_TARGET, "Error while getting storage data: {}", e); + error!(target: LOG_TARGET, "Error while getting storage data: {e}"); return Err("Error while getting storage data") }, }; @@ -751,27 +752,31 @@ where .map(|(key, maybe_value)| match maybe_value { Some(data) => (key.clone(), data), None => { - log::warn!(target: LOG_TARGET, "key {:?} had none corresponding value.", &key); + warn!(target: LOG_TARGET, "key {key:?} had none corresponding value."); let data = StorageData(vec![]); (key.clone(), data) }, }) .collect::<Vec<_>>(); - let mut sp = Spinner::with_timer(Spinners::Dots, "Inserting keys into DB...".into()); - let start = Instant::now(); - pending_ext.batch_insert(key_values.clone().into_iter().filter_map(|(k, v)| { - // Don't insert the child keys here, they need to be inserted separately with all their - // data in the load_child_remote function. - match is_default_child_storage_key(&k.0) { - true => None, - false => Some((k.0, v.0)), - } - })); - sp.stop_with_message(format!( - "✅ Inserted keys into DB ({:.2}s)", - start.elapsed().as_secs_f32() - )); + logging::with_elapsed( + || { + pending_ext.batch_insert(key_values.clone().into_iter().filter_map(|(k, v)| { + // Don't insert the child keys here, they need to be inserted separately with + // all their data in the load_child_remote function. + match is_default_child_storage_key(&k.0) { + true => None, + false => Some((k.0, v.0)), + } + })); + + Ok(()) + }, + "Inserting keys into DB...", + |_| "Inserted keys into DB".into(), + ) + .expect("must succeed; qed"); + Ok(key_values) } @@ -781,7 +786,7 @@ where prefixed_top_key: &StorageKey, child_keys: Vec<StorageKey>, at: B::Hash, - ) -> Result<Vec<KeyValue>, &'static str> { + ) -> Result<Vec<KeyValue>> { let child_keys_len = child_keys.len(); let payloads = child_keys @@ -803,7 +808,7 @@ where match Self::get_storage_data_dynamic_batch_size(client, payloads, &bar).await { Ok(storage_data) => storage_data, Err(e) => { - log::error!(target: LOG_TARGET, "batch processing failed: {:?}", e); + error!(target: LOG_TARGET, "batch processing failed: {e:?}"); return Err("batch processing failed") }, }; @@ -816,7 +821,7 @@ where .map(|(key, maybe_value)| match maybe_value { Some(v) => (key.clone(), v), None => { - log::warn!(target: LOG_TARGET, "key {:?} had no corresponding value.", &key); + warn!(target: LOG_TARGET, "key {key:?} had no corresponding value."); (key.clone(), StorageData(vec![])) }, }) @@ -828,7 +833,7 @@ where prefixed_top_key: &StorageKey, child_prefix: StorageKey, at: B::Hash, - ) -> Result<Vec<StorageKey>, &'static str> { + ) -> Result<Vec<StorageKey>> { let retry_strategy = FixedInterval::new(Self::KEYS_PAGE_RETRY_INTERVAL).take(Self::MAX_RETRIES); let mut all_child_keys = Vec::new(); @@ -850,7 +855,7 @@ where let child_keys = Retry::spawn(retry_strategy.clone(), get_child_keys_closure) .await .map_err(|e| { - error!(target: LOG_TARGET, "Error = {:?}", e); + error!(target: LOG_TARGET, "Error = {e:?}"); "rpc child_get_keys failed." })?; @@ -896,7 +901,7 @@ where &self, top_kv: &[KeyValue], pending_ext: &mut TestExternalities<HashingFor<B>>, - ) -> Result<ChildKeyValues, &'static str> { + ) -> Result<ChildKeyValues> { let child_roots = top_kv .iter() .filter(|(k, _)| is_default_child_storage_key(k.as_ref())) @@ -904,7 +909,7 @@ where .collect::<Vec<_>>(); if child_roots.is_empty() { - info!(target: LOG_TARGET, "👩â€ðŸ‘¦ no child roots found to scrape",); + info!(target: LOG_TARGET, "👩â€ðŸ‘¦ no child roots found to scrape"); return Ok(Default::default()) } @@ -930,7 +935,7 @@ where let un_prefixed = match ChildType::from_prefixed_key(&prefixed_top_key) { Some((ChildType::ParentKeyId, storage_key)) => storage_key, None => { - log::error!(target: LOG_TARGET, "invalid key: {:?}", prefixed_top_key); + error!(target: LOG_TARGET, "invalid key: {prefixed_top_key:?}"); return Err("Invalid child key") }, }; @@ -954,13 +959,13 @@ where async fn load_top_remote( &self, pending_ext: &mut TestExternalities<HashingFor<B>>, - ) -> Result<TopKeyValues, &'static str> { + ) -> Result<TopKeyValues> { let config = self.as_online(); let at = self .as_online() .at .expect("online config must be initialized by this point; qed."); - log::info!(target: LOG_TARGET, "scraping key-pairs from remote at block height {:?}", at); + info!(target: LOG_TARGET, "scraping key-pairs from remote at block height {at:?}"); let mut keys_and_values = Vec::new(); for prefix in &config.hashed_prefixes { @@ -968,7 +973,7 @@ where let additional_key_values = self.rpc_get_pairs(StorageKey(prefix.to_vec()), at, pending_ext).await?; let elapsed = now.elapsed(); - log::info!( + info!( target: LOG_TARGET, "adding data for hashed prefix: {:?}, took {:.2}s", HexDisplay::from(prefix), @@ -979,7 +984,7 @@ where for key in &config.hashed_keys { let key = StorageKey(key.to_vec()); - log::info!( + info!( target: LOG_TARGET, "adding data for hashed key: {:?}", HexDisplay::from(&key) @@ -990,7 +995,7 @@ where keys_and_values.push((key, value)); }, None => { - log::warn!( + warn!( target: LOG_TARGET, "no data found for hashed key: {:?}", HexDisplay::from(&key) @@ -1005,17 +1010,16 @@ where /// The entry point of execution, if `mode` is online. /// /// initializes the remote client in `transport`, and sets the `at` field, if not specified. - async fn init_remote_client(&mut self) -> Result<(), &'static str> { + async fn init_remote_client(&mut self) -> Result<()> { // First, initialize the http client. self.as_online_mut().transport.init().await?; // Then, if `at` is not set, set it. if self.as_online().at.is_none() { let at = self.rpc_get_head().await?; - log::info!( + info!( target: LOG_TARGET, - "since no at is provided, setting it to latest finalized head, {:?}", - at + "since no at is provided, setting it to latest finalized head, {at:?}", ); self.as_online_mut().at = Some(at); } @@ -1040,7 +1044,7 @@ where .filter(|p| *p != DEFAULT_CHILD_STORAGE_KEY_PREFIX) .count() == 0 { - log::info!( + info!( target: LOG_TARGET, "since no prefix is filtered, the data for all pallets will be downloaded" ); @@ -1050,7 +1054,7 @@ where Ok(()) } - async fn load_header(&self) -> Result<B::Header, &'static str> { + async fn load_header(&self) -> Result<B::Header> { let retry_strategy = FixedInterval::new(Self::KEYS_PAGE_RETRY_INTERVAL).take(Self::MAX_RETRIES); let get_header_closure = || { @@ -1069,14 +1073,12 @@ where /// `load_child_remote`. /// /// Must be called after `init_remote_client`. - async fn load_remote_and_maybe_save( - &mut self, - ) -> Result<TestExternalities<HashingFor<B>>, &'static str> { + async fn load_remote_and_maybe_save(&mut self) -> Result<TestExternalities<HashingFor<B>>> { let state_version = StateApi::<B::Hash>::runtime_version(self.as_online().rpc_client(), None) .await .map_err(|e| { - error!(target: LOG_TARGET, "Error = {:?}", e); + error!(target: LOG_TARGET, "Error = {e:?}"); "rpc runtime_version failed." }) .map(|v| v.state_version())?; @@ -1100,11 +1102,10 @@ where self.load_header().await?, ); let encoded = snapshot.encode(); - log::info!( + info!( target: LOG_TARGET, - "writing snapshot of {} bytes to {:?}", + "writing snapshot of {} bytes to {path:?}", encoded.len(), - path ); std::fs::write(path, encoded).map_err(|_| "fs::write failed")?; @@ -1119,33 +1120,35 @@ where Ok(pending_ext) } - async fn do_load_remote(&mut self) -> Result<RemoteExternalities<B>, &'static str> { + async fn do_load_remote(&mut self) -> Result<RemoteExternalities<B>> { self.init_remote_client().await?; let inner_ext = self.load_remote_and_maybe_save().await?; Ok(RemoteExternalities { header: self.load_header().await?, inner_ext }) } - fn do_load_offline( - &mut self, - config: OfflineConfig, - ) -> Result<RemoteExternalities<B>, &'static str> { - let mut sp = Spinner::with_timer(Spinners::Dots, "Loading snapshot...".into()); - let start = Instant::now(); - info!(target: LOG_TARGET, "Loading snapshot from {:?}", &config.state_snapshot.path); - let Snapshot { snapshot_version: _, header, state_version, raw_storage, storage_root } = - Snapshot::<B>::load(&config.state_snapshot.path)?; - - let inner_ext = TestExternalities::from_raw_snapshot( - raw_storage, - storage_root, - self.overwrite_state_version.unwrap_or(state_version), - ); - sp.stop_with_message(format!("✅ Loaded snapshot ({:.2}s)", start.elapsed().as_secs_f32())); + fn do_load_offline(&mut self, config: OfflineConfig) -> Result<RemoteExternalities<B>> { + let (header, inner_ext) = logging::with_elapsed( + || { + info!(target: LOG_TARGET, "Loading snapshot from {:?}", &config.state_snapshot.path); + + let Snapshot { header, state_version, raw_storage, storage_root, .. } = + Snapshot::<B>::load(&config.state_snapshot.path)?; + let inner_ext = TestExternalities::from_raw_snapshot( + raw_storage, + storage_root, + self.overwrite_state_version.unwrap_or(state_version), + ); + + Ok((header, inner_ext)) + }, + "Loading snapshot...", + |_| "Loaded snapshot".into(), + )?; Ok(RemoteExternalities { inner_ext, header }) } - pub(crate) async fn pre_build(mut self) -> Result<RemoteExternalities<B>, &'static str> { + pub(crate) async fn pre_build(mut self) -> Result<RemoteExternalities<B>> { let mut ext = match self.mode.clone() { Mode::Offline(config) => self.do_load_offline(config)?, Mode::Online(_) => self.do_load_remote().await?, @@ -1159,7 +1162,7 @@ where // inject manual key values. if !self.hashed_key_values.is_empty() { - log::info!( + info!( target: LOG_TARGET, "extending externalities with {} manually injected key-values", self.hashed_key_values.len() @@ -1169,7 +1172,7 @@ where // exclude manual key values. if !self.hashed_blacklist.is_empty() { - log::info!( + info!( target: LOG_TARGET, "excluding externalities from {} keys", self.hashed_blacklist.len() @@ -1221,7 +1224,7 @@ where self } - pub async fn build(self) -> Result<RemoteExternalities<B>, &'static str> { + pub async fn build(self) -> Result<RemoteExternalities<B>> { let mut ext = self.pre_build().await?; ext.commit_all().unwrap(); diff --git a/substrate/utils/frame/remote-externalities/src/logging.rs b/substrate/utils/frame/remote-externalities/src/logging.rs new file mode 100644 index 0000000000000000000000000000000000000000..7ab901c004de2b65d42b343d2f73297d215c4720 --- /dev/null +++ b/substrate/utils/frame/remote-externalities/src/logging.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. + +use std::{ + future::Future, + io::{self, IsTerminal}, + time::Instant, +}; + +use spinners::{Spinner, Spinners}; + +use super::Result; + +// A simple helper to time a operation with a nice spinner, start message, and end message. +// +// The spinner is only displayed when stdout is a terminal. +pub(super) fn with_elapsed<F, R, EndMsg>(f: F, start_msg: &str, end_msg: EndMsg) -> Result<R> +where + F: FnOnce() -> Result<R>, + EndMsg: FnOnce(&R) -> String, +{ + let timer = Instant::now(); + let mut maybe_sp = start(start_msg); + + Ok(end(f()?, timer, maybe_sp.as_mut(), end_msg)) +} + +// A simple helper to time an async operation with a nice spinner, start message, and end message. +// +// The spinner is only displayed when stdout is a terminal. +pub(super) async fn with_elapsed_async<F, Fut, R, EndMsg>( + f: F, + start_msg: &str, + end_msg: EndMsg, +) -> Result<R> +where + F: FnOnce() -> Fut, + Fut: Future<Output = Result<R>>, + EndMsg: FnOnce(&R) -> String, +{ + let timer = Instant::now(); + let mut maybe_sp = start(start_msg); + + Ok(end(f().await?, timer, maybe_sp.as_mut(), end_msg)) +} + +fn start(start_msg: &str) -> Option<Spinner> { + let msg = format!("â³ {start_msg}"); + + if io::stdout().is_terminal() { + Some(Spinner::new(Spinners::Dots, msg)) + } else { + println!("{msg}"); + + None + } +} + +fn end<T, EndMsg>(val: T, timer: Instant, maybe_sp: Option<&mut Spinner>, end_msg: EndMsg) -> T +where + EndMsg: FnOnce(&T) -> String, +{ + let msg = format!("✅ {} in {:.2}s", end_msg(&val), timer.elapsed().as_secs_f32()); + + if let Some(sp) = maybe_sp { + sp.stop_with_message(msg); + } else { + println!("{msg}"); + } + + val +} diff --git a/substrate/utils/frame/rpc/client/Cargo.toml b/substrate/utils/frame/rpc/client/Cargo.toml index d26be3a131244280b1eefa7707eb35d864c7e442..6282621e1c750e84550d56dbb415d3c9d5adbd9b 100644 --- a/substrate/utils/frame/rpc/client/Cargo.toml +++ b/substrate/utils/frame/rpc/client/Cargo.toml @@ -15,13 +15,13 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +async-trait = { workspace = true } jsonrpsee = { features = ["ws-client"], workspace = true } +log = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } -async-trait = { workspace = true } serde = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -log = { workspace = true, default-features = true } [dev-dependencies] -tokio = { features = ["macros", "rt-multi-thread", "sync"], workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +tokio = { features = ["macros", "rt-multi-thread", "sync"], workspace = true, default-features = true } diff --git a/substrate/utils/frame/rpc/support/Cargo.toml b/substrate/utils/frame/rpc/support/Cargo.toml index 82652c8fa2627a781385a9b16d59727013e82b60..45b2bc6fa9b3a5573614314ad09230889f9c3e64 100644 --- a/substrate/utils/frame/rpc/support/Cargo.toml +++ b/substrate/utils/frame/rpc/support/Cargo.toml @@ -16,16 +16,16 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true, default-features = true } -jsonrpsee = { features = ["jsonrpsee-types"], workspace = true } -serde = { workspace = true, default-features = true } frame-support = { workspace = true, default-features = true } +jsonrpsee = { features = ["jsonrpsee-types"], workspace = true } sc-rpc-api = { workspace = true, default-features = true } +serde = { workspace = true, default-features = true } sp-storage = { workspace = true, default-features = true } [dev-dependencies] -scale-info = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } jsonrpsee = { features = ["jsonrpsee-types", "ws-client"], workspace = true } -tokio = { workspace = true, default-features = true } +scale-info = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } +tokio = { workspace = true, default-features = true } diff --git a/substrate/utils/frame/rpc/system/Cargo.toml b/substrate/utils/frame/rpc/system/Cargo.toml index 5757a48498c7028c9dadefbd7f44b87c140c2a89..68dfbb833c6fef3a34867d70a591e311ee19f44a 100644 --- a/substrate/utils/frame/rpc/system/Cargo.toml +++ b/substrate/utils/frame/rpc/system/Cargo.toml @@ -16,16 +16,16 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -futures = { workspace = true } codec = { workspace = true, default-features = true } docify = { workspace = true } +frame-system-rpc-runtime-api = { workspace = true, default-features = true } +futures = { workspace = true } jsonrpsee = { features = [ "client-core", "macros", "server-core", ], workspace = true } log = { workspace = true, default-features = true } -frame-system-rpc-runtime-api = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } @@ -35,8 +35,8 @@ sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } [dev-dependencies] -sc-transaction-pool = { workspace = true, default-features = true } -tokio = { workspace = true, default-features = true } assert_matches = { workspace = true } +sc-transaction-pool = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } +tokio = { workspace = true, default-features = true } diff --git a/substrate/utils/frame/rpc/system/src/lib.rs b/substrate/utils/frame/rpc/system/src/lib.rs index 824c871a3562d224b27f8f8a51de69bd17b6dca8..e1b3994c03ddc41bd9bc48353e30060b044954f0 100644 --- a/substrate/utils/frame/rpc/system/src/lib.rs +++ b/substrate/utils/frame/rpc/system/src/lib.rs @@ -224,7 +224,7 @@ mod tests { transaction_validity::{InvalidTransaction, TransactionValidityError}, ApplyExtrinsicResult, }; - use substrate_test_runtime_client::{runtime::Transfer, AccountKeyring}; + use substrate_test_runtime_client::{runtime::Transfer, Sr25519Keyring}; fn deny_unsafe() -> Extensions { let mut ext = Extensions::new(); @@ -256,8 +256,8 @@ mod tests { let source = sp_runtime::transaction_validity::TransactionSource::External; let new_transaction = |nonce: u64| { let t = Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Bob.into(), amount: 5, nonce, }; @@ -273,7 +273,7 @@ mod tests { let accounts = System::new(client, pool); // when - let nonce = accounts.nonce(AccountKeyring::Alice.into()).await; + let nonce = accounts.nonce(Sr25519Keyring::Alice.into()).await; // then assert_eq!(nonce.unwrap(), 2); @@ -321,8 +321,8 @@ mod tests { let accounts = System::new(client, pool); let tx = Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Bob.into(), amount: 5, nonce: 0, } @@ -357,8 +357,8 @@ mod tests { let accounts = System::new(client, pool); let tx = Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Bob.into(), amount: 5, nonce: 100, } diff --git a/substrate/utils/prometheus/src/lib.rs b/substrate/utils/prometheus/src/lib.rs index 5edac2e6650f528c5f138c800fa3c9b350be52c5..ae39cb4a7dd319f952cbe7a2e54eb3cdeffd54f1 100644 --- a/substrate/utils/prometheus/src/lib.rs +++ b/substrate/utils/prometheus/src/lib.rs @@ -87,7 +87,7 @@ async fn request_metrics( /// to serve metrics. pub async fn init_prometheus(prometheus_addr: SocketAddr, registry: Registry) -> Result<(), Error> { let listener = tokio::net::TcpListener::bind(&prometheus_addr).await.map_err(|e| { - log::error!(target: "prometheus", "Error binding to '{:#?}': {:#?}", prometheus_addr, e); + log::error!(target: "prometheus", "Error binding to '{prometheus_addr:?}': {e:?}"); Error::PortInUse(prometheus_addr) })?; diff --git a/substrate/utils/wasm-builder/Cargo.toml b/substrate/utils/wasm-builder/Cargo.toml index 8f0e8a23e54af70f3281740ef1f1a0db5b4e643c..6645dd1803bfac3c8315f7f5a747a4e4b2e58d80 100644 --- a/substrate/utils/wasm-builder/Cargo.toml +++ b/substrate/utils/wasm-builder/Cargo.toml @@ -18,28 +18,28 @@ targets = ["x86_64-unknown-linux-gnu"] build-helper = { workspace = true } cargo_metadata = { workspace = true } console = { workspace = true } +filetime = { workspace = true } +jobserver = { workspace = true } +parity-wasm = { workspace = true } +polkavm-linker = { workspace = true } +sp-maybe-compressed-blob = { workspace = true, default-features = true } strum = { features = ["derive"], workspace = true, default-features = true } tempfile = { workspace = true } toml = { workspace = true } walkdir = { workspace = true } -sp-maybe-compressed-blob = { workspace = true, default-features = true } -filetime = { workspace = true } wasm-opt = { workspace = true } -parity-wasm = { workspace = true } -polkavm-linker = { workspace = true } -jobserver = { workspace = true } # Dependencies required for the `metadata-hash` feature. +array-bytes = { optional = true, workspace = true, default-features = true } +codec = { optional = true, workspace = true, default-features = true } +frame-metadata = { features = ["current", "unstable"], optional = true, workspace = true, default-features = true } merkleized-metadata = { optional = true, workspace = true } sc-executor = { optional = true, workspace = true, default-features = true } +shlex = { workspace = true } sp-core = { optional = true, workspace = true, default-features = true } sp-io = { optional = true, workspace = true, default-features = true } -sp-version = { optional = true, workspace = true, default-features = true } -frame-metadata = { features = ["current"], optional = true, workspace = true, default-features = true } -codec = { optional = true, workspace = true, default-features = true } -array-bytes = { optional = true, workspace = true, default-features = true } sp-tracing = { optional = true, workspace = true, default-features = true } -shlex = { workspace = true } +sp-version = { optional = true, workspace = true, default-features = true } [features] # Enable support for generating the metadata hash. diff --git a/substrate/utils/wasm-builder/src/builder.rs b/substrate/utils/wasm-builder/src/builder.rs index a40aafe1d8127d5c250d729ce07ceac210f27757..5bdc743eac314ba8c24e841c75a07de02130e8a7 100644 --- a/substrate/utils/wasm-builder/src/builder.rs +++ b/substrate/utils/wasm-builder/src/builder.rs @@ -235,7 +235,8 @@ impl WasmBuilder { /// Build the WASM binary. pub fn build(mut self) { - let target = crate::runtime_target(); + let target = RuntimeTarget::new(); + if target == RuntimeTarget::Wasm { if self.export_heap_base { self.rust_flags.push("-Clink-arg=--export=__heap_base".into()); diff --git a/substrate/utils/wasm-builder/src/lib.rs b/substrate/utils/wasm-builder/src/lib.rs index 420ecd63e1dcc2b799d44bc984528346321f9a4b..ce90f492e08fa1c94df30e80686c537b88120650 100644 --- a/substrate/utils/wasm-builder/src/lib.rs +++ b/substrate/utils/wasm-builder/src/lib.rs @@ -112,7 +112,6 @@ //! wasm32-unknown-unknown --toolchain nightly-2020-02-20`. use std::{ - collections::BTreeSet, env, fs, io::BufRead, path::{Path, PathBuf}, @@ -254,26 +253,22 @@ struct CargoCommand { program: String, args: Vec<String>, version: Option<Version>, - target_list: Option<BTreeSet<String>>, } impl CargoCommand { fn new(program: &str) -> Self { let version = Self::extract_version(program, &[]); - let target_list = Self::extract_target_list(program, &[]); - CargoCommand { program: program.into(), args: Vec::new(), version, target_list } + CargoCommand { program: program.into(), args: Vec::new(), version } } fn new_with_args(program: &str, args: &[&str]) -> Self { let version = Self::extract_version(program, args); - let target_list = Self::extract_target_list(program, args); CargoCommand { program: program.into(), args: args.iter().map(ToString::to_string).collect(), version, - target_list, } } @@ -294,23 +289,6 @@ impl CargoCommand { Version::extract(&version) } - fn extract_target_list(program: &str, args: &[&str]) -> Option<BTreeSet<String>> { - // This is technically an unstable option, but we don't care because we only need this - // to build RISC-V runtimes, and those currently require a specific nightly toolchain - // anyway, so it's totally fine for this to fail in other cases. - let list = Command::new(program) - .args(args) - .args(&["rustc", "-Z", "unstable-options", "--print", "target-list"]) - // Make sure if we're called from within a `build.rs` the host toolchain won't override - // a rustup toolchain we've picked. - .env_remove("RUSTC") - .output() - .ok() - .and_then(|o| String::from_utf8(o.stdout).ok())?; - - Some(list.trim().split("\n").map(ToString::to_string).collect()) - } - /// Returns the version of this cargo command or `None` if it failed to extract the version. fn version(&self) -> Option<Version> { self.version @@ -326,19 +304,10 @@ impl CargoCommand { fn supports_substrate_runtime_env(&self, target: RuntimeTarget) -> bool { match target { RuntimeTarget::Wasm => self.supports_substrate_runtime_env_wasm(), - RuntimeTarget::Riscv => self.supports_substrate_runtime_env_riscv(), + RuntimeTarget::Riscv => true, } } - /// Check if the supplied cargo command supports our RISC-V runtime environment. - fn supports_substrate_runtime_env_riscv(&self) -> bool { - let Some(target_list) = self.target_list.as_ref() else { return false }; - // This is our custom target which currently doesn't exist on any upstream toolchain, - // so if it exists it's guaranteed to be our custom toolchain and have have everything - // we need, so any further version checks are unnecessary at this point. - target_list.contains("riscv32ema-unknown-none-elf") - } - /// Check if the supplied cargo command supports our Substrate wasm environment. /// /// This means that either the cargo version is at minimum 1.68.0 or this is a nightly cargo. @@ -409,13 +378,6 @@ fn get_bool_environment_variable(name: &str) -> Option<bool> { } } -/// Returns whether we need to also compile the standard library when compiling the runtime. -fn build_std_required() -> bool { - let default = runtime_target() == RuntimeTarget::Wasm; - - crate::get_bool_environment_variable(crate::WASM_BUILD_STD).unwrap_or(default) -} - #[derive(Copy, Clone, PartialEq, Eq)] enum RuntimeTarget { Wasm, @@ -423,36 +385,55 @@ enum RuntimeTarget { } impl RuntimeTarget { - fn rustc_target(self) -> &'static str { + /// Creates a new instance. + fn new() -> Self { + let Some(value) = env::var_os(RUNTIME_TARGET) else { + return Self::Wasm; + }; + + if value == "wasm" { + Self::Wasm + } else if value == "riscv" { + Self::Riscv + } else { + build_helper::warning!( + "RUNTIME_TARGET environment variable must be set to either \"wasm\" or \"riscv\"" + ); + std::process::exit(1); + } + } + + /// Figures out the target parameter value for rustc. + fn rustc_target(self) -> String { match self { - RuntimeTarget::Wasm => "wasm32-unknown-unknown", - RuntimeTarget::Riscv => "riscv32ema-unknown-none-elf", + RuntimeTarget::Wasm => "wasm32-unknown-unknown".to_string(), + RuntimeTarget::Riscv => { + let path = polkavm_linker::target_json_32_path().expect("riscv not found"); + path.into_os_string().into_string().unwrap() + }, } } - fn build_subdirectory(self) -> &'static str { - // Keep the build directories separate so that when switching between - // the targets we won't trigger unnecessary rebuilds. + /// Figures out the target directory name used by cargo. + fn rustc_target_dir(self) -> &'static str { match self { - RuntimeTarget::Wasm => "wbuild", - RuntimeTarget::Riscv => "rbuild", + RuntimeTarget::Wasm => "wasm32-unknown-unknown", + RuntimeTarget::Riscv => "riscv32emac-unknown-none-polkavm", } } -} -fn runtime_target() -> RuntimeTarget { - let Some(value) = env::var_os(RUNTIME_TARGET) else { - return RuntimeTarget::Wasm; - }; + /// Figures out the build-std argument. + fn rustc_target_build_std(self) -> Option<&'static str> { + if !crate::get_bool_environment_variable(crate::WASM_BUILD_STD).unwrap_or(true) { + return None; + } - if value == "wasm" { - RuntimeTarget::Wasm - } else if value == "riscv" { - RuntimeTarget::Riscv - } else { - build_helper::warning!( - "the '{RUNTIME_TARGET}' environment variable has an invalid value; it must be either 'wasm' or 'riscv'" - ); - std::process::exit(1); + // This is a nightly-only flag. + let arg = match self { + RuntimeTarget::Wasm => "build-std", + RuntimeTarget::Riscv => "build-std=core,alloc", + }; + + Some(arg) } } diff --git a/substrate/utils/wasm-builder/src/prerequisites.rs b/substrate/utils/wasm-builder/src/prerequisites.rs index 4de6b87f618db66caf1095e47b93056f513b7663..9abfd1725237d6be416e322835282f3879de7615 100644 --- a/substrate/utils/wasm-builder/src/prerequisites.rs +++ b/substrate/utils/wasm-builder/src/prerequisites.rs @@ -196,11 +196,14 @@ fn check_wasm_toolchain_installed( error, colorize_aux_message(&"-".repeat(60)), )) - } + }; } let version = dummy_crate.get_rustc_version(); - if crate::build_std_required() { + + let target = RuntimeTarget::new(); + assert!(target == RuntimeTarget::Wasm); + if target.rustc_target_build_std().is_some() { if let Some(sysroot) = dummy_crate.get_sysroot() { let src_path = Path::new(sysroot.trim()).join("lib").join("rustlib").join("src").join("rust"); diff --git a/substrate/utils/wasm-builder/src/wasm_project.rs b/substrate/utils/wasm-builder/src/wasm_project.rs index 26edd2ea1f2242525ed61216a309a1d9be367881..6530e4c22fb9cdfa1395ae5af4887f3a001a7304 100644 --- a/substrate/utils/wasm-builder/src/wasm_project.rs +++ b/substrate/utils/wasm-builder/src/wasm_project.rs @@ -109,6 +109,15 @@ fn crate_metadata(cargo_manifest: &Path) -> Metadata { crate_metadata } +/// Keep the build directories separate so that when switching between the +/// targets we won't trigger unnecessary rebuilds. +fn build_subdirectory(target: RuntimeTarget) -> &'static str { + match target { + RuntimeTarget::Wasm => "wbuild", + RuntimeTarget::Riscv => "rbuild", + } +} + /// Creates the WASM project, compiles the WASM binary and compacts the WASM binary. /// /// # Returns @@ -125,7 +134,7 @@ pub(crate) fn create_and_compile( #[cfg(feature = "metadata-hash")] enable_metadata_hash: Option<MetadataExtraInfo>, ) -> (Option<WasmBinary>, WasmBinaryBloaty) { let runtime_workspace_root = get_wasm_workspace_root(); - let runtime_workspace = runtime_workspace_root.join(target.build_subdirectory()); + let runtime_workspace = runtime_workspace_root.join(build_subdirectory(target)); let crate_metadata = crate_metadata(orig_project_cargo_toml); @@ -770,7 +779,7 @@ impl BuildConfiguration { .collect::<Vec<_>>() .iter() .rev() - .take_while(|c| c.as_os_str() != target.build_subdirectory()) + .take_while(|c| c.as_os_str() != build_subdirectory(target)) .last() .expect("We put the runtime project within a `target/.../[rw]build` path; qed") .as_os_str() @@ -841,9 +850,7 @@ fn build_bloaty_blob( "-C target-cpu=mvp -C target-feature=-sign-ext -C link-arg=--export-table ", ); }, - RuntimeTarget::Riscv => { - rustflags.push_str("-C target-feature=+lui-addi-fusion -C relocation-model=pie -C link-arg=--emit-relocs -C link-arg=--unique "); - }, + RuntimeTarget::Riscv => (), } rustflags.push_str(default_rustflags); @@ -907,10 +914,9 @@ fn build_bloaty_blob( // // So here we force the compiler to also compile the standard library crates for us // to make sure that they also only use the MVP features. - if crate::build_std_required() { - // Unfortunately this is still a nightly-only flag, but FWIW it is pretty widely used - // so it's unlikely to break without a replacement. - build_cmd.arg("-Z").arg("build-std"); + if let Some(arg) = target.rustc_target_build_std() { + build_cmd.arg("-Z").arg(arg); + if !cargo_cmd.supports_nightly_features() { build_cmd.env("RUSTC_BOOTSTRAP", "1"); } @@ -934,7 +940,7 @@ fn build_bloaty_blob( let blob_name = get_blob_name(target, &manifest_path); let target_directory = project .join("target") - .join(target.rustc_target()) + .join(target.rustc_target_dir()) .join(blob_build_profile.directory()); match target { RuntimeTarget::Riscv => { @@ -968,7 +974,7 @@ fn build_bloaty_blob( }, }; - std::fs::write(&polkavm_path, program.as_bytes()) + std::fs::write(&polkavm_path, program) .expect("writing the blob to a file always works"); } diff --git a/templates/minimal/README.md b/templates/minimal/README.md index cf43d71d884993c51ba1265ef49d445d4866d1ec..4cf3fd2a44bb44289c3a2a1e7b255f6ba0d1ee47 100644 --- a/templates/minimal/README.md +++ b/templates/minimal/README.md @@ -42,6 +42,7 @@ such as a [Balances pallet](https://paritytech.github.io/polkadot-sdk/master/pal - 👤 The template has no consensus configured - it is best for experimenting with a single node network. + ## Template Structure A Polkadot SDK based project such as this one consists of: @@ -61,7 +62,7 @@ compiled unless building the entire workspace). - ðŸ› ï¸ Depending on your operating system and Rust version, there might be additional packages required to compile this template - please take note of the Rust compiler output. -Fetch minimal template code: +Fetch minimal template code. ```sh git clone https://github.com/paritytech/polkadot-sdk-minimal-template.git minimal-template @@ -105,12 +106,11 @@ Omni Node, nonetheless. #### Run Omni Node -Start Omni Node with manual seal (3 seconds block times), minimal template runtime based -chain spec. We'll use `--tmp` flag to start the node with its configurations stored in a -temporary directory, which will be deleted at the end of the process. +Start Omni Node in development mode (sets up block production and finalization based on manual seal, +sealing a new block every 3 seconds), with a minimal template runtime chain spec. ```sh -polkadot-omni-node --chain <path/to/chain_spec.json> --dev-block-time 3000 --tmp +polkadot-omni-node --chain <path/to/chain_spec.json> --dev ``` ### Minimal Template Node @@ -148,11 +148,13 @@ docker run --rm polkadot-sdk-minimal-template We can install `zombienet` as described [here](https://paritytech.github.io/zombienet/install.html#installation), and `zombienet-omni-node.toml` contains the network specification we want to start. + #### Update `zombienet-omni-node.toml` with a valid chain spec path -Before starting the network with zombienet we must update the network specification -with a valid chain spec path. If we need to generate one, we can look up at the previous -section for chain spec creation [here](#use-chain-spec-builder-to-generate-the-chain_specjson-file). +To simplify the process of starting the minimal template with ZombieNet and Omni Node, we've included a +pre-configured development chain spec (dev_chain_spec.json) in the minimal template. The zombienet-omni-node.toml +file in this template points to it, but you can update it to a new path for the chain spec generated on your machine. +To generate a chain spec refer to [staging-chain-spec-builder](https://crates.io/crates/staging-chain-spec-builder) Then make the changes in the network specification like so: @@ -160,7 +162,7 @@ Then make the changes in the network specification like so: # ... chain = "dev" chain_spec_path = "<TO BE UPDATED WITH A VALID PATH>" -default_args = ["--dev-block-time 3000"] +default_args = ["--dev"] # .. ``` diff --git a/templates/minimal/dev_chain_spec.json b/templates/minimal/dev_chain_spec.json new file mode 100644 index 0000000000000000000000000000000000000000..91d703c6fd5bd0bdb1793bc4b208dd1e71ac055c --- /dev/null +++ b/templates/minimal/dev_chain_spec.json @@ -0,0 +1,85 @@ +{ + "bootNodes": [], + "chainType": "Live", + "codeSubstitutes": {}, + "genesis": { + "runtimeGenesis": { + "code": "0x52bc537646db8e0528b52ffd0058bc49057eb84632144d10784894742c8bde903e277d4efa9ca4a5f0e2c7b95fe173920d2da637ed48cf23dbde08721d93d1ee93927f0102b631505ddb8ea0b7b14774f612445415d300fa8db44608216493bdf7965b064c159913b313096873c0d17b6e346bb3af5a029e75557bc411556f2ba2ca5e9ad86212d0f07087ae3d5d198eaeed1243c2d34ca1707fc049eb76aaa5caaceb92189407aafbc3c594d60975bb7b688f583fb47b6c09d87695bbc6769f302219c8da27fe84dd441facc8c231b5d69a74ad3d6e2d9240ed9309ac29cddf13d175b79bb5737b8918ef3dab86bbefa39046d142880dbc14169b2d78a6a5f93f2e206435a7f7325d5bba6e1a9888023fdb164ce213c96adba2f975d3d0fcd27be662eef0c51170700433e2f0f1b1817787736ddcf4793fe0420726c8c1126ca26821c4c65d0a8bcd8ba205dbc44b59808d3b9c521660532aa309f712132e86efda683e7ca98c26e2212dd5b8187ea4a5329a8077b47417c38754a6eecf8f7bdd36160f27a082187008830fccb08537fffa41b6363d3060630937b88113de18421d6cd6e6bdcaf47d7c6cdc163c43849beb62a6f40ac1f37a1a3f1f6c1146bff4fe6c21a58f7022d3d3efed5688efb09baa759c3be4e907e5684775a6c7579eaeeeed136d12294e0e6df2f8fa682db53b9cf10fc962614a472a86847f112767e5e878d864b130a5e12bf78bb4c9cad1b08902a2fc34a46258c153c4179e50c4179e406b15eddee67e310c14f185275450bfc32986153c2b870df5d57155aab4183e4056b0832ea8f0105482b086cfa362e568317c8af8c2137ca202a2fc489fc5c294ca042b870d457d560e9b7dbdfa7d3701bb45d4fd49c14fefb416c38707198480051b7c7c6c965ac123a9cffed1ea1cad54b46b42640632f0e00f519660697e0df3700fe43886b55164fb8302321af6fe4861a35734467b8f4aba304e045691b586ec3704218cb4350e2c76eb806cb107f4f47bb4dd6ab3486bd966eb9ed6844002c3e97b8b677d6f87737404bec3699aa6f6699a265adf197e9ab1450869757f144705304218638c31c2181fdbe1c469f44a021492382f9cb57bc694e8699cdd577edbac31c172f1551e3e4e1cf978374747288ebcfbb6bbe2746fd3e2186fc568b9ab4ddf81dd6ebd69a6c6f3db1f7c23d2b6d12acf140aece9356cc1abcde9703274b3ba4b8a03cf9f627478b0da9b15febdcdd191f6f84871e0dbb72d9e4e748074fc0e6c7ecb10775d3ca3333514ee788933fd495adba7b7e6378c08b7346337fbe3e3e3d34c2bfc44a1c08eb7e0e5663bcff8c59278e7d668758714e7316c3c45ca18bd3f2780a36ba8df05d0fb738237bc432b4eb74c6acba4fa9a1fa7bbe499dad34ce50f086f410889b8d7309cadad76fd783b9cf36149b486af61c933a322ade121958d732501521d9ee6f6284ef684054076f56b2bf460737f309e3f625a84efeee21b3c8e8b973cc31dafaee11aee69adad61ad03322b24058da7444fa39249efef56e8003a3cfdfedebead1fed80b56c6defb32e9e191d7155aab43bce0ef707a054eddeae6820edda15d6f28cfb103277fe737fb7de6cddb0e7dc6bb8f562ee35bcc472df5fd35a98ba3fed7e82309cbb3bc9d138194713a58c8a44bb43aac56dcaa8da1d09b67127e8a2dd7b9595ff24cfe0f0db9769328e71fc47abec9ab5fbb26899f4fe98d45eb3febb5a95dd5afb8f1673d0349bfdb55811fea3f2e219befbc2388ab33fcc2c05abf7278b7592ed4f0acce8e6d35f98da7c9a6fd1b46be703321cf7edda7cb8f727056474eb76c68e349f5e5a9b4fbb5b8b1179df09e5f5fecd9bacf76ed69e76df3d627453f57bdd2ff4bb13997e5427d4fbba55babaf36bdc5cbe7696b2f6d6cb07d8374ce7edbe984ed68ebe341fce021cfd1e69560d1f1f9bfdb3428f67e2f758f4deda3d0274cbeab90e9abb422e5aadeb70c75bb4725b6fbf66e5e9ab35a83ee169ebefd5778fa00e5a21205aadbb63a7e6ae108cd6ebee35ced299aa54a9f2877ebf66ed29d2fabd86adb95d4d9c47a1b48ebfe18e77d89177a6b5a7f9dbd6dbacaddb1d56241e746b8e6ebac624aeef0a69af71357c4d9d56a5e541738425319da2b535f56ad6d6d5ad386beb78ec0f43f21ebf1891eb356c2d08b2fdc9e20dfd1e6fadc5d7624960b7687d6d3986221b74106dd8cdeb78cb5a0c446b4f837e4d28b2af5bb3b6b6be7bc445ebf59c237a578875eb35ce9ad6745f0c44755c63b3c6bb2f76e43dae1a3e26689eb1be7f3c537ddfb0245a95bee8ae906a519d56a5adc35b743708e5aa769f15023fcd1a7ba8164d60ddd16411fabfd3b3e0f17821fc2c7c1cf401b8678182769717f1753459f8a0a595bb1fcf22885bdf4b4793854c163deee37b174793050f5ae2f150e83e440fcdf37889072d8960eee3251ebf1e7a168e96a47c464bd927f792a3254951d05e9207e2a263df678e268b8a96aa33dff2414bf4931027e23cddc74b7c1fb404e1335aaa9ebde4e3d6b378ff414b2d2d5fa125778a96a84feba5798b969c7bc9fae82c59f68b96207c454b8e9680bc7ae69ebd743da325c8f2127ccc877829fb10b4d4d2f21db4b4e3d3742068a90588ef98767c8897a60f414b40500e889728188a02120b2db17cc78e8f5876eca017a2aa0e0490aab22a6aeef0c143e4b289a98a8793d60e189af062e943146239a7cc65d5b4c382170f1f3ce40c89604551cecaa61da22be3415d3e44134bae4213ee7070870f278908406671685a178068888a0822587c588e8a8979891f1343f1e0414fcae449570002f052f50004604e53e8f43157f5184822bd344f228224433bb2d307c087e801b8005ee27101c4c4bc643d6688980be0250a124184354f4f823b4e6222882022002f4d0f002d114184c85d00430c01e4a5eb40a8d3c700712042f010c0635ef2f1181209001f829648176288c7bc545d8897320a731616f6717a228000b9104000f1d1810871202f890e449e88077929f4204de6e5027829740134980b2180077989c78578493e86f4f242ff12f39797e65fda4bd65f3ec468742180a02520b404e4427c04c4472f5d1fb597e08738e9a5eca40600225e9a4e042d018088535a02c08100e2a497dce94bd483d052000270185a1ad1d2e82c2c7fa1a51f3f3ea2a597b3b07cf4d28e8f68e9c7e83f5eba68cdc6b4c35c00425c2b07424b3f68e9c781fcba4e5faa4e69694588d3972c4ac5c6b4af5c002f55b48a8d6917002d0580a2a0c5581f8296aaea2db4d4726b885b2f51d4098b962aeb2fb454fda27ebd3429131bd3fef2d20e6a6463da49b444c48e038096282dd10f310f434b42fc9496b60d51f4c046151bd4c105526ca6164ed04516f620071a64a1059b2ce66b7c50b931ed93964ecf83eec6b40741d7c5ec8100e2e32fb43472f90f5aea510211dcd0c61b3cf001169460e3a3a7062608430b9e00063dbc81031b1f2f89a8cbc6b4fba02517ba2ea6085a49bd2f8568b631ed3da874317b1e2f39ca6463da79d0d20e8a82669385e82db4143a0b45818c4d16ee2bb424023d444b3d3b10c30f62eca00e3bc0610e366ec540071174d0821ad4f1043dd864e11e7a29a34336a63d444b20ba2e6600b40ee97d69a2321bd38e511717b32f3155b231ed172d591405249b2ce22b5a42e20869c4210876e0021d3fb0a1280a6e9345104ec043119a10062df6e0069b2cf8d34b929236a67da2a548d7c508a155a6f72548bf31ed8f662e66bfef713c312ca4ca26c9f6270a1dbd3f587cd135d4fcf542ef0f1664b4155a98e599655295cd5fa6598ee1efad866111a5a7c7f374f81aa764125fcec724feb3e9db1c6f261a5fc30d83b7da155b330679269eff78469e2fa7b96dc5b92d676b0bb678794bced60e93346e0dcb196fc5b9d3746eb28e67ccc876a417931aa4b5a7a18c72d6d7d57dc7f5f4c588c0d7b0652d96c444ebebe9d4e5ac6ff9386bad3532a1a256d1f2cb24f91a474d9d52bfcb595f7a9adb71be7e3aaedf1723f2e863929bb23d2eac6d2dc8f6ed5b590b4517935c98d40e7b7f4cd0876e6152fbfa7869afb5d6dadf61736f8ddf1cc3f72efbd1d111f877486b6bfea33badf92e899e66ba4cda065b5382b53cf3e06be4c729fbf10cfcde82b3f59bdb44dee11cbdb77eb71a4684dfe1dfbc8690c5c3bb378c88a36f6ec3b97cbdb94d4464055398b4cdff41840b9f15b2ed3022fcc5225d8cc86392c38c6c7b0dc7f91aceed375bf7b8d6e6b0160059fbdeda20b2c558ac85a96ff7c733fcfdf24cfb9ea7b533cb98e47e31c97d31229249ee354cb3dad2aeca7677e1997777ab8567de5f7806dedd6ad79bdb8bc14309a29ff4c86e97dde8e808bf9ddfe84e6b48df6c6dedccb6b799567e7b0d2f8c73ad29c1046fe82a7b9f31cdb3d94b9ed99f2be8e8fdc533adf72e3cb3bd5f98a538ef15a2a7350458db816c7fae60c35a28b2fdb9228dde76e1994a6a3e9fe468dcf92f4cd36cf819cfec8f4f1acd6fe11926c2c5152b64bb86dd8c528616b72903f6c5337bbe4caf103cada771c381b5bd30356bbed5eedaebd5dc9ad526ff607c499ea9adf70821b2fd6249382a2dbeda7c7ded114e1a1dad3335949590e5ae365d6350e2154743b9407b8493e6d33a0ce876fe62489e8f4fb7d770c36e329f6ebf78669bbfd84df3e985618a93f50ad1c38db360b9f6ea9c3b8ea39249fb9ed61c6059bb22222b90c2a4ed8711a9e17db16eb2f6bd15da963c537f5c43c8f8eeb5fd31696fb52999b47fc66fdf2f8684e963121fa751c9a4fd3269efe63289e932a9bd617cab5d6eb6e6b9dda664d29e31bee5da32e831a9bd866bb8866b98e7c5a4f61e6ead8b2b07456c7183eadac105c645e5b2c18583eb06961dae2b2e1fab8ac5076b0fd717561fac3a587ab85a706521dfb856706971b1e0c2c2fac3b5854507ab0ecb0f540d281a5033a0bca0824001812a83a242d980c2017503ea0bea0c8a08d4102832a817505328165042a0cca07e40f980ea0135062506c5036a075418940ea81c50605052282ca82c28145051641c241db20fb20ea907690749078907ea0a3907b907c907e9072907f907b905c905a9054904d9862c8234821c82cc825c82c4823c431e41f640064102418a21c76828903c6842fe8027027207f20b5983690e931aa41753179e19130d9e101e10a6379c1ea62f4c69906080714c3e8862886f4437a216a628530e22179e1a261d4034241c2637b83d383c4c319864e0e8307901e5806f806e8863805198bce0fac052380accc26486298ec70687856785c90d788257c6f30114038e017b0075e0f20073007920c9783f7865806abc38c82e5e1b8f8d778528834886490c08c7b4c6540548063886298c4bcab4034a05540a60196217ae294e0b4f0c2f0c0f0c970a1c17220ca2176f088f8c870604e321e1254122e11521c6e19d5124c6409621d1707c78519c1fde15b18b9e58067804c70637c7c503b786f885290bb7854b06ee0df7057806e785690cd7854905ce0d8804f70777059705578763e37de18ae2c200992061c038b8c6b872c061c035700c5806110c318e6b06d316ae8a150e092f081318cf079a61a20254c21404c986a98de805de62fa99cc902f786b805ac02c261e5c2790549c14de1c4e0d39830908118e498be9043009d3192e144c6f885b7051786860185c5c4433c028310cf20d52064c06ff807dc054f805dc02ee82a7b00b580b3e01e740c6e01243b68051c029602cd804ecd34ed0ae683e4da6a0c004768c58a0020db0e91a0608510012093802010ba09931c2000520804c1141868821c194e007909711c3c16abd0d168162b7e4c9131d4842c5134b982449623517459325499254b18d7db204044f3449328a59d89d254f9638a122c9ce920ff88858d8253b4fec3c4912458e1421e8c013299e2c69b224891ff10a4c763ed064490e1bbb648907924cb1936487c907a45822829868857d9223c5133b4ba248b2648907888855d8293cd0448a242370b224043a49a4a0a2779a2c01c1089e60e2910abb048a26533449f2441229a8e82550349902023b4d42300514503490b846145340b1248a20e214f6c90896e024799264c9ce134e963459f281244f46b0e4489329a478f264c9046694c28a20c91449a4a0a29d2cc1713285059a48f10413283ce0012433466197ec3c118514394f4049b224044b42108126a258f2810f3cd18400a21afb04078a254ca080c23ed969b2c4c912264da4787c4213293c308a4e5828a478d2440a273b552c4932821c2aa8782289932551e44811021b1c28a6c8b179893ffbe4476cc23e91e42532614190e4899d27a2786267c912a2b804124891f384143b52349162474c6371a09822a7034996ec84008a25552c59894a58275334e0dab624a7034fec3409810792ec40b1c489145134912249121f3ee20aa62951e0628bde1e6b7f7c7cbaf5381f91e3c0716854b4e9adc0951598ad6430cb569677e3c2c7eb769f73cebd4751ee2d8649f9ae8bafeb3dea516d6237bd1727e95e6cfce2732e3a574927a3731bafe8a2dc8d6f0de09cdb7def718471638ccfc1189f8bb072f12dc3085b8b710be0228c0f937141cb4fc6ddeb72ceed7b8f1d01648a587e6e3742f738eef21bc0dbddc8c33917e3bef7282af2d8ddb7ceedb638f79c5cf7a27cbb6eaddde5c7bb3b39d7844c6ea5dce85e74cead73948b1445518fa2a8dde81c3bb791da6db9ae0bc6785dbbd15d4fb6d62294419e48241245e822bc9c94b1b96d020840cc9246448c441bddbee8f6edbaf69e8b6e638cebe03ae75e8b2e42aa45182386ed6edcf81ee4756e59de7b2fc6e8dc42ec391eceb15be76204c59d5e73cf4dd1bde93de6f7765f7bcfc9f6f8b15c1777dfe36ddbe266ce39c7bcbbece4c618e3eec618dd2ee5761fbb5d7e8b619894eebdd71af6f839e72045b9f6dee59c7bce39775d975bf7dee3f7dc63e79a8f186504c01010b6441863848930bef836beb71be31342b294987cefb99de48f1f58931c61f44161842d13c6173fb5c7cc8f992f7e8d995f8c172842e7e22ebb37b908375e39b8c2702bcb237032050896802016c08910d61660c993274b9c50018229a048e264099429a058e224c9134fa28062491202e0cc1ca0005440b1e4098d8b07a470b2044a120410e000072840144b924cb143c5089c40214403c12e019638f140122753ec3c01c51439394fbc2c0181143b4d42e084b27860270440963049b2d3440a27542481e28914207809228a254e9644e1648a24330728c012299cec04c0c9133b4f88e8f1c407105b8029a670f203078a273a9093338593008c60094e929d2a96340162c7ce920ff8083dd9792245154b5ea625409329a4d881e289274d9e70b29364470a1080600a12902d401552786089132a288b932796e44031ea114593255450f14492251f78c2c9143b4f5e8c980152c51422d80162e60005080d29000192146009ce13391d60f2e40926af729640599293a48a2740100494264bdccb1220499329a478e2640994241f982227c9079a48f1810f4c919364a78a253ea03459e246720b5e1e9e9e9e9ec84ab68787797878781a0f0f0f0f0f0f4fe429f5b8520f8fa394f4b490929eb63d3d3c8e95f0b49ea684a7f1f0b092e5e9e971a51ea9a4a7f5b41e9e1ea7647978b8472ae1693c4d494fe3e1e9e1e1e1694a9687a7a7e7f54c4ab6272ae159253dad072ad965253dadc729d99e1e56b23d3d3d3d4f494feb694ab6a72931c2f9b4dd6d2078f2c40654ca6de2a5dc86a23c12c2c34fd159f023ebf164b2945501e9c83a846f8d8e2c487ba8346344dc1b849227b27cc37ab738741e276cb20a60e769e115ec2b2b3e8ec52fe604f6957935f9bd13252b6c8668826db2bf6ec28a1ebf6e45f61e5fac891ebf6809f40c7e3127b2f730821bed6e0435da7d2f8835e1e3a25f133b685ae1e3a0430c033f8e3dce267c5c444ba0b750b7e0759897403f8ed1928f5fa0b9362ed38aeb3f0e33adf8711f6f995694aefb38356b4fd77d22db1f239cd1169c4daa86a7e64dd5105e6fb965350c03a0b73c34ad003d74d12c812e7a364ba0675f0c033e5a662974985fb3a7ad57b7a60b2e868192e8d84ba1bb7cb1267cfc072d65c744d38a1fb782de8af9128f15417ce52d2f65173d344b7fe9077d897ede655a512a5dc75eda71112db9bcc4e32c5f1b986945a9843d2bbd1c88b30dc49a005d74883991dd072df5d884de63969a4d88c2d904085e44adc82eba355d0eabc0f51fd40aeca04f58a44df8b815d7435f9925ec20ca63458fc33871512c7efc7a88f25436a26393a7b2c9768f80b4cab4185240bf284f65733d5e3443cf26f61a87cd6653818c96ae63540bd0cb881427079e7abce8356cd12ad3d42b5a999a4d5cc3e2c745b4d46c86a05694462f0179e9e5401ce6724ebf26163f0ef31eb3e4f21eb7e20aa8f462614a5c01958e677cd1ab856e1ec8f6070a687475e9f7f77e87c2184fe8c20861d8a18b204711ce38c2174690e308721c818c228451042967d471c61b67ac7106196d5d179ca3891ea13abe75d5d31cbd724c73745c393a2a20cacf3b43193e1d3f756c0b74c139829fe608d29b8ea75af66237457ce1093e1776a380283f1db1300574185a6a36252128167a89521e8cb6bc34a994db84284f10bf7eca13841652186093458f4de8a1b7bc86db7441b70331a5dc467420f365f2606ff988f2646fc932ca835129b7c97efdc72cf5d8f8d06140e3d46d45d4fde9c1946ecdc6654ab94d49965e92721b1ed0af8bde639678501e10c522d4f2d20ecab3f2162ae536d859a894db8828cf0a95721bd02f8a4588ae8ba9fbd30329dde8ce5b2d74a320c8b0d0158a6146e76c1bfd1e2a4d6112b88e3d73341895821d44b5c028160cb001ada3f9bbc424805df4ccd188a814d033aa058896118ffd7dc294e8e908290e057a6c426c03fae3c964a9f9f86455eccf081e742d6e037a7ca5a2231d41da44d65e95b4a3c1a815d8afbfcb9680461f310cdc07fb758735711d444ba05f20ecfad1233983a36d08b2776b9f64fb33461d1dcf9289cb09258f076dbc76c2d1e11a9e348467eaaed1f0109e0998864234bc1324daf04aaac333713451caa828124c036de06578667fc430a3e18de019d8f04178069e6435e7ee68742bc4ad8bdf9d4e7418d0bc42dc77626f6b189ed16955ba1d4e27fb9d7824894e10ddbe538701dd56886b7437c83aac08c492686fd803669d662d41c797acb08247f238775e8a56771c496b2c12cf374cab15edbe1d696deddc6b9867fb79668e1ec0770d3be2deee8eb8f0cc74f8176b9964eff1ade3dcf669ee0f0fc6e83861673cb391d6e6878e7fd820fb566583ece3037a9ad0ecb341f6351c6ff80a7690251eac9582ffd9d38d30f17820e5f5f4c34b59b3aeef29beee1b3d5d09cfd4213d7dfa10979ea6d74c461c4d9432249d1e05d3389be94eb8d1d335c033fbc23373faa2d1a0276e179ed99f1d78c16ddd663ce33ebd8567dea769e97027b0f603d9bbdb9f1d48e92a1b7e1b0fa9b2ebef68a294f1dc1f756fa9a3dd2f9e61626d0732be737179914cf36c5cb7737779d923b6dd81ec11edce49c05a2a06d9be9dffd212d0f8ebb2ded7ac8fb430d2e8d6da17a6cd8431450751deab4bc39f67aa4cc3c3cbc837fc90ac61100d0fc3346c03ef9283311afe256be1197708e1f78704560ebae8217a7fc078433b4a89ea1495690390af610c64d5a96ae264151dddb08f4fbbf3f8f83465c411788ad61d0de5750ef591916daa3aec9dae618bdf7a0befeac23adeebae2faad3aa606f6d591606bab29515111c2cfa4a96658c31b172ebaddc822ba115d04519081402dd724b6891e31f64c5d4f7d61bdd5ac718e3a9c94dcdc724790d64d3e3dd655a11f24198244f6292bcbc45723446708cbcac46b47c955d497d0192114c923583f2d5882eb88b9aadb97504630b1d3bde6aec9a03d59e1edd389e233e94a9759a4f5f6f5dcd117f54b51e55f4013d7d51314ec0001becfb03461bbd3f6068a1298b569ea65ec3748fa02e2f1d4de8f299a36936f22e8e4674f9b7045497b744b3d954b38ae00a5d81f103c69406c3c75dd5acd547d645c0468bc00a4dcd27adc5c82e9a23ebcd866d427364d108c8d0b56836a1df721813a25bf0a2e6eb86116142742b5ed474bd98044412581b906862b155eac09e4d2c9a4d888e2aaa13aad2624831c2063b16a13fd868d16cb047913e3e3e208ac554c506bbf52e6a722ff644c3e612da1bb644b684ebe912f640866e7843d335d0e8207a7fa878d154a4f4ecfdc1c11ada0abdaea486876909d8e64e71dcf9f58eb70f5d5f1abec625dc99e2604bb06b9c78f8d2f00dff78667fa8f8a15d5a02b8dbe46ed8fbbad7ace125cff0e1af9680bde13bea5abbb704f0126e0999de15d25c195e4469f8857632b043cfe65b219dd6b0d1da1a76e49d699d81178d1569770dbfd0ed6fe2f0eb939e6e87d7d92a55aa4c690727941d5de4cd875fe836a1ece865d2a38eeef09137f3dfddc4613aba6987e73f3a3ad220ddb1da8b2e7a87dbd1ca87d2dabdc6cd767336ef58bdef818dad108330dabd8a3f0dbf3cd36675eea2ddab0a897bf5862931d110645d9f8f4fef5455c5328c21a95e5be3b457b2e228edee309ddaced69f5b06e9b42a1ddfb053c7ce6dc0464110500c2584eca8f37d1cc3463150a3ddab8f8ec5e08c7677d875fdc24097658d8000f19b10102042cd8c11866118364720898d4e7d34a25880b01b4046a3d16834816411c8791c08193c03e4f0564b102357c728880b63b91ecf54383f5e5da6eb8fff78f51a7ed811f7ead65a6ee53dcd403e01c1dee3d82d6c8ec8e099d1e1278cc8755d2faf6120d774727301f12bf40b087a852ecc1a8dfe32fa85843bfe6a281afd8db0bb1cbbf5bac9e663d6d63fbe189298c67e4c1f3e5c7a4ce80593e0794c67072689662df5caaca56cd61268d65237e805cf345747c337670776c36a58454737ee38f1edeeefd3173312dfc9e019ecf016866167ecafe136416fa3de9f26ac21066ec4400e18d8010672c0c00debcaa4dce6d75fbd3a2dfdba1a193cb30d8f61af618a3ae8d441a0c35b2010086455d515baae67d574725355d6e5abea55655d6d563a0b68d0b72b5068eab406818e218969101d1dc1de8ed1460693e0ff59bd86abf8e927a53eff9cf26dca567d96112f5b831891fd61c11add6e62bad1980693e0195b82ea8a561174756b79013eb7dae4e9f8366b15ddfed9aec56dace099ff7d1c1c6ee8c2c7e634caf201e4b5b8cd2f697d9f6b131f5fe117f2a2b551afa6bb356b154d7db125484ddd54d5748f69f00c7558e1170d4f4d6e8349b05e0d9b190d4790c1731bcd0cd8ce68d8d2f0d9bf0ddfc838fcc50bf8e1c39a98ff1dd6c45fa23f9d871427ba9e11939a602d08cea8a8f4fb8fef654bc074248c76ff312bf7e8a02f86c4fdc705846c7499ae53a6657ab111019a09d08bfd0042162fd30d43f2e3951b6bdd686dcdb7162b8280de1522df26466b0f3fce861589b3a7db1c1d34eb8ff312073d88208208c20511c42d770511c46b38cb800002885bedca2610409c3122408040d7755d5d74d15ede2e2051fce8fd6982196dfdb87cc82711a9a43ddd662dd576d0e3acedd841ee0d57845bf40b85ae8730229dcd6d6b7435acfdc56a1804e0a311f890ec43f5f21e7f798f97979797eae54080bcbcbc86fff2f2225fe8e84810bf1ec47559a1500be3c7acad479f80f87520aeef1e5165fae57574be8515711fb57eb90cb93784aeebbaae09046248627a1cc87b1c48652091e50ac1292120402e204080cc5005e40e4312d340e8e888cb437779e8d6cb74f2fae5a1d772c533ae5028147a0d83bed812ad23157a888e8ef838e83eaebf4cd98d0acf5c3b803ce9e938ebf4c63b365b83ae6b1bc8d4697d1dbb5ac7bfcced76ebfa68fe788dfb31b77dccc90d26bdbbcc680693de7b4cb8c5f4462d55b396bac22947487dd1d1111e079d07957530e91d4477cc1164aeebf446bfcb3aa6375ae60832eec90d9e69f10c963982ac7534836740154ee977cbcdc1332251551d2b42eaea165f6e0e9ea9fe98798dea8c11a9ce6af04c7b9665d917abe1cc72eeaa2d8cceee3022d9dbdc162189e98cb21a4c7a6f542c8a6ad41f25c67c9b35dcbe20b0de459416d2fbf30237bae843d3f4fe74a1060cd4e899de1f186cf102355e30c60bbc6820bd3f2ea8a35d30870bd8686be59a666d96752d26692db57c9b35ebf639a5dc665e6dd2d23c4f65b37c00296f59d79c6bd3e6bb6cd67551d4e1e347eddb3d3d3a221f9f84a4d5bda1df5b6db496babda79b9b3bdced124312631d74eb200ab760d2b3fe6e75dde1b6245684d4d6172362b5f886bd45ba4c6a74871b748168a8ca8ec7a6931becf2372bc7be4e6ee445bfc92e8fd11abf429749d8315a5f8b68d5698d5dd2ba4c8acf406d62733b347516d0a06deb35dc96852189698b8e8e54a75e51370793dea9575ff00cf547a5dfabec43bf53b35161d2bbf5c3894cc709279cc81a9dbe3f4690e1c86f57f7065ee3c708b2f67749db4390f54ed6918a21e536253e83ca15c0f0b1e179733881064fb0b16204a51e1b989f40c6460b199b32dae1163ce3dec033adbfe8edeae8685ea3df990d21dc1c42f01abc00216a0ba3dfdb1894d5a02d0c5e00fd6b5ef47695fdfe7866142f8694dbcc6fbf372a3c338ab489ccc7c7c7c7669ebb2d60bab422dae1fcb141da21b644d6ee0f6b827ede614d7c9eb108ac8d10347331ed0d8bc0da502a5d4cfb6211589b49d7c5b457548070116b84f6233818d4fb33258c5e9fa9f7678a94de9f57c77f9020e3de1f16d0d1fbcce2e93ceb93729be99a265a9ace334d96bbdcdc5e426aabb9d7d6aeee70bb22a4761743be86792e460436814a93da95fa9da79c526e232f29e536f191d6b700494bf23cd3970fc0af0fcd732fe7da2c86c4359feab8692e1fa0ee0f0ba8f40e3753e1996d2431ed68a3c25467ea16074b89ec45d1c5a4c7a416e768761daded4d29cfe6ddcd26329bf75df7ba5f68772732eda84ea8c5787b7ebb189f3058ad4d1725b2fd6bec0241590961adaf3d627777cf670cc96bfb25e264e73287613937378a6e35789c7d6885f0eb52d00ae1778bdb99afe86512bfd18c499012d18fb630a9b55e187ead75613ddaf0b171e1d37cf956a49dcf6f9812ccad5d8c2971842f6f2d936c7f564047832edf2eab5d0e2372511cebd8ab59f9212a27e816688e6e9e8f4f87fedaf5860e9adca1d73068b6b6f83857bd2868853838bd610b45207a3129bef271e4abc35b0fabd4419812af1508f41ab61e9644bbbc05e5b75dcad7b0c38e549738a015e21e5a21cea7df0a714964cfe78a0ebdbeaeaf410f4d273a4b878f4fefac74f69dabb35fcec94eeccbc9f59dd8d9173b92a443e7063d9b3a4174e8d7d46140bb42a110dd0d029ad6bb78d6ead7adc592e0220b4711daa137eca681517baee810659edbd8acd76cb7e61ea77d5bb66f144d346352a4b421a8c53a584a64aff767056c502283bd3f2b9063578810d3ad9d56e8867115ba85495ca5b11532f1944c9adea6ef6c58342cb277f7c733dbd3b13d82087f9a7eed11d317665283b50fdddad9fbf3a346b7fc44a15b1e954c0acdd6496cebb42ad80a095d2b0474ecd7adead4e5a71fcd04593cacb2430ffdda23f8a1637bc476e83c61426bb0e00fa9db26b504d31ca5262b47b31554545d9da295a7e5172b42bdcac6c9a9baa24d2a49eb7b95cc7f8ff9353c9da7f5da631264be45e4c8fe34d35ad37c8bba168adef7a7f7678b337abf4e68a68f49709af5759c35e7ea77482f2661cb241d58f030e31979f32cbee07c0f02193ff25d98b46bbd2b9e7a8d6bb3c5055a6f42690d2794d611567f33529c25e2a375a75fbd1ade92a3adbbe6b646378b85296d1df69ba32dcaad0808cf24786b1baf036d2b02ba3773ae1671a829d355841302597b0b93aa399d9af20dd391fdb242f6922e7d543289671ebddc7c61b06ee083b0869f7c0dbfd95a3e7e3a6344e474c92c08a5bd437e14e7dd5d2ef1eee47cf36f7a3bfc93525ed2f71aa773353f8efcf8f11bff85b6c5cde956c392e04fb7f86ad8f436fdb851d2ef7437882c022f239d669d8ef32e6fbd3b2a18899296afe1dafa49ba1be4315604fe51f91ad73e457a31692bbf397954c606dbe06037ccdd6ad86111b6b16a4fb7876ee263bb0eaf57d7a957d56c9994152794d6505a5fd7e3dcb678dbf20335fa1512e74571e2a98c67ac4f56a8b5b518918da28c49d565fa7ac388345a7bbacdda1a279efa1ed17a9a5f21fb8c67647a6fcd8c492b7b3a124fd16785aac3bf5f6fd811f98aba30697fd19665d2c6378ad69ea67ecd36df5ee3ae59ab5315adafab5b6fd3716d7dbd3ea0a709bd1be4a2b59a3a5735b79ba3a68e6bf74d41e69eb57b0db3ceebc98ab3c2b7cb396a54a7f54405d056bbe414d2d37c61d21e7eb1f6a9413f58ed0b5365dadd6a0d2382e4455adff19269dd71bd4ca2dec233d5dd5d2acba2d53a756b9ad6dd339e99ee6e59b3b575bd618f49d4adb97d4dabf136076adbd35c26915c82bcbc64dd688d77adc5b94d4dc980b903bbd1162671a4b58a5965afdc2822c130c97dfa3269a2355ce376be595bebb42a55aa54e9dad3fb38775cef975e4ce22f0669c624fed5729ed9bbc3f04cbb3b8967b6dd0bcfbcdd83ec11ae7baa06b7b06eb27d4b66a9b14bb2fd61c194aebadd72cd7ad7ee11cda7b7c81aa6f37c7c160b4474386e1c131a120e4a8eeada16f0ee0dc7bd1dd7f08f04c3a41726b52c64ed3548c3d7f7e034b242f6f21dbe41f80edf71e0a30e60d2d630691f9b83a3eedd1ebe3ab7777b1cb754b6753463d25e4ce2aeeeceb9d6f6aff57ef7b9c0dac68ebe1fef15fec13947c381ad3566fa073290810c6428438f5fb3c78eeb228bc79c7ecd9689669f44bf567e9dc7ac2ecd83078f95ecbaa65fe7d1728baf6bb67cb1eb35dc5299f475c68af0f8947dba084a10bd7251461fd0d32b74941d67e52ccfa665926856262dba446f115d1f89cee3138f8b8e442b67f9cab5a30c2b2b3ccac083c78e6b07ad2b578f1ea3977ef0782b038f1e3d78f05869e1c1e3a2ff68c9321e5fe1f1e359c6e32bffb1c2a3c75b6eb5f4c0187b404f0fee315bef982de731b3f798d5a57bdc613b8ec3e3d977f078761e3dbee32db4470f8c1ad91ef5380ecb79bc07bd90908d6e58cee32c54741e54f46592e83be832a985569736c2e7c78f16beb88d8eb71ae8d6144da2d787bd8a2cd1bc5ab7fc9a2d173969fde31a4286337de5d3459fe68ffbb8888a563e5597beae6b7a365df4ebc78f1f5ff9342b939ece5811243c9e5df41d5f616161a1a31df48607cde8ae9095af5011ad4c5af4c9e58b216179f61aae2bff41af21642b34c7a5a78f567e5d44273a5aa1372ccfcef24c34edb4ee71ab5dd99dce9305f44e6361f9257a0d5f3d6e39992efa6247dc7bd09a51160aa5b5a8e5f5dd60df15525b2a73d1f135cec7aca2ef5d6615ddb01823c8e265e040693c2f2f8d6819d6cba8d6a1acbc8d2183dfde153aba79affcbd72d1775a8f6e562ebf72e9a4f54e6b96165a7bbae5a2959616f55859b948d483d6ac45e7f11d7399c483d6ac579ecd952fd38cce8307adb2579e7d34976344af712bb33269f9caad159c160aa535cbb76b8e4bb35c748b2f1696ef386337f22cb758e6ca2d8b312496bb5a66eb95595d5af4c592e05e790db34c6e165a5bb37ce5d9ac2e373cde42778564e7d1f21ac732b9b359b3577f5811ee95b3eca037bb42446fa12e2b44f41a66992b5f915fa138d65bcd688e4b5b5f7ac47a6b35ee9a6a65324737fbd313c581d2ba4928d3db77260aa535cebbf56237bfa2db7d462f31a0ca8896d11e290eb7b183ad1458d61b35484cd9f59eac71c05f483046c8183144a60d8c484b262798ec7a2f67d234c13069fa90ec8294c4a42908932618264df49d6a91d4a5be66f7606c43561df06098045d5a5c5e8ce8e97232c2a4a60409860d591dd2adb556c50e1349cd2af3d22e4f3ef2f4b61c4ac5aee1000dec5001d34aa29009c224e886acba34ec96bdb5f3cb46efe56e4b75e9168549f04d0a93e06b7869f36112a42f6352cb41d66ead9c227c8e9b04d52a7a64e459d72a7c7c7ca4f4d2ed428f6c2b34b016060e34d7d5e09055179786b48e1240648be0571fdd5ebac1740b52d10ab3f69e1e52afcb08134d383135c0246e7dcdba53683e9cdb4a30895f5d9af92e2e2e2e57d6625996bc6559cf7ad57459217c6a1e66926610263db8fd22fc6b4847ef3dfddad11726f1e5c8daab8bd5200bacf7da1baa4b4ec353d1b0ee34ac55b806834c2609f92858116a62d21abbd68620e166906e33e3ec7cc95575c933d3ab5f4c63bdaa60148ee12301e8820fbad032c7a5293a3af25afeb5a423f89efe1bb21f1f1f9944153e538291c130a99d89a3a9714b874bdd39e0ceb17134ef1c43907082499c86acbd0e698e5a180283041248205191687e153224fa5dca2cab2eef95245342630e681803668814a4747fb46e1b3deb90768263f85266998bcb4f22759369996e6f45c8e08f573d6392555514f5c58e94c008701003958639433afe75936da31fa59afbc2b21c7d7109d2ef56c3889098d42c1a630d5fd8a5d31e9322ad39303ddd7a0d5b158949ef90d61dd8f271a2d4e18469f886305ef214d109c2a47719263d7eaf2d04c1ac90f78c49ef62d2db210d6b500796d3c01ec1d204cf88424af08c75de7107903276b4ca60b49242b4be2ba97907ad2e5d49d30e1f9ee1717e8bd2f21de737293c43adcbac38a412479359715471844075349f7a153c033abfc433d8ad2f6773f0d0809215e2809a7df2629291acb2b866bd7518d1c3a3c52d2c978ed588662355d2606723988de657239aef44a504134df08c2561663517b3687d338924e3b27158ec66d0461bb5f958a1f9b4f4693e6db4e1a6342a71d2d146c8266e73f41cdda8b829bd42f4c4d6aee03324f41a84f48e221a234caa58668d8ba9be3287b898ea4c1c8d882ec74cafce84a3c9a80638a6fa45ab39ba7a5d38bafa0ecf30c9e119ecd5a9e019d0ab57c133d4ab8366102655c7a60c93aa5fd38875315535af21643c04632705eb6e5a8fb3c29c267b75a95947501cb2ba70347f72eefc2898847278a63a3f348d6012ff9a4198c4777388a361101f9b128c8c7a9551f2a255c6aaa85965fdaa0103629705ea6e5257f56a72ee7cc90d0fedc7a9d1528d8e6f6b64d3e5ebf6a1a59bd7103235a6d6d6a0a3c7ddecf138ebb23183bd0691e9588de8588734121d635d353abeee1a1d5f492fafe7f1fa225d0cbfb674fccaeb6b9696d7ab775cc6d1bcd01a8ee1fba0cba416872878a6eeb8fe711f671f3ece57c2332ee733e11959fd381ad234a4867acdfa9a128c4cde099eb9ced700cf5895a3717131fce9a3b9b342dc7f4c2a7ccc9cc984491ae018d783d6ada3dd9707dd319d60927bcb6c826532b1329530c224f76c3a8049eea159c32477d0c4e69bd2c5d150954b73cd5a4eb3caaefb46e72c1b3f3812c1e25b0bab38ad4b30b2e9d4ebd5f16e4a303279ead2d15832cb5c5ca64aea2a5d4c95a1173f42fa98d4f68e29662d584b6bd67bb93f26f13447d61ecf3891496e64fc7a5158775e37286c6b5de4b8f44d00bae0032fe4b874a4a3238f0a93c87b7b73d79bed72501445518fa7601873f1c3537e24d3b07c9a265a77da191d23732185b9e0293f2c85b9e029146fc152786679ca0f7341512c85ca7e2a1a61188f8afb82bd681d2977c1a4e92c85b7a03c8549d37ff094185faf38dd55d931d20afa54b16fbc689946f47816eb71c7b53c3f0b6575c7b5f518a275e7b53c9ce635840c44775e4fd4e11f46775c4fd4996efbeb89e250d3db797dfdedb86edfd71dd7cd0ce6e2fa7473519c76d813b4286c6f3aa495a774642e5a11315627bd1c978e9f66298a7edbfe6685b971e9ca243671e94771a622d3a92bda1d2352dd611712327831441290721bd15bee5a2e8e992e9a2b61e0251d8de84950d71463e5708ab1729699710cbc352f26419fae1ebf524dc9a478186a658a5ecdf66956e1e3e393513388684a17e3a46cb2ca2adbf114e602c4221ed0d1effdd9421b1c1f998654bd668dd64647bfb616001953e9d734435c8c7331f135bb3efd4d7fd3f4f816c7346144a61af730222dbe9d21363aacc733f0bbfddee8e099e98fca1491588dc8bbb17aa2431ccdca0a55e262dcddf72f0e69f775342b745d0cf68abaa5a3d169be8fd1241f2ec4d144f4fe70018ddec24ffbe8fdd142186db90b46793d51f6717de03a5a4fb4d98149efdbe26809d89e68a383490f7eaaaf772aa4d3eb9657d935eb89d6d011a2d7eba04f97b4b6b05c7ee5eae92dd5a7af3c1fbba8569fb0afa361a1d8abcc28466b4e4bcb63f31a4216a2392d7d59c7ae2164d7ab75c692c0897f7d511c8b5e392d1dbfaf392d0dbff4c795842c1e7b85c7686d72c07e6f676809b84ebd613790e2c4c3bea8ca61486047bac34d474f6f6768454c2e4c723aad4a5367ac88eb76a659b9fcf2b43225c74c9fa6bff04ca4eeaea7d7b01247b372b7329763dcbce67be1998956978e771705a7fc62455eb76ac2cc1726b975b4ca2c73e9f6ead2eedd265a4b3dfd4d0aff261ded0cad88f7f7094e91eab886908e1087e4602d1019e5b6d7d14caf59bcd8614498d69e52194d4c8f135d17e3de2c1e61807a7c13f293fca34d50ef14bd8b71af8978494b998b718f745d0cbc04d6c6c5c5b857fde2ebdb55366b33bd12e1d738d2dac059652399ef86365f00eda28bcb0ce2cb38763fd6c25499261d0de520849069ed2995011d74566902b0ed111c01240ec29baa610dcb7531ef70be334684a77963b550c3f384b762db235c2f91277cea16e5e262deab99b998376144e06535baa93d38687eec413375f28eefe9b816755951069fa73a75a40c6a1b17d9fe68a18b764526509da25650af4e81b5a168197c3831099c80013615bd8b7927b998770b43e2ceb432217f0206d850a75edfef9593b30c778bb11b515f3ce12376236a778bba78663d4dae4e837357c8e54095020da905ba9ac56d19c4af610d74cd40bbc75208d6b61564eddb81acbd8d2f9adfd254907119bd3f5718423395de1f2bb8a049bd3f5578c2cd0e167474ed4923760d410ba4d01b6a2ac8dc137a7fa6e0d34d8dde9f28a4d1417a7fd490a36149545788a37594d07183d6e10175dd390319425d77c6c0014b0ebaeee82007d619baeea8e00cb0eb133014a117890092d046d72768b4d12b7d7cb0add26f16c3d2693ddd829fe693d653a338fcb63f6aa8d1951b9ea793b642e07764eb6c95c65e5518d35d21b06a736fcd1a0fa31a981a6e34f6ea16ac56484f7375876145e4f56a6267ec08f5d6e8ae90cab4ad90f6ead7ac6c4191ade3fee0d37b396beb9ea9b11c2c7959f9fb9a1d375a5fe3744add2e5b7b6bd779b9a24a43e1e93189a7d6745a1f3afe4d879275cef3b9a22b5f67eac315551a8a6c3e352bb4de28cfab3253be8e8c62822bda0994d8d4a14cfda4a7a977ae3a131d0dbfc33aae231dddb49f7667aa41e17eb79837089f72df362b35bda6de989969a5be6fd412a5961487bf9f68913dffe6f5a338ee8ee2ec79dfde221baca645c6772d8bec6d9d00f6cca4f652eb697cc413a2f45b62877ea76e2d08dea313dce805811afd8e39418e7eb7b0ef5b5c5e0e43e299f8f74751d7dd755d5e974ea92febdb56c8c2191d990e3f4d933555a0e9f216557d9baa681179ead475395bc7569dc2693d5515852179af280e7c7d7fd2d3f11243f24e5dc7897f9f688dbf28f59e86137b357bdaeae92b34e3acd61d357130eaa119254a9428a16bb14b80c61ddf6cb4d82540751d69b3a9716b63cdedda735d48c8b03b87dd588fb462b7e8c8fa748c8e2c2a1f67f5494e387209272749516bd6eb23eadbd6e31c51b408bc758b56164ef51a9158745708d56955a83bab63eb6db88e666daee8edbd8c114390e0996d9ed653b145876d7c2bd47cb2edc6ed4c581c47bbf35c22abdbc606b2d7a2aea1762d8e1af9d3ef6e2e9155690607b2d755faf4bbf4e92acde877f9c300f7de6c1384f1ee138430ce97c6460dbbc0bf47fac22448f73d66369adc235c7d69f7e6e45dbc8b771f22a3bb73f1ce5991d7d818dfe247efadb9476f0af170b6481f935652c1dac3bf3d227e0ff788d6463009fe092dc324f8c55cbd7caeae57439aeaf048ec115f21f21993e4e187c4c8c3308de4185947467c41d3c231f2f0a33da235b4a8f9c22479d7841a8e522ea1e547d35d738fa6e9fbf38346efcf8f12ba65b6a6e80b93e434e10a5921ad1d0fac8da2ec45aa6eec76f6704362ad93a5b07afc4e6b1629228f23afde9f9f331aae90c897dfc9afa6e44fd397d6e9d62d5e5a5befad59f5a95de2fad2665954e7494f63af22e0831faaf4936d638c2e52d018b568ddad557475e4a2b58aaebe38d4772965b9c65891aa7785d4b8774dd379ea8ae24ce73a4d97737b923b7bacf6806095b2760af4d8f0ad70cfc2d11330c086cf94875b104b80f788e80a2b75534131e4a2a12115434e79382b50c4179c50c566ffa01856f02c16a6d848eab35898524b0da97644b4d5160b536cf644f4c2b82c588b352aede812070278812a78551ce3a57c5549525772b657f37a9c53bc554d48e3b424c48a60afeea6fc35add73869ddda61d0ac6d879b97c853d41baddea6a4f52d935a91e8b6dbc44e5177b45a969b9343426a472bbc95bd86dd456ba9afca65cb0bc8688927f4d2f201dcb3bb3f0c09a9dbbe4debd7e467736d42b38ceb9acb240a2b12ab5b53bec65592dae659db1b8624a621bc8090352abd8d7b2e8e527a7adad9e16e77b3969c409177df798f670c09a92b8f9bb50a9d2090c025ae095568b704de82d7041d24a2211147a1483a4d78871d21a21d0d2d7cbf79379d20ba395a7b1a3ece5aea78c68e10d1d5c5ca7787fb1d76d811d8954fb7bb38ac7d128ed6d698918d523d4a19ee1635f787d9e26be3c094781812b7366cc1e5760fe861414b24324b07046a5ade614738d4d3ad064195678a919a9698919a249ab40ecfcd15c4037a70d0122bd243a57594b405812a0f1539dbade98a93bc2a49cdb5b1a8ab9a6b43cdb5b1aacad369f8a0e3336adee7e6dd5187e95411c50457f8b4135a1d63489e8e6b1c37d110b8f48e4b47f7e670da754a5ddde8481d41f323c33bc8dc647bf8da62ed16efbcd218a31f4b3e4fe789f25d1c4d94321ad3381bfe4f946617262d14457a5dbd3f697c21c327702e8d2edabd455e17b8007a7f94a0865e9073efbdc724f7f800dcfba30439dc7e81c803329cf7dcbbe5269479f32a4dc33b9169487542edde9bdb6e2e63449ccea1196ef800408d99f617d16d886e6f619a96042a32a02a48b0832329814a7b5f379a975003097234df85666dd5a93ff8f78721e1bbbf69fdb88090c974e55eb5cac1ca5577d72ff7aabab3668b5805e4ad90d40a2b5e0e3bc277b7e4d5b00a480c68d16ce4add0a2d9c4782ba213f2564827e4a7d7dc0cac33dabd14312484d1ee0ebe7a145df438b1908f8793a7b281df3d627aa43c954d7c6d481c1352a843389b0d35cbd061404e565d8be96558f7a96ce0df43df4642176d59b349d5d6af795381feb022ed2bd7ebc4c9b17e1dbbc38acc26d6b1afccdad3a2f650e8d9bcbe98128d86da4173646168c071043ba0c146c3f4fea031061a5ec0cb9a231086456cbaac39b240736461109bb023d8d198a377f848d5ee91622169a9c7a6a25a4c6f36109b6248992e6abda6212d45f8e954a43c103ebec4231f03af71342d14e651be6c77c8f6a7c6014676e963d2102619515515b62ba45e5d572e22d2fe9061b75e594e9d7a36e5ac59cb5badba75ea2c0d2bb242b3875ee344f4edbc065159abef3c4b56e78a88cc08a68c381a1e675ec319cd5c59556555542e269944d195e1195973de93f25d8c962f86014e4233c07bcbc5688952469509d23ba616b7294300dd32338eb9debc56584ec15ab9198b886c51475f3ce34444b6408349db4e943169f446f3478f16e17edf1f39d0d1ee17d3503e2d2e2f4c33844d3b4c1834f7f1f151824dbb0c1ddd6e04cf0cd1ed4378667fd2f045b72371854dbb03de24c2451437896c11c624b2c518d80d37112e28ab2063e9fd91838cde36beb0e2ade864e7a2a295f4aec392d019ddfce8da83039d2d43c35b3ca1bc83f2137c0d4ffc9dd853652793849f18c243ba1b84efb60cfde6a3f1ce41082b3b81f11dae4eabd2adb1226d8ab53cf3acf79ef55eac6215ab5855af9291ab73dc55eb57d14aab73f1138e3b0e9fabe6fc10e15f83e99b1734e62047bf473a3af2a8d05e6bada5a18734fc9055986b3ac7b6870ccaf105a4b09bea90d6eaeee1b8c73f8c081abce87ad13c1bfe39c3164d498a2f49ddc8e3b8cb89f32e59e573326058bbbbbbebdc6bfdd6b975ceed4a192fac85b16a8687ac35be24912cb2eb2508cfc40769d6d2da9fe3f61e04ed0ae113e1dd922efdb68ac86930698a11b6098669b0bfbb389aebcfe28324132ee62dc730ad3b8f925c13039956f83827a4b5b54f93d16845f01bc3b7c94968453035822201e71026bd33e168606a1c8d4b907e97f4bdd9219319d394a8d0e2369c846680f6f7cbb24ab4a7d5381dd7dca6b44336d1b771488e6c08431f7af6fe84e18d7e830e9797efa6c1a4c68f31224b60a267199ddab349fe70755955d324b34b1dd7b57588e2b48b2aa74177b89bbcc392c059b9e813e8a25aeeb8946559f51a57512c59463d14caa88e6b9ce92daf6196ec2d6f09b1bc62a175877ba21476597de532f4ea8d8aab40151517e3ce32b1acbc92215a5b68a83acb1b1596ea9a6895a2cb5ba1299a15b442076bbada1c321cd061d75d91077d876764a5a88cd69e9c2a1a68565f99a06373ba9cd81b86e46a11add5f489f20f93a8bb368567b0bbb72e1a9531c8a0d24213f68651aac65d13f5660693e0bbe8c645ef0f19a2f4db20ed8e4c8fb7dcd5e81064a2338df28f44c2013c63dd9ad79475c86ad6aecab7cf216bb5756d54c755a922a505c3b046d716a55d6d694c619ab571fb03861f8c93d08a685f8ca28c462b02ca66c691d7d3046bd604d3d485a3a2958246e47437512a97aeb48c4a19dae800f4fe94218c5dd0ad09210882402090dbe186d32104c183ac2164ef923909b76e5d07cd9ab5e3521518b6460b612090f5ac3d03bdc681acebb8ce5ec3a159791a07f4ebfcd3184669dd79aeb59a35836e1dab391bc242ad3ee9a9eda7db43a1764ea31b88ca6377bf2627a1156155f3495adb3bd426258cc10eadbdf81deec99aa631c8318634c6404617a0f7078e3a26ba9319d3d7bdc64dfb6d17e527d74b8bf4b49cdaa3d5f5f4b606ab515bffa4d18dd6bd5becc6f5f4bee9efe7e9b8ef378baf9c4667998b94d933ebf1d5899e4d3813ad6dc7a7f7b01d530b36619f5ace21d05b40a0b3cc155a4534a32110365dd344ab45775e57b4f650b4cae6966b85e513e098038e9f7e95d3e8ea1eade8268633c4b0051c663411bd3f70486931d4d106e8fd11831a6dadf5ba365a5fcf9a754f8967dde5198d96806a45b7cb365dbf89a3536a795ee3c635e434b636bab4721aed1e3122920893d685492d2cd3dd9729be7477342d54b6cc1e3acb79457416d1ad49abb5426b4ba345b4e65c9dd11005d1f3d7d1baf330eaa68ae6e45cd44471aa9deb8c8677e724b404481a3f5d0d696d7f9493d08a705446c4b10b161418697dbd0bd360a0c3b730b21aa4f941648c1872dd3a1f896bcac7a475e1981813e38fc5dc84264634ce7b9c46d0800e4faa0e1f846942872f342b87f092694487cf78c63accb8e9e0e530249e89c7ce0fc233f265ba58533425c7c4670fcd202426c583e639263649af9d52e662a683b54f46b1693db29facb33e3a9daaceb3bed9c5a5dfe3f3a7ad661f9a291dd74c47471a4e118aa38f568dc5887f33c608f9f17b74c438c1085f8c7731b638302231c217a38330ceda13237cf0bdc7538370b00ee734cde5b9fd69e9e89a436a2929396b9cced4b2f27484d374175fc3d3f418217c53de74d0f10c7c5e3009bae8e4936ea916676059d6626fefeddb5db8fbe07b6b077e67e0f75a1cb3d101dffeed3e334879bc054f5981f537db19422fac36ddd5dadc8ead4dadb5f73dcfdae0dc26a515d1ce1891a5ae3d4e7ee3c60c679bf2412e5cb0ed0c2e3dc9d78c7aa5babd6d6bb4be5bb49a7e78decdb6824534bf2106cc020c3bd836f82f4c7a4146e20f41a2c6013cc37f69716129cc454bc0767bab61291bf5e6d2d5fabbae9c6eafdecea42deb357c92f5277fc7537b758bb6576f159386a7dcdffb42e676d4cd4273cb1a0fb2fae6ffe7b7d104eb3cf3a650e77d83d4eff07df1ba78535e0c9e17cf8bc73330f49886fad6ab1ff3abab39a6bee6d6fee3861d7a9a6ea696ce4d93a33a53579e76678cc8c470c2c3bff384bccc7f2b99666dda2d8b9d7beec9f7ce33ad8a86e74607848fea4ced1cbda6e4657a1eee99381681a23bdc7b9c27778baba9af175fa4404a0a9a175e50121ea1096434337ae7314fe12e780b96c25cf0941e9e025ff375d5a08dfe421c72df7b538eae392def1dc2a92b086b9cced4aff2c84fb5d9a1eb0ecbefa9bf16472be2418cc84e5f24db55343a98247b7ab3d1c1248aea4c3da728e723125f12cf65ef0a65f08118ae46061186592c3d16a4b28c85a5aaaaec22161616f8ac86b6217561d7c4c130a6588e5dd73562a10fe8e9eb3acb555915954d11ad21ecba300bb3308b8585e52db7ac6358666556c6f2d0ab4b5b9675abc75940976f59b95844df711e2bd7cac4b9debe437489aa4b5b59265ab945a104d12c1886b1d0510bf616ec2c9895652bb4e6dc124dead90cd10afaf5809e1e61c7b96e1dbb6efdb29649555551b43d21fb811894602d8c55b53164f1d39fb5486675b8722f90d1edd61743725d17cb8e10863dbb2e1008849de5baae1dc75a7478da77ccbaf27dd576c56a21cbaaaaca42b22b84ca42d764a1b5c50b5f7475e96733dd0b5df4f41752b3ac1eea22c2e3a053d39a9589b6288aa7afafbcba3465511645cd1ebfaae8f2a0a9c3d37accdaf21dcdd270aab7ef98d710b216ead2d4595ec32017ec156551af595bd42d5a4718bda17e559619fa3557683b9f9afbea883b925d2130cb28a53e8d9edc804e1dfbe849757351745f7ddf9c6051df571485d2832cbeb563d20c4716ef3ebb4da266e540c63a865dd7454712c342c7ce7384047491f56bd651f66d267dddddd151466f4054442f2947217a5351948fd16acd8a1d6bc956ba0acdcaa443b75a5eb3b6342ef2665708955d97129ba25f33741747d376632a45b31b63bde58b2161d22069eda89ebdc655335e34335a9d4befc056e808abb2e1ad4f67c16e71b7cc4ac4368b942d15c3d808e630eb75dfa0194247d7450fdd25fb8a73d5af63bb424474447d541dbb75f96cee0ab919511412796b44b91b5921ee327d49eab2424034c7a5a9cb6397e1218e8eae577777b1768554777484557444611f5d77c76875ea7b49eb486217bdd915e26edd541475b9a37524e98dcc0ad91b59210ca57593c14c60adb54a322b04ab4bc3927067be44e2ce971876e3ce77c1a6ddeb494f5bbfee2c1a8220bae5ee92ba3c4fc6b007f4740b1db1b0586e77b4833ea0a7a9965ba75a6ebdc5927ccd7ad58b56269d73372b939677a067b332e9ec2bb778bc669db918787755cac8baebae8976bf9868f71d6f99d5a55b8ecdcaa431d129cb7acd2c5a993475ac6566c7e6ca33472339c69d07a4d8af63d82f8c5626edbe838ad1e3355c650bad590fba1c13a2358e4544ab4b876675203abab0eaf13a3677f8a2f035ce715713677a3b35735cb4b84d199394eef2cbccaf5b18b23064bcc4e4688e4b1f812b2cd760b56d37222f3304defddd858ea0759cbfbb6d4b47d3f7f0d61c4d1fc9f3484a49779ae899d61c977674f48ee3fef838eb287ed76899dec35947713a4e3cfc44e321a46cacb16bf4fef11dd39eaa3d1ef8c0073de8814fbb5bcd07ed0719ff669dd0ed6fe376c1075df841b7f87e30ba69210306ddfe2894e69386555bb393f69d37bad9bbbb7dcf5935daed2eac9aea2364dbdac8ea3b32b523cdd8168c93a4667ddd1a1c2e3460d615422d84f86a6c64f56a90c3de05ad584d94bc5adea32d577d69fe0bbd98d4e48cecfa9e31895b98c4fdfa7a6dada4dea3dbc233eff5ee0c1378b6dc7b7474f30e0f697dfd68f342d6ded36c01c1da9d79776752757467de73ecaa544900bf2ef72ee33869bd55aaf4ee1ec1afbbbbcb805ea705841042a18edd7541484ac8c818718044c119dce8e535b850830493f66a18b142d68c36b6d0c6e38106e42b857dbdd15395dfd3141ddd54a74e51af2a8961971865a2665eb6c4a8c42427498d3069ff9e03608491ca3089dfbb7b94d49af9b1a16ba630dc3d587b6cf0f53ab9c97dd7b975c3d26aa477881a3b7386f4ee56a16685b4c00721cc340013851c985ff261216b87300d0689214c8a628841f697f715b287e93df7a4b16a5c61d5904f893da2b442da3748154c6a3454b89826630412350e30a2036b615a2e9797f3ccd6203b0c2948d6e2f222dfbec3c850c9a626984603469460a28d9e98e098e97b984ac6883d027b9b645ccc647d9aecd0d3a73fac7391b6284c9a445f268e46caac66222b5e3dad010e33be202376e1059d2cfa9814a940f8c0c8e0eb45471b5f78c15ce38515622b63c22456120593f82029535dd8cc59217c1947d3cee098e9d3f48683acb62d7a7add397aaa57dba24d695d30c9e119ead3a9981a3bf509fb747d9a2e9ab36d3446047b4d5dd35bce346b333d889cdb0b335161b06af6082a56c80a1f14c2b247c372aa8a708d95d95a033ce300238e46c60d59a3463a9a28ac2fae4974f792752947bfa0310734c0e430290a26d99460b003782608939a4bcd10921572691d84675a98e442729b6173db62994ca20023abcd67e9f079438bd2a4b42d4a73b4752a76aa888249ce10279468a009268c2891f12405c290f688a9bdf71ea504939e1126bd75a3dfa73face4577f724a17cd0b9ea968144cfac2fa76f577234f3ecba57a6ec8102f8c28c1441315cb3f09cf24181726f1abf94272345470ccfb6b34c8eaf6a1df7bed2f8a11257826fe9d89262a2a76968e7e54e80450304357ab4657d56b395f4ff1bd779846257ee1b9bfb8e42093cfc5882eecee5e0a59bb64d28b8b156ae2d2383bfa28ba4777eb874f1a1d51c7716fa73e9a3e596fb72a3a3dce3d356993fb96b371bc5973da18adbda1c11a3db98977871f3db989745708df3ceae8ae907582139ce05a8b628d3edaf7fd6b7fe2e848fea3781c78f748e1dd21efbbfba36ea24b97f9fcf63edb4fe65e21cd6963d44773368e76148a93edda1bfe32b5e8dc1ef13ec40a818f89bcbf2033efafdd5be3b8e7ba3ae7dca4019825e4c0f47b6c96bc943c0dd8efce39b71204b342f654644ad879f07d9aa6697ad1593ed8dd5d07e1bb92a7b1031bcaf7e282868b6f31c6e8761b115a88d0daf0f63d7e97e4130c013eae52aaa27db142f64c5c84e04245ce1aab86038c0c09c29086512861d25e26c84f1026ede3c896616d798f562b3a36a42d6734fc7b6f40c81a844d4a8b32650b26ed1eb1cda779d1ba0861c10c59db37dc60258c4882114a189141d68290d0f0de7b87f385278e7e5490d0a8b45896913aa4dd1b13b276f7217206d6e866e7f5d22898b4d54d3bafab373aba697474c36ff11cb1b6263aba919f3ed1fa7abaa44e5ee70c6999634453efe99eee690b5ed7e1459d60d2ce9a6a06518249774d3069e183940926b966c41cdd3cb7c456a9f2d38eca3069fb6a2e0d66b4dbf17db6c3343cbe2f4926bcf2bd11a61932939d4492634644538774936385b0c1a4761eb3a9c1317130a97dc76c6fb898f696d9da7031ed249e0902fabef9b4284cd3f27ddb8267a6b42ebc30e268380ca6542c8b45a6e5c3e099317866ba388c6bba688e4bfb61990d0d6bb63486642c194b1c6fb8c1b28246d6861b4c5342c3d128695bc8d1a2c031071a74ac51453663931ccd902abbd9e09a32eb72baaee9ba56c2a0820ba69c21d319d96c6498e1e2826166b4315a18a229c1c85c184ca34493e26864168e33d68d4646bbaf94763fdd5e9b1746a4a3696fe01877d7ecd0ee1668c26e73bc81b7e0198b26b1fd05cf8ce4b733394de982bde099898a5507cf341f071addb88b562bdaaa42ce9d1068baa66287695ed809172408c3c0b8b6a80b93626333b6d53291e018e7de6690d5d687766f7de099ba3fecc351ecc0333567eb68eb73bcc1c27eddba2e0cfb6247b034da70435bafe12b67d968cba28d0a935a1ba385d1be6052c392685e4ce982cd10b188093acd0826359a1a17d38cfc4c1384635a0bd34867441019be74342d1c69ab412663c410240e430ac2332d3c23efeed2d2aefaebc5a88ae65c2d179394c4a433a9bdf9609c6c1c87b5339bfeda0ac537c8d17b0d304d0ec7c4ef99688267aeef9de099e700a651c2fb21f1d27a46ea35f62e4c63c4c5c4dad27b8983ccbd5405936e90d516a5e3a73f6cf57858d124b6f990b62e9814a918587c158ee6714cbc94d1d1ead2f475ac430d6ff8619ac4a6c13323feb66412e49fc133fc860634e3c93c0946f65e9f1ce2e8986344f323d37d13dcd275d7f0a2baae21cdbfb89ae8ce6b668249ac841126f187381a99f8241819df88a3418263f8510d92a3696a704c7cbb4156db171d5fb78e8e6f63f04c7ded0b9ea1124615f2917a7c8bc23335a7cdd1d7d5e0998b67a81a1d9bd5b02391083360a3af9c46a5afd73ef1970bd3ac4d3c4c6b63e24a05efbdf7de7bcdd16d79686664ed2f4cdabbc804e12d2c8470dd7b2dd9c5695cbd5530a25726480f5a1959fbbe29a6afa8a4a9156a84a19e441d5308cdd048830002831400303824148b4703f24819661f14000faeba544c1ac9b32087520819638821840003044060406486004d00be28462c28a2f6a0a610a761374be6040d6f85fe866efb38d9846c6f50a36c3692030ab77989c490c3d599539b2367acc9ee78376b35db38b11f3022bbcdd0d7371409efbbfcaa7d7d4aa3cbc64767915124db2f65f80c2bb40bea2112f58f71c45e2e805c403c15d3ba53e0302e8b98cc80089a97357470cc6359cf2b9e930a19a80380e685fd157e9b0c09ee32fcc33f1a0f73a7d4af0b54ceb009a198fbe0d7b56861c41cf61f65c766392069e1e56347be940fbf7191528e2624cf961e68560d4ac7e3cab8a0fc85a6699c85163b49863afb2d237e88c969a0526caf548b6e6598a88febded86d82eb69726906f4164d977e7397ca73095c66b059d3be2184ee95252ab7ca778b1cc98bee6a6dbf182c09821e54d08a520739d4b8d497612089ed27268ce59a7c4ea418c92521f41cf6e8defcd4bda5ee6a56fe3b3228275a53e6c912dd4dddc6cb68bbaa85f938d7efa586396f292b728524e5444b5fcedac8e5a053b054f421a9689cfcd19c0d9060bc4042de8229a1834f68b0b20d270dc2f696f3053c8419fe694159a5ec96f472babff7eae48d67e79bd6ab504b252a980657eeb701e75ac260129e422bfc5b5d795c61c3b47c84051684ff4d4a8cb8fc8870451645510ee0eafc80d78f9556ed2d934c415b443306799a6e9303cc1e1ce0f15188cf2d02d79c40c8e1641bac5472b63fb533a8c9391570530937f16acae7cdc17a997d642369ae3bcb7249136fcd8452d933caac278bd2161939a3fa4d8d34a4a02a0303b272d4a1e9f2b810639149e285566d19e53fd0d4b34bb83a010d2dc035aa3b94a6d7a4d42ae83c0f996297b2f44e76d8f6c69e4b24419256c156d03a1907b109bc6b3c2308340b25f0529b39a74bf68a6548b908ecfaebfbb7d5797eaa250c660903c1efd81b20f8dab7a641ef7ff007ee7a493e35b8849ea7fbdac1cc1e8805256422976286a5db42a09666700d75b3721f046b5669d0a29000aab575ab53e0d24756791bc920fe29cb1441b907cb92f278622194cac86679a20531233065d7be23b010b5fdc8ba63bef52f141d02c25b7cccad8191482b5f0622e583660188ead0dc16a2db4afdfd2954edba293ad83960e91f8cb6ba11d472b4c3f226cd7577d80e053d4fcc4070151733a5496c8d7b57c701665a2b415a6434e4761ebd9e61383c6e0142d8d1c2da054e04ebb0903b0bd95d402303d5129265f7b6669a462e24784c71db10f4d05847632a521f0ab10937ec2c543bc84d3c224836e5f2945566db65e5c035807848eed0c147fcbb2cb6c093f7b8a6f592f7c625aaac6e12db762a45483d5a5e711fecf58acc63074eaf798483e335a0e6243f2a075d23494b1e8e7a49af6e7836a17a98547bcef6ececce792a8d4d6574903199036bb7458960ea0c0ee465d582feae412c6632cfb2789bf44372fbcf7b8be77f0809b0d0ac9718e165ea3eb3cde224b7a127c63d604a3eb76533d07356409bf48dfe3512e439252b46fe3ba3564534163fba63187621855e992f6487c3e3e72f2e76183372e6a274671235c8e257b26091e6f7b1cfada501d999ba3fe4dc875fc39e8773311986e81b461ddf074667f824a808d9f5e55bfef178a84586bf636977d0cf0e02cb1224bcf014ab91d88c2304955a41cec2f470619f81828a585b6daeaf88288212ec1badbd517540205a735443d8c4c0b2ae3709c68ce193774ad68bad8686a39027238987a249871c8c8ebab66d63339f43ae325f6dad06e447ac0fb7fd1c6a5ff2cb39e08a35bd4d49db863bd2f00240cb9a5e0a9318e3c841f928265a7048e5b10b112ba49c92e2994b44991e1c6cdd7a5dafd3ed44ccd3ef49f4fe410563d50485ca001fbcdd313644af4d6701d5682f7aca318931f4cdb4df21240525b878aba868fb054a1982e095efbccca34474ff2c2f6c2d801a3ad3b87d3e1a7c3780332482e8ed10076b4093caf0f95a321257078cb2ba82162367df5c44dff36d123294a234c39d92d2e99bcad214e314e399dd67e1f847ef7684d59533bdf5ea63da4b7b7dbcf5613e2a39df6454e4acc7a145be830123fa57bd44301f8b1c24b39b54881fdb5ca0ee1a69ef850deb1600fb8684fecbe8c9f5b3b843fad4eb0152b4530baf98a56e9b18a1f67d8b2e888aa7ac03a392b5b6fdb9dd76125cd0cda3cf40d12fd21a3dbc6723b0e8c8adfc1e5b2bf4dd54e27a0052bfa80b546dc0074562bfab87367123de85ae43100a973b955b6c40ea2d8aa4ac21c3099b91b99497e7939e0756a0ba2b386e9962f5116819dadc26b9496cd2a444e2b51f8c6eee09ffffbaa457c408c5f46bf6782eaceeb7b40a5444531a9c40af7d2e3839e5f07d034a2e7a19fab0788819eab059726e0568971eaee84bb88a52bb678bc8106614e8f00979fbdafada57eb1bdca04f8811b863a60e027b7601731757bdb69a177bc026bd0f9ff5b0e7e80b3157822183103e41d5c036c110d5f8ff410138959cb060ba3d27cd541a25cc5277df2a752f231c173bf64388e7f24ef06a37fe5a16a9761e7a02b10fdfac435ba9b731c6293ac87b3e325e2c77d7d20b1a5a4392aefaf1d2e4b62afcf193281ea2b0aced940cc3c631e70064c1e3787dbc123b3284a7c2045767614832dc079cbc9f5649990394213d6e8563dbe98072834b15c545560335d15c778b0c10e53a95d612fbdeb953d438ef64a9eefa1581b368a1d9dc4ba4a59ad2c3c83032974c0b1221cf5e21c6ba2fcb6bc33bad49add125f3dde2cf35806d7ab8cf745d2c4ec6e8ffa1a04a653a77861561803e5717d2e61a84d2e7ecf615584f4ed91b5a5d3464b3b3fc8d97b955bd29152ce759c9dcad4f6e41ef0550fabdf0d743a3ba33a436d911fc8ce92d83c1fcd1bd2c155bf4556d83eaad1d46bf1172360e8b87b523d23d2aa4f1c630c0b778710f044df25b72ce10d39e2e46b3ed11583fbc3b08b341370c351ccb6c06381ee5885abd3635d765d2d3d4e3d53602e878c4dc13df8b89740f741f6cbf3931c430b43dc2501458de23406396dd26260678eb80cb3c0d4682a66e61b8da9e80512f7095da522fd66df587a8a00d9e293c9889646d66db31d8c089595c976d0b5d17285eb246733595c21d50a20ef19343aba5649c2d51fe8492e96c100da53290aec36eb23b2fa0e7e737e4871c40441cb5e92d190d6820d482a87b065b1e37692df7504e7735d0e95ebf56b4e4b706f144ba874b03036c3b74c3cb64e11887a7cebedb432e0f8ec50e35251d64769dacb5c20ce737dbdbaef867ae501afe450161ae070fa8b7d517537de560e8f12dce55879f572dbb8f8686dad51149b0d96e799ef7fd0d871bcb2374f0ec1be491c1ff7313f1f9e7a2892a006ea12aa4f5a26350bea1de6323fe0dbc2c9910f71931201ac9db4d56df67d88d61fbf56b47623ac7b9202126b51ae56236585ca0a894c49d0bbdb4d81c3b61144565a1455b3b6599aae3e85cdfab13abe786399667700f9b8ed9a85e8e581ed7a892aadfb4ea11682f4d77ea89c5dd36859f9eef514c9a5f8aa3d672a9fd5c3bcbf61512c714ce9ea82a6e77b4c47c7bf4c1f7e224019f274ebd0aeacb71de7ba8d587b110d711d476aba402dc0389a3e4131173dcc005f41abafc1f5ab4ced996b42d11e5a976c73f24d8976fd761f3c279a5b936ddffa46d954f0fb2bec88faeb292f9de3e73d3a90dd3959365a1a4d6ebbbca12ed674d0cf1f4dca4c15e8c230267557e7e47a0d0130cdc76215413e7e4d12b5e55d0893ab406d5e1b2f72894b223c9a3f45e9fde1ed2ea4af01400e19f24e571dce0545114a60901fa2a73a22c4b791894bfb0dc3ea767072f0cb5075a244e19bbb47f33059c9374b6e3e0ca0064a274b18ac4fb6146a3d418c6035aade5433b53d4fdb8224549646b1a0b6be44cf03ace5612ed4218b0d42a0e771588a6ebd5d0088476113b0da970c57ceb1510f6204c934cfeab38db4f725012589d434e0aca54473bf52f1887e145b4f00207459da412b5e359802622c74355f245d0d95f0f745718875ee35f9745b6879bc30bfe29276fa63bf76cf0e65fff01b4f177de957439b98c2ef8812b3cacfdf71503ba5dfaacdbc3b2dc95715ceb06064c67cf2f6eedcfa59b5a0be27bbb082d305dcd1212ae5fd952b8a929e0fb25e81e53b5db52d181669f40dcd966dac808b555dce81e26d5e3f654f4ccca819720ee56ccc030225c157b960e04600631ab269429decb3673ff253d4ef5a4f6fd9047d0ba5b7202d237b57d85142174df550602d646110c212d57e5ee87de13b0f9340a80bef77b0167aac9aae6db7588758248c5c54aa071acf74212dbb1e0a592cff58f12f6492599ba6873c0475197030d2513f80da0c9196e679cf9891e4765c37abf1dde8bc2fa3d8749653fa39342f29232cf4a5e7adac47e08a87079ca8a3db81d41e6fa0133d63ebca5ed945f00c75677e739ebae6e127b0e4385190ef009b1416f5e8686e09443c603418344659433c0625baac6b2b5024812c3895d9f8aabf6b829994c3d5248243e003cf3101f7c83238c9d82dfb3b56c8dfe0e6782bba1a639c0ef47b2a2477e4b7906ea9fdda1dfa16a7830360776dec2e0e7897d113124c531a6f103f329b2908d92f9cbc248ee91f788234a0247e170ac6baeb72089f2ef35294509b1fd620c757bd259fe0f5cfde5101799349f7b0b915b48e4baa169fb2f7b860898694a5acb13c7cddb2de4cdbd952c8973d97fb631b7177403147f0cfaf008ebd4721c8277113a79cd3161f49f36106c6538d0a4850c7dd487b2d7cbf29de0a05fe0721bb54bed2588223d76cb889e8f2087fa12278de7c33fcc9db14003f8b88735749a11eec0e364707ed5bb7400246e03f7540ffadd079052001845f50aa64e20d9389067d28fc1287a7e0d02f6a2a26c0dc9ac3459e87085ae62bd9f5516ea61fa963adedc84e19f3a7dc8aec6e22d2ec8655ca8c800a5bc8378fafab729b8f9a1fc2e28651c8ba75bb8b4cd8f5b7164e4d3425433b73c094888b4fd4c82b11386571b1732effe3985bb076c7a37acb70c51a91b6c40ef5e60bd1bb1746df0db59ddfb5980def8f49bdc8e115fc08a092357cb6dd58ad1fb596c264d446ec2978904d92caf4acaa223b2aa3c7f5fec2856a9d50b1ce7dbb18bcdf45b25f1d06be13d928b189320e315eaa2c29bdc859bc9d662f56e017d3368e4cb564efe4f907019e450d2a1d84fc5ea02d80af785221da2c558b5d6e141f62ea4f620a4086ed978502eb69516331cf65ea82db57ec934c1cf433682b01ed2f0562424c8dd033fbf686dd869e8cd952e919a97a76b554afa9b83f8458cfb35e0e0fed849a458c758e3530d0e6ef6a3e26ccd9c658e0dcbbb26e558e34ba07b926e43f921fd9036c20a54fa62d144bf74c235577e15b2b837fa12f1d3e2bce84a5d5b460c59edac42f895082d184e6c7dae0c2a009e4bffce30608a2c3a0727b7e1aa496cca9b965cde9bfe81fdc4729da3c1b7fe568a074d1e340178d3defd226da3c8dffa2b4cc7a101739494da9b448f8f9aed684f742ea8f155e4d9cd6993a2f65613e31f58ac60679a451438e7dfdff992ee05bcb235da53ac0cd0cd35af227e57116dc901428141e19515b88adb6f71f3e980144ed98f374475e734e91d3e7d0fc51e852b21d65fec478e492bec9f84482fb14bde37a4d0b3809317397a49ee483d781c3483d03bbb8c79830fbc468d15084dcfb15b640c217cfb2f5d52bc32f508229bf535c1f6b4733da196ca8455540c226e43f0d064c6832b3360d022c42537ab69ba8415732f57d1500386b42350546cd5848cbe0c0769ba517208fc690fa3e05fa4bd21664d7b487dc9711360af1eaffd289016290bae5461d622703aacebd57da603160ba36fc0c3213b4b659698fc1737421f4c5cff24a5fd2bcda83f230e894e57c1a4eef7b33f9401c4d8850c68ae6c7bfd20accaa85300bc6799cf1023c4c4d86cca4678eb9cd2a16595f4a07ffcf069926f57454629213d7ce4a5786ee355cac2c3ca25b9102c62aecd9fde9405d94d5502fc12fb56e14fb4c4002f97fdb1840137b5d77593389ccd5007a19617fe268b736aec480f83069c8c4f3eb55f258f7437a645362f16e41d970e4fc929775687ff02e8202cd26824cce56d398939df8886d4cab5e3f4f74e262ccb006af5d1ed55d6fa2baa4b53f521be3a336e72326e1f87fb16ba07a008b7d531cd970468d58f2690475cbd2a923e668a11ab1067027e52fab45a9065951d7013c8b661c09267adfc54f5679009969c5680bf01d4a9a6ca85ed40c0c5c38d9582d8e612d6562c0889f31cc2dd51f357bb90106a1888418caada8a99886b033fadd09a63aa976d0a067054c7427547bca61239482559ec64bb7496e47a4454302a97e3840453eb5c28d54ce84f309221152e2383e18be3ec6a8069c44a1d2abd9edd389b94ea94eabc785dd6c7f6653eeb6ef7553820d266abcc77c26cc4cb041fa4b72e36084b76f69862700b050deb657dd09d333aa7bcae81855f42b3bd145a1956bd026ef792af1365f2b15baba052119e1d26fde4a1d65aa2ee884d32794fe5d6625a10eaf3626676483c6081b49df0958e4b26133a3db874fcde69fc007512afb86c64e24946eb208e56f25a740208239aa58e16f013db500d44ef243f2173bf76e3f08517a795083457d4df03f96c6e8ae4900f7d4df78fe260ad07d437d92108487dd9bc449c8c79c36331658e3d6f9461ed07a3fce6cc0ab03e53dd4429473b943eb722374e690878340c31704f152f8d31f2a0f602637dec39816236304370e423f6d0411e2e0164f64e00a12fe748369d7d4dbee3f5d65d1c9bbd7cab55718f29808671a4ebd89c33148a5d03f7c4e2c1dafb088d8daa3d86ef68294f2830548d001b72dec9d07f8f7a208451dc2c71a7c7748805ac58902dbdfc7e1413d2382ceaa0f643fb5d8079d4a39005ea75b65b6e3301e0a06df132af3e3e95a56007e6239be1300b84277ff6f7a98ddd03346abffcadaafdfe1f66c0d5ab580c85ca50bf6cb50f904a067e4f4c808dec6a8d539fc25800ce111b331d37d55eeb2d44b5168a9b037380a50033be796d4985d10c46deac0bfdbd996947ff7996b180250f25eac3556792632f41f32b7a0654735be6b88e928e2189cdd5f458aa6949bd1ed06b0bfc7097bd12c5bc85b1beae6647a9dd96237125423326052c16e9677e2d6666a77fcc7bb65540d48307c1eb48bd83f4ab5cfcd0f98c8d3bf75d77ef047d25b83de010f4ce739f2329eed5d6eece6fac4e7828cd69a2486afddffbc6879f11c391b6b1fe654ee81e1f82e89214bf736248a34b957d1640d51b031981c45f8a7a7f756fae476d6bfb918f2d773f6fc8d24963ab637164bb8183079c3ca3079cc0aa7714ba401f4da2b3e2505358d8675874008a03868ae2550b5590d9238748c82cc8f4c97e4c60390bd1109a89023aa89a912b964fc60053d417adcee7518682345f0d520cdaf0bd12d73f367a31fc796bdc674a84af99be2f6b921e9739696539d5a03cbd13806d52f559e81b06447b004dfc8aa307c650549dc397e4db9d8db3706923a2ee6c21ee500ea222dc6c1cb691ad0b24f49748d21f2b8bb33b40f1f95918563f4ed69275e527735d356cdf6db7c5e13523826f2428e22ff2720edf902f3f4db944834b546f514217b74cacffded8c5e0321368ad4e65a3d3e92a92b4afe6a0e8591f88dd430b15569ee28f2597a1735829c0f4ebb97deea0730260763d5a7107641e5afefffefa484b56c50747b648c875685de1e40761c93c8810205635bba8e3e2250a1c54c610eeeb9a7bc0bc2a6efae701b474de72c2481ff3a0095f2461b0fe5927694252b8b51c4b1d85c5aa5a5a0d428bf6fa6911a873c0845cd45c49ed9bb1487009faf1c145172b6b941772bad782a969973a65866a9adf4b3cfce75e4e68ed30e66d079b121a201586b9563bb53afb2916aadd268b6636d4445b12b93213f6f75ad115d0f4991b1d9d42845da4530c66aec35a1096e5113982bd8f50f8b459c4645569fbae39f8fe8189b096fbe40036214336db5b7326251cc2c00aa5e5ea871ea65acc2a15680753280a4e2d737a8d61c6fc4598b35364c569eb62d7af4369310714ba5416fd5f29a0b953789efe8cb4baab9744e6f2d62fc6b96da6e9173842dc72af3d8452b91825061f87bd8be2536f321d0c0a7628a331381e85aa0ac9ae84920f1da55abb8f9cf33247f8779be37e97bd98fb6704ad788db189e6433b2f0ec40d395ea551b0db180562bf1d8a4ea45f22984ef0dda1feb43b14bb5c10e9643c1de9e082d719b9a04a9ce9f01d62c480dc8201fe908ce9759a08296b2c88afc2c993fe5a87924431be91bc10d5e28e3af08e47c3b4c4fc4129dfb88c3f337184f47e63a27a5bd71210ce8742eb8f4ef79077a89ea81ba0fa5e164dca552dcba5ed8bd00d47478d0377eaa5e21e493f72e0dd7916e1f2a0c97bcd7eb4fc47f71bffe1f586fb70b9711f2e37fdc70b6eea164fbf34a3fcb00d023a22e30e44331debe9a043509ab55cdf68a9e5be1def914fce98cf3757a8cc1c76e2afd4fd87817c5309a71fad6fd2e3d05760deef776a654f4bb7c6f2484e2a0eda2615c485d1de103bdeac81d9d3cc8cfdbb3e4e0cdac75c5a343cbcaae22b3e29990d00a7d836a10d4227da67d0fb2b3ce64f8d6d867a7b75b2c3ee030bac10aa1e1b7a0126458fc7a762807c29391372da839c65178b1ac87260499136029a1e0e6a487a5b7de1d704f19234c18d38bb3289b1061537e4be1616bea1a29bff736cb0891d6c4b0f9e051ef4b0641f30de07c52449fc90569ee5dffdb4876473ea4db27a74b83861d0ea589498d431b43258f999de05e4c8507441ba68858a12e98f787f9c1cd13adf444cc44bb3bb64d38be8f64d9235a29f209ea20a9cd48bf850ee167c9b865466dfb9d7626077c9a2c230e12d01b16aeafb7af3acc67b67791387bc53f8ac7048e73a28e0774b8d4a2cf1e226a393c60bdb9487e5e4280cfec435852937d5ad8cbf0f763defe1b29050dffec6372b6cf831d0d88c1ee884608fea9254cfed8694454fedadd7b66773d4616b3fadf6646edf3487ce2de0b2faed810c602941b03d7a14152aa0b509de63a4dc289222e9ec8f025f2a962e69c5701b8aaf3a1fecc16f7834f9602a3ef0485e667a158e028c5aac567126b197c4cffa961373fafc2944ab48c8fab98748b1c03bc078a44728129ff523719d7ac954c20c01f236efd19fb10ab0e2c79189f32240457b00096e18f7a17ce8a40419c0c9acc79cf021955f167c52ae0b5c49b653b4ae71ef3535901121402ee0b4fce0fc8ca73b1bb7c5b56762e87185b417b7bc0e53219831028aecf61570242ea7ed62d5119f6223dd81eaca5674b316b44ea9e91cac5a1ad4d4a51e973e2fc7dc00fda240674cd3d8da9bfd758ca6217895f074c200fd150286d7e162588e570aa812617a55490f0cbacca5fbbd6afb7892cce455fbe78654b34c2fa8a80bfdea8214385e351be8dcdcb85093b4833ad7a836fd9973bfaae5afce82d230c2d53b4e0929fcb739393a6f5c6ee43a691f120dcaf3976a04d3599952d53a98344081fe5fbca625f5f5f347bced397b1242a3b1b701c056d68d4e150220fb766b959f180df32359065757c5fdcfa58354e46d246dc48550598d513d149f88cb42f869d59370c6216e78f3c790b3715756381066d7af3f7cc3e5dcb808aa999575fae0ec122aac96241998227d07b565e69811c9cae263a1f1c5cfb1b71a78cadea4098455d7ce1c637e2bb876e20043cc4fe6cc802b891548b3c414cf16f3ccea62953a15ccef0552fe486d2f2cf3b0baaac5faa6b7c64c23fb41a59fe3906b87ef6019d2978d344e8853aca69af1059c24414c533fcfea3b0190ae626e46bb94298b9c369be68811a790475a712d02b3b920265ec85a99520661ebf25238828f103dd6dee50c494b1b6e9d6eca28e27e7a65d99d0427877ac8b9f8a5a005fe4d25d354448876f31d346375847cf2becf06498f15d07e686b7cec45ffc59b1e59837322bb23a1c623588e9c8ef819f408fb227972c3f98e4a18dc20511e9558c92d0591c118c1dcd846b838b6f28275cab004cf27eb419b52ddc25acfe26979dc167b7fddb2a659f5c66b51a7951ffdb17b8d77aa39e77a81d505cf53a2d11bfa928a6c4f9015c014a8f5c1d2eb123dec322e76f1cbaee3ec227bb42b40ed626fbbb475bbdea02f6c79335af09cd32b3ca318fba8acab168d2f1738f2f965fe68a1309b1e93fbe3348224b6a0bbc1ca6ca75c669386e1e0ae4b374bcd569d134c269847eeaffc355b641322cf333a6b3648bc39171b31c134dbddb4ef6469e49b31643a38b344f86fd37c90039df5576a6d1fd5e6a0c3b425f9311f4c6409195417d9eafdc8ae8a67405a887d5ce651bb8bc473b72337484c27bcc2e81a88d4207dd24c5eb0bd61e52fa437f214b23aa19bfa742389c5c03ff0d26c767b0a8f4563d589e298ddec5473492c79bd8c9d7e48a104dcd22b9026394ebfa20cfa0347897c30af67d2d60a45427ca4d55c98c92464a7fbc0bc4e4d33f83e4180a494a04ace19559549b398d02e8990293a644424d6c97c7ef6b12ab56138a4dc15ae618bb35da4a7383456d9f8329d12514f0e113cb44cfcea348c3dc8574dc7b248b31c87298885a850bd292dacfad67df25dbbf1290b3d02900ad5149c8225699a4083e972c60552088c0af0b569a3b82303023d6492570b8f49843b2edfbcbb55823875725d5bcf4ffa93ce225712f8801622d1976044cd27fd09375609f5fadfe4c409b37962af0d0750272858fbd430fbd7f6c47e8ddca6a8dc8c12b365d997896555f681747da62f3cd954c16ebc92bc5984c3f583597a313d4186171c6062ee7a24950da5de2cb2e788dda794f28bd978c93172a9086972ca6919f67bf37cdab7c7901f19e08736cc227c0b9a3c2113d8ec084884241d3b319b0c91aed67c7dd9a0404f48d5b50003ee882d1b2be381ea96a57860417d1fd7c8158d43ff5378d2826a4f4719433bd46b742ed7d9c30cf2649657a9e07865a668100380e19fe795ae6e05e20db67d658ec404d8efb3314e7a85e43355ab21963c0d16e1834e01429a28b8ef07e00e5124764ae4409dd4179f476e27123a501589ce480ffc00312ac68734f9661d03e5479933966bce2dcea73d77e14e2ac519513c630b770800e59eec6631c0fd07015937334254dbff9ef00a282aae2d03599c546d1551409fa6605a19c9a45c70a03b80b7390901e339a79bdfe622eb0e85bdcff4b2f74600dbe84e814dca7c0364bd2c0c82bd0268660239b6300a2253068ed10d376f23617caa26335aa5ea538d1be1f4d3c346517eae52fb4f5544975413d7f91e83a91193aa95267fdc10072dbdfece422fa8c5087f2f78c1fa4b637d2d26270c49fd1a44a09666a7c35051ff63bc4bfec6f60d6caa05df9aa65d8d2a2a4a7e425187751d9c7efbfbdc1d61891ee73b3872a5023ce7014f7f43283354cfc5b76d385e41b035e044c63e2cc9b0ae994d9a48bac691d95a2123c178f4efb5495fe317614e46aa5fa2de0dcc01acd4bca469913d7ff8d6dae4aff9d9b63a8481b5cda085089800e06619a4c775da4126d345ab50f3a115f7719e4103ec40fafea8445f17b9964909b8dd6e2c63b370b2f3d36a0e4430e81ca5508a99fa572adfd5d820f53df0e7078f3aeba494214f695cc36e7bf5b150e5ec193da042046309ad59fbb63d5087197faf29017e8d9b2147c6ba16bfbfcbcf25859258605cb693473fe35175802139c2b4e7501f645daa2346b989e2cff817f11c7ebe846b49b63ff01631dc983d44525dd9f243388029a3efc58d12b9f39a3b090d75e4232462a7187051353c5552e5d76063d39a8c5394178c7ef17c33d7400b0c34b4fd63115a318d8baba2d6fc2e08226f4e361ab16b3e48b11e18a66788621bfd383b38023ad68727b3a42928cd075fed55892ddb8c4b0dd1cc6ad2d90403a44eb338259fc5a152a0eb57872b97d1b2f44c2f39d64525f5223187e448957bdcbba9f5c9c16d7f3cee02707a95e54573bd0b184629335f1025417dc16e9adfea8386f1c20095a347866635b6b47150b41af8060a4d04c216057a00fec5c2e4d53dff94a4b1dab02ae8937783b1e3ce3e5528533b74e298b6dc07d1fa454200ddef5e7e50294a2cb120865e86dff16e7367a4d89543babbc71ae43cc667561cdd1363aed905b13abda7930d619718cba8f0908ab08779a65dae4c32cb5ed127bfd16bf29bdc8170eaea5528d37b383d176959232c329c5f64bb479d61be3816704cdfa97f9553dda0b7dfe6f1af6b3165d5b0b05d158ff406b1d5016a9f433bd6b4548004ee8b4a710eac699d580a1b6f1c2944a0c41bb4b6bc3b0ba9554e8b1f7a40c92771a49a1691d26aba67051bf0b2379d94a0477ecfcfb94398336af9b8fecd3d5247999b48350d6a41183f094ff59f7a3049511285f7bbfcfc08342bf03c7bb4f90b3655ba1bbc196ac355af1f71ea84972e988d1e884b9b3f5fcc47274e02259ef34861c9eace6cef57aab32f8fec68145abe4326136d272187bb8842f66c929bb5b6ef78a026247239adfcac8091cd60fb28d799343b56927c26a1bd2634919dac5b4364ffa421614c29d52a104012407ee5e883307c12123fcadc6f59853ff80ba68ca5ab8ed49d139b5d29e59c17629c22142c4728587cccdcd6279a2020977b1096794861d3fa3f3c204493a07a6b776378cd7f369e5e6446512be9cae7224cfe0e7c930d09693873edab5f9d2487afd25e9ff85cb068faabcb797be67f7df30969b1c16dcd5440c97984f72c26536ec099ece4361e8c4e89efb2bfac73308238add8c049a1b23810a2b0e00597f72a6b2b325bc280c7953799f179418a498bb95785f7f0961d51e082da618a072d86ebbe55e80ec31c7c1ed23375bcbe5fa298d3a9d9e18f6d95b151fa046c90df76453b8e287f908c4892b8837275102377f74ab56d16705b0bb450745115b739eac193d7fd2a2c0a042c54d02836501733c8662c1408660aa83bc3f4e2799044110a931db2c478b772516eb5126d17946f78cc74b03cd0252ba1dfc3ffc7f03aabb05a73c52059bcefa01dd77ab4d2f85ece22cca8be675b3ffe736cec4ca0e9d9e47b2cbea3e1b2878544ce8aa0fccfb131547bc98f874947c189f4615fb38c13ff66942f0cb1e94b684c4637ddeff7b2b11c7a2b756caae7ff9e4b347a089018c5620edbfacfcf6e00100b086a630b74edfdb07fd9317a0ae835e8740295520de2cabdcfe79126d13b14de6ed41fd5867c44f2234629e45ae4f14174f10d4205967c529f0b9cfd89482e33374e36b8b281e4bd76dc6720d9835c138516af682bb001f3060f5eed70d4ecf3dace026d437eaf71b2a2930308a3c9eb85c2a5d4e4634cbe4b10462dfe10fc0be7fa91c1126b9c21c60924a50445c6af50d50b285ddb1bcb9e631c9440c98f8a4d826d2890af93466234ced822546fa57d8c5a08af9a9058069fdab474b2040b3f32ffce6ba83234e2a727141b3fefb938baad8875b4d7085474495681eb7b00ecc2dc8d754904158933ca4fbda69375d89518e01867b32ac16d93aaa973df754afbe9e447e87d253423d626091e5cf1bb7168dfa5a07cc070165a03ce52c97c6bc3128747b24f326bea753eecb55a0f0a45062d10022e5246847d6a86c952049424aabf170b8cedc093395e07eed93671de2740e68b372ac35a63c95877290f7b8922db9ddc5204be9291d0be75ed35e6f961bec1987dfb902c5a5b91e1f1ff8d9c39394288d30990cd0c448f59e8b1c0e54959048f263541473c64ac2860be3ec04895d7c21418b9176c77d61d8cca2badf3dd62bdff66a59e06265eec45ef92b2af94ae71b4f1351c0d908bf626bb96d57f9c2a0c9d1e34ecf26aa344f163c8951db4113fa2339f5c0222677e7a8b98ae9998cacf922157144235544d7a445cec180b3ac156f356a7b65825141ebc799a5699c03d9206e83ca7560163f88807b336bfcd3c0048f7396c35b6f448cd7c730c735b595b49cf7981fe58a0121a62ea277a3e6ed70363586bf89605b155689582ddfd8e2dab05e742b3524411220410a2d620a3c378fd853c9a24258ea2ffee3bc7149e8683f779e3767751a91e514f9e60173ea2290a1bf855f50ab9a860e324f1f99a930790a114cecc0d058a84b79039e1d94c7da0e375f26ba03c4e2dd0212844413dd037d168fae7c3b2c1d2dbade33f889f47d1bd64be25a199173261f3a5ad1772168ddb16d9b7c400f49bf916822d6014093db6ce9c451b07d33a2d246bc248d1bfc25577706916b607703e5783d28cf51a8f36c8230a3a8b5248091aa40113543909b6042a1dd9a29ad1e5c223aee4380bc26959de1c974ced229da1fea6ea318f5847d33b9b79c87a0e89f503448125a518580df6a101e01e9d10f0e055f1119da1f1d3362957aa56b8a652311ad191085aec3e03ade7e2c7e8d54798280c061271206a3d5e5bc3bc05da8f912bce7e3fe0486c4962cbc11a9465918fce6c4615af8174513ec22df96fda54106d38ae7dc8c7f8003ca6c3d97573608835f1b3070c82b235cb202f533569368880ca6b436d829b81722801779edeb622c101f0ddd48df58b3a5abe685b1096e417c8b5a835068c4ef417b29ab122a069e76b110cdfebe0150b10be3e11ae6c9c83e4446a01e605fccd34e8a0070710b5fff3c6edb8a02df18f0fcb895fb118ef021b8a8cc29865df9e5dc77d397d6df3e0a1c39aa833aece9ec0f64b7d06672a946f6900554bce4f5033a62902c490b6b326700830ad04256e11515fad7f3045be7b9634278857b8c6894b3def989e6e192d8b779665d23ece511f0f6c08ba9b18676615d3ccdf522e42108bf60a6f5a40f6dd5df63b4608d7ec5f7b40e64eac6d80de60dc6bcf0d338b07fe88845fa7597c653717aba0379f9bf8edec9c7a0b120ce562b57a4ae8fc88481f22d1b4455d30bdf8328dd7bab2cb6dd6a53968ba3d4ecbd76fe41a33de94c5a880d7a71ab93b8694090494d7056298cfdf6e1b0cf73b650d7fc4effcd01b52944360f547612842bb907aba6e5eb09328fc6c9fabd033ba518fb18f1e1340e04e3d1f8305183d345f44b52aab0534ce85ec1d37401c827509892e1c08e87c219c73c750644afc00f19291211a41f6d1ff00aa869b872f90427311844ed23c533ceaa4509de78ef0d2ac952fba9a73941430c2ebc3f737df713236ca892696450deafbb26badba9490ae7c5d78910f11ce0c1f3c26f85a6c6fa9ecfc9cd86e2633b74044e2d664415dfeb11d82a9478a47446ef050d527ee64b4db60ac4ddeae918b6e15c814398b2f656750f6beb417ea04610af63c8229862f9fe542ea9fd5aa5b8d73f55766e71a1dfadcd3c3a910ea71f1bed12c96f25148b695fb27af31d86f57c8c63b9d67ad25c06a7738bc637e829e31da1c41885b1653502b66bedcaca0394b3f43d0c275a908a72591b698b5f1d62300a0de3879f2e766b03aef792fe9a0d20e56e98121e9e1033283ca3dc07630093d6f44e8fdea382b353763314d489a51c51edede03ada9a04ab6bbeba349c96e3ea07594c636c61c82d68be3020259b87449a71a34c8d2628682a69948b20d21005b89e0670b4cf04ea0c22be2628f8a28591c3b0b54a51481906951c8e907511aa7baccb66dd580664c2aeb7de9bb3fa90f9db32ea6e769afad32508ef54904197148cedaef4072edcc993a640ca090e88fbdb6a8a2d7cbd3b383ded1210b4a06b952156cad85540fc1a68ecb557e25044fc70f0de5ef41495dbfaf06ab5528bfec6613beb3aa4f099585e2acb2a279a2fd1c7fd37a42f6476319af0b78d1d0d3e03fd76d69b23ddee4356197af7884a21ebdd5f791e33715bf6d5d00ba0c83846b45c7dc22a2ae4c4b7dc4f8bb661a5b567d78a59a748ba30fc6f7bfc180bde1e4fe3a4e4bb4a0219a756fa78e651ec0036a6eabd5569eba93778723efcca652393cb997fc0f85f85d0d99a74c4ff182d685d81346a56ba7d86c6d9d34fb5a9677aec6bdbea1e5398ef5a08f55cbbe48bca76ced3139d998ad48decbe49dc0489aa7702138ca619e9acc3926a0f693f673ac45398d433ff884b6b0e9d2539f339fa59361c7623836a274be1a479dfdd704278da10384ff1f981cd172f63b9674a49635a548414382b2d0cf414d3a186648396b5951dfb4896407161743e0f8040d359457db4aadf5161b18530b283a73eab873d52248390fc6eab5ca42bdc47ec7b251c48ea146f5059dad73fd4e1a43af64f634f92df85032f218f319b46d2da7c96efbad476928d420b6c26b5a972d12424e082af9047b9498a11950c873af1f430c2bed9a26108e882d2dbc9e0ceb3e9e2263575a54508f90e8e51603546c2071eaf2c09895d11d0e2b01465976d5e5cc16eb0be4b286fb313c003712ec69776d93dbfea1b44f46a8bd26d4c1477f163564fc82a3460c6d411ad16571b02a979589746c04d6922d38718ecd9f1c95fb077b502f095d3e6e27eb63e77a58e33f427e9fd3175490e981918d851d724a979e6ccaf9d3fc77e1986747b5cb1f1f5bc7d2ea9ca60095ec006fbe19cdee029ab47d958588d0d36d4d4159123c3b5b195d439bb9bc89445e02503cbcee3045e37462f8e610cc833da61869c528588be08e2c55352e1292aec64f974509e567fa1dabfdab2757e4c62d6acf4a374c777dcfaeb3db15126c6557d97a81a5c7850a91925b2549ffeff42fd3eb6f2325828e740a27f1a62c688804d00cbaee7e73517f244f255e3eee3bac0fd6c4c20a0e4090963c7439e896e606cff0ae92407236598b18ac4c15e654d26157c89d4b07ae7bd7b494b2a3c31be29a000b56329a69b5d3804d1a30c3aad1de9ab6a3f782b62d43b1b9437f381e19f36d5f3c68de51d921e4d71f5f3f10106b5174d764b3d58ea831c62823ce021789b7126cc6dbe83c67e401bc8adc93465c04c56e6b1ff6a39f69c0bf4c32c8cc6c4738085b91b0b03d48bb4c22fed446cf0173062f995cfb317b4b463c444eb37e30873f13c5fab98e1551bd8641128f135c486d5921d7ab9591520389560bd90bf84bc32d4a6b3a09e141d3360294d2f8c6295c693bdf7c78f82ed89ceb778c074d70487a967ebe556a40a167fb2bcf3e9c9da2cc9cf814c0f744e0b7b3e81239762320d82a001b44c54e588f379e0044c1428e012bc8b15898c0b19a20d8d2ca775b12acd9cf2ae2afa39ff3b77778300cc76e5c554192eb5d87bdb409c46d3928e5f254d4a26ef6f95a6615481390abb24d002d6c4a16e71416015414413674f2a006ae54630a683b2f3c8d4d6e5e8a754df33a2950d391718f5247deead8d061a2a39024401445ec80167904ac06ba3260e11f21710d5e053bd4bbc66b49ab52fcdf1966dfc2f499f4ff5dff470fd861f5ab6848008272ad4c2468444897c86da323885cbb408142fa6d2520168137e204ed0784354ebac76c05831f58d840778295389a91fd80d0d11c5f5c90c62fd1e6f3c1d3d63ae260efbc4f88125a0c55013ec0aaf6f0f106585eb6860fcd7f576e83ea0f8d250860d338e0a374ebaa59f9d1a63b5f71d45d58ca7d134084dd4b2c97284b1bd8e030f177a3e398f3330a1b8494093906896a369bda2c5f74a6e9ba6857fba8d49a832fea66e3e57e7a329d1ef0dd9b6652431e4c86035f545eb1c98e2111033711c892e4c20011d45151d1be2f4628205d5eff325fc410f2fa8deac42a5d0714c6256cbc056aa410e0f71cb82c402d2196148049d2a16ffc2f1172c22782168397417fbeaef7a75e58c13460611b77308097b5dd77a111be9b0b1c8d0bea825608cec9448da5b55762f846ce5d263fca486119ebedbf75f90900a61559adb90ccc35d4fd6577458461697b1b27cf875b77d87a5456d6d81a843fe07fde8b629740cbb5ba505c36138d13d0dde063f44ed08086d766d4ac7816bc08f8442f201488b3a55744af53b94b659f9801b0f8c63523354d99ab219b324ff05361e1914c34af43462ad2e3525fe3e1736e4f26c2f0616bdb84eee49e1b244e540da5527716db9b346aef00f203d8d57d2ab85efdd0770c72787dd03a2199473c031d2f5c1b21b4ff91d36ed80835d46256e767d21fc36e905ed5d40e32cfacee03dab88ab4030082f37b38b69eb31ba1d44864d35448256bc72e92d0bedac1bc20c25fad01ec058963b914299a76029a45a1b1d5aa0064880841d981499bd9c032d709c35774288a5da4200a8a29db5c32971fba28c486b2d05068c760728259fa9741b6b110434185e689fb2dd94cf01c69a691d90e74239469c71db2f1a7844b336154fe00863ad5d9656f721613958fce88ebfa1ea4a8926799ab35449a2862b3dc4b812670f99e3a1369714f6972958a9f0e1529e021f61c3982e9c4e6e24d2389fd662138b10a0364e58ea30456bc76dcb397b43ff1368274200c69918641b302df36f2a03083fa8a4dcc1ca17cf5febfca8a80da7a0c69635a1c604fcaebdf50ab7be333a782b9c6a69b847c435c6fd8aa81a53f7a6aa037029cba1835033d349d5055bdbbe33e46967da93e4076007172b6f03e31e3d110a9483f88fc136ca5f44f070689b2f04806f026675303a4ad17599a023cffd16377b740c8d2ab8a82dcb444f301cbbbaee8013ac8a98c1e04dfc91891311934dd816535861c1ed26a98113cf84d01c6291d431a681f01560e2d76acd6964d9d9349d85b98abfb996660ac5358a9a1f6be9cc5d928d77e7d5cbaad3792a1bfeebe5f232b3343ad6d9c3600a0d76d9b7769fecea0e652f8c183881dfe448e0addd9fde9d549a13b6bdcd86c93cf3501f1a3d57d7c94974c859cfd197a9772ba5f49e3a8f608b6df0c5f5d9e3b2191a9530150f7a10f124ab8a2c360eefc78bf2cec6ccd238dde2f25b367ff41070380efe4bbb58cff29f70e2b3c0df803b1c76f852ec0405261d9977712e33d8934ee4e1855a3bfe9862c6457bcf025d22b641fafa9fab550122f878c2c103c261c13b298d26ae2eed621ee5a437c8c232148793ad07702f4dc08cd2b2ea525a4158c428e0a9f3df8b3f1ef0a8cad1bf0a3219186f997ef8b42794c2234513a03ecf9efb6281fe26d6adf1868d54996d495cb8577854fd7c63c38d34ad9ff3fcaf505f76743dd43023266a76b360604e486e4de7a0c5440e85024c143d8413a65e393a602a4dac8e68f9bb0ec0964fe1079b9424e1f076ceb97c667d4aea8b9f8b05c361b10bfec021515b4150157884061bc69d7d28f99d5c2406c3aca8241b2a37f7da967a7404352227313152c919258239e13bf3d6229b6e21830d82d4a3c1c012c4ca46ff15da98e1beb3a46bf155ad7dc6412c706a95019fb2fe245ecd7bf0241f10129f929b0817142da258f6e101aa35004792f04d13af55aa2f221c04ebea881df720a271d1aea752ab2d61041b61d767fc179cc650c9024d7ffb25d6a541744a7ca9b06fcefbe384a7d137d6178877b90951c616e627376afd3cf27876d61cd020aa68ff765222a6e0c1b97ec084b8bd0b54ebba66d81910d6646d5d4241a02e00d69d20c302f6b0d7889872b2c383da71f623f98a5f849844f96a1edb16b4a4951be6815a98e71488c185241493734d404a27cc22d0caaa8394e824e6b8e33945c959ff03d3951fefd347eec5174f046ce1fedecbed2293f288967e226f82742c2d65c3a1c781792ea9294cf2098b90dbcb6ecb9e72321809b26e5d1738c6cfc9917a1381bc19e25502304a6734be3b15d2f804669c0b3da5e822d3d243194f416f930436cd177404a6618b28de71a0f0845f3799ab3771a0ac1d0e059f7b499a846fbeb7a344c52706f10610885f5dac67984bba29182103daea6500155658569b462c415f2b5375aeef7ecb81c0741b6c0d7495e669ccc90cfb966274ee96ec3f4e1ac7fa082868f23cdd5cba09636d02cbe932d921fc48f37d2d042ae5f31620d94f8bb35bfd5aab0e4ffdd2d5f97f696fab38c23ec2acdc2f52dd8a8572e123c7b54059090ef2456eec4cd70f07002a4c35d4e5e7e8b2d10e37c0db4994dc40ecdda2782c3e3ccc44a7fb122a1d288770519a7eedac434464c34bd2b7da03f627101e37be01d529896de121e58d01b38114285bd3fb992c991e3240b9c0fd2c7c3622a14b13be4a83dfc8c6bdc7fe2345b441dce586aea867979691c2c87395144b96dde12035c582d93c4bf015800789602809a58e4c86e4be64adf8d28bf936a34af8e37a0c8d8a0a0ed6ad1b3ad5db9dd9ba562d9297258a2005b4848e883db955ae21e15431218337c2c2d2cb3c9e82c050aea844e303444cb5167e41faf3c0866e4bcea31cc56165bc9528aa3dde69a94436521bb9089955130a66a13da5610b48bc8970a4df578f695f1376ef275b0bb64858a05eeac6cee170d1a2a4162c87874c06bf0b81048941004de5f77164491cded9ea2d20e188f37d6cc506c0436fd4536ce339f003bb484f14c069fa4b70c01e7b03acd3f3ebb9e1663e1b6728fbb90c9af4543e606d365c61028f37f28a3c9b58dce068d2aa255c802a70298f005781dfb985179390f638c985761261da05b4787c62ea07ae8b230518870d874692b9a8f2af0b62e890172e66290cca34466d13052c6e108540aef9e60a6e359a519768841011919cf1d2ee755748622f24968c127131bc7edbe4cd64bc503f8d3ab22582d1c778bdd5358332918c0203625d6d5098b95f69305499b20037e143f0ddb3579619de739868cb4fb14ed880a880895f553b7ec34d3078986fa16b1edfced45a7c2d7c7a282432800591df9976b750a4b7924084f82f22bb9b3fd6a240e81fd20e4d64bc37cd0e5a4921db076154bea0c105c68a5243b4947da3c530bab1c7d2d6a9cf49f1c6dd41e9e9a8fee5e11e6329e5c817240151f5318da4bb7912a5ac3f29723c63e2c6c5a6c02293c4e680779636527c081d3ec5644b6727ce4003a423837d32913bd050d0409c0e5e8cd2bc5a1311f0a3d4cef4896728c9349897afafd1264659077271928b913538fe80ad3514c13f922753ef0acbb573653403809bae13adfe18576fd593877e42c5d53bc321179c216253105ee9008a82c39339a8d4dc16da7ebfa77fc81e1c137d08b4a68433944b30ed3073eadaf2c7cf644fb76804b723829aaa5349b2217fcd9f4d1e962922cb20dd21d786d3f52dca682c9462c482e27d4d288611eec05545dc61390321cc44c2bacc24c5226585526d1a478a95043c29698bf44baf7dd9200c2c60330bbdeaf016ce37a72fb0ca78dd6d48ab3edb426c276dc4bedd9555edf69262b686acb33ac1eb09270bbbcf28d2a00eca8c7d62a3153790537ce5cf5d33a16ef6fca54e47930dc21a69e86d0132af4591f3eccb3e704b5f45fe7c9468bf370847ef58f3087e6efc72fc2b65a31e04be5326e623ab7ca9b6ac375a18b9262605d99c1720b81b6694150cb8bac586ef7b26bc4a9c0ba0ad3dd3e1c5541f70a4ce8f9939dc8702042b8edb808fc0c4fa3a8152ecd83a4e571b068d2c7a2eb61def0f449d69596788866eba3d02b568d5400a1c98cdb9049e37d418c1b3b90313a9c3a115445982c785a1cb2ff93cf7de5d4d205621ab1516a019190be846932ba26704226c725135859247f773a2d4c73766b668ef20dbd614b8ec20a650dbd920bf398a2845e99a00cfa280ce12b6d8c302fe24c8c2ecd2404abed1c988ec29f9f70daaa14900a3ad7843a0098f2eb3948f71adac7fa9cf3bbf0a41700c3c3d817292b586d1f355ad66343d486d8765d2bf4acec02ec3dc1ed667b442f5ae78f9a8166ac9fd118c59a09abcf03007e3fb65ac33f87fe00cadfb8343441d48916b02d0c1e143e76004785043545d38b3f4622907aa0da0150b9cc874b358a3e131dc50024f8a78e9227bf583377e5170e9973f22e7fbcc8b41dd7737a5a2e08d2debeb5237d0ddea10584a66bb796ba509fe24d28430950f7a767ee78425504513e05ca344e2830a238ce6960d4c48664ea1c571840e75d084a66e5d1cb90303d0e99b46e01621e945805e0e9d6487ab97dd201fd478ba83d57052440fb14fdc5d893595ceb0d6a4900347e0a4cb0ae0279d3a8bdcd25bbfcdc1d546dd197fca41d1239d8c0b2411061f53887a7424392c0c00018f1c74b140f1e3658d297bbdd65fd35fb4a3f68abd19487162fc41dc2df5d50e0a564416f14baa10df3a0cfeca54c8bb75a740cd5594a63afab8c49351486d7ae57316927ac209791b08761e91f70b93e69faf8e4425dc33ab8092dff6a06d61e084dee51b59758d9235f12be5fdd66f183d3d164a6ab60f6ea5bb991eca7f650abe2eb2a7167e4b2bd1fc071568676ff9cd2d72348abe223d13e6ad851a61db7411030808230c807a18dd2e4d280dfc64a8d74c0d7440df0429773be06b4012668727afdb747171296de2ca8acf5295a1087e96d0d0cf9c6d20631d6ba17544a99b827cde06c4c0b99894e573e15320d5d5eba9cbbbd893a525f5d7c4475bb8b82ec8c09822dd9296dbe17d06f48094141b55073628d322a6cf548dfdb06921d624e4087dc1e367723b96a9d5e8ea87666424c8e7cbff352c107f4ef2cac62ff5c986f0562ea028a4def00aff5a8b7ec4a3756bbc2ecde4af5779be98b7bd4bb25bb9ae8cf413ed77bb4801d05ff92850380ebb193725b7d4c9517cd8e4176bb9ed7367f2b52389fef4020982347651536f36c43964dddbf4c2367c59fbf890c1d86349d9f1dae176ae7d9865f8017c39d30e22073c40b3bd4898f95c7a4d2ac87107a23cb0f0cfea879972569b9d0e381132cef66c4af14f59a67e9b440855bbd770e06561e5dcc0d5cf4e33557530de88d3acd009e38972549841a531389a181e9f1fb46d73bd3e9d77e5c4300994cfbc70187bc03be9dd626a6176ddfe5e2222226f2ad191bfdb2a923cd2966685e22cdc5292e47b7a0d098b23271518e0a8f89c3b782b007dfc1c3d59de02e376758297ec4c163c0bd222e03573bf807b65cf37307d9cf7a65debc0ec2365ca7ea371ff94363e03a588c0c14fbfe0ae8932d10f99c0f601f5b57b632e59c25f742750575d748ceb25a3f21e4d4cbbc13b277df543329f541e706c36cc44e2105dea6f9a225f87a098ead8e8320cf3bc65ad220d80e1cbb0d8f416ca2d0f7f702c1774117149fc2a92bd6b8a80f81c0058ce16fe0c2c78289e90dbe53b35e8b2f7435419ff3089abc65593aa6601cb5a01a04689bdb6b890d12239bc7dbc3c9bbe2b2499c6251b2367e756e548664c13d372db4b98a5584d30168ef6fdf363e19585e043fb780e85bb6d6fa9375409bc5884b5e097a84a8145b38fbbb096a0e96aef3ecda34f011c660d5313d492ba2b1fc8c6c1c7a1077aab15d749e99d318c97f386c933842c530bc395749220498220560845bc3d833a6206a21aa7816075290f2098c94262b8b072c13e0743a235b9e6e00e2d435d4e01a395962e8a8c534173660004434a78089a7643d5533ac9cf3a4e6ac22ea5264ba348843f888b7546594939418311e71f070fd7c3725902392574fb5d31cf7892c6f700a14f28384382e307833b0bf6dc64b7d748be4ea365f97abd5cd39197527ee29b232c20d2c0d25e82feebb95b685bde0cb138c01260fbdce09941a63b0d9d982996ace76a12662bde2bc965e34866830f5a4d7b636dbc8ffa50b5194123d9ae40ac2fe2d3b7918069f23cf07dde4cec4243c3992f08c86ee2b35eef2b5559f12b4d5ec95fa394778bea7ea54df16832763e78f8c7d81ffdd9780612eea0fd23b02c7e7c4fa16eea15d10a98c113589ec84d40116ccac14c055b44b259d7ee46f9a32741c92427c825a3dff8a4dc30e2432fbb32b6a8b9b01b1e8725fefe20e229b375788d7a14ccdcbb505b2eb3b24804e78ff7dad2aaf67c7efcaefcfdf75cf101b3bdd203837a2bd0db2c19ed3ed0fa0896198c21005021a176d40922e4f3359564dab9d9b6b457c8c7ec0dbfd2dd9968940006700cdce109fbe004dab39043988fde3cc2692c3d0e23a3421a4dc885ab0729235adc9f18c1c9426af70f1243ee0721c9e625682061eb2f63e98ff658941097b6cd171ae6d01ba420c07ae0f1ac0c3cba788dce7c1674fe6a46562c17bdcc50ba11642ea9e16f21b748b85faf9a332c76c823f19e7a4b11ad53dda3c3960f7cd3222fa28a97560882a6be65dc4b577fe813f98761e91a542951601c7cb79bdd6ee9285aeb744134286741f17ca52fe270832ec2804f1873ec91086a3abe1906ae0336594350e610a95b36980781205395accc282852a896848353f3b1a4d2354f9732c023b5103a44c202e9205c8548a88abbd6c89484ade915c82fb0a3b05e4195255e1ba5d654094ef31706567a9736263270d754b5569650340c1ab10e3d894f81ce083222a7d19024d9fa2431942af65923f5b3a61cd68d364a303d7a659ab807df3920fab8742ac9224ee825b832be0aea177c6b080323b8f96206f2c2ade520ecff1d2a5bcf25e2a4ca00ee1dcc6e707770de2ba437a105ce01e19d81f476e0d5084966665f6063e7d18f426dec8a908e066b8437d833e8feccc18f9afbb945bb58eee3e2be515f07ae9dc4c7816d4de6a5dc372ebe6f003db09248d9c6bbda5e9d208c8bef7c1521ef7f67bf3464e44cfa72c6d8ce2e9a4cb091fdfddad741ba6ea735fde9fa9a2eb40d11b2ceb61d537953e28b7ee42f5b691c9486d139b2e361bb8a20d4da1029320c2c7b735622e197527f9f8a2df1f319f4a6f461168b00e3af5f3d5c258e89d47941495da0dc66d6e4ef0399fe1df544909ec3b597e01458879353557bdef923cbead4891cc3ad7f4a416536cc89909374cf8dca298a665c229397742d6915c6fb6c8660d11bdfae16d65a23616aab1f3c27ea74813a23724b0b5893fdf72aebebf67e2ba7d4b2e0b3f3ccf4639a76451153606fb0b2e83c4d2b1a4f42c60706b31aa0884c013019cc13b4a405cec6b68668f2800ab27a82031c716089a43eacd18f1e3902a2b5aa6f29cbd93eec43c1a432b2796fcdd0072e42052b4d2b61b2eee4763a44dfd433b01011854f94476b1dcca10d38ba54ec14adc77bb155796a9e0fb936cda0cd1e9cc58fb7f9ad859260036b1298f6990e6ad36b31f2d7890f1e40ae8173dd1513bd1e92cf897866cdc9ef61190acc7c2694e6255617520dbea15312569b8dc12deb9d881c8b18d6222fa11dcc49fb67a68cc62f2918bb2e572ed4058eff18338c8f433d6bd506c8e6834f8c2209a5116c462ddd91c86618a6d349ef65abe0e692b64dc3498c0e57b220d065f6194217f7b701ff7e3d06f394e89597a2e1ae9cef5cb9dcc7a4ce1e5aacd940dcbcb2f20f63ac238f8e0638b48efcb1b00472164bc86819cfe3f313967a180dfd1fa21209471b9d3156f50ac6f3179d37f02751f851964745690ce5c19a4031a3c9206d7331ea4dcb5c6d3e14444be5d128f8ef5e2a47652512dfbf6b84b427eb3b507f1fd637c8a67cc91cc8601cd636d2b5d274e1d312d20795551886d782d07a831ba30e6de44873c9586e2af45d09144d987beaf8c1f1672d9667767da1d4f5a4d2d006044dcacad0d9301824aa6922053bd68691c9de52aade5429ea2b7386a8512d04966488c2596adf2ce262e3ffba800a520d54cd0202bb75cec50a9b4e09c7ff6cc76cfbd4ffd60056ad0a6877ed3f02cc4d35a27c4fa4a664020ee35f02b855fa5fb2b9dadf018ee3cd8744a8748b9af849cf9efc43ab80f105b573a7db607b3b5c8ebaee02d9ac78c16fdc2603365007c6988cc0cb54e826dc09b0ce4767d972162c71eba288bca6305d9c1622c9d30f7d4dcbbdb6c14771fb088ff0f8deb263f683b46c95b113791594517f960346e7707184615246a19d091b33e4a2a4190a21211c222b1fd4bf29c9230766857ed6c9f2ad933d9d68ac3863b5cb6f9a2e8a670c8b7dd7a41c420ec67947d292b4a5bf83619214d1d3da2b5d0c1afcb93f4b634aa42697b76c9f212547fc9cbaacc80dd4f427ef5237c8136ca77cb239e0e0551ae5394fcf916468b51119a4faeb4fcba777b6a06a08c4dd9890885c6336547b290099098d77ee6e687cb11c7e9c6cf27af73cf841d9cf6aced5c09375a06885de401ddc7e26dd9d84b0137de29a780a5f6860c0b4a6eb0e8cbf6a8f63e39afa721cd79128f56d13181a6f9b1d745f1740b5c1a0cd0185cc3945ca67548be7054881cf11e09ac2c1613c63c7ddf74077ac1a9e2f33b5550590376f11a412dd2abb573a8f685f3481db3226652a2e6357e775a0e1c3729552a120cbb509d23e37dff70d219006890dc96f888dc99ab86ca6e9388b312d3d1da998443ee890e3b76449a974a8796634fdf0effa751ffa5d5286a739e58b70c93e0ff70158e30165096043a7dfc91a492b090e1eedef333b3c8445c433c7126e11da1087d8e647d6f86f27382b97bd2c6e720fa738a83203e9f55f366fcf16a66488bf1ffc28ad0a2f6fae6d99133dedf84df23021494d61d98baead2ce6f7272d84775b4a57f060feb0f9e22f9046c843f81396a7f26af4ba366f006b16016704e58bd14816b877fd01d2432c5b69b20d0680560a65902c690b9bbbb1135c5280d5ac338b42943fc90639311d1ce1de215098b235e672cc3c9fc061f1d83bb1bb47d14a54765850dd65d4b55133fe44815f413435c9ba4df82c74deebf33a14347a4f75557bab78622e5b4f7db44cd0ebd74088cbcf7a3a7e5809f61acf57ef1c3db6ba821f71916bec182a14f4984d8679eeb332e0c8947e94f87b53e1b0aad24cd22fb44d1c7705d87945771c2bb7b83b1e45bf2cf9b1be2afea8cd468b4d77f4eae5a03ea8b77273810c28b7a026143febecf3918274d1747d83041f9870b807affcf2973f6991c6c0ae36fc096a851834401872a7e3886a1aad8cad581dec602906eb0e47fd692c46bd5681179e6a8c8b8ad961d5ba71206237ae0d68e5900f8d1c607c32a16417baf8ce2cdc8f0502e9e12c3c3c6cf5629483a8dcd24cd94f3e01a1a8c84a5c7808f3e661221620cab499102cc85625e4ecadd32724542848d29a8d68717a169169db269c301c84ce4b585e0b6f33db009658664c8819fed14167cf3a7efd01358bd8b9d08c5c9879d5f5cc9c7d3c392a3850ce88790f26a039cfdc05485b37b2978e49092e847bdb24a9ff05b6578962696789f75f8ccc17dda55342b5e2f709db2ec09387f8ab1a7639cc4355e452fcee0066d16853680319dfbddf011c0aabc34934b45bb9f354eba5573bb85dd808511acb8653aec5acc7952ce8f2a2590b88bd147d11d5e32c3a392be544acde48bdbd132018364c89461aad7be6ec9f7ee14bf9ff18ffe1324c4c8454f7de56d3d76d5f088ac2dc14b0410f9a02f5670a5b93327bc6165bc4fc256e08903232541215f90d26994b405b3a2f646d731137c0f3a76def4e4144e784bfe052165a48e067d9a9f59504c3125b679fdb33ad803ba0e230793d24f25aba2348ac407a2f76ff7b46662b0f6a5826531524045fe7fcab449a302f2cc092d40dec112d918387f51cdadf39ceeea1615ba3863896882c489c9106971c5813378922d0bff67ca8d1c1fcf93e78d580789f6de15d66089613236def1e8cd1d477e15f3bf99c16abaeb0f4005d6773a74ee88fe61f2cdb2bc632d44562e96fcd7af498c3f135908ac039bfa2061040c82f0a74191d4c03d0b84e24accb56233562504f83b82d08de658417c96619b1dc9c13c467788db4f8f5bfae0d91e20f90590a1bcca437ae113a06f0b717193d508f8ffa7035adc31149114952a19b6afa7ac690f5cfadd4c91e25aad3370d4ae30c336e11d0216cd8ea103ce23e51fe1f7cd5b4a169c702c5c45693fd2a1ce7bfc2b5d7763b2ee0129f36d3e8c49451bc04f92748022989d3d86ad1fc04174a95fff8ed2dda5fb9e526634c135593bbc0f3ef1707076e6780bc079fe036dcd9bfea3f577d548bc3990c5cb194e9b57071b090e76bd62ef90c61442c08bcedd834000aeab09580afe23c5e53d7780276d5a0c760474af107546538b35a80a9276b6eef71c4140301bc1e883c29b3d3f9fba37603f342ce1e7e4f804d28d51d6c75535019ec175355be0b7f0e2dd1d1aebb7f74522fca17c2c7e200e779413a36a9422b567f8bf9d8d4e2c90ab7a7f3e8f9633a4fcab477d180bdda3f41337bcc6519ec92352a09b9d52a3fbc032ca1d464e76001a2857c5daefa766efbd9e3ebc5e6344c08ce81a8ced0b5746bc9593ba0405adace5ec54ec510ea368c38bf2f673c4655856c2a1c5fd2cc6a61b228e31a92450695cdae85a281cc448585f4e2dbd98ed582e606eb25df9acf57eefeb500037eda579c24608f6f4acf6744e679cf36604f90d0937541d706e262333011c8b0ddf671ba1fe693a649b53a2978a794b4353e7de9840d83a7d13b2cc543cc38a11a50fd75d55d0c119fb7d7790d37a99f333b2e40de6eab10e092c2fa119c1ea4ac5efbaa8bdb17f40569bd877d320a50d014fd0a095e1b940f857b09ea15002d605708ff2d4b7b0609c9352a2eb72bd66ff459c3df08c1135f8b9f604ccc15956c60cc054496441c8e725de84da221af08733753480cfbc08d13c42db674d2314be78e0d1435ffb68044453f1300868d620ab3cc0a494fd735f7e8d4c7fb3cf74e23dd5f8ffe6e47553c6562555f28dea8c6215ce01167f78e73a1c821316e60ab03d79b64b10583713b7b3776435062253c82ea6458cd2cfd2ec5b76b6dc1fc4beb1d699d751febf7abaf7ae17ae8bf7814f741b44b8ac3e8949487bd746dc958087b055b228bc5eecdbad8f76ab3cd70d72f942269d165b131395e41781a90606024c3b65c9db264aa883e4ad64d7474f3c98c7a32f6b0a8db2833391548daaecd31919adcef3d7e433fecaafc6a8c1aba144325fdf1b2dcfa94868c6f3e5c454133f6c009c4b4609c967d6941aef575737d1b0070c0df3050831eab558053c3c856e3c2b054116b62ab8c44049968af5bc47b63994279e4e704736bf1d6d9d9a196192e106f35991c0ba74c0c55ed48d955e07d9165e062f1529cc6d5b48d381441d09f67035560db4353bea60ec641861985af1d2077c2b21efb9b0d6a4d2ae5d886726d44713b99e709b565c9879d911f0f751e2761e402caf9c4d1a96f395a58d9e235a9f03cc8d812e59b6686adc58b81cc787538ce80fd1fa59d6b3204fce5136dc0fa2c39ac4d56493e33e61a9861e33d911125e8c6d146efdcfb0e17b512e873e4feb8ae9a8e40639063d1f00eefcfd5027bc4815b688bf888183ff986cc857ab40e57c2ad9a0178310408b62751509fb921e340bc4f625382affc8ae74258c30a0461990d8a60128905191e867b0599e2176f1791275bafb721e5980a12ccfc27688246601b51089d527fe7f75f0db3a64c8890b048c5ec40cae6ac8589458a6c5e6c34e8a7e9b7e691ac281fae4ca114884ba0202ffa205fe3590e29f4af83ced73068486c363d39e37defcc68f45bf70efb0df33c030b78f9d3306b27b71650178db31216d10eed01828f48b93bfc82f3cb48ddb5056b6c17c8a49f9c83131b3a4752df7b1cc13110d66b19e8d9d0ed9a23b4da5f46c21998b939f5c0d449f04028794e75c79139d3ee10768e44f4ae3bec42090fd0a87e03aaef1d390062626e1535719a856d42a771835b3095488098026a40173a33f93d6081f5f126f9457a1a37530cd96093d0ed921f7c20ef759e5220c8d9d841771fc69ae42fc1d49f2e79cc5f69f53f9b5bef017ef7d56b0cc31a6ff991a08ebc8911b2ac5ef68200f3b63b71138dd8c6ee1d5dde975a9e8002bebf52d4adc7e9852e5803f71de6a952a9f5ba3e813d7ef782c8f246b68cba9d490459ee5ee190f225e01e586e3427d51e9b835091cb968ce45a0c5aed59f5823ba6534294f3aab622a3aef6f6b00ebf0e945625abea830149a911cc836fdec2b1dba1782d256e830565998d4aaa1918a4dc00feee0d5c7e50882b9c56b5995f46cba0c89b860b6a4fa3a164001bc0f131c0d6efb0ecc8d9301c5024a408e11c0065f2a1035cc1ed189b0818ea6b786718573e0ccd973804cae0cb2e5384fbfa382db85faea9f30c7aa6a11e2d6929427f6c1e9e67b18ba92fa5206b8c96e7186cdebabf352999bc18d1bd87c85ba5e8ebbc96e7b074356a997d281dbd9ed67987cadba5682b91ddc7c02cb57abf3f25937c94d2e9864b5fa523eba99dd7e0ac3d6515f05f07030bbfd0c8baf55cfca013783b30877286cbe5a5dad980ba3349bebf3e0a2420f03b382f0389deb8d2b69336b9390d8d45d1e0533308c5b2fe84871cd2dec27e5fae831800a54e0fa1b1989af59f5ec9f330012d8c7942e2e33c4ddb325fb534ec32dc19d18f8b0b6b8455540131361e2778106706841650a82a5e0d4b4f99c17664888d5a1d8b99c3122b8b38417ec1084f0874d339000a1c59bc6817d853d73a146a46012a0ebb64a622aa3750f0e1a1a80f523b0526cae0e69e15d91ca1782df74958a71bd7bf3aa5786a7f56ed8b981d4a6dfd8c7d895f95447ec1660b53044c7556218a8b7a808431e0dcd0adc8b59f21e6dff5814595e05cb9c963d38dce6f0db04a2634d991536a68d15cc3ee64b6577c41f5c95d27cad63bf943ee37f9520dc539cded8d66353ff216b0ff6a1c6fdf8034f5bba855ae43bd264c0cbe4871d834c032dccd050cd19add082056b492242d51835c0da02acfe86f8af3a4c89d041d9b011d957a09261ebc0e8356d7702db3e844d3a576842417001bede3cae6cd49d3b558cc5be3f3da95f7634dfd61038e0331e1d7e46b8c5e220403abb8819eb2b39c378dfefd727e2896996e5de9c6a378cdd7fc695d676b8b5f404164fd754946484cedb21e36e73fa701bb74fa47726840670235602c5be5d3969607593142cc792d17bef419b64f573ff645b3cfb14319d2542af6908b371f958d0cff0661de4f9f7295630cc8344cacea6a229db5560c875cea6cca6e3d3bf94cac1f30239cf0c59b9eebe5a04d230377eb87cf0d1c9e7d699d39a1045935508ca3611fd3712cd2b1cf4447164cd0c07d22c3f26e65868f04506a862f49e1d3c03dcbb83f53411cf1f1ea890f50def4167a0c64b8d6802f740a8616ebd1a6ff9150ff102895065e7afc6e8068592b11019dcf7525a03d44f55ad29c655a61d9288c3ded98d98f032d6484b97e6466914ee9f5bcbfd77ed062a744a5c7930a77c1969c303d51504784fc7d9bc21f5258c7435e5ee906a5d2add33cbef7c0d5a9ff1e8425db34a08e67154ffe4f27cdc2022b25f724a6ee1b8df3fa11e3dccb82d284e1828a717bb9ad9c53e7c134516b81a7fb58f69e2357f3a07175f92b1213ed8a8389f5eda2751e2a317c503b858f4ca1777a843a862c0e3fcf82d456381bdab1601ae8d4330c5da85dab67f3c446ca7b8cd853ee1ea1b63663c78dea1d7a7f99f55e211eb633451c12169c4685a782b0aec087ea52158c036f0f88421b1a03f0636341a41b26dba174554703ef42f885edac08d51b6ed5e60c87a895facbc922110817533f0025ed83eb8d06c6680c4590b852ca2ea89354d7010f00ff51413f35a30cda38bc6eaa107173965727a1500e4c88a0e16ea1388adde6bd70a8348d5b03a0f4e3b406ad7c85ef9a99bb7dc36b06984a5c550762130607b69e27b78b51a10aea73d2bc6da1b07bff659cc10407730446b7a059cde033aab0fc258200eda4a0749824964ba0b049c4a5a44b1909f2333bc848f774dde597243edfed8a79320f582a469ee220d0d1d74a49ce8a4fe9880e497dbe33503d6afec195eea38550ab6ecdcab4d94ea4854354bf6265b6e29a54c5206880cd90bef0b679204e1c1245a1fbaf2fc90230a172b5188f64c171899ca49d1074cf9814b0d4f6cb8e01c7104ee284e62c862ca03e70a0c6b39eef53a727524ebc8d6112e1a6c3428d1b064612cd925fba45a242a7e787139f1bae999a22f9ba541b8c107322cc441b304172b306c32504055191b94f002831c2137dc75d3d679c6df755d87b18e29ab83caeab0b23aaeac8e2cab636bf6560717d1db1d3618b78ea33ce02156b0bbe546b7c07cb6349cac1d443c59c3039b2666846410812d304b4d5c69eab2a447171f5de24451e5244a19191342f094c903c40d0db63056a6bed8f034f990a60549439a20d1d4022d2a406cd55b6badcd51a9a8de726f3f29c795765d145c4d9336a52579468d1163a06c69e9e1c6083060e89d31543fa0d85cf5502750e58a112a3dac9c6842660c4e82c22686a516bab0a8387d3fc8275953e24c8182a50c92315fbc383180288a60a9f2c389ca9c4bf1ebc56449d32413289809549645c151a2e02b79c30e516ea420910316145509424ca1a1a2618c951a68521078d2a829226b8a520d628600d2c6cc1c344d00d18ae28b14cb8369c614c54c1049941942a5881f4af507108c45085492a517a282d8b9f6f5e26a6a9a747a8a32e2ba5d8e48071ea54b04d7540d9381c49344a98917a628994941105d9044c9e189993979640c4a40f9de7bbfebb17befebf5058afd02d5346973ee59ad4e63b90b15bd31279e17d6f05ea92f585e525db8dc4a91642edf7befbd2ec37bc358aed98a01aad2d4610edffba240c828b54d783401d284c895e18ebb61e88d0b4eac0450518ae082c70d1dac149a94a27cc9bab2868734368627ce9ba85c9b1bc40c779cfd6b5f2f26db6552ba4c4b9789e93235f9df3bc3dd8be3e1890d38f86032d5e489681d0d9d3b3c1471553565054c9430f6c57987b921a28ce9a29c49111ee18a3284122a608c102d9c19864013840f5074f0ec911a589da96262eb2a061984904b58f1e21424093a504ac8a96ea97bafeba65e4ab500f5d6caa923550897b7da3d354d46e15bd3b04f354dfe28732944971d1d519c70b821c8d45b28a6bc8522cb8c2e4d7ce9d251e60d113998ebba1f368c7f10e90fe0ecc024082f33a891e1cb6dce129cc40a12048839505b71a044212f00c58a5697114ae059c2cb104fb0e0009e2194b4c83083d21214b8880e5ade1ce126c9941e5d6ca84a7c5ec490e64d191f63947e284d224c4046a06041c7ed081eec4c11ad971e7bd0382111c4152e45e2c08a74016d2962c647aac81658112e748de99202c48b17d12291e187ada81e3ab2eca0a5eed84a7ad244151d6630628ad2122d60a8de8ec048d690198c648121b3651aedb1fa61c5fe0882f61112312801e7a908229a442bf556881be41b2b64ee10c1014b9e293b29a29662549e5e50565eee382167071d98a498116d78c7883c4bdcb8c981ca667da0f2a26efeeabad70b8b53d761f0c3ef16875fcf6aec01373c30d05b17d337bf0419ba747842c6894d8c681dd31aec0628334b58a5412287684359ecabc76acc2b109e0433fefade4f9743c46e73479b3ad9b59f424569f231c6af419a23436cd0414d1b26a275c05b29195e782b15a5c82bc9062d2f50b8100488520c148916cf7a537fee769e5bd780a86b5f0de6d68d42e85a6e9a744b930e033fd00365d3300d87f952394ed66e6179eb990555fbf064cf3bbbe79c73ce39bb29f54abd3e0d852250e7b4d7daea42d866f230be56fa22042d112206186f6b28848c85b4f3829222ce349146089658b96c2951ba6265cc535703def8e8dec400b290c98c28d19b1c5d977be7154708278444984f2fbd3b491ea55527c9728649ef514a9b6a659a73c9b60d5b698b34f2a067330eae960b65744de7636d03a8709119020d569d21a056963560885ced1005430b0c4ccbca478b8a8f953ade287d1cfbdefbb214863c4222a23449679452a732cf7616acd9d697ad20c5948624296d4e244a937e020ae83b5101e7dd4b980c19d366133919f22672610894b6c24cba5db1fcbcbcc501d293d624185ea083e60c95325049d4be6addf0b56a29f9aa42ac2e7e2003e507132b2725628d517068d661ece52d4e93f75edee2d0f0e35017385d2f6f70887c8b97373862be9f648c2c5b54af5eafbeb668d48d248ffdf4d5e914ada960fd466b2a5cb7a2359b87abc9569d46d1275a73a2b5a67aa335294e517df5a6a2b5db39e6bcbbdc6c2b5ab321be6e1ddf300a75dbfd446b72ea8ad6a658bdb9a82817adc925275a5b80104f534f51b436896043f564456d4cd5463b8bd6a47845959aaad25771ecad7a35bbcae0611963b628ddfba991c20c20c73ee37a901f3981b98471cecd1be7dc8c9a4f41e1a4e26a1b2c9c53389c565b5759269430a367d99344af3a2cbcd1349d6a88844314286d6670324da5a51a29d42001c83927e13acc4190ef047b8d19383e7f3990cfc0f1e39e5ff7b2dbecd66b504fa10609422474a28fd740f20933a54dcf8010093e1ee441a10a228e241104264a9b0e047613a54dcfa050fbf524d1a36005a4c8a3731ff7268c849777b0199efbb884cdf866fc84ad4469d37d4221eab0d07b89d2a67b60089436fd052251daf40e14a221120e6594369d0b6717b4a05087484494363d495cc12789f6ed6aef1af2889fd63a6f9fb739d5b5adb36aa5ab6e46515abb578209284ca7699b4f5349c2aa8b25505abfbc3dcbbc1738450e447af92b54a1732e9c617046284402e7d871a882e79c28456f7eaf238a843bc78e6385ce397f852b74ce8138a29030e14c19c2267ace398e15c2153a57618517e75c37d11af6f676a235aea13403acae28ad912615a5753bf56cfab44d254a6ba41b94d6a01009b80b674224705d4844698de494d64d603b515a3bfdf411f7d31502034613fc306a179960e84fa8ded74523e7d89bc69087c10f3bc99ccba7b56bf2f6e109a0800b4d30f4373cc1daf01bea0e2237b0a2a2750d64d714adb5d662af32acd6bacc6f44b67efd86ea9e14df8fac1149794196d58fe845073990b8b95085490e54b1228008d12488205578287a9307ce0ea0fa82a832e00d9aef0d9b14a112476e48e2a94c112a78c42417cc702141226a86214f8834ccd3f6cd8656bcbcb971e2c7a19a8337675ba123433f527783840f0d690c7df558be99d66ef8a2dd8cf92675184ff2f8e2450746dc6cf931cb9b063337597e9cfdac5d89787dabf55a6b12d4125170edb1d2f4430b6d2aaa0e15a9344d8478110657555b2b1d420f1ead1cc27cd10a225600007913e6cb99a52c59625dc0d73756bedaaf6f82f80a136bb5d6d637505f6b97af57acd556c7f57b797323c54d127b65830d36e80da8329009c99a51d326aedc009a863cca8646d9e4d1dd9005768a43234a9b9688178e1eb4531ee5cde82ae0a9160c4fe94deb85a73ea401754c4316d3665f346d624ac371682e11c7872d4fc8c9a106a1383fdc6e9853a505220a186ae9c6c7e797b736796edf274fca183705961dd4dca8947864890e40338318a920608430234a9775d1062f1d89d6e46d060c6a446b52c4a274a9012aa389ef913ce6db0b181d30e5faf2d6c6ce8ff256cb6de6d41bca271913c124e696c4151e27b2ec5519aa396de4984942745fdeda68fd38ab489ca8e3c412efc902647f7cc194b52c53c8da8b1ea5957df1d38310baa8dd9b86f1c5cfe9b219ab9ebbc8c5579711c91ac69c729e74d67064f1d369289df8f9f24b131fd84c79238410714469c4d4115771b22461078ae8e2dbdfa8590ce12599bebcb5c9fa7176c3e3c9210a820ca43bb9dd9b5346cf907a567d0334ba68723e637e09213001051b37ba880b6584483dab23b4073d738271887b280fe5c8e3f7b5ba2763d17dfb5e97b02bc72f562b96366b839767ea19b22e5fd36b10fd0c53f8f9d9306421116693996faf41f41da2a0b6a030645b2fbd8ef11cbb06beb71dcfee0edb27feeee538ffb1ddc6fc4aa1b42902b719a20ee0e5ad8d933647a857a0dcb3a1efa64053e84ac7917b2674c30ffa2fbdb523c9c8f3ed33bae6627b0950df1e821fc4761a6ebebfdd4617c95bd6926fbfd145b62be72c01df4e3483b4f8f6cefaf163077f0483927c39ab1e3ae15c46b9b0021d01b1ea196dcaea512555c6b45e190b28ba662173c2b975a396d11a171a511b52afb5cef9ea7dad1ec81badc96ad5fa7abfd65a431632fa8288c25eded82861bb32598d13a96585e1d336421749a217120559de96c87aa097b725aa9eb2e1e129750f471a433fff45571e898888a64f974179fad45de230775970060e511cb2fcfa17bcbcb199fafaf2c686cb8f43f409f60b620c72dce5eaadb7fb0aebb24142dd9318638c31eeba5707ce7fe1aeebbe9e593b2fec883a073ba28e31e793c338c43db318fbc4d886a38d8a6fd775afce5eec95c31887f3895ee7c07abdd6bef7de6a6f535ab1f74cf67cd130420e557823645cb34f8cbdd6a0da05e15a7307c93ef8cbf5b301323e5fe149a227291015c21c11c5e107fd7846d784b67a7c7513ba26245647e1e9abdb905ae8d4f91af4554b9d255f314789b4d021525dc232f0f98a10c7e72a422119400e73204f014af106819ec77c60164c014af163778a419f25ea594581d2aac3c01b9456dda7fa0f5802a5d53a83d2aaad445f12d73e5c9e1ca23e1dd329faccb3c4b5035be308861d810b559c89698d07e73c7a6071c7dc234699de238b31979f4b99657bf228fd06ada9a0b5cfa94f265a6ba2b5205a4be1403e4ea5497dda421c3db350b6824e7cbcc11a47f3a967d467d853017e60cc558053f4260c09a642b6a0fe812c863078ea2b2603a87b5747ee49a7d38dae11a0c90921a71e8e08da6284699a20b71801f2d451a0351e51d63cf589446b1d8cb983f6b183a731db95db1b7684c403960252ccabab40b6a0cda3878b722e6094513cf5154c06fc3c17be845ee107fd2fec37ba36735cf950bb40a43e97b89efa24928328a59e50f214e8a9549d2deaf3e985bbce250c49851b355601dea08eaf8eec04e61f386de054ea19f5093a51c222cc63a0521661e10d4afbf91b7a32ec5ee752b8ce8520c8b718dc1c99eb0a4831e62748fa8ac423c861eecd648c9f18f879bd21d31f673324a50991f0b90a5711aa100b25a555972228450bc24029a698621010e8790af0c78390606e61483c5848b2d9fcc781602e6135ac80143f17bd0f9c4b94461d06fa803fe0b4511a756f2a3df501a508133d300976847483d2689c3c4346cfa8c593a79784dbe7c99c5758986470450e941636800d4fc21aea4c915227e84624050d564d7a5459d3990bb454d1ca818b962b587ce020db16244e645b66c8d2218a2d34500de980da8590ba687a83b96756b5f0ca66b3e590522d80e1a76bd0b59838a5c874d59cc4e9465c2b8aa454d5d64f1f4146a5cd23ca18fb299bf8a9c5071a7efebc7ad9f939abe797522d50f25216c9af3f23e2c896942d3ee871d2803c3b740c69b2e40c197106a115039494223a94ca6434699220c224cb134ac4e997852c6b2530154162899c35554dc4e932594b011b198650355165a94b9ceeb296d4031163c600f9e2c4081e71fa4cd66ecc60a489234174a8d2244e31cad634f962d4558273adde4e711e655fbb2819e3549543111f5dc41ab35c59be0d3239902f3d6350abf238bffdab2845e9d98766d6dab0c6fcae0ffb640bea144bf65a76cfea33a6e06f5e0d1bf8b1a300bf0c6bd8c8dfd3c5f4fe6a6b57a78e53984ffb935d97525e16de218d92a54b9fb48d2787c669adedaa5cb44e6f6169f5ac6265f5ac621f3babc3e1ad6e611fcc201ff10a1f65d76338e6d855a870ec2b402969f8ba0a3006a600c3ced6d186e55ed5dab09fdd66afdee5ae5bd359bdc67ad35a6b6dedac356b3a8b32c9eca3ccaeb9c2ba5a9375b526cb5e615df59acea24cbda6af7acd0d4aa34ebd5738e60c4326cb524ea6c9446979f40ffcbb6c39cf96e3bc837df072ce6f00b9e792fae7927e55b27fe71c5098c351f6138ef9b34fd875f706c518875426cb5962ce3bdce1ce47d9e3ce311766d9827e6005629a92ce883aabd7ccdb4f071de1494f7af2030253381098c2f6fe524a4cd37b140b0b0beb07ebe7e7e76762e58945e4eb8733666011f9f119583f33666011f9f999f12382d35a68f506782935e6e989e07a00bc940a93e63dfa59ae5a876eb5d69cd145359c583f6ea3af8f3fe10dc789158eedd8a5aca5f0317b75d981526275ddfd6c504f014a4993e1751976e8321c659a0ad26f98fb3867bd7e8727c0c2fae338e4b82e3ce1c7abfbf88489f0139e60dd7ef5f97c2c57dd271c27d6df7aaf848570c366b3e9f038ce1bfbabcf9e738e73093be2fcded88f347efe0d4d107b2e3cc17a97af8f17cc3f41274a2e0639f54c01204fe1d227c43de338b7e018e39c04792940197bdbb90f7841fc13b40ee417f471ee031f0f02794481790f17a37c6e4481401f10f681392663e158585858583e0d74c5eabfc754513f7b2945b5f4322a01eb5c388a601d3601ebdccb31f6ea2725d397571808d8bdf004ee7bebd8bebce6d45fe027e585c127e5050502f613da9d862f8e0bdd56f0d682221f315b4ff4524accedb5bebe9498a522fcf4b73bffd52d9f6b2b58bae8e86afd7c9a9e69cd67740804be689af50b8e80d433eb52639a9e012fa5a8b25e46cfacd3ee6e2a2bc7ddee17cceffdb93ff7fef4cf8d00ece7fa48f4edd5e9edb6c1a9f02ff7248cfa055fe15841f0fc7ad3eb344cd28b4e7f5d7fd170acef8129707bbabea91b3577eb22cf279d3e369739a937bf397d42cda8ae01f9f41b86329e64eb23d1db8aed3768ff822c5e7651dfbfe0d0537054c153eff5514adb3b9808f4bdfad7e7d3bd35e7bdbaf3e67cc45cfbf4e6c7d5ae01f99d1ca5dd3e230bec0d250c04ea81d3e9e73e3e6120485a734ab36e69e8e333faf8e87302f61ff77c8479d87f3ad8f50fe4b014d5d47bef3996afef719ee7795d37cedb7beecdaf0b611666dfb3f631f8a4e4515ecdaead5907235df7c6157c181c55f0d8bbfbd73d70ab95bada5642d65fef54803fa735224ab35eff7ab7be77b16656d3ae57d804bef79c88d68c9a76ddcaa9bf5e38b2e0fb3ed95d780276ce5ff37bbdbc709c38c45e57da3e230b1e3ff6f9646572208fb07c68bf66c164168bd9e8b6d96c361bfd7d43482ecc45c910c925b7e09955a1f484f9ea728b15c1eff4227b1a32291a1f8dcfdefa36ea5b08c86f975ae6a45d6dd42168c41660240c1b1a31216c541b55527d6b7c3822a9c0d0980120cc1a281a331bd66d369b0d4b8560f17ea61f65208000b010c20000b20b56a888a50802ca3f3eb0cf7b7598bbb6d29eb245bb13ea46dd2d6b3494923641f92bcca4ee4e82ba639cf3287bba822787bc71baf5fb59cfb65a1bc67a36258fdf57675173ac5a6bbfd63c9e9435ece3756e042bfeeb7dc2c5f686e3f5399d0be7f5e9f78439441ee904b10dfad3a716bdba6ef23897be12d51908f39d609741451ce25841949456eb57eb5793d4c945012b7230e48a2b941e30330bcf2c7a557f74178e35e4b15e9733ce2587e3ab4bdacbdb5fe1681d7fdc57ffa8e5acbddfdf5b433a43ad75566b6dad561656ab09625fa9d687a5c2848900fd32c6f96b92478c732c16b31f932da8db2362f8cb0b3012664d2c968397bf5a290d1bf258eb37a35c78c3bc2657fcdd2b93f5d05eaf2c7fd567a730df528cc7a4cffed12579c438ccde313b99f6972f77ebbd39d0f8de5a6b82ef6b2d73c14abfcfc72c6b63e7f5863cd6a7345fa7b2977de198bd1b96f0d75fde3011fc2f3756f9f80b6b067bf6ecb1618544e81ba2c086f3a99bc9d44f29a41497a6b72e27539728dae54a37047e7ae1be70bd565a47ab4e9d3a5d4ff090391153354c069e01e6a813f7ded72bcf5673407217d65f0a8688172a2c201982a78d1416f126118499365d412481c41df186b258d79251c7a6c88830955de7030bf6c105fb0823237297e0faf9f9fa9252385471d5fbaadcad5d281056556a959abdfb892c6cf4c456a6b13144f7ef7dbd9074dd1a78dc1a80dc1a88dc1a90dc1a96dc4b645f1673d6da98b52f7054b66a15dbbdb49db81f276bef8bdb9a46779a32d0b2858bd224bef7bb41cc2fb97b5a61b2d27433ee38cc71615e50aca05c41c982b205852b8a2d8a928581bb1777c30b73d6da97a53170659c202933448e932333a8a1a185166c3b64014283133259baf4bcf16287c814135d3e39729454d97952e68d8f7e72f524eb75efbdf7f5a4c6a27889dd2a89a5a3494c58c88262073743aa0c5902cc9310d22d0b0e4ed010e7a6e312b9f96cf101828b7e52b26b0e9a1684a009f3820d5709f0b1e4c70d32d8a85c9154963e0764b1e1ad7d7d16881a458e26505ea25893438a1261921c20141245cc953a46e6f870dc71f5d6ebc255d36416decaef0297cc29f7e389fb418562ee8b963b45dd5c2728272c4e5c9cc2385539a571c272d2dae18c10300c8104092e51a24de2ed142004309187cc115a6fdc50055da178b8a0b857a0ae6091dd9d7207c88e2917c2c85044bc5b2c10d1049a2d3a808c41224b8fa02c4cb7ca7cefbdaf98e79cdbfe3a0ba92c3a2aec2adfdbd599678ad2a4c5dcbd79b8fe5ef71d7f7d0806f1ba064457cf8dd2e45b8ba198b2093153a68851f3e3ce9734533dc069f232664a0f23cc4005b1302871c970040d9a24d04885e92184822d3c04a1468b942953ba224cf5da48f2e329dd7024285b48af5502bd6ce9369cb3ebeeee6eae7ec9574a29a5a46dd00050d2a477bb3ff5592ba54d6ded9e738663fb77fb77067f4277b7d79efd28a594b2b250e78f5d9367b7ec5e3e1e1e59bdbb7b8a734f1e7f9e0299e2808e5eb0f54c3eed5e06ca18f2e79eaf501cf7d28bf7d3f1f43cf3fc741f9a11bdc1414e51c9aa128a982e4739caad9f2e47c935278fe97309ce919f9348110b7b76f46067c7ad05719c4c1d38e1830d15266e7af8411c67139e1fa7d3d3cf39a7cf9b113fe738c749f5735a5dfd9c33eb671c363fed0413248c12a8199814218e93cb050b67ae14015385e68838b68debc7565a6a22ddb485c4cf5ef2938910354a4b4059f962471cdbc9070a2a34ecc1b2840f32dcf9b1a36e0d801fbfc72eb51ee32d2e8f3d533de660382fe0472e0e149c3459017fe124f9eb167674a7de90a96f9c3019ad31140f400b87efc693eaf1ec62b8fa49c3543597eefa1ef1f491f2f8eeabef76baa465e4f5232572d463c818abadaa224dc49132c9b14db520070a2855cc88236db2f32375fafe9162f9a6372b8e94cb776fd5f9760d885e746d8aed4847783fcaaa14dee0b005278a282822e228ada4a6f8f0660d19294ec688a3bc7ae27f945ba3ac334aae6f1b8f6f9f4d557c7e9c4bbe7d9c4edf2ec7f9b4c18f33aa3e97714e7d4faa59f5ed73cb851f679d6f67735543d417246a38238e934b3e91840b134ad8a97a228e6dabf3632be51f7be9bb892c8963337db738f6926fef9b94ef1f9bcb07c0861ec420c14417132ae2d853df4838c5a0f4b485440f9d1fdbea1b878f053c645942e6082c4d4a22c0525a224798237876883de7e99b427dff48a3beff5c8eb1ff5c8e2c58fc38f49f4b3755fef3fa7976a3e63f277af8cf0928fef3fe39ebf7f3eaf1ecebbf60df8c03f99e63d891d732991b21ff02ffe51dece845e5c8949a267f89a9c9e9f6d4b4365fbef3ce39d8518745ec1676843387f59c5fd811876551db8e6e3dc0db2c1ede625934b3d96c39b66657356d76159b382f9e7a2be55156f5d3653fafbebf09f332f3c4f3a7a482f1f527be922fbf3e411938365481b2c388963138a4c1e3c30d6b6a3023478f0f2017648c6e82c8531792c954e4c9d183142b2626551c122e2955b458b909b385e5f323d324766080f1ac91794f00c08eea0cfaf1c345d06479eab2ac270815f302239228e2a9af903fce306111e3664c901f1660a40cd61e3750ec88214e53d06de108ada75ea9abb0f7f5cad465d4bd033a968e24f1c306a629626da1079e3c3ae881018f48e1c819e2596b53780c765427137164310e5511a594a3e4f2ed729461bed57c7b6dc750be5d7635e6db9dc9b7cf96b0f3ed3098e0e1db8d58f85663f5ed41b0a3a6c293e65579cc22a07a46eaa9e7d1041adc534aa9931e236e4b77dad8e08814852b46302d998941c91a91be798a278aa734043b256fa08b466935a3d61fd8111d639fe5a8c1f7037e7c51cbb90937df2eebe17b4d13df6c9ebe9bd0f2ed3eb0a386214b32f203172f5a63aa6e98a2b1c38a1b1ccaece0030c5a7aa0dc6f48962161a483a70efb210b8b6fff78dcca631679558b12499efa2bc89938dd05c254806fc7b0a30693394995c609c53113ba68be6fa06b21f4d114356172b8ea51c74a46f1e4871e4f6f828099e2c4cd136be562c8a34c362382f1f296e64ef8d4ad3cf3429e95960493235a24bbf3ccf9760fbae623f6fc5acb3450e82219434a2579fcbe58fda2043c45a2054f29be2f4c0f61042429ac331fe3fb6a006b3ce9812ca58e48f352ea88a61f3d257eba062fa58e78f2ae7ebae41c7b0d6559f9eac5f5389659bca67e3a92acbdb8a4069facbda660e41b91c44e5bef35f56c3a75889770e551853d4f5dc7d350e8ba74faf1a5c5f5d35f5a5db4f5aad3452ffa7d37356dba6d93a98fb8e93da7d4ebae34beafdecbab703ad0de2c16d046c9c91e35dd7b4dd11a4e2269d39f50f5f0d3abfcf4f175f5af295a932295d5bf70936cb155821da460f97003112cf818418c1d305c323ca174459c3ef1748fc90bd345e3acd563eada5473e53575d11227efc983f2b04cf76e5df4f3d33d2e5d24a5aca27eba37d545b09fee5175ed13a7d79e4d3c7e4e9f4eadf4d440a284dc864cc5f0d37da888fcf4cfaac5fc749fabda357d66592a3fddc7a7cf2d0eeba77f6e7efae4bae1a7fb846d7e3abe217bf62eabdbc24d5394369dc3ea222e8bdbe2b2b82c2e8bcbe2b6382e6ce3b2f0d26c9a4dd3693ecd284b85c888e8769dee938d8aaa5d5d34da256bb34ad6666d4a4b4cef3f5a9b7512e3cdaf41274248f265b44df40991a0b2147dbc044a9358a2143f1fe515093efe854248300943f209853a4becada6fa40225dc4446bd8a99b64d965236e72aa9fcdf44d4e4f51b889d2e69bec3fdaa89f6ea3eccd4e4db75431a060636b888c6182be45a06f4237700891314c40436245606e094d1b0fac6f5062bd0d88121025003839d32307228aa0719271b8e060e4872e20748a282d11599ade3cba48683221c14dd3711376c24fd3719477e535b8c593435ead7bc0531f815eca2f2e248dd2aba73ec66a488d3425b7537947e34fd68f8c41245b50a79e656d0349a30e00000405fde8f3faf1f5a34f8c1ff9f5637d594317f689ec3fba78ea48334aa06e028542f513cbda7529abdd5083a6b5db50362d455107d6afcb9ea2b4760bf68dd266df4430640d6329c205cd1e2f647810db8d644d0262a4eca012d3f5c31db1674432467ffbec32c9e3ecdb673246fd76fb52cacc969f338a25d710d7196d0a444a29a574ec2f658e1cd6b4a086853b398010a5cf642dc8698f0b576f60a0b2224af76020cc9f445bdf1dd53e6befa8c6d2f93b77d10c85aa8593db29953ce6ef2c63acf87622ea3c38effaf3bc1d86918c51bdfd650cebed00901cf418e942c6981dd5b76f37e2db47a16ff73e10bb07be9c82d5b9e904bb047da869d23d5083a649e7c01d386c36117b07eea051d844ec5ea66d55d4b5ceb1c5c20c956ff7ae7d62fbac26a9da5f38fdcf4b29334cdf1ec2d4ea2221fc5f5b697df755577db3f9f69cdb65edde3eebaeea2229932d660f2cbadc40b64d9c608b9c1b35285b4c1c217892f802856460249a24debe6be8b2c5f4b6217713543cd83e49ecc00e834232300e19a509c91092f172cfbd2ec4a0900c21191d0e5988714829d02217f64db28dc93c3d2de1a74bd04221e9b4d1338a42cfe853cfe4fbf59147d992096412991c95d007b49f4e1e329cf43814925fa91351e740e91ce6309e36ec4fc75f9dec3e238af50c4ef61f65329f113dd93912cf9933a5499f4718031d01b9f54c7ed2cff71f3391a44997de4b2d7286fc0cc7bcf523d15b3181c7083077ec3cf990544874e0b2b3a3871da888524ad9840f524a9ad8d2f3c4264a0f41d62e8095468b920e3d5cc022522d9e27a767d4357ee476a19e35b08320bfc3af4a96bdd4f243d7736ed435aadf1f99bac702f419068f60801fc82a58c3067e15d205751bb27f0a820d334d93a9cb1fe554082374d1d7b317ac7a269fd61f5b864cf11246893265a0642862092171608873844b0c4154901e4a78c0535564861e940429a28d0f385944ea9268ce39bd5b822f7a7695e4e9560af7acf11737516af6b01163a445932f3a66e09a214f1d5183a7eea1144315858e0e30f418424712142932559e90e96265844853bc942a93f5764b962a43f5d45f8c444f892e8fec4f441d067523595b41c6ca0816c6bcd1618f483d0459c3c0871f8e5c41e2e34a0c22b541485a030a82adb8c0e7277f414e4127310702599822502c680538c50f4ce11954e117705e012906b9515f00c27c4517048305cd2e05ebd861419dd779decb7b75dd8ad78aeee5c4c7613e1ee4d37521081229c88ddaf301a5c821c5dc8b813f17ac80147fdceb18f8e329c0cf5580d981e6530a58500528c5d8e7d9a88380603e3ff99322eecf8d87612956585a53613583d680bc3a0a37680de6d5c7ab97406b3fee25841be859b5ba44ae93154e4127d9695eb1443d0f0cf21f30e61fb8c27dc0ec40e01461a00a2a605c97653e5ffd3519502905d234fca09ff657023455a72120d15aad3e5960165f89beb2902d2abd58b6a895f309034a0169855bc7806c511b2693f8ea19980ca88e43dcf98c0bc4ea2854b7511d0750f5a934976a87b14b1812901b750704e2ea5dfb48f415834e56f8d7a0093dab2b1c062a6571453883d2ea0cd9a252ea983abeed2bfc0449144c01293b17ce0072987b2b40a133f9462d6112b86ed454df556df51d0a8960bd6d2884939eba85e333ea44d49baadbaaa957d087684db28d7b78f3a7e4e0657be9ed4177e920d7885c7c72660059231a22808c515d7a01aaac654b01b002b245fb042d2043d2ba4d17ac34df443064174b2fd01110aa9ec99f21a36735742224639454200ece55d0719d0b8548c02a708ec32852e490ee91a4429a5182093632ad5140d2da8f902df1eda3b7f9f67126b6681ce18d9e5d2f9efc40965c9ee87a223dc38e044f256a257569e712c5ce35e5713a3d76393990d95edea0388df3c9fa38a31e63bf40d628d30a198366894d0e2816ec30ba28e62abc2673910df063065d96e4c2386799cc2320b111198d4c8d74e27165f2187b084cc8211e7b97456b358aa461b72d9811e15d64403ceeb2a63cf62ecbeaf1d5e390cb6e3df60eb990f8b1d2a192524961d35b7442b6c03e3607b58d74813d667bf9938aa6a158f48a52512b5a5599a857d305ac022daa086b65a2cef97a3539a85b46ea7075b8baba257acbc24039cb7edc27ebb1c3bc6beaa220c7de2dd19ae5a223b65cb4466f3485b7e57aec1d97ae61d9611e77552c6e96e9d645edb866d16e49d76a5703798a70c4afc1d0108b1f6b168b1fbb1aa6ab9aead6535cf211e5d133ec40a852cfb073d1e8efee6eeb144c6185ef99c2a4b69ee12e5a6b117b908c41b5e4c901bdcd9e745a3a9d6a9131aa1bd9e23639a859d3050e0a2b57cfb0d32572cdc2519dd54f94861d038523ce391cb3ec271c65ee4cb4566d92861de39c65fe9409cbe4a0b3a68b2c3246bb01ed2d8ad66a1249c38e1d67d19ae5d234ec42cca4bc8aac87c751f5c9638f7a6cb11efb178e9ee6b17be128ab7aecaf70cc36cc63efc2acc763be06f733ac59ed46c6a870648bacc941d59a2eb063e7bce4eb23bd3d5ea2b72e1a2717972e1afbeab163a7535d34be6ef8ab2ec2de585dc485dc553f3d762e8bd6844850e12b7c45a8c20a57e14bb4262577b5c571611b569231e64bbc24f192c44b321c5be9f1d263f75480382e701574a8f00b42211256f8057e41a8c205bec2a58843855f106618928a19450863af36ec55097b5dc25e99b827ee898be26edc1447c5597136cec629714b1c13d7c439515daa6b75afb2b6b8640c1d79bc4d8fc76a7bec35ab6e552e6bb34a76e93639d15a92885d0588fd46d17ba3b52a62bf5332c67cecad246b938ad2b0e3a5cc84c776a24bb9869d55b205762e1983730cab59b205beca7aec930ae32c6baac72e877078c2ab09d633db8d2e1aa747670d3586cb16dd3e2ea037901c80d245e7a6b00f7e3ecb16edf549ee6ebac1c525b96892367d830648292b431e67d6d7e9ee06f2b979b4cfac79ebc4d23539d3f40cd3930be6ea121196e59ca9546e975dbef7de6afdda7aefad469a640cd9bda474d24b6f489d4a6046f134ec1fd9a36102932c13b23ea7b76ea09b8cf9861af80c5a23b282854083fa055358e1670a73848943083eedc47593e4ea1b94d6d3c6249365a7ef0e7ad6307ad673ce39c10e8e68c83dd5d44dddd44d30a877adf7ea3077c31a82e48c319e21b9ac16c961c2bcbb4c9633c623f46c3ed1709c717d3fcda7f9e44e79945725b8cb64394f25fa6294571d3497a69fee51af7e74567d466944335aab61db28addb65325b1592c7f964fbf6f934adba883ac6395b9f31494804cb8211c7d370265b4ca7eaf681aa3972e4c891c2abbdddeb4b11e4d287805cfa0b295b50971e1028e5acbbdbab40120d4f39e8c8e0254a0b94101b8485e3f7f56d3c1038bf41f9383e855390c6f741b34abfb072e5e92388e50ce612e6d3c60f58e50ce60d130176c307947206f30913a1fe07f37cce0ebf3a2c6f642efca49e3cefd3ecdacba52cebe6533ee49f7c812634ad9d86a04e3692d8e839a7d69d8e328931e644f0e7dc4b81eee912a16d9f02ddb3a7c5f5b003deec50d07fbddeb046e7a0747107f0431bf8eab5c226506d3650ceae13e0af6b0026827deb163681fa9d7b54ce4aab4f0af4bbd0658bcb85f4b657a75b1c0d97567b47ce895ef44c8c19244c5c4974e92023769221884842a9861ae81c3181ccc97acd99e3c321f161ebd112cf862aeda16f1aa0f000068e1922a4a2b869028b8d9a234c55e248f4fd038b0172a0c9f1c39b2a2e203ef002d410383801451a17a60f2d18bc94baa3e44e0d5368f40c7bce199e306532e4fa8df9dbdbb3d5551652f728e370cf3e6bc30f06be8dd92392b4e953f258bfbdfdf3bc5fd873eb9e67633f59e44fd2e6a8c14f8aa9d38825fda458070d5e4ae941f3f98503f450c5e18bd5ed538e9992244c1041d8b1dae14297150deae14c97265bd00c792376983f0e7f3116b497527a94be4e0e0578297567ebbd97527774f82dc5f723db8310b0f2f8c594440e9cc5a4ec7b2e19be180b172fa5ba5af8a097527698f87188fa70fbbac804b23f617abb7c19e5f43497986e4c9bec2b8c0225506f9f3eca293d45be6819634588942444ea1256010ce810256a071528305d620d43a0b449695d44ea8c143b3ff4181147188ff5260715971d803cd51167931e215aa04491c549bc000826be2c1102c38f3de2e84a3fce5aa061050b93168ce1d1638cd8e128a97a7c1122cf0f2d678820421c65160b3fcaada922926ca599b3670e10e2386d3c7e9c4ab28d921c6a728c29428625c47132edf87136fd802a13064b8d141d2e88e37cd2e14190354e58c8dcf9a1469c510c71d245081613737e10678e9f1e02adc9aa1dded8808411b63954e2f412688d045cb28c6822868927a8c4e937682d290725d40ca16487eb053de2947194c85069810b149a1d4479a76b8917bec0b941cc1471dedea857e8a46e2ec98d2a03a4aec96e6235f6ee965a4086e090068c6f67219319f50e3f1ec89b918ce9a5e463f172fc0a8d28ee9e3620be9bce96ef2b76638a44083a4abe7d48031cb6e30e5920c95c07be207216b26e26f2286f1e8c603404e3ce9a6f4aef687db7ac8a422fa5ee80f9fb52ea0e16dcdd158f92c9d225e7c9f911458fd3ac61d9c224d616e4717ef5922c3121aef011611b080f53e20a1dd4208310913a8ed9435c417a11691845687ad2cbf265a1cb95b8820b3c4958714a554ce20a442c302d612c6b3640e044ddea0f34e20650486cb48663f2e8c1495c81ee10bf66c074e9a3cb5ee697d36d0eb197524f604d9f524ad94e316cce4fb668c760eec852ea09ae6f9731a4ec287dac67d30d950d8564581cd8ad27892f4f123f4f12619e2424e33ae75c8883eb3c49f43c494c12e3e4e91f983d5076258ff97df6248f4e04d63779240a0134d2217b4e6372d034246d3ac6f3496ad170cc52eb41305d4c1fe7130d1b37704c1e44736912994c5d24a5ca44fda43ffb496275fdf4192599d0f3d34360027d1054f7aae44ce66da040a36713c8d528c968fdf40a6460527549f586b5825e6a915247d61e13fb70ebddfde8a34b7749938ef3d37094a978b925861cdce50d8c8cf3be73da5a2f28872897375fb44a496bad94d24a29a5b47ec913574eca22596badf2daa8b55689ddca7034e1b6c9d3a9eca573b023e992564cbb7e7d147398d2a4f74929e594eff5f98c499f81a278b33faf7eafef38d0035f1db45f530e731f75fcd52e9272465dd2efe26aa7e2ee3ed1bd6c9e9757bdef931fecc3b43625edbaecfe3a4d8108dd572963ccf7beabe4640d7b182ba23efe7cb7bc76f2f47c3e29637c30eff5f948d9e2de5026d1b03d30de8ba6a973a54c7ef5c095614bdc64cf88963a42c664d84ba93a3cb6f22b0f11adc9a613c7eb2a7191a8a6495c25ffbc94a273e5850e159d294fe24c27c98fb2db04cbea0ff65e297e3a6cc45facc3748a74218da1ef0e42f5d1e101d42d3979795e7849e915c7e35e60eaaa84ee10273cf55921531c51959ffa619c6188ba38cf394d525022182f5c594c4444e7b81080fe30ce40d2344d623d10ad0a3bc232a22e0e06dfee2f6008b10951a25826e7eadb59d86a1e36e5204c419a1a7684ad5ee090e70fe32cb32fa5e444fde8415fe64099c3c4c91ad1734ebd7a9768a867b3e91d4496bd94aad2d266b22bc95225c44ae65e4a55f9002203e16902ce54fe9888ca9e19aa7cc1adf3641cd554b2cf4b292372789a23df9752462c912fe789d264075e505699c8d35f744004a38be69696ac2fa5b6b27e94d929de387ffa7bf49bd4a7535983a394b2c3f436faab6c21c7eeba7675ef82b58698d2e87bb2526bbbdbd2a111a8144bd74c2339ca94442403000000b315000030140e878482d178a026fa3e14800b7dba4a5a441688c3c120866114c518630c218600400830c420c3d854b1002e16191c79da25a86b585dd58671ea9a60ddc7b77b2d358353cea6cb8d194c125d979a33c7be2f77d819580da3c1462e8207ef881cec82d023fc7173b9c89b26d682c1712a6e8e36f178917c768946d7edc34bf8dbb7e66b2f7b13ef34128f9219e7c7af4662dd55662561eb219e72a8c61561d9128694f88749fe1722cc10ca9074b0e024992ae50cc71392720265dae389c580711a435ed4a1d2eae83d5c421a5ffdd9175da1c0f246dae2d41a1af9a27669653fb01c9a83763945373765a953467c9498e6c58a9517408253b5eccd271be08843ec776f72f788366f6c15a691a48d77e61c5c6212891e8c1c8c05c71336a43c9c08b5fe28cc5f25235cbb9ed90ab2173e0819aa7652b6d3da5dc41eac192b1067fb2419983b5f88bd94997da2ce547395613142a3c2b23cf2bfaad07a662f6c726928400e89e5af6a9aecd91599fd9d41f01e027925e67ba9692a03a1cdb3178582dafd4c2a9e783e132fe4ad56d5afba5035ab0f744a4f60b14f27d210133d36fef4b45c0d1c8a9ec48c468eff2379acc1c031a0045593f54dbda0dfe09eef95297c0dd44c7d9dc756a40603d62a9128a869a5847a9f3bdec18fc970685c55f1514d05d46b11de1c90f5705cb329a6ea2d05b85358e3aec6bb97d2c6a64f2724c3654cb462e820c35707af3a41788e3e77025192d15ac075d9dc89bd8d9f0358e8cbaccb2fe5aedb3c61b8d8f64a21be0d6d63fbbd9699dafa943c895ceb008b495d74b425a4117792d92d91dcdc07fb99ee822050449a6dcc0b128623302037b864acd810ccd81f33496de45e08b679f11fa681f0a65fe78a4641286df7e32c2c0ad2db6145643b4f37b99c52a1241312479565d019adda3682cbc1d6d2e31fc29a2a48015afc03f3cda540a4bcf9c7b32d5d02fc1603917f9d8588d48901590175bc71b7fd7d64a82a432fc9d1fef10b32f5760d280dfca781f851a2c34ceb4bfc3ecc6732513a4d32085c5e0931ba7cb0fa4f6ff63943ae16177450dd7f57dbe3f128a6de6afda6ad5c5b20a7e9dd03da1719ac0c63f8a6b1b877e48d6c07caa24d4983f03c700c0436bd940952db262bc298ec412a893f3e8760740315b4f312ac7f94c07402598f53df3a3d039842d1ab9de3b639ba5084351c6ce2067563ade4d41f1f09af3f181ff68936ded1b3edbfc1b7b4d9821b98a1db26dc650e0e3f36fa3d2e9f8943beea6d5f83cb2d84ec7974c543ff786431b256fbe6c08a6353c8b25641790dda35fbdee117559072bef43c1b6a6a351a9dfa9a8a0c411b9b643c274e9591a096669aa52e1236a459959f20eaa2fd97fe48fc2752c63199de7c9d28a6620be841b02dad5428c531e0e845c11b654b1aa94673f005344848f5f7e686a529251d4d6537244329dee0ec6309a76524570b8221c8ae1a80f7c610bb0fc94bcf79028116d3a57495ff30f1becc7136d6fd950be298b967d33f81d96dbe01a9c3588b678a0a0ca88c209eb6eb5845f3744116918e214155361ea7f6ca819dcbbd0020126ce39e5560dac6324e5bf4e4570e5f6752c947653f7a9ec006f7911a622f3961cf8a6cefb14cdbf028df765ce0d7347cc480322e5d45d3da315c370c39763ca9f4c391a516a8a71eb7aa52c0e1a4b1e53103642f246cbac49ca9233bd8eb5fd29d3c705e7e350d06a6f6bfc384eac8c9151d218c7c1a605a362df13787c3d2e5a7d551d645fb996985c0d8be4b20c7988aff42bc8e86b4eaebe8502ac024d275623fde3c2fdb2035e1dcca726f43ace01e07d8b0585c7a2e814ca4b62c43a5d94b4b62e56aa20db213af71be453909e9e0417ef7040d513cf0ff0778174050a3d43c843b15b23f10f5aa9cd067c0117d086be399d979dea6bd6fa2cbf74505985c2eebb8510d64169451220f74cba86a902f521c2278beb36239bf011b63f416bae8250ad2fe67ba4f3cb2e06e1484640bf54082cb178c26164ff61d2b1f271337da9e64157e66c1925d3c0b37155487616c392b657d5a49975c73560b5700bbab9a6238da85bbdbcf27cbd41113fc8716b99651d5bb00bf604c3bffe59788af6067476c2a9463cedd0acd361801388e2337f2b87a008c9a4de842181059c47d3d0e478ef4a96ac95a38e844571730c5c8bedeab5df56144006162461f9cf7227c9883183026e456d5de395cc8a1e4a48b18e07d5b6dc42ba6b1c61b34785d7ecfd36f70bc6eac6bc1f1de38894a9d05499419ae84b56a0b449f4c9d75f924b7a5a787279192dfb104de7225cecce680f9501d05bef0469f63efba3a8758acadc4ee1a4dc0dafbd3e047dfa5579ac7bed299aa3383f141aa2bb980e78414c2b23271cfec2df27396efd72d4d33e8e857e69c9411fa6748887730a26a4c3f49b0371c8f4ae4c4fd9d3a70e01d43e1f15279ecae5665a9918a84aab2b72bcf778025bbeef412b6f08ddd0a8673df805d016f57e4e7a84745c8d8961b644fad3626b1e7b720231b984ef47689e8e9b00da5809bf266afe4842271a35613b131dc44cdb8d906b545dadb5feda5212de77b3b34ce0e051e2dcb0dedde45cf5039fb58baf4ce8a56cd22d25227335baa83b4a3ab65b131b3f1f2b73f9df4549a1d8e56095a419b9094072627e7fa9b644b3a191f0007e91aef3b10110fac2f068411446dbdb0a6888cd098fdb260af6f1a29066dc3e5e4109e687c110c200a771210b4dc798fd1033bc729163b7ba2c8b673b7a6579e0064f4fa33099067437ca986b21b8834b605e17be7eee86b757c7c81b88ed7704ff10741303f6be21f75e3f8677a7d79165334ba6b28161927a41cdcf3a0c43141faee87042d020f43537fc77b95c820783d713c4749d8f824c8a4a690ad72a8e5ebef26306268929ab10687e4d23e917e42c9121012acbcd1f3ca72ce4d7702a379b3b7e758e1a7fb75050a2448a655e5c6b795867cf39850a50625c82646f6be936552278c99ba2e0dbcf08301fc6e75998c505a3cfa50c40d70c833c3b3bf78450735df32e32b105a0ec7f1022bbb8439ed2a1b235b70b77dc9b48105cfe91d374cf29e91305e02022a237383429bc150bcdb0b711f1741f866bf333091c1d2e58bfdbf79a5a475656fc569c96c4372a1399caa92ae449996475622ae63146c8463155be12ca2c753b46b18d6cc965c45dbc7136de516d50e35126491aeacd3825764b80671af65f55ac311792d3a214746ba565263bbdcea01816bd9c3ca1a259793c472e5c475e16e0b374b1fdf914760ad6611599b6377730be4de5ee91615d7e49c582ff8642d37161b870a16a8631d5981a5b61431d5f2384e057e6e1fb42a3e1d9e6d80d68dc8f21df8a9eaa42bab3aaf5d6030f4b8bfe5c59e3dc11abbaf50c0bd8d7325d093659bca093a13a35a722c9f1504c3aeebde0bf30f67a53728bbc57ff8cabacba9e3b81b87bbc0e7ced16ba295fa0b74b48601a9498f8759f14276475dbde7ca5b5945c91cbe48e916b512dfbedf2acc10ab7a707d1468a81beb1693a55108c7d93268feb1592d54962e0d598a9c49501a3d55242db5d8159c03254d68bf2b867609f282b60118b8997af654a96465a2d8d156dead00cf30e29e61a38ac47d19688bcd1d08ca06a765fb7ff1e1ef78eed2e21fc875421e6f35ae12e2d8186d207f8127902843911585540a250a90055623674efe9d37e32c908f09bb080be9208c0c7cd45fad2b742c9c396cdf47bcd9b05a603a17ef7cc34505db0bfa98a668fedad0ce02dd3bb181c73ca55daa4e8378de63fff566817b955c21614c920c3603877fc8a811f114d44a78f4ff741d7b8c42f5ccd3cb4dbeb9069d88cd140ed2a15ac7aaa83b87db7a01671e508135667d16a902cb1420d9bea6a5673125c241fa85c8aea45017839b7b55098fa18796511cd89cbb95b30f6c74efae2af2d5420255edc88167bebbbddce0a2d58cb50d1433708c2e1421f0d9e22bf3161d0a97598b7510e696eb5257185fc0258f6b7b0a90d7fe2787c74ea4af5c83eba13839f258d8f2679311234037cb8c6fe8c42212289d16dcec738615228162838570570670fd17e41247bcab36b35aa17aed6a6e2faa93b7ea42f2f78dcacd553a13f31db43535aac7d51e822dec27f4958492289b5241338685d33e6237652574b4d551cfe991f0549df5246550a625067364fce202fc5661e6f67115e29534a256fce0471be6f59ec16519cd7eded3d4146599a42cb6f71c22351e07dec84d20906fb8127270b224e993dd1af6340252ffa42aba7525b6a19160d1670e73f5def6ecbe14c906bc050fd48b5e849937504ec8d4978ab381f2defe5f0b5bc6bdade2e8a6a9d24e8028b12d61f89d28b686113f451b462ab3b6d5cbc402a2d360271dfef3ded6a06a795714832bdd2b82f0ae6b415aca75a2cba510c5f50523175ebe821c3f08ee6d3478f46906776e0c84aefd4dc6740b0b3e3b56ccf022ffd7e4ba845276cda9fa3f7a8b7ef3ae0e1ec1c5b14dd1401e3b7ada8da48cea5910a81b4e5bf119359490c5afd118d4f4ae8fcf10aa5ea2661620dafdd804454417ccbce9847a4d7082bd64f226b585234ad45cc4d97b4eca1498e9c3f53293af903893697b1ee4a74f5740b212b201d15dace2988f4798a755db10c5d192e8b5f5fcbe6b2c29761417b89da9d53ec52319fa2acd39022100c827ee6faf61f27243cb1da675bfeabf26aea9bae26af1445649443775ee9d871b2ba3ff8bafb6dd67699d97234741452c060de6ccbc720d0ee0aac80c420e98b2368b5c394e9804c3e40489919d869e2beb68943d4c7df846f4e6b8a769bc9c6e9b564bc2cd7e58a9d10b4723422a926c2554248bd7a415c4437692cf06199df5a2468a6c4f8d0bf94f33939d303fbd27d6b6cbf1fe25fbc79496b5ef13a94bef4149bd00e4fc3c9a7c54013da7c991bd283cd10c7ce917895824aa9d1a1c83fa93b97affb4ed08045e53a53d8332080a74664fb93c68da0c2d921b1f5819f53fccc9116ed7ebcc0b55775fa4059dcf81ace4a84a37df65d83cbf591b0a48608f86897d2592c1b99436fd371850f313415405ca7906c145d44e877af5abfea2593ea368695f4fac20475947445d77cc067c550d7dc2e03b8d1f45bcc8e469e4644f2eb9872c2b6abbe102978642b9bb4ebfaed4aa61f6613590b11540bf15508dc11462ab6d52353cea0747e47c71cd3d00edacdff494134d4562d3e7f7a9d8f09f91143393bb59019590e79e988dae0269220b46a172b6f012388f13f6d755e77920d286327581016513fd5c6d16b0bdfc246f9f8b38f6e5096807e414e661b25b84afb245f84b1ce0b94db3bfbc5614e6d17be369b72e2f27229e6a48b7a405b73b2754f81c1ebb53c25131e9653855170aed075c5e54e1285d0f89e40da04c33c28af2dbfcdcaf44c37ccc5909d8cd1733432fd48b83488f47c6e64f9d33047f25c71a00181f7d551728eac7b1e921852013c078f444f30ab4a53144f3623dfd9ddd1d278306f621c73dbc2b326f837bf239dec6cc738cb1394ec186201e355fa01dbaa38521d955dcd7228221ec72ab56265bafc40f26a10d83c952138e7d808fdb5f745a09ec74db495e146a3fc84b0058d7b40061236e64b405988bf749e0dbc562f6e3e7194a1a5d8d94095f952c080ca01ea139c1be3becb4c71b7fd458cbac18c4cb99d1e04d6bdf8b0dcc4f9bf8fcdcef144cafe913b4e62b8d57d4586246757c6d31630b8ac0752468d8fbfed0831b892ee22bf06d058a14bf53949563fb1bca9eed958d1892203a221a28eeb615ab64e60841764931bf408434797cf29d330ce670aa92af2ef395fe026b8c3023445233b95563caa53982780276de19f839ae12ae55c3516e21ad6bfb53cbe6e433716fa4efc9a12954c521496f141dbf2062915fe2ca8552e5152274d808872deba6146060c38802cd01b4d0582c127ae2251b0ef085bb2010a03b76fd33e90d3674833076b550d0e3ca1622ec5c148bb1fa9528dda86d2d59f6dabfbe7084ac04b37c217daa20b9d8409db3135de76af678466b572aa3982f137d03a84fe706c33c2875285343c3dac1a27bb42b3cf3050e9871460f3599b99d7f4c2c7834b199fb5b9d5380f5cfd3a2591071abc3234f4c5999a2bdad1f2628f2d85f64dc3665b8ee67680c80c1ecd03f90ccb0735803a241f1acf567bb174f533d8a8503f3d23b3f9f02cd4425b21b3484387575c096bb06294e4af645b6034758e2925baad8029a844444e49d6fcad82f75062f99b25f6cb2a1f75bdd596bf43a0d95dae06c12701cd0ba2eff97319fa72f3dad5d1db8b8b70769529b207d3528c6662b778d0dac50b7d80832acb79869c911b25cd29c7294e49e559afafd65dcc7348b6e674b67229ea64416baf415b6f2f32858bf6ac02c967ec1579956623cc0ea6c7c82fd3e4a84fe13fc05d4ba87933fbe73bfe240f545136ec71415c95817f3d62fcffdbece1b3e91d64b8f064951eb7e4f6fb377fd1843f1003c48dd82cc9b4a9591c345edcd019d320a90af0e2f6da40acec7436da10275786a2baa786e3c6a5515ad83791729d0f87f80a7e964ba3912def2c715b41cd3706c64967c5183b62921b7531d0ce2118e89c11e8584a96aaec9827cd1638a2002033374a243b5131daedc7005b56e7065120b7c39223a162e01916b8328b79d89d824465f996db6dc30d7edc369b35ef16f3ec8c3fd69da69b77ce6eb0464849b6601224b0b336434a6980be5fa28453c3f677f6f1eff1bb7f9c0d889ddc62d9a199fe396300de1a274ee8386f6a1550c8616c1705d75351224a46f9096b962c7b9fc24e8268962602b7f4be81fc6e226edb74398da6f611ca9038bce2c5627a3d0a1a8812cc2033691673e4af98fd61f67ca73231409d230a034e2cceb5aee1114e5fe6eb23b12e369c62527090f7f2c0ecb57af7210784ecc4d054a379a8b3133c66d858e8315192f691c3ec8e8e89aaaadee4850ead64c0c2f54fc10214a44b0f99fe2ba34ab82f9a868f12e35b796fe4b2c54f132cb0529427376a865cc02f760027ba9c23e610981ec1881ea3e4588b6aa8fbcdc6e13799e159b0673b2e18fcf21dce5c5d6c78b2aaaa69aa5173c03779f242be80fee1346558c7a5dcdeb04677582c09e6aa609dcc17c8adfc30270f3798180669ed87b96b2f8d389afa5fea87fbade489b92867692e6df596a78bd421d469e695111ac958f6e6c3fed8ac13dbe3701e7c360d18e51be8b51dd7cf4bbbaf243f405be11b471c8fb453f607d58b08984f7be450ecc7c230a6be3603e29185db980fbfc425acc0276882166a2ff84249b8e751edae625f683bc683d412a9ed1a317db38b7621ae72e4dfa8928fec5c299a096da2ee8594d37f3da4692fba627cea66be7e676cc7ce18c6b15fab1d1f22b7d5eae0d2920e6d988af7493aa953d1e1950be7d1b9bffe0a58f39afe8a2544643aa3eb8a3424d046c7cd2cd722c68757442c48bc6df225cb4a73bdd5872e90d7c08f4bd3724cbfee2ab0ab81219046024daefe553e37b7e2647ac3d5fcc7eac60468ca5af90044c3c02ca249e4e9ce3b58e5121ca17eb6cd070364ee3ddbda0d54a1073436a5f7f8d200f900357058b29ea156b08915d321af9d8d07550d675a113bc63b9e10e7fa35708ed0ce6c480bfd4f9f88f2cae3e488c8d323973b3c5be9a587da8a35d339a6bf44ec6a0ef4d03a37afb0e53d393cc3605f99163916f80dd32e84293e0f74aa064161dbb48d04b5ea8c80b70407ea5e71d6cee557ad0cb18289c0cedcddda9e7e43c050a92fab0d8d93decb46206504534477dc9b48201d8f51ac1e02954321296ebbdf21835f4c42f263b43f1cdf5de625b8685820d5a2f3c24cf4a3837547770945ddf07bb154015aa36f13c03bac69d8ad52d8142813b3e85ad80c7a7aa3d144fd6a08d20a884c79828424d7b263b0d6055ee5b2737cfef2e16d56dba7848046292bbc474096b5a386bc7961c1907b5f407a4b9e2071c082b34fbee0b53861be9eaf0bf83e6adb4816367ae0f1ea3b22593f5113810eb2ee845ea182d28bb4ff8c047d55945f6a56ba3dd6a3044fa870f084277866eae7b82262d47d0f3ae1711353ba2cd82837437fe93e0b034c6a07fea06b9fda119ec5c9ad7cc46e3add145312ff514206a02b420f687b54f6ffabde11671a3ceaa2ad745f9540a1abc5d753a84b8a72deffb5fe765fb0c346e8398d29bb7125b09527a04bb08a18c834207f37db05ce08dd06eb8e4f6fc6fd31948233c9191c72deda98b35b3bf3de674bc772d1aa5a566fdcc014a12a22d6146063ebd374f44339c10f0dd9a0aa1658df0e2a3c635335c4104428e393206a6be706ad00fe4b41fa992409e3e072f6d4d7ac6e2b0367a325b537fa56d9672acab94d77ff103060c9c30a888e0b6aa4aedc704b1387f15575b9e15785f20cdc4498630b887bfc28c9627a91ea5b46a6a586b2b0ebef90652f5db340737d9e386e7951971b83e1dd4fcbcfde9bb5fbc86abc8ccc74653008c8e180af8833abb2c9f50a4c9da8fbeb08dc95916742d31a04457c91ec7327ea10ce75ab091bac4b8df5e1cebd01851be1ef17f55bfc3a22704b4d723016be25263b0672c37fb3963f4b36cd8d8c12174daf1e47c67abdd2f4a973d895485bed0a49b4588c9a58e79f2ea839bf130f121272a5be7bd8a35cd7284f97ea66e17b476bb6e397952dd1fed25a769bae7997ee0427a1385464d94febc34214dfbd4f7373b69c2fa5d2b4aeb5501c4a571a60bfd18b2cfe9e0a4097e973bc499be69b0f7ec5331e41ca777cd50721f59ce7e980c4c4630052fced4700e4ad6f14661ff981aea8ca9b3fdcb6e0e7b0a3d5aacc3a74d2d6942818f0c29c2574804c4e091a6d3cbda4668cd2b6fe9e164f3515695616093da48680200c65eaac5c09afe3cb7c67f333ba300537daeb623af1582ea1e961f47552c64b500aaa22c03738c9f19a8a1f3c2ea19e8261705859c3b52cb914bbdf36595465008f116b82c8ce2ac76c50e89a0ee96e67343fd791e8e1192bba4edf78ad0023d40454c48a2d1cfe60bc0b8e564a498a9d72a57367ea993c18a4550d89986e0fde5f3a32c5244d7ea1684f87d819c1879c1e3ea934bab5e756ea4773659f63f7892570f29709c2463357230126e87231886f557d7dc5dba7a025f80129eedf63f2c074ba95f1f109a631fe3c5f650f95a15811586434eb54a4e87e4632474b9c3d2c59c5cfea62fb480797bc8a90c6ece557f9ed31eb179a70509569d89ddd2113bc994b50d667b8d5b92ef84d6c579c98717fe50ee54cebab267e6643037eb8b446479b49328964a10bbda7ac7cde40f1132b50ccbe9ddaa2660afb584efd12961426f729f2386a602884a13750a5c6a3f3ee0416810c26d238f6d04c8f3fca9bf93d234ec71564a6e3d40cd85ab82374753d7765cda0fb3cf15c21670af20c9bdd3c5431c34ebaf92ec70c449429609ea1e2f1772373286f282392d358b9b3e5abb62e670bb87087d729dbc697d9ff54b3b560d46e24a1771ca3dba1d321bc5a137ce877b5e4821431115e51061ce9f54eda0ff5b78a126006223f7425c714de9469e2e2af0e3221473097af2f23cee45f6bf7a0079fca44a4903b3cb111e5d4d7e4e2771db397e827a0e529f118ab65d0d793157832404d86606eaca3abd0a236e8af72a723483442d9d86858fd519156900af331fc4fe5397bdaefe2171f256178593be23420a322374711ca71a365606e8cc0b45a23a1254f9ac7e3c5ed0264d18d1cdba3d28409e2611ab8b4b30ae345a3492c89a77e36cfd2ca5ec6121a8fc8d8f66f146a4d8f4594befc801dad5d709348a5cf55361f5a51cb2664755d51e3b20afa7667f7ccec7190c744b511a18134de313373171823c1e225309057f0415998ec01cd32b7cca47e925cbaa907091e6835735b01f103b62adae7346d5d472ade2ca4c5c4899a5e2aea04e756e23f2a55e3ff1e4b11e694d34496ec60c258bf9fc9c7384644cb808d13c0d92288c09a97240675d64ced2de9690aa2d27a789c7d4f0763073378a86b21aeec669214c5f1491752c6bc1bc62781c20d6c6ea8c3b8ad68d6e4901f170bb02a77dc7edb5f7c423f97ba7fc8987124ae6d331203e94028cac919a6a932a0868490e9a559f85c0549d36303a8e32febebaa7c01d935453fbef709a271b90527947e77d9cd6f25a9690d58bfd244c5b68a006a01e4745c5bfb4fb97ed77b9f50722a5b00690ce803bc01c0c764b10ac82875f39d2a8ec6a5af5fdbda5c52d3457d8b1144736b0b44c3921d3f695c037c5dc908384a66055a97a99c642b4ce79b3c92708a4bb5833b61331691e4599d5c87d8a5f16d957a5f6501ff9b98b3567950cf1208dbd0dda3d999f44293f7292e3b28aad13185544a4403b2d13e60a213b18bf3a5eb9babbf2cdd9167939db9388db27a33e04197a9c1be6d58942c3e74720e0b5080fafea913ea86c4faca7aca8ed8f71ad20f5559489402c336ba8eff8a2d60e4262180219bc869b6e0d274be2407a3808b8c49ec4667360b7cc293aa00542789fc43aee8a26de1728bb1827aacafea8e8009492794e8e16ac0fee541671850af71baf5f25f82165e8b2f04f2ce1616c0f4876f5b95709a86810336cd72438263d6f7f6f2cb13f7a252d10291bb6a2f7a1dd3f812972d81511646f57b4c08923b23a49ec7d7dfbb745635108233c3fc07f0b6a2cfa4b0803c344169efe9c76a0d0e9332fca5245dac4a6d7ad20ccd268a42dfee4a6ff44d025bb2451675fbf5afc413063b08503bf5517ddddb3516260b52769b5f84d8ead7c52d662b1dbf95bd15fbd937e232876728b54ad0b22f8e3b3196bc0d1bf2185aef786902d149b9e3f408d416e092303ede98b2f895e26118792fb059618d50c566b77012a11ddf43143f9be4e5315c7f3f0083957fa0202576f1c7a830c0d05569f3f92dc5e7c761f017877bc4bef1573af2964640e9183458eba5dfae03b4e2a4c2714ee4c3809ede09787dc40c02acc41e870e88171224d615d09770029face40895cb06d0f8fbacb58f61a467a1892f4ecf63694f2911e25ab32aaa318f3d00bc42d2071347099509d56a46e7f34944c88f49c4f12744c20ce78f4a55b79879e46c7ee07c84ad2982209cd3b748648214259709ff38e1cba4d12dd3c144fa76e74174daf06f74a04829499aa31d561595283a46a4bb92163d87cf35387d1c682de76fa0f09c665cdda212e0a4e76013fd4d83108a8ee7e48cf460c421f6068aa477951cabe06a45caab1db07218734467dd6908bf2acee961ab2e85ce1ac7e70df556c1c5f671b874331ee23c4184f94853709a0f9645536e3ae3bb73b49071cef19845c2e53b65ec98a0699cd5f43e56e2973c90879100168a4aedccdd1aa426c787826951d7e2495d7d11a914e2ddb1a2878507f3f580f74d67154a7f0e2db3dc0ba78762d74f035f6875f41d8365839522901614f03fb27a4a074fe73cef197b19697106adc1396286ea2b84b308ab647c5d3a5ebfdcd77171b08350c7950421f2fbbbbb81e6584837630a42ee7bcdadee3dd24361532692227d7798be6ad0f7dbdb57b984ce31c6a1a24447d8547bad7a82c27aadca0e6953aca683b2679e471e03b1985971790acd478da453339e656a01379158374ca1c965087e01359340742cdee310ef099a399698f2fc7c36a1b4e955905084e271fa4f74a05e4e4fae387ce52c447b3d70cd76b035a692ba99e1fc2befc88c5708721da67904607e87f049f6014220d9996516b94b0dd69b1e531d39e9c3c607b9a0fd69d6f933180fa798da3b0ab5f0f120f834377e1da614e179de5fc51d6e2d6780e076030cd5b76e872da643f6aabdf5d50fb89925d24bf9096ab7bee9d465265088a20ca3ceed310b7d6b907014832b74c47e9ed34f6c17a24206529ad015b057da21a27437a294e40e30f2df6abf950840e1b2c225ae6b1d8005e7d74ac26ed01d949b8fb1be500d032fc13d2be6211b98615f2459c3873c258b5412603dd9c53e6a925dd37e239f2289d97226dd7d49d31a7550167ce11a0b9a4f54994673f19add7766fedbe5d7df55ac861b3ee9054005beb98412d6fa73779e410d093b0ab5f69312195de079797b4d3d9487ff75d03ba68db0c0e72398fb89d165af22f657afd4cb987448a12a5e5d991da427b54c4867b207cbcac0720f6cb1f3e1a6076a814025041a8d694fd64f6909c28db66d1c108dc2d3d74b03ea827a1331606f7d0bd9ca01a3639d4d91b43fe7bc82f0085e3aee0edf79d5ac5b9847ddf60e1d8dc88022e5849df696bd3391deb29dd01c597825b8d117029b4051e91d93163adcab4d141dbc8fcc439864fae48116d5c24861e275ce7a888c8c281a573667dcefef4c87dd4225f6c2ec18c261e49a0bb747db1883e6d04d8e34f85ff561e9ab275dab445e59ed7df68ed37d28aebb248a208bc66711fbda57873ba1c1a58c5a0378a4d27a4485d397016b1aa7912fb4a993bbd55db6c347029d7238a697e96369c7f50afa993516df4896ca7f8bfda410fde8efad4d5f74d014055e14c2a73c5e1098210a03745005456df5c93736b0e35a4fae34fb1c52e42aca704c08b8ad504cf86539924aadcba9a45d903a398aa8180e68e0aaa0a0d4cb467813b623887c94b17d6c92a2c9795fc910a1aeb56beebe3f62a17d169741633a0249e85e5b3253be8a616636f3e1b0662d795802d11b83325b9e96d66f696e8e59f8cf020ac959b5fbc28cfded11d56ca441a3aa0b074c6433e77fbe1b204a21adeed122f71737701d29bdb5538052067a13c1fb24405ea197b8523ad9f56401c9e0e7c89e38a8fb0434e4c1cf1979ed8f8edaced33373731f54ab19412d30a04ac3cc11e0712948194de196ad5792b138c3c3438b9ba8e52a1d6247c1bcc2897be21a9689c038c6919f890f8b259c7b40618a00672f556a0b58094f451020273a0158701a9a8c0af5254c403a5be5e20c482d4b30f843a6407ca486c3a56489a962335666d1ec059e0ad9e4b9bda454fb425689fd26e639799a546323a7780179f0658b48d6bc9abdc40a3e316251dfbea60e8ebc05662bb379dce7d6f64faf5b8e0231da9b552a4d22f9192a22b5802dc48465cf178468dbf70c454bdd3b0eaa219db8f5dfe4ea696ba92553dbe69d12e0bb7d275b6ddb57cba1c492b51a32dde88996cbda43973bd3772ecfbc964cde5ec1ddc4f4600b7e76649976f43cbbf6d3f0b584517827e21f472a40a6fa65f11a9172f44f98d9852dfc0c6914204842d5fd4b40db5920ffe22191f22f1ccbf2508272fb7a2206b3e21427a0f8c872abaf074352de7cac008944b400bb4cb1b114bd83c4db10c7bb74c7471fb5c098d4134ca77006dc086f82769c31193a7e365a3770501d283412c8360cd125b3dbf6f92acbde8ef0063d9854fe55b4f9e1dfaf2adf9608c09f005c11ec805a8005f5517340e725b19a210284c18bb3bac614c7a43d85b938be80f96690de91ba1e835b1a073cd353db0404309941fb3f8082e059161294da253742d509066fb6b433d3f5fb0ef81479a655e840a2be09ec8f8e63e2a37e146ee36e19de82ca0d2de443d82e758f0781409b17147092d24a2d117dee43ddd096a4d6b6992c3f0b781f009a4e24e901182a47152ecd965a08108bf1504208c9e007381455bc920cb6f4ff87bde66bce32c63e9b932377ea0b3d885d91247e28456e28c56adc5e99b92d8467d6d35984705627a64c74703d4410dba93158202e2931961549990204102c25764b274cc9984c90b94000f9299859a142879a040da862f5c5f29d73959e2f6d0114e991790f09bc3cfc58cae0dab6353225f9dc48351921c644a0af186c093c88169662d094d49e18cca07a4e762e8adbf0acae24147fce9623c3a1338872b45e5fb37faa4e8adcb2c11fb8a0c3e093488925229d5b891a0314e270520a373345d98b91e393baa69eb9791101169376decb85c62211b888be6dae879a55f68f8ff03938a6fc18bdb4103a6ffefb1150bcbfe5d9d260d45323a2c68ec19b9d24c8f6d42b9b5676c343e9b67edf360c33d6a4ce6e0d90838eb3d981f62b7c76b9231015fff63da03188f02d7a7d17dde978f3ecb2ffdeaf5fb7111fc7266490ce1a9bf73b0df1dc4c5dd9617bd47cc5e8e440c80df6b3d0c4eb65524bb57b0dfd5b7a611c871db0b129a8023e1e9cb0409d490fa3207190755d7c14877d91cbf15fc2e569754e713479c4ac7f847400483d46475b6f2ac42961dd3a8c68ca1d0c1a976a7e7bbcb3c1d23e7a26e43960664b65bdcc6b22020dce617c9ebd96423f06543336c4632c705a4f3219042f221f435a58c12d10c50d2cf20d1adad2e060ac6e7661ae88d0267aee0665234c7ed20338b0232127cd698f2ac0faecfa982ea58ccd36e6276c0281649c9ea3b6034532ff849f474c0e0e163e484cf1169df9793fb15e4423ceca744972087cc654b2da675eb3e7cbe65305229130f5e9a80e3071a1ef329e0f28746fc22e1fe17853f84c68199cb83b80b0c8621249ce43bb67d886e371d307f5c0a6efa6f71319e249b1fc14024818aadb2c93725d97a5dd2def6cbd97db0f156c1b0f2293122603ff01612ccf5363803648f32f582bdc7d0649d7055c4ca512912acf59b682a50b495bd8c92b70aa18689753383dd076ed0689dec9c93fd3e5ed98036596682baa3245057c5930a0a22431895d68ca267b2216b0611601b66db8c442aff101ae8f94116ee5a2bfed1536677a236572dce950b3a34369f1ab7e0edb42eacd2bb9ca2fd7bd76943a4d533631735238103d9c908b99e07d02e6c12d7eda48529a60e90bed0d4468ee43e776d12e605beef7da9521d95f59d6a2b1a17567f13d952dbd589fa7e154d99e52e8ebc97d32f86b42b73abba2de9991e69f413fc79b2d924e69fa422373ef11652826f877bc1063300a202fe298d8d2b0f6f6d8502bb76ef3e4ca360384949c32cf4daabb78eed386a6012845a7cc61cbe437f327332d410d761746721a4e19b99b76d46bdf424696867dbe5b41605a9e731ce361169368cd120a67066d2030446aced4df4c8bf124864b276632a74be6c41a5d911239562804107db447a839a6cc6edf372ba806db99d76c48138546c10ced86cad58d84fc6e2a6fe44aef0dfbc76043b3b42f09177fc2be3493298a833b2ee1ee393b06374652648e819f048a7e6317e287109b39cd300b16fdb58b0e2c3184757c1957a4eb2cc343b9921baa97977969b76e5963dd6a857ba760496ab8988d2cde467ad9c8861e67ac0a7d54818bce3afa8415401a83b028a415260cc9c0ab04da6f4c65134a1df725ed0781824b8b743279145cf31134a130d52fbb9bdfadf92da1530d41dc1796a82d3cb56a78ad18ed0dbffce80b13bcfa409ae644ea8a5b3ca36b8dd547b594385e3e43549ba91ee97df26add4837329f50be470a6a8f77d005399ea7419353dddd42f3b822cbb24ee4161702b8b61823914d3f9504c4d8297e16351a4cb806246ad2cf2f802e16248ad300f12852848021b63ca4a9f23d03a343fe54fc3b5564836cf5aa9d92dd2dd90f4c7c776b6cd147f3994346d28a9140e29fe1c41481e6176d71325804ef4c2bb82ef1b3356a4d1b4a5e4824e71f53adc726447721f7313dd921c47faff7219ba86b315ac23f0e905ac6799ad3a2b6fce2182a6cc7a0efb20ddd502963c7966c11ec3490a3f173a3362d6aa21b5f92c422b6c4c44eb61d4b7a0fc3ee721203829a2ea13bf82d9e148d273088f30257335138f261431a320910b031226331e08ddb9ab5d11134c3c02830b0c3c3a182ffec75380cbe6c853c79368b4e6b8b513495468c9aa1849e317bc85260633b00f07da3bf02fdbd21a4c0c88817c5d889e99f5bd0e8f571e3fac22e7a0a5a6b3dc8d5861d8047b8b02953d0c8d980a89c8409861d7e5dc7b7ea0e50345d07df521401bdbc128786466ea44664aa570c089ca2595b27eb0a3b9665c1635161a220b06a599f30555f04263534a2f980d86a430902aa1db4ad19e5d2925b208202f8967f188b62daa204c6ec656a69bed229262f7131aeb879ad6b2aa6cc2ae0558bc262da4660f9afb7cb0ec71cd9665669e0284cb3bc340d40dcae24e3c832fa40de7c5cc7b22492056e757c092cebc7df2e21d9b89cc8223b398128711dc9b4600ed28e21e6ac04f07d196cdb03543be25964292cd5982ef2a31e25ccb0e5a3f8beb23264251ee9696017bf62a17e7fd302f26a908cf830455f1a12db77df8fbe451bbe339c1f6917c53552e554fa60ef794eafd97616480b9c8824c22e573b0370c71e2a1d9f6fa6c900c49a10c70d78ebc6620338c75f6390ea42e33b541b4e41565ad342b0a84a4283008f1ce17e51b29909208f46e0dfacac20c5dd29b9e014861df3754b9073d8e10751b98c149c2e1c492b0862e07f9c213bba449b2074408cf38e3df0c2800bf31265c058f4788ebbfe178567d52d19590a4b428687d379395292d18f1c0689217895d902f54292c627d99d24edb823eb8bf392ff8fd856dadc93f27a9febd1e8965fb37c7484167093a2467b2fb237d8cb65b684d3200dfad92930568fcbb9c0442838ee10a2484fe643fa506ec5a0df25fb108dc232a63ce977c7d1d1cac1cd0d390bdd283bd4ec1445830b659cc4e218fd82523a8b4481961b1c937eb21ddf571ba01a140ee6309fd44510abda59243273a33e1403a45d9a168f218fd1e2ff45e4aa8708a8dfbdadfd610172fe5696d9982dee9ca065e21a3b7fcae61329ab32ca6368c1edb0a5c2d447a4a640955c51878c00fd851ab5ad0818a460cb38f3506c7bf8e945f48d52869f22d3462313936b7bf95ac3bef97d13472b50133e606cd0487f9114468dcbde21ae899f7719263472d2718453695c7048bd3fdf302f1a9a839ef41a1fe60c68a35418064f5045bd5588a0f1633ed9310b95cc07a443fe081de2c10236897658bb21a8cd090a28068e20091bc11c546be9ba32e0c5b2105320a703092895e34fc9b1d0ba08d224c80178e9c0a0670f158c4bc83077d00fa04ac2c4180b9cc5d1e3ff21d4b3d0ade2f3e5410e3a040a5aa9acc09cc180597547c5c4f8f6ddb97a5c92483d04cf47ca22bb59b8f8a6eed5982f6283cc94bad8e80d9fcc6f49fe28bbde30565783a4c36b00dc6b0f177181045434cb12b08dfdd96968fa1f88529225397252c9671e5a2a8a4151bce89e6d5dd85b46144ea10f6fd3004a408263a5896322202daf52ff0c318883083d05246087afa1c07c6d8462a4bc8d9990c6efe7bded7a4b8d7eaa0a10236444b199bcb0fd2543493583452f8215af683a92332b01bc80c212c90208621a6dab063fb8d9a840f8a6af820378db1be5be012773114920c870c6fc30709a251f83644ce3911193e83e48dd567837e163baadcb54dd29179e7fbbc4535f5890d96c1850cc24fad18ef42af5743f7c4574c9018595f4c7da64468d8f37b3dad46e7faa931bd042911f4fcebd9c5a07289473619f655b74c6e41a496c8e25c432ecac8959543b1674a3aef60e46c7f4f48534a945533a4be8853ef1e109c4577ac636e6dc2d04f29da465da6c532e107d8d95a5821130c7d5808c6c356e41ad29714ca88148cbe74faf4e34984fe231caeb5a601f22f2307cc989285f77838f3954afff3955b3eec6b1ff07ac8b516c82b44b3f423ebbee5732c4b2da76feb1a19f84cfc4089a37e5a4b9c3df76ddf12608b33c54833e65144565a9ede0d2596dcdff3ce68055b8afeb556c80de06afd812effde790e1164d5f7b21eff16ee4ffd3fe04c5ab796e258414594f1ce8d35c559fb14229c29687b9bdadab68c34320719c84432134692bc088f03b9724a4d0837ffb40a736d85c493f0d08a6d1d096f9ca869608afc8e0e14e5d1b527b9d248c4d5e3f688c4c27168823bb1aae975a4bb82371ad9c52ee5794666bdf5042ec07c010aaef6f1d23d28fea816aeca4ed442f40751da4461b4622ee8c01b57c10c498aedb123e948911cee5cfb2a279871abf1c7a32aec94ed1180acfcaab845f710fd20da31ecb2f344848156934a329b97643f89f0dc249bb7bf01142d2ef0e5e3b656d8ef37436793e1c0bc867db18d5c280953a064423544666e8430ebb8ec305d038bd4f70f8175503d3c40c2d0eca064572dcfd0c69813d4eb247ee98e9310b740f2a096e110bd0eb7c97988c542cb2267200647b8357f7b995429bf568a2754633ed89e89b0be79693ba0cf4592a859a591be8d772449b90d9cf8dc1874945bb6ec64d7bfc74ea3930b7bae21221d79b92dc76f93f20170c146fa1af4e80e196f3270cd3fa362b014f87153c1fa55e551bd89fc106bdd24d9ab5052edb0444133d80e665746237cf7ce7d74262a6e1482234848db995403b800a5722fecb71dc853b44a20458d014d024fb08b9eedce97b9425355085b077caabc906e4a90228cdb83dd40753b896e11e1362a172543bcc6b313ab43ec345194edf0b6f78f23728f4b0bf676d81e4e0128f7a26639381f711dcd766cf4638405de656d71ae8c53d062d1b742e3a892671dc84559436d0dc4846b68fb05b6e4480b6b0f8b77a3a610dc277895c07af168b9cc20a10eb49099b2040d5b63bb39f163e0574b945382331eddb36428bd22ee9f481c2c6ada3b436969838c58dcde1a0547fcbf89b9fab554dba5f8e3c4137b5d360cba9b4027afb068021441fc8951c85e6d21502a29fc3eb51e84ac0305c3fa025f9c43fa240a6d9752640c63e6f6e8333772438d3e360ddb3cab69304b6d495b2c09b3035639b5aa96988b74de3c59916c29bb26ddc09f53490ae0f6dc13012e671729672ba151bcd41c3e044b196b20c06e1f5c569302ea6e371165515f9f940ff6cd3bd2e69aa18a2abf1fb2c29e82c6bfdcc2b41ee471e6db3f901e6af0ef3fe1fe4fb38ec29a9f096b572e9b27b7a623240fd7b63cd91a4f12ca73c17b1a21248a7a7815b12d24838995111c309470908541a1f6b016c8c093f6da88ed76bbe597d341b8f6df19388d815668907d17a4b6793df243c3e4b257a3deb511d563f973467da01ac3fd766423d22857611c779537a559a6d46ae9384199d6d9ec0c5a79ea8e3a8268012c9fc77e1172a925d9dde545356813e1e8910784e0fecb6960c304a3cc77203246dbdf3b9959c791d2851624bd55ac646b447b7fbc4ffcc20c0dbaccef335febc470f7c4c504cd72296b3159b385b516d4a4a9f364744534185cd6687c0fb343a6297eb9cce3021c1388e8f1907c8e527a7d512a21d19872fa8882daa24fb0de2a3ab2637f7ccdddc8208e87372119a3e20f734ac04e93df55484ac9e8fba0537a567fab64a9ab107bcc73b816f60e6d1050ebc9a184c40751f14a8f81e664ff9c65795bcd73ab4fd8d2dbf2f3d7589e8e7585b4b6263a3310dcbac6641afbfc6fd17fb111d0ba5d4e29d331d1351b3c35850047333a5a962c3bc7024b034eee3e3f8a0141f0c2dee8cfb29d9dad11f11c9295dae4f70f759291a0c7fe31794aec28467361c5d6298652181cf1fc6dbe24434bbf14b80be519a3e916d48c81fb6b6ea07daf73fc27c61c41a78a377d96bbe0f2edcf36c59ce645b773d0ae41dbc5dd8d3bc0a3454ccd52619ecc5e115d9e911efba3db3ed29d818c29cbd3ebe9e5fa71a5fa7e31057684ed8826defe0972cae0dbaa15154600b843dc7ae952baa47d5369a954497ce5d1508ac78d886989d755b80e508333a338cc9ac14a3e0947376b7bcc70b3cf3aa90a5690e47c1f3b8da5ce66f6b6165a46b39c56a1e42ae166fcef77cd5811f5dfa8ad9ef1439be3cb720e0014f907a563f1450dfddcae8220d50e5d029da66ecb806952901279c7c231a1a710fc1eee39bed407d174760f740b5bd933333a2a26d4bcdac51674c8db4d104d8ce6a95bd587d38611f2ed484a96110a64846f42efb166c70560dca6cc520fbc2fa344c78daf025267ad8cdf9bc4eaa65459456fd9aab190c54d92a0c8532fa59875cb8cd037e0a70b2bd6b98271a09cc26a032c876f51e514af3f56812c7c11deafa8d96eeb0b5d58828baf62d978fe738ff93b2db764bae38d8f40f29b68a73c5460f8d000c830130626914f02982a7624e68252ef4a1a5e55f301eba0ed40dacb998e301baff9aa6215444ae0e079390bf60eed444dcfccfccac66c881b2feec9869a4c09045a1ee8bf168f0b158cc50f071f7a9df037577924185037bac5298424a039f0696c367e2524c3a4535b7caa48e6f68974877e1b62a77888f96317675f51b76c48c4a2c9c94940b21ea0c662c5697cdbe90d5ad726f77a030cf418aa89c251142934ad7ddef49afa3f21f1e583aa5e4311c4df89e38175731fc94ef01e87c30fd700a8e464e77f2e30683d7b0e3fdf7c128fd16375584e5b2963e8abc61bfe8105e8b94ef53aafce1678a503188c8fe416de3aa7d2880d4300dd5569601f4f8050bfc232eb5e126bd31c24964d1ed932b53115e9933720a58f842e95463a22403d4eba2fc6cd30a829808420e37b19b91c706c963cfbc5d3c53430a83673ff3bb69a6a6412fdc68b2b340bd77db581f928b26b6c403764447ac49f88bb6bcf008d6eb369a06f140eb162057383a76b6b48c585bf85bd0a5eea57869bc2cd4a587fc925162539e8f03bbfea0c389c4dd550acb7fda39a766d7a4224110c8f48bc602e187f6e6db39be1cec889e7370c1eb166f0e04a07137cee0a8f0401aa3b0a39f5fab7ede3e847c8e61348b79ea01f4e07bca9a7cf4b97694fb05fcdc44ce3e944bf4fc7371d4881cf69ae3bec6006963dcda2fdb1803c9609c9ab44baca4917ad0ec301817220731da38a8512d94f28962b7a6a1f3195011ea9020cdeccb05c075c95b79516b07ac4323e9057fbb94f562d3d3e8a2c575fbf4189f5c88b4bab47ecc9445345271edaab7cf31093bbd9ca9f2fde84f5f47e300d6b5a10dce57eb86eb17a3547b2b915a566ec55d4658ab9dd7745d46c3415a654ae2e1a161045c6b81e57563d4cdda13a31d067a57d020da7c908c7524aeed363a07b9ebcabda3cc50ce0db18b7056d40a6fd5b14df0fbd465ae3b1ba98de50f81ab9312336589fb31d1f789593c293466a71ae28c848f3d1a451ea5fb83cf06a2c2b6a7bc7a38b7f582070c1c3c7b32cc5e98f17a0cf17f65c3645e9005627020de06e97b9a742781cc49b6dec14338e82598b5b0d79f8a407d39cc7ab6ba8d928aa673bd8f3c7a2f9dafaa87b1c34907ef12832ed1d298f2b62f87ca0969efd3c8043a20bbd64b295a60783d6260439148e6685a4b9e7c45fdbecb5117a2ae9b409e5782cc6f2239b0377b485647c3121b1a267a5b207d6885722812489c9ef7fc4c69803f9254355b183b1af50c57b9559ce9f9741552a059da689e2be743e3861485cfabd014ca5221eb995e7836b0737d87c4f43dfc44063dd6d5cb4e16c6f1d101bc0fe1c333b665110c24d340e548c483778ad265a64eb5adc0937691c1030ef3836371c956f027016d51e157c676da1c8d9f18dc75471862bfe69ef743924ea372307d27f40fa55df236dcb61b0f4bafd7471d5a267e2bde4995a8d83e3c1100fb903f9d492b33f0f7a2c686dc91ddc8f5b6bdc4fc0e353a351a6cda0374ea031c8ee0df7e97f6ec9436879b8eff9b5af8fea550655b5cfe119e470f1fdec99ee87dc4af5c42d0bf6cb03e299dbb560dcf5e8ce7664daec52151f0c29cf000aee88b8527739eee6a29be5369d233e89531f9feb1b0e14e13ca970d9907f4d01aa175ed050cc35083434d619f6f7d25807228eac8f4db66a55ae350876bc48b8a2cdd8cad0d46c5163123b323132e82c7ddddf6cf56571e242d2494ac4b46c86bc2338891ba3c4808bf4265b6410d2c44e2bbcecb9805dde36fbda45c2624ae2b116fd2d2fea63d6d8d60d6fe229b466268e273a863983f6c8538cf190894f1dc40be9e18ace1bed7bebecc09d8ee50f8409a6140f03ac08a9363052d966bb45e6cab8101cd713a37c0b154887a6262cfd567f3034798b12564cfb3fe936b13792985eb651264a1f18f7315da70a3f41981d32ac6bfee215ca1b18470c57140661133e0186a578848b7ab8309f1d89d9ac2354301a19c510237d0854d315538327569a7265640ab72ba7492ec5e3ffc897a9492cb3fbaebf635324e6cffbd8d8dec93d2e1a621e4bd99aa10e699e9951ce5c090a1c47a8560a3aad7a44e7257e950c37fc2220ec52201e97952771d13d07edf157844a91cb56a8324e12a0321adfc128cea1755274284ebac1da9c98ebcc77ec73b5c791d2f342385343298cd8c4bca037698ade5540b363af18ab833271dd54e8033cc5441386ebc6f6a5fb54b96e4dfbad25a1502d0998c11dfa88ab0ca9657013c1f32c55718fc5ff8362d69714f7a532d3d7b0b44a135725e858295c055a1a7afecdc38301ffe9ab50e9025c924391182d3000b3c4386f5b75754b03b36eab17d22fc64cf014327fdf05d9ea1ffaa8b41c24ae83e935948f0d12b8d57ac0cbcf13a1cfe0629039196e9e641f8158903f7b5a2eb6b587d2f4b874db09c22a949b7dda88a78e667b3952161d2143e286efe69e2fa5ef450c58a92da116bca7b8d94e005605f1450506480ac3f7f4dbfa57dd2f5d9c3684fba78921d82765424fe1f339c7addf9c1f5240f5570b421e4b978b2f7bc8ae452747438028d9877880e210897fbbef3820b47f6fe25521f00fbead6b27c1475fd17f4015033f3bcfd22629a9620fbbc78678d498309c4201a803bfe9c4f323a82cb40e7007fca20282a0f1557a5414bc365b8ad8128c1a14feca5d89b0898be49cff6a102c86edac060705284239def46f98edaf391ae5211c298f29bde360fa36fc2e03f9d698c325169f9ed8f86270d6dcc44d7c1fe60cc9f19a99e8d31f2d0ef81af56ce2c50ee11ede5a89f1c78786c07522a6d87aa8f3707a60fc5fdda171f7e89a20979443ac449cde866ad518aa55c4ff1c619ade3a22a6939b08dd51dd228f45e6be56881720390b58b82405d384ff4fe66366cd1a1df8703e0b6b7168e1a32400c431a71d68fe47fbba968cd06e4e383b41b7207895e1d14c900af23452a880905f7ffa518a7e9f383b115ce6f756aaf1c5da93aa517807c5f93b0cb3601a21e10cc23873bcb61fcbe5cc415329ed8f7f25c59c536da075437492a83614d3b7a1acae97671401a11342c685825a7e0fcf521a931fe91d55bbe0af1ddfc8e588be93eb8771b343472a99f665832b1dea154509d95554e2fe6ee443d5e2589583a31f1c92c398295b9cbb7a94c3fb012832faa7b08eaa40a900d1524c243e323af07c82028279cea96814338943c6691c3b09e86aa0f1fb587be24d2a085c2c94fd8839f82b85d0ce4dffe1569e0f7acf18e7267478fe5c59f489a1af364fb7a2d10a647e28a8e6fd1298240d4aed6aa548cd8855a3e9f03dd7ae5760d8ca4c2763ea680435c145acdda8202ebec79f39a27c350aa557b970cb7ceedbb1472340aee53f1f27ce2e5859a24901b09341f7b91034b9a1389c96021fc2a3e30beb3edbff5dc1087b0050344cf43976829389dbf2e4f6663cd5c16f234a6a7547a266f2932919e2d9f6a10e278f204284228388cae631c93ff295aa6c52450185e2d1259473b6444edac497c627d831ba8a9c29aee662950db1031567222102e8dd7e82a47cdc77a23c96d1581e76c17d59d66a3a7a1a067a60829cc35be55d10fa06d5ae10daf44a4d8f0722bc43ef2ffd31a0176bbc370471e6ace9b00512501b3b34c8fb9ba44b289351cc7f07b891054cc8f493d186e097b85b1a7e0538dff95bf68f3abede879925a01c5ddcc58e388fbceac5df40233dfb071ac787ab834da978f284d7275328bcc10a32e41132800b138930f0d4359f83f1c645d195590bfdcec87313182713b2ddc5242f5abd5845ec0ccc422037c901f8a3239f73f2ddcbb7e847480640030d998153434b1fb7c7cc2f788e99104fdbf0e3b1d49daf1640fdfd351cae53404619d30cdeb7587bc55bccd00fc5f661e295aa08b1ba09882368c1edd88fa4138a4bdfac195d734f20d2d388090e9938846823426140cbdc588f75b049ab4982c3b5734810475d874d3a4390bee0a571223b5cecbbf0b1ebe95a5544462d90f1a4c13e7799c7dca2eef2f69ea299a4f47c8f1a53b77dc07e385655d2f16102cd5f2a2411a1a30dd1c298974e48af674a2523b054a41d36069ee36807b0288dd7b231a14687859c73abba28bd50aff3e54cf921807802ecfd40f3cbeb45785eae46dc02e381d3397c26173a07cb4788ac141050d189f793a2d143368a3b9d7299a4a72111d2c4197e28a1473d2f386700d17ab26356c552396b2281dae6379717d26e2de117d169b0f07699d595aee9b303200429f10c15ffc9ddeab9dbbe15d037ee9d06767d10af8a640173ae58599d2a6bf9d53d00e68adaba897a1c886435e722412af1b92e736e75977e60369dd39c6023a2edfd7cc28f8a918a27ad2202d10d7579c2447877a89b7f4ac7d08433f916eaee26f8a30c289fc6162db19fbe7bb94dfe9eeaff6137a24c940175cb451308a855a2d4e1d1b0b0bc7861f8c890062f3695c40eb717bf586b96a8d1378c92a76f52346f006d2ea44e4b40a9651467386dc032b8162db9fe38d685b36b821f97c16df6ca1d98cdc6921727a3077d7c8bd2b619b4bd3e673f491ad15c4e2153aacb4651b9610d930e5dd52db377b9ecd76d232e964ca1118d8f2b763af10eb4ed265ada09b546421de8fd0ef1d8b5195730352608ea5e610b37cf980e984243b53f11222930d02271c770d794c40371464e0bb29aa7d9d6c504c26aec42f0ecc04bc6b270dd1a0c106c57f1c644bf11c94d5972d977002c7391194dacc90b0bda4ab855d396a5d2f23ece741f615ea4cd7e0cc3c04b10158e1f33d2c32cf8938ff4418ed43d0cb971ee95b89a0092dbc1c93417ab208986a1d013ed1c5fef46516f4d2be3db4afd5edb07230b0c82ac345e2cb5a1f55770180975cc278cf11f22396d28d933444476b8caa2a4f1bbce68d6486691380f3190f1e0ea7d7041da4eea571e76900afe537f3946dea6c2996386bbf830f6b6d37a0728824860802ad07f4d5993820df48275e714284b85225091f5132f290030aded2b6c8071ad206b07b10ed41c427c88b64d8341a4e7ad03a0ad107699ee4566b34e28c5a0795d63c44dd24107901b9986f3ffe3c011b84320f8eb44a0f4ba67a28ea2708ccd08e55009ed8934bade6918659074661db5fb386db59dc9553db8fee320f4f68065bb5c99bd200362ff5a66d4fc8500aa641d6067c74db62afd90203c7022df49dc166ff6a8802c4f56bc435d106599e4d0c704e31ad08c9030477c82f60413512634dccc382732ff636410ca12ac4f2fdb2c7347bae8bac7040cc1540021410b3b3fc93f066493def2eae11a130b3c73e1f67e2eaa41148ca209cfc2aa2030885f5d8987dc67520d03e9f04d98b6fa43f0b47d5d25b79c3d69f58936763010c9c4d51d5508aa4b8f1eedeea24dab773ccbba9c73a27c380e158a4bad7d17b012569f24edc30d518b9d88e3213008110ff3f8252c721f1e6b0365d9ebd4e46445f27c2c4fe334214f41aa2a6d4e541e05d5121b1fd8705810d7dff1947c47c34e6c0065b883a483a99a219209a7f6fa1c7f4ad6d1858482530604e381ec31e2fbcc9fe1dbb8c18359b09374ec0486df81650b6786faf94869d4cf4ff88cf79510ffad92988710ef03a996639be1b5174744ef8b4bb81058dd6aea57bcafb65a5b174b72a63462c55144217fdac4a4d9b955faa1243c9cf98144e18f25764a4fcdfc2b7d64e65fcb24ab9627b82c2b4c945927e1918067684b2c0cf12486dd33c44d58fb882deebf9c0e7406ddbb0b0cf3250b78a3e56ca999c6ac2991e7d004f6e8649fd44b8ddfdc40093e32264cefa4097bc01661cfb44527e107b5018a049087cd06bc26467880472766c2c042d8351284e7024e07e99d812655b4a2c2cbc4941eada0de8fd46e1a675b19b0d92b29a59e2316139437c4263926cc87efed007b324e95a5d5a6e71c21f0ce8c7a27201a1c52ec4582d663821055ded918d1323d5aaa121e56f578ddf48d80d9664841a0f9da85cfd093698831d98912f05649188b935aa63d61ebd97c84795df7815818aff1623eb29db200d80343a77aef981fd9ee220ddc0769e808b1971fe7b3e2888388d9b38754edb9c43b3834c67aed0e42176aa5301317ad83a8d04bf5a11ca474ad0e5a9e7d478951e8e2f250056553dbf882c58ec2687304a4e711826b2e9083bd5931118532d63b61f5a3ff07b1d2f10b0552ccaec5353b86ac7dccb804e2ddeb05cc2b046c5799a6ecd1a1efe04a3f5ce639c713d29b64878c6c4c664c34ffa3480b3614032ecca94cf282e60504f6a25421b05d79005665d560edf1a7163e94448698e7c01fb7c4cb4b6209fc6da9d549bf7ef5910b9845ec91a79a83b4d942d018a9772bb01fb7320ed0b6f054ef38649d46f9b154cf384816a0911bc04361fd93a23a7fcf9e2e8686ac8c90c2f945a202cb98513eaabfd4cfe0ca410c0e6d2bfbc09d08b94351938fd191f5669bcddfedc494c34d5a30ec9667364c87f6d2203472973083b6c2f608c5880ae2f4c699e1e03cabdb924ec07dab979a83779b33057dfed0754d59b9a578952a6a414b5da39bed482ddf51137a6bca5b2212d61fb2793ae2928e3784f6a29290e1764d3b4b4b9952c4ad497a4410da23f152efbc7a11a43912bcab67cbca638a3fc2910e8d662a97fda6aac3a65c00071a34a387cbe89b780a429c5c4cdac3eb184791a591e730aadc2793469270c1fbf1385df47f12f922fbbcc79302563befb6f22a11a53e703c2bd4b1c028b1159d7026d0e23175ca8eefef8c8d12f2d91c79591caffbcac8bdb52c9acfcd4c650cf30a20550b54d797b39b06ca90e570fd90b3339ebfd8f937613fbd656fc80edbf50fa0941e3bbe9b6261b88257e8be6e12f78ab2e6d3afbd27e1013227a957b81d4d205ce1dadbaab6d685cfd8635e40621c839d59742838540fe44e04aab3a7bc5820103b4a07821d3da163aaca739be6ea1caa39a4b1b37ca99c5e8fbe3cef083fdfb8ed637d61160c18e971a50a070b40697246251c4aabf0ae3401b6dc9ee5491946eb7250feb96ffba389c9c7ca4ff6de7b4bb9a59449a6520bbd0b1d0c2c1eadfa5aa5dc3029632c43f3510c6e313310e610bee03803d6a1deddc72ff481c6f7eac3f8e51bf3e959ef19e5a95a4fc53da2631e0f9f1a352db62db69c3972623135a35c2834623c67ec1a6c1c5b7643e4e4c17a6ab9af5dcfb4bfd91ae3d8b6e62627a58c9376742aa594524a29a36dd5a4ddb41cd6cd8c354dd3e2269d689f148ee7a88ef81441c25bafa69428977d8b526e685bc6fec81ff3a47432cf283742b9a18d9357885478d51e9df8a6e1240c702b9691458efc48b12a6c1abedb45036fa78c6c3580916848c9550c6e188b7e6e71c3f8e54ba4f9f9c522ee41f3b5e8869c54f4edd12729292929c9a7816c0dec9c38389ea3e2c14d8bb21623e24ad79e5f31a8f1cc0c46af3f4e39abcf3a278b2a353183d100921098908441a65685ab671598d60678d51d6594b141cbad132cc940ca17232d3d8001db2fb798086d71058a2633b8f861c12d5ac325c5144d423100db9d5bfcc61a4c48e183144654ca00dbade51fa96fe7e2a701eed15e8b6e7777533636ce692ab79d5fc5a3db7796befdda8eadada539337df4545062bf3f7afcbcc509d2474f059a5762bb2134bf799ab7e3ad605f7e3c5ac52c31674c76cc1148f62a324ae233f7a06eb9794894b2daccebc73de61136048f8f6c615f1c1a9ab1a800b763110f11e4970c425590b4aadd2798db52b21424880f1f3c787cf3120ad4865b255a691d229a94797ec560e643787e3571f23caebef48b1b89f088d01ccfa4c10d99cb08d3f5573651e9428a2b404af34998970e045482d42b497d74c9300c750a674e399b7eb2e395b29fd838875f00faedd9b9cb3fa4f77ac1f5fc7a41e8f905cfaf178a1ed53a537acb2f94eff54f28573ed73c6cb225d71d59c6ee8edcf6dbe4a4f61be566f61bc651fb5be53096cedca362bf659ca5bf695cb6719afc8de3b6feade3b84e883c715d2784bb137777d318369be3db9cb5d65a67cacb66390b6779f678f427f0e439e78c93552a166b6767ce1326fd98c98c14680be3bfa878e9f1157ba701ac0484ab0fbd67a73591b3fcace2a56f927bcc4f3e9319e56bebeeee38666666e6acbb39728fee6e66a6cccc3aedce977b7477333333339f3a8ee6d451eb610df9f340dce87a19638c316a5a8c59966532289cdfea30bd922e398e31c618352dc62ccbb29e160aea8edc4bd14977eceee8e46552ddd2282871768ea7e57ababb3bcb6ca5cccc6c264c648af3e542fafa31f7a0f18b4b3808f36114f3523af56298c814975a25bfb8615c7ae9282861e106fc2506db08377a11b8a155b1cb2fca6f4981901279268610172003e91b93707f4ee9cc927b2875d1d6d0f2ecf60c2c8cd5b1613b5c6e2be1469fad8aded1968f9f9562e543fbcd59ce34c97286ca87567af69ce5cc9447e180b90b1ba3679b675f9665599665dbb66d2d8802250543dd60d862c0b87cbb6dbf4dc8c8305f69a460866f7795ec72fa764b43f7d65b6fbdf5e6e1e086eb52255951dbe4b6f3dac7c92837daaf7e3dbf2f647db751831cdfced7a68b4d05cd6f84f46b5f0af1cc73aec4bee6fc9c1643902ec5b854f3b0c72ae65d96394f733d99a6716e374dd334cd7ae7a5e0c26bcefc9b671e0f0f2747b5f3a55a25dbe3b461e39c701e3d17a456cdb6d2aa49b1a6de581fb5ea4a6f19f08ffea60ddc634620f29b3f3184e952492a2d3135c916cf2e33ddda7b532977d5cb9fd6915e7dd66ffec895936e4da15e4d9fa1fd268f9c9fb0cbfc740e9234b8f3a74c13956957ff345007f510125292d21253530c8a414391c8c8ca11339980020bd13577767888f81c41c2c17025d1cf331339dd5ed962024e4fb9544916ff8847cf02d0c24872f486183194384aa20b143882c098dc5046d211409cb181992b6c8c3db23cb161882d3f4cb1a20527be58a38833323c31268733310ce0394b1c61c4917406534a87f8e01eac5661d643ead537ac39ea189d3c54f329e621a5f3e90934873ae68d6118466fb51e56bfd54355cc4fa86d2957ddbaad9f4dc2fdfcbc3eb171cea65d23ac5c3571234e0a4b9781ccebe7c28d1110ce809947f0870333ff996093ea3358307202c0d8159960f6cd10030d58bdc17681dafa3127a057b0718e74f9b520d97a294fc92d468ef2648474ee8c34284d90ad1b23b305eaa06a876767c32a101b94d3e756dd7a58f53abdc18d7ad89762d07a60bc7255bd2b577245bf192248a5840852fa3528bd063927624cbae7169beac6886c815d7ed219b4eda57c8dc9c60d551fd9630f6184c1ce7aeb3198e21f8ce21eb251dc434a1d1c02f334873518bb213433e812699d3065edbdfe2955c6c376d359e52ae7ec0932c8882e2f2d7022e6b7c84930e0208223e8e83746e17296388c7ee3142e678963e837cb4dd597aa1d07c66d97445c768e763df3fab48ac974acfbb8297dce5a6b8a8bdde41a6e55b424d02eee9170f9e572f2d914dbf3cbc5c4739633669e1d7b7eb98afe3ebf5c421f37c0b763cb492be3c04a6d9608bf6027023b7107881b46a298d43a61aa4beb84ac4844444484adb8476465b2078dae60ab53f8875c710f2edba1db8ce8a02616e4aae58344b678344d61125a92f2ade4e44faa03a456a9ee3a21349f12ba91cc0ee63b181266e22617d7bc138992b02f462397a949e7d00d77be91ec4849bf6b6dc5381b4a3dd938476563e48614550462a523484c6847a1b9fd8f742b47523b92f610b49bd08e42bb926e7152fb92d8a40848c627dfbea964ebb25849aa5447e4aa55ed3c581197bd49750cfb5a988e79fd5aa05e3deadd14a96ebd7e3745ea12ead57a374530b7be64daef56cca3def4527235d45de1d83619e646df62b79d734e4a7b6c37247aac56f118d2afe21ecc6d958b1ed763876418ad272967d713a98c4e65d3aebbbb5117abd5462f8b9eb66d713b854c9e47abd819d543bb21fd9cb72a6e76767f967b30378df234fbfb0b57f8f8d5279b0a9cebd9bb4830ad92ce9f546a558d72c3965d6497972ebbb44ebbb5f7a652eebff507464691ff26074a9367778447aee68e5ccd5662f9e5ce4827c99574955c49e9d2884aae8c6c996729fb0dfd84f4d3e912a85b1e2405492139d42d02c82259455a9158de70bd747924919a4eee223033d7a475f6ecb35d91ccedb7c4aa5ea67c587235b72f255713f370e7a944b79fdb0df36299a75ebb7e7a71e8a917897e7a6104aa1fd2538f937e7accecf66302eef0c895f49d949452ca1db99212e8a54b20192487a44b222997689a6c9c134ad757df3206b2827dcc19736bb9625fb5e152a51baf1e87f887f5ea510806a2c3eb075e9d3948188df8ea71087e40079e625d951cc37ae0294f35c1f12600f0947bf0dd14f1c07778608decb86967d003dff1dd14d9e101f0007c4b767c2c5737f2336207139041acfa8e6c793f20001e5e10be54cada7b430fc017e64af92373f68c8158e7d3096b1d8ee9e8939f4e7eca3e15945c7e7d759e1842f56d723b3cdb0ca8bec33f684a6870c7c730b8212755a5f7699d9357322187d9ac67c40ed7b1c33bf07e52e08eaf9564f5d4ad1ebb0a3478f3edc855f5d9c6bdf11d31a8babd3abef0a66ebc063ff0706a003bf032d07accf43a3c4925e93be8ffa418b7b31ef65bca8b810597cb05a61c005e0cf54c1c2e30e5385e0cf10c98eacf00c0208ec720e6ba619bf9dadad1bc95f2ea714ab73cf07af4d5db4cebf4a78292d46f7e4373696a686c6c9ca73b85aec7c679bac6b9ebc9e13c8d6135281d8e6118d635aec34bc1055463b15abe3697da5dbeba8dd7535a555b553d8757e3b5945655bf5ed3783dd4aaea27af8daa7f50bdf3e251c4d2aaea3b352ec955f532a9a6cbf4b5c6a0dfbce803f7a8ae554f797148aeaa7be04522b9aa1d7c1149ae6a0ce21ed5ad1779e010aacb195cccf9a94b174ddf2c648b199c20bbea7e03c1922a9099d8494bd25bc97e38bcdc5c662ee7916c692ebd9164cb7647ac11edb372457d4bd96e48447ae9dcf4527204c2ddcc11a4cb9636e4394d2d028065aa6001a50769d2a179cee2069697ee236333f551bce1a5f751eb74770f0e3f1d1f3cb6047dea96faa59ea2afa0269e52cff9f9a232c3cf29bbfc3c459e35b6249e986d8f8961adb558bc9a98a19fb8b7c3b8d937978b7de4d12b9c1c223c37e5a9e797135ea260e2c3d487b70dd6dbdf9800576b7afb4d4c9090e20d971560b63c41050ce188207488c28d2eacb802642acf9ef21cb8f1c5110d5b10017d019e40030d9ea801092e690c815cf44c9af8700516196640c5880db08418826ae0c50bc478e20910896747c26e023b0aec2cb047174720f618c41e879809102c2002074b34f8f2431590896cb9610b971a42481105e478c4b3c723d92ac2c518415c6962e6062740f688245bdc022422212b6228e20c2840f698d43a9105cf1e9b2299d62933956e7b18cd3c7b14d33a22d8f9133c86894c71e967e692474e8c7ae6592fc94effd0af9bb4cac5c60ddb05446bf2d3a3520413975aa79f1deb7a66cf2cb43466161f3dd4c67c6b6234305a98222330d4ac68471a9296a42925c9a16f8d48cc875a9026a435f90e3527df9a14cd95317d674ddf5999a46cca7756f49d55c986be3dfbc980beb320373219dcf8e86146c377d684054bc69a9156e97bc9327d3799692ed24c97f963add8a3f963919820f3a135fa765ba58bacd0d645768ad645b6c8061d59d7677d647f2c501553c97c8735ac4adfce61659a4258bebfd06af9fea650e552ad7c7b1d92ad692457d1e9175aa2ef696424b3a83fc82c82f8ae4232a93a91554a9810138391c1ca5457fd59c2929630256c0943c2b854c1f21d6241dfce213644830f312223acc9b7634ebe43caf4ed1cd2a62f3ea46530d7ec3297c07c3bb5d2ce213d0a29124d6a30a8130d0614dfb44a2fd129bf54f4ed1c5229424d72d504d466cc7c489b7cffc895fc994df2a7cc5c9a6184b80875f90e6738ad84f308a9680a157df4704ef90ea7ebdb399c3f4a1f4ea01924a51889e563bb4c6ae7502a7d289724d3770d1c42f45066f1b1862dbeb938c1125d0e8572ca772889bec1501a492b422d45488848ba88e40f11946e3358c260118385cc529252145305cb77f476c955901d2212ea550bb573189b1eccff180c847208d1435ac6b7c730fc836dd4d2ed914bb492d3ab98538545b4248c4d94be7d846e1d6937a1ad902634ab7da79da7bb533349b6dad5ab38e71b5d7eba08dda2e0740d200941925692f4717edd6405376c57bbb6b8a1ac5adc700211c7534a29a594022166522a37944b1f7d9ab961377df4f946f499831bca9f8f615e3ee0a54e1154fcf4b63db25671c328f4d12b15d1e5d1ade286391f5d52d1f4b3e92ba3bbbb092e5f65e96310e9db1968f44f7459cbb2628c80aafcfcfcf8056aa0f813b6d0cbb0811aa88358d15a1ba7aff03d548138038d18c43fb08af4ac1359155dce17c3732f66e586318dbbc5c9cfb34f3141b66a2c4a11912d77d972ca29fde917aef0d82757f7a31637f66c6eec437fac06d52a6ed5b44fa400a0a37b76ca3fa4538a514ae79c33c6b845195db60ca29973d239e794585e5e1afb686a119fe7cdf32b0a2711755b27a4de38a956c5e9821ba23e2ab15528e48dce52d227a244923b6cd359c696b2878be23a90f726c8d6f34a3a164d5cbc94d2bd0ff988877b483fe2aca6e706a356b18752ba1efb88548fc713e1883e21f988e9c354f5028b1bf2118f0844e49c33cc3ecc9e316f3ebb5ca5aa951bb2e80b9766ae7d679d7557848fc0ec63c9d576138170621fcf6f3309f7413c15e6274961d572415ac5b562cce21eecdc8f7de10aaf52fd19db9ccedf09f30a291be784f188fd516822c53dcedcc8ec953681399ddaabdc4bd53ca7b21408f64341560f21f0ddca9cb9a5795800cddaec5e9b4af515771e615de23667967b80672d494c2f5bd42db511e85e9bc48d1eda782584c0463379c688ba21e7ac28f4ad2569893b52416de2527007c7c465e21f66c8a8c6cbe8c6cb7ee1655bcc977eba5e4ac7bce9a21f4fea33ca0d29100da2412f2705e21e46ad9245ad9293887b48cc8b4810fb96b8004e975c49fff949329469ba2a4041ca144acac4928c2c63a675624d92705c19e737e3942dda2bb76d587a7ed540f4edade7570d569ebb9e117058393500bd7f7b10fe21bf882d96cb2f1c983e04617b0f010e4aed13cbf5d691cee27c78e939a92a2f1d87bb281736d6cecf1e7efa4e90d691de38fcc839e79c627ef251e46263f7ec951b4ecb1e59f2375b5580cb1e77549a58ee18af1baa7c4710c4d8b3f14b2872082e5e4e887eb209c5cd6848e074741c67636363c3794d8eec723442aec713ae4725f46fe6ddeeae17daf8bd9a739e6599127a592a1e3b9a8d8d0d1ff1fce6e1ce6bd743d6250104ce9c89c3adbd57b3094f994d9629a19ff96b9e59ab695ba6f9fd42247f95502534e37c05fa27b073ce79e52a57392e73cf9cf3b80a5d257589128184fef0119706ad619b4fd46cf7e524e823099993808966e33aba13c7e5c891c3c66b74b8cd69b3e9bc14e26f276449509f755977369e6ddb969d9cbde60b51d9cbc9cfcb896bf3b865da75ed0b3de840f342d56b9e79e191cf790de73594e68173346e3d474dcd091bea37cd0b51af695f887acd372fdc7c0510dea635cdb34f7b0ea575ce79f70b69be24a8939f4ea72f4c82faee1301ca371270515ce73e2e8a6b7fab3e2439a15392d01a92b03eb619e58611088a941bc62020168f185d881d71e062e89503d22f60bb61e7befe0d7fcc3d5e4fb43cbf9e243d0a01af275f9e04d5299e5f4f7e3eb4569e5f389479fafc7ad2c413a2ff807dd6f20319f9ac4508266a8468a012638c3f31e9670003684d708965e510375ef12ac40f63d20e2d29f8f61e6ee54411f8479c1e32183ebc7cccc9e2db69d38d4a71293285905300d6c1684e4be6508a61b27ad5b98b35e4f043256d79925ebff8137f62c78e1ddb65524c1aca868897a688cf90fe11b847f449e9bc348dd9ca4a2acec266ea6c9a0cedb432c6e831bbf45adbb8ee74ea368f076b049f2228a8545d673da050dc3c78b07cec04c141a57238724c5cb9cd393b4c467ef9cd6f5ad9556b2b0f140ae3c1665e83967ebb9dadeaf881c7ab6f21f1b16a6dc5a6bd01775129968ac70ed749296dad5689e53a693b730ec8c61693f254d7b8fcd9375da57a22254b9652f250390e0a73ee2375f993ca39c7d8388e07cbc74e1014777176cc3297e50af380c6260704aebb3b4e8ec3a670e7e6e25c92ba288151790eabce59d983cba9e81eafda5797155acd373ff2b7a1bfcad99d8871c6192de7be54aba0534a19bd09482a51b838f3120c1a525c5d06aecbe5a282b90b54a247ef66ba31b121f1e7c4a4006111d33224edec44273b4e80a4c4a028c4b31384087b6f4c2e5bd14947274e82b84d312c3ae11f34df3b3bfc43c76bde19d1dc6ecf5d0f3389626688889f2a7ce841e802331071468c3153aeac006cdf34efc608779a97793797890c4fca88e20a23a42c5e2124519484153050c24b10d8484370b811c7105f80be80ed3edc62e22a82083398a832c59630603bf7dcf5f8f074acd656b9b39325c16271dc45a57076583e78689ed541f6ed18e01f536e177221177ef00d985f68f986ac2f4c71d19476b3583070179562a978ec741c3d47c5839b16e5fddd7336eb230fd5c7abfaa69742a43cb12c51a5e2c1f2b173c4a7089293c7128d93e7a383342b9e717d64ab05ce4f193839e7ed91e63a4f6f3efc83bff37624ed1d6f5c85a9ce63c955b7e39d3c2272d57daa266ee8be63ba713f969be4ca3ad5a656acebb18e65b6da8cceb96933b375720f1fad352a9807cbc7ceb55c8a312a8da03e3d237e1ca42aa6d5072c5935bd9ce8499ce768ef951baa54d6ce34b65407e1118188a7705491e663e692254bfeb162f4ed38aa0c2c89a0f5ece30f5c7ed5e0fa3035c1d74f996fc751717725305d83043294c2c84224a58a20985c896387a12ca0b8224b134f718bd540438728ccf8b0030d4d22d490a4c4501b668c81860998286209a1a12186b802f61846414d4a8051450e6800db637f9c71462c95b25796542a6bb98b4ab1543c763a8ed26a6de5ba13d2793e9c776404224db77f01aa08843904e9ed070801c8ea43d5cfae07f3d90de9df304e7a91a39452c6cfacd536b79b66ede41e9635824ab5a4b57da139765191519501589e7772b2639f624abafdcd8ba0f4fa19dcbc0eea6ab99dabb459662ff7e0b8a78851ca18633f7f2304516c1760024ea00ff864bdd597808f2e97b8d5638c9e72d9ca7c8bae010ca171034667117d741fd1837891e3a38b105d4da844e7197fd21cd4ea11aca911a4641693e4391eb43c4816311f9d47842c603eba4fb396364105499b741688ce92ab1deb799d29b98a114876edb2456f213acb158b7ea4ddcedae96ec75c2599b81dda26d407b3938eb91dbd5ba6b1a9d0aeaf45660b9853cb448ed9c9b0efa6c8f4ac3a8b47b622587dcbbc16680630c79cca5abf52afccf5c608c739839ba6d55a6bad95d65a3967df2457690b98d3efc6415565b1aaef54e7a94ea4baec7c30979d0ff5e662574211b98473e94c6e7c18a41e83374630e99837d95cf3ce87b3952557b556af6e6b6c72443a960d896f04f508d45d65d8386763f689a5646b7e3746b6162676f9877d0cbbcf71df0ddb7b53aeea3e972b0cbbc19d5f0bd3378fdb77e393b956647eb662d55972a5b21553c92b579863d8746e923d9bd898ddb25ff6143b3f567db40af3f676bc4d72566298b3f8c7f698efc856bb4df57763a4b600daef862d86c465c9d68d912cfb6e6e0af3cb9d8ffd78e40adb912b8cd52aacbf163a647197816eb7ed3536691576e3c3922bcc89c815e6d149fc6915b6c5afbdfa8db70ac3306761be83390fe644b008a43e7f738d4b5d3a506c8a064639caad76f6b07b0e51df1f120f70d355499aa75f4833bf90e6c31b2d0bdc43ba4ab64ec02b197f5e785987da2590152472553d1350902bd9df11b9926e742350ecea51492102fd11a955d287f0472dad923502394fcf23373e9108890622908c4b358cedc608d682f56e8c44d07a04298b40777c50ee7aa8730a42e297c02073773e986fb239ca533be60c4eeae398c71eb296975256efa60406ad777e63e4a6482fb9a1ce60e6fdb5d05e3ba76004b98bde791838639c25b4d70a52239b77be39833cbd75decdf41b2311ec3c829d778ea93081f8445a25bd478456c5bf9891ce39e7be1638ef9c41acdbe1bc9be9ac4d8571759e2ea279d24b6050a58123ad9220771968bf61d0ca95949e92eed255b282be5499cca6495067695d5a2f2557944a671a9d524fc996f494d24da2ac9d7b839b797b782b607db26f9336b8def9a46e705bc8dc7a04d23fcfad83350dbf4d2e03738dcb3711eb96686ebf9b2abb22d6b56f89741b825651ebddd4cfe7ca1575955c5167908eb949a81bb76997423c7369f8add2d03f3f5bc7d280557baa3fd5b8d29b646346b44b1c9ccf1143885ee54a07476474648e16e4e86e6eb140668a0d6088428d2ed600a36bdcba71bd91832b2c9801063080f1fa42f4fa62c5e538bc8a6e2d4ee3f052cfd91e442186e572b98eb8d24115621a8909542c7d6cc1cc711c6e72c122064b182a92a840fae83c767c2ce6d20b8fbc7c4dc1f47cc6f30b8b99df68b0288ebc74f62e163754edc816834c5c2e978b59eec05eb8ec72c5dd0d69e7b0a13b9da5f46b3dba2a3a2bfa4e741ee91caaacb35c7912555db264c912fbc3c4e8b1238f398f5c426712d577d21b344748286e64fe711f63c1eda4b7a81750c9810b0c37a44106c8592401440e2f2f397cf9028caff710d0b8ea63a7628c593696ad9b21a860024f32607281311c0104b25b2b7ef8a2245b4b2c1420864017f8d5051954c0ae61802ea09000fbd3b265063f5d60f002498b38c4508a40440071c4aa421303335c88706a00d96f16e51b5b85819d350aeeee86e1466ff9eaeeeef8fd85e14f11b9cd089a29ac88f9769ad8c59ee8b20930b3e3a2b8edfcb2b285a49452ee684f2600e1c3255109e998981b9f5f39343db78a6b95f42c2e87d66ab9482cb5b05b742f51f60a0eed4720edfa373e9c0e45ca18364c8d1bdabfdcaca718a5b829d54eeb44c7f11c1e1ebf666ee672dbdafe72a3e7787e59f9c2c505e3e586975f56b678dac50d1df0d10a16df3dbd6c518c9ae797152b36d850609e5309b3945180919f6d1510b7bd82b9d185c824760bb1714ec864f813d27f9f38fae8231491ddd383c38fac61d9eed33af123cd91135e524d08818413354e54f9e845625f2a3e7a5bf6389be33ccd31d205f77af41d688c8172460c42445534b1e202a59f25ba7c01466771eb041cb4f0e1066198015305187d875b1a18830651194414892106185d3626c48dbf3db9fed1eb1437745ef100acecc18067c79962ae4ce2860ef8d8bdc316725c591e60a8cba57c87c5a170aa17eea8d520e3fa00e3ce1994b6c0e20637d65a8bc69d33f8f072ad0d57b829c0f0a69f36b583c44b6fad4dfdf42e3f3f7b9612a98830a44c92a1c4f232a8572d3ba8e8a574f2528a1a1fca201d602885baf4aaa574961e7619251a0f1bcc52d1930f3b0c0d1836131217a42e4d8d05f3b0b5f49493874dd445397cd8461b187615a20e22ea212bfd63a5816424534605663e8c666ac0b05d3f61827a2583c060d182840a3e8c481d8061e462825cb59465a48c4e22100f6624c8dde448afa4949209263ee4250b861ca67bd51eee643cceb42628e5d2fc968a58c153ea07e87a28996ec5a6293dc5af9145133ea03144c41a5b300c769872461958b4d10232b8227410f3ad0eaa5f94f26107751359e50730b62fc11b4d426b8841841458980b28d01023882d3394e1014bc4865c2ed70b559b32a44161e3d2e98888d7cf2ee0f43be5889f44c0f0d30de0338021f714e5050641419eb2162edcc50bca61bc0f59cc10484f7d52b7673cf5d40f4d4fdd81d8f2d4553b3cf59d209478ea3c41e0e0a913197ac153f7116288a77e4488369e3a92156cf1d44d18c288a7beea7a6853bf752b5b263861c5171b663003284980f6470d41c4e0850a80b090016dbcf2f607a6b7aeea7a6c0d60c8a1429c90c7d71bc0d0c7d7205f598b071f32971f9a7cf559dd3af9ea29157c75ef61065f5de54395afceaae1abf3e8f0f52594c6572f40d75331ac879fc79c005d0fe6676297a229da4f23a6065bb626d860fb3c6a1d269a43336802d97c5118df2e9b5ae701df2e91dae551d196a1d661299ecba09203248e86bcb0e20688250a173f5af490c64f0d60cd1725c1fd51fc9397174e80d0210a1e76004bf0d28516519e5451640436e617537acca7c57c005d0fe673cea22a7e7a4eadb5d65a6bad2e80aea7ca90f574e7e953b7d46f4c41911045403cf50f090989314fe34fe35ed74383440dba0420c104ebab7bd753ed85c053249078ea20743d9488480a0efee4270f40d773aa2f444143075f519b5c2ed70bb44a0913058c2f635e5e4c41c24b03c2e0610c144be400694cb4325952b6bcf5214a0a158f617ea5d0f09803a0ebc152d3edbc428aeba7e3743dd343ee09580486393ea6cd818da77ea14001c58a7f4159234a114ffda6eba193916460c60b4a4c0cd5e00d2033e0808d34b458630730a0c8418dafbea3eba9fe23c677def9075d4f07870aba0c7152a435aa60050af8eaa9aea7524a29750fba1e6a02d311afe77c72de41d7c3c17144124cec5046107050c91287164cac9469a38aa62eb28ca9e109592390cbe58a816660df16e282c68006416990d587ace508293d141187115f88889eba8ae88da7bec3821e9e3a0f0bd078ea3aba1e8ad4b5d68af43509a97a17c313153429630666c400560234b1c60c636658b2c1126075e2abfb1a5f9da708325f8b10f3d5515d4f5d2282c4c3d06f3e37b7e97ab6333b40a16122c4133e80f102cc144cb06048146ad01064c38f4b8858251ec93a77b872b2e1c76583866c9c605bebf08b871cbe7d3a691d1ad9622f4da61823c5c9116d6c01db67906cb57e9021c90d239a583103b6cfa1d6894db44e51eb184918c0f669656251922d3e0ac2f29c739ea3ebe1ec7c66b752d3f5d0cd79072e7ef3dbf56c1683820b95329478838a9f2f3e66bc1086061a7478f2068831f1d80e583ce6345d0fb6e3adf30e42bcf553d763ad16d5e6439caf3e80d5af0e5f3d1546071c7cf5aeeba99efd743bfd4e4f4d1d6ef8e95cd7335da55a628dd7ba1e4d8922ec1266c4860d13218ade503223e88c1131806de40b2d63baf0e10c3784e212958fe85c8206356c668720cad8d246161bb6d031e3a7c950123ee420c9684ad687a825acfc5ce2e7a76b5dcf4cd110e7290b40ea3789a79e1a63c4e5a9675d0ff59a80ea56004ac4f098d7ae07b34d7ad5de2409283ef39939d6f564666ed8414d8ef8aeed4dd4de46ed6d45b6a2eb483652eb706926177c7b8b699deedbbba9bdcbb85a87937e9cb40e050a6a2964a575f8958411df2eb1b4cefd6692c313549688c286112f68029bc78c09628c2621b480c108b05d26b54e54e2dba592042397a4646a2966fec816ebcb5b9fd56df55b63125f5520f401f1a775f825b6719873d8a4d4e3fbf4ada34b6a3a21fd4a58c87219f3ec96dbee53e4886c6d2e7ff358f0945cae8940da95a3da6c2f7d43719867b1dce8e21fda071d65a155d2ebdfbcf82357d2b12ebab887f4f8e46abfa455d25968d5a5403764324bf8478fc75c49ebdc97ce02b683c8088c3446a55eeb0db49a1293a241ea6627c8ce183bd8d8b1c68ecbdca9eba198a7cab0e9d356c7343199c4e98464181bb371381b01ccebc6496ee34e2107b0aebbd8c98e7aa79009115fa7639403aae1e262bfad13a56739dad2512460328e8de6e05be8284c32a55e8839877da8efc62ada6bd886c93085141a860106b7af054d73bb650d50c90348d7e16a8e71b11b822d99ee82066a8eea2640df7aec4ae0ae88e61a8d82696eb9986d4180fd429fcf342ff479ebf2a232dbf5448a518f9a8db2713c658fe770f93df43b63a45e8ae334efa61ddc642784c710c25b8420e0319f18e6b51bc27a5bb18f4e0bc346a3dce9d229943ba3752cda8b02bac3c5dc8742b94442ebd337daf1acb556e766d75329cd6e4840647a750930094a00c2d7ef048b592fdc3c89f6d6b7da4da0fee6b51bc26434c1039711988c267878c1d7adbae63cb292a0bdfdb48ff38ca751fd1db9af39743ab135e0a6bd872a399eb2fb0093261eeab702eb75ce39b121ac578f9d0498b03c40c85aa4207a25f231ffc2aef2d3ab3704131a70b9810c68200d8950bd3b0930f1008b50db89c7be13302701f695000429a4730e5dd24cbaa550c79376be6ddbe6ddf56c9dc7b58a6e6eebcdf685914ab9276c3ea1dc13ac87d36bb87d6194de9218e219904ae72e68a0ec09d03f797725f0007ec0858c2678e042461341a0a4512894bbb9f4f6c86d5cb83976eabed0e7b9ef041bd6eb39548a77f2a2e8ed269d2d3dd228e9042c095f670fcf963e3d7ac38d5d04343a84f5b5ca48e5fc6e1ab4e319fdbaa452de56719dde7146d9915023758ac3a5d8c7a16877fb168770abfae364790a2ff01875ae5591e270a38e5cdab67872a3571ee355f08e9bfa9d5d8f6c76fa8e6d94a3727624509f4e71b8d52790f97c0dfba78d08f8b0c62108d8f2758caf9f9350a334639b7e23e589499eee689d0a61ee91d9cc0ba733f99e9877bf1c39257a94878b39679d629e20406007c8020ef0801f07733cc0c6010c04001c02e6ec02e096cb15d6393b0e001888e6ec20b04e45b9f593c7d83c47474742996f586739cea31cc66df1372a7fd34e96db4ecea753cfc9378eb31ee72e5b48fce47cf2a80f377ae71991c3ad97e21f616c7a65c338daf5e4787e8151c46f93abb9ce791d4d8d2391adf82357985b7b7f8cc8e19bbc3244da2ab68cdbbc79050ca18f39bc085ecf881c6ee3fd386863f3b1ccf1c524b932d26a2724be1139fab72df32216dce394f2f0eaf896e0c058c17327a4b9cda371acfb68bcc845896bbde3366f56e185ebb913c242d7715efcaedb3edac3b53ebb14e2739105dce6c5a2566144dca306372679188984f4db2cd73a0a65bfe892361e83a87ecee3cf11dacfbc7ed411378c2e5713ba7131a95598a3bcd8a55798db78dc0c0823982654369fc1c11cce93c288a8514717c41030af5ee4927159ff16bf981423532a7531cc7b098e4831a91980b98d48adc27cc39c2ad172d3b8cd9b545c7e29353dbfc090f2fc9cc6c5ae67fb2813d7ba8d3f8d05f7c02212f7c09c2281611e7f9c388adb52fc23c53d22977934e8f20b0cd28771cc63ce6f08e12f5ee3386b238659cfea66bdcc3347c56dfb4213bcf6d5ecbb4d5c7b5006e734c7dbc1c7683daec27a7b53e6d445c995b78ef5ee4961ba5c515e89e6f1bb9b80079e398fb41fcee73bd817b25cc54b4929b8eddc128a7f44d7ff804a824a82b216854a92049504d5d26e3649924f82a2417d679323439db2cc2687bda713ea2f4d1214eab7930d8f24417da625b1a72fb4dd1726b9495034a8af41322937b94929ef26a5f44d06d1d236e7715adcddd75a9b04f52b8010e455619067577d108b3abda1846624589bb9e6d1cead671ee699b7d7524acd25739f04983c96655f5813669e7d219257423fded0bf73ce0b913ca70365b3b9a6792192d7dc7aa1bfa571ceb22cdb5cebdc7a9c0e94cdc9e6c03a8fe60b6bbc9df3fc737861cdadf19450fb79a1ffe69ba5f1f09e429e974efd33e7692991f4e62c3be73e9a4f9e3e0e91bcb4dcf785a994b51ee69ba78492501df3eb9d70ef17669eb523b14cdc64e5263789c4f3dd6cdef9c9a5fb77b37d44b8935898f2f67bfa6e30bfe1dc7a76c359d7fc06db36977673e99a6b5e78a3494d4a2e0a9a4f0945c284855c86390929b573639df3fe6e36b72efde475dfcde637986b2e6fb09bcd6a5f9882ff0a3b9e044c32cfbcf026735e7a3ebac93e22fcbcc4ec3558bcbe48f1e2cb0b552ef686136c88716d1a5aca48e26661b871312533ae4d5aba16298b9b556174b128ba898b35a144916c72ed14335dfc40832e52b044172fbce1c699ab3dbfbc8c2153c61a5e6650c6adcf2f2f4a5e92d85e2f477c9872d1d84d3bd92b44b6b5d150e705071bad891fce8beb9429e1dac870568932da18343589a14c061b964492edd2020f2813aefa44379530c2724823263a844d2f713471d011593b4c1cf58a6d7c75719dee1241bd625be61583241b197859ab7165053959eecdf3eb0a0d2fb835cf2f29da0873eff34b0a325a70699e5f5248d7e59e5f52bc008e6bf3fc92228d97142d838b7a7e49a103180324d7b7cf570c5ef87e5d91c10b066686b62f36dbd581b2c9216bfa368d7f2a65ed17fbd4715b6b595b59897c8dd129592fdb763bcbd595dcad036593a3e6d29c3a6ed3b20e4e2b283666d63ed28fd1931f5598af843ed72a7bc3cee4f9c3aa8f5cb1b78f6ecf7b29a5fc543a9efe6d1959d2d3fc8dab65d25d55b0694fdcd834eb69d9fa33538f1400e14fa0ec126b617245273639fa44125baf354ccec0d862cb8cc63b62658bc6ad0f919bfec86d956b2b6dd454d44ab3ad891a0bcfce4eb6351116ad8b342c5bb6d1266a2c5a5b6923adb1d4b06f908447c02d1a6f9a6f09e38097b87163fbd6755d4fe69d3d71430df40d342586d0de457064e16904ecd36cd31a684a6d29750a9d426903f5941eeaa09e42bf1377ea7a363f7134fdd338fc90ce6d5a2dd34eb6e6d8a75ac646331d74f0d3b7f8fdaca732edc4f64fbb6c3ba1b99cd56868686e13531b72890c4ba55a2213860cb744a689894ca79db225324d4c6468ae46c3d1743dd3694e35dc4eb72212924f6f9a731e1ee0391a1969e1c1f2b16220118c8a7f9c8cb4788a55d5e3905ae7b20ac5a503a3e384cf747ce65bec325a4f1349b62e98ce9552499d83f9ee004de6db3d7d1c464de38cb454e940e336232d4756b4645f0d57d3f5645ed30de9b79e8323ca49a2d2d86cd31a482c8a21b4b3d3502f6315e69ad7b10af32d06b54e0dab740875107e17011d1c0aad6ad73c25ad6aef92c420d9aae15854dd23c2fee3c0a98b80f7d4371ad991323dd92f720fccb31c9c96d9aa754d801bb468d1e5719eb568e1c46f3449f2ca1d1984474606b2825d212504f30f7885c32d1ef363958c1fed726353a48de1f8304e28377ef4f9717372b93040e3431bbd7011e6a34f2b3a54098303339050238b1184e1c318176c79614c1a710bd00396a84992f28b1b9da373120c5e4fa9a76647ed9c13063bfc9ca91b2ed878e90d002eaef8f6489f98b3861cd14a0b46f779f291bbb0122f45308c4d64f0718b31c61859d1c1307679713185d6aefef9d824c63863d2189ff9ccfca88fdeda2eb3534c0bc05a9376f8eab5eba9292828a50e78ba85d2d32d7ca0ce61a654925dc821a40cf261909749a0f45bc44b4f7131f3d23d8b2e2f5d75c4cb971659bca08b8f60cca23593f2a389e590058e9fd3adc521eef0f3a7571b454a31af205e483f1d4b01cdf9110b30e4421c2c9840c04fb7582cb980053f9d42b9e14c7d7455eb4c9f36cc1c1e3f7d04612d5cb6802177f9e9d30670f2f490c9f0f890cde80060462752662c72c1740e239616547981d3afab5851fcf41d1e9f2d56fc74242d68e3a7a3e0fae92c68b9c165450a7eaa00daf21a226a1d7e5961e6e7155f7e7ab422c4cfe8c59dd3cec9d91748f9f6d6890f60e2a78bf0d3914c4f828212fe313f8ba40617a66da8d40bf7b549ce2a6433020000000003150030301410088542b1509808c3f40314000e839e506a4a190ad424c9711442c61843880104008008888cd0cc560000106300b479d21305646feb36a226480bd58950b0b93287eb086ea7bc5e5392f202ab88e9a9066bc6dc5c46f8e735cdac8d156924cb73370ed34c50faa4d1a3407226f941a47d0c4fd9b2ef3a633a3f04a52dd00ee7c8af34aa95cd98b6b960d8c18a1ebd06ac77cfba8eed1899a4979d98496749b66b952d0e60de04ce18951821f29effb67f59d70617b3abea6c91be4907ae7966cab403307f735c389912ccb0a45a623f82edeb7863d3ca1ca987ac33aea6bae33b71235dab1be4358a5be977fab7582e68836a1ce0f0d5277e67f165563fa8a8db7f7f63d8ce61bd3e161e2b6f27ba6a992c5b5aeb69f30a815a60c704810a23ad63a10c712a1c4ef99403639d7140a775320e45f53995860ecfb0cc96d1303c02e24ab1c56716f74bf70b1b305fdda771d2fed5009b013026795187111dd1f5ea7274a47ec3aeb06946c39d86cd58ccd8f06c909920e23821cd52c85ef61de4d88b6eb73f54316331004d646da1686984c64f54ce1ecd8a9d2c9e8a13eb86d2f0c60079e70ff30feb050c55f0ea46b6625dcefdb83c060ce57ede3606313b23573190ed3264933119eabea3b99f82b2dac5a8e82ea834a3ff428c8e82e6cd367c0d24625183d7a1b4e503b42d4220ccdd299a702e7d8334cd14dd0298055934e32ac50d7a2abf8a6a005dfd48056e2c04f8135933a2a3e13203a2dd80e867ea4867965108c494d4fe00c891937ba2be8d5dc4b5e69a8cacd8f422ea6d12c894459a04182a810754611d9d51eefaf8fd045181f130f60d43215ccd18cd3bcb5234537c7b643705d127df3d07afaf498edfeea2f2371abeca91c3120e1ef59f2b3591f084721febe2acefc755513ac2871957beead9109badae7663402e7ff498106ef4ae2d2f18044b8017b4c59166a7d4fb69b10fb8ee3583e3449041d29e7eeb7aafd7c8afc0ac01190843fd2e5fa392988d75a94facdf83fe3a04ee14b11ef4e420afb56a724a4d5a8202b1a62673037b3d65c2294826f289f171eefe520935b913b1f84492982f42c521243fcc1629af52bd25d1dbae2d0be73952ddf9acccd5a7b67807b13a30a38dfe020a475ffe49a007ac0b6b1adc7f5549195448eb5465596d7f2acad10f908f98b264830d8f21498dd58b093b504f6882f4c4b77bfd0de53a9ef8a4350dd7d16c7f3138a0eb79235c984fb7ab6bb9cf560707704c3b4cd3d2c9dfe335c42fe84e82d9634ba842598895268b2c89e0a6be08027e83af7a660c10c12bee12388e04830dd347d96fcd49e6b49d8b993c8ccaad81da0e50f8e976b80376cc4c43ac307e5fa145b884ac868bdcfa4485cfd725acf933818b64193b1d50924688dc6eb1ea7f3c04c4aa0aa97bf49de9bd404b330818a0876b2a682e4fa26d70eda4f0d3d5dc20fab9300fd584e54ee043fd5a64a58cfdb9649dc423e6266fd49f693341fe591d41a14bed4edca95d32fca263c3d34a5e4f8a91c73779f8949bbd73c084a4fba25ae9f5a09d520b5166567450352c789c53f297823b81c5d01c75a30b0777d9cadcb25ddcd58ed4ad8cde13c687901ab836aa43a4e2650e28afa7077abf02c00d55a5987b00811d44b6d7aa3c2deae7d5fbf04b24a904ec8a6cd034708caa9400fe37c8a566d504fcb380c66a17a67029610264fc0dcec04e7d55240698922359d447188d1f6d57cb002b7fa5a0999ae256c997eda039fcab11e9883290d43f794cbb55d823fbf4062bcbbb015bd646ea369741d4f5ac9e3aca9533e70b7c580010b811413c27e97a4af79ddf66448d92fff37c621d738e83300bc85c909f999bf2b53577e1f25f5e882f88f7bf543861fe0dfd25a40ad1fc158671d53f7fc36a9f2746815cad33c172930e22e38300c07a36ba1d6882491f8d86dbee57529ad22ff8da437969c895a3e34dfcf47349e4ee624a2bb87c311d214f0a4c62e63a85a68da83d9edf8308a3bad2207398d00115b9aadd6179f9418006bb88b046f488eda0d307767b3c361836014d7e7e11a40c7f6bfe37e856cb73bfe66e5e21829de711eb9ec8d4ab43a85cd000c3206825a8c919ff94b572824246413a2d26ad4a06c02b8333b133ea7ec8141603e5ae39a0c839ccc70fdec0c63591f0cc827569f46faadb5de648917a86b7305054fcdd3862689a6c4472db4b8c15d5e71025ac1fa8d8bd81f9260a63da9e4c505136d5898eb68828cc0c4e726911dddbfb2f87ca73e6198ce0fd28d15064771736e65f5bd752617846b38226ff312d50ef4c63efc2f0ce9c309a1bcd9f250e4f3670ade80f1715b16eb89f49fbb67c1ab8fb0bfecf48d0cfd6763c4a69f151d5d8f510e06e2e4ff1d52bc54ef71b4e61dfda51cdc56ed35c613ec596fb92279319e3b12bc8bb2ab7f787b1bd0716224e54d697a20c4213ab57d8c69ae3bb6693968cdbb874ffa1dae6d5a7124719e184dda75d072e557fd4af14b55411deacd663b669e7ba2f133b22b7d4a140e4cbf77da20b33ffd71a5002578efaa2a95ab6b2d9aa5329335fc4dcdf178a9a466d1ed5515c84f3d7c41ec13b913e47d35cf21b38e781fc75e38ca3101c99de95ef5694e2f6b2b7f5f074a65828708723b26300a3d490fed17d91e855deb76b5f90dcc06d7b45377cd1480d9ddbc772b0b37319072157ca197079b5f795a7a7d330908bf074b6a90f3ccca65c619264a620d092b7a60aff7b249c4800828bbab8ad84090060d898740df46b460393c322bc4d7e22c80eb78989cd6a09a5d0c86b29e19e190f9003af29fd571fc6c183666fbe65ef055c374156b109443aa5834f898cc1bf4c0e1cd41933bc2274a8bb9c82f365c81d7b9d84b1128c880f80abddaf192d77dfba564ca9c144d9923fb72745e20e0803bc4a20a246b094731913320c890930bb4d5a20c48e96e510cc5ab71fb45a006569ec1ba7fd78ab85880e47110d27b771553981d4b12c08ff17858567543bf2a053249aed68454fcac4278892955fb647edb429b2b2b932151ddfda736a5186083ae76ea0d596ae165d161e0e86a2a0161a6f6434a4ec465856578a3ed062e4da813c72adc48756dd44a1553d647b31575e46cc28d8edef41b475085202721fd44d9fe047057261ec2c71dc9f7a7602f3c07c342eb99075113d874669204d099054d04f8c8dfd40a45690693761d87fcdb462ec5047b43a446578be8d47a1f96a784aa82073e6d5080752e1f28d3099f74c0a4deeede0201e095dc7435f32526932387a0ca120da6b7a96f0f25dc8e5853e12ced046eb8402d1d7378309d80866b5f230e3b7276cc5ec9861aee6436db26958085178b29963343dddc61a99d1da183396d3b698f1e370bb421c5034c6389561cc9d8c10b03ac1067326bdcea3e311206128fb3527463a0993936f55a0d772e093a40edba6322d524df9b9952f1003b75ec6cba7983c9b34083968ffa2e9024a5b14b74643caa00d541bf37642427881cd96547b4b7c03fdb186d35b750a08780cc80e7d4fb89e0811f3b9771a4594c28b157920fa561112ea69c61dc71e799d2c230f930c6a241ed8d8058585bb17a4eb9473fde37de629db806744750cc8dd18c40cfd558f91e40f4dddffd5d22c24cac13636f4b0ea33f5d3cd0866b9ad9d4cc2bc2a54a9a20a172ef91bd35775f4755ffff7b979124d743330d6f14140058f61c4053c7b3c29520bd39f854bc58f5d35c682dcd9c63f60bd00f891ca4e027dba917034b749dfd036d4c8c4d61b84fda2981e931bf241ef744468fd009abaf7ad86595969e50df64289e1162e1ea86b4bb8bf5372278094ef9aea16f9c772e59fd891afeafe4b978f491811e5379a71fbe8b2fdf53e6d71fb70f57dd84724c705efff117b44cbecfbfd39fc3456a25e0aa0f9ec908c1f868030137cd2d547db3eac2f0c2b254cdd1a0864ccf1986860b0e42a40f570902ad2077ea5385550aaa7e3501d90be63259f602ec1703e08656f4b944d856ee62bed34dd404f12dfc3f382fca8434025437b6dd730f0747625cf0f57f60041e65f065928661ff7141c3fde1221d88ccc23278f09c1f43b432536e557d27b6fe8ecb292e8d10c471f10c9f547c917415cc0854ca5fd9b44525a4e453c9ec105927c8da9fe027a41c6e340ac0874e0959051096504552b7bb5b2e4aabace7841f2de925e83f8cf8d7a4325d0e071465a31c61a36d6551e43c4bb75439b5d7226ff5f35619060de50fc378f12ba4ef606cecc116db33e6b21e60d1afb70474e6ce6264ff2297485445317adf4c045c19d45f9ee9b327db8401d31882444878b0975e6e8903083347145b073130703d052e0ae66094c88b44156a99331ad8828cb9dd86fa97a17a3a9eb634fd2596ee362ea35f534900be16127d834caf63b4b7625db3726ce8d7fe2e6e1b345c6b87298220af7fdc2089eff5dea9f550fb62785e921ffc94776d9c48c0d4b8311d2329c9c8e54634b545fc5b503f9f219e8dab1acf1f6c806b2ccc77540a89d23d6db2cb5a1b83d11d4c68669bfafc0521eb0fde58281f3847b52465296575d5567035680514afa68058dfb0f8097384f4c025c6abd6aaa1453e1a5d90a2c70f224bd5e102cc77bb8929725ab34f982cb51972ecd1d5fede98033c41081e4e0f676f68a000d7a08598c62db715b8dad12f9c3b68a57a403097eaed5d3936206682dc3074ecbab0261f8154c61edc521b6606e8f954ba786f5f420e916fa50eac7d7f50ea61c6564d3151728ebaac4351c27ca05de48bf19f527d158e8be93e198e29d175c7ddd5253a6e999d4b9badc8145082c0104fac11f71e9f6d6f37077cbf952b4692a3e9f066d7b65e86053d295de13ffb421305b0d976cd9729566198d79ce0c2b63e9d1ca5765ba935840b7ffe4a78ecb8766ab290814c9e2255191f9f589f41a36a3bf278a479d8788919dd9a18c795beedeb474b3aa557f178977db85e9453443e4755e5d126806f3a83305ba4241f7adcfc09c694ee0ce6195e3b8642e21068af18bbe1193cb7575e9eccedba3534f6fbbfc5eaf45f5a677060525fa9d8757cc02b0560eae1832b18c923425c817a6c3d7a99d633f17c887ee1cc758272662980d75acbf8def7813d460bbb4e83bc83781f72ffea18344d4db6ba1382d0a23e76d11d3cf2834055d3da230ae3255ed41e6dd15c2f6359cf97c87858fd0773624bbd350ebd08e0a1fa98d768855d570eec59d6d4c2368d1d119b2b4349490652e9a9fb6650d491326055d8fd695c6354a2b0e4926febae0561cbc9da04781a65f30c7c8481718bff93b3860765e7b60143ad26b562427286dc55a29b6c3831b3050539e0089ac7b470a0d189526e254ae0a7ca8d511cbb2d866009e714566f8bc568dde4c1d4805eff8772818e66e5725b3bf12932fef53ad7d6287f59be60ae6b91e34d94084c75a7c66c7868181f1de4d6c2d028f6131b7bd3722e4bfc26c6eb2eb6193dedb1cb4394adaf8168ffc37fe07a6cad32ab869604194ac159981ab59058a0623eb64dc6f1629bb5e36b7d8adb719879aefd0632bbd46150a4e7dc5da2a6b664d9a0a2e126b582d23e756d2c797bd1793fe1e1d3bf59ec5f2c8b253fadcb9756734cb3bd168611355fdce3984718f2ae8c1a5ed3affcd9c8aa381f73e6908f9f60f07d1883561b8fb76e9ce4122513c08cc81a0a8fc362e3cd811e4ead372fd4248c916e1dc3e8336aba896800c3e8ed1f0fe31fcd0999e2ec41b5344703ea9ea45e3db58731c6103aa161aec306ac8c6d8e0d8cbd922728ee86b1c14b1ad51485dcdb767703a49771371c148a07c2bfa82a30c1651d42379b717731f7c2483e6046ca1b8fd2cba6995eda42ceb1279872667ccc9005a2ec896138dabd5e92931813ed8159e5b36ea7871cf097ab51f9620f769b284076c53d232c6bb15db18d95dd184763e98b8905ec0ccd0df662276152bc97a7f8243148214ded7017b8fe2d2a2006ee6a7360494fa397da44cff6e809b6021478786ed379857cf0c0d2595d15b27c250b6b4f37583a0406c3ffeb32441851b29aef58a94f458423929ce255218846660c546ca64a6c9faae911be1b4533581691c0762d78a109bff2b0f31f219a2899219d03969fdd69e9dc5879fe1d053c9045b01b2641202ab53b1504e7127fab1a7aa9559559ea0165ee8023e21c72160ad7a33e766d79cc4da5ccbb9ff0206487d2980301012efde4a4dda188c5b7611a70c60241f76a8989262a5d4875798166448018493451c629d289126150338373e590a3d4a198e4ac950805d86bdb75643bc4a8594bf5d6563d22ce704a5b9548408c002a6f8a460a79129ad15ecdd58b22d1ddcf27b953a9972d1e5c967c88eb3377a3b196c08854791f4b75bf5f557330db01065126c6a57e5a31cd180c10451a3237dfd318a693ade1a44d94f9c323253ef2f582cdf3f20beff808ddaef8839e612ee6ce22c2a6630ea444fce47a63cad5dab5128ff1781b48f1870deb1f01b88de2597f3cb2e2173e4df0bbeb41d76a65f28fdc2956ada5e3520577cbd153d368b3edc3b61116009f6a26f806fa9cd1b6e8cd150a2a998333e3d307b7ee6494e648102c8e28cdf1cca3626e61d0e51c27055de42136ff03a3cca78f665f787be45772e2c0183217621563e3df352babdc29790845b89c097cab9824182e0df8b8bcd2ae14ea3d32f6a40931e3c1fa31cc0048a393c8962e5ba05825867d6c042425e91908c1a1feac3a1c578884bc9726f146f3948cdb97f536bbaaec141dd14302391e74df31beec85d9ad0829074d7a53b9ded2724bd054086ffb750b5dadb223ee062e2da91c5dafa0691879c3e85eb744b118379ae8183398990fbabbf40a71afe190aee81f4a24cdee9840678e5f22e40bfd239c5f9373c45cfea99e5ca4f7be4824b2a1f4c61186550eaec3b10639967ae2a72fd87f7eaad3c37cb49b9a085355eb546bc3a6c5060b3bec0284158cc0974378aa44956c24585a58933b985a6695bbab8d9764fb1d7a1b21158f36456d48442520213ee45f171741232ce3bea5e16ca635572e1db117f4c4bd43f432401157258173485f632204fd9be5645d202b29131aacd527ebbb948843b7695d10d7e025b7b42824e253e91b18310d71d9f4df920c706c09c54320980272d34f9d7ef44198ebc8a62abe7f0c3e8eafe7f02910b4878c2b574f8fab59772f47d2b82c3f6502babefcf9c3bcdfe2e33a04187bc0cd52b3c84cee7b397884b567d6f6d2a1e08d59149b7bff0e70c14f44b72cd86fb7a7d3ac3a9f7937ee421198be36fb142f307d9b34886947d436c8570e8d42c6d710d09c78bbc09231914f4a2cf33706c718a36b649fa199e53cadd47592b78255c6df8084c6ec06bb5b3740eb276ef10f217c30b8cf46d7753e5f3edf08284e2f03e9fd5acf46962dfdc9ac20d35fa75f0a6755f16eed332b28b09a4a605921964bfbdc036b5cfb3d2fb0aded97786fd82acf92c8da3d5f178da89025aaeec1658e71888cac0de9c0c000b5c870b3c1e8e82bc6da131d2c1981a6b7c71be781d49c8d8addc85b93cc4270aa49fdc82dfb99bc0692990b5400041586facc91f109a91256b6e05dc8c25b6a07547fb2c5b6e02046cbf5a55f0846b1a8c19ec8449311838f3609bc0ab66272fb58a1c9c88533df2f4f03cb69db65cf72f663f73e55d8b2521091a218c4710500742b2aa8d3c0c660fd70587f43f44d4b4a0fc82e195e2083711009230c425028ac11f035262226036b90779335fb75432dd70dc1b023627a74da37c4ead1d6035d86a5243d67c449d4a8089e0af839e68820172ea2fff75fe689856ed004ab92a3220f6c1329d9f6c4ebf6a3aae028a0e68a60b3db31abdfb113c309fc08f0eb4055bee4a1223646964613edf782e40d41e021aa1fe18949c937303e177acfb8caf65c675eadf4ca9a7e03ac4827e4f3dc13182b8ff2edf29ac5607d6b321581c0026943a469ea1928fa0fab0414edb206890e7e2e26a0c2042e429b25ea5f2823b3a050e495538319acda8054ca015be6153c8ea03d90c1eafd6134bc402245d5a8f77958f6f0723d35ec18e04f37b8e13010ca6e6c703fbdaadb554aea3fc1d2b0d7156907000fd5c26052bc8d3b8e27675e7056ae985336cd92187e2b6f2b7546820195d38bdeb875a7ef12048d2f202da4ceca533dce48824918939359201a12bf443edd82bebd73a018f22573406447a487ba700aea969f30081251f16d64715810cbe4e91d1f15477087babb4e1d9f89bb6572795d48752c2e0a60b479474c150d184f6669806e9f2cb806b5b72ab18d008007a43dca7180a1a14a6e9870650b39ddf129f0b103bf0f46020e707c9bc44d2e3d68216da180fc6e417b5d990097bf470fca2110c954f6da4d2ae40b9bc4d1fa47d7116becb9d807a02796e9d8a299e266836752889f2d8fa5aca7a5d64e5c1830028f6d59cc54b5d912bb7dbf8a220a2832ba9e1aa59d8c7e3af7b58602834c0bea1723dfc3db63228bbad8e25269aa30a9779eb6127c33800770ede9dc4ee1325eccf1c420a069a2e97e4485cfab4635d628e8826b2ae3e970a7504c20687ce44a102877623880a16e78de89bceff54a0ec87dd8315c4f3c23805e2d3f3f0f47dc39a881bfd72d6364336d20f41471c135d812a3b09353ed70ed304d9b393e45634859571e6dbdd0248204612a8a709e234424a1e973bd7aea8a9e14579cf640d0f35bca68e8453ca83fa0d69fd245b832a8d3e291620a94a8f0d6a9f663f4bad40e9c0f4c146b9494d3d1deabaf17b6a19f6b78225ee1611bf4a363b8831dab7987f089170596d18bcc6603e8aa0e9c78d74cd17dc214f68607efd9a76e7f88001ff1cc166db34d5d00903dc1d7e7df99a52191631f8ddc2b9385e236d5d9fae8ed171119610830379a4e178217020178d8e4cf05f984f3cb727bd5f71419b798c3bf864256789224e3552cc85db02e4640504cedcaa046836b9d0993655fd870f2449b3f7270295d47e91feb5f4509c732bc761f7889d32983258be6a358f2cd8400df6d126c99fbcf3a7d7716b4663bc8e9ac2134e97d4f4f40b32018f8a0244bc59f123c43b83497feedbc89214aab9c232a14c99879da26ec0dde5ff600215d225500aefa7a1d813dd51f7cc6d0b2b724354efdb33f8b313393c69bf844efb8b0beee600a9935d2e9a4b996af6440ff1fe672cbadcab4f81611f39d5e8b92674e6591b97f48d6909e027e3d50cf1de353901dc752b03ae7382df6cfbd0354ae82492f045eda534c7fd6207ea242079f53aad686428b1d012ddafd87ec32a8fb4fbdb3d17375980ff7be85273481e2e598e74c7f55b90c7f2c50a220ff69f4beedae95b8661afb8d8fc200b968b4708345682490fd6b0d4cfc3629c1e45debf6c864e4c19a79b78a868d9f75331a677a551189a6fc7264ea37cc4b39724c8c5120db1b0619cabb872cc1aae41e8e83171a181e56af421fe0764573bad96c4c0a606327ac6429572f4e091249eab3822623d7f0cbc6a04556165b1c7711d07bc0be37072d43929bd0b70554c17c95582d1fec3ae94f72a0d01a1963905ca67f669f6112c58555891fd33d297f410c6d0037c9d68dfdfa2f9bf7c2f0e4a3ead0bd8a508db0922ce766aa73dad1c7ade8d38ce0d839bca1f7e137fcd05437d9b4f08dc3301774576c7f0c3e109667d31bcea4462e7f85bfce2dc529845ea73023de30e948bf03cf2cea413cb6fb116d07459bc158fc65023b24f9383aa3b22c46781aa5c22ea5ddd0ef5ac3f6d043e0a502c49bc725956d709040852165ff20da38ccfa4439ec45aa360a0a9ae02eb75615ed91b6c7368841e784f6e68e184af019d2a7d02a4396bf6e87bd4aa47f4209d00aad00de3bca2a1194162616f4ebbd34706f64a8b06e572e29f0d9a34fd84dba96731d309f090f25fa9bae6b89f43068cf1302817635c361ccb322d5e6e0adfb9a58b3fb866806a8e5ee6e19b386236b4fd6bbfc3c2c50abcd05d6c4a1c4c27575a325a398757d3be087a469d1dcfe309549d6b2553ba3605b400a5f65dfea660a20319d91678e0272f3da692edecd0e690c8ebd3bc270374fdce985c27b972203a4e78b87503e9e8712e9b9557ec38bbc5af5d3503e730967e31ab406267845eb10e041fbbc938dc18f0c8e3ecb9467237937d8ba6f7269c4e1049779a3cd30e105adb8ef28b00746ef4119515754a2fc39deddbdb3bd98393020388b04fdd96c4f2f714303156dc0b0589314f0da408fe72cc00948ab3743285a4cda74fc2e5ac8581065b27c643d7f3e0c13fc32c6ee448ca29d9141a234e8fdedcdbe686d0001585ded9d65f884cfac1dac629ba99b9a3584e59c7c61019caaec2054093b14b0e2ee757774beafdbeea508b7e8e2f105a7eeda19c922e97c29e018d34df7c5d0a31478519cea07a1fda8d96e0384c826b04b8c35367b4441dce2dbb6d64f043aaabcab56ef5b529041fe3f5714a9e4901259f4a5ab3f2ca85fe0008ddd0e80352f050f4f998f20266fc938671f101c98bef8308b0de627c551bc8852326ebe3533135de646267222e310a0217deb2460af94fffdeead3e7f39df439ad2ecddcfaa0134f13b968fbd10754c5fd99e5ef239ef64a7d7de64b614ec055f069126794c81128296ea19fa49d836b2d013375be141cfabba664495d82ab2274bd3109e9934d4d2a49ce27275ffa72ce2585161c97f0bff5912f06cc0055aa50222abef76f365d2ff14ea99541ccf95bd14825d5a36846c4d060e09bbe2471fd72181445750a226145af6a90c903739346c31185d30da496cd98df115742089b741fdc83f05b590afb1dba9d3484e317c2707f2566b602a094d3f87f11f6ea2e2c1bf241e42a1ce5ba614d63d44e91bb0ed791cc36f86834f2de864f4ca316504dd8b32556ac3c3bcf4dd0939960d2fb1f3a30d61f268d58e1e0f0b4965f09b604e9d3e54091d10172f512f1bca0352f61174a771f0d32a4308840add5ba2184f085cd783b1991800d0c15897cc56362d1d06861d7cac643a292c296c80ad89847d55a2e5413786b3885c69a556b99613f81ceb0d6f8b42b2b8bb5e63cab0678bb496acc57159e37836c0a8a2b08ca04210650fbb308e97687c4d648ca9c457a5f971316a43b54018689c14d8408bcebb099917c60a1b9d34d4991eccee3d33ec9b7f72069bd2393b92e7068f58649f1118727e78136d97c11efa3338947b24f2e8f2af890fb437060c1091610e84762ba7abc7cf590f4ea891225f86e1caaa5c4499a00f9e611fbe97282b9624a545a3e3267efd6df725911d51881ab58d050155752750810507d59f3c4c8cd7ae2c0c9ae4908f49506e5bdfbb3643d642c52ccd66b64e03a2db21861ba9d2805cc6341eb43208f02dd225a4d02fd40e016823d98438ecd51b60d5cc7d343cb270e321650adbbe62d1481164bab79455a0c226df084534c679fea77f9820d2b0051f7743d6642ba29b83d40ccd4a61225829881165cf492e881b049c3bd682ddbe443ab9f4967d848bf0b495d843714f55b00d96886a11edb0921c0170c75e960519d22a2673210cfd622f544c7c03648f598b2210f954d21b73601b4b20b4011b678874e187a086ac9a170c83591909fa09609f694d2aa64f0fb397b863cb4731a7cc9e3a1525b52b23266814aa5eb9ced6b7806644d0f38dc34563a421edc51487200f964ec15c9208c7a9a44459d0e83d0882ae53e77abd2995a9bf9939f81ca8ccc3d6be0b80fe36decdc1b37b533d6009ab32a1c9d1b19a050221e4145871a195ca67be8584f09fe62b504d82a30addce1f87f0d02e709f2a04505690874cc115603b2f536d1738b91f8cba32463afc6aef2799b9a0b68d538d15fec432627bd1f451b4c5d65483124a889a67e603e4c9a4e0c60f78b74b2cc1d39cbbfc5c672491bea3ab3ffb014dd4f42519cf8632882b69f4b138b00d541da1afe2627c8061acde25ceae7d1e066137804f39985da5566c5ca2f0e566cf8f1324b4c8d4ed275fef2d3c6ce35384e84a812fb334f3acf9b02120d56654bd9e3ff4dcc58320468f9067a057416c9c0b91f51b9951feee063f3c48be125c12abec6b08cb21298d7db22a4957a98512866facb448f425b0a19a40db8a5a646ba00e4a6c1c1c16a404fc0716fc80aed20e10cd5e1426d18d2cb23b6f403512007ba8b22a47a91d385d65de15bcfcbbf0279800ec9757d41a03e4e0194440771a2a63e8acda76b4affb089ee570acd7c6e95addb0bf4d5889266997a7bdafa336b80fc564ade55d2a64775cbda9b918693c9ae02a9d1e3ad1d7899d6b4f17808e44784ccee6916398b4aaa4a4043e259b71fccf2eac41e34b903041cef7629035d770026e4c29293b9a70131a2a893f5c03497b56530f4b944445b80fbceadabb3b0ce32bf9621eac3d873a2a64945112262352e02f5fc95f489b9db60053fd1f1a70d84e21cce468ca9706041773eafff08860084a118de995d5e0ad2a45288a5180ae34d9941375c6605821dc7b34084a9768b27b2888a392a0e95367f9cd2113cb791ec4b28642c5f48afbf9758ad585ae61932ea56764810bef8b8cc4bd6459ad4f0c8eeb6afaf851bc451266e2550ec5982004fc4d108f6363975165338e5d18565989319f226967821533b559a7dc4936d7b075301e4c5d80e360b1869aeb700dbcf37ee7476133ed50eeb5883d22dfd9e22ebc60062c84dce82499abb56c4e46f14882600d7d8f8116c3409454366858b1f6630fc8d23b495fedc85f797ee70bdbe8b237761728028da46ac2a9102187de9d4a51bbcf8d1ea60bbf3d8127ab28b1ef7fb5882551449ec5dcbf6eb7c75457ebd6b65fae1c6e06bc7c85406cc33e3bd472186fa77735d5503c04a572bf33f100cb2813073980e170cea54b801b4873b043c849917765218bea30440607adca2ebae00fad3129be32f9af69ae697b94a15650f9e7b3765a623f3d9d7abe1d6ab5d3e44ce83dd83a10678d8b0c9060f0dbb6a1507a7b58f01dbbfee2461fca44f4471e27c12cf95286afaec00345524458dcaa33099e48a107cd93dcfbf5261b881bd399df3e2b6de2500b761c8b24733316812fe6fe7945e2b600225f22ff24c6ef437298ab589883e4950f379950d6b2dc70866a2bf3ae40660b468080a645e2e2a65c605ee19cf9516c9758d00fe478ea50f57149efd0bc24ec54c9c52c232f80dd88d8b69f588a7d7874205ae78085ff56681bf2efcc7972a201f16556eca6ae06f595f94d02fa10d8ada4a9f081385cfb41add4a260b84606873fd39462ed5ed108a82e085924cd3f42876a1328a82aa387d4ed0439db9d0207e5c1365d09211200760391098684216728d221791a35a50792186228df9d4472964cc718a9d02c96c600a5c51da8ff246d3743f891f570f307509bdd2a08d51016b37fc0bf63df60b8e8145cbbacff40c85c72e1a5d8e3b48954b836ec3c65a225bcecef5ca4ed986c45ae0969a09d8ba12a50591d983da83519386188563ec22b8cf4b99e03b750ade78c1e5b42675fd09afa4551cda09427375bf3a462140b0c5a1c525d06942f9d824efc44a459115d9f47ff3cb2bc2390cc19b702bcdb9eb91598e4581701e491d7f94cc00a7f8de7c0925e4f16ad5c924fcd10e497cdb1d8a7af9ae07b0022132d4b5e6eccc1a59a32a844a9555bd1bc76eeea0b349079894b8f4c07a08f4a9715fffff61fc526667464413f4131e9e094e231b97204bbbc82a311506704a0afe753ab56de9bbe7d77639c0045ba0b5a4a48f4619bc39052306bbc3e4e0cc014dcaa26522eaba899ad0b46ef5a852f21dff2188b586702d1aa4684655d9f1919249d8390f63d172808ef92d565842d39877fdd5442f0eb35b0479cced0b3b343c52e2df86589daa6c92ab33d35febd396b7d046ab082473fff93269fa6c070a2ee06c056e7f9d82f1915a14d681e7dc1f025054053cab5c4bf56c05cafe91abf7843ffef43b65f10861b2c29a4da6858f759c6f99fd5b704104bcaf0a870314979e25768e8d0c3bbf8cf97616d68042455c94a03ea18f8d3327931f14df149f6818169fd8fc3b4b23928e3899ff4a935a927fce912b57debafe8a0d64f92d6d4c56279af894e7b32b19c3c62b16a8228b05dae25ac4a4592fa0620913013722e673628fecf0608f3777a74e8f10bfa1e1ebc31bb8c15be6370435779a94e5b86973f0f25dc9b1f687b04c30863c5fa08b3058eeaba3a3dd2ac77a992db010536183a83856f50a3b22f5a83a58cbae24b524353c9bce15eddee8c21018ec8ef688cc82f18e26004ee88a99e07a4f18da802cabc1af13bd945ea09de8e5dc6b920f477efca148c827e3889d2d57acb3f3a50b2fead245b53913b632ac0bfa6fb010b1b1a883cf45da579557d2b001ee344d8705054b070a8204b0e4dc6efbf7fa10961f386ee30150afb8c443aeb36078452664b06007b7c2745c96c97f1ff75851c4556c1431d61c77e096548a93f139f0ea84ba73f0c620da068a5b57ca97d960ca18470a495693ff68b29c6a3e01094a14b36273ecf6afa4930e7cb4545b62db1f6064d748fea058077f998dac8fa11eb0d730a85576ecfaf47a7f158963c6fca963f66c4df292b9906592944d7852d5e6bc8594ecd5a3fb0837c327b2827040f5fa32e46d00a1234b45147ca1fac91596217b1291ba66f5311e415b2b8d7d1459c6aff550340609c8f4f0f5ea907d7ec24373747ed8b342adb281d77e79b900f878c300f81e05f68ffd594ed01d81babd29504ff2052a457cebdc95f0e1c0c960ad150ecff0b456ec21a22bc646721e4a5aaa811f4a40f30a38fff8aed90bf4aec62a841513c7f23fbb7b533520df43c1491ea1ea15d807cec4b2d4aff75a930cf4026dc588bfce8a7a3bdd5ff9cfa16b8759ff79936a86cacf277e6cdd9cea4133381fb4d50faac3a5af1a5489b41d2d6a2c8d2d19dfcb4717fcab1e384a459b8ab0417c54d1d1e4456bf0d39d12fd6f6addfeeb53071adeb899060088a0ae41f85b76e84c8748d06582eedd5401f6a332ac8b6b9d763e80a01dba7ca07c470addd0351554420e1d42450d9f24a774abd0e369ca75e86403166ce1362d73aec8236209434fd0a7332fc9b75df88a2518e8ca0e4315e93d57a7b43b132665f714df9f5316334bb11be9be710d53b2737c5a64bd8080aea37d7ca5d801cb4d4df6823a60a3e719b08943205bdbbb9484c84bf5a05e4a8dcaa848bf828a790370e1fc729bbcae29b6a86b96ab3a1e38e2407a83dbae1b513892d219a69ee3888e7bfebb174835b39de1ab17558e2402a39d552ab1cd46b1f626b93388a1f2e6642c9e995d7e16e3745148a121c7aa9d7675a5cc4a1315dd4f93780e777ee44df7ab8528a7bbdad16564ceec1b1d08c97327c6d4fdc60df6173189dcfca4426c7c72e79babbb6e09cb47f6e51ca305c881c9e7361e8aa46545ca698384de867c85377f4ec6b8797070cb029b65480d5e153b89bda750755f6b0e32e963c0c04d23d3f14d6c4a26ce1df38bb3c382f5cb27d0243a5b4a274f5a612a10e9a0c39171e489b2fee0dbd23e386ac3c2a8525bde525d5d37c0d62e63d776e30bb148aebe8de8047682f2e9e32da139811e9a4ce9d4e910522644c1d32165a7a3de1967983469e2e81a50e0d793b2299063d7c1924df4918ffe6c337036c1a17614644abc02319110154b462da0c8b390486a1d83e8b182b45bc8593cc0269e9e2a04426de4124c501361ae9a8e3068a365cd13d5350505eade833b291b88a3c5461be141161d1547d446af5f01212ec83bc89c737b961f2aa7d4090c4a46ad3623d4a68d0665caddb96a58053c48b5b06a722917b1f120dc902110c2edecd1873bbbf82560c155d72d12275ddfdb97ada1ed5d3c5cf5c09f8682c5979b54892cc00964a1bd34914764ce1885032c83fd2748a51f66335a006940f21f14f74853da4ef616df2bc23bae8a284340c535db282b8695db06ad01f0c57c727bc9e92a9aee7759637f8771b85996a471b424a7abef517d34b33454714b96d2c25429a22305538b6187c4e215cef6ea67210b53c383e4996b48a5e7949dd92a9da513e602500b31c47d9c1b1cd3b2ef8f10bdfe525c670978cbdf6ca1e4cccc65c1b618fd18c9893039bd490e26400218b9e137c66fd1a2ce5b19aed7f20be622b9fd7858f04bdabe399e32480725e45789fca40f498a1233f8fd51d620297270879f9fb4ba4f4b01a647ce764ac768bb3cd4b36a8702fa1b2447eec5c0e92959b829ba69610e0100065c93e240306b605bfa6aca3ee20b446935405b81009c3caf42fde9e5b6a58890e272bd9757418d7a9775a4babb2dd172b5f8548d63c666bbfa837efc3455315ee2f637e3cd0af9cc2c2761fb55086023d4c559e1853f9e218f9d530474c5d924a9090f24c9e574ae8f365ff765a9751896fa859625ad9881c1c9c0bd804b187b0694cda94589e4d43c1c6c733bb84e6e48e37aacf4e3cc9d0e3582e34399e333e0e50417d3032430c269221e3a9dec21edbb91f636ac6d96b268cc8418f3beaa4846e165af5e82921d9bad682bf64b0694c7c781d591e850e7e47e9189136ccb7f2636023ccc072aa75550ca1b195cb9560a8f4ae7507f1dded3228f16f149fa2159e9acb74dff27eea5c54ad5848d2866e695e0b34be2df034c49fdf31af012760395342ed79c1374d4efb8a12ee263bce6b9ccccd4d089cdf03ad009a3e15fee4ddaf96ea9bbf676e859928cf8cf33aab931820f28176f4377663440648ab4ed87d06f5055ea85d02be31a67db79864dcecf97b99048dc21ac055a3267484952f0dcdf90b83e2d92e6e3585073f32dd60471021e04d04d28710f538c37f70bc72c079821de1cbc7c2ef6f2ef5cda777e5ce316d4fbda93a6ba05557a7492c6448c6da484b34e9fcce9cb2c0ebefa71855f0ec307e40256beb91ede38658e4a9761d2e4ede7781e4a62f488525764b77e121b699fb71c4e01cb8be3942922ac327f8d1523300113f84438e81668696a84b96432798d0a384702e71150b2b45a5a5c9042a1d52d3f910ec6d2055e0a599d79b4400f4e5cd6f0cb94c68cd607e0e1451dc3e01eb6748858573e1055d1a849e05a2cf67b52e238e2d075731a0e5a798b7f50c1b0e133f33f638e0df2e14657b084197a097b4eabc3d5e000fbbeb885858de5841f886718e200f155494273ae5af01feb3867d6a317c89e4a944ce6a97b6f2c9fb59656b413fd440fbc1a165a8e30e9f96c1c7219685d54d25d273e7387645de931170aae7b84621a19635f08676cdaa55c13dcc3931df46b6d7318980a430248d60061bc4b92230a4582afa4e7f2c2cfa906bd91ad3c8a6b815eb221408b8871345b70d281fbd3e34703f56cd2ba140772735725ab2a0356450002ce651a18d7a9b34a01c7b6c034e7639179920db695df84160fb43520a80a931dd6a64906b03bdc58a69e323b50ea4444aad5c0109ab509710f98039796abc1805039d62312f39bb6ec0ad28fbde39ab1d8844327fb668a90ba70f364419fc65d8ed05d1f9c982ad057de4c83019e381fcb54b5086b008a6d6866665ebcc920610afee51cc4958550507ee96119f529ba821b894c58170e126001c154c9246c21e4741ba0557edf2e402bb925438c4c8bad3a6236170ded47248032c28b2d5879899f05cf797b26a3e1c7555f52e7272c2479deafacb0bd82c7dbac3005c6b6cf84c0f9212584c323fa766d1451193b37fa39c6132b691100b27a51f6e9d13b246a8807069e13c42aba7d1bec405ad4cded96eb735c88864e43296954965eb77f95f71a597c8bab65eff1aa6b8abf411e89d6b196c267d80bb06c64cd94854a08c1ee538d36d6aae9e632227a45cf7955675c8182b200a0f84d8d6e780d6d8584986adc376f12d0934cb0ad007eaf452e32c165ddbf1225ebada125be7a69669e546001f2f614b76c402aa8f7550cdbc7ac90bbb0cd5c339b4130f744757ccd5a6d0d6e284be5141b33327ee0c97fa47fd5410f4e8b528e7c5c633e9c97d731663b243eea36cdd497ccd82b880f37a3499a0d688d909566bbbc0109d23268f2a190f3fd7f84f65ea76c9a2c208a9b8f0d993cb868fd1fad5d47c59cdc5577610d9f702379ec14beb8b9daac6183e2eabf428c0319c88557c8511326feece9e557cd6f70fda9cf2e24d74617dd92118316f00e4094144e91257564cd30d197d2ea94488bb2a1b99aa10d418d769995970a5da5eb9b27ade6e2dd11bde504c529c35a190ed37f88789340d7a7347f53218502e46ed5750847788506b900e8f5bdbe27b8d06ac2c919f457e800bf06caafcde56bf294c40f27fd863581042a6c9363be869e005160b62b4b9bb802b68435e33e87a3295a82f360137c7303123afe644fe413ab008b2e01b245a8084aff0b184e05c373280bb8b6e759fd8e09f4ebfd8ad3b8828c87bc1f1f438650bf4c0895b64627817ffe93943b1cbd823db3d44649e7109491da4d2a29fdb02f84ea22b9066f4f8b8f91f2420ca651c73ede0fc6f49f7f46ac6cfff73be2b3725e021a3846b9b11c92402ba0224a17148a87d5806546548cfb4c59bf56f77c4bcdb06fcb4ba09a7c3d30cf54c706ab95f636a3e10beca58dcf1262b7447fc89d51615275a058c4a8be8db9ccc27753950534058cea65061ac2b4d34b123a6b2efd3b06daa2cf98bacc619e53e45858215f1b2fa218b9443b870ad9f2253cce667bd314adeb6e758d45c1e376375fde1b845b447f504f36d57de962973b6e5256a512d8aa927e0d22c120629327bb8237195565a3c12ceb69509fa35543e589f34ff20004a1e863dbab4f0e811e877161085a5d1e34885f98491b47c0b610f4808623e8da352d92f8726a864443798d9aace10fac11f636e40c84726ca785a8ffc4e1463f05c24c7ca16c5d61cc6f405b14b33d462516c748dc399ab6945fdbea7a3b48f1d024be801767f374c4704f8520b36a9ca5040aa81abe1f92287b7acf105bcfe2297960b0cd4694f1d71b615d65d19f4a7570463d3cc476c58b55068b95018ed1a8299e2ed9cec832dacdc2927c6ce9da8649e95b48d93bd21ff8a42c22061099f6a43cd20a89eaf9a0397f98a944756dce13ece6cb24e90c6123419a7d88a310165755cd7d4bd882d90f6f989a5f38acbc3eee2851b5163debe28b1bd17e212ebf371b6c7ddad206dfcae93259a992f1f994f3c9fe42a8a3ac14469172871ffdbaac8d6e25c47804bf3176fd73ea2b20908bd517f6c78f087847a3844a09d878d72f64fa425012d6a1efe6d5964bd248a2ba684f886883d3c51eace41409ca6e857f2ddfec1809c688a6833fc6cc9ececc112907488ae50c4bdba2e32c39e012ce49d329521d2927cdda6f97714fbb3ac41a45734f8ca0b6be18b2bda1765ae43b4a64715ee4d52aef2ee8611c2521d8cd8e4036dcdcecb571a4c0e8a5c67c6c64ba5a52b89fbfa23736e5083189c7894c3bbf26a9a342851297dc35e4365f0031a18eee8ef2f9e6b80068b6e59192c62b64ed2508c4f24c20234a58d7b2e719adce80aa8a86bacd2fc2c6805bc99e6922452fcc45ce4c65a82f0d151c0525f7697815dfe2afa2e6eac4f6389b7aa6bab20ee4c44472521b756c711342ea6facd6bcadf64c5ea40453c94d16e6752175055237359284fad3ba44458ea3b4865deb3406a930676f48228a670833a2c42d28bd8f132aafc675384721643acc950e75afcdbe9306f6a66f4e9e9620e947b62309830165a9d9ec3b68c121cc92c6e5d7ee12f818296707491957f2c914f4ff27ded392f7974021a97196e23c13bdf6c635b88d32e68447ede8ccae401c765493041cf4791817ef3ff6ea2a7575c8538a3338135d260877d1915ac3a61f031b0e682dca88dcf9ac73775f8a93c9c9d8e5de8545519a93210331aca202c00cc2d1f8d778479769b0dc549a7bd36a5ad8d4ec3d541c168e235b69d50762a6bfc3384699948aca6e8b0a0c91e2cc95e72750224231ab0e71e622ade3e0184c43e47da7607249718a4ef866d96cab94760b37e926ba535da66d8c2c14529c248b6e360f4c9e6631529c1f29c4cc76b43637d44a3541cc4410b0f6f33329012b84d1922fadf2f03340cc143a0a9a8176867f7d792c0e067ec399a918612e34bb973d2f55033e7186476b2da716e758030925892105bd18091103e56d264e1b87fa21cdd4cb749202c29666b82f2fd77061905fa70325b466f597cd4c6e4a768050b3a82aa9d9a1ca630408b0e0ebe78b0b0c010e88d9cf401800db5b641130f913089f45b2acdc3d4e5c213714870ced78388a03ad985cc3f84990faefa6a69a9b79a650c3a01575cc16c2d9fa0b946232a690e99581a453e5927240f3e164abfd01965f9f48ba3fc3105979754d04a67f936455dd3935c751754c8f60d3a94a12f46adcf619b0711e44d52c714ccd6a0f8262f655ac0400c4e2f555cb56a9db83d8dc09b164fece6daa2308bf446b2cff6c94e50ec9c0180b7e8fdd6147d088e1c768380ba14b7d023793c266005de59d247b52418142bac1f98c49204b5ede8f21a50c77c0d8c1a25164b742ced31b992ea5f172a9940097caf82d954498a4534df2321f46a8c8fd67f6db457039940116f519d8d29684d1e9450c88f6f42bcdca438ad68ea913f2fc38a48568ef45272e143269eb97d40b327543bd90c3fdb1b91f0fd2089b06234aaf1e9dcaf7fa51b391adc0646edce285cdc7e2567518b73a274fe289938df719d9fcd178639930d015dcda5f55a90a957fbb2cb82e69553bd20620d196c64c70b6476526308984a2db7f3e8650d47048734421628e807c9411d20653c941b1b4e49b90b28d8ba93ada699d6c419f8cc0039f57421c428d0631b8e0b4bc3c4d58e4f8a1297a602bab95873b6a17819cb483bded9ad2f5b4c6136457d803fd51ea1560be0e023fa6b64c9cc1f4867dc01effbe14be6e8b65e011cf6bbe9d42556d27ec9a7fcfe8c083f53e92fe2146f54e0342b6673384a31ee8638f3058af90656a88e1bc94b978f4a00723ad734871edde1161155505d45cb2a095f14830defe11f35bc4bc1706025007ef546695028ce2d73db8088a3f9fb4f512ae075734264c3d51b8a8480f9e4c7c44f0460ab7b5815d2d9606109fc6bc7551da40d045c4c6af7bf21662322bbef745cd156a425aaa4dfd4c25299e117309f0116231f581b933b945d18fde03958867eb0b2355e9350e88fc40464f582e294f2869297791886cff0c6c0fead39bfed9cbe8379b3ae348e497d6ee475944635f0657d692b43b9f3fd97ef0f4f9abbf6a74511f2c997ec0984791f6223963164453d9e619406db3245ec7e660bb300385695783d136e1c432b9c344720871381daaa85352ff8e3d52bbc97518c2573029b4d54a99ced64d7714df2ab87df5e33755fdba239e864ee59d103eb81d00bf61f96df23c6f8a8bac6ac8c28d0576481b53d5a795828f54e99723621c9a1d71864c8d15460b3fc898eeea8f5bdfcfffbcd4369800775e524ebafc35f826d1c1cd94452df55d2b3c9af6791cbe410980c70e388777af4232e7c5286aa6d3155d5726da4e66d58e85c07ffe61a6b721e631b2d36f70cbfea7cbd2f430272bfcf0dea4ad2ad96ae2d01e791cd87d4ecf6ec0eb204d49022afba367a8bc9b4c0161ba298da7546f7ab9aa3a8315ffd2b7069c1bdbdcffe36c30f1d04bbf1c1d38195f9a88a9ff187ed8082def8446c37ec7b4a6ceb38147c031e050c7ebde4f73bb584d4d709f46ac4f9c86e536320dd0eb1bfb96868699bec0902ee0e58c4963fd0ff9e8a21f5180e73142c0672867b0cf6792052da0e69db83c30cfe4e56f0f78aff3603d584f65680c80004a04b20de344b5f3d50455888196c96f3558150174bedb17a7da28337560564dd83bcb51d73e525fa29da87fde64e6b775b70f3c1d95e88dd05e32c2636f851d1533d0c6d2c638ee1d667753bb6bbff1d8164ccb6a7f64919326d09e5761da9fd4620e9ab672659fa3d4ae6ebfa7e18e4c27b171271a612728a621691b671eed4883181ed8d0ab7a3e70e6e205829e277e3dd672b6c09b21a2226eb447a529571d53f023bb96efac814bad9b852a8c0f482a9b9c6ac1a218a6979e5c65e6a758983e1673422394053fcbe6ad84bd81e3a14e65b4df8f426e41cfd82f35b364aa243ffa224ddc631d63503c12c223200e3e76b9a85944337dfe593a3a3534ee62f4c55dd70d78d2e81d477292b756aaf3c7f9b8fd1cd57297ec7f8e60d180a821c7089a0ff91e7cd8c089b67878351daf0d069be49bf903ecaca641d20aae69c81f4a821782f939b252629395220342eaad7d2f7f1c4d75db8cf026485c12e6037319997fff62c702df35b569610b7f630085376c58708731348499c07c9e008c9da89d26f29b4aa9e6b2396ba069b2186ace6eea527f2194ec91a163e9cdaf6efc6e1d44d2d730d58cfbfe148bbc979c9a60f596ee935abde4ccafeac02a05a899f07bf96cf89948a1f61eaad80b9a7a52d9ab922080e53e41ae74cea6c8578199e92e55452ac2078019eaf2813d18cf4b085b7f0f5038493256add75f8149e0eeeda1a96901fc26493ad25474baa3f0a9e3c0f15dbee31ab8e130c4e1f24640f20e3b97e62157293117f912ce9493a634a71d9e36c212e809ad666a3dc4a6f997c93aae50a78c6d7278ec511bb8cf314ee13b43ac0c73b2bffb8b87b5b316996065ed230be4adc6488526cdacd9f79a55edd85e0783b72cc7fd45022afa2037dcf8f2a9013d5e20d65b74ee44c31c05c1b523daba8038fa1a0af67b1e52a9b4373f7aca89235ccfc25f838e1b221b2c6c657e27d3786fac26f4297a539e6937b88881295cc0b3f7beae9a04438d1b7184589ee105d622f92710a58670a2a6c3690bb85482ea10b7cc14d641294d755b5c51d9aa42b566d31ef3e0d947afe4ba7d6fe656e1811edd6cf6a7d662b3ecae24f2b6c535f2f96870601d1fee1ec24bba1f80c87de0c850838a17b0dbcc6a5136395b2e31ac405ed21fe7950328fd48718424b0dd74e832fd20a7273c5f70baa7c55c9210ce4ce8dc2fc9f5b7c4ecfba9ef0a27b336c559261efc391eb1a571cb756cd914f3c2d4551b40da13d2c1777f51374f3facc41d78da29998896fbf400a86043e1523c0b261feece91a8e474de9126298de7a3278381a0161fb4f26c77f98aeede27f63b11aa0ea6269ee89621f8462d635b28c317b3e4e068bb4feb0a6fdf067706871aee15422803045ea54513c402023902ef375ec33408be23c89e56db0075a18678fbadaaa8527a1f5a227a2651c2df5ce50a2889c361c8ffd6ae273b8227294ca0bb6051770defba687dd0b12ee1bd98f6a22f464e59c68bbfbb62a9d4eb3da95157096c32b0d4275ce8f39940a0038267ec7dbfe343926d2b5ed1b8f314c623b376c0f84d53547f6ada255d6e69481e582cd71ca85da5c8e19532d7d5fd59df6d6350ed65ab5557056e171419572da4d3d334d7136ba7c3e437245e92030f652507537360eb39c6cbbceab51c923207c313bef0d4b2f74dde3d11f719e0882ab559faf83c392519e817b51ab20c6c1bd4c54f866e63d133e5bb9cfb84ce02eaf42fa0433c04449142d4d6d3c2f19b848a87bac2998981ec3089ae2947e0d00c5b3bd0e368719b1f8575b30fcf1107064ea5d827e4267f4a8a7078e0fd907d12def9cc850df4020aa6ecc1bc35fde5e99971aa02fbd384d9fdc3948a4563175ad839aee194f42e9c30c49a96f38f1b68216cae913e9d4830b06f5d2c2158031a50e0769337a0e876b772401fe85ac3421dd0f96494dc97c4342791cefdf280091732b40fa42eca1fbe60e45f5eaa00db4d1d55d4e1091aeab6f324b2b91a9522be91c5c355981001bbc5a5bd5e301d7bc49b3c8b407b0025c494a717e6e633dfac3b410828a5a2d843f0d50ea16698c61260e081c7a34df4319f184b90c11a330e2c843bb6eee9ca76ac5593795989e47a67e5dd858c0b93a2cc7014d1d17c7e394d13675194d299574bcdc72ea499fc147b083df6049451a82657575c299ecb073f41643bd73681b11836e888a24e428297b360c85b69999bc90801ba053d3c1f34e5908d22887d3c350496af3a18b8d975dc4a66ea72d0a1c07caf89200b27805286e07001369c0b9d8b4f708ab2ecd725b803b31ef7c749839802222c1b7b7499f4360033219454f087fa9a526bf037361412b358a6bd9e07fe29bcc29478b1d66be9bf5dcaf76e9899bae27724c90b8a16d6b88e3a625ba8b73f4f610c774f9b21a9e5a6640130739f49f21c894a10fd75fd0322a20c669331c322f4d582c93de7bf75cfa7dbf8a72d2f5436c322090521477f0f646898c47a3b801d4b7eff413a5b4c2f224bb478a0cf3aa3947ea21798174a136f291fa0bc5b7003fee60d7e12e11c1abd37283d10de6dc47103deeba9d2a89018fa9d5b694323adb2b7378ff59022f05b51f729f216ae55fb35782a4562bb99e99a2c4a36a543af23c8a36952933432379b19b9268665b79b8403193115740b1e2318b74450856e740bc08ee7c8f656dfd44696aed3aedaf716b2cf5d77c2e197a622e11815b2d03cc2996e094b796b81363e97a780960654c9d5266b21437e62e7ba8175a18003a9d10d511c65bcbae53c7b40e539706494c2f4930060353f78967c27261129d302b507a70e2c56285e88538271b70527e84a807cf9a9562f1e267f23d54d1938cc12d14c10375ec67a34b809d3f638bb1e156a1bbcc0a9c1f05673d3274b37b52d9a2ab13492365adb9109c27fd4b8bca8d2238cae0dc8c6f4baf8fa0fbcc701ddd4eb03c961063314b26410649b2db820a526563b38be6c5822c50b31126bce4a1ebe005c1e71110f62a09ea877d63115588bb715af61dcba5599f13df81986a1cec0abef5008986b9a86f97610f6875721b4f2b6a5b7b9e0d614ad60db2f4ec2aae4c312c0f8225163279b61f84f7a8cd40415f71916eae3abb8a1ff0ed64b2f866f3125f1c0ed738988c6dd2875addc1e1bb99b79c1bf6590dc6f367a438c84b7bf8f7e4cfae617c892d36369c6f924091879ef5ee387a28ba7f86b322c2293361edb85448e57042b8429b90349e1b4c532086c46663e1be1c3db1f2d1e2bb47ad65c7cfc6ee2928a052cf1d1acce38ba3a294c708d0fd4c79f2c4d4aedf5f2e0449417112431a2840338a533c21d1f2862b3d02857ceb9f24a9d861225bf2a8c4d55b8e392b980ddc19b8c38bc78e52b87e0f85cd6aded3e969f75cd1631b4c8f4729fab9744d48b4d4602dacddd316452d7b8a3cbd97861c8a64f2f3df09583984d6cba3d8b661c1fb86c606d98d82373bab9747c521b2d3c35d5e617211328486b214682664ad196f7793a1d1f0371905b3b907fa9c7202df1c88b9693939289930373d0ffa610cb036e8338a92fc6deb14e4f6d559e9a5d9f30e23a3e42e345263a85096521cef7219c989e31b2480e7b9941b28d4562a99a2dfb39af6bfcd1daa8831f11878d2d9e0b570b74120714c73f670d1854caa4b33ad355b55bd7ea996dcd110347a10e6105e7609046a94dd3a51635c266cb53454d2ca5ea5bf52581deeb38a4d6bf9c40635d2052bba9ba4af1a4bd178f7e4c11ed46c8246863cb84f17c0662ad9578bdf6f0d8ce7e37fb118a27b9ac96e9762584e60ec5dae132b36e7568a1f458d6de9341a715b6a8f0a14825c42b615b678714a0c5e1e61292d3945e6be3097dbd6d0513b458b522fb66d075f02d4a67a83230359333703e209ceea3c780f02ce411a7085fd1eb4dddb4c3b364330d425b2d7cdbb4a3f414171293c3e3ad7fdbb7f3bc273fa873eced09eb216d5edcd24ca716fd75035e88832bcc77c5d1cd21981684c90c417c5fe707a249202184403cd6a9ac275329d687bd0e6798b8782c0c340a8440c30a82e6987cefd689778ebd955f6cfc1f12b10748b5394f8c185a24286afc023250ebc0405299652ae4ed9c0cb4ef34b60b01ee304c42f8c9c910c461d47ae512431aeede86d5a5b769e371833e348f58f539397803a60f7b35837fc923e8163c2d55af9d0989d3a06b92a54240cf97d445075491b775a327a7417ca4b233844792cb985c48c6232a9b7cbbbc6f79d89a78605fc850dc88c84bc1b356708a3ce940b7b67bd133c10e31edbe0afb33911ddcc43d7f4f932f2662741a7eb9fb40775150cc402737607fd695656443595cba6ae2d51840571cd553685d033dc49a63bb19a690f6b22698d4a98e4ba716ed35f3acad58b889f8969412d004d487894b776313cd0caca1826ba1afa24f50d0dc7c7f1a84ad0a2cc5fc7b961c9dd5645fc11c13b3c819937417987fbacdc5bb5f0bda544e348fc981f39b289f7a4cb814dacce76791b55d547c4891477d128b8171d9865daba0bde6b52a3901987a85bebe97e06f7b14a564db638411dacc4490f68037829443b6c499b34a358d99ca150481d118d06cc3bda9eca909449c86468df0009e92f9ee4e49c0200797940f01d96ef032d6e49ede00d116f7976728381992fbefc5ea915b0d6b0ba509c390f69390b742e14b2b9b35aa2a4af027938b924bf99ac17ac7752651d7528f62a05d264fc4490a902675b18a2190c46b48e300f88ad98c10015e6c03ac4f3a6f1b66c1c55ea021cec8c3a46ac5e129d857deb62f2783cae6e6e282eafffbd6e79793070ff9ad8332dd3157a8332b96b3e1f6db4f66c1d82fd4f7b1cc694f683b222f3a0ea5c036f7c0c239222798df00c1e871d21087ff698119240eb6021582fe947cb73c99e60579ac2dc036e2cc21754803eef50bd1ae1ef10da70b2afbbf5ed4209990ea68c214c44bc4c2132e2a585cd85f7279716488177fc5ce09422fa0590713f88234f44b1afa505e17a6997bb5acafd5d148fe93dc30627462a7b3a044192b240f59a1e4e8437ffe0fc2fca62934ce8754acff9352532667e8d43090be840e9b59a10098b7417e379c796a14df471aa57fe8914f37bde975f60fc221930ebce4a6965c532dd7fb47a4f81a671f39aa9cfa94b63f7e3676bb5f2b0e58df13c41f576c8fc7cecf4ef3af8e8f5f84ef27ff3257dbd8198051995955735242dc4b6818cd42a3b243d8f82c0a4cd8b16dc82c91c9a3707aa1490e133d0b41c6017f86af686962f04903cc2abdaf589776996f94e8a16cda28720f4b2dda50a41899e15596b9584f85f5d05c5ad010b16a4b4fc4429e7e9b98d2be3b9d33b571a56df05b91509a01d22f31baa9f8cead38d93c8eca8414bf1553df074eb59565c0e1408b17572294e1240fc286134a7ddfedfbb1e521bb22d8a8efec137d3bfb060b4e9681a32920fb1454acb98d97b07a8e936c73c131251e805e2c693e383fc6365afa437f5675e5d7e6d592480c9dc40fd8c48df3042dadc91c4db6b74b28215eb104f4feee577515cbb64aad2597b89d853bbffc1c547d50e5ce7dc9d54abc78f74a27d5073715fefb085cf241343edb25de690cc3d40764b9e4dfc7dfea89a64746f01338eb813ba037ddc9e9ed4df7301bd3085f391ef897e6ef256592bd3853f883dbfd4f274b5b3e1c678508a96444fb890899944434a9ccb409ac681a6573f020710a39cfee3ef809befaf6ce542766faf2ca0664b452319bca00393d650aa5c94ca3ba1daa37983ba222a619a299083633c811aa5ef919dc743848f2bc065e4f7630ac17600828476eee010e5391ccd0537a0e22cb9c09630c0cb5804c597843dcd8c3c6bec69d6618c4d882d28d031274f5803cea4cd8b6968c9da1b3852875b462c35a11a30957352413a3f9f845895bcf34b7035a2d97d47d8e64a6a3caf36097809605352de65658941a59be29739acaea130b65f653c58a94c23e6e78188f532cc3218720a34ac5683389759fc402629eb0f4513f48caf6cc6e0161af0ae39649af65504edec70d05a80b1407e22ca812b0ad53a556154710cab80d56f06ee518d7b584356089ea8598a5de919999ebc41f37d85e59f7f51f61da21165d6eb5913bcf69ee4265ea39727aa1187b02ee85a1e2340d393105f0bf4a8a8939650fa04aa786ea99d0c29ab65902bc8d76e454091c1b00692507c340af76ac09a3fa18e15e9dc2f09df95eacc66f51408ede7c51707dccade0e2e05081abfa7f03fabd7b50d0d3c06d196be72a3dc4cd0d4298dc28558658f9a814ac937697a38e7ea06e8f295192b109864b3b20749002f83adbd039432129ff0ca6e68660d2233840f26e54628eead398704db95beb8f7352318a72e4d552ef5c091f44476255501ba820651550ce54aeecba0a03065c105be408f128fe68b82640195aea9c840feecafc573e18e64c95a927df91cd99ea705414952054a1f038b8155948f3bedb0d7ba59762c289a63326bd01912c43f2269e7b74d05c5e795a42948e120ccd12c8403488f9b5fb953c0466bf0f043faf49ee8f411eb821fb0dcd92e308e8b2556f19c0084ed0a638aa80699a300b52950f53d54192d3ae32d831211fba3bbd8b61569429a678b83685ce69b498d85add6232e6baf778134520090a03d7f90de8265e31493e80c2e53711a1f68a8b6354b393da613f2c0e2ee5e7c0ef0630c699408ffe14b95ee222ecb8d036a3dd8d96a5e2609eb0030a5907025107fe50a56a1db4670ea4750ffccadb28f6eff7cc5c3121ba61f97b10d270fde0503bdd6dfcb83b1e1ed9032aa12e1989da03cf56f680ac47caaa105368a8241dbe1992c455c137a8f0b44aba150894b2fa9d32b97e855b0319cb5e49c07b750b04bd1dd355089b80bc032d63926e72dba6a590cb5df165eb64eda20a6818dcc655a024c681e8fd1b6f207cd71ebbe5504e1201d076e14e346b55214b05b0d9870759e22ac6cb8a87d869bb46a36cd913fb851937411954ba0be859a61f228c08e29254c0004d10f9f4f2af8d8c94546c6fa0e136b40afdf34dabd058b4a30592418d661536b08d0db3fe45ba5b82df30ee95bdbf901e0d1b5bc4aa6032226f12c262125a60bcfdf4bce2acad71fb996b1216577dd1a7028e049707bb085ff9fedb147f356287290c7eac2b563e8c8d46a98d20d646e691af01b5d8aa6f6c2314e9dab2eab14fd18bcf536a114a332ee59f1e345c6825f5eae2d71f4f97beea6b76ad06962db7ec339591202be8f97bed327857f3ad21ee25c77c22d9577e1150844a89249a131b687c2a389a308dae42991469a882cb0f0cc472cb969e5207d09585770ceca86c41ebd00318a928a1b27ecf70c3b2a00868d96463035bb431b7b306e37226a5fa485abbdab43056205d7da3a10ced95f12c2c63eb4e5c94051012c5a8539ea7c1e10553fb2ecaf1fa70d4e5f0c36918eddc1317dfc6a50173df226e1ceff93f85396bb434134b6d8efea336166957faf62948fb71185077ce5834dc1e80040cff2f6eaf723520ae0e959fbcd4a9426b845f10f309408739b76d8af3364c656fa021de301d4d2f9f7ef7247670f6fdd052c3578ba20d03cee9232a8625125df831cf73c0d8917c7c1bb88a4bb106b2f8c69f33d97edf593aa9bd987aa82c74f1e1a0c29acde05b6512d179d1ed00969ec9cbe1485118d2f385e8366fd154233f2c41826906e6e1da734e104c61c881519081c7866559d403400d8dddd455c6ba01672fc052ec39060d3da76ca31e799c5f73b8663befd58566021fae1b686d859e6ad92c5faff0e0720642555f9fc05b2a88013325e99ebd8ac54517c9e510a62fb64427cac1a0400b2fb5a50a33bf3e6852e539757102fc05189bd997969abe48653867a21a203852c8b93cef826f85f64e93fe3da936dc8100156524df49d0c1730f3685e8cdc1e898b812f30c57dac4f473f6ffb83dafe9dfa70b4175037f889f624618aea102c42a7d3325726a5328813ca55cf7cdaacd3b02f182b8fee70b05021334226013038f35ab01a21e0d2ee709b55f4d087b641f68c6acb0e469c23584a8045269d728e8b6e5412ae1c409d8a036c12a6548d6c69744d1d9931a95dd5e188e02541ed7b9d3636528529ea3a1974bc3fa786b9853ce7a0f3fdc0a4ad956308723710a9acc389b6452264593594a02165aa0ef7835465cf2a61502017c93dc3b330b2ee15363f80281852eb1f428173c6d8616213e396d385470dfc0198d956c2712a73335b4bc88eb50eeb848ad15c430197aeec6ba2101a47633b615d245bc1970dbd5942e22cf9b60376d4d67b2d5ccc417e3950fae363a2d3a1813ccb84ab20183ae45830194c52f8a6c54406f4c0a47a455d30b9f72d40bdefc7437dab4198c253e15181342c0326b28572c675da1e73e6929da21288e767c2e44be2e4f5c5c8f80fdad9a0e354fb4e96962d88b9a859138e690fd3ec6d49ee8263391a064017c927a7db8ab8c9b8e6abeaa14c52ef270f0cbc777da6c38722f0a88aa151b5e22cbe70783289f91b1a428e544f054b6d23a2d69dd0b216d884432ffbeb49170df7b1869e7c1bb5effdd4d871093efb02cf048877402b6ca9ac8f51d7ca2efe4f6cd823b42b1b7073967f620fae9ee035410e3c37dfe472bfad0d1445db43595b76ec43346618bd1453dfa3d2063d0f647bbf9f869d79bf74956c5c9d96d00e571b20be9213599cb85dc2ab453f4a0f5ae416775630105026424bfc88806098126abfd518cf0ab1901b0c06925497b7fc7ce6753f28f5d5545a535b1b724286b13531b23347bb069f5feaad2fb4309a66e2a3a07f3f194d00fd98be183e563b75f0e0a0265054446941946a49f4fabb656d666aef8d8006e4d94920d8a24dc4f79aa14b73ce78ce79cafa123b9a30edbad94fd980a08a515720e4c716abc359a441d5627898b76139e1348e1e4991c4a5e1cd9526d1ca1399c4d637f325668712d5c33b1e81f1fa1a54872ff4fc4147b1a8e845437baba36815c1ea496edad776402f5219a295858307294ecc478b3dcb64bb8794318b991e89b90d9042779da3a1e0bb836a0a6d42bad71e534a3c2928426dd7d6f5ad718bb4431b938b7663e2e8984e7b5bc340030c7006a38b0fcdea219a67e25497e32dd8ef30310e55c843f1703df76d69b4f0d3c1cd7789191a7369ae1fedd88e7d034009af172433d05dbce1cb9c55a000ad1188379f43bad33b15b674115e45feb5b790d8644cb65777fe80b292aa0f64a97d244bbe61c1b0de72050a54329cf4e05e60364248408b774841834cf739dbc04d5031a8047168bdd43a4370edd8171c969e403cd024a1478e550525391fb2028802b180b7e765587c8a7b0287177ba8b2ad80dcbcec8e0774d1637940e38715dfd2abb6d71d326ac7b1d95341b2cbec17d807f12527a9ffda456ae1b93e47be1290baeef2b3840259a104c690fec1d064ee4027b0cd95e2d5bd9429f966c3d8b26279154f14eb1036c87b776a289c8396cc1029eb3a5ebe9363492525c70ba8992fb3c1d57c6477ffa03dbf9ec84c69b0c0f363d6363e610d4d39fc6c2100d85b110bcb61c8c2952640360b4b92d9e960fe536342b3c0b958502197e779bd6edc0a07f50fb6547d36a932fca4efd8244326368077d65d8b0a64e8cfa36bf5126970a4cf0c8c924e98572617bdb25932de74cc43a359e3158f4939b6ed21bf2e78dab4113d4fecf5d2383841a2cb35c59d30f37c58dfd9a1e7af5761e7aa7f2dfb235fc11b5f8865af9ba034571f33c1b9d6b3e2d2d99447751c112b8bcdefaf314d71327d3558c30802049f767ca5d0c5cb1027949e5f13cd7edbaa4e558fcef9afbe3aa43efacdaaa6b295b4eb3d973ecff25b080334ffec52aa8d97a2530b5f7fddd6c08de4be36d9ccaf86c5f4643a19003faedabfd7843cd7daf34d1f402d3da8304700e5fb062ac0ff4741dee9e0eec990c307bb44eeb172a84cca725156e88b30ae91b506c58e0d0afae1982eb4d5122afe272e78423a83d47a28b652df91ad875a90bb2bee1771ed3f23bf462ef3fc4146895cc883255bba586d266d2d8584be9f8e6ca54f4996f4df1c822897be02c8989594fd611415d22ab94af9ad18f5ce9f11b3c3bf1f52ddd8270be9143faae00f2d0deb909d838d339df20cad108a0790acdb26f5c70682ba841a0c7185cbf002fd5da4530e8c9a746fc0ac47e0127a64e9059fdb47a5c5aa9d55283a3cd8fdcf38b7c89b113d01be70cd13e7f113ce9d1dae25bd174aa98ff561e19a0e258537bbcf5ed48301d61baef10333abda8e3bc8f8691b45c91f12bb310304e4c892e4d8369c0c142b9fc874b47131f26b5997cf952bde86f5e2868ba0400e056d1cdd6e692fc533b6148d1915ca9de008ab97ec9a068ace97dfd081a0942d6f50bc639e54db8c1bb79f736322cfbe6c44902b74a36930545403fd74a883b7ac7d695858c09f015568f11c3d2248c44f91ea59be1ec6d4e1b009efba1c4498de0990801756e70638fc9942318680e0ca8fd3b66bdd8f0bd9c2fe3ab3ed6c4d34bad0be40137370bf02d0f85901ed687ae106da2afb619dd8f75930cfb1b9138ba4e4bbc5eb7cc73da6892e8d481c406f0dce25889296a1154af3c86ad656f5ad534b9d20dc727642e85667877b55ae7687eafcc72a929dd09d662f040a47cf3b95ea0782e17c51e08897a4799d46fc3c7047ab3df47750754153969e8e266095b0bb1c159655a95d40d6bd68ff955dec31012d76583c6d4e5d4126901ac11045af976945830923a4a074a33f2acb4c9a6f5b6c4921558af31e232c31d436bdabe82cd3f0a4ec0ddbcbe9beb697013d554701f33c031087167c43156775b8e4da66d3db73d70955300aad8b00fb1e088326de5b7bd3721724b29534a29280911091e09aabac3be5c17e7fc48f341a263f062dea76a1673f215424e7240cf06668ee23e3978b53a3630038b2354d9c202325aacc4195e9c784119461411841b4024146389979458480644f182b8aa903b604c8431484c431fbb070548653e4739fbe9743a9d86ecdb98fb79ff981da2a83073bbe423a9ee8f351d2a5d5dba94d2fde27d1e90ca94ddacc57eefbdecbd2d6ab852a8e17fdeb7bfad9b1a73f006731d6ee1c7ccfc38c893c10f98c4fc7bdd5aa2a238f4bbf7ada529bad2670ec257e70fa2ab1389b08497319e81605e8d40a7cd9e147d07f49dfa69c9c9af5e91b698f298d230156969e8db4fdae387ca0ffd55f92e3ebca18a172eaf19e6ca75f101f0b322ef33c736a21f42f39963ac83c79a8ac75e0e1e83498fc5ec068fd27870f0618d611863d8621816310cc3b00b85990e88c9638eb424c5a3364cce7877729ed1ee724dbd3b799c811a8f93b73b00021298e13a4d07a656a618fd4308a163de417f501dfa0b72f2f620b03b69303c0d022794b29fba0e011010f79daaaa02611bb550d5002619204396ba8ce0336ae46b9ce72e870143e0e1d99f9caab446134aaa991e6ab0060db20833141404060a8008a2c265872e2d0642d345c6c5cc4982b87e3084115c65082d668062f0040927b428a3e84808403f30f102841228b0ec0004dfa52fcffee454b1ab7c65e3156909084b45c801d358c454242e8a1e890b222e8652412de98aed906c35914f2f231f83fcf03f8e84d779afb9cb9461a189ec6b9932adcb757ff89bb8d0ba78a59e04074c7b07cad32c30046508cf07f969a096f4a7bd7031d72d74b90949d701ca109e574540dc1d40ff5a6feb6ce81e097fc5b0abacd308567be114fd6d445e78ab2bb82fc0675e665638d35c11feeb11b99c880ab0ff1d79e1fdfadb5ee0a05f8ec8e53ff2577140e8cbbe508af05fded0396d7e3b609743c72e6f88ba7c986586f2a79a2995a8ede672c9c1154cf54bdf0d561b4e9579b954f10a3a0eafb44d7140879d3f74d58d3270c9544fa918e5d7a6d29e13a7521cea282ddfa9f0d0f57472d475a55057dca463ae272eca2b42254c71544ac714aa74f5e56581a6f393afa603f3d306975c7c49ef9ded81b2a2473078b8f81884e8daace940a96ea84ea793a6e3e4a793bebeec3169b6d581aa107d66db2a74157d87a2ea35d754539a277f9ae4209230457c4ac7f86cc631ac47b7bc9a19e5ecf1e4d8d5af485a8e3e6a5712449ff9d5af4949f4f057c6add25eb94e27eff6cac94f27ce4707fcdad66d0f5e8f7c72a80f639bfed58d6532d324709d4e1c0d7b459b681082424df96ec990198d0a30dd98f58ee699b6a74d7f6df0a4c862e82f2a36c511579d838979e79283464ccc59932e939a20f90bf59ad4e4863fb9bc300c730cc3300cc374602e37b8e4240405a56d4243de9a711dbca59db6939cd9b485bd6c6b92b9a6f98cc665dcaa59d1593b714b3457bcf21d989cc1e6f039f9ce85d3693f3296058b8e299f30943fb951ca13e6acf1c0a45c7e4be44cc597b5f634d5744877b882ce4e38bc624d47eb8881e697b6e14099d23bc736cd4fdbe5518aa7618e61dbc9df0db010f3999f1c636d022d2cc250963e263fe356bc8a7eda70bc4f9b74d4b6d2d2f4faa72b5b8d87e39ca04c95bb4a7543625bd1772a4829e58bd811734e8650357995aa32f725ebe87b2f83ef71433b4e50ed0127fcf5af07d536ebbcd5bfcbb78cf94eb978eeebb5067d351dd7e2f00ac75da57aef82ee4d6677e36f703839bc62b92d099fcc2ef58cc3ef317cd76bd8a9a5cb057d5d572f0bd1d551abf34eba365e5d3700af0478f599ddd16bdb25b35da53ddd9eb626984bcc55db4669963e1c60305e9a0e94a603d374f46ad66f667de37505b5627d6d3360196bebd86eebe468761be4647db71978b551240e3067ae00500b5ba5796f66371a9ac51c105e4121a6be74b99c9174c69162d4894b498ee786bd4155c84157bc83768b8416227e78a83479787543c7f3ecdb5aa27cb7523cab58667077af2d1f7d852e5fb8d773e818dccbf7df15172e0655db1b36480693a68b97b05b5c3b5310f68bda034cd882553754704baa049e736b581fab216b5adbc1fde54f7bf4d7a594ac6209616f3293184ae371820f9fb4079ce010a6f057b70f250f55034218a3632d25d78d202707453540da32f4bac509ed15698b930cfa30314c8b318b61da35b1abbbae4dd3361b7e6125687e617e6d26a41cd3fcaa717f1b1b1b9b1a1b134c30c135bfb60ec85f29c75cab71ad4673cc35bf5cdb3a20af39b675401eabf14e7313b81bcd6d1ce3b41a27c16b2e0c7b50d4d8406163b3753f30c3fcaac1ae19ed72f513b6eee27ee067da8c0d28f8911b0e4ae650ba8cc6311a282e1a9a6bb31180000210000000e0c68d9b1b145038e10495ca04136cd828a184548a04126c6c6a6ae6a4a19999d1b42cc33014ea742aa104aea6c6bbab47e534f04b701368b82ebe8dcfb86a53c531c375f12f4f6d7fb99a50c2a68a23c575ef6d5c55a7c66d6cfcf20bc8773f30c80f7c1b5c917d12fcaaa9f1d66c780dd779cde529aef3bfba1a4e27f780cc777f46dbf41d080a355bbc276c78b6d928e10495d770a9d46759365397d7788d5f279c70829094d7a46cae948dd7b88d5f5f63c209a95409ef89d47ba2041b365426a4de133636b80ee86d78cd66534209291b1450d8945935a9140a426c04b1912ac1860d1bdbf4d66cf8e4bad4dbb051e329ae4ba55242504d485b9656082d9a5e91b478f29d0b18cd3e6f517a45da72f4067845d282caaf52609d159801d2d97924e370beba21c34ce85d77e3c6c302d387da50db7e165fb220f33d8f39f6b6b527dd86d732f6c775d869d3bc3ba09741962bb2edfb3eaae735ae43053d74242d90609cadc65c07f42cbda5afa68a636f9e4b3f71485a2cf9e79ab1a6396ac35cdb5ee87914c67da0a7933ba43ff7e9d574a01ce33a7eacd31ceb50fe23b3ad938e6d97abf60411d2f328ee033d8f72144704e53ff25571b49f5cb6772b177ad1fd9382ded027afba69b2432824fb1b2ad6e11484300710c29da92d3eb6f600131e72dd083e72cf1b461bad6f0bc94f66121295d0aab7f1f4a91a0f1d7b4f53b323ce4c3782879c5f11591f773f77acfbe97baa0998e946f0bc5d549b5180c1751ce79c15208cb088f832b8452033d0d0308857dd1fb70d5a2166350632c7b82350a4671a05b435e3be741b502decda4e2fb9ce87b6b0d52450f3cfa176bd60f3cf00589451467f7ae299c709f10a955d3b03b2fdbd5596effbf01afb74771930e4ad93ab1196ca8093abce40c3eeeec69b18170ba316cad8c95aeb3dd661e09d19c3fa0f19c668fa7520347084cacd40c3eeeeee6a2bdb0d8241d01d5a24ea0b442fb8ed2fa0af501d3c6b9527a24ec32537081414e4ab9c2b939ca4fbe908fdfc704367159955d3d643d53b3fcc62ff4126aa56aaf7021acd1e0badd0b3bf5943b3d87586768cec0fa98adef989f3696eb92dba4fefa878a50414e4d33bdabaa2ea9d1fde5c8a9bbc5205acc74d6e44f086d6565555c90355a1abaaaa425555857a3a22e88a2ea1943edaaddaadddddaaaa8a011105a638dd2e0b34af72a5642b41632407f1d036be7d87b4e77d6f12df5bc4f7aae0d787b815da6d837628531f5ef17cfbd31592aac39c0386e9a86cc6b59705fee15bfa6a3a88488f8ed95c3736dc0e7e9b9a3d5aeaacd5be4538f89e3a3448644c8932c5a6e6badc877d4c8e86ebe60ca7715ce7ab5feec475abef5429470f5e754fc829c92620bc228c115eab2984a7595ae62337437cb8520ae2f90d6a143b8a33f223c5ec548cced4c980fcb00e8f11d541f9ee0cac03395436e397c6659b90998da759288e065eb1f7e02e8e5b0efeeb9c345a8ce703fba87d7acc39678f1ced3e397aecfaf4c87256e0e996bacb50e21f17a723aeebbadac83c713e381878d5e30891e3227cbd234f93c0f3f702fcf7972fb7b30e0eeb4414ea0625d8bae2a82084509799795d99873e740821ec26b1e366c1317adce17e5d3af43fbe1ef3e3ebbd6bb517e03eed191d11ca7e2de4e974da54e7e3fe81b65a7253653df9ae0703b37a470f5ec5227039bd139e5f5783d11c9763e4b89ca352d6735796b278afa9ace7fa3c6e529fae74f5ba1fae9a1fb92146b28fef2337e4c8c3b70553397ccb6ff72dbf0b3691db0d293965ad6f8029ce748875732ffb88a1fd473b90f619da85b4d7f0d3ddcbdddd3ca74ae5be5a754e8e0fefd1ac4e323bbf02b994e3b4cfdee966028361ca174689f3300a1d84cea303399672a694157d5a81fc3a0f84cee3e33f66763c3c57f88de95658e115a90c2654898c7cac136560963bc47cc0d0351b9b6763dc3e0af3ec34d4f0e4d97f9ee862228159d2cba35816092e9e1d1e315f91c004fd116697031fd7e4f2d95cc6412f465230f8048faaaae6ac5c9fe8eed656a59794a598f931f39583939a99aed0420e4f96ba314611475af3e0134f68115ce0258d1da400892108b13551850f48c06c31451a2b84226264de272f80315830328872658c22f6b94fc41823c91534d448c153c3076c0b267d5468c831be90085003aa810228a50a069f1e181a62a8311130060e6588f1c5698b7792f38f52eae504d35cee628ceff52a5fc1a99965df65057845128308317a78020d662632880046d312137b5404a6e28bbd50dd0d1198994f582089b1c3cce19959a65250314f317060a42f64be5b3d30b0cc2e2b50f12a3539788339e76a31b157a42e44ff78f5aea44bd02b52d39227c02b52d393efc13acb8457ab380b638af96dd0751b62c33f7f43f69fc375e51f66b746eba46fac91d11a298e751fdeb7d401dd69818186b2f4d767d09e7defb1ad1fd468dac18c6f81514659fa9d8af19a88f8f7cf33ed511991e389f9e73247df609d9fb7ba51d4a58abc670ff2dca78623b37529e1d7ed638df6a88b96df45b2f257084ab748b4f86b1382d2593c6f3fef21bca1ba826697939363858c75c0f822bf391f2366d7c2afb790e36a6a45c9c8c0de86bded05ec6dd8db5ec0de86bded0584dd673c2ec256737296d9871237d459d7c781f19cfded1ee13db80a5fab2a9f66fe31c3207c30dbd753d8dd1e3ac2c971f73566fbc5a9aaaa5ac1ce52e2a870b4e7fdf3550abc71ddaa8567e408cf0a2b64cc7a3e31e6325ebd15cc4e3ece8a59cfe512d39bd5fde476dada5127d4e9ad78f57672dcb4f585702bf0f0ce93cc7c44034c972e954aafa4a07a1e1e9e6765b6773ccf17a757f499596f8ac92c250fa64f0bbc7acea35778fea95431f480c1077b4b4160ea53ccf8cf53fe1c877558677bebf75d3db8ffecace9d8521b0655f505e13591d712b3e3f937c5ecb2f899e2781ea1985df6ea06d09e22f3d99fb39405f6e72bed81fe3c851cde91d17bf3c9c1bdcc331d0faf5e0b5b8e1ecc7a0e37f858cc76e6619d1c30dc70de91dc6396fa63874c4c7febd96a3af6c62b921731f87d766666d6301eeb3ca4d9a996025ce9a14c5a1ec98b2b332c0bd0d93db02cc41374f5b72c48578fbb9339e6ddcc7636d63b00702b293bf9267cb77a13769ce03ba0261fe84204649ebf2b620366e6dd5d4ec04c07e4e464e5174b0f7ea700f343f40a18581ee5fc3c9ac9436d8786e9b243bfa0f2eb5c202184237c6103e5f244b3502ba2501ef440b3d8420846f86d0d02403510c202b858400a66ba17729084f2c00a15ac5885f975ae0b0128b12a03051458514384005ea055826ef89f2bf517d781208b9439335d0d52d40ca123845f1ed8975f8fda0e09842b5ea8994e044a3c2e22ec8842fc6a3ca0113b7a8a03a2925f07c81ffcd63fa4bca49497bc2e29afeb92525e72ad98e9de010dc5afb3e6009aa35f97da0b3dbf8e9de0b6aac2c9e5d6aac2c957bd03957572bee142097f62edaf2050065eeda33820d2cb841c11ff935ffeaaf138c00fd1326bfc76dc9f5c3a767abc659e4891f828171ec5ade2b8fc65d001402fe8d16f6b2ff04b0e880cbcfac12b9e418cd909e11eace32387575b0a2b30279e6901b6ab36453c5f91bc186a4c8d07066a44a0c91263bc2412d313d52b1213954fc9243ec5ab45622a7a5d15d4c53c35ae9031f288ab97ae9bfe16ead77505514f7d90cb23508c58f44eb9ce86e79a6011e5ba75475e3b220de1093776e423d769ea31a05610b5509e7ad59e142e6f9694337bbbbb45d74ec8aa8190dd0a65fecbd9defcf7fa79ea5d3142f8ccf822e8a333ffb6d1fc82e85b46efb6dbb66dee8697b9cead52cdb9dcccfc63d41e227af445f8872c99672245e443ff89cf1f37fe1e7ebe901ba265ca3ce3c01d4c1d489eac0afadd7e3f8d31c608e1855df1f278c50821112e26f60a185c68f4d590ef41850a55dff30861e40197a3c0de2fc71acc2efb55f67d5631e7cccbbb459061a85ee7bb92cb54bf505552b68af90b966926c4eca6770fb7ecb1859bfcdefa7593dfdabcca53ec0af985485385a47e1f845048ea95016333ece870f9bd77cd8710420cc21cbc8adc7b2fee3bc2022e65458559ba861566e9773c4865b87c271fa98c131d3e0cb10eaa464712238c9958797d1d489cb08e5af9f2e4f5db7dbab71dfb3a7c10e2950e1f8458e7a67dbf393e4222032686116b7041785dd745c41284ef4108e1d3d7c42b7dfae5bb1ff8c583906989bf78909bc0d8852be5a3bc72dd4fc318211398724d685ca9489593d9a956f8e73ebd9365abc77537febd48e5a3ebdb899a8e48e5a147a7f107a73fdd7230ebc51eac93b94f0ecce1c9318dbbe9a1af390f7d8dbb99f1209a5f3ec35d2e1de8e3d665ddce6cddfbe81a966d408f6d42b247497949b33d66610cb160ff79efbdf79eabde1182fea1fea150f04979bdf8e07b6f677a1598e274d0e89f630fcc62bdab3deaa3389e63dda330b24786528bce7513afded16a8a47513931fa57d4ac57f41dd42c2ea6fe7a3759090c5fa13b3ccd7a7e94c59521fc53ee727214ca15c3a2cb2b4657d7b80fcc0b93e130d559a977de52b39e3371137f6130ac83f2e72c86c744df23d68962e5b94c21051c9c6ea37060fe418e978a30a1f7c662989b361ed3ace7a88dc1f0eaf969e32fccb4c45dcf31189d9785c8b1d232a13d90632c14c7f3c74b50e6b2d2b2c037fcfba13866b39ec72666bb2e0b3641af3e9b64eec36f4aefb052679c4ff9e74fe967bc7b57fe266adb4d8c71cb7cbf49c61db161e6b5d27306f3967e288ee7aae799bf30282ce3364ab39e87793b0522f1ef229aecacc4ab072167612d526614d6d19b8c955ee9396b79ce4bcfb9094a4e3dc9a4aa6613ae31b9058e8757eb9dc554ef56602e829966c426725e6f6436c65ce9ea54f96a0b754c5bda827ba47d9d683f1e7b346a96fa4f95afe0c6d32cdeb4b59bb694a7f0913a5f5187634c7f755775ff2872aad49d95961b08987260aeeec014a7bbb05d67309e4f5aaad034a72ccca05fa2f99e32df51b3d8a59c53a5824b580766e1d503b32cb0431e7407565138a43d8f83478aa308ba0365a02cf69c7f4d2cd60b430209d0e83575ebe2bc9853a6f44e5968d4ac87c5dc29900bc885f6a87e5050efa8360add6977555050178386aa10e9a08bae70f2ecb189b90e9d680f0c94051633b78bb947cf0e8dae98dd4e99a23dda438a83b983dc55aae864c628cf45e40fb9ca6c73fd6476ed8407eadb4871b491ee539596f661f21273e195432ff386375b6262cc7379590b3b8361676d594bb398dfd1d1d3f2fc8e6edaf7890f9418d3da273c80819356dc6e9af3a13ea0a2c503440bebad676e62e8d6e5fb4d2eee880db1530dd16276dae4c5ec74494bef28162ac0086a31f683163b8f5916f89566a74d4014077b511cac12e23bec3bec3b550cdd8abedb901dd8b7eb68122f6ea7348bdd8bf67cb7539a96853d5a15d8d92f206617cb3c7b6a893625ba826a9ec0f4574031a69023c47cc5815a474c25cc510bd55262386128cf3ce3805a1105bcb4ce5f58475f4e1a4cd469f32a66a78abdf1407a82ccabc643f5ccc4abe67809a231fd797c7af8e827e60ac17c5407fa935c2724f597b78095313b9ee7e17c32262e8f575c35efe891e44b8c339c9c6134af0b42082fcc48caebbaae6e9af1c50c268757ae244aaf56ca80f2aae9809c518e115391cc78f2dd0e0d098919b3a3ad339afe0d9db184a488b52c8aa9ae1cfcb7cdc79ec643dbc1bafa88f2ec57e381ca8aa672ddd55d7ea5b665c2ab1e621de6d609af56723798b1c9bc7c30d9e393c95c0b3598585c626ad7dc400859b1207a826487c68ca05f5f2a3268adaf15d65124348cf0eb7b65b158f9f5d5022f1e4242030cd1182abfc4c49e4785674cdfdd290b6394a9494a9abe877298447ee66f0f8a635da66145a77784a0c0df4d83cad098313baf3506ccaf2f13d6d9b5a168fe0ed16076cbed97c3ad065ea90d450fb1e8d712290e0cbbe236f14a957761a05ae2378ad5eeee122d125e010dfda2106730b589683a3a99fc3731b43f7f7dc4553267a5f2e159bd9b18b64703b5786e76681533985eab98a1f4ec3716781707a459ad1b0bf40920f7a3592d759da9a962069667ee87bf86f8ea8f6b35776381534bfabbfc41ae87b41f973750ebc47590367d0d469a15b45751b3b4d534346bd79ba88bdaa89d7414e60b07f6ad22863ec20ec4d3dc8dbab6dab5a553a5f2554eb3568c7eddfebabbbbbbbbbbbbbb5b1e59574ec6c01d51871c50eb39f6de902dc3dddddddddddddddd2da7ca8d5280690b1661baaaae15e525457f05313bf973648516c9b6e4949053655e6666666666e69172aa7c05492960f23ffcec399d1379eb7f9be4e75e61b3d40d32620ed3d755175897eb735d85c599dde554ed00531c76ff65397547b5b0c1981e80f08312455180924012a3458a184dcac0c1b7882d93c1ec52377ee0bf95f6f02ff117cc4decc3189f54cde28db57387eed3ae6a54fc81713b6276da8387dd2d71d0a2469076aaaa6fdfbb349ee249d5a154bf9e3e9452218c2eb7e7d7061b2af9c1f4aa60f1a51392f9b091df1899628c4d30c218e18f2e6064628afcc35196e2550c6697eab2f752a96cfe605a1fd6eb3a752914ca7db84bfd721dcadfe5aade610ea652170b6697752ebca76bcc1db04e64c1ec5ef6e21193bd475f377cdd31baee53c2dea6aa1ae3c0c4845446962160dd567c8a575784af39436185a6220d61c977d93b698ff2e007aa6184a6eeeeee6e1962529e0347840d681c11041920c8d001870d93c587237c49238d2d5e5421573aab8f0e6406d659b5a2a1845495ee68ebbd552fd0db4e0265e018638c31c618a3037230a2228d15b4a00c325a1b88419517a0618496222eadc8f4d1cac70883c4e8610483a4179411e2075d50e9e18928987cb10416672c11a480fce2a5394a05135a4bf1e2a52e74c08b255198be28c3840468055d960883c5920e4d4eb60b2830ca2cc97cf9afdcabe252b915638ea0c419517c09a306100e375029430652948c386387a0c4755d314028c59c1ac30fd8fa01ba0cbc239d06de51fe813da04b15021efa2a073a0febf8b00ef41cdc6b010fc5da1bc330ae25d7c98beb268429de9937fc493fede1ff95dcd990edeb29ff3835e2116176ef65bf6e20eb409fc3ec24aeeb5e2a8e75047c6f629efc0fff102c63dc7849785a79c5852de18e1e026e14d834d9d7a888995d30c9c0dd9e131d61bc21c6e81162dd375c8133952dc44cf0faaf3d02fa1d0cd3d783ae7e3ded775d9777af566017bf0e75ec6d99d4f178d5ab8f57da33d17dd8a16ecf444e9f8b70f91c436dd96b9477ef51cd350a750a8a635d915eab7ce46eda81beb9ae7b7b7bb57be671373174ebe4fb4d6495eb07c53c866e61dc111b5a9863d70f735b6644afcee5bb4c834e38e90e8f6916fbf39b07c420aedfed51a3fc4da1c5775ca45180335f2d8639c486a06f8eb25714073b17f54eeb40595d545454b49445c76390bedba3a3a7c31e35eb0aaf160bb3d8b938f9cd62b403fe1e0d053d6cc9ad09e6d9b64bcdda2aca62d741505f31a4e58767c7b86d3053715c91351e7af4b03bf243f6e8b9b51a8ff743768d878eea7e906d78107244e43b0221944fcaab3bcaa908dacae99d76e9cad363cefb48f9b12d2865aeef172f382d73ce0c30269bc382c86473fc864ae2c0e0d38355be4a0aa2073f5f938290e2bb1c35aae0a420be4812c2054941007134037618fde2e46cb10e1fda504c23bd73727839f409e80e4b59d04f781fda73824ac1cc213c643df44eb378e839ba23792207957b685c35340b9e361a80340bfa89fbd12ce8402d201fcd821796e98a83c8a638d637d92cd469c36e62c026c6298a53c9d92c5695aed132843345c2dbde3a75f8546210c285efbdc7b9005423548e3080cf1b4265b15fced7ec33a0c61823848f45185ff3c2196d7292d28a2b5b9292484125099531267c4d4a02c597c9af49499cb8543a6796d3f12461179292c02029c9d067af4948c8bc1214d80bc2061d65ecd357ef753cdaa9d2c6e03bd5a29d769a04fb7427cfeef3e4939438f11c245b3057af4941a4f136af494180f11d8e1106bd20e2091153d8ca6bd210639e791d689708e6ea9d776f9d10c95bb741446386c06c50d39825a5232d5796943c67c593d3f17c438f2e3c6612942953460b7f60f5cd0559e72956698d76ca4ed9283b65a3ac9475b2466bb44ce40cd0a36c48f04e45c2a6cd9ad122d74121e6cd03b4eff66fa2037de47e55a7c6f9348b0bd317314bc50a962c4c9e48795c98be888154ac60c9c2e48914c985e98b9813152b58b230792225c31c9b3333bb5df9b7053979f61dff89eba0b79492f3dea9712985acfe8580e6a3d76cde3b5c98be88592a56b064e12f4b983c91d264be087d41f245c9e3c2f4450ca462054b9618c612264fa45c64c2100a0349184a2417a62f624e54ac60c982f2b284c9132918192f425e9078519275e1d285a9cb972e62b4cca795b4d064333333ed331fb90ed2300965c0f09116252d4a5ab42865e12b7cc4476c05cc3655e91f2b0f14ff701de6b388b9df2d0425934f3af4cc31f8a4ef93ae9fc94ccacda4bfc032fbf9cc9f76d2951682cffc6933d29fe488cc484765fe8af0cfcc705271c8d3cb11d06852e33a239fbd203f6b7fd416a4fd3d4fedd0c89de9a70d8759d2313ebabc69467fdeed329281b1ce7b88613929e0f80bf29c3fcb122c4d0fc7e4601643e83eb63bc18e1fbce2ad391978f5727777975b23bfbebbcc33dd09fe713c3e76f8e0d5ae472a783708c2ece419eb641caf9ec65be01d1e9acfd336dbdcb45934dbcca6cdd2b656a50dba548cf28ee87088b99f79f732e7a1d646498e3776425429eb268a879e25c741637e157a0b7c3974082184101e81619d77714254e9710b66dd0863c9057b123694323434a4830e3af4d0430f489020a1d1276143d891b7b9ecb0c30e3c44f878881036ecc8438c909f60f0bd6667f8de763005b3083fd884cc5f1e23731912f1b47517274ff28ad12fae63324599f6a0bccb32ef321aef32cc6d9c667b9f6df2515bf6368e72cc668b8eb241d9d4d4d4cc39b56dc66936f9d9963d6a9bafa94ee6ec3328c730c7b42d3a86f5895bbf76acc36b834252dffe382a813db8edd1b9617408bbf93584dcce0d1f723773ccdd03ec3d9f8650da20a907a29753caae5d7292875a09cac100537af4ab25d6c913732ff07327fdc4c95dd7f68e2513ac01394d442f0b2184f02d61526633c6089b71a08ac8be4ed8673f272d04f234026944f2803d14b8c06838c88b5bc6c47c987cd90ce673fd18dfebf8de7b32b663efc177043e6882183509bc87106edcc1de412e083b76b46139beb7ba7a0eff71e4a8f05d2fc068de75f973ecdade0c263b3bc68c2396e0691e3311095585e73faa7f2e8db426810775f5fc2481f7523ec9c16702b8479efdedd041341145e00f6c2545e004cc00a2882459084d416600442c31264c109c8c49028c0c9ac6162d787085135c7860869671a2c8092114f1200a17325465509f5936a0aa1ab8118421149103297c5832c6c98c1978616a22093292c69001e88828863842891884499201096058c1410f5c04799981180418230544528c41920415404f888e80628a951438c1e58b06948ca4187cd1c3971e78589a61e98b279c20cad2258c3034a0ba011732c028ac818415445de800060fad1b4330d18352103c00518617193628537441d484161cdcb05d8abc60290b1914e080430facb006a8d0dd1c24a5c05701ec9005efbb0b51172274a5df5dbfbe4decb17577155252cab36738dbd3f72159ad5d47a6dccc78c5ef090f42cf7ea3b96cbb9183d9654966a75a2325b33c7b96b34c94f3ab1573d102ef6cd2d006f1d2b3ccc199b089d9e5e4ace0384b66ec3329e079b71ae2202bb3631e3a3d0ff50b621d21eec6c7f3ed4f7ff1a05f92a06ec7ec98260309ef20d46058a8caece45f55cc05c32b299b7521a1da9c83e79a82eb40e6d971f4e8c6c773f537836f3e78c5ae0b66881c177054579efdc66664c82d87c5d2ff75b7bb0a3a10b1a6a3b3959d738ef3deb614274a41419620f0d00fd160675947935060f4eccce472c5e95e94674fa13b875b61eb9e0d5de186a3ce4cd809d28c4eccee0dbda1ded93494c57e03110f45cfac02a31f93343b99bd28118b922544444818296162a36c231253e5ab647dbbf8dd4f7fbb90ec9b4b3d21cc8e8998a8777688b928cb6176efdf66b4051382f272babbfb12622ec7b302afd8230bae2db393ee9b24148505b3731daf39b5647639392b1cb940e32e84103ef9638415c0de59303183b88b56eab6094ab1b32d4dca01c9b37796e78ad3c1a3cec22cf62ba96930dff5c3ec78ccb37311af9895f08a994871f07bfeb6a959dc4acdf2617652e9d983e0d197143b27e1a0e5d9a702d8b37f00f61dce73cf9f4141413eb480be3bc78925ea322543377c412204e529125e11b102d8a30eb3cb7a87be7ca7629d1e7ad5e907b3bddb21fd5def23302128b06bef6d7d688778b53263761bd48d0449d20d529e7d855883eb6476a9d8c3153b10e3d9533a3cfbe90161761bb44d7486d084d2decd3fbcda5555fd3172641e5926a5e75d0821ecc7751b64e4c80fafd8999e99b689775c660ba6070ee220fea12929a8685b11caec5688cce330d939e8fdc695c09eb7d650c22c5d3299a89e04a630a8842df7643deb14d10c000000a314000020100c8744429160302854c67d14800c8ba0406e4e988ad3288b510c21638c21841820000002003233a45515386bdbd7e022303e8890492f8dd7465338dd39777c286a90474699043cd69a5b2da4addd02a635e971a33c292862fd4a2689d5f743290a89316def58e00fee32058058fe7b598e7119c68c175cc461d2e5591e308694b29ce59e4fb8d1f18bae33a1232d95bfae3c7433ac552d2a0e9ecc12b3c0dd03b00be3a0ce8293c8f1a19cd7a448953aeb0bf9786a2662dd30d4a287c875b26959a0d6b6a58ba3c5a9c6617021d9aa1a8c2361028de9cafb4b8126d184fb5d6606e85a7fca16ebb68dc57aeec05c7846ce7c70d9d0febc2acacc2749d1e1471cea42768ff5b5def5f65b093f4e0bf4aff6b91bd9f713ba9882157f726444682c1ca9d097bf8817042ea34dda67d2758cb15b502d5d0457d3d783cc31e48e8bd1b02274743159e2430592d40c6159beb1286de1379030c0ea3921a7d69ffce4914b0b09689c055b300aec3e214675b44d4bfd8c12152ff15090637b042750d16b6015d68c0f6447a7b38cac9e6f0966049d1af179596b94bcfc30dfdfdcf7953d82211ea693682e29297a37c3a22b68f7a647abc89515015dbc4a32dfcad66ede57767ba1001a9142a0c5c404a65630f57bb08bed56f77d4a08f13265fa3f5c8e8622877e0c110cf48ef91802cfc1b09bfc381ec1ebf9726ea7115643635ff7a8ec00c107e54cd5e906d2072836c502b791cfc72b670bc22fd28e2acbe993049c82f137aedda7f355f9ee748823e636df28c41ddbadf8d512cbd038c13bedace8fd132388a0ee9a04e055166d08f3f611dda1a11e0d3630ab00d12f7a78526e21a9cdef52e9ebb25d321230a57b26c44482f9012156e3d101faaf9415d4990b3f535e85a0d2ddc1b09f3c73024bbd69bc1a298fee7807fea9b8591c56d5628d98cf1114fdb644021a25838dbd66283a86e563931da0b97a47c2c8f99f5e4d198da3fcd90e1e34b21c7eff45ef92cccc946aa5913cffe1948e07e76c72f29a746bdbbb1f8759a86a312242f6d6825410e7082242064124331a1dc32d1061a1c2d436b4885a1b640f016eb04e0008378b445938a26f1c82520d9f46fd269277550e42fb27b627d5babeef2e9f724999c08cd23ea5d62b1497d2c3117759d6b55ed7dd1ea1a53189d929c6d28592b3478712e4527bc349b8005b138f6036e52338f6b42433b7ef8b479f8084414f8b3e32ae394c58fb9a680b986414e50d2e47342a55fd22162d003d892c258b854af31fe626815b622c2aff4b61f744b8bbda024430f663080ebf627ef36c9ae4f2274ae81e00c1c972b6b2bf8110691937a015cec2db140a981c16ab050145f07ff530e41ec162c53c1bc5c21a9427d14a9ccedc24aeee9c37a7efa0fc44d1dc181c96e4151c89407691e09608e8982f174711db1215eef07d532a8b0e589927220d2a0ae07fa604608b4c89d10490109bb0c2c8d6a9c77dd1a0a2faced0dc466d1862f7bb292a72a2f5fb05c5d07aca85cf5cf1c9ac42a1cc93dc17784879636bbcbb1a6a4dbc5f796d503ec6e532a511fc4f6b30be20e654701e8a0f875969f06877a1591222760ecc187c622f6987847617ec52cf66e2723a26191e84cacb2f640ee2e8d9d44789da0036c724bf3670fe743a66888288c8e3c3eaab66cb5e0904ac3b7de8a2d232ebea2a6221ccd5b18a30d3d2eabd3662e6adef3bfafb844cae7cb35e53bb626affc5e03e69e9adfc698447ef8603a79189e3eda4956465c6ffa176ac8c026ccffb4f467e530d70ca86cfb8caa091c2dfae5a655a55f9a971b43596c0a4cad004342543953d5cdac8792afb16a599440cdbf2a805e4c9922c4f062e9c360d36f5066f9d725eecfc5354f5949cea96087079da12a8339008e72b0871268345ea4715280f16936f5554d5a2425916f85075c1788c30b2df8eb41a72b649d92d13dc5c020c45781eef8dfdc928525791ab1bf84c050e5f064abb9ed9d9e5cc0c71b8eb21d4dcfbddb31c5fd76caaf5c4efb4370839d5400f87c7b40129fa65ae4d7a895fc6e265672d06615fbe2d3aa833e95718e98c09d57be742b78984817e902eb791931dd6d2fe1adb8c225d4a524196f9de776c125b286966e58db429ffc2e841f70725ebef85ac6e4f16b28c3bbcd2a1751fbc443902a8db2d98d3ba1d2bb76ecc3cb1813ed57a33d0ae2666c4e05ff28b28516f54714e439c4eb0de0c6fd155c6b3821273baed633c7a2a728ab94e579a590492a37464241a04719565d87c85f6b81433a72b507f42fb95a85fc717e607afae301249a9afdeee7bfcbd4acc0579c66e60dd3cccb1ebe80e77c3630d5b2400eb18569da7bdd8c32a21dc0cf0230021a8f81b7da3f075aa498ab448581386fcf9f13761ed9c620eafa790304aca23888a05ac7dceefb3984aa5e8a459663fa1fe3e5f5fb34116350e656cca7ee5172d0a27f1bfa530de3ee690125e63f3202db2093a97104ad6a689306c217a28a3c85757f68f8c516c562dc2d2c291e706121c8ca3f16dc1b288b7def84e5e7e99ebe68589a922070f0a02772ad9f4f6fd58649e0602e859231bc653605c5a9b3584156a349be3a8499cef5b5d82c390ce859fab5e86f2dfe31e9f88568fcfbee5d0d375162aa9afea48e329ba5eaa6e5731d9cef6d68ad86b5186a15c5e5f6329652f1056d7932ef82ad668c82738d964378e6e1d95ccdc25610eef1d7714799e91712974f6d00d06d79d4966f8253bf71b8d3378335707f6d793f42f86a975bc19622af62e7d06d961c08f4a85cc30a6f56fcd6ca44a9281e36809a2e20235a7bc486035dfa1c464d08ee7183266f29fc5dfdf758c9f8b240b2934a2b3aab782bd16ffc6570b5755ce9a64d8f4064f4927fa3696ae6be51d988f70d725a38ef696cde40147e18aad6b015fac82f80de3687ba13b87460a13107b2d4bb7d7ed8ca3cbab52b22fd7a46574cb408b2be04ded44eaf53cc4468c9923a495b284291af4639f1c4aa156ed29e083cfca36eecae130a1b310000e57261d4ec6c7a24da2018a2e75d379473cfe941e2fcaa450d0584493b46c581d19a0416af979ffdea31a587252e777c1ea05c70493505568b27cdf96c3e5fc11c0cdb3cac002d65529335cf2cc376b6a2ce3bab418ecb392c1e67e1c2694210db2a20936986efc1a9455c2c514fa668eb44b2499d1b898e074f223c92e199b07e918eb60050ad9d18896451fbe2fb9e66ed302de0863b13911430d443a2018b0f779678a2d3f7e474f5d2fbb8a9361bbb72da648c2aa2f8f8ef84d13ac2dee718549cd1d66f8e340f099a8f115adf07100ab8b791aa50c28103b7e8864aa0293aa6d435ff0873ed7a8abfef81c4dac1a903b8c380e90d0c70d5d22ca2eef9bce21027b73b8c0dc707dd532cc9886018d7dd1b89d819df90b2ed99b53e61541fba1659adaba5d11abe4203610d7fa3d2515399849866c4198ac5c30755f7cb730b7d890d58e1c2aa1c6e00b59955d4ab23acf6256ab67e168318ca419dac6aec51cfbc0b609b02cec6c8d6ee49b4636a173257890ea8d559ac5001a23c09e76eb7bed42f7007a9cf4072c2343cf9463c7023069ddacd0110c59ffcf1c4498a4dc4ac96c2c9961f274fca34f77e45f1c2dfff3ecfe0c2f7562a0ab9231071fd07e5b31bc0393c47fe27622737502a9a2180b64abdd49f2aeb3d0ae75c791b72367a62aa90864a74aa2496ed6c2fd995197a774b8b701b9600f71a99ef00c02174fdf3690f01ff7a13b5f9a47e743de20e2cbb65334734bba215803cdf8e6d301bb6d08c4992e32976882fedaad383ee978778ff0689c2b3a8434479c8256e16e38ad7438770060e75f587dcf787ca21fbbe0a0fc1c00b8cb1303843c3102b01a516c77fcc3f247000440b61f7ca4441640ae6b57dfd48361f19a72039f5914cf13ddbc60dd46fdfa855e91da4b791a2ce8e5873dff361e5c3086c96166d4ee4577f32200efbf55b3db2f7a6502b62eda2ce33770855fece094f03a36009fb088b1e4eedbb438a1652ef5c5a3b486840c4bffdd1ebac362fe628db181a5bf767f4a11210da0fb4f6cdf835f399f14804e41febac07e5008444d2f28e0c4aecf7e6bbc41741b4259b8766f42e8feb186b1f39b8f278aabce3604fe4be18bacf63279709fec83aadad45d4008260ba194e987b7b18daa983e8180c06e4626f6a36422563a8d14f30ea89fc3865b30ca69906a1682c3bb62354471ee2d0db8d51a76d62eec87331cd83cdaacf24a33fe1202d4db6c48e92ef6be01cce34ab3ca217641f935b34e476a82a036f5b74bf43e47ccef9e895a8b7fb45a695ae5da6908d3c4cff292910c189b7d044bc6f3e7de0ab851b7c0fc836b785e699bf5c2fa1c2d6247477ceef1deab8ebff9d01240cc3b2cd7df499d7bc277d7f3fd302518cb996ca1c2704f9ec2a98b5768fe68ae7f45af268ad07a5d4d31495e421eb5d3248e52f5c9e8a8e58a57c973de46fe311f2bdf4b89f60f8f1e14d176873e4b216b0e8fdebee35d1af4dbeb3c55d39adf4c9dd1433bc4680e4d5d626819b6c2133b93d102065d389c71b756be21f260c2fd9c9d6375c8ca1cc36a1924421bebddf2d46d4cbd5c686e4c28587d72ea666b7083f3dd350f51c20c937652635926c4fd5eda10008e061cafdf850e2fa2d3dc6f16644eb28d62df312b083ed30be0da1f819d499861516ddf38de11459160015035d33d465c0865d4109088addfdc3038da414fdd1802c34005e4e671d2ed23b90a42c949b93389ed8ecf16a48fc42d30371329599febec15374d76f298cf600d5ba3acc9a52aef5599fa2906b5e4fc5e624ca4a951ef4b33e683756575d05d143a4f6d888182b222efc0dcfc76cb31a588bab4b4239b049028b27057c9878b8bc16361268479036a91081503312eda4b95614b1066e8ae42c91eb4c79139de33999f0e00cfedfab36e384326416bead6c6a37ec00dc1680342b792ef47f993e8251d2bf727e5c351dbed3fb9ed587476bb1675da2ab9c0743c690f1f602613b2699ded61b8645712db4f7b02a09f6fd7b8f1452f2468de03a46773469e7d2c6e3d0320ae8381e084fc76236d926d8f0d80e632d1f7c0d54eb9380c05521ce188c5a93c85061412ee74c81bb785a7f987b09757405b031313bc64a06eac8424c46ec3d1d0e11c6bb90d42eb3c5459b909ea36011b78b2b008822fb0999d375f0a87b682282ddfb96e2db53f2010b9b5a245d7f43f4be1cff60e0d9848d6a9b5ff6555ae55517454a911d6e1cc0bebc9ebea267c8723310b1fca9a7ef0e31a1597c661a2d25c2f7ea3c4ab32aeb57c0e39c60c1c2483a73a7e09a878887d8adc925002a35518c68db33ea8cfea1d715d40748b8ca41209e8191684c338f7a5403b88d5502076d691307f82910a666a5055936c81c2f74d92f1b4e60f3951fe5a2f2c043281c6202ec8352f238d5162274ff38e63e12a818cd5fd3a909cabc2c6978f966eb885ae435166c84261f44fbecb83eedb2b3ec02a19dc766bc9a726cb77a859b36d11ecc2162ca184f4eadfe6d18570451ef601f6ccd852f833657f458700d683dbb4594167127c75f7eda948bea78e346750b17f64f2e378ff6114da845f181ec8af084ab3fbdea4879d5bc90f40dd0b4f416ac071cbf0c98b8465dfe07958d5627e46c7c7145124b7e74da47447403dca68e9190838599a3046027ee5b50af22e9ce6427b7905a49b640f87021fd5adcb42babafd44f6282de6385fc6bb0333e592da11056bfbc44e58e192d40b132f4ced019f216181cb0ac897e35f55483a5af4d2e751882a57012447b217c6bd1cba043cb78ec29372706bde851bc5e0bdc940d7fd7014ac22adad8be15da27c9c7a5cc7da1c710512060aa9fec66ce63a2ad6d2b6895bb1ef1ac075539930feaa6f748aa7cee7513f32b434dae332b6b6868e64f1410b2630586e65e909e141089f0e9e79730e85f6581126c20de961379efbedcefc1cd7290eeb16c979262b56b8632215d7413010d0e700900677fa4d8889d60fe2c6bb227e1e9a0ea4eb31c29ff4d2861202434108295f075f0849fe0474548df6680b215469b63a4c58ec4d0327dc4f08e17bc97a4554949a8913decc8bfafdabcc990ee9325586c3e19b419c388dba9e97c811c384a794566005807398717259bc17c86616d2291c1f56f45099ebb46266acd72c9733dc3e195a3986d9631b89f2844bcd30f184182f98d5b77baa1c64843ff308b57642f28656e535b1869529684ef7027944bcd0183307130b1ca0b3568ada3fe46182fb702451893889fc7a46dcef212375d6b20c1e91bb4e14cef7c6c7d331b4cf2674fe452a7457d7d19c2305c25cc706e2d2c3558d42fac7f4a97238944c447591fb0324223fcb0a0d40ea4ca56eb27370e32507a7ba8cd2dec4faa067e4279e9215a0469ef05f1b0ece2d4af4fbc249e8d9fd64f08d18a25f94dc91d70233bcb5ab8932b35fe1060e9d7180df8cdfd84c40430b3f69875a25aa26269fd0b61296c4e825221814411035579e9cd78f3725e31a35ffb600eac38d7f2a2b4f1e05794e2860981a5c00a8d8922e92244598dd99144c2e373ce0273bdf1e24a0ec6a1ae4dd1271e24d87bbffcd3aad29bc0c7c252ee1e20a73a50c86fec87d8f280664f518a62e31980962a383ccc8541d631ad0024ade40eec455630d3fafd9712adc03acc15d6938df72a9f956278cdd8f30f25ebc748f16c83c53201fa1bc8c0d55ba2e6b35a930c34cdab8adcdfc9bc037d96b441da0d738f144b219fe9e2ce7e7bc4e057431d0d1f39196f14c7b0980042e4241323e3e64f108a3f27bb2428263a03f7603a56917213a85c58056f5ba5cf7273b3f6920a395617573014e7c053a61471594914b971943fb67fe8a47f81bd43096e6d4c1a49f7d6fd625165afaba67bb1b0abd02ceda86c3499f0db5d83a9c4e79cd1dd7794691e6b10ccbbe2bcfc326adc9d23205a0e936b77497111f119cc602c21a12656779f196a6c00ff4fe4965f97925ebe76f72a7fa332e28ac6b4bfd09c8247a5e6584a6ffdf3ef0b05659b61aae6dad8b87c35d409b1468c03fc9adc2b8890628d6e19ea0296af24b7a5992dd4b1766de847e2b0c87156f5dc44301889ecd7e530acbf5d9140d7f778c675a421f54771db2b21037b04de9ec8312fa1c743ae2a417802c55834d0080dcd70a3ecd45ddcb77dc8e90b027117315599d00984293b32b58d9b84e438f313f96710dff1e70da3e8020899d7e347d3fa2b5967b5051c69a678674dd2c2f0d0d58a54c5d4c9f690a8b7504ba0d44a7ca4ae8a411bc49561308ab71775c2ff00e17355d28f8284699a75ed4429614cb53e20c11bb8a9d5e2d8c7032fbb4de0ab923becb44a487db99e4bd1dbb89d52669be9057748c680033daeee4c3c113130a8588b509ffa5cd77aa3d9779432f1e9e34c1503208faa118f4665aea8b25521be89922989f41b13f8b50621617382b6f1983e3d0943d315ae0be13c567b06a00461cd20b6f6100275b71d2ab0f0b37bd936dd2145875b4e62b31aa582eea2eacf22e4b1aa7ac7a1746690626ec5b97970a53c253e0970a08ed7fd1eb7b6694423442c8c2b3f64965a99d3264ce90a64cbd4a7a55e326cda21b167c7276cb090242eee7c7da6ed01dee9c86493f4649a68c0fec64bfdebd28bb810b203115c13b08951574d5876a0174d9b6a77260c5d767849fad2b1df459612fc0fcb13a2fb203c3efa565cc856415d7c0b2baa6ace4bc23c82b733a379e52444e5e749712645790454603b4e94e32cdc711321138bd1e20a358b7efff266ac7c19ef4914225cea8b5f98519c9487fb38095dc55d071c94ed41db20c6892c790ee1d5cd552f31f144a11526c293b6bccc5ceb90d160da86e96b2388cf442c8daf577c6085fdb08d819d61a74c44dfc17af633d6baa627756d8f7c94ec6d4e39099e95fcdf68302ef2e65e14a2437d375ab4c6279677fd6ca7dd2d1ddd1d9599659131cc0a9ca68d49909bba1e3898c4934a9d620c4a519b5769d1937b58a5eddd66f745de2da1d72d5b41ed44aee62fe64394aef5cca370cae8d5a4dac437937f51109d3521190f78e47116e85f3cc0ccb5436416140a7d7d18c0c24363e4b5ee449fecefdc34279ad87afc8442768fbb6c8e9e47eddfa2387653b44f04489c11165d3ad67bfd22f881ef39ff0566316e27deb09415426034c38d1466814734784a3ac73f9bbfb46bd10acfd0b5ae598198f55702f7d1586c910283bafe369216302abc38242fd4abf36ec352f8642315059404f19976008571b4940725e65c09b74aaf7c9c6300bb5aff74a85b47b2d7f81bf67dad69aa28b72a1316c833ddd446d06d233945b60be453b2d5aa7880040e88a503ba1cc563c1b1e07a6fe8e6098e7e92c0fcd4d87f8a4e3e9ca7ade28d4416582efaa9dfc4e11d1e91363ebf854c11b8d21a4fda5ed4a06636773ff294524e55d1101e550438595284783f4e457a04644a136d5182026f836221120372eca289f3a70e6b3d21c10564e6c4171606c7c73f151d562f12223e03f940f6955e1d182fae7afcd28485d8e16ebd029b6106639517a9f5433674ba532bd4bc210ae74336762934c573b1168eb55b4ea9007a8bb4c4c7b0df0fa422a7dc4a70d9c6604cef5bc4d120c05e160f88f61aa12e38f86e655f877f4375e51d74f4ae264c9cd98821115bde7aa8162ef531915c2589e9751dc7dffb3d55bdfaebf2ff7ff4c1f17d586939cced7bf6c6c57cf1c61d1aeb5b880e8c3c5997a858296758db42c3d7662647e75ff2b00c349cb52a45036db50286577a65200a98817f3ef30c5480b3478059e8d9d653d9048cb4a3efab22e543e6dd5def362305f4af855712ab90e60d775d712320bd09711bdc1c0e91a704ba7f8b489e9396741696e8e077b4bc1057e5be8fe27eec5c75b62d07e2ba547864f8c73737d8f9e2fe37c0566b0bd6dd6ad94e5cbc28d66125a236f5b8c3ce6f80704f5db297700a45284114223f89e8d0b4556047b4b563f3ea6971d552e67f0542f3bf4432a820688acd136cc74fdde006c7f9587192834f616cff3728d8e65a1c9e4fe3d6d907b62a37ecb31337f9cd6008da5a40232fadb5d9ed2ce986e5f85c7580933a64c7253417fff7d124396295950d063e9d648c0fc7a9aad455100cd5f0db658800c9a1e1105c897fddb151c8efd290da98cb1c7f740b173d670d608118fe46981e97e80588b75e5608cf279e8f2b8081c6c400112ffc5b436412ebec6a64e6149406bbc81161e3fb92fce0c66d003fa9e55d84147a42da6a04fdf438a10228740769f48463e5b4ba1dbd14c5b0e719ccb0b1fd5cbf8adc71f72905e357c4913240b67e3465576ab5ecb644517157bc358166bc1bca7ff4d4f65cffc3086ff6be36d858485fa1e174954e503a964a750cdeda906c7d06f9de9ce75d9a03c6bef454d9f70fead4643150c5798c5103c1ca95be9ff51d1556e90f6cba5f9c203436d8f11cb92bd900dc759c6f445f52fc02dad4c45b1da7a6d98ead76e8079034a0df96f38ef708778c8ed9cc5e631e7e0eb3886b9cafeb4aa8226d33ad881c4adfcd222fad851043c041166deea5d489b297f1237f771e48862c8527be04d220369e67df0605f469cc9c264b3073b2b9f9d7ca63a587f2ca98ed942e3565813a07ec6ef27ec43cbab662f0f2784e154ecaa01ee2bdd75f5667522ed5ed45de555780508530f116b27c055e12a41f4206ee09fa96785171095f5242b1f7f3f96296963ea21dd94b4f222d3de588011772ba0d7f1de3de0e34ed2d3557c84786982290cf9725f6bb72bc9ef6bd134d968a7d32ea08cc5131f0579c43d4db6ac18c46eede6a438ce83ddf5ce7f39ef175f22c00624f2fe3f6a1c6b37d7c7ea4ae244ca3207aecc4eae9fab534785c7096bd062a422b5a466a835d1d6eec4d6968302d02807abda1f7c2e5ac20946e94fb33ad43c99d61040b56632bb1e97d36363c3f21dd852a876e182db97d13593879e08cc71efe3a86345ff84643e0b7faa056077adb67a7e1543282e0f16c5aa581780924b20d5ddcef0810aaa6080525124112b8415ad9ded6a49913e4c02285363f807ac865c82203d34d897ce29d272cd046169f0866a50040f463a56ba816f59d1b3758591e6c4d25a64224ea62bba3fa68de2da5b8011f297fed0329c457178ed074813f5ffc3a0850431a444939524c7a09e15aad9bfdadb6631288cbe5dded0960244a4d252963cbb221a473584a6d65c709da123821988d278d9dcceb3f6413930043e3c93f419e2c708977c6990046ca3e3592c4390d4c58ff6aa43fddeb74cc52d71f171c6da0b9a3857958281b4a75076146ed676241582ff367e408de3ab4a45ee69e7bd2fe1c9bf5060f0e7e198cc5bd6cce21ea65fbc7f0d5d03a8ca132c477bd490bc3614a6007697f1d73103b013756cf7f2265c4c4c750b266077a3ce03609049dd05068101169036359407e11ab2dc6ab7d01fe3ced5d901eaae8279e200ead9b4d5038c94f304610961f1fb8648cc66429d4dbfa20a8d2f01d067469e8a5376c1fb59cc3597227c87d2fbb47f068906d812ce5f116d0178105e07fb2f34b5a38700bb19bf741525443b2cec7850711aebf21e8ad7622f8618b1e83c84bc1890bb6408fcf043b44d1c8ed7081339bbfdd558d06b35fb21932003c69e83469ab24cdc4976d4a7ce25adc1648a7b93995063d43c5d082cfff90fad850510dccecfc7d79daae3249a6d694aee499418545675a6f73dd90f0a6fc81ce5ef4cbb89a12b2cc19ef471352780154b27a4893da59f053eb6d4414f331535449cdb6f72751d66828c9adfed1c0cb39cae5aef8aa702de2e83ed4187153d33d755cf518dc6f9d7f4b93f4a6bf0f7919770a131598b2a60515f021411e48ef2414cfc248759a95672a7d19e460a3920121d5575d240344c0c0dcf17d3259892bdb9d45053a23dc040010a2836fc55fd063949df35ae64053ff6b597af4247b46311f2170d1b44757cb0c13f7cac7284ae11ad6d1a6fb54d639525aaba3effa79422e00ba92266d08d95caa7c17b5eea4a21b55d795f8e227a80919e0eec9d1c09e51752929577cca9dfe85a7f156d6f264a571d9747e9e4ca6cdb0cba73c3fb9fa4d9542a499fe89e179512deb2ba1e062426a312bb4f288fc8a1581254ed0a69a094d1e7fee11d45a9f5144ecbc4c6aca1270e31b51298726248ed76d203bd40d6da5aa07b92ac83c23fd281a384bf780bf9bd894a76179db8952507c1e72eca07cce3b57a11411114b739dcc8c23fd4a32451890b9175684764b12313d95fd2f8d08a8c16b3729020ba84fac87ea4989e4226d8c378e18f789d4c50889e54a68351d54583a965e1029e2b84a7f41da97d7ab036f7393ec86309842c58de504de6e461b48a18569115327fe1e4ca1ca2f1528d8978e5084c213ffafc0b34002825c56b45ad74b684ed4f5918015aa29516e8b2f94add3652ab56380a777ba9bb7649663395538152f24d6ae349b89260bb9bc6e65bf38fcd270ba3a868d5554390b334920367a7a022a0ac9e49669e71660a4605ff74ed2e0ce6a31e2754ca2cd36030c8dc90333413021cd145508440e97108f0c266d32b1e67e56aca8db5e3abb1f28549ce04b1a54197a13be329f72aa978d184a6b4ac55ce44afa702d2138b4ea642cdce951656c0c12a3b027ac197bad6f3981684e0807fcce98d21dddb73cb53b76b72db17a3696a04221756ec7df675adf4eaab4753b93c09f19c5f045b6153a8b4f75c35551528605012b3f0e4903360102cd39a6487d70d46a5cb502697bff4b6544b16b321c572e7871aba395549883cebdc39b285360b4e9088fe89fcd434c823b8f806b79fb8c79fb6185b1f06b15101fc6c9acff875b52ace784fcc41cc68706478410afde420e673c5d5dae30d3324ce62e36321852f61bb0ddfabd3a716f36a0274eaa50d2d4e341b73aee2ff0ed59ede17e11f535043d037b3a269707d8b9e425e44e81da0099fa58f6bc616b50eec47fdfc375c5aed5c981a5ff23d036613381199c6c3612f2153ba64b42c5ede7ca70e4d66d36d3de91d420d6ecc51d7905349ede560249284839c795494cc98b445a7b7f79bd069a49cbaa65a7b79f98ed10779b810d6e4ca1d462f010065d7198dff22e64f2f892675521a7f07d97f0e8d552e59b76e3fbc7d852462ed73e8dfcebae7494316532a6be8b67cb72f45e7579c6311bcd7cb049406946fc394cb8009ff9d2a44df1185956551b9beba5e672678b26f7d026fc6cb113bfed602df6b733160e1197744687a16fff564538e15fc9dae205e1dd686f7ed49fbf3ab50e01e135c9450c160470ba3adaef74a73aa6022f437611734a0f2c1201bd6404d8b61218ad2e72e2977081dd61132a7c38b527abdf145f64592d24b9f0c3f979ca4295596a8b8ad8290b8f3f08d738e2bd6e7ec79fba64e56607dbb72c30986af7026e86ed2e87cc7edd81550dc29812bdc9e521c1cbd0656523b1ab155132d7c0204da278f4d4bf029b89f2c8cdb0da9755ec475aa5ccead3eaeff3e2b758a857a0abaee84b76a5f383105527405c57169f125bb3f5f73cab59140c42bad766b379715c42cb712f6b98f313dce6dcea4dbfa11abbf204dd2811b79ef95440af3187cf7434b5e0056720436f30f4464996eb097317a851433df63c898f97b31a4147e173b8c4ca864748fdca38ee297fd327627aed8b7fb251b0cfe52d39349cd8f3e6e75cd41fbaa7b2621ceaaa947410cbd2dc8842e8977901309db0fca79f71089f9315dea8c6fb9a43a5500b0a88beb59cc9800390d04266c4068a295eef85e0b8ff58f1ba42f05d0fc123f4d6c463d08e044244ce3f561e536106ccbf758d94bc2fa4018f1d9afa1fc5f0330231e96657bdc8b27aad44088190faa4194085d4a383db79e956130f215cd244a42e70d667271e19a336853176be2adfc3e39081e5977ee6f08e6d1d988be8f2077f657e36ecd2d4941f8054146c2a9bd6bc3948bbc590f1a3b0315bf455467b10f5d9ef22ea65115835135d98ac718fd2c83a6c3ecad6ad4721c3c91ea17313b6e444ae1fb7e897219395936237b318bb498cbfe75be539a453adc517d2f23ed0d66e450c41ca32f3312d46c236dc62c535dea64eb7f84bcae909c5d88d7a9e6bb454aed9a537e7757d09e71d2bb0a95b7beda3f4c2e131b0441cdf7d41632abd67d54afc26e69da03262eb8e3f861aa5b5f7f62a24ca7cfe8743455fe998202efc1d0f898fe66ff10242282e9a2ae846c494e0a3d99a5db6d988283d6c6c6315e11e6e85d13f6ac2d9fc6baaf22b6c01348fc2e52d163d3cc2c14e1bff20b321f5d94553c4a19817f544ad9a6146feac29af2c466223619a898fa4fd4ae3988fc612dff13e5a1c8086a9d7139b844ee094b428ef9f01831b8183ad9a9f440f3d345eadfdce474e14f537ec0c68ce1d6b1a067158bf966aa2f15f4f6465a957b23d6d70a7a06c86c7518962eacd19d4f82a99947d925e6837bc5ec22260b7accd4259ab1eb8bf488f3a9f7c11a8d39e934e61084a705c25620f9fb38f8d82967fe818cb0bde97cf63321c4070d88a2d66e32cbe0b82b3aa84ebc273e36b4fadbf826a99c0d730eac001efd4dfccff21f8096d325780a3e5d9d7bb58edd86a9adb904150f4aa3c7550872ab27a5d54d5ef6473e80ccaf026495b77c21e1f31c5e125247bfd63c3d8e49c4c706883ffeb69a1837ed08e8c8282bf412f64fa9bd16178010f06e4c2ab898835f96461094aa6fa9bbe71655db140f9326ebb7f5138df5c04989892965dd3a680f57013702dc1c7c4f155703fcb385189584187e94d27cfb4d1d362363387a72678f0d552afd7f476f0a1ef6d877a8056c5013f1a058307a39bfe0ccee4d3a5b965f2e54185275a4d87996680545de9e26ab8fb2fa4136d0a29bb3b8503b0783bf4283a46063bc5eeb000d8b2d1e064b2ac0551f49acf953b902de97fb77ad41861a198c86b702385b51c41d389d215df390b8e06f90846a8385f944960397ed92b8ae7a71dd3ec9628223adc192ca773e77a0e0604920322b7d5590dcc5cc3d9c82fdcaa4c9153a0e78164230544c3e04b84676c03908d2830aac886631ba9fd2cc82144d2bccfa29f969938b39b57c4abd6c368a97eaa50894338ccb266fb39e8d4e69a835a737b0c48cc95d9ac066113dc3b8b12757799ead896d7e79c435ea42e74ff9008195ffb8ee777b4cd5fdb0af37274df841010d8e2d70c8a485ed3ed5c3dbe328f7e1e935a51879c08a4962ff2e9423be38267aa94ceebe9c7a0bff1ff6c10456fded4d805d7f55a1a4f15f751511115bfc0546506c65936bb334f88ba37a0692fc49e80256872857365ffb4051b710da83d555823ab42bbf5209c74d6bdbcdfade98abafd2daa9fb3a704351c690396095cd4bca3238129c5d57bbdc236d20c5cb60067e2f1313261bd7981e82bca34a153afe483b07934d56c2ad966881ffe53a4a25a06c0c552289184072ae3d76d5629c75295d3b79d3940fd995da744b746438c4c8d9272a94bad23de136d4f88bac0e445c37485e4691d09eb0aded29c0798a0f099a03bdc1bde2739869e26774e923f8533e232b5cda52aa072b1777a5870543a450833cc74fdee0c12d1f0604c267db6d1f81901a68beb6cb53ce812be5659f5f60dfbe3e0d0db3e8a79a498ed51bd7e6e230a5de3fd71346d4eefabc6805e88511d246e539478fd9a1dcf9f1d01fc0066c3ac1173383bc0661299d643cd309250b565fab29c70b16797d5fd07acc4648d6593711abca7b4942d2cba23c806b655cab6f5971e499a317ceb2dec0969afc9b2208e275e0d751bea9b03dbbd2cd0b1a486b6ea5d0ce79ade13340cf8c08f715f0eabb0219c8c00c7c0400157d2a0e586d9537b01e42fc81d622419da1730377c8dd3aed7a1c55989a97108d444bd1eaf1d55d6d7e9dcf7e84153385574b34bdb14f0ef2956b188f1ce519db8106460a5701ac4ae5cdd869798daf60658dbee306a6ed9e1e6cf0623b73c8fcc44fe93a5406823d8e4c6e8388d4ca507d308b7c4051c67a0b66ec9c018d978782c8c8bb03bea8c37bfad71b43eee6f46e2ff199a6a9f59d7f6cc67538da4ce54efd9f842f581028a32aca0d5a92069876aa547987fc969205ebcb2fd6465e398bafd6f97001ea0e932339f9685bd8b6ea06b9d2b4070a8bd1eed00fa37e878a90b1b011df73646e6ac804c1472212349e5fab4baa27067db10108f399f14d7bfc1a5b78fbf21ca7326e8cb8262f96fd66247cd1dc8d2187fddff347a9343b48ecb816daa1efc1082bd72cf0b612a86bf3390e7002e99fdb5ca90ea35478643dff09428899b36a709b44bfaf44254035e5e8088159ccb203342c1a5072b3e44b7619e46902d6ada5e35dc1a9c164167cb4510398f0d21de0e42d6c0dedc42e31165fa6dfffaaab04c19aeaad16ca267aed6c31bd69a9bf0e3b9fedef832cf045fcdb0e574297defa156a6d93fb170f1c42a0fcb12ac157b62770a457b5ab7c876371192c41e06a6115a9fe01364f4a32123e4f4c300bc3a99aaf90251995b8ea3f1e98df9b989f2ae708f404964991ead62596795d0cde8f793e84d1d1867018ae537dfe23f7c7440af91a04d482ba78b0ce35d4c7802d4a5c55b7ea71289340d37074be72b7cc6b6c38bc3cd82c5230593162237345ac1688943e9e74b7e58ce374bdcd87c9df901ead87030e1cd7ce24d5f42e6a8fb8d8c2b8fabdd767110c92ff4e6c73bb7062264fcdd114666c45b3a94f3ff5541384c82a1a5a183f062bc8cd4b03b1d4393ee3c184d5c71c79663db9ac1f96eb852ab4ca02b3450395d5a3142b21e3dc1a105901c29d4159a2e02e28e926956d09af58fe48b40e47950edb1cd4d622157216702de537a4e85767bdd188789d592ffc9e5b8df2799af981c51b7de02b5f7c44195edb52aa723523daa137b43b36a297c5045bad0a7339a7b6201d2fbee0d8d36af303017659114c516393c872386158d94c9aa95092cfcb52540a9fee7862a3edda271eb101f58db5f8680c44e763409680ece9d745ee631deef0cc87b772aadd4219036cd7cb76539215d3d102feced002e1ad52fa8eeb19668450f4bc778f66344258e983d6a774c8513faadae61ced474618f69c2469035766ffafc61814ea80c7abab622d943fa9e0df6f5f413540665d37b463241730fb211084eda7c58e41de45e1658fa017a509b73dc780d242ac523375b39a74e16e37036e85008b85330028994637066b3a176d86391d972320eb841b796d8e7473e43178b5c78b6c8668b306e6f54d25f77a692c3525b16db8e053d8c72b1368896e8e6eb125a9e47fd17ffe60fd2772b83a6eb1f99f055ef14e190c7f5797cc5500fb686187495ad7d5233ae5d9beb38d3d27373ec6ce2b3bb231ce590931b352e8e2f10155a1d236fc0c04fb83d5905319dcb31c85056d02313785325b7957f08e7299a47aca286862e7d8dc5de9c4846dc79e7c001c377850198d57b7e60e44401d0671d385b642458ff9f4964a47fbabf6e4603650005e6bec9cdb0908123a760288d0102dc26c8effa0c7bc8c4caeb1595aa50f0be71771afb2678cf3de40e4b05e1345764551136b004bcd60a6b4992945ed0737d9444f9e11966f800a459b8a643b49dcc78679858ef7fd38ceebd5ae97e402c79cd707f7c1ea49e0c9a7c25c0150a0b0558fab25801249aeffc56e6fc451da6e5979763398493cea8c5b9b409503612e061ff37a9482012fe76a3a99ba81bf0084463ff264aafaa16d320a4c3c786d198f20e26b77eaa92cbdcdbbd162693d993ba276376fb8bc11818ce188adb6bdebc6ef4467040d386b8904a95db358cdd4894da7f182a661e2e71098ea4089e4cfb93364ce2bbe0b32ce9c8f5e056d51e0d9aba0e6f1f451e6d92bde8955599357e87462bd9b745b1f90aefcf8b2b03c2e59baabfa02b1567e5837a330ab64b517feae80e5ddc66301b19386b51983546276e4496fccf7431cdf56bebd70740cb12b0d689e581af71af56689c031707f4a25f45a8098b7952b81a8a15c445e5d34d29a91b1016b70134dc885817aff7182feebb308a8bc4f6dc73027ca794380236cbc565f3ac007591607a106153a3d091d9929b667c2bf2426f8efe8d0d1057c061f1fba85b7214b4596509885931e14fe6ad4794b3e3b8756401eb68c0c24d62479bd48e287c6ad2ec1e7b15c142d30cc7dc9137b74db7de0741c0916fb987acae665382c640facc4e8555025c428fe9005c6d4140e341e2f8c03ce1bce0b0c3010be1806a6848724a64d2c725880610a438a506f4e402777498e659bf82d258ebb398b4014829a69f4fcf00ea91bd986400708680dfda84d300b463b202c544bca365ae65c4ee5e0a3663a7c8336dd9d938826963b435837cc07b70253ba9849623ca12295b9c48c4846b1752fde896fa106e250091a60d96e3224a6f117c6570517f8d9cff57eb787b2d80d02bb2b02fc5fb89c045630a14e2adba4c19603208a9cc5fbd55bc2a3bd4e0d220ae7d0957dc2d3cb9f81007f4f6c05fc1642c6318fbb2c6ec32f63d254077c90c787a166122e858005bb023a0c0c49738d466b60c7f750378518d54ac31868f5c53f608e079546ee7847ab15769ead850475ee3de71f6e11110578d7f2086f443842d905f08da2ed9aea466966b81e620d9e59b0e935656b4797e177c9f3112c9733acb71ce63b493281565aa24c4770bc35d469be053edebe5082dbcb251ecb23a4a5aa667fd0edabe8e5ac1bd1a9d6ef2173c60cda5bb21ef0c4cac31c210531e9ba65da39e27177657a26c16ee133a2778b8146935524f65fd1b8e3f81032c27ee5b34fc18f218a96af5c955373063f0b851e52f346d82c5ef4356968f507da8d2c0e7a7b18ce7bbb9f592543266f4866f0bff0dea5009d7122b57c75ce380bba0aa533193eae66113dc82722fe6027e950c800e87ec29ccda4f8fa619cab9f5beebfcb2259214d31a937c7f8a4551f3499729725ba02664bc36f77a09d8b4cba41c12d3d78b83ca155a373f5a3e311eaef4f80c1aa4b538c3fd75f2de9529cd2fb87c64c78cf2f336ada7bc8b0a2fe20df71d9afe59ccef3e34ecda5be51f34409680b3e3568bc471278abf60abb06678591d50261166c3575bbf80d5a430d8b3bf57c44603bb1615ec1ff08c9fd44ee404851e203e91fe246eb103a081558fcba690184a7a49282bea1ce7eead2cc1c08c6f1a85ff7a78b30c190b854ff931b0dff1d197f413fc034a8dd55973b13a806580402afee5977cbd45f50ccd47f7b940ebc1d139af6b60b80a308c98baa57c43dcce809a48cf294ab520887659d7464f774a2a2633d3a9b34742c2dc3773469d228f71f3637eaccb80df32744ecafd50915f89e29cc01f98cb3eaac1238c7469281fd16b7017091dc3e749f29878d3ae47940bcd64067401826f37d341d472e89899a6678eca978e66878f6b7fa7e231aa1229b7c0201900e8c6d5bdcc6dc5f2e3ca2a7b3cc34f05cee679ffccacff6637e63f6f58fb0b28d6619cb0ae0d7f4e61a83cca0176913c956f381ecd59cd11be4607170218e28b9cccee7b0d6be24803a8e0fe5b92b3449dd924b701c20c1d741397170e1e97a5857eea5adba1c7a272c5f36644f67cf85d405361afc866fb2939d1c988d780a0b11ebac962be37536d8fb38962954cc8cbbd989f86b12a9766b29e2fbe1673c06df94de47ac60db744e954ed3d0469bd3c6eff84b76b93ee09472f66ec571845e3a86079d10ab240c4d9e3180dd166c03eb930b7fde73508dcce1eadf1a3df8e673808419edb145fad67f50fadd1522576a52edd6ce2b34850a38fde1822ee4526dff5d5f0ca007ea15d078ceb46c94f3e365eda167300fdd00f1f5a8f9e08b67c960df6d754a82174d584740dd9a12d7fe36322d2838fcfdd16a9fb305adc0ab10046b969b137fc69d20d89f4c3b93c3030b92f16e3f98a7024438b014841daf5bff752c9f6134f853ee349f1c84635e41d6aa51aee9ae852be62383bf3169d595a0edcf6cb5d7ab2e8a48a1da81c4b9a60b22b019988348fda0923b0a050d40e5aba17ae1b814025569ad39cd0d6036c6b623195704d0b9f1ae1a624bb97e2431a0e54e4d9478c2524499bf5ce6236f97558bed6202981870d7d14c59b22d82c3809d6e4548691d89b4199500648966c45838fad9b975f59fc896aabaf667f274522079801929519b30900f1858c1b3d1df4206c2c8f9674c94d4cddaf87374abb0b3bc3d979e3c0413d26aa86eabef6e9f360ad490116b451bdc7a8087cacceaaac0725f720bc687cd1ce5bb634d6160a900cde1f417d975c120a8631944c9b218bafe7b992c15dd422c344a6caab95996b0ec09530f97348001239b8db4879f12732786aa7640d8735e6e720f84061c80cbb0350e864f7127e9d5670512f93c684e3063299aa2ce7e495bc9968a94abdb1ce1feb74015788b0d546bdce3cfe367e881f10571325755cd023075f6c8ac08bb0a46cbe1f4b682590c8bac8bbf8e2bd091a5234b62d758dafa06aac2246d47f5cdcd9cc11ef20dc1b57b95c0352f82befc9305f3e37d74a47fe27ceec26746eec5dcf0353fc4da81cbc3532e39e7304fc36405bd52122d7d6890e3f591a093f9421870ae5ef1e1472a7b80365fad45e08544f76d62651aaa67f342923b982a101c1fea76f33ce037e24797a3b50876ed048799986917996b12d298624269e6c450dcba5b0d3103c61c704ad8ca9a7318257e3075ba636c57875d04e31539ec1ab00798a887fed9cc2fd0035291ac9bfdd855f189f77739b1fa8c300380e80a4298b64bacb6d6241def941311aacd4e6d018d804c6f9faadcb21a1607922d49afbe83b6bfe12a54bea624ea105381d6479e2b38ac0fe9dbe2a7ac319a2ad3aec1d81a9cf6b611f849fd0ef6e8ffcac40281f86fc88e8bd5f17081ae645b654fb469c40dfde53364f3f882dfa8bd587afe9b8d704cf75b6850003047795cac34d7d7acc3c5ac4de0fd1259a8193af817ee7ceb792a3f03336ead94953026b9313333bc5fb82626d1dd2f009417c484fa4d065ecc4b1b5f8974ba7a788ee37c932fa1da8d664c4517a74642e87651d6c05e88375147dcf3638afefb44d4b241fd840727b89f3e05014d6b8de2c8b20730f1e5be74dee805d979fecb87f4a9f1e2171246ba9c3a34d54a26c63d06cd02df19d2e6dd28e1eebecd939c69183c8e5bbc92038316f650732f7953252c57548ac7b8afe65ef8200c4b306ae51ea111651afc656f54765851861436152851dbd3485413e6cf2c7c81ccb59d1087febf254f2f336fa397675e358869e605c09b1bc49f43d8c48153e1cdc24deafda954bb0bcddfc67d870e9e38784e2e77e137e924bcc73b370d0229a7f08ca3b7c95e999be41c3b733e593c1b9d2b256cd6653580bf521737d496118084c5cb1252d6cc42a057dfa2a05507654b015d1ba623af2e578ec6434c295526c19538060de1827dcda1b9de99a6e6d54ae852a45d5de63d866124f61cdaea7c28ac30e891fffbb952997d090136d7288a576e6bbd50fa283ffde42befb2b247af84dbfc8dc95a812b7c25fae0a5d2d73ad74cefc9e757c9606ca82e5aa56fcf2d6060f04be161845ea753977a60f0155df90675f09c55a675d4f3903cfd0cbdb1bbbb5c9702852ac9991644ecfba683bc20d61129dea880e4dc79b87a9c6ab86e54ed419590a3d1a67256d5679313c74145653928970a538a4564fb4614d1e857e5a8d38392e9abb673ace0604b348fb7a0d164674bdebfc6ac988fa81ace46088e6ff817e4f8a66c6c996ae0e272e8cb09cf565f283f64ee394ab50d7d177069e3b17a3f44d476b2ffbea9ee12f5add92cff66eb3bdc9e323a5bc66644c0ac4b973affe466d93bde3058bf960b60a83cad433d8957a402a25c34c52509e38742824e2e63ca2ee4a87cfe12da9bd9e129dd432fba8d8c9b474fa4ed2efdb9b1abd22a2710408ee3f90e7de55f65abd5a85afae5e3b2a9256b6da14515c7be912977df8453d5d967effde60ce3d38fad24552971be11ce9149704b68b489b27d3f518d802e3f2a1b3842e42c062d0d19dc253be441399154b3dd5d94241b5ccab7083e58c1b26c30415bfa39d02de2c2dc86c3ed17d96c707d18e41fdd89a5d5c0f6cfe77d4a392c200995e24912af2e9f80a4bde952f8d9491b0ca986d2a13a9e0dbe255b7f2402de19e3fc351ebc531af60d3e377faa8736cebbf3ea4a26defa86935c46908fb95dd855c11a9f2f3f1a9c73db30198345e8816166ea307a0a50fdac6d1c66e125445fef544e9ce191f357465db83008c78f6ccfd7944ee62cf655f67adb0005519ca50c6a05c0f97071227047bed7caeb64607c95de4c8963c32c2319dee978867ab59ee5fc2c029c45e330cdbe23679072527b00ffa93ed339666dffcd2b28f8554262df45f55414952e1ff7c702da6c118363bb2ebe7e4529db6b97c2c5ea048cbbe491f3faceb0450b70dc310387a4b565ab8e82f471035574728dbe1e6bd197ef6f4e1b13c1f7c9b44be82acebcbca6bf979e8134f75c35360769d05ef9cb6e2860f31da8008c895e09203c5f66e5f71b1458d7a2884ee5a288bf6739a257bfa6b083bafa45e99f0b2a635f80032c47f5e8d788aa7cdb7d26a74eb0616a6c58d93d9b79b4a518280ded2982ec96e70d91e726f3293a936f8a4863e58adb897592f81da9e9cf8e00b8c1f6067be49c313ba8e53ad3c826f13bb2ff204b53d6d8e80d68bc6c6f515c351c12eb7c0ca0a1e6072d24d8ec2b8114decac83ef8a0cdea134164ac23807cd0f19ad91057b711a86f83b11050dd6e83b10d5eba13dd1f5c12f64b86296b8b1743176e916b1e2b2d8ce6eacb03f29a69dc0d47e1f9ca4c5ddac2507d87e8d9c031b847d08efbb1c12285dc3105cc80f6f7e6fd62dcf225a752350793a71ebc23eeed365c4798d4b3444cc6055c23180c06bcc138655b671d91417a37143fe91b0b0cd2c594cf165ab63efca1de48aea60d0ab0308af4e4c51d0f2544cf0f3ffbee55ea9678859a1b39b45afaa2fb6a8147d444f2f4005324a453314c44c69106cf22ae815dd8029a867601990e27ac7c1d5e13c99ac5a690bd3341b674765f22dc7da8b545de196e5417a6ad441c7fe66be518adc5be1ca0d1d1fcd75b227db00c5a26120d3506fc7ffd7af7cfe60bcaa760bbd890fb9a2740312fa5042cb94f40844b9dc2edb567bd881c804d51f7b051c411e69b48222ee7573ab023e6477536621e8aa8f4a324b9c753c3eec8c49a82e9436519ad948df3aadd54b81c2d9c7a378f21d18fb194cac621d033b0548f29b5eea50557c1a55c8f30fd10648cc966217617c51e3ef2eec141c8ab426782510e8673906e1ad1c1cf50a85e8c0e1bc45311c8d0008f70e06be895d974727abc84714b31c394d162181967b89438684d1fbde6becdfddddeac8d08ab05299c1fe64fc8f364a146d71693cb044188c445095ed4ac3ddd14d788b17e30edff6255b97b74ca2a67234e484490b4785814cd01cadf4354e279556a0678fd0fad08cf45a11de0f53fa48a6bb565fd774a889931c16209a885cb44c488fc8dbdf986523bfd359f1758fd7c1f20f8e73116ea870feee773de2334219211948d409cb7da220ff8b2bb943605762a23563eb740ac3af0323b44082a7fd260d781a276d767c04b6285480b6bfdc50af89fb8083d96d35750576105e45f8713502e5991cc6d2109525e1c3705659d79d1e20f468df16921fe7d9a791958567c2ccdbf8d06e9400d6e7a626fe5fcca3c953b2b2e242f2d6df3042a8f38ebafb63282ba268806035641b2006f8491536e735925ac1edd9fff4e06ac20a406bfda508f6193b7b7c73b285e98ecc3e749191800d986a2edeaf7740866a3a4c925edb4a50ff0ec4712cc4e8b69e903d1aa82dd64552336b9500d97932250e38cea1cf62b2c6a5b0fc165ff874495213139ed687c9d61d1a5456dbf00aad7500da0641b48696b257381dd6bc4127c14a952b1bcd4ecf1b40151b7305c17248152b4b7821e769dd7a4adfa5d45109b81b8fbfd6dce8fd0757017fb4bb19072bd4c9c56c75c04831c8c1d4f63178edf07d1c68055bffb5e1424499dcdcc462bd6fadca12ee59a6f4465c2c73601215822111900860c15fa86392c60c1d552214376a7103f541b3623ccf51163e5b54a582357758a8d76ddda414bbc9d5ca0295d3125f21dc68c8540739295c653c4b72e520163d873b5e17a5af440794b8ae65ab8a9f4dc28dd545eca3f80ba1255dcbb7b69fa1f8b12d0bfe50ecff015c7bdade175a2379a17d901e58d249368573cf93ed59e655a069fcac516511b51da0151b65c3136bbf7bc415497f708436606652d7839d8c5704b5fcc9a0cd8c192f034babbfae8f040baf5f5d38cc7601c4108657cbd2639e284b27c68af1e2a520676403d0c95cf8643387206dc0fb6232f277ad49404211b4bce6d162929b29b8c78ec5e484c4dcf1bd4b12649b0e82656b179a2e2ab5533e86c3f7affc98028b9f50cece74dc459c2df4636426be4ba14b12cf43c5c9340be281a468cec734093f16cfcc0aa7fa11c63b0a687dede0a5560a2ec3ddd1c1282c2c4b090eb8fad66d7f97c400ea812c9f5596df70236bb37e5de9a8fd08cfd6a19504df40482105f0fa03d12e788fef6261a58c6f2f00d292b35c8c7b4c6158a0957ee152c81532b79799c633b34f4a4970904ae1d238907596367d9e456fc2b4b754da48395e1ca5e052352212724433e55c717fab41210940b1f52b8b1bd251cbfd1b5fab1130f6900f98e09787e7bb4dce965981d5ee667d63da8126105db1031a594e27eeef2d763961fe2a11d49fc6677cf5a8fb5877daf9127cd7a66a2dce341780c96ce651578a4ca9fadb0b042c312861772e0ffb8dc2532a6172966b606ba4f0c2ff7c58e283dc89ec106eb76f979fa7b1bfb8f617e5b8415f19360447534f787d5dc28dbb87262e86085ac1dd9a0eca086897fd7bb1d5be390c364c2545f1fc2d6ff91f664c51020177360324872c66725dbbe617d3e0f525957fbd8de886d88f193012dd98788c44fd8cebfdcfe455952e3b6791fb06ea26913eecf2abf622900f1d7df3167bfd9fd32c1400ccdfff32e274863100b9770d91f9468a5070b9e6b97f3175aa85b73c360d1bd3eb645cd97fc0a7a2dbb53a9bd7170a75073a49fb79118ca73dc3d16bdffd6f8bade6e298192e67441ed8624946702d9f0a9a93336a4304ea4c16cc7e4f6973b02a5b6c6d490e14a52035e9dfb925fcc0111ff2e05e9d45e06fd141209731745868c38f084904eace6d717284d82489624075081ddb9c9472cc98c672586b946e772b3ae08ca7320506f915a6b2ec7fd4aee78525d85639e76be8be055eee668b65814b898e2552b2a6c268fd29bc223d83ac1336f73e2132dfd20bac044fd6a72cfdfb671a027b9edde8d5620d6c78016344241118f8e5cd7840219eea97764ec4ac92aad01bfd445133ec766b63a731486c4cdfd08471b17828f385a49af3160778da3b12bb58282c1b73b47ba71c671b7181833ce3bd15924e58d15e0cc7c954a3b7e38f423b817cefa5e3c6b4627a3a689467c1712965186dcd4c0f841f01f04a502a75513ebbc5e1c00ed3c13dfdaa3571bae56bdfba18a4413b5d8768d07c64b986be1c73fc2900ecede06345b80480735e23e7d1c4b48afff7424aa071bb728d76362aab8d1db36f88847718e4e0788290d5aafb90c6e22bcace03d5e794cb229e61764700bc36176e9234ba10425581c8859a8283e07330b58964130e16ae3fc006af2f189bad1f02998cf87e5820c4cac6704335909be5b5f68783957c715c8cf52ed7dbb1165fc71c994f2846ef2f0ed281f89ddf43ece9bf8384458940ef42ddc30478766d32d4500b5cba7657a9ca2818015cb6e9565b92fb95cf94a39abeab30b46cb625a08f8c16261d141a68bb6ee20733561f17affef6d2e2d14338ad7a68a3d4da06f8e689bac851a8be09e1076bd711a44210e14f9a84790705819e9168d6b2ca7dc7d8e946f5e2f2d500160b92c32bda45ec36f1d34065dc23425578bc96397140f79ac813a6f2cac83ccd155900193f8c5169c8f4aa34f498bc0599590616686124bf422129d7e22ed52501505017d2741860dd33c6b8779a5fd779fc0edf0240be9481d3cf1487329ba808169d30583cdabe6b5fe175f9a81789221c1ad756b61bab063b29db01512aa08afe9a66d599dea55a60aa6e82e5863db19f32c524bd2a323ca57213412d91bc7a2d12585c32a8540c9914cb547e258461162c2882137ef5dc3688aeefca12c960fca6f051fe09413fd290748be2663681c813fc1031c6cd003b4346a61107828c099aead9298988c9405de8f65b1e8a0475ca712797065c0ba4398e0c5a2a58a4925f55da39dadb498b0b23d77b479aba53ad379bb4dc40126a53d74cba2b3fe0dead7e4a07da7e1f8f42aa02a1a6cca1d66a50558e619491e14ae60ff943218ad4416549e1ca05167eec8cc893191eec11cb778fa6bc25052143ce31ab12c69991f9194d562d64c71b05316d632244f7ce17175e3bdfa4bf53c395e7738eea5decdacef31b480ac49edcd5b220942d95e3593043e2ed811cb0520eaccb0612ed80d8d5f558fd2444f6e34ce0eed617adbd90f34fbaef45a88912a474b3615187af9a31b48deb623436b7becf3ea40b1321a7245d4982e04f0dcdce166d285bf922a68f0e88781abfa2683cd569d331828e1efcaac498d552def5f08e88fd637557f8d2973367005dc81c498facc35b913fd63d8042704224a11c7fe7847015540c1f52e271d3251b3c4817e3249a18079b69088d7f9160196ef2b1f51d008cb58e7a44021712216d249b92157d978ebfc2fce024a4b0c72032bb96a8a6f8ce64d1c17f84a261370bea253263ec9df5b97e46416ad12d60c55d4aa6c3c5d2249c4d01cf515993303b60372b25095ad48981bb280106adde12224e7e070837698c3068a9791899320b12543b9b0a439b361b9ee5d8dba695c49976421b80b75909afa92c65bdb6662e84775912653af51ac22e3258935c31a6951c04001944f8bdfcd08408f4fa0bcec7897ac1727a7a88323178441d822aba1b3d79013a5d8f01e0001121320228afacca286a1d8d05c178efe215aa1697f9202063793258bdbebc2209fda4f08f0138b4c19e67916fd36c4831517f30c7a8bc5a960504c3afc9c353276cc8d021a00680d2aebbd94475efe7bdefa4f828ab0c832f378967b232d7c2609b7623d625f4fe6e8283eb042b44b9b015a33790c45d35670e18a54330b3e939e4eec0a920614b7d609ac162de937662ff08a980d6bb2438f94f1cafe1aae14f7302a9bc2b28ea1848cdf04e81c0a0f4a66a902f0c4822d200aa4dd622346a0f56b55e503193599843cd8fbae2c93f90dc8a6a247713d2221a8d86cdb61685f5484e15c82e7c8bd34492040f129b7d505469031a44da7b1d9afda2b7c041f8ee57af129da08c02082b386bf94f02ec0b801eeeb938a0e7a748205634587a913444b31fd6f28fcbf0d4de2195cbcb1b2f9759bc2b58858ce6560b226e3246a1323aa99362259e886d5834e1900a360dec6b380d43eb5b9c30a5fdf5d2753cf712001e4e17669ba86ea165b1e58c1de584eb35f4552d77cd16d34c1ccdd62bd00db1f76ce426dbd5949adebaac777995e56fa996f14815a1cb068312e3e656839ac8c54217156cfab0e4288dab66e16cbe43321c10e3f49435cba39745b160bc34eb08f5e7353a8a352f2964e7ff31a45a55486fb37a5eb7afb9fe0dd526e3b619612b5911d9f655274b8d30545246160c79a6c502283e4ad2be6db63401e51022c52015b128d79f80937c8f2684d54801e44efbde4bd564d91ed9485d796f46bbee29fa60e226c717411e6240586dcc51a99119b632e74948a5b2a60c8de875cce6b8de5ced9f19a12284edad2a86514374da7fd6d7470d01f3e6717f727c7dcb1af8faa8f794cc4ba10ab6eac7fec2957acdf72fc082658ad5b0008fbeb724ede7796d602759585479fe84c1327e39be23be2a0c2a8395a88c2dfa1294a4469b2013da4ee7959e95955520b0bfd1e47c30a938c0b0b7931d50d7142b0486c13b68591ee824dd61b46594c42cb8640ea2e2adbc3a02b7e2c12260d7c8f1a10a40209d8262fc32e57ebb6109c08609739f732454170fe9daf52b5c2305e4f33b0faff9986e2d559fb831e13a6ba5a365be630c101e0444bafb8b8398c5312c86f26ce3b21ba72e814893f62be5b84343cee40e650913a869ba7583fbc4106155424767908e8cb788ad4b9fbe68c506ccca07e3c6600881155364b629b36f5faa3138a72fc672ae8247fe338b7f25443cf8cb98a88901588892b54a44e574d44060b220945738a12e03c44194d65560e6e4c54d71601b50a2d5f3d44e5cb982acea8a2702c04b467ee2f39f120d226cad11925d3d808555662dfe7c105d219a5d820eb896ec4da9033ca6e3aa3f6606335c44e132b7711928a8c646e45ae3214944e1cc82713a1d9a09ba531bafa3f8907d4bc56c137f780ec5f8adee807115a556ec6160a9e52ac36a6c5b59c403f7db41b277943d63260b28ec184a2f523beddc80263e225eba89802681b09a93904621f39efd6827f24026cb327362458e126dc78f2ea7b3e07b58beb9e5d64522d63009b1df72953e045657593e0429fcad29dad76b9c9139a668a5a74f30e2cdbd89913bac76936600c09128b01c4d2054c54e13932b7d7f00752ef62902ac3334ad36884b443effd1e07bff83ac72d4620dc28ff3fa08c6297c54d528b9222d9904acecff2225f56ac1d567dccda611d3e0f421f382ea4ade400fd15e5b2ac000b3339b36c39caf12d19e4736fc80d3a9259987812f6e0f999fd650aca09635b606cf4c6538679a31f28bf5afccc05c61e27a7df75246876781fd75295fee6652c078862e39714ddf23a22f898f63639ba56f8381f58335e300a2c8e64550cdc097608e736b42ad8740b0d4dbca4f931159c6b84a005d12491da628707159a45033a62755cb1da8830112c0076eefcb120aab07b0277c369edf027c06832fd855cf7bbc93894ab4336bcf543b30d9ace4ce4ca7a6b7a25ce98a8d427570e6c1da35223b77eaeb7eb4029fccefac6e71d41a00eefbc8ea53375ef097d549c0c8ce9fe4782a92667aaad41dc301b39363c5802824edf7a64e57728e555fd633e9edf5a20f1e19a0a997a76d4fc32b09c44a9dbc1eadb725ca6a61cef6db5043f19c3f2b33a800d5b964895cca8e8373847c460631b39ee719c5287e422e467fde3e120d7e0e5c0e68bb73f9a282558754e91f18398aaabd141456020626e4fc03b5b0a5cba7966951863c6c2f543874b256ef3be435525935d72dec4564babd93ea267e7a302f923818134b34b8962bebca1935bac15b296e366d5e30ae2a979c9c16791e61780c3571529fe4bf4643604b6c7e4cbad04484f0141a3d3c052881eac67c6b95a3290024d89b27cc39b1319ed1d1d36a69409997d56e460257417cfce87b4bd7b12fb11dce2a79f31e615c9a37cc3aaa48f867e5815ec93a345e6d0a7a4ee42e77f8ba6b1f78c1cf539dcfe41ed6a113eba460a782ec9cff07982f80cc795501cbce1061b59c167bd95d1ea8f523663f3b6005f205faf38d7651fcedac398ffa9f94a8837eba1092c1b06c552af3671921ec600c83b8360505c068913124b5ab5856fb3bec685cb7e45baef951ada24ca657ff945892b7ce6357e441aba810ae76413309fc1d5b340c3cf310da05b7e963e89c32b4669a1162e6b33e586e6b1a85d14ac6dbb5f60626bebed6bec98ad59966ca700fa2e9a2ea5b5aa1d9c12d634db99fb3a486cf396a5c2a65fe5135d3bc11a3690164346581d89146af13a120bbea4c390f44fd65bc4b2ffd5d474da46d093c41630ca80997ade9e1b4b4a567f603246d86d19e280437420fec5b3ad67c210443aef7df168c82cd0709e08ba2d157a5a0bb549ebede55b138dd6aeadaa6686bd7c986066051d7444a595369a8c077389c9d181121f673a361ef243c6bcb090ddc62b63399388db05551ad3046ac90b7ef69d6dfec2b38a682ceefbc6253741d1ba2fa3bb5f97b9c3a1b9d7c2c6ff1104ffe0c2c72b73221a3dc1346ff90e604b7bca13d7ca7b6b7bd5e7bda30e87ea23a7241f6eaeff7f3707488cda70ed3b05de7206619bc301ecfb64df655fcea1bb5f17fd52c93ce89ec17dbb4b8187f1cbd420e935ffc25bda57d48f697b2da6152223566955ed9527eafa3176da807361060e278aa661990b1fa5c7dccd6d652135ef206604ffa3ae346faabe7d29b8ecedc947198aa81dda3e3503b25fcce3a5c9d8198829765f6e583a10f6a8773c935b35db8a65be9d135f7ec9356d1c44f548d5bd2a1ddab9acd836a0ceba0c98b80a49b904287f35e12e50a7afa364118343b9da614e7f1b46095b2b97c0e54cb483f3c6910d48181bc2173b6715a2ac2a900cad2ea7b09e1d80485de4c496ec5f057c1c324253840ae6949b252b947fc8654bd68f49e415271c2605711380da7984ca6f5b8ecbd095e91f56ac5d354aa0d81b569b37b41cd96f6d085cfb63295c03a905093db1428d6cd28293b4d21655e8d97c8cb146a70ddb14b17857ab1d1f480bb13c6d0ea3fd352bf2536ab6379ba6a8cfb07148f0cc06fbdb66d30aea016d4821465710588a8efb648f54f19424b458e22e9adacac77ecd8b12ce5add521c563721094a170b4523a86871260321da3c65ed43b59e817a326c35b286c13e8988845f53ee71a84058849dcdf90218d3ed80e8454cc6d2fef60ba7b9fe6023ae8c28a82601bd0f7927961f6e49d3a3bcd87b9641d9b6bf80adf7ee254ce7b114d57ac20d34d67bd02fe0ef730cd24e6ec12b128299f88b65658165ea82831176d0418150d1d22746b4c7daf0d42505a98c9aca23aedac4ab0d42e54aa75e77a537df65f7a2bba3edc3587b47df52d4b47cc71202f83dbb982154aef7ed3199a1938cb06a22b07e267a1c92b1347e06444762cf53348107558b1c936171f875e2e32522513d4a9256fc8b67369f94a6d3844500cf5feac4860750e428d537e3d2d1c51b370816ddb2ce0c4864a30f0210ff29745d1ef01842e45b7295e8648e9d9d75ecdf2a88bb5c5820ac1b7b6571b8754408e55a55e0a07469e1d8e189de034b2f7174d704e9cf5f6f8f436fb03dea91143c8c621a83984bd199251814861b90628b7ea510b828031818a7e9e3c7ee9a93353c705cf92ddb1823d749ab016db1e17fe05fe22c2d28ed892eb61a8b3680bf0f8ce110f60f4e63b68d2f78c5a9dccac90f32924a775e62e28b26535dca6dde958273bba0d610bb7ba0909c922d38d7691ae9f2fffdcee54f7829c6d4153f4c191c1f0332201b27f63ca8f31aa7f6bede044aa14c3fd4406e53fe1ac48d15d981e779ae3ab4e5ba39eb05014bc49c37b2bc4b65a01ede5622a587e2c6787fbbe86960d110d7fde548a29117c02289d9bebe43edb5edf672e0a1f4f480a2e9258d748034f416c1e86f3e26d02d41d0cc10078b09625f03110b1a251ac7814571f361b74272bfe8eb9fad1fb25296a59306772f53e1d18ed3a63111897d15c3457078a906a01785742c35aeb3a520bcd5a83fa92b5e2f6706f3aa210eed138f74efeab01e5cb458b81c912b80f6dc88bbb782a0840fb631b9528742818dc49caa2d0bceb3e56e4cf6067b4446d5664798934e91f4e5a34fadf77e629599fcd48e202bd167409f19e35e24f0c5f0edcbb2bf210391ec3aa3bbd765dc939c09bdec50a97e1843d44b6257d81cc6cd4cc552373b821be11b9e091380c5fa41fe5360419843431a26e603b02daadbe3684943a7fdb70a9b0cbc38f403a06b692316cd51c8cef37fefb3917255ea0ee5721405432a099d0fb9e93eae489327225389640ab263845bea2ae85c2c1f2ebc0ae150b7a80c709afbd0a44747fb914a72bf06662f36639edba581a476c481852f710459a6ccc5feacf77cc16ab3c0d3790b6f45f7813f1a34319412e46985f2f424597700a1c2899302d01a9e0ecb1bf2623d8c2de9f38af030f9250e173d80ca5ce83cd158d658d10ee7cbe33808bf12b0718764a024302925f2e8f91a630f9352514140bae355118bc4815f3b7c1281ef210aecee053d6a8b6b9c1d36243cb2354104227cfab6f2e8e2e01571ec87e73e0795483cbca6bb57294e13dba5c2dc8701a61f5d9c5f32e6eb29f8aabceb1667e4f93db1334750f00762148424d90d5b0e91e859fdce1826abcd2db2e1597e26216b228a537c680d06b47a4590caa479f20e3592b6bf010aa851b3208eae09a0a9199cea0dd4664157a315e83a670ec6dcfe0335fa41da2c859c671669b3bad5faada86aed3a5139225267399a7bc54c270e51ea0153cd9c7764af930749f10f27b31107763952af12444fce6e71c0b6979b085060080fc43571e4a1cecaab0a3a7e3455592b08434192ecc8228d1f1195eeaf2043a507d079b5fa70c52964b882d82fee5898cdb33a361f074b6349e38eaf9ae1d6cf1142188f2de58076cd80c73b7d58d6f8479749d382dc8826cd327006d02147e370de0b77fa142e81d870c8feeb7246efd771d6dd81515dcd7f74afb4605c4287cb3d4f4214e2fe76939c35f7788f30b6342107be02798fe0286ab49c537fd1a5e8d171a4f945d722a37296fa8bdc228cc869fa17ba8a3c1ac7945fe82c32520ea97fc955c46839a7fea2dba4e5ec536a58e77107e31fee701bcb51d3ca2ecebfc9b6255c3e6444c4cf96747e7489f0b8d0161713bf919bf837c6ad28f1d7bb8f5a67149c8110e58efdc103b197daf275847a2a785bd08eafc5b4daf40343898366d69caa0c09c66eabd44019df2d70a86627e9924b65764c27d57845a97a8fcafe90c7c47dc0a7f774f31d55f59e77190f1746a184905a6a5658bdc701207c3e76343ceb37891a00e8d5cfe171d82e8885f0b79e78661b27299a59f8fe310ea7a4745da7f478ff16657143911dfea2de9324793ec38da7cabd274a743eec51d9e9ef156bc7d3443071794fe5633c45a88ff2de7325cffdcadb77b452678e278fa44704a6a3b1bc5fa4f878146ac000b483be6702cdc07709ac1c769a7e40f23cb61f9dff372a15dabf7d6dfc0a68fdcffeb54ae80f002963d4c7bc2014dc2ebeeb505c745b220eef651fb36508ef21863821672f52217122179392c21adfaaaf6b7804795888a52c0d7662c4502b66086846098652def19882b62f8c7789e4595121f189739ba602c83e9395d0afbcaac9ae6344abdb81bdfbc2ab8fce4fed6b211b0c0b5b20748e014b47c41ddb0cb8761c18f3e70e1081672ffbec2eabc5870b0e082b947d764a2405d6746e5c5f597f9333fad96717c608526f123f1cca0262dcee70d680abe85f95a2b46bccfcdf5cc05e1a18b5bf220f51ec6412d6833949972f3ae256b768f3c62e490118c9045333263c242a54c6fa424adb7be100e2315af087dfe2fab18f513d852c5c80e18afaaab3b3020d59a303eedd0a5c0288190563efb4a422b8fc4192e09d1888541b441ee388430724e8463073f6e05416dba9571f3500c9d64ab3bf66d7f51f83a11a0ab7de6627c69ce49db22cacac750919b85557d6ad4582a23249e5f3643015ed987bf4774bca8fe6d3e10aa7d013745818cc80f82670d63379fc5bf331d5c4f526f70ab2fb1d4d5384e36b574b7393ff5330bfd062b372a216d15af4e15fdb61aa36b67d19601a762038e73009e6c2feaef1055d3646aeaac2a127ae1ed1ccfb4fceec83713fe8d509621d37b2e832d8bf07c6cb19528f72fdece0c6372e88bfbe4afc023b69a140f798807c4e09cc0c1458d99e3c9500c7b5637bb95ed66746d13b4a13b78b65edc1a5da7beec6e3acc98bf863183f5d988ccad98f67032b59be00130e6a8f250233e672344d2ded64dd681e711ea54b85e93b2703c96461c14ad1005c9dbb18b1eb4aba3e721b196b6e5068f5e915b0f4d4844138c6d587f3daae68312dd973db062cd840d5df25c936689bee51906cb93d0dce5048f7c294e2342d401a555c0f766fd25fcfc0ae9c0d06f3a05cf277ebd274159b6a07a2031eb3b93a5868b79ae6ed2204a66031b75561ee7911fcd54b1b5ca33bb4026a6004039d07e6b8960400f011ba655a797c7420e665f16b18068b5d87a8d96547365878b167505e4ea6c2f60307f77e70641b9fad8691eca3d84a5030d85325b8b69c530b6f645a9d2d1606fbd11d82b42d46ac8988fc34c8e98327790725f6cc157ac551a043ce78208261d2b4d65a8ef7b4181a433690bbeeb0a9e414e024453f85406d8337eb1508d176804a45e4f4b17a56cafa9d067b2ecf7832e7a2890e0ad384b3450962b18e1353a91c837f4ff90328c61bf3b278ef290fc67aca8a810d5d1c792811bae2854903573ecb9ead49c831cc362441049a5e6ba95720d23a4a078d66554806ef59e26a20e33fa78239fb961c1bcf5106fbd513392a27e6319974bd8f08673ec0443bc05088c820064957a0ea2a108ae92a7b52d1d4341e8ae5f2101e2cc462b34412b2f7de5b4a29654a5206ef0a410a5b0a524cf1924a19e50456b007fbdc281b96bf5bad60091695e301a1ce0dbd3b18af9582ae41868ff08fc87ae2cc1362a0408251375060b9e818dba11e0a1ca080f25d7d16a3069ba9974cb50143ca9311f6942f8e3892860043065f6ad0012b5aa9810e31a8810a7290062b32d1c788e4c4103e4656a4619ae0e23b1c6642052427ca2039f1e57de9f2dd13d329f8b81d0f1f4db4e2e3dc1e1f2674233531037a86ed7cf86013909a28e2e93fa42688f86ed56f40a80535a3a786082d76e8f9e109a8bd37a6044b0800ecd1fe5c8b66ec3c51e5882c8060a28b33ac159e636c8175151221c8c61696c94b1e045f03aacaab2e397ce66a777794eeeef6876d8ec9ecaa861e725ded34f89b3317e1c669fe3676cc376ee859c33c9325e69247321f573cb964fd4385a43a64094156e698c3072463c19bba623b545dedf460dc7b39d973c13e2420aafcea1fd20f29f8ec79c03e2420b2fc43faa1cbebfc43fae1cb738f27023fd0000df04a7862ccc008fc208500cf9f8d89315ed7550d7995ebea57975ca75d9c068545fa8186cf58a3dceca19f17a6c1f934eaf51b19134714455184293868fc600bebed80d539f0b4449928ac28b31db0a40f71905b5132ec659e29e0624409b189ea397cd58fe99f9bdee804ec41f18d88da6de73c6666d8c5162946909028f30f090ec2cc1d91e88284c417e642062e6dbcc7850c5806135c9828838465078e84c4130f91d8f290ddf9f956e8167aea05d97b527ae7bc1970619ff315b6e61fd2113db8ff905a0083fa8e20e3530f21e4242222a21872c01b585f3ca0bde8a4feb63d4477f7ca28a05f37a4d24663be3db6d728be3d95e58a6f771fbe7d058330be7d0706dfee1383207c7b0a51bedd48956f4f22c5432a22e9db9750f9f617ae88628416538278242d678ef092250b9439d4fc63218a72c072062906a31f581d241ac2d2c316371c21850561706175300a84e33b3845084a7c89d5c129be00f33de6fb850b7fd41853041bb670f1441a5643207c03f1ddbe812496d5ddc2161bd62082123eb01186d58fb505f562a1e7b0a2478f1e77ca640fa78b667eb6a51c64a5fa87ca08146aa43efec25d3a677ae56655c33ed6e32e49d3f9cb741633a3195e02568110da964d842061165653987e45cf7c889555ce4775a40af5d4bb6364d5397d84fa7585b02317f562dbb9a7d3cea5308e72fe742ece5ef7512e35c4a27405167a52e416f3ae13b04822db813203b0104262b14de6d4ec76768cc2d87081c50958e060b3033d018b19e80ef4842b885077a02af8a490f2955115dfed24f11549fd832bbcd8ae1003001d20f0c08c36b6c040891b58ec061a2f443c89018d317050567b2913767a4ee0fe2119b5e0a97c87d346c69e317cced079b2fade8561d77bfcde711e7e16b52a26e52b44d1220aebb9edf149c1c8c459edf4304f06c9ac10df75b93f1ecfb2cce516242bb2b9e6fe7836bf4cf7c2f92d08d4b61fcdb7feb85d9e71f0336e870bd2c4045e55efe155f55755b829c82058ec76b215f95fd205290fbc679472b6ac32c68eb11f6468c60c1a1c62ea86dd97fbbf7ec61d99f9548afc318836c4289a90a845b851e07dece05f57f5d3c57895688fa37a4f07ba4a08fd1ffe78e19066b69c7a3af04529a5bcb566cb9ac32c1761c5a367c0c206cef55eb529a3d4111c1026b13d3193e5c50dce378f6f7f3ff81e82d54454f926a28def1750f18d85876faff179eba1865c4601aa0e57244308655c30368cd606431081e4021f5c0852baaee297ec3dafcfedf3d4f3e793f594f147a2404267789bcc9f08b7983d9650368cecc40de20c1367ced49854f79f159f9990b89cb940d6ef4d202cf48efbe8cf549f59ae2083ccf299b97eee2033333333430db2fb700ef996237c5172d7de576c8e99991932333333df54852fde0a5110a2207c37055110055110055115f2336354b20724be077178f5babbbb6baabb3b6a61e1db8272a14d50262817da04658272a14d5026bebcf7e5eb63d7e37c6149b13492c01e54ab5add8248af5c254d2189176e2f1c4c825d5248f2c4c879554abf6767d5396f05eff7783678d8e5e570f17e848073f88df1e2d93b0463562de52a6e3fb71946ecb3aad1262c139672b934db3fb44c4bf1ea99e9f20ceb45b72558d8fef21836e5f428a416f6791cfdde7bef598df9458e02d51a5fe8037d3db38e12eb34aec4f070604228e29c576528dac119964c9198ec552bc5772979542f1c221652d196cc6afce31d1565b9e43fa4221bc2541bcd0427d651b6665376a791844883c5222e9cf3a6f001428bb72035f5c4523445b0f41f92106f7ca7656173fc431202cc03c17ef94ed350111082528c6d3efe4312c206990368698810024208a2610cdd907bf6d7022e86c83229d6775e0e7bcf97a32e417abc1ecf6e64852423c8eaabcdd227a573c246ba7420324ab1804c113a5cd031242e532071f17244c506882f58c09c5982868885814449d51fd2952a56bc14d1c5173fd80229e90b761b861756b4f07164258bcb8261e50af8b4b082c51192952abe5bc536e01556b89e708519e90a1b4857aef8fa0fe90a15ed907b3694530924259a434a220e9a84994735978324cc7894945262d9d196946994529a6944605c34edbaae4bdb92c021092b9bd6485b84f0a852c603d3298cd54aa552c99ebacc2fa78b8a1aad91b624f1281bad660b525353b30d81ffdc06a5f543da6205698b11260d8dec3deee6f7861a3a9149d0cc777bb7741f2653df5b45d853beb7f0aa8de05567793a479a25893aa7e3c726236f9381672094493f9044789144e460091f6880a2418a09a2879e628248be8048be604e08a44993e661908cc17465ca43375d99b284bb5bb162454c10c5e16a50580a04058202814352fbf01043984396f4cfc1e0745df4d5b39efef15818c6fe4a808fd40395cfa2d6d33f5416a83ec4f2e73e29185921c903023d4a0a12d96766043f9eea881616308f6ef2c752194139ea0a4196f5215645790e47790ecf502647b9c951350e59356ef20c6e44aa2339fce439fc74554820ebe49075f21c5a6cf507dfe7cc574781796abe7a0a347c75159847f5d58d8cf9da53be7af59e52bdab44f95a5d09ff6056b5c207455f3d04ccc34152c0415647e9c1576f1c9847005f4d9e831b62e5b88ca673aaaba4c3476241eb83d8c43551e7541733c4cae03684be7dc3da78e7466c2091897bc1c66daa9bee083aa79a92744e8dd79f4e74eee944b735a9d3551d31b9757b854cf7f9744e75fb3af5d031f7a95ead1593aad97dbcca9274ad0c80ebd60bd494b1310f0e2c617091420d334f887ce083106bcc90c31a3198410ed88da11a72a0e10d2e55ec5085053de001e68a35d2a0f2858d2bb88d36de083460e0450a1b7af0c11b475c20430fa62053e6881a98797001eac6ff21f120c363ff90a8a0f90e4707d99cf276ec19739a33107b4c4db1508b9db55ab31229da51a8a0fe6ec8cc32f6bb95ccf4569eaa91a977757a4629adb5522965aa73e69cdd1da31d638a796b7c7f7140ca15f07eacd0393d7d095da17f40345e0e7408bfacf15007f0f6abd5deaeba503faac01275b11dfcd2031a2258421fc11e6dc5d369ef302abe93454fbe5962ffe6ed2ed08a0704dede2283376629ea1f718997b305f6686fc9c43792b5e9242aba3879f8ee2eb08408c6ebd1dede5b3c20518ca7d3de5f3c9d1663bb566bb37c3b19d0dbc7835f600939af07f496de33489900bfc4d592fee129345405b646397bf87e3acc6a9f1715feaaf41a3915bc46e6cd2819708a5d41e89c05859251a66e5a0754bd372886de0d5bf26861617d3a70e537d46f523b4f07facd152f1df563d80e156b804e6db037505ec6d455fdb4e4efee55e7402ae97509bf39d5f4f6a1cfa4949076aa1f7e1502350596ffb5aa6c4e6088d09f0779fe3a219590e7d1819ec7f83eccefad1ef45e227b105ed7e5d3218410c20b5e375e8dadc8b2cc6ad9834e53f887c2bfe98fa3f276cc02ce03f173e622b732cc6987f38d9aae39bd9d3f1582ed78f0d8d97c528f3edd044a7de39e3f1dea26ce720ea5f4c2c7416898b7d769810bf3d4cf572d0c3dc40cb364eed7095b679797380debf9f627fb49c4ae9965ae790a6b0f8a9cc59bab572cadd5da7a1f4334eab322a883baa7c63071702a8f5e451ffa88735df18a31c618239c1ccae2ac78cc297de8e58edcd9611e99edc81de61902a57c46b8a1772939857a667a37686c75769ce9113a140ba9402a900aa4428579824c0885776eb47ac93cf40b5efdf5aa88c49f1e7fde15af5eae98673a672be65931cf90c90d79bf81e93eccad30e79297d16350b4686c57bf68b61cd50f4d3f3468ba7b5eb34fec0f4300028717a2b9dc84bcd0ed7ca7c4c7c7232b6c60dbb0c622e646b2b771d10c16cd74438f61905b618525cc63e4c806ba77e69969add6a652b44bd1548a7647253d2fb3d4141b79c5fc8385bee5522263d8d1637cc8b503f5ad430d610e59fbc7ce7e447d4c79bcdd8c5e32954c5b69731ffed2a6719a45a56e5ec68e1c6b09721c381d8ef6d3a27ea6dea9c7cb5d9dbe95b6d20dead7367f69d26cda45659c3f1d3a84d48d104a218e1f51a3d8ce1d277503a7a4d2499a3469babc0c923f7768a8324cf769d71e9016a06724fc2479da09e977e408afa411d84920e92caf0f0abc823ebcfa79406e7e9c37ebccbd277accb9e8db5544d8a7d778b764c70e54f2f974a833c76ee3bc1569b7cedce6373e1dbea7d35b9166975b11ccb31a0da7661332f4a61f76aab27ef2f75de3365cc7a988605ee398d75c957515919397fce4252f7906b51b9f0edd28ca7b0a40f98933b9e5840cbde9e27025ce7240da4d615eb91e5e65b71b7aec9a70537a40201398a65b55b6740bf0e926a4371586aa6067a74fe9e57dd1d9a47ad02c92e4a900984080c7f4f9f77874ae3d1de8702b3d9de705e6261f2b1e9eba61cbdae381d5b1dbfd38dc8a54c79c624ecf74d1cccb7981a8476f4cfa6be7544ff58e84e95d9c3d3b9863985fd18d1bddf0e7c3b38a2cfa68923665e69cd3a7c733cc037d07f3f6fabefa3353f4b21a0e7a4fc9641de32018688583556012afa4f487e5c0dc80259cbc7778a7c3690ea73c9ee2504a294539a514db81aa8e79a7034529a59452ccab77392eaa2b1747b05a3d32b046335986f97cb18c241a1a020c68befdc532593c532b566b3cc33c350eab77f10c74d8f506317c5f3c2fd0c70ef364177e81c35af99593df5ccfd7f9cdbda39f5c67829f59b350f558a6c6217cd75c2bf55886c633df05713fd0bcb622d985cdde26af25afb78b66be9d07ce4d8dd55fbd134ed31503e6cb17495e6a67424fe748ef7c7c9dde984fc74a37887f367329c232b0c7ac0edd72a69ed4773dccf5744f4f6a6ad9e46c1c665efb47b535b7b3d8ed525c579fd5b80d3333f3c6b7b3f10b74f9e6d855e35dfce91a57af0bc3a2998f9fd19dabafcbe305ddfab0a9b4d570a6d2567371f2ada9b461527ec764525cae27580c7fe1a42ed3b1eb7627b0e1a04737600fe817d799e02fb7a6d25633db8adf9029d3547eba6d1c7ebaa9a1fcf4d2107efa76e6a7d798e0a31bb0c71c41ff8055ba95f5ce5332fe74123425a6773b0fb11a54c63111af3086c239d38df8e9a7dbad88702b3fa9fcf492641c7efa762516dde8ba9daf3df628d5e5cb55e2e3b1cbc7675e2f76812ea75eeb75ebede2cfebf217dd8866ae1addb081393314bdf6d1897ef5af45e5cb533769d80ff5e9d2e3f438247abd40b30807d3f0ab2961a36bdceb1c392717ea40f54aeaf3568e9b0a2a2d2a55becad7d60e51bec503996fc0bf160f5b6689524ae190ea5abc1c6e81694032cc7339f5de82d02acf7186b2e1763a677ac6f5f06aa67835dd6b8c31be18a95fd02f1a63918c524a23a544aac78da35c6a52280652ae73f299532ebb5d1ca239f518219aa1333054875ba098ec96f16926b4ba0f5f9783d4218499438765984773e89977b0cc43186f107f855ee1e5e80f6ca7fdd42c8a79665635a75c67b983417fe6bbe854bb2e28a6c2329749fb30dd8a649b47cfbc3bf955e3d7ed9ebdb7abb941dc9dee95451bbfbc0b8a1be4e6528782eacdc5f761eaedd31a77e1165e994c5bb55e4db56e9b577b3b083522986f5773936797d6e261ca675f2199af9f55cdc4512fd12e88fdc2bcfa9539f5ecd24c5cf5124c0396e0b53ae4c23c2468516024685104a9eec318a7d5aba1b22f9a76bb13609c7cf95ec47431f2e504266ef3124c03f630c1633e93b66bc64b92cea14ec604300de7af545382832d5e5147713e9d435de3e2db70afa94f98062ca1de0e05efd704aa67f7e2ba8a610eb7c020900c76d5231d4d64ed90d580ed9521acb1ded884df0501fcf4f60ebe0b02f89640d61c90746d7a877530bc4a02fdd3fba7efbc0492ce60b67cbd5d74e95a14567a77bb27e688efa2774d01e93db05d3b36bdb7145ebc4df8a343cc1b07dfc121f0e577f1edcfdb0d6d299c5efa5421b95898c3673558983712ba467c3550819c390831ff6d481e00a50afa6f4312d330220debdf86a4871833b8fe6d481a4c1b3b60ff3624042842228bec0c521936d0fe6d480090c30ba808b1f110507cb04515a57f1b121d61927842c6f46f4302d540820d2c3ea449c3c2bc39fb6f437242230c96223c11d2221d731f6eeef493ebba0991f8d3a5ab84c86702d3a88c3c812c8c490532c7bc497699bc64cab8ad84952ece68739f2e0119655b86b989cb7cdb7c724c824c2642808c4cfe5e890b6f945debd57d5abedf3cc35826c72e11d6447a052c4724baf04698676f43724b44186bbbd80aa54b84b1ac679708931c11057c67b4b992212b6f846dbe79c695dc722647624d9e5da2378655721f9eddf51b4581000b051d64783aef3b2e01051d885e0f88714298cf30c43a39d152cab2cc88e63e5c847a0349ec124916c6196143ffb621404698bf5782823722922c4c4792b753f2cd854a177b2fc9fa636dde59d3e96a91ee9d0b36afe46d81c5064f1da89dba9096d739f60a2db944fe90a820fa0c690a343f6f1091fe76176a5e492433836f5939264634279a97b620469ecc30c4a241cb8c5b1108fc9cd04ce7b09b3838a573b873322f253d7be90ac1a44ccc119624a608164df10e1609cd30c4d222ddb5246cdf16ded1337969be7b437820176c5e890098182309212d473637b9e90af176cdf86395c47cc744db85c22ccc1f2bbbaf7380fa020d19fa761750afe4022e98e1db1fd21a5f3609b497c0cb7725e0e2fb094703b3b2ab6acf1c4a955c931ffe760b3c56c6c494e6dba11b45f1c1c23ce3b04bc5f33a5419f52583dec130743d74785978636e0743abf11d3f3ae4da2347a487183348d3093db3e04a869230f37d5970ee8420bc46348bdd09229012a187ad294f7cd01bb324f635127a0e8329298b0c8858f43285af934230c64c90a43c3043dd78538091828d7fdb1019a628537760fa8724851852e4208b4c0a2f2dc4163e93f1e93c87dd49220e24afbdf50179f1e93094285aafb1ef548ee199a1f8123393a625c0ca081465bdab4efa24d24450d0c98f0e2449d02e91024f1d8707bd360c51ad3bad1becf0d7bfd60db43411b23a80561759fef4afd5450fffb621150d3a9d527a83123fbdddbc4a709efa4dea8d2ef893522d9e525acf401d3da5ee8f7aad67bca359656cc6a93edbbf56d2126ffad74a22429e014605638a242a3688f2efd4aa819937bff2aabd846f1fefc7fb6121f0ed19c717b36ee6a9bdc56b16b6f3f13e4c50e1018144df90630f50837de3d3810c9ff6b2a8b63eae1ca5e428a5acd563adb2ba740833c83c902133330c82fcd5b9c9d095ca45e5c242af15d25abdbbea75e3c725a07c10ca18bb2594af6e516b2da1328147b08aa7cec305d9c3c9e4cdbb425aa0cffb3aa76385517a0f0969810e03c6920c253bfca979962de3e412df1b5ea5b4c83cd11d75c3636311e0c5e1d52a084e848f4b9d93f78a85edde918f659e19637f5fe7155b148e75e6691f3c82f8f015dc7c9e0ef7f08a1dc7ab16d684e83e2caae38deae776ab2e1616d1f9792918ecdd8aa288f987ac535876e880e8dd1e39d8108cecf943f134b1afb585e8db6109d09fd62f3a37793001fd74a037793001fc74a007d56fc2650adc32e5e94ccdc629d2dbbba1efe44fae89cfbb30ff27d724c9bba07d3b8c0f0e791a8dd78eb9e281ed98a99b98caa645596d8b5e8a3b3e78c53ba87f95df3bcf8fd0ccbb50f3d2dfedda630dc3c6efeea207614944df2e8f883ec621ef07b4c836e48129226fbcdb4fa321ea5e17a2ef27889e49a321fabe40b61ed8831dc95e29c82cf34ccfde53ee89496a2161464bdda43e9aa845cd128494fe6497b66d58493355dbe3a4d509a646abcc33ab183ab41c5b107aab15368746dde4383a34d38a475c31062f0861a510c219a13c52ea3a90d54842573df2aa86612750bc5d04aadb4fb436568dbaac343b693e2c5074ea9d8f19ebadbce20ae6eb7d4f27bb472f7fa4669fc7a734e35e8c5e4df01d7517fa8d4ea0b911ed3e19223a2a9a12058a0c3110b19e4079608e60c10d57a4f1c61360589a4311f2000fe0888308454853851896e64230a4a9e20c1ec0110711d2b066b82b88d272c10aa2b45cc0d27c86219676dfcbc9a84f8eeba8d7237b39e6d865f2c203c55ac49fba0f23b1e58179db901b5dde2e8d91655e618cf70259641e20e92f46248a9e5d2231e5d99b9b3ba1398122ecd3250704d961f51686be5eb8d5dbcd14eacf4bdda7af6f2026ce847a0b434f857eba0f0f4d00fa24d29d02fc76224db4dc0834ce0334227abfaa3d7eff6be3557d3130c49afe5a3558e35fab06639efac0ae9ebc3468e6a5425e1fae31b0b09f937f4270a89bb3a851b47b53f9f6d50e6c04114f072c6068748dfa7bd5e5d55f46027bb36b11f3edd1db598b996fc788be7d7a3b4b2c5eb0f8f04fca8979bd1d567dce5ae7acb3ce8741ace86b5c73861a97e605d2bc26d6b46b9aa6695ee33e9c3d2d6e3ff51aed701a69dc8ca2386bc785016369a17e056125d853af4d8b5c981f31e531cfbca963dc7529f7833d360401d7dbb8845da0ccb12ea3b03b98f9dc7e32c73c3b6d3fedd36bb8396be664ed629b05dc09208e08430d2ab02862bd97436f87393bb6fdd407c2bcfdda86601be618c64587dcdb90e8708bce9dddee7ae99b67282ebe60ccc9a5d32fbee4da0bce7cc927968d03cae1dbacf1c975d1338f5b11ea9a5f9b90e8d52ba72272f2e8278f57a5c3515ef28cb7a8c323d074a0936fb5cee8efafb71529f9bc2a1d57c8d05b57fda0fce4283ff969c8d05bdf38e9912b79763be926dfd13bb81c5c6ac575435f737bb88be270b81d5e5da0d2769d5724c02081a1a18fb793db10ea926af272a17702c335f4bc614d9840e0352af4d54d9a93af97bf0c86afd8bd86bede4e6af4fae015e4b68e62af7fad228afe421d7cca8188a47fad21aa7cf778d01a22cb67a6e7fc63ebceb30e647678a7f20698a733210523ad21889e3d042b8cc004145e133f3695f2b77ade57b68ae0529db09bc6ab777dc88a887253c6668c121738ff5a3130e211878f2c5a3058e2841f9050864d97284a4668453a6481f2864d18ad8b11a020418896119416963224d42f2cf4d40e1d395036352394e414358c2a468d2d2c54a294123eeaa5cca84718a78411a749d094e82804f540d0519fbd0744e7abfb70163d767d3b18a2c64ea6cf306f0d106055af909b11b6ae302123bd443e6fd71d35cacd792b0a4eea03db550440f761aa83f78424e0a13f7f3ca2f4c975432ffbca16861cbaf46e97724aee7d4f2963ec66869b10d85748029e086f5c0723f4068a2e04327b15d9f3223042bcf119e790ca81af40797edc8b3ec4cb17deeb85a2a543e1d02a2ad34f9ee8d57835af58f92ec5d622bbc976e0d6bab245fbb9e14a96df4050c59397264d9a0c3ae41e2ca9c15b186dca226a52c8e8c46c2eb41a514b2db8341d3021a2c0561043789414218820ff88bcb10d6422af4ad501edec75c551bc20e07966e21a2cdcc108cb34b45a30e56b4de93d1d213804991a91a19a0c79d19f1402e385134e87504a19f98d27c01cf1f17a66fab1d05f2b0830ff8424e0e79432c66ee6788524e089cc4d48029e2f13a1ef7ab6845d844c051b5f7c2d20acd07fad20be680581f428d80a82cba378c544ad2088f8e627dfdd0a82cae3704e0bf12ea871ee8a082f720c6334fe71c339d429bda1f56a94a27845d3d82ef5347593848660044b825ea0443c6859f9e25f0b882dbf807f2d208e3ef20f947c6f346fe41ce9f2c61c6c472223cc73640558027ce94942c03c90875d3a8c0284111f9dc304617d78f556d267eaba6e576bbc9db5a9d4bc47f2762b66c89021a77e681ce1c32220d88659ba14b511349a914eb720f3aa88b44f1fd23eeff4f1b17b4eb138f42f88b97e0f877f54e1950a8eb8700ed35a83ed60111f75619ee83c1d17f804f34c2e13e2005fe81c3e595ec1a7c31e75b05dc3882fd620a2995826c201b79079ad1e8640a60cdc42868c1a37291ef45aad5e1cb9f792bb6fa7640bea7784e39199c0e7f72e18c384617a91eb1739cad8cd31cb20c7662d882791faee8567ef6caada60eb7f85444e9434ac4e198298d5192cd4b4db05c5d5d35cbb38676c87533b47d36e97b2d235cde116ef2d8836f9043e218ab3daa35b3ea13d7aea26abd3e9a642d26e720bb49beed097ae0a09bbc92dc06ef2a1d7b8cebe962d79cc37cec4bdced13c636c7a1614ffdaf2d3c298710442f6703a1b67f8e7468e7c68106c7d22b85c731f8cd58c1d87e37b7e5dae7906294ff88010e0e9b447ef4d939059dbf9b18a2b5e3510a6300e2043b0ddea1be7e9b457ed76ecd44b5ce51cf6cd7dfa0a8a9fd1abb3a01851175fccf5d959c4152b9d7d151f10f874a0f38f239006eb83420a2a2409c10894304fa724f57e6405e6e9ae167f72b1e260bb77c4734395a282121c79856055f041c1486c29031fc83cf5968fabb700bf7ad65f9eba8a4874dfe1b4cb531f9ed0e1dd45b7698fdcc559efa2d72d880e8f5c0e6f4e3339e6f1b263db10ea28af1cca6b38200c73eb254ea5c3df97fc3af9c5a97478c9dfa776786a8763bee3aa74b88a48c94f9b0e8f1c6a04afae831332f425cfc17599574771361c508d53af351c50f5d3b5bcaa3d25a0eca6ae09bc92f5def02a722d90f003c57ce4ded3a9f1bec7a3fadc8a50efe83e2d447b3ab2f474a437e78357d2a5c328d21f8e44f12c834990f6c9c4039811f938c87510c62a48181202be57ebbac2d6d8b47070f4d0377f5b91ebe630699ae635395c3b6d9a8d3f376d9b437f36db96656cdab82ef34d336d3b4532df5c85ba4554d92df29ecea5993cf3cc847135b73b6d2d1c6c69e1200b36746dbe4307ca2faef3bf4a2dbccd4fde596cebac5747655b4d069439f41c1cf4ab5e8edd6ec96335288b5d548d3fbf6cfcba3ceb29954a289f156a7314ca33579534238f393fdeaeab509777491ef352e976aaedfe5cce362a2235eee46bdc4d8e71a70b9f67455c88ccdab5c57c66aea32557ab44833a24eb5a3367efce72976faa222737d552e68fd3b86ec96baad2fd395dd37d4fa76aa6776daf5bf21ad7f5d5aa89eb96bcc9b3f77eec7839d2df8f6ecd6b97e4db977cf5ccbab6dd6ec9b756b993679cc977bc1ff2e5b4cb2539dc8773dcae3aaadea048eda3b8ea355755331b2e88bf2f7f03cda0f874baac7f5961bbae5e56d8be5d10fcdb75dda999573fb9a9bb5e15e69a57cf7cabbef39b67aebaee8f3f9ddeecbb7cf3da98b7a65d5556abe6ed15f3cb5f0d8a964d0f7dbae58c07b8092992f98575f63bd5f4c92e1566c48b5c5e337fbfe4a9c530e84bf8aab2fb73dd771f757855f3fec40b5ff5e9cf0c197e5faeeadd1fcdb7cbe3c6bda7835d5575d535affba3aad255b555f5fe2459d21ee7f5793acfe535f274de916a3af5ccaf2ea5a2f707735655d5745575eaed5755ef8f6ade9ff774da55d3773ad2dba9e6fd71f913efbb762883d5cc85c585c585c585c54e26e5bac2c635cec64f7404ea278e044fe5f0d3c9ea50e5b83ff69e9c9e4ea713d72df993f326a488759453afd1e135394e39ae4ac7fdb1d4514bbec6e9ed963cf593fbf489db810a8a7f72558d6b4eef09e5d651235c575c57a05040238c80b2b149ed088ad715355cb744a55af25d507c94d77856c30105c547456bad0d8abe044b02c3b0a8a427a9e769c63cf887f5d59b9c52b71c0fcea9f7f1aa9aaedfcab5d6d8b7b2c6dd253baaf5763ebed66a6d2ae5d7e58565795d927d8064be25d7c93b6b602f8f3eb9c5403b5d900f69c43c46a718c5b0cb24c943a759c5710ca310c26ee753bcba280b3e1e483abd9eb01aafa2c7962263e19179a0e99e79fd0c6a357a7d6b7cccae9247a67273660fcd5732dfc128d7179b719a12765e578bf33e161e998f57c943f37d2bb3131714a9542c80b5f1fa99a95a3f797bca6b5b1bb78e61369a635cfdb711f9f1dadb84d85c253dbfb9e6189759cf4e5cd7f327ef7cfcc957cc63e327f7fe91638777a93f79ed1f23f8c95fffd0e127e781f9c947e05ee7a4b8d73bb8d7281f8fb90e2e07e79d83b90db7e295f513a7e4253de6f6865798bd295e616ee278f00af312b7c3b1d0f35a7651bcc23807f3197d3c76a94313c7277548d7b3b3fa76fb9b5bceb9d24df58fedd6cea13eaf3be94e64c886a59e0694f77f2d34c69c4ea7d3c99ff519bdd36c972a5da8d641935b1b454a157b9252e514454a15d8a548f627ae837f3ad99a9ee25754d238f306f8d74ac30b1a5ffc08ff5a62a4f8ac356944ccd2447011fc78a19528b084f714563939f458ba91a23c7635ae79677d8b6ee21effb07193a32e8fccdde5d16db8c7268dd3de9e384d096bbd3b7985f6029d6e07dfe456f34ea3251ca0145882e90295dc7ae9024d976fe105e92d55f958ba500aec11a38c34baf4254a521348fa65b5b736ed761362aa907bd6724636d19ab8c7ab68c3556ba14dac55a4549152454a152955a444cba72a2360d837e6df61d8e3c18f3d9dbe52603b1edfce63c7478f0929cb0e8f8ec884c0df61db73643a72d8e76fa26c6ade7befbdf7b8e832953a59d5bcf5ed77f5c2c1a69159fb6266b250b4707b2a8bf08df89660803027dbec8b3ea42f1e2ce4c7498eaf917c5bd4e0a81d799545eebddbc669d781edb4ac35f8d8a1bfa31abaa8e06502f986afd1f48e4659179910f8d829bbec8b991822feb5ce18f309f8d73ae3cc370bac45a56e9887cb43ae09fc6b74f9510d5d3e839a51b550b454c766d2e3895eedd67e71aa76f8927190515bcaa685a2055e1a9c55b901ec3c629af817b33392f871a682182e12a7d6e8a3d654aa8f5cb63e32285d6e1368a77436a5153e2e6d4386e6aba720395409f3c4c3bfa8d8ce3e84337808c36c79c884e395038453761bd98b1042080d60a13fc84456029b3dd52ac7445020bfbd72dc24a87e50eaef75f1f613e1b310b6fdf40468b66d937590417f7ec6d1bf5698a3ef4cffc224fddb7e26bcd03e0864cf46678e5f0bd1044fffea2581c07e78e34644c8106f42a2b3f304d8dbe746a4037fb9ccf4d1d9e91624f20dd414bb18bd259d52d22965bcb6f95a8185344479e970e96026d3ba9e7f9569d6d12b54224c0ca615dabc3746b5b61059bc6a78648461412b0c1051de1b8afad70af3447691c57e4450dc80451262bc11052b26f1d153644433c210000e1cec6089379a10830c2158b106af59c147b565469616182eb0230a29a820b92b3fb460082a7ee0031747acdef27db49381325318218311645c9185d5570db1fb88ddb2a7d88c193ca7bcfe6b99f1c477383b6e5c3440f99e151c6024be5b7d0ac2231eee402c021043186fb8008b1727aa18bac1063031c881941a640083b5b4c01051e1a839580307d3e10fb9946f0a117d735d19533c24faedc74ae6e11ffec3592a3c205dc61175540a8a964e2be2dff6539f7cbcad5619443cd12a838be3c48be891f1c5185dbeb4c620d31a03cd992a2d32c6b4cac8a1550614385a6140c1d202e34c2b8c2bad30a888d1fad70ac388ef6e5e8631c5dfb8741ce92be93c3c324f94db0dafa4bf60a5bc64b09db53b4dbcf72630f4aaee8901af504ae950144e905183278e580194385c20860e19e59046d1186068b0022c171b454873e5cb974ae6226a8151c667ff5a6024a501a3e8eb3f325aff5a6444f9ee756991f1ce45cf2c0483f1c473dd86d4fa784c576125b7e93eb0afe50598ef5e1777017ed73f9dda1e8ee5edf1c2cb47e7871b11f8f1eeb061a7b777abe7f7def3312107d67f4e8eded1bbbbfb49d67e415e75511fc1a2f6eaf4763db7db59790abb9d850d7bba9e9dd50a9ae99c4ea5ee108b81bc6f20e890c889becab7901678bbcad369e7299d939414bde9b393400489a00b4122964f10fda962874441f4e7050ac297c94bf3ec2ed0e7db0dd57000fb588f256f13b13a080f3d0ae13905e641018c1edeaacb0342795000c387b77adf3d2efe08fb82f0efd98012042f69788043872eacf69efed1eed33f5e1443cdc82cb76c7de9d212e38b34a816b4beac41e34591b13645ffb5bce8c18b28378c8172cc9831eccfbb15e64e38275a8e602677c29b43cca31fc13cb2637edd239af71582aef9e625bed6760e753759e0b1a0f00e3fb9909623d6775817d2724487b343e8991f819e650efdba47526eba42264f5d235748cb9193c32bb4d339d44fbee39eae1014e22be49d435dc77d9d1339acc46d57488baa02d1313f122f17ba47dab52b04a7740e855748cb11eaecd811ccf90a41a23b4548cb11e87c8584b41c6187576873be42257f9d53bd66c7c61f2b873f1644f963e5e084b41839b90de72473cc6b38275a8e641716750e75cc9d68459caebd1d2c7aea1a177b9d0869b1c06349ec3a91fe581ddcf2d44d5c3bc63d960685fd03f6b5ba40f3d9e3c725239591e91678ace948204bb24c43cdf408332a189c1e253d65b0d04306945dc928021d76bf377768b1ab18aa69b4d5c51301a512a8244d55b5458e32453323000040004314000028140c0744e28050342416e6e50314000b94a0446a489988c32cc751903208196308210680000800ccd0482b003e6198211cc71749f0ef63aacb2da0a52367bd48667896351781ab0237eea66ce75f2237adfe1844347e9c71c8481993d92f76a471eadc39f11f4f9409c3a8a64c8c6b65e7509b3511596dbffe2ede8c40b4883bbcd3440085908fc3d1a067c3260e039346df1ade95aaf0256bef702024eecf2ada2d6088647f7ba5b351a3eacc095d07ef27b1f0c872214190022c15e94dc711cc18afed05cbd444dfb9976be15dd0104d1f13f3e55a20b81a71543c552e168996b0aaae7b908c90e430181da5930bd716190bfd4710a1761001eab7b63e24f1397161e6a21c3b1c66304c0453122a4eb59dd45b054a99712ba430b7bcd72f12f342581a5cd1f658a8da127de9a396ffa16655400e4d405de91356d692df908deaf03eb3d46f0f97700f537c1e7ebb49fc2ebcdbb50f1060097354e873cedac64ad5ddb324c5b791539d8f7b1682dc5e50375a7c67119452149e15d2e6af0383c618ce5c245f2636907d7061b98d4449aeb66a92b7bbe0a88560202dae977a6d74b187917b47c5372f35b299d581efbec967c1314b07f23cd7c2c4c67e530c5188f79423fc5d26466d44ec42df11a40ea8929e526a06da7d81f65ea692cb2460aab8c596ef215ae8484af8c92e506c9d58131bf7f5f491e341f3271844fd8346b834bcfeee39143d15875fb5a0a765ea5bf9affa743822d69628867a9193535ab8f9e2e0a08aba57fa0ccca94f0be49816a294e7669e05843e1d2d3975cabda72ca70fb14d43eb37f4a410a1b4af2dfbd249f15718dc310883ff2c5833a518f841da1bec9ca33537c7238c60ab2a9513069542179b79255f6690fed842241b08e598a3d583030b91fa54173d25bab0c6590806472d7889cc0d5b55c5b0ee06333c2a8af6d4823ee16ae100cc89790a880ef703906ac112373e66a552806aa1839a034d6b218ead6de6720d0bfe892fb3f6e7af765a0b2ad7b49b850e639093e72c44ffd530101b5344ae0517b96470972a2e5c0b947dc9808a80856c082cd8e7eb0a03168abb58eab04fd01320281615a89b458568e5afea21ab6b40a7fd88c1c8c5b900887f58ee1a0aab0c759f8acaa02797cc38e2cd9d969ad4fc1e84b13517d6d096aa13ae39e269278e0c238b907bdfb2b4a24152ada34fd1ddbccd79854ea32979e0cf91454de7b05d731e698bb97b0883c10e4bb003a5d54beae365e414ec808d4eb6efbbaf954a602e0a364a4b684c1fe2ba393130ffe8b51a5c3da245d2475a5f5cc3786158e164702edb6991766681f7ff97e942362552f512c4c4777c48946ab3e51c27352613d3fa407148d467faf5d51c639a45eb617fa26184b055a999ec95f5a80444fa0427fd298b8e86063d8f7290c3b64dc52e4fe105f888947423f02d73cdfeb0e853f66b9103beb067af7eb737c5afeb90ae4499b64b80b2c7cef5847ed27281ad1c0d4f3d0fa141a407ffa07f66d42b562019e04fdf27629e4d47f5687cbb61bc2856af50f0c2d530f3706596084a3835f51a48d36fb8f4652325ddf37250e91f63dc199bcaae96a45f331b0e437f77df7cbe2290d4664f1be4e74802580e4347aba8d350df210c493f7be3991fa72d41b387f1af65d3150b1ffa3362a1e5302e1d7ea1ace4c340fdf78b5f0ee93060350e1996e9a17b25408ce1874fb565def3301c65c5492882b5d48e6977de87e32330040e5b2cf33b273105154d86e3b97b57762f1cf8e73b524c6985d34df7a22d303bc5de3ed1c04353416d76c16c680fc54bab26594493ae005a58b426764b51e51f1c31ef034b9232242a92d0ab6283aa4fa5858938fce22b6f92597ea9b81a88439905bdbbebe26c3b8007453d83807656d857fbc84f774d911883b0b6c9aecdc710cd464da2615c0f642234505e9848218c643fbfff6aea68bfc379d85b2b5854294c07619ba95d2563db06dcb29b9ed8d9844130775e670741b03efe7857bbefc7b5204cab35775e417e1c40f492861b409b67f9fc94ddcb2023810ec10cde1f3acb8080be011897dcbc4ce109d5dfdfcb05dc8dc66406653c4c8658803d3493046883e39d9c702479da8a4281a4005c3ff912001bf6a23da60b00705be1bacb76fdafb5c5d83f525b509d0a280d21f79420a91b8bd6f03a497322063d011e9d1a45db4096bbc36e3364d7a606fa9542caf7c099f636f72274ae783818611883927a980d52c6188710d1d2e1d7916b12ed3ae9b7c3c3917f0306711729c59cfdc9a8080b51b8cadf50b4562e44b541f0f16c5e5669000921f0ab994942b3014311f263a6d099d865b83077c377e40f59de96b96c357dbaab49c75aadecb5555add677ce7847b89859d23e52adeb4f596518279b1cfe5c8e41a0fc5acc23ed9448c72e5605358778361ca595b7dfe93090d786e8311444913fa660e737b958d99e0d67d95ec923b7a766097b7ff1caa68160b6f1ac09aaaca53712e2b76b9dd36ff2e654a42ce25f9ff5ebfa0816d44611f55beaf977fc57f549c1f1027733511e2864189ee192806a1c9411be803a1f3111c20aff73c50491aa3cc6f41f107a9b9622fc1a6df7d75d8fd437f51e20e2e803ef049627b79406fb70560532803a28ff97f177fb7c1335f8998bf8603652f31fc10cadd7cce0a0894eb98e4cdd45ab29ca8d1e45a5a0670e4796878b5943562709f526be05066d8cfba4e04346e5da989b74d47e7adab024a4014b6c98b68e3d8002319913dca7b56b81aa4f786b2bb6a30e44f94426efd96b7a7d9e5c67a6dcd30157d57273e014c6874efa5fd9b182567ec506d1eea7bd91e8f38748e37a3e44548e5132a318fc4de0a019bf484f969cdc4ba2da5517456daecbfc20733fe9beb094e9bb9c77f006d32bb3f01b84aaedf709c748664821bc9275a88b02eea30300a59160de09a035e961e468e5b6493f57bb61e11ec06031124833a81a8f89409fad71a1b399a52db117ecaee68cba5a4575efee445108b5815f2f4370339b6c0279709b8aedc790c75d2ebc2b5ce483e2c7e30463f39a58c32ecd0cf5f256539c935435230d54f26e64b027ee494deb4a355366e055c798bc9911198d77ec10d241a61a2033cd30884d94687308fde5bc077617e6be8dbe8ea171d3e2825aa23fe3f138798d3aa1a5c374aa9ee330cc9007f684d76e3be467faa9ee256a19778449c490e4f04f9a8bfe408f384d38057f375bb3d2e937ec951088c358fb960f6100efdeee78d25a8de8a0c0d7ec911a57fc2013d83d09aba8bb322bb0a5c45875a0a82b1daa0bb5ea84ef99acfb39baf93ae407fc43c8544095c5536c6faf810edcc9491a368fe696df6b655a888b9a2fceacce03d3b5e29f38cd1a501725c1f1ebb254782fc26d15894cbd15716e107e27e8c7ed7ddb0938124b0f9851fb6f6117e17b8528dd6f61bc0616242702ddc728e235fe446340082c475fa5ece8bbc45df4e4bcce3d0bdff290c29b1eb7ab88032705292f69587240c036c725cf4f53758d84ad1404caf5ebe1d7c761bb15a4886c0b3cca7dc11de39f16cce0d547f0baf30d79e50cc8e8de793c1bda97b5443e0dc00021d8c393b9ee409adffa4554258ef8d960d47670fd8b13d3bc18ebc8c0fbf4cc7aa9266463c6706f9403b93e24f222fd7c8d20cf31e28cf2db1ec4064d744f298711899fe0f8fc379738316cb03708533ccfe687756c1a923b14324fd41308ca6d762aa65f418722d02ce9d7cd399a8a99b7705fb0039d973b0c9f9b9d00950dacd3fee7a2a21eaa04a3272641b959cb4a83dfd2d1d98e7f38bdf0d3b268e0b35500555b972f61be89d399bd6d97b27bbb412af63d52c386bfaf873b6905a1cf7f1c2fc74edc14fd9d374702c032798914891a69ee47a8617ec5ff26044735872ee2069a38ab50ab66bf2754ecf3e6af18f8c0235716e963ada1eea046f9653a2b90e3d5f7b238ac91137ef54665964dc587b922fc4fd0f51dad910837249b338bfe21f1184871d58eaad87a9636668382f44eec07b5fcb52ceae219d2d2e22be07e9fed704c3229f548922d45e4e27933b2e9af7e39e0e8399f952bbfea3e06b39f91c1e2324409d8f0f4ffa0c5af8ac72afe90a71cb5e94630d57d5e60241f0f26c499ae432aa654241804cb6bb00194d74615c27dc26063b295b06937e2c35688b127a59f789c2ea878e33519d9a28b4b92b0f42d3b70763ebab2369f19f4b5a72cccf02ccaf8c21fa28b441619b0c343603361bd96958294886e834fad4df1f0e5c7ad4396b4c2edb221a6bfd80e1e42ff94b573ad83ff34f22545fd68971271a7fc3ccca10c2d51d4493763005fc6a88517e59b9751094aa771e885e21b2b8774e0bc54514eafe34ed21c4292151681dedf5d37151a477f38b6a5681af67c3258c9dbcf9d2670565906bb85544c466d1491df1fd40631563cc42441efeafab047c27230444abab38c63a8ac5cfeda9de6a9efb0ef3b55a093ff1b603daee9d2f0be5d621260151ce3dbb1580095f0540bed93f2e228233a1601bfae968bbb23392df8771c106d897417e184000cfb4b3338bba8f82f07dc4afb02d8e0d97e30243c637ac1c65a44d651eb121a13c188b088632bde05696250bac9ed49076cc148c6ed453ddae3935e90ad3fc205cc0a88cf74fcda8a8a2c926b81ee9e8c1e438c1df872835110b2c5e2714906502b1a6839be068d143c35a1eba0a49b157161e5c24232c1c8e395acb6d2fa4650c23031910b20da8afe43920764c64a93de531bf787a4f4eb8010170cc0576d1bffb02d7eaf9ba34eb95d54cdfd5746a0f84c6587412002e6e2d8492ac57150c13ee07cdc234cc59c3cd8aa1c0168e348a9a52c9a9e708069007c5a5a8e579716612670c2cf68b699c6758964c8a75f8f2a47c27173c1dc4665654af2072b37d2cd341462382b5bb225ae7e0db6d93c8ff4a6dd426a0f7ac9bf5bd4cd7d24eb6aa654b4c8545e16a69d1ff3ff01ea437807f640221a791f73df4f8b1a13ad3b20818dc301c47c939eab13ec85822ff0670900db945ea8385c4597311f3bd6067bd17ac29cd78a1c4f86f610468001c780703125714dc13c7d9a529cc03c875f8292deed1fb1c417574c431ed1cacde80d28aa69950f633c05a381c652e7b17fac1b935e04029f9bad0ca8e059d30712e34784185e0463d7ad6ad607d42892939588c1b0d81d0b95c39f6ab04670fe3175299d18c6692570a79695d719456b5d80583ca4755f1906a1b0f09784db320c59c8faacdfd1463b21393c5c0b579673f2ea6a389f9072443867253ddbe16579aeaa60a2f95dc9c36866db09335d4b473df5b0dcd6e77e8983efca2b81a0b73cc0bbd8e7a476a7b80c5c5c88bf7fdf3220d499ee76279996879abf018e1af76d34b2e4fa365e02e635758b78e93ae272466d49903c3747c0b35f5d6c1d53f79605ed9b2bd2758ce91725bdd599f22257261ca7a9a43f17f5d2c993c927cfc1487fcf08e4f8d57d1ca5cd4f4bd658a0454817058a069c85f3f7610bf0da382e202baeba5a8746b9856149efa5045ee0f8b02df86fe68c06c6a95638a4987d80f90473155aa5874e07554c927cd0c224821af7385bfdb3ba358814c80d9a12829ef7c1ca5441986965b74888f58a6b8c7c10f0d77c0753e1904ac761880ef4baf9b59b3e06390ae3bad2d721c7be3704e930c838b4474ba48cc3e6ca760a5f469bb413b7ba57080725ea601982716137a10cf28e61c7a64e2b9db4815b041541ae8a0ed3880ace590eb9c7475e4b35fd75cea89aea23596e2980c6171f7a037fd76415d3e1ae01a3c66c238f1e4a2b07349d15995abe49bbc034dd7f979cffcb0ab60435ac0b6484a164f0dbb165ca11a2d7c180f83961be0eaba4b2670d91479558f5fb12dbce77e11a25f8844ccea88889020d71da68ffe0a5f427b7449d68b3cbe434ac82f642195b4fef02f800a511fc8cf88f8d0d64a6b960111034352227e4b358aeba4ce25c2795e4abae0846a0fff8fddfc52c50edb2a137e8089bd045e62f1e120362c0909d7e48815832213aac2fd13750a35878e8f3e195ab1dc8c13c91a2d2457dda3f2aa6b39ae9e5c1283f819a548b0d827eef4c71da959bb447392e948eec02f31fecb2f7ab761b23c6a93c3d847717752b62262c40524d2c83ddba3106e34e26efcb80359c9c5ded6961d01b01727a3b69151ab649c924bfa1a68154b6ee4e18aea747d98d0694fe053e09b614c7a6ec22d0536b0164eeed785271d98ab6523386e9d2af5d0abd72db9f426b18f6fa745113be4b0d9035e3eae7f60a7d56f37c1d7e8a8852b219d30bea3c33fb637206a78ee79b74591573924afab890239581d736802a34bd2515be1ef03ab4a119a1acbf8644c08a75b0cb96da031378a684cf188a9a8564244733b311bc1d11c3a6454580e327cf3a143562fe6ba45fb89c3c38beb949b0d8395114c96fb690f409b179146d1209b2cfa299b602ecc9639dcf45cb2ea2e23a2a304cf03f5a41f568c9f7e96d984599959a8b2a287aea9e18336a38ca3675b70316e7afb29a174b12ca969ca23a28814e0553056601f2041d013e63d405d475df52f0cc2c0708d04ad7b29ca5068f978cb54d83679a977d4ec33d50a7d5e171e80497ff25456d81533f329fadc44b99d2474027812dfb5a2af0398e1b0cea6b9891240e77e2f172d9a597ecc4d048860885e810d0c21b7dea34d1455dd00b93f421d2c13fc039242613402b296124996ebf55f02ae6a41c0420832e35cc72822d244006c2f12b5ed3e06a60017c2db09520c65a10045a71b11dba83f57680613c20896e878fe9df9bcc43ec3ab843010b73882f12a9f3c1c511cb449b1a528c6aad07acd1f656a502cf67639120aa0ce40e7d8d24c12a49cc753ea8b110dcf2f3e5ff20241685bf4b334d33a467899e6088d0b8578117b765b7dab73b9c8d6c944d1dba0d781a736874f18e4ec61b836cfdc3707680489effcb0c0e9b36ed03c6c2b9236ddcc9fc53a38a1225c0a816e671489b440100398a4b252a535292d40a7928f51eb8e509940f5b5093a588f8474ad49c930b9e56322069943b0156d27cb5efc0e1cbc2b5fb601f39214cd80b4012d7fa244ac9dd172f0e709b3185a94e6c1ec733ccb5df541a2fa3ecbe1e3a75e9f68894a4483167b89aa64271ecfd1a4677cc83f86c460ba5d0d3073da13b4041fd46602a2dbd1a76e9e7dd033941b0fcd20f3c8a8d033b8b7e0d08f77b6c67f7b713d05da88062a3ddc55295b3e79d0073e01b6ebe7bd593e22d30c6e6d7111a0c7de2b99df0d2ff8ab91ad550b45bd3350836136b5821f992e2c89f88181063f30f431ef57607a79eb22b7e29755a07da33d738d45e683fd48a9d656d0eb61aa21842f8a0b96e0b5ba73ca8a9efb13db64b96dc0f627af9ca1e7092fcd28b7ab856a8c0a3a715d741c03e23bd10f221592ebad0b3ca6e82b67a5c0447f4a16be0992de122a2952e407577261ec857494c499c2476e9ae069ce91986437e749941889975d3f6eddaa89dd2e162721cc704f39a64042642acd6415c7e39ee0aa8b0f87fe45f8d4d05e3cfeb0289529766b4b1974fbd6ce4f6fde01989b5824d15d1dcd9e1066b660d0d3af03eb2eaf84550b3126b3c100f8ae3ec2e9604ce52508e3663983d5c38e01fd54b75842337b2514753e88df1403b5b1a7a23365082543a690f923be736512a1573da2c46eb879b59050742a97ccb26592a19f793f2531fc2e3a5392b1e935219be72d760ea47e22184cedacad9ddb01d5ac111c86241faac15cbd12044a92fb5a260b381e28aa3dacb9b72c172f5167995694e082e645906f18fc8abae0d87ca80671126343d66aaa313db818dc0f8b29dfe221d52375a3b22c409a8416dcaa584aaf32b940304c320bb9178cd1e63fc56c97f7a554cfaa21efd58e381830c95ade87e3940a5ca3051480cd27e2d5789e99d1be550f52f9d419a9f49f2d862e34a97f24bc93f5369c51a95502de56ec4428511bbd8623564b25a55be1c38fa576fe2ea59523c567c94b945b234fcccb1ea64e6f7930ff9e5bb7925380f5b82e8c154446a54db21dce0da9f5824d4d2a351413c2e6392b88f5f2d3acb2d92ce0aa4cf5d62ee05fe15af23c17a453bfbf8e22a0bce848a03ced5226a8e1099df54c209fd0626f94c64838643ef3922c42cb590a7ea3d2a182977f08e0b092db1be1ade9a168bf0bc8541bcd2c9ba9e5725a9747f091a0b96ca6edbe11ee14801c096ed036e57cb49f8b15b408d19e95dbf04f5e5bd48b1a69c1379115fc55ab88e8be3d57725bc036b833b4dccf12f723b5995b0adebc05dc4ec11b01c4d69afd39b686b1e1a221728776606ef26962dadb6b83195b225a1a30811cc133e06dedfbf4359c8ffa8b32a8d9d1c8d258113288db1815fac65877e450efc87da1e7485be12228a4d8836be6650f6c07eb13269e15afcd3757205d626686ca4775cf3771bd835613e2f983d9abf4c6c7520933c0740fcf42471ed0b7a417ab0b2b4c22b8083bd14b96173a959c9386da76ef46ef2df743049967c6e385403daba07856db7b051a32e411d50fb5aeefd8b6ccdb4303ccbc081dc057994d3af3464a402217cdfb1c976c58f64bd70dd7f5c3e2c8ea3e422ad72120e196b31b01654000a7eabfb0e5c57d5be6dc3e51e9e59fe8c3683d95c3ded3f515876338bc12f47b86bcfc3650cbc0c61998df2e51fc5a97de7b90652ada81c7fc5c8c8225472f7f3ff1be8b28512657f0c082747a9957e8e5f4409eafe74de815f5096521b5f6df551bf13d2e05930925db62ee5d35f04382b230e1e593a7360244defd33e1bdf9400d90c1dca3053a13f3f18bbf16dc8b8cda7de25666ba595c2e70822ca2e4d1e6837c6ffd893098961697bb2c9d70309b2e453f2a78f77f6549024834e39c191b96a04bd9f97c84fab70c8a0622d23fdb441c0581ec2a125b945c82c2abf7db5873460033a73ef803f11f28a0baeab80a9659f627c345799f0abfa87967c79faa84c7fbc60b128438128a0a293757fd1d9049c17bd853260e584be2ccc860079d7325084eab095b1b0106713d8dc204df74db40bc255636f72fbffd1263cc2301024de9f9b81d7f8b3004a54e6caf24fc90c6f8e105ae23d17248ee3475affc17b8e02c4169a4eff180ac7fca0ccbb29a73efae5942e59d9933b12da56530c66a562ad5a669bb7657d494229ca0a6c16689599fbeb06f6ce6cd8501f29a392c16668346ab7b66cc0666cfdda3d43087322fdbb1211916f292d3f3edf7b2d05db95ec70502060128d78acab44417601d5fba8008c224524535bdf6cb2233215737673c3e6d76bca0a799cdcf82454cedf1ad52dd390b6867565ee7d6b3f8acc46abf10d33fd446f52a0706f7d1fdbbdbb650c4cd6a8d48076d98a51825ed74788ba10bdeda2b703f23dbbfad6ac017ada6741088b23dfa12ea4f1e730748572017b37569e76bdd3386ab2a228c94a2c3601f1b667bbd9e90aa51333e5a7882c076013878f58532d061c195d7279babf4459cb0a68e09c9717c5484ea63cbc9b06dd4d2597f2f5ca3450787593bffaa6051f534374bbf709cebebcb3a55daa3aea5a72184d4d4ddeaa4a319e39320092a33fa927171dc99f194db63230c244dfe11749744fc2032f337c3ecc89e5a83d4b0c240215332cf82b273534f0bdd667e1cd5b8aa6cd474cd50309ca30693e1419d851b751038dadf931a85bb23b2e29b8f08041b6cd0268bebc6539cea9cfc75643d703ca24efbd8c46c8d10d5061e7cd453aa6f3ca46d3f0f4bfe7def5623505e88edab4e7476a4509cee2c2e7438ac0b7cbc28c1e91ee54f8e7590eb93483164e238b18d891b3cf4fd9ab5b1a99d4fe774203a800cb564359a6b30bc0becb63b05bd9e648d8bddd9d13106c1957239d2a687d839876d7f777b7263cfdcffd753a2ab7bc96732fac3b8c3c498ce7ef7d516c0a78d14e4c9d3452a93e1f433d7443c6a65b34c27a7992f64f7df486a8ad80b73679fcde8482c4323e014574bea6f4ad3ec6d9551c6ff56dc0445eb8b3686b6600fb9f499dee36c11eeaadf27942a44a11e78c4e91a9034b0bd47e3229204f7287512f4842f0579de191063593e93fc9632d9f7642ca6fd3aa25760dc3ba0bf47c6eb20801b5aab46ba3d81aad0c5039658b95673baa9348d1da2665f970aa00d049483899bfec9527a160113e66b83951eb10f8b133742c975006927baf0c99ca88198df05dc0ff41d057b3e7ff01960aa919d808ce1f5bd91d77e89162c5afaed87770c8ee95b4403ef5b9565197a3762b5a27c501d427972c2949aa73758b6c41b93fa80789e0f8686c65eefa2ff5df6b9abfe77397cafbefb5012068535e7f86d68378494279ba8181eb52a77e26d60ab3856b745f117a048b2ec24c225cd95c98eb754cf1a8e91cf273c1ae87bc6e7f04d20979033143d24ab957506870bf66e689afc1528634ed86ff86720bc1f00732492be4a3cf3acb46096e329d8090fee2f2a3dc656a00a4b82ddb8ab5f3d82a67002e244616411db34b4c53c93a43e19ac36bb246e6efb38609896312ee3b5b2844df968d36f8611a5ecb7b8560e6e105531de274843d2a8d5c368b60a32fc06039740f671d528936da10dd14b0a764d3b62245aef8df82b5ed19f55bd05be02dd3b5e026bac1d33bfb966fe1ce4076be155af8ec6e161b33c8f618be2e2ce60f1bc582bb166dfa7bb6cc3342a48b59a3f582cfe67477afe5a6df829617a24295e183f8935e71d5f1133032204e2a94900e2f55f1230741654afa33d18eeb19d9cd46df07325eb5bbc39a978ea321aafeac938704a950c4c49b2b88ea70525c735dfbdd07c76487c56a3ac211889090ca647a6f09da410ce10e617b2c896818031bc874b707258e9cb0775e51bcf55f31d168ac8028d7bc4299061dd36ae71db27e2afd31ed1e42363174d0a91034eb9741393b0dfcf4d57d2f93121ba0c4f4968583e10a3127af48f39554883506b8550058c1daa9420c15225f5be68c551abf5303b02af921b5ec7ad4af306dbecd5745233b852c04b28fd7c82ad5c48611ec28fd8cecdd13a17c2a51638a806e8dfcff7ece9008ff7bcfca4ca922829786e7a750ff641d157322f4aadf7cfd50a21201b82cbb214223f5f780d494612d9595df944844ccebca218934e94bc18c7f23715b1e44457da2ce6ad8b5cead45f64d456317b43072751330ccd0425224a50d0455829c105d1359eb054b366f49063b1f842e2bd56b7616e536e82ffd3183a3111bc49a1927d902e36cae4f129603d0f5aafe41187a5f80e231a17ab16f1b4bd46c910e527ac0c829da3c92b4241052b9d20c378ada0830c77943d7a2a023f2d0dfa3ffe41a3099e2f90379ee2dbb66cd4deefd7b2f4be7b09852c29014c0b54c20bd4b0ffd90b79bdeb8d3ebbeae199824b29c108d8d48a246a0a8da2a415887d52111ecc2177752549fcfd84096076c4e119323eebc8f69b1c228e83a7b588cd84b322c9f0a28ee2dc8790b2c9280c807cf59ff0941e0d948b02b94391fd7971304acc3ae0f3cc3a3cf8df104841fe9d1c8b59354d43ef41915cc22990d9af490c09e6da7acafff1c4918be6447bd664f9447e2c259cb1afaecd456cf36e69dfd65b88e613c8ef1eaa182249ee80aa2a9f1e72c33ad2038c4dc18b73ad3b06624587ee45feb95287bafa0680be83d334fe866429fdb130dae5d5c1c389093af6c17cf5f81254c647c766e12b6712e4b0b96718fea240b9b8638493ff0ccc19345d30c613f39a61841fa1671e953fac561ee4a28ecd055abfadae51bb9f33a40896560e33673446b7d700ae6247757323393e3e1f0217e3f07c0f18246c77358ab4010b009c560f528f8133ac2a8be97615b77625e79b8ae621f4ec3e85bc7120efb313115dae5c4df308f01400c87e70ef30a9fcf0e0b028aab4fcd8e6edb4ed509d59e7bc24ebe4463c8e069e041d21f51e72b72343bc41b859c7742365c137ee819ea9fe821ed81c2b0558a7121aebe5ce7007d3cf654d79ba743d89ed9433eda61b81933c178d9eaf1d0327287bd73068768cd878c8ee39e1bb30fad83ec4253aed6acc53ac3f61bc819ba1f1936fa2e2663cf4ab6715eca41a9d622652cc9be754bc6604a1697ed1a695946486d5d91c6369b15e80669a7747860a80160b8d6ebd3ce0ca9cf08382599d82e618276a01e2513adae94551016b3382949f3ed4509df7d1fbf1e780cbf47a02da881792532bbbc72e25c138c6b1a04811de93e1aa054423bac198e9494df0555b6c6e2b88141a87cad3454c2bb7f20216796a7db97535d64009794a6d9be24fcc92454c8b5bf836cbba3d216f40e0d942ccabb72068cf9c82590fbf4a15978726d767e1e57a018bf5e18c09f129b8a1a1feea4f0bf9f9be4e1169de1945255039f4a35e2dc645211954f81571fc54422092b16ff146dac0a5a12092b0ce15aba0502b905f51f1fff5cd2cd76663c43152089ec0bd694e354921e5d057b76c944bad4c28a1f296a80ace0008aeee8ede80876093f0f9a45bb908067df1e9fabbf71912e5c344ff246d7151e79874f97034110d8f9c07d1d98a80009c8af7e764e86b7c417baa7d8f4b664698900fa5e6a8812e23d0d934e6273462c7ab44668ec1cf46f08438fba44c52e7c667ced905bd040765d4b0b7aead72a27dcd0501b74721d653c1106a5077cf6ea0f32830a1068a4088cd0a17f987b18c2d9c188b971f18c4ed68726598899907467c1caeb858d822eea60051a99ad905375c1ef2680ec8c5ce0aaea219ee5afa5fc5447ea30a299c4662dd9417788bb1f0460ca0294fac9d7c4edaffe6e62213fb529269700355a2682e65a9eb526c306f61c61927236508fbb23f0ad346eff0d5a711be3444fbcae325b6008c425b58c36ce392bbe00d142889d2e8d652c350ba01583760bc70890a55edc52a2ebf8c60e1bfb05387dd77042f1114594b628a9bc29cc829cd527970ef65871917094a1df8c46c870a7cb1c4561c98e91c5a5a68c18fb6873b38a45896d792189498e0ef9c5148255b3c545951f33736206043e6d92727f555a8ae651a26cebb5f4402562521555f3aeb7469be47142d010342bf252404244dba87d46a8c7f984524e7b34c153f40402d559c93736dbce1ee5e7cb58ff67fb7a7de86d790d234c544300b1bd076fd1662a6afa580cf9c83a6472f7a27a36d5e64f2cfc83ce778c88309998c835f7ca8757e7e57c0036c1240753c44c0734a7868413d9de2dbd4aeb0721f8ebc14fa291ea2549265014aadeeb491a321cbf05d8c3c31804149396b52d77d621e24c89a8b1c629d827ba1aaf4bd9696f6fe595edf15ffae016c19f986d6d55022fd1c96c1bccd31a0e48b96f3a51b93dcef737128088713092ac15a05d78d77de34dc9501edfe3a53e7801a039717c42f8cb5b9d40173252a633c31c34240e7fb6ea8c7865d73811a1e03fd3b1d67def1ab73f9212afacddae40076db20301fe1d049ed021c097648622183645b806265052cad83ce319365c1b84e6f6754fee4c90af3b0db3c175c70a05b1b1b6a68541801e1418d671a5de8ece387a1df895b2aed7d1a26df352d9473ceab3e281f80a8f228257c0e47b28c68951b31e9406eb44368f7c4198fa94c6920603aa6c12908e1074a3bedcaf6c492f641cc00604c86f0c37749281d5c063a8fa55dca889316b6d242ffc8a0bdc6f677e719d2133fecd78031ebef3b262d08caffd3c862bd902da0d04f88a608c492a8a54a5ea30cced680600a881f0921972ab78d3a5280ee123ca93ac380653dca50b48462a8cb36b224c4736d64ab751f6a8eb270a45ee639a91c5992b08b25bd320cd00a467b5e181de5388a988f720a1acf503ebc28f7543fb109fcf9a298b015880f8f76534838a5fdb0a72405975135d1076bf76f74d4803cc2ec906c591db67e26c2474ab7ca3839185f1e6574cec37463269c86800cb0011e213a2316f897371dd1c42fe98ecd7fc7f02c8cbd0144e7ac3ac387f504177e107ad1a1518499235ec17fa11c342710b3b1dc7f0e98b90cb146928b71a5ddd463c071d3d846f7ff92a50c294182dae3ce6016ffa4c4c3c9c313ec5efeab07f226d4709a21c8b46c030d3f46d7eb5dcab4b92c90b8a39180be3522ce19ff4335463b77e5a8a2448f2a46dce4449834f8094f36a4a2d169e70d87a4a6b2d695e882ff9272985e0603da02ed8276b68240a658d842c9effa5c66e5c351e3aee31edb1a6f8cb7fc3fc90af3c5367f127dd1cd154843203eb07e3aeaffd5a38d328fa9572bb79cdde3abde985a5ad65833f866c7e84d7a41d8e3e411f8c9478829f967e1b20da9ccf2ad905a57451555058709aa6496238ec6a0ca949f35f2a00a1eb3fc70680cad6335ae5065ce75e52484bbc5ac2d6e17a88bb8e8f027ea9323a5a2c507d7a857abe0f74054c47b686a2aac7915cad4ccb8a2a6b8e271b81a0a38547b032e9e0f6153b157d347bddee3c7d18b7c725878f4f54aecd11eccf02fb64ee3149f129ee713ce6413d7540ffbac231fa7afd111ad5337830578437dacafc7d13b6b2e0c584398ad14214b2063a5d87af1ad13f371b33e6495ef7bc02463a301c37340ac8683b344eae72ba3a7becee0df7ede8aa96dcbf2d3290f225885dedd85835446f826bedcf2b6460867d5c3bc22e11771cac23dc06b323e9082e7f643af854d6f4370bab2eaf23086e3e37a62fea3806e87375e0ab3aecd47f3067d461149f332d6440826562d83061f93999707966e5024b5e6b11c0c9ccb6a81ebf14a15f8bad8b9f495c944467b438663ac08f1090888429f1ce64dcc07b6fbcbf8af70e318e0837b8277247fe2bb006cbc2f7b237b70c2322ad430a1a9b3c50760c55a3bb156dd7fee2f495a622ef4358367f53ba9f0a3edf56883fbf684cc4049d28fc08a5db03a267c606f1f21493a078806be711420920200282a86cb899e4ce12414f07d4a23a6a25f80544171c3b7dbe325d3222e8f8ccb0ab8b8e4d8af04d0fe167bc049158ea53a194ce2a6e64219616feedccd95d71d4fb82669bce47e12ff8ba0aa8864af592a684aa57faab423e0ca91c4cd684e3a381b8d9b8622093ce4bb27ba9fc8d6464562a8382ded7c7caa720c4c30838cd65b7639871d1eb8d4d9977126d7a924b34b4b2096db638c30178b929c78e0ba349586d50d004d0b86c7d25f0d0768dab15a3a614adfe4704ec3a762413931eb347ba65fbd3c4c4377460c5ff5c21ec7795164f502ee858794e4d93111aa733fde9b37da4226bd500391a76a1b4a9ce7b8180beb7033aa635ed3bed5c7327dde492e46f25c59330ff9fa47dcad7af68b71df6556cbe38ac5396ad7bd651fc647ad7534a31c7ce89e044b1a8f6ea073fdb508e4f3d78355e66e1eb7a49daba7ef4b69ea911c3b0417565488a29a470ca03c20a44d41ac82d84513dba5add2666714ace0804d2a1cacf087186bc0380631105ff21090defd16195b9768cbf6862aa0ebaecb4823e05e0b8be277ac23b2ea1f02b814ba32adb4ba09b12a7cbc5c2f57857c62a8b528265635420757582ffb129954076f65b96a3c07f84f833eb8c7688f216b5b248c88e9f1f23c93f1a9d797463a76dd5a19a0c54358d0dd5d42d55045cfef49b9e7541f0f99cd19693f699146a1f50c385aa5c68e3807dc498879f625d0580bbd089574fc41abf713b878f8fc5b4b5a07f10e1602958b7471e5881263121e20a620dee21188151bf34baea87c138fa1d262c281e006e7d12dd7c93e2f0cffc5533af3016acd01fb792decaea29c854b7d13e0673ce0f842bbe640d0b83648db92b53ffeabe16a963e2fccbbdf25bc42364b718cd0c6efd22fa117116655b31ed6d56490b086d0352ef9c000fb3a0fc21c88a15c06ea00bcd3b99310307f585430ac76168a23f8a1db581e7e2a00d67b616cc1680ce9f94c4f94472fd8d0f21c8138a1b5e9ef43db2274f1f80c7d4af5d0b50f7a45953ea318b7663b1c375519472f279be9e45ec0d20bef1a319527350333d18f2e11bf360650cb9fce02ad1ea92c245c69136e7e83feec3d275eb413efc06d81e5d1eff0cba1c6740232766c6bc55a2aeaf507426d5767b1403831e9c7ee6c8e0381a47a9db06f5102f17a8e847483649020459e1c9d468cb06966910d656201ff3bb9276b84ff29da05068afffb546441b46794f8d86634ae33e21306c92328e099954cffac705c3ce37d8aa758a5db359da4d63c31bd0db0c5f0fc3f048f7684b71c7ff0dc73b5c14e1cc57a449b87dc0c510667f1716150dfb3576e1c749326ab354ff02b41993bee0bd8761fbe903dcb0b1a711f9ca86a4cbe42cab2f28aee47c3d0cb112a15cd8d666a494bdc3cf6651d6ef5c4be2e81e9c14c704a42334639674405e91a0db8d4a980b0a68ab6c3cee03ac0f94f925997dfe61d81fbcda0056ccd10eb7c4b9a64bd5f2d7af6f9222e4721992dc2337a3e2fec8da99d39753c0a3a3223cf3e425dea32391c3a9d47737184d452e19e7809b11cebf889ffd0b5c5f9e25f2eb309f7f35ad1da9bfca2cce314485269e095361f8e9e00106b2dac0a5441068a562253b8029d34970a0cd5d8ea88cc383ecaa060d57d889ed36b9358a2c43be91d98e703dbfed4e74f8d80c19b60cdcf2ea536c335f7a4cfa3a20120d0c09391b4a15f83a2811c6cd485ac962b4600a7297b9757276065d2760ed1ec9c8ae9e1e4998ce93bf5103d73291c51de5220cd8be6aa04abf6d99a3a10733abc7daa9fa015d752faf328d26b55ea80b60d4f85a5cd25d53f7509954bb6fa1d97fcdd9646811973254e31c47ebcadc4a18f16ef1839583210830ab3079737e86fd4eaa9e899a3c71f2674c9e6fa9c2622c96a3c17c41e250e0d160f04032eae3b02be5674aec881fa2484aa2d030c5c6dc1304314bab78ef8fc83d0432e9951873b561b6c4488724ee318e7ec4643d07e069bee6fb3d5f23d0483eb8a952899aa6ba788cb6ed906d19f71fa5162473e7bcfce5d3e77522eb81e1945e81ac5b9299b87530a4722a87901b9438fbdb4778e0456f96a1bc7f8a44ab07fe17a186489161736cb7506d165c269494328e8e1aca7fd115c521bd2ce8c5933f6d176792e077125334bb7cfedeacf1e6eaef1f52158cc64d9c0e7f6e9da351217c65ee7c1e1d973051fb2fe5910ec5cb7a6ab6d2001c11bddc5b8314d129d088355fa5223aaac87e68b095e9205ec7399bd465cb0c255247ad0a0ca3f265c66decfc9b75a287f794d522a6203e3b1d3d6afb32fd9a17cdf45d3251211f43ef028a97b23f92e1e2e1cc4dec84b5d488d729c964799a4a0ba17a86d64af2a8c297e2b4987e4db652632672c26ba3d0125a94991210431aa40b349c7e0f075fed053650c9fea0e2c74b2498cf289c2d51194bb4e47904988e41c8a291ea8710dc8d86759a1c9996af2b63cd3b85b4836932eaa4abd4a9a337b5811b0a759516ca7d708120987370e547c8a6cc09fe600e6a6650a5985c619b6c5fc0741f2eca9b82cee7114da9f32bd3e7fc2f8167742814710e52f79b1fa74df1928136c9628396bb5859ede42764a7091a55dcf31025391f0afa20c7276db794a0149c82df79710532f03f441dcacd8cafdb6531db2a1f45e375e779d5a1c4713f363b631c70c6c9e883bffa3e18c4f936169f7578417a205a04f6356cef7987e49e942e67f9bd1820e6bab4a8e83b5d8578a061af2e38b2ffa0520cf26da1e06c0cf053918a0b91c8fb15749fbe57893ae664dec11f80f94f28e02f6c9b983564a07ab959f0638ad9533033a53979d2afdfaa427c2793d10e33f7074d47f44f210ba19231b5c86f07e363181b6a792d60104f9afee2b2f3444143d94495b10e6382fb1c6636244cdb6f44ae35db7ef58486eefa9218208b159c6c95ca31742c511c435b12c836a4591bcbd9ef434d7d058a9f1b38e74efcdf8f5d0778e6acbdd206d953c66dad10697701d3f5b5dff8a151c90f33121f0b630b1a86aa927f2b54edf90fbe2592cf9339723d0b8b66834e53c69f675054c30855b87657b9283bb5ddcdce15216f492a6e6e20156559c4e4fc12ff0b61119759647ee18fa227c969e407a14847a5380341141998e62d21136f113f0fda85e15a8232ba13a484c5cfb149d1f4212771394458516e440ae992f4be1ad193317c6c157327b7873ca133ccee9cd2b4d9440e88684c176481c1dec3dc9a3edae93e4fe7484e985f3ec89287c0faa37918f79011d5a893977ff0484e61ca9fb64edbcce07bed87d548353b849fd2a4534a80fb1e3ee41dadbf1e33261bd50861bc0933451112ec653178626cad4f4b18f43bd255962e35f00b108f633930954d1915461711dc50ccfe5d1d320042ae16ade4fb924862cb39f7bf63c2b16f37d73e8d7c699ae5b0ac42cc8e59b662c5048d4db7c635bf327ed7dd3969114d17af81b6a90508fa246df5723941ef4c84dcb821a7993a0a8e12756166d4ce52723e9592dc438b337a3e26d0b2a48acf46f7027700e8f81d464f84bf696d9de733589c8639171f47c6569fe1eee15768bccd44ff3dd373f6182cc5e2c938adffadaf02c4f59d078cbd71682394c02cb38a055519b2989c852810341fce7adca776850f3a5eb74d700173e40e9ae90c476cee1f494141765ecfd13598608c6b2641f885ac4c0c35884ea210a4cfa62360a6b67d1e792253e8db223121008786ad98b4a39a19edaad94cf586144f669e1942cb8ab0676051b9fe034b8d8fdb335c4384cfc5a12e148ba247164a5e7a0c90961a8615d1918064d51806b8589c686b4f1b801835e382ac23fec34c4ff560716c299a0ea88ee43718f6e91197c48cad7e5268ada3f64d1b0d9a5bf20368ed52e25f9d88680a3490222c3fe8e8cba48a813405aa6be169495552dd160e435a15b3f93f86fbd3c4542546c0bd7d8db2f980df60ec86c696091c0943e31c8b96502428d6145394e3f1692cf766e05c3461f17d2e7c2b2b17d441b0f5af15044a74019cdae9a98560ffb7babf1f5d2809b8f3baca86b917e901f6b45424df96775da5888badb91ac2514fe51485e1442eaa7e1df4b830bde4135d1e4ff0450436981731b50669fbcfb0c5399c826cc298110808396759ac6ae6fc2c5b6b67539998ade9b0062fead5f8c28bc6c9910fe26280417619fab0bcf13712868c7e57bc7336f447a84a4a073c7e917790887a52d76f60602b3dcdb20b22eee4b784357809ad73d61848b9f05d20150a7a9d18f40e9ed200f80d0c4c2fc73e6dac74f233955f981b356ad030410a284bfd9a586638353f8a4762e4615e40ad8e9b74c8b5bbf32ab1159127a2ee227589c897747e30bea4f3e83f6203d02e3e8b181e82293f7e78d5b9d9e7f317a1cc25f925bba36931aea82563dd5c7148d9f0ebb1efee147b800a3aeed42baeee4064f555ff77f0c294da13d0fe42212ce1a5d4f0af2c86302b814a0e7270717716b7671b24cd95ec3951369267a8e6303bea8230959106af6b0bf05d659273261f8b978edc50c05183a5afec7f43b697c2936c16a9d0fc88e5cfc1baf455f68615a9caac4c9ceb05c5c7117b449ed0ceeb6acb77fddcd7226fc163144cc94fc2ab89a518e4ad087c1a0a15fdd6a1d3252508f95e2cc8061691d8865c7af744239e713dc446a15da842b084548d29013481ed41d8cfe22d58c083c11089c39f30727e8e282b3818b2c1888834743fcc69214846039fead93f9306f6f2044abb882f87f5af8eac8c0942f5c28260d1e445f566d8aecbd42db4b1bf775b696ae64571f5723b973c73df3b6a9d9f69485d0ee03f1ae06051d4618179151af5dc25eb96b78e89ed2db3298e8b065b2ef79bb7d1f5edcb8c9e389c79deac594f033c01e1e8594acda4f59f842d77578c47edb010c54ca5a9559f0976b547a5614d2cbbbc35b8f4590d2bc13d05f7d5c30503222c5684423656cffe96effa52e899624a1243111f3113998bd57069eca51d7f44ef90c98f2804e2022750c830f2f2cc6849a870e3a67d1607703c56a6ea0354c12ad4e8aa2b900404fb7f284bdc8c7e3f65f303f75fbecd60a896cdb54d218984e131418e946048b577b8dc32e68503e755c50db88231c2caa421c3673f7818fcf6dda66a8b378ce8731a7ae064f5d8a33a6306569e6f0b008c77c9ee4173dadfd732637119b78193295341818dbb700d98a1f6ff7a0184cecb2b1953abbf7b19db28805aed471e0a9281b8b08850a6f13de03e938a1a809e16ddc49a6e9a18ba4f68ed83cd317a1e79a6579383830a4678e9a5edaecac32cf5a99a88f14411628afe240187dd656055d0e3d70d29ce95e4a66ee997e22640c6df9fa57d741273049c6af87298fa44885167528b4e0fe94eb2ead33d9c8615637c4f39142ca3681eaa80de9f19349feabf16d10a1d442014b03677ebf4f743a2edb212a81ee4aac9623e74272235283a855d111c22e5a618dac847f9efd4bc81ef764dbaa763f6a21ffa5436b617989c1c28fd0c8fcce679382360f440094e24497e98fa456ed54523d4f9897b5d04c22049f640764897f4850e2f5f8dabdd3cf7f41b39382b7fd798fa96daa4d1d4bc05df5c5e783241849da45b30126e41ba37dbb129d556958ffa86dbf7419140b4aa423f5948280bc2d8089b2844b24d4baa26b99eedd18d7e8b5221188b9cb7405fe8694f6329d930fd73c359338830dc814fe501f236e564799e9eac32960e7a0b15b882f55a9e4e5be44e670cb01ed23393da09ad0bce5f4be74d61e087e0a524fd32f89d4a85a4cf555826dd5ac5a02c20cf9001ae3f740fcb8e08ba59bb173b45bef40554345688fe038c373722aac1cc2d6abe457a14116118654375346bfd22964a0f610d855cb6eaa413394175e3fb53eb965583a1bc49e71a817b174127c5ba0be62eb7a2ea8cdf8fcb1b388b3fb4982fb3958177c63358bec88042d365990fded193fd34059c268f68f9056579ad1c437cc2c9889344ae3e9edc80505109acc59905234fbedcfc987d222a62d9613ae6d862c568718f7fc6400fabc4f886dcd121da142e24ac74ce1d11c938a69e89a85464bf3bb12c8cc84bff874506dd4cb6fea853a18fc65b53cb230a0127864a525473c5d0588c6d910c48c7c5fbdca92d02fbf043da5f2deaff8daffc098ebe5814d8e04a794abcb35d8b30b5c03c63630a5f1baa21a26b35e14c12254c25413bcf303d84afc3c8996336c3077ef2eef269c7e75c22f36176f0c307a6c24816e1a88e6783f2014f11055af738f028d904b60466e3e75433ab5f7e1413536c5bf9bd73305fb998ca8cfa661f78c76dbbea254ee21dc70394cf76cce36b38307b3eba6ca3184334d36d771c0ed26da877bd4fbab17f3c843c3c8282ec4b65653e69495ddd509a964aefd6b853b340a6ab8d2ce2fb4ae8348f6a82864d97dd4a45a848e546309726c8bee7188005d2606a089a13be31174864a92642971d354a9f2eef69363c3117dc61d2fa87f1f6f35f21924cbdbd39ad368a414419544ffa634e920523d1f53763e72ce69b69d7233950ae10e98175d678d95d9b2ed2d5ef8bb3af8d7303ee547e8ec56e4d02ecab1357851db40cd4f45e247814f5b556dd028a375a96aaf69f927f3fbd4ee181e35ad45033c81531a1bb0129b3ba7fc6094f606e2947a8fb6e760fd3eb907f2693e4fc1d4bb78673af1e3fe7cc34c87ce95b0b0cd06241c65b8e5cc9a9009f04428f3ae29291a0e4a4dcc594c36767c2dead46088ce850231c7458f17e79e730edceb46d7088488b2a16dac06c79af39106be9581279473cb3247f7c0e4006f7991b2a1bd9e20122b523cd7959473b93fe84f992cd54b012229f0d87e261cf1b860ac4bbb6286d434292fcc33f186b5bba2112861e81e1aad0beff20b80b93c971088e3723b56c99adc2e0b081cdb58c97a633cd97cb01ee9ff09a71e259d067dca7d094b35edd3225256d87a34dc32816dcdc274fe926fb5df7503824adfc6842bddf65846b917075f937c9be26038788f59ea10bb258f646530df4a1d7d0b3f8531c6e34fc7be6e0081197b80d91b144bd82100e7a3c5036799f5511acb0e1698cc3195027893d408e20152ab14790fc8a86ad1dcd882996da01a4e823abfba9928c96c86be7c9c976276a92672a6bcc4d6a4781199565ccd45c12f1c0601d30d046c4488f85fd04b1eda024815227b27c2795bfd9bd122444a7e6e6edfc35a16ee7f68e5f09d4da07351d4d9473233f2706b5460e25faaa8982ec4c4958ffcea4d6422073685507600091cef6576e8bb363d9609393ba7413b2bc30ef9d8eefb5d38b8c5b41ed7f76252e8de1a53d2065d6a253a5931cdf0acb81e1d629365f0ccfe32cfcab3b0abc73dfc84dc9c87ce4d6e474206557df1ca700e936a5fb41eb30a97e7d90c10468452ba4bdd0635381b4ce10677a87bb04851e0bc62d656857c5fef29e0bec53169ad2af153a17cb85cc0409dd64694f4fee395dca9cf83670da80bb5256c76a70f792faa54a614b77cc33a5137a96dcae458289e97b4903a0a584cdc6f75188653e5679d4606b90fc0c26700b9a72d4257357d6154f54eae4d6a513bc29711c43182e65ae1e4787667a66668187aaed3aafc20990e5f4b0a0d194a6cc670a308c44cad1df587e738ebdb2201b89cbd6f3d73db58545c76afdf9c92e349112aa6f3f07cb7f2ee4d99d4b40b25226327e3b6ac7856497ef2ecb93ec5b105cbb4855ada03b71629488f4d1e0373ea701cbe1423f4b594b423407345fbfc72a9e7be9e3ebc9a6f0676d713116ed087af167a8ec7c75baa39926b2b629960538187abf4d5d428b66afe96802587e9cb828351a6cdc5accacf2fb405cba3a1f1bf7627c338e446130d20f70d51bf4348ebb8c241e05f0a2bf02644b58aa75b2f4e18d1fa42689259361c00a756e474db96656de37a1a27131a64a2d3243b2e7401d8bcb6f9d929531deb357ddcb82da368be1558a89f09ea16cc020d80525865f78c5118a73c89b5775738d943f4f65b610c1d997e79a2e416d5009464c94e9bece5fa69b6a908b8959943bb6add1c68b8f10ea0a5b6ad04b0db8059fef42bb80239b44a41ded60b6fb481c062acc10f9365cb64afb81b21b7b570be6f44f4bd6af62953cbe04158e7f54d4771d3f387fe875643843aa3965b9ed6692c52def11f8ba3f03d8b11a9e59dcb92aeb436b2fa43d689b60883aa9c8dd32e6501893ffc361ea39190be59200a3e2cfc22538847afdc843316c448d749b92e76cabc1ca14a6972d5d0d3a15172aa3e38fe9cbc206ffc563da393a450533c136786050d7b231df4aa86538e14b06e41a832799273a7849aff92be14ca81f223a70cab15f475d3bd11286e1bd23ae71740430df489c7286e1832e089965cd5419c2261b220a0cdfaf553710a72be162b9b62384542d55816db2f48b93f7187ec6f0b2dcc4ba2d87ec3bb3a7116ba5686de5b59bdf08bf4ed8a4c92b708713896bd4432f3b7e38d3b1ff7c288b7dc4d56581fd0bec25a5f0f003a7c4c7145f516590c58bf83a64c2badc5dfc436c4ba3192870c4ce67033983d7977be311b2f59e0fc95c6bb31f5612bde868633164ae2eef71c09d071419ec6c36fe2069baaf0629bcca61e8a21e078307bc05e7cba69ef55c1e5e6fa44e8f1aafe0b6ac6e0f884b21b9f21b8516f64ca23b9a2b7579f35f0af889795c31b7e97a9a53412dff0af1305bc96a7919742a9ba6e9a3ae2ad07f075b3185d481a4fb214a33776b791ae4384b6bbeb1b85051371cf72d1a731e75401dca0d5759e054ebefa7c4fc65714f3b8822b85542b02c3cf36db553dcea8326803c5e325e397986016f0b6097b90acc21621acf9ca4d4c461ef762250ba04e1bf00ae79ea29601adb96f858580cbd05918775a2b9501443ecf976d1a59133aded8caa4087dc99c23fde5b60f2687b0264ca35bb903e777f549ea738396b8d34393ad5ca6e19c893c9e29a94fbcb703ef67a8d51b6f0354da655b7e9cb20be88ecd9afe6899cbfdeb9d46acef672b204894cca961c8d8133e7664e17d636176b7c02598e087a4608ab4321638b2141f1befcc98cc359893fdba65fa9703139027db6e3306ed37d39df27f1ec89c116b03cf238e4f6f0e08762fd89100dba8366df4b8363311306cf9011697131ae480a9947cc133d09da2e14cb3d0d8c18265d51912ff79048bcb318b0a3404b7a60ed0f70c3d5dd132e7ad7529c7069b81c60134c484411c476dc68d65fc7b22f9a65ac8149ab6e203712fc64ab5e7fb66d554d3581de61fc045ab9c96f8abbd6a62a8ef5fd50a3782650be070a1ed123da48130b11b79e8f3823b121045afba208a6ba39e7eb8e70bd703eec2f7d3b9582f2e30f2c4e42109071e0a8045cdba647519f7429d5b911782867b075eab59143d668030fc016351fb58739c8c8f584fff62781808f2b85e077a29d3af8baecb487ff6d6dfba5ea7a57e0e044833c8975cc447b067066c7ec280e8c1bb2923e41111415e3c640a90510684d604fd61a5259b3abbc11f7a107d7459b690cb145fa0015c7849410769f0bdd8f63856d584ee558e2703db1622ed4473c2176df06a5385360fa8ba4f5f8f14d179f2d1b607e2fe998c7e1030c13bb7dde0e15ed5d8acac1825f0886cfb4040c8c15a6181542b5ee1555d04c0053c3b54847cfa890741a097896a3e1473c770427474742be3c06f5790ef2662e4c67d9b8eee3da0d0ff2fddaee21b7bbdbd1572f54c5f41f8778081f11e5c245f973f69ddbd5db77eda60cc7706c62cf6652cca27823f6ff28677da88381b3240ab3396ff277e3d0ac29868c1cb5cdfa6377666ffdcad3cbeab0d8e5e02dca0d48572f0570052de73452ddba69448d103088e5f478337b4f442a091f2c048364f0f5796175baaa51f11357f33b6c4ed9b834ad918a518592e368d806426f4d0580ce250be92675acebc95906a81539bc844d6f6e4f8d0893a4cf0c6333a85e789820fe8999974ae53fb61d53ea5093f79889a837b3d0d981bfe517279fc675bc88161d3474976dafe4029d8f6325005a18f3b4390158152219016e51aa545923f5259935424910385312be18f12c96f886b5da77000438312bbb6ddf3712863ce12f3d0d8a83689e0b250a5b659900a61c70971fdee6849809aa85254de3e048371906b1a433a4e7aac209db43825a4ce6aca935efbaa132d2f63af4e6e1879e952da19fd469a6f2015aa7134f52fd3796581ea2ca50b0cba8f002a37ca1f44dbaf1884367433d3a8f15267d76cc67eb024a747ea13ae17c7fe48c3f9d1e277cc24029627c837c4a25349b469304daa67d5db102623ba5709c584f01b18746c0d8a636b9afa4c0629242a45d5b263549a6451bd3be7d8f60e61087f2e391b681b220a0b99e4f343e25550992a052b78ed9d4d1d1fb763e89736a686d0f4f08e3cde722f10156fc97812376dac253c85c617e594a41d18e85903f9ca0929e03ccc621ee0aa7237ce099c9c2eed4efe63e03e14499edc66db7dd17693f8b8a518d0b9b4a0603fb9b3088717f2b0e6cddb17f21ce39a68407cd3ef3273319990f0d28f336e7842f1d09d624fe40eaf26c807a1b03f724500d46cbf2e26ab7e790fc5abc3003647b5cc39052aeb6e0ea9a0336d878468144bf409246d0520e8dcab7f4c49851d103f28cd7fded94b47a0d1405920b0f621ba6635699abb83f96c423da9698b11b11a8e22d60c3c1c5083c821a6ebcab42aad63dab961184a14bb907885180c4aae055aae2f321cfc179b7b161bb993d89066f2683ae9c3f8bfd65d9bd4f1dfd6a19c2e6e01410561683159b87c3cbf4bb93bfac99328353b562641fff8415054e02451d60ae7cff04f86b5ecdb52af1d649498d2b82693bbd6429e66100dddfee790d07b93e88d6878e57454687b909fa079eb089820ebec2f94e06b7bc818cb98c4e29f36d0060f8b05ac583cfb0482f4fbd5315a674ec96fa164f32fa84305fafd8bf8caa6e0a18da7e9c387154700b94b2bf16288ae4c1199acdb6e14da2b4ff6804440a235d957beff6fd45143ae1079fb2a84ac91f6198c0d8d294bf145b88762984a81ade2b346f3c01d060e15ac39e5ccbd50694a4f12eec56230d1cf72512d98086051760c421ca572a8cc81029145d94d260183ff66075a6bcfe198e2d248f5156af95c47ec14810e5362b03a728ccd430c836f20aa6690ab2b9ec6d5252e6ab414e3431675463f49e928b78f0860d5b85796cc6853dd77cf4fda302d96907b04857e1843eca7d46a90dbb1fd05f773acd46c1452a129c588b3f1265ee4eddd9254bcec19c278fffb28a2092cf8869e4029e930ad47bb6c09333f007bac377456562d8c7a8b4d521f7aac37e0489f08e34f3330b1de3846d97121d613539649163c11e81bf146814c45c050b14193984f54694c8d0e9ebcfc1f8e9e04306fdd4aaf591ed79544e11c054529b22f51c526ca5430cf37a7670c1b42f45e302b9efad70a23a1501156b51d4cc322f2bab0a1a36d8410362cc9f4d4797cce69d8501fe3504a56cf3196b40edc9de2c7f82b2de69f326e9dd7518c381fe5adbf57607c3b47f0e66d66f208b4015edd4360531e140470b74aae00fc948dce5eff6dc72979a8a1bcd290d37920dee1211a0a1e63af58f79f7e881da8a0344c41292d051d134867c5676489a1f16513bc8255f6b8a538061a38256693180d319c19810b9156e1741b53a6406a288af899384f22b1714c80699962c0315e75064511a9c133aad954c3108ac4a7bd3e5a6907ac3937d0291bfd3c689cc50f3f138d27dae5b2004b2c892f06696b293c306a2eaf534430a24fc4b1252e0cbe4d52738b780a6390d7ce59877f7cd6b3ef484ae78a0020f94c90f37e91ac3fcdfb7f932ae6b12ece3fd994295040f93035d9c053d8fe5cd99aa97c411233311fa93e57180ed839cbea1933957db9540505a6f1bc50adb44275fcd0b406c837e7c6c12f35abb8639342d7adbf6654dcf7bd66804fac7ffb2f1f2cf6b9f4daa82d0c2d64348a974d6679170cd338ec761e2407abce01c2229880fa6e39f0d6ebef8d98b2bd879b3fd26b2a9de8f931c1219d9d516a1b9772e4c6e7d1430990e3c3c3a65111e52aee5a49160f3bd52d79ffb3582d107190c957577e454cb252a1055d2a088ab446ca0636e0c31ab541337429df3918acd558e3009832967ac5617e8d4fab35ba0e88371c3fb67f3afd107c193ff1bb5481edeef57a4babbe691e1441f31e8fd85b08c31e538130b17b590bc2aaed3039730e726eddecd6519f9111efad6314f245920ad33d2e3714b0424cb24da3553142d3e0a4f19fa02e51a6f9b3b514ded773af37472ae8c070834a21b8cf0dd1bddca04578580949e843e512ad8b5a0d74ad3afa663abee0609ef168600e46674704973ead788fd41f7d9a23180dfdb4e5d612af4c3b992032446fbd1c0a47c0248982359e42cdda38340fa755c19e0d7a62d57b18ecd9509d6c5bf51ce89b5d69197f0dc39cb8b9eb3ee6328ba19d596525f92964c1e1f9adeb0b5f6b3482001c7c88ced8b407f84930ec649ae8f1f622b367294cbbfdde2caf327a981a0c80b7a7554e5dbcb38c735d55e101dcef2e4e067c84358a333437fc6aa2159ec056ace22fa0d25b9649ad567db1ff33c49815d0ac30e4f90093546eb3c68b30cca2a4824f3cdcc99863217e29f25b082dc694c6745aefaee021c1cfe9e7458d586bf90612f0826229028d80cdc321e0bbf0259cb14c1b1994473ec0c65c064e5107806257492a64a9c3c74eb56b1cc76f439a079cce6392b6d80dba6197438851234121e48b20aa5230cb61e3fb7e6f3f75c0a440497a7c61ab0958cc35131935ff1a9817c3728a6f0652bca438e83d47046f88d82b1990ec2b00b77775cfac6ed27fed040948196b8d1fbe14f35cc816508929b07a9386885e670b2328203896a4e833bb9b7290cbb01c51e8ee4fecd466c534081d39c37c4461696e19f7e9be627e60f7fb30c7679387ce845f107593c18b3e08a390f5440292e19df193ff9d768ba26d8f33bb0ac8e84a1b56f7f01037d8cf4a2ba25dd4d94fb9b8163305b611969322c56b39a079607b493ed06a3391a6eb2a28d6d87454217d39cc04fa067f6de63e8a4d2c28881bf216c9503c9221afe6bcaa2e42552e18e95310e4191aa2f417c84d804da9ec54a987f585ad7e01183e8bca24c01a021db7208b8827a0c399bb73a6fe0f1f47b78925ee4905efdfe2514b02306a75335049e5087a2839ee8e167eed71cdb8aae22728e628719677e891ad4d5fb0a9dac0616c88a76194540251a22fef54f0af5a118dd28233f9f2307e663b6cd75395d34e35f1f2f0ac461fd72a44ad7303e2386b5a793938f564a734648849a0942723f873ea979bffb9a8accfc15b5fddb1ee2b20756640bcabc31c91f1580a6f1b9b3a24dacbe3f43bbf0a71343d7801bc105153d1f22bc277f87d591d394ba98de6ea5dcea9f6b809d5e90c1478256b7d108081d9858bc431a5805cff2013e15a820925d585747587e955fb3bbca7940022ea8f77323b5b8b4bb19f08c9febcc4e4dc54620da11d8f7fcfd25e4a938089855eaa9a7ba9dd808fa808824377e7eaab420968009e44a0bb5b993b093043d19bd68dc016f57429a7379850e293938cca5e007cd161e10e4543bf40d5478ce50e62b50bc9b905e7743e39f560bf7a85ae82efc2557d4bf78cc140dc3d20ba48fd5f05de006b907bb6f125aa92881b591684a6e2e62014cba497fe1ad95e68925bd073a88bcb6a7d88e8c9435bd39ea541ec63e1b4d092fb610cb8ca6461eb6038d06d5d07f22fdc2607c99c8d58580c3d13a82a6d8591762010c8b9092f2bc57d0672de75beee6c4fde28b421426dc5f2a8b72216f516d8c3a0b32e1a822067b5a94d733a5cc54e0075bc5ab95f39160a109f53e529f5ace605255ab0d7c468ff6b7b350e61775d757dfe9050d73ef508b27af7c390ee2f6ab7a98dfdf6ef756791b08235d8e535b986a7f13284bd5f241314e1a3ced7f3e91949acbb8387a4463a86133cdcc173d25812415d6884e27f02cc0b99f6a310ad9d2f899f280a48aeabd9d5b5eeba2a1bf11a4aedab497c511c6d2028dfc5e7ab31312abb6edd41787e3493d117e7eba0bc403356d74069cc9d48db649c598a2a0c4b14570b5ec7a457307f8b0f15352209dad9fabe75e3350745ca868f82d5ed0ecfd7ff9bd9e7df9a2c2ce9ac18c7179178efb84fcce5acf02d9e65a8568654aaa61904f420b078401975628a48048507bb4c872d799a9a9e4e13d8a513aff7f1ccf8daacc5d84e57ad57a22c947e41240fd0b87f6ed5c282cd973e895e29e5641fdb645c4214bb821db20645a076ef65bf0e5a04d1bfe39c21cbd667ea3abf60f44c32299df98557b52ad27bcaa2782e91143b9278ff6096d85ec0e135dfaaa7d795693e55dd5648c937b77085a869b64685264c5835f6006d5dc2846eb546bba071658fdab43012bf5466322877ca68d84858bf04796ee10ac1037dab9a62da441c4a1c96ad120a42356230f9d75c58fef8853189d71c96ae5d46290de8d1c8b4d32782332616881bd8e41b5f7c19383c3ec12d7a609a7a2ab0f71b264b513467f4155f63f1c0d00bca37643e6fb69fdb6a1912abc021a07a59486746cf5341423b1a7be2fdc5f2177ce640124852f6add88193502a058c3f69b86aa1dcfdbb16a5c497c521dd25c5c492f50c5d1c0222e3927ba577961fbace8c91d650baaa95de1788d2dc106efae452b7e6e7ad58fde86e72ab81860167ac786dfb763863c60530285956adb3046c42ba88abbb34a367c11f46394c811f890c07d90ae3ea89dade1c84046bafe6dcd5092676daf5a033923f03601d0f8075d70d8156544693e50dfe04c7c223fdd18d6025b38c6a64be7007b3a1ee1783603e7ceee60f7679fbff4c9f47b9b2e428e7e50f4d0b1d3636e0ff8b3b848d53e3f6ce7482960b7364efd863d92bdc81e77b0034d7e6da01a45df9f3fa8787632f80b1c7e2086664f43cfc29321136f5f4bbd9f674ff85bf03281eac4a1e9f34d8c98f0d381424c161df2b63a911d966c66b51417c25ee2eb5483fba769b65604b1d5dfbe1a8a8c5f68f6bf1efd506e3145d22826ad2ed85a7c30e8dfc2f72f1a9e32c5f16238805fc9caccf3402e649f0f80d41ff56831b2e9012503271b120ad70978c96ff3aed7b07e0e1a8c4982b2d904af856732552be3d8b23c5b8d18d294abdddfbbb616a1cf0cf5d67e03ff1ab1421abb5251f62b1908d4c03cd808910f95d35d1d4d2d67117584f558c69bfb15b2c101164eb3086e3a9dc80042837ce0b20100c1184f0efc72b30a47225961d5bbd98bf0119a68ec1c927bf97c7aca8bb9fc82f2791a12ddda09e88539ff14e56810ed247c7612703ffa1abfcb5efd200fb6c9fffabe80b2d181c551088bc4101765716c1f4c3f764542a837357a581f2faa157e5cf271ce8c05ad82b26b7aa4e9753aa99fe0ef749ac9459f389824337073791320114a049e9c47be855b05d804aca66ba40af4d8cc53844b2186d6732a1a2cb4f2cc2af2ef41abcd3684be5e892815b09f607fbd69fa5caf236959cae8040e92d34d50897354599521a1b97ad4f878b8f2c11d2bf8a70a363848bbb46b8af38c22d203ec25dc5fa3cda81185354c277712eefad867d32629abec318845965a53ed51e0a891a76b525bb41dbc517a8b12f693f1f753a948cce09c3f031e0ca98441d6e9237adb49f920b4c7af736927664a2917bb53e64acbe0323c94bbc4841956f17142fdafde85294a617a4f43189ca37744b977d8099946fa961df772c241c65d76e1f1d98bc28fca810becb19268e6f98aa093de29751622ed488dd0e5d9eb2e3c7992288f198f3bd35eb1c375a232f6e1c9dfb107c44f384e6ba978b69627b096cf7edb43709d3f3d730822f86133db83e9bb10ab602a15ac089dbb3b461b16f3c5b91843890d18d893974e7312299c12490386ba27b4d2a02e53509240ed724f582243b675878a073eda224a63f429f346ab172868188dc108fc3dfcbc40f7ed5275737ea9436fa349cc5ce58f807eaa738831960ed20daf33faae5fbc8b63d8f1fa07d42988ff2d3e1066751d0f0fc8cf6ecc7294036c62dfb36fb7fc6f1c012f60e62e3f4df6d19b51d7c6fce2b1edd0953da5d1faaf4fb357818e829556cb7785f902ac640819c6b584e0dd32582db87816c4daf858cbd44300aa318f6b6f99c623f845c0d9b80f034d6b206efcf70a69a7e9b407c9279e0a1da86813e1fa530a605290899d66b1018d56b264d3c0c63367bdb8e30b4e5996aee67f858757fd9be0afcbfe6acf735066df3da5c231fea75900ca350c85440872b4a46a155f21f1523c7813d556e65c84541aaae60bfd33efb5863ed8e75bf5f1c0aef44b6c95da2f939b3a62b058ba5bccd74ed4b15159f5518b2da793333d6e3de2195d459c5d376c39cc24212277c5bcf166d56bfc9f50e03d92f6563085ca906f2745fe49f66e2ec3ef9a67eb6b41fd4c77a15066b57f88acfd63876c691d69bc2ea4c96722ed394852abf7753ca6c010a19259c74fa0b688230c210c1671b77715f3e09990851ac1b014d30a88376461752b39514e78d497599818c2af39af324bbbf32f26153c26134bf906576b7311f82054560bb24aeed7a159fe542d8412b0f4da1f3ad4de02f1463ced26b81fe33354c56dd6b33c66b8e5fd52d80ccc8e5940165b1f65caf5299538c4ad7eec699f4e44e80af42ea66fcaa8c69e13adb3aacfd2fd96ef20e7178b1a3e4662eea170cb87a34ebbbd54dff7c9264b67c87c5ab27743ba53b1022206d5c35750dc046cf3c250b09fcbb6936821ad7e1465e0ba443c100768a2400ba677724b2906ee2b11a7ee06a13384a216cbc9b5783d286b6549f0afad4e7f8f0b315443cb345c2bb0134a4a04dd275cba0c52eb6295c3f07710be59e7ec2b4ac37725bc32159c7639183ff39ec182085617147918809f6727fd5b6811929bc870db8c9491afa9cc0c6826a2458c7aed9ace74a19f2c07b5d78998b179be02c0953f9e50dcda1aa1fcfb74cdb85a5f44a0f31c33076788c36c86dbeca5966282998a61f732699da4c463ba0b56bde307ac2565700ca9624444316d448594e4a0b4a080de0437c5753eeed607cc855074c21885ce62751a8da5cd0066c9d29e023e07b888a6647b7219242779729d398a6142962dd274b89ca7610e47d4efbee32b9a985248cd9f723c912c422d25d38498743fd5d7deed7ded4693d7ab7f7b61a1c169b614d975400340932e806110e55daafbd514fd77e7a7916c6dce8e6ea768544804174edfd1c44c7b5bb37085f8ae5748e87b93aea521d7d9952008fb7d1d5444f6902be0ab938b12480a0ee5f7ede11b22a794e5d543b8c5bafae6ca418bedb00af8235797f387aed144b04c4124e992943708771d080de2a76d8018250939a0a60051da2a9ec317e83b220cd9afc14ae40a86a166acd04d21f74a3de6a3574c386cd03e0933a9e68594847190041d075db4ce052bfc03adff4178bd759d96e3977d43b3096e805dd77606ff4b95d52e679d27a16ee00adae62e8170ac494ee30606d2c10c0f2dec5cf432bb6c3478235186b15887627e1c35f50a3aab58273a978f157e464d41166a01ff83181a01b3930449b0005edcc90d46c8500e5f0c306debf6d1fea5981b13d048ea297048e7b7082b83657386686a6d3de52e07d70b7c240b212e7a39022680614d807e1ae57bbc343aaa6ef4408105cdb423cc97ff745a763ca3c99f5518bc3462f602deb001f3ac514d7f8e20872d21e7e090c23b07cfae2ece19b0f9a8c5e915f4a7709647033ce2c3e0b1e11bb66dedfcd21818f795b8de4949dba5ce79923c84c5c01e1d4e99ae0f0965c586619d230d23747039028b775d2668db972ac95f8ae0a24053e94d521f5ea166f1e8ed77a9c490c0f5d008a5cd9b3bb84eb5acbf151c7eeb212fd385f65b097166628d04c1c60e49ae0a74693df2e63e182cde6a93b4f2059d807608e5f999d4a74e73d31f8ded10196fc1c1f7c83a085adccd0de03f9e2299bfe8ab8b620ecbc5d694f3659679c0ce5377d761c7e726d83cc240b6fe97f0020b1c11b4408da965e1cfb4656f5675e907ea456dfb023f03c5cab2f7582a5a0d09330404e70f3e48484b9e1cb749b9b955af41e24f01c06ce01c789d5eef99b1c1623000e3375dd9c9f5ece3ecb56c82b4fa11dbeeeb4a07f8db2eba3f1f38ad421035a3495bd6dad6b652847d94a85b6ae8df6fa488c136a2b47f850d12c35c44efb2a7d12350fcd7e06d7bce8b3ae353309cf70d31101f4a876adcd4a3c08013091926f85c388bb0094b042457900f118312eb23410609c10e0c845802d48cb1fe2bc1e812b65078317286919e02a576bc14bb853668db09480bee99bc89c4cc81f7f2b47a7e89f0f638d978debfb88d04f3cf619ba08dc4b0ff2e187cc36620198b9256d9742b0208843b604666391b0f3fa319ce6d522f42555475dfdf786578bd88c92cdd03fbccbdfe3f35420034f42b4d90fed9ce202f131af7410e12a8fda60af2860686a1b037a51c3e3849b07b797cd42c44a2cbee983a281e824fd7f2125a4e4c7d3d3cf5bf2f8a4b95c20209a71a64025906d169a3600f4c8df80bccaca4bd96e8a8c62b14c685dc51f4f007e8a674ef67805604e8ec0beb36d43b482b4ab4898407690bb5fc11f0774b72d11548361b769183b29b90b035aa75e1ade321eac87378f12d0ef035c5a026a83429248101085de741ff76805f00040135c905350a22bfbff3fdf67e53f3f50eb4b2ea3b091efc8c10cb9a669fab98b6c9b818f086cb09fb0b3446fd73216a3d6604244f6de7bef2da59429bc081d099f08c337564d94e82d124f3be78dc470a13215a659c59cbbc1f2fb355ae2f4360ac2e45692d0169a62eb9c56364d2a9974cbcbab80403128bf2dc72888d4dad335dd9c73bb4b31878a39e7dc8e51cce19873ce5fbc2c6777276ee397b393132b81b112d8d4e57dabd465d9bd21cbd0bd808f874aab9885abdeef56566db7b2b2ea7a5815e7be81cab49f524a0d021bc1405508947233bcea77a887b977e54f79156740767261de1c3f4ff37149bbb7dcf34f09536fca39e79cf90fcb6b027e3dfc8fe2efa297d16ba7bc52e94d8a0769cc5881e146401246921a0f7ee83f2c749d011febfe6954766f9ee61f1cabcd5f453b9381b28906f9385a77b2950e7d7651ada344db44b1a087a260d01f513f9f2683e814744d9bcc97674e479498012ae57b9037346ce0ec49a3c54f11a8a94342bc84e901442e88dcd7192183f073ec30b11405140d42e1a21286d0af94df58062289a15328cf983c297dda9cf274b1e3e5080f0ef85cfab505b629f3b6a3501244727c164982fa530904205b3a983427502302eea21aa075d1ecc62a2f8a07ea4f91525400e8a328b4ca07b9881334f3791178e4650c06452d452d401545ff2e4ef4087d9203ed71c3e70941c9750c45aff28c3085032881e0faf62892d2610a183649fec09192255f1a126d42245a051521040ed0f5055327276591dede1f3d892ab94fe84e97224c0a197ac4d7c1924991f4a065c044d3445f28344c149abe1a495bb8d065896633bf882a51344fa1f077bea099929cb78326fa4c720089ce89fe1a3d089a95746876fb3e7a08edb804279a1f092345af2645f3222f2eb4c9525c9e436d60de180293c7ceadfde1e7824bd10d96f8c021060a7d487b510ea25ed0428cfe925476b222da00fdb82f994d18f4f5e47c64a8a411a28cf4efa1e456649125477a28e159b406be91019411142c521ae561f113bde0010a439fee4a56f421e4e80b4284e5501408dd008431fc4cf4e55c100dc142bb248a22896280e245f31e23f4f934880e008d1394c7901d9dfcc7033851bc426f26f488ee1b18a029021d8267cf10337712ddbd210cc232c90d0c5eee13127d137d6a14cd3872922868d7cfbd9e235cd0dfcb547edf7ebbb2756f566d67cc134f5321502ae65d8a4bed4781eab20498c4e743044867ea20d2212cf200ea86163b53889cc900d012278ea220a0ab5c22443a57ca5a1c41bfefe9408a56d882aa31d296fb7c803e203c71fae2b16caae6bdb371f57a6b4c1045392467e73770ee1f97a0cb6398780b077bb5e9a12acc7bbfb749db7e98baaba4bdedcadd2443b75123d9597431ada28b6eb1091d8c6dd4d08ce8a8c7288f4cca3c46a83cc6c35899c708c7303151a5eaaf73a6a6be06d7ad0aa90e48e1aeaaaa5099f5e6f1af8b6d1936e2f4d7395350c68e53a1f2c6ebb676825ed004eb046bac76cefbba6e1937a0b1ba83ef9b1e633eb0328f39f29ea66544592fe7dd80b3ebf6b23df7758536c2d025b2dab57218c7f036c25c0fb34e9fc1181f77c19be5b2dadb745ddbb58b69755d5be2dbc5dfcdb4514caf89101b5eadbffb5ea153fc3a0c5dfcb930233211d7f430ffda271a387f69892754b71fc54ad8c5baed3074b1ed2ecc5a293d4ca1876560e5c789a9b9498ea237e4ae0a3392fdd32d0787e0e20cf1cb8d60286e75de53db568a65599665d9766d3534231931e6d77f4c19eec3741fb662e0f5d7d75fd77df8eb8fb5aa6d5015dbac53573bdcdbe01aa43aa04d59e1ca0aa94cb46ca73a00aab3c2b5433db1ac50090765d8885735f88fe5d4572761f693f5e861ee4cfd67a787b9bb59d5ad661ac1b693d25eaf5d953d8f66dbac97f3b85098f1a0ec9bfc0556724348943f43766eb631c77141b277a78bcb6e8ec32392786f3d5ee50cace46e7bf1eefc4def71372dd47a5c882eeabc3604b835b3fb5ea28b4a3c3ddc5dc87ed3458d441735223ddc3df3d8f37beefedbbdc5032b5fcf6d4f55d7755db5d65a6b61951eae420dd56d880cf9d19387d7065666bc7f6a5266bcb50b1e7007a5c2eccdf162b675b390f7eeb66a7f55b7ddad9c34d56c2a84dfb07b0b0bf8387fe65cdfbd55cf6ab85fe9224f4b5321f08a28a5e6cc25c5da5397b3fd2daeaadad7becad8f1ee69c7781566b761091a2c18586915eb2b0f6acfdcd5fa0dbae757d5fe9a585b3b5015a641b76b0d79d7bcb3ed0a02dcf6b5ea63b815025b37b0128eb5e64a96555355edcf735f5f9f5ebcb78c35de3d4d378be8f1f0f21e6f90dc08757286dcac949a2eb6d0bf673ddb78a17fcf82b29c36e69713e7a7d03f4e1797c438c6dba5faf76ce8c32a188f6ddf69e8e284feddc536a67844398b2e02fbf7cc65f974710ca71ed5bf9fa8236e63a6abd2567109379805e961ee172c0433ddde90f7bf04bea7faf71c2557d1c5d5155675a9a092aabf6e50497ca5de5e1594f154c134620c0b739c1ee65798e12804f3980947a90e1f8f461b3a45f63141a8845f2825a4a1876a0ccc514e3dcae98b1e3a093397a0ae42d761b6f7dc897ebe4ea83c0a951f0797d04309415d450f7516bd4177d8aaad8353ec6dacb2ba722504d5182aa8c6ef5470f97a710cb6b7addee3c56e87b1ddce421b5d27b73f3f6caa28a1bb5d842e4ee86e2fb18d16badb536ce385eef6067411aabbbabb4a18d895400a5dd92b74e590aa2b897155571e63b743052f042d047393bd410bd9427411aa1fab84ca23914aa8240e2b0895c34e41a8ec40a050098c122a6129a19295ea4a388e928aeabfa384caa83e15cc721e4bf709c1bc440f759710cc48a482bf1e151cc650c11ceb376d64a137e86ea6879aa9a94705d9184a0f757f62fb5350c9c241e5e33905956f480924c64a2d8415e6257a58054916f246a59f08bd1f84db38ddc6ad5935a1a317d4348973bfa0a6c9076d9a360d5c1ba78dd3c6690bb6705a382d9cc75373bc6d1d38f7b60e5c001d5809ffbe5832302f808b89588db1f6d0d2617f30b0aa5b49d18173ea23dd696f7f61fcd7056edb56eb586b3d4cf9dad7d78717f30978cc6fb0597f71230737460f79efc4384d5341294ea475298fdba3c1450f79163de4ffff99cb979753420f391dacac8a3990773b447ef292dc44e8a55dd3b269e3b469da353d3bde8f8342e3df2990296ff7d27f617e6362a49d8fe037763ee0fdf76deafe84632d6c6add35e77ca3793fe71b31b43086db3f56395f59beba7cb76bde5de9b2c2189b73cef7d64a13b9ebce27d83d7daedd461a7c1936e25428c3860c609c85dad5c1186a4f53b11d6315da88b932a8d4b9a3793fe7bc956cdf69dbdfc456f2e67302ddd59ef2a00c601c4328c346dc761d94018c55e17397c831bbe35628c346bc0a63f4acaa42193b8ef1fde3d76acafcfa3ffc0bfed8fb06f5deefab5ffdaf2aa44aa0ed6e0c2aaeb086ee545a618ddd43e0d0790caa7bc6b17f6c0db6f10edd3d77adbb664ac1761e6cabc0ed99e537dce08e1063bbb7c136b73cb6fb8e1063bcefdf8b63f7dc7fc1b65f77eb636e5f836dd0ddb3b00dc7ee6ddfd1c75c2174f756d8e6d6c7defe4c0b6ce8c6b1fb8a14bab741b7ec96c7d8ee06db7684186bbb5b1e73fbab836fe74c5409f0fef615bb6761056e5f55fcd82baca057d0760d077fb36acf63bf2db08cef8b55610c2a3574a75283c32974cf4c5409f0aefb8afd636f664aa1b59ae3fd81d1c26ea30d1b16e8bebb05b9f3ee761b6d6e7d2c5da1f6b45b20b4b1a3b742b73e6681da59e12ab440cd3d15eefedd02352fb90e7e1638ecfd89817567b751f7d7cd8db7b971e18e311ddc9177f036372d3242e4be830bb568190cfcb878b092fa88a697f8e6bbb32ea4d8c9101c9bce6160377701a53448e4e36e5548143ea31e74d8b91e79f6fe5d5925c43fd7834c8f2f6adeef41139da8f6feff2fcab3ca2d44b991248a95bb499449b17257d163ca9d65bbad3117ae5a8f168427566a4451b152dbe1e5edc5bb6b3a55ada2e2c7c90719b48982684ca99bd8c54a5d254eacd45cf4173ab1528f59c7941a4e2ede587679f890c1216737274d11e1a18995596f4f43759bf62188f3315e62658e8b77967323a76e6fbd0c65122bb35bbc95d90797fdb6f6ca776324ae53be9ef773fc415914cfb9edcd5da023deddea4a78ccd03cc7e32e6e8a7f8ec7a0385703ad4801457448a092be74b133a66fbcf9900344c8ca8f4350c6b4a23937bb391e94549eae39ff3811fe418195bf38f79f14708e8720ad3f53d8447237a82c5192244e0a902845e686b031034987063cf034a25f12b8775213beb63c0dbcd43d2233b61e8ad74474f19aa8cee96c043d447566de289233b6b26afc733ce6f07879fa8b77908df9f3977a29ce5f8af7a7113fdc08d5550dfee2f7b79a66bd3f6dda9d5df3affdd3201cbffb55331389391e6462cd44a280fcc3efef37a5ec33154048d0d3a550f14611577d9eae81141e8e4fb782458a067451aa8ae8058c7f8ec79378c73fc78349ac04c6828cbc7578fc70f1f06873db587ad53c6a15257213097aa37972cc7125cdbd38e0de265e0c5da973d22242e266b023c80b66c78e57cca52e793d16d12174650f123a774a1b37ca99b70d98f7822c3ea0e848a247c71037900e371748df6b44521a46ee0586f2f4690185f04279f316e7ee11e3fee8a0ed23a5d0122f45e4da3078dddaa2af103d48772e062310fac3a60e519c570335ee9d0fdc362e6f172a21704026033a397cb86272e478ed546a0739ba842848a011f0b81c80e0480e4d19b7afcb9bc8ca3ba5499323717000a9844307df5e194873146930c84d8f9b47c78dc4e6d561a64df4e5111657ca13178b062fa1a890974a94470b89cf6514298edd9f42ef103e47eab8716e5e1f68de35615c365a9e4479db94fc2162e90277c81c14ea1e99d44a6ad40a12e18e9f3276a4c0797ba4f93106ba658429853678f5bcb961fa7ae0e8c3f132a1e42ec1eb73c8092037cf9d778e9c406b0c9169f73c70ab5401c3a41dc1884b84ef87ab86afadcdb3b4c1a3172ed18b45d0983c7473dc2f7321c071f1e2bab9f25a71d23242e2eeb814e44d7269477be9d29abc408bf008bd4af6b88de8ceb4e173c6f502e67d02b75982c02d14574ec94ba4c8cbc15e0f97ca5e8ed691a5d60fa457cb22974b21974f9fb7889d0ce2b493d6bc7465de2d5e166169f1a0b887945829f2bab1d4c3cd6229472b82a5770e92eb64d106855e11fab83cecb47d71ded6bc7eca1cf2d2ea6179df407107299954e4c553a9870b572987abc4526b07a9fdb3a8834283fab81fd879f7c441b2a64899378b17571116770e0a25256da4222d214a3d5e1028e53064a9bd83d467915ba7d01ba60f0f3bae993866d6bc5fcac8f1e20ac1f27a00e58a123b45de38bd1e7ef472b8462cb95b20bd4b16bd1814baeb53c88e5d1c286beacae4f0f29ec1e2a6815249c9db4191966f520f2d937244b1e452820467517ba8d01eccf6010166ed5892e3269973d19c791189a9c205909517fa2416f446d25bac64bb9c241facd0aaeb2e080b8aeeff2ad59e73cef907ebc92c9bf70fce6c865916867f3fae2ad338a79bbfc17a98fb050fffb8f0a78752b0aaaa733ceae24fd3746bdd97f62b022b7f3f0ca4f6c419895d6fb4ca9a5b75201e891bb28e64a5b55c242c14d8addf0fe0748aab07f3272e93481ac44aabf651a420c4a32e3efb04a68a5f2e2f2f4f51ac043a7af3fe30e5d1314d885f2e8f8c223a202b2622cfcbdab2eb3a85893502af793d5e152806ccd3a1a6404a3f5e05040219fd36a441bf2aa0cb42baf3833417af2d10d88790cac44ae26b97a6ebbaaeac2b07e6f1cb411263a5896943e2e0fd417a735f483d5a770ccc14bfdc23be970cecc62ff728efdd01a77781601516db756f2ae2e3c460bde689c3302b04bad1476660f8d197560a665df6519578851f4189d75eb5aeeb706ba6323f4471bf1cc90a7eb4245655a22af748c887b1f00f8a8f33fa4f59e2d40f98aaaa3accaaaac84eac0a30a6aa42e5aa762f554d215555555508a77c6b20f12755e508d213586de12a965d5d0fe0347e39478dda468762570447825e49626efc728ef0b88995433abd497c1191454aee388fd4f2491def18451def78d7811485c02ae213f5de81502486558426e69ca71c08c72fe76883154fba1d65c0eaad76ae724742524717ac299e5eac24d225a2938ab3d44fbd5184de903bbb55390c3bedb94b1df5307b1003e2287ee1c06bfc72788a62a554d51591c50b21cea313fb948aef7ef400480c0f4d148ce7e52705efdf784e62e5b08a568155786f4c3fa058bfeb93f0de496addbb42516daaeff2fc58a0b02f82d5f8e5eeea008dc2bbb8a7bb2d4f7754d8bb27e9dd12f52e83f56e08976be42850ac24fe04bd71fcbde049b012e650f1cb3502a1919a58198a96086708d0a8479cbd3de40632b204d132e50d98bc3d1276126031dc5220490ebbbdab1f7418b408b9b1e7c1d0ded3990586dcc531f0fcfc5892d437c383bd33707e10a8b3e407cb1adf549b246ce0fcfff3c9b19466840a74f6870a2302fdffd57f4ea5d8e6d30947e971a275f5e1282b709413388a02386aed5df43366dfd087bd27cdd09c4bb4ee44eb4fb4dea14613d944c228eb48b4feac41064721101522aa031c85e5c2457fb1442b434b736ee81f3cf461375b26fb070f7d89f622491641565616047b76ff3d0c868203437981a1a07c0f737bd8f32c99d6bbebe209e3055ab3f7e75932cd451246d913a9f621f8e1c3f387a1cc602811ffdd89f66423ebdd89d697c9b49e84f184a1faff339bc901f41a643d698636019a59673efb734d2df97c22b7044f3e9ccdac6793e94c4b43aa39b5cc924d240ca7d6b3c9746a25cbd0d29c271e7e3af634e493f9ecc85e23ed59966c369965fdc95c7667926956b6643beb4ba613ad895c53639675b3a419da04cce61a5633e964839facfeff7b98beb14ca6d140aa690888674b34144814046029335a1631ebcc7b399f4de49a99d740d440d641f40888109cb191c1c14ecc0dc31acf35b5640b275ad6191b59100feae257b5f5f9f30ff9d726d92c7dcaeb3be574426eca2c1737fe7ee31756cdb229df39ff94ce7afd39011e22ffda217d27357fce394dbdf4ab3fe7bc75899cd7ac73deea0074d679d5130490b39ad7d799296b6b551dba3d73adb990cc73d66e8ea473fb69e67a277356ea9c35ab80a6d259ab5ce7453a6755e704f254ce5bb36bd2bd36e509394b6077de20ebacea9cf56e9135775b2ffdcb20d89c5aed0f79d59a6f6d57ebbcc1a99829e05b2f899d423f7f2d25f33de4a9cefbd30c32a1fcfa6118fefc99679d1b655673c859a75aef9cbbb25666f34f6bad5500640d5a739d893beba5c066c84c5015f4919c6a0a99e79d5fa7d0ddcdfc359afadd45feb13fb826bf181bbdb39af99a99f246a2351bb6f37167ad79d7bf7698d39c5722dfc240ccda55591d96b5eeb3d5dc6abe27c842cd96f3c6caedab33bb662da62fe80bdac2fefdd929ab5a67260e6fae2968adb39af9596eadf29bea9ce62663ce39672a6d627d5b9dc9d8dfaa730299cd79ebcc73896c4183e92afdea55a779679df5ab579e77ba7336e69d87b9cdbf9c73877c41bb7bcd2ccf3ae7b09cd5095a2a739d539ec1f49bb7be90f7d69a6b9ec1b4ce1558ae73ce61d98276d79d39cb73cec69e811c580f9b8034efdc660ecc6d1bf6424075210bc0b3d6aa5e931996407c4be4acb3ce3963fdfa6bffc08d8ce740a3ef0e384144e740d0901d0348421d283048d0af08f279154eea245520e9bc009cc06d2080201381049c28f15220bdc44e38b900a904b28a08e604a805866cc2621cc464650cbab938285f2abae8e3a0d8f93ecb85a50b582eaae82b415398579dc88180265777185cf41f863e05f54da2227817e990ef3cc7ff7e16c3ff4ff1ff153cfc3e9366cc32b4251b536e12fee70372320703ff7902abff2ce3f76697c6d0cc0c5963c330a9c674b670a2656d1892b1395b98256394256194653d97c89c2ba519808a15260a3021a245012654b468200e40ff00a26f182e936959676c6433d2d626f87f6207d19d6c4bb467fbffb6875937c40ea23f9748b52c3367336bc97c816c9a69d578917c229b4fa75659d28ceb9976a6a5c6b64c554c6ae1e76e4ede92911fa89f369314801b3e4a52ba9b92a38033aeac6f459387313b0f589050b4d2c2e68c4b862e32967732d0e344c780e55a0c4029d84049d0e2a1670f93e6193b3c20409265236b14893064d853ba6e508003810b1f39ee87b54327929d004f435a4e128e9776200ddaac0c406420023b51084a797ca81c61b43c323243a3c490ffbf11965a0c818e2c775101763c62a6f99ff1f5f6ef22359fb7b0fe2fb4417f3389fda3e8ff2bddffbbd11d92c0f934d2a07f2446a0ef3426f67947937f0df0ff46f4ff66ffeffe7f0ec0dad79065cee459d2ac8c0c09763416bfbcbab8b688ad910cffed0ff5f9ff49b6e633e94c439e399b59664c35a286c1b63a88de6cb29571e861cda4d38904033b8219c18a605f605e605d605c605b604430b0e3f1683c168f5f47af63d791ebb875241ec18c47a3d158347e19bd8c5d462ee3969168042b1e8bc662b1f855f42a7615b98a5b456211ecebf865fc2a7e7d7d797d757d717d6d7d11bfc0bc8e5e46afa2d797979757971797d79617d10bacebd865ec2a767d757975757571756d7511bbc0b88e5c46ae22d717971757171717d71617910b6cebb865dc2a6e7d6d796d756d716d6d6d11b7c08847a29158247e11bd885d442ee216914844fbffb58739dc3f0da2b399a5c6b6d47aaa9d259bccb2f6bf818779024f8696d6649221ffffc2ff5bf8ffaaffa7faff0aff6fe29fbf71fdf31d49b634e404dd45b339c2455f22fb92d95d6c18ae219b33966c6161cf34366752002eba59322d0d59f6ff148053ff3fe15feaffa3fe1fea1ffeff76aff046fe3ff0e16da38739cd966c4cb27ec6ec4f3666673e5b3cd1d2d69cffeff4ff4dfffbfec1437736b3b8b28c595b58cee4b3c6938d590379e2fffb7fcef2ef7f8efbff071ed6d07f67336b2beb2b8b8875fc7292699d6d11895f5b4832322e2e33e3d659118b8996e54c3e91cdb22c1a49b3255a8de6d389f622d9d462be6822cb9afef58dff577b58eb7736b39e6a674fe6b29ba199753564b2f974f6174bb41a69cda4d992ad27613cfb320963466f329bcd645f22bb5972d9647b269bcf65522d99b6a686d69f68c8e6b3bf58a23d9f48b526f3ac23ffbffdffccfefffadf64369b23fe3ffd7f0aff2fe3e1d7fb4f4ed06206b2f9f4f0fbf9ff19ccb4b366720266aff1442b43ae21cb9e7c8ef2ffcc87ffc43b9b595a359ac826195a9a5659d3a9458696468696a6c5b67c8145836d899686b59cc15cc658b26179155b148f34b088595bc7ac2d2de7120d09a32c8dc97c6aadb13d9d6c4d2d32ada41a1386d6ff333dfc58ff2fe1e107fe9b25d3d09067dd997cd620ebfd8cd9974cb427b2ef7ffeff3f1f01efe1610d81be9b2dd998671dabbb88a0d8d5afb820709184f1ec4bb49e444beb4f279b5a0f7b2ed15ad0403343365b349fce166acce61993d3a6da67a0fc07949f5039f4bc468ac253a069fa5b33f08972275b7cea17058b8a01949f6827b855c01cd0d601fec0bef03ed51f4b823daaeeee0074d33850760c548a45b4356e0e1ebd81a0291cad8007fa7b0acf405b60935e3ba45ae99b9b8497c2111cdc07d429e814dc584f5a2cf0a90f474a007b5c3d6ca7ed818d02dd01d8d2525d350b74bf6a057ef56eedcc7ffcca259112b03aa056505d55f993fa49b5c076c2eac0be8443adb0b9e048dd40075072b5de62000b3cece7a43ef1bcb13600788adf94aa570656aa15452aa129abef4ef96fefadf7bad9ddba557caf0aac504e29dca7555df35eb917d7e299a5b0a9a22d86140bb4294bf449e554ac081c3c100e618bfe7d7c87f6e8a943f7c60ddb0b1543848c180bf1c1c85cde35d2d2003bb12b040f2080a26f4e5d08188210911c11fb2481f2d0698346cc162a5a59198cb03d83389b911ce1f9b9533707e6860512b078a0010450144002c385a90970656e6ce9c0820a5a58a8622e3134050224430ddac73749120b08a6263ce569c3260d1a31613c0802e482161238c00002284cd099c3060433484af4e143a7cd1c9a3353a2f4d091c3115e9e3b68cec075698326cd4c8c7862c213c150a0377365c87cb132e447d2ac85042c2060c0c4880e611b9e98e8cc79b38192216d4933090c304162448717c1109d3970d89421f3c5032b4480b4456080890f1dc28cfcfb44300408ce1b367365c8786085c810203fda08b588804508adac3d4b270e706afa5325c4478f0618203ef9d1a3060d2d6ddfe6d397524bba128b855fd1563495cca5b7383167bd589b80dd2760d055996aadb02964209fca13da2828a80d73a75f13cc54e147e1ad729fda955557adaa69baf3ab6dee0071a06bbbb6bc892daeedb6f05b1145216487aacb1ef9cf29f39fda82cfd84cfb89ebc99104aa36e9b5e5574c168e1d40dd057e66fcaa29aabda8520578563da8817e3821e22987e62e3fb1611274041c1ce8a2390d7bc22016d5bcc2c65acb4110290177a0aa8a21459b04a7311dbb80280f288596819fd44f6a002424acb14e5712d4c3b32dc00511786d37531a835fe023caf226d54276ea8a1e7ad272c0d3c6ea1d69d26f2af6b482f103921070c111950e5d126e069505e7b082e1f8c0490138ae04990013db98ca4e6020077dd9ce94e460e272ca0b54ed0e2b8562d45dc154976dda4613056c0b2e0949507735c1a4822d41358284db96f8a930f3f001748a5f155d352c8a4975df114209a6405db6c405b21ed074c369561350ddb483ca01c4d8cbd66a604ba8ee4a8149446bc551b4535255192a4ce0c01270800032445d366bed62ea329a48b1fef85a210ddbc5f6c73574b1b55d5dae951ec08f6239a5dac17402b165cb36d8880e40d69dd01aa30040f17ab1436ec5add42b500e4d5c444a407aa1b5e2554e5bdb020e689656de671b8bef9402a10445ce30975b3aa08be3b2e54a140c6e5440c319e32c59e64202160f34b080022a529c30a044d8b16b986100013a6733d777677728048801841528ac4d409043654a022342845990f07b553938325aaa508902c549930d14186ad0ca529264858abebd3cb207ee1aa169c17c221204ed0c440777ad324488b1dda3c74a9530b0e294d4ebb26b159598302c926608a030c0048911615e5b1ba09e9a983807402ee00125e8cbcc960eaedc606001631128c5ebd0415a00c48762bbaa1eec20d3e58991223d2860c2010741104001a283865fb6b42b693d8ed71b6944fc901e940aca7c741e8da7bdf3abd375341d0da73dd85b740caecc5619036b21ad62a2a0a7b4842cf5149542e9a70cf326fda6ae6e5956af6baa79de3913f0ffee5780836e3e8d0c89e2041cdc4232d3ce5ad44033d352635b12fb07101df94c4b9a259f4e75c4f05301fcfff3e18ec03b9b59cb66f2f944ae69359fce082070375cb9c36105bd02f7caa5a0f7b07ed529e8bdf7feff1b347cd761c40ea23793309e4ca257f7d09d4fb41768cc65763e8c81c3ffbf40237bbef56737ac3f91dd2c19a30c6d895c4b2e99285cf42513edb986405696053616b8bcd932f94476a7052166cb246c4182131dc6f3c53299d6cf983dd9649e3d9bc8a7b3275f2c914dd8c2f0d3c8904f36350b4cdfc3cc9264bd861932d94c4306a0c96c3647cc60369bcc313906fceb3b14ffba904ed345dd0528ffc97eb26811cb4cd9ddb261abd83d78e85bc3eea177023ac072c50ab183e812fe733c4c01c93b9b596c4eb4e68b13dc699111344b328166ec11b1237140a10f912f85a8ac78d87ceabaa63658e2634cecb5e380bc58a2258188be20575b0a9aa021b19de50f96d5ee011571800403a0424747d55ecb32ec7bc83ae46d90da0a1c82955872b2e4b7c16f775002471121d190ad0d99ef4e87e10e9ec240dbd5de567b60017217466c49853cbda8cc04274e50892202b6d3749694d8d813858a080f8674982a7beaa86051c88b113f5a09a12660fc1cc1400cabd35124131e80444150452039fa4c4e9fa3c40e0b1a52e4f409b73d48538f84312138bd25c60f1a152b129d99f3454f90c42649130a12262cb2e43d294ad478f501f33140943c08100926663468d2a2d092dc26030e5f58543ad2c4cb5bcee2e64bc59c1b23117cf09189ec806007110894cca969cb33beac30e4a4c61fe00c1b99c5a39606055f2a88a1e399c79ea841d120c9dd0c03d2b2063eb9915985b51f8acc32139100a05d4da13ad4c0ff23b9082c501a936121c98ad793a30d0ba40296649f7f335c00258f3edc68a922e29f302a7bb255276749838777638280860c53498423bc5eed4e08c72858124161fcab086201d71c0a14b158fb3fc61d9943c19b558297ff0bd1106034f4dedab6ffad85c6a2b92c64bf3e4488eaa4c07d739688edf579a92366aa0142149257797d1af88093d33253b8c9b1ebb3004e04d68cc853854586faca2ef100f328c7c369f2d367e28a959506684e2276eafab20cb180e53810d04195d327a54108426ce828b891458e8f12085c065020b9b515068e2f10062c9e1e1a270931bef0c97d2062ad48902a4ae864e1b3a267a5051db767056f51f8824c096486c91811c60459c2f70c446986d5da060bccbcf13d6004850e1f43c86450c0075f8837707d2ebcd21055b4f10dc083ac459a66c3801e36f85c3060b454b9d2071df3bc740789905e11406b1c6548bb74070a96b306985af0916476894c9f2c6956159049696371a94991184ce69419e08d91159774ccfd11a1a1821d10c89cb814a3cd4d5da010d884e820e2528a2ec71796387c7501e2e1d2d79d978b648a431611bc2e5135d941078423688ee45d5dfa3c4f1d7106c011d6c66a6f04214614318f25902050b517470dfc560c80a5058c527b1ee0f000552382307318f8ed19e9408e8921623454a8f5f13d0a02910ba864aa99505a164346194a040000000002f316003028100a88048224cb812053f61e14000868c834503626114d0581280a82200642100661000060000680100660280830a6d20b1e40e5463e7904fd5aa929c8d05be7c07b02bff9833264ab8c1cd7afcc95bda7434395c3c0fbf27ef18012a03bcf4efa285f0d604cf11f982b8deacd2797c8a76a9fff81929a17b267d3c1d4da7b60dca9f9d72b3710234a2bfb11b471985dda5bde1c9dd68da73297f21284e05febe56570fef59dcba3708fb0fdf967b325f33b711a586bd9b46adde6ad074fd30c3187d42f2d166edff823e6ab1b177b8b1c768add890736cfdd4de4a6fe8c587a163faa9e44f27b829a62ade8532f104b81e9695b871e4bcfcdb77ea6940f06aca8b5281783e2c76695ee600141f3dd0ee323e18019beaadf52907e2446fc557526061f1e603517fe2c1ebb87debf91d7456150a5ab312d0d0d17d16fd81fd8c5713a3f390ef60900b036a47b8b27305a7f77012fee99a601df3d261ba7bfb1d72cf30e85789ace4df4d370fb73bcb54bdda0582c5d973f635a5bf6055a9125bd99f55277d368e67b0d6e83bb877e20fb68c15b430d5a2fb56259a7d627101000a7326b175df7ff63585bc6151a91e975f315e78a65eea2164fa37bbfb1d72be9167d91f45cf88fd8d14dd765bff1f1ca0ddc174b915222de0f4ebed414ecfb133aa05940deaa4c435b9169464c61983df519f06e34c16e1f00751a11ea17b0e5f3747bf4f57bf43ce347f7822c06d217d7fd1ba78cefcff1dc341744cbe885d376d527ef5fb739b9b1b2c35eadf478f88cf6e48997ed0a39bc63d99fa18147914dd9f4f45cde32c1de8e9fffe036d344d7d8946ff18962afe7dfd274f82b51eba352335bcaaa9b73e48b0d1cf93b690c48985f73bfac56f2efe6a4fa721ccdb31106684e6ef1510b7d0c358bf14abc41f48f4ba8ea07ffb02ad2afd5187e4221ff1bdafa755d6cf36128f0af7f98c04b5fead6ffc87cfa4fce4b557c0427d9996c4bcfb853cfb53f5337e315a9cf3189e253e9ccc434da3961eb288ebfb6596a861c7f598ab252f5bafacb528e785fe206b19ef48c2ec3c6424bc396d5e4b0d6c44de0c863fdcacddd9e88c802f2b29922d5f79fb877bf47bd0efe853cf03acc5968cabdeebfd0bbfe8b7a28d0ff41c1fb59605a4f14fed8af804ba15e375e51bd16886d2c1a3d3b39770d12130de7903fdaa53cb87bc4f0c4ab9aebfd3fff9b59673620e539cac60f55f97df849bf6ea3e961e620c59bf761841a7f2f7608b940543d0eae2d0e2e1d3b7896e3b67bacf22658ea01ad336bb8183548112b08b2b08952fb7f604834cfddc3cd9ffdf804f96f3145f613c94dd4e769dadcb2fce29003d00ff4a2e5630326b3f3daf56fe0c907006debd5c26d74b5be45629645f56fb9bcdb8be92147939ececb9ba7555239ae54d2718c72e8412ddee1d5882a8e0387a98772d43d7b5621778c5f2fd56364c555ca3d1bc17dff3e6d0d3b2c25f99a1f135e9166f75d24f5e2aaba58a36b5f47b197687e7bcd53a0dc47eda862d0df517ed3eaaad1daea3af942539e054fae318580ba839047e0a9fc3b6736f001d48bf78c69c217439fca48d7ad3cff0fdb76a023ffadb98f331a789bb404eca2a2597e91e4c1564d666d151d3cd45cb1f05542260f2a6c2e8de5952c1cd326547f7820d4760616a9a14e86cd75f5d042d6e19a40ac996257982b997b119ccdedca99bc4b08b35c31be75868355d837668703e6c6162dc15d38ce4dcdc28fa67df33aeb61a751fc9eb4dc10ab6c2fb28138dada0dbe760006bf68a5b8fb4215eb719db1f08b02d0cf86df1ad71a081a5d36b78400570bee3ab77ab09fed3f5c8733d05906db7dcd491e21d322a26772e220205f57c9be48de399469db95b0a614770f8b27839772b781eb7fa0b52e107d9c6c170ea762f6c131bb0bdaa407d527e0ae11165a31db23be78493b69f81361d7f6358099c8f34df6f41dcd8e37ffaa68a5542c04b6966a0584c2faafacb4be5d23cba3dcce35793ebdbfde383dea08fa7f1005a80f4fb66f89aaad57c1e2055983e17d0052f1a74d42331c0278ed5deffae83ffc7081dde3de650330ea44dcd7bb3e1a891589fc5841c030abc5262bed31aa097e3c71908a17d8fc36e854fdfb2dc6880d02f8f8e20bda08c33bf0fc16725025e98a06b868be42505182b3736ec5eba8048e62914b8af39d1fbd3b33da2a8aafc613a6a4ed75fd5aa9b0691b7cde30f0cc6769f23773825e538e9644a8d7fdc33bc39733ea59db21dc42a447f6e8e8155ead21d11af7bf8fd75579c2dec914818e725a8990c18e8d53eae8dd6298cd11772cfb57f22e8c570f36c38e8cea84ed1fc89dfa173f4c90e75ac4fd27a55702d9ec8fae57c654a5a61b78401d10dae10dcae313b3944d7eb0c389b81632d23591c223b3a5139e295f9a404f6530d8cb4f8582c1e338ab7f1b938baf8e8ed51ea8da8b64e791195ff154219f2b91f33f636c63b90c41541faa3c00c710af82bdb1be78153a4111183d8bfcdd7e982736499fbba52f883b6a39aa392812a1b71c21b4577a87a1625c15effe790ea3d659be1e4bb51a0b037564fcb56317828b693502dd807c0ec204694c6c5c43a9fd326d0d43283fd9032d255e60474bbdc50cee79cb64a5bdfba359e29310d57e6751d5f0ea93edf9677f346589e608e9d119b0745d3b0743a4d999c40dab630a1aeb046d0fb45bd2c44ea3e51c4d761000b77d85f4366a9711e3b85748f33267a5faf550958fe3562a55d717296081f20e79275a99cfbb8652c8661330314177b0662b791d48a277686a0e0c9c0b8966f83f489c08658e5aaf3838d8ffbed4ca5916020d8e4dad905ef3070aa07e06b1666316d693b8322c4af3502a3cc8c1f89ae9802fe52e0ef96c184cfc6310d30ac980e378f497c524941c84fcd0ffbf6625f439e812a541767cae3399305b883b6790b70b2b89308a1192eb071cbace6984c60b8f140c4b7a452cc99ff596b8a74bd498b57a8ddee460f515181bddfb37094764440f0d419a6481f16024fd6fe6ebbb8295e4473e6f467f4c01ee083e8ea49e817ae2e974a524fbec74014325bd28041624c84b980241de11c17c8658ed7da66fd5254101c2beabbccfef05e6c39955bdc25f75b8a64c0da3f87257987f2b44430df19e1cdf158e5ba09628aa44de0990a77e2d571a705e5153e16a7c7988cfbf40b884e0f574d4f95fd43003408cbc0ad35166a1654ddc110b430d430a7d60e2c7d3f4a676be101b4f1e7104343e5fb0b3c30c6176fd1d8204eedfc94678c06ae54c9702664d5086dc38870ce7f1d71f5c323a464411e001b43d85ec22f4ae980928eb5f368ca7a0d4766db81a463cd2e2577666129f7d10c4f1391d21f4a6f5e74146a046723f17299122f910788e6070cbe80d56d60e7e2cb446cfc101b2ef321d631616c159e6b0aa945dda677d5c7d28cff86b39c3948f5cac3db0111be7512ea6f43a46f7ebad44c7283fe9c226229cc6cac8fafce1abb10feca8cc9be3454b0585cc9f3f3d8fdad83b98c3317358db36f038056d660c60c848a06fbbc0162fb97e796723ffa6ec4cc50a01eed0de6170325513ddf8094c5a89f5c8f41604eab37924361f1061117ba95287ac1b8b47f0f83e680d6eb490c1577a52949f969ea97079a65b69b1d01ce6c356e90754f2163e272cdc11326b0aa54b04738d9f9f67cef6da196ddf6eb02c3fa175cf6ab942a076682c1da4e91d47b49627bcd4cf0075f0ce019ff0a85771011e16c0c6ac8f8a460f2757a1349714a0b77b9383fc884027eaa9201fdef8b403b867830cfa252517f86265bb90ea838d077bcb12f0f1be36411d1542f08fb4b443af35c9c5ea25d00dd49df0e49b296d42a6a7e8671c7533065f49fa23f83ccb7139d2eb009a50680f3307984cfa9910d5d06a6f10e86720fec894a2b62c5cbaf0a22b199e1b57389f57513896c78a75955ebf886413753308fd176db0521eef100d396ac7e40aca1a492df60229c6744b792ce8c13f93e912e178e45328c78f5ce789614b68ec4aa6497b410fc4df31e50294e510813d6a6d00fc3a808c97fb254c811e3e007b08ce9a6502d567552ff01902f6d08410df18be80586183946780cb1dcc21aad9081da4f1824774eb135d544e88779150137490c7238c51cd8fc41cd556b0e344bbad067a5d531714b4d91ec15db142b86f810c79d52abc8b0a5be8419c1fba029fb66b83fa9cf8c70c3227e15f1ee669f871802cd079f41b079c73400402ab8fb80eaab0463c0185ceb43b41991b61fb0b42db5fa355b5f5d7166c1cc1a78f2f2f4b4134f73007b6adf657048b470f510f8298ef0fc31c62787b128e9c4b24b88a866a2e000d8f1084959107d1b8accc33ee016c45f0367800365aff07fa6758816c5eb2be2233577bc1167e27dfe7cf8970ba576dd94fa17c1a5c48e825350bed1e18cc4881c10ce0982f901c7af88fa7bc422c9e726570eb931a7b749238d51c9a8d7e905dd0d5b2623ec17e66472ab1829356abedc74c15f44476a792e0b83c3186992b907e4d2ab6dcca9b7648fe2f64c31293f820e455f85deeb84562a18ec32ab3a0a536233959f78e0f564abe07113e49c9ee23e21936993fb434b84adc7d4d67dcad40f34f241d0eb1a4fd97abf0f84e4892d56417e20a49c1f733e46dd3332bebfe3875eba7b385d75f4fec7aea4854faeefda09df9d0f64be3d131fdf77782601cc6d4cbf1e89df211baa73157cd091d793a81bbbf5a658cb578bc84c9073016add5dae1c713de09145b7fe0214f929ca11abbb24dce50af18dcfae5591b643185d1b17b523b26d73a76ccb7ac13d53199e80823579d8e264196f7a84426b686da26e8b5d0bf8c090750f8082c55edc9519b0dc7f4c466919b4ad973cb62ee172484a3733cc44dc01ca60382052c8ec87631ce2f455ddf80825cd18f6157f8ce0d945c57559e10ad4c70dc718b5a07483b92319f2bc764f0b961b098db88ba7477565a1e5e54f33c02f91984ee4c0452f238e7d222324bbe542a295b689e0e9d27a09d53d840d362d97c2c02514bb5ba7471023ce7135dcc6d332c8c9a9a1c31b55db0254e1f4839458096c3a15e8080318c87e32881a9b2b729b6f8463c9e022d9298c6d61b8fd310de019dec9886d07742c039c7652f1aee2eb87df813f6f8d17089d46193e68e85966b381280e06104414bc9d408c5c57047ae877801e0743372e76fd9ffb766b22067fff981c4cbc3e9a1c0d57accca511585ce90916167deba91da9c2377ed690fce05e30664b25768db5801b8f5a8b137bfc66af87cc44a20bd59f927b0f634730267230b8e9cc8f0fb1b189e0d73e24f52ea233246732bf105492d2e2f5b5a222bb12725d123170061db7b6e36295336b49ff8ba57e621fc648e1f118724270c3bf871ed83f37a0e6cc22d0dcf769100746745a101b5ea79852fe0baeb2a5a30c849403f732d7457329bf2a136f2bc739b7a11bfb32530f582e1dcba272f7af230b51864adb7e5fe1b217fee815f54f4be605e933ce08011e2a9918ab4222dc2c555be5d54f06f1a27a383c54957a6f4631c29d52753d78ef86da17a4f42f5a5077fee85f501e1d7405f818b3bd740bbee1385fad824b2bb02a7c46047ae826b331ee13e05f8300e61f5dcb69df1cf74d808401c2a3e0d730917e07420e615ad42fc3d6387f08736f967abfba90c7a5186f98795b15c1558012fd7034708f54f0084b3f498406b52c687242e856c90f585c955f49dc09c3c21d9555f15940afb985620a29b2e449f56bfd6a80afccff28110fa3dcf1cea372c388d1a03863318398efd9d39821c3d7211dd67eb093986dd71509ae420088d03667420dc85a6c3d62a21f907b17baf86cc5130cfc3d7ba0c94251a4ce601cab58543fbac8da80090ff546f83c8061db5a8222f4c54d6eb2ac5e49e1f7fe24a07df23b1b47fe24d30cc8b3ed08ece303c2034cf4e5c51e10ea86e5454106bd977060699a0442efbdb282d84c945578296a9481ff5aa9d521f0ae3b28ef2a31d272ea44f7216621c941c95572010ece7914b9dea9d8f4d3592947c2614e18545fe3e90b7c40a1509b562619cc8b00e47b87c16a4d408239d9483678cb79d48b88509d3c224126ed2ad5921a6db66d82d4d65b9c97506abd8db8b328fd39b039b6c3c616ad49c6fe427f11e02475e7a82181da3123a666c8dbf77516661b687d867d8c7411e0330fd11055b4fa278f95e60ea6417ef9fb964c85c3634a224f4531a0a455a59eaacd3fc23f9887feaae8ddf4adbe9088c905aabf170c5339ef1636bbcf6e1c68693fecf1356d743432e8da0135685aeab8185b4910d48413c603ed581f6117b53da9a1dad168b48b65d6b0a44d27d9959f16a357c9a300b04cd6c0165aeb884c352f2fb27c706423dd57fa75ec3a637c9dacf206afe1a5da71a124bfd1aa7b378b105ccb792faa85ba744fd53c43cde9f0010c4a559fd152b2ec0e816029c86d0020bda715212f9031feb9c3e4a201155af8e4dc27860130089786a0ccd909474f8171639d2232e10d1c3f00a815d3e1b4a096b29d8eed74b86494e75a6ff85606709cf95ecb08ec1c2f619f0db576c8f3e3b25efb7410a6907e273fa62bf6d8aabbd7deae3b2fffee18fb6940d0d3fa93e04d684e3210eac286d35520eef47997732e1be70b5591a613e903e4a9797477bd04566deb1c35aaba62fa983759e7ac5b54c984c5e5d288245b0353aaf60ab634576c98f77edaae382370b64bf079b5cb9e2da6f88239f4edc60ca26405c29d9b594c7d249bfab7cc3b53fa183f1920238718302b0a3477e6c12d9153f6b6e6c317a010603a3d5d1991040ac1832b2d1908e0ad74032b38860d76db59fb8218634171b190ba35c8eaa5b10b519520b93121a9879e21a820772abbf20bb3803f8a3fe36c4e24dc5b81883c189606702e6fb372890e1a2a4ab115c970b441c68b9975cf9e0f50c12d3865f21254c1a0a2cd7ebefa955232c1112cf829ec88f803cc955d0dced5a9066e7bb872295eb0918fcf37c7a9ce7ddc04591b57726dbea0ae8a3032acc095798166c300036d3137708f2e839ea73d7da8a6d8dff768b6db82c2215a49d7e01699ba0b63bf8852715293ee8d67130b64b636d8d802f2a09b4df27dacdb4074c0ba15f72f130cca65e23158f7a37d356f6b59ebde652190ad334f7544843e2b17c89839383f3aca3477fb9e5f6cfb497df4056a42b7f40d3b443c6dfd2e98da940cf3bdb3ce2c4a34938b1301f741bfbd29adcbf740386e8250bc72dbfc2ccc1176ba6b27b929a70d1f0691107e44fbb436497deb6e36a46e35d434e63e866d3c57979916165e99d515c299de0471673c5b135cd89117a57dc34707b4e310875903a5461eeb7e9ff3bdb00029b017c62c84cb3cab469b2eb8c35b263adae9add798df1c7e0eefaca41c04003669c5c71447d80ae78269ba13405f3126181131538e6024097cace3a1480b8f2e9c9926b5b82a594655d7ecfaf4cd3c2c7b2f2bffce09b226c9a77ff849ed257edc3a6f0c2c167c8454cd5afe2d906515ff2cf10784b0faa8260a9e7cd01b964842a189786620c15a58bd5f9c1fe77e02859a90ad44a773bff8a333d80ba1cffcc1fd07f058e127955ed2fb04b4e76f11ba7e7a1fc2146889105601aa7ffff37fb5ffcb87ff77ffcdf802f796f08584bef5641b7d471d68050a2558d4c05582905974b97abce6ff94d8c763520732c148a5d5a22a896a91dae996cb114502ed7e3c3671d3094e267c80d32b6aadb68284bcdea37e38fe02ef559155c25379841532947e1fedcfe102d4b9296f9f57fc52fc91fc1a334f6556094266716674abe91ecb5705324544ae1169ac8da8c1f0aff42b104b40af8e9ffec2fe85fe06ff02db9b721784a13af8261a9e86630962ad626fe175ea5a9544151ba364a3feb7ffe497f72bff01fe43788c980617b49a04406196262325c20e9d85b2598960028c9da4e7f92962512ac82a9a460c3ff51a60845b20a1c4b952a609d0a8c4b466d03368989ddffa6bffd3f9a48b6b47f8fe05d32d95b7f8373a96ca6d0283d4e15c44adda80879dad0bdc2b224205b08b04472e6e6e652f124d538915cd355e95ff5dffe97bf21ff0666e95e9df831ff44fd179ea5f255f96dfcd3f42328958ad6f8979a29c1a935e23f215592ac2131f5b9446f375fff066c693a0ca2ade452d8fe709910f8c6be350e3de8767f59da5069f52f6c94ba6ad7effeeffedc9ff707fe6fd0977a6a8b1f9d3f02bfc4aaedff16f8961cabfbd79869620e950140495ee5bca906e9a555a6c2a5cddae5aeb72df878f8017e49ab0c0159b2585bfd0deed298da84ae521faac05cca5bfed7992e2ed4afe0d7fa95ff44fd080ea5db3afd71d3f465463f02bc84b6ce3fe61fd57ffc8f68329082291c4b2956f05b499b02844fa26c03e2c596180377c9519de92fc04a935a088c12ce8afd75fceb7e74ffb99ffd3fff8f301948b1152c94bc5205a592abd5a63b33feb969607b91f8e67c6709d025f595f67f034f895bdbf617bca58fedf7371096c6c310402506548150f27a33184a156b937e049752a50af2af81bb44145550965cacf6bffec6bff447fcc7fd079a12c4580f7f0cd4a53336ff8bbfd8bf8049231163070ca57a45c8d44c880a88cbd2123211d1002f4963cc81bd52fa86c02f81d4067f88e692b135ee87dadfc0ac242a1e981c50a706fdd43f27eb8aaf401ae5d284b5799381c69ad5a44f79a5a6c43dd83f7b5dcad9b1317acf8064f27cdacc7a033557ce7e7fd007d63a4a7bccbc205a7181c7ad3ffa3e27c1db5b212e0fcdffb2a4f8dcb3d0516ef1782f410a98cab37866cdd0e66414c2286025669a6c4627a9308ac9053c8b0bdb44b4eb145be66506fb30174d0bd099c11f741342921a0233843499d9b8f729273c86afecbc1c111e864d1c9658ae0ae492172a210cf15a80814629ad39a832e092758af3bb1c4ab1f33b7ff54d0e0893c4d804bdf9a5fab5246c01f863fe4f3828818b437fc1a4646cc3bffe974da38a8d7e04b5d2cc0ac96490c461aa8112898cba42ef846b3ba67011d9eb9d7ccd41f1e9940e4e2c3c05a34dea701e556fd06c8b99899f14f18a9e3fa89a6b7de77afbd31c55b03c24fb4bc2d9aab9baa5e8704699591a1d91890bad709c9373d6c5f546a0fa860f540a67c4fef9e1ed147a8cbb427b75e8a09e0ad98ec5fd1506bf5680b3381c400ae38e73d41414d1cef5d1f70ee1510f908b71cf54d9486fb25dcb27cb8e5d52fbd5574a29d6aec890a7b3339d913a34f320bc3d2c3800dafecf19858cf1ea249143af596a7e8604f619a234b3b05e606210cf9064b928cfd259c1649d0d347bb406761802f4ec6d9fcf2048f566429dcfe0d286f613e84c7448f3536895e64f0353413d663b0cfede59067f350c8556e3f4c7941f181a6b598cd424e86e9e2bcda12389934188b28663f1dc09b7e14144d13fe10e0425c9fcd58070ddb0121caa5e124f0dfa89c9642bcbe95781e96ec27fd84df06d803912ac8f108759281f040483cb7e1c6e9a0c4c2c7238e5a592de74606bad4d0866b304a0c495fc44a1126572b7dd4f9c7c1f8eba836bc64c43b58cf9d0b1069e518c9f6afb02016521669eb697f00e1baba2ed0e8ca4ecc6ea20ce420bf52f3a015053c35c1a93234e16363bc4281f19e1e643e428f3073e24e956f1a037b4bc925d6560f68b9e62974eb4c82c2eb6129e2f1950eac5db4bdbe495c90a7127941b902702a8d3233c62fd05fa949ea8a212d766d00f6c8c5d0a80b2a7634e3cbe7c2a5699b369b6bca47cb981c1ea109bc74c373517f9cc96501676749727a9c803cc2b205df12fd4eb9c4571b6044da46f8f41c6a87af54ff09e92d40dd58bdf05451510899191ffb08bdcabe69c037098cb8029330bf01fba289107ec399d16bff93df91a845c07bdee4afea1f4a4c6824d45e4dac4ee6d8f9c861a6b58c285a65df6a6a60ab831fb038c159261e1aed74cacdd8d11ba0aa525fefec23e5c183b4d5da5d58616d780a57a07a4eb7c3d68b77382f20534441dfba1a053a48e8188ef0f014bb93267d99a6db85f037ac20b00e606556d3ddc91cb46940202be7ee95aa1074d5d82eefae772b3487a53ee66fea994cbdca159708448f785733e21e556c9af3873f12b7ef0cb5c395e6687f0c39b9c7c9018540253632c9316aa5195d8f6ea1404859cfa4c3a54de77c3ae43c50589f8e1a75b8d0b0a2a76166ef5b00e193b6206c678ecc96f4d50bc934cd6e3c9c5a1dd6608b02b70c5b7a74d3560b898964a61c07dec87afc901acc5d716821f5c1c4bdb7daf045eeb53fea647966f9e34d03677294c89cb66e43da655d9ec201c91de31244ab0b6f80ad5f0a628ab5c5892528d1dadc2e93eb6a05c60f092174806a99f79ea9e6a7d25f909da0a5e09f3d056866fade13210712d0a3791862476f1e31069c82e00334ab2c4ae8aaecaef805e1ac09c047d55ace993acff78802942343f1f4a371af17adb1fa8111736b140429635a209e36725f6545154f14adece348b6d89f324fa18c9ff6194bc5fd883adb90ed16eddc51e2d6f9d9e2d03b1ede59de1645740630222d60bf07697444251f033fcda4bed65e9eb12a86c55b53ba05e8972f9cd0a4748ec41c9b830b6032ce9d80618e064683f40214f8eb53b99c4e0085823205a3d54885d317e6084e4fe0bdf01e2ecc8119fb92658d415410dcc4d86df79db6c7952382c0483a3b7a0a8e3383cd2dcb67d49ced2ed2669958c8dd392560a843de143ee3858599a2ea489ab502f1cb9662a8a8f8242d687745c9a79ee08d634aa188ee6ba5db4c7ba0a54cbff2a7dd3aa98d9d5c4162c6e042b7185108de657f5b78687b24bd5e7f6bdbc04d3cd1b929520b0554602b0c4f30de2f3c6f9a3c1f0ced814ad2a0f0b1a4e59afcd8af306121b876e95107310a3923521539100295424b45bca4497fc03ddeda861f4237e06c054ae5df8e7bbfebf09b298e0333bbc3a21b58d017051d85b94c5d1ed2317b7db447c24c0756ddb07e52956b9566e7968ee5456e5d0d678d08f855c4d1d9de77cfc2ddf582f852b1d533fed4694cc17eca1995859c0cae1d9ebaf429263fa87b33a4111538227daab0eb709fd632f11c5ef5a918a6ca130f5fe5b591dcc0d5e6ea9847f447cabcfd51181d0ff176835c65a22f93508d0a1f77d8b932c0df3fe84b5d0a1dce5f00aea9f1dfa0af58c3a0d10018fd130becf90ce90ba6ef6b8dc9a239001d25aeb8f988b9cff8df943fac33e8b2dafad85846eb5e0b5c9f4593e1f92e91de80b300c3847c24939a72e84b78b9e0b893fc601436503b54764f26588fb4b4016fcb92e4028db45045c049b54080cba3d8f4126f224eeeddff971eaadd34f7c6f492b4084ad657044193a244d53f0e3a377ea9bb3333e6afb6c8f1031a57d4c080ceeb2d1d6e2fa892b0120a2ebdd1a4a445c065dee56c1254192b7fc2a90fcd21e87441e5618f1d4fbba2768ccbd3ed6bb26ad0cd8287be3061d106d795665e6e31a1978a3e09c8a5e6e5f97e5248339c249d6aedfef5e8eb7ce966cb854afef3aeb52d5db7f61292e49dcb6da7abebb136a4adc7d1d2a073a0a60caaca1b2c46d77819a4a218cc383327e9a941be74b1549f2e4325566becd97baa79f6373a5e97fc92f9ce803be585498543a20ad91261d575ce07f610fb8fc6a1d6b1f420707185293c1c1fbcab3d986915c17dba2c5c2c1fa40a6c300430c081103744880dae1ac16db9b2b0831b4a834d22ae1278cca28b18773a984ebd44707294c26142934f79e848fcf14b7e87a86edc62aaa8ab3dcd9f2776c3d70cd7f2efee38abc58836bb199a24f94b627190c174a3d33e6c8e64ff00d5f453ee740a16dd6958f1eaf10ccc60bc51a72c54657b27d3aa5ab1e4fcc4404034520ad16264885c7426d07245479eae6734f3fdb58a9d526f87f0b68b746c82a83c5126363a374b3402cdb262edf82df5142383e191d2e5bd552dc4916557de42453e98019984452a1a9a403316bfc6c668b5ddb1d491406bd5251cd7749ac4d73f3446cd6c7c52060d506ed47dc3f8cbd601915ae71149d8151813f4c5881e31863d1c1221ad6c61d834864e67d8204376de9f58d05e18aa0a83ae003cb1e98aa2e269147701c1e83474d82f44f8086980f3e1bc14a2c7a303d9883969b63a1cb816bb4031275ede16ca664d324c33334ff3bc81ccb9c52f4862ec18209754235abd65fc4876ef2b7660c630457f14146da6b380ff1ac3a3856c2f1ff12d8cc175229a7e5d3ea65b70e7de1beb444542181096c020c1239428e249b0413a02189817249a5312f44b8ee69fdcb20c051e069e88ccf7973ab96abf66bebb9b1c434027d624a06bbf203d0b3777a574dfa0b73273095b8f6f9986118703efe4b573d5a80ff48cf46b1427b9bd1f23e4aa4cf9bab38d16a8111800d24ac7edac924b8f4b0c31c88b3648888060839f9858632ac75ee611523d1bf646a0c9240e8c8712b3883550bdc7ab2753f369d8469c117e711538244c3310068dc10f9a6fae166520ed9409e2d01e41fd6736541cb9fa89fb09cef7a20b56f21bf0c4abb740107b215d474f73303b7be652bb02677c587018d450e3f4aa40d40cb0c4a82467257a9c09791d0d302ff7809ea2492ef6f31e8257686510778340ae208e0b16daa12632507fdb07b3dada891aff44d43299ee5d4d03d9201781b90cf060310c833ccc702d1052cdf903e11ab0ecdf851321224d949c8f140578e37f8d51e39dd92b939d03892243a1bb8591506787b94ec682855967000cad0d952dde9d8021970c28a12db8ebe683edef9da10f9decb36ddd8f62a0a3b026fb45da4916759cfaa414980154be6a84772f41374deedf948222f979447d7a3c2bcab6827fc081c184f781f8c6b9a9b93d34044f6c83950823aaf55ab78212d13163f5a2ed7400dcfda7db7699a0f1e728682c9ac200768b4d6a4774eaeb97fe535bcea3d5490756c4ce6c4c00e9283c876a7b320f5400e6c00a95f324bc35ec06db65faf4f13c5fd42d0e37a281aa7e7e041e4ae13d3d97fda10ed17e26c7549e944191a8aca1cc714d922264b9e6f3ed83ed74f008a5f0c962cc9d13d0ac40bdec4f15ace0c0bca71ed2b84728ba4c7c620548436267e8921b0b086d76fc56a68f4da9896c4047e6a62f6d0310db81f2c51a16f1bb4b1c0de1450ff12a667db3f912886d2352665f0485fbc2c78c24d403e033ab1a77ed42eace7a1143bdeaaee8304757ee865669922df69a344bcda3e10a5c2f0f83f13909ff82f9dd546b5acbc027d8118cdb96dc52f4d1918aec806e914c163f3e1b6c2b5e0d75afddd0183a0322dca3da50bc3fabf8319663255e57b3d3a86fcfc1ac6c03f4b4aba8f8300219aabb86cee2820170f327293ad01886e150506740e92a207388edffbb7a0712ec7018946ecd6b2b83458e1e65929a0886c37204b5c04703b804e98eea31bc32bd904472d54a6c5bf18210c52477be2a210ba657c2be744bd14caf6813b7707a03fa1a2e3ba27abce9b4503820c64301b9c87415a6709190017681d06f8bf4c906e864b27aeee416a59b43789e84f1cc49f7cc474e201d61da29a0d38132de04732a2d7c7bcdcfc8975ca2826cf18a5e8cabe2bfe5b1af31e7dd5b2bb216f67eccde786518a66b8e6a482326c40dee2b207b147fb74713a72600e7bbc3b86ad07ced301c8ce2e94f2a8e6f3b893581fc89c6dccf33eb2eb9ff642dec8ed5698df8ceded69f0584a75fa3677ee5652024e320bcdc4db24350b21030ad6dc74da9dfd28d660a83480ccbe68d6c020b2d238c65a89cf52742e0f0a08e2543d02c039b4ecc4d487b125425be220e27e958bcc8a2874d5c0d7dff41ad1f14c4afc624571d20266214cfd052e9588bb1e411038b7da250ac55f4e5aed0af1a082be7de357b99dd2ce85ae7a776d93d84c521206919529934cc052777b83ee7f4f7b657777ef3db2e180f357f181e8c0402d05f21caf24f31e71be98fbdc9f1b8cbfe6178e5b9e8fff62f985dd1706ba73c8f9ccc2213c873c5edcecc6b01b037961ecc2300ba03b873e1e9c63d78d45f8e720f007771e2f2c84dbb7cce7f2f993fd8fc303297afeb2eec7c0b55027bf7010f6e1675d1e07c3bc708431fc2607e5f7bf5f867b3d8c833091502707671e73209087410fc7c010068cd6f5a1eccac03618ee852fdcb97ffffbbf8f63391b94c31706064378f66561be6c1117ade37edb7f6132390cf641359f16f7798cc57d1edef541b545371679807df0b7ed641ef7db76f397611f94151de0f3f1319f8ff72fd807e5302cd479de8e03491cb41c0459fb3d8ed7361feb5a5a223e5a13f082c6284d55e3d026ad45209fe59f3b2312830884ce1d3275eed097ce1ddad25a7b10002dcc90ac7386469d2b044a6b7d8ed02ab4266084560bf9d1b9425e74aed09bce15da3a4788059d23e448e708cdd1394258748e504fe708fdd0394230744e03503aa741071a8e7f9138b1177665d7cd25f301c46ac50680b4fe9a600330add57c3e3e16f4ff05b206b6db9df928534b5b83ecd61c61693ed6d4763b30b698f7c6de60f8725f38c3efd85a3094b9dbdd97cd95e1579ee186ba50e69fe5be50e66f61bd3976d9f0e5e11b7acef6e2e01c6cbbde609ffc7f1bc781213c876f99388ee41b4ff36570dfbf65bc6bd0214c8dd66d199418fcc1a08b0ec13038128261b015829910820dcaa3750836a88a0ec106a1691d820d221182090a41eb10ec4d08b613e413948660827408768118adf505bc102c67c10a16fc09c18ce89c05432c08b5d621582013b40ec13e0864279099d6baf8b76d909d83ecfff1980343f8057b61570e025f37f45cac82371580015482d6fabf652309a4c7ff3866b87fa6637dbf99ecf385c57d1e61656ff099fc7d40375e83d7e637088f55c5fdc9a2b5e66115f0cf5a88e667d41f135aff826f58a3c62ff8fa81b3aae127d5646b9f17d8b13e7f661856b332fb44d17dc8b4f605bbef2c03005d767fa628b023bb3f19149ca1e084fc83b2ec8633078633e2a00fb9cf17d424299302b2cc674eb6c647448e4f95d67a8f05fc7b285aebdc9eadd95ef775c3eeb2c166ab8fe1fe65ac5931f1a0a12da6718881262606e8d0c0135d14128f14f07187090739a8708042256a0d6b038b017accf861458960519532ad8500e61a914874e066c66134d1e95db1258d15395a0b04d94de0954212002f0cf979c1a88760a38570096924ee21e8ad908c12085ce5beeab8e89a80521d23ed560223300252408b1318e090f4a060d18c00283005ae1ac8608b0f2b84b7cf0c4f9bcc12f5d1a91e52720430aa082835193c164da0b0207407146f938b2b25348dba44a2930339dc080228e1c20938af265c809c74d2c20391a900c4a001a4f0700584b536436404d9a17c50c82466064d84ab2a6dd4f058c8729403c31c274bbea91d23407a307cb08183a94e24d614d924568746033cb80604f948aba2db1397250949054d3eb54901079f371e89de381e1128a8711845c2c6ae9365511120d906b5b12f4027ce28ad558ae00330696a2091a9d46197c6039bd7577a00c31f53094ceac2ae714324538888050b1a503c4824e3cf056d133e7c18d0da7062a8f2f2060d200c6b2856ac60135998fa657c88168a38d9a822c48137674220715cc992c944154e8666f74818470f181d4a521093030e01129c01836528819363497bc1c59335a60c11d0652c22b230d4f1a2637eff0f08fbbedbf7679e34adf5240dfba0ec84cdaf8da62a411820219199952156bca6344a130106405a21f8090f4a65114ac0fc20eeb75539a12299c4c1bdb90567395e0284df5f9b12b7c75684c810214180aca9a5fd403beb61c663878e1c3856b231b13019ba2faf5331607471bd20c0c5560b162b4e852a52a0386142ab048911224e9da23a22c1227806c6c55dfe6eeeecfedc1808060606b6e563f84d96395ad80c9ff5668ef60bca61a929ab69ad43f05c2c35bfccc53c17bb334be233276bebb0fc8171f37c243ef21e698fb0478e1c617124c3005a430b0da2d67aa77375dec8e4d689bafdf5c2b292ddf7f1979368ad95e81c9d4a757668ff9784336438fe4cf8f785f2ecb261ebf66439d6cd7deb4e1df9d5bf87fb17ec83329d9e96cb0f3c60c4a883458a38d05a274a0091d61a83d687f40f8e7168ad3768c090d63e1fffa9c1f05afcdb362856838a0244541d502d9213468e752e0639635aff706ba0afba56ecd065bacbff1ec338d680f8ef5fb7cb3fadcca7a989f5f0a90989670efc69decf7f9661b3590e92f9ff06fb77855347ebf9fd6e1180a0b5d6fd82ef0f79114cd15a97adf97c7c2c8236ad232b0b7f6f4df7c232877b3de6f177837571af8f075fd70c8ef7d0b9b3fc63b8f5eb8affc26d9c87adefcb305908f65fd717fee07986c542d70de3e0fcd9e2f84f0ec2716a305918c34af82fe42110066b93dd387cdfe2c29987b01cc23efe6eff0dd611eafcff71b07ff97579e1cbbb171c7f26218c389f8fff18465b16aef091b61609cfc274ac8569eb5ad89a96235cc5c23535203d42ddfb8170b8c667790ec2610bbfdcebcbe1fbd5e2deb05027cfee1b7479bc202f2ee6f1fcc2ddbb32db0bfb388f17ca5f6e5bf87b1eff1fdc43d9150b753c1e2cfb78102612f278bcd04918f29993855ea1178d9fdb5303f21e9f8dd708c33034200cc3ef73813fb8c32f8fc70b435d0bf3646c531db70c9f2f08ecf53fc7cd6ecc81327e70d7b1c1deecb2c990012603e7b9c70bff570b7541d88db7e1ebf61f03bf6fe1173e93d53852d3456bad7bf12204f3eece4b6e7f1caa5992e68534711a56eb100c9e355587064669ad759f7d7bf38a2eae36d849d8c50506a6d3855e06846108f7deb8e6f8cbaf0b0656a575c68406c8b4d6bd6eb89321a381e08d83ec4edef97c7c37bfdfed8c9032219cf171a64c6b6d464cce4c93288d50c8e18882b70c5cd05a6378c3260234107e0aa15a6baa02c218f101460b9a3f5a6b2a2e9d5474c500d088a3b5ae325b0bc0c4e50517515a6babac2f8b3c9159e00f23ad750606c68ca2fa00ccd40dad75d5a550083479810c09a8d61a0033bc00caf40e29a1f4f56546879e83401e10064ed0ff8567ef7ae1d831800003bd187ed9e6377cfb6fe815feb84c254db6fe08756e9bccd7e697bf60b00fca4dcaac91e1e332e419f641b599fb80bfbf7681055aeb403d74328f2787330f43ffba6d08cf21129eacc633e2baf17e0c8fe9d4c299f778baefe15ff8dd5efe43ddda9b5d6018dadafb7d335cd00586fde092df5bdbe9782d1cfe1cba1ffcc125f16fe33fb73c9d777d3c48a8fbbe30eee5aefc737f31dcc61f28f31e0c9479bc597e5dcec643f9bff1770be1f08df9f7c130307c61d7c3bd196e8e7bb3fbf6e7b70c2d2c9475b1ecc6bc2d43c203038b3fcf881bffe012790e79bcdeef3a5312ed6dcc15facf16ff5ff87b72d8eb3313f707cf72b0ff98c72e07865d619c0f0672c96e0cfcc1bd8bab854399c70bfdeff5a01cb66e0c875e3cef865eaffb35fbdc201bfb087565bdf9f7e6b62cd4f1bccb0bbdfbfd1bf7721ef49ae5e0eff5a1853a2f9987311cb6af3c07813c9fef7bff65ce7ead3dc2d27084bc2cd4b9401ef2c47863b20e9ec3b898bbf1997b1d8e5027bbf2ffc17d0ec22e304c880f9d0be4619bcce7b6854339eef3d0b940ae16eac842dd07c6c508df78acf3ffcb619ccfc7e1306ce158d75d1f1b04ae6661637fe6fd857b5a18f2d2760ef2421e7e7776e13e77dd42ff651808f7c3b8dff6bfec44e7ae98d1b92b5e74eeca139dbb22ebdc959fce5de13a770588ce5d29d3b92b3574eeca97d63a805c860e1ac4871158595412d0392a51748e4aac73545a9da32244e7a898e91c1553e7a870e91c15113a47c54ae7a678d2b92995ae387400a1250593ce49f940e7a46ca0735202e99c9407744eca023a27458bce4971d3392944744e4a013a2745d43afe71dc0c3ef3bfd7f94f9bdd9f6bf6d9b82cff5f0cc7f1f7053ddc436bfd466b8dc18dd69a0a02ad35551bad35151632adb50f18f4585e6021a1b59bbbf24278c5030cb4d632fcc657eee4aeb0d19ac70bbdefbbadecfefcff3ef8ccfb601dfebf40decf048e71cc5f96c3808c8dbdc1be2f16c7f95e1f2e77e32f10d9bf411c56805656d890a10a0a5a8e756caf1bcf729cef0d721f5076c91e70604d862a3a3450d943c5926c83461cfbfbfb834bbe1cf2d1070cce2187e35f84cfbe4f46e67db08eb897f780e135fec9d4d85e97fbf703e1d6b72ccd4cec8ad61a86ce4d79604a161d82b1b08f0dcf7ca8ed08c92933b41cebe20ff4fdde2efe3b369dff4b425d5b0cbf85f0fcc2b292109ec3f8cb5a6b35693490ff1e98d51c45d467e2ca330f2ef9e167e5d07fad75036834ec836a695438a20c817d506bbd0019adb506fd99b8f2ae85e118d8fe78eeb1b90a8cd1bb188ee30fcf3b607097b63310adb518306afdbbeddaf87f0303dbf9df7f10fffd1b84c5daf817a2b526a1b5e66aa14768ddeadc133ebf6de7e31d0ea3bd3c39b52e755bb7bf7c7c07c79fc9ee855fb8f9e784a7f39e87fe87be3027a9930e2fff8a7f9346b92688b4d6211cff35a1a0c999d0461882ad50b496d16484d640d6c0c0e41776e5f7cf701fac27731d6060dd8dfffbd980725b877d6271b3ebe3730cf7805b6bebbf37a6b5aeaaa252d35a43915df9bf3f47a89f68cd3231d3baf6c761c87f41368cf37d37577e7ffbc9c6da64bea6f6d3789cf9efe1b1a05a1c042f9030f8ac4de66d325ff321f770675fb087ff387c3e3e06c7bfc80b9f8d91bd7087d7cae035d9ff7fd63f50edfff859ff40b5b70c0ed03aa673703f9dcc7b6e211f5bf3f9f8189f3959972c7f9f8f39db8fe3cff5f9ccc9421ec6f077e399c7935daf637bddaef1b7719f58d7af37bb31cce6e35c616f4dc7d3c183756e87659987b2ace3f1d87eccc6c6c3df5b434b4d35359d1bbe610bff36fe8373db3908b33c7c83df36d90d435dcc7dfdfb306e8e3f108e81210b1ca929cb2c52534d2df47fe62f9b2b7f1f44f7f5babc9743084ba693f9cc7d4fe63afc7f83dffb60ae5fd823d4c1f19f2ef44199b321984c40d89bddb71f9fe5f0bff0ffc1720b7fb18533fcc2c162a1ee3371e5afd70d3f5bcc7d21e74c5c390c02a3b5af1b7ed97d0b5f980de3eff6dff0c770fbe3df9bdf6028bb210f5b37e4fd1facc60b756465dde5610cbf6e186c7b32d711eae459867de1677b331b9b1bcfe16767f80de117eebff0fcc2fdfdbd39e4f1fc9783a1978d351a318ca3adc33e312e5ef88171b3fce33fffde6f7bf8ba3fd97d79afff5fc8f6badf8de750fe6c3c86e7d0d7bfe61e9beb0b651fcadd9d65d7e70ba301cffdbb85bc2b8c63bffc63e08fb9c290b3bd2e10c87b843a1e4f2dd4f92fcbbfc331702483e107c6cd3cde1312eac230e640af50e715ffdefcd9e0399c83c0d0e7e35dafcb15ea66f80d5b37ece20abfece338421defc7f30bf7106edf70e6ff2d18af85baaed90de1597e01e43f2e39d4cdb2eb0b7936d668c83c99fb78bc189ed5c2005ab789ff4bc2193d99f7be1c3ca3327d2a7c58be5c5ba9b64f5aeb0074aead84b64a6d87da0c15d24038ec72c11582c908bb5c84a1978b100c08875e2e42ae3004f392432446be877bdb98360342b02eae998740389edfbf8be7d7056b43734a0429914017ba72e8f5baedf7614fd7c3f0ed9f856dc0a4f9b403fb5252448731dcc26f68e58c25bf6b93f9bc83fdcee773db76bbefe1be9bbf3c7f598e756c6f93d5a71d7e61404163a883a7aace0ebf6ad4f86cfe5bd139360d71f1fc7e1940fe6b831876f3fbdd601f9465200dd807d5641ba1971a8d18c6e163b9cfc7c7c0c036ec5eb8efe4ef77eff7deeded5c20df016530b0d6f7e51ea6d3807b6f2e077afddf86e1cf73a03bf35a38f4e205e7ce7961d8ffedcec7dd39febfddedfce7ffb7bbf7fbfe6f6dd0f81f47d9ff6dd0d75f17e8ceff07f700035b7b835fde894be2573818ffe21df8f0fcd6391ec92bb133b413b32b20b783a1d3f1e62fefe2eff32fef857ff7c2bcb08dcf606040bc4bc69383bc7f962b0cfefc1b873bfcea927d9c377fb5b5d7078f2be4756d2e1fbcf06b2c6cd14227c341de83fb6187fb317fe3e1170b75f2fcd9c2d9dded42d90dc3dd7fe133243c0fbbf147fb057feeebf60003e3198963c397f7e630fe9f05fef82c7f3ebf7865def36ac31f9685baf803c3df5fb80d767bddf0b3bbffe5dd0f8e5b784e5229091f2df3b71c3a97a4974444b249d715c3ae1bef7d56767f39241868fd06911880840b18377bffba41b698872fdcbf0ccfb31cbbed1151476419f68573100e5fb6d7f5225be7202bc3bd235974a9ce1dd1a1ebf2f07c8484d68dd2e58cf0d15ad725ffdf863a36dde70afd37b280ceff37de9b8df0b4d619e10a7965bcf0f7d68a3c0afd8fe159fef1ec72853f3806ca41b94814add9ef734578fe1711350fe1f97543ffe7d7fd1179a4b5eb7fdc432f1e2f8c63c3b7352260b4d6f0ecbf1bfce30abd9ad6fa86ce11d1a1f373e7dffffebab2877f4434681f7fe3b197dd78967f1cc6cdee90425a760bc386a8d16243805ae7dfdf63737b210ccff20f32842be8e11086e7708886e3cf44081fad753c1bde07eba0d1fa985feec63c88f7c13d2159b4d6fa2d9cdfff8be34019428668ade3e160f27fdc0ae1fafa31302ee66e77dbff7eb12027f06438de8317fa2ffbffe3ef19d9fcfedf0bbb36fe8fdbdddb7f67f97b30b0b55c100f40fa0081cb0191b1f6a907fd8fdf38c8f2bcbe8f75f9ebb6f1dfffc9f28fbde7e1ee8d83ec2ee421ccf6ba6daeef0512ca6ef8c64116187ce3a0d81aa2b5596bade3bf1e07612f99c3ad6b29d438508b5303a296aa9dd075bd71b0d7c3d6e7b2ae2cc3c037d8e77b5d37de0b67f89dbf2ffc42b84271fb7cfcb7e17773f91bdc7be3855fba1e21d8970e48daa6b43f5ab7e6c347daef31dc86716cc881a4bda599691d6f86e19817ca6e0b8731f705d97efc42e77ec0f9b1f6830a2d4e0e8d4a0e4d0d6d4c6bfd43e77ca4e0c38c8fb1b313c274ee4cce0fc6335708fcbf2dfcf3cb631e64eb75b7f7167af1ef0b671ecec0df7b731717efa197cc7bb27f83adf705e19e1c86bcf0ffd7fdc57ffe9e7f1f0cb3615808f6a59bdfef02ddfec178b3fc3d2ffcd2e9747dfea197ec25c73feee5712c07fe18f8df5f1884e3bcdcb690c36b61fcd9d0ab8bf783f1cc0b6537e47d37d7c777c0ffdb785ca11cc3a157e8e57f5b877d61dccb67ceff4f27efbe8fe5bb109e43398cddf9fde1efcd6737ce32f4f0d463019d877bf3ffb5f0fde69e4e37f7603ccb21d8fcf2b73008c785ffe7ce2e54b58d2331a2a6b55e75ae87969927333866484c9d33e3d2ba18e6f13eebc5439987df873eeeceef7f837d9f0d65577e117f37f7f71ff3fe8f71f0645e6fe6dfef76f16f79f0cb86ffc76de8dde012d717ff96c70a3cf4e8748e0716adf51ab6d0f178e17773431ecafeff3794e3f89b8fcf301c87712c1bc6f95e388ebf856cefcce59087af1bfefef2a02cbbbfe7c3d2e41796f98fe3efb6eefc7e5fd6d661b3f73381e35666e33e1c3c9ee156998dfbdaffe376cd9779777e83f08fc3edebca3d99f776f157a3b5ccdfc0783be0b4e66f613b7468dd93ddd76d77846cafff32efedd05a973dd25aeb1ff7b0323e40fee3b03230fafd4020ef85af8f325e9ccf176c65f9f71efcbef00dd35ab7d0b932516badc3742ceadc07fcf1f770973ddbcbe6febedcd917fc8561391d607cfc679f9bd3217e1d39f82c97e391d6bdb0cbf6ba71d0872e742ec71badb5fff3ecfed81c5572dcf86ff367c35e9ff9af853f8e4d9a2d0e0243b6d7fd6c0c0c7f7f7ffcdb1f8770fc997cbdb21bfeb8009dc3a146ebf8cc7df8675786bf5ef8fddeed6e502d8763d621e1b5c97c8d6724f4df15d3391c24d6495aeb8284311cf27c3ededd5537771ecf703f84e710878d1ff4f120719f4779064732d826f3b5b53699cfdfc3dd46dce711f7db72eb9ad65a17ceeeefaf5aabd6f178615cfcdd7e18c7867e4cd7167fbdf06fe33db4d63e748ecc4b8eecc60b5a6bdd8ff3fd99c7c1de7908bf85706e2bc7fd36362807c3109ec338df8fc32f248401036c5df3710603860ece6d7f0ce43e1f27a1736366726342c6a8726284b4d6693a271604a27362a1d6f9ff06fb7cdce70bd305fdef75398ce19017e70302b9ebc338fe0bf690f73d3c367fb5d7072f945fc021dbebfaf7033f99cecbbcc7f6f25e7f8371507ebf170f7d99ce77c5c5fc7559aef0b3f19ffb63205a2e6cd40f8e5b9fcfe511b6e8f76737fc3f580ecb920b7b91bbd1496b1d44e76e8cb961e3465a40a51986d342f9c76e70c98fd774bc1d2ffc329050f7e560e8bf7fffef07f71fffffe372f8bafcf7e3f0fff81bf60ff4017f1ceec7701b7acdbfe57218f7f2dfc6e799f7641e82e5382b3b4b0b13a28bffcc7dfebb3e367c790fd7421d8f17c273f8f2f0337163d886ce15b080d63a20ffbd507e31bffff7c2b1ebf6fe30cec7febe2c87b15f88fcd938306ce1b00060ae8013391b8d56f40f94f98ffbeb7ecdf01bf2d0ffd60ddf60ecf3b96fa11c9c796f0eff0c9fe978dfc39d2787712f0fd95e17f6e11c84e7afe68361b627731d60606b3d99f7becc7bc0757ebfdbb7b10684673e73323e73fffb0bbf722cc7ff17945fb8cf6f101e6b7f9a3cbfdfed6edb06651eafbfbff01918d89a8dad354a5df397c3389f1c845db71fff170e5b97cbfbe0f0f7d66c8081ad791f9cab8180d6baf8cb41fe0bc3df5b8b7335d0b4d6f93f730f0c86efff6c28f3de5aae06570dadf3c050e66fbfb796a361c1efdc070cdb60780e6739a824ec02039bbfcfc7d1b890c87c07fba06c240ca381800e7f6f8ebf7fbf1adc7b13b246a3f5010cb6c1b0d9db7f6df87c7c0c0ccc069bdbbef05828f3708d060d3858cce7e33d374305ad350f9d9bf14617d7bafd03e5109ec3f8cbc110f641b537cfff2cbfae97cfc77b0b876dfdf77233ccb46e86e1b4dc8c122b740e6c140c18baffc2feeb0d869f0d67f87dd760c0d0b9c1bf1f8771bef9fd2fffc51f0d8c841c5817ad8bfb6d6fffe56117efedbf3caed09785cd5f9edf1fc7c1aeb526750e0c057ba1b556d339f503ad75bb9decf6e630867f6f4dc7c3f7bbb96c21ffc1711bbe2e9b1bc321cfff97cd55e3f97cbcf350fe71bf2d847d500e7f6f0ee53ff3eeceefefcdc1b0f7c71c78167ff6177ce720700efa0003ebc95c07d92c7f36d7eb88bfe7bf678346ff41641b699046ff41908c2412710c67a4412370dfbfbeac87a57cb8cea5642e90effeccdddc18b8db8dba52277eb1c13ef4d38f8333ff6dc8736be385bf270743ff7d2c7f7ff859fff2d0e7c61f08f743d90dc3afb8dfa6b5aea1f50c3030b3b60efbc46cf80f04baf12c83fd64fe4683ca470eddf27f493823fc6c08cfe12ccfbe206fbf0fbf8f75e5f7abc9b14e9edd9feb03b311cf72e8d5dd99cff20b1a6ff0d8fff177ee1cf883c7c0c0faf7701fc9a01cc7d1fe6f77721c07bed9883f9a2ccbdcd777f107c2f15f5b877d6cece3333cefe0f8cf060d38fe13f2775fe6ac8d910cd280e33ff85dfb9952e0f62dfb9ec76c402069b3cccdca98b83efeba38d6381f1018f665de43fbfd598e83dbb76c7ebfdb1a7fb4ff83d5fe0b9fadbdc16365239dff6ef1977fdaff318e2ebb3f3030f9fd6df00c062607673eabfdfe721f17fb3f777e83714fe6bd37982b57e4ca0142301e2f9c79f8673ecba1ff333018c7f0189c18c371bcc6c5f0ccfdcb7bf878fc813e7cfc17c367b2cc7d6364bd3e73a0ec7a398ea3917774f099fc8990595a6b1f070303dbe9c88d0f6839d6f9b80c193d060283b28cffe3712b830dca327263015aa7d3bd98bfcf6be63f660b67f82cd4c9a18f735fb0fb780f7bb3fb16b2bdee1bec6b631e7e21605c7e793c837540f71badb59b9eb1436839d6f93fcbffddc57f76773f386e77fd036519fdbb89a324d05af3744e9cb5d6febf6cae2cf7668e26c7ba53b7b76efc82dbd7fdfa3ede651995f8db7f01a0e3648397570d1b35c22e38ef72cded37034b8b7dcced370bc330dc604008e7dddd67f37fd7d6793c8766273298d0193a845083461883ede95adf970188e155a886ce7945d15a7f0fbf6108f63ddcc3ef6379aff31fbfc1d0fff8cfef0fc17cfc8dff8c43eb00d268ada1e85c8c4f5a8e7540fe632343ebaa118462d0d0a1426b7d06a34a0c0da18dd0bbbfb6ee0697c810b5ae22d2f5498e757fe66cae1bd7004c9adf20dcc31abbb9fdbe9df7c101c898651fd6a8d163b90a0f91b5b2b130b4b0b1904608e7ddafb58ea2735c80b4875f19ffc7332b43fe3e1fdc6d84eff765a8e2dafa05dfe80301145a51a9c390492203401598421d4432c4901421aae4459707c659892a253c60d2f625040435a22ca980852c3642b874c40a05e990ab624a17391543cd985be3d25343d64846b380d6818845262f5a21a50112817de2f302016da44aa8a31d21349170e4882df2a58015155951bce4ccc8f1a92447ce199d2d222cc20407d1272d86b42e60633267871d11f6190d3203b4dc3c64b1597101100845781479a094d5183a183cba52d1006b8a4c070d1d544a9d790b6455c9c4df002d0536c0d70d0f64f0022d9081cceaa4c16b03318e84b07d20a23556c2c0c255160f846878c3929316cfa1f3a5041a044e784801cacd16a739687b9aec78f2859d22628904454c4091650b04a4c4190bc47accab15bcd469b1c303362abe2c9031870b8ca8137d9c600280cccaa26d295189ac01f20412e102194e4ad0a0902e9852630e9c25483e164bc9931f3ba40ca02a4d6d65297227050a25a72eeda47c8961ae50404d2418794a8dc88268020938e8c0028ca6387846f033a81496078d467de14063936c8296237914387d0a1515147902c76513e6bc11b3d6620b1889123f6b93d2f4e5d39613c6dca67ce002881c55722b1032412600842d679228ca194af1c883b3351944a72540e024b0c1254d6f48cc093e1afb32c6e2822b20631af6a8e8030290a84a6707530f5bd855d56f672646850244da3f9580a0c0c1cc99121d0b30c152620d9126e2794b01aeb3c4939a3817d28440c76615046960a44b17232e7ae8d84c392f535457af813a0e38d00485a3213e5294a8146488d88d529818a5f17a818b054fc884c00387c30cdbaef2068a10114e3462e5be24d056c7ca05e709a31c0202393222a605256dc5c486830a74fc00b1644c21069aacf10dca69539b9fc18d9805a1bc68d9910ba5b64321ac82c648458f81241cd66830c122c5294cda833c54fe0c43c43273232b0fa632557404484c4e83d65410cb4a7485a40a93451a8b130cfc287aeba28ab3c144210f44e12939bcf023c50394245384371160635e5f8e34ea5cbe1a19c9c2e00ea9ab4226a0029cc0f960c3e5cad2db222fbc13b216dd21175490004d1c1f8b02e519412542d0072ee0170485da81823c40db25870acb74ab42844c151210ba1cd9c067451036142c8480718142c3a28083141c0b4c211c469cc9d1617971c7440a69742c4824946860b509c2254dda0a228c48c0e5eccb133311050842d6437b4cf81466cfba038a18a769c4244a6d02ec3e5910dc2959738a880b2a408cc08402189e171a09264f9a4e1920c32c4517074820e9b48401198d787438348ac71c12c0297197fe14192190086c800e7c092004c401a60e4d813a5aa7309d593065cb8f920a1d40446812f2048132ef253674ccd2a91219e0c0518075c8cd9c14d0d2c29cd1b3400800807cf124355251811139282642d49c1c06b2ad1e4de2982d50d605c3cc1a06540935bf0c9638560802240573a3e01f5242441192ead19a43e86409830112e81256e65a395f48e80c2201854d671c4550438bd2aa5149c7984bbe34996eb490c21b6e662b84a91e1944400b92e947a2315e4ed0b244257708c888870cc7276c09592c44879e538b921627acb42216f4f480a1717193254816141d7ec22419902804e382ccc6841fd6d5a8660c0765321cf5b861a8cb478b2d4113554a1624bc31e34287030f6f9ea419f324c2eac8a609c21a34b83ce24081294c8a405d9dd9a186412632dd011034c41cead4e182135f6314ccd8c085510356befa5012818922115bce889131c3010f98502185f090600bc0d2e0646cc80b536234522032849a63e29f88c40260e40b0d953c17f0e1c0824e181a23b24ca04db69e5489a2e269d8c4e58d88f5a6839488629b199528d3ef4aa39000145cdc842a51c8c76e85260e585a12a48ca3116c4cc020849a02a0b0b0c1093d2fe00c0081b509900ba704a276a74184a8340d0a8590224cc607b72f73800b7400c565cf037302244a176d46c5a008a8b4a148232f2a6082208e70eaf4c56541a74c6f9d2b40e7003152b87005a723ac94e3c0251e3ec6b4d04146a250240dd494305a4c8046a3003853d4a0203cb09446e9f2e323830e2f9cf26306cf4e0505ee0a5a2c03b3d700dd1f452a85a9127c05186d2a7180cb980af6b4c0c3b440881e79641289a71268bc1dd2806b4a012267a32b94a03b5c4e30440aee409309243e9428f50105349b28590a419019950e7254300bb1814cc08813a06cb26306d86bf4499003b935a1f880d86ad52c6842e404380bcc4ca075e60f113e6035d212253028cb8b26d5226b8798430331a524398aa0270207b28529571a0848c3287748048e15e11d365f5c3063e2460d4454ceae00d5a04a5402861261c264213255c37ce4412c658643b8284450ec0a78c8c70061e30032756868ddc821e1c084a6381ec0a5d8a0890b4c5e2c20caf4821311692884c023b1e6854e280085a96145810dae24a920020f0e4da5b25c22f366a371403891408a2d7c96c832c27e81642929c8c9222204122fa9158eb84804eab340e5e5d30fab6c8e0b31839a74e2f0a20a061c7050610b0a313a6cb0a4d19189d0660d180938d2de46f4b8e851cd5c5861fad1a3909e0e2485c1ea23001242401467f6cc36c06278281343c142907d848715338482525d223811b31a310ac9a63947ce26812054ca0f095f2a02f135e204214e385cfae0c166e34a180429059d2e7452e072d494f8b109cd97107c5f4870712010d7590b3c39e63e232028fb14836053c2a7e0d1892f3e245c8e950054f0a2a2ed83005d4408f1a834c60da1a1039f4b2558c4bf3e745401a2bc6119531d20842284092f15e84011024be4e533094d905eca89e07068c8053e191bb47c8a9121066f82127f28c5b9c08cbb34298228648de224fa14824c8920250c429c7678d228560b0e2f7270235c0d02e2f3036f211a25c9070d155a46a826000008930c9eacc9b1421812bc312b8b815118c858740129199084cd2971414e84ac044e342676734a0046b001a2536abd80c28f4f2e243965c095970b24194ec22c322e24bd90016534940e812ba5b04194ab758f252b811608f5d3600e013dbe68684db009b4088453465894d518a191a527b223112610b9b92a93a527009f27cea6df157755a791091e425c21494365d01f0a38611173c28c0b88320af4b00b306244e8cba4367d4438c026f88118044ccc09067df2883d85b04e48113569cc4d9910046000e2a2a4817aa205b7cb1895c39b2e26dec678181b091aa1ebd36812211a571a35d9c1592268ed61712914e7529a9ee6aa5409202f384073020a39330eb005d2f812c48402d2c18921034600eaeaa010931228b0c04132c901321fb058a5fc78505dc8b18391a73b4f4ec4b9e949c16240c29751f1d50f279b0c11e202840288d8c5d27230744c88db524102a0507c1528826d8067b4ee303972259a90c78546242e2541a1829dac2285cadc1409224a8608204ab87449900a643ad48099a0630dd345021521ac7931f0f985e924382269e0c46482124e7a8042c313c2463e4913c2034f85c284f0368a5cea81c52794294c70c65cfcc27268db125a8d3709883a956285a21f5ba5568912949478e18609814c2970e6c926f584102360c912c250a22de48825e290092e6ce5f2f87c935e1b1ab85690b4c29f0a71d2ea9323c41827e2001ed6c268c0a3252997c519b94d2a65c1862593c2d47cd151e7c695090f193cf165d6ac7087aa47013f22da4492271a3600fbe262916286ce214b5c3a0bd356b3a650dc871107225cc09b427d55e5059e3c6a8c147af3800855885c89a24f646863c21aa9c10091d6f80ca11ed05a44db9e4f63471a5862401e34001040d3fa0a050d882872c2e38017c0dcc030dfd0fa84048d582051088e842f3c65d4f40905874125353c3d1574e5c05308490ba856894ca20eada947d4f42e50838e3c10280e05a1f8142c2b9cd981a265e1028c3c1c780043c49a3b29a6b059c0a418c1674c886bc25b2d8a14850416294571a1a5095cd992188f09f5981203cb974d8bcc707ad4c40aee51a61b9a040424b0c061e80c6f8e15ad41f0d1a3078057049e0a3068ade20014b268b0b39566488dd6256c1051d5742941d2075174885da9854c8e563481a375ee8526192ab0a48859b42e0169c9255355a834ddd021e6acb067b42a29d03ac7058617571518315a6bc18dc541429327d43aebab8ad0083b5888a2739f1465e8014218ad4d685d6a64090534adb3eaa080d3d3e586be1a728987400dad4d5cf0633b2ba4b588528895476ab4be1a1487568aa24fb48522a0d45ac41f0c5280d11a4b3c83e98646a125868dd62374b86dd21a4bcd2ed428127dad4738aaa23508ac455a9350a235882c2a6e84d05a6badb5d65a6bad754e6badb5d65a6badb5d65871447419e49313488021a47894551658330248083256c8e1c8d3a13a6803546d2ad800450cb519a08824e2f4c40c0056a67cd035c19a110200b747a1045f4212e425654e3c88300429c3075eb8288d259c40eb6126841f279af8d0a1e50e1e11a71ce89e5779f0af12dd3920c8870f9e761aa448f1670d062a4484a1f814278c072588dad30682b60dcc17263af8be466f592e05db7cc965f82368c9154bc967d8a0c78b212c714a3098a4b064f92153238003d8140c2841080656c6de369980e1a44319d04f1d2d12e6e881400c104bb916bcbc989329d803c8811902594ee8a193a5ee3b6578586a125151d2c3a9050925b4b03c41718acf88322d26c4012f4712a59c183a8c0279e0e100355d6e1aa4304180b6101444c2021362b435901fbc41e08683062536fde8e24181068076dc3034060c1a077c727ba440d1da89f3ace7a41493510a19a20694de58578b314b55689a3c0ab4224a09334a626a2af84244a2421682074f255052a16604d6a64f39b4aeb64480c082f3801010187868eb40012fe08e090e38d4090e047b48e8c1ca90810b7bba20f1db46a631683e85a52059de98b481674035bca8017bc009881e6e593be4b8f96f58c011b7b505010724420f184023a4830240908f46fcbe164d49d1a5410ad99c0cbc3cd21ca0d01147c6060b521bb40d4c301397434761d483c706994680f1f040293782842c60c19a0c951a009406c58c7556e34a82139e7c32a463010d7e19b07a5c8099627cf8000ee224d1ae0986ad1155747aa8d0e381212e5e286eec3102c0d78941442ab139a0c3cf09a2b6c060da1ac05db540c2eb86060304e73e6e91be1470828308b80650345160187550420804480f2478382122830c5c98c100ae74227aa03032d3c195971966d030770b8f29125730096b7a16a80da6007035e857a95403598742d0f2c7c45b83c595206519911a0098b1e1819c18575b99cc0442559ae3488316520220e36126eb552168940a4320f5d8238797652591a265c80d9e3da28c0f6b5c90893353640216b90a342c4a7b82c2c560cf13395424ee8010e2042e18d8503c808189298910aa2422ecd0e23a2183289806083596448f09174c3066528e881833c02f5c14ec30b1f190c2420127044c4a90990429683ab3c654282a489bac125c468d9428d5594365032711985e6745547862a100328ecc68b904080588296ffc13885044345590e98253c7c2e8b5b7a03949a46087d351b3844a1f3c15eca97102191515328e30d1d271c0a5087874fe00bd310f8070520611da6f089d1242f09de2e248b09040a20bf059e8447a402d8b8d14e2f26c4a31dff051034e24047af4949cd521c18205080d6ff6952d851858c4c5967cc15941466cc0a8c6a62f261754894b6000b80d22c54101e84a223685b4041319518b1a954220089602f30d241d8b26194f5ab0742509934623685ca1cd7fdc4a2e24a1c0cc0b164a7041c121eb01043a55210276f6a840a990c6131601275c91448e7b3060a9a8d0742c94083b6f362170ead4d7c3444a98950c6b299c38e2018b09a09c2a8005a4150864cac14977d5c72a94041f4e610912cae3820c1352402282ab8a0a291238e5aaa96c541736e956302101043232a5a7c6de68d850888925b03b573edc4c428184345bca00c800c82c91af93e5000220b48580c52709870e9f1a0d2892890e6e3b34f020021d5823226129a44ff891d364c8d31fb2b2441cce368999a4c5a865adf8a3a40dc8911b672cd8f409718302362d2531ac648081078dc7e1d12b83a6e982dc1423648b4876b06a70f8b062c7080ca4106c409d32a183232210f56054c21e42e6871287e840d904009e1350788e075c5050959653d1e30328ab2fa0149d41a4019904d6cc215e846a2790c99ec6a89c1a55034e4b0725c44dbd4d63c094f0813fc582592339ab0a272ca8b0e8136aabec49012a54ea202408083a2602a1f2403ae778d234ca17c0040a902823880599c5872a4494ac61018b000fcc48610199e703a7043e9cc18e990bd1003b40a4219a9614cea8f892ceb2948192e58c44062e2f071e7b51401a0b18812829615304208113475c4080fa40001c3db023b216b03050e40582094c708b000ec69d9f89cb89116e56319a772346ad91892410ef8b04146a55ce2e39e920ae7748901e19a97a046b6dbc73c6019b4e23402172a70695169144945000123653784000899288a20b09262c3ae1c01793030b11c08009f2c2d6a7139b305c6552a51010ea905691130919115857562276844600e2a951e9d3a0155e90a4c0983113a2288084a8d20044bc5648810656133031574c3ee00a3b6810f2a7c488cfe58007d06441d232727dd8f08ba302890d4f4b06c0c0848f4e4f24c04a244096a6d20b9c017e9457572ac890c1c3021773ec4481044347da055941e0165c4861474689136b3ecc91d89668c9e1408a824c47c02875436a6957543d7271b034092183d403871a40f201881d6a168568e41a293163d400a6ac842a2381a20c835607ab3b94b6b070f5e4456c26c996993597638a8f181340dac0d323376bb41a8dd0210411d6120b0f80a901c74b075a7897326725603f66f2f4c8d3236bb4d289021402b1517108ce94357c05af089417ed9bb63a5b4050da81c00321e40921fd02a7a66bacc704241ca1383d80b00402079e208548312bb65474b844058105222d4082e7c5e4465cd70f44cf0323b05359d27ca06102518f0705974888615d31144889061e706011684a92374852a491e16a226385c392a813ccb2903663a9d0405c0f345cdad62ae1718106920a5a46d8b932620d064f386090a084020068c0361467dca0d182a3cb519e34233869f040972b585236090a1e8a44c02212de14fe10e0e883112c581540d85864c7ca891d75dac4030a9de0e4429d471eaa8400414762647de10e1816d6a41e9a0400218026535a443800f29481083262d6f00e0941c44ba941e16c8e12b71335e20cf131f5158889130162b0189201882b087c294034100219395e5cf409f5668097885c002e16b89200203277ea74490002115126c468f064ea4d2aa754035a2ba8d862277845c3ca985d570b5f8a298b1e2530019db22c2e10b430810abe1c04b4b0e281cf0344246979024381b79363080a72dab43611302d0782962c82272f32630e055221ca8d96ccdac0738d22d803c8001b753ef1d0a058a065460a488735177222793062029c4f6327073401c28b3c64ba68d0414a3e6483040e427461b1c1923580e00d46d04515783823a8449d184e3c6cf0da00510a0060704298b114720318a0e3d2c088290b468c9909618092b7103948a42410958229208a10522031b06695c8cac1a9d08342051db688b041d38d8aa828b2a6c00b07dae67cf9026eb844c501148805e6498f34686116d7a4cdc04114376f249e84c93a13254705194950b24c093b584bd1c0c9001b3d645e3861b453c22602002200c1c252d8e6ce13383a0e18c0a64d040e6c19d18981378a508820229259112e3e4274f0a1c82445ad0a13903360282dd1b180f4010b969d9c072328022225121708fe804028023d238e6469883406c3d20f1d181f07550c0b14a4168f9c07a118c0c4a4c4e221005230b00af8f472aa121745918ff7410f0317fa885064c296362ec88c6095234e80292c804570828aa639676f2948642584f020018f21b52b4640443f0048e266832f8dbebc68483e1091a3c3a98f63821f80484464d9a5609989478b0ab0b140cfe41525d08d1348c298b9629bf2028b0a1bb0a4456f59aefa18b026eb02f64e4f93070e8c3018a3444497961613db0f924161075e0805fb6060060f8f913b95a555668529a4684420a6cf086827fc64d988c46f2c71600f170f489550e681169cb4bc802105c64b87366fa1515e0fe075295746cb4c0b430869fa35ccd888a281c6871081b8d8284fa446b07140958770030f1025505013d803830f112fa218026f3a4c729433a9bc90c6040f3a4a842e330268d8e02684d82517d84af00010959587ead08e01d04aa835007683129f0066492baa6c9c00f3808a37560a7530020201d2ae4a104be4409e44200ca9a199428750061ef0d8862cb8f24393f0f3a60a0945e2f6e840618a86e806d38d149cc24070629370a1e5c2c9211a1d880006449a19371d3450678428e44d93c12a8616891762086890a148082ffc76e0204425d40829368813c5024acc8c2c671ec579158860892087ca521e165e6863b201f0a04482c40805845419614b3f1c4ce9a32545231150a030c0044129805dc049500b3746db654c1bac9f4cca91a760a420c80a3fe188495846685834aa470d4d50d8e2c0e9da84e283124e1c02e0e41dc077610a88c9c3bad8d1e2ca1409612f0c3922298ba9139f0009390b22e2c9192c03de19abadc6035e26cc59d1e9448ab1115a3bae4664186080f4c52325ed5598123c01df1d747a21ae31129ac4a264897a4049072d9388bcd06754a3b4024468227840952f72d6ec18b0a9824d8d4a10b2e9aa0b2e8674288fa71e4bde7e5cf9d1085496dce80b181b6cba2a2502c185a64c6673b4162122e7b31d798850ed8039254a8f7282a0a8e1b2e3c9ce1e351c584113a393a6102aa8c8324e8271049396aa1718dc328da02543939202c4cc2ba4e824662703316acae4907c62c3f6c7108904d404a06340934bbc1041df21aa382e24e1f3029196d611147025c2102a2548440b67bb03856a54d86aa442545ca9f10c9f41a72ad06d3224c31307102dc8b151c01a1598406934e98713402b1022414a480f455b8044542012d366d2d712522ad0392bc67e1d60b64014285a83e3e00dc41444e5065b2f3250e3e88315620e49d940a243097b61cede23c00086ba4899e23d5234d96119f0e8c9d6a11f33663827a4d48018c04fdb016c678d4e6165c075e8ec0f86111dc0c001bc8045970a1cb9c5a658b401802283f95107506d323c1db0f601501c0034c032e306a04184a552a148ac6969b485843323c4c6746c70c20ca038892bc7eceb0c1514581f68bde08020391cb08061c44eb572002b06066026652222878a88202f66a8b0c12162023d7ab2769f4650d9214731bfe5d109df5914d963c20941270a30c5a8d41c2f5715000a0527810a0890fae1e6d498085de244e87e71234a76dcde20b83a15a2910c3226fa6850d485cc1931595f14781289839f26576bab943d225c10c20171157cd09103882e2b8220965c348ad5ec3cfa4a408b088c381320e43050c7ccc326341e52519550088ec899c0a3d29c2f5a7eb05c243060c298086464bd48f2e8910a6d6dfae03921c58d1f104cb4969c5f3e8838be74ed48607315c12322b92b824ab84fbc4ac449f2b4f2d8f0220df1264e3368d21e158fd24a688406d19d0c25187dc99107842d4d1cb03d60b616302947ae45992e1d0080e646d01ad48993839359082f289f00c05105841237461951a0c4254a25d03426645880800d3b04ced800354946a8b0824593061b32ba880a56f9a095258806049ef9d32760121fad08622dc031655348a849f37a021c43c00a35453110f53143428a42ed9e063af4812378238885402588490251e3c62888d00a563610202e120aca9021121dc01420c781724383cb23b40c3cd19891e2431e1f6843de2b0e7438f0a2dc433bb00c2dba130625dbc04d8f1ba7b4d8a8f030a209d9241632c82af52344c0c6083b206084c24890acd9cd4893363d3d5ecaa041bb8245242b51cf10a92f4d7c495c5895b6260205233704bdf016c7d5448e7a55994a381167cca9e0678c100ab284f510b352e3c015a7533b160052238807a1b816958991b5227e20143694749193250519839130204431714a77c90b00321cc591f12183f86d0ba52b284ece1e0daa91698352ae24b62d1320caa274848d971506e039f983415ad8cf433121843c34dec9f141132354db4121400d5c898892e4141d38323b238e3786c28d4ad691d02d10034072601b08e1a376e9832d2d3c5c62e0289cd6ca9a9c00414f0401bc7275481dc9c04e98798ea71059a8b664f02784245178449dd87305c92d0387ea84b7185a58a1d4ac40c2a5463e30d021a6f43e0895e301a94a190f3000a02245e9d081142b5a219228c214286d03a1f747980360844cbc126e5428c920e080b8324cbd100114d98d52f1c156a3163e898ece241154420051140b202e91c970a4518a2b17f8517db4a9808116484cfa4368050e1070c5300270e08b2e8c8f8aac18a44079f82281973c5abac4a4181100e237a3c91941094565441044110aa1c510123ab6182153e38d4171c2214881075eba1445e4a490d54bd77647ced4b8728852b146c919d183b728fd94b1a30229af16aa8c98c0118a90021f15a72828445170aa0b140f74b9d4d344a8ea875b4ecc20395eac2b8f3c5c0026040ea514c141181013da8ce0f282b5241488384a5427a47410878d831329dc21e3211e3224c8059d0d0e116117608da063ca0de00d3d5311d6b6442594108186810aacca8000604528ab910617f4dd8995ea57c5041c1c647c2499c12df9835ec5092fc508ec68e3a482960f877264b1119155028a2f269a0c98ecb4a955404212071366ba57c10a838e3427ae70cd497395e82a2c858a1123589ad3977800c947a52dcc42e04330299291217fce742822f4c0ba492b54a0dc18b414749db8242b0a0e10c2e620915182248d0c1235145c96d446056920eb4329b215dcd428dd5c92b536b1e05bb5d45e506077d0414a0793be8ce040194090dcec641225166e3a680123ee840cba8a02211040032448a71f19b48522d1cb11a58d9f12136080230702024e0d52483285084f118cdc95204e713439b244438801576f214831208408b24d901112453af4230202081eac8154c284055ee43010f6e3890e3b3c78e8d8e243056b4702211674d06652a41092a4d092430208347460b747540280f3c6501f468be4782061458bc39a17070c8d497ba10b4b1547572920e079e992c0cc4f0b6efc9025b41d1a8178f1c290481fe0504dde80a097502a9461042817e05c023c5290e4c1861b5e84f409008527263b3ee09302853084102dba73c21b2d587a3abc18a80002889f3f95aae0524b9011ac8f2d6e321ec5802187841507aa3871508091f003e187033394901456890489188345c6c1961710de0814048905f1ca9ca510d1886a0eaa8402ad17723ec4a302ad697382cb1412565636de1d32207c4c80c2160f578eb60f351ebc33af29df152320139489302be500578a74d0960a021081a3530b49262ad5e090820b0d56f8fdb2487ad4c60446573e025ebc09393304031fd4c2c696c72a8b908c3b40bea8bca19c84e97441d81a0c265217d0c081bbb4438210444170278c00eb95151ab12d5cbcd0a9b38156219d0beba12cb22a097ce9e22246feac6102d667840dad316df68c62e2e1530a710c3d32d44333400e0d96302e989505211327c90b2723b05c213028e1ad8f0a23726a2860de4142a48f0425586801c21b372319331706a1c863a76b97531b7d2d3d280d8041412405309e4a480aa18387a94e82d25c29b7ecc066979ab41245929882004f0eb60bbe42e052356b60536187850ad07825c2eec4a9cd1974b5210a080e183853a0f1c190874e1f9895e104011812826e6040e710083f691018da59f0d1ca14292c0a15c9c923382fd00cd863e7c903612084082af3c68b1b2543c2f448e3c007011190f8c273034b821f649e26a8e4468aa54f6a29d430372209260560f8053a608dc8e9619190678307333e9c447a4269caa4381e178c38b3a64318b116487ea420d3c5386ae00e0a2a867c4cc0484ed9a6ad4183c900579060c5483a603416478da65da615910eb985111e42e1c0f181025a56a0941c1550040ca9138c2420f348cb9f18ae1547893e6c13c5b2aad380a620596656a80041a5ae27178c89a862e6ca9a1866a83801c12c408ab02481132e04f5cadca408fa1df25470a3d100a3aec89491f2c5e9aa0494179f58d864a5909fac52504688f3637622803aa164e3068c93892694203902d4674f233a3d29877e08f0c0958ec326021a899120d317d50f155810049015e7c592d845a594864f589bd6918f2c5a8e5260e04f1d1d42b04090e6c182035a28c969204a8d9e130e38f31052a12c1d72b208314a4f16127a9faa70529f4c5c79c7e2689d963a9d18f36ac304cec7a227c0070ae4d81e8c4045e082261f97b13152326c891082c90839e864952853868170920bfa34471dbbefe131aa395aeb9ece4d5af3554142d8a475088c42b81382f1625e38cb1ce8e6f918246c07f96921e874dc414970645d767bfc8f43e72071d2394822e81c24453a074990ce419aa3739014d0394858740e529cce41fae91c2456e72005d13948623a0749d43948a1d69ac3002342e4ac3068f8e520858abf8ceece309cd6c2708cd6eb33ffc96e0fef73d37a32d7f13d7c2607e1fff2cf83ac27731d3feef308232b9bf9fcdd784d6b7d6a86e1b41776fd5f7ddc5477e71fa6b52e85a1ed05193e637bddd7e5bf9f36e6820fcfc5b4d69ad2d85e576bdd82cc674ea6b52695c95115ed5d8ef63f0e9d2c035dd707fcf1ffc6e379e02fc886dc0b560b7572c803bab3dc7b9bd115239c11c2086384ffb73baeb0460874e7ddfc7d33ba80eeccd57dbcef6a84e1fc7d3264845eb31c8c793c5ea803c6b1a15717d09d43f8e50abf0225a936b08e238ba228996e919ba5474f34ab4aee932ccf91ec632f59d63cb36ed2fff46459b634fd57d3d2ccea7dc95b94ede3e7bfb72603ebd9a6e7274bd58b6629f6922cb37e6e922657495e9e6cb949b82db7d6726c95e974313cf3f8cf6efce732787ecb74baf8cfee0e9e5f57a7ebdf8d14f759911dd515aa0556d26ccf4e927fe422dbf69424b3a2bd6fb2554bb635d9b4e4620fddeff7ede077a76eb3efe26637e6c0749f15d9e1a052606dd5fdf9297eb1e5fd34fba61a439e676a6e72ef739fe216b789a25b15b9e763dfeab849f44c45332a312b79929d1cd95fa667e7248a26b08aa2f766bad9b32d477593650fdd3d3908dcb5f982acbab596a32cf6d1f26f36b615646b6c8bc7d6d896ffef9b0f9d2efe6d31ffb53fed7bf84ca7d3e97429ba4f736f2a16a8c2c4b31c8c773b2d2a0456d62c4d5444bb577f0fdda9da0f8e71e8743a5d3ccb678f0acc7aa2a2ca55ef49df59cef2b187ee72e7a0fab2a65cfdac598ede6cd3961cd51ebac5f1177cbdac694ba6a44ff7b953966c4b4f8ebfe07bc0da7bd9cb2ff6d24c47966cd31eba77b23bee2e68aa92688a8eaa68a226ea3bda7ad3abe889aae93f4df58c8acb2aee94dc645aa2632ab6a2bc837db9cd78964faa2deb57c76f725164cdb2e4eadf2d8f4acbaaaa7d9b3c3549d6ab2c6fc51ebadffe4bc6bfd763fed5e2596d6f349ee52c1b1d4bd444cb3435c5524d51b24cc9b6f7563dcb72b3e589a25458d6f454cd2f8eec3f55d2dce4eea17b2475505d5955f193ed17fb3fc5d48bdf7ce1df0fee3b1fffb9ea68525959558e76be51311d515544e3e2f9fde62cbb66aa4a55651dd3cd9ee6e72c2b7eb434bf0f185c13a9a8aca6ea7dc93d5a6e53ed232bf6d06dce3d18cf6a7ba7f3afa59ab296ede61e155b9e9abb2449d4a592b29a6adaf929f27293661ff7f96720f7619ea3e97472fcd3f3acc88e8d2acacac7d2447f674db51c53332d832c94b5f43cede5ee9d9764b945b43c5df6c99aa27f9fbd97e488a2a64fc9cd9d6cf43c4f323df798aaa4ba5135454b72e469ca96e3d9dbb33c83bbf9d7a2654dd6fe53536d47f6dc25db9e9f1c49e0cf2559035671b3aadac74dfaf4147f2af6d0bdf3057fe266b2feb4dc9bb33df56737cffe7be81e53b1841b4936572c3d7fc1978c5b47f273d5a3ad499afda725db43f70f8e5b559ee3eff6c23d8d6799845bd9cd77da9eaac837aba6bcc3ed5bd6dd97a3e97470fbba3bf4acc8ae7d5b4f3eb22c6ffb4ebfaab6ad3abaad9da7a939963e354572dcff9f637025cb71ec7b1ccdb125c5b2935b6b39b6e07c19dbcbd3743a9d4e06c21b3d2bb2f3f37a9a5e4cd94ff674e4deab7fbc21af9ff333ed294b9a69a976cf9e64f1ea51534d5bb2fc6749fabf13c9b0256bcb79dbcd746c499ea2deff9dcff77b095c4f136db798f6b2f3ce8a674a9e67457647e0b535779a72b573f6a769e75ba7f3f97e17cfb3223b22ef3a9e623ba2e2e665ba37ffe3cebfe568fed634fdac28a669e9bf68fad1eca1dbbf4138dec595447c2bbacbbed56f72ce966459feb3ac6d25dbd37b944ccdf34cbd3802e19dba83b21bb399664a56b1f7cdfe3eeef69725e7ec4e7557de4f7f926c3ab6a3c88a9ef7d07dee7896c9dedac9f1b79ca7a968969bdda7ba67b0ad65fa59b434bbfad39ff24d9266bb8aa7c8a6ea66fbe8d5dd8e690fdd3bf9af5c25f7c85bdffff8cdb3e43d749b5ede447b8a726f8ee4e85ff7ed0dc26fbac1fa3a9265fb4b1155392b9ebc3dd975f75135f91749738be5ef650fdd71eccecdf53b72dc9bed75db54beeedef9c937bb3d3ba69c2d7be8f6c1aedbcec7f0ee8577e3a896266f65b7e977f93b47d57efa13eda17b47ee73dcad9f8fe8b9d393a37c9769eb77f09cd248b28a651759d57b56453bf7a908cfa68a641d4bb3fc623ba65b4cd9f434fbf73b78dea17bc691f577b19f5f3dcd5615cf34557df26e6ffffedfef0179cc66ee71a36846563f8ae2e9c516557b2fcd8ff6d03d770ef7c722eb886e7e8a2c274b9ea2aa2f7be89ee53fc7704fde99bb8cc8caa2a9297675fb92fd7c77df43b7b8df60e0ce17fca9678afe82afd89095f74d7a923577cb8a9e2dd9b24cc86a9aa65872df79797ef5fcec9823c88a8eaa6896688bf2322d555eae6de148db1a730059395a9aaa699aacfa7dfb4f4fea585b51b29b6c79b662dfdbb39e6fb5f514dbddcd54255bef3d67d57896c5356d25d1decdb22c7de7e6473b0cb33a7eace9475bcf9ea2fab98972cf8ee68ab69ee926791fcbf3a72247cfdd43f78edc9d387cacdef49d93e328a26299eeadee31ec6ce56d27f7a89a9e654996abbe876e798e3970e7ffac16d06315db7624c796e5fd2cd3ed3990d499ad9b6f5364c7913dfbd8f78883c72ab6beb39e7fd524c5dfa2640fdd3e1eb3fdf975779e03c71dff599d7f2d99ee5877cbd553544b34fdbc9facd943f71b0165abf87dead9b19f6ddb4d546de530f3acc86e888e3565dbeec9f3b7ec6749bf7d0fdd33dcdfb105d9ef7767cc7ba38cc6bcd74bb51cabf8c79faa65297ad26c7bf7e9b8c91d8e3535f729fe7f8add647df7bf3c83bb2fc71c58ee725d3b4fcd91976cdb49957f76eca17b8e6196b720f3ed5755ef51ae8e2de7ec2e1b1b53b13cbf67d1b2ffcfa6e73e136337da9262efe5f9bd5a9261ed38b6e74f4b4ffe161dd5cee3466f3b2a96a698fe1445c5b68035f566cbaaaaefadd9db917b1babb9c9544cd19d8aa9696e8eaa698d75fcbc24cf36dde5464771a71b8cc69a6ed597663a72f41c45f1e41e069bb1a2e5a98ee91f4bbf49d43449ef61b0951c4fafa21d35ff56d9946f7154d5f56c4bd5a7bb3c4fd5644ff4f7d0ed03fedcdd1cfcede07997a2e9ba4753f5e2e79b25cb14f5ec0c743dcf5fb22d798ee2389e6237c771c6b9a2e7389628db55b364cb72ab6f30ba4f7355773fbf79b224d95356e5fcfd6e7f07ffe018b847b0b23ccf5dfaf1fb93dd636f7be806c2b1df65a4f93d69a662dbc5b245c533b8c47d833bd93dcfc6f52cd514fdadc95954345b91f7d03d077f7b4db9c8f2563547767b73ab680f9ec92d6e31ed236f55f197e8986ed6d777e3de2163ddfd2c45b6ddea38962a7bb63d74a762e65991dda85b4b72fcbd14bd79ee361d51afd3f9743af53c2bb263bfc89d92244aaa648aaa7b8fa76aee5e965d2453ef51b2e45cfd4c5c39d86e8963d007fcb9a3f90bbee4fc6bdfbc56ae9a68598e7b979e93accaf6d00dcfafbbdbfb4c4d998db17ed224d5f13cdbd1dce426cd1eba65f9a73f86c7621e841dcb7d9e15d9a130e0badcb8567f9eea897e913ccfd4fc6ad94377fcdfd7dd8dfb9c7f2d79be58c7b6454f12ddbdfc686f5333dd280163da76f46479175bb22c4d93640bb29f8c6c73718e6269aa238a9ae3689aa3b8cff26fad24a976b23c4befd38faa9df7d0fd5f78f706c76ce9588b53f46d9b8e9c6c4d53257fda43f71b1cb3ed360f166b67f9b953d6b72cd943f748ae583de9fd275bf6e4e3e937da7be8def997efbe5bfcf7886eade5d80adb1adb5adb5a33dbea3fc81b4f538984e5a992ea4ed32d8adb5453751c47ddcfcde7d3ad25efbd9b28d9fefd53762c7be8dea11b35cf8aec7ea8f8c1f0cee7e3dd4e80142bff6a574fb6f52a29b6e8e7bd2eee1d8a95445193544f7e7ece4fd4ab3d74c7b3bcb596634ba79b7fad4ef763b8cb7c4b1c83bbd43c2bb24372623d511565f71ed3b1fca7d8b23d746fade5d852d3e974baad35b3adddebee8070ecc7329e816626d66e922aaab27ff49cddea667be89e7fed4efdb31b0b31e75f4bfe18eee559919da835962031228588ac10284060c1b87a91933f788031408a0e2538c81b04d010c0dec1fd131950009002c3698e5661155509dde1cf86352950fd64fea6439142ff1f7f316c60538883cec04b9430fd03cdc7590f331e2bd998d88d026cd4a031034c4dd1d32cc9117da4b5a68241358a83cea04b9630fd67377ef3fcc7ece8b603db01e5d5cc7ac0d5daa42b040bbd4230ae35ed3f88ec02c36417c87b7390fb803f976c03505e7b83c734e80c0060903f1b8f3f8f57d3344dd334cbb22ccbb22ccb9224499224499224c7711cc7711cc771efbdf7de7b6f711447711447711447711447711447555555555555554dd3344dd3344d53144551144551143dcff33ccff33c4fd3344dd3344dd32ccbb22ccbb22c4b922449922449921cc7711cc7711cc7bdf7de7b6f51144551144551dcaaaaaaaaaaaaaa9aa6699aa6699aa6288aa2288aa2287a9ee7799ee7799ea6699aa6699aa6599665599665599624499224499224398ee3388ee3388e7befbdf7de5bdc6a8a9e66498ebbc7af4c13b902c340fbf857fb315a9ceff5f13ddce3bbf11a977d5fd55699eb587dbcc366656a71b31b04087cf62bfb337fcb62cfc55ed8f55c6c2c4d6eebb04fac37c7ae580bc3319ae762dfc767b51f07e7200e9f8f8fc5cdeeee3447f86d6ddeb1bd542d95a7ea5a53b15a53712ade77e335246b9132b51dd94ae663ec6cdda5f9484b5b77ab1910321f3d7643c27a70d9f71fcb6060ea36cdd1ff99b371201c03cddc3edb0bfb6cb0f135b4f5fd6df05a1ab4f1fb73191becf365ff75d9de75ecaa2c355b0a72ac334d5d466b3dea2b728b1cebcaed16bb5fe50e128e8bae461257e399ab5de54a5cebe2a2d25ac7f9c21fef28d39a4a07558e3732aac8b12e8eddc9320776de7ecd2f7fdd9f1b77d9f53f99bf85b00eada970c83e3ef3b60d966d845e34f8cc3d500ec203c87bfc1faf89e9d6abaf425aeb745a537951c1e8a2e2baf27acb1898b4be8ac1a6a95e501140e5826aab85f6651f18b716f77980818de4150c09ae60d8e9d2e9745ca1d61a88d6555aaf6968a9a9a676d5856607947f700907faf071d545438e75b3cba6e3dec9f107fa7e2f1d77f08a6b0220fec3b938766060fec3b96d18c32ddf0165acab1776b41ceb601785d654274c687dc6cfdad7fd6a5fb78a8ed6faeb2b0216cd33fc429163800a1a7266128a5b214c1bb400d305450cecc73b05521f2803c0307109c317304f1cad917062511ce9091b383e76dc3062e9c96f120f383a01ab27e8251a5974f0cc13ab13a5d432a2ec38e1404ca1235d5dcc9c2cf096c1ce8a1a1b9cf4306d16689199e144069a9c160ca8514d480d10848fbc02a126186c88a3c120186913301ab4b4295a6ba10911376238486061a1c996150e1a60926167c0a7360e775c48cd0ce84346894a8c03701860c52a5395961a8c0c4053d4c9042323c900ad4ceb0a51449230c164058309af1c5098dc41cf262d7c9f89015cb6f0d23930131c02cc88f225a40b932b352fb45be400e34228096d7863d2230e02306966842954232e1ec04cc61b353b8e8625105066724a5c0026c4101d61810a8e511313b823091b1c9a04b2e08123b681638b00ac6b4d053e70e3098556e404917a335576a3d4d4c091b74260650699f0c4ce5b026c8833804602de76566b5f2e5001be712972e322e34c11371520c560d3c549965ba00b282961c2fdb86961c3e4a9d0990cb735b51443d4d8e8e2168a3d996366aa51909355294b21360015cca3c8d4d99b060b825120f81cc2fcd8823b1cf880125a1d0ec12c1f93a0c88168cc2374b8330a3ad5e73839a470ea608106e620230e80cc4c48cc61c1259358281462e680c20905844070456e34848438716918e43595266041685e24fb180084215e93229f96c44546dc12c9556707c84d761ce2443b0ce8d4c28e1393790d6911b38296980d14c09a31163e88bf4cc566d280525b22ca942011250e0861c9a0b11481892312967899a04f290488b42d191246c60e0b1164498b372e2ed093ae8028c8b099524c09400aba08c81a04670d5825ec447626cb18100d5862d09f28858e40812f9d5120d14f0422d88e9375c916560d9809ab414f062a62e0d504188cb6336e70ce92184f7f9c78bd9230ac70d202a7f4b639a3898c0b52a0bc4b76a4517a81adcc3b034e0af22bfef266587147d016411d7e1d007f67d644f8094342a65f1b411c43623e3afdc61541c0ccc82d7ca5aa5a8019c507f511d2c017064c1f0652421df890e44132c84b8729be17839e9c1c6a986d2a541d4a027b186d15b4650102658f6d7edad4de1658812b88d8a6645327aad4ca011b94e041a1fc421fd0a0040aa65007910cb2923225215c3a62858274702bad71e9a9216be4c209290d90080c943b6b22e1c8115bc4bdc1a7921c396775dc0d17b03199b3e34e0f04362b2e0002a1486f8d8a065853643a7a6d3db39306af0dc438ea5165f140888637106c883ca400e566ab139b024b242862020a2d6c7c052f755aecf880cdab8f134c00905d695970810c272568d256104bc9931f3ba4da03949cbab493f2a5159205d1041270d0a165e141a3515f38d4f89e545450e4091cff7c4afcac4d6ad3a792a34a6e054226fcb3471e9cadc9213e8939c147635fc63c098044553a3b9cbe8e0244da3f9582f8b835449a8807ce93f99c9729aaab97a643aa146488d80d53fd013c7038ccb08d1df82581b63a562ee86050d2564c6c147500c63728a74d6dd845ac1916294e61d21fb03f0fa632557404ece62e14f240149eb203d7006cccebcb91461c814226a0029cc0f1232b7002047de0027e61102f05d3ad0a1132553c403c2c677274585edce1a5b509c2254dda0a3c142008590fed3961a76912a5360176a07678466042010ccf8bdd932e0e9040d229dee9004e89bbf4c7c82e09251d404468120225490419e0c051808528c90c00902f9ea446496e241140cd2f83258e85e4832842523d5a41246a5a395f48e8608044498d4a3ac65cfa21298520d187808c78c8714736a0a4c5092bcdc891315074f80993d4e4081fd58ce1a04c3c12a3871116ac8e6c9a20ac61e482333bd430c844c64897af310a666ce86224881116454c88c40260e4ab81227bb24ca04db69e14993216e95164041b133008a1a600229688d021024764ec02221ecc218f863830041ea2b60dc1d0238f4c224141c8a22e42ca5412e2a3081183b41024831d620e0dc4980a8240102358415c203255c37ce4010828e0211f03440140fc00b9928642083c126c80fc90a482083c38360159cb14028997d40a47d624289be342cc20276b4ed672f4b8e851cd5cac61f511000921600ab5492c04d94778ba9a1b1f6a7207351b74bad04981cf51d3b095e6018ab434696e5a99a67f1862f2434ca11fbcdf0f1846a8260000880168a1d02c40dbb2430382b6a28c8f4e3e2610e1438a0f331f6796cee474387bab7126e6cf06e8e1a8079b1e6f8f34540fabd8c5d27204cc14a108b6019e3161b680593793c183140f0c40f00013018f22613cb638d8f169479f1d5676fc30e12183279e811d6598e4b23b47ca9a94e12833d311429c0e08b88e58938e1a6d74049083518e0672f448c841e23085a3108e0446e0d8e1e8e2b4aeb05ab06aa95ad7cc35e402c38bab0a01324e647ac8a290f1201b1b612cce98db58d85807b146626cc47e62a8585558a2443f8c8ca32a611d6b5198ae4a941ba394dc1894e5c6971b3788dcd82ae0933ea283711b7c6736d87edf59ae3fe94a7a91b640dbd16ac0e8281aa8791a4ddfd0a20e754e6bfd417bd003e80e9a83dea005d01a74003a83064063d056ba4a53695dd5ab12c086d6555654a7a85aa09a43d54653a9b91a21cab1cef31f577da8e25055e50295a82b114db42ff88af022d6b4d6bc2b11a51621e24a84060df6817173d6a5abac488cb21069adbb42302f2faeabac08b2d4bcbfa00fee61f85f37d87f2cf3df0fee2e8f67b8df0b391b1cc741f9bfb0afab7ddd2f1931c2180ecff85938c36c5ce19fddd76d43f9cf70704918f7dbe27e5b178e2d8ac1235fa2288ac1235ca10fe6b17082c48b07c60bfb57c2c2e11b7360f87d6eef2dc4e163353345ebae976b17f35edfc9aeaef57d0f19b4b078575794a8b4a84a684d45426baa115422b4d6e5191475a1d70beb0cfa2ad784a7a193d61a89befa70eafb728fdfc71feeff82ef07f2dfdb7f67f70d5e7d80d3e1d5872373e731eff5ab0f3478e1fcba70a8b5e685f167fbf3eb5e7df870f521038f17c7e398b3bd5c6baa9cd6541fb4a6f2a0d603cc958739d65a7bd09ad55703a420c7badecc8132e266392dfcb17d39eccd1c4805001898d65418ae3a8cdaa3437410d3ba459c63cf0a00065d71e872b561d495000b5c0960a51349e4d97066c2589334b811f6cb7b804a0ec4a0197190333df8156585213dd07a915434900115408400c8a28303350b0cc0138985123c3a38b70fc32f000e6ea4025d068c0a41c34d92033e596df10910e308dc0a192de4442b986c1039f3fef980851d9e5697422534d15853eac89793164ec0d0a30533c28d107c4a9df93240a65821e703690348d1b9044a3d420333f54dc08da2323b62a0d5bac2aa800701321a62ac6001a3b52e306a64a53eba8ecc701e0441c2e6018e007e7c8ed4a3134800c38be0ea525abc620d3b81892e2322524d541ea864e084662ac22024ab4f1db216d2d86c284afdc8490a38e55cb12e16150a33612c803726d4a553493e99584443ca0d37ae84531a05143e3874484bc40d91aded43c8d3678048173834d818708548052b9d0aa501c56984116aac953a3374c2001534e4cce4893856542ef8d15d9a4514c2b4410b305c24fca9627b614c201430b0c07ebc5320f5f161c8cc950ee7d48e2c286098b884e10b18286f46390240844f36c08835124e2c8a219deac39383e10ea7338eb0e0f8d871c3c8086578a64f56176471128762120f383a81aa2f561c4aa43933c0182f232ed1c8a28366da9cd13082591347701e8138514a2d23aace1e3b01683940060b051029c4143ad2d5c58c48890744f9b63a915041e22d839d153504a834025a069f5064a954024498360bb4c8ccd0ea1109556187079998a84093d38201350a86882e155288a0c2118c120304e123afa041962b3f5c989e40a1b41662431c0d06c1487d915e08f72cd09a8de534686953b446ea531d51cb2bae13f02ce1460c07092c2cf4c054621093237132010a61858306986448f2c09e1c8ac68840a1d306b9360e775c48794c402a430170e002a100821619252a310e58417901fe49079ccac422cb2a5395961a1d78e170408a1d8e4e753acb29ea64829189f448801a7740882ba3858356a675852822490808ca98820257a8375859563098f0caf1c40a8c2ebd406aa2287873859e4d5af83e0aa44658e60329ae6682cb165e3ae7adc053a192abc4972a1522049811e54b08974329a883d472b4316376a5e685768b1cb89111ee7ef0556952e3444968c31b13b32d311479801222802f9d08306966842954634690026c20ca0100a802ac01cc64bc5123be4076ea033b74e0173861090494999492c09a05d8988de0238022234c88213ac2029598a639670e688a7d3ab09a98c01d49d87a1408894f50c015440d8a04b2e08123b6998d043ad1c20591b6c811118075ada9b0074cd9b753c84c881206619d5068454e10a91b607b2c50d324e7a72a45d98d52530346ca88eac2c6e6a509013424c0ca0c32e1491d3a3b07ac98d0c284f9090836c4194023012e4af8682008951660bc0aabb52f17a80067189140814f1d308ab2868422372e32ce103124820bce02379218d0020129069b2e4e42a811e10a217da54c47968a0b282961c2fb3050ea81250c008337589460c3e4a9d099ce92a7e2258b000cc8b3106a2986a8b1c165139a6be32dedfcb22427f6648e99a9466a3296ef159641015eb4ac4a590ab1c1272b56dabc734e0ddf4e0845a6cede3458a0a4831f2e8424f9a5202007c1e710e6d7fa89e4e607d69d4f973a6939f00125b43a1b3480d1e6ac8a0f4d1fc8c9f231098a1c6846077ef6c456b28a4941ae3adc19059dea1e6464b082a22f6677b04ce490c2a983051a705282461f4c128438a392c58803203313123e8438a68a17b736e75570c924160a81a03256e4ea7c94841302265038a1801008ac34c0a84bcfd6011ece1411434888139786018a072c18a0a8c18945d555a50958109a17fd36aa6bc7d4da26aa130c00c210af4561b326545053a935085c1196c44546dc129d691285c0153fc6670debec00b9c96e439b2b3b370d4c70260137143b0ce8d4c286c30658a6c3defae20368c46b488b9815b24851618e0345a808d1c01456a000d68cb1e081992e217a2c55c949b351988acda401958680a4405a5c34954c4d30c2942011250e90e4c6d2a9352b7868fd2420c65204268e2c99a0a74c8f3c34b250604a31419f520810690b014450d6095e9e149841228c8c1d160248042294220811e282ec05106f5c5ca0275daddd1984094b0e8a458654c8b09952cc262c533cd080a2928c3c6e25ba08c81a04472d8d9910323c314b81050c42849dc8ce6411cb24a2ac814340413bab9c1690454e544529e0c030c107f4ccac4a0cfa13a5d091276408c0808d59962ca78310671448f41371488e0a2b3d02c011f4c94dcec9ba640b8b8616095c246b80f42cb8a2c56ad093818a980f542419aa6211566ec8320106a3ed8c5bac888400ca4d65d79a9c25319efe3889f1a5839e3448ae8059716602c30a272d704a3c726431f5c286286659d6194d645c900205f6fd1630c446c35873b5238dd20b6c655c7c1232e7354102e961024e0af22bf6e280960b4b6519a4f04482881577046d11c4410f9d2a7a02588489cba6ab3c5ecc191cc018704183244da41f34fa9c5813e1270c099902580b8d4104464a0b83444410c790988f4e2a06356f8d22518d71b15604013323b310638527012c1d54646b28aa5a8019c507555355fc980092193557469425dcd0805406217c49e0260326963231c202d387819450074d4019a14049213d568228f22019e4a5c3143ec5cb880803a030c3d388414f4e0e354c2d0eb4904082830b435c40541d4a027b18063c7a7de0807fbc02a4502242292819e0ec2cb0f3c4118929656f480081b2c7363f5012113985c584ea832b20de1658812b888c324b4220f25088ca8d910a17175c81e09319571e4f20764032030a8d4427aad4ca0102f48131e4031ca2c905038040a1fc0a3adddc6373ff5b1feb3136b6b58536923a1d109dce14b7864818f0ac9f4cff264bef4d3f96aa6fd5d2080609d69eeeae7a9393db73d4f3f6ceca8eaad95bb3ab2269a29e7f3b2b175b5345555644b7475591acb39a24ba45b64c3bdf18e8ac68ab9aecff2ad9fd9cd5b37fdc247baaa448a26337e5aced1f7def6639eeced93425e3aca94f3939b2e63751d47f726380b38a9efb3fb2e8c9cd92fd698f60fd7efb13e52d47792759b2bf59c7de55f1e4bbdde52ff9c9ba59cfb28b65aaaabdfc26df2287601545934d49d26f6faa9d6cd5366b4b6ed177ffdbf497e9e9d507561535c7affa32254d7fb2aceac0aafeb214cbb2fcece8476eaa6c56df7615f5beecede937dbd335abff662b96e448a6a957779aaa59d3b4aba9faf92ef95677798eb21e0c69d6b273efbf799aead8c78da60dacfde4dd54c5536c4dd697628a663d51f6937f9f6dcb8aa569a26756b5657dd9c9961545f2a3dfccaafacd7ef224d53465d5b29f0cac63e94d9f96242fcf322baaa29f6dd3d6ab5f4c51b35c60354d5e8e6967cd52dc2ce949322b499a252a9a62ffe3564d5681959726574fd29b3b2d3dc98e634e9f961d3d4fced134010c62d0ffd5f47791dda379b6fedc2a190c0998fb2ccb8daae8586e74244f31dd23e7bbdda778ee911549df43771acf400c61565eeecff2d69f6aa98a258ac02a92e67876542dc716fdad39825949d314c5913c4fb1257979ee2f6b6a96ad88fe727bdfc9f2dc3e4e6e1f18bcac5f44cdb29fe3c99a665a96690fdd9b07860356de7bca9a7d9364c98e5c357be816f73862e8b29efcf3942cf9388a6a1c0c5cd67fb6a4c9372beed67b534c770418b6ac1c3577caa6a44fc7b6554f5410062d2b3ba2a5e7646992e8e87befe2d8074396b5b724c9b69e244971b7be732e18b0aca82fcdf63c4d13fd6dda92a5badb59305c59d9f42c376bfab28ba4f79fdd82c1cada8eec2fbf699adb4ccdcd4b9d6e87a1ca6aa25bf43c153ddb76b2939fcada9e622b72516c4d51355374eca11b9ddd9febdbf566e0f77018a6ac232bb23b554f93f5643fd1543c8241ca4a9efc73fe3b577b3af2b66388b26ed41ccbf214c57144f766d5532bdfe5264d12fd7ca7a4799a5afdc9fe9665fb49aa65efe32fb58e9d54d1b46df9de7eb7a47f61fd9d9fa769fa9da6233996dc8595144fb1aba838aaede66da9b6b09628bbc9f4fb749729675352eb78aa66db7e5efefe51d54c47ad66fbcb315559b19f9b1d7d296ae57ff3f4ab261751f4aba268c5c2bad5f37fdec96df6141dd1b487ee50abd87f5afefe559645d97f9ee6c880d50aab58ee5464d9fff9d9c55e923fe62cb9bd58a9b072f1f42c6b6e3555b98876aea6d3e974e7560559a5b0a6e466d3ee53f6abe6d98a1bd49a9e5d353bebb9f9cf8d967e6c4b44db1adb127d6c8d6d89675b635b628fadb12dd16c6b6c4be4b18566b625ee3e569fd6b19b24a9ee9faae3f8b958b659a1b0b6bb97bd93ac28b65e157bd94381bc3ec6fd06772f4c6e57253dada7efdb54b71f459f929cab27ac6949aabe54cfb697a2d8b2a709682fd5f2fc9d569e8edbfca57a9a1fedac58f6d0dd9bddb732eef3f0f10e9ba15bd561c569eb925895b0b2e54f37abfe7f9aacfa3b7b5664375b6d5a51313d3d79b229ffeda98add4a134f4db59fbded28ebdbb4ec94ac32a9aa98aaa86aaaedd87bcb7be896e1338a15a69555c52f9e7d44cb76a3a719cfc04b9ee5bbf47d73b245c5d3f7d0bd83bd48c25a72d19bff9ba267d59d8aa3a596f354ede8e9d9ef53b3eca17b07fbbdc57134c06a84d554595615fbe9493e9625db7be876d3153dd5713c2bb2abca62556945777bfa3f7a94fda7efe7d943779cef85c9bdb566b6b5a354a6657ab2275a8e7e8f5ded8fac44e06a49eeb1fbcdcf74cba3c1ad1f0765d8d7b75834b8b54bc73876378edfc7f2f605a2d339b29ab4b6e948b66c99966869ee4d8ae558ee1e5621a4e51e51933449f12c45521d774996ed57d52feed49fa3d89ea3222b492bdb72df8a249b8e5b635b6b66261dab48eb177b3f59cf8a228aa25efd5690569eb69eb3bea7bc5551f3a71c583d5a3d29b2bb6dc97e8ea2e72c3f0b59395ab9e94fd5b362ca92bb6cf9b9f76e9b1a58355adbfe49b36c4fb4ecbfffd28f1b9015082bcbd9dd7651ed67aad3e9743addb8777b8fb2b81b597db0f6f1f776b3e627c9df6e51d4b135b69563dd1adb221b4333db3ac0ca83b57754153d3fcff1a7fe1ccd9ecc756c8d6dad996de9743add6e9fdd45ad18ad28f9d59644b7d9c77293e269feac3a58d59ff252f4dd8b5c144f75fe6ce89e7f0e9dce6a116ba56835799b9a1dede58976d5b7eaaad37dafd37d1f7f2b0eceae9222cad98f8a238ab23eefee5e183d8bbfe75f47d9eb031dad12ad243a9ee5b9f9fe65bb4db5a76745764bac10ada4d89e696fcfd297ac6f7f3e1fdfb1eab8b596632b4cac93d5a1b56c7d697a93ffddbf798a630fdd2f2ceeadb51c5b664912b2da60e56dff9b1c3f2ba2234a9a640fddaab80dad28fa5335e5bf64c7cef67fa2b12ab47adf53f28fedf8fbe6ea1e7ba8ca8015a1758fe8f6db73f44c4df134450dd616fde73fcf2ffe8d8ae569a219ac66299ead49967e9fa24fd1eff3f1b1148c15066b5bfe7d8e6d6755135551ae0e5ad9913c77caa6bc343b17fb2e8d95a095eda6798ea78a8ea2f855de7be88ecf0bd64da6a3398a67ab6edecfbf47726765c18aaa253f5bce7a15f527e959797eb9390eb10ab4aa9f971c3d4596dce2fe63db43cf6deed38755052bbb49b435d1dfbd48f6d69b3d74a76c5680d2b11ccbb44cd53daa63298ae2399a6cf7bf44c99d6eb5547b0fdd62acfeacbd244554654d933dbf68a2bb38d89f4cacfcace537557f7e52e4223fd13df6d02d9770567d56b62479e77d8fddb7aac851f3881505ebd8926929aaa427cfb43d47b1876ef81d47372b3eeb58a62dfb53534cd951dda6e9f718b4dab39e663fd1cf77f9bd47b9df3d74cb13ac6c2b9a6ddfe436515564fdeea17bf7f23efaacf4ac24aba6e72ffbb97dcbee3ef6d02d9a4aacf2ac67cb9a27cbaaa6c94b13ed1fcffa5b953dfba89aa87ab2db9b12ac1c4db738a2ad3a8e9d4dc57d67253dda5954444f3565d15f8a3d74efe2e299cf9c6c6ccb97e9b6d6726cf99d2fc72a3b5676d6dfd1f31c4f922c77ab8ee7efa1fb85d3d7c7b8c7b6c4750bcd6c6bb4aab3921d1d773b6e2e9a5b6b39743ab90d867be446c08aceca8ee4f7e629b2bbab6257c51eba7768a95acd59cfb634539ffa737b922cfdd943f7eec7c0719f7b87959c95454d5ffadd8a6567d3dfd3386bdf2647d14df6ef555f6ed30aceca8a6d2fbf58b2a5f94b6e8e61ac2258bbf959152545526c3fd93fd94337dbbb3b379b5fc57dc6eacd4af651dce2f9d154257f6aba59c9ae8afe6447b4a75bfd650bc1cacf92ffb6ab68bad57ff6df663ddb944d5bd18fe4f7fda70faca4a9f24db6ada9fe534ccfeec0fad9d12c3b9a7eb36fb52c399b758f244a966727d553ece356d7acde64d37244db2ea6bf77f454b3b2ea3f5bde4d53454b56fd669ab5fda37aa2e5de3c4549ce36b09a62cabf39b61ddd3d4dcf12cd6ab69eb7a4c8cd9da2e347c9336babf6ddd5fec93f9a59bd17f946377bb6ec989ae41eb362602d5594abe56fd3eec956657f997534c59444d1516d45d31c4bbec0facbd68f69cac93355c71325b3aae7998eec48f2912545b154605579a9aae399fa323d4773ab63d6f644798a7eb5ed6cdbc56d8a59cf726451d54c4dae7ab45413583b5a9aea296e93edbbff745b8539d57414c952dce23e49b52c55d5dc27e97929a6ddb442603db999b6e23f496f8e6a473d98356d55f3e4a5d9a6dc8fa8687e594551f4e43fcb543dbb7992630fdd7e47ee1c565ed63365cbf64445732c3d7b76f380b56d4791a368898ea73fc7d677594974a32247376fc7f254cfce65ddbf73344d4f161549b6fd7ccbca9a5d4549f654457224bda96a594d544c7949a6dbaba968965956969b27d9556e92a61596753cf738b2e9d89aa3597295ecdf0d87d5959545515265cbdd72346579db5a5959c9113d49b41d4571244d936dadaaac693fcb342559758fe3e8c7d68aca4a9edb9fe4c939df5d1cc7d66aca8aaaaafa4b4ff63f76be776b2565257fba59914dc73e8efcfb6f1565ddaaff652fd594732fb2ec3fb5a2e53fd5d2443f57b739f26f6afda249965d3c49b46cd38ffe52eb6e5394dd277a7e762ccbf3bfb09e6a6baabf93acf95d58452ea6a9dab63e657fcaf76f611d47f68b672f7dc9d97db69ed4caeef297e4fe7e735144593f6a4dd594e5e9587216edffab5ed49a72b4dc2dcaaabc455913f52cac9d64dbcf9624b94d91f3d3875a4fefc5cdb22cef2df945d3afb08a24ff9bece4e94d72efd2abb096a3688a263aa63b4d3d1f7d0a6b6b8e65d97b17cf91e4eae841ade4d9fb1ffdf9b7ffb4929b64db71abed2fdbb4fc28aceaf6e7688aace8b7dab29ed6ee5b72a727efe716f9f627acbde49e7756e4fc8fdbb326acaac98a7f5447734c4d92e59d56758be4398ebb253bc9a226a79565f9374bdfaa693a92e7d84b58513235f789929df7d4ff74d38a724ffe53f5a48ab6ac886a5a3bca77bbb7498a697a7293cdb4faef7d697296b35d2d4b16d39a96223bfa7db22c2ba69c5f5ac994a72adb7dd9fa93145512569625cf8d76d2f45bec246a69dde6dfdbf3f16cd391fde308eb6659b4b77cdc63caff28f24a2bc9b67ba79b4455f6ff9f525a55ff5112dd5b3d4792775484b53c51f3ff511ccfb6355973d22afe6deef1a3235a8adb976a08ebf7bd2ccb2f9e9f3cc5534d25ad2849b6a928a66cfbcf2eaa68a49545c75fb6eaf669cb479485b4fe93a7a6da729125dbdfaa8fd67eb69d45fde9515454d9d4d1ea47d2ff163dcd12154bb36cb48a2aab9edb97235b929e144f10d64ea6e679b67dfb9434cffec18aa26d7a969f35d196eda907ab4fcfdec59ea623db7a9265b4aa5b1dcdbfcb53fca2efbc83d5143de7edf764599eaca82e5acd3f9e9f2d55cfeeadb2a8a968ed68bbbb5892e76996694a72b0b6bcf5e5e6ff9f23ca96bb4cb472d29bbf8f9c24b7da961dd1aaa2df2cd9b654d5f4f7943db4f2d1ff8eb61c45473155cf0d567473bebda8aaa4888edc34b4aa6da9fa7134cf56fd5d25c9422b17fd39fe969f2ae75ffc845675b7234fcb123d3fbbbb066b6b6e93a3a4e7e4d89edb67b0faf17b951ddbed51f3f426066be9bbaafe6e8e26b95b4f8e83d66d7abf59727bb597db6405ad26f763c9c9ce4f9645cbf48275b3e2489a626b8e64273d6ac17a8afeefcdb98ab22537cf406bebcb7324d9b324396baa66056b4f5172b7dddc9df42a0b683d5bf3e4eae7aac98a7c977f56939fa8f9f9497e544545d2cfca51f3a3a54f51b124ff38f659559493685a927fe4256ff7a6603d5bb46d5154543d2f3bd9f2594996454995fca65a9adfec7b5611e5a8ba4b932dc97d8e7c82f53c7fcbf6b33c3f897e3dab4749729bbe54b76f4d36cf4aa6692bb269db374a96688a6735bf4aa626bac7f6fc1b3d255839df5c3dd3d634bfe8db3b2b596e56e5e5d99ae6eea4696715c52eaaad78967e34d193acb37235f5678ba263bbd32f6e91ceeacd164559b41cdbd3343d2fd9605f4a9e15d991557350b16ddbf4645b92b7aad8a6bed6ec5572ccf2ab6417bf674fcfb2c13e9257c5194bd21453921cd17d8a279a8e232a7af1f3df4b516579fa7f0fdd1e85cdd8d86033207ca6d3b1c1be2a38eb78a2e449961d6549b2157f1bc16ab224cb7e4efaf224d5d2ec556fd6966ff67bb114b7bacbd3a36e56b25551d524d1b6f49c145bb30a827393e856d17d8a292a8efb24535525c77644fff6e9d8fe2f92556d56dec9b6e527db53921dbdd9cd2f550face7efbeaba4cab6e4df9c25d533550eaca9e8b9dfa81f5b3135d12da661aad8ac9c64c7f2ec22697e9e8ea6a8a66acd7a6e92fb12f5a8599ee3f7a7899e15d9f5aad4ac6a177fdbaa5c1dd9d33cb9aad3a53b2f5569d6d11c4db5ef5d8a5beda548f6d02d8f635a5503eb68aa9f444b16f5e348b664efa13b1dc7aad0ac2adb3ddbbbc8fbd8fe911d7bea3ec7336be95374b3264a96242bb29e34b3fab6445b16fd67dbb6e63f4506d6fe39a98eedefa958728fb665d6f4937f8f5de5e93ebda8f21e8a03dd64550bacade8cb71dc6a59927f645514dd5245666d772bb264dbee8d9266c95b34ae4a81b5353767b9dfdbf4ad697a33dded71cc6a8a9c14f9888ae9b93749a262d6afb69e97bb1ccbf114d99474a02a81751477f949ee4bf51cbdca8ea73aa62accda4f93fbde929be5dbe4e569a50a81f5b3dcec9e6c53b1b3676fd7641ff0e79a54aac0acbba77da7ea49aa6d59b6a8ffafcbe71f9b233916a9fab272932dd1adaa1e355bce72b63c4fb1cacbfa79bad5126dfdd93947fbd94337dcef8dea4e5775c0ba4bb5fd6aab96a6c8db3d963a5d97dd9f4e27eeddeeb2fb2bcf39555d56b5dc251f4d9477ce7a7217c3bbf9b3953b45735471f9a36fb978b622f79b2dbb8fe138fe06546dd9673aa22a3996a2989ae2988e625ca56515d9111d39d9a2bd9be327cd2cabd945ae7aceaa6c9b96e65615a8c2b29a276992dd93e5488e7f2c7f0fddc0b8598ee3683a9d4e97ee765a7565445375a7e8b84d154dd3722c4b334dbf58b2bf1dc7afaa245b325065651dfbd8fa928b9c1ccdb44dcb35b3ad37784ca73bcf8aec76555556d4a32c99fabe4f533cf7d9aba8ac62c9f24d6e738be5264b54ac9ab27ab2f423dafefe772b8eecc7b135d696cd7d812a292b99723165ff173757cdbf3bcdc2c47c98e53867b873935551d6eecbb11cd5d424c7921c59d4c73c98c3c7753a9d2e0ad5a9b5ede3e9d5d6e427fb5bd44c7be88e63774bbe99a667fc67b7a432b58a9f8fa867f92fd96ea26c2ab635b615b6b566b6f5babc37a6d3e9743217b550955a53555545bfcddf9e665755f585d5ecdc54c5ad8ae868a6e427a95c585194a7be143baab2a3ef24d9c2499ee726c73d8ae27992a3989225d98e2a99b6a378aaa4498a24a9553cff57dbb1733f6ed3b37dd46a8ae4df28fbbbdaf2cf7256444595a47a92a829aa656aa2e82e4b12dd2d6ab6a8ca53763cf939f6509d4ea74bcf78069250a85858f71ecd8ff6f4f38d8ee6cead2d3d2551855ad36ef62e7a94abe8e979d9f6d0edd6b66bf9f7edca1d84c71ceca1c31ccfaa44542bac9f655bf47f32ddbd87ee1dec5315d67164fd2ecff49365df23bb7be83e5baa14d632ddde54f9e6e736c5d2c7ff2c9b7b86fbf30b8fb1bdae3a7e0fbfe5a702b5a2ad9992a6df2a67c754ece827cef9687a3255d56fa2b0f2ce76be59b69323674595eca13bf5b4a2ac2fd3d63cb73755efd97ec2ea5bf26fd6b7229ab2ede84913d65165d3adb2267a768f969dedb492dd24cff3243bef2df9d994d3caaa68f9772f479635b9d845201c93e9744038f6aa2a85aa8495255b143dd1adb65bdcdbf74debd959cffba992be6cd3df514debc8fa3efaf37bb41cc9d1936b6664a655fd25cab2e7f84d74a3dbec98d65114c7f68be3ee7d64c56fbe09505d5a4d6fee4db2dbab5d3dd54ec2ba4f91fd5ff4e8eea96a8eded25a9ea369b223b97fe9dbf68fb0fa163d53d3644bb29fe74ffb9ad9964e07e43f54fc425569e5e7ee623b8aff9fe9e7bda5b4b268ff27599a7c1cdb54654558cf9144d5d477d2ef1265cddd43b73a6915c9ce6ed17bee55b6fd240f611579d94db58f67dbc7d11cc71eba7b7de67197773ebedb2495a4b51dd5967ff4ff74eca1bb728e6179dc54915691f3b4f4a72992bef772f49056f4b71df568ca963f35477f3e5abd9a8ae97992fc77b5b3afcb7fbb72dba072b4aaa5587ad21c7dba559697a7badb62a81aad247a8eaa69a29c65bd49aabc87eef32c800a847514d5cffb99724fb63e2dbd4e37770efc75a1fa606577398ee647fff85b53344dd383d524bb289ebe8b7eb39c2453d364b47272444db3edbc3c51b2a3a7699a1dac6caaeed66c5b2e8a6c3fd16e9ae6a2f56ccbb13cbf17cd7e8e1cdde21b2a45ebc97bf9599445db9235b937cd720e1507ab39aa62cadbdefa123dff1ecb32d19aaa2d599a2aaa8ea6a9f28f9665896835bb789e24bacfb68fe9c972f3d05aa22c2b8e6a3ab6699992bf353758bdf8c7932c53defff7a32a9aa6a17514d94e92ac888aa82f517397855695b77c14ff589afd8b26df654968f55d55cdde72d42cfdf855558375e4a479fa8f9aa7df3dfdbf2c335851d69ba2789ee9e6a77a6e5e9618acbd6c7949a266dac791b7a9690e5ad54d96a928a6fbffcf498ef6d0dd9761b8d747312a41e22f7f698eadfa5b53645bf4822da2e8998e7b9ba54896a838a6a427476ea62cfa4d3e9a68dff18e7cb9ebff6fef19767381a6112a0bd6b41d45d6ec65e79e3cd9f3075a49cfd15d9e2acbcfcfd95304c6ada9bbf40c5505ebefdb3ccd6eaa66daaae56906d1e94c71ef360a68155bb54c517554b917d5162dbdefbb38743af96443f56755bfea51d314d5d22cdb3445fbc0b8359d2e6ed6e9c4bddb71f3289aa1f2b3fedd6e96444dd32ccbb41cff8643d567e57e1cd3af923d4dcb722cd914022a0a5692ed22da8a22fac5b1ff72e4b39aa53f45f68b7def6d9ee9ebf6769f892b8b6ba8f6acfda76d5a7a76a79bb39dede214aa09d6933d473e72f4ff132dc98df6d04d8a0750e9595b3445cb51f5e466c57e8aedb983a46354795672b325ca5b96454532dda9a8d3a56745766e547856b64dd5321dbfda5b11f5adf7706ffe3d5c60586a874a82b5f56dda92ead8459feefd4560dc5a581eaa3beb49a666da9227eabf1fc5967b33b8b5b6c59ac12df33c2bb2f35476d6cf5552355bf344d93225c9141e3be950d559c9b67fb11ddbf2ec69a97e332e9edfb5a48392dbdc657a8e2839a6aaa8a6272fd1ed5b1655fdc8921f7d61974ca73bb7dbdebb7d6e37f202aa39abca3dcad19645d3f26f74a768695b62dad6d896f8630bcd6c6bd5e9743a15f5017fee49d500959c9555fd3f457393a7bacdadfe386b6996e968b263cb929be42a9cf5e42389aa9e731645c72f8a3d74cfad1b7b3382b57fdf493eb62adac914e5bc87ee1f1cfbafdb5a33dbdae58e80eacd5a6e3625c9ee4bb3f466f9bf9b758f6a8b9e2a8b8e9bb3238a42b0eeb3fbdfc5d4b7bb8b632aaa56a8daac248992e9e6e617fb886ef23fb09a7c3c4bb145cdaf96ea59f683631cfd03d5743a719fbb7f37520e9503eb1f496ea6243aaafb34c9330d4da78b61a06ac6331009159bb5f46c4b727fb29fed7ea7ed9ab5a7db973c4d47d5a75d24bf9a755447d64c53f2643bab8e684fb38a7d3ccf5145cb334dcb9adca844ee6986438128c961140641c4503215490040013312003030241e8f074452c1a438673014000365a6708e4017c9439128876114c44010c420640001c00040083186ab5a0d05361e53843f9c10aee8bf507ca0caf01f49a5117dd4f12df92c5be91f602c8c582d998327c61fb9b79c3b9eb8fcc97426d0eace2b6c4b0c6e8f31d7bfbe6c3b080003f3c26eccf22662378b7d1dec57071fe1c879f4dc7bf38931bddc46ce2baa43acca2397114c47ffee4e950879eac623aa58349b6eb24c2951f99284bcef1a318b01a8ad417d2bb5f9347e16625e8c40452c1685321d83196456ee94180e58674d5aabd45bb7a2956867ad4c6dad2aabd7aca764c5d78a5899098f6eaccbcf7c3473a7edeb77d08e96c1729207632fc5c56bf2465e677d7a2bb8b0467eab7a62efba8ddc835e636b3e0d62d9a00c8d3a8c1ad488d922987cfa0943a96176158fa84f93cd27f8e671178a9ae3ff5036fb88df04460057985a851ed36cd34195982afc04f1306e51311e5a9542bd5661392b4787342bcf4a199da67daac0a8ac894c2d5a26d0df2d9572636bcaf0c6ac01de201595fba8160a1933795c2a10c518aa2d5ad2badad132aa4c6ec262548c8c64b0b302edddf77ced782babfa2e08d9888316e248e2ec9a6ee73ca2f53f2ea9c182a1e53e467a11fe209e851b8e97cd38c9608d91589ba16035f76e43675605c91468e2c29427fcf1700da2c32225d148ae96b19ebb747447ec2725309a2cad99da4e0c95b1a811b60cc555f189c135db6077ca6ba9a108821587af2838ea30065fc88c34b9896bfc4fc5b3c39483a8c26cbbfb461b967ec9010e494046d9e0b21151f578a992d05ddf026165182b8c0db0fdac9a5716f76505416b949e698532ee0fe56b57ba8d36151cd04a563d269a73eccde91d0831f86f2ac87b4b57d48518c2f021bd96ee814cbfc4ae8673525cf1d48302373d7aa79543a548c2b2de03b08f2d1bcc8b9a383659803612f590e09849972edebf867b1c143493246d615530fac1595de0a6d05b6fbb746dee7fced73e9c127102163a13ae9df3af9dabb32cb691b5a37773a68fe9a17b7001057d33752c7871d489d1d2c4477f9885c64cf78ee121b83c313d32b69115d8395c881bb42ff90952187e5a1095f95da0b2a11e2291d10ff84bf953604c3a303f6aa1e93300efe653ab3459372d9314c099139ae5b15d28a2b85767d3ab3cc47a92a502ce5e85156f4287ec1e3fe47fc339e31a06aa30b493016361c837b20ca5f3187637276cca8979941c713dd9c98a4e818a60184055c4df3bd4ab3c05abf8d2f5ee081c110d0395ab3c03793a97ac2f1d9bd344db48dda469cd7fb1eecb360e96585b42b658bd41f9fb2285a6c4bee2ebcfd915366d2bab6067802375715ced3abc9c8a1a1a54a4d714e07b063e3d3c0f6dd055820b434300b3a5c5f542cd937b91bb14a6894303f3e103fe51f34f2c9357173c84d471d0a78973aa183783a303a2a639249bd21099f70934d4ff7bdddf310825ab86c362971f32cc39644657888c142376c213dd0c88e3de4b6988ca4627062bb7b3a85a055435e59c011da66c8ce9e96826dffc7c2cdfc7864eadfb33e60de0f613b461415bf25653744642f59de1e747fd35cdbcf4b7ad040d2c9d6ccc2f5ef9a66098c5fc13b9ee04d812d8a7be58c72f9019431635e4f407683f2bf46c31ea5d22f172400055cf28b24543ce7dd14faec29a00fb4973497d5a2568a5de69490e8ef7fbe58b9378d44c29a85ecfefaf9234b4fb87f834ddf60a8b24dccad038e773b6f1231e1ac519d7146312d3112548ca7407d88c2326584c263a30876ccb444b031ee54ee721c346f2463dc217aa5c85fa92ea1bc76a4c97fbc110adc7f10db6be04003595e90e8d363c223fb47cfae3cf972a57171fb1c3a717d0802a1b5a99635e3aa48ad3566ae62c1b8e7de00362ff7d9cc3497ae5d8a7bde383fb6b7894c02ae8c878fbbbaa302b92a540512b165d4a1eb84aced63a9998293aee77ae7ac0b581901085351d9ce4e22d7d76bca8ed6990aadad1e1452da0852a246ee4ac2092d2f009203d07685b457e0f902419f84f03c17702d1d206751844e60e16f328002f04f0c5f1df2140430b19e7f1513ac3ad9be2268e84437c1b01d8df5b361bc5d59b7511e2eed772454728e59cc10169f02d882383e5593fbe6b30770c648a4244adc064761b6f4b88bdccb6ebd2996151191d74b2f30a0419643c3f00390113031698a67f8fe1186266ce87d1ca2980b8ad02cd82514b72af2356db35ab853ce916e0aacd254b954565ab2f97bff4707f4add083466b16b7aa92d070cdd5a95a3da058a86371d9cd945248010aa00dbc5a353b960c669aaa21081b153e0cd40570dd1fbd410c7fe3a0b33a4a01d91cba86cd100fa9273a51302f5bce5b602a78a234bc879c019a32338cc1dd1d7a683a110a4b8f1d80f877b7a5120124b5c1bcb6839314785700479c56043d3e9c427c2f638b9e4b5ddb2fe11c738575ec255fd0e79893062df3bdf3441ed339aae44d2fcabf561fdc647e83f7edcb8526d420f24419f2d66daa6aa01884f6affd91b4fdd83b02c5035dd95095a9b82f4075191b86e267de4f0461b88d2a37be567c53903b7e362928d90b1d6d8f02eb06619e4308f693dea4285dc98174e9004b234b24faf89cb408f0cc53ae55a2294c700c1b1bc63220bf7a6d3a99ccc0b370bcd742616d52e5a82786fb11741303e919c1a8d48f088020f9b836a873537c3969655076a36f2886dd1cd624b34c4a5a9ac2a8ab291f1d2d6bdc665d2890d382ded57eeaff726b3c658bc18662f1bac9a537f6313d19befc86f22909c9ff73958ae6989636167a17b94f8cb38fe4d782264ec5429f46304f5e41bcb41121a79ca165d6182a79e2471d97af22591f7e0222a4e1d6d0ee2ee59eca2c41f264e8e62f99578c1efc46c7bf1d470af457eaefaeda2931f9a9ea80db199f8508c0e75c31c92f41fb54c8514271175639b200336cb14650e0329161b86157654ea18429c7f8d289871eaf89fa18c0f70363675c4fca4dcf0e0694df42d09c1a637444104520f842584a3e975d09b1003463595ce3c4139fac1744d445ba6286934d890201488b3a77f7ea26315ae7bb907d460ba3a73b7f28d27ad56e1be974340dd4d7133372a78f544f72a1cf6b2078c3695c87c55b9ee0739d4e8be851d8c454359599157dce1fcf71aedad158365356c9d1507c5ddcf7fd768dc5a3558ec864ab0e2aeb87df0628dae5bb807cbc1507756d88a4b9eff5aa3fb168660ac1bea66c55171580fafaf2eb1ff7b10d94648b2e54aa1a6dce9540fd4b0cc86f32890968352204bcf7ec9340302d0f3332c7a540b6c3b40d2baa1a32c32fc4998a4aa5bb5b61261045bd4b6d458a3d192e019f1415527eedb876d09943708a7d4500dcf0655167f2d89d5a860cecd74012378caf1295f4108fc8ee90c5e238400a8ec041eca78ad587d677c85a9a78b624703fdb38de6accc1a5f2280f29168d841ca79bc4c1e6bfc8429eb4f4e34b08a71fcfda1071c83857952c8e108623c997e2c1466d3d37866166cae002aa6e6a4820b75d5b58ddb90467824f0e3186ad63966dc7b8ab77bf42807a7347068eea1c9a5a000174bc8f6e70fd370ad7f2b138d46f36449d19568783ca308f89305e21105e9f8225164c20422a89b0f05e14407a112fff8aa1cb00667f9f668614a91002288ffa6470a3def110216be3968655f7bd59bd2e62ced49b657f56c9964f2a6931e53c4445e228352229766ac9b2d48318c43404551651844eff83143b7890a8c63c53e2c29f3d82c7407619c1817d80140539e4396136734983429a1b2af7c89d2eae70a6aca260dd26790ba4030f6f38382c3361c58063ea0c050e393c8997587d4903d00919822b96400b4197e72639c8415ec18c3efc5d8c546e8ce5bda5bddc7d2897cc1791376c1eff23ed8ceeb01194844b70ba76e8c1238a96575f7136191c14ef4263b2c46a0793db2dee93527b7f1823d44d187510a5cb3d368acbf51231e265efc753788b8ad77893768ab5e751312a423f1e752922704a27f0777c13d4298d31d632cb933a399007fcfbc6569cf2c79eb640c8d9f9448fb5a6de382d58949727b20bc5e14acc71816f49063762ba12e6631f1d37f00351f0910db6e71d5233fe33670b52276c0d2cb56828017b7b0eba072db75177a138c6605afa378845280f46bd2ad28527ba9a481a9461a1c0440a7a1049c3202a8e84ffa0b5e5018d3537a10d3a3ad2db2050b25f91322ce5a5d712c6275c13aed7ea0780b62fd2e1ebfba9cad0a314c409deab2723d3185ed256720ad4cc7b3d6b9b251750061151362b310bcd56b0fa9894b9871f0ce6bb21b5abc8fa037dca83c1ad1deec20068d394557caa8b904fee8124c0448bb63aebdb2e34b001e61458c8227e501902989024d83dab078b2bfb2e88f9c78086f14f4b4ab33a8d6e9261906c9ed811eaaa41988fe94b51373313a06376e93a8abfa1ee4365bf6110611c68db867c7ceb90f30ce5341d2f92f9e81c6e5350cd45505119d230234f4077373b73fa0d99c12c03208b036b4676c66ea5f07195fb7bb2b02f684e088b404c0961b7ccb00ed3b9675cc13e9c24a57028d36fe576e668f72e2a59f12afabd8cbbe9e3c4b2d6ba7fcb4f1433f3dd2bf6f29fa7a693cda540dc2baafdf759c6c0c6ab7806991fa4f476efc6e7299d48432c5201cd6244090767f6c01a456eaf5d17d3c2a93b27b632e6bbce55419cbb8023e72ea9cdd2a7ac75b4eff0ee5c33c66031a3f20fee7c55571cc95c05b397b35028e091969998c9bbb3e33c570ef66039b13f2b8d0c77cffbf4e0ae8ad2532d29d95cff21b2be801d75af7523a1eb8f4410bf5f540e826f5c97969082f25b04d3131441b2ff0d280b06b8999ea3bee40943df725b4b2fa7ab326cbb6c467d2625bc0bacbd80c306c5a5f13843ea2c616aa45070de0a5c22d064b15f5cf1efaa46f6a4669d0a0a74314411f81085e2b43da132b423419fa2ee860f40aeac9e30295522df9a6c61ab8d25d58b4a63934e765a4bf974302d2b7387234f6c327af37603a336b980e7a6a87adfcaf7b71726f0c49e94394d21d220a2d77919437a1ec845a0e407f421b40e93b9832a84d6d4f0dfcac4db1bc2b320efb8635a3246212efe67dc9a12a62d9bef0f77a53bbbfb5ffbdb981f8cd295460ac84b89cfe1f321417700aa64e40518bd9a5a98f55b5ac83be17d2d676c89b84034262dfab2e8ed6626265acc5101ba6ede9d779744ea4f8b61f5ec9173b9000a702c029ef3738b8bf44a6262d47fb42a46a67e44c215ef044e80f1fb0f3e942dbb49a03ff64d2f74dfcfd5619c7070c8ba90d327482b394820b0e6f03e46a62b344f122254420809febb64adb6006a9d6247c98e3d096b7e3f0a19b79d1b200e4bd80f8b2995f22136999325b6469243244615bfc39cd18790137227c31d90e8fc06043a6a3ae4dbb9201407d5262ec9c864a540facf57cbc480daf17abbf0cf04fb2ab2d8e6fd31137d7eb223f5adc6a0b7d84cacaf2e0bc64e5e8019436f8f78c42a56d30d68f12bbd45162c5f90e48cded5c3d33c73003980621c9ae1326af022c7a6e8332d2cd93211a76978f5724d6cc5e573e87b61c19389b192409998ade5c11ab24e58a0d9bd061f33873717d68f91e5ac4a192bd760e3de4a8ebba59a26e51ff83eca43f6a72f4b8e6801239571030087c434ec917484c9eeae843359da8a033d5f4aa8aeed5e86725fdb3454fd7065230de65480dcbeb42f7014350561446e8f606189e3e1922913defed7fa4fde50a29b0c610a699ed3105f1970e5f092f0721cc638f5e6e2af75dc41c2dd501510178f8ee1a90e7b7e9f8e9bd35b17dd48be7c5eb162c24ea191223c5d0e697665f589c9ffbfde7fed1eefccc844c7be6d268e03e6497918d1cdd6cce1b2b113778e274088f5be5fa2f290fc8f5c3180833ca8aba364b8362289af1daf5176799123989517c513c6cf0891d8a111a0c52b86401210ef8b81b9337de1ae7ea87914c16c13c40bce9e17836beb21f8d74bce2e18acd0cc8abb9e91c9c214e57cb64a8b354b82244365c3428da9584823edfab43f5cf1f53d2530804bd2a2800d841a086aeb5682f2f1a2ce505fb0f12de66ad724b6f1fc7b4f28829716a1f22881ed6a44d4e829218128053f163acfda1d8fb6d3e1a6d1906bac4ba96ae709e937d17929dee24c3a03e94cb42716a0451b77d7ff1111ca50d711f639cd1148545e988cd67a595e0d304e5a8a29d502e763dcd1ac4d546e7eafbe927bcde2dfa6cf2bbdbd1db927561a9ac2ecaa628259c596c1465c0c3c3e4031fdf00108698eceff5ebacf6056a1425573438c03ef6dad24518a342b1cd8edd45c386c267922be47ca06877efc17fdc11790aa51ca5b34404fc608f52ce6f377f98c0764babd9f2582ea7b18c84352d1226f33c15877685a0f21bed1f60dc733817855e6dbc49b4a196048d4c73b9a4c295d7a62706e8d243df46448b045ba0e32709b83e5999420e97a6be52be47a819c52a972d057e6e00c2b8d1c9fb8fb32f590dde697b1cd44ca334e2f6341606ae9803a945bd02cde6c147c3ada9ad0da72517e7b664303931c009b6342e47e5cfb4d9134e3e9e15f96ea5d053826943c55b008aa20abe8d4660280c2ce66bc0a3548f0f1df65aa5f37e1de79f2817664ddc08f650916887a2e0bda033e2ce6b804e0dc993c7bd2138122a52aa284bb950420a6843e04c01f927308c896c7d44869597c44d590db9e5004e1f435f26567c53d26f44673e9497ca285d460f270d745cd7a5f948ccd35c17544539c127d52c742c700c4c486222cbf966729c3c20b4c4324a723964e304c549b42987e07b5f360555d8b220ec749bf2297912d737fdb5c405d34eace74e27588acc6958acd737188f7e0688da3dd0e0ac4e83c24f9cee5f5e67d06245d5d06d6581bb6cda985496c19e5a58c90a5779d23d5cfb15b83e4dd3574e0c85416063f40ff2e24ed3e3cedca316aed1a2aaf00564d0e6dd7a4fb92bc88cb2ca10fd08ee15c75bd56b1e1c2bdcdbe7142a083fa101f67e34f8746d04f55f6302324b2bf2fbfd4c412e503db925d431c8153fc8f866003230ee41337e0b1a0d8c4aeb46070ee699fff7642beb02663685946ba8d7733c81c48650e1b82bf36845d0cb449be3eb4ac28c7e2cce811d29ce3e4f7f85914684f8aeff8e3619fd0092e4c7678487632c1d7293a3819ba284c850f5567229ac24213b93844b0a1c10306d16de1238a04be1fae5dd4f11ba60a6e7c6f483b988651d83eaaae716425f1f0008d48421dbd5417c886b30e513baccbfe186f533a2aa0e6f4e95c6908976590606ec46a6795d0d2d95ef135389a850b52138cb98136f672471d332ea07d88cb402cb6363c88af4c8c56d1d4910a379f692acba651e0f7173c6509327de9afe1a301b910156a2f02640dbe4ad206e8788e28cc2a6394b8227933e0a7e779fbd252be41861b79d56d5b5ac203a6e4c75fe13680b390c1ad2d1ae099a74b4bb6fa5a35d695046603ada0d0d1d6d9dd4ab819a8e76773a1ded5483d2ef4f479b52f01f4ad4d146278c72951a1d144f813ff6a1ed0d863fa73adafd0daba31dc9504abb3ada5de64a59aca3e51d32c6c40094e419efc262eba8eefac57554b79f761dd509a81ad51caeb5ea375e2e1960479b43a14f1476b4e5a38a295608838f1ded1acfd1d1ae0a293bdaedcbec68b700e475b46db97b087f14b4f9d3febff082f178835dd9eb808eef49a87480e7dd0cd33effd0ce3fe1eb7f2f165602529fada025bf1716fd970ec773c82f9cf20d80b57b5e79d7c3de1be5dcc25f5f93c157a0977d80072d72e72caa43636f64f0710012856b25a7360ff47f23d4566e98614a75bbe15257547690860363675e6103a5b92b903f3e8041e4723addaae29e59990c3f3dc362233f9536b53101ff980ea088b8fa09548f11660d5fa17cfbd8177942fe61c9bde60fccff35b28a3720ff0f18755fc770f5a1d3ef02ec951d8c6b3981f535c33c5b3d8ff8608fb0f825f0eb0b523d4f907dac72a9fb83ff6745bec215fc9f1386eed7b02e7f68f65f0075bd27d1eb01c9b705d79a7f70ff57c8aa5f80fe3f61d2711ddbed934ecf6b109776f23d1e217c59e3a8fd03ef6f9d4cd53d88df739a5d6d9710b44484db47db2c6aa321db08d8e2bc96de1a3cd5d0a1f6eb8407d33aefc05d1f216a7f02e4f11f36659ee3fbbb24a7e13f8e57f3cc7bfc4239e78ca7ef1bf09efef9b49d8fe0f79f4d390ff8ffddc86cf213ffc52ce6fe3f509d39e1d7f3169c673ffe6dce21fff1655fce139cffd34dc3491246f140f67caa3bc3682462e22ff63fb411abf800d8d74b514719f3d6b333ad47e77b5c3f0d4a511afc1cab756906027f46d797773867c841dec5172b980fc0293f78a8d0fa95c13aae2d11845ef6a1f9dd041769b939a3958911946341edbf1be638c6030a880e668b41674da9f441b67b8acd5958c267351de03da716446547c36210fb24ab1c2fb29378ee377be43ed86efa1cdc21d88bb6d32e8f9785d3095bf8eaae9a2f42febb1302bcdb2e005ff26fccfb2391b7bd07ca9e51b94e80e7016d1174d0c40ebb1aaa37f1a1d92a5fcd3d0babb0c9667dbbc9d28b300e27fc9f5010cd0787bd6506d834a84275a2f7e85f2c0f077b1f77e8f70ca48a007a84499a78f75d65c0ee8c5de8234bfbad7ee6df6ab9300d210e3f5540ee841f2362a8dedc24740b6cde18e84ed5f95d78edbaf54f3be571e23f97a9d4227838d5cfe0eacbfd938337921b1d851791f582f9c303135064926ab7115ccd2670117aafb92657dab0a42e267d5ead7f7dcf2c86a4e4643357b804d4306d0a82d390f3aa7164c7c8cb03ceb0c176098abc2bce4201b1d3523b3d4dadb270dd1e4da1a64081ef94d08d824f64a5b4a13715ec2059d6d4f15132229cbbd6fe4ece02413c093d2ae9b6c708f25c3544b48193830793fdfc8eb33b2d1f7d07d03cd91acbd253c1b571491251d4dc23b02359910747c3ac42bac2872c88db81faa3ce5132b20dafab73488a4a55c41c1c0bd9583c498e013c5483d6c89b2e6894500f6b28002244f889f6b5328dacdddaf90116c79c7e2b28e6b9ae55b08e7790e61e6e4c92d28eb720f21c79979f8c811fb97c771114dceabd23dbbe7fe07d0a0a4f985b70d9f706bb379223afbd61bbefd77f6b45ca65dd6d4c95bb26b30a077cf535e6b73ef3ec471fddc3032cb93bd2a1e01679047a73c4ab9e39a8f1d975e9437cb6f140dbe31d2d3a914ff4b319fd820c38537d2ca01d3f6fc0450684d283823c2bb6883297911d9d50ce3856b3695b0db2744755b2837a231c670b2a3ebad94315cba46aee89e6723e81d370026e45e2266c2bd5823ed3b8ec602ee62bdf3a0e29cfd948b6c339a4cf381033189ac5dc123c003936fa40f49052e370a15f1310adffc484d56b8ac2fbb2a2515459747a82ba68fd353263f017e971780a04045584d02d44da03a9455a7f3549be215d327a4d9c9cde0a050c95c20a99525ca91407a912122b4ba895cf72058f5e812d58da2896ae92a557b34ca06839285bb0d02d30854bb972e9a4d22547bcec54bd1c600183471a86349431cd49997ed0325d6a664439f3ad67101134d08aa64cd21459d344889a7150354f750d5661c34ad9b49136a5b44db5b81958ddbc903780fa0698c06953381d9638891a679ec8f9ac72d09439589d5350e8b4c10f171fc48e6ab5c3267740d43b6482a730c55348f284d23c2b44cf47d58365b20781f02941f974903eddda6747fc1caa7ed0963f34f44f0301544f01b592400b34d0b10802860a82c8a0be3aa85d2114a0846648a1335a081e31045743d5caa132d1432514d18624baac89a089222e55d45216f5d6456dc268a7323a2a8d506a23007154591d552a8ffaf5d1d08d866320632816058f22d07daacabc0d55f3413f1473fddc96988b3c2789ce673ffa1d36216698b0d5f4fbb5157a1d061d9bcee327393314f73ec827e710c80cfe1966d8d381dae5b308c8d5a3a6ebe98319d1f057e1c1f88212073e7691b31b00af0a5031785c90dd81d56faf3346dc822871c7cb8a8f4a83eccdb5996f41dea84624ee6ae3787dff63bcda18e610b9b603c3597dfb1fabd9f2b384f20e9a7ad7d5fff834af7bfc8af4ea6d41777ad7f4d2cef4ab759e870a1497534d182a7123bf8e3a36e0bf3bc8fcc9dd43eff34214619e7aa558219903bc96d52d5729d24dc21dc6a066053dc768fa67fa467fa33afc16c142e95b07ccffa590cd84498a98e128974e9b4339e1c8ad0815f3f6d2282be9646f123863190ebb68303cd251c8a718bdf0dd6ac19587b93d6560de13d4c9e60586f03afe182dbe2d00965d9ae8e9e5b5758aabb22afa83ef386e2963ff13410c7b9225740cc5705c9f878c231ad374d7724e71c7fa34cff01dc2bcd19114549101dcdd8c7cfe8cf5638c19e62525b197f12762b702788cbc2e815905b2688080178449153bbc536b1a807a60d86150d4ba0461a7f337abde5203303a78381866186e3ccec8a0a3a323386c080361b0fdfd1fd24216fcfd17fca4a7871f01d0b615c65f3d0b2dc751d630f9cc7edcd3578ef3f117c11c1ed94a4846256dbd430582b3c587ec70b071a16c8e9aaf4464a490f0ed4cfe3170342357bf10c0e83f4ba94222453f2e91e4cafc04c5ba486840d3c0370629380f85a37cd35bd414c2b5d9e1662869ae7bd752d818d4e0790757e03a6758ab4d8f6d8f08381b389bea035925ffac7909b49606c8cc8e3734fddcd8ce8efa30c3c33b36e2583e5f1bd0b09161d8b4b624aa0743a2b92582886ba80366430394460f6718ea1a16c36bec8702e21f324f6f12985b890b05c57d81bbf33721bf19dc0f40c6509d15812db5f9f00443efc470259b1b4e07c81caa6b7560c1e690be949276d27b800cfee1bba8b544bd349afb713fa7f925c14f62a477b0797485a3b15d05608aae24b069d6d3cef44409a8022c4de630697744cf0f2d6251ea10d71b5c4386708b81b28c167615c5a04e989e06aa76e30d2bee6070e7bceffa2469e8673b1579b64ebf26700620b8fa199c2e4bb8c710481e64d8af019422aa6fd632922a44764c34b4bea9262e106b747e5864d06f97802e26176aa232fa11861820201e80b7145e249d20e4647ffc5772f9571a6716186aeb6af175e576d3eec68ba0532dddea9d01d5e4a6e0df624efa1e2c550de4c9d117e5885a569c985d74e47a9c47bf49985a2e504ec408e6437e73f1c379653d6124251ad8c675ea4a2a0122b292c89f101fe0428665fb2d644985b65d0d9da5710bdb17b9636b192455c5f31766d73f2b660c2558727e0e8ac4df160d437709b4ebffe670ff9154651cdbf8f027fa916f720b15ee70af4f048258b3d0000744404ac919def86da3e9ff10615c10383b960037488fdec3d31c70b33cd57a174ae3058fdb591ddd3b77d77d31a1a317149a3a532e988835d1c82feaaebb70135bc534c6a4cc98d029cf65ded5a416f1fc54d06328615204df8702c725534927cc45f97904e187adab9d191a22d5f9082a7df99d77bff435fcb3741b4c11837b1139a78a4884797f171c6def3caa6ce0bde13c76f3c3fcbbab869383565a3a8e5cac8071bd769561c812109dbf301d2c49c3dad06dfd575c7485d82f04f4ed675df049dd43d7b7c40b6851989e96b89ab1bbf193cbe536dd490b275933b0c1df0d3894096ab0dcab671976899103f3c10430b0312f7b12201a71b95dd113fde0d9b42ff93ac88a9f3fe1e468963ac7b57106902c0cdfb6865180eab430896c04c23396bd0dd7568342cdf1c075d232cf6f8a3b2d272815dea999e458064fd5bcdfdeb431cb733871f6950fb94ee8927def3471789c8599138f52141d314cc5011f637eed76fc36958d64094bbce42d63246f2d1b198082536d9f4870e008666167b6859692f24d2333ca61ccf8b9b5ec6bba54308b9362d673b6983621ae6fb0b6e36f9508eb8800dbaca93caf4ec044ce6b05bbfb980f1245e3df66d979925f1510acad238bd64cae88c401087894075a0f9ad541fb8e3a9974769f785120ce681d40957d03d37d7e1ffa52ba4514915f3c7801496803da7dea268463dafd5af986b9b63046aaaa21d0e8f28686ccab2c3094fab813b24d944562a38fba49722cd1748eea5212038e1405d9b4fa3f7af059079b6712f10573b4773012db5feb34c55924d9fe4504105ae0b40a039e260fe7014734058fb10e8af8e36b01e916fce9687ea93b39bd7738d02e0965f36f59e488fac31ac2e70cf5a71be9c471374adf830dd69edfe805444305bc12751aabc66bb5a63bdfc54ec81742c1ab5193e1890467c3793eaccb98694bd68500e2cd3a0958d6df54135d18f967b4451c3c976bfabecca57ac2c7a6413a387913777960c455ffe0874fdaf15681f13cc3f712a1118685a32dcee5bfc8ee63a04147d02df80b58739b57bce7afab4fa7ce5079e3266c8a960826785d3c4495a05c7c8d26193c85648a458ca0b4a6c8228f4e16b90697ad475524f5fe3a89be3f56bc5c33ee9fa5eaace52843173e81ac5de625a4ef331fe6b36952266a172505c67de697e9ae734b5f4cdca15e42b8db23a586d12c51b544419d3587a59327631e32ad491c327863090e7776d2e7cc2c5fbbdb3821452af833292739e14d642311b27e285772e99e78f2686382fcf20426b855bb8214035d0a187d11ef83f10728540e54999b9b735d01e3eae0a4b8b2ea7808ee05d8e16e8b76d61c19eeb41a9e67a64df508fc33e24fc6b0de9fc419d116b9fd41925ea5be15aaad30001cce701d78abff05906b81ae4757704ea5ae12ef325b0390573b62f0910755be074c2f438e1fb1176d32b5833c96f25c0372ef27ab28ee410d022b423d04e946c4270feff850e4d9c711b44e7e7f46a49dc2bb1fdd655aa56ca0ed56189352ac2901879e4ec7b384343714d43b6519e2f8e776c1ee4746fda8ffa3216b2cb9f5de70246f45c3aee32f6ebfd0dea473d88bb4c1d70ff52e5628bf76d1e0d9aeadeda462dfd00d96a4f1e8cefe701361ff908189cde14cdef7da7a9ec798a25d5c8d07fb366e71fdda7334eb7dfe8edddc1bd95309c41694dc7d6517aff3b7dd41ecb5fcbe0ebfcd9153142f0621c8dc4feef8386588dcb5e7470d81771d8e6a03c8a4006017692b5e3c5b06650325c933aab45b0836825d61abe89010e64b843e223796df599f5ecf50c2abb5f566cce081ec22b38b41b0dfcaa758b818be45d69d47ddb5e23a05f0989f4c23a020fe6b49d56c81dce13462e74e52d211dc23b9b5f64bcc1d4c5f743b581e1fb03fe0cb1907cb4dfa3f390d92e0b3453011c53d70a61bc989a73d4cab38dfdc612f3343dc04ce44fe811cf28787db2eecf5d19cbfb62853b9f0201d1dd07f32c9af0aeca7faa916b3001c05e5af2dee5f14f3167f4a0132ebe233d18f111334aef0faa5ac755b36a81ff9108aef84da05c27da2b91169d89187e171948817c309f170ff38e3ed8f59d76491217a3126e4408243574cebb113068907bfb0c84155f911965bb2978a0162f9941a0fbe7c067ce0ff5e896285d3f827d026267388762616777865df1eee0ca4401befc2a47a07e8b387a2db68c2ce987ea3443c8b39cd4e3b46308b67e9e8d927947009ab692380d5defc0b4863646bc4f234f2b96add45d0663bb3b09099be9d18f4916b783e9007e83b883ca3b43f00af2f4107fcc1e97b9bbeb196c22b73b15bd6e4ce7d11983eddd9424b3a928083c0a125e2dc9001f49b30a1c50c0ac6f92b5bb18a9bff39a49c63b7a52380545a5db1ea2079c28151282a4f8cac27b76ee2fbee16b8a9ea1939668743c466504219e839817e4edaaea4938fde9494388dac996111f17e0f56b47a28c98cfa861423d27597c488fcfc3608eb27f9dbba7e9705f0093ea0c96a28085dee742cab2841006554fda115591fb3dd7751f55f4aff4f09d6d14b153f7731926e59ca451a691cafb253b3745b598ee8ba2386cf5cac35a4aa0a3102d60e9c503c48141562f0cfafce9afbf8c605c67b45c8646f1cea985b47e28a4103e2cb40ae7e5316e4035662cfd049e4db8aee076d045bc3cbaee863d37f8095a0c8faa436e3eaa8b635e96fd26f5ab01eb3b96c2093ad2112c3ce97b6fa19a5a8fa375d16b0661440c42aed5df3dc927450ec6ebd4bd5fe8ea0bada96646cb964ddf0a38815b789942325bf07416a64dc56bc88877d0791dc9cde02076194fa2f59cf1057899a4aa26feefa5b90dd47362e8c303529546866ff8253f280d903eea37a460f4af954ca36ebc1feb592e8fbed1915a39fe70123fa44709eaf9e497c97525173718a0a95528c2f775c5a12c6c76e01a157f6b8b327919d0546b20b649b53340e07ee126fcc12466b82ae0d0c0c65535929d14dfd5ff8ccc2b2aea012d58fbf50a6b9d96817025c24db0246d4e303add08236b1a5b4ed21fb9f6d59fc6819c62ddb87f656f991b09a964d583612a9083d79afb50a1eba84dde5e9451cb91d3937eae71b07138a920666348375faec46a4bb038c08c481b2c01ae6bcdbf6b01120ba6edff876b9db471b79549a8376d2f1424fb0b86df8ca7bf80798b95149d9b353c4d11d99a5005469fc4dcefa4e0f27c95616f4af59ec29ca12859da1d6aa4e24f27f9a207e9966a5c5a9c60a0f87d184c8c433414562472256dfa8867d19b2778cad20d4452274961493bdcd32a8ca6682dd027254fc84bdb259402910e46e1eaccbc537d62424a20691804d0c4a7d892c7f5501045ac82772291a0e43875c7a9a07dffc0a2737476733b7d2ed946d4f69372dccb208de506e198a8684742cee0084cde1b8c04c0ebb3016ded9210d5e600d27286d45a08e04167ba822266c2f2146add01d86896015b736e9b38e77cf37f02ebcbeb9187980b953d6101cc37b8a3066ab117b0891d93b9738d972ed120901fce6b87fa7a5ea221f96fd7209c8958ea42c7316842467500382df7d663f9cae2bfce4f48d728f909f6b3a75a2313d0631abfd0c5ad36cabe6bf2646bbc8626046c4cb66bafbb2247207fc2e39e9903c35394120fae136f9c2f1e7306f49d4f7300559c4f7b3d50a172209a13231a78b2c54ac09f30188dffe7120832542bd8afff6f9a028e6f18927125b201dd144e7e70fac6953d0b32aa57d7623d5b315b35ade567f6d0e86dfc366d0a98319581c364f6d37a28d04d77ff39b700c92de32e87e8a014986f66ebf36e112b4a552a26be400ef0bc4942b9e9b629753fb8a9e45d1f11e6902a2bb103ae63762446c3caa02add8f3197d40b7867516863e122fe83ac22c9cd4a456335341e10ca141d09fbae085a6b3a74aeae6a1d22d159e38b48370d0299503d7805f666138078945bb20493a1beb80b778a3cf529d66aca5d98e48b34cda3a6541499aa42b737c4340dc46784cda2b01baece72485c260bf6e1b2dc1590912daeccb1a9e8cd35b738dfc79606bea1d4cfcb9f2380a9ade4c954ec307eee91b9492d68dfeaac45a38b59c8e620ab7a88c620181f534b14711bb96734ba88f89f8eb775fc0ae4d0e8615e7efd076e86b8362a981bbb9b870cced818c5d6835bcbcd274d8cdaa70c9631efdc98c4a9eadbe995b09359dae68645ab6b37ff9311ce49cfbd0c756981e8538375c66e3b3c1b388722ae4ad83f97faef4fc52fd27fdcb219be875667a244635f9ea8ea9e2070c21accd558b2a0b00e1fe2e98c1416a612af5500357536f4124aed864131b56e16d7ccd384d26f5485091bffb693aaf78eacfe9cac7411b6eec64a4c0ff18d21102719709047f5c41037c7fccb7dbd528029f832a0d5bf5cdb6135277e21c1ef225f6d484e102b2608faec7af94ada74ab7bd3e3011c9b18cfdaed2ce55ff9c4db5d224a54f6e7079e78b077d8d9b07d9e5c095a9d347ad795fb667a37200f8d8d2a0fc2263cb97019d6fc19d6152fa4b68f3b3a645080ad7bdd9970f75c47571617e8c6d19a9d1383dc0880a663e2a8ddcc73c9d9da1440df3eaad34ca9b099ba180ecd6b6ef59080ad072e1a97cad3804c11eeca2868b2602612651fed1ac81752363ec2e0098732efce178110159124ff885aba694d81ec688124bdb71d3facc4b6997190c75e9c7bf09e3cf2cd576f3042ab0f60b3f2bdf18a2b4fb336fd0aabb3eeb8c8a88ae6ca1de8ed7bbfb5748f95ff64959b073faf27e51d737ad88bdb1b1f47fb1eb4d9ef1028c65bb07d4f6782115a1fd23a93d9eb4993cf4ddd955fb6a7378cc95081a6d9eea503ca48b022a23a5ccd5bee245ba031835376ed1dd5f54bb46828c18a50e15138240d5018f51e3e72b950bf614451a03f10eb5e95f06214db2541bcd3f0fe8695d6bca618fe7cf90e076395f5c84dd578a60356c80d4ae4d1660af45a41a9b3a12e256048ea8b2021987002c51149d12dcfaaeae761c203356c50b92bff3dd509daf8b450617f8990c63f79c3de3689d0876989a3750f94d86314055fa6fd31c98229e79d47cca2bc521dc642d738172fc820aa7d252957e4e54e48f86c539b415e6d1d140af99b56e7366829c4bff56903b742ddda107ff8f3d2251de4452a0a75f0dc3792a1ae3bea92d6a14802d944b92e120912d21b3d0cfaba12eda0eb60add2f1a92a3318abd9e1baea334943f436d60b511e4ce0d4383f6baceb3312f2902406a429d5389b197fecd3e7f36bfbe4069ac993163fa4d3e095c0fe7456de6a47a21ab4a528eafe982e48861536cefbf5537ae66bb73d991a904921a9a132163678e5cfbdd3b22d81fe7a2ad2e59e95722b371008fd386aa5946f823cbda125da6b80bf61795503b3e4a1fbea0a55fd4201ef42e0f6a38a3c1e779695f844c7d38cff78ec933a27ec17a13623fad8693c5e3d92ee0b23b35366e1cccbd5ce6bb25f13a39924333aefdc15118dcb6be833b9ed315d8496c946ec621de82fb13d354d5690b239feb072b47d573085c3b660f186dbc6e1decf6c21e9f2be46664d0a4e142457b39335f13c250c1771a000af92147b0b11bc7d07881a0881662994dd8a4495006968abb624df6321eb3cb854d022d9acfa27961b4498a5d89152f244a600a5d60d1071763d4c12b9aa452bba23ee589633a703a912c92ee03b29cafe6f504f8bb2b90bd138286006b0b0e262d87be82db5f5efffba9c2d24545935f429586ee048cf7ca2ae0730a6e13ce56d4ee5f722709c76aa75a00fb472313b0050f287c8b28c0cbbad0306beed8471e7a8e23e62f7195213e90bd18b5c127571c189c55456513606b3237149124a4cf91719a0859fd5a1d6751580e03d1899ede85c027f4f0761ff9c8f97119e5613c1548f005d3406e80118eadcf5154541290f3e9892db6c56df0d58a673cb7003a3fc5f65d1d5a562afc7ae612fc5b0b8e37225206ba564a498541f7b3c0f974255e3754a7de423fded7220517b88db0621597b3216b05d74afae4116d1ce00c4e0f42996191feab770259d5bdc81c29884cf96e7f4aa8d3fc51effe796a26010acd0f2ece258aedc1293a7e5bffd7098a220f9c7c2252f6145a7fbbd0b829f4eb54774e322d88f44dc23fceb3f111a231d7e3e68e497fe7525fd7415e93ac79d3a7be89a3550d0955272a577c37196cbf5bb93f34b866d0a122608e325be9c0fad12610a75e88f6197a7016e59604211222b64f84efa0419169c5a51ff0bb7688f379845b009f32613f9e170028d6e14e32f596f23a6274186d0c06084a82e68035ef7a18682e893b027e79172bfdfe4384857226e3df3caa83e24897879a5033085b69ee410ee238b260e827dedd52777a76910b8d0ffa300729ab74a6bb185cfa446fd65fef82001dc688f803ac02d6de04077c504e14423d6010ef13270a8662d83828e383ff50819c025c0f4ff41ad3ea0a502b0132787a1d9e1448b5784e215bed4f794c8086b4cc59fed6c57108c85f4f82ec38c1ee40cd27c4838f9465d7c7238a121acb7c60cf0c36429efba650eb51eb114de4f1cd1bd3be223cf2a02eb9d339e5b342f00d65ddbdda0937e969ca097f3408ce2778c0b7c507522e791d79274cf8f5371c107814dc87284e67c684067833c8279e56f0ade412f60eddd8cbd59c752d7eb48beefae5a145791fb1f03766b62f089118e39faf76b964b43c3fc02dca6b27b1e0feb3673752ff06e0a7c884f9fd7b5c9f46028be1624ebbfedc53f3d22f08ab426b8de16fcf18d43286e79b3787a6b05bc9cfd4e0ea15682777f95c4430157b9570caac6456a3e3036c8034508032860e372fe592bf4e3538a30f05f9f9cf2baddce0e724007b0ff56682a0643eb3accbdaab8595d7c80867d142a155b46ddb15e8f90b43c7bf70c00d87e19114f823e7ce2311340f7836d559515098012f17e70884e1d0027df7783345291bfffca50b89e205f7fb3186c6fbf2bc6909d8c87f39678ce69e0708ea79f3cd889639740769ab22e01e14f77fe8854bcacd17c18ed1c81af6fa847a4706608f0c91f79beabed33ebaa1c8c1e7a38e2f0c8c7ff54def00e9468022515749f69d5d413259b7e3d61ee8c6f8ea65e9cfeb4fb3bd9c8639cc7cfc1ba9eb6af43a4a1033d276491c80502680a7d2fe2468611d6cce664c06893a50f926b20e21d2ed7f996aa5c15ed7c4594d499cc7e52c9cb6d2c8e08eb9cd15fedc1b8e7bec8c7e75c7a910488f8decbc0ab66e429da40d723dfc432b7bede2b2849da64ccc2ad4a2f9a2c15cb971b9924d41de3be2de463ee54122fb7d863047d5388b4c1678582b64ec9a754fd9c94f003aae95b1ee40df9c187515b8fddc2ee79f7ccac1f8cffd9ac452dc988d3549173f38fe583f9058c2c27e9dc763f6e315d441cb2b490fe30c8f7e99a8b57a47e81b44ea10fc85384993d1a0ee688582e924e0823fd7b6c4c4f755e92681f7d899cb698725a98d3e5bfeb24315cc8d54a64be230ef9064d44ba7d708b492d852c6be6d625696606a6716b080c81a20a198ad31c81a3cf1a4e0fc85f235e51b51b6d005072b56ef98eb15c9f648a3ea743f49568231b4773beb1cbbbf411e6cbeedcec347dd785d07db9aa817c982eec722eb2c1869f9576591a64782f9461c76cec636b63ada0549bcf8eee2396b7c89783e5c94430a265cb36dab025c2fd2b976c58ac5a660da34615db4df9090f21098d7ad251ed901d166980c950de4b61f42f5d2131fdd658704adc4233ca1439c6da48cf7e169adf009bb17b0ee72010456ab3f5670406ad88682bcf2f2ae30d3397522c380fe15a7979e238761d98715b5cb31c3ce254be2256fda2b27a99dcf75dc2e5d20c221b810a6ebd1201b509b153f3bd9ec928a4c4638ca67527b7288eb3a14a231890ba1b62f33ee17947e622cec2061ff6060bcfd60a232b3c66d662b81537db32fe408f078fbbbec7a1fc0d1d1a93e9dc381e380442389220014007c78f2dd7d257598ac810b9027c2b0d85061f086262c41fa712b7009ae1bc9967b0539138e237b31dfabf5fd2092883d45c5f7565c7160b13a533ca90edc61bbcf5d9810c59ccc8e93d62160eafa1a754e46d21dbf269f311e311e29eb56bc4087bada2f897658a30e3cbfc3617a0462a85eee0a00c4cbf3586a5465bc7f9ae2ee86fa060ebe6354515ea8420cd036aa2c10efcc9cd24a4396b27f861afaeee03271a1fdee2f1053eafc441fc09454a480cb01b5704f4e81da7080f209cfc85751a30203e33fa85a8f86d4500fb99ee51e5be6919a0d3b7c74b5e7551b8bd3d1d2e8938604e6a826f2acae255acf2a2c82cd0a7ba1e1a90ab1e40770b2c6aad4d8d5bdaf604292fa648add4f1a17a572d7a7abd5cb0f3419d6deb79cb0e7e3584fe7d50c40629bf2c486dafc75f2ad14be40920bea3d4c545882311a74f818b817736234e61041353944b1199177f7d3974269ba3a7bc12cb1b1f02f13133399154d1e2f4bd721cf27615e3b6ee7baf58fa6d8c2d42f4846e5aa41997b8086b865d4e188481e78211d27e488e0dc3f1942116ce7023c144e0ea59231759032960bc58e36df29c8b96190f226b644ce1b91b8ee9efe082991363ece1950d19158888654777800ff8926a09b23bc2ab50d58c23f22de5219567682fc6e49852fd563fe4b89f8c1eabea79ecba36aa32669014f76b4f57bd12a0c6c5da62bc68cfbbee2a8186fb24e7aa94ef6f61e782ede6f5f3dbd7c4dcf7a29d73de5ddf9ab4bdd23e72eeb8ec1863404f912944831dab19c1c76fb6e7c7e09f9efa2b432641f5333d10e4c766ef7266e1cc793a13b380e5684e389031949e98bbb9a47baed582c2b1530c5093e54c4e956565fd1b532bdbba4b3f5627b8ae19dd51a350d5f823d9600c97e87ddb20d89cf858e2219b0411691260499d632d753a0a7594f2719fceb73940ab0417cd92a241a9be3e9741fe25d2533be3321fe80c5283fde322dec46996784f692dcad9e420b66ed5f0aed9618704fe7d45eeef9f16ec30c5c345cce1bb3db74f2ba176697e4ff91713ed0d0ce7083ae1e6072fd1b9d562eafb6f32c8a1c8fdc11c5cf5856415274d9d8c5962547c59fb1011080324739a63915fc7fde00c91fd966451c31e984854de507865fd62134cd5ba22f62fddce95c88177d0d6e7ec5c1aa5dc9835d2ee8c375458a4e8ea73eaf1ec808f6768f86774db527826cba9bfaf5d50ba1ecf82facc06ebbfbf805451e423db3dc70d40aa2cd0f533bb9279a354dd16d0e8bdd0d17608e91a18d12da9da8ad054276b0793aeac6efbf75a63189678ffe7daa2e20f227a796ffd8f0b2c96701dfa5b479c106f5545d3a25c7c5996bcbb3e0f8a8266ca4304ed382d90ba6219424644869bdf825f6c61449bee05510179882da60a3e3d8003ebc809a36c31aa010e7288d85e8f17266072a91c867a46573b33e4818f17c7b70c850de6fb1bf630744836eb5238be89b2d68b01b29e54f04aa897b26585dc1ba96b21e1fd7798cf0114ce8a5ba75d86833160e659ff902bec0644224af1c5eac946eeaca7c860622e8d26973dfaa1e7d38b1c31737708373412485bd49dcd7a70924dd5deb3b9e07862618b5e01802fc1a1d4dbc5ec4a9d4789cbd94e21660cc1ce91cbdc3b9df1205c4e86f3542bd45de58f43e7c29073f0c64065aee3c6f600e4df6c0ea19c3751be07aa3ba6ae09fd3b502e01aad2229542cea8889ae185ab55021047b3db06d23645890f2f86decf6778ebe75c00749652b758d56d5119b9a590edb0efabd91e724eff8672dfefd2da391f8449f60a00bc7f7d0f86637ac8309cef7e92ef48bc616a793dbdeec256638edfcda78cf4022c01b1308d29683fb730b4bc123ea0c1795edae7caadfefea9adaf557017dec2ef0b9d21aa2be868f1c0b9b6564f1d783b09a50a5d69b17271f0620e5f0b86da19290ac65cf3ada99096e338c0df008323990b40124a91f97df45386f942567897172d1464f4d35fb46575d1eea64b5b7674d77fc7ea9667b2a47b71451888f6d649fd052781a091139e71a7bb67992551afe56535e9c90b8522e2031295e4f06329731af0a453ffd8ed36162dffc4f007500f176a46abea4f0f775cbcf3c23adc7b172a822a97adc359de64ca83b72969e91dd955f34ae11157511a6333cf07e8e42ef5144da9b9dea7849b21182bf2b091146f3a757accaf2f8222b31347484e6e93e12b23f95b0f7b2c0be55e43462b80cc88a74d0c761dcd539c08c2bc10ba7d6929628f1a992e73bc9cda25efd63d1f545482679f4a19417698a2c07aa8e9f975118424e5b0926301937412e4a8e9c7fb42fd86d8e6b662b213819297706b384107036ba9dfd5552c2474f0cb955330c35700b4ef26a4c50308949f1ea559d61174edf21f0b0718adb5c8fa4980308cb7b980af1f6c49b26272ddfca8e7408fde209d8c6e89293f05473973d861e0dc7cb379645e252fc9bfc75f3c4374c9e209760f207585d4b8adde19bc8d3ab04a605a3d57a272cf1ca00fd4b3c3bef89c2a5b386906f60e57f4b5338c4fa1941ee635093d2082059b3076ed61c4095ed1201eb6d7b6ec2d1c8c800c7183139cc4d6acc94f4f81c143a2ce67b4c8ddee12bedc18711ad6d2c1daafc971424bf1d661d9aaa2a581dd15ee807c25000291e0336f1301216e45d9ba104f5d67940455ab0f9a155158d21b080d4e00dc065154d0cb1aaebccfba66295ee38dda5df719a2c57e93b8918e4e06ae81cd15844adc2bcea50c56e1aac9735d47c52c8471557ac121c339213f1e46886d9fc70fb89637d68167fc5c291f55e3d95e8f524840a9a42d092867162b4e43a8a8c7eb3a34d4563c60bbee32ad3d8f949271cfd566cd05ce67e4a603d27713572d31b3b569fcc7da62c43d87a442a1458b5a3351c2940a7b0132936a2ec98635ad525a7c21994944cf6ebcbcdb11c8b22ba7a9779b9e8da0aaaf710dc6e833f5392ee03454ab6a793552d72a29c2f337ce268104f477e3f7ce2d00554f50062e710d8a17825734ab8a36826a9189b286df9ecdcbb2859ae94556ccbc46b326188ae7a06d90a0c43391f05bfa46acb0b048e18a38b9b8fe8ac82a64ef20d6261311f5e371ba9dc3d3af7c123e7723829ad777c46cdff17710ff97a73bcc0c5aef2d712771efb8f6fce1aa14f012231d2ce3c1d776871acb64151a8e13e4e3c7177565177b93589be8ccf52c923b05df9e14a54bed6e649d23ad816b1eb7628aaef4ad14f9b9d3ef33b65d4d74116f9bffd8b2eb84163bf520922e129b1d65a6a9174a5fe908b7210473bc33df71be0673f0f6735b7420851617e86d52de9fdcd456fa77bf0832b6a700f6d1e8680bea783cd1ce5b9b6c6a996cdfd7bca918ff09b18dc2d8b2613a082a5643e1fbd6163e5b3b9ab40a791505e9a48adb8867056ef467ed812b3fe9ef67692ee91f3fccd7273a62a6d87c8b96ed706383520c146b9e5f9881174c00c2d38ff88d2bef392f355c94471ec50dfe28c434de859327a9ea4c83461930eda775534a2da0aa6c8f362fdc1fa5b2f787a094b439f7308aa0b3d66339cad12b8d5850b4d67effe3f864ad81b2f7a4662520764f167eed725bf69307154505a9b2c7cff9e3ed94522ad51f2582f0cda523355448bd8fb0f47f30dd0df6fe4034e77690ce83b275f7ef72cd9863c829ac4a68e91078d42d7cbf45e91379f315a9de42e899d76408a023d22913c93655acf698a6a57bf7e842548eb0e7d19fa75eb064d2908ca6ab8bde14b2a64bd748585a0852e52d6e5caff02d557a5b8f11296f52067f6671191c293649b25e74483394cb8dd7b560e1ecb50bc926a462b57b5a9b69f970cb7227f7b480b2657ebd9f922bfccea68218f09744ffced9dc5d6e02797b5c3a6d8b6f304550eeb37cdd00baf23d6ed1067cc3fb13967c27eba383a87d55ac6af9e56f3ef5e313e5f3403e257cd94223882d7e645f17739909a40283e51a0dd51c1ea93fca96bdb84081abec4d7bbbd72f03614b5a71781ed876029ab07a6d74974498776ab9ef8e325f7f91433ebefa6bae7605ba1f8dbc7c14f248797245d7baaa40ca8bbdf8e25d4921e814abb2e19304c80cfdb3d35ccf69e863a0a46f4c64578600c1bea2349e20923de9165cf2d372d8eab6676bba3d2454ba3a3e2b782fb8c1e5bd6cca5c7e231e68c13d379c9b18d3b6987c9522bceb35dac42c81788e4387b1bff77444809c275f4db900aeb297d4a8b16732ea4f5629e44f7cdbcea50e77dfe5fe1282778196c4b62a0cb11ace66de7f1eb1308189d91a7fba54ac33240e27ea0d87b70354f9bdecb9d39face85c11faeebfe306c3d6d86b79c6e1ebe3e3f42e1b04981a9664979164fcdd022909dcf696acf35b31c4519198f4e7917c15986b6d927da77d49145a2dd288e827f88d30fec824d422e4e9488d3d4c02661b19ac192fc1583508a232ee9923be68d2aa2ed081f21c6dc1f781b39bfe10abe650bfe53cf1563f43d770db0b4b3079a40d251b2b60b1561f9144a1ee82f63dc22976312db6806ae78b23b9f5e45031f2a0535520ed61001f6d72e3f56a4609f6b0600e37988858ca60baf5d04de8c92b046808ace57f629167d7e92dda82c1119c34a8b0f646248efa42addc86f2a2fc3950dc57d1ffaae4c5d528902158c9b877a4440f4aeee4d1910848847de10401d2b8303c20340cde53a6f516e98e5b742fe1d704b2fcc382100cc3fc9c5036cccc621d5428677c7670de632596d84a69399d0f23f46029b5bce7a4b6b756cd7d1a2487e1fea5152ff257018c7ebf54d9ee47bb71353fe88f54a29cc0c02259a0c6c7f9772b8db16af897ac4cdc4e7b611d0d63c880e1157887160700c2b04ea818664d9c9a359d8618d1066b26cee358057be26635419fb037ae62bb3bbae7efba0e9b62e8c63c2c0643e65b59c4219767eacb4bf33dae0e7bdc279430c62d4f0566d77b393b52ca1db7914d8d3b37263ae8fef221f5da662b17d89d06ffbc882e50d6cb4d9a0f35cf614c63e2a87b33739729dc514ae967783d37f5c29e2ff814308d094fd378bafdf900708b8d6f9701b6bb85f55517af1fb68fb9203b0cfd1473a6206f3b0b2a5cb2a2ef0a3ef616f66b772847f35124e474aeea78b720511fc97309cf97a05f9fdd1fc8086e57d747ed1f023f7b18c9c6138b701add369628746a109e42e765ccc1b9207c4e664ea5f9c39dbc5fedee8e12f10b0ef800e31cbc38f3bfaee211c1f1f34a1e54b37684102c5a6022ebc045f0c3330b026c301000000000000000070f1add9b77183989da494eca42925408394524a29259960310034d77e471b10349e10e9c40aa70c6b0c1f0c8d3541b69ad0208349dfa9add3ee34e2511a63308a5df709717297652f051a62308ba9ba133a8fb24f2a1a61309f4ee9d7e45c0f553118ccd529e9d264e28f591e4bb62e697cc134fb245526c493c9f1545e308531b326ca93f283d3a3c7491d34ba607af7e495334105d9f1a1c105d3e9b86e7237df82c9c9f1644b28a5828945430b26f5a871a91ef6ee940573ca397d9c681e972e8f05f3376934f46287e91c5dc1e47359b9c7c37cf3b582792dcf478a3dd94bd72a98346133bc77b54f788e0a0697db278fa92ff9b9a660ca964c3e6154a5640bcb460b405034a4609e91b3f29e4451309f944f175c2f4f9a0e0553df8a296b0b4dd61a9f607e3dedd959779bbc9e130ca3f4c47bed523cb626982a6556f27495e3932f1aa0c10493acb49de76769da231a4b3086e75b9813e20927751a4a30a9e9aeaf858a574f9246128c79e99c384facb4a29a20c138e364924a61a74fd2ff08065d9a3423d64345d3311a46307b999ccae6647fb118e677f2793aa1164f9c1231cc647f722693d28b762695255a00198649e7937aaeadcbd467b2c23079268f7a3f1de749f36098e4b2099583ce911ded92ad0282fd020830ae9c46b442a8dce6fe8b4f07df50353d7672f185a972d58608d9d01f7d2f0c4e1095467430d7277c78612685a5285f1ef3efe65d98e4c6eb899e4a79e75517e6dd9799f1732798281766959793f2e44ae72fe2c238b7a2de3341bc2ebd5b989d9c33b93b6fa93e1db3854e50a1945e7eeb662d00a985b92b657c1627b4306893b217e3893d6a3f0e6e16a507882c4062b1134713442fc7f5e84ab67ef03ab841c6292b45c6d1713d78fc38ed031058185b54d7d68dbd87920ece0ff4c131818d316cfce007193acc0e14d818c346da8183043fe8f165a8c0c61836be8c1ce70536c6b0f18327818d316cf4f862328e8e3b2b80bcc270c1d533a97abe556d3380b8c2e04df09c74b474f2d9a515a64e132a3f8827c9c78e05105618ffe2494fd2b99c4cfecf2a36d318b32f4d2d6b11d527ff93d49d4a418d6c630c1e931da7071055189c6049bbf3da1694c006482accf2a4fca430d304d327ba716508105498535f8474e2f8896c924e6126b9e774b2b549eb51640a930a6a52bbce4f3d132f85419fecee1a5a7edc440a93501ed749b7f67ec64b001985e14fe54f2a9e83f44f021185b15367824ecfea70a29350982fd5e3c34d3c5708d57c3c0828cc6482cb67dfc7bf3cdd27cc44f179628db2f849c74848d210403c61b04d2779aab860aad4096395b6fb89c88c561227ccee49692f916a429ad0260c6b9d82d60c2727711e4d18bf456cff5975d2e45c268c9d5d3f2c4d6891fbc1845974ce8ea2e63b3c9914328e0988007209d36e7b9235624bd49cd8e900b184396c7ae794dc94021b8054c2a493ee4cce04d3fa7f11258c7eed57d2643c09f3656cff7b75d2419b92307d34cb297e5f90bf311249116a497c5a1cd38771fa6041eb9e600637120824cc048fdf7e176384ae8f30a587133aea879f6fe808e3577db69edf9df8ca8d2c328e096c03208d2894eb8855d414195102b2085398269efa48b37bf74114618a6d7289d5487dd23e49844194d40ecb793394183b008208736bc899d5a628d936c821cc6eb6fb045df23df73408104318dfc996e542866c4b9e3a8014c23c6be27ae5b3cc7c9010dbd7aba59453de92d6cd7db8db384b15cbf4255bbd3ddec725c8208c7219263fa6a229a509578ff7711700118429592e77d27d8cb3f50361f212f39d17ac54680908a33e99fcf269d27d3fb505903f982b3cb756d60bd274740c1bc607103f183c799347074dfaa074d2460b4a604307884709120d207d3086cee9a04e7d5477e68331d763e535db4a4b0eb20763284d0af2bd09ebc19c449eccf07c4263a44c00c98349dc6d67f6f37be3c68339d63529c4e2d92713dfc1942be5beda7527857e763067cfc1ccc99ff66a4465d4c14ccc0c55a53fe598b3d1c17c9e4d9899d3b5ce379b003207d3ed87735289ef4e42a86477e560d0fd334b3a9f6772284bb67477f02c7802481cccf76492e851d58fff0d1fa7c42afdfbb8b2d1021070808c63825c103824d39c9c84e9d9e5ba07206f281037d4016903f2ca4c6962c52ef14948387df80966706307081bcc332a9cdca39dfcb9e335186e9bbc953bf22ec906a206b4f6c5f7a4476557b29d03240d206830085519f1f9c4381d4f903398ad2e45e6cc7cb72a1118b3108098c1e8840b2a67b5139b9ca22a903298b7c9d1ba4922ce629f8090c138f2a9aa83d23f221609c8180cc2532679e627bb64ba92ad1dbc0f3198d308b13ca33cb64754b2b5e3d78084c1f41594b4d204a5d79d976cede0918307ba010693cebdd4d8aeb4bf720c902f342b7263b992975b69eae99c820a3ae5d393f1831f3774f04fc6e200f182e943eb7f774a397b0148178c4f6e97ffa45268513a38d8c12307ab00840b66e29b677230b34c4eb3b760ae2aa9223e28e5a3c4662e00d182416772fa3bf529a527df593056d2fb149f42bc2d8a3a41138060c19c536fe877abf7ee5bb27505a3e55bca3649c5f8692400b182d975cf49f3e4f9d3ba4ab67cf05702902a1409154cb133b9b44cd59feec68e837c4cc12017d7928b26bae8abca46002205b35ad21dbe9f459354ba648b6b0565e8b0f18115d8800248140c5af4663599f8ef51ab92adfa64060205a38a25a9a1968293eb04f2047367b5519aa479b576910188138cbbb2b7dabfe77b3190269889e99deffde365153513ccb9a5647e9aba151986016409c651eb27c99b85fbfd1b0c204a30aa76aceacfe9149b648224c194e4d65d49eb79bd27fb06102418fff4276522dbc692264730d7c5d0316647e87c2aea06102398624eacd59d5dfc275a0cb38e1384a97b79c95889610a79a29fb8ebe96b2fc330d5ede512316a9af0248561b02e4d4b7d6d1e1707c36cf9cf5cb49d4e4194c03055551ecb2777edeeacfe8531448fca648fbd78f12fd92aa37f80c65801090f7abc0f03c30c5f9809a327a4953c4dd092cf2231a317668234119e5b4ef85c7e7263070a5c80021b637c8084a40c318317a64e5ac73271f5892df2714aaa1e8920593063170627599fa6952a1dd3a4646b478f32bc5f304317eb09d39694c6a70bddcd61462eccd9f57977d5093a97c3770b33703199ecf983145139f792ad1fe8c9f0b143042424242476258037ccb885c94b35a97744a52ef1ff383fca3081eee7701c38d851c6845d30c316a6b2266852d507155fb47c98510b5a98728e0beff4514fcec4c38c5918af65f45fbcf49d54b2309ac9788f1f550a6261f6d1bbea23fe2d952702113c0f70fc3838f8139090f82021f901525898e293f579fc3df3925f6190b91bff5bf1f163eb0a8395f5e67b274fe0b7ad68ecdb5d2e8779685bdda76be78f16ff1c74a85498c10a33314c7e90e939eb21afc27ce1e420374f9e9592b161862a8c153ae69e94b29256a930553d31746f6b6e57c8460b4070e3060909090909c98d6ac50c5418aeda82acaf0fd1e2e41426b591317b499beee4f5196698c2e84ef03cafe404273bd91ec38c52985dc7ebc27acb697fb003073858404232c1d1e35f5086bea0078fc98d1ce81309490144318314e67c82dcbb888f61395ab275e6c3078684440c3346614ed993691b7db195491285f97b2ed738397d101763166684c2ecfba1e6f24ae9c9def95ef0ed748fdb0b333e615627dc292775be7ca76ae120830364e810018e1b64f8b8f68441fafee79a4ef5a559270aa184e7fcc96238612fd9ea81e346d572c21c6b37efdc3fb8fea9644b6d13463595c48a134e9354122ad9f281836db4000424243e74e0e3fcd0818fd302eb13cce006116668c2742a96cd93946e52524ac38c4c9857a459f44ab7956529590f086106260c4a7e7ab18f159f4f26b4987109a488586a51aff2799223c719232d13332c61b82b155d51b9640b7b7056208c1995286306254c7bafe86e4afebbd481a3c407090f1eede051f0d10e14ecf880fa9831093edb74a86e62079f37478e83f8471926d8f181641f64f050c10c491845ddc9a153e95f4b4191b05390a7e5e4b92a3d06332061eef0eb393ee58da5f01a663cc2a0c39a8aa254ab9778673822cd92ed7aefca1536c566467f6c92c5f5bbdc6046238c22cc2c8cb0fa9f4b419bc108c325a19e2e7aeb724abd0748483e95198b306592f0f417545a30f9600109498ff741424242728219dce8314311e6ec5af523f5fad4134b84b952bb982a7dd6600622ac4f65b56651fd3b842947bb9b7e0b0f3559c9161947c78d1f387ce0e041f7e0f103870f1c2f589b610883b036213a4d6aa798e5ce2884c1d7e2499644e9bd77e0409d4108838a5ff6de70bbf9b88c41a4bdc49426978f5410e651eaf19f4c26d5980eddc0c18eabfc643923103d9801881fccf8438e197e30977636416e5b699f6e461fcce460f719fa9fa0bd590766f021cbe4b4132a6e49873471ccd88322f684ddbcf8292f93197a48ab93d4ad4634b1732501d979fc91f979f154468eef41c6b2881a4c414b3e9cbc2959172fd9bad183dfa1d50191349874892af5e99eeca4f496fc3829c0921f870cfcc10f431134989e245ec3342d276da792ad1e7fc52c720683bd8e77eab2d3ddd11044cc60d452ed57ede37b4154065389cc9ac907af4e960819b89272d5272c591d3d787c40470f1e93de31982ec74c91a7ba4e4a411131b456692e9b25b9d86dd1e4f2a7790775f249ba1b8884c198f541fe2fbdd89303834997f24c1016ade509639321f205d36892ce9a62623cfbf68251d4e7638eca4d50da2e98da2e133da54aa2d2850ba6f37c59b2655e3794b4cad882a9d31343c93515dd467c3910d182a9c9a49cf4a58f28ff30070f1e9e3944b260fe14f47ddead2609ed460f1e3776f4783e8108164caace6e9c14db73e9125307225730f6490fcd26ca6ef60112921205112b98ae89574d9e51bad2edc97b800722553007fd79e475975c7974e0e8f183abc70f1f2410a182a95ccb9ea476369a1cc231c1e10307978f1c387e948101912998ecb7a47e366d4ddc908a48c124223fac9ea072493776f0c0f138e8c1636257d6e8797049240aa673b29a302d35d77743c1a05410623bba966c74e3073d7e3014449e60105d7f55224c5f6dee04f35f3cab2cfa548c9136c194827d1edd442786bb26c20483f58d3bb1e3473a493986c8124cdb1f679c686fdbda2bc1946d2a3ef1f6f249b94e8249db7fbc52a66fd2c98920a19456595bb4b562d76cf97867bc3f696409d38a1cc19cc48a6b05d3d2d1f42246308d909f7a4d675bfb5e0c3349df747b5b3e9b9413c378ca4497388b114fb1611845996e3b99a8a1e1496118ee444bc4b32bba5c42826126c5be27a8b8f15141280418a6ebbcdc571e47e964fdc2f869bfb227dd31efd417c6921bf5cddcfc2484bd309daa2708f11e3ef6897961dab514cca2c8932fa74276614efe95752cc5105dec4ed2eb14952705427261def1b7ec9969613b5cb2d5757cf8c0f163c7d1c13ff8f1e37b90a165fca0c79d6006373610820b63b8e7e9f3bb54f9df905b70b1bed36e3bede56d8b37e6a47ab5854b6b610aa1ed65d343fb6aa585d67276179e1e2bda53d57aa768e56bc82c4c22d3b36979d2e7266f9a16021292bd513e32441606b9a0c9eaad7d253cae8d8541554f6c7d3d58aff53db00c326061de7873f560eacea458b2b55606193b44e0a34719639090a04ffd0a6545478d5f3a93a58f105754b1ec53dbeee45a6de3ca095ab55ebd53ae1516c20ae39ffb2879528884846f94adc2e0966fc55abc9c5c5955186e9de8aea7679af1242415e69195e4f678d272390a861054984a6efb789a9c90a2e715424e6126b555c51ab3dc5f9e348541ec63ec63cb74cc14520a33514468b3d8ee52151b238414988d6ab668e5aa8eed4cddcfea16d493924a71c8280cf7a4b8a52e3b8f685bb29584105118e589df1a9e3429c447570921a130a84c0c1d3ce85d7652261d8510509849f1835b3015b65fc61e8684e4f4f8904f540af1c4579ed4e2e77afeb48474c2a49eab3653b493478913c6b827aafd49ddcadd6fc2a4b5a1cc83f6423461caf99d2b564a39ed551fd8818263448464c2a0c3c39a10cf1f634f0c440826cc49ddc9e7535e415c5dea12e60f71523b51448592d5e8106209834e276852dadb154d562b616ebda07395d0e4fc9ad5208412e6caee217413e4843653c9d64e216412c851bf5df34e522a56b2d583d323d5102209e3c977f0d893a5ba5bc956614248244c761d1e3e4af7894dd8c618f6881048189e4c7282588bfc1e613891259ab4f53bc2ecf21fd37487d907f10e23a411a6739195940ec25e8430c2d85bbade92baf527935f84593f29599f52543971fbff809a220c967f4d7cec574fa726c268d5613c67a96df920c21cf22dc9afbb1cc2fc9ecdcde74a2c44133584e172104ae333697c3da34148214c39c5d3e424bbecac574218efae8429199572c93c6410e64fb9c572b6ca0eb328224410a670a32ea51e7dd25ab34098b2dd563ece0795e40710468f26c4eadf8fdcbc2d86903f184e8df6b1707a3f18de49caa277e92f25bd3e7021840f5908d9036321440fa61c476a933249cd7930a9585e9749eae59d38976c7921040f7f3839faa69e7c884b36ef606a62d9cffe958dc76807f3ea65cb9f9e9cd904ab0a21753058de6e3595948a6b69081d4c4ebed46499cbe4042173307726e7aae09509aa7a2ac3478920440e66923d497ef47af80e6e1d42e2100207834a2a3541c7940b0e216f30868ae6042dea74684fdfa80244881b4c67418bd0d74eb214d236189407215d2f09fd2793668341289d27d49eec93cb3598ce42e628a5b34a8f6a7ee428039f10a206f3cd5693df2f8ad2416930e7a9af0c57bd98734a5a820841035bd1b5aede2a77c5aa7b26937bfea46b7a8219dcf8117206636832c9928518b91ceb159090a01e1c6206e37e95dec88535153fa40c069dee449c4a41e8f7a4103298f4ea9c5879daf967cd40c818f89c7226093b4f27373931e8a44cb40b6ba74eae671012067390e2fea97a5561dc103098da62e71c3ba4e5930d18215f30134b3d59de2cc5c9db7bc120b249417a6c92ba14a72e989d64329c942fecc732594042c2cf830ae1822953d3a367a78c11fd168ca6e348efcb54bdf6b4602ed32d2ae54543b2600a3a54b4acbc5a9f3f3a405f32462542b060b0acd19dfa6359ce5f4d845cc1646f63b17ee5eaa45608a982e1fcfe6c54bcb24f7688102a9846f5938ab7d1e420b639844cc124642b852d71e1ea49a721440a86279eeea0e27dd2c447f11012057307f53442134dfcc3bf2a9b8440c1245b4d45c83427956ac288902798fab28715996a424d8a978810271873bbbcb53c9490e926ab3f8434c1d4d10942e7fcba9cf7434208138c5a3ac890cf387175b131c6183638807d085982372a8b9097523221f6018f0e9090a4f7418812702db12cb1309a726ea96405293a4b2dc75b1f58810ddb4448120ca6df4bb489d8934a1324988976a59e4f0e224b4f2147305dcc284dce97b53ba71023187c4c5b7c828c69dbb11806ed6b225e848c18a6d104974f6acacb3a350c639b92a5a2631346fb424318c6d324b724bec2bb973c18a6aa4e4f34d5b0936603c3a4214ce6f9458dd1f51706f57e266fb52f8cf296e213eb777d3ded8541a80d2fb5781ed493e485a9479a7a7c9457b9e02ecc66a2ee3df7a67a902e8cd72de79ec358a959381726f3e4f9dd3d783cd56d4f30831b3b68e0c2b8276efd4eb08fab148d5b184e56d4cb59d5c9227feee091630c1292dfc123075a5b1847765ba6d8ec681dd1a885413f395e58ce492fc642831666cb2d9a5495639efec7e3a0c76981d3988599609a3c6b52ddbb453872f8485b1fa0210b2fc9cbe93341ce637e3462c1899ccdcca54a4bde96a4010b83b87688cc274a061aaf303d99d47add39676be9b8c2f8c41dd924717ab709762bcce4718f27df89ce2469b2c23ca746a9707f25b3c530d0588539ffe2c8b4a05785e9bdb3bcfaeb933e8b68a4c29c2b588a75917591312acc418cbe779fd2dbef4f61eaaef4ccf190b35f328571bfa45956ab9674520ab3c810573abc79b238290cb2e67bce6647dd671426d99da5cd459e26499128ccd5a14ed2153f74527b280cf3963d8fc5a9742a28cc95a69c742262f72efc0953eccfc90952c4fd42ea0993d615d3a2745ff85276c2b8dd8427673533f9f03861d2f9d9b68997740a966dc2a477fb4a6587f5f5451346f7f94af11562742e65c2244c48bf6e6278de91d7083430613af9546ff29a65d0b884252a716301342831091a928804046840e2110fa0e1881b11a0d10846dca0008d452822110ca081881b0da0718805d03044036814e200340891001a8340000d41d4a01188ea09c2f3c2e69678c2340061cef126cee8a8fdc1709a2cd292fecc7272ca0fe6fc356b3376a6917174dc0b68f4c1a09ddeb6be546e27135f3e98462b9fd619d33d279392ad626fa0b10793d4f6f0c4511ab2a387a2910773b252b225f27a2c8f98061a7848e3333d39f1cadc42d0b883d93d85ce3629f7c59d255b3f3839520ef6a19da06107a39886de77f83ef9f992ad54038d3a989468dd09edde23322fd9d235d0a08341a87dd27d55aeceb92ed9ba44038d39a493688eb01466591e3c4a6ed818253f70c0c0478e1c1cb031860d1b63d818c3868d316cd86841096c182c9534e4607e93a9a7da33898359c384abafc7e160269937b1c42fa4bd7ddeb0d68cc8b658dac68a5bed7be714c693e54ce740c30de626a875588aff639f6c83e9d356ce53f5d9c3e768b0c1e844db53ffb7b74ef0d660329db57982b65141466a30eda755cc782f11761acc972fcb92ba8706537c9213ffac63a8858d8cdb2bd0388341ebe6e86951972316fc781f26061a663093728a53323c645955051a6530ad5912356c30f58e88d24dbae4d95983312f9b4e41ad0633319fd47649cf9eb4d360521d4efc83c8a5cf6830c81b79619aa053c7f60c66733d799faac369926630a97c26978a8965307f36b13a5c93a36c886430cf795fd6cefeef87633013379ea0ef89180cb6fbbef949cf851f0653936259de86275513184c269d60f3e4d199f8415f307a10236d4bc80ba6b8d6905e3968c289ba600efbaf3ebb9c3331c405e3a8f3d1232f7b50415b307a48d96e8234ed848f164c4a6aa513eb2a15ea2c98c99968f6a52a46648d05d35c6fc5318fa6595fc118a34498e50f321eb682e1cb9cf0c4b0dc1d73158c3aafe56a3115cca67da4090b6dc1894ec134be4dcee9f94e45550ac6b7115561deadbc8c82e172e5a8fb974c7a775030eae52667856c8f91dd130c16544cdfafce0926cba5c46686267faa5c134c2a4e294f376f72376182d147de935a34d933b927c012cc3bba545c8ff9711301946096b9a02ba29a286a2240128c6bf1f3a1542c27c809800483722fe52674ccf79900473068ae58d27572ea94096004e3c907bda5ccd4655c0c73d0bbeacbe498215b6298740c9317a43669f71c86e1443dd68f10a7ea3461184f39995827cb60986eefb4e8933239f5070cd35d677210264296a57e618aa52d478f71ad4a992fcc04153d6cdf7e8eb9582f8c224c98324b73e293092f0cb716aff267b20ba3cf6c9bccb0f015d285595d7d8493827261be2c169aa0aeb24d8e0b432b7e169ffb166672f4592b9b6d61ae1cfe5a39cb5a98c9413edbc5da63678b16c690fb77e2a4b33007a195823539ba8f280be3c5bf6c1beac40559c5c2a04b597e15a1c9d74e60617c11ab6dc2084dfc15667527a83539cdbac55d610e959ffbeb694cdc0ac39fce113a0815646b5698c74ccaf599133bceab30cf38d94a98d00ca9c294d28b3419b9105953613c15ea579ba0499e4685a93b9758d19e73268ba7308fb23c2ae9fa96ec4c610aba3c569cda26eb560aa3766b5dfc6ca83c29ccf6734fcccf0d1dab511847e97ad8cbbb6f52ca88284c41757b34d59ff259ca48288c16b2730a427cac68662628cce6de39a8b327d9a77c667ec24cccd2b538625bdf2d9888114f98f37fb8263a5994e93cb23e8c74c26499e439bd33c177ffc57a015e61f1e0f630c209e397e729e1f974b01aad726413a626eae9544243a8d864c263b243048418d18461bdc4af6967b2e878a222463261b44c0c1bf144d13022463061f2b0f57c5aea4b182cb8d545ffacced78e58c29c6a4be99adf327d6ba4120639d22f6baddfa59c8be07694c138420953f65a3f3209b3a98d07a5e74b8f76333022094d075961f33339ef91c84c43ce4be52d9c5acbe5743a46a8caf1cd532390309d4a6d2145a794e6a547187e7427d90bf788234cf1929e68a57c72aee848238c622657edc9fe3a278d30c228d6644b159b9473feecc8228ce9c1e694b7d8fd058d28c2a43c4c29fdd604cb151d4984d14ec6cffcee49c1671e4184a95493d35875955c267f08d3c7139b143fed549769c410e6f350b6a5561b7a21cc39bda8a0779d20ca9d10c27826bbd7afc9599496066126cc255d1a5af2ab6e41182e857137277fda883a1208736782980df9d1d37d228311409c296fe9dd4c2ea5ec1427c52a51faa2936487f80f8e09d1393f2969c40f861ff55ae1c2f5c62a288cf4c14cf8d0e749a45ccff57c307faed8d31d915e251cd983699efcc19c54e99dbb2bd9d283a9e3cc87f1916a9f5ec9165e1eccd9b6ab542727255b3fb8819582113c9847debfb43e93946656b2a543ef60106ee9f2d493ea31c1b6834986565fc8d7533deb60cc9db54c124ac8caa12b4507535f68d2653b9d428f58b2d573309332e1c967df27db49a564eb0c478f1f2807638d762d296fb560240e86ef0ed6ba1d4f66ec3ee0600cdbcb4dde9e078f1ef88191371854ca9cce139f7ce1623018718359c4af597ace78ac26dc063341a89cf36ac283fc2036989b9c72ca534d1cadb0acc1a03e888e3db99bf2e44ab670e8d0c103b10b46d4608a6e2fb29fe245dbb51a4983c92efe833db9c3823dcee74009821134183e353b589c4b9a24e5108c9c4108236650148c9461840c236360d6b6626ab89dd5775b0a553a767ef266b89846c4709fbc15b15cf115c14818cc417e59762cd10a4a5eb2c523180183f9d2629a967ea2fc9af60553b60edad3743ec4a5255ba54a7c9c12138c78c1a4c9b6dbf9c29ed5872cc8910307094848ba70e300235c30295b391de4789f0acb1626c5cb9ed32d590e8c68412df574d1708f8f1bb9ac7be2ab96f94816cc5af245debd9ba8961ec182c944c9eabe353946c847ae60de0a15fdf24d255b3e383a9a042356305d29f11fe34f960d6dc9fab8f1c3078f921baf86831d387080031e3996078e1b64f8c813cce08619a98241877082080b2afdfe3d18a182f18356097dd9a4654b19998249eae776d593edc9b33826132c117ce0032437ac0b2352305ab26d6295767b8bad1346a290089d573ec90f1a144cea828a9f9d44479c37f204c3c878522755a22ecd1c718229057d554f2e193a8ece4813d40eabb3ca1ddf61a76f6485911d6919618229f6857593ad9cf553234b30bbe5bedced152efe52c688128c23fa6df14f990af3a88f9124189ea0b5cb091e6aedbb1124987348fbb6e0a9938ad9c811cca56e5d6ff51e4c87478c606e1597d3769fdaec122986d93fafeb534f7be98810c35cd16adfabde4b8e900f3e042424c9075f8244866150960956e724d3237213867954fc85caa79bf89548304c154334c9c2bff3e80a11448061dc79d7badc9d9ab7f68b12f185298f3ff16ee4b346e588f4c2acd64e76825cbd8e7bbc305cfd5f9349aa626da422bbf081882ecc9626dae6a154aaaa447291db8959b6c865d75ced2cc2d33a447061d2398f7eeeaa9cac22b7308f10e2895ea6f5ee49f1c187a0c7f78047096c616eedd1a5a216d43cb15a189edc72af5d56729468610eeba432671e1f72b4b330a51d2768d1848fdbbb6f94882c4c4a54136409f924d10fc7c2182af5d8ec8e1a6fc29c3e7cb0054460614a999c899a99fd7165dd1bd503915798744d4ddc9ad05dd1bbc2b0eef23ada752bcc5967ed82f82ac20a5365725221cf53bab4a2c82a520b96292a96b32c65dd9fe8e7669d4e08e5231155183d558abd8c4f62f353b2c5031ce93cd06cd5980a839bbe5db1fb59ed302a4c3d2a45c6ac6897d44e61d6acf40d9dd7fca445c414061db67c3f257bdddd4b61ae74cbb29eddcf4f2385c15b2dbc8ffabd744d4661b4fdd2b14b7fa6a78f28cc9d72d2395a3cd3d5c9501c76a529727229a7c51295eb93a6d4ae9f1428cc9d391af2d42a8793aa2f887cc278e2b14eca41fc53d09290f08d3a4f985f437df22d39aaa3df097375ac5f102785f7707102b335ef72ab7039f3e73ac6a4c8cbcc9b30a9ebd9fb92253e054dc8d081838484070e114d5c260e1345227209e39a05f9fcd70465991c4b189b78fa2e869c5135b512a630d22a961a35426a8512a61296fed209bafaf103870ec39330694b6ae3b33e69de3bfe442461ca615b7459f49dbd924824cc04fdd1fe891aca9f4c46200209e3c9b5931e4daf28b98b3c82add39ab1b65699addaea4f995571334d529d8a38c26c1e3f6baaffa4d14f6efc60c7798148234c622bd49334711e4e2fc208636982dcb0209fd5275b8439de9d1cbef2d54d452ba20883ca3e273ff1e48292221f7c49f5104984b153144d4ebefb41c5ae64ab5806441061ca97fe9236e1bdb75f9f5f0109493adf83840487c821ccff64a2e9d34d2a0fdae1f1e306193874e0a30da1c5baf3fa9051abb6b251214a9c74d727c754b235e17163070f1c2b788423070bda062285309cb8b4d19976a32384295c26aae9e8af27b5bf83878f11bcc8204cb1ef4d8912322b4118948ee8f58cab11627b1d1c1da552241086cd5aee70b520ef76d173d0f93aefd5ecbf0284a9e24941554e1764fb3203913f18ec9ea42ea5d845fc60d47631f92453271e63681f4c840f26b207935f3ab9c9e904c140440f2b10c98339e7fad2fad149497e13113c98473c298e13744a496dbb83593cf54b8afed167a21eefa3c7490109c927113b982cafe67f08a533e9553688d4c1bc5fa53dc9454b5f4e7af0f8800e78f028811a44e8601cd1179f933461f22d14fc0e14e4096670a30522733069a2e9ee79ea723085d2fb9ce4756a92f2c70e1c3c52e081921f386020120783a52065e4c935417317818341073b3d2a9c5744de60dc8a22264567df27fb6e30c869ffedeeecaaff44da6070353516f4dee90f8d0d29fb20162b46a8eefdc78f43061044d6608a0313c052812e15402b06813a0995f67f0940a09014939403d00f1f65f40009c00fbc023502e0a3c78f60c0070e324e8d1f0020000130400b00e0c38793cc10c0f97112094800ce8f9378f4200901c0001430d2e36fe8009d1f3e6edc30000002808010a3890a2e6b16d5c9a479802395813f76e0c0408d4084d40004193afe60fa68ed972f9a05970c1dc80fe634c249792d75cea4acf5c1a4ec89a6674e1ef0d0a1e375acd6e08369439a9def8cd249aae375648d3d1c8e1f3cc091cab87163a4861e8cb35a7ff7e2a5f2492a59b62ac34ac7f7b8c1031ce946ea52418d3cfc800c8487911a773027b572795234b5718e3274dc056ad861a4461d7ebc0f1daf83460d3a98c69f4c7832713c07777b94f8c83107934a2da2c4cb93f41c8e1f394a7c9c1b373a7fecc0713e76e0f8b135e470387ee42843c78d1b2235e26018ad8ceffdbc4f52b21a709838b289dbef961a6f30778cdffd190f423c29df1b55841a6e30895813f2bd435a2aada350a30d46ad13f2e4a0fcce643b35d860ea3f7de288275ab6fb7a0d69ba7b5251a347ae8672d5659d55dacad89235a57e44e86f870635d26094cfe4347b9a24d487d20bd0604cff1816f7f2ff934f673085373337714eee93570d339842ac89a6f239c982eb653068828bee1dcd3af7680d3298abab72b217b313bed51883f1744e77a95454a5e4d50b6a88018fa246e549a3b1f7a81186529cdb55f40a76d1bac3cdc9d504bd4fe29ff0a406180c7afa53708232f9e95f2ed4f882b12c3f5eec9a93aeb43276f0a78c1d98e205e3760a35db31d652b31a5d30ba7dae20f497935c672e98843639889986121f69c996aec00835b660eaebeab85eb916d6cb2264cbb65c230b6632298f562b594145786a60c11cf652eece3a9d72d27dd4b88241278b93a6c3c4c3044ca861055326bdc57ce2565e12f601a146154c29a9b94c0e55499c675430759011ed3ab6b36941428d2998e34fd9a5a56e6ff1310835a460b47df2ce8d9347c95321428d28982b973c39dab3146a40c114f52c9d99d5e84bd71c6a3cc154397da9d1ebc4e850c309c670729375e64e5fd651efa146134c993457a73c9f30c1f4f9ad9af0969390d4a1c6124ca5efc9724d98519d82420d2518b4849993b4a5f00a261212ab1a49305fd6b69445ba3b70b80e1ec84a65500309e5b79f080da1c6118ca62642563a530d23a871d6ea31dee159a925f4b357109fc94ece4f7214a100c530f68cf020d28393fed2c4306727cce55bdf6ed86841096c8c41421284020cc3d8e19c7c9fc9e97bb4957ef8b841868f3c53006198e236f9cd4935aa43ce050886b9637a0acf7d0b000ce337d13d7bccbc00bf3028276596872d27a5130be00b7350651523eef39d30f5c2143f7dffe73d93c9a6f1c21ce32e4bbfa8e650805d9853ce1746978689eebc2e0c1bbacf5c9852f49f115ff117d652005c1874ea2443e54aa19a74dec2a0cad4750c15ab6486b630857672ac54717255c5c628402d8c26477bca7a8a7e926861263fd9d3ceb79c93cd340b730a6df966be76d4230ba35a9a76b2c6dfcf680588056f17e3ad6dc32b6c6ba5ea27a7578e25536a016061509924c2cdc91df7fe2b4c59663d67c7a7adef0a637c2a79293e9e8ea25618a47dce27f8796e86586192e5bd4d3e7d9e2e5f85995c41e9c993dd8f8f2a4c99b47527e7c396dba9307b5d56ceda57ee352a4c332a8626e65fa5bfa7406c9b7c2ae86a0a535898111dc454aa6829cce13ec72feb2c97901406f1eb7815c7bea4381985492855a3743d69adc94d4461784b2a9b9d6e120a734a7ab3db64b2c8cb4d4061d2f3eb049d1fc47b36f984719466b686d22c5d4d3c6110a2d35c2686c5cb4fbe13a63ca1c474554ba5fe3961182d117d6dda844198d69ecbf9d27790264c1bfab9ec7274f578264c413e418b9eae1c9f63c2b4667275e204fd417b0993c5d2de9e3217ebb58439550a5d9e64894ebaaf84312cc8ab38c1f3c9ee2961b20eb21f463ab1fff293307be98c52afcb2937918451e382c9f14ad1528a84d9ec094e8e16d6dc4d9030a578961c0b5234b1f408835a909b5ddbae8b3bc2e0a755ef924e234c1eeef2c80e23cb3d8c30933d87fd6815f54bbd08d368c9502adda408834e499ba86fb99f3925c22c23d6e266bb677f13220c4e72d14b9d3c84999897639c304d0c2b0d617ef2a58abe3072ffa542182c883db9c5c9dab14b843089103d55fb64b2782a0dc2a05475896af2677b4b128429abf43d69a2abf69340185493549a5ec87a9e00616ef12727bd2713d7467f30fdaf8fa54b7a2a457e307b93e4b2a9e75fd1fb607213d254e90e1f0c9ab4a36a72c3743f7b30ff293562af5ef4e5e8c13ca28496b11444479c0793d0b6266e8407f39e50a7562a554cbe8339c5d48ddc68b5bd76305fa5c86d49cb048fd6c1d8493c44ef988598743039f1efe594e5f3f01c4caa57611a1bdf15948371c684de12ffe184e26010eb356b7f1f840a0e06d9ee6b5f3a256db93718d4925e7f92d2f784dd601097733c1db43698edae9d9c4405b1c1a06add572ba8e659680d062db7922e4ed4607e52aed0de7a6d9a7c1acc379a307b26341875cbfcfa5a7b3ee46730953c157488bcee9cbb198c22ae1d9cf04d8ccdbd0ca6f0f05a51e33c93632783298b26c94c53edf7b18fc1b8bd9b9d9f5c9633591783e9842693b39aea61303ab94f4b7b26abe9d0c1607c758b6dd67ee236ff82495acc8be7eed795b917cca934419e26676cd6e55d30ba09511baa2ce5cf840b26717232b52ae515ff16cc2499af67b15254f0b5600aab5921e26be14e5930b65ab2b32f2768324158308c99ed5d1cf17e4157307fe9ca5dba154c3aa9e44e7ced2696a70ac66a152543849349a3a682e1e6c9d157bddf727a0a061517da39a8ab8d560a867bd3cf21b2b3f48d82419d1eddf952f98ea060366d2746e8e8134cf9ed69bfff4d987a9c602655b4a4f15d134c4167d8f513b4a713134c5d77eb95e5464d5b82e19b94c5e47abc5229259873ac676772932ec5249894ed25753a99133d24983249e52d0b2aeaae728e601ef3544f2e62339c14c008a6d22707fd9e2f8639f4ccdcdac891ef13c3ec99ecfb95acf3b7340cd356ac1d6fedd44d9030cc7655aa5b7430a542c13059a8509e491e3c38716098c9229621d3f244efbf308baaa43229fb3439677d6150523b7fbc1766f27607ddd17f63535e18543431c7c962fac5db85d1840cdb914e982f952e4ceaede5ce4939d99f0b53ddef5b9ca57161aa7732b9852929cfc96abe538a932d4c32942693c9ebc42d33d5c2bc4ebc247fbf82b2112db0ffae66614a293869b7e262998e2cccd7a4d036a74939b41d0b73967ad063173f5f121626dd4127bd1ce45798892e9adc1ee40a536f49ff0bc26ee65698ca934719d3fe996c312bccbe4d9e1df9c4f3ca641586cdd11e2d8e9600aa30d5c8de92e61f7e3a4b805498c64b45b75a2ef4bd044085e9747e137634e162de4b805364a265a96253183febc8fb2c4a59d8a53049bb7fabebfc1b2e294cdd39da58f2ac548fc26069a46abcebcd95a2308b1abda749ef7f2686c29ceec95d9f8969b20585499678c9b4fefcec13a69cd63f9f269e8fe53c6194f99c49dadb76c2f8e4b1168f1a270ce3224fa5890fa3acdd84c957ce4f9914d5a43561f610594e9296b3c39f0973b0ee74827d34bd13264c428fdcaccceda0a34b184bbce7d6ac2a95895bc2203627dc09563acd953037b1091a5bf952b69430e5eef49e5f6d9d4dc2ec253c2e7d0eb5eb48c2f89ddace833a12e65ef7552d592161d01d6adaa2d44567f511468bb26fe5c4babca93ac21c324293edaf520e3a6d84e964ff49e1bfbcc9396584b13a950a1dd43cdca78b30e91e6d7ba13a4f90a922cc2a4a4d9e6c3a29516922ccf1d3fe4be9cbaa4a1161d4f268ba3247e8b8e821bce42bc283aca8218ca67cdde747b55b9342987ca496878737fd3e214cf597ef4a8ddd8d3f085338fd653eeacd3b4810b59b081dbbe4098441ae53d0a5a53ac293803087987acb0915d772fec1e0273feb9977e9b7e907b3da134e7df8272d66f6c19c3f7a2594e55c9a60f2c14cecbc14af54ecbc61eec1f86b4df4f839e6f7a51e4c9a619d82d8cb0b5fe6c17cb2156c5645c67989073349f6e28b977730ce6d094f2ac413ae4b3b189ba084f86a29a5b4967530c50f39b91273f2694907f37a132e5798d06ff939986ed63f68fbeeb8931ccc6e4ad493135d3e131407635c972608bd155c84e060162bf11ca296d5446f30ed5e6a954a5f7a416e308ca753f2b4c5f34c4e1bccda6d3dda276ee2138cbca8744fa5425124108602a160200c0605d16f3300a3130820203c228cc6a20171ae91f30014000449382c4c362a22321a16160b0682812818088582a140180c060402a150181c12cbadac531b88692f60b7412702b5d3b3bf651b6948df1285a274497839b667a41517e85a268f2eef1fed614a13cec94c4608e29de38646d06b7a99e63bb41ab68d2dc0bda9d386838b05b68e9eaadbec4e94a67678042f1a6bbe1fab19daf0705c4a5b66e542c49e6da351e0f0f9c44796acba0cedca67a54200778689b86f9834e93d1bc285c133e99b729db4c73775b8213c9de859931e75d42e157097682e869b0818d9f9911fc52dfe22fc3b8a47183d30b0b8f8768e5c1f39d143c8c39bcae0032108f3c524d0eedbd0dd08547b9dd68de8cd82452237b02e1a4d0a70f03ae0636a3b5a7504cea3cc7a2feea0b87f366b862ad83997a94e24a3d32423a989a44220195f49dcba198d98695dce1c1d282a0f820ff283a24875e859af9ce5f2530b4d485420eeb1442d18c46aadc06d9b492568f221f9c8379960eedbb3369c00f7696b756650d4e9011773b103a9a5d48791dff0d0544f9b4b223c0e906cb245fa71cf8b63407c186838c609d2a6a14cb0b7228b1349dde4d8841ba2f1999aedb09a2ec05c382845ab63a8357a17ab3bc8feb52103986ecd34ed3e2d9d52c2cf0f374d10f187c1efa859f7d440d178c690779ebe5382f11601b8dd04529b227b43d640a90975aa1e62b7a20302019d5b25318bf7238d228b6e8030694917c0a4261f246358edd1de06e68660138aa1fe10cbb2d56f9fbca7d4d0a865e74a35956c54d8a715ea44db7b557a7a88fba059561cd3bbd3e97614d645492170f5b657e5fdafeb908989e063e64e52212d82fe8edeb6f815134a1fafc388fe6f5bc9bc52a24204ea99c9a892bbf4fce725230ab0e5e6f9cfbfb68d7b187fd328b18924fdf80ab9f28f2e1fbbfddc094b2fe2f04a40f2cadb1c2b1d23c5cec30ec0c1b115ec0d18d1139478384411359ebcf96429be95d26a3ded7e56e679f30ee1340442dd609c14d83454102861607a1645708e5fe73a2a980da26391373e940614070496d6d874fc1f08fc36e8fd8d5e4f443e045c0a1c6a7767cf058d11fcadf2ad09910f806ce2a95fe5087e6292b8c765cc05ee73b93ef1c51f30ef62aa1c75e46df55f68a5bcdaa9f1f2b1a5f5a8a8bf96fbbad88bc3eefa27fe6a9dff8ef3f79743f3fb9eb9d615b05958607eef30d6ab3b9419f821ef1030dd5b9d20d79224fb7dd4fb7f03cefda3a8f0f38260991a8b7e06e89a91a9217c4fefc8e93b7c4ed225bed027bc66160a27a6505edb81b1c1a4caa9d5ba36a94c92420aa5ae544b99aeca13e0c338df71ba8ccff521bf63538520c85df5bacecede91bf0578d87dd2ebe2b5fd68ff612f618f3b2014620704518d3a68407051c64aa3a42f4129a717e47a3d29c7b6ec03e0a7ff2347f228bd49611071a9f62d4de3f7fde47f112c7d06b01c516622b585507963d3ef71051639359853a39f4291a51722993fce68f4444890bb6ecf944cc5541949131b471075e09ef3d137cd488d94b88a5e2527776ceed3a9e38ddecdeaca101dffe3e056bc9217d4e20446dce0e88a184cad1d97d068070d797a7f154c3d22e0b7a50a4b69d9ddffe099b920c7edd6b94217f8e23a331b01b25fc03abac2c7dbd60b541ec19f611af5c1ad866bdd0b01f9eed125be48ef63559505e445d95c0f838b7f42788aa4cb216e5e7d989c4412fe2030d2ac626e8ac56c4f9acfdaf08dbd4235f38801df8617d27acd28e7d480e0bf549ca774b9492e9514c4950813a889a1a2beb4e710dde2d3dbe842d5983cf39e296a8b85c844962cb0b80421bd533a91d3ea144f8da993ba9268c58202a69939b0501ed09bcbc52d1d26c2ea911ba1d119a9452ca440e594fdc933cdd2642e99b8a813d9280237fe747014cfe405bd4eceeab148660680d16fdfb975a52ff8c5790ceb02ecb71162279e4b1fbf1c34b90587e4d06e7dbe64a8f5aea53d28c776634e4f07f95c5fa8ebfb04dd5ab391da26ba4b7124ef9289255a571f7d3c29c620ce95edda50321912459a248dc4f93cbd2acb746a55a48d2a3a8ff7653507425f9b6e9175b6fa54efa1d745c011e892ee6e9902d85e740cd62922f35635d222f0cbeb9984504a97ad5a93477bb3d6a04075fbde78d63e035cff9cbc65682353af07e59c5a90cc9f2992c301f95ddcbb8a9c154f994ad77ad5a3d3be659e041a3e2452c2039f36247a986fa70f129bf8aa8f6f7f5074a6bb4f81357db5546966967806996e635d01a76b7b9e44c0236fc59fda817a8992d220025884073515b01fc7446fcf39bc1b16d9a83bcbc6ddfd93326dfe161f87216719252f92e1e39b5d5a4de5c5a9e633b5b169dd5331e9618e6f4b860af1cf5f858d477b22a2fc3e1ee29dbf28b683c198eba57b622f86b8b81236afc6c01076dc94790e976b8d738d5c357972a5c16cdfe2b872c1bfb6ca1ca1cc3c6396049b82f103826b375542151ee1653d446a4e1814e951792e8c201f53ada3efe3d4e0ed8bae72859a1cca887c9a0a1b9f964e6b8d238ea38ed0e1d516d44483c962d03ca8f51b9370f3ae1c96366944daecca4a60e18d2742d06f8393b6f1a0f5bc429a7c06559380cc255b8e6155d5c4575ad488034f482505c4824c00b0727ff784cca767de700fce9beb21fc0d8ce55e9cd54b7fdc89d9418e411dc38ed82d9978847361be06dfa54e687ab5e0750eccb605f86d295dc98006e4a8a4d381ebd53e33b0e9c0321d0112bb27640582df48ce8b7bdc8ed49dfef20d8f8671b0343b00fb5db2ffb33d225ab98fdde6737d6167501759932d5386fff1e45537a8312c26bbd7ce2d4b62eaf994aa20b22b60cbd5099a1a34c38476d6bdfdd716e91144b864e6ea11627e00188f729cef13221ec79da10eae186bf855df01ef98a4cd579dd05891c3d1da70947e516f3bb3e58e324df17a63bc394f34851c504979598dbb9c00135cfa21d6cda989aacb3f03d418bdfe68212c0e27001f00f2fa4be8939d660c196f04594b9b77e34ee91f913f548c23ce308b427e2f9ec09373623dae22ba3b751b0e13e83d616b220972d77b03bd48732db081a574517cdda2131da1cdcf1d20be542417bf97a5279991dac421fb0791a3559c03dc3eec98ce4123eb6027340870de5e6838f6534d19f506075ac6f4d5ef532a28dbec4184478364d654d378e3b11e971adf9352e1495c3eb474a4f8810abe84e68a7789311f1013f5b9e232df1d083a8ab7fb3078a0971ebebe4e1fb83004a5882313e5b0d13c8782020ba851579ca80e04a7a171e88a033d5dc3e403c986392845486052047b08fbb36b860752d05587e1858fe70592b8f0983ef243322e8bffcd5ac2b34d1365158b966f477195dcb4ea4cc3f17e77bd76a8c03c796931e298c5b344e1ce0ab69eb414e049fd9b33acc028ff92e69d2e847c8503dbed96fbe104bfefc03975bb5edda2b91b57d96ce50344606104f9586bbdf4f0a4b77080da42e52c00e3f9add22cebdb52dadb06773cba51a0fcc2e72ec9b082e14bdec64a63939e93bc096ac606e218d675b69a767a9c8dd659fa736f000eb7e2d7deb6b75fb7616ad1af51f3be9472e17ac9509c7caffe2db262e45bd2806296da52ca5a6ac73cda821a3c4da4b5678737c38736ba7c863c28940b222e6dfdddc8331b465110ee9e10244dbea2d85c5a803a80057a8d1880e8575380aa0fb39331e30e7d430545308fa7390e83b25d171149b9725d2fd32d36be7acb8b9e04362734820eafc0a286ac0254f41af34048f9939b6dde97d82f93b5689769fb8c626482960dc9df5f93ffcce5033282c3422914ca1187e3f770687cef4619a54f12db44da5d729fa4e2ece8e08e274e4643bb35e9a8df0fea820a38eccbaee801f3198eae8cc27bb7cdc97f6178e88fda76a45064eecb8b490d46c9c5d527bddf74ea3240e166042606bc5007aeebf252e3f7d75a36f30f56887decfe10d594b3c4a8740a1f2039e9ca9e39f2c7a7fb8b015ff304dc105e4bdea6a2db7fbcb94d92cdf9446310914f00bb813911741823042d10643d457ff2b337781e6f8eed1029b7a8697b7c099196927aa00dada49ab74fba7013fd1cc3a76ee5365c9347c7a9fa7c22f71ec43ace22cbf38e9766148864b3f271c3ed702466928692696bd0ec3f8180140e899563b1f1a0721d0d56230c2a92370d369a9d98a3c872fb4b3c6fbe358125fe753dcacdaa40be357f92ce278dc29c4397bfdf3bb04e2a223e25f0f135b0af948f12add35631d7418cbf99ddbf3dfb59a107f9a9c7a14a71b7fa43dcee32d777027853b97caafa9245293ca4356b6c049b61f76d991f3de29ce63c687b7f60188542d1d30e9e0656ea6beaa88dbd253232b5c2340a578cdc727c9ff2e78857036c19da0049941aca95ac24638f891e320c041e106a42dbe56e4c083079423f8a1ee52178062d7175d7a68ae036967219c1918bba3ae76e1b29d314208af05555b808a46406ae9127307e749b177874fd25bb20ac1a0bf7ab66b326d015dbbb3a221607f679ef4bf94dac16de1bbc33c306a6ec4ec817dd595276fa2f45511b88143a5df063d6fe0e0574bae545f02a3ae17336c1c4e072add3082223ad3a37b6cd0da1553eb1ccaff0d18ec8f27619e100984f4efec4d397874e2878d5117e5b07a00571f7b8de02072e691de11955a162c48a686cc3568b4ea03b0b900ed6cd8f1ad45de9dda8ee04b4e5a0b5aa5aff31d81c750a2cdc8cc12dd06c3b722b3188781daf07a663cbc38800051f6d15bdf408df7b909b5afb8e51026191f8a118aa6104621d9a10c0abfc0dbd44109dfeb842c8f8433aa1afb5757aa9bf3560ba57699022e26b2a8e24f51998146f2b0bdbec5fe85fd74901602eb6935540578a09d490afe2d8a6846ecda80616ef1451ac3c8f8699476f55936b7362b25918c471cc16364d1925cdd52e812172820728e262d4d3fa674ea1801dda6d3ac7991443693d08be66ef0a064ab7bdc039323be4b94971e86f5b1840b7eb85c1b1f344c8faec8675096deb89ce31796782695211e3ebd0c1ec1f3bba319d4d1c345420f62c3d5dc23d9283ef17159c9a903f40543b915bde9dcbce0a95609080895c866e3ab30b4b96b67217fcde48b727cb205515e504d29b1d6b027bca65601e1e13e2148667a23b3bcca6d68d5f0bf7536877899a231b2d09d5e4ad9d6ae2a80dcca4cda1d4ecaadb6c0f4714bbcf8e5a108f5170ab8caad0594a6845446899d0c5c2df02e8f47c52e63f6ad835497c1cb217d7e1517c406b096ab9971b9a7380db854a80f94f0b39f2bf02a0e7b243c6fc133e053d171c2f5f0409a9941d4595db4c630a947a0b333753e55c045f0b41f825c7908eab22dc2a9171388001e3202362d315084fb25e9194158180741be0443e5c8a83d0c9496ddcb10b3c49bbd4a5ff0433a2a417a795a1bef4aa487f4cc4c9529335ad75b334756fb0b9d2fa12eaf8973bd9151f7436749d855466250420f0942fd1b0a4d271c42e4a27b4df01e3e9a2eb2e5b7b8153361d94861ffa63ed16504dc0e5358cd38652b59b13e0cca1ccddb2dcd58d07947696db6186c47c63f3aa4eb141dce7699f34906dbf0d6f04ecf75a830d3462943514370f1550e1d75140156cd8e272169321ad41afacc08b127942598587621df4a740c9975c8069aafd2da068ee8a3e80a7f0f164d9ef4b82273489fe9ad393713e4c5551bf770e8aa6e464720b83b63c8ae2a1c0a16d04e6859224d11b8e1731f2b0a8dba3528ea002315074004348f18d751346b7ad3d8b6ce4c777e31271efb46c57f4100c4d4e28b0bd4ecbeb84a80e2eee0e308c74d9684a8ad07e0d629cc9cd6da510a18239c27297104a586c16942957307f4b40c067583a4559f0d5731d0fd948346e39dc2a05b7a30eda77b03ea9619f0125094f295ca4b680efa01da16f01fac656ce83a027364846f0aca1ba32cca2a0ea03695a5d7213ebee518624527a1310e48cb30dd027733072ae79fbf88d5645130ebf4f8e8d30c6df35fcab95a2fcd5be26a9e3ffb29898680a8fc4adaa810e68adb65d48005a1d0c9d27d2b69ddc1e4cd704360042ef8b27046594b3250ca90a5074164b15f937714a9803823eae6148605ec98d95efc57d0e62dc87138809798c6c03bff37e44ac567f2ae30e27487be6d242d035623224edcb5f7d8ebbd4ec407dc92dd38d208c14bbf89fb252ad940c5edf08590dd2b3ab84c90870f11e4c314565facaac2dfb03abc4e5cb1bf20fc5ff3fc32841bbcba1bfe0b0ef3767d96f59013b9adba3c5e79af66498024316d6d6a5c58d7033e91d306abdf2bfeececf65f3b3c3112010dde4eab461473033187872c322da5fff9956d517bac1320b0f5d1ef1cdb996c31b71dcfa5082eacc8c6817d67657a827ad92a693fe7ec7c0a35dbb3c8ed9329211fe0753a037c1432cf6470a13ba44046f8609453b4425a2eb307b9a812601392f6adc6eaad7c1efccc0669406334b644c4958a987ec38a6acd178869e86746c8da24fb2fa0923ba46f2e7d37d8a7cee6503ae287832557a67e3ab3fe82e994f4c6f6a6cdbe15614a23271559fca73d5f9d908f2bab798f72ca1437fa2322d51e57e0c13d4ccb62ad9ba605f21c7b353ffeae87401129eb3a7fc60c2c015f7820480228b30f445af2bd321bf6886b7b8697803fd41fc433e0d7aa0df1801a4f936302a7c87fe3f311f4d4644038c2e02957b548eed4943c0c8c9e9fcf1260a0a174b14a9e2b1c37b83838c81b6f86401d1b12542ac2f662cc9f424d017933195c9aa836b8e9291abc4ddf998bb3b99403d9da8be6f08d846e16d6ef1e5c73154f627806f9040e9853eeff83ec602ae55bcc36627f8e2b17a6548c66d8dc4651aa5611bbbac0eb613b29ba18473106b787a76c154dcfbf9740f72566484d3a4782579520483af3dc0e2ec7df31c27c57c18d00a912393d31ca2b2bf28b9463ff780c4295920740eeff3dd258a104f886683f26770a2360fd855728d00cc0245780a78404c9944ceca5767484144af80b23893cd429d6468ce5d6ccd087464f83c862737e5a746e9ca6887ce779acbb1209f997da020d0de105c7c9852267c3ed1f576191ae3f20da0214cb06253833015567a22e0815c1370c1dc3832b4f1e77f1491c4b74bbabf85d051a8fcb39d1500cdcf6e6bc45ed3ee770971c66d8a854b00794f26550c9411e54c3840a063747504bff3cfc66b0c175c3809a2124f043c13c82b882adced5ecd7b56353a26e1c2540560cc4e1e01ccf9e0c8934e5e00a6ec13d6c9cb08c16705f4250724659587d106f289c6fa3ecc90e20310800b6323b9589d7e1d2b3f230c9e6ca02d019928b3c21e13f38541b87f2e608230a0e4c161c24048d0368e387c4d71fdc4409a2dca82494049a6f7c3d757f4494109dc99fcf94c6dd2ab2ccff61533f31136da70853ad0e1b9715e57fad703f31fe930e1c5cd0cbca0baf4ad53e4ef1ba00db088b7d1d1e39c469253adeb29ba459227418b068819eb96904a240e90f7b3a54a2b0a398109cb676b6370ab86bc4197400407a02a2e5a26452e5aeef08296481afaa0a2fccf306e5a9d88267352726ec7d38a438ef75b50234719b29f83277d1fd31dc778cd381a481a6bd198c3b2c3a5378b094d0bcb96a121680b55ca83cc89a1236ab8777220071be7f85b3b0ec83ea1e5334d1017fcc818b1f6411c85552d53fd3b915611bd7359b14466980b72f80e769cf6f9e9ada0bf5a949a8a3fb441c1aec3637e80dcb5834657cf1b5c5e6ca263e31121429e94f1d2566045bff99f87f5d64c4641737057d8061fc65109615be0e9913b80923ae766324c5c73d743f8788ae852105d0a9168ca47b1d6a28b7d9fdc87ecf2c84a98bffc6c75be7057151cdaf46f887ec85d30ba302fc5dc1deac9ebdb34d493303530422fa3af3750350f326f9ba76fde21ab798393796533fef51c3dcc2c7ffab00d941e1fd13f54a8806925a86e2b303abb94094defbd96adcffe01b6f5d9cf15396454b6216ea5b3832bf99703f9dd3ca0127964d772a7b83efbcb727df6e53e873780de8930f15c9f5d3900dfae38918345a121180794ae3a142e26b3ebb3a781858f551f1d76c6ffdefcc8e4ec797df6a85c7f5b84939af957eda68190da8a4a0be756e25950babd27b8b8cde606496f99c3b58cc1d820abc7c03574e6c981c6e7c798a0e755c8674004f113a401599e7d40b803c9a236d484643a262511ef46172ad7621a7b5c731a2cd9662a3ea7840d29a0dd3310424faa60199e15fc4c2199e6e9d0bf42bf35e640234ad2574934b1a39b75e09440d4a04788ae6e6b939fe17997060e904765ccde109d35152a54b1278cd0c174618edebbfc689a3b259a9e746d768ddf44f7ab9ec7a40b3af72ca5c5acef3508f7acfd19bd5aa5772a83b02ea45f22c7e52fc6e896ddcfcacc509a7bd1cf03c405cc1310071cd1fa8e0f58a7001cf3aabf0aa44017787f7ec1deab6396dce01392c96d82f47730c31d1b1a5e6f30e81890374ea814ad05d4cf331164d489826673570b205827c4588fee606480af3180d9b9a62e77008031d7f655cf502f4a17fbf81b41ad29d1adc38c95b970f4d44150f87b7b457acccacfc7879e23002fdea69e8b50449ace8f4cd17fe074c31062498396e862df39d14675c30d76f606472899665aaa20f0834f8f81c6886496fdb2aba6b240693921242b4c09243a02400aab791b9c4f7ecf8500f5ed2ce0db05ea5626d256a9e4b520edbf6519b21165cbc71fb4d364f5051c1f81f9b5b11ed298c6f17e4fb77bbadcd3dd2dddeee8e68d76eb387f7bb92b37d1d7b17f160c17072b9ebd1c141701c1d2f4b370673170a820f9aee32d73836de0610c25beaecf5e7ecb599a2d95a59ca5a2f650a8878b91b90fec89f2db4866069b846c5703e31a4c2c051b89a5b1d6832515f4a60a5a2780f5a641a488febb27e64faafd0ce686c779e8100132858a5420814598934a5cee22319b3d882a4ae879c7f0ab4436e072257b7d0b8bd1e3dd7323b5ef3211004d438824194d42227f9166641424140cf961086929c9ab72baba077b376d867835d88016c7fef11e0b026a6f6e0467bf237758043041095bad4cb98c3e09e1342b8601766994bb78d6ddf3f6164a5f6e89069827234e35abb7cfd8bae5210285fcd7e0479c8497968768a10313f3e80f08e765bc9d5e9d8a4be6e9ed5b0302dd676054b16bf6c8c7a14585b2d26ecd4f6a538118a3640da763829db730789c1d84eaff934d7987a3495c1d5e76460ba277226d0e3f6a529f45702d652378fa4de18e0e1a108f917be35f82409745d64d07766a115e801804e4a473220ad167c7eaae7286682b5df619c8848600e1b118fc8b9985dc9f5b983357398918b02330104d50f4168b1844c7a977f181857faf41def0f6ce046813ba1d438147959f72b1caa09fa1a5cf2a3c8b2ae38a845013f8ecff559629f1f58d78497cc13f7e4a4528cba4630731941a8c753ed663c113b2062cfe06c4e255fc20e6f2c6aafa3a87d6dd9c0c921f7023de467308366d322c1bc61336152c0830c22d54434ca76ac0571a89813dce737b69200b27078fa6080c7b5c29b379b9bea59bdad6ac79921012138b9ed38fa86c28a3acc1fb6dfbd3ab811e0357e6c67ed3576f8e948baaf83346a28ba29635f950941746f01dcb2a8d6e56cfa43f504aee8ecc545063160d4df8391406c50320cce01144fe4488ee33c86a31265a8705828fa18be1d5b82529336d90cfe408564f97a8816c70994884b52f5c4213931a6b6e754b543fc2d75054c7b8ad06b6912618e16eb61cfb2cbff56f731111c1dfc23a1be3c0d0b68cd28150e250a16713605ac13f5b10900c83ae1b497c3fc5ef555170026ae46a7d11688a246d6104bb87c0e965c542c421a0564c4df5245cfa4d01f9e8577e0a766a7962a26d3cc964252123dbcfc39846db13108a53fe65ed90fd882588169d79aae6ed9321adc9b9e2755e42096a237f86b2f150b5155a61d4d344e95b9e1b20f63ef2ad253db97943f1b81ca94cd6734417ec78cf9891714b867930f1c75ca229fbcb0b6d1bf2716d234999fb53d3748802e33ef4f166eb21c1181cf9b97e65081256bfe75b2146e41cfdeeb2d911f2a12018c14be73ed7dbfb571d7da6c15abae250da485c0b7e972d4700e3e016a63f0303127081fc465dedc61df735484b363aa7db74afc126858a441cd69d8a8d40b754525ed2c7d73c48254d7f1e08886d180f450d00acad96145274cec0b12c79bf7973f7710696d508b0a571063fba17b040bb5cb338264865bd12d899762e8135a7aa58b25483dc0bfb2baa9b1b83826131d352a8816bd49c00fe16db7d00c47c90ab4ca827c909b1c446286807bd2aef918344e770b433b06e71fd677dde02f6bf332765b64e649aae9294595165e60f4a0d7b297259f9094359a0e7454d9d93f4d141f685ed01374b7c79ee51df549b8204d843f4c40e4247b22da922aba2294318164eca67268b5660ec6ba00bfcbc79ef13b25179da7b4ac5bf3d60ecef9052f6148db2bf043d5e75ac3322759264fef4f182da176bd14b027ee908398f218ee0f2d2f18d0c464ad2e1935f048d4035818ab111b16c52ebfd648814932e271cac8a0102bedb1a12d5715b122bd6c12e94b78768debaa5c3342099535bf8556c265b89942cd19532b0e311822bd68acf33f0ee2af84dc5712411ae9efc8df36af4f2583ce404556769f1b2d894b36e13921c47619d4661fd91b6f18af8706318d1b048753e36ba127c71fb056a14d639a142661b77d3d270d5f1b1146b0576d6ba3ac2549b1c568a11f1a2d5b70436575660b5a81ce41e4a9c7b76458be1068eee86e76ff652d2e964434f004a001df769fd68c7669953b32d7502f18187c6fa6dc9fb9f5c668c2bf0dc913a6fd9dccb1fcc3a7c0976af4ac937a89f66a3cfba8c7cc39c633c2cba5ad5b7ce0efe7459f7d253411471c9e9c5e9e2ec981011badafb892451f4fad043a654d24047645a41c36cda479bd8f86a95ca8376ccad332a8772fa66e0ea60a543da649f241354708359e88adf9fc91159c8402bf32307317e6c35bcf05f74bfacbfb761e86ada983c36add1c0d31499992843787755dc55923565fc44cef91543f6a5fdc3c1db3c58e40a049e6f0514daeae60e9c98b9cf8ea732ca4db03dfb7c3b06238bac2a876613bbc4c0f7423e3166fd768cae92c10de2744a72560eb65e0ce68e130713be98d57cbd95b8f4e013825882e4c450c5659cb10bcfe382c6282a8c3bd7841903d5a3915643e0115a013b8acbe6c6dc5d4372d9d31f2f120634bddda39f6afdb716a90359ae78ec7a022ec31a5f04e2ed0573893202978ff39aa3f42b18cc79af1b98b90b1411a439454027740ba4039cb42ab4a729e0034f02a1e06a040714da1c037013b31183d93f8b3c4e59a7d2a245c82be4a9b7acc5eae798a7095cfb7140e4c590f14da3460719c35c0b4473ea659f452a17aa367812c8a7b068eaa0a4d80601b7eec2d1a3022e5a6c4de39c5b349d7cf0a19159eb03cc4150b41628c106809b4ceceeb9a8b115ce31e1e12fc5aa78907777e302a7c6408ffd8bd384ea29d2f39e00b68538a9ed3bd1b23b8f1fe073727a71881f13461125fb1969a2146fc3aefd0972ea87f8739536ccb1bdc06b94c9ea03f05e5fb5071c51ae8f6326362a2e19c602ba12811e9f220dfa4af617a073feb0c9507f6f50b420a73e04ecb4c59fae1ddaf8826d1bd6888a7cd59875e9ed5fd32eab98fd957f19fd0b4c0789afc055ffbcd3427358e5c6e3fbf22f03845359ab595df6cbb120ad8cf5d2810018630372e9b23663f043839e0bb99f4db4f14c9ea19568770d060d0ce16181374b163713a948929ffcef126dd34a5463c74ddb04868ace3eac2c331802b930aede61ec40345b4da63138eab74c19409eed18ffb702c9a064141c0e080751666a93cae337e3db34bdaf866f1ef8701a59e96250f2822d18e14e8a7e55c71c6aa3cc15e34a76c09e6ed920748694437320191213b70ee5ca14bb0478238faa7b9a23cbdd8dd856652ca03fb010b1c89223e7fa60da461364be6840eb6d44b0b82c9720d7d6fe8914566ac4c0d56ef4b2247e9a02629685e3e0011b67ce843ad319f878a9092c070e9dca2e665ff8578c3fdc96657ec7890cdd7cc36be3fefc1aec83fb1cffb18fd9600692707e1f0a087ab00b4eee3d12a87173006cf080db669b9c8d77f9220c045d88774ee45a6441515afc1213c6a6ec071fc544f781458f405309ed48776a06ec19fbb54369054ce8b449fe588be56f93d3d4d9b87bc8842fd88c8727fdb945f81887d7ebe891647721c6a20ebf0c6b309484bae5c6ea57bc90a0016291c71788132b5529937386def22bbe3ba6b886c1c8c33040079e01f9519f16b9376fbcdacb7e1b307bf60bacc6fa74bf5d7957c8c6e556470b351e823e00e3b2e4fe7ac584b45f24fa61ea7ca0aeb9979f4ccaa05e7a327aaa99e4b1ccceb29b595320bdcbb97f357b4f0e45b4a4c592d9da3d04e67160bf8a74dcdd973ffbb14f006e6903da37a0ceb89afe85d22bc0432d64c66cf861391dfb41e51ecaf875a86395b5012b26fddd3a371891617137610398c3894a75d7582898e65ef5094230d04b35b6a843f0912f7979", + "patch": { + "balances": { + "balances": [ + [ + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + 1000 + ], + [ + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + 1000 + ], + [ + "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y", + 1000 + ], + [ + "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy", + 1000 + ], + [ + "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw", + 1000 + ], + [ + "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL", + 1000 + ], + [ + "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY", + 1000 + ], + [ + "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc", + 1000 + ], + [ + "5Ck5SLSHYac6WFt5UZRSsdJjwmpSZq85fd5TRNAdZQVzEAPT", + 1000 + ], + [ + "5HKPmK9GYtE1PSLsS1qiYU9xQ9Si1NcEhdeCq9sw5bqu4ns8", + 1000 + ], + [ + "5FCfAonRZgTFrTd9HREEyeJjDpT397KMzizE6T3DvebLFE7n", + 1000 + ], + [ + "5CRmqmsiNFExV6VbdmPJViVxrWmkaXXvBrSX8oqBT8R9vmWk", + 1000 + ], + [ + "5Fxune7f71ZbpP2FoY3mhYcmM596Erhv1gRue4nsPwkxMR4n", + 1000 + ], + [ + "5CUjxa4wVKMj3FqKdqAUf7zcEMr4MYAjXeWmUf44B41neLmJ", + 1000 + ] + ] + }, + "sudo": { + "key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" + } + } + } + }, + "id": "custom", + "name": "Custom", + "para_id": 1000, + "properties": { + "tokenDecimals": 12, + "tokenSymbol": "UNIT" + }, + "protocolId": null, + "relay_chain": "dev", + "telemetryEndpoints": null +} \ No newline at end of file diff --git a/templates/minimal/node/Cargo.toml b/templates/minimal/node/Cargo.toml index 956efca34532dc896a8d3c07a9dc70611b0acdab..a2a999f02671bc726e8eed954b1f6026f3f7678d 100644 --- a/templates/minimal/node/Cargo.toml +++ b/templates/minimal/node/Cargo.toml @@ -14,15 +14,15 @@ build = "build.rs" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -docify = { workspace = true } clap = { features = ["derive"], workspace = true } +docify = { workspace = true } futures = { features = ["thread-pool"], workspace = true } futures-timer = { workspace = true } jsonrpsee = { features = ["server"], workspace = true } serde_json = { workspace = true, default-features = true } -polkadot-sdk = { workspace = true, features = ["experimental", "node"] } minimal-template-runtime = { workspace = true } +polkadot-sdk = { workspace = true, features = ["experimental", "node"] } [build-dependencies] polkadot-sdk = { workspace = true, features = ["substrate-build-script-utils"] } diff --git a/templates/minimal/pallets/template/Cargo.toml b/templates/minimal/pallets/template/Cargo.toml index 9a02d4daeaac39323773185b67448d00cdfb7bbf..e11ce0e9955ca642a226a6869786e808d5b30eba 100644 --- a/templates/minimal/pallets/template/Cargo.toml +++ b/templates/minimal/pallets/template/Cargo.toml @@ -14,11 +14,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } polkadot-sdk = { workspace = true, default-features = false, features = [ "experimental", "runtime", ] } +scale-info = { features = ["derive"], workspace = true } [features] diff --git a/templates/minimal/runtime/Cargo.toml b/templates/minimal/runtime/Cargo.toml index b803c74539ef7e2332f2a54b00d4112d86687dfe..1554e92c0bf575a3fd6d4dacb528bd63b31f71aa 100644 --- a/templates/minimal/runtime/Cargo.toml +++ b/templates/minimal/runtime/Cargo.toml @@ -11,7 +11,6 @@ publish = false [dependencies] codec = { workspace = true } -scale-info = { workspace = true } polkadot-sdk = { workspace = true, features = [ "pallet-balances", "pallet-sudo", @@ -20,6 +19,7 @@ polkadot-sdk = { workspace = true, features = [ "pallet-transaction-payment-rpc-runtime-api", "runtime", ] } +scale-info = { workspace = true } serde_json = { workspace = true, default-features = false, features = ["alloc"] } # local pallet templates diff --git a/templates/minimal/runtime/src/lib.rs b/templates/minimal/runtime/src/lib.rs index 7b8449f2abe482fe7d77e709ef647bf22e59af5d..5d549bf1a912d7be414b206d9cdf73ad75c3161c 100644 --- a/templates/minimal/runtime/src/lib.rs +++ b/templates/minimal/runtime/src/lib.rs @@ -41,7 +41,7 @@ pub mod genesis_config_presets { use super::*; use crate::{ interface::{Balance, MinimumBalance}, - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, BalancesConfig, RuntimeGenesisConfig, SudoConfig, }; @@ -53,11 +53,11 @@ pub mod genesis_config_presets { let endowment = <MinimumBalance as Get<Balance>>::get().max(1) * 1000; frame_support::build_struct_json_patch!(RuntimeGenesisConfig { balances: BalancesConfig { - balances: AccountKeyring::iter() + balances: Sr25519Keyring::iter() .map(|a| (a.to_account_id(), endowment)) .collect::<Vec<_>>(), }, - sudo: SudoConfig { key: Some(AccountKeyring::Alice.to_account_id()) }, + sudo: SudoConfig { key: Some(Sr25519Keyring::Alice.to_account_id()) }, }) } @@ -118,6 +118,10 @@ type TxExtension = ( // Ensures that the sender has enough funds to pay for the transaction // and deducts the fee from the sender's account. pallet_transaction_payment::ChargeTransactionPayment<Runtime>, + // Reclaim the unused weight from the block using post dispatch information. + // It must be last in the pipeline in order to catch the refund in previous transaction + // extensions + frame_system::WeightReclaim<Runtime>, ); // Composes the runtime by adding all the used pallets and deriving necessary types. @@ -134,7 +138,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/templates/minimal/zombienet-omni-node.toml b/templates/minimal/zombienet-omni-node.toml index 33b0fceba68c9257a6f5094f252e1830d0fc15e5..55539fd2086203508aadb4103454c42049708c54 100644 --- a/templates/minimal/zombienet-omni-node.toml +++ b/templates/minimal/zombienet-omni-node.toml @@ -1,7 +1,7 @@ [relaychain] default_command = "polkadot-omni-node" chain = "dev" -chain_spec_path = "<path/to/chain_spec.json>" +chain_spec_path = "./dev_chain_spec.json" default_args = ["--dev-block-time 3000"] [[relaychain.nodes]] diff --git a/templates/parachain/Cargo.toml b/templates/parachain/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..84b9d5e29bbe87ea03de987d01651d468f390874 --- /dev/null +++ b/templates/parachain/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "parachain-template" +description = "A parachain-template helper crate to keep documentation in sync with the template's components." +version = "0.0.0" +license = "Unlicense" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true +publish = false + +[dependencies] +docify = "0.2.9" + +[features] +generate-readme = [] diff --git a/templates/parachain/README.docify.md b/templates/parachain/README.docify.md new file mode 100644 index 0000000000000000000000000000000000000000..0d6071ddd95116d77f4633ac6ea73d663e60c204 --- /dev/null +++ b/templates/parachain/README.docify.md @@ -0,0 +1,261 @@ +<div align="center"> + +# Polkadot SDK's Parachain Template + +<img height="70px" alt="Polkadot SDK Logo" src="https://github.com/paritytech/polkadot-sdk/raw/master/docs/images/Polkadot_Logo_Horizontal_Pink_White.png#gh-dark-mode-only"/> +<img height="70px" alt="Polkadot SDK Logo" src="https://github.com/paritytech/polkadot-sdk/raw/master/docs/images/Polkadot_Logo_Horizontal_Pink_Black.png#gh-light-mode-only"/> + +> This is a template for creating a [parachain](https://wiki.polkadot.network/docs/learn-parachains) based on Polkadot SDK. +> +> This template is automatically updated after releases in the main [Polkadot SDK monorepo](https://github.com/paritytech/polkadot-sdk). + +</div> + +## Table of Contents + +- [Intro](#intro) + +- [Template Structure](#template-structure) + +- [Getting Started](#getting-started) + +- [Starting a Development Chain](#starting-a-development-chain) + + - [Omni Node](#omni-node-prerequisites) + - [Zombienet setup with Omni Node](#zombienet-setup-with-omni-node) + - [Parachain Template Node](#parachain-template-node) + - [Connect with the Polkadot-JS Apps Front-End](#connect-with-the-polkadot-js-apps-front-end) + - [Takeaways](#takeaways) + +- [Runtime development](#runtime-development) +- [Contributing](#contributing) +- [Getting Help](#getting-help) + +## Intro + +- â« This template provides a starting point to build a [parachain](https://wiki.polkadot.network/docs/learn-parachains). + +- â˜ï¸ It is based on the + [Cumulus](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/polkadot_sdk/cumulus/index.html) framework. + +- 🔧 Its runtime is configured with a single custom pallet as a starting point, and a handful of ready-made pallets + such as a [Balances pallet](https://paritytech.github.io/polkadot-sdk/master/pallet_balances/index.html). + +- 👉 Learn more about parachains [here](https://wiki.polkadot.network/docs/learn-parachains) + +## Template Structure + +A Polkadot SDK based project such as this one consists of: + +- 🧮 the [Runtime](./runtime/README.md) - the core logic of the parachain. +- 🎨 the [Pallets](./pallets/README.md) - from which the runtime is constructed. +- 💿 a [Node](./node/README.md) - the binary application, not part of the project default-members list and not compiled unless + building the project with `--workspace` flag, which builds all workspace members, and is an alternative to + [Omni Node](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html). + +## Getting Started + +- 🦀 The template is using the Rust language. + +- 👉 Check the + [Rust installation instructions](https://www.rust-lang.org/tools/install) for your system. + +- ðŸ› ï¸ Depending on your operating system and Rust version, there might be additional + packages required to compile this template - please take note of the Rust compiler output. + +Fetch parachain template code: + +```sh +git clone https://github.com/paritytech/polkadot-sdk-parachain-template.git parachain-template + +cd parachain-template +``` + +## Starting a Development Chain + +The parachain template relies on a hardcoded parachain id which is defined in the runtime code +and referenced throughout the contents of this file as `{{PARACHAIN_ID}}`. Please replace +any command or file referencing this placeholder with the value of the `PARACHAIN_ID` constant: + +<!-- docify::embed!("runtime/src/genesis_config_presets.rs", PARACHAIN_ID)--> + +### Omni Node Prerequisites + +[Omni Node](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html) can +be used to run the parachain template's runtime. `polkadot-omni-node` binary crate usage is described at a high-level +[on crates.io](https://crates.io/crates/polkadot-omni-node). + +#### Install `polkadot-omni-node` + +Please see the installation section at [`crates.io/omni-node`](https://crates.io/crates/polkadot-omni-node). + +#### Build `parachain-template-runtime` + +```sh +cargo build --release +``` + +#### Install `staging-chain-spec-builder` + +Please see the installation section at [`crates.io/staging-chain-spec-builder`](https://crates.io/crates/staging-chain-spec-builder). + +#### Use `chain-spec-builder` to generate the `chain_spec.json` file + +```sh +chain-spec-builder create --relay-chain "rococo-local" --para-id {{PARACHAIN_ID}} --runtime \ + target/release/wbuild/parachain-template-runtime/parachain_template_runtime.wasm named-preset development +``` + +**Note**: the `relay-chain` and `para-id` flags are mandatory information required by +Omni Node, and for parachain template case the value for `para-id` must be set to `{{PARACHAIN_ID}}`, since this +is also the value injected through [ParachainInfo](https://docs.rs/staging-parachain-info/0.17.0/staging_parachain_info/) +pallet into the `parachain-template-runtime`'s storage. The `relay-chain` value is set in accordance +with the relay chain ID where this instantiation of parachain-template will connect to. + +#### Run Omni Node + +Start Omni Node with the generated chain spec. We'll start it in development mode (without a relay chain config), producing +and finalizing blocks based on manual seal, configured below to seal a block with each second. + +```bash +polkadot-omni-node --chain <path/to/chain_spec.json> --dev --dev-block-time 1000 +``` + +However, such a setup is not close to what would run in production, and for that we need to setup a local +relay chain network that will help with the block finalization. In this guide we'll setup a local relay chain +as well. We'll not do it manually, by starting one node at a time, but we'll use [zombienet](https://paritytech.github.io/zombienet/intro.html). + +Follow through the next section for more details on how to do it. + +### Zombienet setup with Omni Node + +Assuming we continue from the last step of the previous section, we have a chain spec and we need to setup a relay chain. +We can install `zombienet` as described [here](https://paritytech.github.io/zombienet/install.html#installation), and +`zombienet-omni-node.toml` contains the network specification we want to start. + +#### Relay chain prerequisites + +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). Then expose them on `PATH` like so: + +```sh +export PATH="$PATH:<path/to/binaries>" +``` + +#### Update `zombienet-omni-node.toml` with a valid chain spec path + +To simplify the process of using the parachain-template with zombienet and Omni Node, we've added a pre-configured +development chain spec (dev_chain_spec.json) to the parachain template. The zombienet-omni-node.toml file of this +template points to it, but you can update it to an updated chain spec generated on your machine. To generate a +chain spec refer to [staging-chain-spec-builder](https://crates.io/crates/staging-chain-spec-builder) + +Then make the changes in the network specification like so: + +```toml +# ... +[[parachains]] +id = "<PARACHAIN_ID>" +chain_spec_path = "<TO BE UPDATED WITH A VALID PATH>" +# ... +``` + +#### Start the network + +```sh +zombienet --provider native spawn zombienet-omni-node.toml +``` + +### Parachain Template Node + +As mentioned in the `Template Structure` section, the `node` crate is optionally compiled and it is an alternative +to `Omni Node`. Similarly, it requires setting up a relay chain, and we'll use `zombienet` once more. + +#### Install the `parachain-template-node` + +```sh +cargo install --path node +``` + +#### Setup and start the network + +For setup, please consider the instructions for `zombienet` installation [here](https://paritytech.github.io/zombienet/install.html#installation) +and [relay chain prerequisites](#relay-chain-prerequisites). + +We're left just with starting the network: + +```sh +zombienet --provider native spawn zombienet.toml +``` + +### Connect with the Polkadot-JS Apps Front-End + +- 🌠You can interact with your local node using the + hosted version of the Polkadot/Substrate Portal: + [relay chain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) + and [parachain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9988). + +- 🪠A hosted version is also + available on [IPFS](https://dotapps.io/). + +- 🧑â€ðŸ”§ You can also find the source code and instructions for hosting your own instance in the + [`polkadot-js/apps`](https://github.com/polkadot-js/apps) repository. + +### Takeaways + +Development parachains: + +- 🔗 Connect to relay chains, and we showcased how to connect to a local one. +- 🧹 Do not persist the state. +- 💰 Are preconfigured with a genesis state that includes several prefunded development accounts. +- 🧑â€âš–ï¸ Development accounts are used as validators, collators, and `sudo` accounts. + +## Runtime development + +We recommend using [`chopsticks`](https://github.com/AcalaNetwork/chopsticks) when the focus is more on the runtime +development and `OmniNode` is enough as is. + +### Install chopsticks + +To use `chopsticks`, please install the latest version according to the installation [guide](https://github.com/AcalaNetwork/chopsticks?tab=readme-ov-file#install). + +### Build a raw chain spec + +Build the `parachain-template-runtime` as mentioned before in this guide and use `chain-spec-builder` +again but this time by passing `--raw-storage` flag: + +```sh +chain-spec-builder create --raw-storage --relay-chain "rococo-local" --para-id {{PARACHAIN_ID}} --runtime \ + target/release/wbuild/parachain-template-runtime/parachain_template_runtime.wasm named-preset development +``` + +### Start `chopsticks` with the chain spec + +```sh +npx @acala-network/chopsticks@latest --chain-spec <path/to/chain_spec.json> +``` + +### Alternatives + +`OmniNode` can be still used for runtime development if using the `--dev` flag, while `parachain-template-node` doesn't +support it at this moment. It can still be used to test a runtime in a full setup where it is started alongside a +relay chain network (see [Parachain Template node](#parachain-template-node) setup). + +## Contributing + +- 🔄 This template is automatically updated after releases in the main [Polkadot SDK monorepo](https://github.com/paritytech/polkadot-sdk). + +- âž¡ï¸ Any pull requests should be directed to this [source](https://github.com/paritytech/polkadot-sdk/tree/master/templates/parachain). + +- 😇 Please refer to the monorepo's + [contribution guidelines](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md) and + [Code of Conduct](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CODE_OF_CONDUCT.md). + +## Getting Help + +- 🧑â€ðŸ« To learn about Polkadot in general, [Polkadot.network](https://polkadot.network/) website is a good starting point. + +- 🧑â€ðŸ”§ For technical introduction, [here](https://github.com/paritytech/polkadot-sdk#-documentation) are + the Polkadot SDK documentation resources. + +- 👥 Additionally, there are [GitHub issues](https://github.com/paritytech/polkadot-sdk/issues) and + [Substrate StackExchange](https://substrate.stackexchange.com/). diff --git a/templates/parachain/README.md b/templates/parachain/README.md index 65a6979041f2b230cea1afc7017a3050fa3fdd1b..818fbcb693d1f157805c7f3c25c27361410d0a0c 100644 --- a/templates/parachain/README.md +++ b/templates/parachain/README.md @@ -27,6 +27,7 @@ - [Connect with the Polkadot-JS Apps Front-End](#connect-with-the-polkadot-js-apps-front-end) - [Takeaways](#takeaways) +- [Runtime development](#runtime-development) - [Contributing](#contributing) - [Getting Help](#getting-help) @@ -35,10 +36,10 @@ - â« This template provides a starting point to build a [parachain](https://wiki.polkadot.network/docs/learn-parachains). - â˜ï¸ It is based on the -[Cumulus](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/polkadot_sdk/cumulus/index.html) framework. + [Cumulus](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/polkadot_sdk/cumulus/index.html) framework. - 🔧 Its runtime is configured with a single custom pallet as a starting point, and a handful of ready-made pallets -such as a [Balances pallet](https://paritytech.github.io/polkadot-sdk/master/pallet_balances/index.html). + such as a [Balances pallet](https://paritytech.github.io/polkadot-sdk/master/pallet_balances/index.html). - 👉 Learn more about parachains [here](https://wiki.polkadot.network/docs/learn-parachains) @@ -49,18 +50,18 @@ A Polkadot SDK based project such as this one consists of: - 🧮 the [Runtime](./runtime/README.md) - the core logic of the parachain. - 🎨 the [Pallets](./pallets/README.md) - from which the runtime is constructed. - 💿 a [Node](./node/README.md) - the binary application, not part of the project default-members list and not compiled unless -building the project with `--workspace` flag, which builds all workspace members, and is an alternative to -[Omni Node](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html). + building the project with `--workspace` flag, which builds all workspace members, and is an alternative to + [Omni Node](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html). ## Getting Started - 🦀 The template is using the Rust language. - 👉 Check the -[Rust installation instructions](https://www.rust-lang.org/tools/install) for your system. + [Rust installation instructions](https://www.rust-lang.org/tools/install) for your system. - ðŸ› ï¸ Depending on your operating system and Rust version, there might be additional -packages required to compile this template - please take note of the Rust compiler output. + packages required to compile this template - please take note of the Rust compiler output. Fetch parachain template code: @@ -72,6 +73,14 @@ cd parachain-template ## Starting a Development Chain +The parachain template relies on a hardcoded parachain id which is defined in the runtime code +and referenced throughout the contents of this file as `{{PARACHAIN_ID}}`. Please replace +any command or file referencing this placeholder with the value of the `PARACHAIN_ID` constant: + +```rust,ignore +pub const PARACHAIN_ID: u32 = 1000; +``` + ### Omni Node Prerequisites [Omni Node](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html) can @@ -95,25 +104,23 @@ Please see the installation section at [`crates.io/staging-chain-spec-builder`]( #### Use `chain-spec-builder` to generate the `chain_spec.json` file ```sh -chain-spec-builder create --relay-chain "rococo-local" --para-id 1000 --runtime \ +chain-spec-builder create --relay-chain "rococo-local" --para-id {{PARACHAIN_ID}} --runtime \ target/release/wbuild/parachain-template-runtime/parachain_template_runtime.wasm named-preset development ``` **Note**: the `relay-chain` and `para-id` flags are mandatory information required by -Omni Node, and for parachain template case the value for `para-id` must be set to `1000`, since this +Omni Node, and for parachain template case the value for `para-id` must be set to `{{PARACHAIN_ID}}`, since this is also the value injected through [ParachainInfo](https://docs.rs/staging-parachain-info/0.17.0/staging_parachain_info/) pallet into the `parachain-template-runtime`'s storage. The `relay-chain` value is set in accordance with the relay chain ID where this instantiation of parachain-template will connect to. #### Run Omni Node -Start Omni Node with the generated chain spec. We'll start it development mode (without a relay chain config), -with a temporary directory for configuration (given `--tmp`), and block production set to create a block with -every second. +Start Omni Node with the generated chain spec. We'll start it in development mode (without a relay chain config), producing +and finalizing blocks based on manual seal, configured below to seal a block with each second. ```bash -polkadot-omni-node --chain <path/to/chain_spec.json> --tmp --dev-block-time 1000 - +polkadot-omni-node --chain <path/to/chain_spec.json> --dev --dev-block-time 1000 ``` However, such a setup is not close to what would run in production, and for that we need to setup a local @@ -139,10 +146,17 @@ export PATH="$PATH:<path/to/binaries>" #### Update `zombienet-omni-node.toml` with a valid chain spec path +To simplify the process of using the parachain-template with zombienet and Omni Node, we've added a pre-configured +development chain spec (dev_chain_spec.json) to the parachain template. The zombienet-omni-node.toml file of this +template points to it, but you can update it to an updated chain spec generated on your machine. To generate a +chain spec refer to [staging-chain-spec-builder](https://crates.io/crates/staging-chain-spec-builder) + +Then make the changes in the network specification like so: + ```toml # ... [[parachains]] -id = 1000 +id = "<PARACHAIN_ID>" chain_spec_path = "<TO BE UPDATED WITH A VALID PATH>" # ... ``` @@ -178,15 +192,15 @@ zombienet --provider native spawn zombienet.toml ### Connect with the Polkadot-JS Apps Front-End - 🌠You can interact with your local node using the -hosted version of the Polkadot/Substrate Portal: -[relay chain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) -and [parachain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9988). + hosted version of the Polkadot/Substrate Portal: + [relay chain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) + and [parachain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9988). - 🪠A hosted version is also -available on [IPFS](https://dotapps.io/). + available on [IPFS](https://dotapps.io/). - 🧑â€ðŸ”§ You can also find the source code and instructions for hosting your own instance in the -[`polkadot-js/apps`](https://github.com/polkadot-js/apps) repository. + [`polkadot-js/apps`](https://github.com/polkadot-js/apps) repository. ### Takeaways @@ -197,6 +211,37 @@ Development parachains: - 💰 Are preconfigured with a genesis state that includes several prefunded development accounts. - 🧑â€âš–ï¸ Development accounts are used as validators, collators, and `sudo` accounts. +## Runtime development + +We recommend using [`chopsticks`](https://github.com/AcalaNetwork/chopsticks) when the focus is more on the runtime +development and `OmniNode` is enough as is. + +### Install chopsticks + +To use `chopsticks`, please install the latest version according to the installation [guide](https://github.com/AcalaNetwork/chopsticks?tab=readme-ov-file#install). + +### Build a raw chain spec + +Build the `parachain-template-runtime` as mentioned before in this guide and use `chain-spec-builder` +again but this time by passing `--raw-storage` flag: + +```sh +chain-spec-builder create --raw-storage --relay-chain "rococo-local" --para-id {{PARACHAIN_ID}} --runtime \ + target/release/wbuild/parachain-template-runtime/parachain_template_runtime.wasm named-preset development +``` + +### Start `chopsticks` with the chain spec + +```sh +npx @acala-network/chopsticks@latest --chain-spec <path/to/chain_spec.json> +``` + +### Alternatives + +`OmniNode` can be still used for runtime development if using the `--dev` flag, while `parachain-template-node` doesn't +support it at this moment. It can still be used to test a runtime in a full setup where it is started alongside a +relay chain network (see [Parachain Template node](#parachain-template-node) setup). + ## Contributing - 🔄 This template is automatically updated after releases in the main [Polkadot SDK monorepo](https://github.com/paritytech/polkadot-sdk). @@ -204,15 +249,15 @@ Development parachains: - âž¡ï¸ Any pull requests should be directed to this [source](https://github.com/paritytech/polkadot-sdk/tree/master/templates/parachain). - 😇 Please refer to the monorepo's -[contribution guidelines](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md) and -[Code of Conduct](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CODE_OF_CONDUCT.md). + [contribution guidelines](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md) and + [Code of Conduct](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CODE_OF_CONDUCT.md). ## Getting Help - 🧑â€ðŸ« To learn about Polkadot in general, [Polkadot.network](https://polkadot.network/) website is a good starting point. - 🧑â€ðŸ”§ For technical introduction, [here](https://github.com/paritytech/polkadot-sdk#-documentation) are -the Polkadot SDK documentation resources. + the Polkadot SDK documentation resources. - 👥 Additionally, there are [GitHub issues](https://github.com/paritytech/polkadot-sdk/issues) and -[Substrate StackExchange](https://substrate.stackexchange.com/). + [Substrate StackExchange](https://substrate.stackexchange.com/). diff --git a/templates/parachain/dev_chain_spec.json b/templates/parachain/dev_chain_spec.json new file mode 100644 index 0000000000000000000000000000000000000000..e114204ebdc517431e0c7faffc11af73d020f29b --- /dev/null +++ b/templates/parachain/dev_chain_spec.json @@ -0,0 +1,108 @@ +{ + "bootNodes": [], + "chainType": "Live", + "codeSubstitutes": {}, + "genesis": { + "runtimeGenesis": { + "code": "0x52bc537646db8e0528b52ffd00589cb4052ec5c75e185310686894746ada41c31b10bbc208bc099f203dba4ea0f0df2c84da4097c22a2461c16f39afa1334274bbb07b0ddc6a3e2a1b6703863823c00bf144a3bc028dbdc423f48ff8e8be25ad114208d964efbdf79601581ba6172b175f42243fe4444be4873cc889dc1c8b008527508889897966623a1113d3b9c7dc2c6f500f371b1da109396801c771cf4ce48998c89bcedd3ce40e40c8cd19908113634082c9647a66c283bce9e620b707d8f50770b3f544149e0801ce52de890d96cf4c844ec484870b5d3e5fc7cf31177ae6cec3b3bc5900d707d8f5b19b2514564ca10ace4cf089783813a10b04ecfaf806f11c7421e0c4c23c9c891319ccc39dd8601eee0fb0eb9b6e1de6da51c7398eef9ce73fafe13f0ee43a7c87000420003ae8a0430e39e40004c89d9830101c70c0e1c78f1f0000c09d9830009e7d9e7752a9d4330dcf3a76ecd8f1dce399a7a7a7e7d986e78e078f3ba9c13c9e6f78fef1f1f179563dfba081863ba9c1343c03a954aa1b6eb8c1061beec4846de8d1e34e4cb8c7f3cfb3cecececeb38fe7940e1d775283753cd7f0bc8387e74e6a30cf33006aa8e14e4cb886671ecfd7d1b9931aacf3dcd375775283bbe71a7cf8f0f1f3f3d3757762c2dd73ea19c75afbbce339e7de3ba9c1f7b9e719478e1c77528373f4e8712735b8070d34d0e0e3e3c383078f9e9e3b31e19e1d3b76a452291d9d3b31619de79ce7938d8dcd338e6719140af5fce7999b9b9be79de71b3838775283719e753cdb9c9c9c679ee78b03c79dd4601ccf1d0f0f8f0e1d3a7676eec48477fe3b31e13fdb67ed743a3ddf671b19993ba9c132cf399e513333775283679e7572e4b81313cef18cf33c35ed4e6ab0f67c73e3c69dd4e01bcf39eebdd6da1b37eec4846f3cdb3c67366cd878463dd7c0c0dc490d8679be79a6a1f44e6a30fdefa4061f070e1c39393938383837377762c2372814cac6c646d3eec484b5e79ae7188ee39e699e4da552e9793e6fb5de490daecfa7672bcbeea40667cf32cf366a6a6a78cc3cc3d0d0dc490da6b1e1c6cccc8c8c8ccce97427267c9af34e4c783edbe8ae9898980af3c3692693897b2e9db66da3d16e66d204c3f44c9c6c98de8909d3e74c07ebebba64bdb1ac3ba9c1d661d7cff407ecfa4c9a443ccfc44909cf3ba9c1f34a295f4483e5d501764dc4831050256f1602761d2f8a087c3c7cbc43f19d84dff383626f41e083e0d190b7c31859ebe44ec2f0fdde7b37480ea0f6a0f9c0fd70028226889b562d59d28406c737a9387e4951c544710d241c5f4483e385c077e02ace3f22d093274f9cc0e125139a7782072e28a1f9274ef4972424a1283d42f35172109a77e207121292909429340fbf0317df4b709df4441af8078eef8d080e583eda21f903efc0fdc0f06f28fe07eeadc80f1c2f110f58881db8873be9890fe057399a7718be23500be51f0c3fad10f0864e8a50929175c7f5dffbc29d1425099f6aa6b4de97b39e3ea35683dd7cb51cece6dffc7cc5537e3e93367efe06aa92c8bf4ff95e64285e09242424240cef952bc8152917baae1116cb3abca822f0b7e8e177ad8b12f2b03c8a486d02cb3f2c71c5588886e21be7239ce5e38320b58d8dbcc32be71dba8450055943d57ba69650d550d558bea10a7e3e835b90f8ec6570cec7675393364bdc9f768b37835ab4306ad1bef706e5adb08b4182e0d6436378041f1c41d63a70effdac555c6f36b2d6c9c7ef12aae0fb0d55f1fdac7f701cecdeb7210f571cb87c7a3857fc4e03edc327a87abf812af807e183a001bae2f88ae34515918f9737dfa287e18360d63fb8c68d7b1b7284e1d57977e8f2bd2ea264c11c0492428d05ee6b50f5709f06aa1af75ba7efd071e7387a0f08142962bc29b8780eaaf2717cfc23d0132cf1f2f15d047a82254b1c2f6f940b617938fe0481a0504c7a72058ed7a02ae2f8780955eff1ad138dc8ba6182c0620cd835965b909bcc892db89f995e7c7eef5d093b08efdba27d18dac6cfc20dc2ee417b04dfab9235ce2a54e587e5fbdd40deb304e2fabd0d91b7664f937ff2f93df9273ff4fa3d3e9316e224dc843e9c356923deb8e0261ac459c3f019d4a49578d34a0d64c943c2443ce026546b204b9e17dc2406bfc347bb24e2ac49a4de88705a6f441e127ef14130b2c6f0ef395ec2ae9f455b61d7f0f1fdde8cc00fc55b61d73f7aaf87ac5f07b84e7222097752135d703661f86602eea426d2c099a9753287e11baa1a76b962f85c71bf2150c3147c3f7b5b11dc7b272149a95095add3c3dbf484c1599fcfd456d8c1572b61070f847bf861eb8de1b3a711b12e7d109cb66117ffb6f9ec4dfb30dce6b3f81a9634a8a2879750550f5fa1aa31bc651b67fd83a3afcfaa7df86df5368409020b3260d798de26082db2b44fe30c6ad436aef6e1ec6df470eba1317db58d336a8b9e0f4798fe6d444124ece2897098beb7201aecdebcf908cf4aabcd12e7f8a288adf716643e0866596f43ac9b25b67ebdda7c5c4f6dce39530b209195c0f50dbbfaa07859a21596af36dfe0f8ec69966d4c6de3de8c482ca7258a58beb720f24ad8bd07c1681b76efad03ef10873bc7517d520459bf29b84e6a428cac1f0ab84e6a420b6e9c390c4f0355b9c3f0f05d048a87bf81404f089e83aa4e42ea02c39fa00a62f8de82d08206edd3380846fb044bd6307c940b61795883aa3e7cebc03bf4833b87d13b7af05191f583d1604703bbf7893b0909099f60f7de3559d60d13041663c0aeb1845dbfb7200dbb7e10ec9bac7bb8f77e5f3d39785b388118588f969e2c7a6ad043831e2c7a74d063839e2b7a56d013831e2a7aacf45ce951414f0b7aaae8c1d293821e17f4b0a007053d5610d123a5a7043d1de8f140cf941e0ef43cd1b3811e2a3d51f440d1f3819e11f488e055e909410f0876a4b1438c1d67f420ed10c38e2e3bb4b0e3cb8e31ecc0c20e2f3bbcb0038d1d4dd8e1841d50d841851d51d821851d63ec98c28e27ece0b2830b3bb2b0630b3bc0b043093bccd8f1851d49d8b1841d61ecd8b2e38b1d48d801c60e29763c913a426a4b6a09a924a4c24881914242ca8b94155264a4a6903a232585d416525c525d487121f545aa09292da49e906242ca8c5416525548512115859413525d5265a49490c242ea0a2921a47ed0e120e583540f5249dd968e0a1e1bf094d1d980e70a9d950e09a9187460745c78b4e8929062412785ee07dd10ba2c3a1f743ae876d0dd2085a553417745c7029e11742b48b9a0834137829413a927a928a90e745f521a483d918292ba82070b3c62e0e9c223021e3278b2e0c141aa05a915f0d020554507821415a92ba92c78d0e00183c7073c4448f18007093c49a90da4a6a4a4a490524da438d0add19d81e70c3c27e0b9d261c16385070a9e1cf0ec80e7063c5bf0f080870b9e15f0e880078bce0c1d1a5d14ba31745ce866d081a1db4257a5334157864e0c3c61e8acd0a5d1c5a0c3c253059e0ff070e9bca4b6e001437782ee053c54e8d44869d191a02b010f19ba297454e8c8c033a50b03cf18523b48e9a02b42a7a5eb428a8b0e053c56e852d04dd179a1fb820e2874804007151d53747c4087077420e9d8800e293b58d82163e70a3b65ec7cb173841d2f76b4ec106167083b5b76a8d859c1ce951d18ec5cb1f3829d2c76b0b04cd831c28e0d768ab093841d1cec58d949daf9c10e10ae963b842bc66dc265c25dc2ade2b2e0aee052716b706970b35c25dc2d178c9b84fbc5cde216e112e162716d7067706570b1dc2b6e0c2e0cee0bae15f7ca75c115c20dc2ede202e1fee0fae026dd1e5c1e5c2eee16578b2bc545c12dc1ad724970a3b823b850dc2877caf58055e322d9352e157b86db015b86fbc4e5c0d58035830583c58255820dc382616b609360b1b037b059ec16bb858d81b581c581a5c25eb12eb02ab0565e096c0a2c0b5e0e8eb026b022b0536c073e8553e1607ccb7b701c1c8b5ff11bdc06af010e97c780db720ffc03394ac8112387063938886ae01c01e70bd3153722c071851c31e478c9be684d6848d91a365a6e6690d3054d0325277046c03103f7054704ae089c169c2f385e70d0c8a902e70adfc0a1d83871a2527960e30c9c0970a88073c69f388160e6cbcc194e526c98e15270acc07500a78c47c191c275051b65b0f105b5458e14385dc811414e14385cc809c10da4921172a0c0d23821d97820b342f645b6844c0959153227645ac8b29075c9c8c8cac8b0905d216b4206852c099914b230b23132246451c8a89089914d216302c6042c0c6c0998123230b227645b3233322e3728c06a703305e7050e183824e0ac9133c67be249315531d9c034852905a62b262c4c2c30b5c024058e0c70b094ba70d3e5060ba82e505aa0bc80e201c7011410505e5041c841e35c60373085e114861b3430a9a0660c1315364db041814d086c626063850d143632b099810d169b2c3657d8d4c08606a711d850297961f3848d94d3066ca2d85ce14ec09500670a3622b019010e16503ba819438d1830226045c0b46046c0865013849a2e6c52607302b805faa0e60b4c193567c01fe028a1a60ad6093723c0b0c8c66092c10e1264143aaa4813d4a440a2c064c54e0f5022a8c1e2615193a5660697154c55c8294c35306181d1029c829d8199818101230326064c0b5818302ed040b9a106cd139c2adc3843ce0f6e3c71434ace12728e9043841c26e460c9b922c7073931c819c28d2c9cb2385571438bcd941b42c0f102678bdc82b3841b30b861051b03f6056c0b5816302f181a9817b02edcd0c08d26726c90d3839c24e4d42007083945c8d141ce0d72b0c809234706395a72789003464e16394dc87941ce173948c80942ce0c72b6c83142ce0e72b4c881410e17395de424e5e420274b8e10729c90b325c78b9e2966d0b88162e608335ecc68a1238a9e2a3357542e6c3470230c758b52112e2afcc98d2fdc00c30c141916cc68404605334dc85c9169814c15325d645c70830c32556c1537d0b8e1059b854c961b629009c3e620864122a5a43879c1e9728a8283e2f481d39313074e509caa9c767032c10907a7294e5b9c52c041e19ed820034705470ba8244e8a8d354e529c9eb0a972cac1890527149c4e80f38453132728262ba619f078e0d40528052e0938637024b831861309b82e1c0a4a4ae094c045e19ee03ec099c179801b0137850b011705ce0a1c324a6694968003051b0e9c9c908982abc24951e2820d354e534e22b08152ba42e909361bc0d942890a9c166e5c4053e584064c14709260ea622ac34446ce15b232d04891a5012346a6064d14272f6466b8b1c2b304930b6e546012410e19304eb861c10d1530c904050d096e566022812543290aa5324c4d94b694b050aa42e90825304a652835e1461a37cac039513ac30604a81f94ac80e2a2f445a98bcd935218a52c9c3c50820227829218252994b898904a53e0a29859a3e4c5a603a52fa5317020e03650fac285859c2794d02879a1b48553149c2894c060f3011c334a4c388500d503cc8b921820074a6b4012947450ca025e812e802d80554016c015402aa015a8029802880238053c0134012c01ac02a580514028e008a0086098702f9e1660c880e1022a8a9a344c25903e7811646a702da52a4c62a89902278c9a27d44001c708273058314a51708a509a52e2c08e284e1a9059e470e154822d09395030c1c04483a783c865870b9983d317b019602cc87c6092627a0253a384046c0a1817cc0c18181812b02d5812b032d8c0c0e605d817d811b033ec98e142e3024325434da39484921630282e2f253270b2707541f9e0aac24546698c53076ca4b8ccc03880457179e12ae33a03eb003645460aee0857172470c30cb605350b3757aa166ea4902181e6816b08560c38605c25b8aa6853ea186a146a0f6a928d094c596e6400d30408460d186ac2003305182cdc5cb113841b2c364030c5e09ee09ae02606305430a1404708767260f3039b225833ec19a628ea152a19d50ab50a5b19766a9091c176a94bb869c14d15353ca8e122670d9309785c50c3851c3360ac90f3a5460b3548308de14ac14186f885520f5059c06401e60aa61bc048413ac103021c24a05090aa02334689075b10609e5083c691204f703101a60b3c66c8d142490630667450e0e0826905305c525a70542165059503a9839d2e6ab050d303eb01d30b6a13b40fa48c005385541172bae49c51b385ee8b1a2992073959e86a50e2e25a4204034c82e904305bb048180e503880e9c2a3842e0a4c073b3740a580670affe2c68a9c2d745e7c0d1e2bba235c61d80003060a355fc82923070b376eb02384d410de44ce1960cee009a393c286127892601ac10d2b6acab87941b7011c54e029030f0b78d2e8b2e048e306163c5b5228b881814d83a706300a581629227455e8c8c07260d345aa8b4e089b103a19f05051f38404822482fca20706b208d208f2077207ff22b10821632061205f205d20ab4829e40724955886680669458240aee064904f2494e8257e896b4434a2186217e2182217a4067490006a01768158804f804e80654028e88802c78054805c2013a0126e860b011a012e0186f1215c8d9701f6e047803c805b9c08b003d00a88c5a3006500b1bc351e17600ea006e007a0072017cf8c97854be16778633c28bc29bc28401014113d0e2079523c0fbc29493ca4d70493219638a00141306001030022010838c00f0a3040018a1080480f43840820871a7cfcece841834feadd00d3227bb8df298c0b46244c8698c01124864062812347b2178b5082e4c811ccc7e72604cd04c905865072047543a58b903041b204892345482a5073038246523444111326463041a204c9109b9b0f1071e4080a28219224c911022822024992244708a0e7c6033f375392241982882449861040072c50540125489270a09120a1c091238a8e1459a00246207192ba79a299243162882224451c418284023c3752fa080a2831e2c8124b902441b4c4103ffc66038d84082547283932c4112390c04888507284018a942471041144609c9b28451c4104922270dc4069264b20193ac284c912488a2839c208264c9024c0de3c69274620819720195a7204038cd81b27fa08922490247100208a4052810a0c0144cf8d069ac91011482c40c44d13cda448099225482ca0c488df202931820228d49726c208264a8c5852a40492234b24410289218e2c415244122392101a22e288244237a835da0912239a087182c488052020853a4333b141a9d1431cb94199a12f706488a2218a18a2084901605065c08011498630a2c8082546c8a0c890041148944012834aa3971c21000d358656720412491c29228911494ce08892218838620412b84809920b540049028a40524412498850628412050cb12409cc8312433341624491114b8eb040511243d84085a1951c6144d1104b9228e248d11149981c3102093cc49224b08ce9033d44c4114926700449d110451891648826a57e48924c608822254950e0481111489618518412238e1ca9c97195c0da2286165ee03eca3a090909bfa35843b12db02d1e12cf92dc7befe1e070ef3d4e460687e338ee8a93c628e7b4daa2742be5443867b609a38c55ce5aa22894f401d62ae935e7253bc6d930cabee6359bbe17e39c94ce6c66b3d639698e19e3c4b039e78c315e57d3a672ca1d51f66c487bce481bf675d5da978c3d3bc66b5e59d634d22925a53de38bdd13a3578c598cd7451bc29e3d63a4714e08bbbb69379c35462a67acb3d6963593dd52d2d9fd6677bf9e5d678c595625bdaed85d6ba518462b86454a2336a1a471cecccc6815b625a385513aad1a69a431d28a4953ad5726e345e915a99475c648a79cb51ba395c658a4ca6eaee98b6fd249e37c5a849376946f3e08b3293313e7abb39b62592df59c354a3963acd112e0d69633764f5abb67cfee186bcbd874bef8289550d2d9dddd1a6d9b8e594ccf386562ec9e337657d9b3e71523bdbace39bbaf3967c79e39cf14a3a4b144698c945248a98c924619639c13764b2963ec5209a3d89c1b46bb648a447068ec81079554768c0d1ba6db9a3bf1c6536b301d69ecd8dd1da39415abb56252caf7a49cf43434343426d39c3de79c3dbbad1863974a0de78c39a21c026dd8a0dd0d23edd8d69cb1299d16a554522aa98c3036ed1969cc2165a53da594154e78d5965885f5f974539fd93042181bc6ae9d452ae3b4e96aea8c0aa157d3be68103969b7a413ab55d29e2d3b769d93365665d339b318af189bc70068c4b098180ca3514a0cebae526699cc32292386c5586995b5ca4aa9acb162f492751e93326231464a05705d58c422166322966159c44c52c2c6228df38210ce572114a23737373793bef9669c11c649e59c53ce599bca96598c31c648e9ac73d207e19bf1451495b49bd2a651a2b61863948fca5a258d7176a5904689a915e39c51d23853d3c6d8b2696c2b46d8b223ed53c738e98d91c69918638cb17bcecedad431c6d8b25b42d83576ad97cc6c648d92be4863a5d214218db14e5aaf8e32beae3433662f464a4ae793f549f824ad5276d74a6b954da58c36bef7ba9fbc268d70ce07e79b33a3339b336273ce78757784b369c719bb044b31ce39e7a4dd539229f5c8d20e5aa2a9d841ddb3bb69a4544a3a63376c4a639c7176d3d9b3237c31c649299434522925c564acb146d952b6ec8eb0d63963942db15a2995b3769cd1ea860d67c7287bce196b775f52d2186337cdb2ac562863a4b1ca482916df75c18861586c2cd248298d30b4bb2bedd81dbbbb676dd92d65b78c35f66c3a69ec27319ac5ae32bbaac44cd5b22c4a6b8489934a4cd22823adf49ab5d696affbc558bb5bca18678cdd1d63a4b2d68ac5c7184d94ca3925a5f04d49e794323e08a9a434c6186bc4260ff3cd49a10e2a63ec76e84c19a45de5ac35a334523a679d01e0e179f0c198f9ea9cdd7352ab9b52aa439573ce39dfcc01080e3ade7c313fe69b744ea0a99a16a4524a2aa5c42485524a3a27f66e28d1186795b12d4ae97c3de78b7176ecc9418f0ee28d864b2c39e202482e406d50e128718493a21cd50d35fc460d3e2aa0c4080b3019c202d7da07922648921c5162880be090a9c011498e5440891115a8c011496ae005721a0992251438b2e488a221883822499221960c51c484a766c766888821269024c9113b6cb0c19183a3e6c68d5544c4104c940cb1a4e848911117b8c0115dcd45d9508204092486b8b1f19312488a5062c41125460c6101222840812370b48f0b1851a4248925f707058a92c039b50f24163852a4c48825481c2182891117b80140114896202962c911475437d470a4062443432499c01124462c29da018024498c18faa981098f8faea1e88823ba9ff6310492255d0d92a123454a2051c2a3a6084905787ab40f26454c8c5002c90e1aba06258ca000922548fcd85832049224441491c4080b1071a42849124350004911a91a728a8658824409920a2cd9d9ba86229030618264091217388288234b9034398208244b8e0cc1a40822901cf131a4baa106248840c2044867090a18b10449932338f8b8e1861a8a4072e4882224965842c491222590e468a2044984691f47941c61049325489a20c180114c8e50e0882396207104490586587244119323454a200140132548626a5301ec95758419191d1dd5363aaa46471313e2e81dad8ee226c4d13b7a47cfc848c6a31a85387a46efe8c8c8c8480a71f48c8ca051b4ac23ba3aaaab2329c4d1333a32323a3aa2991046cfe80971f48c8c8c8c8c2e218c9e911126c4d13b825188367a42b4911114e2e819bd16a28d8c8c8c8ee65115a28d8c8e8ea41046af8f2c21fa880a71f48ea610dd110ad14747474752883e3a82421cbda327c4d13b3a3a8a42f4d113a200120cafbbdf05980cd101f5d8737d6ff97ae9d9d3e6e6c333562a3d0866721bf25e9fcdfa7eaff541306e45b0d7a152fbc49bda272261d93e71029c446a029b9e25ce12976eb24b88da0b12122e8ac1dc8b34cc5d8b4b8a28d696682fa2987b6f458e60d3212e9db34438b0e99a25e2e1692693e976f328d94c6ad066ecdab3de8640ed422e36fd6d441e15474d60d385d036de6cd6fe9ed9fed07be3daf8f584b12e073b7a6960077ff1bc27691f2e3deb89cc029cc49d14c5122cc04ddc49516ce9f6c9b16ddbb669cfbe6ddbb6bd75363164599665d8af675996656f9dac0bd7755d57bdf5ebbaaeebad7375c9881eee5bcfda8a01bf0d77920cc6804ff6ddb4cfd63ed62bec2c48f4f0b34bded5dac7fa90c6900a473fffa870f2f15962cbfad64082c05bb7ae351068bd752c2a64f0d036ce3279b386e5b3d84a7206ac2e2b254d06864c2081610cd75c87926018bb4ab06c42f83921b40e2d7c3de433f92aec2484cf8214e9240c6f0ec2f0d935df49b893a098827bfa004b69551c9368785eceabb50bb2f9c94155252233a84d2be5db32074fe16960d79d498dfe7a507cf64403bb879b3c4c27f62048ef103d7d94371761999181b3470c716f44b0875144fa0b2a38fbc4b20bce2e7c3de6b4f2c18ea57e3d66943689861f1dfac1189d3673ef27d861d6fa65ebdf4654f14dfbf4ebed2b6fbd1aec24ec38394646643ec6f9193fa36d5cad07c17a7aeb700b126f3ec2d5d270d9b44d1eaeb6c98b9ff0ed235f0b102fa37db4fea8556b7dbdd9fa23d2308c90c20823fc8312762d9f416b3d7bdb1078eb19d4de66fdddb22122048e8fb79b4715325fe9b56cb63e142312d08810b83e08e68763bddd3ce23721f3f1d607c577ab9fe1162433f5e9c97b44dfd442d60f468b4270c83213117aba1dd37e1dc3ecc9d2c08eda260f377958d360bf86b2f4eedba7b53b44ff1a735075dd7a63a687b3de82f484e16087fd076b7f5b90171483e0b3f9e121fa77b861da3d359087fb997dd8b26f9f3e07553fb87f590e76155baf08fdcb4cdae71fee2d4805eab54b734fefbaf9085fd7ecb37f0f8a9acdd885dd2c31f6eccf46981fd69e0970d403dccd43bb19b3441a661bbfcb12451cdf50b8c8e14824b195519be7dfab45bd4bf4b0757bb00fb6ec0decfaf347d6a351de5b27ff3cf91b913fb65ee185ddf51354618fa7c1b2ece6ecd7336a35a8ca9ed98733ed6f6bd85dcf6c63cd16454c9f3dd82f961eec3a1a1e371c7e375bf13dcb36be6ce5c116cd20ef9e6007e9df6d1bb79e309d0eece2ad1f763128b695363f4cf4be7cf9f205539b8b22eef7d56007dfdbbc1cece0b5d3753aa86a1c2f6de31ba83a8ee7d140228effc19de3087bf389ac08aecfbd1cba0b200fe2989999b141dde0d4ccccbc1a708f482221f5174188a06c2e330b3a864546d5fae5f0ba8c81fbf001b5501f7b39c84bebcea15f0f29d340fa0c40da4b610d0402c170bfdf1838c39b8d307cd6700c03ce47589a01cb9be16d027105963cfcce71305a4cb54164c24da4297792175a7013ede5d0386688ad2dc8bbc2bd5bf0d6468422bd676dbdf75e8def49f81eed4aefa76570b25390751f86e00b7ef815e021e178f343c2bd19814d1e966df311ee37d1c3b1db368eb6dba7ffa864930a969f9602dcd03c0fcef3b9aff06cdac648aef48fba8cbf775f7a9f44f111a2976243e85dfe4d81aa1396b7e959029793e0f91f9c4f38f7150ed0424fe8df9b023bf90870f94db902965ff5b9980ac3143950410eb270414212a27fcfa86da16791640c3ec0020dac800ba127517c84dea5d8107af6491428645dca0e42efd44ad94168850588f7d50f3125ffb6d0bb0a8a2979ebaeb00041ffeeea31254fef4fc754ae387752d27bee2d2d860d10e1ca169e00032a80712cdf8304ae858ea2f0828b14a4c00762d4000c422dd4cf3f401c0949e84d812a1e2cdf3af20e3d2a10ca136447624a66046ae01bdc49239002ce4cf0dd4096fce0f83c6f3eca4c703e4722189e9728124d232c7b33d264e0699d3e1be11cffacab84dea124fd05153c2f3dfd8a5e3aa0bfa02254afd4674afa0b2a7028499381e9e533c5935e254d0696cf5249009e246179af3061d4a3093fe8d1841fbc9b95c0f1d5f67b73c0b4572a51fcf2055f41c2f1400ba0b0820499306a3284aef77369c7ca0cb42049249a0c7c05a947137e804403f024a922f51754300d341942d7456a3284fa59c3f3dd3974f7c89d24022970d1c3579066a4400327b2808424d49709a37a91faf36679331238febd372060a8200517acf1040949a80b6003c34f486390c5b74eee2d183e8bf6e14ea2520696d6cd47d8caa216a978f9f9015599eba02ad6c3eb00819e502785e00986b7610b520497397ca882ff812a88e17f40a0c30741a00bc1f7005515c3fbd04022563d4c450d0c7fd281aa87aff71430d1fb82b310386e427a0c1c9f454da621b5690696cfa646b72059c0f299a5512c60f94cd286a578530389b7dbe75d268b6b77fc6c90ee0743af06bb796960f7a890f11ede823b87113ca250f220eb6702ae93a8848127ee2410a0813b664b775ff894c8847466a4db741b5bc960ec0770bd11e0084b796fbf3780ec3d7b56c8c3f5d9878fdecba1c59bd3a28a4cf9f9a69c6fcea10e4c3a27a594d249e9e97c9f76e8dd9e2774712426c5acfb70f3e1080ff52532c2fd0cfffac63bc4bda46c463a6dae9fa776a89e3e5a549138541fdf386ad4c63f9b413a9fcd67bd68e2f74c5acf54cebf77b80579fd6eae87b7c9c4d6836036b567ad196d8eaf77681edea254ce17c1903554c93f69f3441579a7a77768febd7106ed12221c4c5f3431fcc948d434a822b2701388e9611db22eebcdefd6e139a8da82c013861b914ecaf3d66d32317d36a1a5ef6d883c7c10a4f6bd9be3e71d92703e4805045b702781e009ce262cdf833b090450c899511b1b76af61972586a736be42553ec2f0569b39df16247eda876dfe3ead95f341b042154ac8c3f3f356fc4e6de3a19e277091c808430984d681d3b0c49949ce172fe7fb5a43150a5e21f0f191fecd0fc5cb53fa0a55906a380806412d593f80cb4c20e0227d387ab7c24e5e3e33f9406484e5e5dffbbd09640db3c62d330daa5045e2972f387ea808e20a34c170fc6b02e3e08eaf89ac6b4f185c3cbc7cbc7c266594f6e1dee2836094b6f1d17b48b20a55f16de33fe005a681aadce1a8e1f8a3038e9f198ed701aae4e36fb82d70fc09aa2096cf15677859a16a08bebe21100753f12d6fae58c234ece425ecdeb386df3387e3fbbdf7a40f50017a427ded6df0bd098197b74208e1103c50456380eb24113cc10fe9f019d0d3da52807b4810779208a0c00fbfbe790b22dfb689c47d698970f908c777034111c1b07cee2660f9253f585e2213eee7fe82733c7c50b40db5b60f07dcfbc3bd119d88c3f1dec0ee3da42d4fb03c05ea2e909084fa48b82f3393842afabe13b89f7503293dcb8eb4976cb78f767316840862faec6688b3bf6b361b61ede152666284b3cb63ef0672bdd43ea59bb3c76fbf6cb7cf76b3f6f8a09811595fbe7c5903cb6b361f0979996d8cd921799b3c4c9fe921a68f5b1179f8c6d99fcd0fbfc74d087de999451191a76ffcb621dabb7d9e6689de175c5fb2a822925abf6e7ef83a66f3c3d8336af3c3f4dbe56644fe41d003506079faac33a8f536647b76b3c459844d2a2ebd74df464462fa2cebad74f3112e5db34d2ad69ed9fc70f66e20dacdda9300c1dd3ed9b307c5ccc6f8de4a9728e2b705a1df6ca68fefad883ced024903383bf6fedb86bc2f58bbdd3e39fb7c76bb795c8fd83b52fbcc5b36c33ff8a3eb4d35b25e020d2c635ce22161da0132307d7c6682efc778186f86f277085ea2f705472bed10bcfcb3f1110b7c847f9f1b117878898e9030aa883c3cbcf908cb370e825947a0151620acd7c3bfa40854efea210959f72535ee07718d1f7e60b44fbf630adefcb4e0fe4b8aa97e7e48b8e7a51d926f4b038937cbbfe7adc84322224f6f8e971745047e1e7edea1f88784e163bc6f4bfbf483a0113c92306a21eb25b2ce00d7491cf0828f901e12c6de1168e6f03502c91c9e8b40a7c3d344201b877f048a397c1781600eff1381b8c3ff8840a6c367bd0db96ede7ebdf46873fc64a1e09cb350ba43e1fec4138272df739385627328a89b3e63a1a00e65e63b97b150e2a1c81cc76d58283c0ec5c67bee2ae73d87b1353a77c5837acd5dddecf8ca5ec7a13cf16cbe6353739baf52b7d9f9cae63d0ef3180ba507c75dddfb9cbbda71570078cd8ff7fc3d5fed78cf5ddd9ae3dc15ce7b0e25de557ce7f378573aefd1d1f92ae73a777573739daf7aeee3ddada9398ebbd2b92b1aaee317c7ef573ac7715735aad7f055cd6bb82b1f9ebbe2f9dfe33c77e5f3d7f0d55fc35df5d8b9f73dbedab9c1faf88f8efbc4cfcdcd69d0a1e32b1cd771573738dc107d624cc59a1d3b3d3a3638f7e2b03a28a09a9b9e9c1b14ce8e47950ae5e3403b8076542a95aa47a5c2c1c606c7b53b3a37efd1c1d9c9b9c9c1b9289bd46ddc517383c2c1b1430584a346c7265ebb93ca51a97650f771a0d873a0ab542a150e3a3df7f11ff6070e20a0af520702da6193f2711c6c70a8f9f1e3ab9cff50fdb8c94101f500451c70f80ae738e080a346a5ba8fffb8d9f11f3a2a950ae82b1c2055cf6d7048e170205fe51cc88f1b7e7c85e33f5e0d3f7480806e7c5cf5739fc357f7393c201cbeb2390e770584facf57f6406aa88186af704e838fffe8711acee367e740fee3ab9eff7837a8eeca061b7ec35da96ef82ae737dc950d37bc86bbb2a186ffc0e183c75d01e03c3c3b70eee34034d0a043070f1ea78186af52a78186af6c0ef41cbe8acfe1fdf81cc857a8037900f80f90e7f095fdcf573aff61c35dd1705734dcc78786bbf2711f9faf72eee3f355cf69780d3c78fce7aebaee3d7abc86f3f8398faf74cee3aebafbc4fb0ac7bbfff8aae63f9e0ae8ae6eb8e1aabbfaa1faea57ddd50daafbb8ab1bfef373a0afee7d7cb5f31cee0a071c0e80bbf2b92b9ff3d070573c78dce7ae68380fcf7dbeb237884e59f7b92b1e3ee7f11500fefb43a7ac03a9e13ef1d3e3ae78dc158ff7b8f5f1158edb43a7acfba8e13e1ed429ebffeae6bf2b1c72fee3ae7e1b70bcbbab9cdf55771c361cc757f138ee2ae7a7e1ae70dcdaaf6878ea0ad129cb3ad05da952bfe1ae7cdc958fdb0080bbaac1478422ef0a0b10f1392eefe32b1f77b5e372309779cda5e994f59f1e34dc950f771e353c5ef3158fbbeab9e77efaceedbed2b94a74ea5e24523befee4ae73cf73aee6a47e7e6b82b2cf4f7ae706ec77ce63696e93872706ef315ce5ddddc9f4e595f996ee3a8fba353d6639ed9d816a2f621e11adb4296cdd7fb712bf29e1f12be0e37214748f87a6f42aed707411b7be33596e696d54e6df6a0180483208d65028bf516ba6199c042645d260fd33339c2f32d54b2b97ebb28eb0f094b249c69b6270c2ebb76ecd9d332fb90706f05e82f38bb43d96dce2e8ac87bfd7bbd44a822eff5f5e623fcde1865fd0809af90701fb33d4be01e126e5c7fd9c633b6630a5ec6d698823f592ea6e06d589a98828fb18f297818dbc5143c677f620ade647fc4147cebd43bf49070e730f239b2e0cf10b2472028a64379e2fdefff442028d6a1cc9cbeff230241818752f3d2fb5d0482120fe589779af73b0241a9b9ab98479b43c1f1ecfd1a81a0fc40b9f19af76922109499bb9ab9c9f41a989bec67be9ab92b189dd77c3573eda643897755ebb9bbe26ec5afe25dd5fbc4fbaa3ee730dfdee7221014ecaeb043f8ace6aeeead1ce739b45ef3157ccd5dcd79eeaeb0735fd53c3b14799bbb3a9d1e7357f1d75d5db7d957f6d95dc5f85576d46d70dcb5bb9af3d85dc5bbc239762e9efb4a3b7757d3c657f3a9ef7cc57de7ae4ea7cbdc95cc2deb397775cab98c25f39dafacefdc55ce1573beba74ee139d8dbbb2711999a36cc8c8dce7c0b0e7e06058865d56a66606267216bcb09958339969b25383b506c604abb5b8c861964c36b5999a996a4d3013bbae9871960c8cc6cd5c5a0d8c052bc44c76cac42953132bcf0e2e83269bbd076607e3e1b15193c52b95fa0a3e95ba6666ee3baebed376ecf80afb8e1dd6b24cbf4f69d853b3ebbeb2ef78ba6a92e17ebfa306e63bdef3d5cc7b52a9afb2a77652eff9ea9a3c3c99fd7d3765de411e1e9e1d5f59df71573c3c30f13d3b3b385f69c7b97e9fca394e8e999ea7beaa79aaeb747ce7aebae7d879ea2bec39bee2aece6dd88035bfe7c1c1798e9c9ca38e93e3385fc11ca79ee73cbe329dc7fbb979cf57a6f73c9de7e8398faf669ee3abfa547773737f93facd57f637efabec37df41a19e23e7ae70ee0ae7398eca39ea2beda8f7d5fccebbafb8774f07cf57d679ee4a07cfef5de9784ecebbafe2ef57d779dcd58e1dd7b92bd45da16ec3c66feecac6e6a8bbbab90d1b477d2573d45dd9a06ef39576833af5aef39e1c5acc71eecae6ae6c8e734dfbfd0afbbdab981cbf5f6517894ebdc7bce72bec2ad1a9f79ebbda719f78a9ec3b7785613fddd5e9d9ceb3afae1b44765758f69bbbc2ae5dd7beb2f7874ebddf7c25737be8d47b77573c32d77157f7aeee77ec75ee2ac77fefaab5f0840c5e7c410118442045c82661035020a10c5d300194240841b10faab9d5ded5ef676e77ea1dc7ed98eae7e0d4fce6ae5034b7b9ab232d6c60c616315c00832fb410aa393a81069e10821f38810c62a840a8e62b984b53735734b7335dae53ef376e8da9fecc57f15aa053ef337725739f784250607eba2bd36ddc277e84a0c4c7dc154ce9a6bb3a6ac195335c69411a686cc10b42b1a1e0c5062c08c109d2f8011984a0c49bbee2ee8f4ebd9beeaa747f58f7a753efdba58157884ebd6b7795dd273a2128f4d85de990832db830033494e0250642d77de2425070f8400c37e8c113a294c10c2128f0d657f5769d7ab7ee8ade9f98eaafe67da7dee5e562aadf374a3d1f2e6459d61bbc4336c7bf67415095b5e7937cff07a8cadacd2b1b04c10b5f98999991f9ccf38debf0e3c79dd4e01fb7e199876bef0f00aa5027ed4b4e43de0702aaf2cccd191843194318838c8c8ccd659e67bec30e7762c23bd860c39dd4601b0e800bbddf03549d4ea7d39de4e0d383bc1f045465999b31b0021f38c10c1b1b1b9b9b9b9be71c9e7b0000007752830170fcbe0f50954f37e7dc8c73338e9b4f02781f025095836e738d52ba0414337c298312654cf144cacccc8ccc677070dcac840ba8f002858c8c8c0c8ed3cd5610a21042176c9e651e80e799dfd8d8d83ce3f0ece3c3c79dd4601fcf409e69a8a1863ba9c1353cdfb0f9cd56153c80c205ea031d4c010c730c304081041c1c374b3154218c22003dcb3c87e799a38080eec484819e7f3cf3f8f9b9931afcf37c236af152021bc416fc800c37803ba0820b5e3c2f44410c29373ccb1c87e799ab9e6fbc238c4003523c276090e4e5c84a16664061020b32a654d1e359e600789eb90dcf37a618614b09b4a0010b58b012810ca4f08281a42c7861049f6799fb789e390dcf37a2f892042a7246a0c509507094f840074420039465032ba2d0a2e759e6ddf3cc793cdfb0a156bca08c306e726e9e25e0628211e4e0dc2cb3400328a88183ba197a39e30759a49e65aee379e63b9e6fa06c6e865c8c1105149b9b9b1f15558081859b9c9b552f10421272706e66024b0a5630841ccf32d779be8183ba7995032412800165737306ca70852f43b8712226376c6e6ece00d21424689939119399dff8e926e7660cf8600853b670e3c68d99df783edd3ecb3ce779e6f7f9460ccecd4bf8800c2b00636666668643dd6ce5a08c103c017522262893cdcdf30a3f50c2183627626273d44fd7cdcd5209603c010314ea4e6c30ea9989cc8998c8dce6a8e7d3679e657ef36cf31bcfa82ce666d8852ac4e8828dcd9dd8609b6726a71331395de63695bb195aa90110bc909191395dc6d24c37f71a51b410c2e9743a611afd12edbad9e80c64004194e7eba767d36d9eb9cb3cc7d4d06b279a5f42af69da9de460edd9cd46556c41093eb84ec4e4d26eb6314f4fc47dc93ca594bedebc9ad2852aa0613299ae9b6ea637c3cc39e7bc75f31252f0e2812bd775d55f37cf9b33ec6625a294c10a6bd45aeb73cc699eb1d3e7cad5dc6c4541861da031001b374f2b68600620d41331a925989b211542e8000feac665373f283c8083266aadf5d97af61c731bcfd86b9e2b11cef4259c909b99484ac112aa582762623de63d98ce9de8fa12d3398ebb931ccc7d003763400c2f70b104ec444cb05b8fb999bb59c875d389e4975c37994ca6976e5e22046b40a9c2b2eec4065bcf4c624ec424e6d8ad9b4d370fe0790991fc75a2215f227ffdbaee24075fdf6e9e48e882055ec0b03bb1c1d83313ee444cb8c71c7b36bdf4cc7d7b8e797dc66ebe6ec69e9938c16c607927395806f3154311c9472ac0f0f40518de25c7f447985e987576694c608137c27e6145b0aa5df3c980eb242fb2c0514802b05f9789ebd815d042d7c5023fed86800df02084dd2ea6e4b3cdc80ff51be041e8faf5fc2053086e45302cf119dc88c0600dda79ba1181c1f1d9a541cb1dbdda44efcbb4dd3e4f8ba5ac003c6f56d21e7c102c0004707e008eff91b0d220eb87a531088db0c513b015c10bdec19d54042d45c82293da85fdba3f8f86fa20580587fdc2ee84c32e8a084442c2f1464848f87a5b1119dc44e224d7513d34be6e11ce649cda2504227484bd95604c596682818131c53ce34c0f823130a5125732954a968b8729a45d326c83557b17c10aa601771211bc7021c296a8555b352bb8ebb562ec99d434fb30c442a856b327d8617f2fe0aed74fedd633b7f9d09b0fd90d7a34600f829ad50176d82dfbe347041a004c61c7ba0b8cfdc0d88f01c00ea3c1d87377a149a842652f4216dc4945b001ee24227881ca2e11011af7f070c40f57abd69abdf487b752c974b311ce6a6820d9b1d344a098637f047a42d8bb08c41dfbcfcb41db388bda622c6a8b89e1ec13ca6c5cd33218ee41d046cc456926138c29e34c1917ef70238035f185dda1de42b76435839917983e2260817d702711e10946236a1a077126356a2f2b6157fface0acd3c7ffbc1ef53c6057dfc1aebe3eeb22900d30555fb30db876b8b2a0b30176b509375f9f6dc044a0da450421e0f862291f61149188b1478c1d5a14ce2c0a1ea5fda1b47b450a0f42db3b890841c09d44842ef085dd6c84afdb40aed7d708c4bd9e8b404fa89e2602955eff9703f6fa0cb3193b2abb132fb01323e09245657f425088b3a8ec1ea0728ff284b83f7b5a89c9c357608ed9ece4081893b8b7203f981e044b0f8230d68927c45d1476894c5ff0152936086d77c2b486509427b4fd49355d27aced10f78680fe92069213fd250d2424a46b02475e4af0cec5fbdb0ac0152062eb751e4d38c5c8f0cbe1e197037c2ee177337d06ed50fcc35cf002e17c276939037792962d60f87a73120d5ba79695b8832a7afa31b464a1c50a2d55a4162d1bf10d54d11faa28fd692302b1a414d35b0598d88262d0f2045f2e5b300edc4943c882876045086b609d8ee6e611bfad08bcfc0d54e5f83b54352d50e0296dde0a100fafdc0a0023e6a02ade21fac633494b1418d3dc1c62b8c9735035f4aabd1c3622f0f1e1f89703c4b1003fb8dbe74528d4c0b30bb8797006be183e33113d2c8bc4c39b93a0c0efb02daac8ec027e8f76083e3339c2efd212f5972f5f70c34e8ab44d64b0849d9d5dc0cf3691c10dbb782d8637c36722eff142dee3cd0f2e91181e5e5491778b32dc2fca700fa6e022b8a4e2f7775144649619e03e0c6eec82c7051938fee480f7e04ee2220bdca62dbae0f88b26e1f9eb700b319e763510faec6f33127ffd6d3d24c1e157c461ebfde09f8b5e0f6e46ae5f17845a707cdc881abb8c0de9dff6faed70cbfc3f2b9c757e6a9a162bacb97d76db505c48417ddb220d1c1f37ad374dbb963db376d36c49cb329c9c1331e1e4985e966ddb9e6d0f82a852dd50af47a12e1b38300c1c140a8542591c8ee27ce638574015cee7b3130e544c038523db6c6812aaae219b5fffc1d9e636bf1e7fc5fc08439c5b38db6fa0ae802ad4e7ad2d4896693718769ccc2e2192e53c339dc876ed594ef6cc74f336050af51b4bf4308a5ec313067589da4ef3edb093f869afc6e6876dde9b9114de6e636b6a686ed8590576f33336a2013b189b5738c6e61567f3aa64f3eacd2a50f5621a78fe45346018f86d17fdeb815ad81573fbb6bd3720fe87c29d84650c5c54e1c28a2db2d8c28a2daa649ad69ba6dd6afff5eb77754dc353db9e6ddabb02aa1acf6fdb83a0a65df5a5d7974a9fcf4aa5d255ca2eedca82981e04b9cb2e21725dbb340dbbf6ebfaa561d9b3794e8127872f93257a5aa9b4fd7dbbef0ad8cdfff67a1054e2bfb77f48bfb5da9fd5de758b855e7b6f6e41e82d7a3885dfa55b6c05b8f075b313b803d2b3bad13f9b95c0efb74c18d9ff4842bf4fa25ce8b72d444f9fe7145a76f38bc7e614bd15a0c3708ca75b4055fdcc730ab80576336b78be2c78be28dce19697653e2cf0e4fec6f3ef8aeff04fb90161ffc70d887f75ff6b7f3ba668120a766864bd441a98152c6dac2e38dec6668851df50ffc13ff807f7867a1084364be0e87f708698fbdb8cd83c43ac41eecf6a371f4121d41ee16751df6cb639fc761c3870e0889b900ee3786f40e07816351c381e044da61c9b93939373b805c9d9b62ccb7620068e8fef6efe329c920dee242c4938b3d16ab80fe17e849fe56c7ef95dfb16cfd0e89c726da4c964da8264df7086d2def67eb319601a60bedbc37ca9aa81ddfcc66f7ee3e6e6e606bb390ececdcd83e06f6e6eeacd4515c1f1ec38b22c2b955e1536b7729e3de7c22e6e9e5187cf3621f1a8876f5ee31938733392ba719cdf384e8638d446569a544a3838190e0e8e2d5d388f9b9114c6b9a822342f9de6a567377689c4372fc95346b1c84aa5d286692dbd7451456abebde6dbb3cd66bfb1153f2b5095c9e05862c86bf6e12d7b38c3b1440f67d7b287e96f6c637a3194b57950b4b18d6bac1506ece4692ccd023bf91b98cd2b9b57d7a452a4c3d9451599f9f6995bd3809dfc769f159988b3250696af6958629cec8bc2416c8501558f6261c3be28dcc3340b546d7952e90255303097bd7e7581aacb014110da7b2dd42cd412e43aec02aade25365dd34d37bd37075cfbac298bd74ab990bd51bbb6abc0a6c72d88e9cf3686b18d4d1776013bf96725abd5fed5fbeced2576a0650755b448d242ca1652b4d8b2032967601d8ca1832d7470850e4080b318eb596799d61b77f30a737fd65a2917ba577bf6aeec8d30a1863770dcb34cb3f28f7b99a6d53a4f2deabdf1114615e14e3f848b67d077f30abf1fe1b705790f8af55d5411ecdbb16f774e819dc42e9fe52288b14d4887b1f716247bf44ffb7ba797defc6e11c4db7355237b10cc20de5efab3b9e68ae935bb848876ee4462ae714b8870873911d3b96b37d3c7dc869d76ed6689616e267a583b7773c38edeb43dab11edd018d1c318463723298c5d5491ebf5d78d5db029a0aa5ede2657359e15d8c967366f051cd15bc10a56c0bd3b64bdb1b41e04a370399e01b5c83fcb5e6805b1b072042a90848c64171f98e007424c2cb13a12d2f9067e84a2fc0861799f53a02a9e01550f4f811be7e805432d581e7a912376c901b5c01d72e45705967f565cd8c53dac02ee70ffaae0c6b962797945ca85ec1b4b14b587f8ed60691e0defd3da348f776f6f735c8bc9231cf8e30153dfe645b1813b29075be07e63b8937200056ea41c20755244e3366f031cc49d848333707338c608a37568a1a5599675c30c2a0841091b7710a2fab418638cdd952aa4c3f382f6a20f82517b7f1236bce1cd799f95a9adb03cb4d54ab9d0756955ca85e60ef5aeea8dac37bc014a35a0ed57db42124b1388832e7011248a785ea29851ed59799b91149e16aef83d1e4d883d79fd5ac0491c6081e961fd410b420889851489a73c7d10f10656443cdf41158afe07cfd6d2c3fcfc0f3a78f6e0f920ba1d785e08ecf31688404fb060774ea18945173c6f03544d3cdf7b4f876e42ba0b1adb27f6a3ef47c40386ed135f44713fac035544ef0b7e9f4bfa4514d3bfadc811221c443ce0d73e6f1342b72196f636024008730670fd6ac50413985e46f166234c6fa6ef78f87ce443b543a744e0cd4ce0981f8e3f54591401e023845b9158e4d15be4d14095f5f99bccb29d64032e98da496f7e67c0748de661712788dffbd23cfab8371f8e7a802152f3804de07718bd5c1fc8fa51c1c937f714b6dec30f324962eb9fafb53387e9738b81ad0b0155f907b66efd070db6ac0755eb3d44a02758eab55e04048a42d68380aa4eb2c1146c1d02501583a015c60303430b31cd16f83a9688de17dce7a02adefa09aae42dabbdc4276a9005eae0781da8ca1547f8c6f13e0da41f73ed7ac3e0b2c4f0e5a0037bb27e2ae03a89066760d8493548c2b86a3ac0471a9a9b0a81a41016ca1e145cbf77f4e00ed98b828350700fc38d88081e6c545530e346003e03989f400f60be370a0800036109202e483d80797be044c71ec4db3135024eebd8d3277af80676ef2b68f48e5e7c1dc002cb635564bd847c9627983e9b011a587ecb020596cf4cdfde3fd1dc5ca7832a7a79795dda2f4d7bd534a215d68cc82c6ba1deb2215411ebf39665fdbabd1520b3b092f5faecc2def8c2ae90faeb97f66a1fa64f08f66b685a18766d46e4b13b349fe5991c61faba19617284b50fd1cb5b37d36bf73a3ec2d36ec7ec11aac8f5edd7b76747583bb5264b6dce1e2f3bb45d5e37d9274f9e3c313d4ab78f0731fd138ad2ed23474cef130a8a2d94459c8fb41e2670dbe3b7b811c94e6fde9e5d5476ebf37023b25d54768bd4d3d78c5aec56b5866a0162b52a11f9eb66fa4c3bea7ae3ecd4a2ae2b643e7b76b126241bc270a6d9edf6b944efcb7523f2d9cdd829a60f3f9c99fa557b39c02670bff463c30f1da0aab1d13bc26096465604f7de9d99de14aef1c3fd077fc8a0171c0fb9e0187014671a1c0f83b385e33197f612a4b9f41618701206675373c13509cb476b04976b9677052771ae48583ebe66a94151ca27b797069df3721621ab0d24de18af6c20f1fdc6f0de9557fbf45f6c2033be581fe9237db41e5f697c8cf4319e42347a527a94fcc310a32418e8a77df4cada3e8d65271121237a0d045a2f5ec013da7765fbf4fbf3b281d0f7670379d806d8cd7301ffc06ebeb77c83e763d6de941fa8ca1a9e40d8e77568206f9ffa791e5035ff03aaaecf54bd0e04aa305533ca062d785e83402798aa9f473590f77076d99b883b49066660ab677dfcab59c0b5be1e65a1ac77920cc8c09d24032ce0d3756f60572d3bdb87da87e314594f18ee4230fc60ec6b257c4632eb99b505d1aadd4edf36d7bf679b1522a4be1f04b7a1fac69d24032e78b60f15025fdf163eb315feba9ed9ebbd0de9873b837df3c37ded19d4368be167597c014aef9b2b56e289069a40c2444bb6ed6e4b4c1f62d25bac808203d8741b76dbcdfdecf58906907050ccda362ed9ac0454026befbe1cca8464efec41306a9785dbcd0f6f4230dced93b7678d33a95d16beb721d7a185b53b745d17d6d8aa561e6d4f66219f69f0fc99f33f383c2f9ed78140506826c9200b3cf13c0755f0f327a88a9ff31a54e9cc3bd4491988524e19a50cb088dd842014e086a47c386e45e42462511908cf2ff9c1f31299a4b48de116245ebe5903213c2b585e8fb7270cee59c1f2bd442f813d9b5a696af0b51ec36a87b16adf318b3ec3ecbcd8335be726643bf668eb359b3d28d6ec59c9e65704317cafbfaebf7bbdb720d89fad370ba1b1f1b3dbafebf17a16adf586aa2b5cec70bc793e7b6f407030f3596fdacd2bac61916bb803cc5d1999def006ecab8637c4738f979b910ebffeb3d9350bcfd91632592cda9fddaead98bd3621147b66eb8322562f68ad5c04df662445047e5e6905f7b3823b87513cd22085e2085b45f27d3391f91e6d5ea99634a98f2f92a7cfd24887b351b459099cfbd512e1c0f5bd19997f37c7472c4fc0f9084b16dce1f96c6a562e82789e476e41e26d52efd06dd8e5be65e7e3568407c76705aae68b20862772fc8870e0b7c2749e1e6e4578708e970f8a34c3c74f9b5738f7a58d5b910ec7fbacc0aedf64622c9691d5d74c621be1bbf941916885eb7befa6892f9965bd777311c4444f0d4c2fad37e1701289d404cef044961a4d7cc14d2a86bf6ceebf9b39fc0ead9621517da28126f09226145f6f62612647f8928799c8f282e76111248a985e14919784e3e1f54e1f6d1388e533085ff380d45e5d2fb4f9419b2febd908e1cdd7fbba56b8de21f8be7585f421967728c63bd487976885f31fcdc24f115ca6c1d9bb625dbeb5dede33f81a966e6007af033beb364cbd7338d3649a2bb228829334181e5a87d6132c3b185e83405108bea1c010c33f0d77d215574c087ba07385941f1f54efef815f0efc7e82402f0664fcf85002420cb28881151878f1822d807479cf8a2af07befadf380d2a8e24a1738e227df5b302c40920bb860f898b15f8f9b0ff3f27133021f2f6d66d3b304ee0763f60767a61ffcb621cf07667de0dea286fdc7da8cc4e78787b0c72704071cff3621d863665fdd1650cf44bd4c64559b5b114c7bdb02eae9b3b73920ca13aa6722ca13a2f44cd05b5b10f54cd420ea2df9620ab22c387e45371758c1f1711e3b85f9bb30a7d6897afa698d30a1f96e20d6e935c28468dc8cc4fe41caf5f9842e8b8588079c8f30a704873d8a752cd99130a17979d31bbb800ace32ab04c3d9354b042b5d6e42de63de0d24ff60ed9b1d4a925dfbf6b809b14ab26f8fb1f908c35c1491f8a177d3e393e0acf6de7c7857de7493e070964848ef258bcab61688812deea42abcb4400bd6c19dd4822a5a00c5d4328b2a6ddbb6595a665159c9a2b26d6edb8360666d45b64e6a0119b808e2786b2b8221a085e875a2ded5911076a3587f4273b357a458dacd1e84e75dd179eb17bd46739e7e65544faf940bd1d7393f6fd198cda33269c4d515a7c4bca8ec73febd8bcae691150cb720f16fce6a44619542d63af907aaa0e9464108710bd86e748231db63626abed1f716c4f618abbd1ceafb41ac9810da01020a71520382891bd7ce04f71bef0d881bd7eeaa746ebeb720b8df7810dcc28224dc596b7303a2e630d7ecf69265a2e6a5cfcd0136df4e2d103587b9abd24fd7b3a95de7ab92cdb7bbaab956b22d446399d06e731dcb84cd6b7eb24cacb4d7fcb2f908e7b7e23a8905597036ad120ccf5f960886e7d47e7a96bdcd01a59f6eb24c946e3a8c5d950e73ceae4ae7de9b036a6ef32078b22bd375aed9239c1d7b6669d336e66c6f0e58c17cfbca749af70644cd6dee8afb066399b03913f74cd8af8c98c0f1989fbee20e73935dfd2b9bfbd5bd3d8d6562b5d2be7d257398bba2f96ae636de423a9689d56a3bb7ba79cea1d0dc80281de6730b827bcd5d1d09997ec3ae9e90e94e0b44691ee632c11de699a5c56d01da6d2e13db4bb7367a81a83913da4d8fb1abeda56bc4c48deb7c7bd65b10da75c2e6da4dd7081382f9668d3021ae1bc8bcf9075f9152ba768d3021ed743312ff0e634de7ecf6a0b8d92774ebbd2d80bb2beddb8d523a167a8792cc5fa7877903c96efec1d7b19bdfe1e126e43dfeb24ae6e983223659276c0e73574f68e732b1427d85f3d5cd73aef3fa6e5db34ed85ce737ec8ae6379e510f58c1fd0515ea012b9866fd52c075121555702701bf13709906cbcb670e4b791a8c91a50b1658f1b2022ed80777d20ab254510503e14e6201972ab86015eea42aac588195153c4103e3e04ea2220c4c4517545881334d9b1665dd2217a67f18c35617dc64585d306d32b22d004f926415b020617a0b539c95b46951f39645cd4b248e6106144898fec215f746a447137e80a46d4402f02489c8bd22a5749dbb7a42ab1c77337d75afd1764f5fd92be542a66b84e3da7f8d704491c28310942321d34d3f7100bfe758291782398ebdb146db4f475d23ee278ebb46db957221eeda6decea48a88688073c94fbf5c89d248514fc3edff809d15829175ad5d557522e6454ba7698dfb0ab996b542afdf495cc358af9e94ab9d0761b57ca8560ae51cc9572a1d2b5eb84e9cb972f5f84b6db31953b490a24fc6e91c4efd866a49380dfbd00f705cef34a169ca4b7e01f59c1d78600eddbb908b45d29db4b37ca769de041a8d411e8f27543c07698731108e64a299dbb514a170bfd7679eb26c1e1220e0fcd3ba480232113142a5d1a7175f5909038253ae9a2e64b8f72a1d2e93312985ed4bc4070ef5908fcbe5d26b66b97af2f8777695e8f7779ba39e048484848a5cfad001c8e971b10dbb5c70d08eda5bb2a5d3bdc0ed042a5cbc5d4fbdb0ed042dbad31f5b4db31f56e7354210785cc029ce46204ea962285e4a305821392ef8ef14b7e70bc44267c454adb3195858542d61346839d84dd297bcfd2746d01ce3eac35903edc4add7d7853bfbb3788258b3c583e1b6af8dc10be43d8046ed8c1bfcbf170506452da8be77ba3ef0c0c5fff84bcc3c3bfcd0708b5f77c80afcffa59e13a298a3170e9f52fcb9e16b720da1dcabe1db319de743376d33393c9546de959c9a2884824246cbac41143dc9b110e099b4a461e1236c10f693769b7d43e517bd62bec623f98d2d560479fe18702a64bfb70a9bb2b9d5ad95de9bd99ee1221a61391d8546adcd2d2a2035a8c408b1368c1025a717fe26c6a5336100bf727b50f436ac9eee010898956b876075fefa36235d696bc290f0438fe79a07bc4c73f7a9f9433703cb0fc7b0c3ad536a85a7040a0013280165dabe8250f380576b15f9708d4422df4d2c0f138b3dd25023d29318514819488a9c8715d5e8fe3facc7a665ff7681feb9795ed6399948840b3535615ebb9be0d5b7ff5f600bbf82503689f38041c1fa47bc4b7bc41b08b108a0ba9fc4ce072274dc1f103802a1bf0b302c71806ce36a481e36d7852229001b6b041a75a8b0a844e3596820db085113ad578c227b07b875970c01c4949583e5362150f9f40d5eb34706e2d48b14ae1a8bc2a38082cba805d9217a6c0b987582bc7659abfebf28fd482930da409d0f40bd69e6917d54363ed431a6b3fc219a7d9ecf07a6f91465b34f1552d073ba91dcf9b6974f0c6c0e51f3fa24c9243c8809c6cc83eb8bf93719a70d87a8e1af73afac8d7cb56cd72dd03e2784fed236f21ee0824d329791b70f8d0ec609c1981ba989aafb5eb70641c703b7d99a5f5a98140ed5bd7fd1081ba7b4bcb607a8bad67b11f2c69f764b9d6baa3bf6ced8ebe5ad91dede85f120c39ad0a91d8ba442b0c31bc41404ced1220da67be09785e01dd633ece8381fbfdd7b7239065254cc9c7254fb047a9424f9e44b990f5283f42f5d7233cd73ee62f0bd3374c1ff9069e00c83278fe79e1320d9e774951c54b8a387a82dde4da67fe5998ee310f6de02018ef19b843184590b54e965bf07c9675f7968e0b6e31f03cd00f52f07bd5f684a1e3fd4c877d82e561cf200db80cbdc0feb08761875ba00adbea310bc3e8b9019751ac7b72c06535486754cb1a8817a84203aade67c5f3ba9efcb3d7a51df27e49aa1da1779ee1126014e015f06c592502713035cfeddc2cade40ab720f562569302677f825d76fa03040aa28134d0ebd44e5f62ececd8432e3a9f875eccc32d50952b0c038a01556340557cedc279d1798669bc2ab625442cc62e73ba05b1c76cf7f28bd7b1f9e19d7a7560b7f3fae0154bdd3e3bc7ec06bd785cb8ecf4b9bd402fb69d9b5dc6367e301cec5e15f6f65ed17119fbb09c22a920512bc7b5171d37ff581eb07b34af34af34afffdf1604c77f7839609f474003b93e9f8056ede878ae3ccf1c96cf3438e6dd4056a7ea63be636377b547bdc481eb796ee07a1d265c339497ccea2563aaca4ca4e13ef37aa970f2555ece4dc8af67c77e3d3371303acfb183cecece6f7eb8f68ebcaeebbab9fe0d65a62b522ec4f3a287aff0bc4a2499230155d8e78b9458dd271088e7f31209ee20e5423a3ecf6361124ccd4b6b7aa56263ca94370576f3b20a3cb39c82e76515a8ca71201d9f9755482be415328bc4426621b7904939641559454e0155562e541389eb894cb8de1bcfedc588e9f53679f80aa8d2b90dfb5cf1c94b4ccd9f9e41ad07a87a37638719b22f7a78e7d8ede10197a397246b631a3b36a201a3f514b08bd1aac0d47b0267fde6386f35f05cd17cfe3d692052e2cde75f951e1e7043efdc87621ab09b2f7a388bd1de7c911232c2d7b3842222f1ce25deb999098ce958980576f3392cc40276f33a2ccc02a6661558e6197a51a4845d79b9d77ac9b1f9e11cec2201bbf9ffa8db1c0908748484848424050909490342f34a40d53fbfeaae94c6e641f0564e01bbf96aa5954b93f5ca4122a6e67f88a9f99aab977a715c79739c6c897a25e26684e6f5722b427324a0aae6f3454a3490c6f3742b42f3260f5f37c33ccbb18d71ec8d45591d60376f53635b5ed1f1199b25ceed45c69e6cd66cd818bbc4c2b9b770b6e861d31d00ece67fc05eafd66ea6d17167c56eae95e3ae9b6968eacd7fee7ef0bb75f30fce15cf0eaaba83ddbbdc62e14e19b2d6c91067ad21118166ae6535381efb8c7d9d8a9781c1f127db9dca178e31ebb9019759a5f75bc2c8432b0d04e2d783032e3e5a78cb0e891712371f8e708c0f8a5940d5835434101753f4342f380d7480e9618c5c04faa199fbc9acc7e571795cba8b5c2210f7ba78c15c349a492d463168b02d088c3fc41485480f22412488947507912290d6a9f893941b2a58a74a9519d54af66d013b7aba2f09a6e869a69179e64ef48a41234373baf49994703bcce66c1b31e79e79e0d2636e93876ddc0cffeeac02bbf821784bca3334ed51e142175cfaab1bdc8640dced136f4ea2835144269c7f70e9ef3679185ad152811db5c0050276f1902671b9e3b4b8bdfbb290026703ece27f6017612e7fbd3e432b5d6481814e0da4afc802b1805940d51630e90ca8ca3fd00b08f4aac0d3432d50553a0d19dc1898d29fec0f31456f95ec8feeee479508a443a7e2c3c8929f15dd058e373d1b6067f5d6e303aeb978610ab6228b2c22d0132c9de4650b8e72212c3e981e5e01819e103db402559de485272fc228d00aa8ba4e1fe3eb1604c60b817e5346d82fe6d00a3dac02aadaf46ed39f0d313ff85d08c08e3e086e6fab56ab3030ef242ea481e95f16636ce12509aa726fe982cad30255f94101ad442024628a9e6a3771340f0aa8da4e83208c3dc16e9eb3574e37592b450f976e1154281211a8762a9ebea3461ba31722029d2e042e2bed0f11881e7a29419c27d9b3e6b8da3e4055ae3d64ff01882020008168622aa321a33b03673f38b3defd5cc075d216bce03e0377d2169ee05adf26a566d95c6d805d7d36e383b3272da18850fc2ef1bbd9085b412aec320d76d9bbc2d077d2195e70d60167cfae43a6ca3e00a882cfde436bd1f24c8369eab59b6bb56ee6fadd4c832b0dce68c0651a9c1d6e439aa379f5f575bb636e2d3d64f701aae2b3ff0055f2591094a781aafaec7debd94f50551fd21959ebc059974952d4c09d14c58ba428636030a4d80237185150c13db8c188c20a36dde8e0bed640dd167dae4848484d08c967730b2273865f18f07b57a3b969382386f2f2ca0d64ad9323170cd120038c108481afcb1ab3d4679a7740314b529608f4c68029486fee708e59f2718e59681e1918fe8a5934796803ae270cee89812dfcc4801d15ceba539315f7f57c63a1e29f183105ff82e013231e5e4f0ceb660e47799f18183e9e52a181103d2ff20f5f9762409575f837c6e3d2401ac3ab6ab0b353462e5407d91be3caea33cb0a32df75ebefe788eba433b8e0ebc799d4e443ba49035f5e70bd84401688a9fa0b0abe7eb00522f6ccc9ebb2e0763d2259d7eb8b945841a01f31559ff4440baebd05d7e70705ae421401bb7a392f0424ccf57cc2f5f947fd0febd9ad5cd31fda36e4dabbaebfd85d2cf004d72741e28a761ecfb2fe7382400d0f55f1f53a50255fdf41d5d46ebd6eb542d3306d2942a4534a4a2d2b527a631306e6b0841d7dc38ebe17a78c314e24488bce59ab66dd645ed407b18d5ca5535698929f41a8efd7185282617252f6059304bbbbbbbbbbbbbbbbbbbb0b3aa594539a41b9103d6c21b50f7c53c6186504436b79f2453178a1977352db4f074abb7bca1863cb8e57905ddf118852eb74d279d52ed3a232ca18a18c10aed18416dea3b476983ba59336415f8c53c61865a43017562b7689519f6d59b5ebd12364b07b4eec59c25fcf2ea10a7e5a4b4a294f3437cff684c13d6c6daf6a6f6263c4f920f8aa66a7ac0da18c105eca04094306d1c3b5bb7da20cc39c9615ad3130b54b1eaeaf7ff55e7016a3b52c0b1f2d5455eb0fde2aaddb2d6f3007bba9cdf95e53cb9a734a29ebe3a4534af9e693f407f1c5a8c518e39cf1695035e59b31be17a3a4b4e3932fc207ffde7befc507bd40a57c53182b9f64fc694238352b06e660270fa59c7f74d239a594d6cdd2b2a87c5094536a384a988a517e8e31a4f119314aa88a32c62968610bce3ad342d21938eba701ae93cc180367fd9ce03ac90c2f7066922f934c1e9631d60a8306e6ba4dd3e61644de8c693423d6972f1ae4e484cf27dcd7aa84aa23dc879bec6ac4824f66216b89a3942a3a5f5f18b82c87747796c4d6cdf3e1869dac9ac4bd59933e08cebf2f5f661388e51719c55e63252c5c8fde947cd4d68d30fc94504525ec26f60cbb468278873ab093b72e8f6969ec0dece4a93dece4a1ed7ec0d573106885c4851e071e00dee5b5d59dcf32a288a111b6def4f5075ccb4beba8be19822aa4518530a61006ce4cb16a99cc1e9490287ef95200938dc3bc47612e7d8781a1d68bb10e0323730b8bb943ef361e04df94a1d36d9c72a5eb5936730c06060606cbafca75e33030d76330984b14f110fdcc83e00d98cfccdc88391603f3cb64bd2110cde9160d85f9c5999e652e95399d5a373098cf5cec3197b10dbb987b6ddc7a46ad0d9bb9530f78c00d719f38b71738fe9d2ef720a00ae63e0071f40304842802aa1e67637eb2dc4d16bb654d366e8eb95f411576fd7ae9f148c0d8c64550657abc1250850455ef3dc1f16f0a95f7e44981aa677aa67bfdd9d28362895e1716d2c0ef03b893cae88265ffe048ff7af0432f51fcf2650050f57e6cf801551dd403546ddfac06bb78695f12974f3866abc8fbb49907dcc3f984637e38bf4b14ad4355b46e3ea2d1e9aad005cedd05c7230155f94d5104815a28de0250d54955d802c72781aaadb7eb3e29af877c7e41a4dca8c49e2ba5f312bbf9926772c4a55a74defa8e6460290b19954212aec19d14052a7016b569454e11f1bcaf0aece27d50c02ee2791f15d8c578df14d845acf479f39bf2b0cc1e7b9e9bd3f4ebddba1e23b6de696399c4d61dcaae459f69698c89e97687b25b44b23b143fb1753d6e46268ef49a5366d8cfc0c79dc4458d86f173fef1522b9dde1c230df26e3ea20f6252ba05a15b0445b90579413c0e48a892f741c4481fa3845d1bc9b620f10611e9b557605eb257a45c08464846cf590953f499cd200cf7a0983d4de32c4783b5985f963eb3db108b9eb331db710cb559b3b286afc358095397f56bb3d9612ca7659cad30f58e59ee64d99b686f6f44647c3cb5f90853794914b1348a471bdcbc74c102efc09dd4450ace4cd78e592935a9699a8661524a4d5ec3de94780b1ee163cfb2ec9ac964325529dd649258c3be6d37286a167b11c4da258ad8f42058b2d9080f69cfce6d9b1633847917937fa5c7ddcce4283797d2bb173b7c865dcfbaf91d9b207d421962d0495f195acab05206155c03eea42b8c11235c357c100d1f14691f4eeb34e2969b9023f9e2ad02a1c02f52b94510d3be3452b944d1a6e7095cdebecd6ddb7a9e30ebddbe699fa56f433002994ecf5d5abadbcd3d8649bb451167371f6157bdf5a46dd07ae20a5e5ca10a9c9924fc15a0c03cb893c838038f8101803b890c2b3087235c59fd20ac26d2015b6f9b5f152211cb3febe687654bfc6ec367549b5b10ebbd7134b08379a687cf191077eb30b661cadaae15c1d239db3095e74db66157a237cfec666c3e1f0dc598e1758b208ef270e89d7e9eceabc14e7bbc2538a9b8ea61f7e33f345d7eacb76e26526fddfadb8cd45bf397f52bb332db130627a7c039cecb6dc8f5ecaaf38f403f7434349cf5386f4f185c1c523f74ddfa1fb2c3ff5c879975ecfefac17dfd5c37174d6c3d42eb7e4a2ba5783de26d2e1308528bac82e5e382559c84c23a0d8d9c029201b940a0206c1bf21e0fa114b20a54d5d35f5b10aa05d72d48bdd94f047a5490f8814959d2ab0276f3967d5660f76da1b2cdad74e1cda87d5686d08bbd565a69a5ef65b54ef1324c4af172281dfebd67fafb7befbdec8fbef72e7cefbdec596a454625865456b1b0435b319b31806185af5bc02cb0c0951e83c7b2600a2d29b34095757a29059559307d965860fa88057dcc0255d89f9041082184f6ce6ae9a17d56b8e0326785665ed4660e47b852e2bd2a7086af1b9188e37b12d34b2a128a97433d9553bccd087673965530cc0eb72a185ec6fa7863fd19403f31359f211938467a586b4c750305c5d47c2c9a59a5cfe8df839750bc1ef4d7362473383f2bf985712515d8fd4c202efbc9f57512d5a8e590909084e022101235de5c33076fa6c119c79536eebdf74ad9df76f37bda33ee9d7bc6592bbb6ce3d2965d3329a9b465578431cb7befdda4244c6f613666c1c20a06b6c8228b15740b97a42e9296f0428c2d5aa4c6597acdd60b11811e152422d0b31253f3d3c4a5f44cb33d7331953d5789a9cc82694f185cd1c4eff959c1f4d6311bad508940b10a98b212811e52a7e8e7a514b7e21598de44a50aa62fdd4c4305d36f37731d9f144c9f3d2b783eb334cd4a29687c865c307c865d30bcf499c4d975f85c2431767833e482e9b45e9f8b26c66ae5b84c83ebbb81ba53f0d6b1a24925152945b605b16415eb4a295e0ffab4c783b4056bd8b242162b48c1d62b54d50ba37f414055bd456fdd0745fc7ac2e0f2a38261bddc86589f376e46342c6fd5c357db1306072ff394c724ecb09b8be235e1e136c4fa9011cda2b7decb0711041f075e0f781b7b910b2a8c817f702751e10abc059698cb19f827e63098a4f2b07508c46d887c7c8dab65fd4d81aa6a6d11a4de2cab96c6ded8c38e5eb33ab0a394925105df803bc90a5c7026b5b759f7043bedb55ad5aa56b56ab58edd526dab64aa5635d522f5d22add2288eb4db414579b97c0168659d6e51604ab190399c3b2d62cf00a2b70b5aebd6bb70a5c6f96b71e1425c599cd4a60a2f8450d1c71ce007e146396286e43267e5085ddaa39092e82d87a7e56e06cbb953b6c210155da2d797306bca041f1fbc4f4d69f3d69c1e513a6aff41c54498c69bd0da957bb653387235c29216511ae82f3fbb54969d92cf13bf627e5e5508fdd869d951f159c1f51c4f9e1ec0e59d76644bb1962f9fa862aecf94dc119fe3d7be6ae99aeddfca6e0f797aa8d5b0471e90e59e76ee9d9d3b44ba8daaedd5c14b5c75b329b6e11c4d64b125a8f765a97f25dcaeb61d51b04ec2c2b24e10cbf4fc11267d4c2302bb3b45b58568f69cf38f07288b9842af977b3884dd7639e8b22e61ef304ae0862d31dba5efae34ccf5e91eba5980741534ce9a65fb399dd6cc36e7b1663b167308d0636d91f5cb2135f3c3a2fd8c2003631121d0f8aad88dff30fb68ee51e0363df2c0fdbc1ceba6691809d8501a871b6d3c026fb03972cc530b66df841035bbf6c5e025f4bc458eb30b69eb3ed059b6c874b56e2cd661d6c3db39acd7d06b65e37231a7eefe60ce0862a78ec742b40bd76ebcd3d868cb0943380afbf8dc8c417c6fe38f072688cfdda82606fca65ab845d86eb7d1c783d2c7a877e64b664f2ad234fda3c9af74aadb67eda05084d8bd63bb5d3099f09d9317c6603c7a3ee9b8db07c6f0b682b085957ac68172044df2de3ec2fa8085917295e6b03537a1b387b8d99a06fa1de147024446117309c711f757791b64266e7a92dd2a3093ef882fbb2de7a36c2b45afdbcc2f4e688e12fa8e0c6307d10095997b2661271377e387ec9cff386e1336921ce9e46f4e455d264403270bcfd60903554f51b122f047a82fd0996f8ecb29d046106b5e6b2f920372311c763b68560361b12e00804b81af9692f0608c2f5712b024dddfd66291b51813d04e17ab80d5182898c88681907018e5480ab90232b9848085c9fc1d7a55236b2621fb5aa86d57b69986d2122f0f4662c1b61275081e9ede3b644c28ede1c37239228e2a168dd246870110da6f12fbebf4b8b2a02bf0486a7707e217b34e0e00b702731210d0c57803b290c26e00e77521851300db85806eea42d56f0db823be98b2d9807eea423a441444404882064c179c81a4819c0998913b214a500e72224a42452c0b9880a1211ce4556505982735115b6e03e11110ce09c440892709f6808e625056be0fe10ec085c30517ff9c205f789fc70c20beefe82fbf85921239ab72c9387adbf3b04ff3ae90862e06c97bcf6992faa98a8bf60780c83b79bc79ccff6339be9e712f2db11bae0edd8b38961452411ec1bdc84d46b7f563b66b7c3adc8f5f732bcaf7ddeb16b3643dca45f6d7ef8c87ab04b6ccc4520ebf4306e41e0b5ac753ba6e25f06510eb84e0f44bd552f2f10d7e95d6191bfee632aca204b80a0af77c5c5543cbd1d53f30868219a988ac7b0a4cfc711136a214bc6ae858c587968a52572317c0f8e3452d81096e07f228c5cc01a81ae38e79cf0e6a315961967cc56099887af81c003188973be20583ba6e4a795875b1048247b263c0fed7c461b48c41d8409fcf5eca2892979cc723125ad2d48a67fa864a9d5e973a1204549c618424334d2009000f31460304820168c07e48926c9d207148010aeae484e1b8ab32046621442c818430c01060c801901989982000080e2cd37db40baa8d32a5fc42fb08139d8fa330250f957c50c8eaf00be17390744fb77023a567093ef80882ea562b30216548860a1bfa289635d7e8ccdd9a001453b1ba6702585d19365d61ce49b97c7ae675203ed7945091be151bd5e80b32e120c375cec6faf3517c91d102f8a95c1cbed4c8963e16fcbdb6ca55ae9c01ffabe4b982f3d4058d290e8a13964fc9cce9f3cd65f8b4a9f8baad3ccc3197ec37dea8115deca84d368c7b0f46e859ee96e9929b00165176452788bd0c0dd8934f579ffad60f65b8971107b124054d86eac9090dee7912a0fe578d0390b5d16825603e760fb17a4ccdf24c18bde90ca1335e0e9bcf75935f03a631a770efb020f721465010c23aa7e03bcc5ff119446e48593a7df586fd04ed6372593e428527ff2a074595976cd3c726dc70405543eb65fe0f677d77a2db48016ddbcd21b9c68b25e62980e2affab5e38fc45aa25d37c23e4f7bb416b900d0fe71f272f314ac88ee5976f54e374b0ea9821daba5db6401389c0334be23de3c040f27991d9a04fe4892476e3627ad7e5020b166638806a78977c505fc596ab794bf1d6d0de475fc7ca54c5baebfbd2b8897cd6704774a4d6ba305c010ca4ba86ae2bb17aa112c5e589fa8a77d5e2cb4544e865eb84182b34856eb29173f1715bee31121408045bd570b3366998306d790621c9c3563589e694e5dd944eac33cd230b67eb349f55d3ac3c76cd93decd7b2eaa5a0ff882a299a302d40742be6e8311a053c4534e940297552354cbb4e27bf49a29f952a094e3b2196bd3d03842a71619e1ee0c9c0c122688b4a3b78988d20fb419f91c2b510465bfb64f542bc6b0191fea26d5c4cab0b3b03553606973065f58b108b3a7ed94ac48a4e5916e0c9ee77673ce40e6fb4e3ca0bfe1712b6e82051bd80dc687dc182ab1770d3ca51778868159f0dc6528d32160cd15f191cda817f2414d7bf119dcbdb4073d03e7b3967ca93f6c044eab00b4e99a9d4d8dfcc72ab6050231801d9ae8842eb4e07ee7d24bcd7399ae5608e772f63d0b5a89dddaed80cb0b172940d6ae56e0ccc5dd9076a47b9b130838fa3d356826ef865e464787153f92d028312f1cd0cae355f3ae3f15bcb89a28d084fb5902d159b4ab8dfd08dc8000998e2d401396ac01ffaded234bd2272b5d5237f4b854e5ad8d8e8bdcc842e5a996f90cbef2aa6eac742b6c70ca4c5dfc65e13295852244c8603a93e44b0c891a1969a5b4de42c1a7fa035327e7781bedac33ae8c1f26677905ed6ab1066388d443fe1e293b494c59f9f321078df8dffb422b1fa3897ee42f92115dd33832dbb851f37750e94d2bcaf744a4d560c4a0f443e3d5c05c37a8e252a54dc9c3ff2db95cec14258a159d295649723822ee6961e4c831c65a367d6fcb3bee22058e907770f0f7140868c0120eb7372811d2a6c7129884dae960135368bfd334cbcaf803942368afb113a12d230c12f712123a485d83031f2492a9ea0bec1f00ec71fadbd3139a21b90a16b274059eefd50dc24a5e180514c27e8abd973e9b9967749c8ca98efe950f210864c5313d41f89584f9cc047328132bdebdceaa750c2e0641bb308f2cbfbf4b28630e97ae68361996c9592a50717786ba3d82265b9e801afcbf7f9ecd42ce1ca76a5bb2b0084299e36570dc689a1a457aae9eb58bf28c659a0cb49ab7816e56d4b07ad505314f73b305076a00d1156d332e76376f28da944bcf3c608c8497767e06fc3359e143580b66c806bec213fff2c412f1449ef31198bb53e7a50661ab679c8a91f1b3200c944d4280352298be2fc34717cacb8abcbd55aaba1d1d554e111afad88e584ca2eaeeea9db81e8dd8a0f648e973c6d84e6930ff9345eb09166dd2e50ba19b6e828f3fbf745f1e2af6c301bd8bf3845d6f73491ddee971a0e1dbadbbeed38af18d53ad01b4ec7ab8cd6f00dae44ad79976f425e80f37fafc06d6d5c75b010d10faabe35edd2b1ed9c53b04f63660dfc9f52c5509319ce1e8813722d9ad110e16d353c57118a720e512ea605fe05429112e48c40db039210687231f90b101a6b8e21e17d524a7d95c33cfe139e2e9f9292087af22b049e02790206b3f43dd6f8052ddf366859a3ed4f2ee6142084213583c60f12d40b997553f892bda3bec5e3b8b2a69c4bc0a67a4a0cc8816dd8276f29c93be86a879543b3e745f62acc70d0987c3276f90a6c6b29d8f5a3fb1caa3541eaa07dc7945911d0df06be7afac2d43602ebd24b897e4149822e1046101f2eeed19b6c1619e9d6ba155c93e6d3040e5e49e773a5e1ec1ba6120709d6e5fa8698a597fbb15c7bb29bfe6e597d60d6251d30177032a9e47edf23ae21384e2b017394aea94809bfd0e878195762072dfb3d53f21836b0a023c002aafbc3c33d6615a98ccc854f9cf74dc0c0c322978063769bc1c5fd4a86b5d9512364ebd625014e4c5139be7e954612dcb9cbb2c2e2a551be55e88b2e3f51bbab78097f7078d5273693fd37ceaff9c4196de1f275d0c7753417d7c2a2e57c17875503bd9e8ef95bbdc780bc7cf2fd3c1f3f7b6f70b2501cfa4cf3b13acf2f67d4292b003ab8426a64ccd6d1030e141aae95d0db971da559b9f981e364042483a7dd3939ea66bf90efe2e21cf5a775ae71588785e45b4f3cacbbb594ce43e070d762903e0474071abfe0f7de7a66306a7b4f399e12b9686d2996fba4c2db6f2d60230c7726b74464fe05dc1cae694708733f4d81837195b332e5e0ce76eab476fd668d14ac5aaccd1ec25cb4caf326b0eee91d3022bb0f27380ea41faf38c07c4499fa32fc04e7bbb0e5603340aa7d208355dc42d46f80005439715da8a994c2386b0b5603e741107a3c5faee07d0b834ebb7983a0136c629405b91ffb47c543c91e4843475b290bfe6e812a07315def1f55785f2d1ba16ab646b0d151afbda3a2129fc07fe56020f373b2e8cf8b06fceb0b88588a0baa9998260159e790c6e6c66716305fe4cb4f7522006a10168475acf838349c7859e3a61a5ca400e708d9347b4b7533f8fbb06dde80ea62ac1ad1ff77c0571969c2d4725a081b05782001a432c6045f73dba8e3f8e9b26580abc7b15f20144da2eb7ee01a5715d1d0885161209a847a98094830df0b512911325081c2726ce22fa850ecefc5896f2bb932700c007e2a8347a2c3acaaa8b17c26b5e415cf6d33374debbbe7d8a54fe8095e61f403ee9e28052b7c5f2522f3b18db2228dbd1348e7e5934a9e82f54ac8be6b63fd65096eba12aa8a437c2fb0d3fbf8ad645250ac738963472e2c72e27957f53aa722503838d2bd6426d161a45028859babc192dc0fe88305933cc8d61b49e9bf7a695918c2ae21acbb4179c870d421024315cf230644331b52f2b7c34d714aa77df1d08abda846dd69f9146a33c04f1e0d620893ca6628c850881daaacc5d2000b34d55a4f66bebe88e4f9cfbeafd7504ff176dcfaedb31532d2f954ca700ba5672d0f60d9bc83143448df59f38899994fde3a89c5e8e6db8a154a082d51fc774e92591aa8c164c8c15955dfc047d54e1d0f3947067a176419693df5ac5ef78eb704362491f15902d9393a095417fe04004294d8329afcf6687580c9d042f7e0f7c140541e9dc690217e0530fe13f599eeb8604b0358ec51169dc4a9327e03c76b409b62ad89e9978dab32d355a875e61275def3984658e20f696d99e43849496697a08dd8959887a03501702042d6ba07719a1f91d779c06cab87ccad2d10d0ce07e3e40855e3d3be71957ff28f38d16d151bc70a1c4073fa4e5cfe011efa681768fe641855cbaaaecae0fe88570b3508896094baea0644fc42a97c15a83f6c81e1e890ca8da0798948523f6b14885ca176316c36ed70f782775491ce88970f89285d6109c95265e72ca544de339ce95c4b890d1075c7d19e062c30a611ac5149cb294a16ba1d703008248d839b56e7d3d1eab337ad462bf39e1ca7d4a9c2844b7fa1b86cccdaa1778764b39dbcdabf89040fceeb344754979dae33b2033a53c24f8ba83cc915a7230af4ba30215f79a1bc7dbc4e5a5ce15796bb7d5a76bb3e144ec0b90cd440a6120302dba4321b022b688a7e3904a35410e81750fdf221dd8b754d39860084e27c7ddcd753d4d03ea202d1ce422a2b4d2327efdd3765ac31c6d44275412c99cbf92af2bcc6c1a44102ca80fa4038c2c562123c42d9685596de8446fcc1b42767967e77a3321dd13c75a4658ca1854b552cbaa89a05b9240eac5b2dccac614aca6de337bcf6a47a6dd3950043c040fde541dbe5763382b025725eb4d11cd68319769eda67318cd408a579afef58b4c15d9095f0939903e93c8b229f40d6629baf4b8cdb61dab1941b7ad9788644fc35628dd14aef52c22012868c410a87a8f03dbf5d068348b7f6bdcdf32ebf82c4ea4c0ef38ae3bcf106ee31b9737b848b150a3eeb3946ef8dc688b868cce4a502e7878dbdf11b410205b7abe58a8cd4da0bcf622ca5fa8e8b97c80ed787d5d42e266bbbf61f70e70571be60686090f66f2dfec804f48fdf1dc9094c5d96ec0ace13d9e6a86d6e945bec53bf64a6f99d70c089cb0199a4ff711744431a0b218ce766bc93194c7ec82f61fde2b255803d58a8057e6adfeee90627500a073b5a95f971021004c259adeaf8e21ca367345a8231e2eaa6bd0b192469b5a39faa743537776dc5d80d3b88bf8efe85438cffa37283ef27a2804f8e28c69130343466dc15e293eee25fdbb583ca9df155566ea0c399aa40a096fcf90d2cfb81f8799dfbef8666ed3a206c549b1ca1c4121480d99bdbf4a011dc9935c421a91b40e3c0b518938139892c06cd8c21dc764b51ccdce52228b0148e46de93fea40ca261fe123d3e5366b96623a03b2d9e55cfc68d0e0bb189aae64c847b9f6429c2ef478a0b2545cd7124b35f984b376c3a3d417d3ecf18d9637dd88982588aed19acc54d1cd930b5d95070ebd00106427ba5b2e43dd6c86c0da8f63ca54074408d2ad559859de102055a1169a993a17e512d56d9d2c7eb37613b4cf02cdb3ba17289195a0006a663cf00271b2b5652ca98526901ab63ec4ee780d37538d525bc60c31523742d400e2a922e5bd87ffbffa0ba3738088a91ba500f7a2f8a25ed1e39d9025298b4b04fb037d03b054956324e33588751431992e07663ab2b86ca76bc35645d795458415681c19454c89df660a19c1ba2c4d30ebcb2cce595128d35ed443d1479b7ac9051c033012cab89742c5505c380449878a38aa7c91036fef0504d40450f48b00410aa5184ab00ffbc28cf565486c4225d31d46c9b2096170367e9ba1cecbb953e966234ab8f83ae8e38c83f46f34f2f81494ceba88c6c6d20e8ba5faa3799461457357d45acbe079712b9caa730fd46594b43165dcc1cb3b059d56aa7d88deab174ee52ff2c45518c547438cddd4c55ce28f2af2b9c8ff34b91af6032ec77db7079e154a9c3396582c8a015f7c421e4f83bd66c516f8ddfc31df0004134325237dea193fc244a969f8d1f04c596ab150049a8bcce492ed378a4b5a94e38fe417546afb7e99c1d741006cfc255a26d15c6bb5456fa45e46905eb874e7b60bd171437f35d3d494709ecd2cdb878d7de4e59d5b14ef8fe02fe8c87c70a3f098ba650397063244af54b0bba60d18667cdee6bfcd559952efc7fa197554593c35b9b85f7f85212ba660116253a2e6753bf2dbdcff147637a1256aadbc90708156646ac3b909841c67a0bbb407943377511732b8a00850a81e8311ac17c29f6d5f7be5cbffd6e7830dd7351a9ea95baa6c5dacc4f16f0dd22b6a248d431bb8f10e06fe91639fa7a96bcc8da78884b831e0b2730b551ca663b5bac2e4f76955d7f4b43c36fa0c505fa6a56ff8b7f7a7917021a6dcc6b658e63c663b2a17376eea9918cd86b8a8b84f2c939829127848ee50b277a9caa1ea9cc94f6f0c637056cd61d5453fc8536d49737c92ebd6aef4ae9f89e5a970160e0b3674d814682a8fe662d021af92dbefa07b96952746474b506ebc2fe305d823efb5bd2651ff8baaf23ade3bb7445a73d35ebb000c235f8b2e7d733080b4cc553387001c4cc9f7cacc3e9a59562effc2a9658d50461b78d0f28b621de27e2728031c5a133134612899fda25dc4c853d1440807b1d1e1ccc2b227965c5b1bb4df5f02e5a72894ed3433158da1e6b4ae4ff4ac4c7131551fd0fc49edf6df5f85d33a946bb89853d7f7d177c30ab44f4054adac3929728ac2102f913d4efc546d9f8c6d9dd84f707d3a0e25c265c58720b04a034100014925d2e35dd693f52caa92017589d369914941f4a9e52c73ed9ba01a3970195efd5c1ab7a9852b3545563df2a20dd8c02b35fcf367123d06ffcd1ec09c473016b3ac85fe47ac12ca3f2c57cd7bf8940d71b4f59e5abe1df26facab4b4ba797558b7aeeff90da6d4694eeb1a8366576d379bd1fac53839fda7276160372a2ae55ccafbd3b16b2b01d54862a00eacf8ebe7e29de96845ce95896bf6b6365a94c03c7fa04821b60bbbe29ec3fb4c8c74bd428a5b829a771341685d2e5722e5544ddae546a5da9406226b3e220185b17a4e278bdc7bda1de48fb9fc6753993d108e79d9a8734753cf9fb174e5e4ed67a25dd83674e15cfef89bc4a73ac5a28c9df707f4e8c1f8070b8c3ed8aba6614d7801415d4b7896612d97de1d3a8c64f11ed4170338b0cb607abfb42319f4712b5b73c76611a599b049ad8f47cf1f9c316149d593907aec7c8677e28f22e2a9f0095545759c7d22c37f30c370836c7d35f2bf587425f49db78d9a005634cdf569ec502446980537e769d4610b8e611bc2832786318088ca0f1c0561bf3e1a908d55cd8492126b3c5212237470901607024bb73d30e2508ec73bf6ee21805251b2b2f2a1b513786a12e33e588a6450c7836569157c21c40fab5b36f04f36cdbf220343e02735010f33f3705e94069de7e2708ad63d8014dc83219a7ea20094e47c4955400af9570753f2dd57bfcc26010023c0cb1c28b472fb4955c4dfe8180e7160d74c9637483894eb7cdd925ece5610f4d7207a7976e022def4a74c0d9fb62db47bd16400cc8d3f592ce6b508ef4c2a47c2f78b07deb1eaeffcff9de8efcdc8828100a0f11adc411aaa25fdd57a6b885cb6af0c73a45ef76733f80ca10a170f0d7b90e279f5a44ba596ae8f6e2d72a6781f912e0638ad28236d368fda7d0dc7b2c90964ac9c0b7b13df8e5dfea839198c3583ebdf654781a5f24ec4e8bb1f4f2d22b5ab4c83f316b81b7e8329cd263e06c2f156636e664e45f21a9ee97d012fc020310f8d2d8499a202b7c29346797a358b849d454c5e17f1c45c43e8f4a1236b286768fef8577c266718655a526e3a9fb81a9ca5592e750a785837bb7528e4a5a9e0cb8bb3e8c2f2aa9059e8908703dd1a2f126532a62285c9e42bed5a5651f383808e9e2a3e6213f8dbf02fdfcda4d047f36a1779eee7eb9bf5a46c7f04cc0892ceb1505ed0c38e630a29b50251a90b75b97776b195a162727afbd2d455ce9d5aba56a17ab20ab70f48e8297d55f012fddaac899a88515dd77d6ffce31171515f390c8849a8598f39f0f23ea47ff6388ce5ed3a158a8647bcb2fdc59ad28e2630084031b7c82ac56a97795e25a7fe050a971dab86df4ab18c3ffea787422c365c1abb873d44c391f4fe735e04410476ea8dd8e8a9af0a0672e3dd4fd612d80e318f60eba90b3ef61eaa6a663b97ca0deac070e84ca409d295cb9748119d79f84017a1c3b99bd4685df2b4a690aa32cb02fdd218039c21693e5f29ae875eaa1d4a05a8b865ff720309f8a35acd7dd32b3936488ee7e78f1aab1fd30ac88caa754e80013e7ea96d2eb29c6e96e17fa0e26fd3a327a92504f76f7b185de703fe26a9bd430ce741ec6c6feb2c64310da2976234d687d5e0915bc3625a461afaee84d610ed324d842dd8ec9c585ec056a3753d6806e326c5b557704f4c6c3759ce5753ba7f3927e05736ab9d06037e49de1f8ce55ef4eac186eaa77112432d6d556ad2b8ec491ee695ab61be1e9091ba8a943637d753364231cf21706b9f8fb341abde2824b42896899daf57da47a44fa75bddcf2bd33d02c9b06a9b815729591a48f2d9daa1b4930611f8e9ef9fc60bff9cd5ae986224712e96d8ddec41d6719cbb47146b814088b757717731b0710757b2bf6b8a19dca4d07f3a55510ec6b6d25d40a717d86b24e187bb142e1431955ea06089c5cf9c9e60d6e12b1d766bb8378ef18db03b8c74651159c4cba23d14a368a046b91b3296477fe3ec47fe8fe24153cf1499518c73a1ec4803ab0df4bc0522800ef2ed3c998dc64229c77b8854a4a837e8c1671d6a0bf1a08654c98b0671b8b81b8238f7f82c8dd938a6e880aaa976f581be3b07fb297683d71788948ce8355f7e2c978bbcc7d0916f8ce6d33b8a319ff362950b6cd00d3900448e9cc8a5e9feebb70fd23ef91c3311b77c4e38d468ea2f6eda0def58beb38e616e163a39ae0257b6f94926b6311cf98ec8be9f8cb252bf27e2c37bbe4a3eaa9545d02ab2a870c6578235279a65210e3de9a5800e7fc52aee711631ba94f0c4cedb05c8160278cbe9cf2b6b02033df0e1ce562fa31eaa2e7cbaa64d11ecc3a3131a4fded0d82f01c3b853e86b360fa2cb846d4050e96e428394d4800de9487de74a5efc4bdd5a1f037d1921aa4a5b480bccf36621707cb19338bb72ded16c79c0f198294ef306b67a85ef1420ec4abb1ebf18cc6c7ba13b7a91d5c968a0b832e979b1e5735fdb60dfee7cd35d04e796b0ba6610dee363b2f31aeb368429ced04e91ab8f50644c7773cce4c43dfb6a3363ea11bbea8666f982792281ffcd506475b5a0c4e430e035d452b394f3159fe3459c60ee0f6a4f75490c4d378fc5cb800cce388a417c2817afc5e91210620522b5fc2d696233168364b253d01e1668056d8b46a3ca30f8270f3b07bc1022d3aa4da1aa90e574b1aca27873e2166695d5eb84f1179f96732b12247b53e3eb90afde40afb7b288a6945ede287347847a4fce7210a22463884e31c3f5850ca781e456db2eb236205cbaf23b70dfd917894681eefe13353f0fceb2819ef19d72307660fd96d1e796013f136f74f045956f88d476b59cdfa086e866ff28b9a597e74d8991628bdd04c269276349e94f37d81dca0d6c5e5fe568e5a6c60888786153903c58e41a8589d635e2130ba4db17422cc52c5ef8340042b4e503e8573c73afdc5b4040fc5931e85acac8d89ed491c6396f7d90d5a3b5683d9a619c07189b865a413a4f8259c413a9263f0f90d62a344346448b8bf641b33160d2e4c98543d5afc075dacfa2779bd1b32523e562235f916439f20645367f2d74ecfa89781220fe04ca11cdbd05117eb6ad83ed2f956bed461c6f4024aacb9555ca7ab108bac5524c6fe9c2b24a1a3fa8197c753d47d1c707098c370d50c82420b82f467d62023082df5bd82c83744a2fc340039b0e7b84e3a6514fa7a2894cbea2a38d3b24d20dfdb143aab627ee3089d34637a7a87db081d737c72987711fae80fe18189a616621d9ff6d84465ce1009ab5f8b293b50593b100b5fbfddd91adaeefa5ff7e1d91dca93e421f3b9f3b7c791a7e9125aec99ee520dcee8866cc6e9e687bda09fef18bcd925206977a6be013f3c6fec10dd939f86050908d7e0cd214f80207351dd537682ba6ee9077f80ccdadf62c6eefff4430037b5995986cda03313111d988450bd2989658bd94172f673579beffa577f9b8c2f6aebe613e50d167d8187a51c8d124c76fa12632523dfc9a76493568233d39b336e75b361ace059ba5514142806999b960680fd70179ed14b72a2bf084109c78a286a818cbf000f0cbd49d6a63d3db6d6f37119c8fdc9733adbb99f6e4ae02199125b2eddc6de89f6b2c91aa3d8e13f32ca6b0d4fa802cb843ab3ee7f8be4652b94dff3967c18d7c6fb6a23d4de131057cebdc2eb3d429160ec9aa043b60b81855d9ac68b897a82cc67c2a70220341f656f93290122e53d9ae670b16d995763d69ef655389e746944252e748c7e5b2fe69c3dd5b4f7b0c1b365cfa8801dd3c08ffb710c6f7db91ed3f09e134bce320e5c0c37350250a6e979967ef48b0ab149a0a443a2497e323433c83598fb336df40a776a6494702906e72d6967885604f98ab4a1b9cecca2d3294f06c2d46d1a24c64e4d52655390dec898c6b94c174ed6580622905976d87a2a524c753b86981f390ac69ce410a9b33aec32eda7f3c9725a7638962a52cab99ae3fd8a2dbc789b7cb66e1d8e7f7b0a5c34294cbfb8ad01255873a2f39abe730c7c6c1ab4e4145760d285b05a7bef1c8da94bc9bbc4bdde1c46ab852f50763ad0e5095851e89a294dbf9187045c46c4571138fc788d512cf7bbfb2a32c3c41d5b558ab2da4271735a374b55612823a590103f04cadc9e4df1004667e5c80210c4e55111a0b5b84e0e849fda64ea7c3259c9a8268d3e185f2bd9c702d3ec803d6a3c4d391b07a43881c08ab940578d254268482323485607f4e9aaecbeb89ce15f5234dfb70f035d162d6d1dd3bd32bc43dc58ab12daf67841091a334a27569a08b6b18e3a7763549db4583c246a91b054580b243d04081d8b7018014eecb3a54e876942170433502f2f69e6f284c956dd6286176cd27c66646f6f271c5fd1c4b1c3108f4635488b457185dcf207ed46eb45264acdd9e11168f4f24d27315d31775203c60dd19dfda6404e0a03d986e4ece8496a92cc2d5105c735512da006ed29e95e5c1961a36abbc4470a2e178f62952b0106ae6b196c076368d5cbaa1c61000b6ccee41e109c1aefd657687b049320f979b088fa164c364ccf5d47f45fe99307cd22e85a6b5f8393bc6b088e83ba968686410aa1edc14f376bf8071de1c91e5a1277814e2796345f819283c73ab9bf37648dad96df2c93093a8c6b460250ee79e822e30e33d6aa02ac104e562e632c4876539886808d2d14c12b8272a2a460e0af9339269f2e53603bb4f42da3c2878b59ef41a0206e9f1cda94e7b4090d359a407dfb1714a8e3a48ee49a177c526792ae1af7c27a60b59881b7abf3a9afd129a18939c5580ec32cc60308447b9c3004e87e3448cb08c52348a8ae514150dd894054aff3c6f4e308d0fbbfa71725f53bbe647546c5e131214efd2de9e22c8077bdbf6fd449da96e7560dccb6ac3ebc43083b7712d44e66aefea29d42d0c769006913e75d5daf26cd1a039d6e0be1536f686023309d928f1d5dfeba56fc2379ea6c8abaf8dc45bee0552b5ee27edd51e066051147e0bcc957aab6ef07ac059fe8f4e04a8480a8d667b02e830f6660920c1362b2d3a2c0a119c58df6920fb3dae906e89d259bd8824a3f89f2fb2a86458b3e3f125f4e5812e2ef01ccf98c3449001346f5f2273c20aedb749ea474eea4d2cf756296d48464a510b1203f4c34827f6059ecdd0300f5a5d9357f3e2b731ba5502925709ac5811e654466e9f419eed7ae0a2901942cf6f544cc1fffd802848b50632a3a7762d7f6c3091a64cd731beffc634b06c3934aaf75e85bf7366a8e617b4af7e83189c693d0ff641fc15fa952f25e68d1af9219b4b5757411fd08b4ee67029486806e3eac45688dcb66d0786699ea48284e2a5b0b5c865e9228e5481014cf5a3864820913e10e18f2225fe7843ad0dde818cf880f9eff8768099328d2a58bfd67c45ca2e2bf1915a802eca2a23082e7f8ed86b127ca691aac3d6c43ac85651eac220e00c6416c059750edc9e49394d71043f8622cba5b7575ee6a72a74b7a009912052a9eff58b6190b4008c8bd0596f0d6a5f2560aafacc4b3adbb4e96db61dcbaa0d30c9c9e65ed3f1164acc54f90d8daec098b3a849c3578f16f697a9d48b75c83122fa5a0bbdf2d4f425ca89452e50bf7846b683c23a531f11687a3a8b1d9e8835e5e8479e939ec85d0bb457d614322eacc098d57312daa14aef47707625a9a5571febfe49123119baf5587b2f7eb78b2174d9b7e09579ee70ca706e8024c58c496e4761de65932f90325797a4578cc64ed53e1aea1b99750cbca31caad7748c895677386e58f795e188217814c2010c0abd7f13b34a0552e9d40357ca4d6a2ae19a2eb1c44d7e86bad16a7f170d1974d360760196b93fd1e312e0a6a464577164e82b4f89fc094184dab0294250e56b48b054bc65aba7830280403fab2bded9d0b8d1de77417575c32921787f4d13a9ac06213a9251233dcf4668c4cfb6101f00eec685cc6d14aed21153bb3ca3c7a6774828dac6dd816db56ccac3286ba15f0b641246a4ac955a414d31e47d01074bf2711a0584d7aca16dab0af816f5a3610cef8e9963f30b2b4702ace9aaca4ad949d7c6349ac365a72611ea74369cb012b8dba957364117ff06747418305e98feddc08cc3870651da2ce6e00cf1c5f5ce9c4384d27c2fab480646fa76bbe879433a7481fc68029a690bd84b5270f00330e375579a56a7c52c7fac51c089f92c6b1854234aa2a20f6ba083b3f214879da447320e49b94ef6264fa6c0e6d391af7c1488226f0df160899844cd669841c904f30dcbdecb53918da1699f017f58c57fd2d4e6114f3d402b4e699120cfc191b1033cd96f061270c7920a0697b11610fe044ec889f1863fd67c7574f9005536571e687f4f1955cbed493099f3d235ced1b883694266a08282a94d4bc708ea8ec99d2eaf0a02ac9bdf6d6cf93dc77e46b2a79db375f065f9cf5483b099cf0d79f7836e16652e281c9dd41c4ae6d0cd19e26d0af3afabcccb1df41e443c2429c408e4bf17c161cadc1eb8815dea172097831bedfbb0d51851f954697376b90619fb05a20582598452fdc52bb93136737b2b04b9ef8a41c491bff63942b2c6fe0e13890cc6d2e5c67d6517a75346c44e443ff77d5a38a81ca90d6cd065c01f5520e6da6a0778cc6c57003099216492875f39db68cbcd1cc2a4cc958021e8639a522de1b14e451764f2fcd3348b31cd98572147a401e3065167420b667b0cba49cc70be14609f7ea74a05b8b5ac7e673c923e35c4ba1983634e590068fafd990212367c0787924bf042d8b7f98b8654d410c5e7076bbad438237f69fb55878fcbd392c8da9bd7019f867e4f542214adc4611dd9327b887422d99172fcf6e048606b2c23c812d987914271b30d3559e16758d9a45f748a2de8d05478108db8f95e38935d0bf54d9cc9f61d42060a7b414ffa89729deec9c2022203ead130e0ceefbed28f9812440247d80947af1dfa246c705b05b2a6dbd5e96dac896d5ef21b254b047482dc22fb49d1852c07359c84d7918740f5f38e597ccf300c88457c248e69e6ac277b0df7ae742296e24339353b0a6dcc4b3a10837b9475df7e36e03ab2108638ee08c436596a353af7d3799f08072dafce23efdf1cb96811cae8ce44750a464323e249cf4d7ca53746741a60c3f62c5adfe0997d61404fe2f4b8ea2f08baa1780c20ba2ffc04cdf4ff774dc3f9cc5c2a845fd1016e1928f682c4a56df9d90cc43eaa9bb3508a9b21d6a7dd2d33549354c15737f065a3089f683f66ac07b7fbe8d574ec00b91b0b8cdb54a886cea08516669421bf8c4dd5de748acbcaca8467709b6778999177be7ef16aa4319b220fc25f30e03b06d21510124b3ad76cedfee0c30762b4a6a2190a37a3bdecca9b5568b414a7208dc3eb0fa34668e7de7ab0dfea70d891f6646402c12afb243c083ab075ca392f43c60da766c8b5eb361c358b9b674fffeeec933b177c0785ff8d757537777c71fd470d6c1b92985e8bcb0e28d891cb83d46d59cc5429fd81c7580bb1eb2734fdde390ed754b470de9342fcba8f6829b6ce713828a9b92a3eebd12f3e6b7ab026a3bc45175981c503e4ef66b5c840acaff98e501520608a6f67eaa2bc3156af3b369ef9fe992a76a3d2312cb0828b47c5e80102591ed193a05bf247eb6b6f3241d884b0ea71b4abe50f4a608b88a592698b88f9ce7fd6fd5b42727892217547593c0470b5336ca2dbb078a28033fab9ab1fa857a7a7947ecc5c79b8ba9444ad8fe2819c1c2d6d2ca28eefc5416f191f91895f35a5ba33960da1da5e71f78164c4823a5e38acc7182d4da64500d4dc84f71607362531d5fa2b9bb8934152798d794cad88c6eeba0a4540d82703f26c9cecb655379ca2d829296c7e276a434daa3c3ecfe06fd3aa786a82d0e430ec8a6b8eaf33da26d17919ea312cd7a2d5da24f3aa0456a158027f57311bc257c056f11e9db17df0de95ff1e2344fe4280a14b8c20077f97099c3a87aed1268b39c3d0da1f21c077c10a541c055678bc60ed9b48f82487ad801f2b2bd5d2b46ac23e8debbbc15dd5c61b8af4781781455b3023a845aa8a53205f432a0891efd86c57db529d70c17cff95a5e5606f5d3aeffd8cf27c180c7949256dc382dcbaafb11dd5cbed44fd8ca5d88ba6dacdd98be7c6e76ede77bcfb5f87a0f7854cd74a585a1426d8e9b539051dacc8b4c5ef8f1cb72ccc6151b27717898956b3a400873560f3105844ef8a65c36c75353b913299a8d47225cc4f776f0b9c8d5c028a8cc13c921fd65429a4d04f58dc2d7820d37c8ed4d49f3dcda59dada4da4645012ef0b2f116721060f87051abd34780a06ef485b527ec299923682ea36163863722722c1b5e695e0f9557c2c76dce3385fbc160280f8ef904418d99da469df9ddef7552a146ef085f8898920bd9a49b56a04cbe2fa994caadef5b6086c8eae444f60c30d741d9007351ff929239c860441dbc9b729f0a07d4f2f46f59234ec3d805ce1ab3f8130f119181a9a03a41e6f9f7a071a10e0cd55c8a1a083d7476ac0146d0e976f8cd3bbdcd0970d857801ab37364d152c8e5d225ef9df929ba2d48c19889b50cd0cc65337254d593306f2706962b9435d5c4cce23beac0b6502c6830d7deb75c89d24c890901e709ec93e16998e431114b91d3111e714fa9417f7832eb648f588304f05cbada8188975961b0f5f14cf35dc580cc71f7faad22ca306d3022f1e6b8b1336f09289bdec1a8113d3ec10a3207b0f800e8de58259d8e19450d31f3fb0ee88a29f45044daa0a54d84423ad4eb89622f8ab49da9c60d10f3fdc154fc65b2f1c48f63bc272f90491968c0848e0d4d2a6b268d28a8166a36002a2243f628982f18d14627a17ac11730cf8e31a32b863b226b2ba26d0dbed58eb77fcc9a1741f03abff25d8a52442555f91190b1699340c92ea7675535d1968d2599369a6e89a8164cdc8d7ba8bdf4457d1f81a8efe3f258ba75540823d7e0b0be0c8f183c8ba01e7a1ee13b6c8074482091341608bcd54cfe69cae70f1700cbedd19dbf1b11fbabd5f27963b4c57a8205372a370a4051a7693e05ef34f6c829d86b657d87429165fe4b03e84784c1461a27c5578d6f64538339bf5bac204880e1017592da7f5c94804dd3ea3455049ca8d049ed0d997cc58b444f080e1df5f685d63b2ef0cc81c9cc842d3b8ee00f8d1ef72c4c7048525c9ea0adc3df1514442a9a0a831e8958bf569ecacadb77f44bd0c66e1b9202689211c23431447380990fbe010625372140b1f330ecd546d40836fab6881a0ec4aa32c96ed1facd776850c4336a92c94b058d7689c60f355397dfa0eba3fe04764ca9e4e4137c29e7fbda4f9dfac531df2316c18559d80907b8c581546c71a5d236a68d0198ba100049c569b66152a659fe1a00559ef2726e0657012ce2e725b221759919f9473a6694db75ebe63e463795fe1fbfb5cc008f8187f650b0a3ecd98de21640527cb3637aded478333b4a24d1bed7ff431b3907fda1509227598e729b49e29cca67d3586690aa3c072cfac41fc258988b55ffb2f3687dca41a180329989ec52028d6fa3335021fbda5c252053aa1b20a40133289c0ef39e707998eadee8131f94b535256b37a06e7e4fd9bd17def7cde7f0f8e027f10d539cec747034ec518347e31e8e594e2e7d00109d0fd8e918e67a2cec80a00dea7e5245ffb6d130de0ae15b3f43964a1f8e2282f449ef16fe36b161badff9f62cf18d01cb21d2e3ab0325d9551238d5e4e0b8ab7dee0936e8541fc50eae2df9d56eda0424f8e7002b00ac0dc5d2690fbf9ade34c0b46d7fd8e7dc80e65afbe2f88665e2f1ec3a7b0c907208ebeaffcce70f471a4434dc52effe2ebb23525583696e12e46771c31dee74877322a44e083d2e83a032f122cd81f0549a9c55578f15ef745ccd4b9c777c1ea6c58f912c0bf0e5a8823008eea215e963c830d36135cb6fc0278c0ec899c18d1b92fc7bb675d207a67031d65974e001deae4280de8111dbc8978d209e12050a0ac733c615ca8f5bcd2a87472431844f855ba2b785d32e6d5e0d23f5c20c68796832a733ff4b6b50b3dd5554d869a0d0b19227c3622a7faf21cd57cd665b735a2b7e82f297f5b02a8c89cc31d58c9bbe3485da3b5300372f0e879be21d0b6423bee7212401c57e55504fe3b7f83badb5585cfc875bf0a4bc5e3c3d47ef9bd2b17bf961b5ada11d2c00ac865c002d162edc2eb8c56452ad6b4618318f770ecec2e5cef3fa9543197a96a9c031a6a18e8e51fdca7b25d606126d34a83567fda7cd71517ce4c0080d64d86ca9eda84b3f679b32dba62dad73f509b7d216a38b63954aea8d2dd6ff795c30302def9dc51bd8300028e0b3c23206e80530039956a576ab8d21ee4cc3b1c3ee2f3a4a741fa371086c28b2ccd19bbf446cef963f5c848fbc62b9fd0bd01f95ce5433b2faae12f3e48fdd4de75cd4ad6c35fdd26dd68270dcf7c3273c97e77f77f8295bc09175922acbfa3a3c63c4719f0ad0831e1b12ab21b78c954e39ddfad5ea208d782f3d97fd6886b4280df7ac9ce511f5f18cff1b785159d66c8cff7382b7833db7675153b5ec14658c01dc4c9d949afd4d760300d6a824affbbcdb30ad74b23dde49cb432665cae942063500c08358f4c7321f58e99385fd43eb4096a5479a3a8752c927d5f8b1bf2df58ac59322c4b3160e4915e9ae5e71e676b4c4f5cfb5bd2f56114c933b671044ad4efb0fb156f8f617a7bbc75a43f1fdaea607c1ea75bc79951152a58cb8b11400f5442a0985ca9996c8e16802c5096ecd0a3bc1c2a0ae19e2005a6c762e5e7fcaa932e9e3b882b265c89f29fa9798596592bc79038d897dca2741cd2af3995bd5c8e29d09211998fc6e513246308e6351f456ab4785972e4586d6a3cf9f786451a1e0a5841f13429188a0b5db0db965ecf44ee9769bd5c346421e3131a0be31f1aaf453c44e40ea0874820e6767fe35e3a5e122008bac4a3117fbde531eeb057cbdd27b28bd1e704ff05b892e76760010479f1e04885b72c757b1c3ec35d733e526582a71be415615393103d94f8d4192af631d823047b2d120c235547f9895a44e8c64f555a5348c59b1d469977f283e514ca49c48cd5a075cac43a5d4fffc66b5df9c4b6d1efb1fd5d0aeeac1ed4ca6460ab9a9068e7b34849128d5b772d4caaa763ba2c57bfc6a4f230ef3fe925a722978e5a95683343bf86196e1f7038ad780d2efccdc5754a23ac4635bfe9adb5801994bc9bf61d0cf2ef3bba446f5a01fe3054bbefd2feff829d6e4ed6f9ca39124f7ff959048af964366c19c9faf5a7e311dd9dc232bf545151da16cba2b7ffbcb578602073c139a29b4310f6f4e7c550e3bc8af68fd8d2ed8808dac14a8bfb875c411d8c3a84cbf63ab8aed74662c1efde1371fba61c90dd25ccb5b5eb1410af24b752ae21609a837370ddb1dd286bbd33a912da5a54632e17aa1590349db615703470c8cc5ba4da06a3034fda5cc9ee7d2550c412febe2ac53caf42be08792ff8f1c952c5fc126bc80c09a1323f99623c54282d07a890f31bcfdccfd55b90dd1605a4226daa65be75e51b3064813c2e69f864d90b67799f78a1e818dd9477d747c819470e15b3c3be1cbdaf47c577598d87d0201125423989e098b077fbd1b08774073f4ec846d15967f9dfa99bdf2912083cef4be28be61a239f5e531a0627c739f1d0eeccfc692fef015395130e7f8879dcc82cb24e8ae96e304c2c86bddc859d917f7cecb48e946614d97091af93a37c560d59bb9450debbd921fa13fabaffbddccbbc9be6e5e46185a7e6598cb37b2185e446d1e010a229f0cdc73b99d8c3feba72b651b9060228b7ae634efa101df94b8f19b7c53de59b656656d5afedcf4f27921af2ef88e2220b6a865137f86481f251bc85a815e552bb3d1f35bf12688d7cbe45a4409e430a067ba3c8e2a57c9e9941721e2bf28243d1cff37348d41c6aca2ce320592880105fb13080e3807cb84136b723aed6ca23de91172a49a99d0393c68cc94d654e77129d2f0f1761beafc9277f6255c2d772e22120fb2ef324750cbdb7c84c0d6c59c1220ae3f3bb635aeeb748e88cc4ff6e3167845234d12f6db8840f1b80101cab5f62d0d40652392113b1f3f3438e228a4b81fc4d483614638f00e4d4c0cfaa60e9008d56716060470a6df7c108722cfeaff053be4f333250306054c3b3019b68b92cf8b2603332782ca68ef41c6092c5f05f0d3b5da3c0a4c42ec2ca6c197a0ca32804c95c0abf1a8a432cec2b5c5dcc21aa214a484301ea87b35d5219de68e765a0d56212d8a73629575f61f4640813018d7d97f024b10a19e659102ce176b891ada7f86c03d35b0680f0ee10d2b2e8c08fa198e18d51eb9f11b743084781bb9f4b8f7054f530518c86c3ec70fce4d038c49df1e0143f34fb1844117fbadf214a9edbf056a68102367604c783911fc0890a58cd537eb083d1a5257c7e425882f1f67aa2f7824ca0d13f2829a14a3e9dcf96f3c1c9fdbb2e00ee6326690b3f8bcf5e314c22ba57fdbce40058700e54241e129cea8537f4a0b7971791f9ff44e1081b475006e177587db50546678dea354c9d5966e0d22356f74008b5e1057b521b6c31397595ec83734ef762dde8b896ad764de795369d25efcf502024f8368b1968d1720ac3de096dadedd28bdc721d4c2df1885c16f1a2cceb6f17d2282629e1c7e321f00ae16301675beaaadc052f8cd76f899bd7ce7001ed08ad23672438878ad72b6b7a6b96f8039ae65d032f0fafff0b80e9cab1b3dd1714cb8be1ea294243abe9f7a3b69279aba274015f7fdfdc760dc516ed928e59c3a7cc9d5f6688557e3e383501df0b440dece645a393d345652b6d49219667d887a2ece1ba490fbb6390881a16626efd90e5e95269c631181397b703ea1b138890a72206ec34da9af25dadb22104607f35d4c9f5e6fabad1c5e7b23e061d1c551396ab30c8e6841f55eaefa5bc47c4f9b1763d365b9d112cd1b0170b9aa5dfc31fe63446c93c129fa80e2b8b655e4a87785150604bd914adb567c63faa65c5b6364a3dbc26b4876638c44a435ebd1c6c12fe4b23985b96be873238b73d7329bca672013d80c587eb1c4c8a6f5a54d8cd31728437fd381536e3b67153a2d93ac6492597a82465483402245ed7d4d45546766d6b7af343a3ab9ed4ded89c5b2837553567ce7464384453a582328d5293354c6b9c72f9a8c5f37e1d4c7fd956efeac474a2bef52ade254646685867b7cd9d38e02deaaf2d3370c035d3afd79843b454ef829a04781fc658716b1a363f219caff1b7bf7ab2dd380de92286155fd93c2c0e40363b15e48c147a5709339ec3397b53da504e0a9c5af5471ea5a550e732fbad7f56558560c4321d9da707d9dc84b8814d488ac4d5e0810e0177715bc8f76d7c6fb2af5d10203ba696f729d6f82e4f59794c1da997cadd042d378047d0b301b643efbee607cd2d3bf4df8fbc4d0dedf9d01589d8974456e4535c1e0bb019ca761cb24859577a00ecf58f94f9f6c23e1c896a2da960f503050d8506961e19de0c493130d5ddf38aa9263875e56f001cfc46f20c90da3a1e722377adeece689c719b8ea2770c7af7d14e4ca706ed260e7a88a777efadb2843c6d3cd19be949214845d5ba32bf2194ed092059a56fa9a00e17a2401a1ae9655415eabdb6549d75bf9e6302f08c834e09559ec28b7f4e053cb2da0a114a963f6d8ad7b939688302fed00b9694d66e7f25b110da18f555296b9a2143e4f9481f442a3c18a32a5017dc77f20bd12b909d664f9d89367a18740ed378a7cece4f1db79cffba16c9ddb1cbcc7a9bafc6e921dde5e1a017da21426c8f7801e8b89f4dcc02cfd8c032754fc4ee6fc91568364f77f5b715e1768f1f05892be0c9f108ac23ac0211379c83bb12b99b20061f0cdb5afe10c4df5ae051105f6044e3d02c9840bc98627481cba02b775e57105148090432a7d958d0c44a4be0822eb8224e1c43ae6a8ea7c962f033541609184cde0fd2876a4cbd76750cda326f2e01ab81a0cfa4967387d721b67e6de90dab9c6cc569193bcf05a1cac27169254ecdb2ad260dcaa3106a2ca9a61873645238ad18889dcd0cac06700a221bf10ce3ca12c89864125d5019a76a38da635b7742c5aa73ce20d0fdd63c87c36b55714990c530cf12f9561e86bc908ba8ae6329884266982bca85b10da37d4a32ecfef6189dd26ea7a3423aebcf460bd2c1d0855fbcd95c815bdb473a4fcdefdbc712c0472bd918c6db54d6b990e55f02a58a87aa4389ee35812e04dbfc3482e4691bcd0cf001c0af9166067ce8f52a6c8a33650ff376ae4e4ca08082cbe95536588da7b09b3826ca00d571332906360ac071054dd3c9b26f920bb61dbd3155c39824af4057c03a1c08006fd040f490b443f4a4ab49ef08bc33ab7e00486916d35ffafdc693459c83e7ac9979e721a9d8627d7eef113011f0a236201fd7c0fc66b66e06c4176081c78bdf1878233ec594d13f91ab7e18804c62b9331acaccfd1c967a5ba76d4fe07a7fdac77491aa7b2674686b3a07b2470dab69b3c7726c8f03f6a01f4fb8347b3a0df63ca8628ea93a0e8cb9fb6c5423f0a3c4103c546b4a724e12fafc0db9bfb5b41ef438059fadfc3ac7d914ae123dce769a198e1b7230758a3fa2c92818fa04f85407c8d29b81a00d2fb34238459df4b2842e2074ffdc0c0776e18d27d992d0b39fd3575882531e2010c05e9cc2b29763b7d880d35d2aa82bd0559dccc1b3d4665560de501a5bc683a737ed6ab4efc6073e7769220dc3f7f25f9254d02a4cd40e4e9cf4a81afc2e73a06d52c6caad40e7fdc108589a15a4f68b1f040c6375ff3c731d34a585ece21540471c1897a595b8cc2025684a77369c6fa97622846d539dd5842f1bb48f06fb0849974cb74b072a83cc0208d319d6c59449a509e596ec02f7d122a6ee905451b29cdbe9f1316ec98b83ca53274eee478ad77bb7ab3caffd29e172207ef6592de9511ed19eb95068a718dafa0f52c0d9eb9e01019d272de4f7eb7b8893fdd3af19c6b3686787d40c78b93ef0b40cc48525c8651652181645406a47546c2fef7a30d37fda84ed2b5eba6f6236d89699c6f4ddc5f4e1df608061261c024be125b49d1879f46744170f4c478ce7116b50b533bb151443b8108df2930af66d2a17707af37dc00d6de26d72f5cc33377a3dc98b36e7cecc66de08a118fc6f87eab74c2de1887177307ff974f3cf3ea374f5b7f41e45bf3bed8579a8e26b68a418f4022128f71c380f35430ac06bacad7891e68aa72d0f27ea8cc2295add51c09ba96476342aec3a151f35f8ff456cd6c00226f93efcdca23f911ade88b4eadf9ec22e2ac82269095207fc1879f9d299a8978e9f9094c97ace0761c8a40180c51f76b7a4ec564dac78e460a4854adb13398ef4096297fc70128d9f5e5dad7aed14341970d889a0daa81814a0a4d2545fc309f650327adecd1c144daff91ab3d7ecbc93743e955dd0880e662c987085c85c609052a4ed7df4ca6b1cbcec513204dc86a523a4f853fcd850e4515653e99693a38466b30f674719d529a919db1e4afb3123bacd10d3eace15cb4063dedcd408533583fa45cff7c71177071f67c965fa20d7f152b420bea819107d88e220694077f2ed907fcf47df313c68ff6a3e16d799197d701cf2417659a861f384ed94909190000654fb12ab5a6f11b4190103d26111fcf69d5a2ef319b499918aa6a2d98d5c0307d0f1e87d10250004a5304e9803da3d50c34e10a3db401d4440ac9847e843caa7cb12095e150d6e295f1f2468f4661aa0a907319418ffa2fe937b96455a9aab6030b305fbc8d6816b2549950810e1e9dbe8b717ad8d41b9965929f945d9da25c6440f2b98bada144e1cebece98bbb7d275b449c11813706736f2a2bd3652a032a2ed0373c9fa1a7185409eb361ffb4c4b9d421072e8251072d592cc65d01c6454c35642035f351be85c82728f4529a19d3484a2dc410ef4389751b6ca7ac047f092c10abc564237352d34a286870f5b570156d6f6589efe87ab07f8ee9b6bba7a40d5125dcb8e3f7a6904df7d734d570f28596a6ff1f3d9a591c3aff99658001f6b43c6f476fc6914da625fa9a73026b5c4ae3a451e7054e59172af3aa2525ea5f96b7dce00c2950a400b8b02650324676b39733a8d10a9130c45308511fccb0f6e490ff6920d8f6a7d811ea0c05a37ba8978c1f43c4618901c1c1b1e1f4622a040487c11d04c857d1cacd63c76105a2cb507c62ac92461f41e3d1d836788a4144a9861a2bd978660a482c10dac42f0b6a536b87ab6b483ae6f8709f797ea3a00d6b52670afc7bc9472eec6036cbb9b32b93d2c3220301f04ae307176dcd6ef2dd5bedd9446b7dc1a6face3378d6e3c0a9aa52d4014486ea1de5ec00c237d82dec817d7ccf3e7a7b9486149b27ab81c16ec2f64d9b6d12f28a5ae2f60f80b99c064d39db2e30c414eb63928b1ef840972d146c1a4e810c77c9741e5e26a9972b063b0c8a93b5f76d352685637b44e2fe5c0250e37a1595dfd325b6eafa8b6ba314c1ce8d2794713ef4d3ca69e877c03da4e0b830e3a3a2e886a96f960f9cb2fc1f466149748180c4fbb6f48af65a76edf58e0fc608bb9aee5422ab06f80a208c6f29601f3d9610818a989eb3492036243b07433c61bb69cd1464a37b0d11ad9941b4d072b443f63532565041906defd5d2a43c297914341d3a703cd6a63c1e52d100c9e2803b651806cb77b4bd1b164e7c4d875fe0a767703d5cab314d7efbf82e37440c0e9cd135f28ac440014861767ca3151f23f6dc361618ef315945ca75980ee64fd05ccf9ef1e6434ca61a74197aca28d4c40b050f473fc28f1616234f2d5686483e790a66843b71bead10091768953b38cc71c63bf42c1a09a44f7bbfbfd01887cde83ac56278ad39ef945595335fa637a8da407aeb40bab20cef71841de59de967e3e6a64af6b6ff38e08241d4e6f0a28f17c561be9d24e8d4de0b805205140d86c9312e90eceebccc810d3fc84be8f3b359f6df9b310bcb6ad0d2ae5ad4d0fddfdb14b13cb15362e67a13f3cdb93c049ef1fe5ba4ea812c13664cccac5b895c83a6f394fc21d57cb76f3736d340978561f7565c0270eb3ef483d7d7149ec9db216776bc6c329b5b2be762cb71ddf9867a815fe3af89636509be01ae62432530aafc60f617e3f581d147262dca3ed2f5be1b9eb661d98c711f7c766cbd150d00e4b4342d27d27ead303b97e236e3492443e01b101781804fa26fa947b8ead5ea3e2464fad2624f729ba9bbc94854c652729f9ccc5fa7532b550346802cbf5da906555f2f4eb040f7b898544eded562bf10f4a7ef858433656266a40fd88abf7da5429b8bdf9c725445d2208352637511399d4a2707b0d0d871fac60808a8f93a0d33182caf6a966e6a5e7b19227896fba172d4b039db1543bb303dda812ccbc05a84ca819d289ce45013924de2068ebb9b4638fc052e36dc6398ba813a799ed0c5fc60580a102e5c1302039d5a709190e076231dc09019f3158c07344a2335ecfeaa4cae8516d5514c81d4272fac759bdd3966f8e7027377be3282c4f758f8e7b6f56efca4b6751b73050ce1ec20236cb849ee4b315ed3d8155aeead7fc01fb758400f08360ac68cd591316d02d5b13cb817af3ca415655c7c2ebc02ec81a142e583c8fb867fcf6d8454159127fdc45b66b5c57d6b4474ec3ae604fab56d3fec65a3e5cb80e30093ea0470829d2d88c97e61a6710808424e77f9f455f20afebd39e3611f22f744604e09c1e0d0f98ee9b50e855443e030f91668a9a21be8049bcbe9057d502d271f46c0c5ac8ee09397587f957cbab9039c0fb275d7dee16b449eef51c90be4656c047f549f6078063a24a46f992a4360191ff2c127ee643443720d6a6abb6b8bcd308b8e0c55d3250f19026ae0c46e5bc2b424c9d9e7591ac6739034ababfacdc7a205fa8b8a911df3081fb9dd158abd6b0022b396497c9a6db21f27d51ba8f4043357e860bf4282118341358c1e5d607452b52fe4e2a4b794667c47276c5fb2e18e69fe3c0dc0c0b4d99403ca364c949fcc2f66146a4efa269b1b164e0505cb3d1018ab045d6ddfaab2c5f51f3c78a75c7fa751b2bc93a4a77aaeaf8546cf902cd8d7a66f945698dda9c6b6f760d543d38a7118029048790b96e3fbc8a9073b75e22647efd32a055b79a5b5b513b95f01284717d6ed03cd6f2b530de9a753d6f20ec47d2b039d34572f5c2d24f71957e6547eaa58f05429fd0c8081ddb2c0e4d55661df80767be1aa41c19e7d027533b8d6271545616639a52906343197153e4a7c9d1cf8def8d2cccb5a1b88dc6f8abdd831623ea787f81f5f62a46c7810bc2020d9e0c20f0813af63866d4ec747542b2cec7d11e105add98926034eea60c2c60134ccd711f0d4251a581a7213aec0e9cea2c969fc82ce7605f52a18ac56369f0cc5e354261a414c77c971a53c5d2081909f661a46abad807d4abc4f0615c455369ad3684e5df0943a430d96198bc512b35a679af830af93e01381460778760881761b6bd5d62bad4b07b8db36903b23c4d22307ca75bf130dd815c3b363b8be4a63be8db98e053a5bd850bc620d18c9dad79db1196cac801384b78c52db91f77ea9939caf424e0b1174bd27f8154286329242477235c90b74c144e233c92b9b8c6794a654b5db5ddf520d6c17910c52d638d203858b4dbf27fe7089b8218d1d401ba4c9eaadc8a82a75e1b13452c69c8f09403275bc4b2b9519009722c4855de46a5c583e19c0894f648f7690e8b99e3b84ec71560cbeb5ace0f0689e1f6e23b98adbfff145b0f85bcfb86afa60b52ef549b269d94ada27fe203717b204118dad2e163010ad758d1efbd800bd85657586503bb909608fe456c33445201b79ebdd89d1996d7c8ce122f4d3c01a3a5956c93382caf54a913f58d6dccad7ac0c2cae855ca1f8ff17786bb919275131c773e512aa7c0fe81b725331468cdf2c36dea0758bd58657463df0c8d7bc8b594449c11f5bec63af2da1128b9bea6511876945f7a73a687e9d8ca375e8de9993ebe039f4ccd972324e7c80d2aeec2fa208dfd5218c004523435ded1fa5e5c29c4edab984eb77d5d0b9998cbb609c7545bcf1289097dc9e24484c0457931bf9a8c64f1e553e0672b04382e243f6e9bbc07b8f2637f5f71ca6f540172fd5f356ed95ff5598cb98fe9f2ee03ad75f7f500168b5e802114b3457ac2178dd8e8f1460fc28de2725b33320e820e2732971d4d97d9c59f00f2e952b449516ae69d25cd1a03a055a219c8589e01a33b88dc8e2a424ce5bcc24f4bcd7f6cd476a64fcce7fdb8330104e481a9cc280cf2c45dedda285d1ac77a8e412cd26b867fd2d0e1bd88bf987974d3ad18715ddd1dac748b98a66ae66253308fb5e571c16d94709ad37184cd488f4623471e60a81d0e7a44656cbd5a055650a7a142ee0330bb5d50c44e2f3538476c90da32adead425fdafc715ee12d689c10309bd5f5dc44e2bed388b58d0dad27ad515d61efddd0252252ee4f4c6bfaf878e4cb9b111f4be2a685ca790b449799aec28ea41679bd3a86baec15fd26b8a0da3599d5ce37f42afe8368896baee06fdcb3a90c62f647673457207ff859ed10dca7aa3983743c95e9637c2a53317c825094b508688be83edb6973f2365581de93ec2bad9d023d2559d6a375f50c893132d66bf5d428e7431705db2eb71b9cc6e0e9360e353d4891519ad0b5db970f6f39ba18d9ba56b9c4e0cd079cd515bd89ead1e90f3d8a0f4426bd29462af42595ad444e1f4795743b26b85a7258ed8b3cafbc1874fa3692094982908de933c5b3cd323c091909a1e78ac9e4661e045b022d2e9f0481e48cc1db8e2f4f97b96c4e3298b64c78eb980383d0d15af6b5c0c358e34bdd7a1b411c71677a0a043bc362353445ca7e2a400c47d8b9bca78da5aebcce237289f5292c2ba56d3324ad188276b352bd3bf840a2a93ccef913718b7c9c57d7da5d1a5bfc8e440b1cf539244cdb9626861845bd3c00c28e0062fb53713b99b2c4f743afd04e569268e11b1cfdb801446ded759f917cf38f43be98e50a873ab7263e66ff738658e2e9aecd0ca14aca45967ae6f0713a5455a9cbb2139e12f177d138745e21482b4413817ea5df633dc02c35e78ec31ccac540b6436833371e7e40f2cfd57694b7b642db51352181ae222514dc95d2c86562b1fdb3f162c260746bac9a0b59b7590469160a1d3f2e5a3a6b1eff362d04812761fd7980446841e0688b256bc251780fa3dd37b7b90f1b2e3d013b12dd1a5be3bb8fb26f48c6c9be852e72ed8bfac17992d4ea7beb927ff49cf29b68c7ef5728bff0dbda2db267aeab91bf62feb4567cbe9a837d7c97fd27bca16a3afaedce2ff436fd4b6449b7aeec6fd67bdd06c723aeacd3d58b7b31e7436398d7ae61efd27fda046dfc8745b45c73d97a234b0eafbe3ae858c0722610c16ce75c3f2a219cb86ac4473172ec6f8047560c54782c761dfef22b4943b86f701eac88c8b6037bd6295d0da303eba6a04569b31e92aac8e3e799f0c5aee68e21d1644976e332be795ada7d480bb596bac2b9060993d8d5ee78dde7561471f41313333583763788d454b5b70e03403a41a5a8ec26eec54ef7d3a4c9b02fec0f7dd2afa2941a373816e400faf75677d7d2368b365c408559b1063e35599e8fad8a6210d460cf622e85882f0a11f121de9bc1c2af6f3a6d92d341758155906e06d5b009bb71133cfe42ea7ed9af5984675fbf52172b48502eeedce84eba1cf349f1a6476e77953749f3dc4caecf7d0a03af7dfd81f6a664f8a5d4805e55f0829c65d32f9a0513bacb83ef6af261275b0133453476b50a50e49bf8428aa4afda82b0e217ac9985a50788e1f4a4a7f4841eadd6a03cde59664ecf3971cc379bfe6889dbeace45f7bf16778e31fba232c3c325b160323ec29e582bf21a9c020c3429fc1407a8c88eb7e2218e8d96601aad0566417134dc5c392a3caa9ab816e4af73d88114ae13d88ed0337c107267839b9acfd61bd565e8048cc8b1f492377e60fb87177a68fa2913bc31fec4968d5f2e2476c349df103690c9df92337becef4c74b125a35bb580a36029ab154699ccdfc2137a6cdf4873c09ad26bb588ad16eaf19f6b781933703f70bbbeb66967b97b4efadde45eeb4edb2fc839eb41f9cc067020570299f4ccc900642a5d24d40032edde7c44898e1dcbd13ad81e3f83f01933c70c1c1f35b0df41ae05c3e138a34c21dc78789398948a1c467f0210ce849410f9b3a041f1a2725d65df942f35326770f3f27c824ca851ddf79baa0f80f9dad6ee0ab3778d0a9fcc7197c40cae9cf558dafbc366d44006bdd58f5e41f281f0ddad561e3f83a37340d650468670996987b772d318974bce9b36b0ca58b12d471ce1accebd15854bf3a6075c7bbbb02684ea20a70e23e6163b8e9bfeeeee4ff698f462a1db7d556f8ed4130988e9665f2cf4c245f713a4e00959eeca972e2fd3bf73f47b251ad2b076a4636d4d41fe92fc9ccb3064296390fc851d1c84cb3b238baa6a65466d6ab7796e849c58dfff0dab28da8f71053afa3bea47e64e55136206f4e1debaa17493723ec7b50d1ba95af7841673bac43afaea657099e48b81d21e2f2c91d232591a53ec92ed72e2f96ce3a70924dde51f03d4777178c67bbfe5ade5caa9aa957e23fb52ec7ca67e3025f4b5febf20f49d76eeae95a517c970fa040a78a5a57735db58dec4e9f5b1f2104de598f230dbb257c48639777acadcdbef7124ceed3f73d55ef1424ac523d32b949b7a23d2f5d382f88912080f16293b7b8eeb1fab2e7d5475dd936cf541c54cfe3ebacf3fd1b2bd41b49bcba4ac8742b9b1a6016d4027ac3420d334b5653df983bf4f86c44d6ad98d0e077b4f83a71562cde5253a0fd65f460551935ea1d4e29bc7f95ed206907c876486a87a43be092efc071e2a12c2aaa4b08914322e1e5507a6d7b086dc04b5293c402ab660dbc1eb0cb20d8b9c2158543aada695988a7a6076e70f8d75f9a1c3a910a4658fd109ce210468649869c2158d7a2b343de5637a88102bb46d895f2f9dd55aff6a2a33f5e6cd1df413551df8d1602b2c0201ed8474a9aced82afa0829db918b26e4e675737a34279e128767fc867baf2a78e903302314efbf720f4ad39158d47e4cfdbe2587ab9acfb6dcb05a946e0e797bda995bbfbea85053201df84799cc8d6312426df337796fda21e338c4f5f9e1f636ac54553d3a6f5e817087cffdd8ab3d5bb5a0a59560f25e69044f5ee1ef81691c34b70970ec7f1bfebcebd9db9d28a5a22c25b870e7fb1fc03b8e38e70ff41c488f920438998713223fb01788a7319e8aba51e4811216da96cb8f9617b31b304380e7d4cd105ad9812ae5cc53f2c794d212415705d6adb9d85ac522a04a7293753d46f92bb86fe529c010aa605ab9b0d49b1a38737ab1ec0fa8d5fcef18bd69db58c1e7ac898fc79d050653ab1127db09055c05856eace8353ff04d3359cee61f5963b198e50b5fa014af88da9f076681b9eced0bf6c64c470f328fbe5558eea33b7499623b435502e9a19c85fbee642ceec25e53918560473e99640465b9b4b35bff8889dc153235055016b68f3147749f4632a5637bf4c899ce05e25319b06884b17d88c0364e3d98f72c2b098a1fa83c1d7923fd8753fdc7f0fda5be1b8d1cd13e5118928ddd6595b11ed03a0737121a6474a6040f7264a1636b4643f140900381446f8088744797f8b0da78e2fedd9385cd03a79ff01f18e69848c01dfb43db05162f1dd6faeccfb769b5826b9e648de8eee5c660a266b52f28b687694ee8bc75d284f860e8bef308dbd11d6eeba9dd66844bb5e4b47cbb7fb5b357bb624da66e9dafd79f4ba16887faef46992d106e9f538db52753ae9f45b5dbf261d12854260f0e464ce4dcd41ea4e61ccd6a98004699cb3329473e0957b981691cd38990c04659e2937c8fede96aad88639f20d809217c470c942af699ebf32f0a6127ffc46c939af0bea0963012d11572ee15a1c5a1daab659ce46745f679248dd2eb43e088944af15453b6f2ed70f5f21f23dd4f362385e3d0bcc2d723f68bcb7bc57c550813f456826888dac64de1148ceb8eb4d2eaa83d4fb2615e098762649782380610761c179d3a9aeb2e7807edaeb6413731413f99d2c64bf14983ebe83f24aacfdfb0ede174b855649826f80ddee6f8590990659c52810dede85352b1aaa2298064af2dcab99a212ea2512365ba19063af505d260a903928c696f9cf77c6fe236b2e929b422f1f31d436fc16ace7fc40fff289bb748a387c2fc850e675db888d966d254288b6e1b445512755c02c574bc1c83f445d23fae8fc4664a745527a33be7cd750b7eb7584b234a04cab422fe89c02335b2cc46ba2ba639c6a48469d1bfba8248da519c62dbfe6115d80f0688364c5975742a92e86fa20d1c2a58071b98db1a882710fbc2e3b6d4f75df7609ab7184664311c2b7972a521a893f8d6c87d180001347862b820cd4a80b0e46f780ee5b4ff3b82495426bf6156a7df4e0865cad3a77b58c30452d7860d131d526cb8e7320840a9624861b49072d2f60ccc99c45070283a2762b69d02698fcb4dfaa5b440126a0d177ccb30b05b69f5e770392f8c3f14aa03c9da10685b2b66d860b823a804c174285610b03ab152cacda584a32cf3c5ccf7361815f6f0fe2a299084e6ebec828587dcf93e8fac6ac0fea1b8947462440e21390756f271062d3fc5017e82b7abe86e9506c1cbed4d51c795726ecc446716348f38bf9c76fc49deeb67fcf84f4bff3d65d4332e52a620e2a1ab25f28cbab7fcffde17bc7402f625185230391c4703f53347ba113c67234f235dd90e6461a9963697ac80c519b143d5a2ac1e24495dca065c31042d37ee89d73b4673aa3a2893c274fe0c293656647de98426f0903157b116dd3a7e8b9c16f4fa6cb077c6eed5a11589866d5f4942ad64ce58162690ddf6a6314184bfb4a7f30de81d533facd53e4644cf00d01ab316ed16648788e55c886b853b71a92c2b4f18d840599819e74d43849443c2982b46e003ebb454150d2913ea58e5e90261f01d2abb1d0fcf0beab38ff2f9027badf5f47a340abe77d75623ad13fcc7ead704c1340400a2e10c05ebd0bf13a031479a1d81076da779576dbedcf6add9fdd87f88ee410737552cf38771b2e4a0bb00347f1217139c1ada63beb990507647824053f60558a7da1cf1fc6f624828c93ad117cdbe05c65cf4446c3f66ea609e87744f7774868956d4d8be342a65a8097eb9fc7df256e9c8627db6c23fdb69186ea0efca974f28a420f9fc0794d0043ea944cf0e59305a19271d4532893db1c71e99c1ee2b72e4128c6b4d22f7f319f3dd44fe1ace92cec0b627801b29e0550442d45dfdf0cda9fb8e732bf9f6626a4c413c0cad5445701a7834b689af6a1ffc406bdbd41a216a8d1042f6967b07570e980fb00e5d0ff4f321f3be996c06bcb6213ffad68dc4158e18a58ffe7d852346c9ca7f3686674756922e890047423d6f667a747992786f24931e591ddaf34c04d43e3ba71904a0767a713bcd121331c65b7d8865cd620ceee969258b693bbeb13d87d3bec5d051bf1e324aa4044a531e785a6bca3adcdbdc34fb3580cbd04996201354a59a4a00ef76550d524ae9265b6c61e39047158f3c9b80f7bd6dffb6bd35aa4296b6cd30113b40a149c6550aa08a55793aa95100afaaab8a2958b32ccbb2ba84b904f07aad690755d77260b2cd6d6e736eda8c024739edd606e56fa40b215916c3702c1e390f701cc79de3388ee346a377a3d168c4591b33445d16864b80b7d5ca40d5d4349e99533c30a40340c508513aa665921ea19ed2093b7447005574b25579bc4013c0cb35e5e84baac216b580a453c8ced2319f005eda2c5b4c1b5dd0943d497a7ef41c0265131c2ce9cbb5d4a14302382fe7b444e0bc1c40473d1e6912bc0874b225bfb904acf2f5955d9e6c551fd6c0440d425a9d2f3930015731ea331b995dc5c8ce5d08673986d14eb53a990036d1c14fdf0a4e249de41e5026e9d9c1546615500a5551d2158907402954c5a765959eae42f5208f92477a3d69b31f51d4f45d0e40005913ca8cc2ae2276914ea794a63fbf76a04aa7678d31ecea3eeff475ead84389a7227da990145a44a446e8119122e191823cbcb56fac594b897c52a7d222b4089221264ea2d0293b7c00196244684993fa845d27714750564f89b04b72809fa901a8b3835f03b6577b6d83efaaf6f56029118ec3e1ce3e1d3e2b258b0b7e5e4767dbc14bd0eb4ae2779438619c589b7e3d6c37d9fbfeba7e3d98ec55d10fa40148845ddf66e9107b7566df17697ef7ae7ab3ebae061fcd1f7d31c01edd86bdac23599cc9a74478a43f89dc8825cf515642d1ee2a4b494b57476f961639899c26d21c4819fa4ca4468a002f0dd2949e06a143d845a416b55cc191410a9d3ce5994dd96955d22a948a3081c8109123c280061e174401090bc290e4046010826d3c94f87810ad086db46d3362a29b90debedee8f6b514a282032d075d5f8f0790cec7280a2a7d6a2914c5175e606a291445132c1e8097d581aa6c347bdcd168ebd14863d765d17c980ef5d486f4ba2f8902489b4c2f89f3d3a1f4d168741ebe8f2a09bcfc2868fa143bf1e63089d94be2372b89744debde83d491b8796ece296a3fa7a1811b8d70b6e6ce7ddbb86a411b2e1bd234b7320001cc2e4797bfa4cbdf4ee49a247a3d12c1aeecd264921d549a49adde35718e5abef296f3b40d0b45d1a3695a0aa1414a779aa73560b3ab18e1591e9ea74ffce9907a3729555d7e1b1d9d44d35362e9a1d82256167145dca8971d25765e85910e840b1eeb6978ac2a208057d5b2e7c79899a344d9a713bf63bbe2619ecfd35c9336f1c6248f9c8aa73731875b753a275ed944e7e8d3fc48aa907576a15edf625777b29aa6cd295ba374d28f465a054f454d0345d973a2e103390e676bf0e0bfcf247225f11379903d2d48ab5679ba36a0136b9344ae47a2d7ab18dbe579ba1e013d1ee7e4c0a95d4e68657a2e5869cfb24bd9a272a2c7310f709efbe52849850a15249c59d46f9ce9a4b98f3cd0791d27de4fcb4a593579d9e824b192ec9d9f0fd9cf890a68f9f590fd72531e14d09cc5514073ef404fca16729298d6120e6e1d8db3dfaccc6cbd02919229ad7dd583f6ec5abd1c7bf36b80f611bdb34a73557ab47d3e68cfce89a6de3ab3940713cf3627103ab9036e3c9024e93cf7c663f1deca07f0a5cb5e7da44bf17a39ddddd499eceab331405bb22f953cd9d4de4dbcf4527b73fac66bef9d277a2650bc5e832f8937a74bef24bbb8974aece417833ea7b793c4eb35e9a337a7bb6fe04962e92af1f34188ea36acb2db4fa75e8ffea10e8a9f2d895e9fc44badc9ae36528a8adb4922c729d18676e335ea92be781c16a4a144ae790778e576fedd7807388f12676ff652e68e126bb7e8e8ddb7d0f34e371d7ce9f492744a3aff6457dc57a36f27ddbb495c8d569ce79d9e64ef8ab4496f45a26ff1483f7be3b53a925d75a4afbad36f76747b571b7bd293232badb7f1fc7a30f5fc8cc13d0f9a9854d4757207ece6b7830fa48faed77745e5e767a88d41b2d2ae48313ddfeab26cde05c9c61871dc333ada42d2258cceae4636066733bbf261742eeb2e3ba73d6a5724edf57c294f69f0aab33146969bdc4adacf26a45d3191d947434a627827b9e819bbba35f2d9a9ac5f65f492b333abccc680c1a33c0d1e25b577a6d5da8d697eb8bd53ad36fb6edbec8af3c495f615f7edf4f5a4156763ac341be3aeb4b73a6c792abfd9bbd26c8cb05b2d9fc5a8565a8f8734b92f742b1c31b4158e185a8c6aa5e5dfcf8b4c04487f337b239934fdcd4c53ab43391e3a2711ddbc8bbb322189110534df9b2c18f273069f03d453296486292db3037010c0eb0531839136b5142a83916ece39e7a4a74da77c16c02b9574f55e1c06bbe8eb65a8f855c330f3f5342ece708428e6b6bab2bacee6809499cf2165e62511fd0092ce24eb923a3424c88e1eea843681e4c8cf0f96c245444b880cf1d9319f4c27424880fce4c8a1316326264af663122d2132a4fad41d4f9c0821a140e8cf14294c888a10e11eec03e58912a1093481c472fc406346014e39696946cac8190528ad941967f0c839002f0d0fb3f933064cc7f0283f1e66cfcf2cf3bad089330da07cc74c65adb5b46a96b2d9e29836e8b873ddabd89d136fab397b30e4b84b9ba330f4b722a03559d0dfd91cf72e0a909b42a4419eac7de3f85d1a40eedd9ccc515a4bab1859672f475f636b2200c664416f68e2be5feedb6a6a6548193a8347fa911ec64480942c48a7618f0eda1b96ec672fd3b30ec07a8f3b7b6fcc1e8997bb69b5764f5cf500b676b0352b33263ac92e66d728c7ec32e6d268fa09aa42d65b630cbb4aadf18792f816109fc87a109e18b284e8445558832482aa9b91e8819c97d3f4991cb38ce7abe19ee6f0e38c9d3e48c03b7d8e381962d2fce9a43bc7f1e7107d491e7da8995fe091e65f86128620f8cbd1b3137ecd230cc3a79482b7e1790409bb50e7d3f953490efa6a3983e7cf22d308bb58ce9f3eecd27af0eb8720ac94302d96f49ba36facbf97de7d37474effa3a8d34f87d341f029f1f274ea5de6b188a5cb2f653299ce836c93359da73fb144459378e94ba26a85f2f48a0afc07de86a6449047067fa0af1a09c36fd2b706f59dba299b5e87d3e5974314c0d34bff7838fd137360029e4eff3139cde6ef3b7da7db303d596aef8d67c32651b6c904526a126d28ced6a6bbd326ef7b373df17b893f1dbed2fcbd7ba26c4ac9f0a4e7b43707d7de67a9387b5c9e9e9fc5d9daf3be79409a89c8a62ce96bc298c920a5ab956dc3dcb3c2252a8c01288773b81b30bf50ab0b2a2ea8c8ef58f444708e81bba9695aa54a95295dad906db33b35b0a0ccae7aa85d5fbbda550fd9eb335b2995ada9f10c039379de3e07cc1667f337ebaca46ca4755d771e6477e7699278dfa44e249db9671fe5644d740656af8a379cf4065dc6949fd59975cdde99a6125087bb6675720a3a13cbac8a393001f9b299f932bcd3cd1bda73b818dcd36c29f37da8538ef559d70e34c7c86a6ba20e3753594b38b4bae43cbd4157eb0dd2b0793c86b67e38b33929c32f9304a014328392be5e534eb6d8ce1fc916120d9d59514e2a64a0e6a6a626033555d3c9da463d7d47b773e7d13b4ef446226706f0cdf576d9f45df9357b389d99d9ea71099ae70705445c30c39013a4c02417aa00842730f12309204380c987ec72b5fc8c1f847a7a6c60d0170793974010a90825cc7cd97023a115618b159ed5e26c789c2f78f4a181c972f6a6a6b7709b2fbed02307323803178c0c614607636882c813580803100c7006d0a708cb0a859042fa22591bcab11a64d005161401610813b0ad0a5a1082124f48a2062cc0369a201c98acb0f7a6a6b9bf904236f41516a0b7227cf4e6f5560490de4eb06de3ece57a0a50ecc0083ac8c113440002db00700313a481064d90900228609b65855c8f449a99b342fa6a31cc7ccc8461137e64692934061ef49de178fb41977a79d2245f33537e06d7b8e017d72295b4ccc97a5e9584474f9f5a2601410f28403074cd68220d8f457ca064ef666f6a5a3bcd240378677898b1363c435f1ec82e02b4f6900cad9dc52e53cb243c52d0da5bfca230ed31fcaa306db3292437784288c867071498768ebe12500514342109124b488214609a05c9007ade28b6542c7a3c860c333fdf330a78677ad6ccf06bbee684cdb3441fa0ec3a869e5ed152480c56e8dbda38cb78903c90210a2110210411b440b6a82d3fb37fdaab387bded8813bbb0edae5a7437683256558058bc98de40948aa80e409245fa042e8f8b583fbf62a5b78cfa68471a284b1e400f89b286501aed87c1e333029077057343f85340087c105bbb2ec9aeb3c324bcaf033318564c12ca7a51674d17c1bd964868b1a19cc573669760010be2061241938203011124656de91712d414f2ed2f45c3d811ebb381d6a0d8f94a62bcdc9795f8bd1e557b205e9f22e1201dde543d9e207bf681886567e5d162d7f6f68392d0ef235a1a4902fee51a15aee620ad5d0d0b8984253680a4da129d4f4cc43585c78c8a68527557862711187e470a72e3f1d5234352e525af6e90092342df5ee74f9e900de8657eea5f8ad61fd0e60f50bd3f5e557ec7a17f92abd3e932dc4d76b13015761c3edeb43d9c2f46aef8d8be6a470d6d321078fb4a50cfd45d86441af427cc123bd0da21565300c952a42506a7f01b94d968694a12f893652867e3e01dee93381c816dcf45ce51eec837fb807bb7eb0abe5f40c845d2ca767200e52eb2f8d94aeedf523aea70f7d01e037bc75974bfa5af1f0bc837f0280e880203c41c2c8be41744010aa206164b7440704e1090923db455c213a20085f9030b243f186120a4ddf22b28897eb95aabd9335dd46f248a39894a197b5e7c79f904d88b53b2e60419a2a95a16c0e1ee9b70d490990ac2006486c5063e2ece111b4399c00eff4a9535e52887fb00f1e692a4dad648842c2c82662fe2011414ccfeed8638f7bb5e027ae7aa836b457abea2db11d97bf7c87e52b15bf0e16df69dd9e50a752e9333dd66275c476641e80efc41c008fed842c77f94e8b0db7d791fa4e8ab74aa752a9f47da6c7588fedf01230f02b5fc9645a013fe3c2127e070533a9561e5b796c07069e45cbe93ba5878feda4b80d8f09c0ead891b141de636c50b67a6ce7f497c7765acef29df02ed761fa0e2f015bb98e1c6c6c270036880f6383e6633ba30d1a1d071b54bf03001bc4fd061b447a6ca76583ee76b6e10a1b74bb4f7b6b83ae76ce8a36e87227d9df065dd24756850dbaa357cb5add061b74eb331bdb79790a1b74b37b5607cbc676beaf2cc806988fed94fe624136003fb683ba8b0dea6c6c27b441a4161b34b2dce92c3688b33c54b015958df9a480c54cdf81818fa56c6ca774940ddaeccef7930dd2ec4e5d92032660a02562ee4c123881ca0a6026bb53b24199fd6c50b5f22d1e12774cba5f1c29d4d35d2745f349dce83a297ab3dc7552b4662f0e2fd1e09631210d4668d32f8e4e8aaef6e2cc3000f9c209ba945d27454f7b71ea921c30d1df2fced449d16c2fceac52056772410a42a068ef0534953ed1b31a50d11d69c46d5a66af0654f4b43634a0a2abb52125c7791e08aa5461c8e2916fa326a1a3b2454ed6b2c50d9d55733774589a7bced6dba58e4b6fcf41c916377458ad3d6776f61b3a2d9d3d476b6e7143e7a5f939b4995ea7eb79794db6d049f56cf1916ca1536a4e67b66ca133eaedf29f6ca173eacd7a3cb6d0a9adb5d039c9163aaad62c98922d74b6ceae63eaccaa58640b1d6e6ea1436ad64135db9047d94227ebaaf375d559e96a5936c8163a9c0a7e0bb6e7d9263541274159ebb460763013553cd2673632abe2110be0f59ab2bc2ccbb24f99c210ca2005296040c2053b9841110e37281a831580f0c3029f2accd06c32036162063e30e20324f8e10418a542d3871fe90c4f0fb690051f44c0c2900f8ac068fd2184f96308ac1774010a2e90011a90e0050e84a0270a16a27882079e98c20f1e74614ad9f1a783f67ad91a6b1792d99da0f99dc9534a27d9a5035ff2b9e6c9af999d5ccb6f520b047ec2d54ca981e23dbbe7762f396d4e5bc57a1596d3b256b36b64bfbd1b893c1abd52f13cce7b6fee9cfd829a1cd9cb59cf7247c0b7ec5b65662fd8949e5db5f91cad58e002f582e9870b28d759d32409a29a0493544d26d2581a17490c614bc2085d0d4dcc48c3faf1a3bbe6730460b54a40a5512d9394c00b60c54127a5abd6db7be5a7ab56cab44a150430dbb8315b56a9d22df857bef2287f043c97c3ad809674521e9c555a4b908551b5608b47b62520eadbe2414704528d25077aa0041f54583085343c219dcc600a6460a283197c800c5228c18f540984c068d40ae1381b5298f9a1db8214d0600d6a10c193133c600951cec0a3490e881045144800e5440234ac9ee0389e1fe0a007450883134c8010c188884110175ab0430950a2c02a14485004123ca1eb0aacd62ccb987425c10ebad6da02ab35e32181121e0ced800827708108569534b1829f1abc6004223803acd28d856e6899840447f44acb2449a0419b828f2248fc8008d4b28d63ee32910049e0c04b2208f4058f73a46905f051e402862746d7469ae49200926a4a278c528d9fd034262411822e891f4ddfa21af74466439a19aee5030cb3003ec2c09fe1d7b08b003ea4e0436863ee03125842085e2802141f4cd10215a450c4044d08a9411a9e408135cc800a3ea0a003283930034f912bc4a00b5430328235ec60a2024da86942abced0ec20e9397ab047901e439aa66094d2cccc2811849192a66f51ca84adce1b22e10356220c5b2641c2059d6a9904891e7dc3be9ed0152690832430410424433c18c24e5111413872042a10010430e98427482021843118290a0311d80e60b8810c887004143c7102a26c0941492cf9c008a0d8c212376841124580421252a8421356aa16f97aa1444593cb444df987bee415c62084256030048322c200a36720fab231c11a961cb18521409a80d1f310764926142141d3b3113eb282a66724fce260f4aca486c7f9acabcd9c40299a12a5e9271576959a9e7bd2d0f4bc837bd4daf37308fd2c423f8fb08b00424c50c2aed5501796f08b32a1b4e787a02f1e7a1801c4c71496b0822460f439e88b470548503264094890a1081dc0a85cc2e7a769ca064d3f7ff06b058c7e0261319440083d7c74109464078cde86befe821c80e0c79122621082cd80a2534946d0d314462f448d6bb6138638d18524ec4048133c80d128964072c4080b7c1084266074094d7b2c694aabf042c1e863ac0b1e67cd4c4c0dbf668fcd57a56f0cd1d6856eeb1bd29819937a4a29ad4900b729ad181a176370694dff42060c4a29189a520ab6b44c7284148e48d277c6091a8ed0012fb104c7d54ca9e1255aad1e3f7a107127341866d2996d4c56b8664b8189abbab0115f2801ac2d931c71839e2139f0881a74ad5dab95b104bc72a86918661ec60c8ece577b5b3d4fed1134e083fc90f9ada9b54c72440cface38a93e261124a2e0832488f8c1cf076099123acbb24c44421a80a8bc600714aac03d9c2b3b18b26eef4dabb9d725a0ccb2873d8ed059965d66969900de3ad7c07135536af808b5b6a8b46a6a6a6a28e523cc39abcd99555a2d5602b6ba5629d43a02b8d15a8f94b22cc88907b42221fb41129f23acad3042cd5e2b8bc7139068666f6abad650071e2470429322262698811443cf5011625003911f48210922f0e94a405f48219b30e705222842851720b1e3670754d2d0030f042901c21a86cef0896047656dcf366bb3f5fc400962c0828f1082c0aa0d6600640c5b28a201152058454257239ae89a7535c20b5d3f58ad190f6536d5882c74ad4644a1ab06ab35ab9587e40c972404456c53989cbde0d7ec9161c40f237a94b82d4a419f264de96f6fb537ad9e4d8304f0ceccf038a3d0f4accb168c848c855c5a26f101437f2d93f864819aa108b245ed0ee98bc7073f38c219a40059828233c0e64f5f2956c0040b8056b0831f25b07939cf4d258ed0f392089d9c92862e884088066980e2084c62c003513459c20307476802268d4081ea839df4cda9f5a702a93f7cd413d49ffa43824e8af5875aebccb25ab3ac6633abf6875a6bad35ab5be86acd321b5a6517967447bd595bd32ee5762971b6d66ec3f5e79a5d8af355cc9e892ccf9f9fd386e26c2d7215b90c8928dd4d8f03b295ecf71d758ebb39a667deecac7e3cfcc02e593f1d38e4919ef5ed57059a441b5afa277a5d3562b2a0ff814f12b923e0a847f66ef273b86bb6a1dc954dd44f87ca07a7b60220c0c93002a095c724a047df625776d6659df67af37a3dd9de9c16b5aba65619661bdd786d03f07aa3283bb8b82cbc76d153257be06c481e2f989d452b33e0a2abb531cfaf4a90fc223d3b7d91ac8a61f8cc7f715309f0b2d0368e1b691ac76923f1eeb47643f29843e9378ed20b215d7e129846cc02d093ac0d934086f1a6c8312d83146aae3b93b665f7549399413b658935f8600a1459d8022cfcc8cd43831118f94091288ad8e0073056b554d3de90c605bbe667d8c52e28cdaf3ca8c11023886005a2a228309715bee0840d3c9005258c0063259a89e8825ca20a2a18431226849eb004187f86be4a2c680224064582c00223c064119aa550dbb00d2701722d9310e1a46f4b284b41275bd77426e112f0ba68791a7ec990a184c562b158dcf2dd0c76d1cbf15fea0b9c4f420451d3df8405cdab51f845fa61183e2705ac54c21da49f1e24a1e6dfec47cb9386624c454c454c454c45da5484618a1859a244099221234754e09012700af1384943cdcf0109e09d4b7afe923634dc29d473fe987c4693cf4d3e261f269f36f9b0ab87298809c81c32fde8714d3e3c31872580d5ce253c0ed12a347f7000deac49535393974905bc2613169a6fd6a477b0eb9a9ae4b0ebd61c4a7a9e1f333561976c92510152a241e95184c936058810f7c487089114d982cba0061208482320fd70c214b2d085a2201e08c1084b9a3f0ac12889510a462f18a20528caa086d18e244970c2149c349f63817c8dcec9800be278c02141c306852d079c07b8107049703f56d0021452b61f6c49b6246c44cddf46a0754133827c8dbe996063810c886c3e9b07d0208625a0608527cdd75ea006f91a9d9f4d41be46d6c60c509030920639f881d0af0644fbd54cb0fd6a23f8d540c0591b3358210b9bb5318316baf0d2ac8d19a0c8c4e0a2d6c60c52e428fb6653b22631605aebe131078f758efcca9a300c3f733203d6844252ebe91c3cb6429694b20129c3bf5b2c9308d0c0b439524607e0ad3df9599366768d667008454a182380b46422044efa9ab01082a2eea40b5236338ee68004f08295b29c596915a3a5c949651530f89c34bf1b3f29c82f09a394b2466b06e3327bc79a01590224fbd197e39634574b8b50233b00ef0bcba9829a644b39d2cb73745671565b3f0d89ee76cd97b5f2ec20970c0d09b2a3c74913488efcfc905244b484c8109f1d4f9c082101f2932387c68c99182923cf444b880ca93e75c7132742482810fa33450a13a22244b807fb4079a24468024d20b11c3fd0e022204fe6d7667b777ada6fa893d256a00a84ca94264c8c14a13e688f28508694046120eea1d2449323467ef818e246068cb1c545c0696bd6e372ea2025a53d30cdcf2a065036e141275b97895a7b3737b1f44e3c9d7e4fdd49f452f7c4d44fe28dd92f3930016d9c8efae9e5e1b7956fa89f4ef7c4ebf5e5da5badd818a03d7dbbf7f28ebd93f8f272d44fe2cde9d3f9dbc103affa7696a3564e2b76c562638056655176b32d979f0faa8317f5177bfa0a75ef9be5935da16c0cd5c1ab0e9e369777d3037fef3812d03953a5ba77ba0d7b2eef2e6f6c3fddc55ed0aaacceecd3adad7d31bedbcb4784e23d5dbe45bca791100e80e0413674e60eebb1950dea1e74bbebcc2ebd439d6b0066e7a3ec2ac61bf537eaa7e7cc5ec5409d3baade989d333ba5b297a7553fa12ef9b53acb25db5b1b75f02b51320c686fedd351281b8a126f8eeaa877281b2aabd3f7c6ebd44fefd84ba5be72fe62704fbd4b89a8771d7f3e74d4535daf4f4771a7ec9d9d3aea2a91857e315467e106c50b9ef4faedc08d7a6ae57495f5784c89a8469d3bca5e50f6d07d4e1bea6d37bc39bd8a21857ab3367474b6cfe76c5667b68dc7b8077abe67f59802602b1b344f4422d2d3f4ddbc2402f263c8243dcf50e88c3f2dcb2ec486856c76c78305d5cb2f06476115636c52839eafb66732e9ba8e6b006a4fbd933e88dd6537f5241f909f0f28ef2b26cff38e5ab977ea3c51f68a785397378938f3fbb4f17d5ea63e53d7b149bcdd3fcff4b57c08bfaf54d68755677d903c8e3cd3bb772694bd2799e403447dc357857d60a8b7b33e7247e2197d3ab05cf59178c3aed1241cf4fbe9d7ebd168d4f2ef82e7ae127176668b78515f398b386d749f5f11736002a2ac4489f3dce8242b55def78e6455f6a63eeac4ab7a17532a9554ad19577d57a94a1788269d47df3ffb95ead66892e4925d7d3606fb90ea01f5ef9fbd3a1a354289a693c493bda0bdf17a3ed3880072c051c140438a846f79b9cb657d75b6e129ceff559cafbd785b5f7101f01bee781cae8c11070088ac1b5a62b8c28a2af122a8c206d14bc112572fa24bd8f20310323130e7c760a0d460178e0a9835080047851317210470440038a61cce0f420045646280860cc12e1be331613c268c794c180fcd6017cb4a0dbb54a813682a7d637bf65d0e27b11809808b39cfc09c03709f17180ccc0c0b166766162cce4366e80c0bcb4014860560630c45c6c6d80985e10700c6b2d01013711306e2221cd8d4d3e5b00bc79c362767882186b8b9b9a951a38610420811441041000104103ffcf0030d1a3466cc982143860c183060bc78f1c2c6c6c6850b17353535343434333333313131e338b65aadff2c162b0c43954a0582a0e779dc74e1a0d4d64aad9c33b3821ac00944676aa4901c924439e8c17b5c6f1991569b64d449a5b3889bd500e6c003c4e1fd6a20d4808a669b13368f720121289ad1c40994a2ce0a8decad69cede990584600ac3c8deece51660440fc3800b30c28761c20518e1836164577bdfb456c149f7e861c4c7a7c88e1db56708152a41a64c019222054851d10f8f4ca3e0d8364ba13085c243db568dd4223288bc7811c4850b201c577f709c9da93e661dc29148b6028d68fde1348077a6faa050a24479e284c7274f9aa04dbc73134d30f94e89be3313264be850e9bc6489121e8778880ad1a0f183fa902183f6f0e1fee205dd217b4677e182a9f014d26968a4cccc70510cbb70506a6390c8233c328e5a6dcb88a44578641151a982781e101cd90f6bc799c7a1593a84477e061e675a1b38b271801488470a45caf0590a50f387fa4e22a1999f66254200d4971ba2af398561f81c187a76a8e321069a76caec99d2dc640851f3e710faaa3f18860f840b021209ab91e639a4f95c065026f9216a21d6aa367981bdb884e17d5c60292a1185794991e2750885611c531da2309c82b5b2d5c70f1085e15ae40885e157210ac3e79eeae3522833cd443e9a4fa1b0eb0a61bd3b5fefaaf36911bbae90d5bff3a91476ddd257cea753d87585bcdc743ea5c2ae0b9ee5fcdac3ae2bc4e5a7f3eb0e765dd45bceaf3eecba42c2a7ceaf3de669b48832c4173cf2574417220d8ffc9438c44d0d218200e2071a33789c9fa12f961d57247bb9172b1966642fe862433bf479ca55272cbce202d638636ef1bf79dbe559acba6959a5aa201ba0fc8e36f90b9dac2b27d5e9f2ad137d054f9757a16244a8f07181b15cfea9ecdae53d96ed2ca393eef30263e9eead7c2fdde705a6b241d78609656341779e6cd0156549ac6215394e64b178e473a227fe628b478da7c4340b9d6c31c79d6bcad3cc795cdd2a8ea9da588e9730abf53e2eb0118e8a21b453130c4d7b0d70c46d7686c2d0d750187a1b0a436f6333280cfd0f14863e88f93ba3f14cce883ac92ef93a8070e63b29987c8b5db19d95534a00389054a458b9802b2eaf8a5991c37d5c602b2eff16197b1b582ffc904585fa4eca5b41007b1b58220e00b8e13b2b0e7379968c8cbd0d2cf17b8803cc0df21c01063063633b332cc61521278000b4ac04108055cb0d2b33261607408b45695c210aa0fe256451a1bed3725026c6c67c5e6039d8d8ceb842541180bf842c2ad47756ec633b2d2b27181bb4a5c6ee38d820efb19d1507800d1add6083b2efa878cb06d1c77656b0b517ed6da8c206996cb0201b20850d2a599065b9d567592f9e952d2c2b3688b335658334940daa205dc1f33187826e8675930a447da7b349c1b201447d6725bf9f826e52bd7da7e529930da2df09bf7279106c09555ff9bee3fd059ca8d277bed10692d77df59d6fe774d6633bdeceea2e8fed784f6183b8c776beb35636e8c5c5066d0f47df69e1504f7da7f4d3633b2d7cd3633b2c36a8defb0ef89519e33161b11d950d9a364c912c0b658346163c71b605da20950dba49b566cfe5cc1dcf06e574368877543c23ae419d6c5dce0778553338071ee08dec261cdd4a87c3f3522d384824171c9f9098cf0bece51f8e5289e5c731a15a4ee7fceb25bbfee2420ae74ae7fd384e39f00071666f3f52865e368ed35b6c2cc8c689c5c6826c9446df760a5e12b95016070a65739e9c2c8ed36973c2232d6a8ba354b2373c8ef4b338becf0ac1e3e8591c9e676708751647d7591a3c8e946471904816081e477a8a6334b25b131cdb71f838b68dc85a1cf3386c6de0ccc631ad1ce25136c9810778c3743e9f0104fa91497ea6f4ffa30ddd4d6853c2ae528b10bbbe27ec9aa916137679560811bbbacd09bb485f59c2ae51e86dac6d885d5bc8b5d4201b520d444dbf35619766836c8441365433c80658a54a95a2e601d637db3b71ea73e001ee20a5cc95b936b4b214704a017f5a2b92444245926813e2a6af4d887913da7e7813da7e7813da7e7813ca88b4a2ed472b924ffa6a40da90064480be1951362523ca7ab221598faa6f2dd2a41621f5a54eaa8f93a6e72960d697faa047a0501f4dcf50380aedc153980af56129740717d11e16daf887671110fd2c9a52f807ef601f93ca9cc23edbcf50cf2533ca6c329950fabb09fd34fd01d2c29242c22e1c53cac6389f17d8ca90d4e937229c00d57d5e60a9d47d5c60e11176e1a8e07a280cbd8f0b6c2548187e6b62845d38a6956f5380c21616bb090d116d45d8b562633e2e2fb094dd9c6c4514c60c20ff6e429c8f962b76381e902d113a96aae773341538a6a29e9e1424520ac7d43a2b4d7a4eede9a16ee06cadd3420a49f69eaef3d2377066eb84dd3d67b66a65da0b5ec7e584ea6ee028a075524d7a8ed6b3b3d7749d97fe4ad3ded2755c9a74034745ebacf47c4e46ea48f67ed779c129f5f4f434e939b4b769af771d172a5bdcc0a19dc34dd29a74bbebbcf49cf6927e93ead486d24e16c79481d544b9c4fc591c93c706989c6771547036c671d771e9db592131224cb1cadde70536bace4bdf54932c8e0a225454a95205c659c90698df49c17c5c609b8db5341b7b6663ac6a63a10b8cda988a0d30eff302e31d8eb51dde8f50bfeb37277c868cbb36f2a89769242f1b0378a90f15f0379b50659e3b6c2769991ce27e35a18f7e25d1e87742f1ecbd77ae0e91449bbd92c8f3b196fa64403e4dcf6b006150187a8e03c1309443750aad44f43fa10fd027eba1b509e884de3aa4695133df9134d284c64d131afb664092a86f0624a7f4cd809a7e0e1135fd6c329d34fdfca1afeff443106118d9f430e84bc27ee791df59e4770ef99d40a06d142cf161b28d022542d4360a90300c6d14186118d9444d9c40697a16035837279587c45d13453d81865cff2e33204c970f825d7713825d97fbe9f235d8754737ecbaa4215497f35389d04472641a293289cc2133c8048a919f91a791af912d67b9747978577fb94c71d66f0e8fa338048ff42c71aec41b71fee091de45acc1237d284e1f3cd2b78842f048cf22ae8841f048af12a70f103cd2a3c4b9e30771f6883462e28c1c3c5e39a5e5cc81c2ae6e8827ec22dd3861d7a84613ece28408c2a50141c4aecce6ccfe81c77957b539b369f038cf3667f60c1ecfc0f377029d01e4537a4603387f479abd9e8ece9d7627fd7ef77e4f077f53479dae5c752a83c7f9d68a0883c7f3f88247168f363c863cbae051c5630d8f24711433f2388b5aaee059cd6d084d72829faea9999919c73004418ebb4248bf6f1edd49438ef4fc7c9042cd354379931eeadaf5494e30a5b3cf87edf59958ed9d36f89a0b1d3cba93ac4ff7d877d289f05efa95425d49dce113343a8f201c05b4f63a85aaf4f4091a5d5218faecf3817b7ad2ae1f591c05f4d4d15a23d9e04ba1fe449f0923dd13bb4f98fc7ad8e9697e06f8f7f425611cbda7924a8e03c130fcc77166a6a6ef6b7e39d358b3fc04d8ab0156e78432990d9c59d46cb34b996a67dfd938a6e63b4fa9d010916c229d6c3c62a2e3f3174089029f1adbf9371e5f8852777e102ca4f381d81efbce4305e3ceff61b3319f1461cb75a44e631476a316d375c43cbba380d5677861f7d2721d258bfac954ba0e15dfce9711865ed7915aaee3b3b195a7b8ca54fab6eb10cf43050b3bcf2391b8d16329ab23e6f254b7994ad7f17f24cfe338d6e8e5b195d5e16263367c65652375a5eb7039e771dc68f4c20aef8db897175678b1233d363af7b25ab158e14f1a9d633df65dc7cb55581d36a4b041dab78e23b1562f76143ee66247e7425a05367a6c873e01045bf800acc506e960b1b11d0554a9025bb141f42a1b4ba16cd0fcc9ee4c1b546d8ba3282a810860a6398d0471620bb0920d627d36e8d26b3a3c1bdb9946f8800c50c0b6c776e61340b00522609d0dca2c28a3282a410f18c90655ebcd2a6550021860231b446d9d3b78e20417609c0d9a96a346f8800c51609b553118c0f95be58a2763af49275b771669eeb22140cc1d412607442306a48edb8f0cfa9c1f940a41f978a193a2b5dff0c8f60a49dda6067da5ececc1284bba3cf7e3495f09e5357b300cffb3976bb0bebb3314a04c62021ffedc0136f81c30c6d2a5e790fa747e0eabbff36b64ed3d6775a3c3ad3dd5274bf335686b78649736d998ad4b76c64251d19a6dcdfeecc823dfa5f3f5c8bae09181e87af49c151adb734c32462f4acfa13625cbf56759617bf69dbd275915aa47369cf6869cf5bcde2c682fd8399a50e784554a991b52ca545b04e451505753f47c0eed1bdb6f94fa06a83d07ecec39a8be519fba3cf7d29bcd099b7b8ed637466fe91ba38ef49c51dff03eba3ca8ea1b9ecd01bbf49cb0bfe7787dc3a4baa173ea1b269b0336ea39619f9e63ea1ba0cde11abce9864ed7375236876bd56fe484cdf21cb0579eb3d2a94b9c3925276c97dfc8013b7ccb8d1cb0554899f91492c5bcbc8b48a5cc7c28b294996f11a56431cf22aea4ccfc8af82265e655a28b94994f892c29338f12515266fe24a6a4cc3c28ae48997993a89232f325b12465e63fd12465e63d11942ce63b9124765286c53c276e5246b298d7c41629339f892c5266be8aa19499a7a22665248b799e70522870145026f971fa184e804f0a580afa960975fae7703a3debe53197f350c14aa70f5f3e70e53a58c4efbc7cc5e9551f88fa1edb11bfe23a6229ecce0d87798b8a9f1e0c41d4e93a4c36b683c36ff8ce7815171fe3a18299ec7508e03babb74eef0901c13044a14ea5c75c6c6c27e6308fedc8fc86c77600f0f13b01380e2bc4c7765af65f5dc7cc79a860202a449d4ea595ef319698e3f0d84e000e80efc87c7c32666660bce04cc00413a2254349b071c1a354c2b508d110519dc1f57e6dd6dda73d37e7799ab3ab18a3d13dcff33ccfce106dc4732a4dc6c66d9cadf545a6659a9d9931b4d6d36a47dab1d659d3168f37ab93ed97e33ecf5dd2db50a969754ac9649012498d8d9491aa2ccb40205a426865d5e3aa92db6ab9a8d1b8e0817a29c33c48a41225a4126e820cd9222665e6654c8e3943e3c2e6058cad042a4fab21003fe2ca5559df9a01b2912d869032135532b9c09aea86c4d940b06b86d60cd9084122e23bd50851e3497342d4601d11c313ade6ec0d38fb05bb6266e82bb4a135b363d8a50aa90453620d21788077f6c8a29e2873c7f4993d62ecba3743e4e410a2c64d1040d0188d7e98014306c8da94ce94c916dcacb556920c1e2b0c1eeba58c7a76f88c7cf0ab6685e0114877d9da6bad1dc9de50abb4a6a608183266d0d078f43adade92327c9218f238d2d7105404e09554bad6c776c0903183696843b04b7b7d0ebb48af75236db547626d2e3315a14416a492d6c2d604d288ce0a61007235a4913272268642414a2983004a56b8047a444a29a9ea413c063959675c65854788c0f92aa594b26632e349b39a65999452ca958ea6b6053c465a2ea181944bd820ab3a88e59840a44c8d30281ce206c6ade97a5a0370beded4742644102fa02f00e7af8a07b367fecc1f9b14fa0470ca264c58465723345d65c0f80038634627ccd43a6d549aed38e90ab4830904a326c84d0d576b0aba9cdad20a71bbdf7a9a7d4d3a4ff36cb7b6f66dbba169aeebbaaeebec0fa491182383539dd2c86a6667f048c5d9dd9cf459d72adcb49b340cdd0af762b97c8c5df205a3a851af9a1af0e3f8c4757a854f58c4a984619cf038bf22ce2614665e254e220a333fb2cb74f918d50e7ea92e7dcc1f1907613b04028e4bcd34e9418670107b1336690aa5c4796422a9110253db066e2927fdcd264c52a8232dd29e44ece25eb123f415c4ecf164ee68a2299423454339789c5a8ff45543777c1b48da3efb6ddf068a9348910f15240924f8c49b9ae6ec341286a3519139640639896211200dc22f190c336728cca44c26919e3f3038fa9a5118867625316b1a058eb33cc826ad48970d923e1f3ff80788fbc0d9434b2b1d9f9823c7447b0876712cfe815ed9a4d5a2933667431eb51e895a772ad105c3d057b1c607bc930aefe029cd1d4a9459c49dcad15787cc2013088ff3a30f09d1179abb5cd29c9d403cce8f87f9337dcc9ff874836ea499b7a13013c62b46c52f8ed2c4cc70ac591a0270863275a94292bd1c6974e335f71167cfa393d1248ad29a3c10fcba6118ed93ab2ff81504c3685c23f4aa9901c7be7fc1a030da6ba4302208401acb21f200efdcd1da2f4576d79e75b2352be4bbcc415f9561b4739a565b93421421b63cc8e66b2bbe0c32641a6117b32b2b32c3894580f5b7fe683734cdb1adf234f83595308c76e550111dc7d5349fe3aeb339750627ca1061f0c8afa1af991a2f148b00f930e8cb05c3f03525237d4d25d408f04e20ad699f43d8a50105c9116357cfcd84a271d22ae99137f2c44ada410a96347733819ab35cd5ee85fc9230edad4944da51b3cf2ccb325a294542a5bd5e4b251001e7593c4a105056381343b7bad15a6badb5d65a6bad1cfda4343bb541ebb791151a019cbf312d9f8930a48cfc0b1b1ea5860410385f29a5948219a552ca2774b23365206c1bf2da3b6d7ea5d16bf4dd9fad46b74157db37db7dbeb324f991682767a50c40fafaec463a697a6fe690a656876734e71372063398c18e0e866c1cf42b791b15c68abb8d7a7aee2bed36b2d36b363bfd47566e56521950195019cc21d289366794b362b6a9f16f9032f2dccc62b529ad31aefa4ae9c8a3a494057de91d7b03a2e5839b56d76bb3e3bc201e83ac7e2686474929a55ca92565e487e87a2a521880f339b52ba7027994d976b76ddbaa96d55ab9eb81bcf6a9695a5629050281402f88b74aae66d9cc448e59045d0f28a594f2a0b59c3bb8f2f555be7edec6644eda7a6badf5dc9d57fe0cdd9dbd8a61afe4df98cfa95dcf97ed8dda373ccd23a72745eed62a4625f548a43900e911b6e6af07a9b59cdac68dc45b7b4ecb543a1d6460df2ca34830d12294e857bdacd348db88f354b55a9577bd693dae6f3dc8a3aa6b5fafd9ab2d4755c5aeda2da5ab46cd2c750138cfd394bb1e6c5c152fd7f5d66a3baed55e3e7f3b5369a69d5d8a9c0eb5d57a2c04052838c109a809a809660dc26903151113f4bc8a88093240d3362ad7ba0dcf39e70c69238d9e6d19a0691bd9b7da3b3cb1f6003dca4b34a1094d68026582ea9ea86ad98efa76fa26be9cec69dbee89376ccff3b683db4fdbb66d271f580efef495a358ac9432a783564a19d451f6646f8d3e7d0b7b3b6da9834fb55026784c6869b11754a92813502a150ab5f21614eaa7af5650aa151fc2182c57592965c0b3a8a44c2865c2991a9d117409f09e08aabcaf742771bb27b6dce59b6559b967bdd429754ad9158a05f594b79d5adeb1e779b7e196afdc136f8d1d4edf589eba8e8a5e396af33cbb62b10ce0e915bb6d5ff9b67dc5de8ca94b1ebda352e2add15b78f9f9903aea77e52d360726e08abd09dbfb6ae5a76fd64b1df5d4519e8a2e8142fdde849dfa892e913a5d7e367c52792a7b51290f045dc270f5d8cbcae9122b287b793aecd357aeaa4f2d2b3fb5d80bf6e92c238bbd60af1c14572ef9f57216f02fdc8a786bacd840851f037866af88376cf94aa8ba8d95b35cb57296af9c45f516fb5918b2572cb791faca596c0e51005731525f79ca4a1e57ece9614853a3e7e545c51e12baae898d8b16e855cee6c5592d9a199a9a1922168f2a706b81deb6b578dc6a402f8ce1716bdd8c46e1adcdb466da66a255005fd4168f59a80a5934bd9ddb60c8685101bc353de7ac36474e0d69ef4dd8da39ed3361e53629def098d921e4d00b20728668751042ccc4f0984d01bc6167aaf35cb9a4e539297fc39e3b789c3d371388d21180f39de4362dab942757ba397a05e3605f69690b406ea254c02fcfa34d8037a49d59192aef7785001d240bfe75e9c9ead9ea49c1799e9e35f5d7833183068f3f00c123cff6c42b91d09cddb0991f8661e875afdd3ded5d13b5b335aba194e18fc4b7c49147aedf6d58fbec4e58cfd3b5a9653101e733221db5827793dd1c9d5ddacbfd000cbb369f475c0d3aa14c0aa5c605a5313dc51a8e03c130049b54cfb18b5fdd495765dcdcc130fcee9ddd0961415cf6da3c7d1ea715c079c95c764d148b006bd0974d514d87506a5cfcc0238502387f6b9ab5289d138d948db8960b172e844017b4cec48e88e18921add66c066a8484326dc46d355dfafc26e9debfee5ea976b59e44a9f68ea48366736a67df6a6762066ada63ea8334aab5a67f60183ec7816018ce2b9b4c8a0322d2565b7e241baead6def400fa0a80250673735445de909ba53cb0a633e1d461eeb087adee77d1e77d391461eebedcdc9e4e899785b9d6599766ea7339c191e2bd73a65566a49997a1bae55d634fd6616834eb624e99b48d3f431fc024f47152b758aeaaeb233b6c530daabb65dae2990995abd6b87f1c9380274c12e93cb74ed4612f576d3b5cbd86e12431eb7ed9f3856b17590c7ed289103c51886d9ac2422315d01a7a4afa4d24400b7731ccd810928956c730e511f1308dc184a4f28bd4d20ea63098fb4ddfb34835c1233e3a2352964d22e658b95f63bb27edf2cbfacd62ef9d6d4afa7b261172b7914e2e52e2e6797f0fc1a1d25096d4d4292d048e8f344b10890fb0fecf23ad2166e2b2b3147cad0bf8843dcd4e01108a667b12ba2a431834719305ef058396f44924b8a5e58eb8114229d8a24b0bb36faf55a3be5e8abb392dbb826e5d411b313ba9653b10508d90245835da04906bbbaab5eb0ab741e59466aefccf8a9ec0d29904da53aaf9c3f77a4ae1a9db2d8115713047d814e4aa2580478e5941cec2a7d6766798bbd120a8b2869fc108494a13f892f6c2ac7a324853694c647cb153c2c5343a21b9d8aa36b22d3c0a0797d671aa669fe0f24199d8b3b77de76eedb9864672e173ae998a680d6dc189ad6c6b7c6baaad65c307824a5441b0a433a4aaca130a4d7a0af93257d067d811608cb594982d2a41c76d5b861d777d2876057e9a4e760d7e8a497c4181e49ffc4191a1e49ef44b10890a3302431072620f79a8fe911a4ccba53b1bb768daf9d020129f5ced4453adf24d2c4d450e63e512c021c9d3402390cb97186c452c9bd697566bdca25a64d90d75e351b9ae55800b2676b767394a8af71160175a5dbeba7134ea613b30828bb9c4ecca26c16014d27b24c66d3891afa421d6583a030f455b394c27c276dd98eda9be51e4a85a7ccdeec94329dd859c463fd1d7dbb99cde159d4755eae377b4da7dfeea54b7681aae68e52d117eadf4f97fbf6d9efb3f746d5de3f93bd5175f77ba3aa3d223de18f071bda6b77d606a9bb51dd1b556797dfef7c329d9808f0ac8decddb3ee4636d3ec74624ae96c9b4e4c16db36cf239d38b34a53da1cc36c9f62952cea6d3c9082a02fd429156565ed40ba36df89a34cac3d6b8bc523f32c9a4e501e139bc4f044091128198d46a351bdf67dd3de393f1f847c746d16e2f8a5fa56a791d6f88823d807bbb46d7484e6a335eec13eec923e2a0b6d9a0f8f75b475d3fbfc74200939412075f9edc02f144af41866b31c7d9d3621a867a29094f5390c7595e8899e89a71e488f2d951081941e4c1a87964af420d24a88c048bbb454c2889feea89705a9401408ff58fec1e376f6e961d9078f5b77b3aad926a9aed95b4aa1aefdc49dae5d256a391484313d65b237a7e6c0042cd99cda9b273fb6727ab57645eaf988898051369ff596d96d5afba337abf1119385761bd6467b6fb8b71a5daf105d71642b46d8b746d7dbf0367bda0c845dad0ded23905da7d3779ab87d7699a81d25925a616b3b8caefd749238bf89dab9a6530463c41685a11ced62bdbd8ad68666ef2a1f91e1d0ce6a894f38e864eb452114d0c9246218fead6929e5d0144924672e9d4c4a67bed98c823a7f4a912a70e59e65f9b4f74da4ca5c328f4c2455705404a906a8d4ea168c7341ec18433403000028d314003030181009c662b168a42afb1e14800da0b8465c244bd424c751ca18640c31c61802000020003033401a00f623f37cf0eb6ed47b22b1875f51cf66cf98efc12064fae2692945c1bbd10a4f8dbd26ece04ca8a3d2f2f96554a74d948a439b90ddf2d34053c92a41d7d6508a3371b43d9b4ab55680a57ae75058f95bd66c7ab5e847544c487325ab82320094b45a7f42e68d5014fd804451670c3298e423e58d1ae820de0b27ed993aeae628bbdf3efb7b9951f6ac9240cb132cc6a97f972412c24dc7fd6eee9ce4b6f8764c1b1c2798ffa848e50348b4251299f008f308e37e2ab27896c33cb1ae2bd16981d98648a13fa002149decdb9cf1ee093f39caf98b8ac1b10f6932acd85f3c8a092bafea185d245921134c638dc0b6bb2cf22158e84b6e68361a64455fc099f0b10a6ed3ac312833deef4750d570733b8c54e81a2547dc4a467fd03f13765d3e2dd4f424b906577c2d38cbf0820b2d977e48f92b5f17518159e2b8cd728868e8560ef73f4f65bae08177991de022d7007e7b4ecfbfc941bf7b7d8c0ce787c90539d7a664342e5d1a144ad978394f8b0eacf9c22160dedfd8253c62fcc7ceb8098594ffdc2c5de0583a9879fe3d2693f65c668f3c629a28a157f641d73d5054101bc4057563f8cfaa000d8d83683e6b30c8d212f5e0353aff5f70f5a25930faaa04085d38f747f3f8444941ae6ed917da7f835fe86f09d6aca4e9f7098c22bb1499cf7da67e769c6c3681f124aabac10ed0357b7bff243746d0c53e2073c5b3f50346b56397095aa301d07edfb025cb4db4ef2452321dad5e292042161075d6390cc979f1b73e7d6df3a542d9583314b6b8d540443757359087420a16740362a9c7f466ce84d2bb113b746c5a6392f79777728dbcd87466f7f3c1ced25847a86a507dfc1c692a233b0bf13a91182d428c28aeba5449e74763ab066762204b121fe83910433740735b40d88621481a94dc1c4b14c952bfe1e86a7cbea493b6b3a4557689c7bfbd2c4360cb5c8012fc62905972f5aba9c488b2392d89d8b080812f5bd86460c494bad7a4542323bccff1e1d63f8270e4a267f3c3cf0c204fc625ca0886fa893daaa29e35b08c1eb58c564736f532b20a746e2bfffb386b0a958db549766ff04c96570fc3758609602f36842831161b4e27df42e8400581e85bbff65fdd888259340b271f7d5d71a64a4d1285f8714f5e8a348962c2ec5840a2d8b2e6d7352a51fac0e1e2b52c258a5d41d6a022471c0594ba0b6acafe2c9b8ebe93fa62f30b597654d02ebf58eadd2fa964f6458fa90962466dcecd3d7b2c818cb25bc9c00114e12b30742a49c8e565ca64c1d9408a965dc79c2e8d426f0eb59fd561517216997d8b9f5a3a793ecaf1f41e2294554c91a1bd0845a4c86507c3df9924abd09520d0fecc707e1f2f564546b71e3b8d4a2933ba3ed84dedb87b43f5b260247d0fb54f1793293dec32b2ef53a9dd13f17e71fa4d0f014d48203f8e1d2411c87b5dfe81faf6a1c35c76e2306a859985343b3ea6089761c40c308e26e147ef6117edf67e872da0b704ed45a77a2ff69f49a7ceba28a46db19a381a1b46ddb12ca96793c9631d7914076bc2fcfa945c9bd85d161693c65165daefc359510512e8acfc24388ce539f05bbaeb9ef281459999dfe1c17cbfbbd4ab7c9cfce3c564579db966446c786b612d7a83462a79d1ddc0fb843c7c288fc9ba2ab002d6b58f1c21a18f809c2d7cb7b1ed9566b06a78897e0d5d71992d67d47f63308a0773ec390455c403bbbfaf25f6f3b09d75dafaf6468fc03d15e8c128ab000ad16ee0cbe8d127f6b71525fa0bf2ce81d0d159d8c28c9741ab193a1269bca9bd5fafa6aec3c9fe6387440184f37d7884ccdad93dfda7d1c0f382dee58c789a789c4adf586c76974a14d8486d4f5c86968990f02378e8a07df3dfd62403b52740b8872c27c3d898752a1d6093693bfa0ae2fdc6182289e4e51d0bd342d1d22799318a2bcd59df1a8c6a88eb8245769d138fb4980fe828fc0d90af89eb64cf11f88e2b3ea44824273e148877a5048cff03dd0fd49a1190887fa239f5c1104685835092fd62315f4351620b8c65059f8f90801c256b752583f3d52cd1bef1a7904ff170f8ffa008bd520f7e93a9f18527a829cfdb731971f852663ef7600771224134aaa0d3ee7af3f34d083ab82b5898a753c01bcb0f42a5933e4e0b2d11d93b4324e0d349e72423c9eece1358e4d41fd7989d84ff98c60f261b8d5a8e3eebadbb2856d2bc2bcb39e3ae44c38f33a6d29c3034fa77740c1a65f88f14a12a5ad74c96fc2e6e11f02032cef714a093241801bba17dc9b1bd2634eecd0eca12cf28d1ea5138537690a5c7a8af0c6a8307776e1d18fee88cae06cad80592711a7507ace3bc724351aaa6c6b4812d71bbf82c7586d2dbc201dc0d2d5218cb55cf56050bc5da2a612957968a017c2ba1ef9a4b4562904e7a01d49d925d590a9f6c8221de76176a36006fbaa091b1ba9722ad1830d64a087c40490828c1fb063431d6e5dc2aa34b969ab57266c4978189672b488010d56c18ab46156f5f4d833c853489854937901601389f1a76347e712f8cf5289f4a38e225c002f19a3bc2f0359bb26d010a6e53d4ab07fae08e185891ee8e45e6c2636e3cfc150fdf3b62bf1dfaa538e5502979eab5bae4c22df792af288703ba45406f133d53790c1d3a26a2bf6974ae1f2781322551d7986b1e72e488e046d0a7f86471ca4761d6fadee983cc9ee51b3b87c1ab4515dfbd1e646484d62069af9d9dc03016edd703215056cbec58cbe345694d099ab369b492411d416675c8646b898ae36f6abdc0c3903682b664303e07fea2a6d68d88b28bde2a6882e5f9177bf211f6ec650a59fd7a3efbaad71a5f2ae53d7c43ebb199ff9097ce83d99195b755c3007e4d0d595659379fdd81dee9e91c1f17804ce55dba105f6a5add37adc0f49550f43458c30905860912b65329d0f328c165b06a02ef1cd5ffa1b09df111b03e7cd25a1f7509544f59e99677092887afb0c9e90615d49780cc13cd6497718f3a227404c4fb3c002289f2b52034af18250a2a33c635761d14f4855490fe32d3ce269d2f0c24e4be5a3f1dfb1b74c292cb896a56ba2dac99b97bfe50996844f4737323a9e217ece6236164222b03f5b58476c877524f389e94c44740c78d50d289665f35744f6749c2dcca49d372642fcd39ec87d5e9caa8215559885f93819dab9e57bd79d276b6bb01332b9a55e421b709e1d76e513f5888531eae3b04c00d0a34600637be4de3b8f7273378006c80ff8245b7256b98f44002a10b29311a27c7f1d4484e04ecd8d5e42b7c311980d6ed909f997eaf3ea40773ddcfd403c041f5f7311de42e6dde93d4a7e034eccc2e271ca01acf7e327d6a8668f0062c652047f750360de136d44cd631609a84f2501b93c4e35eb51fbaeb72bb576fe419e081a85bb1d7cb5ca5107b8f6e6179ce2e27f42dde48d44bda64a3ce83342fa439e8773dee101d453f1f7a63aaae4137fe79e740f327934149ea988e61e3b10144eeb232da7da48b37266929645144f230826c9ef947bfe9739566315035450f8aa0ba2c657941e9d41eaf349b89b27302b48d79bb0fa01d95fde1aeb8383996b815ce42e2525ecd779b75bc964a077df0d8426c4eb1dc7fd31e8e98ec110b353e651adf8eb75c8d801fd2664be5173e285fc042a63ba2df5242c3ac546fb084ae41663d915378457000776585541d4f9f698e862d31602d27bfb0e2e9cb983e057318bacbf909d7d7ad35aeaba409f8006cb22a98d12f4d061c36a2f7e40fcf3f1853b10ac17be968197af010ad6a023505bd1ddf0dd8c34d4fd70f207b10fe95bf00c119a221b70bca0b6e070a4e5516cf6390dadf4f832282a755639158d89519cfa1f947cb9c70f11248bb0a9fa2c832962cc25ffcf3b139b452557b9750cbcb4bdd52db20478f90fa593ffd189fbced97825f1c3436830c7c8a35601272c170a8c55a41c3c406d3ae025ae4904eca6b01cbae512c62f38e0a9d25c73a4cb0b4a86a62f32857767377c9df2253fc954bb11e975ad4a6533a02415c0d2c6e6803d2d2da3d30556530113cc5d5a6920556e4e8a9555421a19b0f059e410a7326f51dfe6783bcbe0c25f98ecad3e4520e6269edf621790e89f06bb3299e777ef77f0b8df1b135708805a2fc4ffbc248b4f731a53a24c7b122e292ec44b2cf0b5c371bf85e5efa4189aee06e8be02d71c91445c7bc517949db380a36318fb3e3e27b37149d51e1944208f21b3425803478eb595dd6e9a034d6edc2f8dc18262ae5c506043527fd2383e0933af60636ddf8c6288965cfca5749e928e01f9b5b4c88cd29ef54eda08f43c73d19c5d5222b4759a420ced30656756d0fc05df412efb1558d6d5190e1d62794575cb120d554de749e974da05100995f71aacd38de4d7736ebc952fb15898452649d0090f936721d0858440d52f89c7864c983b1aa0017ad413f6afcfbfcee6f8c67106a51a3c6c5ba574484e951a7d047c71ada06b5b97e9856376dc3921393b63ad876436fa9ea0566ea0cf9b274449a7c5e4b75502283667f000ff770102894bf61400cb2a2df23a7a62fea926d6f52a43c594a5d3a9b29a1dc6ea5c895ac135b53a821c2f296a243245def016c98bdc83273a0dd4225faa8281f4405a808969edd422d9ec3fcdb67a7f19d9b38d3486fd9b3d7b86ad413d35c13f7bdc2980b2efd0588c85cb4a281c9cdc0ab5214ce63f6c4779cd74ab2b64c4995c3b6cef18322974adabb94638e2852914126f3ca8be8d9a85e5f1d63756dac201b39d4de46a739f5c0107d55e61807c010e7088760ff73c8938254517a07b0dfb9b7cec67a506a9775afddc9129677c3a863016699f1c549a507011b71ce9515392c9913b3a53267677a9dba2a13e793965cd8b470bd86c76891bb5026934448276d093973b3ae27f93f53780c458a02b3bfd598879511a2811e3540fba84db9d6226c1ab2d76c72a8e8dbde838209dd16fdf662431a19c89742af6987ae3a5cac94883405886528497bf99565a083964a0334614e226a6d68378856325c53beaac18bad58b1e5db41353237e377df0627efff9fbb09a89682e634ba33a7311d3e7a1f6b02396dc83478f2353b9a59d71f9edf40194bce797649b5b82d2152e0e0a079e561960d98325ef3040043e076e34a7dddfc52df6e6e29af1b5fcafb6fead68538c7503858e8e601adf5e53646927280712ba0fbfef08d78c3e8d03e6b2765c274d8c763ab89703a86612e7401969e7aee2fb9e568118b0ab3889ec3855ad0f7482282c7d7cbbcadbf6f56e25794c1001280a8dd736c438a40698ada1707153485651a757152bb6130b403a2e3dfd1445ea39df588ea87142595d6e602514538d56b5ac2957099273a86694f478a2b6fc4d80d83b3e743643c677e86df9a13550bc21afa3bcbaf558804558d10d3d6d044ff8995b4cf095e359698b4545cb025065c69065cdd171f4363a6f34d6b24bc0b2c2e3cf422dc4bd9f56ebf7c605c423335260d1d0878264d64c817e4a29487f449997da25131bbf66ab360064a691c9c31e8ea391f9ece405b16ef8c75a94163cc501bd14cf403d50b17ccd86bf140de0b6cf208763aa6f9864ba50e82839d3340d3eb832186e0d5fc61fbb708eda7312d54ed121557a27267f911f339fee6d77b3dec9df7df56dea817736c095b7ccc14cf8c2d61e85220fc1cbea80b6fe0539187892c01f1480c592294b52de58fe4efa82115a7613f70b21a6443da123f527de267f857e3c0957006417226333078f75e74071edeea41d5924ba50476dc7a5c09ac9bdf11fcfce1171e792ad178228ae3dceb5b3509f177c51694f00ca4f986c79ce8f412b56b4420869fc4e47405c76f33610677f3cc5bb93f61787a125ced34a96f50636ab0b341e95c95a63042490dabaf2593c678f9c90f9bb75187658a44017a69c3efb8d4265ca02e1aa05ab75ab1c18e2e65a0293d3067e57676f7c2f07c971d903e7bcaef68a96d222fb4972d264af2aa130ebdc502e167621a6d13b579aad152a2cde02005865a3dcf5e0ce1776d8690602b80a93b44932686dfc452a20386bc290b10e8d53fc5dafaedfb67b36933084f2b28e6958a1df2e783042054e00ab58b93f0ac50113ba78b9e561d310a35221598e56f57593903d931f650ec97fc878d03520667eb8132809de1e11f031d51ac9a3f0636db630042be8742ed56128c2f6d0f322d8681480bc44be350418c4d9fb07b765620fa066318f86adbf19e45db6ef0dc44b20e67c53272b0295e61603021c7f1e579723ac3b35d318f4ef33a397f4fe2159f4ba13e6a5ec1b3bd1bee750550a114ddd6b1ea030e11b95330738af6e2b0c933f67ebba58b164a33e2e9b900ac016a4f1fac1055c6f4dffd9d3a7f1ee753d563c21e7585c1c884ac6883d4e37eb11945e58f867e3774f0595476f9301a0e81f4d459e484746641ee729c84312c27828819933fe8e00f7c9f250d87c59175786ba6656197603501db74c1ad81d63ecb1972c954807bd30b5a99807f9a79f730bf8ea49fa6dbb0266d5b87de1bf07305627c2058466a7ced0503045171d2d18a70c50293e1751ba4f2b10fa02102d57dec74dc13423854b59215efd8aacbdc0ce88c8e4b22c92c151cb5f258a4705a8e235702c0e8e562a3aec1a2685312882c3b5ab4a9e8943dd4049b1576f30551ab48a3a086cc08e68067b4dc1afbb74faa320d07574d7ac08f178bc90bbcbab62f29c47ee1a63d58ee14432222d207c0213a20d65e22ad5983b521301f78dfce9fd1b722458620d9c07f2f7ae1b2a50ba157012fcccf6a3f71315a6282076c1fce1986ac7a8a670b1777de1e95806d596ef930e03de7a029182685f4a370c67cd46ef55938053988e0640e08694b13aa466797f8b312bd9f81e7ea9fab88e818fb51067fce0c24bf3fee5fcab5d101c8219a368e27d42f94e76500f221fa69759625363fd07132764dca876be4646ef493cc09e2c6e4abcc125762dc4d1fc27566b16161a50c7709a35ac06e22cf947fe46e285d5dba4c26737e000d971d6707178d42cc33185f6d91c06862245050ee5b9e64ed1bd5fb73b2362e1ac56e325f2286bfa6c8528de7b03e3a74160790bc5c2a440249cd45c29e53d17a8a5d72610304862341d50cdf0759e76f4e421e4e4f4439cbb55a6bb5b9b7a1483d5657360de50812149cbea246321de4418c39f981c68baefa7a4553a1e87922036b47c42210648ea20c792ec026e246502d453dcdea5e6e688334b36c63db5fe25dac8daaf3984bcd780d32d089b82bc97480406092ef85efdac9d7424e84d98d403ab387274d31616dd83b9795d5d82f2c9eb312b009d85d14f7bd0bcb2fbc88f2abaa245ff896b1bf7fe7de89b25c7a7ab0b411356f8979bfd4b3eeb1c905d681a19907a202c2a29a0ae7141ff139f3e7509b8a0afcf5127b3cacd304776bf54aa2f74267c808dd628d51665d50ad3ecfcede088bdda8ceae8105f5959750ddb72e77c5179f8eebd1fbe576d6444135cd229270c0c460687f6129459a6a65935acaec050ea02410163b19608699d1dba960ecc6802b223c575daac480a528deb1ded50591caba94aa1674b632b8df27b138601361e5596feb212b4f25258392392c945a4cd7a294da434e43cc00cf081ee5f626ae17dc3b1dc305d39210fece8bdf389f3b310f15c335008126a33f6fab3c170b6faa587b66a9feb91355467459298307907cd7fbd258d48e4e2ca2416575ba7e60d472519c2924b900f04772a1a7ca452da235cbecd98b912e52967ac1f7971bc5a6c17e6dfb5c9a19cfc74380a243d0a651540893c266b475b5d116012c1dfa94b382c0f032a44d202c4e309d9acc922f1fb0b10b867e66fb830994d2fbf63c359414478186844508de286eb4baf69b069828df3314f451785f87770ebc6fc20c1339c48d3d31fcf5b807ff3ebd97419cd09f061c227f5d0b72889f77e168cea269a3ca62ee3ed6fc389ad11db6ecbfa7459ef259f6a7cb99e3ca0209ca3421c7e5b4260e33bec4a33b6cc15f77e0c0fe17e9e007fdcf3fd8cc1a6e5e34b99b4aafb5018134e850bdc0296816776f6357b0471517ab9d8ef2a5878a9b302c2e2edf6e38691f4b52511e0f7f9dbffc74fdb14c8ceb53ddd9422cb18f492acc4acba1158a0dc083f0f90589e14f3cf06bcc2de15bf5197b0fca2c50383f99b0078a99f38a2284f333eaade7d6c3ad91eae13a71c6e2da55c18915a32e5412a270014371ebe07b7aa0bae07676d57187e89edc27d51c4e371a4d58204686d16a68ece7dd19c185a05e60a4c4a49ad3510f0901a3582cfbed2bf7598c43353b5aa01bf9441d5517d76199d9a586251429d6d758e63206dad64755819e7e094ce8b3fd307bc231783e709a1ecb001c8195ee086e51e2b99cc80d10066afa2b9149b3bb0884d42c30af1a3a02dc0cd8d6a9d46c25815feb738c1e99719687f90598e76a1ad609cf37eb706b26421b9733d0c30b3c96e980447fa471b6cfc02b544a5653382f91fa7e64a85d7579b7da9c1b66f4a2d8e4bac740084a939b22504a340dee667ee75f0e7ec84e24f573bd3ea38ddd5d13c914ebfee726a6fb32f49508d3d22a7895231c3c921506463b06d1ce89d01b2052d04cb9e4e1d841afaa52b160109b5f6c50181be539f02abab503c589248f71d6ef63507a033922c57520ca924f1073c68e3f209dd9fead56cc43fc03c40e4bc77efc0d18b4adf5223db1e19c7549f68126d4eb951f05cd8436b594f96193c2281a23d41ce317e9ec33be1a480a1409120a0d73425e97af6ea6a6b8f52a8f8c3492cf5a5892e10e9e4045f383014ffe54d65faf903542dea47614d3a28e339b1a684cc0bb05ea89bafa7387b4344296ac3a8d70212549ee93803d9744a455e0aeff9da86d4f1aa307fe3146f2bc44af8fdab371d21149e0338b88323bbf0308fa4ac81947b0912a2cd3b4c6f19a849002f8dac585578759abccb09ac747e0a57a413c2110f4afbd0486db5e8561eb87d852a2d8e23f5906774d30b7b07d31b928cd7d1e8d33beba785c9122b796913130970ddcfbe43085d6bd341b0c1ef1c98ae41c042ed8b3c4cd184eb3cd2de750a2cd5886ddea81191361cd1a0caf0e11e0b5b51a7a1b56d6c966a9d4812862ba34afd195e09ec59f181308e68e4c69465f747bc5a7f750632462c24065af139c3179b0307a7971603f433c6cd729265f3cfc2efa2bd2cdb81840156843c1146d09a773c2f8a46fd14071afede032d5eeea98979c5a7e62526f4fdb3612b16b2f9c21c47f16c4c1c6ae333ed389db5fd929e85950a140c3b81636b801897f40665ef64cd7ea65d45b2645e698671492dcb4dc1a8e2533f156e46eae71bacba1b30a1e0b470d9094377dc4b704feb94bf3c6164287efa23c93a67ab24b0edb07f431b3333dad0f419547fb8cb4180950dea11bf46ce453683b85216ab87b31dc611bc689a07054cf1ef9b0794687df0a96775b1c5b7980fff6b698892505424cae70947eb3e19e9087a47b0816ddd19709ffc22a704b3b6d15e40006a371b0b4828cb8788f6599d7534c1be8d105d68581c45ac609394422a55978c4e722c87b9a71807a8dcab77eaa973e9be381757469d519450a0346f3ebdbaf70b81ac5340bd8a8bdafe94dee65e0ccd4dfa706936329a33a3bad859e371ded566ccc6233cba61ea9fdf6fb64c2122667627db9a31de9f8dedab1ddeff78f7868072acdc4c73f4b20d30a5946b98eda79f090015a227a137fca229eac641390f74fe8039b98f2c4c63edc6935ecbb5102aa1d5084fd91ff622678f1fcd7f93ae2578bf7b127136c9feeafd35fa8c67f782594e40bc3a6ffae3414ed9353a77d6ee3ecee7fe9fda7febf75f317386bab25fee0085e37e4b2e57e71291fa4612e250b9d0ba2ce4f38027ba7eb60540b58333b295c0e1426aad2ca3bf19869512843ce3022ade16e7be86d840e69bf86b4ec815408af8c41b1e9d65e17a7c2078516e1faa55241859926ce9f68063d77e3aa3bcab9722a18893178222daf3e28aa8fd1de5c030c6110e987ce9114dc7f5236318dfc09418ab91a752ad213e91579e8c91edf81c2f29840d0bfdf9bb8767610f1464498330f1e0edcef9e50011f3d82c9befda2d785b090532895b4a1c2ced3f6fe5abd39d2d8b955ac8e55590130a9d266a46d8b11ae4b96a215d6b62291bc517e1fadc8ccfcc0548e74db302f6b9bd89f123bc3a2647c48f7bba01cfde144ae091629bad444287eddc7174cf4d544f1bc84ad029d3eda887fde76cf28e2b8fc8b34dbacaaf0eb57339eb8318e91279cfe23163b100e601a4866b1d284c5085aecd84b8d81ea550248bf36b77775af4ce355f4f45699502dafbd956e903a8f1fd9f7ac5a79f9ea7f1b09cb118f18dfc851bbb245d46f57a6d26e9bd70af59b6711f1a37575478911986e330bf98effbe4d6d35f3800192d9e97b1d61c6279bfdeefd98b4b3d50284581d3e710a76d93a8e89c17207c4a8cd55f84ea78fb0fc689db35618498974a5dbaf0d0c80b0d55cb0e3099946ab9601bc5683740c862d89b29e9a0a9a85cb04178734690efc6bac3e28642f7b39b4182a6a9d689dc0a2b54fb6c0dfb8cc3901a6e521859d880dc8cb8b97be711404138f5687d5fddb57ca9d962f6019734fa9fd9f9481a652ea0a997152de4400e6ff9f93bf09ad3ac72bb9f1d84e9926bb7507846ede60b3d537e9d68722feb7fbcd86029ffde09acd7618579c3220da4e9f1a9bef94c5fbb96deb36b8441f44e4f3643c0909e3846d091dd582b3ddde6e4642bfd0a7f4026553049b09201641cdd0e021c5e73a4ccf3b5f45549ee87337434fd2b3bf216326d8d079f135d1c8ec7c4e0538ede7c603f4bd4c9ffb51f3c748b4e9f3e09205da7130b68f1641eaf3043951a69faa2173fee40d1973e71a0222a170f25f911c7edf7e574daddf301151ef6bda56469f508008d06f4a460107a7e8d8021c6933e5d13446a0074024794e1bc92cdebf6a9b5414a79a54d68d75fb76c471eb9815a7d1e549aa82922713dbf003ef57332f52062db39c44ed5795d10cf64240272499a6a3da0481ebea330692fac1b5dcafe41391d2b9d47739a0b0ef1a5cd47dd7347a160835798bdbe984c4ae356ecc4e187e6771064a9728cf106104aa1e2525735a0349c5ef731569720e3054503a82ac35cc9237985bb8f0f84a3e4e8844fbe9e88bab6089b27971147a8fc755f08d6d9b078ced7dc0740ef49428a80a4732a3127f532a8e1b4f4f2fb330a009b24eb27a059bd397699dea5ee94684ef040e0cd6edfdc3096865fa68259f82317d9bbb5c612a7b2ed1dce1c8daa9e80e5101d523bc4519314524d02705e7c0d7223eb0d8012fecb62eda2c650b0b304f0cb9387f218b8b829f8fb972c663e06689ad506294667af118117fbfef403b073bd68df33a3e5523c39c66594f86c7fec8940c48e69ac01d273d2a3e68a11b9b463172a954c4524da9af1fe51085a24be0b34bda90b8b3c53bcf2bcc872c5aac2038bd375725b49be6a86f1fac7d6ad71ac66673b88926451c5997b95068a21956858152b3f2feceea844bf5727dc9808561c5db2aacba01bbf8d712be937169f45406f3031049d112f2f6479c34a8ad51cf38e3de5d83e43ec9e402cfe8591aad61a557de25964a0624392ece1b214419a9513fbaa86874d250567662198602dab8fea34f07bd2053df78ba425fe3b4bf1067af42866cf33188714307acefffb14dd769f039ecd0f24854a04790d56f9d6cc198d16a0b269668fd6defe01270b536719c1738447e56ed6c1ca84ec0b41bb0fc01ffeab1d8aae20ce9d3a2a5e55f030ad07a2694c58cf8538f9939fdd0b63499eb3025409f0fe11a235ef82973a59ea7d616e706ac02d4d1e13975c00bbd4e6d53d3bda575b900d4609a2c38dff8b80a297113d8b577fdc93642041d07ac37cb76391a649b4ad890d38a16b4c82e5a2582f39e50d3b166326ccabb58e1871abd661738a6ecea4e1dd8f454447c93d1071668d61b8f230138149912cb0c1cc991c22677323766ab07279d9ca6171e8c038f185dca594c88bb3dd561287e7401f099d2e5afa593ba19f246bfc5169e7bf53a555362da40fbb85fed2e8da9f2882211c5b59f33e6bac325ba86b4bb7b38e8ece1b29312c0f95067df04dde143d7be695b33b102395b71a5803f26100d3af3e9a7773365c431e044f9494b80e75c42bfa7b9ee4acd621035b59298e1a065f8df0c9aa43c4076f90a5964453e4a0aa40e45d64d1b73d13a123d73af4baa120747dc7a936e26f361c9fd6b820e9b22143b7ce5aa354df936612ddec2a2b6240ee11760f016dd4417618a9bc55b807664ee7c1c3690031a9a2998eee120f0de4f0d9646a525047863742112e0f9c916ba81af0de5500be093234eb4d948fdfa79dcbe76ee99e62a2ced5e500b97612245da030f82f4b67c9dac1e779339e1cb28d11cf6f0d82973a2b867d9e0b6af9fc88a32531da126e944039df7d5fc8e61085cd709063f871cc373e36a2409f4f59c0ddb44aa6e383d814b1d6ede19d9e120d61a2f93a2df1e93ba0ea150a25aa3b4f6d2f6a0977818b02b72525bba66b5e45992590570697b0d71ddb4fd710f9deea77ac1bd629047b51d3d738227854f825662d693dc1ab2a5e1232f568d5b8db333602e4f205c320a28a569cde74bb7e717d21a0f89112549115e67b94809e1d1ae05ad7d94d1a6006142ebab9b9507fe10110c483d0a6fdb62bf06c0088aaf050daf87839f73104a74e118e97b75a740aa9c0ba87bec7183fc3bae3b99ea9c1902a1b414a24cf742c453f00027ec59e85f6b96a15b7fb48dbd250417b67dea799bd0532856ad1a89f66cd9cc1880688d0a2df52c17ffe2b7f78b67c8864529d41b719501d614855f4c202d7533259fa39e6b9e838c904069263b2710fb300dd36447ba48a9d5105fe29972d83e280a3ef986ca7008701547f68e9afbd5c5311076b2a805a373e400a448609c532fee7066216800fd104b3843871251fd3de51d3607ee37fd56703b7c4b4b65f4c9a4c2689fa2587919fa2ebfd75552377b7813139e3675b87197974fb2319c311837ea77cd9acbd080b93065c28d2d8772fa6ad570e1c54def9066db091cf9d65fefd88b0054711dd739e9e881b8bbab6a4c8852b9ea70281b634065dd84f2a8e0cfba816c37498131b65a3753c9af5390f483ca63435bf4814affc38c22a5377f687944e6a98be0ec21afd7bd3688fa63caf13ae4341a3ed7bd5cc5c03a56d57fc91ad42b77aee3ef279b91c7946d53d582b879709265caa16b9c750884d472fdbbdf294f6865cfaa3937fe3cdc332748ea3c0792d5d8abdbd1cdec4a75ee9aba282298c078b9c3c575ad2bafd504f4516007f42919d67c7b184b9bfe7bd34aceb3127b5702bd248c5a333a222e1ce1b512a5dfbf568f5904c4805690c665d0adb889f79fdc25d5fc4e698663d3fae1e16c8c91789f63520b768bf490c924999d326a351663b73218286c3a3a8b7d2ec52e37f258eb4084afb042479f12b3e50d4da19dee436b817a1e6c0578579b3e82fe3480776f1730c671ae7e5f07aaa92133dd1c0f0f1c2b4fb887e9b2ef43a90a8222884b52a718386733a692294ec00dfbc1b87fa594d5b327d7d57ad314c3165d99be1eef3a6e539bc98f362c04c8757b89b82e0d4b3495e75247628856d804df00bcad59cff496d880053be61df5e33ba6e36ab857646361031985e1f8a1e35ac4cbb55ac7489979c34280be5cbbf86a7c9b0182c993443f527641ba8eb9133cc92d02657eef53108d778b1127e4964a4ac0ed5d593aae4fc660a472ff70212dba35a7afd830aecb515be4fb61fc66c83d8dbbbe3ceddb64165beead9b7563870c8892536b55eef07e3ae8117d36df196f730ddbc6c45351906eb515593a331e85a41e2f89e7b1c0da882d1a1a667f04cbd8b2429cff6aff1445cc652f2e58fe74fffec18ebcfa21cca353f28679a96eb1beb845142b18def1e72b4b343ad2552775a2ccd396121d5cd39aabe13024f10e80896f98a2affacd8b671a6c4baa29deb8b7c82c114d31fd229ea6a6a725d9d8a27f177b74bb4f77dbfd19ef9ff31b00a8a72611f2c57edb436c14e22a191142de53588e1095eb86ca8d5ba84aa356a8e9663e7fcabb4ac96b7549922f087275a6a0d807d5026ea007b93530d3261c85860dc62399e18c7ce00a3485015c88d0763548ea2357a19264dd23f320c81b61a40dc64464174b79a6a418e8189693985b9b2b307ce64eaa98a1011e07430dfa09ea691a8223cae61cd63ae5e980ba4209143da9f2f77ed5b9d0a37114530009b3df40fa93dc9eb26240815632a0bad1326cdec65831635d182bc999120f3ede3e2272d0b6b6ef5053fc80ce3e5223eef47a4de0d8b2232e0e442e2b0c85ca3e3249d59adb9b36063228dd8e9ff5d74bd65bc25abf44926aea959bce0cb26fa20d3cda80546322f934586500d1865e100c31801ae231b4f2110966783b0086166a5558f66b0f8d7dd2db626f79f578fa560591443d44b236e3da07e18422ed2799888d0dd4a6c6c2fab9778c0cc4e529568add2a969032a09312141356a91aac5c4cdba6e0c39a7d13e26f9540a956125fcc848aee2f6ce3efb7af371e577b095906555442e68730e4832a1eb3fc4009e0a90599a23158cab091bcfff3dbfb7afc80527b989bae02fcd1da81442b71ee8d8f394acc47b8b10446b6f2c9d93d2e548366bb96997925995a1236972d3c24e924ed24fdb518359830e93b92182d522c392f7b38d36cf5acc1da64a68981a2466211f1c3e5e220a33c30b02cd7228122640e284cc8521fe3cca7508d34271f3de77aea821f12221881cfe1e11f0f3c7364105c9d8a1f7b1ea9b1f0d4e2d0c454216cb63aed7abd805d1c5c043cafc3a19cbdb2ef4fe267e33e4054c18dd78457428b0973f86e0d4119be0766b2d2b57c29033900c81b340aeb7b217c115e8fabbc9696a7a501bf94d0016eb750da1d43ca2ea5b293601b30c1a9245568e28c7b94eb692c02c41335079ea6b5a562efdd00a4c7a05aba83bd02ded250fb82b40b7c7a6558f62a6a05028496fb1fbda3aba4a8724242d3c1409d37d4c619d3bb1fadbb871bd7c8dc13b0e791d5c1e5e719201caadb8b76b92d459a034a86945b3061a42111635072a123e04f00af4ccc5023c51a54884ec255e512e0ba5fe1f30902d52d9e685f28e5662ec5addf2d030d663e8bf65a32a675e2f6e49f11f1408398273d37617440f8ace349c75d20f027dbe7d1ea40132c9be11398db8acab80119ed85d485cc3d3d1b0bda4017e8619984cac79f6e46f23c50c8f962c7d0376c292427a8dea20b0b30382a92192f8e1c84662907adbe73b2fa79250495f8639411d251663b64bdf9f0f008c98a25f5c9c5166fdde37b10a9ac6d732c7b57cf099ae2ab03f488d91aba6921cd6e2ed76535c9af4a7ec5f23ce7f49f3bf92e07f41b941f8f2ad1129d954f46725a0d8bfef27f005e34973f58b2c4bf7f4d5f866c79a5603521093e9af82d0939d25f70ac8ccf0ab4452409a20bbfb71a98c6d453327c2663d32778488c3ceab69bc17536293ed311257cf9f0d1c559e77f6b3322548a41e9e9e8277e587e8535367598a9cda6ddb4c9f03bffe63a9e20a8bb81db4da99b4d7756731961c506ee6a24120601cbc1f31e0901045af7fa478a6987608f0cb5809eb365d8820949013f1a6d5d2b84a86a5520d594c7f1d4f4b767b24325c7ebc9b4e90eb456ba898ecd046963026a767bbf590043ad4939a64edbc0be313d92de8925f4a68a7410ede0e25f01e166e6e5bd9666053834933fb0736dc9396b97769ea65d75299a6159f1df68023c7710c33ee6a61d53c4d8023c527c1415ab98fc513dcdaf1cdd1e20dc6cc4458fb58d1f7bc865049294e52cbf22975738eb9e36568728453b93f7e640d68baf2f7823ca03013d6ee5148e635c95739aa3b05ba2d95d4bbc0fb160e37c5fb02128f10d2426253b43fcb72937e7c4c48ffad27d4c5fa1c4185932ed17934dc39638e683f5b12ce145295829def6f90aae874f4e7b17566916c3805e09e73091261325c6327348cab0a9dc2f53614db16a058b748ecc343a0439cbe3daa4e82f97c343bd854806371b4e37f4d25d7b6823288f40d056c6cc74cf124470fb4d76c6516cba2b07f8e130863028ed9218c50029039c33e06f6745390d99894cecd60d7695bb384470ef294014efe19216dd622dbf6f3b3e2ec5b2613b80af95e10c5c8969db43ec7f67d6eaa6720a11c82f4755193d3a00e584c529b58296bc4d5088a9c091d4d01d1969ea2c198930aefeb5a17fd8b158b176db86a960684ad1ef695a420f667ff354ce3ce91eed04b8109a0a428ba6a478b2e48cb0270f444d25b4a52f08a0e9801133118f03f8ab6fb170102d624c3ccecc797de338149ad79b23c955d1a3c5e581481e3758cb4fa171ffc692c1e86fa2349a94b887dc4b502659b686d325b257ee6cbd0d8fba0dfd7aad85925b4657ad7fea4faedc7d0bf0af90c6241ec87df4f19491087484446c95a548992d15d8f59ecbaa8d7d4384117a4d013be853ff404c5befdd270239b1373bd0d8fffda3130db0fdb70d5e404430539b6ca1e9aa0eb1731a5b56e49e54285dafff4142bd947c75b13659d41121b1eddaf319b35c541a87e41aaba24a82322bd9b43542083f9efa72a631499c29abe9ed4fec68991f569e84c34d0d9dacb093f11f18f188ab9d0470eff3ceca2f03fde8ea3734bd7db899840070214b27b2c62330a2bc033d6d11a822b4538dd8405bc2ad2c4a0c70b4673d1a6640f9aa4ddb41056ee0c1af5c537042dc850f6e45a68c558af2ecb9453188d58eead8fce1601c0121e2f70663e97fe0f0c1c73d7208d35ba36670a4203588d65cc3607362b851c066b339b018b7cfcf39a91f47a2dd50985a92a1c51153db0e8b2064f24b511bf8452ab3e62d9a9225a32f8552ad329cfe4bd57c850204ee48c36a2e5d2daf6362a255ab6175d305fef26352d9853a8825798f46464e4e0d1785337178db2613b2a3c62c4ecf2e55c010ca064512a75ade7387631fbe9c40d272f8638c023f50e80f1476091ce441fb050a9c7b6850b2e19761931f692859d6fa2056edc7e0386ce92f9b82ab04182bd29b66c160bc5bef432c081b7358f03015bdd399c6ff5804937806887ae6f582ca1124e0c692e2cf6a0a0b74404c751cc18d3a49395a8a5a26d555da031a5e0578c0d5c4cd4631cb028ef6253d5c806d5a94b80f87d93d630af18480cbeb1de07d2f7fcd704d7581b8ad62aabc48bc3d056e78a35052dbf35bc318803405e47e01bfd15007b1ece88a55c6f03cb76a7d050e4ead6b2e8c2ed8e67a61b40e5bd48a9f459da0d1ae026b9cc512b0aad28168c3fa1301f0dbbbea63acb052c39ef6e20e7ac8829b254d0482a3a06dfd3ad70ad8780f4cb6193ee4d5f91212d5afdfd06a1ab91942b5d4123a85eb21c8df607ffb1d777f87cf7e9b40a1d112e1c36cb1af724e4fed4a372ce458228239d9fd4a627a4e828b65f4fde2a189240af4bd75eb4710e7a4d0107c58710a74e8712649f860e8f006b53cbf52a17d36e06bb5ccfcd528681878c2ad3364da0e3fabb77307cb90547ece99458ada3eb24fbc54f17440d6eceeffb7c84f5e4da5c6c6d4698219c104d589b97e641e8a36618b81b33828d1e366769d6158877f6f32194968adc331fea773724cc3da573f07fdcb27362188a98cf5a4476b937c81145186b1425dc475db7ab9458501ff92454c6985ec01d812d2b68a77f086acee2eabaf23bee0c0bbeec387033452d24dbd27768ac4e0748bb55d486b0cb89791be28ef8fab327d67a2123c6cfb3a7d8d6bc20c2c1ace58a32763037d142bc1350b3bb2ca170d5cbce7380bae403b55629164db8035fcf06f282e6ef021465f0dafeec327069c705c131a1d3f46d877c28b61ba3c91689ec6ff5dd83ac61eb547cc8bfbdcca82fe8bdf6ac481fa6771a352cff8a9284d4ec6909e2e8df910bfae6221d2ae272c069db470a6c0dab9067db6a0ffd9aefaa12111194897d7742c1d138bcd39a3e277ad339a4eb6576308f8e892b1bb3035ff463496a984f9855b6ffaa99eac94cb79e3acdcc875422b3769423f7105443854ac18608fb0678242f903fcdf073ccfdc8a46c4a459e7241587304e82b13dd78c35cc730163b5840c7bcdb9f909cd0d416fea0b78ed08350041c16d7f25f007818ee75a1758814595392e16d7abf77dc473e4400a466473c6b80dc77e9fb6ca3fcc8d51f8befe76b941f9927c4a33771a9325ecc65b24005fe86efb665071f3036bc2b8d8aff9686570a3bbe15ebf911bd38ec360cbfbf0b7192762df98ca254d68fc6ca6539180434dcf22bf4c48c15b541000ea23470f09adc20f05630c0905630c208c03f7b06d604c14471f2602e90155b6482656c2f7d366bafc0c35dd724deb0db526e616332af0017373a77ea3cf0f631017de385c41eac043179d5335063a87081a23f214305990799792edd3e4a5e2fd7821f9006a1ce3a13129f01cf857fc0317802bab796531fe14cd47ab028d0f05c220bcbc5f769c8aea31d09e8569aafbc2736e48a85310d4b7d3dda8161384ca07f51e3e471e2d1394527e58da22fa08a9abb5eb359e4268264259d77bfdc25b78aa979cff3a8af8b89d1340cae851e1bfd8e683ed111a39341bb725e34bddc256642c8135841f8b6335c0bc14c4c28b9446fb7c307142e12758d5f99d9a8daca468647821dceb4f42b1d3ddd8db3593ff5a149f1e08cd44072005ee2573dd59094d9f0eb4c39bdd8f0e0df931d5959f94812b315662981eaf327ba776c73b64fc5321046108eca69aa66e9a32c0505190bcaa523c6af3bba94eb0c56a10474663c2ab910939cd94f68c1cc2c5f81c77792c2a5a5ddfcfe7faa25c6d145d33b401ec38023fb24912b79270b0745c304b97b5ec1ea148df2b52fa6ae993a44e7c6ab4454589457adbac4de8a5ccd01cf483f0bb64c563a0b0ffd4e8c5ffb41434240d5ac5a56d0983cd9c9010e0e81bc6e5c68d8cf340797057464cfc11fcb8f2faceec615ff708c62a49d4597a0bc22e6fc944dede3f825dea9bd69386a9be342faa66fe3ad03ee2e162a4a7122cbd4039c432d4aaa5fa60b525f6fb973598a250c243ea717e625888f7f3ddb9a152cc68a38d4db0d97be5550286c37124105cc908818b57f2cc433bc220c9a1619716349de3d2013e630ba990a328a8bd6c6dbaafa18ed9e8cffadacff04daf8bc59e07e1a5ed0469f19d733f5a0d5cb0c69cdc337179e935d5a3266e7c60d0f20e87ba3d6ac4346af0d4ed541488bad7a287842121b2774cdd66e5bdc03f4e2919943dbd96fb24fa7fca3d4f6a8a2bed34833529cad6192b6fda17cc1fa9e806e91df7ad6cf8fffa0e835818d1cbf2cddfebb3dacab12bbbc0032844a73ef9421ff29d3e7bffe24298184909c0113c4bcfcad6d07a392701c6a4b0f9cb88189041c51764bfd280a5ee507b107995cb66722c71349b0ecb26f87eed592027516c255a8be6e33e0168de7ce1ff3bba1f3b6f3d870e2cbea77165e705e6d30b2ffa43e9f504efaa4a7e327bb53ccbd8c2378a1e359831a8811be29278a897e01265c6f96c09d6cb7ecc3d04cdea89da0da43aadeda283de0ecec51f06cd35fee639b9c1a3553f815003a61bd2f246c4e20e879d5f98e17b859ee31fbff83cb31b2a88ed962e80e76bd657eb59fb7b9f1b1b972d505dfdc7b09325a54f004dc18063c5bbb335bc96bedece552f2f337b97a2b85e7f62c7eb11d13139bec42669ac8aa50e76c7805a6f5acaaa3ec77954bf66756cc31f7a7d0e8184cf82ec8e29bad1ca5940415d638a41b6d3496a6bd3557e62285312dd4aa45fdf95a03f219784ad453f82d521ee5e93cd56a719c79c41bd268e687886865431f2672bf4fefb451da3424e686b7683a74341183b9837e638949bcea8292e1ae666a945b0bb31995149b75f8e2ac8447113e1829370857bfbbf85247a33dba216fd3ef3ca0db0ffd3e7b2bfee35b15bb61105d961f768788f8a3d5936adb3c04193819465f5096c6d34c44954a3936416e7e5004855036a2d894ae02d6299cded49800ba5764c0d8965e691e07b809420cc4be3e1557e678f7a702bf22211ad150822af32b9befb7fb4c07533c8633ec379f59ceba3779b1093f913a001ee3ae7078ae08b8c23bbbe9f605bcd9c3d1c7d33c28fcbe85bb04700507bb5acf274844fab2eabee19c0a85d0a73561bb2043ec8c63bfb226abdebc3aa1681c61c19fea3fcca85be3801133aea1b8fd8a78ea314e0dd4d1f0654349214d61a30b714184b97d179926a541709a46debc15001b57df104d6ac3d8ddefe5fe4b7b8203e593ab6e217fe46667077428d3d04f962c5711beccc93de88edcafa332470d3a4fc1bfa2c83287bc03a264c7c03464a4e60ed20efa86b667690613f24162204468a3c0821eb41387904119db1dff520d6282e98e91f6a48c4bac5564f09bb12a690b591d6488fcf62866cec0d7de271d45a8aa36ffe858cbdb4b6c580de7d6924498b3682b7c2bd827130c6afd614c2612012a90c45a0270c29edc4ca1376f2be35eaa93992f0fd238edf5a5af05adf316bb6e38376f6f90938151a7d7a28b71147ec460ad298ebe1ec963585d9fc30ad3b4a203ae5f38772439edbcb181fca7e2dc2aa58132b3b29322914c8e3ee6959cd9f95560e8625a3b12748819b0c9462a7152fff51c7e77d983faaa9a55cf0c831657516aaf424893f82ba4f22302be7a02617694cd2bd04a4657963bf78e9010c73698cb41048a4258ed1c59ef1a82a047aa933863bcc84e7d7dbb96c3728b73e664209e563de3b2c720c245befd8963ea2fa98e6b494adec809fce707c5cda9dd9d6892d3382f6622d491c80ae03f9cb15df55d995490139819a3dabb74ed29ca7c26e3389a88b7020cb53fe4a4584f509cb40412e7501cc67b21a8822fa0d35a1177b61831517a0070e013513121ba1401d1609e6d631d0d16545072a89412bfb8fcaa2cafe40a3a6997efd999b7eba76f31a79271a7472ecd80bed72fb015992dcb685a8f77ce9ff73b39918c04ff6765e350850767098a1670d6028e72aa45d812dbfdb6fe7bf7aab420a7cf8856fea29b3ebdce8980eaa2d2866e8d8c7936b4ac96b8ec05cbe71e0ec33f0168995b3561726337c0c5090dacc942c3a03d9272bfa92f8cf13b21b05292ac167082f598183028d8b6bf1ba49a2371bebbe75b43ab910310799850928064044f9d38a36fe6c31e2471e86dfae333cef2e46759b3014dbe7ffb2b085f786aa0914133da401b196b159f393996bed09fcceff1258d89b0476e0e34ce9b2a2ff2d31a986060f2458ff07a210790327dd4bfb293eb14e36b78de39dce7dcda5c41930b0ec268235c4d9e933514d9800ddac18cd11be4be765da22781f8d559cf7dabc966722c49e9f55d674cba5bed9735f2f81f4e55abf1a05804ea53ad5dcb3221417ecfe4c67a5145e518492b7a0c332343158e1ae99282e11a8f47e71254e3412dd05df31c602551b011f1219f0ee144dcb478afe897c48bebfe8d755f7e4be36b05d74cdc20d1f8f0fce1bde908c261dcb8a6cfd9b8e00a74e7c42387f2648ea200f66a809310935ee38086669495bb739ac615d646cc78e30c245eeb9fd7b9413f39b4ba86e07dbe4bebc1c94d6b55f4007d08b6655f6c327b522f0b717b69662597c782c5b546713ea92f50878833d6352fb2d23a6a4d288ba495d8bf43eaa489b0f268978a571ce3aea80214481a984314e4e9e9309dce15372481c9853f41c9002a5045b0292a7b481a02e2372fef475388f1a6f67decfae44e35257ce9e6ba87ef611175541a9b2eafcd3a177ff6c2355b2d0566d1c2593e9bf2e585544605ab8e966c563f85f23d4e8b6e76ea4a8d93b749af86546caaeb892a9aca253cd8d2729a983a521f45f252c9300f1288c94bb7a9bfce960835b2b0477fe98b870edcd92e7bbe26a4162320bb9130dff0e9b9adb4f6e8ee0f82541ca8e015e85350aea721e90d032947b84c6be482b3fc10111874c0ca9a2853c061d472f2cfb734e3e1e5113ee44cd69a1c9e5b4cf715d942ae0348a5fa318a8ca032beb1cd6653faec8914af506604e7394d262d082661c5367ae4bc5f63f05ab66b1541b14b4130622ad731499bb6a20292d04c92f8d941e5723e25343fcf81adcca4f0e04b8d49a1fbc327ab5b9e3012b8f43477320a3519e610e24e6ed7104f8e68f4fbad0636c99d40a437af2d927c459614321bd6f1025270668a0a35e6a43432b49ecc2cb993b1ef710fbd3a93688137c3a19a8c4097ba203ee936c9ff10f169e06975422a0b6d074c434af073638080799471bddd8227e71d2be93b0b06848ed47347d4c6a3cb340c42861f994066bcfd2c80042f806d1ba60e1169b161051078f856182d80e4a99f621492e801b9bb83fdb6d41fcb761bfd2402ce12137c56b2b30afb30737307cc190deb863ae4a94b91b63efb3a50db15154215e899f8ac611e24f96d0e49c0dff3527869438591f2b83ec8381df6c2b0dd8968a157ecff387fd4a43e67f588623480efac74ed5345ae280ce85a38c1a61a71b454f577c0d10a9ab9f6007b6abc26106499920207b592be26d7254525e6f4bcd02ea1b81b5783c440113ace3ba46c9a276abc6a28af82d15d219630b491f43a9e4d83021760ba7f3bea3da9671eca182b27b388e7d84d47f5032e0dfee1bd6f2bdb3b763dd672b4328b46df8b75777c0b7b6780fff5fbbf8b9a99246abab4fdb0d7574ce3c49297fd1cbc333b0ea0d7ad7bfeb3a4f6ec39c267608a56467d66fbd6b9fdf8f1d81c03a9ba3b328bc4e15349f4cd97bd2b5a9576cc018c0a64437ab4195f3d240e255714ddef0cc267f4bb061dbd50dd2418686d2100950a332dfe1b4613f9664e88ed4a10c07ba861fa802e02b0d60b04aa2bc9e594634c2cc4221387f1f363103ccc74227f1350a965220fb6bfe148f01ad9bb1155f24f810b399a6c9f2fa41000bc0283ca79675223f7d767b4716d085cb3b9fd0a7da670095023fa4313fb3a31b21ebe1e77c1e1f44f97509914fe1030e5d80cea01795d9b0ebfbd7ee504ae39100b4465db2c9e7f39153aefe759232e6f814872ccb4758e127a5b220b082a194ae51869512a18937123ab74a1c66b628a5223b2e81ead4ec4563cf275cd08d920b6d8994d952c25b889bcc14f920a4de934d5fce367d917b8df28730174802b2fa1e29024c00aa784814c13f2a8fdb88315179192c9b6e61f0e655c26df1a773bb42b949a02eb93454396b4ec25ac47e9e59e9d274b69a817c285fc78756b5600a61bb46d43435628a212816192ab311f5b8500ac1ae02091607392f0d16b52ecec8ea6c37fab4f4f1c2f53abd184ccbdfec871c131a601c83b32795d35fa6cac2afd32c548a3314ce99523e090aa7203ca3badf1583784d31c3a717ca03579880c9bc0a705d51e2594a2ce88fc3899a729bfd3d95a3f0a5b2198193381987581725ba8de3222cdbc4a9913546f78bf6c22f125f7c7cbb12fa1b0df7f3c0390e6d4074a870e4b07ff397e8ae0958fc2a4a4982c8f6438c51158d62521aa8c50f8888d7db0aa6b9867771c109475fa948f9435cb67ad3509c347a0b775dcdbc9f77ae857f2249e49d56d72b8ae78aced2ec84dc9dc8c8d6259b00f74f516a8484f8590995a01215664ab3e59c57d633358457a0bae8fedeb716f9f6e17a908401d90d0b9d41b6cfccc2cc77fa3885fc29c10ef612366f5d8f321a535ae394d590d21aaf442395d008651aaf9cc62ad168a535a6a48628a31191977548d490f3ae47e3e2bf2c51b2d2879e22272190be462ad1988979790d524a039474901dd4404a1a4ce98628e9a1714043946e5853f6fdc3633b7cb043df617b1beb1e9d3fc4da93a03acd7b2fccafa909ffb24206b6254498130c1f36b56a4cdfdf4bc8b51bf961d37bf46d6517bfa659ca63041f1628cf68b0162818f2b56439183b2865da2f412b8a75145f06fb83860618ea678ecf14fe4ce06b6625d85de90923dc4fdf15575ba9a24ba2633735eb1ed1022f655d28e8da26f0819f7b6fe45f3e0a594c42078aa3a7d099064b46c4ac56e6bbb6f4db012ac27c915857e3d6e9047aac38ce5b270411f01ac14c9d252167ae5c66b7781be4cab2072cef11b4cb450ede4a98453fc4c8d41025f615bd382546f9ff8b9d63dbf129cc7dae8e128a372de6445c5204691b36a0d8ac7ed6a7e0323c6caf24f64af7f2fa3a1f4e14f14dd5822e67dfc29c2b6634da5d22e051de737f70d097127479d9e2d5225dcde58f3a83cd06a417e3636eea24c92c5a7af6822cb8b3c4df0831923aee4e55d783a62eedce32c2b7344f32c38b44ab498f70755930d1ec5150aae307601d8b09a86980f5a217b436f1263d53e3276af67c7eaf3f5569b454b2dd7db2f395138b16b79b30fb2fa8c1375919c362f94eccb286c022ef1f6af2875f479595d3bf2bfbef29138f7bd78891344d54638f1d3e127ca0477ec86cd6d99533060d8331b886d686fdaab4f556c3821f4cd78133edcf2fe002527cb0a425e2852ed011f6e0495b2e173633e7672643f78ddf2967729350ea6b6648de9844ce5d3cf5cc952a2dcf89aa8fa25471af86258eed583383714645657865af80c3efa78cfde5a22bc6c9b2de19f72075880718328c8b0a4f065788874ac478fd2f123e01a5e2b7bd511ee85f27d2eeff5ba17ed1b4be105872305848b2e5b3edfd06ef494f7c8556f816f208eeeda6d7d752577bd3a216e59e21e81df54ea7d97629230602a868a28f9e52582b73b04fc79397a087676ff64382a2a715de0c9263fba665e7623e3b073cc45b317f4b68c9c2178500e7409f035ed69a098ba39c08c362bc43f9fc99768190ee9d5d9775a4a244d0b87698208dfb05e4b44387062c5f3731021c6377b1dec8919a054a87d6c2ac2f1e8e0d90283205e9f04dcf35df792744f9990799fe034c7d68f2ae5f008896aab8097c2fd6dd01e01c8dd59a9bdfaf4e87552c8d0b0c85545cf329dfca64624452946cb81e117460e19be7bcd30bb6edbde3e6dfefdc5c20460e9d99576346c9275e52cd137f3919e9b4aa953451030121b0e6c710050361f92f74d9afc956713006493a2cf152b5865ae140cb162e40ca3ff4e4196edef363989442b80ca6094e61f8cccef2852c065e950142a9666e632630be8c671daf0fdb192b15f82c3073f6081cd0aca64e768279becfa17f64309a1434ba7aee8c54adf6ebf930ded6379202def0e25fb86cebdf790e9e39f21e99d0dc0b8105dd961990e1580804b63df2fe4e903c05404b1aea33722a1361944c702077be13722d1f3ae9b9a1991e9921d7eba4aaf5f45a561b7c6dd6871e57620da248cd30ab0f5048ad6d1f6fb80c18a6f9ff0b495d91b5a0119a6c859917c29968619be6990d87fad66b9565a127fb3de5d82c4d591c614ea9e6bca4a40218db85419c86dfa25996b4bfdafe4693105c34208c607b9b855a4ddae686f8fdc532959051f31dbcfd8f1ea5f3db0bebda726c0e95e4b75235de3b61a2a1ee3b1f326a7d35780369858d6f5e63aa933bb211f7b28f0b44b896564c8ce74d83ab0f53f6931b1ca67cd6c243ec552163bed542934e73c3afeec225a363e0d2a3ce5443ac863219a6d29f2589beb1d635e8e47523d1b65822fe853643b3d5f5fcd3c66e059f144416f62affbcfb675d3a559b0f399e3dc3b9bd06350210a950185948ffa6e1d62240474e7c3951813bd13b388f92d3ba6199d00e47494b82257a7d96fb49684f2fca98abf444e70c6b934d2aecc60158a901c5400769435bc6be45b8bad15aa52bc37376b6527a3636c71d7cf6e7d5c52cbb3cc340d08c356a76b447d4271a2fc2c941cce7f9b950fb15f2a7a2175933c6cbd614d8f1c3dab66a72ecf3d5f03fea67dd3892eaddd4b00f981d06454c28fe69abd35d23d93ddf86de0cb56ac7efa302db69d47f72f3af752a72e056f0cf87368ed95693c04b540e5bd5b4718a3d0106426efd6b4d8eb4e03072b1556afe61ef040dd806c4aa4cf844b2834964defb067561ed37d510c301e9fcd7ff252dae6d34849d43b74ef3c5865fd7c5663054398583c1005f59e4b088633c5d42414f3765716f7e793e1c9f1be41df0d1a18cc4cbc4fb5cc2649afb0a4d0f9adca904976d80b5da8984b974f6b4d73ab412c7f9a3f1a4dbeb1ee8f2caf216d7dd40bed41cfd02e9d8bab8b36a9e1ff2ad0d0061509da68dd2bd7581d00b81dcbf1a1203f4f583e39c15abb415c5b281400e270ecf01ff220f3566502746450a6ac06f04c24fda3c2927beac825b2e8f934ac9a30a50fcd2caa02fe44d0a750fb8ab5eaabc32f20c07f600a64dcb0c9f161397a99960454f574efd38dd80e3bc032039464eb321e4032ee8afb1ef78b13f82c192a3f22f96da9088e0272f7f7936634410683d429592e94fd8205bb88e2c7967f5cbc6cea6b139607136e6422de7d4e351e134c2a8a6900890e990289c40007646e5b52fa0c1b79f7c8b0494050ace56de3864823f952a8879ac55e45ec56b3319d89e25200ac9fd8caa502e37fe192ae6dbe66a3b24ff7481953697ef9e1ed1a830a47924bffa9ed91a1aa39aa2923352038a67527ab8b37497ebc30c331aacf79121a36bbb9d7e7b1b94d882264d0baeed78119388c051e85fe438c58d94d1893d4b7ad2e671d706834a3dea546314c6e78f87c3c41d7b0e54b2a01d88c9942224e359e35b7426aab256015e9b736be475673030793d0f913b6eb8e233f856d2525c01ee7ee98b584bc220f292313d530ac910c6494c0ed0ef97bad5df8193aa4292dc14c77080e9203e1aeb7519e99f2e629f7b4abd3ac577e0155aeb71a24295d374381fc4e1678562685e3158d975e131922934b4372ab8c763f8b0210dced5ba901957c00bca7b96b21ad113e81a06565aaaaa00d0d7cb1799948e8783d94cb9c4a28a47cf40ae4e3191e626eccaf591f211056a7e0ab1529879e6a3ffeac4bdc9c04733e29d22d10819036b127a937dd07187ae590838b291a2de579f7b9200cfbb5c496383af0eed4214232519bcb4489c779d906a67d25fc8fc180bd523102c965b1bd982c5691be99188eca9ef576ce7a1c79ff68ac7f2ed127c45d73b28349e7ff1fe1a91358b4db1f7e1f98269c6fb4f2dcba4147c18806a43bd742da4e4498340ef8837b3003ae40bffd776041407e9473b7c49681550b4efff1c0b25ec783ae2aefd4411f691aa2344713be991ca5fd638af2a3b9e5361bef430a17a3fd1a166cd0d3789b46799f27d815b247fe28d6267ce591cc3f6c64c2a6b867d425f1e1e09cab0fe41271c29a6edb7ddaea3ce7a980b4840b68840b6096c527e7a444403ceee018a13282e0125f8b395e52d0fda8e83a327c3e76c38642d4748910c54b82749caaf911669da1fd03ce9d4b54843bce676ef10f758ae853aaa89ecf3f83d61a89ac31fa01148f097f90b0c4f9d7c090731f7f1676554b6f3c2317905d187d9775dd1cf08f46a1eac0f9ca0ccc1f3bc70fb4a4565df5c683cc49bbb8c41e96849a053c3f3dd566f3b9267d15630776cebec652bb9f22063f324308f5724ecedbe8da61a75491b30234c0074ae2835c4317e4fb53b21eceddef3e4d1960e363627cbcbdfaa9cacaa0dd41f2aa945536c213182e9d7c4bebdd6a7c557495c8e2531d4a76b43102a9a7d1b3f0da43c83aac7692ebb013ab4f028f9506c4d71983746dc7b144e72ff3b01cd0f7d80ff68b5e386b6f8aaf02b0c6c6949b54aa35831e4dbb3e94c961683d194e062b9de927cecd70e4190e95260d33574c243d8bb14740d4f9a6d86e5804991f54a73071a3b9650c4050b7870902308496f4ab6c1449c3240d6b43deda0613e578e7044845b0a127d166941407b1810d1212cdaccc8e1816066fba11453dc95858986b028fea8220ec876f968ee26cd1a49f7d07083cd1b73eb368ab5afd5b0c810424640c5cfd6ee6aa3d9229b381bb2ad2f5548cc302adb6a6e9ec8143b6bb6eb4b1e1d5f2d87e23866c78447a4bf54e46548c140c833b55fa2ac476ad37050e41a8fdc529d85692470a7a3911450f31103378e661bfa963d70462f867a0dda7e8a550900c5736b3fa6acd15d12195691140c138bf4a1acf091bc4ca2cd2606d9d6ce499fb11f3740fa68ab4b08332d0222d54e9ffa903e94c998bcc00e47f42161d6ad4f499da42ae2c7b3a02e3275545b1fc5440bef350794ab385a5a5970768430c9f77f346b12e4eeb9c7d028528cbb88f45ba593613a0601f7345eee0317705a8ecc5a8869c4260803370fec4782846f0ae59ddc533e6b1345319de016694f015d7af8f5f6fe150227f92525cf66dd0042be8f4f417c574141f29f8644f34b4a9f1a5110b0efc763ddc2d12ae7896b34ca15df0f5b08ae9e482b78354dd096ae428660c5900e40173ae06de7fe44910b3637caf2dd4c6ae8203fff517100fd1f5a9ddd653baccad776448e8d9560c28d54fe29e71808ea0fc789ca54cfff708ba15fa182ccad4b9221151ea82ffd2867ae14f071134471b076a208404e0d7da84565a644c4433de8a91971b510c851699784512ee6029f1587c67ea14e7b9a9142ecdbdb34102fc2b2e2a7b8d5f8b3a8ead7d9ce1e1804646ed9dc3f74069f31027e1bb4aabe398f0f854692ad22b188dd6d066ed3995db6c4de49bdd55745e497b72943ce0b9c2b698957c390e36352e4fc406e9ce703476b67fc05609e1cfeb408ba1c29c27fe71865e715a2f9f030b6efb83467bc2f2d4f5fee67ed066923a053b49a49db39c7bd7571221bb9daf6c1719111e6b29d6dbf26adc22ec4ab3c753dec6fec04fb51c67340744abe930445cdc02ac2d52c5c8f726e7a1a8dc1e7e2c26ebbaa089a69c27f83edc8d656c8136a11adbbb33596c41cc8d44b3be516875784864ba678ee66f307a64b447710a9e855e4d31856d07a75194ed1bbd61cb12fce8f858f70e6faae376f02935f61b78010295d16c7b2a333b1dc5345e14a958e0eeca037b72b4bbb663779b31e572a1486d9c0003ac61f87d78c9bbb558cc25a4bb969871edb521825077d4f7fac299cdd1ea52845034b24705f3e7e6c29fad5459311f2e1c96d7f9452c65629831f60d240a95ec6181a8039420dcdaca2cc20eb2a73653a6b851c0ff3e71300fbbac34c211b3c91f1719b12d95321149326afb4986a483f5252376d21fad220ebcb80e4396d5bedc42ded2dbf74543d30de21bc78bcb9cca3b84158ee7367d44718714249b075c694475bae39d8bf7b31f9f2837a26839aa89487804cfc88e20e30764787c0bc8ddae2ff4de925c53d8719877d8011343b7afca472ffb261ffe6aea42becb18980e88dc33c3683163897a23d4c06dd7a4b0ab1c100dc3782d503b79ab6a7753b6315205f2811665cf5ce8d4c7861b998883ac1a96dd1a204aad250e67e28b29481c39f3cdee1b891de3d3ee1bfa25494e7ab860b43010dfd5bfc5b2c026a344f735a4e88626c94c012b4a59ae9390db0a0d45bd856d911ff7abb11bbba1e4650ca85a39c5165551fe03ecc128676808da75184016d6587e0eba75dfd2a3ee3515348d9620fd73b661301b7b59541ecbd47314e9033fac23915d2fc65a2201149025c3beb84d4e8b6aa1e2ae43d5fadd208615a0e2fcda69789648070653db3b25c308bd1058e76ae647e483afa3826381502d1a704d9cbcb9086bf74ea7426292d94d2994fa2a85a4b67453301cb2c58e29b7d4801179f96d28eaa6068517cb45d7d9d0749912590437285817047e088014ba20920b186de94c055c800e1059aa396e9462b2097b68cbc5131abd0a1588f6c9e8746f3fad4a198d0dc1b46c69155bc0fa8f9eea53f74d4096d2d43bc51babc3ec390db534f266862a2cccfa856370899f791ac3b4c5a4bbc180cffe5b898b5480f273904b504ffc9dc3891b6b1f61282f7d0e0b017531d4ca8ef476e2750b18fc8fc7d000cc5c30a85c357ab19836f8ff220ac8d2c83b247a98c836366fcb7a06833b4d0c05762240327b11c6c6d61acda4a4e20ec033fcc0eb1da27da67359a60fbbbabe9f71ae2db0ec58550dbd90763893240515be683b38942d68379167cda408964b33f58fd4ecfc56f0e7e660852bf64bad9dc927bd19340515c13955e8a9792987a732753158837b6c4a0f53ebce52f8aa5f6e7b3654434798600d1b86900cd38db3741d7db1a6ec13982a23d71b06fbe2ee97fa4c4d11955e1a1f3b6b070d803c23c6828cbca7dd05078e563007a378466a01aecf070340bd6250ce4985519c20b5b543d1807b30b523ae609d1a5db644b1f923adfcb057b5a279f4b800810eaa083ce6eb06aa58a4df42cd789ecb452c843c11cfbe804b101e61378444d96b6800a13ad1f7236203a5415504f6f659cf65e61b142d5e10b93ba889e73088d1e2d8e8704ad1c60d2311231ce8ba762694733a75809c61bc43d3e0875fa568906c3ec7d2173d5f2c90d7958f51362ebb9c0d99c4a90019644c1cfeb399b8397a862b93c649bfd022612d2ed78ce152efcecf6e430151d9717f236b550a88c3561b58d46156b54d691083cdb4457577b0823317df92e48126fc3cbaf150176991da2fa8602d467ee27255e3c8026bbd08360481b9b2caaf07038ff8f469e105adc4fb472b569cc0bd264bc48dc2c7aee92ce6eae476a2cb7f4e737fd82657dd3c4f9ad4a4425475e2bca1cce86111dc4548dd782cda243915c65dcb10fd485008a0768792fa702026881697cebd2683eaa1984f4aad8e1bc888b7bd0e13c130df50783b262ce5da20e2ef33316d5029aab72f17e140cb99a9b8dd0107170bf5618c4d4f39b89e89e42f223fb4fdd4521691b454a5322106ca31f21358420e9196dccf56248aa14fdd4c30ef90d1cee213aa29c51440db3c5df3f33502476f1e944f2922bd08c47c4b56fa6bcfb07064fd92e19dde30a3dbf9cf91245da66aeac642eac7a938b957af44aebdc6b5f44448ec9d9179e067a90014a6acd68b92c8af7606345159d8f2a100de691e1166ac79ce4e4176865e8a73af03c7652a56e0a574779c806802f318462325149f082e9084b5283848d67851c4cd2c29a597990b604380e858ee0f5e005ef06643a2ee79454a10346c15661baeb2ea25a063397d9d725ec46775b0f101b7ebb63209935fdc8b1406608c83749005b6200305046df31fda21d354c3eebcd380850c96cbf735766cdb014f9ada0f9883c59d54e7a282b0990be03572522790c01f92b82646762e7a9fd5510be8f542c0a1d0db1e90e97d8895668846d74925ee8baff49077f3bdbb28ab04b56672126610d97687ad54f0045a662473f4b30e9a183b694e2f9d087100556e68fbbc40c7f1237c56dc4d4e2e8ec15eea0ad0644737f4ba1230a1fd79dde6fe8038707e35dda5dbac4443781bc65e57984d5da646b72d6c1955b8cb2d2aabc6f962de69e71446cf6ef70f37ddbc15898874c89b3f6a0e72180d91ab9ebd1a5046a8bf1a8ad5214aef7dbd6dd4b1ae078d9d6e9559137367c74fa681aa41ed41ca881361e3f27760cabba11e2b3953e374b7f75abce950d5248cd30b11bd8d7780db1dd70af0b72419fc75c5d26441939d5ab3a1d9905802e3a33a23c8b59f10d9dce3f30905c52ddd09d27e84b9a748096e8441f8548d4d207f0ab7f8dc0aaefe0eedb4ef66b02b60e2575a16682f8e0d8fc8dd68849da1df0880f06dd1710656b02c46415b65dcf231af6fec62619211e81174bd5b2a51128bf4a419c262b64cfdc852c2a530e09b36069973fb5f4e0a8395b8f3f8bf889cf61003598f4828d923992da9f18c06d8606aebebbe4a284511784be4bf8bba4247d45a1b6b8d3aea41056650f631c94ad0352415501447cdd2daaf56126924e5763f178af8ceab2561c368c503a8f8db4dc6ce76b0f95e6c16c6629b4e444552f89ae88f653a1a0614492dc51fec857846d28b2753249acd37320ad956553be1a18f2a4a78e1f42987567b542870a37deaf610ecb9ef086bc14e88bc9bb2aae03e9c5dc118cc4adabee0df2d0d11bfc6329a7e167580aa5b61d9dd6530f6a995d292dd566eb3e301891362573392436aa11dce3006ada8d2d44eae7190434104850d0e923389fb47d2446746c576d3cb25dd434e1d43cdcb48eb36ef04247d011b2bb8aeb395fecec6d8f004ec010f543ddfab23dd09caf8a387608bfc95684b790e7106e4aadafc047b3470cc7855750fb13cbddb6a45d033e4f24f863c24f902ae9d6b470853ee86e0f3179b72903bb43a514064570f15a4dfcbc88de8a269038cd5e8896cd8fc1dbb77b25f041420317e7e11394c6076ab44eef45ff5ec1c04e9c91247120041cc61b025eceb0a2633b1677374d3c4786d8248ab363d4742af227b3faced8d14aeb7b5e428ede6d5d01fc8eaf8bbbe777b5ee4d1756f9431fb4dc6f4a0057f241c3ed318c1ce685b544cb71c15925ea144486e5b97ac3890bae4ac4a4e9abf301df1719190fa08cc00fbe88aaa0d0811938e5ab770b2e2e7235bfb5048ca8c0caf6aa0c20deb48efb737d85f742937157394f58b55ebcb18e91bd59a61a6be558cc486b90f5a98467369a6dc6c94aa25cafac5acecf0d880811005089fd28ffb151f72da0bc909374b199daa560e01401094f68cd8cdd1752d9180d7e584b29f863e1e591300986e48d2642c67585476016acf0fdae42ba0f2e099e08bba5913694840dd604a7422d990a1c168fcaad2e18bdc2f17f321983f6caaf5818122f1ab167efef90920f3e62f53d7b2692fd2029fcedac0e9fd4fa03505d149e72d8e6d1e8354206dccca5708e781cebe591a5fdfdc9a9284ea3cd78ba32de407f1cc8f6c206b9688880f49327fb80733c4f1f31deb91b72fa70cead0a6470f935fac823c3f4626d4986dca19114c1ad13d1f1256f11dd0ab4d4cbf21f08cf2ab815a2fc80d493bc014718d310ff803426a87448957ceaf19af596d0b1fb0286e0916f6517ea4e4709386a0441e6ca5d7ba632859633b28785712872c0c238319b519b8fde124af975deccb73abd51f03e8a03c7e546522f21a85902afbda9120c31fc15149f8e0615ab8f9a99a1f2dde5758ba90e1da82f19ceeea65500d82a84802a69c812d06105f418ac30bcdbdb9a7c4d8a64b16e84a621c15a196412272f5ab02e87f5dcdd94e40e814ad264d8427f32c321d4df404afabc0cc1586bccae6803d2cb910fc27a6d5e55555d82d055183e4e3c2e18e79182a83240f42380f2eb413bf12c6de19d43ade7d044ecb065d5d3e83ac550dfd08b9385b7f8e2070f6a8c8390d3b0b9d758aa5d53e6a404f85fe83ff3dd259e39440e97cb457c6ccb14528044405a9ed0b30e08dcff73a6df169b2a88753370dd37a94af18713460784e187d88a7521484d0a41995072ab9f5ebfb2b4d99f07721813069e2269e6ec130b8ba0bf54cc6b9fcb169a2d272b95f9bb4719b1ea99ec663dd610920b365ac561c9321b4aa1bfb41b2a7264c05aa65f7b677a49b1bd793f1cb080d78edf5552ede3682a255a61fdca7d08655a2e93a1a9d78911146b085ad4d0dcb82fde9e770dcdde835c8a1281c14390b8699afac17819edd9e10e7b1e43a9b277ac6d6dcc39e453f4c53b76b6ff7563ddd0475edb5ed7b50865e7b3caaf61b193eed2deaa0a50e76ce5a0b4b9391242c97a2e7fc26d5355c1bffb1aeb797f32d4dcc7af978cc2e00c8f172a83ece3ec01f3db336a2efd44182571effb624367ed01275fe117f119f6f8c6dd049b8e08a15a193fedd48e6aea5520433096b9718a7377a56d0e69e5bdd91b8aad2e6d35ea5d07e77e32c70ba6e6f4b647649d7303ba5bb93fa0f5f423ac24fa56ec2780335819865f08744f6da72416c9de74fceb3caf6cdb3387c49ed9273340097d1350606cc356024e2a99ef9f8bdc73efb2ed946ec3fe2f0c3c28279341b85f108d70ecc22ef263aa9670273b05e0a655b64b3d7b3c7a19263bbcc93c0ac90973a956c3bcef21d97885904598cb84a9232b9e11cd1f7c9419033aaeb69156208c69261dc42d35657d9d9e45b3e8f73c3f06e1b13da9d663a6af97f5ba3407d51154dfc14bb2e405a26c3020bb5dfc76e1b9a1b5cc625e0bd12e39ad8945b79a002816790e1f05a2007df6277befbde596524a29652508c008c308b557fd2abea75b7c1dbe065fefcffbf3febc371eff8eaf757d17ba851e757d16b5d61635345b58307fddbfb7de9b9ff65d7c9d1a659d556ea74d77e817750b17e309dd844e2929020bdc0817f473a9080bdac6ccfb6fdeff7db6c5b7f37f36e7b736734a3f9021d0361d9bf8de9cf3db1659e31f61f167fa2cea59c74f31a7f40319427d9a83b1d11fa33959ee9cbd95fc28d5126c20d9d22106500b5c6cbed0966870a192a74aca152f867beb7c9006073370982c1153654d2f2a8bdaf981e585890d5b4de45c3af8457beb9862dba56d97ae4636897cfcb79b343e0669dbc4254d6a7558147515cb6e7110fc039bc34a3687dfc44d32c21f3afe1dd6338d63b92cd78db3adae92d5cd4e7b3e9e3a0faa2e06dd8c45bbbeda9765fdaf7ef96daf8f9fe1ef30fcbe5dfe0ee22aead76c17df425ffb8f3682b60fcbb22c87be2cc310fca0a0f21b2adff6b6a0a10f873848eef0857e875fa5421a797f6828dca78d07853ff4556a28fcf0c1fff8b96bbf5f0c326be6497ba117f578df942a689fc56fbc6e4a15b42ab49ff62e6c887dc252ff469a19be90497bd2fc7e9b648d9fe1f71fb75e637dea2165c5448a2ae823c74a7270c41dfb0782bd205d7a505d1dd30fdf78df7cf838b75e3b8b2f8af9c229cf52efd7da4db96bbdbf7b3e98ba688fc79e7ebf36592ebb553bc85fb16c2e7f5f3ecb133649e712fbfbfb69beabd115aa659590a62e804e7b41d4743168e74ccbb4fc341aedbab0a2f839d3f2d3b0f859aef169244992a7f8412449a3895f967cc4a5897f7bf9236c90f5b25db4176fad6749692f3e4983c8a07a8ae08382eadbae5e697c937f7f342d57ed40e41497fcec65152826faab505140f6f438dd834e7b40b0fab529542cbbc547dcefeafddfde15f313cc7e9db57bd9ae7bf1f7d87af5f3625a96fa4fdb559625076876f9e55fc0c11817f882f2557cfcac44ffe10f7fe093449f8dfcc8ff6c10ed6d34da0f9965108dfcc6cb4f91bbc03d7cd0360ad93d0588d40f3ff6b5deb6ab9f57fb30f3f135a7f7c3abf703abe7e3cf59f66b53d815cb6e6dbc2d58bf59ef6cbb28b65dfdeefd7be8fb5b922ffedf241b77814ba1da0fd55e3477edea357eae187bc937a7591a3f5d60927fa1c9c19872e42b4690ab08b78bb1af186b56cf7fdf7255046cae3ffd985baedac1e54835abb7d48a4fd824bcebe918ebe90013e4c3dc56d4dba163df6e1b6bc3a0d39e8e3d1d77dad381a79f3becb2c1467f8c30c6f77edff79d2b72fddbdf773f0c95cf446d2d252d7d7ec5fae1c939ff08fbe56b6db6f75a9a6b17c57befb5afc94bf1f8626c930b6c9fac0ddb649f705428a8a89a83e2c1b3678c628c87051df62c679b2c0efbf474defa7eb13cc5c7e3583fac9fa28bfb3f7ff5c317eb875f4330c80eb1e62a22b55f7499cab3c84849b45f64299e11d6dc3677a315ecf52b4daae28b5b7cfcdf7f7e9b7fe7efa05b7dffd6b190f2b475fbf789ee9f144cb76fcbcba3fc7ffbdd86f1c4b721f0131e3a2696ebf833eef184a1ba446c43dcae2e06511b62850d7fae37ccfabdf7da5c2f7e6b666cdf922064cbb1fd5aeb8b22bd16bf0d61edb5f6dea7e41d37b7bf83524af7b8c13ff7dedb05ae591d572cfb646dd8268bc33ed5b029e52e6ca7af02a42874f07308058221f845ed6f7f6dfa687c5f85faa0beefdbb9abcb925559fffba747948726dde70a5b07b39873b84f17f637481ffc5cc76a07999250dfd40ef26fda31ee3da9d128e44cb3d65804daf1bd0306480c5f5dc2fba5d46f0b4571044770048982ca71a4fd1038128de669fbfd65481414aa10451b248a61188a24507a049641a339c44f1b5f710bbfbe584107da83a6892e8260bd75b014c7b3f6fb69e1680bc3af4f640b87cc3a8e43b40739f9e1836508fe38d286cca01f4d211e9240e951c93928de2462b5fad165c98d61bc5e4afddfe39a502f07d5d0d050f9b5f233589643436118965f1b1a1aca5f9e4163790a3d7d705441be582d38f4e4388efcc40df1062cf571fca1311443f0e98ba559e367105f71137b397af472d818bfbe684593344f139d2c4591d7a10ffaf3d64551ace0874e9ad03854137a4a86307e55317e7d226e9fdeba58137a919fb72e3e093cf421721ce2381bf936f28748919f297a2954fb9a097e6906f193c67fec38f2558c2f3e39bef8a3188621c86f55a9fffa48716310bf90c42749721c3749063df9d6c49140fb9af8a379e2caa73d451fff3ec7957c041aaf71f1c73fcb1dc44708416e9fe427692b853e0c32cf143de8451bd8efdf172f3d02cbf1f726cdda8f66d0df6e52a53af233d8c9b7d123902612fde567a9bf6696fcbcb74e440a711c79eeef875e932fdacc73839e7f6fdb493e256d09f292e223bd12c78d5ffba0bf952f746ff8e393941ed5380e7c5cf8e48b4f8f82382ee423507a443e0edcfcc48124ec1771e0fd12f4e83e521f37bfd1231a5f712b012271dc7813c3bf1c47861c07eec78d7f9fe4e1e6276e5b6b473e82384208f21b3dda7c8412f408897c21d2dff7b64920af49e92882a52bf21178e8b6bfbfc1df6f4d4b920d58ea368e1b1a127f7cf229116fc052076d2f3e687bf16de2ded43c477ea6e82b6ebd9e29faa67d699e297af9422f96bd1c4fb0043dd16fee34d1ef13bdcd3c6fddf6a479a6e864ed292982edc11f61f74ed147589b593e690a591d69fb0e650396ee8f2fde1fffde1f89785505367e967dfca0bffc1cf98a5b1f1aaaf1f3d683ccf3d2386e243febf1c9f1da1e9a2abe15b7aebfb2dc25ee974bb9fffee552daa7f7bc7cc5ad3b200bd94d26eed7af94ff3efe5b165f4588ff528efb9e7efeb30c1ffc10f73d6ebfc56d5e82766e711fb62258ea97e3f0abb86ff56bf3c4e9a7601da9e3f4f72a7446ea14cce2a89c73e6da7e28a5f7432e5d4fa73f4691ea1402628a12533c85b1d11f23143c18e181ad2ee0a9bd95fc28858da09a448907d81019ef28cfd2da1350a84cb5c9aadb6ebf3ed51e35caf68f8f286162ab77fb2832f00110bbbc82d2adbd7ad26ddd59cb95407c288b7494241e50c286a84fda00d2a2d2b3821c206274503856b6a44c3921a952a6862760bdecb4f764ab9fb92c750d13a5ca587f2e36f207d37b021ea02e3565d6e4e1c101267af0e068333626b5bb789e1001ab6a2030a601eb6cec3da971f11256990d73c47c1c79d343048db21ba4b8fae860dadd9c51874e7b4e6af4d368cc6b5f5ab30d10cddaca12846d92675c61d4ef2b61aaf20401e2032225add35e13389d7a7dc8e54e7b4dd8f43377b7d4828035c581e374a58ce7e76a0b991bb48a34b9836763f9c1f6e9ea0c982f3720e1b1c36238c8a8ecb400c3c80b3b6c32161c524c8f617a0d4b9e5bd107647461e20522bb53c7889e1798ceec29b202103327585ae61a2c24201d8dc5425899f4d869af89943bf469c5ea14487952310ca4a4bfd4977ae54b5dbcf7520c7575b53e656b73cedc36d511c3ca7ded08123c3609ea35e28645032ccaf7c98fd398254e644c3bfc4e2fe49c09b3b307109c3a345cfde918e38c8de4bc260d8d4ec77a8771ee3549f2473cf39fa7fd4a3fbf4829a52372ce5949bc3777efbd7f3b33b6b5bc3a1af6dce905ea087b01eab5a3b59ab83a1524b8bdca04fac12edad15e009fa84c4cbb13d46ba77989abcb4a7a4cfce09c2929c25216199cac2074a07e8895757cd5ebf84a0b91046e5cd163a54c159c34b64e7b4d7674b1d35e93193088f6318fad9bf4bdc59f65fac46cd3d857b74c487142b5aa4c15ab36996073563ffefc75874d1329f81ef35c3651705c093805e2e31dddd11d254130a17f84dddfe74f4185eff1875c85efb168a6d0a4847a470a17a69df8f8535081abf07d0a5408f1632b7593f22761cbc41a406c0ce435eceaac95cdd5af4cb5e9ea6cd5e6285c9dc59590ffcbe14ac01f47ba3aabf95f9d9532add5d5d9df4bd6ea2871531135c9eee88b4156e497a05fe82740773082ce28892285f1c3efbfeaf5fd57b72a96cd55215ec3fc58a61aaf605757ab189eb139353285092459bef8348ec3bfb4ab7114a4aece0a93fc84ca7475f54bb342d5a6da82458b12f67f5f827ef2274077b56f81228511450a412fbe7eb1fc5c3e2551889fbfc57d162d4ac09500860f3eddd1be8509da057025d05ee885780a76292ced44a09d9a16eaeaea6fd34a5d5dfd9a0923e8ac3471f69776fac56b4e80ee62804f7bb19225a020518c2882b86dbabafa348ebbbfb423f95f5db54f57575f344b5c5dfdd0347175f5411377398bdaab96761fcf5d5d7d6d661367398bda91764a57575f05dad2b55f0b1809a51403f5cbfbb3e12225eeb4b724c99223986adcf6cb35b7ef2d89ba617a5750bdab1837971bd341a2181bc9f4d65bc202852149143952ec6cd94203150376668c062b6c6c6042e7f6b7d3de12a88e74b1c42b5e2cd6c7fb6fe52aeeef1b8667ae9fff5e6bcfda35c8cffab5b248a1eb17cd33ffa7a23e66717b7d6ceb4b9a6c2547003ff373e78b55dcdff87b2531fadb246cb527a73ffde911f26b7ee657a1e2fea8cdfc95bc9f4d15da7e25c5aadb504a29a51fe8bdf4deaff752bca130c618639cb3c639e35c73c6f8ed6f8b64a33c09f23533ba229c3061a3b42fd66a474b8260c2e670063e138559bb091f4871820b146c888f9fb039fc266c0e3f8044d5fe5d5aa3d20850798e1de31745d1d6fa514ada4b993211f1be51b7f6d62a4ed34fa16a5dbf844dba5ef64d9cb8eaf651985005d6ed7fc0cc8ef6ac8a74db69af6aabdb47c186b09ad60f906a0e39ddfecd633bed5515e934879f577d5bd75040f9731fc99df692d418cb2432bafea2524eb93bed2109d491e0a159d0690f89193f948612d8be7466e83a8aab6bedb5e9171d4d6bde7c0154c6676744648e9eb3233356c0dad141c2d5699df69030c94744bd5fac94fcf6c61863244d2c4861ba749635ca5aedb8c107f5df721a04f5de558f98b44fdac72b94e7d88b3e88fac6bfb4bd11b6b97d6dc538d3f4be7bef7db5fe8bdf62fdddfa66acf5fd3ead3f8c75a6b48281529494484c516e6c2bbf62216cda2e9bc3a010c6df8c92f6b51f7f8f65de49366e64edbe64e50962048912208008f7a647e7d84fa1af8170ed94e48b7f0e3df86517fa6d9e65f8e2833fc4371982102f3fe847dc1ac72e70a7f17d86ef4204a1f4c1de2f861fca7397162c71933a24d99ded2189ead68aa0188a21b6b9906f9bb3399053241b629f2b468cff33ed56ed606361db53f20296ab1600dbaa6c3fe27630633421eb44669c5e85ecdee2554149754920acae3ebd19040ed33841f45a97725095ede8a1d56546991d76d33156d3f1b7c35809932252d0e481f262b3c3dc46a72c6fb90f75faf6d3cf1c8d9aef1b2ae2f215461debc71a5f3318e329193ad61d638c6f2090f6d4172ed3b259d788f68c0c99f21229aea1881263aca180d26573b4df5a6973b80bae70cb6057df08abd7cfd9a467626252b3abbd291b19f4da3b62d5eb7f509fc4b5e408b6732ca31ceab467c4854ec36c94488df4e73ff1bdce26eec4fefcfafb4f671377427ffeacb359deda7d9c57af7f020ef3106cc74f7b53acbafd151fd8a40d9e98989856d80de9f71f60932ad8dd3fb1d5efa300b3bb6f820d417b4666f40ebd2765d5efbbc0260a2a59260a2c1b260aac279ba3fdd61f49d331105050d9b239da454ba2d8257cafc107790a25fd9bb439eec4f7fb374fe1c697769aefabbb499997d7da5b46acb5d65a2f6d71f9c8497a14d53bed11e1ea17541c25ed11b1d1e9ef4e7b44a0f473f74b75a73d224cbacde6ce5d56c93169aa510eb5dc8dfaf7236ed6868c6f778632ecb437c4ac9f979f14ac5737a2ad5d7f1f9afe7dff540409ea35c2e6aa4d86fe7ddff7f122eae5a1d3de10287da8d3de9022602a45aa60bc95a3ad1745d58be2819bf4f75a6badf510205d6bcdcfcf368447d75fa4b5be75b053003aed45e1e964a7bd2838fd34aa9333cddff72d90fac75904f5fd9b67dc0855aad73fa999bebf0552df9c04f8e8d6dffb9695a7fe6d7359fff93db6b96d56063afa042565850582d496b9f3b83730a54546941b597814c9600e9f315858589822b3cb4e3dff0d4fcf394b123e3d87bb9cc7ad6d4d3d93f17220a8365c4d6366b8f9b2cb73aea634d992d5832ccc2ef39b0ddbfeab5988062ff48b2b45d4d4c9f114a40fd558a01759fa6c71f265469a33b1ce0a4432ea34f9ea648194cf0c5cd0d4a023a4690600b674113343d61524653e946c12c5037e69b321be0e8220b83793d5d1a6fd5442dda4de2a17021396cd59a9e7aca569aae72c77aec64b44d4a0d63983260563c244030f4d0f272111b84106b6468c8531285e99d65d63e303b9cd963729e4586b1836601afa7beca4c4658caf2b28fda5a689fe0017992f5a61ba4cd5d9e9406d9a40c112bb73b6d3dc562655a6e751b3c6e8d05a122787989e0ae3c69b217b8e60ed2093b5c215aff0b0818b131a8870d5b0a3bef89a73667b6225cb101b53b684d0119d9c8962a5dc00b90c5741b1cd593c6e7ac45bd8be8286d134485ec2fa7923f3eae7be1519e14c804ffb09cbb40b77d3de81ff99316428edc06fdabb303c01773f05eab533c1f726d85f801e02e36bffefe32837b1fff209d0dde61c2f658bd97429698f0cabf7e28c75db23b3ead6f655fbcffbfa9adf8942c7f7deabe2fba11e02e36befd919026e8bdaa90bdbabbea764090bd87d9c5e1dbd41a6e6df39dcb0b7921fa55bbd0db96303062d2f602fecf2ef9bb4e77801629241ced48cba029cb19e8376598c0d64f2f7e64abf17f46b642bcb5ee230a5add3de9b256f7a5420831b40157c429df65e30eb449df6dcccc9733356b774633425add5da7b31ce59ebefdbd5da7b31ce59ebefc3694a52840b6e5a91cd556b69b81000d05900a0dfa7fdf440bffc96a930588973a6ca563697999c99aa5b2a9bb3d93462723f53d9105636c46d81efed5484876ebfc8888aa019eb2b13e9c784490f4b5490c5f42be9004b47589c7e1aa369eff683ff994d7ba7c209cbb4db0f9a3294765f0c191fecbe6fc23b15fa7d4be23437f13ded27caddaeda7f2ef55b99fa3629bd3a335bcc45a43729ef387346eceb8b151f3b6ae72b8718c69af2f4f1b2c35b9dfea6ffd1d7f4337d5cad3afd4bb9b5d6da200c43896dce5a6b2dfde060304bf9b53f8c31be263da218638cf199bc2bc6d8661b52c218638c31be58acac7f29a5942a61bc6fb7b2bcf76ab9d80c6551cf3a6e84b0b3a86725e8979335427063b16f53da75ce19677e628cf1eef96b5791ffeefc9ae7bc37ddf4313ff3ebf740b7be01d78bcd98a194d26b86522691521160ddf6bb1ae78c2f364343549aa37fe6d78f73d620b8435ad603270c4e5769bb8dc102d54f101f60e9a124437982e8455878e4506484e5469706bcc2bcbab0d4b05e585e80d8eb824e7b5e73bcc6f0158cafb555d45489a177fcd55d9e4041f15e61f759c1eadf87ab60591dedfaca96d5951d5ff1bae265e4b7aef2b4f5cab41960c5eaea74b54a9e2a66fdb4f5fa770fc6467f8ca8ecade447e9defa19df6bcd6c309b4a573f7338d08cebd597e69043d814192b6233e8a020eacb0e255f72ccf066876fe8b82b0ec652c24e7b54a0d829b7e7ae315d2f943081c226612a57b7df4f4145d87ef1144c65099ef20115ab3b25532f2a3b7a0d2f59181bfd31cada5bc98f52d7f755fd5a6badb5d65a6badb5d6b5fe7ead7feb4fe35cb3eef5b3f9ab55bf58ffab49becf3ea547df7f5fbd5a4b174de9d107a6cfbf29e52ff10013284cc89f6245fe9d7f2ccaffb9afe7fff2eb7cefcdd876fae2ad24c1eef86aadb5ae60a40842bd72a147423d8b5a537aa4398b7a4677ef4a85e12765a73d2a317a583f6b18d2000860c3dfa163560319c6b60f3dba2f420437083dba6f6dc01c8a8ac0977ae9b8627ea1e8d15549bb50bf5057c87d5d9318b00588ea68bf4f6b0806b8c0a339da4f5abf533aeda75067c0561f8a2b0218b09503ed805ea9b12bca46a5eab4e7829a7ed2b37eff03aea87e7fc4fd123d1798f40a869d94b4c7b5a707dd3ee8d7feeefe091419f8c00aa91deef751a0226ebf3dae38fd1ae5f859b9749a799f102a975cab2ceed8ac4cbd9a6725a182f5fa9a04a16a12041646bd7e8c6ecd24bd825504d04edfaabc75fab58a8a10418dd16b153da22f4204499274cbc1a80811e0a73074fb750b1551c1e811fd2df4887ee5424590f697a808cb6ffd9c46a4b54879df8e5b4d21e81fc0dec7d94fff58da6efff93f20d9a431894ca27dfe12f4b38e52fba2199adbeaece6a5be5a7ebaada765a3e72fbf28db1a22b553a03ac5d2694fca982e0689f4265df0f4e997b827dc247c938a9e5a31297cfa296ed2064f3d3cc57df937893f259f7ec5ba4926118a9b34a4c1d39a054f9fd2003c7dd38873f003f80fdfc10c00e71905e03378215303ced369f0b9a7999cf30670f41d7e344dcecb80bff9a0b901e71d75f80fbf4d0f9c97dbe005f09f89e33c6ebed16b1303cec31571de060278dd5f60f23078fd1ebe97e5a7d727f2f045afb9d3871d00be34791eb813073b1eed3d143dee459357c49d8876bcfd45dc89b6fbc780f32ef8f22d3079228e87237a0c3ec8e41171a7bbe3ddbec4e0833e347930c4c7803b1d659317c49d12b0e38def41e44e458fe34eb61d2f3fee874c1e0703e04e16ec78e017fd1177da3bde7ddc6393d7813b09ed781dbefc40f4183c07dc497f05264f00dc09b7e3e9c7e9b893d18e67c197afc19339ee14b4e391efe137e04e4a3bdef745af01770a77bca1c7e06b262f000f83b7b4cb803b6db0e3e1c73de74eba1daf822f398fbf53dd856f4d1e0cded2ce883b2960c72bdf83870f398fe879b6e70d3daff6421f00ee94b4e3855fc4394f3f2f3f0f3fcffefda237b993ddf16a8fc1bfc9b38fe33c0f9c273e2f7c1ef8bceff73bd51dee0160f26c8f01e739d55d11e795cf0b7a1eed79e3938fe3bc0b9e67c1f36ecfebf015fc887b810566eeeae8df4c135747bf021385aba3dfc1ac44a6cd44ba3afa43268aaba32f6456a6aba35f33abd5d5d12fcdf2ea287d9a69c2d5d127cdda7475f447b36299b7aba31f9a23ae8e3e68a6b83afadbac4f5747ff33ebd6d5d1d7e65f1dfd6c96b83afad83ce1eae85fb3425d1d7d6b562f53858bee809b0a47757f89f2d4ff467b50a40237bcf5f75a6bad73eef9f9be98beafa6ef03c35a55b9d7b4a995fb437067281f94a6b1351c0c80e8b30df6ce625747b7073640c1d3e606922846c2b2a0b11aad82f4d090c3072ccb8d2f64c6a9da654242222f53a7bdb3afae35531f61c5d2d481074d922c323e3920357981394942364603982b1c462c19a2286ab0276342505ea0087dd5c43157aaded4d0c56406396735a431bb82e64d0e505fb0b4d0b0c610ed99fac2440509d49daa9d3ee30576430e8f1e6bbc407961fbaa654169223690060f9e50999165e6298e93922e5a5aa0a123a165a3cc103858b08021e2813a10243319c31c241b63a27cadc0e5030d58e44a4b0d3554b1f0a3020269183a8181e07847a7b0518b939a5ac6a275d5462c098c3f33b8d85cb0618f9e2f294f9ac450a6a2184d34a001c0d8671d289b193c2d2b006a6286862d3588da8cf9e1076c0c99ad359512cd98f3c2d88f2c377ec4008209a0ad3b769e902103c3d2f1614111e10e19706541f386888c253860d8076063e78f0c80f21019c3666071c4b187839e3d48006971da63839628217bf89b315e8e949991c38b3045130e3765718ce85193a5ea88872a65a86a24a9f2e584cece1904dd446d3754db0d93ed266bbbe9cabdb837100d23e9ae751826f1b23a0a162636a6414832476ba41ce21a5beb5a6b18d43d32c37ac307cc1529231f303347099a1c4ac8bebce430563dbf118f5dfe0f48e5cde6b097d551b03010c4887d7993e8ed53a60c5697d314285e3bedc30d133a4e3de6dcb0d3bdae7516303d3bc630d52c32aaac9a9efe0f483a8c54ce9fd585e1e7bbed0cb581e84c80cf8e9ef0d3232490193d5257daec344d1387888e3d737a64cdd8e917bad676a4dcb4bef3850fe02526cb88131c7a6bb29c99b9e1caca101e5758ec7811ed88e1b64e428b38d92a83b246499c2c2f13b680a509634106ae214c1e80a011706723386e14319ce5494acf55cf0b3c5d50bcf4b428a957daf418b98419394355430c1cb1b30df6ae78062f107f7a8726c3a1f639be2652d702ae3a3f6ea07d41b00ed6de603d0bf57ccd0e54d91375d605c317202b547af2286181c12a8a8f589b17b21d707f7bd456df18669a73798503992e73662003d20757a64e0e2e3153a6f654a123c3b7a6aac4b4d4fa490dda4462030e1d1c7c6eccd9e92f6d5ef8093443151bb56b063adbc8d65a7f5a07196d7829d3a38c4a142b5f546a16a0337583860e1e75c204b067ec7c81362333a4b9beb0ac47a01b9c9c3149e2e686ab1c2cec502383d887122f6e4039f3664d131b2e6c50e546922d71ee38097466cbc2eea0097339d7384c719ae23cc581bac591fa9cfef29aaf387ffee8fd790884a13c1589819e8a2c889895176370fcd982c397a2d03822a74e18384c9e90905489c960c6829d38bbac956f2033c44b8d3331b038d5300a4e6d70785303aa8b9e2960ce000914c4853f61c427a526c244a489c81311a8fd4a3a94360f0541518b35212c7d4110b43303bcdd6bc14881b10283a56f1bfcbe9c73ce1fd91edda67c5baf80591d05e14ad82e61a0519ac0adb368b1193f3dfe0b980417a804d19221562789942f367bf61213393c9e66fc89a365f5dd3a358c3734e3515efda47516eae10bd5a74ebf74cc79a2e0d51aab09ede212045d00f1b18a4147ce979deea3278cd7a5ea2a10d34e67e92b2236f1bf272376d01df477c9e00438c0c8f1038609ef4d944d63da2a92e6cc4c8c180eccf0f49cb1d65a77ed052c93f01236c2da347a422b8a144c94310d200b45cd8e2de42362a60590530e1b4d3c3ea811b283c8123874908cb951bcbd2b06411ed4d5d1fd8d5fef85c13d78f6f8e1c3c4a7e96910033f8b4703de0c291963b49290ed34942d7dd8807991a7c8132cd0441d332838540922a317057525091c1e57614c0198aa853baab021e373c64d0ea72b2277ec2c25e93a82634b981e0c7c3cd8a05e72b2c135d66ba146988d3f40ceda449d7962476b979df47ceb6c019bdc1153bb13e766803645c6c08a78b12aa300150b6645d8acf9b2e281101855566b669095b1c004491c3d675a52366870428786945a44975f64522c0e0931b307f39509f707b569ecdca5eab372839fd5b66a81591d05412b6c97e1f7e470d16116ded827834895caefb88b41e106c7320cdfccb13a8a479378e3479380c3a4f5831ebe2f5782b089c21483ea855dd65386236b0f8cb2b03fbbbc842cc8100f645d370fd298ee20087e1ddc3b8698d5d1b1397848c4f04382c79491946059d957631d7294df07a5350e43203a88a87d7830018819132d2b5ba4c85ad0b264cb057988984962a60c981c9ad008a471a225c50e1a195d5655927c29fbc2aabad32bbf0f418c961ada6cb9700550d5d4d90b3779c2804d71314374844c063545c8b474c99044eacb06baa687131d5c6aaee0d009aa809f3b3ab6f0e842b605499134747041dca009739247ce8e40391ab63308d618b33a3a07cf2651c3cf26618369ef1c0543f505ca8c286a986aa07ac2a4e7af27558452006547142b4eae86b8292fd34dc330b3a263e827935c53db2818f3254b8c2f7e6a20b2cb60bcca82f8608384cb2e4f916b6a39e72fdfd86067186547f611b3af2c95870469010b0d139c29661c00a4c95359d7d81b3e5683dbd3991e576677fee0c111480e0c46d46c997166e4d32c9452e0d88f1cbae4e873e50622540b9496113d6dbe98c5e1b78c47d0ad1346bcb4fc709d3562a88bd0d9d3e6c677e3e9ea0f09063134200f2eb86280a1c69f216afc1099ea81e7ea88130d42e4e0c1327646816800206049182534906ce16133c46aca045c3284893182d50307a05d413084207b6f21614442c8b068adb51eea9ac498b09c4bdba48ac71c281cee50598094295f43b0e4f8b913a5064da8268e286aada68ed65be3f24be411c31e301cb6b20861014939003537cedc40e7cb0c5c3a688db1e12687d999244f846831c245cd4e0811a2d574c60b1c32640ed2b3a8d58212324daa6ca189b3cb5260e0a2a5e7c6d48ded83ca16a7335465bca89863c2c2f4c1a1434a10a8fd69203de7accb66c0a84105069319a2d01ac244b123a60a1a2e529e2826afc8f44c7e3a8bf9b10edafea1edf8108faac280c8784303abe92a4cd4559b2831aa1c7a8453565358535b535e53605361536263ce79dc39e72c55337ace787f21d18f2d45c83c85814306838f2a353a1031f850b2668b86992d1eaaf45993c68913355e337a68b1ea724108b6c688d5d081e3ec864003e041962fbe17319849a9d2c272a14b56901b986250620406201e7698be3225a4d6bad43fea90559f255470984225b663690997a9b339605467541483b317655176f4d1a36303eadd2045ab0a1628e28c30e71c43eaea6807c1fde5cf6aae601adc7c9f7eaa5345cbc1332e65c998d3c60e591f395aba28e103872b4a071915ec95c1932b654284bcf0c545460eb3399b8927a34857d78c235ba85249c1c09ceb53feb2531004eb548b8e2c0a66b3c482fc6952c30bc92ee3a0014acb0e272201c890c58351e7c893296039ef9ef30d3329e0171695db047e3bff08827c9b7b1c77ce39e752fffeb48db230b26c4d2d488f0cb3b2176aa6d89093438f3c66a07038f39523086b4dc7ced79b610bb6538a74d520a32486312b1bb6fcc071554489560db32774fc8834ccfe94c1282f58d3234daf8e9818a0ec74952b3c709ea0d9d1ebc223e7506b1d864d369c20a8646cd809639421641589000000056316000020100e874382a1344e145d7d14800b6aac4a62421687a2c1601c065114c3300cc3501003300c8318630c82d6b1330a71b39b86a122aca34b1b10976419fb81259ab1370ce04bca8939b909ec18a09346a04188c0f725dd12c5f5316f3fe55a3630556e1cda3471f31d6639dce0d7220150f6a93abbe8c221440f1b7d50781b353f4a9e8dbd2b6e8c6fba3af113e79aea7dde824ad26a116fa1f390a75f5413ba4c70c5bfa1b215861e0eb5cf9c5569e852baf59f52cc02bb8d6951ad2266aaa99c5225ec4aa5a2e7a7aae5797b917d155d6f0aa06625525e01b4408a023bcaa03d81b0c0342d4fa2d1c055eb6f8ba19b604741e1a87221a1a8c7c0d240e7a323eaa3f53f7ef40a3fc6cd4fd17ea670c1a19509452cbb12811b5ac0f2db1defbc3062c581527f76f26c8c3c24cff0f65c7683df10632eeffbea0045d8cf3d9e152a9d63c1a218995ef935e547962323571f68072ecb645c0be9f8b15d996997f20aa3950fe5311b80c010048280fc442a730893a8ce7c55ad601bf5481f7b664aaef09465be9ec91299c09fe1b4f75e3acff0fda1f6d32691dabb62ab444af48113316e295f750b1c02b98d039d14567216103cd9504506e07355a0cd1168482fb716d358b17d0b2d39357481ecd0c2d9a33ae550dffd5ec468c39e118c94494395837dc9a54665cb823b0bb8d426365c110516cb662e400e25e2739ac5ce40104f08f4f95baddf72b6a162a1bf5184aa08b69d55588d73c521a6df144e1fc2b59a49f34ab0a8cc4468e5bd9188734498eda00df09036f0a45a5d223af2df982a615516d7b16029b85dbd477372002c37b0a0abdd24019494121fc42c8406ef29c08bba69d073008795f0923fcdf14f8125c73685d5c7564139c28a383652d13fda9d294164526069a6e21f152c9b943c7471642ec02b616abb1d117a9328faba9df53a251907821f041eb2ef3835968198f6149e5a85a5aa0635c25004622543bfdb6da006eb15a2f92f175982f2e0bae68ef2eae89280593a902404a4c45139c63c83cf17ce54ab2be24c2c909eaac14fa206a0bbd2088693c17a24f38bda8943a70112a13d697ae6afde825de4dc6b009f0799e7941d86bfd131dda3c634c84b9c0c6e451c67a2b4046543d5ea41d17bece77a8cda9034f39705e39dfd275341ddefc89686aca9289e73797b5a20bc0440ab2fa550365d3ce114521b39bc37b059268583c804270db79905c668f6bedc8e069472a39c772f06941ce5d37fae5a6dac83e99263e8b9e1fa13314788e840dac187969e26f1ae2208431a15b465b947593c9bc42e82db20460f69331cac4954480da65c8535b187c3918198cb1f7607248d00740cc197af7183a83d7566692067bf75e4904941d2c892041353961ea51d8e16e8e72dac1c79199a73935742db1ebbddff6bc61b7911d8cb118e552abb42c28c015fe629d0011ac62a40f55f168601762109fc6df0e7c99e9cc4a5bfee6945be9107ad6403428e97bb2c31687511357e373e896413c1ee2b8a18f3d38ad23a2e30a6698e9c82afdcb19c02b0c705dae9bee80758e0d753509758dd4918bbb67b1937d279570fb5d94f88aa2e7a925d261db1de9d063ae2d6215d6c2d9601e8114b93cd4c2dd343bf2921be4858e160fa35ddbd0c58e006779bcdf2946b6d173722b3fa4d4ea18091f2e061cd1eb1c1e1f2f8191b416cc5c6565b91ef30426c568c7eed9b5af6e0e2d1eb251cbc4dbcec38f727006c81c5fff49458252dfa685b83c5a82016f860a8eb1517ea7a3dd473eec4bc9b862ff40fc85851cc0ecf581eec9886645cb9312ad3abeb1e2a8cb1770822f3ef2c0e53e63debc465f325b022f60ef3b6b15802e7cabca5e6d53f2d96586afe40e12a340ff52c503351ce04a9571bc6b29f3a8ccc78edc88585addc15382aa359774738a531f6ed5b3a0e2467f42c0a9894c81f41203c098c9b0ab3d34e55f29b5b5a6a96dff3e42caee606c9e27161ba888c0ad1644a4cd80af788ea08db29a3f5e2a95090e745bd88c9a7d63122a95616a893e29dfce451d921d172359896c378bc90eb62ed6f1985b235c5a9b3369df289e0bdb4a2afaeab5c179cbc6c191572182cadb83462b252c223af4b992f6ab3755be90793a644db974c470a0d3b7ecc5e25b08ce98273ccc167d9271ef2cb8e23ba8e2fd1a2b73ac0c1552972d702cec28542f4719e833bb2f9891517ee51b8ee2923fed4fef92f238296c8aa5d946a799ecd80f4734b7a412c631512746b777f3f247cc59b1715eba758bd6928a74098314866026a3341b2543ea3973c5012b3c5d65bce480334ba33594b611646af6151334e9ce8774e54c0caef2772f16f8e4f675975532780e436ec560c3bcf80e842b38b4faa8833f87bc61c39b9f6311d30b800e80718404bb7a8ca82c8b122ea03a05216325775c07b8c9e77240f0684fbb2501289cb5cfe859a9716b23e4d5430acfd56914164652e7bfb77daabe33a58c35bba59998140f788604885cd204d2b57bbdb109426dd84f74f211d0a7fb382514d8681e88ea7fe417c6fbd0db31e76c8c5fbbd60745b62b9380f0e580e1898b2f4c1e3711da828c0405d7272fd6ac53710b45cbdb1004a31c8b2c2bb4e27f4234401b4f204a4d806386ba2f608671c3981ce37161cc683160694090d0148acdb30edf32b6464a363a0edee07f7c5eca005e659ebc5402aa4c89403628e7c6e8997c835555f6cead08b1c18940ee9e5a413f4c6b406db230780deb0f345e142b4e9a85aab7f5334eb7d6386e4486471d0ea920278423f35e173b54812f27f729f3955b9a486212805cbc166fcc7c510d925f8c47142e273e559031d93496027451edfeb66d711727e6793cf1aa64c4366cca523c95abb8821d8dd62cc8585a8ba257f9dc10e6426929cdccdbeb2e5450f88a3bef24ef64d80884a07b7b2c9237d3a4aead01c6e1de94c15eee63b70d123ef983d237110d59fb9b9dc74ac85a8471b34bacacd4a5dc6518ef37888829421468015961a7f4184dd6c548c648ab5d54a6330aee710b0f01d09798a9468283d9be942c435f2a3bb5a70a605d248131e58865fd8c620e7c9eacae023b5cef7797628fdb43c8a0b8170f0fb72feaab05eab6a4dae9f3f53f54105901a8873f75dc0b62945f4d167d0209aea6e8508d939b1413e1f96d2ffddd252eea92eaab10504bba39a17c5bb3b4687e1669b66373487aa721a1e21de969e52480942446ae58c087c7b7cd1284c9345c50faa22258ad993e8ca38a71485c5e0b696553f47493b2e424459e3d4bff3db2065215d8eca95dc9ced0f107dc58b3b1388a55793fa4e572d21860fc8520b652466dd4417e9fbd0936c243ad004103b9ef4fa61ce3deae26152f565a3050ac5c489d87a50256568e239d17561997492dfa22215131ed26a47baa27cb7cd853bab3407821da40e93ceb32faad9aea8e9b93bfd9420282785c998856f5b08fdbfb971e842b93847bf3329ba5dad59f5e918666a82b521b50cebacd815d2303950b3500f7e3a93007c3d19a4a6935030453a6c1d7eae8502a60cb2626b6f69cdb0d8f4f4c2541158857076b0362a8561044c40f97b62baf88120e9e9281936a53ea3efc419bb54506eeac309918c8ee7a35d6c13445625e08189e218e7431fdbae70a45826468240463ae524409ca8bfb9edf3a1174e21e2eb75a313b0ebf629a734254b90a4b21c68402fd76d12b040bca835062e57d72d4f9f8f992c74c64495d31591d0891a99d661f0113b4c042c19d9deaca87e691758d5aa41311612e3a9d01f9989fa42d355020dc4c796f01f3a11754edab6568346a3f3dd13b27f01122629b33b77f0af74a06f083b4c0d621fe008373bca2eadf8d2e4879d64714276f6b264f0ce6496b8d5020507d328dbb0b1cc883d8aa175bcfed81f6e320ab1949fef0a5a46123bd0e46f90f2bb05b263034ecb2e49178703cf170a6292052cabb386ad024376c3084a750f4f39ca0296e0fae5a61e72c91e88e04d4580abf7026e2f362ead0b8dc9f248f643d414679e1261348f1827a4abd14604dc7a5e0272abd72d1e70b758457eb79881ee830693849606a5c33543ac4351a152052c0902dac569c4868fc986218f7337c2e5ee283e32a45f1a249647c230159d6e023a14de70653f2910fb430ac4407bcc202952bb754870b052f3ca948bdf25d68d6642e5c01568461db4f6adf87dbc1e1fdaada948df6104f004ceb8065028783a0a4dd0a0e412814090a4523275e64cc3be4ebb23ca5fe393639d42a6e43becb4055542f0c94d01efc7bb4adcdcf0fdf2a9b943ad918440eb4f8a258f98c23627e5da64677885952c676f2ab618fafa20c7d226319b0606104b286204790049bdbb72c05ae68a9df83fa7de6ca6fe4ca421cb4fdaf00b10879448cf318abbdc793180df21ab9bb18c9f9dc7748018f08fc203f272a3fab7b644ad2d7e00ff9243f0290828ba1e21bb4fe84a9d85b86905b4b3ddef0004d06eb4b58e823b45fd31aabf2782c6b7a325c0a15532bcb0f454b610e218d4ece49187780dca1a15e55975d1a12cde9fb5064473188a16cf97eea4749fd8524441d53b097f90d3f2f3f5bbb89ab4e621d519c730d0682037bc3e7330f933b2a5c080f0a111c8350dc2dbaaf2590311e83fe259f79ddc60e008b55c6e8fa0d7e4aad0a9fb321810201c05b7ca0d36c60603c2a9b77c896ba2760dd601fdaf07dca1839aeaf40a8d84709544c1db8dcb5843a7dfe659572a5feba93de123284a97dcb69968724a3a2b50f3cd96129258bec2c12c8c433dbd8ca28283561877a6e7f76061e901485cd2e126b0020d7617cc7a620021c6a5387e0d4bbdfc0d1a9c85d1a47b357544f05c461316e0485455038b02cc23420aec60e4b60235e86e01dfb70438ddac525517027bbd621a560246c19ec7776d9203e3a81b49ab2a86c59c10fa5ae1be833c131c280296e7f5c5efd7f9a00758b84307347a131fdcc72b6a920fa63c7ef3700bc0a15c092e421a8f50f18108805d0127b62e27a384183582ce65c7eb2e758c7865579d1e93a3685f4887a11b10e41f89e580390e8d5a3ef3f2c1857f27435bf8b3668a8c4e7a328aacf218959b508faf74e2b04e5dd4790421d9b5caacc6db381f471c2f2957e226b243f393522090901eb5cf786f2751ab282ecf8b1514ac46809726c7575767aa19e182ea46b5869a46882ef31db22e9afb0a2f786803efa67302ea2c21a11904a9a9f227fef042888c0ceee8efd50b0889298458f30240da27535a531f8b96b4199a2c8cb4e211d51518e1872b3722d3b0bd6a3df75513454fd16308e845479aa25b278e28d1d447f6903b4d765083c0f8c90bcd671df7dda9276340b17d9344bcc6bc97864466d942628951a5c0a8fe2cf5084a332447bf54509383217e63e71739d4be3e966e90d1ded3cd4126fce4bd7ad92bb23b24a3b80ddf506679790c8594a2e6d9940c1681d762235cadbab1d9b8c8ba7c9bf19c467d3078c69e4f23b4bf490f041456400caa31f4bc3abb68935e123a9eab0a06c12373cf99366b4af5149b672f5f7909430f4d5b9369ff616afa0c76cabca39f313a3748d626f5974665e7d824b690fe50c543febd2234ee5b17a5b9422b78636cba3c9819009b0d3046857b90c6ce2f3e26a6e39afba74449922c09922521b924252b4b664c03efad18594985825e4a4828d48377f8923e07caeae8968f12bd66a34165fdea133249a7b47f317d44d0da003ae71bbe2834091f26015029cab32fc13782406020d40b795508dec08935b74db41b1ddabb2a63ac92c39a4d0521c97db034e67c2e0efb085dd6f96103f193ba2f1f88c13c2c93c3bb56701c5823c73ad6c626c300af79d141764f672e431ba370a34b17136b299c77d20eed43a64390de79d8490535198fa7336252cd0e093c2e7dc0709bc206bb18f98579fd83602a086cf5063743f4b094f5a13b71a26845f0009d0fa7ef9a0786e710387884b51bd773b60de1dbc568b816dd14abb85d5058c60058190fa7ee8bc252c81ab0b2d7769c7f963599145add2a6ff42393eb0f04dcc17a832187a9b6b792da5b18e0cc089fd7b620ce99686c54f40875128360d087973178b4b420bed94dd53d5d883e9b73be80b8cb067992612cd7075f152ac45ce3c1697a87ef4b85a8f2ccda3473d2dcc106a7d733ce26ab0642692589150d7218f8cd380990bfc925523d5d84f4532f91b19f74eed9beffb23551886ed684600682340768beeb50367ea6d4d2abeff46841e90e6a7fd39c64e186a011dd2014f34d7014c496fe29d10f1065f7309c4aa585d1e787ad57d2edbbdbc2032d0c068aed7ead03be074e0b5789bc9499bf834c1fc21f275211eb12694ed7292f3cdb69780c66860cf8806168aa1fdd3945f77dacaf2e398da4cb2a359a31bba590537762cfea9791b0ef384276611c5e8132c56835830712e0729583b8c602620a7daed7f13b20a6e6a3d8c2c77347b5bc232e6504182694c7e819250eed0ca366c4e5e46496095089ca4e8960fabbffa058112579b288e9b71c88b4194f1a79f8c24b72c9cb2744cb7dece4e22b034672b7086eee4bed78885186b33ce65c2ba24a098ded368ecb2c3bffb1335b76b2eb20ef35363099599c2a54d5dbe705fe86487503090d595fcfbcdf55cc10d2a07f44f834b620501a0b82b9b6812e3bbd71e74373da393f6e7ff39c7261b684e8785e1dd6f8b304dddd1965b2936ce0f21ef8f16aa654f31705837601ed527f8e8ad1bfc301ec25a5c461f95421b1ca49316565134b7288a68766c96c995c0d656f84c5da2490c59c3909694e658b914d0f5324e7c4c76b313d7d19ce110f684ec74ccfd05221f303c146be5ea2f3300bc83246a04b53728880ad54a0e03865da9791f1b0b2f86f23a3c7668f39e9a6e7782de88d8dba23ffab35bc4e952bda84e9d2cd2b46531cf5e966605068806880c800d100b1411103604785d6f6b57052a56ce1a0a3c141034203448344dec0098588746ccc70a9b961aa8d414a6a9b51419205002343896e7b84bafb780e734207fc71efdd774dba351f2577e09a9c4780af54c7896bb35868317d21b2f1ee9b50a699b3884e75bca1052020efdcbd658a568c446949c5ed1af85e2fc36bdaf1efee8537cb47736f5e62a5be21ba3b4dbb20c866456c5c98d08e60e10f815a6c97d437374bdb2a0ecc0ec44a49327467366f86f0a2a47d3c867ea7783e83f3e22266b2470ec3fce6c5ce57e617a0e8d93da18ff5e34806fab31f189d72b9324df055e455111c6a189458a51cffe445740e1bc93a8daeb93b411727b228a81ca26bb8ee049d66b4c5503838586ba0523d59199df44e50b06a75fffee2e520950c8b8a88842fc1fc60ed8e7e0391763413f090d36316a8a12308ff6342d7510e0c1314646567cb76e8925da9dfc9d5095a2b8c81348324dcc099c1b1e8041a10a9892ebcbf08e35d9e4580b14c92cc51d9993ae79a70e2d82426419c2874d5ad59f3259d69adde7e8567d0d6ab8b29f040edbfecf8de2286ffd2e631602fdf24e5ad05202e2d54dd0ccdac21bd552dc39d6b19383a9889201180d0418607a09add88b480b8c4c9b00998b40ca9b2089b8b92295d98e85212bf5a49fab937156a09ffa3a2621c7964f787e616c2637ebd5de85bba7fc472c0c00dfd84dc2fd84978739bb4d59b503b3a9f807e4ad7441b32cca2832df56314c883ec8e5261bea592f5b6bb9ad453dd96b6c57a4c418d42dd22b83b2c4e7a01a65502a3633a1afaaa71a5f891e74f703ec3ef83d8e1355c1494afe652b590d092a09dd632d9a068fef8ce08f52ef1ec5f502bc2258ba3ea1c4120c3094bc061a1e7405d1d5bff7d564164d11753259d730159e8d28c4901cac3378810f350a8184608d32c258b8047be7bb6029e04b935800ffa9e02b64b33174e62079ec1dbfce13e5e27647dba44bbd41e5ac6e64feb9c07e7d1e7080b0b602134d7dadffe1f1e69e9d73884c2d4d45b331af8b8b34d7e26a9472125b4cdb003aa27a6c59edfe0b480bcfd041b22e22422f0bb10a27ff7706be10673ede7616f763dd633db09463856161d030511d3a552f6a870f8485d7b263bb58d3f419b0225ca4920de27f5ab8a214148a8d2316af2339702dc901e2f8137141054feea5225825dd6c4fc63e90e9d9a5530bb4b8560d876350999555c6f3f79a30ea639fed6265900b1413d6326193a71e9a001f0bcfa70122b740750e08991705aff77b2d31b991793a2e3960fbc6e28de89e7da2f01d9cba9ad161f292ae46baa0065adce878175149a548c78172ede8f740eea3f98425c86640b51aa9694f9c3be10c6da8aa8ba21055702577964faaf10f9e020c230d691883806f69571c2929d93e36d51ce53ce7c7df737013f4e682586b4e3565805fd17d1a8a22d88803bd614ad767c329af07c993af633d4fe4e1fe361be3c727ce86c7101d6ac2355fa554e9b69131c48ec603572c54b789e21a21bbf179a120c0962c9be0bed8e89722a63b0372f1517cea166befd21ea43d0e9f46e361082fd6528afecb2d04064acd4ce43667793ff6a7602e6d2562a8adcf79d25562ea14781da30016900d4f51259ba5ba9472d416c7bdc66baa6d446db31366a7f79932407116a4e5f44cbf395afacc8a2ebec46fb6004df151133cf2e2461da42633384c5c3be5c42f5384215b407d0d26fe690ba83bcb41de81239dd8014a0255be2fe37c28ebf63a3a100edd1d46e4ac47801dcc45cafc2b9b6a1cc0485ed702d97d6c166b614b90983e0c07dead43b27a6e5b7f22c296aff999b53c8de74502d3c148684f5c7499e9f274b4936811acd6bff2c3533105f7082ba400f6ed05328e6ac9296761c16b3720f4b0527d000a06877aa761a8e2ed94463e4932c62e3c7fba1b6ea4f1f6c70f3dc3e1f7212bf3164038254af8aa77883993ed9d47b0c2a1339037e4439b0a8b48ae1885bfcd81e55b57b4904a64cbf8aac2462602d9c47767a5d1900829fd120d8c9437de65a07ba2d678302d41cf3a48d4083ed83b58fe9039530c66f7a74d27a9dedefceb4bc6c208619344929a28b76c89acf4ae4738062b73d54dda966cd90d40da7c81ae0bb7a0856319a1259ae49c21607ba7a0b64c1ac83542c067bc8947494390e2b245812c5542d8a3a1292df1f109b66704bc2405d89beeb15d03afd53029e182316c47f81d20a7960d08a9784009b2e58da08e5c955805db7c601853ff1b837e16f234561dca4b4127f650f0c177f879c3b0a0ca44db2cbc42b2430bef251e46ae59a61ed47778eb6f4add5732ffc8b2cbffe6900e1616549a98c51ac5dadd2f85d80b74bf744032dbc235b974e31ea6a7d5d5c93d083f9056ef33aede9134393c6eb0ea5505492b760971cebc7b65e79068c0ab02f7613f01c66745a6fa65c00c84e808a9086d437daf9bb4bfacee9d38f9f110178d4dab204240bdaa7eafdef170912084494bee789551a5f1a087d0b73f40fec34ed856f7d8f94ecd36f861dc6080d2a898123096d439fff3d4403e37b56937dcef563fe939ba06fc15fe58253a92dd8cda3e157f8259c3b1fe8c1dc599d14950cc56623ec4171779af6e25fbcdcdb7e30184cd444647de69f4c2e3932a5b75ed380abaa8d2454bcf63eb796a83199dfcc8f146e1262f3d925f2fbca73aaedcb437156287734fe217444fd3a1348d182c2d47c5a099bf23851667a25af909904e6a2d8ea8df9f20aaddb71a0e0eff55e062c69b065fd9de898b0f39a20735ecb20e6b4e6ead8b07b08650ca0d8c504d08b73b5b0760c397864d3e76188fe2a6eea2288b27ed02de537e888257bfd9733dfcafb48ba706e06c1165ab2980bf47e0bd1fde44593e67e97bcd8f4a678be573439e4e70b2ad19f060b9a4c4682efc48b1d98ec1a175bb17acbf6b9c8bcbda9a5ef7d442b12affd340c8dcee6114d4701e2bca6ca447a1dea36708dafb52f9f42d7a7e25bbce873432752c046d8c8f98bd4a1178f4958a036c0d09c9f1f1213ccae729db6af74ef9bafc4d030c68952fcd034892a0178dd0d715c45d710dba4c52de1f0dff5f3dd268dd84b2b2a8f4ebca0a7b08efae4ff7a0f4795c199a23cb7f41ab04c603bd49d516b18691533d5aa3fa7c056d24cb7b4e27753be4ee38d7ebc607d8f4ba14ce455cf0bdd7e1a58974a86976e93cb213a92838ea1367ab9076c18220901c8cbfb6174840a0a22638b71bd4bb7377001fe5c01e7670dbf68f7fb530203e9bed1959859b4ab9c1f8d21a3dd7f293f2ad02cd42063f7ad424dfbdc6241750e2b7faa48269ad663a52b5c65352b8f55f01b34b6737b0b8541c96605e795395a27aea8dff3d1bfca0bbee71f061c01e5f637dc3e1001dda6fdd5a2a317adf990b461fd2369e42fd30544b7c04a5674edd5b77b4d4d28fb0f7ffef3a0ed432989e4b2114e59ca49df5f6385bebf6891764b1fc9826524734050ff93e1ac94d5b0ab1c3715129932239f4b40611b544ff717ae190c75607705f3b07d557b17e81bfdc57d8962d21be7c195d7152c31c4deeb0beb9910fee98085f229437b55c95db23ed9dd1a9c68c24eb4f6e565fbc3e1692a18e1e024ec7085ebe6a03e7bbd032da4860d46ff4a73877fd0c0fba6c1aad5fdbe31f05163e2ba873a4f5d5f373d2fef0f8cd57b2583860f8380b7129386bfb24d73444e5020f51daaed9f83c05dd473233003ffd6e3072092b8857d597b89443b9317ded3eb2ff94232e0eceabbcaeb92f3bed67a090915ca7019a5db8f6a597a2f03a4d058c2e8b2718c26a2e6e8f889dd6d195f522f8e2ba30f09a29b8f6478d8ac89874f77450f489aeaf5640f787840b3021ff951beeabfd6587eabcf070830e1b753c78e16b4818af3e2b27f134e6a38d9d540a9df166cd18b0bbc439e6f43a1cdf280091ed74f7d6807b9a6f06fd4d2138e7f0573d14e3a15195809793e7a17a9869d6b9139ddc3f1a1a3bcae2df48c4accd6cf144a77d3e7119107160e098cf424403e253144e248b4e1cb6f73a69f57b3b13089fb4a30416830b73d89bb37f35c2d535cabcef49b4955e5cc1a951e60badd7a4208e266c544b68ed114b8cd912e95418616e1079ed54626375b56a2eb2ea6fc6eb97075c4a82b79bb09ba8f55fb5e3584d9a77f21b076d0456c471909a767cf01798b3f074e8b2ef9191d5883b718b1254a266c66b6ac527ae7d139fff41c90630e7c01fca7f0cf53b0c21c3ebf74969ebd9c072a23ed6e4c81798838d6e0877c25558c5da6d66a1013b916b823bcab82ed907f9148d720dd650f19f89a1b37adb613c37c1b7620ecb552c5fd6890e09a9ea3c12aea8e2dd56dadf50a341f442d1b5cde88c322126f06e1fd47c072e0686f9f3d52d40550234bc77b1e326fa5b1a867ef94046c102fd7db6e3834bfc39c12badebe932936f98b739c33143bf65a47662c114d7880653a8dc2ba6e02f6f6d50b511cb407ad7402f92f9c39ae57dfab82a2da3e60902cbf1bccedf1c5baf762bf06220d2829ecea0da901e4f72dc121bbe9daa408f14e810c4920aa066043da7001470529bca842127d578195587a3bc858ae14ebea6c38ef6932e754cec817a19435d115bf22f5282e50fedc87d56cf63476fdea7acb7dec04d32ed3b989dc76787b1fbeefb653659479b5a3e441225b64321bfb22d1c71110265c6b2474fc42233cb2f67f76495b1a334c29318ef03bcd0f3a921915f49d7850c0ca5eba21c883b3e512b6da8604d9899256d3531e027c8b91c1ada4a4ede0a33431aca62f442d14566d095103612b29bbcf9d814570d2f008c4715034addc74e0c06bac242fff1cd90d18866f9d3c7cb57e87fcb36c3de23b0e371c578cc442f3a1b1451bbcdb6c472f7d609f60d07ecca7a163ee192f94530996107e2d9006beb0657567805c33268702be38d05aef98dd29f0e901c424d5e284910e49345382e6855fe2bccab8329ca0a3738f7b8b30737a9300ba419e43d7ad5970ed9a0a9a24bda73a0652149843cea6e917c795cb47569300900b41c75ae84fe85165433041da1821ba571a84b67620ff9251abe6ca62d7c429704799484db2a9f43c7a9c21978291eb8250524568550742f2438f9f772de6eafb70d86357ffeb87f7f40d5454f2e286ba70ba2b21e9c6b659d6cad98f679dd5fa179e1c24f92b5322e3e8aa87fe2cef057febd8bcb2dd5a2c54419083e8668c1001a17ac0eff8496a563088a7192ba980e590a6b68f7a9d78d082736eefd6749e0bfeb870c997a2db0160e139ce3a23ede028808ede7e52e98cb5cd0338762351596be088b2e6b9960e10f1a7e7ee91fb3f0120db6d52cec670156e2469362c8486a6dfb68e9b316a7e69220f7ba2106a0673b166fed0a69b3d721eff3129a404f82b7663491e009f951d36e736f5ef79ffd008d02e81a94d97ed41a02df44c746533b5016da660f68922b5eb186e016c3daabb6d47d872f89a253c28d86279443f0eccf238ee29f5a83c7295a9dc9b259b629936b3464a061b6ef9420727b57c24981f632ca4e2fd5388ffbc2d2f6cfb3c43eb4d1fc2b28a20ac31ac47a4385671308965fcafb8396399faa418ec5883961349e340dcf427b53a042bf669c646601d2be315eff9f48553c46b598dba3c93e7582fbcab22073d148f98a7152e80d7602432916ad64f97dfa5fdc3cfd0b30adcc1de61a39f7d78dc24493405b731772bdf91ad58bb2012c918dc546865d1b94ca75145808ec5fb371d297e5f92ef4cf478a9b8671b50aefe4330b48e6bb8f5500ab610290be2b1016cc73a3373e2ff48c95510b361f9024f13251dc0384e13488373e7b8ce7de7fd483089a9f4fe085c0b4af5fb348a0fce88fc1e87a35108387004672352cdf2650d202c753fd8bb9a73ed0c6f9c04aa9b1f2267611e9d604f8b0f47cd443f8f249fd2066f28447c843eee268613900c3471232e5869b0656f3da73fd9f65a1a23caab29aaa696331f5848b7380994ba52e114842c75bff4d38b5d01d1c28e30f120d09a1431f0469384c0218a150ac9b1cced34cdda380dda2b5348a0dcc21d0d6a9f64c8a619bb93b5c8b2bded75e67932ac52c9cf53dae3f7531f6b4b4379ac0fd55c64dcbeb3255007da5b186b75578e47a89924e9098fd0104e25a26e0939c60629a5e83676044321071887fbb578261ef123ab00841c3d6005d1b0786072dbfd303571d168119fc5f99fc1bb22662f42b514c1d97e3589fe078fd118ac420b4c97c991168da29d1a263419a5a9cbaf84f440dbda029debf5bdebfe325287ee6a4ffb53f82686cac557ed1333ef862fe2752abd79ecb3c67361fd70ef997ac57d313851bd71e2e3f23488b3d5796349d31bc22a2120d4e1ffaae8bd72999e1b0c70a013feb36c50f7063dcac2a98c72a4fcbda0cecfecc7e92189c94e8d1a66a63fad3046635d97910bcdfec9169eaae26e231bcf2506e4e2ee1916512efd272c4df5020564168eb1537a60eb757fc9cacb49a01e248524781dc7ec8ebac1eeb0cb6e4e197afd3d046a80bb2d1045b2f69fe8a9c0943c9a4436903c5d73b53e18527f3fc253a887d08f3c7eaca4e70f2d7682a27b8c055f841b6477a1c564b7fdca6549d5ebe13581d1ed8fe3b6a01078f67dd13aca7b0eab5685c17bf53e7ecb189be00ac7129448e893754c4d0ef410dc3d0ca46820bc0f5d7c86bc7046b9b15582476d3546067a31f58afc364c5f46db80c1dcb68ac57854c99c092ac14217369c2e5a6ce18031168e9ac0b5a35c396f96a6c63e2dd92a0b04f0e0c5821ca5be95c74307bb4867e34c26f6d0500eb4d6f6d4ca44b2b050f9433475cf80f61d8bc6feccfa9d3f2a052fd52feb8bc16a92fa50b62058d4211d41a8562382937efb208b57c88ab9d022952def738496a9a770b0c22f3fc9ed82f3ca48fd0539de87f1678c6ecbc9fd10c56f1d6c18421391ac51552b0fc722a66945704184fdfa27044effbc28ab2985be3d9962365ab06a020706380f2290ef07bf1b1088b8d0a669e81db8f64c04ad896e2e69bfe25a795e28015557475cd3c44b3870107d67d96ac29c4599557c9c1c7f931b299725a4b6daf3eaf6d5230872c32a4e0d8f475b3226902ba1d20f222a04e8f2ab3d388ab7ef5598dea71881ada3b5da526cc1db79800a2793c4f39ae93cc9fecdbfab0d86e5e73f2a19fc0416ab8f21d4341662cfe81dfbec943a864bb3771cc4685fe7e30fab3ef85991f010af70e8b4f14e975dc61d09fe029b3d4b4f0f15325ca48b18a842c592a24bcf8bc73c633b8e7832eba39818b2411a24ac39a520e975a6e78d9320139997da779d1d5a673ae38988a1d50b08278444949bcb702f0477649a75df6201b24588125bbc78e26255a736ed4290cfbd38654f04335a531d55b1653aae77d4d532408cba936353d7701c867dd96ade34628d091d149804c78410cf7f8f756368c460390a78f2df0859bc893a2b02b31ee61abfcd047d49090c684c960f0a345918aa704aa20a37425e68d1a207dbee46494a2ba034c9c704dbde30d1cad1ba471b2c394f5a8e9c9a68c25190a39b24b6e8a43b61168c81362c8d11173c6bdc743c85bc55a34c2306a385f706ea77209426cd5c644465ae76c9941165c1a89dd4034f6790dd213e73e690c7d95b65a3b89398d7d5a69260b21d0460496f203d520689df88eef3cf94d163c57a2c44f81c49d41b67d062c2a1049e859e2cb6f1476cfbb4f36d88d57dceacfb8f09d0210c624ed5f9087de3234b2828159e6ddc47e610c291cdc9aa4b42c53a4b69df8be46384239bc937770ef52c4b05421b1c939db25d0d21424ad61c3d6dc42e2f7cc05bb1e844518c50073a15be1943feebc9f7cf1f8b9a6bbf8c4321b9f55d21bcb0193e78d7e502bfe5d46b4f898778b7ad7708403568eb62355530fa00500e065329a0b01ebf25769b5ff99d6a0c57b00716156c0d62ce13c4b861e5fd52f20020d975bfd0f8e74020c0d720743a3da17185db3f3d98cac08bf83e7cf1f4d35e8ab698aff4c8724731bc063eb096a2ead42f3b62eff3ce454a5df01b55deb952f0bb9af48f7d08c363a1774d866b998c0c01f3a348ff7078a919308f041b1d007fdd5574c6d4e30209b319ebbb37c651f27c9612fa88af380765c682877e81bd215a263a96f235e93a288c687360fb5ac7aa1aabe043af16a838795f4dd1c974c01568cfcd91b33d309e99249d32b87166a047dd77272e9011362afbfd41013bf601870b81fad091d92954b96032f69d6b58c65cc4a1562298994385e1c2f31e4c829bab4ba175405302e7d0c6fff613fd55a5d889437d618bb084771e57a0c4c1b8f4e45676aee629dcef90c933955c0c78faad310435a5f992645f16b90cc48ea1b5c3a924ac18a01ecc1d2d3bb4fcf24fe3080c960787806b29b9341ebf3cb89c6599d61c884f4a6206bbb148a884c103a99320fa2f93b9cd2c20e0477c15af2627c0b8735bd08b0aa6e31fd7f0cd57f196b50711c75c0a170b2b0b5271241558beabf9d92ed05169151ea543215c8e468826601b32e6c7ad7daa34014506a2c078ae43ca1706ff943857a87855cb542d165d3c13978d0a5eb3ee42346682aaae7b757dd2869de980d3f717350a0be3e2490babbbfad53e367a05af7b49fe6d6dcbfaae0e9d28bb1645d8a1eb1708c82c5157bc5f3aecd0f197d2ddef052f1063ce5bad643b5ced4460b9bbeeb71a8a87853a79e1867e201f222931a27efa5310040532dbad5a2769767740653b1a90908a02ab2f08d2cc5d81fc00cf19213d6141f725f26c26967f720da91f79d14149a65c0ef579fd7e531c1e8d7278285ea5d5f4ef670370455bc7df681499eb7f9f0e1c96348c69d8c58b2dead1eec2bbe6692ab7ebf9afb291824842b84c7112d95b532e96f67c192cd300415121f45c75e4fb7685fca604dc85d551bdb440856472cabda1371d775f61b1d760cbbb42a372e4daac6f212e2c0b9eeacdac3dd1d0b0ea3b5b10868cf2d543a6bc30fb7614e12cb2e0f51983aee600e8d2aa0a032df875782beac5fb134c4369ce807aa68e6e640f8e0c3e1f93d5fce5b169829317bd632763fae7e475db9b700e717070bdd62d79a9dd3a1d80397b0fd24f54aada4294be6e5b6db86f1d44b04e2d55cdde67dfc29d3b198bd70327f0b7f8d0bd28b3f4df3a00da5d7aa3505dd02dccabb7c997b2b2131d8a5317a47dd1591cf2d368e09df6ac464b7014f68b682d4edcbce5b8e740c4a59536dfdbb725c9e9ab8af4675539d7c3fd311924b1674fb322d91bb1677f0118f864bd287ef036f558e09bf75832737d010f14b401fc2ae11a6de169215444e9816b82ad6c1f58201ae9d798c5189e6fb3000e5fceb8b5021b9ed0645b331511f362dd38baa56bdf969e94fe75d285ad5df18199d0606b14c9c220cfee709cb43c8cb91ffba95f3efa62d9e482b9cdac0f21367b581384e44429b7b2a0682dc36aa9ca94600a4007de806d9eb8f179b446f385ebf5b9b32b908ae1292ea1d82fb002205c5ad24c1334e653f5fc9e80d53a2a27f628f057584e2b258b2fb2f0c1b2088fc21655c1072def9ec26543e8dc1637c3ac2dbc4cb505592caf29663c363badf9eb2188006c1addf73ef11b869128f4901f76a5653af0b290a5d0b12226a80909521592d4964a2f97356ca38b03b9ebe6489a7c4afb02aeac3dbfced78d092d614a2fce6f3d6f2dbec2ac7a75b724a46ec52c0ba5a6accdaebbbad7296be2e03c49a49662ba3252d614ba94a90bb87772a3c3ab007ff9c672b2932ba79ab0df0bd21f6633d89caa3640fc34d1c56f3c97151a1cc1964e79620eb701be4fc40578a72fba9a3f2e3cfd67b467fd7156a5d7f1a4b97222d50ac4a48e33ff0be2dc21a65a2a718a96bf5e4455d46ad7372e076f8ef5c0ade181afa668dc552b544511e159cfac8d66a5aa5963e6769b350ef53aa699b5cb17006b981fb98e43bd1cca49ce59f80cfae0dd176b15e32e80a59ada84bccf6ebe94f0270cab7cdf3a117d5c4bbf9aed14e499cc2941be1e4c204d62367448f1b59bbb48e158defc41dca02b4ab1845d287eb5b31502d5f6410a7e355ceae091b5d04b5644d68a1a346c31b11c0b555d44a29444fc9bd008e470816709a901c7fa5bd50af3895f2d2f43310daf888ab65321e1ea82d83783c874dd231630298f6caa4948608bedec0fe46f36512aa8595eaa72e06ea3af66963ffeaad3519d1a40fcf3d57c9bacf77b231592cb8d1850b8dd886310a3321f6adb49e43140c52a4d91da457831af06c3bd9d34d964f872dae270fa88df8bea836c2a4b404343e7388bbaf23f583883e1ccc44fbc63287763de88c2f649c657108e53f9f465b2214f441aec1e8e5175515af6659d3250e40d30c53abd81c6dfcf07e7999e020c48f076b4dcceaf518d8a951a02cb9db4448729c13a8790943bbbca80afa359e9423a772be36543732e5e232123fff78b62e95823304a4fd2b81979b6aed143dec22efe1d72d1a0292aa814ce1676b533bc6c89f9efc03dd5ce4eee1e477703b0723302df43fa6ee4fe09b655973cde203fb05de5324709ee98a3eb5172dad324e6fa3e8e5d1ef3e21d0354b80a375eeecd362a5d4c8eaba314df6fdc2e15e271e11f4dc6e1e8524b3b1782d683b801e455734e757a88c4b4681395dca477779cb00e660c2f33934181f5ef1e34ec70d1baddf0db249d5af68f61b4cc505592d70ee60e5be87f73469537ef7c48a222f89c2792937467df5af1ec2b979f2e05a723fe57fff86b723377e4b0f72990e4a2b879de61173d268a9bf120d2c42573fda0ced86940e622b4dd6ae95f23b435f1bbcf9479ec4ddcec7dfdd0f0fba39da6a5d86e56764c26f82f3b29598b89df5f3b65d81e5a1237b8c659cd850edaa1a0a942bc9b71fb0da7718b9b12119fdb8a080a1ccccbd0faf6e889724447abe8020e6b7b930a20051e207e2ad0832c3ab10e02029684504bb03a767006429cd611c9e8089acb4e1cc2375fa7eb1adf00ad2258350acb69cfe64d1d67605acd3e1105719146c7c44df3ad8e1274af01df2c385d0b6a10ffac6e5ab50653b7b9c94a981a1da06edb0429b3af5e540276ebf600ea368febfd8b6c5d01e5bd71eb004ab7dd7e337c94bc34f80e4c7038906ecb003585b7767d3214692e745b5816174947bdced98bd33670af28ef0b106457cc5df64ce95dd4ede9211b10fab70b2cf52a07ff6502a66a8ad721236505bf5eb89e0e895e6410c4a52a93dd76b3e4af346a325a7b4cb1d90568e3be8353c08d9f03edd5a5099f7a991943aa1c566f37a00fc1413f343b4210a9af2120f90fb71fcbe7d003fa37bfaeab1b999479d233028bde1c5b506c3d17a3e06d11db08aaf411db76254b6afb8638899efe85b66f80b7483fbaccdcd340abf4c5df23712c59fbf9b93140cb9e4d891061f90afc1b8f47e60d6f5cf0051e581f85b79c10ddfdd1b8620d134cb30adfb3e12219ed7f45f917f0ccdd6cbfa066f5e26201ecb503b41b611abc5ecdc91ceeaba1142557ba032516f3b53dd87ba0a38e1b2731c6ba0c3ceeb6bebb720e1963d46e9f6e87c6c4ea0d21b4c0e428b4d8a94569b175b3ef7134fb68150caf6d75ea91c98e161e801484a2061637901045aef041fc89c90e0ac4c6b1d288e06ab17ed2fe64ee2f19cef5820f6b8f8e0e8e9b9399f7449823a925c59d2fd66052e59aeb4133a0fc052dbd3a18b256200f9c63edbc87ad32a387e6cbd4e3e288aa1f972007c8acd78c5c500a7b05755e73c53c787eb566e4f62c95af169485a79ae112980cf82a47a2abec53517524693121c18e2438bc206f8c21ce9da321ebc17025b5ce7e5526dc43b8ca7ca482826eb0f739bd2de0ebdd968258052fd48d5f7ecbd2562626fd62a49879c1546d4a6b7eb0b07c557684f9a4e9f4fe084f9a05dc260182fd43162f242cde9a9f1524a572fc87c402df4c1a458c0fd08c90233bd8fa2b3754354633aaa203ce141435a0b4726d52d48dc40e0a36880b941eb994ae4912c695dd646d323ac79a07fbd7255c91c385acdacdf59f36ebba74165d266805f541ca6f92728e441eeb92de8348271fec637b52d4c42d728edc30a73770df62d38525d5aa7e9ddd5712ab04ce38e597b157be5b5fd6f7de26dcdc2f3d6569eb6fafb4325f1ca627f241b57a2cd9e8b1572877b49d4fcca1cf8542e0819509b0e9635426f57bbc10c92263d0ed1b404ec9e3e14e92575608d08d15f5b4efdc6be0987c4994292ef8a16eed2b53177a7f9ea7903289821842a41ce3daeed0e3ee60932a5a3ca3ccf2f4247dc404c983d7940e39baac20f674573bcff7db86df68740cbe38e5741449b0c6560b6e879121382fc1996821007ad24e026dfb08739202b663a79d59b3670e5dd2c98c1bf4aacd769af815de709f36bf273d7e046a33ca1ddf117421c627dbf4f89f41791353604eb2aa3035c6e45c5c0dc1425e123b7e731014adaa51895ce1a756bfa9b11fb618f9332aa9fd7a836b29de2600a3bd08a7a2e2389d2f656a7a0b7a758fbfc539cd3cec669099a71bd74fb662fa5a1aecdab3c7e769248b1b6d2af16ecf97fdced685265e343a33cd51f3db98d9752f025b1136dc172fce7e55f4ff800144c8c06fba4806ea6b048c50cab27793656029ea8353a441ade8cc4dc09f4ccc513071918b568b3f84526ff27a6c48b10a71b6e5e63e1ec42fc8ae60925689412eb8a3d137a9cb94c62043d9b9762d7dc206d749edd51a38af955c4374d0b317661e64981eaf3d52b731225460bbdf902555e92cca7ced579144a803a712a9dba19283dc2b7658db86754a582967ec32d43ec40e09f9397e26000a1313c37bc98a5f0b8eb3d6e6f5496e192d1b305228e301e999360e88aae9869d8a8dbe2dc8b2a8402cac2e047674214179f833e8fa83b8f0ba75acba89eaff873712445cf08e901f8ff520ee9ceaf9539bdd2ae87afe8ca5d9571530f11e633f37e5d5b41e7dce9920e5d048b9775686aaa391e1f6c35e3f5fd7278a4827d3e14df7b3070cc82f1006676382681e08859417a29a5d6108193715369343e948d88b043aafdbd72731b604d42bd86c27270bdea10c14374ad5373bc811d1ff16c93e54b29a7b25ad1ac0f193e403d57496cd1f68487d0ed0834da3595019929300257036df61bc9dd5483cc828d35c6140a68107f5ae1392e40d6f7d2a36050806c0af0708fd8f66069426a5723309dce6d47a851f075a6d8694390c4735427e20e2081e8ebbccdfa102581ce1df475d3cb57272e39dfa08965a362d0c1d3228c3ea352105037c7e34f5321a709685bd7d10a8e6397d276010a7c51b70e63e7df7f3493d9aeb131aa600e81642885dba397de78c80911da70b5abe2b4afc0c48fbca1870ded7c3d620ec898d1f5c025d84d348f9eca18c8ce197fd6b3ab9a4c059c498a3a8acbbdd264918cb2970c64838bdc58bee6e5a9801469ff30fa893a2a303e445483340134ee189484932c1da5f8934c157c0c5a2a59e322bb6f5fbbd9bca6e9a384b23d0ddc126f1eeec8df8936f99422936cf9b73a966dc05b666252c6e2bf04416b5a19179290517832b0ac8c70168b1ea40e7d50fe1a8a20061fb3545155dc691932fd81e85f25312c48851bfd8b23058d4c9d4e30e6b4f9e5118686728ae9125eb28c101d961e1913a027618e5045f80d2eaba03e775aa8b3bb4102e896c7784c25eab37adf3d9622097e211ec88db40b96d42d6c743e582b68192460a635d6abb9f095e8c0006abec36b8edf3c0d7ee49600874667ad6d642f50854625060d085a0e26b9e068c425176a1f32dd077c486165c14c56248abf138258830b9d0175031640526d1cdc6902cbab6d50953fed82a2d543cf4a888062c5a2801d94a979ca845228526227207b78184f524224d7964e9179ad282f071f1f6a7ddc8fc4f000ca464fd29b9ce99eba89d5aec3c930bf1308f34ce3391fa10982185dc7e8103a2d4d128d19cb4759e29cd2ce9e544d29e1746f1716496cf87e199437b7d2a8cc1d7784a1d4f1ec37cc7c63d01f0428e3535440cbd173b9f03d9299f2fca300140a85bcdb342e588275c11fe630824deed04e4a46307f5d5e62923d1568ae6480a429581b0a691408c304c8b44096c3a58ea5213ee5760dbe9a6f7b0916828d8481bde41696b7f89da450520c25c53f097907d6065a02c34320be561bf1a949b59f0b07065e5a2a2f042e092a69024f6364b95b41719b7de85866eb35d48269b35a8195b746a01453e24913d9b5d225739c364c30b162c0ab9978faa37034100c7b0a7d6e60f1de9d7dd3ecd2d27ba79b30eff998e4216c99f8d5751a63733267928c5fce19b336dc49ea5a0effcf0d87c6e658da47b01e921081754d366ad362f13c9297ac25ee0c3a16c3e61ca0eea7217f1361d36db995af9737897b0a85e267c7a608aad1ddec63e05991548de507583014641e6ab55f4f4afbd6e7de25f89f9febc0b352394142dfeb9dddc8d47a5937631c2b484e7727b5c039333bf25958541c2f082876275e06abc51c5a7ae5f59d98fa909607675bd28ea7491c251bf87834e417fabb46a3f32a31d1c3ae3739bf17c6cf3e73d9665840a938cab9edc006251c229355b3e1c82160b9e52aad61e1b31f4e116b874ad2e331d9f173a6a67e7acfd2e8be6d2c2c7d0dc2fffab5e9b4f8561ac9d6c89fba053d60aeb7f6b8ce2563f168d5bdd641d1d84176dacecd6d54d090fea42d71bb5390cdcf02a87e92cb011b76be4ec181a30f38642e3d23d5b6ad35b8bb7d17f839f2144fa996a634fe0587fda2954e18ebfa7c20d218dafc9ea6623bd619619e1c022ea52a828a37f92e796d4120ffde2a878060b33c52fbf98e5d079d6bf38c5d8a80619ff5ead7f078ac020069282dfd62a662236c8bf973a6345bba35e250a3de0de87fa8a9ff6f96cdf2587d216b4a29497ad6e76956f5af31f570094d5771536ec9a7cba4cad421cd966c267432129d40521bf83aa6f2d3cac990aea57d0775d03574feeea0ffd3ee9836157d92232ed46c8930ae03b6180e2bcb96ebbf689508a65509ea3ee5136d6667e0816cb23ffc971c6b262063d1748dadff9a5ce96ace3b626da3e2fadc7c30ee0df85951d12e30a5eef3a729670e73a00ee68b2dc82391ad6f2fbfd59e3c6e7c5eee3d4a046af25f2f67fa92a81f10f7f28574e3560d1ad9edc3f5e23291a1f491c58c16dbaef774add10f1cdc28e61d8f72b0715c1788ba3bf7e695f0cc9fee616c08081e8af21e21c76ec6870f944515ad7e707ec1023d3779a53ba2f9ace0a484407f926d6c80a01206f62f587435b582734db24c91019297b5662be90a77a515616b45083bfb75f2ac1e1c4afb5be83a8223b0cc6413aec82c4c24fd76f5296a0f1c52f1d0d9528eb2a2bb5921667e7f0d26051eb1fc688550474b4a577ebec2199e3ca5ff3aaa2c59212b1fe40861ee309fb40033af121cc0a0054ab7ab384e58666ea129ef9c5b826cfc7e63a3343145451572bd2eb01f9b220c84e32b87f67e5b39bdcebb9b32dbff27bfeb65b45da7c78fc545f8d16a2c135a23899302139921e0fbb32af575b40b16c17814e41e0e2f9765b84ebeb43f00c6a024aa6155a5d308e59261d322309f34615b4f9c419157f796e2f7f81798299443e3043f04f35bcd1830e0711082850c6df58e84f009cd0f5198dfd7050411064fc7e88dd92dc05d7df5474313a37c90aab3db553451ff4a391b8f7965bca94b69472072106be050f0509d8618041dd6d2f6ac86da7ab02412c650693eec594ef061764540ac818255cb0601c11daa10256a8d6d0185383852e424c9c09e9eea1db40b141b939e9567200c3d592ec76395620f13a8648fd6072e34c931b48bc0efbdd0afeffffc73590ee1fd8f83e6c293d62b3237cffcf187431672b352b4ab87bafdfff1972c8d5802f65b85490c23ad24545c2cba8e02c94061842bb22355504a40cf82167aa29107c77fffb495c9cbe06f6281aea73335495c7bbd23261660b8b3ad80086694c94275859764a43390c79b9f1e5e9e6847406bc3275a5ea4ad695dc15dd95dd15b0ffcfd98af6495dddc6193767af328d2b9b631a7eaa20187eae8209efe780cbdc64e08c0c2f589414e9d8818407a1b2c2181c5833ac92d8c0a0cb879be4e5737c9045e9a2763a2f403263f056af5634b73222fff1952f8481ef0fbdff8b391765551e2fa7db9528022b714348c7dddd75dc5de64404284010241c2ed214d1c2858c6c04342653aa00a101e48a7a5733826c1dc162a68b130e4176e8f961e528cc764215253825de10d8cd87262c53c29630c101e7056e3133c48b56141a39a8c17112a60a87992b4d37c6e8d03ad265055f469eb4a0d2ca32acb4ed45dd2aedffc37bef1025f6cab0843affaff359630cfff745877213431226413067793cecfed9ecb115860a109b9a265d3e82384db9dd7c3c9d30c3e0b56f315252e5f1906c18e946ced6e0358eafedf2ff6fc4fdffff9ffcff2ccbaaa29316f167c0000cc9638518762da400460372c3069cb2d470d2edc89cb991a2240527a5e9ce64ce844b0ef2f3480f2371f8fff7f10d4f207853021862ca20595112d60209d742650b10ae274780a421e13a19a38c1b373288b189316afc81601852e5f16a4ab844184ea709f35db0d80663398360db93ea010676f17ff767f85095b3ee667346029680193fe30b4074f359004b92ac1a58967c0111f3821a1c5568c0b01506cc0ab8501b2101caa4407b4afad123aac6b54346022d0a0c2f341c3fd678f8f46a8c0e1a6874523684d95aa3e445e80611a325173d987c77af296468a92051a901c1ee0e04fb75f7fc39d76a35a51aaed6545b528baa51d5aefebafbe3bb75d76c808444d565d5055583d5892c203ad008d1d5b6e076300cec3946aaee978750087f36fe68230cf1952946b0a080030ccc86251f09a8e1691786480c5331945163c1901d5082438b054c7cdc908a9a61c8d714a91835303c699a6c3b8c7c21223a62238c8d861a929c992f0e03cd32206b06ad49951e39f8c8f908d34186173190669c6025c314bb254c6b488b480b49ab26a3871736e430c5e9ab8a1710d0d8f2c103102267a606b0801a38647a4f378ed4d36d4f6a06a2fdaf7bf143ef72ab3e292842c0a9303638553e786898aa0859fa4224a44207a47aa872e6cc8f2515badc90f521ffdb6a48834b32b5f4a587ca10b0335652c2d4e092a4e365f9823bb2c213a4266ab46260e05f5e402ab221c3960a6a72dcdd3dd47ae1a33861785f49c6a07bce58545857585a77f6ce8549cebbbb560ee3d723cce3fee5610cf17d7f1da17bef0ec484ea8c8ba92d3778299125053444b26cc470c128ccf9892db6e5b954c5c2f0bf06fe2fca57ce88fab1430d4d0c235a6e5c19545a17181862d68c31b383c1ebef39ebb8551e4f0713d6f144ea809ae998d23b2ee0c8f8d22c28803f727ad8687842f585898a8b254e2d80f2f2e20229ece63c65abf2785e5f25a6840d1195a001c93bc0d7ddb1f7f8ffa71e0872ed28be38c4d84231428a5e8cce3d0c04418c75d0aa3cde96d757091dc286884aec807489984089c80e4c70317238d02c153065f9c022a389ca9a1674c092e88628bc4ee4861cb2eba5cb1f972b2e5a5cb6b87871f9c20087165d901c8e356ab06c3c506e2c61018d0e32eca0e2f960ada0c88d314b5560351c0121022a3ec808c142f4040b16282a843f478ea70ea0546083b241668c159226b02bebc2d34d0a212f2dd486a2bec6c0b013052c92e4a901379833287542d4c5ffa328c7bd92e6c8275544743170a3cc2dcb11965516189064a99183099114064cc877cf81c0ce89cc131b98bc0023f12a9881e4056d05435f6ed848bc95fd5f17999cae243a6f35468d178d1c023336d0848472418446a0430d627266b2a8d838b2b2f556bcfec17bddddfde28bab28880f4b1696dc17b4b02a048ea00b3f712c5f8b3e96c7b5ebd797ee9ae856dd798d53525de5791d8e217850795e7f4df2afbd70859829534d62539724f17202c0c16b8b3beb22547b677b528a286d2c7a9446f367234a94f567b29e2bc4f3db99b4f5abf8bed5ae3df7d2da2d30806d9fed4981614a0a0cb42dc548d376c0f6a41409db528a68ed7053ac690dfd6123703c413548691ed168c2bfd1c42d33287734306fca59e5d16052ec4300cdb3ee0b82200882a0be17ac64adfa896c4c839a96d53cbfdd6253fd65ad200856f3fc8f55aae45469bb6b0f74892ae2a6c4a6fa05f2c4e46f5a9e256caabf5a7e1bd710557f314dffbf3aa8ddbbae6f7d30669e7583daabe8fab2a8a3a9826a505fd757839a6ab052aa9dd2c613942ccf9dfd999e17d36148290545f35c218261386a1b1d2bc3b07c1d668f6a8fa56f1cd5c2faf6a0ba3bd57e1ed7b1d283bad3c2cbda63f93b975e954755c2c6257c57db94df16cbb3839383edafc32b86a5ebbfd70c55607d31d6deffe3fb8d35e7c9eb02533246f1a6a6c07777771c6fa293e5f645f928596bce27447d70029593e22542b2a29413b02461614714ac2bb817ba6a0d2b279d170b4b8c6c51a221ba2019a32448101ecc541a5f2faafc98914256142c9e092db6da05930503060b4c6e4e5c541a1608cf4ad59816b0a89aac7af89cbfa6beaabeb2be725fbaafdd17987720de1745f781bf0c8237c2b0d1103622c24648da0cf8e121bb9c2d4ee16ae7556dc9ba5bbcc8a2e484e147842b089249c2646c641679f07b92fb8fc912af986348a630ce9e06b06e5e464460a2b4aa941e308cb102254b0614212b70d2418d22ab2b607070c2c970e149d251d5932b5d40722b7cba1470691203869432355e90f01aa38a5b19394055f5052703971c65c80ae503480bdbb9df7b6f8893f04fe960773146935579bc9c6e67020d989089309a779024aae0197509fb40d65aa16a504a98bcd74225598952066ac80d12eec40909578d3560c43421e175084aeb7ac15ee4478dd515345f5e3466b0a0c2c0bd30a307150a4e543144dd108940d908b98bff5fceb85be5f1989e705033dc94c655edf8e48c6f7db11d33dd30d2b2beab3b8435844584858455c352c2c2bdcfffcbb0da39ae430fdaf0a0a8a0aebc125fa60840932a538e1413afed9eb49d0c6d97723203b4a12e288de8896cd5850e89ca0c2849496c3092e420f14484e0a8dcd0a2215742b74bf2d6c753e5f1a04cf898fa1998afbbe725329d8b1960e7c2141c6c88496a41155a9a410042a06489e129cd0a495330f89ac18bedca172341d6ac5921484e061fb41976e4a0d243cf87eff24107bcb137e7875103ff7f9ff399aa3f93f567726774336d71d7e29b26ead3d060bba81a2235486a6a6a94d4e05cf8de45adf2784ab80f5c34d5a1b75337e47b4936293674b6aaaaa90d18b3248ad41219ac76a8f1514426c9982eaa231937323bd0182768b978a2e6c71119693e301406a456038896156cd553a5719d8688491358557f349359dd3e24985e008239cbe3e18c69ce36cbe3fdffff93ff2fbe051d04c71ceea4e3207105c19c0ee77618fb0bd9150f3cd18244470f5f392f61485aa0c1a4f3120308ad2a51295d3949cafeffffff65e4ff7b0ec34ff64664ff81042914c0e068b2848608cd12189eb18712212f284b32bebec9cb44d39793efa423ea041a938d8623126fc30e27b7262c980175c6a90bc729ecefbdffffb90b0e41104781297d57c644033003182b56a07e50d9d1030b7a7ae820b2440a4dc80e6ae89582236ca43be4d238f13128581f104c8384d3d4701aa5d9970f78d8a0e8bc0c81052821274a345cc00093828b91814d1398149ad21a1c74881f64e0e73c0582a36efceedd22b485b6c508c458acff7f7540359e7b7871084ff8e05145b30a622606123fd50218424dae6831a36b4115310b8982252d575a5396e8b0f515b9c588f10258558ec49bb191a302008e9c6e90be0f52f0063861273752564ce13bf12f28e7e14be38469d72d6f71775772a324bc5507480dab0af33dfdc2214d0e19239b36097c07417b648fa2ec110fa19741cc068c72117fdea535c4a5763c41adb54716a9daa86a35b523a50de1324539c21d4dbc89a2eeb8c6f8f1e3c72f62fc415b2a9a2ab0a621b54774848eb63fd3e7051fc231cca1b6fa67268bba65a6a8cf0b6421d654a73ce98be5f94136246ad7a158fedeb164ad1f6a5b62d35a23abf1536d620ab535a223ac0e2deeb768adbddae2306e3fb5792230bd51a63f0158c5e6058a90208870811318eb10636c646dfbb462b5fa6a6b64936c15c710428b74444788da92fac4d1a145da56637de2604c92daf342d38aa3c9a2eef78d1f5b6b447b5ed7d3dbda6e774693342dd2fe76f8d6daa3300cc317f5875ae72351c7cc1cd3b12c8e5934ed11ed895d6de35060b627a65b8b3c9b9164050b4018d813d3adc5e7c2a0b2aaa6ec51ac7c6d5362bd6345a0084005ab5f75e735463113b3b66b7b648f7ea4c0d5a21996d41e8116c91e51231925000dee79d813e18120b8e5d5cb41ed58eb0b6aadb92a17d717659374fbdb6116710d71e9c6b359f5aa5cae714e776dd26b1c4d6a1bfde5055288a30937fffdfdfd3dfc770d562f8ac1ca25d3278b13db5019754a29a5bb6a5aa200cbdf5f9e257cb19b9cf7aadb58d7f271dd7a22dad07e6c85b6d7dfa0500d81b5762b34b49dd45e4d416e7f2d4d5b4b11600001c72975aaeb16fdbaa9b9ba4547bc06bd3cab97675385df8c2160f33cab2e8760087a1ee8607ef72fbded9a3e352b57dd1a4fe432c73c318e79e21b93344f3266da9821a8474ae9d5e0088eda831a8ede7628db13d3ad8575a8d2b7ed89e9d6e22c61dfcf4165554db916cb9f7ddf761d802b82eb6fe6aec1cac5a5c31bd1ab21bcd6ca659e2c3c5401ead78ec17bb9aaee7ee5a23d7e43412061aa506857ed513e652ae08af425bec4a16a085a734049e0d307b201d540d0dda1b6c0bbb5b5ab762887023514ae1a8807108f9a03e2110a8501a202aae9dc7d35906d36dbaa21325873403c806a0f4465436c795c636cad0e924e924e2da4e3892f75bcaa718d85f6e6abeef855e5718d31e9f73e50cdb39452a8bae4690335e1df16ffbe18eb5a2b5013c5fa04027adaaf417cedd720763544b69802f1d0311a42924de493934c35442549265a9e643501630d9474c338079444473c908d62aac710b205aa8dba02d576d5f835ae1863f3ac98c43a67695ae9636b02f1004aa23d4f4f124cc768bb8e95a74ed2f69ac350db75ceb222dc66795ce7ac9c95b368a8e97802ef9cc1dc664590466e739b8736faac5b42bb9ae1a847138807ed09c71084b2b5b03b93680bc417889dadc513d3ed1170cb994626115409595553e199c1b66bd2c88a20992c8f6bd28834228db66b201e5e03b1db4adae70559fbf5495e5d70c504c403884712c9644432d148a6ed403cbc56b7f6b9c26b5edbe1b9c26b5b3507c42304aa01f1d0b16daf002a18e9016ce0d9cc7a20b4045c5f7d6fc636eb6f3c817568aab8e2768dbd6a9d4b055c79f6b935494d42996a122c1fc525fbab5ea048c70a4543113827acaebfa486c0da6bd7d08ad0225ca044119a3e75c7354ec941e5710d42dd715dbdd0defa369ac66ad5f56bacd66ada3a019b1ac325a9435aba766bafd5b72ef9b052269bab5a59b55e6d6b7a606f48afec8ad722bc66e155edbeaa089c13802084f4dfb63c4bd8567b379a965f4886afef93a53a364ac0eefa54e3d7984555111b43a821b2a6675db2a97e6cb1fe28b6d46aabbf9afa353e7d3c2aef6a3b7dcf5f1c3535c391d2d1668ce9510578d22de67185188ea0eff716f6c11c0ddbd1af2bc26bb0c5ef6afaab226696e72bcad5bd7c67a4fe92032058319a11a83b23d49d4f8bb9888eb8d1150787cc74e7a7d5c6c762ecd54814899a9870bbea4a13752512eb2d63ac6f455e6fb8ed26882aa06d730425e4a73ffcc5b468ab0995a8d66efe68950889bee846b5283cb17956a35d3569aaa03a7fa7b85f7f55c4674db567b3165522b2a45facb4294f4fb73fdacc8af0402f472dfa904594e6d98a59366b11787dfb54511168d6a26b44820977ca759e2a2a7a6a4491a8526dbbce534545601981cfc757ccc0c40858111ca83cee79aaa888f6b8068bcc15b315b34b2b0a8d8a76029881ed1705f3da557b567b34cb98721512e98eeef20a9aa33bd9926c29c7265b4a41a625c35d199349956e5504ce89fa3936d9920c27d3baf4de5ade2c5e3c81aa2db537fab451aee4b7cae361a86bbd555f19ae86b6e21c9bec5655a9a664372c8bdad58639595495454ded0fcd9c5cce96d79bd74806f2c3e2477eb8ca6e4fd7e6d86a08aa432dbb8532a65ab195e146196e531d9a55db5aabb631cf1ca55ae9addeb8d2bd39b66f8b661b255bcad9465333d72a6e2a5ba23df96ad10be7b672b2a50c9a19ca0b344f9cdbcab578d10696a1765eb2a5ec4a51dbb56c89ea13e7f6eb9b93a3b4cf2bb45f9faeb45de3943959395775e7b56c49b6245ba2b96d696e677daea0b9150fae10c350637183394a3936d95228c3c996ae0d003cd4acceeeac97ed3a77b68f8f4fd756e8e363bb6c97edf2284c5f8deef7f1f1112102d575797d355bab0f8ef6ec8df62c2e5990600209ea8eeb5cedce767dce539a437d28bda123cd31553ca566a5d4c76ab18b046b77b43e676e75d6cb8a566744f4d1ac6e48b437e337544374fd37b45f9f1f6dbfb6b6d7f6a6c5fad41bca011090c0891c810320c855577555577560fbac576777f5666aebd555344febb5ab35cf15a235451c62ec3ed7561d09763ee649de982739d33ba48fd7e77fb16e89a68a1ceaef8a79edb36e99b1f0c6b2efb7618bcb365b8bdb13d3ed9f7238a8129ab2aaa63e6a44029a147abb6bbbac6de711ac088b54796c97edcab5fda179928fb3edcad9ab8b29463388e1ab39a5629851a4f830c53bd7df17447b5c07d111a0364b6f83f37341412882426a71b476832245d03702093e8a226885add5b18e1fd81d9a1a781bbc0e6f89f1c5175f8c31f6822ec6175fac6d79b2b0f7c32852785ed6da144695c8d2ac4e6187868682e808acefd0be5777e07e7a47832fe822211fb4b69a7052125278603885b7b39e57101d51b1797a5e9b6aabad796a4b1f105e4dc5f13383680fd52968cfd5763cf169ac3dafa02bea33d4f9e62c9a2a826c34ce096a1c50537dc6721ba7b2b09bb46161290d0aa2236cf4b5a5e76d10faec58e97185d9a706fbdcd9413b1a74c07bc0a7afc7b56fd9016f83abc752031c14d4c18e959e182e0e7c82827676acf4c26c9bd3dbca20282887e64959d820cf2b88f6dc0f31fc6a2c7ee0bd57bf0fb59ff5f287f67cfae7bb5a05a6fae787f654fd437baae9615ad1785b5ff6f90143e013558bd4fba123bcd7de966839d8df8f262829fbf4e8a648dfe93b7da75fe9bbc6a78dfeaa29fda123c698cdc7aaa6b1f1b597df967db53c4bd8ee5339d8df68028bff59f5cae7cdb35611ffcc40a821b0aeaae6ec87f6b8291e213f7d8afbc331d056136ad53e95da88b57a3eb883ea36630860e5609fa368a34f52dfc4f48d7972b0a90a3f49d3467b6308753c63a68dd7d2dbfe3fb4876a1fdae3b5f476f8620d7fe8082aea519fdf1ecd1f518f1aeb3083d862e9fbda1676f60362d6426fb1fc9def0f7e3d96b32d823f74c4cf2886a5ffa8b8fa35be2630073f373ba23200e16333f81f9970a8f40db9e590a119d400002317000028080c054362599e2952c57d14800b4bb4405c46360e47e4b148288a02290a62208661180461108041208ab128640e3b40464e1634f5adf991e93e7f881957c2a1f99962201fe13e82cd8c2b78fdb247ee48b2995377bb899d592a4a061d724d7a6bc1cca3e91670d470356d04c5eec1beff922b6a949f8c20c66d038cc4a287536c687072e851c0af41fa24acfd3ee84bb8babc08588677555a5e717228e1b68a7b2c626327a4df86c5c2708b6e2ef7deefda186ceda396580fa1597ebde95f159b583fa5fe52c4fc0f111ec978cd0e23351758a7d8c7a061d0706f8cf036c2ce827f0ed68522617403dce3742172d5f2955877e9c5aed3fbad9e25a71096e111fcbaaa6cd30cd2d71a4737a92e57eea70e2f346b15aa750c3a8c84227130044d2c728f1140b7e92e1a13966a3b9f2d92d532c010d8c443b2268dbe1a8bb05c1a978b3a42fb5727510b52b8cdd93099f3be17f2d5a27a1e92ff0d792184839f3d2ebd8aa65bf027fbae3b2eb5879ea81f03a75d55809db8f0ba66bf8e7e98b6216726cade59199c84380d667ce3235811b8571f95fad7cb20b92500499d5f7d21d6275d13c59105beb052cf3ac7561ccc6f2ca39794de943b084d313972ec05eb1ca47270958c213e02b5ba5d071a9acbb24dbd86812d083ce123e5340a4de63225ca86580f9719ed80ccab81a94a7a68f15001251a6c4aa69126822acc333c88b119625ee2379c191a8143806df48ee105e57cc96e2a91a3236e423df71312fd72a74a3b008d1bc3bfa96bf2aee1b8276fccd8deef714e7a436f6460acb8aad83422f8e91250179a3ac22868d5c7cf5401bf078ecae448f17795dc6dbc5491c85454fc4605f2a951180b88a6b89cf15c0c099ab20064931a53c4786b46501681d1df6802ac4dd2e9f16fc12b77d9b344653d33118af02c64a0dc2f9182796d8f62367708d52b85cf83b8a8a172b2fe5b3083a46906140548852cbf995ecdbfbc0432c02329c787f3e7528b6968fed2ef70e1f604820495ebd6e2fb82320367220d7b690084c2f64ea39c61140ae47bc9e7141cf3f206e01ea4e656538ff9bbc079e9e4e3ab00ba5d4473a90c92cab62468a451d9c342734cc5cc8b7dfda0d5588e16376a2c32c358f95802a040e73364a8a1556253c7fb1eb851b1c6e7af0daf50a993393245558bc4845399246fcde05a88274345ccf93485aef1698d42a316eff45076ec66d08762a624caed5f9771d6ae66c9b174cd039a77e79c4b13374078fbd5ee54741929fcde39b67c8c808531440aec75067f276ca2d19cfd0c6f85a246e8068a9f7a5ef58b483b3c66a55bf054d66bfec78e91e0bc2847ddff5f0d2807af410cfc5a36470297e2f4b2d0e71262d71fdf8f8bd9e5cff5116b49665cb9019d3cf2f5cf290415b225d549d5e0d8f5f1ec724ba5db06a0ac1864a1fc073bc24428519418dccb6f0cdb2ae283f91deac769a657693702dadb7caa051e492538b0d8c02cc77127c922fed305caaa2535fb14c6a276c9927b357f19e492c10c9a9a9a8ad9899406837f1627e78f1dfdf3d93205321e796761aa5fe273dd52403771a6f0f387d07c3c1999bf8c913aa8a7a6a13997e13b9d9dad24a49505a67559bfc33d4b2a6126f7427d54da7fe050966fc3000f8a6917277e50a8cf0361cae3ae98afde315606255fa912609cf8c33158ccd39cf0a1b6453f8029b6ff0ffa8855d4d2318f0ba4ea1927515852d083cc135cf9a9b18f6e0ec89c8b367fcce013942f76d00540b39f0d5876f9469d9719c49753e566dbe8a28585135efb5008e97b55735aa5862777b71dba7e4b43dd0ba948c012c37867bf479051312cc42f619e1985149c69520a1912b9b0079fb9db5b402c8cd913760ed9f99ee7d0a0c20379df340438d5e2b81730c83ebc9a7d1839c062bc7efa6e8f318d2f1324fd2abfe944d16c069005b2fba94b715c3d323abb85cec468946cfb99b8614b340666c74ef61c4135b18322dc094ecdee93ba0784de51e702596d08a00882ce2f7c9d101bb4d12c1e241ec0c7a3d37956abc245dfbc5c0ba0582c4516994732163a9d24079b91b48fd2062ac80b9882a8322d0ef223a27f02fee6f8e0e94581cf6d6405efd67c837fb2fe15ba1bf523231c9cb2c86698b64cb8d3aeb64c4f4dffabbedde700eba4a2a97c4b9d4f1c75a212048150426dc4ea2ac468c4c56ffaf87026cce4ef0df410fb9fa7e25a7576c6b7160c15d6be08d9eb99866e9247680545cc99c330557d4bf652a97ee0fb4f2900567eb6a78aa6c4269e543d06fc0d7c7e1c50a7ce6d3773f3538c2229a9a7558ae4ec4fb6c54756e9a63f649b07ad0d602aaa24c15459ebd182a29bf6d980068f1d81ac939c2e3fab3b907592a907932474a70ec13abefec1605b767dc783e2a85f4595ff12fd44e18dc26fce473ca96b1acc70277550efa0591b1c99f8289ec2b69d67fa1c0fd9929d9474121f6dead4850182681457582d9c9a0a81785c932b12fdbb43764865035b4d96fff1bf3cf1c771de4dec93b52ebc2142dd0ed49375d8ce413be4fcd4accbe9b0029993bb4b2f5dd37d55a3eb7af8a0ab931a27e5da6e9c5572b45ad8e2777895a2218fe7058977702b7ae02679e23ccf4e2a6a5cc35b175e6e154f4a74e1c9704b22bb822fbdf77bc127e9d5b397182525f19894c4c07a41caf5ca87a6fdb384c03ae118cfbd07f5374a2098dd573be89011aa2058a65f7e687b94cf0ccccf9b93804ad6199e1d08ba0049217f3aa5b93fa31340ab1f9d75ae03912e4b9a0815286ab3017b8c830b9df7141c84a12073b58f793201e4c1498c3d06ad4116649a4e6ffd7cf0dea96953e9a092d28d9aaed574eb24e51d11dbd775cfd0da546065fa7561396c01367f5c3405ed465962e8c4f804a497cc4e6a6fc1dc4ffb9d47a746e26d184674e94eee084bd0b60d633aae7f539adc79b83d2a75a6ffb60df398814653bb241c4882540b3f268595b318579ba000b6a96859cfbb63a15f0bac1c92653c3f65eb3acbb62da8706369989ab50197006732e09b7d01e7f76e77c47d6bbcf8aca8a1cb4c6f10b6092ffb79315f39732e6a86b546263b791cbbeb91b0d5d977e2f7928a47579b6299f7faf29da28f57515c9ddd0da1868233b133d2757a708c5a54eb44a7215c01cf500a8e11dde93189769d0457f793b1934796c56189060008d76e48c2e0b84a156275266bc0d4fb54a177e14a3ccd3240c2aadf0f0ce650b8f14e8e1647d645eb3ec698d8494a1019ae26c79e144996368d1350e1e590f1e54f7c8e28323c62e0a503fe12aba819b51b45928c9d3b04bc5d51660525305ff6cc3931db76ef393eb8c64ef46f34dde1be27b614c4569bc187e218daca901ec7ce46286f8a738e6a461abdce17c3b92f95d9aac364a7c483f491263ba933be77aa53343db3c2275bb233e3e0df28a2e059b10c37fb06e75f08fbaf929d5c8d41dc1308ed70327174e3dd9a68396527b6e3dc3f08b6d96b84496f2b2a3b6dabecdcc98461d779fdfa53298f9d4f8c175876026cef8daffcdb71c1f8367b969d0c0c273b6f6af15e12c1c9c51816b51118d458082b232730859c347f8c8217eff0d849dfd6c744d398f9d727392f3bb9036e4cb9907045b29c13d18e24757d551cf87a6467025c106e313b33c53063769e04c8d42093937adb1d598a842b5e6790a1a17fc3b9177a29fcf2b6f85dfa3d84fe21d07d303b419ef120e8535d22fa1cd70bf0b76376cec439460376d237cb4ed75fb333975925c44cfa7be5b03fc0f4854b98ecd29b9d4e383ba376c37949b7361a91f08fdf6f0835b4b426f088c9e6df913cc49baf8a9185763dde385319af022b0a9c500bb26366426677ecf4525ff7c8d909c09c9d5430849deed9c5df67e78cd4048dcf4eadc37d68a745e401b4f32c06f80caf8c114724413b0b31ed0c16e761311670a13078e7b39738e8ec34097b1cf7545ff150ff709b241d1f5da6c78be3e8453bc59b05353ed96827906f10408ca3f6cf5f9d53198b6943eee302d8297a8c8359393780682794f9ac79c46518ea686754f7cbead14e753e543d39da899c8bc01bb8ea3251ec8dfa559080d43db940339c5af1d8809458bd1616d4d405a82908363506359987f4af227352efcfa34e46eaa0e6c252a20d2bd051ed39123529d1b4bfb8a3e6d0b60ceb79399da9d530e9a347cd089289f96b35bc3c17507556c530ad07479e5213e4514818276d983522cd15a33125e8036e00f6d86e26131437e84177efaa02a797cda4b551ca3f699b98bb64c76d3e70b1300f9af8c7adab6dc8d771d09d04a00da2fc2811f9a84bec947da584af6c17e14bf8d2377e9b4092134144cd8304fc62975c009c14a38402ec7eb115e64d5ae7f1ef27f14d5ae4a43a700db8b82641936dc246f995f5eb25e9fc36d356c9ae37a2b75128ea4757698085c5dabda0613ca66f366cc05d3c3563532742989cea937b145184f421d539d86b73cecc95d0946a7a4e5542c0cc21df734c04470471d637ef433ae15044009a49aef9e2717d6a6b317f8f06c9a1ce69b7de1f64b2a2fa3c4b826a0e4a5d8e15bc8a510ff9a8aa6af4669e6158c1c337d0b79c7d1bd26fbd500ace446eb7b69b78019fa40a3e2e6ca1bb7f9f06095c8845d0764feb2fd9a81ce3238a1062d904a7656edf4a90baa165d6fb63998017f3de79ff4754668c087eac99c09047f8ce32b1a601b7bfc21476416bb4ae1298ac497220aaa69fc6b43d21d13f4e8584fae9c89c213008e952ed16d38b292a4ef9709f8e991643e24e9cc0583132212d3c2e4e8691c267571bad32892447348a13f85a7b6e3d78c9a9ee1d25753dde59df6a865f1968088498b44b2905ca4bfc26edf47f9d584aea15a1cc2cb58b709c156583df3adac81961f6cdff7ac3b7193d79b2b29e6e1e904a1977489ac3ec9aa884e1e9d96b66826b69fbc0847a12b27238a08e05d227108e5d2413373c94a22b3adb9f430e428a1b790ab99f8216a61faa9cf4a90a43b2c91092e33512128d38865b4c2cf0703866dbafbebb9158a56c12a44b238f839865ba742e45b67db6ab41e0a630422a3fe0936d11491b69d545821f91f069aaba34595007e05b08626268c81bca267f23f3e4be7aaf1076455258093b006e7a9e2d5708cbbba2c7d6dba0e5ba806d41d80cc09ed4c21ce4f16669022d1ed0677a549e8fb6165c7ae0a31c665e118894b9c119e0a5fb563d6d6716c12bfffa2c68b4fe5cf9b74a90dcc9008a78a9599992152cebc927d8dad1b1a38e8013f9178fe94198af3cdb0488e30090d2eabe1089abe06dda83b86c452f15711b79a8cd9bbf636b501e3501340ce1664a9c235174cd7720127797589c93e83c7deb9bf198971fadc679c881837e90dff4701e0ea0897fa46f8c8d6a3aa99a0705834ab90584431f6b9c320e912b1d090c0643850ba2c805663835b67c6ee3eb5957b20427f5d402a8facab8d695242d55dfc252ee8d218eeb92a49722aabf18faa0f81b32906fdcb98e9ffa59569827b43fbcd8db7525cd420dc8b1a81767906aed3eae5014151ba2445df8f1e9b94e393a282a00fe591dc15e1324eb4c26c973a2966c8b970f981f3c14d15c4747dc92d7d0271805307f8c38445c835a4796393fedb056a66bbec05d4c09be4e2d489c764ca067e0a98eb527b96949de38cc0fed0238e4adcc7974a6f7eb8ecfc20187e65c57fc6d6f1ce74aeacf1fb03ab4381fe21f327fad3c8a8658ef01f8e94d18b769c9f80009fd19540f4e8094160f52ab03074101238efb553b5887f76c9dc6d1c1b1e18d65d0b8a9e32cd38a39261c1737b53ce99755c49d4fea56d082b2aaec56e89285af2e19765623499a907e25f3ccfefaaf07163fcb086353f6ca39d876ad2303873733b0395713ecb7384f03ddf454fbc708b1a7c018a66cb9e8adccf38961a1298e6ddd3e7391d1ebdf76c85ebe1aa0431dcf2eb78111d0aa8734999582a5008641f163ec93c3c68d3ea986508d82bcdf94ab1cfcba8eaa11b4e41f5f4d8022971c76ccd0d2dc2f392eb54460f69b077b5f22c94dc5bc58267d5467457f82e015510808edfcfb33ae9d6a9a39cc10467219ac8cc401c0c2938f22e2ce8c1410a7a4e997dbecf9d32e82a620a7694a1830b35d05a06c4e7187edd4043321884dc5d58ad9c2efc95a009c05b2b50955171fd66fb620aad0494efb2431073692d915cb5d38da0dfdcdf44928e88f3ac8b5e2c70fd05f545991c11f729f99c9a6a685f4706a092f4ff03e8e36fc667607bbb8ed791a11cc14456e1368f87f970b9847260ee9dab51e7b4c9728e6173030fcbcbd1b4f26a334cc35b192b3841175024bf6d49f7e3dd97b0636211c567709ae11388923e07e27d6204014e5d2ce42b448677305033585d515f5f74141f80121ff419acccbb79201c2087f8661b22b5087404c9d79e9f08a5982199f6130dc05fe1fc113cd551795b46d90ab2838955e5d0b3cb9b1e1d2cf4a34cb743a188e4d85571fe1a484d5d6165d1479db2a270663815dfc81a865912d4a4385b80e45dbf59234ccd72ab743739047db17d559f661f3c725a0260fa1c01bbd5eb4c3eac01092c8589c9fe003d5b110af32cdef219c7843aa0910e5ff85ccf0f08f794ac0e151589e60d9653907ba5406a4c6ff7bccae8ea94494e2524a5cfec174f394367d10ce2ea414a896eeb10c79b9a4c2903764796b2ea753343e1564432067f1be155dabd04739ca5b39918b66662595312feb360335117c69a26124552a4662a175c91fb8d52032ab2311c7783dfe63908401f2ddce19868e63f847620f4689ac07162c6a0ce4f9aec7538653ede69a19857c89e316e740f34fae62822acc5c94bb7c1379f1837e44cb0f3fc50c829e9e22bd9a5dccdcc1cf1beb0d82f1f11af605a0124f92f298b4e501a35f33c5367f9812be14352ade8cd88b543f5fd12bde3ff0e084f200c5aac02a400ac58ab8a5904dc21f99aaaf718024eb45b435cc3fd3730e06c4062c301391f82575634d4f4c7f629b0f25bcbcbbe9a3cc90e41d3d1a74393aea54df84304e381e92e626648f8ea8e22f61c289654a48a840d0e11ca4ffa24f2f18d654f99201423b0b6e314d7c42c4c7578095427b9f6019a2abf9152dc29b53a0c640470a0d70fc2a6b055a2d1534fc89537eac2e2f568d670b4e103fbb45a5878c24d41b2f83692f2ee6a67b80ef46f8d929754f3848a5175834fb6d2bf8b1491d8031cd9f00ede6b2225fadd7225d9e32b3ec66be13fc0a863e5c7a748a44951b74aee27ece6a140dad1a558d978cebffc40e825a23e5d4c7000a3cc45551c67f80875952e10c97988a8a79976886df0bf986cf6c992279d95e7624b7f2cc0fd126db2f9f5cf5cb23cc26f5b3b6518c727093ba954c27cc88b39860529e5c87331db3b4893b1fdb460808feb80870bb7a8c8080350f9bc786560ef640bb98b5000854f855526f5151df0f76cc5207992a60d1aec8914ab4335c4544c1053755c5ec539ed302fc2dcd7f13bb3db4b0a253ee25598aa0d786be59c05c070b2e17e6b872f1eab4d284e92f10273c5872f95e96fb96e84fa30d33769eec092b2f2f34ad2bb15ff9a02d4d19d589a83521cfeea648584c5b7ea556ac54449dd8a71161fda75b5737f5fbe74cc70aed20a56c36df003b31db2669c9244eda4a4797bd4e94934781483e04d9458bead27a00435a9d2d6f0699e62dae63e35bd55197954329f5e93a918c29822c27d76957a86c0c84498312e02e10167797580bad53f12d919a5dac1420df35db7c2b445330514c3b717aa2627bfe613d27095d454c75ca35891e4a3781127c5d87671368a6e60921c2ebcc6600563f3271683aa16b24180ecdd1dc4fcdc83f7eba0196333835922a564de8620d69964265a071af58085dd14dac3a91ab7cef4685389d3cca113db60f80ce4db9b213b1e879f17029e9d7f19726fa32ffd0004bd05464c920ee9ff1f2e0f20f31b332ec3a9c5ce5341e4d05f66a56edebd6a03c204f1abd6a2cc4cd3b1c4a0a992dbae1b4eab849beae57317f81fa2c046f0054c8da033891feff8840307f25f9628a6efdf980d9cff8fc38b6e05a0a1a2f9b28a672ba3afdb8cb2ed7f8bae5463ad9d84c3ff80b47dd90669c1b277dafa304530d47f168c113083c47ad9f53d281af45a24fbab52aac4fe11e9a85623611c4b753c589261ec77dfb63b8319e40ae8913d7bce00dff59f4415099bfa36e64859b7c20ae6f308b0faa8255980a7997877686e8b2d827c910eb9940d23375eadb43f7d722259ea4be90197999a218276e0068a72e3c7007bfb51e225d1688e61d8c13697e396312adf986b19310dbf7cb67c637e79e6183b88aaea8d029ed91cb9a987be303ec044a3d526f31a0830da2386c4ac99986683b8767f7665a5518485562900f04d735b03588fdb0fe270ce06a323e08bdf570d10b9ffd03a4bb81c1455f8492270f9baad6eb69795744095c77c03fa6671ceefd3582e9b56611f4c2b29c3b930e5a0e22c6836708e1746fb2f04a073d37a77e64c5c69c8825c3b8b40463b2122ab8c82aec584410f30c560e3af160cfa600a013a13577b9f93991f2aea2564b8a2e5f5a5f7ea9fc141427d70b4e4f2752cc4fbdb00df2ae4e0c8d2cb3c02e415198c23eccf7a94442f44bc3c9041973c0b107cf29e233b7136165bf09246d827ec3ec7323c7ef4e2413b534221c290c75b03d10107c9df00c4fdce9115d79838ca90573f71138d4a016885479a596b3c55107cce1678610491ba4f0efc821e8f3e2a0ef23c70aa0da13ef471defd873d19e4896868155956b392e76495b925b4f2cf6651867f928cd62c58c68ec2b91fa2a0ce1f5eb32a6922461fc7acc53f456e9731dc98b247d629b24581333dd0c9afa7d021f307103022481c7e9da120cd50827340c55705c3af889e1a80a53e9f5b59104dce8c114a6ab113a787714e26f1b32a43935f60c562324468cef0d59175b6f2d9b20b3a8db750484b4b9d698ff00f61aa1753a442aff0d67df3bca9131e908d1aa8039d4df810df01fe9a31ee5794f2f3a878eb03686841153fa5d060466bffe99592301a33f474ab4d73812fac6eb448b6ddfb50047028987d93c1f33929f9b085dc5fd1a406f7377abc7390c12048605da01b0ef9b7ac15debc909e7b36c586af003ead60f280a1db23fb3eac09d608601734e46420b77406ebf5167182c0836cba8c6c018499da9c5d5ec346ca23b86558a9bdc60ec82dfd214c5b3277bd7fb84f0484e15ef3145fbb09c90137ffebe58402e2e48d4e4cf8cd43232637abca4777ee8d67c4f453ad83750944e1f67e0b2ae9ddc3a3a9a87ba0d3e087c17161a2ea61236e5e3653691673744fec0de58f1f1bc6091724aa03b04d6c243174e50784d38f409ad4acde20ae7eca0c9fb00eb0f9f8d783f853310d40d96112e731090af9bd50576794c933d614e54d3a5186dfd37c3626def045f6e421039a51ded0dbe9fd8864d5bdc76c9a3dd708fcf007a87786e5094b3579704b00a1d0bfb316164459497120dcfcd2ee4763185ff8afe1759aed84d03c48ab6aee84d17c11a2a84978cc64b3e89dd6534cc4bb8ca95abf758fd2857817128b6cd6d31d5168c4af9eaa2493658fe77a4b49dabd7e02f7dca1a6926a56ebafa7bfb8476f34a31c3a7b3d8538535d67963d534c1629135b9e6561fcaee3a65c0f7c792475b989dc09b0bb2aa468e706df2e124eab09f0fbdbe3629d81fc747f637aad8eddc360025367349414d8b7cfe411af9de3ed929133b4c73b4384bfbcc4e3d6d64bfb33728f3e7841b5c479a103adf88b6b96a2270b9ddc2482be21560658e831c2d2fd724815bee6b9072e83b48b54cd6e7c85b135c3afa767b63aac54fe00783bd67e518f14e50aeea03d42c9c519d138dc27446ac0b85a1682dd63a707fd71fefce3308e8715f8f285cc51cb8748c97cabcac4c65c33b85ee2a76dc631be5160b11a1d5405e4311c9aa4a3c37b849e15a0535450f88450331ba90ec18db57b2200086de1b7a7331ed4579ff1ab139556405a6aa9cc86b2c8288b6bb839dfd5ad2408548d617876da042c7735139b6d3d8ad9fc2f0b6fd85649845118b7b9ac897ba6661134e2c52024db3d5089189838288a627dc37c7412b3735f0c3d474242ec6508d25bb1cf4c77e9ddb5f2cdc87faa6c02cdc43844f78a275a2d7dde23af5d3caf3669c02e53ed08df12d14050ab43542c1e2e8fcc64e1e917f7f3aeecaff30fcf1610dbf07226b3c54fb7a85577776f9dc5809ba307f974c1d95cbf68ed1850dfc365e100cddc69847c81bdc5a17e57ef86dfccb76e1207c790ceb3946d0079c105beb5c81182f846ef34e08381fcec9d41e10a21a5ec87566df0d0bdbf1d39c8323fc03ae080cba70ea1b4452bc903658a4d9914cc1f4f5d9d65a6244e86810a2421860ee7a69d906c99b561c2843c6a147290c30584748e218b8865d20e7eeab1ac6759b721d190229590a508b010c005bc41de042558f4a948ea7627d0149b92dc6f257e419abeb2ed424ba03e91339495de6e6d9a05b9ce47c49c318d7532527619586f8b008f3490c3ddce6580cf5756ac0eaf93b63859b77a73efdf192bd830feeaebb77e2eaaacccc11f1e710d76cad379ff9e83b71702cfc914d5fb7daca7fa0801c5b6348dcd90ffaa3419588772fcb71bcd562caea35006e1e394f749e0dd06bb64a713b4b16e16d15985e57314e44c5f45ca5faaf973508be22d325f58b10ebff795c245848bb80f5a9cada2ce78c617a8937366a212db3232de70194cb937ed25ed8b1670f20244c73dc599c8116b099d989d119c266126760401a0d38ca16e11e9d706ce0b8893f0ea5e9481cff694fc00117920aeaa2fecba721ed7cf8cfe03caf2ebcec428fb5b232965d28b10f45045a7cf6f74eec370932db85b91cab8fb9546805242530269fc570570b61e248663d7af783416e9b3b837821de84192a7d0c173dfce1f845f9c41ea8d335f85e8a9626f587eb1ec527e48d0e39f2427f5c2bb289020704158ad8a26862a9c8216ca70776841a8310a5451aae497bf19117522d603a7c748c30a65a7d64dc2c60b44167089e542b445e08b767fd2424bc62de2f2e3005428b058b45b4a3d85a355c38f6d0f473f8fe587e74401188e7ff2794a0b92cb1160744ad582f03565e181c522c47505d8aa183c193c6d550c6284ba1f74a7e796866c2a78c0eeb2e03677875a809e98d5468dcbec1d80006cb7633ed1cb3a058d119e967bd5969873e06c0b7f56bfcb20df1fe3c787a7656d1a1455d8d4db3648b2bd0adb07cb2e049bfb4066118630d882e2d9addb0efa936aada55b5f53a31bce5c77027e0d415a4f0ea6f020f104c832f83fcec693811fdb316c87afd121bab60e2fea8c77ce14e6efa646119435b7c422f3c6e14c1c15f2cd2f41519371c2f2a748a124ebdcc0b270b13015b2cdd0eef28bbbc745194f215c047398082d83e569097dadb4784181eb1ad2f07e8940ab56ffdbacccc9f7c4c7da2740cfd190e76d001ce848324903ab93733dc934fe0429d42ea5b130bce5cf7ede6fe1929b15ee7ffbdf5c83a56ee49d461433094c0d061b92714509ed5b0b182b6da904ed4ae17a9f2380873578b84c2cd9bf37f4a8061bff9e7796fe0bea78ef002aae6b39f845ad9bc6f5a8e922f22a6f65b81ce4aa1d2d2f31a1a83acca93c655c87380e7f1134e76cc202f8d32d04e2ad714887d6e58c3fb5498b27aa964e072943af19240b3530afbdf15ab2228a65cb34faabc9b5320d1f7a2522724bffbe71c096a0a741783b74720e0ed441693abe5b5020a54fd11643878ce5aee4dd9b1a86a459d3c327babfd08964ab519f367559732a5ea2a4f83bf9e273e8ac6427a79e6aa1c74fe97d824156b02f12b3dd4d75474263378845f9530d35b3cc21d597a2e3dc6ae05b3670d26894aaa4fb52a303b03fd06e34866f0d9b322b06133a6e6ab9f47d440541771209b8df73671432d8618c4d97cb14732e19ffc2029353990802078e9fc65cec24a5f92606309668e6d135e8e59d63d14b7f5b3bebf9ae2c78341e131114c30264f70e901fffdd462146305dbf78fc7358878a24d8f14b85d92e8c2b992860dfa6c8324459d6988fa1492360b7f9552253e15a95179ebffb88628b593a3b474ad779d4e29ae2ce83fae50fafe729bccbfe333e3c595f743b6796d4c371c43e65597f56ff8e1a67d6874824a56d276ede5ba4a3394d9bc416f47f2670511d5f064e81d15c99e2b559c05052deda9c0e1567fd2d8e5f1a6140e244818883d3179824351f208fbf41f1ba5ba6128d58e6b2acbed3190b30183e5bddf547d6bcee0744ad101c947bd4125a1b588589d161e4362ef27b1007cacce2501b4f51c94242bf0c7951d50e3d9097d1603ba87cd36521306f426e26bbefed862ab87b38d60d213826c7851473d40e40d6ee8506b0fd7da4faacc3591c02a761ccf7a7ddbe0b6b98c3cbf1ede37030be0c86ac2842e13b6924d9fc1829a068f91a0645f68613877f99ee63fa82e3beb05fb73a62bd7377565a1a536f17adce3410eab8ef0442141adb206fd7b949537634bde9d0e3318ae014c397da17e8faf3e62ff4fd31ece7df86f7b331ed9bdb4fab3cd658203ad89fad661100c86e054a198fb434aa3fee894c908dd2e32e541cf5f43d9f653321a62142a583f45f748e2eb9e1a819f1fa3f56a7a719daf4fc1b189e95fa71625ba07a6e60af8710503d632844673bb5032a98f165b51c0df839820e7e60b9458a7f94573bcc0fee88ed939ea69f367a8250ef632c49d0308f8cad7db35224f8cf2cbb8e92e6062ff9dce14de47cf82f637a15cf69d3d4c7a824a629d0ed1e949d63aa85c941b47efabd3fd078ea66104182084ab4ddd906a3d7d6b31a75dddd856ea8e82f027de0c387db451574520f0ab2c6342f1ccb6fa1da19b644cc6a68a0417a7afc2108df1a6e28aedb51419327a4b4ad8ee5d2023e3c783ef09b02ed6c5065ba9bb786df8bb2ece8e06f43f9b4de616f71e643f8268350379442ec4e9d5d76d00c230fffa4c694317220828d70bf3bdd5329c0cd11fd552d3953616d0bb1736c2dd486210dd93e9053cb808cca115f2a37af7cbff71f354f51e414c6e3afacc53b636228887446a5828b4ff1ac05d6b9ae2a153e8d3b82ea6723fd89982b60f420d3a4a36ac7af683854b67e2b84fe4159bac81720da1cd9868ae9a0043c4064d01a0aa1f6f187ea6b065412ea076eac13c21d03a45731c906c18439b716febed8f0b4a2b067f51f6c437b5051f948117eb2c4db56d940e41d58c0d6d886b12a87288f97ddc0c85d123e0eb1c6913551a9b355da601ac9a4ceaf0929db7654d2a657860a94519367811a81a535b2bf8b7ab3ba7d44208c13e97a499bdf64627144a1a219f1ea4df0ca21eede8d3f7cf3c7374ba2b98a4c74f3f78ffca87e68bac39eea6e0acccb980cded8943a603848d8744a825d269a23bf895732b070ac782c71df7101b7080f52258de74da376252a25699317d977c0927b0f471f53c2273b9310fc9a51364479d9c4527279260320298e372e9c62af6f531d1f4517ff7b4ea471cd04910eecea3d50ff6d6178d468a9d54cf43c44be164f96f95e94563cd8425b15223024509e027a0b24bddb725b9a6b002d252eb18160ec07c2a356620c82a784a4def085c0ce5845cc502fdb8fedbb58d86c6d4b6e09f97092961b6f0839bb96bc002700878705f0f05c9e60165c8ced9cf3e976f30e0e304e3d0364215ee9a669593af01f88713c18ba40fb675a2d65f8f13a428912f10b611428e09e90c846c4a7b96b70ae5df83d05261f0c458ba58a94dc7a1fee1604d72bd96ebc8e2aa0b31f2d93c0a890c9017a1e20e8bde6f8881030f2daa6c15e72fcd9fae2dc930e0e32bdace65f40c54306fa2870d874e799e4051bbc3752f6e6c59883d5d97e8ca11e1563f0524061aeb301417f0105526e74a9a5e3a5889dfabda71615238e03dfbcb34db50ad13fdb80b0708ab12afa14efe1bb7a997d43eaee1bf51eb562b24b78910043e361404f4e1220cbaeac3c9e7cddd98277615fd24553c186bedaa54f09f69ad8cb781668a9b251cad0b4fee19ec0fbb4460c5f7e495453dd33b73c7a300217ce368b57c412492f62e11c55dec2d91bb6b480c906e9dd73afe572abb77f5fcd02fc695ca5dbd9d9bf9a114b507146874ec425146db0ac33cdc6bdef375f551ce213932de73a2cd9215ea4fb3b1f49e2f50334e9a26653c8eaa173082fad924a10e59b574429a2b6ffb21caa9b094662d8cc5e7fcc5bce8e531c422a8ee4c5c0f23d4e8ac0478f990f53bf5935744785950db914408e34a333ca885e23527105154c19065fb2df52192ce96f5cbc116a697a8838d86e2e2bd0b35a6c0d20b6854901480d9755a154721f079ba22b0c208ee8746e11765407ac81291e90b704564db9065b3c4d636bea43a52b0266398363d23cda8e22671f766bc58b22794ebf116aa8b95d4a8e6fead62e1e44ce4655602d415aaee67a7609379b4f89f1b257202f48f916d6c4ea98adff212388a7089c0b4292992f377b8e43f89760adfc43ce55a7dd92a3c87897c521cf6058370fde4664f0859339eed784bc7d8ff64eb34ef25519f0c819ddab0cab8e4293fe71dd6b453672cbb504a659b4b749cfc03883e621143606fd54281eb05f2e21c9c0fa5593aed62d27d4a57cc236d1975b9cbbf896cba887417b546ba512f44d6ce7ba95d55cc34883b77f4493fb90afa96041c164341145c3e30e9c9119368a8640627a46f879687ea4418ff3b104a8ff450fb5e814ecd1e08b4f0e150f370fd1fa19bf63209c2785c6f6831411eb92c175fa83a2110a3a662aff05200133404fcaabab227f8b63676d399961e08f3c8f9f7041812be95f4c5d3d3792600214c629f7c42de58b1efbad35c0d977b40d03ac010ab2eb6bc6f96f267f2cd14b2874fb6d7e1010ef809617acb014385412037e4c956a051fb48a9de0cf658477791854f0919c919ade4a07c9ec45a480f3cde965fd5596988fb94f2969288d41ab1de72026f7df44483f107aea5a4c01ccdd8e995d6d0ad6c6d146897cdee06c3230a917bed2264c82ed4e3b388f5d8256a058569edc9b157419ed45c82afe00190f122917467c3671b07467ae797e9a5a47925ad480f888fd27535510c1976c5e239669b4bf158ba6756a1f8b4f97a5a16b8d43a39194b7fda76175677b323caf498c4071c8cc1acaea06b23691e5b3a126b3633d4c60ccd689fcce089dd3ebafd927cc2a2144195e097676ac6a721e72c337a01a1b726afaff771663b0f3bd346b3812540f7ff958501a68397faec7df3d56b0ac009130c63172f2ef7cfd742b061e4981fb4893fb15a0375a7346063d64953da4aa2ee0af893a56aea55ae40cbaf89206a89412dc8449ecc5459f88a5a9406ebbba88007043011b2164db17c84b43bf20b7bb56de6f1c50cd860d5c64df3e2e695ec129d2844e3f5ee15e33653ca0cbbaf95c095ba648740b8ce3b04539b96556a13aa70022f0de7e86cc94ba8c2c7d7d9fd4fa5692350b856758f09333c518f96f8254bcd4389a5db6561d1d5793c7c185b45c18fa48ccd19aa8a9b0ed8029455d48dcaa428ea9ce8bb7630575b34062ebc4ba563284eb7b2514911756e37a6ecf94829b1b62e08672ca9c033665e13d9d7f8ad456efd0cbbe781a427dfe3a300cd4788f6524140e8182348f78894b86c80ae85c57018b0f77ce05e5a5867c20c4bc3b2741e81a8771e6e234cde214de8d3fff92ec1b4d1f835051383fc4df496f5729a1c327d47a99b32d14d4cf4e325f0a634faa8db4de19e9725bbe53a86fa7aa87b87a665c4a66328440a3ada11615aeadb2f65fd8f32f5c197d292d0033b9bfed1496990459d341ef4f61f6582f7a4b2faa36fd53b60502a861ffeb5d091f1b72c459cf88d03749a97475b0256d1c48e2212e4f38ec3fe9e2c5802a78d09f9a11109140f4d6368c90832c0bd1647b84fc19606aaecbbd345b97ca808cc0a14876cf39db9c9d978987cd4229318636235a4d566d26dc41a5c26583f5fd55e4a39161c5b43a68ee893ba6c6ddd9440719d78515f605a0b5064d76f997d1eb47f1fd8bc86bdbd77838f988b63f174ca7f617b24334422dd08c5901b412c7952ea0cce0bb77030ad0e810ad1a19c44fbcac063dd775ae29b37fc410cefdc3a24d70ef4dee8db3602896504a94e28d031427f0779864320f131d6042633db9f4ac49b4c341c5c7627e3d71478a590d98896ac6f08095b4e1f5a6a74d95d8e528c69eb02402508cea746c6af94de092c206d5f38c1ef13242e6bdf889a695bb38a0fef49581b2742fe64d83c4db903a9eb217c488c7a33f4b6ee86cbf08a992bd6becd1fd6b6be2e255a22e65ede863b65d0728e046fe176fc22d14a4b4186f1ad70c56a26563a1dfe582baa6fe52c38192c30c0d10dc40db9c2f4fbd7fea4b5305e81e22bd1978b0c1d427e60d32180ce19920a522f2fbecdb1893a74c91b0652335ef2c9e4f5e858109bd1484bfa065b0cbbb0de18d382916cf3d04d9d609732658e59312d79dbbc91ca2198fce330220f3a3d5e81a71d74c5cb6ba5d12a0188efaed53477b4563eb70548ccb5e14a4a6fcc6ab830d0eb7c95cc90a3f405bab556b42122c6e5ddbbe4371d91ab4106a2a546d3af5b51f7be4040fdc26270a52c8211d21887bf04e2c822b2210804461a7d7391d81e7499025faa486d040c1b9429ae22d3ac00d2a18fab4f25793b83f70d098f9c6d53b72ed756c2746327d89e10ba32a33c37de2260e02f9f7b376c2e638c292b1159e839261815db6008e13272ecd7b42d52f689ae421ec858be9ec8a55aeaedeb6605cdf9c1eac8ad740181cda72dc58e8c495cd36d1f9e1de97ab4845214c6e9866bcb0fe16cd5e8f89dbf0b0bfe7424fa9f7b127456bd6280adcd4a37223a9e6a506d1fa0cde02dac7ba4c7345c3b2cb1476930493f9210c4118fdd9b370c97ae0b0552f86a4b68dca723f698cc012731f9e6f20591a36eb6a0f98ea5820d08eaf8060242b36a957ade0940a4f0b132471ff9fed40747983b242e240856a0fbecac07879e4abd3d395b8fa914e94ca200a33dceddbba2b3e881c53b5394f655fda36f691510209c338468201d846554f7192fe7658aac0c6b95b9278861e5796a56a9333259182dd714cc27cc2a6df58520bc28a3a36b3b1b13dd4aa8bd4e99a9abec73c81ac74ddaefb09ab2ea5c696d89e132c7ba17697837744ac1acc1ba2ae086ec1becbcd73eb771f91c3382f266f5d3d770d781b7071b77009d0d05646adf850a1880fe7db1d7403e25e8de32202d966bb4d74b43885cf30a18198518fcf43b5a025e07461fb47aefa327574fa2fb5c8b1da67f7e7470d316eb509b7b83c3bcbffe47b34a13b21c0748747bf8b9e4804e26d32af87485abca10487a7b7a73e16b03d37704b738eebd66d46d5b1bc1dc033f56d60aae1e8d41d42118787a6795f68961b12f3f9cdc937e5f11c2d4ff1231e27cd054385a12222ca124969b7859dd7af2e465322851f96133950726a792a799a7fd3c8b0e57c1eb759472131bd371182705cd4189a5576c316403833a7532ed0484f0b2f713193b1555c02af42a1568d2f6584af12b352c97b9e123af810c541dbe4a94246c68e4831ba5ae89fdc60aacd3812773cdd8742adda222a725667a5f0caa02f581bb56a15f6f006912124afb38f1fffc63c764ed8ffe75bbdf4baa1df4e334d51dff168438a8b6aa70e8badb00c457aea077ab25b008d7dd56a2668ec419b93309ec303ad8d3dd9477b52496d184093d2d0b65b27ac101a9975e1479862cce73597821502dfdbb659d0de53c2d431701d7dcf4823d313af089e14561265b4ee90ddd55f1ac8fdefe2da7dc9f6d2020e07b88f402f82525f0929e559ff77507d02483b217702e1b62fb70d5051107e7649ef8ca784163c05a869b39d0b53129e235106800acbaa72f707323f34b771cfdd620c44ae34ab8bf256321469557aff2018e1050ffb6c5e70ace4480454d15437c5b092d0803aeb0debb6d3aea0fbe98c44d6adf59ac999c5fd7cd3a505507fd31a1008f927bde2e78e471021c85cdf6abb40c111e8d622cc18ee2eef15c627f81d15dc9e03ee880f81123a90d68ca4d77abf0f7660318d8668f5e682b7bb0f59a3c64229dedec10337e1a9035f9b068695a98f2d046d4532efe83ec56052d0a2d90846e744d3f426baa4ab51e49fd7c40f77ad5ccf0349aa5d35eb871701b10b286048a21a5d07dff30ce56d03d8769c16822c17b692a4dc7b042d9b09a55f2f4e449cc64621d1d8adea1b8260c7f1055e380af01d0e3cfeb16edc99945515599285c404c4b647e5604502aaf04d74c29320627e86fb2f92e352d601a648b831afe1975fae3a09c919c3ea7bfe83c7f4706675d4d41243f21fd6e3c6f33bfa4089652c80f532f6dca8079dee1f9301ad06a921c70f92035f215545a20c2dcf55448dcd4463531677c2ea74c9885933b6e2424b2a10fcb5e771b3812e5383dea1be4e5361b8d01e28182acb5dc197166a5d44beae8dc537641bdde6e41690ed1eba328d36d66c9085a327ffbf543cad5ff380d3277f110149b8e313aa897370a3d6a2e43691472d1c37abaa5c407c32306e638960c02a6591e1f3290a9fd8540d82cafa6b720476c140b81a99f5e14fd3dadbd3b551a4bc8719b37d3a87d2ffe4c208ec12d99a223d56622a0738b6c340903781221ffddcb647970ec46996bfd29996cc5e5c5166888c50b315f449d04dca7c3e4210b3581d4e6c91daa4351060c0b8a88721ea26370d3b4fc244b1e09cf213897a1b79bc3d635805d30a325b53d830419b98cf6f92950303a842ad423c76f9505de3fb984a9eba6e6959fa84da933c99e4998e1a0879bee0f5970ad1c23f6336e9f907304461d3c8be1034da8ca92c6595b2872aa1be8b58bc764a4a1c3f465d7415c3d8d2cf0bcbd53033e32910e40fd09c94a406440d7bd4d9b0d8507f9302fef63f1932cafccd82e99fd8e21e922f490c980359c2873990e8b24c1c9405c92e64aa5d0c532514e78c140655545fb248ba05e5f5e8af487ffdfaea07f8830b3010c684876c7750833eff085f5a0f658fef57213ee8aaee72ec7d0d8e86b71484567cca4c0d19e9448789ee6cd691e1b41614acd803e16ba08ef41aedef25b89069364de9d61976fef122a1d23922105549c783f035c5da09177e81e9f3a7b88b159085faac06e4d5d908f9a8249be47e0c0652a212f023c430d5e418e9c572507d2ec45d63a0eea548f5326ceec41033333a07cab5cedcdf4a3cc41e789759e65a7c919c60f77c2a1d6b574b1c83a9e921872fff453581cadd3cf6c6179462f45a831d33f5c6966e9210c5ca6a583377a70b6103cc170c00bfa13fc95225a49f0368608eccb018ce532b976868668454baf736d6e8ded139eb3f3ccc381867b9829adcf38eaec02c5fab5a199777c488d9c04d308d66768a4d1dbff51534a33e348d75a7d42396ae263c6e1a0dbd84745352b6b351a631729a53a1f610ff5608747184a1c32d1a0f8e9cf66e70deb08dcba028e80a9975276c8a1690f12f0d9dabbb52caf76cfd022407c2318086e5d29ba1cf6dce2eacb6df65525d34b512b852afd7accf3215675a4d7ebe528c4eb038a74abb25a20d032930e6913de365a88cf1104d7010ec467805881d14a6e29720d968d83781ab507adb7c71e54d886b41975981e3c00a5da6e3e00002c53950939d9239c03b45ddc344f0aa94d1c2e037fd33e9184ecbdb7945b4a29939401910aa40ad40a1e940462e9b58e3108d20a580bb6a4e84012c010e6573008082d9ba08f96cd19b653af9d575004d9b9742f6c100c23d8811b18d28ac1219d37a7ad3ddc4e3a7b585a6b9d91478f692bb5a08d59db819e0769fce174eec5ba584863ec3816c06984931d115d7489bddc25f6729898c762b1582c19e5124a46b54f8c2a32a1a414f224042a4ab25c0854144bc89310a8a8f68162b184b0a05c52562b5a35a9e454cbe89354cba80b914f328a157e2cb7a06c75d9a9c7568c6ad9e4cf5dfe8a5977015dae8e88ebdbbcbf1186d421de75bab17c63397d000b1c52bd3efd405a410925f5b425a1e4b6755dd8db4664b3f6f52191dffc253882d76f609612c65ac0c55fd7cb2f9857ec2548cb57ccc62e4ccc7bbdc857ccc56dec7b89bd62db5c2fbf6034de3219051505150515051505d56a1710524a296b15f2558f7e38b5cef9e15429a5a49240b3ad07d5aa55725256ccf2bc31c6d85fb1d83618bc9f2b26a35c26ed05bc39bf589e8717f48e453de7d7f358d259d2afe79cf43829f1d6512a1f80829049730165cfb4999303abc7d5f912a45cccdaf915903d56f6fcfcbcf3be0a5e11a4f771be61cf840adca56ba53bda91403b52c9cecf8ecf4ecf0e4fd7efce62de6f11f49c8b11f1debbe090ee7248f0f5be3c71dbe49cb1a9f761914d05f97604de57708804612d9b4e411e243c487890f020e141b2c353d33693023c46b4ed8bd520917207098f91ac4878ea0e121e2324b5562437a649beb32fd72c1cba5813caa35aeb15b59a554dd5532da35152d72d052d585d5e3031de8bdb975f87b91e138bd1e65a8789bd622f3197588c3616a445ea530f8bd48d82433a9066dfbcb38043b253c79962906e3df5f4769639694bfd445867e92c2df5d3593aaaa57e3a0bf515669761b03c1dc71f72e2ec9dc7e9f9e7326f9be34f84e972cfe3f4cf1ffbe6cacf02c37b1f638f2b5c9fd72a5b705a0f61d7debb5d7c3730471dd3391ba3ae9fd3bbe59c372dd5598e5aedc245277b48ca43ca1b1e3d64e5516f78f4e051b958abdb90727bb9679b05294dcf78c81d9b6f1d0b94df081eb839cb83cccb722eb66d20cb83f4d65c8c15654fd6e7f4e657e5e1f8882e76b18b5eabf56ab90564adc62ba682fc97180fd230f3e5e5e5e585bad0ec39bbc8222945c39b1e3e9cb8b810392972797152f42289a274228ba414f597987c914e5873ab54a8d244ea23692489649117b6bb5e9e2ca2160c5dbce5491f312e1d2676f3160c63a457bfb198d8f5964bde1638c4a525bdbbf9ed057ae881170ca9742281bc80435c5aa0742277b840977452a4ffb3d2096d59748f5acfe5772ac8ef9c84157272725cf80f07b75c6058e4657bfdb66ddb167371155af8cdaa207f7317978b5214d01f52eb0bccd63b179fde75fc40dab2cde5b67d9b1031fe1213a206b8e3b518ef648d6d9d2b166362b76d6a2bc672978dbd8c705d629fc7c4607c8bd523ffb97498988e115ec78ab5a4d7b28aaf750bca22e9e4e6e5a4ab35c908aa24212c6a105d32815ac9fc69d9f461b90ca74fa4c1bc868719c643d8bbbe968581d1e68239d1a605469b0fb42764ff5a1ebff8f402863956170e2c529f934edd152b00536e16984177397d0e1cd26a3975d0e5610716e9efa8a7141cd272ce5be090cdeb73e0bc41ee90e0377ffec35e3d48270ded2421f15369e8af17175a9078b78e9a37f4d3ec993e953ecd1ba6cfbca1d6271a8cc380274c9818af014630bc31e00b386dacbb78d78ab93c7f606fdbd6fa5c1ebf114e985656115aeef2201d2f6ec53f61d264ceb4995ee3cc894fd3bbcf65cee7de7dfd47f39febebfccbee8ab562cee9cf413ac72295e5b889d2397ca7cfbc4102b9f4cbc7394a39ca711c57a49f5a4a39eadcf47af4b03c78c81d9c73f967b3e079bcf9e251fe3e96dfb07c7cb9f3f19f873d6c50ca4302d978bc9196ac574e969e001f939c2cf9ba76eda95a9fbc280fca836a2965ce5432e7ce59d2599e54f6a4befb8a88fc64f670eebcf4967fb1abdea66cc94fc2602d58f6a03ca89753bca8def19e3caadef19ea87a4aca8bf29e3c298cbd288cf1758c3d6c5927d2da5cdb46b7ed73f1ce2f57defcd5f29796cbd7716068c442bdbcdc3789a88ac66a65541291511291501213abd92cc49b78b46df36682307df4b0373d6fd823c47efd769533322b8f97677100f2a2b6def19e368fca8bf2a2c2cff1cebcadcbe372e7e5e6defc2516db26b4acab0f89c42b4c245e79d689c07e8b57f1aa797e529fd283f2a03ca806b25a30916272f45d2b3219fa017c4c6262f508f898a404d25b1a9d46154078ebd1f6e07a7c5b6bf1ac8bd40687c122cd83070f4ed226393c3dd11c680eb44789e64095b6286ed58dc7be417af8e038ceb77aa952bd9cdf7a73e3512ee7148be094b96710d328348a101ae50907484f9f5659976ddb36aac481970dc41f9da494224d5a45bda98fcc6a6597df985c9232269b40091475587fb1ee599f45fa3bee469140629cd6985316939973ce1a3ee7ac010e99734e8f71ce399dcebbddd8cb3b8ac2ec98efa51c128dade0a994eaa8a7497d52a797db77945e4aa953cf38a74e51a8d36f5b6bd14c449a8f6953b45d0fbba8a35ebc4b29054149054af6b2a5309dd7b359e0841c2d7d487ddb8e24908d2a7192fb28d021456aa71b457aeab2988c0bff75799de577f33b67f442ea34a5b1a403251cc4a3b71e462c3e22177fc1905eef6da351e40e7a2477c8d05bb110c68a8519c3c4424925944c6f9d82425ae6c3facdb05938800f7dbcdd3e9779270d856963fde60c83490d000c07f0e18d22a59452bab45c4e1ae573e5160bd7dff094c9962dbb8b7e731a25c3dcd55137b21c451d139bbb5a8bdf3c5ff19bb7a6fce62c28bf398e62678f8f9b1e511f96c9335f48430738a051bed87514264dfe4c1bebe2e10042b7d9da2d479193ec25e6352de3893a366ffd4c5acf106d36b74df49bbb80e1008cf8cd5d60e840fcd6c36fde02c3fc8dc36fce0269141ae5a3512410a71b95a76055cf796ccea54e8ec739def390480e2239de497e12a0515c28128d229f6c7ae86ba96c55d3b657d6aaeb751b6bb75a2e5e5ab25aac164b65b5004d25b6206bd54f3576515d02f6aa56f92d65b45aacd51624e5d5f43ab09da20eeaf2ea73fab964c55aa7779aa7bbf75e6bafaef5b6569f6b0687d8cab5c742c9c5ac4b19eb1dfbb6da6aad6c67a96c95921f9fb7d53bb755ddd6732fd75b4fcf6e8f96baf5f87c9d5c44201bf0e9147f745edd866581eaadd5120365ac73a078c16ad5dd742c77afa5ea5955db2b793bab450269719cc7c2b6566fc5732e81086403720775ce2300b6aafc400ab4ad6f5bbaad923baae5e2a9a5da8004e2c9e8949f3c97df08fd9e042285e40e2a3d8fe3404f82568bdce179dbe6592df1693a4d2ea6171b18ce1eabc5d2212a448da048cc27aba54a2109445ec91d9473a19c21199b3c5187bc9240e60c7147755ac3d70fe5d5532a1ff6d157ea568bd562b5f4d292a5b25aac95d5d24f3692e0222b4422295c8c314e21e2183922f129851b31be184b1b309e17637cefc517637ce39c7346d97df1c5175f8c2fc618e31863c4d7898eb2fb627c31be185f8c2fc618cf7b3126c262a4b71e62a5b771d27befc5d389bef8e28b2fc617638c718c31c68b71cf79639438c61823bef5ed54df8b318e32d27be3ec7b319638d67b69df8b714b1ce7bde1ec7b31c631c618efc5184fdcf3da9de691d8e2f6b918d70b83d1400406f3863591a6a169e89da6a129cf51ad3e011f460f12752cf1a744d825844849cba287f71e4599f35b9058084bd34368d924a16593048e093794c3788442cf42994b86ecb7895c3d4808246cdb8cb48a3aa2e3b7f9107b687deb706cc80562ade35c059dbb9ed29c9cf6f8dc0c062bd081f14d884735eebb6d73fbf567cef7c7cb44aee0d69c1543ed49bb022b63fd3dbb23779f51a22eddebd839efdc73afe3f075224bdad55a9f934abbc9a49e72d364d3267536974d39274b86d55a2bf8d667079efa049f36b9dfebd839efdcf3cf3fafe3f04de3e27b1d3be79d771cbe2f60ce7da9f36ebe3f58afde279b765c7a964c5bd6d1872cbdc39ba619c3832c3d743bbf39a792bbbbdfebd839efbcbb97cb19c360d71d8af50846db3032ba58d3c9f6a48cf5f7ec7abc5eaf145a36333469c52fa548a594c2b4a9515463953ddfc63acaecd7697fce9691565668e42928a38eea76a3a9a179bdbc786d97caebf57a3de63acfeb60307799ece6a607172ba51075b880b997d22b5229612bb8ce030a5ba0def77a1dbd3e105eafd76bdbc0145eafd74b894a894a49e953388a54ac8fc5ba1bcbb2582c5665491008c6054addc84f23df5decd1c0c6c8d23bcc755eec59f771bbf8de9c61b09c96cd24d1759ce7c59e61a6ed62aeeb7286c1dc65320bb46a41ba0614391fcbbb3da3af2c239559ad1c5e8f170839707491a587352dfb31d67cace991e3c39ac75ce7791d0ce62e93dddce0b8ca2e8f7b998343c7e5429d9a640eeb654fd451854b6ebdbcbc3b699bc717829341ae6e5b5f09d3a9b33e118c7c7dfaa21f6b5a06254bef60bcf851c951535353b379bb6c9ab4cdc39aeb214d58f31d7a1fd670f7e29c2f0c269bbe73a28e968992a5c77cf4ee637d5ec7e1bbd94a674b04b0b2cdd55b1f4e6dc11aa9fdfa2205f7b280d2135c12e47c6d8079c03daba056ea9d04c3127ac84f0a3ee0f5432698631194524a5b8e60d26aab5b5badb51586ae251d07c78d0f68666cc8c06ac4c0bc5e5c5cb9c5fabc8ec377b395ce9651476f7921a607e9ee09767b0bd2487777d1c51a0fd2d17ab6d47a2da39452e9f945abadd6ca4ad19873cee92d4ceffaa3a7b7aca00814ccd7089545ba39e79c46a47440a45d9a5fb67e888c4930b42ea56fa01c22ef931cd2975b645b6bedf41ac3d2b6bbb9a11867b5bb5e4925832d62b51256abacd203efadd4da5ae92b9bf1ba5eb69e775385a5d55a4b69cd418cb296b653ba6d9df7d1edf3ba13de3a6737dff08dc907e3cc4229bd21e7b8aeb8ddddddeda4bbbbbb6ba28ee9323ba0b435f4d33bcf7bd59d5a8772e879af187ef34b699e1f8e577b9a7248bf6543b61e3f0c41fe40099a2ab904f4a5042b90ac3b1f724115e48268b8c8d2432e68e3827eba975f527ce1829e9ea7ad21286af5f2bdd5524a6bb538785d2fbf5e3ffcf0cd80f376b5bcf397984bcc158bb303436c1463b4e9baaecbdf3917a3f8040ec753aff7c2106185889b9ba720f59ac2e09b7204eb6a29a5b5b6c6b030181da2c986154c6ebc256ebcdbedf50bd3ace1e53af2a2b6565a698f1268d41b0e6fb1f084df30f6288d71b2f2973bf7aa7b20fd4af0c05330bc6e31772f77e517a98fc12116cce0bd9968c800f6c3b6ed503ba75c9a91b0945a5be7934a9fd4fac4ca3a850c8f4c926f8a8c44a69ea713305893cedb3176af671d389d652d8cda9bb73e70129dc3b8cc0586b29b9cc3b8e4c3e5e545c10abcc018e316b7487dc3ce71d8daaf758a090fd4cb39b77290e9f560303232f63a23b00d772fadd5565b431755500593a7af5d5f08abd3af045a020bbefa908b86124f589199e149123d5cc0884b946624aed7b289e3bdbe56eaddad7b82d036d3b308df836094a58720b48cd3c26071c9fa1c81c60f1e0d94e841cb860c0bd2af57ad95529a2755ca286928ad94562b5e40a568b251a5c9c6d2bdddb576753a29754fa85e6b06adfde1b45f2fd7182c944a034c8f0cb825ee4808ee885b827511af44010377c41dc1f4cc5caa3879696272e3e1805ae1b1f0dc750e0c37a2da7fc118755cbf4670ee16a36fe37c3b9f8274ce9c76baf92b53af965a6bab50bdd67584d65a6903b66cd95284a2803e2dcaa28957653755a185a79daf112feb2fd70bafebe5ea793f84e6d0e36527d3aede610a3e0f59f09b470984f3b005bf19f9cd88d0c8d7b88343c1d318776c6fbd283b4adafd8c08f2b74db200c762a44f2baed6523077922a45c1a28b352147f5b31ed5a12675a81e6d9e8f7c02ca1375a80e715455ba1fb2fcd961628701324471856a6707c88a8e364dc7083aa49f0294ca2afcd5b221c3685e2f9a1b7f589811397cbd6836f03e516b7dc168ac10618508cf9b69dcf626d7e569b331f172973930dc1ccbea41268ddbddc022d52d8882fa7516c5a750f6b5d64aa9adb5d64ae9a7642bb56e2b11d9d711c8cce8aeaded3226a5370dd25d0541ec947a8e3faa537b6db6aca5bf7a37d32f3fea902cd4eb65ef2615512fdf9ba97b43e4f0defc7abdeabfa28ee9ddd4eb7a19013b50c9f27a8581e379afcafabc18f66013167f5ca7b420207cf51c9443cf83d184b4cd64c160fd172412754c9ff1214ba7210b4fa9975f94ba76907e000184a73d542a0365ea2d81644f13b24702cd6a79c946839222d9237b8c50d95547306b91a0b22bcb60504a002b4458814531b28c52b16989d8f46196eaf12e35745734a1436c8a4df348cb864c3dc8a4b20b9bf4084bc2a44da589a7dfd4a884df47ebc8878fa732870a3dca44e450163da57149850bc8da751366ed822cb4508bd3c6eb6d6b22fdf3b473e2e7c31c64815acbd3663a2b067aef64a261a97ffa87bed0c51ad99d54f6b62bc2fa983777ae416e6b2d95a19285c95322b2a7d4db293802d90f797ad2c9ee5a52ec15e32e3642e72cefef011d58e539ef62f2fbae7ff76b79ee41a691af44e2d2b754905b5e0251c7ec3a69ade8944bb1212ac8a7f29cc719cf3d70e673cf39df7ce6034b90bd072a3de79d876ee4e3077260781d7b9036f215b45a66aa53af99a96009b2a720092d734fd61f0fea76add8b329fb763e63f1bff6eec642a982dcf22cefe227422ce6c5662c848d109fe579fc2640a9b3409653678121e7d22b0b9c21213ecb4788cf02673a1fc272eb1dcbadb3c06b9dca2fd6b967e4824dc3dc8c74359df4913f3d53e2457951dd5c775bd1b499de604804d63bde1328852451cb627208365d164d9f376f34c81e94e741d120b7477a7b09b79da18b72851e2d9355566a69f3b8913b240f3047c75cd6b2e92f180f89801838ab65caed95d65833e9d0a5d57a2c1668371f60d8f2add5b276b32fbeb57a78d8e337fb7561fed92cd8160c8b15d26f65bf9140ace79685b12d90fb3c7c01b3bf84f45b2c976fded9180fb983fbb6a43ce48b521b521ef251e121a407c7b13acf657697160fb9a3c542cf5aa08f96715cc7f294c81d0bde7cb10f87033997b1aec35ca43129cb8f3c7c608cb7291b120fefc1618a7970e00da6b83fb4200f6ae4457a38e8624d387d7ea4129fef5a666759bbb1ac14c62dcbb2ce62f986ad67cf0e7179febafb72cc62b158d83d2a17560bb3a44b2c6ce187b0fce5af968b63d0e57e9e756d2dec36b08db081a6cd7c71d95a2f508529bd81ae0b18e69c63e1e7f2ce7a9b14adf3d3bbcddba4e4219f57e9dafcdb2e6b76d6bd96bb629f7bb10c862db07fae5f9f317ef7f4cc736b31e83d7d3115e6160fbc6ebb7b7d929ae8bc777b6c52f49849d0936a99942004092102afbc29e51096a5dda464f916043d28b963e3a8270d3d7dd8403fc3a9a48592869a7e7a2ff1a02ee83dc91dd43bed1f0cb57f50d4fec151cfa4d5b77fa0f481920f967cfb0739ed1fecb47fd0d3fec10f4d1a11c8a5e1d240b3e5db69aee20fff769aa9761a241a2a344c344e4d8b4734514d43e4218dd1b7d304e5f0ed3442486aed343e344a68967c3b4dcf8c154d0ecd4ed4d4b7cf28b5cf2c35f54c163d11cd187dfbcc4ffb0c50fb4c50fbccd00c0e11c88605cb4b0f6774f8f6199f199d199e6fb75165e3ca86d4a46d529b940daa6fb7e16423ca86d2a46d56563696ac6c347dbb8d23a06fb781644388e8db6df4b4dbf869b701d433e9f3ed3682ae6ce87cbbcc50bb4c51bbcc51cf640cbe5d46a967f5db65a8f40c7bbbcc52cf386f976192796a97916a97a16a97b192312202d9a06c506490f87619a1f8e3c6b7cb289159f2ed32399326736476a64c8fcc0facaa691b12d2d5b7c39e266d5bda966052db128ceadb614d4538f50c2351f97658503b6ca8a867b28723980f4cc9b7d7b06a87e5b4c376da613d5135a6bebd86527b8da5f61a4d3dabf154434a04b201b10151438a6faf81147fc0bebd06510da36fafc153c3a786921a4b6a08c5584dda16b405d5c8d9826aec7c7b0c15d1b7c754c538c544c54c7d7b4c53cc518c52ccd2b7c708c510c5187d7b4c504c4fcc4fcc558c4e0ccfb7c358c13cc148c1507d3b0c1711c876643b02e3c5b7c338c51f35be1d860a0cd3b7c30cc110c114c11cc128c12c69daa6d33a42df0ed333695bcfd603f3b3f5c0007d3bccce936f87e17955bdae6074befd45f56a7a3dbda4befd85f4a2f262faf6d7d12be835f42afaf697cf4bc96bc9b7bf7a5eac5e39af9d6f7fd1410462ad587939e3db5faae28f986f7f896a7f617a717a519a34db349b96befdc5e805e92568d2ac94d4cb905411144b7ab67dfb0bcf8bcfb7bb50b5bb58b5bfe4f4ec65c729eadb5d8eda5d94967a26a73409b9107dbb4b4fbbcb4fbb0b50bb4b90cb0c11884d2209971abedd8527fe80f976d7958bceb7bb9e5c512e291795cbcac5d4344bd436d2899cbedd753469f6e8c8a574e45afa7657519251cf5cdfee5ae212fa76d74ebbaba7ddf5d33389c3b7bb80aaaebe3d37b5e727a99e491e5021652adf9e83daf350cb20be3d1f652022108b83c5210bf1ed7949fcf1faf6ec93a75a55adabac93795a4d93667fec4febc9feb4a4bebdb594c5b7b7987ac67d7bcba885d42a6a01b5825a3c1eb67c3c6c29f9f6d60e8b8a65d5caf97696132b8a35f5edac26d6114be99bb5f4ed2c2722909a461a2c28be9d65147fb4be9d25c422fa76964ed3640e0fcb87f5c30262057d549366736cce6715b2a4ae4cf58c32397dfb57d4fe1d29f5ac6158fa967c42dffeedb47f3deddf4fcf3ea02aefeadbbda676efa9dd936af7a83c2b1148d5420b2f8b6ff798e28feedb3d248fcab77b41de9057e41d794a9a569d9cbc25dfeeed4c5a9592f27aa4bc9f6ff772b07cbba7d333976fefa63cecaabebbab6fefa4ba250fbba6efcea843faf60ea8bd0b6aef86dabba28ea769b42aec7c3cec947c7bb7c3517156cee12002a9405420c6988a3fb86fe79c9a2677b8a86fe7a8342d1e71dcd1a4d5a019f492236a5a25ea4a64f4ed1cd0a4d5a3206ee8dbb99f33be75389e6fc752ed98aa1d5bb573394c4dbb4fd8e9db71513b3e52ea99fc62e93e51f049e8dbf14e3bee6989c4b76320fc420442af5c89e1dbb14efc51d534b9d37ea9344de65ca6ebd437aa67fded57ea4e35ad8d9a46a360340ae9dbef50cf6292185bbefd12cd506bbf4a9a3699ee5d123399aed0b7df9f9b3369536ada488701c3bbf32dd533d5b4b9e4359754bd8073c976f5ed9bd4b6b4357d6f4f464da3552e607891de05566d54be7d2bda8026ad067d833b43dfbef51081d017e80b3e7cfba624fe984da33b3b9b8fd3d3a47db5762b3569b7d66ea9acd596b3edf46c69d2e8cfb44ddf6e9578bedd52e9d9f7ed96a86934880659231a6491bedd0ed99f49bb5497ca025daaa06fb73a4da346d4c8f25023ebf3ed36a74a4ddaa4fa59f4d22b53d3e852dbc825a76faf45edf5a8976a9308a48ff49125bebd12c51f4b9ad63a3a42df5edb2b4fed99f5a702d1aba6b54ffb549d6fa75293d640405440d4eadbe9d30edf984ad39a888889c8496968d25a49a948a99d2a695a4735fde959cea4b5d5b4917e77ac7eaa69d367fa547dfb5c6a9f4dedf3a967b207b34a042277883be40e567cfba4127fc8a749254a5eb64fa039836aedb368fa344d0ab58d742125df3e73a6f49d6f6fab1f7ed6fa964bd25ba9bd97dabba9bd9f9aa869f2a96da43fb5f74f7b03b577507b0ff56c3ecfb7b74ed32415cc17df2eb14420307187f486b58d7498d3b74ba97649d52eadda3ba76751f10752d3e21195a6c55abb2c9a34d994b5c7a6f508fa9934216d23bde9a54d9a4aa23d024d43a186b6c209ed28b4a7d0bec24dbb8faec1b793d0dd0e93112387b7088a94bff4efbdd7bde6520165e83d98f7dcfb70bceec3b93348e4f0fabcf499237278815e7a8d2272b849b552cbaedaca8a1cb6d24b977eab7a4677e20fc9f3d3b338658ca8978e95f42ce6e0a56320c74b9ad635e978a867710a15162f1d1349c746f14a42711fcead31440e37ab972e8325871bd24b9789cae1b6f4d265b8c8e11624050c38fc768126673005d1c393319a6a9b18667c71e585d0d316285eb349f1c2187fafe30fe76e2ee470d379e9f8480eb71ea71cda261be4d04a154df1d6e9cdd661d6bd465664c46f4e7bf601ce7683e38c1c5aa2978ea32a87f6e8a5e3f82287f6e7a511d77327f93a935111cac15ba7f7c3a93737ae72c8322287d5e9a5b3847258a5a8c8610d7ae9784a0e2bd14bc75272588f5e7afc79faf4940a2aac1e004f7f26adadda46e69e452a8e98f2d443685a57a3be81134440a448fc219f02bdf408f6d513396caba8b7e0b56a99fc4b55e551c4a71f5eaa97de5633c78b187e3abdd3f374d874973fa7e8e2ca53a73d13c0d32953450edbe7a5cb54c96103bd7496510ea5d04b673dc9a12c7ae9324c64992572d8e3a5cb34c94b1fa4314c666007f74a0ea3140a37cf6e89946553ea0dd69a28af82dc2243b61ffac757cbe4962f818c3a9a0c2432d250cad25db6909293cad0a93246942c9d7e4c0a638628b274d7c72432a6dc7bc520dd82c122279591e4a3049864a944961e93c8d28d488942279b3a76b707991da3145df7ca21bbc92193525229698c51ca08e628398259fe20dbb30c728d85377a06cad183cc4869cb66552919f67adda4fc80fe0f8020470938ae945cd3b2e817ca8d4229e82dc377b39ec762cd98f0d5acb33e942dd4aec4c9d1b2ecd17e2feaf08b9423e8d14a29add6d6f808fd345147f47b4308d2b2f820441dd163c4e93142ff4db7b142233ff300fc2ac82dfd210b2f4158d411bde59a22773efc954108f2f2217738d66e1fceed29657b95543efe70bc9c2587de739dd7fa703c168b2539bb553a7bdb64e458b10692cecb5fe4f0caf80223c6cedda0d7b2de5834ea882f3072cbbc06e50e5d943d69b5ad6ec916417a6f57213fd296132e1f93c2b061e663521954f7957b16762c7b34b096fd7d229190a1e231c32456e308870c1859fabc81e329a67a38b6c0e080a185afe062e4929073b2a2482a1345de94e8b8fa514e974e2985cdd8cc74fcdf77924a19fbfafdb23f13b2fc192b41cad84c09f2bdc72f6335a264193303e9d67c04bd037cc7c55ebeeb3b96276325a85fa4563a67b76c455cbfe33c19c31103c62287b7fb3220bbcf9314bcd326ca58975b26a4bd83b42c86f43d22de47c0c81bf9d8b1bc194610f61d25e2c54a409f08cc6e21ede2dd62c70a8dfc8d3aa2dfef62273b2ee2385b7eac16a491d9f6aaa33ebd8bdefeb32786c2a47db4993e85ea8aa8c5cf583b53feb44ba3238904c6123f6f8d86c2b46997404d5ba16ddaad82248fcf0f0b4d23612ae908e9e92608f97e82f1d345d03458fb5cf2d16e73c4f0d36f9ab6d568cf72e6ccb122e829084fbd011f0e359a3c6dd32ef4110758e860072e4ca5f18314351d2857961062071aa234a9796f851239f5e104facb4334458d926ed01949224415398011054a1329e295d50d4a5b987c78b0d245c87b04735582e080e0e413ab0a25a62a43bff96b7358153bbf39cde60cf870362a553c759a44f514861a4dd262450a2ab050eae147a94910a5349e7a08244cb9e2a947a002530cf1d44da02e648aaba7be8113a8b0f2d445800215239e3a919ec59efa023e1c4ad5cfc9508619503c602a9a418d1b63082d84c88842ca8e548d23222907cf39081f0ee749481e3bc57e6f78ec1e8e8287c7533c768f02cb63af912135f1d87360bf9152c363ef811d04294a1e6329663caef1d8817c38f87abe0ebb51d0f0d77f7c38b7a99aca0b20fe65f62fae800fe7c58b1a625ee881d318554e34f1d94113379022891b5cf922ca10b40b2921ba38b2802e9e20c053f7f1e150a77d38984a57a6af574e50404270504e1ff38d5d7879d95e7107f21e81b981120fe3303efb70606e023e9cebaf32fee53d3e9cd745c087b3e9f8172824fec5797c382f18638c31c618fb013e1c8cc5952a9ef39b0f87bbd4334df294524ae995299efa8e0f870ab5d29314f7554e1aadc91aad75bc2e2bd0cb5ff9e1dba955cfe253533b5d6aa74a576408ea59fca08f5c28b962f5c219405419a2c671e1e4852e826042c411b5f65935a9ca68ea5914e27bd084141c8081058d1882a8f1200c309a18d2c14e0f64506b25d6001fcef5cdefe67973d876c505bfb98e0f67c318638c31c6d8737c3898d2272cf3a9cbfc86c713d4532f4050d0139377f17771027c382e433c05c1c30b2d378fdde6c3c197524abd18e3a9cb3e1cda3bbdf33ebccb5d3e800fc7e5a48917c0481f24dd9ef0c4aeacd88288a61c7830b250ac9091e48c1fa024416c7ee4ce26b2fc751a44edba9791f8ebaf9ed1feba003e9c0bdbfc6e9e37876d4d04fde601f8703629453d4429f2a2865a510e846a3078ea3d7ad683892a9e7acd87437d60bfd83306e271d10e8f1d001f0e964af2d9b3831f4e7e72d59247f2c89329783cf6d8878329a594ba7f3854e9d240e95b4e5b8ee3c3690d4103a2d719535e4092014913326861822974b0d4c0c90c535c5832b10b28a6010c9e3a0c6a942ee93cd5e2e912174fafb8e2081a6071c4072c80aec0820c9ba77ee3c3a1205002d292e34316533a0d382289093292400db1440dcfe0b1bbd563ec32ec37d87d600f82c6630fa167dc6327017b05b00be919021e5b51c663ffe0c3c14f41417f24e5594ecf729a0f8725f4c4122d013481e28c3064b802a2880018f1a013c553103140e207b961fac4134b063d391c14525e64904243cf114a8088410641a26468523503215c7001e8b28e9c7c7bf5e959eb23152e7868b283832331aa4c5a8c1a0309d3969c245744d5da6b50cfda876faf433deb9e51cf2d6faf484f93168fa0b8f22da72d9ff9705a5070f1d46f3a0636a215cfd2f22c97f97058b7072570b041930e39e43083da9dc1ea0c18a2506125882cb51b83bf39f8ebb00fe7da78ce63d2735ee3c3e12e0f358c3d3bf1e4b1bfb0c364788c9f50f2d863fc6e9eb7199c50e23787f970b69d9dd7f29fcd7ffefa703ea32b302e621461460c37706207a824504075e14393145433740fd4b1348af2d869cf703cf6970f07dffbd42ff5dc8591134fbd4646fd86ba0f6ad4e4a9bb7c38140529a933de3b7acf5d1f8e97a5882baff2955fa0f1c26ae2c80daaaef0028d24583cc002680b22a11844d14a8e2895cae28a573af8f085142d5090583103634707e8081ed058e2e1699b524efce6db2635c36f9be70f67830d51cbc2e42994a759aa9e7e71c5536f7d38d431c6ce0240969dbffe9180c5e83ba79d7b1f4e671485c4b60027189058628a8b221d8c141551994207264c39408241de6494159f3ca23e3da37ee4f62c266141a24a9366a448cb0bb4f831028b2daad4dae9d2a4418009a3327a70c11136b052eb1da4674a45abba67d26ea29c9e5b7acebb0f87bb936682186cb0ba32c3ea0569d4b0e7492b01103a5881c2a10929a2a8e19fc74f901e3bf7e16099bf4e6e7d4a819d1b34018413508ee050a3467cb04c7185104788629054a33a40d18443bde6b7a824b226a5af4eab5f2d70f8eade142dbaf8cac5d72d9e7cb5c1571be4f0b5561bcce06bb5010fbe7a08d54970c2e2ab4780885a75137a96e474f5d537d033fcd54fe8594ce2e285af2e829e7d38556a88a626be7d2e9941516922f5acba04cac9c971819bb49cc5d453a7d46f94a755acd4f0d45f4abc88a75e936465cb53cf31c3d3a6a41a6079ea21d0a42c9c78ea11e8d98ea74b3ffbbed65add9ff8ea35b249933995b14411395eb8808a5a0d622a071b78b1c305d40fb5ea3e7ad6397c654ae26b9d6a9b7e2dde002e27399c40df1ed62918fcec7b3484ae989aaa02638baa38258494284d96d83046168dc3b7cfa34993394a9376b3d433eab4596b9f5451aaf8f65935ad262d4a71d2c1053c282a632a49cd48500f530dc4d00107a6266add332990acd13c8a103f8025d9889ff234098a1658d3db1c0dc4a08b93362382045245ce526d47534f4f2dfa9d31613a750ab22093a07215549b60eed88c09200b9407266a2d7180a18aa35ac7280f4cd42eec86e9f2a333e04995a3da8c793bd43a196394b2b262ad633bb2889253eb5a3eab30028b9c5aa7815443512dfa9549726a3651d4a2470924d66e5c7122f2a56ab904d21fa948f1454e2d5aa945cf1e7fccc819098a50dba46a3128d9258572a9fbc768d35eab50cbe2db2453c2b8f25d8d2c47adb6da5ad3b311daa9a6d0ddddd6d6158ab44c76d76a46d117e9af4d698cb62c24e27d8e9a962591a787353fe78df13595670eba5853ddc6c7b7608cd443978fde9156c1186da45330471be99d0d1b34b46c21a5a80acae886ce0d378cc12489283a5f4c2194e4871e984195858f1b8e1c31c3851ebc90610a185f08e548f990a40aa0266520d183177410b5658c30620043135049f0e0604487862655543cc164658303a00889a183209838c34aec420a2a67a4b1058727292a78dac2278b2124183829b1e2c91254605cb133840d31c6b8210c1728098222eb144dd1b49304930d8cbc50c3174c4831a45425ca8e23848e48c202092b3f18d881628a55134c55746024c9942a8ed04116558e7cc8e24a891d2825d2809a410046425396703245185f4432c4c881892b1e7488812a0419969000d2020616313a2457ecc00a26aaa2a4bc5c11a58a9325769e98a84d8ff9841f1cb858424616a6206ad36fa4a5e08b1c6e58c2c244cf556dcaae56ba5813e2a397562fa3f792a65daab67199911cce00ba410f40dc8006216a3998ceb862450a2fa830a25ff0d1499834234f1c71460f64c410c51560d4a25760d262193a4d434f40604145935abce12394262e38559144091053b568e409149395a8234f4652b568c3475fa1672e4fe3a4d59c2da2dc00c45303941a01b0285581060cb0fcf0548b3d1f47e0e387ccee699e9e6d3badd3395dc3479754932600a0232eb88003932ea46ad1a5d4a4d5301d81852a0719102da945974f3d6b1a3e1a497246174a8cd1e40566ecd4a2cba5494b210cab1a8c91831fa6e0412d76928f2e8fa2cba2e87228ba0c8a2e8162cff0d165cfa47d5138f144c90ec8b882a5164778128484961824f1a0442d7aff00f5cce523759a9a1c3ea883d0b4568d7a0811304108f50d8880489115a8b3d0b44f87c7e7670bed5b224454748651d32e1215a6262c4e4d73a9519751724a56595197574da33a3c3e3f5928691ab74488a828c9a8692e242a4c4dd4dba9a37aaaab7a6645bdaf9a4675787c7e8894346d5b22445444311215a626ead36946cda95935ada8cfaba6713a3c3e3fd4ab51a74b9a766bd4a95053225a449d1af58cf5d497fefecb6c75efd1d36f7df4e852fdd26da2e121539f4b2ffd3af52cb4bed433a69e4df0565daa9629c130f4d4e9cd4e5d462d78abba232e95158e327278a9389daffe7d38b5bd4a04729b34712bbedda9c41f3cbedd8d1ce9dbddc795f812177222cf99b45bb4f3d77bbe1d8755cf62521857573d63454d7d3b0ea5a5a69e492a4f4446df8ee3a71d07503b8ea09eb50b8670e8e0e0f9f61b52ed37a8da6f58f50c47ce0d2c11c8b5e1da70e38b6fbf11157fc4befd06d30da76fbf5174c3e8c6d10da51b4b37849a767d7c88befdc6cfa45da00b7403e802dd08faf61b3d48befd864fcfbcab1b3adffec153fb0752543d935b585161faa356a894cbd7eba294858ad10820000000f315000030100e0684a2a1304cd420a81f140010779c5260481749e56190e4300c19648c21c60043000400448466b60900d60d667591b1acf28e8bfb18818873696479b43cdd9472af7868a072b81591c741d4ec14518f1c794428da7dec44d76ce48d3126050b727802969d3b6d58e43ba7f2410cb4a2ea2fd27257427ee36b69acbeaad06a599b742b093dd89710e93a10613766e91103d42927024cd6849a608563bd58ef726c337cdb7e1e9b17ade009901c22ee88d48674dc5769cb20d42caff287181715d4a74921136d79ad24fa73c82d61e148c3214f78e8bdd9f0f0e0b1f4011263068b445e18b23adc9894e3a3a1ec7bbbf8bb36e77557adb9607a9297b16a4060ca2b843a407020b18551d67fbdfa490a3b14c228042034792ce97c8d9ff3e40e9ba62468068444f04a55d0fa6760d53f133b9e382cba5b54184f55aa004b23913701dea3164830a06ae0ed5abbb9fca9258d892e4d8306c680258700f71e89cc3cd96f49c1b84f63887a9d47fbfedd5149ccd3fccce65a3d0a6e8a635b296af92913c590b9605c906be0987e989b7c7327d03d31aa9043752c632fcaf26b6eec4dccd7ec444b2e084967add4c6c1597e170f8b8722141d64f165b7c33c367bb8399f127a418f9dc16d70c34b2d28a46330c05157c1b438413da0869b4307464ae72398a59ae93f4e779a0aa285c64071d06e2a426c65e649f8c571ae9ca90d9a6aa7b324f364b8702255ec6984727f785a4792a631555928c424e78ac5e70081e8b2215ec9b2353d1f6dedc1b8008178a8147cdf5b846a356ed41473e8217ca779ced34caf85cceae1af41fc9ad5d84da1bb19df35ed50d6d5be0ed5f3bc5b8b6a1724b6a45156995bfed8fa3aa49786db89deca5396d2e65a0e2e924577b8edef3021b61316eda96a2c33dc49b2db985ec539b344f6f5b233b90454a2ee61a4fae7c8fb1709416625792ae77566a76eccc53323397190446ca1e1e19b1c6ba6af496131b10d9e07958e77596c45f129939cacd19c794bcddddb9666bc042b8388b16169182ca60b90391b117a91aaac9e11a8e8094094c75f3fd25507d7e14fc9458df8cb38b23da495b9896c2657284c0e548b873eab045c7a972f2693145794598dc1ae07846257d39fd5745a617674f63b0a406075f2e7d5e4216de5b1e6db062097287d8390467c65432a1d019e226dcc93e90afccbcac9757f0ba54e42802967b5c05d53f33be055eb8ac514af48267b219952acbaffb32224a34cefcae466208d5bac8c754fcff0a3ad3b0658a7a47a78497eb390178d7ce14840442a28af739c0613cdb067d80e6dcf1079cad4b0f243ab0e2e5b686de1f3e986002b504b2e5aec77e0202ae7750b8dcdc40e22506f0faee97f6ad7f0c79586d2e8c902df0fb162c239442ef07a27e8e8b740ac2a2f3f2716879ec314f2d35fd54d95bd4763296df7e8792a9ec139c074ef4d6c486073c3ab6915c84d5c7db131552859e8790ba5f46ac5cc44954e2efb467a7d7db80c6c1b9c0f5ffac293661fdbeadcd85daf4e9dbea8549eb01213f5b2e95c073c886d27012cf6ed704a01aa81d275e0d12ca7b17dee04b4547d52e050be2280add1587284145bc9514eb04c682180d2178cc4c402e2122af49f81424e27badb6e4159b6329cece2669c841caec014a78387025d9b5f8fd3e6d2a48523a47e611e66936c2ecfedfea315ad0d10715151f4c40459860de5ed589afd21dc23961301b4f21bd40d5cf550e94d563869145b68eef815acc57c28510b04ce6521343e99a9c39311672e159ab66ba3b5adb57e0380c9c5e14d87162fd475a6798d82b05c148941cac139135af26ef33b5186afaa3fc03f3ea30a25a9adb2c64a0b4c8091c08ade2834a8e38d8abd8522b1ecb445f16da81152c88526e0548472dd65aeaba0ad6bc591b03ed6619b1c41a9a45bc3a2f0b12405f4f6d04559fa738cfdb856eafc8689ee10b910c2a68f8612db9a04905718eb962e99fc7eca2b51bf3bbd374f39a700d2897bc3a3ba2e7c4198587c7b48468b2e8937570e1f952a5d9740f482c8247d91c299f7cb3274f766a114bd313587387135fb25d42c5d47806b6ab304e23121088519d371e62dd8a1c6802b66862b517a1ef871eabc96ef7535a391ce28872a3afaf54b55f2581b1c440b420511638b72bcbefad3061bc43cd8e947f7e3b8bdc61d23ca151d371c956ee057d9d5c4d9e89ac6bce203a236a43efe96a4b86d91ec74e780e942fab819eace23aea1ef0c341782442cb71b652297d675faea732e61024c07ea6681083ed407435fe66c3eb808eefd6818ac607491438d13e5c9400d24236fe838d55475c500d93417806bfc204947b07bb6f4198ab26ae9861e340871b32fcad11ae0be7282ea895af770f26b93d5c18fcc704aba5973be7320c1c3c519ec848ece50ae46eb8e5acbee20c114d67ed658b5e2eb7fd194395771480e031eeffc3034b869f6a3d33e11f3a008b7982f907b768659f920b985cd5367badad6e750756ba8c3868ee998ba3dba07defdfe2ec76a689f12451218de4abd494dec3eaacc887c9d8e446aecf2132f2564f3da145fa4964484968b62199ddd06d1900c66947d8d369b4a30966181fe27a0cfbf175c36f0954db8476d97148b37fb82d7bbf6f4ee18bfa506b85725a4c0b9fb7d0051d466caa7aca34f101048378af876305f7fa7409a9636ffa1dd20a5b353b54778309523ed1e448606a18cc1fe3a1f7d0bfe9ff83b34c3c9d26dddc052e8fe98c6c2d58ffabd0b466c4059481805895968f953456c1212461c4ec98b91a444bf760694f56f7728f8bac0697917956d15d03745355ffb0510ea4b627af923008350d8fee8fef30b6a43131e7f8ed1e9fd6f174364ed4109711cadf49ad8f4bbda127b5b75d5944a4e7c615262c8d691eda0985bbb572a126609e9eb45b24b9396219749125249bb63c68ae1612fc0348764a4ec9d066e1c3634212cf2a7364e015ab76a2087edc42ed9f07b658516c73432d11e7b65b2092c9e1f633040f8003086ff98b6b64e71dc4084b312f59af907c7418b827d71d7039619b31f8b2a15680118f002244b33e75c423fde2017533f07dde82940a733b9140a24aa649cd02098e61cad5660bd6247059d4ca54123c4c520c3bb58433e43f48b6e8bfb5118238277f886d09eea14af334ce781f643ba8a755c6f74772f16714ac76c4f8123c41c601bb4dac275678fe6a6c93930d1246ca103ce189a2d1f81ffec9e471bf6cf9fe54e501bd87e2e831d68f911e6de127d9c2bc2b2056f6e727466db58e0ba147d9a7f7141a524f27b8c2e8659257eac67ae5b8c1deeb57e8310e72d29c686fbf42190aebe34a97cdcad42b071a6c199736eaf5f692a095f0cb0958c77a92e2803163de65547453d58a7f6e13bba7b82d655184724af2120c8d09f07da3eb0aeac7af9d471f0bd11d5cf1936dc3caf3cc0be787284ff8ce94c05c7c0b8cc88c75dc196b9456648ecb40938f2f0b4cddb451471ed53f2547c76a1e44f2405030312a34aaf660577abc5cb4db566cc3c4884f7ebd71d39e62a22c6736ed906e44d986d8e4426e123fce60c174bf2425398f8f1c2740c590478e060f1f740c85a315be26e8363d4d59c109a40849047f0df445f3d932bab7b2bc13453f64144c5bb05c62fad9757dcad76729edeae954ccf772d00603ae24a7a678216a670d17ce14b32ca089c5c540d9dcfa31087f03115a483cc7701062cff4d225b947b0c872e46836e6b9a49f490a111f87d53590acc83cc957a84e3814a6eccc0bb3b9b970ac79b1d93fcb2984e1a0bf9f9c00ca20dd2fde55af48bfe4e59c3cfe1f5ea1168cd17d9e6f8cac1bf3a699818d64de79401f1b1f6e3f16052b9a8ccb769acab6cd71fbb835f111293602e968de4b15aaabc0c6507789e80dac7ff0b35fc9b6643f8a521f63ee021048f1493d72d545fb36463e61c77cdea53b20b43ab079006cbbc3ea751e0c169cc2bf5c6693545b010e70176d8143fc35fc0b3214eaccf853e0f5e5d3d811178da9077a3f492ddfcae4b88e682b66b2f313b4b12eefe7124c64c875e5e248af9c390f45764044c9e5e4ac088e38e016a088a2820e7bd16bbab1a7ed6d06b309e5a8de17343167b7ec44aa7f484b32d830337e5b40ab8cc27ad0948d479571fb0c91d0ff06a3c003c8c59166c63a35d1c6e9ab6ef43412d8ea75632d0002a5c4e1aaac52d8bc50026de9f02000261b1890d7a9635205d76bca03011273f93338bb6231e1679aac3e29f2a1ac7dbe9702d50995c2ef2272ff2b962ceab8868bbfc3b32526e339908aa7cf5631fa085ccf2c92ac4f699066dd75608f81b49648af906f728299c4206578cc8af1558be56743f7ff64659a0ed5eaef6d4d0da86bdea0577b74a72dc799f9baa292c39aab0e377ea2c0f0e59337030b78e0d3ad82317a2ad7a2632a93a5b92aabf0c45af542688569caaf18d2fd504e67736aa98ff57d213dc54c1c608e3b0c4719e4f173f7b87f77723cf09ab62c5b97989c2bdfa52bc316b47085632364201ce033f3fbb48db6791734ca0232d60398c47ad8e0d8858b37df020a178f0b600880075616bbe1258848a73f312857bf5155be1fde76f53e39ffbf48ce72be1bf7934114de5bc50f982d2f48dcf0f11a1078cf38ba0ceff0a0e654d3ae4b352efa60e85f2f14c4404db996c07e6ff5fd4b5b484b01b0fd5e13aa66ebdc4ad1e39ec935884110e9384088e514370fc59f47ecf9e9eb7c477c2507ef304b30f33196875405d1c89e974784623e5a2b8e0898f3950efb13961e31ef63d6b1dd96dcdd8b497186bc69d52500d3cdb50bc9a0ad38c54f410a92aa6445cce1ea7fd32790e7ffa6a44327e145b8bd447195e934089997605941a1c0f42d9b2c275956eac7c6033e3f667908440ae90260f3fff5eb3e4bd3f8107928f17600b7b57e7e56de9d391a87ef137ee2bbb48f0f297313e44a0eb3a4e0352a489102eb19cf2a71fbfaa991dfb7ed8c1c44de2ebc84f2dedd053931a90a72918108774b65e8ca708665238d28714e3f690a2d0f29493e080bf8fee3369c3e6bff3f8fdc6c325a124ba0e885b950f19245b30692acb241a79c2c76dbb28f79c53b42570996d52aa303bdd146673cdda0204b8e4cb5ad1b2bd296ef2f133cc78eaed51478a07cb9b7ae60d14289e30b7c36059d0ffe5ddb9bbc98193bff8321f90e69199ebdc31a3063c171d79a8d389a3fee3df15355b2099213f356ffbe94de7afd327e53a3df3a7a4b62c273a0d8965076ffae9cca6cd5553290b7a676d51d050177072251a26064bcf615865dad213227878890babf694243b265547c03f0115161f14f751de4261fbdf0d609d86705f7d8e5c40c16c390519d4edc16174f3d04b2b3851356f17179e53a5343b114bc1e62083b5b3f5bb9a699fa1a6daf6698bbffec275f07348728c4410b5fa1da50674a8d6711154dcb486c10ccda60b13851105811b1163f5864f0192c802e8b6b314ad1a672200c087b7faa124dc09dde6cf7339f6c24796624ebee0da08111368bbc147e8953868f110694330ed4d29a87205aa766f6a6a5ec4a8a46a7ee07e384d24c2e6e2a729d833ceb8a569556074669b378bd999521892d4221939a386cb18969b2c53a0cad065c56c57712bd09893a7a95177e4740a62f3ef1ebf1402bd0d7c174e89a59d08a039a460fc257b5ff8aeaa1c3525196ebc6f21a846f1acbe857a290b71a95fe7f4029e5cad1d1a79bad9cba2b7ede8845662c8ef1f962cc135aa89438fa862b875b2fc61be3666911e56ae2d67eef833f8bae87b6591c919e06fc78ea2db0994cb131e1346d8c1d346ff651164df67fc705da872a8b6029b9ce3cc67a9f23e4c1fc5d2b4f6d76afc393de3c72f34636302cd38de54a0490a5ccf59557129fdcaf80dfcfa26047e3595fa37c2df1c572792491579edfa0bdf5d1727d548c1e2c0d541396c659cdcf2294ece3b80382c97fefb427cff10826d8e08d6f713e4da133e4a7bec547ca2047353d8b45f07f80828bbc9f6074fa5304e4df578622419091db6d2b4e2852ec3f47e421dc8a545a91dad43ba48d6503060ba453989e1e3745f88ea44d1440e6e2465a795f21fc5769884149e9c8fbec937896d7d4bc838af33945a1a8c333fa8d2646bf746de4b73629be60c1f606cfaf83b755d1be618668e1e9089f354a3811e101a90eae8af20eed0f8c52f76ff5b7d2b77cfdd8f4092d191426042c8c98ca22fdd57ff8b521c82b5bddd8efd92b40fb579a569a8f1dc3b4d63e2a4b7b4bb7dd2594ca2ac1c90af314cd8ca29c1f851cd1d0c90ffd21d4e25db5e1cc75f232ac28b587c3f53dbbed685532825cafc18586a074203e8331d867ebc8e495eeade0688a7275d0b64d11656904214709a7c4941f328485005d3a9c54885d05df20c9b55789f7d8241cd553763b0232fc75bfbafd0c98fd5ebea5c3d7808361275f4c1f328f59de3567d15a49916cbc7a3959511be0b84ad0b170bcedc041756baa5b0cba5dcb30871bcce99b4b564c3039558bc3db25615bc822e0738299d0d0709d13a732a1c02124290b26cef6bbbcab4b45843b9bca2e0f2d624d8f2b5bbb1cc710b43430235cc0ea2b2ea915794a07e43710fbb9f7b52c0d0b403382539ccd8a086a4cf89d433bdfc75328f0525a933934b5cf0c4aabbd1f475b62b9543fbadc9527ca6b71379824f8891324a2c46dadaaac7445081f68ec3d350422d38f5986a04db4b0c542025b3b845e8aa0b80175af860d21ddcbb00e57c6476eee4f8ff8880a0bcdbf3611c04c65cc895063b4f9659801de1a84233e40f68eaba98aa80f9bee83560b30032f9fb0e09dbbe8ed6793f0d01a16adb8e9d36dec7d826a8489252696cbc5254876e8fc8181d81ecdf4d6bad740f12d2400f62abf9bd32ba3ba638fa8ddde5c249efb84eadf4279df5c0882dfd62c382be0c4901e0707727a8b34e02492352ec1120fd8c4ccbd746e9642dc0331a58e4b0c90a62f5154be817b7a727d8cc60970e77e0b6d956b0d105dd4d2ab1ed6d56f3122d942fd92078316d42211fc49604dacace1148833b3cb8bb25126f7bde7edbecf02e7a2cd6a034a8d3fb57ab3360f4d8bb7c61504479af62d8836acecdd3898d505408b981205dcf3a07677986ac2c7ae59ca0a9c473035c0e11f7b00ae9fba1dfc4f189dbcbb47893c89b510d71f2e8f3a710af18f3f783377a4534d6f267392411e007c016d3443ec763709eff3dd067b09ddc39bc9be059d8c56ce19893f3c30cc11114a1ecf7c20514a88b88fee5340d38a0b9d63f09c8f8356c28fb3ef8627fba79446afa195335a89f43e3226be2cfeceb1c3cd90a91356ec0312555a5aa123cb5e98f8f4d001824ea5ad0c142400e0bba4a884efedad961bbad10414612b5c201e28a63b077cf2c1d24ff14c3511aa88ca7b0dd115530e62d8dbc3fc484115d3e22d02b627f487d0c5925b9554c4618a164877888735deadb5ab35d910aab83c151a9d29f9f97ee5193de085a3c26d0a70d8e99524128dc80e25ad15390a828d12d81402b144b48137172e2ec99a188862d06b0b35980bbb653d6dd129f8384ec019db0bc838c75fdb21252b3bde17f1bdf9bac8675a162f657f0196566b03b1912b9c503f4ed6176b4289721690efef5c688701e523808ea8ce7d93019bd877cc668e3acd6c7aafe2698c389d02dbe8b4d9ceec1a285a389223ead38cc80699e62abcb44a49e0f8b68bd7c7efcd1dfa598b736c7fbfdba7ab4043cf0636cd6be873d9bdab4baefd95180dfc3c6d34433f1cc3e6fd94fdd39c469eafaf5f877fee7bffc8bbf207f4520d518d785a9b1c91ad1afa76e27111822143530b71dc2cfb4c2b2ac894318be366cc65e8d5cfc28553b68c45a421b65361833e05719b34cf93e87c0c33f9c9e19305b87bbda03ac43b7dfd6447260ce460374924a0ead048a4e507fd42ed618f3f2ae0ec1d3b8ebb695589ff0752dc768244f81a149b05a1c09665be1732445a9502fd77eedacb8491c41cdfaa2578848cb1231d26629d8443682ff88c0414d5010677d900abe0ee2ce53d93eae3f72a5b6564c91f079ea4c0820dc8fbe00550241fd35b0175a23d9034821e028733fead64744e459370f25013ae062dde9bfea843256b92b565fdda8aa9c7c2e5484c20d464959cffd9f52b6612046035e9b6a6417f2bf2a60c66a944f64a758da8763f3c87ae1feda26b9a75a9821def6dcfabab4c86e3e1dd55f9182c5a58f7da93fa33089a9d47bcd610f347981184ed33abd3f6e050abb4dfc1dd0acbed7e40aac315513e7fad017326db3e9595c4a680b7433c8d7bab67d472b09ae6d33e183f51973f4a86d43606d4b0013b748af5fcf43a74f6ad9574c90d78a2ba73d5daf0ad85530fd0f8a46c56c373b2d24a0746eccb47a62fd7c606e8435bf66404d2b7de1d53e5f9021dd61d35b5bde5fb70d2482acf020f585cb5bab3d23f529e8005d0b5973ddc442b5886043435e08857b2fdfd8367c22482ba52e6259b3b323f21a45bf486355f83548ad0aa7281efa1390346feafbf2ea4966a39543bf4b71e24267373461be8b4aca54c4a139a93afc8926a0dfe8f74a9b30f232949e1581ec8942622706f83bb882643895e944502fdbeea5669dbdb7381571baaa697ae75e4a0416ed2e6ae609be30ba589b3896fd1745b1b8d90398bcb52fe5c9607994bc1b3a2203720f2f8c400619a66000eb889592afedc8918bff076fd23ae5925b27a501387c997c26844060564410da3c39a667e0a8c29f749eb44b13102d4fc3c26f865fc508245631f2cbd2a5e17137c56ae8f8da00edc4d9c5ccfceb5633170cc0af5d2acd434c8c320fe586e836b289e04e3cab98ca89984da94ba46c427722470bb3ca521d3aa6c88e618b6da0bc58afd2abfc02d1106541cbeb186b9df9982aab185b200ffadfef448f28522c9a6496f38ef2f4aa28bc46159f98e33f48e69f5ed0daf30429dd8f21b08ef5ac14abb43984168bd4389e8b14d234be0a80a5622c51c9a02a9ccbc732560fdf8b878ca37a11cdc3db8e38c6ea5bec40d1fef0276570d31c7a333b7dc90ab7b1d9b52b220e3e25ac7a2fc1121f5e9f5ebc84eadbac71d79ef546cebb66eeac1d786f57b3ec5943978aeaa70ed8e2ac0d7a2df85cd61a6815d0464a34950f626355007eb38e42d4c3b58c581f3b83a04adbc1a332b570b05d3196e92266b23e85779b3ddb4f326a1fa8621f828859640ebcf3bbeffcfe7b5021e7af4740216c4cf76c40446e366b59c54adccdea0727ab354bd36d88d236ff60fd2a2b3dacd326ca3ea21d697946781ea4ac1d56463ec7163e3eae45e922ca017dc3331178cefd5b9a1e0301a5980e938fd85b59d4a39548f8ebd4f103182cec20a59ddcc604d5482e3e0f1a791a1dca430c5a0b1600cb3135077da0434800dd448d0447018b65369c81be34ca70829d5653efb555c6eb59583d490d1a65522e6e1b30430b9ce720a5ea2277870b4d674cbb26859db17f8632d1f34fe96e447918d5ade99ff3895150b529f3209c33ab43f60c13c927ccb3e259faa61690094f82d43812b3a8090380919701aa7b291837d04d185046b1b526cd1598f913bbe242536fc8c6590892493b1fa9013bbbcade8753e95f9ae54e8716c6b44a4e2f5a8c747b8f4d05b15ed2a2efe80b9b74f441cabdf263cd8d9971032572a07b72327f5686800bc506663d279ea87409da666c60482c57b1444eda46f39454c3e77580c679bc065a740d9d7b815f136a3c921a620377402d5a3fd00d82eaa23cbfe0640c2959644b00fca23fab7ad222c14f275060345ba89b202a5e16eb57a9feace8b4850693e8f25da2efcf7d8cb8b00fc66cf17346d39dcd1f738e3c5e1d1f16b1d268a21c513bb3ffbcc4921a6c948f8fe6125d6d66dd623bd38ed96057a0fe0e97140eb5edc3d5914959a34d6638a76197104c8bdd672aee68c4c97decf23e9cea495d77f3adad1d4e7a7e7a765148779298430511a4d530cea82b2fe247275dfa3b55d48752babd402fd9485e22bf79edd8743af11776093fed5a14758bf3380c343b108cdc4ecea1ae8f7883390ed4cccf3b4609ebdfd47bd547a4acb73bc0a409356b822ed2cd36fb16390600facedf001da0cb2deace4423a8298c844947d06ae9ff6166b43481068fa7f1455ca6b07af183d40a491ed09a26923e5566bb713126a6401fdb20a8c9030c74dbd73afd046935db39766b504cf1216c0232ccd863a086ba0bcb27bd9fad7136595ca7fedcf3597d8bc6533b1f8ff622df401b289d7b0b03b0f30351387f1868ec20e17c0bcfbf2b68f095f517cbec28dd431fed757546ad37f647f1d08b40cc447b4f9d54c4fda9cde661b66040dfe47ba983d98c2eee7ffd4832c93f5df8d2a172f871ba70bc318e2203db7beecded5a4eb544215244eee14c2c4c766a3009e1aafa8be7f235531a5ccb96bfc14043e7c47d10b2099049f95d186f89a17a66f043260de3a1636d99c72f0962e9b8aaffa564aa576a16825c72c99ce717005dece6eecc61dfccbef63d3bb1d949b3cacf98f695b94c288d5b29127c359b4fddeba7d037b8608742cdee3179316b454aeb915d769041eac6d1b98f53f8ed989f5d263b34c4e7a593849137fbff55d32420d7495e671ca61e4a375f34ffcf53399877f51f233943bbabe4696dfb9b9aa21f1c532a756002a7157c439fb674589ec0acd4e6307df73d791f1a590e726ac819915433ea5815300e60925d077fa22f1eb1143374a8bc64256472e8d3e29a2bced074e6ae82c57e1cf06895a0adff535251bdc4ec3de952900ee69516f833cc73f623f18279b6461060e24ae16bbe672fd223ebb94145de6f8d502993024e5a56750d3165d7a5dc2b123749918e38d0a469c8a0f08ad7c47073772445eae3429c4a884cb26982c0fcbd4b73ea5a2d65af38f5a0aaf75618750d94873caeed9a49d1807f4f1cdfcf69041bc019ab305eca5e3b0ff7fd203383b10c3fdf3ef5610fa1a44f8e4f38316bb0cdea16a90cb94dd4e36551f5b79632d7c9f0cb78fd21b7456cdea395e0e071b9f3ca74f125968cff4d8af723ef9afde67a58ba10cd46379c6d33866b8583fcaeba055ed0152dcb3e157d5bed1ef8d78cdc09548105825c6053c74641696459d694a5df6c22a11d8ef51ca096251e17e9bdbd6f09f03aac7a6bb1728580105164230d071d23267ccadc8fbf872044e314f61308bd6e8b70bf80c2635fedce30608bc6016851609bea9a0e24110c932ac0f3a66bb1c0b190d75b6badc73ee9edfacc505e8bfe62a226e717a8e874304247445a962f9c1d12de5e10ede5491c1183a69c7151a18d179d56e7c09070ffdfe8c4630c11048f17c0505fa98500e405605ef2408ae64dabcf441f0d264a234881e5f5233ce5e6699af3830ca861ed265cfaadefb808a630d80559b6669529e7295cda3ca1ac5bf24e2643c39f15197f284315622927b2bf7d73cb2a451bb19f56c32b02114bc57a699041dc57775306cb44321ffb3c4c089a64ec346699856b49f2380fa4ea42ceb6a0caa86925578b1fd4fbc376bae72e254f69a5283f29b5a00078ba58f66355f8cb3a9744f94c6fd7abc906a38bc58106b8df6205e4793a80cd9d52618e8c6dad6ae797416205060455adac9fecd6dc9e339f8601c156c8785d66f16107f72d365e7ebb5ece284d8b2d3c61ff5a36b347e72e7c60f219a6d408d596235418ffab052bb8a4bde32fb8d22775a85b9988324ebac10261cea6236c5d5cb15eacc567d1ca0249bf49976b16236a1d8eb48c5388b25026499a493dee889291bec2229632636d24c5c06ef8a45cdc70d4395c8160e2e6980c4d6dc23138389ce26eed83302daf9e80fe06aea426d771ca7f5152fca3894d6e1213983d24f3bc0b63c206f21615106d71b38b9b69d1215a6582ca8710a8e1a235cab6bae7b62d777495a65c52b32f50d94b1cd60c515a804e6afaeaf206ce1901ab6852d74d22e98633bf0440da1bc8333fcbbf58d26e19854b84b0784666171c4561b7817b93b162c3c36677fcff33fee1932b4eca4244b594e2996ef11ac2f9eaa300b757d12b6d2011b780d90d3ea9ff9ecb487b126238e7b04fe683840edabc0168fb4adcc7fa99decd9d112e763b852a5233f9b1ca91d00fbc87219f8ffb0e130097bcd8d3d8b017c8f2e1c122ddef94ad93a1d4a41416527c4c92d6a99986dc77642c8dbb1a5ba6b418d730ad49ff1380a72d6285dab0bd1de60b02d1ad8efda673a792523c0d9e423254e4afd519e53acb8474af3512d8b37be12fa1ce882a714db8b135d455ff2362cf15374ff45d6187a8143014093d17dabe2a24f7757a66a798ada64007a5db4a8948046896518b502d61e92087ecacbd4982cf8d413686397295114097429681cd2b94a6e72ab5c2d3c9383f964c8aa09590e925113a977342ef1458908a8947b0b119b05491b97fd90b6619fb064a2fd0ae5c0f47e47eca3e8bcae5ecb20c045d087dda42c911038db3eeac70159c7130f56b01869adf6941b6f4d1ba68b31d32ab45b2621741ee9d0813192a21793d6a8255ab2c6e19aacec9d23ba23cafd7a3568fb862d4b92a4e3e2df3b94f5574ef617ad60b920fa8c9223bcb0648fd7061e5f5d851a095e2eda6d16eef0b54b3e97e9d1d96825184bd84bc7e91fe4282ec5e4c366736e3950eef2708e4308cc10480dd64ef574328bc488ac0675d310c96ec42af28b1309530f662e9404527d8128d0df6e691d0c2dc608be4bc68fb21973eb8b983c1059436bfc5497099937f423a4809301b4d74672f221f064e736885f45cd0ef373062c465a107c9264ef0085e04b58112f214327d8969c992094554de5deaf0f6dc8fb5aaa6b4f65d09e9859c19785c58627227003ef710a0e5ee46d6fcb2d0aceaa8abed18e8068e4b3c74615d6c7e9d95f40ae2da37a293f49614efbf711e349fbf9db717ccef7bd312a48e11576a69d6d0efa4c8e270cc6f46f234b93c2d671a13694e154f13678ebfe38bc99eb5149d73438859e796c2a42d7ad3e401da8ad92ee275894ad5ee595945fcf2c24ba800531d7e363065e044759d65a94e16065595688ec98140a0fd7e3582e356583f18d47732f065d8c4c13899206b001e345b8ed5a32ea0181fff656890bfc7b302e4f3d660fa8087dfb640cb3c66ba2687a97b8d93c2f9bf5892fdb0940da6aefaf7916089b97e7a215e80fb49c5a335382d95990f56dd7419e506fa95372051f01968a920b1d01f96b6f683298a1de0da8ec855e68dd8dfb2967311e2f968e7e3363871f70eed2f6e47bc1a07ccb7f02b6c634986283c295488ab594d162f3f19e2fa14669caaf343fbef581c05606966b61da1100a7dd8c43b957ae4e32d2c60aea31c9f8d04a1460786c1c158472e2557dc90141263ad168d6d54bf31572a34273100eaa11982aee9b2ca366632dfb64a55c9bf0635c266bd5deb433baba08019bdffd980515bff3b3ada9dd6ea92ba8aab2d55e9ff3ff73df645a134c22cf27f2bb908fa2546f5f8b82b9d60bdbf88d3c02a4ea0fb2da8737a13329adefbe288652a5920fa72611880d7a7f630428eb7938602016c67e15dd3190e4c1b1d928a0001c477da0c2b7d5ae19c37294ec84072c5ec519d8f07664845c1033e53cecb44b32b358274d4da147c44fa37259c6bd3b771189c951585883c68ce933c0ad1bae1b45bb56627fe1e1774a64300ee16665401d2145a6f73b0334872c687f48350d34d050992cbd578e9846a39b7bf3b5988f7534060e9551456f29b1a0b52ff43563f861ce2e69f58cd981570941834c19fc66e35622bb57c4a7cd0553d66bd31a715808f4cee0bba54e0f5a4274829a33c9d041dcf3b2f7d072999783d286566bd1b4e1e550a013b6160c18ce88376133bef0cba0a33d202a80029ba7f024f9c50b3914c5ff661461c0962715ce00fba26fcc102b41cfee18c8fba246902dfeb1b770f273d28eba809c7263e923a96e34770c2d3ff94289ad0b396e5d71a8b79a1a24641b53be70e2e7203c01cfe463a0c37acb0a3f72aaaed6d72c3bfd94b3236ed1e2ae235fdec5b560605f956db9f6415686c758014c1b0d75621b33d47660a6ba0e79a2b0b62150e27670facfee15ea9ee97d794b2c7b2135a925e6da7c2df9f2680f42eab304003bda547f192f06287b1bbc28f5f9276c3d421e151d825199d43da413c28454e9f33bd4761b74918c3081aa34dfa573ee66dd9687b5fdac7c9c80913171a2a5d60a99d8b4bab9a941adbd2134d3db6c2124b5a8d5a398305eb68f0345959af20abbed14a17a4bc1ce02bdd22caf2693704e1976a08a805de132829a20ad7caa790506412b5cb58339e52062446e997634593930c3f56f4065a94792f6d40ecf661d31173e041575f788929fbb4671602930afacde175dbf1e9cbbe5b4fa705034105c34d6fd18bfd7d6e50df89d957371b51c082c4a91d4ce1916bb210a17fef96d477bebb077e6ef3b02b402464945bd08354386210f5b02b46f7b530129ab10505c8c534fa9ebd97372e4ebde118da94cdf9d41819e5309889fb23c5361dc5bce3387185ced699508027d44eb8eedebdf9c61888b3fa5f7a11bdbea61401f9b820b80a92fb2033c2db9db082543e71e55bd0d27788c396a1ee6173fb5cd1895668c7edd413d485b0ce8ad67a828ee20ed3d8c7e282eb394edc3f26e4157c2efd26d00dec1af85ea031c335bcbcdaabce6683c10a97a9283f894778944d3a0410ea4a0333a2fc5e57227649c68f0aab81c1fac850fc574c7442d4e98589498b431cb871b6c8b4799d20ab13b72ea1a53cfcc7c35a2ba98d35b1b8190dda91b5ce58a799128544c415fd5c990e033e0ba11a4d78b70f3174590f671293a24f31b591f7e66461dac51c28df0b0c909ef2e6afcbfdcefb90bcaa1298bb354b7028916a33dd8499422d9c0d12927aaa67b335cedb459892632f427433733a52f032c102f0107df22d60ecb5767befc707522929a6c117e17248cf8da88fdcda1a37d9b7f57ab0c2419940407f40cc0a690940cd8d11a12ae70d00a1870a2cd9688d1a6216c8b56f631fe961726f052a45116a193281d83a5615187b97610ba235f27cdf2bdd32860ada516cfa5e51396eb4493b56f760c8891b4ce86d7f7919f7ba22dc7ffd07c1048b5058a74913b95798f5d6f47f12191f7ac61905b8faca8dc57ba188fe747bbfe620d01cb5a023795582faad28799afe55836babd39865038cc5d4fa265b122b3bd4a59677bbb96011a5f346185322d915cd28d3340cd3b5a551ab3053252b79bd3e0993e5fe3cf188ff177e1b0ae47cc06769c5f9880cc3037ca3badedc18466ec423d274bf96145b16ba13f769bfb60310da65bf924e771e023b127aef0523d4a708123af072e0193be5ead1a25a5f749789c07e8f45d030a3e3738db3c1a14f8a76b585e56a94d0ab8e829f03a38c111062e746216cfc0b5dc389208025f9e181dce4839ba906092f466e4e571fbe6aa74251a160524f3c66e41d8d65273b1a96c09c8934f517ece5234267a1c11544b168a7a1f51d82d2410b4881e0098bcbad2eeb8112923b9a0b3e1150cb94955250c0cc0f5e0254e2004c06d89a50f56f31608ad22b91bd8ecbe981391337221b4c0ea7e85c0a69178ad8b94c8f43761e7be67ecfed231aa8fa1e924d5140d9c776aafe8a3a6b1ac3141cc1142af91fb48dff8d734aa478bfc1490a81ad012a708c106544263311becbfb32a434e4d6bea6e5b73e43c4026e305671eb4ab81ac4fd24e5914499db8882397761f5ffceb2b7ff886bb42532ac4acc88d08826da4605c60fa600183536304b2b0f69d7dd881301a35c20cb39dd8f4d99ab6a6ba42b41b18a0a22327abbfa7e909c8daef653c251cf051deea38079b16d5fcf0d78d96919048ee1ef5c2afe74e819d13546acfe1a98d3bf1b15a8867fc2bd087fbbfa633c07f057e7a6630115e9481d1ea70d5003e4ead2022aefd70a1d45e3cd9156e75f2733bd3da820039aa66d42aa1c0528928bec1014e717e19382c7d8303dbac427144b49386b997e50d3fbd69eb29033724f62a5c80c6913bd38c12db82b0bcb56b1252144a26769a266377410deefdac694f01d0832c9cc5f634adf995a082a6bdbc628cc9b5b26b191cf1e3b44d6fba995feb32e438b65ee2954edd8581972a5937b02023ad09127075b6248fc8e638327f674e2d4b37877fe90dc6f7ea98b3081ec8f14d8994b165bbc9e81f5c19cfc7890433609173fdc23889fcdd40de1b5a02428b1b1477e69e66137ef4ad8def8293c2f3582eb3d91ec93854e8b6feca0c9dd3958694414a004e20c3181c083a9883afdd357717ec354840211df2e2d8b14fa72b161d96bfc071022d2705210888ec6450c52e9ac9f7961a9b0654212850db6029d38532a363572d98ea82bb188ecb523a8947859b791512a089d33295f47efa4cd94736c4b1e11b6b2546391994f94314aeaf434a063929918ac3c4cbf3a1738f1bb2129c8a0d32772b024e963a73b8e69de1b638c269701612cf788916afc878361b845bfbef588ed4530d7004765498608627f7d1fb7b2e6f7b91c006828007a2188e1be866cfe49c1125b0961164d5991cd3659cf89245f7772bc433b5059064052ed7ad082aecc1f3dfd63878253b9d11940ec3567233eefe07b9687f2099495e7e8b36f13effe5cf6109d6315096bb9b1d41d842e79a7ca6d161ea7c63043519883a8628c2a61240fa9ca0aaceb046050267a0884ea0389b5885c818780357f729a90eb288e977af39b80c8e930564d9a6db70ac149817a483463317496d704f84b84f3821e5425e981c7a9f285d043a384224904e963f3596d0a3ab6da8abe65a3c5f3295666745e172bb8044b248fece20557020db6ddd201d738041a6b829f6b74187e597c7a09f9a27f1888de719d577b4ebd0bf15739622954e6ebc1219305988b4a4b806f1e03807021e11915aa94e411fd3c87049876a335ad740e42c0234f3f01e7cd4ab0a8a852b480afd913d3317c334d3246452e6718cdf4c9701fd850cd622457fb5db16a816b8fda068a63e8ef9e101ebe42f18570b2bce6a5441bc8c603f14900c4ffa8485d2f4684122a00146855cca98fa84e53a48b8a18863a5c5fe536686381b9206da4a47d870aa50f2c2f3d015d2befb9ef15d4636bebca6b5a47607bd32497e1ba21e325a23b9575bba19f91c6fc6a3385f88f0ae2437c7d5d2c60c1751eccb3f21a1cad57b0b8fe29d53c26867f80796fcd58441b740dbba2a9a0aba888caea5829f31c788c4bedb7e7204c9b2e625b7a1ea587061cf45888a9e6e6caf76c522bba93e997531947890748aee631f2cbaf041abb415e5ccf40de0da90448c8b965a1c9520739f016077c4f210e88b02ebdcc5011c2f8677bd5801c55ea2aaf122c8aec5ad6b235c20563fd3700adbf1648fc4affe2908fffdb8776099828055b4c105ce05a38951c1885d094589ed5167508be68d00779535b58ad36040e7dbb11583a539f62a5a2caf789fb1133d1e791ad4bdd64e95269ada628457afbfc2fac5232329e20a7da3c7a7c976d0ba846bc38e506f3d0061fe0a4cc4713bcc3ece79575fb165010812380d0bf61dd534459c646c2013c82aa24988d38f0e1cfd40854dc4218475ff004a57f47069c528675e0d83a62ceec4aa636cbfbe2c97806c1ea17ccc7aa267a0e92574da0b655b56e1dd747d91ae13b9f03c0545eb3f489e2106f906e70c6debee75864c49da19f62a7c3b7e69100535f5ed5bbe580513af870d800a3cae5f9cf952c001f6e06413451c196b77510a8dc0fa0e805fc72660f3524c112b0e1cb479a4a85fc39e154c159630b628e529c88f12cd432feb3a0fb9c35976b43d70bcbc02f94516f93f9e2c1f2fb8077adb293f16795813daff2992a00e747e72ca8f8c85063ccf5e4361f38cd46c142cf96a7bb340a1ee521f11f7c9d7220fdce9ed0060a4a58db57095b616fcf4409dcbd22fd8707b0ccabce4193b7aba9e5666b3c8b42819c4fcaef1feb086ceb6f1233a76493cf2a414df79753087835491998d74f6a002b7026a78fd00b79e291b32ceabaab21f4547651a3196cd43fa5f8df92aabc1443114ecce46f242549439bf9e212f90588cabd4f36206a8f1f2005ca398ea9d43789e693752fa5e877de998cd968a21644a6d3e8b59f9dd6339d0127c2ac09a0fc1c8a2bd505cafa18a8552345a7d1c24598db513e96c785453c5cb389bc5934b8eeb5c74542ac92484fcc4db38b70a9856e0433c51a2896873d14ec474e3470620222b2839a7d73a685c023cc10f3487dc0c7bd1f12e623c2b03bfbd96a885412660d3bec3dd1667e946beb9084797d6d07165a66673b56ad40e2a7bd1fe218957b3f11287a3ddc93e992d81ea86f56ea69248699872b1acf46c44a65c716bc8eb2d803b48aca9670ebdc276670055453a44b0ef344cd308b4bb136e3eb0d7712f32a8d70be906013237d85366bf60a3617ffc277c04ae99d5757eb815e5bc01ba7f7b17d25c6cab43c7c66a1b0a0442faa14fe3a906874d8d50be1a25ba3fb53cd54c45c51d793e4ba242c9a449601f7f0632ce875cfd4cf188258555c023ed1e83b0b168c4954e81f6caa293602882e3e4a4c44eabec412f5252a46c8019ee15a89fa805822384e2b07d0f915234a2705a1b291439c8ea94e225bf89c388e707f39d6e255e26ddb110a5d1f11513e60883a200d1fdd88bea1aea020c2dbf089e3d6d9f3efe4527d80b6a99bd126b8e0fcefe26f1ae8b23dd50b96dfa80acd09854df085a69e169cba6838b2bd72d84b9b340d698d37a2fd6fbbe9e6c48d9ba63d510ea985a302eab8cb42b602a139a074aa34ec380243acdbec4ca101b5ed440065f62b92acce9ec871a43edae16890d1c28f115f2ecb424a8c03c4e6baa5c480d97f670a04c8ca6d3d03cc610d312d29c391e4d21eca7c91836521b89fd11462ba819712ed1196f469f2e6e65da7e14583656dc5142d29ffaa8a13e21d0a01e07fb28b053be0bfcb524bca104eed95c5c0ff91f9850c4f2f2d852fe87852208ec961cd71a6602ad115ad988aaeda050a863345cc14635dac9c1f7f87ec5811735128ff96ec856e8a426e1d55fbe44c20baccaf26caf4989cd5bda3b911d96c1f299fbbd9df9c0fff70dfb2bd04cca4f4966b2370e6eae62b31d974e8054e0245c3ac0f470461da5872206331bfb765c2eae675a6630cd1a64648995207e2dea1e09e82d01b326d3e28aecf3e990ac68562a4f002df59ed347d322827b66c477497fe5a7068e71dbe90ad601ef11fb6d250087d4054dcd15b69657f9f10b31df8cbc3b17194ad6e1cb6e5004b6b2da076359bc8d6b6838ba63fc9f7dfc7f9907484d96a671f8e97df4894738328745169a01b52cb248419c0c47e6e78e1c59e242296e6c4c8e7ce70861dc0f0322de4a98c9f692404b61d02aa6ecf61206ce9a4008de0bf7ccb5507c974866178ff9de125a17653469e2f42be5ff833453181809579bbb837d65995adc3ebd3bf293b0e90ad44a92a7e2bfeb8ff853b510594c2c5896e58cdeb396536750806ecb49bd729f3ee8db708383932e2a0a3f41c7fd49558873fc01bc7bebc7b0b7b99ee4191f9cd12c47eca954941c49db9eeb8d69ac7f95497477daeefca441913e00ecf5c94874dfd44d17f544e7f6ea7e0d4d23bbc28daf58354469c21314b25138f51cf4cabd38c8fd6c3d0f4cfa663f74967927cfb3e0eb0dc924976c9c1c61fa084b9e58c71afd31264952105f2ce81256f51ad5b2aca19fb4764c0248d8117eaad366935162d586abc96880c758458afd2f3b7c4f745ac50829eb8b6224357a9d3aa1d2407e522d01976094f56147538cca5aecd54f94aa7a5ed43692ea2e00b249cb156cded5abe4463ce69c9467175e29766d313ee8b1637672be331be34ca819f8ce74d8d21c8c28f2adfe959d31936822c5743d5e8410775813a1e53203fa015e6c64029c1f32d0d5340ca230eeabc2ef6f9a26d619b7c672744f1fbb066dfa71c2819865c3c4c73d1c9c8ef4b6844a2168eeb1c1727455e9903abf358242aa870d4cf823ee2ac83cb301001fd380d5f4eb34bff66443526e2b424c3766b6026a8d66e366ebd1b92246a424b997f5b07725758d7a75504baf871ecd1a237cbda8c9a8adc261354ab8b7e0722cba88a6c812c32e8ccc7f872b84c3d8e2735bdd38a396c2c5ab8146e7f5d783629ee3fe9e1ecb9b6f0523b5b7055d677c0d2c305be88633c7121218ea6dda1b11ed5d9a75ac5bd4c7a6ef4f4c98b52fd4c255e5d1e455f94ee0e12b31508220715182f89aac61ba75f8cbf6317c8e4e435c3a57fdc1fdee094c61d58adb51f2f5896e0e46579298236b8e4c6f97f7f38a5c8fe8ea30754bb4a1dd5c2b27a49b92120ec8f8068145e41b70972e8a719ac6496e641990dd0ba9c9adc06cad1911304fd59a40bc60918ae0942f314c770136e30b1f695ed43031e50074bd681e51d8f454ec885876ecf4635ddfa8a6a777132d6e204f5068fce9d5fd3145468cb3ca8426cb932ff61e58be5097c2af2e7c5104941c55d9c2af109141402589317646f19707965fbec0a82c1ceff9964e2424e8046b0834f6baae288b288d3bad19ffe32e5f44b8d81b7df2bc1fe3964e1c4da81b1e4424cba1644b62f40a59a682184cc21b926018bd4afbd43689187885afc236c02943ac0c4def67cb094357e2bb490807d9ee5a23eef3594b8d32bac81ce59c2a4541bb00815758e52ed67953b38c971fab0d20f544b92a169e88e0423a0ef3349340bb11cd0353c528798622990f358be4b169ac22935c96e26b99bb007bb6369f873312f1a0dfcd9b137406395d7f180f6da780d7633adfcf7651e5af13b1878e362743bb64a721c7514f6ce56bfac17e05232939421bae781b6832a5bb431fc9c70c93f3fd3839f12c49c0f35ac846a43508595843d1c35269ac080a6bb233c520c039ae20dcd41de3bb88d793b9e07764bbf8e81de0a47a68c8772f6357136333f7a8e311288ebb2e7b85aed49cb3f333c81b3d15e222b5a5bf636920e8bc99e7ad7e9b71e33c5aa409dc84fb1c7637b3758a2006128e8a1f713adee44da3737140bb931d32263d5a6a71ef7d7753b5e3aeb68f1b6d0316f699f69da482aa4a5a1226d32bdc28a30ac64bac2ad1cc02be2caa8ae86398a83c3080e6c99e016d0c5118ab37840a756defdd906ea9e3eb8c4041aadd26f25e0a816174e958014536db4f2910be60feacb69506af53525cf37d326935e8e6c6e432997452535c90e8be614feb0b9a25517fd46cb0526aaf8c696ffab383c528310c881a095a84d72da098ca411272498259aa6cbb25892ba069381adb88fdadf52cc167c8a9fe183d5c1921f2459cd1e13474d1fd3c4a722eb0bab80ce6d490590b3fd791716dfb868235b5f736b25a32dbe1770eb7d439746a045d91afaec02b174543262a59228ee16705b966a760e0772987c09718eb8ccd32acec78741c6dec23463d474c8db136194cf324662dfb72447001290c8d6e8b542586b48018d6256b1dac889ea69cf95d98948305cfbb1163193f2667a8824c1474df28fc8d2ee6877744de2b0e6ebf06bf652fa8b16980a77eed5f9eb857e91146ae264ae9c7dfbfabd5a652bd2a585eb70485d177371d926757d2144698132f628d1f7657ba7c800b8292519447b93035da282899994b529551d0bd3e9a89322fc1c58a03fe2edf50c6496dc09bc38df6f4439ccaef572ceb58e7e61b5bcd28ab9c27f2a29361799ac04d9a824197824cc88a0cec0892115688f95a66a9e96356a6c711ab98c42633c8e3640adb081ee12f24ee2d71995588e3985ca68a80d3e65f49ce27903dca0655a8489369c9e26fbe616a76c1988dad3555dec884f4635aef265203b4b8ab9f9cb9711c8569601c6a034018f74eabc543df0ec3509987ce6d00e1fb69b48113fe08e649245cb6246e74fb9c78c76bbc0821a56895bf2da60972a248a4367210571c350a22ea48e0a4a309a9f169ba71286a67f36d5b66eca0d483a41bbfe83db8b55bda4d9e5ba8e3e4fb7e93fca8bcfee0bc3ac454a6ba416adb194cabcda8dc65c2e75d6adef359e4109119149bb1e5a37b7eb8263a73466073b0713d951639548da018e1f683b50a202c9a93b548b17de1c44144aefbb02898532851286c2b66449295ad8fe9cf4ca621aa98c27478bc4de3476f6f2c74b0e3f039eec5a9139a50c58dc96f68dd14ca3e7329dc8383d50056891d5dc025144e7eeacbea12c6d27868c87ef9d7e270c3ecc02e3717bcd81bb07c29d373626613cb06fc9c16ad9cbc7e8fe423363c3446c33d31ab23e6d8311c417bf40dd08dacff9248076ee631ad0adaecb02b7d629f9bf886325692c6598a19ac1d7b5f82a5434b4f398233558ca1ff9e6d47c21daaa430f8b9e9d985cf80fc1e9217054aedb9104094d43a59a5239f12146f0258bdbfc9450086e74d65359187959a52a3f1fbb4c8dba550c14d929d3314f29c27dc50b8a5e645ffedc130a7c865d9bce406621e08e85ba6a244290f60bccdb12822108078eae83bca7512b8eac695ef2ee9cd26b669f2fb9129a595298c8fe0f9bac3686d1dc6075166c2310c8beb9ba9f496a961852ad739c8f039670e054b11301e7d7f6a93aa88160a79b035855e57b35666345112ec251967a8556d343549156d871b5ee2615a3bfaf0049afefd9d793f1f201302b807407e759fd982ee4bbfb1296b49b48a7b75dee0d72a0c76765f1f6cb349348a0142d58da8448fe1a9c91564ac33183c6c4c40d648402060d94fd12aae00dcd5c58f3f5a649025c8640d3c08c14db05a86046ba3cfc6856a784d59cd1fed5feb13385cdc2717ebc81458276c09612589dd089e2cd0ceefa06d856402e9679c0cb85b2d99da7d14b7e7234293a216d910f5680badcce13154468d76e982cb7235562fa57ad6098b1274c7668ae3a6eaf9c87da4112b64057b52c5f8f5d0b3eea0dcc7f12091358fa8662b8eb2d1c49e71d979f4ec1d79222fb6f01d26282a093c281f6d4af8ba713601c4827c47988d1de2a1ceb04717b4066b74b963d6d8a1e3cea82f1b3e113b2c27af7db09d2ff109e03a4920cd6c13342ffa656ea9aad27152f232e588c0b1a5dbfc75ad9105f410e9f62e57efb2beffb50aaeb1c3e4f647ef39aca5df2f264723f7d10d2a329036d3615edbd9489a1f6da589f765f886ad6b8e47a6b39879b6713b98eb5c0562f4c16abc8d13ca3f8d2297254263fed3672148bfa59322a39ba0bf7e7190eb052517c05f8bd0bab632212083472daed8ef448e6738c1f70b820e237205094e8b19fd92ac28ead429bd13dcf8a2b1406d14dbad772062581c87b456eed2bb035f6e18e91a952420d9c76491545646f6b9100babadf1e8306fb6402906d2980d9db0de0c8948871a339433ffe1b21c35562771c6d363cf7194006514d6b80a8134beb8fba828c0471ed191c1e099953dba77beb62ae8fe947c5815801061ca5570cfb5987c381bf11a549cec080e0ebee9040dcea7ad59734519a7ed8f1016f6ec870320d9bc11f9bfaf77e5b4e881d5935ef30493b240057549a0a6694315bd3b5f153f7186601fdb115addcf38f056164d7b5c24b3f41bbdbd7a89814157d443ddb720c3eb13b33367203c931ef17fb6d0595185e126b43572d695f6a180e0a604b3c8e7f28afda505e09c69e976afd3316a4021756cd7f5b704ef7abdad41d12a215dd6fbb9d37f395f8a97e2935b86c8fe79c67d9b5df9f1cf511be80e0c6fae6b6ac01566143d3bf9360c7feecedafa336befdf8cab56c2f8a90dc95489899522492222ba9863042dd269421c94e217421416ac321d494ef85695e02e106d83000adeef695a942e3086c000fe959c5db250541572924d6221c58080f25ff9d71318afd2691ac0bdc43112887ca0d79928b5aab7711f753cb211822f5033a9508a54b37addfc0406c054b609ddd0e45e14e37fafee58fe647fae67de249527390b8b645239dccb762b3f2324ae556f03d2bedec5a602f78a843e297d4013b64007e31a71c308772360960349d614774eb29cd6f87d2e19a681b3ce015fddcd3004dd0e30715eda04316258c188f1559722e8a2a46483d3c17702e97f4aa17bec10c185116b7e0573855392064aec31739a6a59b542d5d77715440aad5af4b9781e3d8e8defeee3c1b48e66cc0f2181dcc00699bae4ce1b382cd0fb874cf1b090765e3d2ed4c04e5d2a53c98a4b0579a539619d8d3cfbaa418699d36ddb3ac4d77151f26cd36dd07799b2e8ab8498473d32507322ec3b7a24a365e5aeb76919206ba2af5b1b33a9a0026ab6f01a8325287d984195ceb69c36bc501658a7b34811882c4683c9809c924e1e41f5cc04ca98a7476ff1e01684613c80e8620d7fd208d6367b004f965f62e8d11e0ccf7c5cf84f83a62829ec0144178272f45dfa980a12d751a8a3c08194d209bbd9377f70387b2d71c4d20c7a4bb65bdc4c82b20c69db6025d7519d81beaf2d6fa7cabbfad571547095009385fe97b9834d270b29732a03957e968025f3009884c71652c51d0e126c0b734c5efcb4c80ba892747017a6602d5a983286dcc4f00cc0478963070c546b361e0f123c359816065306e2ad7f9e7b99da574062275a4de6502024f7044bc3506ef706f31e2cb000d057f7b1f11932e13187dc2e303facfba82e0554f8f128cca3b7dbe8765a32e62c104ceee0984326900b6d97ab3bb84469900426fc868c0e15e6c8eb0fd528406a4f20f77bea483a654a8c79c3481758b092525d341655c131ba98124cace60861efae24cb285b9224f4c40149a2f35b70bf6f46f9d0f218cb2744a75138985b6929542da91aa5322e41aa085bc9835dc2037a51d38343c065b8930d0e0c3d908c286bb7e112b400efa123342f4fbcc4c6583bdb1e1f6863482509e8ab2a99de735ffe3084eed9d800e86db2e6fc36547085ca1786b0f71ed2904ae547ec3155008b577a5c361ae807d5cb4627713dab12715a4e069944b134423f8dab6db6301d00e3709096140630d4ca789377ea0ec4a35a1aa29970c71694a2cf88b1eb5ca06dfebb2bfba67f1863b6ad5ed602a6e45e33befb4663ebf06f6515872e4f7d7f20fce00eafe5be2b6f43a57241c2cac22ec8bd625b5f1a211f6cfaa4cf5ae5d1224e8e63f79a077ffff8fda597a1fffe1220f1abde35ce081a45ed0ae9dadad12d971c147827d82367c964f22e13f6400d6243fc95943d45fa0bb9a0115f8b438a3bfb3802ba3472ae2ef233fabc576a87a03b1031ead2fa415c08c4b5bfd703cd9be9119e05a5d773beb616567c0e9ea0cd892221821f221cc8c7c235c1dc40ce89ffa6db29a0371f5f36ad25a55f51e5051b5ea115341024fc1ac3ef32e4fe8d7b88a2790966cb8c75535ec910ab162a9e053ae612f3dcfe81ef5923cf652642e8d0ba42af09bc6ff38ced3c666fc03ba96ec2d75419999560a226eaeb2b7cab0abd832c049a8b929f329dafd6230568d6c8e60297a0d7338d04afeeef74c4f9ce09f7bf12d5615774d48e55b6b34c16e4d6653724bf2956f9dc1f22d443403ff83003b7e19e94e392b0b5ebead079de9536e03a42ffb9715d6ecaa27f425d6240aa45eb2034177fd2955529bd0a9303e010762f12e283bb04e6ca6f250294c5d52a79a1694892bf7f2edfbdab7f10728b852ff4b3f563d0c68d8f29f26785fdd721cd22ee1b451272797d858f45bb052b13e747b0475d580a2ae8d6c3d3627c7ad6fe1e85919cb1e8b3c89e737d27c03406d5504d41f402909839161d434b2e5361c47999689522ce99d4616210755a4533300607011997289634a486b28066fb374461072f8fa588b07297168e5d7c8d4e504ad3486b028d9267f65c53e564634634f3299b5a1bcf908945f1ac3226700ca4384b304b40e901e21a1054fb547a3029a8d194907a1b56621cf160e9fbab52774e0e4699d32161eeedc9129e1c8698a90c459aa9c99cd7ff56f889ac1fd093bdc00945cddc6a42138449549358582af19222ffa212062dfbfd02fb3af04f34af0e43bef5c9bcb9bcd359051f827980393de6c547337199230f16cf7528896310d32246c0e50a8787443d4dc82a92759a718133486e4bdeb705645cf9fe50c2e9d59933a4ead8ac2d966ec24c855753bec660648e3a71b42c8adb62b0b7da7848de7683a8f795fa7acc78726c6b4e6e083a71752ac32b9efebdb9f3e332ff50fee782805a89f4843eacc89d14b847b3265cc4490d214d9db9c6ad422cb942ad1f6b7c946e8166252d78680a6e4767371e69b7ae76d943e3580c526044bb087bf70ca78f0118d0709af5b234bc7509b83d27244fcd7b6776fd496f452016b2a92ebc440f888ca5c7cedb8912dcb904883124d3af4936f86907742605e86842826ae5a157f126fda150e620d3d3df225737057e7153b484f24f5154a7de47cc7609550342663142fc04741a63fc50a8492cb60d12d3d3acf2c90b279931e781d2e194bc2bfdb893c3d4712328d32e8d223fb95f07115ec31092e7126700826c45435c684946f878824546e7fc52e64034d8949c6cc5629a600f4b006a9329921580b8a2b48933620f41f9161bc7e6d42700658e330523575855258b4b18ce660a5a9af0d4eb19e4d15fea633d688b358af9b10add5201c9b92de5a291c2472169c74467e38a882a701909dac15531fe6189f0a8990846e2731fd29451414239374665063762f42e053b53f86eac4f18600202e33053f4cd2b7a7fa6a588420cbcd1dc48425842e7b0617eef218a21e9220e6d50d9b817c98221b4ac74e0beb4f09071e8fb1fb134afd94a43899f421d5743058177d91e138f88b133a00d9dc8fdcb5b33fe32c3e4de70cf3c60ce5577c4448cb1d328d944ce0c004ead25e76d39e056162755301080e8fd91e69442b1a135b905b171f2df304def35c6571f9be6a55aa2a6bf86353f4f96229dcbbe46460cd44d0c58dedade959a79b98efdbc40c49b602c304323c1b333070e374389b79267c3bdab1f2e08bb2e363896b04964d2b732c599b33c0cda3a022d7e70e752eef904255f07363138648c6a2d547aed206841fa9021d32ba7502f4d0e165a0ac5565c86baa2fdae841898e6ac70eeac197ed3dd605aa980272d83ded432744a57b0d5eb768725ebfda3f8f765a99f44a29cdd8c7b51e82b02ed590013847adc0419eafdb6e18cbc93403c96d387dc4d5348f000333e181f3e215b0c7bf9bde728b3a4c47e71beae4371f5d2f2a0042bac9e39ff5794c4ebfee4fad5def48fe5e6d28f16eecf2bc450e14a143289ac2af658d8823bb3eca3aacf13b699bfc17c73fba4193b58b54dc78ea668567fd28e63d6a8760828a7bda3931eddad181da356062b926f9becead9f9153e4222acdef9af5bf8dc3772a7966d9785375dd1e1aa0e2313635f9a00aa70cf73b09ddf4ce1e3024034635f7449fe0b11d187c44f3970e5f9eb771ef7ea4bcf55b40bb9b055a891879ae851aa579bd1fce004d80e208e4815841fe58bf15e2cf862d08de1ff540a82d5481e202418cc6bc026f8a8f5080a1b8c72a0bbb270bb865e32c6ec5170fcb3227ccd661dc23f86fee09541197a7c57fdff978ca4167ca85e6b74e1e1457501f062a78a643e74c827fa82798704a7f1b873a263e6264c0710d3e30b4783604e1fd513cf48442489482864774d2e0541d8a09b7a39de5724923c83e54f8f148320340d86d4da3d6c23120dbdc3ffe74f7835c6a616e8c5d8380f1d3892349a09dd4629a481a54cb450f1493bd28dda18460780f77302a7b2fa46be3eb4aee7ea3cb7902f0333b5c45b3984afe375072579407883d08ba7d7f0154c321ee0b3a04d61552d474bbf41c63d8f20f52fa3d27f77196f29caf01f4610dbbd8084ca901d084902ff0e4d711fac7265d1d7963504667712385eaf39354ff313862a5276ae32236ae4656e210a4ba5cabc6a6e747ee28ec11ed9fe17c6c001ae02e5ba8cdac570689ca5eb02abbf59826a0f61fc62193ea10b8bbffbe67b45b5406f85510547dbd6d5b45554f0eeedd8722b4ab0fcdfc00802b14837405d70c480235b6e17760984fae7c03ab1014d84cdafe063dee40b2d16b067c096b273fd848df36384ee2ea036da3e0f06cbbee843797be6abb3d05336d385dcce7e768d8a7938363faf0e561631718e99f963b75631f6aac554b1aaacb4def7e4faf2fea22ce0701cd97b0ef0fdf73634bcfa904dc28dcd3f9996f383dc89b1f2824d2b97b23aeb372604e8da4fea495533cc73266d8ab8d5b7c803ed2e99499c94dce02c15d17d76134ba57e665d0b6e51c7e4c2d9e530223fa8d9f33d1cf437737698deb89f1ddeeb95ce8c22ee26e8f169faaf532645b09b25dd3f0d065c8501c457bb16e8ca9df6197123cc3909c7a8da4344bd4489df2e68efe958fd429381046a917d69afaf2a72e04321903c3a898e56fe240f51776802b15a57a01154a866de2138adef29e952adbf0a31fe5f4eee6ddb539caa09cf6c7568a7d77c9e8453eebfd942c4f74e9dbc35757c1919ebd61a131f45c9430d860ff70be7219691884dc1047ac6660669056eb94379167a36cdfe0df090cc132ae8932428249b3ad0cc180ad95efc13623c46f4697b8d3fe48d503a72d973df3ed341d026760a05f599b4dacda569fd7315a01c78db8764c21eb298ebc659c1926d43af1a6847bf0b2e2fcc465125bbd18db860c238a1d7403815ec4aeca59aa5e5a70e81fd3e18a0a9763b5a374e1f7c148d57b0be546800a0a8293d0284f7ef4e46502c40f58201aefb3d1834a843072c33e45a68cbb38a6145b04848c54d2d35482b9d6f6cf6b522ced5ebbe8218d57756f171f34ffadb9e8599400d511ed27641e5b4d7089d219fd6c7f2c236d73cbd0232483d2d8f7e0678370501c6a290021934171d48ef163a2890a984558a99b5353d02daedd8e68893f376e2a5c9dad5058710b01717911b471b9b22ba290f4fe4e5414f083ab8035e65394407702c47f0981b0c92df86aee840643ea4e293c7ce9a7639ab648bfbb46998f16543bca02f429042af5a7cc592092b292743500e37489de96e11130e3387356fe3a5e72a77ee977ec36ce47e21c6f018470bdeb0ff5cf1da3204e5c2904540022c4bac03fed7ab7f6ef28e7d1c4c95d5d641b9b2193b2941b1023698d904dc82664efbdf70e8b0c930d2f0c32c29ec3eb23875f0f9a03c6e6ba2c9e10cc84b3e464cc94f3bf0586b35bc74dd3272787b2739e1fe69ce9413383302ca99daf73aca3a3131659691f9f1e0ab695c8387ce41c1a691f1986778c3cf941534776ce61eb223cf354e8f15076ebd653a1c75b0fa239acd64578ebd113f28c2406d93ddee37a4a64f7c8acd6759ef30966309c038643ede32d9035240807285184d8318a20c2b34e0e65681f0f9a2d30b60860383b1f6b5d3644462d758c22e4474fe819a4323f00107fe101f11388c79807c1104001649c65eed8ef80780b889da6cb3e0020ee325df6200fbe9ee9b2ff315df621660231d3dcb1b7398fd375f3d04746e2e3f5485acf20668a9e35f291f356ce9f737270d3f469e5301f37e5873aa70f5bef71dc027113ce325d36bff51033a99033129d5b27ad63267b447562d0f401c66e81b17b80b231136eca606c10b446a075325df639a03d92537cb201706f8ce9f345072960a64f7cdb7b50d6c8de1e852e1c10061618eab801c3bbda01865507931a366068538399c2cf7c5b149b4efdbc43af49eb505b0ab558db735b948d0c0c52d3a260a01d6a516d0f01d08952e86a2be435a5a01f003a510a3153db29da6908a621962c1149ed39794dd327c68888d2f6de16ef09536f887c038e8e51042a8db87ca1d764d4f6f2876f768c0df1a5bda6ca3232ef80089b3f42c1dc80873860527ede9ee6a90153ce60c50d4d3730e1b0d4614f8de150856524d21dfbd8425d8f3ddd891d39754ca363ec0630364a1869fa0a863d4454bd8a3cf2374e5fd8d3ab8e307917e84fa659ed3cbc1d7cf5f51ed881abefdb0d1f4b3b4eb3b4fc55ec86a78fc8bc8d175ff64ae957dee57ba253920f7af00324ca402aa3c84889246c0005962bb80803a3d7c0b0c7fb0b3d9d79a0b60d0d290979eff250772f47ba13a90c5149465a7ba43b4154c950cfa5ae19185b050f8cb06ebed0d3330a4fcdc9633740693c55311bcac8d3634a845659f327e794443c7df1b863cc86a419b4ec5a4a29efa2afd8d413a49d347da53127b1a6ef6235f8d0f438b7a747e36b0051b0a02983e664a265af6e4fc7ce87226ed7f7d06c311636d7f9337cf4d4d2d38517922ca9807c155529638725ba68e225892e60744ad154898ca63721be7ebc71030c5c00c506993160347f20a8e00b5b2d9b4c61f59cf3258daec0f49b2e5a0427f6856f6ab34d6bfad00805c215f5c69af4d0e1576133d6044ac768f8d23146031403748cd120a5bfe913af83112de9fc6afef3aea9467c2c81ce644359468d08f1d0f42cb4d044531a436ad234c64a20a52995934e2c4d69144d7fdf805d30c69d4bc14c179d82e88b5ce2c062412a3428c9732030f1bde93ffa15fde93930c69d2f7a4364941e11d05edebe60d71af8234a496daaeb0b7ead81e10f971c322f7ba618d266d6da696bad35cb660cb5a77c686da869f6b56ab7afda91d83ab96bdbb539b539e79c73626de26b1a0c1a9e785e9b73c6e6b69bcdaabd562db3a016b5564da395529ad54ca30f65d390d6d0fe86f4b59ed352abadb6565b6badd65a6b2dadb5d66bafd9da146f961d869eceb21255c74b5927c8a4ae0f63193154cc6df9280db95fee61f69a71479251bb9dc3e03da79db3a7f6d49e5a7b82aca0e5dec472c716061abae25a3197802ec10c5dcfd51ac658098cbac2203bec6098ad8130d0a6355b227b4e2b3bbb02dfe91343d64822c689afc79d102b69855f8cf14621b94aa42fc6b648eaf98e6db1d43690b88d2812be80a20966c1089bc79124cce3448a438f0395a3a3174a2035cde1d75d429ca69122c82a6b952dca0d2a6ada00d3636464147303091d3ee8a1e55310aa74b6600345cbbb40f344cbdb4816ac609a606c1a49427fddca386166cbc949234b03be318b9805925863adb5ca3b7de4bddfb75acddba604c418632caad222d987b2b5cc43a1080b2dbafe22b1cfecc64929a59452e658e98bb47048b0f46616ad05164d1fb5682a3b34404fdb87040877acb53166d1554bcc8266da4555522d706a4eaf8b1173316e774e6dceccc65863acf35fe489a71dc618638c31c618638c31a54a7141e0d82ad3b12d529ae6253302e5702153a52317324c1dbde8c8858c53e389bbe690769d24c4fe68cb965f6c8b0d2d2f67cc8504166260291788316ab204122cfe7219a28c2f43f7044424c164885a78a981128c7e48330326830d13309aef1c413e7e0b17671c8d41430b9cc0f8c10cbe48a424886882452cb0f88f0565ba5481c55ffac2c101b6cd993f39ab09f08d0e0a505958409694efd8e293cf9973ce7900d85a6b3dadd3156feb9c334fd78d72dc9c23554d3d81371670a92da4eac68a4f626ebbda560506c3396763da514a69d0f4d1527ce10d4012456dbd213f5a0902fa82606ff92c08c6158004980133959491db583e224f7ceea8c5182c9d8c3566d11883e925b3595be4011b638e1ed309cac61e4e4d19533195a56eb27ce1e5b6aba5baae951469b09681638c40b6b5327610a5652723d0264fe765042212e3900c0e13a4c14663ad93d50bc1cbd7c9081482974f4620ea80c806145f705083274f682103588c3b3202792178f9e431ee6404f242f0f2c9e38dd62d6e72bbdb36336db3aa5438d4f36b7c597cb22317a6281de769ec8b5e7c55055f288dbef8c2ebf485efe20b25d2edf275b962b020dee302e4e20b6f532ebe1a690bf0dde29b587620185fb8eab9cae1c9174aa3e6e1d37c20e59b5123ca174aa4f6e10b6354cbd3b84088ef8ab1c3e771a183297ace00f1d11cca2722171d2cf51453330494f9d9737e34c320bbebdad28b9fdd0a88cf06f79cddd304ebc0c9375dac2feaa9e55388329e45e7d7f29b2e16cd4366d3f81470e62be749fec77cf180c907d1d2f23f60c0e483d49869b906989617a2337e4a7de3659d5156a8bac64d998f4799af8280c527df832728840acd9fecb819f5742c50e2490d239c403103041b64d08221c2a451860698fc8de39347d3152f9d4c573c0078f0e5e4f0e1b08789ef1df6e440f9a409e24e3c1046747cf859e9f8d0d5327e3d487600430836cc2803464b05d6b841153492686206338049bac3838d4ff66643fc7be47fe483e2aba80452625bbee43005184e60f245e22b0130704a4307a51364f105263fc1d8b267472e6b98a057447ca17ca2e39cd60b2d87ec8c263e1c3e9c64d5f836266c189fa645cc37c325fbc15763903df9686890fd10c6dee0882abef0e0eb72c041259319d51c683ca82073a288ce0724f9149b9cd474e472451137347531d36c3aada7b3c1d13a7261e34a872d9a038ead705691204c7972723935fdf4e0c001f5e5875e4abfd027ead474a911730730635cf955972d2da5943d160731503dabf0663e6849df777beefd61496239a273226d3c48a316e69c64666d1b333e104ef07d71226afa84f31e359d512edd91a7f3de1f496b5e40adb5b5d64a29a54fbef9706b2feeccccd65a29a534bee20a563ca67c453a5b925a6b33ab65d6da5a6b8df425e3d692ceb48c9147766ccd21c08ebf99d93a5ec6685e9c4facb5daec95524ae79c7332a9e25b344b5a2d9d5d7a76613e69a5f4c197be71bb778bb27d6cd18bdeae80ed3702dddefe2349d0be1d482240c22250fd97e6be2f4b12e8bd1b7a5eee22509d9284cdebed38871e18ae624b1979a48c35c5dd1c7a3da5081d98a508f22bfaf278e4e869822356c5168e51c6b04a9cae12478a301f65a4daa346636e4d978c3cf3a1aa6904b2245429c27cfc9be2d4cfe3f8b2893b1d634fedb4af165ff82385a2ce32adebf4f9d4a24f8fe89a9fc7f36af1e155e499e91b79b48751a6108122d0cdb40ddbaecb3acf2b92ea34af01dbdd52d983449e790dec711120fac4f82302dd1c06b9b9769147be7a2ddf63fed3c78b2ef92b6f2c665db7fa3a9b30344dfb2694372a97207e40007cc5a6754197cdd7ad6e05154f307d0ce5abacd355f39dae9983cb17c3db928469d473522d9f6cd9548279fa5edbc51d7e1f0edd6246989ee9b8c50c16b4eae9b379a175834c97fcb13c7ca966faba1cb47c9c0f6f7c3829f3c1b73151c77c9a1627df0c17dbe4ab1db9dc20061605dfedc8e5063cc88e3e0f4aa6836f6bdbc5a76aad872f6bd78fb943654bb5822f8c4d45b8a48144e38e5cd87862c178d4d3ef5dfd3841dd7b314f5daffd9219752ad5f1768b93edf214a6e9ed189669bea811dd99b6a95f3469a9e92d520f368a9572c60b9ade26cd970d8cde3a99af1945a3a85523ac65c0841133831da8315f374ff335c384a1af4d34ac4e4d5fab4cfaba347da2144d5f99e62b078c3acd495fa1ccd71c638cdac3189a32d5088a054d5f9dcc974acc7ccd2a55a2beccd76462ea499b664e4f9d68172ba8687afa345f18464fb1ccd73c3acaf29a51e8291424da4396294d67602f29460c3d8a9a2f49860c992f2aa83042bc7474992fe924bfcc2d4d533e4b4634ed602f59651ed1cb28af4c114d3f8d6498f9924734bfd7b2d0f69987e6b7ed1764698fb0bb64baece96abaecf6e41bca4c569fbc119d96092ec070318ab12750745a217b4e9cc2cd121c6dec1833c68b580e59620c87a6e6c1adb5ca7735ea8b13599aa7e3972b4badc59406dba963ac08a67e0e0c4b6e3be0af0948e0f6199c65a6cb9e060039940d80ff982e6b7f7ee698e9b24454ad44c631c9cc310fb8bdd765be641198d5849573161ee98b9593591388c08ffcc83c0b99487efe48265212929965e4c8594233b3721e6111c8ac9cac4476844598fda6c367ef39d97b4ff6de17fbe8197172e4f22d50d55fe8ebc865db098016cadcb19700687b983bf60e0063b8ddcaaaa61281e395119fd71481809c2250a42f166eef7da1af79cc4477eccd63f4bcb300da234a653e684600b451c02360a43b203c7a2b4cd8ccd688eedc1cf1852b1edb169f1ad365c530511d39f946cc16c56bdaa2d09750114ec2eb357d31bea613ddb16fbd083146f4350fc422b5c0d99a4ecfe1aa53d7c9e12af59c734eb6c75e530b9c3f41264c9f20b72f327d46b89d4d9fa52f08dcde42992f09b3b746448cd1f6d6096e39918f03d602653301a37c2166fa3e02b855992efb20e0360508b8256d51681058082c050f8189c045e0246c043e022381a7e024b012980aae82ade0253013b809ac84af6027f0127e024381a3c0580a3c054e010e2202ad707b2c440432727b2c2502a9707b3c44046ac0ed3111118801b7c74544a0146e8f9322100ab7c74644a005dc1e1f11814eb83d46220215b93d9e128188dc1e27118186dc1e2b118184dc1e53894026dc1e5789400ab83db6128160b7c74b44a0be3d66220205dd1e37118112707bac148110707b7c25021de0f6d8890854c2edf1520422e1f6f8890804747b0c450432c0ed7114114884db632c112800b7c75244a0116e8fa78840416e8f53108180dc75db3b0f9a5b14fad2a27e7a5ccf0f57395b94d61665fb84e3e33ed9f8b20022a977c01b1579ec570037a4c8636f04dc4a1079ec5500b71d228f7d03c04d87c863cf0070831279ec5300b72791c71e0570cb21f2d82f00dc70883cf62780db51e4b12f026e37441e7b22e06643e4b11f026e35441e7b21e0e624f2d89b006e4d228fbd02c08d86c8630f03b719228f7d839b1178e3883cf60900ef0f228f3d02c0eb83c8637f00f096893cf62580178ec8634f0278df883cf640e0ed41e4b1370078c9441e7b11c0cb83c8631f00f0ba1179ec47006f1b91c73e0878c7441e7b20e06523f2d8bbc0bb83c87397e28efdcf73f8d3e323873d3db216a593c3f778cee1cac77372d4dfca383f7cf6abd54c9c396d8a40addb08dc8657fec8c3d55dae1e6d89be22908f649967aea1a9a1a979b86a1adc02656b48f475976818607cd1c58b2e54e014e3c2c5962db4c8a2490b165764b1a20a2a985230c59522025d2c378a0874a1b84f44a01608c41752a3106cf8725a390754921379c2d663833f7c3207945dc794993ee10b6007e0ae5bd7edce2766ca327d746e3f7d803cc8cc33b77adce6b805ae1e04fc3910f0751f30e73dc025b9637feff7f54aee84ab5efd80af4f4fc8ce25186120080a40000278f48408e0415300211c0c21272704f0d11312c28326fe917398f3aca365b6d219ca1fb17540253c3960ec0c2ad19ae7d113c2f3eb1589cd73dcca5a662b9dad7000ff3180635cd47a106d813f94683d801f8f9e9001044d3c02f8f320e0eb40401f189e5dcea72744a7f3275861156fd196b428a24f4e6b2907c459227dc5a82df1b875b58e872e1d0f3fc2c35590871f90b7ee0d8f2fddbcbbf43053e4b10fa1868f95f3013c362b87e73f2ebbf5508be23c1572b732061375f3f062a67055f3f0fd273e49c26f4f6990d406f992b7a73bc8d7ecf170a5f3f06b56cec3dbac47f9d290e88e3d2b3fe22a409c054f103b21491146c8e1aa75bc470e574172f8b58eebe4f00392c3db3a9e93319314e14e7c3e9e7b3ca705623197a9f587ad5f2c44eed2659a3ead8739bf58a6cb328577a9752439f9dea53fe73807bc6f81b3b528ed2d90b6a6833f07ccde026b6b3ae876a045fdf65ad47d7cdc9e4992e0e33e1e3da4560e654e8c403d1e0eb5ce74e5ebe430e73aef012ea1d153a147ac09a3eef1dce3f93a20cef1ec7454e89171971edd634888c5e4801549eb3999e5e30749adf39c30d327e7ade7e48375bac225b47354d0794e0f103b75c9624bd7eea1937de41c1087c9206e12b3397dad29bdbc3146b6be5c49ea1f687cb982447fa0f1038d480694317a09550241ce182b8002421898cc4133e6e470a8c6e9ca0197cc30b280062d23a3b007956e9d7a425a39094eb7be397d618e417b00a776e9fccde983211b75ce1ea85d72c6782ad33cc0e8d6184f6a747ecea7a7424ea672d4039bcf1949eb3cc0e8e8a1c0038c6e5199598c30607a09ddd2f4e9005302414e4e3e411a6135228fc562a64f2bb386e4dce53d7278bbc77d80e16d1f4ee45bc72d30bc9d730e9974eb3a398f5e2b3fbc393e46e8f1092ae930e91ed7c931f2f4f001c67eeb39ef0186b773c6f85a27e438616a51d327d496da763ec0cb94c1db02efd2746d11c117635b1875ed160ea65d4e2be32e44744eabce7354ceb473de0261c85f92b5ce193a1954928096537a0b28e2c981ca18cc74d9d631137d450c66fae4db63a8e9d3e3f638ccc46a4420b92502394d9f2e11688c08f465e932b5ea10adeb803f42da9a7e74c8448828294643f8d0ad90c9104f43443121c2888c235e70c4194c8e688333ba41eba102318b984ed0283824ce6075c804093810d021132d4f0580e26679f2f2e406d2d20aba58411a374e72a660c1448c2f635c99c23671c1840c199091061332d820a307dd7a0c9978b1620b265ec460e2a50c265e5e10eb9049194b90d121932f464cc070018323bc70418216484182283004d02109a27a4082a838260ff587290455e2ca1446580c1941f194800b420b8f0e8766382306340c1705c7d00c71dc900392142396681435055f3409a349d49013249f0e879ce4201caa61e9061d0e1d29a951c5bcc26908072f38c46008071c0ce56014d3e870e809d2960e879e7879f282a12737d8a1c3a11dca98d221511523a2a51d9a3a2c0aa229883002d06151106e704639492a2700d06191105e84802a926224250d293930a3c3a221c2a83850512282063714a143114e5e3a2c3ac2880928ddeab0688a302574589482a42a7cb082072b5660ada85ecc2f56507485972ba08aae40e30a37ae80a3088b282cdce08c6cc8a05051a4e58b222d60828c829ae4b0030f5e87414948042d1145154158046511d4c5081db6514e876d04934e870dd56274740833ea960b3a8441754b4a872618f9e8d0041ae07428e409215bbaf550c893903130f024035334b0012a5c9882035220a1f28214dd7a0882272030a35b31f840440c4ac4a0654a08c6202d31998289971d302123822b469034822a23784a75480217b43a1c3aea563814c4d011371d0e65e9d6e3d00aba451485a643a226b0681d125dd1ad9028460406d1976e11e560a6431966b8a1044024d90e65a0e244b73a94818a6e853234754b862ddd9250d0a44a44d6e4441b63f474dd689da706e26523d33a345f3a488d0e30375d8000c6a709014f1d249349c05303699ac2a875b23490a7a86e690105d5ad20b600a389d6d932660b99d6316a25318e38dee8d651eb34c1055403f95226ca0da8324f5e620cc07c21250c32ad33bd8069206e34b5ce180da44c1a3b04f1833044d038a08eb85299a8c20a11ad6384a5818019d340b29218471c65ba15e6a97572eb587a4aeed251163a391401946e1ddfd0ad63ea29a199217a28e0d6c96107cae816112032dd3aae1df5884c2f36e896102537aac1005ae43003ce3acd1ba2cd00a95bd7bc219a135bdc7baf7635ed5220153241028ce68415cd091cbaf535ae84e1c4d11529aea0000eed0a189ad20b34a52cbaf525b86856ace8d69b342b4740c154ad60d1ad5ba942e90dcd092d8428b99a87c24cebe4900466d460dbb66ddb280e52e1901327d5ca15d40d2c362f150ee13003214a6c8c6aa81e0a34ad93c3a12751ddba1b52261851619e366e1aa5c2a224a3e9450c85038e4ef3884418b0d1ad5f6f48844111ddba0bd620030c0c8818038a0ad228a2a20c325e70b7bb6d77bbf7deed02a9b0c88a32dd3a972a5b7c71256e8143b78e372e01a9b0e88a1fc42d8eb63489595439617618b405b76d1cc771dcb67132a4c2222d6b649e1025397a89ce65671e0ab57572d85430e638cc711c46402a3ce18d0464d16286eb1101cb74eb9b37e44a701bc76d5c08522106aee0bacd230200282044d1ad73de109c28582eb8695a39e140b1a3071d4edd3af686d898d1ade3aec31e919a33bad579351522f9c20606ab1800b4d0baea31a0a67572084353b78e71c779442e15dd3a8e36158640a95b1dc6b8c31d980a99dc806de896c7001e50dd7af416a091a931c3a7866e2d209e1147b7eee93c7a0cd0c94a641cfdcc1ad27a0cb7751e5b278b41e54b2f9961942284510c20a6f412d9548e0e93410b02a604029dfc02cda1062894e8d65f10810e60c00615dd7aeb0f9a3a606c2c3d14621cdd3acec9b96d01a140fee77c49d69f9df31608c3bf246b208f9e0a2d740b08a824a75b3f98732cbb1c30942914752bc7f823e7413307ccd7097d3c7f87a88e311db0bc3ac674d8a2c553c758115f78748c6981a5ef12b577c752db29cd258e1ebac75bc75c3702188007015d0702ee5c824d92b4c01c0f02f61c084880e78005f8f51c507b8039f67f783bc7d7397a3c5c758ef3d091750a40801e1077f186c8ce377c8f3b2dfa47eae391f678f41c508d72932454028dd3318c329e9a7c190931dc18680fc95a898ca3a3148cadf8b289f347cf3b63824fce0c21af0dcefcc2079d17549ad880f1858f29014737b32431a6e5ab0e56a0602046194138c558136eb8e185951958c90226e7152d9548d25afe27a807422cb4a044162d9f0117e493c833993e52d6e9436fd0f26b597474050e787014c60f4a51d057910f2f18e3e4640c236af0064c4e2b5a5e2ed94ba984186ded57f7deead91b0495154b891ae8052189327a5ed61b50abb9a60bfc01d279ee114667664de0be663f52cf65a2fbfaec669691ecded7cc9a5b66cd08a32db8908409cd92504a29a5943bada79442b9e80e7dfdeacf1c81fe8241a0115bf3e94ed2372f5b542b92b3c6113341172de53f2a3fa40b93375244cb0bb1d0820b49963419492732298d96b88a5c924cb2496ee9914f52c658125fb4e45a5e92919f46f2f368fa14a0bf9934a74ca4e9535b4e69a3b7d7edab7aafb7dd28e46e01d3344d8d295456f5006d9110d33605306bad458289b6d6a700fd04a334c9a9a9172388687a1c1a33c28da66fd1bbba344502461fe488359a4c37cd6f9337875813495d4febb1ca66fad4aa699aa6695acff4610db199aea869b9eb8e669b3b8ff4c9474a7348836696b78dbb0d8761ef04ed34874c9a5eab791b213b072ad9477bb5ec1abdcd3292fb815505db35472942eca131dca6549eca3770ec58e9bb08145b1ef338a185a6344bb9832f36d56eb54a65d217635b2035bd7d0452221b04458dad974291967b42ac3ca1d6a61dbd133e70b6a7643bdd7e6ff625595f18eea6fd665a8ef1479643fb21b44850c7f823b4af43e8e9ededac295835c9941052c4e4aebf1705520661a56bbd7689a0948732ac1875bd4db692526b105bbac682d8810a624258d1f506a91fbeb0b65cd3a7ae5ac67ad00256df7d36386fb97e4c9f1f5d51a083ae36989082111f7ed0f5425cc0ea2bd0c295ce81c9eb18430117fdc3688bf601e18807311fdee8fad54b29b11fa06495e9136351b074fd0b964a106340b4a06b7caa4d7d658daf5afa692145899e9f3eb2074ab4d1f5265460fa7c5ddf0212acd6275952f3c50246a94ecfc727ba761807d9259ad74e412dff3ed29d2369643b09d67e643bf723da71e6386d4bc2441ac923ee12c6c95ab7074d1c8594a4e2d7cb23fad27e5d35624d2b32476881bb3f514be2930e8830274eb86fcfde930464228d24d24f9018252433a2537382215004ab97ab96607cc9af2210504bb9438096a1015afe06896fe32e7f5fd9e58bd017bebc11faea8e9b7c241db160f23d535a8ae1051a6ed0782166ca6c8a5ea061d4f2d6c956e92a5ad621aaae1e1a5f6419e1b8b38e6ce0aa29b4557a0da8b776bc5e4252cb07c578406a792333da2af4e5444952d1327a932a2112db85e992a79e0322cc49f6db814e8a1e1061f54153037b2c18846f845570f5e4b34bb689e992f7c07cf1f6e97211d555ce97093bf55832a919274afe992e5693163417e88b25336b02f4f347b2db1f99d7b23562e96766c92cb364fc22940b49981871624fcf84beeceb931ccdec11caa80517923089507555af15b160a43bf366ca17de85e913e2b4e03d2a2ba24b19ce044eb84c61d2818425a884124ad80125e3f381a6417d701895f0936559f6f3f3e2a870c791ce68dbfdd1b64dd38104858fa41b1919b9d1450c7cd0ba8042daaef6e3626a7a9714514dffc36886ceb291e9028bd01dca714e9a06a5554ae924418724e84cc6ea05278b192f2efd940cb5bd5b6a54f5a04ea174061cd418d3459fee13724d46eefad1f3538209455260816be29e3827ee0cee294a3ff2ec50fa428e8963e2b27c9483f26206234dd3340d6be0ec2ed56666a8913ec376ca586b2a461efb17dd5b430b66b80962061c2ab29f1256f1d2e89a3b94933f7abe36bef02d170b5d5f017bda42114a7f4b31ceacccd934d21869a4d3132287d01cb38c8a329f1a9a46430c5d5ff9fc8b99b665c7f7ad0a7099be03637471dd5bd3277fa3294c17cd66f03c5b443c8e96aeb3c1c169b57efc3042ce39e79c73ce39a39c734ef9386966d9cfa694d2d9338b99c999653393b34b0105f10bd287f37dabd5dfd346288b875ae5169971a269507375f3469957a7dd2debc0148a4c173d0e175ff8dbf0850cec92d8bba4bb606572bb2eda2b4b3d25b4500131528a0b49963099af4b27509af497248b4430469c0073b54b055c791f97f485b1292888097fcfbfd48308346fea8c72a61e79ea1f81661cb882746a734e2db606cae89235d2d48a360b62765bc1d855ce2a6595b36a6096a95c7ba7a8530e75d074e1bb354685ccf783f8ab81306908c1830cd458c20658fc2abe3a20c61638beb0f2032e6c008b8f09d01e6557ad83fc0405d117cd2c39334b66d60adde98f78e732d1f4b49f08745bd3b0b6695b66ad80affa9154eaaa54666da9e3ccdab49e9fa022a91ce94ed04f4f77ee59666d8f307c7c7a218f0a61ad40df5d68ded3b4db6f60cf7469e7404db34f5cd8a66d03230cdf70d14e6137b18f48293b11bdf67a177d11cddf5758d52e347fc34bafbd86aafdae08a4755d651ad871d7345693ef9b6b23f29e8ab07a57cf4fdd720fdda9ae5fd3b44c7b9685ee95302c1ac8aa358b65bac5629bb6223a6d0b96fa4d7fe82b3b50d3bb2290aa4f6bb0d75ec3bc7cd06435f9c2134d2225a17979227b2db3ea33177d69f6f24256e621a59a85e6b33c541f6143f336b32a8cfef42efa1efa1fbb548be69c49d48fbee64fd0bc5b35d2f31381b8d7a4afb5b29a7c3df4550463597b2e0f29d93c74cf65961ddadee52258bdddc0eef747b8e34cb49dfbcd44f84417df74f1d5da03e5ebced51b3a76dc51df7717baeff210ce4ad672d90fcbe69e5aebb6655a16ba1f9abfabf7fc041589ad5a677605526b932e01319d168cfd241108a8b5338940b8357a6d5ed364526bda53ddb91fc14f65227a25219a59157ff3f447e6293d11acbb37097d4558eaaa1ff1de65a2eede559948c94577b41379d71e61daaf79b9d3ce64bab424d3a5d14ca4544f443fa510cdacecddb31097bb2cd49d7b977fe88e76eef89a93e9d298d01ded1364d99c64bab4edc99741c98ea64b7be6a4e9e727408f3f336b9b00975937b3ee85e839d69695c82e02334277646f3a7c9af624da99689746dae5912649d03a9e75f30af4f813a0c73952ed1398e7720b74477bd0cc90ae0c7085b16866ad80affd08f72c1365444a42d9b91369c78f30239a9665d9b1f4a8cc00654168bab0f484187182cf7942a89221f512d72869ea570359f42c9b592b685a966527daae3dc2222c0337037bae064658f6820b59d19c75ca475f97b502f77b21ee37b3e4ef8b6042db59320f29c90f75a7975968e2cf17c152b7309c023318ccca14a8c168a6bf3313cd7b367521fa5466c184e66b9efa919aa732919250ea342752bde64768fefdcb44a9d33cc282266619519de58008539d7524f59456614041b60b19982ed95a0a6465ff1e612a50f5991f49fdcb44df539fc9444a42df53279ab9ea1196d17ca99a195584d180113603465884a5b2789b85e6bd2fc118ec09e2ea61188c300e8cb05a292c7a4284e86f66d947d859362ba9b5de55df53df15d5a9a1619f3ac31f7dd5206010dd895f19f1853d9d6960ec2446b2f74420a0cefe1381e68b3e7b11faaacf8e3acbb22d874f1edf74f105d19d9f9ee982f21129d913a5fe5d3eecd1bc7717f2324beb6eb3502a7baf17aa17f22efd8ef4c5b697f703599a76ed16198136aecbac8bb350f7a1ef5d569debeabca1b5e6c0d9de3fa1ee5e1e52e23e54df9dcb42debf3c44efbd3550b6dd325d590af440d67d847520ebe61ed8f6e4b3d93a4d57e681f9e643bba5b357cf88937a3a7dbe1b8460adabd387bba1e3f4994775fadc1b32eda656d3955debad045f3db54f9a16dfb68117fca81738b6c2acdaf8a28f5ec7ecf445730626516214ebda83a6157dddffd09d7aeda1aa9f6cb6d7bec284d1c853ff435f1288b8535f65955675f8d3f5ca4d7e5919f13dd10804d4f592897ba5ae96629280551ad19d4a77aa0b74e7c6888f698995fd3ec26a96592bd06f3f723fbfddad3e830105d9426499e9aa8f3022b2232cdb841871e2c20459d9234c32d19d7a0ab222ac29c2b6275f129b6bd6c4c5b7baab2728eed4c79dda1377ea83e24e7d84dde8aa8f31ba6a92ee3aebacb3ac64a86d8df5cb87d3ddeda3bf3f32bf65a28d4889081657f4c5d2ce3dc2585a66add0ddfb117c2e1371444a42dc71666944f85d6669abbb7a5688302738bbe88e5d41f9e8a7112735dcd3d7b07d1e4b8f658466563d7d112cb36aaeb508a681b171368faa24a1ded02c2374857b7aa17b9a8794b40fd1b806561978881ea8808c0d361ed83cac790ecf53a9681e82618eab6c52a99a9a9a548edfdb609a474e095d423f265fea62d5b9acfa3c6ff5a9feb9707398ba77962aab70bdb3525905fcf97d3539cc9197d0fa5d06c8630edcc090496f350fb9e7f8068648b83cf39bdac0f02db45766e583afb725943bcdb77b15a864a86b50186a24925342dbc6a6262fa18da3eaa68e55395c42b787aae3d52af2b4f0a9529f0bcd7dded4f159aa9ba4b9cf13bcc7c8b33d64b284a69efabe846e4a641cb12d18036d24f19d863b9005821354df9e3a0b0427a8728c3cdc095e8e9127f260bcc4feb0c8608285b89743106edc9cc74d2a85718d8a07c63518abbe6326a5ba398f54eaf2372f749da2015360283be57973f3bcefe8b82d0586de5337e7e1b9bc548ac75967dde40514752af59b148e974ab19e3a8b070e8e8d77162b2fa0a8bd6da3016b7238934a75a91d9f79b7e3ab1d399cbde33adea552341dcd8eeea6deadbe43c70e30f60a0c6fcee32a1e37f985aebdaebb2e5573f32e875d77291d3b76a4763cf51c3b9efa8e54bedde57049d3d43cbe43a2e33436ef72a823873dad2334d239523b5239e471b33a92d4bdaf52f79efae132b2e29143168f9b9b7b37f77ee31da702ddbdcce29159383836ef1ebad0a9d5cd4d0e5fe85899c53a121ebf398bc76fcee326c7e95ae5f0eec85d0e755cf57049a77298e3343ac0d8393a5055f3ee5d8deadb69e2bdd49178ef9eca19ca194b82d021697ec0b1853110ded6c697e3a18eeff0989a5a7ee6d1536186e370a6d9c1711fc7d1bc0b55bfb7f16668c0101c327310c9cce575804b6acfcc78dedc2eb6b1f138cc6d3335b739cbcb0b286acfe639ceaad931f399991c9f39cb66c70e9bdfb372d47898dbeedddb3af0cba16a26c6b41463aae25d7645f8df4c0e67cfdce6180cdf2d74775720883357fdf8fb4c2a47a8e33b4e93230788c4bbfc0e3084313e1d796626c997e33887dfcccccc6b66ba9c2387f13836cd3c869cf67e6a66726853e365968e2399b9771d33f73ee305c991c39e20974d0e733c869cb6d9b1c36bbdf1279e2f2f470ebdc790d3396c326bc70e9b16bafbc435ac22b17364568e23b179cd73d8bc6626d724e98e85c2f799cfe490a8bfc7dede2da9de96f193cb4223090662060b8ed3a75e7bb40f3b9b9dba63c15a2b2d9a533e816363d5a7eb307cedb8033729a6edb7141c353d0b85edda53a75390697a9a635abdec1a186e47e25da3798c3c33dfc01990060cb77f9f9e0a1b883d9acfed952687dab75b4f05fb2d63958671a6cf76c2f619ef33e07655eaf3aad4ed5399959d854277efddbd7b5e6663af1969226147311220dc3e5ff38b8384bb392cea2625e3eae03c65a9d2b14cc7a72ab2506dde793cb5da4d6d0ea15d7ddce0b3cf6ad6350b75c87ae1476773da8c82ed985baffbd44303747c48808e0f773ade34f770eb548e64a2187c2ff77d91d210ecfc0875eddb9da93207e1adca273da88f3e9cae48a5919d954a4c07295aea74c96e4a09c4185240758c4931a5c304c8c699ae8945131d635848e9103ac6a4c0d2aee963a78ff6f920130108383aece61f73670a91517c5a6ee5d085733f1b7cf6610cb24319e2b4bae9ca7535c9c071fa70988b6109d3f517cb98066aee5249bd8b3d3094a1bdae939fb5fb067a99bc772512996390d8761c91e811c1f9722a00359763e4f172884369d4f65238e93a3b798f1f4451a8f75b96a287aebfdef109408d7fb9576f88f484e077b2397c9c43fc25973bf720da81e13dbdf60250e3ec01a08681fb12ee941e831cf7e8ddc8f3fb0b46392f6fcb60567cd75ab3221636748c6171821e004ef2c9471656225bca9959fb54a1b294160d63b9dc12db1546709db536e3b82b9c781d6357f4a069dac5580c47a6ebb66de3baeec94249efeb18cb8245dba47014aa589623be2e954aa9be2f0b94581629335bc7589619683a094639d34d57cc8a36da86c61342435353e32991f156405911d575c15b0d3ef9d86169ad05533abc63c4ace8a1c3aea3156158b1a56d260eb12dc8f40d968e07d331db00b1e9185ba2071544758124a380e23d3ee24c7b468f23d117634f906928aa04f9014552cff79420a403636c0b248c602030f123480926f0d0c1d73d7a47baf3a8c197f2c008c39f0cbe0e8c71076739c2c0d0a6b3ecf6b331777a2dccbe9dfe7213e7303b7d6f60d75d7a862ff40b365d69ed8f8896effd633ec87c09f175636930cd200e34c20002366f427cf12811a3e107323fbc310636b969d388b1276cd031f60454b368beaab72ec0134f2a90f225c7152c4e4b5b00a0641413e304055c82ccb4a29b4b6c5706d44c8506a5fa8d070dbeba45adebcc7e9dd9da93058eadcc66fad45aebd07da1b3af957281ef0e3e293b7af5b1a55e323bb6e60db1e7078baeffc4c2ed506b85d52aa274952bc8a47c351d634bccd0614f46832f836387366c12dfc54156058d4c896f334343c1977d39faee9395ef3a69387c9b96acc997519109f16551a0d18426e5bb53ac159f364486c647d33166e54986839519ec951e68f9b88eb12a6b208186dde1bb1d6355ceb0467c5ac758952f559eaa54d178505d2cb9db36ddfd52e1b5d9de1364ba987a1ee739dcf20bad9eb389252f891aae89a3ee87d45685093fe9ae15230e0c95c64494ed0446385b22e9da6087b5d2a481f16a1350591734b4ca91ed4941cdeceefd9868aa3d34bc47f49c737b902dff2c01457be8eb7e36ab1e7ace993b2d9f102b51559238b0f5afbbf75b85ef9a281c7fb4fbea312224b3ad9b573d7df261cffc7c6baef1d52f2d373ed61553be9926ac0dbe4f4a16c7a7eac885073a0cf1751db9f06006cbe5abe9c8c58d1c64367003aac6e0b3e9c8c58d2e2d9b885089a23bb1356d0371e6ce4ab5828fca949e5492e8f9d6cf8cf7ab1d6354923a5c35458173506a53433333e74729a594524a29a5a90e29a5d4ebe69c78c5cd39b7ab514a29fde67ccad9b473ce4ae9acb3ce5aadadb54e796bad35d31c363534339f4ac70f17e059b338d37793b65e04f63436729c872bb5dd125bd4415cacf8a28eb7e136ec75a94b2f69100d1279702605c1c69fca13c28a1594311bb139d29dd83547683bc874c54965a43635e76c1966119d9b1af8dc4aba22cf36afb531fe1d2303721a497da44b6c3cb714e38b6a191829a594ba7e224f3c8f18a594529b8caeb468eb0c837c5c143c629f7ca4972e4af3c72d71f8aae84725fd545249f5a994a8a594d2cc6e56537d76c39f4ac96e98e32ac7d50d5b8ecb1a677380b7fba936cca9a4b4a776c3dc449aa26953ec449aa22545d1a6588bb96d8a9614459b62af662d87bb0e731aa51d83a646e6ca323a3ced5ed5d75d4ea55205759eeab3b6a6aad53e550a739d4aa9daec5a96799947b7d3ed586e56ab9997655e7601d576fca572f4f2e931e6baa52d587454aec34b5b9a98b66cd9e2edaabe0d732aa52d3bcece65999669d9693db598db3e36322dcbb4943d56ddcc7a33004f9e7a6e1e113969d5362ea5924746578c389c52a95257acb5d6629c75b3ca5c9a5ae02af572b34995ba82ebe56695a6ad091db8bb9c576715fa9a994d1b38996ee6d2369b724a9565b9e57076dedc32b7cc9db9e5fb81d7799e907b4f4b394da4893495985352a0ac97db26d2946cf6904da9536ae5b63b91e694a419654a557d954b714f61d53c0a42e6c7dc013f7e857332138a14616664345946878edc344b919950b479348db40945f5618d53a9541f94183734a831413f3f3f5063c28ce9a0c6448919e36da90b35264acc18d5c7a9e8551e91d9de4d708a4e3f25a85260967a965c5bc0787289daacb67cae1fac15bc19d8394d9f2fba6cba74607220e99bc3bb354b5127fafac07407db5e40758f3f2fc7636ec34b5bb0e8a8985bdad2c4b4e566ee6a57528ff1512eb735a13d5e05a6c02b9f8d5b74d96fa017a34c9f99e8ca214547d8de09393aef04b0ebfc06ce2ed335efdd3cb74cd7fccc0c5b3a1985be66e28ecd3eabd8676fc2024d4f00e99436da949625cd9fbaa9b3031c5b2125c17d516777ebbc5477fada3c2967883c534aa3bb8491449251e4d1514ad5751def4ae242139f9c15d79af2eca94af561170b4f46c0cc749f0403c6045f0ad3355f64baa47c3342e867be08b4758ccf1e42cb5c83c207dafe22a1494d60683c21f434b5a6fb22d0edfa1381e49f7a9a5cf2889e0f7774fc7348796d340668bf5779426ea64eb1a6a6c64673fdf4f47435da6b3c21d96b2e7d714d4d41f195245b1d6b3a7e844882cc122402ddc7e3cc974ca23bf39404c9f47da5654d599a9ab2f47c77939824994f462531e5e8727842ec73d8e6be3d96d06108b289eecc9d48824c7271e0a53b946be293328c849aae29c5c8244982844114619e4ccfdf1c7e5a3e23f2cc67399c1d626f88ed9a2598c833cf830b305f044a753cc749dc61231d6de030ca5023a329a8f4ca96ec433dec9422120800004000a3150030301810084562b128ca034dac3e1480107fb24a664898c9b32009521832061963882140080008008cccccc411007bd08b97947ed2855f7f22d001f61aa652530a12f2bee402b1ae1412d2fde2855a009259d1835d2e99ce6f8b702db4f0d5e55b2eb84554ad026f4bf946c4f98653ff31af76aa303a5867e14f83c65fcc9241481251939194a78a9f5ad6e114ec9a20729a3693deacfff4c12032f1b6947d568aee1941eb33fab6e48742254dfb38c21b04725a892cae7eeab4da761bc4844882176190f991e85603e3bf6bd297d9fe867b540e1d7556949193126fe5163e3d66d4ff1b19b45264d4b7a0a88147f8417a0ed561dec0394780689ecc16d3208e6ff59ae166a9cd74562a22f057d8f2af78e05e9a363a619db7e5d98eaf9e6f307de0f36cd6d59ea393b92d486c57fed5f8aae66c4eca13c72d1e473cf0e23fa10c82674217f8597af4e8dd0d4e90ef5042f85d4c2a206cf56a299ead6b5fc60705e1164ecffe7627c80e3f06272d11e334c4c7c159141cd998d015b2b39688af695b50689b92e19f9a9f91d9b500c23c65644ee5a91f35dfeee0889051f359179fd985c8766da345dae3d423ae96d88c1b430576044e5c7b4acdffc66ee94944975021440f0083fed3bad93aac792ed228c09463b744817d986a83e7e55c8f864fb15b7a43f3bed500640cae7112471c513d780a83303455733b0675df00d9bdac1f12f9dd8059635927a83c8e28091ac8ea7f959842a0dd2c024280c89107048e8804e5608803883ae0999d99bf11e9baf980a7bf15900c216d68032fa6c02f13b4ad0aca93a481c2df43200b174079433b29772751088e86880ae0330c58957a5bd136bd69061354a2012685de7b075b53ebf261db90abbfdf09f877c1ab1ded280a8163c952103f1b0b45baf37a934838e174cf2c6bf06496921f8bb6642628b28ec7b4c079a198a2ed706054228cdfd18f6dae3abe33308368faea7816084b6f9ed50ea1e7c6d448ca56c971dce6ef3e4390cfd58fbeff3a788dfa71dd8bb032feb9e853cfd6b86c0c0376905aa95f359c5f07291bb34dd53ae419da96b5d3417562efad395f570c6b55f65c49a84cf5ab3e6142ff0af0a155336dc5dcf4f11ee5bb5f8c7d33534ca2f2d2fba42d91de1be44da8235ad7ecc9094ac236b1688d184e66579603a607305481f2c1d52925bc26b2867d1a4ff49f4cf6c94348d49bb10bd5e2c9135fd6e280e4d77df32f9e0b70ab0978ef2b4b7943c24c0d18980736972f7d7f32950dbd6d5f76d72874a1a195455eaf1cef295535e12a22142db3959c7e2caf70f15029dec13a61fe6ec216087ea7dedf2ec856317aaa57c59ecd4ba04e473ce3e1c60e25f1a16b5f05740f012d86bed2ab47bba55a0c351110fbd684a43742086997ae614f3f62243566d93ef8f7ae6379e861bcf1abcfe4ab8bbc8f47eb7b8deb19faf025a0f298ec7578bc9946d488bf9384a36330c86309bb0555f95980e495d6161e3ca7ddea03c9513773e40d147df8c76a8593c8aed0904c96e6f746af487e158ec5eb75e408550383a6fbe7166c1fad005fa2888fd2976a090c2da4fbf42827b552bad3e0a2cb865df7e7d93ead5cb8baa182f98691980395093c2b3bdd0b0d06e0a9757deae6b17f88b06e54fe4defb5edc11713f9c89227667fac115ce817f817cdef2c7324929f60049c5b4e80a75326c0cf67f520f9140936e0be8166fc3d543d8a458ac8d85deb6dd7ff103ed62ab2ab501fee50015534d151d22907f3dbe1b8107718bde040ab6f41ac61179b6e29dbe9ffdda5abe787f8d21a5cef7d389752382a863bab82579438f76dae82375fb8f280f7ecbe2b02ee6828052f07e6b182fd254c74d4eeb3b582ff5b47075dd51eff5a327277d35cf8362948dd8f7d0e8e728c62de6a420c330d7a635b23044a6e34a450c7a76e1a432601079f89d3b791f73ca6400042774d9ba433723417b0a888e5740c4d5e5ec91638b1d20b69fc8c3baf3c73b69a76dc64ace877e7a58822207d76dcc9faaa62c5d632cd9273aee1779f7349a9b8cd7321ea1f0905579f4220f2513ae23ff7afb61a0f12da63cd8b9d73ed5d46d377d912d877d4dace4cc93d9bece2218971023a830c78306b2a95e421850b06b2f694a6888289f4cd1a85e5875871c9ecd883eb95152b8404917779717d0a5c556df15121ce3b13c4f25c23ed7dfeb3a6ed33006a7286f683a3989818018f3db0a32f7c6244ee4c9f4022004ef223494c0f97a34b1306e6d3ac0ab38924fcf9bf4effbec194f7eaa580dacd9eb9dc0f03390440a81b7ce1241ff9d4f663e6728af62254e67770c8b3026d38148fb4efe9dd776e2112aa9b4189487e3e119ed23ab92fdaa1029c4ab9892717674fc95cab93d06af871e0fa8a8fcdad2262e9612b4c5f8fe937e3a1856da459356b91884a8c17c3f575e12e57644e9c1c4b33feacd82f54f44084ed16f950f4b209a75468fd700615898ec4fbf008a8f2a85d4cca184e89fc902cc556e76e007796f6436b13f74ea3b362ac768de191dbfc5525e55c10c6258db0ee61c00da7cf8672c58697dd052bade7f5067f5b1c477aa1e810e0d635eb55515609f2b94867fb04bf90c5aa9c43bcf117052451553b382f8d063522c4a5a942f3f230440e20044b8e26c1c8652dfba62e6efe1f9443af2ca303719816e2895b4ecc771bc340c32d773b2503b8bc4d99bfa52a72ebfb4dde5297b5acdeed5e250c5de5aa662d11879a8015047d0af5e989cc875bd642a89401bd962c0780c5755111630070f2b48dba6447e869d5ed54b7f079cbf5ee243b641025944698a46f9d23f2aec41524a8645164211855ac2c2cc1dbe93a24f88d822cdf957939f80ae91db8fce1c250b57ec00ba35f08243bd61db900214b7d87b63f96fb90bb2cd714022131659b5da06d6f335ed966e52ca384265f2e5de6c5b481a3013a45b6593dbb709b6367ee3548a6de9947d74f649b7de9adb187b7732339f07c9e885f47313f73fe26816059529c04528a5e4dd28013a443006ffcb856f4f55b3e463cbb2cce06c1b967c6d00ffe02da30a14299f299a9e977224003bbaa4d1fdc0e8e5b5ab3e181c055418a26bc3b4ccd6e397755858f0f6d61717d1d3af879e7c13924364db930f592f6c653166594611ffefc9d2f6298c9a41083475fe82a61b0211710589dfbe1cf5d08a0bf06afd1d24a33c439cd06cac4e2f5e5c9917132df89514885515f2a56dc4ff8189aaa6f3bfdbe9a21a0fdddad7f104cd6a79f48f6e2aafbec41ef7728146a6ec269dd583bb9cd5edfc1a18dfb64dc50e1e440f4f777c505bc5610bc431845fb77614f0515472ee0f3e423bdb79338b3d975144b97f94ef7c8eced03b1e5ffef2c8367bac7066f1f08ed9fe6c32f9077fa9146cfa7d020e90351e6f83afd58ef4d24f6fd31683421e73604ad79fe26d6bf67dda2cb13f55718b998f84072a6d9356fe36eb887fe52c73140d3f68852130d66df65bddf676e2808752398ef57baa115d1b2eb2605b50c57e8755f9c876806fac40e7e0ed1fb7d7c4b489efa541d58a3d98d4368a35ec916a8872f8ba15775253999c2164668465add38cfe16bd6d66be83b15183635462016da833866818585a92fc1b736da3cd8c672cc9928edc8732abdaf6b2be8ae1ead0783ed9102121e0941b26e6b151258ad68f56f27b1bc182d1dce82b813b0b37c19dd7392a074038cb0e25e103b5e2cdd2c932b938c91a4c610a7ca370359b65465036c4cf8dd0dd48fb116d5ef2bc509493e232bdeecfddd2424f9a086e6b143d0544db12cb22c3e59be58747ba56f408bac537a50391548af5a38d2dc9fc61b4f508a3e27371c5b2175a8d9d3cca51d70285434e4c8aaa6a48b5ad2a4ad1a489f01e70eeeb61fb0850e4c99e1bc0e4c5d73381b7278e3a264c555e0473fc654d43b8339bd7cdfc4506f31e32c6dfd277072f72aa8df2a300cf4c6b5b086fb1d01636b7fb801cead1fc2867d324fc0667698ad911a582c965fa687041fae16f08481ca42edca4b36c6b6a06c799c5d41181317bac020c7d0ff5289b81b859e3f6f69d7a8c95ec050afc27f589ac064545014b41b85538825fc53f0e574c956d044387be2c63c8c1b6bbd0e08ab9ebc60af0a437814a90c340fa796cbcce88b966146f329dc20a03a0e97d42969d699306a6583f0a985c38d2f16f506b45554d01e0245010e1ea01acdaf86c828d8edfeee286b3b08b352ac6d59646d9a18018436308c98f2139fd90fcc8cdd337a14d42cb41fbcb3e1b247d22e435aae1584d5b114770831d3db3fe1c08087100e7c75168fcbf409b72aa7ccf4e64581018ae40dc4bc3450cda8a5ff301cd6b000b7245548ffe52d6c684e163588f4576c5d1f76d6eaf95345e19330bd8c09313a72bf93b60386a47b8175ecdab1bf467d734abaa4ce25882c8542d43ec7fd034d47dffa0856a892b5ea633fed2d256a47433bf7c7b4543ab0a2ca148e909db7f3089ca28072c79970f9988c9cc4739c7be0c89e32ef64ca757b4a1e634499f4284a722ea561e5d35272f2753d75a60e6c0cd084cd410d01494845010f04225229821e7dc59ad8845449c005e168a915e8807892540a72203e2275055d209e22a9170d9cc081ad52e5fc22bc86c0658f51259176658ed6a342bfd75199b40294524aa484491b8e1deecd6322965ffa991813b327473981b1f52b952f34ddda6a8a5779daeb42b8b4c7d103e9ad41c8ed18421b05c296ec5700b27837abc3519c60f53de8bd85a4474ae0c70d054d3776be3a30067882350d04431c498096e6a659460460850b22a98ddd2975085ed8c19a3dfb12e05e9a7995fdcbd2236a381514f322eee4c8cfc7197c79444a8829bc4366d346f914d93b85fdb8c4d81293fca7bd26442aa3503b192bb945d3cd7121d288c18d8a0614010d4b934bf561ab243cb5214e499e7bc2742b97e8a430f99d8733d01c5c5ef32d7ff6a99717ea897ffb915b8b07444815d38370baf1c369e25a41aedeadbc02305a39387fc423dc85145fdce1e46c98e1f1a0f3b3b62ca939fd498852edf4e40a0cdcca950c37a0ed9dfb63bb502b13f1a669df6d7ecfdc4e64ab8fda6efcd1fd8fed9f3772900f25983d37b28688315cd16bcaaa9e0ac7ac9a92e031607544701a9a169097c27a2bc71ed587f6edbfa3972620d65d1d29982277b14b8be318985947fe8fe5efee40503137e0aba9cdf72314a7de5ed50e87fcf209f25b0238acdb58b998e3277ee9b959337274034988502e854879deffc87f158bc08f87e1d11f077a8fe2974470db18f5343d269a915eaa62600008c2b89377da589f136c902578bd99169c2441f5dbf8cf1b18706f91537fb61acd9cda50c91bd5cc2d53ec88861f9455f9216d7e25fe5c320895387a04b55aad20b9f963641f61781572809d2394f11e4748c67d3d214d4724d26c0ea6587a627fd92606dcd8e411fb5c957db18696b6f6ebff3eb3e25141361ad01b3b17812cae4970c7f8eb2c7edc4688733b378973cb1e9b4a5d599b50c4738129c10b7b5be0ff10a36d20c6bf126fa48b2675f9427aecc109579e1539025821ff365ef21ad130e59c59b6af822489e59df3a9ce947f1a407b9984d9d99e6ef8e73f1bb040c0bc32d7e344dd5631eda97a1738dbf6b39003104cfa000c8b684b68145d978f51a47fae2e085b8e72237c9775cbff1104b91ba176b0d03b35dca8095a781f7e1329a4238dadf623287189cd100d39cd45ecaf0ddd851a72352dbadb32cab955db87e66005b2987e026a4c23af408744297ab2e2c4a2e90f8c887a4c24a1ee726de8002ebd92b7f99e1c6641d05220c4860924d81e11c322d65d3f8e8b5402cdda9a630c3cda5e88b32b7538af165926113c99a4f1a5c2b7bf9567739be30784cce14f8d6ba685e1c88fe35d738c4cd1632e7a74dbead55009320a50dfdb83b912c1b6675601adb481d56fac3334dbdf18f2a4592d0adc4e63be21271684f1941dd052bd2937724a01157c8547dd827704eb9ac74b993f98bdb8dde573bd567e1dca156ee7eb59d069500a54f80c1cc10a1f5f7b6b5a2111d5cad600ceb34adecc819cf4e769a226ac48d167e52f6b0c44f420ea917e74573ec73b675e39a708571ab74b92b2e3c4504678d20411a7c38486240021d05032786a8240caa2892c85c64f001e9dc54236c18b235fd00b9dc081f9afd0df166e105bfb4ff53506b623191647d747da8439574d17877ad404c86a718c42b95dbb011785756be6d8b00e0300204575f8504761e3892020c3b155a17a251885dc9fd5a57b74bf26d260d1e7300d2ef718bde5f086b33cef4e14ca359185318721be944ca5a402629116d3e949715e6c8834198e8f5241a94fa594ba95c9a7e1b26ae357a20163896b0e76aa2819f45f437a213fa1f103cbd2c3c1e1269c42908c3f76ef01fde4d3c1c13b61b7818ff3e68b698ae51ca7481c315f7099e86da200c3e24626af511ad08d2d78efad8219457d360003711b383792f4b23d4b17b0ebaf27818273ad40e03801cdb150f07aa3c186ea470403854dde8103c510c812d4f6c2067227e7cfa9d0178d6d1fa5c2a1bde48a4e1b9e7fb717a2ee945429744343e8acec2c640397c83992dc9860d13346618ff78634f1c028c30ebd1de04e711d90112df73b902aa16a5a4b27dd152aaa9c06c6da564c0ddf10a227ddf7e634a9d0051653b1df327a8b02bed1e403ef1b0077a42a3f29d858ff44497c0332f7552f745b7677634cd3492a683834f211d56bd032ee3e389d6fe03d153b4e69bc8f488067e217b54f5f80c07902c9f8080be9b75fd9e0f097657957884d47d1dfd5813e502b9de8c46746eb953a10b8f3644a960a742392c14de6702a2f882cf0aff8f2402ed58214172d18748edb398c3e0545d2c3158e577983a65dd5e8744f9c85f83f6aee5eeaa848ddcb9ef8ac1c5d0c0e89401d5fd504c127f368d25152669dd0de371cc5dff213d709f1375f84562f62fb67941bc5283e2a72d8fc5a90e6d273c13be883fde0b03a93fc202178424bd0c1ebad3326fc6ea2759e99a538e820d3f47c35672ca5b2940b46d4301ab3adb4adf0170222013ca3a4a70001a35b3e443b9eceab6710320c26f33730d82db866945b47fbbaf00cc4a7281a5180d5b44fe05947c5d1d0854686c90ff22a0bf44f36919c7805ac2ee1ed891040272ee24649a2fbfb844149f64befbd9225bb50a025f63cffe88efa94cf900d9772fabd3e7a945e8f3c164e380ab5f960e5991e4caf47bb5caed934f6b15876e88c885b03bb88bb3deca2f691269ee15232a18213d29e265c2d4fbe183a3e740a8a6b1c973e39f6afa2a7f2cfb1fed4f9a2b4da37270032ce92d0aa52836f5b64a3e3f8ada071415b28391e4f4ec0e533e3a9c4c1ada754954f4238179f1afba1a5ef10f3ae7829d918a478dde68b8606a79b28d924928aada4c3392d4d6cc41651f46762f0aed8c75e3cb4ef93b55e4a3553c3a6b1cfac512fe8e6dd2ce00d6a1ef32e63589ad89fe3d0f0bc59a993de2c6f96d7e9a110da07dddf273d286dce525b0d6474cb49d7650340eaad5e8f3bab3b4ccf653c28a978f4c6b30c76164cc13397b9880c86c48d15be220bf99f29a39838230492bee08f13dfeca1e31861b132226c744a9c74900b1352164940eaa75825da4c3ab8b59849f5fea6d39a9c4f2dbc4a890d39bbeb851ac96180b1a63336ac6628aa73ee3b94b06ddbab90583e9894277dead1e240bb01216aefb4434d77ef2cbc3222d0f9953750c203edb946c5edd2c74d28072471befd2772b8b76f0e1c68e6c84da6a4424f101891ada0bdb117ae0a06c92c45f566a3b582a66962eb5d7e4205d9b1173e15aeea049a949676a98a5de503480f1e4c823a9605cd0603f6b7da0ed4dac391d5e8385adf0bd4c0a219a2108744791bdb154a2cbe9645bea4e09fd355d8a3e9e79d64a693bab3bae34297ab781915e253140c4151ba6ffacac3a4b74f2e5cf195c289b8135882e445d2d051e22f21c898e3fbaa2436757b9a5c7acfcd016b98a92ad511dd0abe5f8e15be5ae8691b0a596a88cdb3148d3a8dabd6bafbc16ca140d426ec91478c807f77120726438a70f5711b76162205456855228fb162c32e0285237752c84585326bad75e99310ff096e8fbbb93b6140e1a96dd1e814c79b4c6893ba8cfef05e5ab215b00ef452a5b08ac11546cfe9356ed49ac51cbdd7dc8d8848d76733967b78ee43e0d719cbb3b75872293b9268058e3927515df3a23ff05a88c62448005a4d4d6712101c91b306520525065661c87e621c40dcaa8357e4558138e1993d57d453b2dad6ad1eb5ad1fc49d226ca0e6765641074fc4880068077a008ee610c3929d99bf0e7ae1e809c5ad07e4810faee08dff5d044ce77730fbf71724580ffa30f932d4e056d3f0c135e72175d730ca17b63c032b07caa5124b52dd063f47679f54dcc68c7bedacc119fea98a958c25e532088321896750dbff6b125aef9f0efd25105e6c42cd98088ef881c039fd5286883cacf8a488dcf4401043c4f512c8d34448b4f0bc4cf6b348ad15a5d732bcdd2e0e26809e6d6edc937d9f99c5e228c9126a59799c41ca2b53d34a76002490b564ef2f2bbd1b130a020d480176019fb106261e7d116fecb4639d8959c7636f804dbca088d539ce5a17c2ddbed272be60ce4fce51ba2a1c45a8c290642bee8e6127d22051005e3f80b937835746565f0aaab3bd9a7ced58813d5e5287900ace0e3ddf53aefa05c1cb5e5ccfcaaaff2c8beceaeda565e5374a5c0138bd5318a2cba4caa609e992741185837ae69d01a4308525b1af489bc86197dff81e12886521e9e91f1cc63982ed41c03fa879c1d0ce84cbe4d5c5be10f967d0c8c9318240ab068438c11021a1e503f07d6435fc0463bd4f64d3fcdd6b653c2c679a59f25f6397acc8f3b54836f60bc584b41c3f59d861450a8ecf50870a2c5d410ca21ceff6566331cb997a320e8e05cb0536364774f988f15fa865f4bd70dd49f17a688fd593d879caaf3a612b4521f79fe1697895ef8c81e21a7e90f5b9fab466b1431ad7cdf460a57910303f93fc6ed55ba8bed08cc6d6d265e8cbe347361b62178ff17377dc5d510cde0c0b1d2545c0588619ea65e07d2072ef2f004844bd76481cab337a0e2fcbe5c661b9d2e2f7d18ee96d1bc6ade1672fcecfad20d67e2f0efbdf92d87bf939033f06c9330f43048cb68fd1e4fa4139667b5c34f02b5de80da296bff15649ffeaea8fe1ea703b8e2f019716fddab143c689197f6dac672c2920cb86448616b62438df7c85ab907180a6f9088d18b9dab5501f62264106f132d9a86c6f6c3440477e4ba680b653cbdf0f9118174b471f78cc6f58cbaa6276b7e736b5e3858c466a1379f3b83a1ba272a52187fab6bc18553990d8e3ded1a2057a56033c02a9b7bb9e1a86e12e1f06ff6670bd5a7d96cce1ed191c5355622962d0c26affd4a062fcf42f600ef598287a237ffad9da8d587a92ebc5c7ac7b434d94054ecb03eab26f271f8e164ee4f3099020665d162ab9a9563c648ff53de928281705b5f206cd2393359187c08ec6af49f51495c6c038bf8b0e6032ef38acdfd8651ee0d92bb85d7dd7613bb773cfce9c04e44860f98f877e81a64b0de53174d4dfd93731d535f86dc6e442a03b831c275a83b0cd9a8a3ab0bf815fdf2c5a3e0072f842eb2b4bcef0a975edb3ad7c0db0cc2bcc77da4a43e86dd2f62d21fe91a096e640571fab5f32d5f886204e67a630483ad02fc496675e48c42c0a52850923454c92feeba56d033e9fce0b86c204a7a7b0303d144daee1377009967087e8e6ca74e3e3d0729837aed25dbe3421e22df95f0b08096a5e299ef89fc1c156168fdd262cc35647f701a31d59236630c4c0ec769bdc9e051a26d5b874979f878ba8deac103abcdf9d423a9f019eaa5cece411556d2274757941d2fb928b0d80c0f9c2cbcb42e786c41d750f31a92e0de3549a547d3a2351e488ea7eecde122393cc0973d406d6ed95ef264944fbe8a6bb82f7438d0760b42739d3fe0fc8a44bd23aa3ad864feb5a449abb5d1fc4f87c30dbd3c15f36cc6955884e7098556a1765038553ba6d780e19d3ed91497929ce91bf824d521be8b3d3b1f2e10c33497e6728a4292da3b4ec5af51d3416b7870a98d171a0e7bc927b559452f02f66d70b7d2f38ed241ad839819f2b0035ddf37145a3ece6c7350cf694a96b631b76544c5fb6976dd2e31e8211b5f781836f8665452517257057767e3a4d1c8cb28a8184ef92d5380d5f5ce08d1b1e4ff70b5cfee9eadb1d0cb47c703e2b335cd22eb9a654e4f5e3ac62b37cc0edd4db9d1823e2c0213f9ba637140e59ef86828ac640456ec4d4c8d1a6ccae3311f0a20c21764b717a0e788bc2b0b59b1dbfa2714003620bca600175cb941abe6565ab4d2c4b4fd4a6144a70b7b1025108f9271d9e4e3321090a09d6dfc4e259fd8252609d9697d446422ac495466dbbcb4e2379b58ec63e5b03307922637a994747d55be3c20c84c61354d9247dc0a9b4c255fe0f1684e9318ef726b71153c43390aad3ece7470b203b8700eb98347bfa2843192694f991f07661226add3cd43df3c8d22168192916e1f12ab42e28c6f21c1ab684ef00a397354d76dc32983372c2b6e78c632da43f75917af5b3b2cec0e0d5ff56c7cdb7c16113a088e30ecd31e1a379a992ba20731126e29172c016d0a89b00d1f63cd302ff4eea11f769ac8abaa9c0600f7d92072d550d2676aa745156baf4d49e06d963e48d65160092bde9a2a884106e1ee067880fa9498550bbd2718079069bb9565cfee117c8cc0db3a95a088409c786d3369f11f4baec5cf7db1b5e00decc7f8a41588f6cde3049b670a7df2a2dfa299e5f43b312f7c11e1419c7f55f57a328955a9ab02d39ce3dfbded29cb7a65ff6bbab204fff0bb738fba22e7b7511674fa119144ab3570112b573e10eb3f740ef57b116de9dc7547c7137d7c8391dc301c175807708d5f31a495e92c0c60ca686fa4deed529d193fd481c4b40fc9b1f2d763b23c23a4f10fbb87fa6c713c13d7edb67dea81e97828a221f12d6c2e3eb273e0e59891ceefd8f98dd0509acdee2ace6f5a9ad987cad4227aa8873077feefeb3a9ea2f7a4a33685dc0920caec9e7f5266bd7764d970d8510bf07b056ce763c7d63918b56abbcc0212453850917ebdb6a2087aa5fb737e6d79daebb2778feec128c0d506cb72e1780f9d02364aab99496f528a0fb55bc8e42635b2deaa9a5741cb4e017d023f7d2deb8127a8e9a0665b7fba1c168c14436a29d87598e9f7488ff35b013d4ef3ad954cabd0c29a4c958eee7af53a79b6fac477fed6bef61a999cdc08135ff5460d87e6e55f3b3bb414310cd37b3453568178721eb220ee6903587a03df7113ae6880f7eabf9c5a98ea26d80a75a84ae39b29e78d3ce4b615b604a542cf9fcecd5c6e43ed28caab6e0fdaa2eda841b3fb078c702110ad3a55afa07a2d542112488cec4cf11752f2b4412e14bb223953b71f0bb4115e0689a7fee7b64f1a0463de50e88c989bbde21627ef0000031064c19a6dd896b30320acafa35639975c15389b07be12f0dcf37264ccd2c8367b13dece055b542195c126c61eb88cc9736c2c616dff4e7e73ff178ffca1a28ee597e6b135e7b8fc088bfbcecb2d43881bdf569f6f507b86320680fdb361d0a293cbab489de47775bdf3fb6303456d26f6b5e2843064fd3c1a656291325a350af8b9c5db3dbb7f1f638a0bc0d9471a91ccedb5e848a112dbd0d9a8eeaec93f47270fd364109cb784d3baf7a6b331b8b465e45815b6111e3810fb2c2d55c1dcb61b66aa9d4472d7053ddfc2465fb5687761e2bb93ab47363e801fbe2685b2af0e3ae90659ef195911633711fa68030d05bd15ac1fa2601c3bdb29bc2ce889b9e47cfb32a748eb6ff6e3f8f3a0d2012ba5a81169b0efa93864e4f393031c26a5eb039526c2f4f31486dc88abb24ca0dfe59f5013d14ff3e080ee1d4f2e05b0295667348797a54e32c295500ced59c48186194482e59547a37570be819673b9808dfaea975c906a1c6bbd8f3418d2140ac5a3e61d287701d66ac77eaec03fc0abcba8661cbffdb9bc2ffc90428957cc156c948bbc0d96e6178f582e0e2bafcf9bf917ef168c253290b0b84201c0bc5426fdcb5dcc64baf14b7d47f27024241c120a2a5b39483e7e82448da9c1aa90deba84f348ae46efc2eb080201c3c5212bc33a051b22bd92b6c9ae547be941381461025f399a0efee319c84714df0d6a144c2d732b2718024fa3fed395edac5c4d8f93cbc58f906f2804186659f181d511dc136678316381961f28cf82e594f6242668808f3bc2733192ab3312c338633b3ca66660a67f6c5f2cc2a8c29676690a036b341398170dbddde8bef60f020e122e369584a54806855b68356254606a23ae95dcd2ca97257efd81ab9175e2900e24decc53c0455aafefdda45d40d1f631ccad02d9acc423c7c02d80842fecc8f07d19644b73d6a6489337f6094950662c32e7c0611ab9a145eb194cb1111d2fdd6d3b5af929af02a9f2ff57d8056447a9dfc02e87f8c95453008bd72fb3cc65253261315fb7a69985cff19d1e53de7e9a7e8854448925137cba5309d78548bf02a84b4639a449cd15a7f3a1ae0dda00233686a854c9ed046c86a24726cf3af04f8fa4c95b17675975ceea63e4649132fe407c923148d89fc5eac1ff5eed54767e4071221c7b015102f2b8b181772764df42f05391dc1b3e2c2f9fac43374f3b5d984103b62139ea7ab73d951feb1d2612d68c186f52603ec6a9f531853d9143bf526e342dfc0636ad80d5e54b4b41b98ec79c2e9100f9fd092b8eefb463323f07e68650439703575ccf7029773b3d8eca8cff88b883a98e8ffb48726888eb15e0c82dcf1d16e27956a4ac2771f61e0c1db37633b6cafccf452c440a3e1db6715383d655d4622eceef72d8037c8e28253742236310d4138861992a4a20db94b07d55c281af03a947d1de4f84d480d0cfad1c56c8c5c3b7be97d00e495f0b13a7bc7ede61396c50ada9a7867727a70f0230461a4639d12c2206a2a9273cc41038a0075e0f20e20867bc1a3536ec9f4a63d7bb6584b4d1aa312c937d30d5e6052a11ada6251cbabcf7780b72fc7d0951724eb519b5d46c3f4cd5d528b3edee818346588be9a18a62f6496041dbe6ddd82a11b03eabc51a200bbecdab291b830317b798b6c6c92cab06c2cb053564a1a39f9d1804eed160e8898e7fe61a901727b602d824f9fce82d7e36ea2f9ed3581fc28c8242982a56f70a8917bf562d6a71023b5339eafa9b45af37cfa798e5053c9ea8af8716cdcdad5606bf000fbb2f7c5a5bc52846ebcfe8f5b6921bcea97e0c8aa0992d01f9054ddf57e32fe1259b15476852007e8040064cac56816a41cc5ac0288301e4694c98171a89a50b0d04794b4121451608e0ce46ad52c0d7cb50a441510bfcb54b2a08a6f2a8cf4d0767ef6962d8e1c01086b48670f404a1f04835bf44cc223d608b8f758ab2b015f6ccff4c562d924d37f3ca71f683823656c775c9b3c437fdd1bffc7703d98811a2c1bd18a017ca58efb1f1aca0f8ae7fbf9e3c4c9826dfb1018f5fb03eea7b47db060cf0a1845cc95d25701fb3b0e69888321ab80dca9020a5a1560a898870553604b4b20eea316938b45054606ef01b9cc3b66440b1500bfc60e7b7cfa13a5d201c7c89c356cf754f2a108606ab9bcbec7674aa2f17eca08bd351f63bc0f347eaff296a7a556947b701070ef88bf85c8d603061bd1694de9c46a9c4c92a93df99c6ba3931407e0eed376f96c4d140266c932f3ab3de1c40e1dd6f2669ea01c6dd8eb121063c4aab2366fe98e167015d66d2191f7355c16ea5dcf770bc02aa1869443ffb56c7ee3db48bc2b50857c06d33b8002aa0fea5a1f8a3980004ec225e46dc1b3be7d327b3594ab70a4e039c3ff42663a38fc5c4c7cf120bd067b7e11711cccf5958628c889ade8ef3989392cef144f7f4d01a24ff46127a3f9bf185cbc5fb0492b3c06e162dac1119f4286748459ff5f92d12597189518f64adfd15d8312fb0db30d29260832abe45330a45efe75f8ab957e14685606ea6f12901592f9c33ea892c6a9099eae44388d2fdbfacbb3cf9fb1d868e9f1c3475d36b50f054feaabae969c51035188dd51ebf5f0f300ddc067c0a7c081ee2c7e956ad342c55112c56448a183160fbe68591bfe144f934a4c65685ad0b985a2edcbb57a81ab9819d91ff466532c8d5faad2b16463c76ae0654c127fa404823d48de127455e2eb6a126edb7d4ae56eaa2e14a7dc51223f19413cb5829ed61c75a2b00440bc4abb9d5b4a1bd72b8da5829232261cd447c8aa59146283fd23a7a28ea6c6b89cf7fe99f8354680390fdea7d22341682084994957ed691e4c7dfac5f87797a036034c0cacb78f23e1e4805695ce2a81be884148d7fcb2bf78440109d12c0dd9f1443d3ef9ad510d16931eaf0e342dd0cb478fce7aabf76a7f04953d7f13b85857cfac41eef3ec781b78198c6d03f708379a7b6f99e9263c4e6b38f9e7d2e861d295eb699af74e993404347a493fb635af2f4753c11d91e536e7d5439d017629584aa007aaedb25ddbd663db00bc83998d536359cd443914c3ecf1d64938a3d55fc2920425e7e239727c21193cd060895fd7f2432e4457b520ed190f751b8d5f89afadd054e8cd8770a0f3441d761f6fd0ac91a2d5e3a27b7ded72c52d7a9f895a058aa2744566e4e742691b04f310521d1632f79078958e26333493a619459133d4e9ee89985f0bf9840ada48d9916e33024394c0d3f3e40189c46a9b350365fec8809a0ff84b962f3553ff775311ca3f1fc76b29ee15893b4c3238f783184aa28c19a47a9efd9a500a54c925f42f9b2b3803fc86ea5b95b18759634796ede36901043ee4640890d66a8418577903c5298b08586df1d9353782aa0eb5bf07c239cefad689c6003195524c50be8ea77a003d213cd0b16aee579b17a55b987c0f76a9a041fdaca8cc4cee9c28ee34b1202e55c569eb312b002cfd1ae08af8b4d4c461b588d134e24c742b4858ea81a6d965afb030fc83d15d7cf19469342e77239f833ce72095e0106c9aa4055e66280a0b724d9218a2550d6ea6d5a60c02e8351ca72eaf17b0b8c84572a4dbdebb9da58fef4b1d4ca3a5092a627c48884c8a6d938b30f8b7a4fdf1c110f83972e0308706ff95d42398e274cdca57db6a07beebfb88a283748ac550b571289d4720a20cee95a0b342798096547f199587375fecbb9845f00c61fce9239673b85288d5950e9b30159a85559ff9b291449e22e97d447ac521030637abf6c5f887e2bfcd2aa9ec8f65363030155a2bb310f3f114ef3fcb9c90c7c5d4431b6c9442813bb6ec9fce3574b2a0d96939666e04d0a0cfcc54ea95cf60786efe1ccdc8909cc505437ca9dda846d3eeecc6554727eb9d74d5b92bbda17f4a7f4aa0b8085c2166a2112ca39f51bedb5423e2ac853d55ebc393c588b900d3518bea08916795d55b999c5b45f50e4f243957a647ab4d600f0cb0ccb041cd5c6f87cdb6fdf2a097b8168faf763f4f226f20648c632028dfcd7ec22f55cfff10fc2fdca4d8d5f70c478d4a74cb663f741f93b0553c766a0eb0b0a522618838a0429e38acf24df45f822246db4fa8f56ea3e51e427477e5b6e80cd1f3e88748c13e8bee66ad9976502ece2a4ac52bd36b11a57c3f741056631682e038440f37a704c12478629a7edf40cf04589c1f6091754ba91b52d56156f6b9d283ae2405acc237c69714161c1220f02decfe0a4dac7d5709044d2a3b61f9bd242f4e35a25a2663bed283142d934103c74eb0cccde9522bb42a32ad58f24bab14301112f794646fa94812a388c283901a0aeb6c108898235a2a4a810b3d1bdbc2bea0614b1dcd22196494148746ba219f92fed0e4c64d520a5f65f7ff96f3b90f643841da63d0a423446198092a1c3940d2b9f17f9578d38d8538727126fa58e9d3338d4350afa00a2dae09a16b3aeccfe2912aa5799abcf4e40f52b94c989152f3d4dcc3d05bc70bcb6786caacedc4e56f4a1f485751b121dcdb7a28f5069ba83c7b717bf6adb20e5602aa79aa9cb64f07d38b43eb6e807798907c6ca76b7ed542ba4ea8a931c468f3a4fcfc7b354b4cdef782a5c0038641d9ec97865fd3288e1250315c345e3385cb8852092c954a1a9ac03b1af1811b1b645653996b25b99e60ebfe311ae0cce2837a9066aa4f2e610f01d99f850240240315f9390bb1ec66185234eb86b06c2cd15f1d28d9c356f78ad851f134e8933daacb56908ae18a16f855025f4c8e2b49da229f6824517cb006dde6ad47bae47e173c5c52bcb37477aaacaa423fca2eec22543bf4b1e8e30a1ccdcda0196e373d9bb294580c758db79b57417924254ef29cba27f201d4a34732ac4b27f34c728539bfb66d484dc8cd1078cc04c1b764453a4b086180206a1820e1f09226cbb4448bae823fb1b7f8a231b8a88963ea0d744ad4ec17a8addfe8b84b66062d63bbf5f778ef7b92ae62313146d081dd1243ea42b64f388a0d296776afafff69d4910f48f0a225b4bfd21fe6e2a40d3ccd8768894bde28d5056f54211ba91b7715e3c8a34e2136d0304b2234f47055b2e94772b31c2450ff5ee2d885a0b868909626d4cf4457e5b613a3672fecf6e02a00e0d1e5e7df9a2f2003c41ed0abdb9ce868e9087c23b8ea7b85fc91b59c1477984f63abbe9dd4d7000ba116ec11e938191bf23c83e8d45502b1cfa578668ac4680b0fcc01a1056785f9184d0fff48bef42240554db04b0982ce928201a5c7a909776e20e7d07237c5aff3a2591404a3c8da1d65a06f00a307bc9e5c4ecfe6b669f3cdee17733dddc4950d70c6194d805c5da353042c788ff505378ab498b396e431997dc6a215c35a1e00c5a91d68648fb9a573228cb63079e258a8cbcaa9ca3f066827d81034aede7d2d4287dc2fa4c9f05e17fec3240b1c43f08f8018bc46ca5f8722f80d9e7b49eec980b0841babcf622ade18cb8bf15e6eecb8acb197de38a039de04d97145c3a0c665857e43ee9cfbcc80759830a7280cde6b6f60a1f62cd97313a874cfd9cd37cb556d8ad6a74cb974754c9b583c7e5cd0520088be167c5e14ab3616741527748b53d2f0dd6dc21047e69560a7a1bcd3792bc6ffe3ee810bef97a3b3e4b4fdfa47a2b6bfa6fb37f64cfc6edfce926dca1908af0ebaeca34715b198ff3169f58588548e9f8fe865105d5f13f782e35a4bc1c50ee9ad41bd35ab4f564b3e4672d747f84be39ee016f3d13e68679ff3fccfa17d03a78272a925ba183bbf145ffe6ca05aa8a2a7588ef58f2b03014aff9e242182cd00008e17ea82bfb5a8bbce12bb2af7e7ee3e00902e5641ed63be39ffae4828a93e63980cdcf93bd6373061e57ad64f5eed4825ba433bb124a7e1e6a038f02d1f3bbedbc4d6cc3d9dadcbea6f919878b703c6133b507bac03ca6aa10ec0ac4b206a6c5d680910b4daeacc7a0d4abeade85d663df68a2e86df49488dbc715616fe1309697a4aeb7dbe2721d481432d830bd04e1be675698ac843dfb670cecb9082afb0f32cb280339204de7dee9865ee758c52ca8c5f40a2d502940605e3ac41fe1710025e55f5fd840897b2248d123cf1fc115e87e8fe3541c7e6263dd487ae5511c58c4c6f4de6d83fdb3ac298ff2d13d03a2151b446d6b36f9c7b02311663faf00f10f2ce788062db8eaa122a0f495d1f4e636bb1c01267a37c4be575fc4c9e3f0e994805101165b4af73d187c9a315fbba5472b1ce6957505bad6d3fb287d0134213bbd7398eeb3f302fff46151a76fa65f4c32ca36bb7baba1037f66673bc79aae9c2ae13c79424da73377bdd7ad7b1fbadeb4dd6b0d036dc56d45331bdb5217f6bca30d4e05af1bf005d9804f005be03480295f906b42efe84bf8cf39d147de368fedc119ad2c37920402add375bb8fb3131dddf1baac6353193e29f904e66499773f86ab4978e00cd8ca7212518c4c88de9b76a30c23554fe05f7a8b97d5b950769c2da6c09a10070977a5966b481a2f413f4173032a3d4b3d5c70f53ee0b1f6d33991a7b0ead25d1d14cd9544c1afc8f86f7571eedcad136683dff8b55a2b2c4f1e6453ea2dbc6ab76b61dc914076f8e4a0d31147d7719d93d039a983de1ede8751d5f9b9b86cf199c41bc819c134b7569974783fa76797d592692d65f1beee041b13bdd5b502f1621ff8162cc81bad36cf709157539a98d28ab524a1b55d6a4a436a2ac4a5f62fce31d41bd4bd708317481de73f8ff3d00496506f401c8a406a7c95f93a6bf590d55f68f97124d5ad7f381578e5ecb4c4d4f443a1767c5aae334c168b3c03c3fc0748a5ed0f70765a241a91d2810244a6447c8dbb9a1f4a351c1fb926ec0a1f6981b06e89119bae2f41288f055e57f723c2347b318384dbad95fb606a62c68ac9efafa33b850105654d9ee654aad183b834a12b40ca055812d27c0aaca4416c40e584a6a37bbdb4b56f6e4eaf5baebf6ef01932a4d77b36818878bdb98994193cb18689657c81561e55f7a0ea1612c758999f4dbe619f217828ba9b67b6bda4ebde2a9553e43bc0fef8685566e9bab31d21108f01e8008e2a752d15cc46c12e9abee488296af50663db135ca0c017f0400dfcc74746abb77c95802c1e1f3b8078e0c564932263dc3c516e00733dd1ccf5e99f350c63459e4adad49de402526cee763ef371bbb68c5754c66f333206ff865398daec8e1ac77c8154b517fe70f8c3f1867ec8561104b47f70f9c01e0b44e010180bd08ed032b85702106e345715538d7ded36111152c1acafc5ae1e2091176ff4b6fdf6e235420d8ba31acbafddc93bfc7585888453ddad806a161bb20277e2522944e6381cf4c416068875efaf01b59ad032891fea42b3d50d994ebc3375106c50a3c5b56dc9d2574fd0ba6e74988941e0104e16b5e3f84e9a22756afd8ea6a48303c01bda005b9b34c5664eb3cc9de9bfc1bc699f9d21beb67d98c3f0c840552f25e49fb3e81868ae93093614c7b27af51453c0b0c0fe0da8b75b9ef9bf3c41b61d4e0bd4ae0f703b9fbedd3b0f123f5e0dc443995ab5fceab51c6fa45a45c78d857d19a9354860e593708171cff39e09952736d33c79fb12a6889aeb6a988ec7b00698dc0dd8fa4cb237bc3d62508f2b5a311b59ba44a013257ba1f66281256d63a28b284e4fc2e52250acc088a58c19e6f4cf15e3df019815e1729847a8fea422ff9a82e7367f49faa2ef56bc71c5efac84a034399cc2a4e2b26a6cfe489292e61302ac14fc135acbca54256f4fe77211fb090ddf4769d94b7928724456c91efd4a851c4fbe2cccf1de8ea96770a9f85f3bbcd4ac272a37e5cd1f1dbd96cdf79a9f58bdf1abe94dc4c6dafafbf956ef3c202068cafd72f697298c2010fada7716962b35a2fc06a492fcb8da45f1d3d1f2f61546521afdb92696696675a43d9cb16da40be22c0b174e30960cd820acec46a00380021525ed682abe76869c5c9dd79d7379a22452845d91b9c10bae67c4678526155c70903253185a1702d966928cd8dbc54da5176fe3afab51e052d5a59c2fba96c694e9cb81fc89b82642b756f924c3417cb7afde8290a8cff188ec96f0d51cd47571957c4d54a7de717415883e28bf65e3cd7cbab869f0c707143019e6377fc9edcf75e54896175d46e3c691e24b614914d06715d5fe4dda08b2f13e2e5830bf2c6a8773e4648958bc7978a26d1bf2e9a96df488d24702346054b2add2bd8e59e68144cb1f7a1201b7b1daabede19cbceda8acd977b127626c87fef1785cacf3d2ea8f1cdffbafccaacda056f45b106d812c47d831d6732254c89c17d2598b070a88516fa537cf2f314dd02a383fbe57ca48c2f10ac10820cddd6eecda5ec8ffafbca978478dcfc8e8bdd8f0ea0c2172f4dc62994208f37d01a118ca59ff46e4ab01d1d4ebc27c9d21898e61d618a774328fbe62b64f9c5536e21b71a48d9ebaa6955ffaa789d66736e43b2e400a4cbbd284434cc6499621d79a5fee49aa14f1aef40a53f88ef25017a12fc281d78d3bca19d006e63b6232cac9721c865afed05dd7d3d8153213207a02b706a6653301c4046c2bdad66a087a6be215bcad60de19e9c6dd9cf03c55b7b89897f67f2aa3510040d3233f0855b4887dc57e2f2461bb728b4773da6e5ebc0b925bd32b71731692701f989479bacb608928c6e82319185e6afd2ed563ebb655e2b4576f297d0d4a8daec67ad8a977c83f2ccca4bc2b056928780ce76294735e57a603269bf0b8e4bcd59e96409c8a35d9db698c8bc1a6413e941809576fa962712e6a91b471d3569c7a633f291c92154459367922fe52fe476ddbf84e67232608e0989df8f7adefde80c9bf3b4f48cf569e4148e7349d13f88c425d7347523bc9b6f6d1f8c26b4db36c9719e5d091b323d0e2c57e55057df4a275c07fd44c701141cbbd9a10708fe8d6c54effd07b83d3608a24ab4d6faa68b58842d11a5ff0184e42cf5e1b0067fd769438a98dc500742de18da7cbc6960031ae2342165f990f618f0301337d7d0fbda7359aa2b97b5f5bf3779424bb689ee2a688c2e108d5d8a77709d181d26e5d3504b03acf37b107d8c3049926bbca94012d40d4bc62f7af70686cdfd95599dc3d4c5225b286efb822ad6735956bb3678fe3012951ba706b731970bbafc93308f2fee4eee8efe115a1abbc5a7578d16243bc6954894c9c89760a80c784850deb23c3262b8b6ff98916593cacde2ecea346088878f8b400b70c13b89de9ab0fc236198beaa9a10104c280710f1c91f08d6236336d64f08114702f04dd52f2fb8fa74b5d955e2c740ecc4efba8a0e0fb3e4164d8e99e4dc3090abd91af61627afa812c8f7c2ef13c0aee6ebc49f5fc4103a70aeccb0b4b622007eeaa1abbe73cbaab7d48f7766dd9ebb404d19403c53bcc9188b4ea33c6ecac87f8f46a7a0d7be9c8e493ea5bed10426bcfe0cfd041de78d14a71f263794a56cdb1187aeb2de93dabc27083111e4e06f102a5fc704efcbaac4c50ef2dc1cc212a5c7387e89265df19744ba55261e856c24084cb3ccdf1aa9eaa34baf87d592750e289e0fb5be974b834a58e68800e06d42faddf34e32083c802a4aa551675864ebe656435065afca99b1088d08859fe12c94b34d6dbe99afd965a8c1fed70222110d17b2f5105453ec217c86da4344ab5c0a822995256fc3bdaa9f77e7f47f51086db3bf7575cada207fccb431c4f1f881a3c72cdf002e96acc00a4d5b77c27b48d49e1c0588800a4279a76f3d5c4ea6d4ef573b18c494cd67dcccb4b6f803dc31078e4d967ed2b014e904ea90f6f19931e2271085bc16cde626f7d2c94462e0a8f9d19152271db13d006c2846e4175a75358002c4bdb160062bbffbb540f001114872cfea018a8e6ceaa2836f658c4ae334a3dc562d68962c15a3d8239c58b656dd3ca1c893aaf46d72d6e46d6dc6e40bf29d4c776ec77b3fed8885e21c77628ce73dad64e37c6688980a008fd1e1e0460195335af1440515325a83eb5d8815ca84c60abbbf91185ae85cefd1b7e56bc58ee19c9960652c83cefc64db76408c2b0a9010cdd36c86b46c5b5866e23b5d4060f022edb667c7fef68f883396fe6cc729fcb04f4455816d51de434a4b50af5f9b66389631c546edbc87d01c652d9a571e04a7002f620214ee51c97553bd12e96acec5a4b21e0e08ea424ad79f2bcd9e7e5f3192715d5d5b21a0d2e139f28de48591fa66251ed3ac24934c9bf53178541956d8c596542ce312b43617d505d222ccfbcc1eb25998c56824ef002a8b79759cba5bc949e484d13b0b233b56ca2995de3201257d3a50ec2e1271eb3d8040181b7779bceab015f00e2c4172170d5844d3f11212c46634e0992652928089d7d3a5c3cc6219cd55d8f3284a51f2c1577643419782acc7909300f8726badb4f6e1f9029605ad6c603577f561c3e5a81a20bfa1a92089dadad825ef32edf956c971015f753b2ede84b45b94c09db4531017f98ed330864b249e00f222598b36f11e1a17da1987ca3601f2e8205f7a1181539de8e4906df14ed5303d412a315545b8d9d0fe6efdfbdf873ab8506ca665bbee820e2a95ae27db042220c0111df4142800252111af1535ca53013cf0fa37ecbb762000c07cf3b3713b398c9012f637d0f2e84b2c58d65a473a3eb881a06f3c588007dd002e84c399eeae493672ca4ff3e26658d5f27a785e0a9595d506d4df35b6f67dcf7ad886d8fff679a40ada3e7fe46c4270989b1b9ebc234762d635498305ebfd4e9acdd11f48c2906d32270907c9dc280621b999c97aed0ed8fca95d92859b231388a0027b90240a73866f9d3c30d490dc619955f5fb77841267510887744f054704dda36308493958ab39c94c43a2c49b9b2f8410b4007d328380f48a72a0b68aaad8f2bd6037c2566bbbd8f74e5102c7b5856a704774be6afb3b6650a186543d8e19e64649c40b28edf1846f4fbf6cd8ef49c10e9c986bb4780074cfd43e5f773a5d50d73c78f3f71c897b19af5b5b249cd81e00f4f558237ae08391426452130df1d6bf0dcb1a3073375beee800fd0d3d1ac224f6638c92c6f9bbee8cae0938785f061005e1c0236d5c23493bb2206d4899832f3f76aabc9eccef78946ff996c119d5e0e9adacb31f9122b1ae51efed5d3f54fb58d8dbf9345dc59a770b6b8d36e3a480f306bc023971891dc98812b38298898e0b8ff0228e6eca726b7c4cb26f0861038c2a54d93990d76eee5d4485abec80f6149b4d26901296441c58ff5ed5d41d88797747dbc5ad1f560f34b0d22c3fb6ad113eb3b2e628f9caa4a2cd4d99183ad9e2229b1ec46e4b4afd64c3454b1f14f1aa43e7f1de8c30ce0fb9bc54da5d228caa04901eba744d6beac2f69222f0e3d8230af3c03fbff8e7a4fb2a901c70e0779d4264a23f9757d921518fa2370fef2b193defe1fc45bb250f74def0e7e0887c40940b7e27789dc7ac918808a5c29f53b99dc70c412546c828d19f3b38bc9636ca2fd8c7314692e7d1cd7b85efad8e5fa63ab1fd39f98561440c493f4edb66ad4f5df5fef9d7343a4117e125a77271b8303bd29f85140411a5754af0789a00dac22770a2efd3c817903f442d279b7069093e3e56c23918a54c3323f6e56b6805e0f72abfb0acea2d8bb04b9ec5df801f6ba0a8e02a299175713d70866412bde3bc23831c075a1d5791047ee5848d730fa8564cd9b81ecd7edaf30a5961a28b194eb8128ad6042d20d7c715c9640586318efaf54c0c524f050a8c08815ea4346ff1dc1e4abc3c9fec5d5d7107218ad8bc8715d32942207379641800da02f67d8465d4a8a21365f298ea47aad6d4d4a50aeb2e53d654036db70d2b6d7c361a0bf91aa69e5e310d11934a636ac624d5802a5b4a71ba4c6914d5de392dccae95c4c3acdfcfbe29ac67941740e5043eed93c1022a298719c8c6b3b76fd1d6f6790e8363653c7b9c46cc9a3ef43f6face3d9e35ac5377e65ceb968ae092f2aeb71b0e6cc567267cf7ea50c8eb0254005028e39ea8cb93ba56b2cb7b2fe90583eff135add13277591554e057f0719cfde2336b7285ad74a8f4dc6a043f74f35289ef292dca11b5d121bbaafce1e7ac79317187d18a14213f5ce74d35412a5f6bc001c24d3c623f7e3ac55eba6800d1d4b2ce350fa8b05d0bb1aba9dbf247da0ff5b054c63725dba36e89cde69c04a6e8b38cf7c59f1070791355d13b226791131b562efbd2671d62e2c26d00187711344c258206ba08361d393e5e76720e37d83601bd987814dcfefa7ef82ecd60bff5e0b269187ec32895a92a68ec83bf4c2bcd1427355e8b0e9f99b480b681b3a65d5f4786d3e7a27d71c0285418083d93bdd38a9aca26eecbeb2770ff5d0e4b247e27753879beb333f398e1d220be022c23584639b9a8ef3c11a048e6fa1e64d9d402c37b190e1b491ccceb871a5fe63c0418fed2fe534649d7adb3923c726cc13d012bfa1b17a767190de85f0a5756b8b937a69b7b1ae7605fd83e4e3bd58373e8b2f2650e68b25acd2d20a4c4d205db4b488fca3f6f4db8d3fc753a6f22ebc2f2606e4dada4bec5b6d075acb82eced62eebea63146a82cc7b9571cfdb7b36afa76a57c9cbf6802335964cf05badf0ee848cd6427731ce6d836f58f98478a5dcd018e01ba4f890cc8f36a72e276ea8a8d52d6e92e7401592f3c6f8398325cffc5b55ce6859ea3cfc238773b6863648d9d5caa472c0cb03c7f1894afc60e9030f2a543949968a6dcf7c2b637e153892a95b9ff35e854c4b8bdab22f04b17c21b7280c1173d1a6b00279b18a42c0ea1b0df3e731071d2b1fb7fe513b89d446965f49e6b3b3dc2cbb5a131fed3ed544898072e9c518e213d0013a97a8a36a39a395dc34c17f945abceddca81c997c8d035556608025f85b380018fb1818e1ee8c955f4d030de7c437b015061d614106923372c2350fc45185e0360edf1b3e3d4cb20f4b78452afbc9a75259c72fd7b2f8eaaf4aac747108bdbde5f0cc1911dcd933aab01ab6dc90fc701104457e2d0427a2c0ba4b41080c881816b0f650be83c38ece090c33f185e28ad9e575fdb8158b703f6596d6c1d412c2521c27d461024f022fbc6c367aee23bf7e6e3299fb8dbc9f2090bd906c15bb611a33ffa380f144ba049d0632438ffa61e4661243f2587ca89cb9c8683a2c4c667d1e062b129348fc919a060fa92ae7a73a84daea6a3cc8c914129af7fe7ebecf62a720ecbb322af273f49ef6d3f489c5a760becaa5de0b7ca628472a3f08d7e7857e489d53c5dccadc0bb4cc25f1477ac95d1e4ee2ad81a6f0093932837128665122c4479113d1ace253fc26f4f32f9f7cb7dfd2defa6d24dc0b21f9448b24d1ec312b826b2458003984f65055f6a2e660f4dd210c43f595bb4d9a7a1612bb358fa36a1806b323afd043c11d4348b2d9765e5d122fdf43c046aaffa14c138052cd054bda31d6e00367f052ef770f4f8c3704e6a7aa3c71217d8a6d29476ab14073f987813a8e155344bcaec24a76096e7838adba08989e81f8ac144b36cf381ea4519feeb71c50f45b30ee49ae47061cea9ae6d4e84b2684d52850388895c968880bc3fd02028a3fa1c9b1d2d2959df0affd21c2494d532d20911b0ce1f8ca481c05d14240842c7d2d5e2ffcc8ec00479aca48a955ec1ec48a28fb8c3658e2a9805bc85d3fc36d3a30160ad16f7a6952077452f7897b1e823e2a4fe905e794e8bc20ff0920b1dab3aaf39864e35eba6ec877c6f87a0a93be5f7fefc602aa4eee477364abe8ac3093c16682cc4b427569319dbf4f7d7b3b9b10cb7af938f5cd31b23f420b1aa137c47a332da64b32ee34715af15a71a7c9d2deeab4ef505838168662bed2d3f85acc3e99dd026500654c5e0a5f1b5767cd01ac4e08707cba019dbf5a82719a0182be9279c3fb0505b8195058b8a985de424ba6f5dcb9f3095483a9317ab75b646a547b035db379d756a61843468acbc89e15a28d4089d25107eff783cd34f7916a3b209bac6cfa87b385a7e40d32a035afc50183da0b9b3a6041610b0c718dc247acedc538cb14dd3fdb1990782307b688ac03463064bdf485c51b6cbe4f7063622aef9fa426b30338963229ebcb553508d2e03335fcf5153a3a84f7b6f02481246dac178e67a3cb55f922aa00a524dd4cd8eb9fd7a8d179a3c9a1a02df49b801e5aa1910a1a046c6d34bece620286f0c930e1385019aaa06db33f567296a372c4491181637d9ab5f72b42635f6a67eb4219011585db534753575f0f07958e59d8f96100b5f755b4cae5de1aaccbb5d5167f56bbf304645d1e8b8e68d2cd33d9084170325d9cbdae328acf930628d8a5a88fa4f9922b9f9d61d8a9a8cc923305bd10a78053254bb04f2ed70f49faf80848eb339c57ffc7d0856484c97b6e4d52e315d37b7c3707d73002549725078a91986dcc3cb0e720959926c0fc46cd2a06019378329692dfad654b2e8ecf8cc99bd8c714b9b348b844d45df2dba8bd40aeefd0e3bb45f3385f4ac87598e8d5e26799f304928bb122ee590f55be95203a551db5cc369b3c76a1ae0d62db359448d61901e5a4f2cd2c0aab435dfc2481a696b68934b988ef6d1cbb26c7e86fc6bdac1c687c878ef89b9fb2b6717693005d46b46d4c08653d7bad36679ebc44638ab22c84bfe61779d2c91390608ba9fd1256319cf5d84dd1e3674e84309189d75dab0a5070c3d3063729c93dc10f22090d9bb19b26e90a519f5dafcc42a72059d68a189d0e9a59a1833e17773eb06e7c0a6d89f2962a936597d87ca70cf08da1cb93f42e4d0f494dacaea188154a7add3427a1bf244d9e6c1970e1c8d588683021a900b0e1aa2e02c1ad5b164ef62156ed18a7ab554be604df75c6a937e1ec91d8578caa46d9b32ce44c68ddadaaa70a4aadc77593e6b6f0320ee5852f3d9c20392b2227139b8c080a05069151d0c564a5815cec57835e62668537f7301c8a378e460b2d6df5eac02a86ac4d1abfb41043e8ee5d0b895882f1b1c19e7b6dd67bf0117e209de7ad608b93e0e5dcea040baef86571763d8bed095a4d3352e6975bb0b1715e03209232de8c03d60bd0c2ef7fd07730147e6646d6428817b6165dcda840685ceb141227f8834c65b5415288147e7c1b9470b9c415cb269a24aefab5b0860d45f851c3e386b4c068ef751ab423268aca06a99233d3d66a0aa5651c9b146e8550a2a602ac68048388a419ef71c591ec4e900cb24aa0dd023750ab0a2aa4a107097ba046e81ac84ba585dd83888d00cb9fdd86cda2afb147a13a5859272a0696b0e0570fa970bc459c4f04c399bae709022bb4e0224686dba66f2df9b882289883060a9b08cb7a24046a7a05872f50aabdffe2e095a31d39049ab4dd51d78f44d9ef0d15e418a20770485512e00a685dd9e224c73f9b2b0211309071d1c49cff2db60277f2991a1845cfb9278492f55ee0a2625526b4f08f0b47c78b1d40b4bc1ed9165218f7a9c6344b66e3454bb7a699d76b9bd7775c1505e7fa1c2a6d55a0aec89bcb4a22a448040cb5b0bc1ea1d96b731a07de8955123039b372f94564b5e1a24f4454aaef8df892ecfa3069c6042569e1e741afc1c7a544bedeed16ba89098fd6bd74d9c078cae7a465700fe5280aac08801d9e8cbcec18c1181d6e71629768165bdb7db78f64ef422638be1ec8b61f0c851386b264eb523529e80782987cd0d5bb8ad0e6733b3fd5139adf5a5438d1745a14a77386f9815b9405c45196c5cdc46edfd066a5806589aa3ef6a664ce96d0c885cf8013c89ffdc6d52e8e6cf3a56f3c4157787a31c5ddc3f147e2801b0ede394947cb30dc09d6665a7d4fdf4a5d94119db7e6740d4d2018455bdaf8a8d68c5b68dd10c4d9703a8c031b213146b53f1b45227f591fad289e6be77e27faa2c9f1010b5058e212887f8c09e59f11956e989692118b14ceca65ad8d7910acc97c13403c1278a7fee263ca160277e094ff034e4b4897c741016cb8ffd40a7d5fe1f3f5b5654012bf34e55a9df23602d3471ba947dc66e767645052bb4fd85508985f85e7e00e3c33faa04666c10611cf1a6a4ee245b414b44ddb8fb08c998772f9d0deeb53c381bd2b0b05bfb9e5b48531037299360e79e5060f6602545729efa7412e7c13be9f56cbdfb84571104bfc6b4f9775d4c0482ba8900ace47095cb823cb235161ac0e694a310ce136b543d012608a3fe21e04cb59c96e4c0d7472e20ea8afcb0a21e608ce50d128c04b8f1169b4a4469c6451a8b3c48577de5c8ea6f000c74fbe27b3d4a0af2878b3d16fc9c6f232d8628fa9efb31e12c86b202aa31c906161e9b6403d79840457b82d06d10214c61a6353b3b7031c6b3ce589ce4d5a238eb7738c9a1fff22f3a478e7adc86d9f224b819cb6449683c378b92d7e8dc5d861a01a4814a137559f3b93a347e9e62ace31296d9b4edbb810523ecc474f460621cfa288f7a5a632a3f3c6b2e695c938740dcdb281df628cf98414441471310b4ee2cb2f6d8fb7e7e412b99b3349785be2cb131b179e56fd0a85476dc9209ef39a8b3fa3522c53dd4c11d5b309b60db5d964200afb10120959529909536c6130113572a0944079e4fda2275bfa9a4913966606170fdf5604866505690c0a53c75b69060704afcbc3b3adf03d89e581a87b59adbd7e6411446791e22464ee825800d2eaccdfa3581521589b69c411426c9e95c613d25396e66049382fa08502de3041e2acd783f7dd4e0202d4fb777a47e0d12678c31b70eb917c82fe6768b0e1f705e75bbd54d32adf866dda6e1ccf59b726807ef3d0392add1343995ff20bb263c0f96d4ab98d40f3a6f5ec2662b64750b57346520f430cd8c383c641bce27399c7fe89d28622b3f5697c3640fe37696c70ff1fd113faefd63505b91dd26d8ab7f10d2cc6200e8be34e3dad62a56a71cab600c3cab21c630f2ad7c25f7dccb98e08494b3bbacf1c594fa7f5022307d191112094f94c4ce9d1ff51c704c0c16ca8a843eee22778aedc0c9fb8ca5f990c9482f6ca2f2ff508b895255cf5413f35277819b2a13e94bcaed5ad726c804007ff6d884ec6020bd7f612090ec35a3598bcd6a8b7a387b3222c9bb3fa18ff4535b64bc995777b1841c174ad2d79f6cc661f66df5f76ff5e8abed1eaa7f3d14422a6e8e5ac18e009795c64e16c2e36b61325f5c6c602925967b57965d42230a94a3ace3432641ffe1dda26754d88891712f2012eb8fe65c54b6b8824e4dff494803b2da0b3b0496bb580eaa06584aee8fd01a8017af16f08e281bd5c0489832460dfd8b6e067b56c1b12c2e3152abe1eb8aeead3ed5b0991cdaf57494ec02dd534fbf6752f4ec6b63a4be7d7e18ea320966f10e870d15f40e711052d10bc9cdc912e8c0bdd8d7fb59bdb65164c1b349998ebf4940f8e877454d2f690882015f365858161fa464170438b8b1b8cca30adb5132af722d13f7bac8b5848ea2351b4f35b7a1a673f0bd051429b6376e53ec509a981f3b56e5934f8106677ca6bbf35832afce45fa389dc83bf9966807134991812f7e35756fb32b2240389ca76b6fa34ff2ea96d9882ec288624b2ae47651c0816661afcca5c0e3d5788c20761873b56f66862849514789094f9e8d0bf971055302fa6acc87f8ee58337609a08bb728c69951b9fc94b6bf22c03544ede4f9392c6b603295cc738df600a8997f6e8eee02d86ccd6bdc91bda9c71aa3bc6c6ec19c3e6ca63363020c9ae7e66a18b3b3169926ae2f5e87c7e48267512164a6f45e8785a54561b87f588892235a22d0edb31641f4ad72a5ab1cb172d4be5557805b9cd2b1d98aed4ffa2b12d3b60bbb709464e46a8361beed1b9e3aba1f00610772e86a9442d34825b919cb04bba07c14bbeb3c2fa80788beb56af0e41a6c133a222d4652a78e009120308e3e79d538aea64603d72bc0562c1d2287493112c98f297a5669b180bab05506632b49090843ca1cf133ab2c4a698418ae0ad207e853ca08dceaa3d355bddaa9fce19d73e99456f4d41c20364f576b728c924d3ef623cc25049a365b9c57405ad9341dd9902b9821f68db70ac48a57c1bae1b67837943be26c4ed4259f4b1ce13de8d1ade4f526605580f165bf01cc05f9b3ea3a338b817eab1dde490c38017f9d8f7b086721f8b325d6da7623bbad97b4bdb7dc3bee09b409ab091042f9c426cf7b2af5a9116d90dc49241049f5d9e0126803ab80519a9332a58174100b5ae97d0fb52dc4b375109737ecab6b2045b81d14023a681ba259aa2483a8b7850857328288e6e410cdc92946842cb91d6c20100143a06822ed12ffb9112d0f833c20aacbc30f4027200268bf2852f7b4f0649ddf63970fa25d2ecbbe802eaf434ef2f5b3d5f0e10a4ff6814936a864834b9ab3d960940de64029cdc1833638a55980207931a6783172007f805564182443888006f21289348b7cb959103d841652a23c2c8cd424c99246179564f9870409ac8970a6d39713541c810214d926570b63e5bd9065599665d9c76cad045025bc275421cb033c1629e3c590202edc78e947dc64215eb62e2325cbfb784cae489265c8bafcafd3cbbfb09f166dce6278a43958b1b468733399e268d1e62e6b615864daa475f90c7ee6d2e6ac5b58c70f6ef76e84401144bf8464c0cc8889f2100a2245bac6225bd2b21e6a17ebf02fbab18864800880d9fab55eb66e5db6cb75ebd66dbb50b71ead1a16140269a8bdfca85feee575b4cb745dfe0824ba27e5886b3362d3d19cc4a36681981426514892794ca73446393db5bd7b37d6d63d5bf495f56acde469e3404dae9bcccc158e6488948ef8227bca9e72decc6453c31bb14d3cc883103c48df2fb4c9f4c11174f700a34028dffb8205f7ddcd422e7d1afbb71f7a0172c3e6c190fb983c4148ee4253dee55fcbe82104f7317192051327449838c171d2c492f957cd0d6e15772a906327c7393730a76d561b7c2c92140f4554aed34efbfa08d85a51d516a4fa8d2cfd224b9c3e63884a0b8701327d7df2f57b2f5a178597e8894e4992a248e6f84a6dad01af04bdcc8b214935e469541466daf308b6f674b8292192997adc062685a7ed652530406529a29987c5b8eb88eeb411b0f08b2c8f40bd0f4f78bc2cf20c8d191a4d49dcc1e9750b526fb720f63edcdc24de942c13f323c0cdd36103e2271ca5c461803cbd52b3c6c4726adadd9475511c3f2c51cc3c5e06da6a80642d1b17a67ae2829130d0180143fff0a1a388aae63696d152d55473a8e6904525050eaa1a265aa0a9a40085e165a0aa4ebdea175bd9fa6a7b40404b882cc8b5cd65b4c89d1475fadb2e95b470583836f0287c3195707a9c360d87c46f1fd4965b07e1cb36310f880464f91b0f8829cbbff7625e0cf91db270bb2be3016135e03537e3efe10680365cc9c2fe3a173bc45066662d7e06b669b18361f2b56e611e5a1230637f994a729fe56fc813f15e282714610c567843146aa8020d791defa55283142928c21646c00431d09093a49b75c19d812d2c6ef76c626ea46fd4927117cadd6302055a328ff7e4cc05251e5427a4413601a929a4c34d898ba4ed3d8e873c7c8f0503595a22dac12c5f9ec994a5968e09d2cb4d2240273f252a530bc7274baf15551d566d0088a9f708d0547757f39ab0b52c1bf250923ec554437df1650af9772b0242a6480226f3e0f20c0dead2124dc7dd3cb505a1eec41449c0649998fbef11408231b169c8ad6342e6381501290c1f0b85012af7a7982e37254452341b76f79b57c44b749ea8cbad88064283c2419cb834de2380012211d5a94d48d30842ef04a55161f808302fa798629251eaa8c514919b1bc8528af3f0fdd0973acad7295bce2871d7c03c937bb2d326e2fa71e6f8495e92264aaae67bda60c621ea37a01f3f593666b2b453d71db411b950eb9442391d0892e791e89767d3a3250dc8ed9ee784d99ecbfcaf135c98d8030964c890a2bead1cf559396247bad6a376e92e754b47e2858b09e20a79fe46bf541af344cc17d12f9f37c289c5a4892579be4743e9f70923793eda99295b59355e579385437ac248a62e6d6feba4a65883e734f8953c6d1ac436207e73f340ee61e663e66fcc1331df01c27d4c9260932bfb1ed32e1da456c01ab232c1f521bf285ce126bfcf4993dc08bae2c27714fa805e5ed28dc7c643facb95ec68b65c5bebb39bb893f976a3f09247125296974f026639d12ce55b66684d735a8104225a711790db9ad0deacb9ee5a6c7247e10a4fec63417d937628f4de7b0f46bb3c634923b6799267c03082942c7da5be3ff97e07a1f44b970ed32e533c029490a1b9a9947809893b29039580d75e6e213a35b79359024209ea356b97ce1e5ee241bd2f3191fbd2a57d403c20ac4b7fd12ed6f65e0c0873ffe66a03c095fbd7959fab6d013de47e0f3de4da365b445b370824571b3442a6364da480dc578002727d9acc9d618e69696ac10826682300894560032c4218e0157236a724494558520427affbd17120c748032260b184294a8ad084144b860865a447ee2684dbc9dc1b8c3042d8c41082f43b946384b9391a218463ef2604495fff3dbec6f8ac1761e40733b4dade5478b85524b81d049cdc5fe909c428aa02c2b6a22a79120227cc404807ba8bce755dd841d83d0fc36e5d97b5163be8baae972fcbd3f9eb609887a22a557f3d47cd81619607db40d5f5908ac5302c0bd1db974396e502040259d7a5d9b84e5d745122dc8d3206026d9e635b86bb0f1e6555b3515f3d47bd1e0b6836ecb1dfd76b36ecaf8b2c1675f02fd66c60bf2ec2b0480466988107da288a501508a8c38dc27254186459fbf97c3e9d570f752d761bf790c73d9499f95a3b32ccb7adbb759fdf6b4f5194ccbc1c0c983a537daee2c213936b08a62557f71d772db080537a6ff5b9a80f45c9cc4c3d57a728bdb756af17773bf295997742fb2526a58faa5e7108261c30ec2862cf2003273a97a2acb59f773e07599fcf2f6b3d1ecfe71d6b2de89fed657b519ffb778ba20eff4659a6a8b6aa8bb50f66c04cbedeab0eee32eaa1175ce6e17464ead58b5c8db24a85f2746ca79375b25c75b25c7d28bdb776a36cb3cafa28abe0f56b94650b5320a3ec2bf6e18a4b73ad4d3c19a2516154650084ba820f7cae83b0ebfab52ccbb2ae7f5e5ddbcb55b597b52bd97646e4dad8d659b7f69a6544ae2b75c5b67030602cab49a69458aa9b2123538791a9d18ae7dd0aa57fb56a5dc97525539f95d073558d5859a9afb97befb4b2f2ea04a4815a30316205cf060555902b541e9042d58128c4a06a22478e1c20097a056154068445409e10030b860a48192a0ca82dae779f679e4f865db7ac5bfbb1b0bd959dbab2da58add72cce5155bff54d2279fa343289cc22b36666e4e2cfaa01c295910be0e4da8d260d758fc52e4e595705baae8cfa5834f6d5a9576feed2404eae561d4d9afb49932f86293069ea292c24575b8c329b0064410339309849a816cca10b463239c8ec1f0ccb785418e8ba282a3da70765f7475f06cc64d02b0cda9a02ddc24016742a93ab3748326026ffc8557bb04ccb1fd9a76f8f6c6961e5608108cafe01813e199669d93f4012c37ee4aa2d08a3324b00206320d91c55317ce1ee47f7f4e18a7baef1bbf1b205aedfb20086a95f98c2af03f2c5177e18b982881612eb51318bb5fcca482a242329d5c3732d4a5db37a489b964a469e0819d9ede82123e16b0edb969034888c9483861e44a010072dd4175070a00275c2ac85d12fb659a64f9f6e96795eeb9679b68b552c05e858ad71317c315eb4cbe72ddc3d86798e41ab8684d6b365b9ad1af745d81d13f9757feecbb5b5daee73fbb9b5d8e79e7732639f4bfbb9cf83fdfdf26cb0dbd8310c8391efb157cff63976b74e14975ec34e5d36a7619fd9fac53866b636983dbd830f565cecf59de75762723ab661bf9edfd77b3db56375307c31b0c774942d18c98f891692e496fc9868214322257b5fabc538ec76831bcc35baceab67a3cd4d9d7b3e75eec54219bb07d7ede55759ea62d82becf5beca07a7bc58fd6c30dfede5ee907df1a2f33c74ea79e7dd7dd61cf67a370198e1c20cbb9bc383795c1c6a0efbed5842c0ecf934905cffb90ccdf51ebe1fee41964866cfab0b0bba7761e188f97317966866cf07bf17e362da1c76cb836d73d894b1c28e307aded98361eaf72318d9f36ad57856e79ead9389d973cad16b4100d200bd7a3aaf1fdbd996e8ac44e799753d87ef07cf33ab886703b2999543c2c7790eb270c4dc39c82ae23da9fd60ec0e96223b6813721ad93d561159d734cbf4fab1b65f6e4c3786fb48b584dbd9ae8fc497ecd3b907c4262dcd8371cc8c1d93b5c3e589e262859dad636114c631b3ed26d2dc84434a2e97a7699ade476a1a490fa169289ea4b68efb18e9cf914fcd0749bb50a7cf6e001100395b3f4d9ffec2a5f3e93ceeedabcd90746e6f5996c7e3b9102f7bb0f6390fcfa97fb00f575ccd835f739ecfeb6732ddeed6316124df77366ceb6432f68f67eb68ee608fe9997c378893b16d64f3654fb7ee73245b49e92ba514ad284c27cca393bc48419502c879f672ebacf83e7d64f355e7e1f5308e392d6bc2ad26bf3f7c59d35acbc2da9c9fd656955555d6b2b6aab6d6796be7bc9d939a5b86e43dfba8db6dbe6e149da429a3912a7d5601e44cd91457267799cbfbb44d18c7ac262162bece640a5a3079f204085332f68a610f9ba8f5110cac3d8c03d311c4c9960f57dc69ea0f923ce1eef9c893c5acbd6a92fc5eaf77eb5356037f987e6d6dd578f863a442fb726437b8b697699eb60c4973d3dfd6cde437bdadfe5935ecaf9aeb7b2b533282ca16ae9c71c619e3945054952ddc78959b96d208f5b5ca4d85d87b72842fe46789300b54599f6d18a74d4ab823bfd3ad2f6d33f9e1192dd83a0e34c90d8b50e3a5ac626ad57950ef53fdea6dd1479e5e6dd4b3a63793278904f843cbdce40c85700cb1a539d9386218cdc9485f0cf99a49713b193e581175c81fe0bdc0c7123f2f7f1d74e874323a2a7732726dbc22ea4c1f1011bf18f1044568aaed8604696ee28be4767742b1e96a903c1f336d35657632b294a03416b9e244ab46853271e684d26579c25c714c733388cf67fdf21ecb7d326066b48bbddec1c893f228923b99a696655d8897add600169fa8ca6e3319b6925c6d323353d32b8d04e40951e33d2cb508821587950c5b49b50f7ae03e265008929f259ab6d8628b2db07c7f5ba8b9b8bd6c23b4e270bbcb72f4c0615031276efcad180824f681eba94d58717819c53cf023554673120627bbbbbba190208d73abdb773042bc181944114fb27c50f2266bddaaf44a4e8aa3a5a9956b9121bb0e694e4a1898ee4dc952729185060a4afaf8d11d65d42cb2b23d6a97978da82fd7e56166c404f1ac08a2b96dc606d3dc854393eb8d4775a9c60ea6da3a99deba777de93492214e920423a584795cc3ee9652ca3ed5d607ea7befbd071f33ec51a3804a386287113a7ee04a09577a6d0b4272fb758992aaaaaaaa7a09328534a50f30ff6c481b9482618e196e5b157651e456874b2a5cc585363673ce2851d22ed592ead2ab2455af66b44b77f76b5b4ae621b4099d7b0b07735a3294b285fb9a7034fd2abd5c752f8bdc63aaa8aaa2bae9342151f5ab46a2b989614e0e9279d844c96453859027f4553792ee46d2ddb2fbd2aad14da48b74901c299316560f815a4cd9a0940de60009d9aa92b6e9b46a58d3344d39d2566d7d5a6d579fea992c51974573e20ba53917679924d51b440c312994687c816978439ea7683791e729ed10e4f9894a18e6e474928c497d2b5ddbd4800fe9703bb066a55fb86699a75cc755f1c409dca018d25c90e6e6933ced11abc664d5888745a213274f9c10815042082184f0a6b9f99a50e8de1a58135f60151ecbbc169280214f33d8642bea0e3238725f7e4cae9032051279bf89ccd08ec8323f05678ae4606785c0217352d5654561da130422493b42a189352d09f8621099336693152cc95d48aae26e99b6cdc703f5cddcc7e40a23598ae9cf156b1df27b2772d17291522c85f4b9490b02a7316121a72161d92cb15ac2ed4250b8031e10dd43438e2fb2e6043087dbef6c8e1d41305f258e88219a9ba3239a9b46343775343725499224294f3e9a9b72c8137e204ff8449e3005795ee0b14c99176342225049bb4c2292347d4e47e621122aa6135c8005d4d12ef33ba8579f766404bd442f3c2749e9c0b0081d828876913e5fc48c93444473f3379a9b9048132956e419e4878543acb88122494df294240936e9db92049b3c59026ddaa58afb7948a45de6619123b0065201af794a477cb918069b3ce8abaabab596a2a80bf132857db8e24222f4d22beb973493e965d9aa8244e20b47efbc41e30b77dfc4cdcb80ce5313ee20cfd3396148e4c598ef5c1de136119caa2d38d428aeebbaae2b5eb3716949c02cf362c05f205e177840c00cdf3108f919cd552a9060d2089cae0d0e11125f2014cd222f2908850f57dceb52dc53180e11e2c25208398d7b4863dd885b7686dbbd265447a42fc20824fadaeca09158835d56d2baaeb662ac300c4323cdb5910b1669ae61fa8a970fa2ad088b4071a3891646e41158039128191263d75142fd23bec0dcfeb8eeb9fc8da630abb3154104b60d416d3760e28b071f515f1371e341bdaa2e096732b5753299aaac156732b571a0265b1cc9007fa81e136be8468c4dacc8f059733014b7c966899fde02e3dd28e2b1c05fe0c580b7b18d29a1d6993783054e730f3f00488eaf320f08ebef1780b10f7bf285a25c2c6ed4c422de0b8d2c770b3285f46abed249ad6acea62f5e8c9ef315c2aeaf618cf2d24459c2dd10cdc55b6849b7361e5e8c3eac8994deee8642ff6b0271d70203133f8300815ac02952a234171b0b9ce64064b82033dc50735927999b360fb5895b88976735b9bc7c045ea3fe985c51456e9c9945941c2912f6287d92302c59144fda38d2ec28d402480789529a0dc58ae6226e213fb83839c69c48ef82b9584e9eb48bf43897ccd94be69469a75563f6927679f6f1bd741e3b7aac8fe4786b6b289aa687349c125f3a3874719416c8b4d6d6b02abd102f534cff797b49d8651f132ddbdd42700bc971ca54c12cda853a3ccce91709a3c40b892f42ba10a51e95ac1aef41c96f887eb9d12c32336e881e4b3ce5b8dc851c2f457ce3c538c2ed1bf158e08311888b689365bfef1c7bc41a75eba22aa65116754bab147e58ab58d4d25ca4b4721cb55ab225aaf2ed4a44087a680911e97befd66825e7c0deb1b78cc8d6c33ad8de7a8faa2cdc51a3154bef4aeed4d7f26aae9b5192e3afd3dbad5bc9d6de7a4c537a6f448e8fb873bbd5dff82241d30829541ed3940af69caa8e61b88b2bd9b375118fb046e14f2a4dafa7fea8f7d1752bfe468c15868c192b547de731c61831edba569d3abdf5bb6995766957b44e1faf18a3851f5e59193d294f0b18046bd5afd3f7c3a1280c1933f20dbb62b148b3ae55f655b34e9de6b8a2e47e2874af1555fc6e34f73e6a925f14784f77ae8cbcb7878c9491b2a23d728f2c2349803d4b8837829db5a4ce66998956979ab574ea8a1930932b4a4fe125e07c972da039d2dc69b1ff1eb1489ab42a4431f5ca55582449f919a55bb188a2f5946de9172c9258bafccb3ee85ff6412d16d5eef80946c22e3b87c9a1dc87f1fa24c5851df9e9306fc9dac3227bebf44e4b5e93fe2cc59aa4c9699afe5a60a64f9854e13ec5af5934fa626c1ac5a21723946fbc3994dfa748464c999033b5a51bea55e30b00204b9757927452b1ca3a3ed4707a6f33f9e10b0020530a3360264bf2efcd4706cc642a5f4c0c98c9135f6e8f0437e2ee5612dcbe74410e5bc91c2fdf3176d42a494a5216df3bbeb724a58b86036b22fac65a8efc23db4389a7e912d37044d1240f5ff5d9bdb874fb2e9445d6af679a6c096bd53539251655fcb40a8b28f997a392422fe4ada8c277ba68d2e7edabf7c4a27aabb5f95b9b265d9b9fbe69138b28fc6ca63d6341290a5b861cd56feeb4f8dec9c89c437a4c8f1e4444eaf03bb4fa977be4e93da6e4648173f3dc22236388e8d1dc8bf8325246de9cda722e534a6994e28b4fe2e625b496fcc9bdb7e4167945b5edf2e24fbd1b8fbaf6e0bbd69ffd9e84939079091eb205661eb74e8bb799662de21b2f67db1c14439def1dd2d8f1369457662a18a5580aa790518b15655842560f5c19e9e578615e5e442f69a22ba29a0df97891c451c24f042210014cc20f43108210409c6948a4548810215240000210c0c8cc0e5427aa13d5092d204dfd40fd40fd409020413c506bcd91ca307f6962895f073ad081d717df11fb70c57d6da236519b18f2e4890aa402a9405ec8c80e6051acb6a8b6a8b608404e66439345713b0bffe011239d2c9289e16661b81dcddcca4cc356417d1917ee5b01a2d6d72e1d86d000b58720affffaf58b5818828522516ac8033d80a0870fa86c11e3f3410d3551059cf7625c410d75c0033d3811e1806573354dae48923b4be5fbd403e2c14e7c4d851a02e2d1225f21ca15a0c028de7b5683dbc92c591a5f35d4b75221f4c214382508218490a2688fe9f085fa7b410199cc702d103a49b40437bb17bec8413608640727c01c69844adbe5bd3d0cdb046f69a7edad73cf2c3f67cc55b23dc5abf195201c43fc5fc8680ef6344d536685ea348adbd9dc53a4dec5383d466a9a26ea7465661813d644148f6c9e98cee419f82a60ae583fc3e732e78c35a68603e618d311c216c889ed13c092dbdddc1d637e9bb4d9acbb463b6dd266d7c4a1268c32fcdb2a2fdc37ca329c7394a5a1be152a4122509976b11a090a83129b91647332b1a964454ab22c6abd8ed08a580291922c4b8294be461b77b845b8158976b1ef8f7ac8f8e81e32edd2f731b254dafac2aaaaaab076cfa3fafc7db73e93e5d6d10edeedd163dbe3429e36095418d396b407c19469597ba7a99aaae935b1db6d2e7b2cf02f9ec0a06af55a5555567329cac1bfdebaa794397a805c7065656535175295546d74d2c494fc9c007192bb9785132050a6cb6c5974ba2c9de49451ba349124711731c8487d2f46373dbe4689b29b3192a76bd479d0a6684c8c9fb6c7852c6d12803f4489757e568452a39032a44568c664a7edf394bb2d2198dbc826324f58036be24b03a157a3f73531840341b8a32d18ee5e933f9b10cc9d708e148182a6a59050c41e332685584c71e20bcc1ed3bd04dbdaa6b99507e019ee28bec5db0a0ec00e280ec2c5318849221531492be9256d23a10c85eeb5c16399df411641c8914798226b8cc4000707e208c1c189382fca21d3268550af71eb220dcd44242bd22e9891782442d12ea01841d38465194d9ca6ced9547037935582b053dc269a890677a35075b1c56ce17633f28ce6e6e72f1e4efdbd1711773ce0d71cc4a2b909717af80b0d87f07b893891651ee24c18c4624fdcee3511c01cee0e375ebca3a177a18e8b38f145d64496794a75c01127946f9a659ebec01dbd1aee6e0877218ecbd8dfc33e6dad4456b1a4392a62926845a4c1d1497392c2d149b422cee0e6064a8d4ee2937699a6699aa6884573f3353594de1b6b22ce0c68c0c33b5923a4499eefe1d3650d92245428914baa904dda4587cf4b21f125d24496f9d03baa5dc88b77a1f9e93d6c46386997ae6f62131d362dfe651d5e79d8ba9824cfc72494921e3e1f97449b2a78f87ca4a25dfec9c3250f8f14007a78f71093f0b0bd5c24d2502f228d86230d455158d6bc185dd3e4f9ea087707dc3d2bfaa6a1e044f150542bd161a34dc88024d769ed26bdad98851ed359f6690a6d1d13352650921ce3419b8da7a88daa813fd048dd00fe309dfab481a68d421241a04c65b7db746a03a96cd6259941d967f36c377976b2460998f30569f2c46f2eae6498e93683da909cb29c79d106884175e9ab7634571d4677572ca3391f32cd55a3e62a2462074a69972a469b7e7955c0814da013f804de4028100b98058c0273e014fa6abf1b31c8083b5668d36d8591ea32609ec8d567f40b8846f520a8c8d56ff44b1568543721c8d58b6817992b26564092ab1bd12e30573fa25f208daab64d1b21575ac895ecea5552b955b6703b6853411b6a6fbb407b6ad3a843a8a4b98a8ae62a152c5c29849cc67529aec7c36abadda653706e304973d5af0d226996eaf112d63457d9c0c8557524da05dad8ad7b59e4ea30e01129b993b993b98391abbfea4b6e57d56dbf5cafbef2aafa8b76a1affe76b9ac5a6f5fad55adfa98b635569847db5826a8424d8655c88f09159e64271047ca22a494724e0989e6fa88e69a34d756c827320992254a2211288e1801f2a3b9966f99d7035a38321ddc877dd46b9b837f9d062596734228519c446bd528a2b9eeb6240aa100cc706596793a4422775a2857325d610de21d91a50f73f7986e8877c417db5052d7e0e5a44109b38663fa68ce478dcc0677fac8de68969e20c63147cdf58f68ae77dcf4e53c8661d92cf22d6d48249a6b8977b8e17616e739b9d2cb30a88d6a0fc7ccb2014334f7aee198595e5a983b4d58f2e211357a34074772881b3a8a88325a6266f48b10b76203e63e8487568d995beb47dc698d71ccec634a96195e4e8132bdd276a1af0240c3bd0de99d4c674c71a761d0aa4131ec2bfd2269743af79d0ba36504cdbd4c9bbbae2bb4c19c5d25bb8a91acf24f9229a02027e3fca6800227b95a0b42723f8736f7859c8e49c76cdc5d88afabe0eeb97b37d75dbc9b5ca134e7064aade1bdd49b4bc5214f17ef6ca1d79896d12e19ee60a522a9582af32ab89399397be835b4b5d4e07630328cc7340cc5358be63c5b8d72d92cac1a31ad92af3f4ba4922bcef52ed857e76207f7933398e5d6552c2eeca2c855c98f09166cf2e733dae503933f6fc99f73f9f3fcf9eb50fed07ea172e2e7f3c9892f15a7592635c58373ea55a81c17b86b91eab5552852899226f2c98d84d22e8f091690e47918fd72671696d4cc4a02e477164a3dda9bbced171f5c7cbe5b6179f7220fc0074c3d1900f58405534f42b9a39e78acec2a4f3a78933f37239893afab8470f2e7c3044dfe3031e48a4eb2ca93807952d95e76b1c1214fe20ba46996f9cf27acc9c2084e9e446ef264228be654aaca16b1686ed60ac5850b95cf1ef2f9f490cfe783bb984573f39395e3836314151c737c48c2e5d13230b70f49b83c3e978159e5da351edebd38e855dba8835e68206dd340a07359fb0b90c683f60702813eaff1704dd381071efee23a58ab060f2f70a7695852a1b2452811a74107e9f0cf2b68a3fe199244fb8036ed737aea732e83ae43a77d300e2e83b0b625017332e86dd59054b4cb077733f9f37990760d7415dccd64ed3a5c6593429ad3f06b4e07dc69a024f9d3b1877ca6ece2a157c97ea6c73416e4ca0ccb2554b556033e57b9658954fef95c45e52a2a1f15255cedf397af773da4de3c203e9faf9f0de6f75ea89cc7324fb120574947e5580d5079f66b89a600054dceae7268e5507986351c5cce9e619924db968039c3928ae6e6e783a3141d2c1b71071756dce076f0098f1daab89d1492e7fcecb0c1272eb611ccc9d3435bc7044d9630fc6cf50a2f868bad42b979319270bb1e72f3b904bdc8b28bce66177771d92f8f464c4729fd729ba53f0feaec311d733ae93da45d429f6f9a884594ea62fae731711a71d9c5677b1c113e8c3481394de401d1f9f398f8d9a4bbd89298f2e7d396848ffc913e78c46595c3acb2dd5c5bc5f6067ab6c1acf28e6617372eeee226b2ccbb7885dd11c2cf65fee08e0339a4b2c17cdf4934526668b9b8e97cbeaae08e4eb8e32edcfd536fa61cc2528910eef4cfa99c7651f9acaac8f6f59dac82eb8d8b1d6a7027cc23fbf40feeb27f0edab2cd06ccefc5f8bc429b6d8f7e363944899221795af5a6ab377956544e9eefa4102e27814f9e9564a5191045c58291892305ad2105130f87c614121c83942b5aaca0e14d79539a48134903a52d535aa689918186260d500814d2d2f26ede0d0c4c922431c23917d07946490200a59366caa44103a5305360ac45c39c52e6f8b705c7490973604e1ab20c0d5392e8a8b2a96c684c31c5204d138453facb31369c20f4c201eadb01bebb9a08deb201a92a318f7779596b37eafad3e9dd6c4c9c449d217db1e2aed6adebd7cbedbdc6eaefd776a3949bd4875b5fdaba914c7e234bab8a65c5239b47efd2aad1ef8d79bc4e3aa4e6a413866ddd844732f99d47cc128f627e39fa0ff3e83f7a7a28e56dddba2a4ff1a8fef264b3b5d57632574ec257dbcb93e2914cde7824f143927814736b128bfa2f94bbf7b66a74e5a43efa48c5aaaa286ad4d1533cec5fa61eb7cac5babdc8c2b6b2a238454fbeeec8b23a68a7ba77745fd988222c858bc774c5d7c5e731cb97f4bb75b2fc19651ed025aa026d3ca65196a589524a296633cd46e7f7a2ec9f5bb757b67d302c8a62a4e269a517551a2f7acd6936a8d38b445c73f13332e7b94e3d5615a535abb066e31ebbc8f3ceedebbb1b6bd6ab538fbd1e4fb176450b6b36eceb4516575a55d768b400cc15d66c548f1755388a9e6683be8bb8f719996b0c0a599683d22c1b8db25136cab27dd34b47a33ccab02c5b1f4f7651d4c743ef95e5ba5d6c94659745758cb238a2d1c256a4ee1d65189d41c558638d31de3a23735c8e5538c94d5bb87e79ddb12fa5740900ccc81c7d5c9e41b35f71b0f6fd682dd5346e9ec7d7f9bcfef3c13c9fd7bf6a23a5e48f8cc5cf0f792beea8cee986fd7d3e58b6ad2ca5a6090594a26022c1ede25b02c1b5d67632bd59afded4dfd6c91f99fe4597ea5665655a4e549da2a4ea4561afb6fb6ba3badaceb17a5b3b7793e98c611bcdf504b77b9fa061c6369969d5ec45ddfec5638fd785d14cdd9ec2392e2c698c58f4f8c6511f5fb1665fa3c522fb784d44dd3a253acd56c3611f6fa948adcdd4821407155a42503928164decf493a53490932bedc2228b2dbc61d1ac28dc751e476fe20ebed68af250967a355174daea3d95bd97b55db8eb4c9a4c2ff629d5cec78f3cc56b208bb8751ac8c971d2e4a8819c3c7dc25d7c57b7a28f8ae97bbeda46932687f4f91d329da8db5b138545989deaab4fefae53bf166bf35afd84699346dc69359eced3c718e3ab896994feaa0e4b5219c99416b1c8fe7abd75accd539b351fed56b136a938512c49ae8ce4f72907fd5461ac26bf2bc9ef95918c25c9ef1416d18a487e7f526545354135c1a91ca14a3defe8646665f5d63164614ae90d3ddb4d2b9929610b947daa6aeb76e4ea18a5a15055659f8b7a300c779d67cfd6752ec94c4affb0d8c5e4f8f9187750effa6771472f4c2dc65d2824fdd61d54a462751aff9e3409c1742d31d7e38d97efaf5b8fd26be86e9a542b751a5f55b17a3679f3e18a2b3377bc2c72a4ee4d09dd74ef49c78e1d6328d6e8edafa68fd76661ad5ed324ad26f1bcc40ed9023399525ba7517f38f9d9e4473d9c97cf844babea7d9aab38796ed1573eb6ce47ae178b3cd8c2f4591fb1938990661fd97aa00710204d6c41e363e1987a50cf6bb44eb7cfdf8bf1c11dea9eebd6e6c13477e2adb05f5563315f1714b77b36118a08f3bb369943b7a8f50a6b93662b8b0ae53bd5570b5b4c5f8ca8513136ad1a75ad3e9e8628fae9a17cdf8b115fd117636a895aa1d69a8239abe4b1e7815dbac52e1d93eaa9ad43c54e866ef31e493a95aa7471c7e639b25d8436d7571f3f7263588fced2639a5219e92d6d953a452ffdb0ec7b7addde4cc1eda4f79fb8f5894be1eee1a4e089dbb53c31b252ae4fdc78e9f50949c2233b672e3875e01dbe3db883d9f3d132fa8c3e1e91e3e93f3d3275b3889dda3a076db4596472fc5c31d1732f8bb214b5b5aae076b93aa45e4b702bdc41202ab87d9ae34b706b096a096e09e89d4a38b5afbe1fb75b5a8e4e0fedaa7f9bddba1e56bbb0a883318b59b55ab747c632caeea0d763ba87ad1b464f698f1c4f61294020dcd17b1e7147b1ccf4ce76e12edeecd928ad137da5968ccc934473a56999692ecc94e22ed2bf62ef442b5a9ea5e7a8040d96c5512c15cd480000006314002030140c09c542c1683c1aa8a2da0714800c9ba05066481688b31cc871ca1863884104080000800000c80841030028c40171a3d837ef28b549a66997a0f589029380ee2a8978efd0b40f6a3486f6e1f963887d4a08e6e07d95dc47c8b5e3d1f71cab9c8509e1cf945109441116013e2c015947fb70b4e800fee9fd7d501a984782f0800063023142825a0ab46e2a86f88e0357dc8c52559d12476b9a5fc4566bbe93a95f78f16f7f01b49310dec7efa12adda434af0c90b3a285324a5091935a72b430d43e9b5dc08173ab6d0532d4a62e66c5353bf17cc90979517cc9fc4e6e414bc75c4d156dbe46de9adc265c8b8120c857e6218e3f2f6c9528870b36315d24b935c1c0200607518689f9faf900186924a8eb00daeb4ef28ed9267fb30b98908c71da300aab5ed032bb05d325caa66705687032d24508cda377acf3cfedb865392cc7076d89da07243c7123ff54759042b439a3cc1a4c2f21b5fbcf81bb943b0645ee915531ba774138fd5445bb0957f7291600a59366ec0d4dbecdedbc4a7b4a77fb2dda597c26593303b04a331a1cb6dd472c4c40ddecbd9d393ff5c7c453d1ea64c2038ad8616f204bb55ece1aec9a5c761204a92457509b83a72ed62c787bd4580bcd41f1b76c02958b514e8e51faa7c638ccb40d9e0f8fe9e2d1c1b90a91f984a146dc157fe7d210e77cfcb2a3efb0699b9526907d896acad728fa0cfc0ed468277ddb2a949c636aab9823242f3906c8dc69733e590a6f0c6d8a2e47c2dc185cfe7a89c3be987c3ac91984e4db2fc4b8f941a627712503767a2b9ca2237691552dad4883cc30f47927e4efeeb27105d7a39389c45d188404abd11aa6598b6e4a25b7d9c5b32a622dffd85b4ee1081dafd9126f00d84faa3eaba92991627ef20cfde60e4520203dc29fce0d8db126a822752ed81b94e02509e79d44aa11c9472edad6071bbf2535732e22672fd5b980efcd5b391ded205db88d25d5865fe29f4694fa1f2397599b351ca6e96213358d9be13a06e50b4960f6ae4b57306a5ae91300e6254805de05fccd4b4cd42000816896d394a0d96ba72a89c7950e4f7467ca28850ec7c7602d4c4a931a39bef8a176c99beb714b1b0be1cfc6a2de4ac7dffdbb7a7d6860bba5ff030b20f63591ab8039c1ad15278aba063e6985c5a4899d99f63ff075da43b42f8987ec108b52a79e84b05d7e7040776881eabd900becbbf0abc334202f47594ca54bf5120851ec211a543efe642e35abe0a7f48396b91be2cc427ae9d940120177deaf436dbca26d73ad18494360ef780f203986a33125b23079c8489c51d98e080c2609a8e48a03af510d5d22c0ff875535924f3dcd7333c5b083cb89e73141d949f1422b54170ce7e27c29fd8cdfcfdd5005fe4f90b083713a72bf765b5a4ba4f442e583e200af276c6cb4d0c1e6f6c12540b07d8e39179e7829c06102f1d098ae4651db383d6f1a59220539bf63adca0459349a4235ebed4f80aab44205c504bf40e8771e7473539c4b68cd26ab1dee4b4a11f7409edacb7055f66b275aa6a25c4b6f06966368792f0a6790a99a9715b3592281cb16c553b71f84d06fc030ef8d26fde58b61cdddab9fac1a4c66052c31d42e7255e5ad36155c1b4729f9b12447e94fbdc09c4acb42b8e54234677eb3782a723eaf842a633cf5fa83c24aed01e6e70a824de863bd30e6c1de47c7152544e74bd9a1a61640ec6d0d8d9a0231ed26abe05d7d0e2e8db0d00e644132b78313f3d10f0b35328d42b80cbcc1156c7307b9539a450d8290437391c5b8b2c5076e2a0c6723121145987b44e43bfa991ab5c07727a50d5b44f2d4943c3e1185474c390db225bd23373ef8faa458433155bf62b7c5e52c2974960ad3880d6ef192ac3178c7d9e5212f374efc3d519ff4a7ba2d84923647f02f7fe16a06246a939e0a7f69a343b23ccced9bfd53480efb1e25919501239911073161a939482d3f6120481fd8ad69ebf72db5a8ff41975b898dc61b282b59bbb16de0a63acd527aacc27d8170e97fdcb6d8cdb96e30204d783e1a5ce0c9046fd00d87ddb09d126aec186eb69542dbabb474cf89eda69be8a569f7562b5d3e89fd748375e962426c4d37b2dfdc56f47e9df3fea0be6e1cde821efe1e8366c6597cc40fb0328b66b9d178731de8be4852ee2b1abfdb22fffb70ef69161bc43c590fcdbda4f5772dfa4fdc68f7d25cb6051ab695d14d00cdfcac916f342bc266dd3739962817026e0d2bb76b99d33239c7d74e8d37f7d293fb0d116cb5a56b6e891e1bd3cd8ad24b17fba9ae9bd332ac68c72f2e4eef95c6f744003210611fd3426c6cd656d14e4a5d27862b8ce565825d8bac76e12e12bcb6c7d8f5205211c47f3a279f12fa60a9671a838a5be1d55903bcd9dd80c1947723d6ecb5ccbf247677b1f3d39305d9490a64f5ce39edfdd17c86688769023f8a0ce69aba4ceb7aa2333fa4d8704c2e8613fd526eb6064d352db87af6949c2f569fa554c80af9f0351bd60b5cbe80d95d62e8b3e409c93bb10110181d23758999c2caeb25309b1d8d28184255a3af4c51d0e605e7b66d175da79a7cac1216e17e4a8a6792f2a97379bf2eb623cebd86a3066e237a65574d04c59dd2f4de24460449e211433a192d475fe30b7cd0556fe79ea658b8ddbb83b2d1e5b64fbd8a147b08e1cad10457ad86b8f3c74d7bde36a8abc73d7c55c88b6b7643a4bb444e1b934a2fcb140cc12cd54db44af10088258c9ed8d52bcbdf69e8227cff20774b1fd8f3fb1a3f566760aa152220a858c5aa15295e6167e45f26c90c1f5ca247884fe8c9e60e6672ff833216a047e5bb74183e85cb97182889a1abf43c20b7ac232a8ca95731a1838f413592769c72a030580a9e2b3622f98a20de09fabbf62c77f95e714c863ebf510cc165b7343e7ff38096088e8791d1db16c9c35b805b8f2ea6cff562b1fea2f65d0fba506634fb4cf785b7f3e68c071c3be8fb5016c4ff5d395165bce712d30ab12778d644bc8f32c48c7efdf1fc86f2a530a814771f7bf4ed73417fc207d4e973590da1440f7e57303bade7fa35d059a0162810073988742e10f86589eac0782a46c29d75823dacc6b73bd72b0624745c5a48309e61002efb6a1c550b0587e7381251835ff61b1b60578940586b491b2fbaecb12e7cd6213cdb76ea758dca985dea5acaff309cddbe55a7ed9cc62f111dc1df80082219e0588b04a4a1ef1fc5a165a99f4070c7377e46412cf01a4249d1158d8893058af70c9478b2aba0b0510612563bc42088b6dba2672a2bd28a811e0beb839b88a1909ff2bb832e69c03224075a0e98b7453a5e20aabcdb80161c30de67d092891ba24f6fd066d6767772ea21eee16821acc726612442082b7fd6119683e968d5a3fc4cdab328a5c34c93cbaac7e1e8f16e6810cf98bd51eddd9612608a85ce27e8a8acf92d9ca67ab87f9cb1a3ea0ce277c8d75c94f97345f706615195383fc56fbc30b94161b383c9a929fe7689e171f92e68a79bd9c5fab0c0167c4fcc0882e1666e43511ac80c670bca121ab4548e7b8b16d8e98606ec0b532ab6f31efbd41cfdd22f37f1b233c1adafc9e24f7286aa1269d4b01fa10e031070bee83a168227243f7890c3125c7ac3e19707bc532cac99af93d460d67fbbcd13585d35ed876312140012e0bd31e9641a02091c78e6da8e96fbcbb052e1f2c38f73aee9b6f3ed29f1c9ded6a8c08a96257489076b5ec328b4043965a41e70cbd3a8cebfeb9edc0fd7e847d3b383f1d42ce1a9bbd0081b4f1c35416311bb3f0cf31d3e171425fbc49d667c7bba8d5e3d6e91650d0920fd29390079f99aed95078f589baaa13ffdbccf3f02b7cd5ec3e3012840ced83ed939e5bcef4b1ef3788230545325138d14a2e0dccb93fc39d87a8793203f135216b14c27e39d6ecae028b33a4c1181284961b69be4c35c0ccc5616a08494a6bf6cba102a47ede9869d3d055d2cf310e485c5a7a9c9ee2672f4533d2e3eadde4f656180dcf4bf7b323570d192a6998913dce0710bc5e922384139451471827aa0bb4326b532776cddc6ca6ee6a8f0244ce0ccc8dcb0a0a0967f2cd0f5008fadaa8dabb6dbc5d55c25adad86c9589900648726e6fde48a268d558590552839cfa935371962d66a6fae9061fafc04ecdb09ce031a0243d5083a693bfe1cf9328c36e8d6ef1d8e7b5d67446e2dd58c246e69d5ced21cfbed97a54d8db29b94754bbcbcb49edde25103ee398ef5b2be1458354ac3770ca68a5749ef1ac276171ef145b05982c9b2f80c4dc664a13a440be63f05c0ade156139eee363491fb562c5a31a88c79e2ee46a7a60d2b6d5c88459d446fb60d6408ee9250b88df2e310313d43f6a4721dd2a2bf463facaf30df3215b3069f1a2b5cbc45d262f5f876b866bf89cd60cae1b8712aef5a333411d3469655229612b63624229f17a04eac526dd480f5ac66bc34e90f47d46e5c6b87ab8b50aad1ad8994919d60f66a8299137d868ef3698907cc60fa07e40badaece069df8ec94cf38d557a25939e829db4d7833ae4cd57b6f030553ecaa359f156f630eecbe4c1f5ddef88ca9029c65c0c9464cd53b7658dd2abd8b77e6836ff68388a1342a7d65fb584b4e3ed5510ab5dfa61f5326acad7eb9e22c5185a0fd42726313422f372114fabf60b48edaa7c685b4d911f766f316b88991935297fdb8fc4803c646e8c728a0e56a154bdde44a8eff25eb2df518b7acc457f4d6c6bdba55aa669554f8b0a508c02beb2a1b0ab475d7ebd2f5433b0b33a28a91b7e146dd02ec03e3f7e93e0ac356055d8f8579090687d83ff72dc6762c991cdd024f96b524d677d1eacd7de8db5c1e3e8d7392b02bd40d83b66d8dd1041d06c054562797a6f4a285db1d649c8e50a994bc1ce50f22c9e481db06a0494c0c0fea1213ce1f1b90936189d39353dd43d2d67a9b0ea873c4f29484778d451a9af27b3ca52ffe8d9ef65cc9bdf091a4932440c1692f01768dab0f9fb016fe3836010c5cab68beef98b1f5dd139f4b0bedcdf235dd50cffbea34ffcf52d4918e71175f9705f797e1eb828fad6b7237882d9679229939af42364a796ff68ce2812ece13962db9997ec38e9a66366da20f05a75465959efd2655b20212d23e60e65b76b9cbcbd1541fc592eeb93e31aa326ef64b494a56967b95869d690e57b8ebe2f7b8a42a6c7585eb5ec540cd096711f20a10933d99427998ca62e1c10027fd3d2387fc03d347853ff851a8b9c241d240aff480c18eab00dcc64565860cda7ccc4241b2e4ce11a390f46232e353908997a8973b2d09dbf3ab27d2109459b1512a514aa784bb4669fd28cc151e23e84686985dc4b77fc111bada8aace8282277d859266af9c7c060b1a64dd13cd9427de090deecab02249bffb602aed9085635a469fac277a6d874dbd401e6fd9f7b2479930f50951aa35fa21ff50d7585e670be276b0e1d8f8e663327e7eff6d32c811c183e85ba79211b5a4480e258264723a05616d8bdc8d02a97088f75572416b5831025b5ad18e289fd495a1125eb4b9c18930215d6b8fe315335603d07740174a6e9b1b5a4b2b314ee460b1c0291487ede979ed7195bfdfeed21d961c19297c658f76be49d8675f8a6c47c92d7166b08b35fe5624162062767ca8992fd024e08800380f64a61a56d754996a8bf37e735c35f726831ec5af411cd307c11ee5ebcfde4a6879a420965a97741ea493bdb0a15b10d786ad31e94bbfaa2ee6af4f2a7fdfdac12b406b14b83ea8f275d29174a7540680643c0cd8d59d9e629558d0a9823502d378ca8b553c4138f4d400f6fef2dd9e10642400cff7c4644fc1097344c9efe1f27def3a7280df1f36d6f1e7a88fa6b36564afedf98c6ec3986ad064a515714b65ab7f0deb041bd058f6445cf2f2795c3f57ca722a4a833c975d9c459369b5022974e09e8d183f0560a2a6b68e5e15fc80d03e345ef8576fed1f84fb38f43cad9c04ae187ab89c9efac1ab5b83640fe3b9cf15a54aef4a046c4f5842e6dcd4d1fce6665bd07a72231d14f0e04997e5b2a26ebb3e02f7a16f1cb69972c22792ec405b092207bd3cf93ba84c5f20114d51f5b3a37c2ff7a059e35e96f0d81be284bb1baf58c5cf416494e480b9dcdb08acc62a82e3fb187a7df77499dc384beb2412724ed0a5d960bf41e1bfb68103d0cd859cc2ad015b16627593c9d0eecf0a268dc11061b582ada568b421da6d59995bfbbe3eda9cce7eca466e04d0ddcda3fc82557d0de2057aed75f2f04e9c0ad1da43bce4136e9c570a4e2f3f9104ca1e06c00b10ae0d2753374abb36ed9ff1bcb6dec9051dadd3a550c72379511e0a83c21a4c39b7e78dfdac9b135ed2c4ce5ff889076a155fcc7d9692e7bc753c7516f7d5fc6051e6383797b1fc9edb16979a6241db04dc049421aaf3f6f880795d81a7618f57b6736c51a279ac0b0236a7c38820cb62cbb34ef9c3929f94cdbf507c8f4093d55d41e5d9452540513296801c3d495e325d6355046946bcfa8205ca75c66b820b07cd0f3ede228e82aa3fbe39cf4b51f497ebe5e66536c9374467c56a4dd9ec89c1bee2a0c6bc3b7e2d3cc5a7ef3abb1bc683a3129657ea3e16ab961ff898419e6a54d485ae98801c8376348368c392efc6b949539c9950b8544a5e3047b5e04e80c6b4077d6a2d348eb211a5ecc80cab2c1751affac2dc786a6320f3ee0e04e28b6cdd59419df755981e6e826d6040593d1305718bab37e58e9f9bb69486226bb3ea74fe1e8c199f61c375475ae1bb6fdae92e6bb27406628e987450096ec9c8de47319346503210181bddd2014209ce96249ff7a7c6db1421f19a011d6ca7303b5d4b4ce8c7ba9827acdf8d452e0dbb43f4c291115d5c5146d68c9f3ccb569e932205960a9e6c9f81e305f28ad3c3c443da11205b7ac8a251c4287991fd8fa6fcd442aea55f293e57c081afe2268237587e6e5acd304be2da52918cbff6985f0bce42b0ae55c5a6bb84b4b2cb5d2f1fc57219a3de4f9658f0c0de3235aecf138c2575cc23f3ef8b2d188428fae390ecdc90b00831177160b15ca4a9fb2cab031aa11e73bba852d9c570c41732ea976e4fa6bf6a05e000719d976a29308f9a6650a98f235d09880eaa8df3b6a859b130e3219705e3f173d6502f56c1405a09cabea01d677674bd42fae28ee5d279f9caae0ca2766e2ac829e2b991806d425086ced2d3ba793652913e4dabe607abf919524fbe33b56f30060fb97629d2175661e5777feba0c357ee667e62c875b4db4f9152d0f377fcaf44e35d2e4923ad1ed67824d1165f47f439fed4e6d24992d5b890fccd34df6a2348cb8bc66e978507b86e96fa2eb4bcb01b12ce1ecbf9b317d10ab59e23bbec78b8d4e677d5084ab39013fcfbec689450fc68be2ed3492c2940e8aaea08713a84e72c6a282ed30a3c2dc6f0d286927bd2156fa1270c0f6288613f9d493bf33c9b3e65449b426553ce94933b54b7d858a8ae3c6d21e6495d869cc85860bce7324b5b93d66df49d128d4dcb074078d9b5a7095de68ef242e7fe74a5e4a94eef3946f95838e420d7ecf4f70914ca13fc5cb9971bde896a350abb51463acc2288dab96ed0c406e671c29a50141fad00eb2aa16f788f286b53fc0a4d09ad0d8b73c24a85058be504b897f1c7b5cc0a2880ed1f5b337eedc2bca019af8b81b81aa2647d3357bd143518c4c079f8a5902dc2b2688ba26ecba4eb2d05131949b60949e3aea684ceb4d0cfe3d04dc5e9cd4db219d4fc5c4e76c943e5cc347e47e727c000408e66108cb042fdf04d2c45e4c3c2758c5cc215a174c1db74e8d42adaa110d0425214c4a929608c024acc7af51da118b85731a794e709ec0ea43f0060aa084a818386097e6c655f0de2c976f2c597e7520fc4c10a6abfce311104613b0204227a3e1fa360c44c90386c90723b0cf41d908de4e2d22446fc4da3a94d730dc2f30f453fa2e319131f1d420e6891ea313f276528508550cc8dc2dadd37783e98378300ac615c2002820da81feca4c1e6033bd5f2aeb9c623e98077bf44753a96f564736b38233a48526074559519e8aa1e015629fe40549e6ee55fb67ebe796afca62c2cdda27457740595460f4c30042cc048420f9d98c815801a5991eed86a5240754a4403e0921d07e3902771c5aa7aad236732c2224d86b307e19a85b4103c255d68c603f96d03e4ee1a0c8b753c306031bda60a8874963473a6951c2cc821e23c6c12203714c1eba43ed881d3972a7ac9846327a7fbe8c01009a7db7dc9fcd3c6ca30ca63a93600186ba1924313b763cd10b190cea0f755340beb30b605c306b9f96ef559be0c8b49cdb92e05558304dc03a4bcfffb32c21a0cb7ecf0eed777a9dc5e4a282793f872f85474942c47d56eada1909764f34a0b95d3ed17c9da6766d591ebc7af1b4173bd1132754494bc9ad7625b13862d7fd70903f3b3d50a4d7ff57104bd0ec419fa07dac01f9f81ef02329464edd1ad8359d6a374e6eb7f1f23b5130a9fd36aacccada635598794c3bf4672c1218035e3092d9191776bb62d4a0374d461d6a6e329ca5c3ca025d8e10b8e1184fd9c373cf2f33d5ce0083006450528a880627f59335cf848a211821ff0cc316aa5ed4f6275e50c5f2800487be580ac2d1806a3155ea93477a051293aaf426cfa63ecdad8b6f186532ebf852da15ad5be7347ecc845585e7e66ab2efc008fb491207fa09338fb6adff9f305ab5334d4fc5b3d7420a12942ed37d1134f145e5025ef985c24e00ce7a563b627f487b46f59f80cb2f4a7049906731c6b6ab56a954e1eb3edbae2aff6065407ea62b6d5bad846121f3a317e271a7192959f9f104fbddbd0984c3964e0c559f32efa154f16d08bd74e16e8260f31dc7083995b9a8dcdd828c810ead415fa0d11c39f9fcb94023536c0dc71a45ea1a8811f8a23854ef7cc4308738175f2260167289e75debe60b9a4f319d9195e3d30b3c7f6824c8557a974fe183ee52e804d09991da797a54730826210530022e78fabf563926d9de4791ffca46b1aa308848e2dbad91d210343efa725ee35b51dfafc646868f1d1e5e353a53dc25242becbe4239a1a893567925d8b7a3661066f88bac9a3f819de964cd34b9f0c6f918c78d5cd06e8a10031ced0944ceceef27249d122f2c126af2c36423b2174dcc0c8e26126598e22f1af1a383b703d231444cffc8d87dbda8d2af52520d1a0100a4e7bde4163241d65bb13ea285d0413cc48283a9594701f861e7359e1d029502ba7fbadcd7ab7b81ebb3f0b18328cc03a2fb76f86f5c4e6450d4707da04fc1c75096ff99b2048d6099d550816957daf6e83470dc6ed12208c90d3c1e271b9e07081c8a140bc0f346daeb2b63b9570d235a101c73c7bd5a3620513401af7624752f119b75aad29656d9485dceb171ec6bb23f2f1703f9258e0871605f002a495ed1a82640f13161c2e4eb61a519798fbd230c41e56f885ee258f8e428697bfdcf4ba43f743934f8804ed0e4e2dca7a0d207e5e524360bda300eae6bf11ad8726f4f70ee615d9a8cd24a124bb33f8acd961eba6541b5bdd0fd5c759416513c6234261599cd09d6600fab0cf432814f5d2ba0a9f4dbfd4dcfd40f1c6695ba04065b2f9d1f728e0642afac0ba62b3851a3624bf74ae93946ed7ca3f2357d4eb678058e33f1fdaddbb233ea7b3efc2befb2952311f977df14057c0425ef86487b8f200e3d4d9d19af9e718723c1da5c26d814493c3b1ec83af872df8cfeddbfc7107c8978e7be19a70c63979bb78948cb87a29479dc49fdfe4b41e83eca6a67cc09341346b9c4d05b8b2b6cdf2cc9453bd01819bb864d131a1c878d09cf17c166fb66a2fe332f8c6b0c1e356e3ba87f7f80172f87c7a37860f439b802dc8c897bbf3c9dfced9b59547aaee3ed9bd1c501e7d9f6cd1cf7103cb726642ea7fcaac6ec9bf51fbfcd3c6a643327eb3326837db37ab0a389eb557cebe1d03f766a6f814844ae6ee66926932ae1eb9b15f3f72a939db2127775c5dbd4c64832b65605ebb3e41007d6babed9092e024c95b8b0c340e55162f889bd157389710d62b0d3aefd0cb9046026d7f3f8ba805b3863d0d4615c1de34f0ac8f94578dc28ef99b7f63138c15aee2cbf0889d0be1989ace170abef877f495c09faa628bc4ecf56450032759cd43924362b93ad15c2e2f6881a09bd1e58e152d672b0eba9d9e2b060ca518268bab092ab2dea90f898a88d3d7ea0e2d443bf21a30efee43170ec39aa2c75b83df7b778d8a4d455d8489ff5bfc48c22276f3169fd7c1e0988558575c05031cb0332bb1a13336da4b75012879b578a783e680a78d7fd9201acb7901349882dcb58e9b0ad7a0b9588c221f5964ca6adb05e9a320852dc65a6c1f496b8f5260d2d614ef76b6a97d5e82d76ad99e5718fded2abe38c6bc816144dcff01aee2dd03b0ce573e82d6b808ef511acc66532878769b4d47c4bedbdb18cfca8eabd89fa6f7981a3988f66892bf108f968026f6757e92902d01b9531fe95257b90467a1b68947491afd0e90ee80d39bd207bc4fe40fca3a0a6408a07dc86b6e4bf0e28a1d5e4635c383fc30d7b048827602838c0f2b09ecd3ae546b3278b686592f2f621c38166fed9e473fcf2ef1b254b6a2b8086374cec47d011d5220e3b730a9531063bec960e114e2f28588395e49089e059095737e158dbe4f1c33fba661373005762d3c01d23df42a31b336bc4a3838bc2df32fe1ec265262805d95ee6591ad67065c06a834a6e4e13fa35ff248f21a6c8328a7f2a6625376df2e71f93183ca43bd244094ef1d4a2de08e0edb2c88a9684b4186f31584b84df44363af047f1176518cc821ffae30cc95db92e123deb7800b841088981aa7d1e5cfc237d170287330e465a82a2d3096251504b05d0562a13e9d4f804066d6a1441c2fdccfe28db8c40b3fabb924efedad8aa22b815e984201c81344ed3c8e676b4b1eb97a4dd92925a870c6f994527ff34484253b0b083b39912ae64d3be69f866ab57dc7557ae5b88ccd3eeb0af57270bd7d4a0d73defc2dcce3fc4eab3b07f2ac320fe21c3f88e31e9cd19097bf186ebf0e03049a80bd98f9b0e60dfe0463b1f41ff76f288ae39b125fdd69c0fb2d29ad837829579cc8ea656df06a0f847c19f072a94dcac2c02193fae4dff099d206d7070cdc212d73d33e85b620a80aefa66a9d74a815bc9ea66c566f672c42d7edd81fee620d364abe1df4a413989df9c23261eb1b00a62fda088fb430c0f4834e65b4a1e332db9efb80989d7a0be28c7f031dda2ddb7cbb9de99391c111d71d1d0bd4996f618178d3622174dfd9d7f726684f23c1035c254c3a77b9ca71838817a25ada0eadf9181e6740e95d635516815d8b655a90cff0eb3f26312ee5657ba7fbd834078c14f778da6bca1e60bef090bd13d89953602a629e52047063ef825c77814a9efd42558ac13d5ec52e756faec4a7d3cb9b65c19e7ba4f2bfdb1765c79a0ccb12b073f834c5e57f6eda79a9bcd4d00d31d7cce428c875bcb9bddccd0d4de265aebf89296256e0941d1e1e5c1eaae1f39bc4d169c87de4299a20d3f1db0e8e175e22b1a30447005a3d1c38cc48f28c08025942338c7c60d8b5bb01c29883840bda5a76f594d600e204a89125dd7d11afeeb1852b8509385fd26bd46290eff449fee90d4ffcdc7b1cc0b87185e5138ff56602242131f7909781aee1e2d2b63431fd0e40f5b3d1bb3928fba423a1c308dddd7ab8d0d54068fb477cf998106d887453441a7dae05a74a549b2129e049bde50d995812853af5bdc20729e9f87fc756e608ef486ce0a7315a26a788421f47011f0c679a3a1bf017a3a41d7d3bd80c524c98446589e243754954fb4578eafd5bcd307d1a81ac38a5c201b2df60ad189d81dc0a09bd71da04d7827d2ca2082778008d81272d1a3af7caf0bc067858fe0dce865676a57804535b6c596d124f523fcf055bd0d810c30c00d0ecedfd2089da90cbbb1bb467cb56db781b44e43c48753f50aee830cef0cd22c6ac461b023e57f2a05c31a6826e005e58c53f2c94a4db7e1db58adfe8667a8e8cee1c03ed8f8141c839af5de03f99724550273b543465d426291f18aedfce241c6ce9064ea901920e0e0d775f193a8ed267ca0b132719540ea070821742ef4d9d379fc0668529effbf8c7198a4057affa5b4bff7bc484c08676aab6491099157ade1cf4b4488a97780b85483f5284874225a80c0b3feef971f6c149fbd193bb10911ded89f5031b16b8e7bd52537584797e82f088c122181cbc49089f8b7753493a1f0d2b641f91a7fe4759409da78e16ae717c0afbca19a139aef4f3962f5d402b668d05f0b87b92938160ac3947133f8265a8f514da2ffc430e8d3c24e6d415d2ac7eaa7903cf4f716f67e965d71b3bba5e2691db01966375743b1a600578d11a8e674d164ab3112dd9d60329bd58d26cbb4e48de8ab21d924f2fe804a28b845565ac99fbb164d8e4656e0327fe3406a3db2ee0edd993fa9006cac9dc18d588367a07fd905140f44a2932831fe92657e2a26d2516c3c1413d30bdae62c7cf5d307397489a7f26f942b54cc618131e701a0074289f7f945ce0236479bd96ee0a5dc6a7dd1d0e6ab8d2239561e4000ac7da06ad7cabbaddd8d823773b842228a328079820a6a7588c1683b14394b4a40fae0221e122a5fd445adfadaacc5713eb55e57a8687e7e9dbc4282b74d186ae8365106f2133d42eb0117660e98b871aca6c8f3b8183e4a4db076bdb4b16a81da078946d32595f9f96f973d2d5bd01844b0fd1a8a0d8a4c3a22d34a130999aa5965b842e1ba93785d0cf0b1673fcccf92b429e46595a70627d53bf914ebea5a198191fdd73c1fe7a85e23ad36a2b077d63e06768824f24758321754b1b8aa284a9fb044552ace5d83d2c0c56275448d05d84239d8157389e2430299ecdc5d93222a096e7e31764accef78098a8e1455ee4907608ee6f0f703d3ffc63be32c89f7f848ac69d27441c21da635b15e83a9666742ff3ff9916a2cddc71bedf0a6e746e507fe85a5da41b17d2c63a473572e533b9f950f8e6cb92ba2c47cc98eba18ae665bf2311e352c5d86c5fe41ddcf7fbdb375c2ee21564de5a647ec0b5c007cee141f23c7c41a0cdd133de0a88334049e796094a6ef109558b3cb2aa2d6b60bf6e26072e62290d4d433354301b047e03ec172001283adf0273870853763c2212e8d452416e7d4ffcbbd5533d018b96aed692922961a7ff8e56e43456055482ad094db1576ee3054f3536cfa537051c49a743e7b9da992360c803bdcb81b1224c04ea65348ffc1834393a3d6b14bf55c560b1c5c729ac1c37d80748326de9d1a02f2e49421f83f6d0564e577151415a68861322e0d78bf5465560774d273d616cd18f3995cd4bda883ff2b2349721af5ed0c1649441899b7277fcca6fba5ebef1c700b64f76312e4c6e04c0fcc6a78994eb33de2d55430653cf377785066c9027eb298871af5b84cf7afe67ec1919da54e2193fcee59cc22b85cfc512647aa3fb6e15ff00087b15069d83abdbf3a00476f22687012df35621d0c56d0106dd01d60bb4b476c46e7d7081ccc6f4353cb5ef41c8a2bab5102b0312ba6cfdcb282276d5c2bcd6cca463b8449797b6329b735025e8fe756c7568aa7e9889cdd66a6f108ac5cf2d52ca68e75063e123b5b5c01ab9e243e38b6676d87219631ed0aa5b2b01e068594ff0f0d4eca253717b1fd470e137930575e13ab938058cf622bce344c4ffabb502834e5f59a4e979ba7656c55102d431ddc348eaf002e396a99b84d435c6cdebeacfcc9a455b7000fdb8299da655e91cd09b40a5e0f31303e85c8c79a24d62b185a100486150e2400592e99b574adb5d2e72ced6161cb9d26e969c97c90c288031d463972d1f2f034bc95a1d0a196dbc8504d99ee62938d565873954b9773953b1be57da98bc9684afd25372591fb9a655b74243d4da5465bb325f19e14302d4898aa635fb65a85bc99d5aef41ad7dd0df1733704d4b8d8e5bedb19132899dfda1fff2b7f966be4067524ae81f31e6565ed16f9089911872b4008f80db43f66c417b1eeefc116935b89ce6f32494397f4c13a3a1c5208457c470e9bf53297779d00fe773fbe5561d061849a9b6604dc4dc335d3c163204652219d2b1ce842013dcad1292a9a4c5c07d426ce1613480d7c9533f5987915496904baa63d68c1496ba831f53985ebfe4d9d56a2656ea0bdbc8004141badb2e084a2d847d98e788aff9413fcd0591122db5ef7f3c82f162b615a6f6c6d693fa4af9352a2181e0cce24628f47c24da5ad9afdac7f537b3c66c97d5cecbfaec76314e4bf743c83f8c85956d6d6090b86a14b6fc93c6c634eec2ce60689a4bc93793852176ce9186dd32d4f177dc72b3e38cb6a05874c7d46453b568023aaf94b89f083e10787a3d480e5b825539861dcf87220ff7b52071fdb5479526330e06448fe61302ecf936d9815d52f34d065b8378e9d03636e49709138cde97358e0fa96371326f9d4132c9d1df09ffbc88e468aa3d129f9578cc8219be98380d7d705110d558b94a5fd92e080d566c5a07da301e15c377241207cb9f827039e7480c9d23fa6805d15fda277b521f63b43dac9a207f9342bdd80d2efaefcad44437e7a9346f5f724a73de0996f39d6c461a4ecee96512a06f51db830ca96a57d5aa0638d1af846dcb0b8d66f7ec67366b89ea57a2c04bbaf85a4994b9c62d778a458a80a420f316dcacbf871dd16c3c0fcb497b15b500c915e86cc8d212d2ac94015c53ae37902369c58490eccbb82c97889e5637da10f06cb7e555a352d244dd3dbc1ddb03699b41308a2ca0ead44efa9ed4c344baa12dfa9826b04070aae2b1129490ef9f521bf3b06294ce923c2fda75f5c1f3056c0b0ba6611e058c088622c2327876e5dc8520a4339bb7bd14bdcc94d4ed0b3240653a04390ff6c3e66b4b023de733554ca3b3f65705f85878b568c3b908e4fa5368e3255a61730442a71eac3319f8e5e23ba3c1b2728d474d9c0c18326e972dba3a3ff83c6faa64c7cef0488c28ca32298469e56edd4430e92ccdb60d2ecd26d6d3462bf465d8b34bc23bb2857ed48d6230304425152685cecb7e1d56acf2c63d4291a960bfd4991def29ee38d834b45cd2f841fdd8c34192a7c2a2faf29de2793de1c4ebbe446a5bc2b8a1e41a98027283a91ca37f11493982dd91f25e795243440b2f59fe4b05cdd6488d31faff411df5b52cd1fc213c8881889b48fbb76104e852653beff7d863e44913aa2140632d56eba766483f16c9dedfeffc9bf0c66346afa771f21a09a7b472c9b4968e9194f4f29fc8109c61a919ef4e6834978652c601fdc8c7847d2f6a1fba7e154ad47f8644260efc29d760e214ac03ae9d2d669ed53fd11d16e4532e2055542a39955569e70ce513593a5cec0b3ba48311c99a32067f6ab6c30176fc415aece8b567200636c03bfcb5c0e01ad3dcbb744df8e5336e340c029de7af4bf62a29f45304595341c0bf668033a15db02b6570f3e64decdfbec058a9f22795d2c37aa1693c13b719c713a3c5b815e2615422df9c5fa09d47bb73123e834e5b49966e9b3912e64fdb84de585d7375de484a8142e545c52568c24ed92d8e59743de142831e9fae5bb172d9be8bd007ea38a1b289f4aa7e6627c8283d25f55b0225a9e839830ae25172dfbb5954c8083069418cefd01f01087d2ea2162368ed32dc7d852c2636e7f9a1431d60bb52a4e6739f8b9f005325cd38fe537dc818681b57cf425cb9256ac7855d0336986486fbc63dd72ef1fbd78be9b6bccca310b7c10ebfc49c588c9db9a866e590ffdf6932a94db05216f7b262e65a5d13154859057cdd0adb6c6e81ea517e743c7d68afdc2017db51ec9680366a19b3d9dd9c0d519ac072ad677daf287fb5d4b4e2e64836e2f55367b87f855e53fb015cbae2be66b63b1ff7e1f56de2eb037edeaddf977e35c2a44db012da444b27a032edcd33e7ad22bc533ede42ca403a6539a2108b4d40cb493031f74fc11829453c70fee29428c9d989e31c02a6a7315f45edc8aa213d28995a2fcb7d3620e4a7d36b281f74a9b34a3812483f3069625dcf2ea58e44cc72044352db0a75be11c3c3328c05981e48cec8757ab1e057d12384c41239e2c4105ba5321da10c9bea3353a4b9cdfae880068e9f75cb5f508bcad7b61a9925dcc1bdb2d889e801a9dcad0997991aeddfcd61188e513965578ab4dd9b8080e95a1be077890fad20ed613ca61c630997ec2792cdf5a05c758e6f1e5a0d078f9fea5478f21ff57e0dfcc2d3f66ccb6a894295973d4bb9f30fadcf1831c1c86d100e70fa640d2d8448cbad28c09cffa5372aefcd993cb8387b19b2fa5228e50c3e233ea5f9d6764711b40e0dda7765646457144c68c11e0bacbe086f94fa090b5ec147cf047768f43d2cd8f73f066955391c019ea8dc0b2628174d9d655fbbe203caee0511f511cf359036d1dc8f309d092c302d1d70bcab614f551ff394d5efed8868631ff9598f5e19eb91026114d059b32246217566b5ff61b4604ce069741b80e2fb9d1bc2604c68d93c3883b09679619c0cd41e92a3c2b24348ec37de98c2e60ff026fcca61194eae15f20a6751f620670d9d35fd250bac6984a0c99b34de54129d73b60750504815a99c2e7e5ccd2cdd3a3ac1480100948df50167fbe02fefe1067cb96c72924f186af690caca3ba9c8bbf0be193e8b389cd8067ee491d507270ead6e67c810fa42a6d03816d92e6d4108c30a05d36c0ce317613167644ff313574e5713a5f370faf7230923b41749b83d581f34db327668f50ab37374603b98a32199511e80f2d60f03e130202f5e6d0740abac75b19067aa8974796f13c0b7a1f194b22ec115b6ae15c0771815fbe263a713e8ab0b3125f0f183c160578acd65296a1d825fd64a5b51a07429ac17a9c4badc2b5785073eb41f6edc69021bc27751d11f5908a2ac47a6b93d1ef5948e1d2adefc2b7870173824c98388926ba3f3061971c20012268d29f35fb7afc092f39a799b1ef2263ee4c32551f3c07368f008599fcdbc3f9d3ecd67f51249263cdd895aeb9541b8a2d8b2fcb60a780fbb6376202a1b4074f3e74014eb007fce2c4544fadeb091709c6d788b09802857fe1e2de1a854c3d482351910bca41c423fb6232b54ed7ad1afbc1491b7ae93fe63c319099ee0b308e6a9b88417a69ae1e60cc3b0db376a19da6e267c22c5b4d03db5b3250a86cf6d91d784a9f424b16fd55070143fd80f1f08f49c00a003a37afee5df79f7485fee600e113ef68e31b09a03513ccd641e40357efef6891d46f108b7fddbb03ad0cc3e8804be11ceaff69ec9088012828f6cf36a20faf49cbc5306a90a60f711d9336d17d879f65a6a7f254bf8130ccf9ec3e388c56137555291e7acc77584301730c6b1d442adffd9e4c0d61def3df9787b24bc5b062507ea5e036ce1d86b46e78e8953168baee5990048da08c777f0b1dfd650c2e0a5b8cd923b63147af95393cb67bafb70768943d7aabd42877c238bbcfcda0c85842a053c6b1fb3f6b435e02bac08514eb9372a3e704a5bc0c22b5f672101894aebb7e75efcf20a0966e3856bbd47d148a7c13921082ade6e5219a2fc434245344d202cfe2dee127ca575d1f5c1ad97fa1b4447166c3c56549b12489e423d52494fbfdac246542d99e7ae3ce3936e4016a7f3ffeb7253cff9d86925a1fbc2545f45bbf0ec9480d3dc1036e6316a83598b905587202e618d1a9376a9324d8d4a29ae6889054bdd1fe3c2b6e309a241e15c1b0e30c28df86d311e4eba6b0799d1b0b495d97826b384263d6b86dfe6b96f0ec077aca1290b039cc6847a1d4696b16bbbf889e7d86e064cc611ec9a0de312f6db11bef055c29f62e7f26cf301ef2a6f3cc8f127a0fb8f43ecadb080e7015591309c4e2695969cfc8ff5d92fcc2d8e0c0b8aefcf83897dbd5fd5bc011ce72116926f99c00e71286870a6c3ac502cbffb2d26c194d8a4dc97d5de223b1a33f47d7196461e955e37dd521491110108843771a82c31129558847b03c655610957748590936898bfc3bc1f122db114d3c8594573e51ac8a119c258cdbd1fedd889f6f3a2a0718b43c5fcf9238a00a27a0767f92c5e0c42b6c1b87726f091e504479891bb46563b4c3a70bc57dfb41218c0995b5526558e66b8ed685d2d37242a46a219fd3863387321be89e4368ae5b4f36d2607528a2d135de2ac0807868113a4e736f668f422b9fe4c5ed229d36126c273d5067c064287fd78d1325127abecc4d4716897a395e3b232290f853b0f6446f1cf2d252008b1fafa2ca9fbe240bc862f9be60fe1d5413ad909c6770c7c6af70c9a1dbcfc9f11d116edc970ed9026d05a9948acfe28a5a33920c9de6be9f225ac2f27108d1990f0c6d7f4e804c7ec22de563ca8ef6c68e4427c9cff48129e8f4b7de9c08fa5aeda547cef71ae045b6cace887efb42062b9532629ed4cca36d2e8d6c4bf493b45d4420e8bb4907d745b34ea4357f8b372e360c7181b31c466f8a5b9c71f40505e3a29a1b9abdf6395c72776d0988c3654bd4d85835a8e3c2558a45d1b97e2bb2e6918473d505c8ba3fd1d3063360a263e5a4a8f90fbfe8d9be2c10874722bdda22254f44c1c1facf22b14f603659b2038a31981a9488e332eaf5e29aec8d4217d8d975ad9e5f6351872a1da390a64249fac2a3f096efdb807efd721a542dff22f7e84425b6b3ba8a1d15b5bac78456878a63dbc1e680aaba6ca213045df44f6a42a3a840c31bae6bce6ec6f627f7c223096e7ed5127a6c8ae60023a5fa5af01cf006d4a582a289f536e8b177211d67aa632a3658d45cd20897bba65b8393dc8d3c02db2ab9038b1e05ce2ba40231b6be7dc8e20472c7ed5600fbb3f2d84bc53ae31de840f2252cf06c42dd895c2ac8e666d1fcf263ead839252306734f46ead3cf2611b464f56f5cdaed35bc4c9f3512c35aba5cc3a0339aa0bc3f815a3915ff619ac1928715bdeb5e0501721d1dbd2a98ab8e7651ebed5d06003bdb55993dced404c2ab5bc4ce2095b3bf57fa66535f4419596a530168c8d478bb0c92828c2360793a4b495db6f49bdcfdf80529691f2e9994c731625a7d8bab9e52ba41832654c7734ba51f3855e83d537a6f35f5218ea2c6a979c44752a7b144b2f6b88ba3701cca4701cd74e3355118b0562e75ae52da71b9bd167178dc1e5c5b82baf10c4c3c412d5fe5476a1d3c89d857a5569c068e746e633105bac311689d775b7b510962f956b07410039a669b1858776a18eb82dc01415c8b7c55c22b964784e70298c9f60956d8019e05819f48314759046b0573970bb44d5d9ce9298fae47173220585070d166fc2953b5adf5a262e7385da562844df0e1e26200585795792eef7dc4925c05f66e745715235329571090b4e914c82e64bc01d12135a6f1fc186c078004b43f77116474e81a08cc4cc58be2ed8a01d2495ae153d4d352a567899fa43bf2401cf41c12537d0dbd7177785ae7bb0b6b9313c8e01548e6c5c82e5fc483c5e5b1d41e6d5f2cd96b636589b0fcaeb2c840b66ffea8e7792126f36236c2791650948311d06b59eb80436bf6476e7f21af85988a681310fe601e32183e689c9f16c1a4ae3cf21c0f102871509c97090f306f869c39be532d4fcd29aaef27809b9fa2c863a82c66b60fc4ee60d708a32bc0bc0849693c358fc831b862674c8cf34a95cc930e1cd67d69c05924de15ab4a845c88512a4b1d2d17be34037ad8963b926ddde70f530cbd79781a713798f3debbbe1bd78047885ce6a501ca766256f565c6a990b9b4470900ec310d8a9974a1b9c396669feea116ee4fa851ba8c7ee7a038a3bf0753e360be15e3f0c68645b168472d50cb67450a99e1b40cb739d89a9ccbcfcb3a9a7f83e1ee9b0bc8a1e9b89d91d009aaad652cf1fc41137e64519a0b20e45534eb50e4aa55a4bce7c331dbc4ac878988732abafc4f1f069520c3b8a609f6ccb1bfb02ff2e9d825af9ac56e65e18804751b58513959c40d233e6603cefce33ef52175bdc63db7ed39b83a987a832e343de4046564a04bff69673839d5c17037868a6e694059eba80fa67b8e88892d3b341f67e0c7173501312973370978e82f0806308e94de39d12cc2fad363316ca883c1111613bd66164d0ab69ec5d4044a035ccaf1efe93e3c7af6257a4094f832439a2f8035eb4e221020a48d5d59892ee4784d4f291598183204630a6a5f991911687b272c788591b98ac8d79c550bebef5741cc6e3fd5d653c725bc1b5d2c1df3f45e274bc32e1a39858ce4dd1f8d3fac6d665f1689c805fd7f0aff227d49f03ed668d06e5bca877bd22733f692e2b916c037fbf777dda2609f928b0d00acd706a1cfa0190aa55725f222ff658c709984090f32301bf0a12d61fd3cc20af8bfd061660888b70684c8d5a9444b52c17e5e7814a820b6b6531c084c9066fa4ada1cb5682dabd3fcbe183c6187db2f125ce1280569b5b3bc14acd9b3e81f360ea9432f59483e5e2cbbbb33ae8970bc0752c582dcd331efbd20a8a88166b41a4c9c88c44bb9b7bfcc1306878e9cf068b9d3d6aa8e2a2abaae0103930bb8e15c6613462351e57095d6e7e3a5135f8ce73273910c67c2d628e613b4e61b02149f2636099c055696772c0113a7ed7b110793ee6207b81672f24a8bfb5f61d9cceb05663255d223adc52cf486398f87a0a73082bc573c4d6747db91e201bb035bf3f68202284446ee024ceb923d78c001179806d4762fcf78414aaa625c7cb93479e6f8c8ce9498fe234a3d6750a060923010cd5e1ffe1cf6c49d422d855fff5513d5af35617261def9ebcc80e1aa14bafd8ecaa3739fa1cf93dbbdb1552a941a4482d635218a17d52e4518789c06feed9de58938ebd453d8c4f576464dedd7f07b31dd84b708769a8636fa1da2219604239a8353e608854ccdc3d85b93343595695aec1b53b18f2e589b21a57b640450a895c35ccb6e09736a4b57052723d66bc4e796db03ab728fca24a27c54f77d05f29a60c6f2a97379b8e3aa6a4809cf3d332712a2b62c914d70e4b8b54a447f1330450b294d5539d1dc90d081c66d18c101ab1b7d040db49133213a52f53344550935c19ca588af28d82cf53ab21c2c88b0ae4bb035d191a3d36cde19fccc392a9048e485a7ba70fcea6e88df1a24f87875a65b462873cfe3c0108dd996c6038afbf2761a7128358c1c20473058603e03c291a44a7c8100a0f8219c3f764c3ac4575a5ceb77aca4ee30bb75993020ec571c39305e386008fc60e44e8be871e208752ae30402129a79763aa5c7aa57f1316f84f6d320807423f0380b4d5dad4433c73c9f573fb3a4b02d59151f1b92c4cd45020bec810eb7f3d3c435b2efcb5717093bcbc3757971923e44711508111dee22bb00161db4c2032c3c1e4a7d217d32b97119a5c658b5d6f5396a528acf05044084553c2da778c9910f5e401d51bd94170fa9918910ef7cd996549f39e2d1dbd00548a4be0e2e40743608b76cc27a52e776d2b8cc90ce648c0698c7a8d19c295d3c3b5d0289a2a9b16053a1a422b4225316ef8ad8544902ece6443eeb3306474b446a22fa001a3563a4510193cce1f796d725e06626be1d348290611376a963da54a2052f271b050d5834fef3873a77a7f1a252f4418fd8c6c27de3e709ee78d03a33de3eed1dd3e601cd40978740f60f61d62e8a4cfa7ceed0b3dc7338ab1a02c0e780fcfd13a7650cba929667cce79d91822e49793298e07e43b254f127685d06f133c7f1dc7540dc9a49521324d270559e97a15ed2ad4d18f1cd39319f4780a81af654e8966e13ce786eeb4c5e972b2454db28c456b1f401968b4e27982d658882d6a5ccfd5e492ddefc685327afb7806831c7cfde8c1f484e65c8c70a8d362e491939d3dba495b700d9a393dd02df3196959bc1b759f730cf9dafec21f9824ca596ec3ced4d519d3923910b9fd984dc06e7e234915aa08de82d034a92ae4f385102926e9a127c81db2bfe89b4cd6525ddbfdcfdcd654f46875d4a4fa689c0022d82e0aa565e044ab42ed377de35679b4db44b750302504ae71318f62a140cfab0a76c9e6828ef5419e78cac0fed133f88047ffa0a19e2a68ec222ea9e58596bcf2b5b82091f56b408561baaec36af2b06d9897e424276687fc08c12da1c8c5882b86b4d171f6d6a2ef8695a28f5098803fb44e4c386c53b0037386f075853eb0612a23ff7002223a20264982fb4eb6e996e22f28de1665f11f0e48648e5f6b60d044d6cc8783cdff5602f3114720ee54ce754485544c9d555b873895b65019ce2eb9dc160e67bcc9be5204a547c74ac320d7cf92bb7864c92660da95c27dccbb9881daca2f2bce27c2435ec7c4df47e557c567b836e6a9ab3bc59c96d4030b24a82c927f3e537d68161c9790adfb0d371c40845177440c59d4acb96d3cbbe7cb3d20cd96958838a3ed1cae43dec956da7fa71e45d0a0c2cbcbb92a1a8d8b9238578a8e3466ce21b0a6633bb6b1acd59bf9d1c035a80bfa5d62be32c04ca6025060a46f77898328f8082ce6e1eb9c8bf5148f4a78e67b6c460066d1716d786c8edc5d1f0810c7fa7cb0991ad34a10e4b5d6ac6fb617aa28d94eafe9bcd8b71c8563624285232e5123f5d8bcbb4320f7a89f0f6cdfc2df73a811ec3b9b7d43cd098bd34fb04bc20a936988a6f8f675499d222690d87880af070391a6c99e53b1577708a9b13fcfba1d46d77d862b2178f8fcacc2e6e763aa254da9cd388f552e4942645b216bfc49bbaf3a1aa23a2e8285c6618f4e57b1544bddae8e61e00d7e297b793fcb92737af43bdda169820e2aef8c591085a1019d675ac2f8f034e18b3b21e0808189bbeec24fe7009ce5ddbecd217cdcabcf470a3b2c741218a14b4385e2513f83c2b8ea726644b6b77a3c19fb0283d284f628db534384dfd232b009ca167bc2cf2e67e151e0ab441abb2ef12ed654a6b80e9caaffeefa9f7788ba8d9389fd4769a326918a782cb38da845d49a598f1aa4bb07e65c73556ee56ae22cc2579f6ba5ed81b69efd589db699d621f569db64855b487ed2978d0cfea40295d04642adee689766a198ad055b792aca6d9ee6076bf42c2da5a034a0f0e2d3ef2ddf00092631d5a86b37c39bb4e84733c6fc0617052e85072e5f145d3acd2e78e5eb46a1502d1803918143435f7ab05cb01ab2cefe4141394018e41a6aac43aa0ac04699299ce1a3f5892d13c88ecad4f626740c78219e49714af60a25eb4f8e9abac9e4ce4c2a9e1e3c264cd1da94427393689cf0a48823ee0c31e6592bc3515a7a32be827adfe4e555d900b09f6bc1c7a7e32b759da607be7df3bb27fe55c736750788c3bf04fb34864e2b1930993bd30771413e5f819d0cbd55822c60029adc92109893198cc8b1e26662768f79e4cc9b31590e29a8484bccf18a0f919ad940bbf5db0ecd47a91100d07d070462ae63b6796b74d0d7a2078d16d2f1c9d9f4522031ab503b0e60b170d180c14c889e663b7f09c7501a9288040933caa6e4c29046bb043f3b60dc5980545dd1421288377a36acc64acb9b4c80fdc65a1cdae09eb5e9d0c4e3f8dbee91349af99436f8f26e310fe293a98d3be97b4c17aaae5bd54357eaf05aaaf90d8ca50d0063f99eb06deb902b0796ed49950645bf52395ad1c3d19ecb937029e31237580f0d341399fedf8454266b031e356191d651a27ec0c7c7b56ca30e27f7adf5802ea03f7372b7f33a5b443d8cf2946bf56bdcf0db210c9301bb1292f1bddeb74742c55a452f6e40b07a6a492ac3fe0ad2e4050a8d94296f3af379af0d1d98d0c5d1db9d808bad68670fe854f1a5413f4b87e9832d368f0d84ef625113a77f892e1de961e1318420d9078ac0d7a7ea0d8300031d68bbb65c852b58f8085bdc2783d1fcc15570323fe3bf6aacebc00699408b9a04259224dddf8410eb12d67c18bdac9fd558d7c3caf512730ea73b40e84f6e69c7d1c81c63d5683f0422f54aaf6d3019628a7dfa88255f830118fae90ae82e44ba5aa1ff45aea3e28ab00ce016a11aff6efc3b7afd255e49dfd297bb778812070f3f75a962dbeb15f034e369cd764156bf745f9e8a42f3c08084db3e8599ab6974ab492466eca922eb12638489168255a49f3b6359bb37946619602b420d205f938ca877c9db14137c58e26850aa91601d5f88fa5cd774b4a2a829d558af2e281348720245a86b92c8260580124824991520439a44904605d976914594661d134545e5496cc7a3a2b135d67f0dd07ab7ac9103560fec80f27bcd525c42b74c9ffafd765f0719707ecc31075554cf5ff8d04a34ae5ff35c3c89dfc8ee6f56f06c063d615b5922036ff367aca707dff9dde389a9f14f52149da429cef9a451965f1c9aa392ad19d7a323bedc9a2e5d71ac7b3d9bc3f67ada226cbc78d547900f06d782183c79e56df7555c6f4a19e89ebbd698ff84068ccb17f22145285fd1a10a7bb79b1bbd61f30b0c9d5ecbf82527bea38d3f513e33c4f4e22a60dd751256300fa68ba4a16c1e63b70f020f6e50b29fceaf3726c500d08c1e6b1d24cbc89251a2a27225c7d70bd3e598b1801c23c931e399326e2bff1866a9c0ef425d9c1e30e42e4a4d2d64085ef540f1161c55ce8c426105eec051282b6bcf9b0c41d22639bec39d6939faef376925d7efa3bdfbb4d88eca9f2cd5f9a65281477f9a5b92d86043512525f310f65ef1dc0b41252529b38260b3a52ddcf03c9fd80a5ce29b4506ddc4348637d8cea171abbdc08965b3267840857c5b778ab27acacfea83cc4254515c833647b4ae1dc92e6bea865135e6e420613722000284720e40f621b33430436abb86a2eb50da2fff9d4a614502011777052180fa432b5f2a5f184de745ddd2120254dcd1f36d60b4292527a4097838d97548e0f561b0fffaabd04998f12dc53840a4609b82e11eaebd3fd9a2308cc5d6384fb37bb1095e0cb16ab6ec7140b9a52dff4e8a47dabadb82388a6c0b4f7e7bc34e2a3b8d78397d7af403eaf9a023aef56e81ebd4e05a1db3f46b962ec2eadac6b2b60a5b96195ff3e3b86256046cd60929f8a2ce0595e604924b51642f37183bc2f5bde3a9be3df69125a252d003c8b47550fb24bec993e0001e699e77f1430d2032236fa5ffcfc5154b98f9e5e7a3f6970ac7a987998986bb998fe3a07cb81834bbc54255c849c65a60dad8f4bb892215e8c40df460d6eeccd27ac454cbb647b58a190ae68dcba894a514ab62b32de27ae50c5271918b0c6be4b6514bcc8608652be101cf4b5301a2fce907fe293a48c35c271c47c7922faf7518cf791823d94fb2fd48a5c1e3ae37e03a68161396524b07a0f399bec4c7a4a012dbb66c6b5ba30a97455c3ef0ec58a8472680c6809508dc8280bfc91b712261c4532213e8a8005ec24ba8d037dd2bce82976ee55262f34504a63204f967997d3b942b7d707f7b520e005c356f36365eb8294ff70580ad16a739141f02481c1cc8903f68c5286532c5afd6f2e90a89751a462838efe58479dc2953e78be86230f52ce0c4e4f6f00bf936279b34fb5e376ce38e5f917086b5f0218614177681d06e8e92040ecb25588edbeabd37e33caa038e2c220b68b44de6c20b7b2944073f75b28b89e9afb87b94b40b482c806fad85e943c0923168ae0874d7c700ab652a84312e86a1d3fd7aa71215d936cb907dce7e04e1127e7327655349d52b955fb2c02e0ee05375aeaf3102964f09c39c4131677c61ae82a3aa34483a1122fa9bf76126c1e204892f59d434b243126f3a72854c9de0f4203165149c40a3e4a1f1ec01ae5d24a963d71659c465230241bd950e858d1a521320dd756ba19d5c8722bb97bd6e77e10233595bdf138170c7ac086294d1b50e26d43e941d71071c98445a226280a2aebf3e20159df55f2f1be84361c20b33594174a2c9d0d5b02c72c6f0c85db82f7b098d5b14f94cc2fb21ec08ac5a125beaa4fe3718dd50419f25e086c094f32d960bcda35b4d660b65ee40289c0082d935023cb9e3b54bc5f298fbae11f50c684f6438316fbd61831704b1c73f277f1aae3c4223564c28ab3ebeaf920ca22cc0f25b2eab2e766c38c7a412014ca3f83eaf922319f41da4949d7f64b112bc2c98a3138ac4473123727d5f19cb404e51f37f7f6d0e67318ab57190d6543d8bce8ed58cbd948cbae69a2ac327a5fc7d186e211c4f68861575d2c5494def03a3d40d7ce572528ac358dbcb4a1fcca8a06fae76fc032af58caff811644e9453f3e017fad5afd216afc071a2bf2e017ac999ccabdf8fd746fb07eff4010f2426afec40bd611cc320d6caf4b188eabfaacb541fda84b7fc3cbc160d2e14462b478b88fdf2a3b2b2c03c00edb347700fdacae8995f48256015b0bfaf8714348d49c09f82261a2fa2ae3c928e20c00fdc6e9e36681ed639c0eb2e41774b70f2f848b418d024222396d8ba73f6d7352d9892995e5a5aa02e72ac296f45a2bfebe15da2788fae66a87837e4eaed426f597cad7dd37ec3bd4414aa9c1755f0f979dcbc656b91f1fce2ecf21518283501dd5b9ab7a8f32fb8842c73cda997b966b158a4a2587a2b42c24b504b03a9fd3c61e4cf22f1d104513623420f6580eb990c07c7b8af2e30ca232e495fe54c75679add6305d4faba4a7d0d09d5ce417594ef2597a841f9538d0f6b2cc166379e1e43f9f218b43669ec7192a0a585a3d0ae672cf404dcbb61823d95f1dc4cf5b4a7a9ce18b94399b6ae3e78cbe7999059043e156c477647659a2d32f90636eee1fac7f034932971c8aa1caac514be89da0d742aa82601c639ba8c4f6edaedcb608170d7c61c0ef26785285daef46161081d3847a1f9e6e9795c627b2c4c668f68f18e60b041677372ec5a5dda0146ef009700c3b818b07273eda915996ee85f7fca0ef4d5aa28123d4f0c005e484a0164dde2af3c7ebfab6dd8ddcfe5eea6227153783c460d6b92a834ba3c6d6b27ca691c7872c66646645102c34a50e3a156fc8c4920bab3429b61c3e97b4f04e8b37f40ae0d7ca67b86096a6b901ebfc1c98ad3a9f54c55cd251f8570ad46e4a45ad18af11656b9909408f500650e2a03042e0720535f2a1c2fce4b5a6875e2a199a1b81c96cbb301e0b00f4ccd91b1f6e82e5b4e24a25c41459e229394c0e67f24be7c8096106f215c66452683b30d8bae84a7cc02668df1f0779ac7475db5762f9e727b19bfb8ad3923b8a0af282b7ac7a24df7ca8fe87864c1f2a05367c9036b9f05f56bf054021ed7b1b4b476db8a1cdb9a3a10e1a718c44f5b10afd6828d639fdee38887fde2d593c22ec4e55d95be13eb5e488ec2c70b30e3edf81ca00af387c76bcc6afb941024e6181a5f9ed452b6031f60746de145b10b00414028b1c7db5fda4656420e2f53cb335dcb85e82e7a344e73b706df98deb2dd8f308dcb396eaa6d2496d2bc7fa59895b5aa451072677e0448e26e7f8440e4cc481c90e98d8b1c9383281c389383c993b604cecd864a1529d7005e853f251edeb6f0e570655e04d054d1c4fc0f1c91c328103133a32b143137160128727717c728e4ce0c0c4c0817d72684c39909cda9363853a1fdf7d3e0d116a8fc5d8febfefeb25e296cb1f54293ee0faaf4163a283bf8e4b69f6b0746c3db1cb88deedf0bc045559b2e2bc53ddec5db60c88d0c5b2cdc513ab981872e73f9ea0328fb44e6e11c13c2ca7de60848bdaac4474930bcf643f602457225cb182c1d423c2ea847bd2879845f90007963d0a392cbb7f1d46b2f9630e87de071fc82a3eeb4685ed03518c81de3c243bc2bbb68d3048d4c9c01d20e6a7f4601b255598add12ecbfe68d6a095aa1afbf644718e673d565e42b4ee4938f4b4d4e120eaed875108ca8e5dd5882788e8a63d555d5ad73926df6ed43f67fef3dee782039d862743d175001464cf207a7dd260470e854150e1474a0371e693a9ea78f88c08501b3bcda563d7167e4a78236f5fe15106c744adc98bdfdf89835d6fc6aca0900f675b6a084746cf6fc086bad8f2c258fc2f89d61e11a180d5c1c2f9188952842923bafa39a0e878bd768412571a0dfc01e0a791719ad752b9c0bd05050661ff7e616c123727deced793a49eb45424b3426d49000ab7a2eb0e625bbf1bd87abc23b9cd2976feaf65f2261010f35c199c9560427ce05c1f3ba0f8fbd503f79e62dd428d3f367cdec0f326ecbbfec3d9bc6bc3ee4da1bd43b45274ccbe5a92b528b15a37184738f67b640e8016fc8b883ba61bc3195e2e2a1e0b0c1bd12eecd7a38914343f25a4dbd74711274d04090aaf6f4bc0e71255c66374f1a91e1912db89b3087db9bfbb8bc3035a724566584aa6a66f55f0b74e107d1274bd391ec75a1ce2f4b3997f50fbec0325b9d575f0f3fd879d8355f52944b8970f686f21d9f59cf977b4b754e256574a4aa870f591eab9c23b627f93cb8b1955bda4e0d5011a5aac8ef7206c139f3d436d12be1803126120e02ab4c28e0b28274b74b761a1f96ea39ee0bb074c28a249774b52a45f68468fc49d0cc205400e8e35f5613d2393536cf05fc59ac9b5a664b74e886e0a2fe415b5e3724a94eed7a2c73b055f7681d37564ae511cb2162395cf5c1d22989438a8838890cc433f076f4160c0c4905f3ffa3350fb3b93f06d4e75fb201752baa838b398f16d1374fe2f7a9f4ef3ca3353e8833a3378b33b0afb783a78f0df69b454cdbe0ce1308bf4801070e56cc2d3049b2b4bebf9033f024b41a7e956302a7a8934e02da67dbeaa33fef52b5c28da0a36428a51c4082a5168300b2c1efe7e5646efcb76f0afd8babdd39e8db86ec1aa82cbbe1a541c64a51de61ddf68a0438f38a831dbe26e0480564a3b771f5ec4d868cc4c80db2783287281a45260bf7bd5959b6581d40bab8fc85b845697732c5ef14fd5a04cb4418da2dd63be1185fe1bc3115abfa1113c376092d1b563674805a4143314f00cd8afc9cff18601fd72210825ecf08320426236a2a3e189bbe41460bea0afcaa64fc91277cd7cb0fbe37b1f8dba4ecfe5e2e3ea7e31863cacc916bc0dcae3586ec46e25bac652b30781383a0802e53e0803790881be7ae033e88f5fc9bfbb8a1de575746140968133d90d16f670e240543a3958035397a2739611328ae18a859a7d0cee167878de63ffbd4401635de8efbf3e1d9bba22539afdd5c12b90a2a241a8d52721944b986925f2f8fbf4d746185caf654b96d5986852656b101e775fb568e21566e19277a8a8f2243df6a2d82ddfd90f7e4239dd18b213116f635e1e069d7545d128bf6a5af0b561674f83bd2bf03a3f3375a1890c7642e46e01590c292026b6115b21a2c3865488c5a8832be0cbaf49b01a93d8fb41bde3176564b49ce33eea00424b290c90993bad89764ec473fdfb2f0e7562716c36b69e07cb1c573698abe08fec7526778b02069dc5940d2218f77526196ef0c537ead101b881295e1870724c756663c2224225aa4e3c21c12d1ba566f9106c1b57f9205f80e4c8dadc76b72d44fb5630fc9967f854f9a44f5f541c12431a90e06965c545a9aec1fe9d3dac961662a1656884f57ceed7d5d4aaa103c078fbcda5328a353b76a1ec9c48a6fd630b816bf731ac2834f332f37897565cb2a5e8ad940f7e7aaf44488107515fe7970543aa1debd72c113c98e6a97b2bfbe6f3747f84f490741103cf705ec80c340be10aa3763ac3f0514035780e7073a68b21f54f5c5dfa42a518f03d17a90d2fab3ce138bec8dae19780f961d2018331558dff98512fa13d4528f1722488ab0e95913ed4620b60b3030055962217ecd046d2bd570470b20820936d10702fe2ecc0e3eea3319297c6bc62c3898b1f6dc4a699b0dc4af14974c35eaef53fc9994fb40e9f987c69b96421529697ddfff98c89b54542f57f7ec64828676e1155d009a36a25019f5d3d0c41a45c39d6e7a594299a36a2d705ddd5b07a2569e09a081e98ae12df1a4e87cea7e9edc52513713cfeed35b4880b5fda16fd003ea72b5168b590cf5fc1ea64a2a5fab143377f94b0101b59b8a81871772cf934e790b0101456139da6d5f76c00cd648a8de584f68d36aaf2b5d52d7a88a77bc351f3e8e38981bffccee9568d71548da2a4329cc8f4850b5889bb36ff79854b3d1e2223b846544ac6c7f8169170bb9286ea2d7b350ef7e8caec257bac528ba8ee9f92f595b887859ebe327859bbe48ae33f775cf2d864beac7baf07614bf9f0d6b0e5501526c4c93df256c08ff4f25df63288cd4235f3dd0d9f4f5aef8c61cc7ce3b9514dd9f786a305a4c2d77c9a1145851270b0331c4bf75d444caac87b509b6016ca47241e24580ee2b00685c96330803201d1cc8ab4caae9b41057c8165694953e3fb47d4cc3612dacdeb152344d7b0ed30dd299d697159837f2c23eb1ad6c3f9de9aa07fd9ad6b5eff19b2cf0b3927d422d293fb9ff7e8b896dbcc021f23a3313b29ac9014a00d0af8ff9279df3551b8de82b271634da42e04119cd63c1db2e634e28f1c66053c7f13be2f483120930196d10f4ebd9c08fd9c5d2552ac6d70046e2eb53077f76e2f237babc122e829611ca68a7d7654f010e089ba3435a99f9bc165650c21b69371c338c9d116bb3c6a5d3d781e78083c9c4bff67d065ba86e4ca0d1ce487534bb728971557376fe7df048653e43924eeb205fa39d519844110a8679d27ca6d4ae04b54be023fcb8b0b1af16c3b6d3157a741a0fc34760f6bf6893a424f23e8228d3e125815ca4b02f24bdf6cb2136803c931d675a36aa286b0ba507bd19d332106c2aa2441c3a90d43458c4ad1a4cf7e6975b5778295f5831ef90066e11aa95a5910d7a32d1ff87781fe69155fccb60ffbfd8ab5b6d9e0878cea17ff5c54c3c3dcb77ab5ad89cd9d8687dc5fdb30328d615eeab03bca186f29963acc7aefa2787fabd4a17aaeb037f480c4eeb1739d8f408b47aaa61f5822e7034830f8e29676e9bc9ab6626285cb4c3d378ba18b48c60cde3522c78c2f73c07bd633cb2ed5a0dba48e82c49308a19650dc12896ab219522067a8135b8e10ede4f03da2668e9e104e4d127cd0042452b7842ff576ce27a0b1943c3181a104588b2472aa0661fe6beec6e9c513cbe5208350b2f283219cc80be1019e39f4efaeba959cd2e312d4e252674a0346e9160eda40ba7378fee76e092624b71074eeace31ac8af36106068ca565f3154be18bc8628b5564d195062d459e2d5ad0ac89e8cee45bb8c5df18beae207a33418e67b460e5d24616c4066cb19fdef87b62ca40e241ba80ea470abd5daaf373b9207553734017c847f0d1b052cab2d7bb5f9733b0e838f74b208008c7b61f5280d64605e06ec8c7eb441df97ba44ddb5f685c31e1324cd1c9d035dfbfa4a8188943c3e06e21b0ad01962ef1fb6b9a617684d24bb07c7879f317b9cfa218867c2f43c96e8f5b4b4bd8b3e641b24fa89cabae89bfe9e0f3d502b969812e03827d18666ce5bc0715f7c5ae41d816c6fd07de1ac7fa60b67f4114ebd576eef4ee3959afa8eacc1f72081bbdaa533afc1cf1aef3b6f93f091e7f1b242844ca8b9f807b1d29374847c7919e6eff348bd9f4b48c560b5b2ba2b71c7b39a4f33606dcc3e41a411062ede4429ba0cc95c087aa0b954aa95c9fa3fc3ea483f7949e84dba77abec3e623927b1bb5f32148a2c1d7438d1b4ba4876b14325b07ea282de50a3b2411e05c419cc430e23394c3f0e67de26f6821f9479c97fdad005626a92b6a7ff02fc0f201fb6438a4e3e2c069ea613f2636d3cd0bd41845308330dc6d4badfcb56770cee5147284dc6a16026f7c4e3edac8fb53db4e4040c8a330ec35fc2f77764b02e2923eb4cd02095d31d1cc261c417275e7fc07780cf31e9bb69c80e94111988ac6d7e06c61b06cfeb488daa73263c1974a3850dcb8ea617c7d2d648d5dda4a524428035fe58550102553f3928fcb0bfc708a6d9c31a6b9fa54130883c7e1f51f9ef2997dbbf8604dd8d29788d80e245e7b33b0475839672f4a23bf0b49cb72fc230e2ed1be4d762d166fa8c9e937273e09886e53200ce1de87344658bb9d8a0dc6b106d32efdccec21c849bdf542d208b86d872a77f5bce5881595474593812503c5f04dedb192eeb3f45967ace7cbf5883515c1eb01efa68131a59b7695b87cff3b1618b3fc2d5b38dc7c8387f77a2a2462a7a74a9060d7166bde295b5f5b69afc2f93c0eee891b259dd3bdb4a0dc842ec3383b13059d3f8595d5acb547df942699ff8ef6d3a66696633bd53936075b60d1e07eba4164cb39ec90eb4e01d4730a5aa536308d2a9d8814e037dd190b21d1a9fa48139c28459cae0d88e7c4f486a8825e04399569a72e27ae81acc90bb562a9e6962d6a1d2c00a287e4d9e8dec4fd9aa5f0a02b4faec14d520b14d42a481e972af4eb6d3643bdb972268ee8887f1f69ad26c57c226f8ecd52968e103718b5fe50d48a0f00af6ed66fea0b8c507d05f472c44d5f4f3696cee150ddc0737a56158fb37319cb364dc1867f607dc9c8e35e53813ecd2c5dfd8754ae81bdb254f98c6e66888cef1ce242cc54f3f7eb6dd76446ad6ff240a7a4e88060a7e78d6db92b2c2e086123f47acc9bf1b6afee9c3e66a3c0951e07829f8ec0fc809b843102ac50908ed8f59f09d84788e9a0df36c8a6e4b863b8a9c34a558cb9e8332141a806bf58e28a0b3d57bc269fd79c1b86dd9c12598aa81d024ea7d357669d6140f5e56da6153b2d2d7430bb20d944b62c32ca7f24bfec195bc5194bc7e937c342c29e0ca0a393ad3a2655b1de5f2d68ebcb7eb7e50ea1c50a1911eb499e44935916956073f5d822201a6217836bd8ec0adc0f75b49550083371738cad1cd9f68db9035ad76a606992863ecc2e37beea2ca5290d61e645e20a463630af1326291a800fead801d4ff618c96e25655038d7174b4d976f4f83173bb2e84bef895fff16da24e80a087239b186e6b1225780aead3f82a37840880519438bd3ab630ef7950a778d5fdfe3855e6ab90f897be3cb8a731712e4a18f83c48669c8a77c88e7f90c4df4ae61f4b21178beea74e018c0b5ece0f3cfed5f4b185e41cb00cff773bd6eeef9f20c419e948129cfd701aa6b6dfa85f49a89649243b1024f5332bc0f1e1a84b57cd11ea2b91aafee0c360ea1dc16b61563b6a391fc031a1a4518d3bf4891e1df6b7841abeceeece8a352c645c8e4f6ac2c53bba3863c146b47b14542f8e6b42d01a50553b73effb312c4d7ec851d6e0784f122be5aea4b51fb147dfcad48f5e4a2dc154da5ea05506bae737c7a3b2332a8437ce1540fb2da2076d0f42094e3229007f4fe1e9dd56b7e0c1826e17cc16e4ac01501359e26eac149708976cdb2190cbfbff3cd89e1b60973c5f2d1857ceb2d6202b4343c73729e629616e03c5db017252e7d7809b507772b023e22be02b4b327c58677729875771c08876dde3bf38fbc4f0679a87a7c3d402b4d004bc16a0eebf6be24d919a3b268d832c6424474a1740491a392b2a430a269ffa65cc29146edc57ef86522f18c4fc860e007fcbf7497ccb4813f334de3e7a2879980e14f04486cdc836aef2a58e60058357559f5b2524172ee4f47d832cd455d35da6ca2902bee21a81423992198117fb410fd528b18f5910843d832ffc07563f963796012e158f2e2c0165b3173dfe7eae916840d6cdf055b46a35513e505120fd028f0eb62c54368514db065e01540b5461590bb338c07c20ba888ac432046b5c1512f037c040832a6c70bb27474e007f321120e3cd20038c4988d72120ddd10aef9a99f02f44e037a711e7c5a30561aa503a90a9580a5116159218c30c9668aa02ccdbbff5d7f7f1e1eb91a10b5a34d889b37e5961ef6be729cebf3739f9fce2b7f1d532ffc0e506761df2b4784aeaf7cdece5e388635f65bc9932609d972ef2de59652a694025e07dc07af07393efb58b3caf5bda47f798c33fdc5c154eebb69822659a1c924b520f0f6a340868d5f7164fc1ab953f7cdf045efe56fa8b9dd32c897afe1d2c1e5d0d7e32c67e2b61c1c4dcd0dd76306072764e38870315c04dc03c235384e5f18d3cbbfa1360e505eb2e6a6c7cd8f1e32c73daf57eec9ec71a8b503e29efc15e4fa56186b1d07becbcb7fe13ed2574d7b084b903335d14a9a0bc717d6fa2e96a4d50aa3c653bb1715d4dee49ea4d1b8cd3dc91aec6c6df611ec02491757b67c5283cc16363eb4d2073587d354c9648a60e39762962864e1c718e1c7679a088279cd39e79501c3fad30982abbfecfbe4deb5b9775d177dec334ec60ba6b944c04551941a683ddc28b53a8e35dc93f6d4741fb55fcd8defddb70d9129b2352bd9f08894107a8c314229a5942ea5e46225a594524a29a5942fa594524a29a57c4c286a2ce31a4aa2d7e7ea30081cbdc3918fb838a75fa391cf0829a5304e1883fdfedacdf5ad5dea16023240d6a5bfee4f5f6ada5f5c7b778f4a2ed53518e01d5dd7e81a5da3eb1a8d32381a8de2e821e7d7e81a8da6c6638dbe35ffe93fe98bbf929b75f70eda67ced970bddb90c3bcd6f375ed59cb615eadb3e17abe3cdf0d5d341af1bd62f0ae3f0a52ea2c8b564b8301deebb3d9fdf02f1aaa05e987f0f21b6aa0c67339d548a3d1779b9a8caed1f540d77f2f43c01bbae8c2f787b02d8dc7bb1715ecec613f672ae3c8edeeb80d0cfbd1c4f76afaa7eb21a8172d8c091ad3918c6964aad8fef8c539e79c486410098f0bb91b2d31ba1006e740e6913bfd70284170e14bd845d94d05c3909eb9e8e92857727f16a40450137777771237c451f285e0a001fd7eea5ecf397f4ecb6ad96d75136bb6f5f1a755d2a7091057a6845020253a08f613209228d94e3e2d04a2e48fbebff23486c40337d060d11fbd7c0b54ae02354480062172f603cd59837b72d608b827b3e62695eeeeb6acffe1341431d1da4da885d18b520a530f6031b80c1aac2ec6a7d2ab91432b5adf266907c53d48ad5d118929dbab8fa889600a0747c15f117d9cc513823f1b6721f22885d816d6ffabe9e00f719c56b9173b18f87637712f5a409274ffdc9c88c0d40e8c8adf73238c3fdc8bd30419d3c477874d89b817f9524b765e0ff194dd220b27e1c6ae9e179f7c7744dc8b57770046c58fdff6592fc382625bf410b6898f2d11b5210c258bf4b88542becef1c93dd86deed1c79e69c5aad8f9168cff55192d2c6cd275168460a1532c86b43872499787a830b95c3a65c99284244408253a11313250a8c02c422af1e5f2298be5efd5958196112353458c4c152f2724bc9c90f07369abbe5517f2cf851a0c3157c60acb41622eb38d8c1556757f78abaaf45c9acb4344a872a15cbe7ce3cb27a4ac79603f7a3fc22e631c397802857104d96fbb3ab416a4abb0f08415b21fbddb8576ab9e32127b8af0a75d54c506fa3af48dd64febe3253f07d29d577c195f4a09a5577d69bf0df2758091be65037d1ad6990f1f05d68934625a02fc63e63546ff684dfe7e693cdeec96842c47f1b386d3f296a338130315fae1c9b51e7e1357e7b29415f4dc2c56fec832b295dd26230b52d82f5e69437cd699ef2f59077e0ea41bb904580f5fca0f4a5ceb73e886d22baea758b5a6a648f358dee5e78f05203b330319be37033b19bc2b3bcfbd7eefc6dbb0866dda4ab3fb9423b78aa0ba8ee4f28d5142a8e3fa7ff04618d985d3c7aeabb8d77dbbe86960f6bd0c7fe1bf673938f8b243c8419eecc5975c16a3cc435921843e17428c35584575e7cbfab0e2c40d5e5861027aa0bf5ba3aed768177a4bf3700db2977de5267bd95f5cc65e466254b6257bffec69966935462c62185fa805f99bc99a717d2be73d46dcf3ef22482dc73ed364e8b1320eebb29edc57f7c9ffbe8b7aae7badc940bab16b2beeb5d085cd108e3e10dc990f6849644cf3694057c63c5b968c25d31a4f0cf35b15ba5cbe91e9f81f9ed1eda9c653e39e0c72c3f4d90bfbcb64b33a57ca4c268d4707cc73e97df42f5cdfd128d5a3db4762b8be7d02c681c179f96f43fd6757f88fb2f0f1332add3b1cae31e7fc989898ffe13326a68b46620fdf6bb5caf1f2e5b72dc7af5657137e05452330beddb8565713f412ba9ecc2d530b8effe8c53dbd52f8bf9e5e38ae0bc7cf5c38baafc8c5f1f4495cf4d178705c17d0a5c4f51fe94929fc93ae8b54d2782ed22c9a457ead48a35974411969079089c1bd9b91c1fd228a4666d1e9b313177be01a5e34e271708d53177b60665134523fd378e01aeec93fc59c62604adc060303030303b3fd45b46d5fe2b6ebc9093f8b1ca7f4f2a7952be86ac271605efe25040303c3c52bc395b8ed9debbb6d303030251a120c151450d8ecddf1ec3b7694627ecebccc74cf7dd6953d9511a140245f32a298ce06ff979fdd9751992fa57fe1a87ba5dfb8afded297b8d2bf702afc3c71f1c2358c6425ce3fd378aa0a25eeab77723dfd81608a855fe1adce249f058e1965fa1538d3a338e66be25c3e866f0a28d09c308baefc1c7de46bdb7133cf7d2e9fc10a351e1d3ab89a23acc32c538af9928f382e86fbfac6fc0bf7d5705f4631a58fe18adc172e06d13c4a2fc37d5ee596ac7bf5a32e5c9b38bea3d1a84a95f39d1d6758eb3f9844b28b0b0d465c49c23af3e943258e039fbe6b611d7f5702a7a0188ca27fc5155a68e18517573e6f7229f50e5e611bbaa30cdbff41d5a5df41945abb6d41117a1d22cba0e4f4c8f1d12420a25285c4344388ccc035393437274833d273461de7e55d5efed321ba7ce6f585fb5ebe5d3c964ccd954c36d08f2e6f451c6e2c75ec9e0bf7dddcfe9651ced9c4a5ff16f7f56c259a4777eba079f45f3b740ef35a3f2d28eb5b9cf61606be357e267f76f6935e94c4d9405fce9701fed79ff1e4af880ef1ca991c6060ff7efdd33ffe0de4df419cea81e50569f080871ed060b2e3df429c2ae2c4128c6cf961e3046140d9f1a79ea487e05a3583a3fcdf61de8d10ade1be5db752c5fdb4fbb5763f5e75118cf26fae9bb04d11b45cfec1e5212b546e567db04466609dbefe0ce054ca05508c104415569871c68e7f635dffb8e71f9386fde8756f2bd3bfc8dd810521d8212b2b8b16b92dfbc54951544c85b3602a39d6602af7cae2667f7367a749b11f1771513315d55bf73c0912db541eec77ba4eba4e730326749bd4f1265bdd0bb4cfb5c1fa6b3ebdd6cfc9137f72f4b22643fb7083d4ee86f6b99c65cdd7d163e7ff80f40298d0a58c3d2674abcfbd3efb2fcebfbeb5202fdcd9512cecd748846eede663205add05dae7c6ee064ce8c28e66ab25b78a94294d58a2869233765c0927e82282112d00869224d028821a4c6841240228d7e5892853fa0a5beee888285f55e1f250193fb7d240ca16ab35a5d75c1e3283c9b59787acb0004671775f8107292220c10d58c0060db43c498211b885086210049225ce102aa3abe984ba3c5446943bba3c64c587fbd108c51f641c89b03109cb4255308e64d851e4159a0673744b91e35035d88b1fa3482b77022561ffd2d5fdae1e8ca85de88f8f51c13afc44df055fc036f19b83499a477ca98229d2c71c4d4ad52755a40eaa36fbb0f4fc1faf483f579c2a75974b673b7e6624ec8fd078ee7732ac27c17e12098b84ab07a67807769fedeedbdecbb9dfd5d356ee5f1fc29d40a3eecbd1baef06eb3e0f5eddb76550d573e3bb1a56285e3d37fe0482df04b2fd37371dff08cb1782f1831b3f0281810476fd6cdb4c53163a61a3396defdbc93deac3116a8ca0053fe8a10102a668230a1430c8d2032176fc0c2443130910e8c927754279707db4e37e9a371ba5d9b070dfde63a3cafd6e5457c82cd0a037dbac26777797a207d5f5b9e3eedfc3fd4f8fc575971d77ca8619d73ffa537febbff93f1b585c7f1aef2687abe9147379888d27d808e37e3442d650f754d2a452ca385b88492d19bb27320108d7af3ff5b74fe3f9dfb809aeff0f266071fd8108f127e24fc31013b8c091d7df02fe38388ea7a02705d6bb3cc4841adcd2e5212600713f1a216b054d4f26bbb5bbbba58223d0a0c7ea082f380212cc068923f848253663c3b2228bfcc18e2e0f61f9220558b268030b105a80050a09050b12588478a1b19c6031415cc3d63682a045f2604d45160bb06062eb14510d9bf5e00a7be5808c199466922063054bd87879888c1484800c1e7e8ca106260384857e08714f8b31b2584d2ce9f2d0185e8c314533b5612b111b4c5869587b79680c1cf4300610d609c6f0218b16356cbd92039b5d1e12638b1f3156d6c59022c3e69bac1c428c24ac88a112c2deb83c24860f605c692372614d9787ae9091041725b8620936e6f2d01526b0b0309787aed440a661b5cb43575c0064c80f21574a70fdfaf661a871b3cb435750703fefb665428a4201268a92cca0881e0051e16624000621d881114c1c4186169913d215df43601cf14360d8e07e345b3073c48e5cd73591b44f97e0481f21a3e88b3ab8c4620cc04d00e66422cbed1e18502c6209d7d26c4919a125a5841042087d28426802e16b6e7ae4d4121c3982390eadd875bd76bd0e15c33eece3c41eebbfbeaf5a67b8b09f015c6e60bf7f67a1f8039310d69d35c8365e86fd6c8416c6f93a6810c208e3c338e38c2283c1faf9ec9e47fea847e45add3dc8228c708c08231c23c208c7883104d05dae2093b0a1840dc990b0a1840dc990b0a1840dc99012427b46fc289334dfdcb43b919543c938fdf13f5274cf3baa05c162fbbf18b3eb91a7d5991ce5fd1fe9ba4b66f66b0465802f7f4691318df4778fbfb872722fdafa03fbf96821f1e3fbd8dc8bdfee4da024d912c4944b257e4114a38611c152e102a1cb5424b972732e53e1029fef062e827d291d3da55997c3bc30bb34ed31edebc38b6a3c5a47b5ab52c85d1a86611a4f0e7dc1c0172a7114ccb07c2537467f91cf174197878a92380a2e0f15fddc8c351e192bd67fcbe5a12e54f7eb14dc7e96e2f2501740176a3c3244b0eec2fd3d2e6ad82fdea6f39f26cb481faded4cee596bbbef86ffe111429767175bdd6bd2f6343d727cc818c1f66da0a046428f6430c28e31462432192b5fc87461d93de9efab298ef33994cb507c8a945042d94e9a6892ea298e925f93036b6e7a64373f7ab8276b7280c81835ac76771df588fbba625ac36f4ce3815dbb6364a8d99306c3ea4282fd384baa7bf93b741c3bea8b5906bb697dca9a56eb3707bf6a9cc603617da805a92fbb90d53d094d26d3c33cfcc77a76fcfff0aac30e9855faca35a415fe0eeaf295eb3b6b75a92ef523c72e35c28f5fa716047e8db376736a3c2ead09996f03bdb88ae32d77e3336e7b1227f339b8986faee78f48a40bd334d205b520337f7a6839bed7d78fd125c63b5d2c6663b5d7ccea67f1ad7d18e3d4784edf1afca9f1e8f8a8e18003d6f5eae32c5c8e9f1ce95d8321445893e924d43dda13b6b20ecdbe3ed14dcefd713f0dc4005d971d87db20ca35222c931f305583432e62a1203cae057a60fb3f2ecab971cf99f4c022643ab00d0170d51cd85cbeb931af5973f93966e1f2b783405ab52a61bb2661bb56246c3fd7236c7f05ca50b8fca156ddc33632509875684627f454236c7f7562dba532ebfce51cdbc35170cb4169c7efb81feac2b08ba26efbb86792e2a552cc54c58b298709e6043313536b11325ba530456f3c44614cb65a9bdca8cc3a32b5e260a6d61bd8aefc598e5a6da0a3d69f1db512f1b5d680ab95065dad439c50eb0c6850a8cc3af5b2f71cea3a0a75dd70eabababe7256abd57792ca0485142af51b039442e54c855a9778a84aab7b42a06a55ad50ab102cd42a839a5a636053ab120b001ecee3e6a632ebdcdcac6f505c43bbeee99931c3662d54669d9bcb9fb570c3030036352cac80f2544801059a133aee77e8c8719ac171639389b919b1e5316a6040f1428801718c31c66da354fe5f29e52525fdc227ca872f3bf85272f771607519e8c6af2c904097bf6377c44ab09a1412e007284865d5c8c11fd58fea474523b597527628a9799e35af16d8c6b51bb6f1bf89a74bdb946c5408fbf951fda8e04f77b18dbfdfac351e4a755c2246f963444cf4438fb01f4624861036c515405a04792174fd1d7618d112b6d95dd8c6eda537f0662c8e22f40c83d2181447f96344572c46744d97b3e9eda3158686c4e8252515c2b2629c73cee85984d6cc9a0a21b9c65e6258ec8b524abbfb24605d41d6c3191fce97b26bb6f1d9490883b0ce4e40029251e7a237503e2e486e5c9c923d10056db7949665598e41cbc25a56f76260650f4c613e10e54f690ff4e1813f13a4af8e8bf9cc89f94ccc67623e2748e0af15c6fdb0297c600ab301a3fc8f40c283241783e2baffd482e8b8b4c37ed8c69fc403eb6c23ff87c3ef8f5cbc19f5e61947b16b10d02887f084603fec07fb8129a972d549092b49405813b2ff1db299999963944ce98438cd5ea4b76512b6a375ad23643508db1075b90671fa22b6fc611b7e8f6dd883e058cd82829200c2ae1690a87e905c1d658c9351ae2c801c471225c138b28a2098924c906d045d12891ed18c7259749d88225104511a55c27eb24812b97f12360aa72e99c56f4d3659334a1f2281294672dd6a6b85407a214692325468fc145529b2a47b634597127166d8c4322719768351f2fa8f0457f02a5d79810ba9631970f6e43b072ea418e664c268599605e1378c5c44f914e3df5c32c19eff1765907bf2c712608a242dfcb00482222593b446c408edaece6a44509905a3fc6590e3c8956482716410d893423025c372059dd4ddd5703b86bfddc5955b1ca216835dc51d6e8c81aee27b8c5166c129fa7ed385a71141a335821d43c8edd9e4745c99c4659209dcf8dc8d1dc72eee27cbb8fe1f01aecb2b18e5af85ca8b9feb528c203aeeece4aac8f67f7285adbcfb9abbd317b69fa3e4640fdb30842f849eeb1f25cf03c83081cbdd68c3257b58473ec7362e06faeab87daa629b4e669d1d53b3640f6b3068552e0662951672cf9b7095765db503fb4123385f07419f0a5cf8488cf8932b5b6657d3a92f0f852174672e0f85b1c5d52e0f85b1124266011b19079b8db68cb6dc54126cb19b6984c6688be64425413581bc89f952476f7ca9b310653def989ebe7ca0f4f679076eb98103fb4d34df7b38c5694851e54a0e825a2083239c2ad24406416c100d21c18a1f76664cccc7741f8899018707661ec74b37d300d3cb9b4c6fea3e204bddcc0c325ffa52e781ed65bec80eeff0cefc184e0a41d4fc176e8607fb7596f949ee7c21987af9f99208a6244c411d7ffe8b5dc840f7a0ea0149133b9fafbc5f6721da72e73714c7d9712713f1d082298e93e3ce6f2a57eefc2490049a324875e797ba2510193172e7b716c77118e47593a9fb176edb62e30653a56ea601a57ff997ee03464630db68a38d9d22e81be961db317d0f393b2f5f924960aaf459aef7211c67baca71fcce1d45f37b4bafa1eaf1d4ccce7cf769e2cef7233c850f0aee9c53f8d0e4a67c677ebc88a6f0a1573065a4881e7ea70847620145764affc32590e3309108d6b8f3a512f34b4e44e9b3186ea60131ab6ec61fc63cefdce0661a70e3611ea6fb40a40251f36f7433de29c077603ea6834510351f5e81a8f9bc43546f70bc13c3f1ce8e24d8cf8bd64c671f47c7d717d9b9ba991962fe464c37033b22625ea6a31015bfc88e77d667311ca4e20320ad623898b7380fa660962d77c69e3b1fa6832b889a0f04db2a986a22889affbf6dd6329c26b0d05e0a77380cc468456acdbf381decb52611db4c8bdb91c51269238da4fde5e24272712159cb22619665599645fa2b87be3035e7bb7050897bf32d0e22813ef0887bf3471c040226218229a882a8f944314bf77dd0dd80ee843f77be774477dafbc155abc10398ffc231b7f0751b7796bace025193eb34eefcded23ce6571f6b3d5fa6995891cc636a402deb7b5eccccccf2bbbb999d27f63c99db85ada78a37849762a988e2f2f7129da2371045bf93b412c75145a00e621d1eb0ce7ca65a10ac8b446c43dfb988254ec136f42b57c4028aec442abae318de173f841ba540facd4520f7e86b5c148228fa161783dca31fb9a8847bf425172f2efeb847ff863e7d865175a947f8de51cdbd4a25010d621b98f21d6e2bed427750d53c8cc03ade412637b8f4e14f0b807e6b0bf01da88228fa012a50419440d8df3677e7c1091f3811c5856540225b15a682e252c5271a9393296a4b3495268426032d0656cc39e98561179d5a34251a0cca9873d2eba2d38a275ab4682f9853a5b9404ba2b5400b4263810684b6024d05da0f1a923927bdb076a2a5a03b061a0ab413684734136825d0a2a5bbb5bc2cd13cba65efa83015dc814fb493222ba03c8f4e564c500b0d275425a5941da78cdf137e431aa394314668754e5834ca0bce48b1aa695a96695a96d5aa69d9a8564d6bb6f1ceaeebbaea37f3887d357bf2d60edcebfbaaddd7b716c1ae2ed2e812026b4629a52c82a552a9668e6545a5526d4e4481061a4476a6ca52edac6408a6a8542a1863e00221f438050b081972bb3b3b31250934e21429cdccdc748a142a95cac90f970a5ac618dd63749732c6d952ced9b13dd9133a74cbb2dc925105515a965445158458d068277ce0845a4e4461d1796159b59c5c77832815a682e25215693e73766b3e589665c16ed91db51e341e3423da0834115831299df4c2b08b4e2d5a08b49e39e72c63523ae975d169c59c4fe6d432a79636e65435818d35d448038d2d4c58821226a5935e1876d1a905c2767246770ccc28234b129000001e736ae17133a7969b39b5b4203d660905c0b2e72b4c395a5238423f1ca7617c282693c934002210e54fab356da7a7b9e94104a6dac767e5b3f259f9ac7c563e2b209f68468fcbd0db3603f8201a32a44a048800012140b66ddb3603f8f0de043797fff43491566bda785cf22b8c5fb98c9f4cf723b4772364c284490a342f9c60aa7509f63bd5eb3333acc33b70f76f7284fe69a0f2549eca53792aff3d4c0509fc020b3f79f2e4090bf17f9a9335f1939ba3448992ed2ac101ee0053d3c753ad52a25232c4143b74e4b8fc34acd335aeac359d36eba7cdf33c2843aa90e1870f20ac0363849e673deb05c18b2a6ef7eee699783ad1133d4115c8380e4ce3d0c33a10cbfdbc5403a1e43a10eef977ffb6a9bc042ff754ba6cf50bde4d0e4cd5afe2c3870bad9f0fdf4e3f8008611d1ad9720f515ed43d45ed73e379710ac779018b6de8a3c685174e261dc721c0e58f648c21c6953032015cae898e1380510b462802006a5c5e28d940a9a9a9a9b959d10b739c9acb5f6362c1b4c2e5af415951010cc741e10b2f1ca72bea828b2daa1061088e83e3f24fa9a9a9a9a9b959d5d4d4d4dc709c4d8b182a59d87e1602165378581ea4cbef382f8c6e8f9a9a1ac779e1e5091a7fd8b65d676ffc608f38ce05a59431c236620594d753e466304a1a637c2963941623639afe012405496425bb7794ffb792af61d030601dbe3d0d89c014a5d66edbbf67f7d9c6a7b01d51e0355a7252937b17f529db85071a259b5391f106e6a416764dcb92921b2283fc19c48507b882254b966c399200c5eeeeeefed1dd971321ef00513fecd0759f7d2f76425baeaba26c0182418c52e292254b6284505ca225458ea02b8d1d3952089921436eed00d2df7bd0fcc3a74fa5c425c418a31573249941c29ccff91c29b2c7932c63648e1c3bc2680474294a8071c9922543dc8827ae9f955a56b2f91065b426a5135a10ded835c3174008e36645b86d34343992d8cf4473bf9717d6d1996d751c6157f79e461e36402184d18ad03262be653d9dd3f26a9866f36882b03498122b82ac6fdcc214cc698151dee366d53d35f828eaa9412e894b2c14824bbe1d1279ad52a2b4acee2ba4c53a352d5c0480e2063dc2f6ecbe38b6f17f7a83551420542b6c7f85a28daa1891ac35a735e975d169f5a05661fb2b15a517cbb2ac2b5e4cd69cd6a4745a432618cbea8602136359dd67c4c85856f7193253dcc061594b88ea90edff81144f7c10051455c8f6d7275250c19ad39af4bae8b47aa08217a311d47382851a1da7092696a83cb0fd35e8a605cbea3ea385005856f71901e86ecbea3e4300500001882e7382e6b153b9ed7dc7a5c089d0c80a28af4785bab1656b2d95d1c2941048f44c64718042fd3718052485cb7fe2940520e740ef26803d17c619e29e47c0515e2502bc8607d4e8000dcd9e8772c0c5355f576ae0ba91a38cb20918e22f650c1c84d0218410366723296514acd3dda8d1384e36eac630ecbbadc6b0efb6463deac6b0efae3dfae158ed9a6d1c043dfa2c566df43ad0eb3dd7d2be9947f440d4628817d334ed2decad975a0c3201f18e3ea3d2a125e3280778471a57311a65a494abed330bdcdeabf19410cf78e0438d07bb80f8905d4e8ecb4e0890e92e064cc9e7cb4f798328f8bd822de6e6c25ccb29f8de9db6d3c9c7e9ba0b2eb00ef711bdfa72b8eb1fc7719c0b77f17bfeddb1cde1a21f4288ecd0b923630dfe945abb6dff9de4f33a899015d67f257be86f5e3114d7e507c2a969d100e1ecda88d5b5914e822308b6610d303575c8ba8d683c461ce708eb441861033171b14310829f41ca9c85b03f6ed6b2beadee1eefdf43c8fed3a71f833010eff7edec500334ee39738da73961a9e9abb9431ce57f4343cee98485fd184bcec764d8c03a2c3f7eccc9f99ccfc9c9920506624386971f5eee3e0d5cfebc1af7bc1bf670cf9f3b6f3bbd75b9eec228d7529edb487db88f20aca8c836c7b99a9f5c97ade929208ef29fdb8d2704888fd8e5e4ac3c886b5a10c26e87b0db216c87b0bbdda56b31c81b6f8caf03bd3e33e0b0e37477b7dcdd2d77cb2d17b4657dcf67cf514eb3411493186197c1ddee848d261c9afb58d8cf52c981cfb5a613eb58911b12654cf3ddc448a262f97f406982b38bee59dd660aa016a8c46c59c982d2a9190000000800b315000020100a854302c168384f8445fb14800e749a446e589a0ac4510ea32888829031c810420800001863c00c0d0d7500df8bce59666d3925beec4bdb61a882495d7b3d5b5feb92e6d87a05c7cc9fa58032cedac95aadbd0cfce0590aee1c2c92fddaf236845abb1dad30ebe52b560848c4f6075651dfec7c3a99c252da8f457865956cdb837bac491542dcd95290378d3a9acdf43c97d3a001f03ac91839e47e07586d3ad5ed54b5ad2897562a1123fa1a37a49e6455f386daf0eacf5855fba783739d262f060a595439a234261021296861451e04daefa21c511eab9089b2225ccdbea13dc90a8fae39ab35140946d94a12409e580c14e25678a11750cdec6d4e73353cabe5a084c4d01293c81a5913ae26cbc690e86c2b7bd54716ae8a48f5f0aef77779f2c65d2d6585980cbfd9bebb3cc76bce626865418e15aa337bd1ce7d8193a4e6acf58cd9599c01d82d6183d3c150804e462fe2a6d6bad3d131e64e6440ad05b0e1a5b716b22b33979edca2c724f096adfb59deb41b04e360eb6e56c4980a810fabda1f97bf28a18aa41b38a5e646c75a89e2d58964008374125ba7c42116ee8572585d58f86796e86202411e089cddcb53d30213095bef61320875bfd3ed39ec1eef0eccc3e5b2a1f9d5c4d6aed23b61916c9a8ef836e28c5b8967a782088308d907ce422c519b21e9df8958036f12cba9c59ae7c0c525b572bc8b388e7aae011aca1f32652b177a55b108a2cd0fd93231e273c8a907cc906a132dc6969f5a3e88e487ef58a01e6af1b548c556cf7f37ccbbb1c7d6c951435c4538a23c13debfd833a2b84a23a843fec7528a42e9d194adb81c9fdf3a656a34974342d91ae04ea2f401b9c749ab512cb3d5c110f1d1bbf8f996317c3e1ccedc80f2ff60a9f676a52f74ebcdb315fd4b20df7bcf950cfc56c9ad80e7d9a2bc09bdc6abbe554119c8dd61e823cf319c330abd870f8b0025a32d465c6e82371e308a4ccefeba75c6be1b373dd62a568faf68ee63eae04e30f64b9bd22023dd58ee7099655b527d7a5ac2110e72ae3403e6c27dec612e706c3a6adc250d95b699d9a62d439bffdf15038b81af1af4c66d8b090f1bf4062693081c8d59cc5aa57fa8400c47feadcb619327785bf9ea582df026b0fb10eb73104a29c694835b1509650adde9625cd28ad611f30ef82eaed571730b52cd42b43ec03da250130516cef125dc758f91e6e9859e3cb72fc9fa7234ce9c579d091cf4ef0b5533881d514d3f61a93a8fd82d1be7d45f3cd161d281ba75da5596179e4e26d52f3140eb53955345d5ce8a09b1a6e151751e978299ac060f1f834d885b9a873db9395cda458a2883a9293050e0f1e015883395f1ea273bdddc86fa93b50ed9a12f083ad5cb37939653d4ad454fabbe5a008e42da81df0953dc29bb0579a320c29c44c77885d9a72f791a40ccbb95ad121138b4d97d24ea52305cb160142c4477169c8c918c3fda78eb06e2feac2e1e0090f6255774467ccc4583a5fedb9fb8a80fb48790bc3df94c864b4bdc5e6955ec4775fd2dbbd59aa29b69bb8f58b7819ba2b6ac65d41ab227fe07bd95d94b1b459b2a59ce02b919e44507eac3ec671296025045634e140e501a033048189642402569f19fd854722154191444495ab343328940a561161226080cee6643320901e14240837d096c5ab031201d68e20806282661298014146644e10065b160394c97189e55f12fe3bd3490cbf0436f95b50f28364121574d3e8865a837228b65a3d3cd4a9cd05a229259f458baf656ec5c2d662bf4e58673b79d4e1931318f79bc107f6d210fd1c4504c5ca549b32e075e62e3ceb0a3bdca8b24c19ba77b1471130ed28f7a875b6d3595cbe560fbd4359df1a4418ad0c2d392500efbc2fba72edb511cbab6c20bb6d5760123ca5e9b63eca280894a799d1f098df86605e61cd7ff941fd464de4566f5be4c5e33afc3d1e0b47863960b58f3448635c968b7922d39cbbf861f377fbd8fed9633f405e1d3a02c50bee1624cbbd6f11212b11ac80a1a75d728d8666f72edd9670cc4968e14d023eac1a3cd8ae36d3e5703a5584e863e3cff8460db7b002a1e417ed3fa3dc0b897b642bc834f4955e495fd6ef5459f3963a4341e08b7edeb06f0059bd586c9a1260873bc0652f4111be85c8c348f9763bf57c43892d180a396a2387179dc4bccdf071456915cb1c5caf07daf7ee0d07e43ca47ec325f2d63fcc1dc9a6f794acfdb0ff970224aa5fd87f73bf8bc529c236085c3d620077b64469ef7e88a02690a4e69a68d07a27943f2555bca43e4fb12da5c4ea135de13c961592d61b7225576a59c18fa18fff74e10ca7a1202f330994cbc53637b81cdb425c281be4cd98e15e88042073b9749a44e092999fa85199660c0b781153a2b1b237b5e1f4a4cbf8e53700a140bb3695ff66e1dad75e82a5a4e2eee916d37b0a01e4bdee07ae1246d57bd983e324cb566231bc35f6ed5865784ef6786590ff8b640a03a600cf92ea60185bd4e6351e515b0a324605e198823185d4b740eca76618489c3e5ca03f39daf7e806f541e2915882db52b8c49c51088b79dcf0edc042b71efaeb809f6851e877417e7543840b84e96066aad5009bf15800b4371776132bdd7406f1adbff2cdc112501e1c0336e41e40a00a06a7cbd5f2597e9287c838b1ed571f6c009c69f1d2237e1a5c4b3e0ddbce784d5c2eaf27e85d9a49c5fac2c51fafc707a2eee9ea153888ddf3db71eb702aed4c0af2c345f80a4da68d28d2a93b748b8b0e39657414c34c6355544c8aa9a24f3437d302aa3287d91dfd8b04d3d9091b61ed60894247691626985ba12cd4fb8c30e4e40026c840b8edf0a3d2abd064d74af90dd55b7f036231a228c2be054af556b20acd452e79751b7ad60f15688d9567516ecab901de4c7403e564f71fde157a77917506d65df77fb1efb6f99229e1b8eea8f417af436c9ee31761ccba2a50b56d361716a8753eccc2cfb2f663b76d485ed4d0e53f758021a307ea3aca82523b6aea911e71c992766b3b1a706e808b869299f65485779da779278bffab47bccd252d722a22a3991309fb6173570d9b00353784c97599d7299fe9c843cbb64d3c842762b97af4c7dae709a29da723585e996c85e898f686335d8a660aec4f8052c09fcc72a631c5a896ab1daedb67e2b370c361639c134726ed068226c6c18cde866316e2da664b08cab186f56f253017206a9309d367fde72f336a2cf5fc72884da0373721136e278b328cacb4e4b5120c6e651916c09e8b57189652b0b51abb83a79866a71cc3e560e597ad9ebb9523049719cfb3ae504bd4138de32ab41eb07dd09df2dd938bcddb8d5fb23b94ae3a8e3376dccfcc7328ceae7b70630932235594bb055dc2f9d80210c130bd2f6783ae8e3e1ad50149823a09e242e72b28914fe74f6223775555bb58149fbf37388ac048b10c3c5b8512ca8f14f2f1190ab0d8cd66474620e8c7b0d1dc540a5f2894469a5955256e68d9846fb8317b0cb823cc5023bc8bae204463375b8bad5f65b295f082b415c71b5d5190b0f1c2f72d86000bd19ea73a0a6b685393ffcc2a0591bf6fabf55cb948fc11b6cdea0359a832b019e0d1e5dd51efeb3ecd3a4c4a691ec4575898866a3fb341c2d975fa03bcd46535938d2ec75c7980821f21322f56976e52f3199040a496f32706ed12762ec9403d5d26b0172ae222dbba47c01d46191e560417836524d4070b7e92b841dd33d7eb25fe0797d6fe4724a0e2aaf017e7a19c061d83a10501a70793d69bfb3413fa7f7bc1eacafe68eb7eb582ea548cd914351e206eb2807e0d12b176d14dac2d138661c808d924d69acef0fbb389206ff2a68118b22ce62828d1d996c3b45f0764306990d1736549d896bc602fae33ee986054e581587fd909968ce88efcff7843b28d19f81c001d64b1ad426e1ec835b30b062cc6a004a54481e566b62251d6c5f3f32436862584e83b118b211f7f59126469b57d6855381c0df8b0d1bc001dd538d775eb29f903972e44626336a7af6c25ca52dcb031ca846bbbeb945f40ef5c89886a6b4718c297c517bac8aa46bb490494db4ce03384969f56809b6a09c4e0cf4fd085a24f6ded21fbdda5992456c036980e9f3124a7c936cb58540059f9610585949f314ec0fbb504f962b4d8b04da4c558f9403188f638061a2ccedb1c13693bcb87edb1763e44a9a43a783180cb7478e074a26f31454db232d3333e75374e1922fdb89fec018eb15ef0ab656e74267954a287e0f7af4d8236fbff318f4cf6475ee2b05621c51044affee1c61e36f42518993453c1380546ad4e60c61c478b2629d52ec9f37b612863763a2bd00321c8ce457e9f1db819a72611060c4cc7c6295d00f82042d143d8dc91d7f1384cd92c27e4f0ab2ae4f47097c4d5b5b5782758b11b041ea5f503d92b4b3c62dd232e6abbb0e1f345089a09bf4c1fef180a2523e87329458b2b8e232a5eed931f3b7837d9a0c1f7cddb42838eaf101a4574f0b88c70e85582d30d18f281ac15e2c2d8e85aa7e1cf7bc1911c4b41fd8ec469a75194e73b5f5aa27637347e2ddd851fd06b00bdf7d6d7cb7eca58f2d39014b613493ca7804af003450331cdf602f560422baf552de549db94278eb0331411ed60eff778859463a8e02c509005c39a4f428e4ce8b7c836d3abdb5dc513d3e4831358948d87d1da433c797228edb11fde841d48bb719b2fef0fac87561c20440ab583c7b182ca7af72c75fd16cbdfd9afcb5b5070b325df8b53a546a4663b72d0f34cc368d2fac44b7b3d9c13ff3ddca943c704f85d16ce1ea4eb9d29b81c282835f3451c14e44403b6c30008bcb9f315c0426d00bc39a27f8ba31a6f51b638c422b3c2825bdc00f44ac00c384bbf1afe800f5856853a58871a4619a6534d6c749e71dc2f3c7067f599540f5378e194a11376915baf6637aae982e82c342bec86fda37228dfdf6d2c15257bd5217dbef2204f3eb920abecf12be9bc1bbdae17714fa89978901ddc137187408fafafc6f33f408cf0b26bd0b1d400a280590568082591cb80653350dfa3b98030d57fb498e4f247d0f7a7eca8fbf17f77dde4928ed700bb825e0074e318e735e7b9f05e27bcf5a93a0ba1fcc657e253bd1de95e593f10837cad747fab29d8ef8a78f5e3ac485c7d4752b863c5a1ee23332a50ec9b1d2757a73c5a614afbf7863ddfe3af4266e281fd0bedb96fdd64c49286c7b6caf046341cb4160f7b0decc65f0e5ec7230fa492a4a3c8cab2ed2d481e76cdfc7bc72045906c5d4673025b7be087e73c668104642a02d7ae41c4216dc0f2ebc3d51d4987709533bb0b367f1475be60494781cb772b0458e310723ce63eefc6ebeb91634a2bba8f123980d2c6db0ca4109ffaca531f241ed4d24a2c14a5e7800c32f2e95ef2ed20be3b22fda4b2e070cc01c23cfd3d8d2bcaad6743bf0a52dd1dc5575f00e715966ef08484f8c645104a9149845c5014a2122b3359a9bca65add513a32d059360ccb23a8eb9974b0e288dc3b38b94c155562cc62e09469e322baaae20327195a9aae0fa394b7de4d9cb4b908bd25613a04556352ed71eddbb109a191546219af3bcd89eb8d452142ee8998bca5194e3714c5b2f07e4bb8729872b320edcebe5a20f3f773dde833f083c49cc4a8eef25455b2f013635bfb14e446072285ba570058209af6c2a8505a115079761a076ec5080390fe5796f6b5128aff6683f6e3887556b58cb6f595a40743cf1edc97e6a7d400cd5a95a1aacf5b6f8d1abe2515facaba8340ed96061faa5e10f835e42f58a0e2ea5dadd2b3ec43f495a20239405e5610c391c63a82d0824a676b9a0bf3805c361b33399beeaedebf56016e3d2aa52e3c1c9c25ff45539745590987b2863fbc721f80ebcbc2eb2fe61417e57410068cbce7eedae9d270ac27f94e50fc38c2c7133ae5e20f0ee336a5e08c004849b836cfbe05c0294d97eddac5a5198ac4bd9af1195a7f7f8f5b32315a6bbe05e2f90d93ba571f6dd349cd65eaedd0be7697ca63004eeec984e05ee8e92d02158e3415211b33b58b2d177e6df99f5ed923fe29da4cba43d248b9991f31a02b6532d81fa9bcd2ca6f1e048dd8acf4a8d93dbe207ac41abfeccb0d1b4f806ae1934609c9aca7a2e65101c518d669dbeea815d9867d7ce3d2f8d7b9b1992523c8d9cc99b55697ff11bc7aff5e15171bf99a5cc4bda22950c0fac7d309e226c75340fa6cb27885332bda871c18c063e60c77772d0a48d2656e26925e9a7c96670483bb51d512337d1abf54c5174ecefe326b170fe4ffd52672b7b277b6b4f23ebccbc8a45af8c06dd58efbdd43962956f72a73c71444fc90b8b980e9160b0aee7aa97aef85a1345025b14b954ebce1207b5268093cab7b4599ad9d0d18deebf61e7b985814a980945f0634167ffdcb29b0811ee347c1bd97d1d3dcba2ebfc8ec1d381248394522865ac4f81de7fce2ceae862e0787e56841e34c78d043669571c60966d1a35a6aaba046bd12bf4177a5be44e0fa4937c5cc8f312809e6b2d257f01b3ade17d9b3d84a82bae6b5bb02f222d0d635bee248a51478a088b3358a6b6bdeb243706910eb874fd1a108250b796919f3ed910df6d2f47a3bfed78556b68681da7cf57391e69e6af1a639b35bda20dc0542e0fa0b514da40a59006691bf155112652f166df45c48637e74426de6e7c45280052d2b2e362249e8298279323545e36d0b0e5be62135f3671f538b58b6bcb67a8b9ed839a245a31907e8b8dc3748790f71cac31942c2517968c9d3c205beb67ef90a77c1eebd762feb165b114f032b338731f4b0fd4a8d82a75a1c179a07e2ba0143ebbb17a0cc62db5567f44d491d141f66310656d703e03cd4046b71e211b32a80038a16227d4e1f1397451168112a93f13b4f250e05100a7f8ffd919c76a884cf8458bf580e7f1e11fe6061b7c80ad8089721df7effef5eef89b565fa1ea2e22a5f712a1cc9e1bdb658f1614377341d30db9459b28a1179d3ee5ccd4354110697b47bdba69131bf7c99e1b1baad37b460fe0647542ca68066d0b69940d913a3d41daf86353f0de3386251c74237e5cb5a43d62069cf82c3bdda506ff1e04c5248d47b10b730e7dfa7d082733229f486e2762031689056c6e78511244c415a4e1879d1bd353f090d167a1462efc2d3aa18a5bdae5319392f351110c5898613b46fa56be63f70b892b9c4aa3d84085c496039e55772d7573a42b0c8bfc4be2bd4f53c12223bd4c50b2923c1a64e6f504e42498bc935da72d628494bb0c966969eb236cd2956c6ea975bfa49a75a26018cd2f1ad21000d2666a094491102062d5fdc45026ab0a86ac391e483b12d12fe4a933ccdbaa6ff2c96b2feb738b4c229713feef6209f5fddccf3d06df1bbc970b3bf99b340e9e7d0251c76ba1281d5069a907242302ada53ee5318f01a7aa04eb70da5907afff7c6387c0c1281ee2de6739efd41a0566ebcf81d163eff968b285048518d8db96357b67678c55a1e36b8e3b75efabf00f3efe08cf9c3a7659bd68b9575751e8c2500aee182b0c06c58086f10352462234709c30bfd2a21205216ec0a52f71b87c7482306a476d0b092dc02b348750beeccf13650c16d314cd6a351a346335121e86820ef1f30434a3278a83b7fd296a2c81ad97a44c54c1ecf227ac40386051696b386e07eda59480ef9af929194b1b7cad31170b80f9aef82fbd28cc8d323e98876db3380d19e691289b9fbba8f3b96295d778a8c9289f34881cf426d301ffc9e7fd338c6d8c39dae3a82c43afa3d36735f68ad1aeaf15b023640f90e778146cd1321fee8e0a833cd69a628bd8b7842f09acf202409b6a44e858b5a522cac0b32794df77788c2b89f7ddedc3f6c1cb6561f1d65ed7ca88d5c2e1e42e9d5ab65a0409c1f16e9d40ec98c8cebb03787fb629bdb12c589f0a87ba780600d593ed073ed863c72ae40fd41b7be77d0ff67b5161afbf544eba53882e10434875a6a00842c5cd0db840f3aa85b7b76e7f942f9a05eaee574d3b4bcd32214cb4cac880e3ff748d05bd28f9b8a369e4baf2127677478459200dbb727965d85ae2aa3fec080a3d66315ea8fc368ec8df10a4f28d19c0701369b65ce1f7164968297be17295401a579fcd79a4e99755f10527d1032e2557789f90c0d614d934ffecc4c7e44ea5e7db1ab08145ac352dbc07dadd4a8abbf6c535af5f93b3f194e0b308c3f598a72f502ae35a4dcc286dc913fa3d20b0a5dc66c81ebd65086e6d8b97026dafd2ddcb89095c9fba6de0ec5dd2ec70d17530640505dabb9f8104b2d5a6ac29b85f6b6d686fef2943ceca0adfd5dd9571ce82361de15ab04b106ee5871c636babf52feb26cc87728941ce59f3c8e5afa49279bfb65baeb7b60373527134b588d0ef5092179a3a457c660e2163b1b23219523e0db27a104f0cb218cac50b6a75da84a3ffafb0c2c6b5a6426cf3abf2d632e72ee4db2815b25999081c43bed4c88838851040605215ca8b61da3adee43e9a0997146bdda202641f47972319dbfd97900a2c2fbe7b72eb192f19a13338690750f538236518c483b2b5aa450cb215f4465f26641d53939081414e1c3041e0b24813a8401cf9a6d1ddcd67882de7213abe5cbfba3acd51a823f0411ca92047bde1f1cfd2d5fa665b90f5f9daea1b9f3037dce3d98cce5a3b43ab3d93ec821988e70cead33f5b220cfeca428faa40ed984e7374458e28747b1b851164f7ee0e95cab4735a2e97ec6bdcc34621336871341936213db5c9bf5916dac78e6ff0a3d39193508cebc6ca77160877c6c56de27be33e4d59373d5f90e03185338bc520292168e145e9bed753de9e9581c8328a91430bc51ca9820d39753163b8e7964288edead7e1da3c97f7885d6492e35387a65ac77ed808644b2627d0a6174e04db3633eeff0e66bb55cf17691e0a5ae61cd4dd8f9ab18bd47f6b89e9b493886c4883b20a64cf0142cd7d0dd08f1cf7d45e4b08131d00e9d439dc6ebcb6d12effbec49cfd2ef2559dc27acb03a1966d051aa03641634bbd305d99e9960b91ca84b2d5ae03f469c3823acc9d4d22574ebce18c4d7e75871a1b0a086a01fd1adaa1e898d300180c48d546f3adf4eb427b94de81acb05e6281691a4f28817cff453720140ec47f8f76f9c673620c15e1e3c9bb0881d34e88421bd327f8eb1ea45974684a7563ee98bbea88f8dd9935d9510f4593e8d0640cbf19aaa0d5e2a5c9f0bc6ade5beb871147d68ddf7069249b51259d1f372b9d33464eef08837a6e2b08d0ce9fb86b25c7df4f0e20b843b07d23d80136674a992bcd6bce5750340ab95e900d4a46e676e23225c5fe2206ecba54a7de77b2e45856d92e6058a5908652427138feeb63b304268bf63758ba87fc2d794f6e3aa6a5ab511f91ee49eb1897f5ef474fc2105a9f86ab04ed76ba22ea326f8ea315ccb93099454c23877e16e7832e43b2a6e2427cf7d44cb363e8fa3606d93b1d0c3244f9c6b840d982515a76abd211219fbdfb757c7ed2c7cf5231f30df17b2b1bde3a3d7d1a4266e175e284f5728e00af697f09fa737a0a5a0e40808f82aa9f13c3a70275f91780a4e73b9279a182ad06dc2dfd30a2325b2a3f375add650a143df8dfe299a60109d2454031e0dc4e11f3a66742f0e8149ea812623c2682dfa2caf0131a1c12867b585188501069219b556c49b97f97c6f2468eed0703121e17b8583f0e6339c852b21896894120d356613cd510c9770ec2523d08b52e247f85699ffea76cb00bb0abef821ea776c82b7cec4240c30d0ca39dba74a3419d9ee1076d5263ee07dd827fee1981ae774b5ce94158a06143806079f0c02c2a38a04eb9996b1134d89a222e2da7e8a70734eb9878bb7b9b3f422b62ccc1773d67bfcf3170377103b1d53cc472db12eb1bbaafdeaa92b2e5674b0c21ea69d445df3847122984890671ecbbd37cc17796b4b39f8345bb3ed87a86afb7a59f2221aa0b7a6219ca6ee810f04b44cf44b9c09d3e3bcb80f44388bcf1ab067584a13b4b61ae4413543b2506d0c72fd44c11d7f5fc0e3e9b53aa6a9cd3f04cb88ddf4f2129bf78e8bb44b84a3f340fe6befdd28c672fd5563bd9962190fb65bd682600987931edee66e8ab7213cdf2050915df516ae7acce2aa7f423381f74bd03162379014d89f1174d8b860ac69b1631b018a52c49763764009fdb72797e1981c59b4d698adad646acc7eee991f63bff41fd9eae0ac41b13c283e15cd230164370a5acbcfb78e333188fe7fb6ea3e2a979e186e0adb14a3763358be2e455e1e6caec6abb99208889bcf797a5a74447049fa121112b727af36ca40301f0d79e04440a9cc72a6258c1ce656e331065b0c1265d2218cbd2f8a578e36917741dafb381697ea6967c2449c15f6fe620b716cfa66d562456b8dec13c1538d56d058d8b802206050dc4bac3a80b5c8438572b56f287b4ab43e143b8098f30fb076895ff0d7333e803ec5e75b689beeea451b906eab003b47b80722cde6cbc8ed3eef93fa125596de52e8b6569a80748c516648e3d644b9d6be97c5aae7f71ec3c6b83f5e617d514ef109a1addab48318238e6a94bca315224da4c9e318ed6970f22b508885fd6f360257ba583665335aa724e06eaa282cd41b1a293f42f401265cdd2dcdbfda18955470143989845db6b1f2ee0715a28b29c855221439b5a4327ab9e8e47fc40c62d4c8aadc27d79ef937c589fe0be390df3a46208ca2e90e9b9b96d609b864d8d1422cb147bfbb4b973f37749ddc9e205bd37d54e829e0c88cab7af3ab621013ffd2cfab565b0f869bf475a9f32376787535728f591af8d816815ae9e0d8ebe0859c99c17ae4875f53adb18be5c9e0a1ede77f03ff61410e939cc7bfd424b96916ac9d5c176cbb100257690d8f15abae26acb6af4bb61fd29936a8d3eebdc1945c9b25c6b8709974a4492c8e2492ba1d7f836698683c43a0058d3676b1ca04b4c8505d8f639e36c99cb980111623b66fe696466d6bd4834f700c7784a10e6cea71e2990d33d6643bc74e651a3ab3b0c47a7bec2454d0d797835fd7e567d88eb5bfa4e00179c61967495dced31946e55e29f30caecb1a401ebde76ce564aa104b0e0d532dc6c0151bdb8bdaf0f138aa039f28329b458a6532c2fc1de6b0623927b6968761edc037817fd624e1e0ca4e3fed83f84354ccd0f52854147265a0f00f075b43f50085ec4f080b2b14455f7e676b0a27eab196fb9b437c42ef6b69ebf51bce53e792a1c87084998d5d2906a1556a9ae6eb450084c7712e88720e5269dd97a018402c61740f55a83f0aaa024e0d5840f3fbe92a28c6f1c266463e5b3fe80559ffd3f073f22b9ae89c57996257752ba5227a3884ad2427b4bc0162993643c41fa9b592be1b4107190db1baab7c40dba2cc1b88db98fd97c8f01686e134c5c228a2628e54663e276851930985524aed7df84d262c0ea8139baef5e4d480aa0f4c0dfc58d0295191041b1704239dd95410c5a677e29df5c4f8b79b01805b988bd6230708d8f334851f45c3237a08c836b0df178702ddcde508c282cf37adf81f8fbdc0c54ed6a290a3a770649841621770ee45e234cc08f0878bb3bd04d38889ddd279caea01c777d902ff1516a7b225410806a81b60ca753636611cd0b2158d8304a7cf7ec74a76e66983f250541ab63b4b1525e64506050a2dba6208f65860a60bd9aca31a115ae8303a42f84a4350e05df729236181b39640e3abe0461bcce675e6a2145bb6027d6cbd5c4f042d6fd9e23161ed838453b538f5d1f1612b86be5dab77bebb95e32961458706d19119cabb2149584c59f89f44882e333e619f9eb78ff1d9e9b97c3895c3033e46a036f71bf72de7720beed7bdcb758be7ffa80f3b5356c7b724f46e8c6cb8f7299ad29eb7eead7782af6a66f202ad04183686194e5acac66076afa479582b57ebec2eda64b8d22b81fdb7a4a2dccb0ab344cc37384833b9bccbcd07b76291b022f4bee6fb67ce04453e146c758025866385220b066c6136df9a6945903b0286af3c4d79d88c38420c83c1fd48a4f2f15ca8d2fc014dc35d1146138875ed79233e58216ad784101556a1371f7bc96f7e356311d992503b3b1fcae5879cae885a79c9779c7b5cb9d433db84437af8055503ddec605bf5bff2c23fe140c3130a7ed7488e93f2e1cea3df10dcd9459527e4e504a62ff8feb13b19f37585128b51fb5d35ccb3329d8785b1ffdf38df02b7df77df69c41952ff4876967233bcbf198f465ee31e0ac300661c7d332b6f93570d50e30c5873a2c8611da644cd8116468212b7dc88706f45ef53348fb0deca48cd11573d6c930eb52196c582619ee8547d302ba13e0ea1345aeeb7b9bee1c38bb382697402b6105d59ae02533bf9e8b99c6a7d5c52657fab743c50cccbc1df76b736d52559d6a309c4f2d4af0f71a2648e3e4a21efbfbd7ca90e31535749a32af2376532be75f6c9a2c3917eff3c33a8eb92daad50e923a20f14beb66feb89bb2a68cfd9c43e8d761c296e2a1c77ae45f4a76b3833f47681fb9fb4fcc946b2559e3fa80e2cb07d8b2cbcaadba0b56e6a2c603d5323f20d981b504d44b5445f961697e491ba2dc7b43361313d4d34f0daf092856c4312ab876af1e927d4263d13e138df14dc504fc9236cf0fe4ac5fc91ad215192866fc2fdfb527085b1df5dff0d27d2cc70d994cc4073adc4c4c66f405835c6f1458035470a0d8d785031bf6ca05ddfeb41e9c6b49ad7e4f46834d67e45857527474603aea0f5182583c5c28a2eedecbd8572e2bdcc04867c92c20260d1a92480a75bd8a38ee3984b60c16584a27110a4b50c6db94e2b131c528116818444a74f3ba3fec5b6e0212ed0fbbf9bf4d8314ae8011f738ddabbb068adfe7e02f08f0fbf090dce90b39056656bae2536b57d1487c130244e69cd52fb8fb649338147cd6516983b21f894c48a78daa8cbf0d77f1a2d4f7776ddb04a627c1bfad0d04cb5a78f95c58ada0188fb0307cc4cf2a84c85b835f9f343c0433bf2e3a890d499775372accbb12e5ca3af16844d1f4fdf61ba31edc42961f15a4992364f951cf9029ba12c898ccaf1abc0f80a7b2877e78dcb71b092cebb04f96b1803dc0477a0f413fb214162262d69a2edf8f0b70b39428733e4d8281f2862061fabfe85bc085485969424828c78db2fd1d38babe49322544325cc6fea7582c5abea9dbde1331b968c7fedcadaa69fa8bcd9e5803281c165c24297882ec3b21758d12b0b2dca2b3892207f6f29efad60bd6968998b989a67540d5db4a1ca28dacf3f56bda2b2c1cc0c51bfb9630352c820740a50fc9a0da75a0d536f02cd65a389329919393128268fc2c2c05da503e762e58772cbb777d1e203b06d835aef401df86827b21848b1ff2f28d2c70d7735ceec09d5cdf1d59a05a0c7f014689049d2d94583a1f4e8414e9795f95ead3acb32595e681ca8e718efa8cfa416402389b0b6837a45e42df81bfe57d6b11d696a606254077b25763c738bb9a1e6e554c11c3d76b601e75bc7fd2137e46218f112d6ee780627532797900c55efd42e592bc3c1dc81b456334b7e80ef117e280077f747e1be7be5c92cb3393cdfbe33a3e6f88420555fd36991edf758c5aff3346cdb9c3512bf5a2bacafb15124a184a3aa51f13f79da86b6168a2ba7331c31cebd9e21771859b5104b6ea9a67d2dbca06ddae1d9ba88fe8e866fc52009a0b43e6bf67184d802c1175f78ade910cfcbb8dac64c2ade3791e3bde831d83ac61cb3e64dce5ac3b5126392014129c45ad3375081f240f62cc54f2393ea286a87efe45b4cadf8a93882c0f080f5430db12ba62fb292a89ef1546cd3c24a90b0e13c248f44dc52d44c2088dfa9488a2e06dbb8810a2d42fa47ddb4e736e7775bb7ff4e279ecadfd763dba3010c544daefda8d4b6cc88f96ed69d56c715e090b369ce88cfa37d2d13fcc1f4949fe208b9371c32d82b94290ea556e16ef5fa6ceb36fd9a301bd4acc95e61b196292dc8107728add0c4ed70eae4c82d37e8471b834833dc9ab1732a067a43cbd24425b3f3eb0fe2165e5da40ee0d28245e28bc0981eb787e1b9e1e2ad826b996c0082a4b63bd9ffb98c11240c9abd27c6132e60c3be3e71c5624abcaf700a6131df3fc93d67fca9897308ac34fdf698bc984b11e279f13802f73d6b88ae5e72cb7829a622be482c806392c7bb12a40d8bf759b9ba6db951fc4eac0758816613f41b96ec8aeca7372e53037cb9641513268a6f40a43e6474dd9b53fdb053f92a70340356e00410f42a9d84090a0da62ed987abf44b9e3522b66094b18ef2ffa905cc6c40f2901622c9ed61002a00e193e62b45888cdaab4d03d350ed4ad696c3ec5da60eab6fe7e15b694040c0d71462dd8d6f515156a2d63ba71040fdccb4e41d6c24dc2a177b9230fa295c106c652908c889163ebf234e1bc719b3f9033c60952f14045eb54f2ea4415ecd7602e95b3a7db72d9a65c16e4be6ec2b7d48cae2d67062c7637e3ebba12b7d6d456dde406e806dcf7178b4d9dec2d60fe9781038b67474bfe86412eb52ae22fe52d3df36dd6b7ba994b6de2a5226b5b4f61021f7a57552b52737edad98b6d2c1ac157954f698b46ec148bf5c28a478911eed848ab4679a9adba7cf2a202e83c6915291aa7fff8ce82f6907b8f824f7d7bd27f3af0a69c958e9dcb8c336413ea7c96e549969347cb4979bcd2822b49e85d3c0e8590d0012c6361815e0cb0b0eebb5d2239d8eb9280d846f1209c118751b01e381ad8c03662996d1149d59d275ce559981fd331c9aecc7c9722590d9db8441609428ede4789b79e489d2310ffa1fe5057dee0ee7174b9728e0c5821886430e086b80059cd78c2261cdca1279899c5c2725dd0bc98f51b9a686fb6e026b4374238002eed022c47c2c84c81c968b65ea6320564d2816461e391c76b4bd25e203aaf56aaea88a8d5462f5533fbe7fb4c71902c54eeef8b86cf0ad21519079c42ef186d7b3925e45dfceb71dd211fe1f2a20dac28a0471badd99f3a9190bf2617a06b3b60df2fc1751c7cba35f0b571c5d2f9e16235afd78361e8d4de62616488c3e62130fbed876d26f39dc7a2820bc704c60a95b4802dd5e40cc68fbe98d460e25b8198c8c4aad07b9576bd78c46044f83aaa74e0fa191235edd76e970e06c1272db5cdcd8687a2e9b0f69951145a63af59af7d04a889d1d32472ad49ff56a10341d2c451e683f4d0a42445eb3834543ac68439bb27944f64e2c1353ae906d65e39ac293f118d36b85f6b222b45d81ff084acb1562cb916844345e24679f932033c56fa1aa53a8bc67a1f16aba669a4289f7eebe313bc494d2f0650eb0ca58a5b114044899ef211b24bc12b62f37ac4933c794395194d70803cf285fb3ec6c80dae9b56e6980fd382cd735f14714111649b44955d45cb974ae50d26b40bdf18cfe93b42b2fb7fed64f863098fcad0905eb970ced6cdbb2226c90ff7c09acfed2965fba80d489aa91d0bf6a519a3c9c0e75facf4ebe10d6559c1e00b7f19356faeb3f4fdc29ae08e9d21c4cfbfce2161e596869e78e39712468e9c5f124522aa39a08e7db20f5965c478e1d20a8c062e1f9a94a9add000e89b3e522afcd7ab6ae238dfd6128d65ced1675d6f748ef3907b03825902c94945325872b0878e5452c568c986595b8975ecce930e20a1f32b98ffdc9fb04769d5d8adc42101cd4180a6c2ab28bd2be1e8d794914d3b10ad382ea48f0f8300584fe6a1fe6edfd4488d21574902062d55ee5ee3a2ad467ce1ee95150efd1d733d253fcf4e8b1345542cc96f64a14d5748eab08e332e21e62005fde3b414b71f7775412753b1e04fad74f60e444994808eeb773dd63ac8da170893ba6244cbbf7999c4c5af28cfb1a7897789a8ab4eac0a87830d326056b3d9d40c1e2297d28bf4150879561b1eddb69098d8d39491fd97ad002fd32c490fdfa8df18ebd4aafbc8c2211591a25d4e1f336fc2502d4bdae3ad8295d8819eaebfa3fe1f740c2607597b1f592378944fd54c336a530bae5a0dcc84f90ea24efd3b0cd51f025cf8606d5ec38f4b9dbad4e6b9e376aeda0574274a618a9c3826fbd4a2aef3058ad3cade38bd7b12ba9964ced3aec1aaf3983a00168219df8b27a67bfa5b9a103f54e447fac784ca9d0ccf6cbf9d46f16b09fee0dc8af9bc9345bfcaf60eeed83a4e50d34c350196b6a2025e52c2893f54be3c194452fa6a73dcfc168048c2172bd6e22ceed920dfe1bd51b76cf890e3f9cc029b3f11232b893487add7a3dcfe470921e57f0987a7138978c59f3d5c8bbc89488cbfcd4e914a770679421a2b09c60818feb3a3f7fd3bc34497f9f16c29ea7a397b788a453b0b2828540a327a70b5aa2c688a67799b0d590c5e5dc17b505f255b7e369be5bb9fa24f31804329c0c6b3036e7bd12ad5c2e36d0d8144e4979be3a757c542839e308b6a3b5e59edc310dadf5f2dbbba941dd940ce4d72dd64994f25e72e21857307dd0d686da0c7c6cbc3082dd3067a8c22056fb9cd5e48efac497bc3d0e6f19c5875e358fe39c82c4916c06a441d4b870675200a190712701b5ead2420f65d15f4849024e46512dd739173dac19b125c7f53924c0c931c5bc914df7de1312261af259d67f344d63ee3548661f840ae2e775a082039be8feae024a1f723ebaa1b2462c5063b03d90492e0ea4c06f8d3073e9299be0e800689699ca5d19b71d57640b5a66520a3d6a59f595d88da052086a665428b88d69a33253cc0a80d4ed45fc019ac1a74a9ecedb934a873511194deeeb336afe61236632e388f4b1cda388a17adf0f4113c7092cdf4875280584fc040571e27fa313dce26501fa1553d7ee27ed818a6a753c545cf8ccf298b91599741192f4e9679d6c8bc6ef2538f056958006fa8491edaabf4dac0e59892d16c9acb32194c1df9b1b712b4ca96536323471831ab51626126e2047ea10a407b6e8a40f52c961ce041a9c33345c2550168cc21cb2a5ecf3bf2bfefda61336d2eaa0e9ea29c6914e38f784f5c5d7d3fdf08d039f87426b0f0217bc8dc203a646e1158bb43189f6d2eb3f892c1864b7239a761292b7f1545031d8580f739b0a9b38adfc6a00f615a1e5934817359e5025b145be670d6632428bfc5dd754b4922cbc82f8913e3ab191e4416c569531b394a768089d19372f1bf0724099515a2144015d24d7303ef1e34a1705c7c9e07b9fedfe1d04be138489e079d79d298832aa2266dd883de91afece00aa7705ed235423317371e39225aceb3a6d8fdc7e4c268ab29fe7c9ad5a9e9004fafb44677223bcc7b44843753a9d144f429e63de2f08043edbfe5401ae539b9c892171230573d1839bda7f7eb59477d38bf9bbd54eadb34fb1b7ba4030252558b328b4f613a62972b60b55043552a831af0f5e46e6092e06c11780c342df58c3ea090c7c68faf9f1eb97078e477bb231f2823bd3b9e3394464286a253072bf1e22e5994128f077d4e8ec90d5806388a9d5410a0d71d3aaf3c0eb4751dccf556a95891e8e1808c16ec72d8daa4ad35095380d92ac8978fbb1ab1a1834f6a295a993c43551e57e94afbeda413a6d417905c2572f10e064b483b38668256b73bdafbdb5c1bcf3c0846e076481453722d7cc49eae12b50f83e0ba0d907ee150ec87b959e018d8a4b026618c3827275e77c67d7ca3436c14e5225f079e31fedcaa50cf569bcbad7ece7018ee5f9ae96c4a09e2ee943cc921e6d62c36bc2dcf3c94dba6dc239d3e73e7ca1ad5d205c83638fa708919349625e5dbd1a2933fa58115c087c24fbd4966b11b1f699a3e4e9c9086a2db1b388ff36ee2d12db19b835229eee8cbdd552f25f2a7a3d04345623ef288078643604f93a6b4baeda2aad142c39675279440c64e9d3850c709d60e3f96d6a4324438df866d72f25812d90030e19c16a098b3400b5c178ca5ddc6470b963128eb201f69bb170fe33f008c79eb5c79bf7a58e78366c665878e18e3c0fbe56125e056c1887279b6c8d7696074e0754f60be23bb785ef2fb0711c64de91605452cd04c8bb66f4e11351747a78a94327fa676cbffb5013363cb1eb82284d933cf78cabb167abb251827b0638fc47c667c06dc18b4f55d1a2ead29463e11171cba532cdf6bf81faf29a21fac65ce36d2b71d0583e3d1e7f478589cbbc6404a6b22136ba40aa714855b035ca12bbacbf66b962f66c5c7310cebc7212f1c5e1fed6c85a93742d2c7b87db7e3a7a00fb7d8c32569a7879b174f0145eeabcc154edf5b1125f57e9b758c58f20bb7ae73c490bbddb63a239a5f68aa400713cfc5525f8d5ce08e7460d699ecc4bf09fe4aceea0634f8ef053d8d65d8020df2778146c698ac9006fcbd8015c9111bab854bd15098dd9a97f490fdba6f043c0640b884616867489cc5780335c0307d5fda2584feda67f01e5dcc5f666c174c989223cfdbc5571e904beb3726050e713a88a9c24a35fbe68f0ddfd1e251589006a7c5eff23c07485248b8caa8b1cb6825f52dddbb6b2aed8afa98074c59750c43d6fe30b3cbea19e86ddc974b0d2e2ad33655afdc5cfa5faa31480901b56d21ea3cbe146903950ca1be789805595a7630465b7b85b9ae11ce62f689c382e9c653d421d7fc1ffaba72ea43fbb744675b86933bfc82142ae5de7f5e253cdf5c95cba55f2008c2e2daf7fb75f358ca4cb17ed28ff3fcf463a047cf312409834e2fa7601520be2723a597493fb54a801f4a91705926c357d5e574e073046ab86ab368e79a18e70ef9a75e08a20441c8b859cdd73b650acda10675353e7bf547b1744a0e45598214e9afc638d770a62853bdd50f28d24d7690f68da911cfb4c03555d065ec39b3258c19dc14456a69cc7c7499ca362bda89b3f3e2e67a48b3444879d5c134452ea5601ae9cc7b0432577fdb8a6dd76e844a3b140d1ae066ab2aae0189b1ed0032a36b010f39b392dd44c28eadb81c4f1815ac2f3e08032b006a0e4304e6faed94aaac514dc5b9df944d7eeb94e5074283e2a645fc53261ac9ea515cf8e2bcd81f163aacbd6551d3484307f33d264853cc8c795f60ef6074296038ff154c1a3b06edb04cf9d83dd03c29122e9dfd0264e5d4cb4fc299a4059a1f4d2a71b0e0bf9bd92c517714faab6af7515d923cf11df49a75723f207139ec1a36c6d4632079c00fcfaa1e07057f82f46c4e6be8570a57858f5df9f4cfbb24b6c47a549bb18e24ea52c02bc040556a5c652189980940f463f85620cbdb0714f709e70bea0e35d5c6a15f950ff35b6d130958b36874c364849b1cfdf72645a33146b0e0c084419b4036ea023cc60329745d14c1f6d9c222989d2b326d6443221dc8c443dde7b9c0e04276f460378ac0888aea94b28b25e0e99bddcdb420d59536331069c8905fbe2b89754342c15da910de291237d1a488a037119787b4f6af9dd0e5c443cf73b9c3166495e6e3c4dae7c741fffd2fcdfac9cb2c8d22a805fd8679e8ad38f532b5d286987287d793dac314a48ad07e20fa40a4cc4b04cb1cf0e308d1e83b4c73d134dc2fc6d2356f606b0288cd4f7ea0d853677be884f20584fb44fce4124fd3783d98e08ee3d1411493dff5544708dfda33c9d307c172a3922a1e5287b72d0ae26aa9dc1863912d7e44d70c87cee09f390c352ad7b9421909f35ab0cac4f822e163ff0402f7de9bad7ffd9f08e38c751ac925f8a1405b74ad48e23f1cb24e6b0037d75690fd477d0754fdbefb89008804b9f2b4ffee18214290ff98b27d79e88fd9c86e47b00d5a78ab10fff21c25c38885c633879a0b057e6db4a3ae1a9efe56bdafccd1e8edd892bb64724653109df9298c62fe625839ee5b26bd9ba5ce89deb644027519729178aed6a666c4b19cba24863b197809b2752520293fe78ac88284e105cb13e77e99c421c46629b616bd557703233178218f2ff0f320304c84b7451332ded852c9d97b6138c87ea137e675b0e9e598283b697b27490962b75d6244e674bfc448d47efff39bc346c1f8bcb17a68714070a0f645b1f6a3b1a687097ec800dda69a1b8d7ad4bab03705ec6d42aedd1d03c8352dabcc2ed8946d543f8fc543b8e6979d4da9b8734e9ec0db3a04c03a9ea26d0a9b29716ee53a1af30916837bf7914e3cc355cfc57178dd96e668f427442eeed6ccad4163b6d647919171bf3b9297c623a6c859328ee66991f25637cfad7f948b7f417c2286dee01a5e6f03692f06163f3837b801b48cf27ee2faa2a4153081505129514ee87b2d61232b71f49175114a0dd711b466e1873977db2e4152a2ca1dc1aff1821327f7adc2838f0b66ffaa1874192e45499f634074884c20496bfb743ef57807732c3aaa243d209f1910d5048bcd1af5e651f02ff57a290b9be116d76b162a0f302a83898c74f764486c4e06f211c590041b01d1c2c0c31be489c9042bd8c4f7d57c3fa9b14b745eed04909813fd74aa0e9c71626466d229a9b190207aba6a9990a69b558d80d6986b54e8d48401ca9022396e0db7113836e1a6d49fcb289c513e7ac65b911d1794bae628e52fa6425f47844143d39c133feeaf1c2b808d7e2f40016c8eef23d5cbaa6d450c69fd953527c33c0d6644a2253cb090d23b54492dd0400bbec33b6d08d0a8b0f5dce708e122befb64aaa694408740e890331049bc9cc58eed84e97a66e380bde69db5780998b6ee9512205f3d78d6ee8c1890dd2c0f17cc6dd3ef81ead84a28b392bfadb7c69af11a5a0ac0fead2aac6f79001d43cc0c7fce1ded40a143114ae82e07538091060013b090567e56604509659afaff5130074a44e989a52b237c3da1dcc4c8d30a83873df1ab1b85a68f7172df190673d28860d888d48d0fc7e4575701a775430893d816462647516a22a4f91214cf5fa2e02528297ecf9d9dc95580925448b2af3282f45c3a475742a61941ab1a675c9f4b49d0b0d8adf32a2fd65a55be2b920b1cdbca5cb24c289c8c32c8d227ba5962685d0fcfce6ad38e21981f0e125b304f3b0c7eceec036192e19e80a4a031aeb529e05dddd1f45513a18800f7d15949544e6e8b7c4f7e768582d3cb8cc6d1ed895b280125808326d8e886f704b8acb9e70ec256de5bb192d48166713d28286c0edc05311096b9519cac0e1114e4e7c22f379c2560babe0e231b48e07119d20a7674397c6b398144bde60a11fd1c3e9f53104cc94f6829fef1263a734331619dbc2a7258e7c6c1965d88f7300b3b1df7fe9ba8851bf9c8d9549aefead01b2d253a864010d283657ee09e4f7fe1e2d9983936067e9a24fe6d277bda962b01c48bd5376288b7866e99a4d21bb85357e813bf4558a6c4a474765c67589dc4b8dff67f30414f14d96453f52dae3698a24408ee9dfe190e7d118200d630bde4f040362f65b1911705e65842fc172e18ad8d1af26c485581ab5539aab544d6f3ab62f9d1d93560a887c51c371e3f90aaa9c86d2d35c1a61a038f54f7adb092beaccb6c385c7da61dfa38c64ff1d34a2ab78c8e15762c5cd4c684bab61318b0d32ac3308de167b40fbefb33e57c653daf452a2ae9bdcd67dc701aa1c0283cd0ab690049918c8e0c471a9b353cc11f9ae1b457ab05df76015e08da6871326a9a2b092604ba1aa8604ecf5ee1ed4cd2b3c9fdd0b40f15a2ceafaad6bfb9b0a1885edc7c9d7da3ec78acf423b9d2ba85847951c8c31cb0f7470b513361ccdc83819a4f14531af7cf7c4ac7ed5bab048a040858f3dba3b90f5ea9dc566d20b775c8e6755e40cb89dc397e8b5af9529c0fa752a1621f56cd8de67f88f87c2428bd52968ae844e508bb696883f14d8d1191b5ec52435034cd0720633be498c48d25075d7d0c6c8d4c1adcfe63685bf59dc772d9d613b75c69cef271d25a3efac5d624f3fd4af55c769b05bf8411ce9a0980a418c0142a57638cff8b48d008fcdaa137eba5e9935f4e48c70181e723dc1e8cd78ef5224d6dc42b10c37e9891a3821304abb68715fb602a29e691b401f64d313f25e3ac22be62b4fdf3a20d91563594344a6ee419132b59ff1ffe854fa03edac4f4420a657ed61d7b2349366a4766f0c2371c1060be79254ecd7164585b15182f31db61986fc6e0dff68082e21521dea424cd5e39198bc65e1fe67ad1649d5d39367399aacb9b8f951cddd980d7aa0467fe336954cb9161768498ba627f58699f78e6dbad172166b91ee63621c8bf8dd3ed4768c7fad7da7c659020a838993279eb7765c92252df583fdbf2a54d57e561b2f3e735ec30bc480c283ddb32059584c0d97178f3e6dbd70b7d53857c6cbc4822636e11a89c7d949cd670c4dcdf424e82557b9f3b9f66726273c6bcef84bb98aaa880b4a527a540c77af5bacebc1c4b12d1e921040cc552bec017669a4d3198db52bb91ed53cc031a5c2b7313429e31150dd9ef2a0e5b53f85159018014dde3a1eb174364f4b7ac8cceafc22829687c167ba215379b027ab4e69250a884a072046d81c6510065d2fc47e7bca46b48a5e80f3dbd3ed5b931fd052b80458dcae44c1502d0d31848739fcfc17cc0fed87c5fff14537f252e394276fc47f7dac7dc5008e3579899c84beee0aa98e0a98e6e05a2b9859ce6208138b5f888a11b9d945a3054daa6b87893660eb9f1c3647f18c2318fd2de2c4a80f3ea87ef2e10600e4c081389ba2397a620abc48160491d885df7790f472760e5c9642083247b23aefac829d736d727f40a34d30cd7c6ecf1069e1b865a345de5fd100d8263d47ae7aa617cc4713416a4909f915ccdd015684332f2fde25fc91fc8487ad4537d54c68398e39b035873dd7cd947ef0d796ca19d211ce539e2137912f3cac09997baa6362e026057a6413ce63595efa513209ea8fe55c2a6b896b392982ad8afe0bf2b4cf20b75949777c8740257ca4ccc958279e314988950e651f047587ece42f4cd8972f214801aa7af9417dbdb27efc2ed98832273fb9f141443e5645e162f15998b8f3f6e8de14f386045fe1030a713b97f5cc569f187d4df35d56b80ce3471311010aac428bc834ec4e0ae3d27dcc9159cee97886445637a131470e708000fbc4b216d623ebcac3f088c2e2af8e852c81a31a74c42340905a4902a04850c5dcebe65c31fb52bda5ec7037b0a3c895a2bc6e81990f0d788e78b3c090f28f260b4b5e2f3212fcc2bd048dfc43627986398aeb52a13b84e951ea744f33769e8cf67b97569d64ca613f844d6aa0fd5a330c9569f814f6db520106a02fe05717517ec868261978544f20c62a106ac4efb3535ddb292e95f352e8568f04e5b6ce79d5fe7934668ee09aa19ee12a0b63c4acf36b10266d2db2f661036a5f300f8da96ec7250b42fa51ca0776fce5d63d3cd62aea52c00587eb202f6c447a33e03169b5e01f0c9c4ba0e58f78e4e17a47fa1e698cb96c085f971c6d849d9b359c6dada52f0516135c6d7ebbff3bf235fb8193802dd5d7ed1719b1b52018d2348fb077ee932695ec39c6d0c4e6da989726ea9d74d10ba094bc8822cd1ee19065097ed921a35c631d24309068e92c356e18198085e32101142ec8076ca4f152371063a84c539980826bef0663f4aa949e182598bdb5e2b4a02cdb51f9875acbe273c105711014366ce484b292b5994ffd1ebbfa3b5066060a4a84f2d8d5cd11831f03e5e459895d13da2b05d2ee38b0b13863dba5352b749090c1ece44b0e297f44ee6666fe6ab1e3868b6f43d6610baca9076b628dc5a1d5c4f2fdaba9e8bd60588b24575e703749f880ee658cfce72d6f797b8a10aa2c48d83b9603c2b79eb53d57dfc4d6f76d67763513124985801c909bc98122fe8a219d16f146952989a69242533a91b7358f0de9cf9a1d8e97a5d066d610992824ab17553a01ea5562cb6249fc5704eab1e43c3dc860eda0f20206f331a31e1cd01d4833750f15dc222ca93c92165680e4eb2cd1590a5b4659fd4af584054ef9e276af23550695e98bc1e28da6c855f4c654485abc45ade777c303e5eba24c33afcada7d512762d9dbb211d9428814fdf725f23acbd77c2ebc81a1be38861c05de45c621cc47c9212619da2aa5f15e021046ab7fc2d6f5a1e0868f67753222276146740c6ae72d00a3951e7debc9090a8fd43fe9985e857cd7e5ef00308ec81e45163e5768ef5e36e2a3e081272d7bec935f212edf2225126b12cf3b50fc3efa6461bffb09fdc551ed9e2b70d46460d90115681df9da4525a7bd5128b29b4b6669223af172e3510ef668bef799d1415c1ccceff5f3ea0538e58debe365e3a642714947089b53373d50224104844b5bd3b1d4cf2518e965febc4e9d91ab75edc301ed50ead77529f53854266c8a916acfa88a6bf124511429ae1c99cd03f5f006efb9d8dcc280039bce31f6a90b070da7e40b2054e8b28b343b6d7dc586be20072c097d3a3545160b66e490519fcd9c0d24029c539cf780b7530885d114f12456104008a46487b3ae5a79b4a12755cb03f64a3f6f188cd04a93d986960301922a5eb8b2d5f072bfed7752cc89d2c7015ab60b90e881b8f19e76a76806c528a2d97a75cfc30c0f35596278e482f368c326b80c8901372d7a56976cc22104721a014312cf2ed5153eb925e0039c63c039112ebf52c703dd59cf470d12cdb0b57ff736b01005d4a34c6593d1901aaf5f48a5935e6de4e23cf5033c074ffc5bf60a2051a3adbe4457a5eaf8c519c46fd2332dcbcb3e48da89b245fc873c04de50c6975dcf68e719f0f2c69a7b5f262ce70e1adce2ca11495da146eb460e134bf91a4f4397c1ab5660f8c6f4d20059aadfc7864f747722488fba44f6746ffd4870e994a936eafce0073bae7b85b96e9e48ca20b3e9a341babf1afdac4cca4a8951d9f2be92ba6eefc68721041c04a2562997cb8510e7efd9a264b86448577eb916dc2bf0df83118049563f1ceffda7db8c76bcf7d5e308c5c1d22226e907d8ae159926691a0413633bbdee44c6f715e6b8b92acb54069e348373e4719127b90dc8dfd16356d3b2b71873351320c163eb35ac4ee4087cb8dbd0d47711b8ba0850525d5cb00804840856a770cbcc3f744c0fa19d571bca02264906acf2a195d4091b69292ac2681cd6210d6919799e75739da0f994b3daf96511e8346fd9588bf6a55b33df54337d45d58a5e6221b7d64a9474bbda0817966a831840c6e3211583a9e9d2d049cf80f5e073fc8292d8f6d6e90e30f5a713678571464c2f99032f7483a528acd7a93b21c314d3958701b41b1475a8aad40f9013caecc1e3d2f5f5719580d34a2e7364273c190899924200acea6760f25a14258a10930f08923068594caed656825659e14202682bb834e7adb7fe31792ba6abbd4737a329ec869329e633f0a3b3e2540bfac69219d08a87af69cffe1fbbb2dc22a7a99134d1ff8f48f5857af4be29d5b50060d7d49e33aabde662ea1dccef3703e3724f34b24bc6b389d673d5dc38ceb58ec9c8ae76778fc3cb81da2ef2e27ed6fefe2b27399769babf359e4793d72496aa137811b193dadf94ab8b0c61e1250f56a1246d8d95307039daf773b4a22855b928084ef227172207366ca24412a93c8b8e9268ac187c0e951de70ac6ede0cf23c65053f084b5b4cd6c36ee2ef4d8c4207725d6f14c4d16d92c19205880861051a54ca83c2d00688665c1789728887b4b696d501c3c009ed74885443105482131ab2cdd433da53c19ae90339363bd232743c13b673b2d323a440eafdecb540a048ce8a289c9dda8e02a6c2d541e0aa171137a8eae79ef9ec168cc87e77a65d8e9ce626f65f1c5cc161ec97487983845b38867950e20f18a4560d5cf8da385e9588b2f4deec198d1064899d9a917f98fbd78f94db02b0c6b422f591da51d5c65b4fef8caff005a55428b295dbc0f79cb642cbfff02c3d5a9db381624770acdc5f91be2742267e4756af8f758cf5c78263027e468c2b00c8ad4eb531ab844f712f919546386eb170ed4914a88acc4c11488327b07cd61087e818e6fbf3035a3d2948d5f63fb0023d5a89c964a115980b9ef68abc122020130e043206dc6915fa4382faec64c09a7ef71bc58214b46b1df930e7c03acd5bbf71c6d6aa0b6e684aa0fe09a420b79c2ca1fd5b0e21d35edaae35d3461d32fcbf655557570dcb1b5cde76871db932808ad9f8b83b3a20240877fdffe56c4b8f32aa1a26cb6c0fb9e2e7ed29a640f3cdcc5745e68be907909671bb5a1eeb1d5d27dec8753d2e7b3e3ad262c3767a5daf04093fa2dd4fd134553597ee708d57642e8abe795ff7348f1186a1054974da4a03febaa7378b91cdf50ae83f7e449fc77396b74278d71a4c94120bd3bb85671d1dd3a7529e779877cc0f3940e90f97bd23744193006f8de737f60cedb89cb0b266622c6f6df916636f4db642788b1e21a9a76213a3e4ad9873cc53d39cea3fc7cb965a113f959da0686edfffd679ca35b81113583a89e71530cb084acda4652a99b3664cd02596c40e20bc93bee53d2437b44e6548b7ac1bc3c86c98f7de450de9e23089c888b93e07c10c09d6c147bc7bb683d8ff171062d20da96f059583bce638114f757faf86405a1d0d85d9db207a7dce7e3126c96f2a3e61171e75aa275abe8e614fd694bde9fe85bafa6f7a9ee4c316ea945da4fddbd6d579c256a429ad3918c8b86ba31e09fe9ee9af6f25bf3e38c7256177d64091a4699c88ec8aa798b4d79abd4d65e54ef2777e09ca6040c123ed4058dd11a8442a8df83dc56f68c22816258570008cf32ac5e87bc31ed4f2e93970df0ed97f97b0b27d8458b6f97c7748dd447a2df637deb5c12827b1876e78ad3c2bf5c07b5a1a4adc16558addcf50e4de24b4f63352392aa3f7876685c609c76b8a6a993b4718a9ef6a465783a815ecb54dfc036c1300b919685876bc0b55c571c880254d7d0d36107fb68f1e832b299e656ad6145e5eb669acaf4759e29c5c26b6e288911e869a79b676f164cca377390f5cefde7d470b0d28f49e17c71f2e06425a58cf8464fbba88cf27b1f79017e1f1e76e765f21c394a5473a0334bf031af20a9b59200eb39a0ef5870f79f6db94a2724f7702e4efa17c22182258387c94933e44b9caf2aaf98565798ae8ca80203e03198f1784151f8df691577e0edd3d97a28f845d077a753698f7746fb153763302857423bbaded2da54c29c99e09af094809a11532d296521aa452b238aa11288ecc5bb06b4dc461472e13f6d28d08db4e4e62911dedac38aa11eae378424b2df64d892a2d365012fd44aa97017ba89b822cd0c788919a48bf057dbad6445aa889f0505f4ac391f6702b375561836958bf537f0b7511382415da345090acb142855acc518d303f478564aec0be3552d33a4bc354778a453e77a456c2dcfefea2c3654316a13848213cabcca1ef6394e280707682b44a8b4139399f05dbc788caccdeb2563f32047eafc857e4e3401a0f90b063674415858dc55603f858203bf9fcbc6cc5603ab6fa7e765a857de4d34e1e6ba1165aea980d6d6bf574b58a15e0a3947b695fa70bf0b14066eab2fa26b24f410f90b0144886c06fc146d261d9a78ac27e2a2eb6fe1179e49301dbc93b8ccfdc7f9c4a8b426e0dfb29075b91b89ea1fccb5448d6f0cb1a063beb30cf003a23188084cf2be1db5121f170622c900239cc33e1039b85709f0c81658d4c5318a7300208132687a500727272049085469349ea72eb82d3a58b0058e208400002c0b952542443db52db12dbb20587650c07072766252888c82723e4b11e658aeecd67edcb20a1503248b7df8bb1bc89c56275559fe220457eefe6ab21151dfea1b4fb93a2a948d8e052da101186f48afe903ef2d89c01290e92081629849f821448d6f0af401a44851cc62f03d222a747b2863f0552244a73981461a43b5c7e16e54189cbef2de09be0b4e03b8c014d84dfb3f92688ba42544836f08408091224488d035c0ee3f7947c4182d4d4d400010204842f860fbed50a846f0503cf21637d1f01018125102040ea0a25457ed5e5f740f858a07518d3b09335200f084990b0a38d0aa44732047e144869348b8a840dae8dace1f724f04d6f025f1882b50a79cc76d1fba875830d34d040039a1e1f528bfd3d542334ac09cbe3266bfa3d08b2633bddfeb18fda094cdf3cf54d98be199dd0a3cb7f933c92307165b043476b4666042647620c6065ac958ac9e7a598a03a261c934a2bcdf4d0c319af0f00245bb46951c5a6c5d019322bf0a0a6850540e1d0185ace78430b1b406fac402645a53431598e136adb1649dd32c98d2b6e0c3df589518244e5ca2422b4b8421bfc58a9e5c9cb1df9e727e8b2ac55e99894bd517447fe01fa7943884a4b05c21856fe58bf3b417bbda942a2224185444ab1364bba4add097e9755482400a4b0321cb9917bda7d83dfd1faa6432eea42170f256c8f99ed5785b11d1a69919f0716ebad66d67bd197563051634a9358a863320c2079468c25e327a1cbcf59b6c8f8f98abe915ae050ba2c73f9798b95cbcf377ecafdc071746961aa5c593f8e8b1a1212e2e7226642bb614a1048b8a1b4838ca3f08025881bbc40630635a490f1bb6a0e2bb968c3e98e4eeb58e73441960528f6320db2869f659042f8c72e0e2bfc73a5631c4715ee9fb428c324dd91853a36651cc676f9a8034df6f28face157c96147166a7f6e3e71ea40d3a51d34551e6769718c7217d032e9b06ed0bf05104a8bf2bf2b3dc64048187f11aef19db0cc420c14be2a05768c729b81a8e2b09225e529369dae5863cb1d1b294bdf8c7ee4470d7499c633372e7fd73ad632a9658d2c97bf696bcc2e78a5163596ae1a5b2e07576a49c304b5cbaa2fec93a727548c12242428947d6eab12c03f8dd43db4b0a345ba1d7e5d1c7674d2ba2ee61a248c7f87db0f43348177642a4e4bf05314b3cb8cc565a6226bf86dcd8e8dd437fc4c85a938ff5099215dfe46e2ef2c57f0f796d17aadd2bc5867c3b0a36dd51fa2fd9c4e709c37356bd75951b3c714d851c8f5d69d3ca706680d35e077da70d902a5fbce470560080f41cda92a01154e9083d273cb147e45ab0b3bb67eb4bcdddfd44d57a73d54d8751ebb1ce65c382e99cffde4c0ee5d5ee81c8e2e54375f5dec28bfb8feb504ce89049b4b398e8342bb0b530d8e3ce3a22727ee8198986a4db624ee391147ca6efe06e7770598638e307505bf959b1c09fcfd19b0b9345481bf5f267548d05929d779caa361a2d7a8ddacdcec5214f5fdf079def779dee7a15034ae968ba645b344ab86ffe15c87ba59b1accc35553f67d5ef53b15275a5819eab158dabe5a269d134d1fa2ee8474fa7dccd3e8a32023676e8628a38d650410f4f5d8c9ee8e2ca1618fc10848d230a22203250d014c40c3140038a1d4c587889c1d0103416786088111b9caf45e34f5aec64777707817e50619c8ec5f9a68ec5f9ba0ee88fef3e576bbecb46b45fe7eaaccb7edd4c4d7c1c1bd7fc3a203fbe0ec80ffe3afb75407ebc6b4a59535dad1f345fe7b25ff776eac04307aae44a2de295cfd2ddbddd5dd2d9b2eceedfd76919ad9bcd41c87fb2260810e93e650d08f4830ae344ee9dceca551b0f52a89d23196c61f9690736032f00e049f971a0aaae16f7e3eb8e66485327a5f3a7f410a7453904e4a1c3b7922cbffb88f10dee28a594d267643468cd7434ad97943abe14960c5a2e570652f6e0b1e3e5c2d395effeaa2a2cbff7701dd7fd2525cdc72307cb3fabbbbb27913552da663a0d3e5a39aeca501f1bb8bbcf18fb3eec0f59334350babbfb5c4929a5f41e6234165a455923bffbc34b49d901ca83ee8e524a29fde8883f9f3fba7b13299f3e9e5509c257a4bac2f2fb073fa6bb3b9d14846f0716cb3f3f5933e99cd346d650576b02f95a37cb3f479c1416951596bf7e0a0b27de6e06a9692141d8b3f97a80b1038bd3b79135f285b8bbcdea5582cdf5afd7c391857b899450824e2dbc0be6282811d45a4b6b6e3783a54f432197fe941d4a5a178a5b29cf726bda99f22a87eae4083cd17e5f8bd27d04695b3e79d2d5ab841a8e0eb87270902b7ecdef31d9920bcbdfbef290654dcd952d9a2a9ca5585fbaec3eb83f8478fff77fd737b0dbf58d902bdfabab233cc9ed9356ee4b6a9a39715ae3bcff381efbc7799cfffe61529c1f6237b549299d5f501f9c5ca796547b6bad0c636dcdd66ccdd66ccd5a317efcec3edb72d52e06e701082080e8e00707727cdf17f4b5a44d262de9420e97881e5bc2c42432894ce20fd394302610400031a77ff1b1c19c73d6c985093a468ec830b49872f2f4e992a7374d1419e810a2959b31d877b95a09779fae2aab0c379d3dea93ce784627bb8d280ab91caa2c938241099033e433d2ba0208109828aaa0503da17242455335a15a71b162d95a2bd7a1501d57a5503161f9554bc8cc8031d3aab5725dc7552d2d1d5b74ecf8b223832f2a249512aa24545854472a2454235019a9aea88e5019d18107b556ae43a13aae4ae18138271262111f80604555a4225211a11ac2f2ab866c42f8124200be046046a97281b346ec26298dae9a20023b475c2e97cb550023e24ff91fb3fb6ccbe5ce4cdd99293bad7405b5a98328aa00030c5a393f819215212b4357f8d840c987289494e0c2041d2347a6920f2ca594ac544306614c48e4ad2fe912bd5125aab44495fa86defa8609f0f3328e001c953a5c951d9475bde84441be25481d998373fb77648e9b4182db210bf229ff380c88abb4e00eb35eab75d72665aa4670d59dc2d7fc2832ee3d7ccd30890fcc3f8a4c3a8c670ef3f71ec761fedf63c453bffb1ea31e239ed473df63c4837a2fec111de68fe2e91e15f674610f17f6a442a7b1b3357cd14f71e18b862f08708f7a9efaf569c875e14e8b9ec4618e1ac272e8246cd2a2ca0496dd59c8dbf8a204424d3a36655f90c0c62cd92df1180d5f49ba7f2d40caba7fa1d0c4a5c396806658295b69f101a338b00ccd66b2eeb937327a29807ef73cf4bbb0c7687e0f7d2eece95c0c3b2629a33f7614842fac9449198e93d1184f4001349bc95043580f39549940e98ccf49133ada74916cc3a5d038d6d6e0ac2928e149d2e275b9e5fcfebc35b461c2d2d1a60ca72b35c9596848489c058926635c2424e47c637d7bf529f30f2b54e2abb0eb54351cadfd4254387e2a1c45251eeb6ee2ed469929e9a7054859ca73798c85bcc8c35668fb934d841f05e2388cbf03bb28b56b0c925b342c362cbfaa0ceb02cbeb28ecf8c960c7cfff32ad42aba8a6b0a3bd723a08525a2b57074c6b8665c96e1c240dc73187c275752dca9964f93d832af6eb54665813bad8141ea94529dfbb4b5f47ce29e59c724e393d1f2a3cf9a39b1ea324092ec2ba92638e6b2c929ab8662550d0a60b0876828f640ecef5679acc99d7190cd6e2fa3315d7dfabdf08fc347f3fba343ef21b252dbabc61296fea4bc9451aecdebac7b1f04a71ff4a8524f44d71154734ced2e5fa67a372fd798b1632975abe88719d75fd194cd1f567267f6eea148aebc2d19502e7ce208a064bc34fb5546b9c0d2b5e31b39ee9f3ea39e69969f8e28f72d9cab0563133df2a2f06642b205761cdc8ac54e168478b0ac7efc2d1c585f2697eb6270a3cb6c1995372ad3fadc7faa99dd26172bad0dd6824b1eea457423ec3f2157953a5bc994fff9337dc535aa56f3a593f03f50da7beb3369bcb263e5999d54bc3695da3b8438365d512a51243454c1654f2ce91effb3ed739b2a3b3333b2046806401629ba571bf196c5272b8e30fa62f29094d4aa284f33850094f128f122e89121d9cee8e38fcfd61dcd1c56443fd268e8e111c9f9452a7744e9d234ab61baeab4bfde8c83e5dee7257383275669f4ec3d6374e66b649f1e18ea2ccc205335c18d45a2d1a57ab45f3ff4fe36ab55c342d1a1d2cdb6624b88f63654dc6c89afcae73b9beebdc5357daa410f54f762aaad4a1948656c276183c226bf3c80e65ea3a15ece80cbf59831d6d868cb2b348d7e4aeb4cd8ab896053147a82bb370c1118fc313e7fecf564bc2eaebe094b0e4b2bf3b4ec7e84e0d56da74fa65d29121435457da6656aeccc20b96249feb3e9d4e77ff64e8699165910d4821cd0159d3dfff5d4f8bb3166e7694495d278ea2f701c7241f806f4216613267beab8a9cdb1c80db1d5cfeb103d4f5c425bb811664ce74310e2cc3ed97729b853c29bf936fe5bbe44b02781d7365d759f728a5d461605d99859720344803c9831584804b1b5ec038bde12508d61a324ef2598abce11348251de30623ff89c7646250f5e7b8ac87d2ddf9120e29b602e40d3fb5e8cdac5ec7b75c36b6e3e59f9044e67c2fdf5b31ab2d1d121202eaba8e03d48fd407de37c119d58a656306703990c845d47a1da0d7525a25cd54b3d94ce6702d23d703790bdb74e064a1d1d4b879b979f1924106b12b454563d4946a4a4a3c78dc58090a92a2284b5116cfcbf222723db09d76803e4636e41f1d21101153591cd050b79a018a01a56c66e665863c6f688321536c6433cf6307686fbcf7f1429e55d0a795c14fb191edf81eeff9f021c2ed6674b4400f72a122bfd23047729aacf1e07027a4d8ff21843d2de8005523804266c29016d59575815f13010ac39aeb8144ae7f97cbc788fbd122f73908e47a20d1cc7733a08f919316bb36f96992f9f9b9fdaa1f99c33d017a540dea071f7c0e76c064098b83da410d01bc310d069cf232238ee5025f9435ab958d487d22445e07d8492eb0bb747bf9165313595aaa1dc7d21133b363c59324666607573b1d3541623e4ed8e96a386e831987e9009ba9614b4bb5abe1381c0d666848ab9e099ba961fdbdb4c475ab5ab96ec5ea38a6a5a6b95de46aa11639ae763a58de8a650487c5752bd66b4563d68afbda751c6b95d3e37a2051eb3990c8f51d489472015985626bc6a583c77d0ffcf17fc3e2389e59bafd37370398b92b9048c7b37e82443fa24c4798f231f2ba25afba846e58cce2389e219a7150e6ce803e462caf72336f6758dc1fa1a8981d313b988bb91e4824f32ba799304da499db11fa18cd809ffd991074b45c9cce080640f291a04282ba4480aab84a2872bd037c0ee6508952490da4a843c2d80401dc0af2cd0b93172f5eaea8d56a4a36252525a4a3a3a22c4759b264b91912e20a30e493011af282bc21225f4ccccb0c79ded00d4b2237373775d5ad6662bc99988e057295023010570011022042afb817a1f600291649849f071733d4b4c895c112977f4713975f66c6534000aeb780cf419b9b02a7d83f56606735d05d56e86090eb81dd679b48e5969638ae724b5c5d5ae2b8ca2d7115763d25e2e540221defb97484dd033cf0840809d24500007789ab416c10351f0c4cc3ebd57cadd66a15e46b8152a643c76a2524084b214284d45565e968ad74b45208a2a4c5b6a1473320add1204b3e0733f028f039b8e356e07350c7f5c0155c98dd1a4e0b7c53de8f063bb69393eab2179e81b9ed42c9809df5c0830e963aae0200003f969638ae76cf2d755ce58083706989e32ab7c4d53004979638ae724b5c05c15f5ae2b8ca2d71f51b70800c4ad8b1a303099121220420041b214130d079305d57c689d50c7881c79af2cdccfce6314ef2e622dfe4eccb21383a47b8670fb5d84242b5721ed7a1c2466a180b6ca71fa02a414243aa55cc7b32a08f116f91615b076453017540b7bf039239f45b57d46a3a50484747723485690a1326036f4848e88ca42e495dbaf050550102f282b685b665cbcb83581f07b20fcf58868999077132877a4c46c861324835e5b1d5b78cd06d295bad34b86d90c4c781443131218df9293632d6cf3cdf74a81e54d8020bf53d1fdf93bb055d06742087f557d0835a742b42455758e067014a779b0d29a4bfc89af6a31db2e60a3b764eef6344a1f4cb80a06a84f92c9006394ca8c8c728e5a486dfb59a08d3d072e063c4a4c59ee08b8414eaf2125510934c900cc10377445d7e16f88eaaa0dbaf0af23a78891b7cf0010bec669d4fa7e40602d7fc10e20127798cd6a80d4c8b4e3148e2b6218bf4110b69cf822fca107888ed361122443e15d8494b4c5d6e37a4238ae0b674ccc788e54df398f598cc773d6147a6e8ca119120426247746e703ceca586f50d082826131305ef40bf8752c6a9efc08b07e5df7953161336d523ce0e8ca652a9274a857ce4304aff042ec2383a54a8a35488e3e60647f477e827a1cfe3cd51749f4c1fa3a6f4f9a8c3292f33621abb944b2957b4649be20ddb145b6c53bcc0368517db02a05bbf6b5d06f98671821386a333352a2dd69fed13f3c5b6cfaddfa4852ecd4d2dd677f2044a4fdfcc965aac53c9632fcad1f0a500ffeee977170f0a155ae992dd3bac3e2aec58a9c52a475eba95936e2dd6ff4007db04c7635e439b57b244e6f0adcf62c2f6d8ddbc1620653e490eabff428135c71c73cc21f327f290991c56ffd6ef615b93271e93b21ac406c5133399943116309088c14bd1c849a0cb6368fd579744bb4e8359fd27f5a1d46fa1632f597d9ed5679ffa4ca53e03492d72d4aedfeab2fa1d4d72f9ec561fa3c904e456396e05e2f2587ffdf7987f03a17870bafd12f51e8a4b51cff39e68848a828282828282eabf4c98a30a9d0af3fdc6c2774bdc52cfa9155cf8bc573db9fc76a3992189307d2bb8fd1e0b6e93505050d02de816740bba05dd9a6eb7a71b18cb37a69bc7b80b98262f4cb74e4502cfbc29e34022d4a7c016ba09dd826e41b7a05bd02d28a8a242be355994ac31a2725ccb24ad60cb04eac4b517e4c905b0cc7b1e9779618f0b32efbd8f06cb1ef3be1b4900d5004695911f1b99ea47d6952fff0b7a97f836d27f7e1cd6f35f265415eacbcb646361059ea36ba20477ec2218780ed9f7fcade0828fcf141b99ea59df451e736b6db79fc19044562cc6ed57917181706cfcc7f623bafcc7d6b1edcb07b2cd6149368fb152c3fabbdc78e936edef918545c2b34b3d70ca3a90c8fb8f6d2efb2ef16d449b8dff28a0655ee8030deb5c6d151259bb74ecb91dd75ac6725cfaa9025cead5db858e7ed3d9d393dec9814b6937bb6e32a1faebc23e748edce0a0c1010411096c4042d64331fcf086126994e00359c411447f48892c6b3a2ea2ae33ea9698dd21ddd1a6084fbe95ec8304b65f721199d9fd1f3b7ebaf33d0e7cd10e7cd15a4197c3667d19d7e1c8b33bdfc545982905b266fe989af6ce3a9f99e6cf5fc160679616a7d75a9cbf7ac1f6f320a269ca3befc8f743f9609d36df6bf3dd36df9392e80bf69352da164be99212a709f8447e93277d23437944c2f86538fa3f71d2223771f28465cb26fd441a6991a54e8bdce449767102856517267d23b9c913272df6d0c2caaf434b825945c918c87f808c3549a2846b2087f937f158f72061fe31b80ec4750d741f0d3ea3d9b181bc38a271919954348b90b1790b43d6f88fa93bf65187e5fa74e246c5f534eec87d715a792671911ad2d90c43e670485ff038c981b4c961fef405ea447fb0b823f7e5d62c41d77fde3cc6752161fe625c9fb7135c6fbaceade0fabc5de7bcdc185c77fa82cce1a6c81a7f7f3ae3229c0f0bf1f7e7b6c81aff91f3b9eedf3cc89b1cb85c1f642c050903e2220a9042749037f41d8dcb0fd4425cffcebfd2603b2a93ef49765fd59755dc5182f12209c8a3a4456e19731c8a09d59a23bfab451a0f3b6e761ff9d9d9b97bc2f64b0fbd216f73337a7c395089ace9241697ba9a0688ace14f29c1dd6ac3f6d06269d8e7d29739f375fac61d1c69bf4af01f3fcaf5fc73eacd6f841715df61ecdde512c01edab4c83585fa0da43ee67afe557072aa1787fa56bdb830492ae49135192a74f973e0ab84399bd54befec764f41bf5d2896e025b4ace24a2aae872fff287c29d829b4c849640d3f65497d35c3178fcb50cf9708c537f5847cadc0d7f28bd6f5e218b2933db57be6b75429e1e1776e218749d0ba7e82a28dc3e45710c761b2935129e3a6754d7edba4fc22c9e8cb11364cd9e2852b5484e14411616071e322937f74e5ff3deae45bf92ef9df49dc6ec677c7710589277f8cb51e7d5a9ce6f59de6ce49f6ca6fea246b9ac708ece8b4278fd1aeb3d6e5a2494e5b40d72bb50719abb0a71a24736ad83fb2a67fc645ea110ba179ac65903018648cbf48584dc28675c74a635de9b0cb95767be4a6913e71d393d724ea918f9b9d2ea75db15e6b5509eccb5eb9c5950fdc7eee8f6511fac4d769fd5eeb6f95fbbfd3e88fb40945717290492e5a6428f0c5211ba9a0054f323642a3c926f3b8f6d58bc765a9e74b940a7b5c90755c5ccf45e8130b7943d6f48f292ceee8ddd1bba38bcab8909b1cd6f48dd41db9e9898bb0d30a06dbdd2cac808449e0ebcfab176a9247e4c93b1b62e33ee790108707d18e306e5c6200238d23e6e8a104132080c2ca0c50e820e3efa67ecd60c76e92391d4385a847a93e8aac93425b16769435d9438a05c28308e9a5c0174feabfd44791b944d0609a3cb6d3b4d3546ba7cb556c8a871cd66355bf23911784316c8f3b5fdace0073a50a498fdb4e60da4b8bb79b5a642cf6757692c858142c5d800106152d443db821e35722632df0d0c30a4070c3114e08c9f87914616ddcc9edf7f776aabce1ba752b008e6a37e7a4e0e77de375ec2697cc4dbf5b417b2938dadc0ee3a4757ba7c1aec559d7a81d982ebff79ac1babe5f3c8c025f3ca81eeb3096ab188942ea3d297ba1f0bdf75e5874df552350c9050e432aa84a9879952073fb656eabc0f5737d5598ff224172818317a96ac9050e43d74199dbe01399170aabefbe0b3bf0a5c0170adda3ba9732a952a27a54f8e241798feaf8fb2e7cf174dd47914591adc23e7218bf4ec76461bd0e7cd1e60375761cc61c44edf2d7dad6493226adc861892cb69431c6184832febec958940f7861060e6bd8b006089e9a896b0ad4ef2308830296b5b6ed40e328461e50a28a15657861441a51a8c0108611405439011955c2dcd695b62947dc0fcaf9a3aac72a08745f1b02ef523d8d2e77db372af8cba40a01b2cbfd5eb38b6254c88ddb2f76ec25ebb7118ac9a4d96ce685acdf881af7b6161804ddfe9dbee1e90cc7edcfab7029eb3cd6d3332507a40a851e21aab8600e99cceb4fb60b4ef55120f70c8e0cc4837a2eec692687394ff735ece99bc3bc939e74dc09d3671acace2d17db6f333295e9d4a2bf47bdb4e833a9457fa4a4eb4eeb71573fc7bdc16a54e1d2f0c53344d67dbd44b2daddda1443ca2e2678933f2a69c8cccfffa55e5202a45e4ae667204e3d3333333333732f6ea763ccc569369b49e1e157d2b14fc6bf849f09bf13d6e587e273f95b9871955532e42a3c4106626666666666d904e5453133f30e1f55309efc314a998292a97364e7049beff7f738908a403cf0849d233a3ffc5a975f170d7f865ff73e739f4343cd424ba878136fe24dbc8995875abce2faa92e8f499994b96ab69b2465b34695682dcaebad1a619ae0a4c54e925474e45fb37cf0aa0ab645dbe2952a245e69a596eb184be1519242c77cc8fa997c7f60e6e3f6b7f01493ecd3df093c293019c2fb66ab45125c05f065023b3172bbf7e7420db0bccd3b49fa9528f5b850a48b246a32237ffa7f3b7c79bfab5144d8bf383afd3bfd435720157d4184175a9011c5e80a198cb4cce03444c30e21889c28c20b19332f5470821f35c2f0f2a3a48226640c0961254b18434a0a786a9ab2c50d36a800b51047133529cae8028d3124c00511513c591983690d597fbfabff45c57a14ec6c4b6acc64fd519a98410e38b03825e1c40cb26662420a315c1003154e4421eb97aaee7a88e1ca4c26fb3bdbeff2ae5cda9ffdd9fbe9370761dba1e84a5b1530e195b61d7cee12264dfa86fbee6d55badc06c195b61cb6f062c0b0a9efe1c5ff79dcec28993aa885644c9ee0f60211609238010f5264fc5d24631d40d1840408da5062862564fc4bdc733d936863737b3cd98e5548eaf594fc6dbc859362c0b023dba0b4c84c9ab4f8e4c78e720b4f07e773216779c9ebbd374968afe33284f0c9c2644518131cc9c20a24b048410c1fd041c6cf5e644e0d32a79f9f993ac657fac64b482f0f49e8cb4ab2869f46631ad394644e3bdb90cc74b1a867fdc8573430af0a2c16627938f22ccc1318272f4d5d98966e4a49728bac19c1196213f3de7b406240d9308fe63af71ec8bde7712870443d0bf4c6ef3ac87a8ef516e41e153669915921932528f0e7de5f7aa16db1e583fd62c406979b130f4e2960210c1fc20491c3113fdc207bbd6047235e0c1896fbd70fdf1ac6df75162524afc8c2891e7e20c30b15c0d023450935681f80e1a90c193fea848ec53c7f33614727462e941639156ee1a066e2a445244b5a44e2025cdce8220d2a6c30421c811330f8508315359c21e3679f23cc84554b88f8b0f871924205152cb0b00286a61f1bcd8bcf1832fefafc2d3059528465266e9231266a4cb102a2357cb05092f1b3938c4916a8d145952729ae18a20819a396b07de951134e6bd090431b4d5186f000921c3fa8213487141092b0321b5a228d18bca0052da0e2072178b0a28a09644b9a3c91310b1c71258b28666f886048c6df828cc92dae7849c10a40e0f4b3432d8323aece39e7e42abf7f2763954eaeba7b48df070d59c0b22cb0fe1eb76c276b5ab6d0b0408cb8eef78dbc7248d80a2d7b774f0e687cc8989de18c0181e59122318595bf248495b9d29603edc74e0e472890c6d54dd9b2b3e1a557da7298e18eae09e4757dc32fb8fcb3e3b77cf9458fb50e0e33461834c86c349ce0fad33c96eb03b86ea3a2c6f507e2b61aa8b8fe438ce8e070fd8fec085dff13921c5d7f9e146ce0725dc7751b9a9e5c09e1fab77082ebdfe3cf3eb61bccb8ae74dd06b4041941566c38f0d0bcadf26ee7a9852771e4dbcbd285368eb882863042405102594c9a8004415db0604113361f993bd0752e8c8c88ad64ceec65b6c8ee73c5e7fa1fe9588cac6356e6ef048accf1ee9e9f1be1e878ad2b23ac286bbcfe8b7e0dbbfae2b8aeabf4a7c3f150adb241815dd73d655aad2813d12aa44e9469c761f5bdda8ddce4be52812e06fac057fd2832b739acfb14f8aa61121f58f75164b49902a3994cca8c3ad52b7158f7ffb5f7fda95015f6ec38aceb31e2f93e15f6e084ddbd47135fcaba1f7164a96fd262f74f1cd63d6a88eeddd6bd27cd1cd67d83ddb398b02f1ed4f7c8b68e811cd60939acebdeb7cc20a779ad03ba490eeb9c568138187c34d8c949640dcb7e5573cfd33df7dd21cff7aab047c761fedff74887f5731f45e67d6007dabc6a4f17be6a586bc883f28e07f5dcca084b7f05be3059e32fbdb0ff4bbd78bcd47b3d5c075a5094359e025f1485023b87b1f77c89bad76914d885334c9a3db9f0ba1b084c7ffc4f5f94d65e49b62bdf09f5d97e44a7c52b35487a0d66a852a9b36e68a7a2abe5fdb4ce6fca55ca71740327ab24a3058e2c5ca5f25d361992e4bf1c61facf1fdb95a2e8365cf9437ea763fc537b0d5979b977d1d5aadfe00a2edcbe2bf0a5ab1becd3af341ca35cb0057634728f5cb1f2e51d6d3fb72bbfe5a2f9a03b65d22603d00d5f8980da041c970b5d2d232d2a61b9f0884e8bf25962d81a1af167c91c59435dd66aa4633440477644997c9ea22b6bf297c071e533e91bf14a9b07806e57eaf6abe1ca770db9926fba16bb0497122868ad16a5454961ff0419e36668dae1e90a279e4092c9e791b1d752c60d86274a50b42493462ecf7484c093b248ede6ca42e44b59a47fa4dd1c4a1622df9b53d6eade3fceaf744e9fd31b8c42b9cdf0648f9549385c713dd6904e36acb3327ea9b28c2ab246705afc71085f964552b33bada4314a92506ab15529b01e12bd7828d853bf032fd0afe10b05ee2bd744faa5ac86569584b2757dbf581bc3ebf71a449161f94adb0b602e7fea4adb0b685cf98d24756f6c53d06cb629b0e4c0598e93436c8c88334e5b621ac8fa79966ebf9212c8fa973071bbbf89d2ed77c2b276a6d25c917ee1ca3076e43b72b7bfafb449c1729d9339a012286832d612c6a92bf3f306afef12410a51ea66561478d248adb5d659df55ad8081713a5a630b0a7e5220ab6fa4d1c5144d5d00a531060d527cf8fcfa3f759f92c4a4749da7251a405fb85c090117338880a28b1c53c828c2a18d2d6474864b39d51976944a29f05069e338eeee4c36bc573b117240795eabd254f538aff352ae2e1c3b5b4319a52d3a8fb0e984cce1049252176ebca1860f1c9ce0c2c6535017259041ca0cdd0ffb841d7ffc107f5ca7d7260b4a696c36031748207df961e89da6d060a8c9300409fc278d55d7c62882ca1345598aa42852210a143c518412342ac0700f34d1ae6207138429038b1e6864a0403a7d408d33bc48eaa108872966704401004308862ab610f2e28905a1182c71041c4588c184acc670eb8b4024983c67ad54dcc83b992c5b2bad8822c51050cc410b1303315420c31ce1d48236be7039620c9fa318cb468d654dd664165f90a0f0b101071ca0e8d1c513b31e9a604085175985e1d6226ee56ea5b4b3ae176d248e8ccdd92ee47ad18645604545a76035955d1a0aa594524a4500460e37e48081a6c50864348a103a4ca9a1055bfca0c50d32ea2f5ccac3a5f4d25abfb0c88e5f77e5bc5e43a7b3d9bff0a491aeeb439139f2d6fa292c61b223bfd6fffac655816e7db1be4de572536f9d9e1456ce994379729b31676d7dfd5d83df4cc00c695aa4ab17fceb6e39a5b36707b558decc76afb586feb5d82d28f4ce16095fade18b84197657beaad87e160aeaee9fdbb9cf1e2c3f9f91c3d1396f7d2e9453825d9df5b9efea4f2b6be49c6e1b8e15101bee8257c397901d3fe73e707462e4d2af2daf55eb7fdff8bd7fdf73382d29bd77eebbcda56c5cd5b638ee6bf82a619cb7fe776be8c4c8a561e7c4c89d61ebab6d5ae1c91fa2b5e98e8538aed3866b651f40612ed7c92ce431b70a40bd3f0aa8f7d0bf03dda302a1c9a8244c59e98a182a4434020000007315000020100a074462a150380cf46cd60114000c7da4466e5017ca83591003298842c6180408200000028c21524342a20e7df1d4816174cf8502a6cc9cfae4b45a135176954575400c6129ef8f01525028413febcc46c0133523e42d6554ac954d85262afffceb0da82512c7c189d24de040543bfb97da868d7315e226ee6b0e7f9091645165f3e1b3ebcc0de3dfb7a1f880e96155f8a077086585624801bf6426abf0456f1c052d00659bd8c78c88bc8488259702973c178ecca72b0f9ae16036599c0d5f49969cdcc6cc7586ba79e958f5e42d65b775a5f9fdb6c95351fa157cbd165c28cb23230b608735d66e64195fc8535e835c8e0be3d4dd4f3994c9159f15ce4ef55ec152884294cb235793640fd167dbfbc4577a09d812a1d6f124b32748584d7ba312ba971c26faad3da22b6f89f54d3a32e8171965a03296e3320d40970a71f368b9f2e4dff23aa8ff92df8782a74af6b2511cda2a81d7a90f4cf1ce59186d97cf85588c78414d8c4962f290120f2492030cedb30d7dc1ba28d12eaa95f649673c85a76a022bc8c348a091d615eb1254517ce8fb1667debccf02ddcec17b5a27d919156ca1e1863184e3e442c8d9a5e67b0eafb4f816f72c84ae10c628add059ee6b9b4f1a210198786573b7aadd8dedc7cea6fb75e9367891f9c5c85ed880b15b8520023c61f0bca40528298862218ba9bbd979ba4d46065672d90cc3540d14e5ee27b3014fa1d6876f7f1428e1d472851ccf7643c5426f8f93c9843641b8aabb18c20b6f3b8f70b041727d312b4155cb017a9f683c4b5ed2665d345455ac979771ed37f65f3a5f05014a7fdc8b2bada432b4c4ad7880ccb7b2d3b099b47959730905702c556b6cba646e0a4468bb9ebede6d525803fb0973264f3f9193816942ffc102fb2bffb4aac116193dd648026cfeab615fa251142426b2644e054838de1cafaa1d0c6ab8dfdc32aae4fab9f23b028df75ce84cd3c7f4cb30796ff09af185236cc35ecd6bb4c9ecc48911034890ce4881fe126b65b77f0898050343146459561a542e4ef74d68971cc3df3d54625bc7b4c4abd0f5f0f1decb350f0e0799ee0906badb350857b2a1c8df7e9f0f142536e7b40b02af249a09fb2903d159c9273b94f377500e338c86029649a205d7ab68a34c565eaf56441ceb42bf2682d02d797109b9f7bcca50babc329d273919454a9ea9df91c4d97a92151b1e516140e8ccc8f4372ed0bca9b3edec9410fbf2c988953440574674086d7e691779bcf6f7a0822493f9d19fe32678884a9bf0a66e77c2521e60380637cb821d1b8c937812868eef6693524dfee8b767e096d1537edf8f801f7d0584db9efed186da1e616620b8e5b6fb4dde9a8b5f99da09d06a660deecad32726538b2add71afc5df0499cade3a29cea086cc544c88479f9d926348ca9742dee182e2012e2853049a3f0d10aac51ee20ca18f5f70d2910f556939cd7a030055ab177639f04baaf5486c0985230ab606c3b27001ebd250161ddea6f595d99d532d4defec1a5f8bdc37ddbc655ddcb1226025139881544ae0fdbece8953537ada3d99e77186103a19042b1247233f9ffc1842780d212903b7c9a8e9783f9a61fafb3132ecaf2120630d1740f1bcfb167cffb996886920e69f1d214011db5e5a3b8969eb54c60d06a5acdcb546cad26b6d0acf3eea97556893da7c7ae6e6932add6328040de1c20d1108996c6439a55918b6b6a0462600538aca396062e3d88ea3d31ee42b4b265d7c139c9ed1f3cc34636957ecfe2e495474388a2099cb8a321e8fe670db408ca0a5d0e4c78a4b9fd4b8afb2540dcdf3da624c64e842e90f6cb903a362b293a5a2d8a55754467ec09ed86abe7d45a8146d6166a691ffba27be91a6ba22fad75bc7837a65aab591255f6cd96a50e81601501739426a5685931a9b69cbbff62b603264596ae37608d0627f4db98c19c0491e700616ae7b9b9d1708119d52c40682d327af560e83f2c8d124ff6787e6a4de391447f896e90b97d157b436d7dcb762a6ac4617c16804d89a71904894a1a965513ab09655b5b1b7d10bd94bd86722bf0a48abcab3c9a78041758580e829c1ae67eacdc24da3dccbf11d2e7ba773ad8f43cb4bdc982edb4a252e28d7b4937259039e1456f07192fa5e74a2ef1eaff086dfd177df94117a00ade817115517d7897540e931b8155769391d36d26e52e9e1e73e8154c71a85d10b8d903d2285c3be65e519223e76cf6a0fafe368f7873c17a0ee851f4698c37dc52594aa11f8ec3f62cf64519a5891ab8c8a09375a6c4fb4d8ead8d15e9e591f72f17294ff75488f6fbbaf4f400cc8cc4239b1529c6694f5f0d48e3e4699f33721900a69a0b527848dcff948252b7f77fb8a1ca45aa2da851751033560a5546d068a2208008b5588073f136cf59477300b67869773e4f1fd4c4a21e6e363b03bdc92e73c6c54b42e75a8b476f33dcdbd772c5ff2bffc02d7741f7840e5c2eb198e66b8053a2d4b5207c0e811b4ec8867714ac1a840b30265098cfdaa922f994952440834498b581ebd9897f49f5e44bc26ad36faa373861a089ea8934b39b9662ae0fd0e3f0f4422b4bd55ecd9ea38aea98cdcdb0c03c8d5adbeb0644561b4d07a9365d464ff9bcf26e0981d227e0dd57b70b5f839455809e2bbd53c28cabc80754b3661dffb8daada07639d822f0d5a841906dcd5bed549e5db0973554457362489ad57c1f21902e114285efc649ddc2834de3a0db9f6575875b5abcb79c57ee4021fa38956590f4fc59498112241e24a56c30a7fd70a4819961bc04b65f387200c12eb809b0cdb4980f47c22ae1e969eb2c414c2e5de4c6257dc90a287b798565e2490239ae4ab55309163377274d3b26212ed1737443db9da49c422925158ae32ae297e01138a4bf879b50c8b15d45c307c8c36d1e3ef1048ef7084e0045a6320f9838310c344f162e5a3490f13c42e0f548836757da0bf3f86419cabd0c4b5c5950848272def834f70e102c57a0f0674428280d40f0ef0131fa634b8432f7cdf606db0d2584df29c42a33ef7b5b4c8e973d5e5f91cafe473a3086ae5d42ad525483b5c2f584952f7aab65193ed3318825c6438cad52e16268e5cc83f0397d5b437d4a8f6af11b14e0ce6d7928312e301bfe55f934d184b52777d7cf0c3789695df0349ee4b32089345dcafad8d3d789f12d316055b894e3c68502698b651688ac352b842d73e3affe60f3133a1f3dcc17a84ddfef1edd6d9c2eb54d536bde303e79f61164b81504a056e4dfe5285672f14ecdfce735d2dbf0afbb02d464c84ba6ad323cace19925e589805ee5c528db2a12a9f35eaf7729214117ee77680dbaead419504279b979601c249c8b2e587b722cac9db8742b5b9f3c7b8c57466381146c1ecc710a5c2a2d58a4b403f197bf1c02432132dff9153a6821808616aa9cfec0ce4ed12b0dc752f410d2069422b00b641d1bd04facb0f03b02fdedf4e356966a9ee8d72d777ecb5c04d802a51bb5107da8dbf396b98755f5fa712c12bb0bd2fd2f25bb4fb78979808696aaf5726381376b2387e73ef981d1a21864939c1ad91cd0e74c2c3e32f17b0d65479a7cfd476fe41a6d3a400f073c70766abb0705bbba1ec42f9c550311f64c5b33ef236487272b87b6fb888cff87199a9818b9120a2e2a9a981895043780c0a0dc058d8f355ba42b32992f157ee39ae988f99ee585084f0931b260820653c3c2ec907d47b2263be0440be9a03e8d040fe7dbe081786b0e98dd06fdc8484432c6f92c5ca20a213a145e89e5d2e13335d74864c0aa630787b9d2353222093fdb546e772ed08998073fc0dcf093edd5b2c375276cf670991338c65ab7db3a07308dde6ddf8538840835cb6ab7e737ed34dab3162395e36154d7b20c01b91c49cf0a09b1df9d711b12f94668bf732fa14b91ea53aacfb864505187b6143f6922e78e107c78611aaf05eef3c1ed4f918eaa7e4bb207064286438efba98f233fa461eea1ad727585548e53460de715ba8a08741afb73a67c375408cd37d34257c0489618d724a315b1626605e0e53becc349f7da1e29a10376b7615327c139aaf14e7131372ec17b488021dbc1bf625b1ff1eda15c338c3b3239e1e3343b5315d09e1ca99d6960c0aef556bc65e99812fc25d64d1eacdfce1115b8923bc76aba6d27e7a1990c1e2c6f8cbc373bdee7a9c939795a8dac1d0acc168aadcd3a6c16696c355786414273cd35ab683fe82f4eab3d15564ff35b855cb06ad0bd437d237192a14291619a4cf6db6d15a2607b621fd139225844e145736c589339bd42a74f831aba910e075c1a783d0053bbb98b0bcef19abf4208159cc785d0976ceb11f847c1962357a45239856f3efd351d7e4309c83d1381af900bca5b9f4f36e70d72be6ebe63cea432e633d354f9f035b75492a550dc04389aac462dc0062c1436ef282c391c0fe9480661ad304356650d823e4c647f4079e09581fe90ded9f0d133aae20c5f893f3845f95512bff7ce10543b30f853e07ab01e0f32ddc94c10ffe53aa3a993e5d5f449f1dec4ea5e42973f05a0edf6b869756912dd19aac15f990be202773b586b07f76b07c0052247aee59c254a8148c3132f95c3a1305e25365fb7f145fd3525b44e571ac573e9937003676c125b681aa63d57bbc44a5187792ed17a84674e9b5a522fb0528beffa7941e1dc6a1d6334995d387c4cab24cb5536a3484ed0e9f65a1ee24c8e664efcbf0878d3aa246d57f1d6b0dee40dea22c1a92b012d903ac531c13c35e7af9a95ea90d64dbab7eabb4e11095b287f5499cf6ae6eb573404a2e7d08a6c34c1981efb2ae44de7848a0956312b3217d9014d507bcd90bb7c144443648c4ac990576eb614c2c39089633223cdb2bf572d5e58eddf86579d7afd7530c853c0c328187cddc9a6ae2cb33c0c51c2fe14bc39641dadf621e86a61eb4738b171f12866755c05c835370d879cd4fc6e65777553c294b67581321e89018b6a18f69c8a448dee5cba39fa1549771a85ff76b4ef1dd2a0c95fecb7e953eb0fc600872fb12bcd29b5a210bedc40a91d8f49f7dfd94432b864899bd451902d141067abf470b313fe300a89632974416ff47fd4ca56721662568f491b14ce4907142d7a33b2fddb9d61b0f9ab1710f18f2023694a160628d743408065fcea16f6ce7ad25e58ad5fc1a8fbf8ebdebaf137b34f5876a60283f661ba9213b28001f65009d0e6c438c3707dee47bc0a59e08e997d82bc0fa32c68a2b7cabbb22861c409c9920b7a58b62c2a8b21bf86acb5ec1767d17d45b79a0bc65b43459b80e29d0ca64173c5b1d5df32d5015721fab673fca007f2133aee161991e9cade08d699792970556b5dfaeb35445f28e04a874b741d7a39b92a08c3c82ddc0bae3baf88a6aeace4d8158d89ee503db44abd5f4fdb24e931c2b53eb385d27c8cdc0ed954a7c7aace5126a3d1377d3866e38e29052f3c6279a0411773d142d11252d4811978afeddf6acaa444164ad6c88331635123b7142fb45e126f755e91a04e0e140c9aa67005e33b429531bf195500b2406c0b50c7fb2f401f82c0afbca2856cb12b9e1bb3bfe0299393241a3ec2fe7836638f3d2b6cf6658a230a3555f5d15205ee70855fec42c1bcb8ebf0d5881d17e93f5a6a2d10d60d4532cb872be867d1cd70af9e49c015553a68b34bc27fe71458dc7ff6a1557ea50d5e799a140ff2c67ee840fd2a3e079ea1855fa6926ac6b33cd40524f408ff146aa3785829bb60302a5339163c60779dfe7fca85bdc5536496ff20ee36b72054aa220477da4e6c8e3c862e700bbbb3952f30b15d27be979ff1ec06959e5a090417af4dda1f3803d72f8998bed516eb55f53307a71ca9bcc828e8ee36c9dd00f7c456c4abf091a9867a942bd74010cd887f4beded5c5e612073753702976014a3b77a13f016835933cb7e8efe7a32788289b956982be42862dd73a2b29957d689826c670ed44c0011815e7deb781e180e74171ae2af4ca5cb50ba9c245881a001bf314217226df8281144f35f9330d1918b771b62373eae4e562e94ed8c3e248b8508cc34dba03b1dbd6d832cb1ba95a70eb202d587c8a99d4f8e0071f17a74970245d34bd8e0af026b27908c9dafefd44527d0abad900ebf051b21ced81bd0a393a4e43a36d565ad06784cd511ef357aa2d01f6b0b3c6a7c767e5bcb40e87a3eb7ecbb2e0c5df4df5a05235ea5b7b7aec1416aa10e782718ed5baf1ec195a879d0701a25715de32e957b67b5197d6e26d491b32acc1c66f84effe2f019cb96f01d8dc3d4b6e45c4941d8da6ea2a408bebb3b33ca06fe4f3d384298c45f97a3fef99c3b8842dc3803b05b143548a180a17a9f2e619a588e7623a169de72577174b3e09c39239c331d56c49e8c4a413a40b3823344c3c377bae2db989698ba5a2b28460bf266ec1b60a0c75cdc4528f8b2089c29c76e6296ce93751bb62a5afa986addf9cfa97f6e1041da6ae081a3ef47d5f84c617450eea3b7bd2c8ac8310a405cefa98988106efa24ebef5c09747896151079c7ceb0d0bd9b28700dd2a06cb3f6a6da6ac85d028a106d425858dc5dd224d606452daf3885d7b1d64d315bb1e3c8682d832b5e6958ba47c7461990bdd848a47b485020d74460379dc3632b878b6f0f8bfa24846503ce6973d5dfed0a909826433a14be2f0cb13bb1faf6d0410073b794718a2efd244c25426357f6fbba2d2ef2f85d1f56746645379c536defc1a9f66dd0971911d365ad962eb0f837a6c7aafece1a7d32ea136ada45890aa2a79323055f85aa72ec723e9e66f5271033093d5f52d30f0fce52ff1407556c041a23df84d4a5fbc290c217a24672bae1554d11f77acc835a2a8dc7cc069946dd09931f4818197721ae965e39f0da1c1e68ba1a77df9367a6b5d4f08092328ae6ca6776c0d66ad74697bfd2bfa51a4e52aab2b7c696a9d78b08b4b9fbbd89d5020f67958b09f20315407f0bf2f34d8d24f75229928ce648974d9e23451521903f7f67e842055bd3accfb1d241d2b251a300dfd11c5f8ea7f03f33383af5df3d69c9bceea35a42192634d7b99f1fd8dd868c75bb478439a026e7577284226dfcbf43b4ebaaf490b4aa5559eff1346bf3479e260f04bb0985d4e25c3edcc06256a30c94a2d0084e4d30cbb8f964e51348a37a0dc6db79b560d9d0090673457a5e4dfeedf7aedd6a460bf8e322e0f478c27d9a219c3d502a09f52e85154b4859cc6d126f0f6a8cff628a8721aa964cf897a0432939e0069405a5bfe4b35d330477821ff601a4ff3ddf09d59fe2176de3bbe505c28acf8463c06761557da7e59fd29290825c282acd488be0d0fccd4a6c8131b0a9add176a15f3807ad2ef81a1e562905b9684cb4b067ab6dab2722f5b3c6468fb4d1620557785f8179a6287d69152dab06c1d603ac0565b1f414df242ed1ebfbfba8cec7c01322689ab9dfcfbe40a467d047985b11fa9358df088608c1bafb368306a56242d12174469a3f4e6107795afae3abece207c8d08dfabe38c7efdac68fb04a00ec40099f279e43725302eb4fdc7e569b26b5ef7779c18918de2ea3e48488f6f976b1b4ce9806edd56c1248f3a9409489bc8d70e58af0f41a32d5a80d2c1006dadc832aaff7c32563d902da9f1f41626d8b18fbf615b67480dd958e20da61d669acafd074cbc30bbbcc026cf0a258307cc50f20a920c5cd58ec316b187a07568d012dc003b0f67c66cfcfbd9dbfac18e505e98fdb8ceec53b1a2165d1d7a6c86a0e1315b3212c170f88d1129ed82380232bb9d34418c042f7fa47284d92131756a4fa4b97e2f9b6f3e8433319bf91952b7a91633483ec39c06bec49fd461143833ccaf8c664e0ce4b3251e1ff23392c25a982c2fb7d1d0b8229a118b8a2e45ecff6505f4863a83e12b0a19a805fd19f6207dbf53873a842d60604184c52e641e4eb02081f0324d539a4cca1571a1676f0baec897e534573485e6e6124c9bc5f55e579015222bac70498b15b82c898d45c098fc88ba2222bea851430476eed275347c103bc9f5951dfbe20844e95abd0de14c9117e810ae7ee95ca71cc39da2aa6976d0137ae6c6b41edc76141a2c6bfe6d3423a2cd2adee77fc5ac2c0728321504dd087afb02fb722c936b0256582b61d2e0c0147a22854392c0501987ef32ab60dc7561dd1ec328041a0c11422ae12eaa9d4ed9f7decd5c37c36f51684ab3e85e3d3acd1fe7d60f828c35ebb2ca02659cc9582c6afc132408e225ffaa303c868b1bd29cdac8ed6b97469ae3ca2ec176894e610024808fb419502d846e39bf22e88c6f5ef07271eb93d2fb05d9319eebbc6e533d354b4059604bf6ce6084049dea4023aea9f5cbbd99c3e521b980a573e8b54caf46bb108120689133fd229cd6158d27b0722c4f1e006041a79c17840e13d7977ce21a636b3319db9e68ad02a0aa87833486f4d66878389be7aa26f11939f5d4e744262bdf802d0af017da95105f621d04d87d1a8685116eceb67c7d382fba4dd4cf31c309bd7a52e09fdce706cb34c88e486d5bf999d10147ad80e70cf20860d4ca2e83d45baf16285e37f3cb09cc296fa86c17940617b2d25c4fc580bcb2a35820206208a269df4e1a31c2b2040e52bc85c2d2c1faee908ee1daf3fedbe1ee7abd3d80a991b20bd660cfab9df7e386b84d35bb9b793d4f373f82aafac489313def3a040cf9784ff62c93a6793a4b4ab8854c9e0c597679f435f0bac7124948cc2e18f42691399ce665431ef135af6aa5623ea1d0884b66ef6136d13d10b3677ee1d706e97a5209248ddb5a49f966e30aa7043a8fa5a91d2d88dad7d6ebf254b864ff682f224d39aa042097e1dbc4e4c6600f9bb2e0e0bc8c32cc5a61a37badc31b8f136624851f9eb742c846b150aad7a6da2870fc321f60c32767ad9509a9c7ace9e3a6742a5d687d191b555f3c955023b2fbabe2a70f0c01b93c364170e3b6562dad5c537e89a6e472cd8e2b5252aaaab7463007996973359dadc370ef1c705ff25b605a5f960e6c6b36a3d6117ef1223e19f7095d1e69ff8de4bbb06ca5c7a4cce88f818323d0a66b24092a291b623635768004162878dd234a4ef1447a33a55db454fbc0659b2e2291287cf597c003b0cad02c915464360e7be3aa08958e8cd1e756e1717031dba401cb1ea179fbb3f7ff5625aa146172ea44aae0c131f737dcbbc6d0389e6a4012d5b68d02a2087c8263a6ec401cbdb40b4c0ae8c92813815f6161871c65696276fb94300fc95eab7dc14210c252438bbde0de0de44e62dd6d38244393db2e61d7161a5c3476ecc8ab7d580fb6b7364cd012260d6c3b991ef04904e49900a1e37db989eb815be57e9c2205bbd1973256e8ba28eb0aa4e9477a489a207e81a797507a9686b3b77658dc014ba5d65e1213c630428f934965f25e7ede7a27dd124655c1ef1a1223eacdcc6f565ae08d2f2500d21087b77d74b09cf7d0699b03b0fd83668025beae0df45d3ed80c96fbcf88bca2fccba626ae36129e9b3fb3df35a32a0e8c358f604d65b48db86223a0fd4d3e3c80a82ba45dadc3d8aee6d8491d85c589093dc85346fda92ee98d7dc1b45dec1956c02d0017033e6dc4bc8c9cc2574fab1f41ec3482bc1e2fd6f3c11a8f9530550a9987bcade1ed1ee77db4c008121813cd350d68e8877bd499d17325ffaa93cf4703dfa0f1754265e539c42f048639fa8eb2fce84ecd3f5b26543f3c8cdb062af604e01ee2541a030950c88d39cf77e115c5aab35d3c9de40558f41a954d26c912593f42229a610ce51c58facb417d60480381b9aba7bef5ed55e611d99b12e54367eccea7a8351e5a15556933d98218b7ecd1cdeb8d93df0118a85c17585d31c6c241131211c23bcda35be02c01d6f37b8f26e06a80c9aa5912956110e89ed8f321137702e7ee95c8093bb6db6afb343f64b33dd3f94cc79ebed56cf75cc483934d69b51845e9952d414d93bfe10b72b435327efef2617b7dc0bbaee370ed466d5d022fb69a8c50a33757927e467999ce92f09f883d73b6d939aac990b41b3b84351b25860e609068d61148f4158f74c2458daa021728fec20edaaa70c3c795a8db25ae285b01450891c34673efc09bff859554e12ae7775efd072f0b5bb1505577e516c87eaa26c60b0ec119c7aafe474f63dcf1d948c0444e3e695e32c2c26d7457977e4cb5113c9c0273972b1327c1bb4c0724b1af2c5c6c4c652805738e69285443df404f117b208e5544b0c25406940e900cfd2c7d2d6bb1ac312e93d0179ba39ea911949b9805ea0c623505d58f31d567501f9ec35ab1be3c0b80e1f26687a171aa324e5dfda1280e540e68873c3f1c364cddb4e950edf8cb41515a9e68428cdc1532c36c86dcc7e119bf24eabea947b0344fe663d9e81b81a1e3045973f422196790249be1a5502fa8a637d5c9ed280bb5bc0840dbb1f1b117afcbec4f5fe42dffae0a7d97c1d938ce7eb3647ff01bc20f86ba11189866ce67d1f15fca4ad79eac8f8b8af131fc33a848b581255a00ddec7d14b7f03bf422268c15f71f7be693b9de6cb9a563bf24cb8165c4e2be1eea0db2d413e33892b88a0b90a66451efc917ab842b911c575c5d2b3220b3aa7f0312ef8c1dde1ba215435884267e0105f69c48306a1fd2e23ec472acc65ff54850f4027bc1998dd9269cc03a39a88ebb4086c120ffbcf2465a3a5025cef2d21b860e8c6c718fd36726420a84811088bb26ce8bab0613280c09291a516b0eefe53103d423c34ded746a365b95d1151a54d754046082a725f6ed0784bff332f4f1d108815a969bdedb61138614484430489c596ca4988568098bae14d2572a3d1a40589697316baba8131b8672f300a520383c081a8fa3845a3b84a1c274d992a0f785aa2c985eaaad89f1aa66bfc1a5310d18e49d738dace0657a2e2f1c85c2486d62dd2d6bbefaf4f46d21950093f4b470c16452382a592bc12818de8e9ce06cd209c1eb72e80f9d104a43d83b8752cedeeb5111bbc826199c888509995877373c6c5fe16f923716f144388d48d75a0958379444b585bd9a109bf99445444604d8958eff2d430ec79c174c6591365522e25231ec2094cd764cfdb4221aba0439774a3c84b8f4ad67d3cb46c85ae620146ac01766ec5eed42dc2a052cdbf3dddcd25ca16e28429836ec72a198c69f83e5dbef02a7919a3097ac0279089805cd8ef5125895892c9e80a899808d650fe0f11dcb76e05ed35fabad06ed0941cd02bd37d7142e01eb7d5c0c66d190e6620f1340f34601d7990a2807771996a615a6e7cf87183a2e40c202649597036aa50e68ff460871145fa7f069f6685412e674c86449bffb2294adaefc51792051a915f88783eb3819f114018cc1d94340e187d130430a5a516abbac10406b413a454ffe0243d99a1a71d07ef5897748ce2ae1576ad6e6ef9d2164362ab234e16d5b4dd05a01d0aea59a607342393a468842c16095089f083a1f0616846e20204a0c2f6de8c4e2f52b4704a036014e0704c5a38678955afd9de1162455eca289fa5a11e2cd385815b94074e4a24ec8819d51021f82422daabdb7097a0264910d89c870ccae30677e8fc04d5e59be2322d6970c79f7aeb3a213ee564991e69ed707ee5047ecfb693c5ff78a88020475a6e70d7cb51e32adfffec020f8495d686a9b557cc7ce038902ddfb78635213db1158e08d2771aa989b52328516254538f9341efcba76babf0b9de514821f11c31cf2a764b58e4b39549d4e9e3a65a10e65e8f545239db35bec5940ce6623b6b3999e85400b609e633be0bfd91c514e04e9c79ae45e0958c80657b782b6972bd8160a027466f8331b54678aa7843398b14ca4afaf5b0acd3265fb6702d3df986da91bb40553b3800a7066732b715d17af63babb0d2556568d99b25f257feb98bd252e96a99341e5794753ce0098cfc2f405a7e293bc6ac598530df7ac9c8a90ef47cdd3f814fe75c3a519b452faeae21ff71fe74256d082f4e01729818cb638cafe6ac16588244aa7808bfd85606ad0dd5f083abd67c26b72a1f0bf6e4a40a748da99c1b779dc290b7d1ba0dd36f619405e8af9a2bf5fa00f1594c23dc71202baea240e4ea3c3d39d6829735b0d802502c4bc7f3a450007deb0a2a0a52eb0a35ea7d03f56c3f9881f429cbc27a416090ef2a70c092fe0157434df1f59f1870c6ea785823ec7ad0db4427f648c4bc42c15a071e265e8759a5a44072ab143dcdce4414a53e2a02c0cea746a5121ef54701b44547a41e553aa6333895542c31b4c6fb09161aedb4125fda0fa0c699addb8d7fae86547cd578646460e81278ec8fe54ed347af2f5b2874b836b612c087162fccf6722b86961d60bad3b8f11322c483ca1b81ce23743299f2a6694354e0f3eb426b88189256e9874c5d72ca89106382a8ecca92dbf955492817d9ecdcd4b81c5ea769d26fb18a4f83f765a6e6404e32e3a45fb416e43b905d148e4edb64248a1a0bfa0d968fcddba75b2be6883ef9d69a9a2f42227346e4374ca89ce84b8c7a0c7527ad1dac7327a9535f2515aa25e8423df25ad871752a363c9c19ab9819fd48bc28bd848cc6e3de29ca9c99df76fb7bf52452fc19c405ac8551c3196e779399946ba83618bbf42ffb9483df687a08af1c7acda5bddfd1fda2073f7e6795b6e07f9eb6a4d77f5fee5187b0bd80a54b030149fff992413def9c1e2495fc0af12089a4ca0a8dae03d329771f2f554fbffb432f46b4025d963a63a9d20588e4d49927a36d06768e904be799e4c675da8be4081b19a159e4ea0edcdc81c97b211b2cbecf3365c044b92749bae342d2ca76ea09edaa04f3d0054f1f7a0c3b156a654da58a6b9540e974f568245358c1839712ff254801bb853e400bb5249039448f19670662c73c87b346ab7a2884625c165c3ad9aa8e987e53bf065b9733f11d8274f2e2e6d50e7370168a8ef71fe0663b6a4950a215ec9c7e21f476b09cfb8494a93cbcc48c956ab20c83e0121b3d39c0f97d88e016bd8b853f1184edf0eacd73bf69853a1cadd287886c2a5de0f3763a8e5eb3ee88263daf2e35c8e23744b145f123a8b86a12c469263287af8bb0c2a34fc5172a08d438311f212626ff60527b9722f0642056d581f246760cb7058bcb80a0e06b111b40e0d224e38cbbab952eb137b9f6c5e79152fab14b3d2c881bec7728a21838a4993cf2b7a5cba45092724354b604c47d7f1a5f2778169b79a51cefaa04481af81cf25e84a7fb83bf7e02cd21f39e0c5003e8c15815a2eebcab296fb809c35d669812729a8cd871388f11b064c50063fd1dfabde1f62f65ed2ec4e11b800cb63273ed46e13f834d58c6d25be2d054fdb370063be9db042f92c80684ffa2c03769bacb33f8b77791b28255000c20a534bf62f4ff194ba15b90e646d638c82d97970aa5936e8b6a7f682ea4c06dabd3ab52f74e08a83374bd622149d62440546f22184843a4c27d12cc24d3b27c34c18bb3e91c7761d2f27a940157a7678221709bf4ce871502ce3ec3fdc734c892dfa236fa85e97f4c551e601608740f7c0ba45ee0e6d37fedd93734458d4cd837d5aad863cfb0d824a4efec8d127e9ab6c619d8ec4cbf9a90789d46f2bd912667a505d2881203def93914c0d588a7662228d77b0553fa4bff8ef0558da59fa84780bd1d302867a5621ab600685d5b458acd9d0d012fbbeacd5082d05602b34e1882ec546d7c6b689ee745b6eb3431368f11f34b85f457fcc8ca6a640169f933544246d7e256fe4497f9069b85e34bc6d50e720d16550e7dd719f44ac66a2a922f7f7626b65f223d87fa8502e25b8d9adab8adf8d76f3fe80ce6b73020de22179332d20c77035ddce2472c24c789f3133f806d1ccfeb333868a1fb1f115597719f93c8028fa45310e025a7a6730c10a4fe029a2c0026cb599ccefd628b800118b1dc2c8fb260e62ae8ce8704f12facf747cdb5c445f576386a686fb1a10a31c8ddde9df98cd3aa9998aaef1eab36fc4232fa206dc0c096d3e516e16ea70a37a97ff2b623cd04169cec7ec996454b49a39505bd32c2f87e53007fb45e8410c90e60fef1eba0aec0868759dd1a53d7d0ba2ecf9e3e3c49b4b6c19706326e50a20f6c4fdcb6d8ee7f3bad6acd0c3a394364376ecec416e3ec222c5b4e5353d1b0c0ad56bd29acf0b2d29c9e63de7e9bf50dea1f4e778215476abeb3aae6ee6ebea958f657626e26924b46a8e222fcc033850e829190e69cc994d8d6ea8f0333b45dc54d9efeedf8ed56c83d28001cad0dfae1485d40043758520801662dd0ce50ae4bca904d7b116e2fcfb812d3fb2700ee8a89f8530028d2e49c8058a8613522a45b3182a6af89b72142ecbf905ad1f0d7c16294c1c29708b57d65ee52eefc34af2bbb22489c25f02b28e28e2202bd9ce9de55939aeaec64a40de03e0e7b16d30a3e227860036c4d192d5f8f609e2b6a2e7267c0b17a6ca10605e1c67c1e0ea9d47c8b695209ad77c7b865cb70780a98c9398255b103b9d5c3e91d27dba2011be9a1f65a5896ec7e77e30323d9b5bc9861f4a693c85bd17ad64d5def4ef1d144dc062332543415814ade4eb4542701a88d35d533436b288c5c97f572fb768bb3a9fcd676e3e0cb56a8cdfea15bbc1239490ceb44d8cc9a3085c3bb663864f53931e628fc5c544181ca816cc66fcd875032a404db8b370aec93f6c844e3814b46e9f5cda8de678e05052ecc4cf34882226049050bc579e104e0ca85916ba863313a9ccb2cf8cb28017605adbcc7c0a7ce277aef7eb8069308c539174bdb25f137f83677635af9b04469828c162ba55b8f8808554df2d688b7e5cf4944bf39a5c5f749e0e9f4b4cb1fdd00e06480ff6b2a439a205808dbbad2e0eb475d701286f5d987104139499f2d547fc81cb019b06847b3944af4e1620a4eb981335711e13600afb0c616100d42997a3323b53be0e5071cbbe94b7eea1e03d62befee31c8ecb2e28b6bf534f077c7004a0ba81bb720194b24d965361b0cdcc1b0971f8443eb080c9d07e111dd76de545bd9267f61020af4b7c0a38fd128d55e77716f9c7b05218e785e0126f33d65a15391730daf4f9f49ed9defb7ea0a7a6c66d4d0c97a35fca335e9f528338f493d2fca280c59635c926a488938aa82850daf4f1a5a221a2fd93c5a6665bac4774a2bb165664cd77eb12ab0ea868a8cb29c4d5a5a8a863b860a298215d1eab2cdf0625a8e84f7d1d68b78db2da10d20aaec80f466365f0c23c3e9ac618fc612e52d43912786894d658b7eaf861855b3fbba1d2038e009fbd4e640f0386d50a7bfcd7bee17e21ea7a01852c4dbfd34104142beafe99e0e1300a966bc2620b6601372deae7de5d4b7d76249932cd84d2cc8ca3982a7b1c485a13d773e5ea5044998af10282811c76d1597bdee892afdafc711c42e780b349274e5ef00ab625e07c5e5730901083afcb4e19d27a6416024f1139a61572923178d4f8d220019685a95476492e7f46cefbb248b0a83094e36f344236d1f80195cf5fc8770cec3fabae8e80fbe14954c775bd17d7fe90859a40f1a15067de40acf498b5c1bf65d83df22574e48f6a88c0d3d1ccdcbb66f7438309b3fcf8e765e91409db8e12cbd000147c7d080a82a3189c067f4d53681244a27fb1f0eb66e25df65b96e5e2b13311a876ffee128d2951addf4d1e6d17079fb41283433e687ee2289f5bc376095385fa9b64d0d52688287a0ae56a3f334754a2181f9854a4fa1f98f5357a186bdac5e740eb98a3789902790a0855bbc8f42e2a1877d7033e928a06370638d390d48bdd42e50734b122ca6c13bd1b25821dd21d9d2c703cd5819cd3bc4635ea1cc0f0e80dd244053a7e4fe8688e17f243f2a9fd294af5a118c57d569d424d3fbb819c40e3fb472706b57ebc5f7a9bbe69d77de5d6a64e92ce6c77cb72c7c2d4e18141453be339dc4c2fbf762ccaa2507fb11abd09816e8d840437fcee37b407c014862f10b811aa8e2a87ab0f5492fdc0e2281f25952abd12d096a3a47d2e8119c6644c7cb81dc7298f42c16914dc336e7c80b598ef0a596d1ed9108bf8bf9e428c8ab6426197834987bf040b299850c0a25471e8a0ff4179ea21b75017af530102c6e4c7d2032f9680d11f0722555d68fa15210617b6a1c8eb16ff7665e476858f08100e87ad83e3cde5abd95bd8f67f369cb057ed1093e175eb14f7937fd268684a2777e180be1cadd3906448d6634002f890eb84f57a7d0632c07d6c8fa18577d07db492e2193432e2ae7f16a0b1038cf06a75bd9e4c3ee76d2cd6962c5face4dd510c199e5da60167e02f4c55c56994500ab3b641e037770152b98f388e0bea2382db0b41c1945f714f13661a9976f87b31a444a769103041fefc97132425d6f80bdd320d45041df56d10ea1ad919c42f6522dfd2ce5e0d25c6c0a57284c5986e587cfb7e8df61a5402db3173a970cbd7bf2ccaa1f47f0d1656c8dbb5fbfb98220a35720e67d05c7aeb427a75e9c400653e598ec476ec444050ea025a7a8ba8c23821966a2cc76a1cbad0435c4c1e648f4c847416bb6e5144ea8a3d3065508b5398ef79b2227f89d611a65b439d043525bdb669be64229403d2aebf45698a0f45fb6182251308fe200f2f9f55258626b8958e038f60ce1623fe7065c59e87d2e987027f923461481f41bb0a16d9dcc4c95e03a8d1a4b239db45bd8fffdb0e166672b974162e7ae489dfac8f227b0420a1a61215afd7839ecb1a84c0835d5fec214637b6ae68d230b17ce4345cac38b6bacdb4d8fbd370490fa6c3899c5916531d90dcbb0ca90db659ad244c1e7096568f7b09af6506cc5e00f7880500665ac9d943ab4ffd65159128bca4e50e346218016c7b381b8e2ed872ca4d146e2f1eb041efb21fccf286e5807b335b1408d14ed35782ad3fb4687b6c5996046557c39c81d80cd1d19d97df5e07ccf957a6cd40059b440c05cf4c3cb832432c626e487398ea6b67590bd18794dd880cc0ef6e058f9888fdb792f5fe9f599e896b22b1da986fe6d978a5e09d9422c9154bce5335fdbdea74092eafdc09e2817c13e0bce6a4e7bb09ae169b85dd53cd221f57d74c2039559015b8b5e15416ad7f050bc316a0bec9159d7af94a959b5f05e927077db68f4a38e68fa6dc8feda75c0ebc789fd3ff3d7f31669998cae2cc1cdb071b1a02d9dfe48ff5e8530be6e9b66007e613bbd2242ff7672d581c7a1fd8dbb8c687d044e409c61c6bdfb1ee7686a3b7af09c1149de7f78c8600462b5a7aecb669adee6af82f48aee8334e03539040c018ee7122ae5f67b9d199fba8de9e82d555d180cce93b7083f8265581cb5067df415033f0388625eb044da68c64804a583c308d865c38aa82f055f486f9653936ca5ab26e18da56e9f44ffbeebf64c65ed941ea254f218f7568729861f1c6a94cbf4a4181a37bed46f9435d2fe7f535fe884bf8c8ab2ec42021ccd8a3cadfed8af9f20f625426c3c1fc2340dacdb4f93657f5731289111eb184736d4c1384530f8346eadefdf2908a865273524f53b3a79c06078b3ce2919cb5a9ce7c50c2a825b8a403fdc2884285e8f975ae285e95495124326b6bfe8a6feeddda957a573e5031dc8d6400531a0c7747316c582106cf49c456aa7948c4dacb410c2a47d48e584d4c4de69e2840499e4feb58f24bb5f5b2443754ee388e5bf9f98af3896eca510312f1141919c51424c17333b35cb3cc21908c0fd003f840f011d3943c7d561cde3bb531b935ea62e6e0913d70f463b394396cc4003d242dfe9884fd36904bb333cdb88aa3010550b54c763c2b33e52eb5cc3c8255693ffa9d96876b61cfa4b40e51753374e737c1cc9edfd8f9aa9b8f6153adb2fb21ae132258d20a8bb240f6281608f4ca6e3e43add4bb64ac2c4ef84e3a18cb44b6ed764752a3c5e84f1ed55d80fd148789771a36b6a4e699617b58b58beed73a9ae2157a005c5aeb01b3c6e92af940736502edd2b0ae6186cbc3e34203ec7285d77fd89abb4c770412a3979dc20a0c1fb898e32904250ee3ea1070b1b446b8d17a5685e8cc4c143c257973bfa27aef93c9a89ca3aed188c778aa96c382c2ec3fd5c0be1c8eb2fcd64fa33f05aaca1260d5bbf396e4718e72a73646540eb8b99fee720adb63e26ac97b0f36fd3037fad32da7633d22dc2e7be1615b0f70533f3ae73c3e8f785c3fcd3d9302b6246ab00e8c0cbaa2f4afd0731d38eb9d10e35fb8b48068fc29c424efc7876b5989ea321127fc0ec61ed32d9dc44d728aa24fa4fa06180bd25c4406961891e42593d8aa15ccd65105ad08249f27efd0977fdd1c90c4b530f628f75fa88fcb31d19648e17180c2dda71d59030910703d7268955bd5e8be93ff979448662cc20c026900b674561a0d613b633250bb10544e2294d8685bc6838a1ddb41ca4449d76c2cf68295ba499e977cfee09285898cb2b0cdf25173a0b6da386ed4d7620b3d79374ff41940ee64160418907050d1433c1836935dec9840420b3a02766fbd84cdcf112384f6294784dcb8af4615448ed0c961a8e1c7ee99940eccfe12b243e2743a83d4760849d2e6ebdfc20ef9838b5623c3e3249d50a33d44b1773b164788e5e83ec2fedba78322b049e2f53244898f40694e4a894f145d7399b06c48e28498633f7a08cd89a3861cdad36dc615bd158c4e7f8288df5eab0a67e364d3464c13c7b948db7d21dc424490b5b4f835d9a1dec888652f8d666b758ee17f2c48fc520ad975b26206bd0a3fe9dc9abe3faca91729642383ec2d19dd8146dc26e0e2c7c99197f7b5c4ba847e527119eb4c4838484c75ed83e3610815c5a2cb4314cc5fde47597e43288e4c0485b330999bf3597eb88f87d0e74121ba08fb7e024c2bd07a549f1fd94072111df544c544c5321dbfb0ae1557c13a3df70b7411fbd3a78bed8ab373000a93ef409cbfba42081bf11889d8a4725476aff41adffc82c249ecc4df021242f856148df96fbe22a059320421ad28096f5bcc759b5f6910d0dc532d8f3fba639d2447368020a002246445ce23c10c0185d44b78710001ef0a2f8a53b1408b1e8de4e28c81de1accfe78980598924a006cc66b001dc0a6603fed3a1aaa3fe265f1373176fa30788e4e79609f7c7f67bf7be1a97fbe68f8218188793981f3dd8f2e8e77a823b91f2a96e2b25c6bf0e978ce0a72fc8449ee15bb378c64c5ee07e1fedb8e636db15fbee28c86ac994cc1c02c308670dca29a2cca29095aef870c976f433efdea61dca41b333e73dce1805ba6363dd3758d04ac027f2e0e37f7ff8ff7cde058d79fa33b4c8be5b9d2040e9931509eeae37e8f86c57b9ddb67962a086cdaebdcf4a01361e07d0ff6ab7c5e8ebbabe792d13b4aa5a1ed18aa9edad75c87bf685818b84321fff47ca5b702755f9f19a9ea02130b6e5e8c8246676acc0687c5266549369fac1e10158baa8f87ea554fc31b3f6a5321d7295a4b76c31365052bba9964378ca8e9e856e7cf3d39c90e17446045f3195fe6f41b5ac27fdb49cc2aedc4820856849fd14b96bc47a7c56745b632f0feca2f99d4b5da2ebc1578479598b4e1857af38939ff9052460eeb265bfaec715ef9fd7895abc1ba5cae28c79a4846f1ec217df5c476d3a19cbbf2adde5c3184e487721fb4203093a7269b3dddafa0f3acdb214bf47704a303bb080ddcdc2add59b10245db2ad66e1df00c971a17b3356a525055aa85f51fe9e6fc6c565a176d3cd0cf514976c7a1b7fd1b551dd715a26b3518c856ec67e80784ce7165e26838ea63ae4c88e71ec9422c409d8585aa49cefc3279b442103399f3dc4f82ad79f45fbfc09c84a2cf71939ee792cd7f42c305bb6343df11090a758ef830611d73b07c97d0ac3797f2f74ce0ec1300aecd47a073b2404d35a0e2c1a572283ab95825df44895e79994ded249800a6719d470951701c8e2292f7addb289c6f9b9ee8ae7f05146cda658a2af77da64a2016afbafc24f6d85c7c055179275b5233ea06d5bd335f42f7c58664f2a686df3e529c1789c6ab306e6aa74cd5f6c7bd4b257ba3ee78cd4785399b94a1b92d9c41304add0a0adbb0124d663a860615a229697df67117536ad022cecb992b02df0846c7fad9fdfdbec73ae7a3f8213d2288b80cf41b0ec704614574dd14f998472ce96e66f707ef58e0a467298f916e123956c2478312f2a62cd167e1402e60e80f80af784728fe0cf967d171a2a6c812ea7825804de6d8a4d5df932ac497c256b31abf08adc5fa6107cfc11fd493aab47d2a5f0e84a90b71fa1797cf3ade87813d866e4f26e44eaadf3bbe2c66470a751a913016fdb65059b5f839d98ef4c547567fa348e21c3cef404fd5740b2b2a05aeee0da7ab1ec2c4ad9c0e3ad3a5db59b4a3df4448f22e273e442207f3d2754e61790c5c2fbde5e12ecd02d07009cf237079f99d4777090b80873b5c1e83fba5ef6cff52332705518fcda1a97f7a4072ef2044d3c4e802764eb957894b731b74ca18ea0b4d2e2415e080906084266b3af63c5684c6dc3aa1d15e540c45edd08da78375958423ed78924268fd553b9434264952fbc6773f21ad41356186956ae4fc66b542e5513785eaf8207ef7b89c7cfbdbd99b992a4524388c53f3a40a4fa6015f0a2664a0fa83ee751d72fa1432cbe1a293c6b72b1f157d53981a2e08e833178a750228403b9311e87ea52fb21643a27b2bfc5459c27e218e35c2c61e6f95536860e69891b3091137159a0b6a08add4dd5d762a39a52f5db292b24844eb221c5b446a86228787284a5a23884074fa5a6eacb7fb87689dca4e04140518299aceecf6378b60da5c3aba22985205437076e64cb72b25387a85b9a5c429e05d24f91901900530cb4f4666c1ce2f9c77ad3a75add49b1679ecac04263d8bc92576f7e40f21b9de4263958e7e05bf5664f3f7a05daf0f573ccefa902ea2bcfc75280c4c99e06951e3a3b9a4b0577359be24396228689929ea5284c3b3f9bc4ce82dcc1aa39c0dc4c5c47d57a0590def792b5e045855daea307390716d541b8006fe5aa563ac23b19158d77aa203923a635ddd5a3475109d731d06cd77e321e8300cd84d654c0cd69e6015d42327486fff4c0a89fc3f92b25210659579d256037e352e64fc3a55700b15d49282caf8854f75e600a31e6754b71b605a9a06f300ac6e0370e46f9c7f702a8d9fb482761bd61d3195597d15674ac83c8fef58e422de43444499538cbd63588b9b24a835325446c69633831d3d9593c3f10d88d136b051f309df66c1d38f84a5f8a6075ea68901e4bf79b0e613607f5a1afb1b1d473168625c210726e479f0d72c9be29fab0097304f6bb901c051fddfb15273f29d983eb808ae695e0fb3d27729a943f3619ba0e7af1dae3b5fb45b65ecdc598ec8812101d47c7d924d63998f5c00086add54b647e8cc886fd25615b8fef3484d51a334329d26b8b4884d383f40d392a2e7e7d7b7501c4905aa4f772b9de28737613b4507c2ad4d772747413b058778a9c179d2deda0f3b853316e388601906917cf027c11616fc3c9984209f7868810a54c9401347114b003b34f1a8fb5af7a4a0c50c4c28d25219021873817f5f366f1e85eaad992dbc95f81fb3faa3dc93f40e910249a4d2ce3e63ec6fcf9bb7b5b7e2062a2aaec7af502da5ee929ea92560e022b2d85484a45d0b53531e6f37a171667e1ce7e19c0183f641dfb5f217e04a06f1bc08ec9ef69555e6e1bbfca7926929de44447202a849f4bc3c515c103dfa4cda0c9980d46a4c036e2dcd4fa8229cd565e559ce8435d5d2bb07eac99487435307c3fb9b02c960aba1015907642099b63b760130fed9816a242249a1e7e48e7caecfd822780d9549217db1e48f1b7718cc95fde86db8c8c9dcdeca27e816673c00e91c003f0a4a2b6a286c3d662d41b0974975d3e07240d9f34948bfbbd311c0eb1e7405891cef2eddee8c9a3ced9a92eb7ef85058734166de2a0cb12b7406dc6b13cdad24f7982016d45551223aa55f0a6839b70040e85cc933e71888325d63e07247e6499a1fafecbbbc736b7ec96c1c768e9481d6ddae9e2536928ed7f835d360580e5705ac74af3c9a78b23ae5dac0374a45dcdf4d34e9d38842223fad059fccae288eca37350f9f8680a98ac039b3742a672d3e91b8299200a5724130bf6a7ae1d9b472cb36b5f156047b00a7c55132616ce9dc59081f38e4d6b7279d56b5d9032189e0270bbd7125be2bc543e51b83fdec5c86a0aa49915a12af03dc612fa668883d43a6aa2e2d68a3b24224b9c18c6fd4574a5fc52d705c26960fb3345e5a7d1951212136be1202ab487e80fc8f06230575a11f584091621180b45f6aa046abcb78f04c0dae64fa2e1761fe9a7602b5b735bd7a9dbce821a7a935e615aeccf9e9fa2235a955806bb951404be12a126e742e5dd0722a6bf17dc2d1a1c110ca72a2c7c63fe0b62850a9b25c23fab3af0a62e8678da160a10456d307efdf2b311d573ee08a1dc958a9de3d48c18d34dd272f556eb14572cfc0d91dccc3fc07459237237d28a7159c4709c952e0d4a50307856964c0fb5f66f0d62994eea9159b59ff067a0b20e508aadb1b3e7a4a63009a0ff2e3893674c8e4dcfa4394e814aa3917d7c56213a4e7b105c43b3dce936629302b2bedad1852d50848381737672d7dcc7dd201f10569af9a20f309090ced85a7aad69223cdf1fd98d8662093340b068fb51af6bd6984160e734561b10a18c12d82915202fc9ea0faeb31619a100858ad0ca44a10944e659a09e0a17d2c32c32cbbf05f93641f2a126a4a778ffe9da8edb7893cda07eabefba4876d5af5a51029e1b8d98c76c85d4585b961d350be67d315868df2823cfa6ae2c24e692277a14a299362741d4362b40230c8a6008ce207d9140340d4b43ca0c30337b0beb0492d8e51a5fc31b27c711482b587ee7313bad38060325f32d908f8397063c50582a0066d69a81eff19a0025d96b6b423f570f6c610b09be44867c627b3d8e4c5fe5f0046ae5ab9ed3507155ddfe2916c58f917e0fda770780ec6c83dd2fa1770981f42d7f2f55fba4dadb0029ece6c0003e61112c7df1daccbe8022bbec9abd922d3ffd5be5e9b0be52188aea0ed575fbb458b026d23aca766db1e3968c64ba5255d9d938e9dfc740bb5f5bd8cddc8846c5dc771dc0633ea26c862230ca4c124f980b51b336ff0961163dcc690d91b55633aa19b0a009aba04f3653994052bc4936b422807dd9a8442b604f79b507b5e0186132d8d872f80b132aa64a4dafd5a31489bf1b34bd47166125ba40ee53c8802a0fb1b5d153acb6e73d6007bf2a1e4ad724f95bbd81802946cbb613d824c457871d4266ee4c2c9949fb4014fecab09745cf989f6f413f447abcc5141ee954f39e70a534162b4953d713af880c0ed8884255fdef2e2f49e19a847ddc86425d6afca6f0ea4e81354368d89524836f5fd43d555a56d9d669525ddecdb2a37e4cb3d851cdd9c426bb4378ba8b680dded303048ac43b0cb25d3367a55d28f1e33b26bfd60c53bfd0485da2fceaec1489c5083a891caf92d173f4355b6b5ea561099d2bc257c75a2d21075321a44a01a378ae5da1a074dc0b67233ba3b3aec70e03e984f73504f596b4b085e2739a3aa016fa0b53811dc0319d21a7f6fac1253d2d8e6a3cf0c4d38c340a7c09b1da07e533693fd63d8c19d9456a56c34305cace8e882009954d99d75da0c18ee336a55b7abc5b1b26a640d670a52d15e52e66e283277098cbfccba082284823a08b61c210b6baa969c21d0314d700139c44fc508788ef19664c67cbb248d5d0180bb5724a55d4bf20a63f1ad64c80ea09c40afe437eff455c3d190fa98d0baee56580da3bbbe46848eeb04a6ecf35c2086b54588a040c50dd6b001b820b693074e5c469461d4b0f56b746c984d502781c8bcd615bb05ff4b1813084d793d9290c8ddfe15af790203bb2a81b139b1e1aea655c687b38aa5670be17b5ea251f60743fd8db89687eb2a15a8f4bbc417995ec23cf592b2dccc0325c09ff59291226c94ea06fa6c79721f8798789ed21ae3dd5ada204cd2a7729b67ad88b61f9c6d5ae27998d440cbbb09cfe29e494c1493b73a07c4b5a82bed982425698865237905e69e5e1d72e19351ba2b10072785bb4bae4446b5e5b8b54d393d4f7eb5b8a0bf7a91dea42f610c737fc86f72e0f7222317961ab83c978a89467c8abfebd92eaa279e9b936a702df16c2d27d9a3768365210423c558c24acba5ce48aefeb2081a44bd460201c3a930d952384f23c498ac4312010613926b14ec8c09e172c2d4e163a866f1b01dc1c2671e386ceeec2b6c08fa9c82a242894e6f47e610aff3ec17fde32f2c91d8f6ba1519f1db21619096b3227974ea734409b5a41cddcbc4b31b57f806e50f30797649dbc5e73222d137302f5a9d3ae5684674c3e4ef6c2697492b9081d183db97b17dcf862b35592eef84261e4ba101616e670a83e2b43763444eb89075777ce023ce1a00455923b110a280569a9f9fd0b9b7ade099108ef6a973d8ff2e0ad30e7cd30ac5ddfa168676c7060140a35af93216e7b4cb5b19ce2e7d504630e83499fc4a2c1204c3e2965b9ec4ff93862aad244f7311bfe27aa8a0fe447267061a0ead0c943249a95c48205ff2668d383d6c4ff6b1449215555b5eac6b5e49cd419733cdde7e9dd09f65e536d14cf382aedf8e7aac271fa3c35d87f68d2dc1701f944173f8692ed727015a29d5c33b8809c46f259fd5557e56b028602061d2018916967dba43080c2359cd3334909a304207dfc6fbecaca1e8215e77a47d7bc89ee85eac97001f6af6915065ff18540bd7262db7db9b761f721f3a6920949a388145e138a94a4a6b3c405e6d71ff3f609b88058181e2fd717dffa72c10968f55152c38291fa48670911043f8f4ed0a30cd0f153a6625889483c38dcf47aeff01dda5fc8b01c0e036432224196ddbbae4c1451370b70ef2f67045c46c297918035c03ff2d96554835b4adb35ef7c3944c5d3b4450917d8dd8bd0ef61bd22e35ebaec1814e58e8ea8782a4ebad270b88508be79ce8bd642a30197dc13a05811e08054743466f4037774ad21d16c44ea7e0c4da2a74594932427b82d32c85d09e91527d91503dcd097b9d377490055288e164a9771f1b283b7606c8c4940371e64ba5ebbe01c42dbed147aacbfb4086617d03f6eebde136af89a72987146faa71e54b50f6ae3953c867c29e1660c94de96f7e611ace072a5e661e2630fca3daa317d234b195b33736f1f10d714b0a05a4226cf198111cc2bf0764fc63c021adaa0b886e9a8e8c6a53cc0f20e3a9531a1f24e328ddd88653999b9770c89c77ee52c864a9b31ddf908fe3b635b9cd81c0ea4cfb868cb47a08f0512caac98ac1d0867d10258ca31a48117bfe8667d33d458aaa660e05d815e0d80cc556a36b1c7685dcdc5321a263f2be99ef5d5bb6b262ce1ee80b933913f841af502d1472cbbbcf02f385f3c64825dc65894336cfc7fa8e5221392b8f418da756e87aa560c22ed0c7d01327ccd7a1a55ef923fecdd55007c6c031af8a33810e5754001220509ccb6039e2b028a9243c5d1b52b7d5f657d4920a227598474301106f6b2e52e7f7c2c1d148492e209d900e02941d7c9c1907810720c5c17affcc1ce629cc6bd4e04de13d2d5d9ff5992896d81c00dce9dcc01d7495f539fb26beb36fc76756de398dc279559f25917f4c9c50ea66d94c11f98c5866f171b1dd6cf53320f4162d4a242e2049b949073301648a764035994dbb9db14e234f00aaf761c1157ad76aa80b547e736edc038241424a03411fbc12b70d17832220d25fc281685d2acad5a4a150f5b320f2e4e30248c3e214003d1c092e1a8f58489035a2b7c79adbe058410b158ca59dc058b10135a828be2b95a9e1a350299c6456a2696af35419e378829f518fa22e2dd874dc30ddfd45936a1db3417bd782a6f4aa55c1a9fd80c1067eb0ae2b0c3cb1802e69a871604864b614f93ddb72a543cd6437e45a1118b529223240df0e9af74a6ec5f618471009de71c0d57bf5311cf70cbe39cb541f4df4ebc395383a0fd164abeb369a107b0990a5180d05d4327dc88da734084d7ae634edd3985fe8d8a96f683c1a5edebf5bda8b1c4ca0fd3b1a64de14cfc09f3cd227e39396544bc8f906c7df88539d062c73d02e8c9b89688b1b52ec90097340770518f25bc40a908a8d28b0521883eaac2c9c6f6839021ef0520622775d4785d2c8a91443981eecc18a4d82b8ce2e9cf089bfa9a04fb919a9a3637dfe7128dace9398643f1987297ca7624578d60c04999e6c2a14ac8c7945268d473d50debc28eedaa08e73637ac3a696071d388a87f8fe94bbe742e058225f5b1a0460db24a0fcbacd726806723a23916a0e46403c4246d22739547cbc6859d83ca6efbf9b4a1d2ba64116bab662680ced9cbe88467e761f52112180b601027f036dc9d2bfabb042c4485164df92f2b130dc08fc9448be8dcf21e3098baea87ff6538153cff150c66a2dfb61e2a6341b3a545fbfcb1df27a8564dc1261ea5788cb160479fd5daab2524d63c78ba57b7abb1068b10e73f4a973f69b085f67384528d06ab299bf51f92085df86f472de34dc287d4935e99c48d6168934bb7d259d7eaa70837442e4d288b9a9fa4e92dd2bdf023bac04861ed5345110dd3acd71fec5410034a0a3e39e7dea3b3a2c2a6ddab0f72e75694a4814e26fa94a5ac21ea59b8158e8f7741de076f4cc293470829dce02e9d3acaf146d9d18a787b10374c5ab4621a51bd402a34a5740c1eb08fc6f25a3d77b160a67c93e063a7d18143e61cc075a51fb5c3a8aad25a6d12675beb3741968df01f967f62985b1fddf50e4cd9232ace1e7ef3f8ce092fea6a51bfa22b88f3abb057b5b6de992a0147de37498612f112fe470df69c5962aab869ddb8bea688e2a94b96e02a9d803fd4fe29bad71184a0d43e31326a21698e95df58d37eff6062d8ad7301f6c439d44aab68c14ed4a014458d515022a3b265758becf2adf8ec13d8b5b5896ce17426fceab4e7d48defb163cf4461adf2aa0f1d0a7cf09b2de809234d1baa75db1a98ec6ead030af962faeb74294065fcc96572da9d040ab3a8497f41e2904de0a4f00d90ad177dd83cc54101109fa63a1cdb37e817f7cba71734231eae860d144f73956f41db805457d77e577363bd241ce95bd042e41f38063602bcaf451f790685c8d7bc45be0c89b61fab7af747a5112cba08581aed671c72d2f7be35d30ce8e0ca4db1b77f5aafa73f706707fda9acf8f4b5f70c4a5df3e56ba586f696d65d1e1390af858eee13462a4b7c834e84bc497e98ba74fa1f56784c598d8c742f84c902cb9c3a22fbca9174f89dcf5b5cca1c7efad8d2006a520f4cc80391d32e62558b5346fd4a8ecd6a8561269f58a6d8a46120ee60534ef678a4f544f7e58afcd861240609a88ef2c46ce8e5fafa5a4c9902a8fdcc66a12857d05df52544d595ab3b87ec02a66217522bc7dd6623556b17656cdcaccac9cbe0e4bae97edf954925760bb0a0b185668016e32a0c136491d1892dccbdba844514a2677e4bc4174e846fa784fd543ef66f0eba712bcae783cfeeeaaec81bf759160b9b81841c83340ee21e2a4042cbd99702cb6f5bed09309e7730b8a983b5376b0831c7fb6b1e9c4847aaf9e1b37e3e2cbf1093a4bcf4f85eaa5de66161415876c9c9feaffa6d0f352e4c1e8588eef056abd7a6406e2b4ffbe27e3897bbd2b7cdd489eedbcf51cb7588556552d33b694fa68eb92ee92a2d5976906bf04b194d56b5d513f42741ce8512833918e8b124f7f2528e55252951636ed49a7e590805ffe204a3ae6e6ea68503074b3f1073a1ef7b4ad50409e6e2a6afbe32233682f77548b4a1f7059bdb4eb982fb6eea38ff8596d15081c404116ddbc823c9286fa270af2d0bea6bd9651aa219243c08ea44c0b60ca3737c1c66957c8f7e51abf97f99745826cc4e2f6c178152d1792553fecb614c1eae6762bde881444b577fab251f3191ba58af5b1db0089f2eb43e4621f97f220534dfb18990a89b3e7f83f42e6e3becd44420ead6a8497013899b34582cb9259009e6bc4846c09f509d9f3210a19e741f1259c42cebf9b7a98461ad991a68255eb90d05a7697e5d132003e433f97166295c67a7382feeaa7f766cc5e8ca646ddb83b6b635b61e902cbcc439823a3c77ed075f87dc9b2e3b324e70bca28febb7857901b00f706959c60b3eb6769edf14eec72acec90822ca3b69ec4e1461576fa51a4dae8b64a3e755ceb9652445805b828774ab108057d2beafc1c233159a5adc4a9ec112babaa0119eedd2f298dedb9334086f1518479e9f21740d188d9ffe3cc61644c5715e45aec2923f8c03f96dadfa70c90d167590f52e395b1a01816cc0ee585e2d921e41a3eb1cb260048c1ec38814957f0c52512738d0ff143fdb4713a6b0799a999ce13330ae6d9eb7dd813e5a5b1c567d67941e1251f35dff71811d042c3a0bb409366450bffc3f5a5ea733a9af919693f43989f814673361888459a0431199fe69f02115527a41e7945ace883ce35d534a8cbf9816027fcffa9827d59584bab87852bfab2fa3486dd141eccb95412c0452fd23e7f7134b80333cc9f1a3e0d564b8f170f66a9afc8e71e27a94f3de4cfc5fb5a3b8dedaa31b982136ec4f60f59506a9ec8c42e9d17b4225c7397798a14a9e89de0c1e901f645489b869bb7fc91ddcb0725a14c79eb178364db72eedecd854ec4f702be604923faefb575a9f13408da4ec238f8b3a7396b47d975d89c0a2ea7916b2cdb05437b0700ca268f76b8aeb52e613c3f65b300b2052024e5c673707395cd26e687645f659ee7643370b513d1b55aa1072a8068111b6cd93e08dd14ddbbb81b6b4efe5139a007979a8df5f2befc7d8bc4072bb64af57a4a0ebae1987246e2cdcede9fa5815831a58d36ac7cddc9f8c6a3c5b12831f7e86c97cd23296ec34b162ca04190fc4aee6cbebe68c044bed20696c45b0f6f33e0afe70214bd67964e83425649d731138a0ea869c66239b95e841b7f76d0a862a9ee11da4c948bc55a0c6db9694bc0a24e9fc56f50eaaff2b9be92928e5cc4443bad601ab93eb05af381c4fd445248360ae7f5d4d186549ee9bd1870bfd4f702fa58a99d73b305c29eddbba357e12de3068a60e9b4abd7b51c4d2c63f83b62c3624404a3dc6f519b0cd0129b049a1fc5d4b6bf605eae63b9536c52397b34b7e1c7eeb65cd549134d605f3cb5ab8048049b8f6046042f9af7b4907db0da04eda042fe9913015c9803fa9e2e8c5820caca9a0e3dea21a8e732941ca3a661f277e14596f02f2c1416df9cca29bdefc972c44a6b1128c229c3637cf8bae04528550540112843455686e0aabc457d6478bd49182990d967461c9df879696defb626b79432252903b708ac08dd0879bcf986257b59e26e6a76b2c4dda844d92c783966e8398db019913441217376370e8785a343c533de11dc59e286a8a21a822acbaa2c714264e5ac5c6bc45e562a8e22c4f2f75ebbbbbb7f278c3ee008d5a7675f7f0709cc63c5923a3821ae3250963821a2b2b7f3531c81e4c9e4433824d0fc3a275365924d2497c9a48b180c9ed35a6b31b673eef4cffab1103b507d1c52e01e7f99b3d96a53c9c5895466893349b6daa4f13bdfca410820aab0782923cd18b5eac3086898a325892c6da6a8d5bfca59d9c531962d9204aaef17865f483d0506eb9d4cd2fb0afcf5ccfeed997d9eb7ef210683997abac7effa995befbd6496627ad128efa581278e0d277ba1ffeb3e8c4986851ec3ecf1fa22b735d5b12f7149cf6a4f11fcf4f1d36f853d44b0887b6645ff7ee622f77087271c279a5c692c49e243f34903d524482da9fad3a94a356f5298014a172e48f952ab3c56f8120615345a92c8a1565f222cf4be0c0e4da651828e7fbedff9222fea2f84d1067a61f089f8eb1d87b0d82b1c6379270e1e27d24e374e39b9fa843b37fc33e3d1b4ee6fab2888fd4f830ffcc21e22af9f0ffb97387eef8925f4c34210c3beef837d18c42058412120881d1402beccdf0e0862cfc3588218dcd9095b8fc3fb9f7d3094d9821ac0bebe5e1e4ea9d70b7c7d51efc03c58384ed0f3c219357bb805857ca2f442d8c360324fa6a6a6f9d79fdf7733ccf3c0ff602f414f1ca1cbe04b10e618070bef8361f71ecea69e79ef791e4abd5ea07f358cf27a7be6d19ec59eeaf7cba6b59a9ca4baa9db1569cf5c66c17a022caeb4d5e57dadb586f63d3afbbeccfe444260c9e3cd4d2b6a8fe6ef0f8044cd9da470b79910514f16ec3f714f81c777aa4e0ee4ef7faf93d31558f62cb492aa1395501e4da306c8feb32f5c32e55499b2aa9df021d5f0a4d685be579d7c289440fe405217dcfcc78acbeee443b22675abb8af4e55aadee4905cd303992270b061ce0f516afe1527873ec0c315575c5ac0522aa3e64f6bee8ef1fc7b47097c4324fef58db27da2dc753f32c9dd5823d3b7b767b4093f96fd4c680721a408f1e432a0a748eeb7e1542149cdadd6e30236c25d230922996207dfc3029eb93b4e8a2ae3a4a64c30c2b3d9042d60890ba24e0e624ef617414927f91006fcfd51484a9a49f24a8993f773f72332c8bfc594943495e6921cca8097273850559058a1859aff6492431b2401c6117466f80289889abf043d1ae4948379461ea1934106f9cf28b287ffa4338a9c79fde84c924154ba768e9ee2f44c66996b1e67d228b192a464ff23494cf0ac05e96bf6b5583149038549e69159059687f5b77eabca2c7b728f8b5fcf08d95fbe4ce6433f0dd49fc308fc435464f40a6750444546df0dd7d5ba5f888a8c7eac081115190909111519e5ac0c9efc695d61ff01ebbffef4af175c936a52d3a44c0ebe9e754dca4955a92e5526068c298304c3183357920690254b162e43c00086935a7fe72efdb2feea94c51275cad751ffd2766f292995524a69b978530d9e7fb1e00984080c3c7bae64f7243c5fc169ad532bf5b682c2ecdb40d2c6185ffb6c9f5cc8218fd63578274b5c10629c0862cb551051f004028c1e2a5be255badd30308e2c7140b4c96464d346e72b213528e47aaeb6615c7c795c427c5d0fa10189694706251c10543ea533241832438e8a1c4362c012fa96c2f6772f969293325e7c446cc8143ab81fa47886c88065f483d3c5b21f5cc8e3b7fa25f506cbb2c4457941a9c03c59e2a2ae84a2acb488c2c2828b1a816bd62fe403ea89ecb4678499e9571d50704b617c6554f63f856644b299100ce6def42677cfb172e58c152db27f8c02990e387088d384080a71dc2d659b263272f060e158d9df891cc40820fb87a010c75ac084b4493f28843ed12405902627d95f0728c4bbbba3c87da3d63d013640971bb2bf047336a5a961431b1ff7f90517342f2c0526fbf3d023abc1713964e5eeeefe1c322e74401c4c549cc8fe325088cbba5cdd78a2720606380c91fd6d503a394066072a1b70c0a18629b526a3030b75b0a87ec8c14dad5d29b71629b97b0709ccb305172cb23f757777ef99e12a37bd614beeff7243154fe0a88091e5fed8cf2cd27891051a2b723674c96131c7c90b392eb6380d5cacd8e139c3c354eea7e1d9fd876af89224878a22beb891c2451558bcd0a68b2e4bd4dc67c8be44761b358ff30444ee7f020a86dcffc550eb8fc97cfec782dcbf030a6922976ab2bfc90eabb9fb95ffc0d3875a5739937bcdd20e192fd1b1a04a0db91f2624a3b345992f5165e4eafc8062871abce012c6183d4c61eaa23bd7af19847fcc5fe6efe3fffe3feec319d91f0485f8cc861bd27059e10a1a9e405173263a7c19a30b5c1537362aa46cb1c9269559ef42e86cd3c317a72937d4dac9c98727b9d7e4f6c18a2cf372959ba85bcb17386813a7b9a8999a0e74441d7cc8fead58d3945871c1090a2b30345162ca2156ebf70084c631a2ec5056b27f77af5c862d5b5e5974a9f2e105c90b13d93f6641212eabb57b56e161859d39576c70e20a17396081e1092c585ef6993f6e7f99eb90bd46cd696be922370b353272124c67d8e4de9225b7182d54720b1d11218992069adf6dc60e4b5151a68821a68c5a3ba1c68523e0e456301344ad63c8eda52af707a194470e1effa5647f5ac316d98fc86e8310f927c7ac9de0942d568408630ca52fa2f080a5a68530ba8489b2a2d3c5579713f6c993f60e64300eb965a8f5fb64716a935b0b94dccf038b175f16edcab7cb7b96f01c5d7f5def724d77777dcb738597762ed77744e274b9c67bbb7b3b298eb2ec327fccc593edc996e35f9915cc650efd30c0c4a0571043c511cf2172bf9ce253ee9713038a427675effaae0c7f9debef177a3d23c0433db47ba8d841132707ea9dc7dfc508ec5fff7afdce1b79fdce1b793d169748f1c5b9f4fa9778e79b1eb0c862830a496cb69ad07729c1c501f8ddd7bd085ffec00f1cc7b1f3e04806431a8e3cb28b01347b2378162dce52b5bd7dc19f3bb74ee35cf2217beba82ceed63f5aa7dca345932d1a7ba3b2384a7fe0e8104596932f562dd4da890d93175451a8acb1a2d6df851654610a273d4a505ab2cc383c8282ca010535d534cf9265868547505350371c50b7a6a6f90e59e6577804d5e4247342d334c7926506c3a32734b91bb9394df32b59e62f3c7a9a43c7061da8274d731db2ccaef008ea0954520da824384d732b59e61b1e3dc179c28a3d614535cd97b2cc5e78f41425c52345d534af92656e85474f54381ab83a4df31cf2531d28db0e946daa698e4396b986474f53377cab6a9a53c932dbf0e8a9ca0a66f5c4a6697e43969986474f6c9eae5e4f57504d9ae6366499677804d5040a0984427a8ad334af21cb2cc3a3a7acfc8143663e7aca924132532c94893639f510139bcd66a392479bb3564d933d249b96d54d3dd4ea6a9043554a0c39e4483ec7d2913f5c2fa3c2535d54ee6a86c98a2c0a66e17628e4f64021f65b523e94e33b47281bc85ad964d65ca09029a3f45ba010da171422bbeb51d9ec9e56291f6a4919b5aad48ecf397de92d1d3afc873cc242477a2283fa8d7cc8a148a03772a8b5830c87dc3fc566315a2f2c1915c4a4c036d792f2217b456e37f6094f0cb6e228c2d73d92273995994759143cda9c7d9f3629df458095e75fb0015dbb211d9960c954ec39749e385292e84f7a369386eccde272dbda563da967fd398ea03bb267d386d4e473a4aeaacb75687ca8bb8dd6dea8233d712bf0e848ae4463d2e6be2525e493472ad5fada36f693dcefcd9e494a5226959aebd96cd62d29c7de92ca493929edd2e65c1ce9d7d9288bcc2213f84142bf9b10a13f9148b193c446cabd95e278733f11786c5b92ad69dea46912e9090d499859fe6765797b21c97593f501a0f8a855e441061e7d78b4e382ef8f3fd22a891f759752fa13d54397d29f76bddec67f3f52daceefbc577776ead30fc63332b111f2bc04712c26f6682073ccfd63a2ec59ecfd5fcf24d7705412a33c9549ae34604f61bf53e418061365cf9cdef0133f327e5cd44066fa1bf4d097b9c674dd103529c5b929a53e52f48a8c3581633d248bc52410adf760ad56ebbd96cb652dace5b95cf573cdace4f933c9ad70fcdc02a1bb16933c3bfa361c9b0df5223c05d4ae865d27a5e39e7d334b90493767952781e09e727777779fde3e7dcee93fe79c09f550923a9db6daf36cabe56a75eb8a3d4458e498f6957905627e2ee3823bf08f88f6546e3c73a0212606b39b36b3ed8ebe9c46334ff10bff93dd75f77ddf27f6808fe47bfbe067ef33c91d8edf38472599e69bdbc5244f57f7dd7d91b7e8074b81daa722fda1368afaf7c29ce0f127d39f198fe95d684113f862f124104c95bc2baf27c609cbbd183bc93a4a3219c6b6afeadba7334c0c94bcfe13bac46687605fddc88760a24c02d5af725c92bbb053427f66f72a065f4c32d862935b5fe45f4bfc6655ca7bdf5d9e054d60fadd5f7108fd6bada59f73b1e05de94fdf67dfe9edb5a75c5fdd7d86e3a724f77fdd610f317bd49f35d7fb7d2938c92aab57c51f9d66eff0bed3eb0243f304727d4b29edc25cfd050b26555587699a7232b697b2bb7bfda2564aca52ce2852cfbc49cfdc6d32c8eb1bf15a15979c506b27077206d04c1b4bcf28ae8a14e7403e023c3696ec8da5694cf3afbcc89a734a27a62882b4ea42099ad0c04c9249b9fb160f14789457f6f69dcdc6a3dfbdaca10465e05a5d48eadb6b955ddf75dd773fc1213e72e599028fd2ca25d6a7df0a4910bfce72b1ec6877cfa94490194586910e36904568ba76d7cff50c4b81c76e92e5c7dee0981bbc44a5953c296355708f33aa6617a7138e028b3cc6f87066e9f3f4ca8e8baa5c9d7456793b71f248efcc5527448225ce0c958c33f324cb8f658933d394a728132065a90e31d22cbf00f1c5c9a93b6ecea63487975a2bae08256bed972c5996b8226c368c2b79bcb73961a8849102012f470de0bd372f7705c5f37333bd326cdcb061c3ae01b36ec86edca84dbca690e190c96c0e7fd707534cf1c5342a4800414197cae80ffd9914d31f192d5307fb504c6533ece406278ff74bd29727bee4a89c1c36c89bcc92372f5ce418092e029141fd53cc4006f1d0e171a84a1e949128dfa6dcf2488980c81ff2fb33903f42305a78f8905d2c9b93dbe77f8a8003c51483450c956b73289d693ae76df5551e69a6cae3ccf2b670b82a97109e53ea7e5b32088725b718a43cd2cc268f378b71926f0b1785c3e5406b14356816ee8badca83e34ead9dead074b8a9a61cebd9775d77ef9d92e253b69ca5d391c29da1aa948639bb569c0d97e4eed45a2c3ab5daaec399c999b9baf77e4f28783f3346489c99dbe50227c1f32f190e98edb4daeeb65cde07be60df0e8d9d9d4f0bd80e34e66c1a343c1b78b4488a8194d6582c0c15ad61a3468dcf0df5cab071c3860dbb06ccba21bb71a336f19a42864326b3b93ae67a4ee0c881034719dcadcc989ba327478e304d6e99a63253f8fbc09e9e32546e9ca71983c160580209ee043a2698009625ae8c0b3f656c8fb3c0b0406b139fef0c1daf4307e8844c161828427372ece3e1e189fd4f65c5ae6ad4a861230ca95c35e277e3c60d992852f0e1c08123070514f4f4f4f8fcfcd0d60f3bbe49e34a0500a8a0029e2c7164a090c57aa643878e070000d8dc19d06c46a68ae22c5600fa66e8ca310a28a0e0270001a0167c3b76eca8c0020bc850f1f8665590008282c83091912223850c9617400f010840e806668923e3e412d143a8470fa10b8484ceb86000175c40854545870a8e8c5e0d80000318c0cf1c02f8200001a872b8478f1e423e7c50dd6e9b027c175c70c1000a50803057e468dff4c80c110d0db5bc7c41d0802232001151cb0bcf92017e18c0009f0e1fce124715c39da27af203831f3fe85021651b525055d9ee191800c10083a82fb487581640320002648c943158e818177014197c07c82003508349828b5406c9203173ba2c71639a7c4262c4c4f972535009ef001f06186000e40007b0410402be0c32c8e000084000024021084040488b4021f2a7b06ac8b1fe0d6668517a6f4d724bfe90594e2dbc1c34d43cca6f713c2183445cd9b34b38b34a6f39a513a10530125964da32c8009ce97b5802cd5474fae3d019b9c8272884cccd1e4e0c952c7162a2724c00b7a81bd6ed2a463df3223af27c23fa19d980f81b79551e659921130328453e6575e41297b3ca23ae6f3cf95364f6c8207f1cb2877f2d8243ce3cdba754c42183fc67d72903ab48a9cc27378c09dc3f71255425f5f3bdd35642fdf9f9c994cadb8f0b0cfdb00c48cc66d4cedc7d5a5085a95bff26ed97369c6def4f50014cf2fd6a1720730fe9af6fc3fea2aeb316e6777fb45dd8fa1a4a1bd234b3dc55b1c8acc81b1cd2b41a762f3638c46db53fcb34cb6f8923cd3db45647a27aadd532e022b1628ffdfa9dd8637f96ab387337220b9a29e5ec97b9840bf433c955d2e0c91f97abab933f4bdc1b36d9ebfb75df7afa5e575d21cd13a45df75de7e2e04d5396b829b2b20559e2ded8f29d44cac8738af081aeee7385324fef6ba8c5134ff86a5dfa2b38a49fbe0489d8dc23752d60b12a78fcdc2ed6d0f5157604b64f87f4d4afb9f52d31b606fff4ec2928648acf63e6e2e1c9b90396463851ca6fa6f24e96b83357f98aa3dfd81a1cfbea03882c56650a8523935c3b51a4b9a7cf6039c59e122e3032c9798244fc1d898b3df36b9e5daf38a24e96b823a4f20559e28ec065ead9ef7e7622094a70eefe5a51d21e2d8e5e9eb0289832c94e3c19041944ff460e2791c789e58ffa3cf627c841bfff279547fb22c85cf6963f6e1efbceb7dd0883dd8b14d18488a5eadda5b7a3dd6dbadda461569912a86d58d3366d5dd74445f7f7fad5a2f1cebaedba74f68895c43a22ad9d7d976f6a6fad230774d0b3ae922a48d7dbda800c8376c4019286441ef9100e79a4b3923018b54ebf23941ea1df365310d80dc8a1db7fab38a7a444542e28a5344c7dbf32c827be1106cfaf2ece1e0fe43137c250296590065f76116fb9b2deb57874b30b8484a8534b942d58e4651afe3c95bfb2ca955ba1073dbbddf71bf140fea83fa47523093c7fc4382e286bb5b277058774f7c888c4191db5912b307d6ba95fd927ed947e457c537a963fdd4cdcdaf25c2def56b196d2aeb7082fd72a377465cfe572cd29d4aa4558fbde85ad5cdd87b825791d6d110df96aa59d4fcbbc0cba70888247a1dcaeec0a897a7689e40fa7f40611eeb49567f2470e1ee243e56d51704ad9e0c9250a3c974c90a50555b85ee1f9287894268bdcba1591f8b7dc47cfe8f733e3e17dde2722f1be7ae24c48888b86a3129aeb1bd56f204d7331ac62102250834e1cefcb1d3a787e0d67fe9916e7dbea5aa1196209f6290535b05e6716ba57b80bf6f2f53d4c1401ec3f1178fffd035caeff5c2f825acf7cf099d4bcf0f5b037f27ad8cf87c1fe137be67fcfa4b604fc97f7af37e2fdebe7148dbcdef54bbef76a7431b2f32f71499751cbeb6d2ff10411e0df11851cc81f16f6cc7f070a7ba6488291d7ef5c5903640df63be29225d8819c49ed2f0c8bb80b70fff5b01f85927889b70307727f0bd06059ab81ff7960c8a4f6854c6a3fc3126611cff3bd2f9cb62520882638903f090d90b5111a206baf6f80ac81df8fd3f681df7969262d292d25cd07c39747c329963047e84c1f16ce1b488211d8bfc4254b5eff12274c6c32a51cc8ff05fb71de602f6baf1fa7132ceca7e1fcae89311974bbb73df42558440421d7f5dd222560a542930a66a070f1029b248cb0221387890d9a37287c0003144d7e28438e9c3835a6a812830a54a69e94a13609bcb250cdc0464a1357b2c40033021994e07cc19d210139c51b365e5039e38815aeb0525e52d8a10931c86c3143026aa29c2122a909282b7052925a58028a163550aa40c1cbf8e04611533471a1ce1a26cc5c6046882f92806a12c4ad4e9729667c81e5871b9aa8d8e28684049d2c2d4f563819637ef8828a195daa84a1058e284fa6606db1c510677c0c09481903b3c494972757dac8f0c282c9268595354e925c0c0fe4e399f441055bb4900486ce132b669a927008428aaa851fd8d09180064d6ca480fa828a2aaa60c3840e3e6ce982c29434611041040d920dd50f4c8021c31b2f4f5859c22df1c2182f53607c20a50e27699070f2c60b0f633cc8a18528ca58f3258ad5170eb0f14083162ce870860c150c1146cc0c5764926861ce073e9ca192830b7390d6007139b3e68a16aa20c3e6c50872709832b3020c44803173637c8193c3e58a14bae0c203d79528e678b98204250649c74986335cdc90a14519cf92808cc0ad8c106260b519e20c2b594f3aa4c00507284ec8f9403e9e547cc0844a992659b47c69e1094a564da1f3050835b62d56d8e2831e19d63439b166082f9e30b13d81134306333d1ca185982b362d70a1c20b75c2201a83454e19459cc1e5068fd3edeeee5dadde649c6cf1bf9ec685dc4ffb0b2e0d94c6218185c4551e7fea149a16283957c81943e6cd0556564042cc9498242c3c0bd94f7e5601536382829b26a65c01f3658ba95a9b344b9c31439b3449676af3a5cfc78e459e42f3762fc632d9ff2c0a76211e724645a2bc41cf3a48d35a3fe138d3a095bdb3b9bf5b595e2f863475fa54bc5ee4ce55e1c910d490e61009fdfaf3e2aaa8ca33cf5c059cece2c045c59ff92dcb8127ce78f4ec764fb2bfcc99ee808161c0d1408f1803ce1163b001826558694e0eca12a744956968caa3cdd95c9412120d73b252944f963825a53c29c59d61cad2cc1ba43cbac0e4fe0ab2c419f1943d588e973fa3eb96fb4300e163388423b6faf4708413b29c47a2072218d9aad05a7501fa9f0c1e256e4e77f729652aede04ddcd675d0f40c28d33994ea26a5a4a2b82715c9f675a47a068a348a66a1587ad6be94fb635560cfa9490a3cba541cf9436699b3a4704b385ef637dec6d7f8d8335929e5a06496596699799ec6ef3c7ed8cb7c0405fef7aec79292596699659659e67cf4d4faeeebdba73f5fbecc2db49c5c51ae96532baade5e5f6009ae60ab1ed0e49654d3c62a35d5a26adad8b6b1eb4c35c9fd2e2c3c769d6c1d696220ad7c48d69a26b3ac350d29a9691dd6c535ede65a529aa6247f50e921796b6161eab2bcab736f4da3923f7ac06a9a3b65997184473d60f51087821ee2c0699af790659685475070b044ac384d732f59e61be111549cac302baa69de25cb6c233c828a927a29aaa6795396b94678044585d381a3d234e721cb1c0b8fa0a82c4db084a569ce25cbcc131e4161619280a9aa69be25cb4c233c82aab2f2b192d23467ca32ef844750529402b28af806e682e5e300878059ca2f0714ccca670709269840870e9815aee32cdfff605678fcff300cc330b451231686214f4863270cc330ecf1ea212294ebdb5cc591843c916880f1bcf7da4deb57adedba7b5bad4bade09166a7dae4317d1f08be5e58065d1828842e51914a32487e4a42aa1450039abd1dfa3b94d2f862344021f849f28042280f58a366f5eb5495e2f96858a5720d1ad6a96c031452a56a54dfb8316f5408e4ead43359a3860d1b376ec86ac864b2180f0d996c07cb643298ec05ca649dcc02e0358ae0bacdd6121e864521c25178e4d1c30b0d69e5efd9850991e0ab67b12b57f078715bb08dd80d9fcb0307286486b7e031f61fe682fb250087804b4ce8999c0014f23ee0182c0a7e70482b1765d182c78bb1fc502086bf63c70e1d1348e0d3b363470e1cb21b3b6cd488edd8b18367078d9d1d77c78e1d3b3a5b7b8cf8a8cdb759cb14979c70c2ec1b833767f7ec9eed3ed23cc5264fca3937e8a1d6cba9a525ca1c28d6b41e22a20f954d851aa6e17d7ffb392c5083af38c4c51896d24644134b723a70abba72dd5cb7afbd1b56c49cb3dddd38b93ba5d48d921ba62e6dacda60759b5b9b5ccfdce4aa0de7e62aeb882c4b26070439354ed5a93acd289accd9dd6dea786ea973a2d3c40d09890a50ca090512144a71706dea64898b420aa5928235c7add626e2e491ded913758c968888a69860f49c2fa0c923cd4e79c459b2f97a4d149b1dd82441a1c2136cf248a14081cd1266e382bf67af595c315d999162eaea762525761755391a85941b18e6ec5aafe8b84172a7d65e5d5d65d56abb6e0d952bab3671d6505d2551a9ed96ed84acaaac9934937cce773549928a190396b8aa5b1e3f494339d54c89623e2961c33de59c9f65aa0d0b38a8a9820305281fda64d1c2290cca828423c70c275560653d91410c1eb880a9b304458922e5e2c42a9d7485eec73e64a3690920aeca988325e502479c618597386baa6842478094b38a61e04aa171b97e8bf33092fa73d22220c8353b15c7292299df2d5e24fbb8693d44449ce57f4c7295bed3ddd9dddbddd9b9bc2e0cbff176b8258edd5ff1eb99d38ca41371bd3ee47a7f290249223d45ac942dd3ea94ed8f3c40b01ef0e8a11f4efe40fc83f81b75e06fc4ffc83f046a65feef2fabbca2e31eef1882ec35534abbb0dadacf24cbac26cf9335ed5bec212232c94c724cb97b9a71d759042c02825c33fd31f3944155ceaa88848a3d44fa65957aad1e805b1aa5180ecd931cbd74e8a51b34ad7f38d96c361490723f0f1ffd44fd45fd418cfa8df450d7bad28bd14461eab752362914b2ff7c900351cad9cc54064d111c82c4c59ecb818fec0a0b8fb660df5d09745fde24b07ba90977669ae5777e8553cebc97357bb3dd5fa0783d97ebbdd64bcee8d3d0406e574b045f999220bd5f9f0ca2ee7246ff9b940661adb53f7d645eed688ce6957acfbacded5669857c68018a31838ae102947d9a3479fc89624e3153fc3e89a901a4ca620667e3a44a952c4f7252703fdc10459d32514f37ece024082cc64c88d58a72b7d6666985c9fdf46259b7a458c861ef6a581464c8890632aaa8420c18946a4e831360cca01922cd126c6abe42181c4503002fa898a45a240b1386e7d3b379df5eb1c8c5a4f0726bed00c210d10ae3d6ffba2c14726fcaedee945bfef490bca551ca344b5c1a297914cab56ef0faa5cc60524aa9ac67f353437d9e985394dec99e89f0f58c626565255484e3f2de10bbab0a3cbf793f4a9d765df7f58c525927fb671bfe7c5fd43d4566be3d4566a63f4116b31425c8b84929a5b25ec130ad955a6a6112f4f768add6de4bbbead55aefaedcb7d65eafd6b6a4fd6b5badd6c517d78ebead2f7357bbdad55abd0bef7eb65a5b6bd79241ae1260b7f332fd96ecf12969811cc8ed5d77b495a907eea42e388c4e4c2da8eade8244ead77b2f112a7ed5456546bfd239b7879ab472c82e4e9bce1a8575d7b4b37fab0cb220c80da3dd5da65f658fdb5e5b1ec8ed4d6510fd1a82fa84d78d843ee8d14ba98551ead45606d07c2fbd3ea4dfbf76dd3ded9916d0e5d2c4b684316e5a3f6e25ac7473bddfbcf6c7aa74abd2121626a7a8a91b152e773b3b5d4b4c2ac45cb08245c12454ef168564507d241fba55a92e8972d6fae9599581b867f55d4bf8c537a1c59f51e873959241759ce5da5f93e4b6f57bac38a433cea2c2f5104fadfe982c49b97ead3aa256bfb279922b4fae5fe1344d8ed1d224d7af5835cb4e9f1496fb9be64f5d379a429613c7e40f69aded49bb5a6bad580484a8fa686f246f7ef7090eae0fd93a1accaa02331cebcb2229a594524a29a594938a9f4e5dd6b387b1c1fd63ec93217ee2da60aff027a90c84233d7369c529c59752ca39e79c734e39e794534e995e703081bffb3026f07c49016fce3967f55a7d6aadef220e6355563d66c34f6afc46f090e62ec43da3b46aaba5b706d9572c6a33f0fcfe590de7b7a8c4849eb950929e398f6982094ab2de041f3e7cf830414992ac244a4c486282d08f912b10b242c83a2282074759b2aa2457265429c15a5265c4870f99121e9d9ef49dee719ae9185b116e892d99be510fe1a8e1946083448dbe073929993e084da391292ed724d34fd2435da3efb393c86525aa9ae26002dbafa0065a2e117d2a13e92893b9cf9016913dbbfd9d4fcf889ace686b0a5b318689329d36a97ad53823fb1bd8b003fb463edf01b594524abbadb56ddb5a2ba350b06c0399116badb5d456aa44521e7d64b7b9ee5a6bad5dd775b6b35dd7595b791091853f19080de434842357537c107cfec83c12c2bc9a8d8ffcfcfcfc1c09a14310aefa48370847ba3f48ae83230f5adc802681c70b42ee480856220489f3f3936f961d011555109e2c714b88b9ba227729be150531bac98a62464646416ed7c8c7c7c7c72848d1ad2888519191ec93000ceebfc91c88c42961cb23f6c9b1e0f98ff337e85907534432df88a8bf839eb9b5f64789b35674b76edd7a071b04a11ea8d46c786539e910d50c042000008315002020100c07444291389426ba228d0f14000c7ba04c6642198a635992c328868220c618c38c0200014080214ccdd4460120ba346adc4421a11c1c6edb944712fc5caa9f9911bdc36fcedb969e0ea7ab09aa55f94e98a8f6aed234b33dcb83bc974d39311cdc2563de7c07bedce358d9ed4580b2890d1521c78c9ad82c3b996838c7f8ea45427e8d16d8b37bb6b457734d3b085f76156ca142d0780cd36a080dea6f53842052351f7267e9b8aef5efe01ac7b98f29ccd454cb9504ae6184a83c5691c09f61b51ef280b40acaa0baeb779ddeb39d849312d4d92a503803caad322f92187d921fd2c0aba441cd7f9668f6dc8b562833458467e2f49c316a902f26274e32f482662ac6552772ae687988826c10e44cf325a4f7d269ad61e2a56eca4de8afcddec9b8abdd02194dc6a6777ce4d419cbe7062875ab69b4c93c5a381c83ca8860e73612a42a8c21728f262158901f984868079aee88bf00a3f518331411e234cde610ff2865c074517142623b1ec1dd04220db88fc84cd47015924d464574b5f17a12e9cad080154b8036f9b263db17df3c23a493c19f7a7217f2f461c7dbe868dbccdd4b0cecef5aa6495a027f6e947f1dd3abea99912c78db72d0d9c00278cc7263bea5def16726273e7a93ad3604e5466e11772c2386c9290cf913c9f4a8f880039a793f636fb79347020258ac904e48adcdf0b450423002616f5d2ab2c2399249e92c30aed0d16879106135b4beade02b6733615be750fd4a7e279eac4aefe2513f93e14e6c6864c84fe936f204d727598eb3fc6a6c59c7f040bfb86dc0a3b61a8c7af5aff28d752b07613f15dc15a0042c9a482130d60faf8f54b4749fa95470bab095df2640a0cbe726051fd49fed0306a0b663e584a7248d1456b9cca565b97fe03ee3af6bc5413ec852e861249445566c47982024aa8c16a2bde12c7c60e64d0d2d8ee0e0db0d834adbd8cc8b1e5b5ada60fbf11aac1ed2762269b5ba030fe4bd517d01322f4472479421cdd7a9eb810bc97625f7ce9be62df2009322619f591bd94787fdb311b18d7103a85f3b607c3f82a4888da33526d58fb595e09789caf1c3d6bc7e3ea608b5091352248e5ad7c960cc70ffdf72dc5f5c0b68c2c8b411b3e8dd3efdd1bb52ec6530d3508aefb25a463357bdecd3089a30e2cbe8cdc73fb78fd41fa454550a98328f54a9d09df1b89b827fab392c0891869ef79d7efa0b3fca59ac28bc0905c2436d4d730bcc9a88b66e93c1c296bdc1d11758129ca2c9104bb33d7606d16c76cc6b1c1791846644e49491bce1cb78f0724303e700fdc37193483007c9b932196e258e5647eea6eeb33c8c0e69a822bb07a58345aadfad012a809e194460de593f1dddd7adca1ae201511515db1dd7d79a080e281d0d2e236aea717933d1d1b918623359def9a54280f9727eeca093e95ad48376817d0dc228f26c3e947becf4c479c547f33f263581e9d9b9330eb05904b1fc3cdb6e850e6b0c70843a83b5daeab33073be89eb8d5c3683c32738c737999f6ab079429f5677a63554b378ee4c6c6389647113dc084df0fd0a62c8dcf48a31b88fca1863698c9b9f657ac411315e3c1d037cd820104d7ca14596a4c15fea759f2c634394b82d4655444064820b65e91b28f622da428a4622c281753d6739be210a77e5370510e9f03e69627844c415e2a5ed1d31d15f759e328a65a07f8e5170d27d051f842d5726a6a49bf08bd08c99f6cea10d6e91787d60de0cb3c4db59b0811669af5fd89b012dfb743e6f8845cacb3176f3015ade75702ad418b9227d773cc038900d375a45122f3b697c36da537620b40785bbd12e84e55ba214ed00b8413f5a5f2d0c74b394f69b96e8d6b3eaa21d91f009d12276520797186336ed67a7b8a4686b4d808166889fbb6ca3b30c23ba3aee487a5181df2b5b9b72d9c6dd3514ea8c362e0e3a3efb568cf50689808d7b73acddb7ebe9a0ec0901f7572bcb3985920f675b48a1b940492d70ccbac471ac65d969188b6d814f1f72d35f87ac4097ecc1b2167b22fae62d65f4e82afc7922301dab8100c8fc83f1efd8216635f0ebd96c10c52148e66b6910d431537985632a74a281d56053036c213b2a271a079ccc50172587d95c6987c9d63dce466433ec84150ec4f00f6f01ca71ae2d25b2d26814826a356ae271124e125a9255bcd5a5c1a0128a3844cbb84e8293c02c822315ae03355c4084af357d26a7872a8a0cb53e3bc8e086261c5f897ef309e2ce7915b996eb9ba18e7ad6c300095500b107986cded67207b2ca8dd759d1e45190499b1b131f8420636b1c14f81ab7af78b00900c889e6b8585b8d3bbd1ed827fd08399a740bd7940f48a3ac5ee8c7e09021a3ec795769f3156fe42f5665c655b9e58a79dc748ea373b0d7c38f396e6be3aa96efe335448044ea0acc18b0358d6a1e44fa3788668bd8ed65edd896874c98bc63b0c5f2464284a6caa9796649fe2e00d38dd3ee7f7316640bb67769e3485571e93f2b55cdce97895f230ff8e72b63fdd5577d406f0f56ae5cd90e13a26e5b731867898f413ba55d8840f5555872230bc6d0c96476f3d7994dddca843e136bf0d27c94e0b02cdecf77509bff713e5b730ae089a596e1520894afa6d62a88cc83effde70abf4b1b89b2897477b1666ac683b319317c1d781214961a0514d8c9159be17651ac621369130f1db0ac1fe295b08ae1ebe44c6105f3a2bc3b2c819b1f1dc9be81aacbd387fc6db2cc6e79755620c3181fb8f09ccdcc8295365fa956c342a6359f71c49e1d2643e325ab76d69bc4be89d63ca0b38cd2e90165fb774433c626809e302b90889fbf85e333c26ad7702f73868b090f6e3a9ad96fab6949de822f6c230c91afadef2810db1c9867293040c792dc352f147489f793898fef5c6eb08c38e9d2ad5c0964be18ec9f03b8604a14124352958890e592e8785b12331c57aa0a8781f23930889e99b68d40f9148025aebaf85614e15bc543fa82b6cb9790193f244a94c734a848221225fe265b4db084244af89257f2a938a50793ba98354a01fb36085c7be707ec11471ba556190f80839091ddbb43a7da84f6f46e78378d8eec822c3e6c986cedfd7c248646dd787f2d9c085f63486805169e7e7fee454fb4ef28c6724c5996088b7c9b242b78252d3a622f4570caeb37f86fdf1f590906a9ee13f9204a58ff26e987c178ec9b7578173651f4c7b2e1a58cea3292987208fac8823c990ed904a9fc21913b6f4e08bfa65f104c9c4a1a3d56af30b5176b8531840e07b1b6c57b72b3c8e85184c66902a4ce6ffb3b61489b7ba1c860e438caf7bc32d13816a04adac1e9e95beb6ff41dff63eaacde561ad4b794a0cc8f89b247fd76f901792f4d525b6d3c0ebf06a19cb70b7572bb3da000cc08bc25834567428802f69bf4a8214ff0854df844f67c562152b96c8ce5027454c1272025c212307281775209bcf5a077b1296a65bae8f6afdcee86674266f2452b89cc0926fac8edc34c1036faaa6d4fe34fb7fd4dbc0ff410a24ceab6205a3f268f0e1bf9e0e33bae074f0e4edd35e82bf2fe9837174bb33edadcf4827b74e5db8ed01c8a235624acdf846baa056801197595d6dfb94869e9b08ca879e47791372c943bc85cc73784f7ab1b392b07d4d3578fdf6d8abe0dfb2237b09f85c3922a72750e518c4629a2de0a2b1636ead7c887361575d8254d14d9e171a7c8fc7103be2fc203a6cc759efa113afe1b8202de79219cf2c915380ef7f113d1387ac044ae355eb8416fb2d0572e6d85c24e0871ecbba9287c6b8f240efe14be94c59149d6b2b81645863388ab9e31021a31640b3ed0743ed149cc241d6c8b963b956eae0ebae04642882a1189171cad0eda4c1000283b934383fff80fdcf1318bd4647d3e5109b801cb4c47a03cc7d16b94083d0a11dea14f2c59f1130b188e8d42da8ef62f5819680dc0c1a6ac2aaac0e37d5b85ea8ed94870a774b410157c3adb8f24e0b174d4951887a0516c6d5b473ece5d634f9912a2ce8c15d429faafa152be9f6930e7172b8cbd0a0554633d99a813f91a8b019fe333efa03dd562eeb531fbd7f3bb3389c3b8b1aa7432d6e92a00aa5f7a38b5e9999f466377935635e3c819a694f83209eee756e79fd0af510fa4bb4caa423761fcfa48b1e7dd8c51fd3fe1ab9a7ec027e4046ae5fe3e63785b1024c489097c219d442efa7b59446e32cb1d16000ea326420b7a913014379e90fbc15f68bda3f613f7d84ffa1f59472939583c07c1f42c03bc497056b320f15b180a44ffcecb089080a6d613fd61fbb3de18252f23be0d4746fb76498c426c33405976749fd7c0b9826a503d65da6c895f632fa0d2e6c00f585adc000fa771b7c5b17f59bd5e5a9af71f67554c38dcdf7fe05facc1347348085c692da2310c3e377daca9e01b600c398205b1778069590439933364cc7e62090beeccf04e239ec06526f6b761367087fee1bb6594bb785056410b6120362a49744c5127ede6a63c883e5310b8cf64fb513314d570b027cbc108cb26f9f4c3047bd32812378decaef4deebd2329a5f3c5f46cd82fbfccdfa07056e26e7681da59c6c282214ff2d3335f1540cd3b6319a0c5034e12fb3fa4e5eb71091b5e49ebd90d6f70fcd3d426361aafb33ad97fff405bf10a0e6e6f64d009dcc98648a947cadfe572dcef888d17518a525520446cd01800516a062f8556af715bf49b7f45c5e2ea13c0019fad004ce34e947fcbba9b4628bc4102f47852126676a6a2b86b8d9914ea162bf4bf21815bd86b621a1a22f195add1fb3207cec951a227564d168a126c08234310ce109193d6a81e3d96f6399a836acb40aa1d0fa56c8ca70429181ec065c68e10666a512d0825f38df4568ffb80696ade33754faffae2524fbdc9b67e5cbf7c7d002c4a388174664dac7768bd80cb819c481d192562bd8a3476246fc3734d2aba835e07f5d49c6793f4bdc6094e4cf587a5b716d5ecdf57db47e258e29e55654b8f55fd5ddadb818c1467ba55002b14eca913e670f98e9554c7184452d4ba3f07c009cfb7d2b44a64f27ce3574f870e1b5caf2a500414e2fc7ca2a3d5dff04a8213520270259321ae25a9b42b0c72cc464158a11594b4a6cea180d3d7ae92239a7747c2d41e9636e38e5d1a4477aee83c2e665985c5e5991f27d72981c75a032d8606a3380e98416bd15f2f005f7aca61186c7c2b23434d80cd11c00e7785569092a2cae37387bef08a445bdbc80c39db908c38907e42d315432c20034f6bc7c1a88ba2a06bdad4de40b83b37c2d32326e9b8d833dcd603096b7004f4ce624e4a2d48160a067c0ad340be641559cee70160e84524966df4186f33617837c35c1f8da43979715ba3eb3b34720608e1271f6161a246b8aedb49f043db10986eaab507342de841b7b4dee2d98ae126a5fbde8d5dce3b5bb309e5bf9c8fdf63eb96bc0458afec9e68cdcf595d893dce662a7ab172d621dc0fad5cc30d2f6f0c9112da19fee80a111b7dbf04eb4dce84630db55bad71e79f897d70187ca620b4b91721baec41cf07ef98f19f5d30a0fbd33dc417a0b24495f827eede264cf3ab87eb843ca71b8a6e28441c426b1a3735d1623532262f35c937be13e6529fc871c8d1d42eae7fca89cca635047e8d25c68d015e243e7aa91cca1ccd23f2e26714cf3132b2577ffda85a94c0dd6c01204d4cfae9dcb02491d202455b210ae5eb984acfbe3504933b74e1271e30744c6c97f0757379e744a307b80166f2a1535162e5c739cc057f4f4540b40a26b4047b24c86c2f79aea27c3abe611a98d1b6d12226229cfc92def2ed1979bd9ca166d879ab4c87c9d9deb7c2064703173cd27bb780c19965cac43b9102e513762104b773a8626084a7235bebaa4737c1a69cdac858ecb1c15689bbad9f334f29ec4a18f22eacc4bac89169e7788eaa42ba36a405a5bd41552a955479b2632f222dda2d70f1360b43a26460c12ca5f9c3d808621d6d6db6a1d74d67e883c466f80250d8d875f0bd9586d05aa5bc4d61a0d8fb14d842129a8882eb7f859d01b8ddb054be38da0442adcc7908c9e89dde10c82018039eda36aa3e103ad60b6447270e3e84da3c1ff82361accaa00a4c6b84e3541052bc22df16eef709a648a43d01776c4153b36172de4b61919420108dab2a2ad3d517f75592d9fba840678b2d8323813e013c7833889baf64b5eb7c55bef493049cd4b0825101e10da2d85836d0bfa07f12b053cceeadf34b9c78107b2322963bdf48ca5649cebe23818df512b23ff9f57ef89ddb91500cfe9ca2c831640d931bb45226a0588d590fc7c3fd5fce2e9e592a34d14fc319facac2169d007f3cc3b9dae692fea73017851ba3b2c00a298b77314a2209e78557f607ab7def3c67fea5635d64b4fb14e46734f6b6797f3399696093e5c7df8e2dda49bc67e0fbe281c2078a66fc56d7d579e75678f20c438824967e00d84925f6e24eacc15fc936dc2295e8dd0ad4bf1d664dc80ec0b4933468587877ed95daabb65a017a8eb8a26797e748739c9c3f89d71723f3315f696a34d94ef9208c7d5eb66fa97d57ab19b54c247a74311632c2dbb5f66ba91818236a6f5084767c495815f818a1fb3c9324bd431429ed1a73231c127a16e76b917cdb050893fd55dccb48965abd59d03a5821dbd71054dbc881de27670fcf68e28f829f46147007d58a708520cc18e2f12cde53d68065f13047a687b23daa8552679d452c0c11fd1d86505329d58a15658786121d933881f35ceedd02ba44db98ae57891cf3fb04a10955f6b9fd264898e09eb1cb02876a35a78294daf5c4037cccd7c46230afef6098f2a3e8bba981853f29acac108bdb594ca244e6221d383538951cd0c642b7b09847cd5dca5b358611b9b825cb865c039a969a4f2907fd1ee15164a6ce6e891217e250ae40bb64441dbd982b191804a837a95018bf09428b5b996546b73ff3c101147352d42ab559a52a6d24cf6b7a31de2908c365a6a4432e697cc11e3fd54f40d8533bd0063983b4859407f241c83b55f6a9c1ce0a13a2dc81174dcf2922c1e4a383f4e6ba40d671ef84721384f5f16de1d7ec88f4c98474237ed90ff925a937c445467e8cf424f39314afba06bcf7b7b48ac528f23a3371cfbb70bb2c8358d502faafc46fd490d28ca8091790ba64bed2e532a280a4771ba1f028135fe2721cdd3593d97d549bb74996a981850f4dc3a46f2b08a4b58f022313d4dfd20cc14a20704e184c2f2e953e0f6b51fb776b8cf968ff3d565aa2c516678ab492fb37511de2b99a42f025aa8b6b930f104dd90f325825f42441e7c3eb41818d808cad851e2d2f04d1825004a5cf8164ade7199a9e44f27f5246b503c8e94f25a92448f995bc719e8fdf3491c33458bd8d4b831daae651510dd4f7ca389d7cb899b98e7311d319808f32f7b99942872871b2abe7a953d742fb88b64289a1810623ffb963f483702b570b3c48a9da76925131604e73c582bad30460556ce64fdf4bf18f99ade54a6b380a65fbc4010c6a436d545e10fd0bc0c724b4ed82ad29e580983b8fd0441900593948059483e131ad38250a4a38e69ab4749e95bcacd799252efd543340c8c678160e8f4996f87e8299757c20d67d4ccfc519e8034c5f5ecea2e0ca8304cb3876b63c40ead1deeeb22552c065052ca60219600d3bd26b09c2061e77224507f7a87dad575b1e6a5496a11d3aefbeab0c1183873e9be2dc782d54296fc7c31d5a7a49c22463198a84602dea84ebdc992be59ded6177b9baa9c3f3a12972e080116db9d0ed3bed9dd9f62d27df2cc0ff8e4c53cf4449fb691b99e2759a3fb388fceeb177ce3dc1b0fdf0a5d9a363d92beba1bff9c234e1b229acf6ced7413cc63841036e878f23a32bbb72c2312a58d8806694c34a8c741d2523d3c1ad036ce06fb4161e015df9289eaf5b5ce689bbfbb332d4250cfd2915d9060117adfe06fe15d031c884cf410709a5f802a72679f088b26a33f783808c33aa51d2ef611274e0eaad89139eddd0a3a85cb661f3e5452549f905a55d9050129ea5a16aadbf57bcc300f3216e32b78be3505cd841177ec01e7e93e96157dda024d6ffd8054deda452922b8d42e3bc6fd2a0febaf16654cb90aa2f7205552f3fca563617ae52102d96e7955f5126f325f183f209021a0953ba3d7bcbf41e5ae8050c1b66cbea520fa4f46655af48712ce0159c7c0a67b91528385c420e614a38c142be06d81da41e9b04bdf34d7f00eede48395fba27c2ca1c0d1ead85f2fdfd325e744fc7b897f91225349e96789401b05546a23790e541d1ce5132e45d30d19ab52512d1035eaca7e1a3a25763fa5c476da5f32768b1d4f2db19dc36f18a1ed52ba39b248b80baca8d6227bbb020af053e57275604a9cb44b05ab9efcf9a50ffbfb1bffa04a0f294abc319b1c5b1431a82e9a4027231e53c3eac5b9c9caf9f8979217e26a54248598feca409d0f70da38f6e4b0682710312f9bd7d7ed0fda9cd359337c7f38aff72d6b28657e0e3054f61999454afe2e3edf6851cf87d678aadc67870424ef8c1437e74c52272516bef1d132a2fd0daf1a4e0c1dcc7468151b7104c5cfe8b332602bc611ec8a26ee8bde23170af6891448e4e159d0ad698dc6deb007a1f93f025743e57d14eaf57f0f13ca3e99b23e9cb9efa8e09a10b3b30852af2b48256608b3146603571873d74005a1e1f41e0f3520be12a12ec52156ed3b1aa42be48b88bca8a0f8113a8b4761571a3b0230d25ac674bb98c5b17dc0a62d9cbfe2d4d2657d4cdfd224b445056e20c0680a94dc5816067c15197abc997fa191ad035af6deae0f688afaa3de65fb996773a2d3f8d548c089f42cb6b870e3717ecf443a1a6bed42a70ed3c3592db32e26d16d667bb1d06fd3b0e897e85dec6235e776df9519cf1be273c6670e752e56721cfcf5ddca0521dc76ba5ac125ef960d3ad2cfd50088e2d68d6aac6895fd17ea2d0a5d07da2c252b9ee432d8dfc4b97d8417f1f7fb1527012e225c545040fe26aa0bbe71658e79bf6513946b931c10ccf8a77b755fefe56f7d5e27b0feb3ac03f27259ad8497b076f1e6ea335e5c0ab44d46908c36d22e84d790750e403bf5c8b1ec2ea68676e98193e10611676493beb88e1507ab650b631a5b1767679a1b771b95f449f4c15839cf2c794c803f3c62288eec8e291acb3b60ac3ff5fca3a73d4f20af18834822e04930a35edeef053499be00986e6ef377048e2cd0a9b2f9bbf055be0d4a0df28abd4e489c1e8a35cac7cbc924d5805b25776a0dcb57c40e0f89ea7c8020eaabca4b4c828ee4e5bc3b34d1d39039e0223e105b93c582bc8d98d6bc96ee1944ade5fe4e97b6cd8e01adbe38664164b35f5612125440eb9dc0a0bcbe7fdc1902ccbf8a3b6788152085647f0160a57b5721f263b978a5dd874ed2e8176b0b1428ee5458f350a81db7b0bb9546a777b76d1ecec45589b53450937d05278ad82c91b00a7c20bd0ba6bf396021e76f844aa43cbc9c2fa8adc8fa865a2fb03dbcb274c8305a140a77f43d432ffdcaca4a686a8ee31833c9b24e795721cc9ab41ff3ce06ab74aadca3eaec3bd89a6b05bd30fbe696bc82fe9cedb65ed603788a25e804460ea744bad643e536e85bf68053077c46cc42cfcb36a2fa9a27a4e51d973d5ab815c5b1d294fcb27c2a3cf90463c44a007a48e22dce5b5188922e6d96648072425c95f1116390758b9860d155bc453d9327f4b34d5fa3549ef4b16fbb5336c6f729b2de08a97feeb0988f08ae57e5f9eb2337f06717ec51db0c792ef23e4d763c2e4258686b1b307ccdde1eb98118fc59e3679e6b1b7de7286f29c86b1b33f0c5485a1c6c1b9c59483d2fccb8a3b2894bb9319b2f02a2a0eaf400b1575bd892e18db259986b63c508365afad5557a9695368fa1d2f576a1fd1fc073219b561acb31a25481f4598f33a3fc8d804145150606972dc575ab3c3d7d5cf423ba1c9381bb8aecc0103b338474392874bfcc3e93ff6caba42e070f745d8e78a74b63307e10a4f808f0f07bbb03e67a4f080319251aa8e427352e660383e3201cebccd8ecf197b9b233fc6a8996b237dcd1aa4d94ee686106d999520dc252670f1a7d671d904814d992c82795991a3d16e5a65f4853b5a77c9c110e72862721e40c6119b537fab9429b5fd072c28a798010525633d718ab69d19df815238270f5153ece464fb82e32c69f224b2d7be266d035a4ca7857d484c3ad36fe131c83ae4173f0973857c47f75d4129dbb614568a71cba4681f78d802a63f90391a26b88e778f166748dca3e535ca51de66493409c91fc2c4cba06b0d3bd98ce1624e8655aff9a64a7891befedf8cb2ce6bee170309dc402430ff0c6b01b048de6a31d94faf8f59801456916217170212c8940584c3b98cb4ea82c64b276485ad1b553a1f9079a9379c5b3d994db9928ce0010c977955285529b2407a5cabf762f2516eaf3a7e5b1b0a21e9309e1b3bde2047b189f9d56cad2f2119686736bceb1b02e4d3e164bd0f611a2b19c23a6a90d202d13a985e0cb248fbd8425825c7b3b0e1250d4de4c48ce8b723db682521149f96fdc14e40620db437766fc5fd9554d6219109d37e3494012316fe4ef5a9ddcb9a2b6c203b100b53956300cef25b473632f5d490549c526ba4b840a1569bd0bd4489f63d3f06438b4eb4e6a58b7fe9eb125fcf886ac3e54d06d740d37c1087b8dcb92ce1a9060cd206c4c5906546c84dbfcb97535bf68d183c6049b315d0c8e1ba8feb071044f0aedbeabde7cda52c526f728f24cc151be2b7c187683819916981c4b698230a0fac116ce10894103b551dc3ffa3955de34fa9c7b3add33a453d9dda3a77496f21c48698238b103241f7264e680529a20c542c9d610110e3c1422aaaf420da710a216bf1d5d93a873a457b73cb85935f4c723b0760a202e0283d96a621e05da607e979ea4c3043b453faeacec05874a0480aaa53ee01fc43babadf3fb0066ec99f89a37b3fff5d63c959da7206cc8427facdad094b9a0551e5414605dc371bf4859dca1c748e790e32f684ea5a67adb21425133fbde96d994c91d661227200a15a63bdd099e4272cf1808370702a2169d654519140e82387c07b4641d824df3c4c45cfb42be7d1a5a122155c8163921e6535a0c89c37da12084b94be294486b4f3e84b7c10bef2918701550b66c1584f0b7330c35ef34fae2526a1adfcc058eeced2dcc97d517181ac8d618492476f13bc1fee62c5be4e7e82b39891fc8c8a4c8f7c9013680a1251c787d6291126d06629439ce58fd36b52048ed85da814dbf6f0850c01a0fbbb80114f0feadab388a91dd47a903caac819a92f9cbd84aa374c2956a02fedfb9bbc2f4d8098fd5d56784a1505af50c6868ce80a109af450943411e6b6039b319f13368fb9c542572c960606e45221602015a15fed7d0cd7f23ad1a076688fefb2fa807f4f8dda0cc88a0a4c04832282a66a582971501c84a06a8a462d19475087a9c445225abd07d02e20d0eaf8ec0210309c2218db40720fdd57f9bb104546b23cac205f681ce94b61873d8520500a4194e88d0afed230e74d4316f2e7b6030a62ab8d6134955d00319ae3852314892edd70e3805244ea49137725eca1a79110fbd8877109c14ea72a79fe02fb718ba192892209c9a56facaada6435d6db30a69469a6ec0ca94d9ed96de86e7862e55b385107e86a6548f694eca4f953a90055b7363b23f7a748bd129836e796311547f040c5006615b5e71200dc20ac0be45d6ff87306d022343f4344450b8ae4bc942cfa0cbcce0e71c84b060e193335938cf5914a27e66894888821167017ebe70ac2628ec8152a66a839bd18cca6b3092a52ea33843955d16bea0174b25a3007a358ac0bc6a6f879360d6a0de26911ca16ef0f4e2333893f29cb7ab1044f0ccb2547aa5d5fed43357c5a980027f019f565c194e880a2fae71f8e9462e69125e9b33c28120f232af0bae2e5bb774d44c6294006a9f8cc509efb5f71af7ca8001928a715b7866193076b9b734349f8730087abb7f77f501a1c067c7c129a7f62c0a49481c78fb200e9daeb3823515ae7433f577fdc379d61ff705cee842e918d6bb7c5b51473fe263efeec6b3369a3f968da084c3a2c3540b81a4bd30c27c42246d1c33097fe8a22963832f5932ec0ddef4c8e0e076f948b740cb24272d095b6a5190fffd978e5203762acd90d06458c5599a890c621614a82484ec3be9316f07e47e591fb7b70e5ed69329254f3c962b1bb14b2ce0b6ff9e05ddf6a9d90561ef1438b0e03420e339dc69eac0f9e01c53255c979d67e377ad1a7581f44bc5e229e7d9950fbdf007da0e4c493f327d50dec3ee72800a5097c44e39676af78f1c234e126883f450f529b9d685883bffb78e7843b9d64e25b60ef9be420ce5dd2b0a829f4c8554cc44597d9752fe33c9153aebfe3f7c713a3bb16349996843dc2c4a52556fb8a0972e3131793494b60fc75a5b0fd1f9d20f2935a9d4a433ad75c9511b7c47ed3dc3d094824d9b60076dc84ab2b2147511d66bcdf00c2e6da9e1bb0ea81bbd274b7c44a009ba82c4bf0d0f11baba6fd41d1ee522d1f234ba413d3490d2c8771f463a0126dc2bbbb73f06f0203115dddd114a7e1f7051f2217078923b14774047a0833148d64e76d210e114b47896a067044aa4a5589fba66253fd0227547ba8f4190181858d0a5d01bcbb6eacd6d516f75eb883801aa3bb6074ff995883eafcf47621afd8103a0f07a4c7a9b1d93c908279506fadfa15a118c20507276ab52553005cf4a77df4b46ada643c44e7a515fd6d3fecf1f186d8641464c7818590a129c121cad9cdd7b49b26cc4882c050adbcf1790a13fee457c2ff1b319b214dca12b7e3062194795f26f9f61bbe8056062ebacbb5b457d4eac6893ef2cb59794672def009c8f4aeb8e17a437d2c965e88b06fc9252d053bfa0e6bda083780391b7850acd5d5b9d239eff56b298884036fdb0506f44a05e4097a3e99769bfda9a60bb018131205ecf77516313e0d62b06f0983d210cfac84102e74dd141d6c94464befb979ecc8845a7806f0e0bcd61e335b47edc3c99c444b93a8a0612d0ca5638e796b9d0cf0c7745c813af08d9bbc1f7820d6beff204be400be48307bd0dd617f493b5497bdfe9ef6289facc7dec451f6e8b8387578674e2f61b67b7cd505fd3e8bf2f59011869b8d26a2d4e89944ff1353365a1d5b3c7c21b01c3fa325cc889b13dd144a5203b70de1112111c618faf67b7ff16233317079e6897164010a22a75272b02d4fa01cc98330a150fd83bb0deebf7527f453eff9c8301f6d44616e07b654c5338a25fd725e074d9460e90af5e087a52dc2531fd805ec0ed75b60377539f93b726b494a130d0244551d63d82a5ab20cbb00158584370badcbceac0b7bf38edb3f2c633fc983cbc90e3aac5eadea5f63454ed4580882195dfe566360bd7e0de1a366ced9e4040d56cc89cac825dfd8c6671e753d0ff02d8ffd9158f2a59cc4dceec3f587aed5bfcf73247630c79cc6b076614534cc038c4567158d6a1db34daa4c9c7716546cf2fce2b55d9be946b82a3895d0a4db4e5fa14107bd3a45efec36502934cd019c7f11d948b8e9715563d14e903690ec194c6abf84a837138916790f39673d316ed69d8c1901c85f17658ce72f2343fad7cc08e37f1fc83bda6ed4c529b9b2d8dc00dae7daf3131258c3d4ea3eb76ed7e81f60b110b55d89af39202a9e05f8ea8fe40a322ab441f811d03c95e397ef3fd6654aa63898a8686085d1b078e945ae4c027aa651b7e26c6c524066374d00ab330a28bdd625e777ac17a75e35b451f70d3d974bf8f49d7aacc7caad764d7883e614e900d7ac5d3baf60c742d49f2045c0d26e86e08168e1ac53b5a0c555a77b3c2885b8a0fc5233bf3e87d4526a436130f603e12db3d8c5afa5a6abe5b92079ce3b2c19921b2da476d0b857c22f53d21359f918fc387c69cb4b4e2d90a16984603c91d689627e220e2e9de8d719e9b19703f25a02822a2c4187a13649bc372a9ae6025893ee5e8dc02bec6d03b6b31040a2dbeb1048e745f2b37fdcc601d764ca25014a5a40990eaf121cb59eb3361587bd7a19b7d31bf429da2714753f60639ef2cc2c7da06585fc87136aaf46fae8d93ac6bb15d72e890cb7b318983b240f1a8d7e7b001b150544e47e248a5ecb9a473abb431d2566f8ed4923064d4cfb17ad4e6e89896fc02d274dc9ae9c1f291d00cf7854a02f3886fa9a47f71b19b71178859a4d54e234099dffa2b265c47d1e53f748145178acbe739ece30a9760d13d825c81bc30677d75780bf1c84ee11bc4ca495c570e40d44b91d9af76f057884e882219c00d7a6f70af202e5e0b9adf988a641bb325dcc66d19b9b10b496e6cc5d11b5bf16c63b59c7d32b4cabc4ef36532f16c6265a3bb58db2837221bdd93c9c6b39e065d60365a37aa8f39daecd3b98807a2ea0bf98ab48df222be51f9a8249efad39315c577b2fe1d0392814b5f054d8cdea1dd69e8c5bd9059a4aaf8e4b9ae5e946e89bf9184becaf2b70cdcfd5b2727b92b5c0532019c7809d1ff20405576028e1e44dbf62eaefcdd81149e5a05109e2e7d07073479b3d68bb6cdad175b00cdac4314357d286c727739de5da9df821d8395e1bce5c7b7451e781bf3d1449e963f6fb96c0062309cb9083c9bc17cfc72a0f7538dfb37addd8de24a693cf3bec5ee922b4b36abfd87c042c7a9ff6b0ca1ea50c924a94a09f18e840e901ff9398ae18238fe783865df69198be4f7a30d8fa0e560d98949fe5006ec3fb5581e0b81712cc2c56cfa9ecc0e8a1ca0cae8e9fce02185b96a1507347eb67a7433d4aef5e8a8f79b6b41fed825417f6c44bd05756ee1b02ea0abd4dd2ad81751a3d188a96ef77db5a85205ab0be34f16e07a33eef572f8d0dd22781478df4a5b3a0ca6188ee26e7dd8320137681b927f1b4acfbb17852ea2d26de2e2bfb93b0b05daa2276c72bae97646e55247e4bf46bcb03f89b68c911d5097539e2bb456193bfa359144dbe6e239c1d515081249bb687d7237595c4a34948afb1a4785953045e14e2b0ea95855837ccf67d0af7160152b594b45bf0342278c02dc90e280796a6c277b40a69e76449f5aaa0d597181b8dcd9a182e6ea33313f3439f7ccdba545d446433fc893c18da89ed923703e2c7d1cbf99e847a19a390004a3ba9438f72c422621c7c85839efdb54ea790aae05f0a2c5ee6bb8f035ff5b692dedb987bf961513d21c58518efc7732d069c488f96039b5b1f4e90e289af64b2e6a989b54da927d35dd4f9a522e69413362d069ea518ff4391bde6c60a0ef9a6f6b2db1fe32b162985c1b174b30292e2db704df87d921212e17f1a2a771d76438a38d07385af5d9b9dc8232c758d1a90b62b588a8f1b2292afe02efbf53fa5d909d92aec6adf93f2754dc5c4af4bafa8ff92957daec4e9796445c0a62ec175ccfd3445da38bd7e986cb53e14d77b5bbc8c8372c824fd69f494532ad7f42a671ffc425f9c320eec40c94ebe850318112f0841d9a0469c731fada623d63158cf2805bcd8f3080a4e29970bc5bfdcd23245be5606e6fcf84027e8b33a627d3658a1108480cc2bb4d7248788c2420d8de33c18a198c11a68236fc4485db3f2059c7f41bbea301e41e5579b87d6bd9934f2c3669e511499d8205e019f3012ef6f40b39b3e8779f0b387e1d26dc4722eca5184eb684ea33942c747d91fb0eea0ea9e67eac591aec296491e6a56f12632e27d006606378bfae1a411ec224802126712125578d6a2584641f78ff9e0247655f8d442edd535eb973df853c138564eb42e5952e75da5ac8e602200600171dc08be4ed90bc0fbde84df52e2e124d354fea79f043e514493ca17ce634b9d2d8350b8e7a8e9574fb49b3abfc159819284ea7a34f93c977c3c050dc8d0ffce9178359b8386407ee62eb36a657cb9896182ecc6897af043d50ce2058c1f907cb35788a3596e963cfd6e400dfa955654cff28c4317426548c25e3a98ca916a67c4462ed3c6865a246742417755bbe9e1f14c9f76a8c5a52d83ed59647f561532d178c71808a741509c887563de8a457db283369544a73d45d936dbd59a0c982bdb253a10a359babf0ca842aa4492e0f445835809e2ce20c2df7c466988a5cb16882e1d2607078869f0494e72381b922231cb50f25a88b2301c109bb5392426e60324c0ec9365c5921171cea87c424d1af685a57bd09d51291a5a125693301b7624b45124f6d335129fbfcda316bde91e6617864c6bad697f989bcc5fa4770de1e721a7002a8c4347653f4018fc1293f03583e4c7ff4b710f428019cb2bdbb152eaadc444a3fac773d4d6f259419d3a7417c200603de16fe7b2fb6b6ccd7f79d8df3656d85cbd8802b449daaec3ad72652624cdfc1e6d278e15f76685baeda4b3ee5f4472504757efff8228dceeb0c1d640f1b7b5bd70a012d1a6701213b6c56678338fa7ae360f0049c03867fc82fbc0c2201f8a416f86494106bc247a5230f5ad76d76ba61fdc8bb87892f91c6dfcc27dbebed7c58c7cdc0d12c91b6162620410c060ba56d9d8ac5d58a4fd77cd3f1fcb81610d9db9e1972f778edf8f8c93e62539d522121602694d450f33b59b97858c4fa71db48a7a354fe15b037264a61a335d299148bb8d651692af1973d72052e4de16e2f9c049228a76276bd1adeef4c98747b1caf175011369a431defb5b28bf840b55d05a0a1c14af4d76dae2d95f0360b64cbba4f82bc263cc206854efd7ee1e1b5443da248adf4d5f0ca40684b2f3332310d28635082240b0f468c8983281091bc059f6f6776b05be51fee12aaa83efe13291aadfa1ac103afbaf5991e4375e108347543c44a890c7fa3c88653ceaefa4baef8ad68739e60545c7ee25077c90c98219b0bbb8677f58effbf40e7041f9d2b0fa31938b9e116fde302cd69c9facbb4fc8071de8e388a02f86e64a25edae2891b4d579b69b4d560086058b04b1ecb5b5adf48f182c159f4b1fb520a75ca1cedf38bc7f489216a3c2a6c74aef6f9710c898896e50f63418cf6553073402f5501b4652c7e314a7369568519762b971688f7dec4e6e3c00c79fef31ce77708e2385c0013777de3e57a3d071075cc9f4c2f7b3d204ad62cd55410699148efedc6d6719f94e0094707772bf35dd00b161bb16e5bf3030d2856bc047bb379bd4fe66f838af22b54fe3067e6a08447b07eda28d847c15f6eee271a813713100c3c72e6a0d1edf6a00ce741b615a7e61c9203b76305fdb30abe22825df258422bd156299c5d78857fc87f5942be4f7e32649cacff805c52b730b340649956665c0f74d089a90344e5f120704a5001891059133a906eeabe88ab02ecfa1f2b298dd3b45bb215f22e6c68be12bf190acba3914bac141a1642ed32650da4e59d076f628eb55c5eb7333e0c17ee63c3707361b085fc2314b8c25ce7722a38b07216721d06cf2e70b62af3c5cf6ac4699891209a7bc695825c337dcc0c8f6c700e9de0e758307824ab99c268fce52c56676556ea2a9c9309687f026981151ee5d8d512574e055acd2bb08d3ed0d312d6b46f26ecf3b02a29e1f2e48b236956aba140e5528f632095fc80272866db888432f2ade94154386e4a77837c5c7ab4fcf26794e37abfd4ae5c642f6db19eb1450277598bd9d699014d0a7923663c971d0257a719c081000631086803fe4e8194ed37f856b48428b19448a409548359ea4fb5c4134671525cf3ff7bcf6d174f1a73b55cb5878cc8c964ad3a478a902d1eaab9c02d315bf2588eaa977690309042b0e922d2cae3214fb53981b9337c3b2eca245be1dc34198bc3ae0d78ba8fd67af930f2187e270ff3a1c048cc695ac8925f56175b741b0ab4292851766fc12e1b0e37a7b9b8f3cbc9e483672abe16244dd0d2500b43140dbbeca158272a4180413fca45282194d49f6c0a9b20691444bb0a3572264af1872e81c24c5c3833817806ae0d368192088be7b9638e8a9a58450e108ef317eb3579a5694b8962d1a69dcb60a1580fc35102c2fcbed90487bc9a05de643357cf217fcbf2143a92fa79811f040a8a97481291ca5dc5f1e5b3b19ae6380cda2aa13d0656a7bb00c771f2ebeec6e1f703e9d2d8a92a769006c02dca892c8ebbe4be21809ffde1a60b286bbb90f11d03da6df16d6bf3c582c38e339acb682f15682093420a97d40061e191622b3b46254fd330e6f4d4194f42a33f743f97afbacb20e8b0de98dc5305095c48f6cd0e92289349323b8f19cb50ebc3a5343e2993c5afd6ec33affb8eb0c766aea390423c4213aa4ae498c046754be21155b1cd4725886ad0d723aaecebfc8f8e2a5a3b0c7305927c3df11e54d8dcc72375864690bacd0bf2b8c576ad619a5f64e484d660eca38000296934c83777e5555a3cc4db7c44f650ee7cf12874720efdaa19611d52c2cbe9fc79f4de9ce831b548cce7e8b343ae8a52e98ca018a2cecac0868f2886785fb250463c99dba84aebd43732062bd53e91cdd86793ad16e2602f80708c49d89dd34b7f98034abc3596a860ba278bf5913598c9359813da624d8d59426a26e9102e8f0b6acdbf73011655bf8f09145584a3c00a1b1e06c6b38bc7b88adee9bb030a74cddb90c127548a77c69626bcc390b6d7425475bcc0e2e6292c9b36daa923a4855d302163760d4a56a1c90d12c209da0468220bd3b8a920e988beda1327288a9d72425bd516cd9427b44e450b787598046e3e9e9398bd5dd19422df216f39867a025b12d2ed9a9b29a0008015db42a1897243ca09a903336bf9b53d41d895c1d937218173942f83d6e2dd4e3b511b01164e2d7dc9b2f265608d0f700315420cb3ff264481ffbe2e5552ae0eb0933283c13a11fa74ca6fbb1986c9d1b774d05abdf4192d17b7a3f2027eafe06c894e983819727ce51416810cd49b4c891983c11611c8b04b36887f5c6b984cbe3dcc1e282336c5ebcb58b1f0e40b76c61fa35d2c11a2b48ac0cf051ff8bf4a95f5c78b437f70fde685c09c6344af951ee3df513a7d84a74feaedac88713fb33b564ac3137997c70b805ebb97e19db0154bbe39cac6451e9947ad0f3e0857badc64a890a7e384c0982ada826c5cefdb2e6376a7ee2cdc86bdf4bf41b5f621f36ae4b300d410f882c89f107cb16b658afe08db679c704a792798e72fb34da9b9fd4d5d32cc13efe7fb1e496bdcdb655edbbebffae151bc0ef0a2c896df96941d805e553a534182e69b901e22e5b7238685e359e5f0c04ed2126f48722188148016366bb038871dcb05f8d4e0e4c1f2719dab65f81b949cbc358594cccb83e0df751376c269d971a74afa3ab25f9493396c9891f8c073555acc30ab09c49c208036998466b2faa501dd345b9bcdbdae54b507936b322be099a24b2051940e0e1f484011cc0fafb6cb36c7fd3f55241f11755c4cd61924c0462cc7816af010f356c95e2c9c21e568ef60a3a6079055e9dc9b1a49791a99f8e1398d83c1754fab4aaf47f8b72c7a5f6d52a52f36ea75ca069d20d161c47fa798f512a467025ac3f615771a0aa935f526077a2006fd48fd9fef3f3e7dbfffc7ff057375bc7a64b0058369805413fefa4df7ba477956b49c32cd911e2b21faf6ea655eaa26524c9eb190f8618c8b1b971832662ae66960f36379f40ba16a452e9006666c9960794b0fd61182863b6d80b16114941e9f7fced129f855056eafa837a9d5144ee395e0b1a4782787365fb38bf947ea91e959a81c1ac285de0f580e56e5c1fdeaece90b33377712e11453c884da191e8b3cb9c3521b2325dac54150262a63daa89290708c094d75aae3f510e8898fe30d82fa9736d03d3f0ddd7bf5a82b2d84fc3fac84047e938933e8d3c47dd13b92e68bb4b0d53cda62fe700ba7bb367b51ffe112d5cddc6bdb8bca4f97526eee465b9d4c5635ffeed65400c5b2e6f91eb4d3aef99c21444b2de8d7e4b1413331933ee858985f13d0ec155448dfe2ec5e19f490177760d6fc0a8e22060b7ba5202582ef3b03d333e9839e7dd31855bd9a50a50faa26b3f405457de3386fe672d5fc33971ba4af3487a5448430a6f97e336e1d1ff4ec1b36c6a2e69fb2f8351d0ffa9a384a354f3d1b3cfbc2aaa6e32be2bc37a0d5b89befe54c03e088b905fab9e110ad0892b94b32243a38cf8cd0261e7791fc2c5eb87f6459c68e7911f885c59e0dcf7ad0d1afadbe162f6df4e3f67e79e9c6069c9da31817a4c8773440cf0e3268b4b42b1f9e47a8f1256960253d0b072a5020badbe200b26acf18cf3e808eb6b64b31833502ab676468101f8c85e4d075280e9cb7cebf12983687d098e917cc84715ec1844d1cd1baa4a676fef33ddd0809f81103029d80f3db873c78f70fb80b38c0ce8a900059829cd690605cdffa5a7a0ae967b4087cff5bcf53207db259b6c087eee3d85265b7de7667dca299e8e9d8f2b5aa7c5fe6e335bdb58c76de15dc22a0bc791a882d7748deaf496ea7adbf4c91977850c8c836cf7b8311d6c8c81d68a895304ce3d6bb01f6b3510ee093373d364e6a58bae524c96c6991d6835b7c4b6ba8841d6544220a36b0215e278edf0abd515cd92607781d0525b9ba426f5c5158efa626557ba4eee2e3247a34987d3c20820407f724ccdb73fbc6b8196a9d310fa98c824af55a6b812ca5e0febf91f015f374177858ffe738892c4389e9d09589368470eb96c28de81502b5aa705331ab5bc5079f3a2817ccd571b4662307b221175abc60826ac5beb10bec75528deaa0bcad5ddebb9bfeda413839c6304b9c42e056bd306b0ac3826edca79e084410504de3e35bd68cd0d54614e1bc0accaceaf5aeca902cb0b8f7fc19af60863630095f6fa57bca3172f8bb804fe61d66a24351c02e86d914a9ed65fe9f33b505bc8e83e3a0a259cf1dd1362b3834fb5bed48f3fe26d8f2453f1268f9c2bf04b9b458ffed97c0b74b7eb3869e6dae395882c7663b80762be146b0d59842c8249969b482b02bf78c2e88c4997eb79199a84c243e4d0f2e9ed44c12fe819fece760b6bf612acbf57f70739c23823d70fc8ffe064a8c6f53d1df38e1e0b68d5bd77e5c82be596e6ddda2f6f332d4cd713bb6dcd6311888ac8beda33f75d84fce8553a80c5f61dd9ffa024ca358e9db46264405fb877b10845c7cd66d6c4d33df3d31809cfbc622c91ea68458225c10fd6241abf3e48d86b21164d9847f2694eec450feae3eadcea0086b7ebcab5ca5d5f3b8e7aebe7148d1e67b9cfca25f66e625f2b0d967c9c2d9579ae69f940c2b1dd96e5e0798cda9054c9c2ff6926db15bdec1bf98c29d05188347ba00ca074d2ec500421d5b6da4218d42ea3cdd77f6bf0839a786016fd11ba3e6384bb931799ddafab9e969bfeb3fede9242ad96ff5abe338ba32857ecc9f3ed3ff743f266defd99ebbfa8929ad8d3f971d56144d7cb62722a00d7090611df699fe3ab28e8dc0db923df0fc91d80f04355b100f7652c8b100a4522391f913857126025787ab5af65197412480ca87f74b53e66ffb58d464f11146db1f51be724851c56ebcde33ba1f27bd0de32ae4effd2cf506882213ff2286baf882bf7ebdd19301898a8817337496e6bcd6078dd720bd6afdb806f1cb79ba8c659b3bee15c139c2a8b92d35e81bcb496153e9eed8f0bede6bc555446c166887acbfeb5cb0fcad08d6f88420bce73e946f4be93618ee8ad38fa876d0d3f20cff65871bf03acb2edb07f42ac0817f951c7bdb1514b308c4dfb80c7d336399fb8a4e740c7e3d5ced6f737aed66c88309fe86d75dabf9664aafa0e01f5390ab6db2bec102fe00429f9692c226fe2a653a7c401daccb564905ae75fdbf183d9ceec6df27bc1775fee21ef8fc1bff4e40235c05b244606679acda94204ef481de09b229be7b2578cfa1c6e0aef46967b169adef5a1edb4d4bfc0a213ccd527cbec4629bf229f3098d20316bceeda2e5bf7ff4021531b085b674ef29acfd4adfa0054768e41aa644cda315271fd8ddebc9c1aa0d8cecae1b8fabb30d5fe592b1503b8d5fccb62885444040481e1744f4823b421510f758d51cb36baa443d87f9d8b26ffee3216b57a8da0a626ee67dfb2793ae50e1eb3170ee37cc793ce49b9fdc1b5d86716e566c433a5e91976d77144df57ad54f92afdb91d786ef0978621affd89bae121bdcfd181ed2bd87c4f0b2e7e776f84656803e2041aa044568907b7345ee8a03624a17c605b9d4f44bf67de338f73b12905e169f005ce41ca00dc7564487a2ad085dfa9dc33381112bf69600dc4f361b8ac0c5cb3ba284ccee85c31117066f5cc7896415b1672d38fcc2c0e00563456e071850846eb22332bbe23517717f0007778264fc2024c753e7f59dfc23fd001dbe533d8c13b211ffe5bda9f623c929eabf12ccee11facae5697c4815021e0eb088f79de3928daaca379662830f66e420b7e2dbeed073075bce49f9e02fe68cda3bb2f9460cd46b36ad22029505ad74a8d22bccfb2874c45e375cf5d8b3e7001c286cdd7a61c20d8c49f5cda74a7ec5c086c914fff37b7ece4b45e9274ec10812cefad59486b733a7a76d7fcac77e79faabbcdd508fe9d478a982a63d8deb560846147e1e98ea5397129fe338b111592cde52e393fea07a7c1386a93a0ea5368355bdfee1209812d07d99ab706a2ec22dd19af6f0e14da4048c43ae3ed7dba854a5c9acd8d606880ba4144a02795fee5935b563d2530181dc1c0718164f2117f29a9a047c516c026f4263b2c7f528477864f95fb36841b3c4517b8e6e3c1ae957fe5fc978b6491e96b4340dc5f4184b3db01ccc31ce671617a125e489f25ce3faba2f9119f1a83a0f7b067bfb82450d0009dae48fb0078625e640e7cc18ca10a36b3393628ee56c1039a037f39632cce9e3be883ccd04ed0c56c071e81aa28dd1761de6195bf4d62882e231167d5e7e14c205adf9076be18f9e87e9c37e0bf0f53a18885128f68804506a027c4a468810ee067e54d136f1bdc283688c1dbdf12e1c07de3d810555d9766cc20bf472116aa6ce198afdcc53f8855e06bfc60fc2a8309f6c78268f7a00b91f1efe3ec028a30944b1e797538677cc21a80f647da2827dd48328cabf7a25a6cbfe9d69e001f634117c92a895d8408b259102bcc850adb53ad11ae0c10c2d7e4181d200870e57e066de10802b2c8dbd3ba80ddc0e4be3452d06a4f6007cbda41e68b50e743c397b72c3a9ebb755b5691cc9de06d62279bd83a5d12fb193e68cf8fdda04942d4612e8dfd8183dac769070a7908f9142159f5015e38044ff5b8cced609f03979a94ca76353004ba3d25a4ce308db1498c43513f3e231cad561480083c5ef382dcb6b388d3041189252149bcb47420d9ee8a670fbb61eac48a057ba69791b028ef05e0d0062487453fd25d18c1027efb8546772845114792367797aff9d07b065a183fbef22f2a123548c89fd59a7d835404d07cc8c037370166c1b50171769da440d4375aa5fad2aaf73974ca7a4700b6d141b65fecf95b2e935e720ecd4dc332b8ea8662344dc9ea89674c8d051a47b327a131cf7379fe8b8431228c3007c07d87d5c986beeb4d5fb6405fe5577db92c70f1f825fb4330b40c8f80380f2ae6f942a66650055a03030b563b87b7dd0b80cdbfb8196a5118a6d3ce30c2f00652652cf7e95d35ab84c288146ac4844fa53d9039864c5b5e7ab7f491c40ac8a897ff9bfda58d99facead75856bf10f1af3cf803d067a7cf776f030c9710406ab266ffee4c08d0fb54b586487077d378ee159f15c70f26aa69844fc2336448f7e87053d95934a0c06d436dc3bfc506a56f92e04b9ebdae1e42f95ee72aa126069c441241d533b19747bfca44df2932da2057c6a4ca49cf81ca9a6bffbda35f7b5da00e43157cc2411bcfcc61070064f32a821000627d9ef99995fc7ec67e4ec5c12753210ea9ddfeac2ecf33bb84ac37c96d51be104d2a2b4e06f20b08196155efe0a54192d71a50a83202b3a96f1c59f86556e8fd5397da09e9691b27667ef0d8b2c0ac5b542c20607074e398f5102b92ff2c2978c5456ad00ca95bbd27998383e97f7226af089af3e76eea660f14abc1e85ce5f57566a3496d22abb1e3d5c3d959af18443de5d29b98bb5738907ce004b4ac37fa38239b42085eca602aab73cf5af640e93e9d3aa0e2053680a95829194875c43e739cc79cf0c26905bbad3d10c7ca65427b3c0ec2293844391995f28e885f827d57746b1a28a84da0049f8e70c31dde828858753419f37b007a13c54081f2d722f8c81937038eab5ba4e6174d00972090130e7532ad1d741bf41980edaafd9546cdac6fb82b4026794122b1d6d8ba416c0130cf59f0ae5c377359d057675cf734695d791221411a845960724201028bda2fb30c52a83bd8e53b4a5f688a835d800b0c81d42b75c593d5c9298a15f01ccf1bc5ddd45cbe40c3f4f9685aac7e4603c307a18b2b07b2cb6a594c680bd33f0600937da4a1e8502631aac748edf9ce3e44aa05c759c82fa1ffdcb93bd286607810387a4c4aa434303b079fe1c4d11336673b6d0ec0777368f09702972c46c3440062940324b7b13dae555fe0da5fc48675ec29edef28593dc8bd108034dde06e4a5360c8fe5012752e419a8addadae48986951af6153212cb4c5fdeaec91ecf11ee8f14a69addd6e222847449cd8a1b41fa8a7d9a37fddc2278c67e37c5719834f8fa03a0a5432f9b057f01e36600a9c42e04b850c9b3e5ae0beddcddc57883c1c82434184f44d4468b9a74bb8b8a3f30599f62e43811fbdadc31ce392bc1bb0382db2d510c56bd88cd6dfb014b52f489a83ee51948f5a09fc56989cf922cebe49f89f756d807e4ecf1a017e86a10b0c7fba7fc7e1b761f004ccfd183d49be667b83da3085930b26a9f9d439807e6564d926788a76c567a6ee302a46c7851ebd04bdf86ca6cf4dc42fe098b61901759e24aab4a448b0b3634892b92bf51842f3556ace70102f0135c361659de28da9baad1a7148a183353b791a36ced301f6faafe4e3359f5c3d02fd316d7af3c9e9581506a90e504284674472843d9e212ae1ac2066d4f9548784ceae36f0c3782dcd39dc2e22231a8158d3a60494d1d794fcec0db157b4aeef741e319f917547bf604b2960639ad55cf9bc08e6cd0afa232b62f551b0de05caa21561b52f8ed7581d6f58e31cf4fc0ebf7edaa11e291bb121028463430885e1b0123e290e816e36f4c9d5bc8a74172f84dd1312c6797db494b976387202a29197aeb04b2061c5d9567b41b367cd88f649a8a17705d6d31f35405a119261546fed414b5845ecf977cf693dfc085671802646f111dd97dd77a941894dcbdd64841285c64237a7eeefff41431d7f19ed80433eb68561f72900c01ed430b01d84dc39434ad8fd1b4cb0f8df1c9d5b296234b6ade052dbe396346b53f8a580fd64237eef5adcdd1fa636bfa781a08b47665d6ab2c2a15ac59d0df16bd68bf4e36e83136f91a899e0fbedd8e17779d2f9c4ab3a6b8dd74c542ca8fed8dd6dc026820b1d8069680c4a9a6295935b2f8e951f8ba7e73c207627b07847acbc3ed2145b5080a5af256ed40de0cfff7de0fa2686c950b096fb631c21f47c90f99d9a26393e87f46cdcbc7df939078d2040a2e15f91c73fc8c7d8aca1a876b5b7cf57a88026b2d63601200b60806481f9934c2edaf72f0657c22d53195ba78c9283ae8a36d62796fe1c50b4d698f1d1ace919cb50ad9e1744aeb6a88fd289e094bf0ac97634541e86db65fb24ecffedee4ea5f8fed3e3b58d947d38fc705403bbf12dc52dd801e7b8fc735e65ddba2d999ea99a5c7c0b276e3d8ece9c8a921f00a3ce4787e914ef714cc777236fc225bb600e91b9670d50487fb03e743d6e1568f8b4b1d4634d4b532b06a9de231ab921a6f7ca80ec6f8095cd5bea0b6b9eb342d7fbb045d2ab5418e0d96dcfe227f002a456679ebcd89b70c031255e53a12c0a30da1b3b95fb5b564e5af1ba2704446bee130b18d8aa2e08e633c4443985a1c25601ae458f03dedf1a8c403fee814c05a22e913abed082329c4ca55aaa2529997d47c14220e402883c82485f688da056aa389b50691525ec134aa443492e1a138f5d964c2230a9bf9c15fa4f14dc25d99fb5d0ec282394492521b5ae1654fde4cb9f7c06b6f175d43180c4094939d2173c791d958229f043458136d07a1969db3d0b6b343e520cf18b794175fc4b6348f157f7dc333148cb712fb95b81fd46071cd674291c85ba02a039cbacc9868f3f0aed30d5033c337912891f5f126946fcb3d5035a1ad730d4a45338dce110b254b9683a071a3dd434810736ceb0e8f850da3ec3f40305d7b22308bdb4ee8e7be88a71174ffc650d493d90faa4521eaab93d079c0402c79c163b20ed2dc5fba8e109c3f8720f38d64de12a6cf781d1f38221347f601c1874f5124c9f8eb8605511a249e238e0ef26fed210946b4f5fa07ddc5ea37233c1c5d4ae7416c81b81b0c04b577e7cfa80b892bb16a408f329938ae042c781dc9d28166292323158b7c63740e43d5781fac2c2457fb02920ae5ec87c420e07a160662f2b2c0432a00ee051c33a87383f43591f3b2bd4a9ec8ac6055ef3fe75d90aa0e1e8d68fecbdac23b2ba930062849410f097878e58a20367a48978ef99639a932d9bba8aac45d616e23184e305ad42da3cb65e05d91b11fb848b6e2e30c9e6ad0f891caeaa0f96e69def4b2a5a73ea10fb33814c1e3fd3a517e4b331bf8c2b23cf3d6feaf7fa496a9012816f6eb4d171be8283749f083569d13e86d87f46b8f83345226fac8e5d3fe56ae3d66769e3ff0530cf7ccc9367f7caa3edba7289523d378be4d1ba7bdac347ed43b8abb467285b7ba10f4f350884fd04edd5e935d6cb2088fcfc2c5e7baa981df7b46a0fd64efc9b95a321a0b48d162f3168d40a6228830cea98623d66b159abde0890ed954967bff0704aae9ce60c01dbb0ecb5c8f35016955d6d2b31fe098c0768b2e207b11bf8a99b26cd602e81fa015a2c46a52ac16fc2019897d11531b9ef08fac6910aace8d50860fce961503698aa7b11fc8fac90d9dbc84182331340d372ce0228bb4611a090546ae560ff18c01e35116ed6f2cba2cd59c822ff357329b38da011a1e8ba70e80b7ec387908ceba47a07f6c4837c86eaf4ef1aadb570632bbb8c109ba1fd3d7df042a8609fffeef9c178fb64ff2c5bc962409cc5c300fa8031a3fe2e22dda073bc6083f09ca761346d1ea207bda4a22181d859f3a6a190aa64ad26038940d4862ce3bf1da6c0148432f0bc103b105eba839b8a81938a67b7713131427196f6e53c079e16beaec9c7ce64d9b9861550fe05f871c03c7237c2e232d7811e8cfc4fb202f2d2d9f416ecc69329396e811bf1ae63108884f61d77e4566d0c1b17c7ba05f4c3dba6400f3c6024814a24160895ccf81050752e6ad971603cde0b77ae32d2207040d594e334b35c17538df710dc7e7361e117e2c3b8a367725a341a7046c9f2a785c9b68cebf48fdbbce6c0a2cc1d2199236907ac0934183d268f7883b46fe0777b1d43d27586c92096a3b50c76b7de3be424c831e243e8cd8a865de64e3e3f28c80a6a4edbf0bc7c32a603a2da8c13c903b9b99e15f7639b857dd9930299d2bd2dead42bffc575cb945fb21c524adf95a9bd53f5e5634219a4dcf1c289cfc4f893d6e13cb4436ac65b64b67730a88080f5db8669f10a8c59d4109743cb419443171ce1abf0a091b1d39015091ac5bd8764bf0e2d99a67f168edb1d98ea26d663f1ad3d5434c2e2929a050736b6f22537f5627025d5d637c55e5b54ba2b7e27e48fcd36b5fe0d566b9d4a90dfba5f3f81c6ce1d7e9c1fcc6e70914a1ff20f1dbe0ac1d8d376e4ee54bab04ed1b13b489f5b3593579a2d30d5108fae2339959e1f6d232c6544f4eac239c2a4e88077b4a80efaa4517aa1229dac8aeb8a5166a0e4ff7a50e9b1f61b69375f8182ab976a0b9ea6c9b5d13f8a24e940e8da8d02ba6b0097d28b902f09500c0a08fc1c25a509c35fa19e161b824eeebde9bc0b1298c427ac085c1bc134c3746b55bb80224fba4196d1e8d782065386000ec0ba41bc25b4c547253f83d9733bb756088cced4459b9c47ab33a76464d1e9df010087e92662d14b062188998c826ef502bcfb81d5b2e3c913338e3de5fa1cbf9af5b2b90d8bfc1876933ccd33cf799d8fec3a4ee4b3ac85ab358920cb2e4fe59971f2669aa88bfdd899accd34085e005ee642197895c36466db0167f0f47c381e5ff3bd883f418fb51a73453ff541dc41474db18a38c880d5b61e19e53aedd72b832374b54ab0ab46ed9d39ee64c942be7faf18e167a1edd5a6b1f0769546138bba063d18f845a7899d7a01fc100ebc363acd962f64b219549a15345cbd72376e46dc86d98708b53e4f8a7399f6868974a4767956f5a9f10203f150e245eed83f190fc6a35f17fc92d4b06aff57dea49ed5bb6ce8927b8eb61d60eb75680b69334b562552e563d753a11b1dfccbcea72bd09baab04aec68f0b51d28efc1b8c0743c3edfb82904844c8d95eee31d54824e8cb80738cfb0036cacf76bd471bea64a3f73c6aab92fc201d7f9d3c287bde18cd76d84aa5f98a82e793439c703b4994004cdbdd1ad81c1d2ca0a1741a03c0c163f97dcc0102228c35e0b4d4d69b11b167512a9e1b37fb74c0ea94a58f41e7a85e2db1229966470a7bd223e10dd3c47bd15964e611ae839705aa30cad1a859732b2947d52b874672a5f5cb088c446e1f366c71953ce1ea32de4cef4ee217418a84a3ba631b735c99a073c3f79373c3e38727a9061a1787876b4077f1f7a1998dee4390d5e45894fa40e9e4e2b641e03baf305625b40141ea5032279eab1e8658ff9fc13d55ad6971305b83ad97b954101e874a09bbd71944098a69939b8ff9bc9e2bbdfde6000ec0ad93760ee4860c222ee9b3c3352bc93f05fc01f5bd84bdb9e709dd61249c8967b6f29a54c322519e2050b06ca05a4da322ac4e18098e190198e99e1a0198efe77e3f7b1603dd6be22f477c3cda8d3e518b7aedb629756afa5623b7af971d56cb499b655ecf5dd4cbdafb90bebfc27bb7bc91e288661bee30bf31dbcb5d66cc4a48cbaf5fdf661341a7aecc5be56abf4d863252e71b5d6924824d2e8b1c7461acb6c347a37597d0f46c7d7b23ea7313ab2361b117e74f762326d3f4c3a4b607aea00f393e93a8d4fd268eca8be7f9669f5bd565b37137dffcbdfe989c27e69df6e3fb8514328b08791e2fcceaf7fd7666df4dd70bf77841ca117089f801042e8d33c43bf49c8f23b7ca64fb71f3c446709fc01f650926a5dead6cee6f2e3fa3bb7d993de6e3256c0fec4f50a4f7a90fecc703820562aab35eb95e17897378c4e6a8deac635171ff56ee25f9a4d6683bec38fd26992d1f623a5b3046e346681623504050d88601fdf23fc77610e38bbdb3bde081d3a81317d0f0914506ecda070724d2b2888644145d553a7d41c500912e2293da80cb13ddef383ca5365f8f99e0884213b547e38eec20f0286a309dbdf8fbb1f13f136266cc73e5daa1d04e53cdde1ad93dcf51d88c3f4fa8c7fa885174bcde6768cc4590a1296d232a8585b67626d478e50f37c3d3b539dd3e98d23baa65f157f567f674e60bb37c458769a880b469a926d0966e7e29d14cc3654b29bdc4894e234cd6060be542a6dde5c96d31d347a1b44da461f3bc88891fe41e1a73ca1700291892886686424c32e7a5d975ff182d7e9bafaad7eda3f79069c13b73ff2e0f64336f26efa2d2b6cf786fa7720a52fa865f4971a0342280a2089ed38c8b4753c3ce4461e72e9e51be10dfe97d38ea47f4656b192e6907090a42f0590c4420047870add86800c1dbac1558c14d6812e03112df9e9cc96be3bddf82fa2e4b534fb84acefeaed9fb9f156daecd63d47da3a0e35da3a548a25a517fdd8515a4aaf78889f44b9fdfc646e4f78ebec7d62c48811231d3f7c18869db089a12e96da79ce6a603ff7d437bc9a861c91db26b86de343ede7ddb0504664bbd30916e6846d8bd9dcc736e603764307532e8e1c2fb79f860e1ececc9afd2e756be56efdbff5746bfd57e3b4a4518659d47deb7878ee75020df6fc8c69f5d269c2e6d5310f7f29b578b90b7807b83537be76e38b1bbbd28d16ce93a927b92b3149b7cede9e5bc72c74e5b484acc0d7ccd13e2b3ce0f3a52d6323b1058b5ca7d11c7495a5c139fa380be4815ba8c1b0c86dcd4f274a4fd7df887fbdcf6dd63a1b98fddbfd1b4e11c17bdcd94c27b69f1dca20819de66e95a584990b673141b19d89027556a34aee7b0b096e819b5a4a2e9f62c0e7ef876f837353b8acb2085979c22a292b50891dcf237aef05cd09db3190914ba93d157111638367810b5c97ef6d9dfc29a794f6d1c973ba0f6e1d6a3e7d38f4e7863d4b7a580321cff7febdf73ec2f7f0b512187cd7bada31478e3cf57ab5268e1c1d76f320ca289fe9c558e39352ca175f7c727bf027cad8735dbe7d9a49fe7b9b9cf2ffbdfab280cf6d11f6df5f2a538c34f8edf1ebdc03cbc4b9614dc528f43326cb51b7d4695dc10a50d42badd651d49d86bad350a7d5644fa6d38ca5ee3427ea9556eb33266b67a8d36aa9d3eaceb9c328d25d0c6635fe7aed7a6556c3336f2e66de9ccc66a604cd94702daa6ead5786d52ccb5c8b466ea9e4dacc8571cd7463b21ad6d652e6a628a5941edd84c20929a4703af129263365dedc09d98b135ebc78618a8179f1a2645fbc78417a31ca5ebc783147f0ac3559ebce71d0a39c294b08c348443212911189644bd5c2c0c4c4a4c029bc1450400105145248e1747af1a2098dc99e4ca7190b8013916a670210000108a0c90c1112114bc48dc0346ddb880c60004468dc9fbf21d312ab3251c18f35377c107614fa73c8815b6d4312bc09e4c086bac6007b0fb349589345d970281bee6d4aecc9649b53818d815a626d82b09c55626992b0dd6d91e8eeeeeec6dc56da0d3d32fc08dbb6669bb3210c231121422292eab8eb00221120e20266832c64ea2f38371c42ddb88221491aaa17361a9188b0a4a118311cf36250a918a9193030267b329d666c4ccc8c29ab719a79ea9556cbd1a0d0993a7bba015f9fb4f95cacd68f5fa9561f23699dccbb32f32f2204b918adf4c6bb3c78fa33d01f237d998497a4bd7b5dab661afc4beb4bb5fa9cd9781bc5fed2e0cd341a7abce8f2651a10dd772f1e577ccf6a5cd90c7ce77d7db1d732bc6fa6f197fbe3864b9f7f2ead41fdab46e9634106fbbeefb236035feb2fcd7a37d9bb398d5836019b33c618e79b34de5086c1bf6720ddf8357b374e277317188649f9d2bd06bfefddd0d033ffbd9b1ae2bf7723b719e6a553e690893233b053189c6bcd25c31c6c6c6c6c6cba1983158697e44d23a4c0206ffffbc562d6947a8cb73f8965c1cc06fc8efe6420849c04ac736e7c1fe84f83cfcf8defc3c47d19bf9bc7236e32391870fd19000570fb69f8fbb606f8e8f6d360efdb54b8efbdf7fab626f3befb7143d464de96a37f3a9f6b03f2782e30e0f6d3f084eedbde978b797d7152516a7925ca0b0cd0858a7e6aabad34279ac7825fc749c598d1ffb62e7e2a460c3bad6f2a374ac3758dad1da47930fc7bf3f9ce35e736347d673fd53a1823350367ce3921e5810a4cb0745021a5efc67366b496a2c0bbc1088e387c211cd6794f4031e5fabf909a31f3333f737a2b9b542a7519b1f0bbd4ed4d12c9196031c61863843146287cb0f0e17d5298f42089a49452ca7eabb22ada848d2c278cd135fea8e63ce5e3da308a8142e5748d3f8dd70da0be94cfb5f65a80539b31b7aac9cf341d3b6a90321b693b1d2f0eb9958a58ccc78d21e6caa7613acd94e05562bb193366ad75625bf79525bcf2f9b580e3c1f0971bf618f64273ae8386a4d7d2928c9eca4f4108218470b44108e1ac48462a6bb2263be3facf782186530e0d1c3a76ec98ac0d8c1550315237af851a9bc702bc516232a7e319efcd3c91ca74329d4c26fb6efc519f0018d753330ef05ab879301cae4005bee6efbdf79ef6844d6d31506c7a1a29a50442852140ccdfc31aa900f84eb007af07413dbc7162f4bd666acd441b427ed90d6b3ad9d834675b01f0b96fcc25b0f7efbdf7dee36e9bebef513470b48cde68fb9bb136a8196b9a62057b570ff684fb56435e90b0f3298410422b2afc8c400a142bde7befbd479fc65f4b817dad00d805ebeeeeee867e3b7faf2de3757fafcec795365c79bb9e16fec1d8c095aec4dd2dcb92dc0dec8e124518c6bbf07320d7dbb2fbddd84ff6f895b3db270f587ce8d11d88ed52d7aaa4745a4d5a6c67ad079b2d6b72332b3c166ab035af857e7feeb160836a9cf92eb51c1a5d43d3d07068f1be5473fe339a2b09b130957a333f250af6be1fcfdcb79a82ca9db9fd49e66b34cd599573f4774d5aacabc0d6fb1dfd145a60e1777f1b676ede524a2994c46a8a28d7fbd120f39eaf4c0ed27d4fba493ad0dfe337c90fdcba7f74f3131884add3248c94ed982fb71f321150e18bce7cd601aa3af3815c27887c1da8ce7b07e87c87ebcc4d0720241df9ad0363668374808802ecb58ccd058eb3f199cc2f90b2a5c3dea14b972e3f3f3fdd9dc3b79ebefd388fcd2acc698400802a3872841eb972e54a962c59a698624e11441034881e7aa03d000104105a6831b5e0828bc9c50e3bd01d8a8a8a882611114450227082cc08e39208e38444430c3144952ab3ca0f3fd01f72c82187201ad4ddcfb22147489a5fa48cd1210c02c8d6d0cfa3af501d42221344772c5e887cb89d63e1723b2bc4352c44d734a5c462e576aedd87050858a4b87b9c5c96483929c522045b976561f9c17204cb900bbb2e2bb7af30a1a357b85cd3ab1876e5882b3ea0d60a856158adf58a104c1a924275e7cbad93115a54587f1ef29da35837698ebeed3c3719ad00a972052d42952a3da8e284655996755d571525ac5529c1355d2fe5f480f75eff0ea8d33f478c34c70d6798bb2124e21e6d5897e634ebb59e5e17915ab31a4359679688ac5966c3ba34d688cad19c4a0e4ba552a9f9a130ea7c4ea41b377e3eea3c2093a3deb87538ae6f9408d87b15c0385b1fffdc875d5dd8b8796ca6e91d087d38f78e1baf2eac3fdc71756123f768da80777e8e1a9b3bb2e1cecd06bd3aeefbae67de0e757b732ab0bd39ecf19cae4d8d5752c2b3f2c997039f10d81077ec84db09bb922318886d88a177e8e850178541928fcd7def753f7b73e0d320cbe4e81f57b3d7b31af1f28bc20f0019d88edf5015ec61019d4c0ccdb50c8ee6fa85e6fa7798eecb6cf4773452dcba27a172eb6468645235f8e54163e6cca0916a2ebaf49f5b0d7edfc6c31dc69cdeba8610421835df9ac6d4023b84f48475970fbf42779f7ea565ee0fd0e7cf24a5a5efa8ed4e174ae7ae6fc79f7dc7572607fdc997bfb475d45e277993264d9a7093264d9ab4aa49932650a552a95ca552a9a24aa5924c542ad564c2840913ca840913265645a2c4c9559908c1ac64e18245add8653d46aad8958d3a9fdbf1c71f5dfe8b6928148f51bd99c603febbf076a4db2c315b6bc8f8fbedf8cab870a5e4417f742f6b8312450a156e7204891227eda5499026429a148156b270c1c2a144914225aa8e2051e2447a5105510951159956b270c182428922858ac5e40812254e2e2f4c823011c2a40866250b172c2a942852a86495b5cee650c19157f390a494bc9a77bb9743054e5ecd6b1c3b1a8de23f1746976edddc7cb30c03845ad9cad34515d135a0b3b9d1e9c76813a36b845045d8da009b0b9d0802fa010202ea9f0eb24551511116cc5498cac3a22746c8b215a6c254749828b5f674b2a94b63119f1ecdf5cbf4345753b77b5324a5d69e4eff9c444908df16736e3f5232be75347ceeebb7d39c0e2f166e1dcde51d1dd6cd210db10c84b7044a082184b0b264c9b5a5942c61ad554aec0629822da52c285bccd335ed3c2dc32dece5c5d8c64a20cdbdb421a70e8542a14e8fd2ff4ba9a4924a593f49dd7ed0e82c81f2fdfa5c59af28ec961d88e3fdb4c888240b24b30a4aaf879af5717962cf5b0799f2a4349eee552d53ecd435585b6123a5943efa0e8462f564fdf59976c9ccf7b9f3e9d3bf51ab01e185f1baea155d68c0972f5feebc74bedfb9c9cdf2abb0e45b25f21ef01356f11356b1133e228883784aed5071833afc77b01dc92a15ab588531b52af6fd5cb1ade3277ebad7d0c3e25141a15028d4b4f3dd0961c642b0b0a74b4a40a0214258ac8218497347f04f17b663d5ed6615457259b5041b6435c1ded2ba7eb7eaac4fb24fd3c5a8536abdb70ccba9e5e3f2190899baeb71e14c59f1e78472db9a338696e18df58435390f0acd97817ffde8b1d17ccee6c6dcbd34eb75a7d3c932a55fad94ce68ac2fb357acc3ef99a4b4fd38e92cf14dc605bff03dce879a0c3ddd470c3234cee969697bc59e208e777c19f856be3d86e6baa68b392206c98d5b5762829516ba6f002fc01e0d576bad7ac1adeb61c2daeb976d580c52da97df2f640823d723280f4b49a57cf7e8719a5470461819beebd33dd6b8f2a3e60f35eceb5bbef47a0c7a8f5feb356f6abd5f8c5f5f4e802ae03b74874e35a74597908b62a93bdf39c59d53dea87cf8ddb575a97bfd4df65d0c164827c07c17801b3b02dcf8f69ae2d65d8fbd497bcd0f1380cb5f3ae1f2cde51ffd7b312e7f67fa97617e2f9a81d47da4f6228c76717b57f6ae9dae77ad15586cb35b6deeb3286c17dfd278f4f3a4536edc52a06a355dec4d5dcff5674db536d74174789d25f42df5b7d429a596523aa2cf94bef560d0fa9e598fa39f69e0d6f7cc7a37f4baf4bd865bdf297d37d4bab4beffa319bdfe6c3dd568ba063ec98a935c02fdeb63a49447afc3eb641646bfa3376873bd843fd396384b17b06700d4ca527ef38d5956cac1a97457eb9a2f4af6dd8f66344eaa3e7c18edc11aabc19a910699be29f53b97957fbcdc58c440990dff2936fc7b08c66fece546f9623d64afb5f5142aabcbef174ae95e946ec6954fa9b5a7d33fc7a1a0508c299e166a19f1f91feb1b7ee7f3bc292d14a539fe2ec29a5e8172875fafafedbe759763d2a5a5615dd9726971b76a3cfc215f6aaa38f6e9107bd5cdb2737b560c77fee3acc7387341854bfff9b5e89c8eb20189d2cb06d1e17548d8d77afdf285b4b95ee2b48e98d6fa58ad55e33baa19bb0db73ead1ce5fab466d9f750ca9e65bcd1f7e62e0f18da7a6f9cd15bd6483bbdc7c20dba996cc4ffda12061857bfd0bbef112e4287b4146d31a3750061f29f8d9d18f8be28949e9e9b01e1635e31e6e70ba18e96c157c70e59a5c364eafb5cd263ff48dafc1bf2cb1dfd4863924c0e528e8573712d7e856bf1222f822204054a4389c274444c501496ca3a82065948d0204b093be127438aec208461ec30e40de92143554b256aa03a4503d52cd28bc44216492d6491dc426ec1e56224b72ceb7514ee27ac7287d0dda13f95500b75f71645935113de1085384196c89f10c3a2c65d03dc025c2cd6a811e04a2d893f748de662b246ed842bb524f05b03d263a3568dda48b32ec51a630cc3b08771a3b8e9f8c01fb0b90924ace39cd3a7b76bb1df6382e07337ec86d5e7dd884ca28a98827ebb040e3d7403914d10f0e35230901ddc3089e81000f98091c8430b218bd8e25bfa2d0f72089f1310b14451113f6c0134041538cc1b7a0890d8c5911bb6582f383ce9b7dc03101dfa2d638942804f0a59d46f998b21fa6d8360220102876e0c9c608a192491042557a6d0c180972a4a30610417e420053a2b6822249b30894c1c68501474bb27148b14918006d67e49c2ce2de4ca92a89041aeacba505975913283e8c4c9fc61d5c588a865d50588188355171e9c8b55971cbc08ab2bb4585d31845882d5154f34117f9e20912a1364b96204525c810391b5966084951541b0c207b6097bddb7b2c209265238c2ca0a27520a4bba6f6545112b2b8c64a6298d5821241a61b32f11096bbb88c0a6301443900359831597279e8415172551acb80ce159acb814597101c1aa0a25acaac89203b9034b9b9051ac89091288884bacaaf0c1bb586df94282d5962dbe84d51629565b76b0dac2c4961edc8e9b6297c0974c8c3ead195bacdfb7a2628818384e050a6ebf6b12b23999ec8c3dd2bda2a204b7e32210778792df118840b0a509597ce00b169d5682831d2872830e523871220947747ab3557217a1c207990f04e1a50b123f5fa2d04981688b131d649043155974bac9ed1515428cf820b1ef8eba1d4f7d026509276f89276e84222fd677efe7459fc5cf0ad5fe498d8550fa9ece77edfdd4188861f8f3f79f3e3c3e0b7931ce08a30cae7852c241c9073b2c848730d00e0b29c294530c53b53b0fe7aad653b6e65f9a774da43cb56eb1dde9e4a40b0fa60cc5a089dca3f65a2e062b3f7f1a2cc2fca091dbf97b704856e36d4dc403c360580416e177edb916221c581c3a76d0bcaa511a8cf2633418b5893183a6566dfe5f3331dcf8d8f53c343436b09de961a995da246cf7ef830ea494b640bd17e59bac7cf0bd2ddea3de1b04aaa61cd8ce34e3def3b67909e627fcc462cc6ef1c99e7ad81dd8ae760db85da2813d9570507242364b67c852ca0f50fa723e953f4b22c057b00007d6cfb781beb5fd1881842474fed4e4104d02c11a9641608fa69341979b50a2e5be269250723b29c4e52694005d29c463016ef16e8a600d7f4e7b11cbbc820f947045f5a3c3453d7c401c164e808323847e74b885749a0b28437e7e7e7e6ed069d5163f0d047160a6fc9065881688d32b2e22ea2188d35a600d7f6b51c9c038204e0b3a504248875fc74e0ae2c0661d5ea9563a4fc7765439b30cea029b21cbf57f990fdd9cb6a7e7d8bfb1275bb6bce60c7ca1d6a309dba37d5c357434e7d7c55238ca2b8ab273a9ba46e67a9f7b597fbdb5c9e4a06f516b6323fe3ae69357d483526b4fa77f8e43a1523775bdc57c63e81a7fcb2d69499904bb646472f0e5716d33b654731baa3987b54773fe392d7df4286941011209ec0b38cf711ee7e534568249042c2efd02dc6079f4d3ef9adf2a02926b3d0e1f3f3f3f38b0c3de3abf7dfd607ba6bceba48ff98ec7d403b3f15bef317de32a81ed9e90e949ab2238b98f838872ed779120b7f4303fab8e96617ffe8e9d9691fdbc99f637e63b6b3dfdb875d4c68c9e357a35f347a3b71a67a3d1ac3ae65b7be9a675d75b7d6bccd6f1dc98b7b48e4d37603e83c1768cc6836e3ca67b3df6d7cf1c486d94be7fce39e75c69306fd278f4c33c49ebfa4958bd7eb0b06be6979ab0f2e3b39dd433174a5d4d2417da5f7683af7d184d2607ccd387b16ec43ce96f4c4cc974ed5f5ffa9ca6305a69b34fab9ad53260ba1dcfc5361ed3656f194fffce49279d590d3abfd2195a86170088dd28ffccaab8334f4821dc3a6766264426307fe4cde1124e2d83f9698591096b499a4c8efafc3c1ffbfa3ef5a7e6fc3403278c34dffb727623bb416a92e6d7d99ffd23fb4767863c92590dcbb2b26c6659f634ab3103ad9cd9d8ba53dfd1a5d139df3637ff5dcf6c603fdfb3fa2fabdf5c9457268767e07479c0f7b9f09b6398c137c9f89f68ba46e9b0888e383bc1100ac87081e6c82fe0608ffe333064211ca4a30942d353548b7a049e46a894ae3665b1643310000024c314000020100a878322a150309ceab2681f14000b898a426a48170b845110a430086220c8106488018620400801c828434456008da10c2e8b5186d0f2a52156fcae6846a4b7360e8f38f372003d435d292ae013be2d96f21b4eeb169459f9c605c5b7854e83a977e09c059f833b5052183444ab30307a8703cc2b489762e7c32beade515a6071a68b68212a11bd2fe805eb6c658bc54cd669b4c27003c32775b079a4e285a22e0db1e277453322bdd150975c0af393b3c2aec60b78c5a29ee68e616d4e944c547803a561d6e6b8d0379d5eeb97827f1bf7b3e1db5d4a7a6fb25491db74d76e4fe430fd85402340eccb4d9c4af04d8ebee59a4d1a70659545084b80ee91e93bf89c82cfc12dd052183426fa4a0e362f89bc57d44d1aba58fb97a019d179b7660ce60f1b4e42736939c1cf81feae530cf0908961082cbbf086c8b2d2108bb42eb421a2bf335e8fb8c3777cabca48da8afc6b2e4b738431407be243245826e8b4e695c020e294c16ac4fc98b87141075f71154bdcf8d860e3401c2011959c85f9144c50c95e9193508d10b88d5903cf265db7e1b4569168ff632d15df5208b3891b7a2453a8e1cd5ebdc843a2a1dd2f27f0bd1cbad4f997757e2faf11d446b3f00051781670085f07765ec6e1862e7e1a5af71a82800651c780bb780787bbb337cec489ffb1af2102ab8ffb5fffe1bca3180ac96710d39b1a36a4dc1c5fc49c68ec8cf52269af0c51b0597e6082a3f6a11c668b45a398cf0cc39cfd4b215764cd14011c952b06fb084f3e5544976f9f533868b68fadcf1e54303c33e6ec5169037885052f21abfbec518f136c960a981b0d6ef6e8f30535df464a52eb21c1d76b387bd4367e169732608148e1a382127cc3309db1a88506bdedd2b54142b361dbb5f69487d1d618a04f546eb1f8c4d6c8029addd4a18f71c74d058f5e41f4fde4c3c04c0aecf4653bc4fdf8c1e9fcec9d41c719c94614a43b2289e1e2dc978008d3b3522088d31fbb80dd3851976b020f754dd0a4427a0b0e5597ec6d80bebbda40a952837befdc0610d93f047e21cba24d0bfa46b7a090170e5802469cdab6543fba8217291209c24ec7126e21b9edcbad6fd3073a8bf545563353fa5714c42da8330785e4cc4be72b6830d6bde80bba4b30f92ef456e853d53bc07d283c97f61b8b8adb82ec9e8ede6ff0e0e8305a0595b705fa5aea72dd1aa04a85f4141caa672ee9c828dbc796757a443780c83b292a23b91eb2b9acb76e05869e0eec0d12cdff2af3d8b4966b8fdc0a0ed59e22ec196484bd934a2cf71eb92b405487a5f4f73ab18936977d702b58483bde833b7f3370c787c0a8ba35d06f90a464606a4a15bd0587b6ae6dae76bec4d5cfd05585370587649f51552e3aa1caf9ede91d3a73fa5c51004bf4c83739ce30a9bfe059d12d966ee66eaf1ed62b1d28ffa1dde7a37bd1de5edd9a8e873ebf367df59015845eda0d3fc414bcf752f403eb659e400918c59c99dde08c1f48f3b77d09c838bc6d53e070bcfd528c1c417a4dec60c2854221f6b05d8caf15af1d099e8c2555af8390b2aa256857b0ec66c3494203405bc60903ec52538085aa0430169a2040ed3520853d3413af56b4652ce19ad60f2a4fa0e30032ea0546b4d8de640e567da16606caa9b1458c91b5530b24676ba383acea56e4a4bea667d3bc3d20a82bbc11a9fc2835721cc7806d195b862955cde894400d7e7129b67d6638010008bec425fb212bf9db32de3ad22818ec38da4911775834380e53cf9de437faab853e0e675a98176c73b055c4cac52d11fd537fdbb0b85b4412d2e3d871a6c3c3f56d4ea149aebe668034f2384d43b67e5db24f69cb4ad7fad21a4739c842c4bf31e68ac3bcedda9b71b6030011c401585133e677f2d3398a408d192ebbeb16f9ea46df82908bb62bdf830f7bd4506a6b81dbc9d559b4df2a6e7b4ad950e9ec791b37cd9367e1ac031f9233255fb249ee0de2d629a60b031365be58ad71f002088c219c4dc58aa763ec117200264c097aa0204346f4016da18954278d292984961d6a5c385a8bed6e60975bfc47056b850b5aafa1e3f3d593d18dc545858c2c86ad164b5e93ade25dd37b360f0d800ccd6229b486709ba84835558d984cf0caad61dc8a1ce3b851ecff77d09b1185e0b63006cc886796d2c5d24732ba64a1ab169093e8a84f800ebbb15827adfa09918db558a52fd92e1882de904a848965fec0ff6fc3671cde7011fdc43b93277bf8cebe139d1cb59458acd4dc88c7ea446161eb7c4c8eb330cbf094181053edfee48b3921b6882c859ec6ccdead5ee0563b100487273f061764823443274d3f24834e6af6f11309198019e81c0092639ce152c392dacc5bbf48ac1db790d855a19814a32f88caa6082b346e2f080a04865936fcc1d0ad422c8448d71067c7ec61c65ccc07fd18da86683ff965ce701b66c691206102d11be4678c83cfb66fa7cc2c328eebc602c06e8e34e2ac6ca87229b814ff750cdc645dc3c20e5303c07be134ac052e2764580ac7df88007c0ca9ae5db61865f01e7e5fe36f17c8a857a6e1764b5f719f0f803b6379810c2ef52f6a052216d781c4e2e794af759d40e9d5b6f1a0214508a222a7776a7f5443dcd577f1203182a3f66a54416fe0b7802c0d8d505283287a88d35ad71f5c1c7ab255e653a028252cacbe4aeb1317dbfec6bdb52e8dd39a6ce948951a1a7d958ce0b6c35e47331c622f0af5c2d1c66dd5e76da813bdff6c960614cb36022e653b07a60c6ab5d342e7f5cf5f78c16002505b0a12c33b284b407f05c9de968940421a7fea4e0bfaf86787de75561a4f0a8ff953c7a8b33e224f3e8dbd163a71c7b5f47b496dbb31429b8902f0b7a1aa4cb9ab7bb47b6f9842a120aa1c4eb9ab5c049863b83cfafc3cc8aca1fa84293a8d8620a628fbc2e9063b528a12f9a0116c12d08bfac6e00da79003b1da701ef4ad29cb9ccb24d0447c194c4189c22849303207acfff8f6d34f2ed83c4588bbc21cad3ef5be9fb0982e0b0e78d046525cb1da021b99782968bf254517b40e10c4be81ee6e2fd9676cf3e04d410b378613d22da6b5006c72128608c870d0f01b08992846ea47159cb05f3393cab48a6a2d2dfa5c3814fb02944a42395df45ffc6448ee486b40af16a41bd19ce18009c1a58bff99f9549284aaf804bd459b5d9eecf128c4d022c994983e0b974695a8382904996ae7efe9316426ceb015f77f11c23213995db4ef9f6aa990bc7f406e4010c2f44e6e9b62c60c0c29775dd5a5999ee7f1e59a3373cf00298b0564d7d9932c0d9cd67ef933e6b96b19abeddfa1e8fe8b2016e0664bf1c4b24841912981ecc610be8eb0339dc265089f486320783b01aab52344a1b56201318f59162b14226ae4b0dc214c3c1e089c16b540ea1341c13889b681e114f206d983e0865e9c5b2b9d3cf6206d3e783ff642f3ed8f48605d093caa267fc62949d15d4830008d0d0163c9a0b666b0e0293d8a586f2054048dbff45d90171d5f4bb00870969bb119293123933913c7b93b76e96a0c2688e2082620a6bbe061dd1151c0f7f8fc9ea6813870d087360bda09b8a4d849bd1442b61b2e00c520cd70d5b02605f642ee9f711e3c55242397d7d3a619944632682d14a69431f4188300b7c3d877d739648cbe6c4ee456cafb0b1e4b662600aa77d48a93c2efb7798c0ac26486410840720faf17f47beb79626fbb36284b837416bf21548a664696a2c4f1159f686db8245909fb7b884c3810e55691ad8346ac846ef82b108a26e404e375e1a3da3b5ff9836a9bc5972344a801a5a2c7e0a9c6e48f7642f555ac473b8fad668900af9c51cbd69339444505d07105e50cdd1dd26c63853dad561b5cf3f5b05addc1c97b43db52500b16acd04b1c2486bfb08eea3e7d08bf644bb69910aff199949e49e5c5b45a58315584623e540a9493bbbffe16296610d55a728a68ae1cc063badd7ce62b3942787930629b955057c50aeb45866338010256d08f2ff91c05db524e97b847333bb3bf82a1035ae885e622815e2e780082ac8048feb560ff90b3dde2e1d83beb8a8ecd5ccf7070dfb2e4f433bda7e8edb60e570a0a43dafee762df488f6f144d9677a9fe3705c419ecfba6f21f1695ac8802c63dc98b8c66b1326c426e162fcec42104d1e953c7e298d68842dcd6621d44b689a016ecdc4889231ec19de476705a3322f51820748d895259ab03a182a0c39f06a9c7f5f9796979dc7168900d48b91858df857415c99093f0d76f1b736da112e89d93e371b5738763afe404a02cfa75cf53abdf74f10cd03bf8bda5711f0d6004b1f94aeebbf4a818dd3bcc67c3d3bd0bbb382f3e91790f9806bc480a194241bd9276b022720b2d1ce0f543261cf7180af744a80233f2ffebaaa9b8556222064a240fa91d806e4c9acb582e25001c343f735dfeb6d0b73d5b81a4148d8362f61c359e322b432800f23ea1c04e8bee4fb6a31774ad106779625a22300370426cda621c8691f0ec8987197a878ce9e8a402e53e0b6d1ea35031218bf376dde1ff71afd3849f05b0e76299df9ebe870641efa82b99654a9650874e413d01441d2a443a40a6361863e91b403e26310ed99e42124589c030584898b1aae9d88a07485b66dc80261aaa02515bb2f3694b4f446eafb17882c25dd04fb5f897725776e9f75c66841934cf8b82dd7357529b1ae6a7aca74c20a4bb38b585d389598a3b767ece97dd623009843b1d59daf99cce53239d9c7adb52cf1c48588126131f605617099a9ec602f0ffd65cb500b9c58bc5d2b603407f4590752e8b78cdf250f612bf5a97c9338a28bf76f5751be786d3a450e759acf1a443dc7e90b6216c5147e0546c4ad4d8c8e17a5f01075e9ba6a9ec46a01e09c5a6ecd7100f13b6b2f7e821857767277775090cc85b4130611b7f3872b811057ce6d5297a04505abf9b5e389f36302a0ae466758d5a6bdc54d313eeb1ca18210ce368811d8b48aa6789a37941858e4bb2f196c41ae208b96e1edaf174e4db6ffacd6fe69b5283ec6038dd0a15203275a49fcf96966292a886ea922120c2242ee39edcb38f594f98f84ff9ac65313a70440c4a452049fb30e1f93e9cbfd1c6e14989aba6f4d4fcf603aa6db7abdf02cfd19fb4ca0912ceb0745a69fd31ed502040f39e9c3c1a468f23c02e135d58d772182630a4a0c8b89928a6697aea8724ea4086d2225ebf5933808cc1d0bcbc4fd01d81085fc2dca07d1b8da976da83dac0badfc68982e60a89547daa5b8026f4c6a1510c01193b228080394bd2aeb9074f8183bad4fb62509dd80284711c1525a72ad6b21981745ad686f30a493d9b9c08b6c6a6684f6bf4ed58af169aeecd78afbfc893bab8c8f166e7bb34ce64cf0c38858bfd674e90aede69e1dba168cc2766fb61a628c4f193308aee9eec1793334643d157a74e9439bdb47f23e7d0fb80e8fc51befbff548463a171416cce6894c4601c4711a5265f3cd8959dea20989156e4cfa3cbba2f282331cf07a44ed042097e6073ba6b39598e0745bf7d55082e31e159b91b2bf94967ca6cf19b76a9a6cb81cce913878a4ca6262b9331ea623960a3000c26658c5a2b25a4e88750bcb3999c8abe88a799cab4e33599f27fe915a662799a49793f82de0d2012f62eb82355d2ce3e9f6922b13f9d5c00e9eb2187ec108ad135e411c0807c97f87c22dd1b1498dc624e0b44ce6a8b55d7e5334cc22a31611661432cedb2e45d9181807801a301c49cb3bcb3fbed1ab30f7c5f23bef8f84ccaee196be253336b9c210e40015f0b864b24a65c2ef8200a9d1edba08b0919d34e2d91a2fd08158199cf099f7fd861c0d0b5135906ab8102b9de2e2ffd04b3092478bc8b8da32e4459bb7fbb8ba47e3f638b876aa60ea156105b8da2f136385a2c6a91980b0ef153e887aa38fb24da81bff1fb67a724c2d613fdac1716ce729158c203887a85aa9b4a40ed041e39c1c1356b76bb54fc15c7f61f6a7fc703855a606b82bc54b287bb3aabc8e8ad6352282140192e50986b106d6b8b1531f48949fe7f59cdafe09bc90278608f77e95ef8424c93e7cb562353c0d9f4c8b8faf09bbb6440b2408ad7403b0168d0c92b85d188c4d79e9106db05147ba5b79b4ef407f175749199d0c1e25e15e6bcff487d037f248939190c483558028494ec13a64b55bf6ceac926bc3836cbf3ed6afda02ae694571b0e58a7fc7d151b04bdc92f0b74c1f4efb0c6be5eeed0604cc986834ce8fb24bb747af86fad50c76edf024849b68a1064f50b57b3f4e08c05d98e1da13d4d01932a8baaf6f6787e35a7435d22c7624e0aa9c425b807fabbccd3737b02970f2b6dc5013ae0784d382f937f83a3dd42b6ed95f624174c1984a424207bbc77d58f571af4d3b0e1a536901c2724e33d4a8ec7c8fdc3aa2fa396690c9227b8538a43efbc2b501cb2c27cdde577c6f2258a9362025b2ea5a076ffb0d7c9682504f61c21851c7b299d652eeeae988e3855e18d1636fe037bb7c9dd1502409333b02de68dbe345c0def4cc56eb08d86c13faaa0d201bf7108409e2ccd517cb540f6c15f900f66b00850b740785820e5cfda0cf7c5e2f8095e4a15925d48799d8f185206fb3bf78b6a2a0efd4bf3aa62587a2f0544a27a282114c5530921289f540049ed5cd1454c280a658ad42258284d1513fc5c494a2db357b62a5e3a53b678d305de0ba51edf8f513bb695d606d2ddc055160e3c7ab64c93c0be508bfe660c80f4b73f7c50bfad2a6ac5d8dfa4c41a52608a0da81c495548c8caec90e2d8516663bca2c2c893006aa703d599a7b103c26de696893523367d1f6dbea57ea42a21ddcbc095c5686ed1cd53e603d2d466adb5d47d8043093712fe5310080f7f4958c44a746ec155185b4adf3bf1710d31d27a4b0b37e1d6bb9c0a23f652a2115af1d0f26aa533ab0ee85e2a5168060bcca461d132d52034264583eb19900e51d9a3339292883436e929460fdf29a8ae1b3b4b7948d60a08ce0b4970a373422ff70e3e23f08bfb5fdab95a159aa59e3b0993a8024c6fc70f7afc5032752414acc68abd1693580900bf2296937cf960abb50a9f4282356440fd5fb4711add63000809c83f633cfd209259c5d5884a3981f1ae891c8b143e3189685dbb3248c6d580799bc9c7179ddf706eefcaeba13380d97aa8dbda2189b4cf44ec242baca537b87c866c13aa35c0c0804ac2c859bdd3c97146805fee91d03cb6857e5c06a9e437da871b273c229fdb5f13327a6120acc0d22d35c48afed917446fe9cdb65240672972d2af4c56ba07d12f0e00bda250b7b7c95da05b257ae2618158f9bc650060b98823e3a9259c196f316ad13a82ad69e1a6d73d67754a381d64cbfb47cbcdf5d88db4596b202ccbfcb932f8d94e1ee013927ebf10fbd2dd4bacf17f3dff3c2add79ded4f52250ea138d5a552a8acd91e5948253fce01476edc9e6768ed1d4eb3c59cf2443c7a701ebf976f6dd67d39745790b2e230152cd1548cec15d72282a23c121fc7f55b1986f1f57adf78a2df4026e6489f304086d6b3e2c490aaaa10625fe15ca7cc59f22bff574c4ef557be6318d2d89cf6db64c5511d2b8630cc4a7e72a4cc2e09125bd840528ac7262d6f051d35fefaa3f327bfcab9a4a614750730d002d4263e6159f0bfed0b1f497e89f7ceeee574bbabc3f66116b2c2d4340c898e346883a2157c4bfa9006f79a3168a12a54340fd2630daf78f2a1abfe53702c0dd4e373c9a7c0c1e33d89347f076252dfd2c31ec894e88f7bc0b3e19f5a680e9d3f6f4550c1ecd4cbc9a5dae4686656fbd49cacebad988db15c358cc5abe0d1caa2b04232ddf15ef9c89bda984c8d4f2ca35f1ddf3c4102fc788054a716b3d17e2978a7f7e1857ace7ae87978bf4d72fd4a95cb2251c8cd89c1065c477db1a0811ccd1e2bb2045a2627c569858dbb1ddc01f68665c8a9abd9c0e9b4b2779bff7ff29896e3c092cf3715915a61c6599272ea92efaf771ff180b72832332b052ecaca4473a19f29304749c03c27f5584c39a9bf5a7f128817e6c7920572ddaa442de7bed9efba629e6b4d1856cbfb0a1ea5c745864e6b323a23dbcb944e797594ccb689c668d2c507f7257a00b059e53b3c7da7f26db93f061713edbf161ffead3d83540ab91a2efeb1ee568f665bdf7509963c72f9ebb665168f4b388b1cbb6ce02d24b8d9010d7c6fdc47744cfe4d1be56173ca74669d23fde1814d8b8ba74eafd796ead40abd102969747c9a2a1b53b947caf18e08156ef939607a5524dca88cd460dbed9df80243b07fc6c92f776ff156d9465647560acccc5444400a094e24e4809b861dfe11199e9ccfb28fdf876581a24ddedd800c0ce0722a188289977b7a0535ae19d96e0c27a47107478c49913237e8275cd3900b0b0f465198609e59a3c81d0a8d75bbffaf6edc5bb8106a225593f76f315f5a5b9df38845f25398f548d8bc9a99714f2b08d7bc253064b2663f36b7bd76b1cd9dce45ed807cd3467e94c4578f2b141e29bc1cfc720f6ccc3805b42525cae28062882a125fe6811fba84724f30cd1f9bb68841b269f85f646d4ff9bb8909392cf6ff749f11d6ea0f07ffa892d6cbae5ad66c81923d51bb790e9ef80f240683941a14bc73871a17373b3a1e65b8b39cba40a6ac44bdce78c749cd00b2dd1164fa542890472c6206079a124f4910a0f9f4db3de5aa310ba1b176b4e5a73de9e48b9ab7ef82d26089469b6012e39485eca2cf996027db1b092286c9ec296b11d9cb53fd38425dd407ed763f458f9b6437efd36db69c1d981075d9ebed0f1275c5d102c16192ffe27263fa812cede38eb9e72bcecdabfc6ceb29cd0205c7b8748900f2cc85b2342f3f8857e69740921e891afba20fe617c06aa06cd60cdc52ddea56f95cbeb8a727ecbf664cce5a564421cbfa13fc32263898c7381921109d6a893a5d7d34f9532f753d4b68db8a32e57a25f2c06e5e294ec5afde93145e119e51b8212822ed566967f140545a0508f268326b51a145302761d844df8b083034af19849cf5b6c69b9c7d767a10bac2135896ae5dd028d9241972db46f95ba3dacccb333687dee422a7621fef9fc6c933921ac08e8acebecc47c554755ee6e75722033524061c296538ed7fe4d21cb661a63ed4d0dff259b6c92fc112f082538010bd85626bc518b00f0e4c893cd66f46fdbd97860b20a9f43d9bdd9c8a3638e6d72120f8d7543c884168d8eaf9de3e047c075ad9e783032c2400fa63c88b5513b8359dfcbc7e32798f082974964fa02525019c2c8f66ea1f8a382947d5fa8b18dd906f39fe7cafc84b737999f6c507d73d79c0559a126acd0d04cfc61307845054488c957f4346342b04d85ff1f431f9bd4c1dd120078ac87d07d0c26a63b68da0f02a745e597c8baea971a7314c4d77c1152d315511f9dc785d5d116de57a467bd9be1256f54b4569582a91ce40e421a0408a4e89b892b305d87b811744b02dee0dee18a5206fe2788a0157d7cd786a368c1ab768767c50c260f0b358fffadeeb6c32775ede54c76fa3b201516ff81c29d32b6735f1efd7af90197e8bd6d6340f2643621fc0e8aad78ae5fc11c46f2197cc13f59a7c3a44d9f9ee28e5af3f0660b8ea7511caecc54e452cbb549b77136545e4720ebcdc00b2a6b88f628a70ee3825ae6d4d3bdb63669ff58b3630e4b8d3ad73fc1229014df258accd480ddadcefaff8fa4ba854bbf56c6b5f704c0132f52ceeb44d43a2405089ac361c0c8afd66afeb037c6ad704c0e64c33fbcaa914e4b5ff1be8b22d95c399d09ef74b6bc385fb8c850d50c1c5151a7311a7863be6449ccdda022e55b8c0074e363f07e84344d938cd75f23c281afe8fb4c21855fae5b7764e98f20309a1bf34ec0181259859d522211cc40c9b8ff852f36428d730917ace803abe520946480d2ff0d10f4d05a302b1d9ddb83d0097a1199e09ea46fb85b964d6a6899cfa541c12caf240c1ec950b5c3c6562fa98fb8898377f8241433f8ff9d4c803bf14b08adb1843fbbf7c93024695ede503e565bef155171ec7f50ea848540b925367a1173f28e2a6a9b6aae700a4ace122a52b6377f145b2f0064cc6e4a629cdc2008302ce8cc7ea3e280838fcad738ec2c4ed848088cf6efb8f39c5d95f2898c54e4c5d95eb72c9a260ad6b1c71d30ecc786f2b6f4244536b984b6c2439f9b7cf36a149d301159b02c2880652c1324ccc0abb9203deb333094a638bf52739b8e3ff44b4c2c1b055f2ea63a6c014f194e12188965b1696cb0995a1d3390d5cdb17ce02d7f78045316fdaa6fba640a47071be7df73fe9b61564af00f995768e746fcf6949239b801a93db2dad8e803dc426876fd737e70c7dfe6cbb22e91e419f3d2823d0e04ce5bb52845e0bc4a2fe16e641e3aec703961a72a6c14f358e02e160211cfdaf37b1d337b9b95630e0b0d6c4f22307882f136a0812da72667753f67ca6483cf4fdb06d6a84c48c0d36b3a58947ece14f33f295ab8458b1a06fa318735379ccc080b86fdca99b1df15e12faa35ca99996f51f3ec87d3d34a66ca4d9775963ed56797579c6daf967aadf008bbce6fc155c952ed3dd06f46cfc5f91eb8c2e9cfcda04eeb200e3210b0008767ef53b53201030f00924112356cceb1195420d4e5c4c7e774dff3d9107f3535cf90b9dc919c21275423b6394ab0c7b952ece9139672ccd2e6d2c2b814fc7c433a95307136aa43269f0faf371069e37ac7c22537bac26fa6a4ab806fbdbe10b3c8d3f8d091d78ad557d0c10169bdaedddbab176f28f59efe49ed821fc7d2083315b9fdd2554fc192e0f8bd8cfff214257f8c17fa0fb5a9c14eba86411affe2dd7178ef154d75d749ac400f7037cafeefa3dd3e21428f6768d9faa193922cc2121d13d4c295b241ca63d07439e98aad329da63a2ee83bab032bc2a9f3932f4635a691b5ce36336c1033ff858981035e4a26cefca80fb550df87a53ca207843739f268b9ca8e6c239d0ac2236a590f03b3e906f1bb125274b36cb50201670cf18bf4d270c91aacd1e51fd29afab450f40656ada6ccf24d454d351af597504dd8285a0e02e67aa3377d68a91ab641d299805fca9d094e8ecf7d8b402ac6fbef797cd9887c1ac9a456ec58b4c67b8cf73eeab67b5511718e1eb88b31103272c28494859ca84a501f02fbcd2d3134ca675becae0074d95ed8c58be2201ffab9d3b9379b1531d3144ec3e68c5839d68903986e5617b347c24ee8eca6e8948cf6680d2d140decf240cbf5571a0ded58f0f31841d838689e866619f3b21c2f0079a3da0369e8f25827e4194a9180baa15f2f607d8bc8015b9643279d9dfbd9064801b504349984ff1b229f6edfb5149162aff74e8943c1b97787902a9c73b5ef4ce202ea02d5b11d0eea5ca0b8fc5212d93849bc3923ac033199a828918caa42373596269099b78358ee9d03c681ae90b72627bd69c3a58092077b201e6f24438967ce14fbc5ac984cada0506e42f6dfb30ad0466418ed4777268f05739584fc98f0da330b97b5d8093b4db8c173119566dd511a6906e54b3c050515baa2d7984a740f81315ae962a3a900a759e75d83d52cd0dce5ee14ce4b3f6d5b1573c9e1c3dd88eb57ca789266551516e1867379f0fbb84a26fbd9859f7e583c9043a4b28d031bfa30fc601f32f2ac005f9eeb21d83f8093aa38bba6cde40a3b38c6322050c57ab5f4a448c3448aed9f5f45058eaf88ac0f7d8c9bbdf0ecadbe0030356d97343ce3cf60f51d05106ab010bcb90b1c498305c5e1ecc06689efda9035817d9af3a6350170117ed21b33120f4323ac9c5eb0780d1e4a0640d2004c831ce7cac1b1df0e08ecea7e1eb14d9404cb8ea7786aa678de638eec08ad82ea63edae144d10b3037ce711f72c25acf2a65555e5842867eb63e9a72426b58168d4c86eef7ccd070f7e32becb8ff584fa6755b7fc6868d045ead3960b8c71b6e92aa91a1a64ce4adcfaea026ca1dc3e2ddb48423981ef2a887edfffb4a654ad06e4b228405fd17939880fc491377a78598852b5af54124c8794ef021ac41c6cde43ae0a9708c3e1d436cfeafdcd978977be098938158ab13553a7b1dc968921d9db95af20f252f2092708944b16ef8dd06b6708216959d6eceed16c6af7a9b20f22bf5eb16682e648cd8186b74294734dd801f49ad7571eb19d242926864280b3a80c207546c600bf3727bd5715373c7934e7c20905680bd79613deabe1b9d3852887fb443fac81edc86af814e5b175de702ae942e3e79d02908adf4f784a6cc93ca323c6f062b73f3b21ce398239d9ab802555129cd7dcff6b575aad1d93f0fe6d8389a4237574e77ed1202a5f41c6bddea76c3b89748f474b737b0d81e21999f329db930dde0b778f90e836fee805db606a992a3901d38267ebd3b50e4d31b385466c0ad7a2257ce470593c6b11a1a140405bbce54a97de4c2b98675fc048d28c3f40d04adb8c0cb4b4c9592f1cab089e513a76483aea61d2e2a48f884ab4ccda4d7eb511759971ca9a002e4d743f16790c5bae99cd096955cdaca20b694395f0f516cbc179029aa22542f93aa8d34392e138d34d75f24d72565dacff05c4c20576e178acca0f82777f1d14d842e63b2e28de2a3efcefae6b761f1c8a87460b979cea11eaf4fffac0b21e51b02a321b90b390029a3479994394ec1006d37ed37a0d7a4b9b856508120f6f5cc1d71890e88c25aab61feaf3a53b270b2e2243a3980faefdeba0a27c311cc38edf2b37f5315383f9534d0512159d63d8082badabe730a483b382cd685fe0019585b751362b70e58916ab208e400065336d9975ec3ca854a6bb0bce2b084a538086bbc91fb6ad660ce6708e804ff6fa6ba35abc2abf1817cae2dfbf812d6fe220b32036e3700bd1e08677d0efdc7b3a8c85c857bd1ec2ac3ab6188f4a0b5e3b89aa325bc2a1c01a122e37c204166705833421677adc2879ab00d2190fb67a5349a3435c158465ae7c0c11575b295c1efe4e034e462907fd9c51f57999b590cdc09a4409cd0b8e639739eb41fac1b0cac578120c121a29a2af2cb2d532c229d8b38656774efc4793fedf23f22031ca5d1c27cac08e87e89f25f588c6e7a344c9b54a4b90d273c24b1c869f00b78ab730b61ed7490789003f3e688f39bccf91d6aba788a7c80f5c20941736879d834ba2ab1ebf0e2f0a78f6da759064a788707c3898a8f898d0cbfe18b2ea5460a744d0aff139b8cad30e1ee1fe8ae0313b91f0b501034274a80b18fc24aad0a1621ce2e0c921ba61801eb28fb990e25d2e06f4895e0a775c7a7b587f3a3e60706e3c00d037ececd102eae1530c541d8eef9242184ea40362a1f201b8ea5c986dd840f79d8487df696cfbf13aa3e00593ff1bf2ba2076f3aae353ccb83de9901b56cfaa6e051f48c3bdf2804b700421384e19e5172c0ed3e9e08d56bfcb190633502c436625446e15228db066d5aa8a82fb88dfa3c80e691e71882e461ffc6344cbad0bf586b6c78fb8c4095e7066599110add863cffbb310b8a5109dd9879bdc115892e5b698f58521c55feacb8dbbf4203304d49036e465f8a1fd868ed7d970f0c04037ff94d5b3ed478fbf2e0ab9fea9ebc24f2603f908b3fc7effb31bb5070dd21e28c510904eb62110209ffb997bd4309e15f743a20f151416ab4afbf1fe4e04844206cb2927c38ad6020856248ca829c2db3286adeeaa009867005c3c30766e8f3e5eb9d6649904adba27c0e91b29fe490525ee8d28ad98e087e28e3ca1e20e80f210b068b3560db5604f77208bdd1228f01be56404a1784e844e9822e87dee3a77d33506531fe2eddaa1dbeff02cd424bb8e8fc491dbbba409aaabed3dc57fc72e5db94424d6699b6ea2324b0c498909b5808e2d852a226cfcc82077556cb0ff03bf47c2b20e6792c1327470fe054cfec554a79e64977489c38888470908ed9f9788b54b0e94494373a8d39c554ba7b36709f164e8dfe472d4fff061f02eedc6028ada59cd49c7044ec9c60753f2928d2b019f2a7b65bbfd32ade7a054e2b8372c835fd28eb26410435fd5541a93a6f0e53ba0eaba83833e2b05282374804b4eb22029bb0977c8291f770b4f09213aecfe64b5251bdfb5062ef505a97436468deec1cacfe2675470fee135dd6b2b2ad8895be70f8e0500158b811dea677ee0166fa58a6f953f559eaba66fbc13d949c74a7a4f35eb7358615dfe1f7717cf002b21ebee10a7a8f090604331a5d1ec818139047428b7037e6823a39bcd2164187f10509049c10c7d4ce76da72f8f45f8a5d63256fac3552a1d7f153e89d1ddbf021bfea234de0ef292cbbf8b520b9f95ccd8eecb3e4bc6a204d0957725afb235d66969187a43ab94f2c76146be1e1c322e5d6a0917da17ebf8a34f1e86c7cd87da8799f306616702e7e4abe4810fa903b7843f00620ab10a05ff4d3d8a69879aa08e7046aee99b219e3e922f10521b7ddb2aa972618844a5f978a2f33fede03c4069ea7df84692390acaba028a6f28c165b5aa6ed7f6ce2bdbebc68c2bb6c4428dd10dff7b6f56b21fea590e370eb7cd10286f08468962cf165ae9fe523312da40895a184ff2b3c1f604014a50b9ef2d3705a12fc1727e26090529dac7e6fcd2774618c7e22b6f481da0e1f9378926f148f84c48b5858d11aaa2a869eba0a4a116440d5a09690df80eb2a12666f33b5180a8f9d7a0092b6260eb06628690572864707037e89145748952c2f3c58d9a2cf7c8b68c392482c6b299ae264bce25e763e18ee4a346e0d71477383edd7aa0b3d5f855944e3392537c438926abdf8c94fa8366f8c71b7ded86314aa19ecdcc97670c96690d95c28c17184745b0169b45447f94826bf9c6d585fcea216eb9feda5e09a6bd1e9ae4147d4becde4ff962bb5e421ebdfec777b3ad5dc2abe11a0d0108888a8b6a432ff52be326a41aa133cde185914edda27dd8f58ff56390d6324fffa79612862604bfc5b439709add1dfda34d30869e16a95dda9a94ff20bbd63bdabed4d2d20b4c0f58038282d479b3f0b08961f56900146b25791ef477f208fad1ee2559f10cd71dd0811fd0b355085a9215f421e14d334860d2804626b98b8bc96d360302660ac5b233f9d032846af9a9ed64cc77d87639368459f3051e7996c8206eaa776c410fdaf57e56d00fca0d6c464efc6b765a15187289ca3ee5967b7050646c84f32151a630ffe2a3d812b2aeee85705ef71c2da89bbca06efe8531b946e5f2c8f3bca0a810cca9d270c082487bf9b4d116e879ee4ce0e9befb74c1aea80f64b2d5311b030017e771c83ccb7725cfb2c9e94f7bcf7eed058d03051a0479b9336d8fe05d068724edfe57356e430c29a60a79ba3d329a285ff9d8ff9f17394d821e61ef43f212cab3e7715cbd28efa9c29a368501a2c6689320f83218e6a41d85fa2074642506647f8a5f432ff987502070a87b08620b7a83089da6586da35a6367d3c85dc066b9d96eedfb43bb112888e69c7299a44a5e0530b8dfe0b7e03ce6e8bf002c01ad1e9f53069d407d029b7bfceb57ffc0170ed3cc6439caa434134623d3ac680d02647d1036cbea87958081981f52c77bfeda3d38cbdc06d4a9e3f8ae8cafbbe5eef352e149c052f3fbf16c440ad83afed035936c006724405019e6664052f7322899f9ce488a2dfebe98be0e29cafd61adb3f1fe5116a35ff5892aabaefa7eeaf89dd372897a7acfb687b98a00cdea582a3141936b08feaf9459839408b35ee68d55521fd1331669ea2bf3d08d6429ddc9e6bcc2657fabf63f050f6d481f3a4423fd2e83121809bef86e5c3f1ae51afedc59dbeaa22c4fb50645b506af983fc7de221c94f7206ecc1b507b12500dc1a40c67a54672557419213c0ec4e254b6e6c27207319480cf15a7fd3d993140698dbb0c6186240423c80522442a024b0e91bde7a4eaaa098142eaa336b05358f94f86bb60e9966125810c99cf7f6a26fb437701ac192aec38f443c6962a2d32ae953f3c824d08da3c8e5ac861ea192a4197a7327e16226316e324618d1151468e37267d52567e34766d92a0d7c26d4005ea50f0686ee8e53ca84bfcd2e10993aa4e45f65d28a3f148a1e0c2c9d3fdb578adbd8fca17e7742f34f8b083f505577fa48699fa2a88dd3e6a4813284036570fa49fbc9917dc483eb4b5a97bd10fb1ad158444319571fdff4b0a59dc26d1d60459622512dfa78ce768f6b8f956bb241dfcad4dc202a2ad797e1d180ccf284ce28f2979147b4ca5354af7694ad4ff8f08d4c40671a083a579266ff06814a78f3b61da5c8b3e83453a3f8372c80a5396a91f66d24393d2ef947836a35c27a4c13fe164e69c082faed916c98cad577c7cf021672788ddef565b4420064c4bafa1fefec016d31d0ad056c3adfe54fcc40080fe0999489c12f310a5146176be5c49ad35e7d77144f22632b415e801d14b653d753cc27718fa43f99e30350901fe096958b70a8816d0e6dafddf5647a6c15de61c1491c18a1601a554d648c67ec8bd0feb62e4e1a8858a3a26414b4911aa97da4e6b791c0bd1aa4165b0ce708a2615519b46223b3bc1683c33bd649e1270a6e85c81b5a13d8a9cf764204970803a8c2579be73741b4f745af4e096bbdc050f3c584d3fb31f244964d8995c7882336b28c86966a22f3a9b01dad54b89a766088452841d3ed68e533250dc5066925c971253ddf324fda761024b01bd6f82575943e97b373254f85b9de751ac32eefdb12478a2fbce73b0d9314f96d1361284408e10a92e102ef57c7945b62071bcd35595b05cd9dd0b5ebdd521e193c6cef318e5dc056a8fceeae5d3581fb4053ee3ed22d67edd7f9ab8c9c468c118033098ca8f16c4e6aae89d1bc02dae77fcd029588bf57238628c94b6a8f1100fb0c7be22185b130b0749ad51b32866c3ae496dad6cd7b32f644320138cb2a41ec9b5d6365974de78c453a176832fd4b32024264626290d768efa471d36d21ab28aeda31d1e48107f91bd114cce91b8acc0210b8c0ec4ebc28342ba43edff87bc745bbe31ca5323316ac53124ce842bd10c2aa1f7f47844e5a968963b29b16090c52f9f3ff7c50e049899b6d6844d0a9e354f4438d6425bff2c52bed60342c6c5d52b5f897e211edc5d0bf8ddb900629c889e787613214e03547e9b9e7b9cbb1e5eb2935d143bfbe19cc8800e673b2fce32f192e0c038394301dd5fe9643932ef5587df13f6469d9d27cb0f56631ca469a6bfae04a18df8a55fd452495399d5eaac964ba5792dd525857564f441f673600efa514c2ad91119c387af0408abe26736f32fdcec577196608eb5ad4c2e352e1834a13a6085a783afc0d6c9d03de9f218d66d3669ca2afebde3f0c99750c3518db6b88090c2c8e4dbdea43ab6bf07d40d930f8a809e1f5fedde9a9c96074a478d3f0609931e36013609f05f10a843c42037c12b3f09ce2d0b88e81066cf27b17efb1b401cfbde8ba14294b5b647ad1362e955ac10eb093909946b7ee1b71cd3d74d01c326b5c8f7699263b54f3f5a02a3d414f95a80d316a9ca0b9f70012502136a096c4e79e1d29ff25eff439fa767fd327b6de5f0e16996e2cc80044910acca3289f11874ba05d2f403fae7106782b15b9f2387168245351ff55c4ac59f7982f1e129f1324c60dfe73ba9fcf6187be8c4f8657d1efbae2bf19471741488eec79bca7feef38a71b432d6e0a3fe4ae831332be151ed6bafe4873a09c7d03eed96f23377e03e7cbc479fd826bf0f2cd8a7fef39d68fe03cafdf62a964008d6723aef3075fcd226a3edaf0ee14040ba8e9d803680a402b220c611feeba1a1d28bcb262b6def020fbd35f40596368fc0bccf6f4afacf0ff7da13432c29d87c44a4be98e1e3f4f1ade4bd1ae84326706e4167f55cf2237d6a205e262f76f3d106397694e615ea0a450e43430b8f4ac352684250afff69ebb0724e888f45718992a8b1523686ce09ce0c4628bb00ba5a5438b779997650a43ba60935d902e29c9fe1e46135e89ce1338f6788440837d5ea4808af09c3ed0a80811558cf2ec6655d9e052233fccec572551a5e3b3d208a9307944250fc02804154ea37dc940ee72d040f22fddb6df8cd424f236b03965f24b30a0466b208e61f0b355442de1170629c36b1e169deb58aa0c59f075c88773570156a818c5a5f3906e7aefbeb410414181710424e47330f50113d579e221b0261118404e82d249749fc957e59b312cf22540d412a8255c916e7444f158e127db3142b1f180616a999f8634c4f00d04064b0ba0e423052372b1f765247f1dd6a19f22e3c9c1a29732b47b8993dbc2bc720a2bb0e9085d6af45af7bd77112028f77b30e6b788baf79f58907e0d99094727a3c71eb040c2a20e3e9978735018a13b54de8be1b36814c0e910d0bd02251a85ff5b30b461c8b613c89e93a006109e29b8fe105d3144a82ba15f16a0a6962e8e7ba5240f8f51b03186d5eca61163d5f1bd43d4b57910ca849060f4f3eb1a246642f5e427ccc8ebe7f8c9293668cf45dd8c95c8c8a0bc64c96a16b100322b04081bf5b6c2858d05212f1134f1102c6c4288c540e5109c636a0f4ba3cafc6e1d09cecca6fe308a2a8fd32b7fe5a20848e128638097b8a68905196840b3235680bd070784f766ad6874aa7ec7509dcec0c043540dba6570cfa41df09af517135bb99290bce4db54026f6839e1028e66637be289d97b0f57ea86042187224478c7081177e1b6347877da869ab85d163aab8aaaa8eaeef54f8fb1ccd6427b039fb6227a1209139acb2a55a0eec38991ac5dd5a64a628470caf60d17c8152655a5938df1811d8bbed073eeee2e3b885be7679e7f835dba65e902a8370f12cd7a320f4586bbc46e92532fdced81bbbf1f82d3579f24d3a2fe4e04816798c47ff0923f541e44989f20b1cf4cb1bdfb9345e4d88b86f869e16ec9a35808ca095404bf332415969e429418fa37480f7ad2a2f656f0e2207e983236562275bd5e5384dedda348e4e3e1d9aa39d41a6a6fef83ad235eca68531068643bffc8e4ea5c675bb634f3a55c4e3488c022908c39db739032a9e467e1561b33689b5650c3acf3d4071ad5a1590d1f99c19272ce60534be039b0d56d5c7c8fa42ee9e58a5ba411aa769022ba9a090d876673962faba660a9a247dd9a423149c2b51c15c05457b05b360b1dc5fa1e56a937493884e686f1413ab06e6e0840adafd690f7f0da7a6f0efa9a258a41e4223fed00387d2a4f545148c2b4f89700037921c0079b5ba04b30f7f033b172417f582a69358f947452a9fa396f15a66c68ef9df467340473227558284de695f1ed38d4d1971a9fbb7b665ec9efce1d017784ed2f289f9a3edd421c014708b05e5d3ba4e67ce15b71211411bc350352a82246eb14761887586b9ce40c6a51ef73f33263b8fdb37a8a9b12197f980b4ea3e7e6710d4dbc7b54103ae2e2cd5dfc2b495b4bee1b5768611a8e76de4a909a507983af61473229dcbabc5f152e84305210abe68d9bd9e0794ac52b8572ccf5cb112bd9ffdfab204aa8848d7ff168f550214f0ab351b568c3e4e5b645658771d1c1913204a62c5600d1487d7c4ccca0d4b06e13295c7281fe4262a71c68df2d4ca0cb3f844f911437c09a25944d55ad79e152c7ba7614c87818787e51bcb812577c125b78c2228f203fbcf394cfe0b262c4f3ce59ea60cb915e0bf6c1c180a51162bbd3ae31637b6c5bb13dc4784a4127cfeaee095df4cd4a75aa5a8f00277971fe989252ab7a8427a7e560eff408ecae6089b435caa1c1ea00311475c7056bd214a26340a79337da7df3b9065b8091e42fd814ff7ea30c0fa69f679ef75e835e160ab70a29f527bbf8d79db3ce9d94d48bd29933c389c410e5c9a411e3ccc38e499f88998428eb11bc4719bdd0650c86f2f009caa9d0cc9b0290702a77994027dd4e3ac01b3a247a3e4546d29e752a02a97ac9e73b2e3e1cdc037fb692121d47dc256b9e0e28aebb8aadb0c1eb72f4c8d2066cd5c63735522abd9102a3ce376624c02a38751b4c869cf595750b38e82ae518d9b2a21e3426031e119e2c501f6c59e41ea698919700a8b4c928d5d85eb8077d490ae4579c9949a2c53316c6a1d6aa6bd437f6308c6764bcb692f6989ff60a034f3fa0c9ad7b544f2e637163ec44fb29e9e595395f205976c88fc819f466cb82c1eb8625823724f07e375bbaa1e51bfe209712bc169bd68c7bc6acc136c8afbcda85e26be0feb387391cc7d3eeb70c1ae03342f2ffbad0f507079824169518835444de6cd96ac102b6b0ea14ea17a5e5db55ede8f1a71369fd2e41e7d600456f7361facaeab02f634793c5dd2e8509775756663e00ad275c3c0e0162a4541a41d66414446072a71980fff25e258a4610a1aaba9f251fa363a4179dfe1d6251336ce7a22b547ef02a3819993804ec249c26edb666dc18fa18032e66e0b85de61cb3cc6f32d5bf14d467586ff0257db038d664c05f2f60cb3814d543402a1fb0aa0ccc71a6907f0e0c7fce960b1259e8bcf4a766386276a8ea6d1d5ab94a46447f462f7ba70c9e5c4205ca3934847ab17db82850520858ff8edcf45d023f2d24f4b8840ead00aeddc6537177dc936fd97f092378b9c074728cf89cea0f73cd9dc060ef1f165b3247d542df94ac873682981442d2781ec63d6a584408e1951f571e2235ffa401b42055c8256025208a025d3f04ae01fbd96028dc78a4ca5006539ce53906dd570ad8f4e9882d46187311e600d4be3de4668924241bc77fecda5f57f8ba0eabf0cedee2728f3c8e13e81320c30f0276d84971be934dd622536018269b5705c30dd4f679ae5bacfd2f2c0c70c18a3f024797cc5488f3b14d07aa41ba16e538d9390b3cb3b2102c43cfa28b718123a834709b923dfee880e9029c99f3021a3dc448aa76daeeb59b244f15eab47e7818a90391d595e0251bfed2e4bf97d8e802e3e912a9f02c384b2d630e32c349495068e2c5bc6bc747976ceca605cf369b71dbf26f59eae2313cc04b8976eda0d26348ebf412857c2c8de8e4bbf7608673faed7d792af5fc18bc87fb2634304f80c85ef4e50a86a79325a28a2fd01a2ce751d04356aa195777dd51b8ec773c9da19199d738954a54842695b69f86842e7c69d23e1e7963c302cffb9d84651353cb93666bc8471e53cf3573a7ec3af10a4cdbf60c8d272c29a7f84a49e6b2c2d3a6de919f06915275152f2bb39dab34cd970f4763476f00d150ab07c7aa799d072060ff3c1e6b47d995e2a64f247395ffa7ad515663fbb085915f8abaa4c91201a48e26988223152e00c9be018bbfa5059a1376d864daac66313ca5a3752c888c985bad7ebd026cf45eee8e0ce966287a42233893da59f228255d20239bfe87821633053d69fb7022c27d02fcb93981bc7885f64736ebc7a1098afc3e3f362d04752f3172fba474762fd4c06b5513b9595712c0a826a44e027d2ffaf99a0ccc4eff355104d72988392a2837f7966aa2d8d2f9ba2e2b8abe1cc54a8c6b19f396a988e735d8ca2d0b5b0460a9d6fbf2ccd2fb60fb431a3a52d027e66475f44812f27fcd39e288e41f0fd1e54e3de73ee1476592fa4c1935de7ccff038d4582660ccf5bebc5e3ce871f47868d50c42e53a8e153c1ea0191c44972043aae8bc7bec0e65ce5f1aec8d112ec61720d8ea48035bf7d256874a8ed20158415cb2dc7f6b26e2cee4fb95ba2876c8c2fed5d1f79106bbdd49602818381eaa35f5731d55be152b27479ce4d99c5597a92f0dac34b05d4701357085da4ab4496f97e62f61caa341e7dadb8966189263d6c22e45eac3d17b8e24a86183938e2a53eec4450a63aaf4afa7715e7de247a020d6e84c0e4820d9a4415732c88e555cb98fa019357280647a5859dcf4edc4280c00b805ba472451e6d8c1c74aa94571d0517e51e40808fed35fcdcf40419b8d54a411aa179b69187f565d4dd7f5451a1adb472b0c9282973eaad9af80bf9ab69727861e221a8487b78e90c7132ef893e0b66b28684b894b573427ae734b322a5874da8bb7591caf2f71e4df107f63739591daf845447eadf487eb38a5fa99276f4d72720f2e9a667623a6d368746a53b3f214bc06061399b449e7e4777167d3c0eed1d9e9224a4f27c98c4132e211ece204016fb579fcb378f3d1f9874bc79d5f2c17e4b3fc1be39ef1c7f4a8a7cd5f601bf80205f5c3bac027846db96a461d7e403c1926e6ce5ddd774801aa33ce107c0f35ed91b5698c691b7cdcbcf4896c30897af6283d47e42386471ef28d2c74293688f39108b8fba9021b3eadcb91c086c0cc633cc7beba76bbf18208b9adefb3e4a8a401fdce2ed7f987c4af432d64f9f135c805059fe67973fc0fa1f99e4b304f032eac3d43fc3e5a3a87f18d6dbb38d97269e8ad73cf0c0fadfc5fe1fb6463a16cc4c3c3092fa09ce27ce81fbe1f6871431508638f4cad101f080211d69aaa2125424d7b1aafae9b77d448404471b0a0877963f84ab0067d8d947841920dce93672d8ccb0a0234247eddf1e45079a643b3a76f50f4c0392cad1b0faf566e6c24b9b02137f01ae803d02a590b787ffca26547043d1abd658764193903cc260046128c915c22d86c4a728a8d290662c386eb2d73f31832bd5ed99cbef804bdb960f4bdd1c2381d6f97671763d3dc05d1b76c11ea70ea15a5f1752371e4a359d4bc76368eecdd9c35de2ff3b77670288a26f64f36b4d92618d422c8cb3408c37f41a8a6a84b83dc394e55d1b6d8804ef93c3d9ca5cd43c8aa4b086dd9464607d72b4915000b60b665a90808ac085331993471be17be7302d9b8a68a14f6262658bd6d20c5ec07bd342c806e3fb8a20e166bda49585b532613b9d21b7cd349b32510f6b140ecd3718eed28645c82c80273cc54ee580615f1ecdcd0dd43ba0db1765718c50ea1933f942ef7d73c80ec4347dacfe8217c456e087f5dfa304a8ac039b8f05fbd373be5d0dc0ee604c9961ccb570686108485822e1ba4f2de5b77017c011304a1c5fe4334b9044f94f0a47fc14dd30bf77c8f2e037a3692fb53be6152a178350301af4c493c14828aacc2af535192530e3246eee52cbcafef6c26ec3f6d04c2de577ba1a6547a0afc2e941f8ad1890c4dc406cd7747f3f3450757c194a517f72cf2dc4f361c0528dc476d64a6e73b52c6c6ae763e48da724f7829efac12d57ae361c26b1acb0203e414a342bc42c44a5939483824980ffee97098d416661bc9a6a5625e69231bfe0dc710b0cd7f734b0d64172ad23366db07385289ababd33f806700294a39e2b00cb071e8cfa3fdeb9ef8e23b1bdeed00e39c55b560be712e935d56bfeb403f26e068af9b5e44f7bfbfec8fa6b4a6368846e59f4f984198aa14b5abc22ea321e195aa83c18afe9d2be695b049a5d0e2bc46994b2b8c29712fdf82a88a0102f606058b39e3b413970bcea94afffa6e94226b3c777c54f538be5a177c59eb92ad69bc9b247b015b24a76b3b97edcf5015796e76ea214a9305b6c43d5919321bb694e4f3fd2233cb5acce2aff700d935055ccada507680d81d6038137be4eca5e648601c746cc335c6383fad1caa1e17ec334c452a27dda29542196c1fe2a3c01d196121104929b8811a54926e3ef1761d4d732b8af5f587f825d78f22e14f6d084a62252cc6cb7e2855dbd6040905e17839f1584c1e7743e5355186fcd75758b4267d1587fd8b4a4606d736d7f85ab0479b2c939166a4ea76933c409b266803c43cbfaa01e946986606a3d40eefa144329d8605a7c4f4668a985e8483697fd44d0e012beb42a245eb302677b90fcf0f594efe94e015248aa06d66c3f1d40d9c520e482d9e637bc677980536acf9136096e047572d46a4ad851d3711e3ca20d2d7e0334f56fe8bf286d7b34b4b39c2692f1f19f16dc0685d3d1e19fc9b1cef3a2793bebbb5dc23b78f5769c9118f524d4bd43bbed3d32429464bc642ec1f3c3808fb03be5167f1701ecd4a8bc38bf9637a34ffbc9029ad49d344198c78130b074c51d13e1dcca481d0d0ec650d1eeba934c4ac2846bd5b460c673d8791f598cfabeed29191ab0ba247f7934c039dd91e33a7b1241b7248a72798070c541104083c47a761383d25938aabff15d903ae623a788d1c4cc7001b8e188ce55382aa9834ad05fbdc93b54881960127260f8d9b7ff9094e3a1b2c6d9fd9f47920657dc3f0f1cc06aaffc8c905d65ae363123984655ded204c5461400ef1480972d72df5006d4330954cde33cc4d31d9b78e815eac7106d461338dbdf4da29b341ecf30153538a9a47e9a838d05f6079dc28ad452fff763df1d7664046f9af1cc71f1a04ae4db76e604d261b2b822f2a48fc5c3dd354a3201b1fa718caae67d3e7ee9df45869b4098f12997d01709b8e963adf1bd634e2f1d73f48bbe8e6fcd19759d62189bf8a2e0d9a5ed0867bef30a2baf63a121dc5b464ac8e60240230126a57e7f8e4b08f0f3ad7cc2a1e3e335b4b45edcc3f7bd2ac7c9d34940f03fbb66ddeb87812fa6e172433b91d982f36fbab67835898efee4618bd9fc8faaa4e12dc0b605340453703f9497c34cd50d52e1a4c350b4ed8da2244f8788e31f8fac8cdcda6a90024d5e4e51c3f7f20c50c5b3730d0e40deceec3727c929e2547f8360168fa99b74ccd0b31507f81ac98aef437627238c3589c613bc0414ffefd4301029316b7fe649390751bfaeda7eaa5b9cbbe063e0bd0f9790bcf6d5d5b73485a75392f39fa588d835cb7c4e1a32c414086ba80ea222e2dd4cf2d00568dc1a54faec4a5b83c99c3d3e1b1805e1a3c2d0c46a76aa290f58c0184573a2b492a4b71377cb1b03ebbd928e1382047056ccc630c388704568dfe03455974936af4e90f41bb49348c0fc6f7d646c88decb869b9ac56e70793981c3a6b10a9b185cf2cb4efc12db09b880d4fdd2aa1450fea5505b08a6061b247952589433fc3c944534331ccfd460b1a38967ede63f72bfe159ce85728d958309da42cea3faa6795ee218d5c91c81126d32ab30b788548557a21eec45e7ae76a619481fd3e58bb444d2b59b5983734b64d578d9c2f24343bdb31f0d6555eaaeccccf0caef7130bf980e610661ff398a11417b8488e3190377fb5a98954356822e1c06eba0a88976fbd9df8b75d7eea17e89586679103056c223a572111b872471d75355c34a46f9ede3675d7649af6c8b8241580881286f218a428d9a78332c86487030541837a76aea1c6520b7e224254c0a9b0dc27860503d4069878c96b7ee1e308ce1340b17e2ca14f12d26269df47223d395ce95ec1554fdae37e58b59f0ac1745551f299243993308683b1cb0e85ffe42692225f33df648ea55a24b6ce80f587ad671d5c0171701ae97eb7312c68c3d92fca84d58c86556ae7277cb252b8693dbddf30a5aa0769424f28e0c5eb4b4ee6684d80f3247155aba5d3ccd667d7c78d65a92a429a6008a585c83d68ddc3f839b0efee64fb4659499e581dbf007d9b0e95c958b4f80ff7e447963f203b9cdab70677015999d12d9ceecb2430b686a4c59066a95779591595ca7ee86430bcf8bf7c78a4578c6b77884e03b174f04c0af9eb691ccd4988f8f3ad41228d8ad161c68b643512c72420cc988198e62587282a937969c34e80a11070d4d8dc90931524b01f006124d0b3434352627c446b01641b5361f0e5c55d26c104998b8eb608342935b36b0215c208c61624dcb7bb8f88ccbadf331a4cb1649ce927ce70e43a409b5ce36bd0b1384fb777a51b5b714e846e06a1841d3998721662e04f386802ce8b5b6cd8c033c9131f37cbf0c0148d39c21ae210358d502f768ce10db48ab9f4bfdd2ca9802a99a83f7d047a8e36433434f6e903772b46eb5cd75f3c41ad2cd520ad859ef3981056b0e7933348a4f3bd0a7ea55d24d37a4b5f8a367e23cd302fc6ce3d4f22fc838cadef7e2704320cb7a2741c9f379f3da9f955db6d3de6c2021b58b48e7ae23d6d64cf9125ae3c52063c583462b7cb9617cc9b8477e61fbfc731d68d0ec1b7dddfcad0c6a89f8fc78852e9ed7812bc1e33172bea25a0439e37b96b155d57fdccf86a3dd813fadf1e044682fb83260be0a92c6e020c3bd8ec0195004e202a5bccb4a24d38234fbb2a5ef2ce79374124a4c84210df0c875e44e3c1d88d95c42668e2f36db0f632c0ec4abee50cdf5440f66223ad8744b95283a7dae7b73b20eb158740de1f0e2a4a3050c48cd406e4ed847036b4ad12d28448ee327604a5821aef10837d2337e6b539486af1da8845a94fa93dc1858cd00ab931f1bebb326459d2cdea40c78b35facdce98b7c1001079b03310e5f4fb7874dbfb40c6f077836702061d0e525e3a7e910b88c848ae71e07d243bfda9a02b5daabd7687256c6774135d16705c489fdeb38d57f4fec5ac6aba23c8b8c0f9ec933a577c4f3eda66a8f25465b6f2416900fe75879f01d10dcb8bc12b5676ee714841c6e5499a63959a93da794794c790a405f117a0cead901531f95d404c195a60821c2bef01c98fe4d1a92c7402f13b1ca042760b31edfb194d503ee2b9e23701db3e3f2d21ef391e9fd7d557ac718dbd96a6dd4fcb3ccbed7376bae3e10f6295eec74a5624f256c321f7e440579d42661a55e0fd2973eb570f2e73ed3dbe4987508a1e9873432b130bed0ea907882c15b492a4ebbe7a11aed3136b171213514537d27113fc068318cc37c7e6b1dae531c542b45086283b101abaf9757e8799ad0fd665175352c7b2dd1a891e8ba1fe41eb51b452085ba453fd1d4241d10a689b4e93e7bcd4abee8a188d0ced15d13aa92dd98b369515d74e7cc18f2de6a4221afbc3b850ebb51d2a6e82c7d79492729c3d02736f1936632bb513c71a0b1fa33a855fa8f37442b918a6064313c88d37812c7a017a45525a40b4a8dad04ec691f87094b9bebd94257447152663b6312a6fb6f1c37d4ae612e7e3b9692132d1279a179d938ac71e29b4e4e765fc24d37437b35c684297caa58cb1f7d9903c135c34e8f6f4a683a27fa8963f51cd52ca602177b7ab2d53c856cc4c89345974cf5c31b1aa5a73119c5acf321cf672d4dc535d64d4ed930dd0ef82e0dd3c6730b09f53422440019c121643cd2ff620048bde19b192ebd5fb3b1319c10528a2db97abc2990332405712c113e2e054462f2d30257f8780b7f6b77234f8c96d26a82bdce01c4439120922a0cc4bd013a00513460500eba9236e533ba503b8f850eb9a8c0e85092817fe30d267e43b009e67dd939f26b927714a42aa23a7883f34300a2a74728c47f85020062d86100a24b25cd3a56c27b9e0d2dbc4f59e1688335320671f4a1d134494dcdda264422bb376dda010f0338032103426eee16838bbbc1f0e26e3ebcbbf9c0b85b0f8e2143c6de27d7de3c2eec10e6ec0f6a6dcf90110386b7dfc5c268736163214ba265419b0adaa68296012540fabbdeb9402e258f5faf750e7177a7faa57eea54ff4b4aada5544e199fff0e1052279deeee320698310e6fe89c47f49cee8ec2819a43e67477f714f43ccd40fbe47bcb47f882ca083b4af59b4c25134cf7dde91d81cb9572aaf8ac781d877380d8451a2c960d78a4690af0bdfe2666e35727041c20e240d85568044492f9a6016422ad5811971381ab4cc6ab380e55bc4a0b5e052b7940bc0867cc59ad0b26a5945886651996816c6af7c8eb51c5c060281ca85b8252f0f4e8f9817d0eccf52fa1f7e38b49393e85efb5e7883a88a7478f1ffd70170f76bde32e974f2b5dad44b556922d11bef096394daf4d2ee5a4a59b4d69257bad7fca51f141edb158311ccc090301aeac8e5626871222f0000ce28bd0e8ba3457e6c25c5d0e78a36373c1bfb22dab21082c1800b8c2501f2693033ef9e29763eb8159e05a47728e38236a54c926c13b3049457418b3162949905b7671898139f346a662318717ee3accc126ce74f22c66c1150b08bba90566c5357202e2214665714b496a77c1187d2c16eb8bae5f0100aaa21e3222e508a34ffba40c0bde44bde960695fe9ba7c1a0b97d44db96898ad19a5a59b9d98260b42526ecfd361b1e47c68133bfb39376b15cd69fcf9494ae93fa5ff5252fa534a4a678437ffd75fca3967b516bb16c3b010b0d26b4dbb71de48e5bc917eb2542add38e79b283e5d7c3a9d280de1a709021910a5ef39704281593ae5676e4bf766a51bdf07164d8bf046caa83dc36c35c1b7a5a9b5ecfabbd63cc217b22480f999206127ef2d9970afffcb78cc208e9c262d6bf92c8e450be1573a2795f521b41062762535b6aff2ec424d06adda8b65da2d59fb536a31e1d3966576aaa6dba9aaaa7e5c1240d5af4d251214f04d9b51d7d402512d25bcc435e79cf5e79cf377e4686e2bbd10e6e47ca6dd0ec20aa953a85d4a2985536b81f0664ed903bacb3993f090464077fcc45d3aaa34028632f24629b10bbbe650b17be23dfe87c4bb63d610425ad582b8f374a7315723e2bc8d872e75f42378843178a482aec5c39f06e674673f0c6f525788e8665a50eb197b318a4b71d002fa7fc2429f92523a2391546b05a7715a2f2dd46db103efa4f402a456d182f0218bc562592ab3c34d564c8128269978e0af502d1a425888d32f7405070ebf414f88ab1c5aea87f2e5f360c99bfdeb83c21af439b5b04de919eb028c3dc5693ab885c2333ca8f8ac704c2d785732a66caa646a92fa210bc99f9f9f189d682da8edd5186b416daff78039157a2bc338f99446a7f17962945c6dc199163cf00eea7d2e6d416d318a4bd5259268d55363d82cfa7eea4f11fd792b3f23d5e4a94ca13586d65de779ac1388b60d60f75a509b8a7ef5a205b596b968b1b0c779bfba69493932daa2e0bb41397dd08248d0833729277ee5410b6a2da33ad88ab0ebed89cbcb85bf9269792f5c0cc61ca75944dcaf740bea6c1b8259b1c10a0d5a509f84b0b8f0572d5a50a8850697450b6aea04fe0aa6b535c1ae7f80bf72693179f9d0030f9b06b0eb6d079a9616ebf57b36b62535b7253d08f8abdb824a6c2b890e4131ee0624e36e48f60c390307fe0aa7155bd1f384c05fe1b4f6ff0ae714018d535605d41c5c514d656cb4cd17b58d8d9c13cace1dc2b21482fca7bbc298348543a584128ea0d11775d423c01c78e3b4434656943ebd5f951023152ab42442e92b8109a4121dbe9cfff8a224b452a7c4172b1cb9b1b3da1f4218527bab521d93d8db16112bfdb4786bccfce46767d77596561a033f5b353be5476b6dad0eb5967a65efbde31234aa16489c86c628c3b2ec6344da7bfb6c1f3697d2d9a6a16d62b2b3d0de7befedb3b7cff6210bf964a1bd774b113e5a9a9864a1bdbdbd37855f877dac117f6b4e1979f0d93b0bed22945cf161fbec0fb0a0342a3fc1af858e3c02dc881269bcf1dee3e738ef3b81084f9da483cb483df1ed0876bde5807d4fe8c150a954aa9307ad188174744b251e2447f2bccf48ec4c431b0e745753946a3786697bc3c076c366642b825d6f369836cea9a4996dbb0076bdd580da2a546c179a8d88cb7ed986e00d350703b3fdb0d8a9219b0bbb8630e7e5db846c4136209b05b0ebad023377a3a183bb51c083bbcdf0c1dd7e745bb5c9a0da34f8a509a6a9b9db046cf6cd7f52ca1265a566a078ef710f8d4a767389bd79e8bdf7de1c0f9dbd4d4df6de7b6f081f3e34e1bd378fae9350ba7cffffa8d3e9743a4d749e05d2d6aa594c9bb14520474b18d78f5352873106315299b2547e7d392746abed40f094ba37c944f2105a9d52af16cb342de338adbbce3342947fecc6b06c6baa18adb32b0bc94132906c81d3e6c49651b4565a2d86d94a97e40a60d7990697174a2915e285a3b5d26a6da5441c0ca5ee43302b287517b28205a5ee423205388cf30cf94796214f20c79061c83eb24feec912a0d935b4565a2d86d94a6df20b2ef60b985dc811c810c80fc82dec19940a998143a9101c4add656471c49cf325a5d09473e22824f4f9a2f445ebcb3660fef74a68562d9665daa94318b3622eebfafa4734aa6ebb0bbd2a7d514aab8b524a299d74be94734af95389bdf7de41ff441af4bd5d2e17678b88523adb24b44dafbdf7dedb676f9f5db4f76ed1235f43388410bae9b577a494521fd4b36a5d167f2b429968438b9e57d193cdda3f1de6d0222279030a7eec73b9a0b88ed4ea1e941d9059f05add629976af9661b60a651e745713946a3786695be615f28eac42d69179b0546babc5b20cb335690632a8b5d627aab5d56298ad31a8d57d8a5add7f6a75ff8141adae17b8408a285ac0822c50ace089adba42ac247f617107e244135754c044adee3fb5baffb87badee3f33b220728f52470d8a931f1ff0ff553ec771f752090286c59c4d0d0d4ff429876a1228d8a8c1711cc781a0d3e999ce5e8ce25243860c59a143c723d11e12684cfe4acc81230150f2f60f1fc6d7ff4749c4fdfd6194299bb2291f175fec92563ba47271f620a58c28893a92b152edf127ad6f97f88c27295042ce181e4ba3a07a3a8ad6dc094c5002128cc084b54ed2f8b47560450428159dd64bb40a974e6bddb9bce8176e03180d42c0610c820f2481c4114678a00822aa74aa4e6bad597eadedc04b388410484100114346a7b54bcbd89dd62ebdadb59d8649188b08d8b75b3f93b249d5d4a066f42549a839258ecda494d182ba86e6b446757175e306bc8214a36d01639665091055f103f18739ffa3224c0e155fca4fa251781d8c56ac4ea3775d8761b0a9a1a1f40330763b9ecd4c1767708b079a8310caa8a2b1b932e2cee624f00ec63b508acf351733fa55a8f90215550e2ff45ee5ac57d18831a6a65f110da0a0a6e38033920929bf4e4969cf005e58c10f113604e9a961c914342821229214c3103d51825c5682286284039126a41220667062c90a4056f8d971c40413240e65f16b7b35acd6d65a5b83d1642acd7ad6c3af97a16e26c5e1ab2d6b5b8bafe56c8abb4260d7f85aedd94d0675e2c0a2ba196df1cba22ccae26b2dbed67918d003bf6c8ab329ceb31dbef8e2fb9a7e451e9b78c83a9d6a16c745427b19c2319a9c6097fe9ac2e8dac7581534f9ffdaa30dc18e7d19d2f0e647100e1aca5170d410056d460b6f601529a5943213815d4be952a696c0ae5faabed7f15ffe4329a59405f85e26480e42c6e7528088f1b9141c2573a04bc1f8bccf8f607cbcf84c462e3e53167cf3f90dd86d6c0d4693a9b4ee73a32746ac0f3e97f232946ac283969b7aa20353163c8339385924d8f50c920c48ca7c3ec52289f95c6bf9c2c0a03fa7b2810a0d5c5a7caeb5979bfa3c0b660173392059905df16541302c52d79405bfb470f95c8a8acfb5b6c13565c1a80fc68735f2f2e74ef2146e9339713073750fd8731cde18993e9732835bbed2e7536c775f86a4f6b9d654d79405679fc53ed75acdb5b9d7d69b6b91e40dc0bcf0d1cfb5e6e26629d8756a7e2623ecf2f323ec9a03928db0eb871163f8878db0eb6bca82e1364920499229a378f3290d92ee45341eb5817af505cbf3bcea5917dbab487828ec61cccb6e309b699de62457a45b0079aacf56cdf72d79557cb5bd6b694a9e951dacf7c269a4a6782ebe9777e3d9e86cd5f7321d12d55753c31b81068da88a3a0ad18042341e12c541d3b2108e5a8b317efcfff71de932df335343d4a038c963ca297980412de66c6a687ea83409146cd4e0388ee340d0e934d04c672f4671a9154544a921e6cbe572f108e5ed15047a0cfc8a5e3b4439d3751d9512a6904043a7867c0571082174ff970b175fec83568bb92c17499833c622454f8a88a4cb05c50514949de42642f907289a9399497e651f720f99072c4959037987276230c5cf4f5e52ab2b2bc949b20e39280365243903f948ce21e390f4efaf8c017720f9866c2417c936e40bfcfcd45a7f3666e25fce5a4998e99062529462a8044e5d97ae0620000403180000000c0685033994635918e665031400153a8c30646a3e14892391501c875114454114c4300c823008003114a390638c497802758a43b48dd94e5378778aca42820ca2ea0fba367ad68f69e4f86dd3ce84dcdb49ee8f071994eb2aae1f8dc99f4aff66fb79e8fc467d4d35eb5950a074877e6e25ddbf80d46ef53797feeac519e945be5dd790ae5a17b7cb9a4a9c9654b97b6ca16b6c5ee0a1ab2f516f13a4e1c8923c34256c85c801156e6a500a5618765b33a070ff78fcbd09d6af9bd5618d4fb04851ada8eb1867242722690ff74091835d266b6c84b046a8849d0f82a6f5af776ec2c9807443540cfb93b9e4baffe460e10324603871b4c8800528907b099bc506c08e676d32907775334244b778498b0fab56b8e9f5b5dda1bd91ffe58d874d8c0b2a8b793ab6d852fb2c7d9b69affcd033e3bd9bef63c87b944e42f9ebb7a3d4eed6b7e3d0f77126fee465748a08562a92394f6043b34b23dcb9146b5be687a2eea16c2708c0c0c66ca23e2bc471e9d0d0ebdd6e35ae15f7faf488cb5ebc234de1cb8da620131140e9fcdd182576b61e73bc2f503efafccf40e8cff4730bd1195d2f4a42f3798be613f607a783320e2d96e23635d081cdf66ec98cf108c7e01fc2e7415240611ea0da62e3894ff8252d53310252094cc662e4df9d17594a412478899689224a01c0fb9cd923477f5c8096943673015ef623f3cf92ab3136460d17c4b339c1c95517c196129d8bf503ae42d0d5bf1938d90d2c7caff541d418451fe03a83a86a1591d0ad26a9a15a8cea242431afb4b07b5d1005d3690be68834a55c813ca2206f210831a4dafd27262bc558884bc2e4e5b21ece2bf272bdbab64b13aa0856dfcc1b5257219ba97212190d744e64e43582b23a231e730413b71138d99e49c579ba44632091bad3a44769034046148a10e01ff12130742f6b666cdc50f73feadd344e2c8d43ed04aa069b6c0e177db7f1822bc769b51c234c2f463faf1a33f48fb3bd83cc4df4b123e479bb0847f324b7ed89d56f8928bdd1108e8d435f6445678440af5b796a541267edcc802d30de45c71a24d6c65610f3718a1c05c1c8ada3104055595c82c6e20a2008cfdb107503ca417c07685c688cb3d18f78a202be80c88c36a035b3928feac0c9246e732c65e6d1b27236b1e0d16498d8f184473ef181986dba834043e86871f1e822b8b7916e84eb8cf5151eced2b0a961bfd182ac48cde330581d73a34982852a21d156b4877d53043f76a51b09ed7ee3f9b9920c0421d37cf06139c5a6ba2611fbb92bb35bf16a394dcc7433c8276dd6c0f1116f340f695e222d682da35f1327d65bc042f63a2ec051a512a3c50452308cd5724c7cbc5e860fe467ebe373a0d0511bed67f43f74faf1e4ab929574e8b7741fde34637df73ba5263a251118e579b983f5ce9e20a8c62799bf3d85a0ba6e3ca56e2aff99ac2a36df8ddc7c95a106a2e1958c04a96480a9b3e1b5bac25a3c3239c823a1c41b89333d12e5614baa8eb4c98594aaa98e4be20b6e64a7a636a05e8ea434caa58ff6b49f9ada865b01b7b2c76b90880aff1d57fa0e8c3fde2b88446e2d2c405b81007f2741daeccbe72291de8fc4d9768f867e4676de6dbc60222cc906dd8d839db3db50b1c09efa2480bbb4a9660853451e7c34d966b64ac293bdf406e94d7eb7649ad107d0e2515ba75d104cae39bdfaebb80fee582ff86a5b6f9120c906f83e23a896e36743a61a04c08e9015e467e8d7a53161d856d7512464080f7e0213851dd02b6a7b7955e3cb9fc31d7aa415256cbc7e8732cb00992109d45998f6a8cf77ee537fdb040ab1eea08b54f2f29e13eb73a1eaecae0f211d3053870d90c074e7ea5ae3b90703c23f836d0e7b07ab8271229e03ec4592d06d5fb94d3720bbd88a7c23c3d0fdf53f3542782ff63f37e2369b36d0f49c9f2037155303384fd8b3ea2860159d393601a4722b6639d83700763a6af5f338aa9fbadefa78b113bb1fc9dac65d9dede309d026271855409176e8fa4fccf13358cb2aaf8f24c37fd1d4ee839176ce1c00b866040fbda5765d6d9596b868d099d340a2375fb1e7e16b431b1bb6d88dc26d54541108fa84f86356bf37a76663442a0e00c7edf734b18474376e30441f4be2ad7750c4c7bb1395ed7f455692ad1085b300d2083e9020fe1b7cecd558fd7ab162bb1968b36c14b4de6784fd882b80bf3ce3b0930ae3d883893291dcdb7c332d5d02c1f96fb1c129a373044408eb7a0f852f3014e734ad0f2a4e888c69441118c68698b3f0e7b5013b6a0f706b7d3b755a7e24a3ec4ac7cede5874ef0986227bfecc29302c8c182c34d3c5a92ac0509e4388f1128942339718ab984a5b0d29249af712a1a9ac9db771565743f61530e17935443c7b5a23b146c5903fc2cbd9da58bd56702bb72c2030eca71f23fc276b0fdd8ebe2dc28d95026bb422277b85c57754541ad71c963781cdfd0dd3546fec889b6d325e7863ac977389e96346ad1d4e1a33900d029aad3bf23ce721a9a92d744ba960dc3aad3d2b81456933f17e37ab67bf3837b39538f32f2eb81e18ab7f7a9565b35388c52787e640610e6b78fc1713cad853acc6b31551ae61584c069310a7b597537549e63aed6af7951b8188cdc690bfd76ffb8e31fd3c76be4ac6d0cf3c6c05644130cecffa371646b20be091c8e21effda467f12d82e80cb800a5704665f32233f8e83d9c69432e7764e30969804d223e431cc7ee739dce5bb8f1a8164f1d6018b766229336ad3c0d6fba227d73b989f1a98e022d732e145774f85a8fa22776364b2297c157b7f20174f77a2948c907bf5c2bb28a05414402e848d49efbb9343aaf7e01b8062f903f36e1072bf7965177df78d5529bc2e19f88e950db8eaba01f4ef2bf052f90ec3ca4c43f54110fa7f66e4e0ad47502e18ea46426ba9eff89d1346e460381177fd0e0a17dff6ddafd0bfd7ef260f87d2a8dfcb1b4bb0a782713422633f21bc796b56c7ec7b1b599975e3b1e9b1f60368ecbf45f0d96505b4f27ff7ef34f1148f905e4d2542dfcd95ed7c256effb3bbd704bc11a3fb2d0936bba990ce7997d1bc4670ed4ae9be452e661aef5f2678a7d0ea0f2541f649060ad98478e5fd4716ec3f390d06efadeb6b2176bbdddce59da22dc2b78aed64eefe9474573ce2682f38749dd79e9aab8b7d79839bde1181f359d77516849ec01a9c9cf3056f1dbc35ff82565154f98ca9cd8566e71b302bbf88f02a3d3f5eec49e1edd20199abe137eb9b1b480fbfee2d54651b5e9b9a0fc3fb8636cef04bfb8f798a373cb6dd0fb0881d88623a8b03f2bc65423cc984d0f0bfc3a1a9fff92eb4b08e783a1753f7cc4cf18715367ce1653113af773c86ff86ebfee34657d3188b07891cd6e04a0391163a411383b85b90c0dbdae7c2ee866b12712644039fb228eb6a408171c44cd2c28c850aa2df42d9b02847e4de7a48f45844c4a9dad1c3af10bf66a0d4d132182bc524b012b3c0a90cf0a27822d17f8ee622240935097e83b86eccc6846b5d7020412178de28171321b2fbecb636ba61acd360c390501e3c36b5fcdd32bf6512fdb39100e042bdc1737d9b127e107489c7874f0d627f0e411a5486ed8822dc96413c78a62c0ffa39e895ad161ffee0a5bb26c4845b487d01f92608a31fe8c14e59b38108ef7d479ff77bad7bd06360b25e8921c068784fd098d897de9a0085c7e58f629426227c0c5206debabae78bee724f4365bc2708fd0f2fe677a2b8876789dee8c9530138860a06c070c10102d4325c8000dfdb4e04fdd8037283cf67eb81850750074501522dd561ae364ab6ed40e67945f29c61a0563dc0db67a7dcf25729f82ca50639a590f4381cda01420922b1ab2505a0bf31613feac0493f3f87e27de0030e1098e57bca03900aefd06a6d396e5206b264b26f23767d2b2dcc4605afc6f347d40426268cc472cbbaad96b7f01ed3f941400e996c173486334c46d79a7b680ecd68a9f0b9611d9b85fff86b231d20a07f8b66608bbf63b3662b672b233790af42c4e1c3c87d35fecc1ef66c488b9c037803d5f374392169a29dae896cf6255ccbbe90c04826cfbd5ff5f65ce22ec650b563ea6569d3cc78f9b2b2976f2de56c9dc70378e20523441ab89f34a0acfbc9046f209149fcb4e05f64e5ae03bd5a61765ea66bde152c6c20d60068d013fdc51cf48e7dc8928bd25068b5cbcd098b50fdacaf3000762aee9a5982da08f7069528bba0772998f7c39a9420f04517c4856fc9067aaf17c0c334416a92d271cee1fbc586d2e130b81c2805adab0b7a408c38288c6c1155dab278d5bf3567933a06a177623901e130d905435a93b5bf5068f0cad6cc1ba8ea27c8d3039b5b7b9a1a7caf6dcf61e973f2690a9b4afca1321408309788f49f42dd23cf9b6ddc19f66a481e5d3a20f5a9d4b404651ba302845b1e69c23c96327237a7aad92ac0419655aa09f919495370e2282d15eba2820dd275b5c19cc5b19c3d81d95e57899465ba84a2878411864b047a083861b86558858413865acd864f3f375a2026497e6cc7b440d9448e10820a18ab2076a406c94d7cbd9c308ce523a871c1124bcb72d412e8adac494cd23403fbb66049c365e8bc63016abaf7f5d2019a8bfb5b92028f3e9272361cec335910106d5845c223ed839609e052a9049cb3878bc17bebda95b7e11f693d11c2f02e010a1741eac4f4ceeeaddd11ffcc41c21ee2d82056107557940023cb37a7412d816f12c5ff2d32349a4d4b39b805961246dfb4d4d8b87ced5b6dcbbd8174a213718fb11722c818123b07e81a65e15b87ffbca13466150872e2f70e98395f957f75f88ad9e59cf956fa76837012f76ad11b6071525401295fdf0875eb6eb3585117e215ae15f0b55161b624fd217453b8d6040bb94ba91b13d073070ffbedbfaafec569d2dcb1b9d96d12f844b6b1c3da6e9428a0172fb13ef15863b933c963a3f102507bb34e0b2a85b58860bb16fd3b4c944cf8563e2714e6b292a10cd9822131d20a9e2cfc4e4d7e43e591b32c914c429208c95343d2e37f7bc3104e80a4f5fb70a9e666f0f3ac0eb2eb7d74123a43c636814629e51fd703ec65b2f300cc7b6181bec799a227957c286bfa4653f747842c5bfb09f963372bd4bfb462026dad48076bdbac2491e8aef5fe97f0576b2043084e7be49885c2f1d5b5f43993a8519ec87d0350ea05d0d927a8c1261386084ea24e73e57291e5e720a585d23cf656767f597c448f294e70e43b01defe179d8bcccb34411486c6942b4584173a448cd03296b2a43cf2ebf2396ac58491a8562074105f960dced8ddb33d27b27607b17a07a79a4b1398ab8c296bc2b4144904e1f1ad4e71261b2bbb7cb6bbd615d5a1b71d07d6f16753ecd09ef38935370571d000e3eb482b7334843cf562cc9d8c86350920dc2db91a0d563e746766e213c6fdbc75a4087488d47377d7ba069e0a60c9387b5a1ebfe0bf4b8c2c48d216f3b4357beb22166e9e20a2d862ed4f216c9c5500778372eeee6b30746c729fbb18c0bd878553c2ffd8aa9e6496bb844e129f025fc0bbc10381e3e152085eb185d4f758aca9aa972933d5703fcef8c7c762b8cf31f85d930e5ef81bffc2bd58fa8800003ba5ca6b1f8f45eafabdd1f30cd60a0ea65f90820e07128f0c5df242ef8cccd77ff738356c7324721884daa0ce662943e870a3cf0f2d1268ff70afa3b98a1de19c57144b437f4e26591fdac6097a51147a962c58b06429b293870b3e0b42e1151c926cb28c794eb1dec25c59d751d21209ec2895cc4e98e4006b388621db14ce12a695495d8e768f00494c4037603c38702af1bc42011be3c45060a190f30a0d4cd88e5828208ec62df2702b37c97815d836fc74c3c808c4a9e7769fbf52e4fb99264fe04bc793ac644da67b579f122fee1e950fbf79b4f76a1ddabe4ece4299e44796874b5bcccb828a556d8a60a4550b02e218703fdc6884d2a7f5d1fc0f67d9b5647756ba3367f3ae4daf105003a187c90d49e40c85e445b711c0ab799f79729f2fb932362224644be25dd2b6780bd399638fd9678bd86028c000a6c56686ce9c9e2ac069c03346f398d18c1a68c292006e875ce658a1379df972f4c4d3cd6a1a83a65f92369757b6f0d0d948607f5b4f14028a8a88e5637549c6bf5db8f8b6bfc042015c83c53909ae0f51f0efdb6187afe845da9cdb2f5da4b7aceefd71b5582c88f53a4d4218b13337d676a7f9eb493204c03bd0562d2b0abce4808aaccf050ecbddea332d93d5952d2b72d4fa8e06c1696f464b62857d373c1814651385d9e92de4d68e138b5627a8cc6af286ac16754d5b220cca1b9a34db2f4573fd2d282c7033b89e50df2924ab3b05f57eab3251f25446dfd9d12a5be95ac9461f7a4e9f9710dd6545757d2c618e117e97a538ad39e95cdb780a9dc11f3aaee5a2d5eefc770c05da6e151b2ba36edcb6c7759ff73b24b760e1dad3ce030534ab92d1cf35d23d2be5b0646ac2ac8f9c47d69973a186e641b65dcfb032f8ca7db5d5a92d02d660eb139397a8b2c95a421f073cd69410888b7081ad76eefe4c4d79c555caa9869a3e83ee7518d80dd92579c670d8d6f0fa197b85e87607a86a054907c6791ca88f7143670cdfec15514fd1f9a37e31be2e7cb5977bec9be9bd032868f5c2b23b81178e34a724abba7c56600b872d23748216b538a43e4590c1122f8a53053b74059c097dbeff6caade54f212b9cde9c41c9d51bd58b2946e42cc834bf24297cf857732d612cbac258087dbdc04aa88df7cbd7f5818e875e25c4013e1d81d16f77073e30c5dd5d085bc714785af80a753240b77b4d42647afe9d357b110d8cde6434b89e3bf3c811fa804bc4e0753b5f6c8da3b5b2b23043a7199f9c932c3b0cb88d7050b8065948486e65dcf35d8a6f3eb3bec6db45962d3fdcb9fb45970aa7ab68c40e8f8539debef7a03210e8428b8401d69b0758d67f5f5b99ee02667318fe808fc62a1e0fd94fbff0f7f23ccb359cc62aafeced29ef76a04837f96913ec5a3a05686b42b2b4d01d4e8037021bb86a12d68881463c4d37dec4d81bccfd828d89eee3b1ec15756243629a9c500c17b0b1f0adef836fbf92708a0cf669df7027fda7030d3f536f740233101db8148d30a9e8a6e4db3905306d8de8bf2f2aa0a89ed17af38d14f56a33f8e426e0f49618c53e24d0596c3e4ab0423a007f667971a7b93262cb472e9bbd4907224b95a4c64908e083630bfdf282d2b52781b47487b7c41f584332536d04c0ba17eb64727fde9a72cc0384698387680133d9b0ca8b4b0bc2bba48c7eebaf1de7e7ecd820fccc169d7807f4fbfd715b0b113e97c9eb107ae27b74a5e61f7c29fa592625d5c1818ae07d28f0ded1a342baafeaec242595bcd9074b64e3e64e3f4174b9dadaba4f9ae9e2f4b39c091b1f4e157e7dea2c6b6133dd3a06a47254af7f04bc489cb8f28ed6105f97cd591d5c3c568f90f07f5d99201f60adc7abe33f87dedfa6436389557b02ef41d8ac53334308a0d8b079949b9041e541baba0021103626e273417b9375c7c1cb27b621fbabe8019ae2ea947ecbb30bb4a1a1d7c452c08618c303a7cf7682a5e42624a2856fa7de27b8b9aa68474d262b731ee41c7d49a10a259880ed884359486d7e0058940e85ecaf2c46bc0ead66c8dc4b64f374f2b26524ca4b58172fb6dd31d8d14aaf17fa84b23dd3d5dd070e1c7983687dd9196ca175f482fb5850459caf666b19600137bd3c28a8aa1440be1dac515192df93889a66b9da75b07ca9850310494c5bed1779f1f7dd3c70b6f71dd3d40a50fa9e332dba05a0aa27cff975a584c83b49e4ff2bbe6e6ea7d5632e0bca6947f0912a27700dbba80e571cd87adacebafa1e57abd548fd7651d1beec4f19486f2fc83b3c78f4222aa9e7d2a5c146cae8c42ef829a355f8f9745e6279e2832438c19ee9821b6a65f4a405782fe977f4db22e80f540a83ed76faccd585edc7a2376bca38e32a9335952576ac35770e1e63684df61e10fcefc2384f8f67555f6a14b0792106f14846a1b39eb1a55cb3d865ce6baed952cadad8152c3da417e0768449346ac7e573c90c59cced6a7099a912af86ff52c117780c83f4378ed1b31511561a3169edbf674e6e68065020fc0ad3b82f3fe60a89f292df7a94b0d355401d1f33ac884318d7305a183ac6dc030bbde98f4453b60150f4fbd9e9c0276a0054ebc51e72c253df8d16e624d2314329c180f49088e1d0cdcdb8a19ec03425c1911f6b58cbbb79b506da021ffed26c2a133e88dbba9b1c7df0c11eca12a093e55c5fc29224839ab9391c8cb8777b3bcc74b171e71831f4830f754de73453f418ed5d0bcc662c2df9be0c1a6b5728f87ceead855c6bd19c67df0ff43a711f128e2b03232e79463f24331d142c75b2eee38398dc2fb276c8db56a7a6b954b74a5da72cb655c699d7f875d70f567abc06be61885bbab464bb927a1d659b96f4a14dcdd8e396a83832d296e8c0a2cabdb4a63e32ae6908b32a6dabe72cca01bcd5729eef9d44787cef84479bb34c473c7f7ce563ab94c5b4054acc3066eefb4187fb206cdab9d3fdc030483887eff176951cb6c4c618f91d47f66a3d1b46214534ef7a61cf0c25b982cb742c649ea36e972de884ab3d8396477598e0b8fa0128f5fc8b817289f2f4079d1e7bef1c28ad5889638325dad86485b230e8ea777d4f5e78754a12ac39c3a86662897156046e2ba76eb82635fd415f7a64c993202d1ac433eb16c0675574cc39a07c931ecd5f2485230401ef9ac6cab44dc9b6519cf01b27663ce51b0c1b05eb05a0b1878509c838ba97d05d93e078583a2dac44b436da0e836c87856ffe69c9009827633fd7be33c8faae83c4a9b10257622f78ca4cdce751366a2881d25ef1dee6f3c389edad6805e768ee282d2b7235d432279013fa6b8aa536881da59bdcafb84b58f7547dce4616f332dac389bf389e1d5d908d1e9aef02eda3a66689f9c4e981d8259158bd856837ac5d8f5a8ee2541718d4c880c1e59365661dc7855935032cf1edafdb5cecf7657c355ee63a02cfe1d920c768e28e664924ab6b508d486dfe4d1fb98e5c506bc31d5a7ae6de0c31b7022da11e496db6e583d348cfb7ffa2e0752a356f850ec34a69615af3c0384bdde0dbe0f46f4ac02e8a8b4527591e4053917fa027bc3935c1f24ce30189c1144c5d28449cb008addc53f887ced0795021c6868fdbf43ff8fa1c9bd6510f4b03396ec265fd07ab35f0abc7a79e4dac4f34f2b470047ab7f1e3204ac1cbedd1a89f0b099bb66276da468fb2291d94e062e802a3abc0a81ddf0f4d09e6617fe741aa00d9afc2bb6d19c8d0fc47075c27e520ab70e10a4b0508f0fff706de20622bddef6ee23e2360206f31af657d056fc5a977e17c94513c7288c3217af8a002eba571751ea20f6547076fbf6e8677355d9eb8903f8fdd164a585a1007c43fafa5d08a6d501c27353c821c584125df70fd80e979940761215f2ebd106bc67ce27626213258e50e4a71a550318e5eb0d78276e83c74db070c403b1960eb2c7273deddc2663e83272be51498201119b4ce16fa16ac3086b5dc592ab91aee6a2322a84a0c418136699b9dd5ef71bef32da475443c490091c409a941dbca0239ba47c5e472b0371a64652899e8b8d885cbad8ff32eb15af2ee3f2a84fdec64c8c148579b17fa67708338343316c42602f25a1318c00126197b15d14a09d76e95cd2d960d7c44edc414c713db3682eed0158b6de5f8856429f10d09818910ae531dce753283a9b59140d1af865a15b32038998426d95bb15b1a088bbf9ad859455ba89c5be31a06565dff388cb5d234aa91faa0661ee3511d4eaeeee920809abee7b5b9dfb065535d3dc3db341ae52dcae99e114ef9ff62b15f11fffcae6b30c8da2985fe3935efe8eb43c473e7d660b04e8b05e38cb30e274dd656b7221af5402c809fc2654fff9ca713cb1b1210305dc7dd6b96dab3063da2a5178ec869691084b6bc89fff26de38f7f283baf7f39e4e692296de6951793a2fdfd241a162a2b4ef6c5f598344d475818b1525bfa1ea08c1ebfbe5e362a4c5ef38045787844f2e7390acc07ec32612a9a46849960f69c5f18964ab069af630f3d3687c14f341789fe5186a9609a8ec2d7d75faf732c098ec20e3829793e3834bb834954fcac026f0357baa97d0d738920053898a80a8aced9e64fd0c4db817f69c217760a480b154f8ae963a1cf3c86d09fb8332af3a15361a0f40fca69ab404e8336034780d0bca179fd61c11671539dfa110627bb43dc320acdb014b8dfada5361dea98408926114bd1bf05b1fdb53a2a8b1f3b5b68f504bc957c0626e52fdbb65f6cbb286a0911498d9eb337b266b2dfd9582f8b9e0d78cd0bd487d7e3ef956ef2d66f23609c6fe97e303325bb1604e8ad2cd68c2d939e18ae7e40ba0938ccc95fc7ef4f2f6a21f570cad90601ba446b92f11c5e3ff87531fa6df30faf910b157544c24f660b463602ee1dbffa4ab2f90fad2de583b11d9869c55ce0d29435ddc0b964390d23a24649252d60afa91711c36f2d237a7cb675492b9d7310bcf971062896172f8f0099f85b85d5319c45e94de9c8f109dcf5fe53662009b20c972b3e34922c0d69e548b04948888a9736fcfda8b727dd960b16d241f6ef3c0d5866a3211d872d161e9c9eee2666ecdf35d7eb706a7fbcec1e436db0910256a50b727fd197cf1af019ba1df13c51892cc53a61c543cacedfb7ab29ec95e69b855b204804e8b54cfc731acd80e42e1e275f1d419658b2f6e642e251376cb4e51424d2cf191fdf8430cabf4907a648846e8440dcb5db09f676110488bada841cde9972393abba1f3219325f8fe0f35fab76a2a98e26d75ed2392e47067cc6fcbeaf1ad46b82932b3082f121d657b3b4584bfed8c4051a0eb81a71386f0de386d55e132eaa41bd2b958a550a97b6d5e3a8791bc0fd44ec14b1c1584c1cc54d0aa41eecd3b531a84d20961ca1a273a6e0555e501f2dc30f18b2bba7a8e6a4907a05f5c46061329dc85e3c52720b2a6f4254fa72e87bc93b7f59a883d7857f9fa829165d33684c1adc11a83d004a44c71a2d5572c105dfe1519f008a6e0250d459e9ef931c46e38f503d1c83a2e5a5f9699fb1d074efeea63f0c3bd1632ddb124199a4466612751ad5a4ecf36ea717c04bf65ec7ca1227f7638cf1940f41213aec4409f66022e68cbdcfb25bf796432488185d97f12803d1a1cd5d654139f4188ac798135cedbbb79ce85f4fb37eebb1e838b14c767677de9cc3c015b2b4d0fb26221e60aaee38f1302bef2400dc8fd2d35d50ac51eca9ea7f6aad529c3e023daa8f9500a4cf6ba3e47003544a8d4f1a643a0b67a446a1357d2ed2750b6532f5f423d4034e99602bca9208e5a4f2306fab324e13a148245d6b7171e0e39281631cc7bab39543898a00c686ea56272bc2c2693ce5a637311aeb985dcc1b28c956fc07d0eda89c05750156b670dae2ee418837fea673deffbd10699c2227493307c88208aac0d34ab728d29dd9c269a28d41608eaaa7f436840a863bb6cd6104f9a9deb262f2426d83c15947c5a63c70fd0cd0c2e943bd6b4b081bf3988cb8d61ccebeb82c509de8169498c921facb7ab791d2b8a6e7012c9c264ff5a7a4dcca7be4bb29c415b11bb5ba226ce2d6c75ca3932ad52302c0ca2a52328657613d4b3a0afed3c2e9c2ebce9a5d2abd31b809abd8c693b085f51457b562fb37c0b9362ccfc0891a1d09e50f29155abc6d9341e57a9b3027f0b052bc46b0bb12c5bd463cca8d61266e435076fa4a07985413505a8d527071e72e231d4dce57f52ec243104c47fd2af3ee325c1d297bb9982036d0e3bd1e16796a62ebbf7e33c2fb2dac73a928c576c5def354b8beb236ae4466becdcc036769ac9a9f8fb7d10d5e3f418aad642f1b742af3c5881ad6a96c66b43a2f56cc5495f3984a37397245f384dc8c92c9d096d48fff63dce7a3007e05fc748fdcb0671bbb91feb30f3d03e51162a4ab8b641e982d3494a704438a237548049f5b382769bf0f8b2bf27433961858eb60522e57f541264ffcacccdeb26c644f04279e14099c761b59d98d2625108320e8d528259060fc8e85ecfdf22cc790aee16eba2c2b7476d7ce9318d36eea249072c35e3ab413a90f318f7614fd1460d62c5f08276c086f0fd5ce94a0e39eac7c3c5b29e2c780c5f2b64c00dab74c7fb2ac6448da114dd6c5fda581629fb408685ef1205c301d63f0b2778867d5a136d47394892b1b0fa933fe7de26f77bcc856eb83ee5613d6a3906136de7c588a7bf9cdfd4ffade990951058381139cf99f6e43ea3a37be7c6e9bef7cf98264f2a20d1fbe65f44e0d9d108030211a952b76236e30ea07c580130cc0566c18e80b318710b61a8e3669153b111dd9fa75dbed6ac90c924c07fc28c6eaf84002f8afbe5eb0b5b3ee7d2db096e89d5875e526dd8ddeeca0feba127273963539c61e3bd791add992eef59fa70fb2d4e715d807b49c3ea89e181e71d088bce92a591cb09dbb9b7653a233ee364ac429af003284a358bf27199118bf9c0be239f326a05e846f3a109a9f680b1b17512bdee0b51fa45a3aad73fe181f0b386202690482ae6894351198df892301003b64051ca21cae745ecce9841fa461468e0504ab55df123db34ff674c23f672d773cfc3ed74a89e754d761ef7ad4db5f7ea296bd24a97919d8c050e1abb4509430e04dd1c7a9346a64248c23b23b066736b71a496104c0ee39f506c96e435463acb12bf2938f697f618db4b4a33cbb28c79af397eb208cb9d9fce5e73086491768e86f8ea0a3b3fcd2167ed0370d5ca97f3da88a30763d412dae78e2eaefb3baa5198c638e570faed5a113a931dbeaed55fda4201a10f84374f790695fcfa0219a7daf27cb8d93932d74541bfdc70fcc69bae8e523d994b7774a350195aad7465449173d6719d04576ae5fe0bce04fbb678e341b6712f24455d3ecc2ffdb561a0e05ddc84b1c2f6a2ec1f1d83917d16e77139b60e189501c456109d2bb05e31de151d5111ef4853b2c719ce24398d023c20f6fc5cd7385681f44e6b90f76fdb84d63497a506ae82f136d2c272b351aa21efd1acbaff79b9f2ae4729417227d0e989f5f2ceb6de1dd980d0ececc6395dd66d2e308f1e0a22113fd370eb7098d84b3260281dfaa51ab7bc518c6bab073ff91022ebde9d2c0a6a38c10c1b044deb9f3bbcb01c0d17883efd56ea65348437afb435df0f7d2c5500357b1962ca3b8547b20d4df41f0403a5ed96d79fc14192b8099afccb7473484cfc0efe068c6e494894ffeb386b0d57c20921b2eb6ac1f807b2e0dfc9afe6e8b284f93153686fcc588897ef52eaf4f7ac8237c426e11ca2ca9f648bfbf0e3ad0eaaa78081da321a16ad5df2937edaeaa8b06e8ab367d21d98b7dd4bf7ca92ac5d7f08d6767d6300ce93c8c98285eb859981d245350cfc5c9f62c3a57e9957d7e1a17a28f0e06964eb8fb7685d82f511603c08d0517a8bcc815526a20da89fa7eb2b8c791bc1f8054a8784245952ae5fd6a60aa93352e641ed7006ba9025023a9139461413470c9060a28ddc66309ae88d9964c5da6898b60252b76cd6379f58be5bf7fa92ca79ce2bea1a077230200c2272e04c546bfe5dc144394835426e99795fdd51135286373a9dd81f110ca4250ec532f2a59cee8598dae887b2366783183e5da2f0efb973baeb6cbe7e2b0dc4e7a3f07720bd00fb0374b5da3bced4ba89a5ef3a70b1890c81932feea4e4c6d1b3e9e205e22de697d6adb5c77196fa37524684304fa2a9284b6847f3074425c95ee885bf23f56878aea0b04e712a3d2e88d7d371ad4dc7dc1f92891af4192ba577f065e511a8ca01d5b10df57103d154cebe04d443697a91ba14fe2538f382645edab283a9cb7d754be1c79a8683446a170a202a138a9f60859ab4d7b93dcf494b4135fc19c70965c295fbe145ccaf040ebb2106515caccdffe7b0ae41949d87cc6029b49690d4a26d7dee6440492149b5af05f07067923493f5af48589c1b3d0e654a00e708396653a7fc4bf25f1b2677dfe703699f7fd841f3d3b492024e489a7c8d45e3d309c70f7220710af07c3227b74715a05eb811b3b1c2c849470099c521f6258437b18ccd1434cbd4751aa7574f6f41114684458189580c6a2c8e51965abe348b0b77b197e7324d8f115b865ddb71c0094d3295d86c732d45b74b67321460a5d9bc788f16722685e44332c8087668fbeff548d18371933bfc505cdb2335bc6c3884d0830819c87f997675b049adf37000ee4c873d980a3553da27b4150f4f1981dd39315351fd327e2f075b4b3fc36200b6394aabf2773a60985c899436f8f15879d650daabf94c49a201a63f8f1bfc63419f139c729553b164f96b3a241232c88ae26c522989b884865c3082d44f1289099a0d92f4c316e1dc5749d3599ec78992d6aa95b2a48c7026992eb5199d48c9517a27f0fcb850ebd385a8fdea874e81f84699e5d3202d2c275ef8d6ff40572f68003c522829af9cdfb82f9d157e93d3a80be7a19a56c0c461647f6c6257f2161dac2968569c1c2c750245979d7417aef461d9a5d4341e6cb47e71d76221455f21d3f2f94829a97575068fe2f7eab670f4ef3c04d40fb3abd434604e571f4b5f9f66a366b84a47565ea1abe0437359c23797bf6f3efd84722e1eaccc9b65363e662e61713a0b74cc7dbc932d6864528608948c95bc53228fe4bdca2a2a8571f5e860a8a4cc540994422e63a6a721f7714bdeb0f449f6a81aaf4bb5cc62bf6c4481ef5e698c9b0d13191f37e8b40dcdfe4959604a8bb1b5fc175930c1462521e6a6d8edfea51831ed5b3e3e07be9a9134b39590dfbfcce4e27aaa70502e5aa7946b7967df583e40908a90734b0b6485c29758c9985af60efb608c11e1fbe927713e70204df76c4d4ab7c20db53d056f01570aa58d027acf5083dc88d0f7c98c5aff8105c27436fe8f5d70283e0e6b260f10d2ba8d6b4b1c64b9693f88ae0f6dd13dc3236842a228f9fc486cfd27a1eb2de573ed0a111df26a4f3f570e92b1e9765c88692dbc060283b72372cfb0ec33eb53ed8f0b58112c71aba8b0482a4977c9e91d89c483941f2203205a4432e05b297a654a0b0d705ee9c6a3a89adb1816ca55f8e24206ba8c747b602284886cccf9d63633ca734ac113dd5e57855d43d050fd45b2005be86a490b16f9980ee512584a8c204d62f8837e4494d0c3fe676ac6ef09d2c442d611d87e7565b8bfa2ac0b746c3d1a76d354ea7478c068f6f41f29be8b1b21a18e2b27a71c90fda6fa2370991e379283de92e7f0ccb9aec90b9d1600cc9fae35505fc9b035a4dda775a2014ba92361bc41f7133387d4e3a549f37b38fe6e1ae40276cbf5a94fc9e565265a02b57fa1508dc0959193c462f00660001004f81a251c960e89aadd73beeca586bc8fe7540ebb6442dee731b4d8b4db626298908adeccd2d03a207ac0730073207665567c821c3141da529dcc5c9c9d1c981c1884d508e371de91011e9c8ce97bc468988a883398a42494390476cd904b1b00da9cfa4b1b07a18ed4feea349d62c2929edaac5973fd8276b11dec42622a4a323a1a027f857daa1788d13fe5415f7e10fa37bfcc97d3891fbc0a1a494f311eee4cf0a8ae3d4c7d1e00fcd6bd904c99c8983949498f08f044a69d9c46b344bc3723de4c10181d1ee3f9eb3b2112608ce3400b0accc2db460ad55420ac036c48f8e523a6ed9b628cfd9ea57f1263f3af259aa304aac22c8c33fd35ec53da7cca269659ab522c8607ab480533a3a5971729cb8e44ad9a7401efe305a8529314939a608b9bea6b32d7d641319a4e5907bb59340f53fe953256bcab4ce6aff5938ebde796da04c3f81a902bda989051a826304e271477cf890079178b494b4b4b4a413252969c888c8c8c828c7a8a8c84748484808c7c9d0907c92395e0fe6925bb09ed983613d2be84516ccd72e675a109d9d2677ca3ea5fdc569f8f2a6a6d5d43ce08ef8d9a6592f6baa76f5d0f48e10d50a3bfe8521862780fbc01abe6c976b5e578602c438c08d1b2d25ab5519e24ddeb495bae44ffa34e1d81fc7b1404dac929d09d8347c3579d3c6da523a6f2cdb42a20379c4a77ae72259009e18d6989abf70c4bd31492288b982e94ac1a4801e229493ff6cb7a186f8c54ec53cfc6cadc41e8403c14c35b3c160d1ef4ac360552ce68a1c73858625cf1539857958fd64e655b3f545f8333acb9a6c5f1556b95e54c725a5a874f34adb201d1d7d718a728a8a8259e914a010116931a5ca942a55b895ce21141424c5d1d2d1524cd24ad7c0a866dc9bd9bae8e30379f894c1f619c5a512fc217f48d64c23ba9a47ab95538c6d88943c31bc97d54f7a647c8cd762aca4d08643638f7c7baf0c8dbd6ec432747d5dc732347ddd8965e8f9ba15c7d05095c11786b35e5202284839488110144048bca6864944ec70c4ce3407a8304102d35ee99a24050942549c7a72582264041e70e1e5c908259ed7b5386b023865e1840b268eb085d36bea9a94a58813ec4c2be1a2670b16ec9586507811a50989212bb84079a9740b2f38a50a1e2400310220a0e0795d0ccbd0f275352c43c7974af7a85ee67970bdb867fdbd588686af189a07fbeae9e1c1f58279187f6fdcc02b8d836a29a46592ac51925324152ea9b8d7046f862411f066873c0aa2caf69758c84e06c19b2514f04adf50a994e08003c736c4fe3d418b3677564f570ff7dc87a75f54a5fa4f0152684812416982fffcf19942db699a58f7117fa5ff9544fe079df0b559691b3778a56be8d081575ac68b6a0a40e0841d39786c455658699997d54f569f6929e45e907b301a0646a200fe605204042279e20e873fecfb4b21e09dd13a3bf026e2146dff95e65e563f81f9192d7d6056af923deec33f46eb600179246b788c3ca72789ec67640e0c51519643e582c9d1d161818518262a4c301851526201b3b00df941847484615e9aee3c48d6787e7f0f729cf847474b4a4276a010b957046f623c22228ac1227f92e787f67332d91fabd7d44f665ef6b815f79140eec39db6c89f0cd2ac140a92536bc19b98a6fcb953101c93a444fe5cc9a1b8e7442b9d5f567bd2cfccabb4cfbfb21fb937d3b9120eeb147327855c06b5ac27590c6f5a364e4ebe65fb5f201808be1a6eac7a652d85dc897b44ee691d8e2bb9870e17e6d93ff04aa75e563f513dd5663ef366b4f5e00d00f0dd5e5867a0f5d6bd1602b00dd13e26296f43b2982bf2e7474b4af0b5d2d82bd34f549f52fd556592e74a90cc712525b7621f8786f59e046f7a7c688eee8e1675fefaa2d2fb21bb1527f7dc9fb40a5a27599ccc99be714c527ee1982264f93adb3030508a2c579abe342d9d48222d8bdc3372cfffea0cd89dea9ee4eec9aa9331f97d23e2dab693435264d913254fdc3a2b04461b813ce28e8eac790500aff414c00e5e6929046fbcd2fea256f6788d4b1e2b74f6901d447414c1d671660e9e4e514f3b6a46e944494a9a53aa30ed2ab34a8e5151d13c5a4ada4b7309c7c9d0d01c92393f89f634cab269445739f38157d9ee1e5f04d28137f12f0ed70e1d373629e4a859c1c37153dacbd0fe8aa1e14bf2c89c1d96e6017dc487a157d0477c4d6778f32dcf0579c49fb1323e767c2e67c7156060ecf837061408c0394dcb7d1ce950b8b115f16ecdbe05a061009b5934079821c68d1b2d2979a49cd247f61489db9de08d7f8ee273a7ed2f5f0e39712705cca8542dacd2f00503f39247de684578e3c68d9ad2d08b7f6347edc99af8f0c6012def870c837bf18db8175f32005bf7d1006c1dc0e33d5ba2b65b696ac1e75f19fe90b1852c836490f738f94f100fbc899fb50570601120861c154ecac78679b8765c2dd3213dd6809b2b608b0b5bb30693fc71344db8ec361e3794a59414422825c43027654a6ba5b5d24aa95b2e734379021c6744c69c11d9e51e7c496d28f5b8a11c7385a45462ff04eb9478e0256ddccb98cbf86386906187279d734e4a6bad76bbe0cdd3602e0827753abdd62112897b50ca6e06f7a094c1e17379c7bd20eec187989571d642bbd73d970c14acc09a11fee02104de641b9a88b3fd1d54c7cd2a5a6813e10f9b43abb91ec6ac77e9e8eca0b023f65c37766eb59cc67379efd2d93e27536386068c1956e6b064962163b060ccd498a9289cdc611b314aae9da4f75c2eb712a65b9d90df8337f169bc26be2a3554b857b38dc429418021028901ee88b0073bfea7c92fee8c13aa108144186477cac91eb248a4d352bd64cf1b5c42f72dae8cda3d513dcca0ccc042ca9f27e79cefcd0867adb5565a55341f1f6a215e33ab11c7c1387b0a25cb320cf3526f3f1c6fd8f753fa9bbbd6bb64df8c05119b5e4357609bc89f8b0989f41def117ff08911283a0e851fb1f7a7812f1d0001af8111b342c62aaecafede2ccc054d106832d27d4158ac9c3308e3cee0d630477b08bd9833614dc4d08bef5d863551eb01ddc94aec255db25d8bb803fe4c027edcd9a4fc5996a4dd476d8c1fe22a1bbf0330fe09450412378e529f6e8a7dbbf6b3f63fcf65c5304ccadf3ae8def672c9b6d956d31f0e5bfbd46ffa9b1b5f0debfa59a5f4c99db4f02aae853bb99313120893d019a77b344af7923cc001481e406b922443dcbd2aed0b77047784f625523a638c58ad15ab75565a2b85b4c69f49ab8e4fe78414881b93e453a82012ba02e50acf15a02668616a825313a63401290135c41d2184418a1ee3aa052edd55b03ffbcd0da3908822c24695200a88281fa80f9720289992c462e54b0cbe047d51e28b4f14972f445ea2bc6cf1c2e4e58a17a73929867941f2b264bdb4c0cb8f17222f4257d33e1b1585ef8de2b2bfb9a398368c72c2445152f8e0295b1d1b959628f6df5a8ba1b84f64156933d228132915dcaa84dd2492d7e2ec5b97b52ccb59293d4f1617bdcb7a96f32ce7d158cf7296b3195b9bb1ed69a21263c5e72d673997f5acb573da20210a4b1450171978972752e687398304a5b4d68a751902934c40599141fc3c5579628a53000591376bbf70ca2294e5c721e4c20396ea8595cd6129e5c430285e08c1c6e4204bcd407bc1f502092f8650f222e5850faa5454be4d5871448089d1414c0db82d45325b86626262b89999b9590c634b0fab0b2d5a6861d2b2b439f7a2162866cc98414383869619684142cb0f30517a0bad75d7755990984ee86207b10ba42e96e8e2045d8c208b1759b08859a8885996bc141cc2fc2cfc2cec42c55ee17225cb95a72b5f5c59ba82f44484054a1542fbc31b6259da9fc5f2b321161c6041b2e9614908def8a0089fcdc5c83970a42f94a073fa0aa0119408127cf1c3173ef10b9eab69fea58bfdcddde58b12f667b32f55b2a669f7227d19c2c574a0e209688a1b81c8fe5789324d3b1425d2ec71b2da504a8f5176293d413d3e2a2d5474b0e2d1d889a55025a14e4ca3020a89094b164311ef135945da8c34ca444a05b72a7189c03aac408a0e4eb6d9650b293a34518ac29299b0834a8a0e3d968b2e72cc11a430c182af93a551ba8205a5124566c274258bc9714a57843827324c56881918accc0b1824308122db19738a8a94a766831ad3c66f11f452980c3129fa828909119673efc95492c2e407d6175d7d92ad30e1840fb4722b8727263c713239e51ddc98ad8c022b07264f4a0e46d4648162a5490e2b3d6432c03680d19202ce641053c5064bc98105527218c2e6d0644ac9812707a049450a8f95a4295146fce1c3527890f23ca2999c7b4f4a52789290c2f3641eb139f7a4f00049e119828212c5c3fa628b141e1f6f0b50972d79e68b15a28e22c84d48ea614305541037422c548c88492802ba678297c9997b3ada1f4d864a4f44dbbe0cfc11b773ee45ef91f2c332527f8ee3389c278e52e9054539621cb4e3d360913fec354efd25a141cf614f312c6e1be84e42837e0b69b1e8149b90c42ab1ca3b102ceb4e825100b9fa5e6badb5be8cbe7dcd0e6767771cfe1730604029a53096c8f1b5212062fcf9374ec731665d46639cdd8c12a8b6d8a1185d9c4894dd8c1a36dd78b37c09538f96526a337b03a5df711886514a69b59236515a659358257da96bc452524929e40106144ae90e94623aec1d9e483521bf7e955d92209b736206125eabb6f89b5b7e78c986431ea9a5534629adac2992b3b8309ec81f9e71ca2f897cb967dcb10b1964b7eec5e420cf39e79c19bc89d89c54b5efc4707fd3e73b0e1164f933683eba3df736debe15a9d1d92d4c7a8d10abf513b06927f597f3344195177652beb421b38c9575df0d74675f5f7ead5ea576d66ad67ead99077666e16359d5b0ce5a6b06e1d75aeb4398c15a7f6accd6395b9811eeee3153f2972402b170e6266c49a7efb7b52c567c4a23b72be532b7bd1003318cba8739233a3b09648a92525af99ee3c86f390ecd4fe9eeda7dc7dbadedbefa36fbaba7d768d89744d3d7abc81fde53f2b763bf637867255c9dd502bbdaeca3d9d1d61836000416006168cf2478d224ccf9207f1c0beb701cce4862156ed38c252fdc8cc0245bd35a1ca5f429c6fdc0053a98539a986c0a6685a3f4192282527484e4de153157604d99a6d4147345ce569ac590874d614d75918e8ea4385a4a5a5a5a6d50888864306444646414a3120a0a3ac1f4119a4042323d321d7fdcd3bc8a3701c1c37698ec7f58f9246b664fa5db4b49b4e10b06ab0a6b1be24d997ec2c91fef493df711673eb5afa552a914075f2b583718bdc48924285d789288d8e188d706a36368e20110549094a58813bcaa6e61a7523abe2ea7f36d61abe50f50d050353a4a8a92c91e2408e08dbc815b16c6a280c23644a3369e0b854fe91e9c84049235413cfc4bfeb80fc924a94826c7b1d2ca2a470d8c6661ecbe29e4b04901e826a57d8b47f9160a40e0041d29b8746c452293cc3102841b2104108837f9f4007134fd49584b2259e356bc29f55b40b4afdaa7543e7cf8f0e10366aeb48eceadc8fded6fad5757c0bd24d65a0bb39a59253fc89650d8e664e569686fda4e21643c9fe6e4c2f5352cbdefbcf37e26ffde9b44878bc945dc4159f673ad9fdd333ea6bf14b6fcaabfbbe5e4623a4d2ee613f6d9175ec7522615f9b83f6dbb5f7084c5bdfaaa3feeadceb2573d9439334fdfca1c99a757d3dec21f30af7539bb732b423ff5371679139ca6f9192da3afb619d908b04d9caa386378b51981db3b18d341f720bcc11e6bb9ba8f45d9f155f7317cef7dda291b84bc7549eedd696f47f1e3c7f7eb17f73d72f7edbc89b8c348c67385290cae31f76f07b50eba076ffcad8e483b36117714394e1311c8368426f94b18d782706794a00208d7df3abca9f3064a3781aa422825e6d5a9bbbfbbbb6371df06934b553cb8b00595716c561150f904a8dce90d956dec08956b6c1554a6b131548e91555079e6679063a0328c0a2aaf545039abb60a2aa75450196f1a2aa89cd9ad82ca1854ae5b0595e956416587ca510595e1964f699cd3dbb2b3750729a594f4088b61f262ece4564a29a543504a29a594524a29a5b4c29a940d3294524a29a594524aa9c6eace28a594d20d7893035bce499b665e5084cd39273634e79c13ab7556cc665992146c4adf4b6a8c4c19a594da23589105a1ad54db2c20988ed1ed496d9649613339b54cc3d79d1ac746e1466b6b36139a73ce39e79c73cea961ec992db36dd6c96cdf44b6fbe573ce39e79c73ce39a7c6f6d578eaba6f87e79c736a224b36f6537f75cf971856998a90e3d7b061a32666af0356abc681c28dd6be3a5b224ed8ddbe1557adf7ef6b71d536f6cd70d535f6d570d534f6bdb86a9a7d31ae7ac6be1bae5ac6be295c758c7d55b86ad6be19570d63df15ae7a66cbec4ceb2aaca19ffa6c90d95c88c15573fb72b8ea987d6570d530fbcee0aa57fbc2c055e77d59b86ad5be3170d5a97d65e0aab77d67e0aaf1be34b8eabb2f0d5cb5b66f0d5c75b6af8dfbb86a6cdf13707dddee84dbc255cb7d6fe0aa7d5f1470d571df1c355eaa7614ded0af1ba0d4d6d4504ab398356cd8a8b552574194b4860d1b532401993ad95aac6216c8d6a9ded32414a82a7dc8619b5959c336b3589606c36836b3625a1ab69995e34097d61ed6650406197474f534574d0ee9231414049382531396272844441c360426213b46b2c6d270c6363e3971e24342e00f8b4d213e64ad8d4eeb3d2d624d6b796f658dc6c29f79b6874b4763e1cf3cdbc3a5a3b1f0679eede1d2d158f833cff670e9689ad5340d5b4d73b574f4c3604406978bd57a4f460d8e06e7696b39cee3b82716be99a60931b2a3b9743c6b2dced8667bb3634783491ab62dcf693967ff5b9eabf59ecb7233c0b1916371e668b0cd5c0c560b305ef03310f26731b602b0f10ec938c8240c005ff7b70de1966c7de29303bd5ec33673387334d8722ecc97b5d95a7f59ed6bd64159a3043d3af448e57f184b89d3c3e59e7eeb299df9928a9a77927e4c23b2c6bd1617c4e53890ab87c5b26847d6482724913c62a57004492024867e7c7cb22779b64810481fa1a020981aa7262c4f508888b81cf6e10ff93ec5957c08ce9445a4234ce3f77614d073b927556985c6c04edc0950901e4e85906b4f940d3cebdffcf28e1e85472a71078e53221559bbd81094986a529424cdb134f953566454b481c0070f3919ca8f04d04a9523907b4d82e8ecc09b96731e9f649731843f6a5420890cceb47402e240288f1c27e33a0b6f98268c49b50c33b8c7d120f770cd90518386ce9f5b98e613320e3cb5f3c427f71cca6b9c738208de73df1abc45e21eec3eb2e768906d966dce287f5ece91adc8dfdc4f11c7a5a8746dbe6cc32f860e1436eb3d5736238fac11e245178f960a2728c16185f8d4d1e42af297a5de9e8deb060bd8784fae2a7c7033c0a141458e0e0b36b37e006151000241003033339500703b2d0ffe90369e7badf7bc3d5beef9bf042064082633ed3efc5c85560443c77972a087dfca54f29769dc93d7088e9acb34c89cc78237d1051f407c014fcf894f5bb6cf1554e0e17a1b3568ecd0716393428e4e26a950827ba313c06610ab74a6e2ef78f4f2658afd592f5f96be445102eda1cb08baf470f1c2250b172570c1820b5346c4658a2cd3348d0b1297252e47140717c6193d463c239c32e51d4c42d4e26e0f229f5322f7d8ef729cea3ac61d71943f6b30c95f8f2d279473bacfe9eeee564c79a7b4b1c6e59c11d20ed3cc94a9c81fa65c5c992a7064c77591e5865276e0815c70354c1e9e20f60ddb0b3036d67d1ad8951ee176b833e2c33da74b29670791aa3819a1098911aa20a9121fb662b4016ffc3120e4cfc6761b36ce192b76834b2d17c62132c481124db6de1007454f5630c9b9dde77115f7e6c7e99eab6807bc913cfb8b519222927b46b1a88afcedd87207fc211f63cafe438e80ce0259bd8968148bbc8968f483f84394f4f5c0d8a3b1d6871ff9a9f78729cdc3bf92b875cbf82329a55f7fc9b6fac39bc649b07c89e919bf09e70dea160ad42047d9de4504129fea96180308c4c61d6e99f83005983cdb0e9cf6b7644b01c196ed6d28a587a32d05044c5b7ef6db5b87ffd19f53045fdd348727944065e3874fe18ffbb0ab9bfec419984e760d2a21f69526d87792605f1ac4be1587606b5b916edf2b2b2623f52d71b2ef43293f406c4d7f30f55ff69e821ff61bfc2ffbfaa9ad48f6db006238b2751ffcecb36dc8ed9e30bab488751c7ae94f1ab2974f03fdb2ecfde7160391acfbe043971758b225b417ce396fac5966a20c8b06126157493f6e49a5dc8ecc977546c24904ebd3624b8164e484d3cb9499cbb2b6529f0636ed7c501dce39e19c10aa90fa34b09370e13f9c7807fa347eebddd543cbec9065530564cea5b2eac8137bbc463200ffed9424611fc7cd00f095658dfc582fa6e3be5a4912f63da0bdee57fdbd906ddbc1d78ddb91d59e1f311760f62c9282d5ac36e1f5d4ec7adcc378e886737d4cb721954553ab0bd85f15dd54498c7e22c3d738c8fe48648ec6c16be4537d030c8c89409c87c364ee967f6770d5da890c7f685bced8280eee491d7d648dfcba451e78233fbe206bfb8b3c1c97fc41a82d1f2a711cf8e3266ff938a4b62135d31881012a65a151199a864593614506163168e804d1c982260bcd951935e8535ab5a76ddc939f37eb678f6556a6acb111b135cb741adb91fad853892f4e7d0fa89b6f71931bd5a96f490a36d6d508b4428207fe8c98ba7cadca2ff4b923514a29fde31643e6f2258c707a2c6272324c727dae3ed558f745b9a9d75a4407ac9da431ceb7d4ef84f2e7c72d065b5dd0b65389452a82cb75e12aac8f69e8b825344623f764cdbd53d33d346842bb5f17005fb7eb8056bd38c57588c32c88c01b10d1b1b3df522c783205f93c597b22f5e96b5ac97c0f682fedb9ed056a8019b4acde94a63f19b67d9ab9cc4e2be9c76ee52563b97d970c377c79a69a5535cd8dc1da951b119805117803223ab686cdad886bdbee88cb86e0725ef27c1517956bb4946e4046875c5f86a88c19f9ec0627866198a5349b710123f3b11fc05f00033636d6d160ffef27d1b6fd8b6d2f6045a0164b08b10403d462092176707724a66ddb61b8418ff548d6658bb130c37f080c4e092c791202eb8eb09fe79c15d348a4f0c11e4491b48d905882c512961d96a03424047bdf06b064cfc7b608402198c760d78e86fa17a81d066cecf9b309f94a7c89bb14ba1b9cdbdf7beffb36e46ef4beb58fcd70e1befd6bbf641aeecfa84c0306b7ef765fdc57890ef18b5712db7b407bc9ed056a00215ef2e18624c692164b08b1a4c512412ff99c172e2adfc734dc9ff76b6aeb3e1936be9fd160bf668e6bd126f7ca9c3729e3cf8f93d617ec912d392d396e2e685dee38d7965d0c78e3cf8a0fff63492044f863cefa37d839a70d3b3b0375d710936a883618c0129b6f3ece716a8bb1dd5da6625a1a63c547b02ec78e9b8fa1861cd632e945f66be38d5b1199fa10b06987dd8347f25777eb9e0d37d85dddb36167d7606367a0eef855dbb08467d710801de70eb82d39be85596c1b7e831362c7894364d4d667b8009f3eac344a39b723f4e75bfb5ff63bcebd906d19b6ed6a8458111d1b2302b338b2c49df12385b3caa96b9021bcc95eb3af3dc6c588f58c179c481aa8f42e7f1db7627b11a0147feb4ad8feac47410c3c0a7febff710a231c222cc1082dc8018b286ef062bd124027f8c00f4540c4c0084ec0a429a092acf861c2698859c50f27738494344b1eb6ffd4f0e6e18d7c977cef684b860953c64af68f52e9725ca4e22b02b975ba935404928a3be443c71609aea32017e4808818fc00eae50f01c7998f241a21028973c7f3fba6ac2072fc6f09118c9efa962c19dafe338907ed794392179726c6241eb4e54f569396b75dca2d3f1e394ebc34c81fcdf61c300f39f280204ff8dfa473ce39e79c73ce39e79c73ce39e79c73ce2c40d8896116a3d5ad486863b436b35946e33644be0f91d6622cc7b16fada5706233abd33ef655dbf7a3b8c376b1086eafd591dcab181d82d5107f47ce8f9a1097041a1b9697b96ff53451eaaf29ad907ab576134278635363144cc3a29285622621b150c41df12dfc19c53d6867140bb1b4229f94209d9420ad482b558b1a93377147e4204241868204051922885010a115966884c509c82901e10fb2400821841042292184504a08219412baff6cefbe24ee59a2160d29edd1434705970ab1470eeeca8b1c67c097738fe69fd6ce69e7b4ee9a9dd6eae8637fa69d764e2965b769b8f1edb07bf475cc90b4d65a39fd314cba94ee532a7180cf20c3e7da53db57de6bb3dddda76bcdb3f79d759f07ba6fee3da7e5a0b8bb572933293fcba494d25a29add4b5baffd4d5ba47bdbab478e5c50cd48d51d9268a890944a937edef8639834a309b4ca019348346e00321a510166de840fee3f383032d21042e087988dd511d893e58fdc1cc896d9aaf5ed33ed5bdd47c2ba23dfe0ba30f1a8b6655557f5535cbe0b6a96c6ef43dfaf823ed38847ce38f8c3ee02ccbb2c7b22397be96e9647757e2df84d1871cb123aec4f3890e61d8c74c27c7b11203a37ba8f42dd844ad5433020000001000006315400018100a06c482d1582848e34c301f14000d6f8a44665438164923490ee3308a81300629630c318800648ca121a2b100e9ab718d604e2da8dd17c6189b0ae85f59ec21c483294034f9d64aa72a48ee05c0ee4afdb053ab62a322e5f26f56ff67b106cc54f60d6e2193cb2cb56ef4eea7b31099c0836dbe9bea4b3a446f08731097901ca5908de83ff711fc60c5c9160ed6593890dd3238c8b2bb418acc4947ec4c4ac22da511044b90729e1a2d804e15030a8d3a82016c817b283820b7352999879b063c3b76c35c2fbb61d4274cc8ad013f4d88b4facbc22cebb3495183ea80f26840e1b2608a9070c4cd852e29955105f0cf7b6b659d7357669dc250793b39a40f05faa763d8738d1fdaeaba38aa17ead01c876643c2bcc5254f36e0cabb5532cbcecd5365ac446fff9785553929e7c8fac318921a04c9dcf50735457fa11d5dc05c765ae86ac3d64ee381cb04e8e64eb17d62ff48b548a7ac7b5b7bd28f5b909c3ff540d9a348c06e8fa461c724e795d30b7f476b35fe0f7de4cf0b77d0f1c4ed635933691ac388de7fce58cd67ee11e071e3c6a2a8b43c1bb0fc6f454004326379e338e41041c1e8a758daf9167299e98d9a6cf9d105ea8ea9fce4b8191edf38212bb56d68325710ad5af5c4b064f74674f5813a673470abcfadd84032bd41f14c72c0292e32ebb2c6235d19165c5504bae68cdd0006b74fe6eae66f8189e11b2cf99270d56a7a1a07f85bb447e9b706b50687cbde25e873af7e9a1f22cf6d9cb33217231eac2e703cf2c2773184f032a3390c5370d37c2545b1debc788aa717fd312f24f58dc9bdeca8c4932b24243580866c1cab75b3e485d8a1e639c2083fbbc429e2e708d863c1254b5a9c5825e7cce83263d687806c99a8bef562353461485a0e4e37244773ce672207ad603122408b6010adb11c4b052f1addd13dd4d3aa2c341bdaee45b6bc041c966ca926e071a87a45ae31b4e87149b1309ed0888f48032e760ea87bba0d2ddc30e43005053806937a42f6b242e12db82cde0ff3d471542701eadce426a50be867df16c61b370282612842f36fdb4f9e69042f2ebbab3c796de2fbe69b0f5bc6055624db14bd9ea5e99dd66256c164b797a01b4d590e42e860ef103333c3a39b5d187e98c28993fa76eb27206c478ce9e73548e89c5ff954180d6820dff3850128fc15c8e47db1dceda9998720fc72ebd044c976a2e6f518346f3de3f0b41a07b6547f65a92aba21f281b8b7f39a178ce9f587362598edefe0788952e0e9c60830cd374c9d2700988f1a71ee6d895295783b3289b70aae8b52a299bf4087b66e8e7807173e7247c3f2f506c3bbc0e9ef7d18fbdd9379733f329a28c5cf40a7d2513f4f3419a97ff6c80ad6cddfc6eee9f92e190ff73741dac7898820ed33ba46e9ce3eaf9f668404e4aaba2634681a1d830cb6e2a2c09af660dc91e9f223cdc0d1457e10d19331c7e122ee70eb6d50c84965f66fa5f2878bd8f3055344bb311859706028a7667f3887aef004e0e63a8b5616837493d1b1954e1008a3a49ed4f1ddde3725a6204345f12dd672ff6f4f880904bb8d5cf97bcdb09902838b83ece72aaa2cdc197e5fb0ca781d1357a2414b7aee403d4e8f242cbc8dcff114a72d1426bc267f606b35c22ea8409514a9435ded82ef0599f8171b1d06a3c776e51b9799772f00b936c289ece7abb5cbfe0b8dc6635eb6b2ded633f731f92cbb0241325480961825bd00f84e60ededaa252422faa8f66c1877361853f0d27f0ba8daf9c6b4302f07c2545186bdb2b56477b90feae8456017e423b9333c336afb1c1a2b5b9b0e28fae1f0834258592e4866e5a18117051c70db74435a6899bf58bb7cc54c98273309cb272660467228f8edced16a659aa4bb6143108c410299ca3b8fe4ad031478cf5406f51aade5ed291e7b68117688f4c5a5d1ce9944b59f925003704ee533d77edcd801c4a5e16315d8bcb31b981b8a44977a860c3a9c6c62a440eadc0674ef0210c923f42effa2a5fb55ef59c82f79bcdb269288e4da5ea2293896b2bfbd0ebea2f2ba7325ca7e0d2ab93c680b0daf33d747178569eb8ec78e2b9db703170819b95ff92e33b0207df3bb20c7b26c238b4987a41ca18e24344aa045241f280613aa72cfd56b5edc35efa7028d0a467621a24efb18e34cdc24b9a5aab4913c6e877c125bc13ac20156398fa82bb67723833098008b8cd422c229703d44922525042d6c8252979119ee45e4392e8e141231005cc16d876fda18502348d2d49439e03dc5845e8e4707511dc90b6a4edbc7225b0d17d469feacf7a8e2b9e026205d0f07134b44307013b054595b2034a1893aac494a4a33c1580f80fb7fd62205e5309dbae5a946cbf600765829da81de3eae57eeacfab974b409b8695349290e1799709aa77b6db65ac258b49136263862090db701293fc5b1f0cae9a77a5b6049bacc5d2b8dee37ff5aa8e3dd74d53a1e830bf01a38897a9250949ab18701a0b99c33dc3671f85c415d4b2a16e4fe9f477704b55bd4276831ac8874da62f60926cd2445ada1fabebacc1bccc27b0333c06599a6dfde4c6633bd6fe72bac3f66edc3dcea55bc2432684e66e90dae93a7eef3bb1e024c814a43c711f00df62207aa9ce9e7e0226939199c89bb703d8a4656fdbd76b7b80cc61b6c79517942b6bc4c3af1e20c84a0e8729f7cfd9b9a52c58ff2ed477efa1edb8cdbeb1bcd5075c89c3e509f329bf5a99e31fac32b43c188d1a2f14db683ac2335c9e2e646a0108ce7703a34bddb1d44ddc52a51664025f420b2770368aa98dc4143ebb1b32ab74878ce5a1e78f453b347cca8f9f2efc3dd0a8eb1ab4b3f9ac23773abb5b213c4dac9c3f54d423ae0e6f7d50ffa294eeaad18df0efabdf46058ca11dcf0e4e6410601def74dde12b4e439a5b3f3c842c2db371c75110f063a92b5a0d97ffbc045f1c3ec7942681cf4568ad1e778373d62f9e7ec102654394beed81a37d14361668c40a59ed5eb9275661dde49c322aee9731e5bd0608a1805e192796a55066bbcb64d08dc305ec3ac944c76205f88c4294df9932248e151fa56ec573de4e97573cab4aa3abb31bd09f5a9a3e622da8a10a590517deaa9a77980ce0d83436b1991140fda0b54f1721d71af5116129690db827ef82eff0c3d44af37406421cd3cf3993539f5397c562c7601e35c5318e220f23cae0523d0519e030823ed67a1480c257f599c29e82c4b6b40f467ccb7649c1896140bf26d794b658b6d871cd208150cf884a4714c498f0ca14e36e6809d0e02f9fbea198876a2876e9a95bcabaa13c831e5ceaa8ea3c40b9782954ce985c38b48ed4b93ec9fb250b78030b21a0f31b9bd00b51ae31a46f804bb30c927c174ca2858e1099a1cc0f13208e0004d0e86b972e6dabc25f593c237372c4234e1e238bbd430ab68c6c9f495b735d547bcc415005b407c5c6086a9b2f371c0041c25857b26a0e8e79b88f6c52514c4a107cd2360ff33f8244b6fc79aaf62433105fdc8a942c6846f54b717f84d38d38cfbb62f290d5de98de96a080a6990f690da319862742d7ce77487ad73d7d676836944debfe5c4ffe9eb339514c255c39db64c78ea5a9e019b4b98b4802963c39b1b995b92f5c059964dba0b3a83dd74449906a8bdac3c9a2380f29448f58777452938936ea3a5195dfd55ae4ce9cd74c7bec3130afb7a701a531c4ef7b4db2a27ca448536f6797aa20c5073abf1dd15a4e41671d960cbee940d95b0512584f35e2fe27452ca80405884e7ad11bfb3b17569f5583a30525ee7133eac4e14f5a3d604f30e117e7a128506bfb21f27a45a69cb7a6552787390dd4a21231c1eb4f8854eee19aa99543bc21622db6106fac76b62bc10170244fca5a1f56793b262d37a7e84b66f266ed985579bb1465532362c3321528a62b447213cda66160cf43b74c24b1f7289d500a9aeb11ec2618d739d9a67a428ff261d6c236ed98c001dd81cb533b4ba8263b9663d192d8ac1bd628af991473dea34d15cf1d589248013020cad32f3858f18417922c26e31223d150c8e98bf63e5c1739a9531f9fde3d0d06798626c92c87ffd58b81e1c3df04ca6bd488c2099c863b390b2a669112adba541766612df158fc9494550f28e635b5cb9760e9989f08692bd45f074cd5bcaed98c42f9937348bbf9c6acaca76734ec7b9b9e5fbfc5ce06307094873ebd6dc08c6b971d8a520418336f4cf747976f46c5115f89e7eadff00b5d26d30a8fff39b8c3cc39d009881374e1c61ad9f22ee1dd793835258bd3883a03d3aa603bc1311e343bce37d023b746bff12341aadece5b89cef4732da9ee0613fd0a6e083039f1efa08d4053f20a8d2c480bf019012ae339f459973d067cd09858165344652ac0140d629ec756fd8b9f3a2255e706684b9e139428728bdac206fbb58cbb52ff03eef4d706168108c3971f7e25e2da7e719aadd037e03824357044d91921bed378aa32f3a929719282217b8e1ec1f36792064417e6cfd1f0499ba242c64a3b6e8b1a5ea6335d92b73501189c5e6a5d0c334ee11f64d5665a7641d33a580d5244b33c0f9037729274a88f5bb12a10f5c220d1d71c963fb61d62b7cd7a937b2748c0824c46a98bdc0031bf2a969b2853d42f75c64ba987ecc1f07886cb1f320cafa26f9c342b70304c930ee434ed5559ff1e080b807ab1691c2e522d50485e5f6f8bdeb21731ddf4d0dd70ee563d80fbd173636ecd28ada70576db0e3d207377e152abf9c466ae16911e94ec86fab0342ca883888cbb1f22d6ab9b521009769728c275e19c8f656122440f7fda7a53e29e980ecddcbc2b3d54f5f8f606dc5b6fc92d643fb68fc7adef100aa4e52f443fc276df5f3efed4222bf31766325e399e811a7a9ad982619fa978a50798e44ca5dc56bd2c7fc7757edbda59401abcf649be089de3b987a8d9c8ed34887f40bff114da1ae8e8079c95f2a7494292ecd1bde51005e4cc1d284499dc33692c63432a409903a5d08699abdcbc864c575414adbe41de2262aa29b8a42c3b4dc1c399cde213927e54429e5da672c33c07e27e427f9e654079f0098429f346045aa08fc59ffd5b53a21f0aa5b1e368c9cd658fbe435b0a8440da2841332aa9fe08ca79f0ff6cf74a27aabf826acb6c870973c80cd282473c3994a8a2bde04b0c3d022484790201c19d44e5161931288c14e19676a53dd23dda6a112d927fd61e796aff46d66db9cd0dd7b53133f86955a3d4d2d99d51e6e62d5199e729037a39e2acec45a3a4a53f3586309de8d8062ca4bdf8b6a87ccdf3050f3b64ab4a890ea219f9300536f5ff957884eb6cfef2bcaa105447a4f48a68b12cea57af19e5248ed3f1bc2e8c76bc368aa53546ee8818f4d696e2bde1305a59d5fa9e2fad6ccc224ee81c2a97588c2b5dee972d9c59cdb11a5fce0fb61a774c3c91cdcaf10b8dfd09a3d7442cc0f811117aa81d2814bf39465d51bf1242d922d43b94b945ee0dfd3814cc5551dbb018ec2d01429d1aa35bdc7e5416a8ed54ebbf1d7dcf873ac5fb3c2b67c25f5715d256e6b8b67308d7fbba1a48b2b01aa352392193541c21596db78affd788113604bc46fc635aaa66589d449f6c8d60c9a9a34c2dd60276e8f3d74a760c3ee8b86708437e5befecfc18f98f33a6610461d94ae3c103dff6385268b0831d587a5723952a0132b220b4618fbd4f98fc04a71b8cfea2b865ecc469b96067340599b95e92d6c518ecf56853fb89c2ad87bd908815a9ca8ddfd7e120d4aeddd693010d9ce357cf6272071bb2f571711285ea09f146f1477b79552a6f63cdc00e1ac5b906001e7e001e95f0730ccc472985745bfb4a3ed0a11834affd70c3bfc9033d941c7cda2685a8444f6d7d6bd70de8eacd9583ad19d21186c2b5c26dbbc09bd6c31c4470f9d7fafa677f0026eb241e63f3888d68748d94e075e95b282847a64fff2b1fbecf5e204692015716fbd88a2b2b6a0a47690547db03e0eb425f765b51cefaff6da1d0757e0281b9530881be984999ab5882f395e1ce0459ed97863775b10b70b9403c7b8023b0fae4399ab0cf0045c146ace41125f6b355ca7160cbd827a1210901f3a69ba50e12e90b5e56d830e28f9962a73c5f905a57dabc4751be8e0db00e4771bed6102454df8b7e054f9c0251d16584a81d19a26d4341bc7bd6a62266a72d167a5a46b3c76a2d65b729d50df60c67af456bb8fcc31cb8c86fd64025e85a80cb9a0dc851b78ca9df9e459e75b2d6ea364cf52cc6b65d2a8c0f9c3707b16306bfec320b4b5c245d672cf1466159fa98a1da7744253e74ca12934189aea6919819c8861880f8d50e035f71631d4e9272026a6e68cb3651494d6e32675134d9a489081826f816b2ec3ea3344bb7a21cdf5781874be41d6e1b888604ad780556b5b15bc520f3628d8dd1a28bb463e10e55035039c698326dfe15d54c94463ba43273c7c2d7a281751dc232ecf4c3f82b053b878c8dd9a773f8e1f46993d70ab1141d8d25cc5ff8cbb6ba74807e69a7c63315c260c95c38274102b28434a7589ae7f9cdbd7763944f40a28f42bac206eb031f3f0444a9218f3b3ccfb1a4eab13c96faa8a5ef62a647b32a9ffd0f09ac865f06c042dfbbfd754045746f5abb55e238de569c401f15c4a5caf637d61234bd46d4d8ea18a85e153f33e9d1d78abe1cf45dde85ba8e54251e7eb569c42a330a7ebdb9aac415df6c5f1a1f6fd02f9ec0ed14a014ee04ff06834c4cb95d344641832bedcd2a5bf30b6daf07b81615353baa3a7857e84981446d8b42592b63ed1181c2a0040074e8ca283cd1e9173dab62646bfdc69c2ea8c2a946d4deaeaee1482836dfccfd2deea407913296c538e9f04fcc9c5f83822b8dfc2a88da6524e3ed4a0002c32dd3ae18b8cea9ead9c3c5b6f4fc8752811c838f4e94a67f722ee8661f7dc9183acaf559cef53584917a3c24fabb57b116e4715ed2f13f661f2e6215e343b3cdcecde15f689685925c1f1412f1156537436a739a32e11234b5f6253b3f1be3603577d2b288c144d15143fa0a0a7c602f3585d6d157009db7b451b9e6b6970c559f6d274c3129a7ea3040e9292e655fa224c44dae146f2afdbe6556e86e4502fc29941b820422f529cd02446c0f365ed475b9594779660520e52b7f165af80fd28a9c1cc826ef4816de2729b50f2849c3577d22171fa028dd73b3acb17e0824e9fca17826a657fd5245f0a02bf510910195f0af1706c8896ef65c245bd97ee6e3308b58c0992c3b464a2196a1ee812686b820d834b2f4fa1c704680a3e28f88325b7a411d4073d680c0054a442e13a1a37711f094785251ae1c23342ab807ee89039102f8206b50469d0fabe077c91cedb641a79b1e1f0b7ddae806e6b4fe8df7540c25342a569053a02f3c7aea5dac0e691cab24abbc5439325924d341fdfdd6bc0703d1e8cc711eb789ee8364f19d1892735c6e3d77f90bdd672b98fa234c0a315e57a969756dd54f5cd3000a4af0141bb1ac8f785a3db3ff10dac3d3f0b5cf64f4428738ea25aab43503c3ba98b02b31f6ee4de078afb3e0988144463f56f86e6f5619c7b945caea07a9e626284baa7e0d43f40380ef1194cf51ca6deae736137e3f614996b3e52cabe4ab1e57167e05752286e800a673e236a4062c4729a6a1b89ed9c10d7d2df3bf39658f0a8061a9cd9a5d5336ef2366c1da1c715f7dfc794aef711f90e576a5abe29f3417a0933697a6042dfd1a410ef4f89782744a790179854c22e24a2ced9ff898e5e8871f0ed4b8f1ecbf132f63e4311051a65f619182711cd1517485ecf5152a7d30985008e5d06e32f02835c8973acd27fde00b57e9b509cd7db6916bfeeb9e85c8197e9a0060e06a93ca7508262a4e424d1f6ad1920135220a1b79111dfaacbdbd0a0ff457325b0139e98243ca2fb06244d721cc1b2d89e3449e08aab7a641b5c0ded3e8a8bddab474d42f5dbe8eafb50429a2222e3c48a8da02df6adcf6a3df5ea4bb9d58d212bb557fc98512f0e767549d8a99f734cde6f96355ff10cf680a3c39c185ec8d91fc33ad3d6591b5d44286b227b74717e7c91149ee441ff5786c72a0e2518b63ef5a69d121035866d180c74bd916f4c8b99e088cb6ee256337d83a4e4e1ad33a27364a5622ee5342abbd1a56c0654e505f929bb9d3374227af39fa0406a4681823660eeb515c60cde5fa2f8996d6e054501837d7bd94d38d888e056e19e831e4ffa33d2a3ccc2c635b185473e2e52e46840bea0969710a590d6ac43bca4cb2f22ec81824539d596b4c73cba1ff11d72ae88b43f12eddb5670fc8b5b5e1e860ff4538bd44ee9344fc18f8e0506a495441ab24f754509187662682f2af02ca7516684673a31755f02f316f90c06e37f17a460cf25667f44d3c608543515396023da8be051507ad769363b13b53a7ec070ce2d9e3c8260a4f9e387060b0b0e69511af6cd37af7338feb9472c5b6355a44b206d6acc96194b9875666ede49243925c852e5eceef608a9db4c4554ae26b71de4c3126f661c99c845cb8d069248587e12cde9408c42183ac71e65c6a262d0403cfcbda66286086a2a9a96d065cd0cf9cbd58a0eeaa1ed711c20811cadff025a147f5f9e15dbe75aa997c2e422be410c1fab28439b146877ab2920c0ef0b83b408c6ef2823a1f215cd359fe2fac3d796b9ead0495ba439da844d2f31b710ef36f9fe0e5d3dc045b06893ef61c4cafcbce9e41cfcf5356a2c744eb52f8a2f73ecf7429c160dc7520b854713e194eff98702df9854b4688068175493b242f22a63172a65a24ceb6379df5145e491cac1133442a40d1e124bb1dc03f28c24f06fbe33919d99d717a99601fe4b1c693b78a8669ce6fb082554d8ec4244e8dfb06dbea836485167a98580f494d3f3dc5f993534d0aa36d24e0d025686500e1345aebe190b080bdd10a9b9e2397f44e00cf3cb17fad0c7f18b3c7c7a3d13aacd9bd69858a774257427f62fd680ce6dd88a98d88c187235bfce5fcf3afe9526ef788f1b6bc95d856013ae10f0880cc06a816248f558dbeb013f82ec30676a1920268d1c6a64791c63247423c2f51c22ffd2089c8f1ed9f8d070974bf6deaab54805aa5ead4db15ecf15bbc2b2930e7ab1558ed95c454ca23d28d37d98d9409794ae5571ec9f1f083d0a796c89a058826acb6410da7ca29775295a0a4c369f3f4859f77c9f18bc3991091168fb60d31f1f388ba90a4a699e7a525a5602d682792acd7061b5ead891c75d1aecd7d30fb430aba5ed86bef035fb084b4637d86ab66cc250886ef29e7e0ade9695f2053a50a9cff8d233bd9471408b04d78a52ddb07aaa01b3fb4cf1899ea310933411b051a7fda3a4d81fa99718ea11bf16db37a0300a78aa0cafbb3940a56ee676abadc7d15ac2a84ebfb7610e62a2caea2d4d190b4adc3dcfba061b16962ac5fc6ea2c2f61fd60668ff66bccbba8c40805108e1edb06ebdc8130eb8a0fcb4cbad22da96166d064d046dde6409c906eeb0ad62f4d67bb7faeb12a77ff6762b9cd309a12e0c50ea27ecb0c274c19570fa480a7b006d45792680339b81c22c8898fb404640d9c391032890f5523648d252ca432b3281fe4039da26d97608ce4a9fbbf5cd1546b1b5105a27863e76973380cad2a56282da92930f420c67217c815f26600f830b576c8e92f1002a9ad210ad1225f280d941d6890acf4257ae240601e17181c5cc6e7fe97cba32affc0dc5521111142466ea2d0d70aaf53c4f339001229f2fcd8d36df0790cf3e5745143349ed30cc0e65480b952bfdf3ce73abc12340cad11110c08b65e51dd6e78f70e26bff7b8ee1eb6cdddacc71bc02a1a1ecd901f71ef86b69190650b8c810a0bfc05d09fb3fdcfc8ab979bca7d0adaa6d7ace44554953289ab272bb19bc222a0022311956b7bd99c84a1e79213536073dd9c1d8d1b868cad0769122e6223d0a6c399f08931cacabe0de9695f2f95b483710ea9f1808a86f56f41d9acf520567dad084dea59bea4ba2f52de0d784b3ece1d67af509617a3d405bb1f8eb830a13e6a6c8b97c184aff034e76c964ac29d9cac0acde978d14e1731a7b8b8c93da1f1ad10f0923462abc4d9e96814be35d134d23ed85ec6f9219eb62cef64d28c6f46cf20f9ab69aa2198eaf23c3595fbc202eec44d819ea6e9ffa6d53e6ec7f1289d200e0003b099658cb02f0011479ee531657d75cd5ca55cb24b713fa0e9e06f2e7e96007d4459aabb01fb653a9b2ce20c31161607efece304e8661e26e233c87df2800ead95cfd177139b49ba6609425fe02e00e4b65354453388c4cba60d3b4fffd505f52d82e2d291af171ef68a955a693f2e4ff9d86b056422fd731d909e2368953542ea272f5c5784bad2023dfbf1445ea480429bcdac2e951a80789690bea425db24b1fa41a9d662b8330f3595df77b199e16f6143f970e53895d218159ebdbadfc9a0a7e74ec30736490b10c5541fb0df34dc73612248b72533b4c99b93b4cfc543ca406b2e54d4e125c7678a53fcf24a45c300bd8afa9597947d590b3a2463b06b066bc5ac436f8c96a053ce8ff391d56b39e6c7750efa2cc93a8405fe2f27002381716b11c50a485f3b294ad23aa852e105b7deb87dd3b38edb58bb810e67a6a255a0d828795f06446ac8d0386c4e1293830c7ec5358cab14913ddc8d6c10156c89e96230d1144ac8514f39d362d9271dae26779370413e01ee0a78f08596d3f96df8878648fd843d3a981790fc12eb553338d85d80cc68d755b1f36087a0bb1853402c34110c2b287aa707977d26b7e42bcd46ad0e530b27bd21d621d2961ccde5042452667feea61e0ed49a83d130631a9fea963865f827f691477a56f6cb63a0a62e9c0c16f6c0a82af011921de9de29f8d40e24d08c4c2a9b1cdb8106c207612096a70618f92c056aa79dc8cf8994b032d55b86b01712210abc68929a82df1503644a35b40870a6dae630e899716ff7521ace91eaf0c8fd63f554f2e0505a2f8b6c44ea6621b09f8b78b1ae8cacb9e72b649ecc8b1146e5dd7738246439ef10460a2f83ff732c8b6ad20b1e913fcb163e06d2d28879eee2a41b651170e4fa52724272238b4276b912b39da6cb3e8cd5b0edf616e9ccb0a0d79f158f90326ab2bb61d7b22409f57ccc043dff2a9550b0440bd8ef3143a40a212ab8e9b24d073c9570ba3e3a4c195c88895278701cd4792c93e284c539c885dce2e5635060c640a516bf27097dc7d5cc28b3b27920ce048048715eaff8dcd1c399f284c71c0e5d96cea6bb8f262962a5c0961705c20a72fe07e95bff425141ebc181263b044e8842a58e60c166c4e20347ff41c1652c1e6687d79c48534ad82b4858dfdc03656368d9a397cc4db1c8d9ec18b729981e55f91aaf7583d7f319cfb46124b9f9f2ac66303ad1443e57d2590d14b1138077d25b71d440745d0cf5014be594935b0ef19bdfb097300292db51cfc19b0c34e512999179e20c92365e025cff8fa88c2fe1eb0fd2fb4c789435b4d442c84e2c726448d6032f493c373e28ccdfa8c0a557eb83891ad5fea9cc43a90a23b5b85c4575803d70ad9c8a315eed453294700b39c3cba8aafdb32ae124374649184e4f5a43ac7c24354f404b086e435a3b72d4f0a43e122ba9e3616f32d582025af7d4b07615919fa4874ac908994f5c69143ae79fa5544528bae93f20a5fcd73e7ebcff8aa243f263dec9b9a6655a4bf2966e9bc7bed19340a0d0a4c50460f3ccb662465ad90948aaf70a1062df26d052d1d5dafd9f622b6ff2bbbcd923a9f2faff050d94c2b91d12b5a80f69a776d560c572ea71efc2d9bfad6ee69880eae24530311a05e9362e82b767ba16b4f08dcc456dfb7fd183768fa8a1eb409bc8a07c7d0259084329b9339ba9524c711b16bcc27a02ff85870cbda464e11c0722f09ee07ad60c389596d6943949bdb4f3906e17b6b518bbe0e4dfc31c89ec02a510a9d2cae64191bc2fc8a4d4e102994965a99263cbe500d1c396ef8adcbdcfbb6567db6f9907053bf1ea2a2b2504f8ab436c81a9fafd2143ff3f6671751f7cc93a87e2293100834348d58c7e6bd2f897b94fcd17c8757b681394fdfa41947b66b51e0c0212f7c812983dd3ed1db1dd69afd5b54f65f65da2229934d2852b734fa2bd1d070d93e919bbfb33a8dadeef64ced4ce48629ef496aec30cac8efc71a0fcc7d7fa9793d876e3d32699c40ec8fb153b86ca2fefb0f6e8f2844432ecffb622806bafba11c8d5c4c3cd586c1bd9f51ee85285278e0e55ec411635b0ac160228ec782ca7c08f8fb217fbcc695694604500a9ba557c60175640f1e50cabf1d0bcfdf69ae9ab7d609699d5c2ea61929640e0bb91b11057471995e7bc07740f91223f31db8d1445d04727e4cccb28e854143e80b1aaabb50d03ca28afdfb2718803e1ed8cd683dbca11572f73779c76ec07aeba2e766f7330055a3ca0783ea726d4e92bd3ec5580f0b2426151b827a31cbcd34f11a3a1cc4f722c71ac14dc5bf623370cd734c8e93ea752b80a4dce4273e215b5031ebbfb466ac21fc0bb6e74e8e5bd10d1b983addee1384d101d24b15c0b71362eca0ca045e5e553726c1950f173a71f4d18fe1e1d8f27641b8a2941fdfd6dfdca27d6c8c680c0d19acd0afe16ec4bcdeeac8e07315d4bf21097cac32a9819e32464b8c54c08866d5a9c7ac4ad3e8179b008ae2469c76241d08642b4211d966f1d76e24b12ddbb44347a23044332886e567262c44f64e877fbc8b1046bbde9decd17141a230807ca6cbc7d647448b0a9fba7c7554321815d685b401d5045e241676846a342e73157c0e3980edd51448c76e6b29750d72eec852f9dafa5d422e6862deb756514962f1a119063109fcca1a794ff7414e2e3fbfac2dc02e4403ac44cf7501c187881b1594923c6451db3e3ea3a447f2f857d162ea3890630cf18b8dd7e68b3c9480340a10918651ec25610789e6c3445f87579971e4dbcb73f8bc65dbbc1e853aee010f2a991abfeb45beab7c0ff52d9d162f143191e886ed8034a36ed97a73f42bde511cd615a8e24402a0f2c3da40f675ebf021b84872a9a77cdbce15e27cd2a87361e252c98bfc7b61b32cda296e2a3951d1ed540f23da6bbf3edd934443a36f217cd8f14b29ca28774b5cf58036bbc9a3189eddccdf205a3c1227c36dcd1a03c301bc25b51b76fb34ab0e87421d83da059f9fafec4f5f688948d8362540421695dd568cbb4d59b05f7787b7c18dd31ddbe878db920d6e4745017b7dc839fa1399429a526b9c8ae57a6ad8c64b2d1afa60f1ea98027f08292708b42055107a55b801be8b869d715010393f688583beade2bdd78ad6e31042193891a0ea49aa1c787b402a7602e40bc72f21272d34de74781d38d2a630413223175e64ccf8585fabdc68600eed2dae1cbc3559817571563658eacbe4e85b3ab14ecb4aa402929c1080e790e7dc20910e7399237a4154a2efc002b96e28968583d0a12eb6a1782f418e38b792423ec75d4040e4e1afd1dc3abea3b765928e1b3b36493c223b8190d04b8693ce21f22da30cbd4fca269ea5a410a4f07a408c6cd76d45d12dbfb07404dcb3af8e78c40e980da5778e3071ecc5acca1fb6e7a059c93a36028827dbcd53a37f529be0ee64d5ade645af13aad8a900bd9db59d7896ddd373733b3c7a7bb63e63a3321f6af22fd12e10ca55c8c50328f2a7d3e3f040c9780b0bae76f2573f63c63f56bcee966ef09051a2d2c19376dcf34397acd375e3e2750d889337603a00193c4b2034e01cc91dc8ade099c66b7369181c343bbab94678264fd6367deeb4a54d7f835ecfe74160d625928acd3f601e42550608418b77303349ba08cc77e7c6287658c8d998b223307f3543efc275e306d95593803f00638c6ef52373dc6e5dd6715e3db3271c663bd5e0941dac38e0c4a08b81f4cb8e41d65682feecc9bed899ddd881f6a2ce95c149dd9b8062a578aec0f496cb3857171c7428af810ef65c513457924903dcdd4f6350b99e99cd111dab5204c9000a024574391647e473952fd9d0a98ca767868c24a55f99490c538b521fccaf44dbbf15b6609ea5a727ec1797b1069fdd569801a6374894c36d625a0ce82d09c632b8e14b1bab856d8292d73031647b67c4b3d8d06c6c8580c8ddc2e30d4e4548a6cc01bc53b1bad0c124f3849b90501402e32ead06aa8a7ef409d73133639fea2ac32916f943bd4a851742112bf5650ccf381da1ffb2496dd5eaf8844203cd9899452447013254e2275340be6614a7555685c71412fddb8c61e33c0497705b265296d970e5b9339e45ea620f21c36907d33672643fb68700fb8b1029ae1777d59e4a234f7cde78a18c155ae123126136448e7d478304713dc31ab5ddc1aab9433ce4458ded9911b0b19f153ebdfd2a447edff472db941295c49d97930cb5127db5e22aac49a6e344f83034b235ffae0ee0702dff76df3f3a0f3214751d1f0fbed7a751069135e9512160e022b905f4b725b2b0abe3c360145e1eb1d770a49e0b3509a23be8a2a71bb994b2deefa5f23742b5b15d31522bf4b04ce4a517ef4a2f287991c8463984defc86df7f36dd9c432ffad4af6559b710a57cb9b440b68a33d4e6e8427055b41899e3cd477797667a44ecd7ea9ae9a9139add1359ea06f487f34c8f6c12a47b58251911c0aa1145ebbb656150d83af5993cb5ab3540d769c38f8464690a843f917d86f4f603d54faa68bdd34b6a4d124d5ee9e8c9b9565e003abf7523587d0f46515a077762f820e8d5d7170a51e2ef211413bb71be8ef3e843332167a4556d76690c98d5a51bce7af19b4a84141c6fb48305ab2f23eab20a51c8201005bbdd93cb3a1c1d66c9e22f3e45a205d4b0f9d453effc65629f51c0f26e3d5449efe22cdf4900a3612114d07352db7b170b31e6a9a46f1fb4041bc2fb174c2abc48df4c190de40d67fc6e1b238d914de7336166098398dc96596564ae7c39011d5c501ecd25964b5a5a98432abae801cfb08db03bdeb270536084ee96919692c8907e790a29ba7e6f3054750af3c6dd415010355f3ff7ca489b32132b54a8e5837d25c690d2f4047a4b039f103db98de9f2b5470117585e629df3ecc101eb20bf6b2899970f9f4048315aea148aeb62b568ce4c8ba77c1c7506e79359a43b82e5c5fef2f5036b4b537a0ddb28cfe3c22e07a966256ea2f2f28e37ad1d765905db49d6610c676323380b090da9e4a2de95b88a661d8f8e7650d9cf4e7d19083cec1547435eaf7f26909b0bdcdd19565e70432df944740d954fe16d75c5bccfcd8ae0c0552b3b5d7aa461f1021dd05830f5d60a829fb01b538f8535bcb06db1c3ff274811ded5d0fcfc1c30dfc1c253f341bc7a085c6510c961166dffed70b1f7edd247e05d8c195bb8287c5391a52a0f0c62c28c6e1a0ddfda3bc4167d3d50e3d950c35e79bf859463376eac5770a99f5a02c95c122c8f8a5cfddbf2cc7a7d0b30098b06bc4a78189b123aab2863b920c1cb6f5e03a955205079605b23785707a2f8f2cae3189badc9d7991a21335ed16ad88e86ab3fc02fba5557d373e3c667e7ca3c35fb001f574f7f08f51829e9a9606ee9bebe1626aa314e9c69fa7d771f1c232c1d10f1b50bdc600976dd906297ac005e4f0d8532cec6f1cbac96f861c0ed6fe799922f582c4baf396d455907ce1574193c7419bc536b9b741dea02937d9595e4e1d1bd335eea7b22b2e61f0f2c485ecf909f810efd3731472343d2a624c9140a073a62c48b51c09310cb1e9328a507072f4442f917dd8e1b2d51d282f3e21a0279a24d1a4425e82163c8aec177cad63f4b14913e3b67babaff56966e50a4892a24060ca015e2973e00415ff609f7416fd67ebf427a6e9637ceb0c0cb14f60dec1d19484d96f2b2505f9a220bc0d483da0f4cc0673556eb6514f70da1c448c41f800eb1f5a4ae7c20c6c1b96892c8680386b8c33f938164815a0cdaa2a48143608862472de63b4751b33c31168f6a2c5a63fb3d56937e412012bf1e1ca59dcad956123ce3559321e4c683c1ea0c3417a3fe1ac005c33b4329cda777242820ce71de2d84fce0881cd4108588ee29d9ca9b18b990de8e8ceb3124d62866c7f70874d67175ba66b84dbcf48a7fb648e17b60966cad953aacce33eea5b9b191e6225ade7a7cad51440e961f283feb71b9161e2222024d9365f0413059220bf9e0462b29ac49162de368ad307070f643d129829a0ebc0c6eb8973f988bc2e4f46502b42ad6a3b5d5bc8df430b4704302d21577b220d506499c67a350d473da4893c8235b446aa0b3a359ab46b249d02afb0d82b2486b533d481c17b1ee5d4b9513367ebf8a99c73ac708eced757d2f2f66e7968237bbfc7099a31667143aeef884d103802c6f72c9b00a21bf345acff82458e7b77f6198b34bf13cae210c820dbc360459e6bbb027c08a9f76cc968804f747f19ac6bafcead3c7d641b4241d081a47d7a1222f1b0b3824aa31b490cdd5b5021be7075485d9cb51cd66e51336de901ce1af602361e8d708358935d599ac313a3b6b40126b2c0d4d6308253336b898a5a47091c1e41407bcfda61931252f3104462e9cf2a31a9c44148d2b5b1c495823418b01b7b50a72bc62eac1a1079fc94ba40559fdacf5ad50c515573339ad48fdd9a4d4e5366a7da3d6510e5570132ae61b6f8f22623bcf57d54b1dd30cc4c9c242c01a0e223fc7db34145fb93556effed7068f0de880cc4bd6e006e95851a0ebba63cafc2872e86abc7cf79922a8b4e16b47b40da727fa261ad09f85fd6173709dbfda40985265ec82b2bb20cbf6eb52dc382fad744456df046d0783f5f873481f6d4950583bdf970e765956f3b88490641d104a66fa81fe86b76cfa4771aaf67505271f9fdbd923866dfe0d9e65f83d538c6cb3a8335fa39d12d216c0eb0bf8c9b07b3e6bca68201a7774a0385554144bba495045a3c58015fb94ccbd6d7903c35443c8333d69695e5cd525f3b2ea00a9d325819e1324fe2900dc9f89305869643904c591ae9023f5d6393c5076a8d2394b35b342493410881b4ca02e34180c2333b0537ea7c7a9f95f2bcb161b6599fafdcb690c84f8d623fb1991eea022d2a78b2ae8f80882c03a985e4dcf55e51af04035887f9dab221c8849cf631ce3818f81aebd9a54356ee2284bce6a614f8415867d312997337e54fcebe093e1a02ff6662347c1c6e63e1960570eb543cb97855930f37fadca0be24de4fb988b954f403e9aff24249d81d21463aaa86ec48dfcab564243c19e6b36b8722e094bb27a1484aa37efbf9114266d039daf5a98349e18664c4c99fc70337e7fb06220076431e67e4bc6dcaf6cd540468b745adf5ce2d9f96ba5cedcdf8868ab8aaa2eb202111545ad10bdb20855225614adbae80aa22aa4224c3511955370a56243d6d51a38fb7a194290964a582ed902290b255a26ddb2044b125a2865b1a465a42c957a166c89164abc5ab534caba65d5d9551ad910213ff5f1d95a0a8c9c89b1bfe366e6876c21ae201fa8fad2bd2d98224c07b50781080ffe29cbcacc6ff7e4614c72e01890ca7a873ab9aaf0ab89eaebaaf0ebad12dd959176f3f5e0df4dcaa620948b04bcb7c65b41730e83c394cca63ba3a7ac96c5b65107104039517eabc2cbf8c529c9a2cd176e82dc857c3ff4b8ac53141bb1a9fc1427ff4a60d6f0fa573f0867d74d4a75626abf691305248caa8b0790172d7683d8a315e8721357d1e3a03ffac10191f4cf7f99e8207cf46a8b8a0eaf27a48ef7e70388d5f86c0f62fa878fe885198699aa7ec628c20f98852ae669f3fe82ed1e5d93c06c70309fae5710d3826861095a20b5d518181e17b5e43b4bd2914337d3212faf90110539a405a3fab62da204b5cf1229d4b371194c51569029372e08503d15f4fc4a93c0159a4404316eb7a95d145b510137728f453e89d4ca54ff5f45902ff87c6167cde15149babea6cabf950ddc98205a01fe10cbe812edd177e48b34b5703ffe7221c735fcf5c1c136f9c2f6f3bd8dcbcd037bc952beb8dfa87226f852be40147ccc88e56a4a69f22bcf53878d9a75d197f20551f3237d4b8eaa209fd3e26fad57448deee4219049f922cdc8e0a7a560a5b17c71ffcfd2679b1a7f90b7dc6f8541d4f2c39be35ce7e44fb0e2ec3ebce1089d877e5e51fd18aedb0db297d7d74762f736822585c2c08b2960720bf7762248b6749f2ee4f95609ee2bb0c2df4a055d24b050c06c9186b713b4fc0cd802758e0e703feda4ff75d140171adc0f6a387b0f94377f0a4afbee6786deaf83de4443a3ddaa6d03ba1689c9041666b6f2a2320d9d0f1a789f34d8dd2d71fdddbe84eb74bba5bcaecb2ffd5a879b0cbd5eb38b2bf5c5d88dc685da05d6d1c3e2d817fd6a40ea08167122f160ff8946dd9cd27afd0e2c7da6a714bb6b6cd75ad289f1fdb18125d23b7169c650b6b4d21fe865445fc120c27e47e186e68d01a4d5f17f3fa1377b7357bc10314c7ccb927135dc015290d159bf73761c7b0ad28c5eba1975bc6085ddd7b9e2fc758ea1142f3a583c894830c627402f4e39a29759d6bc54d426cabf990dd44b7edd0a28a80ebab84f797904201dc5ac914580c1a09b6f1f11332d81911eefc07258e0ca0c83f784c0d3cd7ab1cc16e858298759bc7837cfa43120227ba7122f09188f65d4b76857624f7022c647a81dfc6d351b403e05b7aca3f7f646719a937bf11f6d985662e51bc22b7da00c5e985fb0291dd6dc39abd3be1adf47aa54f12147549a4c6e3068e1c0090e47a6d8ec41ec17f113bd976a24f2d647c076d09f68815c42e3484f68d2b5ab65a73e4ef6bee198e634176d9b28733c1272d4a740178ed0d232207af4e1d2f93a7ea4a9f12d3803c2fe062d79d83d5211475e28d85cfe3cda11fb4eaafb124e871b319eff69b12d5aaf88c4d9bb6315632ca78a4575f2b48bba79beb6b13e291e63fba482849c50b00544afae023313864e62fc4ea908d9583b7908f163e54bd19e0f4fa7a14cb3e9a2d18b7961c11c01f3a25ff929cfa7212ceb11364e3f0acabc063b58976e7562831e089e30427674fa9ccbc867b7e133d28e2f134424c85ed867ecf2c7f3ff2cf90dabeda65b55b76b07db752b6267e03eecd6462c33c4b8029fbf6215821a7abd4de2204f5a3eaa117a2ad7b1ea0e79831bc25778a5197f7942d5ff2671fca740f95013ceabe50b97f4538ab6cc3bcdd053c3f1f3e9993accb273a54ce6fc170d97b84573035e69cd12e4734a7a52b15675eac1508da21e041a8cd5bd24bed9ee9e15dc94d0817d6491e4fbc6fa5c903845ddfdddc2f011a3e52b15c67cbe464691d6af13bc28f9f8250300666e68098ad8eb15aa7d511750532cde422918b3512e172b019bcc0a43bfb2c9b33c051a3bddc21b09f0e5ea3fc9f4d2de8c9f6902c2b85001d13c48fbefcdb5e1a6b1143c11fe8e122e7a1a4aaa57af09fdb336157c6e2309e8133684df8d12349d6578d3dd2fc52ab190a0a1dac99939ed1134d2249d73993903f64d7310271893b2c226eecee57e3f62549464ed5aa5071089cc924d12dab0d69e85b4adcfc0a49ce2b592de8e15fc878adc01ca3d68687398e4423a1a38386cd6837dcbc019eaa8ce61e600487f5af55186b8c8fa00111500b400d24600a502429912e9f32cb402c35c40e5b5f2c5feea08602a70fce931ec6106cfe334349d62848bc87248016a98fedae2a2b0214c53ac16340a30da41b1caa385aacd499ece836ceb7debe8b00936f89a148edb2c31640e7c63d59098b88dadfc9203614eda59189451e31641016e5871c39b737d0c03b62ac2df2261e6f9e1c798f0e5904126b9b3c4924d54f483bd195c65a2255c5deb4bd741a6b7332fe1c3de73fb8ea8b94080a8d665a2a4b38814df9f1e2f456916adf7b0358463f778c06f3d658b0ddc2bbafe40a681766c0f2a7fbae695f30ef2dd3edcdcb9bc8271865ff5f38bede45aa47998279ca619a5010a09250ffa1b50a30fca8c4c1cb4bb60beb325c68a604839f847387d3b4a0099b129fc7c9b8d87291703cfa0c196c41f6be95cfc9278558d5542e80cded0c9912f236837061faf936ed9a98c7ca772e53a109697e68993bea9df3cab5c834120bff104713cfc40925eacca76d1a08ff0bdcc7e9f0997961a3e07f994252296df7d140db8ec92fdbb0d83e6cbea5c638d2558ffad0e246354c7477f208f72f5406d51f55e90f5a6cc8aa8e46dcb030a93b3036beddd7da38b3f4b95f0ab625a351057bbcaadd20af0e10d5804426cda732971ac7a27cacaef8863f9d45c50ff9c7eb3243677480954a8abb48f2b5754fdb8950276c3f866bb4b014844c389b55165146e34425e822b99fbfe22c7701db0a0b39efce81ef768c404614a2436eee8fe0a5c7e923e1bdfdfbfc058013325ac5ef226a29e90095ee3e5705a7dda75aba1152ecb0668588a848a03d60e83ede1edf660b2b9d5ae2415d5e1c038b76d41dfd670ba8bf82903f0d058894d0f22a7a6a49d090bb2f7e17b42fff0f63fccbd1423f14c454faaf3056a5cd1edc6747525285f8ffd6b79e008bbe0d3cea8d6e49fdd247e3fb62212fca51d34aabe15c3f2591adbe527b4b7c4f09763c5adc4513ae60bc1c81ad62c4945102b3516984356f0d9865713f20a6b40e4b95714d05fbcc8a7ff9c0ebe197503545fd13cb153d722682392d3c6adb898899b27b5b7fb48c486df23768d69f8f916b709b48045e0b667261def336e396e93fa79bd44e6c0360a7f688111d4ed98cc25d639dc68045da7f72e14fb81fbbe73b725e61e9304ee3b1b364f3c88c848ee29bb67cda3f46004cb32f2751d506290301e6495b33e6f2031f2c36625a6635f776304b99327b36c94db1219dd89c0103988582d14acdbb74419a83bc978e9c19e3aa27d734ae54371e3b2f10658a2cfdd85824a24bb45d36e8001faaabf68d3e684dc1d6d2f94f0b5172a6e5e639ebd508cc990f3c789ca848f8718a961479fea8c92dd6602014f76841ad81160180c3430f3430d62611c46a388de627a918c7e34beda7358b924fe8296b9d56c1417642b833ab167a6b64e7782ea0a27af91c5333be03fca0e0173948aa3bce213271388908aaa2f3d4cd75e189a89821f15d80be2c0d9d3cd78404980eea0ee43ea80785f405c71204bf131975a305c247e4edfe23301baadcbb18e032d525d9d6f83756052675d7f84e81facbd2dc09076811d2ba09d9c7b52c89e3076aa98d5e8ee57da1488eded107d2c4c79760875efb7a2bdf57400da9b40f5b04aa29c72f0f5dfe65fdfc9fdc0459b6e96271b0f4a7a9ba81f79ee655266b99205c75c9d5f08ab54128264bb0f82f638b6409095b1d51448f964c566928a350cd4fad14c5753020c567ca22aafc46f3d23934e3bb66a89b35fb83c7d6d0f8b7dd4755a05817560ed248657a6e65a46ed58ff99dbc54e689e5f6cc4a53f2da44973557628210f44c2197fa80ff472e1009573bcd7b117498c5daf3e6fe77fc21921038869d9619841f51cff8fbdd897b541d9824a11425a6b9182624403f5151ab5583821e763c9ed21896aed18e0133bef87ac8b5b81182d5b2ec51e19f0a7cf22283db6d3668e0005d5b62c77ca9a8205cb5be9160a391de6e71d79f94e3ec978522c9139eed23b164c4633980b40bade10479cf8676ff79ade2be3536a9466a6bf56a3a524af4f8a402bfb8dd7a677ecdd84643bdd8723c51de7a916f57ce25c59cddb91a61b36334060b8e513c619a03746801ce3433dbadb045ca64aab542300bcb727e0291c25f3031e80ee79ef74ce0bc1389f7b7bbbe7199e83281979eb2330c11d25fecc71d4c2a3235083a356952f9030f084bbe7c1fb19d19ac3517052c30378bc95a819b8e7d5852e3a7b4e10f02483acfa9dcc531d45f837c43defe8f77aa0b59f87063c87a313a1349389ed9f3f9dd772d1bf36952809a8055170b3597b189201cd1f6908b8067805e8d5d6baa7c02bd77e9721f2366f5e6f0e83b2a4c7fadba5a5f54ec811e7730ff47eac670837aff8a278eac0d3168223d246ef77150624730553665a32f04ab525082ca99b70b66fa7dc785b6864deeb1fbf1e1a6e24201d68748f65cf6f32896a3f38cf008c859e1ab880949804015511f1162128da001c077ceada219beaaf6d0731461bfa95f40759965b4be660eac37cc5c14c8c96cf8fd6ff9fe916561dc1f3615963003a769a99ba4d5a2f3c097d2014ad3a81cfc30299ec4ca20ee99a3468e23c1191d366549992163dc73b19df5ea61492d921352dc6cfd31952242c308d565c0377fc493fced90c81903c6c46a1987f4521243e88f9874e40b61b205630aa4cb6682e994eaa7bb126d0ff4ea57f6bdfff4ef5e804703b8bd895a119cf136c0f5f0da73215ec2c4868ff86c32bbc81c84c70429d5039eec403c08bd106b2bea84e6fa90a349052bfa3c05b7e52d5d0b305be2c601b0d48915c3656b6cccc8044635dfbbe5c2071a362416ba1193562202a8e68786bd5241b7ef1c48855f217b592d98c7d1309d09444c241b25012cd8e237feef71a9164a064544e3f01f964c814cb871ebb73214428b382542382b6f7dfb1747a3d2cd0589a49d8088d026ef4a2a2688ba12d3d1371a6d9ba5e20f27337c018f6c5478007b1993bafe1938d952c4f8fd16bb31fa2521773dcace24913f21947b810950d7f8f30aa7695642907c39ff65d7a57946622e3ca6085aaf4243eff56a6872b1f648793d6ab07f476fe8810473865134dce8f4272d401a524101a6e9bcd8a7633ac1a3cb7ae3cadba2c27555d125e5722f30d592bbd13ddae25f2215c0a726e78d91254489f035659bfe3f465be402645e90d1e1867a719686fb8f1529b93509f3d7f90f650067c6817bb13edd9a6483ac1ce7377d67b70cf6b4c8177f45056cf218caf7b5e0f12aef8c8a163af7c4064f028e6039d1fce910cbe9049a8fca557daac28e4ae974918cb29b2ab5ae8b8f26a2d42abe865126ca220b1dee7f3d12ea3342e413a8cb6153b8e183c3cbb24703aa0b946dd6349a3ad5e90ea1e04c4ae1f83268e2c3df54f902591001b3e77c5fd56e7bb881db68025a46017ceaf9dfa14f67067618b43c4fa0a71926a93cbcd7cbcf0d198837edad214e44fea1c622b512464e75ced8d5ea5f67512a6a7f1713c335522409c9532cd8e7e33134cb8a96430b0e8268e30af871087c40cfc5124f5fdeb89cdc66e0868868a2764e214ccee63a028c65710958527cd6ddeac22bb0c4eda46749a378f75c142964fb68fa158ec051acb4a22d67bbe4ffe8f8a96887cf2e32c17dd42d675ccb61dae399de15c7fb843c6a103e98d3070a69fdaf7f3fc8b5c215e3cded13b76e06b2f5d667fd484d630901d4018c7fdca61f3d77942fe00523023b8ba92c2a9c1343e5570c18abc46cd1edd35db4f06a06604b8987a3200d309bb059442ff86587204a8d07b8e3e2af2af8beae4a5f839550ffdd89a71864f1390d6894825af5d6aa4be52417ea4bf427a201a52b387e128d28119370bbb7f0fbb7b236c9bbb2a91c9961489ce673949ec252f6d05cee65ef4fbfa07e30a75b680896abd435f699365e094d62e910ec9a48eff198ea73f698fe49f1a65d71bcd9c90ccc7439800b3e06d58e1f3a7e0b415f39f050f22787af155dd25c49cb583007d6de8f13543ac99d8f1d71bd7c9a4b898c14b05c0931e932d75faf47ae0944489d162606e93e020a8c11f3709b59c955609af8230af3c6cc21d06e486f1708ecb1c244cc8dec33bdd8bbe25f415f94dd84e99581f0a74b6ab4d6a517960979dcd040f01ff6401f259c2e4931b7312fc2680774b1fb71d35b5e747cbc56e6ff16dcab4a618e74ebf7cbd697c68d2f52a4bf13ac0191258432005f3ba4b4aedd5f6c00bba58ddc386a2e524ceaed726d98d63ca587a68402631615052f16484357a3cb68106cf7758544e11eeb4e0eeca346587a3acada02e7c50e06e0569c2761c9095ac6a10f0e7cef03314082087454256a4b9eee28aafbdce9c730f8b0bd5076c49a45e6e0ec86c302085bd40e1d9e5a955ae9355fb7b72859195b56e98cad4ce2a82b23d432defe13a68191b0982e8a46b9ddb97893814503d8c6fb896be68df3d57fd6585e53e1de0c63ed85d0c48af89575d68fcf4360b617917bd72eadcde755db93f400702bab1cb715fbb90abfa9b51ed9ada586fb4fcef33d727479aaca437fb8a77e5a00c6d5396c817226c7151d25f70a0f6fb7fe98632eb33ec135769ac7a0fb594901fe72cea0ffa8b604812c554d3f0157b0ae4c01e40fe82354702f2cade9865ec6e5f9e8b9a08cfac6967a24931a194e82455781999cde1ff4e41884881c1682b67d117eadc41b155715be6da1800066f30fdb6da4a3bd85ad3b03d7d2c13a32ef19f540c893045fdba5a5bc44b55a1a38a156132bc1dc20751b7a0b2cd5dd94448690bab39d71c56b3a4bb492c8355f5e2a57c1e962b0e133d2ab29ecc0ccf3e9d760e8882ea2ce7981dec8b9d50b7ed0f3cf45699a01978ec18682004bd1b51e11a9ac2f34ec8a9de5a991c10d388bd023885d4259d969011db8a352a2766ea07fd272fdfc2e72e8001eb026eb8dbfc42971925e074e26e6385b7e1ce9dc2864cb7348da204e70da1e2c94b4141bb4a211a12f02b44fc4f0d0fb40e650a9ab38d133d71295a9996c33e03c20ba4c7372b19e8029f4b25f0af54c8b80162118e2db5140b79dee4f859d940d7e849dc5ad8e9bfb40a47faf567c318443ab999a096c879e5f594327f6f5b499300930e9d233f82bda2f89bddf3ea5a5d5203290a8abae3e2fc02bf94a60eccdfa803f8dcad4cf0eac8eca70740e7fe4c509245d22d87591cd23121c84405c6eecfb47455d93af4c02fc7757f86106e489b735124474fdb67beaebb685ec4126685337e9437a9fc99e6562783f067d40af4d6436fc3a9a566e1a997488c20adbbd284fe014f80c8968f732f479c94443ba0e9bc1f4e5cb142c3754a835d5e6ba3dd54c1337d83d88a0f9938c96b64b50d9fa5b7b86081aa8b04a66a166dfdc946ab6ffcdd00dffba5300263b19e6e4133b8428a4472da6ca4150f822e4a157ac79233e4ded599b286f3559234e4d0e22f7efadb09426c78ab8a175afe1afde9884ec6da58cd86d6e9db8ae52a9a131e6df63fbe51351a0f19034a2adaec30fcf2650503c4b4c521970d87198b9ef5a04d0f2cdc637d17d1123c4fad2239dc281cfd3f2a0ccdd94431a810d0f80ccd6dfa3e8555a9917badacb5a020071fbd4598101fc23bd0597ba4fe13fa13f708fe241fc387971207f1ed4d2a0ea88f68a5f452f5518eb124ea08f73a002f0e878e06052cb6d1450442de903c9b5f28480bd5a1893f29344667c2c315bd26a22100cea7b434f3dd779876e2a3fb68d8041afd7d690e2ffad8464e953b01fa0b0250b1fb177c472fb50319dc32cc2f0149727071b17de60c7e0af5d8851c9d879d8de83af0a247e5fcfdf2e00a7e33cfc573d21affa851c2cd7d5291d56c7a1aa91562fdf6dfb750932b7448cd5a777729797cc07670784789569a84cd861d387381cab660774543c2e97d546029142b7c6bc39ed6e8352fd8edeb8cf8bce91582f75fd6729ab5d841b44f029c5cf25ad71038299106942e87bf752d97caa8a194ae89b56b28ec4aa400a5dbe58f5dcbb5326b28a96b62ef1a2a5b6baf511f9da9fc8c20cdbb66c6eb63f0945d776037e5670ff82599bc5c8eaac8533d5eab5ea90fe4ae76ce7c2cbc089eb698adddb6f000065e29784d0c1d71d0a3e68d690f10f2d9f21a9dc6b655bbce8bdf84be6bebd593e4daaf596714ddfb27b030d3f7911f4694379f79b1dda7f6327e9f1e4bd6f3479afd8ba690ab23106e17a282daf556ee1e12dde2934d13146e4e29924e086bf32ef9c88a846d75e1562ab9fb71a9601222e0271b7fd3cd4e73e61c5f4e680493886bf873aee90f0acf2a0c3a6492ea0ddfeccffd0b76c4924b5bbbded6407d37a28f00901482e5d8181d9a128204ccbb2fab41be7168486d798ee6dc46d78039a6c192bf71e21670a6e4ccd3aaa979816245a8f3744f3938cd2bd6e0d7bbe6fd964e1ee737cce11169f46f726b6f1f04b8df62dbd956f464faf4fe5279526fdd6e8347c9f258f22d40677c56b00f9890b75fffb73605237bab824a64e851fd41731f33922d0efffda0b7c95e814df6e88aebe8cc264710b28236dd69c81429dac2846ca993938d69d64cc7f5499726237bcaa9b0d4e41b9e1d0d81139432e1851d046b4aa69699515bce6af2d177eb2fae80c6f0efd74a9205b2603ea0625eec6d653bd20cabb216786bd6bca5c14d85711d773cbfe3699778652b039528a2c59767b13f9f056bf4ef11581ab991d2f15e464587e49c142ea4a0ea569d5ca01789bfe98925cd79600c505f97662708ad5908b2150448f28215dfa299f688aaa0c56c01160b86eb66b7f82113dba8b234bac1c9fb2ba109f223701b139099375e199a6de0cc5e956905ff31498a0966661ba98c6636386b8fd62a2f08c56491b1b9f2fea09ab8f8358a29c2ed72e4bc039ccc323738dbc57502030437e6b7bd85a8d44d44fde3381b21a9720f02f00a0835c2d655e71c06b039fa487ff765eeef8885e8ee767f9a5b0bff2dbfca3ec05469e9adfcc5ece7b480da20ef6143600754a7fc19567c1f1de50d552d0d65497c7d8a6f6c939e0516a827b28c7e8cf21471cd64429355d8c72d2c14fadfd5a57d9bc5f1f4b7b1397f09cae4bb11527f77fb8e28abfede38f515331025659038f3224ca6b43a1c09a5ee9c87979db64866b329dbfedaa9413f3094f8d36a176d4e3028fdc1e23c99e961bfbfa83e1354cab4f2141a4ec230ca6873610132cc37e6a7bdc5cce4e7383a2d5b35412a7923e65f2df613b141f937b515b2a72baffc94e4123f67fa63b7711f3a877d267bd4c1381dc48334cd11247c1a77f4a19e7c30d4981b042d70944c5b0d85eb4da5fe1e761c910e26be1baf4541536b4be1f82b29245ffd776b405598983ece9c5d4b8a115ab53a41222b249c2a070d84ced79eaec9734bfb30c5698c848ebe9b33d7d1c2b21dc5692299a32900cbb8d9d42f2df399f5ce028914c22728338fee36e78d730da9be6b06db5bceecba52735a6d7ed160d00cef1cbeb6249ecae6bc7e59ab5b974d5e8fcbd04404104c20b41f2104121206017e141edc00dbfb855de1c82b9bcf0963e4833e2ce3fb434f4bde7d5b496b38d013d625bacbe94b326e7fd1bfd1528dcd5248ae32ae4979dd2b8c97f04c4738972d6bf046f3dfdb4f006a780e3b5c4e86b7de6ab807a62982616f2cfc8f0f2e4bb16dcb3989406a41c8cbbf39fff13e10812d294f3a936ee31004f1020e3ed0d5f46484295bd04335ac9e011b08ad9401c428bb45b1979d3d18adcb4678e20f561d6a3ba09301610eb0697bc44138dd4c8f9d9e0df91be4e3044e0040bf0a0643523d4524d5662ea894fe9ccc0f9a2a7e4d12cfdbaced5854cae14c47e9a9ab48aade4f3c711baf36e6408e336775f326da8e0002c37c0a3ea5d066c4370e2155797dcdebd5df5d9106deceb16ffbf555d40e9e081c1109b2bd2ed3767330442b3e9e3ab0e473da3b23c555a2839a0482e662f501c277be3b14333e3eb319e2aca36bc6e16cf4db9d894d30950dae4fcd5f06a555a1dedf54ac723595788c2941867c7fd7c05c209dc3b76769f0fbe4ebeb2355307269a5c5274d5071fdf116d8e38191ecba018df863cc127e4b93fc1d27170ea313518449a98ee9d0e239b7926f8921e25f74e2c560507c27db738d38d253d5ab91cef5c8362d351e4cbea6668cd4b0f941927c0e391d1b3139283d0e4a152be7264f7834283734bec585506ea0741df52a9746ba91fbf063059fd3fa75cc17005f94ae67458ebca61e9671fd741c0105ba3109e17a548e204c8178a62e6dea67de1678d892b100f874cfce132e457ca31e2eb95cd41377873d0828bc4f01c9717440aba7ba1d7b84a19648270395ce77aa6b8c3173ec8f15ff10ddd2912763b8691e5e9b3c39ba90f2434c1f8dcf3df4e06767a3eebb983e6619e54777a60b95d7a5cf2d8d1cd41b79d3678237d1f9c07649437b616cdd365fd62c1d1e1104a32feaab490f42ec234a16eb342af04d6b5ffe8d52b5a02922edd55423f79e31f0c136980e079a2dc8425c24c84e24e7f0f3281cb59f4d212a5be7338e7e1dc2b0e422e519c5f49ec46d82a62b22a413fe75abf48baea241a96e0c8890978e586c11bce85ae44db78c6b78a6290667a515a08285c5cc9c922f1b2bd1e4c836071fac897ff4ca6d55ca447f1c453fd9267214bf77f5a7583b9d38f6a3273818a8c2e492717035cd55292dfb70d139fb9f5c83b41cf4900f11d4171dd34a82a379d04d8d355adbd1523a50408b0a0cb580d8be01647ba3f1f64164b6d0a181ea5bbcb57361c3301de96b96397a2c2c30ac1e41109ee2dbcce7c0c03c09f82fcf44b57b44c439ced851acfa9598bfc1fa9abc5e5c74bab82730eb3e8f69c15399c88a8c541639f09c13910e5fba36a1ba8bcf089587066a9d3653e8859c85c9845162d7b3cf6876bd808027369111928dd22de4592e230da4b8ce8d4624eb6d043fa19d2c91da92c442c6f232352e490e423de5b379c2ce7cc22ac5cf93d6991ec9bd995a3900f78766ebd9301acf84296d15b278e41465b4174f79d3e96fc3315353715ae1924105a597c389664750f52e98ca9a62d3d94e0017ce36c233632fcb73a57ecab49176e9684568b0b4139d251676924228db7a4559605e7ad83cff495583c2dd360b7284d84644b0cd81bd1ddd8d9064a9fd70746e782e730d8c86adab04aa5ed6f6085c282f88640bc5b429f21000371fa336fff832112eba4f0d47206009e8ac51c626c6c6f518e137e4650ffea8e2ab29f32503f52f185c0e9887b6b7aedaeb68f0b1113bb5eb22fef30d7cf714d78ec601e94aa82498e2b1a6a0f0e22da6503c70896011e26e446619c05e3946bd4c16d14d57932dd105fe02f98d06c4cc4ae858acbf42350c69ce26229d58e3de0ee9ca69d22cd0a0e54fa6ca3fff1a3fb6484798e2f3458a12b78720fde346748b6e931d76e2320b1c83f5b85b61fd5b3a9bc2c97868f72b9e2945b2862aa95feaba75cf2f51857982e9ef5fbc86699a76583042f93d5d5d7a393eef5f458b06ab418f77760831cf8c9767eb8ce7b0648096e8c2c4eaed8e516714014a3581909be9133053ce0087860f87b2ff88686124002351fa2f4273b76d51ea990a58aceb3ca66f77bab7eab87c7ec85ebf54c561340a3532406fd91327c2854a8ed1048a94636c41c93c154c3a585f92a91ec2d4140bb61eda97a2663632e38547a372a79b8e0888c96e3e425998d9663b4db0d31807844d16c394a9e8a9daf1baa583a862400ff84c24861f41027a39075c6dbb90300523e9e3a1a237eee383db8b263adeea0e2ab0c1fcea39583d86f754c8a0476cf9899ebcb8086ec0c403e5cbc513427c52cfecb146effdb2836205d72893e1d52c0f254f000598a8f33b0a28e80892f80431ee239e2a3d296d55f4076511e838b01eca17e73bda47ea761b8e04d03466c609a5a4b1d23a9b07800aeaf4bb8b0d2c74e828423eb8a7d1a553047e5906eaa99e55e6efe89ec6007e899c711e3dd7a727c354d76225e4dd83abb9c76902cce71a26565874378fbe411300489b0b134b314df7c314885de89f71b9867c497e149f530a6e552d43d11f40acae6baa6cb04131c004ae3cd13c42822df48ad3a28c9e6100af49fc9ae715662eff6e11dc1e183da76bd9ec0190ec5818885ef42cff5dc02459a957541c558e96d4c0507c97a4ba121e7c4f980b41559bbc07a8bd89e7f62e89fce559ae03fab04a6864e3bba422c602cfeb479a90e359765d13c4c37a9e08f306cc128de9300279e589dbda1d0bfc5024db15d70ee399d23191e4dbf12e35e660a4577b009017b4f8a0ef18c883ebf500f4908c49aaf28241f17e79480f40df78a79b0720ac014d8b4b04d403002f38fc3c76d1b84fc7e209305e6e257e7a00d80b76cc3ce6b4ae2ea86510ebffc3171bf3b49556770ae3794b4dd15b7f550ccde5615eb5a588566360e4c81acb1ccb751645e06d30d8cbc35cfd8222001561576f996e442a1a88931584d462cc7ff6c0a2b334e6e221e32232dcc285781aa9cc403d6c89573d5f42d7d8a841ea00f49fb8b40c42e880dcbc0ec981f5d800f047d9a4056f21f8e005f05373e78ad424a8baf09cc2b7cfdc34c5138ea580f5fa0d628aba1e5c009b1a075762d8453b6031050edeab0e6828ec61329ae033b81bba36f208225206f31fc3e100c84668c54649a4c7628ca9f010692f3d6363b1ddd87088899b218072697ad7dbfb8408f058c3478c73b288005b509cbc2143d9dd802bba787e15d6e3567d9251ca25d457cd2f2ef5622fbfbc60764d3fd9271384e8d43646675ac633bed94d0c9fb77090d9b0d429fc905118d4b1e440938b102714905d476cb78cdbd23d8745e746749d4834acf218c16b42cab5129eef3734570d35f73760308c2bfdc5fa7ecd3b187614c57d751b583092ccef0737a5300393fcabd375bf9c6b040bca8f39d07ae260ab9a87394097ea9bfab342069b735f0016b6aa72d3717c934cea3ae03056730b0ffe7441572a599e2c2c0823df53876168539860fced00049e60bbb65128b71e541a8e30fddb06468bf46ef0be1a572ff9c4b4d8b5dc3afed3af4286fe92d2c18fe20605328e0f4fb7337395348ee30f62ce53ea0a33c9a037bef66e65ab80811e2a62119f8ba239cccd615af4799ad9562888b9bc9e942e4a5f187b268be660690e013677746c18049a3e13a35a309a554d07b941e8bd21df8c6377325e617346ddebd0473acf72e4469eabeb344a0bdc3a2ca445c09957b0f538f05100240ce1004c4390c3b0e2c11c86c219cf8a094c74143fdb8ba8bcde8f2e892baded2df7965b4a29934c01440ae809f509403f463cc76b9c26497cb978706e820892030e312f0dd30ff4632467d64c9a242f178fc461b2240848c7880ff11aa224485c0dd3df8a384204a1430e0dd36f237e3eb7fb9bad89d3848950d0111d6923872821ea4152c45bce6a3224b473241289363ef490001bc0963fd900c76fd22177b32165d7dc1b5d4ae076043de6a5472ae720c1197d34c3161eb1a56b74083818a510ae1b9193e53fc96628151e9fe5e4228dd4adc8f2db73d5400656c8a97d6a653fb39f2508cad94fcacd4c9bddd09823c10f511996a39083524788d658a9436f867c456b64ca7960d97ef4a646ae280ebda136d927773e7984c69b486fe4a4377276a1d459a275e3466f5c85e3aaf932ed239bd4ab459815ee290638bbd6b4f5aadb763cbda0bbbbbb4f36c0dd353e2b709d9ad066706a42a30115a24d5a482ad1109d208c098ecb26a93102942d6919cffea72a905edd137c64a024b71072fbc82027dfd30b707cbb040ee3d0500bc7a01c3f9c39ac4cb03c5d7c802b93daa43271150d4d398c4339ac4c8064ca7fa31f905cc5978c42d9bf850cf0eca1ec6fc150440888a0b92fefcb4b048e2fb1ed668f42b3c2bd7cfb2bdce7037cc71edb30ec62d9f760bb7daa3d86611ae51420ffae681fbf7b481f60d65efbc0062266ed7bc1c17e2d5d8a806b09e6b700825d991f7f9b367cc9f1c196a1374a72fc76cd163bc021aa68663ecb5bcf934edb8207f84f57c0e1ccc1c1188410b532676cdc68192031cb2f86c5734b3a3dd085ede3f69109446cf2f6b108eccd7d98e556711902a68f12021c7eae2ba38fef03ccf1fe4beefbd7be6fdbb13898fd7ca97daddf02b6ccfd9a6a1920b128be02e26f7785eb117ffb56eee703ccf7ef075e2062bedf0b0e0e7eb35d8a80e94fac1ef161c111fbf6552df6fb115b4ca2d001cbbb420a9612aaf88144a1dc7d73cfe420f5f77944a3e26fd8628bc29de95ba795da9a5afb0bdbd58d7b7a9bfd6a95544cacb11d723736e7fc2d7a262f08c56aa4af1bff46af87cca20ca110267872ffb5c01432d02403f103f7186374018c4221391992fe5142f6221465dfd15fc0425b8127fb4741892ac660851ff49091ec4ee8287ad04950423b010a5588a17b581fc841a4101f545180281440049462a3b0071196e8244451238aec5fc8fedf1460c800f4428b21a2e8520023060d05eb0339889bb27fe1058266cc28f421081d0a545564bf4ab295c2a5360a33008451a126c183ae60070195886d42f6778e872c32e59c53ca39a59473c6284661963cc88e0092ab874cf6ef85c888c5e0b250261d9ab3010437b8dd12ca56ae7fad033950bd1f516db7e844ab02d060b50d7483dcdfc3c40d25727f4c810e10f81d5c957df7fd3cc8d50c2fbe617cfff7479b96297d7f2c41e3387d7f3c41ab4ceff252034acaa3381e64aaaab8f8fefefe7fbfd7f33e7a9ee7cd78dbdf90ab206e729a0913263739dee679a79aeb9dbcca7b2ffe8567ffda7f138ae562d76ed7dbbe39cd73780edfb66dfbe672b8e566b6f7fe865cc51d21c9835c452299aa376e44212122a2ed4f45e08c6a99307a9ee71b37cff3fcc3f6590a0af6fd70379bcdf33c6a52cc176fd7f3e679be27dbd52e6733b2b1d9b6cea6b499b818bf79318fba185eca3b3783b2f257e5c5b7a71d7159b6eba55cde721b4fd5771751884261dcad9dfa396822f711cdc5d0627cdf1131bc3ec9ed930311e4185e6798ffbcce563b8223030d526e1f1c70216f334a47e09adb0707444062ea3f8e87672fe58313530e8ee4cd6b0ff4623671f73bed9880c40cfeb5db0bef4eedb483795170a0a3a160daa6e2dda0134f0a96b71d3705e56e27de75b94e4fa0badc3e39c041d9eef57c090de3ad18a81800f862005199b97d9af84009c68b3a5c3c1792a9fa2f5e1154c62d20ea80f9db621b6d9c776d64fdde060806d7d36cb0cd9bd7397bef3e8d6f59a1f197b4cdf062669101cb880312f38cbf7893e1c5ecc20577bf1311d8fe06246619f2030d535fd238c86a982d6017fa13200f0a6cdf6472d06b1ccc34cff30c7a4dc3eb3cc3eb2cc3ebdce28538b78e185e68f3e7fd303514cf2b799e814e53e0d06b6a868c36cffbebd97fefb672e42a327149148a49abd66fe9baa18179225e87cbbfb83c11afe3e56f9f743e0a3d90bdf7b47ceac17b59b6271ec0f05ca8b4bdb0f94653fb28047a71c485e72c9e0f79fe6ae17912d3a682e5e67894b0cf81543c0ffa012ac573a29a1be5737864c2caf54d289f9f782720b464fd93e74c8835ea9ba8e7d2275972e542a117a150f64b69941c18cf859c00f55fbc7602849e24d777f17cc85375e68bb29d781ee460fdc8f188b9c80c9e3391a9fa9de74b1cac24cf479e2771c9950779aa7ef5a19e5cffa26cf77a1a12b87d90d0c9ed83835796f9a2b6eb71ef1c0f2f72b0c2009b1cac8fc2840cb08a12036cdf6d1cacefb97233be8486a98f710ed430f54f3ac0a1dbe45adf6d9c882905d532f70b85e4eb9d6870fbd480480ea512b9eee831642ab2f6d8820ed6bfde8b8335cbacdf4fc9f7c3321b65abc501b0c4555312f1944f205729c9fe84ec3f837c157578fb908027fbcf1f57f273420287d226fb9c14932c4cd839ad15b6e89a4b8a42e9923644aea4f1260efa4b96a4d9c9e1ecb1e971d04980dddf8b3a4796acac2ed860c88cdc3e4d0c91df5571092b4b6e6af76ea6abfd46295e7d11388c45f78b7c85996c9b83b188c6a28691119372f8b55a70a28bf42042117de4aa81f0cb9367e5a083ab482fe507c9de564986a8d3322c1df26d90ab963ab6bb6d1f1033dfff414d736be2031309d28acd8aad0ab80a2c5681755af82f57a59727130a0a75c218bc31f7c6fde85fee352ffe9dde8917c60c44cd618ebc7d416a1e8d3e878fbef39af64b4713a347228af973227f4e8c3c91e7db0f65a273ca497aea85416ade9efbe666eee7f0edd35879b284980015f510b95e3d9f4e2a9427f7d423fd26441e8d3c20921484e6d19346dc6c574d1be4f976447532a519e461a4c8365bfc0faa791f0b774b9e10584749fefc1cfe82c34f12c6971f99c855e981f290b4268fa60927e49247d3c47ca17cbd20335fd3fd5866de5a667b3aef634fbfb087987cb5d9afee69d70d940f631a28d30663e677af4aa63f859fa9c92b79148bb95acac403313a9aa89f7590fad8d864faf64319c12cac19a240821b1f2796e4f619d224d7c8ed73c490cc6a1952f32c38070d3b744f187be28f83de6a7991233baeea57c52ffec4d8e3a047240de32fe394919e4480c3f88a929563be733b0dc1617c05754f60cff1389423764f8ef185638c31f125c65eb275e2adce5bd85b3c9dea4cfa4224236ffd70deda5a7fbff0c66b5f1883f2d9ef957bd8a048a73a635ffd7ad8e048a750d9450a7c73fb3881853ce32205e672fb383194e5dfd933ca5198816ca5e738e8ad22dc8cb7ac121cc6577c7964cdcececa12a3e4e6f2c1551d847c9d2ce1f692244e0629653683a0930864a4942d658b9311fef4fb4a795f5e2977224fa6199594daeeb7dd51c77e4b22447294a110e7f17f56b618197238e77497534a293777776f28b894d2871f262b0311e20359e0fe4e22c0f1e54b77f7b7c35919882c03cb9d95a9c8617bb7381981a7bbd7a0094996f41832a461543d86d434d8d93ffa2ea3aa52c1892c7041fa174e841a42c3d447f11c4904eb3b6bf42367853847c9aa4c9adc47796fc9557475aabeb539fcdaadb9ce8bd79abe257332c6468cc1b7035f4af5060dd7243d470d18d62e3ba594d262ae391e2a59fbc805b9763d2cb70890e06cc33c1c72c01e0b1b48e668ed57f24a6ff2623e9d7c7772521a039098453e5dba953c0cbb1a146416b32cc69651f6b2f439e4c9d3fec4b39d371dbca71ea5cfeb7c7a7b3307afc6cd9c3ace7ea70fe0fbb7a4613a40c0c12ad443fb9b6d18d723fb80c4dcfd8ffbeda037048cdfe42a9458e343628dccf65d96c05052529242292508219860dc8eaec2c98ad3174e37b399c783830e56ecb330e6d29fd8932f8c29dc0eed5bf181bdf6d86b6f4b299be6854232f61a2ab4dfc99b5c75ff621593986313ab59b5315a1ba38c910772e90bed87fe03fbced86772ebaaccb9192091c78e2c1c91bdc7411712fcb92bee4021553c9a264227421277228f0ca34df6cdcd38d12afb8ec4c11a9d6830eab88afba48d83b565249186a95fff85aa3639b9b32c4183f54bd0d1f582ab3371267245d3448d3aea3babbed37cdc892a174204eb5787a26b54ef89d2c6ebbf50a85a8d90eb879248f6798d7496d348815d898350348cbb1a46497d57e24575490ea51259f4b6928338ae53f37706fb228e4c47c471bf432f77376310a81716026a21a18e91cdc4865ee980615105ad8a51050df6f45c2d08b98a3911a76550a81b44f3d20805fc9d5947c2997829e12988ee46f808da916818ff98e339a1d7788d86840fa9892d078df09a588406e5d06f806113f621767323bcc66b74b8261de3fbca03467f3f07ee37eee397c368f4dd90a91f72b8cf7d42801f033a4e70266bd7dd3e2c5375fbfbf1d37143d454c0e70ccbbc98f170b9d5568b655eccd7a3e6a3ec318f0707b10fb19fdf03f63c44e0a3f6634e1bbc0cfbca5197ce8386e46904fa8e74808010761c72a03f658caaa684661284c4b408f6db700364dfdf1185125fe419b97f145758a63c3ee6c5530d963123d119f38470d0232b6671c88d1bd8413f5121a7c7810685c414002151c4a90938c6c10d7041348c9b488024875189ec2877494516c241a731d29f588dd8b46d1b0842074c5a46b81e71f8f7cf68230ecf7587ccf24317218aac8dd1930dae1f2de64ffdadbf94d2bf10fb691d94fefdf019fbf9a18d2cbd624f6db598e7b55d33d32f067410e3e0d0868d1b3404e1a254baf3c601c61afe31ee51f982c37ef1c0248806ea61071ca20a0011f4f798087a4cf9fb8311074cd7f01fd44bfb8733647f27e01d22d00e4a9897f0a528e68462f12683b55dadf6cbd85bff96b7265caf4ac6f9502183045f408c082e41f83404f69fae8ad1b1090a1cbffe9c3373f3e89b90e41046e676a864ec23a792b18621c1b766021c76d00b0ec1181b5aa88828e0c91e734bc451df7f46c461dfffae80266beff9ce2de2a8df3f230eecfb6b5c6d7f3f1ce5fe8e28b94317b9654a0ec19c9243bbd51d271b6c3dab913de6c9ae913d2857b453d93b4bce3e3c403ee5ecc31bf20bb38f3fea63af7d3ad45740cc9a6705aedf0f00c8403a8b32641b7e397bec07206ad63e88cd400e90b52f438010afd1068c03811de04be655093e28c8711f14dcf8a0e00814b07c9200834f12496aad984f123c3e49fcb413994f1239d9a8638da22da4ec80524aa594f5db935fabacf24a1f524a29a5dca9b5d65a7790ed5eb8834b291dab95d269857ff7596bcb7e611fb1aeb5f667a5c0f46dbbec8bac354a2a6a6c6d08170acdf190dff56bfb12bf42a1bd3b7ba955ca1a735b8c35f9c08fd7e2640466b9357ba9272870f6db73dce6b508b5ab65da67adbd7bb556f758eb7d2c5eccab632d6759966557d65aeb77b76debc40d6f637f83967ff427a476873e9a0ab97af5a5e7a3c6145064e1812c3ffaf7a3878ec226d7cf021d854dd63a0a9bac65acd65a6bf5b05a299d4ff8d945a31bf8dcece4fa5eb32c609f9b9e2cffddab1e4a0131d78fc5b302c7f74fa25018d70fd77665203c03894da2f55c3dcf3e3fe6eac54c3f209d4516d95b245037562ba5dd326fbabbbbe7f423248131868c06cf9794524a2ba573c625dc16b4fe984f23ffa03c30a17d4c0e1b0b2fc40342d8b7dac7ca3e1a226074645f53d261432119fb9826ca6129e34093bd8df91063a23f9c5fe881fee61cd054d0d13d862c3f763e2530417fac8f1f9232fd4269ca0fe8286c74c44f887e944c99f25b6b336b75902969adfd9892528259dec8d2c672e104056edbdd3d29a5b4523aa7f4a8051c44ea3c7bfbeccc130459b0696ef656af7653fff8f5a391c6cc06bad70027dfdf65a5efdbd762d7cbfe5a778c89eeafdd300cfbe672b8cdb6eb611f39fb1a378359cc467333531c71e7e466a84d4fc66f3d6d44fa288f688e01ada3bb8fdd25b0c8ed8819ebbca803d7bc8e932f793444bc8e93afe675745f8c8354603bfac87d31369488d1b4eb65536099fb270846b2ec72ff046105f9fedd6c6c7e9025c780a8a3fbc81d91effb4f1058f97e8881cc7ddc0ecf31275eeb2885ed6a1d365a87362333acf3689ab895c32650d62f793478aafe492389395181c19800c474de0d2740fd8b9580953b5b1b63c32a814107b7ac480e5b09d7f1108e4d4f9862ff11a6dd38ed47df5bc7fb397c26abb19ef69666d9478efb8be5f0226ef4b2e7b229b0e7fee9c192cc6d315f997133d85b4f3b82ab1b663ded88bb6dd9bbb671f6479d750ec7b97d9060426e92677f73ccdecb23013d8842839d6fb6c5fee901155a8bdc28142273f6defbf75adbe260f6230fd45adedc6fdb666d6a72e7cb65f6b69ca51bd120288735465eb0d85a1e9b595b84eb313131f7b32ccb32ac067b268cd97030cbb26c26cbde7a59e6f95acfc614dc1238fbf0676cf677fb5bafb7725fbafb57ce7e272270f6d76ed6d36cf028b70f09965cfbd7da2318d16494fb08466c21636f4779fb6ec4d8c8b2cf621aa6fe4bb418a59b963de56ec0b2ec031d0c40c3d418f025cb90e4f000edc2426ed607d65570b1c734cded6631ed270a9bacb5d6bee6ad606fbf531153e0ce3383d16b7190be0705c63edc326642d1c7288ac59405ea54833bfb7ec73c54cb681faa613a09fdceb2c730eced63f6ff6ada176a206716abb4fef874557c67c5efb4e399b5acdb2d175bb9f57fb57e9de3616df4c9d91a6caadc0c661db43fb9e666acbd72f35adcb919d93285c77dfcbec505d8625ef615a348b8f2fd7b3dcc084418c9db637f6fde3ef01d6cc9bafe14d2bee68148e46417609483dea372dd68725883cedb0293ad74b35098d65aac5a4abb56138f17eac58602a514e5d4ab7fa5b518157707f4a4839983db04c0ed93c44d96aeaa1ef61043df52399bccef87cbe803253e26189a64a0d5c6e8d3229297b8b973fb3669cf52f5e0c30dd7500e72a6c9f170d02307811d22d0f27235fad1c77ddda910471f82380801073dc61863cc334e07fda404cb8f8fb5ccfc18438186e1c183c00e0ebacb8f86ccad15eeb4d1222de0d1c19f9e9c229366b25e433c3bf24612595214a56f21d9f4e18a7d1b19e7589fde30713bbce37c9072f6d77aa67c676c2d24cf57c63683eb419ac1f918cde06e681aa9e81d2db891bcd1872f1cb7713f49313b49721843227d899b21c56001878e244b19f3d332f8a52741fe09a700d267df65df0ac64c3862085ec94a5c0fd2e36f25fb7c8039fbac3ffb6ef0b84afeb03b37c39f316148939cbdc54fc2a6962bae53f34fd64596915d5e600010d375f8334b2291be7bec7137644fca9e44f23a638f943de973b8052266fcc5a1ac1b715c0ffbc5a0280480ed923c996f0dae072993fec66c91f331caa40fc096dde039ec00c9f37ca57337c89c49f86fcb0178ec99b17723e36656487fa3bb27fdcd3cfa24aff31a7bfcf8c14cfaee5bc19f0f30936c6cef0d33df8508f86d947e9cbc7df9312d637af9016899ec81904a7f32e26eb09fd9cfb21320623ef9fe87fdec4b5e68bffb8df3813ffbcbf5207df6b138487a41516e2be4f679414dbe04e82173e8812c73cc73cb2197b779b56cdba8dbcc4761074e7ee613eba172e64d59e9cc7ee6ec0b3b90ad83a4bf127b6c49471de931e9b3a32f8c4936cd9bd9170ac913b3e1a09b228d18dcf85b6c6b3146a17e8565aa871d1c294e496b3146a170cd1de3df677b4e2e60f93bb75b220d4de9e6e09cf8fb861670c518857a30c67e61cc8d1a1cb4a10acc2272a9bc2e5dba943f657d19855e314914b2710bd10a32e4fe58855ec99c2f62921d7df972551257614f5f12b9dc71fad66399f4a5924c2b46b1afa88e4c75d5a16f2db55827d3a74fb54f0a39485f0ef5c8957c798a7ecfcbbfd86b23caf45b16653a5b7e9473ca1f09e4207d9923575228a6e8a35018cb1e07e9cb0f6da55228e2a0a255f6e9cb28f48a7beaa414ec431839944298271bc45e7a1bf6577ab2d6979302713a091032312d62053ab9456e9f1510c9b31bf539a24e2cc0d373728a6270ea9288937338911cdaba247b9e5f141a6ae170aba9a972d22844342df8a8dc0e6e357988afb8d756e32abad5845b4df6bf75625c9cbee25e42dc4baee25014e288e6cdb822c9019751becf9885c8186b70fd2fc9bda5c5c186997191027f9e7191024b2b797ccc810f4e900f0b887c5810a49541c9690a413a8d24cbfa24ce868e2dbb6a3ffbfad2651877f28e4f0b868628eac87a3183f7e2755ebc78f1e2c53ce2aa172fb67f51b7e9c2037c2b0deba74ecb78de84620be3cf8b4530de36195b2f7efe74a257ce44a6e6ec419e73fae4f91db798d3f28592855d5f6cbdf062912960ee336f99ceb1532d3f5b90e4d9f2e1fc39b2f9d479e1f3c76790cfa13c5f7c2afb172ddd470f77f8f1b6cd68d95ab6967f3175e61129e0fb5a4bcaeb2847292fe6c87295bf44d2b3b2a49d1ea1ee7e7e0441cb84a0552d33a05c5028d4b7742e33685ba749d46f353060d49a1419df526f48dd777fbb9312f8340ce0a9d49752a92e76a9208238383b1d9d233bd5c6c15987c45ab3d2b58ab0b46acd0c3fbf0ef19517792a882e16f1566cc59683f3278d5fcdf0f327cf744d24b367fe4ca019348574748eecf8e0601041825c05aa8436b7f8f6167f7d0bc25561f74138384ba9239d4e91ce8c4f7da12d4d1d4fcd6f9d930af0cb22793ede3ee6186999d639e2aa9dca535daefaef8a8270707e3fba0f12732cd27d2dd35671523f73ea0b3b903bd0731a9e2799c173224fb9f8d099c42231c74123b3f5e2c5cf98adee8b2d9dd8d2a935b7e5a2fca9ded49a141929df526bea4dbda937b5558b340b8be5c88ed6d59bcb62e9d41b9dba44cb6b4bd3b4266ab562abdedcfc80e530b2660eebcdfcd3f4c2396d6445d6cbe4deca1b34bcc4d8b0d6daa2199e2a8ac15cdddddcccf7b73d0437bcd5e4eda77515fe6d8bd95c8270bceda2031c4f424d266a65c0e194abc9f2d46c9d1ea57d8889b80ac66f2a1928f0b56f8b3f7f169939ae72d9018ebd05e93e7adb771e8ff8db8ef8db6f1ffeca25404816823f87f42188e46ce8e83ec41963dfdee2a3bc8b6f49e35dbcd8a9f83378292fd2f04e1f68d5e9e7cf217a1ad1f38856c9f8f9b304ad027ffe4ca255307efe5441af521f4d13b8376c42b17c0b886177c32614cbb780d7f6ecc2c96245b71b36a158be25cb38bb61138ae55bb809fe758f5ce3df66cb001fc6872fe00744cc32be707bfccdedc01fbd7130fe8fedbb8f53892c8aa7c77fa59c9cc72feca1658b11c669fbee65d1cfd7017f393828a50b87603744f7e570f046260384813d1c3a8706b532e0d071ec369aed73b8f7b80ad3f86e9c3c1a1a8c8ff272740d0dc697e1d9d0607cd04b4083f161783834183fe5e5d0a9edbbedcbc1254048debeb70fbf8de90205f651a22887b3a7c747892373b68f1244dd365993153dcacd6c31d2291dffccf80b3d80f2c29851189c343eb8c0e087bf1c0e72d67629df3e2d560ebd479b611701b92ab411e42a1f646a3eca67df32c43450393cc56322f59d1724e653671f2cca175aece20b318d2ff5d9702e7e1dbbbfb163871de4329b4ac4eb4dc57115b7c2a41ffdac3579e6a46027cb8ff2a37c1a704cdc627ce466bec721e2d07e3e035a26fefc06b4eafeb45905cbe00f5159a553dbabfcf56483db6b5e778dedb1b7fd0cdef6296ffbe83dfe8b71dcb0d6c9fff99409ea2ddebeffbfdf4206383ee56c686ec7e7b07bedefc90c4183f37d98454c2562cd4c41644d242608260bfc670112149d3163c68c3a830613a9a7f1391c04b56f6b10c458bb01bff6011133f885f1b7ffb161fa7270d2a14885585e2b5a228ba850eae7d3215fb9cb53f32fb66ddef636689e4f0b7eee0a11d93659726a93a53d8bc76750d37ec6644dd6a4993519af0021e5edfbc71f1031c7dfbe1a1cc48ab88ac6b7784ae42afc5b6c5f79cdf31dd0731e1b6cb82b3fda41f083c43c695c9ec0397cb2643cf8ad6cdbbb7c77b9378f6aa1c1f99325572e1fed42a7e6cfaf889b19b2b0497d58e4f9e1acc9330b0d33bd7150a25863124d4a3487d8b6a7c1dabb3c81c376fdb0c1c1f94162aec1c1f9daf743fb203107f181c55ffe5ce93133f833835fd881ac3d0dcf751c9c3fc3f3230ece22f9c57cec6d5e104214c180f132641196b5cfc50738fef6e1644d96abf05321999a7ffaf653caa3c478cf0b63f6f0478984503f5bbed066d45fa13cbf850c70fc1897fbec29d17c5ae4aa184b4c1f5a5476f1ed2e627893c6c19a6dc39a47245344f3adc5dbf77d93e5e07c4a44fa70b2bc106f207295125715b54ce719bbec4d185e885f78214b76e18598c50b5956bc706be1852815ef070ce58533c50b4d19c50b4df9e4b16cd8307da12d7d21fe3439b7efbed0e26f461247c1cc0b5142429369cece984b86e718c16d1a7054c2ea96f89265d569c12cab2aadb4e172f228c9377215ad921c5a8ca2c95e6f86726879c890436be9e735272ce01e420e72723875b27f9c3aae3ae2aa1d577df69f36b2e5074e9becb202616c6a7cb7ef972dc37dbf672e97ccb2ccf3222e9d3624a0c77788c04bfa10a4fea49145a3b12ee7a5fc167374c995b3582e96cb4341f13413305192eb4b96ab426993eb5795af2f69983409586081051658608105165860818515565861c515575c71c515575c71c515575c71851556586185155630810151c6d841295bf6bf8c899152ca6fc6b8bf0f52f6078ad18510dddd73099ab3a76571771babc8f343ec0758cee0488e1f5974be0cfdf8271ae0f00b8360d93a287facf4b09f23aa6528e2f03a46c3cc0f7dd2ef1ba6a8fe940d62cd32eb24e55a59f2744a37a79fe9bfa4b8dd12925899b2589725537455e3a4dbc172dfd56dc91425d1c67177620d8a71dc7e9a658abeb5f9e3959356cc66daddb81109772725d3092505a5d26285c5c50b18ef7d9b26c3faac1877235332b613ef12fdf4206918fa0e4484ae71795e3af91a21eff06caeba4cd0d8e2d7f06e92fbba2e4fd1673151e3e347001499017e388fccd0b97c27a3ebb4d7ebf5eabcefb4264dba18df692e97cbd57ddf6943435da7b55aad56e7e23b8d87a783f19dc6eabaae7bf19d86d3755df67abd5e9dca77599326ddca7799cbe572752dbecb8686ba2e6bb55aadeef45dc6c3d3a57c97b1baaeeb50becb70baaeb3afd7ebd59d7c679b34e94adf5997cbe5eaec773f146bd0a10e7f675bad56ab8bdf599ecef204f19565b13a56d7599ccee274d977ef83abb8efbe879619bd5eaf0e7b7558930e6be272b93accd5759df61d36d475adaed5613cdd3b36d4d2327285b53a8ca7eb1eabd1b9fc0c5f884a7de04763c687c9f8be1692f76ae2bd5eaf2699be1763f4692e6dc8d35c9de6720d65faff9c8b96c6e3426b755aabc593e9c380b1bdd0581a8e0b8dd5692c8da5e164fa2f58ae4af6ca9aa864af2e7bbd9a64fa2e56b416992b1b52c95c5de6720d65fa2ca8ec94b5329e53d6eab2568b27d35f49b128192bc33965ac2e6365ac0c27d36f61c24eeccb3639b1afcebe5e4d327d9552b5d665874eacabb32eebb243993e0a8301d3164fabd57a22d347f564dae379be230f7bceb22c8e65599665591764fa2918868dea935e4d5eaf5719327d944ae56bae21970b73615564fa27ce088dcebb4437498f83147bd7d530f7e70edda0d30c706801f0f46f6a730fcb7116f115c69a46e611ec67cb55df633f5bd038309ca944c67ee2b44ccb633f59beaa403540d30668de3471558c30348e5a543406262de30df9aaeae824d151a2b344c8552f9ad038eacfcf14412df33bbeaa3636ae6ad303c389c6518d18c941c65eeab48c8bc78a8a244e912c6224632d1eab432412197b39a465588a7c4581809800491690ac51e2aa152c340eaa448916885a46e5c7575447274867c893640c85088d83fefc182163ef485a06f5d87b8eafa8d5c9988def60298fbd2fd138a8112330c8d8e9b1771a5fcd221f42a40827631b191a071d9245939631bd7c35818088808ab092131ac754a2840aa196e9787c35757490e8fce80465ece34ed401fde141c63e1e6919fbd8c71b5f4d9b560e76f2464e90b1692488afa46d4293e7902e64ec2bd038e690f8d8fbd043cbc827c22f1c7c2581648a3ed648c8d8f7681c52890e121d1a5e7c257512e02a924c7dec41578dfe27d30f69640c658361d248a61fca9c65340ef9b3923633360e6924d6a08f3d86cd0f636138180b63612c0c27d337b59062d45af4d50a5494e997bcd10b9d9f5a6b3d790e46ad2f6aad364632fdcec5d6a2a80ea19456cc7257286d412990924c9fa4a2a1e8fcfc4fa63f426529d4861a41a136d486da5023993e77b25bd156848b8ae8904c7f33612520251b1006025292e9dfae723a3f3a3a3af4354b7358979ccc795f28ee9b36731a91397e01cf1cd62559d11c525454344990e96791e3c99447564f3e09480910101017327d2ba5e474bcf9239d1f1d1d9d28327d6c724566e07aa4f2e86738f1b077af66cde35a1e87c3922bcb15f114e554d035e8b38e6023adccb1a061e8c7dd9cbc80a7a471b91ca4df79dc0d87e320d7f2147d6e27d39fd63ff9491a59233dcfdac45a180fd6c25a580b7b22d35791b2058b6ce518d1a951df3dffea71452e1207e9edf1949188e342d135e8d3fbcaf4a58d4da6436818faa1b441a6af713b3cfb775d0d43ffe4053c3f1c1559392a1a15654b5c157237b949ae4c4641aedae28bbbe1707cf5f2f4b9225c8e73465c489e6cacc1f8b72557f7ee441cb7899b93710c781d303ff30b4792301b496a0f007af9e61ad03a5e3c265ebe86c7f178dc8e4baeb81beec6e562031cdaf6281b868e5666e6beb003597e8f1701e045204fd1d73c2660be9ad7f1c2f2caf45f3cec29fa94bee572e4aab91bb9428195bb415de98533cbcf7a33bbf80009765de1b6c2581bf6712cb9e2706ab89b4c7f9258347c024fa82febcbda5ccb60d77ad22b101fa8fc4ac1fac5d8701a4ff9fc94677faf71d0c1260eb25c88e0e10ffa445a067b7f4f424aea492c0138788e1a3ce5394c9733ce512824d3e0a0137d4d437e796b2dc628d20f0e5756ac741ac039df3ae195e9df585d1673ddebdeda19e5534c5f3a413945172c4972fa92c9f4a7fa31be15ca6d84dc3e30b0c95785655341b538fdc98482627a133763427d98f22653ca8ac9f46056f914930a0a25e551296f52417d8aca0d26d3974a5ffa1caee2754e41995250505052de6bb81b503ee503e2db62cbc496f1eac9843253bf30e6939e36bacd0ecf28d3637fc354fa52e9497f7acccd9cbed2973ead5b26e543943ffd2d79da9f3c14171d4c5a3991508ff2a1bc20339b6e344c347d2b40cc7c7a20a47cfa52e94f28ef35dc0da747f98088d9f42eb81da6cf87c0d8aa4baeaacb5617299c4199d2098598e9633faa3f1a7d29c85542ae1ac29c8ff9d83b8785276ffaf9e3aab0f4f7e98fd28f9e62b1fe68f425ac7ee94778e49972e42a12fda5d14432fd9c9c9cd1bb10019342944ff981f2a7a7efad96413d7d5fa2f4298fe2a1bc9849dc0da72f9dbef4d705f376fc7079e2c294fe85dbe13643368e87e9830cb13969a738543ae17a9cbe181485ea562afde47c9cbef4a814cf4f5ec903226694cf711ca4a5d04479fc709c1b07e99b3e972770e8446a88b82a3cfd38bd09e7c4f41dd7e36452cd5c7ac9f18859b808817aeb3e7d2f42ff3bce47ed81fd95bec596a69fd9f4851dc817fb8a7dd6c1d217961e8b3232e62263a78c3dd7ab48f46db796193d1693ef96e93d0951d89b43645a73788dab86f4a46918fa9148aee6109da2df8224e611d45bf4efe72d0729a55f91b80aec71d567faf32557adc3ced76b05fbecb515ecf301661b0e466c23c7bf1886d9585f315ee0d5ab7bf58eb9215315f3ae5dad5cd5507115f02907381492b1a7576828f43048cc3f7e607f271dadf4c86e6d66719098e5675f9d6998ea7dfbd218316eda10f38afd1fd2450abc927d3da40d0ed6c005bcd5c71885fa07c198fad580550d0524d2755224bd6792ccf772a4ccf34c82012b771272fb2c91e391649e73092619e7ecb327e127654f4292ed673838204aa5c58a0bed06c94fdc0e9aa7fcbc65a42ffb1516172f3eec21c60e018731ff884351e843fc2412c63e233d98ede38f643bfc71c6c7deca7cecb32f5c99590a3783e70d9721e01f4162fe81fdf53618de17c3fb5a66a8a052544e4a3d6dec1c7ef2ddf821fb6e18fb76c59ebc899b39f9ecdbd287276fedc98a35fd893595bc2033677f4d7ff2d7b472f2d8974e1ecc286f5a39f97c8019e53b99befd86ab6c7c93e957ec631fa487a8e4ecf595452e951a1a00000000a3140000281410884442c1602cd124a92a1f14800c8aa04c6e4a9b89c328c78190418618640c0102020002000023836d00a999ff8bf522fdbf9b9737be6e5ebcf977ebd2cddfed8b374ebc9171ef197b73d84bb628ef32bd3fb797377ddd5ebcf9efe6e58dafdb176ffeddbc7463f2c60c7b4fd85bdabd19f74ea97ac9e4473b2f397c39fada40c24a0229fcf5eaf6634a577f10a7c41e8aa669557166abcf799c8c09e16665236a4db317bcc4cb2e85051dac4012e4531178bf4e5b5d1fc6ad2e954ccb79eea51275ed83b8b6ee6d9df7f95babc5ad5d111a1c7af51b04b55f82bdaa3e1c7cd7fba8cf3f2e4810fa0d83e1b96381ed326afb07ce71e16dd254bcd1a5574dcc5a01381f0124af3d257efbaa864eb2706a349a7b6016af333a1b45b3318da4c8a7d9b67429b7ad7e87d1bec0f52f447669694ddc28653df798df545f2f86de56e6449aa2fccb331e20b8f53d7506c37fd6b8ef66adb30aca73e2c0d5bf398e0af08317b3fd0fd8e9490755ea0685a7daf261c015377440c19848a86b3e88400fd355c7baa92955340ec214f55b383564f940584b4045853f6a28826ea232768da09f7cb6310107adb42626583d1bf3e3b0bbd52d3aa01f9ed0372458d0f1ed4a3c58e3d72385fcd4f5f5dc9a2159d398b3d6dc8e60b41fe2a40c0f8f0d56b51289477449520e2aaa37a0fc3a3dd7b4a8acef06a9ec83eb508f6b702e56fb988df7bc0ab728c41df9d47f8348b3067c9e6e62faba61ea1045746f392ea2a6531a45c8ed515ab6dda280f3b1f7deb95ce8ee9ea973dacdd74644da5c1a13d548a675dca88156db70f636e755d003a989d9e37b5f03a63647b863a78031dc6f3a151754939e1bb62d722a651d035449fcae2bd1c90a47b82bae384e8316f9584079ddd551a05ef8dbbb6fa9ae354fe5f1e898f48890803cfb87a43df2662685c0f229f181f5919881f611d1c0f595d881f6959881f615d140f491f881fb92a881f715d1c0f4d189c82410d8d4f24ad5309552eeba7d28a58855e926b19f21397c478e108727c1c17d09da7c742aa5f2cd517e74348416b57c3f4efe885ba6ae9a9b4d035bdb9e031c6fee1b8d4454e3fecf4d2dbcc1c4e8db3938b23a515cc97877c098bacde567518810581a4702964a8d599d1af2b6e3ef956232f23963cd0b5fd20de9f6c94f2d7f7b6b1c933ad89d7dfcc80402bf668cdfc0eebf059fbd91f182ffa8eefc23b3f17f44ce7f272ffe46afa19109bb7e1d8fb391f1867f6de1fe6d2dfe39d37923b3edfedad2a749bcde43f44220139223541d88ab86abc3711d50a6951b193cf86d26fe850cfc4bc8f81c19eff247038f3132fbfab7f2e8afd2e22f6b9a3732c1eeb7a9988e8cb7f0cfd6e5cf76e60f4b9c3932dbfc8f08be6afc8763249fc46022de40948670f835d53530044c59ef91c1efdf64a25f6460bf4c83d7c878177f14e2a891d9177f238ffe2a2dff924686cefdfc7d2046656d73c6adcf91a901ecdac2dad35aec69dec3dee846e6706e7f094f2cee961999f41cee11bec3567c56af04194c178d0c6e692db3684d76cf5a18e42f7576ea3282e9528ae72aad73d0528977df3f98f29149f87471fb37ffade4d14057cb59473e4d9fb55a1c19c6e71cbdf2053a47f7dcca38b8afad2383918535ea8202cdc6840bd618cb24227b4ffe532d178387a92cfd2a258ed19fd4f6c4d976706037978a54c1f3e12362fe101ea5f530e614f5287b1c7d5fc5a180ea1b788583ebe072ba01fdca4acf89b763329cff54b9580ed2221b5f0cd1b28e5f6052af39cf07af5bab3c27fd1aca482ec74ac22afa160a073400ec062e4fb9e2de6b64b99de878b4a7df69c299e1d15bd1f3350ca988c1532e3ec6724a72a9a176ac4de1698d40231ffefaeb6326462c7f5f5aab72f8c356526294d313c2257e3c90ec202c162b2d44e09b25de21ab5ee8bbceabdb8b481d9df191133c8101e6d413c54d7c0ecc30c4484732f79f8eaa140dec4d5e78f98da1660b9c896c156ea9aa7af760058eea4208780233274fb38a133e48e350eff5acdd81f9324d09d2ceb137e070b07e714e9a25683b8c56ba0f4a4ec902a8f5d01115a66061a201f01c8d804f7b04776cea6ecb79ed65062b1e2db153b6306dc08135fb891c195b75a271254e6669f6aee58d08b776d2b862de483c99dccffd6f3904f6bf75e68b2905e86af0979bb27e38b57872bd82225aeed71a409fb0c347b9b08a8e2ac019818070b9006cbc9f7db0ba9d52f9206c21e2626cbd2fe7df4c6c320db7de5813e69f4565f03ee183cec5ad5190b442dfde33078d7ab599cf08ccad9f8f586329749ec102b48103f4fffe148ec6b24b0b70143fb40bfd737c93479754baedd7fd820e139bd9c7a4830a8b7200518feb65cce56f9bb0379e8e04d545141ecc332796960c6af6f5b4f85365ff7de21c2055d5b2f748bb9c2006fc863351adf478b651ba9f2bdc26a327c720589854a75db711680cee120617af14254dd52b2840196e60a746cc4c356f1a7403c1011448129aa2b6040bc3b75c621ed89cb96da3dd80d5c1463b375f633951f4a92c1316722e2b41e81339f3598e8c6b92ee9697fde44c06277568ec3bf3394a5c65e0e9be70ecd054d7aa187244d7233c6af307b8fefc2a236050659adb3e5d79b91babe85c3855eb87b2e863b96c552a8ab42bfb23ffa46de0cdea278882a97f10752712f74b87f8559d3cce8517f95bba567a6861e902faf0c744f42d2daeafc52ad280528e00ca4014495d75c41b90c36d2377e712738a8481bc1f9d4a8d07a5417dfdd09c9778d43cbf61229f269105cd6d5fcac4901ce7a827c25a7da556789006e4030cec83157c184e93f05caf8d7a50b3413b1239dbd0984b3a7bf8a7fd6d9f7b332c1aa1893a8b66c36c0c1aa3c167c7e6e343e7e779f5c390c089c57d5c28d55c092225d3f68286701fea79c171e2718d170103b3d0401604125816b1c169e07f30d79f0dc54b4c04ae3835681026b6c37598a812285282dab4d640dca4c8a1a4c8030b9c4d9b9ace29922df44a4df694bf54981a515be6d7c26b19515319e5ff1581506c0e9ce2699bbb1d16b71c0973dc5e6f0db3323b40407b481e0d4eb4fa9513da19eec8d6526d3bf9509364fed3744bd6e6f25692671e04c15825efa6831324b2607f4bf83d31bf48a87c1a00ead7bb42ac24f96fe784153745c9bf6b73d1a27e17d3a658498be76ba358a7aed15b2413e6c69be4a83122fd1a034672b215a00d1a43d3dbde608bf6abbd6ada7d908581a260341f54c681f62d893e9bc9420727e4772e959a7830715e6e02f504bac24ad7cf551d8bc4719f603c67e79592c05a51e639d95a752b6cd66dcd646632848a869a1d7e41bdc7676720d67c513477295be8d249fb86eb066bc85651a7947b2dead818019e106f915ec2c51c68f89b49580aff66688a0ac986fe92d1283dd61ac5a4de8820c2c02189a0bf7870e4c089a4f74ce024753762fbe8730cbfb51b49578e4d636964c061eba5033a5408f0154a9827cf671d4eedeef0d7d5310c66e7ea1c474ffdacb5464b8a47d6be855e532da19dbde0d15f3b68d268b33f1817edf7fa2d65ebf330038f8bbbf3d09566057abe2dbd638444298e69094c41156285872e55ce8e254cda4e9ccf22ab6da26fa24adff73e70d87a4916d63e2a5760c57bb98781af027d9997dce2b050f383c9fde4655177693fff043a293fd94ba273045e315142cb55f5812a18abb04fa6f32c516434344d57d852485e7cbff466cfe20997b7d81f8d619469e5977027cae3e9f528a00446f7772f887c38b2119511a6335b0365b6d1d3171c3ef9973ad75c24f991d81006285816e014795cc2c42ec05e2e163b6224148e1146ae371bc9ae4e72a3fe8ce4e2fae3e878cda735ed6e5754f359cf7fceee49158a6abcfb55dd4e155a5bb762a978012d9a9f32133e38a87a1b6918fe7a5e0e56caed0814025a57493b5b9c91f64cfa51bfe3b14e873b82aed7c98ea323d08bc12235c5c1cdb9b7d328e7e3610b08b5c12c0276687b8115d0df1827e1dc47a3036394e534c731e444cef77a76ebcfb99153ed386693ac79dd8ea706695159ac0c868f6c4a4d12654402c59270cef14856f65e5b60141b59e06b5e96de4a4b3c1f547b27d59b80e00db643d9965f1c987ca2beef5fd9b2e6ca57854a0717a327791774c9b22f7a4e04e94c5c47525f215fdba4cef720383c7b250c4c1d32babd430b1adb5e51a4bac756b72f0f4b907f5bba5cc99e83c008df248ae9b3f2f4b1f7fbd420c86271a2b3265b0c54c395b075866aefd826f779c4a8a11a115105b3f05da07465044a0444f5df89ba17c0013ec5fc8fd2923992a695c62a93c782c4498615eded1b17e692e3c1ed69efeb6b135c18d7f97aaa0503f7058b0c360b34ac8d5176d3ddd6a2c91503317796c67909e738542ce28ef1125314abc398842dddf339882dbaca04fd6ad1f7847594c7a8eda6c368b1ff80d5591303456149b032f33ddcdc0189398130edb891f85b0c77b0c75f57c963983148ec4ed947dc3799f8b5c7203a46b3b5dcfadc2435e627548bf769bf2d4d69e85f28db82bffd53daabb81eb27cee5310c45c32bba71cac412002d4535f37bbea9747c237a70872383936739691a618fdac03c61892ec625638cc594a7c76ae814d0c04dc89ee546485987d73ce13251ef30bb37d9d7f4ef85c9cbc76f9146a8fa321395f6b1724144e2df72d4b10c71d3a82028bcb72bb8b17a97084342ab94e8e2478601927ba1adcfd33ce3b8a40ef69fe070c64b2baf6cd68910c249f3fd219c4d378c092c1a85c2bd7a471ee5ae60f9c42aecd1c9e39c59e66c2abb3dca83ef1cc1b19e33be3e2d8f4078de1877e533c3e6e11963daea28abf4d02a8b042c00dca89f7e6298407e5b5299e6b4c466f2cf040012a1a2bd3d065b63902b9f748bac47dfc9c9f14eafe3255c6e7e8b20837f9c2744fb083f58d249b3497e3f64e7571d6e0539a52f51088ab66e195c8dfce8f27a2f00632e793194f8eea312ca4867c81f69665f370cf5c7c32876bb4f0bedc3ec54db6286167544556c8e75f1c01280f76c495f712173c11d4be4b97794870a8a0c0aab1cfc64ab06c7962f140c8d985aab93c4ad677f09a4bd282b024e47ea2f5ae17926e172541d08aab9b8fac385890a52cba6a65ded0a2cef812530d1308fd1bcea80b0c116f3bdbc8b1446ad759e91efcd78160db465622223b6b398e2e97afe4506461882294828c4f564a380570a9487f1a6247ce51b94bc48164408f96fc2a38f8bd0d7a856098a409aa27b5e228efee66de1ac3f6a5fdf79c9602991b6762f14e9b332df7658114dbc540784f4ace22086f2e098bb9af9b7af253ebe9fd25505afc5ea9069c9fbf187a2959c0899e498d1b16a014563f69946e217ae2419830814f3972238b9be859b1f50b7e7f3c9183c17405bf54b4dc0c632ca58db7fca42254081898427431c3d44ff78c3596f4903b169db4b83defbb7ad234f6cc864151c238166dda01464553a0d1e674f1b75ca993d4bab3f24dd698c1a8f8a5be965aec71cfb9f0a84ac111d454bb00558fada5c6321551d99c2c2fc3262592c1f8d11682b872469198f13133b865fc13701a272771c336d5b4de320d3323beb5cf44c7b485b633a1316a1a1e5919b884462c6b75b50ba21a7b75905d83509a3dbee8ea3a4273a1d719617ddca55395b6b5b1bce658a876b106d3a254c80a1775bdf8dbbf448241c62c158b7760ff4dfc620cb446d7bafd0d5b9bb2d28f4b288e36d3812348bcfc17d1dc7bd510ad874308c0b041c14bc2f4316a4cf0794efb49973bb9635c711295b2957ff527c928fd36c7ad2b3c622eb047159d614d7a377077707407466ef07bcf6d988f0d1bbc9a3399f3a63267c821bcb3041bc125d843d7d4c7e50a4d35210edc6cde4c984ae8e6d7d290c3f6782fa1c5093ebe667923227e553ddc31c14e798ea88a8b07dfe16ff8bf253d58f5fa460cfc9b37a14b3cd3e197abd388ffb2c3caf451873c8ae940ff16ffcb5305ae0e3e02813d20da7779cd1c5536cd11f41cdabf7257b6ed61f46e744ff5faccae7644d977424983244d6bba10372d805e2e98001dc5efe9146d224f0971d8353887062a682ecfbad4014ab8dfaa2b9631353670ab19f9af3deed8f145d2b6aeaac56281e9b7d241c2458240e95685ff7843e30ed6edbbf59ace1ed3219f843ba5db02b13d8ae8f897e0485509f0bb75de22809676e91739bf4d79484730eab98cbbf1fc38623d7246fb19bcbb479edc0a868162c862aa1b121d2f487a4323a2146c8177030de1e13f310c7ce3f9754480942e51601e8aa0d034745ba03e7dad1d2b815b61a27d1ae8416e3a49b9d1a35121e9647c6387985ab68eb52a9a9a604f6a9b101d56093d6f0658c7b7051a0e121f66788d45f79881e508c6113422a2c2dbbe980011b1b78ed9685e91599559209ecadd63238d88cd98c07a3c2f8a1e90f34d65fa54d6ea8352e90be7bd9c022435759eb8dc31de30d5e35344bf1b792bda2037a222bb2f46200a8752639606eef0f426f8ed62b41624f0c33cb7e025548b064f23f0b3888f396a181f98214e5f8e08f2b5e5999c54bedb5c842c1a0d27fa6bf12d2e41fa40326d34a6e9ba45965705cd24ad1bd1d50c17c4245933144a0ab005a09a986b0397ebacc355c68ebe2b4f2bd6724a42860acc077c97d2cae5afa811a6ab1dac79bc494e438360377d353c38ae61ca87fe9701b7ca72711fe498615133367bf4fbf5d779a12644a414d3e9f631fa5aad5b0566dbd2a9ac715bbdd930a83bee6088483b224e659a04761e3e8615b846cbc4182e2053d8f6045d6681d665b10149d41cd566a98b791443695eac26115ddc015da61ad74c2dae38c9082db9c48ce2072c08068d315ca61a710ae59c94b4e40067ee4cd59f52ecbf3d4828e712e16208faaf4506f01a1c0872f2049a38353ee6c35dd62c9057caf52bc0c8ff8eb9ae85cb05785dc09c9be027337ffec8961498568c04c1c1ede96279c220bba95d9b6117a464e784084ed7c1cc1e1cec30a60683636c2419d9012c2a379bc76324a27d347a66fc0bfcb4587d12840124addb2756b00e224a601c03fc0070bf9da85bc4a218693369428525d4f9819227acdf093e38f77c1f19daf6bf74a24015c948830cb262c48a333d242a710955ee6693cac2caf037e659e62af08290be4d5e4194e64a2dbb57557fb513da645ee70985931dc8068ad018d05bcda1f522889d45537214edac8a619d453a9520b665afc96235f2430fd404059a6bfccfa38d646c32af94e5a67077860c05dde1e3c46f34dc98596e5754477e6afe4aa66807a24d40557d8f97a27438a17cdbf7260678e201bbc7e8b1edfb7ae193815d2f021c41bf0ddaacba133c111725a1a4cf4f73cd40fbdb3a59efdfb0c232c65da5b4365c451e6955e27856986ef46d83255ff79c79c88d8895c60a2e7670374e14de86ce1df192697df04027f35ba41aeb5ce1e043d50b1d09eaeb91f1744ec7fb4e098c000a78d5ff8f185a47805b5812053a49c465c3f1cccdc1ccd52af21d0dca8661ef4cce1553ff742005613adae01baffb59321b91b5242eaf9e139d69e8ac899238677549f23c7540c7eab131ab7d3d0ce9edf2d16e74ec0c3d4db12a2f3169d15f81f607875f51a46b22a2a8a4a7f3856979fc62dd1fabb8287a16249eaee8cc8e488d152d709fc1e9a6b4d49c662efd19489d29ed418097ef6f85bb429eb38bf052df43cb5d2f818ce3729d30f7d6b195772c7c017805df566d9435fc5f8166d0138d6235b011a849d7b1ff2383346d178aa2c83cd6f7a109fd9e8a00c807b6798560d6a2d87803e07cf88aac440be5afa7a41e8c4243aa597fb0d43e217b116bb8212f3160b6b51bc25394c02121f7cda2a09f98f331b3134bb4df52f64560bc73a754c7a15137c9a6f5c7d1b14a430ae942e8a99f79d53097416fbfe8a1cee44f42aa18e0bcd7deaa5af8170ea85953995eaf74c260a5ab038dde792e11765a1fe7f9bef3cc4700d024f5b1b4d535d5d1417f9f1c76487f7f81229a05044ca74dfaedfc13da038a4a3ec06c745eb22bc69cfc3d81cf2e51266a9d7ea4c98b3064c627c027e2116bae77dbcd61632de0ec4f8bfa7c0870663450181c18b1bfd2d4e1547189138bf3c930f07a0d344625905dc4d7ae5a4ae6da9332953f3e2d93db6902edfa3a107787d6997b973105a5407d1f4625eaa1fcd73bd21196da573a92e939e2f537a12ac9fdb6536b6bc6f427bf7ef475e2f86e87ffa7e99a58c503868a9bb92c51118e88a6c349fcf2416c6a173c320f1d3d4151d86f333d3fc4937f44828f4d7c12423f6ed43259ed3522df9a207a933f6789d984b55b81d7730bb99a213f2de99237045853577c0b60466507e5aa05c083d78da4f724758b919ce8832c6195985c7ba58545d916562e2c40962ad5a6ca7f758b2cdbc3ba184cf55adb94496c1e957c8d63e01e705f64607ac03ed52c3e0e4d6380169cd9a51cb8adffb5d91fbacdc842dae6424af56ed5c9aa386dbb71946c44d50396ac6b6382b3a676db150bfebc739ee5c7aac764cd4c795cc1147b104c05d3ba7375178e403b22a08905e72f4513bea66f5f073f3ebec22d10edc148356cd020fe4f7af8cefadfa113e5e8d83e8503be634650ad7edd604eb250ed544fad1b5b64c7fbf03703ff4f99118dfc0ec780a53ca5a15c1a38c9a1b4fbb3223bba4db153b6c3afc023f836a56095b59d1b13da6399beaa03bb5a8412fb8ab9708f5eb2e60a0f71658010ada4d7b9428a5512947feab79965c40670935960f48c6731dbd848e35b2e50f005b5ff11d5727b858cdc6031ee7fbcd1de231895f7e789fd809fa818d27e149e3d1c368aef817122fa5185c3456d434d29ce0b3a09a9b13439f63bb0c5d1f173ea3ae151c6ff2eb675c3f20085de82318a96100dfa50fb3bc79ec8e955a3dcd00ad6e464197bbda1079c455389bc1473ddff853bd7c28725abe2be117aa39fb8a115dd66befa8b89d6c306f0f4c26a7efef7127f4dbadd74807cd21211c477ddc60b530cc9bc6c952eff97b1efdde1fab25ecd86abc96edf7ca94a61a80ee623d1e15c02591ccfda79268ae025c46dfc3e39e1513c4036f386df6ce6097548c266de0a25b055bf7187a18fefbb3354a824abb7c5673f78e8a4a7bb325ecdd7ca964a5d0d288e38e893de21586bf37448bed366924f7049346ab276c73a08541612e95f88aea2edb74d7a1ca845a12251011539d3643e13fa263aafbcf71c08555209c73495a003aacebfef08bf73f3264d3269994cd1a6db06a1f2015137ddc776fc38cb92f6226429a9076f3b6bd12596c1c1d8ff4928e865611bfa99fecc815f7ba5aaf7c42452a2789afc9f380af755cacc2f439ecc43281cea7a37ce67002b19011252518526a592ef67509fe25552fd30027f9737ccc11b65c8c2a4f798087b2bb2e81f30f5bfda52540939d48b1afef0a90a322ff6a85e42937169a3be55eb9545f2a617b23e6fb179be6fec695cbda988efb2956a8e12c5c95791c9d1046242c9d5677f24a741838b220bac5adebf3ce05ef9a45c1e3d9a9fe9db1b0bc7f2c248fde581b135b9bde224e3b23ec8a4eb898aafb8997050885e2eb0c2ac27122181c7d34be69dd4bc87a75a31aa0dd813ae5af8d989f2272ba66abc3312904e58a1e76bad810bd0628c30eb64a439a1a5399503b3b03d1364da3e70683091029ac8baeaf0a1835801b2bcb8db2c85209c97a1c700901de3e708484ae40373127c28371825b31f28e99f26e83ee16d576392527c9fd34e444e0fe4306a1d25940a9eb6e00de0d60b7d4db1b95ab79b0aeec6e6cbe29b61f1868deb35d3a9b44283ca3d30aed9a2b01d39e1ebbb399257b438870adba3700d515c2ae007bcb07309c6a2fafb293a6a8538d8d7bc3077e68998c655239d4e82f6c0def13b1cc2fc617e42419544212c207a2905feae7e4a0f176458c714caae01ef9f642122a58af2ec551634e435afec41a4276c08f952954888a11b68fcda6b57fa668d9d03b03e82bee64df753061155659a53a583b98a064db74a606ae514d658601e8129caa7d3723e458c38917e7b00848045cd53e0d83fb0384ed6886350d41187cc02b5d9c7cefdd736ca9d079a3a8e13be1c7fca024b7df9ff5285cc46963f77296505f1fab371ef5f50b37222eef89e4291f181b6ed9fd929dd7765a45e2c5480f7e86fa13d65a77cc9d43c703ea0c738b971406a4072748462fb4a717b0318dd8f5260781f20643c3d8d712438a69b3603be3b13dba2145fec252fe520a85e380e03c520623fe0634fc4be85e0e3761d1e42273f851104af5a7f6e1e7f20ce796e867ffa53dec6755b770a6a6c19b389e541b4687ff087f3f8e4e6a7ff3e101fbcfedfebef99f893699b542e121c05ab2387eaf39c0424c8c0efa1fb194baefea18154e5891dfc2e819d38819bbddf4da42351f3f9c14bb6945b8db93d187a81e23f5ffb1c0ec5d72d7eac5d7e6a34a6c63d6b560c15d05930f141ba25d648f46ffe05c8abfcf3a16c56b906994da29db55ee33ba13c0cade3324da6dd4623fc52290040a7bc98be2fb66d75c2bb55cea2b35c2fdb8d412d31aa357cca97651d2839f22153089490314f0b414912bcfa98c461b9062a542a99f24e217708530ee4504a65e7906a98498aebcdecec91343223fa030aa21661cbac967f13a81d04304710cc44c25a5e2e83a4000488a7b54d7411dcf55f9bcade2ccff6b43405c909fcfad72d9b0e5bfd819255146042c47e80372df6550e929c091b89dbe609d60379339330731227620ee809055986e692ddf479a0a01f50ba54f33dc17c19993cc009b8c66d516d4637e01202b2d5a086125e5d0c909b0744ca4fb5aa88d0bf9798147358257c0b0cddb3daf7338e5d4d61c20784eb29d8cd8091ba32468d3ba45329735036ed9421968904997a2b80831b8040b1c950aea2cdd8b8aa11765ecd5250c983b8ca73ea3e5bb6ca364083f4337d0dbc4dd7cab0019bb9ce06d16614363c4d9a1c31007843083037f35474d10aa08cfb2de63298533b1d76d5d79fb7b97afc85a62fd4710ed0b1653b3c15380dd0aa51312d905cbf2bc3fd1d52b6273daf7733fad26940dd98b81dcb0da2c6a9e8500e92f7b3cb59b49ab0a60d243db20ab5220aa7976cba5a265688edbb5383f94628fd7e5f0802c5c14327d5ab6a516522cb18739a2c19d4cb26b66253994f55f09c06f61ff6abb1f10c4ba64e60c73b324a5a9972ec87fc1b516f127bc744db5ae5e25d73169fac5e59897fa28c2892c82fd9b4097be6daad002be5f49a62076fc2df951d9b850ed8193ec580da9499605ef453a453fcc631f97325393a3d7349519bb1d87c88145d7bf1655aa2dcb012db04c62104caa1e59482644e6a0e2ecd5fad931e6b09dba3c63aaf5cc95e8babdfe8affadd3194f2ba8b20f704bc9d1bb50efd998d00dba40628b13dae419997de81df6d89d7f7e7b51a41977755eb6dcb44556390ce6b13d4d1d841e8b6ec4d5105075065456213fd8c20dddd6028fa68d427d2a44d6a7b4b99d3651ab253590465e3a70209968af27f14cadda3a653af911f7887f04beb2a2af8095ab92b5bf884d266ee4bf74198bd02ff549180fee24e2b4c7461ac4fb8cbe79179d07ea8a6691fe2110ef2457555b3607c00be9ab2d12d20fa024ac338510f91e34bef8f60855a4a86bd7fd69e4818a4bec1c50a349451ebcf6415aa618a9a5a62bca5f2954a5a43238cb586566f12329e8c41cacade1fc9af415528455c10cca1cf1b8272280e10d2b1abf76de7778bfa2b34c4d678fc16b7a420fd92fd5219d1e49dfea81630fcab4ba5e74fcfe7b9a65a0a7480e87aa18f02e2dca6e2ff7df4140b88db56ee2cfaf1584f4ccd425af6264b9fb08969377a3d4d18f5e2bcae3f596c92cd82802ef71929c8fe94c122b8e823633866dd55135047a45e7e312ae1380fc8149aff3bd20d3a7f21915dacf2bf361f4cbefa6fb6de3e2764015f0a3244ddc30b1945fc22ace282183f5c2e71a98cdabf7cef1b8a244b1571c01c416546cbf4e051c99b27959be5324588e671784a1b37f41d39c130f441c05f1babd3e491b8800830d20848ab28453dc824f49c1d3a3011d4b91638b001f1ac147fc109a876d008df12047548971a479abc44195d53e264a555341eb487d7131fbde54ab55e6d9b9a8023c12932fa0998dd8301d4e34dc9d036a849d93b09a05457f45d18f5cf49cb3618a871503a8d4038d3da653156492183e69b427993fad15db5decc2e8a5b317ef391cf73500d4d2c2fc549f84b199b381d7a823400cbd2adc814031be22924e63999fb4b349391664440697a083395078834790b61db22dc1d8e9dca76b4b1a0531e4161172bd42cf4a212f282b4cc9db60d1e14e8aa30f9052aeb08a562cdc9191de9a79016be00e03843a290dd31037fe964efb67de6b97183de4690037dc58a7f52d45967d3d3ec6abbcbc1d8b96c090e6b4bb4f9410dc49afafc88a59d58a3da224e7d9f528ddd3662179043180aa14e395b2c95c3908ed359ad6b3a5ebbe8b62f07b64a94e88a68a34776d522ef3ed7672be284290bce4b205d87fb128d79e514b2ada3a37e50479e08ea1a4d18bb76d5e55c1db24328abba8601a189ad862d63842008aaaf883d6225c52c25423a41345e20d540d7f01528fad5cfc0f4c0f14392814ac0c783fef1d3b4b53c7d9103f50af22661d805408c61cb23329915c036a940131d74c946e0c9fdd1d7f10b29bec56b603edde07fa945d54469ca5b5c987696024f0ae25dec46de754dc20daa70de94a8b54df242cede6242783a2097ba2d4e88a67aaa800c534dc11745900d63a611e2e9e05ba1d360a9d07db3ed8f48f72d857dc42d11832e683a39e3f4d0256693ed256e20e3136f97b70155403ae4d622ce25e40f7b933ca0006a1a61d822d7cc793d48ee4bea378c8e8398465dee5f781a6e2f41479b99df38c33306b0c4a75176d4ae9bd48296606959d80df32cba98ea005515beaee594595fe4e7979c3496a5f1a3e6dfe741c725c9c7b18e7a8492354e7208e0b211879efe630f12c10fa7a03ddf191aadee745349a320a520b06c6acf5c899428288bffb0e4418b9936224a9593aa3eeb247cc32c62e30f7dd74e9ac46e31dc64d41f53aae263b0e208d9184d61fd6f9e5033baa441cea3d58de526355061139c3e705259207ea368c696d2f236747ca00cb52c6e706bb8a98faa39b5464029544071f4402cf155a4cbde7ebcc4dfa86a1f78695b345dfe21f1be74f2a68e06506c94623804fa86394a0e0b44337a3839103bd6934040debc80ba9dbb747f5a3cee0581986d02af8681ad31861700abbb9a9ffa4188e92bf68e8fd02e95aa9829fba53240d57ea4b28f8fc711d9673adee762997fac690a3f8a14048d647980650e73058018494b15e0cfe414e8d2a4820c48d6954e3dfb6a48a9367de86a736d2c1ccf406a085ff0c86284456f507fe05a40071a590270768dee8c325feeacae1ab454313f1cee1200d87772f5b4fe0dd03bf974437cd3da6c7750a043a03ab415cbc393358ab9329b2c5f78e770ed8a949792b3b4ff9bd4f6313fbe25f7e366a8e828da0203509121bb70305ab2ba3d807c626806a6324f8e6d407c27aad3b307980b0035aaa1a78a706961fe4f7fdfea8c206bb07997d3c4c810d5edcc5dc958f903ae8defbe998fb8456a33bf812896e02957328d843e51771b81b868d3fb8b53457bc4fddf3134e9bebb296b37153381f198cfd03ca88131ee1993296b0a8f25bcad8f44747bcef4065b6c91ca29b48004531d4708f05cc14524de3130c562601a135daea6a9a8f85545e72437d9b43197ad78887f9a0c19fcd74613564e6034c72ff15fd335351fb2960183b2a00840c60c277c8f002b9800f3911c28354699572984b42e966c69cdb1de53e97341482bdb54fc17adde30657a7a3261f2ef0367a06451a3cb046fcf04a582fb946060afe5d9caa84451fc5b1677ecc0363f27b4ecee7ce2adc57c910e9e1939e433408171f09b038b70569d3b32444ea5d152cbf26a1babf464c483f9702b7cd7d69954d9b26a3b54cd15924caeb5775cab817a8d2204a6da5d451ec7d649bb756b6b7e0cd472f96f4f854da2c3dac25d8fe8b3221c60cfa9ca03dc482e7c5630e9fc8ed5b52329acfd3d0db92ca0eb22f9cd653d019bf6f4f9cc426e27071b2ef0ca0d2ed0f4e82a69b61eb9ce05006d05108d1b516263f1f712551273cee562af6da0ebdd54ae600d4f5a0fded2505acf91008eca7f28261ba3ea93e13ba3c883f3f3fce5f07c8b78a57e8c9060d1055a583af8dd8ccb2209bfe156c3bf8c8429ec3ec251b005b6ed216711380d912612bd92756970dae1c4e4d94f13bc1984448710360e0ce1041c04369231dee1f3edf24267ed4c7fd1f832c1ab196603d531445943ff1f36c508665281994933eda8e7cd2813cc9c4033ba0ac4bb31a20618e14cee7bca876e56029a1100974ea5176425d6d8d852200e15bec4947777a57aa153a2605c94df500717309719d8f5591eaab2208ed58f1f30232518f2f83ce42302e6819779515eece150f5a26ffa95c8bc52eafb0be222c06bcf025c6e72f84367a7bd57feba1658c95aee46d61672ed0b9cb4852bb57c4718e177fe370efb2bf40db44ac8c570acf02de315b8e67357435662ec99848e6b4c89e13a4edd4381efaaee0d31d5174efe7d7019190fe71bc9c7c366aa8ffe33807cb07894824f10113437cab833c13d74741cb37b9394b8fc8adf59bfba3d4bd36c52e3f40f511ac6f14b82d2d42ca53940139fb1273af1c2418987a7ac737be9f3caad791b386cc655fe1e64e43e225fbb1fdc94966d1b228cf000d76a7fea215631a0893bb0278039f3fc8ea8c1f04497790a09586aada429517bfbf7cdd4d88407a08799c2b0915e3d9c053ed0b6164f17bcbd0ced7a42a0c9befcc7de90ac69bd6511038ad5be73ea6ce5f5be9ba57fb4655db823c88b4f57231e1269769c172c089758e512db065781a62565a6e1daca3450d7436536d6c1c0cbd9e84bfc3619ca2294e41431a9cabe2cdcf5c690d6bac652abfaf07c47b53a567a0e8be5ef98e35c8a76bb52edf1d628aab7321745ccb6bd7943f103fa2d610288cef1f40bda302586d4709155376cd5eb6a8003b88b2c26d3b7258f00c7a76bce4ed54cedb5a6ba53bd090fa6d0d3dffd605c7ee87223f81321bad3013ea89036690e1f95a391ff69fc29fe56176cf10369ff1a87c216c2df5d0bdf2a9b88f6c2087f831d9d635968e56365c7a0dd2621a4ad9ea4a5265d0fba4305e852f272a119355d17789ad09c176bfea77eac75eca5a8d0656436bdd9c2161be4f0b6e70f857769ba35a7259f9033219e2e536f4893668fa3877d21535911deb9bd012f0dd6d2dff8b5a50868f8b5a7f37686284e36201f458da2803d00cda3bce269e42145cabd5445abcad5147b3c55237e004cb09673d9156d9759ee4b89f49d250e47ca1555f14ed056c89610f325906237c926f07ee445e64d8d614264304fe95ee088a7406a63616fd017413d320432996a08ba899c356110c5d17c31df2241efad7ab19fc24bb789c4e6b59565b62d15ec1e5a8a294236785cd59cd926945657c0b7b17ca12151203808725d871a831510fc0da6aded401667d0a84a0fca4b97ced8da07dfdb210fc30eb4d95d936d1bdf1ed7e47164f4e03c314e528dc515c6eadbe5c72e2e4b3b00e0e7c22ee182b8b338d9030eaca18a2427819342bb1ae1861907cd29b4e42fa0a78e0a11ecb330d0fe275544caeb57e4e56d4265c426fad4e240e54de53119f284a892b660fdce0160ec3407798f26980c956f14d793ad2ced8c3d908f5917dc8db717d46279b180165c68f7bb516f357a958e0221ff53331104bcb56ccbdb92329791397c7c2513a679f867688c3b1f73da7918d8050b356ea9687a5c465aefcfd07a5946896756a6337c7728cfc96dfa87461adad2bd9c4cb1ee4db8f2f358d5b59039f9944bd67692435d3fffefee2c124064ded7e09757a8dbbf36c059d869b7467cfc837c72a1ee6e08ea5850cad45da044005ef57e98e4102e756947987323e61e25dee8ac986db33b83b5d672467c308511682094761aeab81b919c8e4881e3690f06a638e40c337b2ec07e35db5bdbff3a4e3b36b23aa4140f88149ff9ea5798c12d5a1a6592902f0a76b377a924946479cbb9262878476985e8aefddfd1ee4c0c5aec8fcff2ec7aa2eb2d28d84f7c807c8812b736d2c01d31cc687f39cfb625b69ac8ddc2b3a02f28baef83335b6b0af64ae6360a6c6d891f86f848a39c8c1c659f0d3b155f129b13900927705228b4b983f448692308edc836fffb0025e0cab41bd24e0870f2f2bb116c2107988a6eb2519a77fed1d506026fc34d520043a24d33379d35a69a2b95e8f03f674aab27b82c53fd41af35fb7b0ed930109136f84cbe22692f9b59bb25bc477b4592ed4102bff9f218508909661022d8e4dcc682be54b631c56c62ef79d3d6f7406d0ebb7c283d0a1ff379f6f474045021a66c2b547aabb0d8a8c8209edfa92fc45bc8fb915ffc4b625c1a7a8c0d26899599c9470aa6cfc262440a0ecd5d016d2b928d914ee71f9e2f6c19868dd6976651b76300e65dd6e9c573767d8de20b95866a8c902da19cb3013bf9490c4008f9431af7a520f202bce4c66525cf1bbdc42c3564758ec39bc9d95bcf147264aaba10535851152e61a684c3c1701384678c07a02a88b3daa3a23d3f8b0f3732380c8b6cc4ef7f9ee5ac7eead59a04422a504528026a8b0ca0441ef45f80bcdc459cc3c0d99a0246d0eb131963152d7c4ce13ccb5085584177e432c0099831d0f54be504e318d80f7fcb07cc9abb45457301480d1f5c4c4ae05b673ffc536b197de4a9971e5553f97f6d44464bb274b9f1ce2e27205514e61f1e9024f92757337bd686ad2088e17915d75079f86fb20fb2730dbb3b5dee87ed593146b1320eec85335a3a0aaeee5ae9096cd00cc80b6704772ce20e38b609d154efbd68d6560332d5ce9789f80d950cba39e2259cfa06ffba7003f11c6ab83ff1e3c73715b59c42bae8d712b521b0de0254d13ad9aec16a169b1feb2c6f56f4a914196fb215341e3e8d3334d5fb6d8d6cff60dbbad7f679d0d5233ae899c3ea97973574a0dbba1d0f83b83556410c13bf58c82822054ab4eb02beca8cab12bbed76d1a5c3e33f9e4dd39ca05da66c6af36a3b579b78091ab7807985d8a67842f4ad36ef689844c32b467fb4158f399b1119d3cb688cdca665b9b9a27356e24eeae61a4f8c4e23cb02f2f7cf9ad487dd017db9263b27fcaa2d3c8f75c6ac0f458ac5c96dfcf637b74bb22c21b79f3f92c96440f70e1f7eb6aa00c530ae079157c8977f8ae26b72325e49c0d93713083a0e3862a10b8bee10415f7a8d8aeebb8526d5214fd9920024f094a0de47b90bcb2aa751299a7615875d292b35759adbeb8ba5f3b54208bc20c0969f3d1f35051d2ab388c13bb8a477831314c8154777f644a1cfd0124a875163bfe57972861787f34b789f0ac09316f7d55d44890b36f7c6ae94ab3a9cdd26eddd1302164f7f2bb61786e44d4986b10b80786420ccd8824fa3324e9c3d74f6147f5e12b252a5f036d79f55549aa953b94e08365d6a8de2cf61b90d77d69ad6523267b0267f7cdbce692fa955dc9df8b6c2377cc42996a62615d8957ed5ab64676a47bdf1adb6406451e56d8612a14b5154844780133d81e18dbd52e471deb29400564c2e7fb6196aa74ed4f200d3a0eb151072d3797055dbc9d302e71f96920a8c834dc8e79a565e765187183987162b77202b406f6d7731b5d159bbd53668d3c197a3c7a4311725b6bfcaff00f49ca171dd1e74ac15373700d21738a437ca2da9ac4cff28a5493c1e1ddd8b27c6be41ebb83587c12ddb5158ed4f5ee787e31010383e3a994aedf3013e13517ba03506bdcb949e27783f72ff682b0595ed1e5ee67d570005a9f519b2e8d775edc8b77ceee207ef83aa61974557d5dcc8b6f9bbfa83d0ad07b2f7b6a01ed728684617842eaeb4fd78f2482bcc69ceeb67718cdeb5beb353a58b1f7360a92b8fc594b40a398d4367b6387ab80d53e6197b01bafc648819c414e4da8240315196c12e4a34d32d1719934d702aa72703eec75a7c647eba77c5cb0768a53686b628565dfdbe2769e9f9f12874809e05805011003c2dd40a8fdeeaa9128e0c0c56f321bd18d9518f8505261088b3c9acd6067236d0516136e30d1b511e0db2a319cb9e3460d145378997e9a6e450fb7d49cb2e0229e36575ad6de72de5505500e409c03f03b8f3457337e545ad867d74b8336d71293f8d9c267d1fbf924a73c60239eb4e822385678e30758a8d6705429d4c14423f2e3b28fe76acbcf2c073d008af375a5752a5ccf4a6a4f6cfb46f8eaf41630665c6a44e75d3e800e900110b6e2279e9e31a11f95dfb714281a12df5db481f7680be413de8ad28fbcb95a3d6180f7173560183820bacb8f0e8e9e6bf3d565f79e1b0ac9174ddaa83d8674987f04fa238eece8dbdff789bf9618b88631adca8e1c3c7fff2f6b7e221ecc56425de222e4a3f7c40fb8395ca4982452d1b3a681411276ec42675aff13a045d0754cb4992487e8f021092d513923db3537fcacfa2315c5a0affa2aa197229f0504065440bf54732c86a3bdb24ff9c84b9a9fa33aa6a2afe7154e633768c41e4a7a267188b3751da031ef821f29fe0052ed1fe2c7d5a6409f4876b904a1acefdfcc5c4fafe6f8a59dd32421c5945815a1a5067ffc5137257227dfbb299c5beebdf9ca502e43f22d704a554ba0192120e6a6f4b877cd23b2830d932260eb32ae5d3647929c9ac2c131451cc4f8f8e9903b12098a8efe27424a38cf9739b4d5b53b0ac4c67b19104c0c75ff070e0bc8ac19ce408b6b840af8ad0cb7bea913d24c7b77e6563853b72f189562dfb5897a29168fd7c98339a4079747b5640793a15a65fe2071f4fd82a9bd0cbc929c478c3ff29d3b8ee21508f474d95b796f4989938d4a79980d47a349fc1aae193a5479267af5ff03b2be3f08ee835cafc0ef15663912bcf9bcf98347b314500ac52abd8e25ba59d75c481092db84c30c6bccb973b314a209d77ac3dc74d1ee2f74a544cb2b015127cfd5839308178415a62a8a191403da26f165656c09930a7ce8484305e97e86e3037f0bf7753872bc63c07ef7803a17ee2fbc18be8d33298d572cf3e5319ac5785bc81a40241ed6daff2e6f796862662d8db1a022beeff70393695649efed5af5e8c5d96473382da8cf8747f986021ccd83ebc16962a0ac389b3fefbd5608d213891b58b4890b6a28be35c493c2fcb962355d5728f86e55a0f8bf7c39a49d288eac25a190920f264cb8067e88ce53c5a231264a0fa7cc311aa45eabafffd11ec36a7e4273c6ea21f83c4402b3aacf4527e6fbbd016864e3834a248a34abe22ade1f689cb08eb70e7e34c41419f2f51292809bc5910e4437a076424872c3c55fddbf32abbc1b99974ae0a1be11c990035ceb50e0ce480219a3a82750fa13c8e00a74b4ac8f18caeb4e99bb67a2f13c298a8fe9873105831b476535095c5843213f32b2ca01d6d362d025516f6540741f0d109b86f5c59034e4b4ea15d0ebeaf398b45c557ca60a094df2b723e5c7539f2d096173973c216e820b2b046e6020e75aa92afa29bc289cd067a17dee3725c191d4f109031c17b7d939c3aa174a339d0fc9ebe49dbeac18bd4c50521beeb208da03d711ea9126de9e611175c84df4840cf7007228f512daec563447fa1816807dc360f7d0735309e601bc0d6d2575efc331bff5f46e4360275b943e7cb69dbc2a04ebf4ab8bcb523922cb2a8c6e089779739faf8a40eca7cfa4cf7e26b1c073166edf713ca06f4f2110cb161bd38d077578382bc68292829fa52b3b2aafac9c5e14a7778cc9b256442d6015275ef4d7bf378d2b244c3d7d0c3e3c05723f87729cea6620f5c56645cdccba6b108cf976fbebc2e1af4d2d6b9d2df59f1e6348ab9565c98c36b346535a25797ccf00b8187b6168eda3a8f0104e812157bf2cedbe740e5ab357b4b7cafb29632bb6274a922266a784d2a5e35a8fb2488568efb669e46397e305454a5224b4772c2329c8242641629c8e249a1f9f0d90f862a3eb0767c8d87dfa0fa6fa06df7787132ec42fd44497eb25f9e14cc633a4b5e33931609ff1018661004bed2a3850a62114de4fd0affd5cfd2b7e4f5173da80b9de4db86e643e81d97af2b15c892daf2a32f8b8a1ac644a4fc2cf51e93fea74d056f4dabcd9dfbc7e5c212c9d1a064c29ba16a4d06fe618624dda9db00f2cdd1a0fd39674b6ae871a9b1fb6efb5cd927665517f8f7a0507a83ee1aae5b15bad43460317c7a8f047588a341a3e2f4803f8da804759f0edbabf1cc8885feaba1d84d57b24a6039ec62755a07092e58df33fa7c9fbc27d53ff3f246ede2712c79640d74de8e843dbc7ee3add868e8b24b436704f386aadae8ebf2e1baed7ab3c6642e9c5f73c261d189a9097c811870cc90e4752dcc69b1e8541e55d68e0794236445dba1e24eb06de6d1e74b453de1d73664887b5fd40e95b6b7f1e2b04e38105bb0afb23877a420bf4e363335f20adaa4eeabd4b65f7e7fe8da717c98b842b9d72cfae4339d1842f3d06edc2260a53dcb769f548d5157ac6c1f5d9709bd4a8805681be8e20cb485c41c2eda510d1ce616fe53c49d6a182119ada3cbdf64efb4cb17226bf95751878cb2e8e019731bf72fe5b28e25e2a8c2f918fc15999e3793936d4c94921d3e8cc04e28250cbc228a18a2a8fa40bf627b895cd31d1a03202aa09efdbe6448c5cbd83b67dc1e522d0672e1097db519c2a0d86f16cf2933c43944c51fdedb350a8ff522e36c1cfc10761e9b431c56656304c3c723da330364b49aba93badb0293adea114af14e343f2804ad637f464c73a5a9d12c04c9d137dd48a702af05cc718933d6b8f4f8ed3fc328b63e711704b0144a20f407b6f3bd60ad4be3d513b04f57f00bb52613fdf8ec11171c7d82ea0345aee7eea6de894b55203615b8134b8a17432bc84448afbf30eb3826ece53d3b74d7c3dd778b2341941fe735b75e2bdd1a0a3d21a1f23858d52e5b733baefa8979be0c2f1215bcbd8542f8983ec51106feb9147ce281368c9caadd0ec8da1e9d95236144d29fd3b5c4351206370fe29759948fcdc1e51ff363c93b5068029e729184f77304f61dcd487bc82ac26ee49e7093dd49b74d326ff743a1e8c1dc410101c53c4f9ee0cf0ae473c2df4e34485502bdcc731cf496ce60ec40cee1282ade8cd376daee40eb9fae1c5242a4c143b6e65dadbff6b9b015642da56f1fe7b33f32787e8f1027e1661de4a7004e22391e9d3c8e9cfcc1359eebecffeb4d604cfc439c6403e9ef79fc7b1e44655c31e691ad67094659c077094a001d716cc06d28358b50611923d50810b14d468611869e05a52a3d63c61c175b6002276db03be7d73eb95e723b4e594a6367be1d01dcf187fa38aa74457e3dab59f64013e5b61459b5569a839b59a65e51468076afe4925b62e16517fc9265c9bbbec80316f42854016778ac6333733943db2029add8deaf8300568fedb0ce15ed71b1fa5da381a2c0c1cd57f82e01c7b563359e90107ba00148a038563c6232a9315b87759d8e4e14095ab677e1fe5ab0b8b512f1a5fdf38744d1c58225c3c8c0300a5aa452851447bd0f6484cefe753cbbf198339326de0617366c65afc134e7e4b53932edfbfb157a4be06e4e1b96af15ef08cd4a9e767f60a1806d77c5794d80e6975263ca5c934d7753fe8c8df5f46078cc19b6c44e5117f6cf5080a4a46ed5642a416516cdd221d6504a563ee8f0aabbdc2a5a5d526d2de508091036d6b0d0f24799c08bf57f309573848f2a1202a6c2e9354b8b8654e42e3b4780d409e99f7d1984d367b14430f8d49075e1fa928095452412166b6baebb8d314060208aaa26202d2655ea2efa521e84fd2afb3034d89eb6b6c9b091cb3f17ab81f0b150157f6e0c3b383ca5bf1b9871b908b528e6db0271c9cddeb41e28f4f4797351eaebdf80d21584a77ebad47922ec3fcf4bfd40900350239ca144c44ccb3ca7105af63a8716a27c244200ab2849b5fec13cb8d385c24b3629cfa54008afcd46406e292ea6d42bdf9452fc6fa8026b38fd2849a07d3f0501a4b1916a9b83866890dbec2bb89dae4618ac2d0c1f7045a6839e12f78d8e5c4ae302d0f1833c3d0ecfa10a686e8cd5a8a7fa53f2c5121991f3c054ee5423b1a4620010e03f2db5ecc1fb9c2be13fb8088160e5333f19fdeaf100f805318740647b8cc1c851af9936160e941822cf71dbe6d0b5784258776bf6d05c381ee07901a1d8eb1597cf7f5802b39dcaf529a2cfe0fb9266e6d41dc405d12667a7c2356043e233f90a081d337de5e2b0072c079c7afd7cf318a3b614e286e49e6d726f330912763511ffac1e24fc5201bc61b2b7e8ce323996442b9f85a01eb0b0128117245d6b75186cbefcca30f2b8ce4461fe607af21fd80a89ba078b3e52fcd55fbe4c50b36d50e732068428638f889d593cce90e227b6655c881aeeb4be25c3b756431b1f91e1a7c13e9ae8284ba87af904fe80e9938bbaa9afa4e32bb6decae2129a6338c30683a9f5608a6c58afd38b136e70d84d98b22c6792a1c66b2b924beb8a2d1f4d32d9c3b4a86cb9903f24f30035b3ba1af3a3d2a2dd282b762049fb515f5dfe34bde9765c138c13959d835ebefc60553918682e6fbd3b99d33d31582da78ee9db65618da9b275ac71696e4927d8d73e977ddd11fbb1ba24a66d68314c34c14c241ca7db38e41e05c11be2a5e99733b9cef477eb97446504043290410480ef53316e6e2ce15049dea57e7ffcd587ec5dfc36da53c6f90fa43e905fe93f64b538667406a79feffea85bcc278de10cf163e98cbef517a63ae0d7441a2fe2959ba4f4a44daed445bc4343f78ad75a295b0ed6d7d6b8c843094d9a8f061155da103c1b605bc4dd35d5a44ac951558e2d56e5e0fbe9bcdaad40a8d4c135dd8f491ea03f2136b9199d7d33dc3ca537c9e303fba774f463a519286c837b85a9835b45fe4c5237fea4e0251245742a4a0a625327407d67ff187f3e3a4c926dcb2315e877474810e6ee7febb129660881924076f928a85a7701781a09b08d64a8213d55f73630148fcb8a7fa9181a3b9933b1f585ea0ad8c8988ac6cc9b61a2da692c1d726f4de4a7f8c01de136255e2c71d464d4c3b340971cd3fdf28a3ab438802ce7a306a82f6aa7f9fe3d3b142f410075326822fc9d3ea8b4f59f101625c734d1fb6b3687ee48c4569c5aedbef01d2b421545dba0a25db066d41469ecc74798315c29653594e0c292b18846529b80decc59600feb9d7aa881e917e9ac26e6ae2cd8c2ee46784e58ad6d4ac141dc4abeec205474469a57f741c9696d55bb3401564929f844524ad1c6bc4ebc47d5f5833f1e208b0b790656776caa3be57cd85a5a51584825971ab3d9ac33b1ec48f976ef9bb60dc5bdce9d4b41fd54592c62c70aa1e3128641b301897481b8c083d2c021c3cac72251c0765ffb2c09a2e8b6d1871ea6173b478559cbc237b30af36aa1c646a7d3c3f0958c261aca1b5d5398e2e0a390e2910db47c5b01cc77b94370e766e10bd469c09e2a48ba435ad33bb12245421a640c81878c452f736ad8a8d5152fa5ea596f0199d39e0cfac2944c8ddd03ee8c52f36c6fd70a9894d99c1f15ce25d45bceeaf0a18f9c9eb7425a4e4a15b4ecfd1597d00f9569952d51d0b6c4038e38f5ea90eac4f8d2d3499a2609a9ffe52379e64f5e431f77e4279ced7dc5a754509739b1f1ab8adb567a232a8c50e2fb253e18309c2848bbc43b8b5b46350e3a6b635b55ffcb037e1cf6231cb449d408aca51a3755af8bfde8d6cfd38837bde47c9c68252f12e4b56fae7d2e80683e0e68d3507677b688139d0e80527088f3dc5d98066611caa8e3b09662994952f0d42408aa0319713acefbd60ffb4e679bfc6978c568070bfced125fbe31535542cc67ecad20d0e225f3664889f6446fa1e44241b085508d16937b528d26a482c8a1071791287fab30a21423429f9cf5e1221e98453d8f363b03cc7c9475f0d9395a043c865b5ce2bf8ffed6b30d9d6cff35856f2dfe83af9541e6a0878b9a90bfa81cb19411494f63cd48ec3958b4c78bea703112e7af15235615f320e245b777b1fc23faf7e8c7d666afbc44d17b93399c636e7a1ff0aa8fe9fb5e01c38cff981df817598a57b4ea88658afe03ff7c305d6417fcbc36ced6feb9da0ba5d39daffdc9da3b732ad78698736b3fa6acd55ee710413b0da6d310d589028ed5afa29b6b7a939740140b68bfe41560b26b731c7251099b73ce50d5f363168d97a9ef0198ba2f2b119a1bcee339cdf81dc18ce988abbede70c3c6cfd825a28831c3c2fa63ee136315de759f25edb76692287448c619a13e01ef1b34a3b32db5eec77fe71ffcc99d11636146a8825fa6175f0a306b0100eaa88c4dd53381d24197f28004246a66efe0e7ca860b39b942b58d030c220300602ce699d4f3076a9261423d0600286165bb7020490090ebc5861d5f82cee24438937f638f13b83851e88dd4d0f6adad22053e28558a0c0bc370930fcb53ea8155c29ac589d0f9b9ebd0f991781f166989ff4df6d20994e22a1ce31efb5705fd27550b7bd73796fd242174c6ce05c4fd1f6349a76f11e9f1d7a353e9b96890a4370517a01804b4c894dda9a450d48bfb66d521b7bb9d0ff552a4f9f0228e22d60f85e2bdcae52e4dbd3b80c52c187daafc064410f34555cf32ce7ff95f8957e414b76b968687d96739aa4febca6e0f986d2c7adcd7bcdab5498b933bcb46c74107842c6345a3d3243942d6e2e20892cb1a3dd85018b36353e6a0d0db818dd2975e63dd7b8609122f90750e24f2d6f409b902804c66b30bdb1e4a8dbdd20bcf65076fe946bb83ec22e3eacb06b42afcd9794420bcee13b50eccfdee018c1575f1efd5f7892fe92ad1aaee3e31ea19733d9eceb47a62ccb77bf9e4e7167db3452d9f04b975ddce83b333cf4ad14c03da0021b000db27babb9767ffa685a79105425a8e49535a75b71d17edcb56d817ef83a6a00f223f21b1e158da1188ec4d3c549c952bb422a95a150d8510f0e747c5da206e49103b5d7fcc8e9262217fd7b941ee07f38e101b3db086dc12f52db7c076cbc5a3c95bd0829983416c21acbeb34bfa53984377da312e29ff9aeaef2f6939e191c86004695fd3d6f721f784e0f48097cbabfbbdc0e0e6e5e28b1f2851b8e9a77a0de3862ec9a7dbe8454e5d8980c478fd72312c0e5b423dc1269098f0bd102299ef7a3b42ecf392dc4b8aa4f074388ec04c9620fbc224fcd2f5a1a68594a220ab0de148f6e16cd2bc9accb64a1867c0608e0033d1d0f234a08106c7b4bd5e4a3f4506d03f1171a8e87f3a7da948b892afb03f8271c5625dd29a5c4932fa916b9f8747136b8928af3ea14e56466a856bec83e0e1e9a282c2871dffd7c06cc9e6876b0fda9146f7bbefc66c8209894b655d41c8c47a8f5254adbfd92bba5066d021551d65c5f2e357799d845acbd4b4b8a1269054a1ad8ef460f945ef4cc0ffc0d210cceb9fd472dc498005dc5f6eb49dab85c7406ebdcab1e3ef233a221f4c8c189f2e5edc103794e0a6c02f6f152770941e939cf09961db0e79472dde9f3f034fbd43378590c8cc763802d4ee5b063c054c82101ab3146c3c0eed846c7f1a8a3f8a523a809e57c52d69f0560ba7054ec32bea88a0272647bcb8f90585551395370c9adc5f105873d7f208ffc9a43e6ee188c7bd988a856407499c080e40795e0b07d9c27ec155302f9c13e894f6c53f059c727fdffbdfe80987068d405fd0b762cfa19f02dea7b718cd9e84495efce6460da18508cedd084b019ec45ddb1a0b00120a28365001916e91b527db63f082d80fec9f7c09f26a8e54a2c98cfc187e7b17cdcf0339375b9257b8d641011b185df25885deeb68fdb3a4e5c018929a88af31f4186cbee7e514eda4e14e5c07556a4fc0b3ee92ca59e5b7d0515cde57d091db5abbdb33c747cd0b50fadb7dfa029339290adbeea85468447c4ab98ca789302ea0032d73e81d719850c166179bacd82a48c4dff8a9d6c783a84c0bc831dc1397fbb1385605a8a014bcb3fddc20fefc051d4fac88daaf03ee6d5aa7d91512f95a0620df84afe89f2187d1baebc56719e91cfb8e231eaf5a03455dc2f8e3803763ee1f2684340407972f2b1a2b037f667852776bc82ecfe7e63fd95a24b170bea4913d2d9c57436adff5abaa92ca01afd104478ba1c70d5d588878490044ba4f7e73b8e507c79c9f293b9f5f5f246aa06c06c2191273493d47e0601f511b62f7bbbe847395886f8e982afaf2979ef189857c334353ef3996f9182274e469787b6bada4d5ee32e21e4ed30d55c39879aec4acb5e05aeb192330ce3747f42302b61477120c0123f24151e8f43e280055a2d1c32bc0acec773c2d819981fcdb9964c051e66e64f332d310238ab2b1c19adc4ce4ba7edf09e80ec28528df251b00dbc716d559e563bed81fed6f53f3bb11ab9f27faec3f82b50f01c1fe967fcd6d8cf82c3c31840eac89f10824df8f546c75c487e388e28bb2f55c7688a9bfbc974be7c08c8c8631274ed4ccf52205667158d48e8daec5df2f8b7320e1b13905cf8fb5d2258c0724acdc1e90c47b5d4570f0cbdda50b2ebb1518042ef522bcb0de3a4ec96a2d1bade345b93650930434d93c83bf0f43fef1bdcb9e1de39e07252c5cc523fe6fa6e46202479f939359f5693beadb30cde7dd1984e1f3ed382de74cd14844d038b7be6423986cdc1525c8b38cc8199b21f2e9615ad39c27b9032f6c8b66565bc7016f44361e8620d0930096b3704bf7b4c9372d0db1cfe6e0c7ee5a85fd3db6f9cb7a82bfb5f5369c88f86ddc7b5f098446898a55bb5aa95e0edc48e3a8e0836be815e7eb189b62662689a369a73f6a7f84ae83434188fc1f12a8276deb187c817a8910a6fdb31fb206f560fc42c61cbe0280d000a9c65ffc315fbc21aebc355533afad958915d41eee5fdc100d20bf69d69f363f2a6cfc5d06ad0bbd3ba54513fec60e3df6740db1a15954d5f00643523758f26b375cb1869ae2dd18b848f9c7e0713d7986abaed628df37806d4e8c3a416af30d3afaa25011d783a2ae59851a44a1c28f37443c0514c7d0c88203d100ee75cc9e0eef85928d09185109b5e46baf75e52b2f0e101867df99fe0096520369a676e98b91c8b5e1c94d1f77282c14b38cd6623c305799d4a0fc9713f83bf0980539967d87254d39bd2be5cb4d15f3ce216700ed1d06a1aee3cb410e2539c4f1bf3ab592f60076e73fd5732a41214ad309b68274a2ab3876a2c5f1e507cc7520e50d9dfdc4f0070d742f0b16a51913f26de880c4ca539c50e19dfc9e89e2bb8ed98802aecec8da1714f00fb42fd7f3b7301bcae10855e3122e5c3539d5bf8906bbddfaf503e8257d3926b004e5e556227ee30e02ad85aaeb78ade49e3591a5229907bd8328c9d96fa6f365d6f32edf965d673b17c5f0257cf43de5609547a490e91fa55c00f33e6d820969d29c080af7207a7f94ed757d5f38a8311ac1728e90b04682d0fc86c490ac3c049797efb1f610f0383c7486397ba94c5c0dd4bac94906f720ad1f3dc5586ddbb73a2c04b1cabecd7d846959d49ab6d8ce60d8c666b44adeba5978700fb56f76bf8fcd071886f59197cc773789bd0a6cf49306711efcd4837a02ffbc140fbf31505dff2d9f1f89903759fb73780ad7f11b4656a1648b06daf18f1d0913094743a2fae057e2fa0200371a9c972757b13745f0b6c84ee0a1d529134346ee221a7820a4b3ab1220cbc02377464e54e2b2d037d6d0264174dfe7b8a598bbeb270897273dca967e39fa40ed8d24b5ced6d729a4a55424f8665f0b19eedaa0b3b54514f05b7080a61e7986f76c07083617bb4dc420baeb5d1c6e24dfef9e4277ba0e4b4c1eb7665334785c3bfe1271a335ccb044045daf47f6a65da994fb27224e35ca9fe0b4a9c4f44ea5a8437867f80ff77ba929fd95e1ce1786e598ce86913a2754f673200ca5d26f9b341cb5800d966255b47942ac36f6af25828b870ffd9e8fdb0c7c313e12636486c0c45276cecf5c3ceeb8007fce1eb316735b4113ca95859f074a222dbc34a3cc8c0e367bc2454b90df4ff2f94733c8aa044ef35f9156ff8c84d6d09ce81be72e86828e3ff241fb7a26cbbcbe75d8446804a07c0298192209d0653371ba97cadf234fba9c0db58ce0611ab328f5a0a50aba16f5e0e799cbb681f3c5144d112a98ec2901c4300d14b13f6fb9f1fc49aba5ab9138e8bcef7a404519b70bb6da20047a417264fd8be11ec2bfc8368a88d8a1cd9ed97fb1756e9ea1ce81bd6a8ea25833072b743afc0ad7964535659a18cd3363ca5a8c92d8f25450d6c21ed99ff6fe62aa298112bcbe48e428aa08d1acc6ca68cf8104a8cd27c340cd0e10522e9a05d109b108f31f432cc814edf6439991d195233d8534ef5d036d09076eb7fcbd01ad39ca704c740c10be012d7548e254ae553e1973443584def1b6d6c564f05d4fdc15317b1525eee1631315338893478d6e60c2d3c985cea73960ae98e0a1b6a35597aa3b673049d5ae8fadb4b42f4437bab9693727aff7d90be42fafc7c0750f1520a94e0d578c854712dec1640256270f3ee5cf201fd74e7e28d08e6d3f4a37d8620b486260f3569a78b36e3867c9e97bede8c730a19e6b94209e9e1cea3045ad1b03851c29e75204129827c73e222c415efbb069a391b1151f6dbf2d909ef5e8a5d435293ddae93d01ff35a15b029048ed62a305ee5b40f855a0553079daaaef813872d651237be3948618149620a3713f1001c355f0e3a52f3bc4ac1ed07345cee71d2fd01f2e477b6c44196b7bd6ac4232a4afaf8ab11cb1e5806281e79ce721ee04ad2567ae6f57330f51845ae0bd43981a2e91b7e1768150fd584e6bc4b130177780a6c0cb3381394b2ffac919ebed4aa67f1c2310d58c09c33419ff518b4018cdb4501abe6d5017119c4fe0f987e6128cbe221fbc37459af10c0122274c4cd9f14b456f3b9205dbe8b7c12e6867c388074dfd02b818c478ae371e94efc2c823c6b3033ca4ac7274aad3c0fabb7c6e189f133d5ded7907d4698347207984ff661ef64644213c8f235ca68e4eddf8af0a3d857a10aa99ea1cdf8cab75ca8bb24c19a7ff9feea8b72d9e6a949c316213889485bbb3aa428f819e98adea40461d4075506e1227e4db2ddc0f901225b28ce871b135c86f21526ef612a0f8593c09a0884245a0cc881fdbc519e9d00f9ffcc0607908a8ddf7f9ceb929158885e5517e6973fcd28931df4587005b0528c626e0193fc76b95a94c65d44404728bdfad67b878dd3f05c76ec5f515257c5f119c4c8980b176ace38061d86e16fb1135422049a0822387518349a988a5dcba32388181a0f75908467b7058686cd1632b7e26e7623b0eab81db27cc374d5e222e2d3f66e0dcb36f4990f08e4c209465100beb5565ef7259dda40101400d4435bb1f6ffb36bded5e1090f1584ea93ce3cc7743014b9272271f412d814bac7d9e4b48d8a05bed2929de66b53342047f5be526e1b01a5200f3582dcf2d64d9fc879fadd1ab8bed6f8c486e58d808084b520d076203543a27c386259408987fe38e867c2976a2359d1462891a3c024894c13163f148e044f9554f1a640e34d0a8c0eeba6694a8cb06834a9fb5e8b2610d928fa4cb86fa1bd481e29bc8e9600f7177347ef98151075c1dca113d45a2a6dc56b24869afd4ca058d64f3bce421eafc6779e25bf742696f136ba0f54a1e0f08ab3ab93c81576a2c3057b232539341193319711cf46033b1831b43426ba624c016793ac57e9c90bda51a0d45ecef5b087ab32947b6fc4001a6ce58299fd219bfb137897191f487febd43df673deeab8326d5aa640338652bd6a1d05f3f099849ce547e8d9d26cb48a0e11f74dc0915359a1e3c68e9c14b39690df85d883a0c7a1ad9728cb8515fc3c677aeba77ef91827410ffc062479926111b62bf210199c03f53f87df98cb17aa84d606a779e8ebfb7f0ad218e172b072f6b565a2de33b262a13de0b9a2d85416135352969a26da75d7eb640a387e4e8ea2decaed0cc9c525a2d3a1e254c68e6a7c30c25831445197fce55441f61b7a85e8950fe9b2695423adaece64f7eccc7993d9cabf457a76c1b420e3111e7d552025362b271a1bd8b8f4521a9c4c018804860dd4d0fa2f796edb970c314a24651270ede7b98c66936b193a145497699f5d895ca9e28255a698027c12b98aee8225e0920a9bdac1d9a77867d6bb31a6fd61483ed4b5563f52a890f3557c39ea2ad795736e56b7fd0a23d3618150e2fb4db338115a231700a28b5dfda939b54967516a108380ff47229cfba6a993cda3540e537bf9516707984731282b42d54c26cf6f31602faf35007b7a8eeb3ae79bcba3732669c58448e53f0e9b54173fdef6542f126219747b2b1860a2a6b8ea01fb1deb8079c4e41962d3c002dabcf22c0014ca2c6f51f29eef2a8139fa21f8fefaf5d0d9e1afa09413de4d538a6fe08ff0c9ce4719728f9b2ce40fe55a2f5c2828611f296c8ceffc0a48d13aa730f7566ec51342a8957d7983346ea38fe59fb9b9ac057e448a87e642ebae0d1fbdc992159ddc69552b30b85f98629797b577808a9adf17e60e1e774f837f44972c62dd00122e0209e651ad3cd22c8bde29d4646210ec98030987186d3f03cab93d9860fdd11f87132f90bb18cc46fa7a513d346230ca0129fc090887cb3e13a7587d6ae1cbd7588f24796861eed3435f0a87264c73826795c62cd911053330503e804e0d345c8f108f6c9cc5a4a6a79d5aaf8575128a1ce89a663d315728a656b189a8518e9838de8536375d3e0ddc06628538ca69d806c601bbd0b6c04e23da7b50af2e0c3eb044bc090dad314f40cb586e6f10e834532a710fe1f6af59004603091f9be08e2b91a3c494795b35e6e768155ec96fad156c93675f264b3a0e0868076df0a703120a20bb4bd2ad14b85ca2213265286f0171f9cd6f6eebd23addcc1dcc031fdad106c49e44c4c47a6dd9d245073ad13a2e17d61a30ccefa9c5ace7bfed7ef937605086e4a54cc6ca3aac8aa15c43a1be754ef1b526d456ad6599f0892602be7e37a827d6c6afd6d1d906bdb7b9ab556a28656aea09c1c03c39cc13a4b676c24147cd7575128b7a8e6aae36594c1bf441add21f72694ae98e7eb0400470656cd23455ed5f7dc7ec57221319f373d145ba80650142f065a22b9b8cbc6e2103e6d72bdf9f52f7a048c2ac74fe503f12973645f23debccd0e77c376b04a50bfdb9f9549847e37b10406c2084482595c36ce11f1726a383d9e9433fd0e9203a171bbd59ae0c56d18a4ec06bc88d5dd182c38e85f4bf502a5e36d0bf81cbb3a0eb96c1ba48bff12d39bbd0214974a8b844701283b13bd6818a8fb22254ceb00e477dec8b9f3e656d7bec82ddb79639948a71006e2e0a0b78177149a3202106f507f59e2b58443b7f4cde8fc900e4b978cdbd1a61e4d1d6e5d22379f1921e3caaa48057345202c672236f5c7c1f06ad27651ee000bb7c032427d30c6db0f4e5968bdcf5c3e8073fbb0ef8907aa6dfd11b6f9fb7007b6da07fc845d41eba528c5575774efb48266ee556caaa4ad5a4c51f9b2acdf88a563be7118eefc0079356d28849cf081ec24355e6845d1fa139352adb8abddbbdef467d946829f702bf4b3a1ce0f8b64406418c176a6b91b40af1dc876207cb757fd5b6536e500f141a57a4a666c0b1a32d49da040c73c34b46d27f4ffd9f10ecc2f08e6dee44190cef2851e690c7627f2bf5b556bc121d91327acfc9cf14d5ff4bd1244dede5402b14a82f7f5227099ebad60698db2353a9bbc0d458260979404b739d5caa31e6442f440e28685483e83420c1474de03c27b1713759d6f23866995bd8eae4ce0a098a2e3657df3140a9ece24c13f20e77a4948550a23994d6ce89154546229798b9d4af8128d50152b240dfa7b0873ca076ce27de551dee95fab33cf846e985a31a64b66a764897ddd76356bd03795cbf05c82a918ef2cefe73ff87517768d3f34a4ab8405da410cfc046ecb54d66a1c11d2e59104a9fb41ff268ff08db382ceb7829e7485ac9b2836ef9ca26cbb962b454e0a8d616c12814d963e6d1787099aaa7411c2ff7c4b5c2a31d6347ed045a452b91b05c21e0b707ea7d76e5b475298051e608625fa09298adbafc815fae12953807afc1811a9cf8dfb8ff3cb4c1d0c4538ff0a9540cd788f69d77ab09720d6c6b1e2c659124db32b0332cea53747288e89672a040d86fad76dfcf3f4ee0d35f3c035757344698341d2530b4430ea4963c24df47d4793bd7df02de104ccb9026a6e46795b4096914d3a19c560db4ff943acf61de9b01f8a011ddb562fd05eb09e4fe45af439b258efb0f88fe9c197ea54d5a29ac0760402884000d0d4efd824d4f7936bc50d09f76fe61a08a327cb7a5b79adc33e41b611228decbdf7de72ef97075e073d07f152999d28e361978f32997af98b3240901c663374c065ada51ae57bc950652992e54f6aa87496fdd30e688a03bff207f9964414811dd5ff2e3189619738e68b6a20daa32a94d2bea53946fc591e72c45f8d1a327685b5cacf2c256b622d9bb8d4899ae85338704078434c3f9e50c91f118df2693ada8b407bf0dc8e16b5d70a5c4027a5a794524ae929a5944e39e7a454523a29a5301ca5a794524aa38a948210048a014cae41c423062f36689c74e748ef7c96f8458ed1271a11a911b30702cd77f74121911670f3307a34882802b6608670152f7d8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a463b99e40859224f57e4cec86505f6cc60097af14748160bb8125d1766019563fc1799c8f219bcf3521b9eb471084436209a48941264015d3420ba341228041214342020bd323ef9c3841ae5c358b294e9c94c7c0035c08630a0a046c1ea8c28b86f06dd7b6fc530927befc5f776f7defb7b63dc7bafea5e18d65a9bba1765adb523d6ce60adb5276b5d586bad88b52f6aadd5646d8b5a6b0da955865a6be56a65516bad5bad94525a2b49cb28a594524a29c54a97524aa90ce8a296d23e25a92424b592bc56925a659ce432486aad3a3a4092357756a26bfe8026f140988a0f518f50260213599e444637736aadb5d6eef55d9d99b93992357bdc9fe89a319838d767dadc9ee9e3ee4c286e1535663865e02a7626c19debf1ebcaca9d91acc97271a26b7620e6debc6e8dcba5f1c09d4245fa489fcb2259f24726bae413a0497c9098a1fb0a72c9f23e2181c055ec1c0377ce5036361648b2e4cd5d892ed903d97355762c518e158ae23281aa79bd6c8e644918fb135d12053eac4f0fdb2363775260ab7841e1ce998b1eea213b23594d6471a2abad70b1372cb646c5d2f415760a172df46267c7b24856f35899e8ea2204c5fcbcb2b42e47b04fbcd081abd8d9458f1e1548b29ac6ae4457cb20c7aadc54a29a2a4403cbc40c0b4bcd91ac6ea93fd1d51f90a93e30b5e7a5ee80a056b12243fc893f38d115a950b921ca51a8d2c429d429a2cfcd4dc5a95200e1a92f9deaf283fa840d5cc58681a140921563ea4a744515d454151a4a344385585099c85e1a86613c78e448fa135d1578a13e2db46785ee402fd02a4a3b325b441133926504c5892e0440217a0381680df4a1344aa0535822e02a524a29a5f4524a4f5824ab864c74ada27cf1e092257d6244633481ba65e3375d59c9b2bbf7cce9eebfe7cf4f96dd73a65bb6ea3d71264e960de33d59ba274b969d7a4f99299365b704ea964059f6c84a96fde22d73ba659fdef247fe64d92d67669a47ce64d9226f892371b2ec166fc9d2dda6b794913259763750770365d9216fb92257b26c19de9dd32d9b7bf74fff64d9dd33ddad03e86a9c26bd9b25c6c8d6de2d9365b7cc8e6645a06ed9bd92655f02d0d52ba5775f076c8d80389f9c7e10d0157fa2893cc4e178684943de67ef7bdddd5126cb7e8d3824b0555931ca4413f9ee70d761dc1de30e63927727e930c63b730748b2260f5d89ae598420aaf2430444e8089409926eeac438c63ffe31c6c7c77f8ce3c7c0ff678fe3d92347b226cd4f744d19e4f8cc9b9e59b333695005fe8d8e81bb39130377ee60a87e95ea57fdff5557a9f07f7c954a35595478b2cc19c99a2d38d1353f30656e26ccac992f936682608aff46c37837576648a954a994ea29954a75554a954afdaa5f954aa57e5258fe4c16c9924032d125a98026f12a31442f213985f984ea377a0655277de02a7686813b672f4652a99191d44752a9d45323a99111d55357a54646466e46b0bc01922c89d3252bd125a58026f140328fd49142f2079389d46ff48b54276d50a79191d369e4a79191918f9c464ea7d4479e1a399d4e12e614637ea24baa40d6f8489a1e39b3235950c5c86f346aa4932f14eefca285c8e9242272bac8e974fa49e4242232f2d3474e222222444dd45eec2038d1d55c902f37b245d6c81549e385295a9cba1e82ab6812113199446e121111b98849c4643a5de4271193c9c4638a3d32d1d548108a01f2912e4a7842e437da8548d73b2e70e7162126534888e9212693e9a610534888c84d1731858484d0344d7bb17dc895e8ea1ab48e6c9c6e9bee1b4853d73de02a7221211c17722e2424e4215c08c7991e725308c771ddc2c576f989ae6ec22746be9ca8224b16215db3b0c09d65206d1cb76ddc378ee3366edb42ce3d84dbb60d688b4138d115a97013a46b869aa60a53647912d7c59f92b66d9ab65ddbb6ed9bb6691af7eddca6691a0e4eb34856cc9189ae08049f989ed74e109ed862de62e711a66918a61dd334ed1aa661d876ed9b8661588c890192ac28d3252bd1155b004de26d7c10f5c8f1cdc448eb224c7631ec5eec17c3b08bddab1dbb86dd7b33707106da8b51e527ba2cf0f2893b190c5564f90ceb78c055fc68de3be7fdbc77de39ef9cd38806e044d702866e826a6464c21459d6db15015796ce49e93c9d73d249e99d94d21ad446cb44574c0e4f6479d259d3b4666140b2441ec87472ef0dc6d294c6a69f42ee7ec7cf1b7b9e76a653c4b090e54588b87e04922cad3196e8a2d0247ea492254694a5b421c8d2cb9b76f2b8632c59622cb2448eecb5c1dd9c72be73a3c949b330a02e912e280016c922c08d2e1bd004eea0204b22b864d992da9c81e5398a11a309ace0c2104ad7609e32c85481798999b32751cad92b561a8d5c462c231522200c68fb68343a696403eedb3eb21fb98c460fd972c867f3234c5f9a43301f15ead9c9a1416648c1f53f182474bbaf3feaa49ca32751caa3571f89ddebe3e3e3e3d325f63505def1692404b2a8102cb1a4ec127fa77c4bbf2f5d4caf80ac78234bec7328186e3c70e344b63e2740b1ff2a0bb23d24014247c79e18a47160ab5b687860b22c072d872d88923c8188dc80832171bbc095fd86bb046d8268621f71bff4ab61da4709774da700aeec1b05d0c4fee224669a6666ce4e073781d8ed68efbede7b311f580f4c0683c15ab0967aefada37ab1966c8ff9e0b6947a47f75e5e765f7f7bbc91f7725cbcb9f9e447f6184bc896722f3fed5bb3da4596d8bfb418d34ccdc90c9b1a1906c52cc420d8ba41312806c5a07b4727fd7eb7d3ba2f43826bcf22b1753b6e37011d304847a3a0faa806823269e0e54ef7489806cda4c970d0c4107db467274fb6efc9d39e85b093386812db1cf1a4993e66cddc993cb3a73d3b730057f61307d064da4c1d9c6c6b95af407c787a747670b2bd8d665d1f5d622f658699c6490d79596595554a39baeca08f6c7f81685bcaa89bd9fb19d65a8480235d7b26a990316f39b6774fa340fb6d4bd9ba19f3d65d6a05a457609129709306aeeca95074c16c4f871a85a8515eea6b84bf59a351aeed4146863dc01e600ff014b27a8025f6f037206bd2489be1ec01bacfa9074a38859a388d40568425fd7f642424a44718c2d51233f79328c9df2d47f6ee96d87c18759ca599373367c7fe524c85863005626ea21a79a2979576fd32fb154393b8e64885e8507b44ed591a04a6e76507cb59b9c9f6a351579ed168464f0aee1e084fb6a7c15217d8aad99ebeb4f571eac1c9640a93eda94db3a85097d877f75d1c4e275b6be94f3ffbb73d3becbefa6a972da5bf754bc44cea3e12ede22a836b4c85a91153a22055052bb183a360c8f6b4089ad86f7889bb0497e5b11ac9aa337146b2ea0ae693f39b6c6b6ab01a9ada5283507b432cdfa4b1d97efa4c1fb025b3bdf63d69a289d541b29d1a258a3e4db6873ba71c8e857559e60cc949c589e4fe6613d9fedb729f42d66c8246f5ba3b9d84f4eca42e9579b305de976c0fe184c9766447f6336cc07d7dcefed26c295a2644d3b72c2ad622b1bd4402840e171f3d31480c4e03b5d0f048a2ced83d675fb71cda6fb744ccdb69d87cd84add8e98358947ef8e0ec195fd0853a126b8fad1e50f4864b6eb97f65c5c5ea008b97dbf3a0032dc09572d43e7752740524812ed785a0d9b0fdd2141b2ef9b6fb86d6cb2cd8dbad73a25a6043257dab3b73ded22b56be252a7b74bb6973d9275d2257d1bb96fb89cd3ce7bb27d5643cb172711f36d6b8ff96c29b2d3ba2562be1dd79e7ddf8e6ebe30b45089220c615841187070b27c7f76f70cb88f8344984a9e136888858888818bb9f7761f976937fa9c36fb6c2021ad256cd1cf1ffd4d4369fd4e23a2bbed65a3c797f620ab6f64c994ddf5abbdbe995c8e5dc364f0933da48e9c812b1f702adb3ff94322922d8df2f59c37999efe68408d82c40d1282ad9215a35bbad747f600912aedcd1ea91259dce70c963dbaa46fb69370e7965a07a451b6db5263c512c092f97f642404bbddd741ff3901691ec9bd7f801a85725d94c973f6bca8e190d0be23feccf801f7c9211883848080833f6458328c8e76149a13449232599c204a72078921f6f848a44e7b53cec0d57c71c321d1df21555ef29c48d0b989807274c3724812c9205fe0e6e7e550a3a0bebed13a54cc9141a48edc6914aa5d97c6e9a0939c5226ad5ffa051367bef21127cf4f0b126fa24d0dce6c03e1be696f364eff1cc88987d3c9f3738610b8eed1285f9f467e5ecec0d6fdbc94016cc5244a345ff9081b251f7f51b2a331634d9eb1697481ab19d428584743ce74c9fcbd1c773a7df7e6e8f2773483759f372fb1d769f88b3779c2084c6ee6ee3c0f095410f2f9147b2918ee4be598f2a6ca0c5164302b69ddc1b902854b35e59acd39e79cf4325379528411a95c39f0f62ac5f318fe817ed27f94fe86a4f0060877801008b91e42f85c391053cfe0313c6137db9bdfc581e14c6ab3ab71a3ec0b2fd6c1fae38920d9abf4c4f48a61bf9fb937ec62d75a9a98972c6a3eda8f9072c485088bf8842cff99bcf82703f54f2592e14f6aaca0cb01d044b26ca0a85487d7764077c04108011209ff004fe1e9e91733148203193e5e1e0101d86d243cf10c1e4e7514fc77616ccf6060d53718aac350fd466ff8aa6c4854f771b0fbe0b5bfe8b44ce586436a98874cf157236b9f1b8e1aed69a790e21938dc47af05e52c6a5246095b76df06f2d6cd5c925e8d1aed49fa6feb50107627ff36ec105f3c838643c15f14fc08de835d8ae7e5b963c2256e2ebd371c351a0576347ee548f7dd53f75d179d48f78f4887df48211df626e460493bea258ca21dbc129d6197c40654b2129d93d8804a2ed197b01271c3240ce14a256ff5db804ade7173bd9d0e928c51295caee76047499af73069e1499d1de19c1c50456d0bd9a4d1de84f5da6517217622244be324497e6b3c6f760e9730097f10e883423a301c011d36b27ca68d81fbe04e061a450822242b3da88f917fd42653d89ee91fc551fd131141d57a138b8b5416aa9a42bd18e9be67169d49b2a892be868288492041fa8e9851d76e5f9de88ad0c49e5a9bed7532c7953e7ac669ff0e0d81de69c4468610c34e6aa9542a954aa552a9542a954aa593d682041ccc34d3ab2ceb0ef50d984ce9d04f4ba6742553fb6f95a9fda743a6a4cd87b6b6fb46f98344df1dcaf43c60ab9ed219361c8fa8e202a5dd1af2a519e6435b3c473f797637646631972b2ee5090357a39772c5242252ae78e3d972c51a4d265b2a1e018d72c5180e962bbe1d6383d85c71eda9b962ea83e68ae7a8ea80ab111070357a8f60b6204ab2c4096a8fca00c4a1361cb785083894d3e933d0282d4e5d9c3e7600b6424e3f5f00593247b200a270a79fafe8923fd1849efaf02053d973831c2832a5af4cedb9abb614ee96b39fb79fd6ce1346dd056e81e765b80c42cd6ebe08e14f0655175942311e31c645b2a64b779add776f7eb3a69485e02426800d315f9a2f4d146a76aff6a28ccc1213bb0cb38b1968eff2c8407b14126519d42c059cc63c5f12f06fb620e0df5c21e29f0c92e39f1ccaf4d545b214d0cd9a0490fe1d011d115d579a35d3c608dc376ba0c801ef65254a4f620219fbac91ac1c1d8f2ea1bf9763d17ddce93b91baef5e49e742b89add6765c0dfcc2178c7cca54bcc0385c2eee5eca50eb6f7655d8c91812bfa8bbd6e38e65f40fa91cd4d8cc930ec62cbd102c38644b6947babc4f4a87c61df81c3da0e95e2499c98f6285773244ba3a71447efc9d7cd89eebc3285b9d238a9d15e861bd0f245a94eeee3c4541fba3b812b9afb59a791b2ad98013a36a10522a11829a55185521a7dba8184a65086c22767521329aa91a5b92a69b5b803d0647e7a00aee6a3cac71d217de6ecf9a822292c997185a5429882f0139ada8b34e4d49e8e08b4177fa970c28911444cbba46d54b31b23a5f4bdc551cde8864d3abb0fd30e6146c29da915b0f579d948bbd97502d3b4d1b669244dd3489aa691b29336baa568cf46dbbcda7562344924396a98353c9f69a974394ed3347b374dd3346d9b983bc5f2b1f44bda4635e3301a638eb4f491a66918a6699b76d2865deb6ed237ec924422c553d2b69948a41cd89b84914899c602b6ae6c026c6917cb8cd14a226118866118866118866118a68db40eb6976931c61873fc85ad1863d4300dd33ed2300c2b69dab5ee6e29d8b7edda49dd1231cf574ccf617a4cc32e09c37cb55a2fb17befbd17cb461876efbdf7de7befbd17cb2e76b17bef8804081d2e3e7a6290189c066aa1e1914413e6a40ca569d80e254dc3fabe4aac625a0a56314c1b612791b0cb6e89adfbe22cc5a092b66025ecbe911861a4d2a4b9d8e651cda27677c024065bf5c64b72afddd6ea2dc9d06e16b2e520bd492452b66924a3ae757bc02c95b4e5de7bb1ee380cd36ea47714e9cc23acde5aebad554aad86eb0436d2300cc3b68ff4bb5d92f6fb4c8686c551cd48f7dbf5ae13a49196652312462291301289f411092361188661db46d2b46cd35ec3e683b65d5c6b0ba65d4dc348a312cd1b69d849d8d6c3d2b4f90cd3d33cc23b624e79a6c7e2b17ff25847a9238af818e5a5eca0a90737e18588ae1b4d8ae892f9eb712779ce9e873c797e368c3d49b403ca20c9275397a9cb3b5387cf4e32464a29a594524a29a594524a29a59452ca1f91651302cd8a2f5912146982c4f81723a7bb6871919b58c8f01014aef488b25d7bf67821888837a259f8f14734cac89bf3aaa9c40863a758f58b9fe17a0d67dfb63786f152e92198bb0c38e42c585c049b5ce0163f61171fc1a7bfc02347e1174f6d002711c7903bd670352f1486f1148e19e3951aa8cec40780ffc2d40302b1d86daa35c385b89ae8d3365d628f92f8668eb341d9a0fecfcba8ee8b43a87f6df3eef3f2083f4be19867409d6e395229a9cbd45329a9c35061f9f2ea12fb09811b6487721f5a2562466198ba398571c8a3f00bdc39b875dab33fe1dec1cd83bb073790f6c12d036ea00e6aa1a1201a962a7245b2b4d72231962f5d02d3367dc3c2d33e1a85a66b78c87646217f3b64fbdb4334c4eec52697ed675f4d04dc1779e213ee8d30f2c49e4601025b30e5de2bef9d6412fba259fcc046dbe7ec648bf5648bf9c01958c364678d2eed1c7475c49ebde79cd80d3681344a7ff4e97367f7e920216559b6e2f39e13906ba359164897d8df16bafde3f68fdb3f6effb8a790d53feecd6e76b167c7b27b6feb407b08edb5f77a43d91e0392edef8ab6a564c7ba25622e755fcca4df2d47766d74d24ea579190db1c81ecdb2345d62656678ae73c2e16c2c8d8f6cdf44d2fbbb89ba3bfae8189c066aa1e1914413c606dc977dfbd7dfec6d4fa3c0948f68a6b8be5e5cdaa34218bd825a515be42d4a76dae587d5c89ef65a8480bbb30735f34dd32872d6ccf737a242d9b641b1936dc8f0071432392349205b102539cb5868763b69a6645946da4428ae27e1f90cce0ab77b39cec330d339b54c8bd9bdf7ad6df5f75e8e5e877eb637fdd4debff0021c4c820b4e96226d53851073dc77db34ca28e54ad97d42b2bdd1cbe54e16127da3976c472f37377a0618b861a4ec19d8b24ac49cc250856f7b2d25695e856f4e75468de0930b1c81b4e7137f22500c8a42712812e118a4557a05b74bbfe07ee186e99e1127e69c8093382c327f2533f688f620d0dd76886a6ded99c681b511db72d837ed539862ed6d1759687c8d74d76a6d952ea96e31548211946004a5793a4193365f9041920f8bc9fd2cce80e3c0ac812b0a5b307f810734a1872a272c2edecb5a1b93e9edcc03a0cbd2449a2964fb23608bbbbd03a0cbfaf091a97d0360ab757b064097e5010f9c90ed8d580074591ff88009d9be08d8c26e8f00e8b25248a1846c4fc410d0656f6e8e90ed8580add26d10a0890d0210b0a5dd1e0774d929ec0b90a1cbfeb0bf015bdbed7b802eab630590ed79d801baec113e57b6b7a10374d9247cac6c5fe306e8b24bf872c8f627b085ea31c9f62be8b23ddf8c6c5f025dd609d0845a28c8802e0b056842a9b0c7d065a9a0e1c1966a86db1fb6ae822efb530597ad820874d92b40139a05fb10e8b25948c1168e717b136cbd60b1419715bac21e832e7b85ed823d852edb0568426f8b8aa009cd5ab0e5ba3d84ad03dc5a6badb53ceee5b8d3e9f73ce89aa7af43420e618b1687d0858b43d8758790071e0e610f3d1cc20004e0100a400087f0c68d4398f3211cc0000e21010870080b50001c8710870f87d0871f0ee10f871008208230408a10431cc2200ea1010e61ca2114e2100ef16c845bc7f0e815632f954ec2da374cba096f2358e4283cf214465d85539f01ab7e3cc331fe63607c00ecfa10f8b345995e8814030401c40f3ee028000106906f0820003df0d0b96811729ad6c5d25817eb625d689a045c24ca39b2240626bedc186728fd8c1fd505a5518ad2ba142f6bd73a8a233bf972a1a55a5db2ea426b89525a698eac34e7c4d11cfa92f407f4455fb08452164056bd0942a6348a4c690e32a534c894c280077dd1170aaa44631aa5dabcda932cf992443276384280ad105615a218802d780b40976d8926f4304ee9ab4be82f37d3f475fa475f5548b2ee8e6d89ae2b34853adb526fec8dbdb99dbc19d59bcc9de368753900be7908fcd92e647a21f067afc8f429f8b34299de00f8b359c8f441e0cf5e21d303813f5b854cff03feec4fa6f7017f968a4c8f0317007fd609999e00f8b33d997e00f8b34bc8f4197f360999fe06feec1132bd00f06775327d00f0677f64fa1ef067a7c8f43ce0cf0621d377f8b39f95e2b33ef82c0f32fd097fd6c767697eb8799e042b1165e06f4a91e969c035e06f0a21d37bf89b3f327d09fe661132bd0df89b48c8f433f03797f0cd2664fa15fee613323d00f037a9c8f426f89b54c8f427f89b56c8f437e06f6621d3e380bf6945a6cf017f930b99be06fe66d137c390e951f0473b90e959f8a31fc8f436f0479bc8f43be08f8ae0a325c8f4167f140599f60e9f7907d73f8a02e8b24152f0025b5c09a0ebaaa89c20d3d60a095a606bd404745d1616ea44a6a72caedbd202824c4f5760ab03d0755d3c9069290cd0755f5ec61004b6b422e8baaf171888608bc405e8ba30305e1882adcd0ae8ba31315708c196290bd0756564b490e967106c899c7e5a01baee4c34a13358c8f41308b6464e3fa9005db70715d075699e005dd74713a0ebd64413fa035b281fd84aa9606b86f7a842a699828f28d43821d3cf2540d7b5410274dd9b2240d7c5f9015d37279af0c0d6610bc3568cd3cf1cd87a711b26dc2801e70839671261462104e8ba3ad184e2c0160b177d45133aa580aeea124d286cf500ba78e80c21d35710f20f327d1603777f81639c057ef10cb3f8c5d95df89ec330dec21c09890c1934d050430d9e575262830d3366d0a0b15a0100002626272737dc80430e357440c121871a3aa0b06cecd062753fc69f01ff2a3cc3535875144e7d04a32e82476ec222dfb049c3a497b0f68a4b238cdd85e75b78f4bb2359108025f6d4de9d4c2d05122221092243860da242030d2a2b35d4b0c2e2792c2d25252d2e36d8e0f23263c6cb8b068d17cc6a0513030000c4c89898c8cc9c9cccf4b8e137f4c8f4970687e3f0afaf8f1c6a6ad8e8708372fa8bc37a0e3e6ad4e870941bd6e96f8e8d1d4e4f5fadd35717d7e979d8c8d9a1f5af5d77812efcd16cdfc21f96ed77c036300ba3601d700dfce16c9f03c6017f25d9fe06fcadb2fd09fe6ec8f626f8d321db0300af300dfc412064fb19f8b3c1be047fb008d9dec31f4442b6af017f7009d99e06fcc126647b19f8834fc8f624f88354647bfb8b275cc1953d0bacc195fd0bccc1957d0c6c822bfb0e9fe0ca1ee3145cd91f1faeecca5e853db8b24f611a70658fc22770653f826bc0955dd99b300f370a00045cd997b0107065ed315c8411b80170650f031f0157968333372bd924c31f332e399b1aa4d15af08e4f23374256e7bcb447ab4d7b9304def1698402492c4056130196d0c7888142b160913f0994e5af73f249c359336145d92b31abbdedaacd8c1fdccdc939b05542f991a4ed27201c0fd7f54b96d0dfcb71a7d33d998690a32bf403b0c5e254b2aa4b97d0cb1aee640ac9ea4294a94abdbdfd575d447c28bd94b1002ca1f7bcbf9ba803b7891264fa9c9c0cc015a5b18706748e73b2e1a88c165a70149c44c0c50f64fa8e470b174e062f33439924c31f333ba5cfd2e7909d51c94c9ac81644961d86e2b447e9ebe2e04cb386b315b6b0d34f20c43180011245d7d5e91434091a044479ce39c4fdbcdc27c7cd1486291033f68a51f316c5e2f53486c58bbe1a05a65939ba4965689969a8846a9a0b9b1e55686600000002005316000038140a050422913409c364f3f814800e84ac4454469d0a44710e23292509418000000000001000001080192a1000189a0ecb60416974b175a606993472cd82a9f3868eebf31c60f4cb7df57bb82dbac94ee230b79a85013a096280dba21b036836bcdf65ce3aac53db984359e72e412448c170d17afb209bba08878749bab7f5c1eaf9f4e8a26d6e4edcaef6aa34b85050a06a1be7460cb4d5612eae47d397eae42593aec9c4a3ad36eb03ec9bdc42b85c61a42f5d490649922b510905ea9bedc06eb8658321b131d8e90592bb4280f9eeeeeb984f23d98a0cef0f967fae60c38b26beddfe06591fbe0eae4270b271aef280b0e209f0d03f1bdcba180c2377edc99b4acc29911762c27db9191a0dad91df7d08f641f6223c2d10b6a66f9f006b5426b2bee1de217368eb27ae7050705fcad87d3fe7cd756cacfb72109d2612ba2f0724f42dad4b4a5cad4b8500ff1d82d0d760ac25e64376d9e1ac679f51a574a8a081f7deb1e619f63d82f5aeadfd10687b7963591c86e159188d251d40dc5ce77a0b3dec9744ecafb008e52fafdae893f3b5ffee06d1ecc4119e126ff342dcaecf95ce023fdd6072db01d0b696d3059a28283c80ff5f0d9f733afe6d54642ac216d675168a82e21a5ac16b688ddb439f590ee8f335be266bf7156d92648f059a5be7d08410ffc681b68ed2f0c3630524949024ccd0ed583d071f39e163fc8107ef40e1931483ff4cbc9384bf8c0afee9e13b21bce123f87f0bdfeee00de3c0ff0fe3c1bb8ef0755258e739005bd2c5861ad84d8a5d68b0ef11db3f61a33cc431178d174893423cb2a51af00bb5a22415f4885abf12208c9102e070b860e236667539f7dd10218bf4c1a7dbc7137ce749ea1c010134494c449dcb0340681562160030baf20f6f9014f44d01e129dcacc804ebd583bbef5dfea0605d490933d90fc4d2fdac81af8ae70f92d3853d80df4eef077cfaa121fb1493bcf61301b01f2ff00e69f9bf8778c2d71524bde1cbb7c555ecfd6fea8284bf6f156a4962105ed8000b1eb848ab08873565a4b38482fb00cc0898159d16c0b558f43f4a5c459c34b24eab2b110ce2c82100ac8810e145a1b7aae12efdf7fd5c37fd29a1f54723ec16a1de18f652e54f0eabbaa1ad6099bcbe22e2c29ac164bd3e731044bb23f1bc95d8bff8fe515a25115c9159214c4de35e9ea2e3bb4dd3159cd7e6865e492b5d53f80f8e385b25a867893c7bc1472f3c286b2488d7393a0bcace8f3794fdb68336359c2d88e3255b48cd53b6afd95576f0caaf2c19144fb2a524266547486d5c3cc42387d73f35e81e03d5052ef8db2a6c49b60485f607447a06c602054d3e1fc742dc21022920ad3de0b91468750a383e00500d62ffe4ffb790f3ff75c1a9bccf7877e7bac6d1a45ed62e576edf7aef13174f352fa10e8ac82092ec9ca7e8508876d211b336d325a22eaee437d34a3f309a5ce67ce2f59b44639269d8280bf6f101702902d1fb4070393eec633257536ed46f6078377044c6ea58f2f52844cad20fbe810d85c815f60936eac79cf681d55e30644a89e823598e77b7c780349ed6506270a1c3c3eb03db065e0cc56d8f6e1ba846ea0d30f4dea291becce1e201e3c1e25e0ac290c4543b85b5ea6dfc072602e0f0f4d15271335d2b58192b01ff604f04c1db21663524bb55c402eb5179b1fa40c8a39a2bbf5756a1671214192c04e1297e12f60407950c9277eb04d012204955282c800e8c17caa990b2576439a9318bf6145567562c91faeadef28aba4a766b6246fcd79d38920c3365b5562de91b2376a0cd50ecb08401cf6090a1a8d050b7136470bd2e861d13b9a2b6b05267468499d6fa061872b5d64e5407e611f42da6574ffb44b04eaf7bccf3f41c9afa948331da56b53e5dbd3a866815fc071a1158da10c19605179b08949e7d9900353ab663125c42dbc7c7900d38c71ab031692a735de98c5761730c1dc4008aea682199cb69223262c7ff688649c6e2c28351e87450280d0d4a18476b1066abcc937ab1dd12e28add933476b389d6a5efea2da90e7a04ef659fdda22413e4f4fd590b5bf7ee9734961bb01a5527695e45d95196bc14af29b52622bfb4270887254af54f70deaac48653d1225e2cf339433ca36dee9450fe9946b6799b821185aa34baa6f420c2e9e60bb3a7b87348155da0a6827e65047f621241e90f3f2605344ab07690473355edd1606a653e337dde9e6bf450dfc3adae8349a657a93c950140a86e70ab1b1dd282d6fe300d80317ff5f512b15ff6be18bf324b1e38a96cda1d7474c5d3150a6c93bb0ffe336b576a96d5848f92a472b1f2dbe8a85ace91a00ee47428f3a3c01a0e2b2d48929e0a31e1623eecd3fe2d667cc7f93c55c1c6edfe2202fb69f16c08e1945505a5018f3ae9bdb24e17555058a8832a60c1f9b0086761da6bca262591264946dff7cf1126d439806904c9978ee9a705223e18b9c5513321b7127ae9a8f029ca41ebb417a543be4ee4e6945860015624927be07e43146d3eae7d44aa44363f94528eac49b47026cf4929de88bf89bf08bc17bc137f13bc2f7a117e257a2ff026fc22f25af422cd0da20129c61f6bccf89d4717123ae671aaca971cf14bc0467820984371c551d3db73cb4dbd0a3d9a68daaa740dadbdf04dbabea7cb4b0426c092a2cadf9edef35fd5f76a9589bf7e9e5e368f677f3995d8be11168f6d3215b27e659d607f212c8cbe2e24e97e85dbc39cbc3deb76e005f95e8125b10cfb549e406f1fbcbea20d42b7d25121448cb14fe3f933859e1124ec59b8e1240c01aac77b325b85bec6b1c030941ae59ea44135e00d4a8e4af594838284f45b8b6ab95ab051cd0a0be8b8d1b2fdae408e2db582fdd6e429b2f4e66bbdbbc2549a0942c119633bfd1f6a62e34f697dfbd03877629b64f180731a30f50693f9fb660473e67d5f6f24ffec3d7a178bbd89930b0b592ae36cfc2d12dd02a9d7d5484f054ac8fce99d686007ad433f9be33bd25f85e26239363b2172752748b1e450e37f098513e27bde2ccf85b6c8932fd2b973fcd0392a7a44867a9527bde185b57c6a9099d5adddb4a033a406c14a084bdc0c5f7de1233690f8dd26c57d70370c34e36b0dba161cfd79f941c05c4669568f080e66a115ab4f1d31f4530c36d9c08019969c1011d19ccab18d7f662a20af83d81820e804903f0db0b0ba340801ca9e5569b108df70a5dfae411bacaa7cae42fb714c55b2f65f3289edc26911aa5423a9ebd01fd977f8a12859a6c73b23c08d462dbe3ba2b0d44d5b9215f4aa34560978fb000934955068898be632dd1e8d3235bdfcee2ccc562c9053ae2879064295594e3c9a14233f4939f8f367b5f9037cbb8471f1932c19fc557b4562ac7ca7d1acdec87309235e43e867078e7779d13d2377a8dd1fcb5233f48bda6bb83f0605878d41979e51ddfd8af88ee1fda8d20aa8be4b72814f3229789784bf02b0b89a1042050c21a730bd097bdef660c0ff74f93c2391ece6f7c1ba24be3111c775d3bf9c59b3e717a922b6f8e96f616755d247414b5cc6ec020f1b2b618c3d58aaa6c335a6cd9b49bd35ec3775c48b15538b386cde6a2567141a0ed28872853cd0108aed3327d6da29e1e16edc12613abfbbbb5a123449999b0617896bd6c1879867e48f5b85d8b2de1020aff1170afb6153b1d05e26a7f9e0d7a605e47aa2370cf006946b39b03c175e8295fb1c3e95cba5a78973ac6b0fd9d90c49f72c907a2ea76641221b3652c87a6e38fecb1534c2905f8e7beb4b0bb2dbba2f05898960ecace12be76ccde964224e096e3d921c99b5a6f6f5dc7f96ed2897d63145bdc56007ce1c5a4863e096d751a25e3d0be684ba68d43c1b44bd05bc65e93d3cd56889c1249c84938ba9234222cee5e770eb08584513911121a5d0df5004a128ee812421ec15a34e2e81a788ce85276d4e3e67b16573992e2053e4e509999f4c4c88286e2a26040f24883a3799cd1cd1c7cadf4af33234d60ce6b3de46806f2f9f2de6e3626dfd6d1067b821ac0fba1d33d7be48dadaa5c07fbf2751acbe85d50d09c65b585ecfde58669ca3c3a0f60a9bfaf79a7e069b903863aaf64d3fe0d049f4925f08a096a8059cc97be0b5bc9746ef4870f7e1a09b2af1ae269f0ae4dcae6f423b081607e36cc205657f85736ae619369db76ad1d88321d4c491c118d41f100592329ae1d8022072678d46c003ceab303360cee43f8e1b3d52a91d8ec13bc31b1bcbfabf759a86322959fdd646996c9f1b1f9eb881d036866683d03708fdc6d06be564ae2bac4eb45577253846ead0a04dad210d30937c2a21c00a9771e5c104fc623150c424a44080cf9334afde14575865f21b052f78a01fda0c2f2ccf2b87e109a9d91be91b081fd60543b828880d8dc20b57ddc592e7e791b6c6fd70ce476cbc8bac0ce51ad643dfc512b87ec13325db79692ea3f37a1f1c93fa5b2141818fed4c707103f735e4f16b28119e79c169a07b9ee9c833acee7aa68d9521d7dae41a47994e375bad6188eb91e96a94aba4ea01165e86552c6915708dd6499d664f8fff1825fbfc5853dfb27b225b271d66113351de1f6e07b27aaed6cda3f239287f01dfad4f0ea4d1990c514d20bd02bb2ee9f458e6e70327bcf46e14db3fd3304c84c00453d6be396548abbf683034436a1529c8e2f1e87c32fcf72c1cf504e39132f1f6c34c848c8005ab0688f510b77b32bb98ce24aef975b947addee08701fdc0a5b36ac87865e122c7ad085fb922f212921a2fb15b36200d9ee9e02b1e803adcdcd6c64ca31b11214cfa0dfa783dba3978cea5884638907dae9ff10f5b4f0e41e17f7992667d880747ad73dc22dd875259fb89d0a5027ba0d03c7e5dfb9974be82c07b3abde30fc76350a6024adf04074b40a9b55cbdaba7b57b63971e747aaa8b5a651da753ad86b0c600ad22ee67b57e086877a062025f1e54c92d5517776ad101d5e5e876aa5789998d6093086be4b80b6632e7128661b40c8a84ad6a746909f4171f4b19b129ca5aed6ca96451ab2d1cd8fc2cd0c93a5d6905a9dc734e70f8f36d053d58b06922d21920fead1a3fa8456101887f2aa2a4e543c08ae69a383d09c5afa3a5e7ed7ae602c4d5bf54705206801f24a161893c16c94aa6617f5a2210bc0b5161a2732dc34acf1529f1b60396d94bbb6dfd8f01cd11ce96dd5b84eeef4a8470d122fbfa64d12178e400fbadb0aa75256d143b979946a414151b01e388ba9c3db4f2c5fcfdcfd0ad6efd093374b50ff43e5c5749ae9fa3f096de89b36afde1d9e67fdca7de762a25f475d4aee26d0c933ff089fd32b8d02675d323166226b92975101280b0aafe792cf002dc621861e9aa2ad5efa4aaa20fa4e4998c4e5cfdddc2b417f2d0c122f1ac8ad55f0ec0f9332c499338aa69e3a09826a7e12fbb3d3fa98f24163897107fd691cdaae0318653ece65f0c17f8209c92e82f1f3cf39072c68b782342d62eb4608ec33b01dfd5e332d6b1000deee7fe7fe94e0327b773838517e44202a1c72b3e5b16584c7bcc2fd9f8db0eeeefd84324eda55a07ee8785921ac091f8d194b29d60d4190d461968943a59dc57417efc6b0e6e03399cae76aa49d296ae4b803c9b861491451f6743945305ac82e31641c03fe9585486229347632664f06841dc0d0d9867d8492f77a7b375571064213878a08b2960d13e40de37ea189fcce163e55e6c01fe5f40e43255a7f1dc93543c6f19b2351a120f92dd461241d475ef600e442d8cf4963200ad93453e02914b7b65b6e8f6b949c9f41ff3a39ca8d44301f4bfcd6037518d4649baa0dcb6bd9b16db82312ec0ee93cb1c5bc32c633f850ff2a88cce7df63ceaa04ea8f9212d232a8d94be47b7412ae154b1b8f2482644a9319d03e686ba5e60b770ae97a3544407aa7c223044b1249c51788d1f222061ecdbfde2c5ea6993e345576f38eca1184a801998d0c6f55341276c14da344293a9f0f11246f175cd51b0ce3fc2b8c90a6c65bb332702fdc2ae684ce20e7e27332d6ce8fa161dc8e1d610113c474567999d694baabd54e174b7cc4b6b0b78bc98e2e826a69200139e05f85fc4a784bbdb655b8b017b99b359897d8198adcb4021a95d297030758e888cb9b7465a04c1075a46c2351e7596260d0fd95fa13fc65869b15540a068403ff6242bf57c33db3b1ea0ec4401b6ff2112392f1b9a2cb216bd974455b32b38f11535b41be8950e88af328244b5b2104c77843277848a8979274c37a829c1d30657cf4a3ab9fbd9428a6259efae07fea8afebd1b8ffde5e2d2e6d7dd3d6f18d32cf1e473cd27d2840fff26eb1c0507f77152c1aacc21345274f477e7694098cb247e61b75f576d832af403550468fa43caec028e6e5a74b9f6590cd2d7d55bf9b35e119262e6ec5820b56c9c9461d2f179f03a8a98f7683aa6e8e7f5396f0d85faf9a4c023e9fb6ea8d82c5344d4b2c01ce12524033ed0a6db1fa280461d2884e0a5825c6e2689fa9a5647e783ea52c38bb2c3e15bdbf0f4e328c524065c3c1e93a88c019462e394bd003151a7087bb9d1e9163e6ccd897b168175b461ae053ffde95ebebc152e2c9fac850f58c12a416cb9318e5fc92d37060d8a0003530e333d692d3c27d4c349098ae7234b419b442669abfe70983e6f09ec993cff383a945db93b179ac02e10a50c55b0f8d98cae82f47391c6988043759673fd130b1b08e4d33fa0903b826a9815be5855f2481d9b88af3ba824c339ed8359166017b3f4f26783c060ea49b491bb2004c7d9ef1118d19678503caec7369a98d016abe9d449710b3c201d892e9c905ed66dc587a238160c0538403c43a6adc9bb2e3e9c2811d385508598a0b8c1a0df8bac8337eb1bb5ed7d035c64e58207ce2c345461b292ebd97d85bce50371b138b77358076c9881846f8bdc58597442a03e4f6c79daad995618f3e1945c1e07f7a3fbbd30c0e68e29694ba654eca3d814c73180e032181dada27e28ef98518e9bd48a7bbb5a206260eb7c81e0396f9c3654a1303f8744f4965373b7051cdadf8d4dccec795f7794c172990bf4708f1ff98821ebd47e47f5d408305a496d81078018320d40596c8258db4920ef85fccfc6f8ada8362dc8b8ec53a8b1ae76ee96802ef4765fef39c02e05017106a0aa4dc150cb0ac427b5cb718341f302b8eea1d48c697adabe4dae0099591b92a84ab56bdc8cf1321f36bc1732e9b6ee25f387486f16baa9ff6a8c00f26f2fcf34bdcecec7be3b0d4646206ec0b0cb29b42b93522350bd5a2af30d15bdff17439d2865658e682099083caddb4d3e975330944ca59cefc187250b878ff3387dee70218f94596cfd8efd108f691d8ed5be005e9bae42460a128157940a52752ffc03ecf4bbd79adb5436f1c3190189706c7bad90b70ff337a89ff9b2b5a60356458b27a64ee69822f917c5cfbaa9a214d3b014ee40bb190cc5a18670111da3e66a004243ad5891e3fe3a06df3ddf926a5f375c024aad80e53083157dc70b3fe593fba022ff71bb3d54d40ba88a3f49dbc99c3ee2c7a4e668ab26330c0b229806b7c3072f3c02dc0db06bb36a3cf999774185447017e241eb80e35e889822646d424811399d2affafec9bfa098c02753e49d440e840e98f81f4af6a69915f0e78f3adb9f9c3ef939e4ada7643ae69eb545b68404944e033a0b0a2018c5ea3c61ee05a9b983fda55eb89d8a6350f0932a1cae6725f86f629914dbeb29a8357e6e2f2bea78949fd95be4225924e1c8e835dc94ce652473516949be4fe5f77afa4006df5bdc404f7355fc3e81a0800691f0fd67c6d5ac76e0eba2fc890186e64ca09c2adb59df0ae782979600fd816f0619ca8e8e174442ac5dd3ec6f055dc6ba99fc0d1ed65ee52d2a5c38b6cd3fadc97466fa944930f5456a47215ce09c692005e87c77bc337376a3eddb6e5f0a0c3c4b4e43e48bb736529793ff383c5116b9aa8ab2d3884675b3c7dbbbadc1d452a3d951c14ec0f38f192df7c11aae8ff5d23eaa7512cd627887fa91a115cc5d7cab699329a324ad68fcf3a4845a597eb752da84a2571d86be6893ad7880c9aa1cc2ff5432778cb08521079c97c181944f9e7395a793b65dc280e48d225004e58d7943e4d8c9c0f934d275ee12fb057d739f485b89987b45539b3aa9f89d235f00800471ecb5bc4304d3730bc5110a3da1b4e82332494e545b0accb493750d5017ad688b5b33ccd2408b4fb2118b5435042beedf66cce5e76b3f0eac3c28f93334b2f1f8c6ec4ef0010b97dfc89b5764f3fae4fe40c193428d694ae5a5f06773cc4362f588fd4979e904085caa890afaa2438ade2e4850f868d61db78ff04ea0f15d2d5d1a98f3429ade0efffab6fa1a028ac23c40ea8b1bf57c9d2392c2ae3d2fb88f9f519a15237d1faf41f498fa3b36c2f5ae61ae5349231bd617058e0f4e472567b447d9f716295bc0a8f4bd9b16ac7c7441950906f9e0b49e1b555a3c93c3372a493ec1b22143210f573f1136045b41430e73f56792a59a39073955e44519ef110b62cc0cc1d10358c1ec10d8f62624e92e44171d63b8b27fb14cf474f4c8b52856e69f00d6657eb0accd7513b349f770a65e84a96522a916b7301461258a8fa0f4809ce5b46183a864e79cd253a59f4a8da35f0307558c80a9b63db18a30bb74180276854952b355db819bdd5c7eb15a83b12508a51d93cbd6483d412749418a26511659aa758e63639e1e78c55238438d165a226ad916b08b6defbdc992b4657861df2137f843df3d3d6f5390cc2c64a3a3018ea92000f24b0c844dbdfde58a46086ba7cb5804aec4e9200d614422a434190b380125d6fb8003840e68795a7480ab91c703f7e8dc5666623292ccb7864bd0cce2040d2f538716a64499eda379532f5e4ce0adb625099d0a4c9ad83fbf73c2200a8cd122c7c68dab19939cec0a6a8ca7bb3adc46060ca38d3662020c030a1ade7077ea56880fc81434a62ee1687c22097abc21378b6978cc186abbc5e761b2beb3f5b89e4340922c74da8b9c48f66169abfaa7f545eb274579afec0d95b238eb0816996e5367b26af3b6740ad50c882106b94dc73e1be4deaa1563120416b83fa3656b4ad252182a9a02852ef9d5a7d6c6ba6291b0e415c42a7d0adde6479e6aa4e384ac341f6f2e572cca421737ea7a49de79e4de07f6a821b2498d156acc548728b14009f62244b15e83732025e0da58a1cba964e7b002fa5d4b65666d2283044957ac1976f00e0312a7795eb04c6d6f849aa596660a089e458c5f00de8005f7875e2e780a2a837e48bce15412ec0adef0b2240920981809c64791e03ca8ed0d772d86eb14e00ad09f9e2aefe7caee1e6b7ad8539146bc4d2cfd4e542d35664402928386474880c784ac3b0030b405b97c9442731b9d5e3e613a7c343a91a1dd3b8a85757e44c84836efb0d7b777b9b3132743c53693e42bd6be89e4f2405b646db9f13586739738b9ede1761f0e34d070d53813cd8f5298b61b78b10de64a3cd6a01863a225b99181955bf70908373d4fc1e33504f94648a46177cff48ca4bf1b5846ba4ccc7e10bf501fbe69c34713e38dd86f0a783ac86cb32f6b7e0bd69a7bed4cd6934997c2a3fc3dad274a434076238aad2dd2a88129794699f481ce2a14ce953dbc1af1c30a46eef7821acbf478beb8a00be9f1043249c7559282bf04d05068c38ccbb405ee4c70f3966ab161f42e23396737afb37832e53eee44745ae72620a5f431a1221ee815f746e7a1fdafcfb279f70940858ce9e271a9a0a5ef663d85380bc842b7308ecbd96c8b15b5e3330da2f740a55563da00e711c379478923576cc852a1858a0b12f70034e312d47e6fa0c2e61d6d77d2dea7cd58884179a2aefcd46beb5bda4194a5b5758d00447e98b637bc3956ea2be83f4856de70f2e3166785accb2128e736507621eb4faba67c60f50320300daddacf18b2cf2db9c177dd93a8276d25687ba6b7c44342b8cd995b742d17bbd6a5cceb10ca3a7a0209f9b74beaedcf4168f78a4f90bb603bead01f771ad6c88c6601e487b47964041d98b7881083124e08cd08fae7a24b1286373d60623c05b936479bf9bce147ae59b499aeda2933052b472c496fccb96a08b828dd4ec257522cbf5a60086033046bd3ce079d54a369f52cccca20e0caa47f6b0d4fb25539cfad821b5130b07143d567b240e58bec9cea9925a3019d389d5c6327171405f4339d6af94aafa168184f4a26d87b0d5b4f4ae67e5a322798e3599f7170a2a5132f49e3fd3f0043dc8d92603d1013fc68ac96eb6c35601b809709864ddf9d165ea86d0b380cb0ffbd23ff79e35a1d4855b0f679b2de35abdc139628a120e433be73f0bcd9e16d26b7878db2b4f02b039dce61912d162f6d6516f10b9d572c2d235608d25ce6bc284e618c54166a059afe61704da5eb501b3e0f0a0fcd99a212f5553d3927df3239a419d20123fa56d9cd30af4e96d31271303e51a6d7c6cad9503372f4b37a9c4442bcfadd219787cbebacf0f4a62cc53347ab312a805cc26758e8907724dbc429943bc838c42b43d6d92e1b7257c7e1c5001c93214151d6d803ea994a82d73ce83cb4ac36bc69cb6dcb7d538336816266f931021bcca6766faface43ff3717f711994a6149d2b945fd98db59e63542a745a7b5108dd11426af231df0f1057c7f887dfcc33ce18454da6a94f63246434afb929162136a0f883bf934126a28394d971ef94fc1cd8e8330e2e7c4d35b42d570589ac099ab87782af1ca0fe11dd16492b6efce54dad0c62a320298c11ae39dab1d77aad6e5eddeea6da902836dcba5e62a9bd0d9e859bbcb3533f5bece7100289d9b3b43d17666508443f37ffaacb9fe4aab9e4558d4bee87c757d9996746bdcb9cf65d334ff83dd8749a44451aca5bc96e39f2d6c3926dc7766e6b1d5663a32e4b8e02015c64bb276404f52b13deb4acf9cbba2b46aba2091cdf85fba020f99cbfb20e4d4aad2e65ccb1d5facb4a1d8b053f36d2e661a1672871591f223b8fdb5ef53b29eaf79f7e5744a1869da57f1488fe5104fde44df97980eab8566c776e40fe601552ce2e321f54865c1e8f255e996944b912bccba010b8aeaf3b998cb8a6f6eaa8c9d9da6b6dfbcd356f24f3428f58f1d02c3534cb64a156778c55175c4f98b0acad31c5a42933b82ef1de46169074e0415c58256adb237259579253693c830b0f2c5304000cc856aece42d0208b22b02501092dd8cb475eebde8eb61831fd5c79ec438f5ac8510512436a5c9464b6987353574a4f2e552d464b298725b470f9490ec7b33d4fbbf5b31c7dcf15e49335669c2cd9914664ead4ced91f3edc52621d236ed5da232ec7e927024e709c3cdb031d293782680cef89350c9a412bed672805648832398533e77986bcc6a9cc1059494e52a1ff3538e737eb8fe85a7aa77113cc0e17312b6a8613a03f0179ca12528b5a524063689c32f2cdb62a2af99a62aab8ddceebda1a4732747835a6d2cf25c8a31c7af858aa4293b9b920950ea6b843db64bdec8715fb01907574ab2f3d11bd497a6a6e662eeace165751773a04abd7eb3c9b7008c0c8c14ed5b744d45c02498352409497d2b2c494c95d64301504b0882147da68a95da2f07ce022d72b51d1ab4350d718398d221d69b008bd3465e3c04d7926789a10b08402e2ee9c2cf7d8f80bad8e5204ad0c2b4630f49f5c23aab09466235b5964e3975e942802221f6e0fc3fc691dc5f87d396ce2802d3bffa7141bd7a445be5f821b22452267054da0bae9f9509bca31e8666c188d845d23558cd9650c408997a68162e9cc9b36e37298c0f7e96572d56c90f7cc04986e3eb8fb390cf1df2aadc4e08fa283ed15d9d99747b6252c681b09fcdb2ede096e37724da314d759f1ccb0991e2b4bc88091011cc45808d0174d25a4644be38e19c22486bb5203d6015ab40867a45d02fdce1f1a6d3674362cb6aff0dd897319286a883a3aca759e9a9fc5901fb53d88d12385fc3ee02e994a8d5b7b28893479715c90a63a1343c0268f397066f02ae3108829c95394696b01c9421179cab5212cb8181e5ab0684abe44d089b95088a0211f9bee1e990da3061f4525fbc1674b749491869cfcd5ea50819c6dec4de4a65006fccf490d3db99689982f13141f2717f9070725a6df8ff4c29725bb075ddb13b37899fab0302233a905e009a9d05bfc10e88e619ae64930e524fb27921538bd88f4c0a924c3742a665ff21fae4988f28a7a465cc18d15c3695888ff33b30d2b94227bac16042d7fd90ea0ecd5865d102000836e8dc0bba65429d866476b2615939f10c4d9b8784ffc2097d7f8b51e4e30d021130b93800d0d8bfaf48e050b21d1604befc4ea8e1e6af8a017768d80e735c0a110c4c3385298bab33fe519262215026a7200fd55d0ca29f58f393007ef8cb5ff819510500f7c00da0764b4b350139f9fccec033c83506a96b0846fe85d26806df4d5f1b51537f51197d0051da7e64f15eea487102c61371ba823fd91ea0804f5648ad4735cba68cbe7fca63e8e7874b5785e3a7546cbfbc8143858a77a2ebe1d7e0c444a43e2b374e67e1931ae5dd34632002203648de11e9fa5d4e2ebc175fc63078da918f19629294e289c37185016de53ccf3e3bb26ed8a85676e7b145d7e91ec592d130452d3eb9ab29842b8c8fb9ddb103b2493cc1101e6a1b0c8880503cfebf3ffbd1d77d964d806a696775cdb49cfd5c25a9bd374bb832bb63d4b7fab0bbb1f21842e34ecc7a0bfb164b5a51cab14e54d56d95d61627ee75b9d307cd56e96ad42067c05f925b420eb3bcce48f2e30b03dc8b15e129372213b5f0fa838d685d56aa0ad26a72941018dc822a82ea4035f8cdf7ea9cdcae84d92336d6e8f2eb6700cd38fcf27e2532aba0a17dcb7fa3e21fe40d4901a3f88c57e8dda65a92d6c2a76299c4b1f7628206943e2b0576b87ab0d6cc6e6a5681cce12831364b9387ca3f5cff53f60fcf71d2642928c1f4163bd2434fd68eb9c74ed154edc22e8fddfd4d12b23edbe4dc06f7bc0f040fa90b7f96169e16ce62b35e2cf1eb287588fe353dcb209d9632171c2e32b0b8ecbc008de7f142d8662d680ae3e81191ef9dd33ee857d6f70a7ad9b4c8ddaddbe822aa208b7b98a16f5ba28f36ff64326d4cd53aaea0af088424ebb381471a443a4591e89ca070292f51d74f5568b80723397263f519d21908b722355a30a744d99b1fc20e3138479897106c01d047e0ed696146f8258982653e9b4a16af056a44a13358082b85530c059ba53ff21b45699fbd24a93eef3cdf397bbc51390f48264a1a47498fc48c8ea30a57394a25b62df037d1af3f140a569a5c37f8aae39f4711aa8169aedb420a883de4ce943c189000eee18a9b55aaa54dba182928e54e8437e80ae76378a36aba5d3acb24ae8a0511a5e603a06ccc756b0d7116f7d6e619346d268185fc4cb48b8560a54cda14a1c009a7ccfb9c9d75727d77f239d806dcf4cf7bdddd54aef26bc822df344228bed33fc4185cd95c6fde673cf830bfa0d27bd77ff9c6029a0c144a8c480486717285928fbf124f417616e1af65affaff10e87fbef0afa133c6a9e4aa5ab3e1e099841c3bad5819a0f60360ab05d1bb00948d62d03a9aff6853f58eabfd8562c59069d41ca1cb848cebfd3e06bd4203935d83430209f82f913de592acb0cb217837672047be62369546f4503bdd66f480c73548a05fcc3cec76d6a64331ab4316e51d764aaeb907869359217b22223db508b41b11f562d6510473ccb8f46037af5a9c5e32fb88dbdba92c641e9a71d30eb38cf10b24480c718279b2bf31867f664ef1749ff25e960e8b2b81120e5fe24ba76737f471e3d4aa023ba10d2467ad4237931908c4903fedda6afe4629be543b6ebb48591ec77de89a090fd1a706cad24021319a0b369e194e089c02370a85810e43d2a4b89e111181413fd868663eba0bfbc84b8c19de8dbc9e2e50ced04ba3600f41d1fbc80c6774db2c592d13c370c69aaecc6282872859b7a569636aa565d284b535533a430a7716a595326f945cc223f31c6e3ea6f30206379031999feaa343195d586363b1516089ec274b8dccba3ad5e2ae8f0d0457fe384aac128b45623dc1f2293c2e53f0e4c9f92d0581e6e119aa97780da70664b70ef30af53a2480fab70acd110a64935d1cfa17aabaf957a45cc95dee8273a11174c9094222e519dc2748d0f19b3616e0d64e472807a8bcf60e8250c7ff44e6315ea96335eabfe2c29217bcb3e01d64fa508ec58c1d28c3086bc50a8c713fa13b286177ee5c5d51b256bc795d6b46266d043a96aadcae45a14cb18454816acce306d1ab958221b5104c63ca6c5325a0359f7c55339ab19008d47193f2b428859b57e585d51a8e3d49c41fa181c3b5f1e4eba6ab459b712d19809e1a8a2ea00c7425b98797c50c269a2aa8e7733284104ba26f7b44570d985b7a056726a1b8e9a631529da3e2076a0fec5ccb600044645f933a94d9b0ed2b00c5d9494cfedece09b8eadb73df68036dab0e339da22dfbc2b9b69587475d28399b5ae1d97fc9ce0fa526ce323d2f870822bdd9f5d89bdd08a2c0c942f3328fa56a31a2287ffe39d1233448207665c73ffd5ce505cae9337e0599bc5df638218d3af6c47258ebe103c1f0b5f6dd755376e4e4a68128376763c84d1e778be3b99daa0f5ccf5de0e68329825acbc41b6054cca8a974bbd26cfd73d584c11f9714f8f09dbe44c10dc881dbb3f5e89144ef34fb31d874e081a9030eea26678402f501ce64c27793a27c46e1e8918804b1567ddd49df5d9885daf0ec608bb1e5b630c8fca66e32dc41c3b78badb0c1458d47b2c8bdb77e5930145e8be4102c75d11472a75047e476664f923557e7c1ae41d9bd1532e23f859a9331d01a6c426287f40007d9828100abf64baa37e8cee3234992695736d988a826490f7fc3dbb3b354877b63238f0b01c14ec1235dcc1f712c42b5bb70829222a95f28ed4c2f2b2509baf0bc7ee1352a4d9cb5a44a140af35e597bccbf22368d215607970b7d7b93a4af7fa26c721205211f6c5c821563bdcd518c74aa021f96324177b53c705a01224b09c262792f6f4b6d8059ed391fb81a2d7e16a3b10c923aa8ac3e07d5495174c81b6aa4adf0d472b7d34bbbd65d513125a622264b467971017eab5bf57ad3d071971a75c59007834c0bd2d7b54e73c7936d2128cc401369805b556f4192c5e6187cc60a43497a92a75cc60350b59606e689fb5b5fdec8dd87d50a05e27b177a230ab5c6840357dfdb3f0871b6beaef464868707a8c490e4392bc3371c3b5282f1d3333ffba79695712e9344cc0a42cd737960380d78370aaa9de979485fccab159a44677582bac9e23b1973ee430e1dd29b2ea59fae0dc0ce4cc7baf9bf3414588a4444c23da785d3815023fd61b2d05f938ad6ed3bb0939041045734d39347a9569f07170ba881dc224a285996911296dd0e249001c843b8f31a7e03afbc72496c363cfc8701b9f588ab90355237c3859e0e78ff8dde7cb5d5e9157715809ff766ed040d39a7942cc848f04b0fc5e91c59acb3691d3cdd36cee613310b0132d72b86b8fed069f30c0496058660cd082ddcde651555c4fa27f0122e9992329a11575d5ee0429dd503d51811004eafb2c65965f95f2cc8bdbe198f288239b3918721f1637f968f3a8d20b224166b88b03bd2d5fdf093f9f4c8db5319e14e388a182631d303574d2775bd09962041be8b0412246e8f051bc0553707523dd1e03758cf12912084193cf7158e3608846b830831e29908feef76c66aeb2ae6ebb55d9851debd7c9a893ed3caa34896e44e633099db7096205135e60d53dc22fc3469da578948a992672fadea43467d62d1145a2a48fe3719d5dda4028f5b930fdb458985be80a4dc6787f6852ed07925ae70888117b46cc1367f4c65fa3b7e2c870a583c495f1e2baa4c647748a690e5145a4dbd586b557e050aebb99f073179e4421f7817c8981b47bc5113833ee045d48656013eb010c36234ad195b9243d832d34641de7a6269a432635f281da583800c62a1a799579eb18e00ebd9e5f7d0d6e494e56f9b95f16e5771cce0e2a69ca657b5fc0574b8c19096be87e0419032d2857be18299d14a7a9432ab14265e2f6fc71dbdd99cf54c4c1c067b09afdff233147cbc149b29cd71bdfa2b0fb099fc48ef4b3b54267c84ccfed6835d692dc1998c56e04d600a5307ce03a307c5ea9e293f338ba6a6e971519e7eb1a59c89b3c62623df58abbc7fc13368dd16db3252eb2158dd682b151494a800446ed402ef329501752bad679f0dc6e03a6da0c9c63952940860a9ba91771b47da99214a05ba695da4a369f5650e39b7a03a80d90d30d58e902f6c7096d34d540822d88ed35a25db8a74b370b74d0c672526423603b96249e5a22f1c1067e70e3f8b1ff61d971423af7ae0a6ce2fd859be542c705985010ed1752e18a9df3181aa5caa642904377e338649efa3a00bb931805e4f4dd7851760d9c093acd81be6c90626625a26c05350be8ae805407768d59f1c0aabc1719211ca774f264659629e9c24b07bedad1141b163ab98232a22c7a3b1c8162946349d323c881bf4d3c2eb0401f4a85d1dddf6212549d5f8b33a892dd585872c4400af6dc6636412755ce14f265a1a1008da7fe60187f9d0153645aca847ef6b677bf37d2da25777bb5c5558d9bbbc03e851539932403459e93c10dfe0ab83f3ce7115f8fdfa433fc7b23d67343f9429c6b3a103f77882c77e53e90de5b2e69821e229d24b8ac593d84462a71297153f7e12270a8b5c1cf510dc605099a0d03ef1f062a10034d2706ca3847b89f6788fee053f386bb29c3b25ebc3d643ee014114c6812dfe562464165c846c4c93d8a1060b06b93171adf91bd33c5778888b9c110be03dcdc4c5705be03e71b471380c310a6d40849c880b719233a93bc33e7adf7b7d2666781e034391a9cb1c5ba774ecf699384baa4ea616bacbf32fa463d957b1ae959a9b7516fe53d1af756ee69b4a772afc63d90f622eee52bd935aa5f99ae71af4a7b1af754ee6db467e59ec6bd95f66edc5bb9b7d19ec8bd10f702c7bb7f3577893b4c7d95002638cc6f53dc372d87f8082a46d60ae9f4fc8e784394bead928544cd3c4e16251416390f1c770556ca52302b518ff258b2e6e142780b90a5a503e4ba1ae2feca227c2a0ff552448141c0e502e271e546c857343f7fa3ce55320052517307b21b0c32e3ef3cf12558fec9016df315d26b07afe619087863f0ee05260b41cd1f3c7c86dece8014520a982d34838ba82e632cef43cfc8972f115867230bd1dbd0ce6b142ca038072bd01e408070c0ed2b376d28048ef02b124172e6aaf149c9b1c123200c70ad3733023764cf186e274611824ea2ec99c49945d980182b40eeac3a7505612284c1c810da877c3c885e1571f4e3a34ae108b5162124dd3c47158a3af8f1ab24f0035a1609489c4f11aa450efc7a14d25c01ed6544c8c713915511c73e3e4a298e50cb0262c2c5734445a1831f6f450a7fa4659108c1fd144959e480bf47c7ac5046700f11c20d6167960406a1232cc6a35ec386a71fd34d30c52498790863e63a42f0ec17f2f626dbd1e27e654a3a113a6a27de7a7c30df6ce473a0b7481aa57b509b2d47593f952a9b539056706bf612dc876d0ddd02808f943229b257f22feb11822868b1964a1b0fe0049f3e8f701b9f1f685bf49323fe4f85c980212d99c9dabc3d3c04bb32f54ee923bc84a5806149ef960f43f4febb94eddc6fec05bfdda8a139c52c77f14a45ea0704b38a3d1aced07c6e20825fd4df5e6afd6e2092631936ad285c7c0f3d2abed1fb4f6fbdab27c1099510eec05685fead88308fe02f57d2fb8ce6af82ecdc480ea81032df88886c9b207abf48cb9fe8c34df0412e81298df1d0122c7c512c34df04ca8a8782ff4345eccdea5b35717a152479223494f7ae468279f53edf882f96b6af790d4809cc9ad129ec0d11fc57567aaff5be62a4f75e4a3b56625216d2de936fcf7d5ad5fafe54190c0b409ee9fde0355f63047f7d2e62c09e08c6a918259cae0b2781fbaba6825ff7f6321d0ff0a4df1dfc812c7e09b52987700ccaf48fde574c6ff17826f8f63ef7214b4602bb83efcea5e8a968c3be31996c403a0f0adef7d9f0cb746f29d79c56e43de6ef91ffe34a9d205f20f407b6660d1e2600fcd0af875ccfe0e1067ee2296986f85b493080b5cd4246b988248f920459d5044c4bc612e7211e35d941eab238a2e1536225ab13a07a54ce597d8a1f89bf606fe1a238205fc487895cffc67f2e9f0302ac3e0b8bf9efc13fd76f69ffab2412349cb13cacdfbf91d904152e5a893aab889b8f3cac3fe09f2fb17958df3f38b327621cb40279543999c7c352fa229eea3cecc99f97f31e250eb1362e4d78d86d7f2d3b2d0f1b52aebfbddfdcc2370ed2a63081eb576b2a79f2bf494224386ec07858027f60cb7f32985a46fca6c8d8070f0be7f8174a1e36847fa2b303a109d80ef2ca244d5cff3e480c1ed68087dda9e4614ffc894d8ba870d2cba6f9c8f5f49187f5fbd3f489e461bde6fa95ff5f1e7c80202a2c6690e3fa21f92e5fe73cacc59f35bf7c6c440d54f25087ace06109fdfdd3a71179581fe4b586d27ed4d57e4fab4d01e161d16bcf6e821ffb60be23416a79d8fa54de6cb36710abab2481277d1020a0e161ade7167a611fab576bfc7a79eed3e389a1c5f52b1ff07e29d73f4fdaee3659dccaceda1a272e1b34dc6bb2f80b3451811a12715cffc9d1ca8099805c2f6822e775eb5827916f877b15bed047bf5aec9f90033021c3aa20782f3e9da887b7bafd8f11f10d7a1e16423e41f1b589152e68aead119b0a2e338732aedf36f3b143a3e0e1d74977d912e5a8297fcdf18b33b3277f8f3a9e0d1485870062eb4d9e91dfd39a919ae28ce1fc5e7db973142d4e66d72765db6054e276f51cf1917aac35a601b9f1efcebdad56cee9edcb3bb628c33c777d2b5a9aa392d50ebf15c8abf14a7ec123fe5ef11ae7dc12683250bbb2167542b8d746bedaa6a5257ea75912f9bc079c0214b9e008ac5a5015c522ae331d16428eb1312cae876228b03e5e4f9fdb3d4597a7b1f776a169cc2a29678952e0ef48f387718fcacec9b381ae2f6c47ebcb66f58abe93651904e6b525589d5900052fa4e923aeb6938a3db8b3c8ce89bf2c2fa0f827745453642dcfa4d14c7acf36ae97181fcffaed4cc58fa9ddfaaadc340c951072cbbb139f82f6e787b9c301e443ecefd9f09ade0ade13999677d2f627c9395ab9cfd45461bf15d9ea4cd6b6f0ce4296d3b17e9e4b3739cdc99c039ca20af82ea3b5fd9e50331ba7f4775e1faaa0167b873a11ce6f005f6d89d282e99d284bc6d73bd1b03d67c97d7413e315f04b11ca6aeafab6859b0ee195424de4201908b0de43a9a8485c43f395cfd6ade09dc9364e593d817838fa32b9c1b91fa52214a23e710d1f718ff78b8d7764660e8a47c19921be2fdcf57028776125ea9440b44080cdd1e31a6acb142673a4a5cef6b2c443d935b2fbbae4f683875b4c46ec4fa1db96a6c10eb844db52db0c9eadee4b6a52a4dfa47cf5136b2bf60e532cdc0321e01cf0e454a394a29662f4de2be5630ae76a56a2629b8ffb29c7000e3f4b2eb8d57ea04e44f31bc2d750a2b4607aa729632bca18ec9421af2362b94d07c7201fcda126cedac0d75f3498bea991626431a1ccb20b24b2482183dbe0d599a550f01235ced31f0ca02b839c3a39eeed0d937ed9487c2b23fb545d90226bb624ff83f4c0c78fff7509571b0a5b9339e12ff9e4df25c58222e121e6244121eb3d908a0ac0673fdb6ca909caecf07c3243081068fb47f91d4b49e4986f708747354b563b786d11099c2d7138c82ac8d315e439a2a262c21884d535eb50d02541a0a4cf1822a9a81fd6ebda06bd03d8efa4511b45a6d9bd925e2931d1cff83a7380f5d8c44455dedd572b4dfd00321b63b2bb380bf0a4ad171d300d7d4b012186e50b6f4772d3f326d7aa8a66c213a33b47d2a9b565bcc0a6f980fe9a12a079a6de33123f9aa6c06b8f6e5ced7bbaa319997d5220260ddf26d5c592f8384a9fc723a33f9de467f66d8b9d646540180f1fb81ccebf78a005f1602de91ceb3e452ed810c4800c4e5e8796140e31ec3b93cfe0d8097b97611ef02fde6760315d5fbc971fd334474598478b1ae2a30d844a397a555bcd5ffeb1bf6221c30318d1887cc8ba270a5b587ba66826726845d87d87b95bfc3e92173d61428c0c93b91f364380794ccef9fa05170a64652147c376ca94059d06878d715dedcf0e7dbc4eb1519ad069e432f8a919c25150d8db490a55692d8dedec457605da20178411671611c01fc5c72f47395b7266255dce04558dafaed9e32a7fd1f1cffc41e4562e7d871b4d4927d171c2da41329b9cdd688d89e303c7835f90760e2190ce9d7c5ee327586e7f1a9dd10e5cd7a301adb7d4eeef887d98f4f45503ba605e785a401c554d07c95ac031bc6245a101d61a05ce81d39973fc1de804477b1175806b394a39dbad60e3b70563d987120dd787c6f203c0d839a984bc466db04a0703248f1985eda08bd0d99b7382370e086b56ffdeddb69472cb94920c550778078007dc5b90db5050b089aedc310275456ef0892dd21b6db7f7127d0f4479d10b3eeec76fcffe06caba89e86f4fad8cd832ba2e7cd125fa237816d932c61854f40250a6685ad01d0f607fca16454cf03c198c67db76f4a4e4e58b2428a7f712e5400b8a5cb84e24128944ef2715950e4542f25c09c8315ee2716363cffbcd03452fbd057022a277f92211d10f92b7af620bc1ddfe47ead6b725504801b20cbbb8f7720b87f040981a7cf65bb8975db824a0cc1123df7325a0cc112a52fc8b9f5cba449f725b27d2de0ef2b5edf96a9b96aab9d2bbb1fd0868ff037b84305c7b9d95220ed8010766902579b9aeb3cf79d2f3788a8c42dc1af668917a37fac2f01049590d145226e2b5c040c106951c271279a21a4d02f6681922df75214c8b94470cbe1a2d7a200f8fbd0ac814fad2839129f46bd840c1e92076474e418aee7b153de0248a7da9e2d65a503bb95d1b40f4f2e35aacd4be81601aa26829b81ccbcb75674b64b888d8edcbd58e82138c81525da54512247ac55fa432852a4727a0090d3efff6547cf08da26fa3fe48d2a069f6d3340b925cca55a0dcce97c3121949354df33be70f0d04877c728919a440207c72c704b13bc2b8ade9f8ea73b459d22932fff352df99864f4eeeed039cc2b923a7e0a7fe6ccdfaf3016e341af9b8b5d6963a7bae80c2c7f323f96ca7e283134591e68e5209736a13e5c4769aedfe0445fb47e21b4f4e644a03d17714459f0f856434e79c9372edc168b24ad7d781cc65eda7775e4badef9ef65ce70b51fc1f8dbeafd670e4a172b51773be11e5a268e004573c07348b7de93580596ca8e225297c2a5325e6c5c9f79c33666a4dbb06e148b2a40b5f8b92060d37575656e6ca8ea6d1a2080d9ff730b945f87b45a6cc1da365298bd0e158809b224f3c64693ef33321039a2b6164ca3451c137aef0fc207a6040b480d441641e7206fd3933812490df29ce3971b81a15c10c659516692753e84fda759776a3eea834bdf4605282cfd2f76114cf4ca04f2b3b242075d0c130e8d7a7ff710c1d9452f9844b27b3b5122661d5565b3b39c35363308909c2d0900c865cce56abfaa708059071fb5fa44811f85282866fe4a0cba3b39cc5f4251cbfdbce9225fe0e5f5a9c61c7c286b5381180840a0148ac6c0e3967fa6b74872e932939dfaca3a32ecb97d387aa4d3f6813489b56b307ecd47d7a8abf26b5bd0c74a02ef13babce1acd46d366169ec5c816fb9f51cfc7a3c33fb6f71ff95bc8cccc293a7d94d87cfcf2cea05a1c27cee51c94f8c68993a343650ce584a47639b1161988c90ed422cbe64c8b2c9329fcc37cf6a769d30ffe353672460ce40cf9fc9c94afc94e4a294bccf08d1dbf0437c0570b49d2591d92e4772b27e532fb317b0eab3d7619e584a47f623cb2ce650639a7450ee2723882bfe85ec819f2ba94b23b1f3825f3efb64b9752bee8eb421fc217d14cd6ceed0d8e2e59854c837c9e52cae75c4a29aa747f928ea0b4585936728ae492c6bf206730102898a345fa9ee2548e9ce1cf335459fb7d237e4fb10e3c347f0df319070ff9ba5cf340e05410bfb3dae4392d1d178fbfbcc77de4f5a145b963478bb287d99a44d87e2b184163841146d01861040d0b5850c4bcb91acfc0c8148d19a645ad19d562773b4f7bdbea16bfe5c4c85deeb5dfaa0fec54d43c77a057bada5465b3fe7ad4dfe246afa6c380d2ed1f51af1aa77bb8daf9ad2f99524adfe93b7da7943e47c2a55031e2d5556b58430d225ed9774a652868a52bedad1ebdd5f2e5337a2bd6a246c22cd43af5aabb1b9605fa0e6b538d4905d07729b2df37fa14259395c04890307864cacb860ec74b40e972253bf85678df98449e5b6cb584be9811ea5373858b45c74a408284a1f574898138f6e3e21d0943fb6f3462263894ee07e6e3313cd5256d07bfdd73549bd3856ec8202467d491dabe79162c4646e88f9b4b99422d03ea536afd079d1683f0f97f21c95d3c7a51c55d2f1f8ff15d91190d05f5aa577eaa72865fed4d4cf08ddebaee3bf4ad65e931c03ef5569be4bb8ebb364f0196d2092353b4af1bb077fb4f7b1b9c9cab6d9acf380aa87d601d5a6b180cd64fc814ed47e9c40d984c91dd9233eab5a75e693ceded2d6fb94947af8b121e7c3e42a2071286f61a8d9abd45460f5c1aba8eb754f0b9588877e4e9ebab0059967c3e0b77177df7cf008aeb21f7f6a9c780fa1e6e60e51d8bc4351fee158bde4e433921f97994d189e8c60ff683085d261a92ecd3ffa18264c3fe69516b1c11509330fcabbf08f4ef95b85aad5624ce751c08ec6b62024de3fa07ab367dab6e1c969cf14415a40ef435ae611d6b536d18ccdad035be51eb7bada5fd603f084a1fc53bc83c6d1a0949297ee47fb8da34123a5008c4749d165f6db2e10f92d71e05580109437b1190532d6a3c60c02d5c8d245459b1dfe8bd45b5295913f1c22edc2153b4ffa1e245e00f3a72c616deb06110acbd8a70de88348b0f3889d2eef5bb7e837eb9061d3c796e34ab68a3b6bbb559451bb5ddfe2d1c88f92d9e3b91d7c1be75d24a4117221beab7b0aa8e04dae0a2550da4833a0c72c6eb7c398d73359d2e696fbf6f24ba4ebe8525817cffe43b8f01f6fd394f01d6926c8b1601a21f11895e05f8a265887e636d2d9dcdf5ea142058575595a2a9a94275c8ca5814855d5a836e2e7d1bc3c4ead2b733b6765c9daf6a3bae76b51d07c4aeac8a899a4bdfb2aaed38db7175656b55d50ac4da25625514be045989de6b1589ea4b90d58aa5b52e0542eb71b548491018e98026e425fa232f5934900481141da0295eb2b0d8f9a497bc447f5bc137f3123561e21b674b76552a4ed449af7fc5d4282258483a540ca122f418278285a64fb27a89decc30d3cc9b9b1a7770d6d8b4d83dd3b469d2b448e79c01e78c8a65b644a25697e88b6abc44df32b8b55aa4ef31ab1847f989a4431b62637989be163aca4bf48960a15b8dbb4db5684cb60db430ebd325faee21c9ad8ffff8f071777ff6578b27d757cbc59e6fd6dddbacb7d9e96d2d9f944eca4f2b9e7e09893b387326ce8fdb9c23f8f82c0a91589bf89e268bdc3982cd95d77e2844e4090a1045f45aac872f417c4c2f2439fd2d2439ce9c4618721ffe2f417cb4c8cceeec03c812f467cb4f2bbe629d03c7a97329904b7f45cb04943962b4a94b5f0b2b38fa8daf5a6ab44847607e4af1f43d68c638cbbf2075982d7697f7a8e40c7965fb8d65a13f6b3c66ce94aefdd1c1a0aaf1d8ac5b46ce80f18f8f74f5cbfba74f92454465d0c843978e3d143476cc7359803752bb149e8bbcdb6f1c497d6d28920db5cdd46c2812129faa9291c9e66c0a6d32118c87eeec8d97d8ae56d5488ada5355f59562847dd86ca86d664bb5693cd9642d0b24b3c184c64d1664849f4bbf63802efd2aaa221e0ff3f66f3058d04c68688b41b1c9b4695cf181c5da24fb2c904e9b5ac4d3a6fa1ae9b1346d6aafb169533de1be729f820b5f82d49458b6b9f4bb1b9b921223a86a6a6a2ee775de26c23f5bac4303240cfab409e31c420e9942e977e17c2e54a1029c6048ca00df1e32457ee1d2e9222f416e2acc4fd63680b2589597e837e1abd1227dd5a55f0f3e1551e0170f133e91a16f9c3c3875bbafa914496bb69ac1eebba513551a22ed87d68db3b562095f0f394372b5cbc105df687d2eb5b3dad506d959ad2fa4d822b3f5699085f20f8c5bb3357526cf7c4d9ff93363b2d9336113881355e74455f378684e542727aa5bab4f2a9ebef569d3686737a84da36d5dead278fa24a33d25e598cfd7a74f322378faf6c7b6ac8fd00a18a365f093802a2d835f035fb40cfe09eb936c02b54932e103b24b7fcea6900a507689bb44dfceda347290156ad37862536d1ac5b1b278da346e35a3558df5056bd35863406d1a256cd6a6d1a65ea99f213732d4a5cf413597be0f3b6078f468935ffa735ca931980c68d6a6e6a16f7f7d59206d6abbb23896d52666d34d9f260bfdada64d72c7083197fea6aa3995555b6dd26993e8e957579be40988028a2dd576fdb24ac91e528737fabeac664e245dd261bd1db4fb3284e5bd0cc99129b2083f37bc722789215c0e5f8268239d0121ee48c4b52f436ec097213e5a1ca26a91092010ba0d4a268440e7be0c69b528ef4b90991c7cd227c8cc7d09422353e47d1902a445795de84a5bd14ff3b5a7db8c6b0a748193289c09cec7cf897e8808e0caf742f0bbf9c0cd04802b628284bfb5f61fe5450f02f351bcf420a0bd88cc11a2dfc02947e0c97bc9b68cf6c8f0f58f9f8a17c1ad48c28a24ac4802c74b7c97eb9063a2bb5bd5dd46d4a4aa96aa3141aa39126cb5040e929aeb9108e98d50f0f9b34fabc48d73229da3f05ae609be98cf5dc46b9923f8bcf35a7c84c23c590a38580a9ff9fcb9c57140c2792d73da7cfc2521e732e91c244defbc96e99389159b8887d3db816fc62027b8491ff972f6b9b37b05123efefe9033c2c7bfddbe2c349d672c64837669f376ebb2eb63f0d5a4aa96aa41a55ce6304b1de6b294c3266baeb63a579365c45ca1ae8db996e4bf5d7b6b98aea14645685672fbd468dd369119da20b0805432564bc7f5eaf981c566aa954aca182c69bb6f748222be5059614de9278dc5bafd2cd6b4330db26b177662286d76fb35d5665ffba9a19a8fa6ea139f60a5adda84c3d25a1a8ff6d27a866ebf06db2cadf193c84f5ecb8e0f66467bbd1b7fad4ce9977664d4e2ff40edd7a2add569b4a9dfbd04ad93094bffcf6e3f0ace106e9bdcde414276fb576a54ea9aca4fa2aff547a7a9baa4eafe47f7a3a6baeddd775dc51adaee462b73c4a879610c5704ca1cc13d84abcd6cdb107012a5abd127bef192ff4d0c895bd17bf4e0ebd1a23ffff8cb4fa2f0a54bfe5b6823766380ad47a9f3ea5007ce37cad9e8271292999f5896a8afccb9cd67797a1f09f94692ebfc92257ffbd5d05f2aacf091c825a0b0c4f5c7d1275e79c97f745fc5954b08b9820d475bc315e59577e3c5a83d8f6dfb06bd4bf3ca16672de0a63cf94b18f2e4618ed36d968ca30624bed15ae720ee236197cbb58a627cab042cac340105ca3cbd6d5ecbf61c33cae542b95080bc2361d017019909947bcf3cec72c2b7518e739125fa52ba54d52f0f6a6ef7b57ec82f76b548d955c303475602bb64ab860d3add3b87e11d9942d92553e8cb6078701c109a3bb2176c3743e163c42d677328dbdb069690a03a0adc06d7862d7c2b2a3154c401c814fa6edd3dfb9527478827870b355c5189a10285172e495a3e951776db018e280544a607e4fbff9cd7711cc771288de3388ef302ddcd36dccddccdcdcdccdcdddddd43c5a3eb54dccdae7d6bdc9ad6dcdccccc3dbbbd9b9999b9bbbdbbbb6937a529e66e06010a35846608cdd0502c369badc85277b705e293292ef6f022f66974a31263a5860d1c3974dc5c7f1f767878befafc38288a7d4acab13ddf3ec9f09033f832f38bc09596e132fc2da8d232fc29f8a265d0e7af61234753507e1dbe24e123bd97213923f71e892f49f8b428e54c6b4f4277b7b04bf1dae95181857dfef27e32250d2f7ce8a104166385860d971c26abebbf2386733b856f144d703ed14f3cc892bf13aebf15ae7fe31885263f900d92e6e8bbd1770ed1a6d66e74bc3ce1faf7d0a700b0f8efc0b9fe305160f11d57ebfa07d12693255073f466b8fe769a406bb56a70ebd5af57bffce6e6e6e6c64b6eedf7f911a6cdb4e9396de6cd9c28142a2553e485f938772646f8c68e51a980b40cca729d39b8e0f3f15109c99d22a04c7c755c4ded66abcfcf109ae9d3ad031b4233846608cd101b99226f1164b1d8c8b2d93314930dc58686862eb775d66b99cd66b3d9751f22e3b98f6de21b39463aca51337336833c35a4f3822b8ba03369683836696a623473c66231209922afa33c4584ce799ca7679df3f8ab9bb598430bbefe972466b3cb49e676910e5f92109229f2fa8ddff88ddff8cd1086e085ebafe5b48977602284a9a3c33c53a75d3c3c3c3dcdf2d30c1a32697d63d7748dadd3d699830bbe1eea2196ad622a201367b25633876a1bcb308a633845b5adbbbb3d66862f49f8f4908f8f0fac5f7e9ab3d76cd6af39ebd79c29c12a21c0aea7c071dc670e0ee34c1c160e0e4e0eab56adce561ee2a016070905b55a2d17b7989983200ac1bafe43b4c96ddc278d18dd5ab1cdb475c526c69c2d191b9b246c92f0e1b5bc0c01c23233dc9ca9384e8b99f9899b676db9b335081edf314d13749d4e6df3a904d0a4da46c1939984aae3da43c5a3eb94c851a954ab1afe1ed302c257658aff386773e6273ba4e566b8fe946577c66887a48edd9651eaa9e618671a8d359bb3193487b4180d35c3403c6b4d464bd9eaf1b0b2754e5b270a3d71efe38c693e84da44ca217abe437d9211bdbf26d326520eeff9a244de7b67f94926b4d23264b48c96d1afa5fa247a938a03c193928ff5fafbe8dea61a7fefd5f577d978e2cf34fe7cb3babec9b48951336daa3cf4e5174267b6a66bf2f00dbeaba9425dffc9e29bf6d1a6fafe2728e20c5f924855aa8d3aac358958872f49a46633771249a492a8f94cfd9255cee877ada501d6ce132f20ec3c11c47dd6baf304cdce133e769e8871020c569882134a70c2084ee4ec3811c409d58e1331d871a2043b551083164a7e4c7848484a56aca8026ba70aae510784850c6aa84211a49d2ac49c74412d445a70d19d9c9cb470e162ab820d264c7c491010e62384b1e092b41886e10d37dcb0d3c40c1683d2c4134df434c1c44e1341769ad0c14e134650810c3950018877a8204bdf07388932b66b9ac635a53149f444b0686ffd44d221f2dc734f048bd71e48594837c40bc3a2b1703ab232b07260fc2467b7be5c72700c096a8653ac19e9f2a10c3dd4b77ec3b8bc72f878a97e831cc3280d86478f2084c0b9f5555efc2473441502e4d67f11e32751c82d2e2425c03b1c236082c4378a2a2b34da648fe0afcfc1f0b8cc305ecbf6b6d69f40befaf28ea2f842e5d65f59dd5a1fa67b51f84140148e58ea730ca3646438354be7d67e197949f35e7a3ab81709ada7a37b2f1c42f4ddfba959be1fa2afd1cc1da516e7245b54433d8be839cbadfad6825fcba855148a3d72866fe41ad14bf53b741fe92769fc0ca46974d5d2e939892cd5073d7d92af98989829b0d420ad5b496e7d8eb549eefca03ecffac42cf5356e189f5bab78a2846f1459a64d4d89f04e11d3b415ebb1ddbe91a67c922c7d4717262ca3c8f51fe70c13e0031b29cacb61027c702361b03cd197b2c4e2ca912b59e44b69a24f9480096030f9f3edb66b357afde329a5cf39e7ecc94e04ce850b3e6b65ad4c696b9acf39ddb566172ef8f88eb67a377c13e197df24902d53fa47ee4e96a00d172bf8361739834ff545a9ff013201b23f4596fa515adc51ba46e9fa15b75bb86b946aeed23311f2bd087d7acec3bd1d72b4d8efec01d140f40e09084199d2de8d1799d2ef1e0e97f0a5f08dd2e5d22e57bb70b4a88375788084d17e5d644a47e16b590ddc5e03b7672f87277cfd1efee0fe07ea59b897927993f42be80efe107d83e1945d8b8c92c1666db34b9f93d275034063a545fa346a080951200a4479a88bead016cda12c974e8b264553535155a6a2ea4c8b38df385f970ef5bcaa8ad627fb1a5e616385dd66b940be9bcd8da939d474586bb5485bfafb585ea215c62189495cb97265aeb3a65a323e9c8be806e35cb4c9702ee25c8452372b03da60b25a6b6506abcfab4ba254cbf6f8c8c8c884987320ee38f7b1e270c779338c53353637ecb5700ed717f7bc9868710ed7ca2f4172727458386dd26e5aa4ef5c5997c35153f5cfd2d5542dd26f611d736aafe25584437c2b4cf0f58f5b4da54fbab15dd177387eb4c6e7ab45a116e9a76caa86a46a6952555467157c19d26216d1cfad66b3d97c6caa6db5e16c39ad0dc8c6da743ace6955759c6bb28ef3d9710ea2889eb35bcd56d31b6ae3d96e546c3e808c6cab11398337542d12eee9f1e790447d663db269d3a62714f62838301f3c3436ce755950cf68936dabb4c9f5a54da6da64ad717a685432a0202156ab1a47f43425f28f9ccdf08d0fe769f1e4509df9a97c00c1f1b195a0d32608217af0b076c0f0d8e88c9e5cff13e6410999717733f82264266426448824c6e9458f580059f8a838459161210c46494860fe5f6bc40b28244a0e2ef8461a3c3cfc6a9e16d9cc3cd260173f3193706c86c341356c300c48c86a3cb4d5260d428821acc8ecc33e3cd79d630da46f5ac5427d12bdb70d11098f04629beb23e3ec609d574f8e1e607aae6f967a3c745b1510ad97e0e11184104368a0d862c7b86990ba062f512324c6dd1c8bdd92784b0290c1d79a8bbc0801d23c05cc910058f0b18f0b2006dfc831d2e513fab08f00d2f0f58fa2c8fcd66be111499bfa7bf5c598e07c02e8c237cad9c89091d8837c940ea70d9736758726ed7150e574edbad591644797fc397c1c7280430cbe1e52fb1a92e8cb5bdf0712fd8f52aacd0d06df5f1b50f0511058129202a6d6dd36597b22cd6262623e7073fb4f50fa4595226e7f0c54ce6d5be90e096e7ed05e0b3f143be746899d586a27c6da89adb470b38385d59561107da58af3093eb9041ba12b9700831801ae5c020c823ac64bfdd67edf68f42f8a1da3229ba58c0785e9a694b66b488b59f8815c5ee9ac53265d989999575d82e1e11d4a04a28841849cae4894718456ee4988119a4c2907110a222424068722be8e89a9a9d961bb3818f1f11da54c6803a267625ae46fcea4e77bc964403cab747a9d53ba665c46d92ca18b23078c487b4ea4d5c933244ecdb44c24f04c8f023933a50c047439518743113052ca0ac3a3d65a6badb5c2f418a23249c7101f112c1c248801da1d1a19f2a7d37bd470ae428043e4b4495ed691978368b12b0f6f20e600b46442e6fa7e75ff88e1a6b84c6061e65e967ea7ccb233dc963942c6302e33b71f479b0a70fb454af089defa28597e625937ebab01871a7cfddd6a6f479afa03b2a7888c14fbc7e6e16ec538cc224c2fff4c760cd8a86635e16316f70dc32105dfd8ac91632ed3ab7d7f770ba89df55cc249bf81b4b89229fd1dd83d9030fab946e2f6cb1639c1270abd17f91a8e5ce3e5901109bd3087d2d6e49c5a299d27dd753c71b172745cb0574fec07d6e2f18971228deee0449ae60327d2268d1669cd6a569f507c7fceedef96a7f33d8f487fbf44de7572bb7f44e4f3daa74fb0986c86828a39dc629d8e71bbfb7b94dd34370da49f657416d42651f7bcd8a79b7e0f385c72e878691ebd830499db26faddf7d3a861a3034f341a044645127d017bb1796014c6514a6ba51af812040693c914e9bafcd6c5fafc448280f7222f1276805f5ee2f7426e79891fa5b986c1c34914927f172f5f3c719da6b578c2d737fab7d7c00b70eafed0bfc3f64cdf0528591e942c13e497df6c3b7c28a9f5a507667676e9dda3e7264f394d9ef396151a35b48f11aacc4ed3bc1bdb75a15ded4c70489eb33cacd702a3e24d703e51278aa1e22f9da722228202c5ca37329222460aaf45e5c604e71b798ea433c1f99e3361136942f25c49d7830f3b5af49792121a26262968ac7059e1b5d870c161c25eab113454463a465e4b0e91e78e058be7588028babedf0aa668b1e152431a2638df8ae7489d09ce67b2850fc59f78373c771c2dbc1697156f82f369ef2cc20aa230e2136f8d16dd468bde99e07c2ade06efc676619cf8b4e7fe41300cbd169516bd562362dce0b5a8d4bac2dd3063423b74c39c415328e8861bfc35a7c3dc63404da3d929b6a83d0aca0d28ff72063b8aa6fd0d9a3627d8332dc38af1930d27669142aa669edcb48905a9863f0149204a0da07883d7c282854299321385821234b5f0858562a1346d4e8d85ba9c562a95401004bf54b2c1458b9312a90616a55269545a61522a954a2502b4787a7fb40887f0008b089274fc3f112c2e40920e17dfe25b844338cc4bfe2e9e0816e9f9cb13fa88b8ce4abd70f06e94bc05093ee9e383d91d99f59a3298f89adf42c42857034c2c2d42098efd357c048edf5d019a80252009384ad908f8a1005f40cc6b032873c4c8afeb202873448b0f4199235a84dd4fb074a5a769a19d414f5ff4d726c95735ba1204be0c69d16813f5d7a4ef63dbf408fd38cc650ee441b1990f6d534419689b220db54dd116facb5f7d72f1fec9b469ecd4759f163f367c8b97f94ecd20f0c1182d83be0da04acba0df027cd132688bf777211f8a41b50b5076c97f24763156da346a31d74d23bf460d856364a0eb3e2a51885d7f1a2e3a589dd32d47b97a5e3e3f30d75e8be11c960ebb4c230b5d6f13e9c67645db3c113264356d4a6ba3d0a3d1f045c890f69ca651aa09191232c492d15296881a5a06117c6a1a0e38dfc881fbb5e8b3869be07cfe9e07bee4f092773834649fdbb8ed2dc7322e2f301cf8d2a2e3c8a1a3072eccf12250478b7eb9071f5c5af45fc1854f52f04a0f07253e7ee92940f4a3fb2efcbce46ffdc54f8e5c33707733337737333733373333373337333333773333737733337737333733373333773733737733733373333373773333773733373337333373377377333377373337337337cfeeeeee661cee66eee6e666e6aebbbbbb99bb79767707a066a8144df83539856a0400002000a315000028100a0784c2c15090c579aad90e14000c86a6565e429909b324885114455108196308210610000841608486666a00e45d20c0c96080d524b7b1a431c4adf65d4ea3af19b856ed98efa4f66fdd80a38081edd3a7a9905cd56fd3e103e0f34692b80a5a5d02ca1fcc2acd65c00821ccc0342028c0d0fd47be54c8579f8bfc8273e9cb6db4258c10a956a5d2db982ff05a84ce6cc34a3bd5c29e07347e75e7f15b787bd05c47c59e9d93d62815f9b212139434e5aecbe811e3cc57372b64b4dfc95772fc32b62b7a61a24150181ae325768c17d0d53699ce6121b0f470a1be99e30438411d133dfc47806a5f59dff5cd1c9edf8262c45aa8e3472c4761f0a79adac926b09e6197ffd644eb6410c98f28ffe382a64facc4490675ea66f8327ba9f1809567e0c1688f1bf9e130eb5af30ce1c74b35f1b58f40f12f9bdde8ef4338616bfe49c420977903fd12cdd9756f60c6697ae2581f9950fc38487337c57a571bce91869b8f3d0526e674917e94ec27f9cb9cbceb4b0b1cefd1a072eb82fa6fa1ce4f1f888bb920284a7db0e87fd23037fc6ee6c2bb91ff724cdfa2ae48d33b0c47f8333d32ba52ec61243f56ba6f62830f0dda09a54987d9c0be530d40a09d58739d03c23549431e5d7c90864168c09560f07acc6b588d6ba0e99791f361997ec0801de88f8cc39a3cc8e533f57c8704f6208aeb48e709d011067528fa291ed2a98a963d2da4a9e1d2eb573c54eca234895f1c9b709599253d25a8025b0ceb7ba890d39b1384024ca342f835bb12a81d2cf2986930e3d367860651498fdb95d089264c6958dfbfc94f92cbe494dcf4880bc1477c92ab50234be7990a5282749fc3392d4d4397327c98d15a7069d7d2592184eb52b6b0825c3dbf96ed0c23028844e1d2e436507533a97b41ea911b16fece0c2e3c66944720c72c8d7d2826f2d21470b4f7d9db98d17ad4aa26e6aec720515b2313a43c379e961014e08a84e6beb6ac816ed72faf8790dcc4df0342f126ccb36520fe3b5724d51857963c28c66decc547eb9cbf12836f1299427ce75cf8d6af4977f0b86a9828b3112e447322fa32ee3c57a45cb5b06181ec7a23b8d02d32d05c0bab18926f56d69f6fca4efb72aa8472b7a761aceaed51f0e4fb6e30f19633b4f10a2ab93ec974af88f2ff3c3f6939a4664dbca8e9c633340fa101b28d1e7fa809fc148d563924e4fb0a59b0e7ffca5768e1bba051559ef98b48a0c7b19c26d0c2369abc8045ae394cfc78594d08195148caa2e6d804a5cee3d713c87d2fc6c80eeda87a68a26d266ea9bbd6ceffc2edce5dee3d7fceca71b5bd1a098259f7006d0613cc8bee26615befc8eb51ef7e3b89d8f8337473b0ecbefc7bd1fc403b051b388474a59c150cbf81adfbb8c9619082cf2e78055d9e99bc979ae78a6fa6e387280987bd4de789367fb933be27fab2a5922d698a18dd7aa2ff5bf554c4bc4b347e55bac44ebafc26105b221a968b6b90354f3f50306ca4a59d7dcd3c0d9986ffe63bcf1e3dd8f6bb81490b0e2deae9f5fb586c24128120a12728f303dda07422d055afaf1b6358d825453bdb769d13a5eb786e495daad2e9fa79fa5a5d0ef717d9e15cb519c11a0dc44c38f20dbb8357b8b9739d80d24dffe8b3f9f43167b1222456b96882cecf716dcd12763145495c45b3a81c90477b7c5989723865396905dad03c09f2e97071a63f03a73f3ef9e8d12c1dc7ad1ee28f7bddab3d11c794faa43b4a27587b27e596d483942742bf5eb2bc49ef77a1802c3efdd92acabd1ec5cff9a1aa856cc1b38613198ce0a4001433f080cc9e32d25610e259d0a8371f5892c3eda1f877c6e061ff4baf1cd4a430ad9d6f1e9f4b5551a1f1fe30d4ac146f738cadb1868dc0cc21f96a528410c19b9e9fb67576452d3b3c7ea386575784966ebf7be43a9e04cdc3db989373bc7efbb979039f7760ac0ec671523224eda6b668755ebafe269c2acc56703a4879da70e6f7c0f1b48c1c6fc9f20b62bbe19acb9f94328833042d5a0bff678bb43ffa33d7fea1a5ba10fd9250d9757a24eb1524d1a68c290b36ed55b84f7d07f35bde60bb898cbe866e902b4ad0ba2f4275c7da23ca59227774b4b1f1b1546764ee3ab55199078825f0141030071bd8d9ab3d996c4e4180627a646d96ba5aa5c3ac236c325843b1d014de2f4464f9467b32b09248d602498920df05c59eecbd42dadffd3bb7610c50ec9e24366bd82153061809a1f8d83ab0ce4986cae123bd962261e39b52e90b89d207674345fc9d89fcdf64894a2ef6c705da0ea25ed111865d6c1c2207e12d5a78b68818cdec4188c889ce8268e3dc27ea10fe5884c76eff57cd1c3e16efc6cf8f7929f8669ddcc9a3553ce482ac2b0dbb0509e0362da428397a63e5a058b44175d3b16682db24a899da3f19e183fc131cfb9b0c3cb53f3ff1e16aeb3ec8e8726941d488e9c9c7c0a5c150964d3d0d1c5b038d1bc9a6122f1933fe8a9fe0706890379c2d1bf987bf5fd1b1e0a92376d3b17d93384a951b49bdb998a526874d79c953493de8e67cb450a1385c513112bf4fa6574157689b069681f77110ce804b1d05216904b036c66bf1cacc0a945b8fcb617e81151dda578a06b6eed4d57bc4ab0b2d610ecf317d3e334f6a0e8e66184a44bedd5f5c66a75b835157e7100e34ff90be38195c05eda4b57b7a8d9264aa6741d5e5185a69de8afc035666527989eb8a4e31287354619f7f84040c6e5d21ce825991c07f52c0ab2bca7cbc023a46240fbd631a8d89ce52230aa104cdae8a04c393751d4d192f36a4604661428bcea9852f2a4c862cfcf3694e887332e7368ab55ba7a4f8aa68c21e2335e89bd46e45c0c783b7429993a290bc827f2a0359db3e3cc8da66ae4e265f4299cddb3f7ae2b6c535e30a25b1ed633ad42eb8be60980b9e1e2713d7588a552e24f5330a567e61028f7540f71bb95c4de53a5f84967e10fb8d8b831ae524995b2e842e29d215de6d833d16cee252867cb33c8026f5ec118c6a756a9771ebc6b4601bacdc9638ea737b6acf21b8a1dca9e549bc491bd479a4b164d3c1c8bff70be28df77e1f81b1f2886ad1c4677812a9b2adcce103f53089e3a8f2894026f7b8bcb6e9e684e79773fd91ee4e9c70853ce4aa39e4730a9acdd138fb996bb54ba8bcf39978df5193c03c1cd426b64bf7d5f4c61ef79d6be6696d9231398788eb2e09cf44b672ab7e51ab467afb7329ef456dfa5555d0d56773213869302a532dcf6b25594340efbd87ce67a9d3ce8dabe042bfc07165e848aaedf1d0928880c31f22756450ebe7071489a0b7f7f1db04c26f9026b92beb96b20cc8e5d97d9e9cfa4236fa45bf89976135ce2fb81fcbd0239296c6c4a3cd57dff5cef36d4a9ed40aa1fd24b865c5505bd679634369b972d07e615f78d38cfaa5642b9eea82b2fb1357bea121d78b2713cbd7c82550b89daccaded3ef619d0f631e1f12598c47236110d37f549a3baf9e952412c91d5dba7cb92e7d032c4fc1caf59c8c920d07d18254d45f33891d4cccb8e503b15c985622cc939f4c161791de8644895160ccc153535d9e94c225d97a6963a35edfabdd6c1dedbc4d2e8a5aa4f4049229fbf52aaa462745d9aa876f9f42979d80053688042ccde9b5939d4ce180cd886e44c3e312e53992b863a1ccda9011844456b540b5b3588fa86936184eea7128ebda003d12eb5a8a8b1de1d7f5a4301497d29e75ef515b645b6aba4cdda7d534b12151aa98dc5bdfeb3d54993e886a1e6b8e9e181d1245901c53fc4a417582da0568122358b09190656c6a9fc3e38177dac5459a17a50e498c4db526bc3c2ecdaf414d255047dfcc6a2a663af84aca99aea0901782434e07c3bb393845aec7392f66fb89f98aa41ec3b4c3040673cba96f9cc1c0a26ca55fb4a1204755e9f901e4b1f7f5a1a6511196e15923f9955826f970b8ad228e4e0ea089202b14c267dd8e85471b07fe17e301fc2448999c7301e462785b7f3e87d3a930ce02d0be8784fb44059d2cd0cf613bdaca62cbf0e55c5661b120b45c9907451545493f45f5b29bd0ab61b17e918588f779ab74c5f85152d21fd846cec994f9a963764c1f8de1b475faae0bb295d913e9dceed8b9662bf6adf5bc38c8013c92d4ae49e4edc81941cd9da44768499869efb3e9e4ecf10e295d1475b931aab85c261a19e16846ff6d3307466c1831888ecbd75ad7ee62065c4d6d52870432aa2e6927108e5518f484490f2885d8d2b56dc0e9a2b4a18ea1bdb1e102e4c9fb3ee674ec1a9bfc0388e5b7832b356984d221ae1490a3110347c57249e2858bf04f852c4c1aedaa87c74ea489eed5237d00b8c8cfcaf2894a674975ed142cc8c3a5695f7adcd859018635e30610e38efabb82141c288e676c35f7d6ce30910ddb735594f985c3d0054e93bb5c33c72035b545014dc394d4a1b9be8f1314a33da9b9252554d09052fd7b02dec465548bb2ba10dd0243facb812466ff1213ff05024564d300d0febc051ace36b64e9f5602412bcfdd2754ae0e265db6ca3ac966bbec48a401f48b5eb21a603ac86796ba2e28ebec84adbd9c0e6a819c728d33028d6050ebdb11358512d7196f52bfc37aa452050b95e241749f633b4893d7cdeec61cd9d57fc844c67649ab9e923b3220d64ff3eec093afe62943de7e9907bf82e6f11db16a48dbbdf026ff9ffd4b257dbfe3e16f8ff2a9dbe3d13f6309cb1fa4c63863f214f11ae2269bc60f35d68756bda6f02f8c4298e27768cf045f60ac3ecacf252c9ea118cf0c9a62f641ac64b1f1d08364ba3463b58ae164b79e0d8c413b0d624cb62e969dc9240b02a308985714581aa019a29e8155e21b82ce0efad51b83e01318734da4d19004f3aba94bd9a8449d8801260fcd0934399d9204da31fdc531aefcddd1f66aabb8c0bb00b1e998d9b05ec3c659dbaf06c415d14f91674b1e3fc61028e842fda788273e2b76e204e059f869cf446a31e437efd9404f591c1aa2710d4f69727a1a15baee741f3ebfa03d65cc597c518b61aa5adf87524a4b3ef08b22d304632fba84a771a517dc4c6e1ac9d18bffb176bca6e4ade58883cf22c892089ce382998195ed77a043d4cf36ffcea8ed0e1e31576353bb61fc7fc878068be228006113e05622d7d34a45129eb5ebac79eff5dd3f6ad9e37e86627e9a5141fc7d8efa02859dda5158da39742eb7c9fbda449cc4b8801182fb0a21c0a688a1668c77e36e4d92c64547cc77b1f3e7afae4037a904eee0afbebf4b6f36e0c909cd8c3b460024c8466b7b0dd3eaf62e310aa3cbf3a5fa34955ae249fa941f27e8d1ef56344b2c892d0e1d111b4a7670bef23c65b2284bb3dcbb6ae55fc8ceebaa1c75819107d79a1abe17d0c5098bfdd3e28a2047e3c0987a19ba3af28889c69c675ffe214e69520cf977fd7b62079fae93c464deb82050544b043dd288efb372a6c7b690a3d165596594d61279d3f586d316b7c576fad0e5f1d2644cf07d087305ce4b34afe7f527664d2ae2749e1633479122d104e9a7afe2b8202d89fef3f8a5f0541435c4a61839d33655d0a8c6278844a4827aadb48de3c5e3a0ce1442223becf400d2dca72e95375696ab126a555fb76f0744fa70dee047d10f49dc8ea944e22eeded038fc2f353807fbe549cf82d7a4ac693a3b5a6181ff656b77dbe860b756323f3a918e9b760e38d2c343b3a70f8c35edc8b08b76bfe33590cf83172cc1cbb428ab88ffe0ec1e56cf50e03e98e0aaa5b2e48e116f495f515e34d0234a2faceb709748eeb2bc65994a936fc613a40be18bb78bb5d778ebb78c7db5ca6eb6a7343376c19adb1f7ac2b503be6c08e0e3a7043b04f7746636cacdf7ed29315f245950f0b278378c8ccf03e12acfaade3e24ec061029facd74fec9452b7cc9b05d53561bc94189f632f26b6579e3916190469c61725fe741b84a0be45c933721facb03ad8cae5d2467aecbaeb9b6039b6307f65d226e0afb636cc28272a15090b860d5bef6150ce6b27434ebdc0c06aa3429ec2668695430e27f822bcd71023b0ce222c88881ae6f5bbfc98bf6cdd2f4e6969f93d79a7daec49b7e4474bb36005f809e7fd2edb1ef4e41586707dfa222832efbc841a919f5824e7035c69d69cb27806daa91aa89230022ab4e85a74d5b4a42030ff928e11625ebddf95e90dc88397563d30990edd93ba99dbb00b9d227653c24716e1ef97a64d41d9d6dbe377a9c0a02753fd5532e5d27c8750e56c4ede4e4b50e85376387b1c5d1149fe27973ed56baffcc69780cb6690b2138ca74c1732fc08a0e6681875797e3bc3df191a7ae110a102827d1f9a18e43ad49838607fee2f918a3a9bf659ff9a47e8e89c497c26e51fd93bc61de54197c4be786240caaccc980ec5669d8d45e53d0285420785ba18140a59f9be1b2584312a2e5d33c8c6e7c0203d24429c01cb7558e70b6a211eb63495a725d832304ba88768f202357176136ad3de6fee47757837be2d6923426ca883f7f9ce28368fedd4f8c5889c4d70a656841b5d703dd82dc3ac848168c8380d9f580f9c1755074e6162833abc8baf96b2112136eac4bb7c6685adc7566afc32624e669c28d08ba397d4c9fb7c66041b11b1a3cedec55743ec79b6a0eeb7217066ca09352537a24eefc7a7256c44881d75f03e9f59cdeeb3853abf18819319ced417d05bc00d6e78284866727870436638144aa2b3532032c4b8428021ecf8fb8a4517827a471ddfc7c743de8c10bb75e4dd3e32cbfe635b357ef16fee3cb3e3fbe7edf105cf5e7372dd6bc57f12d4eb1d96b279a654b4628d682a654b8a4bcb02680a89831645097a0148d7382cfa92e5f9d8327a7c16b8b48f441719dfff37726db7352daac0acd40d178551a6f4c6b10fd02b723e81632b024d2b8e2f7a5e8cf74ffbf7c51c7be6d4ba6f45ff23d4cb3b5c4acb8c52cec406a155847c0ace1666992c89f3b6cbc2c84cd89d69a0af787094322b46b4d2b4c27321f3c5eac9444b19f117fa53d6ca710b73a5499ce5cdf27a7f5f433fdca6b05a6e00572b68dd14f827244ea76952a15da9e0edb83236cc12a822bc39ca999577b14de2f88143e615a71ae57bb115448c1682b7c5d9b39a71f2c49b0502494f1e6372374e3615027fd43572d8d25e491cdc90182fa461b37fa47c6000cc819099f5d711ff72f1193b86b7e76c290fd37d26ad708d60c394934c965b9c89afc861445a961ec06aa00bd369f4f984cb4c71778959f3949b16072bc84aacba3cb87e2c3a6853239f36d9ceac6f6268399964d40bc3ad2ff6fcf2dda65f9ea12786dc7d7e59865e98dcc2ec8d416ee21693b771f321d8eafd436b982da1953392d0fa1022d99dbd14eb3cf9c2b611753215c4c0647332d09f9270533733f662881bd3be77f7d3d1e29dc6801a52edbd42fbc2af640075d3b1e438c5eef4871d056bf3749d18670c14082f9586a1bb4e6f93a2bd7e9da2d779c4d9d31de47ba9538cbd2506208463add34bd15aeaa5ba7780d84854a41a9d6a14bfadad9c09ae40c24296f153acbbb62651a856d522a7fab21dea2dd903f9f2b7761081081aeb6b5d7fdcd57d1c1c8d6a77f426ed4a7fffbb2e75dc8c9c9bd3f349c870ea6cf889f0d5159c45d8f311d7a086141fc4c285280ca7698ef2a54a62752955061007c5453f1cb4263921aae5653bb7af4706d53e080645e7cb22da6fd0ac0cca77551560ee5ef5323858e06b6baf593e48f4b37ecb79574c0221ad39f41e9e3efa480d89e1230dccde9087f5a33b647c4c6eb05873bd5a0e06d9118f630a8ba7271ed095be8546028242cc0a321956d00675e2ca1d956eb029db1d551b2b9ab03b3c77b8ef930a7984a96101c2e018c4936b4c87f7d3b851f678a647793040f52def86ec94e43055b7c7abadd40813aaabfaeaf20fd16d19b55b4c45d222105fe0c36767ef88016902fb703ce86d8df880735f81d0a7f476978ad9bbba0f08af82020b5d2c7e334c12edc58d05ded42d43253b5e261d39be30e2b23184a114dd2ac58d122391fc8533a0d0f03ecc004fe4c258b3b11e69cb69d5eb02d6aac9e283d63684ac311182a414e5b21f20d1919dca617f2c09bb365066d8f09d25ceea16d2281dc86b4b09f1b8316b852b2a1da64fa693c5e079f10f9716e3887aa6bb3a49c0d80cc6211e9edf97143083c2805095bc349bd634fed0a57d1a5bfaa2404af8a633bd92d00d336384ad725db67552af8fc048c33840028fa9e72c13bc28859f1d65a867d1b2f7ed38c05e01df0bceb9cddb21437c280f96426ec3d846557565c91bcd9b1578f94b873c4caacbd3868703f6fba3978a1a36c0a7247aa0c4bebb38ded021a45bff8c12e3f1f733e6a84a0a40f2387c91d6db88fd1f2896c4ae72ebf7df0095738e0d681976d67d119ed85bf21f2d69af1e34a3b17d8f3fa011d7a4ccb17a4600477696005dc407b34684167909e6ca15cd8cf91125522c7a06b1f9075669a7a1a56ef6b1898402f009303006f68ec8b62bd30a96098b264563c7709ba1046ff588deac492ab1036ee1e9b9f517d8ad060419ef4445dfbbe2284b0e3d7ff26b7e00d446978cbd93f8a91e3e43048d37241bd0f4f0e2186f298c44e8a530e247b4a33fb12210de0d7ff0373e4ac7dc49d43b5b626ae8a8ecb845fde1191a5d049e205a03204e54c57ff86d09846575a56fc82e769d84bb7f4db6ad8b04a59d544240a3a90b487e1748ff615364e2c01ecfbe02b7116a941c37ffc9cffc00e88d3983e1a455408bf2c0317450cac43edfc324be606dd4287a155c93c7835073681f0250c1ba2468b94edb0065b3d2e710fd84bf75479280ee4901b229fb27c9cc36f727262dcc20ddad60db4108d49d157187d3a4b751741c84f3bd5c742582cf7c6e21afb8cc737a55efba97805ffcf332d20b86fb57632b748528b63d820f44775e48cd26badd760e5de7c86d3d25c44b5967411499ac31b2329892a3f6c222f4d6ed09db961f23be0c25a9bdc1962557418da6e39e9c45cc565b7b8d4c8eaf17245af47915e163af67c343277d1348cda8432968fa144d069550d8034710aff079bd7a72190a7475d18c370cd6065880a047a3b3eba10e7a9df9bc90c341d8028e8145cf4bba1e3641afd8f67a49afe1aafcfd79239c57b75823c2d7548cfee89be79343f3c0199dd8a65945604a3f846aeaf28ba121d1b5e9219617f14c2d0f511694a7684bccf34df326dc01bee2879923425da1892cff9099af52f98d461a233da941cda53099a7266f99dcd858559e336d00051a42d19a6b9c188ab9591f029a6668bebc81a8dfa325757dc70c4e9abe79fa816ee1dcb9e69a244431839c9e2fdc7584d977a0f3abcb58030cbc824b188b1a0b01d75c18446ee640f9b7c1414edb4c350375149455915042e59fd7a048a9c2c388358fa48e9f29f3ae9bdfad3744ef2dbbd3883c324b4dc75a5369a663add5669646734bd2d0f789bb5e567593dddf31dd6468f5ed92d94d3b6a9cbd25dcd62ac07d0cfcddbe0b477235703717550111a7317763783c0d2c1c1855e74ba098c93604026d0b1de64c10d646fb78743d5b97460e0faa3bb2831790f10d333b0809aebad9dc1f3b505a003c87112a6bb279a06e5b7dc828bcb0c97fc2b62e3624f7d5ce7043fc57c8fd214c6a0b6d6f7095102d45adea7778388edb00b53c343693a9ecd88c71321c7be9b3f139d63612f00de068327514247d03b253c1d6d399753921545d24d32f25871c658cfb243bdf5cd7a4a08fd206ba6e05dd806f286934f96f6ecb048eea3302a0db0dc9c230404571fbb443e4f62ba21b5c753840bd82b456f8cb42b6c9324f58465464ce77f050d8be20618b201caf4a9685ac95047f89f6437147022a7a43987663d14cf931f1f721157d31b0c0854813b75154c175f2fbf1ea495b00fbc86a9973ec2d024a8258a5c8efab83a40f041d65bd5a4290460342ff6bc2d9a92ec5f74d0ebe5898644d3aea43a30fe945b576496afefd78e5d3d9be8d750861a68a32fcee2fe9925b75c5c2a693ca3ddbacc20c6219d907a2a781ee1b6ef83ed7efaf3094e3050344d2df9a22de646621a72a9b2bf4be342fbddacac9c699d2139b374b2c9608c13e98e0288f1174e22489ee400dfb81e9140c98d95235d9b01ae34c3e1099b2f77b294e4469237abde611353a8902c9f53015d03f63aade3c21f22900a43b6c509e3c5d91ac1245df0f8e89c43871fa0a27c4fba0a7890e8b587fba10947cf785d60eae1da2490200005e9c91db812e8402d8b2fab5e867c579586fb76b9a01b2e564ddffe3bfb873cf3862ca9d605f866466abce357a1d6badc754c8b38519af3924b3a55aa6318e997bc44b81fcfa02a6e98f69da826fc533035c4dd3e6337a9ee479b026fb83d946f6a96be7c65fd4621abfffcfbeec4a4f6c7e93c668877ca7bf0fa627ba895ec43e7ebed25b466cdd5906f5d43c73ed3a0e7fb7ed8a1090d01dbc86adb7aa6222e957cdbc4ad0b3066c77b6cec078e59cb9224fbfb7ffc0118626dddc315a0ad54b24c77c45263c618f222c1b1dbe701d5ba353c33409209a26ff945f02c673d52bb105d2d0014db83dde8825289b5623063f584fa7d23bf9f58a09393d3706e612588b3ff285f45fc903d3ac3a0c1c7560c38e546cb944a9194d711913e78d6c1e4b1055577b505f8b33c39870dadd337e0391550259b8957b5ec837b06a7d7a067ee2a0ed23debec5025c547fa7bbdefb0e5d3944b0bb6db9717cdbc9302e12d6cab8306c251972bee073e316a177883b723e024f3dc74d2cf9cbf4dc5e60ff55824b7a87ef79ac570fd4d4e537515c75c3c32f59b2d9082ded7008756abcb3ce05c4a1571f9ba2f5009621924cf0b80f7a6138ba81fecfe6e8339fa599021bdd4b4d53fcddd7a7cb6b5208aa310247e05563f9435317e4c6743125691c4092138a9c251e67fe9eb33661135d256fa3eff5e3918be9fca9dc30d48003c93c183f359c85ecdeff7a93cb0e6c9276a19413172d727a169b2e050b55dd817a86053d2f8954f57e35276e8d32660fd6ba8a975191c8aa6f35c7abf5ab005c9f26576c734e8b4b74473c888f138f43ae32aa8b75f1eb3f17a1eb8a9eb5f7857952a4c15ad22836a971f8000bfbdf74047bdcc780fdfcf08884972d46efa0ad1c5d929179a1b76b042f2c2446a6e43d6e90f6c50fc24954daed04d31aa40a5a6783380aba7020ea02f5e6eac21978666df24a5c6175b3f52829b6b489a47ed03a98ce2b272f5a6c262545d246cd56310ddf2ad614c41f8c8dd085b2c4e27692a654087296844dabc3dcbea7db4ca4824cfe3503f6efb65a4e34b4046ec1a009961ec5253591d4ab14d11dfc5b0c5d7822924c76f0459451e9f0977aba9b11e8f1170eadd8cb972b302606bea196b79cd09adb788e816a35f216ced50f01ed5face098584633ee220ca78228e179bf5174df893fcee3753af9250213689c2447a5054db9b44b5a364440cc2c2d6bab1076cb259a9328321d609f6b663468a297ce6d3aeaa85f873226740aedb9b51c0ea5021ffb420802dd7579c124fd15c52008745ab5107f0323ef65c8599cc03236ea8344153342818bdeffcb4c81fa7b9386fbe89a87b9bd3debf6f9acfadf6989d6a91bbc4c5437fc3584ddbd4e89573cfd0a1271a7c7b21663cd965a244c1580f420508aa46cdc5e8605eb1acfebdcc70bfa907ce8b572bab947af5db6356125a96a7e75ec34d8560468841965b4d043fc7ab1657a008ee1a7b3ded6c60d860619baa35d02eab44dcdc33a48687f1ad7ceed7afecdd8e93823cc68a2d2ce464eadce702ab0ad7beeff62f7aa3b0f02a4e5448348807b456249b732b6079191e1dadca6de298d44e57f0c7b01f7f299b12341c823bcabbdb3fab1c3d5a14f57af8ecfd2547478b432eca192a177623ddbf439b887b92485f7163aa89075a155e57c54c891ba112261201c4d743d9ae5b4956ed4082d523861feaef2ed3899dbabb5a9baf2513e72f643730478650a8987ff69d2798efdb917957d188a6ca2409ff1939b77c2d0f28c7f0903cf2e20eeac12e1c609bd306c982852d2fbe12a68789d2e9f6ed0f5163031982746e66ad3eeb89689fd62cbf73994fcba8d02bd2e4d6f729b573919f8bcea224e03d80bdb30787b82910ae6018ec1b81e5e587d2557c9847d555333f95117a3f78fbea5bf4ea27da13eea2d2bae2b5af49e0003eb831d18038b36c26f0d0c4bc688650602b8416e37de8844fca81c2c736e60ae640bb844ac090db2529e0d94bf2cee01a62333c4652507ba8b84b912288925ce0e926fcaf0ab0400a2c91aad2223f08e22b60e6fcef336eaf4e6c1cdb496aeb7413635b5423537b691334039b530d9e1fa26f6eac7ddd21e9171a3f010eeb4c56a1d6640a440560c4412fecdcf08c3fa9c1c84a14b25e6e91884bc81dfa0bac346394e32ad6623a15e8f6bb1c043e1e1008ec55a07a4d9d4e0093deb0a577d942fa4b64113cb69987403a2d663c913bb231cd72c25e94574fafbf570c37148571e9a9cffdd6f93c718fa8430ee3662acc84e4d9c13316598f43d39fb63d246c647075300e5f206d00d0b50fe2073cba67a1b76708074594a615216239db4e47d873870fcbf1a7c3908cbcf669f15e86306c35e0ed9cdb20c3d8f1059c9e643a40e48194f1cf180da8377120e7813f33e8de0fbd1d704372f220ff4fe4bb9ffdeaf55f409622dc151f865da38c4ab4338be2043782a47dfb4979b51da8480bad281a8efb51698205ec5652d800d76a3565309ba3661a2317601721f893cd66825c3eb37e8e34fe12d1f315c692b31853d2f369e04d866e79b2342aa9013de7cde23af466991f612754b86f27319cd32ef9a2611941480780467581d7836f8daa43952b12467de54b88b2be5f8165e0a3a6698a16790be998079fb1e622bdc99bbb8972a0505b760210d5ea89ca1ac972f59d46c3407a94053a33c482351baa92895a12a334210bf4c55603ac87a0c9830f08cf6a53ebb567464a8a30c5f7adf014bef6320689a2e0ea5226a6fa92477f0b4d5da51dfcfa75318036104135b22d6143c11e02603fa2ce7e846e91924fae8a7a6fd72ea1349bde3cef59cce3564ae61ad528c22b881ea800499cc34022bf9c852d870bda5a8ff42b8e00270bfa0d459a220a849cdb549cf5ab7de8bd77af877c224c9171a569f6387b7cd750a1b93c736ff95c2b533821055d50f26b1011465edf24aed7c63afe97854d94fd9a7edef5082f368f19d8deb6da83a3739d9f9f7d00102fb2850b4e90288b211a6a3c83e6080954038a282dc46aa9480b88b8a70a8c28ccd11999f97929045f838f82dc6680299385215b2cdb628d1069826751a8eaf7925456b93a1f016a269458c91fd1865f07ba5440eb787feac823fa5e3b73590f456d961492aa63effc9af49bfcc6796e0d2499e30a5f48b9e47be920e4c3f54ebed8d205d026763452ca9be78df6d5b6ee937c84f4616ddafff159ae442cdd911f473b8a192794b5d370ae30e9333051956cd1b8e18ba0fea662154df7c0b456e91f4a66b07ad647d7ffbcc6361cac1ebfbcfde3e7b69bb2c4205cb2a7ae100a5c2aa9ad37cf3f1eae4d853da523febc8a6698a0997ca4fd7ae9f584ab08fa23b54b8adc1569e3668639e2b5022b163e33386bbd594b81ed5b5b7bed9cf1641adeccdcf3e1a531aa6c3c3fddedf73d7584b2578c94f49b147b979ffd44870fe13724029be0a393be481621a2f5f092ae5e9c8f14b08191a0d265bd857b81d5dd95f56e6814eeeed9698be3194ed7cb5dc1cb9a5029a9dd62c195fb369030d8673fda36041952f79fec821a5ad023e5525cc7a0d805014cfc9fdd0d2a666af47a89c6fe01b3029df99898609afb06ec20607a7f427f1125599a5f5dbb40c0254ca4abc657e4e83db32be24e24c4c1682efba2d0abed7f0bc30f37c1cd8a38994154a82fd8b566c654080dadeb14b4a3d7c39a2dbf9061461640f18149a126a4cbb7c74508f5ea02aec41e9fc4bf036ea294ce06212bee139052175550efde64e449dfda8aff9a93c5a1849a8b13e36f61402bd8b5848d5fb0743bdbdd90c2987694bc779d5ebbede3ef1bea29976a443e138769d6c449067ece4fdfd404417e74ab8b55963f672fbc78a076ca2ae8869944283bb7b21a32effb6596a6991ab248825aa37b32536d67a018a77349c5345622b82aa61eddb0a0b25cedb7b73fea904943cad1fa2ad20926329eaa686df4d20a5eaeabdafc7b6b6c899dbc5e78ea19d87ff73379ae20f67423f987b3834246ba3989dbd1e614a5718aa72b79bdaae609c488011143285bddb0342cafc19c2e1c857d2103e5a01ea0b1e09d803628682238a7e57e9845398de23511b0c5668bde6cdaf374cb83790f8b5f1d42cab7bc133048d5aa22a2c08a50f6a341cd28055774236c46b59eb16512672ada4e0d864ceb9ef805b0a6db3a8fd19fb91492cc11ca549e83bb74e001ad7423ef0f3d3a5cc46dc6bdd87dbca03cb8988f34918b824a6bef9e25dbc2fbde1bddbbbc156b9b123994a8ef60464ab75753eeda740fa2519f3217c746053a1bea66360e275bb3e6e7c507160bff8a10691d0af1c5e6577a9f27aa8211ea37095f9f92debb423befc3fb15f718350daccba0c1267b3fda6b73c5e5b0826e3ac3f585b6d6f62365ea5b80f06ce90a3ff31af69b95b850ae88946f0f819d1e819c570100d76d22daa764b3bbb3fca3ff334a4a0f6e60b05d51621230e85194b11ddf10b494bb2baf494e3e8cbccb9da1e8f621c01dcd4625f4e78e5b08d8191aacb316a06e51c43d6677bce4905f006ea88bc3d4bc9137893d2276955f7050e81ec418dfc4b6a16f8b5d79e041603ab16aaa75ce3a81fc67709e302b9881cbb235bf3f80004e654f586952a5fb468c5a632046880a39dd74e841fbf68ed5ca91bc34f8b701f32a43ef2fd22fff35e5596f9515cb010c6a31fb2a81e3327edd83b3ba6a747a192f02756ff9c64484e1b1614a55dd666c354c8def0a6448f47e70cb3d951ce131d0c52d50d70e53f25073b444ab077934f7b98a88cfa94c624076f34c80dd4bf2c6279aeee5da27f65fa098a1c0d20b07e6a82e43462e4280339751de6ea4621a08521ea9028d1f73868ef1d2f192384d4f8b980971b2864e0d136921e1b0c18147337b917868c17ccad22a6c48bdb43114986b09eacdaa1929f5e1d49805f97d3698ad405a02b82be25e401b209f1eb1a3ff0a91c8f1ed29642dedd8f59ff5db51f50df5803d371a4f23e6e5f37907d4a0d1e178493c3f540d9430cfedb5f44682d89aeccb82b170ceb5d858599da2620ed7dee6426af134b68eaff779c0a1cf9c4cd0b7d5e32d75294fcd2bedfffb006764910ffbfe05073277b34a179c5d9c52504638c35456ea3b2e38ce5c429003f7c34fd0a105ff4e94ff320b5d8dd3b9dad28d706162385372cacceca627108a628c7a39a0df215a403af6224a045fb299b4ad9984048a9fd9159b75e4f654dba56ef5aa94ce751b5af0a500933c49623da225ec3162cd104ba725fde52b15597afa6a5810ad151ad204225930d7f39399d8239a0535925353fa84d399804793a2d71f597090b5fa9ba05961d414e1f83d86396eed54f96aacb9c66882909c2870e5381f98d9cf61286c4f55a47a749297cb4c8ac8a69ba8cea4b293ec884aaff9c50613e8c77df74af467d9d71d489dba0ec5dd7b6ad9aae0d793138f1d548b1dcf40117bdf1e639dd772c411e1fe05817644a1450e89d912e779c8274157773903b4735bd55716a7e2fdd2e1d3eadc0cbefb964bb4336ac1f7d2fdbe5590104e391da3eabf77400b6ec6b6b8bba47c574bebb9d1dca673a61e782e071b763d85b6a1dd14ebd3171b381c9ea6a1538053ef45be8cd14f69c74976e7583549a9a9f0369750a9cdbdbdb6100dadfd3365adda73fe6190f6f401b0c096633d941abdee25142f8ca95d2516c4bb879659b101400eaaceb7cbeabe9f5b9285f994ab5d4e7edc3a7477a8aa7578875af0c01dbf49a62269fe3d7feaec96d161cbe333d8ea04308eee6672fb471bde8fae676cec620b3b35464f77adac6cda4e1f0ea07e13abc0f797fc5c79313e0241ff3f0317f4c16132dde39c78dd943acadb0c797cc0016878016c54242e0e240100d54857a2c0867c197adcc2048d03d41ba11db53f1d2bb17da63a2188f070f5b6a1ae8e95fbfe4e6907d7d863062c1523213b2d4c223352fa72c63ea00cd240853e1593a1e3a39c6bd0655b911badfd502ea416ff427af127ef2550e6c8111f1fea807a8ab3acf30d4f6d391cc539e207ee6091caad4560e66f8bc329aad800d3c8dd48a2797d898f0e90ef8113e9a37520998b0eff84454c2ceef1db58c19a8dd6d471cd7831e01a3bc3c408cd9c6c122334b6dcf79318edd1943ef76a8394518a37a8bdec8d9e62e735a8e44b78eae25d190493585c0684838ee7a03203280675c7c9a32793d17d9ae1ef68936c27ff142d862ff5a3a74c47d1a850ff12ea3ad5a65424024fcb9850c96e3ef58f45a79a0e4c8c2970193541885dee3ec0f7bc5b14ecf20a35607ac714a57e9e1a018069d546dcaaf601874f880df7d8b9810dd3fdb4df901128d99ad80c9b6faddf3723d67a6b43b05a3665018f5d13503f074706465361436a4d799ad49f824cb098f13e74dae95992a5b991d5d2d6c9ed62892170adf77e3e05d747a2fe6d26340b841be4a6dd507b1220108106eb0a225ac48ecf59c8584ad1c8e44ace0bfc650a208e32749e78b88c1ccf3411466ce8bf06d9584288e25c4ab0edc926b3bf618f34a4494a0131b8974d1c24f7cd29226a7e582cc590aeb5d8f8dbd508f979fcac28accec14cd68c3a02d3be5a7eaaf9aeb96a2eb9739f5565008db5cd044a37afb82e3f5e27ea67fb89338ff15949e1e0335cbc85a8f8357d7e23dac70c1944dd9fe963a390acd736291981ffc32b3a7c1032553a06c4103fe137376d376686bbc05fb70a7e7f38afaaa82ebab8d0b8256a5310c17080a06cf0218a61fb0a653b8947d96d822503a4a38272db5518a49e2cc2b05569227d41225d3eda852a1eb4156bf312c865a1713d507a9fa0e191755b6c88b2eb97afb688d4815095e84f944e3d4ee5fe795aeb2e0029433a1a06ac2f68fa92f2180ae85bc33a38304c8bc008889d56f4bc2757323058d4a5d79635f0a9f419f1ce306145254fc3b7564181348be56900792f1c424a00d1a3614a110e892e80426cf54243b9105cc6e30445bb415fcc66c92a06efb059f3f5b3e835753fddf81dd938f034c1ee383615497cd67b4b027e616c1d43c1a1d95af95a57ce521aa740b206d59a7df8d9c9daf47bef6e14bc482e657495a19a2e115fa2055e99c3e8d65b3013d2e1a94ef04e533c26a5a9da71a2f1cace5a2983b45830a833a183376ff0e3004a27ee8a943e0c2cec81c37552d661f786e0fe9208ef5c722961abc106f1643bc4663fb1c2c9fb19ede11cee791423e9e469bbdaa4b7b5ffba2a11d3cd2405890601c9fd600cbb8a7a0debf4460c0bbc7af9d45ef73dd96f605649c1236505db677db1e6f5e293813d3d5bf7fede24e0145673aa88b951f75cc2ade31ff47c2fbd7982dff8518a8c5172015d3b5a7a51c1cc3297b6cbd387340ce65be156b44a9e3f021391eeb95c775323dd23b68985f9d6233513fb06f363909e734e16c30f9813a55c04c98f11032f1e7a4696bdbd98102776953159ab3c14bda2e39716fd2f22e1141fe28d8f1accc3768ead65dc14ba5276afc698b142e9119e02e85945b78a71a11c8d0ab42e0aba41223d77e90172c8aaf0a8dd75d07b659cefdd18778aa2ee8c3b2d757cd917d0e05822701446194b9fe895a04fbab0ce4d5758936a14dcc3953985fa30014469dc3e16d38acca2137f498a16a74078c0013cd3fbddaa1c8c1b4758a05039d5effead6770b81100aa1fdb9586342e3eee177bed011474632998db6b2a4f9581002a86c66b838551fa63069544e98d00e5733a67636d55d0703fa020257c02030e356f574dbf13861faeba7cd6de20522b574ce66a6d974b758aba33ad18e54a5065245f25185980750f289ba91248efde03cfd81d42eaa62b9cbead6dc58e61ad0261bf72302a14df7f238f91e40a98b566398022a3c42d2583f3945094160df1a1b8ca43265d40b69259be3d5d00430b7a3c664f893e67e3d2723fe83dfd3b69008c96890a35744255f295b318a5c23364d70f1b1379c2c54e6188327384c9914ed3661673d0e8593d5b4b123169966233548c03930da74191a9d81aeb494cfd32a115ae2a0bc4b9dc29ca8651396d313eaae3993c50929921715fce0c1f791ef40e7029f98b3b661a5602a7f58408125bc38576997145e99ee0b0c3629957918d96ad2b1114fd54482404c9951cf93cc723ee11cd3a52cbf08e72070d27e4e96aca224a974bb34f4655b610258a736c8d9f9b11e8ec7cc835b743278ae3a0dd8527a0e6a9499ae8320fb04945e450db9234c07f400cdebd90ffb9ec4d4cddc61d2894da22ef359b876fd08d7173b64fae1366451bdf7f286713bad63b1272d4654629aa7c4c6e75f84437e84e2190c4c303d904d3c7070d459f7fc7336613f98e6fa0c68c3cfa7bb41fad7ed3bda6f1aac28d96a643621eaec07867f4e20ad5d404bd9eb3467eb25b63cee6e20a76ef6582daca3efb7e6230cd8485bc0786d063f7da246d502943406fa78d550b601d8babc9319660c5abb0a170ab062aecac5445835cbd942f116130914dd2b24f3e3524c1163ed2fc2052a95685b94c2064de869d3c20b2230ce3704ad4bd3da7d7be42537c63a8409107c6fbdbaf1c7b98cfd1726fd71e1dbcf0170fcacc9d0a91a6ce9138a402a618311687caa06a4bc8fc144ccc69dbca8539519204e8850ac5cdf122e73530dce78a9067765c65f24d5a08af3e4a093283413409325214a2deecac642e909b43bf6aa348772f21fe99c36a0eaf5f8749d67a6eaa89cffdd8cddbee3117dea1b8145eaa8503f18841fb3237dc544498cad3206544db58db75ca853330182d54d031fb86bb259c0eea6184f1f24948215763cad7a431c6df7783aa5869ca25a19c4976d9adf07e0176302f147d344851863cf43daa2e61e40e2379bae3d4042847656298738a88d46952ddc8f6e7ee40d2093be454c367ef4c3b46ef1dd385b35cb239abaf22fc94f3b0875a311d055daf88efb73301f94dc08e6839f108677a8f906804844921a7fd1925561dca694e31dfd9b0387890e0cb3c5f22b0c006a0129941782998053d68e96026b733b00d4bd0c1219ee3b5dc6be9111f3ab629ab0d067fce6915517b5371d55ec5e663fcd2c4104ea0868fc4e921c482e1241132cd3e122d0f6fdf4fcad3ff8a68c6647e00d6eef21f09ad50797523275f3b768a32320fd88efaac8cbecf01e57cd9493464da73f2b7ba410b46d53313b3c7831c4499707490c231a39922f5add911ac4602f4e3d4f95cf6e325160586d5ea25b9800cfaef370aef3008406c8865610005b3a87dcc0b5d12b5b0c810198456834042f36a7043683c38b4d7195c4334c839674c8387408c988433302b02f6b2b7640ed05c506c8eb8bd8d354e37ffeedeb59c29d23b2161b579894777d46fa2ebba5d216f49c80bdfd58cd65123869c2373948be61aa0ff500ad00051f34b8d8c16413a3b86a0bd5aa9319058c9c795019afb79a8c4e23991f99c0087f21649e2e4d8d384a93f1892efaf26b5eb67dda1415f910c680273b0c433a555f9e427be47e68f2aae8b1328eb48e4296711c99ba720e5ccf27da0fa7d06ce044a6dda7d188fd4775e355239d203b38d4adc05f23d1f88ba2e6a51092c5c09ec107e062fc9dcb0136ca684ed15371be1e524fd24f74ca8240f0ea219a3a7119cec767492b77b6d7fd21adc8c69345ede3e4ef50f24d0f1d35d93e8302d084b3f4b78a2d1fb12d96b8ce0e50b899d72e5b25a872fee638c66a371806ece6259692eae0b947272b3654d78db7ba90d844c1fc8b216eea63dc2a1928cd56f12b645b68ebaee0976e2c58dd9f57fca02cff320a9c13d3e50a88e9e95b8620437fc0b9de42175d2c2afe4cb28ee887cd53a68ff926cbd3acd53b1a8b81fe9808d225068b8c169b192a8329457ef545c4a2b29b1c4cd3460bfe2ad553d19806586f0f00a7d8236290cce79220c65623503738b562b93be1fe64403a841787e9cb0fcf59da0245b00091efb01597bfe68cb3c56b39d763e3310f4030696a6c27987d4227ccc4e679f65f307e05f69c5f807b5551795ba9fd2ffcbcc7f40a7483b096322de169dc6754ab4a486df5c434c0be699cf66584d4e8e1475eea500ef3db1bc3e7ad86cf5f8a9ddc306fa68334145a12bf91dcbc3a6eba1ae0e55d44fa608c2164744293df300b10e7cb7711e2d3d1037a008459db5a5dd5d8dce3d90e00eb4bde28acb6dfb1b7b697109f823fad942dc56fa39a1349c1e87cc77e331c71d8827a3ad0b1a5fd4b7f2deed58156ea571870444d184a3bd055e23ed7affd275f30f7aa22ed3c7ae34258749151f4408b2675e3c9af1fe8e67ed21b8078c5c5962d82af6bb4f500f2f58e18d9d858cac09547356312a65399553781e5c1047c44133be27dee663f1e1c451501820df85e1d9b332d96960e51a252a0ea6e8e1ddc77c462cc1ad3345f910389eb0487e78d5018e03edef4af0858e0bbe7aa5e4d63c2a7f84d2485f9c63be794d476eef1fd28a41b16458648555c0fc50dd5728cd73b1c921fb422b58b35b22cd618cdacdb15734efef43e0bf8013b0cd0b04f6d1fe4d4f9fd0f7ca2ba0c57e6e48db67f7acc58c116cb8f1d2ee8d09b64efbfa42ee89b645c032d4d234f8a01a19ce4a4561217160f263ccd06097193fee5f4b7eafaf006ebf5fb14eed95aa28bbcf9e68145e4da37d0a805b55e7d0aaede6f6de32ecf448e28a79ff7fc90456229312c6576f4c50c0b918c7569ac5831243383f43ff4a82173ed05eeb1cc4e5f58c021b8e0fb02855e64e7ccb53d2162b810e8fa142cd09cdbaed827124ce848d673c58ccd2e5f3c680eea36db80c21415fcfb9351f403705e74dee113294f182aae83226b335a1c422b81538eaa6b0899b2c38759f0889b785401df54c4a732266d9a7a2909e59ceebd3b37255b4b576b80ea385ff01815f9ca5a8a7adc74b44f3683708302ace09a0864c7a05a17b161cf1eae7d34e69e1b500396cac0fb6250103f4e4313447479962faba79a9f8d1e9eed2da4191c1b8f4f4fe0cd64fb9454e0152ffbfa0ba3943aa938c27d6adbce6e36f7cf4ea5813b2570bb56910d76f4387eba8646d98e16463ae3a2268e286a0e4901a457e41d8b4d64dac1b9ca20b0f25b90c53433279049ed2b8ac671696246a91f343d4b722036e66bfd80502744737216a43480d860d44307186da01881d134d5d5ce5533d964ad1520172c5327aaca8c0d7b3daf8740f732b8de6237390a72a59ebabdf828cc5ab654a114d5ae93f36c48aba1cd8cae16a57b9f85c1d7df461fb5b35df34821e05acf6499351d30e18266c740e16c1b5b5a2ffe1af720b525ebcf6f405c3b4b6a3429744d1481cff1771509bcab2c6438c16d5bde45b7a8ad15d32fbe5c36a4d30a4aeda9da35246365ce4fa5ffc5d274cc7fcf2bb6c46b5469173ec7a94e6ff26b7922952c48f39b8df55e50fc3ae71b5d88093e860fb445a8a7dd76266d944b98a2b625f7c32811a2ce6a78bbf66dcbc98de35c0006c4f0e811bea8ad73149bcff0b6d9dded8d2d390533c272e672ea5d5e7675c442fc9999965d9c2f9b3dd6b833a3dcf7ffc2cc940ff9c28febbc4d338383504f9b97e02e7b5729cc995eccc8551ea804b333565fadeb82c3e29503f27e2833aa4361e3713162d4148b3921731cd17ac75a276113a90ec819fc94060e9b3534ba652482c0e9fd862ec0036a22a214e97dc1180fb9ac9b379b604e71e5ce03fccf884d4cb43692630ae6a4e646437b7be50d21a977a2b4af3e871d87a53cd23c25df60c878ff62186ccacf88a581e5f62577c83b4e4c934eb963d3a924d2264c9adde41c03d261b9b1632c5fe971be7757ef6d5b352eaa6e0acfc0759149503912b44b0f917c15f94d3fd469ae0746616af556b78efda313bc7ccdcc43cdfa59070afa00a50e582e654258e96e632e25d94471e5a77a04aa65734622f12e60b9c61d04d56e62568a6410e937c4326d1cd8e22570a118cd56bc6a5b766c2eed54db61faedab4a8fcdf07e940bc6c565eb9e32748d5519cf6fbf01c2d5796d24bd61953d9740b542813465a5370f44518c0f09dc76b1abdf366605e0713ebde28d8f58bbe9654a463542dea5c4e2a16a05b224b6e635716ee429fefdf0d044ddfa2971eccba9b9b3df0a860bac73d948a63d98943863eb0d62c621027b35466a64ece9db1329b98615d749696ef57ec554f002aa34dd54420fef1f04f4f0bdc9f9cd6a522c11af141c2bde948b9a75f4c257f7c6f1d6c2ef21aa7ecec857dadcde237601ed486b0b1d63a0685ebd1f72d79330344e7d3b56a8a2d3f202d51eb5b2a0ea6082d4ed514a9b730a88e3c0dfc62795fb24ed5dbc3819516eb29e6b938b42cc6872b3227e1338ace6229de93957ef66d68152078a87cba05be72d662df38ab6e9a014d43316b5f304b1d5010a351c14bfd358f106aa58f9bf71ac2d24fd0329ff575e0ad170af274d8d8f17b02014e23b5c2cbd82c0d90911be4d377dd919bd18d62518f8cfff6b2da1fc6bd7802184f6d74e99c2d72aea52c3316c93f8ffc1774d5ed63b074d0706168bedb17e5efa69e9737ba993fb71821ad8b988910944402b8723487c1547c94b146ed4b50841c937f504a2cb5767b4f0a978b4708a8a7ba524b0ce05da00c3673f103836b62f359876c1b8cdfdafb582439cc476a0e90f82d477751db527dbe7dc1ed2d7854940cbbbe8a2d7adb311f386d8b0384a18078eda09c8a3388913bf701d974577cc7a81e675eb806087b1c55978be076650a1677add5ed29b3575621eac7a1c3d60cec60a269983905643522a6b433b16ef24a9b3a9e48a6dd92747b7fd384a8e24903c811b4b9746e10851a4a6bf0275d31125bdde35900ccf99783d3919955d861be55ca3ea7ca530d81942ccfd265b43b360aef45dfd4de6cc35535830a35687df6e914bc6dbb0b085c2fe8b742b554bb091203b66d853e06d3d16ad2e2ce1d1b1413a709c76866bcb899731b6d69cb004d91dae68c3a1bb80418cafb205072751e1dfbb994a9e598284f32b9aeae184e80bd59543be68dc7bf025c62c80719d9488355d5468ac422d33b99bc6e5d99e22ef086104410c148fb4a151083dfa72b81d504efcc3cf5939972007df983c0a710473aecbf41224338e63d02548bac00a4c7f888a8ea566baa6a769f6f1eb7ba1e54aa2f3168e50c9e9e3a258506303bd619d1024a408f24a1ea27a67b5aec744a488ebf939787efa92ed68ac4f28fc5a5e65bda3332034024e560a6120024e1b1316385e475e073b5be3029360c9cea3964deca25c3c12b540d8522ba229824ca7c606363762bfcee2c881398b4d5a9f6f980d2f04a22efccaae9fa74dc358cc2d0d6ef378d60f1c0cd7ec8fb2a6dc7c19538072a0842fed6d90383ac2870136938773e25d91ebce43676d0faefb0f25d55c692d4a4d7a4b37274bfe0b3087f7b11ac6ec024ba2784bd45709afdda1e4521ba4df9c295d85dedd211b6f4e55ae6a53991de593bcdc410519713cb549f1b1a3108671cd297a202a48d17549b94bb3fa02b51ca21fff321f9984232cb2ae6c3a0f3611ff2645d8e6e6aad47e8cacb2a73424a1438bc0fe10591482c147d39339ba6df8b1731b777c8a6caa32aaa2c7c8792e1cecd597ec3220c18312695e5663ac8dc733d2a57940e5f13212467196607254c2e0341c7d0c7d789882e0a999135c1fa9f1f597dc17101057acf2c716732e1c7ef67f68d9c69edda4e67dcbea77f0d991918f6e960c5207262aa6222015ab67950c41aa2a791478b3ad9f0405bcd03f7d57143673fcd7f4ffdaf0653a1c3e157de8aa151a03165254e30220c5eeaffd41c97e5371b81abbee462b93664d838d57567d0cff5988dbde46f0125b250424e3f3e8a7743fc000f569e071d18aceb9cf07208570870890544ae035af1e4292cbd7aa4f8fa5126c5f9cb95332f5dbb0d8d2fa04ea63742a389d2168a9249232b135fbf915d551dd4fcda49a4bac3496fc47f67394109812396e549c806d7cb46330b76594a2b336a0e3d8cf1e239c2f2c7ccd1bcb31d6618cc105cea82a35a108c88b1dffcceae12aa1a29636c8190c4a92ab40a8c4adb854aa26c90c18f506ab03cc4da731cb837a31e3785e2df14721d5be8c4ee0bd1036d5df8eebc2e14c8d3027774e6b798c797130009e02ed6b4a2970a7aa7aec742374706ae3996ad2b917465dd2e05fd75e98493d1c0b3f3aef7457c27e2b1a7eb2f0619fc134467d8abb86c6c47b7bf6795718d6bd324400900532c1d6881ad99556e58334a3d5fe62af0caa1d612de601007675bf79f753a2774ef75b03fdc5be5f66f69370ff64f432f47c5ad04bf3f4ee212c1b9902580431754aaa6690f83379291150e73c11a90e11e8412d64e2508bc202f067dde6f00059563a4aa9218e54a0355d5904c8b666ae497097efcf59abe46b102b59e92b58a25dbbd21a761463fe657722918fc9aecbee53445e63bc2ef4681ade234683224b58a1cf755ee7e1dbe45cc3dcfcf2ba444431da1b2cbf5118ef9255e86d8ab01fa474312e2e69233f20739e7b15622ffd00789091d3e9f1412854ba0fce6d8b7f8f7e17aae5a6fb0d92cae3b74998b823428568fa73a57929a6ea043c8663237766efa481a3828ad535d7fe75343500629ab03556f1981b227088f2b8057d2e6af39f5859191303677365c9992f7599aa7bd7e2080e2685c17ee2a9031a81bf13ba6a0e1abd279e961a41b49a4d94c007c6d0a76fe76f09bd2b6991ea70f8a516c93be143e2e44a98c00e2021ee449ce37f2f097b61d68bbe9da7b178a9a24edfa8c7e3f1bc8af95b9d58d2096a819c6b94a0b7c851b57c8fb014ac2bf8f0500096b406cc0b5db8ebab82b3c16c4526b36a2d0e15c215a37b24643247bdd2b9c2b151288f31469c1e8e77cca4e2d6eb326381b78017c4ff6f8eb2a761da222f64575e8cebd61e34c0b977538e3ebcd9e4f6580da0969fefc675d22135bf07d9c73168fff3387f40099950204c1ee650548a81a705eca36e68fb812874a38268774e2b87b347bca54b9c40bdb1255cab14811a89b153722db6fad667dc614406b68a24a2309241a077a41fd3cb4590102a4f2552c38863398d05cc7920a1361ce67d6177f70467f8ebd9b2d8029d771578881ba20ca66350cff1545a61b725c8fd03ca495b9b4fc9586924202460949b7d88879e5bf23f8c990efd7637e3a59821e976bcd74c96e85fe6624d45e3251b920d0ab18c8c65002bb8046f954578f12321308a3a4146b11a8a08606c65df9b7c70b83046ca5391fe4868bd89fa23711c7c1b4921b14cb82a07c4a9433fa8e4d15ada2ae2f9700d6e7ac9c17d1c66969a273e7143c0d2bf88563c201638c63ac311832f1b23adaa2886faf44c3cbad9dec156c2ca4399456fe9f10da853d2a70e4a67421be34be3a5cf40a1f1156ec7e99c44873187a4319e156c099261820fe4df3f1ea6a5e778c65d014027a8bc6e95a8a7b9effb4629db97e900b32d452ccb34130693ae2040c7ad3f92c0e0c24bf44dfcac609b2cd90bb5ef6c7e38980ebd427c1ec6c2b92b6f15c78fb0b0e8e7516354fd21ae5be504d97019213138d88be914868f27e087439f9f8e193fd7974a0dfb8ab2be5b36e72482cfa3d6acfc265af817ce8fdc54112b5155a25710f7b7b22cffbba488bbdea3520c2f5699811cc447d5690465641003e63763e39a3814f92c302a70e2de1c3a281bfe38243f4305c2ba0ba93b0b0a52548b7d03005bbda041e4fa27b2825409c4a3e118a5f084c30a8522431d926bd9fd13d60f0594e9931ffc1fa5681eba0328828fc0e660a0caa7077f2525e40958845530a218ca997994fcaeca06365a1a908895619ae3285204a83c16d55425c44c1f52270c75821de1ddbabd541c5dcde9bcfb4a31a9fb6787beddfb66923e109400da2b90d2909c2208651c65cae4cf9fff35850c0710fa3c634842016497387fea1cef76991edb11c789c161dcf114f2c85bdbda81b042b97197783876916d876cd2ff61a218741f798591c39ad5b694c8ad6620d10e67c0ce1a0c86255c0eb02c77c9c2939785da3414bd19780c22b685c88217d5801fee2744cd52c080d600deeab3cd8115211108d68dadf3dde395cb4be44b2181fc9a6436b35c542c1e0de48e30dad06f516e103e88169c5150224cdf668d8b359db890017e230488a5c06b9964864058893494fec90f9a68168cb039fad7319bcb3f2229177b481fd867745d819f9053323eac3d0cfab7a4f9791aa59fd2d7628bf53ad4680ee713b0eb0edfe49212ca42179278eda8af245273ad3c0741fb2a35d0e599289b3ceb27a703cf70b2f2d1df1405c333a565ec754d4b7742246dfc45d418b03d7a62a45c6d1885ffe2cb97d847c28af61d967844be2be34a3e018c3d65467b0485bb1f28b5ace46fa0b09d5ca40a0941b6ded7947a343c62171d009a7beaf3c66cd0a9118155c344ddec9be5ffb44a6689e2b9afe10e5e0daeea9c3ec1e527c54b2454e94c9f2a7f4435bce370fbc6d7eeef5bd96189e688e18f5fab21c17f49b8d92492741a0332e10d0a811001825922589de899a767d7145f1ac5aeb59b352d2aefd989a22ad6e9439ad25ddc364b7f3c177110d788d0070c03a3a5e05cd275498408b8fe7123bc2165e3b54c3a9e83cef298b98f6820d82b529fdfbfd8099ed82f46b56bbc07801d8f0c11d2a0a0460dcdbb706ec28d8c5f92e396605cac29246bd43e5da26cca2461bf9ccc5bbb9d3dd22349f9f6e2f1384daa3c0d3e9e028153952391aacac57155d2647a4eb78d9bd5cb9bfaa4a7b5400c21824c2d4c5aff745edb98799806ec80021d8c2be9b6e286283fead10593f0e980d076414bae733abf7328ed92873fb750fcf8565c5eb1ea5de5d5f8c45331d7a87195d38f41222bf815ef48c7fc8fdeeb8de4f37afd686f257d3f14bbda7ebbbd2dc83bc3bf220ab25cab591a07ed43dd140025912aea03bd2fd284bdd5221684b06ad6a72f6e7417b83f8375079e5054ff1adc126ef40253151705648ab2e6c4de63a0c80b10ec73aaf9f796e8009b74f8802d098fc86607a62b68917258d7f12e68d9780ae517dd9a255f341eb754b71d9854c7f360f09c9dbde5bd83106d386149898beca2fdd925b1d80713a14850f3e00449d095720feb484d367cf8a3213e443157aad8dbf13187563cdcce9b6c85af8af483600f60aa0facf1c5dc08fdac5725169ca30f46350eb74e8aa06b4c9d4b2087ef8828df80c5025e545f49f40f04fe6a3319ec11928c5f3c4722da7fef68314676426598e61d9f9a1c0afc0bd7ceb7b3c8948f088053ebcba6b49b3ac8af5b3dff66cc87f36dc307298f860560624a90018649e27bb85615df06d563d35e6c4c048a2a47a25430e050510709e6a196e1925cf40d383ee289df0383abb925d3a27ef946d3186b0bdf639a1e001faa825d1b003008372cf8e2721d77bc2e7e51be998bd139e3f8bfda9be9852f1533b409215bcabde5de52ca24652a0a060af60973ce49929d265beebf176416102061e0141946c90fa14d4c466e7cf039c43cc92f9ee471ab6dba526d9af221a7b81497da6a984b5194d394df40cb78224b2ddc6a388735d92243aaf8b3f8dff0476bff7753c90715345ae6a7d051a36d04563759d0b5e7ce0e99dd54845aa11645798cc65a9b3e5eb1e2739173dad9a63e67aea35dafc82b3e1710fc5c3a6be78651464f7a12e9492ff2a497428c467ef4a3f00893098c3ee443c21298781884901f3d8a1fbd146204054845601e44c8cfcf21e0cf94890d899827d81ee6d59ebb9be991d6e040f35975b9683e9bb5ea0a5273d51a73d56a351845ddab75a9f42f8a6fd03939a19c108cab1b57b79cf37a5154ada935af182b08a613f241b3a9776d8eb5b6f5663b7ebabe936bcd4eadc9396867678727eb0bcb570b7accaa7b814aa4db841e3d26e61137e81e3dd65aadc787abdb16abb55aa3281a2448e8150bbd64b39eca63bdd04c8845635b90572cc8b60589c5621bd73cf2841e3df654145291b441d3a7b29a6b9681640b2bdf41a2484fbbd106a2353751524899d854355ced11c986e9b905a9e23017451d20549bba20d1b4f72427e86336007d00320784e262312e4639a0d45e718459f75945612a202a93a7fa84e4823c5d2ec871387dc685fa843cb9cc7dbe94c75656f0484d8ff9456114866bc779064209ee6b0fd703bbbfa54438f4bd28f481b8e37a36e57c7064b89e39633e3832dbc3e0c86c325b0883335373736b38afd9d3831c8885a6a40a7d2ec6c5b81d1d809ce322fd007c2ef9e5221577cec9b0957ca327b7bd548c8d1a2eae418f5e0abfc8419cf27dd1d7653989a6f3939cd06edaf32f8c74639b8a5b7e335aecb922075ac6da73362b8d8114464d249ae69ee484bed8ebeecb1632d0723b8b385c2f8c9d1c685335e831ab54af57ce84d5b9d9bad97965645e2a95cc2cd6859839575dbd5cfde15466e5ce059b89f303e707ce0f9c9a8eca90abb5b7371f462f67f825bf7cba8cbe137a9c31a7301741d31eb9821e678c73970bc80bf85cee5b9b5559b5cdcd0bbafbedb7e99a41aa4b6eb9e4a2138149798447ab64f2caeb6e48e5751745cc40f7b38aa2ee8ddf1b55ec35655f8a092eb4421f0e85fec3f20445fc5c5078a189a5df8526f6ba6e088363736f50a0e0c20ac7c63e0c8e8dc5b1b1210c8eebb2286ac662ac4d9f44069dc371caecf963d327ed680f2be8ac550c0509c6801c1bb9a13098aba8897ee966956c71598c55675285521869a40bb265fbda536badd4965ff9c58d5033c84df447e12cbd0dc70be3c58b1850170201c5c209c4053d56a751d4a84651211535c9e42934052972419e422f1765cbf6f43d45caabdca2a6979f720be414e429cb48917e0ead326bd3ac922a9366d6b8986f6c268d4baad0af3d37d3c6c6ab31584cdf92fe92f8626640de366e40967e5ae1803a2ff51a0af9fb4784b7c7126c0fa910b4694ec904f969aa54e0dd34f2c88d5ea552a91508d180cc841a7a360f6f91a3478f8d38bf4ee28fa362668c07e14363b8c9df0b3bfda20d5956527c6ec31b232a78a9221cd0cd04b0e98fa441bfd080cd3a5a37eb66ddcc010ec023372368d063b7dab64aeca21016811c81cf25d4e98868a7e3a798ac5be9d5a6399baea889bec8873c09d539419a5ed286a246617ed1298b8dc4740c1765b8486dc7f2530036fdce47f7c34f21a093e81ebdfc5c8230fa18aecfa0fd6514b63f10dd7e346954d629a3afe2047abc419d8c9fc6d08ed2a6b1a08eea50588a3ce891c2708d1a9fcbf6353e17fb80cf65fb077c2e567e2334bdd50764f09b619adf8819b4fdf09b117e439874b8387fce300420802390476ef43825f0fec54680e8299b4915fa7802263f8dec208c4621ca4819f4eb5eada7386343749bfab848bfc54a8ff9456136328912da25e0537309641296f61f3de94326906977e63f5a60899afc49334a342113a040cd47269bc56a3e14c814f85ce6941886f473f4f3ce4d5aeb30d77171ce189dae99cbc446c8a4cd17099c336aa27df30e314aa9fb5420063e53c26b3c182183f6d86b722c6a298c02b1515c0e9723dbf443a28b2e42e21dd982b63fce1897b3e9b798a19763712c960d3916b64096a49115242a24a4b1306f418261d3c7178049951d3dbb4d7150d3fcdaf5018a39f83223334d1abf245566b76d8aa2f42d699bb22999651c35428dc20c444df443b40f5228a96966d31f1983c603c82362d0b5d08aab1b57374e044209cd855414456d629e65359b7a6fbbcb2ad92dfcd8f4b77def8bf950c4cf8500d4c6e90d0108b08518eb72b95c2e97eb2555e496392abfa8c96327a31f49aa4d698de6d76c168bc16030169695150a8b65d8a6f9b56179b6a987149663a0c71973d4e8e96314df0cd3b62185815487c6a889fe4818f44861a598130f82fe0a66259f3d96b6205b267ddc92e567dad1f759b67f7d9c81dc2209dd02067aa4300aab9c5fd0c459be19a6bd19e0f3709cb1048433d684b6a1cf607cd763a0d824211420c365089190d18c174d8468010ce82e8d0614c16d0eb0b5c6032010011b540213a0004c05dc0217b88181295f8c7edb41188547984088902a0365a00c647310a573db6648356d4855ab8556f66e5cddb6cb5531167354c8d30fa9fc3466a0d90835e221d5484f3f24235b6648858224321a4d0a03e7cb4df4735006f2d3e8b1aca2b01d3f8df945bfd436e3f979f909e7f8a9c274623a310a9bc9f84995577926b3b0d7b966ea2ba7e6ccd86db3ae19d78ccb472816f279061996b026d0ec636bf214d264df27907922512b52cbf4c8cd548d9a6023078f18176d784438532efac43e277efb3053a562d104d1e38a5fceefe5eecbbd79e05c4d550b21e87165db5da3850e60ecc7c026f8e5ee7ab8c8dfcf17ed9c2b60a0835c82b6cbddedaee6ca4a0cfb8205860c6bbb1814357afb303770c896fcf6853c212107204f47acc896efad8aa2ec23214fa1b77fc4ca6a243558a2262a8ea4cd755efebeeffbbeff3c94341c77b0d45a7d03577dee5390e3feefb7edaf08c70eda9db92fc4d1cded94b5eb42f36683dbcea0942ad335022535cd70e451dd41774d01ccdfa6cd04bd1f91c04fddc5c3a14d4819b04461d55a65f52953a94f1a169d35620d856e6ea7d7af61e30445acb826a3d3fa615369b57ee8cb9ef735a7fe84c56aadaa3aaeacd4afb5aae64cadf579f43822a6e2c8a18520bbfe0e1a2f7eeabe7e0d1b30dde5706015c54f734f2dac706094521c180e0c2756aa5904d3bbeae993773097d0a383250a0378cc6dda255bbea6b19366d658ebd6ad6bd73447070888540a02ebee8fee566a03e64677b79b1b47e59f41f0eb7b0c0be9d027c996a945a10f4fd9cc16ebd06456ad1a1b30cc6caf9b323d3a550e1d4c804166d337c27a575c826cb376dbbe40b34b90b95beb32d98e8ba9eb190f58a2a8542a954aa5aa8e927abe4659993b3b2b3b9bbed3e6ceca9ca711655b9121bad0359e9f991a1b999c95cb27483563abdbea7766ab733d543a72eca85eabb9767cd8f8fc0405b171b96a357312a9898e2b75658666d6bc726066326adce8369dcacdd7d6ddd0f8692c6d5dfdd143a523c70e2378d8ea28e244f139a7d7286a6565afccda5603d2242d6b6e85013548a4bf9f0ba96416c225e04cc97d496f49a41791483fbe0ad28357aa905e05585d2495fcecb1498f02850a14289e44228d2620898072a34001ce17312181f34b486f62f21e8944fa923701e5c6380695222492c8c7f0292277268128de04247d09288282cbf762d0e44bf0eba028f0edc3ccd4896cf1e1ede3a0a87ffb189794e42f24d2a390d2c99f944a3e9c94944a253fdf049b987c8a1427a51429dec4a464f231fca464527a1250ee142529c0f924252fbf52c9a700e797a4f812d8a54891e24b4aa0dc29c052a9446262f2a592090909c97fe0dc252520c99b80727fa0fb9ea992ef4ba0f724e0485841b94920f7226048a847ba7538ce166cfb2250dca39ccd90334c3c13134f31059949d273cac8cc48999518352c8cb04a2aad9d3232aa998ac9b99391f151335341b5d63ae3a2cc5cc9e42e77f7888cbdcf39bf7f2e197bb7aeb8687d747d7f8bc49d9e0bb0c4227cbf0bc792c7332c795c73fd5c464eb2a0af0758f6f8b23930bfe42f38beec928fe172c74dd50be7ac8aeeee3c357ecd221695f7538439bcbfd910bf178a8c2e707847c0ba83654e910cf4284a7ab5a6545689a854571d38f41762fe05d894555a0e1911b5ca64321c19ce8e17bce0051f22bc67d7f9a3afc9a5fd687de7953dd76776f0b8767add9cd6eb4e401471e4853fce9f9baa7a99fffa3e03825fdf08d932650f0a0880f796a3fee70e67fd077bb8a9d6fccc181e562d68d27a72764db10d317737a74d905d7fbe528027b31b59ef085a8fd1c80b2c1c1abef7ce79617068b48db99c255dd9531c7fc5142f36821eebaed945931f09c73bb2221c4b2ac2f17d0847f144dca64df28651a728a97f4dc2f18e84a32e5194cd18a4581fc5ec710a90e44bc0d27fa0f7730c2848f5af162995706adce76f46b7a7ee3e86e30e741a17ebdbdcafb78a5c1b6cf2bd7c4578f681661536ff96412e87296658025ea9723f055877b06c29b6edefbd1fc35380a309b637aa3f23efd69e2980fb1e28aa3517efad6f9802b81e28f7a57274c1eae3e21d8d3e8677737b0fbcdf81a31cc21d387d4551e0d75ddf6948be9628eabb6e82739f70db876dab98a0dd2bea7eba412e4fd077f8bd80724f9227c99fc3d27c92097ef6426b47fce58d26d8f94340b9edfc10ebae29001bb2b1df7dce71a809c71e7bd2900cfacbc5ec851fe07648c8cfcf25e44532c4b7fcbef7ed347eea3ad14b14e04837291c7d895d5f041cf50e0147a0065333e4ce1f023fd0249b5cd3f63a0fe470d7bdcbfd0ebcdb03e5beff313c081f9a8045882270e1b8853776e2028043adb6b333f284963200ae982b8649624862566e62748944a202921892148cdc8c3c4172517a5844e282911b4d3720202020a0fad664fa6455ebd3e74613b3b909927ad55be39a39d8ab73067b96da3fb13f6a6c5c41eab66da82f36657367fb0992266b847112ab89412b2f68cb054b8ef11377c40e1d46f0e86103a6c64d0c18325e6870391c9793eb6f5b13403939393a5245eeea62b1582c56176a2c16ab852333c3caf8e32156e87cdfea46450f60a1a6f92cd492f6c8f2b990b0407f437c33ee10b3468c0a4c2ab897fb668052653e2855485650bf1924ef9b3192e2fc9114e58f486cff19da432df23e0868dd79ce53da50fd665ca972e5f4ae17fac0dcc33621c2db1f987ba8f36d281f093b6d6abe26c2717edc3d61dc56371d8e5ba9304d78bdf01327cb7e92cd192dc5337ba60f10cb59abf9e329ecd92d077bd6d660cfd61b3872d429dbf56fe8eb5f55ddf9685f4df7e8af9f650bab562d9a3f8efa823c25e3a113929bead75d4fde456a65868505a356f1850c68d78f41c34f92092fa876fd69e3aad307abf58386d2999a2a6f6584cc67c3d8b881234775fdd4fbb9c882b87e5c3fae1f5790b74252c0b1c1b161830c2660ab94e148024e45dedf0e6d6caf9faa8a8de717f25965e542f3c645fba2ede0741d713f9724688904ed5f0ff97862e9b386ee8f24a7f5a55b692b47392e44ff82a2cdcda4758144bd507900574d01d8692355ec97c4408a2f90220ba46869204df66d8e1f6c6b73b0ad5db1c2c3b154a2e1a875b5339cb53a686bb3e52e14b2934387c85b3b4e1b548ab7f8a5a3481e45024a3f7a11d008176d8ab0879bec8784e34a29946e1a85e36c8923e18b3d4a1e14e15c6d4b124a377da19b70c873431e2edacf01eac02759d0343d2eea25f410f4acd19adbd56ab55aad56d77dd82d34e15aeec3de3b5f36143585b8397418c1e388144db5a347cc2ae36dc391f1666f64bcd58cb70c9ea05ccf7ef7391c651db68364c4c893de7e0f12fb47949ee42dccb27810f749c0171ec4fd1170c583b84f025d7810f7678ca3486f57333e508024fbc35afb76e5950383e9c85cab9c9b6d7fc6766c7697bd41e3a5860d3fe16d4f74c54fdf673b7ba6cfacd5fc098a9ff2db175d7c1973b0cb9970360cab1bc22c01767fe3eee56096005b026c093117790a17dcec00ec8f1d7c80a6a6f99a9ae4cb0f020fb8d434ff52937c17d1eab5d62a7d088d24d8f4e79e0276ba7d89a226cbb2ac8f6d7e2eb654da36d14f25ea02469057ad66ba668ed7cd785da5325e6878ddc67d4f5fd6649bec1205a9538b4239c7515967ee7c995592b5a0172b1fad77d1daf4599aa8e1cda347e83082470f6a03c6515508c94413db0b0dafc309b2e1ccad091b9c2076db2c4e109c2038393e9909188fdcc8c057c07dade148822daffc0e84a1a6ed0c7a9430f6f6f3e5bfdcab75a9f42f8a3062c8a0affaa276af224ddbd70a76e073a88ed32c7bdb6ef8e2e2465134642331cd85fea384a5aa1946ae1a1aada6c655535373e372b95c1308257050948d4961aa8d3969a5384efe6694ed05cb14ad3f0e47e11f4fe68c0e6f47e641b3e74479aa2933a78b6e9a5266639ee6cb8061b3e7c7a0f128f36fcc17575eb0ccad5549dabd0b32ae96535a2b75b55a34d3a69391286348ba571b1b9b4de205ed3134e891c2624ef202ed1ff2a4b77b1496408fb1acc044ea73ce2ee8534c24515f51830f6d25f902894c4bf7112fe8dc611a1b83bde6ebf5a2a1a179d9174ecc2d0d8da521797696b240d8fad899f5f1d9829dd9999d3dc69fcbf67ddff77ddff77df68b51d49cdffc6613f4f8f17cb5afe7fb5a3c9bfee7fa5c9feb6b7dad2fab6c685a5fabd552a954b7d56a612188d3565471aadba9482414d14d7f37d746db6633b6c55c2e17b7c5624ed0e38cfdf8b8aa8bbb2e919e907cb32358d0a2acc34f26ad632337da35890d5e234de80c7391924eee989dd033d0198b887c2edd732452033e30f7b047a0b77ed1a7d01ffaeff1dbef677eeff3fbcd1a24cd28ed6ddc31739182333653e4017c2f974e5044175174ab4d65541254b1f3c69e27f86d4cb10a1a1a9a555dd55575ae3eeefc310db6a1b011bb367dbcd9add2cd372ee43f41d116f7fc66b458f105df1eaf286ac6285e711d5e552aa33e80417b5ecc8bbd288e1c412e8a1e63438ef540081bead881023de21891c638aa07356119d962adca8b5194e779b1910dbf700dd3486678865f2ffcb27846515467f6033dce174551989b2885e197dd429fd933334431dbf743199c2e00e70ba4cafd8d15dbf7b5cdbd5a6b9b949a6c09f7fd141f8af2bf9f723f2588454ab6ccbf8f45be9789223b25c9d5a6c85b09c6397bdf8cfb2c8ea047dcda30ddca3904621a173f5d73ffde1fb5117208b451c3c5fb62121ad3fc90315c2355ee639fd269885b52e5fe6c8154b956b547dcd2e1e2a5e10d308754b95f411c9fcbf44210cc987b4e9554b93f43650761f43a4aa5d14da975373ac5274ce9b9ff9d41fb8f29b57d3f876cf1fefe0d1cd47657aaa0dc90030348753e8029608945413482b40c39cba12123879da51ee83df70e3ff95740a264c0c8162076fd1eb2092d73ceb0ebcf90a89b9a2a189224b730bf22ecc6dd6edb3697e162b53db9cb81f690c60c5f3e3250148a34d5af234a1d514472d0a39c55d2ae2e735a28418f72067303470e23786ca918d516ce907b65e29b06c11692aef5e9f4806b6858c34b43e05e3aa50dd10f819cf70e81ab0de100cfd978b794d20a739460c05ba5a0fcc06fbd69784265334ea460b6250f1ef678378c0e3cb5927f0b463326d4d0843ba5540d4c54353cc1aa01c8a606236cd2467370391f35734ebf570d2a16a5b4fea821c65abbd17c388ebb18ebec745de7e5ec827d19639cbfefc7cdf78542212756be25134eb49c68818b32f40226d2c09386a034fcd0cdbe4cb9bb76ffff79adb4a32bcc91166e76f1daa841e3055aa79cc403ed3490831ea5ceccf64983edef4fa3860d1ac8161b2e4a9dd729e9e898f840878b82b7799cc771dde675c1dbeee083bdbb6dde7d1c1d28eb115bcf814b9178a07127311e7dcbc7937b7b6df56c774b9286afc034cb0a907e0103cf41a3487145bb08b3ed4fec1f03e8e67ea5ebfe5be93eafe0957b635b29c5db1db16d585fb8e8bf72023df3dcd200d24c5124eaba89ae185db8987600b0476ef448fa4680642d5b58f0408fa269676596e1f502e203888f6aad8b5e06d6c637e34e622c6fecdce6d6da5b2403cda908e26e6c365a0352fa63a32d9a8f8f9cdcfcd1993e40e440d3786adde5bcee72dde5683af4242734104a686ed268341f9e1e1f9f5936eb1ea5943ab9abaa767dfaaa2432e8ef43e15867767dd28eff64ecd5cfa59b349e31c86c3a3a3a32fc9743390c76e3d91f58bb52aad6d6ec6a4b4e538d57aad8dff6f639145532d3c6c50a73a9b079430a839e3fa6aabeadb6ce619b7313c955d0dcbd55a56af1053d4e17a29d2a9213ba854c7d6f5114e9abeb255b2efebe1751765a2d69db36d2e8de6ddb7e4e8c84bf462436c6b01890c7788c4fdee6dc99afd70b0663b628aae43e5b3eba813c51cc922db3556b3d19cd9f2eff5ca68b929b64932967e87b11e53975f6394799ce1992eb9c22d9ceb9cd89227373e2bb8dd8418f4e2b9dd43e6ff8665c5b24b08ec0baaa32141908254a37481059abaaa61882445f3f7fe8ab73e6517d8dc4d033fa361f3e7cfc902a7207056d78dbdec55a72b156958a95abcc4ad62dac2b17ebcf946844a1d0470655a6a191d1d0d0d88852dd489e30473d36c975d651e894ca49376aa70422077abafbb4d95a6cede528a579dfb94c4663b2568f8e0305c5667ea23bd67fead3d4e4364c75e6e5368feee8ecfa74aa5674a68a2e13c8cdc42e9b01b991cd9d95057203e406c80d90aaba57eb52e95f54e55d698bb6269d8314b7ed45177ebad869d4244f2ddf6fbf396df91ce489fe408a351492705555408a3f90a71b52ac5f31f594229c56bb71db089cb1ed2f84912af5318bef45ccd394bd45a640e4ea22db4d256fdb1680cc6d5b06e59e33ac32d54755f155951b34f72adc755d288733355552a5fef59c2655e6571545398de6b48d6756d5bd95b55515454d21b61f52275f2c7ac6b0960ef581ddea63ceb889467f1585e4890844113f970fc8ca73687271687259ab39e32e069d15deb60d8401b202b202c2a2831eeb8fe4c9d320c5ea3417ab6cf16e477a1a3c023d0db6d6ee7b994e6b285b91b62b76faec8daa3aaab232d6eeae569f6d275055c9d49991d69aeaa2d547a5a937f575691cdd6276db3c5555392aa47e9df1d1fa993e284822a34ae3a8d1d7af3637358404661595a12b4c5d33ea4375266d519953daae5febb3d4971164d7af51df6935efa90da8db3a8dfe68d1d4d8542a934d9d099bb129dbf6c70728a8a66ee080b9d9f57504a1ae979fba1c3fc1ba0b24c6b33934b1319e2706568cdd360b242646a60514b455f9c9be48489529957e65e3ed7b719c6dce1bf715f131bcdeaa4b5fdaa068639b2d18da78cb16fc36e6b205f3c6bbaafc84777ddc650b7adb8408bcb7e7f6168e24a83112a4b0c422910c3415453925b79a32f4a5e426c7b16a6a282e384a374aefdde6153da53f28a594fb5e747e94d699daf479f4382226c70da04d233f4cc69e9da1cc590cc86cce3981cc80cc80d0be1552a42fba28d150cb3db1dc0f5c71e24f25c5f77b29e20b592e8bbe17b9ad75c1cdaf7f6b0ce76abdb576b57ab5e25a73ad5fada1faf6af54b12f5d9457aa98d8977688ce44ee5a378edb7edb66d00472717a8e42868932c4684c94e10ab276afd6a5d28ba2cf0a8bc45d370a0909bdb56197523acf6c4746f3dc3382a25cc7bfb779a280e2e762a7e71e63428cebcc397b3121c6b97b3926c43021860932adc972717357f6207b72f76a5d2afd8b1cf7d28563d92374d0a3ebdf9ef6c8d37c428a6c90a759cbbf59efb791cfcf278ca0a86d4a91fb0d686f348773c67d2f4570d7862b5c38672e725ed7dd172e6e2b2e8ac0b0b3aed5b2f787edbaae9b05e961446a87f6f23abf409edfee05d6425530c8f6f62e83bc6878f4b0e2e7e2398d2e84c1e979d97e743ddddfaeeb60707a7a36c6db3b15df0e6d3cd2d95b6dfb91bb82b86b34729d1a0deae1b31d6f1d61848cc968b3767d441be233efef7bab36ba4b7a8d1ef6f6d367d660a35c88ed7d66b37768fe12fdf63cb6d06f22f00405145d32c785848061e7e20079dcbd1c0e100e101352335b2d753b2d17e2de41d1c6a47cb7eef19604cd21417fdc6359bd9f317bbbdcb4992e509e78f438c24ffe33531e940303c68b172e5ca0a09c9c6c2f74af794fcbc56ddbb66dfbd1fd66e37b9923d949324d916d49deb8af88db712a807063c7fd71078c8b37a0f0dc73365cdcf0df1c383012dcdf273d418fdc731d889fc3f77a9e87ffc686b1e1e2f631fcc6083ec0ed610f409b07ddf76fea04280348d32665db7b1b3037b8cec68d0e687741d05e38fac6e195281712c76f388a80c51b46177bfb5113b8920c476bfc64c2895da8298b0b4f5ca4f153176e72af688b461b2183dc13afd51c88aa5ae880ae66fc347aabe63dfee38533a407f92946e5fd56eae1d9ddc6711e78b7cd6b5e732d06ad5d3c41194fc2a0eb56731ffaf23c082260b971215cc8763f06bd3d48e0dc242f343fd38e56b1e2edc3cce80d8214ed8f7e3c5e01faf02ac093c7a0c9cf208cdefefca1b5a8645fd4b927a9d697df0c2943a26066b1fbe2449a3e444b22d1b858429a2524d224bddc256f6f499433132ec484e342b8977b2b01358c8e1e72669f34f2a470867784934ae6933ea2dcf7433293e9dbf9763edac7635292c207abf583a685acd5a2a86fe7db69edfb21d96c27e62758ea27c64f63484726b4f2d3188aedfb058554a119bbd19a22c40afd84824429514c09b6856aa19e900f49e8c64fae90cd6bdf0fbd7cb05a3f6846662dbe9d8f36fb765ab460036b369b6dbce5184be83c73f7b4d99e05735eb9785d665ffd313c868c8d1f835995635cbcbef18f39b52ffefc313cabfc84537eca32abea4e6974aba18e27eeb34654d09f8ca3f08d9b3e156e51d3fd5cb1f6b92ff69829563d56b92803e215886740cc02b10f17ef671645793bbeb359fb7e9659f9c99bb1c2be53a26248d37def152051d8c7070bdb66a7724c5621c4c6b3cdd6e873d95a9be4072fb4560bf4d528eabb5fadb6ef67e383d1fb6c8276b0e09a8b17f7b8781fe6a74eb6c88d9242ecec6b79f6b53dfb3e46a1793f3f283534d95050bc0fd1786a197bf8870c84125a8b541415a28954bb7af2059bfdb001c86eb47aa2969f466f67dfcdd2da8da223ecbb595aaf8cfb31fc24997883cfbe5fc34f726f96564b2bf7b9e8d064a36d3c3cb41f3e971fde90b3f12cc1b304cf123d2c7cf48877f6fdd1db89e15aa647cce3b3af85f1eceb01514ce3d9b9a026e540df1530d014c4361ef3ceca9610cff67dacba57975edcb06adff780ae07b4ef7bb5eb1e8f6cd17fdfdbf1682c445ef6eb7d2f609e0e804ceb0fd9a6e48d4587a94fe93dddde88637f19082534fe181415fa71d3fdeb75a1891702c2a19fef23a27ed8c335fc348668fbf2d47288953f077c2ffaecc29fcb270a4d445e4d246addbb733b10051443a1ef63388af4f743b41f1e7e1a43ac17111a227fdfc6c87d18921ff9fb219efb215688b6ef7d92fc23f951e4fc381c25f2f775ecb86f440f474d21ee9340e9a6fc9e08f8c24df7433f7e1a2d2c04e4276edf0f0589607e1a452a1d3f8d9946e6a711e7ccfc347ead3164c3b3f3d9f8f8690cc980fc347e351be4a751d48af1d388552a3f8d5e8db6efdf31bbf6fdfbf9be8dd57edc10dbf7b7941b68fbfe26e3069f7d7f63ddcfb4cc936bb927fb709f03bc1f855ecd4f9b8c6a35c3f2d3e8434222bc1f85262351cb4fb6a7e6f303c400d9f77ddff7bade7f3ba19b904dc8150a127a5defbffb188673b08e9f627e92f937048de8be48e527fdf7452b3fcd8858f7b5e83bd16391270a6196588542a18977ff0b4dbc17f7432137d87839845962e561ec2db15ab144362180c112319588ba444cf6d9dce399fd1e2e7277469284fee6fc76a06f1c34dd320e1ac526d18196cfb5280ac6879ab8cf318a9a60904cc804889efb9c9361b225b6b926a4c8e59ccd89420e065c0d36c7f99866d8dc574ec5c94815ee497ce8ed5d2a4eb5b94ce33df75e73693c469e3214a4c89d721017398ad2cf899ec3b2e53717fa50396eb8c83d8c5b108754e13ed3e6cf9d40a20c051a8a2ae5209ccde6e2a0b72d71e8819a44e686e3f7367c71f165776fc19165ffbe27902dde0a767efc1ec8f50000fb86dcc81df42867996b816007cb9e46f07091a3c1ed19f6705b71ce7af42449e8b1cecf659e7d797ea1cf453eae9524073ad3324faedde8f0d47a7c7c443834c13db8f6f1641d7c8345b8c79f6914754450a66d8edb36f713c86ba97ef8a869d9dcb85c41747262e3e4e154b21d3fcd9e1e8ee6e6c5869a0e6b46b3b9e7dae0dadc7b4ae5322107d80f8526a1afdbee598371329c8a5b71331ccb4fd5251432a0f2f8c862b31daac313c4f5ca6980dfcc7eb468b821a60cabbb73fb5c443e19b4b9140e619688d99c481b54b5397821cc1273ceb9c4124bc84815d9e2312f0279721548917b2ec522886b719e63031dd8213d05872736f73a744e4a28a549e97bff2ea828b64edd08ae06e4013c3505c03df73864cbf4c251a6a1c43d9e370edae338706ec9d1b8e8dc8fd43cc02742c1e66670cfe2087a64c9325ce4b81793d01ccd0f17b91aa9c2591b722da9c2bd733fdcc43dc72d4173d9674b008440b0c4224c92849eb5d68f41eb8d830ed9bf29f8b225082ae2f799d0a3d6b22c5bec538ee3388ee3388ee3388ee3b8ad5a9bcac272fb896fbef9a6e08286b1e1e2f6ffdb977aa06bc81602ec4db674ffedc014c53253a1edb1f7bde0fc85445a6bfd3cdcb43dcbf6dbf6a2d0880cd6b071f2841ef3de7ebb5ff5df7f8aaa7f5f5cf193cabebf7d2ef7ca2acadbd5f77a1d4c68c3c54dc62c6d4b0098602281fd86218bbea0e7db3d561b783e96d8e78c544b2ea4d289961b77d2946a3bbf2268bedf4be77d2ff87bc9dfcbcf9ef942c3c5199a7c604f5b2d9ee970b1befb0e1773b8587f0e39fce45f1fe6467d6cbad1f64d4ad0f667b51bb7815553940c7ffb0d41b769075a873ab4af549932ed5f676806e33963a0c7bc93d063a6d3ce6c39a711f478b75301cb1c5e17498ea0c7baabad2673d35d438bc44662630b82a082a08ebe4dec23b1ad49098dc4ceb752a7db10bf373cb7690a7ab4ab6d575245bec6241c9d84962113348e2e9182e0c480840628da503149129a6500a97da7d33943e74cf5b9922a77dc7c8c9b8f8d2555eee752496aa0bbc712d3cd2bada1a9922a5288d156197da5c38f2976d8f74565d0f9edc65f8f1e5350cc5cbc286828765c4ca9cd03f80964cb8a1f3fcd10aedf8b97371c7a1da3a851185384795f8741cf644bfdfb52b668238c615fc71406f200ba660ae04e1632b2a51a2106fb7a9ee7799ee7799e57e7a9fb5e749e5f11a3ec2199da8dc38fbf8f04069a3efed08b421abaf7be0cf4a86d48623031090db4fc61df4f09a128aba2a88d058bc50d482449e851eecc408f28662839a0c9fc9027fd0834996110b61fbd7d4c0245528470500a814964a0290b4cb305457e03eda3ec943b90c0941635dd2d4c5151d37d0ba6ac44c0149616f4f8032da546dba84b4df7537e64cb7cfb4d60fb90b7df04ec8fc21030c5879aee8ada5707ed3fa46afbfe9072f1ea2d52d3234a6cdf4789a1cc644b18640bfefb2834d7be1fd33efba200ed27b47f4a2d4588fb294014a56dee6adfc760c91a348b1545392a250ad204807d5f86230b5589c509f41db68c421cf6fd149844a5d4a8e9fe8da166fbce941a6ddf2759e951db881e4a8ea3e88c90e4494b94a6a0b48452134a4f28f56182a689577c2f4eeb57044ab63fe48dd32391c8470203fd3d7d5198034507250b2839303f7d285bd8f3a6f8a4f4fc26b9a85dbcf76f4aad04444daa5c30c527e58786293d9a0cda0c2932490c4a3249121a041553af34a5864992d0638a8afae03a5602844cd79d3daea8f9d8f7537a9ad817b358b19871d4e8efa7b0419e506252acc99601ecfb2c621cf5438a9aeedf1f54fbfec84266dfc723fdb800dfcb8b3c83c87eca94255b03e42de74c0b30e78b398398f334276a4e96390d3067cb9cd8a31f090cb4e8bfd72bde8bef25883c4fd95199b2e46a806c5bf276005abdf006f6112885f8214551292a37dd14d5be3fa4f6ad4f22a371b878ff86373ffa2932900758519b29aa149994956c99630a0df67d2c025db828ba78ef7f3f574a682b4a6a2e7a09cf8ada8a36ec0bc3c5fb94a5a484c7c5fb132ce9094b6852e53e6ddf97286652e57ea977a8046d376639ebd40c000000007315002028100c068462f1701a6782dedb0314800e85b25a5a441709a324c76118c68c32c82042660000000046468206001471589726ea9ddac67b5fb72fdcb47f35aac75cd0dc410606982aa139c81f9a76dca1d9712cee78cbf541ea9d3595e98e26ea52e4646a4163857ce3a2c489abd6e6a6d2b8863c344be12735fadedf50f67d47a1e9ebaa4199c2ab11f1138b5468a818edad8a0ddaf9758dd0282351f09fa9dcc3711319083ba8f03a01bc913cdd4d2d381d3b8eee3aa3913f19800fe506fab03d13933f78054e3ef24ed527d1940f99306fe3688de838ce99543adc5f493511caf86cc9597ebfacf2fc3cbdc83c3575358507d52a66b8bfa6814da0e163bbd01bb2ffe89f86a28a45ee8b689f15e728474a836bba2341623c35f543dc1e0420c8a818ec4ca9a2c81402d603fd3372833d7b30f209bebcd957e6dbb4453c1380dcb22b3e41505379f31573543ac30bececfd28c6a568595474927acb897f264342665d01c0d6fa70d1244971014028c5a8e76756ca40a08dc4b7ce09d0255bfb3fd06c054901bc01771f43685ea542e6c47ad8fe852ecd9cf071a6e059c93edfd00b2a29d51bd1dcda67c0f9bf3907f98e878105e0068655e0df0b877daf6a7597008ff2fa382876d84cbbe854173d19c0f2fb116a139910615eab7eae3cf76d42ff1021a77fed157142c3631959c6e2ac53ecdb1f6082f944000a71e35005748f62ab61b2394c71bf1161593d941e833bd077ef59161591a29c1076098e0c9095ce276a3a4996c5c6971b476f65a6b3d4162807a6e446f8b7992de37afc8a672ba7bfd7f704f21b7100c0f97fecda27ce8dda698384caf5b89432e9fb405f580df863ea9e74fcf191f733e0f01025a8eaae2f57466565b2522f3aa2b28fa02c0fb5cd3b7d62be90d6936c57da49bf3e54be28003c2c994362afb4283c4b2c25996c3bf4a1114b28edaec49bc2328d01e399e8b3d5f06f77ee6d4431fc87cef5d9086d91147d3e8d4ca56c4645a0ee42904152ca7e42a2825b9612008bcd6f89dcdc8d91c5fdadb68502289166416c8cbf7288221a287e391fcbb7f79e67e696153bd6f7515313f1c2b8e58067c20c8391ad7924ecab0f8685da069c97130d31cdcec5339eea775b03f557490a775fae5c3f9561c6f019dbf7ffba1a763c6046cb0e4585d7c6f23b557c5e61c5f77418798ebf592ff05e0ecd74aa588f63e55738bdb059bfa17dd619976fc85de74eee6a1618dbfdc8f6cdc6503872d3e8ee802c8dcb2312b70df3959e8acd11ab0216bc58d40a689f732f4b9adf0a6d320365dbd586b22f09a431afc0b96fc0f3be7b661a1383a47873ff7ced08cb4d8c63e47496d053f1c38c9d949866db305e0d315a99d41cb7d436d050e858ac5e01b43b0c77669de8b92966eab4a74756288708021130cba39d393c11fa196ad809f9e6f3981069998ef8dbf0cecc07b9ee6597cb4bdd4baa08fa304f46b6ccff2e77b958252ed1cc4f2ee5836c856c52525f8c3c2f5931455a51f82d9d33b2fcb063dcb4720a8d1c0a345842d934950ae4061c021e4529396e416fe5cf365a85bbec206cc5cba3410fde36113143f8b3bf7183739d7038d18103449f7e3647c97e711efe0e27b17d56de7aeeacca56f2cb4662300d72cb0cda5a02bbc0b08a4ada3ee8b1de2e100625038d2c4ffd6d232dae3212082234812ea0bef7d2a30f6e246558f95368f6b8e4ecee146db2990793eaf4dd8429cbe0203da6960a1361db173dd119bfc1b11cc3a2313c8b8127cac5632bd7707c3160ca0857bd4ebc268b8426c600fe0df4ef6b29e01dd544aa7a630856eaebfdd22cbcdd70169b946793c0346257e82a9bfabac41600586d4f729d47bef2db8d0cc5458e25d53b0ebc518b8efc610cf9df64bf581ce20641b5f20ca6fdf552b8854ff83f46c88f181b5dad33b85ee76a6688c2da608f007520671c3342022beee68b70207ab2636f624291fca3b5bd87b1c0aa11029e3c1880e890fadc9f0414832a2d3cab8c83f5aeb5faf6ba0983fe69db1dfdaa29930d5bf4e1b75c3d00fcdf9af644569f02ed471e6ca47a8e537489eca1c92d8c2375ab1e958b6e50b2527ad38f82591d06c64a7351792da4a91a7abd4eada29c8acfc86aa3b233d4368aaeb994efd0f4effa6ec8b0126426c19c1aed80ee18dac36c7138dd8c2255f5b5a0c4dfbf0f5ac6a7416438cf3a9bd4b35828169f46efb7eb2db59f034a55de23e442f0e6cee395f83d6afed13fa618271c39eda408d7602269e8548fc10f04a2b0c876fa87b156ec5881536670be37003c39c0012fd55c04bee3ba1e02d7a889cd894f7fb5d07208ba4587a562110a5a9e1527df212f193a0e6d73ead87319174ca2ff7833652ab19715982440efafd6d3a0fe8cc6ad9d41f94d743733f960de32d18ac5d426f634eadaef7df7c1855f75dba452f9167c638bf1c835f88a62a1906e3734e76d3a36543230508961e5d5626ae223fc905790fa021aebb24451a9c76eca1dfdb899a17134b6be0658dc22251e2622512b3e4c547aa8dd911fe8429ce7ea2c2dd6615a5846b24cada2a1a24b8d720f61ad9514db349ee91833016d5ae9154751ba76ba1605a9068c46b48f986474a81ad00c183656ec0d1286d6d3db16123b248e0d9dcc642273196a513a7e1be7a7f7c5a301bb414b853dbab6c3d67668d1162a650480cd7b2c1e69ed401a7e832c8798e27597d23701df4f2bc96eddee49d15c7f6a1821668897e055259709f11981a1d895d7cb6e749dedf4b8d7fc24d362f773706c2f07903518d5032e16424f69d168e6e51bdbf11d4f1539c814b25f1ed52c3adc2cc5f7bf0253391226702d16b551004f38d7fc75e525d0b4c2a01d90a7fcf100a9fce941e2248e2a8e8f3e7b63f41676cc363de67aecd50b64307039f87d7b656762141bde97007d98598121a9a71893fdd26e6f3cda21cd808698fa4b8f136a5847a4326238c44209bc0035bc2ef433d43d0ece4002956741fb3f405b7a10cedc9e878ee1e0c0415d3ed1552571d4e87e02469588f2c3e24505469885e400f3ef35d838c54efcb0e8d40e195bf5f680bf6b2653a74862c78fae1a14c33ae6add816b620323cb67bb0cc22b57a34b0abb6de44f58d7724e8a9b9441531750f348c17589f5e1dbc66a9a86845362fa7ac2e0fa247c07f7ee58910c8f121695c31d5c4e62f1d88764b84cd9bc3686e63e3c056f2f26a5ee3c4605de7817c30651d1bbe1a966de0753035769000b261121edf060dce5620c51cb24b28791aae2d04d9a4ddd7c70dd6c3d97b9382fb1d3a24904e5441377b5117a804228ff535dbc213b0ca1d553cd4dd0e6b28ed808d16237f443ce6e018f4a5e05898538f1a882c99f058413320d230dcc27dea60ce9408ff26a76901e07f5385c86ff93919e844334e8b1c7be96a94c0b5343326fd15c8bdc5cf047028c189715c1005ca172b0e46342b545deb040312c242e99dfd7f0ce76c24bcef0ab4439798d1d65763974258c5ef260a0b3e1b8e0d9e2d8e5d142c313de7682aa489a14ca593c884bdc1776e8012ebbc12b16f8a49852dd6ff85b00c02cf148ab0260ddf4ed60c38b4ee53820de0c2342efd7f6d1aaea5c94af3a9454282a0cc3c2d4954bae51a44d9f061b0cc00569317fec88f8bda61850b11f8da078410c241974cd83f280cd8fe4c1a22d97166938f869521d6b554863815ae0c0532a532a4d698cad075c620a690d1e5aaf58cc3da17d8919c288d9772c379db46755212416834424f7d3f3697b5c02963658c102fc518613d5756c6735da7577dac4604703aad460fc6b5e4641575451c5a9de526377688fc23886e4b44e238286a0edfa40266f5f794c5d111aa2c50e97c60a19f0cfd6e037bb6339f2a3a9bacb7a0be9fb6ac3971c8fde57eec50034922812cdee8ba4adf3ae4cb69a20c09e8ac140db3dcab814192e8f92152e452234b7a389e8ab6ae8634b1d9fa269625460a5d890b20a14f8d3304729897824001c9d19aa12c92c184935fe408973b6040a9b12d1e0a1d61a5bcffae7491718cc5311d5071787f608bdfd96cfa68c1a58756c1fe63d68a0b59ad9ed3f5b36b4a7e5599e5c2f0ec43804fec3a44ff9192fc83bfaf0dd70481dd1738ea4d56ef33ce326a2fc74c7b0765672c431b2f1c8dcc0575acf7083fd5a496caa541a97921b7df535991350937c3223ef050569064f9037023c26bed9d45b2ebc3622696049b589fe3eea232ea8f76cbab2fa2e7d474b729c52b5af3c309d57ebbaa52eb71534f86b630a14adc5b39f32aefe356823a14f510a44ad403ed72040533fd1314ea223e8d025ac58ff82a6601d39513ce8756a7b26677d8ada52772179a144cf9a6869c3c119f1659ca114b12833e8595df5795d154db4863b934ce08e121383c779b7c946a9039ce89ffb297cb4a3bcc05275cf7a1cafac9f7df3997506bc3996cef296a034b82ee23561c5957695c4813e07c3019574053c88908a175584d6b3320a776c736e32e24cb8985eac8880ee1adf6088a66bcf8a0408c6a304b45046ec8e630571ac4ac0d2758fcd64871a1ef75f32badf0f018a3ac1a50970f2fe9226a75d67966176d8c883ac8967c9dd475a167e20c112262c118a39c5649e291a2484a17ac7087e082a8b821eb4c304f9490f68722bf1c82e513e8761c1d8a70c5673391fa44f6a82446548daa69e58134ee05078c3b5db1cf131bafb73eec6ac05197d674e2b5ec24e892b1c97889039925279526e6d0134ae9b2c3e9491c46fcc7e2556e58ac1cc10fbcdd427852b36285ffe69dc8d27b65f838a522407fea83c4091a52690ef49b270a410f7320bafc438773e5aa7b7dba38936ee3cf4ccc0b7d310339f00ce7cb4e2fd41b3f5f86b986290c544200943ed6dff5d23e9688655f133a79fb3fe98562fb0c4632a97d07f0b6018e0bc4bbfb13808a9c8727018c93a8eff2624d24a28193e7a20b50018f811d4d8a891bb0eb52fb78d1df0bc02b65cd88d42a8a3fd1a3255b8c630471ffb4cf2c84bfece734e5edd92f0a59f1daa93d06311d43b1fda14c8c3eaff84e05031a9f178a3591a7570468239ea9b927a9f99e6d97414b1236c25041e94805593701e534b704867473e39f1b0b63e157a3c4b257d737bbb776de75fa57aab33e64f4b44415247d9f3323ea62a9f6d470e371ff8e0f834ac14dc037f26b0cf3b47a8d95909bb21f3fc61eba14e6fce006189a808e9ec5f5bfa1f72a85667254aa10352c06a47434c51b78684eb3b5a6fffb2311718a99a5b47444a794c2e2373106398de9bf33fafaef02f3fefb4863720f2f7cab33fdda8fee40b4121fe02921aa9d88254ef02539f53992cf26979e0ad3c0a0a1b78ac90813e00306f8db58196dcffdaee7c9f609f12949dcb4ae2daecc01d914d6584b227b64ee5432b3971229c7249a1ca2442e08d22a1707884a3b002870b6a4e4183779b1697733517e6f2c1cd146031ee707c7be05dc85f4e03644f46606a01082bfb68bdfb83641fda547152f2cc1a82272d32052ef5d714491dd145909d919f7a32bb9f18182afc909dc5485606320419dd6265741d241e6c03e95461ea01d11382829007a865da7fca1d8f7db59687e51a0e4e1b10e12e361cab4a541a92126748a6e09ae44c11a9ac1f0af4bb1c4c532d73d1e32706e2f502423f94cb521e69930c79bef67c70a53cf7b06705a9490600276b64daee04821c0c72a916212a3c3f10ac3b0659426712a728c890b5aa1e8ee4078142a4604ef863af6de0f23bb40062ccdd22b28b8b0cd3c02fd612f01e4a34483d303632f0ccbbcafb286849c0fe7270462e0ed2cc9032a6179c9f1ef9bb6a70f2eb63d6d576f7ba8196700b93de430cd4a5bb9e0636ca202bcd8e6132d6a434448f23008207cea5a0c4ad60b1f054ce17888483e753083f5621331716df849197f231b4be8929856264ca2c00e42e13e9a0677f8f1a2302957ee83d6c5eeefe12a0b27bfa6c25f436ed8958924cdea82806bb483d4140292ba91a0e7a43a42365e629ac0837162c565ccc59f1e35d0459464e9ba41c626b91879d06bc4d8cd0f015cfc85e39e2fbca5b4a941d2b33489cca5146458e5017bec583bfcec957737e2b35346f1d839432c70282d00d25d314afb73da87d65f5cfda1f417273d1994e34b781d41cccd5668cdbc3fe18766cfd1d0fef4313454090e61beb0d757334bbbb4385dd3304be753bdea31a0279847a093341157dcfe04f51f92b2658081f914d4e9cc77fda4854f2ab1fb6d130f57fdaf0305480c58e6ef56f1a878ead24596f164efeef5ea29ac103ba7f0cd4949c1a4d330834244b5adb68c609fe16a4415970281c4d05179bb8a184a5962ec4ff1f45ccf54ee35e64b5b546ce0fd11e1dd7d172189a67db89456104a5aa4486cc5163f53a23f0f0478fd6225ff29a082e24e7b6bf73ba610a643211a67edb1907c3e12a08efdd2a34578c70c61a7f9604ed872737bb8c0cc304b5e84edf2ccf9cddb0ced78939cd00a85ee17217898ea86f16cf8158147f9754254fbf67cbdb4042da3a0ad2c15a4a6ab33cc4f9beeb765941135ef83e6f4e056f7b76761631eb4b6cf2824875a6bb73fb7e9252c2bc3a6f637aabf1014db4d56e1047690adc9626ae9ec8e052da45220bd84515f6b22a2e95dbce283a354da0818857d238551bf86a30e77cdcce9e60b7dfb77542032c166639cf2d7be83f067e1c14b0e9b84d7ca7fc6ba3665327203b8035320475331fa7722523bb957629325ef23f1a7e904b43dd452f95e1edb567ca41028666eb5530b792525b6a8bf3a8736c4266b65f1c9ea5da44bdf33865e9188244cb1dcfa32bbb4c6f792e97c86982af29bf4b0887b16c9a3643829894143fb6765e2a3e178b6c5976a5184643fe011c2cb6bfb8919e04603b4b1bf6b14d60f55a01cb82d52de9311e46a310ce4622106c5711f14d44ee44ea0760cd104b5bbbb2750bb17cb41edf8e0f7f9930e4b337b0d3526d87d8b38f14fe3170ee8e6fa235094f638c1fcafd35081a3c4bcab59bd32ef17145231c6ee70282ccf115a927b3396b9a9bb797960e6b8d90eec91b9eedb27bab180f54c2cb56d0ff41dec6acd512eb405033ec6097242e643e110c8bb427095bf1fd46e9cb6918a4e52d8e212ce8ebb1e39a06a0cf0c77080602161f2524f90e91f4701a36558389bb62f1b9816bbc1e58ef5557979c60e88abd2de578e8c90120f2139bd484ec888a07139217679028ff7e21e39637410d106a1e7711067f196e2d7fa42f59bda9334598a8fd988406c5644152ac710ee79ec36d3ce376704d69390b3ceae72d500663794f3d7a28e20b33abffc0ee65ef7c62cf3e74e06030110ec980d2d854d2f0fb5e90eca8758293be9c84624dd66e0ef7079f81985f2772f01725d4d324ff6046812260ab2af4059aea92772cb83ef8eca8b3e66685ca8daed37dcb1e33a7f4838e5b99a2dbe95ca19ca7d282449546cc86c4432653b6eb4a3f802ded12262dacd40a7875ee4a16f366c2c6c320d5e6239b3c4fad07d75d6f37796bb6e757c258e77d51b208791aa10f085cff332424e1cd9ad502efc17fe75742903fabdacb3ce2e025858ec16cc97bd9e4b0ff313afe32fb85058abcba7f83edbf84d8f506aa6dab1d0eb344cd07f858e5be6c258212586dd861b854e7bc8a43d992636fedfeca20fe8f9bd98446cff35fafc79ffa46c03a84483dbfb3fe8ffd717c21c2a71e0f6ff0bfa5b961d7a62a45bff1f8eb64b9b97130d367409b25bc1ad80255bb24c6bace0072b57600ccca97e0eecf8effe6e6971ef86852a5f6093ce9cc415186cc1df66950e23ad5149c81b9e975c40fa10b3209409f5ab1530ca7b3181d175f063cb3b9b3fb60bbe66815bbb4ca3b1a30018616a59639686f25b1f4d18160b24e30cc59e2d9d0aa84650f443aea7b8daef0d8d775885f02f684371f2e23a4929543061f7fbec3b330cc3c47a5ded18dffac06163942397a98e6bfd75f23874cee8c7816b969e73ba5fe24ad20d49e962f17e7c9c8dd7ca116e8ad92ca7518b5fd805208d469ca4e00a4cf3598a13282da56704ba873cba45fdafcb9ab7a6785b1bae381de7eb992b2fc3fd75e7ced3449d6d6e9a2bd1347db31e734029a7d06e40214d5666f69742bf11d0d446dd854854f30543f43ed04bdc270709e9c33c1ddc554fd0719c4f270affc821337080b9bcef5ba387deaa21a990a369ac8fa94dbb473aa2e706508a994e2350d619d7a695947a8d24bd6eb452d6f65fddec4821887db4fc490e23f2443154c0c9f9ad482de2712d68caa8071a9e49eb3f5f121c986be540549495207b41716ce517730ef7ebbd6f189b8a39895a9a80e8919e71facfce0868e3e564d9d31c0c71575379a4c7563d58fcd8e5fc4ac6ae432765a5ab286c0a218b1a4017161a3fd4ecf7cb45c10c83e2a7c17625f3501f0d12e50982fb2f29c440b9f5b63eaf3cfc092982af4e48d522edb16804eaadd56acc997238468b9b04ca050f9a02699ad484fee68470d611e08ea393da4fc5ac1be9a99ec6b9e1144b01b2a50b13a41434b31511b13647a569dd30c1888b65a1a19fb1ac29237ef89d6b5cac7387dc6c88b975489f87c24cc5ccecef7b2884f37f829add33f0ca788d6b434eabd8e9ec2af58a8e44f1b74d7b00c4edb496ea1fbbadba1f9f5b00991ba511763bd9c405528ca27f67b738de5098ccc73b6bf237ab5238381744b0ab2c1fd58b3f8d12803134fa33ee7d39136630b1743218597877420b0d6c0d8550eb101ded227d33a098c13e66d57b9a4c61600f97ea2d6b28ac3a3861fd1cff60547b92f649bcf0f54f90b3074480120ac46110c5b6ee50edc0be59459ff75c00ce67afcee0648c8b5c29dc9c6917066008c9075a4155b93a39e2407abee77c0054282d6bf40812ab76a0fe44fa2318ba9b06c4c8dbd758ab35492b9b3a17ff3f7cafb6a6ef06fd80133a82cf34a46dfe71e19b1810c62b0a698fd10fb775a230e0a9a89c2048c844864bae0663826c1878639896abcc6b430d3856fa136004abc37cf588db0f997b769e0af49e11ebccffc0f6f456875d4c67ae168016bb5ef7732f272ab303d2235d9e01da84b2f7a70e76a43165a41a11cc58e6842fe7f6bfcecbf1f40f6cb8edc1907001e3199cd33006d301ca116c376751c05ae6b424bda5c04e1de85612a3459724a8f9bc903620bae29c864ee352315bcfc32970de887f4d9cdeef6234c8d1775fe4db191ac8854e574e3336a457246a39d4195df40ec86f839183f661e17d464a179972184d8f567a871f62dff44d04a7761aa5c4556683dc956c8f648cb7226b97576c27123c06104a0c686acca5e21def32c3827b164ba172af9957624b2c5c157cfec5377c155cbe4e16580cee87ada5b1732d9255c5005a8b2e998b1f9a84095979b5d2c2e724cfb35afed2d41108de9c1d8c75f8b1b7f6413f437788a495c71034cb5d84935b8b28055777e96d83c76db9b6585c1a19bb96640ae0464b65d60a3a9ca430ee5bf8f8d76e695ee63c2111674535a28ffa670d9359d831afa7e18bdb746c3a8b74498f8a8a1bfc59f4da9ea7c4e332136adc8228cbb73e8fbbb9b4a06310c67ed00f26a7195a3ec9dc6f8d512ee37c2ca920ff4fc5fb3540438e5d35677648ca8bd286947f2cc4133970851c726f8dd90c767970002c6075a96cad5374217c6d0d5fb4c85bb726d99b60667591661ef4d2ae9537aac5ed8770fb5abb137925be425f8b0d8514002e30c4f8a04fe0d2d5e3a606c9e1e0b819e6261e4e932a8e100594c8ff4a9686c6654529de58cede78a3d726e40d726c4cedfbfebc580a2e0bfdda5ecec253747980ff10e5dbc7c30013ec46beacd5cb9dce5b822efc25751364c8cec46ea4765d966bab0020b6fb3a93b70e0b870748da09fb97c459c02be90c473bd9b681ceb9aad9e97e71a2e15b6ecd3870bf129a2f4135a893c266dd3de9c655a4710e8cb938b839f2e8b976d6a4afcaffc4ae177f5c4bcb918b2ae0e286d40332ee9bd6bfc8915c5553f8018a1afa472e22fdcfc6d7f1e6452e01c01942ba13e10902202b1a82cc1baa98fd7d9710253daf683213f903129fa06aadff7adbd2aaf912a895919b301e86517d0f6f44cfb5a1ee003edf1b0afd8b9edd89d767d5caa006fea1682f96d46e683dcdfbdb5a0b93e13bb45e00968c5c5edec76a040135146e9b9768f68e551cf46ff3bd4c27b8299b132f79dc4c438b60af9a4c717c1f51cec7af1774c4c820f870fb66725848ad3d1c69fd77c7b09ada06154f14d263c6712e63162fdbb52f6eed7a6c70d03b496d29908f788bffe6f867c664e61467d6a44745881a441a231bd6230a42fce6334c0a6872a7123bc5381b70004a7434cbcdb91537391dc3ae84df8e186f67cc21f1b358287a16d92ec2a08b08770d953097b9c332bb6f2321057379f3e356e5671f21ec6ed7d562ad5f0d5d44211c23431ddae6ccfb14f8c82abfcbb0b8c384f2a15f583468d7250326c7f55e19ba66252654a326f6d825483e75552ff0b64d802ec194f1da18c2bb7315c1b696f8fd512af11abf969040052528923e9cc63a328d09de4774b9f28ac983a316bb9c72381edd6a376500eef20e1d559d3da7d162ce70e32c5cf475369f7a97efededb9764eec1eedffca60e24219d9430fe15b1e7797992cafd8cd0d0449eb5d1a779d28142d65511d337c0216e12a354871043f043f6e75023d8f0897c3c07d42e6de7aa74f1b755755615d77a6da173228d69ad09f8a9a4320943d112cde05fe0b0e190038a28c34e2e931abcb3d1505d7a986827e709843ce168c653e9dd2667de9b533e73c9022461da727e542763e3b2263c6e5d7535419d5253a3029e3f9404c24599e3e818e10889d0f399b2c4c303aa584418574cadec7cec0297880d0fc88a9cb65ddd6e8007f65c5f264d1244365f19f6842c6384a2d55a842e0eec50d704270d43d48fa867c8e7669e5bd4bc2482e45e3eedc179c83264e242e9c6e7be8395d0470f29e0ba4da86a6c68afdc951b0bc1ecd46e0e28237d4d28865ead0c4160e72b524772a69cb55c465336c2f01c956a192ef35621be940e00324e7d7a0d87b990b3c43940c71d9ac97d8d48285efb4aebf6176af9aa6cb3aced18148ecda6b655c1398024cc5403c220b4dd55d55ea4f625941c44a88dcb18c63588cd4dda4f85afee603e80623f4a3b099ec2019eeff18bf15eb5691222eecc2dc0af7ff1a77627418a3a02518106a1b23a0d76c87866ebdafc761896b3eee0fb80382d1724a8136772553bd80c35c0a55cb1a22b405cc46be845da34eb7742e4d72b3c4f270a7312d7839e5cdeef1a413dbd98cd4a49de9acc15db363fcbba2cbf002834ba17ed0262389b677ad24f420cf4a3bce49606f86eafab673f5091be09efe6c99d6010d419b5e0eca4dc058d9345cd5f2310ada5316e00503dea3cc047820c65f2959d7a4f07c0062a71a6a964d70784c950c59b864eced104da685be171c096a789ec8c44b1777889140ae53895d06dab295577dcb2a7e0c931712c61ad7da9f1f213cfb974b3c092e7bd24a8fe8fa901edfb57b432a47b0d344f419a9984cf6c0f7e48fdae2c5c9b984e17e05073fcef8b0788524bb7ccf9238a4ce03ccfadf7cf50bb231040536b8c18ae133385bb1f1f0db4dbba4fc5ff52f83a89669870f7740b9c5d6252d44902d93be02cd62cf8e0e928a116d550a3a37126074f586af2058c8ac47b883922499ff9f0f7a532e457f2adc4ed3bd1c93a8de02b3c7275843e6aa1cf327c04e0c97b7dcef994814904797896883ac1244385a8ea3726e9e5c44400829b82dcab078073f945f48de8edf0d2594fd41c94b66a1649466503222d414671efd16df08dfe2e6bbbbbcee2c3c0b6cbee65edcbe059d1d06fbfbf8b158330cc0dbe160b9af1368f8ede470c8d849fdeb282a243c207a43a45e5465c69523af3d1bb840b078d3ec8007a56104d926040d24613e5929470a653a89123236e022c985dcc80540bcdecfa8e85b9182610add30d8a0068b71d1828194163694b936dc75deb63c626d257fbdfbb883f2af3b0c8079ba08b78a22fe8406187f8f4f884b74f8b5de3eeba0effff239c4d0b51d6659bf500874eddf67b1175f1421a30e6cf6a5474af91e51c7cb8383ebdaf0c13516e4ac673150e23ce417144c505310041663c32e25dc33494f0434c5a656fd4e8fa601b8944ce371996d0738c040145df5dbc134a0793b6fc4411bc94332b9cc9d1067351b10f96b569fae77772f27d0099428aff0db6811ad48b39f3f3a9bf0aac6f85f49a0f93dc14371dc500e002869279a21bab036e0e9bedf92eaa68f457b880d2584467db075ca04cacf13fbf4893fc6b9fd2c16e87fbece2000c1d47a2a70a106fb53e5172a945ac8d1a065c695aba069150a5f8d6c5b23f9f94637414619048ff6b55b358dcdcca20ddc62c7ab75f405f941c93a94a715138d04bef8772dc8f30e839a5a6d97d8c74fcb483a11e7a5f138e4b4c0af0f7bcdacc41a9f8a8f1051b3c4405095e2f9b8947ee073d25c171555c5f29acdf5af041e83421049f722603904e5c87c73a7aa22729aa2f8c42428a5d7fc47272fd82844dd9287460bb14af7c353b06f4ece7ac23c5eaf682e77dafa6c005b6f88bfb31fe534e1eb5ef3b7b5a9608f4912117539782e58bfac64f37557bee56ad3a98c828b841f7cc2a68e087c0711eaa24174f62f74126b076a33921550bfb8150b3586c5a111583154618a4afa778477fa409051f7798e310243dfc87f514522feb8fad713bfa201d5e8ee841b0025751f8e6c2f1cccb26806ed8f68f2a62d2c1a1fc6a858f28fd09cb7d1a43ecc4348998eacad704df9443bf1bbe4bbc6c7e3e25f137253dc3ead317e3a657010bb778d504227411abca59b4dfb2f426e18b5fc1c7d05a830fead2b7c9d768d44f6b60460a89c7010085474808234565986e7d305f05d9e1e9ce9fdb27b392c6c712ae38b1991aa71517c5ed29958ac1d28db356270483280bd0e7776e1c5471fa4ef6ded1aa74dc395453d6fd750028214326bd70893c30bccd251962b4b554d459b6eb66b54c5687cc2d7cb812be6b65d03be962a1e654578c34aa00c9230e43392ffca3ecccc7ddf7d6e3032c6e3b0898cd7470cfaad7ee0cbbf730a508cc9dad314ce18b633adc8cfa86905578dacd875a1d7f5e1d48c5a2ab231e2694579500e2a7f165435613f6bb4a13ebd9058907998230552a3d505887e2552a30b75810c678fe3a6e1c4de01905b51ecc7a73edfe2ff313ab56f4f143ac644cdf1430f545ddf412e61845ad6d5552a11e0364342cccdc27ddcb60a43dba05b2fa1e2d0f063147608a6b2075604961b5051f919264621c7abe97dec30d2ca9c1de20c1b9c0be2d47e08aa78d3f0be06faafb7cff6b48370ecc52295700573655d6f24749906acb971cf6d06fb5831bbc9766d8e33e62e83ff4fdbdcd667d4f8663fd1136ba01537f6a5e6fa44b291f7d78a5635f27cfc9eefd1ca1c7600c3c5ae3b5bc6568206d1b124cc0d07431484c1bdca5337e4a7f088d447d1758b690ca27b139a38fce32df8104d86ec6650764773d500a26611f58e3f6343779625d3268b995e82dc09dd070962cb54a1b7da1fba2922e8df2f9f0f9f90b92642584fec3a49ecf14223eb03782d43d0943bee88ac8bce6f33317b202cc679f0287d198be0980c93778fe304d6ae9b2a524e5299f862a557e843ddc64274755d0cf8d5c004c2d68ac6c3f9561050be54d1d62f5ef45ce8c261f28d3ff9e347814ce6df7c6e9f35390e6516b4301bf5b24dbae16d3427f8d984bed3e1267dca9508e61d1c531f050aa7f60c0fcbe09cbb1f9f54fcb492669fc83ae6042b1ae9952f33c4d420ab36126863b557289a3776cf18d51d2fda0cdc112345eebc0b2e1c28fefb1d1de2f8af7c66ac2b2f38122158462b172bb6af79473d2a9e80d243373e54e6985c22904a0386d75cfa42d29cba8f7fa9f741ac6a6b6d3d1949ad003b7689bd0e23d2578673756809d6fd8042af315045cd5271491ee739ffa364fd54786442080ecee765eec679a65bc84b5fbec721574619c33374596f03a081d87812f777f04d73913acc976642008359f5f0eac062d98bf52328fade9303020a6813040c67c9da0d387125200e96416a0b56a12fff2202459b4f0e38bd19457f67a2c1733c05f161def03293aa2f58ba1fc9f13c588caa944099ec47f9aca02eac1d1aa3602d0472d07af0c7ad71c90895b8b8944faa7408301c5879515297db364b98d172119e2444ae4e868a398fc67a40c31861045008d37c85b2d800e23918b9341ffe3b162af69cdf79ef5c1078dc375ee63cda4024bf80ec91758fbc9e8cb5ae4671a93b1f88503265ceffe18446924c0417a8938a7eff0141caf7a543793def68d897e0e7c2ed82fd814a4b2ceb0aaac8e8206dbcac654e1d2b7cf9b4f0757ed0333d7584a9413ef4268bb3987396fbbb8bde57cf298a9554ceb7365e7e98d1115bada0bae08f2b9016fa575734e1c6ed8d624b3e350757027009afa558357e6062daeac4865587c5d017c0df0221066d122053ccc27deed7c091fea0327a2e6f194c04b2e8a1fe3fbb272257e51e34e5fef0121ac6cad4d47fd94122d061875e3d359a8bf2356cce99fe707558ed3a7b47b086626d42f5361faa1d800eed9438ab072bfc7e5ee9c17e3d7e5f38b5d1e004a808f44d7793b67fffa881fddd4094fc4381d5dc9660c1325824c28b505e9f1f067fd8ad3ad67dbd2daa0bede41b447bb038cb48d75ad261c7d7370b58813091a5cf900ddff965f83187ff74795ead8baccc81353563db9cef065668b2b2df6b9a8206c57364556bc12b0b4d6b7408f4e52b93086b33fc4146eebe6cde00ff39aa224dd3c553d7313e085d910f1e726e8e86be6d8eb3032f7ad638b541eb90c38e73730557caa849dfa2b8750a04a0d237322ac02b1104f2d4de8701fbcffdd583012e1a85e3265c3bafd63cac409b8866054bac974f5c91a1113c663198dd4f7f339895e0fa564078240c154fcc0955c47bdae6f244a02f4ea12b38f93673cda8632c7fe2c94343eda619510bba9254ce269281fb47e10f5e91a3bdabaf25b0cf9578bf5ed09de831473a6dc19d0c4a76ef81f2becf20160d2e738da2a6ab48d71b9a4e693126549512a154c3b1b95af5487f1d766b3afa16f2a558c45478051fe82f1172bbd59749733c08d8839a5ab3d48e70623dfdfc74ae1b5d02810ba695e540992ac2d39f7a620f84170bd0c4d33604594d51db80859e6f52cd83e7e37a30265514a4bf8b8ec39ce2dcebc9da22afbe480de74af83eed5abf845c1128821f7cb293a0ccc5f9e27a10c5c5562187ef280594bc88dc2ac2957cd3c0722ec437b517f9dff874980346e261f5f6504e97b9f39913c1986dacdfdbbd2ec30de8c9cefa2f5984eda552a6f8dda79c7000324f5e45f3d75592559251b88a5a381b31885c36417d9ac194df2393d89a2a79c43dc8b37fb3a3051696d15ef175b3d86869f5f9ab692c35b62c0cba268c13b87d190511a7a28e9a3d90ac14f1b2843ff9742cd77c07a5fdd4453c54274cddf174a312bc5d2bbd5eeb2959764be160e3bc8954cd2ab7d3aee105c3625baee8ec7b5a9d2a3575ac9a0ea2d74a40289619b4f4da47c48e81b66e2377c72f035bd9d25761178db93f6d34a40d441f1302eec02378327ac22a5f54c0e209b6e9972b1720fa815bec789140abaf81c18844d8925fc96000c1028491118dcdad8fdb13fdf65520f23b9fbcbb9de5b246e63adac88d0251243ac28a4788e8e1ecd1b0082f206f8b066756e06c2c3a3e9abd7ed31d9fbece4594662e5f7fcc9738aeff16d03422a1d3c040a4f8b04ab3abb92a12867b2b56e9e10e5d5994e83486a0a222e240c021e80a00017a0d089e1473a27b2a56e704c66df43b66b8dedff41a1a33509ba5df9174ee4987cf6623cd279be3c337943edef21133afcf21f6da5b9bb3e6661bc6a3cc32b5b9862975552dc9af5c4cbc3878e895bfad788fc2f302677b00005df16eb033ae0b26c2750de6ace97e947047272d725a66c0f1427681c68c34f4af6cb936872dd3649404268b9cfa228aaaabf325af9facd6ad7f57c66374f5ec534c2f49a4695645e499a3625fe46f87689ed05fee32483db1797ee5a7eb2a083a0bb73e2782f0d60a2c5715ddeb1d33ccf497cd23b9ae0833c74c1b8196c35cebe4b64c8ffe95c569123f82e3c5489569bd0bead5781f7a1e91899ae4211aeba307b2d454515457eecfc8c136fafd6a97c03a9f3675e0b349bab4647127e2c233a66b803e5582ee8e20c8b4e8390f83b53f934c8fd85ca4552c89b6ce15c0c6f28fe9bb09fe9435c7f42dd76ba908e95f5af5d7bf080f0674d99130487f0d173c7ce8511aba42aaa1bfdbda8a5226942415e7199f729bc0612230212c4721ff6e06c5f4c9fa51cea7c78b4848eef9a92123318d15b6b3c9b8be7ab5d32ff167c3e585b67bbe978e50422d81935ebd023b4b363f5454738224791bf79bb9ac3a470295e30b933dffec75cb80ace1331e406c59a9a368fc8df4710fedeef77414a276d335cd878ff638879b7ccf4b7c764e79d64be7cf64375cee064d69dddc22b66fca6bbc79b2221a23a3f6f660df35951c2eb2fe8ffe697e908a9976794da4d043a4e60ae39b74a5165eed3c6e342e9ea9b026879c1c585b8d8ceb630824d025d65802ddd6d8c95bd7d8095d6bfc045a6becd4b735fe9e907b53c9d0a252ea25bf902e9a1758167e8daba1c9fd0cc73a962a40b4b8884389f75e2ec8d98b6b8de51db9b96c26d84f3605aefb124473ab1d0f9dbec6e0cf33648b73d1397e77ca43a3e80d94a4f34abe306023de8e3586e73a76e0be44efdeb2f56a6b87fd2139033af5a236518480e482342e4d5cf9137592813038aa3af8b21bc7ea059a57a9a20b5ebdac03fbbaf255c7884e7c519ae54a62b91112e2766aa3428756cb27e8f9864af67a450c033eb7d4176a4c4ce7bcb62781f761ba4b78146baecdd625a84f6cd7682049ec94d7305828e1339bdfe06de7768b8b8b851dd2d959d865226644ea8eecaa087c6e6243dc821bff05dd35602a3fdc87e7370ca2ea843362952f07e9e7ee4df5c2a5df9470adef17255bfadc35be70894207b22464e472129dea135ad52189577ceea6638f5c923ac67edf75c7a777d5f147f76d7b9bd77c1ee73898b017bd9b63430d7dbe9e88638f70208e618c90ae4b918f9bfb2840ed5e9cb0da78b0582ebe42b0347a12e19e4da41f8347a094c9a6ec1be170c6f9abb12510eb3d407f22ce850dd51bf92bf84b2cb05d7eeb8e029e6c03b7bd331104dfb3fb2f8d59ca0e0f5ba51888be1963580855b4349c840480c8b35ba3c584aa6ef234b6e2a80e77fd2a464520b3cf2c068d5f9dec32a6ae3edd59a6500252e77921f09b52cd8eb21d53833617a20072f2198da6411c47cfc912c58bfdfa83cd030b3368af620db9d6c78c55d239218f7735449e439265d7ddd20405cac61cc670a63db67b84a337e4b9cc9251428b06ffcaa569cf9c337d4c035eea45d365553e135093279619fa51cf76f1ab0b758fc48e896a1a16c9a0f9fcb3c8d0c1936c3e730f216e220ea2cfde2a6c4869730a7f75c5a7fdbddce85bedd7683578e73f7555f52e313e2d2ba1d8e173d3a319c5598b0c5fe09ccf617439d7b32043682e0e29598da33fe79327366060660d71855ab43d50a9fdefc721bf596404d2815391211eea036917bc8b22a6616f1c6b163946931db2f7a0b5a14d2d8baf13cb6f34be8064c258a81b7c6d8f47848c58f3b65e98ed3c3bbb7674bb2c534820e1be7dc1ca6bf85074e6a582e55dea1c9a8b485ba7edbf089000f2f94ef681156545b3fce06438be0224de37e228e1b9c9857582210f1c5797a5a1bd646fbcff35cbb4fbce987f39365acb40f12e988d95ee18ec49536eb69d30700dc2cbebb367d51183dfaf1e699f60d44aa826eb389f90a218bc27294f26dcaacd575fb57d784c5bb2dc1bb12015acda2440f0ebeb369045d9b7cccf198733292fa3649ba03ace165d94b5f3462186d27b21eccd059358a32e1105ca1f6a5cda9886c4752b462b6e38431473265dcbb04327ed6301cc11ca1b718e03fd4491c58749cc140cb3aadda4f956ff9f83dfc4b0ec373bd7d279c6721698038aeb4d6c085662b932aa6ada3b58a1383010421803f3f8b0dd013f4b9ac19faa5d032b6272d07b9618a3f2a2305c03c088fa5977fb35f5fa3a43dafb8a09e3f8d6063009f52d3aff8220a70d08b2d59cdadefab79687008bf45cc09d2d02ac953bc9a4657b576c829f9b5fa47cfef3ccf3e522afe7efeb02fa53302e0d8945d55ac3950f61ac2e836accc36ff1eb3795d645c3dcc86e3dbed370fc2f57f9692ec3da37c8682dc899cf02357e77353d22de75c5d874e3e9cc9ebae6cf895d495d65f8c77b95cf7e0df857bde1be47e3ef66df481777fcf4b8ce3afff3f48ca489f105ea356eac6c142ef473ad9d9741a70d01a3cb8c82ea94426bd31910aa7188cee9ab56d9230638c73fa892555c81bf70a9895ab4bec2eb8585afa729ad9d57f2671662a257c6b5440a72237a4e67483d065f782f32367d2ee4c6ea16b5bb1411d10123d6afd1fd261930406c7f1da6c6c2d76153c500230beb601ae29be60bf7656f7e09d5d37ab24e41ce73d989daef6af6a2160da9ca52a5523f9668f7066863f3bd11dfd5af4b8b672c85645f4f183e5d45e7410beef90734f628304cb2bae75fe9a575d3c54b4b0959fec05b65660cfad7ca5a67f73f017cc40d684741b4e0fa073f23ea8be59317a7e76df75e82d5b80a9c53105c85a6775e1a444a7ffc881d0bb1fa4dbd2e337fac1842b2d522eaabb411bc01aeefa5a73f769916761e2efb6ceb4766ded71bdf3f4a2a90cbaaca4b38919345eed3e32aa49de8f9811c008e0992c46de86b0c142b33837255e44a0065f3d24731168fee17c15217c7f9c48263ee3598b7b67b7a08ee7a7a2d847fa1f0dd4276b001226ca100346d598657345b5b520346058095f0c3dd606d0521ecfe255058692128437b3142cfb43265b40733f5a0804f0cdb2172f654b5ea076f218cf6bc28bdaa93fb56d0b8eeb9bf36518986d8e983c920fb21feb73fe407bac966092574f9b19e197f97f4f1fd3c58ad2753362090aff4d432ea91c93d4ac54cdff447d67439a803cce8374f3c041de6e0d005c8c7ba0efd1f4acfa4dff83498e8b7f0e7ae5c63b73b2880bc858db11562a4e7022e36c6fa601e670e1a1b23a241f6acfdf303de8b42225c415d0f1b502f6d3ccead7cd6a345541d974dde22503eebd0d39f8030b5114ce3db13b97d166c17571c0e0185d1f5d8a83adc12cfa7d23431b5bf1b863db1c8703cabfb2a0b122f408ece00783b195724e41ff673b938ed06fd14ca93e071bc7c183cb420a42fec8602f032fd3f6c98d08a70995d8c07f1af5f38b0e890a3240aba92c08e0a57ca61937d66cfcc0c7091aa801f27c1cd6f3c484fcd67edc7cf709578964183c60b4d72dc615dce2d9392445b49010a088d2755dbdb29736914adc3d44bd3ff9454220c686278836df0e79cbf859275e225c1c14657fb05f417a45bb815499e01e40304d9f062f49dcc467816e806f9081ea0f3eb5aaa3375b6b44297d279dc71a0d32fb39be3a983ce50c2b3c0b7850a149d4bbb972feb2bed0b4cb5c2e84d3ab8e72015271b5bf08f7a14506429d9103f21315f02dd0658af26d23cb8a67da350f1f59d6bb337eef4567ac9bccb42dd701c2a185070138cb2a66219cb3a989ea96241ad885cfc59f512ff50c5492951e77f543db0c913e9187f027cea78175e51dcc074d0c262b20b9b8bdac883cfd616effda86f6e09d0ea2c0abc584a58f6dea82c580cc4b042822b41cf6eea43fd80a5b9466534db79d0dab225bf2392761673b25261db9bf92e512d0aec4607f8baa3a987e9c26d8d964302d4320c413024383837374de3bb16a432cbeac95d54f0dda3a24f9ffe98d6612162f6f82ddaa3564362b0421376c083997e2493968397d4eecc17ba97bacef915952a9812b57a077d1f13cad158cabbedd6080ca39709927ed37742ffac1f392908020b54b6c24c2e01f5d267c2e4a8ca55c062c0a91c9f3fcb18ba1d3e2daa78665874f71b28d2312cc74bbb78006cc650e064a48b04140ee83f96b5201b55a97eb6d78310d75d52e580b64211da74e28a20b2fc87a84b0f84844f934551011daaa23a80aa1b27fa4744cbc7785186530b15b26d4ddf0e825aedbba80b3235fc360f3a1900a0dff887afe9a9aa296fe634145f9c634161533f9c62566b8e7d708abfda0e5876c44672648834200ddf45db784674bc87707b59b0c5e64d6c0318b51bcbbc4cd5ac5ee28b806509c4a51f479c504f97e8ab2dc6a34af800cb9186f77cee5b02014c322516dee678c14934736b2989559745e5b543e74e4fff191a833f3fadfe65d84918f3fd14576d6ce8266cbbc4562a54b10638bb501e8fa66d9d09d3ebcce35ba8f11c486d84667bc54e871a91d6a458e5415364a1017e7a3b301705c42c8765651833d13ad568e877d2a46a920ad2e91d8d224f34217f5bf2f72df551c6f9b03c6e8d32ce1c18b5bd08268564f4ac3c89656f1a39629f37957a793e903c361d0f46f67541bd129f32a4ad85831a23e7830380cca0b7767a493738c8487e91f8ee23a439ba109391a86800d7bd41c7e3451a18e0ea8e1ed0ff3843267d8b99a0e5fa7ded7c272007686f064f02d4212fb3e55f10219344fc19161d62729a1a7ad501b42d6e9008678538847811b838c060b72f696d152e2dfd890ae4eb672b157b4ef250392971c9ca69a0b301200406082eeb86ee9c255654aae7aab63703226812e1615a2a5716d5055bb679b0167208fc00a8312701cb0662c72372de6842e62ed6cba105de297802d09fb505a469b4a9b872e3ab260e97522494f87029ded8a4946ba5fad5dfd24e59ec62ddbc5f8986d9e990cfe95fda617ca00dfdb35f4f74369819d294bbec0e941f37840ec1ef92bb93b858ac51e375b7e196043f05cb213b2dfbbeb66ce5bf9c7a572dba994e8b953a5aa807d0220d428d5e68a4e6e1ed6d8dd24028c5db7654dd8c992870bbd2f8222dd21f8f79d2fdc0b7cb1228a8dabbe97a123ab18ca5d1d4f157c871982974365ddda9d1ab2b3a0429c2cdce2862cee7750ed7dc591aa18f16370533e15a45014b625538ead970cc854df4aff8cf5962226ad472f12ad6878abd9c670a0da7794e2dbb48effc3cd3e7ce7c3bbe27498a8fb016ab74a89fe4d670adc3d5890247f961da75255382e8a3994b1c5899eb2789bdc82e0ac0f46c3d78ebb14d793081cb8e6d389457ceadf80e15e1384f0783c42ae9fbbeb38aae6f1036a2cf595ddfdd0cf58061070d654557e57f2c3c83168abcd18aaed188ec466305af1a0cc244c28f613d3ddd5a308ad7ee3cf77634c58ee8ef08894784919ca3f89f1e8b46931980a2578d1c9a27533d0a67bd679a89362b4e1cad0cfc5e761ef486fdf700bf91863e496b5694793f177c832c10b7a4a1235b89786eeac069d4c403fd1d6794c347f8c70259f82a1b03bb2d2ecb298e473bf460761ae4dc347d9e5af29e5c562adf9f4d6ee8ef596d44a1f8ed9f819a4bc085186264f5792f6be505c6a68fa08aac2423d32ed5b4b40188aaa960d578202b34a149e71815763c4e7ead42c965fa728946b8bb97f4b2aa453d8b5c15cd083ff528b39937d835544521d4290f48035f634de2b27f0a13d1ad9c3c1dfa8d8482b323d1aed93017a92095f03add1bc9da8ec4903f315e98c28e20a81935f9849d950c1c6e1918e6aa55e141bcc884e946ada992043d628ef2559f0595b470b62b0fb8dc6f497a5a513142111dd85ea94c4da1e5c25891a4bf61cc141ce789dd4ec6ef08ad87c9fa308850787c66720ea736939c41d179f1531c86d063c8eb76ed2779f88b1576ee9e8b85bf298665da6f7ed720257e56a6e180884515a2f58d894f5f66d9da10c1d0c16c81682b74d922aab51e8f29438755874080ad839924a12480fd01044dc0c40081155745645c11b54c524c003c7302a86563ae347260a306dadb43c704081e9ff343c19bb15c9e721d01ed2e942842dcc92580bd79d0537a4146db3004ec4c78f570b7bf370dbc47e6432004e083dcb1f4e0f8e5d424be212457eb16e79c1d729210138bd084a0d1809cffa0f0a641384c9503427a3ecf012806c94507a4e22f5f556ac241c7cc3fe45973ec7d2d274eff5b0835f3756a0da2df39c8ffb76fff8d1d1cd5f0a3395ae1b982a2e5ba337643398faf1494de5439f498a9a5e509c001a94212c7b0a0fb5edab5dc55cb9aa1c6cc428c1ad2a771c2515b376ff3678066ebcd12bf6c9fabbb08dcb46a846c36a2774aac6d6cdba4d8e1eb71f083ab68f8fa23b319f6b7dae41a58e625f73324e027d29209493aa243750c3eedffa79177deffcbf69d32bb1850fd697e9d2ae54de1f6e18395ee618febfd476e34b9d5239b5e5ddb174d348991ca426d444382bb29366a78d21ad94944bb950ecbb4f3684aef6aa638e6941469d905a690f67cf85e2fc4209274c5059dbe04ea9b42bb982b9042130b93f66fb35d2a0ccb73f8d2589eb83f5d6597281578daf6c4f0b70c6667407ae788f15dbd903b20d49336887bfd4cf9acc99517170e7fe71c0d528078889335cdb9eb36e7c1b78c445f971ac6537612b9db955e5fa98e86a06bf745db98990c095cece0b87b94ea376c6daea35f8879c0107d2a3245277a5779f16ef081d7b57a9f3e5d374dcbfaf6c1866105a05807cb3c662329d4602f26bda0b17ffb8642cfe15b16fae661565998941a067bbba81e3215f7a19d0f7e51dbeac49319a884563fab56da08883ff41b60181aeda8fd1995bca600328da8ebff683f45e7a34b3872f3f99a28363545a01acc2260af71070210c4ebb0dce2a702703612d9a645e6c6ad238ffec92bf8c520f85b6c374d755e29409e10d1ce8580134dc7071405641839741dd3d9f12a14fea25f44d7dc915d29ca3cb0d86c3109b208b82c3d03a0cbcc794efa9447baa2a095699f8e0006b8875a50a871621f51626601b8e557fb5183f1a1e4c83b1e3ae0ebe3d552051eec2349b88ab3774893b57c01ab0c201368c1141694c5538496bf96d6cb64055e0232704b8fedee6dfdc758e0161d191689a0bcaaa3a97009876617f29cf59369854d17f41c50ddb8a8c939c0f3e23a4fefde230eed8808c482d4f1c128680e9af6ab687ae2dba504972cdd97d6fa185034558e268b118ad1cc0cc73d93340b78b6add3a88e4a659cbbf90bdee6fafac8de83bce92c388b211a0a573a0731bc027d53b2d1be18a8b5d62250ce110976917850da2cf8b9867c33a12d330072bf1a04123a49fba8a012d7c88341e0980ae2402d5d7457816a44435da4231a29894d1de5ab341651051b63e3b11e249b5f76e2a6b073a7bd7df2333fb60e990172d02fe0e49457c0ee80dadb81aec7bc10d24eec72c0e78d61152754141b13c6f27fd88c128434ae69c56cf96618899155655cdde230215334a7143ed49aff93f2555db0a94223194c3d1c4691489e41d23878eea574072d63ee081b0404c414aca9ebb4095d5a98b234d5cd9a2f63d09be10d4a5491574e76e4f637593193f85e88b053c8104e0579a73c5a329d00e1efae9257286218512bea06786e21fc00e5d71e4c1edad92e12e52b54db596f947c4c6604c70eb4a0d2cedbf732feafec957731be7f5358f7bc45ed3c841763cf563d69ba2c641267bebc38763b174e376b9bf6e210c98cda6014795b63ef2189ad4b954428f51505e300beb5bda029b69f9027a4ebb1e6c3bc7ca6c093cd764f69c09814b718aade2ad93b7a42fc3f9b69ef621eb84e6415843a5efa028a6db0ae3de2152860e254b3ccf3f4051d00a64e7f9eb770660e9c64775d3f386d6bb563b8b4ff016a4f70c08e1b4b1fa0bb01fe4b1a02bee0d4283faf04ff864318bafdffbad21d2f07f52e18a15d423abf9540856ffdc9917824f2d2c745e051a97259c4f4f945330a84eedb067ba54e1be6758f9835717c73f85256ffe2d03ddc68a6eef28525519e0a23fca3ef68629345526f2bc2806bc526d9b9e248ba8404e1d497959a01d126e039a0e226ebce518ea18e752dd02731b519d3ff87964fc4415aaa8142e5845d5624d9b5d5e4caf743e1c659b4b3c732a3a27019589188158d817cad95e9cdea0ac3d0b404837385f205a01d78037c768dd2ada60b5a9b01902b05fc22091ce96410f6441ea15ca114b0ec4d50d430d39867962365e14754396eaf4b0cb0a740a3cc8d1b04be76c63f74158105bd39f2279ad7c5445daa371d9aa1108cd37796453ab8a753e2de00be4c435568101a9640009c5c1c1336dbc4567d779664ac7b5a578164f62d788788a97602b6f2acf1bda9b7ef6057c6854f2e475cb702bc897694f20c006ca3d0e847e48ae78b12fc7eac1bca8b611df5a9d78482d24d9a34d9daf3b05fc5541b0fc654372b3c1a8af82b03f661f73a719e5e6f21df169ee8e3a7ba8332c5b45de550ce88a45ce962f82f3852b24256e133da3b29e30b9ac3b5531c665be5b632bb5758bfed22a32c504b6f7af0b19cc904234e8c5511523a1664a03e67408b48b065a51bb50716048031151b6f76e1ffdd7d9cf6166752d3034a4e52c5346d8aed9e740b70cc44144a11f60f45fca192a1c64e118701635f92ccd6c8e45c1aad46d70b0090104188a4bb5b60142edd2ac17d4503712c7b9ef9e85de9136546173a9addd32a69732da09a26dc5a244594fadfe66c452c02199c3b8e2c475d50f3e445c70b2de9576765578ef106381e953e6b4888d2ac4a82374f813df221cd0ee0c16b9fa6ef136446a601bf5236c2cf7ccca1c1ced96f3494b13fe4c00af4c493268f8974987dd9fe8c0b6ce3da3008e04aa228b2670b1a105a397926c02cdf9fed984b58aa529e96b45a2b3a688b5087451b75803c263a72f59ec1645115522cb443258b704c990c107c2040f0607c3644dc3ea9369662725287e622617a01f16c9a4ca3e0ebe55831c50f02d57490a62f0f6e94169c9ed68fa1e7dbd028080213d763a8b99f7ecee8a13f7a5fe16724712d4e2e372248903d39341f24362e4524f2654d9897b5091c51fd2bd3d5a89ae2d5b9b24f39f6a2d89996b36f7101bf90c912ff96510637ddb288c78b49e5a8db12d269e686409373fad7607bb5c92550cafa4f611e679d47357f9463de0dc36d5e2c91f5d289e6920266ad9a253663a3fc9ef1d81cd5e4f74b317e57f05867730ed9663ff1a5a6da9ad6afb36ef6dd07b7acd5284f5d47821607827ab13b115e540419593e298188b2d27ae74c682ac3351a0b611262ef33a6ca4d57d7f97121249b5826c06abeb9b4e15278fc298bc308f52e16f91e927b423bde1dd4e68bac12e55990e84230f2068489cd81c7beb995521a9639bc310cd39a55fcc9840fc119c5cf52113dd61f7b16fd36196fbd3cd330d0f21bcddb57c978a64a31ca5c90956321e494104e7733c287bd3c737281f0773c1a17d5421b82dcba5c6bdfe578b6619b16a747a8234c1f6a6abed6498e160a787d395edc982846b9656c8a96c1936892e63b51e8e3933d57c54bb588a1ddb361c569bd69cffbf321b8bb4a650f96415036593109dfb464975454806eeb622698e77da663a8d9e8a2ad15329e708e870865510a4c9c5c09a4579bbf469cbdf8c0efa00ff9fadd905d75a245511ed4dcb6adea4d01440c740074f0a00ab478ab66addc8f9296407ba47ad450ed26138552c75a88475e11ed55b6bb4d37eb0877da8666c7e70757939da91d1735799dda3f7e2991db35927a10404a90eca191bd35100d942a6cf73712274ecdff061d0136b1d245cb08375ec66439a3de42b5f0fa7a98f10c81fd8bc9ec9c795b682f79a89d87c477a512616ddf84eafa6e8701c54928d09bf6d0a125ccf7a1a1510f82b4b60d7c8778acac85de81698d7f80b0dad12648165fa97983530d6b1f447d0b675869d3067443796d0ab5fda88c75db399d3bac688d25adcec26074ff3fac2b20ab41e0d9ed0e3d59235879d943e7ff95eed48f7f7a2ff23a63c3638c6d59177a7341061aaa7f5e9a9f49a240b622d142d3a24548b3d576fcaf7bccce2eaf49f3e71ac0bc26603fba894bb1b24d0f28a963ddc4a55a51d145490af4bea35846d5033ba3f751fc597e610d63b6b49e0b60a974c48824a043e61a74a0025cdcf551558611bf75d734f995905d946c53ac015c5df223a56108251b7b2dcffb6094fe8c3c67c98a616623ebd34ca5008587b9c672c79ec066085c52e36366ef9d3d5916a11c59dffba6d9b9c85deafe69e6d060e4538521ebf1c8d3628d5b74e653117127dc48d0a260ce8302c8b11c2b201fcac1ff98ba24643fa036a68a4f227efb5382632bb42e57642dc1a7590de3bcec966223dcd6e57a00b804c9de8629eb7f398de6b36950cfdec6fd2a3307dc8cf3398981e71e1e13cec5e5d0ae131ec44da0027e7fb0e9173b445b16bd902ffe8b71cd9f8bbbc233475efb1a97807a4a99d58ed79a2c4514fb04c707a4a1c55fd6f163da1d289206ab68d5a5799c88ff2e36b37e40fe94e12b82da62d9994410f3e2b2406deb24853a33183a12e3b9c58291132b900c5a359b25ca82ef0730e8f946a434759b390b3da7c5caa8cee1d93289dadab54f60c505b2dac39ebe4de97a63c7d730ef0e154fca092872698aebc171c986812aa34c4f486c101745168071c7f302de3a37f8a9fef970e6380af648f810d6cd0cbf042ab62452a6de29e8d9c4130083373876e46361af343dea886dd3efdfbe11d767fe2e3ba23934802b77b438b0cbe336e2fff8e1883f7c528896e99b1c625f5f235fbf349e411e99796ea95d6f53dc0b6abc10a0c94cf59c4106173c5b002b8a91de316eae10d274ef1592db22f4142caa0539e118775708cba6dc6f22e208f3cfb04ab521680de90a8491fd0a47f3cfd9080c7796a0a877bd34bd0b344012e8562a002e01eaca4f2ea5a137a6c1b2ac4d7999994637efbc03ed4dddfcbe9a07e9b622d310ee5581e726c65ee32a7624e01be378b55a6a2f7c1b2fd860ec36e71ae2f802fe12fa9cbd92563da89caee4d43c2f794bea14174c34e554375155d1026013a7b3b47affdceeab715f487e0692ca6c333f5cdd31f419b472f1c0502261c397d9e8e908b03e7d80f8fb892045217e07830206c1a91f0c421eb9d7dcf5d86873052e2d9861691225c1ffede004047c2dfacde3d612362841d5c60436ea03f579aa6819e43322d1693f8e382595a5facde601164bedf698495a8748d35b9fa91c723056acb447f92006212e9eeee6a0365eb300c256df688c52d591a62001bde7e858d55ba9db0a44e58941100e4db66fea9e4340a92ff4416b3230fba319ed02c4c30a2476c2a476364269faa75f1760a0be1aa88bd63907ec75e94b50bcd1e8910d0b1c56936b769d53fb96e19fb587408121fdc733d72e4a246046c1474e5e4337f13f2a7d5eb2e9610bbf5c47a52c7e110af032f46e388e3efe1251a6b97946760c7d7d12e6f366bdb338e989dc70ca9f47f4e32282a9a141f70c490d0d0dcbe521fb088b5a87405d3414ad4509d09ce0548ca94e8d2a5d5ca377e35574bd25a07687ac3a558392892d5b839c290ef956f26a9d62363d419b66e5652f3da6ecd70f38a7a664a9690cbeb12f8fec4f74dc931ae57cab945e9ea6acc7209085e89ca705db8cf1e7cb39be3058ce08219f02b80c74da06a7317e04024fae61280009853655622856dc48df5249d384e16ab28ed40ac9b5293a7020dc852d72daea4c19b7fae9f9236ef173e89a3b6c1f42bb86248a6c1e1bccd57d2dc2b8eced351f58f3de162f62906f77468507d3040273f558b57ace6d2994d385579252387217d8009153361f4bceef5a28fd7c6d007380cdaff0c6de82b239c304a39edde85648f27a8e74a72718878d54ce9890ddde6d03f0776b8502b389076a86063cbf2da3d3ef57c7cb12314c33fa1a0d27136ad8f4abe4e8d5722d36d3d73f1cadcec0502829e1bf4f98ce9af07e42382cd92e2e5c6394f71a15219aaf6747b180c450c87a19e0ef77a1192aa2f920cc95c361ac732ff61cf736efa3d3b7c1e2bf37684727ef1a7e8e2858a889fa2fde6ba233b49a755264d99f8cba2e8b8a7dd2fe6655ec873259b0732ffbbb0f0981e12c40a9cf79bd94795564e2ab3aa7a8cae0b0d87e4584651494d1862ece19de96530d445cb029429af6adf5017ab3a844d284f742b7b3314b2288f8f2f925eaedb3cb22d38b8b7236a00907e0a26aa3778007c27a91e90ca802756401b87ae54144656b41df241a8732f78c60cb047972a619210dc7f5c06e89e9436ab9a04ca8bff0308f5bc5b01c7bda4417ab67e1f12305549661deac2ad65bc4d21df7430cc3c6e8aed6c24892a5f0a9759048f770f106dbc5135826c10d479710eebb7fcd7ecb63e7cacb906c95f52a9540f70945b1a8576c672d8dccfa34d06c73923ccc60363ca539258d38a0cc6c924892201f9062f1454231fea51ec37bf1b90499ef2d5109be01d70f0b4383f7dbc6b51b393fb28505163ad6131dede18eab8e6fd8fc3c5c4042b1d1f4257abad1c9648760647b090c81a24c764ceeb623d70cad6323ed69c1ec810539513716697fa131e3119918cb9d1fe37cbaa7649f0061538243616c67dfd4d7d2ea972bbe59de790287997a9faaae96bb3d7e936e70344b587ea514788f52dade74b03493677f9e4e50d458b1152700ac4048a49be33b3455c7587dc329cab54528b9c941d8bc7be6354f859ca8707c34d9522b512052752080d7ac477d9b4ee065977be7a1d8110d6556e9142e10a50460ac88124b08fb6f382ec3ced8535ad45989241fe513da6134e44c2ce48c4997b398344692e2977a4d2a470bd96f5e53b60c64b53bbe2bd726ad195aa17b7b677b2c373081eecfecb3aa21801d6e44d01290b59d2ba72d816266278155750c5859f7fea6bc2e59fd7aa308811a57509b0f02e4ae280ec0e53140cbf36ebab79ead08b520e6e941792668517479404659bb8bb5f63bf8d79e3f370dbafe496007e719c93e60822d3ef93d598e8c10d8ac0f671085fef838df030dc58b5851b011d99f6329d6aa848c237e4a8c36c39a57341a700f3ee45db1f02fe7f1dfcf3e32c36c3eb2dc17babfb4e89ed2c59302489bc7332d613b6150c75038a6413258d9591f42af30d833600e3b8bbf58eb8bb9afa7322e5d6bad1d792b0cdde8e772b0bfb5d2792900762f69bdaa7bd5ce7350b7424f78357482f1ddaef00045bf95f586cf2cff7a5ada2f28ec83d72adca290ffee4cb74644a792276b7c9eb6d280e49a3b0404049cd215f68fccce835ceddb9b10c76ddd7007ddd86664fb051adff6f7185eff09b6ada2eaa115c2aef53381e57c38905c02ce78d95ffd8c0aa7557e05265eab9fd12fd710c4583e7fa05c7632dc52a4bf056bd6cbaa09ab258e10147eb9f72e8c49429d59f857e0f44fb026f9e18b363772cb74b57345e668ba363250dd97ffe1f6c6b824e633f33eb6c5830fe4867d9ecce2517b3e947b933c9f1a9a33689c54790c499cf071caa48416aa71b141f253e121b66bc535a819ace9e7455c85ff3e81c713cd15f639f9e7bc9f3b21eb8fc0c707b7bd1dadee037969890f84dc77a1b9893589b32299dfaf1c257fc4b8b37c1cdeeb7b0368a558a8dab41b02515fcb1611c095a138aee518eb99aff70ad9ce82da83773a77643b77bf4c522a8f7d4f219787955dbaf372dfa57ffbc3b6ddb0e252158631a4f64a708083e90d53dcb4382c8861b5bb2b0acb89616900edf603de99155cc0f5c774eaf64ba38cd18aa7bddb9791a164f29389bddfe47b5e272a18a1fad6c816f77ba3ac196f2388c7e7645170fba21515567d2446038b4101e8abe4793e7c42d7f8dfea0c3d372aa4db180b8c61d0a8728028908dc4a7a8f27f3f8207ad48c46d797f7ee1c8fd1a8161f8bf83bc9b700146721e0c1428fad2c8a1a06498ca8726809edb0424c6ff074f7a6842eaa11aa11c3932b9b80c394c5dbe4290ed5f149eccbb43b883953ebc9bcc15797147b8c69e38693756fd721a8a54911fc0f2b147ac341eceb0f380b3d142cb0923c27c978ad57780c78ea9315aac5f9284b045b35ed634c6bb71198e927eecff09750f74dcdf9fc79842e4d069884ea2985192ac471edbbc3812afa520a8929dac58d7f2f5441ba20a64791ee15a3444a41f7ac44dc9cb85b2edcaed18c51922c0a2d8f7449dfd04128e731339d316bb5f938b471c773856d2867dcd9254b14d0cb92dfbec4c0f04e0c447934f8fc7c6bbcd369d00a78ff4330a600daf46ce0789619d948d76c187b9f5ca18f2f4421508211ad467f657149cf10208bc42ef0d8d958a3961d15a32db1a81a1bfa8b13c607c5685ec01a022e62fa190dd36b3b59853071771374865c88ed5fe889ff4aeae0667c0426a8c9890b440857d099222cbb89116eb088e7d780307390a2257c1d4b53aaf54ba9df17c73afdf20d1dd4751ff0e6470947254460762604626a6700b166e1746e5eaae611533e8aa8d9c62112d6ebf6f79c2591639cfe812307c39654b666f1ce6857e183d0575689de1e91f31a68920172476a8d8331b49b7a98e8b96838211ce00201c441d7de97bbcd304012d9ac5822133a0d44884545b5a9cfd31d7e01dd87123b0843a343b1aa836458eba3b3f288d33e8c0ef75246d88e5a0e8df56f30a46c29db6414ea263010c3b8622ed8604da2bb8b903fb55c6d602420991491983e5f193ed64a6b14b474057e314b3a2ae6083abc14a3781a3a0660015b1ab740eed952bedc4f3fd7a615b75ceadf27020f6ac947496a8bc0d32885ec978174497e5a46caf8707ca264867fb12d81851ebe36670c9cef223444c19149fb5737f3402760e37fb881867ce0c9032635816910452d772f23d6e8528b76917618a6dfea6a889fc59dc1b55bea886e19e924725e3c0283224a5c413917ea909f12c6c305760c28dae7272e77bca5ff943ac103c1bbd59a2b1d4a49ad1034f10613756aefe0c1ebf7648203e778bb619ea24269b5bb3924b12470cceea1be93992ddb32305d9a27837116a9d5eb911d2deacc1df88ce2e1e890be7dc9888a1b3afecaf738782ab6bbb35c2c65772f8384446a4689f343f3240ddb1db1c00a4c8278e626d96c39e1e955a3f8e8bf36209a105e01ec3f33e98a53f4347e30f1c3ed78ac1f8dbd27fa8d62a264a5947e8416c23222c6a5e5011cb27cd61161da1f0707578e138dc8fd0df5d9d4780f122f5135ff4e6dafecd34ac000c01359a0e5053564e6c16fc0446179d3160b67e69366c0783d40fa9dfa63378316c4ba76bf9924a4a3c123398731d4a8eea0b6ce50309a328121a3d0c6d11748f3227f0b5a580eb140387f240ef82db451d753bf2a7276a1f2e54ce07f6365584980179396094c105ba2c40a451f889f228ba031552c1b67a16f003981557625dcdae002851987a3d73e07fab3a0b1890d242e6b63a6484c7dd20a21932cbfd53101693b8af17a3811d4f80db23dde395a6470c1fec5b021c9db540755476569d6302b9a083c3ae7364c42333bf2de1099c24aa8eddf20334b439af0bac6687e092a5f6447f34b5897acb94bdb45de5b45be16e127c2302cc39fc36720daf721b99acccaeb5cd52f8d204b4164fe8785efcac65f69169bcc4b99bd38194fe216f61a9f35b860522254b05bcdc22c3c794ddc3f2b0b1539ce50643aeac4ec5bc1cf7ae27310ec349595c5eccb1938893489c1b1c9dc5092e84fbe24bd1ebe7550f8b3f247e77aa50d1e72fb7a3986cc1a4fa3dba6bf893e66f2603483a62b30a46f20aabd13f0383309ca6d84cac7259a5cf99cffe0c8785273dcac18a2f7bb91524612aa10cb39cc6e728c8aaf2446ca32630634cf2aa618938e4465333073c0cb649e9716751b93605c07f468060a85610db33553dfd9bb6bd787f9d9b8d3d3ffb6c8fa04fd438437564098d13b875bd0ac5aa280c76b9fbe7d73ac050b8dd758b1c64cb6bdc7ba5684617e97a602e10e4e41956586fb3483a6c08b40d886bc92f77e1762a02f944a213c4024f17a53412f7958618fdd51f8fa9e05ea5bda10d0695583effde3ce4a1f5eee32f4f3680a2d0d527ca95a0997bc1625629e6a1583797b90e703ea8cc9e9b225b43b287b7c886cff8f10f5d5ca1fc041680d0bf16f4db9503fc5b0bc8a65ada55d10496061bd61e15aa4af9108b038b00d1ef14356c81afe63b91bb5dce5b4858c9418640c79d749f0c1fe417a12bab32855711ba8385479920248cc8c5084474ceef049a247e72d7dcac900df396b5a3962faca691e54b61a2e36652f97b0120a12c2c2d8a04827c1c6f78026f79ee961bd30535588b347fb04296f6e58df67307466a4c367b6f723094bf28137ab28ad65e71e27be30483f8017bd80b5f9a6025a92cdcd52f5e16b49a2598147e46f7be6f863dca1574f0e6c6d1dea1da358c158c10e049d56bb7a17faa75e232d388e385c913b9e8e4be06efebcb3da429b700a643c49b84976bdb77d3c6f9bf3cf9da193c3fc33d494535aa0ca1431770cbc7c749ece4ef33806eafe58eff780b261dfe9b19e688df4309045a3df0e48e39e50f2b9420b5e7c7858b83131bdd4684fc14bdcaa2bb74f4ca9bcca425477cb6156dcb5e6b580c0a1db4eb91519a81a722aa0201654450dc430376406e1d8402bbb381fd24ad57b3fddc5c768c1f28ed2885c8360ba6ccbfc1af9a45555b28c32259693c89ec95c5da2cd7689e051f99d5a31b3aa603858437ff7e6feed452158da33c97fc88ce470afcb2292937180e794cb54a78e702175743460fbfb0206ba6a5316e8337e48f8feeec63af503743f61aad110348877c9115ae5bd2da5d079ebbb6f2e090185efd3f7da47cad0469d05234ec531f3e88d74e44ab5c961c9c0f113fc6309975061cbd2d38ab9349ae03e2a2c01e11bac61d52613b380b5b6476dee0c30f64190ce90d4a02fe3cd15353cc33212e5c43c3776f2fffa04bac6df384311f746070b07b442106c7d152a1a4354b44afe18446f97fbfa747569ec7d93051e3e36c85bebf89cd0ab1c0ab57691c4f3814b5d1bed0a1e7797893ce9b9581760704aefa11005af8c47f7d0724b09c7237064d63ee2d6a9aaf6f40d315c39af646d5bc503998c03161b919afdf8aa4fd4d04ba46516fcdbe1703d5ebdc0a7b4741f4781751e749221ba203b6592c1384ab91553011dd5f500d528205c105dd171e7720abf9b54a984720970d46ba132f1d015951b36b9d0440cbebc65d523685958352903b48b0e6ce90e89011694c0e27df69b484b02e9a60c015a7c3b153adac4767925f4d08074ce8dd57bd905481d8f3ebf8fe6dcef4795097c5a01806209f037591eae20b56f6d192ea01f6d43ee33148324279d754d4e5aa942d530728122ff3d6cbf883e1922211eaf8cdc6eb50432824806f5883ecf3524a8a90a9a1aa72927b4901c5da7c252d40343beec58b527309e5c6d9b5b2de93aa8f03ccd1d8629ab9333074b1e38bfe51b05b4546baf8fe52738c2a18c4ff591ddf6b4620c5a58bbc37fc99ca35219703820c8ce3c881b8008bcada26bb1ae9fe15e6daf8230e0698487d13fd5e7e25d0ddc40256071acc81ba4d083dd1572d47f43e2e31dc4d09bc181aa2b3d5a19c1243c69e54100e425dfb52fd9a92f6f9c800dd9aab4827d543d479781d1680a434924dc724a740cc26065eb8de719a399086d3a40aba65adaa49432d40bb4307b34ca2fde9710f2a14f96302f47d7b4bfb872ee6cbda81d5d7ed7556bd189b2b2c785dbb17a71e1ff828854d5941b00d78cda6dcfb9cc85217686b78b1c9981e3ff29cdf9788c0d8a76ff62d2e04d34339c6630b08496f57c447d575a35aa42adc26a580b9f57044be466d73a42c3e7af2267d53cd08a8d064d6d1411096ceacb440f25c7d6aa2e9ea039c5f788cc535472f40c15292c1848af7e033252eebb0427f2479e574aa596f353fd399b56de310a88fd0d7344c637c309e47529ff0b6fe7e21c07b4855c4d2a2de88f78ee0ec0ea103f32aacae67d80263a9010827cc792ca327af69fa6c0edb76bb02556066042e22278942c96fc34e81fdf526338f6ad85c2c1515f44b23f3cf6e06f18c024a712214aaa560ca6e099dfdff0948008d4b83ff48713ff2fc7b2cbaa66800de8efb5c2075478822280928b0142d9bf557c3d7e57f1190ba62a12ad181638b65cc48ec26816f7f78db43b7f10aaac7623d76122b81f9ad4ce7105da3b35c7929b2d7ef32436ad6e46099812a42d06f37fd6f061bb14cd9f83faa55d844d5dcd309148ec79552197a0eb7e38a50706309c42ff0b7aa8e964b567d1d62acd28cc496c4477d1cfc016b17e6543f0e929f67c02394cbb02eb20004ab0c981ccdff616441f105f8ff82a0cfeb1878874645f4c18cf44b93ef93151e21337e280a59ca70574135e9d9541cb7572d1fc4ba7a0ea561a70cf5b7a13709f3797f798fc27c6bf238e3febd70ef1203b6b8c01e2964fb08de57f6fd4f1e7f33f9f66cc27fb2d362422a58dad0bd099cecdc9ddaffbf0c61fd3a344fb0a2dc3794d64bc619e03d7e77ab873e48445e969f450991a8f7c2fc7784007806d822b0baa01b98762086f40b01eae5081f1ce42b9a8a437798dd93b6d335e9b01f8c38b7ccfdd23089ea90b7f14c7a7129ee4292c965c0650ac788e728800b5b6a678175c067e74437e3848e156653016f284280a16f7a89151dcb4269080306c44c498781cc44f1d09277c0e7eeb18203ad107ed0308d1e4ca8d8ee2a023c4a03da783a805b3014964d07968f1d24dd7626f7a9fca705991d2a587f4323c9a04ddacd6bfe82285d458bd2e3d2bcd15e0b10e405c0b83d1a6f0c983e83a0fee1b1940e627bb18ba57baf746e9e1d7b310c739929f15b48db72ef2da59452262903d8073b08130841ba2ec8af10a16357514b15c5847a72a4c355885ada363675e993565baba49f92fbf97e3170b62ab80ab8c818631d108174297d622e295c4876b8da31e6fa91524a8f2db1b4431a73e9c45c385bb5804da7a03de7f6233765751bf413bf06adce341b345b28fb442400ca6b6f7f2c2cd2d8d73eca7011d4fe07e5b339882ced35eb83adbd6691b6f6f30b55b6f6fe5922cf164518ed557c34fef0656b5fb7883417d0558bd8437b8bc45b392bd85efbca345bb667b28c106952b08b107bd897acb348a35ffb9a146950b32ecd387bb6a605d166341e2f313295f2546aa6523495aaa9944da56e2a8553a953ceaffdbbd4944da552a9aeeb6ee2135a65083dadddec8737afd66dbbd6da2f46181e3a364102d1e65486d0f95158dbb8dc9d2e4a3b9dbe08c3a13c1a61ec773db3b59aac977deda6606b29567f914b754150b2ec1090a2a239860c9b556e5e75a8544a9ed9daab0ca1b33d7250eb3a2b73d02239a859a2adbd3d9259246b45ecf1125bd05e3bbdf6b6e8ad110b5bfbd0f640938121821a8ae6506f7fa505b18fb5b7cfe1202ec30018b4d7b4e72a7defbbe18223878ef7be1b2e3872e878efbbe18223870e3abdef860b8e1c3a40efbbe18223870ed0fb6eb8e0c8a10344d1525f0d118615b586d29ed33ea5bbb1753659da63acb5566761ccae8934dc6b9ac6b29cfdba2037b274c98e23cf1c99eac815cc96956f77c21d4a688123db3ef7e1cceebefb3a9b3131e0ffecea6c6bbf7532e02fb48c6e064ec3d7563a374e3b711f8a171377d8df3c4b3b192ccb47d7bfdc0b2b42e6ee7eeeeefbc2227bfb56b8efbef6dd60679ac7f480d287c14601b8a8fdbdf76be69dd3fe29eb2734fdd3a9bbcfa528a54691267f9d484cd72bd27833c92e329b41ab69456ca57cddb1a716bb4ea55d533e67badc5397c95a87dcdd8b7c08e35797b994abcb9cee32873a329221cdbc68c8dd8d50ae75f9d47511746424439ae5a2229da2a2a2cdd97c2bae5a955b74494f5c95308c25d08783387cb9b8040bf9b834d9654ebbbacca1349f2e7327a32e73f2b4692f97f6e3d25e5808070d29e5e026cd872809ae9e245a974f3d2ea375193719695d26924249e831d2badc65aebbb94f0805e12542414e9e08090911e9278e224cdc74b6ae0341b6f4377f5ccadc0f8808bb3eee6ee64b0a651c9c55bf08f952d9e14cf7abb5d6156abb0a129ac9876ab4d8e8827c17c4eb827c5d901b5d10972e880a08f4fcd95446be5f680a6258a48938441ae9440a4d1f3b8c4b4b71c965877189655720b6241422ab7e75244b7c28f1984929739146928929c2e02ff4ca7db95780d87d3f1cb4517ce83ae7b8e2218cf1a9cb4346db6b7868081b1115cd5e3f409a4b7b11752174d485d1cddacb65fc2fd7c91074351d0d89b6b3e9b0030e3c1b77f80966c2f85e4cf404d05017332020a025583205ed7a3fd7894b38cc9ff99aaff99aaf9d5f436da7af18e70e7b9ac665aef32490398ed35eba203059b290bd853c6332dd91ab0bd976080040be17752a3223fe42af1e6b38f1725a0ed6e27ccda0fb9a4bb01397711cf092ccc199f31cc98424cbae54b98b959c55ebfb508ea64374c29b6627ec071bcdec5a57305f48bbbe3739d3a6c98b95562e76226f9cf83cf1baf73ef17ac1ee90becfa578a9d75eca733ae5adb0c21ec2d5ff9af2864c56fd142fc8645526f47c459adf95b567d06ce9af295f90a9bd9ac9aaf817b0eb43a1f5a7bcfe940f57fbf5dab5a90b02939d853c5bc83426d71dd9ba906f969872a153bc099bacfada9b41f69b2f07eb4f973131d5afb7890aa1b7caa40d9e2fa4d96afecccc20233a974ca27994396b9532673367a9afde059caf59430d675798065a82a4e1c85429287a12f9349a4732a5ca4c4c4d1387a97ec54343bb7eadd868d757daf52b0e9223f42831edfa38e8b0c306240e3672993924248d7cccd4fc0852875856ef023ee193b5108a04d9133e1863fc848f4f12cd06146ce3571942fb7334d2d0dfa2e47838249c0e8784dbe1601c8c4b5a9a4cdc9399b7250737270e626aefce3859a7bb892a43681b63b460b6767bedd2ba1736a36ea973b26d1fbb4e69eb94b89e70666f8f2a426f5f334f3067619d3c9b2cfc186bbd5afdc7f0703e1b7f17146926a7699aa671b333da7cb03dfe0d8bd883d3912dc8b80f64b1f1f6e1f6838d7f3bea74e886b1bb365ec92a43e8fa34862042e7406b68a491b179d5c8c15aa4237bc8b0830f6cfc82b6b7ef7f2362c9db8a568d8ac07a1461f0b67d9528c2e0af4491867b4d6badb5d65a6badb5d63acff22ccff22ccf405b5d5a6cbcf7dd70c1d162e3bdef860b0e9a936c9ed93c931dd5a38ddfbe76d5a26ae420fea5a521373170431c541942fb4a54892a5125aa44941e713b9146e77afdde792fbdb7de6befbd95daa8f56bf56afd6abd51ab4bad386acd516b8ddb8f7a5f3e6d0ee58a28172a498c4747493325274b12e53af59c7a502e5412d4ab43aaf911c3901a6c9000059c246dfc4580367e2393865cd3c6b6f2f4e49e9c9495f2cc8b9f8d3f2f25cd949c2c698d93d20b9ca5a5a5cdbde42c229823fef82b0c6dcc2155a24ae4207e1ca2d9ca489c9545c691b94cfe8168e36f41810ef3d0f6781b893230f36c3f67d8fe86f9651e07c1edf39220219799dee6645bda9e38887fdb7603d1f66ce5d70be740bfae472ecded6efa4329db9a48267b934ef6f6dbcb2962ab834d098b34297b7b19146950bfbd4cf256b77d79e7051ae5d5b866cb88b3f06cedc8c25fb7032c764764c5c6cf7d4d601b6f8ed5219d8e4e47ac23591d9165a2b190147ab563cc032f1af380114060b8f469a936210b22e8d0e0065ab5634c870575476704b60a1d63c7988e0f9549774db047ba53b232d02a5608810910826881123a65c758103e4d4120a943d078c758103945e86ec718104fa826d0283bc68028c22542d31d6340687193d0da8e312064401845ac8148c10e57423709ac311542d13062a57d465cb63b153d3380e8004a4506b255115a64c480490507346208c164d8a0a648b114838a8c02854c05db522e90a590204503c1507e2822a362d093fa2188ed34c592fe4246b704868ac999a572c658cf40e91483737870e278c4c4cc082975313937d832c63a0aa2288c740cb61f5428c17eb8c004dd8f1988e0f423082796b3537f00c998563fa4d8e1e2c70b5e42c03f96ccbe07dc1448a6e0d1787bac03449bdbfe6fe4acf803063267c51de21bac3b20b4792eaf338e58afc21772e90047f7b722c42b12a9b3ab4e84894f251357358ea87d439cac5933a3177d7b3918b7245ae8ecf82c5accb9ed4c567cbbf56c6741cd0f078f302144100a8a60023352b271392ada220828847812051baf5080403b820728a3d0ca83b0a92f3432b331fe42b9b134c2875a4511a2908ad92a8c6044358f47ccb3acf04aa8060894efd9fe2eab076938d9618c0db81521f25b1122a5bbfb1fd9be2224be7c19a317a394734e9752ca3fb2e55401c7b2853fa594cad0a7921e69162d28a594fe8e2c5a68f933ee28e50ebd82e3e29c32f739e5e4409da5e72c2b156aa5d20e16417d5049775a330b28add635ebce6c6975e70bb52b66a360b663e2459bcd355ba8d764f963acf5ea76405ad828d88c1cd42be4c09611a6bef340b5cd85f310fe476c9f14f5ba598508accdd73245bde29cd63e87bdc901ecc908435dde2f46184aadb61df5dafe289f9f8dd24fb4d2ba5377228c3f4b10b4fcb0ee4c152c17a1da81961f3f74777719a9bf6f0f43090e8c2db6944c40c210404f3a8004a3261b17028c5962b36cf933f26ba490fb8e5eecf934ce369e01466cfbcbd9ae71179257c79438afc43e96e8ba6393308cc0634993306456a6ef8e4dc2b0426b12860d3817f4eab91d9b8401831d82467408313333586fba39948bcb2f997841c1cd6c54f1400ab14d3f6b290c61d357c56ce8b3bc0bf81205a64d63c0d8ee6f8304bc1597e845b9820940ec1d4c7841ec490f9b3ecb93a14dafd8f4639a90840b9bc26ee80ca54ebc20869652c3804182ed8faaf4573bb1094048f240172f248ef861ce7c20c44f6c0540e8e20929a06a75c2e909b399eddf75378e956872226329e30fb29892238410f401206c200062a7490d624de400f61a42a72461091ed0b1fdb910684eb9c305112260c1911220f0004c0f9844e1451394f801c286d6166c9a1363ce1a44de92384d28dafe5838e1043ddcd8fe5b77e338ee4d45c04c706cfa9aeee10ba24d996cfa186b1fa47c42121f9bfec5f4b5bbe083938d9cad771a1965b149149cd8be032858b175133acc1b90cb046d4b7a364dc2a60913149bbeaa09143eb0e90de82fd971d3b7c15b1b90b3e4533a9ffe6a3e1147700981c809324872d9d08f992d032c71c1e3430f47457cc086fe8ccb54166c4a84c3834daf0dadf5093adc80f6945c6c7fea3231542f4849185102156c71841348285e4d32d6d121d2c84d3f2d031de639bfed4da62085ed79139ac1856d691fa563a16fec18f3610950e8cc854d8146fd80045adfe0eef800bb4a681b3bc67c280289fe1d633d7ca144b7ec18eba107d5073d63c7580f54dc20746ac7580f4eece8bb63ac87268ab4ca8eb11e84d8f26b09346ac7184ed3ae39d04c07c0d13c160f65450ce7b5b7a436283b365132da74c7184e921dae8496b8e812ce806d39e727b34fc97df540a491cf7d75e703b5082037f654bd228dfcfa2a2772f0ad4598fa218a89ba1369e6e720514cecfad36325a143fcfab6b7b9e932ce1d3b2037f68ded534a29650f154cb0e3cfeee6c89e9cf41cb30a111a63cbd1287bb06082fc9cb7f54498fad963f9e895ee0b8b6cee5784cc223bab5ef5721cf7e1baf5ececfadebd7063e7fa5bcf6f48644fe54484a95f3b203736f7a95e11a6be0c2868faea86e4b0a4ea9583e3ad67d7bf4666b6cb1ed9043b7edd893437f68d19300d0b18138ce9eeb8bb4b9fb12655a5eae452c0c12bab4c02deaa49cee2e1645f1eb2ea64cbf80bbd6d1514e88b7373ae4e052a4a6aaa5a134eba459787b5d65aae365d9cbae4f76b93c57199baefdb1cdb74ff5aebb3ef5f6b81f6fd6b83f6fd6b6d68ad52689bb6911dac92cb684e5ca62e3d6172994e069be432614db2d65a5b6d5191b5d6da1a46a124aed70fcc6524101f970979586badf5ee86ab4bf5098c890aa19bf38209c624a59430261813ce4f52558a34d64984394294b14988e07dab94b4ef47abe4e0fdd02aedfb35c44083552262957ec04388c6981d9418aba6d6353756298845020e58562eab6a8355b248f828fb2a7b018b3878df06073d09d4c063c65b36c4dc9d193f709143f9a4b388fd30cee20c0797a91bc879406c81b148a4e1b1bd85edfe2bc4d62a04400ccef2973afe433208f81213e3d196d2062273f52c380b7f828b2edc9c534a39e5872cf187484ae58c6134e79c73ce5982d99e4f24ca39e78c2f83c04517e9b1b61074c8d23284971897cdb9471da42b436a6da508d177018b68e1b0a1cf23c9a63f23b3a1319fa64d636c1a2381934de957e94d6cfa3aba931838db1f6fa7bea2abed9556efb4bb9d77cbe668a4d2e9a4f7341fb569e744a398be4e0b7d5f267bc8edfe12efb0babeba66eba29ea09898d0383ac7351297d13b9ae78bd8aaae885a8a3400d8aea49a20b6f0647955411d7296bfee99adfae32c7f07726dd7afedef55a8ba1c4c525f5de8b0166d7fd412ea098ac965e4761a1a5bde97f288beed822060eb82705d90dc054a2cd9f627ca75280c4561140885d2e17490f80e154a6ae2f94112b2dc8f0c546bc24938399cce9b0e122aa4c3711ce7511805a241da4c9a4d94c7da9f259ed9349366114e8ea669da57431445793be28cb4e6969a388ef394d49ff5396edab8a784bea41f33836262ba17ea8ec1f40748bdf4c7723bf20a23d0db0c9a2d39342497182151a11e1da4d993a4a963776aa532e85cd775a2b4f484c92525429a49bc63d32411278776ec8842888bb3453fdc61db21a7e764d780fa710725f30b71d8e980c4ec3967984f43006c58d8f28b9860da37f2674743006cea4baf6508fabe0edb3ee791fa29bcc0965ffde2648514d83fdf7aa17c6d7f61620743ed3dcea8757e2b42eab65f31176d38ebc927d2a5c44b3c4dd271ac74ed7b847dfd0b8dd8aa237176f070989fecb19f0e09b036f4f3fb2d41d0a1ec41d2838383452e63af953c91c6ed8e9438c258230ecac051c2271ac16187d9aab375024f9575dfc600847d91d8f7579149bee46b3557ab1d2e85f1b892bc54ba192cf7cec3dd689f10eb3a5c92ca286508052767c60e1b8834323205a979c91f09731e7fe2421234446464748434537222e453f4235b923484ce12edb128ae93ea6eba4f884cd2a825414243442724495caf1f5837048e37e1e4e4a219423f8a6a66cc0001525151d1e65af20a23d03fa7abc5fa4c32a7aba5654e99cb5c6973bd09c7ee40611d4f761dcea70d4a4489dc5d4d2e13cad7be46f408a932c9d7937defeb8aa254c95b74e9c97dcae43238272f7a3e7838ebfa4c325ff7c59544e727692a2db97f29cebe1f3a0fed813992cf809008f914fdc8928ab664caf6f3b79229cc6542d0a7c795e4953f18e8b62b9a4371a80e454277b413a5a527791e19f94fbb130341cebe3f7b5c66c90c9a4273c8a7b561b5191e366cc49028d92884ec9c368a28843657afc5f354431447ddd26c4d9bfb9269b6e8ce64dd28883840ffc481fa2822f59ba80f4f96fdd3477ba6ce4cdd5ff1195222f75675f9cb7fa28079ab86cd7d0f92edfbbec4c8e6c6785efb76fbc658088c5ce69312519c268ce49214b2a913c5be4f673dfb56a07de913faf7e70eed8911a18216f6a57a473ae1e22a20b77c1943c14e0c05ae23702e1523942646588a3551a22226c44eec0339d5052e0e063530c2d5841d222cb1450d585c21050e149ed00029941fce774ae94b2a25a594522ae9a7ec139df6e6e05e9859b4f8359ea5a723890ef1dec06c6937b357769fb2f4b48391baca4154d6d20bb7a3ad836f9654762f6fed1e47a5b72224945bdb7245e5607c906e8e66233334567b929ff472d8f6eff8b9e440b78c2c3d1d27d02a2a54c5bf6df3208f4cdfa498d441153965d09ff91863aa684a6541b1b4becb9ca9ab598e80634e5bd40ee68e728faa4285c94a45e76471306bdb7c9608137fd6fa54a334ce981dffcec9e1eda4a51069f936c61823cb8e586b7c531a6f4f68f9f67d075eed185353521146e357e5941bcc39e704276bc6d8e230be78b2685db99691a58c2fff6d506a24ca36a52eefa0a57ac72c290b1256a7156acf2f2cb25d98829b6103280e4eac4aec7c71304eea39adb4ce3fb2679d72e7307398d33b202ddf76a794b2ac329eb5e2ab699ec706acb6593beafe4dcf5d9e6207630e36f49f2f37ddb26b809eac9843ddf202732bc1c07d6dc78e08c5c121229d4814b2b91f9e2cb9a7911c53a2b375f0afc93d8772d71d1a89a18068ebe03bff912da58c99db3afbc9bd92c3b17cc1f205cb17338c780181a0a50d275afeb3741967e7e69c1166a35acd338c78015ddede9badca8a1863d4e89cf4c374cb2c3fd0f257734eea59c5828596afe59c2c322d3fc739e3e9141ed971b2e2eb9310a594524a29a5944e4a299d94523a29a574ce39294551fd4a0d1b20c8020f1778cc08a0471e40cd0d8f17e82c02003d5abe0bddcddda144cb8f19410d2a60ba9b4a5978e96ee80b39c8a9ded497ae06152af410d2a9c002b3c841a98e2c6e280ca08e1a1838971bdfd32c62a0751661a3a5c60a4b0c326808a0341090a9f7e25bd5849b210f917d7eb4f65b6ea83c7a08a58132c1c4aac61434644800a531230b154a575950054c5a831cf4a742d31dd6a0a00f81965f2b902c2a059205550011fa024d53dd0da528591009e9db4f691c3e4d7280c4122d266859a28589162428a594524a29a5b4524a69a594d24a6bad75454f3a860a4b8b0e8dc50200b2532a83638656225c5c5afe0d23b4fc2f8996ef19a102fdba1b3ae3a94ca1e7bf0d4e0041365e68ba1a2c2dd4a061a1f131e50e1fdc0a0d1e2b3e68046000b412b19a594da93200193162a80a50536504a9062cbd359b5259e97619505aa90c9b266c6e6ac040467733096000aaaa896100d5ae3fa6ee6eeaf74e684a0d9087f003e5470fe8e46e8670435f7c78214509454c34d1d2440b095a5c2d4ac826746442cb39a554125dc26dce396bad95deadd65ae79c1e81a93285ae4f29e5acb510b0d2a9c52a34348aea67acd4c8f1b9e478d99137dd1c007600a03543937d14c026efc82a4b34fe1f794583704c7783bf178196df42777359e86eecf727d0f26192d0f25feacb57a1bba973055677337f6495296e729cad934d05bb9bcaea6ab840c70c904e470e1c375c3e2038881c4010eec6e7d998dfb285e4385b9bcd7ca941356847cbb7b6a54665b9a92b420ea0c110e30c18720c39ced6b5b11e41baaffd7888214f1972c691a6bed08001586774373353a1e7eb6aa36dd1353ffaf6392b6607abee661e6006ba528921432543569942d31f92552e3355dd0dfd5e092dbfa6d489a287e448439e35a4d013d5ddccf9311d90df31a7ee66561d1ab8d32694a3286584965aa80531acd0f143bc658f4ce744b25ef8e09e40079c7346b88f020ed6b86350908e1f69e28e1f5bf2776a487ba1cc456c70b0765df71971b052207fa4f12dadb5359c748eeb3eb9e460954a0ed69ece23e2a0f46f888312c791f84e17838a2c2b71abd387baebbe107fb2c9c11aafde30537d9741b576dda7443f11aa3f39733973b022d9408752a9be7c329d784b7bda062597b999c9416e6983b7d140d3fcf3c399ddb92b897ce5e774d65eeafddb6ca07d895da7e3cc449ab8ab3f97ea3847c95cd77dd77d21f7feb98b9e9e9d6bdbcdf9b7cd431dd91cca738ceee6d4458f739e5684ecee0b79d8dd77d67efeee67ec679bbbaeebe8eeba9bce717e7c44b3f6bbb747b63ba5f453f1f09a49b4cac12a1f0718daaf75d78a87731d47123f9524beeb788ee744ad42e1406bbf525f3b48f1fb87bfec20a55f68644a29e46eacfd50a71e1ed9f27aa1f4aff41dbb287d8ed24be9bd975afa0105edf8281c68f9403bbe0472b06e7fff4209dbf188266d10e8e36082e6fef5124df5d3dc3da59f75be397dadb5d6bb7d67535dead36c3e79d2c7c198bd50f6ec882fbe1feddfeb4defc8d638cba1a0607cdff7bdb7d6effe3efd48eb77b5bbf7e2bbd5f0c8df7bef7dbd51f7f5be8f9233fca87bfad8e1231b9fb6b7db775bde3e99a4eaf0bd584aef37e4e9cada7a72ae049c8defc32d8e2d74b8daf637cbbbb80cc6f75b7921eefbad9cfef4ad70859451e29718cb9eb86326125474d9b0d0f86740f0c8b618a3260a7f617efb9eedf48dbf90878ddf3eeede76bf3db6dd6f9db5d8dac7187f8d676cef758c31fe564e7f037ebd4ff8f5c67fb2569e3c6aefdb7bf1b5f50ace7dc8731c89138de009518489bf63c02a07238d94b11ea21d63470ced10b5e547263bc67a7276ec6e861889b38de7fc7bb59f39a7f5e2be30fc55fba9bd640b13bb1c9cf3e7e3393deda76af5bee6cdc79ead80bcd2e785b8637c975981a14a58d0cb27f289ac27c041b9f40de85fe92fccef3fbba8e5e97d9fbe900779c37cbfbbdde9f4d1c1d317d2ddbd91977da29b76372794ea3eea31c6dbf6f4b79f5db71dd9d9a2de51df79210f2ebcfd6ccbcd15d4df70fafba7bf8f0ae36c6fbbfbec853aecfc288f7a973befc8ce5a0a640b38c9b6475cdbfa448b0557b71f1cd9d593d54eb733ba944be060423cb283b361ae1d23481db433d0d56c6da0fb55e7610f80fd4223263b1b77371ae3da4507a390bd298e2ef44ffadb57fb11d9e14a7dfb746f6fe4655bbabdbbb1983e7ecdc3dd8df6d6da7f216efbade4bfc1bedef95be10abd22446ffb3ac67a64fbc8ae5e98779dededa917caec51ee63778f6cee53a540b6909264f3e0da58be1cb4de911dbb58bdb85d1661e2ef99d9dac00a563eee99c98a310ec607676b03d2067cdb51fbf67b7170d6393f74d9f3231107e3e76f88833f6bfd6288350ec677428d83aa1b96693f6c9171dafeb6077e7f8f32b76ecd05da000a10c1f93f658de1039d7d0c6056b1e7c7ce4784993ef67c95b7b49e5d399d5891cfda333ab82283a2684719144f84c8307a40d6a6b287b6e34f28e87853354051745335b800bb4c08830463cf8f02a15376ac8289297c905e3f2271f0ca1c97380ede40dbafaf7d3b4cd69ddd8fe131e343076ff59375eb87ac6dbdad7d38a3b5bd7dbd691e66d1c0824d9c2cfcc9393d1593943b78abc68ed4913bd227268225547a4c9ed135415e5ea4907b3d022668062c100289165e3045271029018923946cb1440de2e3a8c5174a3fb8c0484289255828725085d10e0c063fd8c48f353448a9e7b5f1c4db46cb4d0f67b79faf9e1e643f5bcfc619ac67bb92fd2212461dc78d9c0867ba11936cdbb736c99c4bafe9f2e4bef5a346dfa9ad910a6923b4f4b494a0c5a72589121c718314162f54622ab118a30e547642584b5df7deabdd59ebbd358c42d74ae9811c2ee76ef55a77272a6724b5965626955a97a595c99c73566aa9a595894ea5505050eabca1cfa16c41bee311c64e0e4c5aedc5f71a1109392082dad780352f060467666666348c671c802311d6b41122105bdb3b2c76019bc1b40da5031b9773b77aadbbd329664b226410613c21a2544654f8564b5133b3a53dc89acd6276913881b87d928820b6c730b6e3e87faba573a788ffea255d71e653ce96c47a656172b6f396b008e3afd281961f4a9d4f3a954aa1a0a0681494f51d55137f8ed2bff7a6a8b88d834a13197ab67098ac6de31061fc517ed02a2b1931eecf2be4fefc94bb9ae171fb919146954263e512f90fdcc889600e9bf19ad2c1306f50a91ce6412ee4446ee43fb829bdc2e23a3bdee34ae2632d7a2bad717cc77bdce54a28d52819f7a37cc276b9e4ad6644238f91d12baafea9fe144064f93391359959e1b327008930fe33dbb92e6f3e2d3e4bb98228b6041741b12584105b42c6719e394ec671db0df7c140573a6722b097ed6678d595ce9948ce32996c3669d048a24163c64a45460cd50a058aa8525052daa24eddcd0a2eb2b282b19deeb90d179362443a75994bda8a34e93ecea3fd200115f1fcf0d49b648b2a109d49d279bc27c979a44c4a4a72c2398b2563b190787e7854484a42429a618c55d8ee6b4a2aa82093c966ce79d780faab4f4f96f5d8d1b0c3264e96fde493b95711c6beec5e486d1c61ec47a30d673d8ae3600e9c23745ab4ced62948c226eb255dd207a967cb106cebf98fc31c286640c7589fdba874ccd8cb6a142aa2010000410093160000200c0a074442b13449d244cfb50314000c719c54544c32954623390ea3408ac2200c52c600020821c818c0901aa2aa00cd69e9338a5aa6d79fd4f16dd6b0f552cfa21c49367b0edbf7e5d7a205ed0d8690ce98b1137e686f216a6fed0641da55a363dadb947e674325ea9d26e80e45e8390514c0d5adeb09a43a6e4a121976c196ee59f6043ad2e8085b8281d1e83321399a06932d7c187044e327189f045cf11a9b8cdb1a903ac0748330f42df03514a91be4b781d27eb0f07520ba653fcfc6b0f56408402957da40c8b703f2cac7e55c951cde72bffe37ae996294aeff3a7dd76a80b5f17fb9d29ee9dca6082807c403369fa2d6ab63c8a1850a10cdfdd09c3328acee62a5d97bb95848d89a05776cd5807b52008763e93e522eea0dad6ea81ed89b6d9980509b075f46a864255b6a782dc89217826bff178486da7e853613cbe40ceb75f33cd39ae3537f3f395b2b216b2ee006cdfb7d1822c282dc350f45b9faa140e46b5b18d64170b7983e53d92e35c4196c5712bafd8acb72508d3c6d66312da383b6e1b6ce858efcf3f811ee2677c56754df2926d076a97b2ce50161336af98216b829a04256e7ab1c1d3bb9aff223678297c0948b9db4a7541aa57d8a43204b1b599c4810171ec5179ec8d7f9c55ffea7d99d776e729adfc827728239cfccc8535439eca3bde3c0052419fbcb005ee04452c4e104c43ef7eb7b3d7782076e7035a4424a63ef346d0ea07f3cf99e81730a119c340faf15287623a36c83aa174e313269a4973e0d08b04d32e35943831a560a3a8d6e3d93d0a96f969c7035bab06c81d5929a03e86d4dd5b4003b977962b65abcaac3e3f51310541ef1c8b66c675161c507f50a4094a110a19be312570dfc585a8858961616ce8dfae95e766793650923259eeb8439747531c6a04e9a1a94b55400f8a94f2b7465d8eb9033cb132c3e0417ef7ec5c3eae7105ce8ad39a705d7578fea6559aa1ce598ce60a10be742f59f5c64069b50a982105bb28ec5579b110d3715200724042d3e7e0ca876eed994865d195d006870579d9be61d483fec06d255120eab0c18618ed6592c8b00af1b7da8981b0bf1b05e7141b6a22ce10240ea8b491e48c3694b05c49a7cb79c7964d4c68a1812da6e0a97ada6a082ace20d77b1d620b05a27884918c166abd2e9026975f5c94722045f4ca44067c95b022d977622bc3c62f2b3ff3b50fa3ff893d9dc100d4350c1cf12368ea2af17837ba7546de7161864af7f9aa8ecee5fb16f0e77b1bc86c2b199110252bbe42112e46681d211bd8531fa581d1304d85350d17c9a3c510e7ee14bad9b5781c99fc95ee53438fbccfcb58ac6022bb99954f409bb2209b6da25ab4b36d0db6081d102621f0e468a3e38232d15de398c70e4b169ba3276dfb114e416ade057e9f8b55e8fdb6bae0afde5052b2957d3dd0e726457132a19acdb027686fd8869ddbd8f467b18264431de943df1f38707b25408e0906e31811bd84b0ebdcf648381c9010e68919e9ff819ddca0d34f0fa59eefdfc792f342485369e833099613f82c1309331442f5a34bf73dca4d3b90d8df6f4b16b4330106959b75f7d1492f1bac454081ac4cda21ad2860c294e5b45009d4e660caac8b111d7ad7d0650ae99b5f984b41837147feaa0c288814e5df0f459f70d3d29c4ebb142a804dc751216895019f1002fe9860e0d3bc2bcf7ea092014b8c5d73b5e3951c87a4ee7131fcb70f02a33fb799f84ba6c5149ac300f0ccd358c476e01ba7cfc0ab9115a171b6ea9179c49218180de6c0d7a7a3f04fa426047a60442ad22f403fa8c268f83219f820e239740bc99b92ce8e558e5f1b32c0f155d1f829d40925ee4721335f3bca262d54205dc7c1a3415286807da756a0910c88e60ae1f61d455cf36e58adad33676dcc4220ecd2b71418cae861c1d751650f751849af044abd858d6c52e0df872b022af74941198ee2e0e613460eb4e43574d434cb5ccb2d79cd12e04b5de7e4892fd0c46dbb8380969899a32e497c1c1de610cf33ad68fc369dd424d708f5dec6841ed316a470a1b0c3b532f9211244f1984e081359cffd0ec78c602f241711619eb5ddafecee74eac116e2e764b95cead12547ec3619fa12adabce80e812241d161b8362288867a0f78893b6f0e7158f223001d36585204a77b5a4f12b2d843b423dd96ec3581c5928703e9fb67e20ed7cbcba304a92039cc0bc3043729dfa042fb8192f8ee66152305d26dd43d96cf077c6cc668b38fe96a600b9c7230763698c18ab688002af4e18917adf78b7d8747b940e32fd8e7eb6b3bdce0fefa2dd703ad1b0a9a9b003ca783118f34710656fc3747b07978d46d90a15cab1bf24c3e4fe019da162fbc0f34fe1e5009d2995e0787a65466ca92ed25ad582bb052691aaceb2e03704d50ab04c406e536894209df2a7387e2f705d4acefb9906625d8ae392add8fb9cda6549cd839848393874fd4cf56a829e4124a8307c1381cac580478574179c75830a662117106ca9232fa6e9ed743235062fb40a5245ad7ffdbe12c58befd80a0c130e7818b7e828b731692d0a03c1648fd29eb0c006509808c78a1703455d4ebe4ba3baf393cc2bc9d9eb0e6d5c75e1b6d751c846f7026f2312551869d5bb302a5708c333458335be3b2a77b9129ca2a03b52efdff05c135defa3336a22f1c25049a2cd78c0e87ed2d9bac43c79615abc0d97b493645db7852cfb9501e4f3c2a3e56651d1ca21a8af016c6a4543223d446f4d7b46432681a2f433a4a04b95040c79886c329479f6ca064c4a26c3d5c5885624b56567df0ce1110dc1f5a88ac461f0440f7ea8bd6b80f68668984a0c46dad9fa1cd5828f6c3684dcce1e62fe402230b1a46538b4be53bd44307cdff8c51e3f7aeab088b4dbbb74b0a830b7258a784a680a0768faa1e178b7801d99fcd786b7efad645f4f8f3dba63a9afa978f534e012d3af5f16ab3161ba6883992f8d90ca1ef8a7336a8eaa8ee34e5e1025e11886a34e57cc9808e2678cfb922e27200f9ac981171ef5bb4e0176dcb74677ab6fbb0ed9645cb3d9d1ecaa4915dda345f920f505a621f023bc7b43513cd75ef71eed5ddcc356386e44a85cd24f0eff173ae07b31d07d11664a77abc821c9cf74de711fb43862d6f9147403d6b331cef9620d050b25e2ed8ff4c9a54d77b352c183d2b680835b7e711488c32615763ea45c90c440851a8d96726b51e5f46f8d9829ae5438d63c11969060c328c1f7f8e4da7e7cac67c3d1558ef02e13dc25aa62654a1f06a9a46418b81c5535864d3b40d413fee6028ff39f755e84821ef573a0e8e115d4172097bfb38757291e8567a21e41126b96d64043247db9587864e55535eccce92eb95145774efe018bc303134d1d9458d49838d6c5acb5617d5522566ace2591c5d412e05c538924a83b551722bcfa5dfe87ebfb0e046e7a4f55ad338545afdde3714be82d508ebc889b6033c5e01f886e094153b925ee80197cae6dce8cd0481d5447a5a5ef51f3d90aca88be3c8ccf9a43565c45a217367e8261f63512869dbcf8b20cc664851b714e4117f02209462ae2865e228c42e40c651c4510f63231042494750c04b6b6fa57c262e823063318c1ec0213a2e3a4bfc43f02ddb419041183589b91d15e1a5f63b0aff46d9e1c042bc254055241fc7472b71bde7f57de8ec9f481d5846b2ad2fd71584c4af125a92236b6f3e30108a63c6675df3505ce59c8b2adefc4e19bf67171487cc448d12bde53153f54a1c3f56dae769616cb3b06cb107adf0663a9b885a6b2ad1e8442701b5bac8ad7dea7840cce6b03b62d066cfee2a4087db4cb03f72ab3c553c2f5854e1b3be89358b6ddbdd4f8d972aab73eb6f06c74103e2eadcadad896b438c5c02ed78a38ae21af4aa72d9d82ea82456ea788314fe58709931430c2fda313bb7fca61a00f6d9c7ca1c844657c6231c9f270e70b1a755e600605b7ee6f8610a3ccd5183798b195b7300a799369b0cb4d9bb3ab621186bf6f350d83721dad31767276842ab9a91088f940dca6e0c73c90df758c2681f92e04069e4f6738b36da4180b72cf1a161f7d7b9c04b4d66662f41c5f98bbfe8d51d2edb0fd8f7501b67f6469211c7774acc9542a5830af7a027ee1d2814a6859d744e18cdbe744d2bb3a6ceadf099d1f674ef40155c841db89624d20dd4b5f6c17ff61466c58acc5b31b5a18238de8a345110cd866ab2bed9fbc43255a34d754173e101454d4957ee141e121cd53ddbeea801197ed5437a5b6ecb985c7c775968078ed9a7cabdf0ddfdcce4bcdf76e26b8aa8a74f5bd6544fd3668fca72fd782ecf31e2997301168d22fa3ccf733a268f3d30934bcbc8ae9629942a0b7e90c9241b0cf9801c019864434da3130b8a748fdc14a433c74c8567ebbb646fae312bd4fe46cfab813da5468384fcf0f80ed5fde580041b3335ac3804bef65706a2dbaea756a2bf163505c81814d0eca79829db2c180cf9935d4db4407d8c7817db7d1a3d3637d29de04ec15e8a92bd832ffd2cc407afded31b2e052e19ac442b61628f649d6066e5ad0f050ddea4e825de08b0a2a22721d8e3c7755723cc3f398b7849321536d91b16e70ee12c5464b9917dbed3b9f1bf5b64ef1cabb9f6f528f31034588bab2c108678e513c70df5b021cdb72a248e652eaa8521d19c1607ecc0d7a3c20c456331c08e94d0ff1e42a65a7acadb599612f8f5e89e3166e01d1de20a32f6188c83351c0bc869e54ab03f9acb0ef65982a381895c25bbab5b10bbb0d775ba1c843fe93f6a26234f9d0c76e2f68ed093330610526cada863aa7be9a132a6ef90baa7e2db6954a53419f6028c12a926039cfa2d7ccfd1822d4533e804233eaa7f7a928af16f069ca44f9421eafa2dd8ae10bad0395201f2e1d8e80242a73eb60dc9c761a8a3038e1727ed89e449de7ac3e85bf09f737255aaa20f9562fda9bdf9110d326bec453c1daf45c05164e2d6efaa5b9e7b92da66e2c8e46da240e1472bb0d0c66869f6a2d1688f773191ac40fd1d4dd74f36fc54e1648ce0b2443646ae2e01e0514dc9c46666a9391255f380c2c62cb188c701e721bbca3233b7c06786df15c7552ce2ad0d14399ef63d9dd2d00fb02926333204948ac4dac5f4dff9edadb6dca323951bc55b4554c39fdbf7404d6906f02e16f405ac3e7c0e8f38f06e857946722c3bac4fdd0f292735c7cd7e3478e922bfb5aece292d262da5d364408d4c4b875d8f827dd596f181fce5e1783e24c0dc7658c6be8f0db914cbf87d9230fe1210ca65df3e4e549873a66c8750b73f094a2829793ea7726d40a40bbc02e137837b98e2aab9d1287a53c3bf6845edd3b555a3b3698fb1df797c4cc1d50d7636cb6a04e17b74d38818219fdab9395b106700ac20f281f8eb03c75f8ee7649558ec4e2be50222e66ff4a62a96cb3277f68872cf426da1435324561d137e0d7c2131c2e85af211d0efef46e3fca26110131f82be61543da8ee01f4aac8a4b17b152381d1b3325a61f30c825ddfb0139a27a48240ae3df4be609d497785dd5acc3a78face49006ca6a9b18a10d5ac9bb0fb91bbef1f806617a6224689c2122f358039aae4b48fe8f0aadeff97890e2e72c4c1bc1e5a1e9774aaf6f0aaca61becd5411e3f256aee9c4b25658bb9fd90fb1f1ba3e2d61f9d210d890789b825435c66b44af23f9593a0f6512ef8fc5e01e933ec09789c1e0e5d72bf353ea354323136acbd32d1c630699b0b6231e937552d4c4438e0c644a7b150be128f7e0b9bf6de142d1646d6c1f0a8bee0ac41cd3d66d5e4a089d07b021e46802d4634bc36c6f54905fb00bce023eea933e56ef879c1c51734415af25446bb60920faed93ebb5d2aa2eeeb7bf7dfa36f42f0443b044c86238696996f1e518107f3c73d6988fafa11bd70c6001a4e85af40c90e04cc0f8512414142d670478d7f05f0bbe89e176f91ff6ba82e8f801ef568b14eaead536b7f29b60e9a3ca2231c120dabd0907033c9b5c5c3fbcb1919983b5c113f0c19f45d3cde6965fa86abf89e2f630c03603e7612609ab214888d5eccf9a51623bac8bc59ba13bd6da111e4fae43b3304856dacd554198099f6cb63b9e9dcc993dbc36e273296745a55b564ae47b593ba523dbfb2ca2626fb19bbb29a3c4079bb26fb8ddcab429a9361c9942f8a35520d558fe515a760414bcceed3b28a67e095a1d4a025893a3461f90f6c8632820ad183c9b5ddbb65d4d26a1a41cca55ef0d7384cdaa378625877e89660e4be64609337f8c0aa26e6bb2bae16ee6fda462a7ac0b3b7b396856de1c92b68bc0da87e16071a6778cd07c26271badb86d32a2fd7e52d88243b2bf133e8412c2ee327201df9e65af06ade8b744844f7d7a4f0d9d36238a945284acbd0479bf780653bdb09e9a9777541fd1b8d62b8d6daa81574a5a8b31f1137443cc76b0240fea8fbda8319039a03d900590d14685a908e816d2a418e187bdb901cc940cf9b904b072293c9cfa25d659c7632ef2ba790af144cc25cc814ca52f074c62b64ffd722826d3f23c92dbb66e495d95b589da069656c5491691868df491bddcefe0efae208cb7eabca97bbe24f74ecd4a7d2396800713f73cc263a583c01b3a9b5a83b3b082ea6f9fc11b4540a8c6947868cdbaf7758dadf33e234fc1a3395f4cf68ac2e51ba58828ad9dac00e44860e021ad9f1be3028b5684c9be401926f7bdbbd2b8f09277c62b5feae5777caae6d4077eaa3d0875ac61caa15e41a656e89883600899fc58ae720447fe0caa982e748b422fe3d088ac0e020e1c0539fb0bd0036c9a7c2010dcb21d43eed1a4b49e40d0b5d9852054b0c83772ba5c5be0c739b0f065f0302fc92501f857219324bf58032c10bf7e404b24751178332b236807fef16070ff148275281cf9ffcf601968090fa9f24a57c433a4d93140d5adc0edd39d41c3e5743657990ec29209e5c9175427b5f7536cacfca3e26fd3eb413a009c53899ccc4b88ed3e8833e86cc3f21355ceb36a034989f5bdfb84cffcbb14067d82d443a67ba4bf198a18ef07417bc6d6f77b1838331508affe56d4d688fce1207e0b5e23c68425cf7b154bbda9aad03e54d3165b05c09dca9b44609ee1120e41f961453f55818668666c5797a988f06ebd0ca2452310ef4544e00166d8383e4205cdfb5366b6fc871d39e53947cd03ab69ea508a83609eb7ed1e51eb36baa1e686ed504e540af1daa0bf7107f156104e6e598c035b12755031008121eea2e541696270c501555068e3f03f36494dcacc269cade7a023f2f092e6248280b924048c305e68c427d4539f0b3a210f8f0216098d412b994e4eb7f4a4975d337960b034062594f0e17289c6bcd3f33ac5b80af61307941ac5137ae1cd7ed253e9d6a07c902ae40d866e4d19d42e970776bb05a441ed0879d437e5ffdac3e3566f71d5538b009a0e4a65c32feaf8770c4129585c3a3b90d88b442c8b5b865be4ed2e15263e14d8456c6caceb5b822e03e94567db0091127859887d44257536abac44555f2daa336f7d6aca120715c1fa5234b2e490a92ed73efb122647f0375d436cda0ad23983a6d87271aae12f1d2fa34a358f058f444e42263eecc5c0b3fe6852aecb7bc80ec3e481415dde847e916747f242eebdc7c2c4d6e5ccf03e05a1d3269ac214168044c82590312840a7db7370120c944d0d1056401d46fabac1076bc30e0198a74741a2f286a9d116b394c89767a70d6e23049fa058cbe43433a400cca9ba575b404919d508c7938de5f0fae290d850c441d1a8f623d9d5f0ceaf2ed6e5b493d1f815de28fd699d1425c87771f7ef1e2938275c85d37ee0429647fa78e7b7ed76204ccc20a29454527c81e441e3bc317a5f3fea70c44c639acbb99610181e1335d2beb87f8a5198594315dd9de35eb4a2b6766c60b8ba0988b7986ca178c5a0eee99b61c35aa2c9322ef8951c98249568f644d430c7ce8985e37ae8f9e8a6b0e8fdd3f1584c45fbf7ec8404a2a42eec073d475184a77fa91079cd1cff53eb736ad137453a15c4894e672acea0d4fd922e96a6ff62add64ca6a42be299e1dac2583303cdd713e9d3f521bc2722c505bac721d9e094490211abd9f101af2a681b49d3fbb6a6fb0502956a41173b60f8ee701645008eff501ce0ec3ed5f5b5e86d98513df66bae7ad8101493f32136705a8a78aa82be5c2ba50f3f25d63208e616b81986d8fd22b79be0182cc331796b84a1500f2c343d4843ea709196fe30083a8592212ecc20e768c4ceef4488c63fb80cb6d7677d5f1a3d1a12e50e589e5229c75f1f0624d31e183da7a6877107444f1c3b032f72bb8109f058010583c27f5306c13b5ec2d088eec3c83f5b52601994578a66f59b91eb3782f1325e7d29cc60fc09bfd48a5d9df2eb887c047285f261094844ee5021a732c38c2e45701b77ec9624f98b4053b8c48c57aa7b1e67e9d922c6226f5f149188fe4aebd6a6507405e98b1602457c8d915a205196f15f7240fb6fa983788e60249e014e9254d47f0322f54f9f01decc0b5649e5a544a5f893b643d749659eda44cf264d55ddec094cb49200e27d25d22c5220f093e1d61e95d863542410388a4aaefa4038ebef70e264916018a3f08d445cbc712ed212855e493302ee1e10e50cd65ee14c2e4972d3a07abd9fb7fd7a405c24b8e72a2a32457ccb45b4b792bb6d0f4b0f4358c245daf42cfbba70b563205e46a50049fa5d8abe07e95634cd02bd25616cd6610428181c29781e1e85985ea1984898f1fff0e974101bd2727f4d9715ceb3e950f7138a56d01cad3e7eecddc1eb5e5f89bf3227803bb645b91fd35872f8a510f7584d2229fa858cf4d3ef47ebd06eb267dde3b42db0cbfbde2f96d75728d6d31e7b40647ef23d76522a0bf3fb4e61483bb679b6e7ace0a03b1e807031f5c00c172d0f80fa05e8c4fc013417fa2dcef85cd085507fe0e2fc0caae40662647a18f9ae44c939fbfd7a1cf9e240727b1a715f63087a080ed047d75470ec09c7e2e8fedc92a271c99cf40325e151a167fa73675983ee543cc9064e0fa3025cd51b7430c01c2c6e1200aaea6439e8dacc895a9603cdd3d115f7ec21d4da5348ad83d9e0eaf2e9dc09f8d1a2900a7a72cae0b90464250fbadcf0ff8a5f2925b0df2d3e2a0f3272992bcd010b68f0508100985d6e1f2a782f0c7384e1ab837198ab6dbaa06c33d55e68027d99242b4738c3990a8787b2ec3222c33aac03209c128c1d87cc3fa0f7fd9d59762fc0961d79bd22754b5887b4ff1cc374dce596288d19ffc66cfb78d8815d6cf3a098eeacdaed5a104624ef2e1848851b1593eca8214970895dbdcda8fe64cbd261dc2404f02f098504f242805e4b502cb43c5a9da64f4e9805c1608af039e6300e360e3e784e6917afe6b5250f93f6e487507838e32bc5012e6709d1a26224177678eeb65e3f72b3b0e512ec434d1aee745276460f6cb59a31c52400bae5fa9221b70539cedb6f3486b95ebcd2b4e02015da525334a2b590640f51749fe5dd344bb351342de0109705e350ab9ea63a8d72a6540db74e828de1fbc615d848b6a7bdc5f73720f47d0780d73ace3fafe62c4e1a2e21135c41b424a71e640c8f6627741bcacc24d155cb8fe395ba370be0c2bd2977f89cefb6fbdab2117eb0282a4092f37918cfbc7befb69fdca4107e45ce23e98be721e54a1b82b843aaef0523d3b5111c1a3ed6206551c6a2319c28ec1fe47e80999c1ab4c311009780945a7e26485a410b093b844d3c4c99e9d4bd6c6aca9f5e3a76f1db9c500cb552b0bd06134e245f71d77779edc44710d75fd2e767a87e4122a75a3702bffc98044affd72d2b9c95264806a2c6e1212a45c82e0db1aa6a4b8fae8265e2c0acac7e817b892d3bf6b04e30336037e0c5bb5db1144c1364e561ad21e46a62dda0cfd9cfeac2996adb09782a15dd58331f1a9d94ad3510b2f6e7b08c3ae680a18a0c4491598b0cc96f612748468732a939218545461443a2fe90a0afc9619c740e88bd01a735c9309344eb26435fb66ebe3cc786734c55b98436b0dc57d769ea0bf3b456b2b9692ea5c03b4a13ac97a0fa8b26763eaf0fe90b41db9e600cb62e6e372c91ab2251c5448dfc1412487a1307a585d90b440c03e71e420c29c9b7d2221b3de2f7dcd24e40557d4168a164656fda2154b07847f1812e611b8ee22937759ab224d1600dd4e3920d7422a27f517b62882d1e126b86e95fdd73edb17765406c49d1516f4c41490da54db501889297f803ec4a1ce0a3d42b64e4f9462ea735156f901d368392e38ab65175bf48c7d24a533875196cc44694f5c6f65bb24104f99e72a955fc65079eaa444e2dbcce5a5f4e988524055506d1c9a280ebef687ccf0b5caa168103daada913d234c8b42dd922400663e5c5ab9f58e6a302d204b5a2734c108d0334e78c2737cb5ee1e31caa35d1ddd5718ee6c6b3b81f91a280ac874da092d61fc4d7c3529ab5fefba6e47dcdbff421d5612bc08386b1a2c59271df5988723c89563d274f99241e26f53b65e48bcd32dc98a5591f4770dbb2dc3d2a5c70b47914f8d442ca5595dbc157b4fd7c7531d130e36d809c112ab75f899c83034a2d9c44401a7ec9599e17bcf93cb799370c4763e979a00290c3d85687792e8d61a6eb7d95d10eed9a41c01c3068ae99b352040f2eb6cc905aa645a84f8cf15461b0f25f886fb38a0ba10317478a84405d05dd19eb802361e1991e67a6d579558fbe08a54ae4e8ce8277e5fa867dd7caabb5b9eb97f079bd2892940f417e571279a7d465153b0bc8edc0259c8a99b021dbcced6b844d15e5026ccd561ea065b02949e512f39a4621b420a0a771dfeb83a272741a9a155a32dab9b3c20dfe4a6b7cf902ad296500406f538c0803ac6e8b5aad26a367cf983d402a4cd6576ed5b21f222c525a2cb855a0016e4a5471e84fca686ff2d532a37d34993cf62f2a2a3fe1445649f78ed2b04514abc404a11633653ddf91ef73e1a02815b119b0d962c6ce2ab8a50efbb8be8282a2a5e036556193354071105eb636680db33d0907d8cd0f7839c17fadde5e48fe9067b7782316fa79e70531607b16713b0fd29de6758227b19a32b308080bfe996f8973b4e688231e84a214c4aa61e10125852e65d642e78f9c5505b66d448514520b5dbd9c0f2d55aa947a07ae92e661510455c35d28bcf1f052036602e4f37afe56aa8dc2d29127f664726363ce688d11cb21f1f6f52224b668a95d95a9432dcf667e801af2e936a15b7197596131e5dffd9ca571834f7dbbfbd9732a9f19efb1460c2b20b43d5e14c3eac2dfc1723c1f605a44739477084611b66d509ac3792f6fb14b0579bdce3b5851aad9e4d8fc3b7570adcbe737c30e55560806e8ac2d30ee14507a1e4a269259202828d40597b6879b3a87adff7ac61a95883cecac614f31f2a1521a385db92fe9840cd881c5edb11112b72732231df7820500c2a2619e9e2bd9791588acab390b032e59004d2d578b8802b389ff654b4748f753b80349a6dcebb86d5610d996c3579592d32a2d664318ae0e9c1dbb016b13aaaeb6ed4c044561167788b919028344c1ca6bd690d0cfd9050c168b60e0fdc3640ef66352b91d057f2654a6b656ab752eac400a75b7dd7efc4df88e4727b6f05bd3985005d45b2985f6608edd33df3934e7d163383c0a9122f1ea02026c90ddd35a73485f26a6ea44a95876121473d73e2778a73c5395a61a55bf0482b1eaaf9f0e288ff9090ea9f330f5deeb06299e4c69dab26bee1cd2338ec1dbeea93e733d184d9a6973e04661a2a2f289865b7265709af74625ea50e9838103018719b1ae06694318339ef0fbd1dace029dd7d99f218b7b2126d3d320cd60be2546798f4778c39f76b3086c9096245026e8656727886c91716d851e8c08f0580a42cf5180b3ba1a684614f77608eed3abdcb3d7b5ba56dd38b2448c6fb23769c597f354eea11ec6e772c8f3570af366a021b7e74f80c317b166a8b562ecd9ce055d76059d6bbae159c1b934d1ec4d0ff2b7d9599868b4a75a51dfaecdea0c752107fbdaaaf6b9de2cfe62c7dd5f37365d01f3ba83a0effc2a2878c7fce8980c9d0a42e9c163bc1cc763a878af28b84effd5527de65310461c9a6bc23a9645dc8574b11c4b6c892a7bcb60a92e3372fe347ea5eaf42201025d14837fbaaa6d631f0ac75b3e712e626777a7916360b9a1de7233ed4273ce9c042edc990e62cb64f9226412532448620a7eb3ff5748e15aeaf54a92e1889bed796fe6d696e036939940fdcc6986a86db42cdbea733079e8a1dc8bd575088c413b4250d1845776d94eb523c9947744d99697e915a2cf90de3dce424c82f4a6942fad3addb0d7b31a0f7bafe6547e0e3cf8e8034e569385012c9b0984b05b0eaf995e1d8eb587ec9ed6241d87d0086d6d61602ff892b52f48dbf8b7f51c707ea16e243afb05357b624904b912136d605a597ba783f3748450e15a8528d10913d10cb6cbeb3ceeba477beedec26e941272ae4cf5245abdcd68ffe97cc1a66cbc2f1c4c06b863940a2110e46d50e9e1d0ade8c382cb7f12f4ff90b432ec95e761a3c6179204d3cade8e456131eda208e43d4494a929e1276cd2900d4f844fd7cf4d27ccc4413ee73e8bd8dafc1778ab70d3ca920fbdaa0a9bd31ca91a5fde9a28282d9e1ff9ce05d88fc863364efd66c84969a149878ebeb5401f710d928acdffe4156e87fb7437cfdc3473f3d9df6a8247f0c6e42b7ce49c07f1afd9211f6a666bd839d777d333da646511c6c7644acc6fc2ee9608df2fb79b8d19b50e5830f589fafc83885df07c78fa20af9fa6d077f53e0c3e3991447799371e0275eaa1c9e149dfade3ecfa837d58fa084fd02ff84518e2a195ab53af22dbc0f8bbe9d36d95305eb560ea6b28cf714d369542a33921887642dd61afd4da2d768d3b153084a9d4f3a64c8876ce2b3ca38db0fd42448204af5b7b4f09a1a42ff5dbf1ff477ff181f7db441a2648c76e81533f65a5da971c06c49de182d6407988901d092bbf862d46551ea1050d12a061e381d15fb712d4cf6f04fda31fecc35b9f256c7c8c51b64beda1842bd8bce183148083b2917b5e183a7b20d70787ca5b356911626c4b944034effe5a362a6a67b2e9605672aa43a7141710cbc57a1e40b87d79e0d191fc6529325dac2787e59553db1e762fe7a8fca484f6c9a2ad2e1436ddf985a0084269336d41aa17072d6002daf0755a1ec45de225cc8d11b933ea2373e1a98a5ca854cb3f982f2bd3d1b4772602853079219a0675ffbd56359d9fdc0047218b99e9a614f75756307da837f4ffc9f5b36d64d77a7ca4e360cd43f51e2c47603ae2232ffe0bf3876793d1d5e3109107b3804b748ecd0323a4236f82c6f4e7c60df46af1d07185ed44b17a632b38aca4dde872856fb47e327031af4f5aceedabd23e79652b122940cb9b5f196b5e840ccc06f03b75cfc3ab34604bdc1196beded9159b8cd6b065de9a58a8e6d6d21c011fd812c8e3e356d7f5a59bd31dd37d066e32bc9da16e01cd11fc2d8ea9adade1c2750ca554b7115e08bc047018e7a220afaf7d52b79337e7c79617d33eff1c7388364376ce1dd31bbb97ee88bca47c525e5ed389b92d735b0481b4af2d44c15450307de9407c13616ee071064d824e93e6941e9882e6672c96f41e7487437c0cb7f55429924c63d424254a5ce7c4c1cf242b0dfb80d8390683e0efb830bc2750011c89a1bcc3246897e99d2e5329c9fa65b3ebef7e77c71dfdcb2c37fc8f77701df5bd09ef128c2ada451ab0ad4788d8f8e1392abc0bb114ff8ce216b760ac4f81b13f753bbe45a146e4b389e97259c405fda022e830e2addd972024817e01058f9b1cbfc18643c275e021ae3aa178601df30120048e1042ae4421c3ffbcbf6e824001d9e703b95c8c6c441047023526d12c4911963b1feed84c6e0a7f140fb5a53d04b00ed7cc4c3a51de465b423a04bfe100d37d76ae4b6ccf1af252f35bedd8125dd03f8c006a5c4663583bb1f4b564a06cf310842eaa2f6a9be896e7ab9a11f0d9fa65744786034897f36974e725fff825043af939123be066efedf62f78beb553230b582cf32f988df4e3465551dddff1c7920bafa185638c7c529480758b9d22df3fa26d2369d5c1aedf18109770f6f1d622115a8640cc6cf4befc3b6875772844f80ca1e3fc2eef97a391413eaaa948a1b3e398bfe0960c9279d89ef5b91c0c7c5ae21e9be2be005dc902247ea3f2dc22befb134c5c629d6fefa9fa6be17280288cf88ae3aabd5e0729de336200090d8b7e0b424ae1aca74105d449328bafd598882b377715308cca5937891852d1312d2a111bd2c49c2c100ee599358aefd631a7e14a5357f2f11759eed4bcb8ff5d03070720fa359700804428b7b9a982bfa19d1b04ec343197d93fa5ea9e319556c078a78c73976f708c8d9ad531b8e4d4e9ba19b724a14a4ee1d43a35f8a2763b03f7500a851cbbe47cb1b2808b286d7323558e376db39d9b159e6180f209797642596c4f87da561220d691a373ce1279dfb41df028a2334d42dca6648c951656d9d39d00988d541e42ca470fd3e9ae63d55266d4a22f693811fd3bb89e4b257b9a6a14ab761254157ea34fdfe0856968318ceaaffc6a83b2562fc449b074810268a2e7571ba820b43d3295ad918660ca55420999366071311a2e04a4a6564e90ffdb38420c5b36f8cb2e1402e04ec48b622f51da5ff3805f324d18572ef71276c4d3fff32d4384c5af9d65cf2401da3907540c500ae2127cac1f820376b4880bef42dac7c49c7676cba9e15138a934f4277fbca0794065c3ca8efd8ba1e051607205dbb8fabd9b5f9137a3ff3790bf83c96ffdb0e3843f5c76401001ee5925dcf1b1a40ae50914eef9be2f191c74a49cd16f31be1bc88dc673840dde814c6f7fd2c275abb9c0e584e0a5a28b12a88ff285a87bb12d1d42a50caf125cb5427fcf2144af83bdb4201555f8b7e66292dccfb98496bdcd4eea908a783c520357458a264c71353bc45eb97ec0c0db46f952c825918edcd9722615b0e6f336257921147177b42a76437652c25bc46899e198019302701caa812755ae2d0eeac049352e396efa3b68c777ef3049700bc7bd1a3869d93625689f7442a0f35d0eb294a9d491f217ce3bb99ef9e829862f0d326b4bf005f9e1207f770bf8fe2e17e38cecb6610461e08f5d401c6cb52fcf0707502e08874ecef44c9410f9510a3ac63d4c6a3688d28fb40c79fa16e26cc9fe706d34793cbdce3e970a765b875ec6c1eb4151fbdd4b905e3901e766a4ca451b9ae25134386031746532b60fd195c119c5f70d2883aa2356362a44d64dcdd30f18d73ffc54eec14e7ceebb23e008bbc1d452aa0be0c17a4ad5022d7cdafdec3f199409b6126722515169c5a5904d77cb33a396f923061ab465f118a07767adbc8a2facd7e2ee3d26c23f6f9fc584a137e31409a4a8107bd13c2d088871ade4bd43ebe57739063dc5d6da9c0ee8125110b74534c3e9b41d747350628b60204b838866f5e8c437ca5726ca2be63742a7e9537ea03b2574a883c2e292a9df95fc9711b0e7ea1aeee6e1a7df08dd7693e5e1bb56399bdabfbd8d2cf057754e729728545fb6394265d57c1e28c6eb14894187bc73e3bfe2dcec1c64936a0606fce9569e51a4d26fc4054ce8ad6c6a7addf0d0395d3bb69be5545b2711e4fd4cbe004531529975071230a193e49cfc2650dea2373c78afdb10df78958feddc1cdae41fe0365c1044b509dea3d3bf448ce8e87aa619f40a7e83721590dfb72847fd5618111a150a19e371db2fc7cc2ca38a2023d32a818d42468d1bb9266e78baf9660ee4c463992c3735fcb506387851c8c2327918e06fdf658cd9bec82f0e5c02c679b68e41d392ef2f964ddfd3980f792ab8e2bc2898737ee02d2243df3f312d008cdce2c1f894e4f5ece93ce375be7b7a197849bc21982e62534d8651a94f7057bab00be5b2e4c8e22217b062b8573c18a89a2ce01537ec4fd9619e96ce04089961dc381c294a6e71d700328655ea282ce183d55bda3e54fb2a9a9095fb02d8acae8404e93d3e13d67007ac5379bf4ee96448848831516aed7bc385051dd7b7810af0e451f03e4b5aa079d8bd9a79b3f4d7d728b3a7ff6e2779eafaf20acdbd5fd47afeab1b40501bf8be0818a338f22dc5d07ca44526225564fd53da70341edf3a956c4651986fb67acd7445a9c054b1f1857955b9d424f37f1f7afad66d7bc9926d384bb4b4f905b5175be811467a5dcbb6d622ff78fd40a06c1e64651fcda7eb33b4723a9e097da2ae1acac23cf07409f52644aa4ff73d41f5a5264239de8d7f8b4d9ab94ae901ce181b974c314f4d9c5b628f3907c66398828e102f91e7c1ba34ecf990d03b77a8f2ff845be7296b13858dfc4283e7e297c8d71615c0a52342867d5e2bbb848d918c8d9dcac7a5a044b5a6d88e95d04dc3c7851f4d814e5d7d8b590ff1bf8a667428b972281625e984722f51b46cc599133eed28ebb270d738c4b25aed6a6721dab1d8949bac4872691cb0567b3f24a7577a8aff4652e36645c3324fd18370971b319b5b52e5e438b5416d7d60f3686c6b4681ff0fd046f99ad40d8ebe67e8865df58813aa4f31e921d6f176d2e12ca35ce8274e3987e85bbc47a32d81739e65cc21a8012cd15a60566fd346595502e61d3a5a314328de94a23da99c77555503090ef69983ac22377aa06aae0eb8362ab314d08688354c2717528c65fb7821154596f60334eff25f9ae53f5712a5c532df9eb332b74e793ea8bb5901d9935217a13d985156a1858e482c3500270702a427f24a0e938798a65b71fe42475a20add0ea89582b2ffc774c65347f2b48c61eaf139e9dcad6b2050bfdd3b3575921d39d2ea284e79eaa1fd9fd4ce16b615cb2a89c337270c3789c206ec33be23265a943273c7d29d0d47cfdbe7d68f284478bcd6df7f56d193f703294b7551a2779f97dae46289f03591619abbf89dcba2e9d9dfdf44eef18ea16b177859932168e0561173e5ba0745c84d484eca94bbbf45c35059d26578c24a06996096e20198fa3bb11d0f9807a5534fe9d2e29aad9dcea8f3710f41edf827095ab78330a6da67b91e42290bd39709c2e98d42af58253ace424fbb0859b9477a8a3e553f1eac99dbc4fe85595eb04ebce10b123cfb5ee7180707b19235bd362a3503cb1823fa2909d54f7ef261939b77a1ba1528a3c2b65b9c212b73a396ce8a13d5c25c244a2558836140123bf69512ade38439a77b8690759fab032d78c823ce428a28f0f896e0baaa706dc8f4f88c9e5ea9dc4f7a0732851104129de860100111f46cf2322bd03f11f0e351062c8f9e2ace6fedda79ef682a2c6def336852e8d5b4546a87aa9041896f71ee866ebeb6bf49ad87026d0f1e833ac45ee7f17e5a43539d85c5ad6f0630a21ae7cfa0abd42821f54b35fc890a2ae8fc6cc88b68bf169895aa344a2ab0ed0bff7bd56fb00ba637698449bc7bc91cd047285ab531f25abdf223eb79cdbd6647aa2d087de6654e805df9b1322a3a8d23c3f033250e83f45e1bbffe75b10b89b1286fd41e020ea883a75a2901fec9a70a2adea9c043d3019906877c44a1182c9406ce071cada5961400bc1d12b8db941d723e44de3ba54718d37074016d3403b1f9c344faf8c061790057895602f6a80310a84c9cebf6af548a404ef1fa29f6a2d5175ee0aef45811dc5223edf5c2336efc031ad5d502bc9d027e2745a3d26a1eb38f57d7c9e912a9b327369336d21acdd7920fcc9c5565015f4173caa0ab465a2fc59983e01a5a4336c21255571bb96aaa031b0882bf7d052884a6f8a1f5b8b3bb52b7b8b7475d0ef669beb1f6185ce8fe9ef6188429d1b59dfd7d6162710275d3f90fc37be76c0a454a775a1051e0825d6754f5370caef43889eac33266456db911b71fa58f3451361aa9775e83cbb0cc3ea087199f242a69de57d46ee4af88480930abe1c19efe1751e61df31af850ed582d5ac0efa88ba40969bb5196e5d02cd6746248db2304f64d2f44503d8ac95235da6682f70f2645a48cb30eeb86f2c7c5780cae80d2dd5ee548c921012717b035ea8bf48cf63068523ba8b5bf461045d1950e7166b45b4ff377a75ea60537983996d81aedcb2c14e739a771330e4266bec98268c5a0ad6277bd5841367a85b5dc87980f9d66b0f6d5941bb9cb50e4e53f140e40e715fafc3ebc96b72752c174b8843ffe74dd7ef31d3b5b4475c7ec7caa7ca6c9a703005a9312d9e9e0c7e56485b7deb62c4d5b95ebdc2aa9180377f6e250f6cbf44e14b36e93884e0e656a72244fe62d956150ce40d5bd55733f7b56fa6a7985e82dd772fa35b6eb906854cb7f8a2c0e0bacfa26cb34bc62ce9a7fd30e2d3025e18e91356e44f7bcaeb445b03171451d20fe7b0f5947cefe330e3a7ce5a4e1e1206a8805380a1c70bfa24be270e1eaa1f6d87b0873cb444114e3fc93f4454e9246d8fba564e4fafd7f15df1e11aeac17a48de07d09bfbb37be0faaad1aa04199f7afe6d805d20912d66cdc72d6b3a55d644585fc5e1cf60438d7aed2e375a3c30d71de41bf6b4f0622f14df58e31d7deb9f2304e74f2daa7ffecc268955cb09248e3cf4481e547727883acf60c837e086571db306f400b3801757b70cc6beafe8ccbaf3513f1bb2a47f1cd8fbc70bd544d0b8233e56068a616d01393a30148bd255586b0ac81e8900a0a864a08b2737ff7d9f45e88280ccfb4c4e27c467b003eca5c8b4c8d39979b802e9cc010c547805ab28865491b441ff438061254441aa72fe6c95597f5b2d530641eab5f540a43ec8a69f8f3b69701cba050f9bdd513400311e41a95302910f7a9e3902eebd07d0479225554d4bb553394c4ac6f3c50ed8ea7da2060df87ed5c64afa8d6a52c7bff93a572640a5d81c4b12287b21e9b1f954663f96130901dfab3f489917d23cf56856de2cc0632a0c4a58f3b488042ad981ef071704dcf59c035485e2fac7a9896981ec5c5011f544137362065c2834d381ffc461903876fcfff7e18c029ef82d7fc3296025834e35791f58446d4eca28a3c393b605ff302379ca86fc81985b9ff90aed7fcda03ad8f64dfcdb78062906b6e95cb7774a67d740836816e52a07a84180d4b3507fd520d4dd81037253026b508867e2903fad087aff838af256a18bca2794412ac072a83d8f017dce713452d53d29f354da99ae5732045cf2b92a4834caa3a98f582f9e464389d6beb022ae5f07af6b2cc811685a5eda914b5e88e9c56c4419ddcf2f75467cfaf7002bcd2eca9d1fe40b12be924b570aad3af3c92abb795a42534b1d500c4bdad763d883728b425f22b622dccd03181ef5465af547dd6ed8712a52ae47700f24e028f897727f88a289322b41e608c4e979ef57f6b6eb95eb0f20ba372df785c1127dbe76c072275acb74d2c6bc2fd7fc24c8bb7988c198b6d05111374c6dae0e13ca21376ac272cac7bb0853909b5ca05210ecf69103f4aab791c43a8e5a9e839c81c5c9d120efb8f55b2f481a84c676714e1c0e5483419da10bda0f00ad3c1ca851407e3b2b1b1221196a2c957635ace2e4e2c7a14ba2d1ca2dbc6ebd9414867e081bcac7c293de267885addd91ac7bda382760958d80864059777628943ea376d2013717542932d4c52fc42ed4a94e642209586804adf523e8273d6d1f0868e00f8bfcbda98079cfeae4e32b02bd2b88513db639a9c5c0e3b62ac5f4b4cd6b4388ea2388ce3c823f96e89c1cb733896a0117d5437dfad2dfa671225388820dccf15a3447aca2b5a10990b8796d5dff429dac809a6a7b3db8ca9b9c833855e657b9819628a1bfac0538414af45a31ecdaa637c2e2741e4ef33b2931dc6fc852ab54e2c851cec6d43cb031d5c9d68031725e098120dd2b5f207be1f6570f2c5c55189e7a47be2aa488c65574fc6a4534b587d10c7f76456441b28ab48883da36d13758f1569404dbe2a5133dc292cc7cd27f0aeac16c1e5b72a5b4862ce274a72ef47b474cf4b50a7ac12eafdea3c793e9c54d9ec09b097cc002f38596715b514ebfb38b83eb436bfd046417ce8ca62fc6ac83d47d86922f1515a451b30b73a8ac4bae0165ac6734159ce85dad3156c36d33563d4b1b54ed8f046395cbe68cd50dd57a451ab0a9c77e133919a9571d5177ea6076d13f232290cd4603e08c832bc8925e8f470192bd20b2e0292e5f59a8be8b0e7378b0294f4fd2d03e1741487b6ba9f597051750f5f6623e974f7dd4aaebb6fe4ed5abf99c7a243d526d771ffd8809c6371487e2ddef0aa79fd648ece53c018cc90b92aa3c9941ec55fadf79f244bc63c0da2f0c63a0256cada37fe5b6d6a8d9ee901a998f958c11fa65eac72b60e79d9746bd07f866661221083e58829e125df4316224c831a7c3157c4aa2b79e9785061113b4bb267f303b2570d10c6403f1d7e5601cc9283a3de08cc84d5898fcc43f5360d0f8f92250673ee2f01477f28ad4046301b0abb40c203027b39a92cc2c93a5858c652968bc6c7743d15af6279d8e2801609e87deace0b48b572c219e97ddab140869474e77f0af00cd3ba4b34fe080bf83ecc26f0b34bf40c0e11051c94e30f27c077b0574e826b604380a35f7ab514f4259897752bf08d81e078758bfc33d7fdc3c940338b0bad80e0a3ab4cc01822b54012ce9980f7a899001ba3a30f97fa2f02b447bb60b46aa9213377f1a128b5f8dc194ed37e40638010e00b01d8c84b70483fe369c1e62c2ab0757ae09093fc8e0a90e5f9aaffbcc7895de1440c6ff9eb9cc7844320ee0563a1f55e0e0908b69a1ea233c4c7857c35b9d5ce43b10cf146d9bf46e0b3fa885f7aae425ee50a357720d268331bd133164258279d575293be81a56299117e9418d97a7a0ca0e87eda85edf6fdc61bcf6fa7ef529c06476eb0f622bd6fc25d729f54aa7aadb7339df8f324ee09568fea1bc3f8cef3b2df2af236253a0fc833d5d035a3a1a44f6d8d3bece4d8858691049dd9186765d66c9b970b58527e98911229a15441423300bbe07f73f6fab624185c001e03dff55c760559b607a448034656e13060e4cc25be84d92797fa1674c5e43da9c8cb790d95223a13647e27588cd3c53cae00049bc086d1359ef2ff4cec85bc88d92791332b3323ac4e6e4bc847c1b921de44ef6da5eb5908eeaeee0b7e3dd6e751ae048e5a27bd8f2029c3d2d071181abdb7ac3b800ac1cd0e99ec7f828106b3e429ef1d260c3e1b2c42bbb6186a6d211bfdc369ead797622053cf6babcb79d9c9e7cfa032c0881e0f6824c197008bbd1359170b9038a5668ba1a60957d4b05e3449adf5947a5569f211e84d003250d3a9331d2c4a90792371377f756ff2764398e44a3f9cd05ed9fed55439bf759009670c54395b66f9e6cda2fef7effad037dd1eb0b81a42457f1bf0ef399026a655b15a4a9a221d05a39258034079f76411c52d17f761399567d5df88df577d8faba1da5a1cf19f7d8fc8a3faca8019eb754c05781922bdee0db3f929c8d5bf28e9233de09441d651fd6699f04a74d648411bff43a5efffb5a4a8bc139461150d890e9f8439bc690e25062477c5d0552f320483ba62f54e18c04f734184baf3061fb6644689cc722391dc6f3214bbc30885731aaf6c1286d8c57f0ff0e0c1af9ae9a6dc0a329204a1d554a51ce279b4399834ef4c6b33948aca269967c9cd3d0c4d0e6d5a17722b3f6d6fc4c3b778ee1429d193510e42a722aa9bb77430fa35cbecf1c292f8dd1c714746b80985b912611b17daef08a0cc7f6455cfd8806d0ef14b51ab7876de3d10d75fb8f003b481306e5fccc5819388ab7a140e8b4f57a5d62647e4452ea455bc2829e655257b8130aa2440709b7087a4d8804ae28ebf65c3d8e6f26d735e8131e355650da6a8e62a8f83f4e3c0e6986bfe36eaa0d5e57d2a053b7efd808678f2774feb2a9ee10a51eab9bf8e6a0ee52abfd32a4395161aa419875c580faf24175acf6e65c2f57d50dc1572ba8089d8704ebdbb63fc161d9378de98887fdda991c20c97a49fc39829a01786cc89890d4e4f68c69ef8013fed5bab2296d3ca8dffe56b50bcb9456ac88e0ff5f05f4bb41f38a1695c2b2dfe02a445e2d15355263628b4f9ba4db23556dc895ffb5c8c9a4ef31b5ce939ae528663d9571bb18095738840ad17f171509fcb37d25e9e37b702d3e5902ddc8b6ece33bf031adc01db4c870e45cc0590e30c14a1803795c7327db5f21dd56b240aa9abc495b0dd5dabdfbeaa894358f4d89639c74453c07a2ab048da70c545266bba7adbe985c704d83d4ee837c1754f511eb4152f89daa32f24191876df881f4b8083f1b7dd241a9c510e88a22c23f15142b0589efeb60f911da823b6222afd291b0c5da51549dc14b3d75bfd89a89f93d5fca5cb540d3e443b66bd9b10df5caf20f25f859df323f9f5788ff6a28fdf5c77fb468f80fec5b0104683d48b4386009baa2d874400bf3bb0a68213c469fc0e0a16a2a9d8df2d06143991c71ee9c0a260a45bba57da9cb6116bae577c2d3dcaa5a88bbadea902cd6aa7215b056d54a1c6b5587b4d80f83b80b957074272873e411555fce5a10c16f58e401729f58feadfbcc24d214be42124edc1cdbf653ce78d90dcf26c8c9e08efa7c7b1223408145decb897ddf42c626f911a7ee1f4c73c9d06f5410cf8e47bed32404d611b36f21d1080329af20c8c28d51c7d1cb4bf57feac89ada5b699d1eda308e6e0bf3c05371422115849dc4043b928efd4c4b2410e3efff62f58d3a48826a9d4f6136259066a574e20ee1f34f01e75df845415eae05ddc7c2d26c75a7f043bf6183623e33747f91ce822cea094f97c7d951b885d31ab45275a2eb1115cf4fc5c7f58fd281d24fefa7e050a15bc24a50ddaf26c409ab0c9410d0d9ac5a7b7d74465fdeccd72460ec5203c9ac3ba166645e82aed775eb15e089f455d9cbfcf4ade7bc763b63270afb251e49553e28bd4bf78d689430ed6502eb9284243b0502f70ecfd69861462bfb1cc2ed6ef9c7272fd20e3004505e98164b5755fa90d43611698af1207bb20cceab5802804757a0fc0605dad7c0a33104ba5a14f0be8dc96f9eba448d37ac0524c14e369943eae25bcfefcf688e952817b67f7fcd470290efb2be09212c2fdd801ef01aa08995fb4008af9193e33799e1eb077018261cd2246720f1ef47891888eb210de0b0e01803bca6283e8030cbe8eb84ca069ac75ebdbdc1dee178255e605c6b02d9a945402e3b25c8324e180abeb2c8242d0b242fe22659c54463ce3cc1de06e957e999ec5b8a7549418faf68de82d9ce810c52f95a4d8eda5de1e0dd15a865890c3dcddc3df1a0b87ed0122d80cbeb1f1c6af063d5a0bcf183b35bfee72d752a8c1994f94dd4caab958e50082cd7e8d7c0dd98bd4dc9fd1956f90942676a24a628a02bfc4edfc0d87e11ec73d8c00d03e40845940c3492f991d60e95370d048ca34ad90c5c9653a88be73da3c5624031736cb13acbd19cdee4be963962f1358d74d847e09427b38d0f07dba1f1c51e1fa861778677a6e5810f4733a7dc603f1d7e1e30705de617ae418d9a67d10ebe40d25d7d80c7a17b49ec559a0940c8e8a1a1de19d0502f4d0d7f1da2a91eaf54586221a1f11ca734f287eebc6d847910dad715e05271e8dbd9d003fa7de6b35642334dcdac51e1f70c32f3296077d18cfbf6ab16d309e0f6a249712a3068a726d2fe4f6e7f61c2b9ce503a17ff0811fa0c01c64041815b0c4f4594aa5d4d2f45634c6d4c056ee032410a7f6e70ec57696fdc03eeef23b87026d335563e151728bb4701e0ca28501b53313a63c2d90c7f50a000c1cf2c51052baa9abcbac289fc932a49088cf85ceaa30c56aeadfa18e46871e4ae494915872a00240f78998d3cf11f489069664481b83065a987af4f70610d11f516582a0a05ba0e4ff56481ffd5548b6a2a0ab2a0fd230f3a73e8be518d901e4fe656905f699a4458cac2a3be3a66cdf187fe77ef6b46940d163035df27ab3f2e32cb59e0e0590766f67a084be104eec2b7d8521e9cf0b2d52841b6342e5f9d1433f7f7a602c825a8a151b8cefa1d6f5e5d03e6046a1c1a0490ec5f781b26a47c96405ec8c3552c0e5422c4f5d8275136b6631f57067a6d1b466fe29d0c80c001348d1267c3075c9dbc1a86989a93a0b0f98f59099414eb8ca0cb2ed8731c5b841caa808428b92855489bf79681d3267831d71ba0ec064942430e019808224058152f82609de0ef08401d0ad61d2356033c65dcb5601da3b02128c9c8d415ac0bb82c86a0fd62cc64cf869c947f82e5a0780748eeae33900713238c0c724c22ecc57e635d2a91af697d8f8d89699eed86a7dbdb82319e9fc6605db332d6bcbbb0ce79d18c0e6b38fceb56ffa63b302dd9bfba1610da64b38a906b7595b85197240f134951528eb0f49dec81867fa12aa18db7dd412afbf55b918833eb045c440f179fea187ea99a3230128dd4b2ae2dc91c82730acd0bfcc958eb92fc1077417cf93fbfcbf21553fe1ed602eb0a198fcc7c0ace1f369820ee2e181ad2af7782d7db99460526326baf8ebbc2fdab3d1262aa3b9be38f8db7700765f36999bf467d365cea677f9ea072490a1fe06808a6f7a2f19929cddcd11fe995fb30b83f15390b1645f9845b01ba1dd4f78ae99f9effeb31593d6681117431d27c10504dfcfb124eeca742ea9f80cb9a42c87564d61f5907d1574826b8068aea49c43252abe6b008e87115b035f646ecace671862b7203739c3d0977764b03e3eabcca4bfb01b68b8d19a8f0468e3538b3b6cc025080a8b01c344fdf25788c5c827a8ccaf7f8a11e9eef6f6f8675b0fb89be1e8b3f3bb4a7617d7ea1786cbd8f2a68a5527a2f1e9c193b09fc99d08415f4b329e3c9536600f3475a76b381e0b7a67c0645fdb7383852685b22eb7c05f4009f340ac5d119f265c82bbb2dcc3f65202149bd76c677991504e6d70678db18bca96bbb100bacbc7b85359b77af902f384d63349f17ce699ef52185d988111190e03780e058ae76ad0425a1813fa264fb6300c1711a10733240573da52628ce4f602fb08d1368fde0305333858cda043fa11d8a10af27343383acaa8ff2de7223f7b2a3624c0485c24450e007a77ebcc89060bc1d8ee4a0caf69acaf0f5b90e0a6af392d7dc7e2abb5514cef6e92d41d25745d5381598c3e274be49521804e2080af9467d9e3ce737456c46258d9ea7a539bbcedc8ca2894150251180953f2128b841b9f7b5800110e42a43630dfc5e73cbd5421e695f3d9ffeabdea3dba7f7d3ffa29ec38bcd9e3de83226382401c33e72d039a190bd74b5e052882d421c72962056be729e7b6a7f7fc037811b48780f7513f234d940e45e42be89cc78370db6a8d90326a632ab320b9c20959cd389eec888f957fcdb616ecfb9f792533d39f9c80c272ba563c8d8cee8bda9c2c088568e7b2e9aa3093b9641e88901ae86355417914492af30ebcc65349db9da08c71c05fb04ffdfb86445002907065309084e908c7917f27f9a7f431512d0cf7001c22982f696b265dc06af2da375f6d5631256fe963e0a6a8fe4c42735515e9fea50956616c4402deff87d67c87096c26034a9606c9ebaa9d1eebfffed140672ccc8f94a4e3139f43b53be086e6e8f829c8258090a9599649da0a4bf2509ad37f0ce6663cf35aa6da8522309e48ac1b2a5725958235697239160c5364eac465ceb44cf86353d3c7071ac9806858b09d8301dbbc60a298f28ed208fe390a916edcd3d550489a215ef3511fdd728a3207c473c70cb06895e9aa0155f9b24f09f3b3c88d62dbe8804fc54626c121f3e11612ac1da37d71fcefdcbba4fac410f446e363071a43453e6c126a8d0f796a249d386109c657f1b0eef03e0c19c4f08f1b155129270cc22db4766ff82029e8c84e028c37af184748062c2b3a08d9a2792bb9db9d98210226f9f488cd8f74194e90103da8cbc9272e1ce3ac3e31be5141c9567cdfaedd67b08839f82702bd097b42e129a0400ed83365de83404fa5b5b0df3827823a69a511a50647da3609cd5b189ac5e878f63a788ef0a2828f2d15de8e23c18aa1f2cdeda87eb04ee7a5ba55894083a9f57d75066e4e958625c799a96e1145b706b3843357dbf281424e801ac2b7a3fb702751c470ea1f6b6d3448971d38bb6aec4ac50235a49d066c4ec62e1cb123a87c529a3fb24c3ce7200707419785d4a7772feb43182c9a20cc65c9433427bdb02bc79cde4fedbf5c0e3c376c1214e768aa1a88b8ac6d8ac2b001ed2b3ea9ae87ff5fa8512be34118d271712896620ea5586ecc89f56566ca23ebe17a1771de836fdb3b75b6bf179c2313080afa8ccc4ca544854ca070909f20b2b6a6672332a16787753c27c3297f0838f140c4ca9024ae261645f408eaaa20e122daa8a9bd225dbf65c76f5d92a9f97a93ba02b2f4cd18877fabada3ad465c6676b6436861dd6ea4fe83c6a0e59bb42f325cd098c8b589eb3997b554514cee77b55b983c142cac9522ea8993a39043142f6de7b6f29a54c5206d9051f067106988414aa0da5f1c75f71ec6bc458f4ac3f90ade23050497b81faf8e88e5ed29b0848b0453bd6d1a4c38e28930e930e14938e14930e19261db9aec60cfcd0be67660c8067665224a5d1c0afd0d709b4857f65d71528181be30c04a147f186d9051abf7d1452e071e3382a2dd06f430a7dd29d0d1568fc749bae14284ddd74741357dacbb3e1815930fc15077fd551e56c6cf7cbc2f07f5e9ec138b5f18b2b8c717654182275b98f3def1b534a58778cb1b51497a3bdeffbde8f6e9cb1d78fb2e0822822d16ea23edaa9fba9529a3384053c46fbfc361d72344487c597dbf7c56bab75e5b59db2b269caa6d5e6f4f4c99d6dafa0af6a63b5d8d77eb1ef45a234a7bf6f8ba0afaa03ad3d94a6acbd6abc655f3136270da02f0ea8adfb77c586947d52dca96db84f8a1bdd6b3517b328de10fc64e1836e6474da6b9d141dddb74942f8fbaac4d23c145ca6d6d785d15b10a4ed49277cf2b0e903535853322aa9352535253525b520fa4277cdbe76e88044a02ff76714a1b8fb9ed6e4bf69ba5c3f58e8b3ea6472cdc88d6a6ee4473e386e54fb013a79f8e4e1938771cd6b4846b6d3da04394ee6399d0be519d8132464cbc22867c2014da14f3ddd1f6cc407c707c707c727465fe87619126abb8cbedc5701a24f6b7ba1f0dfd3eae294d7c65bd71679eb7e4b8b8b0b0e1c315567db22ecf097b509edfb2b9b73f3be95c974543783077d56dd28c4eed01c55a8c2dcdb907dbfeaec0b858bb3b518478c8fa61fd48fdaeaa72cf3244772a36a6d68b536379784234b462e73dada762732479a511ad1fbc0501c73cea7ece193a9f2438bd6c6d6f25b99ad599bfdb1403ecb42c8be6e7324209b7d81d8d7ca56326d74df779446f4a4d369d49d401378f2bcfb387764c9f3b2c039b2546bcef5c8d291259f1bfa42c2104678b8ccd5a5c0050a3cdcbc5fdddef1347adeaafeb8792b90ca0b1ae847a8e6d0008509a2349f16f5fb7119201452e051dfab7ffac7260fdcbc1ec69508cab4d2e1e68de980bedc0fcb19170ea3ffa0561d8a185577630c407370e00f4073545d07270236077254bacfbbe948bffbfb82fbbc5688d294d50ab9696d90eed6146c6b6d36fa72f1cafca1ad909bf7fe495a1c7db9d6465fae4b0331f4696d1b638c31c618638c31c6d86485280dcea2e78d9e67f2bc93e7a13c4f7b1e8ae7a5784450efe2266a656d8e57b6bf5f0e0158dc9c4f4f8619d7f17d8fabd303a31d5f14c7f1effede041c7bfc1a0f3f18b1f8a6f7ffc610c49ff17fb47b9f3069c2b64ff799cb50264d00edfb4ef39acdddff9c24515d4fee856caacdf827a9f5fd150e774149a233dd1e8ffb283efafc5082c871f3d6cafa4226d7efa0c683ed4a4a6e633dc885ea4cb45f0e3a7f8d25334b7ef3bef879e54a2b5f72f3b2f8cadeac6c4ea519a93b2e5c690731b20bb0e90d8817b22a04c8be52f0968dbdd456a0efd39a24ffcdaa54dde6399790697b1de9d2e6f7d9b45014dafe5997be2e798e0ba9ba6f758d78383ab2ef7aab9a64e3311ab4abcde92091a585767171196fa7c896c7f5efae5bf49195a4ef0dc86e53bf774729955900902de8f6aad2f61fb9230fb2b5100a5db7a844c9c9b7b3462ccd09100ffaf4a0b33aa96f8d88d7be971e18e3388ea30c1a18d6ae6e6a8e89f5025d5daa97b6c7c8b6abd371a7ded983dcacef41dfb80ae1cb2a445665b35be25a636b929bd5899bd5839c04158923e45c08e73a23775b53b941bca14e87ff813ffa9bde7f749911f5fee068adb5f631057f3cefbe7fbd11e44134892830e38cbfc62feeb1bff0869fbeab33678bdf37c6f7def1f1fbd3d1de8bef7dfc26e0c8e383f6ec213ec618e3d7fb845f6ffca7f1647a6acaa90bfe088a214877cefea7cb76fdf8fab57e04ca5a8a32811edd2d8aeeeef9f4c0ce20f81ef8dfdf12fcef0310fc32088220f8ddff4a0f7bb90c816ebaeb8cf5024df759693709317f2ec3267afc5044319d4413ea846242a1505aeb174f277d2ac1136832a14c2862ddd57bec701cc3d3e9743a892693496b7d3a69144a14ff249a4691ee9089485df0487e642549933f4962fa356ed2fea617fd840f4637e90742683a3b2586e10f9a8f7ddedb10313e7088920ec35014c5db8ed06d4816e2c96b61e9b6430e925291135bce8812598f0e373a37daeea683cd8df64489222ba436bddd800bb20b835042071bcaa6b71a7a6eec36bdc2033778d66d5131d85506ff9a1440abd87645bd0852858d9b42686d566e47bfc8324b513dc36649cfb8a12f2c34961e4a13deec7e3f1a4af4c58990e4ddd63eca826051e5c9d2b3fd04df041cdb583f6a75e2d846fdbd2f629c13e7ec6ca94901198cedfc278eed9ce3260af500eae699c2467db8c2d9a87b9a8063a8f2c43196143cf8d4a400d5c63997f1074b56116b83e5b769e420f2b1c1544260e58258a911ed20840a46826e15900d89c289db911f2380f98b34c2d0e7bf4a49a544695edb5fb5f4478e7a24272a3ff4484118c79c4d13b03f7466e5583a56d1f7b072d534828ca7739993cb7cd9060bb52f96cdb6fcc5177399f25f29430c4b0eb6782a6d9ccad19452b274324a561107fb549e509b65b3af17f296b3726e962ff4b8cf95387618e23ca1cf1752c9ec8b064eccbe6abcbf6a66a36471e1ac3fb2fe2c9b55a1d8366bf74547487fe432dfe7d3e9743aa14e277d3aa19c4e29a7938cd32965d241f7e9f4426efa7fee7145af7ba31a4e84e585deff7394267cfd94e6f8a30ae3af5ffc4a96ce4d1308f7ded0d9178d0f01dc79f52dd9ff6fe03043a7397f8f7db5ac68686d5bdfc3b2fa9efd7d5a53a1517faa9450274a797e5b559e2c272ca50c6e4eb8ac284e709f2fb4dd4807822c33fae258a53272537555486eba2ac94ddfa21d9d3ead717247b3729446c6cbc6fbb38c8e9c382b89b5b32f1aefcf52cac086d2a4b0a2a039f47b062ad03f126d39e351a5ca1eb9cde6507e46ea59ca1a9f52a25e55a65e46c9c13eb251ded49d8dd72507bead7a915e6a9d56ea9df2eb1b49ab1b486eaa704a558e9bfeb654c5dc7494b751d67819258e9dcb13f2ebbfe1c44d5f5942df4872d35fc6ea06920a68fba39ae893953b6f38d9fe2c3315100ae3df52aa68942a1c554aa992d1979793bf0ac8be5ade5f65b32f96f757e55442f645e353f5b8f9449fa9a55385b3fd593a568e558482f22939db4f2da96c54379e338dcf2d9f593ee7accb77e2a67f8df251e523957f0585f14729297d19557e68fb472e432d4be7a63fabc84d96117dd1940615e3b2acdc8d1c7db125911aff47f65563e547db6b107db2cc5a584a965cb2c8dc54dd70b25490010bdb9f9c704196d9a912726670b3dd077daa74a24a49612adda9735ce6fe8c1b97b179350367c6cd4d1effc865fcdfc92355b75468bc4f95a7525298e89942f09db32dab5b715429c92749b284696284cd67baa3153daca815681a7fa276a85d8844a364b1b1ad10c959a0a977b3c57eebae1971d359686efacc4d074d13205f85c6d358f1806962840d4ce57bc044d491b678b37247ac5c0b88c69b9563b171d3554a0a73d308216ce5554af4c57f460f9a65665f2a256ff97f9fd62c3374284d159aee53a554a99fb27d52665bfe646913c2c8d8127daa94769423b8f90118e8a37f9f2a259592ff0c1bff1938f4d53a028484112ab6f80107f39f11a3af0cd88c80071d827c52e0c58d9dca215083485de89b686a53982646d898cafae305c06fcb6ac45b2dfcde09b7561e9d5ff41ded7d6e7a35ee5d4ae3eeeef9845bbdc66ccb052ab3a9d941cda9a12fb8039430ce0e3c4fecc1dfd244d3eafbcaf348a0ba18096ee23feb0cd78a6b9dd911c76256c7ab316b3dcfc567f6c7a5088783de973d0f7488f07da6c7b5e71ab9b602d9c76f562417943ddd2b84cbc1ca1937710b7fd519c16570071628a2b71c6c367e13c610efc01863cff36ab5eb62f1626a3804bc2f0e3baf5673cff35aad56ab013ddea12cb1b9353f7d9d677c46064d1a8066895bcb887daf9765be999a9919135476351ee8d83102054ab0806cdfcf80cd4541ad82e5e805b85f65340315a0323a768c50c205ecebfb5b3dd3fba2200366ae0da4589b74e0fc79a3985def0063d074d34d4f72939406022bf41501daba7f856b8a7dff6ced9b827dc1bdb283bedc4fd1dc97bb24dc5a5970bda94e234602054ab8800928a4c003c7abbeba16cec8b2dad814680733bb19a75e0633e8652ac3a66b66f08b28a4c0a3deb80cddf8f3a718ac9e7f745b708325cdac23fa84bb45075262b33778340e34f946c8a68f755ce6caf08f1936c53915c69fd21a7ba937fbc442643a3935078e81ce405ffc4ffcc3ae706c9485339d0db734cbeccbab1ea9cda7b55acd81787eafb55ead18636b31c6d6628c2dc6d8da9c2b7dc1ee9e6d765f51202d16676c57d6ab526cbb5fecd1647c65db712ce666913ef18d2e090ddfb8cc8959d82cc46e5ee1580ce7548c836ff08db7fc6f70ec06df84d6c8d538a651b4ceb5879771cb8d79917caf1d717931ae388f268d7bec0bf594e2a7d7be4e4f1fcf4e01fc18bb66638b2b5c3b1a4bf06decd5d903e583cfd315d7b0126c0bf2b7261060b65d6120166adbdfc4f3e1e5a2046e3980010707c85e17c69e8b54a2ebd321747d52085d9f06a1eb9340ba8e2cda64464ace683a058004822481fe6d6a176f611b0aa501c0a64f9e2b4f5f20b8c2b6d7f72700ccf0fbc095ede85b518b6dad8d85dc442245860d1870700090324246aa464ad5820a010bf9a3625fdfc7239223896465a4944685f46119c923ba3e256bba3ef9033902d2075d9f3442f640f2e0c21a89cc66455819ac6c0f8fb08dbeb41c65a0c148f698ae96b5396892c88d91dc618391d401c748d25e467288e600c65dfb5ba1343cb2af6f856d2e361b2a04021829a571b94e36328408577cd03f6ed6dcb4bfe283c65f82e90a5bd007f56988a14f6cdb61d2b61f26f5d879c5a281ba297803d54df14513435e7451f34173427a50ea828523230002661fd730b681607de30d8ab43f2dab7bee6d14f0aeef9f41219f4db9a96dd8868570ce3ed6d9dfe58aa1a82f923bbad26cff5a9fe5057d6357bea2b685631310a98b87efa56fadf7d67a18af581480f1dfd597f1c50ca0638cb12fbeaefbfad7480d7b9b4441763e47208901110380c1c20a32ec404b0113b05a89489ae1c7878615709ed0501849a1a3830960c003ab4fe6a07f936fd27881d69ffe34123c4bf414f140830f2460f7ba690a4df7a96110bfcf003b3b3c3c30eab6acb070f6d52df0da1601db5a0a435b96ea8eb6dd81da3b7a401734789e5094c6455fec4b00b4657f65252525dc67b8cfd3065dd074df150c7db12280d9f6779050027d7d4a680882628c254cfc00b37f01fa6a80123e2ce49458228117301ffbed94ddd6fff3d7fee49bae9901fce7912d96861758304600bc1622e03bb8aa29b2a404142a6c81029f2d60467451dca488893103cc9be0a9815ecd8bc44c04715af28389c98b805093264224d940c405b01eb5a52b821411f2240b2d625cdf427e2e22d4e8188a0d71026c7f4abe49804d8760d8ed6fd3211890f6e9f28424c9407ce131331db8593f2f2559c13ea9ed263d714b82bb25090a2207b92531829fb825c12113b92599c1e896c4c7d1ed270c2c82db8f0b6e3f506420b71f2643e05d4d080d3f3108f1f381fc012d82817dd0a0162ab82151e2866428e7dc901401441ee286c48897734322821b9298092edb23c2f38020b9b9546893175c68cd82fb44abe4a6380233b9f9d472d2cd8796a5b8f900b948379f9c9b8f0ab72359dc8e18dd8e1c713b32848e4e6d7a3b42e46a114485232fe417e8199bde6a5ed4509b62632b6a4e6ced2f6a6be192d4d5571e8850a3e3565b7235f9b71a6da336bdd580d8e2a6b7da6d9f664eeb20b248400c1f49e903bfcffa20b2488047c5a206d3c046a16e12c017bf6eeaf289a1dfb5785243104b37d800829d072ee47e98757145154f60fe4f5f0fb0422745113d3a44e400f337e9abc71062d83023028249113730ff10f380b3cd4d6f457478d06d7ae341b65f36bdf1d0b351a8cc028ae454159a831eedb62fa048acc2d07daab6b7a97f8f4d29bd15c9a1d7658f882b1ebefae86bab51bfa9d5ee2b1e1e8079c06b7681888888888888c8075193a1a1a1a1a12122222222222222221c0e87c3e170b85b1b1a1a1a1a1ab225a0e170381c0e872322222222a2e170381c0ee74d44fa4736b6abb34ae9d76a7d542702644706bce363364617b316789ef781e00c0904c3309cd9bc9c381b02e32aa53fb71c6635239f55272b881a8cd53db231b7edeacc9d381aef35410924d8a700451a5b70785f82b752a4792f2ea99b186f4b4d2cf8f14ad3a21d0c50f45fd571ca1134ec36d9f64f706dbbd2006a13b9b645cb0aa4ebd75d4dee5ee466eade72a0dd860481ef1d226448927dde9db3b130726dbc9c7996f6796f3c5ddc78926cb91bcfecc6530b41d0e6c6238427168a61980306e3fc6e3b49a328ee60b1cfbb8136dda16247e71245711cc7996ee70714362a8adb0e44ec30020d85e7ec8d86c44e5d7ceffd402f41921f8adc683c375a4f56705756d09ea82265490c60a0b2848ba1255d0c590283222f3e6f36a40312ac211a174b0c0df131c4a3e4c918bec31509b281eb4132c2e53084c36129c794c2045944090e0858c3c6a4053f1a30a9028726c0606285101e94df1d2e530360536b0f26d83505ab447476b5d9d8d824d1d3c36cd7176104bf5ffd25494776ddb57e10bfb5a4dade7b2fa594d222aee7ed64fba2187bf5d3b5150b8c6777a644a42ee76bfbca198ff4de22287df1802a1df183182d3051c452124ef49061e705171041c50b4464b831e0490f49e4c480451039b868860b39b41064c3131e24305f693345ed0ab5848ee9c03533a65aeca6b7dacf3e5dae100840f02d2b38bd94521ff18a0fc75f8e987242cf64350653e9639c6f037c23b73cf3d61082e397b282ae8f33c0319df2c3e5bf914c596b6d4fd6e367adefef075d51ae9b9fca0100c5da9091522325a325c542294943af8cdf075294b68c42b64a31909b35e8fa630cbafee91a83e09c718f0d9407807efa327e63105d8ed65a6b4f5ab6b1b563106ced4c86632d53e8fa1787beb44cf1d1b75f1f03f475a52f79830dd842dbb6bf0202fa374c1fd0a753e0197db97883b1658afbb18b6be3e704b9d282ae9fbd777b7e1cbc380c07d55a570772b8b6ed8f34e0da485d395840f27d302ffafb7c3e184b546bcc171393bd8c931061aa59bfcfb4afcffdf3ef6bb95dfc7ddfa791f8b09dc95e4ccc8e8989018ac134e78d5e2ff991f6ae3ee3af855c814c690d861987224992e49362cb68f2570b8944b660f267f2b2528d2217e78c6757d7a41515566cc6682972bd3c1a6abae82d78b0ae5a84b11403ef03c359399bd9b0b05bb586d9190a6eb44c296610aa5faef5ffff1350e3328ff86ef6b0b5b5626b6bc5b5e22aaa67ef03431ccec0d937abd58050545801928a84d6d96c46b6e4b45f9c617986296c06f7e1f6e2ecd9e0d159a6334c674328da86167e6cc0d52822b3a1856a4313b3d94c0a1d972bbbdb9cdd3de72bc3c5787667b66e91bd1a45623716c339f5874896e28736a2f4c92486a11495be800f56f06decfada5bdd196a7632dd8cb3d06f165f0461bfdaabe4451730e02204556cf102cff33c2dbceff33e10fc3ca2259e576b8ee7d59ab3f3bc59162e6841d2132cae406281155548e1a456115031c50a8e54e079b5e6785ead39b556cfab3507bb8c2437dc606ff63c9ae990288ca0286180cc1a50cd6a7ded894a72a365de0be27bad4e171a1c5332a7d588d4415f5254d0f5b6a03ff732aed76ff67e180cc52cee843be04ead06e4e58c513e508321675762ad925bc3d8d9d92171c041dfec7db0cf236116df6b7580ecbd367b1f90cf773cdfc9bef3058a0ebfc776a722f8df575e53032a085f8975dc0c692ed3e32face3f9f3ea52203e6ef6f2ad81834ce63503b93fdf67adbd5eccfa4aafbe197ec480b070f1cddef7d11daf24b535fd8edb15aed58cb01ec4f39b3f9c533db3590d1cf489754e3c039e21855778867baeadb4998ef517a679cb7f360b2d129e617bbffc6ff4f86300f672c6a914e92f0f49dba7a3906fe604dbbc500467b6cf883b2139e3810e1176d4acace032774c1b16b1edef230a2e6ed9400981b3cbf34097aec913a413ba3e59643ab576aedc2ee7237742e900204136a1eb934ca4b864e09c0c570ae766bedccecbed726ea763924ba95a3b576e97a32fa92354336a6e97f3919ba15273bb9c8f9c4ace9549ad040b79029248d72771e490ae4fdec82448219209b924030d5a3b576e974322a781d9dab972bb5cb869906b83ef08238a206dba3e49844b0035b7cbf9c809600031b9018ce1cebef20ae7626a6e97f3912380cb5a020c40c7aecb68a2c6e859c2ccfceb2cdbd830312c49ad29ecce5c05375aa60635901e111d6daf964449d1572894c1a62d3e03a46b0f7e02a8a92353e3a68742f6a543484768448564ce108a100a2a24a4e3a3ffff9f801a97191e097da34e912defe21abffb8d576f71bd5d993f1c821fadd580f08fdea2e65638f55befbdf6853f8d2f0f08c822b41a3a34db116dc3403621fbf21e03b1cf9618abb93aec5e5bdb2716634732681862b21918ebd914d7e88bac088f27d3b0881c8aaea1f22763a3d8ef6bf8853d212da485b49056c390b6fd653dbe6a9710a9cb89b26dbfdf77e90bd553c322349ea60999d57003109a03ebc8503df6856532144a891297395143a086908545b63f95c9687a2644a7e6c0406ea8412643d96e0844eaf2940699d0a854ac1a8b52cad08c00000004016316000020100a860342b15896457130d61e14000f6e964a5c502e120763812087411c04410cc3408c318418808c21882954643500d3d9f42ed897290622390ee4e82591532075a0697941c8cb29a97a82158babc86411a26d4663ba3c84862c496301ac395c23dc1c814c11251019111c4cc64b806993ca4de5b39b6022ebe621f19a986de43bca6ebce4f5911d89cfacc4026845ffa0d2743430deb9f1ce00ed094c9c54551ec696f244ba9ffe6331b44012bf9241d5ca9583d2a73b0d4833703dc7541882fa0ea1023a21c5e5fb3431801f9c831a5389220bd88f4d88730cec22171ead89c22460159c1a4fdb69340a6a7e1c836de6217e6490988f557d1fc15386e5da67d330b47b41f1829dda6032932aee482c37d3a53792e069efd73852dfd9382d4cb4b982d463b6f2fff83146d5cf91e329f3788ed53c53321489b55b2df61be21062f6c066e1b66cdb40dca431889bcdd488ec384d2382701907aedb365e83056343823d6555efda6cfd141f4a9ae3524c8ce380ae29c6a9af4adc132a97cd1b9e18423b0b1ce2d5fa1475caaaa8a0270164fdf6013ddadf7200814560084a4b7abf5190ce6eb7013ebd9aa003bfc5ff8b637e851e03fe09946622e504386468d27b48782afcb2cad0f44ecdd6a420d28cfeb76eaefce47312e208af32abd9fea1403067709c4c2cc083d083c240830b0834a6066136f4275036c1201aaa02398819f14d525950ad3df2cd9fe0b153a532e055db9c5906d322106c9652824275d21a50ca4baa9b2d26ddeadf275830d56c3d15254f695a4fbf7d5a48b0df9a18fa898f8aae26a7d9af2aa7681d97eed653b93166553bba36d5f74756db53981ebf66f635fe74fd7f7b303450e46204853318a7383f517e4e5dda3596e64a69933aece66ce2e62d5808d920df24028d7253eae82a5370a9b5fc338bc3c755023d6f2141ac84dcc107ac968ac0fa01e92a9fc9b22fdebfe9b272b7e99ab1ee96d3000827f680931be6eeafccf523807ef79f26e643dc812231115e54e853d5a34349260cfdbe6c1bd9c335049339aad415b33f236b70d734d25836fff2eb610b7d8ada43663a1f021f156891659c63322187c363da757b0d21f597624fcd8c8bfb6098538db827af263213bc01b55b80fd041d43e8b896ee59e2a5aacfc0783a8a1ecf1d8194a56cea4f4d13b56c48d2403bfce003c716c003128384581c1a9dc84d1423cf270910a571ac569fa47169105bbed1a334240149097d49d4628fe3a74e108b4edb2b9a287240fa5ee6c77e9d8002c2532d764b41ec2e6cf22c24925dddb72cd0613096c330d7faff20d8b1fd58ec3835a59014242b3fd51da4f253354e511bf4b675f25d511f34592ee07cc97b865a86691f2183d6f312f07d4ee671db4ff9f122a9f534280675c6204b90449a0ddb14c2eba24efa3ec33f570f24b9e71825f66a150a14b0ecef0643fbd549e47750a66c7892dab3ec9891ec582e11115292ef51e49bf240c7e7658e349804de9af65354a7648e6d24bcba8c2446c3d03c5f56a38e1beec099bb44915720055646f1dbc2356a97fd02d214f01c96391c24526259a6723c4222b21b35d8cae85982f0f495442115bb73e77de21074a540db109e6c83f0553b80496a169c8d0e52666142a0c75988a55d7ad62c73a8c6133a89cb591ab4555c17fbfea7229de668b42d58ea67e9a58449db53a8ce9ad707746e2129f73c25c4c582e7cef9abf571d66a83b45fb47d36f64234c406e3921a8dae1956cbb139a6a1eecc0c54a76ac7efcacd0e135e4cdb5264f51330ce4ed1ccc864682587c8ba2bdf0a55f71b3b7b1edde61d747149ce04c1081444fd1f449f4107b6a36e2f19aac4dbba15f18835383bf4819d75bad0760433668038a37480aa6ea84bb614bc835fee546a2f64be0c37dd9ad2c000a4b8b7c83b1b61dcee4209f10c04c23c4d6c71e64b77adef75302ee9a176641195cfce15616796944e80f9b37888c0a492e0805b11550c20ab03c1b9faa3ff5647da6cd10478c556d50da410aa8012a07d4360beda7b4e4454dab19e28a2b314ee8e11c568871d7354f31d1b871d05579ef481d53bba53fafe735429b002c3b59e4b4fbf5071464ac8f32beedb917772b6d4da3f05956ebde2c70d5345a95781f1cf9fac47d6f423f1c05cd3b177253f8df2cdf540fd8a1cbd6f9051747a83aca3078c4725d4c64b1328be47610e4d3884a4a774442299e8f8fe1a9003500eae6a6fa1360e5b6c8c3d9a37ba107f63a07c6c14dc067beecffb9452a74184f64debe1d92778fad7620cfd65020f3327dd9b3cd842df2536997cb0391016bff6e7c3d63970e0bf161ab4bcc86b545d39273d41b8469d1133357ab5abed65aac93fd8accc65e8cbf46b7dca7a5c8d3b25da6e4abd321fda73e2006c54aea5745eb45e0021fbfdbca3eb1dc18b356b05bc79a3e1f82b296221e83fc9fe481151d56e83a801bd4e7208f1d7fb23538c54eb7b831c444b884ff24a2e017b09c52cf4770381a3246646dbe9c47bc7feaf4382d6d76fccaa05ee502400fc0849c86c0539395cd8f9b96f44e0e242fbc338c6a8c7969a6308b6ce5a87baff5edd9cc717ce4ad30972327756e06babebd2d3deccd6c2e31b925d3310e9806d94b2e501888a7b8ed4131d2d8adcb8d6ba15ee279db3fad124c9a5a6be7dd7aaa6cdc5f2c169084c9767ecd0b19d8821120104f2058bc457d6efac88a6e326c5d7629ae603a094eaa112c5d3e6b6dfca5f8049c3856ee72742e1b422a33f52fc0391523a81c0d69f5e62c979c62c496fa55218042e46ec4acc49e03c9a23499206baa643c6740fae32f0ff4b677a0fb6abbb18fd9c90d5b13f1479cef9d88c5c26193a2db7d8e93e5471eaf244de125856957b720614648b2bdf323c10ac70830bbdc9e8998f9669a86f817048551007b8c9ed466e2979627367379510acc964ba40bdd658a5c9bec7cc8c06e79fedee9f2b65c2e02aa00791b6cfc7175c3432ac321192da4ab39a23892e20718e96f6764ba3b088a06021b49f813c08065d5bc275916561de3f920f2f7b0a11fd272056647dc574220240a34f68a3098ff3e42ae138d73b549d04f4a4befe0fea9b0b1a4ff98688a0543e6d1d935c6ca84d1685e05651b56aa9369677621a8930272890fb26178c266cdc7e09908b138127d3081536791a35200a9f271225b0e2325105ebb8dc2a6b8682a066c4a4f4290d0f8439ad242509000bdd9ef0de97f0d274bd5ffc42d1e06c36343c0397d206f46deb86479413aed85497530f71e08603dd32cc7df44de780420eab432bbe9278243880d6e2e3f496e6ea16b83e8b2e48a773337fbad92811ca82a0f219998684e79565f04b1d89db6023a4222a5a761907579440eaca618a2248b9668083169f18124d120db6a19c1a8ab5e3662cc44c63e5104285a92c1caf4279c51ac76b1430e4549835cfbd5cdd36c9579e5a910d2d5f0a98772568b3ce10aaddfe6cc55e49374d89fa8d23288ebb8ca19ffc85db9db2608a9b5730cf0a936860e84ce4797653876b7c3a42aba9e892a3d4c0d5a60dd246f5e6c9b7b537deca498477191b6dcfe849ebbc02c4a13c897a1633cac3165361c88607289ee9d4e50394ac4ef422ae38abb13bd9be6f8f61aab563fef70daf9571c48074b1742a36b787a9ad3d4d34e2168126a65a143b80982768dc01d1624c6a604db0712241244e53725ce1b1a1c81c7297fd0d4857a2d6264d6092671e1a2cf820ee4a4d461a82d03750b26104a880e8ca0368ff144d3bbe69e7bb3332ab6865942b69949a199b098f2f13ee4edf608a929f81baf5116b1325ada2698f6c0314cf7a706e38096455d425491f19da773af13620161a1fa36da12e02c90c23602bf73c9d4054ff7208d7d90642b736ea7256f836b16054726888d5080965c5a29f8c01ff4b424711c281643e732f480c71f03066580e42743a1fc39eafa693b8426448cd304b018fe282b46f6d7b9313e4e67167d08c21de3edca1491e2cb7cd97af1a97dfe1c0d74a482cc8633ef41613b5de56f2226ee980bc7a760fd4112a8ed300943c08e530c8a02269e14bad24ed054ea3cb35bf72b134c3dd354fdc3fe11b35d27e5bf44f97866037bbd36a4fde5c4424b8115f3504d06cbcaafda437c19bb22e3f8b51ebb902b1ba39e8a9bab46e11b85fff56664d71a1f7288e5e1becf929deeb3add83e81f15f44e2707cd78d557e78f8ac64c3f79d57356cb751e1fe044920406204860ae824d19b2f364be1eb98512f3a52ea7c7b0dbd6640e1184f5781abb15b6e8183e316e51c796d7913e8f169de6adc93d18e83d2fdaa8650ceb1da4791f62321c54804d983c3b0621592e89bf327e55ad9a4b8ad5c53330b98aeb6a2524bcf469e85df8e78f2cb3efccaf5979fc9f76775df03b5420f988c2f0ee08868449363064ff578bcbee40edad5d51f089e74d7fe8144bbbde136cc8aeb6fc2e3bc0130b49c433edb591734773a7b4576a2b0924ceb7c7390282e1cb5406a81f76912b1301bf75f55614a72a8144a794e2f64b3d54e2a7d3313d9a3adfcc966e923a6babd6216982e08cf0e58ab23d207efdd9a62a20a1dbadf6690deb3676cdd01a10a0005d692c4ccf69ccbacbcb1f7543fd22c3bd20f44339293d1d28bde216606fd994b4b1ec54f728f0e9f064ff09aa33a2a9bae5b0b72921b17e68729bf887e0be2ec4ce019bdaef8608ff5fdd99e7e1d0ebed427ec600df399b145949f9fee96346d80b84f2e613b9e1d39a7a69bbf97e8b1c02f29ec1df16edf6b2ae0348f2d8f20d9ea29c26686c5eea79f3f631684c174b1d4fbaed5bce6f238aed18c1f778beb22c3d7fa7a7feeba4bbe183b8f56c76e7cd45b5bf3ff68cd9248ba7dab063dea17622191c343135e66c2826532034ef14c2f522a424722fa27a4a117e3e4fe41eefa27614e213a77078b93a94cec004dfcef3a2aab3cf2035968a9809e25368af49c4d7b1c5a9a85af944630300605f9b86f9535e840497dc5028a9b904d34ca2d582c207910c22b3584a712400486d9e1c3bb6ff7d101b5848a52d3d2b7fffe143b11bb545f3b59be689f56aa0f68130aabecc336df77c4567f486811cb0eb0b9f337738d7b44157b4000a910fdc7c1cbcbe72bdb439946646a4658757db91606b9e727876874e033662f814e112a83075ea861a778c80cf89e36671c27b563c38366d68801ab57c78d177e559f1005167a2e8f29abf65eeb98f9c9c40ae992511cb515888babd16e9f07687af02dfc25f991620c2b47c5647a8f2041083d227cf4a98a11f3575579ba38f369cec771e3698364764ac3969da172a037c93bf401adc576f2634c660003e7fe118623c3e8b1ac6afd92a1680cf58f4905dcba306a374a8c43527af5a3a6b292c0a87562f4844ecbe9202392fde40c94fe9f85bf5782bec097782d250aa78fa6749cc92c486495b9c9cfe41caba45e7e54df030d59b0641a49ca45398e87c6da34d7f6d7e542920a595f9bef46d49f8028c219e551ec5a3d589ddfb91a34a0e30c2f19ebe5ba9c212a479b697d555ab417949782354a7df36b1d89455a5ea7eecc4c5ed104acc354aa357bf25100964aa322b91151b1c2de2c7fe71b6940f54facbbd850864cfaef56c2c0f982e39ecaa965f5d7cfac2998b394f802fac68f556d8c60c9a40dc3a059d34b5b910ac7301ddf569920ba0f64e37b8c5f9ade9cc0b435b5d36c911f42c5d8ebfeee98615a4705264461769ea6e537c2690f4b37c74b136c26be1b21565bdf62dd0098b60a27258efeac1839295b07f6422b1afdb8c677cbd8087ebefc02915d75f19a3c852e9a02bfd8636af8bce433c93fe86e7e032354348b8238aa9ec9c07f8d50180d7032f28d44055f3515edf0624dc1b6019669ea45ca8fcae12b8a00f7e53ab832776850318ce5223db78638f08cde389e37e83a02c7bae9958d89076a7ebc063da26c2cef10829d8224d223292dcb5b80be7a0c2e4714f5a184f7c54ad6d5081061ca9ee3f96a95d10c05b7fca91dbce1d46e89dc9400ca3af41718e57f88a79fe4d6a53f1ffa92b7a41ac14f929b6aa9690442cfea2aab8d0381d421ed275145420353f6f97992716ba33d0968ab63350b34fd7ac4c500ecac93377622ff2e6998d59fc32eab8a61e6c274e9d8ec6a2dfc1891ac4ffd8ca69853d9956e1ab7701ab60c7eb5ed6bfb177049c6e2d0dd67a35783f1db3255305e33fb3f32f107c0ba7a1cbef259dcbb6fb1da55b1c69bf74720285ea29d78c4a6214bc7bea63f7b7dd0bca51123b4c6e42713fb4adb1d65f4019e1698adb49e9da62b3116ad99d0bc16d37d0692cb2841a1c80a4061133cef0dada9e69825cf7af43b63c415b2284f51457230e9031995d0c50af753ed9d316f69608224db1594b11c76705a3b3423ab5f8c537e244ab2761cd67adc84f42a0b16d1f70e7330434054c9ad98642cc302f1f1286a27107c601310c908d7877714078c152372da8565eb98486d41ba239b1a72cf90f05a8495a6c072af27347521196525b71f0a45381ea5b005f27dfc1c3d2e1e212155f78f89150fe73828acb15e7aee238f3036e0bf6bcdba174b463554329fa287322711c63aba32701f766577113913d19d620d5afe7feef1b08fb97db65a8f396600ea6810847202b2aa0dbd1453a7476065a58cc9236a83c80a4df917abd188f30bc86f930073ff83bb259b34ce05dd6c5d28804372eaa9ec58381572f1ae7cd8dbf118a1070d8c5fbdf8d04baa04119f0d865df92584141010d1980da131833fbaef26760bf7624c9ee0d48d8f28b43f58723f3404b064237a7d7854b07296e0b9c5c9d22fd7bf3d5e953e510f430596eab74faba0d6a95d6842787fb84d60c70c0327911606138b755b1b1b995c3f3d8f020f0c38359ba0039779ca011aa6e8cd5509c9e6aa36b4f6838363798604bc7f835df9f7db1e987beee6c3f84ebf847e9182045442a38c25196fe40a1dc8db96e4a1e96e59f1e3fbf29d32c2283bdc8d62a407856502dd8d9e915d0291b48c60ced705747ce25c5637290d5a5ec2c326479d446da7046d8bcac99db458cb747b84c9a0af721cc89ebce33a79a9a207c5f9e052c87a6b20001a1500fc8ce5b6fd059591f383e1d0601552640820e1b2f822d5d0db8ff85bab277c33a869bfc66ee8cb5e807dda6dae39ec0877f258c7f61238ebb9687608bb1409896785b0de16878c2efe189ae834b5d7ff52a8c7fa998d113a8e6b78d991c328cca9b957efc3b2986734fa2dc59333cd5e3d2e3a59945882fd9b51c272fa883a40c48d6582b02fc4dc5928e7f119929ce19db73368fbcde45ac59c21d9f6cb6e251e582ec9157737b1ae916c95fe9cff35e2343a10bc75b9dd13d8504a23928d9b548d173fbcf6931597be9f9d8fb22553ae2154092ec3fb7a21e6d4513c68affeaede992ef16bb36dfa70839291674e1bfe7073d34941cea90a68f01d791d459f1d64d829d083491838ade358c0f14f0804eacd7f6cb5f7d777d917e3498e29559a45b15668883a403cdf09b1819478c713ec8a3332ecf6db9b3a15c76f8265d19152670041b685584a9de3b0b7dbb57a728eb702cfb2b192cde2163b746e19dc36bb20026348434679adade686b73184048da63523fd1b18e5f386f0056f189cbeed8e87f09d2c22a35971cd7765b7644aaeeb7d4ba01d019ae779a37033a0f523ae74d0415989f6fcee1e44919f7efaf2fcc9c924342295bef7aa4e8064d4a2d01b9c2c8ab1def16a4e936a1e985c1420c3380838bcd9eb8dce503154b364c1d887742ead22c7fc65a9d91170fc710f7d4b9c126deffe0694e422626dde5cc73d9b71fdc890d07a6dd10d534ecd780d1145cdee5ee18b67335d150b89d07584c3219589beb7abdf804a4692108033e01406002deb4bfe27f586f48c388378aceb27029b82138d423d28b74ee0966ebb5b005b3eb0699a1bd463fa6e6ff73437507c9232cae02b6e939b3fb55431cba67ae7e72c269052322f27dec2aa3feaba785cc3f4d361ba693f97dad08a70d2673cd5346477bf10fd1231d075b8fd07dd0c8c30ac424836ce08c24fe4dcd15b11f8233c6ad25d4b1f626523981c375cb8c07beefff032993d9046334c723f27c356fe5aadb302777e02927418ab80c9ed031820e0a82f6f1af7f67db0a8aa06affccdbbee2b0d645e59a96d70f5d48cfb4a2b53a2bc07af8be09ac3d095a1ff3501b8aa5c0eb058a5e16fc57eeb14f3f79a505d9cb97857c03b6dca99cc433bd8fca1685c51b46c2544c8b1dd6f17605475035b88c871b9c6fff67ca3073baac5bef0cc2cb118190c5ab4fa2f9c5351a000f2abb66b9b02422869ba85b621f869b580404635f65d2ae5ec7c0bb0defabd00cd74ef3d4deb69951312e6d49da785945439d8deb2d46e9b69c54bcd24b60261569ea56a28eb736fe4e32d07130f65154e62ab67662d292f37bc31a43a6d9379e975d90c742b41961750420c2dd200bb4e5ce28d926b85a6466a1c0b15ae937cc02326215eedb2b17996a6a59368acfe63a676e149e7581305491ce621236eeb9c7cf73b6fc831f53658ced4a8b1da2a4b11151010c94ef663b3083e0c44fd3af38683282f531b139225c0698b69337b8d0267ac40be73c9d2dd0084383ddc828f50283268bf34cebdddbdc916d06c02fe6361ab5f3b7fb5bdf949682f2e550d4b1b8188abec48c6cf2b46ded516dc2fa2d5c2110651963a6ad1417984cb41aa20b89c5febffea75ca0fbb1b3b74805910597cae53497a0b6204eb7169fecddb21cca24f20c5d93d8d44cd8b426c6b0a23a542a3177c3086a28c8974e3b997a860013248d804e4a40a10aff1a2bc7a3651383d29495ad725c49c62c8928faa64cada22ffacd615554c70ea7c1a73859d55625751bf7031f795fd426dea1369827153cb73e67420a405cd0e1feac5a66941a636ba0b4293e03e3cc84c94f829de4d203bedef6db4c868adfe50960915e0946b236cc0137cbdf50f9e7a673142705a1c1b6ca403098e0f3557aacb08ca24a3631e566f87062cacd15570bac926061619dcc9604698a923bc957c7c8c91535416b2541d5db1a435c65134187ace6833b19f14b8c928b54e62cae29f41812d8d3fd89be2458b1ed2f08fb20900f0d1348b72ea0cdb8d80379594684155e8f151616d1744cfbab4157b6b739dc0099c68553e5ae2dea987bcb48352e123ec8d418a92db2dd187ec61d90148d6d5c60beda02fbcaeee62e6cbc5b0f8afcdd9854e115db55b778fd64f75c282bb460eda5199f9ed70556036a9aa56a23370007e82ad805cc7061977e7d92e0909caad1edf6d31113b38edd676736c927aacadb41f9b744f68ba9e3a957a30a606fd28ac8b42b368921ff38a6733a5312583049b1d399885bdd44503a0390fc958c56961a5efb206ba874e57494d0dea84675ac3c42c3dd862fb75f0a9b2fa1ecd86b800536769a7fd9553ec4d383b6ad5e3b0f5058191466c46743ab91242040221a4cee12d08833ae817eacf06514d9550e9885a08ed9e73ebc1571af020ac368fc077cd6c4fbbb16bf89c530e1a99b2541c2e3620286433972a0702b2243e0a8131771f011b6ad1438d9bac627f9a0c60a4ca8bbc4a404e2b2e1b850edba8655bb155163dfdad6499d431770219812b5536c183f5f30781042c0bdf2f037cd84f7d54bf26fa8d0936616f0a507d18fb4a8aa68f22c2ea90d5e085b2aab7fde754f6a3fc55bb3c2281935dacafc888d863b56d870f8b5d954f96bb1642b77591ccf9fc0e396489b43a3d1056bb4327859cbfc2e1f4daab72d7ad982287d634bc86e62b9a2bf9bed134e0fb9bff9a34442366b1733c367bbef2cb6a9338ebf4b205e6103268f947a099c99cc293e61b43e4deb08e5f379d78ce742b2a980a17427078b1b1bc3e0f302eb018b36cad0eb15fecc83cae5e5ecd10156b2c06899a028861c2355c67843e113943ca6de82505bd72a0d473c951d0c0f96db0fdf387b9f58fca226b2e71d27f8ab1fb940084709ddb1b79719cb4d2e0df6806533eee1ef5ec41b5cd81326cef24c4f3b0b6ecde5a2844a6775f3a58f65e693067be503e8c98c654c973e9631011f18c7737774344a61034a15d2b004c09a106fe8f1993ffd58b1eecd1fb4a0579c3896999666586602e0efc0164d69dcbafe769963813a208b0be3eaecddca8e65a6c1fbec719d4484ea4a4b70ee0aa56bd4aacc7a5d1a845e9a2c58b5fed7cb5a510869a7efdcdd45056f839beced8ac73d85c0a3c558d2b3f8118d447106288ca3694b631573407ecd7ec4f0d050dfdc11d73048a700b7a1a81e6a83733f570eee12c595c7484582e530db78207c747ef844281243de323dac0ac47f2a34fced8f93874677e1f959ac8e3ed420a7bbf73b733f8d5ee6dcaf93855c71ecdd7d5518eb14f106f3e483e621e05cfe4bdf1b6d0c02c771e665f9db73c91358868d63abdb25451ab6b4fd7db9f4a735d50bdc3f4069730e71a6228e38326c60bd8d53a401de9ddc7731e9eaa45e4bf3d402fa2a3e9fa2f94ae4c8967d7344673a6dfc150fcfaf70720d86f6a7a110c54c68fd8adbf2c91ae91eb45cbf2fff258ff15f3c737b644faa80e9b11fe1fe24811f58331f603af4528096fcf88b26c90a464cb88e4947f83df7d729164b04153477eba2be067a902a2c6c6894ec1be00f2cb35349ba4bae3b50c2801cd05be0c9802b165052dc34cb604b86cc4b7ebb0487b2a32383fe8a5ee1e418118bf15ce4612548e190bcfcd8c276a38698164816a0e58de5ad77a5319e033514064923ab189589a13755ccea034f1735c9d7175f627c51d5625b502d602e8c96125f677b918fe11e570e83e8a0965abce26992eec2b2ebf686e4309c1acbbd6cf7e7dc62b382af4c28ae2fbc4fdeb5b95e8bad130215c4e2aef0c61962b54c3ac66041688ca789a471a6e9e46fbe6187401f88cc5fe11930c1a982769a0384c056da9933e07704ee006e741e8941c0f292f36401698c2799c80c51ff90fa07cedb18ac777410e93ef80335c7e827c68ce3895d0443a22784734833aa07652e18527a59732a36dc583ab198812ac1c2d8863833ec78f3a3c4154858c59d52fded663294210643b75f38f12663adace65650a1e9deb0820523bf92dae845b59fb0a79a53038a7bd304911d5bcf60330a8638e911050cef979d2c2f7614397392213335ffa7f4649b9a2a842b10e22b1425dc4067b98e949ccb0b4ef2874e62b60c6d90e0854a08f175b9ba0871718110bc23e40203f143343f358125716d24d1ba38e45cc61bc4071d404479d3669d45dadd401d4feff51473e01d44f4646ebb59931fec39edb2263e7073dbc9427ce0e6b69b85fcf09edb2e2bf181ee6c3b4d23e0463fdedac1e6309c0d4fc242f5fa89729401fbe6c514554c2c44815df6c559e0d8543a9f0623e38d9ff090607540d4e6eeedc11c98333476b3e5a22a8f486709ad099f0dbd261bf46e8e13e329f79827823fa456d03d475545d0b3ca3006eaa4ac007714546e1372e83ef84d3a395e9f2305c139c52d3de245f86d1954892a555dc12e134b5f1184442cb34f26b552583679297e09a3407860e714b72c2bf2b6fe8e16455dfc6a28661bad6482b8318ba3ba8affd354f5505bb44aa8665b4fa55037f99c1ff925e9708d0f7e898c010b86c807d50df8392c06fce36a64ba0feaa1c1ce9d8e36cad8fcf90474e1ba3858ce6b36ff9758338efa53d8641af9bb1d3ce964fcc1d922300e6406330736cf6012ab16c4a476c7debc541143c3cc81fc470139d8b85e8c228e7654c841071bfbad99fb72eb277131c3711789c22edfa5719cc848fc3bbd3089ae03ad1d57295f5d8530a6bcf633ef864a35f9bbec7b9dab2483a4ddbbe601e2c18b3e110b25b035ddb77be31a24fd0e6f12f8ea27e79005c27482d40a12203d769a264fe0f306977bbf1bce9b5f700c37a624e258795f540685c2c8cf71a0782c07c86a1c28f952d9696e014e8c723b0887810c859ac198d707f81910d219fcf5a37e239d50c4385970f9bc60df2a822e39518ec9adeb7138b400b1ba35a575ef70a0f187c5d6e099ffda9b4bcaefc419076caeee25969e745a25eb143da3e91b049c09779882770d0ef98131043f0c029d4c5e20e052bc5bbe1cbbe2ca9d3e947e2a454329437d6de7e46864f0378864f588d6551166f112c7cdc0a02f8f24017901fafecb82d8689468102226830071399e880c34e23eeb51e8cab0ef0e2396463176314bbaa9eb34c183e8920828d84325a13a98b10ac7a567004a22f28170398e58c70d03625b27bfb28604f4a58e88ee828bd7269c3a5f676c38e4544334b49f7e91adad368d420ea6a36230ce4be4aa389f1c537743b36f20a5fa91c2f21c687f559294219b57fea01870d1d97cc68279ba19a0921a54a4bdd81a9f46fbadd79471fd67096259c8d12434dee7f863ec6006970696fa70548208e2fab3a77d50aea107ec6562654f94c919259ab39c4ea4387d7f8fb3ee5510555b375c5f1ea6ced63a186f672aebdccbd8453420059f3ef95f6150922630d214e094699c04ba7e956efa6d302a3a6a14fae31eefec1329cd3241489bebcb87443f6ea5ef884ce33d98179c5235b5396260ed973f46e7d22cb17608f9c96a124c6a9da8f33259bd598dd4585c68b95410d3a957a293aaa787bd60731b2924710fafd0675cf1ceed8ce942bbc0093f5c304166565e2b0fe0330676fccf678752e06fd03caac16b0f0354c6859750c4bf8f299b8141ba1f4b531450c6058b10a7990385058c557af41d7e5868f2bf903c66f40eee06b573e112617182fb9fa3fdd14c3c8b190ba05d784f82d8aefa8346ffd57a6fa1d9c83a669f466b98c72d8e0cc9262addba7922fee1f5b61e8503f8a224205749be8422342a3ca96826dc99b615512020da1c8b722d1fce45fda1664a0e204d69aa0e57794e11399c3cdd032be63f73bcff2a44f2042bad0ef88895d8cab5c8ffdd76e8b4df4384fb73b4db52bef351bf52f7474929eaa9eb26f940f5b4ad27190b57bc578d97fbf08bc960ebc541f88a76f298c0101a448f15316fe121b6368942bdad52ba1c5449da6790a8122ad10dc9d414b96bede1bc8e54df3288dc3b3c9bb3fa858e5712c27cfe45c309d97db1f5780df85fb12f403549878e38d7348efd49087f1b7d98131c312e3a99ed2286493edbcbe6ae5e330299eb765240509054b8941a2462a84297a82ec8a7ed497fa57973e6a1ba133e730f181c68ccc94d2e06216395131e54ae9558bc706bb93a804b28ecd82d3483855837b06fe467c435b224d11f4158a81c76ead12d0a82bf0b18566ed9afe61260b5df300b587ad7d558f4ed4aeb0b1d49c47a5d9858f3c977f1dda79e5ba7a0417ccd328905b5be104315775fbc99def7b9e9e68935793a6ff503e296bdd996a3e675ab19e5bbf4a60aa8d27b3f37dd7cb1e674de7f685aa15b2d2edbcb4bfc25a5a07ae9ee866cb084c312905bd23c9d7c7346536e8b390bad894f071f8f37bfbc956ab967d5d9fbb9d6f4d6f0e39a588cb0e124e0ff50bc3e731eb445d8871e74e50af6f6195e8cfa2a5ad8fc2c53941fba61c4b73ea70e73fbe55627ddcf8c09337d200b73f0977d3807931e373f475b7525c4c31b9c0638f0d728e4befd1a0ec6f648391ea30a900f6ce7d0566a5de5178ae0a8091e5dc1a42f359fe0feceb015b412e6d771b1b03cfb5a113ee016850c747ab7476879d60ac2600adab23b1921fb39223c59bd6497180a2fec996d08797fd281199ca4205684b1c76f1be7ca070a960fe71cce74420c06f890b5390f8f0e10c981cf335519aec86fc55786f44bacc1c779c65912f135d96bb54414d817859428dbac0e618e6a45e8139c482c1d6cfe9fc6928b513c5256c59f2603b784994a06c00db5436374e266dd62851fc50ad5669fe30e3109d26bf8538e209ae87a00ed6358e780a224f9f51670f944ca7ea1c92df1e37faa915e5a002d58d44cb4c92bfa90d825b00029211e58093cf6cff5c743bd46e11e34dd451d22ad04b2b4c2d2fb26089bff4bc2674f8b4322d1e01d1971a73f660f1d8fec2b0493cbff906b54b6f2908d791cf873acd918a439a51aa4c00a9bfaacbd69f3984ab6e558aa37ace805cae8c86dc9288d5d6744ddc309ad95cb25a0a22b02e110ccfd50a9b6e78e836bb71c14b002056c6007c22004ef9b1b7c4b3f2cc3976a5bdf352a6e2caac1ee6d5eaeddf8a20339bcc00ddc9a0244a2be711d2ee5b7032b979d0cf68eb17be10bf060162311422683edc465e119670e5fa72b69dcdb6c41202ab829a2e203573098b4f4c486992467af5a0da63667095974b7d6afe860cf9a7b05de0384ef742d1ca4dd691c9edb8a459ba9aef8c0279941430e0d0debdb768406af9a3f3aaa18f2b51b029d49d418f67a807ba4105eace668c323bef5ff6a7882dd668038e865c395b6c2a0f69eb9459d1398bd6347d59bf106588e87cea79cdcb14a32e87acab83287f8a2781c8b1c81cdac044dca5d1f7e92321b9617a2ef6f58f9660f4e150339a26a7a70ad948a83454f16b8ff34529b3c35d0d22f115a90a5a0998f2b179c42e76942c60136edba2e7a99ca36a8c9303296fd188133f1403a1a981587544b915ed523e5474d73635f3d5011121528151f2429c54c491b29398089f9c3ab5f1864f451841dafa7d760d72e9f4209e8a50512cd7aeb4b50521229fbd515ccbdb345983079092ecbd93652b9310001d2f761f04e4f2bf37cea7a60010bf3d26e9fe3202297bb40b8c71863afc6dc32f3d6872d4c7fddcf79916177fcef87a9a41ca3a081bad856f57cdf002dc70637dc3a80dcf702d1458740376d1ac9318180d792c33f637e60544a8a55c85d077654c820ca28a12b2b795b0b06e0fd713ff7a8d7b3f0c57516f83137a43412aeb36633dba8a6a424b7c3c71b57e01d0dc151da88df04efa502daf09f722a44f00548936f34614883845ed176cf2fb79963a55223785f658eaa2a300055adf50a2205d9a22e8a3dac686f5e8b7f00c325527fe692b3679451534836440ec4b70acccd7e3c4a0c7b68509fc4455c62011ff2ccd67c907ccc9baa1069df44a8b40a91782016d6f2ac60df3f1ff21aeeea5e2d2fbb2507d16e3980dd3a2690b94168ef37ba1d4bddd15a3780189add69fb7f011116876fc20a6f61343d5dd464e7450d7de34e315d6705654645aa78e899ad5c1391ede1d92e0989eaac904cb988aa14d3bc0aa2f6a8b714dec1205123ad797f45b0594ad47da36948918a8cc66d7671910b117806847adc1fd1b0452547167d1f60441fa687a6961e6a00a25239b0fb20dd10444f4635b99d14ca3983312b6accc892be753118d2efcbaf211fdb5c11a0bd9576e8062d09e7010b77a185975618a1aefd29be3b4152a6142dcca27f7cbeae83775c3eb23888126c04305e09f55688361620f686b2630b8152ca91d3079cd517fec581e9f886b7807a01ccf87a9ec8d90acd07a900de72a71c71210a87898dde651be1ae6e4fa9da90e89939f6a21c52ac9a7d044509f330ccf9a17d1122a8efc901909a2928ec681b4a04d8d076b40382ccbfae59a8051edfb89057a8144ae07ba00e9584c1a7868937e2e4e5acad50c9aaffa2d071f11ba6d98ce60f8864e8862392631e162dccfe265c9682a3bdda3b72b4d78a7f1cede08dbdbb9823998460bb079fbc332007c01795932c7fe0b7e4317af2053b538671e5e64bd50ab7207d760e273e19951dcf48ca8204d768d970d39b65e953082a33b65c0e8d5ee780ab641ac65e01efddea31a9add5f5df2d9392c315afc4b17de247e7d063db81ebdc2c7b82d2e6e3618636152662a854ebebcfac3d6e043185501260c45b7d1a9deaa3c139650f7e3656e9f42a227328b14c0612ce2c3f405359af9ea510a2179457b6193dd9ce12b3ace1a059e6bd5ce1fcced2386b01b16f81433602394ce685900514b301e60554d94bf67b9758a8b3eefeda7eb589cdb893519a7e7419b7c6a5bb9b0d0a74cad4eeb77059dbe416886a117281f41852e20253d2505f4fe75585f0e62bf7bce8f9bea49bc0b7d4610c8ca7ca49874a2b26b0c9272c7c8cb9de491472efe9894676ef7f6590024322db7934f38283214d615551ba8710872825a49e8f2724be08e50d6de4f52fc28687ceab7cad213abc6e2b30d54acb795202eb7f6470405cb92c5c9dce3329483df9ca1a1b65b1da2c6c5863c04d42d0740b39e6fd6650690c2ed783d13aadb6dc113303a031805aad544598f562db2b1f7cd43670d89a56b952857002e05fb8748f87d68316cf1d814e47a8161ba5bcee1d1d39a73c1eadce58a3314f679884911fd07202181d9616f644a0f75a468b6131c17b77dea681fbb99ac75ecf25b427cb0a8563abaaabb670f89917c269f8c0cd5242cec7089b0048ec97f9d82ac77898f291b8d33f5ae6d527056008e14f64971c7bde44fc4c8d14a5bf343b93bd8b86d3ed63b4c60ac8a758696d29697b12d2458ee5a38b14a4b80e4b6cd1414d09a6212ce02f2a3062884e3d91a99061e42abddd1c0e150ca8477518962a16dba7b3d3dbe37c847c9795309d113417396721c36b7147c2f331ee75b48aaebd1eb755ae360db9b32d57824537b4650cab6fa824dc4c5d355238fe90b90b77039f7a09fade05749cfee1946fbc81977ba1e93b061e9af8e38d6ecc87295fcc5f041007f967f2a659799c337ec6ace74d9899442433f83c261409ee1786e6e2f6f3c7894d19c3be363a80d89226d2f78d41ba08437dba73f2ed2c69440ab47a87ce92b2fb4f21ddadb65255f0ac120e509a69fe348cce3ad6ce99993cfe04857d6c3834bc0b2c6f243351f1a8f770d8e94577594365913d329e37604ee7315bdf81fc997bf28d41bd4c9acdc2b17b8f335357f45a9f8370058e4ddd801a81a94df24f8ea96350bcb541a921d121af3ad0f3edf255fc36d819794538ea81eeb0d1011c31381e10574fa094bc78ba7abdd20fc453253d8dd86cd6c6c33b5aabce731b197dcdf419a5b0a16b60a2ad508d39c60f87756b01455e5a2fdb1596845a7d7033248ca0537af59dc878ffad6149697971b20e3dae5b2adae92ab53181ebf1a922f479bec8deaf5d9267d36ca1ef75aaddbb6d0ef651c20b7d4a2c1c29395dc6fec161c13295b619f95e6fa8840147f9168fe19a6119e38b27b20b1a543b58c523515c40a6c740557080c685957cde05ffaac403a4becf8cb3264736882a8155aa932f856c6aa0c1210485389e0c3c9d659012749dcd098d42ad7e22f02e97368c5f0b0d6edf2a9c3b4e92454d1141139c9104a06077084257365d8de477e605a92a684e1dd3640a52dc21066901b97fd34890cdc4b14d1f44ddde3a9bfccf2eeec8fa8c826d1bb1847b96c1a818fdcd67b2ab6b9fc877b5084796047263b82f09f8dc82575fd6d02ac6485bedb3764fff0ac3048c47001170e58565a0768f200119ae527511488358f56026fd0717f6a7953bae52a407c8497c6e32cff9954b97d42f00ef537b918b4de2bb2e7eb22ccce9a1ee0f3aed7508905857c8b8750527feec2e229152c450dd7f53181a236dce46752bb377e0e9c5f9bd13bb11e1649062bbfb479b32968a1a389965cf40c572a8f78c040039c2581f1ca52cdacb491b59c1020adce6aa7106750dc5a977611c5a3a6f20d50fd344b7ab58f34f2e6a869d936334803cb2ce9dd9ac6d3a8e961ac3cb7c452bb9201f89bb32ef2ef0d0610e76b7a53232aba80d02dd4f7a57079807741136c736abbaf1e80750f4249ba2082d86466a6d3349831d3571a645030201a6f56008a73b3c089a84601d5742046b38f010acf56b87606dbf4204eb14b9439f7e56267f37e07f7cd5ccf657ce2dc3f5712f1442d8ddba1ebea9fa093307ed63f851883a72ddc91ae9b60803d2d06e7366d5c11d4877057e0a4a5dcd61e887cc763d50bfcf3af3b431456c6c9b489a97e21ed8236c165ac7919d96036062c0577c62817ac42d7d75e61b74b2813bc88042a9298306d875cf6254c2416c7fda0171fea6d2e788714eb7912a2240cb44d318910ba135a85c6d51c176c7049ebed36766c6be2c90f59926774462770d72fc4c1298c191178ed17faccb414ec4458b39e299d3ad4b6c53c7b92ffa22a31dbe658055c967e0e64cf23c3356a9b832214e5650f5c9f48d2295c3819c8bfd83070060828ae61f31c90664b08baa4c22493d4780f9be90207dbee03af26a33c2ed140d99cd61a7817e5c1f83a9991e397414a1730e81ab45ad659a2fdca0607ecc77fd0e488035f1cfc0cb8a4e51447ee3b8b7a67753067d2fa0dac6d8d6d6624c3b64d629077363da4654418468332cc255f977534cbaa49c0b61504dc84c9d8d6a3e2a333535977ec6c14dc4680675ac4bdf5f5117858e8849598fba8dcad3891605d1a47db45be32cd61d73e3fc67d7ec3bf2194dde1c0d171b82e2e9fb6f837bfa8a5a8927da535481442c38449086bb377f16036cb51b53675027b9f212c6746539f21a75fbc8181b4b7963b3b2315d187fe44b9f1ae8c0dfa34694ae51092fef5c2be21546c4647282bae4a14ae312a9e6a4881a78d95ad6f8f1a8f81b8511688dd837e0b6c68f6a3af030d7dc2b551bd3de0031af145eb358a35691cb71b8b4f58223ba3b24247c7c90fd9d2b0291e74e081871e0368f5fd78ea0f2010dbd78ce382697aa17616019db51f5cba9f2c55c31f37d39421c6aa183982b470f951db041cf2c820983683fbf679189d67c2168281001ec6ba816664bbb296658ecc06cafd41c2cc44e6e3b2aada5d261c84fe8d6e19649634a67d278131496be01dd65e1cc2081b7ef2ccfaa885bcd06a8b5122216530308418540b0bf2d9cf0d2e695014dfebcfbe1dec55d683f2cec7b3c844b5c2d94485220718ca790372d55064f09029fad542505ed32be23d801015b8775bf4201b20713b9579c0ee76a34ab9a5e80d22c1271bbb1d95cfdb659d4b426ed05009db319ceb7dc6cadf48c1d65b80a89ec13cad4111532100c5d2692f601ae1f32e915e39d4781c6c0b50a3e5734f2db46c6a2cc62faba54832a8cdc45b94da0451e3bb6b7a1c69ae56827c73f58c01aaed62faf98f2cb6de25e9062b824b733c01d5b7700aca39a538a3efe3e464adb3c900888de57181fd850b6936e3beec8403c857bb5bebe40e09df1893bd4a3349fd159eaf9fb10b830a5001c337cd10947297096d25aaea02c9aa5e20f4b41c4ed4a07558c0f6819fdf80022a2501079aa4da0ab1406265739cab5aff1fccbc30845ff537071a4edf6434302efac95071fe431ef20ad7405c8e8445a9b53fe900f56a000684733e0437dd06a3ea1417274a461eaaa49f326ea0d2775c7a793a63975f258bdbc0b90ed9bf48a24c914accfb81eea474112cf0f8fff028b3721751537121e2928363875e3ab46b9c922979e0d909074bf7db2df61d5a6cdcc6d65bcb44f3832bcbe9563b6c2de5dd538df673595317b0f88ed8de39b1076256f615ef6c8a4226c71dfe32b45f02fa3d47f829b8531400e1dcc34e1161a5320d1af9fdb990b52c326a9f239205221df8d411455c66b73f6bbc6e746855d874d9b2f13b32ae316cc17e7198eb0d6f087696c153f330ea53501ef37b61ccc1aa671bdd6072c71b40cf872023544723a5fd7e7e5463ffdee3876f76e8040d24ecd401839459ab210c2130ea9c66e7a749bc1b4577e7041750218acab8cdc658c2d77cb0b34b2af1ad2852c6182c31b0bd701ce86c44fabf7ede992d8b11c5f1aa7c7535c36118afa8812baba7ee7bc17b66a971533f9dc3ce202efc7f56d1b2d458bf951552e7daa8914959feb0de1cb25b836b9b930ccd8cdbc5a8214a3484083d107a70f73311811399f0b58f67de52f1d67f38ee779f25c507b28baddd8595eedc7a2f74cf292d092254ba7a66ea8bc9218c9921f59cfc3986a4caaeb783261c8556940b3058e36197ab4b8e06c0249ba64346e30f3eb24e3018f08f79602cbaa8ad18efe0f37db1547bb238a14f83595637f7639eac2ea6f28e2140074d19ef9c84c87508c7a87504677ac7645edfe51cc7a1da999e87f64bcb41f16605d08803be2ec416b619c633fd5acc13f26390026e9aead9e4384ffc123e27c6b747727331428ca280f5c262297aef1767c9924a5305ee5762ede5fdb8b0005bb3edd7393175720ff72474f965160c1284f629230ff3dc71dcc23ec3e29f1816b45b0a6ba0d316b25954a9208b2ad0ef608d946bd1233950f1598ae511b091cf1739b8ae51cfa42af46db0b2f85b41ecd02fc5549295228cd2b59e17a8b143bda2768f56cacf90436cdf1ebadbc532f0037ac616ad3db725b9334aa9393a659df6c52746cd6bb098cd571b44f94116ff17cd94497e6975c2a8234804608df0b03bc68f45f406063a43b4ad68943274ca845d61449bca2189bb87c1e0d91ad8b1c5b37ee82cfd1ce82b3645672dc136cf516f533b861fce0bedf493d91e9b4656915c9934efc14a283fdc8925e792feb62606e4e756d91bfd95217d4cba91a46218549df2e068d771a479af71e7603b46edd868c3416d94d6a46593f38729b805c108df7ffc72bb4296fbad212e54b1ba8010926e59c13dfe04bdec4090dbc1c0fa1b2365b26cddda88afa52ae308790e7ad8b92dac06c37353aaad83eb2e5c511f3ed7a9f0f36c5e8fc47f5fdb689308b0db340cb5114df71be45c1d7ea25ea7fd01e15fe380bafec92ee556d19e02e02f57b19704e2a104da73901d98d7da0350355231af80e11480721b5f8504798c9279903374e600a222938edcc98e6b4ec4c25a52f050091c5d3a3703322105215517cffe88d22bfed41c13479123bccc69a7b39c5fd48969ff5f63a1a3d160d01cb0e58fb48d2692ea28a08905cd1d00cca1c8823f226dae1d45de777780b24eba85f2e42c6a12115247780d3fca07d717b89d013a23c5aac9b93eef2df64a5e1f1a3151470eb430d2c3776a8e305f7056da8cb19a943434da8fdc1324f1a0e1f2dc9c26c53cf92e51d72bba30b29ed36163a409fbcb6692235dedee2a069f42edf569875e2eb5a5d1e977501b43d442b52cdbadc21ce71a097f7686f313e09d447478fc3d3977e54d60fdb5577a9759ad35b9d5ed67bd7aa7650ed0551d6230dd946f98e5c9eee80a4459553c13ce84a9f8b0306d3a4459f553483fb4c5ad422cc3dc6f25ab456a200958b4c4b82a930beba51f96b2e12a4137730d682a329587d0a03a742757697ffa9a013155fa6fcbf5ae45b090a54b2931587fc314ca31870d16a119ade2870b78e731220cf1cd0cef12a4959288b9d9fea9687463fea0a8c44c01faeded694a2dd2d341cfcd2323438f05bc75306085ce0405a87f6e18d4dc0cdefe989b1cf29b6821504742f1d9bf5433e453d8ea60b5584dad05f9ffb1713f45d82882228998d59090e5d3e3724126323a1edfb32859f2d32087cfd777c8e7154349447630318c128d478ef40c2f2f3e60d1e8a8b928412e89627bac430c3c7142e5ca11b4d412c0d9dc682d2324b17b3d15b882504ce249a202ff0959b543d1ea065e133ca6b5750be299bca63d62cfd8b5201cc37722cf40012b9311dacd047da09247103bac502945c300a52d31c278be9026604fc5a5e2c69461d9289895795142fbe629f50bec27d529a579e11d0588501eb7bb09640c80680a161b00c864b0dc218a7b16e3a77f27ecc5585f4ee743e32519b88b4436e501a14a597363512da44ca2465e0081309f9089f5adcb6b81be3ee168e7e67f53ca7cec925c2b05a9759bfb8abafbf0cc242710c8d6832c73209d6645a872c04238ee351aed807086d571a391ec9c8c8acd6383816a7234b0d3c17fbc723cd44a7341f203ac5b3d8b61fc6b498918ab03b637f0bfbfc21cc1433ec530c7bd829e6fa761bf91219df0b63a26f3dd3ee31eebcaef384ee757f12fa4682dffdcb275dc7a5ce05f85fe902bff7ded9043e3e4b17df83ff9d4db02f953e0fc4df7d78962ef2779fcfa6fce03ba1dd59de9ad9e8faf6d35cff336b67169935db1df20751d046fe48fe49fe4bf5e69404052c25b4b00031830ed4fd02f95aabadf5adfd9c71969a386dbb7fa7da5687eae76cf5b5d933d5b65d1fa9d65adfa8deea1916a6a470e6ab8d0870687da47a8380193154a090d4b01105ad5f852a956a41c036b405c19e1478d13fbb6af140c4032c577df3e6cd1b367bac0780fa585d0165433502f4dd350cdd8661276a132aa34f4a3aef3f5303dfe287b427ff7eb1f14c99f6f94c3bace3039c3e6015c2103fae28e93206ad7588fccf4cc5f4fbf08d6ecd037157e035985ea3539ad097c6104d5c6b9f8c2ffc87fd7d1a0d76bed72e78deb3262494d2f6bdf7aac7e819880d556cef53fc5f3ec2dbd6edddaf5ed27b5cabbda47776ddae42d785fc6cdbfb48f791eec3aac3cc143f4268fb30323a9f0801161180b001920411e87da454cc0feb601e0c3c8bce145ebb33ab312e72a2d99e6dfc5707ed2533c0090d75a86e6cdbd7e643a57f171921717d6b4832e8dfc40c0c0a02327f7a76784a1f99bb7f92eb77c7aca86992cf638e0ffe07855634cd7f1e33e8eb7864d1d26c02faf26966931225a02fbfc9c4d39e0cf282fe9241200c06c260e01814f464d0e88190bffb33e93fe831e0b4efbbd879dabbe0f9f2c320b3fcde7c27f4cdf20b7a25f283fefba09349c9772a8d6f9e4c403ffebf13fa81e0ad2c73c0fcd9f09b41ff650bf2cd5722df3c9994e4ff93c9c4597ebff3b4f1834e26a0b36cb1f3b457e2f9f279bed45f9a311f13e77b4cf16539afefccac28cdac686956b4ee9ea3241e73c7ac630c4787d439228f53761c2b1c1e83827680806a45484b30eedb8c925edc5bcfdb9aad821ad3f7c9bea0077a5ad08f2610d0931f04f434d9ff7c4140563bb92f6c5f9a0f9dd66ab97e43450220b260dfaf9fd66a35f4be91023648525a7a21abc5a0cda8646fcd22d98fe378cace5a0d9f15e31084f23b938280de7b7af279d93fcdfe27f4d162708ac6ff639a786cf705ca1641fff32e809ef6f8bf4afb2141e7efc49e24334dfc3f90d954fef841669392fc4db427dfcafee79564ff733229f9fe4f26dacbfe3b957edec4c954fecf1b39f940e6f8d8bc2d684fbe124de9cb3b945554a6ffc78cbd8f99f33313e7794cf17b3c74a7c7aca86cc7aca8cc94d98c925e54af1a2dad6dd98e13e8b111e85cb56c4535dbd7aee73c81ce0c074b9d0730bc66664a8ce47932168bc5cc8455289364796bd1a6c9ca92fcfbf6beadbab7d9effc7d19f706f477fc7b6f3d7fdf5add1bcfdfcbe4b52cb8abd5685f3e905951da8f66f9763c22c9f2c9a791e5cffbec08f19cfb06edfbe1242ec11454a925c70ac693929b746f2bf28a7def8a7ae27eb27ae29eb77cbc817a028a0376e87e696bec6b45d8f9044b100c5fc6ed89ea71df0eb938b766593918860fcab060a61aff8e5f964f96501a1d4f1977e8be8c7ae2fe7846e72f67b3fe032a0af4df7f67f59a97de9944a3958f012720f3e969f63fffd4f33e1fd6aa8a18f9a5393e3845df9dd6768ba007faa5a0b35ea0b3f41ee82bba54efd07766ef2c5b900ff42ec8073a9b9428953f7e13ed24dfc5f8b4a79d4de589bfa2b21f53bf8f19fb9999f33de2f3983adf95492439a232d3a29d59370df1759ec7ace88e99f338fa635fc70754f489fcb1a278bc1f336dd494b5ba9f63de17b1f6ada8288304bda4c4665f1d27d0715208ab506af3bd3dc1fea94656e25f43a33d4f5036b4e76b6894e767a2be379ee7f9fa455f7b7a4620745ac1c0b8b79cc72fe3de688fcbc73b8f7fe9de801eff8b9fc73e8f711225df5f2083ba49e7f99ead1b9e76cd61d7ad30543b7431c7e4791cb3e7814c8bba59770b2754ff039c507d3239a14c4a947a5e3fcf2b3115e9a12604549f4c4c457aa8a9e31108de97204f8f59517006e3c929425f1cf75aa825bf4930556ac9c97919d9d691926c1b3fd9f82d8d46b3b4eca5fdfd7e0946a09ec03da604ec107e1ef3a9c6a3d55e90561e36fe6a0b90d6321b7686b0323c17e03b147fc96ff6e9ab8689daf861f8ad07c5ffc1d54de3677ab331cec66f9fc024a9362a146119d40d7bf143db92d7f0c3ccd0a9626f4d185450198d3d8f09a5519eef319f6ad8509e134aa3b173e90ee1f10c9822f4cf288476f04ce2f9ba95ae98343d70688f59d15cfecf879df9808a3ee97cce87be638eefa3f33f66ced34c9c9f99fabd16157dc279fd46de9549b118599a15fda96897f4f444637cd93f3de9fc13cec7fe29e79f6666457b78766ca436736cd41dc21fc331a59c704ba318ed011c27d0c9242c584f389577561fb259cffee2aec3175b10c7c7b4e12ea2ccbdc62a3cc88053d86c6ebb37f1023020992f8b5b48a6c58a5f94d166bb6e397161afa9405ca1b9816505ebcc9406a019030497a6851953ba2f543c2dcf3d3b8f28536cceb9f34243b7a5cb55063561562872e40359696c04a1c183060f378c01e3e42afb6be7d182bd40041f0e609e218587847966e44c42a39909191c21e79c2da0eaacac49d2657712a47b27286c7f219f21eebae5a404272280693488f1f9f7826777548d6eed86acd136f4aeebba0e77dddfb33bb10d2c68ef14abbdf7deeed608ab90b5b67a0ef8736e87d921bf6e2f584ff8dbd3626bc19a9da8b3d6ad5bb79f9a9beafb12829041052103df19dcc71500d1f743eb544faaefebdb33cd5acf665f4feceaf5d65a6ff60a79ad82200e4ecda9b9d3bcad0dd7909519b3b0305164df0cb887afe7796f41f83acfc35784b00a65ae15865861c8171fdc902aa29ed0a68c70dd106f5791a99f14ac5cf90193018dd754d8c65cb264863448402f6b8909269458c95d7de097112fe81421060b143c00ab2918788810b125e513bf922431a7b874b98283b352e8e29302d40c5f60395944c8e81079c2820355282c67e8f4b7adef7f6f653dcb16f8bb77719b9428f9dfb3ac5fd1a66ac112f4eff4bf662bb26e75e8daf5ab4e68efbdf5eb6ec5e0fdee25aa8fefc59d16bd1838c518e39a660fc4ef3e18b1af59fd7af177f37530670bbbf7de7b53a73d4407c264b01ac27608133f6bed674f1838b2a880f92399f1c7448c6fa8c5cf9d862ca6bd9c354e976b689c1c134236211a42393a35dff72a8bfbf8472c2beb0d044f2113423621a11d911cc726d77a9e673d0f04b7ed2c115ff77617df7bef158dd8eae14e316ce3fa29086e8bbd2c3bea7077f197bd3619ecf41b1086613594a560aa817dab68435cbf7afbfe3b65b1edef32ff70044391ac9d8742c2875d1cbebc80e60b93205f8c7456be08a9b053db2f2684b8661a607ca87a44bee09478f9f2e2159ab459c6d5bd58f16cccc22c7ac112b39f1a0be6850ae77b23057ac1517a4733b8b2175c771402d4d72588188ef274d0b938adce66bb5da600b998b35ec2cb87ea970fd59de625d8f8ac0bd58fd791085f5da4626fd81ce170d96f35ab4bd4672e5fba041ef46cd7ad2a22b6fd54483f974e4ae3547941e334f190a0f252f5816f870e77ddaa8a4241f7205103551065a876f0c8686fd72daa1cb8786d2181c1eaca93d4bebe43d5bbf7fe1daa7f3ff476bd61275af34dbcdd6769cda1a0a69ea87877f7ded3ddf3d5e1d8c4377f1ec6d9896aadb5e23335777d3b9be1ae3befc5f75ec718e38b31ae666a31c65eeb50edb31018638cf76c638c31f6a927ea678c319ed513f631c6b89ed0230961758cbd0aa579db7a432f123a8dedfbf962b36e70cf4c2c43589de8d3613f45917d230b7a6cfb1cc0b0ff817d194e74010e4345756dfb6d9b43438648971b2104e9422d0839ac2b23a869b3860c6abb33561d5656906db77dab656d97e7445f4ab6b50e80dae1c991114c0901671d480f979ab2ef46bccabe5bd9772cfb9ee5764b939d3f73b9b1f3ceceb5f3d639890bb4a4a02dd81e9aed7959cb06a98c81d838730db9b902b1ddb3e5da4ed9661529ea097b7adf99bbc7a7784dcfabc0b5d7649d28d4bd977a9ed77d075eea7dfef4fbbcffbe834ffcf2d789fb3bf377e6df4efb7b21f476fd9bc5dfddd817adbf77b9d7f6bf5166b6c3b61fd5d86103982f2e3e4280fadfa95b75a358f15b87fabb1a222ab0a548ca6264fb2b2ddd9b4de109571a2057905841fd5fdc5bcf112c1692e072632609b48b210307ea4f534fdbc9edeeee96cb89b8d82f2efbc6902d361c71a10aeaefb87b9be008ee0835546fde2c417d87ed7afb3be8594ab89673b9909d6f0e087147c02a4bae8625daacc0e4deaa579637472c2183c385a832dbff5a61d1f0b4451550f145bd648b346c9106375bccb2c51ab4b6f8424e44dbe2bb5e216e0b1bc5da16c051e85cf73613b3cd0d7b703c82f960e9aae585b5d3d90c3b36f8e07fe31198bfd65b58c457169abf1b8fb2feb2ac70f455a2c76d0f0b6077491c75b91279160e57c3e170217ce170a8e85c3e64734f52ecfbae03e78a778936b4caf7de6b31c69b48d645397731f1beefded77e7f76452066dfebe382942452b868650dd1ca32b3fd2bb04dc9dafe2968e3911755a7276788eca0d1058d0c6aa266d0a1a3aacd0e34319059a10a2708ed3becfb65e7858a09a96dfd3961f0daf77db20a51b3f7594661e750c1b2eff768aaae1dcbb305ca4e450155a48724668c00b7c4459b2323806aa10b0942ba9638c9d161b37566df2e30fc2f6cdfabefcf2ed81410214a880e24a020d5027af10835904802499b1c48a077ccbe37446fd7217927cc4aa7b36d2750bb9cf1e85a2b3eb2151db6bf78048d61c6762e317cd9366cffda95aaed5fc10ba87f8aba7585ccf65fe144d5cb161627dbb76c7fa41b35cb55a002c34f9648632d1ef6fd5809e4fbb6a0a4d95e85fa8b9ac7762c516eb8cab173756b04617602b63f59349b195902851c608888c0061b50ff0ae2871ea0f820820f263a24d1f22478a26755c2ec7b7f2c85686dd1b6bf98c2100f3f59a210539992dbf7613f64e8b1fd3149644bca04db4590453be68241ea3a923b0a49723aa4f490c64a0f4c43465041052242c2a4e9725082c4997aa061104fd91a9dce6cd696941bfbdebce48db678a88ad5beeeee48866c77c7efb6baddd291a7742a6ea4242a25a8d8de25a424b41c68ffdafebfa7113747b9b6b86c31a040edbaf524cb4ec331db7e05bb6e3d1113766dfb15977ed69997eeca326d93ea0dc6a60ed90bd3aa556e6caeca46ca680d7cfb59b2b82f70fb7edd2c88d9294c0d9b6d1f67055d7e6f3ffcce8ab3ba281d9614fd58f6abd6bd6510d421fb22e0620266db17b6fd14d6b5add6bd5514c6050303ebaa272e607716bd96ed87a15498c5892cc6a194dfec19ab702a3452155a85577eab59f6c328f6c3acf00533db7e683fe4f29b88dac7d7a7fc695bfbb60acaca7160e9c8dab1ed835a3cb8bc0718afa1cbcd7eebd539d9f6c1cfccb69fc510c161dbcf44f4eb07bdc0af3ca5f59971a2cfebfbfabc3eafcfebf3fabe3e3619f779e5288bd539b119d9b2bc2acfaac3c2c26e3a5c87eb705051525d550da115dda402bf8e12701c0a9e4977c87e165d51f05fdc21fb14d4a02b5acda015cd16cd5fc724f02cdd0bf5af3b64ff8a4e9d6b57993d5ceade72954fcdf4cfa63e736deb5252f756d1a92a2bcfca535eb31dd6b6df6175599d96fd8e8bc70731be765415fe16f02c169e55ca60aa6c74541516f0a09ab15fa5c53d99d96e56331b87030a0794b5513da2ac941d92a7b6b59fa77295fd6c653f63855d268c086f35b17aadce6ede59417796dbdfcbba43b59aadc8dfc8bf6645a739db5c9b05d1d9b9ee8eb00af950e9fab59953082af030c20e348e086104684ed51b1b5d3fe4e07a82d60fddda2e60777dfb55ab3dc77aa287d54e8bd83537c4879de6dc102bb921667243b8ec27e772aeb3cc5e73ae1c38af52cb928ac2681faa1b6aa830830f1caaea1a2b4582b8e006c7103eeab5b596309565bbbdb672ddb0fdc3249f692ef78abab3492169e9853f8c190834aeaa98eafee97bf03de90ef9dff7f7b75256558565626d540c085051f87bfd6bb32e20c1cc9d89a9f2509dc184c3a1f94173c65d0287e6f27a9e6f7712b8fc6d94dfc45be5f2b7462c96d4f6b7585e7737986d5a1766fb1ba5d80e66e32c948df2b752fe299ca89ab3b1968f3a0efdcc8aa2a0e938b49a7557b39e50023fe7873dec644aba4e6878cdd4ad520bc6bf9eb00145b219762194d8fe78082edbb91051e5858825bc14c185c8082a36a95a0547a850e5881fd8c870031245b6bf500c28125e354c1253703c6b5184d03f83bbfdefadf7de5b31e8fe3aae15068245617d80d8af62746a896cfb56c6b544aa0afc5158d6ca56d977af5eaf8c7bf3de563b54abb5958dad6cfc6be56a11c36b36a99eb051b46ffba0b59f3d2082a5eb6b081e43f840c19717a22c71258dd40ba8fdac4e092b3922602a88753b147a2ab3ba7db6e8a214b5629081bc65335ad9755b3b11b618386e77672a2ad1b7bef93eac5edb31766774d3a6f7210f6dbeb7fa2095adc808e98424361f9f3551b67fd27b8d3c95d618d9fe1bd09068f1531d56d1730a5d1473aee305abf6dae7b56738440161c55a5b77c5b923ec8ee3bd81ebcd580d4afcbfbbfff1d2f74766a547d83886f9f2d9fa416a847d9aedef5f555cc0bed93e64b4dd238bef655eb39fc27b1004c7b364917ffc0cf28fefb42dd3be3f865fc77bcd18bec3d3028bc2f36462893fef4b45bebe37f24ce143be62662be2b444842a294061f981c70810f0048a47171ba40009025d21c4ff858b2e6e21cf54b6ed8766beba0b7ed9894a16dfe7eebb0fbf2f67fcb3fc754cc2574c9a1e543f5f4709e89fb37687baeefe1dea3eadedaea2e3974f9a3f1ffac5c9e09741309b9902bfac45d2fcf93a86fe85b02f0c3fae10f6c9be10c6f585e1a765ca676f7d19d9dddb10f6253d1e37f05af7bd6f2aa847692c4ad8db76f82595fede84f14288feddbd7deadee2a8b70ac4b0f084c7961480a8a1de90a2b8e1010464ae80e040c993a91c01fa339e4c16871e84704005fdf93abaf080911f67a4a02315ed27706170e071e071e03d15f99966561ede4879f3e6cd1b943c2dce87c06826cbe5abcb0ffb1066daed2398c137721808dd064f0f3cf05af7b929a91dc337157932f950f71dd3169d56ae24a59ff264da79b295c6d0821474fc1f93fcd08a7ee6cb6fd754f7173c372843c8a1d3ea66e71d66df7d1e4dd26412a3a0244e2ec1ecb75a26ca07eddef6e8de4ee5ecee6d55f7e0975fdc9bcf770fe3de7abe7bf0babbc05f9e4c6294fb3f27d301a658c942c7afa30b0c5441c9afa30b14062852d0f1fc79232fdfe89626f93fe6f8783c02f145dac089c053fccc3b9a15257d66597bfc1e367b7c9e34517bfc1df349495ac5ec4ef6f5b5c7d799dae3e7984f4a7cd8384154edf1b50f5d7bfc989aaa3d5ad17e9676cafe53c6694054f225419244f2db0459a5961c1a1c24786dd6841e6a3d486dff0ac222ba030fc40b3e78a61d78bfed97b703f2bd1c63936f3fb51a7818243bb84f5ed2ac5b7440b528520ea0486534a54cdb8a1d786fb71822d934ff3dd30ff6cd5fc73bb3597c247ff243f2c1d04c731a039366aa77eed2a96d7f2698df9841f8e0998a185427c2fb88ae5b3d74edd07d8c818f2d06fac1f72c06380f7eae2ac6071fac2ac62f7d9f45cee7cf669a5f7614d3a2ed5f9d63e27c91d770321904d7fbf410b5fd65e49079d4c0f712142c3ddac816f4e7af5b9be5d639383a27477f71e5e0e899508e7eadf35a9f690cbc739208c5ce06c41ee7174092e7065eaba306fa714a52c72cc9d779bb7374cc1466498a39a2cedb7c7396f1c394170e5cbed6e7470df493af63963a9ff3768b66a9f3e2d72dfbb46af1ecd8643c3cb247f25b556385c3e142d83a53e6e683fabff0f7204b4edd4273b5fd6538d1e53197a05c99e6925554cffb17195512e72c75c427bfeea21c5b149d746ee0353b7e373620661679ed7b21af79d2063b68ab01f949e7065ebba303c833bfbdf5922ee83b9ab99e005f34c17a027c6b62d3b32840d8f7f040448963f7fc0f9aaaddf33e4fbbe767e69392bae59565f734d9a1f9305ad97d7ebc436b7effd78e20683fb3b01f03dbf7de82544cde48a11ae2053166daa4a9c1054b93206ef4b095012268ba6c5922489a310ed831038f1b6460f264046aa376dc2785afd9ae3ef1878a1054c660a9004411e87d52e46a4b17234ac019d17929d11ad1171c5c2a657a8429f3c38d8c96ffae13b23b18d9fe477690418c9923db9fe666dc6caf22caba1c4e518260216744152f3264d0c490b543f8812a391ba84c504148093ec1ae5b6492ecd8ae5b644e10a18a0d1f34a66e6c3132a3e301005b2534d9e18b1043d2f87583d3f156d88a5a701ff34594d124d8756b4c934deeba35c6c84e854208b313d5fa63cb977336714d65db1660bc5b6ef7d6fd6b67babb7f3d6141dbfdcfded1e972ba73052bed1d06cfea35bff7e27b1d6071b87def3d9f4d98d65a6b2d403d21dba2ede370c9b5b37e06c9a78bae45db6a1e3c0f63cffbfba1b5f89dc89ea9ddf6bb139f628abdb741a718878bfebc863b317b2dfb5e806fefa4d513d56b169fa50629de1e887be6b5bb43872b51bd786793bb8e1218771d2570479069d1ee7d9fe7bfbf9b5d7f5cb1916731d70c7d77dd1a53c24c0b976d1f67fbdafeac7659fb29fce6a1794bedfb45d07bc540d9f765b31cfb3e2d87ae7d5fe8ea8045dbf76de80c612d7ab1bec63e64554002d62c9c31165db8a10b0e44b6dfd0e34515aa6ec6351cbe30570882e376f85528edb8ccbac3cfef149e658bf06cca3f3e7896e157f4ef10ec99c66f02cb70e604330291ee2dffe8e27bf06c02ff2b2bec9bbecf6759cf0b5814067b23d823c13e09f64bb02febe79a5f09fc7c3229119f293f7836295102fffba67c96f5bd3f959ac0d37bfc4add67368d9f1f9b4d4a94c6cfdf443ef8762c5b804fbe04f28fe0934f23ff18926645c3d1ace8b6fbc585bd5155a162c31ea9aaf836ec93ee6d7cd82fdd1bf9b0af1706fb7a613f926696117843db5d8aa578962dc01fdf05f8e3d9a444e97efe26fce0bbc83ffe7836a1954c225198d11d827d8e89642edd21d86bb35ed8c7cc7a874012f48a22afc1609fa6d830fce02f09c1ce0e3aaeef4858df6e77bf7edd7d834a95b59ecddcdeaa3d2dd1696bd5ce7e6cde9e4e507716a3d3dab66f7fa8de6acd1a605bf102d6eb30c667c53f7cedfad76c45595b6c39032909636b6d917d6cd3424a5a7a3143a7366a97f7cb7b962d6c3d9beae3fab809ada83bd13001cd0b58acb466a0826249922557b4acd045aadeaad69412482c51e44bd417d4df56393ecf4f9af548ebd707f7a641984a86076dbfbed50fcfe8debe2fc2ee50ba77f257c27f3229a9a7d2fdee646272ca210c753c0adfc8ed958b76cc407f1f8eff99faeb59bd463ef99f19fb4a9ed56bdf7ff97bb2504e449ef6ca0ad156b415c502f0fe4e6b554fe4e791d223f6dad7bdf894daa734676b15cb0f8a61f97db7e63d36afeeb0493e8e997d48fc3a32c06997f83b3829f0623f86f8de097ed7c910a3536bd581a9de69ded98e31994c8b9ba2ad55ce39e79a5506b202d4fc2938f0e0831833cff96bb6a5fc13c8ffe2003be72e528730fca1808b8e61ff87821b741ac3ed89c706dcb383ae037ffb0de8e0fad944d7b776c78e211da45b4fb2ecba23c73e7237f3bd624e879a9c0e37581c0cce321104442b7f7c663d3c3bb21ca59383a363e4288630307f5ee7b6bbbace256b02e118ede4d67f849576656bb5cacaf538b265cec3c79cadf5d1c35a179f6eae4712774d68755ab33dea8fb4415571eda661cad54ec0b62768303dbe6a0faf1c5ed9ae917203640aa7796bedb483dad587955d733eb838093e8c5cfdf04347ae078e5c8f0f649d8a33af94f6423cc06cdfa1e6de90e351e526c703879aad1994c8ed1093db6125b7630904ac5fdc795f0661a138923151e3e4e0e00c11b15a2093335aeb39395172e845a7852cb211e34e26135265777876768404095fe0e9e1e1f1788c497a663d3d394814663eb39907d5b5f9f1f11182ebdab470e3a7fcf9f1f27126889920448c4a94a2288a635906e11203e20a87582ca68180b00499080aaab92055c4205899c643891c0f36188ace05a9caf508015be578485d0dc972a563482e4b9a9010068360614c002698406747d63504403461c2c407200019e76a43b55a101c1a4f61c10900000000210108204c228104124c400105586659883a31801327a4baa4beb0a4b288c903403180018c30e42350d850a0d0686c04b0d99c6c35e1228005042080d49494951d2225a5ab5870810516e02ffa8214175c90a5a0d804c939205eb9098e90a84908b7a2db0df4228ec959536480a222d08b8e1803a83080017240b672409e643540ae7caaa8c040850a21568058cd7876cd011921ff5013bab1190a062b30c040cc4e452b22f8cbcba8561c60c58a1f51c25d733fb2fc60010b1992fb91c4079b1f4f3f7a80c9f9f8cab91d573ac2aa5db1acb5d65a6b2b0a5863722868c9a1a0b5655e13624c54ecc71064fa1e9f1e45882c346352842961eff1c7cc6a9649b4dd56c6fdc06b58745abfc49e2f6dfd7ef9cbf0b750a109336b63de5624c2671a21cd3097aeb4e8e48e6fe44c30824d18b2ed2bc5521839100c4947547e1ad98c405cd2d20ba01a65c29c60c6073722a036a9ea8025cb114abcf1d11543cd0ad3038e366468dcd0040263c4e4ba5419a34211d43ed2bdf9df21133ee8b28247f408461f2dc0773d8de8a4a517f5a6812621603264aa481128a87d18f556cf58497dad908394244640ed934d74ed52a85ecb0d31921b72b5655ee321001200c0340154faf4ece8e0c4c610fcee2c954ea6cf09ad4d20747e10ed67c623cbd1a4087bef2c953cef9d02500b40e632bf9095885c99291294b810648d0770585206880f66cca480da9fc0ac1298198cb62f040073e6459fa6598b41bf8937d18148c4af36e60c4e7048c030082808c86e77eb43bcac252698c8b9f055b75a30627bdda0a71d3bd6b0c084ca136a9f66daee90fdd22c3242ff98463e2652127a66f6984b24681ef3c58e09a38d4e6bd70777c8be8e1954844eebd7b69f631fc7b4505c3ab5381b33add41db20f42e99434ed94697fc2d8174d6b159a16cb840d9d5a2b180803c120395a665982805977c87ad1568d1a8b81bfb56aea90fd9ceb5573cf5497b128ec5b35a95b6dfb564db56efcc9a1fc66f3289e6ddfa5926c7bf175b732b3ed3b962df365bdeeb6652c06755bfcfed8a4c08bd65ab6dd96886bbd496ab55835dbbed5626f1072c26ea2aae233c518934ebc291c0e1704e786c885a370385c0f13db5fff80a931b3233615aa31e24b4622017141c6ab2fc8c5e170382022142b0fa57c3f40f12e1c0e9703760547062e1c0e1704fc022be359381c4e47268387f8c2e170247c67727e58c20a87c309f18ec852dba770389c09dd48b5fdc513708c5672dc20a0962fb7bffb35bd4f710ab12b40942162fd7a67eac16c77a778662976d79d5e5913c2bea7df44d46946c22b3eb8116f54d80f1b4e752012fb5d6128f593e23857605f30999a73c6e623d2bc1971543088fb5b0caa77a6f94cf53d7776cd8520ea3f8686564f54fb5acc659c68572c133daff3bc6c7a1de8bc4ee8df762694d9a0655585cb0a5074a202791577eaedeff5fb9f9ad61315e043fe5eb318d5a0718731193003a0a9c90e166058b2c209646a304243931158108313c21ae192a3cc83e4aeb928231f70bcf1aff09bf9857f03fc4af897f0bf68636e9c3301ccc61f03ffe775b57be2afdd1b1e2206c54e56e93a2ae12162d0fb6ee22162d03a2a7997167ee0507f7f8cf5ac96f56664730567b9f0f3a815c2d4400312477880c971c6c80f22980102117e7801070c630e56ccb8b1c2c48d07bed7f6ed5fbbfeb69b83ceaeb91c5d50d0f9807c1f93fcd52c056c608434fb9a0c53b92281a3c7f6a77d6b48f061bc42c211404e2df0b011252b872a1f4c501f4208115b4e7a38e1041bf8becde5a0a291d8fe359703041bb66b2e07108f84c738e7cfbba2c35d7339700ca1c55d733970b612ba7ca87a7d95c066f63684ddfd0edfebf7fa9bd056cb55ededac579df46dab25a3dd3de79cbf8ecb0d2b37ae58dc88e23a8b88d61163ee38059998f50540798936c0805a5c800d29516643eb636183eb8309792cdc2f771dfeac0dae2b36c4e064af1365b4d8aeb91b5b6cb0331fdc90188ad69113b2e460b812829ed0830b2ae080c4106b3a209604b1a9b8996b7b10db73234cd94e667b8d2b51861a0c775aba67d75c8d13ba1ff4ceaeb91a50de087ab66b8ec697c565bf55ae9c5f4643ccceb29d730a9bad688c111faa49f2958ee243751f8db1e243d56bf5d2c0b223e54375eaa84c15eca31276b87aadb5abeac2ba01d0c17cf3e7a1d73979f61699d2b15d7334b08458d0b00273feb288c48d873d8c974704d0d5ea78201d4c17f6a2adad284c940fd56cb5487d04c6ca876a66f1e4e6ae5b4bdac08edcab84083a16775f3e1697734f3bac7db25cfcd6a1d6c6fe7e09fa7d39119cecef653313f6f7b4dc0c33fb7b21271ac0fede7eedec8d1fc6e1b6d7bde84440ecd9125576f7b417b26b6f84d8dd57d0e57032ecee5374b9104ed8dd16257c1c7516b731d6d2c6c897366d6c70938528092db5cf6be3afed2591c58a04cca45410451060fb832b08b162ce3a5b2e236ed857cfd072830619b4acdc0872a5841121ac8cd8617544f703122442118798b3f670381c6e0ac59f2b9118431056fb7e774208626fac1034a0d8fe57e3cef36af785237654e99203122cac41ef165417161a284bb6ac805e35fb8620cbbe57cc59cb566c5b149e5ba8146c584b6b582a543302000000f315002020100c870322915012e6892cb90f14000d6eac446846158ac491288751104431108310868c210000620c614619232a00960d47cbb70fd031099484f8576724ce80e1b4eb95df2b6b6e7c8662f7a74f6ecf135900fd1fed7d7d74cc992426a9032c8848a9f260fb4661729c083fc44fb27dd42480303178fe937d5fef79ebde5e0406296a58a386b9b84e5964327a618994efb6ea8f045a2197ef7249b52a370dafb1373b80903bd228f85abadf8aab2801c22bf049e64052a984ffcb672ff39ed1c74e8080e1d81386106594a413bc1fa4bee425903e74ce02803a7d11c5e98c6a5d5149a28dfb690c22687c12b172057b4fc33a1204c57c7cbdd979bbbe5a166cd6f4d33fa96a72d3a444a825ece7ef1649a1278317067a94677abeb60d5dc31c30bc812e447d3b931b0e105e5bbaa868fc2bc8b9d20fa68303e80f09d96ba494def75c6ad85cfa74f925550695799a3df84e87ecfceeaa42c65a3157551370bdca4d6ae4c320029a5d1127755293289d6da287937fc4c4ec7748b2e0cf8a453c0b072280ce738929d71b90945901c2583107df76927d52e8e7df4727f4afb423a3fff4f9244ca171c5db3be0825a04f31fc1337d0e4d6e05ca17dc8f0ddf825824ace16271347c7f7bafb57585eedbe859238277b45fb350f5983dda6ac3be400d916e8a14d79bb5a295b010ddeca7c71e6eccee27cfbd0199b892a320bf56a210463d9d254b3db31090c93c67da1287d8fce33673521820cbd79d3d6227a7a64b87373c748a7b14c72341a486e050dc40c425bf47992464aaf5d0f13fc250d69008ff2e15cfeeceeccf377702c74052fd200a95ba567a10c54f45043e02b55f1514281902294a20c5045cbc408b11a0680f187cc63ee52270b2045cac408a09b8680117299062023ad1a0ce9b2e02fa957cea454e1f95b3cb4e8684cca86f72873fa9757768f11e5a542f392d0446ab8aaa39ec87361a48200d3191d1a1512ace26d4ae5f20ec107448139d8d5e3a0bc1371e96cfc78c49ebe9bfb67f3ae58e52fb16a75732db2d799fa7bae01d0dca835a8453184ff4174194fd27a57ac0c2cfae767451ae8bee767a9aaf2da505dc0a0f29e5ada013c09d6541ebd627724c3d2a04e8d8c6caecbe82092312309848a3360ec68a4b44646dd60c8a53000e36cea552554a4e764418c5b16fc75f9f8462095fb08f8d0511284b3c527c922ddc76e9eaebb3c862d8d419f2d215a417312b5af6886791461f8f56cdc8c3c0a97473bba5fa81e5bb8cb4e81910f4d4b3f8ba737096b56b635f8e30dc79eb74da3772062e61b5e9f1e32c15a617d786f30f13cda1be5009845d9f524e396d6d2a179aca59dd16dd40fd106b094b9be577612a18e84a480c9a0d61632cc382eb55914302e25e2813fe4c52c37ae177f0e70a82aeaa72eee750b337aca46cd87fa103f34206a08bc069f122f682ee11a8f62f096795c56ac146fd6acf9347006ecc8381b9d13a2c0373232ca65eff9cb3c7e85717ba2e98b5dd18478a15aaa37c7bb2c13b9d35e963e1146cef1d762701e87d5c4bbb977b401bebf2b1aa001d7e9dd299ed4401ec8e26c06a10de8c79ed916b16fff6733983c7ceb9f5cc363e12deb1e033f3f06517e71f38e04c999b88a6bb797a90b12376f9cd20b2aa04dda22ab2b399928a2cde971d041bb50338d2aa4fcee09bb3e422f6f4833f10ccf0eebba0eb00edc981aea15c2002dc2f702d15e0b3d953f7d27972f958a57610ccb8d7425ca5ef3c698dfe3f1c8e603c215481cfb3ec0e95bfa889d7fa2e2f47694101fd2793800e14c68d8f27d90807d50f3a95a3eff820fa142b2f78a60f413714ca0c94b125cbea7afb0ea720f8f47906d544ca10f09b18bb877edd62ffc942e9c0b946b86c1548f3b6076dcd7d2c359245fca4be7c99380fd1a6d074e8c09a1f2064b6a17cead95c2eb103a211e1a398586c8201f3ddcf65ba5170edd4652bafe91e47bcbad0a99be244666debb5884dd7659582d3b6a1a150a9388e924bd6a6082895724eefebc8aaeac2e632a658d6b09a03d27dd212fa012df87cb363c6ec846ab424e006bdaf42fe6d015966efc7315b7dfdff502821a6b5ac56c8c75f6cad2eae14a38528093762e7d6ecc4213fae99c7f012430577cef1819fd85f8f305047b85c302a6b93b49050371f1f37ea82672361936034e0f562dfae7d47aef3b2e95a6631eca09911f0ac865072b63122ea4ec412c37bcefb6fed85b5336c9922327874014080e2cd117c413598323e44e5003961c24313d9278df35aa39c4c05eed8705586ed7fda9655df53d6cc22a7425d414ecc48f078c1c9087bf5ec74db003220faa41290cf63d9f1d7d4c8d73d782ef07573b553b2fe5019551a1a5333d1537dc740fbbddc495d9766f268d284b8141771d3346b9872259562525e0324fd229557bf9d4b21588b53766f61a0edacade3d12037e812e0af24955c1d350f321d2a660ade026a7efec84e8709122e3622d672c09c00bfc28e0a981293587aef2e9124cd78156799038480f22fd711167a336a828a6f0f0d7a5bcd31f2e0013bc03fa2663e39b232f2ba6a5f03620671a9b73dd46a960fc75ffb779f915c652c8f29634a5229d415d556fadce583b169f61a29b118059603aa4c5b325960318ab5dacfc60816b5a062ff2b4b13a7d2711dc51ba62c41854ae138922c3819024271971f92cc138c7ad74e0b6d3c9d588bc65a0daaf7d337dc796c902a40ff3cc98f951eabd07cbc426094ee20556b2da70f72fa0481f902831e18bd0177d4a4d6b6ef3b03f6440704950d30589ae4539ac87f0147c8ee69c060d059099999803586122a5c08f053f0b974f59a095cf080c24e19cbb76df8ffed88875976cca26b53f178a5094878f02faa69a86e463ed1a7186ec572e6af111cf540c517ef0a228ff2d661dd9d1133b5b64d82a405d489a2f0ddc172331fcad6872d476f937dcbf6e0b2767fa9c9dea7092b700121df9016309a1215c24cbd8e8c0ccabd48ebb53e6c390337c505fc308fd29b35cb6753b7c94c05a37603201b7da6f4fc3b1d80f1eb80bbb63b1a498ae8519fd4e56f06bb7c6f35e9a9331a29b115da216201fd0d05fb127754843866cc2085c46eb7d2d484071c31e26b7be5aed97dad56eb7e9bdecb7503586e5b3f02412e76c05a6c813cc24ae262d1503c82a63c3bfbd4425146b4e43168a5fad75f167fdec699529b979934298f7c99e4cdb24e8832d1bcc3679661d2e2557abe7ec69af9066867972e29d0fababafb477174ac147f181f8806626c5e72a075c4a9cde10d9c99a609737be0c6bfe15a1bd2ca5b97390305e05ff193efe495aee13d7cc00527aa0b375835feef3c00abb878404b0683d30efca2906d84965ec3c3037e30b15a60c3aafdfc01e033a98202797f3b7e18e97a8ed87afab6aead0f3c1ebdf5aed0d1fb4e52b335ea7eeb2c6c2fa311ae62d3d34874d38681f70662596b9a2e6e6b91201ff5a236ee05f9f8b0217f739701a6558b5c684c5c94ed94c8c0141cbf0c4cbfad7a8d0346bd39d09e7e48bfcac0d0dedd5b71a87efff3a4e641919c18a8f27965e803d3b0f9f288c685def5226ef34cf33c13d3391c8c732756be38f7bb21eb3a57bc76f733ad6a62a86c01f6479d0ca3b98b50bccc1c18464529bbd9a5c25c6d59fe772e1371181e233ee90b5aec7d6ce72fa8cc86aec03d82afe854a1596b7d72ccc1e2492cde14587155d99f872f37e30f3f9666a94fa34e4821de52e714a6c35be397f58616dd5ed2069d1ede87eae23926edb8cdafd3afe96b6da635646cd14ff13094eb608c5ebc3dd821294a6ff0b3b7e993d3c1a08079f6b5a2f7240a9ad3b2530ad38782cce067566c9458929f796b6155f5c86f2b23bf2532a60f4899fe37447e59e204b6fb6eee228ee8cc15f611eadaa3bd59b85f0f813d8426d62c283bfa57cdab7d50fc2e9ff895d2aacd04e4f20f63d4b4e0bb21c585bd1cd062e1e23029cc3d6eb3b6e2029fc35e1f0d681b591c59991e9579f08012aa01432cc6c43ccf265ee2d3367951bec58bc879c1855c3c69f809c01c355a6b5a4a96264e6321445ad579be6d99a6b3371386f57d88bc7d218966909f0580ea13bcdb673548665fdb2fc443d77621e84c84f46cc1b0cb1a33049340d0bb33cd815250b78748d3f8e7d1e6d34c4216606a597a6d1d44178084ffcf0a394a48a1672ab90bb20b40c25f17b20617421602c47f363fc91c74f54d9c772fa4f6dd505b980ff209e59f3e55390d9042cb3a12cf28be870ae493cca714db23573aa9e4875e03d003ab836dad4d3772fa64c0932f10696110ee29da02f2c348dfc6b70877fa5f4555af3eaadc1d4b0679e87ef4e55c4b6b5222a656a74d9f453c1faf15ee160603e817d332e18d6c090df15b6e09356ffc8ef3edde1bdc365f8ba6b337da06fd52816b40aa56489d75d35ca56a0c6a0926e81f48e2b9e48800f56b9845b48a1f7fe5beb42d110b5eac99249fa1a3aaa500d1c5b8e0e8d2545094da2c40f633207a310a1eb55a0b243c06642f83a3aa4941d18abd805037e20a4014f5dbd348819f7243966f2dab3ff6c5a0b08f20620f64eea56ed3df0f21bbd349d2d8332a9a9fa22aa33e325e25200084ff81f7e07d68508a2fa0a8c7b62edc8222a45acbad87171f6d1966f0b126a2d59c3c6903ffcfc7c8c9944947ae430660b07b371887af4f422767b5957bc42db8eec6f30f737da8cf89f8c49dcb42c98d65a6fcefc1ac049c04df0c2006708c488a51e1a131998888ca9012d65b191bdc0bf4176edb3c4c94bf07d91e4adf9288f3f340a5b22507b0043ce2056a2ec00b08ffbf237573b5dd8625f4a0169528ced6a198a64eb99d5f626a2e0e276711f3d21ffa8a78d08206169e9e39a7d64c7a51fdb36ab61b41133b0126e9b9b50cfccbc52b836ccdabaa3f112dfded2e6698a0bd25cab397ddc9a0251877bdd82387a6608646558a0aec6e471ad124bc521574069a9604db1d0a0f021ba8a01f28a4efc2984042fe722be76d57cd3c6088eb8727edb31c7739a8a0ecf88342cac334b17e71ff034c255d48f237ed6663725fb9229e0c1ae32649bcaa085673e275835c2695cbd42a8344972aa2acdaab5106613df29058e28b6c78f0a95ee0339aa78b52135ea482159f002d2152a37da540f68d9abfa6c917c5d427a96dcccb8d9aa23572c2284b26fc63820bc12b05832f20881f1736286ee07e836698593406920004700ffa81e52077e84dd061fd221efe770572a49308d27f7b06344e62850aaf90ef7b73c75804d6c201062fe4e28c6289926db9bbb91390531bf9eed98d31e20d14ebd4eda001584f959ab8f3d718189aa25949b1da167e3cd0566d4c60ac0bd0b342e7a13c347b8183e0893fdba7190195aec0aa368b6c44b039bd7703248b9521d959666c14e30b8f01b298cd0876156ead1012c247e58e89d3e8ab48b7266b8e10fce140a429a20f4398616cbe0ecb34294bf471c42a37293a149e28142444e9b02481fb8220f62c5ef5ac90c3f2565c1286def0849fc5613180c797d68cfebbb32e17ac3580611c9cd7a3945dc30cd97e0d81d7afc5ccad08c6682615466b6ddc0ca9db459480dcc9c3089421808f110ae6a98c2ab4c40820175856e31ebaae0d60f52ea458e80f80b33282a004512bf11bb5ce31e05f1bd234d1c827e8c909f0a36d15a7d07362aec295a32bd759253c65e7f22ae4024013bb551f3a69342905c6f51a940b9ce8d673dd8bb6b2fffa80700ccdc025287e9afa6ea5b4880f9a549af45cd3753bd29bd2c3f69dca17403d45f8f462c89c6ca0ebe41b9c0edc830cf4db57bc97b5419402c57b9344ce44c735c6f4c5c78760f2988263108e9549c0617f96a47fc279d1f9121571036d2790654a01a12c25337d89148e1a3236ed6bae70210c11ac140234812d2f9bd73a7bc44d0e55e3aabe69a2691e0f19f0de224ec02345d520fe9b38fd552794574a93077231bffeb85157c06acfb3107180ce3470e2993c4b04691fd2c231848b2576a4011d0d5d1335755ec1a270ff3800a672c69e1c8ca774eeaf56d4c88b73924bf807d1ac1396cb40cad4ad9c820f5e4c377df8b9aa477773c5c1e1f092ee176cf3ebc7a482d03b583c010e6f34197c5a6ae8d0cfaecb91d6096735a0ba17d4c050ed1e141715cb5b0b05404cd3a3273a033dc2ff00726c5824d85f88527f5cb70cc3343ed727b8e250e868267bc09fe32a68445fcb4b2bcd9063973e70ed1fdce0b511cccb4102a8f9b2a5f6f448d1bebb158a6eeeda2408b1232aa6be0ef2217b7dd4b20756812dd8d33122f226a3c76788fe4c8bb012882c4c9adf38207b93c2174be7921e7ad1c111ad96d024b942b30d859a7ade0e727202a5cb1d772abf504e25dacf92e78db174581cf46de70a1a137f5ba3caf4901cb2e3fb50b57f5872d3a4c19094af95e56e82a53ab63aa3df6e016718c4f93f2a7c4d02797d6aa16833a85859eae7e33f9f3063dc2f16207a834b069a11b30cb6932ccc900b8d182b9f9c9fd9b16e332b8edeb890e4e156f50df3bd5b95fa189b98bd47225e2240f70f0369ce055b3f84d97bbc7f20f5c1ed47a551eebd8c0d03b5d7b89e620eba557da267f870a542688725c8156bb51cd27c8ba654e8bd93a6e417be1b0971149659f1d8c4b7f0d16d5f954dfedd3496643022494f5bf52fe0d08e7767c05f5b5241708f94cd2c883f15c91dfc9aeafe73c32bb0d680854dceb192b0b405159617510c167bca3277831bc03f4dc9025bf040caebcbe76e7dace24ed5e8473d32b01a31e07382e34615054e1a70d53228a5cf3972487cadd41ce2a202e7bd5841fa1ad74117928e10e7540e693c04100880964d96ca5e3efa4b7b2c6fa35f52c141dbae8cdfd1c99ce0bb5e1a171b011d2903b74e12aa1b6bd02d82b3f5ad83103dae1ac14d93f80c51ef04177e2babd28e9a72e920c7b6a7c1da6c9543cfe5a62d8a9191d350e48b121791b55848ca2739f2f066ffde3e44149841f513353004ea438c74aec134d2fb370d33baa87b5fc3bc2538ee8d643f059ae2f833f62321d107a93920f238e42c88737e42083d9f2c316c30fe52e221bd0ee5c1ad03432e772e0ec8c012357624153c67c84dc19abe453ba6881f817df13b93a63d443cc5e29d3ea91db0553a446814dbeb7f9d3e474a455a0125446da08adab8e39cd0f48ba2c19b4226bb1ee3a7b5506fb6a604f5de8846f6a80fdf7c79c8a7135f811cfa3dfec6340a9789ffd8acd60c9138cdfab1fe464c05fdafabbb13e9491db63fbb943abd233cbe93da19319437e27902119bb8703a10167f4b95f737a5e416e4b2602cf0da661fe4160389ca20886e9b7004c193d4e5bca3392d0dca99b4e6816b1a7fac828c8e6f3da8f5dcb92881110e2350b8b6847e5049065b67440f41edb76152ff5d1e80f0f59d5092d3a871a70842c3600c80c57c251c5f9bb9b3503a92d24ec061a7ad123212f310b17d776714da544099ac95901d6e5d2cc17e19b46158b3206420b353e0c5176c2159093a7dc1fa88a4419056f2f2319c43be586c80d411881cd490a50174cc0d9a80fa58556d690da3128763dc9880c7a334177a2056ac31ef81e0b600a068a03f5314226765d8ab5183a70be8b011c18818fa9481ed5f34d1543ad86690b8d00142f1c6a959e5264da894ded4b412900892f8515aea49a9469c3513024f7086a5d142b03602ab17ce4d55f61012568b54d25be47f0865791cb9ed45b37caefc04e4ddc53ad92f5493a8719151e47a51cf7ea44343704746ac124eb0fcb5907315ac5404ac83d1157920a2dcfb441b0598b110a2e05862e2f082c5c3c8262cc823d17f36c715030f35bccb054751b8130f9a514bd9348da8b636562b4835647c65b4c52c53a27fd42d7d380fb9247887ccc8ca84d2f268990bb598b9503a178df6c83ec1141a6d96c818e5937a5ac6ad44db0fc19a1bcbb84cbdc45f208ebcd5a608b484d4e26a7d603a03097b350ac1d72892accce5820c93f995291ae592d3ecf8ff14460f5009d290e7c6ac5a6be24c90d424bd7932ad15ec2b4604f21c917ce8b2b051e17a1509fe5735affbb59d138d07f98d8f17b906bbe1c791d0b2205b0dee2f6a72ca828023bab21be9681869c959e9c261ccf2da9a78d76af9d1095d45576322faec600cf1727dd0e1c255d296949d54cf3a475a6676249b0bb56e8bd33d5c5219f3a90b3212cfecb9598b751b74f49b5626d0c7c25cb81013c3aa49027cf89bcd645d5978b1df9a77e235e15245c605401e4d966f0ddf0fe92741bcba862b5ebd192cf5f3498e0c3d0b564c3f1324042cf11af561c864cbcc5fbcaba26164ee117538241d7e74afc1e1e75a23e58fcce0000442456ee30ca77f55feab4dfa16683d9ff475caba68f7d9a77fa7bc4f8d810f364542f386bbc0132f205b4f410b0af3aca9f6f4a00564ee7f8cf76d0ee52f9664ead5e41d0970375cde3e18743e3dc24567b6d8af44ed0426901578de2c3cecf133bf58eee0c05e1ab4cd62fa20d00837103ccbe01f358b965f1c9400ac25c1863bea8d94719e05d1a8df25136e0189d92052f44f261ce6584594a9b4d8c5530653b2f527fcf8ab48f82cc4e5ef9df20e060cee0383ebade175a6f77970fb3cf9b52504a73dbc5a813ee35a4a0bef710ebd04f56bd0e745aec26c8566c6117560bafe731d5e4582e49a8cffaa5e370077eb428821920973a5985de97c3e0f912ca87c37391ea1374d213102932ce600f5a3e148e629adc7604066a20c4aa710f849d0fe171ebdc3e453a699f7087b73a41d195ad0fc0aece911cf5826475a88d9720560363e3e54a200cc20a728e18f14917152dbf69e8e894139a2829c021b02db04d81b2a326b90ec832c8c2b190c316a7c9fe1274d5b1b47c2cb1fbabfca628daea5c40bc0b25205c5cb0a1fff6de046e02eb0ce4fb8c6a864ec21e3f270caa08064dd509357c49564ee5a11690f0202a307138e0cc116140a5a902fa600140ffd7f53edd5eea129c669d02bda39f59683d51a1ac947122bd0b0d1beb0c9839cbb2e22f93fa14ed0567a1330419e7cc7360e8887f2ee3e3de6aa81206e88962d92da60b8004ad70b0f833a3b17be9700e6cd50e7efb08035932b7597b0d34c663393cf8eaeb652e4b4ae2628fa6e3b01448dd14fae377c572284a9284aeb0cfa51ac74847dc52f9d0c48280dadc13484332e76a91e34860aac007b4341d818648927acdcdb42c769826d875f809ea40e56ceb21cfefe242ee04eddd57ad0d5f3b194a7f2a3df50864804aac12bb2aa89913bd83a2fb2bed0c68e4b33e299c2efad18d594e6388ff9e296d44cc816e737cee7205ab08cc2380772cb096abab460ca10ccd7030a28688b9238e005ca2682bd50b684b743946d0194ea8357f6c1c04f168a5b0a4bfa569b05f8cff63ad0720bb28068ba71f3dfd727c24d89a2aba38e8e05fdd09cc8ed03e788ee3896e6190611331db61e857b0142da5a3ecc295b9b7bc84ea7c1125058cfac174d25844c6da3adb115037a3de3969987932b9676eda2a98a7534ea36f62e6bd51b95adc65edec716b0b1badfeb219eb31d38af5cc3a1e9958c10708ad5cfea18bd8b41c2ce10a8e009a3cf5897f6bcbf92916645d5141c5424c05e7988fe0f884f34dc126e28fc07d4adb844f45eb925f4f7c58585eec3b367f136884b2342b07ee69e7d40b8ec14c745cfd145c457954617652084c2428ad1fb7e384ec6eed53c61b5a2fa41a7e28f8cc80a5aa9e1bd7da0f19c3513a68ff7d781535e0a11fc3455a3ac44c62ff859a22549f327ead7a75bd84e287d70416eac1173dbb8aeb2004d56e0360314e63905000d00cf67b9621e9b72e0271750a5140e55d960a0821ae3a3593605d96dafb2b005441a2d00c6748d669c114530872ce5ec39db47a0ef475753f12b1df4b71f5ec60f2228f20a3654c343e79f7b1f18930cd5a9e28d0411dacb601be43700616bb87b167e4d8bf02c5dfbf317fbd26a244a5c34c544a4b8a08eb8085144b16013af5b929a306738d94fb35f07c25809b25840c33ee2ac14cd599dff576cc9ccd46d35f3787204d5f129fcbaac83997d5724063e60c19decb3433e7bf572ddb387559acfff5f9813a5cf879292b0e5361d49c151dd2dc947978592fead9b440d03ab65450e0d9d4f151451224a36ac8e9873a7484f6d0e6556db7eb5ab74871381a0fcc78c15b6d005d3bc3729905ccedfc981b101640edca76745521bd2352ee69e4d21e418e99627ec983ede119420850e2413150689837d93f43789dbb95d5005f45245df888bf4adb22a7c402560347a20f8260a1b819627982c2c05eda01871d08f3038292b821a26839a0bbbed07e111f1c11880bca0f1199a78e5fd75767d58ef0bfe150a949c6aaa3dfb59c92ad82a353eb24e2a17dff246d79e954ed489d4561f549ebf0253e33c8c8b886eaaf51d590cdbb92a9b0742b8e2e5e846bfc21cfe59859f57efab612f0aacc1aa2302940dac450ad29a5bea4acb2585525a439164a877cf0fc57dd999fad5a025c1a76526b08436edc6e5ce9243496893a4df023757fecf8ae79f4f4e8410fd73e303f1fa227c185291962347dafe7ec979923f6571034e49b2af52587c5ff239c75d2341a03c250deaacdd083e5be5a7ae3daecb235b759ea1cc284cc9c4c9e8ad1b678a1718581587f2f2886b77cf0112e89770292143f6f0ee36a53979c3ad741fb13dd64244b68ff3207c1993102104122aa40e37b11fcfe32ed486dc1d7e2b74ac0f28c9b048992504b5c7c106d6e259abfe39b14487914210c74f6f44b0d33adc87a86cb59db25057566dd4f2f656fd3ee1d09c688487ac76ebbb9d1f3d5bdef0fe2c5396d741af530b0b3aee4d232f4c01170268a07ab8f2ae86870078fa505eb3247d186328e9615e482636327d1244798f60a648c9efd7f97665cf220ac8cf8a4f1e4997143a64ed8ab320d3745e762a5660a430a06edcef2a8a70aaab8fcceb9268a112abefc1534ebd399d491dbb6e8a4378658dc03587e2e29d7ee62ce4ae32b27facd07eb09c94b0d91a978c1993d4f43f335ba2322b63d467e5d29cba507878c9c5dc9babfb9f29dd7e1be06c8eb5141afe2e14c736fa1d3472b767f25d92161cbf5f980059625b3c8791ef5233d1da460dc3522c41e22cc2b42dde32e84196fb81f7342e24e4564218cfc4dc74f34d62cf72e81016a343e08d980dbf37203543e5277dcbec88afac931517042595dfb742d5933786fa7eab717014f400a8eb08506e2ed6f76cb5c96ae9391bdaf3bb331b59c0741719bd5a674f9a9b1ceff6279612560b417c3148b0d8dbe2d7dd1eb1bac7b0c819d04b0d651fe124a3bdb044c94dad92e201708518a5aab28a5a5a16cff7630db8bc07bfc875a82eda42c9ed0c91940dd94d3669f0a44327dc0c8317947b71794f46f75162902b73d998e88d736f085588efa7e31a87a282f40f9eace27ae58ee0cf186c812cf3199deb12847c95b78bd241c39b70aacd4d7fe852301bd53aaf3fba4c50d99a203180011e8eafa2591c4e3539b7617befa74ecb52fa3738dca4ab18d0da19dff310fc77560ee01397693a175b2b3c901ba2950a81e0522dcf1965f0c8ab74a1399b9b401acf5c47f483b6f13840c0a56f3aa241652a709a7343acabbb414a51ba295bcf931b51e215ac7afba8cd915b1f93eb3c8479da6ba88134eaa59cec57d158d664b196869c46d045d18e87d6e5e63fa168dfc6c955cb28eac18a0e6e9cad624e5f4ca587b250e3f865a9c9fe0706ad549d39298297344d6a52939c4a0e7fa93509463ed6a6a647cd57cd761d09f09a57dd49be3ab434e6479aea044aa3995b469db442ac5aad00f3d79441499031ea270ad8d7445610241c9df9051512c21fe968eb60a758f3ffd634c3cb1f216687f2097f097a59c382d6dea3be051aaecc5d04e9de6f15514b91682522946d5527db4c1a3eab8700bf54858e4318a53a825a206b517d23368c172ddaac59e7b357a5a7e6fc91372ec209464c598bf7a7a64349a3be47611ed49abba68116dd949fbefb0aa69838ad682d2a789aa2c68df31bf9d541fc7df9b94079fd637cf53856e769e2fb8430030c086b0192a008a3379ca104f807886cac25481301056d8553c65d1c45aa2a21b7891560f624b604b5c5921320580cfd6c47434eb330dccc6298484bea09890e5d70fd896aef8186a2c4299ea150824836bf5b8409573c9dc4aabf64a64edffde85cdd73d738638b78e215fbfa3e6783c56cfbf312bee9b9f796d2932d3a430ffed1010d09926382feea24a941ed12069a582c7ba13627cd5a387154d664d75b60840c065f0b842b0149029b7c3cee4bf4194f80d9a661e2606b631f793a0524c4ad2ecea191a05c206ebb23d7eb4f9e78b4f654faec285ab81519985131ab61c5b92f117e50df62ddbc89259704b3071a38a3a2a4c7d80e8d64a0ba820fed87468163b9f1d478166fb9ffacdfe31081e9fa5f29911db2d5dc908e83c0da4ab50c1eea0ee3a371b76dc6f6defc6716e18b6c8488caba71d8357f01b370b36cba39abffd96ded470b9bdba4481dba3184e247b5893cddfa57711ad197ebd22763eceab4e48e1356e73ab9025d89bd683760f3a7774724b1ae1be90511fa740b8fb8a2c8a2e55e27ed1d7c11488662810cea39e2bc49cf95e5d2a75b1663fb10e995b331a19eb455edc4868ec6ecef7dd57a62d6675b86fb0a290304d681b0bce8a55db49a3ed8dbc7041c09a22129ac8023557b57596a92f033ef37f528064f5105c493c8939992d678e86be32699922176b3b6e1d22f4e98d8f796a3c1985d7f46e1f904e91520e054255452961e6a07b38b83d5453226bd42a3ceb6ecf8969e2ee53e3e0d1462431203134e3ad6e717887c089daf2c4807d9d77a632cbf226f6c7e4d3cb655fc0dd2d978b0528f5df21e53196b1da2b810592ebaffd46740ee189ae9021cc9e17f2b9bf442610b849b1f71445d2e6aa44f5aba272e509bf1388a7472a1cdec5f3d00b830fe6c1c59b5de60655b46414a7832d62e00ca51cfe067dc5c99f436b7b754ab5f22fea3175eb6e9beace56be20c7d0a7dde8b7cd4967ae390ebd9991549b6e6a51505e5d895b57d415b7ed7d7cc4cfc895089294e0af2b4c8978785f52431c33188a76a01cdba34325fd391b6d644862f885fa6963233197999db8f390ca7a7131a91cc1d62a671d0cf507c7679690a974a6a3975dc46c1e77a9146cf8bcdffcb59d0e6edbf117d027b189256580174b5b5dd86ffd2350a658aec68bf5cf21a4e4068c63ad299a7b0cd3bdbd066cbba9fd847ae5761f55521c00de1ebe4e07abed501b2b31cf0eea7c35f07482f94151c73790c012584f37b8dec581a20457fd48c3d17fb2a2a83734dafad2fcc18d51228bafa5d8bd40c54aba16a4c8e848b9752a3e4fb03dc10a3666ce1ad8a552688f47c0d3bf5cf5e587c8323bb3c740b0acf312437112b0a5c8df0b632c8732424070f1059683185cf02b40c58ee1ebc120834507cb631b9da2a7f857d240ac8527dfe95263de9de11ef366b04b4d608047d75d09e1340368158b7e7ef7feee1c12b4a8812bb11e281d680783c5438d2b0f08de7a242b73834426d4542830899864c2d320a283d358fa920c3a677b5f6d802fc80eb7572408f0b6baabf68fcb9405cff55be9f142c68805bab8af69fd58e8c0b54e5055310c9a394f705f8f34b5a0b5740d156a6fcbee781df0b83aa17f916e0906342d34ae6aa70b135524e208962863666c49d6fb8f74f652fbc26c797865555b9d2e0a5506474a20f1e4af4137599f7d3a14cdd87500dbc238340a9548e420667794ef9de41a979dcf9d95eab2c44ca214ba6a5de1657b90445a1d087fb0aa4216cc90114a238a17042b5c6302119f024d6cbb45b3d04b9bdb5b55463b689a2196496927b18b05b3c94b17082ab950e08ceb6da21d5054d869a6493659b35bd4784938656aa1001dc9e4cb695b48997e3e2351cef82376f2f4f122ee73c7027ebeb3944945d2febac6344e59a877fddb27b44e5492d85e4620a1e77c33b321b3a289f35ac755070e062afd4ce1b16366d0291b679b75c8dd2252da65175015a06c095ccf889d91f069876875c43a148e139f019dadc0b30b535d274b40f41d2aa5eed6c93b38463690b6638e067dd77afe0c97fdc1cb75e034e83279a58bc8cc8a30952396752bc963efe5a2075fd4a624e1746d30b59176c9ae3e4a028b14eb449245c5b653ac1d89af2ee8987560543c5e2411949d24de545306f1c78d02f4bb7c026c8df5b1319374db5472fc438a47607c3355225f25c10a4e0cb5710f0369db67b5872529115c055e086761960b05e97d44748e41487bcac99917168370927ae8f2f5d90125671869f6fc1859a853a5c247276d0553ab17f63d1444fbdb84f182a8d44f08a3146cd7099a17d93c2d90109a808b72a68284fd46c885618c49a103b8809e80c5700e6c0f1578f841a58a094d3c7ac5aba39648765d9f603cfc44f0c3b22a6db4106d4c63cea99277222297ec9f96d631ef1b533696ba8cf7734928bf5f3abd40a28308624d1575ec3152cd7ed0aa6864b07b64240ae55a74c68b01bfd7a426a5a6ed63d3d92b6cd150b932f7e979b0e159ec034b9d7afff47a20aa062786a767ec7fd2540c9e9b4a6249f07e0072995efd229ea9cf1fd3e0fbf5783a330f39c0509c55e0cdcac8c92b7026f72469d1b2fec26887fd1eb0964e006b914b059632b997bee47c078576ba4a95cd7996f40f6b7812530e33140514ea884f8e8ad3235bbf6a49f085fdd0648d901346f1ed1a3881e82c83bd45a16e1d5d6ec0022b5f023858d8e379e317a8f3de968e3cec95b07690cf12f3e6bc87d00ab58fe5bb1df7f736a1c86c834b04ccaae98f440dc66fc235bcd7cbfb0441dde4c7cbd5402e24408af5fbde4be78b149f59981f9f29d1763262cfa443b2933ec80d80ab30ad02efe547e38487d654ba9bbfa166a05c60667002c778dacfc27748216b1e44026aba08ddb87586e5e2d79dc2b5a8cdaa534130ff634d0df548aba05bd91db82686648f7c7e8d5553b58307f19e5d95abee1c250b016c97fd958d0007d2d0a98ecfb92aa92b2099006e8f27a1fd40a74bea632be7197a1478c412fd7b29263ab6edb5f96f48d05388f79d3b2643a266bf18aa5f4966fda0976bded15542812ebfb34241153bf7b2fb76783558d045902b09324c9ee41657462224475714e82a943f87e026f94b2bcf4f7241686f389d7e205bf4092d54ce48ad364d708196ddd92bbf9186140def5128ce47f4533fb483d021f837fb16620b8b45b08f6b67bcf8685ee2b82dfc9260cb32355b6c45be0dfe2edfe18225f43813af4e56af63883356bee20cb3dc87d6692df6dfdc3c0d74b9cf11b80b43054c26f978f9db179b37beb12e31a57135cfd9d4b75a04550d9009b1d5d98710257fcd47e7f0dbb6712ffbbdcaade988b6ced8a947f73819fb90402e81983ff77874a9eabbc309bf70d96cabb4284b1f0959ab07d8a61a82d09477c0a8fa6cf3814d5a24f56591c27841365c841d51654fa2c205abbed2ae9f8f02b11ca76ac5456e4a496b4533bc07a9724557dd3844c9544cb2c0193049f38a5646de4060ebb8181ae8955b404deb71bbda7a7870723fd9369a42a6b602e23066462afb080adbb292d108e1f217aca6cade32e2ff4f8d0b92e661bc1100a7b39253a0a82913660870005c10c1366631841080fdafc9b06090c4ace82d0ac001d6455e9f6677cd09c3e1fcc47718e41c98f4a9731dd44eabd3782801711cdf21da02a51296c9e8888108c52b8f5fcae3c271f655474f2ccc5dc9ca7de474418e70d333700d50e38190287fa794087f4990045bf7f2449057f085a3a818ca8b7caba69097b501db9515f01c9553053501bc1686d17165c9a662e51c4220b68fb0970f66672f5fc9a218489801c8d388b55592834d3c641b558f004e481f064cd324b45b894aa20348753506f1b83d5e04178787c25164968a62c5be7294f60064c4dcc7e517ac2709d2e17c39056bcf3b4297b8390041d478098456c38d30ad5c00d502db09da613ed2b8257a8fcedaab1ba8609e892e33ff59f96946b5373ea5c12829030666093406a2c4e5bf3544043ce37b45bd0192163e098a21298b6ca06b36a88f228f062a8287b8ed89a0ea626a354985a4b6e657081f231b1b3649c17329eaa8b029ca88ce919db5578d27c922ab861080686353686fcd7b0f9f36b34f990f9678f246f73b5fe9650f1d6d5af0b50a37d481a80931510720ed720ade6cb491ea8ec68b7265aa92b2840efd06c7c04cc7598442645a694173064e34b0bebda83079a83c9b204ae811a6ba018c4c438f59d29a44b2268002ea0d6a9888e9a807128329bf9ba316180ab1df61fc2ef5876da132d0fa5c39e16983564b9926c60af4e2e519187fafc498e488cfd7b468c061027a2561ccf3906965f19cb15939a2c95e55453d61d235f83d4acf62008575a64d5a45adb7627d76d8160f10f5fc69243d783c4f64bd30185695de027e3661c2b37218f96c845154c0bbb0bfe8ebe93ef355888be6d2dee2d930594cbc2768e6a1bcc1b8033833b0bdb73e3ae671dd0013d84a35a3319efa6438144699283ef1db6e06a0cfd51eb86fcae1f3739d8801fe7e7c0ddf336a885ecfef1177fd18ae5488b69a28ec56441d83873ba0811d9a996eafd474d6958a672256d5688dcb305886df3b143436ce131a626c5ab44eb94d23b46bbf28c2b7f7fa788190e5a87aaac58916246c8671d59ccb7b3dbd171c34d53fbdc613820f6108525e07d6f939886673771888113a6e48e79691a5d9bf813749168310c790d9ec58c7e6014affb59b5b7cfe9b572a3159e64e90e33e02961f1c21b2c2db4844818250afdf8b27e5ea8b30097acecd0264b830378ee37681d5c1003bf35ca5de1519afec742f8a17ca7108b28869a7f2173b69bc6e3664282b7921bbd5befd0d04a4ea79542fcafd849149664c5e4dd88dad3b84d53dd72f74656656aca27fe0bd0a62917460d76fe123741aeac1671b04d53972a0c8bde0fe0d7a8639afb8da9a501216bd1784c2610816c14a41d59d4bae9b501549df79071e10dc0d33d62694b8e468f7bc716f9294fb143cd4e5cd50fefab613afa6586f1f560ce1c8f7cfdeaf6c81eca981b9df6aa786d9d4727e0fda0cbdcfdd1826a061f721b64155eaeda4835ed865d91fa868fde66923ffe5ab9baff50a3125140923c468797e2bf87df2173998a001a5e42eb1c5e7c9fe389af75640502c65ed64a1a5a8fd508020ca638cc23eb1e7e6246b6ba3dcbff21de52a276bf37117366093682eda3f87aa016b027995c6ec8632127170a68a6b061ea6017ec3c08f193703e0e095063786be6647306be7a7839e43219c7dba84af0b14d10d98a5fbf9fef7554a3f1ee605a05bd148e4c81def116b3ad8568c62c0b90e3d68091854e59c36b2d8ad334ffcaf118d3d5cd7afa38992b9f200008754ef1636a0df182ae6b7cf9dc0a4fc91ca2da5b0d3e0ec26e065f66236f5caf2ecc8f9c9c6fb29be6c1bdb75002358a7f06abd0275210736f192243c920d749e3cd9980b6a05b7408139f465db3bfdd251a065423ee2f1877b4d0c1f4d4421be8fae41c7b2778a48d6e5ca7769e1f751294ba8d770201dbcf7d686c8058ce3592aeefe7c4cfd162845affd7ba06db37b80fe1d7b39389089fbc6e8ff3d6ea028e339ad06eadc881fe7344d2d84792c471e5b3a4dcc7dec30c6f2e2d69ffee629ab46ef881dc8122b37b25065a8506f955eb441ae22f3aca8c35a50c7deefbf68dd99143f1d3e9a65c3c6ed4ff0eb7b26336525547bd37930ebe04b9c3fd8851705f46db55c6b51701467ad090c51542c1257367eaea6f7ce06e06f751b565a3509d77c914d902ee6765cf19494549308c3026e90480d815e5e3b73568fbe354b2beded7d7bc415da58b7450283809f35897cd1aafb9570c545549f4d2eaf82cc676642b16442b828bc2f89c02025a073f452aa1c8dbb0132aaaa88edc9ea622bf7cbe1b9202b854d7a03b32bc1825fc5826c3ba9facdf430389241d1e634962863b41d1ada7e88f4e01ea23feae863d0548cedbad5245869b7600d89eb54408ab723e49bbab213732b5d263f0988f7622eb7a704c92153ee15c458659f49f85718a5c2c8265eb786ea21f4ed4077bcc20179c61b3a4998031baed5c900a4e2e76b63bfb46a3dbd951c5008f26746476e868cd44448d0ffe0252b46c4a753fbcc9026d3c4386e486d19bcbcb46c34ecce1658b6d435bb063838716d4a076f544a0a5bb8787457044bc07571071a930912cd50a3cb1bd5fc564c920e08dfa4e5879b332607545e04a409bfc366748962a5b7c046e5c43c0e40f0563473db91168a311e08ba1fc959d544f1781f40617437160dd28a2641fef99d6b714a9530f361146cd40d390f341b7663b144888f70ab73f31c0c3f26633953d3cbe92fec25650a44f8fd3115a8388de3d3126edbef06a153946e5096189a0a89718f764c91e870284e053edfa35113536135214f7f6562c7035966219436aa3b1f0cdf0f67a6ac878fb0c32d4bdd2e32254103890750a224ae12a778826e8df1d1e6c9d3e764f18500e1914d4f751d0c90419301f7291b9f065cc05894db309ae84315044d879cdf7f213c4af67366b1f8341f4b98f3c43a9e1128bd858392f6542a9a3bddbc0c983b1d16eed019ecc99774ccc1373c56a20a8876ba6b77aa02aeb09e226aa5c8b5a71c015ae2d5e406b6f23c3e9983da227e8755c41e5688db4c5017ebb817d5717f108e2e9b7b34959b99981fa6023dedd0acf01b1f8f75e815cea0974e0860762743a14203a12198aa8c09ab638d9148426cc001b96424cbd619a280418d15a5ffc06bfabf3720f0b8313fc599b1b470df8fd4a57a7e3fd0745566b1f49ed8d4cfe9c0cce1d2a4099f5391d204a6e60c256d525f7dd5c6b5fc20d13a98a0ea31581027155683d1d2d7008f23051249413a8c6aaf33a07b8edc89d609ecd83208633fdb5aeca995d4bbcd89ee5d13ccff5929d6c09ddc8d2b9a9fec48b170ff6b91518b4a9f8fac38209e3d0ccf660e94ec9c50ba8040e3732b8155857dfdc8f7d4263309446e205a713adb486aeb00ee095ca815a104c00b8f594ebeb310b8e6201fcca9a8e1ba4ea7dd21da3c5a1e5cb42fe544810cb14616e0ba6b6c39443e75f569e8db0e21ba64e3b478bc1c5a8cf77124049d9a869085b752797dcf0ed29681a52582dbd64827b5d50a7f16bd9b9188a10d54b8637c84d6a21c2c6c78292ea6b9437db26b122ab599b50bd8407c22ea75e287f5c65a1d8309904526fa566f34e89bb0c2f3108c433d835782d6886fb51c106fe2e782b0aa233541f0d38a0132eec06d82b98993a6e5f13672e87cab729886bf2e3db3881862f924ec266eed0226b56edc54355851367a1bbc75f966cf167780f821e26851343758187ae748cf6ef576376f9f7dd886808accf6689ca705b272d6b6f535573c78f9e41525209a1888a9e91353fd5ae2e43ca28e9e00a2398951ec15601fcc1f44f768804f887aedf087acb1b51b6a63ea5394d76b07a4ec0b5eec27386ba91fd4542fc0010be03cee44119e04ab13f1276d5954dd042d1d30d62309aa09bac3faf831aeab222c79d98513d5091d2b926172b4e5815cf9c44b5de0847a078c067554684e99126802db1e09092d05807f6096348cf4782e4962d128fa6407548e51d0e38ab6435c7bcc4656174499dba15ed87c201243657210650d40c295857025bd894aeb050d814f1ece9563251456b7c453fe89c3950d8b76cd88813363b355b920c5b549cfa1ec3507acb4104c136956e6c7b7013afc5343585c3d1bea3c55416160720b42a591a1ab23a39b00340605151941a54776c6488a2ade0a208aeea3142aee80d5283332f8a535382c2f13b2d6b4648dd6caaefae6ffda861e733fe5740a1a52532af9927d496b741292f388c6f19617bdeb70976dea5c5332bf5a0b27933a716d452de062ae7c1dc9162a8b429428f5404be18a70e82865257b2187364071f764f4f213a2d1dd8de743aaee9aba2a0be5fb8a7948078b2e6fd2e201e5a607ee72dff0a8454b33b6834d34cb534f7af9e08e0d287f32f12595bd9f481b6d5a43c00683101abe5f92822149be465a864507f614663691c8da5f1340a6880ccdc007d0d15cd27810140d78b50fc944273d7dbebff4f1c52682ad0913e3733850be2fdc6e0e69b72da1ffb33d2e83501d5c3251f1532f0890c8cf7323e058fe9ec7b6bab733b4f549adb73522f3eaae612ae5420554d95f72c64a4a9e3c25727a627f1bca289b7e7179c85f338da2d48b38ef7c252adda771b907f8fc3b71f798cabc317769312d0a5cb8c7e5032a69d7ddaf2ff1867a787359961428b4787f64f2923ebe98bec2bd9a86ff132bc478bc72d5f4867b40b88951d0e3faeda1e353e0db7089d3a00c80bf61d5bf89d71c4b171ac26ed3a977a0736a6c29225ef31759d2a8ee467412aaeec9e9826640418aa642d2424dd5b9d2da8a2acc0aba2a008aca25200204a3e49850ead4a4e74dc698a0aca8c76bede58bfc18a6981764851d6e2e289aa2f9aac70af399b407b7fda53c2a783c2a322ea0cbd2ae507343c8ce6c56e9ace97b0009966680114db0f3986d2cf101f681650e54dd0a840e76e820aee10f582f8036b0bc2d89137227180b6789022c1183343217dee0c4b153be9192ef0c167c19e2f008b1dd4f247decfdd0263e9407e1297e8077d5e90beff1195c9ead26860be2d24bb40aab00110ad99fbeb7a74d5d57d3f4e0f0ecf12bd571abf69410e48f9a70aec248f220280eb1202510bd66038658ac6bda6a4ea3b43bec26e562656570a215c5a86bf8f384852aa87c6061f05be79f2285d9007264e2c4b6b98925ef126545fa9cab32d5c386ee15b8c66a36046353144b9c84e9bfa96a2755f42dfaa750919aa434e069e30ed3547828aa5c72fc04972acf613b418ea643bcfb226004a339301778558fbd8eea2c415805deb3da2e767923e3d6ac33aa9c7b0a67a8c9f12fdb68dee2ab17127c096c2dcefabc6b41c993205e36dabefa1dcad71933c11c237daf355c7cd04f4710315c5be6d78c37f3d69d6d3b251d3836f46eae66fbe59bb4c3c0f88547f7bfc48914ccecdde1e1d9062fcc0c252157dd8268c88175d10506bd26f9362c2491b521f64385e049e702d9e4c2ae479ba2e7798ba71e58074348a1c024390eac9282697869dd1a3cb76ddeaa91a6c279deedaf702eb118afb85bca06ab3d3c443f24aaafe4b2a23c30e490e575495b00a40655fe3196a97d5da7195fe916001cb8d833beef90b4f4ff119705fbec778b860d5e0ebf94b2e57111ca0928f6650e9d88c638075b20a89b885eb054b34d04ec13644dd283ea1ee4ac6522f05392ec11146d4cfcb1d4367c7a06e919bee0a901d63ab6d17bd64f6723f9958b786a66b22f0c6c5bb22c9ff882456c19aac21a2e603c790312d4a7ab32ee55f203abda9b1cea3c57924602d99f47965dbcb07c9c6e5e6377a9af2821552730259bbeb75969e37df73eab0df73ea4e48a5437151e6ffc9c9b8c32612f23cbf3607d5c1aed1dd93cc28fee028d8ef64f566262f61cf563c0805b7f84def4707942d0c482d830a6bd0e64b91cf887f9c99ea08430a8c1ca737f0c6acee101e3080f519c1233b4e3fdf5b1ef6bda1699a395c7493f723ffa2a81f8eb16ed7b58add5e223680cbc8dfead3a7791b8efb29a1b2d9e244014170a0bca76ed8162ab55edaa25ca8a8ab4ea9ccaa5b570e7b0ccadf6f28385e03d63e5368d0994b8950ba3d306d040473bf9427c0e315c24ccd86e044d861a8119e2fa19e3fb98ece5ec3b000874278baddc03d89c2b6add94e6dc6f6d4562ca8b6aa8eff122eadc5cc13ab011a4f0638ebcb1ed6684c737843de96bb4e8dd50c12ef5ca3926a266a811116279f2a719149962f2d65d01683907ae45167c09afe035ce3a251a5579cff9b888b7ad6a89ae9802ed5efd529f40f4c91a6efae21a6fb79a43825a41c1d4459a835d1d666d73578370e994074dd2945067ced350e26fb522180404581bf3f6d0ea0b257ca6edd56c20d77d2f89e68174ddbe83a6821e153e16cbc471997b5f96fc0788c4bbfd8ab4c97fd588e41bbebc2750b8571390cebf8c7ae0ca3c5675d19ee288b9241970ebeef1c2059f2001b3a940b10ba589db310450de9af3a2e82081ca9218f290f269bea22634ed999bc0f433f9295346496d6114fbb971a52531253ff618463e6470353be40bd5e87a64060782b7d690b355c5944ae8b508711a334dc48cf091ea1e8eb2efce408e8f6909bee41eb182d39d705122b03727e65d5a1ded9c6259a773c766ec40870d141d7a80735f7e5fa26b625ac8d86b1bfb20555cc879901cdfd5d853b751679ca546572998f46b611ea0a38e10e79023b5c5eb8e411b7d32de013710ac0f8f17d8b142ad30094660a940f5985c99369c30c4a3adcac9b5a286a3794f91e0a17a23495285f57143ee8d5589447604681e9b40ce95ddf01dce8145b7994cf84df69980c52007209d99551ab9688942333e320c9ca1cd8d2db13560acfca127a4bc84f7e8809e2d97c36a6958d800e6637a3c5e0e162c13bc402a47eeac3f08320a073bab76029109f2e1983f2499c4797845ab04a239b76470071b702e5d3735236edb0c46ef492ae52b319f300746257411d301af6333d7074d49bea3932a21cd9413e0425efb559dafc006a8e891bcd4490e4e0c99ba39527c70f6d0e282a808e9c1a81b96c729ca01c50cb911373286b8ea9d4ff85ea221a315214421192183ac739bfcbb9803e44e8e80c139e21e5c5a41f3eb2a543d36b991a9a5da3eab71593e30a167e8ed9fff78af5900160cf93a9e08456d86762aa393705e8858c34f966b15fc30d13e6b5c01583c10d3127302dfafd7b108843318909a54e28e828d3ed6433db0efe0d1e9c204305b0d22f55eaa27e7d4d65269efa2334c029150a4073bc6b8df706a44b00bbb81887b414395f905523c0c2599a2ab19379b753f8d793b0933af23029fcafc5ed2faf8c14b0420e834be793eb7b0fb6c721503ca0e3cb985886c0dfcd63cd1f37c6999e2544f9278e205f1fbcf948c7e820c6f3828092130e4bb59ee158f9866821eaa9cec2ceca2c0c79da3bd4584a339df62246c5356f6514046afea13c00dddd2897f938778d050c2540e520124fbde6fe90f7d72a853d1bd861564fb52fb3c690e80c1e5588ec2d91dcf2928fc6bd7257b3d6fd5f42db7f085810c8ca52e3b1e4b2b844e670e357b57a78022d3fc823ec5ce52a79403a2cabae14de03c7a2e388cb7d020c3c888ffd231f1eb404a8fd6b0761dc0926d3da067845eee39fbbd1811784c8f008806a5aa8ead6a0e0863d9394fff9c169d4641835f8c6e00ee3de8c67418c277169f0d120c00dd64a2183f035a020d06e1accd2125eb0a50bb6becb135200d5d305645097e3d098ec32e874e83d990b80dde96488b340736bae2201df75959b9bd904fd1c8beecea3b7a19005c44f589cb3b61111b3739042a69e04014f10346c9c15b4b211ecb1c683a01990fc386a4db383cc2c01ddbfbff63c02ffe7cfd8889d0df887ae86657ebd1ea4f0b7767a26b5d4b5d6b75c4053e382b8f8e32d0e8ae2806cff5549ec427015196cdc0b35d39c3f386e3c41bfe19a46bd8f4fcd6d81e33bdb4ecc0caafe5fc6e4e38f4531be29a7ab02722b51f43b0fefb7a040f9a21dcb0f1b194a4a621f61704ff00e60da29c2193ce09455bad3fb510d5b56d1f6480490f3ef4ae5f7cc4b5e5cd695bda1b0583f5d414951264a0faf1b09df94d52959df006512a147daf04b017708b9c01fc3d63c02ce59121629eb4cd1486d5f070d7d7e6e6a50303775bc9bf08c85a00caa01366799af41da7be820f59bd18dd792c5e066ebd4736004f28778346128fe6baedda8517e5be8807e9a4aa5f4f8ef798c0f919785e0d85b733ef60e5252189a0f85516d6034e4b81981ca7dd2a47df26540019852e57050ce08d5395905b0727e1525d99ca7a3c70673b8771805f00a9b2126f34e59902069bc619e2c769b4903378c6e497a4ef1790e69e93e3b59628538907ded79ac07ca0f9987dc86f082bda3e5eae23aab0a0e904e9adf77698e7509a1db10b43ffdb4b80574cb898b1b5687b77918a4c528477147fccd1e2016040665dd6d654b9621820b27cb7617444aac33f5a3c0d7548d753774e711bc9a84f53c60389175c95ef5db96c5d5c38fb1fb16295d583c71360a0cf0481a2afa98e50ae28319e065f634ae2e49d957d06f3beb9f8917681f456484ebcff165c085493e1833c4344192936c20b70a4426af5aee209c8e888ae01684861143c027947b1a5933753033b312f1dc773700efa9565ffd6e3cd6a29c993429db8d54d02084633bfce27efcb1293e099951d42c0895d16c9eb0779744c2a3e604e7903bca2cde543ce12b49899a503bb18537e492d5b25c419199e090dfb24e6d2620dbebfca6a5b756f2196c4f09e96d4ea51264614732eb3fa00e4cd5ab3209e710cfe7fe7278eb94084a001fd67386d8045b65b9031e4fb2982378faa695cad48e09816ad70ac62c1ae4dc0e4eab4f9be65aeab42dd857ebf62ea423d39f4ccc59e2092ccc1321bc8bf8753f4f508cb987422c7b9d46c977f70a0012abfb43ab0ff8f6918a7cbbc58741a664b0213f286ffaff98949d593912423cbc0764e52a12e4e0b51de1efcdd1942e09db4929b8bc92a4e5a064856622dc7e5ea97236a8a3411561dc85da4544af14cd505b1d5198e678cd6b41dba7e0a7a3f950cdb90d47d0195955ff8c0b3fc8987c68c4cd3620533fbdee1d2ca13f028097039beecad2bf5840f116ad8b131586b2c7396a934be83d23b9be0e4e4da975f806bdc71a69c2447744119b3ba21936f1dc1f14fc560fd8f5c8c49706e10631b1e13fa839a2fc9d617b32020f1f15c79dac89b5d04de5504a21b1c4e2a11c72e14145f269b4429672c8bf8ce3da58538e43d2fba3fe11d26627e06727eefc0342c57151e1054f398d9c95f943a53aeac43da00b8af453e06ac88e67fa0b3ced0f0a455d8a4bfa5be887bc8bc28b5088d1134565ccb80b9fb8dca9942eb525d71ac14602354daba86a2462a425f671d400898b46e623a1b39552b77324741211d5254a659b56177a9921a9d72914c6e467473df10dbeeb5cad80c60bf9ef368f84f37944fed5bd3a4796bef1a34315081e612a564384ba4fa2db5c5c81c62abc0c8fa91a03dbcb2872905a7c067f063d001b68f1cc1cf201bf783a3e314eced5f42f87b6c82c88256f3ee45bb026d59e65be9e13e95be31ef8216dd29f35d30f2f1651c91d565a9e16c16952bf4016973389bb1f4266adeeae211f47aba778acd05474ff10d55baac87dc332e05b7f3b330a2896f920b3c0a0d6ab2efb7297626ed0eba575fd4a14dbc57a236b4c9dc3027dd736caed4b5f2eee853210f48ca0ef7b385e90963350aa9db4d8c3790fc443de35a6ecb5d84e96acb60b24745c7a49dd44c2c77460c9ccae4baee2a61add1c7398e416a3a6eab04c4e129e310735734dc0b7c5205047fc2fc06a5fdc63479a63bf98e1211c5599fbd2af49d32e3687c808bff5d7b5abbaf2fa1d582907fa2e78a76f09006079e2d26898fb5c2184a807ab7db2e04e1b140588ba202445110a8c2fbd00d87dd7ae92e70f036cdd7ee844cd85736b9d9ae6127e7041e6bca56f992742516b1ca2a1fdc43cfcab2f4d392c9737fcb69027ec509ece3d915d3adcc5f5a5f625f2e867de2bace77cebd3da2947f4016da313a181aadded0665c96ef3b28339b4a73155f86184e4421a2de040901b6910fc511e43e53010242e997919d6d01e0ccdace499eee61015b273544c0b32c0891d9a1949bc289fb6f19b6d55bb2647f73c63c467eb98922ccd3549822a4c40b20700e541c0a3d08e0709e72f192bce57e76adaa1887a26f8b50695c04527a6b819957094da711c1fa20b5d1cea97e4f6b16f42e797517cef5248bf5d5f10fb764bd602eaceaf94441bfe5099aa4eef3a41e88e551a8139f8c06237fab1f60e64683291afb849cacc7285e11bb10a0048090e89835fca35f05f6d0a8baa4153582dd18a0c6aecb19fb29f848398b34760c3261322c74b32331cfc0a36043d6175740967abfa4b246110132a054965f61ae1f4271100953b34c72fc2dac8a7e1d0aa07f0455e2bfac1218c0a1c3ec6539e3146f415045a1c23a05b30d0324a3aaafdc1e7977797dd2dbd8bf97c6a8eca319836b653a34b59f2c27bbe2ad74014df0724db1cfce022b1bb16a002820607e15067b48b07dd6bcbbce09575c203a1aa95ad04ad68cdb6b6dfba1f114208d96493bde5de01ab090b0a1e0ab20a8c46c12cb51c45d94829ad74d2c965eca35bb69f13ed991633ab0499a794ad8369b805c565c7c0d56cae6e8e320c059a9d9a97e317370473af56d9f7534ab9ec6d59fb9c6cb7dbb10cf3ec1273c33c5d79745978b48c18d5f2c6edf038b92108e6f40e0f0f1e9ce60149aa343881cd17aa266cbe5045a173a34747473d13a78ff4a3a31ee7f6b16032ba3df5a827679fd8ff5bc97d71079e40a697dab98c6a00c8f55f0ca1bea58217847e11ac0f800432fdc4343a353c4f2930637f48a69f4b190fdc07e6fc8051b6a3adbedc2e5b5648d1c56351b775bf2243d90b2e05f333e9330f050c0805f23cfdb279ecaeb95a610d0b92cdb6f35e19ff4552574d37ed32cf9c1f0f1cb4000192132c41e28626afe9050728c094c0c90c33bc261326db1232d89c5837a162c5c4e6edac746237aa066e0841183136083302395c402208159cf143951464d14dc2e47e0becbb9a87d56d724c6ab2258b91c3568e2e103ae34a2154c6b02d5dbefcf42ab394521ecbb9224ef6e6de1587dce939ee5654c491614e96b3bb69f599afaff316cb51cda5893b3c62ac3c5852827179e47b29fdaa066232a5cfa11875a5d10a87d2247ab84942428d265ce4109c30e02216232a479a1c1469c4e4c4300cc322760c0b638c3162188661af52642a28d34f8a7113a3dd61f65289c58a55ea846a5850ca079583b8bc6998d71d66ed451d85f2a44ce4b6c8cd74f11cb669ae32a41ca171e4a7c4ae7c30a37457afa73c3e7a981c659e21ae26450de3ce29981517cb31a94913398c57321e6ee798d4044a0e51a8eca214d52489d6a449eeb740d7d1939a18e52ec7242660b2fc02802ce9ac9f975fad301794d18527cf39e59899b56735af6c5d51143570f065cb0f469ce1c3ab470e6abcc1a44a0e4260f49a3cc8a0ae91677dcd596b5db991e76fccf822cfee3567ad461841593926192143ae3926357123872e9b066793451b3a4089e3c98e952c92704d909cf18aa1f3a2776802e915830f228008c1ab89a40daf18e216afc65058f245ab5b83165d5e3184f0860bb61cd192e41583ab862448ecaad1d8a106548e5e3ab0e0157f0b306509913422e5c92b8649c3ab6b04c8c773928a71e2047bcf39e951d81ed515c36a50df897d4fef588c42658abd62d8111274dd39cbbd9bdd9b6bddb8d4dd1abdac809d888c67da1e7e5093ed4cb69f51a7efc316c147667a67faae095ffb9ee9438a404486501fae24be38ec1cfac87585b943c4cad4e7c644ea8c8cf794924b1b9cf53482b6894811640c61feabd59d75e6302ee1996f95efed6e77db11e9e970d899bd1cf7ce72f84ad9137bcc6ce70e91246e1c81c890d765c9fc0a9e57c31073cb302cc61e75dbcec9960b4332dbf7c378347960c7827c397bcfc41ea75de570954d0f3fc8d3f4b066469dec61e7793f9f993e71a851c1b4676c62ed953033f6141197c751d8651e2c5c20f6e8b3683ef39c06657e3a761fad83fdf4d906659e7db441996759f8419d729abed0de2e6332b86b705613bab0deda500ceb6315ab0fea28177b0f96bda76bad1b867152f698d9d66a7b34cc54821bc6a3ce662ef2dc6a6bb559462b8683e2120eb8d801798e044932778d044ff263c4ed0191e72e3d203064cde616e61c46193bf6b980c9ec36ecdb7eb8b27d1ba41f646b902f379eaf5bace764675bca53a9b2a79f67366d768b659cf482640fe2de1fe7933da83e46b9f55e0cbbed2c7bd54e1bace7b62bb74ab93239b9f2a6125c2942cb906bfdeab56ad6e21764eec7236d0e1218a1945222ae4c22ace4790570b1251fd2df2c5b07e37404115b2dfa81b95f332a77740471c396cd0f3744a1beceabd69116d3c6e94e3ab197d4da0f4a8ed276d847f39c947673cad83228f0660b0ac330ec0d4ed48d0c6ebee912b07c8d3e58ea1d7b1c0dcbb175b42c33ca1d712c628989913cf150e70dacf2d02ab3b0c72ca238624639a288d1d13bb4175b726f4a4c9e60746e317283524aa3944bb10703725fb6117786c42f1ac5615c32c530fdc49d6dd4a00b7449927ed2469873dfb7828963412042f20820b88519b3cd0de49339d3821b4a2a98158b82dc975544112a7672047c71ef57af84c99dc33c12b6cfafed392cb774b1af5daabd944c9a4b9720a4142188ca10a80f9e99328a82c8dd6f19027d86ab0c81bea318f644ad4e5c12a2943b9f97b670e9a223f7e597304a57a498de91af666329b7cc575a07498a80059c120cc595837243f924f793065b82c9f40b8772385fe31776f342dd0b720bcdf2c934614b5f86e923d50b2299749647b40ec52c9fd3300dca2c97a451c572071944e5309d6124982e7269cb0db3d43a60e8fca4584885069bfbcfdce170639f9fb6c319817243ee3eb9243b2666c8dd1795bab341e6262623db883bfa5cecc2046ae4ee93483aef324c0f993b1e6ea34692fb1c3ee2c92519296e188f7a62665473452d21de34385b71c4a6e1865d7751d4ba9a8d191b2ea58f51ab185da233375c4aa9666ba0ddbeced47065685fedb9577b4d7bcc55ab5ad5428b6dc5dce0622b7461b467abd5922b1a6e7717456d83e96fd5602421c638697737a5b6e95c237b95a690beedc37a8a29a599fd51b1a6338e8b456a0aa9ede447356c3b866198d53a2adf50d2424bb6cf66945252541c1705c218623afbc42068b9d935105470ed639e9f93e230c3f633485ab22487362b09139fe598b4e487a425476630cf75de6326b51ce5ce849b63c2b3f67eb52f70ae3c5d3e7e000d112a6204c99227538e5a074beb749ecd79ddf103372611e1256f3593b97188bb0e080ae75c8f84163a770c21c42eb7470f080c19cb699d4d7a2874c6348ce6285114c831206083372b12dcb0253188c317640e654f8399494468c18c2a9d9d1955cfe7e620fdec55662c9439e3eaec88c964df29323232324262646474c32bca0cbb0a1077cc631f0f236e0bccd9b873dd12085aae7627dd35adb5d9bda73b7cd338dcb9ee1ae41eb9b095b911b8736fb58e09cf691deddc79b48e09dc4157eb6ce7b89ed6b1e7fec34be67e42ebd0735c47a586a38b27800022c910483290b29d5b4d9cedd7c0bc6d7152dadfccb7fe66b1f442a9158f048ae514305396da684983dbbd7c59a375b06f61c424f101dc1e3d1280b6cdbb2a881bf2e46dca502f51889bbdb18a55ac62556ab2049a1bcb2e71c77695926bdfb70d5a12e6b5cf0549eb94bc5d7699d9ce711ab6d867373d918cd19ed8677f35ec41f4f598f6aa699719f779ed934f584adc27c7bda7b7b07bc8db3719a5c1eda627ee66ef8aa7754c883dad53bfddc7b76d5581af107bc8bc5dfab06ddab7ffd8fed33bf1b56d1fb225297992b74883bcc51be44d8ebc6ddf8e6a9dfef6d5b497c3db1b8a20825022c4972e3b51d3912d17bb9e13cb1da411303794518c4439a29f90cc720a152bad435b0709b31057005bdcaac12e77bb0a881b591c40444540444531c98b27b9478e4958d6585153161d5aeeb38d724357687964a741d9e7339e5c4f422a678bc37a7a219e56abc544ff47d3304a39e98ba3fc734283ddd3a03ca241d9f2613c3a829346e40ed207b96466c6da7b57ab1f045d2e1e1f9d0f201d64310fc769b5d68a55960f77ed1ca60dfa6ce7be618b43d4d0f491fbcd7138a83bf7b6f72ef382cc93fbb2e662476977cdf5764ddbb64f1ee916b27d55e650bbdf82e4baabfdc91d68cd30aa4189eaaf6e13b7e7c78f90289e0ef445afec3f4628f6211f918983f589a000d1d7134101c24e4f0405c81e3b50760d134101aa95622290c8ceeca17d21d18720865913b322803d3b10bdc54450e6e92706c22a66c9ee2e43fa2cf489f48b74d32d485bb8e44a3fb1b842dd8bea18b5a8c9232243bd2d69576e77999b666e310e57328f47a66bedccbdde6a2583e3c4716230871b1672e520fa9a65291f651e81ee32384e9c0878b7388c9fd195ebea52d1f3e34788d731d9da9e80b357cab2881126b261e30a19c202117bef6af553895f91698a67cd301ee99d1bf9a024cb0e762e991093e50759265dd192b14985200d993b46a0b0a024ab1492bd5a7bfbf8e201278e7c563194f88a8b5ef223684a6c9c45af0cdb22978b87e720f855cc4a0186ec155f560af6d5e215f69e06ca321cebc702aa2b5cf4ca321c81ec338b8b5ea7187edc11e5ebc7a258c86b0809ee1124f2dc9d3b777557f2574a30cbebc9230dca0b7ef4b352a91343c292886592be0d1b7788c888dc8167f6cc1d7b2d2b050a430a180cf4180e57ddb3e2c9cdf305eceeb6d6ded5b124dce579fa53ea1db8eeec55e62a911cb952302199f841ca33e9c8446acf84ce3b3277583f333367a5302f4f9f9089337f8cfc983853860b20a0c821e50823a3d7e9a64301ba9f613960e6a703cdfc8489a0f48980666ec24433f86210c0b8f135f3398359dd9367e2cc23e112c0172270c11233b02125a9e8c81057b8e4208d1d8ee8e10564bbacb8bd0b7539dbbe1764c67048fb0bc330942ab090fbfd385b200a73b297a1409483b2db16a67db66d32abdcddbdd9cb7c2dc86cb55ca12e53cb751f7d12185a72764b737bb2a32d9dcc501c649f6551cc0fb15fdf0ec92fec7c38ccf2e9c7066577e9f97817ea72b6f934dede7998f558818418cd2fe61f0d661f4bbe3ee6ed63c967a759fb581297a035719fe51199645e32392277f8249206e7fc8ff99f9617235cb06443155b7278cd0f99c7a2747de1e03c40729fc39b9b3c9483e2336a6bf7174cb6e032e74bded64672351c6dce6296c508c6d1166074684125cbf33801e6e484972c0f94758c4e3cc936266da1254bae3e9e460c9334c7d8cae1c1858b49315812002b3774b9b299eb977d218f6e99e769c703e3a11deaa66db0244994b2fb46caee3929c530572bd755d659cdbaf2cdeaa6e3b6741e09b741d445ad6cc6d88cb1196333e63737bfa1f4addfa4903b9b3136636c9e531a8c3c7428b9984e26b40637c94b187b57f2f768e07c68ee78593383d5e541291f6be81f7272503771a7c88a293c50414282ca163078f55b7167841040518507269492e0e1e50537fb36eac8cfcf6e76b657d0658a272bc8620237925efd55dce1b2a0b1060d6ee4c08d284544b0856114a374455154a79ea3b376ed818b3c3727cb186de0c8c99d237fe3c262ca727584d1a1829894769e5f28e9fc646e7b5759fc1de852860f2b887265082e454bc0a0428aa534b21c51e232e18651952a96968811e370c3892a4aaa44818206167be5571b90323cdcd8a16e5c57b494d708952eb61c91a28505af32a808a3c3183d2491244a132cc06c994203276dcc2004d7cff2ddddb70bd05edeca5ff995fc89447b5707579d3db40fb187f59d651968ad7d866158ad57d3344dd334ed8e8052ece211cfa3bb1be76415bb9347eb50ec6a90479893731a94b85d3cc29c2c73725a2766da7476a89b9612cf9d65ec115fabbd2b89142d14524a792645e040a893ef8c6829020824bea8f2040d91065760d90208245f1ce10595fbb3e72b9518a534cbe1cb0f61ae4c5165ca5360c6161aaa94f184cb4bbea50f5c6cc5af3b38bacff6caafa4cc72c68d3d733078831b5b60f7ec9987e2698792bf917164f95925f16007b8f88ef1b7025ed450328233d658c2060c8471250e2f9498438b345e1ce02c8db7910671251769a0c184d1972274f8a28809146e3431470d37f03266005708a104172488638a23734a982a6ca0f1858a1a243131cc463185002dfd2809c12a56310cff687029874e0c9cc77eb35a35389f7d35410f85e8c5daa5d54f96ac08d8733802159c3b4496c02522585c18a0006dd70e03777b18b66bbf726180d287c19e3b0ca61c82799ee5532f4a53725760238d33bc0c110317a4a0051a2ce14618696841c192d76cc1bdd853b0e79e82f68d856107d87307b2e7de1848fb96e19f49f34f6e1cfed42face14fd6eccfcf2452441ac92348b0f684c0970e71bbe7a9856c16d4da026396c30e8743190ed2ea2d7e41560cc3a80e4b2273c80006c41e030861e71a973b638f99ee0ff207030861878c992c7f1a75a80e762941174fe47265a60f32316bfed9493de6aaf8364e5f1be2a08bc7cabdd1831e72729c688f3d6672dfae86b01c544f8f3d3c0af5f489db8f9e09757633b98b3bb010fb0b1f64547777c541ac06c40cc2187708cbf6f3f605899d36d845e93236b8d80a67f6638406e77b7ef48c20b13957717e080b448a98e044bb6bee68731577cc735a0bee167bcce47937ccca106acfba4192c31b24b9aec0421cc20291223dd60e895d8668585ceddb65b8ca208c91bd3fe31472657a8eda7ce787b040a488112daabd0971af6ca00667d42265bf6b0ebdf7c61df35a0bee8d3d66b89e5e6e9c7155c60d7f72505f48bb76edca8d53bbfc2ca171e8caddc72ee48a3b7a6573f44cb854b8e679e67be67f6447b878801b91b6ac91bb7b4f4232cab33d1f904c71bdcb7be73c4fceb8f20c2ed260c718634ceae2881c93b8f802428e495c8449bd69de262473278551d69e706389248aa4228f245277d8c93d91b973b19d382dbd9dd3dfffca5e2ca389e644264e90f7ee5276a1902b7b563a1f0fb3b677670191b9bcc46151963990e86549e6def414ee4d3885d369709c38b673cd1cfc993b71ce297128e4ca33e71eae3c146666b20b3467ffc954b2bc779f79f6b1bccb9cc97cac0db31c60fa7d044cf8e238712220f36edb0ec2185a0b56dd17b3fc38dc7d310bb97294c6e94b232aeef3aac80808a0009364859630218838e8010c2a4bec00851428af2e438a16c06006286881155d749199c21b13802c633c081c41011761aa68f1e2065c7c71821bacc08ba3226ce872b851caa12b5202042f403285106fcc804a4c430829b8ac6087263830e38624318c244159c2fe042942ff04104bc412462c1e442c21780290101556d87111192ac225cb13cd065d1e64294496ffe6810c50833f0dca1364b2bc4cdc310097249060238e2b6fbce45571a748071c20c9c086265d182902095643472926bb4cf3e577e38e7895149b2c2a29d1767906b51281d14a044769adde0d1846652196fbdb8bf28ca0bc87c30fe4ce74e69e293feddc47b547cfc7c7e323f6903ec41dd9b32b20eec81f224ef6ece626950a259290276752042ac30c19779ee62c9b3b32b377f5105c81d385a4c16c4883d9db33e36659c619c1c5969c382c0cff88481d20b1258a0f39e401c3010c80e3993b441f407a7283d24b3b7dcfdc21f2c01c55bc329e69c2ceaa9da516463f40f442e887d0af402f444f64a84e8ca7c7073abd143019b078f5b3afaf7df25102613264f1b2971e1095620561a2bcb647efc7c88f06b19089434d2990cf48a0e0a6c6822b5dd3c53302977d2c20d9c6ea38ec68ebbaed2e1e1f3d3f4e0012a2c210163a6ec38e82b467f8ad06bd6f0546ebcae8875dc1901abc50b27bf8cadcbb8761f02e835713679ea210c4f2d1b2bdcd198847993e668b59406c100b4810bde6c3fb7a7834d8391ec33416507ceca8c1b9e37a510c09bbb2b59d435e2cc7394f82a43348e710862c47587d375a14f704fcc333e35e69ad4a7fd57f1f6c22569557f68b89a21750f490e45557d0c21352e444174b68d029b8edb3b3761586ac90827d56f3f6c9ac5d85217347e6da8bcc1defda1310e315918b98256b31c62ae364466a9466edaede91561869f7a1bdb4ffd07e827620ed425ae794b5aba09d05ed14ab3f188517edd123c195b5ac7bf6f8d22c77eb8a3d78620f99b52720f66841662732d7ae5d7b0f69b1b85d64ed51eec41846d66e3f3b71264e7dd630ec80fa9ac1ae89a33d6e71024c8e5b9cc092653e9e89a3ddfb7a268e765595ab5ddb3e9a5dad136e11c38ebaad0ef51c5d3dc756831269eed0d72d924bad790d0eaa49ad54a9d52ac55a62852157aa54ada901274e3df6715fcf8705492e61c2fda0b4759227902813b73e669984092ad7a42948840c9972244b242cbdc1e03eff344ef54ce04ce8d73c4aa47ab9a5de03736beeb1716b7018957209dc817e1aaca806eb8f136a1776b9bfb71aac24c899a7e703d6c62b4e7e240069ccd3607b28c8bc5dfb583edae9da8bb20e0b4851eddebe5ee1f49d3e13c795d9050314ee37d895c2cd3b780798a8e87583c1578de3c6dad59aba4422e5e684845622ad50f3ed93994bd5a452dc1146a91aee83d7cba692cec6b6ed336f388c21d7fcf732ef6acdd7821c936b6a785a27a5faeaac9aaf264ebdbc8d95d427bfacf149d6278bb4fd62567d31d77c33a7b2ac065fb1c1f2c80a4b898bfda90ba14eb3f2ccb8f5ab1596585eb345f549a306b1fd3acfc071c3b8653571a613506481e475f34d480c5e7461858d13cd771bac2b34582f64eec8248d536fc2a1b533b8c3358c1cd73b528825a924b5d4cb2df5b24bbd5ca35e2ed54b3046dcd645e6ce5f3d78bd4cb2293558bf6189a5c119326e1f49522553bc3ab86440132b48af9b4b2f850f78d9427a29f4b8200b292412fdf7c167946feec1b725df440f85c63c0dd6fb68b07a3a60704354ae8fbd9a3b12c9de55ad204fad5532a9f5e65adf410a1d60a2d885d2ab83f91b4c24c324a571f4ba79f4522882a2895707374c010205af9b56f591b845b180b47c9fb2b213b28cce39e79c73cefff8c92ab541f2744e1c1f120b57338ad6b678169018341fbfa0f9381fbbb56dc68e5f785948dc194f27d618a6d33b12044a667c8024c60e62f880a46404eb59a5109327dec050d23e2350229218467a3a082eb6c2d85d2bb3a942f665914bc6ca5e94b31931b73fe59ca02b2762dde7e59642dd66b5b9dcd0e61c57838f0fc119396e5f82f67a209591a1711cc97814743de9384dacc6d28d14df6012939eb00135efbcb4727445a9b31f3f507099a7126cb0882b99c82533645c2f5e1ec1366e87ede704ec9c0f0d1aa1b9ea4c8e9051e68be6a38188ea341f0d332a2c934c1cf9847bf76d98db3e2ccba8836ddf867924601b66019139734df3cdd79c324914f7e7046984fe98c15d8373a68d7bc24f8356e491099c387249eb9c4eaf73fa3cf5ece9934b66ae8949923c43af87bb308a21f23e3aabdce750269149e6e593b853d40397198840430829906823ee1441d9b2450e64d0408b259c78d967477d2c20d4694e8389a0f4512f7aa53ed6cf8be6452fb9e409ea93513e49c5ca5183f3da27af345827365d399e19b7cb6dd5163aba5c90056406f5e7092b7a90de45cf831e01c129d38ff0e3c766d576f30beafefc074e1ceaa3a741daf28b7915c5bdb8f3ccb8f42c24ae7c9cf30551279436d26c628f9666b4528a6161605b563b576fd7c05fcc0382f96caf386ecf70985d03c3ba5d7a41362014c8d9b7af33aa61aff559adb4048a0307e600e5d9d5fede637c7fad08ceb754f0be9b0fe9bed5cd0a79a2562cc418639127a70db88b635b8c52d96d70ad5333bb7db523001dc4d983e3380e01407287517107c661391f410c25946747efec8672046995b30d2979643c66845a126cf0cbc974b837c7a4286ce4906246dc538e49515a90b31aae97635214a51c52a4ecfb4d52142639bc995fb85a7462e76694ae3d8a9264e2c44cb1efa6719ae698d4c417395c6533b81b35ae8c15abe0c4e6c4e4a4747663ddd2a78132e5b2d50fc51e31d75ae94c21890c1e94e062c4134863e4c004225001192910838b4a82b9841a4534318339b038e355c5e0e0890f4d80c8c11855bc2a11b94e916ba571e386b475e83bf6e84ce77664837fc328941f36549ef4a89b2e1edb1841153b1c7102ca1546de444a0d3e5089d2c6d1122b509d45e2861dcdab5577bbab61b71873a588243288c194212216738ca1c50b6e580ac30d254ba05067dc70955b376881ad371a58a45b9432bcdc20046070b1e445932819c19731bcd8000a297508130051b10b831bde3c736ef2f46188d22d4a712752295332fd08db6ae2496f5e58abc118b476ae6b8c9e111a940658d245a162f6acae9841c7154a6a9df3a8063457c8f0c5154fb82b9eb02e70a30a2f8c4032e206367a7e98030b0864f06409172950a49ca0ef32801420a28cc970d00529c7284639be5b1e91ae2eae7c946cb8e65d3daeee05cf337198fce811f2538b644b1948e98426312ab8596b3581163648c973ce6a278f76c9d66d412ba726c7a424a0c837c7a42490a853702c07f4e78180e65912b720b33c58a4c40c407c9932841a56605884a96249066970c183103df3477f62a279a27e11145c3fb67ee4ab71d1cbceb15ae9907283266870b1d264865711122c502a4329872ce448028d25d4cdc9e1e163043087878f11b27339432855c10b231e3c3e46f8f10324a4462e8ee48a6139bd13b11cf1e0f131c28f1f20212aac80d17a573e1c49296792e250a8e3a0482f24ca613cedc2f8c85d19a7ccc9e1e16304eb44cffcd17dac312bc2d0a78f57a9a3e0629bb2dc793b43e0760420cf988a3b76e244578c214cedc97dfe1132249e8528a4690e21c78710bf13cf807813e252155cf440871b9342e0430ee5132749e4242c5c72bc22c7242c4a323763ca72431e308a64e24b087e7132f39e9ec1d6de6b678cc7def1184f8ed894e566e7aa67c2f61664e65ebd56ecd1595e3a893db6cbcb24620f0eb7362c89883b70b861fc42147bc85c9e85f841ee997d91181577c4837327157fa2873bdbd3f1fb20db2fcaf105fb98dc67168814a14701468c1821830c0d0ea1aecbdd906a6848a813ea3438843a0d0ea14ea8d3e0e88628cd26d35010aaebbaae13ea56434cac6005ad82aec700b72c91e5c12d4b6839389d4ea79309f5eed9515f0deaf485369f50bfa86728940a1544f5d42f2e928be4ec3a66569dd5fdf40cab30abeb4c5a0e503f6939380dcd1c64ea4ca88e86e6f4781d5f78c24353cb418c3d54479db64eaa883678baea8b71c7e9de74bffae9437d21918c4a6155a752851a90f735f7f2e921cda8d35128d36f5824874333d7dcabb9de17343473cdac09ad551d8555a8b8c3c61df31649420554057352051839caf20a30726483eadda7ba29757a967d3a4c1895dd67efbe1095bbaebbb801451985b39b740865d44d3fbdf041be349706b350b80145f9e2d385b2a946070a95b20165432aa552ad6a845036a8542821d445ddd30985b211521d750ef58542a81ca442a554adfa64567df7d353fde2109555aa1f3687a88c4209b1be882186c482925eaca030caf22b288c361a361a361a361ab2649981e6dd47a3c3f4d34dd94d9fcd2a3399549deae2672693c9f4f64a08728f7af6d3774a61960afb5c1c89e4d3331c12c999e9ab410dd16c3aeb6442fd1ea56306d40c28940e1d281a9a55cd0c9708eadefb4d0930c090e1de7b2f4df6fb4cc7553adedd67a99f9eddf485349b74609fd44d3a74dc9fde5e0941baebd33dfb7dccde61a2dc3dc344f9fef49e793a3da50383a0e56667dd9bdee18b7d9ea291e1b4c9703a05d17434384c9d3ad517deb6b1b9df64b8f7cff7debdfd359d8e4215dd9fbca370d819f5d5ef17da7c7ffac222f9741d38a4f93e950a353051575de63eb6cee9a9af67decf3bea8bf9f48544aacb7ca54271325fd05067956732d198ce71ed05517d32cb7c21cdde7bdaf4754e1df58534a33e3d9f13ea3d13f56df7261aec73c2b441cff4dd7b7110f770a82d91cdc68617d68bce568608070e46566c386c386c386c382425696fdc77df45a5de71a62f7beaabc9b0ea59f714cd5327d309b36854a7d380a9a76eaa39d75deaab79f44eefe99a77ed95a06ae1e6175839fb2985592adc80a29c6196ea41d9bbabb277cfb277343f3df5d347f38544722a759a87dd6b30085a6e8759dd4dcff009b33aec933acd53a7e950da1b34a99bb43752bf6988e620544793eaee1da2ac13d9a4aebd913ae1b0289f6efa5236f7629ba71edb749b9e99fa422239752ef5050dd17c0a87a8d780a253786b9ebaf97ea6ae3a2a75d553a8d7600fff8899a54a5d95323d8563837788b688b03e231b1b547335607191271712881c3366cc98219a5b060fb06092e53dc082c9919439f799863a9b1ed2ec3d3471a6af032233eadd87baa92688e627aefb4ca7f96caee3266c3a9da6fbc256eeb22eebb22eeb329a9bded9d8dce6a7675fa8420ea24afdf4af569795c23eaa8b6383a79f300d0e55c8a667abdf2f3cdd0683a0e59e30ebf4ee26dc8f9875c23e34376535e76477efbb6b60017948660bb820330dcdbbaee6a6d7e0f0621afc82cc26d4439aab8ff7b041a0e30b4d8f5f7da149f33e271da8e150825e9031dc9c8885a1708712727f41669973a76f355cec31c71366f93c9ffe2199593ea7db9fee82cc4332d3a0b0e9f47c7a36dddca89e4ae17bd5171bbc385ce9dc87a86c7acde97493a906ab9efa62832a1cde7cfacda7c7de615d75fa35852a9cbe50a8958350f80599691e7328d4ca34379deb8ee69399e6a9b7e7634f738ee63b9de3da0b22f3e90b5bd974a2f9e9a84ff5fb85ad7c3f3d1fd4553d93e6eb7cbff07e3bf56852d82736683a0ab71a34fdf4d309077197bf58a895b9470c84bb94b9cbda17aa90a34f3ccad9e351ce70d00b7f41e6f085ecf2434116704166799fcb90e5cd67c43a1103c68c19338685a1c8dcaaa9ee9430caf31c4dc1edfbc42bb27c6ca36262b23bc9700cad1714ea46a6980584e5e32181e0056924b570365fdcf898ad21464b52185eac603922298c29496128916118e5be67bb2f612c981c520b1a612306466280e48b29e48892a4749494254c8e49612021404b99454b52162f59ac2465c1a2c4655102292da598dc59a6644992c39b6396279d94ddad2426cb0e744e252fb975c821cd6078c9392629e190a4042649c98c3c39906760af6d9b6633066b659939dadd2f0c38c2c591170eafd9f363eed057ed5e3adca3bc0fbb99cb5c08953b51e26c51d3fea8632dc771c73c12b820de3b9a63ebb4b26ce978bf38b4760687376f58d35a59769e4f770fc76f66ea69e73e9939fb43820b2a34682fa4411dae1066e16e38ecdc36e77e5c02011b77cb31290b2a6a1559289143cac3862c98605a6481e4a6ca1976f5cbf3a3191b71e48ecbba68b859eb9572d06458a8956b4d52daac6bbac1653c5c2e9e2fc07c21c648e3c4958b96b1aa9db5b70b2f8d13d700d38598c689d91ee98471a1946151299623d5893bbf602e2572fd4d67ed05834ae3449bd92a4b906bad59e60513ccdaa3b04892eb6f6aad58d6c0b080c1302c90d8aff5bea97285971c7e268a3ac3156180495c2dc7a42bb8b8a952850b658efd4829edd9b02f64f974674e4ae9e269305ea398cd5ae66a1771f28ecccd9e1e103871e23bf9cd8c659dfc9a27f31e305f2e1610995bf293f98918a905e79cb315e36c99630ea531c6185d99c5346cc3b078cad126c78e3891079473aaa85c79ac3eb39a36edcce66cd178b5262ea533d2905aba8a5206577e4619678e0dce22b46c5332edc4e66777ceaa071ad3a886b14ff929ebabc4183d150dcb6ca5dbf789b3baada3a1805dbbe601f9409ed750dba474c6c06ad4562018900f64fa3ab1dd6260358ad13728c586a8954ea2dc9462b576b65f47b07a606695caace91336d6a42b96a0c260f5d5ef23a2d816c58cb006a098912a998091dbcb3d0449181cb49800871818bdba088719d02043890660e480c1ab2598dc8dc40a2e793bdd50f044dedeb5cea5216f476dbf390a236f6f6d776d6087ad2c91b36756e0c8d9bbac8a2372f69bd66140cefe2a6090b3b792aca892ca68d4b8e1ca94821baeaa50c9f5b4de52d143aeef2a15306040ae2b9bc17825514141a6f4ab1f32a54953acb1a453a918cddea4f5a2b3ec72c8118ba87e575445862c6ea800cc1626af2e7a62e5091b906144060f5a5ecd24f7152a4ba458220731a880e3d5455290c42011822d9a509282571781940509f6a259bae145d86a5032630ec618238a151ca270e228c6f1c30b984803071fbebc21b368a2078e4877d6de1092a25823cf77931b5a294515b94f5b274a23c923e4cecc14bd36d517d7ce69a79d1689fce2f6511adc0c8e2c48aed66101912fcad9cc19773e9352b08879f2aa1f5532c56bc60c8924bbb537cb4678c5af45af8a5713277b7c659f99e36e0f7972167b74f66637268329718b5e45af306e09b12c4f3f4f1bcc6a6681c8dca9cf2e64eec457d6c5f3de7d34b3da23c185c62e04f9b909f37def11830dfa700f3f909b725f8c52842b438eff82fadeb74f06871d0ea3e7a824cdd6c4390c19aa19010000028314000028100c88444291582c9a06bb2cf714000c8daa44704a16cac32c8a51ca18638c01041000000022222343d3060007ee4fa3e6eb4512a8d7e614f143b3de4f9380675f89b1d4be0686649dd4376a2c99f9df78c8b20a77fa756648b8951728d94a16a0c74797b5c7f2b4a27bfedc9b38ebc02636ddabf0ce4a6bc901e320b9ea5d6b3e6ad95940af4a7f63f451e083832a07c1f29bac33cb2be4b12f9e15452bc9e379feb4fb6cb4bfc60e156e734e1132b0fb1a7d1e9e8cc164b0fd6211b3f8a84eb8a6d8b40381a219455ee69b10427b07088373eebdd463296be8267c2ca918edaa6ded288b311d83037a694a8d345a26d0a116bd6348528b2ddad1e098526a82ce98793548b3232b575f62079e75e8f0fd54d6d41385dbf0fd55e956796f38c126f332e55033dfd6758964e0d20305cd6e94e0430762c4197cfdd870947d44f68e520fab1b550b8620aab030411d376dbc2588a38cecbe5400335f703ac9b5ac70188c34d21f6b08beb597e86a825b0cdf05fa5c22672d8d595e6bd4e670df96df613fd6de355b363341e541ee2c44de3fba4fab16135aa39b98220280e2921c7766a7aba7c83565e8cc56de6705926b374164593798b3807da58fc9631ae23b0a1290420f64d6ee1f77de06777833bb7f0fa2211e057160acd9b5f00dfa685501dc0002bf1bfddf09b35b97835f401e52df389ff0af7fa0ebdf2d50f7328bb526bc79bca03de7070aa2fe0776bfe9bbe62f6797383bffe52c11faa05f0fe6f61bd1e3d71bcfdfa9ea5400c0640f83d4d9ce896c0c640685e16f0e991f5d5eca6dd365abd639eb95f3a82fad1505e79ccf3935a17db99199f5f218f68dcfa6de50d3cdc80216e620f069d141f785a9e9321cf36b24519450aff44adcad4a5b9c1f1443539bd7ac13ba2daff72dda857f1926096c981a34bd32ef88f2e4e82b6e98fc96a786b2d0b58e44bcde9ef358ca9d93f990b592b974ff2f4f3cbe9ab0641481574eb2e519d3cc54c10b5b47eb52f862a0922c970c1c2cd628fd3612a16e85bc80245fa176cb5b7c37ebe1dc2b10231f73e875e5e674d3ca8ca63cd2a5d754b1521a62edf41ea715f4e36f7097ca92bb2389642067dbbc7ba89545f215a38d924ea3f3bf5d5e7d5711d6e57f2c8608eda8b0e6774e838c7efc5fa9d5184388610f5d76bb572d27c5fefbcfd53f1ef0615d93b08fe4923b3d619081f17d22d631deff728f6f900fa11be1a3f17b9936a52681561d090743207e460419ba4198ef199921550c6fab7ac3982fe6eda40ab84e1ed8b415a2f60f73b32e56fab8b3455f53c2a9914c5e52d169c9b3763239a270d18027e9505f4597edbaedcb02b7a57fb0210c167358610ec5651c21d01fa9652bed1fb0bedeb20dd936bfa553378cd6916e96ee3d5106f030b7527079c92dc29ad8fbf6b7accf7451b8645a3b1632a334bfd2f089a9c24c601fdda8ccbd1cdbbee353a35eada231835a6798106cae953091415afd16b4c95a3281fb5a870fe783f791d7cbf61286ba3d06bf19efaab8750fe189c76b284e8188204407a92346850572339bf68bdb3ef5dd00fa62c4a070ac1003a0d5cff1b0c10ea29148f7a68ed56dabd112fb479e66caeded8361095289ef5576d0b079f9c611a4d121650de6b865e0c925d7fcc3482495bc61fd66436298db79378d29a9180b4227f6ed0dd145527220c650a375310d023b94b32f36dedfebb59215ae1af5ec0e0be989267a9eeb9c9bdf5c17568d7bbc691b923358fbea1712c6529ff14e30f67de6da943382e3f6bb23cabf7012ac93c0f07a9418e80e6ab4f7dde1e86cad5d5778032cbe7e01db5a50d5574822c3afad7b312380350a4cf8756c4fa0c93a91cebbe5b1fdcdf642080a1a879cef15ea6b388838c84eec9d7872719d81b78ed06ebeb354d9d9c608b2c4081f9ba4c09d6a8d84eda3509150a4d641f02fc997e29f016f69f98de0b1df645fade5923820e0e2a0f413b2259a0a7a8ca968a9c5a551f106e5b4f2c44d3a544df7341c018c970cc017b23e31c4b69bf80039142d332b2bfc1e10d1e97b9db52d751c6b0914eb9f6bb57cb1a2ec81d7c3eedd9160a13f63835ff09b66504b5d4c5424116af7f6dd7d5638f8ba3d802148e2a766fe3636e7f51023a5e017d4b48fdf184ca8fbfb8ce4290a0ad7a19347d999389b37f60720f69c7023faae3ac6b609d937cb0e76c556f640be7e612d0db8b81753890a0892a49c2ad25966dd6e61fa686eda709f034cabb47361733ca282cc0dd977d52e0d2ba187ea1ae30252668833c1fbf238346f3fe336397fca6c5ba642205ce09a54d8d391acb33d6b482fad788c9d26c59f98acea67558a66bfcbfecc87f0c6cb37ccfef0504ae035de64bf1b99c8e88dfe5aad5ffd0406742e10e67c712fe80eb121450f6cfc84eb7721b3677fac96ce8a7fdb022b9b36e7a39e2a8a54a12d11ebfd9c2e552dceaa58a9706965711723e9a1294b1904d1012adf8f3275f890d7264e17c05ba7b6b8b2df4efafff5e3bd35e89d67129dc6c7d42f7503256c208e9fcbd53322a1d3bbecb7dd9908a0778e7df509047adefeda99893c7a2768df770299f436803eb0843f61695ee919a4572fd588a10a1316dc79192578a20c1cb9801df46201e9d51951d624b8f02163707fd04d114871f88f518c6293b580b26f8aae3cbda0a60862bc73ae5d2c4a79448643f94aeffcfde165824c6fecb2688b7e077a0572d7d0ac4f33ae19678f0f13160a2bf23be084015d27ba11492c90e693ca54698218e189347516d020ef68572d70f03c74fbc7b9b1e8e840d7352e6f4c40a2e68c5ea1b22b0298df1040cb35da9d439e1662a648ef3917dd52f80b3700ba71ed9f27584084d4f328b6259e5b15e1b7e59e9ff0f47894501388906edc3a0cba61e97c8f84258e7ca57c1328a1046f9a05930185f0bf5597c45bfc9e5d789a827601ae412670ed293f1135a2be2c43520e974f4ceb592e738889e7d4e16f0bb20d6b5e922503d6c00ba12f233014f080d5d24af1c5deb7d65c9ad363499d7b0cd436296d61e54773f841755e1502e388d83ec25fddf8b560dd74a1100d4d2716e56a2239b30d47db9ef082b847a004db0a2ca34717983eae4e7b45692bfba41580b3534b06b4269916b826040bb7a13045b1b365b4a50b55025b2be1f769768b9600e932e88e92d62a47500b808b07fbdad7e69ff286d3788a44939132b3ade65eb1e9c01e9288d59592b4dddf8669bf39daaefbc1b83e1f933365d1a6ab4109adbe0642bfcdf58b46756947b36e6e047fc545b3a1c52cc8af3efb52ed907bf690586b44fff6242d45232421193025afbf2eff766c7b4c55a31e9d3fa3b2c086a3c5ec3fcc6f4eb88f5c8cba849b3ecc2f9f2f14e879ae455a2450ec1b02c1d0f0892b138d6f574dfbef3df1f46139da7b1eade4f5be982748e3c4a25daabce5995ea099be5fa8d6981aa84022a1497814bb83b07c877a36eb091e2240d61ce99a8bb06fb5ae570aab9b5ef8aa5d83f75d2a789113922f0a20c45403c607d6bb324fd39776118eb15b504c0feb106f57312d5242163dc58a202234f6a47d1598966c3834270990066cd024258e383ada5ad4b335c3f33978db5a02344c559b28a368d5b7f08a17a1d1ee515d9340ee63715343d74c67c7747145f5a43c1dc920e91706df65f0ae1db52259effcdfddcb5458e402a3f91dba7141832afbfe6a8d23c8d994cf55071afcfa90896eaf671ac2aabdbd9c3c9d9fd97caf9581e1b5243312eb7b8fbc56195f1980d03d5f948f290aed945d85e3cb8432e51395178456b398987e376a4ff284e6105e983a06fb7961e27bad18ffc1ce39b72c40b56e6c26f13e2642e9dd4856cc43414bdb95c74cb7052631d09dd90685b3cfee0f05f259fc68df5ea5138c2e685657281ac5f82d80d33ff73a1e6a876656be277f38c487607a8347d4d1efb54908f4a7464b321f60b8280740944073b13e891e01282d32161152b6033d4517b1064e0c2bf0e193c7bf8858b0398c7dd1cb42d1b463b8a500df904ac1749d410ef9adac3c8729e0368a513016716894b26a6e6eee96bbcd24c2ff6366a044616631729fb05003595cd119e47e1dc6943ced11205834302b48e220f8402d9666225a1beb574cfac3e44ce884366c1e5ca04149b1d15e90c6800d0c66c61582d4dedaa9ecccb83363c2341b807a303047d59623dd4830bf675d054c6f95498d20c4358b042cbf9b0bf5b41a769ee7e3b7e153e651ed254aa0c98cabcac49a6ada16699407917314454f1e47781abaa75bb6f78fd89fe42ad5c3356562daa41e250cbbf8592fa5ff2f2587374584ac8500d3a13a1717ebcc50c4691ebd6a73b64222b4f63fde1c23dc589d304fab36bedfcb1770a62296fe50ced0a37935a6a1abd91a2f2ead870b675a3fc6ee53d2172ecc1af46f5b7b5f450917e80885b4351799f57172b00de2a7c76580c3d82bb91f04ef278ffc88f9760407ce880a68a7d522ded4b8bfd914649276983d65bb8ab497431157d194890b562ab9de33f5f31f9c876674810a080e413330380762a24442e9adc51e504321fb03f93e08161406f4afaa10ba1684107004018a49cf614053155312513b371df234d0dc209c9ba6090f54842f6f451b96c28de6c1eef996086e873232557341f914bbce808dda038cdcd311a402b929b8fe66d20fce156c9762fc883f08c0875ce7fda6566c089dcb00c8380443b5fa9126d111e7a2e82704ec02f9c68bf4282fa4cf133b5367f71ac600afdf1552d8e62fea30028e6799674794dd82fbc975364ad41420988626e56fbf120890b0358e57e5bbe9a107ab5766ab7e905e70b689c9b0e6fbd5622e482c7ffec5ff40b369caaf84769cc082a41b7033c6c2fe141903cc037a47f973c3508be6774d6353039ee98907617375a485a1681c03889fd7c90e0941c4d77bcf194592d4b9d04e3cb40527b4ff806b876db526da24cbdf328c2d432d662bf4c5144caad6d311c2aade4c7512b223b4e3006b7642674791d2b899a46554eae76b76f4f906486045650802c565d2f01f424e7704bbea3751e3927d36ed4067c43067937d1075d84651b0d2eeabfa1df436bf60b7affaea84a22752468f623a56947e6c1a6edf5b2697ad69fe7e6d159e1d6db5cf5444056c227193834f15f2a0e6267619530698c98715dc93ecb0f793e0b51944dc44811b4ed782136c6a34077a229d691fa69df108a04cf85f6d0656d91ca7224d7f0f79ab6f035171648c52d8dfe8efa2f5d05766ee3fdda989906e665c35bf6363190e34d0319921e8b898bbb1b59abd284c8fc9e431dcd057481bd2fe8bcf77f8964763384affab9e089ee568c68944d4619540e48bc6abe65e4d19a93fc0d24e898bf15e9cb732823704bbb57d41e5548cd10726afbeaa329e3cb59adf2f084c42187732ca0b3d73551f77e12c76aa92af5abaa75e77231a1c658994d4ab3e34a4da33ab611acf49cd4c8f8fb099c25d414195d6270583bdd10d90704e791c882e842e2a1cfd68198060235a954c0c19928cea4c829f43a58118f1cb88b1cef31b58602eb029eaac638101f58cb30db68c16d3c5990a5b664138634b2dc1ae30cbe56ca08e2797a0c507ec2a26b92480c450051053f686d742d7c6310e453edb08aa7bb35322172da4a1f1916faabc693c73785e4d96cc21973c03d17a2e650b883ffb4c43c45e064e20667cbf2fdb0646f1e119245193e20f2df13cc124e6a2d4ecff2a3ab271092824887c00a0fe5850073eee1fb9e95b86a127b851dfd31e8a2d94813a4462718b911ecd0ba0a081e6ad115e6f4a735df0eabce939e5d481a75e66531efe0eaaf0d12ec19d613026094ae18cfe7f993f54586a380a4382e42080cee00939b634a121ac0ffd9a21f0a225ba4110536ca4e817cc76adce5ac8acaf0ea6cf3dc02cfbb2138eeb43b90262b3123dc49cab3f53eb121909017d4c042c9eb298b3ee143ed7727a39d3ac127dd2a2b444e0212190106ebf5c24fafd086234801812aa8d5bf94cfa9288cde70361771652a1b9f8c0286ac2554cc45d20a3678c3bcf3f89b51810f59dd6787f953c711c916da1d52bd0994d4dfd9398e36d0edce3684834989c643eafe6c8778e2c82b843a3daa26970d78ae37e9c61200dae32bddb572e280c68c53334a73966cb2bc47635a5a52456ecf2133f80bf9634f7cbee14100119a5121d0b005b3179687abe1db40e77d6d17c0b37db7a3da132ecfc4bec9d94368c18a78ba278c0cf7bc3da4aedecacfba452bcfac9a5251e7a051abc54e009060a27c28e1ca0ad515408720264fa8cec0ea4f7f01f3c2f770dd61c24e2355902f82d0799d2e00f617a29f874ae19206fce18650211a8bc6b16ee8604d1d7363540470c3bd076b3083307940d0a549baa1a20aa6c195d3d333703cfd6d6afaf101b8c2b09c71b4de5524c2943e052bd5746bf42ea4c7e4c5b98696db83df7d2fd1090a052e25291661d54b2c172b446a3c075502eabbd76fb4f2bddee97b0436aef424ca917d673aac812bdba57f22e40eb29018d03fcb0ff3bca47114c7021f350e2a536c5fca4a4c47ecce16b14a264e403983c1d2f203aab08789a6c4dec95f76a5fc46fa712530af14fa0ed68e607202313479474e013da85326532d75acd56c9201d34c1c5334c15922bdad93b90905a9c68923cb13915e3791989cbf27c1106f1a1a03e42669b1b934c4f2a1f98da0c4f3ab740a282e3f10ca07ca4c7cb16d3cfc43c2d553a6d7e3c2112cefc7ae32df7c3a4458cb3921229dd81e0dd510b6d4e9a013d45fb059ab4bb60ea7683c0a0f31caf8d725f37cd51fef60103a55c0f9d4a8fbeff53bfade308a16cd926098c2def758d0bf2995c9621518598523bcab1c2f11ca2d89e891dbb478f3fc1a9e8c6c414e335076a1f6c3d21ec152bb0f9c9ee1017c04328c703e9499a84286db0ef9ae0d0ca4a0d111ff8da2d1bd4b455a85bb9a162655678616ffc9a1e8fc274b48a3d376d614c59f6176664e823582e940e04fd0e5d49dbcb8a83cc3c1021b3916467954b454eacfa662be7cc9a8355c9570644f944b3fcd439f089dfdc23c4213660b80d74ab067c191d57c8295ca1010a87da74ed05f05e1927947aa8f22f83b248062c3393d3591ca743735f7415b0db37b9b51b0beb5c9912ea257f6c7e1d14f78301ce06384c3b8903b820deea7a6f157ec18be6f0f0a795dc0998606fc4d46f9283224964d16dcb1a5d4d673556253cee12281e5c61d1ca706caa6a821366a2a9f531845285e0b0d5e0244021a81ee591afa40088ed1a2d76e6c0b7178dd749434993d6e118608780436fa104a681210b7ac9e3fd94928d9b993cca6b26c9f70672ef3145ae7642b9ff7ef80a12e58e49c90a98a6817c5e10dc123d692a7a2244fa5e628d552f1590e404bbeb64e616ff51055b3df4d70fe486631ec92600778bfe4abfd98919b7a4552b207334bcaf4c41ae451a37423bb3c6498cca26b5721aff680953cb965c0efccdb69ecd8c9229564e67558327becc3355a0e35c6955a7e0fe10de9cdf526342c58ab5804c16495a4554a39ce870c792c5cf87d42d2981a102737fb8ad27187654c1c995a6c8e3bc21d425e1502f74a8483adea78933ff88a802e019ce060eeccbf68326447b9b4c45f6f93061bf76cf1ce2729f0e281efe4bfe1bc7d6af41e81efa9af0220f4859c8a516ffa42801c6ed5d10b102edcfdc367aa65023fb0d44c1244841b1a8d9896a7f20ae29c08e9eda8f8b741b775ea9f07043a13e94ea150d74a2426410d6280d5910a3d8516c9154f2d814063f72c8d392ef2a55e5385322a0098d7da50dbc7a625c7810e52822cc73be8251156c59a34c1daaf821e5b94a39a4b0ed8daf3badb0ec92f23941a0cd7eae108c700d4d5c9e2ab4fcfa729ad02d2146241da49deee2a83a923828802e420c9c095bbd280d7d4fe4fc5c057cb47a5963c1603c280c4d560a61d5cc59bdd31326d3157c531c9853a9572132855ac0c31c5bf38870c9fadc9a7148b492ec9486e092d2ae91ad31fbfe0a0217ce7dd05ea79ed28932c0b227b197516cba8f875df88b913db9c774e615c5dedcdb8c63078f1d12c0ef6a26e1dd3c82993ab50b287e4b164c7af75c76977f62de85e4b749754c4a5d1b690829e366bcf39b839f330bc9c06005db344ece76897e45b973f001e0067511b9d473f7ea100445b5a5779583d47cf90296c014e08731051b328fd879f1f3bda11dcf903cdb412233d02e889a7a4878c49ed69a7258273b1dd2693d481e94d193e3115eac4a667ad20cd436ebba47633781c52c13a2bd4339817f9c0232a113e9db0ba14322377e2fd60bf94d5b52151ab600572bd250a472f84927f0ac0e27e52378bc64cb699375dc827a6c20a289790b4c1aefa10f27a5b3cc753ef3301c887ba717b5603d31fa6d32c8433f6db60908d5376b9b87742b50bb3382b900d96c189d4ffdb864e6db4cf326cced0a5f7c930e3b7e78db687e62ffcea1e3b03c76b76576145c6e8fa35aa1921b09a8529f7f96921a554ce51f184f28d586417b863cec91013c8c38a68ec7e9e05a33fe3125cfb7536461c527a29e85239a385faa58d54f1904dfd8c30b16eaffb25c185c95c10feb3dbdc330237a74bffbfd0507ec4cfdc5a0e260e971fb5d3c61cfd87682453c68c43b8b33db3af7555b1d83aa50fee23fe9ca546020a2cbbf1b3ac5b42e8c7da28e00c38d7cdbc855101622da49967c8550850a295515203804befd7cf1a53c400aead573fa709618e8c5237f63dd8ff5430df56c47fe20ee89d3b480beff1bca1536462e8b5ae16b949337b60aad416213fb8b1db40556653bab0080af9991e6552aa5ebb9d5fcf30a3bf7f36f5f13d8d4b421345b90eb9a64be0835936f7de09aa729819e762e4cbe9fe13c3d42d0030a2872c065dc297235548e41c8425b0628569d44cbda90870f3b9c541845126de0950464378e3d2108a1eba547449603ffa25ea3e7ea442233016df6da5422d122092e53c17d7db13a13d50b8d74cf7663dfea11f9b595e120bb803f08b30706d23cd74829c49ba71b4aea97399d0dc8d0bfacab2a1ca1fbbd24eb8e637b71497757c8fee4209b4c195928404869d263918116db67a69fb80fb04845771524bd1f7b2cfa41fa5c7947218b346a86f208e2e087a6523297450ed3bd12d03cc6c87d1f18c9a2db85b8fc2ebb2f2bfb4a46f24a9039d7454d5136ab3361bf5ff2ad20e8ad13c33a91c463dac20add850b713e7da1c2471239df11e854fffca523fbfb81a892c29802e893b587cd3d7135084c49bee41bda45cbe77ac741d5d4b42f1a0044ae4712a6a7ba4b93edca329e8d2a456c546009ef40dc6a102b77c16dbc4fb3e6431d0402cbf22b8768570dc4521cfddb472370bc1c91574a07bae1072dcc56d9540a56dd666df45dc72f29d4f4062a04ae149d51cfd155cd7a9a5905242e794677289709e7e14d11214c8f586dcde62e0d88fe8888e4c8c76cd4909aecfd0b0ae5b5fa57817fc94830ea6a0cfd9b01be5c2a20cc54e1a891788ec2eabefc9b070006732356277ac73e46f434f50009b1441c83278fae54fb7cd738b754bdfb28108bde1a3e1a9d5c48b737dfd82b985a22e1c34a59f1e3f8b8975b8b0620fa536c35f14a1a12d14af42d9901c0b9482fb32461845435c3e7b46b4d013b26306a1170b114095539355b2ce2fcc71826fc4b95a273680eff12846ddb95c342518b6c91e85463fdde3372988a0efa0ac131f429a84803d9d6741d4abd0abad9fdb54b5bcd166c6726dc9ed64a85eea1668007fe5b0d222e739571073224b5e308a19b7d917825b8a084c840074067a0af0c06af4d954be7a45b98ffc04e031a6bcc56f01f2c449e2332fb8e64e3d298d46fd68894d2c3b4a926c6b893f27a8eaf26f87b65ef0a7349040db77b57f81818db962515929a37ce020c7500244fd877fcc2aade6db430b139ef5ea8825ab325d3f6c80affe19247a98325f93061be26ad883c2c937847e59b9f54c8dc6c550eacf3a7b781a54a6f85d06b3c82c76dcf527f70fdab01b858d5adb1a78150436d386478e1300ccf0551c794b6c18693150d0f9b43b860f0ec8b604d81925d073351d008a90ac967c6e3fc2d5cb919491781ebe3ef7bbdbd8c8ba91ef76693be29199f04ae8ef42a954a30be835ec5ae938767f1a0425b874e69093b288deb916dc8cc2be3e4d77008412b4a15f7532086554bb38e1ad3dd2fe80f57bd3ccc108a2d503da72b8bee55a08e3f0ecbfb177f095b44a193f9304a68bf7fa218229362595514fd9495c3646235d18429ba5cc47909ee0168d1746b31a96dd1335499066c266194a7d38ab6b1a487eeba6181049f89ac9aa33a80cfc5d6cd97e7b2a644830bb04c3b104d0fc88c06aa588b1755f4043daa9b5e786dede33c17572247b37a6e212d0d349f0151a879bb8fcaca03c46691fddebedcb925e9c4459015be50829ed0b25d48b4c5901829876f97332426c928cc2a17ee4367356a14603892217fcdf4d0fde8761fe67c2f33c532f07c37a54705060d71c67f1cf167278630adcfdf2d5ee1af9968c2a93410549a30a165002ae99c33f9a7765112c0f71d0497dadb8c96408d3526bba67b11dc1b5a08544514430be502ffa126644483f3c280a8832f27f5fae57663838ea72dca41c573b311fd4492ac8f1dc5ed9cbaa6e7b5d9265bd8d0720a99aaa4b8b48ccb3844bca4ac8bbeba64dd3152e93a306ad8e3e7ea75c7892e88a2225b8c9c8f56ac0a472d8532c7be10ff878dc081545d276c92c2c82a76c9872ec9ca38ad5033746b0c02690eb30b23dbd85491814b575ebe63bfcaf328380dd861fe1bec0be3240817b7de5be8aaf015afa01f02642f05ccf50111d4741929e0877c6470818f3d5ec9e130006643fedd2aad795e07beee24ecae7036bc8aae4f627e18e5837a77501cf4e55fd9b99b2241f9e2097752b346707d5e3582edcd09a7c554a6f35b9a08a131e61f182a4021b42c15426954d7a064a816f63d08ea08a8bf7eaa0c0ca4798f73326cb064b74dcb7d8890d4af4f32288c0a683d94f1191725274e53c17bbdfcf0b9787f79a00fc41ed0ab492dc957759e4c77485853a4e0ea4841d243cba98936d6d12c1b1bb063903388eb14d0bcd5f38715b6c70a761f86b0e1434bf0d370fa377bcdf7ffa69091e04eba23e80faf6d6b61c4661445d5fc1920432bf30e6c073f268a29f4a363fa203a9b9879fb0a65a5b63748c753b680f974231914a127a1bb66eeaf52b3e1092f2619c7bbb8d4a25112c515f71dcf612dd594b30d339c323f1775fb63c50391b6929ecc696952269626e95119252e6f86b32a67a1c5799a9c27ae622df6ec47de472beda2a75198e1d2bdc83e392095140c41f4978b924534c863fb3d0964ee64c0a4b1a07b35750c7b98879df54523046cb9012837162fbf08a07759be78646070dbfa6002d084864d76528449f4153073724a69790dcd4cca653c3486df879ede5384fd6a44c1d4af95137ad7433b181fd0932c34dceb466889def1d91c3c3c6d4a42b194d960320d8d634dedff8ff5e5292a4e805b0eae72b99dba88aba0374326f149a70baf1e502ebff74d033d2cbfc44c52f67c52ce69c3974532e300354721537e27b4582e70c38f46268fbc48128879e823341a9dfbb80451c3111a2beae0dfcd03d531536ee896a0ab1c3f7574754d0a6240110a96eb80ec090c7e3e19fd1d8d4c7db9193c24e5e6ab9b39cda1c27b93e8fa60aafa94774fe18606d668ae95ef0b1667f0d037ff07fac883654bc287460add0a25751c510e46e7081e17595311bcc057d11b9f3c7d5c3ca81d996fbf8eee44995716c03ad3f3be73b49b5978ab3a204f734742fc628de10be4b6d4038c7cc1356f6257de804b73a0a6fcd2679bbd5edbeb3c47f5c7233d58972ad280dc70ac234198111c5a48e0e0bb297eff9eadd39a5c3b6e7d780bc1c908303a124c006f5237ed57c96ec4cb9972b1dadfd7c8c32090001c50b6ad76bb55155166a0529e91312043b6d1e227087fceff0b1f27ef1f82375f22c0cbae75b437873b47fc3a2f138f68382e658645abf0db5aa24112eb3c5ac7b714adbb2b071c5e81244f932262f88f69a8920ee52e86284f804998f37a753ed7cdc70f27829eb43f3bbe3b2987013728d16bf17e89450b1db8506284c1607f584904edfe1810919c069d8b16dd4d648c2cc335f58b91c9b6345a56d28c97f73b0754edbcbe31a3c0d07a14d6057bb58e01475f7f31db8a32385283e469ab4028ff0e13d33a13a71a5381df00a374b5a208166af11a10d9fcd38a52332d3c446e5c9caf8dfced3d8ec50311275496e7b3ed2b2dd5d10a7f43d4ca7c0dbbbb193b5037d977d9ad6a21ca4eb9911074033b471cca7362ef011370908f55117df7ec88ef041489cd6d8bbaa1a1628926098af57e2f1c243ade3f9dc252e4aeeac4215ee63f225547256165c7c14b77a57b12544d19272ebc43a39c76c0a181b20a56e5f5a687199c6c34f56de25c743488a0085e142cbbe61a93617fe0533bbac0bf09fd65fab89982b3df3e69244f011411c504e387ec5b5dd279573526e3bad4ab489616788a907f328c497931987a3c123518bc2f9e9cded04060908243ce1c6b449abaf43bc5887ba10650fd429f7b3be72c46eb66f0d138170f1b80032584a70b0a9a10fdd802c7928d47317e293ad513ff542883bfa84feff09d2edca1b1212b238e9e38a2519f7163d10e6c00f6061f41ea6315c46146627b8001451a48a1ec01884d1e3994ea6ffdd2d06f6cbcbeccf36d84fc6d6a8cb7cb6d338d947c77d83c1ba97436e964df05d25104366b4e6249b21bce1d69394cc3e18d6aad81226fdec0c04383789549a22709af4a62658e9ed2a38aa77e56e6ca4b55c8183917a9075911943771ec2ce5e3478c2d86168bf66e919983664fe73275f48157daf2aebef810c64f7e2f2d83ee90c452a5ae75f68949cb6542ea69da5ebe88827d1a15cbf6386f96af6a0d25ef8497d0d21317b3119cc87f82e5d9afdc5874112bd8c156d9a23a682c1892e0c27a832a92d6ce6d12acd582b7ea82244f605733822bfc23e8a6193ee715b57ea8302156b160476488a5a72d46cc71a64654852725814fac4751beeb84b8808f32f3a05ab4e997aaeb6c434a332f7cec896c2bf2eb0a8ee36283b73e5fff6f47ef43b97fce5519bc86612a4e3a815f7ca3a96cc187503d5139ac8e873fcc8b6f670bf893df083e96330b1a7054f790bb4c8208699fe729ff164254db17a43644a8afff9848718b94a046e81ceb051fc5a686c83e8cfe44aa9820547a40952e1c444b792e849d1766851f930ad0e609910005a1c3c50be1d7ab187bb4f7b404e79fc02c6ad0e33c2ceaba8220337a35b780d01ed0f1504a052b570b214319134060829585d4d83c784e82dfde381db530e1e3b9e566486d19a70acb0527f966d40347f535927400c7779036989b57b7dead94224af9b73bbbdc92f40e5276c124e6fbc20464bd4224eabc9770db3f5aeb5e70899976ed3fed39853ef7bd69faabbf00ae256ca4717ee3bbb27ef6ff36a01a0d6657c54127b9db3e1b53b67100071b7eadb366c5805509320795c761df6b521e3936654b4596ed58fb95cfb349c8a35470459eb1d32ee64dc9c4168ae0ebe37c14a66492b5f13e6778e055142a2fa6ef4dcdd1f83607f60b4909d5fa43bf6b244dbcc6055eb7bf4bf3e3bb32090184684023f6444614bb421bd832594304e2f60de1e68315405a1c450677f0382811534d2550dcb37069fb3a62708373332b186cd6b168b723c0905b9b992855d4fd822928b1a0816bfdab2962d5864d28a64f74f9dc90bd1e3b96ab40178e686299307dd524852370e9688a2916e51218ef30bd508ee92a6fe3270c47f2a888f5293f1176a7ee0347c72238993309295f2ccaec5d77e7e67457506647310548ed2557ee4797f860b2bb1bf1d88185355e119a9fb1836c85df2ff78762a990667a481cb9d8de809a94bcb44fa123df7b8fb04376cedae45e602947ce1b6eb69d4e80311c072eaa9d2503c756bc2323d21d05c24bfbcf44961a02f758311389c64c6589a781825a7265b4ac766f2fba5201e1e7f9e7aeb7e52bf520aa0016b5f557f73a76686a228edcb1404b0bd5af65a7d6d52910f19102986c4df252a48616a86bfed115fa0f3dc3ccabb71b992029fea93ae236dc734b065cd784c675b855d7b8512a81dafb235cd80e5ebb69d64b8a32b7f1396313c60328fc96699c1058ac48f48048f9057d2ec4cfb074332eb7683941cbc5bfa0e562b393386949d635c03780155ca08d867097448c49cbbfaf3aa0c5b53c2de738695ae0143aa83de00841b35198958862c5d67f415464c9b889333810400fdf535fd5a47c0c2960d0162ac18f1ad0482685c4d398c15fbe7d3178539fbe0d3ae58b141225b5088e60df10eb95dfbf9a7fc472a1b9d088330908f7b8695296879908d88487f4ba8663c2058ba7f32a86e400a17f1f317a1e0b3f8c09fa2ff18a056d3527ee51f90f0aba62c3afa867a73a10aabd99ef11304ca906c32ea38b05535d686fc8959d5982e68849cfd6a854ec0594dd522c3df601f752ebb8d0145ac21a243a994d1933ecc323ff813822e76ae52a917700592b77225aac33e77480770fdd0e804149b31b441f7a7107da9191a364b408ab365a64d16c458916fd7fb335232d5fa738361578ac48e8df7f3eee5039e86adf36bd634c5d05bf6dfa625ec539b80992f156e42a19b43152ae593f6f8bb644ac08895a7c13b8f5a6a4813257c907d6ee248e9cddb951719c5eb0090834fb174a4654df98cdbadd4642171a0efd8f3a388e325fed8e1f50582fd0d374124cacd7e58136eebd4c19b0549dc19e14a92d297d15fcb6bbf4c176907b351c257a0ec2979bffd710008ef5785deac5b533e4812097f7705df1ec5dc00481123308b9291d2cf1a52a54c2511d3f136ca7f581c1988ffcabc1eca7dd028fcbac7f07654837fafd1ef8a4842daf58c4a20729c7d5265c1a9fde73d65b31b971748c6746ad9057b373e020f37d28c6f4a6dbfd3e34834b1bd2d73bdf2c194355e0ecf3b6a28b26af92e151a510f537db15df6c91c4f76c9d2abbd16c15b669c3b23588aec15a2484c4b199a1686bc25a2f7561953f786d1e48de9a3c6e4cf61097533c2f654fff80fcdd393c143201627034950bfd11163641c74f5d041511d34c827ee43ff5ba691850007002b9baa9258fd5414d2585e9c00e39a718a94650b1399b51308906cf3f412b97fe69f2d89a76f13a313cfab33388b7ff58527b79ef4953c1cd44ef8a78eac81ee139b5447a2e63185fd92204abd314162aec6288c98754f44fa51c0b26694a27a6b6bd5910b05039a56fe33ee20f4c491303789ba40ab05ac91bb7c6d2fe0ef91ef9e1eae150e3c8b7c8ca70cd7df449bd9ebe81e6e0d96fc3b3f210cf046c3c6bed7e924ff8595fdf997ea14d92dbf9ac03fce945b986d33844d0abc6d549716322dabd44ec1bf5aa12c7a4dc69c980645ea2435357c7008986e4b282037535651297344bc8dfc51e1a103124453b61f1c52fff22a0c344e09503a0062a0745e4001416a01c96da5927908236437434a2db5d153a32428e927b70023057c37c0290e23b9f910e46fdb3a7c7443ae73005600f14d8b64741e7e90f149371be19c2785f02e0581a7d33f53ae21679593c678c836a88074c9bd43b2f7fffd285df1c6941432473da6ff632b925044b9740ccdd9f4d5a904d939e3e35aa9a1c7967ceec628403d46709481130c012e0505268d2ed0518ae2bcd24ce53e7df4dcc4ca324028c839562b715d807115701bc24938934a724683a0a502c25839f06675bce761131c6ad63903cdf24a73a004a026bc51a0e6e1a246ba40dc69477b471183423593437a629ac24d4d0225c750371a176732e48cf81f762447528c2899889de19f1d6af497f0442de3ddfbfd15d4bc1243b3655ba45b68d9160cad8974972a10497158eabc6be94029d125027d73400ad1563bf96b2441c21a169f26f4e694f6ebe54fec490536072854b4e4370462df2a40a28da9fec0cad1949e6297cd5d395731d0371d53059d82e2f52235f56c46369eeccfa4fa2f43da8ffbb8da2d5cfc390d1306ba6427b49934d3f07b7536bca05726ec02255140e00d553adbc3fe2e4e5578918bb2ac613dd563559b0dddea23ff02427ed56cbd93b88066b3ae597ef9aedee320345bfd1f4bd78df634110cf3b13897d59991abe4d13dc0a1d8551a50fc25c0320b93a71bb0ca660f45e2a8be07018cbb6f7eaf8f340138c5edd9a1334814786b60ed3a9741563ca749520988dcb8d7388a590ae3484f54b0f5e1c5cac1cd3dde54a354d9758c83861d41da6725e6d107264b8166965211ce64209031db09693f20e65bd9bf0786fc20dcba296ba3b0e3280c2590b00f9d1e877243e44582881cb2970a4a83cf1b235c9b1f0997e9964b90043623e5409c8bf81c554f0caaff70d12d1b1bef930a244137aafcb5684b4fd08e1829e19258f1980f1e1453b5b32e67e08bcedf5fd63b1e76d80110b21ec0b68acf9e8e2add347bfce41a0c4daf533ad8a20150cf45908211a1dae0e1ed824d565c5699b8527d3f65c93e171de37a581137bc099b2f2d203743a8268c1f36f92af0016104fb7a8bb728bfdb4a66b8518ca3d525747018dfc2c38a39f5c98e59f2fc96b1fed1c8fb48e0a1d6519b067643e0bb3bdb63830767c39e07199eaa9c8e318dc7c742c4c3d8caa9b877a38ba45885cfb10281adacc380c7d09676f4e05f7a9038b16e9841dca758be7e3a67b3a05a53051e44aef502dbf7ffc321ac9b49fc6bd0fca91309d1a662920b41ff29c95c34f28ee781c0e219655ad9f8a4b8f61a7f260e45a1eaade99b40d85fd2690ff69c821fdf40cf6540d1a9fc56c2384e54e630759bdb8657de3808d5eb6d1080d92e65ef3e3047dea3e5e91e878896d748530d90e4f20c37ef16973b5055a6d3d7874b27ac5221425dc957251db7ac4ad3e1335d23959f3348b30a6ce8a9ea8f7337655f32c778804cf1fca009ee367fe1fa63f64a0bef520d85d66e875998d0e74601bc674d5b20eb376d36ff0dbe6ad0df53376bb2aac4a501ed38566cc3ebdd76080513186de415a5e014f1502142422e5d7d52053ec4e6673886dbd847d750a9aa6e04ea1446e07746da7de1cff54f3fd22d7b3aa6981c35da5dc027bcd18fd0607ca79b82a54e85f0bdcc1ffcee0e765e683a7bb5a3a0e4aa2eaa5da1bd992a61c98d6692d23ddb3fdf4ee950b25eb65611f9ff27dbd050337adc664db09a61d6fe97d9e400f49152a10fe2316c1ad92a947638ae51316e747f5cfd621e13ed53f8b5efc7b54629c41e817105c701678cdff6f13ac94f81cd7f7ceabf111c1517766b41f6cfe686b55dc3c7f0af463da1b5a5a8182f581d254eeee91928094cf8c7bf9cc9fb5b3927f075946fed34f431fbb4b1bffe160acf63c6b39d89298d7e2bc96f308754202e0431be4a843c0ee582284024dbf6ae4fe70258353535603b936139a014c34148be2ce1fa0a5b596c610cc7887841eb7a527ef90b9bf7b9b9c01f74d78eda59c57e0f98200180170a4033f955a235fc648628b314488f61255cde1a64873f55ffadd35a2d338e49b10c50cb161414152cfd91d3f05c0d62a8925aa2650e7660560bb9c87eb234908542c89abc0d8a7f87be7c6e074054faf67f5bd7a0d407e88a51ab953977ece7200819a66caacc831de6770340562259ccf281fb24ac0297996d87b0471a2e61025b5e767ea3f54a8f0269c4924a70b400b56a26a7f1ad26594af87b4a8f44ad3a7afe8862f9c7848343b35eb3993c04c9532b9496bbf3753ec4187554aa5e9d356fcc29e4ad3a101371d034b6620f7f651d695d1284d2b418cc475a034d53909600a3aa9fdd7606e5e678cfcf367fa7ff849d3d7ad5653fc3e684b1a4c9ed880a6ec23f96101753830993532675a8d3e5af5b001a34c15f770a11d06da4e75a1a92e5f4fc1b8b70e4d97a91edd1b78af83afb7f586f24beb502c1159e136a996924e24975c512071857529012d75d87b23fedd307c13376b5070a93ea96f88a0f50c4f93665feb93c245064c894715682ca8f1f5e3db2f2b5cdfcc249c102859238d5fdaaf30b043b1a1651ae92809781d25cb451abfbc775ef7700eae587706c7d7a9970a64ea8248210c5bb0d46ebd71f295c8e5ca478c1059826cf6e9566ce0243c9d2738c975d1a9e07afa303d97c4890e3222b9515749a44451f070e500081f07f4b4d0c0de5902d1672c58c98b0e7a7fdcb6a0e8fda4178c8db0357537b8b7db164befca5e11f44487c949e4192303a32214d009988c7ffd20452e8e150123fcc5c5e37a3e6831f0fee1fea7fdd4043f0f68be7cdd64fc626045a4fd48b407afe25020c6bab7d4c35176e5f0206073b1ec7ee2059d386e9afb183becb6ff5bcc3c528293bcc459a67c428e8487b0e7b3baa3eb76ce697f744baf37853625cfd8d1b0b984be68c848cbacda5c0a700becb3985bcc17af44620cd7418e44902af90aab8938f0a2d26a82d386e367393f58e8344e78a3bfc0213cc8e67c66a943ae779584d26b27dd5a96f62bd7802882742b40c7813eb8e2883f653aa8fcf92ec3f2986b7afa38399203c73a7de0eee8f62fb9447980a323cbd103d0652855e9753572b0e4f6cf68ea6fd43df5376e899fe979b0701bcd5fa9c00885403fdcb57874e9404b9f5ebd7a2e00abd78425c4699d4427113fb13adf37c9ee9416901808789fa2c0bac5ba29f6dc163d5d4ab79b8afabdddccc524107550ce4fa04ece38055910a8fe997a826b6ba26321a09b6808c59dc5311b0ded158713693d9ed772c74ca310752143a18bce669b5fe8c82d113a9dc707afe494a71183c6fcfcb06984f22752c54e3cb1220f03ac3f49e8b7f8fd5f6574f57c7b7542d888765da5e6ee99ac2fc8fde2ba964be54c94be55002ba08bfe5df681888f65f07cdb51e4fbb5014451333fbebfcb15d09c31166f171af1034dca592a443c9c039f9ded84b93641ef7ce9418e5ce38faeb7b205aab7d0998e74a6e2afcfadb67a4471ed86d0cb3d89cb59468db50215917cee1f5800507f1e9d427adb222fb892f505ba33fd6818e1e207701b2ca7abb61c5f57b3d525dd57aab16d8faca20593efd0429aa121c7fc1f3f29b125e7ce05b22ae586964aecfda43d81a476df02e4dd83f2ac95fa793e7db7c2b8e766545b91fb22e2fd2bbe248a102dac1800183af5d7c0c92b4bf846410d56a3ed5cfe5e005b2dee1342142f6551ef6911b74494f62d3f3b9ff5c10bc757bda9b0a7bf24c0bfec4ba326ca1ed87718aead878bcc500edaef74671d7f6f7d8c94d00c0f41021640a2b3862b96c2dd9b60041d5fcf4a381342e976db0ab18a03fab31b436730278234da14411a36492855f7c66e0a131df233e24b21d816ac30d73a7be3bc7f7bd9043ab97e0f17493b3ead87bb9f6ce71dc57a26475cf219ba9796e54d320283a73dc616d346025093757a44d5934a031206c2ae18ce69c1bf4c0339f6c5765d2b4ab49f098155d4135ba98f0f73de99681ca4c560212df6e5b69afe027a1f475e52959410c8f4eada68365bd6406fc189f6b36a7fa062ad86b816327d0c894df4168f9918f4a0188a2752fcc576697be61012620496faceb78b7dedbc6e9fc7d37d9b812cf94f93772505a76918753685621d4e4f751bc0a8c05bb406a6c941adeefea4223317ac2b684170e28a109c5975701eb3ca43a72d5a730f7778a2b08236a4b45a3f06a8f27615d2125d9111f90dd84ae92e8bb968c0ff1be866b793ae2dccc5b7d5adb73b570cbd53f56b198a682f55dbc18eb53dd3456b360c5cfd2c43714b74698c430d194d96188044f070841e623e331e6da23a2b46b3899c5ea4319b2464dec4c1e3b55e2742462d98e6c334ae34e35687a47997ba835750fc19a66b16b73cbd59a21ec8805335c32259771396828a91c55f05c39fa1658558906416e54df20f17493247538fa39721396e3d2ad54008545ab7417083cba55f1d5e8f50a75b9d58041f42dcfa904ed405674a58634ee2d13c21e19eb6b921c1482be1810eff8934c9144537a07a5d98ec35ace4ee3a9471dc1db59c119dc3f6566ce514085617c4d4fb3fb7ac3e3ae25880b06b0c7bd86580b1345af5153169601b0ce4ad370600f85a247b4c2fe5c0da69b566b47726a68394564ab13a197dbc99639ba3538aa14511200ec5e715b338c99b4ffff58d02798acc21ef36c620c28d8d9e1bebf2515215f2a8b21bd7b47ad7b9338ae82220e723127e7e2c2b0838e0f61366f7167a0945659779834ef252767568f45c280c76f6884936246693b18f27f8128cd10db4a496a64936cea3131f02e4aef4c8ba985faaca464226e3d96348d814f094aab1b55b23225a358beb556f8e9349025f75a65d257dcd414151e9b106ba528fe60a387ba879c44c39380e6f00a25fac212d2d33975a1f559819b7a0a603302780972301a6f5b802f4b89ab16f8ba62a119fe9a03c6d41efacb3b59db1ad9f112bf0135b5babb0cbb670db86570cb9bc4d934178d57df834224ce10f7826ba325f6a2303bb195e58192c856b4430078d6b0dcad6879ee3c04d8a2d9061a136f5f6957ae0dcfc6f7cc7bcad7d0ad6b340223ff85c125517d4babc2cccd511b593eef00e9b039c3548172e84ccbc9ee65fb3c4a4404a3ffb44db80deb1a4cb632e7c3ee7e0e3ec8862dd22cd8d7c00aa9ef039e55d11c110de565e2676b216f6e0edda0b8f7f85d9e8bd8744b691703dd78d1092b99a1758d23658113a1ec229b05e24df3810bb118cc5459953e2dc50d351bcc7309c6a942aaac07dd06e5085292afc46a97a5f51aeb612c6a0458baea0e078fe1d1906eb9cd6ef61029bff2df8ba6ba71ef1260193f07b22c700fef3e2fa65f35411128debb62166d4373b214b6191b0b13937de1ce3f7169d5d7804306651f5746391e63a043baf40cd37e5f4fea62d9628857e1151bf0d176d51d2c61e87af8270181ee33e71978a64255353c04746ac97cf02d9e2549669961c7c7d45a29b1a339650dc9cbbed70a5258e29dae32be7d32ce7a24ea6e54cf70ea42a0f0958d7ce040c4c0dc233ab8d0771f9152230dde376e49707c64b431775439ced1bd56730e8c5e2cabb2ac61f6ba80c3fe8af7aa9b1f1955e1ccd4076f83447ecd9c4847b182a4bd8338b506e95bdd18e2a2fcbe6e7493fe6a5bc4ceb8bbd48d378feb1b7a808f71f25b9b37fb2a20806d91b7de2a830c08631e6f9a9c35e40a9dc64a320d877023d051f6464fa8cfd8439bd91c208080a8dcef1993f4b1f08407212f49f7ce31a7924173f0ad1efe6941920ed4368c8482e2e54ac551fb656bf02d27205da81a800e3e00f35533835d89b3100333f21c23a2a7bd23e22099f9bc254716e8e028a078c06d4c0ce6d49a524adb2c8d890581631a4680147e2d05bf0674c26b1f4c6b5c8380439c57d6f6211b3a087c6c15ea767dcb759beabb8fd12545fd2728ab106dfe9ae83c437403e1b7a8753bc37f257cfbd83bdc10dcb8331960e8f17313435a9f0582d169434817825d76960528cd0e5b7a0d2fa613b5fc523af731ef98ee8ef6acef13c98e7f8b67912f03f4bf7177f954c13a7ddb67cae82c1c6dd3d4076ed010d450cb1cdd6c21604650264b6d8a194f2e39164a5ab6bea69f4b59d060b4704e9d8cd58a89c5f43a12ffb1628b229ed92192093b70db108f980b92bd73429c47461fd62ac195c2c2ade5759910c4d47333d498c9f318f19229b1f0d4db93a83190a54394d9fae91fdbda154c900519dc3c931e9c8ae42f197b764b265c008eaa1babd48d04239b963d54330f7b362b80129670b6d345f64b8866c6c815d04898c66a5141924db9f54f152b177c9078748bf80441f6720d28e1c10fdf7dcfc77f802bf065ab3286be0cfc4e1a814264628d1f14d50cecf62f9db9c5a12070c52cfffde2482a97fbabb59656117e6f84b15541e2eb88298570e8059d5c11e79b755e3e1a8f60e8a057baf6a20e99390c6f8121281e6761c85e10634f6ddf4ca9ba03e8661539ec7ac75e7262e7545a228bfeb69da7071cdb92c28feef44dd7fdbd28527999b1e98362b422d6d82768b6c244d3e9ab2459a78264d31aee00d64cef1fbcc30839821cff140df68d39a72eefb6e039648fddfc57ff1f1503eb23a5febfa0697d5074a3020c22168e2636f171d839f20a2463a455ad494c4e3218d966e6ca7b56e20cb9315d2e16d7cc6ef22f8d67166905ee2a2ee28a1290a888aed9bc8e737993b1133deafc6891cbc341402cd6367c49229242dfa3d5afc58d77b447e0def1ec30eed745ae07f06457ae4def46d4991293d8b116480413401eaaec5281111958d037ed9765f7a645fc2d697a132df33dcbba5af15f51a959b904ed0dd8d2284fce66acb4bea53f8ddf777252454e0848ad5526b36c7e64a8ee6d73720933d0a906b2d101322b3c6dcd67f804eac424d9b12edc948bd099e5ef629d7f46f7c564cdbb2a9415c0021aa0dbde12e4bb0519a214627e4c3fc6fa6be5fb71f3fc241c6fb7c5a3ceb14fece792f4a13c20b775420061d8fd2a4dbc43083d80207a249994ccd87648fce672e97425f252ea6200dcd62617facee81f16bf45a4f2a4dfe45235469fe0c58d66b18fa5be394f78121c4921b6afec8c58fd119ff4f56c20272494157b5887347f9036e4a76164922df857ca881a621f83d0f807421fe84f51738da71b830cb8cf20c9ff8da8706b17b3ac8983a96920d889de18fd25e09621753c3ca59bf8ac982df71bb4747e9f3ccee0eef7f5b6ab00ab7440bf7b7f414eec3de678ee9dd5ca29060826f796b9e891c8612798731e842f8796cb095c4b3c868c93ff820a3bbf06a090fd661d8301b02675588ee982650afe7ee8d2d13d5a52b2ff5e6b232735b5d356789dd14321583f0e732e5e4953a206eb2c5336a503957920d5b5d5a14cda81ccb4a42403f202f56ebcfaca91c0a7e2a792ade21cd48b8535439e6403e719c0f7e14534faaf240d3823294aa1e11b4945d39fc519fa5004fad3d7de58081d9be2b9890a57561390404b56ccc6f4bff759ed70e5555ca72cef26639817396638fb7193abce502105d2d07c0e7ff1c0d587094738caeb7e09b09e57917cc9684021ab3dbac297ed5a6428192f98025141f58cf501d848284eb026612127704642c854f193631285f750354fbc62f1ca545e9e19bf63e1a2a77940a88e3ac6ca4cacad888ef0d2f8215e68b4d101ddebea3e0c6712496d59915e8909d5dae14f4f12410552ddb1c6f45ecafbfe916f0cc545553ec461a7597ae2f37bae7a1c4dae91814975ae4e7c2e2e8bcb0fb1cfc0eae110ad59cf75b46b4e5570de9751c17dd2057101d9716625b499a50fb2a796a943ff6a12cc833d035c553e9fb35e0c193b1db67ecb49a6c86e95a16fc970a6891c2866fdcc4419bc80dae1e7ea60d08db16af074a82e2a2cd2aff58e9c0eca87728ddae5ed0f885456f05d0d62077c4cf6558f58d04969f95ccbf9be2a01389f4264be17c06c446ac3b0f513b159cb3e9b7536a6f8a3c0b5d4f356dab800ff02a59bf90e44272f462e3a79baeec1bed21eba4d2ed84be37dcfa4129ee608de88f6f2d4d6bfe403307cbffc4be018dec4dd0ef3afb080fb9ef846ec93ee3e1dad81b4e7d6d090178d68551309454cbcdf26a63acbf4a37cee544b386edf962352d89543702fafa96ecebc1c3947811e6ab47332b0800561dd4b3223e6f51f3b7f9a95f65e20b5b7a17f4230c5b6c3a9e7702e5bfe12d1bda42fd10bb60b9bf193d1cca8b10afd2bbb29d260abdfe61b34b888e700483b95616c68930aa4ead19aee8bc2b1828595514ad0f6e22f14e3d16305281bef420e0ce2dffb8f6740b379d2257cd2d32ec5593ec4149c7e852ca7d6fccb23fde874c543055aeec177b82a63d4030a9829ea64346dd7be3645bb1af3248751310a1455b932899b2716449f8882471b481683c371fee24bf2d5508f72b535c461e2c40d4e65ec5e4ac47ce3917455a0c1c3c09fb9853acd88393425e50081491d6df3076470fcf1d7306322f8c730a06a765c44e13d06e835f00b8921563df34011034a6e45dcdc911640f6cfaf7036b42c4c32ff5bca484a3cb4f968f95013cb6f2be4be97a5a2c2353f41ad5385cb1be590d26c99384244359d46afc4dbeb97026e48e684c1194db61f76cf4e124adfe7a4306616cb03cc9ddf8e044607257a075fc6010c16afca0ba18ea84a1487932e262a0af489bf60f68eebde75e4150e33fec1a4364c2b1a04a0a2b228daddf9c125bcf795db2156d5a62f1e302529c4c5cde0fdc91ce1bad3cab96042427051ee1dd6a3f9cbc51b041a87fbf6de4485b743bc2bed73afa908498a9e3b7e411301cfae8c1080ae42899008b711d56953d9b4c868b42473c9dcd74148a7f943805466e4860c494e67e7742b8f88d8ec20633e5168eeedcb8a4992606c4c67d73fd3d301836912e229473bc40f761d4588f8d99b1e0d29c6159125124c90ee482040dad6d276bbf77eb94371d954f9562aa5276c9f2105cb2365b28635b8802d522f60dbb60643bb303b670413d7152899ed77a12a4ad6bf77344d7199be524048ddd1b65d2b0baf79d6e0fbd1eb8e4d621c769437cf70753d57fc7fc618a555a85a9084244b7acd201d47a209116b354c8a55e5b1ebbf1aff48eb24a72cc9397470b18566fb5c50f519f8f6675043ad0cff4bb706d48411d68217f043b265aac60bcc674d894828c864f6eec7a76058b0a20cd3a9e509e7a57814a46f2cab95501dbdafc646448ad6ad31c2fd2799d8c0499e0bff47213ed8550820dcdde0eedf5ecf874c5ea37d45ac9e18f1308e9bfc046adfcd15740c3480e4a55ffdef459e0dad349c0a65ada48572aaf71580044e1c27082ed688f159c60c5f4c21952194ec4f7b9ebd208e69fea6ebd389e64f17b8f07faf6a561ab7264140cf004cfa9c86a2519edcd10af6946342f83da7371f62d1cc78d0a1617aa3bcdaf84c229046658443eb048cf30b28f12f06ffe585ec3ca4a34bc689d77ef2ca01d73e6589124ecae06746bf1c9ddabc12c5b643a73c373af674ada5e92f86cf9d9f81dc43d6827f0c4650d519b83f0661511deb53664fc466b931c5512b38ecc3291a86be776801f19fc9894994b7e6e724924780003ccb20b9e00eb84b74b1312bf9ba75e140d88b915f8bb685948f218f233a10e4ae2b09cbf784304b38e2413a49d6839215299001aa7fbb0eb891c966aa9f93c7eb51655f308ce1b8e82f86b92a99540603f1cb9b9e7c8a8de9721efd0ce7fd9baf21d2c0cb711f52e284b7c25ab80a63a6d379a26bec169a00569e16bfb977602feab559a62fab7b15b21c12bcf2af331503e8fbb2368536fae8cc094f3887b3150041b3d31706031f72751b20d57ca113b4f6f0ec31c1a46349411ad27318341f28c7197fdf7a6d805079f8ad174fa2f7b4b71df3d473875c2f938bfad60cc3249df625815442c4294182ac14813ce08a86dacfdbe97490b5b8edd6cd86cc7ff60c205d4d8fb9c71449af6d23e3d5baf509396b3788bff2115c7a1f2441fd2e6a4e776e0453f604dd502c0d9617c92d4f71ef49cd67d004a4b181cd060a43f7ba3309f2370b3b6b215b6d5948f8d9dcda44dd25c2265b108c8ea42cce080bf29ec587c704c587a8c7110c78d43b3832833368ec1e4e2c4ac4cbff66132cfccd916ced4072a8693a307be70dfc0e0eca0ea799be537fdcf9169a3b19bcb55e89a92565713b53d0ee0ed6dd4ff982b2c1ffc05fdb45817a007be67443d65b02a31e61ddf58d8532da3a40c2baacf2b572f929103f24178305a5766831192308b43bf669a8cc7c30c9a0b5138cb226a44918649a041171312aae2a99be42f61de629a8479b57c12e24c6c0d0b00c457e7534cd1f983123ef377a1cb636674c0c9dc9ae8d3d9c8c298403625c7053911846fc1b0642deff16dfe501080440192ecc9507269a4fe0a853145ec07176a3d84bc3810eca6ec78e78f10fbba15e3789a25d1bd61e2a36083eddc32edb5ac19157a870d682025dfbd7e92b4bb602214e32f8640bab2924fbc4684312dac5b93af91a2a8d68e1365d99e1606bac0654aefac64c9e0dee51d99f8a39c995952042dc203e8d5f671377d2001fe2a70f00d94df491e975c7accf05640fc53c2e8457102b4579b52e27637f5b4bc398c0a328de4b6358bbadb46591f1e67e373396bcfeb3884a28bb9d18de249cf65d17b6990a219cc057146369493f84f54a480f5027339f49f68b1799d0c027ddd3749fc25021cc6427133524b82ec87754217f279c3f85f717ee41f48cafbade4de5b8e291668e6b6afd14803230f547c710fb4dc1e0ff14cb05a3b9250e78c5f3da040c9f4d49a911110efbbb52a20143cae6e55187f813ea93867c16b42b0ec317ced02027c96137753f908513287e9a94e0eb049073acb4e5e3d0e6f3f126ccd8a4b71a603e41323b5169ab5e6056881e949254a9c7680d1d3d47d3284a4a6cce92385643691327cfbd53a57fb4db95dfe472df35362313344da873cd19ca583667eb234d0a6c50762cef8ebd15a83d6beb69494b98b7d53271c42bb6ca68730481b496ab26b0a716c3252f3ee9ccd8c2ad07190850865a62b816ae970da1879307baf08a9c1cbfe53b2522afcc6e881a62da2d3d2a27d08ee5b9d32a0413535cf09134780958c400f4a747830a65b7137f9202888dd38caa9e3d34a7e5cfef8bb8664b84cbd6e5121830b270438ff67107d8baba2daa5d90d6c497b50c1d8fdc41bad598cb6d885654c78c3709845e9f84819de3ad8390aca9fb8d9f1ff0ed44e9ca80d4fca176be2f6abd5aeb9977decb02043420f6227fbf45925e6b54d4d71d2e273911f482589849ebd7fe3da4734733246fb093b4b15db1a9499e8c37a733f3e7007e22dfd506289a7a50fe301f54c2fabcb716967b487f9a72bd6234d0f4dc45c4e4dbb6798c651ed2150545a1f15dcbbd695238a09c2912eb0be38b25f086a20dd935c07dab819652625a0e62cb27803c7ceffd36e56b0885fd175ec0c0fe2b31525f272b5ab810a273cdbb6ee9e6ad425c072b82292e83eec34453e8b0121d1b95076f12ba5037af69951e8a828d90b7b39d0e9ef90683ba12aab72eb906c22ca1146625b783e889093d1a7972c0f7703cf5dd1b81fab3bd8b2acd874786fcb42012a3bc35447a07f07f2daa26bae690ecd89ba82723ef42dfc18c0ff0d4cf8e8bf22c1917308213020f50901bc42c537b7f8a6673ab5c869ac9d84d2fcc627fc198ad07128eac2d4859ca51bc565e43ff8d0984c2f72144c68ae2a563cc228498d4c81753353f134b509dcb3f92e47cd05d8b49c13056f153a8858c74fa33d87c964acadadc69c1ead9a05b12a57158d0cb96ad5c04b62d06c0fecfac237c86d177e03acfa231159d98b0f3a3623a040fe32a060d47d74715684130a132a4d2beb52e35cf80180cd4411d10ebbc7d1bcac7a13c5af24a0c5116508dcdeacc3053283a1b1f11589188d66a19072e923a984c6ce52e0a6c58d07ad021277fd7d19d8800c300d82f67e52bf2e0c8276f381d8354413047bee007a6cb1e197be1cac206e81423789ce19fb39ac7f21f97e4ea006b29abe91f0b2c3de154e2c122b512486784e3b67b41534dec3cf219a93d96895aebadaa00cec4815f7072a028c5307d571a960372c0ea4461a5dda896e5182d538a61c439d57c28066f7835e3847d6b9003b87180a4c92a0027b68e3fb18402b4d8e2cd1a2216d5c522dceb4e93cdd82578d6cd1ea85e6dca533b87e875cd00e74e5310d2d08b22f500d5d57813412b0bd81051a200e6b3fd9c50aee98af7bbbba23443f459e3d044531a54c667e3fe11ccc39aa5760689c7731f28300b4c41b979d6935960b731a5148c1bf578c027fa37df63df29c6f2daa1850b4f343fd1f1098d641d79eadd34898ee4a6003ba6d9e89fa4223e4965e037e1b5f552475ee0ceb62968d08a599cf047f5b0589e58c3af666a49f380272923b0813abc91f376f6a81de1390c2ce1ded4d0a373bfaa7ee220bb9dbc963aaf3127f91c83bf19226176c889fbebc24fcefaed2abb95e2e19d110ec1249a337dc8191509e0ef7716f0433c132b9d111832ef787c155602015294ec3d28aafa2f888482311655d62d681cadb031ab9edbb8f59ad1dd0e8dd15f915435e48dbdf0c4a53ac6161d92216a4d100a110e383925b8a2d7593287bbeea4829bdd2d3a475c9b3947c3a753a0d80e32df03b1e14c6716eb4390e6aface1af8b2248e8a84a14ab96413c3cfcbcae4d576b0a4cb58fbbd9bb3d3e1596ccbaba5b72028ddf0e09bbc570b7612c7e5ced498d1142c0764367aae27e29bd298302cf4a3eb8ea5de8966b7d30b284d57e0315cb9a5bbd7bb4eec66160e19fc6c02ae9428ff33988c1442fa5cfa22e7689ab025de81241a0b2018491d2dd851bb6d124e9ab833ec7ac34187c0b881c607de7d014259af7a40eefa2cb35b21a3b7907a4d400b67386252d262a6d6a6506c8d139e5525293b0d5f24ac39b28d25872e66870492f0f36b8b2792821b9912692a669735ec7759f1d32dca07e26823fa91ad416358130292503f25680c3ea165accddf481b6274cfc2cda724dc9ff7ad8ae4619d91d3f1afa8e0de5b810ab01481fc1f5d5c6eac71049c5e29cf65405fc96a34799b3fd1d1c703568a8525ac764fdbf6d3866e3a793af7369ccb30471ffb6fc7c83e1349463370d8955770001cfaf9232865a18a5bcf15f0d7da11495d708400b8a20395ed271b2c233992a28ddb434b41e111b87910dc000c20c8b49bdbb3a9838daee30170ef5bd7cd759c4c821dcdfaf6cd920006a472b9c78da3b3a9ba0d722a9725416922a2be854a833f2c7d5604f3cd8922059aea3c3b45ab8056dc70487fecd5a70c1db64819e26e6870501d05a18eb91d843d2a053213f61052b30805322a6000a300b20a2e6d9851f83713ba9d42a623d1bca6d6071e1ba121f748c08826824c3a5bd134c8b19eeca8759fdcd1e54eb94d61fb3a04488409c74fddc6c102f13960460cc0c0d6b105177b76949adc3e6749da5ddeaf10cc4eed90640e8adbbc198810cd9bc4c22a83373a619a0398445777dd0adda73825b82a65a0a149c71a5331a7017b0ed77cbcf27ca3ba105444015decdaed76804ce6210305dde2841173b4097e4e7cb6c06b2b2280ccac8176408e94ef6339a40ed990a9208660b04aa8ba359532fe72983b83595f8b704e940f11a8ac48741fd9482b1e1678d795e087a32285884746a7832edc8878b4b29990013202f57149ab5a2b8505e1664833d438a11350960774aa012a8d79f47845f585f4e28021f6588f09814d1ab2ba2faa8c876ccac9ac0aa10cf3621ba853dc49227a5b419e4488cab2987f7ce03f6828a35f0734a520223f5ebae347f7a9a90eb24f1eb2b1fe7b685cb1cc63a0cf3a9a474d4199ecb1ed826fb7bf801d910dda25f284600da076731a19072845c45b0e9d7058d315d23554fb003e053e2e6672ec46952d7a55b69aa61700eb87ea1a74381c66b112de1a82bf4994a7f860dc4374180cb3985157c7fb1b759f64a229c8fb374f7204bd3c2efb8802380287075bafea0f919f9e3db5d86697ecb2e2b214408a79e95039c358463f7d456ea4638deea726542cb001d826de84e8bbfbc947ea50e3082c2936dc9a7ce2223beb27170de7b696d51798d109a773b7975a3f23e41729d8e3424c4e6249acccc8fa6a1c89d361c84cc17a71089c7a02961e852429607c7734af0c2b2c531825835fb83da0404381e1d2b02a9f78b14220878a89fe28d95453001fdcd7f392f6f349fa8c8c7ad6f0cc70d580a9da6ada4a392198961899a801cf5c369546f670a6a697862cc11756ad078f6dc5ca2eff616348767d30bea4dd1b30937af10f15147625de308baf7b07de7b60d07b281b8204d4d90670f96acec5bc9291355049ab19126ebbff05146952e358e8d867ceb0e5b09f7626ff21527f4f90c2303be2fc3ca028ab0032f86395c3e55057a71dcdc58724e56c0fe985d583c055d615682f4deaf307f33a69ce4b793bc3ca4f6f00a3ae3f08bbee7cf9cb78871c4f4f7e16a56da2a75055af309a103836ca65df458500ef1efa01c8fdbfdc9e47c904481a918a5c55b2773015a25cc44ff1c7ffa6b023e1ab7b8147acf3be696eaee36f9ff019e473e1eb6a2d523a93a4852d37f90f065d2aaab65cfdd7a738d207db8052ca237fa84d3db3ca01bd686593fe87bcdc9fe08d5bd16866757682fc9b8d5e79699bb5a12351b30ea4cafe05c60a9f6f3f365eb2cc1b984d1eb74d5f6cd835628734696d8ff20a2f776d07d2bc3aa91220b596bb1f16ae7dafd5a391f862418b1ec49555e3a45d1da50f53154ec6215ae2b3a9903731696abaa482164523056c711d404522ca063ccc74b9eecd09e72c409c07815f7cf80cd236470010a95b9468936040629de4e328cfab7f7cb3c12b67b20ec472da5e6f1037b1e044f4cba68e3e3f3ee6fcb3fa12eaf2bfcead73654d0290444aa4d1bb62b424fefb8950857265816a1dd2785c41e9d18e78c8b6ff3e557e3d45129dc148d6909d413835cdd4c857b464218014f5599822c9256f3510b75bc82f988d007640b663bac005ebe50ed1ebd25b44e90f6eaa27b95aa9ff221e1f3308578f671a0c3bdcd047e7321f92e9631ef8aafbf099dbe92bfa9e22bd157526f3395d6b0b025ee0a129d9d66530c61ed2b3c1a2ae8dcc86bb092aacc46ece1d76a1d6165ade407201f5f67c033b12ebecad1fa33d9b3c8b8597d68923874e59414877d99a32bd1ade7df1a1d586075418e05043054ecc995e04c09ae904921b45cf6acc0193768db032525529dd5cf36571885a87a73c902a8cc1d1fb069a09077f1ead7cc6f4051241c1e838d57a229202de934b668dd52cc49fdb44220f55ce1a61497095bda3ef04db4a622e90387ed35b53a97f16eed82602b3d1093a6a5d963200f0e53330239c16a8ccbf1a4529ebc6f8a6f878c6e20f7747956dfac0fd90e4a96a791943398b74d728b70e356fa26c4761b3052d0ae55e1a77477515458ca83db4d2a56bee360504de15d31852923fea1334ac8d130212535901253143ad22138d03902a565b3e061e4add8d553cf98d215fc7c209a94166e04a2ea96faa1fcc919e410d92b989ec066c8a8d7963c194bc19e284a5c8165130d77c7d7240a2cea0d4e1113e746e9eee9cd6fc70b019d4d1ff086ae3a76756402e4dee6a07b02bd88769e5c1e4486f05f167496f0cec7835c9535839369fff62688380696d6ba69a9bc508d278677d7a727dbc48bbfed6d9b7c24e1afed177ab7fe8f607fa8881e0f72470f9a63a04f8fac6a6545323f2f404fe259e285559d344b1934ff53d067afacc66e03905149f722ff104f659234c585e3eb2c3a7659ac30000f4a5d3be8c10c04e280581bcc025e7943c661355b6ff0954ecce07917c24889d050a381b745f246f1194a5a2ff1231ac0a13a844695bd308b4c09e6918449f145a367efc4a88225a99f5bcf2f4e13b24cc5d6da13905849763dffc73bfee8869b139e767e9f781cf308519d764c90a55665657bd6b25eeb66ff5bda2f524d63ba5f7cd5e3dc72b43ea3e370951a4033cff7fbf74f00c85facfab132f37c4e34588b6d30c631c50b67e70e9c5f65f080f229e572baf44a602ef0f17e8545c79469015c9377a686e9d3c8c1817d0570bddc1e697b8da611c2872c0340a6871df28985eb7d04d11c27fc2cbbddeabdbb8df60025be8f7f6082d5a234d95c0c588b5c405b36d10530f556ac07de1ca02b2cab70191175328cf10b79bf64065ba029fe319812b3d41a62b276b8e547ab8f57fac7e205f09721572d40b529e8438181e22b71af1edb86ff86fdd084db5656d3894eaeb25b76fb43b66ef472678d628fd9bab665d0c4eea9c47add8ab3c96b33fad386f7ad7824e8e7f69e77ca8f02160caef57c13038315d6ba862fbec12bcdf5a9148116f547a435d6e070e82cd36c5efa22edba76e953220ab160a5b394c39bc9fc4fdbf7c8878f64d0bcbb7d97a9ab3add4a54395e2b930d372b1852d40653e6610e6fc0d95f7ca468f9974421d75e213a13a41ec5931f0c74bd8de54c3b36187e482ce79cf2c68f749007463a5027876cd714a5c93ebab7e8c3212667d219982f8bd5e013a936dd6c8e837873808a4af800dea60c17a5376f19c1a1110c4f5fbfebb71bc0276ba7786a79e15d0ab1879a42620219a02e05c9109af13f68ba723427c2a654fed465dbb6e390c676eb722c09af54362b0e649033678638bb2ee6b25c6b1be7f8af78774af00bd35e13e8b6393fbd9adcbb9e705ebf91486650162bc824794621a9ec4caa20248e85199375609a972c31544f5c1bcf7eef6900a329ed8c27eaea7991a4b3e606ee967d190e2339be8c7fb1390830892d9d83bfe541f1a82ea0f660cda59d004b3751f979f45a2920646075ceeb3684621b8a957830b7a6b5fc117375efb7f8d87901558dab4b6df4707fbeaaf9940faaa2265e401448d023ac5d15e5d3090b29a0e36e07fe577fcd8bf8ee254c9effa9266b5128873f16367a2636c3244e91aaa937a453a9281070b82a4c29005e4a60150052a5307a255d5621dca1a9e4321702b32bb3442ef66b001316bad971a892cd9ddf6de5bca94a40cb7050006ea05d469587b7d54b1a5f2bcfc0071daa54c439423a3dcdf48de531ae7add570c7296d7c9b2bd0777777bf4f5ba0d1a6d9486d2635c12f31e0d32ebd5d729de681daf4fbc03483a6dc4b72dfe80d769ba7019f9fd82d11d63089bbe68eeb214144fc4b4742fdd6433a480b3efde604769d0a84f190db0d3ba98296bb52c1a7d372cbbcbbfed46e3ad70892dca7df7e2a2b4a1d822dceafedd640426d366e017e6dd76673224ec46d9f5853620431c70d2a9272ff8e557277f7ee55709e8eeeb47903aee5484b7c23b7bbd966fb804db840dee91c4a73fc4b1b254e9b3d5ac1270e8ec386b02c9f9ffd756d5b32a5c2daf0de7bedb7e3850ea15f2609fc4202dc56745050d78455627142b9df0a7547590612f8ec20d418d8e25cc7be38c3a71f315d59a237fd14d5420a95c79260710fb83c16e73641f0f9e1446aaf843f49bcab32157c3a847e1947e0ef8887ebf48b51641545420d1b5e7e3827c571b75f83eee89cd37cc1da7a6f354fd124f76320cf93e4e20ea16b15732d21cc1fa8a2d98f736d140915cfc0e362103220ab5393727ffda921f4b3bac0a7ad32ec0b002a99f0e72b7024b95f90c296fb79d8e651651d427ff9037fb64d7c8ae516f86bc27148aebc20a5e98a122667ea51996a16fb4271c167bd92fbeb15f10a6d721e2fd42b62b6df1561e5a1344edb04c6b51654793a8a9fdc5f87fcb429a3824f7c035cdb9bf2d94157bc566b53073b2d0f69b36f804fafe57ebc053e9bc694bd866572f86c5acef9e9fc27d2d7cc5b994d22b46f5f31445094b40d6d827324d24731a9e0e412a05c691b7ad2dc9874947b4948611fe4d6e1838229f78bb8be644481cf8f5629620d52844249eec7f76d08fdad2178a6fe27c3df3ff6d3d0c08294ebfeee27b2fc5459122700138f57a2ce11553e406c099b6624d31c7ab4032d531c350f21d439333e43086921621c3f1cc620aa79c4625b5b308023a3a3a42d5bb66cd1420b202c7eeb45f1e55abdd77a71f5a24ab52a6770ac9ef53dac52e57a572b8b2a107ccbfb554bf4ca191cdeb7bea7d5fa1e5679ae903df105b37a9dad77950340c2ad7285318b3fd312590fb45aa98056e238c6c49c80c61572cb558e8f5f38b76262621e8f1f33aec605d4ccc2388e33385a3f7e4febc7b2677c170b333858dffa1e9c55df43db1cdb84c5c4b03acc228b93ec243bc94e322b56c026b0096c029baa54d1a2458b962bae08927af185f270e87928d4cbf55e887a2f7c6ffc10f59eea3faf56ab95caf33cd47baf53471e3f7cd4eb6ce1730864b2ea512c55193eea43d4ebd491511fbe4e1d398c513d0a55eef8d587e50a55f6b0de0bc35310d587a7202c9677c5f03d55c9f25c2f2e94e7a15e64c2171f0619f1939c62eac354102f955ad5d4cc982143060dcdcc8c8c4c4c0c49c2c0b85cadd638b258ab954a85712af5f282428962189e4e20a85295e737fee9c5c84f3da531d59fb7f5a9f2ecf28ad29b97f2ecb2f7e32b7b30e53896aa9b574f698eea57abf74c4f473e77dc0fecb8d97533eb3d95caf5aaf27c55ebfcec895fff66f1715982f8b5e6d721a3a202071507dda5b7a674a8e5fa291d6a4ab8e098d518d37ad7bb5c30303c5a2f8eef125f9e4c671706b9805c3066b14899a6a101b9a854ff59f558f5dfabc2564bf5acd6e35679766e3df9aa178b358eaed6c33c8a85bff5a299d582294350f30aaf56a3eac310af5e9d57e559592fd7b75e8e62bdabdcf196ea5763998385552a159e098b58610538063806380638c694295ee09915fe15fed5aac43bf08b21a8c9de8ed66a0551c45eac565eacbcd8d16aad5aaf536cadbc5d14ef87bf8b1429a018a018a018a0185450e1297569ad5efcd5ea15e35aad7e5cadb07882f7ae1f5f7c893362d9e3b9c4c7e28fe53902a6ecc18c3118af4611ecb2eab25a7da025b6ca735c89b72623037659bd4a35fe8af5eabc529d2bdcdaad793bbeb5abfaf114746ba9db0898643c2523e1cc14d6ada3fe4857c2706b3c496e3953024ef6c7c95ece9460bf874a6941aeef30d41ede95879f2ffdf449148e547f0d120c73f1fb3c12c50af638b5008e54b0a650ef7deae5bde0a0caae047dc7c1d7f919c707703c9d934ef8f4f33fbd7e4e0fa4525bda77540f08d9fbefd50342f63f01846c65068054b3bfaae7de7b7feffb8729323b0c05c1fff4725110fcbbd7583fb027e979a5d866d7a2a521ec3e7cf7f7140c20c313204d4ba04008110ff81495c20a48bc604b0fb618048103cfe9bbb2beb0db7438bcee48488b1c4539698ec812cd152192bb7eb905afcb18b25cbf47ad2e84954ec9d63befe8c7c22b52f5d84cbda0fe85024c021f3f7e62001204488612cc684266a0a156830d370c31010e27a099669a69a699664c02f7e13ffcc76370201ec4815c062f81cf9ce6427c06a7c16b5e83dbe037f8103781e3e027f0204781a7c055e044bc88af80c614406ffa79e84dff9fa469e2c0a6d018cd34d3c641e7a0afe8a5aed258b495bed2593496d6a2997a8be6a2bbe82cada5bde82fbaa9c1e8305a8cded25c7a8ceee2b29e42632de50634d6555841634d05151aeb2838a0b19e220734d6525c41631dc5525885c61a0a1634d636b042630dc5151a6ba42c68ac9fc04263fd440b1a6b27984e5bd05837c1058d3513f4c6bba0b15e220b8d754e0b8db5132f68ac95f882c63a8926100c1a6b24c2a0b13e420c1a6b23b6d05837e14263cda4c7a0b1ae4177a1b15e426fbc88def8fbbf4ed02a4f921ccb530996250970cde73f8d551d454b6ac0a489114720619484124e724b30d1c491134f9e4082c2065092a290620a9aa578e07bef3a5589daf8c3603838a6f9afa41472c1d5bf1377512da2311d4b684c0335a0b10c30a1310c34a1b10b184163163882c62a80048d51c088c67c92a0b1092841631270426311c8d1980a4bd0180498a0b1149aa0b107d09b46c1091a3be1098d39e0091a6b00128d9900058d9560031a6300141aebc911058d2d400a1ac331058d911085c646a082c61450058df148a1b14c6f64ce2acfef5b9527c6aaf224c97fd3c4c181e5ae324a6fc0d7e13a95da80efe08be549aa32f86179e2ef95c1d27eadd97bedd09b5e92ddc6aa3146dceede2d3fdd0f2ba1920d7e2c91c77bbbd3e947f53cef0bc3dc8f213f8c54103c89e2961f3eb008a5a4544cbcbca43026ea8c543e7458ad562c162be783084d894258af41d6fa17b0d980dd1821070a2af870e056b25f88dcddb5957232dc7232982027c30c39a02b39a02d392028404a0029f9016807a0222f4037e48084e0972eaa30cb6a5462893517648971490b08579016c088e338b660609c7489920b92432ec80db92031e48068c901b92207640a1a1a193366e480ccb69465071d7470452e0629723138c9f948928b2117432e86dc4fee27f7a325ec4017a3dcec496ec6c45b45aceb87570e8813796cf3deaeeb723e6839204679b435370b22a4c294fbbfc76208fd6014b8b38779c6e31f854ea136349ff7062ad75c513d3e54a13634f7f8707ad51f8ba70294fb45a7f1596a6bd61f2db1d8a6b53d589194fb69b0f84cf029b684e09324c527f8b4f9eb62c5ad6623b2c3fe88692ff0b0593a84fe13157cd2262330b6df47441755a5aa5495ea525daa4a950adde99717e06ad381de0401d59adbc89ec06755aa4a55a92ac9c6cfb6d981aaf4c97654255955527aa18bba21612bdd1067d8bb92070bcfa3ffc3268dd5bf2f1d6ed3cfe3019747122fcb9d5bf6e0a9f9e505b8f6d725f4a6bd72a612ddbaf296d5e67fc232cff70496a1bf2393d92a7572035cae4df0cb054bdf9be0136ca6325bcb64d038228e8dce874f0039654beea7e98fbab545e4585af21c4cd899d224f78fd032b7e5d6f95241e01915ee4c0a94e767690685ee53a0dd1fe9ca6e729bee57a18a506d3b39faeca59c448b6e9cb4686704d80d9ba5c1141ace7efd6bd9426dfa4e13754c14f86c1c912a37510b38a1cea14f55992ea9af0f0645ba6f37dbad8494fb5b080a3ebffcc2036e5b9b1fee731b2e5fb64747589cfa63c3543292fb6bd0a29c9923f669f61ff3c5429bfde00c571a102a0a3ebf9fef23c9aff1409e09a0c1529872ff98c4be933be01353ec8fa3956b766b63ca0c8a20a9953690e013a7e23486b569a34d1c384fb0d5129efef5dbec166b77d7ef09f6ff1ae3278528f78b15e58fedf70194529a546f4d6de441f676b74baab7a636b297524a294daab7a6d50c94524a6952bdad66c0f59d524a935633504a29a5df4c4feef2cc1f941066c9b5ee74038ea3d168341a8d46a3d168341a8d46a3d168341a8d46a3d168341a8d46a3d16834db4ac2f3ac2dc2f3acd2b5b9bd9df781a75044bda4b06ac51a5b2e183246668646c68c19cd6c6626338b99913398996b36a366e69ab566e38c355bcd54b39a9fa96678969abdcc50b37fcd50337116ce4eb357393bcdc0d9372b3b987d336fd6018d9937a3e1c1cc0300f87bab4948699cb7d60cd15d77fde9dfcead5771afb5d65550dbededbc0f3c8522ea2585552bd6d872c190313233336a5e29d75aebd75ace904133231343fe4b86ed1e2d9130aed6c87a9534360f62ad54b8ec60a6a9636aa3d40baaeb80864ccd2b6a12bda2dc7fa2e1414ced54f300002255c2e1ca2008801901800171dea704e23ea54fc9e52e8627598b8b5a8d08d304febcd38cfc8934762dad25f5bacf6128abb8bbef5daf350a61965cefeeee7edb2dd6e0ed458a09cc127adbe58314a8264142a64c5947b54c9fde86a1a902657f540d2a50a65fad9f8aa8bbe758c0822347689a689a6c000552120d34a420053d72b5a61c56d1875117445a8638d1f2939d64c992c34f3cade7dd5ba2641411eb0f1e1549b83d2cea7e87f2b7f5be4e9bafd723d3b1e61e5645dbacdf09de7d6b14d227f8a4a94e00b54873844f1aff27bc544d1026ee7650f8723881f5c02acc083eebadebaddeba1dd08b4e3728f496701df4e646931c5214113c820a6110c4547f5ae7026d53dfaf74162af9845da9439a0a2dd326d8cb77f0c228ec043eeb2d0bccfbeebb7750a84cb99eb04c3bd8eda82beb0de63a15f74eda5b63a39f85b599ebe77514b4c5e4425adbed0d8de0ce138feafb348975805a01818ae001fa41fbb44df5f9019f6deba6b6a136f541f80e90ab2781cf8a5352a354a49a6b82b3354cf059977e0849ebfec9fd75cb524d118c65b4294608dc4f95927c7c923a6bbb6e42b304f6b7d19b46029f34c97d923e1372f46054c38611e89dbd016bb3853ad7b0538df2ededc85abbea5509d324bf6db4a95f90b5ed969b742769daacb1019f6d6bdbf7614c92b65ca30caf3d81de957e5e77c397b3473e7a1551dbeb8ac527e40b4241f5b0a834bce4d08feca50bddbdd7ef75f7e161a1fee18d51fa91bb741944f17050af5851c46f68f2acd86f1fc771c8a8822f6438785c78def5ac07e469a172bf1c4677dbbddbbf9e7b96f674afeb89cd6341f0743a7585a1fa0090503dfed79f3aaac7ff03b7c4382608ad7f87ea29e1fb40106cef2602c698ea1f0012eefe2ec1fec82e74dd30e46e6bbfd20fecd36e6bbbbd9be8a395d03fb20b5d083a5bcf7fe06e1b06f6afd9bb7befbdd7d2bea299c0f8fb3e130513c7344d25f8ac38cfe57f92c4f8fb4c00ea6ad3a10387c3019f2d6b99ac65ae53a36deadf206a5294772c8e695ac7d5245b569cdb3c590304c6e50a5483da6bd29bfa7df40203b1f36e3bd1022987993ac1e2239f349ac0a21f070a316c958f0b1330866440c4921f6474f1810510367cd14517358071b900ea41c40e4c60d10477040b131e074cd1830c86b2900288925b0f5c521128800839e9e2821018e9a10b2290c4a094455297c4139cc3039ee4bac860a5042da8821aa1839521a880f203085917611c5d1f968a58b2a4c4ac3176e32521ba9786080011427648820a961ec6c8e143c80f51dc808a24349e9e819325a17c9a4760fc481fd327a90d559e7eb8092b548b262444846062ba81053c3d4c90c4881c88a0218c22aa542ebe9a439035024a128b49932b43200fb99f467b5f7522460e2d8e34620409431452e56db0f2dd05b8e26b9fa29d9413f8239f7eb5dddecefbc053e8a56a68e8fb39ddf8abaf254cf52fce2a55250e5954c34195356890a8ea8217df21652f111dd8c3a9bcd666fc41a022c4d00f6ef8847d3fe2dbfa260eeca866840376ad61fe2a7c82655149dcfaeb1b3d45031d3aa2b754397040a4f4e97f301d6209731b12564384cf5aab4a4ca2efac35ec3615663c01795d3cac60ff8aafef851343c904501dd4772a9fb0b2d628085484aa03fd20327e90849aba10621262126a126a920935093109310931c984b6c87e64424d42598498849884ae0865116292e143868f05d07caded509f68c736e9efe85b92a2f8965aea9452cac477187f29c0f67d866843b4ae9ce9a95d72579e5dd7dddb7524beb7eb2e8931ea3b22ba74a1a4fd72904fc3c4b17163670414ecfd30954273ae8c0c19f69924f8731bff282e0817742fa5f4e6e26c38dbbd3e389be36cb820c705e18270379c116bbb0e77f3c121c1dd1e676b1d12470417d4281f5c106e08892342714ade122e5cc66bddddba13a75941d7596b2dcd0a3adb75f63ffca26dfab4d96f51d65a6bddabedba6bddddba93bb05f6ee2d677a6a977ccbf3de7beff5acd7b9e79dd7d510461849fed98083dbed84c38144b8ceb1ef3384f37037ef7623c2212112225242844382e4d6a17c866e4690d8db6c2987344cf27237c8320232cdd920cb3e4320a55bf0fdfafd600b3dba1e5f39d353bb645b9e94de589fa1213e43433c1409b6e6645bfa0c9d35ec5027f80cd17c8668d96728a84d9aadcf10913669f619b2f90c1119927d867c86689dd323d37c5143403e4335f7d267c8bbd6dde98703cd17345f309df4c3e49b38f74b84eec8ab6d61547f9274d2173ef85dd446dfa9fcf0a9a43b4de2b31a65571d8169154194654ada1ccf71b29938366eec8c4024fba3e055c997ba287b92b0bb6d03689bf7d579b691b227652fcb992ff7c8fea00ac837718eda8b6c33795f842e88b24cdf913c4ae9496d36922365ff7eff3e4c3e4c6d8b2d558c31d25194cf8edb741280b94de7d349388f294a027de436fee68c0aa70f3f2c799c4cb7e924f06e639fbe3eebb5d669e1823a8708923321c9fe8e840a1e7fdf4286ecef449d93cafece452e3b2d72a3d6a13ceefee2fda9e4717ab0fc50d46ddc3defc836bb07bf07cfe995a4fe577ea17d9d4dd4b8a5b32ad59ede5018d08c0ae0073ef8a98ec0272cbbbbbbbbbbbbbbbbbb7bcd83dce637577a09237c9274d2c922f0ad73587892d4d9113a52922484adb57a83acd650652fc0673d3afa27498c8f409001d52e0c0683c16032c06707c982828282828a82faa893ba19bc2530180c0683c2751c077c92745276f3b2cadc4626ab61013e61dfd72c8427508b4c269355d9f729c10cd1236e85d2b90ef8fc1c6dfaf5e123dfbb01ffbf1f8131c8e2a3c9e2a3c9e21381e7dd7b6f5773776bf77eedbbbd5df77d5d67afe71ee4074eb170eade24d76baddd3562b1c6d06864b58cc656cbe5728d2e56c8c402ededbcefe630f4ba1cdeaeb3d65acfb35ecf0966011fa3a29e32fb87a0f5ae4110fcbe4ac317dbbd583944b15e54e00b4eb1700ab3542f2ad60ab562b144db512d9460b1c6508991d5eaec055763abe5125b2e976b74b142292cd6eaa3b9c262a9302bc57a61a15822cefb58a1fd581fcd958f05b2a49cc0efa494527abdefbdd7bdcff36e78eff5ba7b6977ebf70286d7ddcfbea43e9c7a41891ffe5462088260189e700db5d6d05108501daffcba9ca23ef54a5bfad3e81c9d4aa46dfc8d64af09597ae32f03043eeb1195d6a935b7e9f75ae4359aa2259c05a4231ff0519bfe4c4526e468d34d27db86c9fd61a6db3aba5e4099ba4644485385524a69aabcbbbb9398228531b306a0014029f6c0d2b81d74a5f7fa1eac39cd08658834a8991799540c265530949e94524a59f7ba8f638be55ac1a8481c9392799941d18832c219a71af0bf9757761d5c1ad603074007c014298d51dc6241284393da0e5adbfd6304895a6bbb178d6da24870b7b91a706e240441cdf58665c1da1bad7353a35884e8843cf555d892ebfbd080a7be0e2d86c889c9f53b80854aaebfc28e76b34392ebe37430c8b5691e616767e76575c84a868d536bb459bbd623f7a979cb1b376ed03c41d28cb0d3376eec8ce0b9224b9d0abaa328368a1648e413774a18819fc0c2841c317682852874828588102c489a842c242e8a09aedf96d21820b87e7b8f5aeb947035c36a862f7fafb3da2a5317792d53a54c71dee77d5e531b79d052e7d19932a8448c9a6b62ea900c0000400093160000200c0a064442b1248ac24cd4cb0314800f67844c60502e1888239114066214444110033114030021c41066906186a1e30d0732ccf19e5efd94d735aa43cb1420619d8f5369148e01b6ff505091e566f02dd09d32fec6f5522eaf52900788b2be7ed1e0ee865f7e148e7a697838108b6ee04256641a9866ad4a4700396d221c47d3a5fdc24c153252e302c4436d07c47c6f4ff7837862aa29546ce16ce75e82180dc1a1b15e5c138267fa46a1b7a632aba98d5020c019bbcaff4cda1f9506dc947fc24f05f7138b1a09c5920a1aa60cb0aa5b9dfd31ff9076a1e6c0b335e1658ee0d0ab30b91729caf90d3a614ae38c761d2ee57bc6ee29a8a8df87b3496ff83372aaae79f560672d906377c6f062701567ee6dc4dd4a59a3eafd6a86aad4a57e4cb90a8135a92f56ff55525c5c815d31a41a48527b1e8262d3376a05cc40d5874642e673a2940d3d809add648d744ece35e82d010ed30efc8c064dda955a1778a946b9b96a7a50d2bc99be27af671c8c237b62a0cdaf61cc043acb2d0eb04f6519cefe4d638a871d8b8b3cba8526f1bdb96f0e5f50fe7574b19544cab939dc2e1b6c597caa2856541d22ca275695d2e923208e463bf067aca8d915da2c8a7b600baa958c7f0e007c4fea5405ddaaaa16035ecd2fc3c94deb1e37105e0a4d1339f1439775076915869ffb2b946d3449b7e13ca74cd7ec15da380e2d0a05c78f60b49028e915c939ffd0452d46a191e27d83821dd965b2f3d435527a0df417c999875599fad2d18db5e41cf49689d2fc1a238742e0f61ee019a2f9c85525d8e5b0d80a49df6fd1455f7b0129ea24f6ba728532d94b08585a65f38770fff4a3859fed9af8f96a410b9189a8d288f0d14989d0aecb836308317cb0eff877ff7522044125cdb68315542bdc4fdeb798e3f8ea22cc9d6cc123241e0cd86d97d84d753103c23dd1e5cb2c974b359757b949708257a8afd87269779e97993f801346061f9ed44c805d05945336b863f0c632bd40a690a10531fdbff6475db254c0fa65504f035dbb9a024b610f4660be2d94344d9319e0784108203a395bf7dcb04e0a796583d04ca5b65572fc147135c2790e571c8552d6eeb542f2dfed43466b2509f853a9cdebe4fb78aef66727726d6584c76c2704efac4e88b60b95bb7d8d243166a84dd329ea3cb7f2903ecef6cc2e229a765a0c8b77de8f5155d48e3df7f64d8a463fa0e90d9c77b5687b88cabdfba1b24fa3f0c75f4a1c4cb9f62fef5bc3b5e7a810bc1364355684f8985d0110e82f618ab0b2d89dbe83abb6efc0b96cc289bf59a6d7c3b9ac62d422b4f1b16e4dfd8328211439106661352e0b276df6dcd9cbdbf4dd94eeef5d6e705fc52a61c487f9c92b3f180cc0cabaa09cd01cc1596dbe8ed5721b59ec247379fbe701a2f1a8c9a4f5f419b53326463297e56d082374bca34c1e56ccdea452f35f3786561d367e80dc3c8561c44d31e65327539377a6804543c81150c47953b147888dab704b03f7c9bde582e39f2c9a3d02ef99d2a43519dd2f7852294010c9128c0945fbdc231cc5ef4cb0284777b1061262b749edbee10db4c8ee803f6cdfe907d13d9410bd0e30769e14cb08313f01ad1529b2b4b609170d8b125e9c6d57d414623971fbf8cc57255936311115779dd131c7caa55871bed9a8595e33c221b73739e22b47ca72843047b839f2319acaa65e692c0c76b88e916f44fa8d0aad20d79c8f8ba0d982ebd8c529559b5170b84ff1821c9a19f0e3d4e90744b20a7605a5e0e9500210a8fddcbd9f9614bc1519a099af1ef67c1a170ed11ba24d92437208c5797daeb34e3be21c9e882e75d29cc0e953c0a8ef4118dd10e26facbeece85d9492f148eb1a652159337199f6c796a4c31793f5b84a39ae86a9fa2f89295b59e57c31175c53ba97e661e081ca6c3493f594b3216d767d25ce69e9aa663bee1d954e039d7fb4228d2dc8d805457370b9a1f6aa16068e02c4c8cdc5cf71148938e5f5be122049228a31aa34b4b456ab03a663c6311049f3362b8a8f00c4cbda04a3314be523cfa5c168c8504776458611dde0b5250a7e1952acd5a85927d1e7b92c4c44928ef60e0f13223201658b3a183718e15108f33502f4189d99fbbfe59a8c1c3c05950577304b1df0b4e5eec769108326f9afb20906a54d61ca544c820cd56cae0fde8eb3ed01e7d87975adc17ec9784b640b5d22e8eed095795016ce27ef88038a57a2f50be1214fcc2daf43d9e7754a25edacdeae0592f21e41307390fdce79ea2f247bc74b59aceebc7b16c3e790b2304fd0c8e2d7ca2d5d4782fe5629bb605155ab3345c59c0b7474655e202048fbc385a86501e4093e73206625d366a43189716b6027c9d24edf18b74ec976ff750f4667bc693f44292fcb258c6907759d1dfaad08ca8fdb0308f8d12c90679805bad33eec2a4a7e4b4f4826ca2bf706d535ae08323ced4986a90ed2ee1357f8c6b512249f9bd85a6a85e8071640888411ee8f0df585635a432f41804b67582a4acc5f3f34abd0b0e7f0e0692c530916a3019bb6ea267bd619a49c54a699161a9695fd9b4e6aaa0206ba757e8b8cce10e3d292a2fe97d62215c7bf30959d1305c5f0a99707ae73c66e3f2e37645ca875ea765b027df3f958ed26b069d36b638af354043d5ecc9ba97b41f2403e8ebfb28a7cac4b7a055a05d4ecdf0622dab19958cde3af993887ab8172c876a04676c5580230658b291e441c7aaf6bc1dd4d5114f45d560ecb479fb9fb0b657c2c21f2a31dd4d852243080d7575c54616692120b46abda4949ab6e9da08007b40d088ebaaaf0d48090ba3ede289bf53ac8533491aea82317616890cc849f200418548d0e1783b4d49d04e228695724fdd928d6f3038294c4cacfb0a2aacc0f4f5c6b47059317fce55a307e79250fbeaa69a88d7306c57fe8ec39fb9b79c667aee6f319d3dad21ae74a57eaefef16d7924961ce6f11b8336796ec16a4955648b9f94dc7f1042f2ad29f2d703a4e31cdc4a8aed96dc8270285c4d01f56d31bc2c43bfbf59cd031dee46503570b4f7065e7ca8017084052322f78ad2293ccf28af1f848173b7aa93342b56c6597004173462b3771aca42df85d673aaad43f93e8fd3bdee718d6a9b468362835f7f427b52b14aa9a3bfdb12ae14c8f3fae9137b58c730281a062fa0cd629e9f409a499ad8dd07ccde095c76add2c80d6e932a804230ecbfc3bada2f55a5a7376f867c4683b1947b2f7dd9ca03788b97a336744dd18635286e027228d995c9273aa8621da52778bed3317afc9b8ca8cf67d3d9bbb450ff7ec59f844429a86e6444734461c336ca77f1ad59ca169c802f0824cd0a73427d8d49ea4155e47c0f515c15218528b4ee4189a16f8d57f836e14e9360ea9e32d211de40ae002094b9fcb6f38e529ea60375dfc0a3b729f950bc4e5f21bd4f10094fad8b97724259298b281aa3a5a39b3d8d5a15b3b70e0fdc7f5354de09e7c51d445a218ca3b4231f0a9913db58bed4bd60d8641af8f0b870c382ebf9d010106e097571c7772574da4b4569357050b2832404774d671f0c03095cbff29883198f9141a0107013f217e30047100557884cd1d5dc980e4853bdb0837452b0db66afb599707c4eeb0363fe052fa20b9ce895151e76ae2ac7e81eb774701e529e5e636c0cf3eb0262cd53eac3c6ef44aa84655809f75d9c6fca92554e2155b6fc65251f207f0cb0a317709292377d1b3584241643fb710c07fb1759089f95e53f1282d16223c36d832e66fd5f41268e2c95657d860cf6669a6e51130f62a229f81678052ced347c84d810ae172cca8d863e6c579687190a48c062c5239ac3341805b2f6c85521ef81303b49e94490b31068ab708c2833110c9285b50815b30f5172666f33211f4f550db84d7a2487082953655cb227ee1e79ed351b0b266340d35a718522e431536118fb703b60d61d994cac818ae6a214e3cedd2365c9844bd7680b48ef8b00d273297270272068006b119cecb38dbd6c3da49dc21c44416504383d7557c5332d8424b48719a1c2735fc96ef5a7a75c3c2640ddd50c25c8e0a9ffd90560591227f07a0eaca3c309111d16045641c9822428013dba25885851f9c0d06b6906f83dd2f6c2ebefe7d11dc7fcff8568130fbed9110deb623161e68930948689cfb166385c6594465806fdee3b10b50b3586602fdb58037e1a3ae011882bcc4057a62641c2dd241bcfef9edcc76a182b009bce3a2d2e2b7008e010422fe666d686372e36079f250c1d172ebe263bb8f23ba225b134155c5d738d753cae4042fa8142d0e93cd5bfdbdae5b4e7766eb32206988e7ec6b7283c1d1bda0695459f442c27817371adc48fefeb05600ae298476449283f36354c599d54133b722c3e1e26809d255453745899917ace9cd3ec0ba937850bcc0198301f310c831c7efad47c249a68a2b0bb937dc952f5e167240117eb67cb5660974d182499b4761a2e4c833844e4eba358c936920060e3f27c661731e1b39c0e133312ca3f358c80508df81f16608e7ff8393fd240b62cca9a7862fcbc1dc4671adb6a04c897387a8d485da6278d53610070c9b13c361398f8d38f0b099182dd7694c880584ef8871734e63454c28dc8c1887753276c4406398fd984abf7cf4050f8ed1d0c6e413effceeeb6335dcf4734f2f83588d5109870510a59cae733062baa215b413417cbffd9d97c63e8d1a5e2d6ece1b4e8d425ce34ae67e288934b6964b27a4097c82625dc3c3bad422ad7de5091c92507885ae904505186ae333acd1d40092b3f89991d9c9f0cc552397fb140bfa999a2531514f233aefc0e35248c3c1182b8f1347d0c740848c6fdf88fb7235766f4195bff9532da7805c5e6cf27d84732f1c244dac366d195601b56a5ea83a8d168511fd0d15ae880d4c976f73ec888b7298be769296580525c408c8de7a685754eb545385f2d606149a8e619396622dfa9c9b6c403dc3165a76b9aba1a1d5093f534b7fcf89710bc29508d0c4ce4bd403f65aabc562c97e5f0c7d7b50542e1d901f5aa4698252940da5f3d580439d0172fad4d9fbd5d0156e09d035d4d72485c11abb4ba43149d96d7a7e69edfc61df3b0150c472224de08a7091eb03d2a7304b902249de7ce8905576a5476c7968b1f42c0f4d9454c5cb1b31a5b084d133600aa604dee6ed6c927495bceb225e9d3d702446a1b827292c642a5905acde6610d0c0b26d3a46264f4189cd13e90796f35becbbd7aed9ab4108415adc4d4fcbba702f1201c83ba466eab6e5495875cc690bea6113a0b49f0aa39087b9f203888d18f3bd733960d8b5741f130b616ead353e4eba4a3a32bd36820ec494b4cea50c83a72a5dd563f88d6d3952b99e924e9586be895327c00da2bf975045a3b31006e23973a8ca26dae8501ba0910d1049aa479fb45dd69789809429ecc8d2f166ae2f9b392a639ed03e8879cd42c04d22fb365fe8063a13b861222626f5ab11fffe17f504ca07d7be445e82e1937b626642d6a1ea896d6d562559522f7a4e351a40b0a20c42ee1d8258367440d4bfc998de733f841c9d27ed4aed57e2b1d337bdf8ffbd607cfd2c50be8e4bbf138cf7af04cf3f71eb67c1f2fa9190d24afc7f24705e806bbf0b96ef4c88bfdfeb30228b0f8663d1cb6529bee09e7d7a76901e76397cd1cf716739d4ea2c493636586339a742bd74e50b971d35d06549d708e90aaab9d6d8980bd5b14fd8b9a0837293f530e34fa5c1d4548d6df831b4549778ea110dd962164e478a83bb86aafb250d1e8743416d1af2390e8078d3d52e253f2d9af3380e817ed1e0776c085ae34a940e8178d3e4756008d44ea3767ddc0f80da35eb711006dbb94cc67d8ec220dc742517897808f485ae743703f87c31055be4213f406a7eae7889297919254a2a2a02d24a790a8179b563a9a29fcfcf8626a77cbf54700f12096d88aac59bfb668f23930e6b51759cd9879de3d944836fc5273652b17da7ade7e0f14cb62cf7a7ee5856805cb954b3e8af42d5d88d5c057b73968612ab502564a5ee70ac7f47623904c38d94dcc02a3c0255a56dda4ca961f9f7a88f3f47795f51bda539efb1fd0ad7143030a2bbe813aa8fe91d4dcc8560bb69c9c91c41bf763b51f3e63781951ffc6fa95f340be2ae54abb1c2a3353515ee76127e8d1effeb09c4d3fc2425f0b6c7165f5aeaa91761ecd09bac0adb9b84bf509ce282c16fb4021fb7360ef8cfbbf6e43d5a7f6c1d6ca11ce55a9025fab4b99f0c8f86494fbd4fabba5b1b8811c5390a9da4398fe0bd6be11a9d3e695fa103eb8f3976de233a8492c5b24dcbf7c445f0d16212973f642f99286327928cc6e9ce3c1744ff3fb740dc11beb09c8c909d468138ec7ab6c6a2f0c211cf47be033db3d84997681662a92086f92b45aae2a9fd16c5fd3dc44120f689909a0d78415fa19147ed8f7393de0f718cdc3d484b69dac79a40cd037d3e7a3972ddad4af947816c87ea1d1a3448c33b3651093d6384548e041304f47dab9a0f510d7fa1028f0987032d105aa5985d2506041bdd15933d900a35c98c25a75db773c8173daf056399b21c643d8af16f3363083594e095292bdd85df50bd90c9884c38cefad88b0d37fdab3af41086208941bcd11e7b3cf35e13e57474951a01403cdb2a861dda2fc8741bf30805e0ee39cb5d546ead8ac0920e34877c5f9cd692ee9da657f9be46cf21555574ccad8b06def703c099aed110bf6ce79f43118c7dd1e8c07e3730af90f414eb127eaae134f66a99443bcba9871be4c3bd99a1de0059547176edd32094903734723a331bf3acac40f0892533178ec797405890633c8ee5392ba7771f66dc8cea9099a9aff8e3d7659e534a82bddca50e815e38a51cb117540270c8ddfc28f4aa581a9a7eb46f7857cc79ee5213e8cdd5d2e7cde69712ded387b0d2d5804e04d0d0b94798ee93bffee2a1e25850cb98545e3cf20636242c528b713cc387819e546cd1946d9bf5682aaa4fea0984200465672ef2b29c0b98c41590ce009b652ad0190458302e567b10f075f70132374970053e03d62811d308b02d6245469286542cb8ce931e06afaf5c742407e283518380cf3eb0134464c9c2a1671e148d59a58ae5b8f0f9938de83612ce4b2a722dee68c74049904c8845cf3cf2404acb755e395aac113aab60c3ebaa6f8d0b2c62404e69000a2ba5fae9bfc742c60909dc1116e818417a025e3798bed99bbc59fdd86919c1b06b40cded300b7d4bbc2f2025e601507d2b4b3116e7a5ae77fec896fe10022f1daf676b7a2c46454d85881709c689e888729fcfb999193e72aeea48aa35cb11c3c0c3d9d015421c3e39cf2ccfb926b48d9c9cd3219493cddc5f28e71e7de03c33dbb009f4d898251210382310a685727ebc2530aba7cb513b0c33c81cef7e5fc6627d967969d5182855904f03164cf7aa5bbc7e360362ded0955b0d7e148e033eb4d0bb006b92500155d83bfa0cecf4a0ad30197e35c8efb67702b7f4857561a99410beee4bb6fa492c9835b6e29dbaf89d3d1a47549c63ef1b3ec107fe86b760958da76a5385d52b4e8ce66170eec078086e6621fb001390d3588a3abd516cc5294c57fb6c0f4d1e28b5e87f86002ac6e8977a05247119fa7fabbb32c330efc67c5371ced6e0cbe5b39f99d09b0cdc9e4c1a727d984fe4b08badf9c947b4230cd36ce85e8122b85df72458bbf170d1dcbf084e4ecf05ad1208f52fbc606400f81a345450fb138372ba75b08082abd7d69f744f1e5ed23b21be17374c11ae418d66a21428217cf5921911665b03cf991d34fae4852c1506f2c18257a7c502e9f6a80843ffb5f52b6d2819fda7e61e21faeb8158a59139e18a7876a43bb8fda1ebe8304da4c65c9daafbe80c1bbec6b0c3d0b665fcdcfefe1a1c1804e69e2be49ab55999d5ac6b3328a9042399d7a3999a989489b030327a17f46160c2b096811f3453a75f07591f43d2cea6e611737a36c2df8a955fc8d97ec6b2bbc2a35c21ce269a9e4b5ce032cf3d4eb33da347830a959c24c5cb1549dcbe4f7e184f1a2b41f033b139abe4021650cacf21309d5a1a9f0dc265f273360e048887350e3f854ad39dd27765cbc13cd2aa1f3b8fd35b8741b9a969bdd6f690d37512b96eaed672139b6958346c3f308216d9f625ce1a09d52235bc555ef480327e05b54035909b84c61ba43309853bf0db4256f076736b7462285725f05040d9df9c1e92d0298b6c2ad70dcd551d83db054380f5ce87929a828a078e3d5900e03e203e61f61fee6ad9f7f885df590c5f4e62beeea59bdbeffd3dfcc52e3145309202609a2a84c08317107eb235e9c7b1947b4fea6e3f623f748343dd299269f00697e22094bb1c2ce8438eaeaf6deed05a590c80d657d2ace8667a3d7545ec1b19b7b8ba5519393f105613788a0066e6e5146cc5d4362bd4cd4951825028580b8040b6191de4004958ca4793fd789f3983f2ebb02b5e072e2334571529b570793e600cc302a127c957a545240003afb60ebba13a19185b8360c330cee6529cc4dabacd3871f51d2f5a1cfa1623036fba3a1235331811196d9643bde1d3da213329651d03e5bad13da45723b9689de8b071a5633153a930e359e8743480ff797fcdb85845a58725810aa84187eb8556fdbddc66bf29b8410857e3155f365394bc1a545422b0431c0584bf24ec5b0a1512022bd4c1c1151c1bde49ca6dd599c90ec7e18ddbeb9d73725beb7892b79bdf8ccc378103b76333edd32bf18aee32b14a73a1a104ecd7b43f6a01bc462f5c88f6ee90a7fd05b400c1e02c826e7c0f62817be7b9ea36b3421bde79fd3f70cfc0503d7eac8b84fc3a40ac27d0835571c4070c87be85570dff546b063993e6e8e45435aa6f20ef2553e339109174ef0ad960e9b3fc71441b632b4d8b249ec0c5cf4fd343e2e71aafbc7e21882e75327d490e251433b69f25c2be0cb157b9a2365b320e73597ccbc8469c18c60959d6cce726999b3d15e42de8abe2334f95c627b04a6646522f708054ce071095be276bda735dfdb8919301e975482c3954ff1ba9225029f508f51dd42945529a9e503a1dad22790521f3b568ad2e0c47b2309a5ca8bf7412feb88f3c4d15a044ebb56e5a07aa961988d0b0c37fe59d12a4acf1ff367a871116ccdf6848d8fe14c6071300fb6e32696d623d6a227fbd246b5a293b80395197b7a72a75ac9c7df93312eef1cd2c16a3e029d5777282e0150c764e04442b384252ab72b6e3534073a881c4a84a7e5604e9d9e5d8f8b71836c1840125af1a4bf8972bebca37309c1f42e4b6d452b98e855959426331ea4181fe3ad9b4b33d4683445994838d19fc1a17685a9b5d253f53757ff922655bc9fc4d22bcacaaee321c9faea97bf31c80dcf9532540cfad03868898d8e44d6dcfe3af44bb0c26dde07cb6e3b8ec10745ad7f1afef0a4ead79a6e10d4d6ee2650901d298c0ed789f225f3d4015176cccec8e1cf9bd75a181cb1602931eec4c1cee3078a05fea4d29db4495b58f470826466e009c3e58c5911c1635c29205e8f8a3dc2cebdbe9ec0269ff7eb64da0de13be05cb4ba1cf2cb25fecd071e298c7b7abc175156981cd4130435f02e164ec5c19d9a9dbea984e1acae3ab1e5e9eda27248c36a03763d17f6859a463c7bf9dbd6957e7c61e76d48875b20b420aa2c2198536d22fa2bb8a2f9c58b5a2f523747770d11997299ea12eccd4cfe589c04fa92f10292a464890fc42f72782ba51f86f2d73680bc76d8cd3f1ecbdef3bb622a2595c07eb499bafdb46161346236e42998d2478aa374c8c8339c97229ea103a91b804ae1e39d19419599437f759e816fa271478342855df743426736267efd2cceca89cd1f39920c3e711f383462cece909518c7a0ab1586fa8724e752a4fb587a63336a91cbcaece1001defb3a6a979f08cc7110591b88e0f298a38c812536c44ad6e2f12d2c538bc113e852bdad90794a4eb4603ae6a482578c689af741daa376cd57e9406652060efbd121731d5bde0ee9586a715691fabcb709a90283ae5bf7916d1a86b3df7f6c0850751f4709702fc9d777550eba164e8dca979e0f2d508aaa556e95b2052ef72af521ab05b1625152332dd16c584d108da5cb90da2c0bd921ba33992ee4ac1e5e128f98e62cd908966d6f10fa780087c8e8f032a32bb305f4ed3bc4069c3367e41ba5f90de32bf1b3792c29a27d9d56da216a72da61a2cb16c0bdd03c11935a58176a4f562e9a08cf4a49232e8186286f88feefed0ec866f506080bea6235eac893b8ea322b73f8b2fa811d297824d3e2498228d284203633b47a632bf9e39140ac23c2e3763b82f8c4420b6c02d395506b3944abdb4d250516f4f80068934091604c5852f53b320bd57cba7eb8cfe384a859a5c37d5b7920dc3d4261cb1ee9b0f6ace810961477945dfa6302e94685ef8ea89bcd06a29e3e1f62b16460a8274abf0eec02dd3b293fdeeb6d1fc792003d00914dfdc9fdab7897ac4ec33e81ff7edc96d5552ff4e99afdb310e5abd9ecf5c747cbce8f1e0208196d6f4542c7b65ee9c062b61447107e29f74c094008599032324ca59986cc97ca339e9c75a784ec202c4fcb2f3591cc549e45f9825b4c26d7763a2028c3f367c7ff485a7a21fee0757abf7c245376e40f4eb991600b1b71e610fe81174e68a241d8bb9047377504b38d7a6e6c1b91dee68cfd2e12081ec1eed8689da461ad408ca2f0167d48148450837521cd234deb66e8cac9a6e3f30f4765cd431da664ba6861a0adf1a3057c031fe6c7951c385c7f804afb938d6828c9fe6e7b9167a4d25c5ad7b1bd6281f36307a2f75eff22384b829a6fe22b77c5b98e7988a244c0ca124491baf92b4b56de0cd1b3bfe1bc20db2b360b1fb059b66bc22b0adfa8331f0f8c871fbc430f6b405252287b969e14268464710a8dff8eb3133e2d5b81f731a6973f4efd4c336df47e7624345fafb32c1630590fe6bbdf3fb8e753196afb873d81c19bcfe2dcdc644986dd01942ae9bc4cd3f87b7ed7d3b3310dee7a01ba294a1f6080a41bba464bdcbbd214ef2e34e3bd03d258972b04f02549714608511156c3bf2b26394e28d00fdd8a526b32da26809507f3ae43013fcc93cc34fb676b29a0146c06d090ff191d549126788a3e204ac4327701fc2fb081c8ba0cd84e616b762e0864af921d859a5d947b724f05e2fbdc24aa1e2f4351beb2af7580b30b54cbe4d2c7a882e96bf1acf2a78dda3dc1fc4800d64a3feff4c7afcb8c9e653d4e4efe1a2b8691a4da205a83848b18bbadb9303247edcbfa1dcf5ba88b1db62d31ecbf3be4738a50cd23bccb3fecfff5dc9a3124216f8733c56c25580391be532445427e44fbb9badc41319c97084970526b8023d32022714b87bf069790e70a671ca20427cfc54796fe02cc8247ca5b82aa6e2dda5d82b2725f0c9366c4a0387b0c4694de5143b80f185d0374f1bd34bac5aafe337921986e57b8ad386f2cc61a797965ae98e0f8e1bea04dae9a86a42ee09d3aa372c7cd7fcd497bfc5a980f11a9a9d5024ec91c3e34a181d06c54a0c40a41c822c8993ecfd55b9acfd58c50da9c0b12210600ce1b24a08d8eeb0af4ec095f12bda475039633d80714258e95ce1e4a9f226fce173f480863ed953b7b1b4ee6a1ae549af567102a0bddbfa589a9b7307666a73c9eda14c00b08e5cc4fcd8ec3b8d404f5f4a44ae95ad2d890a224880ba7c8e861549fdf932de5860c274659f2ef5d22591f207a865687d8144f77a0f6b61e6735eb25e99a793c3f2e5673c928c1ee4d5988dd245066934bf5656dda78689211dec61a42971c0ceda8776c8604bd5e5b64e21f841c6212ac814e92968a5bdf4b0dad08cd8221c08afb6745c76786953ae8a7fca37476cb6977658e6d429d7bd0f0ddfbb39ac40f56019727135aeb0b9ec4549ff51b9a5a5a09cfdc3635322010f9b3e06bbbc68e281762839e54cb45282a3d1427dde7a8d8b445300a228aa40c5f369dd39efb5193c6cb7c06c44754e37e5de27d08944dfbbaba0192b1d6006fa512afdea77e62f07b4d6dd6379a7de5f5413b7238168081ddd41a804928dd7b2ff7a5dda643b3c160653869c021651b41ea71643d8635668b5989a7ab31fc91e4acdf3453d6cccf7cec601b774a87218112f0adf647af90b25bc4c07ea71bc65218d8d3db18e5ad22eeec4467a78140d0bf40351dcdbe8985d9579556b39683bfc80a91988a016d1911138767fd2b98ee0c58cdf5c876959a76381cc374bd954a267d112307f31cf6a67a298ef7a28ac0f2453f4af3ee193af368d1fb0b4a02a6bfb7f9e59a82872acb89db933d44caa7400ea90eee2d7e2babcb62632ef5f55ac3dd81014f70908df4a2cfb247b43d4b4bd749c591985a47697bc2d9c83976aaca9ed64e2c0ad8a0cb801000d68a57401ff12028b04da43a4fd970c4d529e8dd333fa0d89ea775e80477bf0cac0c1f006a161cf5f1abcc3d17f855aad8b9c798f1a33ec60c077f63417d71a9b492aa0749fabdc8b25853169d82486ef5b3977774492b02e2b012192c648010d3676a443f4e20836f9910e2ca05c51f6bba926a1982890a5db653f6030cacc6418ca34042cd38e2f780623eebf80c28821a65b7ebfb9f9d0fb409cfee8df6018395e123c7dd9743c5b9312dd04a052c32529c5bb9678c667db748b37a574ce0bd7340f1477ffefa41c1817c51fe872841372fa425c4455f14e3185d3a0696856239c6fc611459bcfd6ded0f6be78d7f26931cc6b511b922a262488b4987072d2a1f2a34a80c9132a172444bc447032d15d3717c8da9bbc1c9e99384699f1b18481a4337a92439c147a83a1c848966918141a4b8fc9d96754e460c590fa8155ed81714e831dca90b4ab8478bf30b02151105810a818a408d4041a244a020a12051905110033dd5c290f34604d3104fc32d7782e483dc3a0f6558d9934fc0a045f252ae7499974337580a9f86a3b48a8fcd92b34f115311d9b027b658f5a6bc54a6210d190d869c9dd8589d93d0dd8fe73b0c59676c88890362e893426288d8826566a0771a00072c1ee65672f011d1e991e35968e6358afad8969d2b4ab0ae7ba0d8de2b5031fe25ac18e735503032cf7b8445ab3d5dfc582e81f4edebd8bdf31a9359e101ba665975ecc722d5583f9e62b8d52739368ac8908fd1f73d4bf1f51f26732cf94ed00945a0927029190fa9f380eb882a4b2c9ad4b8c348521725473751ec3c4ce8c39e255975bd25053b4a8967804cfc91bbef64e0453e5e979d0772f1c2c898ca3d0fc9b9c188ccf82e3f1fc882c1fbad7ab1b2f4b211984346906f4f9e239c097c16423cf3ef000967482fb8ef870d076e2e0ec8807b849df467cb101e41383e77505bc1abbdceb73da500a7cf28e01bf91067002ca8aebf1490e2ab5182d8bea10b2d9bf48ce18244d3caf0feadc0eadc40755ba8abd1472f75cae4acb445899bb950eb96c413dbefb148f8469e5472a7f2f7ed35dc4fd251430c72c9d3574afff6f188bb296d77fb338f16bff941f0ad7313bc7051f740eca9b2aad20aa97e2723d99499f08250d510e4874a2a708795d44d07860a20b786d92d70e6fe7ec0f7ca708f2d35e305a61602c4630bff043939c5373d641569c48f0a7feff4480164e66a5777e34cc0981310baf5ba89d4ad8cfbfddf63e2616f6f6920b2d7cf55c66b088df03a37f9adc987c5eeb1345d11bc5226439d0b7c2e910fb1a2b6a36961209205e1af8d2dc70f4053866d2a3fd02bf9a550df84db536d5e09877646a565e2e38cab753190fef454c738cc72073d204469feb4e8a31f74039232f5539a64b39630657f6495eb191742c51dff17933a2218059da4d09cdee7267c292278d0a986638f4cb91abc770a663e6eadde6fd6d95452e45fc03343c71c7456fc6fd1e3646e6be85dffc8c98c5763ceb8b72d98ccbcda3c989bf000837a6dd8d7984e6ad0a788204c2b2dc22d61afb6e465267840413d223222eabf48879c99716fecc610b31202931350319caf3674d0edbf57d2f771f584d021529e29a22064ddd6112574b1c8554cbb82f74cc33c3aebe2317e8b585e3226a8a597d23f469e0494de5886ee438b2a563f71f3d2cc6256d1d0e3eb7389370adc5f24fd72f851636cba1dca96ad0970e37c0748e2b9e22c2cbc3b086a5c8d45af9a04581da1f6d287e725eacb0592d28d86469d8634f3a8a1171f5ed8ef6ae5c334adc9de073af0f25d082dd19d22d38ce37a965dfa4d23080be7a66b848d50373b3f72d4febc7811df817500fd4a4e7d1c341dd55a70b3932b6f5be643091fa1369fcd0134185e1f26cb8dc07eadb7e25c48bfc50602eceae76da5f4fe3ed66eebe60b1ba654c7a34c2d7605f5f6491d4f672bcae2f71c450da5fb3814691883f1734290b3b6490b3b39146c61842cfd4c2cf8552cae6414f7dd7eb6685aae594b8b77bb156faad8b3224a4d862f38671dc44536526a3298162d14f0bd44b67b6ffb0162b0b2e7b9309a0d958971e059a32e1857c948264da50ab27007c0a74219c0b072b0f73e6d1c645ca8428937468a8f6d63a80bac71744ecd5cda6b7a7345588207eccc66372f81c81b921f8983ac097926c28883ef4ef6225c5a247ddce8a3741bf50e376160923cbcb3a139563b763364452a27abbbfdb82bfda63851b6648178a778ebb0b019cafd0b71fe313ec095b792e20e75fa721c6f438b7a0ff6211a78a915c43d068d9401099ea97b5d79eb16ced7fd74c046ae11ed99c62ea2f71ba1fd3cbfbccc1b46f6649a0042c0bae093ec59c8ad7834216c85e1657b841fd846700d1792b0b15671c95de663d9445662da856bb0b96c5e3a19c508dc930f05fc0497a7453f0f7c7ea1ebc811348c6954c1692b628cbf053482a2d737108c059c428fd021a6137c17b0769c74a5deb7b11798720c5e9bb41332f3bae34ed4d5572cc2e21dbde0bd6968a42cbd1e52539cd223bf6761ee6d744bed4fbb43420f6aa71735c25dba49944f3504bb9d32ea8018c3589d45530630f0e8e3026d720c207808762d473368defec8b10bb09944c5bf08937ec6f04288569b13bb13921ef065a6251f4e41c908bb948821632360a8671f725ff52d06c2d6fed803d67ea43fd04c3420c1ee0902021b12ec71fc28b22f20683eb9e1f122f7289bbb6d5427783a9e75b9876d529df6e63a5107accd39bb35dae09b0dfc3085b1b27341354123cdd775ac5e01bddeb708d233d20c553472b80518bf26a8914619f6e0a53ebd78b09c67e73fba4bc45c4fd823afac905756b4500f52782c5c96b644657896eb2ed25d7d0402a4a85c33fe8a9d68b64760e69365fe037a23c957043794834ec0105b07910547e1ab93419070a5c099ff559b96595f1221d2e07cd5c678aacea44aa4a8d4eac685e11b1cde347510478c9caf611056ef13b793f560e88b3a19d69333f5680497bf819e9707757baf6d0c435d811cb1db02076bd73254b305ac12cfa12f00adef5942fe61c3c992e6a93cad6ef19aedc1f33680e905a885b64a0b8aef1f0c6e4bbd5aee045e93e87696c26b7d7e372b2ad25fc50641320a370fc29be8e6651e606c087eea43b16444f911a46ca35945a74ef1b2d4dde0023f0f27252181fd5d6c15428b622b3cac36e28bc4ecf8930f3c090464ebe7ae4b49f638526a2c418b4a0e3613594c1b35adefbcc69a84875ae78cec3cd24d570d52d27e480e15b6dbc6fe65a2a8f6f927874d7de68d8e5c82502799baaf0be44d76398531629f22812d21ad7ef4ea516be67a64e4c85f80891345bca90b8e4642250f4dd1aa164722508f490a703c8b940296bbcd30d57b243c0570a2fe0fb471a4e55e4cabab05bedbea511e41000826c9d8615352119863f5e1da9f0259465e7b14ca1740ebafc525a2c6409336068f035beb518eb27597d99afc3943f115407a18954f7d6ba8ee19091540610c150c01c822cb3ac1822623b218eb808a7e84daab34ac3129267364c7b693c502efe641cb2ba5bc9f80b4a8081a98609300f9dc54ac1c0fa223f75380a9d0bd9ec5ad274b547ad0da5b811a6e285a000976060022e4486e60a8053f1b4aae42db86627f3ce417feb84e03c27f999c68ca3c3aebd353252702cb91ee5a1fad1a98977c27f72aaa4ab2a7ddf5adf8ac89bc1ffa560c0f01862da25cc6fc06b22df90845daf0ae44ae105db09253738b23ca754397177d2ce455105dccbdf89edf71324909c6cad5b586e30a6cdc17f8e6531673229c2e03a0723a9613cb69f52eb5d325b0593d5dd0505fca096b6b8c2fa9b83973d9c1c77222808544d77c53d4b2abde369d64bc3819109cb961428cc3e09d1055b9eeedc92decc368e126ec62361882a71eddbc86acee4355d31d5c4987902dcb87d244464956ea51cb1a538e7753655ae87b909c4766d39e8565dee3e5387699d05129396a5c0b462f5184adc269971175a0733d0013caedee818997927285d600361c707fa4cb89164179e7aaba8800009abf0c502439a8ae174ab8fccb82685c74aba99d1271ac5f31c9e76f59a0e30b26258826fb9f9ace80437fc162b9810c8b4c7c30bb34745f919af70cb13941d4e28210a6659eeb6db61c4742e11bee5f1d17e32e11ac59452b4139a2b6c753782de95765ac1475ea6e809e123a9c138dd50402b7e460488496a2942b49f690a431f9ccca6e39860d3a59c6d20f93215c9410998ba1da4bb47173f007075de7a3032c747c8d9109fa70b0f2e2fad715df4413a83c5ff92cf4cbde32493769ddf60d3facdcc9964073b900793c1014f74291795ac6f52d926d088a47efe9b4a460782ce594ec5c4cb0fb3b68c4ff934e3500214dac15f0f344f215391d57f2f899ef7f6c3a80b30f0484a001993bda4848490523523bbd83ff6706e7e778393076c07cacba022bf601eb10ba59dae3f518f7315541c63ec98853e51f388919c7dbe8ba3564b9a59a3ac4cf43fbd92a7c1c07e3148d2c952e7b895651713c9bb07dfe74b74ef46409c05189627284ff6b4e52ea46f70ae9997e8a3ba3d94a28b2b6ad81225833b01df56dbc7854d134cef72c44516cfc5651e7345687db20b0004bf9eb390b4860fa6553945ec98d49a8caa7c297ad4410e0dc82755a1fa8a5dc5a3709ccae471bfa6ef5584aebdc423c37a169bbaf0cf6689017c24415049282a76ad8528d5754d36ba2d4d0293a0ea69b3bf20da5ce45fd7f55ce3d91219f3c330d5347b161353381771d5bfe9aa2b8ffdb33589a0d06b491425df704afb85fe51a10718ea415b2a0acfe15787dcdd63ec6a06368100d2753b9cf950fd84379269778e2d9317e2b70aeb97a7885a095622c16e3c2a45b6ce26180ef130be24fa260afc8d5ae9a229d429e5e16fac620969750a4881f27d31cd642fefd4bdee7ac84f389488b3ab1c0ee4956a715431a5e91e7d30e99569f52f2045de5105f17a2a64e61589a15aa81de08d3f572d66519c5ac8cf20c21db72905ac623ea14617acd7b3c075bc223dcfe5866665ea7c54190541f82bad938ffb1aa9d4afb5e1660560ea328615227edaac20c0609bd6132aa72d0624e967452aed6a3775fca4715fc8a8c79519a5e097070dac3b9f2faa51cf7a1e895fcc3415f64460c2554b8fe8cfe75868299208cae24d1ff5d4f7f4300a7b3aeb83ef93357164da3a3c8c5f5c35874c333fd19cf2c764b32f659d600464935c2992bdf1b97e2bb76b36472d67a83f3c7fdce1c67c8c6f17a44616135be7f9c4bfda21fc4a39f17fc4ccf2b4821202d8222c922789fab920dfc47bcce262c1956d6c5e5605b3b8f9128cad8346c183f5bfcfcc3607a52886407651b002a46ecd328203215a211a1c4807921ac9f2f84328a8f8c67eb91ef952f229654abf887f5d4c92a7d41c8f7d41b031bd5a95f5033b9a77e95c3d4ef4768f88cae44d26a9a1797c07b2dace08b3b54f5cbf359cc4f91fe8f9cdf917a55896e4b88750314f30069495a4be4aa9f86873439098e1a7562a53dceb64b76096b5940dd4c3024e83ae287310ee68423ea868481b62b8f6c883b51a255cfe4174717011127de161f1acf64968911957932a8c94e0599c067b965b1ab6acdfb66ec4ee52459cd238294ac1a2ccebfa43f10b78c159d9ee5eb404d74ca18696295808e54399fd9c13a7b247bdc2bce7845603a56164bcb0257fea759332487ada1b45441f4540695720aa820bc92c18fca5c68bc6218658825479e3f3fdad250e16e5bec2896ab183525c419095cc041ad0943088b4293ce39f4b250c0f3524072a2cc5258445e36dfd834f13c7ffd2adc5241a53e0e2de5b4066063d28012cdfb64da99a99a8bc39ad4476a8b114a976c504388368343deb662f14ed39ef62afa86cf851cd3afc1132d8ab5f9f892dd324fb6583254f59ac033c8d206911aaef902615b21e42ca26ea3f371fd517a0cf53a980471535cc81ef6410e4c8ea2833fe37736cdb9201c2327311615a34b7edee00ceddb18563772ef334ded6e9b87210a8c4c16618a0859b3f429311ced221a2e3de5e2a807c2ebad0caa58e0d3e807a45ee3622ecf026f0f69ad4b90d31ed87b519c180b56bbb52e89daf08fb6f0d3a92eb5a0159fe4bcce5250c158120ad1c98831fa6d94565c29f294a2ba92ea1b0b541131868d88391f56ad35c9cf1e70db2a351fbb6d6db8e03312edb003c631e3027b7f30ef8d984006b0ef1cb1b0cd874bfd2f9bfe2c790e7bb93f9a11e1598b53fadddfb2dc0f722e2bb356541d111bf1ba19d86c9f84b7cc5e5195701b63d379a998c52d017351b6fbbeab9670b664edd7e1b58cb66d135d42e8d0fb93ee1157a894b7971a25f37c5063af3009ebdf3d5ae41506373d2dd634586f517f2e1b15094549813f6a7f5be14c674b56968c01079e8dacd23163b702805b9772b1d75b74d059f2c5b36e4c18440bd45876e9f470f5fb919a20b6241470af3cb8cd127b3dfc166193ca5067af9e5029eaa93279b91ab2d9152c74acb073c9d524224acce1c380fa1c698ec587dd627e54b3290705b726e997a546a081c1c03c263655682cfbd90121450854c12b1126148fbc01df570404aa2d496a48b63b71798deedb3f540757929e5b22d4596941fcc41fa33384f8d1c974f51c3899bd4b3667d5865d3b27713a2b4320c3a8135e40792b1f6b1f21f8af418f02395b8305548ec38f75d0edac668869e317d5d8c251e5d0569bfe76561cec0bad8662d5ea5640552ac5c443a0634e70aaab8f7ef2f82908de60ddc649d6be678d4e3467a8fa804f81f4ca07fc98eefb20245547ac0375d820b5fa8427b3bac96453d0c3ddc8299d284ff19b89bdb718b39b8fdd2bd886d827f668a1c98d637a197c23622b44c549186bee6bb3316c327290faed9a20ea827750b0b4c1d0cdd6102d3f683ea47b83ccbbe61d5991ea77b142515078a98e92a4ac41c652b90635095dc8ab12e89fe6077ebc2754e4a64a615ce8ca11b8cddf1609625f390e33cc88945a72c879022322bc9faa092b74bac269d513d2f6d5ea64e03b39b7493e9ae9d0de74473fbdbe2b8ba6013e5d34659849d5e764efe1268ba6e3bf5a9af9dc493923761badd790e4a00ddcf24dae880be0a21a3d4dc2840c033106624c44984931906044c294803901033286240c8830106322c74480991403016624cc04981330231eb8901c0429eb63ce8ebe69a26cf7957686d21c1e51d1272d7367e09f8b8a330660757a0b4b97591dcd0735ff6c7e994c16105733d9f4a4e05a197ca4e7332bf40ec5e158b0811bd2375a8338fac5b1e5f2ace857d2c83ebbf0cb97a0f6466205f6f5b83e84820beb0471d6e9af96a25b1c664c151db9be87442bea9611f4ea69e9b06609fb20681e6b03ee82261e0a523cac0780bb9d183b174773553bc8a475f6b173c01d27aafc63d8c2da45670f139ad21db4f7422e2409d7d9037676c836021a720be39e79c4c1dff8ae28f728b339523708a023134d79097c5f310550a4d02dc385f8c1aa900258866a68108d8e72eb138a84a42a5b89745785965d995093bc47c9283ef8cac45c893c4dbad4194c38f89b72bda4e708ff05554ec65eec28131b1b63ed5ba5568275c632064aa8384a97d748bb6a0565e2e001343c7e094736c526940976e951466877bcebaa860d1384b4a27c209ff55853ec093bec59930034397ba5116db505530e1dff61a448e3f13cb7f4c306f73bbb2c65f8907b47715c618e5a827076a47ea31132416c4994724bb9e116f2891f63730171b8eceb153045644217770e83fe1e35488550a65e3195892197ba640a73e3b3d00acfe1ef302d0974054955491d9752b823d165d2154b3bbdf547828d4cf05b906649a42c0bb6b12a2646ae20ee42bb264a2a0e4c49600579ba3f6003bbd36a615cd56a36039cfaacbca93883d66dc5f0d841ae80f6350a65a3af42dea86838182aecdd556cca7c420d8b7e5f577145a0ba606a2be775e52544613a4c2b4e8cf68897ee5039f634a14717c9e4062f3de5c14ba9c4df5c4718d8f86b4ef03ecfdb238e98a6678b630555d284a1498bc992bdf7e717bbf4953e2a28b0b04be82b60c98d879ff5652667cf085e24217240139e7b2276987ffabc5a48790d6a036841e1ea2e03f527cc0621992b4217465aeb8845432fc62fd309b3a2a32fdec40f67cef2c88056656e2519fe23b2d0d0f08c9f20729a6f767b151c12af288d9b747ce6150b9d891044f81e853628bd47c9eb148f0aad1f5247288300a435422ffe4b235587cb83b5bfee089b29f36f66af876fa9ac733e1a3ce6e952ee4afd13726006b039f4c2298dad71b15462e518c075fbcf807e0fab3380c3cedcdab9401af3d23b513d67e683e618ed9d1b86f2c9119aa550c5f1a31be3642e4a374eb102dc82024ab3dd5a5d308ec24a07fe821dc6704a3436039ab0a10f1f8b250e6767bb93370844061dda0c9ea2a0e0dedce728d8d04a4a76b35fd681e010a0a3328c35100d073d739d239c8fc56275e22f8a69086f6291c042235364f9c9de726fb9b79432c9145f05a605c705f96670f3ddf1f2d5a0cb17001b88b100d07c0530f385c1f375b096c506105bd110fb8c8d03bd6ece15c91eac65b10df2a500d0a0d5046f3bf97219e46bc37c3b58bede8b7c3f17f982af7c57ae7c6974f2c52df25dc122df9a56be3639f9b270f2bdf92aec26d78e811ab0d9fec561e59b63936fab265f162bf26d81f3d5a1c9d7b5caf705e6ebe2c5cdf7bd7c615dbea1cd37032edf9d2d5f0d68be0098f96ee0f98a9f076b5985c97c03204e971d8bad6a489b67fb0f64fa2dd26d871482725725e51e351f6d4506f544a15545a8095256df012dabff33809a6cfbba83901e8eac800b11326fcdd8006ab296d5f7b754601ffe2dcae04a26298f1de5a3270a1094ba83439e32279324fb47c0674c106496750417a5307af2d71125fb9b80258a29d9ffc70e098a507ffb2de441021f1d9041f5fdc980e91a195af6514223a0df43b099fe77c7260d3b5033f0a379fc7fd8dc5264ff0d740cf7d0c719fcb9ad40c1431ee25af0405691fd36cffcae634c0f66d7e27ce9a0875d08354b3047adb26b689b673e28f368f30743991b6ea8b5564ab74a29add469e8a9874f9f3ebdbbadb5e20e1b8a3326e2f942063cb6b5184b4dd1450b831345510ed1a080da155adcb6554a29a51b4729e59ea87e9f67f06f38f8335bd8f7b63e68ffc66cbd8b5da88384e73bad61ebdcc06d1cc7d5fab572af84c471b5564a374a2bad525d10a594d6806334ac74ce49e7988feb31c013e067cf6eea98b1d9853f63f35ffc309146f0f123023c3a601de31973f518e009f0f5e75fc163d7a62b20e865ddcfa7ae80eee7b78f5aad36bfd361ab7c54f9a82961242a887263215325bfc88de54c55c6fe8ec4aaa24ab335bbbbce3927529546f2e84b140e39e4a249f4a82807e5a22e58a7842e4548e00e7ec434bce162bbc8fde7452f84709db5ce39e7a453c2e86cd59eb43f9f0164ce59c3a6ed475ed40d24cfd18bb253255cd4023162600c3104f6ef3a2b9d73d2490bf272f272c25161aed67a944491847c87319e01d3df2ae8b36d75ab2dcc54a585939290908e923a87a2a2225e7ab3ad84a9c89225539e28a5b47bb285b5acd5086856f3cce932283cbe8ef7e9d3a7e796d8c1b0e257fa1e9bdf4f5094942a9d73d289654889125100ad588b29026d86aac1ec78e24401b4622d7a4329131e5b3bdd6443466bb29a67e6d01b4a696d25c134b0c7fc5bad7f5184f9db0e1ffbc9dba9c9697c00e4346e78cca1b2ff53f6b047cafd44ed88da81b5d0683b28a574ce4ae79c7462b12820415295e85aaf534ae9362d0b5bade53c277c671079fa748c29c3635fdff01e61e4a0c9329a67fe8b16e0511c65e4692dc6ad96cf8ef946c33ecbba2c8c8e30b273226c20fd065bae6355b75ae99c73ce39e7ac1b9d736e948e00cb1108a575a394d6ba6d94d239eb9c74d22221dc2e523b2b9d73d2b904953e6be83e2775eaeeee52523a5d4ae992cb992fb9b19cf92922379633435916f98ceda79ba9e5c652e6284b6e2c659632adac4c73d53702a1b46f8f0e918bf3bdc8ef0d756c78e462a7c3ece00440e070858b0a2fb8209bdf7aebdb93f95e672580a5061eaa8a8c909a92cdbf1e1882fc412173a127cdea473c1c753a143951d1d17cb9341f1c028fb2a8050aa699ec7fa4d499c72ce9ce7047cd9072833978cc90421e334a0552a66f6700e1313b9a15516e56e433e8ac68963443a294c72c69a6344b3aca3c66b3a259d18c88c7ccc8c24c98b1284cfe8ceec1f61408903c65519eae54e3250631e8d1e245047714532de31c81250f2633512f23f0f360a262a2a294d258a6b07103c1cbf5ab4c6698cc5029401b9319262a262a262a2940527ea400490992026499cc388d66aa62a262a2aab9d84c55434ddc4929a54178b45912659744d8566a14033042846c1c1518bf803a4996589047bba4692eb9532ad21222da12142c51988e88054450b48721251a8698544a5b181a6287806847cc7096e36847c43cd18e3471440a731c679b9edc1713f2a7caec414a77a7b140fb522596dbc519c475259165b2bfe88357c9fe1f9d4f2955421b0294fda57bb9b126f8933f48f2a82e49e9ee40434613cb082295ecff304a905d66254a5176156e0932f7b794dd0d764f1bd99fcac69351cdca50cf94aa71832a49e9948a9121c2e8a9d13f27083a5cd451eb0730765c2c3904828e0f6a30ac8dc22ca496acfa1d31ca2711063a8c3b35ec0260b27c4a4247e57622cba70db3006da8bf64f9d493527a72a82983f6a30d4175e033604356c25e31e19382d6ce09047584065a293d1274e449d51122370286db36235ff248b311a73cc2721be162244a524a2bc7fd14a162b78db39666a4c84892ecbcae3343334264a488773daf46315a9ad0179a50134da809da91259a9012342128da4f124d68041ce8286532258f5f5b61f2259bb95304f9f519e0997e831e785e0581fb279e31db236db89ed73cf3ad8c315d54b86edc46b7ee2b41e6ee6fb8e77d6feff73df89ee7795e09277cdebb3861b8b823d670b1e51a83dd0a0cacb51847175ab7f6e9bb70b167691838f41478b4f67bdef6d0e00fbb6adc06b7d34b0a57163c3e0e2ba7e5be23051e2d8b97147864b9b2bca0c0636bc705e8f33637abe5726579458161e19701e863ddbf6f07f4b1eedf7782f4d5103f9cf0f234c4112e519ea6643366aa34e1d0a587289f607ea8c87899d2f2c409a6502101102a52949a2873e44a114038fd49c1c5132b48586882c80a36292120e9f28253940f2f10e153484350463c018312282a9ee4ca161882949922060c11f7222978000823a68e56206a62626ae2d2658b0aa4bc1071c26b34215081092a5cb4205164a9bbc430040d6776d0c2822f2e050a408cac1009f3840a9896aa2454047e94a0a108a889ea0939940215482a1061347485c88a0e5b0d6544906589124154704a010aa2ae8041da4273014521c846c5040e645a700495d9cc89a32cb52142966809f2313fb427508c3cbd008c0860c861014cc280298304162a50504f8c9c88300244942a51a4c04ae189312b541989010b9bd2d6460401268ad0142fa042c69fc032450d9f1bcb1442b2971b0b1933ef148eac39e794f45d4be091043ac32746e64f561c38d359e59ca1cf49a5bbbbb7bbaf5879c67ec311984529954fe55b80ca70b230ebca0ccb74127d3d58321ce7f4cf459f5356fa74864e2ec4481750e091e61cf4a395eae8e0e032d56180cb6ea795e4e955d40d6f2b1bf0dce846b7add229cf740f51fa3464b958dfb504ae734e5a87320d6d105408fb371e5d4b544adfe557980c5b89dae76a385b2d8c9f0bf1f8e5f961d64629985b2b372bed8a31b7e128350fc76d2ef2e4be724f398eb31cc7598ed23a5baa79386ef38dabde100b2ea573a2305d512429d2b3c91748960431761bdced143d261a81c79d9d2f3b1eb0720d6917cdd953451017154d28edb979de26956d5ad9e69449670d73d6aa427d114d198ee89c72ceeea42e5f434ea667e73957b8de4fad06359db588fc4c612cc7dd2f329d1dc54f98deb0da6283cdb37d6bd531b856deb870020c086b93760daa456a907fad498c20a89b6c5f71db77edbf35d35a04a5992f8964960f36071ed1dc38f08a640cffcf18739b7c9127ecf31a5a6e033da05fbfe2705a95c76a548b6095c8c5244492831a83572439a848aa934c945fe40eeb50f3f87f43b87fac43947b0ca332c42eb6cc9e1112b7badc604a36b90dce03eef676d9723ed93a58cbeef449ebc664e36cf72f5cbc5c3a2d58b47228ec26dbaed697f325f71727a7c502d3acc0ef7a9de5363a9d86b479ea7fc0679362bb493920b96e62bedc06f9daeea3300db2673b5ae4ede47b33c8f70bf30561f9ae3e5f9a17f96217f9ae78e55be3cad746275f568b7c6f58e48b9393936f8b05ab052b5f1d9b7c5d35f9be56e4eb02e7fbe26160bee117340af0648a26867c1143c50f1c937662c952159522429cc8c85c8c9412435c8070a2040d32e79664076284c25450902a21da0a32c7c961071b42a00413304764ce11ed60e2080f2e7001162d4960d070468a16aa96b8f0848321bb8f941f9c784272644448e6564a1122a8501591d4838c7b214a0537661400da0633747131f1422c065bc49530481c278798a8329b9230d3d9100c8d3626fbef884d86969494c1dc349a1279dc09e2b28801f7370746098c873aaee4e4e2a7e4e442506871a0215ca464fd3686188e5421eb14c8fab1540206b333b2964a41d68f8b4c9d919254a65831a2814492951e62a8aad1648229b213a80b46dd0c73afb5effde73412d06a417ec9e6effcfcec29cfdcb41caaf27c1b1d5cafcbdb73760369f5c921b3951cd71cc751bc1a81e4ef7bee499043a207db876067e7b405df268d4bbfcd451278c4130116583df8f35d2481ef7fb0b19f405885a3bcb0968bb5be0884bdbf3fb67cc420f8bb31419f01def073f10a1e6dae2dcffb09fa78d64512786ce5da72b13e8dd3dcbe4f87f75f8e8b43837c431a1a2f1c499043797bdb7dfff61d82f449bb6de75dcf2b5282045a515391bbcfa2244a6905c12225dbb671ab550662acb51d0d8df7057f9ee75d8c372edff7812b56d43cd57cabd58aa6a6a6c90f18e31536363535362c56931c582cd6cdcd4d13a22648389f144a02ae6c46b009194545d18344c524a869e9280911105517a8a6a5231892000dfd54810188aacb0b50486a4d2f2c011d25790188aa8b122816684a9a683f494b474994005175814ad2140426c9120b5b8e922401a26aba42a4b9d025f7dfdcb6ae2cd8ffc6fc44a0d2e76bd6e373e335c3a3e538ebd3d3bd66debddfb75a8d34bf66af999432aa868d9e9a1a166eb15a2e6c63c3c22d56cb85592e16e8d372bda59ffd30bd116f409f1d9cef359b45bd662d16200e3057e9d2e125ab7ccde4183cdad72bca45d48b2859e56b26e5f65bdec23103324806d574b93dbf5d595818b7f2f61807d1e7a475e35c596a749f08ae563418f4c13536363932a7737070707072725a2dbac3c22d56cbe5ca52ff5699b7efdb42aaf3b9b260d70bf4a1a00f76b9b260571611168619645077409ffac92a653892d051fe12031d952de0210d3f1611486a524f4eee0898bad8e14dad56abd5a6a6a6a6a6a6a6a6a6a6a6a66ab55aad56b3714d850913264c183faad56ab55aad56b37185e9f7c9c3a57f294ce69e5c1bd033565979ac9137eea30ee838e7b8e6b138b8cf615d36b575c57d057188e38a5c4720d9db3a30c7c7813b805450e17e16c4d1cd932b67579decbaefe33e10fcecd87d1dd8001a8cafbcd782f641cfb3a1b7fa3cd067a4f92ed8800fe430e8800b4a106cafab1598c3fbed57200e9bbd10cc61bbed0371d00ada0773782fbbd032c0cb3273e1c881bc85e3cc0de2982fbb2a56848484685168448c684496d08820a11111728636848a36648a36248a36e40b6d08140b2568e972ba94b2410065dbca53098c6cbdf51676df98db566977772d12457777ab45bb76d7d0465144fb296206cc96964e30fdd958aaa0d5ff7aa71e4076e9c12805252394767ccbed1d1958869436a40c19dff3b9985d3b3ea367ccd04e69e3023ce2d93e9fae77cf1d57d32bd89f877b5b4eba71f649d779f703572b31583e0d8803cccdc22b6c449f0eab53dcc659ac9b1b7963738373838383c3b2a9c1c15981717070687056200e0e4e27440621e16be18f86be66d5e5a298dbbbdf8a86a6e635abb1896ab9582cdc62b55cf846871d166eb15a2edc82c92816342d7470b95e635e2e5e44bd6631565cb957becb29ad75db38ceda0ef4a155a9267948debddf07622b6195a826c941336c45d7d425b156a953ea52ad32a52ee1e0489caa64c362ddc89c9b9c9c1c964d4d4ece0a9c93934393b30273727236e0a2e4941ec8bfd2e5f4d973526f3ad2b9c2756591a110240f8882c0bda3bb7b859570820b71efabea55d553855bac179957d56be6caf2129333e4de971826442c6c71b24f459321a949902c53b5a44a2e995ac2c4a476ce0f0c74ce5a6bdda8b5dd77bf10c0be256dc78139c0ec83049bb77de7ab2043b6a35e0d57cf36c4071226a7c8a50f240792269b74681effb187c84ec316573cefa6e05b2c94b252ebd034b8975ab2bfc42281903bf8937885d2cf724fb82730bbe37b52dac184e5835fb5dc505774ef1379223d3f84086eb1bec8137931e422891741a4b2cd5ec7e99831da2ffdfb656db1fe06eb5aaccfc1c016eb5f1d3e63458b3734db4145c927f9a4a347d4534bddda75d59dc0e56e1a37b4459c11278c7144f6f7965a9a46054224b2ffac8038a9976ff7bd0882e7e349e35acf68f3eefcee9a0f175dbe54bed59540146e50801b0717b67c5ef0685d30bedd43db2f411cac177c972562fac9b9646e6370602157e1755e88b81fece5a645194385a3b42cedb618883cb6ea0aaccd47d38ddfb893b19cf2c38494c73a8a52478b82248f3bad9caf853fd6fc700bf74c5a94153c1f3c6475f15883e9a00b554b28cacff604e672d3a090c9e38703bec94d83d2257340582a8fdfc7fa004ad48cb57d9bdc34274b649a9b0685c896825d8262a73708cdf6ef2cd9fd867f5e8377449623bcceeef2e1031c2a21928a9a368210641f28a51f143a43f0283ab90927481f8a60f2238352ff04e964cbec8b0c8a8e07d57eb56183de290cbeafbe6705bbd1810fabe77eb4018205b8b00317fd6d702f372ed4e1be673e08d3e1421c30f2cac6f7fe12b6c2211ffc1bd3e703411df930fa47cd3714c31e251d3b78f468ff1e3ea577847bb20ef7f4431d2e9c6fb30e1b3b5c43d9bf86672b61a28bdd0eb8c67cfe0f9bb31440f3f8c73a86ff0d9735d6a233a066e915bbe84ea28e1d3c7a5091d22a2918cd08aa08188dcad20f9dd2c68e516e271d3b78f4f0f92b20725e6c90e78f2c328d5c19c13294b1514a29dd6032dcade099a5548f0a8ce0333e4f1a8e98a679e65f39691060fef4e9314205922082022796c8f33b240c4a552124cd45d982fdee1fc850d9d2c40b403491ea6658610a6d8a19124a64f26f90e01f1f72225b05f620072201ee2080d892b9ffb165fbfd1cf7e422948b12ff804719255f76e13e6a8b94fa867dddc11cc8835c949e672dd7efcf8563e3e8bfdfd37b5fdeefef7fd41c2ec8609947ffc9f5efe5386b7ba44f42c3fcbbd07b0e36ce9fccfd44553dd59cbeb81529292b5f9c6a4f55f9e783c4a1266aaeb9e66857bf4b98b9863f6aaedfc9a52230c84dbb6129d36e38ca4d134117f93172d3444095e59749c3818a24a972209807f9f44bff21833bdf16a0183cf697fe01bde0b1bf38e83fcd23df451596cd7132687690b5d87f260dfc12fcb1b5fa11d3ac78efdbe946c7fabb5f01c3301ad80a06c2ba63630dd92d7d6ca9f90ef3bfb0b1a5727f7fe73f59be858dcf6d34a42e92d052991562f9391e025a0bd5058a4a6ef95d92037f57c7a44bcae572e51a19437e4b054173828586f434bdc708359c931adf79977b3f29d4e02403fe53cbf4afbd37b498e3da4e3c63ddb20b6b59eb09a640d33840c7280799be0c9a342e277f5a64f201a038c00b32fd1b6d9d280d59d0f0f69c094f24ff7ab00b93413afddf0391f5dcf75e84fbdef77bde7fa1091d9831fa5e38c28cd13721a432ef9b477e093267a0d53cfeb0e691bf3d7d3b6354be53b187f664fa1c0c16d65aee0c4eb040a45729a44bea2ee7e7b77eb472df36bfbd79bafbacf775f7f3eef75df0fb2488637e577152ca714fb9e742dbb273161123372d8721b92637ad06210dbe8c8152324193198a90390b6170f82630c30984e82074c5052776ab342847683433b9959b46c3182719d5b9bf0bca511147f0508549133222331c4152c60928cac85c86cc334513112d61bcc440029923614608156a607aa224e36210618a1278590113253f3c9101a9d2c213299c9a08512b22f3a2ecaf63d200325b8a62aa09e14408156440569842061b98a8c0e9cb0c5c93dc5c74a842061768e161494ae63fc20c9847a8e40dd834415424250000007316402020100c0743e2701c49c24099e50314800b6a7e445c4a34950723b1502086711c04310c0531108418630c32863185182a2d004950986994834221fb911ad4999122cd7a270fc5c724ce81223388c41b0869469c27a84fd278203be46305f92c859dcb4b405bd16ba709bdfb498cddb268589b693619525d4f2bb76cd8c4c5b2ea3b661c3bf6131e515136d4e8821759f29024095eb1eec50ee1266a7a708594405809dc357e4e2ceb6f79a5653f7abdbdca849e3bb9070756c5b623c178bcb9376a642e4c7722db0e11eb1d6bdf7ba85d7d7cbffcca74a19ac2d37f96059452d014ad73a488db202a3cab9fb2cec6937dc315f1cc72882d40c6f8d2f8480475e266476077f095eed6d2538e5b769765b7865609dfe4f2d59b6fb7578fa1dfd238657ef3932ead7b1459c817fa6d1d88809455608d3b890837138209f63335b5cf5e14d5582f0a54f4a21868eb0d671fba00c91f82fa81514bc45c5df2435132507c34730eea01055a184880a00c933c76453d2c8441ed1e299997027e3cd0428c84213b6254228265496a605d318eb41589b72fc898c5f7af8e11b8adb6dce045259c0413f21286a422c8242a578d58e84e4cdb958de0f6c431fd25152c69e02917c227c1295ab20a57176f412449ab15c429d70e1a2ba8ee96b86a784b0813f1cc84e15d5cedc2de9ff2fa4285c56f29618eb9dcb297643c7619ee0713298f0f0f28ba05b426e0ca18d69ad05cb64a2e77e523bb78c0ba203e2518cb38b47368403940d95d1e4908622197e1162e05c5ea2be0586b941847a6c0ef223614244038499745bc6d0e89096974212ed67155e93dec634ee580669f19312f9a4002b5c468e6d132fe1cb2e074531abf611cb3207f32f64d65833888f6230b319e43b5a69eb70253496b3d0393a81141f912a8383bbd628202db5d48b902313623c5059e7c30b98745a8c5f22476484d0b835f2a9312d87fa127f91694c83cb8d03dbc489ba1234224820e73a9b765c2af9b461b7f5e70ad1736fb450ffe0718e2e41a25e3c2bdf7ba2d4986ed3095c029a34271c4d0b8e4646a30459e684d416219fee59f021e8fb2cdf33602367e63a50b4cd1568a60a40a14aec31549910147a365da2eaad3f933382f5559bbede8c554e9da4ca6c5a16c0af8f41a4a14920dd8f74fd4886c275455c643536c6dd2a5ebc6917a5282714a7213cb212088491b9045ca300a769719f41c0182be5c88e8a4b097d1324f689d6d429270455d6f192c20dd4febacf9fc43bb4c6e7639829b9888bb0dbf9df589cc1551b0d6caf1ae08a29fecbcfcbf3d1c0fab8f6620e4015889a1a32464be222b684aeff8802459c5b855b81f740c2f07493271e7b1901710c0ba296136a2c8ab694681fb4ca789ab3e41b493f8fe6e17999fc4102d6fee882e353c81ccb986abb7b7d273627abd14391b4d65637679111895400c54661964c781126824c10c467db0af36c9a62dec0dc632e97246184d4b6e987fa875a3bd1fb52dbd68861547eb2277bcc835c93c46939109029694d15730b73b453d6c724052482128e3d09b268512b24b419e8b19984944e92a1bdd7eaa8e03345bd4a9e3aee0ae1c97ce2ad095f9f7c4521c8ba47295c3c99da5c080c57ddc95cbd6669850b166e4af72d70923fb6102b0d79f60d3a11a0fdf250ca9e935f9009b7fa6dbffa193efe5455cee65b6466d3c67553990fe4e47755a7adc1afa43b87d08ececd5eda99dc0f8ecec206a8186d26eb838710c3bbbf7886e39e841dda51d94daa5fe9d5602d286c5692d6e553e56bf9d7598f551727d458b3d32d82b2ad20ed2d8bd74b823b08efc902577e3dfb97732df7aac8bac25dd994e236caa01c315e0dc03f7b2a9c2505e923150f5603b685cc279b3abd397f4fbd220ca0eecd5778670759a0eb18c8add46adddd1b29ac9121bab0e8593cddf831f3ae1c026bc1fdc723e54b413f9405ec2b383569415cc76713581f5810ab6bc919e93377eb127600439a6cd7e84b6f927594d00638322c616fb554a61c992ebcd387baa27c6b3d5fd191b6b5ec7c9c01afb206658473dd51f8408f895725a591596ca8454fad1225f03590f414096bd918b30944b85c732c66f85b6d52843636c69c866f579d6abd05e923528ddd05a7ddbc65ff0c74008d36253ec03fe7b65bc3320f762055d39c8b46749966d0ad74b145135bc6d9b84adad662942169f253671992fcdf1735799f455bb9fed671f90b9b16d6f2e9faa41072c9931f5272855d824cab6548869529fa866a295a92528450c3fe2602ca709e6c09feebc70ae2442f2622fe4065096318b9a5d080a0bb0456680ded02f238e574324a254bb1d64b832108365805e738691fe85392582a11ee1364aedc6077e4080cc6a358c83e040e6270a355fcf2a049ac0412bfa41832d90065241e44d018deaf321374cf8f363289bbddba74fbc7df6af2ed3cbc519b0de344b5c79f61c2b1c07e47b045864cd603bf983fc520f0125763f29fd87cb5b41cc7c11a03ade8bde8d2d0ec66994ed0519bd725383ebb631835886d72b8496003401476294822d6d89e2ed25b78f1d93ddc588d9c077efe02f3b46c02d0ce037454e31407123fa9593a6cb32f4e9cf13ddda9a1cc64e6608c6791dd48765350502c46ca8126362fe0c7be0ff5bd08984f9efd139774e2d5832e903e6a2aca8b644203466456f534951c715e2ef3dd68d36ef67ef91ac80862a170ff9a69d048a508a41e1a1b76cbe76d532c6c24b4b5f2ba09a1b5ee106f84bb887d3661a009ef9bdd3fcb78dda2e645f41feda791d1443abdc0227460f640d9e07ef914ce911537e1d7acc234b82c62676fa7ee1640239f1ea8884bcb500abc0ee25ae04b01dace130ed33003e1b4f0fdc61e104816e167e1c6e30943a515c61f96f1582493458bf5e7fb8562914d2792ce3f3082d39c93a241521fe119b68a54641d192b273bf078afc6a8ff26b486c34041c4daf1bece1e553ae27a02c000981d806b521460aa4a86223f3d77ef2e09a1e0098c9eafb574223802568c612ba41ff17dcc3a39831945ad0d253028fc439ef9148eadfaedd05244dfe4230b4f752d02236dd75f949fe26bde40fcba7fe1043a1ef752a256480e24ce469b1f136a6bc8048b523816d658e47b5f3c343b513faa7769845896d6c20a5194e2744808dd9d8f18e1122f0c8efd8c6cc47abf33dc96902fa9f89f281373e92ff62fdef40510d0957ddc418c8da17ae18f998fe24ae1b5a073374f27d4285065e39ef36a824733a7c1141c11604bc20ed4538ba8a0bf225ec02c21c306df89e2e7e8ec917bf14f101c9bbd3e50dcd86b2e1f5cd595733d6e7a89836ba5bf50d29cd098825d79526122608aed4d4952f077a5488cd818f531802546b418a29329d52a1e601f91352a25f91cbe6e887cf4dd08d1853cb65d3c5e08f793b44e600eac2c5cc7f41e24915ee5a90403b9c2937e3f96fc7e97748f8c863a5c7ecf15eacbf7a4fe703909cd070059f95c28b00c7d94303bcf55ccb0ac17c52f8cb75351676fadc80ac6fea5b4248571d64ca3905e8084add60ae061189d9c7ada1cf33016e265f15173c517fa59ac4d37534705602380114c45ad44ddd72298277839d810fb20cb0dfeb59387da538f545db650496428b50e101bb31c256e7b9afdcc0a92d456f5bce0b99d713a8a3f7d60ed37712c4d295f83f718d99bc1899f01bd83d3c1a538570e02e8c6b3bfa759bb9d858bf63307f7d9e4e011e3739732d82ebd2581cde52a3a148619e44c344cfe4694b8a09218cae73eaea8481735390e84a64dc4d41876ed15b4f04bedc119106d24d1848034b95bc19a38dec3584f2b60ab604946947eec19e50ab714a44458a493bf901b34214cac1aea715f2f1393bd74884855a62c5aa5989e62da920bbbaa2f4cac989532e8b9b89485053843bb915de86d252611d1d6159a18805d78f8a7f0c6f8509c448005e20a9f00315dd0bb2ab26be5bd6687bcadc64b36f034c8105c324b58b988642054604feb55551e4ef4d4ec9957458975c4c72ee20f73eaa35bc2717553c10f59a202fb55ba88fbc20fbf8fbb6d1e0571f5c00c1593e93dafeece399f91fd97a7f13193c95a6f6ac82474763880104118b2397a74716acdc18d230a08a140aeaf07e7c4022d3b00f9989a74ac41c75ad68811fe6d88740eb43cc4d16492c138b6763739029351eefcbfaec33ad19ad70c590171dd13efbcbbaa37c6d94059d8a8eac0bfa27a01b76e3cbc3a6465d8182ba47f01fa319c922ada734e3080b46e2c0426e3c728515127abad3d5fb809edfabed30f0fd756b94c284ebb15b7e0715e233b50a0b9b7d803a050216fa40ee110340319ec6d2014f26ed444c792d96e67442ff9010c15b51bc8e0c882ecb42d90150cd31dc9fade2124f3900748b473fd2d472a8a04460e80f64579ca67e284660f865bc9db4cce9ff88a2981f9986d7f80b4f7ea4c7b8ca4d44954130e135694e155ae14a82f113bf9db148005bc75d8bc7c81a4b0d256080f86143c8c42e820d082c6178de451aa386a13084c85b4b7a72652a0a0761d2e323c41ae1e34c95c7c0f288df26375706cc38e7921d1125fe83aa82343b8b39425315f8dda4953aa9e2d5770b1c853f02274e2d2595588c40c5756184b0ebbf08226af95d10e43817813af912291f8fc4e9c63d835867030abc49193fb7649d146e17882cb1bcf441d45e4893b981fbb0d67fdbf007144b93611233ffc91bc37ac6bddec580f344b9db3c000136174ee80144bce144a42f3306d04c0e89ee5fdb555ee539bbafa8ae5e9eae067f4d422729d7544d133786b9db42c03b7c88a98f1ed39d9aaf46a94c97405d03441b5e23c9ae4e0b341840c2a628a4494052333e74572febeac653825e94ba12a12039c4f6b69925f120329d12731d37cf6a2be9bf0f28eeebd960d43a01590b168b264d366e7a262e00a5ef95765b805ae7da5bfe741dd9c69bca20ec0a0b3c646f5defe6df9b4fd10a1592eba0b7cea4a0f7bb60a7042a42fcb926de070d69fe1222aad62b391ed2bf9eabf4fc2456b11d2b822053aa40a59146c07df587b0fd83502cff0f20c184340ac0bfaf14bc97b7945f4f3dc30753ddf0418bbbb103e57fbb0f4b27238adfd6139a232eda786e8b5dd1a421e690d49870155205d28bcc99703a874afbdeb28566b66cc3921189780e977f5e9ec32ac5c777daa806aa56f99b22f1d5e0838e8ac1c3a2d1c8eff5d99e4c25dd3bcead8f00bd6cd5f0e56f2e7797f94d5efb4fe7029771f4d3f267a858db03c823ae985be3662b8d364094506d34804c9b9c93a9bb4d27a1ff252ca1e66e028d30b0d8d365f2e5610834f2a78c4d3a035f467427399ca6bbcaf008433be52c97bc1282ab26ca313a00d73186f61e9cdec1c1f25f2024d3c21424534fdce8959567d27edd4394376cf594c6c36df27a371ad2835c163d8dbe27c44020175b5e0f737b037112039ec0053dcca84ab41a5dbe921b77a0aef5061ca140c33c27296040f907ca5af35cbadfbd74efdb0470ba389228df575dacedda861208d100a1463ff111348ea252490e7cfc51ace3e3dc035ffe59a379bf89ea0cbffbe3c7d5c74e294555da8d117f35c5cdea201606c45cb7b4cc71e46d90316929d0f4ab6025d1eb246714eb412897504a4a2ac871c91bb63410c05fee79baa6bba8c3bb01f229cc2e7e6a045732f75bcd88805e30ccccac9859c95dd95a1b04f60b2150eba1484a0ff17c06c62689d4bfc83cf8a144ff0f668632c0933191b73368dcf0d338ff63bfe18eb5b8f0c84103be51d4cdd3549592ca096b65260c1a83628b988cb5a16fb563526a7a76ad80a1179fa79e91cc9081c2844781c7e46fd7d0d7d158d7b33da72857d6dd9ef1644299feeeb10e75bbf0f8c17d01c5bdc5183c90bca570a3e24f6dac9166f4a59b211990150c9693e09a9ca88227f34adc1ac4fdf0d9574e0bef149b5a8194716e61d97b10ca59a0183ddb0f722d4c50be0931c0ed4930a31062d3c414553495ebea859268d74fef503cfdd3ddf57fbf32261b8b3f8a196cee8e84c4c3e4da9abbd7c57eb790411a15bc71ee0dd4c00c66eac888a2dc6a41f95a47d9999fb3f5475f86b9f5ae8b7bc597f4a4cadb83696de01631128f82146980c2702510a889271862c27fa75b4e4df2c0a89c6d97bf3cf8e04c49ff0eb39081a119b912fd055341068d7d9a6b4abd74c6501cdbd008a91ae924a322feedfd83815c93f9851e05f36374c5fe024c1e5f70b8b2fe0530e160fa1bacff19a9e0e0dc9eabbc9a949881b13fd4ebaecea84d7f933696b202bdbaff0782dc840d0243a3090d1e0956b4e7fdcbca4b7b1ad1cdae682df7b7101b431c75904e6f0a082b1c4564dcfe08503c4688b26b94da1a987a0411e0ccd64ee0b2373d40d9ef2943eda7a92601174d03d7ee557ed47281f827088eb0754a35ef54722dbe47ee4d4515fba3f549f7542cd3a1a9efbc59dc9d32e29d9c7dafa67b5a8a80bc6bc6ddf33dff1c890b5b1f85632f545bae44b0c8f83df30ba1ba03bd9697c2088a641a836a26fdbfab0f069806dee82b1017781db51c56ae88bf1bf60b2469b3c56f2f49bd4a8d1778e7397ae8b785b09613125e54ab4476a5696f2d535fa5661d21e9265b85181633b2396c028b0b9f6eb48d3577151b29ea439cc65317aae0b80771e12e3ea392d726a458745505b2d5f0c8c1abcc406341d2fa8434ebae6c00f64811d23508b4ab3f2ee87deb83971b3add8183b1b5d8a5581be78935801fe324b397d0706889493a9740fe2584b510aac6edc16315b8e085cd93800d14906a043780f8a39b6011a0f3e0493cbf96a87b662dbae66c3429eac4b79f2f99829d28c3627a582700edf8492d6e54635d89696a78834a5e704cfd381e493089b8e9eb3f0ef1bf27916ac6baafd9182705c39ea86ba8c887810bf300069b936fad85abf92f07e28231aba489edc41d316c9db6666ab7ad17dc1fe931d8dec258683b6ae4e2f27e3005b7200b59406837240fd0fd19338e8cc41a40322605e235781b933c5abe1849164576e17a4b8c3478803770dca9b8166a57d7e9000bd33e05d5ed068046c19721a1e40b6d3a2bcc3a51b43e2b11df6e9ca48cd8f44ec20a5023ac2ecc56c621dd83455c1687aab86f1bcc379560931e023851d1ebc57dab56f568f12976327918b7311abb5ed471e31f080140ce4900ab56fb76c163169e5aa7d072fd2dcd1899804a72599a229e8714e10b1b629a0c6ad802ef3cfc5c16912419f12ed9ac4a22e0beadf06e4e33d9883e3e62fc0f24f90e499bb9e6b6000006ee4a798225cd28a85f60f208fb976bffefce832e2bd244e695b0c3e4e2a31d4ff1cca9dd676cf3f7ad1beee17761271acaff11e895f18576496e9f10e036a6a8ab1b048ccdc2243a45cb13ae6b937c120abc0b25bc8ea5df13b3e3c2f62dbf54e797d75cd7ae5dc96000c9a3aebc8aa6d5231803583144cd457bd90f44d7f1ab69029596521e059ee6c74e17baf972f129fc36ae6f57ec96b00bf087c34046c3c7d63557f85e697877af5d6363016b6de017baf190566aad4fca157a11eb3772fcc2a3530cd691eca72a858fee96e6974aa7683f3e3147f0b70d293ee050f4197bd7aec0bb5f06bf83ace68fad3b57ae01304d78e582698b7cdc98a782820f7690f4a332a2683f4ab03956f5a2d4aa56689d21231539341c61b8a0d60173b24f1714e9c1ffff543bdc32b389dcb5d4f23853d7a765b4bbfe7cbc1f147f04bbae96ea946ae950ec9630ada008105ddf520bb62df0b1746104206f6437a5c8a61747b20cc140fac23af62547b05363e1957f8bab941e1a3bde331b62dfc2d96114f0782b9a201c4af95f4ae8d32a4d5a1d487d83b32ada223a9cb0545edc65ca522852f059fecc558ac19e322b069435ca0009ebfea87184fdb99df62d699a61a4aebd5745b17775528c8a5d33ca5473e05e908396bca5c34912ad3b1b1fea5bdf297474588b8823cf7cb28c0b4b0fc48108eb2ff1cdbe247167b21b284842dbab6833415e920533e85badd0b3f43bb7216e9440e6752c48dc5851b89e8878144da337bb80658d84d353b7ed48e09c73f71001c775c4cd6ea10c67798c4925a9470d47e15d88222745be3c325760882b6098b85db713a522eaab75f6fa28aa5dea4ae13978ee5db7a68c4603e2202c8266ab58c7061abddafb35a1361f0acdd101f49a1b63be11d25926c0f162e62aa7cacb72a7e8a0de081e05f60687ad517db97c2e7f478b39bf3a85684531c0a6bd1cf89686f448929106d74f7d88ba0a00cf77c107ff34959ff84459c2393413fb8a3e713542c5f82390c58b20cda9ccc74795572c11c0cdbcf94aa473922349d65610e14e4a60435b011f77d062cb0a0a8d8a407060b0106284e1aa55d9fbd28a09df8a2a279acf800ee0395e35171534971538402a68cbc6f6f6fe4ff4410e7baa0e9a8d0cdf87777c1eceb5fce1b01055c756a27d38965566dd5050675ef0ea83006d99adcac40148297de9c307352dd358b9fc0ff98a3e70090fa270c1896285cbfd0c5b5af7f0b0c4ab6ba8195778611440170a5ab0ff4f37ca2bddbecb61accdb6d02eb91aca28c09cdef58177923db41b1166800f11524bd6fdfee2a6d805e1a9ec2704922cf909841c7c7c8b7f98517df0d0a92894be28a0ded4cdb12d34d85bf48b8168e209d8ca815cf9796e02321cc61ef90fe49022347d1c8b77982478478491279791a4b927952a8360ef258f5d86a6a8bc519265e60e514e06f3be3e95557d37945ad54e9cb640447288439ec32b7d71afaeecc48b2fd5326d9a29a07842ae2c31a220e019d1b9e91b86ec87b87370bab66cc096b62b45ff4df91b375b501e5cfada2deba21aea1888211f60ee6b60d13ede266c2395e80f4a315b014d363391e85295bd129af669d31ed2f308404b37844d85bdb155802ecfaa0fecd7e22c65d57ba99ae627b568ac87f41c84cc3535aa8b86285b8c6bf75c5a66504216d1f974925108ed1cee8f9c72c9dfe32d136b785bd258382150108027e18c0ccf2c8edc7dd876b5a08604389ebb4e56240c999276841945daddc9338e56d74d425d4944b282917dfed748a46fd142056f0e8267988215f3f0aae05b9b315ec223be829bd55b1eb279c61df5aa1fdb2a495a501371dab26e7d950814f2ceb25c0b277a6bf0defdbe2049e51cb393aa8fda160b8688c05c84b654d492804774524328416f7963721c4d0aca8066124ff7a193f3589b3646cd4265ab98984dc82f98b458213dc0e3a7d409582a40a388065b249f859597caf8dbca25385f02f8e4cd17cc50191483b52b49380a520dcbcb8f6de872866aaf7d83ea88e618a5d4322383bb9519f2da84150ba5205d5d579173919d49dc833724ec666cfd8a95164e113e81bc8ce52fd0e42fb9e2cf0de0b446e64bdb05c54619fb0583c129f3fb8e62e64b0b22b728f2b6640fafdcc216f4018247fa9e0f948d324b24ed594cd4e6a1897817c85649b2d390bcce0ae7c12b0d6618d90bbeaef823164ba280fa1c1129e066d82a7d03daa53278c3cdb361bffbd9c294ef80c5010fcae9869b4e832f7c31a5bbd278150f367ed4d3430660f98ebc37d56253bc15f607f0aa1b979646716c553bf4265ca1ce245c5ec55986370d6d11587029decdb48d081a6164349ed46a17fe0f0a8cfab563fde665962af64ddb9d75c8fd0c8d77a207237da3fc863ab0c2815dad16c03b1d49507f08137426463f67617bb497715689eac4557e9fc7aeecd521e197e32925342b25a35a0d5671c57b82b10f53856d3f1a39c2dadbba8a2464ba569fba8147f647610e66bb7172ec2401899e3093a74922c5af797665f2378b506f2eba5cb27ca0f3a5252bb862aaad4a1adab32db9a1cc5f23a9a798d32d8c306b69c71179385635b81baabaaf70568429a11493361a57ca1c1c07b85474b88492085a7c1291c3e342d677e78ff847e6aab2fe81ca194a750e6b7e5b0558468d3793ca6b5fff8b5602497719464f4f9816209191d2d62563c89daf9ad222e648b1ed829784d2fac5e6386f56f78412ecb9a0cffa7b0c8a38833a6c9fdd74d12abc6a3149449a5845229eb8c4a35951756bd1558198422011c74c0cefc4d52fecfcd5d7af5664bcf0f9f8d5bd96ec9641aa4e18ee4d1ef691b6206bc9eb1ac091e3983500531b2bbaf6958fec9280fd027cca2468a728ea2735f8b701914831e28ff2c4f8c30d2aff704afec9d0b2c6a3e47b399f126fdb7fa9d288d381d0bfc2d872fe650c19c6f993e23a34a079fcab365f87a5687b9865acf419e9a840e952c75c92689192d76892614f1cbf89e21f8690de5a251e868c2df9aac358ff43a280b391d387afbe6cae36d4a0be27fac9cfaa0d3b3f71bd626ec025e9440c90e9afbe5b022ea67869a1f705250cc68352cff0b51079268b0fdf499332e3c0950f9e211da9a1ec567716f83949e5d8b9f6eb4d550cd84fda4cfa9d948a0223faf681547043172c795856028e71576510f94085c832f699516818164518583acbe982471ea8454c12ff9a41f5fd06919bab16659aed7330a4275a7dae488fe597e070e6a97c42f2217909345c2e2467c112d47c34294dcf12add17c59a3fdb148b0a6f9eff5b61255526267b4014d65eb0f59bebc90c4f599b4b99a8dea36b7dc92deeadbfe30984a654b129dbaecf984126e3f3355aae277577ed4367a580c24539790f44b050f9bbf887649fd7b278940498b83c23af65badae5f4216574999a14d6506663d3ac491088a28f051deb70150f57bd9203adcaab1d05d5a0953acfab753608a75553b47f5dba9b0c957d43aa755ead52f3a8db88e5eb9b5d21ecb566505d5d3a2172b089835f346c6da640335b6556f57a5ab54b88530cb3872661a68db2b1a7482fb6201b8d08d2b6f256d2375729bc85114c81b1bfc5a1d8fc19861fa238693fa6d89547985dd0be3fa20870fd2e93270b6d3624d2cf5e5d12b8c5f9dcec404cdacd35c15c9043270ae36ec4e537b87c5a731931f0332eb45c97caf24f522ed639a5f8ba5bb34bb00cac47884615d9df759bbe3c9ccfb53d56c162af0c7d975912c4b7954d194fa061a905250714a9ac6f2a7e88022884b86ceb1cb5a54cebd8897d9e176b22522245b014f6922d0dda1706d7eb3bc1393974b07c444b4c68d82bc83180c2f50c19a1fd2414f1b687e04b07d7a19a79ff628e9ffccd79ed7309911548769041827a81f1c8fd483fdb9355b0496c83346dda478e4f921c531b911e27f9609ae567bf853f6ecfe9eabc0c5d83d078aa9e883f37764653f73f2f20ff8e9779a71fc72dee566a090ec6a45c56877a72e14a5fbb98aeb14a12b1b3addf1523bcc4417a60748166f387205b51cb380531a2685087f28c62c43fd5785d8e101a02e38f400be83590c136426b74b8fcda22ee4900f0521621dd5e8d67a58772d3cf6e794f4a99886aebe5d34d459fa9f84383235d2030c4cd2d0a02df7a6d362a30befc3a0b64beffad96991f5dd2a359214f68e248938579ebafca172331adc41895aaa8c7ac174221097095f86cb52031b84e5df97bf16dc9e69aa0fa21cec574c3e7951f4e00fe99f24c98a52d4aec374d4074b8c95abb29c34f18873f301f14578fd6b2a33b0ab846774a1a78eb57b0263d71476754155a0914181d7403bffe283732d558844d4fcab70685c28f9939a0be91e1d420f7061e64b17861098d4e375360b9ce26c6cfd438621b5e07eaab653bf73ee3df87271072cacd2eced6f781642812e0078accfa440175becf48ad36a3a5eb48b72f41d6af0ce903b4350c67b19e4ea875149ecade66b652d89bffa4e10e0028b0904a91a8804c73799fe95848128bd2143c3921385a7f022a2c92c62a59a401c009420f1d30bfcac03661f021cd32a7ad4ff4cceb7a91ee5a79e252b986500f7a9f540e9ad81d4a0791b4abb8203e39d78462954c3883dd18ff6ac4380a3d4d373418b0975449a184db3eb8e6a2d62054f7c9886e21150548bb7fe1237e50aeee14db733b3002696e918c8505252480567631ab9916f5358216d7b8f0389fa41aa564cc970dc4308ce6cfc42a62e7f2399567f9a37363df688cc33c8046ec8f20a206e2b977f2e632d5bd05665d904a46b281f5ebf1c9c247de5e0142f3e20b3cbeefc173135b1a8410d23cb423f9b0281ec13ca34aae91186c0850ac85826354d08490fdcc5bb8e6c9cbc38fc3dc1afcf2ede9eb0381230b37c56f2f0fb07dabc0ee7650b2003182afa659374c704fb56e6ad549f2aa16d0f1d8a1a0655e60cc78c06d64fbf19fa4079e643ac74b0c03737f8a309072d0fa1e4e55a71e8d943884615838a71aed57e060f42452215cf4297c45b1c72ba3b4c706996c33da6dfd9e523ea72e7d4b4cf0bb5eea5f2de5cabbe6de8b90d28c42a8bb5f73da46cefcf430b59f2efdeb5f9f850e356e4b1a38529c7f71c232155c21072d1170512765b8dada35ea11e066c1cd646cc1e8f5b28a53bb066a7f7a7aa71847cb2afed0955bb50a1d05f57209b6df5430ecddd26abb70a0f54e8521962f3d021563a76cdaa8dcb27b4139142ac4086fce90ba8c24b344b027b59eb42695807e4b676953f9b9e7047bf9b81497b8773d0e0b48b863985aba7f2617e5524d9993a65b7af9e8aa44c769b79a6f9d6b1cb317abfc59d6a727024680a13e8e24e29d8395c8a6f82a217a360ce15f0cae4f20baaa25b24080811164cce3222f5803d52c8cb894a325d83804221853fc9327f3c37ac11f14b2436a92d63602a0c9e83752332159a28afea4866e295ea1c450fe56a85ea8c63a15f19ced3b9c828b0a808c69060ff8c5c194d4b0fc5690e53a46e2c3dba030fd0f20214155a6ed747a12ca8a352efbd574de1fbda089015d09147232559423b6943811f59d3e5a807e33fd38d5794e1e965876781370093639f0bbfb574684edb9d5f2d1ddef46a6a8db8da94f2a75afe6b4aea2e15f428708558eb45ddc22688b51a179107602803723dacdf67700404ee565f3148a8326da4c668fe1dc178101878c96b2c64414d73f361e719954bec5b0d6ab2a41bd17a03ac9b2f2d7a4145222fe2c3a5a88a221e1370757107016ce08401f44c60c9af64d9e39dc3b9f143fa55598b3c1f1f8f9c5087481b6ca2b8f7624f326aeb0e07802672391141f9100d7d552268c157c9b2f3449162ddb97b166b6bbe8f545e44049e3e19b6320dc0f6e83f42d6dee2fabd4a256f3fa2b16f128dcab8791f4398688264ca082cdf20eaba10a992c9c0041115d8c27e62f00da5511be3f5a2614566c0fa7b38421570ae492b17e04cb85c10c95114c68047e865b6cbe71bbd6381300ed41f557f7bf5a8f69bf4215e8af3cbfff67377f68d5f4f5fe649d45633840af2a1f7aaef3bbae7e7643b0bc2b8ddae5c710be8576139985a3edffce88707a6322fa14a52dad6df0088bd8ebbfa23086e4d52451f966a3b19a5f992fc538689d2e5f1d3c7bacf5820ebd590ff56651f44a84723d7ac1d069dab3e920b463dd47d2fb0c9e507deda802200788ba99a38ac928e8301392586401ee84d20c63834e20e41189f95465ac6858d6411a99240346f8e8fb1a2e42c00aac9b101eea5011de7b82c129d9b6556a4b0947375f77507e633df7cb589767bdc6a6323a1ce60fc5e081c1f23ef59e452c06bcdacf41c3a3ffd4398e6254f544d16840698edd2fd55f9cdb066041a533a4e42c2d2a0b22d9f3dcd6dd8a6efa0382cf31cfa43086207a8bd3ef0c7c3d8ab48c911b25e7ec0e8ed7e2f5a623509141c542d4ec9ac73c7c69b7aa0081b432d6a0c7080c43f3fd884888daa1493c306829062b649abfe9b07400ed4eaceef2ceb632290b4ea90c0285e799ea7b46845cb419b2d64b9463c1f91a5c141559c54982bf3cb6c620134a7d628ed3b282d943a3545f1dd9042d2f239d70078b61bf2799b29a8ce9db69d8f0d749deba1f87e60c2811f8436d2e295171c96326ead0c58407d753d8f80d9f6c2f2b02d74d2e67def26ad6cc1fc00ec6c8eed51bc5939e4d7e54612f97c9e9731ab7826d3dd3674592df2460a395a1745d6a9b09cb0b074f4023e419abf993745460660a55734c3f4929237f9e300707ece2511ea23f1e26b7a5e68726832cb40b9e497e107cc4452e5ad91fd6d2d60fee339043f0b6d29381ac4e5b6e126b8dacff4968ee060c1ecdb6ab6ca2b80e8a7fbb7a42db8060050cf7ac321e79c89495ea0a42b5aaed98999ceca2f4256d5c8dbde1a4cc91841d34f284c6c21d8597c4808273efb4f7dffa8a4dfae914a8e5f411bbe69bc083525c38cc32c2d0c708ca611118033c2e6b28c5d895138050000726c5a5f12358ddfc82a00818b2da248b2bd9f278db22730446f63ceac40d0f4923ae54a04249c828f180538e4ad0b421d6c0178ccd3311659781523642bfb2c9eb8003cf1cbc82d4183aeb04d8b2c296a7e97f40a6119cbb482f6263cc81959c9e0314ebc787caac283ce7daf3683079b7f23bbaed1b01c33247585e160c5e321c5bdaab94ed516cb3f14232147dc82a973824c10de2914c433c0349262e0c07fa4460881ab053ec22baf3edbefb7024857a96d389309bf5f2d6108d8c93d131c542009da9468a88f66b400d15a08f1c361bf9ce019fbfcf2cefb04e6782221003922c00986bfdfa01d9cd4c326859bb9431c59e14c9f119a70e004ac8e06f76f3ae4f6c76e8092c2211719d7a4acb9b1a93032f96c99dbb8665594f50936a74c38e5681bf83855c2ff78436d41529c44f4d413ea059486db65523759436c089c0da61b5e2d5130a8afe0e97e307434dffa621c93e462c75c656b2115dc65186987d81dd7b1a9867af9e5f29f90bf9856c2a3827a6be1d415ef37988750044e9fe974069e120f46dbc3970c97f11bf13bd6ccdda4f933c95d2b2a5341484214e5d233cc8edb10cc11a0f5476763e30db1897cfcc7c8e091a0d0ab7d32018c798248c485a26da0bddd99d85194a3fecab628de5fdce8e543f28a17d6e188dbbb3e259f8eda4419f6e43119ef0f9ee974f2d46ae4c47cbfdd09162af4da46b0fa1ee72833ebbc184714cbcbaac4a244a7e4bae666a09ba064f5ef40d6c15ecd6aa92615c5e725983b60e304cd4e882a59d905e06a9d2ccaf9838de35c1737b9e2f675518d88d5ab1c7982a962a22d72ca361b4a70201ba01f8cb01281c9d06fe3ca664074b71ab33c632f7385fbb8c677182ab759c070cdd3c7fc1b8decb5ba8bb96c46a290535aa4192a6fc4b4a9b4374833d5c57e56ca6a972a035f88efe7864af57957437baaa914584d1e3e769452f025d0309b04f2c9db2eaef7e056b2c1aacb31e6c0eb3fbb4645e718ed9e4ae6dba828005d4ffcb89b08111952c8309c275878e94bf8f0c4c0ff42b80072c58ae85b2ac7f2210b311350a3f95935e3870c112131d4f169382130bd17610fbc3c55c2a444ea8f1d0ed3161cf1deee533314fcf05053fedc7d4d0ae771f84e436597616f8729e2f3a5867aa90e4b1bb36ccf311d4733ec3e8d9d00f85c69963afc5f6fc03e2bc5eddf5178da7fdd57cfc03d250d91baee927e0760d292012fb7f193ccfa8ab233e53a87e6f595400168ddbf03c1dfc7d745f4d46ecbbb83e7dcb48153a8fe236fc090079c1f8fb0a7661ca9e7adb2e044c4082a27740740a34047ec7a8ba3cc0d76746f181d9982fba44f4c22c66c11e203936232ff82dce0a1250c917768591d2d820fc7c577460cc325b41dcb20cb4bd193db6fdb6287d58b02ebb65b021387953e874e8fa941c80ddf14e16dfbea6dbf771b70d0db3e8d15571d809080408424f1ab25b209887e44192710f887e1d2c6b07d8c99ddac590e2a72166f0bdde430b0363b7cb0fb8db00a1e497fc46cdff2429cce901829ffd93d7f82f8c74af5cb0508fd6ae832157bdbde2939d1a600cde106a4f96d10895404623d0ad115c5fa5316bdf828d316e5c6da92754446b5b106cec67ae7bb0013304236424baf11eabeedc15f118af649033d0be4af981d0e743d522656c003046e849d75e8e170c16a4903883eb4c4dd79a1953ea14204ef620bd7381a554d8195f35252467cedfbcde722b279addc469d0970f4087ec858333bbe1164dc90a0a509a6b03e142fc6020af852fd35d89adecdf502d11c214a4c8096acd913fcbc896567bd13d8e7b5ceb76e9bd826caa1cc8c3cae40f38037265ec5977638b09f623dfdb68d10960a9c7748c77f988bb81ab454ba31fa3fe0724c6c685f2bc227a4091e63ac7e7ab2408b271880b3051d3a966f9421ce265b49d2f20e24bf3e0f290e6fef0f8b473129ee58edf8f3bbe83037b5a61ff897068d94201d62e56289d29a15dccec8fe9e62989480899c47a5c8111c65383a0281f1e07f29a20f89a10bda2366904d7ccda8685918170737e2fcade104fcaae2c5929e1b13ff1a71222892e3279f3564042e78ecd8b3b5827d5a7272836b65e4701fb4b89a8a4748cb78562129b25503448235aa4f018a5819434c3b993126bdfb2432abfbf8205fe95d31153e1c9eae4b90839bbb4bd04623cb3871a1bd92d863086a92aca757cf42632c8b3b9aed793b8751a22a3193ec1c7d886a770329305f5b1f6cdfd193905670594ba69bf35e8abc8924144a40df2883b6be3a8e9fbb4d4068639e4a78eea5d4ee826b11278bcfa6d32ba6747ec52f166ef4ab608ae39b7e210735c1b25d18920219d9b194c935be0489fd118de5103944d1c8ed17cc9ce8d7161dfa5c3b2838d479fe4a9cc4a4796b01653ee303cc649ec83a03c60554a0f51d155267109bf48496e1cecf34765e5b9e88939b6f2eb7aa3a427cf416c47dcdcc872bf9114b6e9c7cdb438ed6baa2aa7ef4387137a805bd2520b62fd1e209e0f8497c3cfdc99380c97cebab7f5a13fddff8b65047972bc341cf3188dff55713aaff22346c30ba6e807d7e2caa41730fabd32f1b94ac584fa31926298a7a8c2cb10c646727505bcea9da9d4f0afb091bc1819ce1ae6fc348a63b459cb560e4cd9ac2029e6097d72fafc3cd625eaf2bbdd89c1a1e32a354e90857345c0e11b8deb3be82e4052fa814afed96e56003a0439663a8cac1d821aeaf3a918acdb877cb41f2de5131d1dccb1e1555b1fcf0305ad07917d0517903c4d2fd44078570d161eef0ee074f5a2bf6c9ea03a9def823b9c488d6501ba5e692e68690f7129672c0fc3056d5f8aca317e8e2ff146a1dc1e82b75d9a1aca0c1577e5e7288157781f25752fbca2c056684c2f5fa848beaa6cca3f6cf32f46b0882bb09dbe72f5fe09704ab7c5b7a8a791c36829fd674acb74a5d81f903072f1697f6198503ffc24ca8d4b62af2dba648cebc479eabccddce0958bc82cc780b878501fb5cb6ff9bfbbc5790057fcbb6e3734861133d1932f204c107d217777f289766de21e2785d2bb84a003b9cb8945536384e1152b3c0dff730871602023f1002eb0883d886f537f1484f975a34d543de2f56464deeba9b96dd1c111a9b58cb82b77465811b58595885110119d70cb7bb66f8ce15616c2d00a116f697082849d003a8f4fbbe88708fb68df95f2c2f860b6cf4fee837cf9ecd9e6787fde7bd1e110b60cac5da7ca4fcb808450c27fc03b9ac07f0652367c4be83e605cf8d105c874f4d786f0cde37ec70c235c771e17d89d1c6cbf9474145a7140858450a70f5bdae43f69789edff158e6af81d46428b10e799820e9fcb21a119fd284201f998f8f640eaf95319e27077e1a71e8d613bd5bf597e6f4d3fb900eddf8351bb2c61e50d28759b8b0e1690fbbfd070d2b05861e0a343a6212302dd84db192f79504a4c63ed9f75e3a6d90bb53bd0fd3f2833779e5d7438ccd001baabd4bfc4afbfdcecfccb6692150d4f90dcc0fbab7c20b6d1f61f70b962fa848eeeee7273851f0894da8408c3724205baf0833c6067676b2d65046041a7e8d021cb22c08d4d08facf9dc48d7debfce494ae895234f37d3db6678552c20c2e5e8740cac0daa123c692fbfd72852b83a9f37db668818026fa87b215523dcb32cdaaa9f4b14ae08026889d8323b3c90660c0175dbd1641f3200e7e1b458fe921c1399fe215c528ca40cd26f4552dd4e96c7df30d29dac42ce7232bedff8627103e478931ff10dd182dc034ac8a56aa9e1a24d28a18180ecf10986d566a91e37b9c3a70ea2ded378499d80f6049336197d4a01cef65ec0312f7e19fc1870fa074b979a5ef0acb4db1e65540df4baa53c521336d2074a1d67c8450de282298e4ff2c0336a43b5e61b117b0904efd79219157a8bdcc7b446579752b29a13d1ca800415d6504a7f353df3a17ce0334909e216d6e4a77b4b5bab5e7c4c5127831a449e11e294af1076f3e0fc3ec3b232da8b131f26ea3a1a0991c784e86a5cd9331eea970b34a61f883ebf88d26f0a3d0e8475e661ab0ad775a21b0dc2466157f780694bb7421501bc2b451e895b756fd4f586ec43a9f9ad8f3ae2c5350df03cbccaa0acb8877f2a07d05043b440a3d4039e3612de27f32b950385d1ebe9a0899c6474360851875e89108ae0566536b7a2da7d1e904d2bdc8f67ca2ad4bf29dde88fcbe6b0f64257169f10b55d29d1fd18acbc5935ef9a567d35600eb6154e8b0df6fed195882a573caaf64af90555cea95915bcee7414042edd4d517c31cebc95d0bd028a1f0316604ba0f3691e227a2c0a138c739c9f1f127a05952d5f793ff32c84fc2e43e3507ffb058b10bc61d5716365d588aede4a6263b3617318d1491964c68a03f03e2dff40732a0c6b87e7168fbd07db19e2204053c4ab14015b19953398ae441a84458eb658c1c64a2197c3f3cdc649dc44162aa64788e780da6df91124502ed61706344a746b9762cc2011c28dca9a36c17e5665770fd2b78ae58f32b7b540e63b017ced77bc8c4b0b13d3c2031df4cc92c3a6391afc0a2c0c9c9befdff947f3bddd424e56a0c1b6f1c8bb1350416a96ecab171101c8671d4524b05f08960a6389c1ae281dead4ff0a1d5afab5f487087028f1950feccc4fc21153195cf300ecf24af370b42fb8d45b44874b02c7b3b5f0039debb4cb218c6dbb733b4b3007e450837a40686ec2b7ef17cff0d4e50e693ff84c2ce917c1162cd375b8e6441d3befbfe93808e62dda7bbd163478e18e750cf57f20029f6e88de2d1f2d256143dabb2e5f55a65256086a3c16cb21e3b3d520602dd55ccfca51d9b66bba3dc5055356c4b8783506aa4551a8531f99873c8192815a72549be032a2b1b4a924b866fa0a4317732811d57b024c580a37ef4515c5f1e25307b0cadbcf776be1116df50bb9bedb988e7cd31840570b716e44f1e7a9f80a6c92821ad734161dfd55118c976c9ccbf281cc824108b3eb48b11eae7078eae31f4195a1212ed9a111c1e503a9ddb2c1a2902e4996060b74bbba680b34d39044f047c73af4a47c50e6f4774dd28a8cd2208664d54cb6f5f6b6f643f549dd4076eb68e5a952c4212c57664b6777ce3cba24e5dcbb663cbed24bd6c727cb640efb29b20e9832d939007015745d17c0c2147214ad7222f1ac660a294a55151745545c9f6d51ebc62952c0e44ad730dd73e44ec45be143c037e6e7185b6407129ccd1cf2282b4e3ab75347f2bef6897fab24b61926c80ec3652b87ce8eebaab442d466e307b2b6664a9293943881bd348f10050023d4250931d41b13824fc551ef61b0f80ab097b160cab659bf7430fe17d8041c872b92b812df1d5fda2f8f8d303e4b03809b784e3e53f0f102bf224d51df3689043d9f9f3f567e0a4143c36d765f72489b17aedfb69578ae3a4d9b4c26c0a8ee77e42704f431ddcf95f92ed861807f4f71ab0b6d10b779a68d082c67ae8cdaf1d603df44b8d0e861de9d93040c60b7c039c5d4f2558649649ed3d541f4b1fc6cbe0e1e35b3c6558e92ffad0f1bee5b77f8776bccbbcbe34bf6a922bb4f5431f3fefe7a1d7600041ba542a08cdc5acc4ba12a6e4b7c5823f641af45e09274447d94c0d71beea10a6211e181d9901493cb6a1ae08bb506c1fe4a1faf41e293baf374e8d4dd87e7d3daade2a3138602b86a317a0311e4f3826bf2cb9e76e41f11d79fe3006790f69d95eacce5bf9704608399887d24d29da806873160a3f9df6a50bbef983f7dd8676c30fbfd8797e4df71f00b814234ec59d651414b30af8f89ed027dacc8c518a15f2921f8db2a35e8f28f72e6d4ccea7af2c8edb0e5df0fc45d0de688f193654c0549be408726fb767d2f79ba00584cc9aba16b7ead9c152b3b2eec72582ad6756593d576e1689f50150dfb1d58d16cc26db7dc3348e293121f842eea19dc20f88768271c0fcc18a78192fc1acc4f26ea4c17c498a0bc5fa99a1a116534432894575367c3a52012c474c9b09d67a0f9d0e2a7868aab254e2cb25f6bd932599432da822dc982582352bd1e0e0184e7b2386b50680f73c25a2d3dc797633ab6dd5c1c5758ca9a647c898682c88c908e013a686abfb761b14bfb4034fa022d08fce958b682b10c8b7e72ae0d3df9cd01240938c87bf3d6ca2be25be5a821f8f8c4d562d0807efb442acec2068e56cff42508d457a791539d1a3b0236f76b431e817159c7f98340659805fa30b2f2d23be316b9780ac2cc1f5af3983c983d3ce777ca4116e6c700bbbe6ae7f59ccc8f0a0c3b21df447a6ed942bfbc3547e39e89af3b2c33083b2f571ee7947ad848df5f80181f966252cca7781dfdeaea42ccf405eda804d1dc25f8dce3e6772ea2a375e3d01245a04aa105f9e59309ad9a8036d154b114b106eb1a97b12ef2231399a9348caa3963b4fd0ac14f3fc5047224ebb3492bae7e23e7bd7f5467fff79108663360a5fc6c84b329d874c7abb780abe8dc25e3284c71e0b1e10fb643791726f29654a32d209f609bc09b216a4c3f1bc1a55504a44ee40b8ab5145e581c1499ba39eac242d70ba6a078eb267a308cccd9594b9028f53c692bb065a0eb90e17db6fb97fe278a9fe0d67d439bf861b9e5cec30c74c7989544301327d2a4bf5e95b69023d0debfdea5ccdfe71ca9a4a6bcaa8c04e3381be2b0fefc32324657051eae0b2056ff6e6ecf15190861730fd9ac901fa34bc806f0742f943df73db1c71f473ad12d07bf573034b2c16bb210b97cb55ddd539cda9788de51be31b06a08295fb2a5f6f49c19ae97916101c83e4fa171c43d9e3f90f8c196b282494677823ec81a374f3444d1d737431358df4d2d4b6f94993cb92fd19d6e7c1440d618f393d354df4868fea77c825a7d2b2ace128e494e7c70220db801c80699d9f1bb280633159acba5cae1bb2e855afbc575ca73ad5368c72174ed99402ce5912f088b30e176bb2216e9aac17b94615784e6b310e85e64a9cab29cbdd3a5cec7e1ffd93ab81478f5011b89f94bbd61b62f6cfbc2901853fc8194a87862101a4e09c8f0b000879a360d2014cb2696ed04813a6819bb8417277efcc792d9b4e16192b3c92328944c23730be412219c1ebd99c3d67cfd93d27e98628a51594c6a801ce411243a1502894838e88ecef4d50ce524b111c65f93e830b7894b29913a4c9478f7464effeb1b7206956c86e3fb9990043f66f26fa493bd15240404a40969ae6f4b276caa41b377816a54c4b496ba552d6a7769a6690438831ab4f163ac35b023ceac84e8024740e57e36a5c8dab71357fcee6cfddfcbbd4901c5bbfe4c81f6663366742ca1881c79e31319bcd66dca9694612878edc3e9bd5f6acd074dadb896c18e5fe1a55b87bad3020059281204286b808c37d76846a5481bb6b75c14db342c2723149b796b8e80d8e72fc9032fe2f57e8989e6c6712b88619f56b7051ceb21039abf55f478dbf60e6c8110ba449760cb2bb62b0e13147b52d43c0fedd777845c8e94ab5f66905f2a37358a5fbacdc47bcfe12bf495a9d53058f38df70d185842117330c97e567470d6778c3456baded9cf6aac86c08c87247f6e2b23fd542d23c5648173b59cb0059ee4861257b54dd04417927365f93e6a2ff92aeebc2c9e466a54acd454fe2a24f96c8f225b9c64543e09b8b6c606042910d8d1b1ae14de8673ccc1741d5f822d324b2a1f136de46788343918d0d1be18d8c173d8d2f822a82f2af01da27704b71f1c5631ae0c380a1189dcdd0825a8b7148b482477afb3c0c2f7dbcd4c2c44b2e58583e9f170fe3fdc5bb8edc303fca40e3c7518d1f411b3fcac8317e3ecd73b45fc613c67b5ed36423acf1a2f182f1a85ea38edcbda7922f3f862e48ede7d8e5c57720fe06657e0147193b5eb8c0708101d22c3f2e5fe32e304a60c21f74a0745ed059f5fc8c2ad02b3535e30af80558539509585d20bd02022b0e0b58554666d3138eb555c351c4371c675f14f0489764ba44cee8c899fbfe34076df2cc70b43dc0410811218a2cd22478f59b3b2a058394f1f7a8a68986a147fe5e9370cda0e502560a563588ec5f6f78f4bc3c2f2f71210f3dd8cc430fd88997ea1529030689c3ff825b5019e4d15933ac80e7cfe802f60821659284a91a1e9de5514921458a9d29a4d01b934c6fd3e451518193e58e1454f2e8ac9d29a864674d13bd527176a890ed5011836d21491ee9cd4d14e50fdb0292ec4faf78a99ba0e71628b0dfb8e8b7efb7b74081476925f77b39c277c113b47fe7c9c5d66104feeedaefecedba2e033256c37dd7949cf1d30ccdfeb2658f033d4f418f114f8e8bdcdd41f7d9113a82dee44c02b27f4d35a00922a88868c20924720c90441249dcc00744702108940dbe13858de2e006f4e625f9b181fe0e9d27c5819cf1ac3c49a48c8ae54f6f206766e81142cadcf0c91e15165894c21e2164a9aec0082c2ab2bf4725230978a43755e9bed39b089c01b210015795cb006b8adee6c89fc6123c3a8bde2abd799e1fe96df4a876742a8f7fa5e0e9e4efcdcf02eed7e0a2d7c400e7a85284179a9420082d685004085cc0b9b5c080022c7650fe176c2939373ef28f9293715e2e3271b9c8c3478e71a8c65cc662b2b6fcd081949aaa2afffec870011ee96d9a6a623525ab39a918bdd1db8b0f400b523ef69deffbf01578fc28a5d44a39e33b6414018f9fff778023fbbf6769c545af554aeff91d8a2eb690509e5f5d5cac21dda1c7222d6385a5532778d24bd3eb6fd6b00273df3d49caccb077e82db20d69c7e0f39122bbb37a4aefb091d8c8a935aae8b057dd7d447637f77389479be2a27bf43d9b0576017612560c23f0f86965daedd39a261fb216feb492b05cecee295370fae54b96a84088cd02771417632ec6a2b8e8dd1deb1d0f54a30aeca2d680021e3fb04f142fd9ecff895939d2b5b6d2d2357ca4e6403f56be295e14cfc5cae7ef3475cd470ecb6e2d2b5f61a5921c59b2722d79b99c4489128b4d91d1a47869742b53bc34fa8d6664fcb4aae458f1d238c779a4c504892a309c9d28332954a2d8a864973b3548922b2b499733e5b3e4d3fab83e4c3e2f2f71397079fe3452ab1519155aad4a0e740a6b2746a54489c2c4f5729283e739bfd26f1b96641c9553fad9e10945f386a25985e2405d766ac0248759eed0e00ab93fb3cafc1fae2ad35565863f5c3d8576c59496302069242c8136a1a5c819da3d7dba605a23cfc7c02aca4422659e90a5290329fa5b1bc23e62e97fc0e6f93473d538fcfbc7b96a623ab159604f1e75c4c8bfe1e277f7fbf05a7a0f17fb270678d09eed9092d620658d945352a9e3434d73b30e39e3b6e5a24db26a9a8fc4d1efa022f2fcdc70f38c41093c5a16f861499909e620d9964d6297d8ce71d1df4df35a96bf65f5db56763fcdb26559d6472633c4d4028f9c14d95f76f2e5931efc69fa3147fe249228fe874218f74f93a344f56f7eb83198a174440ba02548824e0a35e2f14924eff00545cecc9067bad012f2133904d2d44da000d1b119f6ab09786c26729a0939d3d903135c406f91e90238fa5fb5f23aa148ae7150ca386d9ae9476bc531f40737299dfd4fbf079bc304000e7f9ac304a5e8997e7d29e330ff2f584ce63fcaeeee52c665c0008ffdead7ab5fd9fbe5a5717250f0a47c2b1f4b5311229038116536440fa2c031d1e084181550a0b302283329a57c59e32d4440c2122a22b4d080d27feac72e21ccf2cd027e40893ddf4fea3fb96e2e5bee8fc938ecb89264b9a3c41453963b4a54c9ef257147899d2c2a1125f7c7dc707cafbee78d40b2157395a71a8ef6bbb6167708488dd3f9367842dc001879d66a71b6b7d2e99f1d939bff46c0230d615c9421fab98532e52395704507aa27e8000c4f4001aadf234d0270b2e409267a000515b098d43f09b87ff608da2ce0fe9720db5e5be08da3fbb44d704c7f3e7db69c95c5bfc61b8491c79a71678f73ae2557c119b24d30e5dcb5e0591fcc7022cf39c30929433ec0fd330a5ab9af761efbd951dfbaad9d87e3767cb614b0672de338ae725dd7d95a2bf793fea4efb50d9ceb450b7874f9649fef1f37e2e25cad6cc366a8e4f318cb649ebf0002f624bb95391b0c7bb8f8d25b0ecab3b58434b56c8e5ccee890c1644d72900dc891b715596621e3d19d9a25ee4bdd930200c50063b73804cc60a3807ab2d4a734cc91c355ad56ab956ab95eb069eada1c794d5553d554b9a7a96b2ae81a0cba966a79cb5b1e228542a456ab837d36af613ac7945248391fa4913dcfc27a9e3aaee36a571382f1a80985a8e72dccc748eb22950d130ac96b05f6247bc85a8c4321717e689a1c15928564a1d0952c565043add734cd90b9dcaa96aa455be208f0f728a53332cf252edf41d2b40d240eff570a075ac8fedd8202eef7f1891cc07d7d1db8b0da399228c02d9701aa462a193ea1296bb55a304f149de2114bc552b11983359d52ca27238cade5d1a9d8951bb39b478c07f8466042f565e8869b87c41a6681c0427739f0b917a58c934623d934c0f48fc09462d60545361d0e25082185eabe08aa3f0f70940ded1ccd510c2c603b47610d0c06b3d42b1656621992ae6f97cbe5b9eb46062250373f2000c5fd0f0140d5bfe9250421c90f50373f745572505ca833ff878ea207a81aeab4cbd52f1a725026078583b95eb0980c16493407ae092893834229e7794d7a4d5a9d83598e8be27ac1626da178ae26d2343343eea6c04f40e1607246ce7002eef03a01df3c92dac5c15c742ec64571f143c3b021524605d2b483d4828371b069aa61c33cb9735c2e97cbe58ab95c9c0c744bc92db8da3f2aad431b17a5487ab6241cedc35a8c4321998f14ed42ad945692cbc7c2041e49d50526e6c6b49609343401a4e0a69476cbdca18c15c6186379049712d9a90d1260f76fb4a123f011d2d4b0397239a343ceb0402e1bfc9032139023ff9a142028db2bf0286dab297846d2563131b75ead564356ac966b369bdd66d3b4831cf96b6181988543a48c0c23f0e82b5f598b7128e42b5f75ae8b855a1d20c30c37fb652cc19fc7faa43ea94e5429aa12b2e765c8ce8563023a28d9134a69a528561d2b76c0a3861a1d1f3255c0a74c012b5004e6e1a2ff94d08bad3a59ab43ad843da9954e2965051d459233359fb2bf0c2d2d32f228764f7462278aa2288a1408558aff7f9b0516ddfc793903ca3e631c81c70aabb03a84fa84a4c9d138627286966a11c61a45ce4c4fd6ee96b26bb591c3bd9cf11a1773944eaf5a6b6a38fde9743a9d4ea75395754cd040ea1093fc3f3ba9bbbb3f5f0dd7252e253eaffb9eab0fd8f8e82165d7e5c82ce7286701b2011202d26479344d8c8dd998bd3e593c204b95136b105fd4c923168b8143401f362eb68f6a5226a6697290386a905288a28cc2a35d57051c8b3342d52a2bdd2a95ab542a15cccc9067dcc2e0886c83ecdf2a1aee7094b249644e3a67112244dce98d94f19737aa9c55371129e372c639ae7b761721d24466f79ce18dbdc959b16a54516b7873d32a95aa552a15c7f29e2882470d353a3e347d72a11749a71e353538c70496119707cf7900e51e6c9e200fe803a4610f17270f175dced11c59d10312045204d264e76848d3c4340e184d8218ae76dc14afc82e03067894b621369bad07576d8cb531b621ac19c0a4445be4c89123c7ec9e3946fa8db411d52a4957eea59673a3f188c5704ecbd42e67d24956ab20467fa965ff97dacb14e44cbf27059907cf00c85df8cdbe2528c11ccc09cc55d0b01c33ec28359e8045369115d1cd092c4ac75a4af544b930eba4f3249f9622b2896cd3d4b116d9b24ff1285efa66df142a3523b61c2b5e1a3dc977a5f54939f9a8bc34bed43e46609f23b1cfeaa355f96e1f9c4fce91ecff41d2b5c2bcd4475c7ca9f2527bb1bd5879b9e54073bf3bf1d2121793d2914aa9aaa9d862b84ff74e4771a0f96325168bc99638f152bb116476f1a0c92bf6032698e468da5dad7599181b4962031d49e3b1c6e17266be43a144f6c641f6ef969c9939b2a473ce3967addd72aa7234f8d15cfca6786c42f1cd5a6652c66f88b429e24725fc6652c63f6cd5376b93ef78ec9b7db38ff6d53edb37937db36f06930ab3b06121bbdfac0f73e4a03c1fc66681439d0f1346ce80ba1c239472392177c445a71d37a563d231991d93ecdfdd8b764cbac94de1681c15ae368e919c2396c977b069e2a670533ad7d8e158193bd7cb4bee444af6e7ae50c9d7765c27a59b75b29dacea8c7439dd112f49186b398ea3f5142fb54d828446a3d51640b928ceed644e4a76968b5c92ec73b288709821e7fd2a3b6704c80b2960239d029b72ab91336284cb39124423912727c49c819a20d44522679274eb0849c305d138fc67eccf6f91349c101287ff9c72c62504e32519481a2efc80cd9e69d21967e6beb2e45ae52c88a6e9648dc33f88dc94ca4b2b85c97ed3c92a15f5e6015d6e5cc4f1a19b75525cf418aef8f7cd1b524a4a2595524a49a9a4a1e8625349aba42fc09d6c9a3a9c0e67b68a66cadde0b4d9c96647c54bb35b19813161d245894d4a67b4344a1a122fad3a5697a46bc57438d9bf5b4dff0e678a8a498eac83c15446bc34c264da46aeec2431925d8a97665e9ad2d168c76456a566f39ec245e1605cccb91d2fc9bc44290d7fb8a474730e9122a4eb9098425d5228a5d425454af6beafbb296ddadd949e9a368c9cf1ecae299d944ef6d2834e96fd7df8113f178621e9e643678b61045ec5c2ccb3e23fa629877b8e999743ce80663fe4cc4c8691fd830cf1619a3afb554affd800ca39a0e0fe318797fdb343e400fbf775b07f431dbab773b44311165c61d540f52406452089401035d8020a5848f2031aa0fcbdfbb27ef73a707f4322967b22ddd79728afbf4ed4f66deadbefb7dcdf7412e4401175ff012488ff902fcb29673f66b3d90fdbf41b84273d9013917d622baee32938ca3bd9a35f8d2a70f7624dda06f0048f7dcb33dd53f2108f7edff76101618ebb97a5861570d13f035206e38c7d18f27d5840b8c5450f9b205e87cc9197662ff1917b0ee9a921a9972970178e720766bf6f7314459638204528f57f66febdc1422d1c90251729e25a6badb5d65a6badb5d6b006889b7074ec7f78b44f5a3f0ca0b6290f176f8e619c44755525bae99452868ec06328246766f8422f926a0264a23fe953a9141250fe3a6a76a248c90e44a52d65154a382ef4e46f09780c7da15028140a855c42a082fbc7cf4ee69c73d6c9d5397f01538705536c919574df85291dfa3fe8f48b6e134d4124d00cf624fbcb1a5584a02c9e46d224438e5c26fb5318501558ba98edb4328a8047f186e8a2288aa208abb46a45f697a8b62e63427090a61839f219d94413392463313131481c261ab27f0c0f7f1fae1d505182222ca1092cb028928216942734210320924c210072e47fbb3106c7648f91323199c7b416e3588c078d43c2d66f510afbcbb94493dca094a8063daf24090ca05c00ff02c81969fdbfd085239800092b728a80722a68f0842756b084178e09dcbd41cffdd921a308729643440e32471e649aacd76e18e50e874c5993d1962b1df30de362ff70d17dd4fe650f4286e001a4c0d4838fdc24c447fe57820881b997a3ace19e25b863447c9aa39613cd9da0048fce8b93e1042695d0ca3d78064120d0cb19364c924cc0a168fe29c708caa0183662b8a0dc201920d2a986076d81a60706293f0b430a2ced179c7832a63f1b293cdaf003337b8cb4e3979b40218f36b7bc0037b12293a048c24d014dc611b16ae29393b3240611b19d26ac8f885a8f262b8b4334db3dfb7b55c1238730842c7b14b0cd72e70585972c775841a839e8e62c0d35ed38ae524a029f9402512bad16003394767751b4f191ffe8c2591b5ac33ada4ab94a63e0edb81399ba57e9f4e34e29bd620afeb201d4b46972273ea24f298dc20e9dbd89947949991ba69fa239b24c61b8e6b16938e0dc3c78a048f6b7a148eed754893ca45cd8bd923a3c4fbfb590a68641a8855e4fd84de488fea5b4a7c8f4fb2565e8cb5ff8c790534a5df4d2bce1261be6d07191f02414b99f5a3cbb4956e4fe1ca72320c16369bc74b2db2e32db9f589136a64fba22aae07d764c96232426a2e41866ad575e60041643e0029421a4284d4185c3ca410cbc20f3182819c1c5fe28c82e450fa6647f981d26709065f0c00aee1edbc1e2ca6a0924d090a024154dc0108620bc9c8024071d0d74821004da4921fb4bc1031e9cb23f37e75492e4d34038a51d0593198c64d9f16c01e1a316976aec4007d9dfbbb14f2979dac04a9e73869f4b8f96ee3071c246761623d8f28ebfa49ca34b29a5acefe00e3f64291de62275272ed2f9d29920673ca43e9032b46966485f8d833e85a2a239744559b4455db3355bd3355f13366353e637fa9a266fcd117d6a1de350e87f14b3aa9ba62ea4af39a226bae3e1682dbd1113435f3117297dddbc75f394ab3cc757ce8ac564335acdd62b56b75c2f98ac0519e243a7e8e954c3c3c70f20018082473bcb74ce68b3366df3468ba028ec298a661b12b05cdf6bee9ff8133903fe90321548e3a8534e39e59453cae7b8e93e398e3a71b1d2282bd7972b5aaef14f85a5d3fc8ace750ab96691ab16b9bee75fd350f93f9a86864e6b1cf5eb5340ce74e84f9032f50300058f4ecb920aaabed7a6a908aabedb2ace4f15a6cd51fd6ac547f56bbd1113932b0c4cae361b4cc0d47677c84367f98efdf35c0806b871522e561f86745d5398b7047dcf0609707d6a336d1937c09246814757b9a4133750574b1a335cbf5f3fa44c0f81a5ad02bbca9790261f72d4dfc4a1f813dea1ade11a4eae882748fff163bc4615b8ad94f26ba6fcaea79a1a78f8b0a900900c0ca9f4717744f627d99cfc2f5cf851ce897bd4a753147e9c3873a0483ad5cc1005814798ec3bdc20330c0d29e32d69a5b4526a8306cd616e41a3d21ac2d00923924e352ac0300e238aa228762c4fd64001964ea4ae3891b292fd636e387139693da03ef73e766877011ebf86a902d850fd3e5e249de6fc41cdfd073449818f0f2e0c2f924e5524f99ffcfd33165f12aee33acf735cd7bdb51cd77121f5e857e90d3db8b31e12632931c6249228fe4f1ed32451b21d85319e1877ab89e8171851abdf50844715b873ffcd01fe3c621c7a3164828ed926ccd416d8b379cb76b3c5405140ef5f6ddeb2dd6cfe8ad9eead8f1e296af316ab07ebc6ce0dce04b2f161b3d3a374dfc6c74e0f1b9b95bef595972c6c24929c3d95a672eb0d75b6ee9533944fcf23e9c7f4aea7eff53ced79a8b7bce5a696f7ffe1a5b1a9b82dbbbffbbbcde79cd3845d4ab780171492011ff937696cdb4e1052d93f2e6f13dd01428ae563f97ca0e8e2bde18f2629ea85a24b457483d41b2a10766e93d4bdf73649a5545246669b1e3e66cfd9f88adef2d634b5cdd6b279cbe6ad4f87c7715c65e576c5c594e3b8caf61e699ada6623d948beb20db1e1d08bde9aa98fc30cc1714dfac891bb4a95b362a9e48c91ef5bedd0c08ae338ee7f9a82c891bfeda60129935558a8542a95aaa3b994b81e209e5a0568adf487e7c74e91c0042748354d8e150b7274cade1c2c7387222965eccbec13b8e234855316a71fb3542a954a857284422e2866e779dd8131747e57ae60979ea727b90886bc24e7cb0cbab8f87d20ccc7b002a6d6560a8eb2b5b5051ecb0816ef8628b6682d57abab5cf9542bb5456bb95a9cedacd88eb3419678356dad32406505e6e854a73acef4b1e5f1be102af33300925cbc37bce1a2388350c980907bef1217932ce051523909b9f7b25c1612add5a2b5682d5a8bd6a2b56831ff23feb20bbed7e586a1ef74ca2e264e602e262f27b096f8e556ebd43ac9643eaca5d1582d3a9054ce392bf73bcc4cb949a9fc82cb077af6e637ebac35f0dcbf058dc441de466098e6b18d701c5705a621fb677f1ae4cc05a48c3461408efcb5504216a8d00f84861deb2892a6837b938cca92a166855839ac91344c1a6a38a341ca00a1d168341a105927044ed6afa2959194214bf6412962193ac0230963397d5005762bb2a5bc4dc037bb9f6818c1a34b0e61a10139d3d95da48cffe97482424ab722fb7b24196a14d77de72d44c02329574a2bc9e59321011866a7490639f28f418a91d1c2f4a767d26956a1db5a8c4321e9213ff990d3e4281f4337b03d3501d31da44cdf1c0eb9827dec151217c944f6e75852ca2127f0e422177ab1aab61045511445519c266b310e85fe45b1c51669224d143d51a4e98cbd849113c8b2906f085be668345aad266766c8aacf5bd56aabae754dca4cc163cfbe50d45e286a5bb3ddba26926ed45ea686c1aa5fab7ead5e40aef58436f477dd6cd1b4a17843acd96e403a53c91c076476b2ebe47c8e9b0418e1c0719c18e3a228d66cb795afa0ac3e22a5d56ab55aad56aba5f08b4bd52dae6766b17988392709f36a4186dcf717e5e069ba21111d22df8f69fa844486786e5d17620d9926223a369eef8978de7b51bfcdf7d73fa20e1df0fd7d1d3ea983170eb950604f28eaf7dc50d4a1c8c77d996d3ce10d098043a4113b8eb31d5783d46ab55a90d495c18c2bd8e65822b2cbd0e974b2f69f047a1afa150f714ed3e428693b9da4edc6719a1c61a17ef886b0a1746678bbc99919f28cffcc6929f3c54dd794cdd8a4021e6172f20c2c3a1bdeff74018f7e9b267f75a1fd3eeff42665fc354d33f59f70145f377cbdbcfacdc412de70910978f42af946b609e0329dd25a39395b69a48c7f022a9700ce3ed7d6862729e33a864c9344c97c6fa71a1d2efaaa6f520606b755df7cd537964c41cff23a80421692f8dff5f8b9a10df3ad057ba859d45fd381a20e4f9daf0b7f9ea9a99a397d034f52c66f7ef39bdf96f8cd6f36c7c50b9ff6c051f6faf35cbebfc2ba4229cb0c95b0bc3fc751ae57cd9ab44e8a97a47d99afedaee781df7bb2aea48c7fff98c1e48ca7feec0f03bcefc302ea3eec7d7eb46e27ad94a99e951e7c60f91a498cfdf5e7cfaf808b40eaff70d13da11039cb1dc294a60b90f7177df62304e262ad5fd89eaeefa2882be0a915b437251f1899cc5a8c43a1956cd52b97fa5e7dafbb6b75a161972878cbc539bab60b569a248a7e877300b3820cf624fb73211bac5a28ce2ccde0c913b2139e4409c1c095e7cf9fb7285c480110495891021b0891c2882594b0851630c9e992dc80091fca0421bc20bb1070e0842654d9a10021fbb7d8eda3fed313aa44d1852f04e1044274482cf9c113206021092cd8400a4ed018021e5d44dcc414d9a378a6f0a4276fb0dc89c248fe647ba4fc6e4d9385cdd1fd97972fa5add99aed7653577573e44c63218405b6427e03a469b67a34cddc691cf7e58c42ca5c6973acc538147a5948c22e4d4d9b334d31febe5d59966d5997bdb44d2e2e5e28a0e56364f916669bc892b3a478df89e72cccc294c81796ef23e124dff728a8047fe3967701718210512daf0aa1f0c700453e62d0d4345d9513a127b2ab5925acc5d805c61741c588f12edfef22ea175dfe06c687eec3086f11d49f422fe31f705fc6fdd0690cb9f8f1545112f5a2ffc71cdd2f828a018a3ae4e10112e5638eeebb84373ce6e87e8cb008ea5d66f9c2084536303ec6c308753a50bf3fc64b94c813fac081f12e0f23bcd1f1e112de340a7fcb1741bd8012e5314091cdcbc7f05e5edee55f607ca360fccba3628012e54aa4a9abcdd17d276cea49be1d135ead62c546ab5171f14a9acef2bb69adb41bec6c9e678997c6fbf278ded3f292bbc9062d954a01e1c1b9ef51b94974df73e4be07c97d0fcb936449beef7159eebe2e6c9a7074680cb05197e625b963042a5eb2915fc022a8f9d99112d9c478978ff12ea1488746b97ca35c3e868f978f011641c508bf9c39ba598443f8ca3df80bf8ade6e8dedbe7862e072102bf78177f5f8422cfbff27d17effa2228d005c812796e5af9be8b50e409450e70f12fdee3093d397374ff45e849cdd1fd9b7b6f2e5e897a411f9432f45b1e3f0c172ea1c8c6c5bff817948e77354db7891cdd87629f58dd97ebbeee0d89978490a64b9ba3fb335ac899f9f7af179aa6fbfbf7bee7bee7b2fc058d32a435e2e29d14ac99ebb2cdf6bbb469bab74bcbf7bd0eeca8d038ee775ac8d17d2b842a0391ef8f9e9405bb2a2e7a2c1f057d1cfe6ccbe7f95e7c2c2e1fce9e8b172f1ab7bc007d2e2d1f8c970f0f99db45ba5c16cf7b5e7eba2a2edeee2665ee7bc0cee6e27d0eecacb838c36e0a52e6865d4dcadc16d0eb5e53e6b7893d21ef625efafe7ef79aa6fbb2f7354d93e5b957beff013d2d17ef77b569ea72ee6deeb8c963c4bb5abe2ce168f38d98ee95ef7b9eefb3a3a7b878ffc65ad63ba327cbef888bf791ac5cbc74aa5cbcdee8f4bbce1c0a4e232edebfafa6e970caae0fe48c87938a8e898bf73d1823e0b15be5dbe54899fb353ea99033d70b52a669eead3d2a17efc36c01d7a734bfddb738e4f9f99aad697296c53f86666ba769fcd638a29033ee0529439b2687224739d3e491cdd1fd298857fc124253e027f27d8e36cb9796efdb70c4358f2ddfe7c2f14e9179e8f2786ff7e65e9033370c52e6fe7dbf35cdbdd238eedf1735c1f64e2955a678e9febc3f6793e6a59bb5f77a38cad8fdaed2b6505bf19287f70300e5feebfeb51efe429e6701ddd734b91239ba0fb23170887be5fbdc12b2e42c17ef5f24a4787fe498e4fb9c6a9a72a649a403fe966f0989b43cfe15c7e25ad344517d6f7274fffe5f7bb790262a9b31e4d1592ec218461b31401e066f5c3c9111e07711baf89697a81bfc2e423c47fd38b47324daa1ef4b54e33802eedf18e0cff7efedbe2775dfa3baefc9a9b3fb7546abd96e5caab66acbf582c564377aaba9aaaa39755559018072ef8ff796ef3bcbdda9ec3e65b5a6a908ea3e068ba0a8eb4ad47d0a9b268aba4f6372a6f3fda8cc9dcaf27daf3f975496eeb456eaee7287d5c1a367ff589dd30861efdff0de7befbd9ee7794fb00bb26b61082cb6101b020aa2408201b8a0039b1528a8c10e8ef0b6f0826e0416afb85984239ec0a117ab097011b40638f473098cc43dcff33cd204c1f94e5069010efd34c069863d92b2277a9ee779eec9861d17d6908672ee248102ef147e51e2c9183761f93ce44cbffd4acaf2e58b241cfca52c028c0829593e922bb2fc1ed2d42690230f3b49d3348ec4e1ef1d84671624301039e392a62706e4cc0c9ba643297190802509dcb3669a1a67b691d8ec23486aa649a264990452c69724712df1648c7725cd072cf79efcf940d8f2630c3a548ffa7e049924e8e5df198f9c69f9df34fef2012015d0f2f23da980efa5686d06818d725009e871f72da02a8402853e78b400e79607fd28e64ea270cb833aef4060e8a21ce49ac0aa650cc2088102818efa803056f010179b86a6b212efadf70d2a51615408e549d4470bddebf0eec51b6ef2befb1c6ef27cf7b4ee3b3114e2e3c71c75d3b917783cf460b3fd5a5de4a1c8019eff5e078ff79f47c47b4f28f2700754fdfa627d52fd930f1cef6ff82d7bb22871d40d6bdcd6d082e5421937c01e7a89b244445c2863ea6c01d7b5fb5cf6719ce8a3efab91e58e0da24c264081e5c35ce1207fcf3bf79768dccc4b20117d17fffde7bdd7c1c57ff2bd2236feded3f7c21b9d2fbcb92932c40710590d8d266536de7f2def3d0d6d6e5e7ca088fe07fa7baf03cb7f2191ef593e647922221a8e9c0dd5f224ecfd8d8e0de85bc21b3c4733bc91e17fef98e525ca0b45f47b1dbcff42223a362ffe7b222defbd0e1f9117ff3d05f0835e445b40110d3f10c67ba0cbbf7c1154db6f5797b2e14db3725cac54e7bc80dccce54bc560857ec61741c9788972f9b15dfd2dde7fdedbbcf897f0a65f7c4b78a363d322fbc29b9b8679304ff6f58b77011b0445f5fb5dbc83a21a8a1ce0ff791dfa5ddcf7ffd81a17d19ab0c40a1758a9148ae55d5ec7c6c57ffe13dee8881cf07996b7f9d48fcde75dc21b4f85cecaf9fce8a98fe7b0668a664bd1dc3603e4648021300628e788f3686efb7e9ca997f7bfd1b1f9bc8bbfd1b181f19fbf51d180727996f046c786e55fc29b9767b171f12ce1cd4be82e4277854134ca05d6f212d5f2a3bb5a4251fd22a819ecc54bd48b5054436ee6b2cf37eaa363d3f2e26d58e8dfcc6771d9b0bc8bf066e6b084d3c562cdd17c0f64793fce1c2f24a263f3bdf712354e16912f14d93c083fe87178a323f2e1fd0324ca0b75e06e2ea8820c64104ba550f346e5a8ef7ff01e148ae8b33c683e018e80254a2683e5e9e4043c88220a2a5f48e4072f4c7d7098b4499ba329127d5ec927a4a9399a3f05cf8003cbb20879a42cef1be54ddba48142918df79ff75ea2b89a2d4f910e8dfabe51dfe7a4f20c4536df7ffefb4f28a221374371349a93e75f1ae0cf6397c3cdf2fcaeebbacfa3735217754d6ac1f9e01558a23c28eaea5c8a508ff185b73764e48cccdddd5dfb03b1cfd97d4b1c5dee7ece8a039562d7d54a654f4a01cfdbf007910e36b64079be51aa0fe5791545e1d80fc2a25acac0fd051bd51f1b8af28441900084b2ef28dfe1c38652cac0fdd7129c4e3ae4bf686f38cb0e300ea7eea8742db74539e9d4908000000000a314000028100a070422a16844a8c9792a7d14800c7a9a4e764e1bcac36194c330858c31c8184400000004046446c306b8a9a547014b2ec0dd293e0d829a00bef00586b2cdeb6c4ae54def8aec62eb776cd85c0ee70c4e4da2542caaba982f62447d3905de36fd6ccbce4bc5a5d5621f38472940023a0ba2ec7a0f42448954bc1c289c52b1194dd71889aec274a9478b519d2a212bbde1b7a2a277f08490cc78abc7e7a9c4d430646d2ee39db1235676cc03e3dbe8e4bf3f6c910bd8d1f10db5c49c5ca658c839043d51639583c6bee86341d10fcad3412358e37cefd436efed70f5ed9625f534182893acfa3a0b67943e93a4c24e752f941ab841f725ec2331cdf7521b5c297fa84ba2fc6f08a4522ffd34fb7bf2085ccda9b4c141d138d92e7d86d4733fe3b7c160d9e748e1e0666743d59c63fd7761fc8ec3936a64be076bb223c9017e6de99d43a870fba85c4c7116bb0db798d93f89679e3063a801253482685bdee203e8b5e28bc728dde143b193c2a420a8782c86b59670ce3a0b13a20e427ffc3179e911f73f78ae0193984b91cd1e570a081c0a380218d7c5f99081b93d504bc32ec3af3e4a0959b763b16c910141495f5a4c3694a1ec1ae8184c04ba697e71042e8505ec1ef3bc81aec994aee7147bab54a46d3b2d14a5079d709c3d723a9b3d0669515bc760b5e998bf2fb08a797c4999244e6ad4880f3e45947f7a86b2211c913fa43314ce0413477fb9b9f69002e845ae854b7d2e82de4dbad3fe6ff68e680a0aa745b2cde4a0bc472bc859139457de47f7d4f7ddc3e04c69fbf82aba7b95bc5b271943dc94ec8b38427604cee2434b7ca61d1dc0989925cdf82a7473b916538c34bc8b44d77add0cefbb749b165a3257929d579c9d3ce36042343b793e498c4ca7ef45a67487ec44ccef988605d98937a5179e9b5159203b99d7b2c3c7cebd43571ec51fcb158e196959f4881a1e333b42b76ba3cf2918ae5a484a84e0d030b916356c0cc9b5abec31773fed085a14156ab92fccb45735f39902be1ecf0eb22516b0a10919d8042a66fdd23af5f6f20cc82f049980bc9380c7bbc6eea83998d96783bb82b93ebc989c00c00c6b64e1ed28b3b3660c3c21c9b7254d8c961a97ba50dbbdd864468019244e1e326fe41e138e73383fd237c5bcd3e9cf9c3f86a712569e21ae987a99c0f4578215cb0c9b2995cd49c7cd43d84e245f47133d72f8df80ebb1bab133f54f61950532bf4000aa94e9119478f8af3d5f343a69e8217d673d9e837b27e31e8d4f53d2d378cd60449c86ce9972ec8f8829740c37d34152edfe0507b26010472976deb4d56adb69a85f44d71881fc54c0cd77c89524960f887fa0d644971013702f0e9eed168ac9c96a16ff717638d41fa00b94b8c023dca2a02768dba4978fac8911d24d4e2620c675777512f700a90f38c05c6ad24e1a50383afd4e5e749eeac9efd527f71e2509e1b9012703596d02efaf92e40ebeed46d3d477664bc2c3c6cccd7a390959863b6face254ecd4214c17d5c0acb57df27f5e50de43085fa85d618452432cd00da8dd21e8484d2a20a4754ebf11c17bbf1331ddeb60b00610894686c466744dec40a22a2654901cb06c1e3665a9eebad7774fb1241ff67268093c6b1c73d27d6bc6de0cd916b77f987ced37f9f30c254a9148b9584b8302043e9bc5d398961cbf4c444d79198966223e6357a6af8203cfa653d58f98757347f2fded011a836ec21f9e206802ed774bc591ee4bc41d1476fb225ae121027145271c1be877f6a0eb714d00f9132c35f7474327c9fbc44ff9be8933a26a80ac41bf04b2ec602ae8bf7593f0c3272b64dbbe83f59eb6720fe12168344e5aba00b7464477acd0167e1ec5c2758496f47a19edce1d204b57e192ad60d14a7b2b01e7bb652ae8b787c577dc73bf7b112dfcab6ea349998592c648108065ddcc98248c8cd232de3c0331de2aaf988d03f06f14b97b3c344994c177bf083dbb0b6519dbfd7f7e85ce2ebf10152bb61759fbc10ac469e4fcb9581fd050fa272f773694f42ba5c334b5240cf941eb6f32274a26a59ec0e3689225d7d719f8049942b017083ff08806a19a28a22324ba9e19229187490a42cccee70c59df83318e8efc64a7e0c60ae66a5e0edddf6cc5f0f730ee66716a76b1b6e75fa10b16a5c040d42d720a28dec8a2fb49b2b44993b6fcf5897bd7ef316cdaaf2f51439c61fb6619c843e6d3e0df7caebd1c183fb116cd06ed7b2cd7203d49f4394cd32740cfe547f4771ba77a919f3bb81f7706369885929c2a2579a6b05524ccf31a6d040078407f17771d01606737786076f41313c1c58dc8a1d8e44f7601cc104c9981d0d83ed9fe439c346fa1e74cd9d829250fdad8470af438f8d2c9eb7aec31fc6112da5fc04a4b9465751661c918fbaa29cd7cb39231ce7c84fde5151d611a7c801f335cc039dcfe45a93a08a991e99ac44672adef3b993684e9cc35a28e5a9a4f5a27ab68dac5af4180cde655c916954be97e5151e7eb582978aa79866b2e99d613ca0708d87e7b7c639044cb48aa9f049c5868ee792a8dce879501b5c10a822a5034a8c28b150f10ad9d45b71ccca4380f3f2c4e9279a44428258d2d7113ce7bd49222e8659714bd17a8300e9f523c442be619c07c7d6283d827c4418c632e7e5c2d95449cdb548640fe33b107802694069aa87add6d42e564595153ce057a3b2e08ac542961c10007037ee8972d95817ffd24ada6f508d722bee0d44160a57fa84b774e6058549fa00868374b32d772397c1c484db2e27380b23f9f2223bdda0d3a220ca03266d11b96f1fe4c1a35a7183f96f7309026519c88c68420aee0e7856a6d2e746815babaa72512455bca9f36dbc2ba8b764ab4467822cfb6ebd50a6366ca8bb2b784f98b11430a6b0428dcb7069cd096835e5483adefb16acd51ab7a05089270bbfa41809821fad40208b0a024100204fd2c3e1439e643f26321853f500598cf93592e83371b57549f5bfda072197f6297a2846273341e5b54bf76482e116df4181e05a02a275281fa2a635af4d09d0232e7ba08a94e4c1a75ea8a9c2dea6caa4e854dec48589f91ed1938aa3014e862859677c1eb8a42587ca3f56b4e2215419881a9b395aadd26503a4421cfc4332bb1fcb557693ccdf35346730a47f9c7185dec9ea551613e97dae4fe8f8b0b95f8f23f16a03f08e5b551e043de5330011088eb5c7e21f9d8e0fd7922e857ee0ffbf995e024f5b6542fa57fcd31abd45166e05133579f17e9704cbdc8916cf400757f054b0e4c8696e26bbf56756db056530b3b5e3ecc1d0a9e71319063dda6f599d788c61ee6519c508a0cfb055a5651190dc3876c40b33740c75782fecc80778ab075227f49fad81d74a43369c0644823373a3f21a065965286575c89c9214e35491599f35e1d00f5ff4e9398037ca67d33e1afe9d0b85b50fb1f894fb049fbe3bef040212ba488c3c754fb2d7e4522a080098beed6db80a3ebcc699222b8179b7791859494bc9ae44dd7c9f25ba19248d705d465dff442342abd15108e5a58e4305adab5dffed76b019941efb4bb2524af27f10eb37f2da2f7d7d5ddc2a124d96e326cc7f76b11a6447f3bcd519e43b42557980fbc824c1407bb7ddae236edff9eef0f24c1178c38e10159b4139031fed8163b66b4325d9377a6ea1a3084c95db7b1d057bb7934ea30e4eb01d8a9be32580857c44a51e4470a3c3cd77a43da0e420918a2f34268470f7403c3ea7200201b6b2fbb2a6409337f266767730971e4d897a5b2f2905e36a51c8fd389b89477341c1aa8356b87fdcf555a6452af10cb2c74de7572afa0593bba3c651ed3461ce3d3119e34b909ad81ca61b7d05a9305adacb5e3f9b3e1b9d6e02da4b931d24a4b1114915d8dcbfc97d0e79ada93e31c5f861c64ad7229f691fcfbae8600f5da4687e41efbcdffcd1bb2608a2f4bc0ad01a71b910422d3c0a480b60557ff7e220409efdb2aec45fb9ae5c25d098db6813b757d59fa34b6d11e2def872bef19f7a1065c44226060afe7918df170cc776bf8eb686f83669e679603a25a9224eef01ad7552a74d22489fbcdbb1e648b1a603f7a4fc2149dcacfe2df5f58a3c7b75b1e8a647c6d76c5230a5e1a12642ea2dd772746e622ee2b98e2e7e450dca38c2a2de71a251040cbd1a7f15dc734899881a9ba429be950bcab8a2f21d1278a248f2005c0bf98d46f46b69aeeb340a6a78cc5f8aded7c0a6625230c49a32bf44d3f1ab8c9b77d065ecd81d3ecfe58a7ed817c62ceccccf530cdd80003ef9e1bfa30c7a48113515fbb7e8c25375c806f2b6f45000231da289047571cfa3b98cf9f65ce281d01a9251d6f1e6990ec4a9d5bf1eebade2ed93516a0eb1dc6de106522c68415acf8e81219d6e21c3a540ddffd27b563e09ee13557ce2c50e554ba86e18dfb6d07260e752b512bf6cc304e98e6fb024501ad7c3ae6985a5afe5bde280eee2fa768cd8c86d759878a88931cbd498edb288985676e9cf63f9f9b08edf2960bc6c421e825e9a7b8df80609a8a48b376695ddf709142977c30b59235135de5c740de06a1019e8f4c202e8ca626fa03977cc6bac8d58bb125046417b4e16328aace478983861fbe56e5c7ab9e69020e63f6e34c7ff9ee32081b86b276fbbb91879fb6964defafa54fd54e9c47bc602cc56db0902433bb8d92e55860164affe6e5229639c7b86b396c17eb6f8b54d6bb1d71b964d5f5c8a5d44b5c5c7de5679a8604ed904a41ea69913bc12f9398b1db48c6826ed82abeb822bde9f3d6fa3fab728779114e8542e42ad4a3132f5a1b27569543daf08162a74918087d82e64a2afbe5dd79553e9db14541bf36bc2191e9700bfb5af9982d7197c6770b8eb5dbad7617b3d2be8320dd3ff73f4b20e4107f9169588677295cc12d2d812a05f37aa6f503eaa32c20b44a529cb2e82e81ac634177e8e397cedaeb61462965d03374085f3d610ca3152134600a6f6f87c261c076f794416823c96ff960cb50d0b38a1212cde596a1bf50581fa94577f0f8dff1076a930758b5cc349520ae7385c8e4910202d5efe773d877aa44861b29025b3a4fce0504fa3b57f52137c3d963a7b4b9ac72f7343bd63cb733c268a63747faa62bf3adcfe08084d03afb329734b813092176b15c90a05cc629ef2787f69ea2631a5430907726d8e02b65ec0bfb8baa40e28c879f47e2e87e4552d810ffe8361cdac73adf3c8b72a333dcdf6a0ed331e508bfda92f5683b6169cc95ac9606f2c86f223433eb87e1640aafd7044bae1ef3c4919234bb4ad145c0e1b06cc2e529c041f968041606897fd23bcb5c5190b253e9cfcc32e0ca2fe690c89cc302302e0ee3c2bd16d7f6863f80c76c64a6ddd2325b76ab4068028a4a87f70a84cb717a1b9e450a3076f676a32fb556b94ed08f4ca8f53a293d8a4880d73e356f0c45f75ed482e78f42598d8e497c2e01b4a1c1832f07888d2b991d234d19667062ca3e7695512fd099a41657dc95cc66358150e15fcfa8bf5c3b7dc32eae7e4444d62e77738f3429c07dbb135a5663ef8d11ac8287182b3ed56de05b0641189c75979afe00e485270d9372989e13dcea1324ea4ef1828bbe54a5b311b673d5b4205618c93d53a0e202e88b336b282e55f0331cd39e08cd6fc99adebbd2a8a569ba14cf55a82caef6254ff3b951493a56693cec313a275f296ec6bc8a9890bfbbd8d451571b6dd4c38c04dde0e7a17159b79be9841a2967aa6d54e4465a672950ce645aa6a96dd2a2b2924484b1ab9e129f6d938eb46de21e634ec5cdb211e70ff848b95dfe78aceec190793fde5ed9a10dba06d3308aefd577a4e5063143cfe4251bfdc66659900b25514f9c8eb8f1d265f519e56c84bc6773e1b293c20fafbf3d33611322ba62d0426dbf75f2cb51144df1c0db542915f5a5492565e51575f5ce9c372bc413c8c5ad5b5c850d61c14a32a39f82b6ef745bac5b700951bc0fa10c09b3cc1a433f55e04c66b8bd5974ba662b61f35016ba2287e6d444be65f4a35330fb1af6ade8c5699f0c79dfd717f0e2d180e0fc9ceb52423e68a6fc0a950eb617fdf21e876998f6391ee64fabd5ce035e678ce4aef7151ce66bb77295d8532f7169e945e3521d7c4d0e85380b48ed302130d6fc1396c383940e707381fa6169c2116c3f4d9166aaa5e6976a12d2ec41a9f95227cd33132cc5d94d4592d2103d9569c5bfd82afaa962a3429ff848f8e00362b79b6f7376fa6cccaaeb75596f92f001bd6119e7d1c0c548239637877abf8c6698fc87b83053c380a4347a0ab4a3461d0f21f1a737f52516cd60840c7f3287c1e0aba21f5c44af7abc4cc62a8dbd3cc0cc734310c4ac2d6c8088743e28456edf29f2d1302bc7c6b9ba689a18e49897ac9e20fa645e2cb4770e67ea4893993e58e652164d34c617987700dd52dac1dceb8c29581a2e36f2ce1a7c2842449ca5738f515ebb246356d399d9f7377c49b6f1c0a84b5af5e340164ff4bf670674c4c936ba7ba001ca282d22051b1a4249a8941201aaceee3dfe3c35d6c3e352938eae2ca6eaaef99d0748fb0776f313c61089572979349fda0b3290e8d1e17d6fa8ecb5db8159231898657dea3e03c1bc869ae08ce0290865c289c65c8e6413f2e4355018d352a71465370a64744c9f961ed254fdda0ce74b5c5cd0d2f41bf8206c12f7e0a36b536250e2cd77416781ca62fc561f7d0f6cf12d32de9e0558244a759cfec0010c426281c4b4f5c1e81eedc219ba7512369664e8c4684cda497ee1c49cf92fff30d72b441941b827477b2748b623b34ebbf9897931f8b87a1d2088cf85d8334ffa4a0ecf6651c43b566eee8932f650357abfd1c69f1db74d547f5312232e248ce72c90958b71d49bd172afaf4446ee8b385d74a455226efc2bdef809209c8027cc82c4d84aa49c033315d11f5b70d669ed2a9d558d71c1f95cb6f0f402fe93b842e83163fcde51755a887983ca1edc6890f0d938e387237fbb0e4535a7117ca59230621112ad3383f95ee95efad8d463e6f8001ccefbe360f79969316eab5ceefba4e5837f56e103380cf790743d64d6a95c891e4376321ab0268fde0926d8efbf06ec02b96adc5ab39e1c5128b5fcf383afb5adf5355193be9c0476eff14270a06d71f6fcdf6e25cc22e3edbe0c882c1273f2442160690cabd7c1b4e85545365dad933ff573b3218b173a1dda8183263469063a3ea03070b4fc2de95d2ef9e62b4083450f4e5c838aec7923881f3820d2dc7e8c2b6443f85a78ea027aff203b692fe8abc409053957321334708c49097417946f1ac183933a7786fc5b360c1c094db93e53c71c9438330373702b9fded5fa296280063c3b3a6b5bc681fd00136767a58526929aa83c87d0b5353891e42991010696495a91dc1c9c50e3218e01c6bd513ed2838109cb7358fdcb04851cfb35eb0c252198e2a994b7d0086e12c6dbab85936273cd4923c48cc0ac8bd97660c22e83b47568931c56aa0c8290ad7715e7bf8b421c2b4298a66b7cca3f2595a8dd2f14f7303329f2354a3ee146dc379c1b9647a3a0581602fcc9867aa46b6289212d16e6e36dd9746220748d65f798921a3f7b6ac8ec15ac90cd538265e6e2ed634b54f5fd2013ab0b3eecbd06acd1a4aa6292b6248c606ece89be50562f122836cd74bc0b36243d79a8e210569cb8ccf445de605a352dd41705598159678879e34bb4d1dcb41289d7622b6529753c6fa0ff794ac1fba8ae4284e0816b3923eb61cc139f139aaa5c0331d866505316c6ffb229f8f719c4aae7ac1f2849d4ce5629803c61c1dec774e61a6bc2d946c22f590056d4d1af942a8a3c5e8963725c68b4fc265965d06ec1150981f876c460b6af9448166ac1d41d33c977d48261ad1d593076fe6f441e7aba85a4152b52eb2b10907e0ac212c69bc6a93d8cb2a6757cb4149dbb37417473c814f5f9b8eb9900bdfd7ebfc7cbdcd6ec55264e3f465ed39cab6d82d3b2c1517c3ff6aeb1e1cd16fba496e126a8f1ff2e07cd59b68567cd86fdc0a2a79655e3f85749b9de11adb1d3248bec6ff0bd389fb968321b44efb97eaf3f7d6993d5e44368b552d45c00951ec148713c04652232441f8a4c32cab0ec6708575a446964c7c01780b38389c04f029b02cf0289b8a250e34ccbdd751a8b061c4cdb0c5615a1fba75a01a85881658e43017106303b89931dfe7200a968f9849ac8a2a64fb9ab801c8eeecc3c916d8174090eda3e9b1bad4420b2c095c7cf6c174d1b9a72ee86b8dcd52a67fb1738e7a0b9b3299f56da655a244a8675ca16e55395bebf1d247361cd133ba654683bf84c96470fb0ed14d3e6d1bb322be03d56cd4a1aa3e431e0376cf3038111229d997064db8fb9a208b29ac1403a3222d74d4ffa6b45d70799abf94f870d2380dfe446c1c2e13addf3af7a9219b7afd2a531c3fa981ca4352b93eb418f60636627b09341f99b18f5324ddf1cad99255ce69cf1a040a4c4164b8738d8987444575dff309dcccca8c194d104ea7a962da4ddb60fdc9a57ad14ddf10a8ed06220cad379268ed421a5d401959194b5887b4c832c1f7ad2fc422e95b687edcd7fcb1dc1bb481700ee7fdc4960a9a4e07fd8bc8bbd08555d33bd88315cba0c051cb383e46cf5c811882703d373a706c68ff28e100e0655d288c1598452526aab3f198c7706d35fca803ae78941027d4b8b5d386fe42cb8d3c1353f7053fc74401f6039a5d04e1bb56a691d729645c1b9530c041043746804f943a0dd4a92d1b83283904f4d5f8e7fded2de02793daf9c8c4022aa5bc890f0a1ca84b82fa389068e896723dc9903a0ac7a8c93a76de52c85eade94ffc8778653975a63af5026d0889632005d909d69d826a71d8b00cc4c6ed5330c8a8541d0feae35de37b10acd1593f60c2cd485893fbac1a9cb234535cfc03d982ccad7b45504dda3243659edb3c923649da868d2d5bb4cd9b81d4e4576e324ddad3c7ab34f0b8a28a075741c63b18e91c262c835071520a12c715880f5f2022b8e2c6da6e40b3a838778ed9ce39fcfe405edc817888c2ef8dd8cd3bc9eebf2fd54a033dd74b74ea39087333678d714de7026142fb7a01d228d026958ef80210f82abf38473cc22383b8bcdfa7102420d3967bd5a0a4c22d46bbe4c52778b0a4bdbf24a217695784a7eb06980fa82e3069ea2592634a78af0b774b9e5462b999c88ed800c4e5782c9f201e42744784cd679e5cb38936463610ae532b91b0cb293a284058a609fbf6352eb441e858333c5d96eb67dc4442ca099c35ebaf9d79a836e589987f79aedca0e3a2f655039d8ca9824bdb4ac619540612063d627e2a9dd0aeedbe376752128783b49fb31bfef3125e922daa607b76b1d53057cd03342ba85be06cfc80a2127965b0a673f220182449474a62a4319c15371ec6cf5a294063cf969368562fb8a4cc3fa0138ba0af8316d1a9c157416e9a216cee8d2c9d07c6380457f870e2eee9f6acf1041ad29c3fe3599145d242b90025435f8d8a5b4836c3902698067f96f63a06165b9d50efa4288712c288111974ffdfdbc5458f1d394e503a0d595c271b22f72779e698696782bee806b382e5d6eacc2f25e7b9fad21d8acecadd7733cb2be76c1d6d5c93f508e7ebd633d79d1fefa5224b39ddebfcd018daebdfc3912575d87c906605f87e9ee84a1b5e61c28fad6c497539aabf3d009ca1d32d38d1254b982d2c83d3bbfa33e41020b92e2bc8e434e97c7e1d05af1257a61bc466e10fd85cc215ddf246add5b6aba8ab1ea56536a2707d4da5d04cff32c5515259a4010091cb0317ff2e7b0591d0dd8d3908d1bcc800d07d475e5a45042db5092e45f4c5f260adcfd93518328134d408c7b462f62dc79a21db57c362800ad837557747091515b754e76d3cd8798eb7fadca240884ffb4d8eff04b075b55ba2d7ba70e2ea2613bb205c1f3cd3390b7c409f1ce8ed2834acfae9868e1a4a04988b0d35330855c36bb687133badaa5f418d58a7c06692218288c62c4a0e205fa1514cc494a63294bef13ebc0c6f756dc11a11468b81f865a290a12fe87b5b8e30887205077b2027c936554b1875b90642b1750c5497f634e4b0885fc6b9ed1a0a654da3895a1c35d0a54f636ae4ca5cd9ba4639ef030267dbf80526577fa65170c7a3d3994586d63e33008ac6ab772c447ea3eb1620a54f85f31f75dc455a74cab88e774dda270ae909a84dcba5de1187df87bb23918c8aa2b836173e3d0b5f40add4bf196eb51c26dba138fb07dd31adb08ea9544bbc2bba37a0ab27685c1c9b3ed98af00494c5e956d44ae41153d0401b232d0202125fe779a69c751c7c056cb14866ba14b117d1b112ad2b5fd0f7592ba1a7dea9357bb0d61d7f6a08557872d59e81a2f186c1632fb76e3959d19e97802cf6a62ef20da5abf456bb296712bcc9985faff5cb1c048abb522be7621fa76c8a7162ef92bef8a25db0b0b5f65f2044f5c693912010e034757c29a5b3dd0bae7fe00a84688ab0edb4b90453f6268f53eb538f6ca93e6131e6f04038c9cd0613ae1cce487a4d6ef584931a9806028ea337ea26b89c4ff19ef3b97f87d79ccf68b4aab32925059de44f35772c8e033717841ddb8e185d6373424721797c77507e07db455647fd6a3ce0083a8cd733d38dac581a3e1b3defe3519445d5339a24fccfd7c8ac160d5780c1dcac69ddee3970e9ae7c51a51ab997a0c1577656537ca019b7e76c23dfee2632f91e46503f9fc67511e4d477bec4a49b1b9a3dc8b8fa0fe4f6a34610ffe033d48ba13d7b59aa7d9e38410f83a611288d8578ddc281149e2912aef4dd300476ac4b58470c362d8b7fd7e71f1553b5a02b65cbda4aa2242e43990f5c1f8fe14ec001d2fa3a30fd697517dc9a126ef92477ac9f8df2909df2c8492a99fa46cd7f65ccbca5272e920a47b27c066cbbd74e424bf4cc0f2f8495823cd3fd9cfba059d650c722e7db0575a5f1908c942654edf1c8cd23d4abc16ed6b7e3cbfe912559a4f1eb0d29ccbba090c97ebf8f29f1695f6248ef07e513b291d64d01fb186baece453853c7292c18df111ad2e87ad9a088e0a757fde848e5f7ee5d893c1a0c2618816638d2a7ebbb0bcf626690cf0c36769e6059a54e5296b4495aa029e6845547ca15453193322d731323dae8e3ccb206b965bdb40a8b9a7d32c1ec5d1fbb65707b273d7f4dde777067d569fdfd59d1f601766c6cb2c06f266ed316b3836eaf23afbb524d6cd41e26d71428e4251fde2eae77a218d0c59b2c4f9462d2d0157b9f5210197e43b51341b0058c438788b145c351010231bcbd6d7fa94a21eac1cc730d0ac4a51f0e04fd74b4b2dff7e7a898ce5f75df6d898a3542a420c9bc3ea9a12dcf8554e3c64b26174d63f571027c53898530a5a246106e27a5c57fdebc6a4a0cc64a621b8019c7f8d6a95789727b2174170b0f6dc6563bec7f08b912c6d74fe505207ae828977ce172a4bb63d8017c4a2bd510a714c94c1e8c7a67b21c8bfd35853d0a219f9b1697f972af54829818f56099a293ec1e654708320f981badef57ad3eb6f943a50050d6c94b7282e85c855156fe14fa5c02677b7210d02eede5d0781e769d1de390c4b5a0e60c715d2c4c53958f489c34870605c47c28f586f74baae42c51f9c1c88e894e6959d62c65d29c8d6e577cac87023b8b421484c68e2d6afa0467f2fa8d25724a79724b8785005ad64e3b081a10e4d4a82c1b19aca0ecf278e804090bb38f962cfff7185662240e9a0a00ff5877682d3664613124a0128581741906e78f2d429c5fa16cd86867da54820411f6cb92aacdd9e488fe46efe795bb9c64c1adfbcafaaa3b78215c9b527f5ba127ee4f32869d0a6eb6159990890e5ff9fad4b2d1baa9fbafd7dc67f702a5c3f6abc3e11980b0f42569dd38b1fd409d735fd964319dd23380b47d3375f17925a4b45414e170e6a0ed7b18e8cc15cbc293d07ce45357442e6e1e3267c6e98a3fa6a41a37eff74470d0fa8334a04486d5951c5313c2b5ff20ce83f8f72ecdcf0e66f696ea459203a5f35242dcac9694325f0b6b7038d8afd6affd79a8037e6e724f2e37792ba1795c07b95dea8bf20deca1e68761dda5e009d1de9bb67b9dfdce3ebd3b019f2fe7c4858df7b930249c49c608b769996d2a59b2c7e565d5097995c044a29e95c82790cfb4b5a9a5656d0c237b3ae5fae37a663cd2973f0655a2fd4d48a65c9ce94b4183a45f9a2298732298613dd5b95236ff77210186684bd7f10711f67515c9ddb963433b57aab85b1979ba4f7cc3ae519d8d21bc80276cd438fb2809b4a6f03f96da464adb4a067ddcf53f5fcdc62d6a537af620124752151788c0f0e6473b61e6a496c6da8fa0ff3675aef364c0059fb3fb9c8e9c62883bf2cc3c3885c99cbb86d42beb2f9c4422e9576f129677748b0d18d8c5bbbb1c8b8434e7d2f1736425f85bee0e4a375fa050534887c5ae5a1393650e847f2636ab4c2f6932002be59df35a43cd8ae7490ed767ee08d1f53e82204060df418e44ff8283624a2004fd68c648f7c4c3d5a20f9ac3a3de043f298a73071feeac8123288337e0cc13cc58e883fffec54e3e9fc25771f930b76c02d6901fa5602bfe8e154102940ba5b71215d4f07955319b1ebb8b0d290dbf2ae4184ae152178d0296903bb65bccddd941940615971a022846bd5938520f66088297dbb7972ddd240ec5ae2206dc5a6290084bfb7d6d91038d0a0a9bb9a4ab805fd2e91e6d904d47b8a42dd82bd0ca0d5bf866928be8ba196f32e909ff425a6402030b3e3e64f07092812432b656934933516666ed7b80cdc21a51146f6e709105ab7134c109054cf4bccf9462b1a4136a8e7d2acc3a390ed61031f55514c42f766f25e59fedebd501f42081998e093c8e70705fcea2b711d0a76071a3afe9fde73905374ace40c3467ace05d38a527b82ae2a9e2272d59a9e0594a1e163d97b35bc1d7c520314bb08d24962e98ecba67a4bd47b3ffd949f822681a9c37bdb5ce2038193051e790d5d65e906885d1efd0dcfdf30a280c386908d2a9d1fbdad2664c6026da18f0b620230f00a1774a0e4c4f0a7a95f9e7dece71f0de5b4610d43a9242c0f900b210a540e967e89b5585b452020153041acd687b30531bd17a5c5a47b658c022b990a5d33c7799610b2983929b0c30418721fcb40ad99994275d26cd105c93049b8a58cd604c7456f2211365e6b19391ee18aef1791a4a886461bb2283964bb00e9dc2f2a71fbfd33e7a8736c766b47b57c6e066b41ade6e0b61072bcfc84dc3f5da0d74bdb1c20dfce98ba7f1dd64f6b92750cd278a7ef2cceebc338c1f4714a817cae495efb777fbcb30ccf5f28dd16fc3ac08199de98400cef521292f2eab615b45c9984917c431c9e468f6b328e02b47e47124e80516b6965aff16f070c534c210e1d717e132345be46b49a2a91d236b5544d3eebe8476f975d726bc66bf5052832d4cfcc07b454d3e2d4ac73245eaf8ef7fc650878dc931a5b6b924395631c5ec3f2f81d50f8b32bbbcc38d577e7abed331a2926b11f19873d689cd60770b497b322f77a824e5030436b9f02e862c1f341f4c884d3e54413a58e7b308c52932960dde5c60c6b69e9d9a0ead8ffcf26bfa0b5b9d03a2bc7b6190733e00d4e026a05dc854489b45810fe6eee0dc90f12e6212364baed6fee051da33590decb2868f3d76bb421230505e07617a017df87cb90b2f71bbca7ac9f1880f51f7603ed35f86870e8e358025d2040d2cbd35c8dd728ee498aa4fef11c52d63d5b5cb72485d27665bba7c4af7c980e4ce26adc603f3f6abc7cc480484a0bff81dfa6d7f50d964f13b7811f31587b14bacda42b23164bc80df1abeb4e5232b63a3ed3dee8d7bae12e2d5a5f0adb9080bbe6cb1ff7832b929a71fd5483ba763eb9634eb3bdd8f6f2dc1214a1f5489757f26db1d39cd626c16523d7a610b3376d9e745c93658f1a225c7d1d706f406d38d6789ce2c72b7a90d3caaef524c9249a409961bf7cf7379de40ba9d3dbc0e75b6ccc23c60d281c13412617d1f3d944ca20ac824e04ad182f22ad490dc70aa1aa89c5fae9d504b5ea89b4a4c4c97dd0f75fcf35530575a1034c9337f5b08cfbf2da29d0a8e7c27aa2a570ae305b9ffe5c92c2ca3c9b88f7ce5298385336f4a87c3ce9f15303f0d519f7c7342b2b0266a5362a660751937a6d23bf9a5d02e05758a5e9fb61b52f0442972d690431b33d45a83b2400c4631644b4c5e1e8e44cd9f2f9399c023afffa47328a4606f96e6d299110cf8811c5974c9703578aa0c25d469e7b47e16430a7204fb31101bf5402d67178abf7fe505b64fbd0511961904d61561da7a8de0600482eda1d3140914c66b70510af1560ccc32cc1fdf1c77a4c329cb95441605e86d824c2221dfd40e9c12551915407dc835a94eec5823722b702cc97ad51b5fd96ac9fb94489b96048100c8c68b4dab222bf692490859d216566d8ae4afe2d111857295e9b92a82311d42724021f4ef5122e222f638f343a3d4419c1541b9227b068e41d36c448c889f4ebd477cd200ddd065fbf60877295934b2a47707f8d88987c27344319e7d94f046d7d8cb1c4158996e82b7e32338d7a94d49114a9a919b3be9a428a5cd19ddd382736d0718c14e0acba1625aaad5169e3d290a37624d26ccd27117e422d338feecd3a18eead039a67d23f9eb7804149486050d4d2a623049be167a78aaa619a0225f6256ed9a985a9122f42175fe57388dac84d6288fa50d20426787830085640b5c28aa0d1013706f2209beb6e167ff98d74ec1a962616ccdd6cdeea9abcbcc10583d575390f95cad724254dd2e5319247ff63550b1c5438cd0402dec91552f79a0228268d9abd1153d121dd845010859c79b622cf0824671d7c986a7c295049903c00eb8703af4aa87e7fc56a7c31a5d6fc0a3a107864c49a2a9d7e009d2677df7be4a930df0f42ce854c7f08b2f991de746f6a41a5c72ddab7d1430f02eadf17fdfc929803b6c3e3cfc8a15d9a5bc0f0ff34ca5d9cc19c3327db67a429a8e5664b969720c2f3fb05ac24b57583cda4f2a605c3caecb8e47151084b6563632e3c1c3bcde194f7be9df1a7312c1330426d4399b01856e000c578cf2bd546cbcf3ab7059f7f7e371538130d6b8a8962955fc3aedbc65b999e51e26aff0d1e7be288ce770b818479615fc1ef38024d37ec361e707e435d16aef706f20e61c927b7b3ed3cd38a67701e1875c5f3da0ea3c2c5e409329b81ec4758a89947756ec3899293287f50a5f587b3c8567b61a4483437ee3c01a9d2fdb5e7dae53024277740cec66aa938260caaf5a1c01116c9369137f61689633e39650dba6fd1d23d4452b738e519b20c1f000370d3606a913d9fbeb4b232761cff7bceef9aed6ae9473338131e0507cab7d26587975ffab62f93e82113e8c40a64f2c167b085248e79e9f2345925f5e6967180a62457cb15aa0b1622fb6ef74acd6bd7faff298ee8f826ffbcce974f7f6d1a4d08846c255801d39aaaa388e677fabce10f7d84a9389c458cea70a159bbdbd41d0527aeb9ea2b259191500d99976d82f339bd4220b1e69a7eb06000823d344e262a8b29c959281a366bc2f4b059d30850e9ac4dd9145b91fd8e3cccc2a72b64e7c1808f3fd247a68cd43c269620d5f3c4d6f19a9d2323a73a796083ac1955d321ae58a5d03dd72474dd2b5b80442595a072c06bf458167ec5eb40f3710631db60f079f3cc6ebffdcc7476fdec5a0f20e0e4b93740ab512a283c3628474fad03c07c1fa2acfa01171b3a2766132c43689623d9852b8f001948224a1013d8e62da6a65e96eb104da88ae96077424b889440842004d84fefccb5322ef8b21b096d64aa20103e1cd70846fb5894663c6b86a8d6e4fd94ac9a9ddcbb109d7c8c2e03f491cf9bfd61651adb551c7966feccf602c991ed6d8f8b7b042acb255be3c844c90600b29df99cb665b4d51accfa81f0cb46193ec26083e7f645cf14afc30a9e0d83a84fc4ac0a52c7bafb355a55a32ddbe990670aeb41bcc50606ee1fe5fe40a5e6e2f113f49a1a9b6ad5e81e502b4d52e1f726690290ba22a0bca9c284ede79566c6b64cf8f6913e383a7fb149ae7dcfc8da4457c652e00c1b60446b7361f71ce4c3daf9fc8d1378c96f51cd36767fdce948f1414c2e352eeee2a215ed0907edeb833d34f0db9346761f5eb8f4a78964cce8c847a4aef29394ce19a16938e85ea50ac24dfb356056b9944f6f9c02c864a4e38dbb292bde5e19907b00340bf790f6b411528162f03ab1b1af6424b53579b5e55b17a8706a11c465010c5fc007e4b1745b3859bdc28875ab5be5766085bfee49e04bfde8896a6899ae858c2657d373ccef35b7249e2cd226011c53cc8402874475020d1854f52bf181773edeb35c15da7cdbed3a42d5a3c1bd5a13c3c3fc8136d4f11c8cc5afde40b480fc4a3183233700fc6b9d837d791bc96dad3223e800c64eef158675f3eeab61166de5867515d5be8851fd80c7dde932a271513315c3b2e2c3ebc18cb9c0051bcb249b30b46884d6c938d9a92bc307e136d84d13eb0f484b97d0a2557ead99656e576ebb4e8d9d4d664688636be722814c488446722b35ee5f5798cbfc651bd9591e55c807dbbd3543b0e3f65aa225aa5d6b482b22162509e10fa83d06973303030f5ad2825be68af5b2f1997c9d900f56416f387902c7bc9e165344c1eb867171c6b47f4cbbe15ae47bdc0e9ce7d98dfd41353378dc982d8c1e1f2921afb33b0e644b36176507f7e3f9a3ef1940f9ae2b4dfb8a1b463ebe5b6621a97738f18ce805e32074a559b70bea762e3f9194484e30fdda217b8efc0a27a415c71b5c0d71ae513c049008f4dcb8b84d0a96cf232e465b7a89c2d92cc6d0978f98d9b9609f0cc27c885cc3d7183ff733713b518e1c7b2f3ebd10c77e05bb1164d5568c775e8a391929b52ed31cf631c2ab7146378f5664653f07ca63901f19d120947ab6626f4be53e84328626cbcb51d24d39b9315578bc876fc79279639760972c33f4f7c0a261fe57ffa0f703e469a08360a61aa8c9691e3e7b1362656e678930348882ba626a3fdfb5ace9bc214bcdbc580845f998c50e6b3a0856c6e4342f5a9d3f4addf08211c1b120a8326dea3dbbfefef0a32d4eee64b666a6f1f63b3c848df67ac614c0eb40966264388f90146d5b4af08c2098bd4aff8b4abcb00f4efa045e8e2223b4e36cc5d5c09d0047021a1aa48cec02b40cb38732f3c1527270cc46fc7b39caa7a8b03a7bb7da87f2194668767bdc87bca860322ea6b479234bcd4adc49bcc357058269f3d9fa3d00a61428281d564e3a2a253a91227cafddf32cdbd3d44e8a9621911c0920e9feee318f68c63e34617adf3d50c84d4d231d1e5deccada2387749c34b9404f6014bacdfd8dd0cc02f1f6c9134bd7eef96cd7948fb69888f616823915d954b27942a9a837b54e55d2b2635cb37b5a3bb88042c85fb46b5c39954c5fdd567c5754c31067941a3b9afc8fd0302080b9ce82967fa87475d69808993442d2a9df0e614943232a22d8af0c6a60c3bac026e5a19060dd99f2dc28199f89d4f8afbf0a0fbac4a1dd513937f216d48170b9c5787e669dcb98f6882b69a480020b515c45796433cdcec44095d424c9ce40ae55b701e6e01604d3ac68472025ad222b5a3f5d8150c31b1d2dfd79cff40b06801faea29480123f966bea2eb95903d9ffec4392bc954a0a842b717a202661c665cb0c23a72d97ca0e23cc1461a41a5dce859bc571e2ade51325392d31a5930bfc8f56ce6e4ee9da2f7d4c2d8f60925beab2d6e91a1a817bd4557e7ded26efbab726025394c37059f3e53fc8020d8ca47c995852a500543a88cfe6695308d9d21dce3f0c471257c15043f917db7aee3c59dff8427211dc8f23d0a43a9fd8832fec62fcd90d2de4448dd9cd9dd2adb22d446e61a94ab8268cd2f2c59a505e08f49e7440040d4cab53fe764a352f5a50a0e3116c051a6cba4d39319a6c8e516ab05da7b7af2b1020a7a38014dc7ea66a98b4058b01bbdbfa2718bc63943252ce0a36cd2e843ca227b0cbb82071cfd207e924e35012566aae9c2290b4ccb4895bc9d117a2d78f248268d42021831fe7a8ac6be18133d98f6cf07c98485da1a9ae92645b9afb63891ac927a5db4334f8ae8b0dfab7dbcf5fd8edfc7714bc2143f5a81b1c3f4fe23ae5a91ca53af1f16ba5bb1924038faab343157718d77dfa3e5be29121bf61a4aef85d2da0c42e9967859c8363fc90005148d35749a729c1efda8be7372f2e8fe4ab281a117957fc9f1453f1d10781f8311a7cf397f8b9a20e82508bebc262dc2daef4bdf7c6d154c96cdb50fec6bf7f256a1e944410bc31413a144f0c3a0b8dd4b65abd2978ed0f526a9e5ef1e082c5a1f4ea0b4a638990673649a2d737b3dc241ba8a66609cb9cb202b28d00215bde4748c12ca4a1bcd40b99dfa350621b593234b34d8fec3d301805fc29ed099990cf4ba7467c115806ac8518a7432198e11bc00a109877122f1dc50a020f575b1a744dc1ef86fbb8357a459504bc1b2f3b8e0ec72737797d726701e6e15a46046a789b4c86177000e7bda302685da309705fe0e5da961801f1a06f5a3204f5cb8a9c2a6a7b0f77e0f5c0cdbd1359dfaf549aaf5bed233eab8651e65da0488c6c86d9aac0923b00273586a9920e6bfa56587dace5beb9a13ca6d60a5b926ec9f68823727a4df79737c61f4ce3527519aa51d0e80371c9955f5c0d7359f49efda78be38db4ca49a9e02ca964f757f186252563f14f166735def9bb4ec0536fde750df761b46fcf3eb0bff40500ece7e0e12689f1ea9b3070bfcb56e3b977df3fa1cff58474fa471b1b1fbc904ee7d3147104e409338940b1a1b697ac76a689064ca3145ecb79d58b27a1428aad13f6ab22f3de3b09d5d7970f4aee92634bb2475d75071c898e4dc56da4785b2aa7e502335a426bee622f06b7365a43c07e4054ad52a525e850d62a5a9cb4f7c44b2ba6216defdf910a3f484c8d969acb0367c75184adf31a575a021adc238e7102237383e6553561a061cc5abd345614f4d15ee7810e5644f9ab80a6df33831bddd1cef7ae0ca045c271314ce0411881b1b6cbc9d1d925ac365986ca8a9169dbdc53c87052229b1f9d06ce71e9b03ee43afd6452ee57754726bfc36da470799ec692f1244675815d7e4b560a7f182765b658b45d116afdc4c01aa673c1287eff943214e7e99d2d02ff5242394d6c1862a269b33a99dec821feebd4cf8961d25584fa6222bb1642a6d004aaa05a4ec71b4f8a04bd4d471771f36ec5dfab01ec57e6ef0abb81a26ed81b2fe9bf1c0802fecd7c330a269b4dd7474c5b043223171365e8dec97a69fdaf059381a533b3b1da53062c2ba3f4650599a4fb16ffcf77874f8ac0a2d1ea6ee54c3fcb03b1a6334011215d9a7039abbd3834953694c82bad08225cbb8e4d27ed5b79104c39be85b130194e8010446ca2e0ef83476a827ddec1a092564ab82167b83f0c67412106860527001a4153ef3e877adab0a2f0890a859171ebc1809d392d0b7df376b5c8f65e90cd77c79331ab1393481c43f218af8f41f9ca5c660ec93b442de1b731a8cdd1a99e2fcc2a3b50a990a8516832e0f6b7806e81e3d0c35109cec4d7aa3ed77333331866ee01ebc35363ba31254c3e4822ada4335729430906890673aa982cb3e49b9e0650c241c12c0858b08113046274a4de024a0cb98da899e41333152029b30ae3f290f93a903a1c468105d02cfd3c9f1e6f3566d6648591d85ce48c569a0a621b1d72a94a1834e613369ee6146e4b611a273e41066380e8eced18930a8b4eae44d5ae0530192b076645013cade45e17c5c1414b7fb1c846f9f434821192a548a956b8986b625ca8d0ccc81aa7890b70219a45e39f449a5c02686269c0e8d8821e229766887041107eeabcd28a48fba21ea50fe8cb011e8213d2c3d194a80988f68cdd2f46221a1a23756c4a6850472aaa317cb8d74737b0a678d0ca072395c9a607b7d41eaaaca5b22300f0fb60d415815da9908ad2f835fb6b6f94ebe416bc820c7230f8a002cded0066a908067ba3b00e7790228f41694950c8c6b8b36bc5f6de0f84f9dca71d98f09c1a17688731023c5851b2bead1794687706689862da7345be8a20e91dc6f68c3fbff51befda142065594c99494b07666b2faf8db99d101fe89380b6c79517edc51ac68c096319101bcc950a94d6682900d1c840bb4929e684ab2e3c536b774e86d2d4efa2d453d9e19ea12061e781fa3c55f481b0a431afb7d96035b7e423f184bb37840e1553988572661c9acec0853e5078c9dc8202bde9de374cac76952a026194023141843160aa0001a3ae680c81b6e267f97cda41175b1ed608258543854871b080e3b4e606c442ea06a490ce7274acef6769ea9d43841802939a5694304a66ed89cd6b9eedf045ee625207665f6035944e69838c1004b1dc17b141ad9aecbfe1f50e90121e31511e1f503656b03e6af9dda28115e2656b3daf3186ad04bc6a49995dc62b878ef161a813a760d81ca2a0443dde52c340f725e670d161237d0e33a6f4ecb0a4eac948dcc56da17d547645918ff3966daa1ef032ca2a58c03bdbf8c3eb92264e9fd2f79d915646097ffc07e6db40fc011e6ad75d1c720b97de154bc255c48c199ee34a7b0dfe41614f2d243f79d52d8aea9007457a0a26e3568bede6e01ed1b15181c793040c958264bf1ba84ce83c03a10ec94f1620bcb896442d38a6b00fdbc69a3bf54f06953a129a612be25dc08b0a1e70170db56185fe70118918398ee1d19eea8caa188002ef6b5a33a9b7428d075a3c42dddae1734cb52b69bbfb7015c2b25bd6c903bcbd172bbefe97489836d77b58c621e03d7bb007f717f09d61624af3beaaa857cc39c50738672cbe278fad6d113504315e735e2765bf0034fd5a8398d8aecef69026bd9b9e574e121b674e33290124bd661040f5b720bcf8ed8105bd03d170682e9a1070a01c7f50a13c1586e63987988ffab6f6e8a576034205f7b6163109009ba091ba281d0a03604f38118c54202928d351fab1e59d1986e3a9692d0cf0f5f9496798ed06aa276b8bb1adb8273f8eeb4d3d8b07166cd6abd92111ee6940faa73fe63379672b2c1176a93906d9cbc8d3556bcace402f7feb78ba0b7630e7124171be778a79f1bfd9a73e5d54581778c6f9d43f675a6e6260c4a079345618d298ea1aa70b7d5c6d3ce2c357ffd4bb4f1e96abc6daba12b1f846adcfc43ddc182aa6384bb633803c10732bf86246866a774373304ba624c0c1bcd8acee10014e6dc3bcbb30631f83e3c3c74ead7c6e6e87fe698758c7cfd63ec133dc07a8dd8e4d87b2a1f2d0595948bb8139c77292a999eae0e60009c66a4fa71fc7f6642a6fb84e2b2a045bf3d483166e98783f764b6cbb03832dc963f229e569793ff85ed24f7a3af194ec4289e0885eb0612f51516a5a52647d20bf5d5caf07ad7b5172c91add1530a1093f2e9269f1f62a6950b343f55cceb22e7895303eb8fdfe1bff0462d0540181c90c8bfc54d65aa9b241eb52eb77a7a2b4c4552404a7828254be8e8d6e93cd9d0dacee0f458c6c3729b38906610f216e109af542d9a53f9ec8b64cc8152103253f9e86ebdb3bb9e32389503d90f612fb5a0d13f2e10f73187645f1c6875225e621bc07149c700841a6805a14dddf669627b87ef89832e40e95d96eadd3cc3dc22d4aff5ffd12b2293123126d3618a190dfa9cb4ce1a1b7b94a42682df2910ae145b68899333eb819dd2febe237a5a11f14db04c66a9d2ff3dfc6482d9619b7fc2ab66c227d0f54e6bf50566e1afe06b482025369a27eda53d6dc361dd74ce9729d0131a72c9b54d8c83e6eac38adfa8aa5aeb828305430a5c417f678bb038017774f12fbc02d8628259d39b4c4f280a8aaa1feaae254e3b3dc0412f824d499011dfe30ae45098b245454b470ec1c868f06011e8f1d6cd804143b254a7e9cbd2be0162a6afb356b4406cc13be32b317d84058993d7ad68cf11fe8d4215938868954ec0c6535f8e7d3502c61ca3b8b4ea310b9639e4b961d2c5c7130ab26d95fbd0eda8a6e1636f37aaebb418c98e9ccd744a8f8d4d0c84f639dffd9eef3fcd63da04ca9895e385ed087537c32b80ee57432b38d4005449f11c5c9291da2476de5e3a1ebd182f26b363a7e6036dc5346df00cd36c42e003039a32141484110539272da9d8c4571ab37467b6c16ea05aab41b5c0ad57169bc752e3d113e6c2821eb98bf4ad8c6db99c325acad8b66432fdbf4694ddac60d1c72ea0762143bed5eca2dc44ef7b6a2f632ced9da7bc490054ca98713ce7a0f79b62b49711578ca15b8ac189084d92dd29c223406d6881cb1096f9018b628c0ba41800b4bbcc69e0dc8116bcaac966da24f75470e085c1670ef90c974d3a9cb439c4d2b3bb5155cbd7255cb7fd063b90d412019629a0ae0c6704aa07df18edbcee39b2ab93a07272ea1519cd594138c71f4a1ea0a44017656018b321d31aa3b87a6523dee4fc9db6620001ed391153130307f729ca512b5248f3d26125df95f343f52b20d0b769933d68d88abc01a672c1fce39a18ae167b4e5af9182e340e4aef51206b5cf453e836be352bd0cb64195680c7853bed4aef5fbd6792dc026af04531426d4d6c76589e688926684db9c6ecc0a45d64217948b3bbc24aded9ede724997d4116b81ae705823cbb8bcc06071225d997251ec6f61183f3a443984045b9f66dec04b856c151b3a03888db46ae23a05cc28da303ea8072137669eaae629fe5be5cb881aa7c2cf3eaf9970234841b28db234f70299b3011455352b8e1c35381e276991591b392d7fdf6a0e9c3286e6c1108379615c3576f8d307eac660a375e63617f2cbe89230f1840f7c6ffeac968033a37edf91704f7abed5c2a5cf1592521ef45e8ee14aae2054e8757e9de29ccafb60b5f5a196fea3492a6470e037bbd8832a0a41b6c04be45b49a2d2fdaa5a5adae85dfc424736d493a286b88a9f390c483c439be38dbbbee81934cd95827ac095583efe1bed7685a6af99af855d51f6424816cdf20c90e82f97ce08fd0b0fa0a2c580a5993723977064207bd285b59a30efe807741d5eaeb5518d8a25afdd78d215770592336239034d8ac06c57320b052690b42bfeca03e96257ca2c822eaab1182938478384df1c55902d8871bd8282a83219301148888f7cb53b30700574a629dd19215600247209073347b28a1ecf1d4718b57aa44d33e489742f8d46ff042a06886d903040700ca5ad1974206f1d6410e6fc5c6e003736753a64edb257e605702560220339b852fb215d15e7f5730d16e5c726720980b95ee8b52d9cc4014e967f30f99c4586857b03d65b8dbc784d5658aa3c2bf49a69f46c68506aadf0ebc704b0607c70cbb17cadfb4b813ba31b4a6cbba980accf43a47bb447fd8aca5736a06ff55669f78255a01f926dd8f1b6ecc9d8b3d6e1132a38a82805dc8307f0a55b50cf66ac0ac37738c349b23083a31e87723a67aa7f609538c290a093253ec49a3e62cb3ba9280ec0bad938add58144209417f90986fbce707556e74d45ddb6efadfc01f42f2c48ce8f3bf9a42e86b364e2ba7ec96ec8a2b1ebcf545e9ae165aeb3d8fee7dd35c9f8296557716f7b380fd9a4c61c01d31940b0db13515a7630dbb0718c056e2795d4129245d2ebf1234c0064ead14f193483dfa03eb0d7f23397b3c227d837294375f50c6fc85b4ae10b7973d496d33f90283ab59b8f1b0af63c5c0df11aa5a53f2ea0bd0b02fc0a503ddfe6130969c26a8d901c26d454be6844fc102cc2c219c14d67116d690c7e4fd4786d8329218aab9197c6f7e1178936b9d976b8f05745d96bf85fbd23c0c95453d2e4f77fea503576a61f548b41bb48961361e815105c133fe67cc627c8f6e5a1df8775800070e8512e3078478fd28987e59a09e9980697fd9d0e03b874b564bb8d541e2410be2e7e8c3ef90cbcf6fd29878f28efd2b68ad391b6ce0d66b8516d42d9d26963c21bd829fdabed390e20e33a87fe4a45e104a55cbe677748b85afb5007295416a0903a75264b9d664ef2a5ff7b9f6245b4a951e5fb7da14dffbb9139bc82d8dcdc568160ad2dcdd8ad76b9058b0a756ca452f7cb3dc4bde71035335cb328d0f95170e2bbb054fe5c39a693610a18cbdbf8e18a1235908d48e118a12638d40bbd502cfbd7bcc65909271590caaa6f5a1d28de6355f80de76e7abd78bb104da6c79f6d8bda992a0eb338cc571d5f1a8571f98338eaf08cb9bee772d25443d07ae28169131a4c40909ce90e98c42408b4b3d4b9c56dc8f1aa4032eef0ffb01b79bb90b07b7db4d70c24722d9fcd149103cba6e6f4aa2d830e94b4ff35b8e33b9f33cb836b4f075578c9a82d147032a0a9f857d691c8e4246e3ce7e46c84671ac18742f182c59d931a461b75297c0a48204c633c3775e5cc6f1fa4845c8b874e0f732045c4eb13ae588cdb46b318826df8c16cd4de54b98c22147f0cbc6780c13e64e30dddb6f938a9a31ed23d15c6ac22334d05bda7f3e78f1b5c4b90e6f1c715840c3e9ea8d4e369c89fd117f8d3218f65585ad37dd00453b8d21bde6856fdfade98444019d08435a8d6dbe32d5749d1f3b8f45b73177203102759a4212ee53b299e2ad2424f4406f6624f32359f369ad9e82d20913b2a0f8fa0ee3aea481ebecc3efdb104a1054bbb155e2e6e1a9f8c0d90cf39653e85de808f3a07b3e0cd8bc27c62e00a2cfeed58e710bdc0bd49b779ed1f9c0e7f07fdb244930d0b393728e15013798d0454f411917ba961ad9f745d35150c62395c1127f9a00366613abc866571830ea7b216ef6a074c671f7c625dd75616dc385a9a6ab5e928dd8e37c8e2fb267456489f0c5bdfcb6b972b1c4a1ee730144a3aaaeb6d9b0bb1bbbb789aeb79bd02e05b7a8b694a92b672c945c4284abc04e625b6863b4580258b56840c51884643d46e79b05a08f627659eee69719100f65f6dc442304fad8045242370532d8c1b6edb95043aed7a7994677ee4ef6fbe2d584991182a65f4211ad3eaae8b32ba943c35c37da1751d23f2000c94a97ef6e6916bff9b9fc83d347212a0c8b13950d090dead65c4276f1321c0ade4e2212448ff8ace264b10ad07ef513bde0f76e621cf3ecb3419fa2935e49ec01de01b515dfa0f78d9725497454c783527755f3bdfe1e63ab0e2e3ade7c3fd6698dd04c681e01dbaa839fed4a54686a78b169a9fbfeb85777afb835bfd3a72ac7b325e7aca350d2b725e36aba39a142b26dacf38741812e5c5816719554913798999fa6ccf3171d45cf827dbb782dbb2ba4d620bfd3e6722fa190a5c2e0596a34dcfaa01a5508d2916b7d729194c8e0157503a1b1c7998c76f848c83ceb3611d4069aa99dfc3759dd37d2e264bb23ee04636a5dac1588c0c27204cd0b0c8943469a81a802e03e616e1dcc43e002c8d6b848e6f091267ad6faf08db6eba63191a1dbb24a3dc3b19d98a432adea1a4c2c12c19f2736f6f41c61f56cc2c2e4e16b9cfc6e83fedb9c8e12d29686e3a6758d1c17b9fa730392ed9783d0a3dc5c4425db7ace6486c5d9f794ee0ef4cb8071c00a1b124ea5fec3b68aa3955087451ce0a906852642b7a54173c780e38664a54b24ba3e8ddca5a75a9285e1231912df1fc959f43cdc103e4d2996fe18a861dad300493cfaad5631050cfa46e23787ff588e4174ad6a2be689679aa494eb0875fd6f2c5a0fe5b711186bc35b6a9130e733f50ebfd321d656c4a57d534e614b38f82705bfbf648a1c2f45ff8842159cf5807f1c7bf610c59f7a462f59f8af54711db91b83f59bd42a1d6a524f28a8117adbc16e7d9c82a5d3803eda112597074ed39d92ffa2220c7da87f687137028e59f73247f98e0df016a7c98623049c3b6fd2f4fdb3e472060016097e378592ccf95fc1c4423e2881e2ce704c5093e55760d61e1099463bcd49b6c726c52c87c8e4df11702dc61df35515f422f976ed7cac2375e825fa49aca1f92c68e1d1d255c1a311d3c2d13d352351d7c2eb517ea1ea85388e2bf88ead10e7b500c46b2d25f5bc956a68cf64801eb82d20a7fe6ad53815320e9ea1297ae3faaac96262eabb9c70cc05ed88cf2b7d18be78001b6037e3888a565346cd921411b30b8211e5d6746e121948b88f6d077090c2ce125326f1f768d18ed3ee934dd516381ac48e2cb8bcee8a79357e78470e2a03c2a4217cc5ec70eb17458199a708b5027f22dfaf3577b1db207adc747bd37324bd22878b080c0c9e32507ed8c91cb2bc2ad5e1688e2ff8844d9c5130079a35c4e52ecff14f413797c1c8cb60239a6c69e65788dc067b8f8680828fe61d9d03c2807986606b0b0fb1ee1b41ebbe845dc4761f1be69278461c8452a7dde89c0f99d7f7a3a8021571d8cc321afa6ce354d9b66e8b224959102ff8837a028cb0a8efcbc76d95e933a2db87970eb687025d8424826c1200bf43f4bb41383a964cb37886d8a1cd9ab5e236686ce6c798d9d7d9d3aa0d05b504dfc7ce4f016dd4d43b7dd15711f8aecd46cad7a078038c4d9125a0bdd34374c810335b1a48b8a583099fa2c983b9c58252f215ea86f261b4950307bd89c1b3abb68da0ae0ac5334c984ad2837dfc907fbcc590457d8fa2759aeaecf61868b8a0e84af8c13d05fa83bb07f0e9ce24fc0a012278c2a6c4db01e27def98be0239c67c76c714a0f73cacf6926b67a46902767852bf17892d77a770af98f85979641c712ed40db59aee4002b7feae06e16bebfa1e17848bb8f7acca8590a72b3a9c183c0bd6c85481424d53d2bc2c6ad8905329f337948ad66e151ea2ec6ecb9499a811d86c888c93ea2d47da5d7e885f1ef494434487297450f126b27f75eb716c274f9c8cb6415a089f4e60961dd38799253987ba8116fb40b03b102318d46d6dc9912427967b2d08f21df21518afbd45e279e1e0f129eccda9a0a57833aa67559aaa6581238e974b566c23060ce7ddeaef7a25e55215237637799256093f4113810b3ef768570b05e9785c1f63421b8de6ae69d1023ffd8f0b9351f1adfc69af223f29136a55c190225948a9509ca8e2aeb3a23bdae9f9407f0b49aa71f253ec65f0f1923c1e88ed86ad2a6c1771adfc96187f9707a25ec954545e4425375d04e57c52ad450702eeabf108c4fcf0275ee1f077debc7411566d214a1ae38fcfcb2fa1db348330eee2f5ca51791fbb1978bef9d280474a5f746309706427e65eac1f2848c885139d8046d26903fc614b5b4cf9722895d4051a464d67e57ea93d1209d5c07b4abfcaa8150c33d3346dd031f60100f2cd3e123cf6c31a4ebbab085367d3779cb5c0f7699dd81e1cb98b882232f193c434c71174e62cbb7438d2b087ee35df86bc3dd97b6944c912c017116ec6d22c4eac6287fe7ebe8af597ddd01fb5d9814777562228a54af1316309b3d350d74fbaeebf90ac9e866dca21e2bafec1dff340473c30def94b973a23467c83f57a66685136b05c405daca2bb4ffd0adc45b2187db71309944c408a00406d38ba38098d9939e2a3b9a0fa60ee7bdb61476a5a432c6a201ccfe6df581b782eb45e52d4b2b226e021ab8583ca2c08706ce6a161cb1a943c927bce3aaa344d94279c301674c7b567a0154d423eea6f2995a81a456e7e07ac3c6d863bc07eff48459b42eebcda0f8c63f3534c19bd2229530e0b01c151101a55fd771730c8a4a6617d074a1c1a1e34f0e8f2933e9290ee2a01e48c3d082090ed6d4a2341b8227bb6feeacd14fb715c6a264c546778b56c539910fb58840b2e2e7c3a8e7274bd95913697382cb194576842352b14c7788f115287a03ca70674d0ca9d7febc1462d462b0fb7868a3a2c5c88f5eec5f557506dfe2882187eeb2d2b6edd7ac9e75bfed0f65959e0cd6b3b853298fb61a1eb5144be588feecce687b1d727a0865f123e65a07df538a3a8efb7a9302be5498fd139f7261d125592b6aaaf12752f715abb6eb305274accecb05b36afc8416a091c5a4bbff9bb6e864eacfea71aa3578b8724d89fc75914f8ea66bfaabcbbc0cd82bf2a5e426378ac53115e157cc34d7f6887edbff81acaf99e7931b3cb799561b1b33933f0d025eceed1a32b72003c47a6746464defdaca327d0500cbf868ff33dde550456cfed8eec39010619bd052d5c7e56a39ec8c2792c32611ef20c1ee0d4ab004887e92675be934092a8b5e8053de63aeb0804e4be3eb4ed355781e8a91e160f583fe6140c7a3bb4d90b326f74fdbc8379e9cec6610a5682b396c5a38dda062f81223ad4a68876e44902da0df0a2c4e90d9e524f3d65000a479eb950689a4c490c16fa324ae56920dd5c344a714a2261e5dd48b7a3625f19660345e70325a1cfc84f88c284c379e092d924887949c676ad7b51af47e4d9fcfdeccf9a9fa40aa9cd406122d0112faf8534e0bcf840fcbb4f4603c08f810ea1c1ce03ff494275d8601b211d594bc69324f24def14dd341807078b70df5c2b6c11c7b1f40e69692401854208f73d714cb1ab5a297e1e40b57c636ae795c56b84b7c12e8e191c0c6418aa0170f343b3be5a99ac112c6e61a37daddf55503a3a1aa7cb265f7092d87dbf884a6a24c99abc39c4cf11dbcd1ca5475481426f70b4fb6c5e2bd43b50341ec93aca59bd83eff4ef5c351368a24facade5b857bdac7e1ba731b11253c6413a62e511996e5308a23523f4d9d2ff1e20bf019f96623536f2cbacc4a991e8e33b004cff6dec2710997916981bdcf6ace3550c5ff62161b57c56cd6079586b6a2f5ce72b3f6f0d20e8d7c6b86d28e6b9e009413cea6113faa654b3b5a850dc9b53240833ea474d3b8a786a7ad2015ac11884108b46e8a5386d92a2dda1568fe53003bd5bd7d7f3d4d0984aa6554f094b6147f473f2ea6379f45c8e91072bcebeb608b0de62bed382abd7eadb93b5e17dcbfbeebe65374201e6dfbbac9baefcd2326315d71c315137efd4a362d9502a3b32c013544ae8ce3c7e5dc01072fb30e37ec36248c3acb674ebb46dc709ea991d0758afe31f2225e392c98e3255e087aa0da7060742cfd61f5e8bdc6df1b9ab17dc0fdbab6824804c920debfd3c29c410845902fd039170cd90ba7b4a0c4881e034c90532c5101a4a4a1fa1a0d1f81966309f8142d433f4e119a038472a9a6bd7e59a2e6a3ff840975ef6f4cbc4cf811574ccc72001990e956552a689efa92161db66d0cc02a9997af6d42958f8f404b34e83f8164a058cf99e434f201d5376740c55e8609195cef970608beae38273252027f44b1926891d9df42cf8406ee4b5eb090571229630a2125e81b835301e0500bcddff6d43171f2f1db35552caac1c33bc078701abf173afea254d3e38bb1ba63b46b41ff5702e1a34b3cae284eb09166a4bbc568e9cd97e3e0cbe4af04532c592a2175a9da7c386922086629042804834bb870774bd7f7b7e8b81cf4bd2d5cbd77420bd63f6dd3e056081d4949d708511c56315b2e920ea9e4a9d7f51c74fa5a4e93e3df12cbaf55e77f38d86fa16ad256e6ff6aeafe32ac5b4cd47155344d8979fe588ff8a1ce922864ac006b3700cdd4d5bff73e9774d864a366c2750318668da7f6f2e9145e7d5970c42233d35d5fd659b2060acdea3685ff7182982fff92a1c005bc7c65c705e389917a97823f046f91224b64017212833ab645b2cae4a24b55103690b9fe1c6d976f6251fc83c2b142c819891f3ddb025440dce853e794bad4e25a54d2745b594329bd899cf71133feeae951e78209c0fcc3c81f5dd2887cabb6200642cb35113d81efba83e15ed13adc4a0e65d0782ce2bc34ca31e15ef08ce798a18694c1c7ab482fa193a246a5db9137920c518ae314e7266063bd6451540096cd65655b12d2a632de710426b9516829adebb1f4a43e23c8f7b216667bd61dd7c47f956d744ed88fbdd9fd8d63c3a671d3f05aca5db5378d1e463cc7a37c356da70abff47e37ebdc358b3917d12797c174b7b288276d43ba8b989cf2cefbbc236a8e8938d0aa0c04dd84df96de1e93b3bdc2b8fd2337d496df7ecb1fef4eccb243dd3d9e5bf2fad2d98baf64cbb591d7b76af96693662a6aacf9d134990c7de2473d34fe8de84d0a9bb547e62bc873bd364408433d209d9798c744c858c5f82769b09b7fe8b90b4bc3f60e781327f35677bd9c5a80ab079a64fd7a6493ed67e4d4055f56dc3887312feda1ab7873e493a7c4d46795a5f6a05156d99bd213122a5f5a4dc613a04bfd1af1eb33809b875a4a48b85f026ac647ad1228f2082eb876e06eae99e07b878acfd74b371fe4b7cb0e4469e66ea55a713a763e5e5318a0f5e25eae3fa51f1564d62b9ca5775daa770dc2bb844405c9f8ba6fefa2b121432aefc6df2c377bde2a93b27d99bf4a981699d271e0bc9cd6595b7e3d1d1924153c52bbd9a123f152a22230055bd5745edd2810846d1a90f6652c506384eb4ade7ff4ddf445db742c113f368e2f2ca8fe7cfcc384ed1181110dcc8b89758212e3ec6e7482841fdcf7efeb55e785371df8a03870b1a83b3df38c7c2803acc8f4533142de07d117367f0795d513e71d651e0323d1ca912aa0c5f691f569e085740e84fe60b07c929c6e684be30d32a7532930e9f581f23edb8bcde5373e2987e85ab4fca86210da94832f43a674d7d8e5ecdfbc363612ce69f73aac5bbf67a3978099c8cdf1bb595481de88f73114010e26f65f29d767d6b0127a0c753311811dcb6d38c2e0b6cd7e9dc3dd4fe74e80adac927a6f0f3f0807c5607e4f170f804fab1a375603966233deeb3d86e1aa23ce6484a3e7b541528e728cbc9b8a78724beb15d3c50daa6a200ff8733d5fef6cb1b78afd90652e5ff9e639af408af5b6c4a27921f5d60428463eddc79da95e3d2997d11368974db682b4fa91cf31d441835af7f8346df204bd283abe937ee2b86faae7d8c1663bafde62002b733db01e4477bacc1404aa92fe73780465079c636f53156c28aab70654772a6028d90399b2a1e4a96dae7cae6c11f928c46f8ae66a7b231c429b878884c211a181c951d6f628bdc1e8c76e9e34064e5164ad4156242fa8b6a441bd16d3fb550710b53b69ebb40ac75eeb03687d629925308fa26d118aab9973c01f4762c871b92c4174cbecfc2440aba0ad12ff5ecee559fa9a9b3fdbd4565cfdbf82867c7a0f0e4b67a4cac65b5edfad0e1024508fe3e8839ef7bee821012f7b6a18c7db3ba5833d12456a76d022e73fc1c11bd8e79f0c2318abd5e89b35e0c034af32d12c56edc41c944cc36c5d4310b94d9bef6ef4c45fd5a754ec509ed26f664ae7e92147c249af7c19791f5d294bfc23afd81bbbefe91c0505b5712502c7e05fa3b8cf4395eaf9eab716ebcec9b96898c344cae4b8638f1e37a6861a0deb34618dc0f6989b5e339373dfb2b0f7772f8019e3c2c31ea4389bcccadf278f812a3f3992b61bc5e034091f9e8747a4ff1359146fd02ec1f9d7495f2009188fa475643df2fce0cd2e24e834585f3de16d631e055e82c870ab51a9f388538163b022085c472c53602228e2e022963e97b310de78d5cf73e7836fa1382ac56ca92c1fd364f37c0598acd14ef350c4912bee6c3d98b883f1d799b32da3c650d2892f77b833827bedaa1a94bb3e2597e67d4e5642919dafa07404b2d0357b2f4975415b8bbe501005d3e7a9d968f86924213df70fdaf02a160aae2781fa519bf0e8a673c5e32bc5fa2ec9b11bf965a20e8447f5b28e35fcc7855522f920c804812a0f75df12e3052ecae810a03a24f3e7904f8d4a975042d31d8e46add16e761161cbd8ac07b34c41ef01f640b96cab6c0b3fee4020eb7d47f9f8f78ec1215fcbe62ffb6d9c9d62f302953872c3bd9de390ba4be4fddd71940ee0cc8e559b9a1eb4fd0c60418a693220d8add8ed286781c16b1e72a34468955bea45222fb99113fa1d03e02ba2d85440b96e1e6a1c600fb15c70a24c02d655eca7d6e51352972ceaa1abe79049d24078a5a839fdfb60bc921d91a15dd762417a25f6fe576e28121b24c94780cdcc459ef11d05fe87cdce8f008de3ecfcda8ae978b41486c6c30d4293b69848bb6c16fc7b9cccb40bd9f816e4c03f193845797a8cc9900fa3225fa77eea1dcb2ca594e5dc5c603a43214f88c1e68900da1d5f017a6c92d9014501a81914b57ff7081816c70c2ab8deb437a5d2ab366432aeb085940298d14b1e920b3ec32f8d10e9d6d991d07a0d4d2b6d01e46d9854d51fe93808bf5e462d6f564a3d240ba6c007bf2f429cea22887f41665485000b6f7c5acd9143e64d10cb3850f8dcb8dfa8b2cb606b82bddbd1bfb823274eb8a1ebce004d4308f24804a0f8e72b6febb7e976067d09a5ce27f98518706b640815018a16be417968e9506ebfe070d72a0a0c833f076a58a362361d44953a0b7b178a61f3af6b9f4e1b55790bd32b6ee861ef02c53854cf04a0ee5d38b0359852616e7c9daa38db5633a3935cae460d7d25cf494dba7e09fab753dfe7f8f5c727d9651f916afdcd59e77699763d040289740dbcee025a2c53b9938057d6f6923717bf65da3613a4efb9184fb8779f6531e17ab9b13bb9b18844a2242092ddd076d3ee0ec287ff738ad785c1f943f68119873d02cc3f25ee16b8f578e1d630a416bb4e259101463d055356b093f5266b1a0c3f9d643135cbb2d07ba5dbba99a56ce69627ed7da22cfbca4216f79fd63220d135c6b6f5ce12e7cc066ac79bfbc210e8d4cb2e4f5bc56fc37ade5a7868b7f2e1b24ad851fd60c523a6d442ed5bf659b846ca59753fe6038f769b04b55a66833262565669a4d23506df97fd1105c4d4ea0553027eb836bee0a6841cc95e51f862f8b7ecf312c71add403c5ebc5c8bb9d0bac1aaaf23c6cc911e20eb21fa1a55c894fbd8356136982e95fe89ffa68e866c508081d9c7e14bbfef695ff8fac7257869a48fa1709fab75fa6f65f7773e7b8a99ed82795973b7d74fd78eace3dfb1af54e766603f2c16689f5af706068b9591c98d4a9ce6e559c47e647711bdfe1165b0dec94276c6300baf90cb0418c02d9c86a3ae19474db1ce57ca8d64f9e20bf9d60a686f983581aa09522538807709f91896fd8128e219ea7f2949b90da1c66288da064f11379eedde0415b5eed4d66e75b05dd7a672ff30b3a90ee24b880ca24a3216b99b4bac7898aca54aadc23013ccd36f76ff46e9ea9324c615d033a9543289206fcac8e13dc6e1aaeef833c68a7e221e1141787e9e0a2c9b298cc882b25b8724c6129a710905f151a425873ea89cedad980f3561d11063979646e932363369c3da6e6c6533c3bfc7dc5948efc42ddde4199b39cd5d545d9593582f9c9cbbe31c45c17d0694ac45f604fc22a6c95c4dc865339ea8216cdc24d19237d708920cade34c465723fed125511238ee03f19155a41e38041c161ca0cb2460a81185f795a7d0bb1b27f2748465327f9613dca3aad4393016d62e6948062da96e57477e3d8a503cdbcae8cf2dddb27387aee89ab409035d0c4813702dc6ceedde7f3dc2d40735cad4b77f0b11efc22fc557f69de3ef52e1c41387b70c88e4622722997bb01ff3503304d3b3df6a61f9c103c082b90f8be48d929047feaa0516a272b560f25a0b71f26841d4aec77a287bdd26a512d11406f802fda2bb1c7036be6ec54541421d0b2cc7eb969183e93f39a5b31e0af0ffcdf41aa1b458a6c1201b141dd348fd2f0e9922d46819d0fe2b1f4aec1ea60d2014234e77ba1235b91428665c43774381d7129f5f68e564c2d47c0288c6e4b10d61ec8b43559f1ce326ef910878c1672154dbca404c1c6616750cd51a3db2d476383397f21d50a3372228320b284919b0968a772f31be90db3fc76c522d3ede0d506634e7a4613e8e651fe256caef546c89c07502bc137a6af3d6462ca3c2c8eb6ed5de46af3cd78a13db3f25b1237b53d190d0d9332eb331db7d625e15c9d46d03f70c3fd089685db8ddadc410584039049080d53222650d4a94829c7c61d9482e0cc460c8c6a051545b9e4306ca0567ad85092b1475c00cefc1020fcd3999c27f77c28d872fea4111b1cc9991c2e5baa6ac096e12d1f5404179377d2952fc690785d3e11aefe140ddffbf1150fdd6a77abf3a0c406a78b4ddda59838bdf46f7bb2001ee5c8b5c691418c4646d66eb8dea82a41ab039e04bb4b07ed152282e6848b2870ed52aa05d8e76c82fed6bb9f997881c9f3345ce7849e7733cff7a11dd66b12097dfde8122b7dfa6e8d205fa2c158bafe58befaad25dec98e8eb3cc08d987e532cb1aaf9797b77299e12cb3c9ac38d7f5f21e299bc32b3d0a0f5f505eb0b7412fef9e85a687b4874003fdbbec7649239eb9ac9ea1539ab3b8138f269d597a1a6ae8bbfeec200a3ccdd01fde61e14047b5a97768c784d57420d1f7eb19aa1cb66ac2bfbb86ae15de7a90ebcd87bc72a7289043a8a2ac72dda01bc7a963f0af39aa961cbbe59a0ce008edf4cb5bbea899187ec940eef237f1caddc94dec495d1ff24a2342cd157c9e0dd00d03f1067c4b40dde51f61c4f739d98a87ffb12d7b5b753ceb79738b50a8bec3918fb4f3184177e0fb780a9dea246ae74401444ac8c7c88b613da340ae8cf76c7c524d075b607c06f62b8acb37b58f24aafcc8fe654f66fafc27e1f092d2c2ffae84e9a445a42f7fd1b92be1717c9dcfe649db1060a5e72da08796fad3949b978e3b8aec618b9285c2c416912f7f8fc25c444a1da391ba1670b0188f458e93d6668b12009e7d9acc699324502b3e5546d55371c84f76fdf88e98c87950e2663662b237ff5b36213141dbf1ec0e3b67930a9bdf8e99f3971dab2661d0f4bac6f0992406536e9d97edf4560e000271e9eabce3fc0252efe541a8d0b2aaadde49a45e3015b00d28c640ab305be0cea1924a052a14eb2d5644413d9840a89306fe23f38967f6339a23f60ce8d1d8c805249db92064e80e641c1b14121c0ae897b1c98d14215096f7513ecb0bb5e76e681f2ca96975894b4545c58d6b4dcc6bb52c2ed4ef89de7a816741f66eb47d6bfa59b9b5fdc06f494d25f2650b223f49ade5cfbc91239c403fda50136e4315bd4954e52d5c8df688a3a89f0df009a90348fc871e3c2d4d80efe748022f7680dcb669d1544ab00b2cb921ec6a3db1ede956c362a4f155247f8b29d2f2cbdff255facf1648093c06ed9a1ac000937648bddb20d9267c22df95b8d2d2d3af7a8fb1e01c99c52f320bf24c2e3e247a254c199464cb1251b88fa4f34336b5d506bcf3261e0214dea2b16c0f0f39a317c86b61a67c4ae7a1567cd48545dd6067a70e5d6c672869db7199f73736f51dfa5605f1a6b1c88328fd32e964b5fbd0aef6eb7b8ba51aa53773703aa0e8a447afa203fe8ec4d799dee3a22612b301ab441b5c9bb01d0e147274a7177b14aed661a8e9bf971a193402b692a9533bbf25558ed815ea76d19199cc9807512262608a39cc9d99f4b6220a5b55a6712046a742823e07c9a37c4017175af3a3066570f20113affd594af79622cdd431071d459d0055ac0bc2312aae8e3106ef4ac907218e95ccbd5c9e9e0fd1501bd4ad12b5ed0b74106535091eda74e44f1c4325dfb19679c8b521900f9fd2b6f9cfaf18a7b4f6ef4b7f9d8fa3dea6a68bebe4251324432bb74d9c2e1612f3ea339429f922488d0f4cee477e34c25b179f02df073f2b8948ed6f21a27d4220d0e147bd371db9ec50be27c6198b28cca701316193234d7350694b3348a7c91509dfa49641ee7c2cab9fe4ec0d34cb24cd18e8f383376f07692061eddbe33add07c91a388c88e82e640ff96b29be4869d78a8ff196949715c8d7f73017a781aabb082165f67489e68fa385ac4c412b1ad1a31dbb284db7ad4c83b90122434a668ece2e4986c5771334faf2b09e621d2df35010b295053c13aabb77333fcf9ded4c15a97cc03454bb2a3816a9038f541f1b0badfc621cd490043acc1bc5592050c2927476caef233fb3ec2f7999bd0d331c600add460cefaf4b6b32fedc58e1cc29214c1077f47bc9167aeb16039cd812db2b1895ebe878d856a1c6b80ba0cef8af7bcd1bdb046aadfbf8fed8486b84104236217befbd775d0c9c0bbf0b9a96850b9765cb6205ccb2832acb0c1e4e0f1b15dddd3e5f2efa22c014a1449f0a4dd3342352984cc1f403196ce1454c3bae421e7001175690396207333889694584d19af6258b526b37314dd3b83cb958e1eab008d183c54b8765051907af303b186bc95980842272bd40c7c7e522ca6ab8d18e85f6c50814b4a61521466bdad4ae1d8b2ce8148667185094502510b90edb69f9254b173da3e59723606831088c0f66a6fdced8bd77d11d3ebfe471e4d5f8f60a10dfc36ec65858dbc65a51a9982b1d1149aa27885079675d807c03be5c996a56cb2f57b468f901b11a116ded1722a0da6a316b6d069b0045efa7e517226a68dcf20b112b68f115448bb017efbd37680950f4ee71d58e786d2db80514bda654a3947ad9e5ab6776641be72cf704c9fe0aa1b3d685c25eacb5563b81286954763cb7b2a87e85a424a79b5ba0324fc5a808a9a79a3a9b097b3c8f0cf9b842eeedd1ec7a20485d6718f2d6ce9aad943bacadb48f46a0a7b3e5671721992d7f44496859250f4a5482c440275f93524a0f05d27b97524a2ff19105a7ba21a648608255b541ae04921e4877d42bc4be9ebea802d1a52f303003c33d7cbe08a1a6295df9003fad2dbacda0cdae9d9a853d09d0b6084b421d30dbb4cdb099833efb9e04485b84cd11727e245157f71d4877d867b741e88bec3ca1c37487c85387d01729a03302145bdd2dbac352987c80c43d8f7be61e21f4f968b6cd43a06c4977ccb992bfef3a7aebdf821340db126a06312780b725d40c607a02e0dbb0965d5ba9fc2244126165ea26adb656218210c20594d27aef4d0aacb537cbbe0891d42d335f9ed45c2dca172b611a6cf9c5ca96bed2616737d36173ef5647b451a8adc95f869d5d91363ec237bbe8126bdfd7b04848d8d9c3fb0016fafe49ee8f72daa3b00e57397388f8b3ff04cebb605ec64c6930a9edbf3c3db5fc62454d83d0f2cbd352bb66481ed3075805bcd6e6a3daf643c0a44a44ab743d3f6a9b362dc2cc21efea9dee563a21e408a005c301c0cd8b95c1af6aa87c3e1ecfd5e970aded06839a2af32e6771cda89d74e658b569d372e690563af912ad54d7a5a64ddd238436b5f62f55d4f4ade451bb53414a20fa24461a9038459b852a20b56d6aebc1b8a09b219e7a310b09a0a7d0f18928514500786f63335d9f00f0bebe8ea94248480c810330558071f26283587d95aa42483d38fdc08987324874a183587d455385909ebc80850c4e346591d2265665165343f020039b150c0144acbeaea950a63c6181e2d215cc37a4a7b54f8d66f6620bd6535142653c549849dda469531fd5ee09bb6611a9b7f9a69e5bee11f23980b676293474aa3115a2323f7300e5172b52dad593be6e1377a6b5d14caa99bdd76655a36d67968947d3687612ae6df628ec48d7f3435c3dab883857361367146acb18a663b2b0456154409a45db626553d9502119a34d30307182c31c61767d53cb8e6110aa9162e2ec23b55568f10328d4ce8e43146a6bf5a73291da042217d8c00947e608d34a79647496ee10ef93d8687a299be69ef9a25af351ed2b27b5183cd4a40c90dd93dd67f4778d1455d4f4cd47b5eb2b129bd4361f715dc2ec9eac6bd6f4594ba1d48346b2339ba1fb42913b7e57c4dde37e7fbf851c0dc9a3b31528a9a9d1dc6e441b2a8b1dfa0322a592e49db7f6a844b1b79c04379d499f0ef213b2d39bf7a27ad11ac9a9a4a43ebae9edb51e79bd0d49a1e98d48682a81ea8fb2b62245627f9409d1401bd93022a1a704aa379a6d9a360029849a6dddb6968f0402431a12681ec99b36892f4f505f9ec0a491727256e860c592154d7ade524aabe75591c6dafb7d5588c9324da5aa0206186fab55155338ae03c12aa278dec76255c144a55adddc54f10208b27070c494717383d36a89f922474c183166c43881e385882a50e61165307951d884aac1184fb12f62d2ba286ce29e22236b88344d31fb45ec1e292643c94382f5c7d62bb024b9e10a1112c8472309707e4e3a91e85c218c30c208238c30c200ef6b96364bfa43459582424de9ec48a1b323050f3b5d86a081955de0f84ce1f84cd11b8ca0946691cd29597dc09873ce3955840c65c59e14d8ab79f9d138029c979fd3c74a36dd413f7984b74902cdfb6c014a3b25ad36dba66a3926f4499d9a50ea8e17504e0a65a9149d7c7954c8d21f7bb92529a9e5d11550b4406c00e5cc313fffb223e8e44bdc8a10c127a2505dad0d528856ba43d74a85e84aa5f0997492d44daa3ff81be87b1b4aa09861e95aeb052381f08bd418ce630bb19b547fea90f7cd564dd59c9c0014ad1a275d675663d76c4f5a6e183061a4b484b912e687303bf8b897e3c22c8569a2691aee3a2ad050b1a65b73db36cef3a800438514154b503104155154aa1508520103153850e1c2cd1463dcdcdce0e04c2135c59497ce4a9f29e713357af8d10107ddcf1350aec44c1183450c0d625020e605f9da124d97c415de540f0a52a04b50fcc02546d3f418cdb64d9ea410e46a9a31fc252524c4f09b664cbb18d44304e79e2abb088d69f814d0f0b56ff832263f22db89289315f21185d14b307532a1f5d6036194de4617a0b86afa39955ddcb5428a0627a4534f40f037efbedd68b68a26a05741ac13d11c16803fe779275285744e87868282705a17c19c2ce58f772e07e356c6c943f3f2673ef3be1b1f20a769af1c1288de06983598615bc01c454ad8314f3f777cdf7800e9f9ae5df60c7bbef7a030fa656dabe17e86aed047a4bd02c5f96dc2eef6439225aa80020831a6b882c5ad3c5112049393175210339aadd29e2303f8aef427d0cff028ebee9d0db9397fefad9f5bf625893369cc1a30535cd04049254135719973defa854663775d3ebadd3d6f1888217a2d4472d2010b2b6ee8e1073b34e1800d56d4488541850c3cc4e83b5959435946840a493050a408a962f427084930f4474d9a7e851186a6ec82b3b727201116bc6011e6c9851aa4d181d0078ca8018d0e4f4cf0804d0a3e4c9a4571788071368722b4189c80c8c8707292413935f9c0a631c656e229335369a6d44cb1c9c152150aa2d6071be4d42d09fa23b7602ff407af09437f30fe16e04d4b2f3e885903eab4f4e283962e3e54e124065d5888ed5eac616dda2c1bb2d6621795526ab366b656d7bdf7de8bb55ab156edadad55a378decb6d202b862e8bf7de7bf1d46aa5d99c9752bc58acb5d65a6bad338bf55826ae5ab779f2ab52fef7f273808d2ec2b5d65ac1ae887befb59ff5d3565bb52cbbf766d67ed64f5beda5ad73ce39c1325a9d942b680043da3ecc587d43d05db5586bd7ccecc3bee0bd64f50d01beacb674c7bc5cadd5bb18ac60b4e4bd41d9192a5938c7a16066c0793187fcb1aa6fd5b16eb0ea036fb4cfc3c9bcaee5e9ec7860cdf5f33e21216081d25213dc7bd78e1c0490423c724b061063b96d5eb7510199e8414de744214327c01c2850e680913eb8c20464ddb002500539805c68b26ad9463527204f4b2f5e9ce8ec0b9075050c2f5268f0c2e41a01aa5a7af1f2c2eda2839b056cb5f4d2458bfb04e2b4f4d2a58b1890d5d24b97233427106ce9a5cb0f2e6c651078853b1cdcf2308843a907ce2e3780202d9374d305052b2f92583b68a96ce004ec02fc6c30b592628667868b0aeaebca70f9a2e08106d705132f8aa69e0d0d940e0a1de8e01b24713bda0d9eb6a032ab0c34e3047f3e300683737504050da101a30525d1b2db1928503c7966a6a080029699826209ddb6336330e6b286057ad84717502097a9d7410f639e173861c3b54052d41357a8d0608398186d52ba8192272d624489418cf680a7d2676f56a368690fc2f589364dc12402d88d4a815a417f148c143543d1543a55852c9326d54985aa5b687daa57b8e0d0df2ddd01eb0fbbaccb7adf2762efdeb9cf87f7794ffe9b591919b4f8a2bd5befdbe7c3f37c9858ee092d9a7bf7f9e0706b13d3f8f84c63fc4496c6acc63260d2f8dae703bb5abbfd7c68308881603a7b8bee2840674ee490b97648337d2d02da5e92f181e51922c65471e2c90cb40013ab55aabb281da595524a2ba594564a6bf530960f92f407a553fe003287fcfc82d0534b691e9240f27248f6687997fce56138a8570355857ad09ffada03aa07edd1833bf79ab1c62d5198c671b9c60ac06759c76df34eed13aa56cce5ec997874c3b944614d494bf593c97bbd74f23ebd4c845aeb8204aaef2c91db351bf1accdaeb5a004aa3d7cd8cfd0886b42a93f47b7b517e986206284c2a05428c903015d377c275425f3498175f56f955519e34fcb2208ca9f69c3a3dbf3446e8b3dda029956c86cd3772a49a0fae982cc51a790e98284d57bf65e9204aaff7cccadd6a5fac905cc98999dbc13d10d54a19fcaf54cc2aed709855f31fdac3593da280e162ed74a062c5c7910c40c1fc8a406210a40da322a87293d2fcf420bf6f32b41b68f7b9b2f87dc2c8e3d3fe4ed8be46d361a5b6c224a3fa315965e7021a9a584c90fd13d799ef616c62058e59a5ae90f7d0587daf6151db6b44761f4c70a62508b4774b42de279dfbd48ed9da81a69ed892df4cd221285d579ebe7eb6b38b73b67ad9f3308ee6d6afba6f5760aa346b814e88e8e48910b88520da5144c768c41d0e5fa61ae2ccb6ac6e60e21f5292c5c025a0a7bf574019cdfc2681dd5b4ab4d9b8bbc6c4e1d98f8f848fbb23b6af85183ad40615edfd0c3228c5aedb68a5c22130611c6e685298c1e07200cb796695a06227bc28083f66c88c2661385bd288c524a4483b28ed9c259c46842b007328d881387a6da2f5496c5143aabf7f296c2b4ec38006b16f145a2bbd86b2b829d4f5b9b45fcd53b30dc56b7c7ab6c555fcb15fad86dc3b4de7a6b3dba4d335ecd8a83168aafd6bcbea0d8eaebf3dd2c6aacdc025b210861c0fbeeaa77abd0fb7da7baf60b2f813c2f7fcae5f343bbadd742d9d389883ab156b482e9b86f436de98e15eb5375b10566f1b256a12a0c4f80bf2cdedea3b6e5c711d150dace42d92cb469d3864b965118ea9b8b3c2815ad516b82d46fd90bb74f347dbfe5d0d4f53d4236edaa47b9604d57fb65d742717bd177ede65202b1be85acf02614b7d78bdb16766f490924de9c6ef53759d4befd7e43ee351fba436667fd3b2becc9ee5d7ea5a25f853d42ee55bf57e59eec3d42bc7fdfb3d68928cd58939403c0c10925f629e23288d4f268156384c2e689501826a24145f6d566a21282481ed6763c4136d0d6e6f934a0ecec98be867a481eb3e727145046e5e0a4ada7025277bf1f767e0d40d232c4bebee6daf20b0226d1961863ee95fbb6693fca1ac9f6a36cc8061a9f8872f89b86b39421dc1ff6442d650862fdf55412681e689e664fb8e3af74c042f8cbe543b40089cf6531083e87cf5dee138430e011f7ac72f83e0806fb5d2eed55dc30f70bc32fe232fe5015e22e0671dcb92c6ed9823147c05ac6168ccbb0663774b20768a7daf685d2f666599659353a3abb0daaa7996633283d047e2e15782d9c61b6708e214361f774e2104e27147625a7b371b9a85ed32c1873849dcf19175def74de85f0f2e9baaecb79d775ac19f988764e2eaaa22be7bc9b5ed7755dd775accb4fc88ea8933371decdf09e30b7f92d0b67addf6613ca04869e5d7d57b7acb95a5b0b8d6ad7db95eed89ecd674d9d5dbc3050293a060b151aaa3f5732ed2e2d8b26c8ea04e30578b3aea14882ae6f7d86b67197c54ac715b68d4d8bf3dc17a436b569e608d935aba66e948872ac6f17ed548b9382aaa99e0fe1e5d3d22ebf8b477404f5978baab8ca5264c68401e3e5f618d2d15a168fe8d05ecf52e52f63cddb343347a681e155616d85e20423ce317d3f5445d7bdf2e684fa738f4190e6db7dbf9973ce39e78d6ade9c7e9f975fb660d8be7977136e1d0e65832bd5876f675b096a07c1607f06ba0e43e3e3bad26191eaece098cebe527d25c059c5e13e68be34b5090c6cd4683fe19e7511b6bae8b29d1dd1d133ebc2561285655fd125fa939d95459894ce70e8ecab2cba9a64ff3218dfde699e175a30b24d42c96dab5b168fe868fcec888ee62e1ed171444710978bf0b56f0ed0f0116ddb193ebeb4606cbff97c6c760a6b5ab660cc1c59ce849d7992d45edb4b35ac65d40d585afbe5a7c75d3a407b96c520dab3214fd96dc6699adedb5c940df15abbd5ee76330861407a7b2fec425c7fe615ed45385f2d8b2a20ddd2aae586291dd432ca0632da49e38e7262e6e6f2f2986bee930b7c65a6a91f10fb0ecbc69af689c64987f65d87431f7a9cb01506d59fec5f38446159d4132e6d8096514f9874675d1496e5ee186b58c31ad630d6f0892ac6586a58c31a16822fce2ed43a9104ad719ce5be779f61097ade621868d35263eddd7c4793b2fe42718536aa6dd8f4144dd05973a151e5a886af1de923da53d33eda56235285705ad7ecb39e57cb7e34a0e875961de4b2681bbfab745cc1aa6971fe8806a9fd59ab85e2949a23606da631aa2dcefe72d1f60de9b238d1b4e87dbebbb87a77343ddfa9f2116d553ea2abdcfdf37bf72e14a7382da52ad10457c3764acd1cda13856951374c75d7f3681a7797691fd79dd338eec3e7ba6b5ccc116ec423aadac479a56f8e0578645b958bbc2cce6ecef9ee66fe46f5d5c16be78e13b6c22fec6e42aec1504af52a9c5e5a15dede42c9a5d34ac0dd26528568bd762d6bed5eae340861c0232bf5696f8546ea8f769c9048fdd14a10caa95e85134cabc2ac59a1f482261449d01b09f0c15082e95538b9b42ab4fd85e2dcd2dabdb00b450935c4ebf9c9c51c6166d1043db978a79d3bae42f5dcb3cf01f85de642f1c8da4a55a20982dcde3e9fb86f9f0f0e4d631ca46b9c2717338726a17454d0ddcb97482765802c43c53aebefcc28483374f2989287087d67b64868121b9dc98f015b5706c832fd33a0285a4e6ca17b84d0c645720449e6a97116ad580f0216607df633a028de8752aa4a8b358bd9835ab417ef03eacf80e2bd98bd6624540c6a27eef74ccf2c8b4825b0ba5e663d14d862dc67d3ae658c6836f88cc82f2b3092a94007ade56704014b5ab0e52288ade5672487154c702d3f23950c0e5ad0b5fc8ce08062e5094f4d0cb288fa5a7e4668a8a087263aa8280f4b6244a165d5f233a2a385104a68c0969f9159c68a173ed8d0a64d8c63b5fc8cb06aa0850f65288d4a3346e5bebb211221b6b3dff7fcb87ddf434469c6b8dfb66d0481eedc91a880d4f84edebb6d7555c8fd0b575df8a93895d377a2aa2a72eabe9cc4c5baaee3be0abb7fa1ea37fc9e85463e32a31f454eabcb3e92a49dba0c7eefb8d8ea5c4ee262aacb46720f0150884dd24edc3bf95120ab92b8d8978da872121703dfe5242ed6d1bea19001b4e8f4fd08120eedc47dffde85aa83e1ea9502e0ea5d4e926662aad952acedd444426c480d584820d9221d61480d63640eea71e1c89d09294685052a43b4d7abbaae23e29d880ed1fea3e87239e9c63827ee44b5022972e22efb8809da29e9c6b8ebb47655b88150067d97f5e7cba32a6309055ec6be8be0ea3f6831094e1f9151f810d5da8beab58397f507cce32a7f72aafbd20498ce8a92dcf4118b8689ae3722e25dcf7e3e88283121c59450c98e14e36ebf21247496a954799c603a29280e8cd484f2c4644219835001a9ef3910c08035ab20a1da48b66951b2e9a224387dc406115708d1dab3d565fd59d17f59d61f714e5dc654585aa4495f5ea231ee45351fd9d6b2589fe52220485d9fa4d547241085a9ebe5972f9e5aa44c188034022f2d8ea04b57a519ebce854a68accb3df5dd97f00d9104a15ddf75375a59a09ee74e44ad53938e18f72e54d2b8ac843c144c739e742d481e48909af6a4596ca1bead9d2f3a42ef112421a6ba66143ece164a60ebd8338a06267d343bcb2848337d730ab666a751be851598294bccd37056305c70e1002be60e664cb0f752a0e8794d5c61028a04228ad8e282271bac012366b334b1059759b5fcc24494b62dbf30914454376716a709e0edf1a55fa2227a58ba9b9f9c2cafe51726a0c816181f30c1a445d0c62a54105dab50166699052af632d62d69bfa7c72c8c54222063f75a0e7b662e4136cd2a50166ebe0d653300a955982095227b156c16818d9e9faf945229f766efb2f08e01c556fd75dab6dc02154d3bc54753cb3d3f66dbf704c12ed8c67d0a05e3a1354363ac7db179d11d3d41e8670d87d0573aa47ee6ae227557c3225abf7a1acefa227bb068568fe6227ad76b164bb0f90295d44443699abe4709de67008a32ccfcad770d8132eb21ba309ef7b915560985d791d7abad955028bc92dca3fb241785a12343cf021c4261a8b35c745fad6dc1c5bccb1725aa98e28b1233f8a24417bac3de2be9bd1577b8eb0e67d1dece50bc97974edab92485b52adda8b669b55a3a6d28e26ba27d0d457bfc4eb452f2e82ed6bedd45da5d9745fbed44d5e25bfba2fa4c4927a918da412bcbae232b42aab32ccbb20c8b9954e917257a50a20577875b2bbda06bbe956566b4395b476b8b9b1de34a5533b06074505e86edc5689e8c694744d1da7d2eb55f21a1b2561e0aba69f9e5082cf50bcd882c53602cbc64f8e00765b4d12215db3eb6d046cb166190e1831fb4893165199ab64491a1694b94d87626a4d8966908051665d968852eb237c8fb5e6b1d304f73adb6d64fc8cc348bb68dc6d62e1739c042060895a1694b149896c5db805b745fbf1f924cdfdbef47ee9b6f87644baf129da53b8aeea794a651589a347d1d6da5e20fd974486d11c98af7937e52edf319c65a28ae00248bd9e7fc4ac09d65ed4475eb5a64f41f48b417a920c14c698282d6c6cea89440750aa1166acb8958732f9388d7f7d8d7eea9618fcd44c2744fcd13408a6597513f806919f58314fd2e4011262720db909165256ec61504198651c0496152ca7955220b3d4ea45ae9139d7c18660145dcf50fead2f5b028babe858990d2f55e0d9a2cb042c6dedb2ebb27bb26e7672bbd54d71731d5f538a96b776cc58b151b5a76ddbb20ddb1f67b35ad6a9af67bb57bc32c6b57729383d2dfbd8e4e8faa6e2ef2fedd3bfdf77befa647bf0678de89a80ca26527d5b7e350446a6c379b5be06254b6673eb03cc2b590adb52c12d9866c8d9f85d808268dcfbd7e3b0eb32c2205e11a0301c1ab2a710299fca80ae722ee98dba6d755eef4dde49e7d41b8e377acef0beffdeef5eea997b92f0237e249952aca2821032831297fb6cce1d77b1f10fce1d3db4f2a3f20f6f36300b559dcbacb3814029ca110a0dcaedd4e1d44e1024a680085cd89791880a4a585be59b47688edeef6b159e8165a7e43c0a4de90927adbba7738446244427b3fcada486b2981561e0ba1918de62ebb2c02571fb9ba85c65b9632041129b344346a48a05903b8b58cba02a56bc6195429d1c987e111a068e5a5ac3404700c152f2da38260d2422da38280d2dd4a9e4a2882e8bfb008f0025140ac69da6ffa137a7c01748734a293971a977a2fea4a5804383d1bba2b4d3e6970ae507579b58c1ac229041fe878220a064324600c9e2c70b81ca10ac283c20221967280a38537c58c2c3c3af4b48c7a5a8ab292a679422be0bc685d335437894e760dd2c9ee422a601570f6ec99e72b5427c9bcda9f5427f1a8edcf53491e486692ac91cca94e72d3f3f4a2d73469d2cf30d8465969b2fae98ed5671533abd7af720dd3f75c15357d0f5a59eafa55f615d8abdb83a1b8baea7675fcfafd589dc3ffcd2adc7eb3faf6555ebdcb3cedcbb4b0087cd18673562822350b27941268757a7075faf9008fd422feea6058b4ba6c5628aede815944ea5510efdbbd6fdfb255785559ace7c21e21f7dbef379c8b5c1fb54c8040f383152d6fc2206605c320261529cd723ad1ea6c76af0520a84579032001e85b741f80d602d0ddfc28608188818717a22411db8e9465d369ce91d2a0165b2de2b4b6b4cd396938e9a4a7348f93d24929a594d239670f41b65eb4b7dadf9a8becadf55e5bebd56ec7aed9d56c166bb7ba874ebe2ce511e745fca3d5b368bed5e29c00520c735debcc4eda459be547015926a69d887a9429cb272021868fe7330ae4b0828936b1b1ed0b98b2130b2dc82f4c625a4e7a76716c7576f959407e6112cb72ad56f3ba00450c82a59a189dbc2c90c25cf28708f2c7d6cb6737cce85540ba9ff5f75a6bb36cde15ba66d7565abffa09a9595ca1e9978990854545f396d6263af92941b412dd5117ce5b6898a69f2246dfb7f00a7d544d0045571f671a83a6744a124ded27274bcb1e85cd2969ba9333db287cbaef05499ed49a98b2a6bf0e204d6951926ddac8fad3663a5989728ae2d402a71344392965514e491a5e83555e6c9e169c0f61a27ae0d22d8f091fbc0859fadd872b2a7c807a7a0cd3a07c7002827cb7d2b4803b58630228820d83616d89a81ec8347d4bde8c1369520d88ce0543e2e3fe98cf3ea9887407644648eecd17897d910a340758c854697abcf4425213d56e762b13c07919d5c3941f227496dd6b6dad944e09f463dafc43841692657185cebe1f22fc16c93a2f0defb42e9c0082327c02caa81e9c4449c1d24d447748bc4990250f8212f09482a00434aa074109686f0f8212d04e81e2a594120425200390da860c61b6a093724e4a6bb5f6de2cd3348cb78de3bacef3be4fa55aad4090c5bab9c1c169b57272747476765cae193378787a7a7c7c68d0a821792031b2d1352490fcd16d481e496acf6c4302cd7979102565ca14ca82840420a0c7083c0e3024b4c3003a0a40800104e5100128041004f0030b000070bc6ee4f0366ad0f0e9e199e1dad1c969e1dcb0c095eaf33a6e933c646b9287e4215bca6ea2b3217918c9363d9d2450fd31c618638c2f0fd3344dd3344dbb7cd0b66ddbb66d573214db2e3f2485388ee3388ebb92a11877f91007509c4d3c8ce69a76ea5e3f9d240f2412aa8d486827a5d926967d6969a9cb4d43b1aeebbaaeebae6428d66559966559762543b1ec9ee7799ee75dc950cc3bcd1f2cf6b9625f138e7ddff75dc950eccbd349f5a0cc262cf3a9ab42a118700a69afaf63a450f6a996a11870f614b26a15e454bf79401003aa20dbf45bb638679665b80749319c9768522cbb162e2d2d350dc5b637d588e12f35a962f84a680c7f6949098e29add6242971c5943436494931ed4b4a6031a55a46524ccb4bdab7ac848be1ac6428a6e5a61db12c4f2e57c81d53c2c6481eef7a325d2f258f99d434e31667533d951ac2039afa23bbcad814e6610d98e46a62f2e4a4fec89602a5fec81627d40955484a20934b4fd9599cbf14a21606e05bc46730a679668a0350c4340c50a452160660198a41d76141435608700bfb04822da37888ea550f32af73ddebb2ab564ba75849670dba1ae22cb33401de45af33b1d5d92b088ac759d6ea51322696d2fba27680218a072e513c18d1ad19c5c394966ba27870d2a73b6a3d8287a6ae5d57f24545f06380f7f3a8ea1003145d2e9f5324c6b870a8699aa68114a6612d07a0cb47d3344dd392ce4445e1d2326a07284d8096513b3cf55f5eeb5af0d2db31409b651785e96003269c3047385a61d6b0030e6d69186f7aed79593cb2cddd92c179f7f2cb06a0f8c2f65e165d20ae42dee715bae3def33e9f3c9a007afb164d40d3633501f5f62ed9570ba9ea3d14a85f3d02f5ab8cd4aadc4301fad52340bf3a527b29e0ae3fd98f6c6fd993663b6ac9b060275f695ad3ae730d0a99d65ea178e9ed160a9bce326b6b09446de53744cbb0d66d5e90141a7f033813515b33fc6b34db751b02d15e5da127816ab62a09d4655d17e6dc8638afa10d5baf6187bf21f8f6da3704c9b3a1761c5658b49df555286e5ffd0b7b722efb738177853d39ff2ebba3f5cbf94dbd94402ed15ef5d5b96f8876d11eefe87c27ec11b25de7db75724fce7b847c07ffbd3efb0cfbc386f839e10fa4fede0a45eef838a1b81dff262c625d367e16bde31945f86006290c3fbb2a1ca2b07b3757eef1fdd08eb34fa883c2eeef670da0fdbd7cdd16d57c90d6bbf7fd4585a4fc7c28cdd7a6c52181ea6d766b9f7d21dd32db5fa5b40ac1accd661892c7d44b02d5832228e22daf424c7fe83feff4537d55f57db63dfacf47761ada0cbfac17dd3dadb5563a9bdc3b7f673ea23d3fb5d0e2f004a0172e8162d00e0aa31953a105d09f9a852d00c5a0201d433d8814317281fb9ae16b7bda68d3a64d5f5b3325474ea030aa691b6737b96533bb58fedad7efed9c291ca1306a8204a2a7444a600d9ba0a4d07006509c504fdabdd01df2cacca4b4cd138639db638112c8c60046c3a2eb64689facb5f6da15dace234d345644929e7c38a59eaa9d5ccc1cd6a29968ec953394924a498f8354d72c22852f8062504ba949f97b6f0b6db3799a5d86a14d539f85549e86f352522943fa69d77428bcbac81e5f76de41afbbdfe79745dadfb9df702ca1ef02fa7e2d34f71b7ea1a5b0cebb96a1f0ea22f92bbe1a634e084b7c7930403184103eef4786e87b7ca117ca5508baa6fd66af85575a1f745c47bdbb610c7af9224cbbc4d9de00142dbde8b2f4c7dede721cc7710fe22e2d181dbd9ff7cef30e84de436a2d147578a1c8fde2ad0b919a3ba263dfa01b83cec707ec83e10ddb8c2fd4eb41215098b5535ed54b02c96fd36a41e9ccf7e26ba4b5f616ba5efaac86f4d2eb614a2ba5af9ea5596ad3e28082abc5159a5e04db05863480b8c5e34e3242e5713863f2c035c132fd0b104b0fec600747f6c8b603649b366dda4c1cd41bf9a8cf8c346034dbfc6671ce50443a51cd422d3c483ab47cf6436e61035ecb28a9196ca5b70815b231b9451a2dc868f901a913065da3a45850bdf0e84f945452db96515bb069d1b60ac21b7cd149bac307eda1c74d27bd9db8663c8f90515ae0104583292117439a98d6b4282d82686d15d3b4336502d0324a0b309db58cd2228a1f8465402ccd98e96242b50b362b77f98e0b59e0be7a17b240a50897e559d02677fb22dc6d2ee2bde671aa6806c1faa3fd5a04646c89e6b9037e07fc48a588cee9e7bb1799efbacf6fb988ebab3caeeeca44ea8f36f318547fb4b3be935979249a6046ed3ad986dc6be85d158e548a4c7bee45ecf611bcfd968bd47bdacc239522e78a70a7dab9cc84ce99c7ef7454fd2608070cfdd45a44d5ca98883b2764a163814a912e4f282c80100566bf609e4ff547d30e8217306361a4120119bb5c66e15ec6c4f9d49afd2aac9f470327dd492aa9cae91498b17b221a5b2155aa4dd902e3a13543e116d6bed8bc4229fb10871194515940e9a223f378e276612f05ae714661be7e3bd7f23344816bb9854b54c4107ab68ccac2057d5f74a45ecacf04500c72dd21ba43c478eb4185668c4665d1a451b05c50496a99250f6498c5d0d48eab76508b2273c5865a4461a1d45bcb282c9ec8c88e149bcf3291a59f7ba41891fa7383eacfa5f5f3239522b6fe5c9b47b0d6118a52d01e842850c6e665ac5e0c925334874e8868de1ed0a705baa2c8488922d382908c4cd3d9a7ab04224636c044673781ee5075f613240faa43cbaf1074f4c8d219eeacd31135e669cb433400451d3d8f6f0873d51e2116600944a0eb5fcba8313cf4aa65d498262dc2c034e96415e2362dbb7da54963fd771b8ef6ac6be1a85df51b8ef7e07138e2eb3c0bc7d6c7ecddb770dc8e732e4c134589105ba670c1530ddf8dbf5eafd6471c9c2f79c5703e72d7d139ce1667b21863882ab218230d8bc5faceca23ebdec719c78103078e8e8e8e8ebc424b144344959981cd0fdff77d79fc9ef351e7afd7eb858383a352a9bc7031460d69aee0015311aae77cbc711c3e3e3e5dd7c520862bd6402105758397ee343ecef8aba7a7a74794152676a8e2c30f9200697c74f1f0f0b0582c029cf9218c2b4a7080a205ac9cd56a95e372b9beef837d418332a67022830f96be1bcff3727272542a550f9626ba9881931cc4f044757373d3751d4b872e92ba9061e64995ee353ede58ad56200872426c81050b1aa208e103f81a1f67d0a041c3f33c168b95adb1610c272634d8e2081d1d1d9d3cb25ee3a38e0d1b3670e0c051238f2258828a30ca100389286bbe871f6b9cc6ffd7eba552a9c42f7438224d184a55e4a07af891c66ddcb871a3eb3a08c4a08a131dc4a0b474021d1d1d9d3c760f3fdaf867cc9811e691012d88ba028c19d810031c7e60d122872a4fa0f001081a3b3b3b484bb04153860f68b0410733d868b55a4852a63c7181126a50e0831bd4c0c1c161b2859636519ec090c4141a3e3e3e30948162c2052a988891c6464f4fcf92991c5e8862054d54a1831a3c3c3c72cd1463be50e3848c165252cd1352343813c61267d84834353c29430922a2c8010f328b2f3080820c2456200331e497343f4051e506253374b1e179de016250a221892a3d5c295303070e1c3ac658a1410b18be9061caebf50a801417dc400a36485861c38d1b376a3cc9c205261428a9828a19533c45c1810cbaecc00524624a0c4a3384c10396a313a8e0490c125ba07ea0801726b63801040d5330f18100144728b9c00825a438e9f1f1f1015244932b4f309882cd138ba7a7a727d6031760a8e0045c5891e173f1f0f0a854aa292ab0010a24b86892724195e372b9baae438ab2450f9a34b1c1162566ba9b9c9c1c100491589025054c6620a5c90b24c0d5cdcd0d8bc54272a10630e8c18c962268d005cb5bad56dff7ed808817ac18a28c1667a67c383cef4b4888792a95ea0c130e5e70a181124f68a1f2f9f8c2810387a40207a52859d25461832dba9e8f375eaf97948286275a9ca88289982f409e8f336edcf812126237582c96082ae0e2c45483272cf081e5faa83363c69790109bf17d5f00b268024c9822b86cc1e6cbf9b8a3a3a3d36a1245105360b29099fac71a6ff9f8f87cbcf9d8dad9d9e9ba2e3be3c48b1c9262580aa3fb3fd2384e4f4fcfc7d5479c56eb4b4888b540109c001566a6f0c0c90f5f7af8471bf7e1e1e1f9e87dcc383838cfe38fa917ca18e28b283a3461e34a4c367c3ed6788fcbe5fa88e3639873f6c923929620b658e30332927081991a5762aa711ba7d1f391c6797272723ebe3e3e0cc39e3c223db900050f963c91a6092d366cd8f8c844e34a4c345ee336783efadc757373f3f1c6471bff79f28834c5490ea498e20b306698d4a851e32393cf95987c4ee3355c1f7b9eb35aad3ecef858c3860d1bae3c2239d1a1c90f55c478a205081a34687c64eab91253cf7d4e23e723cf6f3ccffba8f391468d1a3572f22877c0868b27539c38e1e4e3e3f39189e74a4c3cefb9cfcd47d7573870e0f8b8f3d187060d1a37799464c6fc8044172f4e4046989e9e9e8f4cae2b31b9cef39e3c7e5f7dccb9f77abd3eb63ef6f8f87c0909319f551e651447ec30830f633c8142898787e72353ce959872ee3a8ff7f1e6386edcb8f111e7234f4f4f8f974720355d54300614412c8de172b93e32dd5c89e9e63977e5b13b8e8fabbf66cc98f1317f74f1f07c0909311e1c797c299992e509256080e54c4e4ece47a6d5959856bf794e1ec1bf3e7abfa1a3a3f331fc98e3727d090931d72b8f2b291b60414366082088b8b9b9f9c8e45d89c9fbea377964fdc6471c9fb1b3b3f3f11f6f7272be848458ce8d3c86600b19a6ca3491449732abd5ea23138e2b31e1b8f7d5eb3aad56eba38d8fab9b9b2f2121766382145860b1810c276698c2f3be840031ef23d3eb4a4cafe3b89747d5753edef84e1e999ab218ce9996d418ce9700c5703ed6f8e89da9498badceb46428b6fa121262abaeeb5a0a3655bcf8408a144d70e0c0f191e9c695986efc751c3b1f67bcd56ab55aad56ab9557d8c923d213305c08a3832a384821e2f57a7d649a7125a619bff1571e419de3a4c062b19092c208424b9931585011e2c68d1b1f9974aec4a4f319bf9147d65b1f779e4f903f53ea608b1aaa44b5000c16843163c60c9dcfc8e317e2e0e0e0e0e0e0e0e40d8851a2031625ac9471032e74747474f2a8ba9197931a1861860c169c81a1b341e486892aa49a00c1c4164b80354a903f93c5621951f2820a14388161a909160d22f933bfef0322861ab0a0011545bce0e4dbb9cf10f8aebb4aa55221b14007a93051ac39a3c3939d2b31eda85aef09621dbc1213ebe0bbaeebe4156bb62cf1e4a9062836b4aec4d4face71f2d87dc6479cf3e8e8e8e8e8e8e85cc9504c27c3e4cffcceba12d377d64110fc92570ccce38c3c1e0088a72e5a6e88e205969d9d9d8f4c385762c279eb3b79e4aef3d1b5b3b3b3b3b3b3b3930faafe5d8949f5ef2c16eb4b5e31561e75f208022c76a0660c0c4e4624d16ab53e32815762028ff3561eb7b73ee6b0582c168bc5626557d65d7525a6eeaa7fdff7e5b195472422a496e041195c5cd981151c1c9c8f4c19789c3ce2b33ede64f076dd552ad597bc62aa3cb2f22873f064062c60810e4a74912206825f428018f891e95e8929fb3d9847eddfc75556a9542a954a7525433155c6f2672a71dfaec4c47d7bf7aefb9257accbe39747d984132910430559a29089121bbdbcb4b494a5fc991f99ec95b233dddb8f4c4b7662d999968810cbbe8400b1ecf523d3921931ee4c4b6ac4b82f79c5b833296dc757e2ceb41d1f694dcf81aec3c2a070a8fec8b770635620062bd8f4ac45f34d71a573da90754777603a42571d52480a8124743db5b2d63a0514ff900b28ea681d3d1a2079cca41df494d200ce79d935db1f429efc99934b97508a500a6d4ead658188e0446b675a7688b0caf5321826f452c574d4abb5457f2c90fde691ca7c9665fda91190b159472af32d70b14be7a5470a72d3f4d64b33aaa2d987c25cda2550f5a1b0cfe6300a9f0a76a5b49661ad4a64a14f6161f80411cd3cdb355bb055bdd31db6c1cf6621087aa73baa8ec388ac805cf7808551803d2cd8e2bc3da100e710a0cef2d5c47452ce164c3275f43506573aa354e8864880f4f300f6e1ac534c27654c5e7eb2b5232a4b93f6e608f3d2ab332391530409348f444e11a8049a3fc22d02b08fc046a284943e9a72e610655416297d34a70d31b54c5a76622fd492c93004d8cc311f6e01a5675d3e1bb5f554d334ad884acc0005e99c37cb12bd37d48b482db31009511622d94027f1ba7ed65b2ad2d3f0b693c14280ed642e1febea32b0057adfafb241166cb5bc57434c6132ecc19a34bbbcfdbd0cab549b647fc481814a6a6be518d9900f8824d35202c9df6cbf20754d9228b960496aa3ba26a96be63173d0cb6f68e6a00fb780f2e210a5a1bb9d8f307d27d8a2a0268bac8e20664c00ca282c6a3c4f82abcce52334447faa8e7bf1b6715fc779de875539c8642a379f8f9bc32640eafd75bb1cef5ef5f9b8338485809f5da3dcdce69c786a73ce6cda79672da2123380afe1538b29cd453863d1065181157b920c6867b804787f2fb7dc6c432de06caf89ee92cc8fdc36d32981aec36191764ae72ca243ec29962b2468913e496da7126c1f912d2b2125414161d2b4f4820b4931a52549a64a0d2970828b1c602113dbbe346a208ca761a8e4ec9684414c2ac5d0cc0000000400f314000020100c07440291483424d434c97714000d9fa43c6c4295caa22088611032c418620c20c4802122043033b36d00970c1f003cf59218add2bba698e40c77acde61f6c6de484b46b71f2480736d6c4f49226fceacee3348d712b3327cc460ce47f1d2a38c8756111521b70444864917968d778b8748a8f76292d65262ab1edc238aad6b0065cd824363e6bac6ba2773178c872a65c16785e9e297121cd287fa200500882a59ccd3d56862c064f0cc42a23db684f27f9fa8b39ecb542c23d4d1872d577e5666b06ad02c4a11847ebb181f02307a9b1c65b40433362716fa2fd2d088e545b8b349ac404b5659e589d234359a896ef5b5e2f84b7fff516f34a55d145aef7b9c97b417c35fee018f42d4900db337c65306c1172b3ab1de69f814886b936882072605390432440f3b101956bbe85a05db891ae2b7182bebd600545dcbb73c67f5e633cdbd28a404855689180137420bace03014e3394fa3786fc0b37048a9066d7f18c631655ed3766c563fa96eccc85ebbc4882c61d6e0fb83c5c07aef29775e5458200a08610d5d2145129fcb2b8d1cd80c030cf32221346f46d42f787840747816eed242042e8699b18526c3e28d268d37a39a88bd270385790131690799462a50c865aef66492897e26e0c95e4e2a586fb4f81a1938c9c8152fa415f1bbd882f534b570aa583f4bfcf44d781156acf67e6fd68a01d44ef919967198749a476d85341d88bf3ac0e786e6668d00b64e4c844a19b2e84480c55c16b1b8f7dc6a22eb8d13dcd3eea8f3ba0c9327b3b161646388f90ed6a377098091f757f4867fbcff4bd64bbf525bf2194be3fc6fb15a8c0f2b065d6f8c890a8024a2cf4d3d313e2a549620fa095ce4ae0bf7debddaf6f5092831aa56022e14bfe705c3714a489bf97ea7ecce95230be67801325d11f81049a0cdef0255e5692b1f1c00946e8ff15a322779d906e0d6be10a156467c04d477b32f427ac3bb6e857551bcdacb060833d4996538b0a1500dba215e6a588bc729943b1fb76dfa9f5d55c5b95b722153c8fbee7d4894b3c907c2271874202d72dd45356dd156b68a0c1371c1cb107334c63d980aa7200db8c69914c09d7dff2029ca092513a342660b3043107c08ec6126f4b889692758e761d62f29fda35a0a179a6e8898b13c9221be4032aedc033a7efc3e6d427c7afc4b63d969050331b53571e878f4215e18c686745ddf2172cdd152d87c137c30f8793280810a7947d6926961b53740f773147394afa6c718b5afd29f65d3f93127d8e14ee2e1171aa0a8a2c4eeba1b48301d8b089297d3ddfbd81c632382328cb22bb17aeb157afeccddf4bf01f97835d58808a0cadea8fe9067656b52964154719c30dd3f9557e0c585c25280d914b7e1697d2b4e56c0f2d182dd2102de49b9b3a3b94fc819fbd0cfa0ad4080668fd0166288af6c1191b90ee09fcb57686eb128d400beaa06d7c15854da657e306f6b1f248fb638096109db80bd89125c1fba5edd92c23a744998d5c514b3f4cd80f073043b7b080f614bcd7ec9fefbb1f50ea07e8642e84f2197e30d6c637d67218a31db0b0220f77a67ea30f43342e6af2ddfe3c0e16537e3b6195cefc247731e60dfcf10bd600025c483b9060b20e38fc0cdfde21125ef9f634a389c790edc4f3fbceffdd414563e3f47ed513201310141403abfd948a0cd0fea13cedd5a0070bfd021f235e92af0dcc2b165518e430fa0b32818cd350a27fcfb61794b2a610b014740f02f4c1d2a7eee50658c33f800d9bef8abd90189f12e74097608542ac65c59182082a1c2f58f88ec2ac8bb59e58a22308accb2d9623b866563ff0c118ba7a279af40bad1c299a150ff8d0614201d03e9567c810f33bf4d9ad06e9318fb6fb7047975ac78157c0954abbef00d6e7467f5b78e08f34bd851e01d80093478c0bdb9b55afd83ec83e49941fb407be7926af407aabb92e3046a07d65bd757db5fdc8e488ee3c71d406f7d5f4d7f75bb44b28b1f77c079eb7df5fce5e912493b7edc81f2debd6af803cb679220833cf0469c40e0611be58482876d9413701eb6514ec0b907e2cd45d5f15fa7ef4a36f25107ca3b07aae51f745f4982e4e3e1d3587f793b4432a7f707bfc6fba3c5b192067b3b48e330317b708d79a2c3e11af384908335e2090c076bfc31ad8a1a2e25f717e3430a84f8fee0bb20338bfff786ee5c27c846ef0699d9fedf19b893416e6ec4e7934a832f60ff62f8480140ef2dccc7b875069fd9fe9d1b24e77fdede08d5b015065fc0fe85f125c5418c2f0af5b87a0681d9fe19195c6741af6b06e5893002f61f78d841f3dd8406362ed9a0271f045900e4ff7a53672301a4d20f812c60ec7fbeb9b33dfc51e907401630f43f9f35c14d5589214e1a1e1035e0eb4fcd1e2048692f229c523c7004c80292fef79fb5e5a66a06f05a92fc68580f27d8bd33eca81081f6200e0cf73383c216c01cee33c2bad152401f08224d7998f5d3c961ab419081e90e7ba4676454cb3a180d836a60fd206ee94814afa73dd457a6b7a9ea930967cae7863f0d838e497751ba2a64a9795df707585071ed2e4a5f412c3d9ebbfe00073ae806f9d05374cca05feba10d80464477ada2627beeb340f257975b496f53ab27d19c91be4bfe34f819304dae8a8aedb9cf02e7afaed559ea15af4c6c9f7e16507f75ddca65af485d62dbd6b340f35757a744e89552ed70041ccab1668133aaf5fc36848a07adab6ab39eaa551974ce4fba7a41d49031c4623a0616728db5f6fc4d01fa321432ed8eedcd2d5cd24591d499d34376632ba8e5c1e371093928a0b0937489c415a32712a0985d693ad4002542bb61aeb5df080eb03f809b26dbfd5178b0d9ff883977b6bf6a060c586122669f028274b17577d3a593b9271f8cfd131178cadc88c9f4d4d04396b8f2dc41be0730ba241ba4d292ec2a2ed5018e7a6c01246129eaae596c055512f337b4fdb799f9c9247810d9fc471f4a6081cd1fd83efce98ffe425ef65877dc7740e9c5cc8b6fb82c0b8fd02ce2cc1ae010916cbe8a9d910d379b990c1394be80b6d88cd7820d1f0ac35f302038a859bb154bbbf4564f44e063ab5c8bd5af5987052c25897492ddb33f34aff60a628852b7f79f1a5902c4e2c3f5539a503130096a102f805c2a9008c18bd0f625852e21435f4b3cb4f5790f11d98668b20c9d1f06731e2d48d3c68f8132f073ddac1c2956c134466463ccea2160f4ea499b267c39d9a5db2234c0a15400ae1b6fee14c72ac7889a767725cabdb52b2e8f0194c404d579fd0781c3fc42b28bab8e74314578963b6626a9cfe5a16b19e470204b588f33a8aa63aef5cb7a3a316f4102d510ca7ac3d4b03af816a190b526af4f290448ab194c872cafc77e439a03519b6aede02eb6d654072175002eb367a9ba7948847702c0ff58464cabb9e10e3526b49c6516c284afcf8f9371effadf62299ee59b6adf63fefb8d38567773bf5a4565e8d6137d5f7c8a5c5f237c257c63ea5d170ee404901902f430b7b57947644b10251aa3e0081aeaee0688c4c83cd1c15149b2b37273de4ae3144dc5b652f48d7d1482e53c60e7e772e47511246ce88081d4fa3a2d21194c9ed00cd77a554c6748b681f9a9967ec2d0ab0ee7eb7acbda66aa9aa80df0631d498d418c9868842ad6ee4e63cf9dfbe82cb592b75cb3d75bc91f325fc8d72f92802e71af74f8d88b006ebb3454de384320f873ed9a6b106ffe3f6ed84a2847553f62ea0ed6c97db10e608937b5770f5c6c10a0459ff6c137961848735021206d8b14cdca3c36a1f8c4be31a905a0341648e6c6e083ff40f0145d235e93ac0c8b8593b6713bac589cb9eddd85e8b1f961a612184c4c189a81489ced7e7ce70b2d50b09c6c4ed54747b3e620a0e6d3e044944475bf1f37f3c623ae32d2ef9ada6d30c247e81781caa2e85ad736b1a10ed776b3b3e5b0ff8474b4549b5097665b6cf911c9dbf0ac0befa142cfd6430658bc7093314f3fc1b195786ad2d1026e5d91f9e3fee4a0ffd8bec35defb401247224ac494de4c06694c6064b767b3635682a35e0b43fd15138442fe9cb00bcc475ae176347af884ba8a2ea7576881d8677f62bee6ba575acd2e906091450a9fae0e3359f4ffd36892e0a95e6a9547550f28638c6ea4fa67e0eee5c43936a0c4a400456c0066a714eca2f41bff2d6f2535595cd71251e0bcb6cfb108ae8fb13df0543d38437017ff90183aebaee55d652459df17a2466f1637498ba366ddcf386b1c61940a5153e9de9a2f39291d48f7af02067b5bb582942c2589be026ab1505f57b40c6b5529c28dc1ca872098f6e3950dac3d5d8ce01a001fa475edce33d88afc1ca39f2a6c212d92878fde7460ed40ec4b878c06f19d67f2a8a522029c73a402bce689684d7ccb07fb668b2247bc1b4cbc6bb01d2f38deeaa3a649750d4269190dc9f7c82d5d9ce0c4e687b66dee93cc4ce7c47c32022abd2809ee67fe8db1804e5bd31514bd2d6f66823126071bd5975780797a91b2102eee4723ec33f41701e0d6406aa8f004abdc127b8e5f0e808ab7b4c1e3757c511cf8a01d14af0d90da1ea6586b360f7924500d06842d6a9db3ce0f527a0daebf6311db00398f4936dd20a7392644a1fa1c66906d34e81ef9eb94f837dcb87a7c20cf549fdb9bd9ab23cb3c021dda8101202460900987dcda8725f7ec2290e51715e42f459c847206512f59580894eec1720e084f616956b4ef181970dd018856431e0636977dfd801bcf872a7a1f255ceb39d1489c016465e3dc4226261ae4b3bf15bac5507b714aa6df8075c3d81b4bc602d069cfed630022a9e104c525ec50ba22126636f441358b46709f7d50141ae32fdc8c0e88e2a78677943794cc4bdc39ca3c4f726773e72aec2787b06b7ca377f44fa703bc4cc5120db85addaa2fb47dd1fc3b46b2789b6f8115ff4ff9e43ccc3469565d13901aa38db1c2d7fab664352aedf68d4e95f14197a1b6214547302961923293b3fd6573fc0f3261088321e591d50f385edf9ff9bcea77f87faace75dfd147ef74ee788c9f6a9aac3a1db63c1401d3686e358c640806dc60206366f50abf9352e999b1e9affca1bf5ada01ad6c04f37c343232225544390e83ae6efb33445358f4a35bda3974b7065d820bcc78f16ada9ce86e874e863720b072fdd7d32543fa4a66138bb368e8cf2e01147cd06900acc301ce801befb8c2ea5a0ca25c3f64839885e4881b431deed0e9b53f44f3a8292d96aa798b42025b81acada39a67d14d500aba67159d5fa445a9fee992c4022b31fd31855ad17513769895455da676a810d35bf79d823f9421ceab0f9cb851c93362f94e14d87ecd9eb5eb48f9550eff8c6ea0040785de0b9f9734034f3c6c5201f159bcda246b7d3fda2b85b3291d887aa8f75c9221eb3c423f6f120d8ef78196c0e89179ca2283815bfb4610e6a89bf4c3104585f7b57e538c72df99f2e209a862d83ddfe2430626603a61510818c12d0759cd51a0de67294689cbeea94a28088066d21e5a92e60a6cd4fee989e24ea99707a0aa5af96787bb47db5988c8ce46c254a1cfe7a003d3d2ad6facdf98c71a9342d4d6362244c48439e958c9e534b5f6d31301a863d9ba7e29ad86ed0173d9501729e242810185d3d5160087bdabf5a4d27013234002668c965ba5a558ea080bb3435e9c53f30fc172e51005f85d48d60b680e53890e44061b1e7b9d0ee11c499cceaf3920595f863cb74c7aea4e988594ac038356483b7b65c67813c9470e751f1a238e7e40fde33614d767f657d92c7791a66dce588157de084d50026d700dbf3cde3fbd7a144397a146829eb069c2465fea80693401d5b0791573df4d0cac434d3bd0847ec0c29ed7989c37a7f89359e09a2ce5e443020711a723f15f36434b212492979e7a76a40c6be904f44521aa4ed567fa6a19b8a3bb12d4733b6520bc47828280049ac0712a12a98b57e4e527d1da0cc8103d4a8f6999450d78e271ecfa346fced78f4280ac41ba9cbf8cba9864fd2dd5d9dfde9b73ff4e39d4fac7b5c9b1bc07002e640ff9b85108459fbe0ebb127fc6c8544ef763f9988f01c4bd6071a1a00a6867239d9aeb1af19409b6bb0d0c8e8d814056a5a3210c8aba76123d21a22f15876b7a7ac6f3d618e8d0460a9f12e8625f9eadf9979ec45f9023e046f1840281a40770da127388f5e13bb145eacdf83124021c41e71184ec220796da08a9b613f2c3fd5972c974971202e0d5a1875d1f68bb988a66ed240be732ea5ba65501e5327c1b857b7d285c66c2cb8a9e234ec56c0a16cfc5805aa52ac105cae4c8843fc6a7e9eb14631ea7066d891177d4b238b563cf3828af832339281628fea6bdf0489a658c4f5fd60611e22ff9221bf6dcc755c799829d883c8f688be5b9939f87fa03782ace29d51e05f144a864817c69c2515708e5002ddf9595e0a9109d8eae68dbb444f04c0a01e412604d56320cba95d93d0c713bc6135caea9e3bd5f577b3b6a52a24fb63257f2d2809788b1489b181dc958d54ca30ab5b499d2d2f870888b6d137c1a51436bec9750826f70e9e114c982be6c8242049500dfa2e59618a04912d1f971c899c4bf97d5bdf16cb19a2216cde5b3b94120786a527df0213291f321695ce3eff5f0e71a9f5765e4a88766328c7b52fcabff317964841fbac02ed1168a7120f0388528fe0ec452c541abbed987a130ea80f65cee0e489a0643fa0e884142eff951771e1726f3990eb30067077ad3e919e9b6046e87e5d4dd9cba542ce36ed8f1d50e5d1c7dcc795b545b975172aaddd2f615276d88b479221b01cab9a64dbde2be5844182b05a79a9e1452c052e2249aba19db5f847b2910ade620aa4d7f34ce374d01aef195724b8d3069b3285bbea55bd5996d860944df3c11a45007ac6e7d81843ff06cefb9349475c5a27b8dde233abef73f5e86b8749a62905e25e7c4aa3fb03eef6592891d26fd5e88ecd1b0f2ad4fce5a05d7ac101bc7893b9e6edbe3818d3374c94a832634bbcc1936580cce6f5df1b948377642665619033dd15bbfed2ca0156f89a343398d80c213b828271ab8ceb3ebc613032bb200013c152918034baacdabadb143184f49ec94e451b8eb327074cc125804d90ba1ba62e6ca1d851c9eea22b7830d7e7f6e260e800db20f4635f46c8c4e8fedc4739089a1f3bab438939bc5cee025292e7d8f9e054e0207f89c1fc8ed25af5cf2e18d536b462c31f99331a67a2d2c714d7450166d7e2b90708f482842f925b054e7da04ef8f5dbefda6dbdab2eb9089a3eb5f0b28fa8388a1c866b1032370160d35d932da093398874aee22038f010ec4c03d3196b9ea68051a4b4ce708ca45cd3e83cdd0ee97a09a1604c45c40439d439d0dbdc6ddf0047c0fd2c00b80158d18e9b388b472a17ad9d47bb1abfa4ecb338f45bbd608fe0e3d47abd9891da72a8ed009253cab51dac83e24cdcf2a52987ef293f32feb2b2759aea4a369563a2ab1cbc46ac1cfe25405bb0f53fc255c48eab6cb80938abd520c30591a7aafc3199e3b4065f2cb8643990d0bde217227d48a1f42fcf0ac35bdb66ffb6b4edd7f7e285db0d28e5b6ff9f2f0adf6e25c3af6cd756a7c0de84d585281cb5ed3f3e29d791f58d398fb9cc80ac5b42ab97a5b89a37185e03fcff1f5b2d8b5b3d0f411b0c96dd24629f00ba42124228c39739e15f2341197e8e0c8c8d040a715fd734b22d40c77baa3ae9971a3aac59a0dd788087bf57e930b1c54bc53cb141a40a6387279d21846525fe51ebc87e741323ee07fdb15449c15514e3c4d337215e6e4b3a07a998855d63a54785b2cc7623f09647afe835d5a7e00cf23b2d469abf374785cfd9d890b64e4de05d250614d2a0c36474f25ddd2e8c7b891efcefb536e592d940c7e7dd3d97b7292805317327abec6deff32d13ca352895bd3044dfa7ebee75233ed1b8f5a3471ddd160997b7858e9b9bd8f916980d3db9508cd6816417114aaeab5970082734824e7dd0bb8e0fc9f32476182bf4bef43a3694ab805028fa7833e5e2644fcdb21160c45e8b72a3337ca96230851e7e031d3480ed61b0c512f30276a3cdbb5c0b2df92dc8ebc476b417f375be167efb0798fb1b8002209a9125a6c794df1c94775ccffd0033b2e76a18206d3df7011e71c21b30637f11e19dbed07d909ad6f577a55ae9bb8014fe6482feaa094d61dabc23b691d1a0f8f055f36545dd888f955ac5878690ed930add1a25c5cc7319b892d2608b8a34d1b768f874923ad4a505b4c8b08b576869a90bab34f25d2643fd466864387134efe8ae11076eec816e98d748d40196efac4049398dfcde2fd8fa46df647c7086f4b780199ff7a3245bcb2d5aca3e575f9cc7255e4a856ccebe46bef659e193af04ac2cb7a1ce9ac08d741b5179819f6fc86c6633c327250f1393521bf73875a70bb63e904c72141a9b61bd83f99d7bc17721a728c2df903e7eb1895af40d9db41c22800176bcab35e51c23e109cdb2e456002684a3178d4919e3fdc72c2440b1ddadcc8b8ba0ff6061ae1501367a4dcf7bc362077905f714c7c5a043290a3f063c65d699784ee8c9c4891a1911c0504c9a021adbf0f66d42ea45df4800d50492b5b5b9c1379f692a04453df6812a6c3fcfb5591af54c340d1bd031ed6a9d240fae5b93ad2413f400e93adc213b5b5c1f41dfe182f1830517d04516169d0e80de4906677a8dd918b1f9e81433ff838aed69f7738b9b458af65c1f91cfedba522e430e21cf94e4a385ce1c86adf4e11baa0c093253da1ccaa8f417bb41770621f378e1b85c4cd36d512f2d2ee578b418e6318572de55ca17c9c4e8523f1bf2c9f320354da6716f5a33113182107b375191824811bb8ee0dfc29a70d1486a83172214842686fb0071927a45d84dd9188517908a997c62b88794021efac7e157a272a7c667944c4b2af84be39d2770701c73602e4b2e107a5262d1060d0351208cffb6f832390cbf8e1eb88d75e9a341a9320bdb355478ce783f307c7eca17d189a1ff9723aa6abaa76900bb3050a66e77dda87c73a7ef486fa853f0eeb5304088083156cc932295999c111ecb63e2aec0e33574bf1b2f028080a80dc919ddb9c3ef2038af1c382b9f9647b5c28411f06bc4e5ab461b7c7a1407373f51bdf7dd49ca757b9b33ade932aec051ff49351fc0ef9c526585f224428e27ae785165133e176360d6667cd2d0abe7625a134b5010ce5857dd3e98906802f72a1528b83e6f1d083f26fc9b5d492486bbec50aaac9871b4637ee8ec053d575f583caaa7bced0a5ee7cfc2f4822dceeadc71efc2cadcb18d1003be8d872449550190a429a84e426da2e5698e5b2e53805bb7fad92a17fa5b1ec1bab57db6e3c4096a75ac09d1fe58a13d1359e7e75cedd0191cfc96f4e34103d8361c0aa8b926ed75e0ffcd963254f0f49f625b930f5e6a3de6068ee76254dac95781ca68e87ba0fc4cc5a58d30f63991f1417e07d121ac906d8dd7c86849de7099c461f70432ce5021cd1377d8aaea89bdde44bb402e4e4569eb2aefee6f139236b6708fe8b514ae54c280ea83899efafd320e09eee55d4e36db69b2390dd503fb6bfca23f42e592f38c2ec0c56e719b7a5b5f61a5e702982dbf06e43ba110e485357ac5b99131656a53bbd52c98965c70fd26a08945cf7741e1454c0882148c9db612701bf64666531a70cb4ffc13260cbef54b6b4b55ad8a6b4b55ff44c737d95eb1b48aa2dfa22a50ca82316aff37d18033767f480029cbb406e8f1697d0dd8f0156b3e55f95e498bad86a8a80e38a0d8ce3e832a1fc6368441db355a2b15ae726633622b538bbebd73bb915155e8370fa55bbec40e542d482042ba8c628ff16f8ca2cb240e608b90e21a590ad50150c1159e1b904565ef8c5c4903dec80e4d06f1e76346989aa5d89b4aeca05ac854c465b47d0d2610859d4055aebf4ace4f25e7479de3af96f7a7caf157cbfb53e7f8abe4fcaa72fd55727e2a393fea1c7fb5bc3f558ebf5ade9f3ac7dfa1986d200e661f4713fccaae7391d409a503a40b08f1b300eadb5ba9f1e0c04844d2df5e8c09ff3c02624ffd33dc02e1483110a87406429217e9b3132f596d314e08a64d39db61e9227cfcecd9abc58bcb6f2ebc6eb9f2fe92ea8b6737d41fb2b8dc76a5c507924b941f8fedaa19706d47a5a5e88fa464d884133aa1ccbaf628a406389b8cf2e166e8a7ee6f51a802c9a66d3c8955b98aa312e2a9ec0ac954fe275962ef6f435524c8d71754fa9b266be3ffa58eb05984c25ad6884b7da766210ed0d8be3f2e20491a293a5d02e2e01f0f294cf0877532c42e5bee51b8baad4e5a3760be7fe251791b76788fa86b84601bc9254dfea2e8d5ab5aa803074486b1971df1933a06cb5d0afdfbffb27224b60195170a13e7a80fb6f6cd5c8139ff7b99ee4f5e81086fd1891cf74af11f9fbcac9ad38c7010f7e014c32300e8a450fb0c6bc9d3793c083a58ccbe87ff9a0defae2ca44ecb2bb3ea905be06f5616345213ff61cf7d0019cd84dad7e817def95a653e3bcd852e5f68cd38c78fb5b8aec749d863ce468a8dcdb07e08f5a7d8855f7dcbc29110015bd0ea0b601f9fe9b54c13c6febfa4c3d3c8a6032b23199439812f6bc8d5d0355232df94009d1fbd60f757c9bd8121b6845c561784cff4db483430a6353c6afc935400f4601c82504e270089834059fbaedf995be94939a9260a88920713c82f71bb4e01dc8776507a2654388432a28e62d9711439e5c2587e451b49ab1bd62e7b4b3b95280897107674ca421a6f1b1ee1ab9e801bc8e552ab348db7056a30d1567144c1d2b9a6ca8531f37c946f1126b4d02152660d44f941a06b222a95e9d196c76ca20734b34dd90629d04c9f1fc80a84098d423805a85962d207766ce0f0018c1e401bd03a0e106067d6add5d6f0cd68d673405e44aee172cf5e1be295b71ce4f2bf46be938ad379d1937f92285ca2e85bf7f02d6bc6703b2569220339f7a0ac390f303b28a559eaba3a964ebc6bc314647d1a57fdd8c184e1b783866f83ceea83452fe67e93be82f1eae2a7bd40626f9b862b46166ac0b15e30538b32df3dc9e13290bdf9403c92716998d786c83da2251644880ec58b3d2a42086511d6d8574d5089109edab051efa61c22e41c8fa4a307dc8ff3cf931e0280b288248d001060abf29cae62cdf8698dcc2b9b00db1e811a5a6d86d46f96fe6363cec490aa1a3065bc085bd313f589041c19f84ea6d5545900e76a8bcfe06b57190dc7c09c9a9900a906940dfcc2e6ce46901133f5eaa312604e38963da23255395a108512fa06b2101c18c08ab99047f9187453fe3acc02923d745901f45fee7eef0a5fce072bf92c70cf04ddc809949904f0543925750e0cf64b0c23d612550b62b5feb09ceacbd19dd54af97dffd43d280ee6f9ca1d1adebdf450eb1160b5b775bd82773c2395a294f7aab42642a845989d39051a0849b419970a218da81a9002efa33fe82d2054ac06f3f93bc883dff73948fe46ad8471985ed3b89e3108a1660d7c90ccdb13b3c8ce98816fc82c7907ec6616a968377e4554aeba8f04091818df5ebffc17e7f5a6dac18ab58099caa73bb14f12effb29f2d06a07a875d521ea26f0cf010440d04c84f800f43555c3067772910a4679360b1b1a9d974794810b460d157dc093efd384d05efa6d459307c3fa51e3686a2591e9a73eebee094499d2f0c6e87608ffcee4a9fc5c2bc84f7219e546d621a26d621af21bbcd42c1e0ab8679c00dc89e57cb24600f2f60d5e8e36c916a07f030d0565ae6aa68d49550ef4eb20be073eccfb67d53a8d886a20b9969672f82c42dafc94ca9deacf501fd7acdafa49575cda1cbae8af2df4600822c251203060a63670323537648ee31df6ca0758e30038d032d2c18aa8abfecb39c3d71a9fae5f8a55419e9b831f62c4128a5cc6762285de97b50d192974fd95680082a66027b57aa81b6fd02d0d200763f3809f9afe144399e3e3ff6e79f9b60b2e68ae49665586f0db73364dc75eebcfec0183c66ec5e24a065babbb181e4283bdfe0396f3fec13f2d19eb54a04297dc68b92d7ae21c84fdf7877b0b1e64baeeaa90958649135d958d5a1f121b824243d930e20efd8ab48c450c5cce97b867a8ee2757500242556f27e0230e3789bef5384bde0cc634f2e911e8f4d7ccfa42660497c2640e27de99d97aa52be48e358494da53378db20b8307180593abb0c0bd4e3a7cd5e3d000d5a65f27606d805b9dd85a009fbee5b9b1297485abab303de0bcdf024b10e2c00da2c75eb664c3ca158b56dd3550df2a0e4673b63be8664d8298ab9e0d949b2b5e14db08ada23bac0d33d10393acca5498e6132c09a0d7ea19f364badbb04c0362cacdf639e8c362bacc9d636ae0589f7d7ab8fa51e674d3bb59eec11081c29d3de0e51d98a2229efe39d4115a48380be1a3fe47c865d629f0777172cf99ee0550596b1721296132adf833b8ded1ff0c635d550a731c1fd53878ddfb114529d93dc9ca95ccc78093e0a1cfbe112c1c81ed9a19bc4c9e03f40ed2c2cfeda5cbca4314a5e350371a382d439484268e144d40884bc4b3f6dd2302b4cd5b79a201ea089e11ea718b0daa7d759c322da82a6e4b1ae151ab2a5334d1a6e3583828a8fe5906c59c5e80823de219d7b46190aab8f6d91177e4d4c76d0d9803f8a8e8a7d25213c5dda55877909afd9a221f156bebd4a04ea5959710a2545ef5ad868a92ed9e393f6425f2521b64b9f6e33784a135e2b47d5e8245eb45018adf9e60235cbbbac84c534b19c9dbe15d51a228b46a5b7ca2fb5dd47e00742f7bf912ff05f33f4bbb15470860842c6602a5671bb4f6bc8111afc32412d632013f154c1e404e29f98642f8d1ce31b61e44b0d4850b0a88140101ad777ed260be2aa308b0778b6ffb9f647700b8cec886748753301c8a1b94e191c6f1ffc809b941671262f47e470da7aefe645fdabd7690545858c7ba72535f8c93462f5bfedf20833f7538626bbe8fdbd0022632c09a85034f37d55cdbb4043dac676575e6a5fcbb168367b4cf79a5674d2560aad9df8c0519e347a99048afa1d7ab49ee3ad88f8b052e9236032f3714e02d0f86612a5ba5da8b8791b0d2b884156e3b53e15432954d82d8f44e8404b9fb686c9d660aafad9eb72ee167cb870dc2870f66a3b728b112e643d77204589b48cd24ef8d7c3398edec8b9d04a456dc8af1ea307b01df1f9c8124c8a4e0f609d157ec7b1b00cbc32d0446c283befbec67f0092370eac1d4efe3ba513d831f6f2a0485e562e7d1d777b318de7dea2650848949c897bedc4b0eac15af6e3232239489b38299703af385c6350062e234277c949eb388bf3c71071636fa78dcad7bb35649f4b99cda7602b08cc419b242e8003428ad2de84829e439630820d26c6ed15803e2f8226998f0bd70e168ddbec01052f2b5719ddc076c047e5589e28bcebbda98f043f28b94333092776a3b664a7a442294367a9cc12e3eb916c106cdadf37821ee138cd01590a280ccb7196f7359ff43601220037f15351f90c8bfe326b2912c63b1abb60d27beec7ab089cfd8a6b8a8668712082f0d5e1ae218bba4f881c4080eda61215dfa81de9db77eaaabe03621077bbc0e31c8061a842a50e498040eabb93fadf6dcae0421ff97c4fff000b6b95d20fdc102bc277845228196140465e344b3b2d3a988607305fb635b78224f2bb0e6059a117773a6b6404c20a67c08812763ec899d02f16483a6aa2d17ad33824e5560024c864aa7159a65e66476f9cea7c2039774265c70ff6d090b2dea90f41ae935410510c95460736b1432b2b1d31150c090dd98f556740ff5a7c19bf02594d043063641973d8593184dc99041e9b758bdbe5eeebf04187186c2be40d04a7f70fa1ace7b20d848828a8b205ec7f63aa8c4244b2ebcc686d7562989c842791a9717a79ce48fcc533cd4a61fcd622c088a738f3e9c63071408b960c00d6080692bfda90c834b9bd843014a8b4fad45e3d9b781ea33198e685edd572c7e9c764dc035d98f8a0124007c00db41b49668c1e30a788b1902a18fd9e542dc1a6650b8083ce4d15c887dab119fffdeb35bbd8bdc3a199ab109a81751412579f325e27e1bb6073756f0610fd36e4221ab192d92a5864fd49458dc9f28e0155a7f5dc7c77d0db38b29a706f1e33446432e14cf8d4685d21c249515a775f2b15f5b8cd0b6bdefb6eaf7d85e19602f31aedfa55bc2d088aee2dcc7b4deb2e4055a2c383c64cb0b88879c18650774ed4484f0afd1dcf17b884ebc54abf837cc8e8d716c10151c5a2153df11441d06b853f8afb29a002b90432b91138863e06601eeb070858addacfd919382e8ecb0c8738e6ee90bad64f5d97b99cd5df16c84b2ec7b892c4626f19bacf8affe41b1ffd2870aa71bb20ca8d08199d7c37f9142d2e71e8b2963629e3ba95860f7561223e3550826b091c6302b4a7b92d06dd6a32e09870c5997bcb3086a10ed122fd48eb37847dba56fc0caa07cbc8e4d02c0448abb2af94701380069cf61895482432839be705177b24fe6027dbe8ea2677b81d25aaa76febc862134c6bea7fd3ffff034cb208078f74348795c947d5c98437e4309ad73fc2ddb62481810cd8c68f09e2de766074287bfa634c3726a452e3a3ed3566fa58e0666e193008b340df8f169bb9d0146fa04037e443ff187d976f89ce99acfa1ebbfef362d7a55cc56831d515b5c405e5c20816970d389276c91c505a33b7b16651be3de8d3610c8ec6b8eeee8025fc08467053de9315ff1506123b7d152819543244fe18d774f346f426d1db3995f1339b8160dbc426e6b8bb86ceb0cd8f131c235b949383eaaa49060c43ea11e3947ed36035bbcc1e70cd23602bcd80050556f7caad37c7df14ea10130ae8e0ca06dadd032158d2081050aa3d0b7a2f9453705808aaac28b258f43e3c3976829375de84941fd8c364062fa585e75a94ed8569e0487e3b6dd0cb23e3cc84186fcfe04c3874d7789f8194a6035d1f88ab96047335a0d1aec2f12d113de20ea0d8bd41b353a145157296fe8a5820b49b1e5b2f832a672ca0dbd8977fce6a15f8f59ea5763a93797fad35fd7fef409ff8997bd4e20631a39b5fdc30c8893f7cfd4417329e531e1b1b8551063296bc59442dfa3a51bf84e97a86040e0282e890732f24d7b45f78ba72ca4802e835b645a25340425bd52e880f2f76f5737007b433936ba70f2a77cbc9ef8a3905e8aeb576b8b3a0da2493b6cefa1f3cc723799d966c800376b09851d05e951d839c60b5acd483a5be2029e3cb180d273a8c97f34c3696b96f1614340c5bea19fcfb57548e082978e9caeffaa7c3e6a8465ac69884ae9a5fa8795764de3277d8497a8556540c78d0ac89589766de4f95f967479524e6a8ad52f06b0f73661badfd876fe4389c3b992c1932575e5142a12d8c3283e62d77c4dbd490278891645b5cf5d79ff47a1f0da6bf7fed837e1ad123167495919232c4ecacdf8df5fc8363388ca7d80152f20c0be8096d0b089ca1fdd1b89b9f012f347df3f260bba9fd6349ea90be4b3a67a6cf2ba67c001768ebf14d94b970e4af867b8a377a38ebc2c4b00ea9c32a9a0b2c27211720284b3990c960442b2c5676fb7870969426e45f2fa55c3d90f2c130f9639d6bcbf9d301b184449a49f57033d1f0b8f648612f632c9194a66d7c90625f0273b34a616da80100cedecb1c346acdabf2890a37f519703134e080eba6b3bb577a8e50ec452cb5d36dedfb39efdc8bfe76ad77b960d94dc5793eb081ccce4be737fe328ed64daa249a962ac38ec6abbe62998a55b743b1802103038994fb32de13b30ceeb256ab7686515f238c411ce8fd53ea3b291a5e069565cb2a463b456970b402db7b4b59c2bdae5e8f4cfe7850fe29b9ff434e96138f4d97d219a91c02433d794d7ef3fa957fecaaea390fbc37f883aa5aa6d9e3e61313d8819408ac00b01421722d3ed85ed9b5a42074497db80e134b6e47de5541652d8c4406c6b4f2ef915242464f507d0faf8d40fc3b85c70c28f6383830908ad1285e98ed57b019218e6ab345e2d861f32b5335d95b6cb57e8f69b1690ca37fc3b2539cd7d0c5139ac2405a42e8ccdd221699ce49fe0b31af59b240262e2cfa7c31413343f3cb9b639ca99082e8ddceaab25785449014f0f87daeef5bff1a93cda2a04145657a65d3e4dbd34cd5a55c5272f2589614a12b18953a987b45f5279a5319eece2433086bbb413aa4d8e0e79810d7ac1c9da442b321de3f24b628475bb58ce3f8b647402b7cf0776fb36de6be8f82eab211afef4962028fa7b6c869c7fa7abd36c787fb881ac081e9abe97bb8662e01fcb5a178691b92d8df73514bb6603fcff49a98d6e69ee73675127ccec4de8abb6abd11c571120d2a4c9c93df0f2c1336e0370c5e0d341b3a7bda4abde45f3ef5c24ad485300d14e39c80693bc79076a5cab199bc4d87eeecca899552f768295efe5aaea468806a434d58b1bd452e84c7693000bd1dc17d22f03e3e7d56e51dbdea735b2a20e2099319d38dfb254d8162fc1920d2fec08231ef93c4facac015d28421d7eaa69233612782027f3946845d8b4ecfc9bc53e0b6ef99e5243eced19046f2ff3eead6c03a1e2fbe59e64c51cd16ac3edfc6c2543ba940de71212c87a65aa58cb1e4e8af50ff4bacba03a45fa62bff84e2e90e82c35e9a17477024cd3e1bd252ac79174569461529a7d2400135431132aa4dc3a3d1eca9074dfdd01b88a1750c01aa7f6b8db8f784c3ca268fce810ea9cf79a949ba7c218136276239c1a19647358be4ae93d0db7ea0e099e9e201711a8e1794da1eec4d5152b212ddcaf2511ab6f0f14e417782cca263030e270f6517e533d4c3e786556f2f33a3a7391efa57dd00cdb1f21e33a1089b236164faa61d1fb629f31e4a043b276b7d0af04f8917a905a8ab0b739b2f45f6614bcd01800069f5d08fffe2b901b54b546103ba68bae5e9681bef7972d0a9740eeefa74777e20868c3cf2c038304331c80650b0d11218394f0dae64525b3276f5a8b293d6fa6aedcdffe2e579d76ddfcdebde318e0beee8733db0e5735f34de8a9dc0bae70ba7f6c75754c0f296e633c14502c2985f570abbdf43c256205546046df4303c43c9e3b807b9a5bea358093815e30adc0865d468236e471c7ceed8491214e586eab5d8b057313d6e2377e011e040dc8b99a5a34e4bfcdfefd4cee2d21eafeb9b6380bb64999f4a03aff982d3145010f6854aeea347c2a75dc1585f6ae5a93dbc339edd7ce8b1e95faf57b80420bc5054ea54664b2111c90f563ace99304f558845cbeb39bd24d2dc720f264c4c09eade6024e681d6e4d00cd1cb756e96e945fe6d058d5aaa841cbfb35867ea4871139fad8fae24f2431c431b78d763a74eca3e19d1107de288c209fbebcf5434100c1ed3dabaaae7db3c7ef768d1e0414f6e0098b732f8ccd195164fa44787ca48c5cd5af50046139ef2563734a146d0af255af9d8ca577b5f4ffee958472a86cb567a250aab3692385aad4bb0cbed9e18734a706388e04de02873821584f740b7413becb3e6aa949540a291da42553c9937844918882aa350590d950a2b0f09ad361b72b52e92bd2be83b62bec9992ddc2ef64c814b2dce7202374eaef7b87fd87cddf5cfaa070cb70859865dc09995b75e38229f9666dea7d86c758a01deb0019cc98f8f80d547a31f96dfc34f7ad137ff40b078010831ec5f66ef11ec79dd7897e5016a27ee7433ec94fe44e194476a31fae1abe1898e6ef1f217330600a51772845c99a58dc458fb586d9b4461d85c17877b44531c759e8495a5c1b947c3320046546c7646d425446441ec625af8aeea8b996f2072a25ca75c6701d696ca661f42d0fbd79a9301b671f0b10a9683e4c69d7b85e20492cb52103ec0dd3b50725600eb38f9e1c73477e753a054f7e27890ad265d309091fc20483a89447c4725302c8d840b6902a184564fa69836f7b264f9a886418dd8af14b4464794cd9169fe1479c2892dd07c79d7bb1b256a69a3d591ea7638b36525612cd5f8c37d8023fcc2453922727ac17991d488cb123dc6305af30c6f7e09a2c130f447e461070739983dc69a79869afc2f22244be79dcbc425ecbf5ebd25175f0959084c4852f193e3c42a7496c5499c218f0a4f1d6da180846cc32b069a2693517b9e143655ad208fbb0e0c94fba56a566fead23db25984ea519fcc6bccf4f10ab93359675e629a61bb0aeb7aad21cbb69d0d6b2165a4da24b1ead27297c2ef953d2fb56e428bdf119c857cce8e3e72d3840437195cfe4f48398d3f1f140bbab5ad5cb598e910bfe54076c60cd938324bfb80c4c1b4b7f9857101fe3a167b4112a5150fa32c8b2c02ac6b39834317ff020dd31976d79907eb9322c631b18181d28591b4f7c29a9e89501a20ae4165ce26e4aa0953e0ec577fa29b33e0f8b8ac71d4d7facaac4d6e82132e0702dbde595d3e3c28a26983b56dc1dcd2e35ad2bca263f48dabc33e619d7c66beddd552451d0d82e343d94fbb1f56e1c6ed1f926f0c1436c930c4744b9c42e283adac80362d586090c18c5facaee896c99914bcf61db531360366050601e296c622d155b88ada2f6996583afc6e2094a0c404affe21e03984faf0eec8c5f8851a6f3c886caa2d6a45657f2e2680451839b8f5506ba9ad094d6118b933ae99a0114c5dca9e727e4adae010a422b21c985352aaac231c6d1d30fc4b1f9b0f67e33de2ad88c1410ad6cd8635e094b3c94b9a2e30b9771e14ba302e86a4066a24c17c832eb442145578c11bb28b275d3ec9269ab1b9d171340fa8a732f98dd2c5310a0c4ebdac0ef893ca3009ad3d08524de5595a1641caee26bbd35b310b1621f42a53e0ba7f9b83869c2eff83444c5343ffb7062be5eb2b42064d800dee3e434d12c6d22910657b56ed776eb15f18f6af15ed5bba861cc443526199e5671064598e41160f608715a384919c07bce9c626e591fcd28bae7f542c41f401c9a56e1c9225cede9bea8203b1eb04b7eaeddf1a56b12978ae89f8c8fdc183101de812691a76fb525c6eec4f77146ead13f56facc004ba5bf294d5998881f4a8254ec9c0364be2449628b9ed5eaacc1f500120d04cfb053f00e122870b796ce2d89936b4911680a2d1f3f6384ba29592fc67ac0095be8472f54633425970479cfa723d5caa4115ec4b2f0dc2418f71cea39d4dd9a19b5c8793f846ca887e2117c8c7ddd80e0efe7cb3e87d1408a8315d59adae5e045f011b165df43b150580378406a733bc47cebb9884ee42fb402719017dca8d348ec47e46291f9e84c8da4cdb1bb46e6f830c155a6016a493da9b40720c60a43903b9b5dd807d6043a15b1bdfd0b2e56cc71d28cab1ea5892a252966a387589676ee3a8db4cb113f7730fdbb95ab6810d09814a71771c0e566baf178a5ac484f3b54bd8bc9d5914bbc64558990f4101ee161542e1afd17918e405cfac6a9447755ebfd5cbb458e42f7497895aa618dafc41ba16bbfaf4aefcfbace56237f6007d2408de9fa3c1a300ebbb454d25ed2ca5774f801e5c07ca8f173e0db6c8066bca834942b65706065a07fab99155d2153910dce3d49139002c18daaa32ddcdce29cdca649d7ed2d0c5ac55719924d499591067d8c240721476a069a4b21cbc2387f15ec365934f089e68f7cdf78fd306579fa222663ca8479bd52be7dd31e85250833bb4901af3083c482810b457d39413ccd341171a91599a924396e6a9331f8a2654fccd7dbf49b768a3da7d802de758436f7ac790e7140e8c1096cc3b79f193d0819f6187846983f01deb695f0c9bcd92b7df7bf148573d0ce74260d4a67d19a534b6faed63f1db2135d4348be5a2502c9192e3b7a4efab2be1afda474050c4a888bc086f172d42359d80c293534c75da7bbe131e1cb5e49843e4cfd76ecc30e55da21f8d0cace97e4fe6591bfa328b8c7395827c4d463f7f7084eeccc5c2f5b7a9656ad8212582a89dd954f3279a24d9f79c23ad80955528fe86554244ebd0962f1b4c8d029dcd1c9bbb1e0db8e20365aeae6f7708db4ed7745c31025c97be014582bd777b904220ca32513822a0299cf8c9dd9acdd175b041e04d3d964c15d12ea300f9187c50a2f4588479ffd48b2ead3b39cce2509a7ce73ee7b41291705176251b0c476ad76eff1768c1f73869241453edfa879edca4675ddfd221e6ec2c96fb6b0dd9c51527be2c0a65cae707162a7b5437dab4aebea731fd430b6a068669e96d4e72732bbd8aeb6b83ef9dbd1e7807e9b89ca7232dd29b06981612c98de8f933239dc09ccad8fff6aa2622e2dea10c8ef8645e6839cc9532e3727d65d24577f2ca4e4d942b65c6c0551a148a9d4b6df27b9c46ca0acc93a9b03e8b92a788abd2b1c22ee6d96ba30d6e986881a72fc4c3436c1e9337d01a2e831c57ca37b90181965cf24864f79d4a03559573af21d095ae681cad420ad38d062cc32f0313a6d3d0d39dcc3a59d105f71d7d4c5a0efa8ddf2c2df17926b163d08b365c482c61a318532bc536436046313d1337db001edf32433ca5d01fed13b764491065e3f41fdd647d52d0bda47ae297454762e4734b71b3ddc80b556088148f69ee3197e12faa028d73496847793201c7203c21ab4747fd6d3f39a99b8325a4618101878b9c8850986898593ff073ded90d618759cf968c6affa2a9d49c894e0c103f8f2d9cc7def29720cdf391bea7874b4c9e2267be6e1cd0bfddc787a910fbb583d9dde7c5a8cae5f98d08492269da8e925faa4c2e6b235975f3566014cf6be0e4800c1ca3000bef0bede8b633d62a0e3aa57d887cbf5082f494fe0897f70cfa7883b18649d18e9e74e12cce41318699934713bfd8d72bcb48815b11f2029ed4f7e7bf61d01e9ef7e3b79474d63b1da5246f67cb0976f25eb06b8a3f6ff77cb18476666dec20203a19cf66ea2d5d1dc6481de6aa6038896ddc5b6a23c89c8b527ea9c847bd292b8501821ec371841aa8e80852a56e2b74ed43f69b832eaf8ae541233cf034e35d6551fd9aaa8745520b4190e53084369719b1818309fc90ed9c0f8cc0e346b547e27c5963d6a70551ef96aa0ec1bb66ee2ff36586f89581afbe02b18abead73826db99964adc8accbe2b0e575e1e4b02285c9f1edcae4fcd25c21c24cd017f181cf8e2608fd065c150f484c34e03ed9b0e04f0899a6d79e7a3b24f07b1d786827edb11c69190a2b2a4a7012114fe7426c0962c8d0daf68520523242c4cd8a79d28532f30db1f2a69287c2e5351e83597cbca39b565558813bfacc3e066b34a29dcbf7eb687d8a3eac0f74ebdc84d8687a2bdc14621b570d9ae68957f21bb1d170617a6d3222d29162b8fbf7844a1fe6e77511cce701f42fbe9810ac88e67ecb366fc2f7d09b0dbc4e311da54556b170c685297de1b582e8628a820a372fb3338d7206446931abbab099436b668ea1ef97ff835a10e79a7bb7b8548c64af3bc7447eb332f90dc4ca2823d6843d6977558b47ca46cf70f47a84d6664cb51b9e3c7e1724a6511add330e6502c9401abe234a5ca06386a12b8c9bf33ed1ff1152df4bed80fc88ffeca8607b2c31ce74c5af8af3120f184dbc4a3b4ef868cceb73ba5bbf9de2ac03d9c0b3402a22c4c576a2bb8938f11ababc51f60bc2ed69c6efcdcf14c32287630cb5dcdb8344976a06d3a94b563b185b5d1a2a7dde5f7f423054a0d7c6de0da97c6f1d0424041be051c3c1575792bc367782e2c5ec59bf39821312eaaf2943f6408a6da2cd1ca9408e719cf214d18352ad75ee87f548fcee57048f811aa8015dd06d2203870c48d97f29f34317a463a187fdad7f8a624cbe52ddd82df970e149f237a52b22357e243cfcf1d0bf8fb648e25a145cc12ccfa9cdffa277ed573dd5459163a89e1c91ba51b5b77c5bf38915b1ed8246371a76dd6b112454a02258a2552efac28748b60a1713a13586c6993628432e86e02a57013e3b0edb26274b2c84983aa8d6151a742436d9c66294d8c2c43dbd3cff4e7a6dec0280132db05d4ca5758cb6efbde6443886e99e8df14768d3e4541ee22ab6182c230ea1de82ea2542052af55a2e300d404c33a4a5564966250fdcdaee95913187886e2e5d9cf139f460e04fe525f5ed80bbd14622a7ab6df195acf716858afde0cf5458c0c151ae669c02de8dcd2a3bd6a9edafad2a3e0374e7c0bf7a26252ef1b58bff91481c96a3b682b47d9249951d0c0cddafa6c9768e8f9027a19df7ce421f68eb96ac4e09348b1c49f86b3566ac8233e25dc4e50180dd13d8afcd6b60ec31555db2ffc2129b403b2a3f75c376147dd61d00e4f3f992a3aef3b9620006a3dcb894cc4fa3e276586c25df299cd082aed8a8bba8315e8cb0945b221631934d295d9cd4228f6f1d15650d8deb1fe015654cf02b37054667d2b8c406c054431762bde5c0772e93c794c78141eee7d42c425a437c49ffb70efade4470fff1eb15f0e76dfca6139b15500e0187b1ed594938e2b5bb0330ba7c3bd21197de90b01eb5eb627d7a3269b090dc0d2b018d113ddb145b43df6327abcc90327cc3fd40fc00f69f73f02e8653e44739fdec51f882c81c0023d9d717dbe2e3bbee73358c5d40b265cd0d41cdee8ff41515e20a4228219b62a95a28ab7b5d0529687c67efa7e456fe8c7faaf64b5f42b72e153acc6f3ff626d55d55b798ab449d30d6eaab007e4c31cadcabc479172aff0e624ed5a29bf0f64f8526f5a3ff5da46fc5cbb9da2ad109bf38405e30e3b709c729623630dfd8c44f1ba60c8d4f4a9a572465f72f1ac4b3f9bb4c0830621aa513ecce2c687f8548bf44b8d2366a5cc3e6e3c0e6e9f46280ccdf54fe32832e5c47cf0f60ffbbb3854904bae2fffe82ddedf855c3ae0f00625e96f8bf669ca167bae8de609b70d3026e8dd66cc3e167769b62d29496f8eb0b891500bc82c99890faf9131fac1af68dca11964a8bddf7508c1363316956668e022c1f379206672a50be5f4c0126879eddea0b5264a63b78c9b97bed59ee80a0dc7644b56034659e640fb23f81cbc109bfc7bec422a99fcb2d05ea2f29a8b42cd38d34def0866b3fa2875c5ba2e9f0d1d9f44dd88bed2f53210338cdca23f5e5f010431c495ae6ef18e604fae4ab4f61f7fbb8895e756678a2448642e9e92c834dc494a6c8dcda9d10b4cd3038d9214f5255a8dca37cf205efb06b7c6afe9d079cae4f572ca01fb0836fb11210fcf3130b89890d64dd107ce9dfdbb2f8674cfc7884e99b9e9627b08b0cd805027582305efea400573c86e0e5c370abae2ff43e58e54d0763b86b841b0c0164b3781650c00abe410f7ce7e7c3619ac4f3caf5037c0e431a4ef9ee40c1cb335da382d591dacd7634404fcf9b5386d81b240f9fa0e1ba2eeb6b6326bd3d80417c64ac8cee3be97fe3e2b028b387489dd4e1815c13631bc5771aaaa0ce907371f91f3762f2e265bbcad13d52a51d7bb2c77915f3ba262b7377b1fa0370fcaa9429dc1fdc1c287a483749753b35969c8bcdcd2475b30c04c4305154cb4d16180948a4bdfda0a1f413806dc99b10cd9996b501b43760847881dbef3f9b720e14528463dc83bd89c331f0b6ff7ff06c0894aa8f43e1e1ced65a8e561376ec6ac3353f2a323209ca99ec2c87d165e08c179f822b1fb3921419e865a635228369a6a1e9f296727628b9bb01fe91382e27b887b7789d9c3cf244df1789cf610de8e384af0fdfb28f68fc977cec030e29b4287cd36636a20879fb87f7d23b7ec10cebbe502f2fc20f0df3b8dd3974cb2bc6c6a17f404ecf8f290534db1074ae80f8ac3b62da12b3cb91371291d06f6c2c68aad6a4e9c5d4f03a3e50ec73ab72d4a838d08ceffa84a8222a9179dbe36356f22ad65a37ab16c3b8c0b07776cec712451f0c9216486e8765d631628b077e786f25f6c09a8be12d8e2333abdc7afaf39800d52c3ec5c62b677526ed0de58320b967ad33a5a9ca941634fc473c9cd9c89d998264a4be68995708709d93897e0531aa2b93072e59430c51baba74b703b0bc5f81666dc1d582dc95cc90d050a3daefb7c449e8d38de9b2cca11c8decbd7976315b0dd0a65c0136f5afd94eae521072fdf87d10af8eb58bee8ab3dc0117807e3a5fac951eaf41654ee97b8c6b7fd270afd9432abf842e285db1ad1af4c345fa3bd00b6386fcb6f148ea0b54d002d1655be3987d122c5108e96057a0a6a16f8873a265d3784ea235444357df72e1b71a94f5e7c8e53acf8299e65008d7243f3091595f01d74a6626108ed64da46d604ad09a0dfae61362e9559739a1bb4e118ec62487b2d64e3e59ea15cd4fe4cb68b2030faea69cca8cfbeee368a707e4543e9906a5029e42e92023820ba48398e5c1ad6030ad737f5d2fc8ba6ad767847241007231fce55cda4d178828d8883a7672886153b837dffdd01728db373bdd274ce57e7c098464eedf8b9c1767dca1120200b3c3fac144e18e40bb9c8eeedbddf98877d8a7bbdc0d4b9547d2345051fa19ae842ddce7a31f7f0e7e4ba1845cbc65fff5c11eb390c24c9052073c60dfb3dd1d9eba0d55e50bca2656f72aa1e18882d9c2b7d9f4cc9a1839efa5bc164a48745648cd80ffd8a2b4235ec590a50d7f845c9131a6e900a3b354770bbf2430303fc0f6707ed934b80f9a9bf34467e4a31932b70a5e1fb41a59ccdc898c572d353f26573a5d9006c7a6aefff04a7da015a0f2aa93812e84c971b4d5e6c3bba680ddb52aa978d35af8e9ec358d517f47503ae7f7a2b9c19643e19c1953503af6826f0b6a3da01dc9a0a6254b22e99281b154f1ac6abe6673b5955780a6d988d5755c4a47c7aa037bfe4d893ffd259d7c09116adcc6a962bf180055fc309e438bcf74764ce9f0f1f7953aad086168252d3472755fa285674dc0cc45e8e6d9f79a3462bbfabb9e69e1fa06cfb24a4e25322eb8ecdc68fcbd764657e93ebd6a1f25e263bbda65698fe29ec8665988348df2b141311237c505c071cd1cebc290aced7fee869b2590f0c08c3d5020599923b46d4cc067bdf5dfaa905be4cd0a296431609c5752fe6f6afe15cef1dd44bd3768c56698314dcf570dff3d09010ad8e561a9cdc06e5429a89653fd9cb3eb02870e674c66e42183266dd74f94c7cc73537daa934c4c67e11d15037dc921abb5c1f83a812e5e3e1b9b75991c02ab33451791c6bbee7dad375bd6845c2e4aca87238a035fdada0440ba17e92f15c3516d5d261f5f94afebbe658c8a768c65f1951c428f0935674d87f9e76709969971e816ac80f6849cc50a39ebd5c138a4275c659636208ac3d846a3ff0aab876b2a0fd070d721ed3ec0f4dbbf72100d639a032d8ec3033fc4d6cf5f73d2cfc219c0c8fe52b33ce91d5d0c05d1ac80d44b71a6099bfe7fc6e96e969620ddd9e840528da9e855d700e4bef0a90d5ae666bf86012fb73e6b923528836ea7486ab5821e07c301ab80dbb2a009845d2a5551ced523a2ed5ca2bf458cff833f7c975cc3e45358dff0f9c2fe34dc0976e167a5a61d538a3918cc2059ef186238554ebe9f3f4afbdf2f792e90d8a1375c0cc247e6dc056a7421d7c2837cbe9ace2ea8f9a3070a51aa2cccea19d467bb37561a502a2d07ba507f9172fe7608502a141acceaf25639fc8580a4c351d225de7bc3bfd190cd18f1ba6b4d2a20c83b29158ed795d658fa7fc5566a77a3cc04fc682f37c97d83d655e3764e46f74fc458c0ba43668fe2c01a3a75c4d6c5d49a5aced5309e9bdc191577cd4e7e81009b452480ed7b4953c0c30948ee70e06483bfeaa45b31a1f4e2329d7d4381da3caa4f76c48666006de24e3c71fadd2b73ddc505d7e68e0010648886ea4c7b1872e901af3ef1ab6e99dd7267925287a58008a18d9a351701aaf6708c1c910cd5e551826e32c3406202c48fc19ee87c588a5532c924740dad8a97ba3726e18abb32ffad4c03a0a58b73edebe6e680f256f6df9f955960ace13cc493d42fa92524fb2c3008ad476485b3896c7d6b52ee8b08d174c4a74299bf242d53a3b62a5feeca001f688bb940401c1f6a7b66f8556c8cab936afe1a4b2f39021a0e809782cff543bb044de9968d6d58ce3f19d67f1757d4c539d0a96fe343f66fb0157040c1e6fd4952dc5b607d1af149f10bfc053a3d66fadbeac8998d844263369139aa92b4d354625ec2decfd50e27c4af88dc6983e4084f42a77c05c9b28e099b6fffd7fff2f3c922f258d847333a1df9a11fe629b339cc00ee12dfe4a346e6c157043f2b02ef288af3ad6b8baa3a5006a5b3182d05f7139ffd1e0968b1d28feab3840ad46630dbf07145a53e3ba6b2ab8a3e5870b26759d09e387c12b81a8812ae9028c283eb74df4b66d84ed623ccbb20728573421bef80e0f39e5ff6fd6cbd4cf05015535659d2428b404386ca0d999af5313e46b9b5a696227e753b6b90c9ea079589c26744d8f2e3a06a76d0e3e0ad4b019bbfbceb5b0ca11099e505446ac2312c19c9ddaf671342843fb96a1ff8e74d9edf1ab8805036346aaeb9c61c26c39d6b7c0ba61cfd03dc9999b0adb7f470f7411366d35111614a981b18419f173f9d18bd55c648a3255d96e90f41f3c97a9cc7dc32fbc4499fe3fcd37a213453aa431ce7a3502453a41de12a57b416b2819f3adfe3f7359b439035f3685ec44ac494ad12b1b024b1269e442eccb26c1228b759943590d9f121e2ec23ace21de028aa5554ac075a570bf23a49f90a055ddc7fcffd7cf2a766e0ad2d9ee874fa14dabc6d1fc0b133b9077590556a75c34c9c100016ac25aff297eca835773c0d3fc1386a92cb70190b0e38d02d9744f1bb8e9c69077866ff769e94caabc831173031e42a56a1033fb4771d2496fec9b73b48ae85c6f09a77d8d2fd45dff35ec913b8b2be3be4fab327212db665839b4b30127553504fc64e3018421dd88315448c14b4b4140f3eff580b91fc0acbbcb50856f5c3d3e64441e518e8f70c0297c96f9bf1961ab659ed18334426633c6645f2b32b23039f9039dc7330850a7f36a11df3175fcc1ddd56e8c1331933c9090756ed79f44e140779d7ee8fff4fe35e2dba865934c838659e7c7c154145af581a95e313e8b96a8c84bd79988116ca066f1b2d2fe9bceadfb2be888d9d03ba5211e13368ebac095f5d3f22236682290020b106b60da97f440b7bf0c1002baf5ca6c393752ab6a16f445c498fac32fe4545608a83501604e5986e89ffcb1e21c1b15c5702008f372d98431ab00ec2727adb3f9b481b5ec46964a48a03c547953644fa82299ba1633bb1325de173c888b6d0fba73f60ac71769ae9bc1a1864722a0bf2522edbe0cef58d2593b8ef84fa148f83b3989325a3c466cf37d921f2454a80bfffd0c5600fc7614d21432477c2e9bf60e8db96aeb8979c45cb15fc5de508c8dc132c2fb716938f86e0042822bcb045626759f42ebe86830c21fef26c142d11d10e81beeb796f71e697b4bda6e87a65f798ab10ee020567bb90f47ecd7d178026066f5051fc09e3643b135964493d3a9150c10aeb77c1a67a0e9ad9b0b98bf225a1093bcd4113a7ccd98754a23a29c9a595ec357dc38ee5a00ad444877c45b8dddf57ffd059dc316c983ae51688eeceb41eec7c5208a29d7066c0f27eea60f108bf9e4a493a6090ebb33ef52a2d5e8e8258fed06f91a675b5632e4ab8587569ea745e32fd5d53ed44e97b1df1dc15274fdec844bf609ef7be6888b9a4b3bebcc14901ed2d6ce75720882b7ddf5a6f471fad1b971229b6445be6677ed343f2fdddebfd7f4a9ac608216abc26000e7e22a335b56fa77ad8b8c9aa5db01582009ec456c75382ef6475991d20d4abd8a5b19c5ed7e5056997443495f622b4471dd17dcca916e52c247bcca08eef683b10ad20d4a7a88bd0286eb7d40563aba41299fe25740e3b6cf94558d6e50c253acd548dcee3365f5a21b4af613b32216f7fdc7ad507493125e62558e1af71dfbc859ed886e5948bbaf80d80956da463a742367d91737e24c78b891db1c5602151452c53dba09ece79d52098b27db75b3d8c98c7d8c45570752b7203bd7982e035ef26b82df4833e2072826e826cd881eb6ecaef05332a547fa51a1a20c6f47c513a86341ae762c859430519387acd21db5403eae259f9b767943f3ef067e8a9bc94736eabd3bdf76ec70e531ddde0d2bb96c7a8229b788be98de956671ec3b8e67ed0bce7eda623ba58c004e36e27a6d1bb1901766da6e47eeb61de178675b2487c2c18da6b9ab581eb85327cef0c589e1d24421139838b133eb4d03999dd89ff96606e11e0b5975dcbc15501f4a4f966a689465f63686c9e5de0cf3b9377ff6f1bc8d475b1a120187d67a6a155592886a4663f3a5dfd82682bdf2a3065420dbedc4d7cbe8794b064064f48a9da37462f39f8696661d0e07561daf837faa13606581f6c8f6a1cc7b9007ee86c91cc0adceaae33c4d662f378da9b3e4daecb2ffb3bb3ac4b2c958fc484a9ce3b1addb2eabe86fa20eda992b4111a34e38eca5dc6ac4029976b4c5285cd9ded1e4c1856668f67800c02f2eaf655b9de333099d66258283435a1122427acf282ff1df2888087ef5d879789ff79a8d1590dedc6ca2a1c4e16ea9e788056efbd1d77dd89078dbdc42f4b1bb1e36bfae2d2a11dc0ba61a30a69dfac579121fea16adbac7b616050ea273a5d3eac59c554d04b9c9e47cdde1ee423cfabbf207603158716d89c40a474c9c35f32fbed84f1269c1be6ce1dbd542c741b1274655cf57aba07cad011ddb1c88880eaccdc780b22c1fd4aed8d23b28f5268fd996094772325b6534b06b366f8d510b926025c964f44014b745ab08fc515bd03ced035efbe32409e413a3a9f5622942a171c3e048dcb4a83ffe0619b715d418f17cdc1d0dde925b4d04ee3d54707dba453d900ce9ba8b70dea42de0969aa8f86ad901bdf4b2d460e64720a39ad3129d133e453bd66d9bed4010729b3cb37a18b58c4d871a7806e88f124ef0c8209af016a799b3166077a92508e98e6955acad131e49b664f73c26d7441c25c85911f05fa4cdb90eff8c4fde928a848176e4e65d76418d2573387599336d2e94b088ebee90a2e55ae3ea4dda87455a9ec6f6e426f0a6d75d2b4e31212e757c85e7a8ba25dbac58a80d2689217b07ca313138113cc427a92f947eabcaab4da4a3d85d7a1c96c94e3fd9f88683756a907129fdccd626173953838a69a2e13cb73146e4bb94eb7dcffeec28d3afef7a75ed2ffaf59359a087e818f5d71a459eece76fe84774bbfd7337606c40162bb991d64fd930c580a8df05ce481d769b53dba94eac66a7681b45c6a04a3ce7f5c9743ddd01bb69223fea1ff8aeb6a4d782689f3bcc87db30e744a8d2cdbc52ec5654fa4db4a756d76dd1e10aa670b137450e28b9cb5d666bf8283aa86bfa28fd83cd88639082545e0605b954d647f26d617f8d374555c00a4c2d16c25e7b57b92427f26cdada6f0cc28f11749e3435d4d92b9b1583ff494d2c616d3200ebd8b7d753c2f16e6412197866a4b970b0af782ba18b7be7de2a05fe2ea294b66da682076a3fc45d010aae87afab12d65d23372135797cc3989fe22aa1817bde71d55156727cdba514ea6b8f9422b3a51cca8f98a9d8e045c08f8ce951a58cd5dcc6b71c94771b308d8758f0d8a8b710fa7471d66624e49ab59e2ab2294e6c2683eca725bd96fe214d3c5a807d7fd4d7c86a81f419dd97e737527af61b954d293832dac46a45a04e4b36ae453d68d308688acbed87eb6274b810c088258805bcab1325f857c6f5a2c0b5dee3fbd61b6f1efdd8a3b2e80cc4b30b405cbbb50719fbf185abf4015b97b4fb8887e12fc15d79df35ceb0ff6358f6fd5cc51de8eb6962a29d68f436efeaf98a480450fe1d1ddbe2e092f8a9847f7d9ef01046d2cd03a73966651f2a62494754fff0599a1a2bc83cb98010f54e711fa83ba98b5c42eac7cf9894de7720bdb6df40be3b469d2390264107c87016746a082e5d337e92935177209288434da097957266a6431655b39646ca3ec312464cdc060e1af0c918601708842bc5199aed13adb74143befcbc5384d86d747a292e08fb6eed3eb7b5d950c63cc04f2f674a4a0cd5ec0d9a7cf49897b1bbbfc7bc99afa11822f978fee6017de40f56fbef600f6814d8cf120fc9c1baa54628ee9cd958bb94c55df5e7408f5a011c430adf7826a9649d828b76cb3e708ae6fb70442773edc0290a177d7b5f1fea71f0e02aad92d586143c8c0154d0c99cf5b3c6e00e044277fa4518af4134e9e13c6711af5b5e3a42db5cf78a401dcdb233be5389a101a5f0ffd43dc90e8704e83a87d4b9b357c579521dc3288ffe4c1effcbabb1b18bdddcc492deba69ed5245d1346f0acab78b61fe1b14482fe23e918e12ee461d413e76aa4c520844ba022e5b80ba2a804ec398aeb749b153100d143dddda970424e98ff5c56a804aaf253451201abdfe7c4245fef3ce987dac407c4c8e1d024c27749b1deb6c21f9dd7747fe1e30458ddc52ca8b48d0fc9fd6ec3a6e6e49a3940d81e0e6db257a720047a3fcea6b266a75efbcf37b4463fcd19be9e1e09f19c62bc3278d4918eecc437806c651a6f2c9b1df0684146c608f4b25753df88dc7167e0bcf1f323a996ebc4d4c3126baaf192e4fed095a35ae853fd0633b3797998e067197738185f1c1e04b8c8facf61dea4c8cf0b00cb2e0b4a2dbddd3ee849e7695f7fb8ed4efd2924025f0b88b9d9ec9cbfc1bf00796d165553b515dcf2daa994e15d09bc4bdcf48a178d163444bbd57e6cc0742de2c71a540c237df3bff4647b466b829e522a811c4c701d1aaf027c2447c141bee3916fa72d4486c5dedf7d52fe7ccdb4c3d025fcae073635c9af3553831834a86d898a7909064a376cce3f9d30b48eaaa51ba9422202014733d6d8df4843a7d08eb4881474a746efcca5dd10a42996132f02dd1a6c280555a22da7876a9232a419858cd62f61d53307ee6b4b24b3f5008d437c8f04b876467ef742406a65e6e9c4e90cd0f4d129bdf40a10688a19d8603b689af258c516eb150408370db4b401524e9e5ca1fd6454082a66e5f691e08f4c1ad588040c29271836039841d60a0d8c0093739105b4d8ac9bba389042a5c10ead217ae207275946848090f0b10516a29bf395871410a2871cac98323a16741b7f1936ce05fb4b1742b36e4aa4119d55764d1e541b8001523d1f8668f4e4bacc855d1cd6f3dcb27e1291ffedaf58140fbd05d4a5b5e83c7b99e1f1e5d566ee0fae4755e051bd6f824be81d18da8678e0915c5d03b630ecaddb54cc33295a26dba5d32009a8196db505b948ddac2d3d5de38ebe46122f09bfd7d53a0de29552f53e07104a1a131b7a225f1b7bd2db935c2bc17f4073a005913b7a4971cd9e44483640a1bfd0e4f3171493a41c55cea0ca18609a3b3d32fcf1d7bfbaec028562925cce67454b1d89e910a92c19726b22aba75700bba9c3efc28bb5b1cc88d126059717ad88d67cc5d7509caa839763a491a7791fd1721572d2a15f2e188fc186fa75aac025fe9d08b0321553e3b32150c3e19bbe6bea9d98d9acd9534ebc9c2cc70175f1e37a085292fcf12dde3493091a5a7855f6df950cb060da258123a079a010f00024945e1aa0e02e04c3a76b03856b847e126a567b2e59139de0ea8fad5ae86aeeba3310963dfdb3051780d4005795e38e52169cb827485cf8f6c1c178806bbed339b4772a24da88ed8b7faad8e96e593d53838c5d22bfab598fc9a001a1b432bfe6dfba33ca446c1f7de37bdcf2c9a54a083a28cd15f1285dabdd6d9b0ff91b61a94706f05a5da1b1a38f1c8b3bc6d78a9e59df0f0925ad6fd5007d734a66176366258c29b19566657cbe5fbee2b5b5de7895e9a4e4041cda31e633b5d3633f8b21f6db469adf8f597da2a162f7bf3fc70540d6fa2f833c8d5d4e43b265656f25a6e26daaa8b5e75316bd696ad309f9233dc9fe5a90831d199fb72545b700c3f5e1da288eabc17992073110246c354fc81f2b40b52efd0eae03c272874090558e03904fa52ef6cb4cbf54386396760e54e056517a3d49b4784cba1495aa124b0338968ace52f5ded0b9817e2d05534751af50dcc53ea65d07a849fc886a185ac96d1a2826886e236c9018f0552fffe0a81ef9134b560b4a2bfd9d8665ae3b920ac93733a5163b3934b04dc778b0256faf53666b3f92f0e08698f7ec93b5573062b7a163ebe856f7b57111795daf8a8788076a464c8662d6a30dc24c040322ced1dd00a8df06aad8c414ac72297fb4516083a46e255d08ca59b7e340126754b503fcbb03847c52f5960321c1c738e014fd22988450c6fa661f9a85ceddfd6d8d49149b42f49f4f350a43b88dafa10085facc89b4c831e2d9ecb603a9b60bbb1f624c02621a893c0c1d0974e247d50c74a441304ca2b155ebb399d9fcd8f3f6478364c701a79c8fb47f73e50a98b474f860d62446629d54dc598742608176d3cb9aeef1a1b44d9f2921915ba4623279d3922ed64f27fa55ae05277ac4b734e9659e6faeea6b2dbc017882f5e2306ed18e84d6f84091e4301329fbd48ec4eb100c2047b7e85ee5ba347fdbbd92dcfcbe0493dccc214310b304c0b6d6e41f4902b0f283f277f6e480d6c822eaf728f9167b37279e94e116410320762102133ebf9707ab96c2b36997a2d848bed2968b9915c5e15855717f866d95962f51e218812020a9534b5ce64901f22b4e4c356910f1954192543f592a68860efd1dad883e7f3632096a5cb51aa0b743ecc07103b90a8d7ee89843ee2e3b6469f07e2d2153b1fb525cdf72cee8d22009526d93e6be425158d7cc16c7da2cad84dedd6578c25678489bfaa0acda51c104871ddf0566597395a235cd5940794d8833a78466957b854571e64c283d4f08ce21ce1205be181381bca7c56d50328f66069fe4645ce82bbf92b563f90047290222cd833e02092ffda722994d5cb814d41c5e7f758ae93df38b7933f59d814857c7e8756cdb9422dca1130f32d700be8f31d7007883383167773685a40064070b6f9c4004af233d897a40a7d9833e27c8695851ab38f321f88308c76fd02590ef3ba0db5423d1067e8b9c850916fa0cfe0174a01202ff27f804ba8ae7d89584a812a6a92e912e7a7367f62074a9c933b1f8b9c9f35ba1958e65a45551ce762655e820220dea9985e547aa33e2f681a5737bf39beb21a4e182ac2332ea0562acbbc1c54a4317ff389cdc766e0361fa8bb143eac256251be34947f5ce6e520bb2d1122b00096771987a7c8d940eb7565b9d057239a77ec06fbe0325d682cbbdcee422a961b06f04b9ed8f78c67491f54f5dd8bf984b3705ae5db9d86050ed07909d5a44fe194e862e68514a29628ff40ff0dd3f0ad03f76f14023b9505652e82f27eb6873070f0c9d9416c081ba6b8aa7369c5e8c89d0bd5bf758970d09cc175a4f31261cce02c179761871d64bce8aa33c0c414626a94deaecec89aa2ab49cbb8e0c6b86d3e2714dd30917ee60a4e86c32c93f2ec4c4384848904ab672d34aee96e99f09a55b6adc1130e2e8cc53134056355a58908152c92925fa36b238c4e3458720bb18ff2b2a5078b3e9e213070010fddfb31fcd6fcd6fd06ca1982209787107a92b3e69129f284e7d93088ab047c911b1f74a30c09767b2e0b898f1005a7f0eae6edd53e1bf94d3a986d7471fdd50d8b7c866de8c02e113b1a57803cc37f6aacdd1aa8c85c42b30816173c5b81adfe84b3e0a2b4f69ef2905286386d5e1a398be38a59edcffa9b6572e5849021d6643c338ca8873fa6b39c0c1601474e68fc39e698f8040c83055bb6cc017499307112945eea360dbdb5a4851e1ab0b835014851f2dfd142b98be6116ef4f6b2847e98764f3501878fcaeecb7d0e304b692f23f6b05dd950c17f9b1cd568b3176c16fa030acd0171d4017b5cd1ffc9af6f17cc1e571b94f43aa51f3f59142b06aa701ff879b5a2fd4e0d537b9429a8d277e04f95cc6369570fbe5d4e43b98f377cad5f7243e017825ad980cc7cad46a512469631eb20c3ec18fe9c61e6a3967dc22127a73b58ba32efd5a731b06838fe8edf7a8c81fa67f4678019385fa8e5f24c01a0315e4599216afc600909248a8a0424fef95896e34b796ff02a133f992f14ec5a6740f126911c3f4ce47bcfb5aa30dc33f88f2c3347f95a23eca3416b346e818cf87545a8c0b0034d5f1189a2209d9f95efe750fd3cc37179d0a2025d73c0ab2454576ff6f6b16eec9046f845d6ae0d197c08c0547430ff3502cd6cb7da0906855a79483738d89c5ee754cace7983c1780a36ba969d89c84e076387814ac947e9f7629da57427b487e9c67fbc080d46a856b9d5e6cf5d15d97663e3b5277b8516625eba381c44273c21d0a6e7cf2337949255cecdb65ef3aeb24d00e6acb9cdab88bc5fd1c13df27e6b66f950802804527c147418463bae3078c741838e980cb1bac87337603ed52a0d29283354ad939c8991b9b032e4768711b895c4d7c04aae6f6baf95781688fef92c981a06fe597e13ddd0d0dab339ea12163a04a9983c446c55a58852c9f55a6bdb6d514fc7d542b2af8f93334df1db94b760537cc3c01809e0d2f73391faa82a19e89ffbacd3a3b91b6442fb43a4db2983cc05b7c66e294706f6a60c4c1794fd3ad7c1ee9c3702b1bab898f08bfa64e0a168cd61cab0a61e81d1e87c9e7616220344938bf70f5046310507ca3918f9933559c26faff32d0fee4dba6fa312b52b5c1083f21ba646e4990dabc01b54327d3c68d316c146b8a287614bb137699153ed8527e493b5c6ff90ddd663b1a7a01fd653c710220a33593e137ec798351418685a5a9ca908de81403065cc1bb51a832fee355f4c327abdfb57da0d15c24d397c6204e91f3fdee2a01e55ef78e980c8d3dd39c9942d4afbedad8e63ef199e0ec5ce68e6a16b6e7046640d03ca4bee735c40141ea503caec6c67853e3fc4113039f9a4ae9c10ad1e52b58352a2d604dddc214d29d8fc48c007879f177b1cba44b301fe8666dd1364ee5095bdc4929c559617b375063b327266562ef5c8d33862099184ec2df7965b4a29654a015207e306ea069c05fadde6986886a3975ced222f4b7ede67ab541dfd6da1c3905242e9515ed78ebcdafd61869f2932da37e78331c6e8028ef668ffae0ba4944f4a2218ffb507c580091b3831e2073b7841811018f9a18b305d4029e2eac28afcdc3e4f3df70efc731a2b16ceb3b9a448713558fa5c6445eb922042f8e42b72430b9a00210b089c3839810e7418e04d1119c06c71a406432091c3169d4782243878e8bda4818878e8cde4dd68112767878f183cf41558884005ae1084b08787560011716ed0acba857a2ea2c2e8bb15d10e551cbde9b9880a31cf5b0a1d16362061a48a28b0f4a013dd68891628c1050a952674e2141f3da5e463b462868f02d0895768092ab010512e7aa9766f1548c1e10725bc041131a143c3132e3630018208243ed0692e7f49f1cd6aa220aca0891e868062899314e84c000b1c746008c8874b129711655bd4e549171f807401faf95a1ae29bd6d2467dd4605eeb2579402fa893e09aa804d744c74213bdb71eedf37d3b0bd7444f126374c8bd85fe3a09a9e43f47ac8ef5235467f3d849300e251a0826d7eec9ebe5ec762d83727abbe6e1a3338c18441f637030381427c39d38133739aec4913848136f52a9e82b48135990263a0ea48939398c433a7b4eb7806bdac80d58d89044e7e788eb7494a780f69f23299d5e011fa05dde96c235ed47ac4eefc007688f6ee18d911e8234469a874e40bb916eb1e08d914e02698c34500ebc31d241d0a36fb7a7f001dab322d8a3468abab4b82bb8a3e97cbb275b357ae42215fb8bb61cf1b84df376d1d111972f415c9e289a4286a2297e3cde199cd0e1afebba9e8bb828f1443c6f1ae85ae61d0dccbb1ad4bb1b4d73b909374c00bef961e3a1cfb0cd5cd1eefc9ff08a0aafaa44619a860e79087a777b88bb6128000098a1621c22a464c480819239992c47276d5acd307a4d1961330ee83f9806ba917684623033bcbbf1cc43cffebab05d0f3d7c58d8204230f41c086ccbe01b194cd317d3b00de455934002974a0f8f88fe3d227a031771f1e10a172b8f7aabeb840aa2e7a22e4cfe75b901d11226cf441f6f30ef7969300d0d661afe1cc4c75b01ae797e6d60c238923c276df37cfef91301e3e8efa1e77f4dd81f08f7049137c8ead1f8e7702836b152ca6b444a197d4aee0d858d48eba1e452c3776c3d5f26173b64544a08cfdb0ee50051651a35e270649be2c2818facf3b0e72229b8f8e87ddd8edaa358230bacff968fde47cff42f4616582e9262098b7a2e92e2ca031ac19627b67c798ea286287e342e463e3c174521f4dd8c975148f9194e63f51a405d7a0d7afdd148192bbf6d460ff6ba29d45bbd786bb0969986a45204064028b1032a3bd4f0d385125c9e5c45a0c8e0c450124b1451430e5b861853994a41c5c8caa521092e42d48a3b43d1962a5f9f8bb6b0a05599c695615593371bedb87fced0dfe3bed896cd71f269f769ef916e0c9cce949f29edd41f7d9462ae7ace455074f9e7715a54ffc354cff906d772a8143783f3d2692cb9048a2a4f9f9d00f35ce44495eff84a911344f0952cff9cf58307c511ff22e51ad359ce6dd231968b8600e63bbee22ff477b0b3978ddecd1c1ceefd9c2e2f4e0dce3104a38ffebe2f2b8b6ef58f993927c791ede9382fbc17d2623e5ae005fad287cc8f0e617408210b2d60d236f8a70c9df5ebd8b39b8788be9fb78643f0066ab96e87733bd62a95ddce621736960e87b582a994ed6cdf223acfbbc65279dabbe5ef1199d217060161c11247e8d6bf26d22d9d9c21d7cfe7ddba3c28bc2b84c7fcf317ae7fb72b82e41d60598775e4ed960e0cf3edccc5b3e901450b7875d9f480e2075ef1773cc4438f5c0948580ef3fc1d173de4f2138688314db6b0e18a0e749a250715ec20063ad02dbcc90144952a56f0c588231d74a0a75000e60b155e74200f903029e2c20a1118e1810ef4550d92089305071282d0e2071a2871848b2974a0e38c004c0dc2e8c02b082db139699cd775c539e33cfde458a6e7f0fed88e897074e6eda8bc9cc506737a894c998e75a720cd9b4d1f25326552ef9ef43461c329b97e998f3ebbfdc21ea92bf2716aa9528790845f0862963c8ecf9c42af453687dc9bbe752ec4f0d5dbdfed1ce3986bb4db6db57de369d7b8fe4b338fbe796f3d706cb7ab913a75de9c10fae8b561d5411f7342cae67507c757a799c74d07f55835cf6ef7b6cfbaea43986ba056338ea707c7d7db028eb740e643e833d740e895cb68a55cd34ebb22b4a598da578a4950dbe0cd8f0c8ff5e72c84258f79ffe6d93fdf66c65189b9bc14db52d830df1cf31d39df5c23db50c05090cf663e9ecd9f73e6dab329dd92735578ce9b8e61ef71cf7ad5b6cdeba6f5f60093e73c367966b76ddb4cf6765de4f5cb0dbb6fdba65792d6db03a824d930994c26d2ed5e0c98f7a6c3c633d764ccc40a9ed946f67ed302e6a5733d65db762ba73279bfc94f9ccae4cd1b04a4db4ebac96ba53ee5b66d9bbdd834dd0e7ee6b3d3c06f1eb96ef3cce167ce454248f25c397e196e4ae578bb3c996b5c7b6f10b8fc75170e237c5784063bd9773e30975c87390f261dc29f2ea70f99ff7eba94b5356c3e9fd2dba57da61ba8a082a322462806360f01ceaddeea9a29cc787b18cbc3cbca1ebca2b20ee97c639452ca8e35a33396e7917e39fdf67999b09aa5f6862b56a8c41ed6437fadc5870a1f5f1355110383142c1fe3101fde8a9d89d831b301dbcd78aed1bb18b1963ad4426fc5de415ba472bc14a243e06b3e7d5b4dae060bc87611f8f16ce60c2034bce7d9902e768760bb1c2f493624977e420f6379d24d49df4a5c8d47339dc21b7f34d363865a3d5fb9e00c5e642c76d3bcbbf10e6f5270f5d0e4a41a8fe6f28ddbf156975b2e0707d25c9a439aabd2399d44df2bc21c72d84de6d9bdb41eadb5a85cb9b28682414a829404290952420a5242229148a793475712d457e25f9df69792d3298992a013494910298992a02b4a48249256baa49418053e7943fd49da04fe786dde7550c9db63b7dd78c130b975cd494e3dba89e3b78a2687c1f1a3319950d5dfeb4c1e65eec99f9770842595fc957004255d9e1249f3ae47fbcd37a7b7eb25ada44d385d9ecd49de1ba9ef7609fd66ba5d91bf3cb692b611a3e46dc706b0f843ecf7bcae207ff5fcc563c68cf9e2be10bf2f8f741dd2edb56f054f27547d6d72de6ea41afb7641415dd5c4d14723fdb2be7193ebacf4cd2f4efe4cd1aa5bcee424ae1469c6498c0629095212a42448499012d333d920e5fdb9e80b111fdb571d23f4cb637739e11b0a7cf46ca66f28f4acf1ea51ffe4a7bbc12d7e9e00cf455a2c81c59317e1b9e80a245fa1267fee6b41310fcc75fbcb75fbe8ad62fc214f1e7d447947ffba5dc9d9bbea24948c533fa1b81ed4493371cc35dbb377bb5d0fea35df4ab707f535f3edb293b8128eb0d5bbcc1fe69bbf2d059ee9f1ebe5c9503f9dbd9bbe79d74f7c941ffdf248af7e79a6cbafd8953feffdf2f276970be9315f5d488fd101e1531a6e46eaa176653fe0cd69e34723398e6f456fe6936bafd325473b9b18c7f04122f7a888bce615025fb2f351e995bc68f1f3da53cfe2892caabce3a4589df975bb9dccaf1e995ff2ba848268e634bbd1c9f3d56ab57242e5d5ea5dc1155cc115bc564e9c442027585e9085d0972e7fe5e9972aaf05115a30f92cbe7c7d1a839ef2b3b191499bd0dfd715858604618163b6a5401d7a8c1c9dc4cbb178056291056fba05a4e44ca6d3e9a6e9daf4b735d15b4def166992bc04efad17d74018766117766157c5b00b62b16b8fe8a44d28f9c963d0c9e1c5aed3e5c1b458a32c3c4a5cd790eb202c5e41d0a1470ebb5dc40093e8589547c587c73cc2e89087c7ae1a85c02d056905f3ab8b3efcf5aabc1b16a419a2511e9567535d89d5e5352eef56f15eac8194b0299fee9806e123d2f5c3d0fde4bbe874dbc1b008f46c98b40998d32814fba29a479ecbb11ed42f7f5d7c93f5e5f92807d26499f0763d2bed5d0e3187d121e6d12f787974ec311a3d32691b97635b0a9476949f4e8a406f356f52acb9659cf5ae2978030452a378a757925b4a29a5b644a9a5b47479322739a599936e74d2362c75ebd5725d2265f22f9871f2dd53294a6ff786fe5ad5281991aeb05635a854794318b0f2979778f8cb499767dd053e9b4738ac1b5a9e78572ed0250b16cc1b689666fc6b35049f7abb89f307f45697675b0fede963f268802e9fd489d05f6e5ba5db59d2dda29388ad7ef5daeaab6397c3d730ef86c0af7e6134cb2ecd328f404eda04143a06655874d25868cc40001a0348a504b042a594c68dca4c4d2cf4f0855ed1fafe5b6463fb0ade5024dfed16cc770bf172c573acfc6c3cf2abdddddd5daa61dbb9df0f5e8e48f8374d564395de34a9a4c37811aee7157c17281a3366cc18b8edc487a87f2a292dd00ffd0dc186f656efbee049d953767fe1807f32f5565e96f4a379fe70fed58881ed52719552cd6b017f339e4dbc50da9f7bfb0a6f88da7e24e6b9c88a2cdf959ead307ade76e68ccd794c8373eab5427acc335739b7ec3c2b0996630d80aa5fdf15a0bc0b00db377586e538239c111539865cbc309873ce795d2a2b7d2ea997083bc21dc28a6065aa8efe94a9cab01c2f4a6350caaad9522a8c8ab23f53fa29e0d9c4cbe1f5fabd2f32bbb30cd96ebd9653d4fe793697af783c1e19900ffdf27e36fa62bd75b43b9107918f10b8b614e2a675b1834d5499138410420821b4a8d40c937523d5962c69ca272164669255cd4bdfbe5f1cacbc343a4125768bb09bfd99021d93108b0e3148afea8253a6a06af6674a947e2bab9e4349e23ad1f9ef0ad1a1c45b2d8ca1fb299dd4ed5bd149fdc2c076da53a7ae59149df3428118c46e662f9f4e9d60527be9735f60253fce02fdd163840da9cd7934541dfd978a2fbf45d8697fa648afceb5d90605cc62398fc675796cf750a585fe338539f55ea9bbfbfb461e3a8a3c1dd1df83c073e873e32181fb9027faf3f83c109f6cbed0fe4cd19e022e10f6790d32ce38cb1e665eb43c177919faaec64337c07391171e9eb7149e7dab1a6f05fda2c07635605de263996b4e9de4cf4f5785b92a73ead537b75795dd1d1586f5d8bcaa300877b60b6f47ba8cf9e6b47ae650d3e05569aec23c53658eb98a6e57a5dd9d9a5d56d1d81b8fd20bfdfd5d9bd4ca31d8da9b8e1ed5b1ce7ea79a391563cdabca7cf88f1e98d3eafc41fef2b619f40ef2aeaade1deca2aa7977e2e5cb0ebda94fe72323f6ccab77aca57ae657f8aa308fce5c935d15751536b1bba3a2d0e574e92a7a777e049937876bd8e5f5c1354ca49a9757b757755d15a5aae92aea577655543a73cde5aa797754d359efcf0ee7dd61710dbb3febd9e54ebc7c6d6d7a344383896b666898a1618606949fbc9e50d15acb351e2463c10cd065ea49a55ed3346d041fc179bb7c043f01d1d123865b37f909869f646464b8c6c43540fee4a6db0149a566805233402919130a25422a3563c60c50ea8100f9ae27954aa55229d44d718dc9532c203394d4190ed8cecaab79a73241016ce3792742d3b0e952dccc0c36f5f171724ed8f156373ae0cb9b43ccb35ae6b5d6eab57a6d6d8603b63dd658a3e59ad40665bb31a474a648d415539f4c41e5a6d78ebe5a39f8b25739feecd107afbc2fc487cfdb8c0fe1e5697fef0615c678d139e725abbc556ca218233333330f31c658393a333373eca1b78ade41af872a394b5bb1f297cf74c056efcc2506a6381122cbcb1eb6f2f3b6c059b27e2300c2ce0c83a669104208211d22bb45c4b74dd334cd731e84104208b53be3adb4db959e9412f7a2cc4005c63139967cd338d54ec9372fdd0d62be396f4e728dcb38150fad04e155ed680e5d73e82a1e1abc999f6e676f07bb4d0891cf1cf30d7a062176df4f6b091093b7ea242f4914b64bd17843f03ea1b77aa6a64ec2440123e56b6946c91177c91204c943bf56581eead38e3cf4000f84a0e2a163589575c219582a26cf797109c6013d628947c42c91cac91f94b95d919751edcc1cf8accf8af28b53f1b81a06a597b92686bfabdab9fca9785c97b94686cbb85dc3b81deca65bcf814288589fd3653821f05f7779e8370adbc5280fa1c728cfe639f784ded06b71ef877b499600317912e5ada488c9381888b4fabaaef7de7b8f0e91443aca93795dd7f5de7befbdebca17e549015ad24241178b76862652d7ff3c928ab820e207f05c14c6888f5f4b11909e6518865979ea56a394622bbc9b1662219b9d7cbc131eebb12667c4622e25ca11eb5fac8ccc8512c3f9dba146a1c68fa6a364b144b46337057e6ae5cab351d1db40df561ed18c8f0d43e5bb06fa288fe639f6a3f3a73ef363b921f276d135906cda5e0120f0e607a4e9be502c81c140a495d91d2c9433bc9171863727677863af149dd6f5dcf89253a7ef4914296fd56ee23824b63d070c1568a0f2346dab30380e89c5bcd33824362b711c124b7a594c5c0fce578740cfa67a1b411a268c83c22330591a4b5381340ddb51b763a22cdf2e733b9c2bdf7ebadd8aa8f328f4a9b598744eb5435d965cd6dac5c738158f2c9e30a78ec938bdaa9dcca98a07cae915d2635032f774e3eda4974a264e0891c74a2e63f8925b4e087cecd2d27d3f6fd54ee25eeb2d017a4c648c21e8311884edb4dbc54679faa467dce5909b8e713d6df473fa1b7aabf627f456bd6ad722106a496508217c7c4d208fa66f2270fd601c90ced810d2211c5472c54a101254ac1d6366669e31fce58cf96caf025990bd02ef2687c809393556fd04d7f46322c004e915de0e157c68da0f2258ddfaa797fcd86ec74641b40bc35b3537e409dbd9b7f1e1addaa29e0d2685a5c2ccf888ef86345d6f8793b3e3adfa84b7ea2eff301f332af0687c8dbfb09b9a28c9f978ab151ee94e613b26ca993dd4fee4d1740771ddc2a18f8c6aad517cec61a35afdbaaeebba3ce7d19cd70283c1ba4e5285add8e55966f4115efaa011ace7911e5b524a2915e2803163bef8cb29eb3b1ffdd76d81f5f4f244971847593f27c9e79c94dbeebb3208237db0089733568fd1e7acb55e213f5e7a74c9f5dc7898d1709494524a597dc65bd54b338a712ddc781eeaf2ba5a6c878ade4fe2a07599ae94524ad902836120f0f208bccb87300e182ee3979ff06c507ef90cbf9c856763fdbaaecec6f02e25c33b4f79b77a11bc63e1fce058803e20cd6b22ebef76382c116eea762b1937c63d5dd3b5f684923171995baebfad48faaa9b3892f452bf27b86ed5b15e4b84773198eee62d3ef630982b047b790e6c23ac8423eca5c23ea3bf9cc17cbc3d6df4f03e2d3e386ae1066d979286a75018876118d6b13ef556d99df15652b6708372d9ed6e7ce6b2bedae5a8396af5992516f32e47ac15ab58f5ce1fab58e6327a79415e33cbb2ca5badb5567fabecca17e4f3489ffe12f3b7799722dd2bb45a33cef29a577fae713492362c3a8d50cc8db78e951cc3dcdeebefd5a7bd58a5059ce7998e653efdf28bc7cdc7ecf6e0bc9cb55ed7755d17ad388fdd78cc570e6f34c7528f39e65ce359ce6e1a173ea93bd33a72832cbaf07100025a3968265cbc8067b6f862091458cc800746266881e034c5881c6c9841132684788b95175ee209ae415b21b5103ef858135ab0c8931984d8d204972a84745082286a4d121bf8a0a8074df4500226b230162d1e4247611e6031018fa9b283974498241f98a20b2f59c20c6169c5c036695171021d5050c5171d687072848f133e2703b47cbc55a32573e03380160e4b6630c3a503de811757123eac1698115a5b7c88f0b96975691080d1810f092d312de6c2081e88b9d24408f80815b40c3ae0e383a6010b7c62b494601bf4123c44d197a7e569f161217ce153d3aa8207237cbad4f281c2070b1f4e82abe02c27904232c9a083171c9008c3c50870c0e2d3c23a700215b49034b9e10810592bcc3c00006eb466b4180925f15a4141828b2d32e8a22807127c71455642932f9ee8e0871e74e10307a184565b81858f542db6d23670c287b570100bb8814f0f3eb5a5b5807c847c08d06a404b0a1f233e096825e15a50f8b40f50ad2d3e5ffce0c38096123d047b6926b8f00fa0d4c0b14b7294d3a4d31bbe08e2d2deea9a42455ff0f0d2a423a5943785fa185157dce083a3c55778e163c3a695a3a5858f8c5614442518c20e1c85f88116496421c30f5c30020f605a245102254d42f00384f6a2890d3e382d2a9e681d68b452e013858f4ccb053970c0a76b10a165e45380d6155314d1432c441b711122401204971f199e4f87d172c307c620f1450823a8088ac6dcf0527a8d7713758a90e0080f20c892831723273009600552003b7091cd00d9e9a8ec546b4f4a0646c96a1ac965306d4339759bd5ed94d58ce3a915c86baf546b9665d5559b65c7646e87ba3da99a7957cd65c420f9f88a612e3456b5cee4d449192acbbc1d06eaf6a43e6b19926337b336b3405e23c9b80c57728d3bddccfa65ef6d21f53cec98b377169b31d2c35a3a8a1640b8a83a84b17386065bb90757dd0ba1ef1ed88551d234cd4f305c33353f0cae9371f61227a4bf3acfe62d73bb7fabf595baea9b56da583d3657a16e0f55bd3d986b30ade4d56be9743b93174bde8bbcf8f9ab47f5ae59116c731931503f3e23b9b0b98966327ce358480c5d7fdd387e195c7772181e8313d23aaa7330b813d714f3ec76413ef313cab19a5dd4c96588505e714824128a95f3993f0ce52814c97da436926fb7dbbcba0a45badd8fcffc919c545db5dd9d2ec867fe64543c4ebef9c94f5cc933ce743b7bbdf280dee385f83c0c354ee5c20ec931df5ce5c2ddd1b099231663a26fcfb8ae878f1ebb2fc06fbf5bd8e9fdf185b1b307f72cd20e6664602c6cdfca5f8d6733e50c795335581c8a072dffd8db4a5379c24ee4dd74abb5c01bebecfd04e3c89c1d06f6e1d95bcabb61b19eefb05c97b899a2f135faa6855e03055d2eb66b26fda487f54d34e7b579824ab79a4a53a9b56afc240dd3305292feed761af80cbef6f356ac25bb30ccbf4e582273c2b0635ed79565cebf655acd58c06ea781af0e3fe354f0b1c25b75bd3be4df31b6a954e79afd11cf269b35c37ca579bf763b0dfc9ce1342eaea9d06b643bbeb243051f2b94b46c36bc8bc639e79c3362f1ea16911d6c97e39fe738e119b5b3204d7b0a42222c38df3d2a442b1cbf5b6c5703e749c1b0fba2bc55d0e5da5b31b94d5c54b01f3b5eebd15cb2e32b57aebc2a5ed879a950611cfcd521f5393b18311bedb10ef598f3bc30e813522ae47a5a393ae18c4b9a3e266fd5421b3d4f74d94623748fca8c076cf6320ea34eafdbd3466f28de27f482629603af6ef8468547d32bd87048ec7b3708d88b5986dd1eeda9ef38e1072d6533337741348304422983b85dd847e7f438393a7df6b410478e1ce1a32153e5c2ce54b9b0f37ac68885dec9dbd3513ede1ed6f2f0be00df7fe2a52c99a5b3370f4b8f555e7a8c3e93c4b6c71eb622e5114de37551e57c6f3eae42adb7f6e773ca09258410fa930ce1eca8b9117c74f4858f784bbd49a4a4a6692c689a47a0d6e69669b763bde6d9ed70b4dbf195d7b4db559f41d9aa2956e431ae67152510578bab45aba075404969f2ed28294d8e40cdb8408875c6eba38b62375e3dab1e1973662a5ddaf8e7a98f2ddba11e765cf44feb2c6ce746ee16778b16844bd80e358335243efee7396f92c03410fa0bf2edd8bb5d3b090ce78c56bb5fd84e7bd8f47eb1cfb1a8dcc5135f4baf07155d3618a17ffd5aba94524bdb78ef757b7737bdd306d606a93fdf346d835bc9377f5c0f929baa6b5ca7b2ce0fe43567ae4b3d9087cea46b2997ee4e76f93edfb4db6ddbed807c4f0a655340b014f5cdc743c7340884fab67126d7b892a73697bf798ccb282032645c1833f5307a52bf6d7eb98ccf6d7b8fe23a02bc748e5e2e1db5396fd6ee602c18ebdde0dc124c6d4e378c42b843533dbec352df3c7398aa57a56d1bdd6ed793a27ce559cf3da9d48ee72bf2f4b60d548ec1d2aaf973c8d59fc30cf34dd5c3e425e75791aa33a7715d108d74774cb7a49518dbb80bf2d0e1ed823cbb762a715d902f7965be91c1341276da0e734df7e36190a75e390bbb20af99bc722597c12edd52975c83912e14189ef3605c7e8ebaf67213676f07eda3387aba2a5a65a8578e3e198e677abbc60d892fe95f30f682b17c3b7887c49f2c5a9dbac94b10d2bb63fde425373974e857159ad2a8c4ac96a5490c99aa810400004314000020100c074482b15830221448313e14000a99aa46604898c7a22089510c031903000000004000000000408c010349170ddfcaef976e7f817ba9bbedff7fffc7fd6f317789fb22bebfe07bc9ddf6e3f7fe7fff7ff9b9014ceb823798b80693ef3ed241509c7efe7ffefced63a2b0aff5eaffc6efcdffad7fdcfe18e12e7d5fda3715fcdb2a805f7a57cb476926d562f179f9ab7f63529dca287f646cfe50f811eddf1154495aeb2dffb4fcd37fe87fe9fc53fe537fa87ee9176c5bb0b7a83034f48c3562efff48fb1b4897249d297c2f4d5777bfa2fd19982e25ba2a682f25ba52e85e8aaee26e427034ebd8db1245af02fb52347bf39ffa4ffb434f8cf5a260fc68ff1130294f5c96640050f6b9dfcfffcf7f3fc98cf84f6b60bf246fab40bfe4bd69cfbf904ba96ee680be52b8d946f496caad0af694dc6d8f7eafffbeff51ca559a3755a057f2ced43399b0dc985bd52b7d7fe9fddffede7f6fffefffb73f777eafffaffde7f64fe05deadef2f4ebedef405709df56017ea97bc3d7df2dbf17fef7bf7a990cdc58f34d06ba2bf2510839ac43af8a447ba1cb1999b151c8b634cdb2fb3f06a44a85ae0ade4ae8edfd2fd1ff06a85274356dfe02d452349b9b7f02e952a7310a6b2f4d47b9c19fd22ffd2ffd9fe61f7acd060d0c857b2fda6097d008c3870968ab8288529cd6be0dda4b6201ea2d497fe3f90be64a8ebb10b097a0bbd8fed0a3217a4d1c5bf694c4ca652cfc6c4e201b11b3e3cb6cfc08a64ad15eed2669ec3dffa1fa39822a4963bde3df94bf817429daab8d7e69ffa7fd5920557234a6b0bd44d15481add4686b617b89a235037ba9d19ac2f612455b05f652a3ad85ed258ad60ceca5466b0dbc97126d15b497a2b5b8fb174e2d6dba19bc2da1576bef47105da23446e17b491ad3fb7f219452a157c19b12babdc92f9d7fdafea93f74fcd2ff4f331944b7b437e993eecca14a538d9566cec27d6a952c2a6d2ad363c9b545b0d6b64cd551c0a7895c0cb24c7822df34385a4d409cdc8a0995dfc9ad1724fe3ffc169afb164ace8f6505fddb4b623cba632bac7ed3bccbb8a088e4f11f046408874947d46104158649ba21906b945f86493349a36896fca0930f4b49cec0ab4c94f104555760143f96daa71ad853eade0aef0fb2ef256ec5bdfe02af52b92d047ba5726b19c12bcddb2ad02b796b7affd7bfd7dfebffcb3f6eacbbfaec0b7ac27a8f7f8f386ebfff1d9052a26855605b8a6e6fa669ed5d3fa3f817429730ad45742f357ab3f8ad9468aaa0ad149db5fd35f56f30554273adcd8f207a89d28cc2372549ab02772569e6f69ffa4bd92f05ff94fee9fba1ffd2fed3ffa97fa8bfa6fe0d3e3dd5004a4ad0fa204cc92580b3d19333620ecf813387580ca9a05400478ff2f551a9c04d9ea28d06ef7016e6113579e20cc0151c444e9b3ca7ab3e3f7c5061dbaa7a3a779ca3e9934901a5cdf618e8649d2d8a441740f5d6d32471c1b735953e1637f4315654fc19e1b98cc5cf88c883a5e2a557e1827fe6998c0b49be0a6206fbef8b0122a47b34615caa9dda64d495facf1ae6738c883a12c72a976d354096309fa172f93a2aa76834a0422bfdea98dc5c96827638294e266f91684bf7bf4ddb1fe2aebe8ab74af52ec3129460054854e46880936d6197de4f75a67aa33ca8145d1eeb44488d0278c5669496d0769887cd0f08349945970f7b714c0171a800265ebf03092551fd4e698fe83da3016f8bcf55647f179dceae5754bbd85bba2f2c6a51d7831f68fb50386706234d85519447d777aa7736311af4e775a7f6222478a6407848c42b45ae11287c56fe295c98b52450d540159aac67195d74e8866237f5d16005372a439ebd03e355aa3023556ea21800a30957de3dc621937b6d3c8738cb904b69d910202435c26e94207862dac743a598aeaf4353286828b662aa5c7f78c3af9853012247863a8ad7f51e67c2e7ff211814ef2a84453fd4ceb4433629c68feb7adbaff7630674f4d843c3c1671d05c450d34434e03f2b708a37be4d0cf42d636449629a272974bc2155bacf9425f2aff6447cb18ab3be250fc0eeae9133a957830580a65781590a1c79e9c1ad0dc264ca015247408fabc4721906dc142a9ff8e5b3a7f2511f5e3188adbeca24389d18d31fd1bda89f5483f6d9538d519056566d9bf880c42e661944fe28c84fff642d0b266795a833bdd05de883e7a6479b584d862047bce9bfe71468eedad6a6051416a853c5ce46f046bb8cede99c93cce0422c5dad5ba505f346ec121f7542bfa4654252db66a1bf3512ddc68ad052e93be407b5bdc0853e35bd4c81ad466273f348e5bd8d1eaf6afe098c4487c9da6224299462c3518966cbec8529ed0a0fb5bcacc4787ef1575b077d0744795e403a055e82fb4038bf16729bafc38026066b94472b272329ffbba5e893b9836eadd02f8247d09b58eafc75d6917318ead989267f0b83b99f98f03325abeb52b0a316c9c2f217f05561dce574ef0b879ebaaf5457da8a0aaf8cbd998fec221a41a520332ad2939cedfefd52508afc49ea58a658c583b20abe5bd5d81c4f92384676d32621f29ef3272ca34c10c29952a4b39ef2bb030d9ae8b65e2f5425248d7819a019b26b5e9074a3e92028c747eef63c856b5239c5b0d74291b2f4d39018883d8f519057b45a46d43fe2104dbe991a35aaa3f23b3e7af0cc3ead86aff205b9f210ecc73c8c034b2a45e629cc99bd01b5a62a093b09aaacd3685ae10b02e8d64012757690a67bc128115c549c029d2fd80d2ec485965d290cb91940b8e1f0dd1fe8242e349f72e41e43443923868f0a9523f61cbc8335785ec189c785d209f2053e971780c92cb4c8cd5aa5b85c85fd6e8d54a85582480559d335996dc061d780980bc7bffe2f0c97125781b4f11b10e7cc95e57691d77a5c6f3f6e3f39b11d94b65fd6b026409094b09cd8d80f478405e7e981fd3cb1e3c66a68480fe9ca409b8af24c7fb61447abd4101c863ead52d57d7348ceab6ab8971212d87e68e634ec668affd5151b86a06adb3ba3621779125dc41da670e836a9def81301fcd82f49ebe3c7b3f49f9eb8f9cefe4b5302bcb2481439eef502deb7ba694bbea6dfb3cfb1b3deb1a14dfdba85725963fd87ae3f9a43212ec65c54a3687ea06101d6d814efbb6924b46539005757de4680583ed271a11130617b84f5e82dd14dc4fad398ea9b807a2aa7eaf6fe776518fd5410267a29a20730a74afba8bb4f8430d364f771edff8b9733ee5441225d419a2bfc320c410361f5ece80d9417c0e84bf64133df572c185050aaf512c484b260e409c338338b4d49e5cb0e078245e74489ee9b05b27df246b2ec6fe1d4536fe2f2d340c2e453dd43a87038ffd85110daa63e06a253cb869050df715741b7c3db72d4ee4f840e80b4053a567f3aea5ce50a65723f416611b27f5c7bbcf8015b1f5dc0058d8d8091fa8d910620cc27476ba69d242118fdcdd4d3224e8c3b075b4419081f8bd96834a143aac38890340367be226b6141ca1af36e1f6e93944f81c11edf85d6b55374324899e3bf3fe788b2cb52e8aec96cea8899ba50737e92c0af15a80437dcecf9c18be03c3c6407b8b59972655e86b4bbf8ad2a32f13d6bfcd25c03b5ea3ad499d55cfaea7a3140cb47417a6c11c5431af9e27012c5942c7f435c705aa970454558e2b435444dcb2192f0a9c0fdb9439f4f33afd769f4bb752f2ba3396e0815bd4098671ddbcfb17c22628ad59562e596c9e47ea619d06a92cf0810747a49292a1f963cc23b06a81f8ae81df8d2b23febfdbf59c60d7d9958699cc22d0b60d3448380a2ab045bce48e3a9c0110d91e9f5e3abe1960c59dfa1d21f1cf1d088999168f85225a8ff4528f810838348aa3d1740059ca015f3615d98dcfb95055743104ebc6b96def10532bb47f641761d2bf9e649d27a9f8833dfe2c4f2142e3fd275dbf8e0861b1ec57f33d728cded563bfaddabcaf641a2c0545a60357c68b68504478d17d1ae19cd033f7ddd00295ee7f8740865b36a9fcbf87bced308392516b4ad30bfd2855118bb35522a826b20d16ca5e992b4480392c5c2be53885c4380e0aa94cd64398463acdb970ac231b2a95ce26619102433bec4a80dba08d7296caf6919be27ff98e5a64822ba7b3f4606705e336c850ef081bd11446aedfa2e25c1c6c36716898168221339b0eeb785d00ee7d16758426e766e213c60228588b2adc8fc620ec0f8266334b999006b59c3f4ca4ea23ce1e372cbaddc7df01372253b1e3b38f50681974b1714496fcfab7d98b3f6f60c240bb4cee92a66d86f2b535c28606b4574f4a19e03851d990bb7b34ef659534cb18f86c5b53502d60b41281c7cbaa10b480a530299ebb01890c05967c095b3abb202a410c1e498daeb5d0f666ceb48b900385c94a73094e9c6d126d9279dc09f43a2db6ed8cb3ed083fdc7c3f97f1ba9f6791ea9f9dd3dd3ba9f39dfa847fe24b5bbf22a4b9b5357a5ff232b428badd8c5ce8420110eff85b59ee97f4b828aa602db508250ab118636ebbecfed716b4f7bca1ea928bfaefb0110d9b9e0b6495ad668048954c61f380409c4c448d0d10e3b2097c56628d8128f61abf232a834cb8a4fb710ca2ae8d83544402e93cd205c9cb87c4f7754dbc6c43113e7a6217267668303d3ae5dc2f764e6b1922b1be61f656b019c555db4ff242919149ff1d1d598fbe5b41248591918690b9a1a75df41ca01759719c7448a2bbe5e1277549f63a0cf44ec43c8364864ce45933d350980843a773741b07d3aa3de216c6be87483189c0e6fb495a8510d8b1004d16a8ee0b9035acada1c35ffcb85c59cf98387ae8dc01bf01b4232af60477089cea5b40a0d93b6d332cefc1d072d0845b363c6412a1a5aab27b18425f10c4bda54aa6c597cbe889614ff57177640b56d70a22463195ad0d94c3f41816377a4a556735819f1eee218e79bc90ccd9e47b15a0269785fb36723aa712b698a57aacddb7c299937485078a7dae81604e39a00f4a769a93e512a42b1df214e66940f205a9838a8f1701aa4b748d252cac2d903bfab841bfd7f373b379094a66dee6eb7b3a2d04a06b563661d26b151a920a172a43ebcd1132a8c42f5efacde6967f34cf33da8f22febe349bdbbd216b5144f049cce508e3b7c54a9e0bbc087fdb8532f25cf2dee98dcd243d56e1d0d272c7105fddb446d8762cde5e53f245907e34a5eb88f8e2aec7469f0b0a14ec07005a6a929c7182e338060364138b6c4b6dcbfea0bde8244ded761dc15625dc29cbda6d24fa70e06e7f55145eafa99ab2ac829dd00247cc58d72a4c6522401a72c11484f1816311b21333f8411ea5f12f673745a442db249663fef71c9a8ce3a4e842edf3c5daa0ad505aa07272f1c93541b37c932f2051a95fe26c4448e3cd8e89118604b9610db110da2812ee486893e043eb9d24fd64b62ea46d19fe5e28a6c984660685df7d8783a60d576a3529913839fa90a1a1ab6cea88a2197a39c0afafdfe305144db3834756131a7e1fcd8ed5d13eb816fbeebff29b6535b2e9c6b67c3eac2ef29e5d6d1b528241b4dafcba2e18f92789d72e92c7309c39740ec34b847ebe1e599346d3beac0cb2ca1508ec40ff2ba6664bef0ff8d24dd9aac4fae43d719fd85864a4c001f2cc1b1470933517d716e1437be47ae366612b578a17f3c79444a55a06480217317442efa9982b9211ce8497e8b2d87e4213bf3c3e4dbd89cbce6bbd97a9643e92bc03dcb2a53f9858063892ed9a80ab022dd4b5bbb7c16020bb35662388495df2d33cb3085f37fc79765eb472540a02c5624fbe77df5b2dc5251886908008b22bbe3b054bffe757ecb5154a63f474d95e91997b46ba17310796afd8af0622aa34753d97d3d3c9ed4f489165017b050a9c2ac8756eb05993ea589ad128e6e0d25d97556ecd6a3b2e3d5a0ed5ff0db7b8e4109ada4eebdfbb64a52ff21b024413a559b4b914cc0408529f95f09c9c466c6720a7d5a6c162d1758949aed5b657ace9412b4b4932e9dab1d116c0164c4f1bd55e536ff23700779f70326a98460ad489e541c4879a2e4402491d80484ed0a74fcaa5997c14e718083a3d785cc87adfc1a0e568fc70c551a31e2e33f6eccfd37c712ab48fe53ca5a250f4f136e064a0b8d407f4c6d202427ce2fb9120c34f75ac89ae00a429520718e809bfe1efb618c3ce7c553cf047df82aa25550d8d6225e70bd5625dfe1f9c9ad53cd55dd556f9a23eb96a852f3a1f5ee68402ac48e8c4d3a83ded3352be92e9560f9fdae2e406d55dd7116b19bf50df12b0724d1eba47c5bd371c76ce4123b5c6d5d945e5382b7f27620cd2c8bf3141dae8de6fc8cef41b76ca3b7bdbda506bf31c91a9db034e6d06c198210a192a63a463b19f3eefde856f6a05769c9ce6b24e28a7844f65fd9ae3191ae99d8f395d4325c9c1100cc116aa9836411049f2ff17d6b477dad6c469be87fdf3c170b6e428ec944641dbebc3778128ed5b4249a29c823b8894c89edd863096f84b475bb287fde49474b46356689489a7701fe2039b908db59f913bbf5588560289fff8ac963cab21a1fa02977046390a21518256bafa809bedbb0443472c458b13460ba1b424d34d1d85be28d17e9d9fa297a61c60f52a2291ea670f7f1daba5fea04b5e6a250cb7cfc4c41670382a4ed87a9996a3b388d46c30876c63595099b707138fc5ad82d29a8912b1f4cdb290fc4ce646016084fc260973b773ae82a9d9c477328d0e0c47b8b0ce2a2f57f46a4f958d42cf2f26008722451b3cbd9bd15a9d8964c34cfd437fe29b91bbe9367b783f679422746e90c668f2ab32a001c60672b986b86650bf8568b929015fcb6113bb7f06809f6a69950bfea46694c4b9bf8c46cef091a0e37d52321959ddda4408f715d7f789331ee4ed888470bf9a693a4512beeeeb0c0950dee0a1d868d042ccc151156ae377b98fc7909b13768283f6e1ad2c9558642a3bc8e20f0f97423f434f8386997c65db686698afe037b4e2a009c7c75910e1711d1007bcdba1ecab698a3889a013e74aff660dbb739857f0d3eed7f3a44c80645f097e5fbabeb417138e563eee2806e0873fe1f16d757eb70a98b3b6ad7622103e79f62ab9b1c9cfc8469aec9a72a0c6bfdcef8cebc1be5683ed932dc3f202bd65dc397470301aca59b4c9ba24fd3015fe33939257c9dcddf06376cd70623a3196228d5b358251a36bf7cd3fa6d568363e2636c287f71487c6b4bb6d9a64e90f4a91a852b18e6ba42998181205f310bf243b85cc6862e9e0ec1302bf0c65d1ba1ffb7d91d03694fdc04b03a602920380909de74452dfdeeb582924b022fde565861a8c52909e1928acf78c1884ca6215dca18a072fecbdd58ea3b6f8b0ad40bd6f8cb8a82463cabaeaca3de90dd82d4241612f6e7e8e583b2767fca5860488046fa5c850579a1e70affd07b3c54eed311a3f15d71e9114c9ced71ff200e58eeb26e37bade1e03b8ab704e44576a96610e14a2b31f815fe723bf1051948c8bbe6569bd0ba54f938d0d2a11c98409ed17efb1d44892070abc75a8a2ce1e11053c7e2eccb5729cd2e08f4286319583ab7f71bc8e1bc2f1d14c65c5674ae5b3b8e4237403f6098ab01e4e371d788ed6f3f19b9868bfbfaee6cb229143c04ed6fc4f6a22d3f898fe4f43605f39328d68ad16ab6c45e14252227d8e0371899bcc28759448fe9df4a2528e95617589ad7169348e27fd8965551573e1d64905c04904f259d2cb37949e5fc1ba22c440cb5d986bbccd6f89d28384d514b170aa40233a8dc72494b45f6fba1470e7d52c8bd9eadb180ae7b4161a31ee9b8d9697555b1c49c69034cd25198403282c1d3128f4594a6587a515074abfa5a8060a42a571d42c5f2279a9de89ac333998946a75b21f8f110ca96b356726fa6f8178a37515feb7905ccaf9768e8db2b168147d22122f2db72d5852a8dec2266b8f57920511062c1dbba9e16e1ad371886466189cfdcaf2654dc29c7c5b3a695da300e47cbe4a308b820dc467929c259ee7b65f4774e200d7aedb879fac2f594d0be87240e81940cef157eeb41ccb1de8d4a3dcf67e2eab2a027dac453ef88b7eb361157388c5f1509e5d64d90deca5d919ca6f51a5d4481322df044c328fff9b7a4d3092637c878e21cfbf3d706979a321edbde8b91639b9f2d508dde7d373a028ab92b2d9d766f43cddf654a0d7872f6fd03aa4f5f6b292d1f9eb88f9cf312a17b38167ca6514a3d43bbbc52048657ba2c5b20c401826957cde6c34d134bbca72c9688414f6b151f4a9380f659eb8b9b6e22e6ca1e64c3618e1cdc4a9fcf8a43f61f2c0d34c90a28f49935a965f17762bad62943ef6aaee9cf782f396c1103c0861188304ed4bde8ad74e707e897f754af411c82a0537beedb0a2c8397abdd3a94ff734f49cb48aa6267f6bd326896c90fbb5f5159fe07140f7062b05040bcebd9058f3b1143e6c5c7503099438b68b8563a2e4f2d3581d8224b4754d043d7cbdfe0c841d8c46e65eb37d76ce12fdcf2deeef3d3bde3fb1f4cc1470350a8c30da894daa767b1b5603a6d824c17e6713a2cd622e15a9428073ce13109f6335c705ef83aed636c471d5064a0f0f6e2fffb9269893775b6febb681399987e903b1e2e8608ad769e3a87caaf57c4ce4b6e409780c12234148108c98cc232c305064fdddbf2505d0e4ec4018f1e54e611b4a47cacc5c2e41b103a0d39d0af362c6eaa163f8dcf58fc92943ec5bfa3d347c5621bec1ac211cafc7fa532a9d3a38a2c058f9fa66e796c49a30807f401b49791ac10ea3f715e95593cccb8c38944c25f83236d5e70f7a082cefb3fba978db46197b33be743c6adb89054dc7a12eb143584fd5b3dc828f4c38920e1d0cb5135fcff465454c2da2c377f6b5592ae05b453ea0d6fc0d88b3c834a8d20f846583576531cf6c5b1d9e7437a89be5711690b4271b5a9ae1428ba84ed35220e028710512755a62a6d311d8e44c1dd682520d4ab9686145e7e0b90f2429182e53e69f0194df823092fb38e7fe63dd12c4600d87805fa4cf0ecb5d3c46bf136285b6abe90d1ac45c27442cbb5eebef9af37e6742d762e9af0ff3c0b69561c1a46d89cdf54721417b1b71c7c189dcdf3d1d4d578a12f4c546842fa02352e1323fd7bcc47514d95075aa5a95f416a4bd3c7dbd50a31a94cf6bd2ae9d9052d1a85de9e4c7488962d2d584a63d5a29832a13679d7dd0dd8506a41178aceb78061e33f1f5dcb6c26641852bfa78cb339cd9f6b2a3852cf6b09df26420a6f524b3fdb405927f07f52ee6a53f0b15a3e22e6170a52f762ecca26cc5b601d8a98e30e70cde1fdf9141f9054bc892c4d77dac966764ca36c081a63d9bc4d8ce2000f1084f85b1747b33f331ef15e0921c19dfeb42213cff3cbe00bd7fdc005e5e9bb0384739e02cfeb6cbe3f85baea26e22f2f84c10f33149dab6eb92a206fc40c614f0527e144dc5b1f10be5baa08168af50594e54df352c9ead4a56a19ac23df3ca01250930c12b1d014f959c85c27e42672d0a248643c4d0abba0e78a0b9c878d632af1c9b2c9e8849991f3070c1e668943f1aa21d50bbf251a48a55d8adfe1c8d9673ff23d9adf3cd23677fb05e0fda74d04474ddd9e599ab2c8eb3c5dd532d6abd2844b1b63a6caa5ec424402a41eef307bda60f17d3c70a18ccc90368aa2ea8da8855b3329b0a5423a087df14841ec2267243e117ca2de1f4fe8a04473ce075d8785e8e20f9e454efc2d156066a84021a576f20d6d11b60c8f3370ecdc7f4dabb34c37466b0683648beae2c5531560219194408d30d30736f60b090eef28e8a140aff787df1dcbe864ba34ed4eb8ee80d040ca5957e4be2df581e11b43ba179b33c4ff8dd3988231986a26114c37b6a70ebba3a9d93561144eda4a315b6e7967b0ef0b9570a06fc5b70cff02bcd6cffb2aeb27f78b5f51229d7c6ab3d634e442da08cd053c1cdcfdbe02de42b797de10a5649af46174da86efd7a49522c8ce2b9bdaad7ea9c24b474277a0f24b974ad98dde4fe8a7927e4c8426dffb2ea90424f78489ef03ed218328c4aefc51784c233959016d39f073837974f483ff0610f3504b70ff00a009224befd3295e77a3ce3115e6a529faa77dc1db83653b93e00d9081d0f5b7f1bc82364d884800a1a5db6476bbf5e5a887410e64ee361b1ce7c184c6578c223744f65d76dc2ecdba19956203e26aa56a4ea84b30ea77376f934f3eebb229a1fcd65f48348e5004c97c90d74ef6ad3052f80dffaaa8e119b544fbb3b32f7e8a98c857af6a8b9aa5ca8b725706391ddc59be33eeb8cce40eed6722f8dc0330021e3519447db2b54ef29a95bfcc875a314532d9e63fd85f19ac965dcdb41d534996b686f72ea0d6a2850490aca872cad2b3015c120d391cee0816a2ba6c5f508242784ea2952f8289ea679033a57c0e0d7388f94b664c6d663a1e46c9439d749fd7e422958a4391688e87457c8ac6343d10ca31d977725e4bc9a7586cc9ea2c6c0124be816187932ae6645dd456e7a2908dca776ee3e417ac9cd2876e4b2af254dc46406a71f7367093519e74e4ac85bc7b55b076abe295b6871fd98c01c4dae15b4706394440f394a3b9bc28c9de224647486bc934da4ed68996559aabf6e5dcbc80c06b1384ffea6b3ffdb553cf67144fed0175f1c1912d42ac58f722a1cc2faa460889270fe7b66ea02522a3023450144ce722467ff66d57990a4ad8f147f11ee7903c2ae4da74f2e6686db11d964d7ec3f82935ec39bf4fe01ed9ec4faf9732032504186be65647b33d910ca51013299a2260f4df0170eb6c8ab43c88b606f4a6d147ee70ffebbabcc9477af1a1fbd7785a629b1433fba32d147952a7f180ab78fbaf157c48f5c67966681b406aa6e04871cbeb912ad58a3564c6e223ee28a101ab6870ae6b0aabb6dcb63492befdf42e54c2aaef7bd2d9cb9d0d80c9e6c0f93e6949e7693f7779c7451de532a08c3f948aecffbfb291dabe067594a35e86a4d47f02d6c362f761eb24d1906255dfa2125534efa24ee5d277ab108dc78cca592514e848ccadc37e0f3da533b7bbd48831cbdfd8c39ea71102cca2b03165624a00beeeaa15d65f207c0a6e458067ef2d7b7cc40d8de231c96dbcac3301dfc13e0fc6565b6df0585b433eb1baa7243e54ddae4426362bb993f1a827b49b6fef1cfe0068aa8673a42cd74a624c0b4b49e5c3806de25a5df94156561146278c8fe324900f610ae68541f7f5f704e1ab8756a22a504edfb7548cb715c552fc63a97b7694957cf8a412aaeb4a3d9e8ef55520b8c6d8dc233213d05ecb7249a0de6791f1a2bd646c63565efa7b70b0533499a88d6e0851ecd4368f99564ca106d2a9e19b47e6e1925ef541d16f9d98cb6723c2c49029e9e18993447176667109d616e98ae75f25786d06f4054c33f6f35da9f98bd5c346902c4540f696eaf574153625326aea5aa4c4d93bc56b2f4a430c710c11cf3270357395ef369e2923ec9e52a03cb17050e8200ae83e362486aa418164dbbc06257e90cd503518a46d89bba6df1b2c646f4d86e614beb67590aef5894e67b24fb3318dcdcdf4e01948fdcdbb91fe2f2b4d2b3c53e29f327cf4d23b1ad528f9e9d2684c33d5e3f397a965f76fcb00ddd05f59d8cf76ce9dca18d97e2b1677265f15235f47f426a8d0164564bbd9ec9f6bba92cdf097d3b546c9db28b82aa124a1ba7f8522568e6d1a0c61de26d89849cefd63962eb3bea8f9dafea7eb575e597b3136b260327da178c881e63e42c2d6b5b67b4728cc4ebc7401a069a951b5762a789c73ec8731d5a586736397f14568f4a76eca667cd544830606fe316f104ff51f4d2af6ec4eee3718e887b89b20fa5cd026adf38ccb67134e2d11e2c22a8e04dacc4efeb37c673818028d341d632f30b868e69dbeb91529b980e727cab57bc31b59436c3c1603520a8c01f10ee61da992359b39fa68c99b3abda07f55a9f0d9eb563c733f0304ca12dbeb05169f7de795b0276e062b1ce8d45c50d3c01c605ffabd8e5eee76068a93871977b9d5c19e51b40809ea21445b0a060c44315a8006c6f4099c35f9a762e6fbd05b26124a240c0efba1caec5e251a206102118effe14449a1fcbbb1641d4913e2393cb90dee9945ac230a27370b549a1297ebf1c31b46f66ad5e86f23d3952aabb2a3b6135328261e0fae6a53316b1d3e4624e5e2bae60ed678eb1ee2e2e3598369cd2928e1250576f601b04d89edc28650d5ebbf3e1c3c0f1bb0d24227103c53716d20e14bde0a09b344f9baf57c81705cf4b669a380415df86ce59a725f0ea6c8a093ce7128769ddf3ccf2e9c3fbc479681cd9434b8cc4caf13c413209972e2f63da88ffea630cb762c32d197d7677af6a219b18083091fde5ed578f0ab5c531e23b18a582f017c1611f14d794f9945508c61c660a549a29540f67666e5c9a2a6eba2a01b948058bf6f3d3ee5391439891ebf77ff07b64d1f4ce5a3ad9ecf040b7386e6e803abaa9d51eb6cd0b6339744ba9f1724c7713bdafcbdc920e976ed1ff45a5c9cb383838804035897315dd721a323447f128f75a0180b44cefd472ecf09322f7fce038452ec81b122299a07ec791d01b89a8c8cb9c256aecaa86cba89045affe3b1a298c048d73233ad62b99f1650a6a8bd2177cb3633a0983decab4d458e3683f22809754583fa5aa4d9934b4a85fd9efd09c93b7dcd9ffab57876426a9853f55bc3082203f47e3acb56f9eb88d5a51e101bea9486c1089da11fd18625aa8c110d2cac9f81158094f60e06c8de4ca6c5d35aa7132563f5c3c2f1c6dd73a31293b573682d0d01a789e2b88fbd48d68f55394ddde4d08226d81c1bf8d267c1994c903fdb49ddfcce2deae3e23931c474108d7a30876e7490b5b6fae4c1df6d597ff1c95ad1fa68798b81f5a1b321a1d977841bfb1e966aae6ea22a2f4b7914d2a7e2f5d44d0f2de05e716a9cf18f8e1684d02e8d424aab5abba8c6021650b1fc027f1dc3545bb4ea56bfd28d1d52a93b91755b90eacd03cbee55e56f7b2ff845db5626e2d3dad4df7d20cfcedf2172fec64ed9dcfda762b87032e400fffa664ee322a75f5aeb585eab29f22c873905e2f7dd9115bfc6962ff477254b04002a3ae2aa44638abdaa42359d05a6050b1ee64a0416c8d5607fd4287d18298f3bd91c5f3331d558308be86cb8fb596e2956b122bbd5f4be564f28164c12252e607f79f9f9f70562fb5511aaaf9bd954d19fae519a603ac97394b4776956a15c611e73b7c09461f2cba94f1e51b5d8700ea06727dbc037628dc9dcf32034c2f5bf1e033927d99942ddd66ee61efea6d41f08fb6b9c22c77a58cce75e3812e907b23dbca77288ee7c4ab60d6fe9cd76e76df913fa73893fc75059c3c793bfcaeba4940140af1c0290e95765826896938d2a57c2ce774a43e7d83342e18d782a69ef463fba22cd8c0823058eb2cacd0259a69c17628fcf5397e7501f152e0d09fc3c3fcbec8e66d06f2cf9716ead330a486627154d4f5e5ba4fdb5b8e60de76f00af28bc8a1a7c94a1269a742c9d18265e78996298d31d542cde63658be9e9b6e4c1188b9b824820bd1ecd8e213ba802b65d9357e48018ffefc80bb033fe29fa318a909423982694d4e059dd3ffa37b96a1a7567cb99457fcc62c07c6a8b4330a1612aadb45c4f59909199c1342828c7fa6e60e784f887361992481428e2f8b83559cf337e7290c6d5f9eabcdee2e8422e1e9ffebf647038a0116600918ab98d4a85aff98fe70f0201fff7dedc080cc9cf9178ed0876c3b0d3a352c0f1fb7dc540e26c6d243ef8ea0f7bc7605cb14c5e1a5ee0f752def8913a568535d821be0a6645227a16f5973aceb236d0bfd1e82c20cb92e8535fdc25737b53a12734a24f0b583b66a15c2863f94ad2f76c84c886130896f2c80ed2496701812d90fc76f672cf6fa98830fd95532d7eeae1c8811f1dd1e79746418b94c74f7eda824b77c1ac63129efacbca215cee7c6eee0cea294f35296666d8344a266660e24561945e9b9dcfabebcd09a95b8ee51d1107f833e10010761d4a17359ad5633ad8912c55ffcf78911c90933bb16da893f861ceb77948b7303a77d20896612a60d99b5bea75abd8febf3d1cc559682df214a9996d8660924c3557947fde5c3fc7e8879077c60f42c62050884217759b0ed814d02cdea0844a6cb24ac9f63999b4ecff755a7c6eaa1a7cf79125ccf2c282f92a1f6e9648bff0d790fdef1d6c9536737f27d8d78d4c2a72bf56a50af31623cdad97864b8e11010e14fb8b17e8588eaac9d8fc03cf7b5f4068f53901472234ca0f185c1298b8ae29e58704c874220c9e714237d282a6600618c0d8cb66b45cdfe1363f6c7fdd5d5f1244087c572a21fd0cb2256f0377fa02d869c578624611ab7a78b2ebc2101e4fdc1f1336213f6f53d3645ba89d50389dae85af176f477fd68b41bcb45a6c060e1b6072bbfbba29ce1d3f01366ff32dd96761dd63a81eb6ca89277eed3daa38249b8bfa4eb20f57209f49042dd46321710bf5e496a7e80f4e33e4c6c9771354463ec822adcf43034c4f84a0386ddace07e7ef35271c6a529932cfebe259645e71170e1807250ce4b16b004a2d9711e190d1df1e0fafb1980ea63508d1515d20ffdec220fbdabd2dd301b55bbffbd0ec18daba1c5be786d34c2fab28b6914e2edc58ea3f92df8b0c0a0544cf5303c321d281ffa08a1f11de72e73053249eec384f83e8699081c70465d43998af2aa2bc116e2fae346e9df07a0f8087521a9c1fab6c1614f370028019432a63927c0e95e197cf16c871611515cb66b67316a83e4f13a79181437b9cd792be893218a6e07fc1486afe712e52a96ab0dbed20a7e8bc4e05a44ef1f24379a67483d6a3f393c1aedcddb87813eac2d7abac0fc5fbbdd0039fb922644715cd7e1e988a02d9b5e405a01beacf404b73ec3033e6672a39c21d053b39143a5bd1fea2643c31c12803b1467d46cb4f44238d98aca0488e1d6551f7e2ce64c49a998f6a1e6e53a5ff84fe6c4244e2b6b5cb1d00c6ed6b5bec616966ffac232057b36dcd91d45b953505c25b2c9c21a8661ce7025696249170fb422f6338f02b3d5c1567c185c7e6a93f84a97820fdcc8b1a8b7b3314ec81a40945e2a4199fc6e05ed5512e1611e6b4ee56a2aa7841a4e5c4146527563b64ff137d1b988160abfa72616d15ea1b19d84b01c543dbf33123248b1ab52853aa50320194dd98a562367f77ff0f498db501af5d92073dbb1f2dce4e2916f56c731946c27e24a07cc90095a634c1937bb158bc4b96e8cd1f9fdae641cb6a466286a51a786664a474d66a99787aa07ad0ce5c2bcb5b5332402bd1f644b421810680e1c486f742791a86945af665b0ccda4957208d12409f2eee24c1a610a052ed90de4a857806ee109fe957626b043cecd0c9113508908062e9bb7cd561bb0dbdf4fd51db9f331df19e0143bc2ef27d2d5b557d55b8dd03e29338f7ea31a8f48168a9df040ea6332f916641b8993ddbb95ca20760d43c126bb7a18a8f9d4a44c763dc3b57bc8c4dfe209bb316165eaedbe24bc0b49e045138d1f7919f17ec9069e1583d9adfe91a450984b748082b0506aa8814627ee307770cb32ab3f5948c362e36fc7e4d13a6401cf70c49ed0755f216d936dc6f5e2e116d517853d2a8bd1c1c5e67ad165ae0db3f9babfe1c719a1b9058b1129d8ff031340810fda153b7335a947aaf88ba69815d697627f52490ba680f42f6e0b36c27378f81f30719087a0fbb2b664771639f2590c4ba48da4756c6897e4d1853944836e6c56b371f92c6e981b9a56eae88a2d444a3c0d51f87899c865267fb8740a818d1604ec66ba05d1afe73aaccfd370afc84abe882775c0887a3e12b683ec02ba2aa4189a88f970d6c4494f75c6a7c1df606703d4c12d82ecb46d591f1236ca9a0ccc8445131e9fc18815a29969707934511e69c7350372675587f0c471cd84a3f971d267d22a4598f6e51547456388f26720998e32edb4e55d5e97f0db4f40724debd2ee88b1a40381f9a05f982c02fd3afd8b35266fc5f16d603f25364311cc890f9d321d500c25e845dd39f6ca807df88e8a5523e3343171d892cf773b1d7b53ae063f85af4d359172544b0ed7f3d1e4357d706bd201b23a432c2888e22eda0ea232d94ceb3fa5a548c615b74f2e9c3b54512607fe87310522e7d1a1e84f3186314156df1410359a4be8317978848e9e880a2096da529735721b5451f344ef2702d3704674c1e65ba57cc68354bd369327ce8656216a025ac91309b360520c9f796a2e59764b34b09da81221d402b41a97ee4b076ad1449e089f72cb8d932762fd1464f56bb7d3342d3467f0ef0593a0ca969fee6d111c623396a8e571b6a69d7c67fc130956bc9816092665e7359351b28663241f767e16f314e467ec4f1d599d34cc577a800eca72b7cc4ac6719bfa489a4dcaa8155e696125381c50386302520b220b5ff251124194f04861353aed76c5b55394a860b91326fe202d3a83881d47fbdb0d334747d6cb4c538621c6655cfb3e0527821d9086936481842cec2689b2ebcd1184a8d985ef90cc480654c0dc51ab8a1ea7011e78e8a5cdf400938619cb2490a2cfba40f994cac67e882cf858dcbc6df7743058275d5517b8a7c37b06572786bc292dd9597f28961a6ebe9654dbd6f1d23a2bc202632b65e9c30074afe52f341b0b1fe85bf4162ab7fbab45436ee8e6cc4e25022f7ec346a59f9abcd9eb352a6384b49e4011bb7e2bc9da95a7734f0c60eab5fd69f97d9ac9e2fb868b24583584ad0ec26cd3895fd740019dd33e28dbd98983883663bc015394d549894461c0f84ac5c09835cfa2d5f8eb84e21caff1b1240a25bad5aa261d86c3a33f1fb7a5011c81c82652f6c5a956f238c5355fe89d419dc4c12a380894b63b136ad833e4b5f72cacaa1aef0542ad42e124659fc5c68bbf80a76ab206f8a8680768ad24270a348f59350e66d02ff0e3787fb694c151b3f5389ea35f5b751379828f5edc1673bdf38e82ff9eee06160fb9fe7bf69e2dc8f9c93d8d769db39896b560ce06cf328a9ddbc869a843832514cf0ded0f9e3fb6e519f32de31b826f196470ad9d814d64ae3045b97dcaf66fcbe422e264511aa15133dfb090211b54333c1268b8c0ae80aae298be084de6b154b4d8c8ddd37e07829650d715e68b8f7edea25e74c1960d78d2bd3c4577720e547d99fb3badbdb0afde4017e77adf7141ab295c0a9caa8b4fc04de85a51f4f87afc3dc3900b20bbb880fc07d6b91f3667dd6f24406597ecc56b46e40d170af7adbf890327097dd7f730e088fc6db925290f408444cace8bf6a2b7212d4c3c417943d539306e470cce8eff0f37ac7bc533567a1410d7f33e292c32bfafb1650638810ebb7a21e32f0b8d3200d828bc41f4babd857c098fdf0965153adcd482d033ee8cad9f5cf43318909f37e068d42e0c7bca12e3e3ec7c6eb5b4c7fecc4400d4e9bd451b1eee85ff876f0e9bed2e63db314536854e0d8aa40f627c35beb4891b474338c6b59a8e2ce1189acf3a66c832cf41d84405147da662bc2bbfee2399a139852f82f21386251f03f7ba3cfcd4f540a7349348ab08bc5008c32c7e467c2313e4be7336ca0586e6e9d83df17bfbbd8fc93bd4173b260b7a6d45a2e42a5a4421cccbd6fd373eb1affbce33b2407e9fc065b33b80a2d8f05f1234b0d961f4cc1bf57a54b1ba81a64ddbaa87baaf01944c80715f194f169157922420ca1001949621c97c3912b729960a32151c204f22629813fedb3457cffc0f709b345302dadd76ae504b90776ebb9711771a12efa27a1c686ba566fee0e1778bf8e012c9b24de47bb16376869a5666120b97f94568f984dbbadd77e6d4c47d3110b4f0f361f72b499c13cfdb4a58fa5279f1f10cf6f1b681eac1f0db20db780f1252b7d53948dbd31ca7cb3172343149d80a19d5794fde1cfca316f4a7cb652c09704bc732fb8283283a777b8f58a72d9ec7f9071a1cb17087e142995774d8f7c50545254f958b44441b8bf7a4086e11651b97daa22bc69ad58ccc87129f1fbbfd44d919c4d500a7e0c7d72615a05e64372e882f868cb9bdc36ff7750e001a918701ca6a032e7324077e6ee6bb861a810f3f83149896194288e85a2feff60247d0f418b1c8f6e4ae8e9a17f491b87e8585ea7dcea56fd8e2d56f14174208a2133890e866bce0ce51a95cbd3f942b0f0f64b656a2bd0b029bc285c4bb328c51ed56248113aa210b072b95c72387bf577fd9a51dcf7d51bbf3647aabbe1e1f27d665d46b774a1448bd14e361f97d454ca8029faf3e19bfaf8583dd814557a58c34c5bd7a4d88ffa5da8fa8c6a42c2dcecf50fd75494a7b89db50d8b586616613afdb3fb210d2099bbb2f4b0567c854e006893c087b175cd53f1f18e360a7dd86e665992f55a582aa4166a01b466e8b275be8b1dfe92ffb40352951e200c5980c49208d8fd3dd11d9e7c2e51ca20572f2016dffce41167774d7f30c56d6e3fd1c5bb0842c6033640af94f6e84aa4fce72f12ca91d93821aceb3940733d5a20c1e47a4b8f64cffe8b1e9acea674e80fe87bb7329211c12f55905396d3563f426b89247fc596706a88f24c5de4d2a647ff677d01688646368fe026f3c3e3872a407854335e17d5c49586aa24a48b13921290a8a2d4e5948c07952ab6291cdf2006d6910fb0001223860cc94546c795f4fed7308310a45eadfb2140c4142c10e5f03284522a450c20cfb607342a96618bac518356ae68e8c1a40d33e428218b618990de88a3d1cfa7d557c28997bd96439e09cf9e20a480bd5c3f6615308bb0694050a9ec74cc8898cb5bf62d11d63307c9f47ff0cd6a8ef4e562645d4384c954646e4345c405b2997c244217d7338a674b2cf20e9f3c824805cc81c034dc4cd6a5903308d9e8a0bc3b7086f60523f9841441411975d6bac46226c94619427d9bba40a6c77f8d00fccab10153d8d3e42cae7708cbdcad2c0bf1200b7f3c32a03cec1085029d6f6ef06d7a9d9277d7f00f664cfccb8a1350862f71ba91ee4dcc7535310c6daa9032650e3c8aeaa876183cb783834ed5fcacbac25b2289ed7f1c34b3f033ed7066e17a70ea37e9e79422c0a0b5b3b76295096653625c52e9d08238ca6d232075bcb4e25111115e63f74951281e30d10e8d4e7963cc4698d18c86229e09c8a2786108e700819a5be71e8db046074bedfb32c509b800830822cdeda4901ed9733da09added9930b55c1dd95a492f54714ec1d51413bf5df88fe338b7b82e94682dd0c0dee4e101a00fcafcdebaaa828cebc6793a1f12f4a582694262d436500e91147b179084da488c910652e5fc2ff1beb97349c58ac9b11fd00b0cc49eac84d75d6c626900cc6bc3baec012b98a021ab63d5cc45c9612a9d9f1fb3444750d673db24397c1b3483ecea85b03ddcb4e178fdcdcff94247578f7461ac0188ddcaf4ec8c5f6cbeaa1aee8e0891cc7f3ef252a96ea6d8be15e17b406fa01fbbfcc56757e26a2d4d7cb30144fda4ee92c28b8f82211e3884a036634c3373217255a0852b0f4d488be11d978630dd8e984cb30146087118657d0b2f092675ea2ed80098675b7b9fb7564b36fefe9a46661c8730cc497a1cc87221cc2780773f518c37b00e8391d09163b2abd2cab7d1207da88f1f820b001bf80a47869f6f70269d3e9fa922bb81cb7e112b0e46ed6d86ee6d107f469d3c4b600f96877f9a4cf2cbb88941d1a1cd575f48f31593325fc4f7e046a20421c4ff4d420396fc5dfb37464b03a4f2cd031eabf99f918a974dc789f4e8cf8114af9d9e4b2e518cbf49c4a56a4f34b8dbe882d3b149f7b0511ce08d174025fba3cb80b0b837f93a807f4cfe24214ac67779afae511bec9b0b084a5a0948e2fa2b687c95f63af2d1d094ccf4d412dba587d3b047a70a3fee73689ab8de38516f04bfbba343b3a6fb3540a9ba6b87f1b78b01babbc259bc786e0c60537ef5604bb1b5fd58fe7087a4a8ba38ea9d21a6860caa69b771711f51da20f8668ac104d13a2aa41b45b1075241035d387a2124c1ac6ad3b7f025bbb763cfde46dd9adbc9f84c4d0a05a3e84d496cd19a422ada88aac164891551deb45927055c77fd4a6f89ff10b44ff849f15b51d8bd5b9d0731da991fe109c6fed1457ab2092ea95850bf8aff8b9b3c1f4930001c20bfcdfa2441cb822fb27b1248373e4edf150f95a4866d8b9eaea30d3a24fca8c0dceab57e59a68fdcc9d1255175014c480224414a4fd71e9ff597e6bf8f6f7848481e072870dde19021b0c65fc68a912008520df8f548abd7cf1703c9597e7e81d665a9bdbab524872b9f3b0a4ebb48cc1053fbc196c9a8ff4a6d832925549fc87f1675c3654783ec24597f4a7febfee2d0b5131f150a615a84841a55754e8a9dd5093e239b57174b4d34365ce2e45ec4fa2ae048ff18953053e53a452dfb64e7121c162b613bf6dc78a64b08c069deda44eddc2de03c3684ec9b65b65a60bd5d4869055702477365ba7bfe58b4584c45ce20c4d9b3d843621ddd9a57a50a80e145b48108a7554f14569acc7787b3b0f09a6de412f3b3a1a1aba4a3b724205e49d0f5a350cc70646489930b141bd50478ccc532c98da4b4de0fe04176a2fc22b2224e8c9087abd4f9ee92123ce9f6d704d8737caf38ba80fff734dbb0bb84ddac5e3f1c607ca7b2f945207b556929d3374102c10ea3b7b836624400785034ab5c0ce246bf255140e894852a48b05e426978e303eaf2b9cf9be912b4736768be010ce5608b795de5820bde03dd18a6c2859ec280bbd776c746b4fdfbf187807f63d5e59a2ad31d710852e06de1f5c86f190e535c41c1d3ca380beeb9605f4deede5e7acaf73534268751b257107d654f17a874a45391b0d7958cd0bce32316a197561484aba4972b3c3333670b7a3e099456d80647d30d442d2ef2236b030abae0340368b0c6fdfd03f02da12ecbfc5d59d96d8059695d48a652d5fe6f91e97a99185a7568f6a875c841d30bedbf96187c1aca5010948862e35fbafebc68c3ad6945264c4064039239c8ad5a8e369d98aefe8462d0f22fb66ff68fb3039c38543354be090a2279230683bd818350b4eecb6fe1491251d39ca34a6a40f76d3f01ee48e5f610723d4c5d9e03474c12123a7726195581e9ce178c6ca7dd4b8662190363ffbee42a8ca3a33c8583efb5768ac35934b316501f80e0abf90c1f1f47b0879f4aedea069dfa1c37cb4d14b226397ceaf8f6a5cc8208f5ae366523e44a737d1b5e6d5c661754728d19deb88b4261d766aa1ea4be586a534ad75533a9ef18455133366b3a0018281d609b30d239c9ae4846f802c4c69def705ee7de69e1bb05628f561223f236954169e035d8fb405dc16db9af00681ed7996b5301fee6ccb6df431b4471e8c08fe2c3af5fc237eb36d8e7091a0215894d755026f0080d62e122457240ceace1ba55bf068d87866815747bdec2f79a848cb3203575b11b2af1814da43582bc0166d9850fe462315c487fcf37ad47a4834df06b481b264192c6555aa44f2b06594ea6acc2b195e11bfb8637ce3aa0d83ce06a8d43f3911220f9d99b3399c9161bd5f0a22ebabdabdc408c6f7a7c2ea2c50f61e41b53f40953f47cb5980052b1808120616cb658995e1c1fa8f864c14ecf5aa2bdab3e1b83f8b92a4c8a87a7174a5aa4899377fb13636e03b2902aa518259b19b63202ca95875c0b1fa7f102e04a63b5340c564a77206fc1d57eda7166cc2aea422f414fcf5b6aa1b111b4af3880effb65d1f8ad07c8771b555804a755305a406973324fd1c8959cb268755aa7dfb42567c2ffb55e43eeddd141304785415e4892e05749f3ef8e77e5e42b699ce5beba7efc90e56f5e32f0ad18390082076f40b81aa1dbf5354e4f0c864eb53d30fe0752bcea29edb6d0d4271455967b17c0b75f0f6f3a16c1295515177a09c9255ba7e626da4c494f576e4117e59b213349ac5e75e55a2593557b290819b299ee7b0bc8c5a9236f1c00d11abba15f1ddd3cc9384c853a1c6ae1aad66a7b530ce331846882a39f2b56abc21471b2690aeadc7fc318e56a3edb4ed62e2fea37aa5cdf09d3a2b11b9634248d7657a80d008bcb750d7fe93a2af996d07558280e447ec2607fd390d1a5e37e0d60e25deeb11ca70a0e36eb09a90a55fca88889a486e55c0a497cc87314dc2f5957ae0d9b149c487949e3491a8a31e1257662929466a1a348b4c898f16dc4c6db96457c3649aab40603cada5c5bfd462c0f41358407cf0ac1178bbef13a0a3cfd06cec029646f9e9167d1a52adf4b1b931ce4e24d3b8013b762219491e1c43e4fd6765039aae85547515552145e08cce908ad11316e0f3cb5531f1c956481aa0788253444c941dae934a4e0bd57ff87ea81fe15e50c376d1a824f62a2f6f92cf4fdefc0649b528a762ffb601625c0de182705b3e4f70932a6ea370aafa789cc255f0fd1c0162a81b0fa469d6750ff1e3e14f2b01ad452557abd9a3fbde14c80414a6399d1b1c7a3b24d09cbc732ae055545993ed9828d411b54ecdd7c748d442ff3646a2a6cabc6de77c4a24b33118a5cd6f3cecb986b67c384bda86917150fc750a6b999f30a7486980bb4886fb4660fe4e6b1d5e7baf22552883d4c341a66dc99fbf5981f009efd8603390e0f87041d643161c6ee53e5f1636f47f509a8e2cde23a8ebc3594ef83302db7d01e55a6c37110193eee0a18e82803076134483a650bdd5ad84850f97e7f1c663e3545e9c76086d2baf74f9708432d2a63a5c1abb9ac8c0e409472c5ca7f4fd4874ac1cd38ddf497a10dd813e5b176c8b316f88c5977013c281d762858233ddda1b5605d15052c1c275ae1551fc29bd88516d39b645783600825749bc8b44c015fd270b00161515e2bddc7678b9f7bd1d412ddfb3da7dd67dd8c721932fb570ad9e64dc8d42d64e43982b7b701680988a364d6b178ae97d9fb868570dca03f57010091324737c770f7b669f5c83bd22555eb4ba116ed8b83797530e91ac32cfe9031a4b147cd790582ccfdad617a106d95c1c3cc40fbf34d65ec8824d6a92c375ce17142c776fc79e2c6d3f14ca3a607343e3c08b2a53b12fb067eb871441704b6be7a1ba3b696350b359cbffb467d30bf124a0573b8b9ea6beb779f336eb4f23eb80273d586fec40fd493586b3a38c05211d2670094ecaa524a63f297226021135e5e37e7d4d7bd5a8321ded2f20b744b90e70b91fabda0bcdc41f9e319199486d43d1fb129ddef31fcee9df1251b3b8b672040125b9bb07d3dbd9ad4460787d13dc441b68b7f19e02f5ff33278f290968ef5bdd4ed2ce15e7555871f0e5bd0b1e4701da2bcfaa5e232a79e413e39ebfd72dcb0ca16e63300b03651a3ae8d310d8d2c2f84741231cd06223133ef63829d635ffc0d7192dd1d02ddd8aca9700ffa4eefc8095309ff70a99b56207d3af92b16f2715188a7b76de97866c50ac6f2777c2caffd3f0c27696bbb84aac133168af30aba3dc02653b0b919d33045d3a82bd297635588f355c06203497dbacb9c9807c6f853f2b25d5341350e735f7b04fc8f527afd02ac2398b8a5ebd4e2f841306947c81fec813e8a996a76e25db1e9a2177cd1760c9f1d260b94e1cff4b9263e19fb270b8110ba4a2142b84bcfd1787feb8d1a64b09ccc1b349367e8a6d0683c37ce03a4d53793a6b0265c985b2f985cd2e2cb1867159313b39f0578723e24d05fa76cf3b667fca02d564d7b6a523b2629fa60efdd123d6083e26b9b8436e148206b38dc5b073a3938a0e2484fc7a18387fff39e6e3c3c9057e2ec1db752ef8d6b79e3630260f06938292cad1ea0de4a917643275adbebe55c2ea5789bb518a083fde44c7da315d9efd631847205489e6dc5a408181ddd2ad635ab7961e1c44c16e73946aaf2068b0cfa964ccb5d57b4e7b9b82171a97d5ab4c76c72d08fa6bc1f4a5c708f6e959e2d0b021832adde41fda4fc38831c67a38123e4c1b25aa5ce2f6e041d1c5fed284eca3df3e06669d5eb7608645b6e0e139e88df346b3406d5597de54326738a69a9d7ee55303f747bed7e0a771667d05bbc1964d841d7a68dc7537a43985984a5eca084cda8da30147e9259909ec6c35371542b4d30c8e47bf3073af6c955febb3c10552680c90e41700ca4b1976a891b93da5d6dab3bc779d60dd6a9e4e17745fb9d0f83c01d0ceafcee845a3c973bc61175221c8154cba27b240f1947dd769727a18546b3a9001598a9cae0ba46c1f59c10bc911f07a2e0e376821045bcc81032de6d1b9bf9a6eaf4357f41a234b1863eec95f8dde6a83a6d93781ce68eb090e11c165556bfd4a9a22f85ef6af2b095a5a3ec69a25cabc66f034d3edbf46d36a43b6a48a50cca95493b895b0e2c568b5ffa2de47772cdca7017e76d44de6847ad23eacd7f9f1cda940b11dcb0ed41720d6bba90b7a5cd9cad538d66c035c460cb01b5ca479a29e46dbb649ed77956d94bd42294fb3178ad89a0f9d1e2f0e82fa0dc34bdf154521eefbf9548f95838d2a2a3dfee0ac5792ca772607f5a71b10985caed28cf5bba81607b168fb45a1b3d065803068020ed72b5f6bc29c89776b2f2a5f67c30dbfc67d7752d83e545a6fe8f4301857072febc3e83243a52d1d1110965888f5a97029606393fe6e3e5701fe3283f03eb8a6a6b779235b1feb4bd434b7e475c4b387550dbcb1e4e0f0f1609d8b4e95d280b54c2613ef1f9af517e56fd275258dd4e4e4022a3bae7b58300eb1930ed0a49c6f4f78e275e1c751237bb445d41edbabd3f81cdbb7c9ed3a1a9da72378344c1ccf5bac42ad94f92f9db95f9c90d199903d8a401d065a81a051cfdb032384ecd57e157620fb37217b6fb2a594524a29652708df06f406df8ffa4eea3a4b6625fb4cdbed6b690b118a2fdaea8724ede047d29ebb36c3c5c8c4ee67cb32f395261723131bc1863a5c3ab36599998181799bf4fcb9a82a26d6f56fb1aedfd469eafb6de60e6aeaf973489d2f9b413f48ea5c89ca45ed5cd4ad62dd6686a8629dbfb6953fdf833cec9f4e53d73a4bb9a8fa00f1c3ce497d5c977b49006cc09a3d74006502281a0943a8271c4f880dd12684732814197c9c3fa109c09317dc5395db9393f00909f814e4c946ee6928a7c79907ddf3f410eb5fa7670f2d3a9c0f700423b53c94ea646d402fdee08ebaf16a387b58f590ea268f0c9e1dc1783aade9af6103075caa47536804ac9de22cca840494a9c3de412c62e0207b9f596bd377ce67770892c2055d15a8b131518a3c8555f9d0375d58575438ac9284f96cfe438a970ecdf240644d08a7b266a5b383d4a0b1d3d3090cafc2b51318b7fafbfd6da72cd989cae63addf3e0747a3a95d0cd0da715dbc170b2da0d719a1a47ddeb3a3d9d927a19bfd7e6bdf7d6299c50e834b909f9b5e3a8fb2631dde43975184077a1698b18ea3c7c93930c400fba26accf7641a7d335f9d0f9744d38427e41a7fba8261a3a5d530a1c2636384c67385359661ad34c609b490b7e9e93c98ac949cb7761c7568225bf19aa0344f05005d8e701c107f2a1cfe37dcfe3f1cc0bbdf252179a394deaa0c6b063acd3a52e6723f1e5817b4d3da61948404e389b540833f3d9517dbf0e0a56e3c251b4233ed06c0c41b570c667eb4e4fa6192b6eafdad67a71582b7ef3823d515ebeebce2169e6eeaef6eead9f9a39ec17d4d5d45de8f6e9c954a25bd1830f2ae5c93d9361a7e2077a86183d43e8c0e22660f4e440081c74a067d740076e081cd06dd840d7e9c9834cbf9d9e3cc47453d75f74d96d61e1fde0c2a1d5b381046cc82e0d146f49474f69c6ee8a4711ef0b8f133c156f762da4d9e918c3cb22d64bd2b20b3345c8096f0d8bc7a3488e28193bae24ed6cec706164470a9e531a95353ba932bb226349507873f2509c20a6849d932a33523c164c6a6ceca02cbde0f9581ae2495943d16637e54caf04190f0b6cb7e6d5bb11a54656132bde50105e96130ea55e508cdd093a589c3a84749cf168a84015a667820b6fec0acf6beb8b094c8eec664cedaa9e5ce49892c183918388c72487895e8b168a56e06d29ebc1f0d23b9165d7a34b4a131e95243b30223d1d3e78464bbc201abd18388c88e040c153016dc746cd0f3231bef8f8e23da1b233e1c9ce49092f8d4aed471b1e3cad1a3b2aa5a39d92920a1e11b5de0a36bb2566b480e90d6dd925559101a59763096f8711254076534dbcb01b3d2318426eacd831dd08da7db1c02b018d90147631c27843acb891528609efc5d5ae8a101a3d7860705841da21d968d164a3441a373b34b32b313c13ba98c0b2c3e24ad212437263081ad4938eaf196b6a14ad50e304afcc1b5e94344ac6ec9e889169e19199b23b73b20382d5ab518428aac98e9e50d28e2417504929765c69bb37e3b7867765fcca9c00460cec84959d972dde0cadad24bbb222587ef0cc98784437dcc4d8b140a385160d13476f7666d4f090cad800d35301e6c4ca2e852dde192da924bb1a45784d3f5630ed6edc001283c68c163c2e334cf464bcd985a9d98d95e1b500c3b302b6eb62a537638be7450b4b925d9422477ef0ba984ab8b1e31223888c164c324c2079c36b41cd0e4d192f305dc0ccac406ded92681d49c22b518467e4c74e0cd36ee9066f4c0c2d482d7659904cf08abce921a9d94595e1918149016c775ae1596df17268f55c2451a1c8eecc0f304c3d6ef0881126468b9d550c13bca237bd146a765d659ec020017b6165cad62e4d8b5725490f4711147eecb68c9c69a766e437d020f558c0285282918285b5aab33f5ba19bcf864c37e9c98302a69bbf537b11d64dfe2faef05ef2bed7acfa620a687240e555f30bba6a7b72ae978f3997f9e2de7e9ba1e22190f228d6efdb29c9adcd38ccb9bcf052c210dd7e257b49463344aee750a863aed70373adbfb4996626a54ef3bdf7b36b591c36308072e059fc800d1d3660f282cd5037e0e06ee860cfa32a02e037c898b9b2460c654766fc3a3d8f967493b75b61acea1d6c0f255d8c3b9d9e47368e8ebaa94b6314251ae11001d0e969a40297d155de6914c4cb10bcbef756237230e3fe02b5ce83aec84b675dd193105fd0e9744548c25b1424bca0d315f1d015c1182fd02c9dee798a86bab9013c93efb579efbdf96ecd883b3d8b4e786873136f1930fc6203bf3e5b9b51ecf424da2a1b6d9d9e444cfa6ecc98b1a4463d8e3bc03c5a7d8483471ed7a83b3d8958dc934868e7c6b16b36610734cb83e5a91cc6bdcdface7defbdcbdcc8a3cb8d1778e3a87bb1d3b3c5503779c2314e63ce165cc29e909c4ecf165bb4c7d441ebb1adae773e5aeb9e06b3ba17846f087317c04ecf1659dde465319da3feb2da929a8bde73e29c73de9231777ab648d27b47543c14c6dde9d90288d6f59a6c9d9e2d98faadd3b3858ceb0674b1e4e22b4556076f0aa90ee230858e0ee6f0923a29883aa8430d7a4fb7e4ce2de989e2a9d31345960e6e17432e907878af4e23b17ebf333dfcd24130bc640e950e86e12573493a285ed236a483b7d092388abb89dd92f4146aea40b6dea05c0e631e1aaf07a407a4e3de8f1e905388a807d583021a5242172540a014217b0154c28a0a46424e682554387d80fec871788088509218c48290d71b6ac64c8842a1594f847a4142aed6aa8f0204c5104242cb24110aca71c34b2883202d56f509163a86eb4c6d7212722354bf3420a885100efa4414d118f2c5cc93929b2634390e2083820a536daaa03c09a26dc06a0936140c88d047a970a16b5410cac05930e44bcd8b133b663431a1e1a8968f31586a48f150caf242031ed8fe7b910443e83aaf4060b5564033d56c8d083927a535b6b6dcf73bba19f8c74b3344985fa7b4274a4b64794a33b44737dba37ebfc7850529de2717551950499419df8c7b5c46cc71505835b3c67e05fe762bf5d56d2af7fdfe3eadf7edf737eaf0cddb0fce8faf45907fb7dbed96bbcab9ddbe8f3f0e772b45e4de83cdc97d0e97f35f99a379b8cf707ee3347f31d46737c3cdd11b97fb74de9ee9f071244df16b09eab00716d6db32fe889f7bc5d92fde3eeb069e576c4f687baa287e187e1d4b13f755875931db03da325ba9f8e9f03d58911471d7c2927bb8a0e61caa57a34ecfa9a41ef20d44101a6c035dcbdc94e5a9757be88002b95ed868eaf206d1db3286ddacddaca588bbb6f6b667dfb76d387face9a63debd7c461fd043d7fa0d91e31075535c0af23e2aeadd934fef46b5d9af4c4a6ce54085797664ec5bd1842d611d0e939e48ada9e6b73b6d95a10cc3b679bdf8223ac542d2d500fdf8ee3385ee1c6f176037f84edb752c4eda34942ffb97df8a3b5a539c2de1ebce298802be3edc1b71527e2ea78fbf04d9da95e1fa7c3c592fffd102ce9589abd6ea9a9216dab903321a790acbe804e4f2144b61cfbb524a8ce12b524725756c4f500a8f3aff8ca524de5a4fa1de1c1528a7353a5888b7bfa14a71f87d369dce3fe8e8ffbf0adf8c10f06c1d20c41307c50e7f7df705e7c1d51fc1cb234c712771343b00c01d473218ee54441d0825cb967df0f567c7f84edb5493f40a76710a520449d019d9e40beca7be9adcfe9bd5404079d3fbd7f39ade5882c7ee06f8a7fc56e86fd3cfc8ff312f7b79df37b1cc707714a5347aa8fa5c94bbacb2ba3586e6d86284d9d262b7e6002fdd84af3e2ffbefee5e54f7d1184fa6169d6c7e53db58eeff272ce96a87efab348fccb5cc7c94fa7b346d6c81a594c54ed1198bf6c916c566b9bd68dafc9119c20c4152560796cb6d0668dea0370dee6e48cb6f7602be6b7fb18677bc4c370c812514d85562a7c4b145a281be70f658da0a47ace4f22041aea217467f5c81a850085113fc59ce2280faf256189acd2b665226e917468e36fda2370d4f6a8dbd78de54f7d5bfe90834e6bdb575dad25d2efe18e648572234d5a6687749703dde2577a396ca82528d65a45db8be27bb0a048f29d7b3e31c2dd64eed668002290c8bf473061d0b7ae47150906f823258037e09e7f00211902cb9325901f675b698dacd1833bfcac11077fb62ef3bdad11287ee0f6686b6b5435c8d46c47459eeadf1252b7d50e7d2896c10d967df9c1ccbf4a454ddb5726e0ca781fbc5f936e38168ed9a170cc4e2911916a4836147e60186aadb788b437894f7c057f81a0266de1d8b658386ff27b6579be4f87649fcd668742b1a1133631c8f6a1982d44fbf4195a38a64331a86bdbeb3c09dbeb7d168e612e2da3c5b23d59b689c51a1fcb168a856676e8fb50ec0bcb6c64e1d80ec97afdfdbbdcfab74e979377dedb7eb5b7866255036d75c81cd2cce19026eee6bdc25cf88a1d02ffa89bf70a141f046ff5cd3bd46b098eba3a8663550310e4fa8ab5d692837bcd91589afa4c0412231075fda07eaeb95985da6bea514582415355022d5636c7fce1587d539f75ad3bebfabf8cd6f59b16abeb11d80ce1d89a9182391c23ea14ad8366088a6689bae65cd7c1f2875b6b0ba1b343e1d8f785647c876319570de0c4b64b1384a16ad3b67a5459d871645159d0cae22f58c5aa5f896cb5e7c7fce8133fa8ff997b6361f1c32eaf2679349438a4ce66f80fa771e54ffdbeeffbf67f5ffdfd8196ccbd92b4739b7525da38fb4d59a9af434db88d9b6c0f8b5c9ba07a709fbf03b3831e22ec8f653479cf1deb5e491306bd3eb624ee1ca4a02daca439f66b49985a2775d3e6affe3647e8affca99f78015c69dafe7d1e11be15f36fb1fca9fffdcefbb1e580f3bb6e5bbfe66d9fac435583fc5cfc60c43d586bfd3eac1ff81e6c254ddc41f16f6fbf0741523f6823b9ba48e6ce49dac33cc2cddd83cdba67cad581cd901f477ab03c23d0af2e923ae4eaf96180b3ba48e21e9266870d7a66f1fac11d82647e5bdebb047f780842674dc4b7ce3a572790a0534aacf3747a4a5df5b3320157c66ac64ccd39ffb6e5a67252dd96b37dbb1370c5bc9ff75ffbd5ec863a4d51b9289da55c54d5e096f47e15d3f8a1d437747a4695e9611df7d3c7573f819c408efa299593da4b3d3ff8b9a9b236417de207b6d6a89c14cf855d24cb937f933bc9f2e4b7e456b23cd9f6d83e043549f55fdc8395cc17e741323765289b215bbbedef9c94e65f495b9a78846d4fcb0525d7a4dbd2dc7fab58ae897d9d6b02a56bb0d411d321b31c7242335597fbc8ca887968ede311f390ffb524cc1cd4beda2208b7df07499d25db531ffc1c547d807ed0cc4d5511b7ff6cf1025c9b392911cc49f5fada8678b1fcf66bce8be6addaaffad6cf41550df473f103fb3c3fb6f2f387fc58fc704511b5efdeb7edbd8317871f69bb48d6b58e6d4f589a240875fe35a8734e1db003beedb4d7ffbecf24a1db9e2a20f7510588540140b7aadf74e89cf70e185078c08a6260d1a1a7a88d9416d0f87852834ae162724a21480b2b68f0235a67b6b349091c0c2b00d0e9e984ac879d9e4e5a10c250024309ce3b9c75c050a2627c6f8c5a7f6a01bafd167cdf1f81cdf90e0805e8f60bd0adcd1850ac8df44d0d9d8707438b1d1010e8f65b40b3ba7ebad6f18f7efb2248087302a00ed81b02dd3e043e10c1c397cd6942d96902965af9158613f79e5a41dcea4f13cebac9eb3e72cef8b1e5b1f7ed150e4633d06ee27a66c24533dcafbc973606008380828e1f571c06a0e3922be9a19d4cb2b6d6447b3f15f5de9c7f449cf3a3bd59ec21cb8228a3d9211128c618e74aed91d65a5ba49c31ada51dcad6c81ed9a34aa140adb1b6bd7ecddff67aff60ce3109e67bade52248f4abede5f6ef8fed963cb495a60de47f2fa0ffc7f6f8f5bdfcade8817efc3a8c1fe3b8edd1997bdf6bda7efbddcfff7ab85b8320d1dfd4bfeff333fa7f7459fedc9256ea03aa939d9e52278e40f56b66c9da2958e6a42c8f051f2c31d9990e50e764ddb4416896e7accb68eaf47b866d140441b0dc09ff07fcfc21f8f94130b4e408dc79b75f2e0a676c56d5fbe240cfcb5871cdd2bbc24933e41f72d0c671c7173b01b3923a88a3b8dbb36a0963228cf1d16571311e62713111516693bf67366217d4de63339a3c3cdb8a4b36a9e7359aba269bd4f3db260b65a52ce5b0499e9e5feb34e940e948d99e6d7bdaf7b729efe7f6b6cf469a40e0388ee3f87bfcfd63b9bf07eab7c791e6882e9abc6b114702f51b59c5ba4886087ba8496cfbb4d6764ae75812d42b964e4f25501dc4ddefb5309abc6ce44fc50f1d5818450e548b7bf0d67befbd386fde3e7cfcf48633fe487270257e10c7311c4b7c479c7114c9d1f2881f70469c6bf994c177b81c322839d7e9f1c7fc7d97836e1f972978c3affc480d55340720e23323eeb8ffdc92c7f2dcff117b63cb337e051fac97da1eb4220816e7c3cf8561b80302fedbe3d2cc7933f73a7fc33732477690f3e57c395057380fe250e746c2ed756e25c6e2071d92f69cdbed77c2fdb7f0430f38a40428b07433d8bfbdcee3e0c7b90f6d4fcee35c79b318e394d4f2dc428c83737b1c9c07713a609c0fc9d0f2e03c1541e0e0fcfd101a7aeeadf821875382c8fdf638f2edd2e530da0cf875ca91a45f48e68cbf83ffbe7e1c728f15a87d0744def4438fcd6876e836c4e5a0c16ae8f6791eb5f8e186f33ffaebe37cc579f181bad8887e356e2481fa25739834759a4c12966e24269f32f89ac163f143586eb01c32e850bf0cc8de9ad1e43b9de31dfcbc07440e7b05286079e8ef80c839cb4343a00aa054c7068088d65a8fb0fc04dddca97b9722581e1e42d76958d6af3766d47487be8d7edea12588b38ebbc6222d7744b0bd36a339a29b3bfbc7b07fe3984500c36f9720720fc35e9be85e96d1dc0005abd7b35ef90aab53b8cf54876f87f687a1f861f8a3edb17df839db73fbf0c30ac5c5cf9fd33d075c070f1d589efcfafb2a7eb08103cbc381e5c939141b4dfd22b93f0c4baac1b7d96c1b36e8f07d1c7401050772db437b2a8c1ee037b1e5a17544afa5b9533f8486ae69f8b5ee80a0df667b2a8acfc5e764ed4a923acdeaf4c4faea31e8243a09fa869904d225fe20a2525dee549e8f5a1e12fa4e2d41e45ebf82bc34f7d8414b5afe37522431873124c79c8dd4550d747307440ea1e179b486febdfe48de2333d21dfe9b7fb69cfe8e2d41e41ddb6f07b2d52da587c6da8ba6b7c2b89f769347b5d63a1c73ba0b7f31c618e452c0e7a948a47a7efb0348c096b1be696f2e973af1432dc34b86bdb3d10c3bbe607b7e9ef758dfdc67ad2bce59dc83afed5abf49e6c6abd176dacdb0e3de82da47d84ed7d6a80322d041a8413b20e0a06e2f40b37858276dcae993866d31e6395bacc107c8cdf9e9cebd255563ffb492eaf43462d613d0e96915d4d7d6d6d2fa7d0ff6924f22d81efb574756663dc42ea0fbfdc71dcca2cd56a715560779b8932fefb72441e703448b1ee8cf3de70e62073fed2f30017ea08e4b11c62ee0d715cb432bfdb157a83a747b2541c33a60351082b9a1d36ae906cb437fec96c41dc491a6864ee9074a73dd8a1f407aabc5f8567bdb01a15b72ec58fc90b3979991a1d348503dabbcd0b1a2c83aabb674939f5553ddc4f4acca72569d5443544121ed6298491ded350cc3fa62687b20bdc7c6fb5be3ffc8dcf86534791d63dbe3eff104a828815a04ab9b21f6a12711a95e3be7e204e8e36a71aef8def68354577a6f360d05da7b71df57efa7759aba997f721922ecf739ce72f8077116e32858ee7cf0fd4fd5ff8da824d7212781fa26312e2f794f08c9401ade0a8a37923475ddf62178233fa0fd163e152d0082fdf6f86fa569fbfa197cad7f5731fb2fd81e7e002e19a0ce2d573d009d9e5bba7aeef4dc02a59b1b784828b031c6e4871aa92e6328400323b244cc912d1f3ef569adb808d6f52ac3e10418b363c84bca099622663940337404117a223475444b6badb5d65a6f2da3e6dc266333fd4293716918402fd2d8203212c5c9d80a15cc2644be38d94881a4cbb7e4f56deea5a6b862985c14d921036d0b061334229797355d4ea89850061b49220bcba1630b0afc4c1520392ad861460b8a8bd50c214a55c34dd056d40e13693546d79af866cb4cd593d5d4112edd84cd0a26299a04c1325365015b583c994ac14385151baa52d84a3b53060b57996f8c172a37d25270a13443ed015ed092babea471b2844c0e06a2148da10975a521a5651753bc9438530ba384a862798585a413119831466c8acad444d0a30a0b191e6325ae8cc97182d24a3aab72534299a55e37ce2c1c0751bb2ef0cd971b65f2cae9c582d18baa1a35c90a8a30499415283465c94aa2f60597bf88ed6c7b20bd104a1bb2854b596e05434aa274ed58b1e34ac554575e58fd2073f1a5a69af07213168de158c3630a0616aa355ca4450a4b5294aac4daf8e42f3d7b9d618634aaa51a44be1011a989c9c009119a3552544091e493956ce9c1246bca491a8fa2acb5e6393095c69acd354dcf5e2d7a9ed226afd0d48bc55658d1951869def03055aae4f830fba296020aa11e486a1aaf669ed3ae951738b5bc4298ae135f2eaae8909142ce8d690516b4583da93a31d79aebb14a75da5780285c56c2502d712293e2631346ca9616636c0ca9505b0015b31b5ac595423bc40c51120665c8279740ab070aa5302d98cdf0c9643de79c73de4517ca45068482f8d0e2d10687189829526e6e5450e1d16346571a306af43b4332d4c4048d1e676339630b398c9c29bdf9ea4aa36976cdc9d2967b47adb5d6373752204d474b522421d391e4a351f83aaa9183c597133efa4bd75a6bb1aeb5de6a7c701d1173034709240a9490a4a386d1991f41720c5c05091a2154e78ed30b8616aac2a0e032c6c462ea9e5e4ce05a4f97aa32497af460d3a60b5d53a24b870a1a5f5a582c58ce39679ed1546c012f2f90d0a0cad440810b062e516252b0c1640d47925a0853be74947961444a140fab7223678c968e2443c8a26ca441b58932c348888e28ab16539680d6880eac1658a2b6c4b295289be5602aecb53ae34acc16193f968831b345e47144a9aac886092a646b560a0a38594b694ac0b0f062460c04529630fd40e2a116c401325f4e6872da08e3f4d59546c4a21665284b8f18595cd46cde72b393a57b94c1ad582b9820a4840d1592215c0b95233bfa0506510e2d132790269a736526b019e384085856962a4b72706b8ed078a15a43c5844b898971a4028dcc0aea024792d4b04af155a607962ea72e287e25c5ee2ac2014242f646c51ab3244f547cf21315634acad0aec08eae96506e73b584729babc5a5765d549ef60129252de90126869033a84e75797d21943143a40525355268ad4063e82a49c78d264a56a884e54418d232e165e8858e3535baa3ea0429566a8a988a6a781d8140563ea2b0005172b5856c2d7b60e5491a2f574796a4d5a7b3e9b656e2cb0a5e5098a468a9351b59eb12c245480889d789e6a360869230b910933285a9e2621835ed006264c4c79a101955602898842c9da83225c69d32853b89367dc246c912941d519c7015518dd981163426660886a562ba80f93ab1634b8c2854aaac6022260b852b53d430517135d80272d642952729b0e0e2d524d44b59d2a4904089fa92b32485f3451b2b542ef81420196145ba4c594384090b4d428e2821f981c40945574ed99694122c26aa8871b1a67717eaa522b7e06a2a0d911b2f243a3897d71130cb122eb1356ab450a1d1a35ca698697a66c65497549b2b2062b4ce5a6bad75ce187bc0cb271ba623ceed32ebfde001c2465c8eb548d75d60fc1674a5036ccd8c152b3e231851117346450ac5988f2e73231ebf2d976f7581d96b1a6319e93917e151cd0b1022535f68d6ea092b4bb61120c132c5ca8b8ba94c5acd2f858bed099f12de5c4439070a0253e34d150b66b078f8e4aa184a3ca05630f2828d4f8ef2044be1507a7e5d8e9eb3b40f26a5245034ccd42814224bb85851fb71b504bfe0654b70a4609383c69a103fa10a9b1ca012685650c8b2a40209d55ae572d333ee9ab29897a34ccd1a0f154253fc4917193ee5c51097b1296964d8d42749563bd478996286ac2e51b1196fb85cb0b0c44d0e4ad37815396345c916295595469926a3c2448b2426a4c21459dbd949cf5c627ae6da72ed10a65a53cac29c78118615d4848a1a0690aa4b088a364c55caacd6c9d5e6aa99d045a4e7e7e5b40a240447092527d8a02d9d31395950ea2a0bbba2cac1826b1f804b88af28415f44b2766c6555011315a4890a96305c9e561c200c438daa5c9587e0ada78eebf4dc4ac2d94a598ae203cce4866e64bb66880e1b589e5cda255b26f8b9d582963632486a6e82ac49f5f0b95fdc087171034658952f9f8bc146069d9e52d04829cb024d6b3b8833ed5f6b6b5090cfd500cdf2b925bd196a27a102595b6980f4c91194b6cd29e54c81a631f0e5360fddbf47f76f92eda154966a34d99e5b7fba54bbe372f7faf6b45fb62c4a8a7edf9ab13d3a666bd0b4261b4242f574c555f5e573e91a205ad4bc143d39e3c3e79630f5a6dfabb57e77939cc5caccce5440b341f68425c25075ea2a0b4cbf50b8ae5c16dc1761015470c4ae070984f0a4dfd700073a300218a3b9c10828d2b0c2b270594104526acc8cbc88892d712552df09a50ae7841c271c7d42740ca7637c40513a014ae3bc9fe2e71c0b631879637ccb135c74fb1b20732605b5a12262c5c7221135e932e6a3480c13d6edf43c81e83e8d40c793f45ac293d20d1b594e464d31a893174f94a841514385191290be098638727f27b90d07e610177e589ae1e370e18338d2dcd9d92f7ed0e713b48e23cd0f68ff09614b15d8cf99ebdb7124ede2077dbb5f2892f76da4eddc03ca43f06de4ed22d9828a7b59afbdf653ebabd36e7b0f162c039ac4a8444c988e5ac90c250d000263160000200c0c874362b12c4d523029db031480097abc42523e124865a228864110c4300c83000c0300408821c01003a14152f1005afa437bcf93c628b1aaed63c47da03e68b60aeed5e95dfde6901eb4bbe1c0d952a512a1b4cb3ed44a2824d1724a3f07793955ed8de59144810f761d146c65df519e6f50c38d156d8372c5df4b3f3681ef88c3a62ec66365d18e5db82abac9b284b4ddb85521b366b49774f1c20ec62e52a730cdb12c1c60cd7971e6bfb4566404819e64c2a42015626733505e4c91d18f2a64f74247393eb9a49dc61890023882163b86434c73113e5c153ee677119f26b45c26c4519b55dd6cdbb0739f6967bb6e6060f50a0d923f9e0f34a93d401899a54c9b55b72ef2d964d61c93b3aab93d7e7c80fd544b715cc946f7c41f97ac6e2271fb6208b18ab2fbf45984a4744085011ae3d407961383a8035a942156ebde4fbacb0a46c0fbeba51e507afbae65c5a1482e37a30fc4b04f13ee3640a894f28d49b2a1819a9c9a09980fc5339c74a0ff63eecdc2306368acc23339061de50e084a65ce82c70f0e64dc488d617288aa5a7367f345c4c1a64c4ac19210da9da9833ed76e52d0a384e279457e6622a60a28a431400a37414085fa0dc76c4801083133df04a698594b5292e5cf082c4bcdf20d49daae3c527bf79a7404372507b78aa87d841dc6d2ffd7ae39993a76b1526eae46c7214481d093c3828290a57c81ceb138dab8764a10ae22a17ba9c0d584d7e96ca6146741f4748578628a06b2b2e806a8cbf4a4d0c80d93ba2c89b31e37a97c841dc62f53679d458f59b23ce9379807ebb149f06059be8f7b01f722317d3b1b1d69484d1efc183d764a8d034c15c0d1e0f8b1beab71eb6c6ec0b664bac3d39f153c508d305733e18bbb09715366288b34e380d42bfe15adbd4845f7bfe7fa002421f61737282314a9149dfd93a934369861bb4972b10aa55ec394d7390db84f795f14db0ccb8399bd6d303a5240e25450762600cb098ef13a0a0fc9e9252d9e87521a1f1a4ed04c5cc2bc8215f09ded0715a3b817caca4f503f4b54da76c36320539dec819f36e105142599e68b856e1c4c858ed207c5568129d7b64cbcdded3f0cd7b20a616c400edc23986db7a39285e2f8d68525e62b54f3f352f215143ff63ba5e6c7bc75dc272873240c934321651d239e84bdd123718363b8646973333a32f2843be212fc4a4bd3d09b50e7a4c794104b9c81463b1a490de227b58641dbe79ef8ce24520c08e0e184de6fee8873bd83d92a5a47efbb6957182888d4db1a47f143b9d1b44c6a65034a3f724b0236804bad8d7dc449c7ab2cd7b66e9c18b7c8633823c7c43c4cd36721a5b24ad149d5d5a42218666103b839b6f5f21251e14a51f3c313abd8ed9c5ee43df64bf137644323d83615c408b65c59e866c903baba712e62c18a05a86d6a365b21200cc25486a20d29527862842840d8189444cb2d06c6c7c40d9c0b462e93c49d698ee97ea680d30d3de5c52c80bd10c95ee008bc695ecc51aeb6a89206622a24fa0cb71f73f4959de39ca3e497be7ea3b111b4b515380e0c46ea711983dc13e25a6317038584aa51d05a96e6ba442afa6f8584751f0db3acecb2840445962a36f988fe257f87be064aa6feed144b2954f2431a7d5bdb129bb909a3b018055ebd03270b929551e8b99d5267b8d47113eb9238141954a2eb2f0d994f92f39ac1a88032bbeeda64015004933f098512e8a78e9d269bc8e1456477387d7f05f8405af88a0ea150e96a6aec5c0215469900f22588067f91450e044c5a30b50d7c32f77e42d038bc924867d12149626d5500656dcea961070109792d6927636267ea732d2b441ac0f7abc26bcc733c1c41c7984f8f32e65fbc4d7d8dc26ea45842113e2e25f04bbc80b5c66f21f32bf009bedcaae0f76373900bbe38ad1beac16f7c882116e62b82b98bd4d4d99b60a8803205d8fb4d1188442ecd150275df6ef87bd3a51be14011dc06c66bb2345df21f61c15e502595baf795a9797554873e8d5f0098814e07722c3d24fd2df586043c7612947375c8db6d7f39494425f7e0f15551ace3e16beb5b2042f841d71fcb56ec83133a1a720fca3f7207e14a4ebda68b32bbafcafc000e68411125e0ae611d56279e733b28b5330a3c6d812061b34e535d4feca2b10c3713e06d8dd15d26987b6e37b9e144e9d39581460d34da4a88e076c0b5ef867494c3db91ff6848f90e07381687730141c4169b65e2ef2afb0711e05464f204a56b92715115866527daa1ce3b1bc0fbe7eb00f2fbcb75c1b60a6eaa043e6ecf6b54bc8e5d80c255399c73a5e5db31fe6163f4c3b2927ca2e08604207d12f7c927fc70e8ade665cbfa4e34954e07c6a79920957ed99953eeaf0535c72cb29f2388ba3c9fcd9f870f57bb2c7a714918f985165b461a77c99cdc44c5467c364e26b4246dbc76a180386d954df24227cfcbf93ef429629cd6869627129d6e71a921d1886926d0a20fb118d132848241644550f47b45942671bfc20bca54191821f5da2e158bd329e8d32371d2b738602a114f1c936f894463fca187154524799fd67c570d9d140abc297a112bc8ac2c3d78b590d37a3695e266ac7bff21c5761681ab05925e76853c699000adcadc10c355d5ab19f11d16c77143f57fba158ffc003ff1ff831b04134975f6d933330062791bc49738589a008d5e04627a1b0af81b047b55bf2499afa003c2813c71ae70601b9366444dae7ac4756644972591394c3fd2fb036efabf22781edaa8195017dac053cc6da4fc818ba1c48e4036bcf9bc3ae945ce981d5232e6ebc6b222406fc81ba00b47bb687111df0d4cf1ca7c75c699658d53f8be2d0593b31e63d3df6c863b05421a34f700cb9563a59dd6adc133a881a6f47276c2c50a1c8113d7f3fd754a5bdf379ecec963eefbeba9b458c947e0204ac21eb3d1c60f98c081cf0f50e3796d02392a3bfd7e5eb019cd9bceb3deacd142cbd27fb0375e0170e8a208346e26562b10b317e08179ca546db5e122cd510d5ba7e87d49c7916674e016e909c2f600fb8991f7c70277238eb5f3fb6b06f111d19986e881ae03b596443aef6d3d867b3eb29ab7fed3d7920474a99d7db1f6f074636268228f4b32ffcf66961a09b6245b8275f8ebfd4a26b8d8183f297022d0b3a7579560efd1ff3987c7a2842d4dc679b7b7968e5fc3067f53175330033ff70a330d534485e996ffcee27807b316f7220649f9f9b9e84806adf4b1a363901bb7c21da1fbb97d7b0efc7f6605e151ce9d25e5b60b666fdb29db96041d92405396f68380a7ca69d1393656a028d3a2834f0bea4082d4f44ec6765d8ff34d7d88dd47b205a36f2740114b30069d4c0fe74ff18832c1b6211fb187c6c0215399c13e587d6a637ae53317086bc4e99c2cd3b2762fcc0fc57cfaaa40f9f5133ea55bb25f76b7efe5bd8957045cd56ffb3c33ed1845818f78536b7199644f93b74991ba169a522847cc74df75532cea935f143f8347f88dc07a4b10bf8270f4cc6fb472dcb2a13a3d4c52d93311523dc9d343476ebefb315753ac87cc83d3cc5c1400f1acfa4f665b32a56062ac5cbb0c3b78c09a4aaf728a8a504ed46963865719683d6c855f2e7234cdbba04a9a0caeaeb3fbd498ed43c295f8e23f568281bf5b1b3560c0533da52473ce5a688de00b625411f2cf3e8c73718481fdbf88f21b02f97e5aa54021875a7e651a8a685568f20bc05f58fb6dad2b37cce2118f078c6b00bfd0077344e9d19d38f7371845c9e017718d284708c66e000c19480d772b4a993604613523296e7b3cc9269d36a414e5204561356ac3db44f4916ecea4451aa83d879121073dede37a6c71fd8e4b3752b6c4aa7167d8bb8e243bf50e4eb6aef06ed53fc21c44dc5fa2169c8204ed4ff009f1a7ed39ba224836bf452acba531b88f85003d34d8893fc8c48ed030a91226c15ca3234e6b0d66fc62048f0102f1f05c8bc5ee0b1367283ab13b36defa4ac02166b149196c893e9d9afc65c107197b9729112c0aaea51e92f0d2c63cd8d9d980148d8e1c39d3dcfcc5d9e8fdaa8abd4050787ebc9e961cd467fe6f689f14fd28f62aa3deeffb84e12831523b64a7c3ada273fb38456dab97a99a65bb391133ca1e42db9f43bc090c256b2d112769fd689c533ce6ef4b34a2074bc014737a3d72524ae91a996e504208d75a5d783b99b504ccc58a25668a0ce66a42815b3d2ead6fcfc7e7bc52ff5659821d58fd03471da39a8a57bb0077dd9f0e5d194c01460394b883cbb0097ac5d884fd1322b638cdd6ea9be96ef11e6a658206f394529ab723444789b8ce49bb88f3d2daf9e7e9c1a32597ff482cb799fc9fc9af977b2127b5669496ee1de833dc893baff8cc06296308b2f1f65e5f125d0ce61149a1dc9bc490db0aa03637b63e30f7c0fe11bf5b7a8595b5d279401ada6889c7051cc56ccc92221662fb672a8422aeb0608c7f2b69995dfe75c1481b411ddab9ae3336b2df9bb778116c10fe8f6aec2ff448652ef174749e4351df2060fd70ad2b257721b00809028e537055a66a3cebd735c4e92cf16a18e2c51c70f0bda749edc26980130f7d46a048887fec08314873c8ee75a6cd69bd971c51b4b2924ced51aa8afe8bccf116ce2d63b63d84162f129de762ee2986879b9c802d944905740f83bf697dfc2330f7ae27098722afc66050be6e2a7efc49dd853a845eeddeec9c06d8a19d831e86760180ee9a3e83a58949a40859953d401d45cef3dc333a49ad0e7d7177938a9b9865e495802f48fe5330c16bb35253af89adcb6bbd0ebe261efb6f634d71d5ddd3c536f087cde94a3eb9e018e2c5c27c14899d103471bd1bed1e2c99a6496f4e029dd280cac05fa4101134ca217e1ae006b7a136ca215498341655496b13c227a67fb07a30f96cab5d10e7143d6c3eff1d0c08f12ddd7da639132679e51b9243848534e0499149dff80f56ebc8e127df0fb4541f2e00f7d3da1ff0036d18cfa7838342419aefaf16ac8eecc7d3595f4f1fc205ec9d2a549c7681997e9fe673452c1487a40ce82e3730cd5bc408f3ffffcf7926e27f3111b0290bb05df81e04b7461001b6f8baa75bb95d6cdd7f3c20d2b57a7cc8d9ffc902b27f644c1538019db7713c093167e3eabfd7a6f5d851a050b957398c55601a51966494ba1988218223a6bf00bf7de93869a21810dbead08539b6d7c5a91727dbab2073e546973049d022975a31745e8e8a2d89cdddc57755fba12387fc62be74a0b851c15809dfc3987f35f3c822eedba4ede6b3f907e2132fbed601d709a8a6b3fa620d27d5dc5c1c195e0226bb63348b1097c48751ea6c1506ff0b414dae8c582441ee96f16c3e50fc75b899232cec42a6a37bffc8ffadd58999efc4ec795444e642f28912f5ab7b667cb4411ed23412706c263440a91c3a0d650517b7f8e74e05b086c4c0b2f93749929ae8b20713c327582af94098ecb964397131702cf2d427ae3cb99ba37cbee2cf39c87a016118f3a4b74c540128e824dab403d9a35c32cc9c957dfccdb731b5e22323c9b4041b6b3c96e0647534d904f221fcb0d7b6c24096336c38028a84c033e3a363c30f3a7abb81aa75aa545817ed3395c54597db3a9d594f1b405b3fede6f3d9f95a31d8c5b1114fab0e78c4c7df41e8b04ca5681a9800fc042f048aa548f3f0cdf204339e706c19054a1f0a516f9bfa96820c1a0ee14a79c68404813a54e4219537b56b8a639be0523e562f907702740472248d702c1156b5e1b8df0b20bbd4c43654efe2ffd1dee4ef616e37c6be143943d18d48fc3315e5ac7e68bf5429f86420eaefe2cd4d7eb11a269a3685329c5eab80f4156df74838cc7364bb15961e31064b67b39df2a9215c24dfa41a87e92d3441f86b5a95cbd5e6aeefe4d310137a75de1f00223cf76e1698d813ad7fa18cd2e053242153d16a6d1c096b938fb81a92646348ee8db60747a5117f233fb66e84ab434c3e6bc8c4771a25caa7ac31cf7292c9adca47eef28175c1011551b1a20761c9932c6a3ecfffa730d28174af8231c20ef97205a43edeb6077fc2eac277401fc9c2202eb2bbe161331801a2b765983b6a436e37518b38cbac9104e007e63a5816919dccfebf8713648fb01c5e3304056c2d8031fb4dc21446add271cba0e56bbf507288ed9089f612989f3090d80668f9210e553cc3f73196972a6adb72e666de20f6285b7e0760ec30f7ccd744d8258f2b807898760dd9004e76291fa755486176444f1e20116f2d6435ce77c4f8e0b4e8dea76a599b9413b550fb16a050ae41b7c532bf5699778a9cb45669bd4e84d380a6f1fadf872c57c255f3f1d4f3a61c1015a76283c7e386ebc41ed643950ec88102e978cbb3d093d87a08f985f7c1f91cee777db00dd2d3fe3b346cadc64431efcac7c0f0c6774dc42a0b2bf06753eaf3bed667430e7ba4c6a8c64e483b9884f1bbc4f223cd09f073836ce2ec44a24f187c272706c058cd6e0e1e9f51345eaf4be60255349c55a12decdef141d14e9de09141922bf77e7969b88119f8b287235331b36d1362501d26da3d1f90395680775a15b022eb2e536752ae674827516e5dc84c8823c243ea2e357f99deeb64bb8bf1d6e7c8ddb25afb1e1164f5fbe4a6e07b85cd0fb0af7d219050196a2a99945a5fbaad238b7b059b71bcd29d70707d98eccc9e938f6022eb5e3c260c21431613a9530fecb3e2e3b834186ad5021311478cf58fd9a2e4465f8bd6265b097351d2743133c06a69cdee064625cf952ae44516749fad217a84d51f280a72836128b1ffcdffb76984b974e6cf5ee87592b361370a35fc2a5d0b3f559ba6c13d7c0a85982541a1ae093fc9641afa3d6913f09eb90f5a6c5d01123d04f71ac8a240284bf68e7f0cb2f8936b2b0b45ebeeb8295cc59169f11f8b6c02cc7f89566c6557776887e6865e763be496a062f31419cef3342d6dd96a2a9bba915e5f55bddb68bccb2f574e9d99125a9737e4f8d46534d0b0429db9d48b444d38b6415f851802d3dd55ced8330578b91f88ef7653e754e2add5af5e817a090e5b9db633c463ea81eee52d042f858698adc69408061d8d79433cdc5f6b33a3889a526bf752f4541145497af329f6cd6c84501a88b6e649c90f59d0e08d9ab9d34ee16b1d58abf8160efe58026cf232df61953614da6a48cb399bf83cc0b6fd1425d19873ef2d31c569901c3291ddbb1c393f9b6a52530fe10ca646abd564b9c9f03bf5d85dfef39eb3524206fe6e6559d47038af6bf500c41cc00ad9d83d3801135d491c5d0837fe0eea8c06eb8fddfc8d38f5235386eac4697e8d04704259aea675f26cfc43bb171dab150a5b978140e3c6245739713c0ec507aac94362a7f8151561501758744b55895192d6549c54fee057ea0feab91d9a5facc2a001554e31bc87fa52d184223fb2552fde4b520d49b1579dc680fb8738f532e3ebda944b0e6f694a5c08964f3e710cf498040a220aa9289d774e7aa01db573b44495b5e71fa2438b863201e6d797ca9b062c5b03a69bd524bb6278d0ce94d0e3b17dc1b007e536974acf594b23a98dbf062f0e5a101328aa2cf2bd4bd4f8c3d26392f639ca15fdfefe19060c0a941e7c7b331e4a31ca11bb283896cffe6c421504aa20549fb320a8fc21f7a721bbe316edcc2a2a0b09d520cdac948411f97d4718da179f50870e1cb1e2cba1843109c091ddeb9ad23252f9f7a9b53f22440a70aae79e6a8d14fec2a970a40ccb2686f7802788947a796c198cdc7977bca0f380fdd6ef84c134c9482dc17ab05da628a1067882b3fe9fe8c47ef8a087dbfb7968258214bcae972b5ffe75e1087a7c7af60fea1dc27fd4fb2cb3a0912987021fe06f74498f4c1b8fe84ac0d758966334cd75aefd266a777f4fabb32dc39389fa7b29b38a00c0921e670ad0f9a188ed2926f2e8518feaee390478119b8543a84b22932b33385c0ba143cf1ce1e441a7f3a215a7fcbf6a7470c5b1ccff9cc0fea2d1b3efea1bbb3577a542ae2b0255b892182ad0f441d919f4bed56288ef17131969c1f6cd4a6617eebc7740b0b429c5c7192b6360ed2908a1d7270f32ed9c65dd9c3cff61d93041f63118251224ebcd507fac1c96aa70530c117fa8e7a878ef29f9c58bce992f2f7d3e27d8016553aac653511a2483775066c8d06ddb277c74f32036123fc30b1ac4b363e286c78be3a6dfb3acfa6fd3365aea3b4baa6f0755751593685c0b566bf290428b4dfecaea7c8528384d83e7d1f32cdf8775b06ff83635a6a7cc4ff5bbb414886ab09a0e0f66ebe9f6b6fe1aeb8582aaea2a53225c681a649c3fe81aac74ba5e8204b78a6c2db7e87832af5871cb4c530c9c23843c64c5b7eb0d0dd908cb4c061ae05da2541dd20bc61a921261c6944007c8332b17f3e6ea6cd409dbc84274a1e9da0a2e577853fd39fc730769dee23376cec04998d78eca88e9a8c5cf6b46a145248602f07d763b1575582936ae687457368d5196ac793bfc339341611da9376f6756810ba55d7a55221033138807fbf7850cf0fe73326d37a7174246bc908e61a6c29363cd4d9c90b90663f1760825f8538607b31967e556d0e94c95e802f6646acf90bb32ef7ce88cda84ae33060d436258a409424d59ff790ef3ad1c8a3aa67713a89f0df3245406d000c97dca75a093885af12f57135dae14d0471b0aa5e301d0459d00946a1329b0c869c193380501a56d87094d63caccba5c228f65096d66a8f387a2e131d6fdc7d4c017b20a4c79c43c63a76bfbea58ac2532b73e31a0d417824765aaea25444263879bbaf48e38ed9720799a245349fef8437aabe5d97904c5ea5c06aab734f82a63ab29499884deb4f0112e35af7abd474e93798872e010ac2351aadc8b2acb37a5295eb1b71b450d1f9f3810488427a067700c8814fa7281c366709d700f8f29e379b9b04264591a92783ad86c24c2b8e18f1986f1d29e47d16a68b4c16d36920ed6d8f14906f1eff1bb9d8ef43572b1d44674b66d9bf38c38d90fc441ad4a979d37fc35b3041991b2a3200be55adee830dccb55e350c3e125f0b60ad139dcf40b6b2aa75caed6da70ce5d1042426bbd237186e9e86f49e99d4225547c28ba705e6f933a9645e4fbecdefebefbe5d7ff4fb21f9bd642ee5812a7e66a977fffd1a24b5855f80cbf31b1106af611c9367fd6a30a05531b3f92546e210596d2bfbc2e7224218a8bb39ac25381bac4adffef47b0e7b137417dac779fb7e350092fe506050a43dab17565fd3b52715723a75acdbb52ecca690c60cd440d3e3e12790bb7aa3dca00d94de6aa639d81c1430451ac0fc8176e6f55ffb13109b8c6bc701e508660120adb0443ccaa0b0e841046ed4fb759738b31fb6004f9a11659035fb7e3933745fc92cf6a52fe445afb1b9bec0cb1838f92b71bc290db3a7cb831feb4f016c72d9f28fcd60fceb784cebc9e5d7d79bf028b822e830c3a47ecff086d433dfa361561f89a311324fc455425ca20abc6410a1788eec58860697bc06a9074d009bf6a1a137ddbba1953883f83f1b3cc83994f11d9fdb5f4682de8d9878a10ab78cef22b516f6b269e74d8ef462a1920ca43ed2a5e41314475ee016cf07a4d9aa6c9390328abf505caa9f7fa5b946d8e957505806f70674888163c46f974cd4572f7c9f1067b94b26f03668fa16a2640ed87a3d99886ff5948ce9f887b741796bdd2127204c6fa4b942cc4675f70747e0c9d7d4be27d8f46d664edf8b5bd788fa0d424b865d1471f9a7f3c3f99dbbf50537b4241998b9feec9ad5542a28483fe6c95fa427ae073ff0d8fc8c78b8006db95939f917b43566e93dac3e302abfb3ccf526fe3dc6678ae56751af5e566518cb3de3b885e8d6d70d172fb36733af62767e7d119bba63f275880a3142d94f32482868d08fe90c240433ff100d8d4951c99fe7782387d3bf4f9c6ab855f6046d583c6108b41bd800b709b67cf0649c6b3d07546459926cc968963861dc857e6fd6a5f35ad958e03e1515bbc50024b70efa4d6a9432a494c2e8bfc06986ab56685f04f64c76ecf37fa3ee11c363a178ea534e190f597c6906de5c0f11ceeb4df3bcbfcfb20f65c423624de6d6cda41a00084aeef6983763931050632c7d5a2e89a31fc03a4b23078495452c9243136eb1943e4a5be0dfc90986cee848059b99c9ba67d006f8a01b4b796cddd21c9411dfa4b6170fb2348f90ccc7d943c2913093b11799d628222de1aeb43f570c5ddb729b8fbdf83c7dd3e89b8cb12b56d9b07072dcd94a029e3ba2a1c02c1d00c15e9799e1d669165a1dccdb83903ea435460fb0940043c759c48ef91194ea7b16adb5cf6bb143edb0f1e33c92c78bcb82c491e73ea28a2c365f7ba5228ea2d003a3ee8ffe835d17e773a65812a14b84441dfafbe09c02f92e7dd1ebe19cf0caa2f160d1abe626bb723730782591026e8e57077d61d51c92ce4746a1e7ad7ece6c286d1201b5f9dddd5485d8a6ccdd735a6f6353378de061f8c2db6889305668b30ee620fc411bb0ae6056098bf2f823d75d9cd93fa2a98d1fcca349baa1b8f9a030f82ebcb0da77c050131375a0bfa960e2c85194469954049b148f0412c2992fd29c9be37e08d6920e91000136a16ec6738c9e3621152a56d378bcd24801dd78f57390a2296dad3ea0abe025074ed085617b6744f52e3cf8920603dcf00204200cb9f27aa5a3a25fcb65cc022873232ae8e2c0e61adbe4771dd86d9bd152b53133934c45dbef0789e17b9a11f205da4809aa03afe729abcd7c0cce40a249d29dd3d90408e7208a052bf08f1511e6e0e1a6d2b223a935eb0ef171c4504b5204744f44458ef32f8238223c174e01600c10a9b5b387cfa0bb1ce3cb471f29294e42d86b6727b7e541e2c87741dd1296d4fc657be9ff2c34acd771eb7bf76c69a66a9c0c5001afd320e7108f8dfffd2f5f70611b3a7518ca45722fc39e18505be2edcc4e5ba1fd10a976f772b4578d17a98fca5c1dc86d7934589f32178351750df27cc5b6b31157340598287a99859a0a3d2c4493d11c7684c45ed3f051340e3cc19ba8e8c419953ad5025d127ca1061f0059cecd41bd3ae4887db8961ac248d1d8a2298504edbb326ec94c9dc8c24446539303275d65affefe0fdab56d1609bda3ec43b92a313d02340a0a25f1131e7b5c0c34373fac7c0c33b8f919bd5a0c076829e5a49e4e137d404f699b5085aafb0dcf3ba2c48a657543a43af0b2c46a81b2079539f50da24dd3ee10f002b63e32d4ad297f2cf7830c99a51a8483b97fadae0b36e0d2af28c716a0f1340e9cc007bfd6bd96c769325047630677d0b633c6889fc1fb5f6c5929aead3c694fcb82a102a13142ed4ec4915004f033b84be070ef6d5a324778cb67d24d998837818fa6f807a00388e28c42614806043156b6f58d3cb7de1d33dcadffac6c0c1c52c5e8b4d3abce4b492970e4792014963e8acf76e0c385cc7ae67bd6d99f44691b6bd5b29bef3a3b6f1631b5a1beba1b512b33978af6c7b1524d566f043425b1e847fb2538a28ad3b8bfbbc300a4cbd817ce5cc4a631654c8d8f250e252f8df847ddfae1ae36c5323be2070a5e773d231ab7a5e632d3f7b241277a972d6737d2e78cd4f282876b48c3a6368cdd4b03eb3dc6b7aa11b5dbba6d470ea20e4bec834b3b859c77d16361abfaa490aeb2ce20d220eca6176a2d03cafc252eede1449a29c96ff667aa3dd3afb3b60cea11ed6aef075f4538a0d86e6f98771db81b5c6438a7ca0f5e0f6d8cd3f25df35cbe812491a8a451925e16a0cf5593c188a019c4b49e7fcd4afb2497a811771590496004ad3cc2b3227b0ae9c211f1998b550ce779e8ce82b065da3329a8c214e247883b993aa7f27722375a86348c407c718598944641c6766ac625c7f6e420b7a4a93bdadc6c4edc5cdd2442adae21a93c467c78ff6cadaf2910aa464c07e3daf3b72c8b5d553ef3ef45bc9e72053d4f100012f1e9b035794c360133332b4605079fa0c2ec9d1bb9b1af4389ce9445d02ea74ab2e6c4b819ec8bb4e1b485f86d7c437c8a5f2279a9bcf76b5e301d7974ac081e20f5a281ffafa58057fc08ceb5c67f33953982d4061bd9ba086bda12140abacd66e77c15bb49855e98eb384a62c571daa561eead389553cfc3cda2bc4b3f22561cc6b21a6cf87a72c3e3289ec1c51726b2818d8c977862e906f53f13604a4fe3c3ddf5e3fe6e6ee0ecb39d81c0d90cf47fea40bcf764192772123588f9efaa4c6f6be7d793c8e67d727f99d5fb8169df5ab4aebdb9ec0c0db3eca9381a449215ecefbd19aaf47f6ea2d20db9c4545e11360a944a4ad119ca523c3f340f83e6f9b2eb54db2f35633f82159e9b6a672bf692aa487e0a1caff9b52e4d04dad57d2c3b53fb813b32430d862ad40d47be4bdec2be0161d408e77010fcc7ef78c201e46118c12991a2595de231af1a4d28b4aa1f00b7a102d785a8be02d5e729d9b6f4fafa66e07b82ce8b04e42cfe5134d6f82fb48e8494610628c7d76d8ea44f5be72e38149f1251bea74e31b1e34431bf38ed7990df0fb3f6c9676f49f1cd5868718e5075b46b926e7c360c16964592aea9935370b31475b5c429192f8b5f0036f13d3c5e9be1a699f2896fa8c5d6f1b0dc12f35a3c267f3f291e20d86aabd0068df32e132ec2398777a328c6926a9dab7870cc6dba5e77e7fc7a1f8a7785865e042be82928c0d10aea15c72f42375eba510a3655ce6d6ef6568d708035da6a029ee334c48175dab03a7bbefa1be4da63b1d2cb57dd1e208374821898bf221813a59f2a4f243dcb1a41f707f7647918b4c7147b54ae113fa8f405f7a4f15522f2f17757afcee1c372d42fe6a255559588602574254187c9488650c5a6f7ab223960bc54a7637d6d8bd729afb5d86774945cc20381d20fb4ad7e82f7b24bbb96e55403fd27ab08dac1d098fe4eed86ecdd9c34dc175479bfbe93ede912df118421423aa7da77f4373379246b2e699e0c557ad135a6697fa2044c65edbbba25f7daaf82e883848687850167c8de15c209afab9a1bb40cc4fa5b99d0ba94762239e01119caf817e110b6c9ed9c98cb433b0ca4fe7bc69709d4d8d0112fb5143894ee92cb732a36438d0895c1a657bffe7fd3592f941d685d93b2e27215e38ccd659d4c8bc1e1101ee14d925eb79dadc6e3f1b2924cd2bcebcd9178f16f488b01f563f073046f190411aa80e61a3bdc962546711fe0e9b41ac2850fac6e7c3a7a09b8ca305b9acfb987e4c1248b5b3979b82402c29ab2afa53a4db28f20582af7e154da3c3b95fc03b233f4b4ffecce4942d1e8ae395454075e57f5580acee44007c0e1522cb2f7f90f92aac4597a308e8b4b6e0efeae9b822f6bf5e5c00d2e40a948b697902687963b8ec0426684a839582cb0b96042c8194fcf6dc2250256ed050ee2b8e6da420a4c04a64d352ddbd51ece4a82860248689178100f6e9cf9b7e3f00d2432b0852e8392675f7e8b01b2b1a1808a8216b17a64293a1c1d0c158d7238ad243a3d6432024c4ae512bf04148a3085e81a95512275c7b81a94a982e4e942f90b97e12e6265c24185d7195603e13a55bcae45945b0f55c70ba1b25cfb676152a905f09026301ea8b4bfadc52bd9eb73dbd79b331fc4582cd257e810bab5d6de3a69fd22cf80f81d1b8c0e02db709bf1d363e2900ccecda465f083507905dc781610e60d12af66a5e628fff57a36386e73297880500c7e05a7bccfe970052fe1211a00b4cb53d00e675b03b2ef6e25505c5908911e07a2037337f03bcb61dc0e30590465145671b00a49e22e5b3e550fd7412694c4263387df5d1dc9d799b527a076ae4fc670006a43971389492fa7ba8aa4707a6c6f0a88f7ce2ca66fc572f40d47826e36c7f9a12b4c34a968fd80079196d80165c7ee925e3a7ef278ffc0bf193a3efbc34b5420fd5c68b883f8e61746e96d2a99fd0a4c28f4e514631c1d4fa3e8bf5e356e1ef3ac8ba990b6e241a75e6c412bcb9b861dd9db8c836d69a5d855e7374ac2979cb1e17570036049faf59b6bd0c0da350ff1a1822bd98d45bc67537413ba15f0f77ab8ba7f40f9f3f7ae35084a728d020604e0c3f7233f50590e25a70670a0f71f6b831ed71325a95ac40df9b47424df50ac37f9a9b4eaf3941b03b2768ce80933bb474dfa08f24a9701a95452055f4d204b98b1d532daa490b4488d9d9575b0552fefb0f4d4e84d15f31e662e2a94046d5e7c5a6e66995258d3a6a0382f5bc6dfa6efb12a2f62a408c1869a866175a91784b375bbbbc4b0775fb2b34831ced388f724827999dddb5aa390401f79de96c6b3869c7ed2d27a387e98b0487fea8e931da2dd3ad3e7b4a8e1f7282507221d83a039f79e7250cf9407b2d1d79e3e5140260e5b04f214d485d542a2fa885a0a8a20768198e0b93f3848523e9f7d8564f7b16b2d4f7469467cfe02e230b43c721e4c5bd771bd65ad9629b1abcea5814c7be5b8fb317758ff4e2bf3928b1b676aeac4e006c9526ffb084eb182bc667d304e110f3b53b48c61bfee40548af7f64ffba106302849c46f5878666f565ad2e96886d95c2807212e8ac3e22bbb92bd686cf4d922fa227bdb8548468f22c6af048453738658453dfd295062dc4c34b0c3f03c7623bbef8daa991cc3fd50dd8da17cb8bedc7ff159ffa8e60cbaf9fa71f86947d778ea8e12e2f8e104eac46c68fd5c5bbc6339e140cdf107792728e938c17f0973ff687a16f5c5118d7ac96c22dcda14f99f0a73381d7878eaf4353463671a0fde0bcf4e2b5090d6a22171d352008bddc5111227bdb6b2074e8ceda8b57a2e4ce40122c77606ff77d8bf7df45539735a54c705c63e8bc77e47557387555200175b1a06d5098df13ff2a8b8ae14b4908eed146e931436fc4bd32377048379545e7cc4e9ce670f7f5a4c69c89403547554e1213c6a69b9352d69bec44b31719c94b25fc1d72c10a6eb657e4a0ddd6c9059ce65116995575cea10855bcae7c6cd07a66dda32a1d4868d5d517a943645e3445acdad2928c423a420dba8827e5c2ca5f254d8ee00a205666c6220601036c2b95738a3c318049b8f5310910a7548cc99173426eaad76de409ba5cf5e086f2eed6fa3984e8fef31ecb833a268785ba38d38d1db0c363186f973227df7d5ae95b59a5b3f9e9256e36d814c9277e3c0c61bf249a88ed65c48a4bf853def5f2fcbd6e1cc97a0a934b795e23f652993610784ff7a5fcadd82f61776e41100fe792457637ae4f14e0c4b97001f01d3c6fccddec4494c2be46405f5c1112ef55ae969a7a9617ae3cddcf16aa9cb73901a6c5b3217704b8cfa3a7966367c8104984e358b4c57d4e4a9fe020fc70a499cd02d7e54efd3e85b476d20d91a523b3b591f9d811a1272cc423616fabd03f5f29a33eae8210a3163d29dd943942d9fe6868acd6e2860163a402b43afb4337792a1902348279a0aa8caae38eb6c5e01460ad00b3d267d797f658dbfc7b6f21ab03d29f349edd441cbed51d94ad1bb1133c6411c0cf2c2f79f24ebde0f1bfbeb421e75209f32f06f552314939974b9e2acc73a4306712559cbae60646d5fbf51db6506ef8b77043d6dcc1d7ea1858113099fe0a41790630bb4d67bb2b1990b758fa5657fcb495b821a2fcb443172f5eeecd65cdaef2fb38b809bb39724d06ce4f5bceef031fb56ad50897727498d8aa2d7bdd1da5d48af7fa31124a2f76e25d1ea909b06416f3c7a67efa547ca5abdef5fd4397d653fb0369073205921135b6048878fca96bbbcaab73643b7b135281992f213a0c6e80cfc2868cbb079f63e9b3f981daadb70ec6d35a9d3a87d08b580cc629842fdca6e24751c7cbf48c12e74c25811b59838488d7bd1ce4d5250994cbf406611532b6e02601b34c67fea7f220b719c9072dd7ee749cf36bf5cb4c25e086b42f1dacfc17df93a7eba19d6986e7b39408948f6b1e5fec293e044f476604bd7b621067f69c55d0b8d9a2060e92ddb3f1cde230d9ec60eff451c13ca40f5a95dfd1d893997c807a78d49f7474e9fb94c2df14a895c1f8362a38e2ecb23c5d9cf041ba855e735240b0ad91380bf6014ec4dd48f2ec3d96e7b6107d5bc238285de4c832230733c6f0dd9b9be2e7cd498f6162f230230b37fbd63114cfaae37087d53f5c50ec7705efec42fd056a9d3027f5f4b889f076b5616ca0354ed3a1ca9d9c75b0344dcc17ac5ecb0b1c94226d6ff6a8d90ede4ea1209979ae13de52e842d8136639c5d7b89589b4da9a27aa17828feeee3d8e3e2fa79a187d040da9020d86c7db8c1bb1586fd49449f0c4cf0f75ba3be4881b13ceb06b09fdab004377e326a5c24ea9ece14e29b7acd6b74fa9f319e66c7de08aadeb63e33a9bb94d981466c7dcd7b0d183012dd37cf295d6a350ed2294bdf0b2d91185fa44931c63212e00b262a9a58397bc8f7ed323c756941a16b2e4bbf0e445a0474d4b980a68f6af19a0a147ee82098989b5ef4698b4bfe4b045117f10733ba5944c4ab16200ba130489e3bd61ef55ac0f69938646ad40c088367fc3b1f928cd5620a401b8852364f4379de26e20d68c1c26e9bac9ab8361f01078e21fb01cae48e0338c3863313e0297b8a5c424db5ac36ce4d50b7477d6129938bcc16f97ae6bb855272e880c8bce9b2533bae4412b99c164bcc89b504289384ac7250ad5230df3fc1d927200d93c47d149160a11d7b719a7668e05c09719fb6d8628ee7e2c3565f68746b4bc79c688b309398f8c863a73d288acc8639bc7a3f279db521d0c59b860a7f5de4d6d46280bd31961d4bb461264a8a468b2b372708f5ccf09d5f3983f28b158121f9f477c91c25cc694f2848f6788ea1cda7787ee29413c98bb11237fbb59f068821f9286c74d9d3b2e6d165ac65ae4c8140b265b78d324f26706b7423875672c46c71c290e071a7f4ffc14ff0ddf3912cc37758a2251c75b3a3ce100945250aaa7ccf80557cbc431c3d108b9eed447d54b49b7d0ee3d84d3542075cb70ba37a7577da186c622433e30ce33b84e60425925473b61eb50b7228d46880d141e1d71c55f60cf0855fd2406c1a21279f9de74368650b9523a53539b6a356dfaffa331ce794a190bf8dbaf5dd77682cc26927a298fb703ea62136acab2961f5107bae75f1ed6e2e60ef55471f850f87af92d8f06db07dd7c6f4fbc017f6349327e77e8b50d909b966904126ccd12a624aeeb73bf859902da21b42fb010e3b33e786129c1d4244b6765580f096bb4e5b85426d6837989dd91ebb24ba88d4c7345515cc3783dd0dcd86b1528bc33b13e3852e4e938b4d011b3ed90779b571967e72230e62ee6545c7c84ca20860824d6f98de7ea59b8540bd425f913619bd472c6e119c997505e16abf99454e378e9bbefe3a8cf4c4cf6116bd6f1f770face06098c9c3c9cc75a99de75d73841d65cbfab5bc82c299c43b11b63b19ff1f63d410c8c010a2a7cfdac03ad8d9cb1b8f40b29c18b9c639b2cfc250e8e6d51f8e1df006cd731d88455d86fc577fe9ebe8b283dd2160ae0dd114a2d5e7a31b019a8afb3e8154e559521b5022e75fd1dc96aa1590b6bbda799f4fa506063d208b48dc5c7178632779b7a152c412372529bdbe834711b7b64cedf55ec3f41583ccb9c733c8b00f337fb5d6b7af0fa212e90b45c26ecba965da6ee8223faf877451e1a6a37f9db33657693ff93019c8405b53d500c52d18ac1ce613f986664385ba9aaa812fcbdcd526a6aceaf67daf745b2f480995e1a4027e6fc2209ee1a36a9a5cbb2c718ea1ea5424e73d8f6229305126a698305318132f342283638b51611e4897ad5d8cc4f1f41a415dd00211edd241465a803328cdabe99a9d2a62c386d5c560d1a47e69a358c3d36dcd42b4611a6aa61ec9991f26d5ca74a616955e951b16a0863271c0e1368c33dfe747d1235540b90790c4917dc553453f5d1b3da00e85e7e1f65bc9cb9ebfb7206165574c2df439d7c3be1f573acfbe41340588d8f373d00415eea2d6f81801b29c6887ef0fae21452426812ac1010862661f26510868da1c809b3284ec3c988c64a77cf836c2a2e60b5331b80fb4b7728831fbf5f9e858ab64200829075f9f051ca1b8f0be66abff0e34097e21ad909c1f27fed0723d406d35bbed6ac8a7f73e264add09e19316eb78a207e680c23d057f756c2185f071b75956128d0e70d8e9f08ca621c014b3b129646d557e7296905fa7f2b719f38cea06b40a939bb1802bac84b12ebe54e392c5534715a65d4bf77bce9a36cb80f4203a907d98de50887a8de2c7641d59410acb9555893e65f4d225008d67aeefac82a65c2f0f6f6e53f9e3c899d50d0080476094e0750efe9cc0f5d06240fa0f63fe915ab8000433146ad13895faf1ee55e22299cc4b48b8afeb3c2154c45411b02617679369c9efb27feb40547ce2345944f29ea8321a01d00af345260fc859171d5b756bea44b4220e669d0daab1916549ad2d69926a5a2dc2d1d13af9b1a3ddc0c4c9188709736b11cca35b050d2465076b51c24b331ebba67eae289153edd5727ec2d09a465a1f29a3f7167aa7be6c11b805118ab1babf60928eb1d00af7218f5f99ff47705972c3a13020c7c586527c0bb2bd3ef6b9b272ef8c8d795f6af311b9610f30b3066dc1ddc12ed2f5fc09cab3a805a3011256e5eccbb81f74ed34e367dfd6cea4641c47d838631b5b2ae7e1bc4bea8639ea45761ad81c87c3a8a4250ae03f0c8ebcd588af9c042c87e920c4dc21022a1e8300b221a6e4c19388ea37561dcfaca709b57d9d73f31ce1828b538808190bb1c5c673b0a7be92191abe6bb13477df24410abeee7d66c70e1124f210b270704144119b5a947fa4788e8d68a936a5c48a2f92c560023b0800197695d9a59367343ff2390fec71ed5ee73073d550ebf88be830696e3c2e9d982f9879b9b187e2829e1203ca6abc8bd7e7e111bcb778aa0af26b079c62976730b9dc7054d3b070c26c0dfd4d6201156de8592276ec9a87b9df18bff66a55765492843989bee72cda3da3073497962088491faa76202046322868cfa5918bda9c71b4b716d0852b76281ff094180f5b614629c9e0358c855704fe6984c731f6264330eec26d127be00236bb6ada9d961211527d6108caac71c6ce9c7e417606447dfa18f56f932c7345af5aeb695f975ebac74fa48206b5c4431d8b8a951c04baec93b4fdad66797f2864fb61bd84828605def408be2dd329be15bc65bd076afc1964e7a97db1dce304a9b15735ae4fe1a39d07ef7ae6b6f43523da90e01c9faf66d1464d090127b3e44d5b3a1ebe6aeb5b538cb3681a9409a0a5f522fd05151a9b31ab55b166292eff1450814aff3d28b88b4507193172496c6b3c2837fe68add7c31b1afe56bc1445b89db6b557cf3fd10d2264ac5f50524c4dd7d741a71cd212a1fee8e2c5cd88f3733e1ce27d8e8d7be479b52e3050d6e1f68785e8e0c61d268f145d607bc94c1ac4c1d140cd8722e9343a43ca1221644e2864439b92c01550a420748852249f1289c31c668e928b103acca0c6c4209c021634ddfae0e270a90316898a0050dbb2390202c07c0d8105705019264c854209e8383cfb8308bcca54204301e10658d5c0a0844a6fd79e8512c208c074e4bc1d90cbdcc0d8971725902ca0f236a21416156b495172c4258009ce64640126e8472c5e030aa86a5078707b260c9e18414dfe24895920c0b2e73a4ca061ca2946684c868491ce31212c4e04175529809e110328c28e5991c8a320445e305e0b05823a3fd99108998a1e38c0d269118d4725224a2d4e40492e7a60a0702c638206c060d97cb225025216ca06921a4140fd55e4e32b07b00cc420859284c9ef63241853086ce906b0f3cba00973141190c7d25c4a21966888da15704383a46ae79b4109ae8710e8244942a4c812c6f9219cf5ce9521813e182311e202531610a65140e0304e2ad7c8c88280a81076258b11b10e5c9e424346f44d0805009e2e021311654e68a2061121c19ed0711b1a084108fe218069622e8245a8608b2089b41e30c035e1158084c848588b990f87bdcc960ef01eb101889cb036dd307eca3f08f94a6e0731debdb4b092b0fdbbec1a3acfa6fb7508362d1b2698d0cc792235a330e7b16e19b15835b9d88b41a646f5d98f66cd5ae65c3168948201096e009b1cad483904d80be7f51abffe549b2c5b2a49de614b5eece5ef07069fbe59f65f2a27268a788b1914ee80ad655571e2a3df5a878cc5db716330ea29edd71bb088238ae4aaa04ad64b4ee060076bf46c5016cd6f40708d7d141a7d2ec6d65a44b5a1b5c3d9d797a4e7beb06bf3e0c36a46cb544f1649f8c629f102e617355167ca16e230a0654ea17cbd476af62c35426d53061c31c959f198447011b7558b0ed675b921b4872619040db4190ed67bdba483939e4ca8a6ef6a7cc5459f40f87abe0e22517ea5d127a83db150a9fe7f50c8fadf2bacf78f68c67747fb51bbe9c10efd2111eaded49dc065d7f74c476e2ae0a89e390150cccd8747e95603e1829332b12538644272d7b51d064a62dfa0ab542f806520143f4c5308f02befa7fcc16dc9827fc1ac5f27dafdb5731f0e6d18aa76d99eb3b03c7e9c21545eb1b11fd611fc34c0321986eea13b06e61ddc4775daabd05f04ca4623324b31f984d31885d7d55e3c6ada7fa01597f7a57206fb77cfdcdd1ac1a060623554761e57e307b16356b9f5a64bdf5cc449e533a7bdad85de86d46e31e3afb1f139c1ab3f77c95bcaf1aa5f547c48e8c6d424917888656420f19de08067bbba657c6b7fe10cfd55682b9dfed9cc02cef9a43cb5af2e0e7d903b800d7fb0bfbdba792032240d6bd464f292b845add94406efb5c7e61713007f2884b27405480260df2a9a4d92376937e43a75c702ddeb5124efbcfe2fd4f187eb4ecf64521f0da1efada56a4057b9072747ac430247251f929e1a4d9cda529bd4f55fd667e29c83689785445a90a4a080e53a7c763044aa1d09dd8e680a88916ac056c9cd85e77ebd4f09b053fde5fa0bb0aaab839d1708c137359b0b8b1ca779af913754bfb0be7e4234b01f2fe06c7d9f65c6cf8003f4e71de1eae79af51f070fd5424511e10d0d362c21b59f7cfe2cf1671789cd79b6c5f4d98eb6698b21b7c1d6d9af3f38d5ea14183423e272dd3e900e668727e62d6493411a9ffa8b0882edfcf6cfc3c68750236641c04ad4fb1e6888f5b37d694e7309b8ddfdcbcce087fb42b2dc14acbd987e836a37788931967d4ce1d2e60b617273f840dcfac78b7919dead26baf8a46c1050daaa9762aa5896fdf0eb8ba1b55505dc3b4f8a1d39781b1a7b85242382423b6b9d0268dc0620ee377e34c79e675209c565ccad3a2956f904fbffaaf8dc686041b54f8a833590c9c282d9e89bc35ec5fa9ac73a1076d861c8fed13c0a3c77a27daa554561a9d9c22e970c4f8b10553a0bc957dc2ae7007438ee9edd65cc588e3f789b9f5974593635b4011c75c864f890df00227940cccb38286ecf62f3a90a221feea52c0e6829999bf2b6a0388ee53e4ccc10ca12784dc98c33c656ac8b54ce7e2ffde8106dbfe60e4ef37c2fc5ed4061a7531638b18c1b8267c6b359ee6461883e0aa193423c3c428a1a09a6a864963e51e748d565be92a08c3cc3164936eb029569abd6f9fe126da879772d17e90f72f1b9f8170850a98a3d4c789e49433445b59939abed62c865dc69b08f9ac7e4826ad001b379c8a2b7e4b84f6f88cd2d97e381d1082b40abb24b8b4214706b7d227d123aa0556ecfdd2adc380edf2e137374bccb7ecd532a8a61c0c31e1d24ba32299139e67b2d2efd164931ea7c80b628efa95c66bf0b461fe40a5437f4fbe7c71528a86a5ca21e68ade8485ae4df583e81de69f9b181e00c5ba716fe34ba083b7ffd58e10c0032ca463dc1b52782052b6181e9b8c2ab39e20d17977eacdd1167e96c4db0dfa11582bb5923db0b9f46e1e7ff1fd7bedaf01e592fbf531bfc4ff19715da26555b051eace9efff17f2b7e65824640ded0e71aaace42f0583271636850c9caeef673977f06fca510b72e21a0746deffdc5fda7b0bf4463aa60a0d4baddffbbf4f7fd8be5b7fa53fe77ef07e87f0262254db3092096725be1f7431caa442216718a12fae2445341dd058cac27c3da215d125da1e31877b5d46fc0c8544241b0d0d812af24fa0af127c23ffdcf8d7ffc4c06256616102cb55677f8b9fffff77becafa1d725e5b5e1e7f73324d6a24d126bc078e54539a4dac734c624f850ec5a02eab3855a5e400cca9236f74393eaffde39dfb5c7be7ff742fe4c52dad950cac66ec06207391b1156cd62e3e20eaad44fcf9139742b5b09d0bac04eec32379a377934eb3b0992221f662050e6e52ec4123384c6900a05fed6bac9d68808adeca595dd3b94057b059c0534645c6738f04abfce27bdd6fab3866be8b5049d42e6936dd6c87482de100d2b4540af93d28a00dde9bf59818bde5097b9be0c9570d135a434d6f786680bbaf52ac5908d14439dd64ff7dcedb7c10e289472646e04cb0916905594950fb76487b8568e5c95b811ba026596139716b180e039755751ea145af974fa729363f58c74fad2e26e3f163a70d1850248be984fba8b6133e154c0114191a3d39f4f2f764f9dfed4a06ba88ba1d39f9b49a73f391574fa9323d2e94fdd44a73f6fb7df0a2234459a859b91e029fc23d3145183b266e182aea18ca4d3a718ffe8f4e965a2d3a7f5dbbafd6a72884d5d1a78fbc13681e9f42b357af1d2e8f42b065d43db0f9d7eb54d9d7eb53b3afd6a3fd8102eba866042b42ea994eac27e4caa96d2ad4b3a7d6be9dbdaedd7e1c045d7d0285fcc2a2774faaf1ef962730dbd96e8f4b796d2acd2e96f5b05378b25956e7f021cd429e4af44b2e0ad2d2d9146985d740deda0e813740d25e1ae21bdaaa02cf73a3fc5500a15f2d6b3fd603b6489aed2d3d356816a500db2b4881a5123aa9ebbf4281504462e63fd18637d03b729017fe266f8ac872a090e0417b94d0f6e8385719cc77ec4f2040b932e86630f2223203f600112323362c1f1440585a9681caf1019af1c19afd83072fe92b7da4d6603c5673f559ce8364355b8701e7bdce55885a98b21ddf5ad95c5d835d317eb9c6fbbcd0ebeed7bef9d57de9b15ed935abb51d0b2c0b9add2ace52440d20a549e806ae2c444e79220093146292052c2a3044fb656d61376b33629694c17ed51177997ba4b5211be37e94817edd7655210bdd6318988b576c338a9270988de8b73b6a2343535a7b595a24ebf267523b7390ea947a7bf9940cca31522a3959fd1ca10a3d14a0da395a0bdbbae1babac80852465ce49713344081e6e08f94031724313073d2051e68a1314c0cca07367112cb9c9163d4b6a82344bba1192d3c16a2ce8726c82872eaeb8b16793314a9110518a8c516e90618cf24287758071d6fa87314a0b526bcd712314aa283f1b8a980ed3196f475b6ed64c079f3dbc3ee5562208ba9438f561f285ddb66dd35aaf2628a72a529538727686cb9f5f976bf0f9f7bd08d060d8baddce7e806b99e36ec379a70b270dab05bbecd56abc9d960ceb5b20c389bd1a0663126766aff6499cfa3736b2b733254e7d2d71e6efdd5d9c3d3db5debbeb3aed4580ab2bef031edb736fdde9efb6ee3caf9635c825cedc9eeedb8b00abebbc0e58de07b9de4c8c77e7ed581a4efd5ead8232fc21d4b71a043dfff43018a382224c59a55e61cb155bd216bacb9fe980c2d4e508054a2f4097e393a42e9f10a711ed863a7d75fcdc13fdc8cecef049c3231aea9ec169149ec0df491767285fe07ff5fbd7865d77a22ed29721c7bed1169b7c21c2b412e34ebd9d57c7dbec0c17733f1a254efdb027d72eca1a707317b3ac01dbe3c760081b1864bfec3028bda15468c9969b35d3c1e7ef0ae6215e7fe399de036ebe15cc12c73a91e39323a3f149921c5dba40399929f179b375ab1b7e0ccad8867a8dc2817220ecc0adfb3810f610b6fc6228862b3aaee162d7b10b6ce5ccc11cfd62ac8bdf279083b3a061cf23d56e735cc7752ffe26e6116cf7a83df3b9daf0896f1f5217b307875c2875b26f56b85901ca4f134841927a74dce5d884501341374e5bea4e63f586fe8a02b6a7874e0ac8221d0af27ae3f54d29a593ca7095b73e9b623521c6af19c5363bacb8728277850501e098ff334c4c96373d1e0d8d4732dce05065476b5685c6cba626168341c50c16808d137775395289ea622c565dc0782a535e18504162055241c27d547c782a43f405548438155478dc0050c1b1aaa172c2962179810100a9cacb02a4265d0ca96895026988a3b0390289482c86c4420743ea794d715796554b4ac78f38d6117aa5030a6f4a10cd141f9794322c2959a444f1a4a040ca09b4142134527a7452685810854b2cca152f8a941ebc09e8d67b9b7e32bd17076e530417633a16fbb0e9084bc190cc90c4a505a018cae85ca53328282983c40e4d6ee48c4c3d7dc987251b967c2ced5852610905a52794c4281d292529196dd483fd302848802ee9077983345841f673dfdd5f435dd4e09e066bdb2f9cd9f46d05b0a2ef4bd3b98f86077a365ef59d3befebde7eba6fad35d55a6a1daeeceb705dacde4cda22a74b0d3af7751f2c96ef9538f46731703116d210cf8bd36fbaf56a61b703b04138304fe1326897d370d3be81a204ba0533e858884d69adac1aa3003dd390ce309b4f1736f386732169e0c4bce6974f6702f4ded5556bad157f72d78d3fdaebae55eb7bb7d6f77afbaba1837e9facb39ffb6af8649dd54baabf7befbdf7de9bf3b6dd9cb7cd82d5b7bf1a3464958ea7b5d6de2775f4273b06b285ecdcc69cf7c926456e28430415105392228c20c1df5c41e7bc2fcca1c7872548dc58d2c140b6f04009880c3d50fa21619841cf3e932d6e643833e859d7a8e1f33a521e40f4fa7c9f1c1d290ba02502e66340677873b6e936efcbf9687bdb0ff7be6967bfad6fdbdd8ed391f2453df2d3f32799fafd42e0f4825499be39244bf457998ee8f99b4bfd7e9b8d211fa2be395e5d78a03483bc91dd03a52012477631d6e9ec36d3859ce0bc994f71fdbc9ebf39d38000382c0690c18d010c10cef6aa5e8d80ae7eadfeb1fa94385383f931175dfd567d7f79abc8c046002b3000bbd5d55bae2f9a0a15b057b7d29fc48975b0cfdff8e57265dcadecd72ff7fd65fc89f5d3fd72b0d93c79a71f2ca8a7d39f19c1e76f295fa8d85171a3a4e7080b182c5e6a36c6579b5fa9add3abd53e7fc3f6626fc74a6badbc19e33c7396749bd163ecdfdf072ee8f6ad9d1267cbf36a1058ab8d9b35bbf1aa765877196bacb1c69ae5d5a8a73dacb1c61a6b8efb9a7dce669eb99f7d35ed8e9679c5ad404e67ac8312c12464879492c6332f6c3969b5dbc532af9a88c07b056ba0cc0ee6b455b66eb198bdfb27bdb7ced8acf56af656d975375faf4665c6b9d229f3b6b26fb1377f5bfcd476b1d502a5c4b1200c7ab7f9b5efdbbcdafd40ead4b0e17df73658bff2be7b1058abad66df8252e26858d7b9ab55ae9bdf071e08341aa739dfb118e75beb6ddfeeed6d2971c4dcf5d6ff8107c2f46a96e209ca40f0befb0f3c50d60ea45e6dceaa5e937d820390d2b1cd6f3f7fcd7edeacd9edaceadcd67dffee3a6f27f7fd9b9bd2e3b8957de94d501663bffddad6169c7d66f933faf6e2ec32fb5566c15a35c1cec8b4a502b5b26f7ad8010a5517f3a805cca88569aae94891a61b3acc6ed6fa90724713d06dfac11963a6334923539891a90bc63967a625bac4ab8bc3be197aaa011e827dfbc99e2ecee854c2a02e64f7bf1a3f5927bf7efcfd641dfcf2134de8f2b70f04daa3935f7f3e341d5cc387031dfc3f56277fa59eec821ee0971d04ae935fca17b218dc94befd08397d034560f520f9654fcff893df05f8fcd9bd8d5a26fb86879b2258a8b408d152849620222d38b4fc6479d2824296a3a42c44b2e010b2f494c547cc4b49969c988496b02831b19ae02f9965331b9cd5e5a845c90641b410512d3834be3707ce51312501c9720409efba1cb3f448c2575d8e4b54dd09bebb1c97a008591a82b30863305eb38207068f3d1311d35017c371941203da0f9596c4434dcad9c990ea6486fd492b5670829354d28e9f1b74700c4018d99124a54415123a784a1315d826558f2b8611fb706cc2d21405c9aeaf7ba599c50e28946aadb5d65aab67e445144bd412b572d52c275651563e9e115bb1ad74a9d717698ad09d0b771fc189e16cd05250e4a84fdd92ee5cb8fb084e0c678396b2d95b4384a6e8c6cd441843611e37099a22823aadfac6cd44180af3b849dc6d9be5c0a7e40b6ae607de74b0f68a6d0106663383371dec15db02bed6c2848a2c05436980fda015ac459682a101b84e293d096df4b33df4ab5354431fedfb66852a94a5cdad0da88bca124f65a1392f1e29dd8c7a7ddc53abad4577c8da3a45557f53da365affd62ebdfec55e07b376c1d21649f9e2d5e5cfcf53ab1b5b52b99de1f407a98bbcd7076131f9b46d00cf6f4f21e84fbd01eaaaec9bee22fd469875cbdb4695ac142e49b840e132c4a5884b0f2e44b8fc70098a12c3a5469452d454d451545214519451d40f734e4bfbb441541eb46a84ce82b6219b7475b55bced8264a940d5de458a260e026886ae1d5e50865c68a116ebb1ca1a87e404d413561ac6737a0a8f8807262058a09d492aabb1ca188f8214514283bac0c31e60622bde30a37248a12647420a182243ab474b7da2dc5909cd3c16aaad313d3939879648f4f34e42ea783d53c31f144172de1a22c92c2350c1efb255d0c9964e1986a831a385622c28db80cdcc98ce660f0d86fa95aca3574c0f3dbdbc775b9bb85d10de456e47cd24df7af7fc18c877841ce06b47941573444973f93eab5d65a6b4d62cbafb2e39db77ef1a5e4e9734a29a594524af9e442a47549ff6bcd3ba641f12f28e6482c35098ab42efffecc079f9f679fe9e0ffb0990c5cccbfd55aabf87c0aa3ca0b6ea0954a487bb3e496b4eaeddc0bdec7b38a7a3b34e73d31ae5e0dd37bfbb66dd6de5a31a5f97e3323bdb3f50a832c64b59bd32938a36f224f9741ebf42ba5a05da2a32d72c26ddb36318b3cdd2671d176da3971dc828a39731e8bfd8bbc639d9fe3db8be1b66da0c8d32fdd40f1feece1a098f3b34091afbcfc1d28867d8b3c3d7fd67546858b1309eee026083edfda5a6db6998aa6a3430573913e82dd04c1453ee3c1ea94255fc86e6712216da155e804d44c0d6e8bcce98fc6acd2e1be067f4da7b5ded5d6e7e7ef82729b3912e7853ae594534e39e5cc07a750bc7ef81d0693817fcdb1bf5932fbb2ed67f4d9e7f3cc23bbdc6cedd6db99f5ebd72cc892c922a03fff066d6830cfe83b60edf69a7bcf1363ead530bdb78bf9d66db3b67731dbadd6ebd5eaa5bd7b35daef3743c2671773f66ab3dfed65ed82614843f9336431b6b75fdbc09c19b5db459855b3aa9b60675c7a91ec7db3c20c6a7c22323e1d199f6818a17074d142d5d0c5fc64a68b21d40f282150445d9ea8c6718b129191d0b86568dcf2336e091a9dccc8aa2c2f04d1a0c265c8171d35627ee45002189238f3c24412c2e4a4a3c7081f0c90e244061b45ba38a1a05f68d220070f4ac2d070e46409090ec094420d1c4dd4a0c443228d3e3a404db95215146304224a91266a280569179a723861bbddf9374328a5b403408ca9d28108273b5cd14047452db4f0f484c61212500e1d0a5b1152ea9482193e7aa6981153c5043f4d33821c7184912135f0c8c1870e6da1532715f42961354e559cc62d2e1cfd604952e404238674ee8d2c4eb450456806269a0512d41dfb083fe1e85b9723548f99156b57985931b31202753b5c5f8ad6eb5ddd27d27a873bdce10e77b8c31d6ebd2b85eb4b91e25f3cb559d5f9bbfe05fb57ce9cc762ff6128e6745006e3a0eb5fff7a7d224f7fb52c78e941c0f5fcc5d7a70065d05e2f3086ebf9bb6842205708e472d1f0168a173b143b2c9041bbe045f021506bb56a7d22adb7dee5aa799905319a87a908802cd65d6f412cecae4ff1520528e674d7c39e220500ac01c52c030170c11528c640f17b0b14c3de7a1a500cbb489160a098d35bcf4131e7f27451068d46abf1eaa1780e8ab48e028548eb3268349e9e0b2ea8409e969b2078fd591524e4ddee5f9a64929352e8f373520a525b46c22481eeccaa59149f3f439a21cd90ea8f8ca914449759971b233c874f3cfc29d2448c41913645167dcf3979ea043d6003d72f4afcf639cd694e739ad39cd61f8b2e4aa3ce2ea1724aa26d97a3535111398486f1787431c64ac613e451091e7bc838168b7de8c4c3e52184530c3338b190c47197a3d38d294e15c5c33449e52e47a72096e9361de1dcbd4d45b6be3118b9e9869d85029bd3d95bd1608cb1f5bcef3e9facd36a75efd5f0c9d1613ddf39df7beb6765d1ae75a5bf2971365dd5c7dee65dfd605e573f97ce5ee7f2babd8275dbd32f1a4e77abfa77f38ff549590091b5b146e1c5d05d67afdb5f0eadded4af4b11424ea6bb0bce3aefbd77de7b7b79ef5d42094649369822304436ce7963bd31d658638d75877597b1c61a6bac3dac3d8d35d658639df7de2594f02369054cf8669df3de9bd39ce67cc7629c6fad77de7b639bf7de259400c3d01821257032944b18a5cd5aa4d1489131cc102063981e631817a6c6303e63981e282318a695cf94721c6363d2395b6268a5f44b17731703a577a93533d95a472798941083023145e01f3049c89829a3834c17324a6492c81c2123858c10999e314a6676d661aa88190c94314cca0c8d39c289126c8e9489a10c0e324a648ec80c9101aa32660aca9823148c211a334399a232426cad46e80e163ec10ac008b90246088c0e9b27605298aa02339582a932535241432cf5200606243118818309519ce4e0634b8f225234b64cbd3005e44b972f4c4ca1a0847f2923b5c3171e3a5a73be3f3f116615c7d56e3fd3db51d1b3cd1763232c7236a05d20699d765be9ee839110485814164d96b547390aa02e70c881bd3afc2c7538d8360eecd579d399de74b31e7456a1d0c897a02f42bef47c01927a92a2921aa594a48ca476983a32f5648ac8d4125348534f5f92be749122fac284ac807cc2e6cbd08cc6f4f1a1c9489fe72b5bbe62bbdd1b1c6c6818a586dcd0e9d7501a83f19a053840ca06211efb1c52333c0151c3941d53629a5c51929304103f6a38c2441729214a5f8e4c1dd1c0522c94c00b15135ebc78711ac2cb942d5e96f0a284948fc6f5cba0739fb4d405f714466fe8efaa014ecc6a4035b004e0310943285974fd494e7fdb66ef6575374156881d152120a59452eea72fd25ca0384394d564e791792f3b4f5fd15a4f5f7edce7ad56cf49706756f516d8adc04fdce206f37f22adf39f9fc8d327cc7a2950dcd577af4fa4f5577dfd6af51dffd737f9a3b83430ebbd58b582e247ebf25b5bbe4071f6d7af5616cf00e2a1f1f4ee69f7ad3e863928c626287e48038a391c0c14b38d7150fc097e078a39f461d4d7f420e0fad66f5f08aecfbf37b7b9cd6d6e7372833268dd25a59492beed9bdbdce636b7b99f423a0f642a3e7be6cf4fa019b47afe1eb7f7de7bd3642bd87bcf7f83b2d6af7ebf98338be21e286bbd6cfe8b83b2f9faee65ad15f71281fbae93751cc7fdeb9b4571a068413991e6388d66d12412d2a2ef011a9b3f33e8f5f3f97394267b81def39f4f29a59482b2d5bf9e9b45710eca562f6b7df7dcbf7e82b256f7da9955fdc57df7da3f857439d5651bdc11e5549f4266513cbbbe0fcc9ebff5cd9e3f048ab55e843dcdc7f8321fbd00e9b3b57a1a6fb57abd5eafd7ebb5fa16cdaf60afdf953bcd27f297ef7d22cc9dee6708ddcf97bce3e9997a5dd7d5b27cae058ab0e9e5858e3f7bc1d1f15b126670af4ff1d2eb00c5eb5dcf01f5dd755dd7753268afe72fd2b814ff62bd7e572bbf60ad9f21649e16edf579efbdf7deb217b8c3f1b85a2f9f93911bb0fa58ce9cc762ffab17c31528e67cb2fcdd6712f6cbefc02c67489cfbfdf91365d49fcecdaaf04c3c801f0a1082c88f0a3f445346324a8b0f132401244b11931722bcfc10ae40738883ea50057ac311501ca6406df8435fb3a3495fb3a311f014f033bc884b900f9fc297780f3cea072e58b8482161d3dff493935a1ada4d4ad967d039794ccd79959e9e261515120e7d429fd027b4714139fb8c6d6e3d55f7d9195e7fefd5ea37ff58b93fff467875114ce8e9fc8240439bed917edf12e12e4f4afb6b8f7ebf066df4a8dfa746d60cd5549d44fafd2984b66009514d9934e7fd095c0bcc23d6dff7bc0eae9ec01128aeded2b0ca9d6debbaa7f48915a8684a08cfce70fbf4cbc087811e695927d872c5a8bbd919aebd18b88b33ba2d0a96d0a894cdb5372d848a4803009000d316400018140a090422390e44792007ef0114800a688e3a583e34134724b1402c0aa4180a8220886218868118048290614e21c69c1200fad9138f19cbb323475208e04828b5663f5952b8a100aee13805d321793a83073122a9dece72f7a4ab62f2a468083d232e5d4df0d39ef37aa36d40f599055299fc9f413a2c5e6ef73054de43b3319f9c45bd803a44be29154c843c68bdf1ea728f1098b0080276b02439dcd9917f1ddf48002b8ab39d0c8b471bb217e4f7e000628807a473e207b838a276c32d707b880e5b8ba0c4ac40810816419d35d0a3e06d5211d141896320f28b7ce33f3b1a72fe28bb676fbd1fe87e6dcff8862030102101f5476440b410fefc93393c3d7fe208bae75712818e3cca4ec8d31d13a806b08950e688ad6dfbbb46b670d0cd23670f6f210571482cd342ca5dd2051ad6592ca49839418cbc28e60aa9d64996a9f57a5f26a18810e40b5e1d8bceb6880811c4079a331bc051a1c0430afe430f41c9041b922148f307fba59eb0f336d4d007ec1d730be6de4e26046dc8adc8b9e34f618106f8c1ac9d9f20dde0b441908cdca5bcd2a83973227cd6ceabf0392d61925b6d798364323407820008110ba3a12054c10419df260b854dfdf3280a7832a50c856da60690cbf037404d7c350489fa80b597b9c7166e86950064d3c7b1fcf04f3237b1a1661420840c64337fd4fb1f12a2fe7dd274fc5013f8324d2b56c05289f2993c9b656a4a48f43c6d8c7be324f15f0404250a5b38fa03fd81b45553a41b9e95b6b9b50fc48186079e98658e4bd8fc9e4ec98c7c2298e3c0dda842644a3b584fd0df28fbf840d8c9536d7fcb64a625f5401056422ec7eba59cffb708a844d41c980d9d9ade4a601a926142d0a13682cfe563deaf3cd1a990c12afed94ec881436b9787dcf80e8f9e0943cb7f989e6e91b3417b73f3a022c58376d3e6f79cd034c373fe0ad218207463804fb3ce1c660a8340457a1e7eef1a761a5323754ef126c1627610c2acf7e8f3cb30d72b6098601145368e84d735a1b7bc59d66ddffa7444da43c199788f50c726e063261eedfac89b7d922bee2fbb439299eada27050631b3bc4d043339bfc03a925cb26721528be1c5ecead4c30e5cff72415a50f5c6ef1522fbccd3e92e53995c1ee50ed08d73f7c4567095254b1dda41b1d3197576fa1ae1e90c1fea31cdd99d7ca5c1e054d79efc23137fbeef780c52df6427a6ae167d36ede8f0dd5add15297808462e00dd61bb13035e4bc30900e7258b9b92d45d04df876284c96b24a3590eea49c480b9945f0c576d1523a0321831a4b2fe38969a31e8d43b65f45a2a18d7335631b36e841fa74c488ff18b7a667c2b902360bcf7359cf708338127073d7afe3744d498ba038ec32558b96da2045fcca1e69258ecc0f16a67d9881a273a6d9996912758b3093dd45ce23f34143a70b230be82c218f47dbdde407f3e3b7139d4942b00b3b1a865213e60639d59f58019d9461d1371d79c7166a625855305a8f55d375080dc812513adbbe060c3434de6a23558494b0d6bd1a731997a980b6b849bceb38278aac7b9c507ffb4b029fca3c6a319c407cf0f2c1a78c2b27ee4158421db92378ba283226ec7cd5094c53c462312b7b67c61f24dc9ac0aaac1ee73152c55c8449e834168996148934447e52c8031b6a460f9f7b7c64f50a857afe2c2160bf22b2a01d8cfdfb18d27e89d60440b553e82805c17845372f7de82aa3443dac0689516e3c5d395044663025afbeb0ce805c25cc2286f4e5e3f45e171176bb4ac6f638220ff6747174ebcba450ecf612a31e65008c2268ca6eeb4b5bcbcd222e47021258c6a1ff2a640253d0805229060f20c159319680a11f5342380d6d79282e053d1628b05f31595006ce58fdf0329e89a26fff466e00e521174c1e582251991007a4420aaa17c63a0e814e2904f4de1b36fb95158744fc6872b26ebf82c34914c2e40e46f8244cbb63341103e566c1f4229a4e654e46f2944db43e01fdea49a56d1a82757005bf5b0e89b4b0bd8a06e80b8b651980bb21c485ca09c3d05ac83368da401d114894487de05e5b265b044e8c600c4dc3aa8b780cd6a005f8e236ab97d186a6de86024e8562b39505a2bc6892ce1d8c1153a62d247a67d0e736529ad73e641efb8c657d5b5f9f4af2af0a46103bdcc30601bef2927e5a018a2cd57d0fdf1765d4e24ccf1475d04c79a0c8ed733f30edf7c6fe9bc4960fa46f173d3697b51c70f57ba4beeedde3d23ec6def9020d476f28efa54359156f45a4382ec50f75e78cd3855fbad68b1c581792db168104b8301464699eff657740b7ab251e792beabd273b3b36b46238a25e57d2f371344a6aba74f6d62397e8fcdee367e050d705fc7d8e35129bd1cc4ae8704ad28af6d61483b3f5da2cb7a7f8a9ef082a6070c941e8bcfe37c757bbbef62ef7f4e85b4b5b34f5972208b37d4f7da294f18c618247997a59b5c799e4e0dc59de2f4ecdfb54f7cf3dbdb5a21e636bd04808d6c1f9d5429366b2f04b9bb48f751a17f2f5abde166ac553785f20ca8955654664840f3b42be718094ee37ab358452a00b9ab963ca38d97e22574f3d44fb14b502149be617d4599e2591fbf08c40dd663c2cdaf6b3fd994d918fac35d28ffdf24069b7c04de060530756a8366fa86e3d7ee6b27df3639712c01e9a099013316e3d2aa396d36319ec68f7281549dc59ccf060c086316d8109c676e4e16526945f394b55932e2ad367a50d813e1736d348266c31d81349f3220f764c3be0439b163cd378c290018567556798003153a94b42fae2e2b978478983426383a1a919f70231f034a4a9d6e1dfa2ad212341c81e8fa6d2709a633cf1ef91884af304e32625cd2884af17a0119a79dbe9fbd04c719cfb990aa7b634bf8ba210bd33966442dc16ca5c453fe983cef32e7fc755c69900819036c1b14fcd601d235f632b36015cced74fbf974155b0f48d6936b578c1a322709b06a104a33f0a5402a5052944a31a628eca8f2451e10c022754b97f6421019940fcb8571222087555de0b37d29bb105167cfa01edd01a72103b1f4e0542f8178285f1e25b44427ea1e05186cf8383fb9744a708875cf0f957d115be2b8ed80c8ac0bd3e70ad8c9ef8b3a5002d3a3e1aacfe485935960c5f9089b1d713a7c168b45c2574942c3594307fd1fb76c488b4ca6285add44d16b5393cf241a1d1f89453988ca4354812935a2ea4ea988b89156afe5b8c43776021871c570cb91d56cebd2c6634a3662ee3d72634189e2a0cd01cf0134cd0bfb71fc8cb6ee0e071d905df1f4924c308919d2000e821f56bcc137999d0595fe6e420c36d2f2dc2ca18c4554dfc63ac9c634336fb4ab0b2e90a53c9a0f13b3eee2687db4d1cfedf41ce86ef510c0a2072a1ccb129b04a065f0c75fa20c6c1831f6fb4fc842d7a938d36a4220304015ef93608bc4a5254dcd14c1a4151064194eaa96495cfe50d929e780094890d01ba842136fe1c7122678331554b144e6455e55a85a423a8cc4195bad62f1d4a032e37e2222c2cd87511d0d5434382b668d125529fcb45ab65a7217a3002687d9def4803bdf96a30b9ff5f3ca0b370981f19eb04a672205a0142c7ef1ca2920591c381e180bc66113c1b16cac5c631c9d096e0a2728ddf52a261293c9f93f98c594455f9462e903b924b9b1cf67816a6b5dc2d97e2da4b93e5bde4032439c9624ff84e9f15d24e88361fa6a12c801690454afe9ffd0b5f9481b85b49aca1c8347d212c3da109b888037bd1346e2ef99b9fb957e6890aa2e2475a6e3c5aa437b4e71e578cc7c1d503e1235c1c0a03d0e01022ac9483379420d63f6722768a2452b76df61fa9d7d2274f7024de47792973aa70c95d88e324ab180a312f0494a5ceeab0a8afd59d4f6075ebd75c5ffb19c2f0d61d6dd0d6b114a14f628241afd541139caa791d9180f4a9a3e65478662e690b72961f048f421c8d6ac4cc41afdb26367cc4ba11daad0fdb03da2af3e1d08fd66bd9e027be07412d83a4595df6d66420490fa38825abfee9692758a1a16cbe4309ad2be768a14b81ca5468ae27d84c2ff092e985a7aaaa7ab41434aef2e3ea5eccccd6934fc858aee82d8e721aff226c73f0dcd7e81332c279bbe04cd93cc2daaa6bfe55748bb09c42eafd8ab4eba18ac9e304a29f0d466d9ccdcffa2e15ff91052ef1672dc106d42c422407849af4d5ee54daeff1a9ae281372c45091f9561a2d4b16b178ffa35e18e065795596cf0e26ad955e9c6ffb68c21f53224e65aabdb68c53129811a0f7723eb2be4d5c1dfa0fad7977599558c0de3cef846aa1d79f1fad380f24ac41459ff3295aa9c66c73e491e023ee94cf51ab1bb319ed52fced6a6b3c0c8fabfcbf1e03aec94560a5c731a9f170ff677fa093f40c5511c5cda7fe431b6c69249fa53763b041d17dfeeac4c5802fc7619bac7dc031ff3e102513a154e07af9a9568be31a46d1c3630a279cecf44a7a980dceb6339527d919b9133d336e6ebe1675657bdba5b34e41e253647d14d88e91be54cb788cc2990e505e41edddb1d50c8a36263a46cca7c374520f5394e0745ed917b3df7455e151dd48fa77c4cee7508a21da13d60e10c814b9d6c07f86938d29bdce3237e9e32136f888f5add29345a4c83d9d987ca0952b985a146738703a151d13f3a96b87e6ab5b7cf7bb695c6e47adb3d2e292944fadd1565f8832da31f4d7b4fbb77c66772a3a06dd58cc8489b80cb9ca1f0e6a85de32678eeb0a1a0e6dee6298b53a0c5fcb16e14bedf96f2331506b04b5f56633c246f887c8865431376da73437fa998482c37d30457a4d0106cd8d26d9e4aa69e6f252d76ef018a52a536eb1dd6c0790d9778ee66eecb0a3c22b835edfd45dea68713d782ee9cd2ae8e5e07b16d2faebb2f8a3f5ec975a8955dc18da4e981c59a7b4af10aeb73863f67b28d61d5fc7a133d9cf4dc0990763a1417401e5bfbb0e70f28ef03d64e69f96eebe736622e2051e13f042cfce780949bc745416ef744aa6afc7ef626c54564c685ccf3acace98df7908178ab695d31b439ae1e451f59e1c3bd493c595d41c0fe44e62c28544456c2f8130a50e08cff7f9a81de209a0211388d4c3376b7acfca81e4007c481efb18a513d8f673cdb2fbedf630909138f16de83d5e13b0002c8c96f426fa019a64bd3841ad68b643c04c29aed144a5bcdd1d9c90f48fda9e5c1d9bdcb8289a82ef030c17ba7d5f8e3d424af84155b91c46f62f0161cfd93c217aab73528e05cc44f4f040ede276848edea5885cb00a82a04bda7723257279508e3c6ea352297f104ac209d919df651fb503b1893637bee664b28a7052fdff1989ed4933b053fbebb6620b683409099562e2f1b465a5de4ed590ce9e6999386bb72b16c879109a8b3dfb9333e775fe871dcf9f28ad8873a7ec891100dba4c0345a62237e1184ddc09f7de4be5ec3272131825a79436bcf3d3b1615a33bbc7f6b9a231c10f45195fbea6a74a9361b8f88f0d29862c2953599d34b398c3b307e624930742874bce231cc1081628882758d1f8604d917777d51b1f02c2107185e83b0c78d56abfba36159fad6b2402f6abdd799d5795f3d28ba5f06430a1b1a5a7b528e58f17b58aa0ecc8e46450837433b9cc5155590b11dabe44474ccd634237f5e7af6662a63f0721ceaa00dc9516acb8d2ce386624c2f7e95290865f83c370e4a4dcf71f11882084952f23d0fc2a8246b3fd511d88880a67fbb2b1a2ac9ff5a0949cdc483a02e4e82b0115a10e7ea05ae66bbfe003042093820e3b4fbe78995be078509c39e82495c218214e359b7fdf3ac0e2edd1d78e8261086556fcd380afdab8db7ab68b8d706ede3b63672b00279a8e587939fef523c34b2fa241f35b9333ffc3515298d275d4476c8af99c982a6b1f33e6df0800a2557fbad70880352529805ce17d1aee619e8525a80c3ce53083e941f086ad029a01fbb4ba1aff4ff08ca46a5742cb8163d9ff4580869cd82a02f9ba50ae2dc3582552ceb01eede385356b976a20988a6e411b8299efc7ac2d184c699f813b2fe6781718aa061dc3830aac3c23bdd63206bb0d6f4dbf173de488270958357ee15c42176937db759ad5658ef025dc16579a1ed395e6857e621ba867ad6f80baa07b2c142c07220ad20322c1d5d04130061410f84eea025c7249d55b31a4456278cfa0bfcd192000c894d486c751f5c5a38c3980a963a85e5caf8d8dda584e219cf24b3f4d1feb70e96d581b1ae76086c61f7cc318c60677b4edd3b6233ca7c9f44d9130fddb060a8455b4b02248d217be42bfe89acd88f45e901e3772e97b5306f67b86e23e08236743f72811724328209c830bef2c67e5a161670a896f077b0cc26c734d5f4c65079053a6b13f7f9af0d11a91526ede448e1bfb25ffb6c60f8939de66265cb63f35448b079c4265f73c67002e5f5155b5625c7e566f8b6cb85d64da337b7133d688cd90845dd74334b63f7e4324dbc58fb9fa5500ef8f960e25908cb6cf504087b1ef893160679366e91b6357614d06809ea679183ad6f4947ec7430dd18b34a546cacf2c9b8c38906557af198759cf8b4a1bedf49997e42aab2c6027998177583d1fa8e279862ee5f4ddd124c935e30cc04066b9a03f20035c090349a041f225c8072ba64ee94925698243dfe38c077cfceb5a8c7ac1c889a92c897d4c7818c5eb04ee31133fc1e63db0d63253fe1702c108fd4214844ea4ba81248532f80b1c053256daef44af66dd1277e7a300a23b6be8a3a075649f2a715599e42d1771ff01c3c16fc480554a27e91ed51d7d1c2b470130e978326ed26f7da0fbf1b44aa92c599dc238ecd10622f71cf2628545cabe26d11687225b7cd5ec5d5b7ab8e1103699528066550e21a72a0e610e00dfef6685b524c378fbc21972314b62041eea24dc8211920d68d72b1f098f050f1993350040df11fb6ebb701801d9fb6dd9bc1dcebfe45a07b2a3f9ed35adce57171621aa02169e8f3da25e283395b60911dbb7724c9857f90bb22f60d187cda10e4df5ff14784b95bfafcb88a05f25338f09aaa5a6e834e635b89c0b5b5329d93164289da2bd2bbe0add838a5c4c97d0c99dbd4d7a86123fbe270f97d29f547a94b4e5186b2009862d5a804ee64043c3d56d131dc6d2d183d5faa87d6f0d1ab3881b730015e48fc47c1eb25b1a6eaaae0de8d200d373d1950b891612f69920cb76e260431d860315403117666803051b296f04041a78ef61f7b8421110171810ab45b066fae473b46ea0b6a92b7143a8dc891b71a8116d3c6953ac1a89de2a16ee2de119c72f4d4a69d0feb12d9a190406024440d7c67c8b27e77d1ff06fe37fd93250586d5cf870a2a32eb0c027a39d009d06e00aa1360d7442b9681585ae8af75a73a94515761d02e299f81a10fcc36b815f621e11216d88098a164d4a43bcf825667dbe89424675cf97ed7c8a7ab9d19518fb6f4320c64c375a2b6c3477926f763620d7c84c87f6bf695d4c81c1f584354f3f0f54299a7fb82c5d248353ca170482c44706e0a18914e04b86d8951c28dbea3c69123b11b04843a3117ca8f147f5623d9b288e169e39f9efafeba2f7d503619dc0ff0f559069170870d6b0eb6cfa0d6dc28809bc0a81f3599853beacf7a6b34143662c3610fd2161f22a91d7092a5e49d3e2ed9c1d03d7d02ecb608fa81d65f24ed578330a1805c7c80f14061e3e073234d441615dc4832cb470f41923f90a7d59530918a906215f21a59329726cc8cb41ea05ebf6897b222f2f7ae60503bca169a1d03505a7b07f8b1685f5de001171fc4f693e33abe8d53b21e9c9396d81a2050ed739a3e9e00f9174686a614bac3a350792ab63dfecf676b40f771cfb0e8b7eb13ba7794ed1758fd2d49b2ca22e9a93974895903099c32e05e70d811a889a02ba4fc3cb071971f637a962392bb40930a9cfaf242668156331945c33938e03cdac91c6a0e28e6142454d65b8af32c2755499e1534696d122f58b3663063b068340fa313c8e28c617ab0c93c0da33de9417a0edec2345f455fee34fa6c45b243fc557afa5de3b18310e328883a7c80f51e232fb189bc40d4c2d32b618acc37a26c738d7542cfbdd3b2618e987a064ccfeb64bb7f15e2dabb03db08860caeeb55ae9603f15b4e9aa76402d141e858d11be458e5de24ba37b76f86c9ec4e12c3ece33139ab53e2e84e1077eb53ea22b6164fddff2a4b123c8ba8df9b74e16b4a884c147fac86228fb02d11373c6d55bca68a03c0602ec4ef318e6a5020a81edd79f7ad1b0513510a9173cbeb84244b12fa2fef81118eabf4a25f5b14bff60a35e4a6bfe337d3f20a8d13b6e0a5315849df2131c46da73458180ed84a056f3815aff04ba4e31d227f02fa3ce423902c86cce0f12b37d67548b47cd81258993f2e097fa407f6c7d07518754a9ebc60a25c0c55b88e44e32fe2fd9aff60b8f1fd3b7a960afa0415ee2e2f05aa9012f957a7330243c1206da88fe374074d2d16d241d4e15a5c3e82346f7e6b2ad1f19654d6474949ac23c44a365bda5a375c960050b36c8882c261e750d0faaf240aa448793139cee2ffce3140954b8580f9f1fcc1c34285dd47a7c853b34ed9661a4e8b69d871a363cee1a73c26c40a6fadb7b46d5926046e78ad6d6685294b3c315c57c524843c4603100f9d2cfd932e4e56eca0abfba5a04275b91bc14a2837ff1174a3e6c25c91a8dd6f2e1a1a8ba3b8385fb019054849a3ed1cfa8d950d020c9d362d63acf2d86e2075228549dde1829c5484771910de1cca1d10644c6ebbbbab5c1d28947b5e8b070cefce70140517bb663553f57f31dcf668e8ed18c035b9a3403e2fc8cde8270e4fad67fd4efa662ab6764715ee848d029d7f9d8e3cc82e44aa60b22912c632823331c50588322d8bc2c84353f8b8168511f0505312aa224c4c88808a5214ed98476d342fbb601ccb107534818fbfdff06dc68c68d7de5f81bd438fd8e1e9109bb4d02c6bdd9b09ea7d57e3fc531f5de90c6c51f45c5c053ef3933eb641ca6852d76ede0cb6374740c68bb4e924d980314174058253aed44136975870e9e47815ff8efe2c9c698d335b6f3e6bc6af620698807130d0bef7e285eac9a345b0d22f616427a92344837a5f36d1a4203f4e6a95b0f149d4734664d40b3bc8178ae6613d691cda2fea350f7aa3c47bda918a34dd6e0a0938053af8d4b8aaaa6d3dc5a5c72afe222dacc6093d61186e9a08a3e47cf1ed0944838157e26903b6cd875b0a8d46196e80c99c98683796379cdcece88b8b1b527d5fc7ce08d8d4cf04d9f388724d90a3b9651f0107ce328541ea756f5f8d485463015f01113240dd9a5fb76aa7b20c00eea57e5027431f0caf0441b0efe561f2f0295a8fc502283a163e7f9f89b1faa545d8331368e63b574ef9b34b8681a059d84cb229e71eb6d8724a62907004639d35499ee29500c92dc882264e7bc54de52550310017cc0471d0995495d1e304425a35c40bf438d8c4f100e33c68ac1c4b0313e3ba7b6126bcffcc05c0339afc8f57ea580d9771923466c84b6b9221009e3496abe951503cdd27617b5a5252ff812f28b0463d751278c76890f2d80e095423023972894b648628ebd0df769e0dd46bb2c2a05d9fff1bec729d385f75fc21e42b79083f48ff94c311b4e971eee241f860a880bde517712c2eb0db04e12076d9c57e072b18274516c5efce4a19fee57cd3df43c23a71be23d30314939531e06219f465dbde2239087b195e8289c281c2e1e6cf437bfb1d4d1f248db02677ded7a16204a897174ed65f139594f9749a7a5f9162a504dfe073caf2f539e00da60dde4cedc6e985d89b3fcb06efa17ca4310dfe73719fe4512c1b5bde03e5aa97b52a2d82ed77a896413270fd839ea77fb02291aaed554d507bb2a4a3defba3b6cc8346f95bd47071ae907254d46232f1c76e7cc0c8aa14e8617ae252b6932617c41113b38560eb8fdf0e903fc1f3bb2f6c75e1c9da71baf130ab5187dcc8454cd05ead9a4f348e5c07daa202365a4219edd464cea63190143766e165278aa907715005acbee9f807f5dcc28f78c09556586b499aeb29e4435c48ffd9d70910b951c6390869e64eb856c29d6aa844381bf3aa1f7e529ad11489bb26bd7f30d7b96cf6e78cd42abfd51afb579a5a7f6173ea94d864451415a50eb3b80ed261c1b851718e098d528f79c5ba3e714728dc232802c6826428fc2f67f677779d5f65fab7168581485dc2931c25e643a15a7044528982670c9a0ec1a64d8ba4e7139de2635c4e5fa6d9f93842b545637b50541d37fa5b8cd565098fafb2b8fc9d048378e492cfdacca02dd9dc8cbdff7fd0479a0bc4ba5bfbf723b335ef980a24ac8233c69623d16bdec0ed2788833e39c4a366990493a6c3cbef9c7797d0752f4f2d45b8450fed43c123d46f2491aa4511f028c6ada14c1bfd288097bf51b07270c206c250ca26d2da031f40d225e28ea8ea1dd22a655bfde3a145c32e6c9e7a6792a3552277b53e78e5001a127c5a354051a2eed6c89cd80f7f90fbb2ef9a944c664689459ee7f3303fea99001e03508724b2c9e381a8535b9f7a0c1c86d72e871ae3e29b8d7c60159740f779f44cf21d6c43e6fc4ff0252964232f21f3ed20658ccfd5b8f21b549ee1ab3dd4d93802be3137bdd470ae550a512e172087bc36af3df8059c76ce4d746b2c4e967df92a74316f6414241027010881dc5ff102a112a840f3525486094a4ac9b5f6acfb90167803aacedbaaed86264440c96c939808c60046800b1ff00461bb3894fa64858b52145c02a70f596f5ef7ef88008e199c5e3ec4593a87409ecc3ce2a10e77a04672047a5adc659b58428735c60e5ceb99e8c53a73c5c739dd0ff5cc590673c27f18299752342c9af5665c7bfbe96d081c2010335f02c92dd452cbcd24a8b8cc8c7fb94a37dc33dbe34c882bd581b9f1e7dfa1d66d282592d9abd4dd0387667dbdd24574d7195a0a513fb27e25c1737a63badb8b9f695935e1f277cc1f36cd87ac1cb2cf51110e4bb64f49f4aa33820b527ae0598f333e97e552496b6d3017b002936aada32f767029f926a51ab5a5d199d2213ebbc6101ca4c34b4558035240297ffdb2089cd19a883c0054d44077c7dc8591994fade51b63e6d609b7391f52de9f98c25119a97f0cffb90c1e63ee50f5a7f2343f2989da8e3f74498b63d7fb0bb01d9c4f2dc3c4089d5308d9dc9523a3cf1fb6b2b0c4aaf54877099a1f298e99347258a981fecfb76e91ed6da52af53facd84af2f065ccca7bc0126c7c56eccecdbc683c4adb178c2ee0c79a8b99778cccdf71aa19d099c141fd67c0fcd9dec72ca1b35b036e1324dc18b6b02eb717036eca75341f2c92c8fd9bf516b66059cd0c49c6bb0a679c944952a156b2c32a046714fd2021f39668d124043611aad8fce6725ba0a59fc2c5f4e5db883e4a90db641a919bc1f91bf31891c4a7d60da8b017b2ddf93b28fad247fd36d4879bf8c68b9c27d806fbcdcbfc93646b23126f52008e7f3ec77ebb41b5fafd608b981cbb31a170651f847b9aa9e4570db94898ebedb88111114261aa55641a9fe448d2120a1ad86cea0f724f6990987d57feb62302c920d38a303da07112efd8ec20c3ba65a1053b916087f9062c3075fa615ba9ba5725d37f7dba1878303dd8b60f0eb1f6854852dc6619e909a908e06daf93829e20aa482c549c6cbe8e822e3b8422b6b343a6299fd43eec4071367ef4eb425039d276847e982435f686b63a85f90eb848da8cde0713dad15915451efbd1b188ea6de10fc0465e65bf05afa29fc9536dfddc4e51def0116ab55206f0de0c188444b9d9b1bf3849c0cdf80e5f6dfd5f21766988171e33039eae219714b993fd2a1d0a70abadeecc6e3713833219f5c9318f111806eed2d046c07511fd13dcd13f371d9f23b33e2c3cabadae39b61a9ca45f5b4da1e4ce7ee12cca3e2e9b99fceb4680d5b11b5c08033251281b7158dfdfe3643cb2f366060afdd85d129fc7068883f8da162a14d87fc50000ba1a07943b1859faa0df7eaf5e1d36ea2ebead8b3204b7449b827e9fadcf3fc23e6d8e9c07c4ee28677ce667d4b64bf95d037c0248d846afa0ce90170a655b28ae79438e3060636e0589c337cf95be87b1dd8448dea50f0aeb9a74c0830e4bcb4441866160b58c428dbab1586c602de98de06a29ba05f32eed37401b6acd0f2940776ce28bca5c09276354bd773257aa8651a5c72e078166a6a01ab2cd428668abbc1e09fbcc1970aeafd1550410bcd040308d710ed06dcaaf8c764ffad5edcf7b350da15b0ea05558dd408512c5d51e2293fcbad41182d8c9cfa64df83ff59faec4a6c93786741094ea8aec04e949494c309fd81603bb42214018a947bbfb2c2f3315e69f7c65c7793c57267842dc1fa0dfd4986e2e4cb36a9174b7fe306185e1ddc9c90e1b03b54f10931b9588a97aa55ad87d1979c5e88a76c66e9763a66cb3af633426b447e59d282bb2d62e8f3ea4ee19eb5428caeac8d177095119b76d51eaa7f14ea295b01471f62089236bf7d31da69482659a9af88d64b52b265d76196eb17873ccf26a8597510260399c63cbe7bf56d668da3a77a310cde367380c52a5664f74de1b6a66d86f52adf5c6137c4ff496d1c2c521d5a7437b72db94ee11dd22b2f14118ee57d0f0f61a0debbb70c1d761aa31e1ff439b481079ce826dc7a9e99c45534ba7e0eabc564ccb8ac4f22f47acb9681a63d6fd197b3a966343ff732b1f59be18afcfeae36d6045cd789b2c98f9fc1617becb9f237170d3c900286a69c0cc8be9eda5c0ca535c93596e380bc4dc53865fdc0ec5f3672cf64845e6bdaa316be5fc4ca36b03b8f617aef86863b570156e37c098da5271792ca0eae8c04ac085911e1aab07aeca0c513e6bfe47fa7990059be81c05f632ded3b00200d7d70f07279893301bf90d87b0fe22dcca32b76cf64f706d2a81a53ebaea9e13b5f7b01090d2c69640d00a82b034edcf54c3681de82d070ab6cc1977635b6d699a574b4f103a839e2f90d63b6f69f25f6a383c67ca43f440347d8e65ed4bb7fb10a3569c1c42d9413a3b81f51ca94467949a23eab43cc19b52446b10acf41a69de994ea911b093c0f7bb88bac6908174c1672080ba58f0e236ffda256778999b7aa22560b384b49379aa4d681f27acfb8c138e13926c6ac9d9bfbba3bf7636540a41a478049601c5aacb9c9996e07c5793a8288f21a0951b86952b623721376658db49f96f8ae149994ad59712ece0a159a2a7e82634d54f3309481a69dad80b9d0d78de2faa023c307d850aa962dd873fa92830d704058c1b95cc9dd75e7b26b61afb8812a33854793164b0f48b225e96ad7c7b1754fd0e1c221dc4640e34881df7fad042a4d0e14f59b9ceef70d821a06bb9222cf96ab1eedbac8154e177be18ff68119cc11f53ee623375e04a41d7dddd2fbed7fb07a095b189f26bd1839381da0c7199d2d547f1d269d411871eceee945684000902bd3395a05b68d898bd43789694b5cf4bcdb2e000ee234d40d445aa23e703e5ac9d467f63ad6e5e8634d3e68cd60ab82f9e9c5ee61a309bcb9efd2fc2eba5ab18d281990e6f2371251438b82e24946f839f43fd646567e7d54d6e0a1b42d62ae36218f744a4c997fc235677c7365888bdcd8e03e5895e766232226c0e0e0a74b81f9d91d10d7e2c240b2da43cabd1a1a3b66a7530f6be0aa974b690e5a0ae3b0c8fb64bea2a3bcca7b7db43b407724e1b8aa403a3c5dea915ee523805459e537e274fb5bebbd3999aa63d4d6aad1b9dd6613c6e9ba7f8c4d07a463f13be128f55e5a31e500daa996e864ed4b3aedfec42e08313cfb9468195764e4b33759cc8af0aad7d1f116737a8278feb992827e5516ada32c4875cd88f30b57c454a35a69ad983c09bd00c25086cc19281160e517cf0d42d33a676e74cdd94d546f7c4eee9f6cb3d31e106787535f6b6d9dffd7e66027743c099006bfc9fcca79bb273a202be7cdcd92c1a60f7fcf7a118fdf481dc6cf888580c0f907d81935305e9307bd184203ea4129592d87ac8985410262ef9c4608d411bdc0844c05292679888ae79e936f813a499bf0ce65f54754d30d2cfbdd75d0c13da801d5419f80ecd99205ea61b6a2faf376c2c330a419a47dfce2ce3ec7f44b92dc6e3b0222010a2608d3f9547760216c7a0f3f93ae2bc690fd6ede99079d943ed7366a3b89b90775a3ea649bf88c5bd0fad30c48878381daa8fe5172228572fca3b21f73c848b8c928a7299c351050c675982670859919234c8fe9e8329ea113bfec13af6db0ef050ae099ac2d8b43d85519cdaae0664996a1c2a200f430efc48d34065e23659266242ec13c934af8a90370d637aabfb9b99973a9c87ac8f2642e48e833e341dc2b4d816ff5cbb54ab4ab4f11953db5ba68b1ac82dde5fb8379894b98cef0c042cd4f08014d9080413ff332bfa6d6e827ea36d1829f2802544b45e87d007744e5fae8c367644f638b34256b7a8a976582334ace912df39d347ac9005c4767474773054ec5a5464292df8c70ea6a3778ab3c80a0894141ae79bb6c549c92379950e903ec3995f7310455cac810e15d566264470a9a8c65083a6482848b808b78f05e0069ab43a47a63ad1828db0e21364e1d8d08295220bc29251da9f062642d6d992db09052b78edefbe4d53a0f3dcade771ac90d3c1dd3ac330952d350e9cba938a281824174112e7483ed0d7ff82b6656d318ad9e28d8e5a666c0e612c45eaf2863fe9c1eb0a21d14dde96c6c5bf7af052787d87a15980712943dc51c7f45acb3e75e77180a6ffa1eaba37b96d9a73415aaab86770d83dc89e3ceda0b3e39b74ba1da4e788ac56c83a3a7ebf4082a4b5b6b51e9b05378fbf8a9d08320ac1e08f10a6e9ea12052bf5e16e886c4123f0feb01311b9eb40fecc805ab3c8660b7ddd9201e0b475b2564126fcacabba6e9c291357895f519f58facf15103d213300076bc59bc5235c359ce5d01d4db8c44a480662ce53ff85c8d3c626254e5399305367e734d20078894c07e405ab76f2089dbe739f89b95fa56bc5c555a9b537e780d874d8af186ec13f83a52c19cc89d87e3332357a315f1d385f1e78c59b6b79011ea9a7efe0b420469cdbe202094eb56e9ea0bdf54279d3003ae507bdf530f74211c501eb8b0543be62eb3786c073df689a4727b94c2ae885d1d9c44fb293743bd0892743917b7a5833d8111362d5124a7c92401ebed79dfc48c15c00e5f2b2b648576b34da413d806c21542790ddeee0d93eb1573b4e15c71fe2127584b5c541b23aee281c7d1c1c555ad1a2167ba3d67b46cba4ce817837ecec17425aa7f5756eda37f0fa8ece9a210a3020af265fa4245e26611727bac12abc27f871e498b8cff6471db3cafeaa3e93b89c484d195cd2ab3abbcedebc3288b78e0dc0b557a54439e41431345a77ca0c870073d8d5bb291070510612443dc16f5a0b9f56be4e4c50517cc4b9a68809b715187eb6d5c4362a9cc044b1b70142758d08db030485bc3cf139de78a1d913ca5838e61bf1673f620c8d33dce32478e398a763378b53a921923bfb730cc64d789cdd2a93cd4526d1efe3de2b859d3140b0837aee2ef3d97784a78c706d14d217855d526ead386224b0981cc63093670876f10598100c8953aab65757b866ab45276230332e67fbf3d7f2b1e6ed94f17709f1acbfd8ee1814601dff8ac53a775ac4e0ab03922c6ace478e8d581db71204ef040db75101320f7da711033615cc07641d2aaaedf4e3461c5e9b4238ab462e9db8d5e205d0d7f4b42353039f26887d78feefc4fc309a4900064298c06bd100b15193c7a384e8c130c75b2e8b851f400f0b9e57070ab95086a7b267e1efde3550aa4e304fe0f08085d5840ef4da05f39bd5b1ac78be5af7a4e5f1c022c3c3f73e770a5c1e900cce95111026525214e422fd67ab5391c609069058cdbcb1add3e5a62bcf28e128a8d7fe9fdec9d42941a4c73d4cc40e3af0601edc08b896f257a103abad7557c95b0691955e939539391126c7fedd7a4171cc4639faef1018621b2d705f5a893a1df9e5b973b9f86dbd89350affee49a69a967fabd72f0ecdd3093d01e2cb7fb58b8dacf8f5de3c86f639e2efda23a5c1f0adc89e948d2c8e5166e71e0ba1491091e609d7e1507005fa4c770f39b794be287d021868ba4113f9df81bc181e2acfcbdf438c36b4fb93a9d07e901f8f8bce3873af6705740770a6dc8c23ce6fde0dda0d913a7a3146fa25fe0c65bc33a69fb045b1572f3ad619ca325d5e1a845417e6a22c97e49ff8d1564b1bb768d9d769bec8de3ae1389f1114c43511608213aaeb562cfc4deed80e06625ccf49e2226b8c79093e87c166a959960a4440712fa54c44f4cfdfe1dfc7938e87d96aca95cb6d303b324b87e7fb0a85c5d6e3920b26978b97a0b4588558c6c5a2be09846aedf2fda6af07f79acd31158c3049fdaace187aaaba133264b95097c3a24699c6e0101802bc2b63c0526283caf27e6bab86d5aeb0d48f3c6f1d7869bcd708f9502341ee7560e4faec691dbaee8ccf11b0d09bb12b36ce28f2adae55cdca6f5149bd70fbcbbd35c0c1374317579df146ad2acb8936e0f769caf132c8c78bc4cc6ff50eb39bcd0d39985f627c673a34d66823300dd258b5473425d29c62cec9642f54ab697c9032351fbd7f79ac88c24d252d4bfaddc8f2cd3e5ef6525dbb7340d5ba884bc17151e82d3f63cd72be0b2066722712ed2b5f4f6a5e79ef6fc3a929d2721b603ebd5dcd855d20278595af1771e17b4f8c5b337820b54648c903092816602a9b90f845301ed6eddb59737c546503c7fa49f400b179a799d505769c2fa613a9e7da7e8af22a00b50835bc9b12add1cd9a4e98c34ce81818c3e2a9067dd2a2e2e9aa5096a04bdc066071c0d34688c096d184d0eda09c833950c8311562e319637c41e6a2c5ff0159b54fc2eef1162a8c41aa949d913355870ad62540dc951fbd61668d78e877eef1635b20ad9d9d400d0f3f0f850c1c17bcf1c51233120984e30722f382cf1385e4bee98470dee41367849307498140b826be0478375258061d35a26681f2571991d778dd573605100c3bb8e0a45a25da44661cb058714b0ee8bbc8ca893316a66db5b0668c99aebdcf4921a449d8b7dd84f439eb28dd2e09916439dd440271579a20b22496e959114606ae4a99579968a1394745e3e06214452635590a72c2b8ebeeacf24f476481ee154e254a684c035e56e677d14d94ad1af193cb9c55953144130111f78ccc85c3563a1e66046d0b841a2dba2f45ed449e0a984f135b92c296fa202b8748e490a7d8a2dfd7eb1de23d5b504b8c5bff1f55faea365dbfbdecb0c23b3e18223ef4fd545bba2c76b33cddb49c4208ee72ab635ec4293ba5e35c7902299b9f99059c629e8c2cd55a7c01074f92d732352f1004ce18e8153871300f98004f7473344b78c6eadda89736e42c5dc976fa4d12247e08b864891d5152496c809c24b43a5e203a38d21fa4c40643f31a6ceea7748881875a75a674d00539d1d30a4325d6ee371ce44c56ed5fa774c73de7065e2806ac9b0fa6137e8ea179efcab8fbf45ea43adfc513ffebbdd90fd8e89419a36c700b7bf3c5fc404e02cd9f87b866c8bc625a4498f6402a7a92c7c1dbd4728dd6e865a67c6471f9a1dc8f922b9f85d9165f9ec858b4ec99ac9abeda5f3662a450dde529a4e55016495eaba5c28e94c91c6d49ee639cf303eb22abecb11d98beb1162b5bcb63e9189435512908f13d999aadd37dac6601e429e6d93de6d8335756bcf0cf1c25835bbdfeb6f65a4c6dec2a91e547d81177a015cd3b5adfccbf29e8e845709d20d4d490cff9bfc5e868bda817eeb958d3ffa64f23ef2f3694d524fa47453b3c5d992132abb8c7597012e459dbbe7162ccb573abcc67116a2bd79c21d8906a2ea19081bb7faf1ef7deee9dc549598e0c156d2311c9312243d297ee9f7f0097b0f368b25ccb8e0db4ce814a711e11acd59afbf89692a04c251aebaa3a22ad0e9a3518697ce2ec201539c584ac62bb8a08e29681b2f500bece1c530a6475cd618a6f8e4daab41e989f87e6bcfdda0f134c2ba9ee5e9927fd27bae5a4eadb24601647fe895c948af159ae511c2f1b90720f7b4714edb3304a9d818e7a9c212ac31c7eb1fc4f6cf5afbb9be051ffd291a07e47d82f527f583e0c204c955e30002e44419e12e7d17e403055ab14b8270130d2ec896e77d5d49862f6bba9c48274c3065a1e987d3c08bf000b5b9bbdb008cb4e5c1b517d53b21aa27b13eb2ea5558ad16e520b70aff1851d4f6ded7fc250610ef89a07f9cc9821447ec7883421c81581a11444484d0ae4d229671150259da9e2fb37a852d73dd97c13d034ba4530a73d71626b80c65dfdc581afcaabc12c75c86520da935b165256a2a5a590e99c1847ee795633c8ad12db9f4f56c17a0934c4e81f66090bc941f90f576a80097b1b4f49ab3471dd19ce53e5a43a9bb37cb816f5221f2da3f2c7ac10632c62fa43654203986468da6a7ae723476126aac9da36f5cf8a3bb33134c15d157a441b95f90fd96349d622d432c578e4c2551f88f864a87b344db61064ce0eb96d8f8d0af6cb9a80107441e7f6304440fa258aea5f9c1017df3c6f0ae655e961fa58a73b51a843145168a03b11cf3906d9167c02e6c0e10c1f0d6f37244895a31ef46fad2880582deb03b929d5b7e4a1d86292bbdc7e3266aebad774f6fddeadb4bc6d3f80fe05fdfed9a250e2385f7ad45e99cebe118c6a0c004c87d581b0e6bb364509b550e7b40ceb73e1a66f728b89be91db4c89c7b2f14616c9cfb144eabfc00a6cd10685774f062fe54609e833cb717b0ee7f5f817bae0dc1192f38fb44cfb11f85771e09c259eb3cc7a162788754fe98b87d96240cc2eb37446d6594d038bc7a13691bc329e3e1d51b216893337437848757572491ad4f59825aa6754e02aeda5bbd1627b0b25117045b036a7bce7f5e40125a690e134de811b4ac5efd2b433d7c3055fc0bc84cc96edf931e7651d97f8e55e41883526fbd359c22994284e610e0dac69940bb33f467a0bccf03c3de8ad8b931da21f867a283eab2c936602269adb5d65a68c9cade22b794324919f4079f087c072d0fd038a5b4d66aada5c12e0f8f0ff99092d2f491b2e846cb0334b7e581fab3e563277bd21dbae3484873c81c324aa21b2d0f68ee00377b979892d64eceb9b593736eede49c5b3adcd0380568dabab16d1bdeb1c3068a2d90c32ac3016451052c109b58011426284932e7f499411699109198d0027d9ab3484e25b29473c264d3e443101b01ac04838007a11150522110c4233083d408d83b95500810000742587a6d31841f9c044161831dfc70c3111dd881120c016ccb1b1f37008104c90f215c604326d104104000822792bce0061b32c8964ebc51464aa54fa68fc6a8421ce2aca1c3183ef1c5044a1ba7138dc14d4ca68f61d6483591d1a347195b469e4c388553168845c2f0cb784f5fc68ba4d13dfdf1bfa73fde1aa9a7f4a78439fdeae96b1aa37afa5688c67ca12db24c668d54688f58a1a42d6b8550a1942e4cb6fdac02a58dd5aac8daa2ad52bd3dd2de6a3fba35fd8c9ba4687f057eb4655f054a2d5d6cf5428984ce1648ba5819e962efcfbe1cb881a3cca8daf6332cb745d25e7d90650cec545b416663199b86f7e38beac75fbd94354eafbd9430ddffd3985528435bb3c6296c61ceb0af0a5fda48a564688cde7c43a1be8559a365ceb06f33b9844ee994d2db71267073516b6207b4266adf81528a299003b51dedd6b6f05699c7aceddce698f002135e5047b82f658d3a67d4bf2f5e4a9811a458bfe5a584697177f12630f50f92f02018e34700593e044bf83a6b0d29d6af8f554f5f8e286cd46fd40740c2a05ea431e1a3fe45d2d01ef51f8d91a89fa8c734067cd46b1af3af5285d8452a1a693cc2d76ff999409286f675db3812fedf478c07bbd75cf7fa159001d9aabf658b260352acbfa5583f5bac5b7918bff2526e1821b645230d822ca82fe16d6c3cc8820a595ef807bfe61f7cd483210f23a8b006fc12421e311e7c1e24601a53c2d7d7928626658d185f6bbd21fa40abfe7bd4af7ebaf8995af9c0224af8155844f8aa121e15c6f84ac2d7c74163c0afef028df9af7873cfbdfc91eeeea5fccef3f4cb9670a45b3f0b88828ba88f316b0440ce407d45fd299c5f0228d216ea5d802fd205f562658523de31404c5ba8270194e2aa7e0d5538d68d922ea8d08ebe4daf85be2d65d79d6c8ff3270b410d7c912efe20f8a0a4d917803d855a3c1b99608a061b8d7337e6cfd609bc2a27edb6c8541421044545d8219bd1dfeb2d898a60030e53b4982204144c319a62059dd1df4fa9c11212a3d5caa52782829062882194349101c52008cfa70ad20d98c0614a9316bc6ca916206019ad16004c68bc5425690acef43997a424ed948bef340a295288bc6ca916293430a18714294432294a60a0c4446b41c5bdda8cfe5e6989c65ffa74ea1eb2b040a9cf3925f589d9aeca3a6b485958f0af4fe361a5de0116eafbd3541bf7ea6f43e7062ccdad214b0df6f61580d9592ba5944e893b1afa44a699b4098bbd34d45467997fb62e71c7d19085c6432c5d58f0b74f411ab7f6a99dbe8257392d9d93dec7bee63e76312b2f9d136716d3fbd82be06299d5389a4c6d46e7d4682852916595ad519a1986dc46391bd6ea9827634fef0cf40d47fbc38277f677b454d0b896539401d51c230a84798cee90f3286dc77cb6e34ca8ee20114dcf1e890ab9bb533c64fb534a6925aad5de1b8b613f5bac81ce0c7077c6625958d03e7b1a2d2cdafe99f5d94ead2dda1e8ed85751bad45003445b87fdd96e93cd4c29cb18a32674b51fcd475b818664ce707ac7960f12ed675f0d8986a462f85a7cabdc31210a3d6a3c3c9a9075ea826c3f7b629116ab9836d093d20741e92231fcf76bf05f7c294763e7942e12bbb406ff7d05e0502694a1ad4b31e568dc7bc1dc0c62d10cb30a4793edc49b056bc55c16d2d8f00a6ccb5ab67dfe1aedb32d6b19368b8a6a4ecda14014c8c8c8799c670ecd21a9340fe075a7ee6cf9eeeeee543ea594521aac22ed29bfd65a69acf5217bcab7d65626533e86512653fe0d45265a6eacd6dadaf3c7d6d6b20e7f7d1a1cb2e08d85e3b8efdbfa63569f060bad8ff50959c20ad4700518fb8662b8028c6d4351ba5cdf6a9052440a12294494922831512aa28444498812112d969474b478a2459216544ce999023445670a0aa62099b204869f1baeb8018a901e10d8c0b2406a82075390a007af259620dac418d45fb6548bd2d166d992480a0e538aec31861090ef7bd1891b2411e188fa0cf238378b02a40dfb3ca4908d0cd4cf807d53dd527481b6276c008c73ca64ac02236c175bc2acbeedac550176be78a31c0318e07a62c7d81e0935dc2fd8d9534d49b7ad557bad9d314aa9cf39e79458eef0cae799654a16dad1b05deeec8d73833bdbed05778365621c8b0c5db072d748211b7575552a550a8542a150a7d504553b9f5c984c26ed799ee775a7adc3d2a5cb388edb72ce396bddd6f94f10dc196b3e25f6b00659e8156829f4a889d0494e58adbfc75b1265e1430ca6d34aadc4e4fb01dc00b24a15df9686d5f79532b3dd7c624685a413e0ec5e9c444417e3063fdcab82149af9927ac058c33809488e7183a41ab21a454a467fd0e30703fd7d515a90a402cd87a413241191f443520f4940926a8822e54994a4284ba23489d28289b12082d6ee0d42ce1c0b27f8fb25302c094bb120c1ad181644b20c8b9e2842a2188942444b92356fc337534d4529a5f7627c2fc698f6f2620f638c7196590bcacc7a9af779fb1b5e6c70ef7dbef75ef0eeb9318b3186b1b097d57b47f85eb4b0b8800163e5c31dbe60d6427cb346428a65e563ad5cb850a5b20e63b6e3bcbd75315027934ea5bc2eebaed7715bc771f866d904f102d8b91f59662fdc868b10d100c5300cc3b070749e6dbf173b22e7f19e25db7ec721d9f6373fb2ed632f42440318cd71226742719692fcc89b64605728a6fb32d098e9f3227e1269d7baad0c8fb634663e79e9c251c4d97669dbdfc2f19b3e139bf7de7bb3ccce811ffb5187cc68ab11940a0509354510e4ecda816b072f203a3db95554545454545454f40292c50bc8931c2469f202c2e405c4a7891710a021c02a905e407ebc38e0e405c407901b1e2cd963dd1e843d621d243a4f9a5ce15214b10327e59c3a43747ee674771d9ea457101c27484b9a000d1139d2819d214baf0ee818bd72507ae5c083254d8ebc7248d253e49503ce2b07212f9d9c97ced24b270a510d327d8ce5bc773a8639b596d65aaba5d462ee58ab0abdc27c297f8a2fd24582ee7352a218620f312e1bfe28e050244612710087edafc20b879eed5f23670683edafb325dba798db69b5d8bd18bb17e32cbb38bb18675ade382ee76de3ba6edb38aef33c8eebba79675602295e3e9c78f9087af930c1cb47113ff0f0f2b163a4f4c4c90bc705d888944378e51ce9e09543035b6b0c5a20f4ca017ae594e095a3f3ca11e295d383f4cab90922e50b87e8c7107e30f921841f497e3cf901f4e3c80f213f4af08388d28f9c1b8a8c766a665a96016d7fca641bd2b2a6dd2064fb5b138cf8750311a4d70d1e78f970f2ba01c8eb869b978f9b978f1a5c40ca0a6444797c48e9072f0e08b58052263e23fa2b02c311811240383da080934396b77ce1d4b0c30edcfdbe175f360ce1872a96bc6c207ad9e082970d412fd0d8919f8ab77cd9f003295ac8132f1b82bc6cc8b9dd962f1b8420c292ee496cf9f547d238c91bfca088273a90309388c989a4a18dd00f4c2da8902d9fea20616612e59134bc974f77248d6e6ee12e7025ee8484f1243e2469702fdf85248dcd6120c587a42148182a4449d2c8489286267d20451a8484a14fa6cf954b4891be2061ea1117617001c6fe90a2c5e9b11a5a452145faaa24544aaa22a474a0f2511d91d2215594aa02e5442a2705e4340414104a05a720ec0f09730a8214711ce664b44fab1e9bbe6935b48a42ab92d8f43d9592aa085d4a079b3ea7f2511db1a574d8f473aa285585867262d3cf52392920f734844d1fa380502ac0c61515a7205645d285fec9c86156af531024cc8a89acd6be20e230a6252629a6d8542fe92a52a46ff2a18b6824b4125e13cfb3c2d3f182783c7448ba263a284ef206269314a625a6297415bd64f2a191d045b4125e0ebc269e155e104fc7e3a16ba243d24181612d33c961b823ee8a2cb81e0e0829d2e788d8966cdb14792957d97ce4221989ac849e454c22d388d7715770475c161c105c0f47c426c5b6649b2257c94b9b8f8c442e9295b8b7822c9ec461b4265a0eacd854d3d1824891bec64386246b428af43328f011be0267d14d2d66d2dc82d372a035d1acd082683a1a0f591319920c0a7c053ec25960ec4288c3e01e0c0426e22eb952dc29b025accaf5b1b9128741d6301015f76022ae1477c99d02ab822d5d1f5916aed0270e8315c19068920329d2b742a33e5021540419860456045362d39738074d426b8583dfcef588c3581d17e9db20a71e1747cbc3a53e9030f2e91321e0221b24b43a33b43c6cfa78cb9275ead994c5235de8d72524ab67d3677d408a54c95e1131b122922e49f4c81a42c41ab2e99f7a569b85a4e70c2746b409382448c20700411881136d420d3a06da0aad54b5fefe55c314af1a960091eb8f13bae342487308917c9b3d8d9cf2501faa4a76fa78c6c428f40cc528b418851ef1967362405e3d7ed0638915489ad4578f9f8b61455e3d745e3d46f0ead14325c9eb86caeba6c946a4654d23a2c264fbdba011bf6e5e37af9b2a4aaf9bd74d952b7654f141152aaac8a0ca0baa34510505558c54e1a9e253e5a6ca0e95252a465494a83ca1348522737aad24b0b605155c9411a325e5c2e755c3ceabc7d2088808f1aa6167c89c73ce578fa5570d3af2d54369a7bc2b744d41e091906942029926364cf460e9074676a28860c7c655a2879b2484e8514568e67bf1550392570d525e355c9124cb39dd29add55a0c937fe528630321e3b7f6f18f22d7695bb61d97ed962fb6f76d0e37607f32a7556449b14dcb3fbe7c2829fa8f55bb38bdc742d15112e604f989277cc78512db326d4eedb517e9625f0365e0af14e3db6d6e9f6429a355421bafec10a5e1a8c3fed89fec76686a1fed83b1d6dff77f43eab327f62b34f0632aa13d6a52b6cb2b1ee9fb33f4d2af1390c9f41d0929fbdac7a176224ea18730d6fafbfe87ecd0f67094d937a49876b2fd2d5deddc2a660e502793b641bc8edbb296cd3a844791294dc91160bc686171b1f2b156aa14ea64d25ec76d59c3b933a04204c7164932323b98e1a801a2237bdca4fc6d5a2113c3041c4829820f18f163831b1ea0a00606f0c1c80e109038d941164234c0a513295388000640a8c0718288228a5e472110c293251c2065035e74989c20498f201e6c900181440bb0c0a28810f430a3903206179000003cf0987892399dd2235b10a140d0e9be34c524f4c4342665855d42cbcfd8d642011d0116504c00b2c5ce16374520a2c58314b249971d9ca3b13737f5595680b169e84843cce9943a0982bb918115acb562a225a23d6e3babb804598e301d875b04c8bd4917e95ebdd73c5bf6c8e7640dcb3367603d7306e599337a660dad47e3e9e9d9f47e1ec10eb143ac53ea1a922156884d5beae7a226347e30b6e59935b42273067d4a9f88f5b146c819d41eb1a98776888b3dd41bd28a50ad04986b4016f480a40b05ba9827e409d1180d4803dad4135a21db1a908b542ba244919f234b606d68d31fed102d8974a18f819a9086c4450d0a39836a299033e853259b6a2a902ef4721a9074a13f04c808394423a2f9481a563b62d3cf197d8da73e9d95ce9e7bc30a62bb5b1dd0636d05e1a4bf4794958a20842326245987e9b4b62c862d60b2a468b598ccbd26ece877916ed452706ecad18814a82d18eb68e8efb408124368a13d08fdd20ec48cfe7ea9674967a9880e6e277fbefc1f220b4416f8e7e9e0dcd9bb1172c8b4d18e71983f2b9b29eb72a6f7e6f5c27b7192c77a4ecf7bace930c7fcd10bf1e94d7fbb2daba8f3f03c0fe55d5003c7ba35ad35efbeea671ce5bd4a857a0d158e3bbcfb56fe62bd027e59c7fda843f35e357752286a7afc59eb7e9eb4d0393a9944049da37bbbe7097f97655e38daec8d9bfa4f60f71c37c399e33037f4242e66d9e6cd1d9e223d3b42b4cb6d5c98fd04b7ed66f3a74a2df4dc630a9bbe9c2ae068a166c353f4f8183f46849f21ce99c7bcd9311d68ceb038db7c2d0b94e00e0f9a429a6e28f784641963f49b4dabc432939a942fddcb7900d3ea538f4271a9540a8542a1b84fa552cfa154618efb2bd38fa97087efcbfad5497b2f1e85f2501eca4379a89fa128ef438163dd28ef55aa30c7fc173fe31f386e60e748fd8b7f0143f52855ea3d6eb64c96e962aeccc97a0e463856c066a7fe056a0a519670d42ec2f15b09c7ffc2515485e3cbeab9cf2870058e27d6676c6b2911d9340b9de3f496c5fdc9cbd97198940e8fe9e909543d0b3cbd099ccf81a65538aac2d34f204f3f957bfea0ead19c61fa13680abdbfa0fe19aaa7e7bd78865e3e00edec5b076ef8e8b6948e6e386f1c668635c9451d32ecc0000f9c96143d4aa2711ae062ad2cf4bce18cc16166386170182e1c6db6f7109080c3744fffb583903d4ea1712ecd25af1d7436fd99a442f142aeebae1db9b002361fbddf77b32509362d53379973a220caf8185f8b4ecd1cdab27f438a0dd9d65a6badb556ba7cb4cb478f52480b5f44f1fffba6918bd67fbe17fbf2b32f13afff28b968a710d6df8b2fef6281b6f6676866dd68db99e3f950ea52a2310b586bad9d93db723767e5b6dccd39ad5b241650f0167d0e9cc9ae26f4f8e25a81a63fbe6419285bf64dc160217dc17a63e9add91af59d608caf76aea06dab6dfa3354a38f5f36cd9cfb40bed9338b89fd9c73ce392b14dfe305b69499b17129d1f567e8ad610a18a594d68a84b1d6dff72f8a154ad52e8ee7c0217b8d2a946a2d466bb535ac46f571b898d0935628d6731148f66ff9d261c9ce32cb39e78f03619a66c371ee04d48b61169b40f3c7835c6822d1982dbf07d1cf2d2268faaf9d57901b76f66d7318ec62217d69b3c35b5b48d25d46e8512ed9f45d46d45aa70765e01121cca544dfe7c029c45bf4b78c7e11398714538af4d1773da1e923edf91e3489b4a1ce76b6eb72ceb9b3df650ff220ad81b42fd17afe389027d15a731c669fcec7f34fa699e79129348f4c241ee4411ee4f9fdf900ce766df9e229cad6669bede7dc3d95b7eb72ee2a5285d2a552dd57a3d4d7a3d4522ad575f9b9d9d5a4542a69731c46e73c4dd3d4d39bdc4c8563056c76770ab12753a1542815ca95c2c2582fbf1eb1306cc99592b08aa4aa46584dc230d68f15e962d8079e60036cf5d8ab50d774bd7beb7df11bebb57fc1fa16d6b3b0de05eb5758ff81ab5087cc565dacbf7d3f2e6b374b6d2bcf7cf8e0732ff63ef0f480b49dbc7a2872efbd0e74af2fb9b38673890f2571a13b7f3c49ce4239e7136c90b98bfa9afa7b3989528563f7525f7de711e9e216ead87676d2c21d3528b5dd292e447d09c618d398d3e370bc4ff19f52dd731e38916c0fe59d382e1cb11b8e3b2af699b6a37a90077990076da0da788ee5d0430f0f49f4fe0edff4c98b07242f1e8abc84285529a9a4924a9ac29e2f2f9ef2c78a14eea09462aafda5dfaf5ed29815177ffa96efb66f01594017e00af881381c318cc2a7d0147ae1288a346ba10e994db30aa54249a9fcfa80b10c3a3f6b9ec485341d64ed6fe00e1d14347f9082e68ccc6b8f234d3c37969e641e01a231abbfdb952e84b465a14441064c41a7bcb5e119dfb66ddbb62dfb6ddbb62dcbb60c447d9ea0ddaf3265d9c54237cb404fdc4993fb1bc6392bf2797ff426fbbe0bf761b8d9449a491eb3fa1bbeb8887d2b498f7ac332f0f413acbbfabd17e3257b9c77c31868d6b89f799289b72be4fdfc3cbad09efffae6f0df3efbec33ffd56f736f3ae6cde63bd09c714da0a3ba393bafd3df4ded7d9e9bf65be8417346f75ea7ffeea87ffa0caac014b84fe05c72b23568b7369390245137912eb6e9dfed5d601ddb12fe153893bc753fdf57812ed0d6fd14288b34e039db044aa4ad416c73a0f49ba57d7fcb409d48db044aa2adc1ba3d709c464ff6fdcd8382e68cdb81d783660dfafe1ee6fc77fb9bf1a6e1f913e441acd7909b2d6ef9daa168e37f7911454cb3ecb3ecab91c3e02ccb2cc620ce9ebecd5e5e4471fc2c938102c508a9072f0f1479e9b06487bc7440b25f428cf600b67c09f9d92f213b3b6fecb1c95a6b6d4d72982953a1681c0d568da4b55f9132ee86c5dc8d2bf3bdecbcd434aaefd3c7fdfaf5eb7729df9b25945943f577e57edfdd7de5286c7777cf2a709c3a64ee884f9f1db45b06ed5f3c5f5b9130fd0ede70aca306b65f21bea3635ffb6b9bb3af75776c63eed6ea6cfba32f714fb2ede63d309a06a3ad43c689fcc3e9c4c5fa2d2c747d593186bd44494d79580cd4a3b9a4596b649326f6598716497bdf2ce6bf8539b051878c110a4e6414c5206a19ba42bbd6bf98ccc62c86616fb37fc6d1a4404e46ef910215058e27709c46bbca6ae4a2dd64aab847995d656c6641d3cf97340673e188b5febe51dc1e9a300cc3300c33698f99be4299334c3fe3d904cecfba2ce03ddf7dfeab54d9445dab401ab9b1377137b090bb915285a3cd56fd4d00fed135845ef9ef574f3faf582c168bf5b1582b2c960b168b85c56a61b15eb0584740295d301068df104bb259acbb2b8bc5fa0d6c9663d8ea2587fdea1dc47ee5be72ffec6f0898ecc9cd3037cae70c17bc8f81d97dd40d0769e4be2e1f17025b7091be032579180f71a8307f7e8426916b045afbb6a552ae22747daa527d2a4c6117e794b5c33e0b5d4210f9d35fd19efece718e1c47e7e885f5fcc205daa244e10356c00ffcdec6c6ca7f210f23d8f3f87ea5c6c5affc4ac863e55dac2692285a1b259112cea6afd42293c2f7d83c82b11ec3b0d5538761fdea635a9e7e66dd70fec81cc156ab9f73de8b61feab573dcb66b354ab145ca440ab5086057b90055bf91a17cff2dfaf1c99402c20a6adec5d642b6fc3587abb2cd62625d1e967dc851a18c66fa346bd513fe28d423d0be8025c013f309f381ca86dba6aa3abadb236fb6dd8ca6a156659f659a64a811e7723c562b15c381afbf9abd5df7babea31d5afc05186bd7d0acc3f39eec6b66949348a264ed671db787278dbb5c3c28b8ddb6ee1054a9631a4eb871e3f17117a52fcf9573f4b1c1fdc773418f1a7617bef2ff7dde693e585edbdafd9def3d05aafeeeed66ea0f427aa580860cb570892b60cc65acb504a75d073fbcfa739c3e1adeaaef5e3bffa317e3d8f3ed67f5fc3faefc5b3582dcfa22fbee5c7f9e34a4b1e03a3beb030601cf93c7201e7c5b77c7611d193e53e0cac677918be5fa1c1484dcbaf3c0d2f4216fa2ccf7a96a7a125647981e5595fc3c2fa9aff7e25cc9fa9cd3ebd73374e9fa774e1e8ebe0b8c3da6cb7a6bfa6cfe028c336cd2324f7c9f121926e55ea25f7aacf7463bd8d0dff9ef535dfb358ff85352bfffd4a6863c3c37147cdae1f42171245a3fe682ab9c0d1c25fa0cd85384ca5dbd71ce3e2eb6ff5fda5e78c72c8755cdc6c329928fd7c7a39e3367b03756cfb14b6e062cd393466fed0fa7368f52e22f4387f66dcf356befe149234e8c77c5f7f12d118d6d723a44837e7f3f3081e1a23e9f4f1b086e1e459016578cbff638562a8b35f81dcfc5181a369a7c09cad73f9e8d173dc6f0c4e9d7a0843a6a00b2ed69c5b70b13e0e2ab7bb7e48dae2a1ad07380142cf7003cf1fd70fbd71276c8331ed481add9435e6d19c51b904cc2517c5faa6df42df912e26f128cf711e79e00c7e235deabbe7646e53baafa58bdd903286317dcff11cd7f118d6d7ffefbb976381aef3238afffee3f5a9e7f4681976380c0f87b9c199395387c5ff6f2a65846b08cdfaef4789f4a30b2ed68fc1c5faf34739545f86afcea1f9e30a811e3d27c7612e8fff05e7769d1d210ed3338488c35c175931dffc8f054af962ca00f847ff1ba65c9c9c962e9ec298bba1ddeb3a92892860acf5f7798e1ff19ccc5d3b9abe06d68b4d9fb5bb1b83d24597d3bd7637ac5b722b6cfb86385cf0185c08e53192b2d4bbbed0970f6b31e4585e40fde96b50e12994b4552369cba3ff0017e9bb6cd0f4b3d7a88f9f55600a3c815ef53ceb7998e7792f1d46569f32b6bcdc0dee4da6700670dc5c381e38eaed693a411ddbd60ee20de46ee0b7af5d1c91b29b2cb347df5501970dfa299ef835198c806ab78b437c19c0850cb89b0b47e3b79a04c71d5b4a0962709c4764d8b54e22205c3e5a46a6294a43ad69db6f97117a7c0de71c6ed7103d5d4180a0c80f421bc79d3e73600e2ec70c779b5e6ecc81a797dc0c779fc21cdccbfd197833cbddb8d99bc009eabf09d0cfddf05ee66e1b37b0e9cddbbdf43376e706d2bf9ab5b9a00bc8bd4fb38537c4d225df4fbd8d8ded862c2fa03ef53546eef3c035a84f7d2ae411dad8e85e72375c1cd09c7451ba98b9c7d2257be00ce56ee40ccc17cc61fa2c7b9306e59e1abce086b7e7fe82db73e08d6d6fdbdf7b6fc665d95f2f64b9df8154e33e5330c76b9ae45e721dc7fd8c6b207decb9cfbf3d05f36ff4396ab325f7d4855ca43b632f390dab5f692897d03ad7ea52b20268dffe8d39bd3f8d41bdcb2dd73fd590e585d49fbe26f5271ea74fd5b026f5a847853c509fcaae1ff4f83b941145677f7ad48f32a9c9f61d18e011c3033c46f5be9d38afceb7ddcfb80cb1cbee71e1e3185983a3cdd67f83e35c387a9c47be33bdf7d3667ba08e6d9ba896bb4aaee3dedf5f56d4890b451765f60c47ba73380660fbd0b67d885b8020e422fdc7dec6c6a461c8c3087d1ed8dfb006f3b88fa79c40764e219182325c9c3f279d22b3b0e02f9fc631a642a753a1d3a9d0e954e8742a743a153a5d5468709675f2ddc7093d4ee8e1faa13597d635c4a882c37cf493a131353538688c8ccc908c8cadff69daf2e9f487a7a1093f68171ee913fabe1fba0587193fecd9041e70bc455f2a6180c6cc28cdb848ab680fe5e7f4a3760757161ae38b6198a5eecaa252c7d2054b1721cd4ef77fc9e5ec3fa9f68a737e9bb37e0ee3be4687dcd77861c636ab1acddb66410eebece7908585ededd36c32872e1c3d5f03b1ff198a75c86c2cd4e0472b389a367d2aefbfb950cefc7de2fbd2f167d083e68f0379d00c0e155e688b7e26b3297dd78d07b96eac942c63648cb5febe7f5126058cfe29d3268fcb5ef6ac50aa513d823267cc108607c4e0227d09d0981fb1178eba0b47fdfdbfecd657a1a45a5534de74a027a8342dd6a639c9d4d00800005000f315000030140e8783229120caa24450e60e14000d7ba04a58461bc7e2a13088611406610c31ca18438c01c0184290523243360091c2b032b848390fa3868efe95687c66c407bc0e1b16d73d529ea1690d56e3c62415d9fc60808da85b2ab21f3f649b41274458cd80eb0502f618a976229b5ddcfdf801b6362e2ddb8c46625122ab4e09c5ba5733e3f71a9639d17ffd4ea0b9a01b42174deb8976d1c5119a4d3dcd0d6775e438980880e27d3ec66c14202c07c93fd9f7477f3735e10b10ff7035f46f70e408f757818a8e88977a41339c954e3548376ff358267181512c1ffe56b3b567e1056d3389d0e537259c98008f5a6e47b5d99aa8668a2192de0e11cfd55bf8a1577472cc33c538ca41bb1c95225706ec9b1abe112ae52cbf6db9443e97357050fc44570fee7d65fee5b516489c6cc7d9d4bbb678c987fe2ea6df693ffa9111f873313de282af13e2ba122226e570c7b09c19e9c32b2c2343b53b5238efc0f2da96572267714518c9f67d360b3839403527e7806f8fce174dbdcd818320fd83d9881074ac3b380bef3704ef3e106b7bb74a427fa1f9639a4ae2e2825667a77ec5f8085383dbe2484465d13353e99841096947f72ae9596eddba8cc4aa11402f4ba5fc0368dfa6991a32827f62af9eaab3d76fba7630fac37d5d882bf44730da6d3193cae91ad9087c9848731ca8450ea0f5448877bb5d49cae2770ebf525742f9723e540cb396085ffe15fb1eeea8e2f3201250d06cb0b788f05d3bc5920dd881a7510186a4a43ee9c4e9615bc2763b0335f61d614128f6b15685da680ca6eeb04ddc8d1587a8eb72bcec3aab3db95a32f1c4e7e5421c1f6bf44525de0b6e23ed643431a35e7ced22987f0ac923a4ed1733a1cb0120017de2306dbe7d7d11bf7ff6b9d95b39d77af029c2ad1bae78cb0123878a71be8ab76476ceb04635f5f6ebd2b25d56b79dfb595451cd852c1a61e4a5bb8084f798ab72ec3b7404042334329d6a708a7fb2fb8c6a99de2fcd707192c99170a4899e8204a4327c863a217f88f1a62938a455389a0dfaf728d5fe8b830fa6ae6a18585da82b222ea2a7201a0871e9a9b7c8838a91a11d8c57da1b1b2f9761c312c84f530213e79f4c9e01727e5e152dbde6d04687d41a699ecef24650344ade2a42ff86acb26265c72adf561050e897272ec09a2de9953294881e4f24c3b73993ce67fd5a7e28bd1106e7ce5ab0961a3143d4bd4e6ccde07f41d868de95563c46be99042092d117c358bfc0b72babd4053c200d25fa1bb61cce97ad313337a815da32f13b846a95247a9d08fcd8f1d36450b6d77f74d98790e1bdcf908bea28f6b990d429044f70082ba357a0efa6fa3d7335ebea9ca16d0a725a30ba5b47164a3003c7f5fa7199990257e8d3347e0bc0d1fac1b0b990c6e0967145a77e3977f520a2fae8375e69a26a3db3a82aedaf37da3c3052a5ba493256a631667b55c0b081226a0374f435568144aaa0bba87d6dda299e35f9f3f52722fb5f451470a9608b764924ea9a54053d3b764f1d82b957a58584775c5678c226e0fbb1430b5616ac285fd7a21f5a0cca6f3ab3b99f71f2e31c5b64bb21c11fbacc495c11f9a756cb5b54b874a0f0540542f4781de5a9b6fbd34e10f90ef4c21eda2e100cf9a5289aa892aed102a2275ef67b2f838e28d002e7ca0756e29b164d489cfce16f98761dce3ff07bc32c67d2481c7a1ea76946254d484c62ad5b2d886f703846ee527971768814ba585630e4bc9f4ca309a5dba198190bcdac95243cb000a31c71b2ff2d4697ec56dfc49ffced2c5ebcef84ec588b06d282bd5b9a7c56b04d428337fbd07861bb71102182ffc87390fedfc6b1a4b2828debe038148862e247b3efe6251a32d7dee8f833190f006b50fc99171c739b7a6e99641d083597faff341c44ff216437f382ad05bfd316c802ffd473ff401c6bbdce99f3928869bb6b64ec423ace3a6ff128e720f2a844f2122787782a61dbd825a71b5f2cc507b0a565caa0085b420873364c9e2554b19a9845b8727674ad17b445f762aef61653b47bb336b59986c36daa8ee5b3258f316a86aa19b7d1813ee5c3f9f5f833073e0b33f33d1297c3c7612b397c84deccbb229c8a091f124ed590de29d76ee38f8cbae42df1df36ae99196abfd96348e2cb2158f5d0e8a37482b8f3428f4a565855653b67cd17fed716d176837bdde6d93fe89c29f7863f365e5ac767fe8152300090379316b83ecbe5a1c43e60444f3b8e1e21d1a1bb9270647b4d5ea3e5036bd05da219fd230ac9f2a96bfa6dd32f53a77fe56f990c1398df98b9b6700860835a25cfa8e1ac2998c4f91546c3cd4ad227f908fe01df242d2214efcfb16e2b5ec257e46aed440bda075eba588abd08844aac726b586f53904f42f0b4db607f15c146bc8ece07a9a67ee6bfe14dbad03fc0b90a40335e4a71c984f822d2c7fa709c7be2989dccd0f67e8dc0762a7cde81ebd7cbbc8a3caf56ac290afe4768627cf9e686f95d8c6e05992f9c4e4be47bf53964f8c159e81156e5ffd6c4ecec872f1d339d62a85503164fadce43f134b90ff5afa07add9b6e09cc7888080e5ab52f0142d7f844916210693365e2c052c9ed3866195e02f046021d3e666aa86d0539c60b8b57e71061f5f203ae8f666ef1cd4808befeb61303fc4bf3781f5be80bf3c0c111f722ee729ab27e26f73879e0a8307eddce54de4102d04b3845f80db4ec3c31ba0ef9c813b81adff64674b66ea15eeb1931f85f89962137bebd0fc911c58f84d5d37d26cb7c30ae56a65906570a03571afaae4a6a85071d458f035b04c8b09792d0a44682a55934f229d5046fa2d4f4ccef12aba2e5a29937543ee8aba755e04c92e6fa3941bc0c2e216b0495401b72b8677f2034b93384c1e17a3d0500fa17a22bebbc3b0dfe28bf7a6602802108ccbebc65a17c7b945e8cf5d8a7cb0648a5ef3c46803816cb2127eb6bb80510058fcbae9a83b9bd3ce82fc792d3e0b71a497c1982824e2c6796df504420dded21742ceefb084ac35531056a1d559518f965cbd63612e235f2aeaded830dd74f5578bcb293a6e8419694b8b40ce825dea2d57434ad39840ca08a2b79c62966c1897b7e0bc863245318fc35d0d4b40e020be625a8372a3da46c9b179e7f1c295559b4fece85f9fe3f6521a2345f7dac172c6e73da9a4bdd1f52b0a0bd40e87376cc868c0b36d654cc2bd5ed28cc58f946a194b0b83b76bd056bbd900a5e03fe74acf33e31e2f581d742d272d96c109210727b4368b1a1a4e30a9ffc8aeb90ebcdfd05da5e754719ad79d7cf9e3da43cdc943edaab209a82ae7bca52a25f375368553cf3092741d5296688eb546b2d1defe09cc0ca93a709036f9b3fdbe9fb66b86706ab3257cec58950253a5a38367d9cd50bbb43c54e3114e90110d74de6d3e9fd85c17f30278d188848424f921ebb0cfa11fbd9c7982c3c2cadf34638f1d091d74ea3f1893c4786768220109c080eb6248d751ce8b6b7946e46fafd72ce4dc96ae046e474f5ac0950a50a0a5841ac6e281afac21abc7a1860f5e8d57fb43fcf4529803673c17b19711832f9febaeeb5252bb1898f088aa5fd88e8d74efb96b3e7dc935167a5675a9bfd28115c34734999585be39a37e598ea3830d2bd1aef982ede7ccad77faa2bb07e58658fd0cc2dbf525dad26ec636acd145b32bf6e0d6592deb5faaec988fb9e011a6613d0b2f914cfaab7d9f634ad000b113ac4ae930e95b9aac74fd2348f5dc9eee4114d801fbf08756189d8f11c6a5b5a17d7263c6c8b98946c4317ed4a0f418e65b619969c9dbdd71ec67db5ef59f3227c1bd5af73d08293cd609ee25de368423bfda605fbbf6202ec3c8544bf82ef30627dd30075e78da07f7be67d12361d99085c5f70fbe8712e47f6343d173a0ee636678d88f3b23c447696455c91f4e5224ce982e90213188c6224ce34f2e4e6f57559515437fef9d4a9cffd438d2af33af4762f1455a641629268c987a7870e1c90b4b88d5b0914c9756ea2abe8012dd958eb609f6d53cddc07b533764a19cb666ff486b2cfe2e2d5dc13012f55ef85319999973cc46740203ff43ee6b60bdc1219031853fa51020439db0f80863579055c5fa53dc00dcc6edc1dde8055a45658589fa29032faa17e3304458a4742e12199040a88f9b9d7d6ce12954b04ac5dcd53b126deaccb3d08ca5a1d7406ccf49f3da4f877a144ad76abe401b33a6a61d3e1c14eaa80df919b9c06c2babd4091504c205df1fdc0c45444512206cbb9fb2263aaa82433b5c99818e45d69ad1129b999abdec3ddf09178af03cc83b5cb11b4b908042dd0f5cceb69f0f5358bbc1b260616c977fadf5e691e5363b3aa4e3afc41b9aaaa971ec7f6bed81ec45f27a5f29a85b15e4c98ed7f74ac45e5374c0dd926307c18e1972169c7f95dede3f3137bca4f241b07ef34b0464d22b14121701cd30644712c10635d16dc035e36bf767930f872045371d8cb02a6c406b4596e02b136f4c912c01114a6971e9bdc549c822987a14db3cc40600f1c43c3a6f94fbc0a57278d206324725699f5f28c09633d48b39161e10a05c64f0143397581f2a22936c49381e9a531923cd609fdd8ef414fde931848449963192f70b07adbb76f18b18b78673b81b240ec342c768226f125d6840c26e446d3f5f2b6e0ce8d51ca3aa811510aadeba8eddc00f245c7b0d53bb91f5d736bd68ce2ab50846eca7c297209e04aa7243d213614b4099fb2b4b36cff8c1854d1b47744997dd1b4eaeb494e8b2b260ca22501db291fcdaa4e83c1acc96e2934621afe4443c834c5139d96182ecafc14f4083c857070b412569d0dde5b2c04d03430567cd73c58c0bd8366388301da140f67270745a625bfa19f293a94fb4d7ab3a0df690ed47b3b74eba310d008c1c50b0bb1142190d9ea41a2a4ed2457621dcc1019111cf8d41163b7de6237cf88b350c74764720915128132a8232d320983064dce67ffa85c37255d556b42c24935b79f69b1728bb1a71cdec60bec5656b8e2af8797d26423988dffeeb1c525b08778bf082e708712323efb765996309a18434dda9a2f4a967cdc11c94080893ab3cb0ddd3e088dd5fffe13d3ee06173da5547a301a1789223aecfabb6301b092b34f8b790f7600e0a91937f86596bbb12be1675c00f6c8776e6e3a0324f4959bb8aeb0210893665a73b05144f3d0279891ed64b471bda9751798b8d786808bac0058482f3791d319f667b11b29e0d6067ebfdbe12e965a5baffdf36b9f64b144595e8db33692dcd9e4026756a8a4eae7016ef4d98ae72dd5a4342d3f3a9057cb3aa2c08912114bca5a848abb86a5d4e5901126b8c4b01057c2dae2c3b9d9a1bb168a823613fb14929e2b3ffdc9ae601cbcb2882d80caa8283856eb3d2d2f87b059266bbfa91c1b93c8b18730dcbc7743d605171cf75e39983361fd6a71b9c0f0696ac3fcdae554983f3f86cbcf854f23aba7bcda47559d5f86d0b61552c8dae1071a10e5af486a5080b7eaa0c358cbf2c5776e0363768345f5fbc592f62cf98af11bcc6bbce8ad2c3739d64a70f1e035237a39994d86bc3b333f28b93b9fc7c3a804ac3aabd6061d6dd801d695b7051d01e5bf774f93b7ef376e4840bacb8188fb175941b248027d582d70ba050752ed7d57b5c8cc9f09ffd291d24e13cb83f703b2697b2aaeb0bbe509d1bfa8f4bc9ec78363e8cb1d78f71eccd6d4e29430e3131de483f26762958c0677e6b85cc3d4b4352056576c29ed866846a50c26b53300b119d4c105cb20acc520be40a66117ae1026b77f70a1ed0366293248b0917156bdefe715a4f0ec08ed612eb622835404a669df3f9c375345c3807f1b070db423bf3d2446c4dd98866b8cc89ad572b81abbcb2005a71a4a5f649bffe0f8e615d71ac5b23ae4195111180784b242113c793097a0b9f3a638904ee289145f924a327126f4ea96123a46f895eb8166d4928d307fd63ba15de285edee0eea59f0313dcc8f38e27e684f457b47cf77426cfdda37edc2c4c2da77d4bdaa651b3c66f3d4f71c74d21ad8d57e4a52509d8d5f0f748f7caa8156e8e4d09b7d534a8db70e72e04a7e1d42b3f49c20060eb89602060b4a00da8d193fd059f4cf9e50e01e87a23bbe93587295f18a0b47bc143ba55d6f5a75bfc4528dec2b86ac3608005e22d09562898d2c914381d9eb41d12dc9312236d052858a77dac9abaaae6f76e3cf1ff708b0e948da9712a5db51409b6ce08e37d51d9017b2f5f87e5c11fd6469a33868973f4c22a944332ea4c5a7d4edef662c0ac1ba609970394562e90d3fb672a06d940d14a6727d396093990f5b99615dcf823bf0e488ae1bdfcf371a2cc3536616c0b42c20a4f656cf77614a685b9251a0d6bfe8f40653de5de1d6b1eee39ec64a2f4f6ff2839129746bf3076a4cfef35cb3814ca88192d38358eb78a861b2b0a7eb6d58458ddd78cfad399e6bd5a8399e15a600b03220e74cf9962314a24c0756a592208f4b15553531dde7b446c062e23a7e6314b55aa6c444a206b4ed34d9d1ba23b21f1b0e351f215741e6e40bfedae83ad94c3a521e7fa98cf6754036cc7b4537b83347908077ee4d9b759c9386fc41abbec1aa5210f35f1de9031251cf5f155ac46c6cc835b7ce6a35a60e3408b9547def66fb283430a1c2c92e77aaf3b2db8ac1b4d531a822a6ce367cd71f8557d5467bee9546cf8c3cb674fe240f155b8ba235a420215830313d5033913349b8310207ad571d2f3b31da5125309248cec8344ac1d9369b3901f15407eee8260da52f0c583667893870f23cebe8d8615dbec8ff50450a09cf53d63ede7071475b2ea5731b9951f8206b98f5f037e1657903fae1bad2267c982b828d28cfd7f3c12c1532b7298438aabaa3b8dec69fec006b10729706eb46e8c611dca9bc6509cae1ed6364c30a77182733ed2ab7ca274044810c46774e089e4fca8448fcc0b81613e9074a605831bb22ce5fbe674a1ff795d68d75bd3d219d9dff28f4796c29a2a62a3b6b2004b105cfe5d150c2b9d5f41a714fc34b08a0e5e6a4505e5c496801d5153284d1d697b9024e0083339c465423442b603b3afcc56c21607a6ab76c31044c4ad7de23b4ea747157cf54d964d559b7e4edbadddad4f4c922a7f20d72441019727328d171ac82f597d479b22037c1d6ceaef735a4558a10f4daa26e9e4536f1fffbe5a385195e64b77f24175875c26aecf6d6bfc447054073ff35fdcb9f1aa9081dd362277d861c669ebe27805c3a036c94f90d851424cb5640a0922cf3bbdbd26f3ca06cf0367c6c714077d014a2d5be0aeb498f0ecb8d13a5f8dc8dcaaee55d917e5742b8cdb7e9f558ada1df406330e4c03ce90f5692c5c259ca2347c63c13f08ca9c85ff0980fd55f6de482982752dccd9176bde2475403e37731486a09163cd80a6784c7c7c5a7323eb8cb63b5ea81df1cb5f635d0e6bc686550523b9ce2b914c7eb3511d9cca76a84a598b7e3098fb8381ef59380ead5a5e4ae42007ad7cc9160c240748d0c56ab5f29edca8b088ca9f9d5f2af2ab0fc8e20c550109e76777da49b238cdd0ff749dabb1b91e850e418c22113b86fc42656fc44978eef44e3fd013ab0f80a32e697520345f3e54800d40b343dd8333b2d22b51bff7f0788320bf325ed9fc2c9a242eb90c30143095ed586e84d3a6482f6f01ea84f9489c12841215d8e5bc0f0186ec61359a95541c47351e176ae1548fc80cc8b427024858f7df009cf2a0484e927e7b6d28c18663f779c4b67252dcef4f0c67d97deec10380f02d2d463a090a3d496e0a099868ba0c7c5c431c077f76bb3740f8d831170b9e0e00ba8d109a6acfcdf59ad35d49040a3e0993226129a0264db9f7f415f98200d0a39f8858ea5184eb017e706e14dbacb84720ef72f844595b4506f3de62a58fc8aeb12ae706518f42f9558d048d02c3d715d3d24c2c10d31c3e7109c91fe45efdbca02b8f832b22b0b4d74b153d43807c070b7336c0a6286dc8a184360ad95d6dc1201f4df11321c85214c18d6342873a96398d3d46d78f687fe43d55228c7358f3b99e1ff42f9358c42cae5256609071d231fa6db3a142f7d5ccac9fe1667a4f83216da6d332a3da5580db4131f4b0c55d363e7e40a830a2e5d13278834d33aa7cf4267cc25059dd62e1e6e78d0f807da9026ebed1702f6b73803e10849945510ec911d8cb6ddb618e024cfd60c9ac47c77da6ca13095ab46f10267f426d32a15d83bce36e9974556e1f54b9c65981d935850cdd6f05892521fe62104d7982edf82c0a5d51253002e1b0938e0511a23d1243e0f3499dd27e6610f9ec96901da2b0f2f4083baf4e77883996814d8d532194675785f33937e5d3ddef39976cd60ea3ffaddb6bda320cac3c2a60bd48384c618e798715ea0da1e8126168fb8335a7874f4b85e8dee96fc1479c6bbde82142821f132c14d8512df3302fd5404bb261dca235e73f227b39c29abbab40e04ba79e205bade922895d0b6c99113f636a76ea59a9c3498bb07ce9fc4a21d4d12608f30fe5628978676d9f5163af38a57adb7949a050e1dbd399e699e91aa62c9254de11f51cbe61c8ea9a6ba0874a474701dc49811197061b5ccc44541cede508140cfcd99231f58d3cb8dc52dac6a1161d892584185b525c591b51336b9381a8c65d6e3fa2880b763e4cad3ebe79914fd3f2ef1c356e1684b84d8847a0b763a137582733a8d3ba8b784e99613a449f990b48aea1f05cb984c16c4c4934c5290bd517e37e14fdc67232bdad09c39e456559ba6d828e272d829304041c819f9a5591b92021578463e8dc5b47d0142189e5ced641efb3c7577387801abdd4d5589d6ecec92aadf3e9fb221a46ff842c7091e0f744e12da33d7baa3a4d6c0781e1e64a2ad868994348fc37bf2b5afbf8f0d04800263d5a7db1bd27f6f1236b27964ea0660e3a060e2afda34efc29eb0f683eca59090a2c3912cd943a7e77fabeb23deaa3be774c52a80e8e094b79afc25f6d2d6e45ea66cc5fea6c9e68978b61cf4ff95e6b109233ec372c5650d1f32b3100f08e123a323216ac88b4c6fb4e1d52e4e1c7e4a485eb9adcecde8c6714c586f038142cbe669562ce07919397169ee21609e0611ac04d4058435138d63aa5d9d638c836eca3c094a30dbbce346318d0a9a6b1b7769dba701417db7a1d91db94e4415b6e37c24078bb82c0b7635a668f22717c615b169147e9770887da3c710881ace1e5b003c35601d80ba43d908247299b139d3849817057bd8693374737a0e3e1e9e3dcee4fc888558d1e98e97d4b0cfa8d84579b7ee9233123d99909f2e5a300a0f04c19d0b3643041482176c8f2377f45ef8c4aa1f0525f01fb1994a62f06f1c2c1dda90905efe5d096beb708af3a7fe4359c85790941d12f68346982e7271bb6352190c2b4a3b835e815a2cd5b6c09009a81379c029745240dfde5786081b9825f04f7079ce441d508f072174f672e63ef44410a84963991093970517ac597d8e6f8bef255b8979e726548bd6035178ccb08d31fca73724b63ea7a010463528d45e185d1e51c04b8e8e62e674a5b1d41ec5f3c6a097bcfaf225f9f8b7ac0bd3b2a016f5a3e8c2a544d8da9ed683d9d979bfceaacacc1063f2d3a09c89b3d74425f2bad7144906ef643222e1c922191fc161e2d56c25ddc82fcca07735859bd1c3c05788c5d9be60180793f0b28ba117f1f12ebb1d1b6a889298445614cb7d95475d5e479fbd1f015521580bc4ec66e8aeb09eebe09124ae8efdb25b632a01e37eb0ac61f93cc953558e3fe182ae394bc0305227ce8202e6d88b288d26418dacb465e8e2e6fc89651c67853f3d3f2a94224b2d24a2f016d43a0b0d69edb416deb457c14a9b409e7da440cd8e225ade91dfb5f46984d26c8e914c1829f8d70b7f3c3e0b90e01081a024988580a51216026f66b1853f032c625828c67e50e10aeb16fa7d56100d1460097c90978dfc2807efb8e0e76fc1816c1b12a0c2fc3799189a5f19012a64d1e96426700ace8945a8df624f659de1b0b1402a809432ce09d5f0a38ea182017cc592fa4043ecdebbd860a4b993a15b098cdc26fcd9aaa77c883d84609617d9cde16f3d61be26d7dc300a3815dc8d6ac660f29798eadb1e647f439944fde4562fe075d43fb83f5f5c2fd3b845cdfb15cb301fe57b55c923009acdb987db7cf5877cee34cff08029987b648a2f2298fd514f2c721b137eb8d970b0c74426dc0537508653274064ff9e26b668f0858339655e26d31e8835ec8c0f69690a6235c2ed26b35cc19e02d207d077c171bd1ee6705531603d0af00abecc1d8c2032166cb83f8c7bb866541d927ed2b6d8cbb3600dfd7c203e07fa6f05ef7057c9bd53063fd372df3c06f07631ec325c5942194b67e551ccd4ff8e096c74f782f1230698bf5363b1756efb23a69a52d1e648b0768977efa5cf1a98f03647d76f28b5a41e8a0667b44d559878eacbb5cdc9f623fd309a66eb596e7b0549dbe182155f66233f250533e68aeabf7e9bb9c2fd52cc069600375499db2a17e7dc156f9b8351fe606dfea8c3e67b615bc2744cf652bac9b5c13361c093dbf124c8f919d24ce1904095edb892543988c41f285ecc94308b615ec55102aea660971180a0c2be8288095c3d99c3ad8b960cce4769777cb2b6dd44d062faeadde03f13bfbb4b48d603006d6b46aaca6283aedff164e65b255603c38057f550feb08fa3a3e732b2006c6f6f93cacafce75cbbe71f6eca188c2ca5e7c6935ea4a5527ff1ff9ffeaf307d9ca10b04a23ac4716c63fc5d8e44d4c4f42fa592e218103aab8f344d0257300d12577c69fa246ad039ac08011a3fec5730f0f253ebad73a6b9e990de62e63a369dcfd7894e02cd85b43b2e332022f825812488a849027e9e917f9648fb333e368e7d39202a2648889a6016c9bc3789a705444d743b842ed10b26a64b8500525e70f2e3662c06dc35c60223a2a62899bc5c220aa169209845b3cedf34ba4003f1f24df2d81d74f9ff34e9d8e9f1d27931bca1dd062af6d164a38c708dc4fc317c4793c272bd517884baa0080ba9dc54a3c1c758a91c51b428a53d35b8d1207e1516af68ebe5b45de5b6e33cefddd3ab066912a9b2f6c79b00ddff4cb4c726ce3cdb68d0954408666c24e01600e7e8a311add1039dae6cd8071274479376a4863e7018d2493f96e8e1f23a39e008d05f9215633a1d43b225ac931a28fe06ac749acc221d24b845621bd868a6837435c4713c452f16940b619e26c7a9b16a9a0fcadc34ff04fb081674204bcda0004b048cce931751add43702058ff406505350bb0dfc83dedc26187f35c1e3addd00a84453b22e122c7f2a363af09fc0a43ca9d17c6b2ba97102deb622a04556cb0e92209dc8eb3faad58faf92bc85480f39d2ba3190f8c6524c57610722d33982d45652b9667c2c3cfc2776d052b7fa8140b18a3b99acb5338285d49421c5f74d4874beed6b2b61801d535db749f068f321fe4d65c47a65619cb795244a05b83a7d5b412eedc494bdce81c2cde1568275a82077f411680904ae457736becf20c9e2b2479d98c4ce80c49601ba92cc42def25173d42d4dc4f7ee9523c029b6ec95a6759d2d9ed71113511a5edc8413c6f0404ae2371d92f75420c097fd19aec479774c44283847a495c75352dc5989a3a5cd2c9b12f8b338621b138376cfd6878441edf85b70ea4b6c5854a118f5cee3b812473522589e5bf689da2db91b2ffce520c5c96140732584a7263c681a3c0f5e4d2773cf3270aed9129cc9f84b32804de14a7884d1821e89d56db176f7fab09514774e4e6256a686d47425555d6c63ab2b8991e2b025986ab0a30daa90444d17055f6f765d09a8f550af64342e2542f48e1f8ed1731f48c0df1afb732bc96a9e3f71bbe359537c80da5a980bcf5b49588cbfb3b3398633a92c642b141b5b63ab09ebcc0c122d490851d3d64a7ef07ecc15e4329cb792188cfd26919314bc2b70c0bbd2cb47109e6d12b7e8b1843472acbc4f91caab8488367ba4b427267e889aa49f9847d8082d8052a67cc6a79046b1e1b121d7b8ad63d83b9f21f3570c1a323b1d592310fa31caae1e133fdb31b92ec7bc6e5636d923c20e4b091c02fb41359d9eedf9e865f15535527e5dee3debccc49e92231926c2ed511b53d99b24985bf0869081788d11913b9c882420b3532f23f2eedeb5c44f6a31318829b21fa9cfdfe6d0c2a812892432ce84cea84d0b74535802d3fcf9becabffe48c23c5dc3e8ad2b259801e3e1fd30520a17a16f637b5cdf7087e01a388b4cbee4bc1995f7bd93032001171cd80109c04852c499bac0d799e93c88039cb47ec1050640f32cfcd2c5d51f5049428cdb2fa251a25cac0e1544ee2c2fa9d329e31ad2fe3162ce61c38cb08d0ce3739655f85f51b63de31dba1849f005533603ada1bce0cd42c204ffd91fefacaf972292b8b88e4474d51fb6dc807aecccbc25624baac9fee02217b61fc366a2c7c6bbdb2f4b69fac3b0277f1a4a1859054b74ba12f7a0dde6cd4b38e7634d7e18e86ab75f48f40858e6148566aca4af76d393e7e5ef724dcfab3641e9f9b3fc4f719ad494ff8238f40144798321062830c46e70c4140004536455250917ed52242370b305528b35bee7c51b2db68923de6b585dfd25801a1e6163e0a617e8b07c44db047d34ac85c2f17ef8d148a0932229ea911a0a8efcffddd2405921c771272f4b827fb44e1068b22c490029319e6f345c5bec8eadf21359491ae8303a1d555d2c159625d91889872aa3c841284e6a2662cf82149a739fd9a7b7ef757d0dc2f5cc384746aa3fe49bf8ee124dfde377704bd36322f2c5247d4b62a018b7b11b21297cb60511ee5df24eb92d1e17cce88b82028be1d4f270fae8d3a9adcb2287c81e18d0635580d68541ef07defa10ce1c177ff81491c49fcbf290b57531f5a70c36fb924bfecb3c8fc8bc928b855d0990983f887d319000748419ab1e66a93e436c40261f14b9502c4cce8f8bfeaeb001be50c559400ab6e03e7a224ad1af16c2bb4b6ed40080ef3c7a166a92a7be8dfa327845bc20ed73539f082077336e57073adaaaac61feb1344a8eb6f8c874bb02e9b3644a1ef1bcd9092b8390f2643423fa3cc2d1785d8f6bf756c51f26c1711f64a967030ec1f55c099359478234caa6fa26d7483c360394639bc2831fb3e5861a2756333201677611bc732a663684e2110d1789800a3c0b499ddf2ad816fa8b90b061ddfd21b1751f2a6ffda61e3e3544ee0eeba1fad4757b9301e604dc2b4a577d6b13225c3464e851387a6025d41a8f3d8bfcc32a375677fb83ed5553a6ee6f9acc6e09d37d830624618f707262aee5faf35e17666c18b4a129fa41fb4e0e6c47b73ff05d700a25532ca0db1b0cb3f6679787881b6aafc173cd432f77cf87cf013dbce6d7c1f5b91439b23efefee52be10572221929fbbf5b593cf3e494b7cef7524625603e43957ef839a8f26df972189f4413a88593707ac6331af23921b4e58b228bcb97d81aa40227090fda5c3a1192d2192f12621c1fb0b570235b3c90db73efab1211eef2ada67fb75495325e84a570a3e55f83108222aa27b5a014b3c6064292f8d986047403211cadb6400cd41c25b5209ac7d8fabc92dde3d5c2e2a4fc856818e41df09ccf8786a4012155f7efc377081fb1e66c59f03cd52d8ea51cd5e4633f00c8bb23b7031ab01628e9e785848954fa03063a1c8cac509cb9c650a14c61e942880cfc873e64fac21087e1b5f02785f946ee1ded14023bbe8660dcebd3a35d00d1686c1944f4aa2bacdbcd1351acda5052051c6e63aa05bcee64cb203c158eef563afc7f0b91ed969420645b95b8b5e022dfc21cbbfd7347a64398a6fbaaab12de3e849d3b3c65ef83b788c8c700184482e8122c704fcc8f7523e8bd431c139a193aa84ac713068be0bff84cfc9f253a111eccccd283a279a33f566569089c7a1ff2b2f7d81898f2811012cc7eb5b80a08f36c9f6b7ea0bb7ba9e847c77c590f7f23fcdbf5bfce307c2d5762a0e9062fdb20dcab792f8c49252e573caa9734b05a01873fa745c172be13fa87c133816196ef79e84626e67cf78b454f0181f3cd4c9b192515f26022544f52c1de7fcab890b643034d6385defab23396c5fb8e96c38760445372b4fa5b2454ed7adc81daf8f05f7a90a88c06fd955111019e0744caea781b6418d0cae15109be1e77638a8f851cd191ac8f6d65f1c84e609b6d7444b5d0c44972e589145c518f2be78ab2cea144045e2ec03bef4ef527b728ae703e04df1357e8a429c340d888c1e70219712ee6248c217d5f499c62f0e4fe0291fa6c104fa43194c598b044c1b7b0d4c48290492686731b8070891843d945b2f2c5ef3d61c7a6f860b6b0d0a4d654f8429181327ca06824023194349063bbf1e38e39694f283e417674a249d67139179b61fdaa03aae0e530941f19fc02496fad87f241e251441bae9d918084540bb6345f2e5143093502dd12aab47aab0001679ab49c403e952c64558f06eb1d361b0f4dd30322a833bbb58503b4708a6f5a728de5af6d572474a06834092591a097d35d9100f804307aabb2084df66d815c624d6c5baabd2bccc6ce23f8d6beb4e0df13b981df4b6c1f7ca9a4238e07960c1d47bd3e99770c034824d602d3a8d677a017e5b919b0be6499278988374416ee6dcc095db1a475befe80347213b828be91f5142424c29cfb11412a5e26b24d32d74b1107a3fb46b7dc5a823070971472c4c974f95bf7befe9c7e302e5faf92151f559638ea7566ca3b4eb26460e1951c169c11bb630ae9b025a49a840478d39482ee91afb0c0fe212671e1d0a278d30b80a16e722d288980adb24538a35ed79a574d3fd317619472413c39e4d991692104ccd23a905c8c76b09e115b1c3118ab4bcb7a66affe96949cfc51ad91312d085c0b11e702c76b28898d3756aec4d4770d6d805f6a3a7914577d7c41e2836d681c47905af49ee422a8bce222fb93087d4c5612013d87fc7d0f6308ece3f16e21a18135e241068e4ad70241d18e5c0d46d4c94e8347860f535f0ee71c09ea161288df04d0be6813c3c298a4cdc9c990e81d8051481fafa5df30bf65a6b0b2163008e9c3fae87a292c072ef3bebc92cef734f95c74243766542330a0174c93dad3e42f498793c232a772ee08a9c29ab85b2183605b7e08d48448c4fd74389fb232a22300d83140460013067830028c8812066d3ec8fc4b7abdb342a0a884611475d0037a5003e5a006ed00657660caf4145166f44e1bcbd57b120d5bfaa7dca595804ccf4db452c874c0cdeb4ff57b19c781089e3247137baf804959a0bb87e444b19d0f96fe866ffa8f9cc4fc62d982132f6de4f2a4ffad9a61ed0953d4d2951486d6503839b980998d0fb2f387a3996295815a5178e503c67783b425d211f158049b2596cca97efb9291da578529c87650554246d1070ba05439a6a8fd39ce1c5885802e8aa6fc5e7182c3d4d2e5547b0b116a4519e90fb6f0c2ac2eca9547b9342ac1a29330b78e663b6c7d028335d9b0d53034a43aa4de964e934fd59dfbbab0e1ef8f1a2bbd47f6b23985878b50aaa5d84e62123db8deee4044293d653bc40974d8957602134a114669e71e39e876afc5d51324369bd73275a24cd69b01ade8b1e0ebed04224029732975b889523365bb892be0c6270b81ee5a99616959cd619ca06a08b0286fed645a3eb77fb6fcb5ffb7ff6ff36bf3ddfedbead3fabff5bfcd57cb7ffbdf964fdbffd6ff363e6d9fdb3fdb7eda7fdbfeb6f8b4f96fff6df169fddbfacf6d1f7ff97cc3a6edbf1474193878031eb6cec7c9d6388a91353c8a4afe636b6616b278a54aedf35658dd297869429d13ca07db100eba3582719422f5ac21f2458a7d44ff273a1f57a33715f186840eb58dd6ec2107eb51df5481f1c6c051fba01c6bca377267e97aa2df83aae5fcaeabea84c1de8131c0a8fe8c6986e91984df18d09c9a897a68c5421d24c228d9ee835a158ce9732d63ad17685e735c2d20964c1f75ed14a347a33fa0fd3c2c8bdf36ac008f85f79b22950a34921657bc8ff96022f7741f77308fff806ba4c164da342cab4712ce340d8f3866974ed70fbb26c236f2cd819c394d585866eba23fd1766ab7dc573b574d4b3361e9562f6966cb6881b7d49260bf7b9a256f7760fb606477d36512520ae750a4938391779e4220e6d8ca034e83a041fd9eee02c79a61ea3b0859e3297e98db7b5a69793d964144f9aae01e86e642c0f8b030f11543c29c53dbc6ef1a0048812e8e0076c6df79ea8969fc888bd97cc03c87d182fa87e667704b54c60319e297da3a2879e3befe3e3227b0b04c4c8b51b8a74864db1d76cea1387e05029d5eeb91d058a47c8a0884c513193e4b5f39f6eb221aff32c8454cc5b58022cbdd7c32d50cb92a640a5dda2e45446c01ba29487bb7a7df6936501f55406cfcf991189c419e6c17c010bfb10049d4398dea0fc89cb0569b84c2314e54cf4fccac0e7f40b0311f61d27c9d11df90d9a11461d1ba96ab6228964195c29c36c7921fc44535da599d99573ae85cd4c8c5656f20f3885ad5ebbc60810a8e9920b233ab7f22d44217c4036a9f9b886080b57954449d84018fcdb322046fdba7332a75e8d6b05a4c14b9bdf3171a78ce8458eea321a4ab2ca12ffb15d4f56a64fef04c6406b8c82cfd76d60f05f7f1a1469a8d088f05d1e7f4aa58d0c149b0889d2497f260150c3b4d4f07f246519c008712067ca38900f8ac09984d716e21cb3a3905a91983cc50042b99bd70d0ef48789d4609f1caa33cd3930bd6431eb026cf7b9dccae4b7cf4b77d634bb283ccbf6fd204563f4f7399df96e8c1617275c4812b3d92c4ff726be35e92f32cbc0fca2027d8acc37944f68d72026f80106c3b82cab5130d13e7fc3d45f48e5680f3f376e5403ac9b2f9c44161373f425f00258832d97cc43c3bdce29c2b351dd1973ac235c57103d69e890e81ba8171dd6c5f69ff477b186b8e0dcaa41ed1e5725aa649bc2cbee96ea510c5d76e4a21700bffb565d48ba162c649a11b52d43b322c0d08f7bd3fa2991141b000faa38ee445990761b06668018de48d770a802ec0751a59007e1480bae0b06ae6cf3c280375419ed69084f16e1545c129089937368b7ac13e30f69f155a1cfcae4f6a1feea025f1fec1f7aa830364a943e3e7378c09b6f4f899dd4545ec6b0b4c6b97fe836846c3da1316007e2ca1b9ba00b2f7bccc81fb0ab9d8ffea3a5222eff16befa56a25cc49c41f5c96b3b7dc6c10cdfd04bfa2e5d2d1e7839544be52e5a623f7c3fa09be92e5de4161508efb83515a5d5de62cd858ce5debe4b722d3451ad711b82b206ebf9f3e46394b3328d70f7e29000879c6af9f49816dde2ff23ba7e436dcee186b093a65cba1e3ff193b123fe5c871077256b919fd1abf8ab607d1c038c4a861f73ee56ad67f68850180f82d972b02e16a1259250713e4f98f0b5da6a2e9a6223994e853444e3bfe67b192c829bddc3a90a7cae1db8d31cd9ffb9edcf8da67db51751beefed17b147da5427388a04a49f20258c0981f44e62f109c2a07d13d33d1d5c51508de251613fe15960b76c6f3b55fe47d0994202eb3cae97af699750c418f48fde10a27258c041ca3484a220c4875b07b85b159e272b0dd413ece28a06849413a70502a4eaf7f24d40d2105ef0b55476099598204e0164e484420acb49a72ffaef25d7c9a0e9f9ed0c7743055538ed9cceaa79923580e4fac0e877add0d2b25654095952f93b4fee6af31bf8a9c4dd783985ac0889b8093082566ec0905c7a6229f0803eedd4c4f71e468c106d48348d82cf28c25ece41ea180bf7021ffdbd9aca54ba831ca2b6d1a1493c1641ca3c270e44b1d5e6c152aa8ee42336c694a5232315e0a843919692d242941bfb71530f1375c1469aab1e542cb51ccc2c440b48959c08e4dfc4206233c7d7281ae749d91cd0341a1342321dec016f54cb14373c6f3a9b3e32302d487567759346b20fd30fc3a9c5a02149548b04c0a4c26804432d09bbf3106155b5b1d447d12483d25277929ec811fa75de152153e666cc4f096f72dbab483d40b3212db953c398812d88e560df998d70b2a38db9c7d897c4068634a37ae54172603285dbd2ae1e2bc5cd82d589209519aef2e1ab0acfe10306a81d087bff9ee594a5a49ce35ac0930165f7fe0d8f3ff2982a08aad342b85486e186e3c6f360844c2478f3e64aa654411125c6cca04f1c1a2641e509a425dd307e8302944fa947c3cbff0878ff9ce27f9b3c4c0fe05df489f1d261acfc7642cdd834fd871ffa9ecff74018aaad783c39d993edf106f18ff8e1a6cdbd0b9abf7ae67b5c644703e9f6c2a5660c90879709773f89e446f5c6b8c316ab2b439b89b868316896a0f43e25a670394fbdaf4edb53aca078a02ed03eeb2c33f7c79eadb10f7fafe0d973686bf1d3ce8bf5d09c4b737a210fa9b9ebf528dc61502a0f53059a19f88e60084e8205eff334cf6fe974b081c9cc83a12bbf658faede0865defd037b1ef92093700395ebb5fd2fc765474ceb43368ec85d4ab0066b2fe2564cdc2c90c1841637d912c65e18925996c7f107d78a52dda671ff040e04002c5e30efb44b3e4a12797d8874e09655598528ff2d1b743b74a3214a94ee01516954055f2a8fef7e2eb10702879d0e53c0f7bc4790a0386a6ff481ed8bbda5b69f2330a94bde15cfe11e811f427d043809fe03f1232bc73c3c866bf6269a360cf9020db048befd49bcdbe833bb42541a127182dbd471ac745f0f9a256155c3ba98619c11d3b59d87f4a19f19cc36a5497c33f4f479bddd95f491ff50b7de5ea82cf569760790d5b8b171de54bbb2ead3ad7164807bc565d30625dcd3189e9f010f650a293a6bd8037b61f087ca10dec0bf3aa84c1dfac40fb240be1217c5977edae81b8bc94d7173913fb96d042e458ad2e493330a0d50b9e4435555d3619b6a231687d6981fe58c617050d4942961fbacb41027b8671f231ad3e381e2b1c36e06fb7ba59547a9f2cd44e8e03f7a8f2408fc15e00e03db56902e2577830bc4f076ca3e474174f1ff6b88a8badf0262d1ec8ef97a20be35d47051d4dfe556dc6ae1465a45fb59c83b05940b3fff211f88688e73829a64aef493de3924622f5938e5472010d8c47bf80170c93f7a88fa8d0dcc6d02da128782c52f114f5a9c2964ffcd74bab75d94d963048d08b25600c43ac06f81375c0942e5ccfdc958b4ff35758e3258ea90f06e5e6bd9ca4de8be55a5c4ecdac470e65f94322d202aae80a3e329aa8f5ed3906850112ac63ace3cd1ab71aa9433a380cb9c0764bfd479f8e15d3c6ecd84b18f3990fc9c9e649939e9a783a6e226cb4bb8b4f063ddc265a08d4abbc7a2120aaec50b61116db2c96a9b2860634c79b0c3e64a5e9eac6df80bc6d64e8b9628953a3d1a99ad125d2ea91d69a92d91e3c196fc2ac835d45e25680f752793295da5e12b153d262f3c837665237a3f400e039e61a775e7df40e050d0d98e9ec19cf9e89670aa5505181ec7eba3c4c62f41a146a9c12d8b5095eb8ee583097f6343f21d4ed0fa86956433b552073c895f31c73cc25478e73e73c772e72e594738eb9e6962bd7b9e8ab7f5afeda7f5bfcb4fdb7fc6ffff176877f9b07dc2f4d87b17018724074750da8562eead901f0414351ba3573d44a20c2ed70555a91291ef4b9dc19fe407e1ebefe305f7a38a6f726de43e270440d96c87c9bd6770b7a56d04688566f0502772e86d750b51c744f2c6ba6d668ff813618e9315491e3fabacccfe6d35b10a9f10c134cb5af8bc940ec494b62987b0e1d5b79919fef3702726e4f1ffbbad3d025a756ecfd6a6ecd1a6db01cfdca3bcef1a8a63a9df1307c627fdab25703b067fc061aa6cd2c2e80170d29d5af69bf4b66b800837687538b2c4a2e6b0f2584ea1319f841158cbde94b5a48b90486a7e479ece59b0a23a2a99a7405660bd322aba4e6a2f65f3aa4e304b61e997af14040f836d0e910e0f0d070787d209ed6e98a3f83437d74cde02a2352e2d9a178390b0ece258b2d18d773c611353ad4ba42a64141b1ded450855302abf945da18251860e6760aea05e3ce94462d5d3fd61d4da677cb2b8dfb3857176bb061898005fa3965f2fb4be9594cfc6850511c162576c64fd5490e0f088b12e7b75935940d040f585f7af5d611d90f533a690ae362a34f20b546d17fe484f8b80645a32803efb86a0aa34a354e35675488550319ca282a4629bb16f5e5317e8a62df90fddc467be9267572cad9e09b6da4c2a59286380928e0fc58db8d70d1d870ca195b88d812db4d49d9201acac5ca07e025b6d722d6365263a83ffabf8ce731a78251fd3d59684f42a9f2e4509f74dcc643f1e24f0ced439e1623bd0e81450c26ac25b134ad7409bd939183895befdcd9d3db8f692b8b86cdfadd169604adb6a4ed2e464be132bbe8b3d8cf46c6f9fc9bb4789951fdf4c947d8717e00e5c4d58fa2c8205f5aebe213f508b2a8e7b815392d083b7606f6d630634b9541d4e50c83e6eaf4b9e117ff141e3dc2431111d362e98a8d8c927532ec55ca6be352084de2bd2fb60ec0ac15f410da5af52ddb5b556405976268028d21ee611459a465e82dfe34fbe0ff9bd4683d458e234d5321fa79b9308380a35da09e004da11e2787e1bafc4923e6b53dc4446778d576d35b7043b524f63d7fcf6b9d65f1dc64a0920c752768021e1a8bf9ac45550fd799952229b7f94fbff525f8766ad646a58ef06c09f3eead8d0ac0120f56b96d7e51034cdb41aeee47225a5249dfda28d44062b6a05d86ff04172c11543b0076140268159a976c1d87a321e76f15d19b7e650ab45694fe96f5b936d9cec9ca8349fff91c7993b4814b802a136c7e887d9f3eb27308954f80a9bb90d3729e9ed486f2b3f19c9ed53e02fabe0bf19cbc04cdedf4e40357aed95778c99d5e151952b3593a9fb8c8f4afb1154e345a2941a8a8f93b7471ce41879ff2b83f61bec192b8fdb7c6c6019e93b63172936ec788aa8c0848e4e5d82a77e9ce1cbf1aca20884f7d0724b47823b188d1efa854c01ea3601315eebcc86fb10b17265a4c1b28e6e9be5fc586dc2d7178cc9f285df12a571be21ce19d1c2e51918814810bd03a849f57e20cf4fd3717aeabb55fd1cdfa7d20a20eb72708bb41f483a4ca4c5e3bccd7f20098e8a107dbd1a11b26b1aa00d8d59417ae9a5e0a9ce5888cfde66af97ffd4f2fbf741c414185a27b226d3ec7635bd7f4d4ea6b1176d302e70bb0969a606d9b1d147c7a8d8f5f1ad2b6e7480760ac79ef138dddbea8a1f046d4adf575ab2e79049ca0c42066867c7f3a6a056895d3e31cec877333a602b1a8197e11d57e6f1e9391cf151c69f5dae6197d155ce60734f366ea93fb96fae32a62963837e8808650b39da01b9089b42f86564d8d95f07636b8a01ced89a001eca8f052ef667594783728d0f1af465a231dab49a7ba514d4cbec4041b4e6ce5a19d8b866e7b4902c9ce6ee45fc0a9f53e061415dc6a959ecc19a85f4a29dc53303eec976bfc15041d175dccacaad9a5f7ef9e157d431cea5298040abb41970dcd4b86b9c12ddf19eb1cf2833c21e7c265432c30ac5e9e1c31c8e8e2fef63497ef7059401f13e87526982f8d95b294deabc446b71e673656c081572680329528e806169eb45f00c11323cd5395e809357e943a2b9f119c34406e63fece0db1ee31dd960a76eed5bbc6191b414ba8fa188237271fe2733e7a136e60e30bfdbf0a67d14bd00005438bec43e313f13feb9b0a27fe13e72dc7be1cabdece739972cf3a3b2315ec0e38e8864b2d0db490e670e021f870df5d3d1287f1460258f21970a0710cdf2583f4d58c6e6a38b7dcad391043fbedc88fa1f1a70483e531357309b8efb02147cc32394b25d9c76b6c66fecc82eff91127a79f59ecdd37d963a6e1e58327a3abf07db04684ac9358a0fd231aa00bae9441b9f445cd86711e770ee08b7d211d9cc6bdd6b2b410bdf2f9e2a6aae6b9f52b4e0b4557c49aa621fbd6c5f930552d6fd8e727de21fb1a087bd2074f05a11c403a872a6d8902bdae66a1a6f2d9ce82abdc9ed9a2613d76fef51b4b2c2582a9665ee46bc2649be0a9de00c843e7eed687120aaeab786ca1e7b5ac0c58db893d46edfeb0e03fbd034d604938d0c96c8fb8ac4325f8c3e06c34fe67d9bd755089e9bccc4ab541ad2bd1170f7a207721279870a6e6727ad0386a725efcd0ede1b3fe419de8a486d88933a0c38e444b2c6f530381e46213a2a69ca6493055670441285ac6531b13f336837f90f6825cc10d65d8da69cab3c77b96fe628ff4e05c946cc0bc6b389ecb56245ec28e411255bf992c3ca517c2e4c3656d3fa9b9d872ab2402e197cf4675d232b905b0c864654e9b1f4bc7cfea14df9651b29af52419988399a80a2ce7302d54f201b5d168c156b7a87f0ef572dfe5a3943c601da3b8cb66ac2ab10ad55bc52afb7b4db734e2e829a5731fbed789f9f9dd38638433013600c6fe2d64017d128f6c024bd3fc7ede0bbc41bf2be70d70bac88db45dd8bb43dc94eafb153da010251ec1e6a31a1062e85227be33e8d7e6b768bb2a12b5954fcc906f692f044cb8b0659ab2fb3a87f1bcf8aebea437e39454f62ad8aa9ed91ae1a3d6ced1c8821570b49859dcf4b361ad4ad1d66c76be4a2d5ae7505b2e7da5580a2d574e64d8a346384bae937eed4b84d89315f7b95be9b73971c5a5d5469f269a73946c49fdfdef3147104d4461b7e5df7d0e46daaf2b293c10d7ee7c35d4454a5c950d9bc204e1da4e6086398d3973e2d0c59f1d9c04e82d667bcc4670065909713e612f022eb19fa14f0fcec19e899ca965d2b5a0d9cf3877f6fc1041ace49d23f28e458d100abd313f482c20efcd1fa2a5f7b1efbb7d75d6b5de81eda3cb1fd0d9162d46c3ce228e974d0cf6d69a963cf73461a4ebd2d04d8f0e1f5c6e2f018bdbf2d5b43569ce34689490c6ee364ba80b73a94b72ddf1968270bd1d69de53daa137c2513e8fe2b10bb598b2e06550467124af56066539ac4fb0e0b1e1580afc737bd75954689819acb5080177e997713d97f795c6c0bead8404f1ab5b20467633144c727634f74622332bd0132fd64fa7d5b5e65ce1cbbe676edfac891b207c817f69517a1efcab336cb46873de1d58b80d6c3b1963798b64a010a43593c8a37fb7f886822feb02f0cbe7c664de65681b3c74edf5268ff64a9ef764bb33e1b9823adbdec6bbf060cee701df62b09bf32193d99299b10baac737516bd4b9df07ea7acbba96f4738e213025cf7cc241e6888fdb9c58f0a64cd8d43defb19e91f602452ca7ddee75e7531db33557d488ddfb914821561169e7ffbb8ea571db5662df42502696659a8e5873de61989236ea4064e44d26359b54f8e83e7e719c7416b4587b0f722ac540ea118acc4f3011987f573b53410b5763a4274afb21d7d9129962aaa969cd45d3cf45930ba6627bdc40736549b7039c7d8f19aeec035959cefba98347210e91a369bf8a474bc8ee2366c6a0873b7ab5d9ebfa464590c3a3950dad902e140a9f86039a812f4dd78b08e0afa6170c72e79c1a4bf13d8bd17e92e6661b0dc7c9c3273412a8ea66977313ecf8c9e4e2a673a21b9cc3f5be9153e23ed0208d48a5080c2dcd0dd9c6adb5b3640f9967d409f199e91edfebcf15acdc9ba43952eedbe43f81c5f924e049524ea018badaa93fc3416ab49648b10025b23a6f3d2823ec9c9c497b6201ac74eb33bf11db1b5f684e83f49216584e423cd5ae12a0cf442130210d1b749b88bdeb8f695fd1d4ad7ffeceed3a2c33adf14665257c86bd95d8cf02b3e500228e118c616d495fb63b58e8129fe95c98d4b572075f0f9022781ef82578ab09739f8de7703d09d1433021c84ea70ef42a6aa425b90bf9f13db8de7c4aaa9657f9f44f0620d229754bc18fbe8e142d8c7e75aec323209b9a141a2f95e3839a2be293f44ec601d031c0a9338438de200cc91261d24a4fc6e2082342b7acbde7d4ddc3a6b716f151be830040fdd09243efc283b96ba3ecc476e37f2242d5fb97fdd6b538fc9f2bedbdb26b8100ef75108bb4e011cc077469c0472b3de82baae50d6fed0609d41d547a30b80b368d27c40e807c0f6dc3b830c48ebc4df71de09671b8caf8c58bb7f39eee44ce37c9f93750e000fec2030125742e085017db480331f813cea1969d258d0e1d6cd237106bbc25e00e181c360b634ef1b0bafca9179c1323d4c6e502bf7ea80acef45cf26f87a20b24ef4c1a94e72e5bc756a7fdf4c182601f8f2c0ee3227d614aea7909c42d292ba75060e005ba2b3dd4b849398da624eef768fadb66842ee9152f561eec1b2da49f006c240a9f9403a948ca2c5815af7831366ea95829c0fd0fee55d82651a1abfdda5a37a5240954c50fcc367a2ca7b611c436e5304edb56d310844a87993ea24925becbd21eeadb826e743f457194be5fe928418a96d0948acb0c386a081fad36e440efc9c2b304b368efc426858c525b2951d017877d695a624b41cc9d324476bfc2f39f760d50afa9452a17faaead978886923cb08e721695d42bf41092a372612557b5c7374a5bea554bd7b1a5e9c502f1786c453ebb700b411c1e305057085ad630b2f50e6b1d20deb9a5e9e06354a72cd7659f26bb734a2485b97cb542b40196acd1581b3b0012980639f2afdf5005882cc88a44ac57c0a33cbb8a2fbea1d9cbe558dd01bc6041a212052df7808531d6382ed2c79006f9e7ecabff84ebd8604c6f34f3755fb1af7d9a43490fd5797e2e7b857f04ccfb0f5ccb3bea154f5a2a98325382528db48bab7a10e812f513036e7f2ca0eec9bf3a0028aa01b7538a7e6c0f52bc5dc240f802ec596cb918839942313a76df3fb087743f9ff802a1f11fd60a7d4d119b74c85539d0e467ab1dfa0091ff35da09173e1d47d89bdf2097b701aa49bd66ab917690e69053bae5bdbc52d93d3e7ae9745cde1692bd0f327b6e84905d00391c18b041309f09bbbb5e4ef4e68e1047f70ed3911ef038f461c244136708b7af88e9cc2be6c6de1af52e3df34ff9ff0f3289daea4e979529202529115ce82e5a864765220419cb71b85e0f02c3c25e86453c71ba610f8da1953eea08449b71e6a898e23b378c63c773a0d2f6b659c1ade62c96c8b61b9fefb5a1779fb0512276cdaad5f8c9b1a8c026ba29cbe6eb7b142874601f2915f867e6ff1b19688b9ba32b9a2311e8b135548a18066d8ea897334388f2c76b41949130b8e4ca801fd5a0e7d821889fc5bfaafe66cec8ed67460e2ac521bbf14bbb01ec84ba4a8f526132efa7e3d2d26912575329ad33a71263d7b5c740417e1f833e184e295876f498486147259c38cf2f6047e487ed60f7cf23cde4970e10b5c2a40b96069f486d432eb9c2b2fe1d98a2036e156c9baf7cfa93149e7e282447a766945e89f720d449681b5434ed571010af0a7eb8da71938f617ff371d7b2b5c252c2f6ac7f58a0530851bc06ac5da885cd480bf973302b4e03fc390a697161f9f10d41aedea849d6c5faa60fa3b46b3d5c07cc2b0801f9c162a77aa5b58c6201057386925fa96b69d5b5e784a7c434c1e7ce35a497edc55e18612fec7228e6b23f059ada4497132f2fc4819e1b5fdb515200eda594bc34b1ac3bf00d566c2ad490b8276589c61aec0d11237003f827b022ed2570037c17bc22f753f336db226d2afbc10e831e6785d738e07a2d970a3b589123bde7d9abb1c2d3873f66ee00b88ae8d2b801da59642b3735506cfcb8cccb02b8096e65a3f8ff23791241fc78234a6d03b00f96bc9ff1fd574928f0151f69ff05faf8e54f87d9ea2b97c62a41638788bfac69614f53c5f74b587c4cb12b8980ac79e14e6e7fb1f79bc5c668afbabc75be378bfe302d9dd488f42c781301ea854910bb30778a00b0814225fc1423a44a8899443450d51cc9304fed0260c41c2e4809bf85a8a17bf2f4322f229f4487b2e941410c619c65749c9bf9f1074411b430feec23900fb6643a6996abde09502f780bd8ed9bfb19acda7a071b369ddccaea088336c6ce8a9c7c7812ed037a251754475c42c56a13cad8906752f0a91a51332b6042fd34b7e9f8b125b514c950a193b4fb9cb4bea245f2e22153489efa1f35f9561e73b51724c70cf9cd7d57e1b8aa1478610a6c61d3c46435de7f3689fffcb1877b3278138aec2a6fffb4fcdc078df47b0c5ed9895d1441647443e6743eaf05479d24ceebc1d4d349e201ba99a0feb616c1e63be584e9c05c8053277fe4536c81bb4fae0c107d0de3d3e3a56c83faf8a96f8094166fb478e756612397c407fb8550020b20e2e6347ecfc8000cc37868023024fdce9671eae8f36e50864560a9bc82dcb2fc8cba283e183184f4efaf855f9d7838f8ebe34df5fe9d14b35bf8c490756553a8af22d584436fa8920d7f497c7ffe1486f09b4929185e3758805e54162826abe344971a988b9cc12fdff9784ca0e46c24730b2501603e3000c0413d944a32dced712d5f374b5e3002a8c98c3b2bdeeac27baa280fa7dab9367356fcbbc384377c8b474084fa41efbe73c96a44bef234e3020d54309901c5034c473fc4977ce4fb7ce607be083edaa584882f7023f8ec9b6833e613ef9952017ac57867561e6809b51f94cefb3ae9d74f7117f8b3a0796cfc2bcca1d39fbc853a42e875488b9e2f15ea80cd034883f1aeaa87813586cec7d3e12d72b03ddebdd42d8ee04e567b0df31b3caa831e5827c7c0abde1412df8f89ef6092e619541a001fd87d93a3255bf4b467ea6e52b3410ca4e8a3ba3d3ac66d2942f3ac5a29169b21e7c0e772a70bd374ada7332e832041ce30753def80e4f75dcb13119ece8d55394304b2a2d153f879cea9fd911cf11fd8fa352134f1cd4862e2e204862b80896fbb6b0a1dd8694be493d6904a151ee4d437483fe4e58dc949a4e1f9d6493dad93ffbbcdcb251c1e2967142e9ea3cb144628aff2d99225fbf57f91b16f776e31be53ca3534652f32241b99a4b661270fe31044c6b33ec53b0dc025245b227129602e0bbacdcd4b1e4096c38d969ab38d039746f1559979176e1a46cfc019eb82f0a097910bba46b10621c7d21466c3733729b95b332060f340544085a9d1df3c46f709b63b42862cfb1a8faf8ab448144e113ae4610b155202f464057e20c7ea1937101033a90398e8ae8e3aee6ccad7869b5a8e656feead63ac2a80a5675591957579971529ce11e7f806b2a54a1c8f01cdc836bbf79632a53618e019aa1a1f4429f8716f98d7de3dd6a7797ceac15bbf7ac6ed8a2e3cc860ccbf03f3869faede81ee8e73f35151e97edab3796f9b717e6673ded9755dfd6d94feaf3cea25b7c7b09f8e91aeebbac96d3cbac6a4ab35d2684c1e9f7ada755d576bd795d9ea02eafd6e8dfbddffb40ac6afd39d56c1f87dfa09fa75c5ffccc9ecd47bb78cd86a148c5d7fdb285d5bf3d6db7adcfa9dc8edef33bb7501f3865da75f57ac076f8fffe92718237776fa75c5f073abe7fee92718e7fdb4bebb56d7b7c71ee7fc463976281366c277e76d83f07de2c86e5c76e2708e21366d1072f17b10ebe9edf16f1cefce1bc7fbe4e2f7c00fe317c7e3466e41411b843b0e168c91b7de317e0bba0e7be1a7608c670ce33e8e253663209fc727e18ed33b86f137fcac51a58f28f5566ab97f2685d7f518a23143cdbc957b9a6e76c70835df9a4717d4ec6aee445b4fd3857bdfb7ea10c7a1780793d813752c8aad55b1765ff775f306e3a953a73cb929ec14ebc247b12efc13fb768bc4be93d8887d5b65c45f1827f6d895dadf00bff4d3e9bedf096f775ef8f8b739c36ff8f9eaa7eff7f5b7ad6b646670b76f320dc3cc0cbff54e32335865c4f0f3c2c7563257bbfbddaf3559bce177dedaf93b6f7fa1281ddeda998d46e4cecf2f0dbb4e2bd9512f6f8d244f9cf78b444dec7186bebbad6e2ce73e75b63e5dc6f1739f23799056e8f3452a974aa4f39873de48e797326f5d24d2ddc827e0bf71c243db3c4ecf79275966d3dbf8657ce66c34e6576fa39e718fc971dd77fdcddf60e386ff8211f91e7fbbda8e91c118deaf62ecf1fb234f4c20f6c41f5a62226ae2b7d813890cc6ca2a07db7eac37725b61a4f1e9dcbab17b8497dbfac24ee40b2737f28595806eec6e668677c32f2cf7fbb5ea0d6fbddf7a7fbd5f2fec34c21986619759e137acf7fbebb8a1dbe3f302348637fc8426c9deb2f0f6a755466cfdfdefebfc82f7c32fbcf5ed577534a2549265366bde647d628f3d3e8c91dbd5af12e932f7726ba6516bbd955a5b9d2ecc7cc5786975f7d705687cb4e68ddcc80cee13f0bf8e5a239def7ee46fdf5fbfe1fdfe86c6c7d7f097fed2cf0b50a8cb58fefb47e9fee6f78afb4b49f6e57eb9cf0bd0189aa19e58643076776be7d73f2f7886ea4d96d9dee3fea7d168f40cd5b3be3befa356469cf7f1cbf8843eadafce4fb2d737f2fe7f52f027fcfc0274e3df7e55f16f78e1ad991bf27ee311f975dc46a4f3ebf8ac60dceea6607ceac1328eff9d941b698461e63e6e23f379018a844e38ba2ff71713b993fb9dbe99c827e0d6cc0dae7e5e80b71b77d2f9857c5e80c6c7e7932d3ffb19a218574d1863a3b75e7e2d91fbcbaf2af6e7d1ef33289f80fe04fc573f27fc86bf7c1bb7fbe5cf0b507feabd6e8623244eec6636bddf2ed3afca392676b7d65b3301dffdbf22a4480c0b9912a4539cb712489cf8b5be5bcfb82b34636dde4ae57b99de466595f5dddb777f2b9f7a4f3de5ceeeb42ab7bb151285d88875fc4c39bfaa58b93768e6862c979d800ff8470a4ae3466edfc9ed3237c0dbf804fc58f9b4feabacfaaaf0d473f5b2fb7decf7717e26dfd5a2f4c0771b1fc5c60d7facf4c0771f83fcc95f17a031597c8cf59da6dfc75eeed64f3d781b37ccdcf0eee37e9915de4bbbdb9ab7bbf585776737cedbddb01c9c9f7af03e6ed769859917de7ddc30b302d3ef0133e0c7aff7db8fdf67f4ad9418244ef42f1c666591c5d925b3bafb3d26f576b5d5756eddf8ad61b172c34eeb88f8b85f3df6a5a9dccfac10161f3759fc7b2b233e6e68ec6f59bfaa18d08ddd185bb9b76e0c3fadd9faaf8e974ebde3f6d8f5374037d6cf77ff76ddaf970c2c169015e7b12e4deb9771bffbf9657cd825f3b2dfca87f5e0619cdca6dcf0619eeff307dec8e3711b8fc7a3f1787cb7fb27439f9fe7f97d9a59cb3fcc33e891e297222c62b8e54c9c3e8724e54e4c62e33e2291fbe1d8f7c7f93ab94db9fd7f23b712767eac3312b91f46228d1b69cc8731fcb42e95dbeac6c6edc7e336dae06235ee8cfce3711b91c67d4ceee473e718825e768145e1e640d4edb5b01124c763e01ec8b747a9acd5a88961e4ce5b0fe9c6c7ad77ecbaa09181893eb6c4f046c2eed1b98499158ef9a8df33bcdd187e5a61c4ef7390194899c86387a5441e361277944adc61c8531fa52be3ecba79c284441d6762df474f24541393897d2f1375d4c9a0267147a0c41d5b12772426eec893e9d0bb7fcef9f975010a7d24b03291c795d8137914c663b127ea508c8f3d7ac43bf6441e9662a5c620233e56088b957af9c8bcddb874f371ff7e61e784ccac10f6423f91479ed82af6441e6762e52fad3072bf635f9aeefe6e7723df0d8d8d1b7e9f17a0b11182cecc096f664ec8fbbfe0fdc21b1abbb16fab1b7f65a87ec7bc34adfffcaa90e37703aeb0ca88e3675680371cf5e0e717fcbc008ddf0d8dc1b41da55ec325ce61a56736bf0e32b6a4a9c4e71029403a3ac5e9dde18325296730616a5323545cdebec58efef6e5db05287d4c49d313739c897fc7c7cce68938eacc78ad954f224d9c099a449a385b7b9a38807e1f2adeea38ef238dc1d85f2b97441a431269a6a4b9e2fcd8a5a0b7bf7de9fdb17e22c19e91d2379ce107079d806eecdbe8acd08cfdab4252506cdcd9686c7cfa559d8aadf0ce1e0bc86cd937849dd963fcbaf11b2a76372bdd0827d688e3fe1fe7db47220d49b127d21814bb83bd0b68d489adcde3a0d833d4efe3bcdd695dba56cb0c3d26ce503c66e83ff5d1f5b22e4a07cb6df07e7796b16bdbc0b1e3921bb9df0d81c3eb868adf4d0285bb6fa3932caff986c98edbeddd7012f2e74fe410f27f209c2fdfc70d43908b078f33eee3f6b82f7f7ef00dc3f8f4674632d3fccd27cb409da87b2676a357626b5595bf15ce206cd38936d428df8f7377d3dd59d07f6db8d53a4f9bef7413699c89dd9de5662476dd641137c69e48e3e96e37f89beb65aefdd089d2c1c6089b9e38182a5e90c994555ef65b6b9017357159ec83ba7c510689dfadf47723a156d1006ecec330b405b1d168cd1b562a8df01bbbaeeb42d0d9af4236c6ee9c2bb7fb845f57fbdbfb9b1bf711ce6a360763a5d23960b9056cffdaeeb5bd6b3b17619042c32b5fce1c3ef7bb8db9eacc79bc6216759639c99caec39a7bcc29e6843ac39c099c6869db2d3e3e9fd604cea775efa7b5abc0d3da24f0b41ee364d93e8e41b7151a1fb7351a1fcb9ef0201d36c8b8e878e5180a39a8db0c6cb2014febf13826e0696d36c59e5d5d50afa775efb0279ce0198de1416ef1818db4fa2063e151eb693d369b5d584f6bd7864607b9c56e6ed12814baf71f8fcdae6d8c46e3a7463acf20edc4ef4e73828181816d34a70103060c806da5335d384fabc379f3c64d6cdcee65f08663e3d3ea6e2b3c287ef7d30a2186072d722565c577c3a1e0c1277cb731c813192a1a8d2e26d8d127215bdcd90e3b3b3bc3018daba14b0dc62578da7007e3c29e389271678409122448902041820441c2c2c2c2c2c2c2c20aa15028140a85420dc5c4c4c4c4c4c4c4ae8c8d8d8d8d8d8d8d7d21232323232323233b53565656565656b671b8c595edd7cccccccccccc6ce3708b33dbef46da71649dd70f1527312214bfdb8a448c3ebfc4ef3e7b1e930f1bb36be63496398d7574d8bbc99cc6431df6ee31a7f18c0e7bb798d3f84587bd1bea3486398d099cc6c2e4a04686d26dd0fd0e7a1b74bf5b49e86d09f67b74ddec26104b902041585858d8164b10f6e4e26ea858585818140a7dda806eb130e3a2f8dd38dce2a04f42b6b8044fe3201df64c127ad9bb8fa162ab5e5468946475adeeb6fac99061630b72e19060bb0e83afc41fe71a7ae9b9fb07dd9d4b1566b6e3e0e060fc6eb88fbfb9afa8749dc7ed056ff77874026aba1b16e402026c27d8067682a72017f709688c8f1b6cbfdbaa17b9575c45dd8660bfdb0da5db0eec176cbffbada3db0cecac4cbe366677bf83baed07bd67de2bf1fbbe85f1fbbe84f19b0b5fb74efcba7de2775bdd4105ac7ad1de5657521ea4dbb45b14badf2ee7663eabacb0b8deed85c0e91aeab0771f701a6eaea7550f8a5fada7d50dab70573daddbf9b436df0de64cf6d161ef36e0cc85ba4d2f4a06d161ef7ef7d3ea56e0698d12785aa1039ed62de069edf27a5a8fb15bd7d3ea16dd7a5aa350e8be7a5a775732885ee41aeab657ea45c9d78bf422a35e94f522f719877c5abdcebbb353322683c4ef4e06d16d1e06cfe8308fdf9deca3db3eeeddc941bacde30e0beb18f6b932e8cba233927498bf8a3accdfc138eb6c7c6ad962f7b23c46a36974f76b341a8d7be9688c213d9f2a19397429ce7adcf0561e6167d346ec2d62772f61bd5ce6b6a6d1dffc74dc8cad7dcaa60c02590293b11aef9758eb41c7454d495d8a3b87820e460975d853bb866ea1fbe5357cafb85ff2203f151f8fc7a70d1c3aeeb87130883b3eb9e4a3368cb52ed461eed39a9fee152fd3fb07c5d61f1c64f59331e86e27a0e6bbbfc3a9505cde27a031ce7b9090304890c24243c32b573c0c7ed1611ebb339815c60035b5b2492644528c0a21924a442445870d2229325f080b1149d950441c7508c9975fa42688987ca8e87dd4901ad56951cee29545378b858b67167b10c656779186a950a6f2983a94c7541a534cce98daa24c264d1db11e114d112296faf407b15426c4528f104be159fcb3c8c994a4454b8b8f4e3d3a45e8d49ed77543c5790e9b3388a7be209eaa8278ca06e2292588a78a208e5a84b8480ac662eca314cb540a8052574a49294558cab0542129c35235489522056a541e3147628ec44689893225464aac14e2283564321566ba92c946a6229814330d623a84c912a63a9806611a83290e26434c5f30f5c0c404538e04989ad6a51899ec5feefea7bf4f37435d12641e13b294c7d2a14b8a98062f0dba24e9d2e1a53e97ae5c3af3e892e1252496be58a263c9d0921c4b87994a215eea816809d152a70c75ddf60e214468509c3d6c8cc4f9a538a4f02c22d4044a4f9c6f77ea2ba0b4a4195566caa0218853e2c8d07247ca294975a69c9a22f668b189e5f0b67e214e198338258a5818a3d16814d3d364c68e28258c9ae15511314888527a1cc520e92c26862c76c4287fe265ec88520677287d2b5dac8e181e313c62786688d911b32366e717190bb5d61a1b8bb569fa166bd3146bd3ccdbb7a292dc3d7bf6ecd9b3bbe936b768140adddbedb069757363f2c86d6f28341af5c39c73cef95011bea374b0adffb86cd3d45bf9af33ce75663fceddc87733959ba6756ff8e9effe3cb1121abbcebcb715e38690ad3b86c64c0f39cef8db78b89b471931bc5fe30df8d9d5f5ba3a9984a9f4d6cda69f989b660c5786ce598ef6c766b8472fcb6fc4da34dddddda17a5b39f4461e0b04cbe90d77700fd3dbcac3f256c2f9ddd5304cd5f4dfcd67d3cbf26f6dd775ad5babb9b79c6e0e6b6e2d6713cbd9dc57ce66fe67b32a5365665ee628f2dd6e554bd7edddefaea2622e1f2eb3359f56b7e37e93bfad4d0959d76a65157f553e02510c2bb549d44d05647539134256fe864637d5d33a1753f5f977a3a2721af75e3ead5def9bdddc621780a6d5332d97c7a69899e6de7b278a25f99de485c50ac50a8f47b7b8ce93eaabfeff5b1c974be4b3cb088da99ea1706c391e91c7a775bf4f2b347e9fd668347edff88b24abd7d902763c369b49b038993c2691c96432b9e82d598f59028c92dee753ec4af33793268e6697eb4acaba2fe9778b4cf5c930838b5c312c4d6b5440fce657bb49e4f10f099964dd1c8d33fcb4fa9f68341a8d463fcbb884a3cdd81ac5d2ac8b1236adbee81082aecbd00cb781b332b36b6f63d7090931c4eed30a37dde2e3d2f955e1bf26e4189a3704e7344397ce7f94a184577458de62579aca37398870efaeae66f3251d8fcd6657d7de2f2933c961392ba7199a2164b6ac3c7c9fb9d0ea871032d42dc54aa7d5a7781df77193e536e63e6e35879dc6e3cec718362ae10d3fad1232f4370ce7c9d05c5898dd9dddbc9562639ad67cf8637258fe2fff255bfdd0ea8787127258d62e98152cfe386f652e74a51896e64358d2fc1d2b335518a558a30df88a4502ebc3054c13dec8e451a9542a954cedc7587bb9446eaa9db772cf6d09c10d6e4e9944269149641299442691492412b993492412894422914964129944269149643479542a954a241299442691496412998c2addf68642a351b12e4daa8d448a753122b77f69c5ea2c2988d56926c5ea349562789485735c9a99c4b68879a94284b225fedd443eb598cb25161616d6725bedab6655b3d9543aad75b132339bcd26722fb75289546edf26142671144a91d8b8c9dc923262ac5df5428893cd6653fba0eaa8542ab76fac445e616de54e2693cabab8b97d914d67d5fba5b9dcafefe53e1e8fcd2e24539bdcbe914fb13b4e313b46b124794b133e640e7a8c6672e79dd45a6bad95cf0ac89d77f2b895cbed4c3aad49b7b9586b0d8546f7516f7777cf3bf9b46659b9c5ccbdac6a5275716d25b17692b98fce6555936bebaaaa79d4c2c27a5ab3dc62a553cb69cdb2ca72eeb91d9f4aa58e1e9362599a5619415eb7bda1d068742eb973d2a9dcc7e68eb19e566896a675eed87c5a9b3a7a3ceeb95945a350a811466aa4466aa4466aa44ef62135522335522335f2f9d5dccc4d15f5417ddc8ec7bda1d06834cbf1b893fb0b561e93c9a4d66259dc8c5a6bad359c779fce3aebacb3ce7a1995b46850dcd5a78edbde5068f418cd2296a59914cbd23c26c5b264717bb33e4092addc7923372891cced715d9cdc48277267e7ae8bb5d654cca7de4a5f3e276464f269b5dac88d7c5aebe2ed1b7f73c34fe65e3aad75f1522b0b4bb98f75f17127ab94482412a9dc47e64e3a975a595854c9abaaaaa7d56ae7bd74ee58e9dcbe56abe686b7ef8e954badb515b9338458ef17b98f47ad7556beefabbb923bc31b8677d3a967dcbf955d5dc8edcfed1b1beb901ab9dcc79dd45ea70941b9999b6aebbdbf94faf9056fe5466e312d4daae54e6ee4d752e9a7f7f1a88f5a8b6969ea5df5dcbe75ba4d92a2d5437525eb74b945a149fdfc4a32f772e72e312d4de6f34bb9bfe07ddcfa0b3e6ee4fe523e931376f2e9f7a7901be9b47e21b72f8afdf00bf99ce08787b12f8a7d1f9d625a9aeedbaa543e934899f3ffff53cc49d3eac230ac63c4fa5df7cdfb9deeefac5d576b3ff75de8786c5a3d6178672177d5bb8fba4d0b6b2793fbe86f573bf96c229fd67f991b94f14bb595b92b2bcb2960cba9e5cc4b2cf732e35e3a8d1e1f9bca6d747f5729b7914ab9b552a9cc5bb99f29a7a4722e8f569acc7c786c2a95ef72b9541e9d3fc6fa6ae28ce62a2709291fc54b21968ccc0dcd6d346e2ce49ba4aa5a5b5b5b5bbb72ad92cfd76552506ee656ba92948a7919aaf714cbb9773c1e8fc7ad4c32e7981a964caa2ae6dc9595dbacd29d8c4766ce2fd359a5b97cba8a19fadbeac63ab3bdee768ddb1add615d4e57b9ddeac6ca8df436d7c3b4ab54ee573337bc5d47572e9973cce5532d73d552bb56cbf76a0963259f5e36a67cf35e3ad279a0715ecd6ff9e3b1d9dcdb173d5dc50cd5dbeb728ed27959de9f9944e3c4503571deefd3da656d8acd685a5d511d3a7f9b73bbd1dc56b7381b1ffea199ca8f1373d2b47a9f3add87833ea41f1da3cfe4f3a3cc332fe390a120f2d3daf5a87928d61b7916eb6d1510eb33011b0cfa7edce0dbe5656e8f73cdb4c2cc707e7665e8bbc338350fd5da965001f1eb716a62a3f1d5b071331f6ea37a2aa7841cf61a95fb3d1e8fcbe57267e38691c69d84a08fb0bb39ac4fa7ff3e4dcd42b72fecf4ff6e2e26b7addbe0369f73b3e439433f56bece67f286392c8b3969fe76e6178add70372b879181358d58c248351d4a165573838320610e0307102450d44df3f6387dd86233c7e53df7c6f749c1631475d33cd705dfedf1fbf56e3fba925c4cef74f7750ff3a3fb41f22822756267ec8844a6448913fbfe82443ef361861e9148f7025ea6f73e1e9b48311a4d6b8102fee6b176dd6412e9ca80dd33cbac3053cde4f67d6742c8ca24f2d8a5fca7b52e561ea1cde8eeca4d2d4debf6dd62689ad6e5d275fd2856a6d9d5d5d5d595c79594b56bc995a4dd43a56736e417dd2756566534f9cc1757e743b888a298172dae1d60c7cbe57269dc5714fe549c65ecc24ad3b4da57bb2eb6e2dabedb57cccbf68db1c4bc60616d5f2cf74780aeedfbb505bdc4bcecb82ebe22b0afb29a5c595acd2cae2681e700ada7f57814f06c36ad729f187ef756d2b4eef8dd755e5e59312b2b1b8981c9d2e2da322e8d4be3d2b8342e8d4ba3969696f6e3adadada7d899ae2dadad7db5b7b2c4c06c6d8981c9ca7a5ab79a4dae2e3130544c2a2615b38b8a8b292786440c89189232ca0a7937dda3062c105b6d105d3aec27bcc7a6764d2157381e8d1a86b3fbbaaffbbaaffbea1c23cb70b39ba84961932b4d08bb7680b9cfda7d61d8e9cc0a3b2b2b2b2b8b6bcb5d62bdcb2c78a965dccad0adfd50e4a6607c2e97190b0b2bcb4941eb99a544be1fbd0cb9b313b9032c99728fc4c27299725a4ca4f349a7d8ea8a2997f544767d7905e882b98c59cc39ade56f2d33d7520758be5e4bf80b0bebe9cea397c15bdbc8c58505c3585c5b6fdc5bd995f4f086e1596c9e036a082a1fdd342b65d1f757c181db1fdc1d55c4dbdb1d15f0ed4dba954ee50ca57b6c687f745b6db68ab787c5da6db5d516543aed9ef4baa2aadd5ea9d5beea1d354be5fbd352b37dd8ec344dc1df740f9ba7a566f3b6745a094ddc5dbb57956ea912faf8ee5a6cff364ca9d32bb54ffbf7416f83742b95b0a28a77c546a03a089b52574cab59bb180a774715a1a5d36aa9d915d72a1df58ffae74737cdd34ae7dedea6526a1edd9d1f1f9d1fdf94eaa2aacd3ad864d30ad2ac9403dc8a6b15bb62fb87a5ebe3cbe6d1f5f1d1f5f1fdd1a529f8a35ba1e50b13d7449b359bee55ffecd8ccd0d0ccd2cab078d76a19754f7aa7bdaea86ab5d2bcad94c31770f707ee3e2aa624e2429f3403959a05119723ee9b8001c299f8c195bab71dec69954fefaefae77796c26c4add6eab7c7cd8bb2761a67727b5c36637d56e2bcddb6ea9f62acd6ef795da87dd580dfe0fa8daeabea34ae0cd077fb6586c76535748b3d8ad36d97c50d8877c3075f76d82eb544a957270f732e7f3a683476774d4ad745add93029a6da9daeab6da82bae5aab80c4007703ec5ded151a7d86b35bbcddb4eab2ca65769dc94aa0d41dd4aa77d2122f4f165e71c4ca97d7dd5ec56fbc6a795b28053ac9b5ea97d7dd2baea5e564add53eabe52a9765b9bc09534805ea97c2ba85aba2975fab77777ad5effb657aa969aeddb54bb3deaf52acdbbdb7d7cd9bc3da566a57b77d4e9b6ca57dd83c3504892bbd37993411e07eede40d09dc1db0237b74dc91c7757e1cd373857aade9dd48e8e5acd6ef9caa85b2a60755be5ab66fbceecfeb4532af6eaeecc0fde5ce0c9dd3d78f36dcedda9e5b0af542a8b2dd56e53b5526af6ee5add817b7bd2bbea15bbc55eb17c59aa1ddfdf75ebdd61f7ecf62e3bd39e9de94db5655bec54aab787ed3bd362fba8d933bd2c968543c95b0b2ef807ea56bafdb0e25a5d21d56aa919b6d83c6d9ff4aaa5a36ea55b2d5da0748f9a7d7be3a36eb5815a3a6956da47b7a5eeed49fbe8b47f7b7f7c7f7711565cabd7aa948fea81fa47fdeb9077b795f669ab1202a57b540f97a8601d02f420ca244876ae10823ba105cb3892495a91e12364a77191af9cca08b8e1e8cc81aa1b1892388330d38c341933a66c53d791ae3d80b04888910862478344b0e73e62de7423e1bedfe078b3ad87075ea5fb2acddb6eafd42c150f8f0a28cddbd8edd64ea9d4ebcebbabe6b1d8641bbbbd56a752ecde94babdfbcb6eb557ecdd0edcbb62b7b66a26dd78d83ceddf0eb4db53eaa6daed2df6baafd23b77477377cba9de6c7377ea9e9d5df51a38a5fa6d06dbad744bb7d2e9d6064a59ec49f794aaade66df7f42add795a69de96d651fff6a422dedea6d42b8b0dd4e98a6b35ef4e7ab7a959e9a68e70b3a54bbdb956aaef4f0f8bdd5b9066162db39b9651abd9ad430d38ddbdeacdb53a0fa4ead1ed49efb05aaab8fb9d37d50eb99a199a195ad9de159bad025ea951f74c424dc256baa20a28bdbe5216d4ebdb0546928cac8183063bacc5b40adde25131bc79c86ed8e25131ecbd77f2c56e31add251574ca9747a77d2ec766310007fc14b440d1ce051f7de430de0f6f6fa87bddb63fb4adda3a39bee61b5a29bc9f5623cdb6aa7bb4183bbacd6ee1d6be0cd682977a74eb1d7bddfa853ecdda6eed949f3eea4d4ecf56f4fa959eaf64add6f7bd26b96babd56c57841db65787b54ed6ed0606ff35654a97966d415536a465202448508318738c286db2da6553f7727f4e6d9236dd62c30bbbd52cffaa47c13dc8d50dc9d8637cf38b86bb3667d523eaabbd7f0a6199aeb19959addda9d69afd8eaf68ebab787bd73b0afd4bdcf59677775f66756e92fcd66d0f531e86280c5f61b3dcd6856197cdfa51141a1192e1434976b39450bbc63d0c580dc4a0ae8aa31882ce19ad004945b78799c707483939a8116a2c1e399c12c1433906ddac92624a3c1b739c32bd0e072fb643388eca2ca4606efd95b84504bb9a58486db63b0ca59e5d4655667c72eab5334deb1a3dbeb405769b863a741b43338096f19d44ad8cb2553a2e977972e5972c23b662c017bc149d8bacc5a6621bb905bc612da140dd84b7fa1f1d6193c6075f936dd8101eedbb20ab6725f97e9082e8725179c8d238c5f460daf16d5c34afdba4bad75d6467dd4c8a400571b4318afaa3824ad8cb0f1b5d15573cfdc3f06d002661c0bbc5a06a9b6ccb082ddafa3041aae221a4fbaeda71603203408b18f9d3a7f2b3e08092217f7a0411bf69a09d3598266d5d178c2c4060da7f6fc49e4b3a7009068211208ae4491203e74da98f162e5cc0cc191bf2d106dd69326c54380fec8212323860a1426486cd040614201025c5b6ad264f0d225060cba0102b0d702548a12f9f10d9e5a04f509c509ae2d2b4174e6b86963a60c18203f74d0a928316283860a7cf6ccd1b3a670ce50213e74e4b46153650a8f1b3534290a02d4868d9a303606a9d30a72564c45488f14d9919306cd182c427c50a7742a509c1851418ec15b981593cf1e3b4d9608b1419daab05061821c0387f488cf9e3b73d2a0190306cb901f365258a8d080c15b9b1a5ad89595eba7203d4a74c7ce1c3969d080c13244c80f1f361ad1a540f1e0ccac14290c17aaa8100908d283078911d1283e7804b6e0823ca517b3a997541e75d4e266c9585c2b9f96d1dce89b29cbf498de5b05b280ff327ae900dfe572fd1612ebea4a37ddaa255593e9b50ca01490b4421a8fb031c3f0fbea77d8c6a84123121660747b937ac012c40c07d9253b42e1e4d1cd5d56bdbb84e95c169aa9addc4135940cc254d98874c99803dcba0c26452ca72e19d58cacf6f586701f867b33d8008304ab1ad94dba18c2400d2e9af1ee58a041b13e33fe7db32a00614676bd7459df3b1dbc63800661b800071b5a55713734851939060320472cb396592e2668f51db5c94f83705b24050c6366c0a306eb80348d48cd7cf55c1d3d661c5d2a78c05729981140946458201bc75048a8b2f28560655ce05ea09e9833f8f66506d60b4d4e48228051b862c60534e6e078034009051aef1220c9ad66d406f30ec38e3883ca3fbc25359869551f628066600cc03623695c00278f54baad02053d2039014e523dcb0c268d15981f8e735f721392c8b9e587291c00835c2e99802c3809bf6131e1a452eea9a2ca60f23847c06e403e9650bebaf93edc014ebe0c60310771ca3f6482e31c9c3406604a4082b91a905c590210786d891dc9da74d3439fc17c1ca011c0c9a399510153415b050ec6a56bf47adf341b72e926d8108c4863d23d7b2fb86e10fefdd2ce772b8a3cdee905720cba8ec92d64db8ca59ad6a0543b425d30170cce322846e54a78c75e0248305740159a1e4063d0ecec359e2e73f5fbe2a982482244d8ab972e7d0ebf78c93265870c182db47a78d4bb3a15284c901011e203070c16e6dedc1440111c30b0dd39724e65b214c00307079e5e018c867af258a9d241eec1db00ada5128ea3060d972b56a6488162d8240434c199d91225203e9c2ad5c9ce44264c6de8b056438054f11c30481aaf0809d286243408cb95b7056c5c59cae4d128e0cb4c191481f0cc8903c74d19203e7aeca04e3768b0506182dc9b1a5ad8951515d375f243478eab456b6bbc74c92263848836b6ae2c17b10f27110e1ab0218d30181c68c244394284c78b274e9c00d9a0c1018304cb27e58ecaa15cc58d294b7203c88cde455f280bca819080967f3262e693d56430da4bd673ad0ff6058e035e01cc00992b6b7d9600acf72a5b7dd3a5caca379991c763361adfecbefe87dd3de95e4d0424dcafd56c1f1e757a67275de0d6c6b6a6a6200d2d0a0b381087efefda3c30b8a7d73f2cde2edb7529d8146cb2b41375d54dadeeedadf68f6fbf6daf55ba16d5a9344b97dd6ae9f8b059bc1ce34add6b21e5cb26a272d128354a06ad01dc8e9ad1dd51a763c444f1ae550d520d6365d5bc32b932aa1c95ed936651b335aea6dd66a57d6a124413d86d073b45f404de9f5d9aa601ad3858e7463ea3e745d7dd06ef46c39fbe566ff4aa7fb79eea8f696e9beb222e01633e1e61fd3a5736f3ec59f91ddc07ef4606af6bbdf42af8fdb9f4fbaa6565f6ffd779eedff8f95fc32ef2e737e62d0ffff01b3d3fd34c67188626f7bb39bb41de7593ef293fd2dfa71ef3ff5bf37f3e86c85ff933ece6af943f7778ae7f9d8f2132d5bf76f57ffe87f34778b5914aeba8bfc9e787ddace93ebbc77fa45a5ebe83e3bf60bac11b7376d17432e70fab62fb172e9a7282bb21fb1bbb9ea9bcaba1ee9b5ffb6be41bbde7f7773f3fa63f66b1fffce6acff15fcd91f7f9973ce708407fb9cb39b3faac9fe95018e089e79e59591fc9b03bcfbfa3d73c83bf9aee72aecbbee7d6877c279873027eb87df353ef37561ce47b7f7dde0eceebc4422d06e32eaa215887e4ec247e3ffdbe76c48c38fccae06fc67fefbd7658f54e63f1a3f43c804f2002dc075ed5ff570cecf749bdacd0172ce0fbf8bf748b057bef9dfa76afbffffaf5cc1a844e6b73b2e8df93bfcf15fe7777fee07e4055980fecff9df7e405666a3fbfad5ff6daf1ffac85ffe3f83279093daf847f7f3ffdb3fbc95b3decdffbabf202b5f6702af75ce6e767f41ceef95938ffca39bffffed07e4a451bf7b74ffbf6d7f971892811e7b5f3ff2ddae4722df9427be08bc01f77386b391fa4dada872ee3f7ffeff32377a9fa790f18a2830f9462195d4e704a347964c381b75210165918f701672115010a922a47c3c84544d13108cb01b4048ad8a1f1849097880a206410323029c2c3012105dc0a8eb6c81944a4b1b2326589b33f96c8cde145bdd29276c7bd1cb16912240b6e84a26abab821d338c8a8d6db24c05eaf6b2c0e6ba7243ac145ef70af1edd670d76ec2dda13ee84d0296b45933e9563aa5a34eb177d5eb1ff58ad550b7afd8eab66f0355d563783c1b23dc9f857f96b97c65a08bcfa4debc68c1c201051c3d6b76c5ae9066a7ef0750748732ede974bd7468f0101d5021d44037514565e2ca13427b78770a04155c9c1f1303b4d8c0b8ccc1fb82208cdf15909141557c2c5cfbf412719873a575966310a12759367e729c2834137cf7ac61520104ba8a021901d4e8f952288e7be8e8878f274b06b68fceaab4dc4c6945499066461cbc776dd0931e88ea54c80a924b57b74bd336d602efee622ae38b4fdcd8926986bb1bc0b109eb4693d76176abf50aa2ad6ed89b5ed2a65789a69782a617d4eb361e76876de8ded469557bad6e6d756fab9b4160569a95e6dd6d69756f03edbc8ca002706a06805457b7575cab77d53d6dd86e00b7a10de0f6b5ba826a97c2ccccecc49829eefbad19804600bd00277cbf25f7db168fbab777b26e9bb7a29add7b8b47ddabd3521b381b3470773d8080665729f7adade66db5556ade568323dd03bdd9257529760f8f2eb03a3de3fbbb467703075bbadda34ea9812d600bd07784bbb3f026d78b6b96badd4afbceb25b6d8a1349b2b024140a63c34892255fecfdb6c9f68bbdf7deee0e75f704ee8ec0b9d8f69b9919578026d75558743bd8596ade161b55db705bbbadaea84eb1d707f4e6d6db8aaadd19dfdf35f9b6b7d23da5eadd55afd2beade65b53cb92efd949b77f7c5a88bedfa654bb1d389552b37bd20c596c362bc7029bcd62e3b498b8fb9837b5fcb450b8fbb5bad552a73bef6e2be50b8352efb27f58ad96ba7b0e6f66c5c962b57b7b523eedd9949a95e669cfb659acf6ac99d597a54eb3d8bd27dd62b75bba03f7b677d46c167bb7b7d8ebd652b1d3bbeb1fdebe62b5d5acb48f0f7bb7a7d46915109bed2bcb52b57feebec09b596478d89dc56aa53b6f5b65b1b3d4e9b52ae5a36637356fbba95be9dedfdd9ef4ae997574776c96ea6207da6deab40a589d6eec766ffbf8a8dbad148b12d61f9f5ea97bfb67b7f756bab54dac31d32bb5894503ab6fe76d575ca97b1b0fbba977d53dedce62f1b2d2ad7653575ca5d93c16dc73260deed951decb6c5901032577fcf5c4c8bdf0c8a057c541f0c761e89ea0e9ae25b9fb34d123fa43d0c06d1f1f954e5b35c3fbb3736e814ea7522e61502a1f564ba7ad9a8569ffae553eb329d4ee4f4b37a5eadd51f3eeccb65729846726bd84a1b16d38b88d06dd70635d3e80a840a182043a0a153ed0918833ec060e7618f44add33c3fbb3d3b2d797c5a3dbc1d6fe5dab58a2dbc1de5ea57c665318ee8ec39bcde69fb69a5d7116b882ba9566b7dab33bea346dbb8777c6f777c5e056553ad2e68a46110b4212b42727010c0d19e3e80b0b9c2ef447015713263dcec46afaf0c70a26672a249c0242a6a9b4713e82a0b7202a03d33c06255c50e1c20a9f1b0274df16e82664c7a05358960e1140730929284c964d969ac192e614489384a085bdab779f02999a5546d8202ba5611e5a7e642a03818957a75c290d20060c56a648685aca7d482d112434c58a04b847b9cc1542881f50381273e4505ed2c890cc212405a26f7ff28cc22d139c5e383f28ca93f1f2ec3d4bfca06841254ec640588a13d7a667dc484d463373a8cd071093d8ec1993b73ecd51929552f804c5962f4565c91b35430b7a1c98f28c124d78b22689219d23264fc788d81814a390188a29f25beaac7d9053e7488a063d7e088e1338869c5822e78d8d673106c423288188ba58083e8a4dc8162fc6c0d0bbb1fdb8287c529228c9c4c2c2de2728f498a65e7d3e284d9f39244395acca57002450f8a6382906888af31ba004840faa41e7f8c34b0bd510065ff32511d5ad471d2e572478944b7751d0fec4219500ce099f6c4880804bc9534ecdf90025ba4dd13269308fcc3f008991b6e70525147d60ee768864cf0b3b462d31ee04663c58d0225411a40d77cda54525395bec7c794a92a43f549aee9edac30442a9536210cd70916814804ad6e954921e6af220b11c08f3583a15430da4e374889557a547a7ee4ce520b3a986dfa13a74aa421630b02455ed103f7f4e993d42d18c2336c600ce732a2bc9834673fabc90b125cea2283d5f0e84a0a2a19551b3a868c75131549b2a2dbd318b710090d04704c895257cb62c620985858e0fe1164691298b40aa285af3e40ca43340982c6e15896201d61716634d9145134734dce821040d82103d161fd85173aa4ecd4df4838dc50339ece8de3d410b3c04179364c870b992e5d4e099ada94c9c1a852292088e26047ba60049d18247824957e8d3d4141a435bf6c060d851b181c2942111bd277bd21cb0730484a91b72104948e0e5470f1a0ca6f46cd4f9a32871ce0306124c7108e3b68294d85a981f70a6d632a9250bb9c4b6405233f56588838f1e246d90742233e582b637f01ca0f14607ac54a420472861dde60a37082885e74db3085b065222dab24a81a101771598a2cc9e11cb5247bae4a92004428b0f7ca7dc522f283179b2308089079e52a5a44dfcb600e152c50e994ea526f8e980028a1642490d4ba5c87cf429c025888c52708f4a59b111212453ac17687988d4a7532cc09462a3e6d0863fa40e69e95be4a2a364ce1d77f7c53ca4dc44f2e2a31b78c9297277476e41e08e9492c4e0eed4bfec96aadd93e2b9fbd3dddd29962f9a1f2c40db0e7636fb7727b6dfb4d2050bdc16b42d605bb0b6406d818205690bd01644172c7073736b7363735b73537353e096e686e616755bd0e6d6d6d6c6d6b6d6a6d6a6a02dad0dad2ddab680cd8dad8d8d8d6d8d4d8d4d015b1a1b1a5b946dc19adb5adb1adbdada9ada9a82b5b435b4b5e8da023537b5363536b535353535056a696a686a51b5050adc14b4296053b0a6404d810205690ad01444152c48734b6b4b634b5b4b534b539096968696164d5b80e686d686c686b686a686a6002d0d0d0d2d8ab620ea166d8bb245d7a26a5105d1b4285a341a6d11753c77d77af3d8c3ddeda2daac19dfdff56ccb8747cd62efbe7974d1024514c591cf1dc5164516c515c5f369e7ee48f713de348aca62eda4ee5ec0dd09b8fb007717e0ee5fee5ec1dd2de2e2f0720fe0ee595cae85e5ee578ef4d2342e9b46b0fda69d5effb0761b0fbbb77fd88dddd2b97bd3ddabdc9dca97bb01dcd666cddaccd0cab4d46de076ff2d4af73f74f714de84c1dca7d83bea14cd6ccd2c5ae6b6a6cd1afc21f9eff1cfe2ee76de7c07edaa7bee47dc7d8537dfa36bb366596c5f9a59d40cad6cca57d5d2d9a551f5a47bee06dc3dcf9b2e10d766cdd8d6d2a479daa8990234b36899969aa795e6d15dabd8bb6dd5ee0c0f2d1a5d4393dad9a5a54ddbd0f0d8cab055332d752bcdde990566b7daea0a6af60cbb222bbd1b6c1175f734f7bfbabb9f37dda35f03b567ee1fe6ee586fea1c59eadf9e1470d765b37275fb2add57ecae0b83f649b32cae7f6f6fdbbd3f2dd543dd9e52f7f6a477ee9edcfe6103a518badc7debcdac469b35bba302d2b19066adbc990ddd9d05760f2f4b05c4eec06df54eda27bdcb7dbeadeef56977dd565ab7e5cd97e4ee57decc12dcf7a480583e6ab6f98beecee5cd8ce5ee59defc53ee0d5a50a7d76a96aabd56c3b6b60add57eade86db54bb3dddfbc3e6697718d2dd8f4677a772cf31dcbdcf9baf61cb87b7c55ef7b43add7c526a76abdd2baed5c0bd2c757afddbd4bcedbe52f358ec2c369ba5eeab744fa9d7ecdf969add5ea97cd4bd3ebeeade56a5d9ed5e71ad6eb752be2c166f4bdd1d76f71fe35fc6ddb3de7410eedaacd95960569ab5abee9ddd65b55bed5ff6ccaeba77f6775d51d6e2ef5add2bbbb2c05ef5ae7f656a6c746c6e2dcaa87b76d2adf64c7bad4af3eef6b0d8ed59dfdf56fb97353bbb4aab5badd48eaa3da3f261f3cedc9de94d5f73772d6ffa769f52f7aa52bcada56e03edf6c6c3ee2bb6ba95de61f7f6a43b308be5a3f2b9fbabdddd75f7a5bb6777cf62b359b92e2f79947e383818bbee4a2349a97430eb7f416edf48bd7fe9b4b1dc38769cde48a78dbc09779c76075de4d6ce4177239f3670ec381aeece6e8681dcbf974e4119714be4fe0d43b905b980d0fb9f82bff516ccfbab8372e76d02b97f0b720191dccaa60e5b2e917bef8d7c463517a3bbdf8dd3fb370c77e76d42efdf1ee772583e053f4ebb4e8fcb34b273502f9dca9d99727a3f870cff84b1dcbf03f313061710cb671d12f9fc411df66e41e40feeb0778f0d6a1af58edbb83d6ef9b42a9332e204957be982606e77c3486ef729a8f76f417789740a72596e250c260c2ec60d23b97372e70d22f9643e21e8b0778c4f9c7330b9974c390ec927071df68ef5691ab7f383e830e353cfb8fd300e1f426a272b49583f0af594dd74997b361b946429b1dedb61a9b4291e9a1e4e8a3aae8f25d47c690da8bcc750e40a66605eac015472212a065e2357376bea7be25df8acbe46569abc298d12679203504e11698a6f08b24257fa9ae0db3d82720f4711de49c98dd660e6c71348d0c1b176b87c82873a5f3169c073d854f60844817237bd1f8f65121e9ffb10ea8b1cf1ca5cb0dc656341484f4833f89e0214e4f35a08c98e5efc7d8d0899320a48552ebd20e21504e3e965dcd8e1737b9c3f4a4b4af3c902f91e2d0e3d0a2524de8bc4964e1221eb8089d31f3326e621e96d476c337d4215369c911b24efc86d2e27ff147caea2c0ffa0f071229930389d619a77b3657dcd14f2942b35b183d53c52e302e83c7af9c840f05508305f4b6c7ccfe48b8d2ba866c3fd1af0d164d611cfe844e16bd94cf9b540566791cdf44b0a91712d7de2269cd45c2a08896f62e684cec3c5c6ab88b4a61dd9a83a01ae3d198a00235ec24fce1761127229499038152674f4dd4e64349464e5d716b0c9872824c9762cb4c05463005fc085d285f0c6c9e718e1e7624032f806ac471e0b00d887a063e2d72afbab3495bec4a7c667e8c3c1a953a64e88b5276bddc54e28c3836bf91c5daf02a57cea2bd17374e0a04d6162e4522c2f6e60edc0e7991ed022305c793072505881043087f146c42d5501cb235a33025652ea837f4973c39da81d6abe4f64790705449733c74ecea2e9d20f89bdfc14b0144f41d0f0eb70c9fb4aa1b24e03108d2b76926a243b42fd1501dacc7506fe416994f7a33dba9340834c83819dc31f1a6918a9e209fde0ca5ccab2b286024db7c01f9d33d810f19e0a539187b8f093dd0071e19f0c58727970d2e24c2070d051177b7ec4c1fc261b1f8f448e83b6e481d1a721b1c3e10899b9a5536e7e4681b06fb394a7df35882f08d194b780a6c3119970940997d079bf2d6e3c88094b4f27ce938cc565eebf5c01e563884a9ea18df53b91a978bbab2cd748fce3f9a4e0016bf86b93731469d39a1001ca735cf8f22303731e0b9b485f49f1cf978935dd851b010f760598790c0af43e7376b89c1aa33ccc06a6969403ebd35380394b0c0a2fe1c40bed6684d4635a2a7a3082027dda8ed7346a1078520a141f46013bb9d1e207374928ea3d31076943766f5a08e09ab78b1ce137b8b107964265f46d320eb84002a478b435355a2d86d6ff96a1ef8bc93c1a2d47799144f4af3e01ba01381c8e36e7ca43750dfa514e99998c09f00e6e24bd97c53f0faac4a20e5017c087809174b83c00fc92487bfe439901f9f3c4d0748010ea0844fa329d17426e878d91a70045928b2234e76d3460ce8118ad34030e707e0e1b25bfc44acbd55cabbc71879b7f24e3e93de23ebc5a283a1dc5117c8a1725f48f897ea8330b9c03993cefe4e27246660a7d18070178171448dc4f23f93484defc9a444d0f9785967d88faa417bd729f8d817c9b394ebc612130fd0243955341f3e28ef2434aefe8e3474fa092e4d33c2d7a9b20c67b8528a5b92e317fc4222db95301246ea5f3fc691290af911af56ed8bef81a0e47ba45182d1eaa7090d6cea9722b8a369a2b43a207000f001a8609a9fff0744387e97143bf5b40bc96be6a7e8b22fda41746dec84a282da6c2246d0046a27708f5c17ba0fc129270e9a387a5b4f1947f9c25374ee054986f114aebdf80b0b99596ef67f031e47ad4e2321a210c7ecb9413fe4ae8e16e9a2efd9039015cec48815c66411237452de4787e69bcd399a8662e09e5075506681a1812f98f0d67b98405e47bd283e6a6f951e9f186ec31968921fa2d3eed3258f4c379c8607b3c8b4edc8f1123b9ee18e53324aaf0889dbc5c41a7430e6630cf73919ba43b8061f080812832971b65dec11c03971347f82c1033eda942a2d3f810e2b592027019059e7911012b6ff485fac63844e78a4d4ab87e36a4c825a452f7cb44a04ff0038a8b22d2313f3d017a2bca435753818fd63a42c15f8400f3ba2aa363b7f47d970abe870931f840bfd8d334886537cb1b6f4809c6fba414f0ba1c639e111f215f6a8880ec5b63dc50212adec188746d94045ac9882bc3b15344eba97ec9284c097a13248ad30f0cddb8db2a2f9bb9c0ea6da1aabe01a2866e73c7e117a51ebec7abe63964c965095d213c45a1a52622438a3f1900e815b5c8f49a10ffb29e281b57224245ae24098e5bcdd469403ee0be90ce943d45d0f34f64ce2781927047169a5ac51fac47f1658a0b62081c97d106945b2a04492728f4c975a043fa6c5a24e9178214e408441eee03fbe04c222ddd4794402e88b8e3c5cab0bca93488fac3ddf2b598d1d125c2c4f91c0448b20d25563e791621071ab07c4a7f6e5e5708c6e7cd6191cf80f0e436a65c39579a221f84c7d72370b972a00d61ee5c00a59e33d2843f45886665a12ecd76e6bf0fa0c2f78302e93be100ba2ecfe0736a8bf308390cbd0e1484d720a28b0b5872e90871ddef403c3486bb34a7f2c92bbf68cb7b2a20e64b7af1f71dd8203e5722e66b40b21c008c1447a085896ea428c5d351a223eb41ff1d68e55e3e3d3db212c52e4db564670151129a00ac232382c8e19209eca2b9fc04f59b2045ded322a7bcb88d3e0150edf150f1f26fd3003a0bc9d1471092f0744d2f9f22a12ea8801e37a3e3cfd1dcc9bd6019259d97a593034111266381cbe168908fbe0f1f1fbf74a59ab787bf4fab54411fb9cb929d57a072232e9cbed3a530cd68ccdd7be96be4c24043f56c0770b85db60f3e8bcde89360520fc145260f60a289e33d61c61353e427ecd83218972b7f28e722e378b3f43c6a027d5628525fe80fcee9348ae45d1a5df82b89f43e069b06b94a9ddd6735437f97959133c105f4941867f49700b29c82c191cc64cb0fec4609c2c705d8e637561e3d968534599007e503ca90fb508964f8101a12f284b9c0b7524175c033393c54264ab9c89d202e01c614dd8503a0f3805423cf1985c62705bed05806b63ccb4b919ca4cae4ef1056caa10a21b812b90d39d0e2853c09915dc680e34e267c142aa7792be3796f26721d0850b25c004e9eecca9b7ce3fa47f7d1703eeef84153d9aef135192e7e0e53a02ff46188f7f8350f63f0d57e4ac20712abf27630f4f00d7d4e78200a767e288b953c85c4d9838101d140acf86b89d1f0465b94ee230a90ec24141efdce21f26e257abc881e33741814707e0d2717f0a30b6faf898ad843a10fb2155078f96a2a89f388e4ae25c01ed700a186ff7182c4052cd67835087a9c0cd2ccb9042179a3180a321a11448ee2020a3d8150c71fb306b80f3a15bec40b2cf9130744df01c5a2ac2de2e933bc9f74008d9868245d35e02c1c15396c500bd786852dafa2691116626693a63ae5fac05b5a4ecc722a235fecf100ec10d17fb4fcef0e4572b747a13b70a0237721c0fa39427e3458124b0e8827e4c99031e532e428724a5b89de459b1f3dd8dce3241ea5c808a02b9e80293e7e054b9e07c1f3cc7d3646e5017012641a1a02e807d12c58c6a2b7cfa265ea95f1a0e98b68f19bb11223475194d112629cf149c841396d4b00379405d423c2c0e6bb6c2d8fe6b0ccaff151fa194cccffa131f813463cfcac2ace0358407b327617be4a15a4633a63e246fcc0034209e1817a3dfcada4789327751eb0c9a63fcdf9e49a004be4ff82f29c08c5808fcc19cf292b93d14a01070dc18197cc020d8de7c836ca75561a9f3994d082e45cb896b8899e74808b1fbb8c654f2ecadc9282c43c8143d40d1d3dfd6ecde1435fa4fd11b9fd6b9d7b16ab2b6f24f1359f1b41af6e7df21ca3ca645287457498307abe00254a2e130d49beeec0a057391593dd4462e3bad8e6792d092fdfd269d1733271d41ff82cfa5e22397fa059e21f59f5b9803e3fbfe7caa74c4655c86bf031f916613cf8a08f30ddc2899e7752e5c89f1ca9e27baa6038dde09a1f914285de6109c14301842533b7d0e54930c6bc5fc950cf27470657cb86ea15542df9366e9a64153ce4b4080e95349f9544fa861c400ef84e7320a59fef3224c8b1069c8fa8064636245ce15f64cc3ec40ce4270af1977234f5493512f98d6a819b183cf5570270f24a207f9e4f4afc665af8fda3af0a3ea04a252f953d72091c30b2a11150f21c974f6f94492edece1f332ed721811c93e27a8b60489187cca1b946b2623c9728389a838708ce6c12c003d431e4133625d07aa2b2030240efa3bc395cf9a7d04b2bb8fd9717428d8494e9255525c8471448f2aa256a3cc12824cfa3ce81b6f1a5e0390032d21a74f8c820765cd15c3e8238a117c537d4210eda506f4a6611c35076133dd155c0ec79628b39b90cc181b71164908e1579f8f301917ce6cd01b7e350e763c33eaf681bc04717e57c4fb348bed0ccf3382534e5330c147d1c381779cbace3312101b9cf5001e744c52003b018e59720c6d01b06e764bd0338fed45489f3e96b92172d22fc4150705eec998e1c01c2db7f8863275fd863e5ffb24c3c4f9edd3b93f8bd8642cc67e4038847a92146070ac3f740f65479465f550f282df26f6756bc470018b2112fd2e79020a729cd01e0035420d08494703836c3056d36a7cb318095f21225c2fc213ecf6404beb205ba4eee22c1585f31052827c1c1e1e14cc757c559febe454d2e5660000e691325c70156e7573453de4882cb0f4cb9e276720efc562e725794c59f147ae3ba49878f9d84c965f2207236a5d8fe9415311e69c0d5053968e231beae74d6852da77367c8157575e541413cef82472232d36b2e1381b095ab1018d2409a63f20be8c41dcc7275934ed81fe1f31f38c352eeb023826fea22a4776c35cee7539e478801f85170061f6434f7735bcce8433a107c4813334e6947258771e83e24cc0c6f9489857e7208c1a7420ebd2824645fb735e771047564351748690c72b43c9a2c579e050faa3c2092570e80458573f212e025a32098658051e301282e78be197e4e4131ed5dc0638e23488a0b40b3fa43448dbe0ba4a41e035f7a1b898f72872b465f610e2f5358f0ba229a3bbd78e980531023e339a9719d8a81c59c089494c783b8c60b2df2c95342f5b991e244793a1ff2dc2b0501b2294a66af331e92ad62bc7089255e7a436012184b80b5afd2218a364448efff22a5398134433e062851eec692fb385ad8e9aa33e7cf19a0427f9802c3496c91682c7794dccfb08bdf3082e2419488a3ab4e55fac2b6badf2235c919ccf893f9ecd1e05faafe64116fbef80b06db178327bf27647c9213fd61e2805948573be5cea5681dba2d22c07d9fb6b00c0808957b4570ff0b033d0f88429f637dc9e1608e77720a37640e2380a67cc78e8d6398b4c88d6ec9ffb227c84fb425f8c32cdb9be2d2e1c70274f1e819271f43b5e06130bcf9aba2bd3700cbf89788144de08f889722c84e074843f05a98ae1a80a8190d5647c21b40a0d863536879b4867e009c887a2930947eb8b647732d31e0826e0cbc2600c21c4f06a9d620c78337abc67c9f3b2b5e8eda06b7945675a32343bfa3a5f854b118faaa42da233aa52197e591a1f3984ddfaa3e1219cdde8c47a03cd3783a99781239b09941a012ffa2911e8d6cc4e82959fa721a762efc49a3ad93f0b3e87d4497bee513e2dbe120e7c6bc7cab590757fb2be05cd3324fe20a4a3ed4a6caef1942e22dd415ba205380f6d37f7fc391810c03efea11d878e6137cfe642f1c4a5c288e6d1ecbaa3d9617d01f71572987b1793d1e59ec4a7a6a7912528c99c988af4f2008939e7204cf0b0ac08b0ec1a9c72bb380e91b3a34bc9423995fbe4d701b4a0db9d2904c0e6a308d0b5e01f581570d1e60d106571440f8544872ee89eec90b9970e0e150d83e66c9a87ff235705768cf1df6b67b5c52979fbc97b41024481ed0d2ceade1872def1dc233769149f153be27bfe494a5e7628ee9ac1a649e650c885f6813c911545fe84202691e3600ea36ac801ea6cb9227477972176192fc974153eea10c5ece2129fa4ce404d022ba88702f826c3c589c587e8f04ca7399ece4206453729d9102d989582717479442796ec5f3237d31903fcc28a391562aef626dfd6cd21ccfb4e2e958e6827c13380f398ac38333fab927b2b5e64a8c8e9cc8023c3f8547d2570264e85df028e289b424bcb40bc47be15d3da831fb4340ff3cd81a27afa698203b79d025ffa13429b72910e5de282346d75de9f19e606ae9431492bc55a52c3c1e4578f9d19f09f7a02090fbc1e0e4835035c868472c4f336344de8a4af43f3ba47c003948f998d740560cb4c835bc297258e0959fa140c4f174b1e38c72dcc94e42ce1d1d4968404ed0b891c00419462033efa9805726234598e1ac02c6332a4dff8e6d35922f49198f00466e214931b2930e8b5c027f5d4e8ac9a16fca4be13d666cf1409a960fc809dcebe972e6131351720c709df3e179d06a74e8f8b74352fcaa94a1679329c7cb2d393c833a07fe6740cf0db960f9e995015ea0c3008f30814d1613c0c2ed9b83e5e1a439f14e7178bd855191de21cfed4b0251969354c1c97eb6e81c4fc892873139b51904881e17c392931004e230a105daadc1a46764204bb3cdd8723e4006dc2b94e283c15874175c0777503797a9ca24b88c3e899e63139ab7f2a19733e5d1f122c286fc220109ef227af4788b5ade99a1d0d50f20e4a51383bbe5b8e0a73899a3dd5060e363acf4b9601ae4fc0103d715096417235b73d704283fa587a75d1f14de0728304dc440255f33632c57e9f27e229f217d84d495334741c80ba2a71e2a47fa7b22213f05568986d1c5813bd07420977294e1d78cd2e0eec03d076595c3d5b0e8ed05f078f3962dc1f05e344ebccf178fb2226496174b60885b10142a8f727f7c194026b4a4527d5cc8a61bcec2450107340500ad34faf0393cd4dcc0909a2f0e86e900b0687c13343d1c8e04ba6764dc931ddc35f1453146690c77123c291a6f72130f45b2a7d0f03e1104a40b75a870ad100cfa161a16b494461ebec717385d22d0f303d419a0cf54203a724eab5754597b0b7a28bddea10c1e4509070f9065fc9a40033c939b4e9f08aec5c72776788c5146321c30405e0174ef4385255f9141979c47408917345325bb90407915191c1c910ba1f71208c2ab451a5f0891396e128ea09b6aeab49a390ed9cb2473952d2bf2816c1c178c1ac40f216ae27f733afc221cdbac22fef36c6618f1c4103f7c4595500ef0088993552a92bd2442e46d56087848a048b899d64d36fbe2c46f48e2f16010ddfe8687bf3ce694244fc4e8804664e44de652c4e4caad05d2584196b296157879c086419de404e0df790de45253b87f832711f6b344c53d24b5f9ca03399ee146a81775a2c14bb108fc54940b47a00182270350f2883794fc0d507f7c4f0aac53b104c7d31838923f19dbbc8c444379d38213ce454222dff19cf3931204e06e8e6af90326245a2df02dcb65f8f7db6eda0b1389071344c28f280ce386da16d01ca0f86513676bbe041946598dc4d5e58a6c69029f22f9559ea75f23e5f3d4287832103a8d3c9405054e02cf752485d9cc2610f6dd380f1dfbc88047e58278923f079fa7474fffc130c43521153d1bba44996af19a3d71187444987ae4aa9f016f75e9d05ff134f65f88d9e71815f91d57b62641679347f042f15792a60fb8f5e3f97850fb0c0f10e84b1b4cb85b444f5f2a237cabb7cec38fe8389c1464b262033437138592ab3a71f938c438190a922b0fa542cd29a4d0e283005d7d960e2ade4c864eae12c7f401889dd086c8f8c9772e46bc14e208653163e6e0271542df16a1509f3119e04a624dffc6529c4fdcb29791cb1c5e462c2d37f221e7e3a8a0faa852a11c0660f6a308f1724a108664acbdc8dda836b461a0368733402a93f054c88dcc2cba001c8817f442500f512394c14634c9f0d71ea7f2e9c0bb47c1991382f37cc3e459ea365911f2cb634d072a31c31139107c4a312cfd1c43519e45e943de88c89c05174e390f1386bb857972222aa26e28eb7f464c14ff4bf3f78990d0bc14d69a6790c08e07207bfb3083373c092244df82caf0781064c9694e30df85c59287d4a89337ec597bc362ac0cc26cee453cfcf380985c72bd32d865246a60ae06296d3cced194c7a218c8e1ac82b9c3011bcec08d842c0acea07b3199ba2e4ae697490e33a50b7ede88109a7b3de2fa2612a99b6983b8dc07003e0576873b1d08f100c214f87371a261f498f2148c5c325ac9e03cd717256c486d2f97b0b206ef3111e173b0dc6b32c6cf000d22ade587f6545944e52362d2b81f11355ecc0d1fbfc64a993371dbeee006a6fc000912ff54aaeec568dcf01061a2b40330649c8de08adce2070a9f1ae6d020fc83ef28b1e0d9326df2ba2f939c422034990a579a31eb5a20237a11e5f584832cb7b12421afc5cf4ce00dd41b8288732f56ef5249b37f719ba63fad7d78397a691e9320a18f4928f2685a40f2b14291dcec429c2f8ef273c053487782c194d39160c2899080e17e916ab82107addb1975e0778831ea10696d7e46dc8ccfeb6af3866f11dc340224fc912839788213539f1c42fd2648be5e4a2649ae9597caff5cef7229e493cf335492358d2df1c242669e8d32c73719c2f0761c7a7c9351178d21cec84fe103e7fb0cd2e1591e89f0bf106b1e0307042eca47f32f045ae399326cf84c664cbd214b1ea7302acc4b25525f5209b707e00a952650f87caffc86ff15682ed2dafc1c12d2de2fc4a3df13bb72312189fea15e208d4589c35fadaffbc4bddfc08ae6865e2f4ec952a0cf7494e159c826fd121774df2590888cc76488675183a5adc80cfa6b5b960cd7c5753bedfb4e103f789544338f0174e5a7d12e3d2a47e2cbc10342674853fc40c69c617801ba983e3272993011f48253601ace4fd0ab91bb947528ce7132ca08cf0612a68f50a2872f5961f804a8229c08a040973043aa5d64c97bac4e6c3e0cc80cc7d0e98cbff15273203b90ba53244137dbf1ea40da284e15ff6908421ce4020eb65a4a830b8f61caa2770a06b4dc20ee8d7680a14c6e3b54f7700e7cd2609ad4bc3d1864bdcb7288bba5063c236a350f6270f2a40459f135cb3a5fabca70205d4d1e879dc44c65f8c5357377ab372ffcd14eb3521653aa7bc185b01681843bc255b3b0252bdd93de691598a199a995e951f7a4d3b205686b6b6954c0ecdf5dd5a953a70a98a25eb702d1565409227b7849b5da2a83a84bfeba7c04ba32101080894e03f7ac926080aca019590fda4668f990d18040d6179792aec99537330ce0b4466e40006b479c4857a2004f7e2eb9880b795ae9dcb52acd7370ad007747158187c566397c787ba356b35bba3eedb44a27dd4a25e45d2b4858a1e5db4ae9b657aa364dc11f20b68fae85946f2aa5e6d1d1699873037c7b936ea512a6d4adb6aacd523950b8e3eed30a58bc6b85bd3d6c8415d7ea9e5df503354b9da366b17774d7ea8a6a7603752b5d41cdae581153f9b01fa857ac06105329756e2aa5e669d0bbc3eec5e965a57c59ea56ca224dc1df9505764a37a56ab5d43b6cb6cf01758a8d2002ee8aadd2b1a05efbf8b2737bd2ed15eba21a4f9eac920f3f04c09e8ca479044e61679713976d7281107d7e89904bdac81ed90b81eba1992c687b150d8c68aa2d5ade28b08fdbc1b2c7dfec18d10c9a5ede869760d6d2f3f17c015865e7dc866742a0832fd9123f1f4edbbfe077e8708522bc5be9875382ebe44bb0d8f13148b6fc873f8e9ed3913319ec800d77a37c83456819f9489306bc618650596f43152da4849c1c63449d6b174980179ef8e2365162bfc1d9023f1449cdef308bf4534e10f82c66d3ef714bf19d1669c986d410391231fb9453817cc21f34d78308ed9101dae4167183bec8eaca4111127c29261669bfc5223fc791683074465e8c441cf7e980eae7bcf892e3c2c4651d4e36e50d56e4d7e88174172520c996a6588d6d62173e6c125c96ea10f2261c1dd1ae32c9ab4519e2543eb8f8317c669a8a163edf2767e6d79001e46cd50d99941bc07fa0c64ee3512efa8248634f67e3cead3411b51e426dbe8ed8226d37ca42c62af141a70d30a3b1788031badd75d2006839f3a00c7c8ec8a0cffb185a73d4cf0cefd44ce3ce238307544a910b8852482f5e85fc8947cc0e4a89f9195b78f20ab92f7fef70f0447b591c5419924c251afd126bbaf40eadb42773c04e0b82b4e6bf44fae2197228721f4ff07819adbf1cc40fc63b601157d6537566fcdb8293a7d859d30696607a098234b29781039f25449c3e0346c18dc0b1f4820984381c4b5632812e3acf8702ed038185704095c27e4e80fc547c3078a52f818f9582f67d43ac781874493673d2f0c44d893c8ccbc2af7002750b0b0efc010613aee9d28a3c03ce0a5cf5c54fb329b3e83e826bde9b46e8295e92644a786fb9cf9a98c643d20996a18983c7669ef20c08157ec3dd9cc690c2cbef6514d98c9314bf459496a7ba5974356c005f04a339b7d236211f3aeef044026bfe5282e4c93d03f29df11d8d73ebe35e6cf8a49d123a0f1091ecc6e8a5bf68487ced1400984c1720794bed8d36dc42ea33a141a3012844e83bbd10a4111dea78b13d61dff480e71551d8a1c3c8993a21284567a404f0cbf00a1f2ecddac769c9a3157ce8e3f195aa7f63e2880b52f4c12554d1be46a7c84d0607ffec09034dc00adad3d0afd661a849b6f281cfbd935c3edf5a0d4d64bb24b7b802d47ab657be50806816008726238e72751923652fa38f542348f0e59dc6fabc871d5e1e498c99ab29165f92a19976a31379232f47bd214af78bcc14f8832853bcc8a63df90836c9bd04107e87313817e103ef2b2198e3360e1df59d45ca4fa14391275a1420c38880295358a3f3163c3c781f3061cb7107ce349d9eaa9cd716e1e55c3df2ad5387396d8795fb45536424040d5fc3818e7f448acf4bd98374241667fccb4c5816756092eb40d39403a100723b2444bcaa0991df28cce469829afc8228047a2f9186e78270279f5a0ca0e3aab09b5662835671c70bfc6351d497d0ead19be64ab81b287b9a8ede96b7b2c3aa07f8b9e173d2d87ec19b92af7201f765d8787c12eca037dace71029da87814d7f478f200fa8209097c8190e93f038072aa9e3d1e05ca947f2e79e44d30b4b8811f7a9a06984e5f6071a9edae60644993b0dc4f6f67190036822f4791cf078044f03d29b092818c60ba825d1e5ed4e0bb68eae470162184388503a69c4c9e233e403ae4cd3038e275fe84722f833efe0519f3332c32941531b2fa258bd2feedee851f3a7ae05f69804e0680325a7103ec0e1ee8d096a0141c498723590c23003e0e94531ffa71e2dd5146de6d7cc8217da0ea175eaeb4dd99c43f9258f20e0a21791e7d067dc31533073221f1178850bda0b1fd6a801dfcd0888a47c4805176e421c72bf9c3e54dec587a25552359ae4f87bf319973ae3f3a5c0e9d956cb675bed5c61457c0a3ca25b801fe4d0312b97a0253ae32f6242360f1e57ff849d15898b646412952ce04b7e771cc616a2e2d14bda71e0f7e049c0b6d078883db558291fba898f9aebc079f6048030fac10e44f5c9972b43856323cb6e1690cca94db70d8e190dad47d15492447b305c51346ff78de34c7b708207e464187374144e2740c2db95b038cd9ededcfe5034bcc7a893cbc57f6d23486306aaf10bc26dfc550a6378f893ece6186a13604e2d12b4181d47803327d02062a7c13924c7e2789c941616d793e8b717461def4f5585192875478f3364e6c321505db39f49df902089e1e29ce46ceb369e95bceb0bd570b357ec40e15bd06410c0de7e3947b270de0e3308cf41620f397c13c4aa0f1040be40e4c3657ce9d4b5f05c75d9e6adae38ddc40d09c06297e0d08d1efb364c6c92c7ef049116ebc5505167a3347f81446991c510399974425c2fd88a23ea43b877cc18b3c3f8e9991ebf2307c85172b721c48a2727c444be630288f9b990671a573c472d81e34af18384abbb153e24dda48f05f584a7d851965ffd500cdb585147d812424ef0721c77f8a4343f319a2c599b05d7a25a4007d84d442eeea72f13938a9c95420723e2d6ecdd3d104bf8c1754af294c0b47337125930918e26ace90e93051c01e4a95a39b62ad2c1bcb8078414c00f265aa44b58f10595f9cf2e47de301e90971bc0ee5a9c75b6291a8b53aaa1c479f0efe4341032fa2d8295339ce70034556788cb1062f2686865732eae36108c9d11b3e25fd2c919cc622ee784f244cb8a20d28decf5ba28661b57d349c28682618c24c086209eb416f5ccc9983976c88022d48efe93fa8284ff361ed155182ca23df7c711b546377b264bb12487e9c461791eca24219a7114187d68216c0a370563894ad221e227bc7117d6c723b3346b8a73080df42c49bb9fca2de8092824ce345fc989488598c1a2d4e07879da7094c93937e4664b0b0079a6cb04326bc52fa0d46f0e00fa2e27cd455c040eec2f206bb42ae11ac0e4f6527c7dbe932e695685e7fe586ea916b827a069796a74165fa359c34f901431ee83f7b0af87608943ca82df63b2c373a1946f52642647d8f9614d7f267475f81e5789716a67e4d41a54c7525ce6b6404c9290119f2bea953e9a16c38e17661e9f269ac04b8a0a734ab193a7b9b3078f2263ea9c96e8620bc911e89bc891f4e6f07cdaa4d74d0e4893ab0c917e8e01f90604b473162f855841c34293788fe10528f8f8416e871e4e9a0e1e8ecfda7b7e9931191e473786a7b344298bc8eacc52b79c97e061a7bf485407cf20c5151d70b449a4c9db4e2cde10087f32821c41505010007c38af51916818f27909f1b48f4f710aeeabec628313f7167e93f60a0e0808a025c0fe2910f24e99246322a8e3b2938b9acefcba73165237fb083fc1a6e718ee5802697738dbf2f4a91cc83c4f395d891f8207fc4e42e893abc4e8f232d23ab9afd9e381cc9c9cc580602dd4c88613eea81a1ee93a6e8083c75f2c52e8d3e461021c74b4bbe2588f51c7ff04b39fce14ee8eafc8f4423341d2e69ae45ca0f5d638fc75f1175cc9bca70fc28c10439905af694fc56b8599c4499000e10ef61919147347196b9666cf1528fe86024135e681c6fc0d5e3224cee0a40c388be08f9447880e8d7cfa45f23d5c897d80df013850f720c6b06570c12c0d33970fe812d8ab4a234668e41aa46135810e6007071e5314478f8dd0d4379c113536fe299421fb152f264e21e9d55a143be50e5818be351a99c034b9e5b6a8a8a1c05550546d340980583c0e5036808bc03a53b9acd94b897b36af1190e0172158572590526b6d743e0ff66daf370780ce96cda91d71128f821dc8879335fb8b2da96275f077b712857d03f43292e93f2b1744ecd339f5267f8691604f051d853df444a7eb55c67be440e22c60505443d9c4c14603d44509e6f500038804000b41810672ea03e229c401f0a5f638381fb69eb5f8283ddeaca94e65495c8c36a3c7a1f522a7e0059c4f12431bd0816c12c89c0a0c714404d6b21153872cbc553152579142d6e6ed6a4c95f4923e8a3b860f407eaba5c497021fee30cb946bc28f213390eb85b3e270e0a003c07b3c91dd14d9adb044ac74554a5d035dce07945371e7452408aefd02853c6b381f95e71029fce20aea7438df3382c17dc0b9070246bdedc4c8f291aec2bcc932010cd11b018702ab552fa84114edea560e712357c3ce002bbbfd3ccef670a98b7cbadd3679122e6fd10638de6a38ce4a605f2f4372069710d2972f2ae1c3c1c8602f7ab6c6cb7c2098d635814a6cfb48067642253de5376e0d57038e04f485eded7494bfff5c1e47a3675bd21cfe3341c1d715139a81ee9c68d2be8f3f22293bc381e291632a33f515d86efeaef941992c96c503e1d24103786251e59921148c62a4263f2571fe3ab7738c1fbfed0c86b94a60fe8268b1ea275e90bb86993a101b6bca02a40ffd077ca170338f323a22b2fa0c24f4b62a3c59d65f8725799368f3b03a7eb64cd1c110d375d8758e54dd451d47ea2613401458bef01440ff28e1279dc3c7465b292809caf231597032fa5f13fa34cfd234c6eb41724105c43fa27b008825fc156df1a69925b20b49489e8d9f1392855de4c973bee0670501b0205e9ad5b34fe46fbe6d52762b2a00fff9912e52c07c2152fe105fd4360893f67e0809e71dc90650040caf545d8731883644e788a781f091b5e4a58d6721a293137919bcaa67c6cb51860d6e31172e7a39cb6be8d8a4a8f289218277481edfbb08b0e055a438f294bf198ace8710f3808bfc31a581e520a87270034c55f1ba6fe8d2b8c3bf000e2672ca6f11af0a82fb238275b413b920d5f46deab01e169902ae271ca207848681ec16c718a32db9610f7d04689acc5d40517899ed665a821a2afb8a24bedd83e051c1dcec585d4dbc234ff21ac3c1f0a2a7e8496f18a298e3c198325ee9403c40b152df135d34f6fe345036de58f95832235436b5be4f91c3342fec192c9dfa0f0ef282cafbf33c988aba650926cc98a13780f2fd32b62f9030fd9d44953a540e246caa3e4590cc21e4187474fa7809c7f63498bfb21f1f47336d9f9a0b1b44c2289f6d712a3b2a837395ff581c95fe979f0866ee65568c1e2a01424723837e078d61d09da0c9a42df60cfdc75c6270f4203b5231a9db45e81a50710d17a1df01b4da586826e10d64433505e792f5671798a204c9fe2c5943cc5b0cd53da23e2b14a3078d399e4a9d330cf2a83e3cf70c0f2480038a2b75e58722a34453a6f8ecbd39a58c92d8edcf8b5e9606e420af2012e59d02204649daa08608e323be5177e2c7d090a6fe6737129074293f447105b578c46d9c36903468ba968915fd44133a0b8342780a5f45f720c711a809af21977d16f188397dd981d7e0e4dee4ba984c3590ce1e4629ce6bc01347c1a071143a70278438ff142dfc34f34a740a3c7af5040f0013601f2a66286c754204eee1143c5abc12ac9d6305daf93b60139d324b42fc467c7b9007af45a7a1df9559b5bbed0458c0f1a85f78808bd7829043e656c5cc29dd26826f2527faf8984ff4bc1c7b90020e9a15ffc9e8ec215b741c3c59f11b270517d671e881c03371487c4e53c59a011dc2d7a2428d25fd4c7f767e664b7b00c850f7305e94d13a2502e838bb87356245fcca1a031e9e3c5a7f182c4b5a12f3c8d0d5a5713c6e1f300597a96ea703988727c20e7a32f94c1a7135f24f150897cbc0409355ec81d0c2790e0ed037900de82d297bfc900cabf50f1f2265e90c92cc0409d872233fa4c5fa9b774c522df81c7f2a2153fff06ba7a145484bec41506ffe2f5e12a5042296bc12bf39cdcf0f0298e1eafd7658d3b1fff7c78e3b90422e455ea0a1fa93c3e5b063b0f6a50ee3f9d80e4a5ccbe68217eb07c70c4367fca54c151b030957b8c48f1818e001d53d8a177c34472a6a8241fb7a6521e6441546e1a407b2724f9f075ac5c742747d6cce9e900ce8f067320057d2e8b294b1e5ae086439264c445f530cc4930587a0e574c9a6c4b05977204cee13c5b1ed1283f4d882a7d2b90324fc884014781b8e90bcd48fc0662aeafb74392a7b04ef02d210e3eb6fbe1630a38e5eba21d574321a8c752a8840e11fdf2660c9faf0b30755cc9a3bdb78b844d598ccd9e6b260426b711c9cdef6cadb98c2d8cfece1c13be26ce92e6b3ccfa4da6aa99c1169da790e1c3875de77c8d49a4f211532d5e1209387f12af3d9f4fd2782a49f2196a7c381b36025cc958d75b61907c3718ae34902f48f71065ee254bb3c8eb209ed288581ce43ed1166e5cda9317e3a5cb9b41d0d8a708e521abb191e4480ce5b20097d4e7a994f8684c2abe25d1227d45aac3b35245994ad99447040326230e027b45931eb92848355e3bbd3a9d43025e914b013d8186a9efe306c77d2ca0910f500aa57bd4a1f38bda6c7d9ed8989c64020ad7411b45baeb0601f8c597063900202def9724aa0f3126eb686282bc0dcb89ccc1d1f4f526d8c8648248f9941e5d59d000903781c98f763182c53ff110c6e14498e4802f967b4c92f27aa80ae9b26a985ff172f52bd80d6f208998fe3c4bfa4a8cbb69719c1c37caea5e5561a32704610063b993f255a68019bf1efa1e0e49c9cebfaa2f5537c6af828dfe830606ba8f4fa9af92849426b1647e2039144ff676e41120e2c89f5eb0f13f2d2abc1a1185ef0646030d06d2f31b8428a1ab1660f8742bd1689f74bc9c120b5f884f96bcc687275d81005f3e6e38f3cc5293b2a6c1fd0a72309a8118a3bc053b216bddf091976440e37234e6fc88d69c2b1124701e7a7ffe4be2078de92cc3832293e5ad7c48f70418456b2a23f7543fb0decea2135f2641217d46f8c99f18be1cc8da84d78294c1b192cabc942743fe508c2b2bb33ce83051007328362c6e9108689eb9a6c75857c7306370c2c9158060c59749f893c7a8297a422af2b2131a659e118dca742db238202ca5ec8768eb2311667d23eeec3fe19990532861f25d90b0fa1017423ec73ae969ac8971208989745aa5086e82509a466455e3fbfc9b6c2542a00ca9c29fbfcaf8f17efc065cc0d2009da2ba220bfa32a42978cd30a2ed89d00970d881632c1b6f5e2171432f683ed4164a1e00f7c3d5d04d710b9776b8082f4477e235e0df0a6afac88406af49c4d90bc04e9f6ad4d8cf5800e3c32402c97594b4f84f871cb8f2adc2fb75fad2055c0aba9a654f27d1327c9016747e12d2fa28e46471d7cac47f49f1d14f4971e6b707af5f534df3e8153f575308fa00b045d02de458711358d36c182b5fee29e15b14ce71159dea781a86475fb7c62aff9555f118012472b645b777e045c0374384fab32bd2cf44a8f0cb345dcad7022e320aaae6fff082f7956c48d2824631c85701a62e9c414932110468b94d96cf6bbd4d703d3039f3641d96daae4fa0bb31b4e15b283ab8123464198b1ae89361ca7aa2748d7b8171406330e0c6013844e8291847f9a740577f4aebe2abccc19a522345cf820a0867f0f4e23d4a34759b1a326b316fb8865aa7b2853bacf76d12226f34400f265c9b3a9ac12eae033217f2e0990f3985a8ab971140ea03f0c2f71f2ac17fb8c8c5016d087e8a2da83f40a351569391c93b44b97c12924e2e0883d82fc950c2a35db8f4823b775c71cae1337012e7035841a7537cf61110007ba9c7e5014b4c309c1822ffe29001cd6049f89402e7e8163e2ebd1e2718fab0cf88c7d001f1bdaa02e5735d7b74050a7cfe4e130a794d81505fc50f8f63cd00e16cac8cfa6c66ea37a4f9a233c971e16df6b0c837e6e85cd118937723a8c211c0b9a20d1c30e46676626f87417e3d8f4a653c3928f27312954c060dd0bf80b1ee5c8d505fa6a57d0369d6641658ec34da8d361e8852597e63c7c8dfbd90e6ab1696dc41a6333939e5929bb9f37a9d3fd9eff2f7438b505e1d8974cea725b1e07a849c3200757c1c8d5714779403fb1fc290c6b257e8711a9cfed246c98fe4d8a1edd45993bf6e5c61106e0c69e35b8b771939ca7fd65199ce753f1c1e44d9c5127e3380e8bc7e348c78c46d1b8de5908fc355c9d038482472b4b52f39c01c432f251ca4fb9a09dc2c09053f3146e19b6c29741e3eca7c9a62eb4f6ad870390ecdbf2142ea93748c7a1f1e165f821caa2b4a5ca28f677b72e01803ef030ea08e20e8edc5163981e51648c9240a33b9766c9465157022e80f7f834e4649f8474a7f7c5118a68fb4418d3be952ca0b1442f33800aa3946dc9767d38384cebb7bf34ee44d26d344cf030874f57d5d88fe04dfa6fcf5a4f0b540a56800a4289f0a8f51190c963a9fc491a72ce4c8c9435168a149d959c97d685ff420a44b990b94049d09ce528f2082e52e5e1df1b3b44b5a4c0c876f8932fa361192bc4887a656f3a2c67b6078f3c616d4cfe063866b7111f8b1360fb4192fed77b40a6ff0927a44158232214057af05479f6e9186d09358aad31ac0467c8326826fd440922765e6493b42dbfaaacba8cfe0a9c25378e0458f3a35e833e903c6177bc3c75020f9304ef0f8080a11de050f400e638a9617dad4e4210cf1f154d2fc3c001f3f8a4850dc86f2938e1286e9771ecc1b509d79179a00780d7456df30e6254bb13edd111847ffe9cbe21b713a723448f2fc034036dd24119fab8121bd070472190e860797c08229394920228de849cb3374c565ab99426ee94df13f8c28f0ab252e2783d2c81db5893d1952a2dc4aca84472078c6c7207991c38800e30148a8e18212697fc303918329d0c3e1e418d41df0c4f0111616fcd81c6bd6121ba2d1f4dc3e4d94026e8b2d06f2076a9f0e2250f98aa00d5eeacf875f0457c25dbecd7847790f5c6a7ce030fa323da9eaa19f9228c1e33823e63bfc21cc51aa555fe603fc679194f85c28065a8f48766902429a031c0f1773a4ed7d745d79324e21ce43c3893fa405cf2f19d0c04b30c0e08299519717e5439f00adc3c779d2f0071c81da5a2a871654422cfb8149e1e628c3c54d8851f17e37d0be4b1dcfcb357e5e891b22fe66d18e4fdac1722f6b0c3cca150cd98e80227f706694fb3122f4b71294a14049e37cd424faa6440efe265ae8c3b6d69c815c9e9c68059cacb6d5a29f2c8ed15110a139531ec447f3b5e58572e72a7725487437113ac83ab9e643a120e1432cf578b1241e5c8882166e48d1c8bb0931a0ddc12a0ee746497ec928fa92268fccf6ecf487ccab1cd593e4bb1c4af08a01fe3c4c28673a4c63eee58089fe61f5c30f2cf0a40f5d12cb742869653bb0afcc218ee2bba38ad0fbf951202b5a234a87d9720936c4a488ee81c982ebe971fefbe8c6d3599bf2921e60694a7b7dfc9121b3d7b227bd5b84385ea80aee4d6502c0e7f44cfd8c0330996dc19c0f0913e9178912f38b96a0d0670018f516d8d232da962d4ecd4aa0b196a8f21d4636f40ceb8767a266d075d307ff8d03776f9f2269b2883462dc330f6f7fe0c9d52fc5f087a304742b5cb6fc90284f8ed66575b80a22f41bc405cf698d14cf32f4f03ec6501e13008bb32930fa9c9aad572472ca432d2af2ea99200e684d20b7010a2d7361d3f292aa7664c4e3076fe2c3cfab15eca02ddcb1d2657b8c0f2553bd2caa9df22765997e613cf371e0035ad31633ef0310f6df90609d06169a756c72f31f3710b3b4ca8bb792a7c6e1b43a389a2b3ef98de1098fdbd4a7aff2940a9c24a8f44d53b5410c228410340220000000001315000038180c08c542f15896057a9ca80f14000b8a964a684a9c49e334886118838c21c81002000180004200cccc4cd506004f29c012e6b92cc7652bf8bd343092795fd3aba97bc963cf3195ed16bee2213fe59efc148263b3a1017b3f2539c783dfbb403d7cc6dd9097f6df66df040b11192e97f292222d30c8a0e251145cc3340221d0c900181eac4e34156e48cbf1462557e8a4ad479dc12621affd82a102dca98d26f56a4d0b8dadbab747b0bad5b106f6734ef62a4e5498dcbdfa9a178cc97469ea20d2f3308d8f8806b7be4f34b64156e529752be170f7b8d218ebf4814f8316c83d5ec28fe20414247b0ade4299f7e03836029cdb484fd97191d6cbb3d1cf8a19a240681ba93df156cb1acb2cd4669d0588230f33a89dd1cefdd03382708ddbc0ab73be23503f2470a66c8ce38a681852c973535cbbfc0f48ff74a0724683e30f6094922dfd0ce74833f74b7e345538ccf73a89b3272bea022cbfb3216430c086518197dc2d6906eb0c7259a80161cf055bf2f932624d5144f1011cb6a7757b351cfa4af825385d39c443ad261b315779dfe127af8a15367a1caa0b645efda0cf5546ff17ee9ccaa8d36eee42354088914d0ecc36e03617d0bca8bd0d102f10c69dfa0313e307aa9b22b453981689228d51f5371c101ce0f069ceba93de17d6ee7c7fcba9f386a33677d897f7ab35a6567fbf349e25d44ebfa1add7eaffeb39677972bf3033a5efbb7acb6ab0a35ec8bba084e7ae80c079585168074c391c5b3328df21ae1c028dd3137d8fdaf1b19cea5a2f3355ace7d6c95deabf16c303881d0045836cd6e126efa5c5b48953d416b96fd30103fa3dfbc015d38fa4d8dc422414cdb12337d1f14ce45831742efdfe532eb69b3777bee7fb1d787b81f5aede2ce01fdd3acc2f8cafeb10577cca1edc5214b175b3768a8be74d759bd72ae42c01cdb2f9f7d00850df67831b0db78367e7abd4030dd4071ebf5e80785cd9f073fb9b8a33de00193b9a9f46c453a6984effd29b43c9d3566fd0abfd5aa2efb1295c8fcb0d300cc0a7f41d086852637af9d837a632a81588a4bc1f6a8b51df309bd215a3a38bbdfa0cd84c0df7236452880689bea6f88d40f40970538ba1e0c50f4196ed016937393ae102471a8bc7cedf42e6a89083ab16ff8f85e1c8709263277372bf29a6d1bfdbb2b65ab6200d5e7cd286db62c482a380b78360b0eff600e4e7af4ead874bb0067024574fedf98749a875732d4db9540ba1faf7be5a96a7e815fc012e5feb8809bb5b07ad7ca96c77c21e5570d99d3de217162c7afadce31b23c67ff04dfe9f14ce99e9c56fd19292debf5fec251f99de6f09fa9f6726bbfae74699fa81b27a181913baf8ae311cf88617a7913ba3468e98b9dfa0a0aa9833dd18fb00cce07385910decfe7bc63ed15e9bf47ea9191b3f8323c66f32df5bde5137bbe6a5fc5f923fbd6968558a7e711ec90c3f37dabfb6b874ef4186233e137f876f63cb393c9fd616a8ffd98455d2f612f5db4277503646109e7aa7948e0b218455bf1ab3519700edaf46e6414c9b55be69cbc5f949796671994f27b96b0b288295a53625fb65edba3d04e6e43cf0e771d2b11413f8d6ef89aee6d3b3f3c51fb8c8203ff11063f3624f0ece980b7f9db8ffefa5a4a545ff4b8211113439f37039d8ee13ffa248a5393659cf6525ce15f9106285f336b8ba39cbf6fdd8f38fa6b8659cae286cf65246da5df96775e80f7dcec7f9a745fee552943fd38ce5a47e6ca14e0b9ddf1e10d5cce5d12bb7d3ba836d08b3ed1065f6fd311f6b4b9423dd5c731b1ba324390285adc268de8ebf3174580f847353f3ef80000c77b581f7af3250a4de4606988a27e68d7659836d427a379cdaf56e54f48a3b6b655fda6ae78a76bc1b5d095c4704dfc86fe204a97375faabe15f66f1fa7e5bfb6d00afbfcd6fb182c4f93aebb78ac6d78f18ec774b07abadd3d82aa48cc91b688b8c0dee0ad3f03f3f605e30611aa41590a6fdb5f4091bc860946f79ca1013a6e873e54487d409764a7a674a635f559ed5ad590bba087f89735a5dc3fd84e51096606ccc2266fa43ef4c521d920799667a369cc47f2ebb3e4f8765232b05edb1bdabcf4b5a590ce1a6a8dc9fb4bff1d0a98d0f608ae5178ebeaf9e2d60de1d58db46cc8f2612bb766aa042e8ac36e549f100c71aedddb739d27a7f0f871e88d8f02346e4d100bb8523ef097d8a9767112fb817a60e8df6f7371af697ec22a8f329b70c79b4e48bb593ede08498e50053f621b3e3f7aa860ab71b5ab833ff9991ab96e4a57026a746aa69acf6f643d8b1bca416121d8ed9099103c92b7d9b345881c2b76a49702a1d4d38f938a3b0b6be8ef8523744e1d8abef1e9b4d7cde2c60b355b6b9e98eec8aae3d8132abe1bc4d5d3fe8d19e61714d5b6cf161f2cef6bcf7da9e4d96761793546890e35cd110e19e882451d6c05ac7bed88de14227867c47eb89cceeb39f4e1a917937b7127284709d45734bccd35cf5f5d23976ff328ab0f8a3cf84c6ad8e18c9abe4e6ede4da66f05dc3846daff0c610d3516395789d51706b62380713a8a7c87e7f6471d921083b1a893f3b5d27263852dc58288f5822b97c4bc315ad60bb7fbc4b40fa07b0b1eafd85415f6e94122715530e8b853d25da2f360f42209df7ab1e683faff98902f47ca04abcb3047ccb6bf5bbd035bfb29048f4737dec2fa86f2e2bf9fa215fb33d232be326aa5d7f698e73e44bac1df3a7ae4aad135b7ffc5e974ad68ae133d74bac13fbafee955c19ab8fae94b22236dfba2bbf624c5fba945233d19303a087087c40603313fe6fb74b5f15dbefdd94bf624c5f5d96b66a0cdfb85366dd187dba596ad5987c70a79cb5b1eb036eb9556a9598fec303cdb53f19035bc476fbebf022386e4c0dfbb941fee259e346f6d9af2140c4147f42bf893c9dbe3af30e8e15bc7a05b7710f58fbdc08e81f03509d2c6bd2c16a6b1b54e12a50e689a305765310ef00e4a7c0310e6019023c6327f519f178538c25c0671e89b2c0605f96217dc6213303367a0e8c0c66e7d9791619cbaf509717a412f2a35484d71f5b97679eb9b633874e188cb9c786dd64f0f2b8ae99f725873f41be36c33f7c612be1087ebef86fa1861b466d8d7aaa7366e2c8fdf098fb99bf475261ac6e29a37f6a8de5faf5e311e1a45268995fd9fc8dab8f53ca786bed9c0bcc2f433bcaeb5e7659e93dd01ea5d37fb0cfb559d3f614f34de100627da93512f2545f0571f078f435cff55133b400c03b90e11ea4c485d5b19d93063c466cd4463519c412cb5e5cc0eaa77c80560de469d2ed32e6ad0d629dcf7e846e1fd52747fe63cc3a7ce3fb43e0a1171daeb447950ce06f5c82a8dc3e467d7abc552471c5f7a8a059f4e0564694ae8087963d181b5ed0a91e797220c3ff72488c485c02ec3c3f5c093de4cb02d9a3154f58ed3c0cff14600d12d0da544e71d0c4c578faf1e14dc3a5b6a4464a97a9d93f939d5c7553174b22675d75e0f2342c0c814637bb5a99085a0956a72bd69df405b814988611e7b5d8bae62081960dcbe4b0c913b2c2a5c42c9f3cd85d1940eaeda05b98c064f8638dcc6e113a60353beb6711f3b8d94f22090dec8f692f1728665a3550182ac65b248ab59590a19124e9544002a4650d5343e432aa8a9e8f924d2fef7387eefaccd14f338454fc613f6ce4519cf4c9f41841bd103b25c097c69ef6eed6e30c0431dd8f4fa97705debdf2d83cfea0959c344f7d3a66409be299ac7cbfc243d660adbd325d29c9b8044fa87fdb0f6df593b34cb5e75efcdc4ffaee947d2cd7a70d7f32478c0b93937ad08a0ae2dd54ef83ed414c97775b9ef7d30ee8ac891a0a0f38cab7516c1115c0efdc15138f0dd17e10251a1e63de8787c27f570f3d5e63c4039e9235a7e1207951d8b8bb1ff701694be4f2983d73636fe41362db95e52221869198d30af74ac66aa6c5b37a4ce844ff7723de6388f43465cfb16caacb4e8f8e86b718afe4d0e13289273542ce31d10276e61a60fe6ad9c84e01a4ce8314c51300325ecf3495a9d1136297c6dd283741a6e5055d47e530d5cdd65391cd3e860632621201e30fbae9e472d65ecf57ba977a2f413cd41623083af575aea73a916fb77457d3148b1f543cbdc1953fc45bf7750ac04493465d9c6e752150d6f249ae1c070590119688027a3e7e911e5d13cbdadb40406e50d074d72e8f5012422413185e6ab4781dc3ff7179b1d4ba67e925e2febbae18bcd4ccfc3dc998455f29db5485bdc8283b92942bc01919ad0e13578343b5c5d321d0241b199a2af3933522250925d142373c1ca269d3c2e48ac60a93ee12e576f0d4ba5b5c190e560cd2150d9809a521d030d9c3cc5c41820207d40a06b87029e61acbb4d7b9661ce6d3218efa26eaa50a0b310c3630c7a206de630a0c62888b4a75888728a1c7a5334ce9f85c87a9d9c72fbda2e3cd98b1b8ab22d383b1bac7b0f65b1f55405cfd4550cff9496322fe1cc18bc4b4160d1846f4555437c18eb2f737a6a1fa38687cd64f3a8a8dc0870a31f7fda62f268dcf6780ffd3a369a1fd94a20064ccf9f0f3b3e47d509fa49af84426dee9a284283838600f68c370d3b40bba5944cd182d2c864ed913dcfd37355f1e2a4ccdda29078558dc4135f5b11121424010ccbda0e3556263ee6798bcfe3303cd4b86176fffddf761d867ce659625e341f1a2ba01734148ceb19c3cde04e0c5afb6e1fab3049751972be53091fdb97c522b1244260f230fd9f13d0b818431824b2a7f14c5efd45da59aa8adadd1e8b11c5f728a4a6c0b62955e0cfb17eb8e358f5dff6004644f33218e974d1921048ce1b48ab35a52ebbab70d6a8aa3fd9baf89cd909642074b4f3a81cda8db1578c7aaebef3745bc231a7ab6d0e6b9cdd6d21cb1e3ebb612140a0f97cfc0501d257f62846744a995e00a16d691699060497785716035e225f51f9164331be10bf12956e51095c63966506ade705abcb121e4da4dcf66a17bb5985531cb16c46e92ca31eb483fbc901ad39b2f5a841025e52c9c6c00fdbf7cca7e62c254d4fb45ebb11da667d21c5699ef4bfc695e98a3d2561a1fc16f8621f032b0c31f05637354061aa4100a809b9bb65272925909969953e17765266016c7eda3962233c8405a7cd8ed754d0da054b672f1b3056b60d9dbd7832e431faa86bd11b88ed8dae93b631d5f35ca9cec3b8a773b75058d2e90042250f477719b3cc380abe07201cc1d331271da40f7138c08f69b44f4639f37e9c8462fda31ad550c5c254af6afec4774a243df952584db5742ff40ed9b6839758acce2ccc6fd25cabf39b588d7b834d88bca21f62045c033eb880b9f70f8f00f66c7d3bea6218b2d539827c67759912d301668961932e27a50904e369ce66c4dc2cb685a07f551d41837616833b681c36b08c1ca562042cf7f0706f7292e12c9f304b49e280194b496bec9669e69cc88dc80bbf674be0d16c69c4d211097498936810d050f258b1d602eb7c1965556c497bf1538a9e444d993fbb769e3ac95b0d578a8c2c839bbbeec370a98b32274f4f3923d0a08adc30ed2707d949d5b57ad86966ef5ca3974bbb23d4359e6dc596d7e7e814dd1f7f2fce56583b63497be631849b1d0d5428f601d48f06e7b1c6888b9cf01971731d0a49a5d5cc5241bc5f245445c4bc48ba889235c4758049d6e1ac326270e012ec88f6bc1b9f17dffec24218ec09f5bac317a1a8eb6bb0907486b8aaef2c4900cc7b9b919f6adbc9f42064e05aead815edd01d062c604375e0e7045d0634813f09a1b6c1995254fe34ef697ad8a918993f3b1fe40a2cfe044d34d4e9dab3a82beaef79e93e91cb326602a9366180e645bfda1672e7b3e3616633c537d076d7fd63e1adaf0b3249cb69c2edd3e91395238e0b61553be423cb19d373407837a3cce748b92df49d90d8763bed5d81a0c9841fa35ece6d7fe3dfc2bab392e713ece91aa3ef180e628cae7e94b0175d70c81fa82cf7ce7decd64e1ddb3523685719e7c29e1c8537f71e0f2a8d27525be8dbfdb5d5fd91c2299107eae6b0f8819f7ed39c07feed61589ad8a951b96fae0f1dd040916cfb85ab429038377f0c54dda99e55e4f4d967bc1351dd6ae8b22d0443084ae05839d2d4c1c96fe585c51012f8d495a0160e9f7719a7ea5997d39fd00f420be8b3c427dc21e7f157aa2a4c78b71a7d981ad069236df7cd3d96aa4d85a0bf3d84f2c90343c1d18dc78d4cc250cc785a5cc5446fa70e6195254d99133a713729ebb6ee54172a1fbba519170d765b8b79579e790f16c320d0e8acbee3f1347f1edc06c04f647f2364344072a67cda062b4848d366f21b82c966e5f563f28d988de975d416714be612b5ee208dcafb668dfb1fc5f15bf2ae9d24426d00b3be4305f438de6b15ad30df3b2f6b1c68de66b551e47eabcc9c5f30c5d09a1a9da4f67889e0b3f18edf65c6d2f7d69ddf98934d5d50d55b7a111c83538708a8513e491526503c9710dd644a5b62448beaa41583ef18e69efaff70305a70bdceae1c332251fe5878f7265f69ca90835815aa5c87eeb6a4823a351989b4ff8209f181df0d705c29ff558bf27d078fa62f039eb4b6a1edb98f5d238b6fe146ab86c5c66aabc88f2c0c7529f45ff597d265fb8de6b33bf048c89aa8c95938b0a081b29a2c3f6f83c1c65b4b20d356501201446b890c80295ef3ff3fa9fbc598f0c7f6138b35d727f310f28cd4f8747ffa31de7a2991794c011e4285026fc4149936b8e0a1c023c921008658293a7f27dc503e8b9e06007ce319cf9e4ab9390a5e5004f524716e959e86a5dbb19c1e5bc78568d32578a939300d6703047e7792e234d786396683cf660a61bc82cc43016a53d16caabab84612c83113f84195650679c82122bf8262e21bc1f40bb5c72df60f202da233a548ee6f0ea1820ba4ead746e5698fc6fcba6532223b2c52db51fbea53eeed78d58ca47cf8ab6a5d67ac6b98774b863127d3f359662755b43086e520b8f0046a63e11e949c33c34862e3d71ba6d3807161d724d05f15eb06b1529e532445e00ef5cc9d664800bad17bb58cdedff14e06c781a0631ae64e614bf0bf5d8ae1c51d8c8a5a82cb49cb5206a88aafb4c70f99026a5c60a4bee9c3a6894ae40c11a31d1dba2b08ff66a7be907bbefa101ef10a2668fb96aa6b135d15e1b211b8a46bc6f539e89f098461e0db47f1866cc511ed8cacf07f2886c28d6ba899656f773368791f8219dc94e7aa7b606458c3abbb1bd0c153dd7b4d47cd13fd23f25e80bfaa39770f19a90d01cb75554ce6c13916420942b9c6ca4ebe85e358c4b4092d59bc4360a600a97b2e8b42737580d40935bdda7039bcc2f5d62a54dcb2cbaebb552d9553c7644719683e4b6271783555ec38c2806615953f9f70645410141a085ffc0770b892cfea643156e774137e30ac39a74cd8b597f1d5746cc9ecc4bc8a45256edc8ea86997622846ca450577b91d8eab2bf6b6aa35fc6a9d7911373f6d536091f1366a13312efc08a82324d108a7012daddb446abb0ab3fa8ed4cc4321d5cf576ccb4c7ae0ef22b8cd2d3274919e301687a4235503590058c4c2c6c5c674d4808a0d78ff158970ab59b9f9a373dbd84187094631dc0cfcbc6555fe90946015e4f25c2ec1ac516d4e030efea0c2eefe832ebf1a034ce480823fbd9e70e9f765754adcfc9e1d4363be63564e8154d648760a43c859e45214076733402a06b153d87332d25890fd61eb2eae13413d6bdcdbc8595b1cca907e556fd27820673514bf936bf4932b773605dc71d0cb995115f37aa6d44b76be7fea0921644cc9447709308c1a06845f9d9da75a2acc3e251169b113627aa20b896314b0479ad0fb59b4b113c9dc30cde95f281865dfa09d90a5a3a0467ae21d87e47ca5b396cd3b6e633e5178c32ea584e7481930bb311805e9ae09a26ebe9e2aab9e43cf3a7f5040f5894a9e9d1f9216f8deacfa316e2171e4fe3b40526b86cc233150d8b5e7a1383faacefb2f3eba0c996cfb8dc408d267c96bc14cabb492fba4cde5611101cb655477dc42f6921bdaa78b6ee826724ebbb327462a334a9e3b7c47ad4e9c538c85ffc1656aeaa94651b7083e04c140c41c2089ae1b0b2a14e7a00e3f222de7bd461ab724b408b8fc9f7669fd593526f85785e8bddbe9ed4db19cd67bf4c1054b338ade741fc049510dbb2ae49196c828226c0191b1fa7f59b3d648a5171e14e1628434f4150bedc83eaa705855c749c8902f992ccdc245045964ed1a03acc2aaba36b76d5f0048b43016eb1f14bceba21ec6504ad14ced29ffeaf68aae5686543407e411981441d5965397be1b7754e3bdeb9c75b1dd4536b0c0fc1b18ca413f3394441d0044fc50aa50d10221ffa5c11492d6ba195ce138b7b30231b8d40e4f770dc768b5851a1758181f533b965b401165112013a5f9a1d4d4470487bd7d90bed65ee98bc190dc750ed06e057cf82c69247f844a6915e6e830cbabedd1d473b3b909462fec75f58d9c78d4896230d38d2ee09a546de1b0c80ed8ff6ec7d30c6258f595e92552c4c749745b0baea87be42dff7fc7c2c1548c019227bfea601580cec516d73ff233ae6143b61a852f094a8840e7dc4428b2f362a9227d26df06950bb8716717d691e36ad92b371c67587073a8d0d0442c2076ba4cbe23c66fbfda0f402c3a40e94d9d82ed39b3893d6d90a21b9d435f05fe928cfe744118780c2fc7e29c725731be2f9a24d7c79ee6cb6aa53a34df571085e5783f69365ce046bfec883099a73a88d38cd04d20478b80cce24630f4499870a69fa25b77957b13fd024c9df3dcfb108a861d9e6e0911e3ce2f45aeb5aac3870bf2f3794a9d5d2b40ceca179a3d9b10a1cc91c64bb92573714c48e04a86e56091f0e45ba996277d2f7fff00aa22aaa015f764b19eae853b27e68084720138590e5529dcb99f2add14617fe84c28f850d86aee353168753e80b91588ba68ea8dd6c5e81270e82d44a05b375cc29fe33886bc3fdcd41ef848249d3aa708ca3190f2ac4079221fc7d5bf32587175eecd55100838fe3e0b9ea3d187fc79f8f263187d5a14abf32be6e3ec3d41a33f6de77bbcb5c5e8094c47a3b05a5f6a79a8d360a968ee60b5086adca526fd99ddbafb1b0b235a94ae236c533026f4a4230e7db36965757854b091f5e139925fe9f5b1d00f2282de4dfd77aed16da35d78e6990b11cc68a84ef12a29cbc508f08b1dff8248ebd3c0393ea10435e53a99a5d46f3c4c4cf8305740c72141fc90e166201b014b05023f2a6e5c8ea372b0bc689d9173e872e903848924aa818f81c3681d6a2df87a63ff0b1f3abafedc11f186f7c9b66a27ae9e5d9101705be1f1c7460b204f09db5e09dcab7fdbbd456d70309d491041d6ea5b2975843aafbc5ac85efd3f655faf552d5506c52bf1ebac6055073ae83e305035e1b22b9502e8acce528d81b5f1ff2859055e581c0ac7e67aafee29861966e80b41d11058d07f7fc82e3060049a39b1e157b52a60a29faa601e5120b27eebc2d94b4c7b4dd64564cca81392502294f307938f94a9eb55165ea54460562c202a71f49af2e4b46d7bf8781f56c19b9a13b4fb2a5c63b6a381bd02695d505015f337c1bed7383704b49b198c1ae331dfd796c650924d1287935e684c2966bfad43fbc6fa2d8ffcfe031e47a99648d79053277136404375adfa46720339ff76ae958a06dc48406d1c8b3733d1fe093d036cf2eac11603f0d727cde689c2884e65b214ebbbd027d1c5840d3e2c67c2c872b5150b29aaa22a9bebbacec179369c08124467ba68af279d0cc10ae5691f8ba46880d2091cac2cba4719c878ac78b54f730c4e189dc17841f48d4bb0f801a6bdc622c53f8c6ea090deb4e809e9c1645fc0c91254b81cf7af476b3a9c4144d99c553eb0186744739aa015286c3cc9bd86c935cbb2fac4685369285dcd2d71f2403768a0c65d1e0df6f9f19335b5253a87a04a542584948cfa186cd8b7bf7de75b5e1ad334cd7a8d3b435cfd163507f45a8749ac148a834b43e2594976ba8b865e3f8db1f6da145689a993ae1412c62476ea7957bb9ef3036e582a9cf5faea52e72763f37e455bab76952805dd83a8e9af83c55612f4895a7f56a841bd12c66db37d1651e0e2c45cee45affc0da404a2cf0230b1a363ace89a6eef8ab615fbc30335c768dac4f8303b143e4d2a8cdaef1ed5a34f2d569619dafba33e631f5b15277e0bec9392415806470f5eebda02fb8c49419933ae8340fd092170c383d1e6660f52eeb09ba05344238eb2165646654c2b223c51170b9ff12ef595342d3a184064b5255129fdc7ac141de0a87933b0b6fc9a6511967fb78640fb58596d1a3910840afe287d31d3ca096370cfe66378602b0b5f0ec3563b4b302238dd0e8b6606d12d461ee0887f7674ff0b32c679bbe1bd1f8a9c92d6b3cc78f65dc88272e58593e6be3f21c1e8bc9c9c770d704cbe3607f60a7c0f2d7435b4e4a93492c3a7c72a0fed019953fdc22c967f53e89a8afdc7a15535c9435a10b3bdb6ca94a5c88235de82cb6ee9744c1b5961625ea92b5744d694cd2971055afe8a156556c1d06c3b54c6f357202ca2551c2cca7fc335da37faa88e0f2c7ad19eaa46ea1a8dc6c2842e1a8fab84ec179caa99b74cbf8c06ff9129bb5114a18e6aeaaf211a1c89fe0374bb0082bd47404bf8784a521a8d3a3924c1a702380bbd9d54f1da74d89091505f5c4ee7b714d8408cb505fb21f082ab8ea2e63038c2df8f2aac6d463fa531215bdbdad03d9f071d8c129efe106847522e7d778cdde8d33c46b35c264cafbf32465844faf4eb38d1b8fb26c8ab7524dc848d8a31144823b85f07b700e17c531c604cdb19a71528e374c762654281b362dcc0eec6f5b322170efa7c3d9f732b5e056456f8aff3ee577885d28432265b10ff8a287e212e4cf148bc7c4d88ba2834dd98671d721ec89919d4e8123cf9b676190e7589f59563d43f958210e52f42b56aadd880ca9196cdb45829553f2ffac011d39025150cd14843fb6602cf299e59f4b65e11ffe17267e4bf80085eeb82dce1b927b21b21229d36c633d9c3482e22dd2a7fc91c06f074396f80d7097bc48523c94de6a1570f1272db863c86d85e4b61463a8163c4009bb27dd730f1ad7acbd4bd4ed45e00e04f3ad14d6e40f1af5f61a2bfd721809afa77c59d06513af30467c1b3ab7f0e5dfc46fcb49acf8511dfc0caf9c669db03e2ec507bfea2d21aa22dda15538fbad3c9fcd9fc977d1905cfd5939f20ad65ca71e60ffdf82e4ae432723271874ccc50ad807ae0612e1d15c33a7fc049832d62401af033b472e630875e994724ffc85d141bccc66a9ff4c3d4377cb152147d379f97e6fd3209cb9f77aea014cfb80666d761d539c5f5bf104106dd936370188b56b1b08cc3f0f835c79af04f0670b6d9f4e77cb27e8fd6cd0aa76cff7a8e1949751eb9f2e1cdca083b8f946a8109352f5e4013f632198088e3084360c0ae4ef7fa4206b70092a1d410112941d1552cd13f29efc77c9a26aa41149160a2ee7a5c445d1045537f4a693620e522138c90f521b3683970ae70b3a44b3b492e0e023b2823caf0b11058c7f2cd4d447e6dbb3d2c1d07c1d3e42c1f4ccc0bd899ed88cdde905e53105cb32cff8958cb49a50347e393cff2d581f5d0034c12a57b509b37ee3c5dd42f4156734494cb8930e10b1b640b7e542742b2234c27c1143d3c25339b9069c45cdcad226aed206edb1cd5e5f8ffb70a516a2e189b10ae6269d70e4825be695703e03be0d5b50deb2c93033e044bdac3723f9fe101d4a8ee8fc04acaead51aa3ffd3308f10ac7512e478293ef36b3468d22d221065c197622e3deee958149c217c2320c8e63e85886f8b46f5af01e2389171999e3c5505105bd12164013ad5b9c96c2cb21a6b207a4f18da76c3aaef9e30a57e6211061068c70b3b4c4f8c4c4dcdc0c69b7d0da842361b8c0bb1f924ed8dde8a4ffbd7c4fd821e67981ca3399a8c0918a259cebc63e21ba71f07e0a126c4c324b2e205812813b38070379b47884fc5cf9ba0cf9d7e54410db70d24daaa8d5fe99d7e402b65ca25d17665ecee4c4d9f12b3c376783ed652027a99405c3bd2d74b75c5e7ea685046a8af12ee60ec67de53e267e057b2887dd26810ac94e616b43081ab40b2bc0117ee08585be082e9cb6c0c284c08f5a53cb4181e7e104b84ace1e8fc03e1550efec803c180e99439715ffbd1b43806018fe5d35e59f264ff40ecdb307621f754389d8caa1241b44d2fac1038a5b609ee49521f9d266ee0400cc6e465349213483d8317f12b6c280dbf2098596cb5db78c03b41ee36802cf6f417d0f554700054ea06d2c1b383fed0011326bb797f8be91101a0e8038663e53e480293f14d40a4a543013a38727e295b84f2b67a7b403540050a404ee6e8f91b2f8303aee92eb21252dd9d0d1535439672932e1a886b3095f6746e88d057209581acc106c50d219fa426e2800c6a4b29fbaa3f1153d74b6c8fd91f988084f789232fa93624d364c635aedc371cca480b2d9049698783ebc97e8eb5335cb66930bdbb174a950a4f1d530eb91361b3d02e98fb5a6d81ccc66d3d6e1351b0782d9f8b3c605b7d09acd2af79bcd52550f37462668b69e13fc20118ea0aae51a11cdfee4210e419ee13c5797f8ac136f36fccfc7bf36abcde6e32658ae50e92af987f3736e369bc697ad7f4360055039dc7d060d1e15dc41ebf6f51d1eb6b1dda4066870956597e726bc2f2693cbdf376d8fd76f9626d1faa3be803b0d3f37cc04ff93af265d9ac04a29c8fd35febc1369a2f6db7c4613d8629bc8f167f045a425ed678b8025ae3d70a894f8d103952e4284c1f2755dcf308977fa8e7412a6d6e1db8124d940de98a4f48f13192b817363eb6760df81e3c639eb2307d094df5b5d1beaa5d22802ef2c961624016ffcbb869092a3d1f936534175a7a99aecd1fb09a4347b0513cb12ed411d7de850bd8744f3f45c5262fba871c88b6d9e4eb324c4953950143e0ff0463cb86e75515463d141bd93ac3d458555adaf11db9c7e17a225f8db1807a4599afa54d4b7a62fe71ff67bffaaecfbbc93be08e80c67373a77a049b3227441f7aafafe442fea2ba4db6a3449ba7430d6b343db1f64575a182ef869d362ae37089db2fa44e6c591ce7b4a4d50503a62c046a5e851f27f51c85760de9ba77531d9ffbc84972b669ffe6ef0f8107c8893f5a20f87f3839d2bf1b6739c3ad8cd26cb1f65a8db767e7ffa00b7ad1c9fb401d7dbf3fe6942ae6d727d52c2bbdee7fba00b73dbccf5a11decbe9de18b2ee47637cb377dd035ee7db6af1d693bde59888c0b0bad9f43dfa812b2ba0ecdaff289487490d261c715fbca43b32957f2917f2cac123efe87d24aede3e88357bb4207321735f07b24a02a3e70e086bce2502a094761e2d392a5442f283fa8be91448fa504bbdf66f6410972db9be9471dec7e3fc78f2ac0a56e2efb2a97b613f050f6323ea9065c6fe77da708b9dae4f5490b77bd9fef8712e6b233d38f3ad8f57e8e1f1d05bdab3ecd2e1d37775668847b33267e2bfda53341f6d21fdeb8febd4bd423bc91b07aa53ac77cd887d70f7ffb905750ee6034a4a04d931c5cb6bd4f78881f182e53fa3e99e44342f6e0805afbcb611a67416bbd22310abc191fb8e04df88004dc3c0e24e8a67080026f86072e70133e20c137cf033afb5532515b3953bdaca40e5237f383355e88cc7decb01e780dc25abec5978d3466b0dcd7ddcc67c19865f72b3daa8aac0ba4d05e6035ff94b4732225e71e0c232ef60efb6a289b0c8a3f50e0a56fedbd7eeeb08b88f861ec84e1d1648f1b1137c7a45fe4008ab3e1bfc933d44b037a08c41178533c088137e28312bc413e24c80d21fb9f88e1dc3f2d43278dc7115d4cf750c66e1139e10cc72a7c372624590abe218efefec079e84010d7ec1edeb8cfb5293821d547bd6cb88e75dd1d018955e5980d8febc282c1a22c98528314aad0de217300e2d06baa5396c4ebbd2f56a2d88cad429670fb7255ac8e955fb830ac40213f9b92ea334089419b98a6d5675c5dd760beb122964ad4b1ba041579ebfee64a1952cf951badd05919e2ef0f2b2aba9f2b95b27f43c44a7db30be60e53f60299188ff9804f91129a32e339e4d88461b34fac42256a75c633e2df91e6e707d7175f608c4df377a39e833089f7d12473e043d939a62893f8a4dda52711be4eba3aa1efc48d5d03b9c14956e10ece6acec1190dc416a612ebd44dfff8e638d5b9462af2982c5673420becc06a31f51e08a8deda28f0c35d30c51037a3646012ef43d6015c306e6c09dc6f0de58b5c8062a5a6a9cb4d4a3996e463274601b22ebcbf10829b1b50c85a0480dce5125a670658c1a9d938039f110304e94f8a496d72c4074ff1095ae06da4c8cf38a0161d1a87455680230e22c0867c03bcd34657523d306ac68ab80d44e9e23b62b5ae0caf431776c88c2f88156ce514f77c76bfabc6eb322b7b0afa11a39a23af9e9e37ee67284cfdfede14a2b3bf33f6237edf1bc2b488a9e5472143007dcc15aa36093a85c857d20df3cc3b65012f573a70a9ea6d1dd096e221d04472453849e12bf39e15152b033304d48ceba57a7fdb531411f4f25fc6f66236749cac2a2f1e926ba55bbbcd12cda371e0500e035d352bbc1094aff8f7f70797c6aa3cc8e6e7e2fd0979418ae74b31df931434d1a54c85a142a76a7e11fbff510603e5d2932cbde622ceab2ff92b1908b37df2f382449a9962a8eb7b48431a37ba1984bbe8edfe3d94e70877b6bd5f61612a1be74b0f70188d7722ce3bc65c3b1cb1f3395e391e0ab0b0f8ba66a1cc60627dbec79cf704d5c09d0273bea815feb3391c96f8e68d10829a357f194dcd1671749dd2d66c14acc71ce01c3136030b1422b422472555331b259bc342133ab3017b0104717715c751fc4daee8ba68f8943fba1d639396dc3c6ff4f0d4626426c0ff07ace30644ada97295d3b41ac0c992ce10276a2bc1becedae5c4d35c540640fbea0fc3930b838223eb462895e726f5744e04443a0a31ade008d7647e68df9278d95764d12c3eb0f923e406c49bcb1d01a5ed97803c06f1e152f64614f9fc598f546c5586c286239008c4fd879200b87ab10edc04b5bcb7250b3f4d8dbd3ef25e233c3bfe05e75df2b8ee52e6aa538cbf021f1d3011c3a43061f367553c3ffe811306da0ed29014d7d907f3eee64f6bc19ebb1a38d520d67c546b22400792e5edca978907e3a39e2e47c29080e6cbe1dd88b35377b468fd00e7648f55b45115285925fdc6b396c5c2abcac539072e9453abc947c5202850d1dc6b4330391279a3412f5eeba5734961a22a7139eba3962595111735dbb1efceb0e40cf436f52686da4f82679afa85d023bb593d5c8636994813f859c33d4ad449e5a95d22271b034cb3a43eeb5d9fff6174a30d93f0112f108e8640c61c7d1318ca9a4fa1cb17ff1d495865aceef1184fdbb399366f8f8c07e3f43b283e20018b706a21612e2578201411b98a06f3b35012853419adc6f1759272693951bc24e52ba06fa56c05b17a13a821615b1596b36aa2818bd11261040833b5536e1f7347278f1259a9446b1656fd0712a80edb6a5bfb564fd41ed3e3fd8d867019d03cc1be22818573d933ab4cab0d69408d449a5cffb952c2388f67c9a60d835a805835ee93f278aae34b6f21722f4d492e11f6536fd2c697ec6dd2aa3d1a99c170f5e7c1416a0fd6a5ba7104f95b2628c14c876b015328e10eeab682503d7533c3630793426cfae0c5d2224113714170c482ed4357250e276fdd301306d5166d48cefdc4108f8cd321035dc3f60e0722eb0d6870cbfa7d0dc7012c9f6dc68950ce67f9e2d5d3f9e6b1a2625e7056c6ef467041386bb0626640b60bac962841074739c15c0ffc019b80781d7d27ea7387216c1ce4cac00a726a0328c8229a2eb40f9893848b25662547b716decf25133e5f204938d6399cb23523c87f7741fbf52d4d67dd0d8198f547108c8315ba5867635178f003c6a42000f307406aefa2693a1d1126874afee580727fa93ddda0202f9706d8b6e77fff7821ebbe0e0fbfe181a9acbf708a1f4dbf99e8dc9f4347e6c560886786f9e0e4c6a7c6adb1b6c5c269f87b9d58da9a6324ea8f720a94525e4851c497566d2e777e71769b2de42fee04c05e1229277b6b836c71039533da0a1cfa272a63925153a2d35877069ec740ef965820b1ac1bf564b42a4a1e83287b9dcbe3b9a64ad92c50fbc1654399bf0557d9544282afcba4aa4b8875f3dd42b2a56456efad34d60367c759705578976874c22aef4a56822596037dc23b2762414c94ff135ee18f2386165d344578542846997ae3d781788967aa594fc5220868193149f72b45b0cd560b89707e14def8a8853e7417b8f8147c2f6f79871fab3892e23b3f731fe5e73d56f1ea367f55febc9df7875e594b3c87cb657ffdb397a8a4dc9bfac8c624068b3e25ada94248aa51fc1522c444cd6a6aecf62b1f69f8e82c6911b47d8e987868b77da93b68cb980b46130ec88c50bae44bcef5b121cb2e4742525e838708b9c4dcdf6edfc3839e969947c321ec315a8c85afa4cd545528371b644e3639d4d4937680a20067132c2c8d5f6de1cc9b5a8c780371dabe68f1a34c7d731296f5374cf18053f6a6a6f58a94b2a921f0c0d493ead596c87a4aa8ca46a971ac97ff8f9099f089afebdcaa414c0f17c073fe348888858251dcd72d233b2a99b4f3fba5ed5d9bb09d3d2f8cdc984aa624bfb1210e445d6b6e4599f7a20d99bd3541f808dd477b181ff1feca495451722228c4b29cb046c00e149e7f0086518a210e4450797609d1f01674465a000061dee26bb8d8b4751eca1b147eb018165b0c5c1ea63b19e8e50b6c168e80b17f6e6467d4ca9129e89b0e12c0cf6358e13f5d8232735467b9004304331c5ff362cb692b1d226294497aa811dd1912dcae125e222381fe0d76bb5e616e501713c5556da5917023441f26fff94b888de61d747c873f79d0734d07fe3d7c3399ccea4c81746babc4add8319b3f6d789774b43687591928070a87566acd7080fc43da04e45a989d8e3184a00032b4d2f2694dba9e58234a54dc186941c36f0870212ef751ac2ade745450f8e69fda9e9706c9bc8423c775b8db6e293aadf118bb0c86005de324cf423d113b9f742e380ab0b711ff9d7712cd705cc4765715ba43f454a89e31388475fce38b763724c5f0cba26dee0e12532022640e0c87ee0bda38397200963463882abdd607264ea31f0c083f357a7c6e1783f5a79a70d603eef8b1c4b01d6390be0e61871f27a94d4c4ded05968dd89e8ad135cec8983f2321038067ac679ddcb2329a721bcffa191e01e3230a5785ff826d1be4d646b8cf07da3b701fe599e742f584144b2356fbd29f1bfdcf31260f7771384bc221a27023c5841b9a82cc0cb8edd91f320f24721cf4c4a84102cd4da0d7aa5afedcfe0551ab076367db305191446ec47d2fc5d6b2f99ce767734b4f8e1e998f4744131636fef28a02df505cfa0e1277176cee9fa93d7cf7700d50ca31b5565e205a7a36981738e420374ade9a0d2563b8ef22502dd59aaf1a3d50a922f8dac48093ad8ff09165d766d5627343e558f9153e21c0c47034f85d8775e7e902cd029696414648ae9294b5096de4fa5bc6826706a64c52dd3f922462f6bfee81c1e26efcc7d9e51b009c27411bfafe154c2c915a0fc014f83d0d0784ff90edcca89e53a75b436131f80f64c37fc465e23f9961fcf715f0f11fe7801bd42ce52b665831d3b8875df49f8dfba17c6f9d4eaaa91330eb51fda74fd38e98f178687dfaaf9c020283d6d4f1576773fefcd69bfbab7455984a77157bbdf5a62844bb2089634aca501d0d61e95822b6ebbb0c6bce3049d6b0614ed835547d138d5a2df39a7fbe9456acdc9a155b658f599c51dd13fa0ff2182faad26162a2de4204fe2702ac40b5f6081e8b96ce901a449e484abf855ed28c2a162dfdfcb23724f9e5462f7f312a6eafb44836f32659da04a31347db29f08637511e12ac82a0947159c80dcd5740e31ea71e63899e0ee37705f4d11b90dcb930a63001b72894ee65060d60e728da4ffd92de3d149f865261d572b7860622dadd35859506e896aaf71a30457d861e99d042f51e38dadfa25d5e5aa8ea3df24492fa87201a388896b0e52b30086af5daeffed383918b257479f9db862ebdc81df1c0c726edd27d95897919f53d3d848309dbf7605b083c2ecde489ff120684fbc881aeee7b761fbf3755fe43b5d68b9e45a889cbb693e749a17fc0a03e74d2ebac05cc3e68f2f7c66748a8ef2819fead65fbbabb3b9eda880dcd5f3e50eec467f3fddb2ecd75a322e010325b091d09c7e5de9614ddf7e8ef50051f5130ac937920dad73ca1a21f2ac99d324b7f7c3d15330a0d0a553ee9dc2c31a93767bf042953a3c958127a077fe37ba70f92eaa81fc5fef9483208dc8ffc09201e6ef66f8a3d7a2aa72e54f4b8b8dbde21fe48303ed3dec0246a01999c4ce0106a65e13644264b679a6643cd3cb6271e562be9c2cbd82e700ea98875974a14a5450e1fec197248e0769fd86b0f83f21da7f9b3de58fbe7597d752fa9bdad78406b5dc386fffef76b78187fdf038949f75d28f722491cf97f981e445ef1173f4514c7dca61aa359b6acad43a86b31177345ac389f6af6a83ccf425e048cadcde33a38efa2f35e95b8b3c2a863d32b7718466196a6da9d614ee30f011d1362a0832396a8be0b013c3f7c9bfddd72025badb136b057e991f04032907c2dc0012ae8016f68e2fe40fc4e0b9c0e40d1ad270614b82b0c3b83939634e0da9ba7596442da70140bd63a566cf53a7d9c929706e96b0e7243e4290d2141852880986f28bb7dcd928fff92923226a99e4cbb2ef682398316e7eaa019a824ebb21a381a03503408b650298dcab16f1a3d046eca71e8b0fae56d393e1f63f83e85163c8b00aabdeda68d204c5f1652d22ad8d12fa404fceb94227eaf87df7c68ae8234241f42848833980ae07f20588080686f83f61b40728eba9a6230e7b13a41607540035712f4d12738704586c19040b427f7382db871b814d50c45f6ca417d49a4164523f9681a2e0f54371a04443609c8955d418006efa6a9be694590d5a6cd67ed95fb685437c828c79a30f0b8aa66c0dc914ac3b79c98c6dcc9c3f72ab029a4cca52450e2343b587aed6dddd84098611a606c33b18846a1efd5e6f534f8ebe85015750749a62072827894fa07a139d0f8e138afa27916528f71ed8c7112314ba4191bc44e0ca1ace32bcaa84743628a2cecf7b0108af7049863a1adde3c1ac2f8afb8617d4b238467b4942c454e959aa4a08a743c4a7afcbd374f855cdb4d1f363652940f5c36fd77ee6e4318832084f33b61d857d96f4633bf8d3488f331ecc7e9d81d3ad7993c7373fc017062ce50ce9ccf1ced7eb35bd17fbd192b48bf09e8f6ec230a583bcf32d16d519a80596916247a1bc7c7c6b9909ba8d4f9f0ccf3d56a81a4a43aa4385100413fb4ca4eab0d012ac6a98aed8cf5408611f11fa9c3858b398ec6e5be51b209d47e222d1f11490d41891186873dd2cdb6889353dfc22088ebcca6449a25846b1d2ba99ccadd449615d714f2fa038308a75d356af3cf80b221ca5067351a3e06dff6d658e40f3deba83cb4b4d14b60facbbb9ca5bcc0cab9487170297a26d96ce9d5be52a063f76e1c339b54c457ae33195089417a4f38a1f2bfb15da942bdff4fb6cbc33655a54cdcb5b18040eacddd06b0b5c69d98c39b70efd08b3fbacfbd57c9e2ff0fb6469cf4b48f80592f572d60f481116671ae04bc2f1f541489ce46c3c9438efdd26b531914ef4e522bcebc46053e079c8601ae99b6835240fa7a83f9f23cd3750c9e3a9a40c3d7dd5851873d55dad963e5941b35a478d6cf28bc7a73f9e9e2c7942191c45748452fc7dfe6c1b87d03daf6d0f8c032e2507ec9613ee40413826dfa3377b70488525362e6d7e1fad24031d437fe14e86131503e19a64758a73f81e5c82aa07d38111a69d310aebff7664607ef38d915394259d0fa65d3e054d6442b182245d489a52db9832f84be521f7ea7ec66450238dfbf7b0b5b84c18b03a3c8832e849642dbc4330654931ac831d27b8992e7647e0c700c81147c8be0f64a328ed3748f6328e4e551b061f849025e19844e5f24cead20a252089d7a66780fe7a588347cb176ae3a888ff9b1d202de38d6e065d0f846865a06ad3669cda8f03a73b95610da3c7bd7d17ce45bec4276b3084c641f2f5fd11313b63b0a517438183590f62b7a56c39ce04bbcafe04857b97216de26caf36ec1d7816436c0a138ab7539e7dd20beb08755331b1da060a08b44d86255d676acc6b2bbbfb32ac0e3d25b8e23bafb5d7e6ba5193f740436c2412d3e872c429ea56915ac15c087f533ab616a6b87b360d748c3e80a770da049e5c96b4d86483c4368e2c5ffaf6aaa0a6682e788743f666fedccba8fa7d530a492db9b6502a43cbe2313ec9b657d864d560844578da39568f141730a8c92b0233ae4d3d398a180b94ab60e70c3235a78bafc373886f0ce8ed513ac2b638ec512354138470c00308b80f790ca98677f91ae09cb6d70040d768494f1ae42c7d9dbd72cf7b535d931df9a4111e911d07c2d08d4643b8a11bd9604a53942ee3fea4e4c8434395b154376e4cb0897abf00bf4c4fb05eafe9cda0ec7998d42ad708bd4c6f285abc2f68c33179218a7efa91902c4405a2d2619de28963c556e3a0188d24a6b2b379e2a40fe4599408428601a45cb26bbd0f64d5222d6aaa4a3e4e6c1bb28f8ea57e8d1c823140e2940c69ae92da3133a28730af5aa1265c5301bfa1dc56ea01f9650def9b95e4a4c0d0c69e10e9b80784a216b4f55492c9bc5ee63f493bcac5b1246f5d349155d19083dbc72b4b3907fca86405a316de29ecdc2dfdf48ad24f698312db5c2d82a70c3af2a27d7a4a75e996989e28ecdf01ebe5aa7f8d5822651e807cf1ccb9633c5787e22a829f467bf9e0bd0acbb343a1a5ee8e53ce084dd924d562d656b1a46d36620ade153cc8b9f60fb45c42d03341685bb7ac6467ae01e2f01feb70421ea318ae993a6e022a8d8ce658e52a67cac9b28752652d2a19ca11f2e1186f92655bd80e36b7f7adeef78c8be65caa20663bfd96d19b27bc964f2fd20e0b39a4050e48a08f785ba25ea7abf2acbf65d26bf0d312be3b554bb59350f08dc7571cbd72e4829e2bb910722eb776ed0b27c0c403906eda4b864e19041d47139eed622bff41daec472331edd1fe2d72c2c2c219875fb0fc1a43892606128984f7ca3821369f2637b6ecb1c2ec139da12929dc0b34a3fd9461593ce7d3ccf81ef1916935c03a2b5c465d25079f685ef31c252081e7d304712df1061b0718f990f36adab11b028aee1be52b0b22bff02d0bb1079f931c29397632585e987a8195114f8edb2b0980439cbea66457572d8d3db7c914c1be5a80bf42fe026c381f94879e4f0f5817b703e2398625c429e8e28582cf02d48d9074bb6dda0b40adfeab7cdbdcdc9faa163fe725203d78e34026d3285cfefb3925614ef85195e0a28d3d5610af996dc018f2fca3459aa0dc54d7c58c49139512f48f8e887757c6d81aa932f48183e0ec5f1bc62c1e019ad4a9840b80d30a96736e25b369c22d0afdb3db01d3853d0ea1178e34f636f286262ae41b515ad3b7469ea7942bb01c74358e4d520e8ab76a9c157d9cd599e473438d2e3709ce5ab2ed94edfd772f8ebc74fa56d95a5e81c3f5507560c1006a413466b6537375452a5c703a3e504038b49126ad9e2b2d14103d4ad8ec6541c3b2b080f34da046f94c544301f9bf9708b7a1ee87d04b0d9120a0d27b4420b58c768a31a4f31d21ef782b97319378adc462cf0387121049a7aa09238aa7d616de36d4f4779121aef4b980d7c1d3ddb86410f42dcd2d19673003dd062d4d9a712883210fb1c3adccf043ea72367dd715431f61ca7ecdbecdff8e8a8cc350f29917e5d088d9b0ca42c236c27641174b4bce80863ed97cce4bcf3d091e6db0af9283f1873cf1f617c8914363b0522c2fae6ae7d5607cdd0d876409765c0c749b8447b44ceb7a43ffd1afd387df1931037d566d201eb727a920220eafc8802fdb0d09ce573dc5bcc9bf608297f97cb94fa2f2c3002be5dbfff74b2a1f7556d80607a05e6a4c7a4ee971d8b35c6b9d9ab921986298dab9a9e4d5eff2de275d9c59de48bc601cf5473fa4649075fad269b306f2f399ca1782e5254e663dff4d266753932ffe4e7143864dce5b12a082762ec57165245d4e9e0b8cc30e78599399cfcd09f4a2ed8a14a642531aa935916ca75a91462b58277952ec51cd473fec743123c753210ff9846e69586996faa80451b0c537689d5d61328d0c421eb747ae478f473fa90ce20613502887ec8a1b2a057c217f09c6f63fc6c59c53fddc96be8a77acb513a3fa242a5a589d3d0fcbc446d4acc74123414f8f84895f919ebfa4e7de96113a3a090f11bb6afd45bd38c4505ce75f01cc6718367434bb57bdd8da016b3a3472b2a176c9197b5a3aa7d52fd77ac88f4565774171989e7e17cf61aac7566256369f72fcb18d95b8ff47c442c1a7a90beaaa4a657fea1d39e2bb4ddc19fd6c493187e732d532dd9950b72305404a3920726fb10a7c6c35367f7b13718f752c689dcb582ad55512110ccb58179c589f71cdaa672004f966fe6c2ba842f07890b263e7ea543363d7d851b201233b800bd87551426507421c57e1955d2bdee93010730fb8c6afcb07d6edb7c67c5d5082ba8d63f5a55cf55e6b512f6f07fc094cb287eb8b70062ea18a79e043917e7fe5c065961e6994d5fcad6c665acb2a78422fca2985785bf6a359ec4392c321fbe46d364e8eed2663a151711694d198a61496d48095c5dd2978ad009458c6baf9ca801d10253dfb90560063cb27b7013d34a769af988e8d90a09b586d4aa88e45d79d2cb26a5949efdb5469c749bc2c6f48af3a74c624379409863b14dcee98b115245fe94d3210abfe51deb8266f6aa6218d9cbd7d94f395e44a106ac3fc361c414454db080b7c273c0651e494fd318e1bf868f4129231942f25af6e4e0c44e4e11e85612ada7bce7caf156618c1266cc7a73af22e3ed94e58e4cced01410c8645b50d6d1d14a41c32f5027ccecf38ce8334a19a8b780f46a5b6d7ad3e6571e028bb47ceac837de589cf238b48256d5bb9517a866b67a0eb26a095f6dca7b4fe32bbab31db01713c5a032cdf7910a5295adfc92316eb15225971f6c5a58b6bdeef9667df7ba5f3db5087caefa8cdb78da3793368422bae786240c15db63457d9131632a29b3ba64c3606fec8c8cb0fda815df57339e96b48421492f8e21b14eb6b5567d4605a23d152ef582bd31251b7eaf3a39719063f0ced74fa79c6453292967daeaa557d27496a4bfdb869fcdf665f686a2441510fb915f4795a570264fadf481f6d4f6c2306f54057439ca5d7ed6476c7b1511d0540528b7c202c8a0cc378d53a6c7bc6ffc7c75410e78bfbd2501357e5ef4a1d15d331daacf7a848e5f6fa86f1c8c52bcdc7567428cab32da33b070f324addf9108cca3aa5ba2208d9d5fdc8984a952ba1097d262ff4d9e1929955f053f0ea93bd33add0b96255618f4e809b86010ca7f02c1eaca67ca1d4478667272eb2248e7765412669ffd66186a59a4be30a5ace978c404f9d30ee53c24005051f97ebdcf8d06ba45f35556892b23ee11c070a59c1dd8586b5617fecf62ed5b3401cdae35bbe871701348046aca71e28bdbaee517ba21ef5ae2a2b2ba05fec303cc85180cbdd61e6ab378c1b4025be23ff49360cbd0935b7d84e07c936e44503932937fbdc622684d8ba22e93646c99385d6d5d2f64db10ce3e88892e4b4d14bf740c185bcecd69b2254c8c9b2dc9fe76475b8acbac2565617f5fddc2bc03fd7762383a26149a1c0d96c2954da9be05b7adcb8b426135cbc2f0d0f28b2727727a58d46e3e54cf654c1f3027f784faddbd38fa22e26cf9f69c1b7144a651a211e90deb0f352399b37f707239f25ff41a2eb634e2707c2f7f988cccae11122c4ccd782f345f62cac8b094169d2143ee56874086db44f9f518156bc767fef583d331be496dec5aff468605da0563e527f2cd86fb314a9c222bf3718b492ff0bbf53d4c8efc960c911ab9089b29e3c006598f9cc41838f9a08092363db32c7990d98cd87c72c1705d7b8880edda038777d71e70ee04afb56bef8ccabbf6a8e6d65da575d624f8e7618b4e1c4330ce6cc4d928c110ca18ab3fbee3fdfb8382c676ad19f06cbc78375092ad170edfb47faee8be38a40953ca94d75362e640199ad0272c5da7bda4c0048d1715087802eb0eaf15f6782a3ee123e421f570867e7e349039e447147b2aec2b7e1549fb520c65378ef50a3b1d457ad9f45155386242d334faf00a654152f08aea3a55b0ba6e446cd6a56b6f1192b8ef9d5c6b8a2cd91e0780575e08769639dad1a9790d9be8e9e9eaab4a1a6451ae4ab3318100e9c30469cad30c21835e1aea05c0c2ba879f84d32e5bf4295d29f5399bdf5b27585e9df384b18944464d2b4574a3f0197f321acb19e59de250b01d4df3f5f7d7b867957a7efe2e73b33f218bb10817b09fa516f59bbad7c143107313300befc7be205d37b0b3618191736b6ebb4360d8c662bca9f1fc987399e59b1433b71c64e0a3d5364ad9b1fd805815b5143c91d2d561bbf1d8181268158258be0bc9ebf3c20e5b04da7023fd56985c1755feafa1484221879a9adb390c036c94065ef747a91c331010b7a603c000030ca83bea020924411a2dd977b5dd1d77f4896ceb1db263f97a2d703846b700cf28cde96b7caa818d6c9952331e351ec11a6a5e6eb9a51d78f29a7b29136a93e797008ac1f29270a6b0662cc8e68642c605d62638838e28b713e297d11eddf15c03da3c9431262e8b8696af0bd43eb2c8f615d208f8865e61ee670ad1bbc491f21e0a98f81b259ce85d1290ef5811c639c559fc6b4e4346914489dfb291c8ff8230bf8a5caf7710c14d46908152d6c522e70a6c767c2de9e3a253a6c81112d3f479136f5ea1808403d0bd0c7d4247984a7c0d231a322415a2fe0412f7011b2638a84f1d900001fe87d592ac40038cbf24860a4a6b150e9ff3e3475e6443e82e28f6015f643d1958717e5322b2cf14e6f80100d97e03209fbce107ad1fab02081583e2f1b90e6ccf3e85af642250f08c88363b388313a413014dee0b71048d650485f335f9fa042222fd8ea59bf79d88df0a4a052d3467c4f50c9fe81c2d407f084f855f432ac72b17b5426b7cd80f662b8063a093e95395c565079360390d54a15018507abcbbc283d37203f718dee6a065786cca717f62c2156c2e3c815b71b94609bb9c6aa90a4dffda8703064490541e42af6a38e86f89e9bdfa48fb0503610e254d25cf6323e97b20c8cb6433fe38964716fec98e070f634bf138a8a2bc083b477222384ffc969f21b981d3cca71ced381d4a3ddc2d28222b797df9023ce2a0b476adcc400d9b0b4dc19245d9f024448436f84e0dfc04d5a8c8e536da25da3460ea99e8e0e31bf294f2360f10e425241b9e474a5c062545cf69408a936f6d12f165c244795b9433a7813aca5a34309f0aad17d9c7fa7c61a939f7f009ca07c9922b935203e4a5e488f3a22830f05806600413413193d9f67ea0346067a655a60d4a6863e22d99094065e428f3686a2082171a99f246a9f6b21c114abc40a78caa5d0ae4a53c4441c9bc297e215685b2a363f6209252a17c30103d95988867602a84ebfa50e3077149e04eea72bc1375e26f0be7f3a4c8f5a622204009b105fe89f242361bc3cbc1406ac1d68fa2fc54b641bf7619caa0025541dbbc21fa239866e401676ab80799bee2cccfef396a70f7f28f5a48e46410f97876ec5286d2e0edc928bacb8e826872351f22d0464e56bd0f3c4f77559df939b6b11c820b2687e5e7f3971ded104e09c8b212b83946e5866e5235e35b3318d0066d9857b5c6c83f3281fa4780778491c26294835f0df44dd0e4757dd06fc045dd6b9a1203e560a3f8a6b0d840435889f545f2ca9eb963f8ae2952bc99c8c067b943f69672cdf929b712bf8455173f55a702956509f0c384128086b823e51f1c88e34c8a8c418bd0c510aeb961f494d22010e23871e23fac49820a0ac0b31304a350393f92de8f9d5942164d524e229612b440a72f97a4f6e0670c5f79a856d4672823c0c930b9cb7a0558385a006e6812e7ac0f400900f4d800ee3b401a93c55cd0e09f3c4042632a781e049a826ca091036fd208c2d7580de403b99a5fa99f1a1fc34d84b017a9426f2624530e80c5523e632242109101005e0a9515e5810bc9afca05e2bb9831e0980a304179f038e4798a19baff86c7f2a19609b05a3f7943901ff9d8f8a52d2bdf28f3c739d41185266132f85a6fd27800541efeddd3e69026249d88a3131a438e90ef7a43e3cb06995359c31c14dde2e6168af796264c6501c8d3197ba19e24bd4045dc2913ba90a0c551f06184eef204e74350ec79ad3cb07f9a3b6f6316c0a58820e55264d8782f7440503e2f4277e28a8817cfb47dfb635606d537e66f2b61de38387ca54317d0d591064713c6979b170ce461a316aee48fc43f47b1a12a8b2b972ad3230801020494108932c10e6810e43e63ccbe5026f5dd6480f07206e8f8b653031c42ab06af5a1ae58e1ab87da81c8050ef50e8952bcc3c0d154457d46b422e44e6809741f3278f5181e85d448a8316b973e57441caa045f66cf14a31cea0e88a2b4fc74a54d00c2348a8de4e802a181ec957f640fabad02bc8f30154234fc28528d7b88b83c6430eb98a252034c700316e65125866eb7b7d4c5912af90a4f8a1f2b8e8ac3f0caee20b88dc84469d4fcb84e48da408f47cd680f8e7a300ffe2cd1fca74c4c341f429e284cad479519d76a1b73804ca0b000f7c1d4e61fe8d9510041147ca8bccacf13e3a603df2511bf4c873146a648e5d50c29383a321a3822a68c5e5edd49cc9ef0ab1671b5e70382c2d47d2e702857407c02b20e49731d04971426aaa97426aed0f4529e28a2a917dae4b84d05405db2baa6bd120aa5a3ca4560bd02020887913c8135ca1800ec5133614666814f864a8ecaf0f6aa932a94ec69fc16a2d0f11b410e66549f52670bd2f4318eb7704a26f458e8c171154cb38fc567819248e9ff5b8de438011fe2470f8a428797a3335c63c0a10a4be696bed1538b7e41b3d40f99c8f0028280c145cc4ac54ff68920374273a137421638007ea3125478a90246b58e1263b7ac3c61ba16011bcd376e707bd59cbc421a9bc6ecf0c8a2c71c027d160f54524269cd65e1f4f56dbcfdde1fa0e75905e480b5c680a4b8e2f51f5e3ec88f57ffc28b93351194f60e0f8b430a8f93260303982a11edfa5860a1a3786f893c2e04047e57943d94089bf206458b306282cd058411aa07a4ce07c6d5064211ba36808d2c835e43d6051d12396d6b89d14c1974063e443321cf92017824e4b0c172a42f9c9bf5ab5e567a6275e8413af2c42931de790b4f3a8ca4479347d88a0498c855c54dc97952c18f3016f14fddd8f3068b3e921c7b260012d710a47f00c1f4be8812c31bed8d441c973e6843da10790503d4064fe11ead1337814e713a489de2e862b74ca84201f430906f44c73c7a71aeeb2a10e4abcd52c2a2826277d0e77c50efd7b85e80f55b18472aa91fa12971114802581bc49813354cf87ecae966f9ccc98895c22478f4ff5c6e6020402f039384a641db44a051318d1f5751a5009f3b69d8fb00a8f7b7aa5fa9f3a2c5eeb4971669c77180ef69ed6667b1492f47ca9190672830a9a7c950a2c3ee892d32795f8843af221eb572558fa174f65de8e4da21cc503a08bb1e2221be215801bc396642c4f315fe20805ac0b831e0f8b4d8aa09a3010baa6e24cf06f33ca5bc4b828a44c91f298571dbe5ce0899dce150cf78320cc57e23403f50124f8ac7849f8473b029dd103279fcbecd3a3af5aa06d862eef1677c2c3eef01ecd09e67e560d5f2045f05dbca05e0086323fe5c272b85815f2a537bcb287bb25afb668d51fc9f32657901bf5b650d1124212e52453c8a300550b3223775a14036d6068e8bcc09cf89d5a06fc409aecb96811f9288eda7cca2b446f6690d137ece11bd98ad03a0290ee814923749907e838aa78723a4a52fc1b1dc08f9228c7eb200504b5e362c04fa251c20b5ca2e2345841f83f2620650e86c2845f5c2c5d57a139b490163c0f26025dc6c4a382e3c915ca811a2e7c1b6726ef43c6ef955fdcfc8913554f81d62537b24ce2cd1e8b9cce84acbf1006f04719d0f21ad68c64a2021d7f8baed363b843c10749f8828af528e501fa6aa1708ed851a00450374283f200373282074c457a15c998af352630bc112ef94b4460861c440509636912959d4553e8a5b835cf07c78aa27540e463f87470498f967c032c951c8e3d3e998e23d905c3cb43e002e4a6c88c0ea7d519aaa03df391b2a0ca0efee8adfe9678100b1937dbe5c16d603a7e221c72fc481e02ffca4c2cf4af419cf3c037721c034f7217638860082b7bbe5697e46b8c449037b087c43fc59db9845596b3f934c49b38e980f65910f4646ca4c8812261faaf34927cae0c259f850606da83ec8193ad19404d04d6bd08213032982d3a1e0a9f34347829002f6368d7470853be9a93250a4002532fc5cc9fe0d91f367302bb822a52d3e757d12015d45e32e4662e228cb048efc1743d08ce3a92e777910a80de0ac0c097b964e717b4c8f0d743f737146972566fd2fdd64a3ca1331c0f16ead2e7d204e4fbf814b9065b9a7c4e17e0eb671c34431bfb4a6c08c938e0c8f92c5990dee352003e6cf3f496ccf05ecf822830363f790c3d4c0427657a9011c10244e52cb1cb03ae04006bbbe2e06b85d9426d0452e33c48b0ca44248d7050ac1ea18ceaacf8cae1c823e115cadd8405cafad3fb1e6628748d82e22bd00422ab5a133f901c4a72a93d4b7e935188ab2121f4a3228d713e379afec68c014f60ce972cd667c2979550637cd2260de89d2850cfc6b1c995c436fd1b5e8982343eb8703d926e78913c429ec0864a3953ec7e832d6cd018524c78a9508050b7b7baa71375e317c8c5705978af7ec801c88fb895e563c5720fa639227fb291ca91663dbc01012e9f44e904cd70474cce14d7e639fde9fb5ba04205a5bc35c95f21008373680df2a11139be78ddb8931df0e911367ab6f00153e0bc140a10bc445a0bcd16047c5c0908198c2fcee732e0e6997d6cde0f823b594032f957d248425f3da037583be5c328a49c48af256f0b004fafb667643f1402de0093b5bfa5424316d1e84bf644b97ecd76661d02546f74e72e588557a207a522280c33c026d7783423a8c5cf926f11a28313e104bede1a879ca54920076c26331b2faefcc02d28ff84afe9a802281f02ccc731280af5058ab0f22a5582bc31cd7e5b9752a04f6284f2a4ab3a289c80c2b37739505468673e0d13289440d3c1b3aee4be5cec7182eed84106a583240ebae3c4f005b47cb2d8183a8e490df319985b5e958146bf2ac6913fd5c5d4ffa85b739e1452be8f945e289f1b75cf248d25941504e283e93df131304c3fd5bcf3e08f11cee44d5bae3404f36a54a1c814e0767906133ffe0bd0d7e7a961bee00cb1321e031a4134f72c4bb11587b2d02423e788734728e9c8ad37f5e2c7c72250c4a7ac6540856f889cc99bc1f58821f3af2018df01a0166e96a1ef1d6d1bef42c89e4704293f5fa11d8fc9d42b7fce419dc223018f000039ef8355f6b47e0c415b04a0e0055c046e0349e68b621a05d73a71c82a48c865446fdebc8e2fa0d040b00ca04cb40c6f168b49c6d0e64c56f47ebcd5985a4e52a3ccfb01b428e30222e7b3a0a9fa1812e27c0907c257a924c5bf9a1e321e3044efa0022fe73f59de8081b2cce444a1a7d302e8aeebc5018d207f0e1702ae37b7278ba5cd411dece96008097c5fc243941c6bca5ac6e5e6210b7961e859a469409fb1aaa01a0a6509de4961c20380ab95218d4ae008163dc87b1c947d5992448631e685bb5181e0fb70d2f5510ec47813400878a00782e6b50cbc45215dff66009ed70570e2736b881ccb2e1a1fc31004f7b0470365e062785da7ce08d1581281225327c10f635664383f1b82acd098e5206c7d4124a644e55496cc7cd140f53cc211b52ff1009527d936e52985d4ef2b7afaa48ce3ff8531843ad080c3d1427e32dfdbe9ff30b442ab8cc9e4b056a42ea0eece53ca9506f5022a959789c8f04ef20d7f428750e61fca355a29859ab44cf8515b76804df832ce27e6ebebd91c722d9bde5026a0a563e8e382ee01b0eb3dbc39f33d1f5dfe6c4b5d264297e3d9204b3829b3579f46468b3732cbc0df7af100bd8428c8478aa1e87f9cbd1f7361f3075db9fe31cd4033310dbc21bc1e6e8589198f35400d0dec04c49b8a2c6531641cb44f8c12f4c9ac0a4fe7aa086a83178c1c08c010ef83a7cc33ffd6086bdd98f585f8debca4fde99bece5fda4ba3c97a301466180b9f35c13dea09deed4bd1eb323baa6857048a0c278701223872267885fe0e3c0cf34c2f1440e195ecfd09a7c60509e7bd9cb85d62a20e7dd60d9220452e491732971038b90cc83aeb97183bcd85078303776a06d8edacc258f906c61737233c4b4aca49586e795230c7a6057ab57a373ca974531cfa689a157d5e28dd70983e3a1f098e1a7d624f12e4070bc314dee9561801c0482be677f40fa167133ab2fe5c922993d022f30f2df2fc95efe126435592aa1828ce04187d8f084c6b295eb8bd8ddc8b90601ca6ba22440f7ccb0f23e494dafa18894cf73e1e8640665f9f58cd04fb1adbd0d41ed73a9d3c90f15817dc0970d9ef708499e03c5e9ec15073f29c2170fd5e2cbeb41d07345a160a0500ec078043488bec5c9eb59271a2a482dd2f3dd59be280fb3f2a166a3601b3d21de092e3b6016f6e0bd99661934492bb14f5b350b9d13bce3ad9c48749698adcf353c945b0da1fc1a8ca2535c497213a61e782060271fb0e6cc53bd047f605687dfd0baf84f70621403a235cf28d4286fe224961b68e3902d65da832a98532b58e6e82187a97227d81c0423179261251807f9baaa6614e1df260d7907a51dc137457f1ee08c2e9fe18be5977a98ca785074f829a94cb898036d1e11af164ea699f6001448e5665096ee8459239f7985f600089af06f85446f04a1bd5409549ec465f16629127d9003f2170870f13f08cd0961dc0a937d4c72852ff4d87392352b48a507a31c65c02d21810dcf5754602c884519c7996d2a500521b4785631c41574c8f1c658553ef0959c0ba34cf2274707df85548c8e9071e778428efc95259c07c975e257d43ae0808a08e510905eff34620fad622bd04bb88182a6526425b3500505c98014c82889643ec3e73fd72150420a5a1c3d860c2ff2ad43aadb7054ea8b5583e8ff0b0fbd94698c1b0071c7db40e0f03332f4fd1c2af7430c95f287b48a270af1221f6915e7dd3a7441fbae88700e6f543d9c19966f8581c9ef018af82b7a4cbca9668e0f41bbe11d0c70ca5b26e1fa321508062d6e99826a29d2e8a5384a132403e3efb7706981ea9923249f6863f2156ce2cecbc18a571a6afaa7a981a201e17c4282dea081e200bf19e08b1b39d3cb717c129205c1749c6ec11757f506cb4332f6423dd9b13910517a2ee5c6ac1f63d0ebd5509170bbb20befe7c30f87221728e8c14d09344c0122bfc34c9cdcc67627d8a21331dba96682884a14412f3cca4123830cfc892ff51ccea4f9a65887c3c9a0c03dcc8885d65924e7601ed9702b8460f819dddad74912e2ab5eccc823e8e4f848250051c05b6d7f6b519a8fb0e1f823b1d7bb7a3af00673d4721d9a5d7990a665b0481f08d725c2d2db19c992095ce112348417e6df075ff2d30b2f948c00853c29881067a0084e8e938881c3423f9731d1cac996951ed095559f06901354c82134de8a8d921c86bcf43f92917217b1487f684f558e612196af7a64f6352481f1568f0bc56596c1779dd8904951b8f20f8e2b8242fe00650b6b86d0584f0d99159c861effb099448f42c158adaeaceb1003548ea501cf46cd5b7e6526bf093d4f07a5d767a525c86b21dde3524544e1128c3c8409b35ce646a20f616ce2707bfe3cc38a5c28975377dc851f898c098c870b9ae3204b9063f3345b7d5f2550c2d75a52e6d334b4321e4c561e80520b5ed6193541471504652481496891b63bb9539136424601ccfcdba8f0bf39bdfe4b861ada86852db7c325cc6a14bc5fe39331878102e0b58462f22aba2cbd94a33d2011587ece9f304faa44f7003cf28496f083e14b25b1817eb882e77ad8247a2e74333e149b15e19a06267f088a913c474fc71bea14e52b964643954186044b84b0f3ac60050896c9e1790322653211648e1fcbb303156524888f4d09e08908319da5f1208709083e588e56b9001e125e008c2994948e4a4f01e9e127c0e1f174b25c40adf0eae45a880b9c462a0d6f66c4153f0285d21b2a532bf770c124c7259a82063192c057bbbba0a507a55c51063ce80fa7a117c0e7f98544cf06cfa7183d2837247c8fa55e282c22eab93488f90918c01cc9318c4f10a4e585141893ab08d5fc8f1e189caf81847f163bbd293d3cff179625bbe1a2e7789e5b1c561d2edec9910214cccc94cc434a9ce08b307ab90e8518411237b0fc872e82503e0601729e8e25425663e63c93158f6009b23a6fa88889cc46a8864b1255e33359a1e066fc022aab17156794074a36908197af21f4e889361c2ee453a88fb1c3e9677f5abc7194120fa0d50267f12694935dc8828a2ab4c65d40daa883312ed9c822c57b71150905e002110580d15aeee502003037587ade8f0585aa726326e3b956b29c2b1afe06eacdc7b724fe272a0e5af702c38fd540f11d8472bc0c112da817481ddc8f1a0714550f1e3fcc13cb746f2b9f15078086b0b2c59b04b17c0fbc1c8e24909f5f85e4cab381b2dd8ddd5e7e34b71f1615bb7764a6ca1771b9f575182441b12cd884c6f044050d2226081591828996a8a3e1d9fad0f1a11f1487c036f75f86b95e86902a8f66061f07891c548b22050f4219a3606aa81c83ab534e0870d2933a92068514c4f9a7ea04b99d1f1cbe4c953287c2a0ca675263807249b7e41e686efddd06284e49d0867ca6579b2b1dd1f2956c05e25ff485092ad8f1fa9a181742c003655c2ed3ab0caa1329dfd3c54d7ef3c4c681201982ae0578f352ac99a37ade797547981c8492a217056a150a4a439e37815b3f4c1028afeb8bc8c77f4e7778ce4f608f78a91513322d0e9e2129520456c08eaa7c83930674c489062fc28e155ae64bae7c264ef835084b8c4f92a54790d1934c2ff43225fb0d9ae22ab25cf10647d4fc4cae2bae67b9e547b1f1f047e0422f74a42bef61a2c091c425c873628df81d10a038963467ff02ce0f9ae74bee6f7432f4613a013a001878a131749b7c0980b08fd228d26f31442518d8a9495e568202c6e1009bbfa31705ad75c6e7cdcc8af171fa64f262fba271e25af984261732815b6efcc29439344cb1ecf5882df81f418e7c203c5228901921fc0eda0a34972614dfa38ced8720c0e57bca7041353d168f5f993d823b4e2f6b23e129bbea0d91b92e14f0e21165a09031457991ed8e93be866310d0b432bc501e53c6b8893a46ef0328c1ffe16105858348959fb9e2f111865e3c6dec0bca028825d73283cd139571ee51de9e101a246fc3d2195ff1c3ed097112e06d8a74f29721e4f10cf4f15b7a6265497c2cfc1c54a1be54dbf2d788caf4e55684ce1ff82a2434e561450914f1e44169543ed0d951c821098e9ce54326e18145b43e890807caeb09ebab0c17644212e65e061f822cea819ee7b3a989f642f3fe4ccc88cce602a29f43e255ee4587c5b379e39cc9ef82ffc882f4465b72784a8195a35232c217c528e30fe6bca07f5904a0a4d0e6328e195482694839af03d04390485c24540904535f44c09beb9cf10926c1f4fc704c60ae14689343dae186baeaa5eb9f8406b8ab96cdeb9a3187867113b8504d4bd6c48597b7487026e740f67035dac1c3c222e6e3370bda09cd2727e35ce50d0894f22e7c7950137d52e4362e367cf1a52af14eb9245a3661480e1b8429686613824741c6f8614bc01cd00754c1364e9e7c9d359a725a163c9f870c594e51ea9163f972c75bb0395fcff1c08b81fd7e4ca3422f8658c837754193c95cbae4a486ab7200853ebd2c09177229d79387188be237d4b870d511a457a304f7b92c444045f031fa2474347d9140220905808a26213a46175a42d51144630d7d9b1e13505045283d1f10419e888f93e7d2ca87b7a990c003d3c03cc0aa36bf23cd42e3a8e9f03136b95c050a994b51cfa08a0c3d9f1114351f44cde8751e1d71023ff87cdf9c235aab4a0e7fa1c92c87d004eb8d988a4321a0c0e1a250325f2e163c9d564be2cb842d4a1ea8571c301350ec6433211608498098f3547f17720d1e949cc9005d28a93a5b5fc599e616ac8687dd88731857247c9a10ee62f0b6bcabba2ae8dd9d04fe85d69ad7acb41e4814121fa6e5f8355f295f48c3e6fb5ab8f92e8698be630318b00c7c01fc992b2599968d03b9550859c2308204e5465ca804f58a588ee74e2e18c749e71dcd61f13d5b64bc6f9659468b44c89dacda816e42d3e1a9082081422ab57a9aa11b395048010ff2298c0b1ac2e6948cac4101a5c0f014a906a0585a197dd09402df276c86d7e9e2223f0539f8385acc5c925d8bfc6632198fa1befff4618e700a81bf0c1808011d8ad291f718a91192c8875e82273339ae65c3cdd01293fb48b9f92494366a218a82cf34de50324dba07b4a1c539a172e41954eb31d4f54b7851f5c726183e00427ea82706839e550b24da06d1d82b18a5e955dda9700cb88064464d54fc1a5d6650354e0b1982294fbf648ed817e992004c44051e7f65ef2c88a784f3150e48783d719c7e6748ce92a888fa388f2e391b0042be541840594f41146790e30bfdc1a2795b1b442d0851e548b7ba0765a4870b4b1541add04ae08df684ec200ef559d226f8143db25e52720dea44d8e1e32cf284f63176793727ccd03a280ce4555e62e19bdad1ffe24cfa62023bf049ada0bc664303e75465931b2909f14294623ca1218bdc1119b0ef25e08e2ff18444e76ad87a67c522837820e96925d1950fe4197a9d1f60681a1b6cfec386eb6e932ebd86c1e0b7cc58e6040e425837e1566662888296dab59d541f4e59889b7f2f3f6fd02f36aafe951a98e09f9b7b891e195043348ebea709d7c74863f8bbeb81b37103e764824372940f3afe6d4dd40fc304f04eda3b14b560c8e1bce57a5a554af85d980ee8040c09fc2bc1f25241f8cb24c9ce6500f1734939fe66132707dbecd041bcfa7e2bea82da0ab405dd03f2cfb1357c4e181aa18190923cda1b0a28a70040f82816213c86915d0e6b8a9667ebb5e51dc4c1944114a13c1df6cdbb3c11f2140da46436351ebd0126742831e6f178bca4a14237172e8088e103f029fa914d205048b5e06c824aa160abdff272042808dea5dae288722c834a44ad50518186c25799eebc885942d7b3e2eba99012e251f68878aa33217947020eae8405f45ca32c3d5bf3cc53b0521f24cd9eafc4a9fd222eea502adb8cbf0a50e76db5c571426b11b20d1405be0284b9bc804d89f2aab6b44f20c74aee10f379366229b4871e49b948a9c2870ad4e48f046af4858616790d2a2b5f6170f07c007fcee4460582785294a8bf6587872782534455bc11fe19bce23bed4afd4ad201baab03a20fe4030aba2a119987e0f0ea3dc5f5792854f8721b9fbb9c861368165683bbb6dc5c4df28acb49d4e067c92d7839b64fa6cbc1075583637d6dd8932813b592ac6a287439b87a7de600e5b78d214f03ef89cb2955c0595b99be541b060f44e2ce6be0b3f42047183c1adc1eb452a0309f3323563e112793ab72a2bd850faebe9802074aa384a71f53ab97a77625fe80ad3627a507cc8b9993275fe283226370f597232d793a263ccfdfb0bc92739c2995f31ff567456143319ca0f063a38ee4b2284abcc390491f664d95a3c95dcaa5ec3c71068094a002f2d9cf6508fb506656fc8b0d8be7030be56f9fe2d0be383c7eccf3b8f34cdd87e890067d4285957be25486a6da9be42a7065f94adbe3e3611e7d05533879935350c5e00431236ec941110c8591e40ccf5d199272d7c370ba792d5a769e879450998e92184fe428e9d1268df9a0602274c69e19ee3e20e3bc48bc57b121e76f6972398e0e2050960f82af15d6b890b51c6e454ed8c30a23e71b6615ca415a7fef069580a02d2b57d0b4e7956fd06ac34718a501ccc0582194840fbfa0aed7c377da6523bfb270c6af14e995ebf0d83887e4066754e2e95ae0acf13b14847e41d089cf87ae7c5df12e2b09f1c645dd49e56a98187943765f729db63aeee1c667b04c623cd2a6b36c2a048ed720d6960dcd6afb3875b0f0442d672ee2f90a867aa1e835717a95c1c4b9be392749d8ea54a9af020841def154dc872923ee46d6805f1a057823434c7e0f971317c2e1eb9f9034bc1405eebddeb4df47882b6f6b52c463c541e4955291c9676e257a204602332cb45cfe6ee89163087c1fc40c49a662a217ea0b84c23890f4e43103b4d02f1d90dea98e1a5a6a039077736052b0ce229d07042026380587096fb601f931c91c6ea453a54fd7155fc425fc170f9ac7d5e7c6ab3a61e51f209db98491849b5852cad33c947125485c78123338fc598a98ed68597b30719dbe92a25a7fa8121a975444f06a4b9a28a9164cf2010b69eee3d5e6b3f0e9126495a6c93f4a610b85c406c593505b43e758f8f42e98e93faac3e4625e16a073049c713181947c6ecaad8f158a0e6d5564c583e969fa025b013c05345db227daf1a43434794b206afd8c358b1c82a406ce836fd5db0a83c39f81fe3c52e7c85214c5c1bf2886fe8fa24228a63c6696d007fd3bcabf8c3c92a11a1824ca7a24c232a30b055e93ed40f72400f578b84cc9afb8f0f8519116b9925803fc996784fb2101e57d88d8f1bae6e070449c9ce83019c61fc5f9f0edae39afe6262bc70aae78a43d59fc6fd0d76f00bce01534a43d94275fc219bbaee4ba0020642a7e3c729aba48f8414aacdc44c188af577beaf2203531afa88c855fe0c3448988caf37cc0cca080ad3e7929b682e71580e2b8cc84ae2ac7fba4278c6ca80b990361f1e5aea4987d193e4fb2930f191e1000631e04ae1a5e2b4ad3efbcecf844970ee4c03f72f257290a289294a69cf2500ae1476be68517028dc0014cee80e5fd72dfda500832f8843960fd79369070fd9e3436de47ee0a3a25c51847a428ced77851e7effcea3c0c0987375224d6930aa2c4f58adc653d79909ed0922ee80301977e0024f8edeeca3ecd25f4c131245c09cac4fba0742a4f10b22af8a84a9b8713e04f30cfa83def59fcf210478afc400017e1969d08ddc31298a7a09af3412530b28723b8de2fcb12f40d160b5e6388f323c892731a4102eea6a9c613ed6540d5e6d079a10b362f4107e05034e178164c8e4e498573232342fe6b440754d187313f818f98710903f8456fec1eca061c4f68caac609a3197b29b003a5f7741146409850051240c86e41a739c6f850f1f571e69f26c0340fd26076e19cb972fef8c8097adec7d3d254c7b198c82abc730e1f90beaacf03d3c78b98f54633e55639129281af30149fa64b5504ca73375234f3152e5f7b214141088368f268895107ed244dbd2b840b5245843ff583c598ea822b9ed6028b8639428f4cde9f67507d62dc7a0a5f8bd908cdc068b30772e604f401400bfe5031cbfa0c6cb87b9c4fa1a4c6150252b1d1e27d033030234c0f3447af1492eb0ff45bfc887fe623d9951b4be4f499b8b0d43a16bb1f36bb141f1748534640bb98cb9c420e34b4a9040684657ea7bddd013ca7111f1bac858ca303ac4b53c4afb28b08e782b373ad04f75981fa61387acea479ebb52e52ab7e903eb8d384d65261025aec88cc8ab6d30f32b2e3193aaa4e76cc2f0798566dfc74d1ca14d6851780b53de9c8a12560f56020d5e2935c9edb89f50b2170f329b263a729e4f829c579b130c7650963f64e0f589de5e7745c8d6cf595fbd973b71fe78a6d31fd2cb858e2aa3c3e3f0155dd414e1fbf914fc07b19d2bfa4086362905e754e85ee5204b343c224b6a8e8621f033108284d212a1e8414060f42b74b1d90e2a2b5b22d2fa494a3ca1b022f4ca04405d00539442c81f19d22448cb8c8976c08226830193e42f244ab9efc9911713e2d6eb1064f6661410e5373621bec1ee45feb1a48c0b3b90ca07f262f88d4c5fd0e59ac03f81a69663548f9e490606e88426fa093824ca751a00f1263a02f887131cd0ba455afe1083b0ec84af44102f94838c40d7134c411630398386212c1b83e8e90cba15d461eb873180c69535b878163703be031ce72b99a3e57b3859f07ae638e5760e993f13742b4fe160e7b9d22cf11e6f021f9589bbec2a9cef47c986df0833f34036d96b2a03f3b3f4307f6d34229322d3e60bd7994adea88584600f3550a8daab26a8a20611be12830a42090900ddca90c6d7de0e11df646f0c5513b3f77dac8c7a48e8a63f7b800775c4458eaf40e202fd2d9d5e87dd8f976124923fdae8a9ecb9fd892991f2864b609e49a619fee343ad1790418b9ba194f70e221df3a1593c721117b9beccb0833b7a842aa8b7e9c96f69c225d8aa4cce8732100b2ff918f16ada3e0937e9d1e29a60d141c51ca37c8c439f9c54db1d475f1a6829b1c0cf9353e3afa82c703559e01e578d529fc668ec7905cae480e650f130a0e2fc98dc82c78241d40f7a05e8c348169e130068d03641a0de93063ec152913cbcdc1a43426283475f87c54af0510b0f9910a8178ef881eb57383a81325735de8ca0e347ddc0f4500424429d1428be6300f327ccceb8dd9d45a82c14645e098ea11ff50791df2901d12683164e0642847c43159d57c1da5e8a508e13d8acff0519f40a8060421b08d1850e79f48622298bf4488e685f1c6e2df23e548c04255c393a8f5463423f9234fcae3a7628a63903be4b8f1139c15a039fa28614da4bd10c173007eb9378d8e372137aa09f8c487a4d442728cc27c0b3893507d522a6f94ce4bc789222c29f83e5138a2ea2e43a9220bf0a960d4eeab8eb3b1dd083bed8414a284195f3624409097ea1925ecb92497088f02b63216540b87658663fb64842222a4dce20ae7d5aad78781eb13fe898b690137a91a8195905bec993b76c22cf8cdf8bbb9331e130f471c070f0b035d4c98c497fb1ca8dcbb17980ba1d49e06063213ec38ae14fb272c50ba1dd65538ff8a0efab2a285830131895076fde02435a30071be3ff14ea106c3343d557d99343c8c88d19870dec723537393f2bd2945c834d867705298de7fa837e6009a62fa973c9d9c4eca0c8317f1ccb22362723a095ef911020eb2138bea320355e98e3860329c6b98c3dbccf2556e9097cd0f36c20dcf83a910aee51e0b884f64104e84bf89012ba5dc4a0e8a83b605600d882b678f3e89bbc9f9c80840f685e1d279fa4c8a4c7646c7b49cf4b7907dc2c0f53620a45600b853399b3cc7446edb90a374d5097c8ddbf59aefaf682895fc0a8c06bf004e754eebaa0428ac0791f953aa044e0a8393680c479055094dd30bcf221d6cc43b2252ba3b9e1e69c7035408fa02a827e211af17a6210fdaa0c351c6c95d2f3947e0e80dc7f9c0326aa04439ba78228424e963ae57b1ee140d92b12dec19733de2a15a5675185cf07b8436f256a8cdbfac0e2d95645d03e77814f634c8eefa165d63fe2c3046537f8f728d6f93f3f5cd98095baa0914932a780e225f8e30bc59f9270083da2a893278295268b5aea706610b0e77bd4e1e920a57a0b475edea3019d5bda58dcee03a29c269ce56a4836e52300c87a3d6f47b4d58c340f4b811294570115cfa44f1d148f2941cf287e8246798a715453900421e4623307a186ef13a52dcf8231ca3f4079916f302211d4f3e9c42b92f3f2e52203227fb1d9a06e628479db853b345b28e05f30e4a1318e3b4f6a8cac2c36c3c44f5a5196b13c13bd9f06201e4e9d0fcf064192c7baa5e89fe8a0f1af702d780249e2f2010faa5ed41618ad628b0d3db3864401a7a0f8a422155f457c68fa3aabb2a0adde9019cfc5257cd162c7d341f39cc8568f0732b7e675edc9f021698838262b63efc58f2434003c421ed6a555a805378cff48cec85a5ad179900db3f2282179eff6c18a03b02175188711d40982836ff93428a73193e3bd5240e81041df9fe2a32b2799b0662f985465462802fd15251f82535474ca9f100141cdb695cfab500475e427c6e7809a92934cb8f15f7bd050256bc6b8153e84f2993855cfe0cc90ffbaa25d939c92df21abc933aac3e5415830f2258462e4af5134fe8e572dd454281a5e071bd1022dfcc8e111fdc02813dac34198276972ea03b0d14598c91384cc49f50236c749987ce40b543600b81f089a9cb0c411d6efc8bca339b3744546cabc19371f0f8021f5092c587b3f3161a898096a5cc533c4c1e2eede05d25106350ac29fba638492613b39a09a47994e1be7b8e698721dae9a5f01cfcc374860cc6aaa04a28ec282a00c2e541236e31c41d35e10f9520084fd5d6d9f60284660d9c957f61525102be7a384851af24be10a84b0a8942e333c061bb29fa4e8c15bd9f4741e752c3cdd092d2ec442e6197a1dcaa38ca4702d6400fc3395f61c6ace21e4cd3d12bd5e8ff6a7e709002272b935655fab06af3fbe35b2060913826f8de2fc4d9bcab97c84f12475c27c252a7524fbb9e2066c4a2227ff43ee44aee2248ff35981e78b504c3917bb0b3e22851f4f1f9dc9b326a53d1cb442a8816ff47b6268b294b5130fc05c2af740a1d4a76550f44c8a0bbe088148a88f18909f72d425c79e06e41577cc041d7c71f26972399cd30333fea900a12f148b40655263a2be52566deea32c1264b347e7537850e484746d7a3dea413e85e1cfd339e5cc2a6a20df4d1f11fdd0e7f8690c51fab9578ade2e10f337bd8af2481d47504281784f00fdcbb40257f85a1b5bcf225412972543110a4a45ac37f3e6107a19087733ea0b2c018ce7bdbc1a11540549cd3f1105ffb7d6fc4e0ed2be4a8582e5a444dce51a2dd24785a29fe9cb0da734e3550e3be5fc34b3bfcca58d90472b6e47b2f7e2ffe0721dd52b487f6a961c94012742af8b46a627953ce53b82ca8c87d6ac6fea50e0b14c48f93b54cc2cc3cf2cd4e8685136a235ca89e40009d25895c941791834396054eec00b07fa900afd2a3b7c9f0ab0c377205323cb3953f47a8034c8b7ba14f21b62343c13500307e1c3884a99b2e5d7fc24e51c9122b81881037955885b8f40410a9490134eff87cff089f0b9e57453c23e8a84cf66985209e7e0ea918f54b813821961e4b7bc6d79931ba16bc9230b75c2e9c33779337c180d27bc878036e8df51df541437682915c62793a0652977ebb7993d7a090c25d7f1e6004dc065ce6b9ae4042574a6c69382dbd01d7938b8708af6e7031afec88f924ce601145fb02bbc0c2c68df28d61ca193535f32db9b4f42595f7aa09340211d8fa037ce0ac909211f0e10500f9e26ec44658dfb3882e22da5a35e8c18541fc5079687f5e795fbd97be1678e78f9893d083e92983cb9012330ae6e39f2845050f00ab894e43c2ed03c8e74c9993068f4a1f4f07138c4210f23ca91ec778b929b82fb9113948142a19ce123ac900b92e7299002b5d18bcd3928b9134ad2b3fc30797ee80965eb59f27c643b5e267eca883be8a01b943ccb9d166f8080c70f1a3a3dd0578a477b3dff2fe9c8314892f3402d4ddec9408a7b7235c015b5317f439f1da795770a6524c2ec1df049d15153726540483a41130ed65c50033141292506e5b0740caa4ad283dcc2840a9682d4f1db02c0827e21113c9e4041aec18a9767c3101dd2a436deea2c763e1c567883176c9f26d6a9ef1340f967ae2dd7d0e52ce38912e09bbd2ee42c7ac41e8c0c096a64a41f0a8d36d938c7fcbbb83847d336c807cc706515219ee41cb1e0827482c4e546525a7d91f970e667556a20bcb226e74aa90204633969f5343206940227fd763ed86445ef25172206ce6bf9bbf273e2dc640765b2bc0e87a2076ad0c365b56abedac98cfb40a5078d7373fb5b4634f9d3a0112e8310226f7407ed6d25a0e057fa10e5a07249f02d97886c45ec181485844896726c2284eb24f9354333be6eecd9f1423838792335453c52ac4d9e622fc8abe291967f3402e2462d3b47b1fb7fb38a782f34b638001fa87c045485bc6c0a2427e243d2afa895cc8982d871b23e027e7c52c177813a9321f0aae49806b50e96c84c3eb5d2021b7ba37e1c77169c63b0e61b1d79fe1340837200169790430c4ff9479c0e610122549e499de5979902cb9b9c05c83e4878725476395053750bfe4ed128f41294364fe729ce3bafa0f0504d8d36672cf20a67f0b224314a3c0d1b001ecf30f12cc046e425632a38a82e301ec3802939804709be562502f42c1f12c2669c504f87d61958911553f2235f082623c0039e9a2124e5267252608c7e9492bbbf2137e96b0c5a73527279d0220ef22bf5fcbd2a56613ec34700fe9665efddae0c7a265f20f9d8aa20194d01acc7a343084ae7e4c0c3cdcdf9dbe3cf27e141c8671b02e5164c5cf0c80611fa95a54db62120041a6714f00504d19227d4c3d2674c68d9e837267f81d327458a15dae5928dcf1484912f1132e00f0869848a61537e0c4ab13ece9c19ee662f570692b7e3f3c8f8f14474ac725e6d427d16141dbe4d158a3298e5c659fca1f10dd8f6c5ea7ea09934289f7502277702d2f73b98b82826e6961c288d0034cf904c5e352248ee55654e46f4a1cd75c19dfe881b13f4d01e301f666c126a686e8ab33109cc56e6d0fa1510e872af0e39fec7ebe8e370387950330afc8a3f2c9c79c47334942db75565811f68a6f91a69604ee7d07894179c6f0c62f13bbea8429b0ca990fdb0a111be2ae0e0c19088f0a551a1fa3d96dad0b8b63857319504b5a24781d77207e4041284bf06080d77345fbe0a1546e8a909c7a712c6ddeeeffc923a2319ed0c93e3e913f55aac30bf012328af2e6bbc5f8b1caf01c9a437f5c72ae362e3f337f6b0fc020f26de489c2c21188ce4c722b0f7058412a80c220c9df047081deea11158908a1c148fc8c01ab4ce58fc4be42042d500a6f1200aeae4f539c2017d89e499063571b55d4437d1a3905f9ad5ba87bc0e9f47074416456ac1ab7195fc2a2492f891102e50046a2f8f350781735ad2f680c8c0a0c75ec13cc24e2921810c710e3801084a6acbc8625f02fc33409c8c8582535825cd939c7c7296ff467d702c0c2ebc98d2c247601d3894134a3e861b5cf9568d7f581bea729f0859df4ad65bb66b42e8d71d6f1c8808b3a70169c8474003e02f98117a104b503d8367a0233203f19e1e5dae9287bcab51895c66128b8e7da505ed9caccac12086847dae805c0555a7e05b07d0474992862a8a83e91baca9e515ea9a8fc5c12d9481273bbf400b19b4cb2a031fe9158f6c437371beafa78f9ae1c6c30cf9bdac2ece0795f4f14b2c64decb0bbbda8d421f27511155ebd3e4f528d985dab864095537ae4c6703d6a79b3605297029939b2b6a81d959d3269390154988a6c15056cb8e198b86404e6b1293cc6b0c8b9f92c9c5d760d1e273c582b30955947b391ba20eaa8c7a13724ae52214be7e1d53e385007c7050450c788d59937c2e7086c7a8f5c9e76cfd406bd992f595f21ee52b83e27e5180b24ca997816f6bf0e92bc127f43d48691356cab1e21b35f7c0d018d8411d5939f0f57720d6b74120cb253cdae56fa530fd940fa17c518a23fe2a117b331422dc0bfcc25dacf92ceb8e07f7e1a8960be2e2e68f9f16b801438e9cac8222f7742cf47ba494425ff150f52d9005329c3c81eeea3d0405bd1193952c16817dd9e1b92e0d8e1ca82b8317da2025370715fa1a042a6513970eb89624590e2186c3230921902b94c9f0b916ecf2e7a2e66e01096800f81fd2924968af4f8abc4eaa24cea2cd8edbe0a223bbd191d33982c8518d89e056d6a0ca1ec3f8438244fc5c862a996c51afaf1fc92e7e041717744f41a537a506d2d7083a96b29a2401b9c0a32d3903929030d514e4574a68994401a3e73557e44ba602bf559603282101a0be9486a73782d1e969f4e893bdc48bbe50d82b9f9587cd61c13a84fa29224f66c73b2a01adbe0f0007af20cfcf935176f156776db29e520bbe688c19929d2d48106d5d902bd8150aee4a82f656e464401bd819f27c3ebcc83a4a59f907b280bc9f2690bec02834cfb58b8f8fc244f43c1074bf6789a5cc078054deca881ed44595335e44f401ed7b30f65c8e2cc803d0147115c33b0e6ac40e0f0c85ebc100aae1615cbcb996527b1cd00f841cc9438c2c6b505ad04faa443f024d4a3691680dea622f0d866811ea71edf9e3b238bcef690497acaa898987d40b0d6d0be2e34951f1f2165819df40d8a49cc7b6f4e0931f6f478e967fe22940976390deda1d412321b0e434984d7c43a3302e806df89e68357a1575ee32233ea6be8f871dcf8e09448f7032f20456ada1246cb85febc9900c7ff9920da1621e2890dd178b0763bee68894cb02a3ba00154cd01281de385e8914afc54280972409057acb0e228f7bf5e287652dbe93203717b4c3ee634c71fb089b70f9030a69be69541737a56606f5802395f7fa479fc3c28f0f6468c1d7b093868a98f505f542c909ea85cfae6f65f43d51d7ad1ca3519cccc86d091a604edb3f0d5dfa427d3c3d874c790f81c6a75fd502e9474605e09a16a5c90122b072a31d012fa7d2ac6f96a8955914da73367b6d167285f92b8c9ca116dc4c7a15ef88ba29a1fb017d5fee680a33eb215a0ad74858c85fb2c0a06b28cabed60313a80f3ca9f2935a29be92b881f34e014e722d1d64bf640fc9dbb935e7af42c1f93714879f7254832eb25474549458e4313d2f2ea66dd13270ac3c90df59768229734518a0782e2e245e05b3801b59c15e2dccc6bfa1fdf917cd3a5445f0c38bdd51146cb117e2ff361761ab277196428289c221c00a7d90b64068640087ac1b0c419d6e2678b1cf1ff47920cf3f8400f2644e81f830ed9da70b1580870a409775d1c9e48130c4c8b69e043e7d530a85f0c0f8a85695784e5ee65c889d179f0a42f3785f5132192f6b5f2410139f2365441568f20b36dfd42b09d17d3d5806782170f87ca1163a9720b2cc4713a5f28a382c5f59e4387a3447b6b8a45d377e44a535cff288e01bd686cfe58fd3992c1280f28121f82780f4bc994f697ec9cca56fe31eb9112f9e9e05aa3cdf43c6077aa2e8c9d3a069b38328aafc0facc9c398183e8226a9b2998d08f9d3141c1f248e4f365375f6550981870c26c241b0c01448af44cd949b81c3f3ab0f0ba82911f1afc7670611d6cbe5c838fa5f1c3e9f25001334928e204f68cf150a00a6bf772087030d2349933358b5f82c3444ef8426514e2285c71789b1ea4d4c68e50184071c86d3ccd312120a5d5622850210eb12deeab4e2335522946f04811fcc2541412392163dcc2147b000843fcf655418b4145a8ec7f007882a480dbd0d429fd0bd22089e555e9cac6726c5434811cb29d0febc981356d02748d733bdd193997850e437c49ebc924c741c091fbcaca66891d735b0f1cf2194be565b18ca08c58d7c8008a4e0aa249cb2212db172a5528250031224e43d1a0b7ec30a35c8e68a7a06da4f5ea046e9cf30b2857ab82373039ef25ed613ee6df4e9a167b2949f60cd1c6aaa16251ff309963330ae3d1544b69c682995dfa2622753709ff812f9c8b188007ca31528f90bb920fb50cb848065ccbc9e4344614aa24e3e05f80445e2c38fe371a6023b618c3f44201da19db0e934d2533e1423e66f57843904183d0774c845af001a79516b94de5549fa346aa272de23197fe5c6decf1933e7bf3ef472222f27bfc549571e6096e67bc972952bf88637002757ae15ea154aa183a41ca90b857c634b93ccc0c85b96d3302a8b5101912db460856622eaca7bb4d8849e82b4fe2c5586f23978e2a9d4dca175ab80e7e4a6653f6be2fc441d537904a716cea252043401cbc377098564261188b8244195502477933ec68127d9b5c3e24d8d85c89c5238f058949c5fc1d09f2ca6d5a3208bb42d073a34e063f172f043943409a24a63f577d6d4a0b9b8986509a66e7d1969a4e42ff179f12a076e3d2aeb153a02d2966f73ebf55a9262ff2609c8dfc83af32b9e98a09292201d90813094ce30d33fb003849e49f3e299ace1bd95247b7c15142528284b467e6f090b457387d5a7cb24688f2343b21e34736043b6907ca1e921ebca35e8efd0a541438120e081f24a092985527b450b2ea021dc20f954b56abd1aa94deec648c19701dc09d598f26bf6bcf0593a6e7c19121f6e27868017e3ba5e4624356f4d91f8445b86643366239e860043a80d3a6a1ecf78d5ed6ed3e4d14def3fa3aaa647ad74913ad5714ca7897f2bacd9d4193b99ba5dd3b85d5326ac74b531deb051a76ed37d9b70aa8ded6ad4f0f118377789e4ca74e3d4ac75dbb23d4d8c37ecb66c99ab6a18261b030205c89abbb0dec8187632853efc71eb87991f793fd4dce177edd36a9bfa25573fc0dda67aff95ad3ef8f8d83ec2dc89f0330574dbb20da749bdfbd4ade3d44d13f7ebec5b977debb2af5ddbd6f9c2ab59ebb65b5d7b44e951a207ca630e8f1a47184e8d3b9a35dd185ed3659c128d80bb3d2add173cd01d7b76b0eeec9afeb14832e418593ab6b7c3818e473a803c57227c3caeecd15d021e55371d0d9e819eec532db053201377e207b54dcbcad4ddd7ae49f589d4ea51c3817581608082005de21186db2c72ed5b59b976ad3bc24f76c7cc55ab6aebbec11d89adabf7df8e33aaad8b5457236c84d7bf517f509e29f34e13e1e7f683ff9a4e5477bbba38fef0dcfec6f69c9e5af78974de515bf7cd090476f2c1764e106ece00aa9ac2f930bec3a6ce1c311227c67cf17ec6d16918316c0b06e30e7388dd387767e1435885bbbb861d6c967d3db9fb8b06702bdb1570b77803e1d796135bd6f9d7d2e936872327ee38cdc104dc763940b0b19b4c9b9b6d5da47e265375555b4097856b0a72984359d3d8ace02a3aae49fd4c99309bc6abfb743bb4badc8947381d3bdd0fed8a076fa613de79ed95d6d966703fee679beaa6fbaaef8e95291310930cd30853cd8fc423e06e61386daaac1bafef124e97ac2b22ac53ede2d5e5a9d0ad1bebc8897d2b7265b1b87abfec925d73bf544e84e17875f5fce8b8bbfb81c0110a47141caebb69a28bba0e4f76b32edce1c932e3183842e00840ec5b3db9af6556bdb58413e17823d5ddaee9db67b6db55a9f7ebd669e276e6489bae7ec6f14eba7bb9b7b4e5eec4a3881068c6a51ed8c9d480ae0a75c34ee3128a3b71f7a5cdbde51185bb1397dafbfe8ce9e3eb1ed5adbe69125e55ddc7c4fb3c285d99c5a98e57b7d56a9ba64a34c4764dfbda54c943895486b93b5165ddd86df1b6a5d8a9dc44886d9a546ea486e1ad5bb7cdb7b36ac9dd897dedfa7ce3fdc2fb8fdd309375cbd4d86d9749a75eeb548bfcbd1b7eb26bac572ceeedbeb4b9fbd26652963b9bc6b0ab755fb22ec986248524837c810c90c64ac1a442dc8964dac3d89d2ec26e72996cf7db463a8fda8cb8ae7af7a5cddd9736ffd6f74d35d37d57b575931a4ed5fdee655fda74dfe6ebbeaf7ebee9eb36997b8fe8feedb71907eeb6ad8dcdee75bac865dfba5be3534df7abb6bb2fc5db3a56388653b6ad6deb58f574d9d6b1ea35d3ad820d2ded851bb774ba6ccdd94cf772eb58f554b4edb1c55bef5846ac83e33b0a305a72b7514397b9463f46286ea38656574da306d1e9d6158b7b806e7ba3ce0d33376e086d88826283cd06811aaf6a84a9c1d66850233bf1186fd8f86e566dd3a5dec6d63d6abf342e16d535eea895ee987632cfb419d946c65b6fafbd3b8e699865d3e4b34dd3fdc6b0335d066e9dec6bd36572bf2960aaf79b26c2f1eed3f16a346a78d5dab85bd7ce68e3a8baebbbf1984ee6daae75c5e291c613a99bcc3486779a2ecb949a4e13dfdd7cb2eaf673ffda7533d7580f761af77e954cf755ba318d3334f8dc9d08c32eab63a7abdbbe762d938b9d48d85d02d2d03466388adb953983c7aaee3739234d86a7e63326b66e72c33a86dff58eebca802283cf9d28638d3894e180cf1291ec55f28139d21df9f88864afbbfea5cb8d845726df1a51dd80e932e60be0c418a6624071e45675d9f1b81783cc8dc7b4aa07c396ba5f3786f73a06030d8ca53b912c85b1e6ee0ef8014698fb6e1d69cceee10b435b555de60b30fe621998d6a99b99b62fc69c4de1f58de375b867cbcdf6db7cdb33e4c465dfbad42e8c3d247d7fda6d0f853bf1d8a6b2ae8a3d36e59dcabacbbd3077e2712e30ad3b6a75c35bc7deafd24eef90dba6a631bbd5bd5fa51170b7c5e29e5e300f519e9cbc1a44d55dc69b898165c64b225117e33605d44ef7a937738d4990f04b6dd048f5f2d65c787277625c06a781722e6eb818806708ef071e8aa453dd6ffb3a91c7b2cd22d7b45db75fcadc4c63fb3a91469d6ab77db1613655dbb4e97c9d6fee2ee7c3bb407756eef2ee84768e9cf8bac7639976590eafdaeefdce1d9771ba5523a0bb9d3956d68e8b5dd3897b7777793b894c5fd431aa0bacd3d0a2d1b0c59916802dfa5a3060e187451f8ba19b0f5738dace371ed3cdbdfdf48edbf44d9d7d819b6c2c4dea32385daaad53a9db7d64d9d478d43bddaceb9a1189c464bc75195c06bffbd9a64f6473bfcddd3add67d9eaa753d6ddd3c4175946b6ff56363536f7f63b8e69984dd7b8dd5add71ba8cb571b3c854eb778ddb3d5cd165c58de10a10ee721a03f198c6ecdfaad32712f94c01755f1bd3a5a2fbe3c463ecbaef51c36bdf7ee996740e5404a0e28b0a212a94ee44b2f6752e935ae9ae802ecbb66becb6acde31c6b40610673a76226338ddac8e53ad5cffd614d05daafb45deeb97745becfd2adbadc37d95705fca2a53b8ca4be188a89beb12e6e0c314605cbb144b7855ed52387027c271ba265d188639c4ab76615db6ff5676c3fab959cce59953e740c89d9283e34498e92637324edd8847f8b9fdf4e4c8e2300de316c5a189db83db0587062e106ece89ea0e5c951a5ef5916c0c5657e373fb158bf073b32816f78e5a2bdda49b26eae7f6233b1bbb3b6ab367bc06c2690c1888313c46f6a66dba9963f07e5535d53087383abdae6e7223771cb85b768d5ba78e935ba9dc2f3cf6dc7ecf36cd63d33d954e5d5d65bb1affb8178f6f60dcdf80345340b7e11b9ebbbbd6addbdb98bbbbdb5b7be76629a9e114d0cd2d70bfadabe1d56c8d592753bbb939d828fef4bdcea6fb6e1d66ed1e0a28481478eeed1ea208e0c41dafb059ebb61a4e975b5d93ab53ef84a8136098e9f00410d7cedddf7c7822c3b0ed943b5109e3e8eafdba702c9b265739629c4cdf38706eab6ed2ef7d6e166dcfcde2b9fddade611b1ff1f9eebfad9f480d471cb6a9b93b71d836c0449da189a6bb85e46e9aa5c9d6f9ead86d61e2117655d505dc6cbcd8e0b0b1ee4edce9eb4c26e1e7f6433a61d8f9ea1d4377e85094dbbbb3ebd08198b26cca46a044a11239862546144715a13851dd802ecba630cbba11b753ddcb24acddf8758ff1936d3653fd6455fd4c99cea43e6ed5858bb75e7b77d43087e68e23aa70ba6c9dedd62c5be697dc9ba8923ddfb45d91637af7a5cc75bf4dbca20aa51b6fbd6151bb3b91b9e395655716ee4ee64312a6e295b9fdde94b95f6dd60e497069a67ba9610efac922991b86dd903bc6ee4b9b5ae9ea916023e16b6ad6f4d6ea46cc8a5d378f658dc70d3b3593e926f71ba7cc56277713669bcc34703fe3ad4ce1d8e92653b6992ee1b498c28ece2ecf2d06f1dd71caaa7b659b4d277c3c3a63a7dbde56dde486d365b36dafe9c672f04eb66e9bc6695c1aa72afc2a775efacaed64ebda158b7b6d719baaed4ec25b6db6c1719bae6f0a83e9b48bdb55852e8bdb5545dcae4ba35f3245b26caa642603dbd8921bc6fe5bfb8cf18a634560ca346ebfe41ac76d0ab855170ef94ceb0253a6daae2cd476f5331a0353a671b34ed565be2e8e9499e48b6195ceceef7501e3ddb691e932b43435114e777d77dcd2c96d16e974e35c654be72adbd518c32ed318a7466632f0ddafdbae4ca3b15d99739b99b66cbac6acb38e4d2dc41f1b33c9572ca6fda95bed84bbcab6a9ac9ba6cb9d48166f78b7ae56ae637bc5e25e1a1e7722cc3ae1b440a2dad4700ab8977b69786e69617eae1beb1876e2d064c5e2980d3835baaa13a6a1ddd04cb91355279a1f27eabd8cb79ebb040c74df58364db6edb129ef5a00ee168d0e6dac4307e26e5dd5ac7512ff5c66dcee3665be1befec0c095175ea01b26c9a4cf3e159da1906108edc8946239bea810874f763ecba79c73d04910451c089cfed07b85bb223e06e8f635aa5738b9fdb4fbbaa13d671bb97b1d36d09a77131bcfb563576369770aa42c5bbd5742fdb02b7dada404bb301026d4874d5d7aecfb4d9668c6137b9845315cc249fba1a9949bed77ddf2d22049a8a259c2ee1bd95ea6a5c73b26c9abc2b1637d34dc64ed70c4b9a37349b61162c83e3c423992e53e974d9d8f12f5d3ad51247964de18e3addc8d8ddbd0ccd8328772233b9c629d38d9d699b2efb5675b937f400c543b18329d609c7bb4f757670d3818ff86459a712867732a96e385dea0eee44586d37594ca606b85bfd0ec73e8d61216b5f1739a6e3d56dddc6da3898727722ec26351cafadbbb6ab1b315eb5bb0484d3a5051f72b8c3741b7218c281b8c1921363a7abd5cd5ce3f8757519db74aa3a8d575df6eebfdd32d374b8a1cc06bc65bca67f2e53832db22333c9a7b65befa877ac74c75a78efb9a1064b4e8cdb0dbb6920e34e4c0135c01a2438d16de155efd7b8fdd2f8e897c6f0aad53675916153862319d43088c21005031b11e9be4d0dafedfe7b77dca6edbbffdc3ecda6ce36add3c4bd8c61a7fbeebe3655c9f4d38df5ebae6f534df79bea76d563ad93f86461a7bbfeedb7f944ea0b8d2e04fa1e5e9031bc10e6441876c24d37869d39f6bb630c2d64b260c40288a6d38dcb74d9cb74d5fdb7eee47edfedcc21a7796934aefbaa1a4e0153ada64bb7a16eba13db3499b24dbda15edbc96105576b0530cea58e57bd93ed0a9ff9b0c2b102bc47018cc3aa2e1b525812fd9870bae4e0430a6b4e7c77ccaeca099e62a76b369c506842d3dd95ae7ea6801a5ecbe01450ddefba379ca089ea8ef7db6491c73d09b69cc8c5dd7809af80fa49c6b2e9986e01dda1043e0901dcdfad543a91c9b4096ba5abfd9229ab530d37d3e5bbf188edaac9fada3576baafab6a37266ee23aa6b71386570da7807b11e0b83b3165935bd56ebc4cb6a9d97ee12204c21cf6223870f74bbaac66d93586f57ed358dd3b9974c27b102cbdfbaf5d8f47073e840007c29148a6becdc0bd12fb887b6c08618db8dfe1034fafab6cd3bde103241eefcd7cdd78d55b5dd3e6bbf7e0e84e7cc08098c6e9f64bbaac5b1b33c9d7d6c6b2295cb1d8a6e154efb1b131937c6c81ee5b9b4a8796e66c6b231b636326f96278ab6d6dbbf9dc630b74dfdae040f78d01df8250c3056d860bbc2c68e144f8a8cd885bd57dedba911a5e95eeda3add58c1588248eecec28709900c13d4481060281689879886a1d870a867981c0ef37a85b1198601091b1b227885208cbbd7f910c11e023d3676c6473686c6076badb526c26e93b96a7865ae659b45a62b2099934d06ee314d7684e154bbf17ef54eb67b39a689f00135076a10e178d56eacd5367dd7bd8cddd80f761b1ad03534c0c6c08ba10116056c1510e4c4ada64cb7f54b63cd7432d7c0148637abe175d5ed7a3cba76c427cba66bfca6cdd7d54f76c79b6d32dde4b10f661ef71250f7b56abbead68d58a6cb525d07c747800a01b601a2063c19c02660d55040230181eeaeb51bab6dfaba65ecbb06d0132099c28701246cbf3466d9b4ac753ae1bd95c40e1d886aba8f46a3ba8f4575c76972bfcddda1c3106cd0100c89bb138fbaccf9c6475d3cc26e9cc6b0ee8b37dc3af5ebea78eb0dc1845f9d865f48dcdd830fbf6c7c057027aa7b23f55e2e013f3c6378c780f00b4ddcef064ca6819b65d3e46edd67ebd6baaf53fd7363e56661679adc48dd6ca66f0c33d794851b0a9ffc50881c0a0bb83b51d56edcd7aeac33556e95990c3c6a33225973a7c9346e7772bf3a865de6baf546d6c1f1ed653cb69529ac531d6fbd61d0390c5e1806030c515043b4c9100d1ca27a98150d3312e68e8968692a9ddb46bebbdd7d5ae9c26eba68467463dd74eab2639cdc7f5b4d991a5ef5de734c171ee6b2e16f1abe99e15b197e9c3b71bf2bbc557823d73709239db07eb2806eac8fda8cd8ae4cd54d3f91ba4de13a383efd6cd537eddbaf869dfaf9eebed665b5dbbe29eb4cae695eaf1cbd22f01ac40be6e56197a6b05d6a76eded6ae0ee771ea6cb90ae66982e0eb936e58a92cb442e0a61b83ab93b9e87e17a826b04aeb05b7e6ed9b995de12606b8ebbabf0305b7eb61884d5d2e4ee2e3cacd6925aca5a4377e7f3305a78682569bda99a65256cd68e5963b22485c9cae2ee303c4c169ccb228b25c99db8e174997c9dcd3a38beb2679b6ae5aacbb4bac266664418deedca7c77bc55edc26c1ac329abc27b2bf7deed9d2e9a118fc73a383e98ecc9225718769b5a6dd3b1add47a2fe38d2c9a11dde47663ad530dc3ee9b4c35191991a8cbc888c431d809b71b6e5d383645ba6c8a315d86a68bf0987e5d5da6b61bb64146248ee9321a47bd91b166d93586262ee3c0ad8b66c43a383eddd7aa6daa749b69b24e178f476d467cf7327e9d4addeebfadfaadac4eaeb11bb8e3cd74e1b223998d1e5a9791918da93b6e9d2e1ccbba8da9ec1a4317e11d71342c24f7df1833c977d4ba08a71ad670ea849d9ad8dc31acd9b45d953ad570daba7ab7eebab66b1ab76ef2ddf166a66fda34b62ed38d616772b7aeaae155c3eb724f13f7327e265bf78dd325a07e26f55eee118fc77719c3fbdd30d10f5e5f1da72dcbae803ad6656d0aafafcbd44a3a4d3c3ed3a67663a2d28de195d5edfe5bfbd4bd2ad7e46ec27a2ffb9c6ebbf57e9db0de7e691df1a8aec755abfbddfae944ba31bcea63ebae724fe7db5197c14d77a99f6fbaaa9b09a79a6537a086535dd6de7dd07b48a70a1d6f1876d3f0ba6357379f7bba2d73d373d3fb7399da8d8f65646379ccbd8cb556db14e600af80e9ab2ee315506b3766dbd7898c231e8f5aab1b4e9166f10abbcdadc6edcad46eacebe0f8f4aa5c63f54d015f572bdd64aadbbe75bffbcf85b51b178f6c9a745b148bbbf9dc3b6a335dd4afabdc48edc6fa787c37cba6709a788ce1ad1eb51beb8d7c5dadf566a6f0f14884f7566ab54d75bc2657b619b8dd98e9be4a0debbd35b43367dc3a5d66da0607a675ba8c6c0dedcca9cbdaadd2c1a95b6d9dee1bfc6ea6136edde4bbfd922efbee16d0cd53374cb6e6d4656bce31b696755de4fe5be3646a84d777abaddb84f7dbf6eef5651a37f299325ba83b76ba9bb9c64a6ebc6a056be9eeb0fb6e1d9b55d3cd6a37d670bc22f5aa543a95eb128e93674411c43433b367bcbe69b38ded950c7757d93bb57583e3ddf7a67b71bbdfd709f784dfbd2ad7d8b5db7a7bf7a5b15edf5a07e3410057585cf5703526808fbb3f0935345d9e34b5bad48a88003c6abbe1353557ad32b03214204dab0923ced044b1b2c12a40802bccf4ed73dfa691048933344babd0aceab16a0c2cc42a2fabba04eeb875939b990c34ba46554d43a829520565c219da1f5520242970f75745b693ebdb443b5ba3932a4e250101883534a888800949704195c1dd3578d8a959a7c07cc972f7470581990c04b106e5d45898a955a6b0e83975e1d402786fe56e3e636632300d6d0411536361a684a5e07cedc5afdbdcec8e41a08928a50696622b3524b56783d15547846049b1858d7a348a8750082934529ad40352426632d0b86397b95f7535c27b2b77f3191bdbbd8c8d6a9b1a77f3191b8d20ced48ea0c2440949a4ed896222ca86a800c20beefe61a274b8bb91b9a66c52ed2c4d05965072eefec2c340b90aee72f70f0ba524148fbf507ea1b6bb91b9e3d5b8b7f2f9a66b7255ee76235d38de309c1adfad1c8176c60605540c4abfebee0f2a490c6f556d373c424418a800eefe619f7c7c623dcc932067e00044d8a71aeeeec0c33ead857912b5e6ee1ff609cf18983277ec4ceec06d642603d1ced28acc64a0d9076632f0838edbfdb73ded7177203edcbd070f341871bbd764eb7606236e37f399368d1b4e37bbf33a5d7277ba4e48c23a159d18c3bbafcf0933f56e3ee3742fe3b2bd8cdbcd74f6bda9decb78ab7ba9e174a935e0b6d303dc76619dc0dc95ae663adbad8f47bd977b30bc614ecb35b433e7a64e3b6ebf6b5cdc6e660c3b71a86e705fcacea56f1c28c7b26e4978addb7b0edecd675c2cc248b875e174e735376cc67455249bea36a9b9bafbda74996cba7a3bdb356edde4fac6dae9ca8812a66993f7a5ac52efa91b766aa72bc379bc4be30d4d0edc1d8d088278f627f77697b7a7777c7f340fbfc48d74df180431cdac5dd9773303cdc23251815fe2da4c8d3b4effd6b76934a211d58820ccfa5c36138afd3ae3742bf5f1cfefb8d7d7ecb9fd9cee1ebbfba7c214e8ee16f6ec5edcc119e3ec5edce519e78c2cdce0e0e458a8add37db51b0833bf3499dc7e011e089020400600710702c433104781781220be6489fb0017e0eeee4a80b8002040820001b204883b10254084bcfb844b63208820cce2dd87c2c32c79b260018677cb74d5367db586f7bb3a73ecf719af6cba61a77663ad6eb7bdb637867559728dd318e6c006e8c26d66dac6c9b4c974e18ac5d65de177ffa1a539e1678ab7b7d2083fd3661b0ca76cd2f8ee6660ca7c0353661b5b60cadcc60da7c836b43427dbbbf10253a6dbee312b59707757d9d26d357dfb9a2ff262386db1c66b4ba76365d3769d4bae70bbbe71a0dc936d36dd369c2ef15c654b17b7fbefdd78305c2cb6783a93f668e5ba9b815bc3abdecb3d5de2687c5de351dd4b786d57a57eb7caaece37f825c2f1ca74ff3d4017ce00a4490afe4848e2e11ec3bb35be3b5e8dc9dd843f24f79fa4a3a43bb5758645022052185d3c1e759c6aadee40104b385d46a2e1eef1eb2e61edba7171c3effedbaf6ee9e097b884d36572ab2e9cda3addb9d8b553375decdac146c0bdfe158b90b01021391ddef375e3546dd33a367d77f38e4d99af6b07c3290b073f533c362dbe4436fd50c3c31c292212891ce2cd96c5ae9d26c29a4dfbda15d065954576ff6dd65874ede02dc3ac58dccc356e9d2ffc123f84390ae0ee823cac519cbbeb62992e0a1020408080014475bf6edbb7c271bb5bb7c97493cd149ac8a6ce3899c629738de1fd4c991a4f13f7db7c779ceeb74fc3abd63a865d3ca5ebbecdcd5c63a372bf49f70079f1ca5c5bf825d3584f65f72a03f0dd7f77ea6e5fc44de7fbba70736afaf6baef2b87a7eebf77c33b6fef772e09afcc62116eb79f1b178b70b118bf6eb3e98461679cc67a3bd9a66cfa66634da59b03dc2b8cc055a974c236449ce9126b4e1a81ab72bfcd188be02c727048d7213a87742ba76e1699c26b5cebae70bbe3c06d4ce3544de3cd346e66fa2ab7ead619620915a2434857a12184541f74e69fe464f01ab87a3d95374b6fc2502f7f4b2872bc87cff3402290634422e1277080711b28509de813f70f26d87a0fa2c0bc2353684fe60a1ed48dd89aefb085283fd1e474516a61d9d09bd2e72af17825718a1c4d0f593f37c7cb0dd991a1bdf41cbdc99511e80c34c6675803e907a9117d2ff221fce107282bd0748786b243ea57a95191cf9aa37e0f006cd0af01abcc0a14949cc6a9cbb92089f26285383ddb960032991310fe6990870fc0ae800b1f80f95064f2bc58012f689549706e36ebcfcd04a0e0093c18cb194e317923aacc77c2f67c970b6c9ccd542007a42af0f64bf7369cf754ae4f35cd8bdbad37e443102dee6e743e8d6a0b84eca9a63b0fcf2fe9b620c22c9bc26922ac74351350c3aff38984e9f0dc88fbddcbed7c63edc67a471a75707c9a48c6b269bc6ae5aacfde753bc98e47fd2722049a7e6e169a08bfa926c24e17d06dfd5aa8ef8e5bb434e7511301ddd64fbbf1068489af6be3c84cf2e5bdee4eb6e956e10d3b59367dae7f6a4a87e7a699691d1cdf9d26aa4f7619bbc8310dd7c1f1c51be6a059d64d1f5fb7eca8eef645b178dc03dce37d6fba8413c0470c104b99003c1b00f7dcfd8a18c030f7d8696cd36480347efa68e1a3678fd81f91bd95da8df50b054cfecadc75d1e90a81173d4e2c75707c6013f20c91c5044fbef2b5979d34118bd1c07b2bc14288c1aedc5d0c76e4626e0a1773db5ccc25ba983b26e0abc25780353623d6885013228d1a9ab4336866cef8dc739977ab8899c9c0d7d5e1ee0132993cc6ddbf101c6f18966ba6efdedbc531937cef869def6e3a5b28deaa65ad7617c11425c6e4a28bafab341a8bafab64eead365340bd15622e64b6fc200c736c4f0033e8ba9877979727a72d58b0808200831af8e28505e37d3053ef4c17f6987bd46209f8a9725d8459f9d171f7c445ccafe9f7ba804e18e984e156ced1161ca298abdbd46efbb6af3376955f7db4e040e3eecf378efb1a94050b8e16ce4c06ea77b7edaa2f88e108e0444a57d63f1ec68ad81ab8dccab45585fb1eb15582bb6b87ca115ba6cb2488d7d5ea8eab524745583665a94296946501679d4a345514b13e45fe098a581f1567d934890a7a1214ebdb739febd3ee300ead7e6de1a5d4e4baf8ad9c4865d3dd37396922a674f0e6574c966c474c942409eed8dbf5dd0344b2c7dc3d3b5145a9705ce5e12ea68e89a90674516d573f5da68bf04b3c9264a2ee04468a8825d7dcfddd33912141a42a779753258684826cf680e1140f2d4ddd6d5fb16807bb1d15ac9e851815b9fbeb6d6564cf94dd79c5a291990c2c16e162910ddeba8c2d5d61c0baac0d2d4da5638b37bc5f3c986e8c2d5d61148b46f71c04c80f77311bb1ee6be1dc86580d53647d4ea4bae33d07bb35f6d4c0e22e5663cfddc29199e42b16b7ea64f7f1c84cf22d6b48388ad1884543902e96c910a3c1c3ddcbcceac4664872f8159bf1466c861866922f18434c86ab19cf197cae8b3a4ec9b6f3b9fdc6c464f8d171aafd8c71e06e65a8c16232823b6e37b3e9b2fb6fabf16e6378c74654c781007e2f5b222d3c221828e0c5071188034b56eee2e860946208811f5f6b4f6cc005037525986240d23822a296870b2c3681c9c1a3bf1604d022a46a2d28f952840d042c308c75ca2864127238a48703a4a4a6365c3a471a8db72e10a0a10f0fa20cc3ccde6820abd3e90d0d6586299f20bd3881414230d0461d1e47719457452b432f8394b0629ada3c25589d5dc941c252afba2029c06ccd78a5f7e17881c78f0b85e2bc3a9ad260e441295ba21ca41a93624d03f6209784c0321e637c7aa44ac242112f302a23c0281c303269af0cdf0718c611775a917adb82c76948a40c9823ad05955204dbf4c8b1791921f10120286900015ea91c849961eaed0d9b9763a0974077794488769e0b32d9e8c3c3551958737700592c024ce73068a547c09683b6b337481ccd68c60dd08333e323c1aed39ea73492a0e481b974c55301e39703625ad51399e01e5e5336314186392383945674e102db8f488b3eec01a4e471e78e1608034404b313345df804f72658b0d007e202af9d455580a111aa362231c6a0e1a2296dce0812521188a03a14af35f41a00a0a8c0018b4a0a351be470812276688a88f4489e2e0d3ec2f03ab421091f61b020f8c6490f31cc32a588c0d0126a518b227c45ca8450fa9226c80782825442a45d0b0a4d092129038c35f06bec5b952b9c0c341ab73285d5d6451a5d65abaec6dc7cff9c7113cef1e668baadbbffe312d323c34a2946195f676b53f94546ec6bdd1750c7ed06d4d34418e98497c9367ddfbed024402b04675960ba67162cc6b34d5537869d2bdcb39f7bc6b1bae7a5c711cb6393ee1770db91c96b6ea41fb13c07c4ed7c4ae9cf996157e7327e63572399ce37093b9b9a4927987d539dbe2edd565dbbb7b8bdbe7b5a8de16211c974be7087d835551e6327f3b84cc65bb9ff386cc02362786a2e245462777bc4ec5ce53ef7ac14b353f376cfea18b13a3cbbb908ee9b55dbddae6cbb7e9aeb780602fe3963f8cdd1745ba61f8e75d93743065f0c182ff6f4f25ce0d9d5b560b142458a39b938b837b70f44b3b25e1e3a908d71d8a02103860b167af5ea15a0d7d7106361ea871fd68b178bb8659ce2319df05a08582f99bead85d870faa6a66f03ceacc82046c7e3ed2055c8f034556ddd377855e9c452807287d1e0e766d1ba2dfcdc7ecfede79ed300622984b8fbd6677a2b53381fddfd8deefe29c65c17d3e6f434d9bd34fabd2ea07663edc6da3ddfc8365c590359a3468d0b35be0c04c0100084985c0c07a104b588c5794a3b2382209e99b1627171f04b8c9d4ca49a845766125e991fe0d0c08d89bd0d719675baff8ea57b7ee19ef7f4f2dcf3844ccc2e3cb9673c663230df65bb170bd678b3acf36dbf2edc9bba1a994eb9186ed24dba796abb6378c7489929ec64f16ea4dfebce3d9f6f752d9e918895459b2ef7f6ebd6b94df7c58be174b788dd986ebb55a6dbbe73f0bb91cc5db793ab0b23afa05331e7968c5d3bf79c62ceb57bdfd477e3b9766cbae35cbb40399575ba3196c93865c26bdd66a676309bcebd5f3350ce75f7e0bde7fadad52e30ad73ba6ffb227edda68e552f30ad83e554d655b15964cab2ceb9376e5fa8bb7d91cd101d90631097e1127da1fcf691dc509c5057386692cf3db799602be1ee45f74c22afe51159c39dbb1d734d59b82cc24ebbaa88b71ebcf7dc33c50bd146adcc5a86239606452c2d0642a9558a4a780007e572f78c5c4db8af424d41e5eea88c66de9a490a7ac572f7201a3169f012e83134c2ddff84141a77509274d9e3eebf245505b257c8b6c6dd730bb913a396705852cbddd1054f1cb1ba82650f2477ffd252c5d64b2af242e1ee8f288b212b494e9714b97b16352b0a0f5d57428d7077e19623589665221c9ddc3d6f03885e3196f85442a20daad124c7a6d7cc2c4c98471eff59e251b8bb87a38ba151593b2382308b5df715be12432bb0e33598a54d37d3a41170b741466efe7962662372b1582cb62e8cc3b8d577c319dc1dcc3d868b815805bfc4f4dd1ff2874ccc66f19b365f278be79ecb3c2ce19e6c7b97c6a9fadc2c58f7dc814ddf649bc6f073fb81407b6e16996c551311b8d5d60f2d6dccdd0378e69037640d6e6244179e33640ccfd709a7fbdd6bdd75dd716cba2657d5b962ffad71cac26bdf6eb14cee3db9c0addad97e6b1b2a5edb17696774172cb87b30eb0aee1ecc13d8f7954bba59c21ada99138cce0033310fa67208310f78390284070e1a64069c7ed382ace03125101b8621c8079e8481029980518cec455fabe9c4c6ca78897655d33778f7a578f0bbd7a7728de124df117f2237c4361470171b730e96dc5b772d8a71e0db3a563d3c95ce4da55b22b681c8861af07e1f621a72e8d9ddd1edc1d9bd5861e473ab735b6d74cf03dc3dc087107f1f922ffc6a8c0f5f75f1e12b2c5f7f22832577955d63b8ffa9b55d953bcefd4d9572f757deb1ac9bfb9372434b7ba6efeb26e26cd49a28b567fa860adc6a4bd7be7095a03e7972ff4e9c8c3b4edf4deeafe933b93f26f7bff4b7f6b9bfa54a94dc7f9224f78f94beee0fc9fd1fc1c1d6b573ff46eecfc8a95457f75fa4282349bab97fa278bfc9d685737f442becfe870cb9fbbbca96cefd0b111ae4fe820278bf40ee1e60335d3df70764e72a5bbad76dd7cd4cdbffe3c7fdfbc44a57ef4d55c070dad2bd3b4ea66fba84e3c31a5ff77db71b1b9b4ea3fbef69fb5636dd3a56f8f5e4717f3caeb2a583ddffcedb71ff3aee0f00cb3aedc426c070b2b131b0e64166d465f0d8061ebaccf8441a97c937ddb091180465c2847e9dc9e011a10831097beeeeb6c20262120a0479894588e4ee71bbf5e2dcc4204031aaed86c5200c95ecd6232ee315308eee848ea189afee6bd798dd2fd3d59b4d35318e371cafcb954ddfcddccbe65e99da7d9bda060cc06d77a4c174f58e1a5e59785ddf38deb08de391065edd0bad2a93add36d01db15769380bb0ddc36e4e2eee08a4523cb6ec062b109d3b0416303ea326de6f6c1fb6fed73d3441b473fd755e1c6b0faba80ea865b68e2aacd9c3936dcbac978c3c7a331dec723bc91b19b26a6ab2e2b7b77ba99ec4e89eafabefbcfd5f01ae72a372060bab5d2d5e96693c4f74d931a56f75faaa62e8b749ddb9943d544386ed734b99bf0869b7bfbb93f9d39bd8e47651aa7eaaaa69b07013ede8a116033132350c1bd697477132e36008fbb85136dfab7d99da78fb033de7d47bdbfa46ef797e3fe6b44dd6edde7b29af86e75bf6a9bc2f186ebd435c5db7b4e000222ecc6cd75659bcc74031e7593e944ead4d5d34478095882cc832ef1ee956dee39dd417bd07b2b8d63da846eaeb131de6c09b2e61a8f25d9346934a175bcd90f1f74590c3bdfe3f1a889485d46d65c63edb663daecc9be29bb3257e76b16b769f2c8a6cc176eddb5b9c63a4d91c7bfe3335e5f777d9b47d8752f34d7f85dff2ee81d3b9b4cf7787cf7ba95da4d8b09f1dc77ebf68909337851dd4f1d698be02c4b64cc241f9c8edd764cbb6f5c104db1e9dc255634fba0188a462c8841175dd469375677da5fb108bf4431948818caa7e13e98a9cbd63a50626816cb74b429b1acc0ddcdc4fe95d81f2ad345751301dd56976d55ec577ff76ab684d3a59998cf71d76d26986bb11808027edd393fd77d5bc2a90a186e9d73ea66e177b78077cfcd0276e19774d93775bf2e9cb678dd3899ceed3df77cf7dfcaee38f8b959c481db0e6ef79f5c2f43537afde8c5e7baf8a2f5e2d00b2dd3c5325d74228b45f8451aa7717e49f7c9ebe8ee696ef22a83d35db360a6b3d5264c309dad71d71d5ddcb5a6e3f46dea12ea7e775fdffac64ae556758a9ad065cbe3e474ed41eec1e9e2e1eaa12174e95ca1ecf4dc9cc562722d169bb0badb17ef2e1699c940f825bedb8c990cc4a5cb75115677e03a87ebcdb840a45b75e5fee1965dbb6a5d6c1118338b45d856135b0e6c052003a6969a612d28ba08ef358b052ab478b97be0364b5f676a41b3e986759ae774d36d6a54d7b4d9be80d395754bae4d37b936ddbd78eb6dd5ad83f79e8bb7de6691e98e5d3bbfa4dbe275d9147ef7dfdabe789ded8b66e0ea166f3d155b0f76b2cdd7a9a7b64ee5ce735504cae5c5a59b8563e1e2e64e26ddf2daddbc03dc764de6ab62c7b16c0aa7f6c74cf2a9c6cd229dae5a2cc6e2134b0e3319c84c061afbdab52f8d61a359aca0bba3716079c1d1aecc116969672bfc849770aa2270cf2fb997f1ebd66dd54d3eb79f99591c8b38156ddf0ac7b18853e1c6f17e6378cd7b5fb75d26f5f6e276c34837f78fe3eeef26a7ca9ce7b2491d18375d37868001f0ca26f59c8001708a44b64acda6ee9f23c3007491a9fea6bb7f1e8e87d0e01507f7efe0f6b7fc83990ca5a2f3b26baa9433356405e2371f83fd369fc87c60b82a4671992c3ed3dd83feecfbe568ba690f3f65a0bbcbf0a1aab33870178bc626bcd737865f621cb85321711daf8fc3fd5707dfe7feca571fe9fe69d8d1fd8d68dcffc68b19a2ba936c370c6f63c6427c5d6599baff5c669bbe3de37755efb45b9e3bc58408afe91b576b80bf8247a0cb94228ff787a451dd4f3786050c685dddbae50d4b79c28174370e4bc5b92f936972880b09a2c4dd591f92c202bfc477388ac890946e77f3ceb51b6a22e4434d787ca8298d0f3579f1a1a4593ed424c5879a8cf850930e1f6a6a7da8e986bb83857d15f8ba84cb879736f9f0520097def8f01297a125a50f2dc1f0a1a5371f5a42f3a1a5073eb454c087955af9b092241f56ea53e98d0f2b71f16125243eacc4c38795581f567ae1c34a716161062234081b4e08a374c98794f6f890921b1f52f2e2434a50084c5ae5c349a07c3809910f27ddf1e124393e9c24c68793b2f870d2141f4e42e2ee391f2060e0d09a0f0f69f0e1a1063e3c44e090fbd0d0291f1ad2e44343887c68c88e0f0d89f1a1212afed5c0900e1f1a8a7d68a8cf8786f07c68286e5820010442507c48e8870f09a93e24c4e74342743e2424c287841ef870502f1f0e72e5433e9b7cc887910ff9c0f1211f2b3ee413c5877c8af890cf0e1ff259fa904f0d1ff2c9f3211f361ff2f1e0433e137cc847cc877c02f8704f2e1fee61e5c33d9e7cb827930ff704e0c33d717cb8278d0ff778f1e19e29eeee808230c18021010307c4083868c060c1d7707f1aee3fc3fd65b83f5f8b329e22050a982ed1d49324875fa21b379f6fd2bdcbd0d371ff89209e9925f7dfb35de3bf4ebae097d8898bbb4e6ed54deef58d97f1deeaeb02c2708a97becb8f31e561bc70f7773fd316aedd9a327ec174df39f7dfdbab9eba63d72e76ddbcd65dd52dc7a6ee9f975ce3cd5cc6ebebe6fe2efcdc3df7c7835dbb3b770fa66fee6fb7ebdcbfcedd832d822c3635dd7d93dea63877bd69eca8a9972b7787ddf6756ad2a46991bb1f8f7a65aa7bb3ae9dcaba2a005d76bf3dd9f6ae7d01abad738e6553a69eebeebd0d3361196aaab0c782d0d010932b13bfc2dd1fcf2bfa47efc8dd6df810d357ecdac1c5b0abc22d76ed9e6feac683fbdab5d94cd977ffdd89bab4e6452e05708fdd56afc9dd6e3d87a53f778793305debbe01ba2d5c778fe9be7331bcb670f7e862d70e70cfb9ee9e084a9dccde5c770f4e55d3b70c9440b87b93990cfc007fa873184ee6ee30d40daf71fb6f855f6772e7e1bd78b2ab8ae7daedbe14cfb5cb8b5dbb1893c6ccd49d7cddb9bdb9f685bafd926e0b75abed9a84771e9d9af756a7ee259cb27a31d4386dee3c35de31ecb68861679cba015db5959364e61e6997bb1323994997fa4fa770747b2c9bc2451a1269873bfcb755621c9ddecb3da2565977f9a6effe73894a1d98d6115b57c71676ebfaa55ac96ed88dd3c5ddaeac66ba71ecbe69e2aa5f67bcfb9e291356ba709ad473e3f4ebe2683a75bb9b6eb1057cea38d5effedb80ae0a4d54b7d17db70ee37e9dc90db7eb51b769b2cc7db70edde1c9b2e97e3b28e9b485bb3cb919dace4dc59ece93db7393cb4ba1e5def49ede8b3a4d6c77d3158ad1bd0aa1844fc1c475acd5b4510dd7c5776d04c275117ed32618234cee0ea7fad863c7516fb5c78eec8c94309dad26a6311c3b5dad74fdf65b86d47bb66e9aa8894fb6758f3a3dba71dcbac9bed665d3e351c34aa5bbd5f4ddeb9b261e8f4e57863e1e8fba87d6699cc6e9b2b758d4656ccc75a9d74cebb409adb5dea1b576dbe3f1a83551e91e3f9c85208176f681659dcd6572c74c3749443a97f1daf7e143116ec6f1f2a8f7d6d344a7fb972a75bb6aa5ebda69221cafad736f7888c921377722070ebaa8aec63258b95fedbe7b461ab33b2e0e8655d655a1a63b4eafaf5d53b5ddcd40b9962eafc513d70e7e6e16aedd73b350e3543a37d78ec2a0163e2c64a5901077f7013b868472b9c3ef934dd926bcc6e9d4fdd3dcfd5fcefde3da17aa6b7707d7ad31ebac53b7fbee19ad6b07d7ba49778fcedd83d5bd93bba9b799a95d7237f5d0bf4156868202697f098236b24836e6e7f6d06463c5621c91a8cb7668adb576978063c5e27edd58afc78e0f1f3e7cc81df2c4015c717762bb6a5775eacd05a0e6aeea8403d00eef55a57b129f3131b6717401f78e34f4869d71aabe29e0ebceb57febcab22bb3ac751a5bf748d4046869c19d935b605a572ceee9d8fde1dc3df84714d16dfbe8d2d7990effb8f9c3c43dd93adf58973d94a11f41ee7e8c8339a4afd32d6d32d7adbaedebdc7e2b7b647e328f2677e790adb87bea433f201c8d28c2ccec99be6fea4f867dd8f856cd867da20cfb48e853c09d08036e3b5d06ca1757742f36fca2a5f34bba2de25d17bb76f0de737a31ec97745b00ba2d62e79d9e9b9e6b97bea56fcf36ad73ddbd9df78c9b4c39bad8b58b639169fb42ddfb75b26b3aa7b65bef858e15deefbfbd5b2ea047937bfb3a9146d8c8b269faa6af331de6f1a35fb7081750398085b33c1767c28fdbfd37c443004f1000037776dd5165a7c9f00ea63b90ee0cba53e70e1c7766922fbdc3e7b1b3797c616797efd62d9bb3b3c7ce14788d53a56eddb64c279bafab3ad3d799066e63fa3a536632d055b6743bcf559d7a6ee9eb748b03e5d2d7e9060284fb7f70fff475a61f8875f0f1a8e3d4a8b556db55a9dd58db38c27a471aebdf91b85f178ea877dcaf0bb712b7ea32f5fed3c4b5e9c6b2291cf1b8f79cbb77d4ce66ecdc5be92a895b59e686b7b7d2e8c646e3de4aa3ab6c371b073ad7a61b712ff7884a57ef1470ae91d8ba2b9c26beae521fd7a69bde8bd865f58e3a3d1eb5ab8b6830b4992ec2705ae9aecb3e97f54b96c5afbb84d574272f5cd0ef9abe4e373a3cb7964ed3e1b9b56ed3696cd3640cbb4ca61386535da6f66d56c96e385efb5a97d570aad3b2639c6a1326f45197e9189a086f7575ffd2be16deb033b9f6b96cecb27099199cbeae723bb7d3c59ebafbfad23da226eee7aab6eb66beaef2ddcd657c3cc2ed53b9befb4fbb313376bac854abba7dd3cdba6e53b7ee1a18bfc9d48d892fbcbea9db6a259d8d63b1f8ee238dd818ef3e7d3cb67bb9379988b5b41149e53ba6d5ddf76e66ba84b58e358edf356d6a32fd64dde618bcbe4da69b241b53baf0f1a8d5bd4c3a61ad5c8fea1aaf6631bb3a91f00ab85f753daaeb51abab516557a7daa6cf368ddd36855576af2d8ba789ec9a6ab58563a7dd9dde8a142cab6365d3f5ddf056e5eadcf2e6744aa789ad0be3589b6ee9eb7423a6af336dae4bd885e19ddc2d11de6b6c8c5d1626a6afd34dbfae53d5b1cbc2ea869d2b1ca7db5906a7897e4976d56b33d5c7230c07139f6ffa479cd3444d8cdd165ee13685e9f0dc341170af7f5ac7ce66bbae4db71877c4b599ea77fbbd2ea052e912b51f9cc64aa59e869f9b05716da67a9a18a7891e3460fac2cfed67dc3151dd1b7637ac53bde3149ad8946be106a7d7ee384dc2799ad8d7ae6f0cebe39a32761ef546ea74fd0bdcfab9fdd4bde11e20865b8e432c0e86577c68c5cf3d6ef75fdceebfd8081b5dd509b7ed38659bccbd07c74cf2359949beada73af56057d9d2b5e9323db6d79d63d9148e4de18ac5014623bc5563bb55755d1ad7666a6cc2c5a292dd70b1d8965c59a413afd97473f78ac50c1a4e75fa3addd8d2d7991a7330d5b6259caa60e2493341e34ed4ba65a2c25de19449d812476cb3b9040a71095f0cbb4b2560d6a5fb36951cdd89ea938557d74e895b1257c4d6f96a375e01e155e3f5bd29ab912e8bd4ecda4c02a6d9d7ae499aeeda25a1806c9d6f1aa77148a020518144c1115847c41c11e2ee1a4e976fd37d9bcd5485dd8ec718b075d736a451ddb0b34d45faa6cd3635dd4b373785514df73235ba7dc6adba755b75997a46d81881e1c427b26c6fa53e36d7f85876dc5b7974db5b69dc5bb9b7d2986eb6b9c66d6d6c7bcfb5eede4a63ebae30b2081a77a24e35a0fb328bc01159e54e74e3cdf6b52bacd3b86d8fed75e7888021c270bb37ecd43b0691a6bbffad71737d9330910e44a633e9aa9a752a9bec5ebac8214a868c10124ac8122121829c0a2225085b900040f638f1b88cd767bb06a52da8547108f7c19e854c211a01040080820ec31000303824180e488462d170446f0b0014000161c4d848a53095875996c31432c418030000000000604066300c00851be8903c5653febe17452f432bc2628981141d5394901b58fbf31d21b18f9f306c111e56d26b4747361019499a02f5c15968194d87a2bda47baab470fb115de9ffc229f67005f338684b3f4e6e8aa743b6cf592b46a482cd33672fffd90a7eb60887219f8d77aa5f2d68d782f167cb07839bc569c267ba096cd21b49f9b42efa4e5c7afe9c74837780df68c3340f5911f262d41c30faa794deaf59207360471dbae14d3343b4e44275e22e1e300db1d03d0b95d29268799f5923cc460bf9a0c490c407ab2bc7920e586890f34f1c47194fc43bd008cf367af8d27ad275bfd8c16f553c429bcbad9e922520260ed2d956d4ee3ac2e814cb6b849ea185af92bedf0d1107a90ae243c4c69a123ab61b14e165b8f2b499a8c8037b391eedb1ab5bc5446024285ed346506352b772a9c779af344c524a83698ca428b310f43645c3cbdb262f71f804e451980e2cbdc5a4f029e287c5216827e62c8d31ba5f3fa0fd0a378974d73e99bccb1315be00ce422bf69a2c27a39a473a92bc61ed03b7b0d1f414eac2504677960228d20e7561dabba8d52da260b4b9552694ebbfeec2037db8f24e6096405589269c5c80cee3532d0d0ddf2a8c6965c039ccb295c551201b18d8c0af55eb5b01469fa0ac18281fe5c2306d59cc130e286ffd638e4b2895e22e30ae522b25be0d610c21b70b305981f9e1aa9b329109538018dc3832a1e8d3fbded954f6399f1229af003a9ea4ece8db50dd3e1af1d3cfa544e073aa4264a8139d96e51bcfc107916771728ab15405d0bd984eb228dec106a2556b451403cdc1f00daafd43a305d93c3bb1b7b6b210db26c5c33b53a45ea77278005ea4ccb8125f536ca7a26f5ed738225129c05f504f1542a888c21df3dd24a680fd0b4307a020ee2908827cd8be33dee9c911958984dc0e996f208b394e7965a88e9ae22fe6d126ab612bdda7e9762b494f4924e504d4c9f0db896a2b3df95621b03ca0e545d6c42d2650dbe1b34c9643f437b1cc0776e6a2565f91a99a55916d9a56bccd2dcedeebea70f2d03375ffcbc729ca3bc7f3e4a30f504bec97923f8856307e5eed7bc76c4cb7d794a4e685963f1958ec04e1f3b709a83dd2084c3516b3dafb4cfc56dd346f81d9db7d1a8d647391687873161b81c16b8674ca33c44e0ece8f165b8ec14fb70be1f50db568946d10d2b61b5b15dccfa5b23a595caf93d096fe975a5344f7a10f3e1c87a698ef7c90560fec1c3a6a21990378749da30b33572fdd3a6fce3b372e330761f0227166843bac1565d39cecc91c1b2a6321c3005f0a232c5fe068799536a228f50c1807897423d763b06c603c34ab97573c0068741d18a9c93f0346ac6560c40a068e58c500106b1810621d0343ac63c074c0f6b624d9a6b983cece53290466303bbc4cf8b94cc37e601d84ac9fbed0bdb6dacdd3f9eef6fbeff9ada89aa951a712b2bb5138d3c440f1d287ae86c2785ee9f0ea70f1e4b74a0fdf6ff13bf12bb04911fce5619b51178522100977526c8125562fca9952f7dcfe003998825f0cb215240910c703676e23b7cc1c60ad8bdae496448e6522b282e4421cd6e8de9ff893189b50ca9faa836d9c04e1bb96c4d6dd6359b332b3f420860f32e7f113e16c22678a62867013451537b71c8e5eb56faa68873f84f849f0dd0c7e0b227086eea336cb2ee01212ed147f8b8a3282e0f3bf64f0e277be32d2ebcb548ec917df4c9bb03c0f3d5a8b790bc907885d8a39a3ec19e534ff7f4aa264ac374b11ba044cef8fd50f5b90ce584e96be29646b0887d08185f5a8843e0301ed680caaa45c72c44be2c5b783a53cea5effb0d4383c85648c9c2d704db763a3eb25b2fc36be0b3a974e024316007a822fb4c4aa52564a29f0e447db128c93afaaba1dddb5de8d7f2a6ac9bf37b9eeaf8f0ffa6e1a2c23fa2db84d5a3c80540524bdbe19f8d5323bb7eceab6733301d480f4b79d6f22e8f07e88b666ffc09b850a16ab7b104cfd768c69e0028df714f9ef48164b73c2fe0460027f478319f8f9cf4bab7aa9124ce508600ebeb24f14da05e1a59c82e63ee972ee94c686fb93e0c36986a3d5c065fed500e056e81c83436ef2b48e51ec3462ac6d172fc7fdda48dd8dcc82dff2bf3ffd4d1d9e032e11034a18378a9df73044feb2bbcd73b0a0a7c473db5da3b4e5c137831d06c35ff398a0f5399a63ecd8cc87dca8e772605bb57d11fc7066c5914cb09deacf2a6e8e19c1bade2fd3661215e04b8cfd2dfc753120a3feb333cf57cdf841a77135ab989c7578581d89bd5492fa88396f88bc0165b6ff64f0fe6c81c7b3813ae549e5cc0fffd5917689568f546fed3b3d748f4d88fd343a8006bcda7c67c88dd80a53262ac11bf28289bd1886557fc8358b8cf65cf0208eb1ce5e52c0495cfd22609eda8d3dd62ae607f4dfffae9af73368cbc9ea9473fe974f656edda0cbffe74d744f6c62e3bf70b57ceb479e3801c1aedaeb82d238363643aedeec3a93a1f2636a7cfd8233e60cf77a64b1bda5876fd53adb303d99f8fe0021e7930c656fe8f60a4de6de887daaff697f45425e7fe69ad574ee77ac5dc2cd7f387fd95664982ce6826ffe04b4b8d3152762fc74e1f92748eaf337de890a4906bf414a3c118a8d8ba08aeecfd8800bd5f22bfc108ab65f27349da524aaf62859f94b2579cfeefbaddadf3dbc7fe9b3486d0bca9d3affd981c20621cf3e7034504434d2076c193b0fb24e5e57dc61dd68833d929fdb94920fb35ae11009556663419e44b1a7462a8ce35fc9b6f4e5b136f7940eeb2703abbc95f81502261871a163b9379e3313d68604f7f5a4a732619301b7701f3e5a90eb53aa082bf240f9509d25c855c937b092f9ac47a9f521731ac3657d6897d5cb28711a003238efd15f13231891215cacdefee46831e93b7468ef63b2803984e36cc78503bdcfc467acd3dcd2d4256bd11e724064e983e83a5ce157f64e7dc0dcaebed8fc647e5f40b3cc9898fdf6fcbfac97ac12d5531cdb6e345bbbcc9ec490091834acfce548c2f2e52a7707141e570d0a394baaa28cda09198dba2dadc9e5a7f8e0fd2969653db9b1da61db9826f82da57013c7e8f2e031d4bdfda27d4fa6223fc14272832154fd863a06e9636e3555bb53119be3ee07446e5b1eb9ef497f75c59a6131c90e02604c1c0e805fd7a4f6120aaf182a8f3833fc22b58cf1c110b820c18340bf397789b882bef8c30a828c5990735468c8ec82ee9bae74f606411e3dc79f5d13122eabbd3783d1f5f6a375d3b0abbd8ef507664152f603ebd9174bdc8b77992462e022004733608d1a311673f5ec4bfff4398552fc2ffede63f90dade02190bf1b12c226042c35c2d806168d7dd266050f6c983f8b23e70bfcc8fd4207ffff1e2a5f3c226e80aed96e6bdf7bb594f3f7e7887c5bda478a463e372c230ced57883e3730e570a49b893578f565761c4055e958a8d317226f826b34abbc6ac426aad58885e0e95e1325fbc6c9bcc091fe4e33e9999563e4fa33a9000ad23580879eed49942f87afbe077e5cbee705eb99fcfe8dc860186593c5e68a90f4c1bada217a020387eae5b2763684773587432e373692b9c07938a1ebbc86e82469da2e16df4d860b260726da2cb72b9fd0dde154bf9c0b7d96d37fdf0206add84ec54c250933a2b7a745e3ca3e369409b8c1b4950a87579d2e20cb1270253d6aa8df1db3d060e6bbe465a86bbfe578635e56c7212a68a0a0e24aac230d0cc4d7e40021ddaf57f3079f51d7962174e9cefcc066be3eb17a09448a7b1630acc9c3f1c2702e7bc7e6c1dec852a6d70710bcedf4e3e47fc5acb50eb827f4fe24b629d0824ab285280591afdf8d9bce91b3833ce7f7c19b103286bbf6c92a54e7d4fdebf89a3e7e86e87fc2772277099010f9d9767666e2ca66c8e02bfe6f2c13ebdd639d5de87ea26df861e9382a045ef4ea422bae87e86d8d63e716f4cc2267d5cab959121e2c0bfdf73eb201226dd29ea7e38fa689ae1334d1933e5ad399e82d90d21fe4c86fb224b1e4533344afaf4baf0b0cf1c8378d35fbc53ae1cda9fe8af8be00ed4f98038d43a69e15b9c503d490a67265e816237ce6e63196667fa663f78882935b5ca5efeae4c51623046f9667fbb9df70afb23539036939aee16178f2815b795994a676059635eeeedc7bf8e381e9ac6c99cdccca68d09906d5df0805189f9dee7d8985bfdbb749f1ef476141e0e69d0d39323afbe1cd92c472a4b0fe603c59f23b26edd73bc8095428c6fb6098e08228a029fdc7fd3b65a334526236690b2298645578a7c902431855862eeb330bfcfa9dc4cf137bce43ba35249822a6701138b33b299025ba8eaba27da05330b8f3d6e0164b945ee304612f690d3ff05bdc2fe9273e1d0cb13d2a2375f417cc10f26c2b1ddd156f13179eecabd95f175849dc7ab727dafd7d1e5ddffbff94cf5cfdb3f1d9822fe313a2c5c07a7a1c2cf970f347b9cf8f3e46b368be9af056d216b97f414c839db60317f2b705c5d011793827b84b4bdbf130a5b6bab6f0de3abda6cc673abb4f003db3bc316fe4f0865c623c5ee7cb1f47af7ebcaeee024eebbbe4a06acee74fbcf791b60afb4416c49ebf8ce28113323ff92b23a5b1e688bfe795763d37da91b98bb769d135fd24cde412daee4bffb985e7b0034697f684934f5fc81863c7db8a8d5fbd9efd068ac6718023b88e5ef1a86154e4704518c8653bff2e702a4256f40f43c0aca72e275e022be6e38f3b258a9cc08143f4bd12f8e175ef54596738f66a092337c1b65166ce9fcc8f7b4b1da5b06894fc780649b969fe6fb3b38308db5bcf10ed25957dbcb10a46d7dc0131c6888b4092f733fad4452a5dfa737d43f0179a156ed4f6743dd2bff67359dbad331b6f73f69ac481fa472c066fca7f80c08c6f5ac3fc63fc3bccc6efcc4204aa18bfbad792f92b7679a467fa9e8fe354e07b697e59c9aa1ed6ff991d15119eccab84b6eb0561d01e2719d6e6ad5aec1b295003655e62cf1772b973106e74e2938e713fd56e26166f67e14af969dce72a9120bb2229a1daa94f603c311555375abba6c3b3e5474e1ee005b0a54b616ee741c21e51f331cdb441cff81dc2df5c0e1f9be768deb07ff6687df863c3378ee72f75b03de4d3d40cba98025e081c780369fd1989a3c673cf9a75d8e6af286a68b36ee75098235de4afcab83f725c95be1a5df3c6bf1a88b1af0868c86d8117f69f4deb0c9c68687e3276083851a295fd9d9cdeeac92a166f58eb171c50fd25697a74a4806b41b1d429bb1d05b08af7730ab9f0db396145f476439781caf257f976ac07ea95c21f1db5553eb79594d91a133ce983fc6a0d462bdeae1d84b9731281c69398b52ee4cf688d30e618bea0b72023e1cbaed7f57613ea8d7f572aa6f1c2bf8b1646d49f8e8446f6191168468a38dc797967057845f9c9b1bae67a664cd37e51ac7732f9526bbc2c5301bacdfce3f8e363808efc6c5df7f357a7e14356d499aa57e5f5ba8f771fd6ed6470a730fa1b72591e0c22e7531d0cc9f3a7f1824ae96e6bbc0be69842232ec9fc0169db234849aa244e5fff67e39f65c6190c784f1ff1e097129d46aca9c5c9db614154cf178773dfac841b44bdff02371392b43ba2e7119f2334987f6cedefac187a62e5eafd31401ec08c72bc92e9e0624b091906f9d9f5534f54ae72a2e2bf74347d1c78de23bac435c0678c6367e78425e6e716963c6dded5973d794395e885229b7c0cf397624070b36d060aab06e3a1998ab5881d7c5bfba360ffdd66dd1bcb3b6194549fa8cd883028cabdf32f45c341c8fb830eeb74a0f9eafaa3a94f95c8ecd3fca8704fde12b6aead84cab3615fe62cb5e06d1f82a5fe5eef77081b68a8b060466d8147554d60aa48417a6507845525145684a0be32c0e9aa0e948d9d3634b1652b9d1c1b6fa94d39fe5efc5bc8a8e6b5a7629e51f374176cb0465bdef61453115b7abc708d2991b1f8fe6353c096a444b7ccf9e2bdda7dd8279106d24040c0c3a11458b614e58dd5ce3c556ea60089c6ba0ed74b46fd1dce80ed4807cc343f9aa075737a18e17f07488d2e29c57740e46708ac708770f971df5047d4da80517cb4d034a83105ca48e96538e2b130320dd24c7b58797e5541e448b5ec9508be3393ddc437628ee4f516a77cdefb06be8ddda3d3e8179f43cf784fa2e4af99d687448befc0665736fd559728f81d622e23bc129ab0c876c9a9d4a791b3f1b088be06b1c3f00d28bb0eac796d9ddc86709f03469099c124a635bb5b700bf795fbdc389e72abc8d189d9de0b45651d0413bf8b3d1a5b8b233dca351deeb7ab27e61ac5c0641f0731afd62d6133fa2eac15eb10b711897afab55786c239e76f90cd63f0a4ad123d1bd9f58bcc4055a4b8cfef162bd9deff6bd43cf1d86f03306e4a9907f7c989c88e90c9b699c7ef7dc305ddccc3830a7384e0c388d6dd8e2a6f4a672e43d15e026fc3ded8913d6866d733be6af0193614026ccb737c5d1d081ad296a8ec34c85effc2b6650364ca0f7d964e5873e930bd17b5fa2eed9b1f6ca87c9e75bd1c371c01d8b8cd560f3d8149f5c415f9d77376a5b0f6cab4267524bfb3f180b82b19980aa28c42701be615eae9d9752274dd15cad22c17019a41307e8cfd0ae8c7623993ec770a23f5bb6128f6f99fc979f0ebed1bc74817465b31e891e1342e66d39f9c4503953d2f463dee1ff255c056b1e3b5fdd3aaf3f2defd07a9fd9bddfeb4232e029a980874fba3c71cbcff0386d27f07de8731d3960274dbd1e54f325d8fb12a73d662ef2128540f4b78467ffe71e91e3be68c37f618e8f1105e77c95ae2bc4e134ebb43e0f58c8ce0b602b2cbedacd1985dbdd1c4bfeb901d1d3f875718ea3115b025a694cb3b41b631a09918dc27bc3fa972f7a023ad2f28077cadcf89a8515f4077f07fe813f773c975f90eb264247fe7cfff8e221eb034ac2db4093e60d4ab80bec3ec9ea308d27f29a242bfb4912cc94c1846d462d20e2d65bb049095e19978cae3bc46ff36bd985667410d80a68c8f818595d2be4b828cff190984c2fa082259974682a5f174a4a16e5a9b01f3c139b74a244f6e06e0c1a5a6386ec010ba66a9b2bf60ca67d7fcfd6aa122eb245a53993c89243bff5f81c6bd4547d0445f17728932315a68958c542a8f2f3682af43bc08707395a74f337878f8b579406c2f8a7efd3890b850c3301a328d13d6d81de016a0125b3dc356b83aec3e4a81970100c872a52a812a31c0f71af56a9b39bd0faf43bcb5105b71e7dab2d1a152a54077403d543338f45464dc7612674091540cbd76e4ad680a5cbc06f9271805d46de84ae8453012063bbc4cafee223dd3e99246b0c8951d3d789304789e00c5e10cbad74d24ef456c9ef4b64449c405ad16665498c4c2ede94c57f89383c44961f4474bb74c96ec04c361d2e8179a2131cda1df88cd1389c8cf4e2673ce2a3511dffc06b81e9408ada00d264adef82946f1548a15f1a6595924e78bf2ba9fa837e8ba2ae8b9089bb8a19c2c9d7c5e6f64471b963c40a2b78fc976ae53096aa0ae6a9bc819cb3fe52539583184894ffadfb8710cf10cbf5cb0a0b9017f97a7b814330d96c61184c48fd641205b7961af7a669b3b76187cef638fbaa7f8f6b582c70c5b292f7c8cfec712c3697365035082df34d6047495091ba30a8d3fb686552615c21a24d48a2a54e72e66ca0ecf4e311958d9c07a790f2133b14e1508ee83d2a7419161081c400bc0a06d065ff873aa8d75d59b73970ad4d3e9ef8fa1053add4d9ddc70f8efc4547b4fa32ee361a93511399ee85d34ea18048275f7af969a4cf7c1f933865f34c20d1a12736f5a41a2b38d55caff5ad9882109d15c451dac85d11c4b37c9f26855a39b9cd20c54db0b4f2180ce5fe4648fd63f1d1e04c6cfef3c1ef2e3e5af2ca243515a66b4556455f1a406d0db8c6cef33f19937a7e0c49fdc67a5cdad7f0678f1d6fa70562cce761b1ab192d53a4c016515a96eb073c5d4ee222518ecdc55af6174e5c69e5cf499c5fc7d83ab145b11b63eb7331e0beb9e7c1b56d7eea3c561a173cede4e5422ca126faf717d08bdb87d81ac05855bac2fb185a5bab1f6ebc5ee5c9461939c4f50788c7cea702247e88857e5fcc09bc1362e3ed2da1f3497392d71cc3e4075239814694d88b75c58cd67d27728c2da8b13e24c3ea3de3b955ac55122e60e9adccf5b7cab2c983d7195858ac2cfa7482f57e0df96867931acb47ee12993b0cb2cd02883e3c800f3a48506a3380e306528d07f6cb4659840b476f45733d858656cee48b62a38be03f8b0fc1d03100e596ed5afb51cec811e222db5cdc415307550b52fb45b08bc90f01b0d03cc084da90629c022b72a828e0f71276584b85af53057d182afaa17c429b8bdc03562195d99cd023e1428c5defe554d9abeef06e3d3078ebefdd288b86b0253cd44c3af1458a3f3156a96173e31d97541e326ef9e8f01684f5016079326547567451f72b04e225c44353f610ef6770fe7e976c8577838054be7d370edc45f5df6d8243277f321cc5c6616ad9f5f99e6c4c066a2e753926e09ecc928a4efe5081b1fc0fb532d4c028d45e398e8085fbb69a30bc54018eca3ac6c3c7240bd2fb0446e0f06a9419b38961aac9bc2c7af7246bbe60e7ea34fdacc397905762a6f6c225f262a2eae78c74c797089e03564d866411f86778b51dfc4770ed60c470cf4c5e79d4dcef34039be1ab6b1c65fe7a344b69a5379fd10f7f9615e53234e7ddbe15398dd93831cc6c216799bb0802c903ffebf20ab2ca74d135bb13d22aab4c3dfd4acf131f49cb459680f06a46e429c71b40d071db842b9871bcc79149c3b93b047638e1c6e1d8a6b1433f1f1829a8d8c3600da2d14b67f9bdb70761fc48934f63ff29ec807b21b624f8719ff56cc4b40fd3bb04ad7049d6f9d0c848b06b39294d90d815dc7263ed92f166948625b8b736361540b4f6988857d87551838ae919b4a4e6ad6c7a0a425ad94cd62f1eab25f92bc76bbda9f55f119240d6e5e5a82c07027f2fade8497dd6c06131f0f3a9b1457a2f01e718d075161ae83f98f889a8b696f12361e63df332f58555e48685615b839e45a7ca9b8009c86df1977263644fc68547c1fcef493c66665b088af98c2bf974464a6a8bf045fbc16d6b10e862b34569d23ab135967589ed5d0b382ffe15c62ec20bf5b6fc2e15e71faf991fecbff08d58b8c7d7d381cd766a913008e63d8afc1122093ffaa3df15ceac9a94e41befd0691332536618e7bed5b7ae91e184e5fded683f7ba59f5759c059a9d3e3363443d0b61f4dc8a4eb44133e374ec349cb77a556ab7aae59f27fa8b07eaf8470914638e3ee85f2ade6ab23d73a99f23da6a1f58e71662cc778cbdf56921f640d7b2a89b39d5b688b82b833963f17c1b808c21a53b9bfe32e1efac68c19cd81793f2024303d0e77f8224227ac877bc80647f68515fbd59bd0aebfdc91240ea692f5c5231edcf47e551e72f7a174f20838de8164ed9398c65193493ba6fa8e731d7a4bf5ce8af43dbb10cb3bc3b671a64741366373e24839b92cc20123680db8ab109ee245739f92c59a1a3a0d83cd032854472051902e0e5c981a9b786f0475c6c516be562b6cdb1d4d27f60ded8928bc49d0ff7bbf325750635b9198ea1fc633de0482f2ef798c28d9742f50588e3e2632bda49181da69a97488cb7d80eda65a3732889ec2f4d55dfb90ddb4f35521d6a22dc377ff9a1c03dcfa412ebdd5b3532c3eafca2f93dea437903eadc477cacc1633bef5ff68d1e63a8e344fa45a220e0ee2897d12bb8988008e0f412bf501206ea277ef5a7922a64d39c185e9979d4788df85ec65af6598c510aa070cc057a1d0e86e83f1b33c01f4e077ed9bfc1abfdc49b63c5840afae13bd484da61f7fbd61c89b4e57222158f46a3177b8923d564f54d44ecd2513ec0b14fd83a0e30adcf5751aa70a2de4f0dc13c2866c002aeee8d4e02a6aa74fdedda2b18512f583b0386fc12846bfb7751e91927706a73c0adc4d0ccac44cdcf473fd6bcc4729ae069c495f75a3103155bb3b5f0a5d5c0b395daa85b566872166d88894063256ee19943624e98820414bea4523341ec65a447d0747eced86e4a18a9efddd5743681b0179f4b84b44411de20201edc1541072f6310d77c0b8251c88d982073d946aa582b357bc4381871a4260fd631f221cb0a3e2bfc6702eeacb7f8a6fbeb7dbffe2f48d73cf0e7418c47bf59478515603b56f3a6829009bc7e3df8f16e8417dc70e80ba049a86a4e0778417bb4d287388697257138a8c67f007284a0861ae6022c4bcd70dbabcd537c5767c624e864ca9c0625b5a45dba62bf836d81091180d914e0c31b69d63f59d6f14d09f150216a450da6557d66438ddafaa2fdbdf535d1c46c67246953dc1fb459faecca6418ebe40ef81e8174ca81697b1b9ed552d049d084cfd77596ba42367409ed688826f51f77a09735f27b7b5f6cf22945f3ebefa174fa09fb7601f644395378192b73f03561131cc242f12973d302777acf2b40b7147b2ffacfe3de4758d6eefb36696afc10733ecb4960bf5ebc0def216da9df5b23702a9a39cce55de6db71c26619dc5e4cad2527ce2f7e2dec77a7b2b00a51a5f32f9c1052b7842f1f12ae743266d7523436050709147d568785bdd47f2bd323a30680ebff651b2e875b7ca77d813b8c903d2b9002219c249ae178783099037563a40764ba74be385addf44683fc46e945ff95229bed62705b3425153d2992e450d5162a85d71b5cd2c935dba1efca1ec7f58eb17bfdd11996be8b4b4a84865fc0bce63ef7057f2b3c52eed713d80c57ff5ee1eebe34349e06348d2f451d4c642c71c002f9fe533e6672f8d19776cf5301ab2d7f73132896b894975fc9ff21372858d39e1e3f0349a3c1bef8c4de23264c16a832acbcbeaba7fa637fe3e00eaebe6b5e9b4d14aaaa064e500a325bf4e438ff219d52a57ded04bc8dc597c75eed7be657eaaefde5d8559bec36ce2eeae0037403b9b7df73321ee8ba24b4032fc499fa57ce02c3dcf7036f3375258bc1b7940992cccba5fdc559f4fb58f0dfa1398c73ab6faa65b1203dbcb0506beaafa41bb8d7bbf3956d0ae6151aaea7e9754b8165bdc5603a024964a811148e83efa2eb3940ec891ea89b1617047fba520e369be47a71e6895d48575b82171d399f7bfcc114138c2b5b2c951f372e76a313b1aa4315e991d68dd8392cb1c59fb30b7d50dc2606415ba45d6d9c6d3fa6c0bef1a911dea65791cff545775d8b447dcbcbf6e7666f880b04ddec8fdbf286b3e6dadf206905fac4e89edd2936733669c677353605b741c0c0736ad2444d8ca1d00a26e0c754d19223fe7c22c490d0e8f522201151cb5899be1910af4f01e44746e2a08eed5232f6282dbf06f63a9d65966a6c01977d0cb5f5417f8505a1ebdd004b3637be98cc5ba69c03af5a865635abe079ed7b32a7d14a79baaa5a242ce4e12a410e93904cb5ec11dd527df0f30170db9a068f8d8c5067c2d41fbac6cf764459b30b8c1090d6a9df87ad7417ac2519f86cc7923bb67f9dc726aaa9af80bf2070899a7be488e5569a69e59d71bc4bb8a39b22b572300a2c4db7e6c64af41ec705e8900e53a7083dbbf32d6e1589ddcc84a18665bf8939878f3aa92eb04dac854f187de5e4f1c7a9f01e14d58682d9ee5ad00436ba83a7712f0b04bf9305ddba1e59b3190fddc9d9836845c0edf68f782747043ed13c59300f786c2c955d00bd8ff84cca13bbe7534db7097c81ffff3c572afc7b921dbb04fb881bdd2f645e11adfc39f0ed834fc70fc1f7bf9786fe029985fc87ff2cbb44e5677b8f8f39b3ad58b586d188fedace7f1458750a0693cdb370916e4959db7a9eef9760fdf6bda5cda17b6fd189f2ba05fe66243257f9b2b3ecdd577f0b3e1657dd95c1d92f94875ff11bd21a13e0ee27b7d3f46b5970e2eb0165488f23120a5c3fa809c71013ba6e6384f4d37709717c88db5aba93b83784321bb184895b5f14ae714c0406a8dbe03167f31ff0d8e2aafec04c69f5c3a11c84984ba2cb2f3ce732678030932159997e5b74a84da16f89891dbd83cd515738c6f4c06ea1db4ca38ee98829eee990c538164188830b97e66da023dcd73719033b3ae8091053e2bb3f6adce308dc3376d63f04c7bc732a83ad1c0ddd007ecaa265967f75df4bb58fcb820f166655a9ad192ff32925656ae4f0a6b67eab207ce156c3537f7b33efa3bef2055f9f86cc580e0a503f6df899dbc2bcb9bd7ea0efc4e10fe185c2e44ebad3f16eefd31af894f1bb8711f6b8d19b11c8b107d0449b678ad93212c759f3e628ea0d1e397f1e1ee53d671a13830eccd6fff880df5897bbe5df03f38e8deaa39b7e11c209c49b2892117797bf7f5409e6429498b5735c93e99dd551bc6a7406c5e6765bf44543bc619b67d205d9f813f7dd38a13fcf693e322dda7d6f80da4277bd6fe9f3d8b4e3693b26a545ad677847b77c5a2da9f3b77b0242f408a1ab201cfe3afab049f7f64556e74146fc3b15716deebe00bb4b07f68c3edbae60c55c88f2a29fd33760263ed58ac82ea0ac279d3d12ad3ebb918a046e3b6ce4513064eb7903d558398bf4a6ad4b5bdd510735e7df137bccd4611e84ceb88a6e356f1eb80f3b92a09e9888e5caba07b6ee652818041d74a81e1ba9164bc848d94108bc9b5c31e8609b513c2b81e3317842ab601034d6f0cd4a53f916d2bc21af606ffe66b9192e2ede85b2bd1e753bc95025408f85c1716c74da79cf9a079e3e6297b79e8bfafa5f04b603013f027a57f91e3917821b2bd1b906c197dd03f3467bdc46dea9826f77a7b35092c3bd169a8ffc5c2268cb7ae12a50847a382129803ce5de32a7b2fe9621befc68a0195eba7e5f6230ae0b3321f7e9aef4c55fd5f22fa319dbc8f0fcd28c89811ba49b790035c83e523eeeadf304a8de0400e191f42ffed820a530fc75e7f06080b24bedbb1f13058b943a5f50a3fc98463b9f3e916688af7a2986c36de6a970524f714a6a26a291513fb485c377e3cce3249c3216f70dec764cc389039ddb3a180b7b419482b9d1c1952a7f75996e16632654db0cd0067b78a94349e4edd674bd54055b6857b0e566535fbcc43513a001ef18c06c213c1fd992f35dbbd0287fa4e3673ee0fe0f1626dbf804ac5d952cea8a9cb3567ba78c00c4978a509a43b58356fc058de4ecd263a3acd5e41f20cad556bda361895ce3a4da12dbcffb41750305b7038104188c080ae3bfaa923d243904fc083b2c3fbc7ee199751863eb43c2a2f853afe7fdb69b94d02425d15be01da155a09184caef3ee7da45e1f3174a3262fbd3d71557ab508f16e4236b7de16b8d4d5c5d3fe10ff79220570cbb644bd50b85d492f0b1615e27e3c3180ef4394fdc95a4439825fdc86777de8308a75c6dc0f4d7b5e68c9766089167773cd28bc6b5a6ee4a6f0b4ea3041940de16e00918128616eded810804008cdccd09bf5c76bbcb4ecfa6f660daee46a73d1db9887fd4ab6cd0aabd7699ee466edd5f339cca2c11cb47da265c145578198f2183552fb6b74473ee69ec9e3dabddf7a11b9af7be455bd9a306b8dd6738fb070e75198d34b597603c21f239b2b76e2ddaa5d51cf8811d6df72633dbe005dd435cf00d07690462b296fcd7b8606951610e88d257eea3152a3d09df80abdb72a0a87d25b1a411159773ef8d555221f30c9f51bfb5c99632d730615b02c3624d490f4738343d27c8f6d1ff33eb212852421555840fa602a8c3cd41e30b637ad1a3f85dcf54d2832a5e221a6e5608880326674f0da122165238b233e07b058e69839e8d810db4191ec8bf9efcfb49411f7fbb8e9096360d421f755901c2ab00f071a6a44cde886bf31a88415988d088eca3ae8b6f9ef6bea99006d6565eaba684a51d3dc876648579348f283f069a11b2d5341a28605828c4fc3a477e3ffd8214af81dacb59c5717af722873ca2e3a64e23743a5c1311e1cdb538cf8272759af9df0f7efa0023efe440cb950522858ee0606281d33a2623a785b2b4b610193cdd9c59374724adf23c17ba90fd75faf4c5167a03900fa8cc0910a9753f6f575c66f88162aa89f33991ce0998847cfedb4209037742c154ab6a70ea718a174dc8b597d1b8f864be108e834596ebfb8c97b0cf641b333680d2f1821f17824ea0be3ac32e171002039785cd8dd931b960c0ea04d9e7761f350f7cd0cc21fd441a80cc3ad56a081fb3993ae052a2a4ddfd3e3f2ea3118205fd59dd6dd8fcbc499fdaaa4e8624c4add9bcbd071061061d4d608f8c88a946232a6f46bea0802f48f55cbb35c063a18fa3f953e421ae25aa9d7b101df3055c9231b07dd4a563bbb489cf8a5bc0a93d3697583e4f3968b4aa9ab81e434c4a900303804c7e1fd9aec6769845a47c2ca657a02bef0edd3c8b143e70e273772e09c57ada18eb7b5d3adfc2891f00c97108035e14562f7957a0cbeb99e1dcf3558f0bfae90a4ff3063543d91bb9bc15f6eb85f2022671727ec4f6bf14e516559272bec5a59609422018720c9aa3a597c1ebcfa5ad882a0f944f214f58a7e1139ddaa43513429822e1ff77f176df32778ef6ef92df30fcd4c2dc760e14042a36a872213a5e68b5d1eafed20e58cb65bc46a211302c8ec2fed6b417405f73461042c8b1427fe17df383b0e50139b12329c864064b24714f557da11f2262ac917ad045317faf97cc26c79e354d89c7e88bf43cf51349968d72cbd5c0d887176174b54c6314ff02ed6a3989f4ff183ddf13d4f62f79b7ed1b82fdb480aae21bd32e141f6e102da179399c2e1b51b57284b9469545de8f3d598fa71d3f822b90cd5504455c10e5e243e9e4206b13b4f42d5f0e2bc569dfbed332961a912dfee73fd1bbf60c66e8915573dcd32efe86c8dad8c7914f0146dceca1b1a264454ca3f160aaef92132786c85734c57eb5b44216e08f8633a1c0e1143471a4589842879d02975190db128461d04109137a55b30bebee828e965dd52383f49aaef495da896739aacfb38d301246bf63220fbb47b10e74acbbdf1b23bb57bf80fb9d38d82a94250430a9e020399a166486019d4be8b6d985fca370e2b8412ed5d5e50998ef6a5ec8b7e0a27c47843a739b7b592de5c1d4ce5892c64b7388c5d387acdc6dadaf9342ce366c837a2d193fde18418e766042bf63296a1d20e88178b50544294d9e7b49a683a220bf0d69fe54a7e45260c90b3b1f4b2f17e86d74768316773fd5e37572ff93eebce607e9ab96eac004a1ef64446da7ee39ffa883126ee6fae5d5bcd29dbade4bbabb23efa76ea07b38f883b3969360881089bce3cc9c8df0c75747f663636ccf76d35535cf4671729fdcd1240e0a9d7a37f862283df0899273face7f4b9bdf2a21d388abbe90d9beb188e72cf8b2b3ededffe0bfae9bda0edc732884fd0106058e833c3b6c51f2f28c7484c48c53e0908a1e191f7955423af85a1426ce7619d6c56b74742e08f8b67398af0143e189ca926d72c8c714106528d2e4524acd5f046428fe9997f845189c3fffc43491d39ed5557cb0856a52e3f156188ba859630e1112a3458bc0679e1416b2afb664bd4b0f7bdee6ed48ab0aa8488a0e31c49a69c5be6b2bfed48ad5c28de319355ff155f8fa251811114cff35708b30e0599f9b1358c84a7c04307d62d8c355f67de63c6a9e323d21bac18931382cf6a344238a554a4d5e75d742cea01d1f9dd1ff57e40b24c2eebf1daba8ac70e2e9340ae340f642c6ce5b2528b2c6153be962a231eee90c359d1febf54f6030b0a7ca942e58d60bc99fa495324f41c10530c6a4ca9b166ac83680086f7f66223708b6d15eccc8313dbab5cd6e4d9f7100ad7f35a9ce0c44eb577a682b5331051c648ef89e42d9e9d79246086a2a9a9dc5e2b9502ebd51da8a375fc2111624d50e641d7e9f238aba8178c30919417c2b1e6a2002c0fb4dbdf9ec2129f5fe4ebea26952a8aba46d91a67bec1f33bdec36cd20a792f565c1108d24ccbed694f8941994dfee635be540899f1bc7a1a989bd9f7908b19150dbf1597072fc662cfb0442acced2366f7b60939b395afcb12027373121c391c2e17ea03a538651e4e4e22f3b89e9e500291619b7194413282410c6ec139298b3ad4e5838a5429d7c7d9100c94bc6067a36b08b2dc78904158cdcd4fa1ec05efdb7bae121b58a89ec17c2fb2dcece12e4b1b833f76f01f4ad24c9dc92c46ec3c7337689b97e11f0e70cff7ddd3ffa06fb783243a32f10f1dc54de2ec72c139d4c16ae65d798190033ee0c96e5c0e0985cbfbbecb357db28ac04b00f6aa5ab8749156fd2b0f878b65d4ef94d6fc4c3fa6341bd38a2f7e236651516d24a772e2d42f50b9b92147aaf19fd997a742427b79df27babae916d6b848572ebc717adc29dbafa2d4f9f44228e3ea67878334205b808dda1c0e46f47270475cda73852f857683f390e931528b7ac13c62c4f4492693834c0b33f0cee612b5285e043fd43ce7d2d111aab4064c159b7a848ab5abb596681d3519810f89b901a6b0d5af6b09f532b36663f7a99622457741bec61e71a0132e17a6b27991f7c57fcbab6e23fb016bac21010c6e873fbf06f98bc895e83105c8e7f763912d140d1eac0a66366ea3dd649acf26a3f897ff1b550ae61a60a919633c4ed02f10f883743373c89b346a52b17aaccdfffec0420d808618a4f98035f60e0081ef560733f0658f41f90333fe29793350a296318103937f140732f48695bc63575284fbdff9e2ab8f0eb734751a76d7f54a2747d0d1119da326f1e7c65a6856c878f9ab99f35d5afc90bebf646c7b17156ab08e9213bf23a6cb6e4eb37803a69f689798f11e100db10805e6f7c6a292db6c206a547282e64668365801a4067c790866a64a106dd1ad5ec2270e143e1a13ee228afa28a973ec49806d3715678df3e870a81865fd700b84673c3c888d13fde37652330c9b59dbea3add3c481134dad76f621322df8b1de9574515d26c223e8a0a96ac8a0970748cc5827f2eb1d53e23029f2cc5e895b03492a551dcb705bd38c0a06b8b864c68d8d8ff1dce21a1c8954ec508a0dc097fe3d181730c2599b54cec5a3a56485e3831f19050ac13f56d8a8b5484ce1f845521a46f627cc339437bb4e033435cb3234070e1ad0a5b4d351f5ce09fc42f6b8dd1a0d811c42baf9a71c3a98fdce7693cd48980094ee03c5ad83d2bcf6b63c5ac604c0dafe5ecc4aa9cb7c40492916f6f5e228d240ae565e2ff4e4c41a7abcaf2bb167b248963799eb5374c02793d865383c2eff342cb6ec17e13598294240b96e1014a78a9721fd4b9b007ceb08b5559843bba82e4cda9e8716cbaa5de5471096d0283a759841eb9e41b0c082f29f2bdc1e341b9ba6d5ca93aad7e0524b71a4a6c5a82478afd992e0b3a2101d2eafd9483a21432db7efbd1c895137602aeaf08d4d869b1b5ee5360b0504753b08744a1e3c51858e3a97bd87381fa09bcc5bad5d8464366e8ad485d7d785178cc7e2eaa4490af52d3a0dd6292b5d7520b4d9b1c0263641f3f2855a31f8b496f31c8224723747cb211a460620028b66bb8d42aba96297addf740535e497ec01bf0212db196dfcc0caefd70b42016c8015140a204edc14075826a4026b49361f380162a5ec36fd5ab6dfb2a001b524434dc62b0525e982790b023ed1e46a902110f20bca4b02c53a51ae1f7f113b987f3e4a0781822112aae7f9d1f0f0e017245708367532569eed0c2ee88380401ecbf36cdbaffc452f4f8eeab00d223c9a5a5236f92df4684128aef8d3c6d5981eda8e6ccd30af2aa79a1eef3f604dca069ee04c9e630c43a78853cab92c2a4f4889f34bbba657213c991442a06f017e378c4b837fbd9bbfe98806cf0cfeb9e0b6d0839c1fcc8804ace5857651822c7982d61fa4c63027f651c782f348f829a9680b2a70ea33fba1f3a54b1252f971e4a3cb322f9aa21706c5da7822620eb3099c03bb53c932bec11bef1b2bec430393a4203b02023a371cc363b17b2734f0e05a259e86e545c1c376ddd022dc37d4e60ece69f750968282c3041488a800df636a61242daef4d5ab2b990c031561759fca2c2e32aede4954953090827e1148c61f5b21b156f20ed6bb787f747da58101fc849bb3f497e5b58db7cb18eebd955d01f8f6a8d62e1e245eb5c71581a071903a5d8a660c81e953a4df67e67c22366754be4e6323c15f18e2393ae4541e4c43fc1667553826e254050c55dc1fb18403441cd0e5625c908a60631d64cbd6eadeefb3d41bb0e158d58f4ae78191d4bd3cc7c9af1f679ee12466cf0cffa1271af8ac25c233a70bf2938f8bc6f5609b1840d66a3e2260c4006071f596fd01a9523f4e144598cdb8b479a548a8451219239d710596599bf9f815563af8d349ed9f3a4c64956e9dd65954c564e33edd70a684017c518bcaec07480b7a798fc5e14422ca13b6d1493c6da7c5199ad2ecdc73e07b77641e33db869600dd20c6ad0b483208457e592c4b1ca998a3a5c693227eb13a2427f1466cee8e0c71eb0bc36c49988b38b2bef7024e0567fb0851a7de2a43f542e01bddd0df021041d704ff54adbbc91c7a786fd4ccd92c93ea91fef2aa2a0acaf1f193e44bbe67ccaef4b91760fee22287cc8743153ca2fc73bb8da649e06ef4ac7fbc4152084b95895244145fc1a3030e5598a9b9bb508aeedc78ecfd1e592966c4ff94debc3d8f1d29edc2f504a3c615bb3b2da24b21658853a8ef3d85891882fdad012c017e21d4a048b4d465aec105b4ff1049bb011a367bccf3f0cdac3b87ed2214235acc9e79085f63412d1b77b6c24fc4b8ed996e58a9e569db467aa4cdf0c452107df29e48c1423ece6479e25b59b374aaccdc0851beb1af9657e9f8fe84ccda5037230910e61655561b7d7681dd5f9bd9577de2ef49975768857e70c33396cfa440911ba38b96ce2b631ba699d78d4136da79a35cb0060366a56472c6063677f7de1def6d3b719bde9f3a46e2089c6b17d4b12b7c646c83b9fddb6cca63ef133202262b51efbf8f77e84419e1a91cbb5b5bf5f7696d40857c5f59a68b9bf02711fb7cd2fd96e72df65b4394afa2185930b3a6008a07514d1e49d30f5c963b1c5580da321796091b96350bf4c3456158baf4e4fe07d7a4e2053bb459e2b2a2acc25db64638f13af2c301282c803f0ae1767221e61f6b4082dd48d2338724301ec77f2e70347d2ab07e625d01c902722bd1c7a3addb77c96ff522c84e902d08a5318b7933ffc7597f0ba22d160c045fb23d68fa1a1276dfa756630de43134ef5606192e3cd8e554860aa254b5ff762a75936ae9e8b54332f66d0db3b84bf42ce112c449e193cd6228ce52237e9d2bd97bb0338e5c01bbae8476494769587666c1b464f7362749cefee9cc52e88546c60212837966a90e538cd33d6f1203765042a83f220ec3437937720eda0805630cddd0f39d2b75a14d653e7f0b9ce159ebe9111c13b2494968d61f7f2371fcfe73906790095b65d69080095c2e9cbeba469b57b0cb1184d71f74d59ca1fb85c555ce7e65c7b29dd7bc8cf1460e33c0ad64f9ff3afde228fe2848221483aa894a3f73b8347172c8a1419413dc43e17bf897f702e4e496a1148bd22f292dc99ad7af882ff89369347290907811e843e8815269cb4486b844260327b869c9724ce37a0def02df3faa488a7c350f42f723053647b1300399de78a948b21e935dba703c873c8961b9cd468c0253cf1324650070efda0149b5c1614f2007a3e9236118a8aa416eba7fee29ef0b0589143e2d14d0e34d7010e110eed63273919aa68bea30da711a89c9e36d3e6e18a48d105a9bf094fbbfa30cee8dc7794ed6e8c57c39962bc26e8bdc5b6b735c83c5209bfc11830e92ea8a04703a4ed70038fec71e953f556816f0e8e30ba5bfa09d5af80a00c1af37d4ffc26cae5e2b00e0c026086cc089ff6f92e08037001d91540ab87bf2b15cf3e8fbe04c2fb352ffe41112ad184625091855ed0c602fd7489dae585d2a1aae8ad0600240da4a8c65e716e665d6150be607002853877cb4e230a66b41ff6eb12be3b0aca0174de4a73945cb1fd3ab19deccd0ab6830f3e1b744509ef6bcc55f8ee27dd277119e0b108f614c64ac19c69ad7ddf526db9d51fa37855b1126270b641daba39dd27b4c5d96020a8a71ff9f6b18380f938bb40c17ad685c67f6c2f8f108f0179c118bc5741fb3418d8e1f85f831bfff95eecfc7425f3df64af5d64535b9f6203b3771c905c9f8db77025cdc4d8d22acc5c30a6e765a00f8954df36f060dd22cff9285ee11b975fb2a2f137b8bf09d58f947e8e79ceb671116f3000ad97b64568da12f3f54d566a668d35db4f97aa3909c9ef215f3522a8499d5b60ff97b924cbf14acfbcec26bf6bad1c6efe60d0fd8f3c26fb097fd3453151ac3a9228b9e26721a556c90c9be3b8deee681eb5598ba610e26c95d6c9c1fa309e3653474231f04f908681c527b6619877cc469ad72cc3b4702619034528e38131e038c71022f6ee766fae037ec493e34e70e8f86754ebdad6f3499f76dab1f2163f9f5fd87f33aecab95be45e193afc07b69a77ad9991a644b48d4d075961af4d5e505fa606b7e83f0fe95c19410f8547526d247294a21ac1f1cc12c851329cb59c2d0223e267ec5b8779ddb1f0e2991fa2969ec8eb8c949495358ae642543de9959c301c8c0d1a771b17d3d443060906721175da92d1ab20edec4af500a244c24666f40ea524d70ce4ec24793f9865f536789417fd055e3ea53844e958f55c98361fdf667f133d09c3f8e201849e60f6cbce4bb339e81e9ee9389be58185599d8447c4f27228e2fe900792d3b925a184abad9c33d7c31123cbd48141ed608ede0e1dfe1c033a61ef0cbd06e66a220dc30807e5f5e372bb57695b59830937ab4675758ae24d9539679dfb3c8356a8ce67dcd1f7ee00971d0c26a1ce7896841e2ef46805f41dfde99df53c27c999406729a6f801dae0a4c05b0ca00329cec69edc09a09566b534fba809e538f9522f54f167fa5bb497cff88187bad67fd581fca1e448f495b9fc846afbb41a43ca19e0d8f8fe1ee34f7e83c508d1f22d6e5d350b5c2ce90cfa74e59821f8f67b13a36fb2f983fc86cfbf5159d628d23eb98926554d89b18a238d3729666f37dbe42cc5ab4b56b6c195e567a95d59b9e53cae0836de93b4280f0d5a0cac23c60027b2e28d78b0e8fedeef2979e627440553fe0a22ee0f4815e202aa48e19cb3863b8dffe28b1ef056f39d8b446a8726f02c04cf28da56b9a52978b9138fb4deafba00869e5ae6fb9ebf593f95506d1a7ed5c86d051c108e57469f04d555da20c8a6f745229a7016412abce3e8b933eb1d7ef9f2428f39d98d58bbd83be2110c6251a7ac23564971ecd8084bc8a5d1a0a1e51a9aadb82ea04a79c364880a5309e0647d84edead25bd051c744c213266abbbb6058d63102b61549e6b2b6c56f1b9fca59d3a26dcdd1323d660196e9851a0313305410b76e0515a9ec519032f821af46749295fe9c4ca12c1c5974a66cb64e010a5ca8a4522a86a965ff4474bb640bd48170983e242e9fe2c79a80379394322000cd199254d946deba1d61a3e80db37b0cd9c482b463bca01fe1fb22fcd4685e2c35a38c5e954f9925a8079f488afe83417f104c70c079015508cac8a65f546ae00d375822b3fd245d4ea677654ddba98189b566a68502c4e349026a53c5b321f2bd7bce9802ff2b96ebd808ec62f709c3aa2d971c62ac29320c4ecf4d25161636efa009537cd7e424d92252dce1d1c4260b2b7e132f885b0a4a60a7dd1a939e10fb528214a458b190e2f4c33a273d1c89d05098ec9a9e8cd52658f76f14afb9cd59110209b8997dcc59893f852a6c7480153fbeb0004801ed98569e6f6e46ef3c1a9114c9f6af8ab266773ee3d6b666356d1ec7351484c84897c4a6c4d0ed50f79c655fe9d04938aaac492323467c6a8d9f7e670cb5244d114739c1d2bd1273276d5023826340ed0c75696cff2827808d6bb1cc0c0791fead5fd5f1bf5201ac64ce85ab37a16dcf80ce12d1c44079a5d1291686916875d6aed158f2550aa6b1067fec21a96aaccee1a520728d72ce5cc3eb350bd7165973af664e367c294af0e7597105d0f28090a212c4bff467601f98342d36de1281aa6884d76f98bb1ffe942b30fff0d6257f56276dd086d886c832987b0a556bd18b324770c54672d679633afbe14c6f1b5817ab6ad11a8f7f25860ff1d15ec63086eb935692f0651d74d0cb48ad5b94c6e767832836426e84e717606dc238e9f162d67237b2508c0f1e8e24cee0813647ec2300cf49157b63e7f26041cf7ec2ad3871cf556211c8d2e62730f353a4b3e4841b17bf4e01a2b05c37528bb1553206cac4f4a6950be3d608aacebe75083797aaef9d115c41afbab647fdc416291bb706b93b5ad261e74e4f1bdc79a17ec7561314f9954c4d2ee604b77ce02212112eb580c155b3d4a9c2aa7ae356e7c3887b52d9b738f054a974a196cbbcf5e82e28380f3bf93d3b99f2893eea2622617bdd90b9285cbbd12cfe6732345925bdc068a951ec60652c3b275aa36b5e1481f68bec32e7aaafa0f6fec091c070de3b5d92cf9bca28296b706fb1c5c20908e42a594d518f28503b5ad47d82a4df58e4f6c35cb45017ed999009183cf41dfc6acab7be3baedd2e51e152efa2da627ac568ef59a98b3ded35e2a84537c6be4ebe22195784c3025a6b26729242aa181bae895574eedbe8c39a2a3a82b1d50586e6004af5855c65286dcc6179fd346e5e7e84004c3900bb71ffe8282a9c0587f547d218ab1d09b93ce870ddd329e934043be66a7fb42f36a947b430286ecbf358ae642ae45736551cd338680b19fef13faace055c6104a487737d5822c9a039fdd959f4289f3ae8fccac214cafb37036eabd3539747df9edb1086a9c1516fe379aabc29e0ee411ed0a44186d2ff25731410d1b70be8426ae27dc450b4a310a22e1cc8bd3cce361bd8f37d6d9d4a7b0117ec03c2e1bc64710fa54701b3b45c0ea4760622ea0eb498f95f200657a31a8f9bf905f5274fadf0f876e5052411c98feaeab91665480720a429e80b9934723d87758612cac0bc1d62d8cbd1368429a7c94283181080fde0bf525501d8c204a15d296b617a31249d62e7f7db0468d9d77055a94d0004618866ca90dde253abef55b01d3d950d260b9c2bec211bb1818a91e5bcb4010df70379ed5e0e8d789bd45eb7d696922d913725542db386fe52c107942f65c316a3c7d62b9ec2d6a10c902bf70018890174eaee27c9138ea3c81f90624b713f0c35715ee6a5706b01b976dc6e9e9816905ef26ae65ba8611fa433c58a7dfca5b8977a1ae07bf20701f0c4d89918af11142be6978b575b1a2c1a909628037c6dd1eab6a02029d1545d49e627ee50e00d2c83ab005e0cdd0d6ae8bda4b9098dd66aef9a95c2aef2946173ee029696eaf0141491ca7c1a6a3d823bb12f5b35cf1b8aee78f28f0a2331123e9f3edc708fcaed175a85a86716fef1295c4c09acd293056cd4f62265680c42129dbd6b63f76ba867999435109e1d727f92650d8a5eadb23acd58c2b1ed44b7520f5323e8bc2b8e35b1fc92e7949cce8605b08b12a3a83f0234dbbac2734130d9e90a6b834003abf271a752c6c2e6526dfd56b435b98518900ec4a4fd6c4a713583fa91edbde6def74041e7538e21660c5bdd1abc08dee01c04bdc25f7ebe0a622fb3ec5cb32919589a089aec904dd045c7b5c9f8d58c4a4c93f4a01eb3cd7cd10f88a685ce4eb8ab619bbc9a22b8338521c8765433c53daca0416b8932b169b57d018d7149f9e86d6c15a3e57e24c00950260f8856275ccc2763aff590c9c57b6aeb7ac90468feabf3e094a7a0082028b4ef9befc1f582a3d2e0663a3826b2d652be35a5bdc4d57f2139947dc321d5564ab1052ce5bc4e66a908efd238a13cd263d47b1f31041ed9b7e1ea5be1d0b9f0fefb0a1ae2ad57c70d534ac2aeb3ce1fcfce4fc0e2f8ed1bc46e6747d028ad5334dc434b6e079a2a18f632cf21e5b0fe314f2d45b0c400d98b30044b99c32cda8cfaed2c13a210b730d6691e46ba27700addab607d5c3b8780133f1513b6767028dd60bcb6ccfdf0ffef355e56a592f2a9a47410e8c75761687d71be0e3714e57f2e281986213cdd11c53c4da1d5e92cb9f086fe71d0c016c1e153ea04c22d534d21ec4b25e185064940c8ae6bd2a68018382d407d820f2f9e37e4b65551f2f97724597500aa8e3f612b4355cc8ed82713dcf9de5f0fe8008715d00bc5d1e2b7f59c0bf987bb50213164e9f8a5f6f368a329311a6dfc195749b2f45415602b533316d53f7a5267e4d1e7b8eb5139d3951dcf3b82a381adfadc9d082ecca9588a078f28006d8205b0bfcf70785a38de1c20e474b48e21c40c215f326ae32483788eba7442f973b8d8c09552b242d1e15fd719f67bf2e715e93c1ecec90ecf2ec95977514088d0e82879de6a97ca13d4425d25d3415bd17c5b5c00734e98050891b59b17605a90241cfa364c0fd340e741def9b586089d1a08086237e79c44e6585f7018505c770fde75a2f245f66e24d7d3760e3f3d5ec9b17801325452a273e89303ce5ea402948b38310ec8c921a2dd73c95d45da6dbba9f80b0372097b972b1d3b2205b2b977ebf5747e832c44741c86381e801eb31c42c0b18a024f01615c0b155ac41f3aada0360519a5208ae6787885a0406e047dff5dd2a593a4b30ec06fd39715bb7ba1ace952019d96122c7d8c6257389994f533c9bc9f675495eeb7bb8f4205c295c414f3fdbca23ea73bab917f823c08fda70863722fe3181b8a44e742ad16a4705da7729022861d5f7557d6b045498294bf8c4051a1cd8475deed8da1b383f48ab66055873170d5ac85587c5ad0e091f67f1c8e64b98bac3fa0bffc850a65edf3f0732b63d1e9c475d00180a57a9aa78d2301be6bf8aa936afd2e45e4d1e4047000898d80500d81699427e31098235f44ea5539f602e4ef225f51061be585478c9c424bfe2a1a80c73280293bea6ab299b03ff2273091c157f556d8e25154e071c93dcd4fc87e337bca5172612a412ac791caee2a5cdb464ff6652cd0c0674728ed0b05cd477b0aa66f2f2725408cdb58165135f9ea259f0199bd19e5cd552f6771f5a0822460dee1b765cc63676cfaaf005c39b124beffafeb814eb0af78bd6913eed166d140944f022cb06fb44e270631c9c841a1962e73c02e6b9ef3942a2be63fea8e9fccccc80e571c663b0039b1fe8cbf57c9af3af133ee03da30946cf7b5c5a0f3518bc492fee07938de7992587e69219f7f6f45daaba4a67a861d1664988d4cece444dab0487de3fc360d2cbb6d9a6d8a3007510c1612cbc490e140c09043b9a8043145d8a92e8220598c061df048ed64726b2b6942e966a2c8f812185861b611fd536cd979023e308299a9e6414a2d7173925930f9e3859143d16691d6c413d80d08f108ba1ffae88aba651e323aa1591b281d26b5c95f2101a98b647b1b6afa7f0b356254db76d4ad4381081403bfe445fbdaef10ed0e31253d4934f6707ee7baad8a9eae74462bc42229fcf6b932acdcd129c3f29e6d561c7f2f88df859d73503b368af5140a0b3de0ae96fe1ecbadd8e07fb1ac7868ea144120ccd005f64007ccef7b0abc6cc3e9cc021b4cbdcf237feafc3dffd6c007d236d48faeba32a9df69c7beb7b3d516aba316196c93bc3702841daa7b0793694fa4d30721823e0ca0c33088df17b04e6053e90ecff1f5e89e90f6b8de38b1e8c94058daa92a327b06720502436289897cb2ff5b304a10c2e32893a8d5844b16413abc2c2a34568555757b8927103219eeed12c15d498e8ed62200ee2b0de71ecdfe3741daea4e749ca89a21307e35db9dde1b732372828765929add7ac245014681e433fd6dc208f19a5e4580ac6b49b01e3e3ee0acd2bb1dff6b93b08535c8fa6108ab1a1a88410dff1a53b54efbdeca1eb57211109bf592380c41cf1d78b8a853e0afa78539d827f923678b33126ab3f9987874bc59d11672532f6ef00ba7f2271655b2da0243b2aadcc5bd1d9daf0646c436f81b70a20102425f87ae079fdfb0f238fffdb7497058f6316b19c357f9f149e465d5ad8edb2fb304458a4b09ca0d19925fb6406d1f7d95b7d0c7c9c9c5be1dff6bc51c916c5554c9a100bea9d59219707e2130e976104db37fe69ff6d7f1bfece45e9cf343734089caf6708d522a876a237a28b7b7ed85781597b13f0ce836fdfe1dab9dcb23b7b146a4f77cdafd93c8bab4e6206cff6ab403c7dc2ce43f96e50e7c3ce6d260c0d4e10bf098b5504ac0ea977b8fef19cc52ef0508dc787172147c9fd5f31c327a326e5e3d6995af50bdbfda45c7cfe559ca8ff4ae6b9298abc82744a6c82ebae545838e48e0c9e6815e9654b1e2edecdc509e01f73f1b1691cb8a9043e7abe3af0003848acde906024b94a8deeb7e52155081f57e7454475260e5504c6d19ba6291a6559c010a733890d0bc5c873d24affe49fab3eff2f42606022f33c3ff023a48edefdd8466019ae2c926c2b6a60c4780ed163e790aaa7e3545f823bbc8979455c861645f021e01a29fca9fe1ebe5f60d86e2589b6bc83a4cd8ad531ce12f9b4689a0f1d51dd6e59f141760a01f490744c547c86618a637db477ec8b122e8fa4952b4d174d03bc7112039b0d394074f1a50a2f3c9c9bbc8522a5e88c9e9ceff28b07981d27356548f5a65a16a2d18c3842abbe2db9f3389d1107e4876d54a315b314f559f4bf2de8772ae2a614167d13c2ca99e9809b19e25999c7fafc934a7024340ee8e9854251cdf621c123aaa84653131a4849f4958c46a3f32fddd3ddd37d849c6425e8bdc36052e0da887272221b9401d0e18b73444ca37e145659676dd13a98fdeabbeec9f5b52f76572cc246e37f401283a149206f7afc15bfc6ad0f39d5c3c2f8815010b5b5c5d1f8a78e1864662bee2d52d80519e1f04ac19c4b5b14d6c89f901dc0d617be5433185edeb3e59f3e05bd57da0198ab79794cd2fd241cee624e58a80fc086e9d7362fa21a1e7b29e080ae847a1e37b74ca1e17bf81a67446a6d307494a52c9d43d57e7504f43b903bf612bcb9467747bdf6a5c73c34fe761048238db1b88bdde08ec1c0d7288d299f6c0ac23aa14f6c11d5706eb8331c15ef6da96da7e9d589d7becd758e324c440f8cfa6fcb0cc347373255e1c488ee0c09a76e8ccfbdccbb99e465ea1af4284a03e151a13d380c28abb7da0a9f9de64d3511e8fbb54ac51991a9e8a3832d73ac84fe491724c3d5c981684b8109fa3398653b6be36800d0d9062634b1fe229d4b1cc8b428a9644c4963ae6549f2d84aa7f6cf743cc5b56c847578282c0d347bae1482fec4ced8bc79957f86ead59a526f362da7534f8cdb50cbd54b5acd3a71bb9a8d46061b640f50b4a06adaa0134467baab9d1bcfcf05194653d91cdd2799895d08b419a74839f87e3f58067cfe89da295b471719482a6bdca2535662819dc1d9b778dc1d80fe8c9f4f7eebfd043cd0ec2c47781910ae77856614a38889a5ef4dc057ef8a674498a408a0457f3b6348b71d968bf0af44815f479d5885991d70e5ba0577d67c3f17a93a69b40ed316fa9fce8b16036482ae6d4b91ce15c0083d915328e4fadab2e27ff78ec9b9a6726895fac739606e08b3823dc2e4d1c9a153dd83c4e04ce457ab46a46f76a92d5d87f227ce2732119d423ad40c595d4704a094a5e6a01a4f8f6f01fe8cb39cfe3b583f77a66919be63f519baf94b6ce700f9545f6e1d8442d66e3ae65e8e2bf7117c134aaea52311595cd433ccc538bfbfb6f91946bf95a15c6ab26d93a765961fdabc2b5b9d504f320087852cfd4e57afe9a79e93176fbecaad9db090cf2dd3c6275578cd67a2733634f7d48939f57b370aabc343a3766423cc3b2fedddf0801c157876c421822929e276383aab6d83d12e3b3768d01857552f21e8bdce9c9e547bac7d68381a887a933e75594c07763379947642cdbe19b8f6efb122e8aa533e691801a9633743365ae07732b1d67954dde0d8287b74f38857d2bc1fe28272c4b0f51ead1a6bd6cd981b9c03f5ce8900afb49811cada8b1695d697a4dfa2738427518be4ea60d2656a50870811848e447b9b3caaed3edaaeceb13a5e9e950d19e4f4797e132b79f1a8cd811de8003a33f3053700eb8b44b2aae68072ddf92d18ed5ccd815f857715c3199ced20d7b85689f10794b741e5d42ff25a22107e8d0a0718ef83d8d3456a48b620bff5be9fca225d81d8217d92c20c015368111d62ed07348c12f330c64cea58ab0b89b6d5004d6071aba750410984b67257b58f832f4b0f38c155bbca962e1b65a86426c053c4b37c75f3a5e176fd740e4360a4aa12c9203cea663f281626f374a5997349f342731c0f82e63b256c036808735d805613023173a39e618ab6dac2cd1b489756e1a61c3a6c988dbf6d9fe4686b019057624b7618eafe90c40b4f89faaf1e10826a0695e01372b367370ad82b8f26c0f88d6553870d002e46a583d61fad3bd9beb4568028c57e4f23fc4fda5a19983d6c906ca00f0eb83d336b4c4231f47f28a8bcc9bcc6317eb1dd95c1ed9fc9850f02bdc60556fec8dbba7c19ab0e925e678209f8c7654df9b119a476a7da5d7f8ca90fd5ff92eb879d6023c9488024da59e202158fb9aee723895e404112e76efc95e1ebd86902a3b13f7afda78f870fb6ec9dae12f98e7184ae5dcd8348512a3c056ba9d16c02ce11604b38d222fb389d523da9cd5f5406d983e5a93ab99b33186f83cb2531d2dfc0fc6c513a8ca0cad169eaa125ab7cb0ac6718d02f5c98f1e33e93fa217c931ffcc59cad8c53e3e59da8e06653de634fc504c6be4be1eb43ef414a11ba2b6cb522b3064a912a7bb24ffa3fe31fdb0dfb01830de749152a50a23fd3c881bcd5995d3506383e385dec95c4e6601e92be08c38bebd0e32fb58479e0783fb2aed76a8b552bdac149b4976fb867f79952683ccadee7202daa94408bc19e1017cd4a5a5007843710e44921a8e25f6355a0307ac320a9b5722d2586a00b261643ca471207d50434296866cd360177d5330e3f1d59dea1979abf96b4fb63102456660ff397888e64e1274286f0e9568dea69a1551920ed64c2c81b5c70301329dd0c08b512f0ea0e0a4f81a9b719ea3716a30b419fafb8e9fa1e47edd3bf169ec062521630a0038bdfb725022cc5d0f7575863607e5cda95ac645e85ec3500053d2ddfb735e505150903f88149e35578523f3446cd39c4caf34c1b77590c7f295949e091a2bdb2a75e5f043f2f22b925d9508d5563d3e485827956304f3a26699381df3551a5e46e63c1943758e4d4e363ddd0f747ca21b2408d6c1d215d0fc915fce3335b38db0949155a2768324ff97cfee4c18bb90690147978a1579ea947fc7cb65c57f0b2fbeaaeb5fec88528f660a27e8c701dfdbe2ecf8282d3f8b8e9513764911baa3340965c52fcbd8c0242ace489a45c1016220ce01e08fa895984dbcffd8df4d4c73d74eaf74d0553dba2a040c3765ba12ca2ec0f01a311184cfb50f5bc7aade6af3da0e22eb9c2163d28ff475d279f7170bd9d01aee0425c93c154dedf0e20e1b7ce8223fa363079cdb83c8778c405b42bb2ceb8723796acf94ca9985d1b31d94d3e4a03f4c0c7e40dcedcd6d080b7227647435c8d01723be09b4fd7b930d584f95dd346edc6e8ccbce2c3ad8bab4b67273ee1f74ac820146ba6784b902b40c5d3e1d5846e16624d9c3aa54eee90c9b667e8adad3209e94846a694386fdc5a9a775f79dbade7570364237ea8859aa23b926ce39965d5427bbeba79a5ffe1fd6d23c64c014ae6a30cd88c19be662885cdeaa3a227f4ca6380bc7715fdce8bc969418149e93d5030f29266f4a23855021a84f14e42076b9f4b1b48232094223267c9aca11326dcd0e40be85930bb0bd30506c59e52919ce9e0ae97b765a34537a7f25699668fed400c41ed0b7689ea1856213ff0c8223e7db57032125e01ca3947e39804189f035cd018e7ba6f33467b424a31a5d27ee6e31049948b385ab01adca069b1416ff0ea1824abe9646c48855d0ce873dfad17b8372e71efcd30542957c1d41590210dd6f2062b8e74e466733f6ded9ef9d3d8776021236af2d2c0bf853716986c60c877298f9fe8fd407ecf70405c9d3ce2cc0032aa134f42168f20b8e29d2ec58e8e7f8ef32884e8511d9f20a091a5c0666696601003032a34ce99914a65eb94a7566ee1228e19867ef1b2b3aa658b0dcef94937dd046e686b753858b896704e8c11c9f20bf143f418a49479977c4b8c7d07d23cd348c51fe6e48f356fd88f54fe888e7bd139073b3799b9b7da97b2927a94a542d79ab28db439f11d4bd1fc75378a325c715ef10c16a6827e181e25f6c6aa0b0316d26190ba203cf5ed056fbd0a49e7926ba35f315d4f64fccd0d17aecf001bdac42507443ef74f889555625f2d7447c3fbe4c026d2c339097988a0421cd016f09832288b0edf5c14a2ae4ba0d8ebc6d1ff2d2e90daf72556cf83c48425eb2501e144f072c33da3e351bc620975adb5f5c53d07ebc2ef510c5fc4a28d427bad1f29e1b467cb58e1ebdf29c32e2ee70eefc937af93fa31ffefcd6492144fb367ff10c036e78c1bcd2e24014950204293f16d80ed2370a2753c3e466a5e6d7a9948e4127c1349699057a07caccb79fcdc13035531e14d8d97ef4d7de308adb07f0ac9f6e6162b64b82b3a3fd44bb46b3c60186999f02c5e216c0e26d7717098e48e8e2de90438a2858a1fa67f0601189b5b937b005706c3cab877531f8b7c7520fd6ff6005a60ba3f82a6e9693e9e15040df5281a346dba42081b0ebe215485ae4f1da44a1db01b7653f9329410e0ec8a76f13958583f188858e28aceab89f3bae91d397a61eeeaf69fbcc6218093a3b9cb1a7fcc89ff7b4cf67a00223c796e91ece71c87265eb4810ec0653fe6495132b406ff803d40445da6fc500b9e4e15739527820a2bd205fd5792ee1f16d8471889088bacabda07898790dbd9ec2e6497c8a271856108e3fb221ff989397ce55397c11ef2fbe31e74fb0e953fd90e5d06fe19f2496350e3ea1b8c3129ccc4cef34507e07e7889efca36d851ef3e2ac7738de2d6da02dafc11b9aa1bc1374c158f3cd343e2863857709bc76923ccf4716540dd39cb86814fbc2323972a55430cec8147d360aeb25bd49c933747a8d80e05472085ca592db07e1cb9d784a9f0fc72f9250cfcd00602a62a0edb211f3083de0ab49a7999404653f5d138af7c2c58fea391f4800159dd564dcba1e126e3df701afa228ab9c87c18283b19d1afc2d7e0e864a6d290ed17a60bb3b712e2ed1f31c5cce9bc0990e4336b30e40d39cc8b935c23e71860db8e606820f522607db13843f7ba6722b4b8113ff4c84ac4cd8e928a97ae1c43f620e9fab48c1c4077794cedf45bfe3d2518054c4ac8d3ba38913601b451de40c7f6f141de2175736b10c6b23394ae945da3a892d35691fc855a2a3f27d3a2f5b6d6c14a47013fa86b1da7cd2eb08e4081de371d24dc385b2fc170c4a7d68a5b6f42e2a0b90535f5b5ed72e6278b1f17fae63cfee3dc380ae02c31965e18e1ebbcc48970cdb0c5ad0614f0f63da4376daecc82d704e91eccb60de252ea455c3fad417c90badba32d591462c05c1b55ccd3b39a0ba60eb7b9c2d78a50456409839ff8691892f5d69ef6d076e700dd23e9c4a03c61ee35557b3b04d46b914f182d56baf19aa28d494327333c50f276ac3cbc54e3dea02f3c0ce24a573e42dcefe880a9fc3f2a108469d4f6f42810c7c5b710cee10e408afa8d71c7c8c3cfa257fc723aae5f194b2ccfc3a293e3ff2e033300fb333237d00dd8ec78a7238f4908829b122516c1def706ea41dd477f58da3202d50e8a99028d475c19a3b823d7a2e3056d89a9f167e002d8f8d790360dcdc41c59ca8103252b563544946d06f8c642b3a75f0f7a4931b432afc7a30f4d245f3dbf30cb1ded2161eb5502ba32e43a08a3453390df7c476a0ed4cc0978bc93a19a0d375b704870f2a1fc159a53500b6f391d8db4e82c23e2dcf68996161e36f489f3b14a6ea0f77b0d809ad35947cd543462985fb0b05af542491ba5504408ce7c69681a9e05cf5ce939acaa0d5e2917e0eb5e4dd0debc5ef9ed969b8a0397e4c10051c901c010f0f4eacaacfb914cc1b72024b8691f36752060d59554a68f1df98817d9bb93afa0e7fa7557cf6de6929b1d0e9a794a06afed0a6e157dc5606c33ac2357880619e3fd8ad2d013d2728a077e924c400e4a42222872cc7ce325cf10499ff351c8dbfe6fcde1be7a9b7cba2b321b064e0e5ca52b44bd12fb56d623f6a685a771330ed91f6f5fb188fad32eb7c64540ef46ec9f8ba84f455658439d564a5377e073941796b9d006ba24c5f1fe80daee7def3b461d6f6520df8da957b732f27a37e182bae2337e6e2c3792939905bd2a01d1dff4c8c2471c747f85bb9a72fab70efcb03b69ea6528eb2c8b67d43e8c008335e8bc1b1b8d9167b46e7d9d2bf80957afbd0c9fe94defa8e0f8a32e184d2b0d33c05834cb046b41e3c8e97e55007c38b385870f052d55fd2d4cc6769ab791bc6571a1b27293a9f5e5910b1e6585789adaef9aa2f19e95dec4ee873334dfd1bbcbf63baad0ea3e6e82f18d95fb199fae5dd8cee28b01014dea241aac787877cb59a1b61b4798d3f562218070b55b3b2f64640efc6f9b7f8ae0eef0291a3a9b67acbe52c4ddea2b92a203b97c7f1439e0847775b8f162eeaeb54da58f90f9dcf1bcbe1e0cc494450219677c5577887337efba34b6c822050ce17218a7dae0fdcaf0afca553ef652fb576d36198d553994a41f28bb919c4d4f39c901e7648da377e58203a26ee340ad3afd00dfe07a739ffa49739775e0bfa79e444c2281321950006adcffe85e1441bbb9ddeb55caa59343fe2235fafa0cf3e0e947610071dd05ff0741ea7a0bda3008b3feb1688f443f7ce174f61090645cfc6f7721a329c6bb9b8cea45645fa91ee1e3edc3914f469bc198e2705b46bab3c3a96caefabb56b0f268a63b3018cd6d81c9ef67891936861b050668347253aaf424d07b46995dc78f83cb5d228772831d43979644269fe8746790c09726187fa09d017980f362651334e80baae8c898be7e450519acbad8c894d688e910decde71c78bc91b4fbf2e014e157a8e4bc83fd946ceb38f5d110b5f58a77365a2bc5b74263cd9d90ce28c01d30c7f49fdd2448411ed67dc324ce4c6117447359ea5cee82963e05baf307afefd29a97cd814ce45835f99014fb875638cdca985bd369632df1481596b8551eff29e9ac8d89bad110213699a4b872caf9a4f70df76a3e1feb2b04a84c03d79338995abe50f4314b5137ec55f451db9e93b885323cee4896efa3837ce208a12c07ce56c60ce193a9d5857c87bd59087a055b5f5c7f3a4c7a0fd331fa541dc632d19b549f8af428818e5bd547f9019114909bb6b7741f0f9db96262c71ad1f936f1df055070f2390a094a024b19d1cc81e6f68159913a644f6e42fe2ad5b93c3dc8984ac76c07c3ff92623665a39e8ec17ed9514644dfbb49c0e9c29d1a4b5e747202b85d4308aed5251c5238ae4723d6a24891b01d08924dfdd035bc4a0729a63b2789be9b5e3300271266ece585033b46aaca3c1bf8acbe06a12607ff7b7a8d1aa7e23472ca639c8b2869213f9549f04a9e0c036f748aefa7ee44537f499d3dc7a1f91c476f0e614baabc416644d9fd1eff8d4f52c7c1076b6e70e55e84e1c66e386abc841c8de64e0610b4ad64534699267f5b6ed6d1f3bc6e52866347f212b3f83d464349397011654eac222f2e72e8055f53dd6c2e99160521cdb6bb2ff285701c1fe5c308e228b2941c6a20f94221ad258ceea17c91b78b83f59322a69722a30fe8026d7d31cc1b05566e29acbe1cc3264185206d43520ef747d12eb6ea23e83a4c7bfd5e9716dd7640a5878ab220c4395921e539192e9627ad291330f479104b1a14a20e52722ed6bd52e5777ab019e60a2942db6dfe8fec7f62026ecb6d6bbed248dbdec652e1cb7f5766b30a087770908abfe97e7945e6c4038f505627d6aa5c4697949bb9d8b3180d52dac3de9efc35933c9f882782bc2e44ff53ef61aa7537873c84d8fa82369b303284fa20f37d4d78e6bf2cfe45929873aeca3a0f863de2a8445fa8f137007cc148e5a89253313813cc373a4fe2911123c5302461f3083d1e8ef81ad3ec0de5366fd29b9b3efbc94c7f2177e5e0d6f91b1a14ed6206ddaf48cec998ce905df4d63d37a57128f0a7429cd0b380846604d045ba607df9377cec823026c9bdc5cc71330ffbbbe937d91c2496774aaff4b9ad2430878e0c808e37452f2392e5381ae280fa08e89dd4edf04485dbc361e27c51f2a6adb237f9bd514a83b94f419d7e2c55ece68bacef2cabf0a06709fc7e0411d7d7edd3e7451710c3ef0758c1e71bd20ffcfa05ae4a87ec1a351010f6e6ff5ff55ff5ffffffbf59ff67212444a6e46592625b122149e40ab27e3eedcf13de13915eff46dfa9eadfcba481f1b596020817f618d61e3b6b75b73cdd3addeaeeca68536db3d2bfcddfdf66b4f7b7a957b5bfcddfbecd6867b66f53d55d6ddf66e7ecdb6cb6997d9b9af936956fb3bfbcb9eacf9bdff3a6e74ded3b6f76de3b6fb69d37b5cb9b2ae7cda86e7953b3bcd98c6675afbca957de5457de8c6a67ca9b51a59237a3baefa6bb9bbfddcd75ddcd26733779d7cd5637dd5537bf6657dde4ebaa9b4d266f6aa6aa9bd167b3f9d7ac9b6ad64db5e7c4dc744273533f73533f7b6e367dcf4db5e766e7ca9e9b51d7cd4db5cdcdaebf6773b36b96cdcde59a9b7de7e6dfcadc64a3ccf665f6fcf7cbfceb7e99deeb97a9f6fc327ffb76269477a6fbf2ce8c769df2cefcbb33977a77a6ba3bb3d5ba33a37f3f73677a9f3b53bbb9337f9b3b53a7b93395b9335b2867badb7bce5cb5eb3933afebee9ca93de74c9573a6ba72a6f75a5539f367ced4efca84f295a99f7c65463b5f992ce72b532bf9cad4de65f7cad4ea5e99dee795a97b5e994c35af4cfddcccb56fa66632f5ca646ee65f6ee66ff3667ecf9b5e3375bbdf5633f5fa5bcdd4bdd54cffee5633579d98999af9662653ffcc747d66faccfcad667b66a67bed99d96a55d933533373cf4c3733359b9951661431af99d9eeccfc9aa96666b4bf4b9de6b6bfcb68dbbecbf65daefa5dfefdf6650be57df93defcbdff2be64f2bed4cc76f7a5daeadd974daffbf2b7cb7533735f22f265d7dff3e5babde74bf6ed7ca9dbf777be7476befc9ad9f9f2fbb4f3e5e77ca972be84f275e54b75e5cb75f325932ffd9bf2259bf932ba2e9bfe5d97ea7ed765d79caf4b67cad7a5bbd7e552eb75d9f933afcb9fd765d3e775a9d9bc2e99795d22eee55efebd6cfabd5c73eaf792ed7ba9fb5e76cd6cf752b77ba9b67bc9ae7ba9d7bdd4fd65eee55e76addc4ba85e76fdd4cb353ff7f77aa9d3562ff59a59bd5cb55eaa5a2fb55eaeb96b552fa33dbf5da997faefce9f79f932f7cfcbae9b3f2f7f5ee625dbf332eaf36ff3329ab679d9e6a57f399b97d175b379a9d9bc5cd7bc6cfe9d97bfdd79a9d5bc8cfef54df352a77999eeaecc4b28ef4a1dd53aaa73ff8888edceab8ba875df2ea2da661751ff8da8f5ba1111b5f63bed1a51fb3745d45ad57fa7885a7baf5344ad9f2b7f6aede44fbdeea7d669abd5fdd43eddcdafb5d669347bafb59b53afbffb76ddf2ed79d75ae55d6b9fbbce5dbbfb6da1b5cafd76b56e75b4ff56eb5637d3dd5bdd64be7c6d7594b368b54edbdc6cb56e37b3d59a9999add65166d7ad8eb2bad5da77aeb6ba4d73aba3df6575d750eeb23aca5797d52ddf9fb35aeb347725aba32a57b2dad9b392d5daaf5affbdeace7f7e365dbdeadf66a65e75ba7bd7eaaaa15cb96a2653ffce974bad7f4e5f55ffad6acd547534d5bc99eaa8cffafbaca3ebf659377fcfbaed5947d79eb55e995977cdcc7ad933548dfe576da6ff55bf7f55ddbeaa927d5567faaad1aeb26f575dde557777d5d9eeaeaeebeeea5feeaeb25977159abb9aeeef7357339bbbfa97b9abfac955edb9da39573b6fb91ad5ce9673aeb6bbe9ae5c5d57ae32b9daf94fb91a4d4cdccfadeadff573ab7aabaadfaaf79bf55b6dfaedb7aafaadfebed5fe66cfdbadaa9965f5bad5e8df2d73ab6bfe7aababf67aab3addaada72366f75cd5b657367e6adeacf9f5ad5e97e6a35cafbf75aed3bbb5eab4eafd596b9bd56bf5646ffeb6ab5f95dadfe9e5dad465bad7ebfe6dd6a55fbcd6a556d33abd5685eb5aa75676a35bad4ba6bdd79aa55ddee75a75a4db5aad59c6ad5efaed4aafe59a9d5356b359ad5169ad5aed5a6cf2ab3dd3dab3a6db3eab259d5cccc6655cdaafe6fdaf46faa9ffd7d53fff2dddfd47d53f74d757ffffaa65af9a66bfe3edad3684f9bfeed69336ddf9eea55fbfdf674d56f4fd7bcd3b7a72a94f79ebabca7e9febba7aedf3dd5ecee694fa1baa74ead7bca66ddd36862eee90acd3dedfae79e367deea96e734fd9dcd3bfcc3dd5ab9a7bea4c734ff5e7519eb6dbbf3cd5bebf3cd5abfbf234da9be9cbd396ffcfd3ef79cad335779e465d9e76edba3c5d394f9d9ca73a6df7b3e569d7eaf6ba6d79daf294ed3f7dd596a7bfebb4e569b4cd9c65799a6e96a7bff325cbd3762b599eb6ecbbf2b4f395a7ede6e9ba79aaf572c9ffdbf294a7df679e36d3ffae69d7dfbf6beab2ef9a2adf356da17c4dd597af69f3f33565f23575a67c4dddbda6e96ef79a2ad9bda6cbe55e5367bad7347576bda66dd66b1acd6b0acd6b1a5dee9ed7d4cd6bbaeee74ea37fa7ad4edc7fa77a4dfb4e5bcee6b6dd69cfcc9d3adbb799ead599becd34dd2defcd547bce9ba94e376fa651ddb2bb992ef56ea6ad76a6bb997eaf9b69b477dd4c97ba996aaf75337573336da69ee766cab7cbe666ea5c7333ed3b37bb4ed7acbb4ea3fdfdad4ed334daf5aa539dea674e5bbeff9bd3df64be394da39d33f5cf6933fd3965f235ff9cfe9caef9f539d5ebf739e5edf6396dfa9c6a9fd3df731aed3da75fbb3da79cd973aaf69cb65cd973da73daeefcdd9caef96f37a72d8fb639f5f9d9e654f336a76d546d73baeab4cde99adb9cb63975d99ce6aed99cb26b4e9da97f7f9ad3762b73aa9959a9cc6962e2abec3afabecaa87f95deeb76fb57e95fe5f7ccfe2a5badf657d9b5b37d957a55db57a974aeaf52b9be4abee6e5f2554655f555ea55a97c954dff76650be55dd9952ce75da9135bde95ed5e799af2ae74fadd95caa5de5de94c77572ad5acbb523f73577a9fbb526d7357b63b77e55746b912912b954aefb992d9eece954a27e74affb65ca95bae54b25cb96eae8caa5cf93357aa3d7365d3bfabb2bfab52bfab52fb97af4a967bcf57a5caf9aa54dbbd2abdd7abd299f5aafc3eafcaaf9d6d5e959acdab52ab79553ad3bc6e65f3f7ad54fb5646997ce55ba9b65ba99d5ba96ea55ed368ba956acba35ad9f5ff5a19fd5e2bbff2af5a09e5ab56eaa7deab56ea676e6ae5526ba58efe376b659bb5b29966add4ebff59f9b5d36765bab372d5bd6765bb33b3676574d53d2b7b5676ddb65999dbdc6625e74a362bd7352ba32d4fd7ac6466a566aa59d9f2342b3b5f7533cdcaa8322bfd46ccd0bcbe4b68d62cf71a9aa3199ab53343335773ca9f39b7ecfbdfccff9bdffcfdcb7cf3dfea9b7fd6eafbf7ff5975a6f967fdf5dafc39b3cdeff3f7dbfbfcd7377b9fb3d36767e7d0fd7bfe695eeadff3ef19ca7bcfdfebdeb366b6b9f79ca36ccf9db33dab9aed99ed59bfcc9e73ab7bcebae774ffb4e766daf3babb9bf553afae9b7bd6ae9ba34cdeb39ba36d8e7ebd11dbacdf16fbb739b7ad86fa36aff96ddb9cf5ea6cf39ad9dde66867b659afcc3677cd6c73e7cde89adbac6affb2f9b72f6733b3dd8999cdfdd53fb3b9ebbf66eed79c9bee9a75cbd7fc3d67d71cd5eb9ad735b77aaff977bde6953779ba66f655ae99ed79cd7af5bbefdc776677d6ba33f36f35336b665677eecbac76be5ce6bef37299d3ade45d679deea79a75dae6a79afffaaaf96f56cd3fddaa9a5535aff9d5fda7b9f39fe69f669fe6747ffff234ab3bcdfaa977334d739a66ed5f6556fbeb95f9a759d9b95766bd2a3357dbaccc18630b4a7cbc6007afa5b5a1617073b7120df0f35590b360d62c0f473324cecbd22e929c19e5ca96e297ac1e188bc0470a3370f28cfbd93852202b3a384e408297ed9e53b0e57750ed07d57eaaaa55d50eaafd22fc7deea67137b96a10c94167ba45f0265477419dfcc9b4bdcf01d354c0e4f181176edf9eccb0218998d9b47d1655267de64ba6a9d1f5f02cfa2b6465f430e93b4f93693fd5bdab384c6aa59a6cc004c8b1c10cac9de9cf875a2f331ceac3c3438cb3781c1934c2f8bb0c661c19b0ea5e5a52c0b10cd5806359deb16c08c67e8386761fcbd030c666f858a6853184e4cfc996d5de67324a473248f848f6a6ede5ed257dd2ee42f250ddd36e7430aa451ca645b533b787862d3ff45f3393febbda99eae792ef253d05bf9b3add2c6f79aa8f6289f8f37e0cab8347c2cdba49d775cb5b9ca5e018f68631f609bc7401e0c83578049372040b00e3a876ea96e7793baebe82aa7e6a11512d42ddea2b08a67704fbe2ea1bd54e9fb5e7eaeb8f5f4370daba2a335a32ede86505fba876260fbb66e636f71d26a34a3f3f0a48618c8b5c0c8f48bfe351d1f1b807ff56ffac57fd7d3634fcdafbaf7dd72760609c05654b514b8d245aadb27e841d8d671a43310b16adad07c7ae21185f39726df13dadb347052ac6f828400a3e0a30828f0270e0a300447c1490e2a3003e7c1450878f02d4f05100047c1410868f020ee0e3162a7cdcba848f5b8730c6061080194072d999a2848da6a860a3a98c96014f00c360ea02369672858da5306163a929d8586a898da5406063a9326c24750a1b4925c2465273b09114136c2495b191941d56b3d6cb3d03628c05c483cb763d44adeeb96cd753b30e8cf6df7a50f31123c5224486e0a29020181781dcfd28faf05a7fc0c3b8d803e3228fac44c51d451d7fc51c02e0b851b451ac21018b515791467146514631461146f105560c175966128bd12c0788d0e7fb7d6f313804c39d3812a87b7bf2c3432c178d0c183a0ed8f1219d2b215722ae045c89b7126e25da4a605c748b0738c67800c6b8d816d9e2eafc9c0706367dd6d32af7d3229a3eeb691176793fffe356db710b99a4d3ff2c4957410b1eb7b28e5a9cb059857c4747c76cf7aab659afc8626775c0388bc352f3619cf5e0f35570c0da11179b788a4c2c404b8de59c847151c5b898625c54160f312e1a625c5c81715105c6c514181751605c3c8171d104c6c51218170b312e12625c2481717110e3a220c6c5408c8b23302e02625cfcc3b8e857e4c3b82802e362088c8b7b1817f5302e82287ac0b8988771110fe3e21dc6453b8c8b1ddc04628c0f613ebb41bfbf137b817f7a490e1b30215758877d24fc0c3d80b01314f4d067981768570848c2b0845ddf61b14e03c6b885282fad2838480e0ac67dbe625ccc80a783d50b561a303eab4b9ed17dbe7a12acb2566532ae1253f74caafa8caee76592f3af61951ec6be1bae7ab00a638ceb3e5f9367759fafde50d5745eed19fae46854595133aa1af01609624c35a8539da5a9ea6a974a0ba7a14a843146eb1a1515544a30c6c9a49a37a2023ba50ae3e48664928ed91ab229a2e1a97caaf0d41ec60e92746c8aa8e108df08dd084f859d4280d914b1f0e9db176f50d5ccedd3d40c693e6b655e933e43ed9abfb739d92acda05eeb65bbd7bb07e79f87c1513b4609c0388df5199ae6259a3153036ccc04868d99120cc012c5152a124ed2851293a4fb3849670c35879374a1b492747e9274a08c24e940a5c0384907ea0c27e93ea1c23849f7494e92eed30d8c93749f009374c64fc848493a4f5a9274de93068cb1a701493a63273f1827e93a1d76aa90a4e36487d30fcca904c649ba4da430de84d4844a931d9ca4d39402638c85f11f360ec00afb9ee6b79b073092e65b3a35df618c55a24fd142d1ec4c8534450a5885269c88deb57cfbfddc794509118a10301d5e52b325869b3e3793b967774b42f22c435dafb6f94cc5218a64d287a537bc00634c021b2b0dc1bfd52cc6c50768954a64a84496e6fc4b7ede56e2c2d8c995464a408c94c230a6b485f124b54911268d4d2a60947407639c96a44bd2b57a354cd2b5685ae57e819a99013d441a00779db805d3dcac9b6a9a99019351ddfcebbbd49e4432ed50afeade88b94fafeaded7fed0eb948ece27d7756b9fa3eb66ea55ed7c196d7372b92b24f59abf2062ee4b5375f42cddcdfe977c53b3254f5371b8fc6b35df5ef3e69c99b44ba6b15b33cd2af0cffc3969644486f1a24a182f128371daa21dbb75061a17fdad99d9ed13144191208cf1e1a1222618af4daffb0745878ad654ba44611249c0181121acf574ba9f5d0baadaeb764f55f7548e17edfac953bff3365f05a35a849a96f07f2e9bf711b59fcb763d5fe8b25deffbcde75bb079df673e28f7d3abbbfb60548b68359f67d312f9e7bc3cc1ae558ef7b5a0baf3dadc48f04115af6b403bcdf46ad67a698bbecce75d362d416768e8f7b72718e881d0d00308bbc0bf41414343ba414228065d1a7468d084a6cf7a776b6d6d6ed01ca8ee216ab5cdfbaf6f9f9a022400602392ac23a3b0f188266c3cc20a1be97c6ca4b3c4463a84d84847cfa8e40d1bf7f8c1c63d6eb0710f9a2cacacd4306b0302247e64e9cf20a018d82f49176d312e86618c0714c130c658c52f8c8b5e078a488c8b478c8b468c8b5d1817b9302e2ac0b89800e32202ac48587db23a140f140d64218b050864a91507f02902501450dcd22aaa7a10e36216c658ce2216c65879b214bdcaa4ea3c8f31562557adb2587d3d6dbea7a175ed4c731fd433b4cb53288f198c931a92c934bdaadb7079294f8b31e6cb764feb0f86dabf3935e42a9e6dc4d365bcd3c678478937f864febc6dd37c7ff0339bf4ba39f857651654b51d503ddb01b4f360039ada66b40d2bfb9b69fdfface6794f336bb801c2cafea6a1617f7954cd6b75bf4c359f72d5d0a97ac3aebfcf2cdf691a2818b67cff560bd22162a453068898fbd2fabbbb7ef2d527fdd6be73289ada73745df3d7bef3e59a1173dfcfc968764124d35c420ee8f3ca53fe7977932bf79ad9f23376ab38e4cb760f4fd5cd6852ed88b92f2df9551c0a2e971c4dab19a2d9c9d7d6d6d82a2479132f99a6d3fd79fb35d3affee5bacd7f7dd9ed93695de7338db6e49c1f26b99af91acd6efecbbd72ae4c6ab4cc33e896277f4e26d33c9c1b1c5c1c947766d2b36f6132edf03079e8fc3bccbdcd2987fe25f73e99a1e966731b5579ffad1a448cf1bae60c6ade30c64cffa76640318d9f24ff8ed2cc30a6e1c3382d430634a430c619d0e44123268dd9ee0cebfaa24944b3867186649ba44373468ff73ba30493309e69be5ec9d73be3990a388336038a847e7131006392349cd66a27ef19b0d1cc205fb6db0c19c6182db99389dc58e6115e961153c608c6becc0b0764566132713026e480c9dcfd86faa4867eddf34ee6a4d7cfae05936959c5a1a1a6a38bfe9df255131ea248b63b6737f9b537a4a3fb5a700d0d2d0cab308992dae5a94fb65b30d54f4367d65067d6855ffbb4c1649a6ff959e71fdacea1daf3669aec2f37914cf3fefdb967d7cd162cf8ed5d27e7cd64bb974c2a5f9fe49fcfcb9b83e48b17495527eb36bc789134abe0779ad67da966f43200a72d754f667615c659aa30ce4a8571162a8cb34e619c650ae3ac522d76fbaa3a5084df2bd8650f639cc6e5eddd031708696669cbdb0a499d7fce4b76ae77ffba1fee31db6dd6a69d79bd33e3cc35379b8369c94366bb9b3eeb1d66aff31c9ca020776656999369d1071fe6e0bc7f90d4cd3f0727c84c53e6a5e594e3357dd6fbbeef57ab2bafe0d9645f322dcdd3f909c9249bdbc334dfee34b3ef29b015927ac9b4eff9cadb36ddeee1e1d986410d9d7fa870e8f5926fd6ef363753ed35533ff5ba26bd9aeeef77ba5dbb3579b861c3a0060d8779b0423299fb9267eb9a0fb51b6d7ebe4ccbdb7b0f059369aa9b4c3b34e4ed61b2dd49bf53dd5ddd7499fc27ffeb26bd59bb2b24d35835ebde72cd72bfe4684c447e974c8b76ce6a16addd49420e985cb67b2fd95f3a0f837fc9b46f5621994c3b7cfb60d2638cf7b03100236c9ee4c44f9c375c9c3c7182c4091027884e9e4e5e9727cd2454504b9a494826cf2424e95c9e3c9390ac903c4baa40318893744ee430363a393649857192ceac427679ffad36ecbfdd7474742ac71bf4b0c50415969b514903254823922e4624714826783ceeb7aeef87233b30c6132868c018ab18678daa33e266cdcddfb3fbab86ebba9dffe55ae586ae1f8231de81f156872c37ac0046618cab608c054be0ad0e18e341820201fae3a70f1f8cb1d61e3d09f2609c85e70cc67880aafd9b27eb0ea41476b0e684000aa98031360ae162859374bc095583040a820827e9bed7ea199e9bd56cb834061100c4159048493afe936749bab3249d211e8980d07003c287b1f85fe25e0febec39c428ab8e215d268c99add6bd93d1c70feceadf6eaf0e629c452799db923a717bf7fdc9dca3510f277bde8ed9ee75e73e48011b7b04628cb9f070c2e3070fdfb5cb7bf22c49e7cf366c98d3d0e646e2fb96e3d1d119aafa4c37ff2ec473e6c10dbeff217ac89183531fceaa31e7869c1d3910ed8c3908b01a58de3f00aefaa9f95c3e4847e795461c5f461c026e74c27e6740aff5879644605f77fea0ea01a0a5f51328dc4c49a46303b661ecc89534a812138012d5654fa23dcd2708b89ee828b28a6f8f30499715146048525640da1900caa6010a2f7548e0c6a830c1868c9829080268c9d296846f559d323362cc0cb9301cd23c9a32a00c2933216a88273146483d11ae36d18992e32d82180e67262550ecc40a5758ca8060ce416be423eb0f603610630a0ee0a198dd98a8e0973a5d05661c01318913a541c19e049b0cf12dd1846548189d0e76c0f439b609c4b2c7508e067c63204a50a945301271a100e8228d981856ec8e857496680992854589227d8180fe266c09432bd3c14866449e273491b2d098044a9001862417d80a4b482ca84a06295178691a29992990a0c91f8c453120d57014153107ccc9940345ee20a818c120496a041194316536f6fca121e4e5edcab24e4322821c2a8c5dc52042785da46032bce4c18f9e570596a0d4870f8690f07020070911871343529111229324110f3717360e447f14b0c1c9198b61a06022f46ae2601496486631e6e07ddd689263ca8526589adc214adc1eb508804c029de6de93061d7dc00c9c0e8710a0ea4ab284069aa62e3119144099ca84434f1baf56322445d523868f42757aa392854ed382179014217ac326cd21304d1a7d4610b0d2956620d3844346c85c42c31ad1c0880500af6890496b3f30408aa2c6eeca12d1df8e3144a298b809a357a8034b6002394020b0e0c2734aca994e40e2cce419b5375722ee0112d5991d949aa06ee454a016e468f1ba8cc54dda448677762511717dd02460080997b28f50a82dab30ad3ef08ed01010e11132511208305e624b9630494a191407d7a66788b876e528e56d73d7502ded5e2d1fa570e563941f4728a48e5016613a3a160268696a03c507c505d5cdfad0c685a8425e75877d0e394a6c5287a954073946a058d210c44b2f9029ce933b5c06d8f924a3efc0a24f70466d08f4431ad575253f4ea5493967ee1e1b1ee0f032b50562ff85507a26f200b8e3e56cb5381b23a694985c591d16202677b03829e4d0f6a8f18aef10cd0ba5a8215ba9963937c72557524841c0624303432636e252cd31d40051ae543848550012ce12b848aeb51c961a052aca4896aaf3cc7b64a9eeef559044667dd424685860644aa2dd043f8e028b5dd2deac1968f1e777aeb46b55a5dddc5303d303dea56dd4db7996d9eeb48dfa0ce5eaca796a68e8b2bdd9b56bc8bead5e0da33d1baabacded3564b208b7b9bdea9e5639de71c92a8df70563ecc4c7251f67111e976861259d8e4afc608c93751a9249bac3efb39b4c7edeb665c8ecbba8cbefd4ce0bdb97e3a99d17aaf90ee3e2183efff297b995ac1dc64531591cc264fd65eda5bbe5ac6ed9ac423a810e196ac16ed5f04461e188bebd3c0e74856e6916242417801d15141a28dc2ac4ac4a85ea84899223448502f5b943070e1b3463be60a1f2648911213e72d89081e857ad4441628408420f84771b3010bead9d4970403846f845c8756000a1d6ab55a5426daa1489d1a1417ef0cc79a3664c172b4f961801924346b4afcd8ca2305084070e736e24d4485480504662cc8b84820224b65ea512d5e952a4468a0c09ea8367ce9b3462b85879a2a4880f1b32a2dda90d5110fe85c0ab1b84631bb4306110c22083c1b1412f058304068baf528dfa34c0a4478c0e0de273678e9b3361ba6491d20409911f3b6cc8887eb799aa201cc1b7e7a1039d5b9aa005c107820bbe04b90c080e102cbe4225cad3a5488c0408fcc3a70e9c3464be64a112a54912224076dc8801ed2c33501988a230d06f0f8f035d1c1b9a9904070b02bd028d090a046e0562ad32258a53a64a91161d2af4470f9d3768c474b922c54992224076dc9081d882859a62c4e09f5e5e9d5cdb19851111188c181b711c9160c0882c57a7426daa042951213f76e0a821c3e58a94264884f4c031c3c5db826da6862748008af0d021c35b1ae004c00662805f5c800500b700b1589d02b5e952a4468806f9c133c74d1a315db044617264488f1c346044bfbbeabfc3bf12827f7c20f038648053bb40e1c11f0331e45f02027f5aaf54950ad509132547890af9c953270e9b3363bc6ca902854992213d70d078f1e45b3d3c4138824f2fafce8fcecfcd2fcdaf0204bf055f46047e04fcb45ca92a054a13a5478b0400b4a70e9c3565bc5c8992a4c80f1c345c7c775555148ee8eb03d167d7b701831b5a853e080bfa8e7d0704f4e155a43e6dba440952a2427eeecc699366cc172d549e2c391264078e198950cfe7aecb9ca230904f2f6f031d9f1b1a85076562483e057c04f8b65ead3a25aa53264a9018211a2402901e3a6ed084d942c509922039662076a75e5122904f0faf4e8e4d841904110d448889308a40200200115815294f992431320448cf9c3666c26cb922a54908250100e18163868bd75d99ca15274800f2e985b00b9121ae2d445a08b3100ec68e07426cedbdda63856a14a74b8f14190aa4a74e9cbd3666c064a1d2c4c80f1d33a27dd52b0a034578d8ab9b633bdba3901c3bee1dd813f02a55284e9720252204080f9c3462bc68b10285c991213f76d48811f5ec56551406f6e9dd6d90734bd3b31041cf811e999e979e02bd027a59ac4e89fa3400a5468704f1a923670d19305aaa385112c08f1c325c3c3bcb4c5514068210f1c14ec35c5b9a8508c9055e2014802800020010c557ab48791a6092a34382f4d079f3e18c982d56a02829e223478d1888de5d9b39850912817d20ee3e70f890e14ded830508655f1f1410f8507cc52a15294f982a41524408509e3a70d490e982658a13254686f8b801c343e2b3855355e181c488be3dbcbab9b6b30a101a7808337a38e0618087e2aa529f305172a4c890a03e75e0a821c3a54a14264888f4b80103fadb9da628cc1becdbc3ab8b5bbb102199b7200f99c79537202f8bd529509c304d7294c8d09f3c73dea421f3e5ca14264886fcc02123fab24de58a1378817c1f3a64805bb300018f011e58175e02bc01785aaf569d1ad529d39da5478908fdd143a70d9a315fb25471a2c448101e386644df164e66565128f817e2c35d1d06b8340a49b23bb0ae3b03775a775895a9509a2a416a9468909f3c75deac31f3650b162a4d9010f9a183865df46dc132952a4e10da8db0e3d3cbdb40e7b67626c181dd8230a45d975d01017659aa4c89e2746992a3440200e283a70e9c3566be74d8d2018b14264788fcc021c3c56f9b1dd414847f7a771ae2d42c7478d0a10cac435707031db45a9d0aa5a952a34380f6cc696346cc162a4e900ce15103c693af4b25871384817f7b771b30b8a9717850c601cc988083010e023864b54a158ad3a5498f160930a88f1d396ed08ce182a54a9426498800e99163868b7703db4c559810fc13a167a761ae0d6d83d98607641bc23620372838b081c086a2ab54a43e69ba2469d1a1417ff4d4719386cc172d55a234392284470d18d1b765b154aa2821e807e2ae6eae2dcdc284ba066275c73a0505eab65c9d0ac5e91224468704f9d173078e1a325dae5081928408101d34603c5bb42cb4e10912801af83e74d09041835b1a050d111a8869381ed0008006ccca14a84d982839522400a13f7bf0cc7183668c972c559e2c0940480f1c346240bfcb4267158523faf6f236d06588cbb0762143840619c8c08c190a64d87ab5ea14a94e961e1d1ac4c74e1c3767c670b942c50992224078dc90e1e2bb2c52ba431424fcf4ec32c0a9d199d14948d22d40d221a0db72852ad4264a8e0e0dea8307031d3666be6491d2e4c8101f37646048c400ed2c862586152604fdf4ee34c4ad5d88c0200c431706031804bc42550a94264b8f120dea63078e1a3361b2445142648ec70d19d13fd7c2a91ea620e117e2c39d0639b734b30873656173c8390506e6b6e6b05aa53225cad3009622313a34c88f9e396ed08cf992a54a14264682eca001432eb1af72ccc313837c1eeae2dae42e40582007d6254740aec8aa94a74c951e2d1200109f3a6fd288e162e50992223f72c440ececf2f004e1df1e5e5d5c1cdb050a710ee2c6e2be12c40978952ad4264b90182532e4470f1d3769ca7ce172654a13234276d04884ebbca9aa803b41e24fefae2e8eedc28407706460704638037000c065b13a25aa13a6498e141902b4a70e1c3564bc5c91c20489101f3a6ac440fc6e339b087c0b81a7012ead0204b2b7b0b7ae37036f025ead3a15aad325498d0e05e283678e9b3464c06cb91285c911213d6ec8487c762753a9c2cd44a01b9f5b1e073ab8340a0edc162015b815702bba4245cad3009522311200d09e3a6dd08ce162e54912223e6ecc688bb6fdb6b6add97688627084080f1c32c8b9a5b5556883d0d6a04daced4bc18036ac55a54069b2046911a2427ff6dc99f3260d992f58a83c4912800fb61d3660405b38756653516284880f1d32c4b1a95988c080edabeb009b0036ac55a6406dba24a91122417ff8d881c3c60c182d55a230415224488f1c345e20fe96a9545142d00fc45ddd5cdb1a9ad903066b636b605c6b05d68aad4e8de28489d2234483fae091e3064d982d57a4303122c4078e18882fdf6aa96189116a7e7b796a1ae6ded02a38502b0b533baa2500400dab32058ad325498b0af5b913a78d19305aa83c413204485a8eb4196988afcb42555128e8b7f7c14ec35c5b5a9ad983b23430ae34036903d2b0548dea74092d125aa2427ff4d48193660c172b509614f181036d06da8bd775ea43142504fd40dcd5cda1b1a55d98e0800c4d0c0d9960005ad6aa529e06a8f408d120407beab84933e60c8c96294b8e04d94123eadbe6f1ecf0c4f16cf0ef2cc4f1ec0387e3d91ce78989b92b7562f3bf3cca6c5f1e7d7914cad755477bd651b5e7e8f7bb43f5fabdee5067aa3bd4cd1ddafc9d43a3bd73a8ba760e75760e553b87ae9c4355cea1d036b31cba5c3914daf41baafd86b25cf51baafb8642a1ce764399ed86ea7643a15c6d37b4ddd075dd5075dd50bddcd0e6573754ab1bdab5eb7713eaee26b4d54de8aa7513fa7313aad7dc847ea86679ba6aa8dab786aaad766a0dd550a5866ae8aa7dd6d09e35b4e56ed650683443f53343bbfe19aabfcf50d76768d36768e7bbf70c65f3b267e86f33f4bfed9aa1eb9aa1ce3543ffced09643353347f9937d57fee46d4e57fe54377fea74f3e79a53fe54f7733fd7fcf773cdafdfcfbffd7e66bf9ffeedfbd9ee95efa766e6dfeea77e42f57e463bffe94ef7f3b7b92bf7f3d9e6aebfd7cf68d7cf9647955d3ff5fbfcfab9eea67efef5cdcfeff3b373373f9b7eb7f9d9e6a77eaef9f9d42f333fd9f74df3f3b739cdcfefbd5eb57f5fff6a667fdbacecefd7de7dbb76ddb7e5eeabaebce9beebd6cff66d5ffdfeddbe6ce7cbddbeedabae7cd9be7de765fb72b57d5fbda6eddbbe6f5e57dd95ec1b5d99af76a39f43fff71bfadb0dfd3addd01ffdad867efd5c35f4ebf5f7f7b7fdfd51edecef5f75dbbe3fb7ef7fbf56be3fda7ff3bffd7fedb26fffd1f5ed5fbffd3bd3b7fffea34d97f7efeefed9ddbf33ddfd6be5ee7fcdbb7fb5d53d9afb5ff533f7dff4b97f37f7cfccfda77bb9ccfd47f96fa1fcab3d43f9ff7ef797ff357ffebde75ffbcd7fe79fabb9edfcebd575f977f96f7eceffcaf9e75fbfcc967fddf2cff2cf5bede4ffeb75f3aff9ef7a5555fea359e59fa76956f2ff7de65faf7fffafeabebfbb7fb4dd7fffbfeedf35737fadeedffc4af66d7e28efcdaf9fbb37ff6f325fdefc7e37bf6e7edd75f3473bd74dcdeae6ffb9f99d6d6efe8fa8fff7fab7adfebfcd7f5df567b3fecd34ebbf11f3e7fcfffcf5f7f93b7dfedacddfd9e6ef5cf3ffad5ef35f77feccfcf5ea4cf377df1ef52cff2e1bf52b57b251cf6c37d4b31beaff7243fdd71aea7bd650cff60cf56e867a97cd50a87ffd4fa3fad95fdfdffe7acdecaf7fbdeeafd7aceeeeeba35dbbaff7d1b67d7dfbfaae59f6f57fbfbe67e6ebbbd6aafafa66fafa0c55befebf5eafd1efbf5f2edfef7fabd5f77bbd32996fff9ed93ab7eedf47995a3ff9f72be7dfeb967fdf4c33cbbf77a6fb7baebbfeded5dfb3fa7b67d6df47f3f76a9bbff77de7ef35337fbf5ce6efd977abf9fb55f7bedf165e276afdb6f0bae7b7858fba9ed9eeee7a35ebeefa68eeaeffdcf59cbbbef37573d7ffed7aeddcaeef5addaed7e96eba7e5db5eb55ad5dbf6667d6ae6fa1d9f56bde3fbb5ef7ecfae67fb9775feed5f6e57e7db9ff9dfb68efdcb79c7b66cbfdcabdba57eebbdeeaca7dcf2bf77fc97d56b977a6dcaf79a7dc779da6dcbb997b286ffd72b95befe6d66b676ebd33cdad6f7ece59af9d9cf57addacff3db3bee9dfd543f9ea3f5f7d9b997cf5ec5ebd56f7ea57ef6abd7a36af5e9957bfaeafd3ffbe9dbedd4ebfaedbe9b7d37bfdb3d3ebc4ed11b7d73fbf7ffbaefbf6cc767bbf6e1f5dd7ed9debf6bfd5ebf62d737bcdd4db6b757b67ba7db4e9f5ba5cbe4dcf7c7bd3bbecee4daf9f2e6ffacf79d36bdef4ce94377dd7cfddf4ee6e7addeea6ffcbdd6cfae57277ddf4ebaa9b5ed54daffb5fe6a687723537bdfb7eedf5fabff6adf69def56fb9edbcd6abfaeda3bb5f6aad63efa37ffbaa7fa67edb3f6cab7a7bed5baa7decd3df5ffe5a96ff9763b4fbdefdcfb9d7a9db67aa77ee5bc997acdea66ea75ab539f7add73eadb9cfa68f6fa99dfec75fa66bf9b9effeca34bbf7ff66a9bbdd76cf6fabf6bf67fcdbee5ec9abd9ad7ecd79dbd5e66efbd6ed5ecbd9afd9afbdba319dabebd7dbb5edfce7cbb56dfdeb552f9f63547d5f6e55d6bcf3bcb79d7dbc9795f72de79ffda99f2fefdabe4fdf3ccbb9a79effab9fb9adfbfbbd3efae57be3bb3dd5dddccdd7f93b9bb5e97bb6b75f766babb4e776fb952b9bb7277b6f9a3cdaef60cd51daaf7f3d9bebaffacfbfeba7bddf5aa64dfd4ebfeb37675efad76b9eebfd51daa5bddbbda6e5677dddb9d59dda3abee4bbd57dd7547ccabee5f77a6ee2df7ef5277f6a77a5deade55dd755fb552f7967bdd3b4fcc1d9a7bab9fb9eb9ebbbadddc9b699ba3eb9a7bbb735f77ee7cabb93bd3dc759a7bcb799ae6feb75299bbfefc6d91b8df167fbf2d5abebbfe3dbf2dd8f7b7f96df1b650379b7bd4d56b62e2ebfed76dfad7d5bebfaef6dcedaf1bfdbbbf6ed4755f77ddeeeb36d3f675dbed74d9d775d7d75dd7d765b65b555f577d3fef6ed7aee7dd8deacf59dedd9f6a16bcbb6cfe7bc9bbcbea55c9bbab137777dddfeeee467b667777b5babbfbdbaed3adeaee6abf737775ea76ad42b91bfd5d3fb9ebf29f3f77bfe76ecbf7efafcbdd957337da72576db9abd3bd7257afdced79e5eed7dc65357735b37d5797f9aeae337d57573355bdbad1bcbaabfb759b573727266eb799feedaefcfbed6a77bb5deb966fd7d96ef7f375bbebbadd966be67699dbd5a9deee76d3dda6dbe56ddeee9ad3eda34d57fbdc9b2effbce9ea9637ddafdd96fbaeddbfd9aedd9fea65d76ed7aeab5dbdba5cbbeb4e5bed46ff9a5bedb6da6d73ba57edfe36fbacdd9eb5fbdf366b57a759bbfa99ddd65d79fffb67b7f9b3ebb31bfd3dbb9ff79e5d67cfee6f75eed9eddacd6ed7cddf6677d56d76b3da66975db3abd3bc66b7efecaeaa9add9f6657b73c91f35633a388bc85f2fee46b7e72ad6eff5fdef57f79e75cfd2f6ff23637ddced7dceadd799b999dab9dabfc6f65e75c7beebadbe5ad7679746d396ff96eb96eb94e5bfe37cbd3edb2d195ebe75eb9baf26876f275739deee59277bd5cf2e59277aef2ae95fca7bccd3ae5fd4d53aee4babfa95772cddcab92ff54b350f2a8567267e6ff6db5ef6f9bbe2d94b7ab7ef2b6d59fb7bcd59eb7cddf79ab57957396b7da6fdeea55c95b9deef623ead64375bbe6fe5fddb650dd7475dbfe54af4cdd3275ab6add3ad3b68d7e9fdbb6dd4e37b79acdad7eeedc367f948d76fd7d945daeda47d9ef5f36ddfe6559ce7bd3bf2cfb6affb27a4dfdcb465757af2fdbeee5f2659be9cbeafde4ac7f39fb7296e57fbf2d3c6739ab3b67d7dc39db72ce367fcbd9aebf6f39cb76fe77cb5996e56c74d52a67f5ba959cd5fdfd889b5535e2661137ab9df9fbcd7abfd968d77eb37a4d73dfacfe6fbb59b5dd6cbad9cdb6dcb9ee56ddac5e5575b32acf5fb34dafd968ef9afdbb6b369a76cdbefbb79afdab66a32a5f35bb6655b3e9cf9ad56c9b350be56ad6ac4ecceccf2ce2ee3fb3cd9fd966fa33eb7d669d7ef79ed9af9d3db3badd3db3ed662a7b66f5ea66b6e5dbb799e5cc36b39a6d33bb6696cdac76e6766776dd99557766bf6e99994d3733b36a667dba7b748df617f15d9b69fbae3db7efba6a666632df35ea4cdf75cd51beea275f35b37df9aabe7c8d76fd3f5f5dce5766cbd7e8aa5bbea69bafebf74abeb699af6ae6abda73e25ed7fff7fafd5e5bbe5767bb5725bbff72af7ab957ed5b9dee35fabb56eeb5e7bdae2defd9e57af5ad5e9b9fd9ae2d67fdd6abcad4eb5fea55f7f7a77a5d66bd76ad66bd4613f3da7534af7fbf1e9ad7169a57685ef333afd1aeb66f5e9bcc37afedfe79edfae7d5e5dfcdab66e697d9e6d565f3aa9d798daa79ed5a55f3dab9322bf30ae5add399f2d6a997bb756add3a37743b5bfdff76eabf9dad6ea67f3bff767abf9d2c5799ed76aaed764675ab6ea7ba9dce743ba3dae9becdf46ba7abb5b3d5dac979cfcc363bbf7f5938db9745e7bb7d59e895d9be2c5cf665a1d3fdb2d04cf56511e52ca2baed2f67a19d9c45bb390b2567b16aa866115d57cdc2b759b3f02f5fb36691989859f4cca2e79799597ced4c338b68e7cfcdeabff5cf2bf75b6df3ea77cbfb6eb7fefbd96efedbfd7dbbd7357fdf6eddb7ebdbbdbf6eb36f77b4efb745bb7fab53deb6bbeb966d77b4bf2cdafdb75e73b3dd3fddebb2ddbfcd4bffaaedee9cabedd63f6b9f35cbd376b7bb99b65bfbf575d9cdeebd75fbb2ecd6bed52cbbb5f7ba5dd7bdee75dd7a55777419af2a19d2107201ce0112298b2c986905b6c30f1a32800e3c1882a94b19496deab432163a844a88010d993c271e29cc20895319348073220662f69cbe68313208a1075d9898f6624b854908a81e9df09e350cad3a82b8c1452a0f273f41801e4e5604e2dac3205121bf417fdcd482049700f854c2a425eccb9b084f08de83395d24f006a469c0e6f8e654a4b803706798765d59ac219f9106484c2cdd4422f1c940133f167a4a127588794006ae95818ae2615a7892125d15052987f747d1c183e2c70f1412fad29497b8cee04e9935a184a952e54519a2127f0cc4221196b8ebd0ca12a12867ac7cc88991045906f1431326db0d2d320ad7f89f36239790009c6170cc89ebd4e665421c4428eec664047ab04405101cbd11c7166da22e3cf16ad322a729d3152d20cb9d9c52417884e02a2568f829790390508326931506a60c58f080c12b4bb890191bfa5b583336e9e065c6d2921b788b0a384d750647dd79fde95271235110d601c34e9b405033ac2e0516e82974aacc9b01566c2a11a118c13d827ed1e19d09497128f581760885979d31a9baf4c0bde10003f18f8a8fae495041410ec93c8fb43eab469179d2e54f501c3360585f740c49e0ace8940af1c923589a79c132c23d6f984892a542801f290aa87140c82c7b50485721d413e5a70fa9b8c8812b3400e6e7254d0785310b4445497be0a6029206e315421f5102497da120e3c34f480cbeb532010a3df449c3095491341e42c8588b62027304c949c010949cd790c59457265635388259fa445786025661c913a6572ed4f818fca812a662530b266a87441ce8834ac49730b746b4929b1392e23ca2e25641235078040297068824ccd82b4b7e726af4602135d023f185a9300691256d25a61cdea4576202b820ae31f6c9f2451460601127679420b748a01d323ad4000e0affde7d9dfac2a59517a1ef45173e257b66169a7828124a4c0a2b6052362227307ab950f1113490330ae7ce8cfd469b26d94110c00fbb37aea517341560ea50fa94c909a22d75f8d4585d24bd2395d628973aa812a52537898074d527e01120e1a5117f8e04e93436c906571f9790088fa8c6ae455e2fd43b133706694c9331f0849ce6193a401f5976107ac9d06b63c7c8c59fe490559e0a27d6d8d526e508a03d35447d1df698fd41e8f12eb8f1f68092163e1f600025d012230a9ea7af628a4c6892751a7c2432b0c695a540a1ba9c8410b47329495ae5a8c1489f4d07e690a1e1adc950c007212838d0d8407a9418e8ab8713236a19aa24b02457a8a74c98928371000cbc12ac6a74c0544701a5be2cd0648b945b28d09ed83033478d8107608a6800f35a20f722428cac5358870332f9d0b26684eaa55a2b636b82eacf2536c7a1e484321f594a89e9f93324f5a2caa416844b108151e164072985830c537cc8ce5040747134667ab3b988f9591a0ef83a1588474f2306766a0a9e42beb84a0e84f291c6ca0f20ec4dc215543dd48448054e1c3ab331e43da6d0b409ad829c4d97da305d6fe2105c60b0aa0b51cfeecd4b954a22fe8e40d0cb0bd3b3088f94370b10d0006ae0c0a58614b963a1497333ccdafcdd39c0ea559b26650c1bacd9a4f0dd1dbed6459a46958a812bb1ec6008866351c5c7113869541d5a5282ec33e3dd54781a4003b40f9e8c89c1916b8359293a637c74da938a0fc2c4294c81582b059f9e648234a9111d3e1b07bc8ce243f2f113c03ac9c824a689db3c048a4e15ae29a5d41c232c05e5bc30d109514e1334634edc9850435717f827803a5e41702a085e21d640e031d28a11a22dcd4706dc54d5e81a30210a21ae3e5f1f1c24c75011b2aac9820e757e6cf875bda9c3fa3380965891225e7e5cd5c1254ab0d6aa8981a420e47bd7e5f6512f1c3700491246c358072e15801660ea520891019f17122c9d99f27389158cafc04f8d0ec3a6b0da54f6a6481d0e247cba188dfa2af30153ab1d9592686193eb220059000c7aa869e1d5ecd8389e86282f3bbe1a85b5a948a590f0279436851504146bdcfb22c1489d179b0c50b861079081513dcaeec050d111252defd1952974943e0442c19a3ece60e09350b434a24fcf99418c8a18d9105091c72d7392e66fa0bb9060efaeccd4ab4c544669a4c49a56668f60dc15c150a9c8f425859fa3639e566dc2d4b7404734b04e85300c545250884d191da08e2206607e801195bd48111b91e9899046ab90301f55e254f0835006c4670386650f543f521940af494426645591bde000605490064e0504cc4ce41111e9c8460e38a0a692d883ceb2018381c00ca80e3c0c55f2942117048193162a0fdd9fb3152e0f9ab94a5c5f06fa9498b3aa15e001ea82800b8768c4dc40ea32072e5ac9d196197987bafc01854c2246f866218fa6109eb09c1182010a5c8892baea9058dbf0087e5031fad3d0600c05169a2d128977624c0141aa48d8619b971713545a794a39253404a8b0fb223687eb915700714f16187e79db0263be01f336c6ead30850ab56bc7291a3f5a1480d46d22b89044614d464b4ed992bc12389e233a3cb439a4938e22829c5e2f932a14fc880e5022a710409f00ac583bd3a498726c5f088be16f8c9d2b5c4eb0c00aa992e99b459b42bcc214e09905d2aa2d8a8e3668d261d46c8f87c1cf7d69c512a2144288f104757d87c55a1f21a324264c197f75d3d581a3d46321aa71ce165d92b2462e14d0548783090a9da3f8e6030a0e5c702e891046d87e880a5b139e4288f861c875e18726941c743d3e8434f974a51d2285dadc4485000f4a4539997ff814e4914750023a5481c80b1c705d81bb3417a12c5ca747051e1a84a12434a63ab360930ac02118290a73131c8a2cf330c9476280941d5bb46c0c8b4274f85379f8e7deb58e6216038457bc0092054996e50f65e719d8c8d494cd1b5c6c9ccaa43619556948d12e37542cf5e961384a7d41e1d07a85489cea83c3d29749cf43c14f941a641a329ad961ad60eaff8b06880a644346230fa423d392c64bac588822ce4494f11a4bae144a53ea39a52ae2c71c193c0da7101b24d02acd312c0a9570c1c041a11e16d4c6c001662ac3a708ae30a899318993074424ad2b103b9e74f0fb736e3571f4eab227099595747ed8601154831f2c041e9d134c60604f2088c350409068b0c2833c688cb2b137a3a6a25a7400b47765284e885e9d0b520c19d18ea9229a4c660f571604de0052b620ce0432764eba0d1044c9f4915b09725362953f4743684007d49e09992b8eb06853266a4a0d0a3c5caa31b2672ccc06402ae701a74c38360d1145540292b187f39d380c24b01a5850b33805b440031eefa84c74420182280b01ad241eb77c0988ccc2153617a1029a1955922850dc6f8ab14a29d48520ecfc0310351468f0a55726362974ef519c490420ac6b82c350b55278595385dd694ca603574907212a26cf9609505334640852c7c4d19c3a08672c710303578857b815a14b468863155a06872129aed27dde59ea7afc6051d42323110d44453a21745c444973a8461a1f4e0c31e27875f701cd59c39b3d57e9581a3d500c8a71d03c44408dc236583090c527e0cc0c8d00cb648aa2e2172f0820b054c6a0d8c34d67688296a51c9d50d2c4c1d905f48a5692bbeadf161e1ac42d6102363887851454fcc24d1b2f2480a616a0617a84002fa04283256e9d0a44f805e61ca10cffce850a2d22191ba057358a69f9eb7421c89a474d589ca459a41e62c35e1edcb8d1d0643d69c58ea3066848ade3393518a61b2ec8f064374c328e8c60c16c71d1db01846628c31c618638c31c6c62e5f3feaac5ee21028914ac4db0d2553c048345223050291a73e61e4a0605d81c3a7ec8260865026fe688ad1640b470d05692f8c19905bcf4449324292947c8b3d1809bef6281260284805486d039001f635693482ecc59f1e8f3165be5e5549d59c554877fd1903845081024f867818307033c54e832fbb32505d8a141ae0549a2b589c7ce0282c18a4d743d01d95de2b42b804b8a1d3a24b09491550e04c903d3aa22cb4e052e9f06a93a40e00204a512e0628b483030f9a6981051807f6dc3661c6287d3983c50889495caa8250f872e9ed7301f111d94176068c1706b6314b0e8d52fe60413aa3090225b1311d31ca5ea901dbc3778555542f8c2071190143284ea10440a4983df130e044c0895cb34807cf43886d809b2128962c06f088651a6126620d9e37213aec39320410901a2eb6b6358106c9e2fc9a305a25b6d17043d6c968e29941c09b94f5e445a2380e6688f141ba131cb52217d9c86b94c0d300901a48013366020562ac2bc2d7d1c8c54ae1d14e02e72024676e083440fa6ce282254dd819a3803babd2fc50a1b26db8740450653089700734f10aff6fea8248a1b302e3d8f7f40ae1258bd662ea433442042c80c29285983c291cc4e1e896088c4513548381d9001a795fee0449840195e945f3a818155d01e4c3a94914049e41a84f049f4b604312586995854ea1123953044482f2264ad937fad57591e91403d41e3d25785801c6a60ff7d205058638218c3f862fb422806581e96b70a6d01e1f67651dd2a0d84223ab632212b38a5e9b4ea7220cb1c48607b14b55d859073c08decfe6b948080732035f8850e4004f0d213468002411b7049412907dddb92301ee0520476ae28c0c424d067505c9d044ce6e4728e32aac8b539a927a85e25363abab0e1ba91514af3e8d78b859d0d63dc841f8b667a26eabc986425e2e7445b8041740a9485f10e1814003803c114dbcda0674508d2aa1a82f8d97bce7b582c6d89e2c65d2aca92998222149b4c909340fb8e00c101418a36cdb4913c30b56d81801909d7c6831cbacd881b3b4a2106bc5a31063153add9961094129bc156738cd4173c4119709234d5c2115d68458c0562695b18c0b92a7a2c34746142627535c254f115ef112f381aa0d12a430536cf081b105eeccef44034fa6382441d48447a53765c71ad00cea58ca04263897e6c4a5996a3b63ebf1242e4c94980c464a0b1b6ccac04953d729cf4603b51489637a402a6d3904a311348f5839c258e207902d01668046ebab0a59858e30159883ba03cc222b823b36888a9ac9c4e077f8abe3b288485f93855f98b0070f8e03b4b0e5e8a18789a07cd7028d7b1182f1215497092d71ce8d68000810f92cea52f626c71fe59ca21696929c1274a81511422392df4145aa03935a0c559372ac483c04a6a10bf0bbd99156ad58d3848229311f6506bcac10d974c3045d5c132bd4a8289e8c133ae560e4d696c68e120b4b6e0eafd497182d9988bc7aa5cf24d2b0c321605014598a420fd0c878c5e1812207442e81d094d6c5a6588a8c82573ade3c6656a97112a5e2abc28d510c0278b5053150c1b02a531aa90564b0a85d9a7b85a76540013d8993e8c674fd6571f48952941e7b5b02def0fd51c37855212280b3918e95d107bd550a7eb368c18167083c85b6e819c116e9d0833b8de0c2b4c7939bbcbd3f06fc321971848d087a3970c3c249224507bf0754d8203bac618eb1b8804380853280b98b88e612404c73b86aa07083b0488cb1ce80020786848628425209f58ad3a0145f7efa080d90260f9c02b13e606464da2a43262383b022614d562057833e05a18a42420572e2074f9200a67c2485a460a73cef09cfc62b5475883c0689ad656a663a32640043ea2b537c87bc786800e3818d076c4a45d88bc16154a4038584473070444a995114f7c51326542b006890842da163ca3f5070c3048b29a67410b09281811c37330a3405a0f99d10bf821f04218e7349401ea29c34d08cfc762c91b1c32f4c4005d50bc7a3c021f08185389058281900400606221fc7500a267039c311e5a932da0914040b1a4ecf2d8816c6e84b3ec8c09ec50ea866312278084e459c43344a658237fc2ac118146df6a173466d0e430ae360db183f405436499261624c9d0d088bbe06e0a0824a0d115814415a2bfb41899320b22a42e00c3c7834640821ab08a0c83269755dc2406342a93e6327e25424f9327615bf0cf34872cbd445025587142b46488ee02d38b546919f48084c9c9112a5c1f882ce4857a2a10ac903030add8881691f40ac3cf152c4a3ad8282026c97062a36786a81c0ce82bf4d31c818ff10f0c2f416840b82116f5d5da3d4d444c891f045cc0c4516f402e778117255ab18885113239ba2e02f62b618f36220d59a4b29e820a57b0403bbb0fab8bae87881544b65a50f1415868e21a408421eda28c432a042e00a47a9bdb0b23dadb103c531907a98ea8a41a1375911a189072e1035fe2ea051c4c753a22c53c60e493a8034a19c64caca903a2e4106f1c83f96f889f1364dccf499e9f36ac3b169cda738541af4e152634a767078420685a51667981e47482188586964a6d19c10442966787d68c21095c525384e3a7e2843a8c7cd8883080d0e54b24144c92225618767891da266ecb1998010ddab1d51146858e0a729624da5b03b638e3d7158e02964e0419c1a2bb4a8601098a4c20b350f883afe6ce24a5381df380a4ab4ef890fe760a432129e281ac5034c4c0eb0295a9e3e580883f4690bd0759639c9f284c707334466a28069c263e5470e238d4c74de3e8b2608a2d48a7233019121b007ca51c70d82c4e0200b11c6b906440f99a655611225b8d1084baa0285075663e88c46477d98081c0960723566c2c01b4f77be92189972d8a7d04f6e25e593df17164fb2bae81e35aed0e224c22070e141893bc72e2c2b8323944649bc0491c0a614d4c5d91717001294e408e04d923161ccc80860e2d40223033cb9d232e7a5159431520d7a8879bb34e0386268066165504350a200d080d9f3f28145159a512b88091070245063c504a3314f43d6a8624094f8e46519b8fce306720930e61f793a4b72a44f9c51333a2cddbc0a64ae435bd2bc71b1210ca843401972c6f85291383028c1cba204283821aa51c470b90b04a06026c98f017aa97c9f0d9400217f3ce0d362ca1b5e00859c2038099cd0110b7301468202677cfc88dab2d3a9089e282e042ed22ae041e14a0e185e13a40b16502497a254644e8972e59621692862e2c14bcc8b0bb1c1994f700e44304105101f3ad1e0c420120a559c5f84a0b9052a6119087554f21d25c203812ffa622900d08c2cd0b01845bcfc4e79699084918c1d359283061940896342e54c120a300819b2a0b2656bcbf1c1a4e495313110c1547141e5ca962a3dc03631d112a8ecd1d722be133efe9007d6b24b3404b32c91703204528bb4078a14e8308ae12a242910091a8b7c607123409e2d67c052780d58846761d288b421ab010c461e94b98d89e02a548a89a5c4371d09004865c1c80154abe9467446a0f5d050d3ce2a365f8138f5b8222189a4228828811aa6e8b690d9c0f4c5af6da5284fac5004a5327b04bb08b97e466cadc8c247c821212da00a435f200ebcd21fd08c78e3e6c76dafd2bbb72091200e1b94a497ce66c4e940a6a248932e660d0a3a1a6ce08c04417774b17131c56b8e724c1353020384ec1c61f16536c663015b1b1575928382e479d9d86952265fe07ae535b684548d541ab61861230990ac8dd78645b9438b2a697c144a35c0122b10348d0519926a489003575a3da8548ce0ac12d792b126aa4ae4c1a4eb9a6c9831408302649c7c87ca532a491b8064254816107114fb8421803416d49808b2764744cb014788b8e06cb00a4c2c2387ea91591302030a6441de555ac5143bdc3ae56506ab4f080480628250e83a405203c067e4206b03d9102b0db4e0d033080c0b81c0ae01eefe5431f3e7b847f0490d3079ee1412c1b6474e8e1138ac8d961b944e44d3e895b9d2b096c55207069a8a6021630b93302601461f0d831e0d390636c932084a18b0b5c2368a80e285b1300a7744f00838d188151be2431e1ba52431603bc6c9e9086524c30e2789a2ea2813c6475eb0269931c5ea0a9c12001190c2c2c0901466a150beb50154830d0f06871e9595b9e0444714dd1c4d7420ec0bcb1f47ae18599f0a91baa66d0da183364930473ac0060842d557874906b30b17b34c65860620ebb3157708241bda21316246292104842e5794ec589548c168872b32506f0afd08bf623831ba68bfd15967e58cd317079bc818e0818d85e802138000c11c85442c965d4506f0e4b621c0a47a2ae08561ba9be175ca0a8b033c1c9960c5a64d1092836372c10be486102f83625c648293e9d0008599bacc10bba23e663cf5b5808ec831e250282f7f48c8900d54cc22fd114002a64b7c5fc604f971fb7e21cc98c00ac133244889b8e8c89052418c273169a400c2f1e4c2264cc8510d76b439b290c2128f2c0a5acf4b92c6d430b1e060e49362b0e94ecc1d23529670a9004585e9664f58008204c0b87851e910f9ae7db0baac5aa186829fb1b11465886245f2b4a2f3a52a6ec9162729655484430d4e455bb5395153e7159cb44c7bd43c1938aaf2b281c3945189320e391a01e30da93c61a90afb02f5704d34f063e48a23b416a91ac87d7180c71ee0cc386b020d2202821e6bf879e31381c21d29880c3930d2874681b054861dab2f6c690820d86396ca55ee63f0c2d0e822518c087d4664f888305249392268e304c2f43bf4ca78a7421f3f375a60832c7c70e73cf80107d1b3bb7ae0dc2abbf3131d08e554f3046450c19b2e5b788c4d360003be26b52a1536c8e3500f4a4ec0eef0f4f8903579d3576c2f7c684e6c94190d8db1aa42c70b20535a6086188a558c17ec0c7174c984172c13748234b141e5c3179a3367bc0c41816189716bbb537b242253d30727d27022a343c6c115a1565f8274c179530a91cc2822a946647985f7e847f42b8ded8720897942b174bd421e524bb280cd80403f923831e22504498c4506dabce81af44023cda3398d8044e128d0034122cd6b0be1240987baa0984468018c99b0105d70f4c2240c5f824c4f12cc9d8af4e905e3e22422d217213c46282b5062c2e9c816352f0a2b12251ae5bcc2a60c198d2c6d8e89632de2842858fb20c4cc901295ae2d894c70e5a023b6639c89451979f220a2008539a4961ea58ea3340d12050e70e8899f3102c408090161468008980031adf079e0e008041784253f066dc97149c278c27401a1890f792603d4d6871b0288754085028958254862725138095032a1802191b8ee42ca0ab0063874601ab041edc7a9f78682b11def9363990f4a4f70a4666c4be7e11ed9ab112625f02c867b648203c14017fcb0694289819e651a2807d8f02d5ae8988a33c01da3635d87363a7948e1e04320ba482ed49e3d61078670f0d1505934f8480478c793860c0c1498f9032cd176190003483db5fb8430ee1134a6885c1e9f2c7d1d5e8d51699000ec459e235377bc5a8d4812d92029041642652808bdfe13c58b2bcd3341963c01cc6134e1f5a7b927151aa6dc229acc0a1cfcfa84a2031d56d068c6f0075133ebd200645ddab846b53952b866875ddf5d514a952e4daf422821514758a85405417b7779cb2f1da76c038b1f5e7688f25a288a083063c1abb44a41b6d41075515370a7437b40ea9201c32f519dba4b7c5a6d46cc1e68fc78414a41250829048730dcd809b46355190b0addfab48de152f3e442a9e5203d6fa0747dcd58f10a0c17afcc103dc42b7bdaf04a917cc66142828b8e4fa11ac14a86228c95a96084cfd034b8f7a21396e62302675c6152a5b823a6c8ffe209b903abc71d374846a7ca9c224bd3a5682fe182d5097a61cc584a0a294a950efdd9c1c81a4d43a5b84428bab12401df0ab568c6814134708c215704b21427b0409e7644926b4bd2a543b1a70d486a138044f72c3591009c3044d5c0508443aa40ad2172b2828e1881b553a68e959208487188418e396d30fc5d81c34455982337794819c052a15b00a08a90343b350dbc2ea40a95400899206678fce8a9084d38b2195ec46d110bb4644d8053791a412811a6d703459ee0219e0c1806de3585ced2189cf022caab39376898c24fb0028853188ca8bc80f565086f6d96881995ea4cd50d303488ca926b562c4d33cad64e2162905066e547a526a63c593a1901061c2188d69823518239431a65196f5f80ec4870ba02562188b145a963141216198d415d9880c8448d4992a09728413827a5f2e1cb6e8e110c8cab0d00542e6eee9107e79f01c4be9e308153dc23b155cce37508cf9cd490cc03167b6301970f796a18f121b67023d7d52559a91309e1d4348a87823e20bca87c790d502604ee89004b629c8270c2181958d024554715b5f4a290e5a8f0254ad79f2e3b060dc0244dfa92b628a468b5e14c7a11d1581c2d4251c28d9f5ea03c563c641a2b1587eb08a0a8b44d9951710d90422597a2242c540426512158566192202a42285e0a5ae591ca52f796a5921fcb646af53026c80a09dba0033406391222a9ca1b00845c1f8623480fb20c826021da4900be810449e40909c24d23ee384b853541ccb8d2460c8d16172f5826c4e111b5a28689c34016b8bae868ea8209d1240d852ebaa08d68b8407eb95d2194f467511fd6840002149aa2486c819c205e98d88d88a127468408bd1e7f0d9462c814ca1974c5d1a2462b188c79a1747c02863f13d900808b963d6c0809f292200cc79881c12055f61c096465a6e306a22e06580e839891e16ba32a4b1bb70196070e1d29b02ab148012ff5428b00b805243d02c042ade25c018810c5a850810e410ad90050e629318421350c3e15c9e347d0984e5a7818cf1c68ed8c3a9268e32652167fe34108a6fcebf1544b74c18297b0a201ac2e5dcae828b1c73680e04a153228fea1f375c4980728c2198943496b083b9dc3257e0cbe0e617d9163b1668c8217051d559c2957c87cd408c3e181793826c9f10fcda71e09ba10b1fd098a3874d2ba5abb089140b3f7c5d7aa4a050673757ac001e0ebc326088c1644aa33204a021e522cbb8351a22b9c0875ad1328400b9d94b0a3be3c984c3345af3c6a6348a0d9684bc128b2698edd14ad0573ab9e7041ac14824ca35358c61421e0a1abf8e4ae878f2060c2f019d393b6ca431fafbc1b01586f9036007d4f5b859135f870dae1211d9d77c07c95c99c66075effedd735e31d2673520b49c3909bee3c4563b63bcdfd6fbd6db561bcc586f1d69a1ac65b69186fa161bc75b66501e32d338cb72a60bc45618284321a6418d72531de8a80f116048cb71e60bce500e3ad24c65b0d30de6280f156d916d982b12db130308cf180ad2f8cf1d671cbb8d5b5c5b5a5e0d45602045b07b60c6c1548b54580d5d6802d008c44f9102fc1d891a8bf18dc6ea2ae0eee12893adfe0ee90a8bf0dee0a893a59540f323ab891883f10558c44e4c35f655c886f8808a02ad8a26ae0d6e0dad8b60460bcb585318e7100898886480131893156dbeda4dbd2da2a6e656d616de157ae5ab15aa52a15aa53a64a911a55a6f5267affa9b70f769b9343857c0d7c3318638cf52627c7c6e9ff79549130c658da997f5c24afc577c002eef824decdcd10a8be034e87d40dd5b836393d7dae3768a1a709c618cb90ab6c18e2dcb4705b8070414d797b13e34a71cfb6fa5c22ae0decb55ecac1b9b159e185f88badbcb9373736253c02c6980246b6ab5a2b5b6e74746e6e6c3fda1410fc6c9db51b30c62d8fbe4ac3761bd2d175fed6e6c636837dc1a260b5b4f858bfb6c6c6f6572b6b07ad076b9239b82628e78e2229a41392dae636b635b614ce39168936219d6a7cd96eb7b5353640163b58ac4972c380812c6f27daffd0e6d33637b72334f5d8d90cd635a76f7167cd3537701892482620262256dc1b063224b30233811ac339b739b201cbe41adb1c2b2d4743d17a45049a4b37649b9303a56652f9a8582a224338b94a2a19c6b80c235357a99620fcd3d175fe0697282d912a61a41d7e9ba186726e717e945fc98531d6c2522e0de3e6e4643d7950c3028ccc5c30c6be4dce8c9bb8b838382d87eae181b343a58e837362c7b0c403433038ee6d8821d70a2bc815aa7034bb86fc1bbadd58a161c5980a3b590456b0ada0b002825639bbd4dd50eba5d77ac9d686a8e2858a08586b6c706b8a46296e7cc21863c161384c1101c52514431ca540a610904ad572726f8328e690274c9d908231c658282c205120d9ddead550fb9c1a7edf39d4b36f9b53c3b837bd131d4e3c30e127cb090c27d24e5c607ad6aded4d8389d5c4801f2656c461705b2ba1a5445b220c638cb190259094108231e6c2c81232d69c1a1a7ecf5d39b708c8423d852bc6186315d281930b2bd422f4220787244c22090f2c388024c189042124891718638c8524f128cead8dae52223b3f8379828730ee430a222205f93052f00d7f8dc911d40a74830c2481b1908375584ece3014c65aa030c63158890acc840c6c840c3c840c0c845dbfe3e846c4c9d36e7410d00da016a00fe4df21d20f0dc65f7bff6908d28f02365c73d7f2a4d529ebab4f0392cf08728f0fa9f78604110573daa469804c982e59c2186bb5b911d2aa4409bf059a24492b92d62148186b09f2f3c851238cb5046064b844e60941e6bd48d22d529408d121010c152234485020406abb45b8b9d961b04bd3e2b3474f1e3c5a6ff48e9d3a8874e6782d3971e00c40d6fdc1588b4917367b1bc0361c78e3a68daac5668d86a5061468ce982983b116190c4430e46053448cb55ead315a4f315a62c280f9e2a50b97b737b854c8b8191863382c57ac54a132454a1455afab1614ad274e9a6095c9123aba36b7a79612ad245a3f2a3898eae76fb71ee1babe7e705a3fe030d6f2e1a6d583c78e57abc50601342860e8c3240babcd8c32890608a0430549095174824376407072470efa6880bae16ed431ebcdabd5d99f39715c9a657e50000d3a32e1122a539900bd14e744e149c258abd3d1bd69e9d0caa18543eb86960dad1a5a34b466c03de9e8b46468c5a0a37bd382a1f542cb8556a256540b510baaf5747bd2d121d1da906814b0d6abf58d480b0830d6f25a5dab85f7704f3abab7271d9d1bdcdb938ece0deeeda9eed1d1b93d55ccc9c5c1bdb9b56d50c196cc804243daed36a87b1bf8928e6e43abf936b49a4fb3d92fb75e5d77e1060da9267c97aaf7762ae67ecf805195dffd9e5d752faaf2bbb936b677ee55b1d616d54e756f50f7dadc5e156fea367c8b7b37486083f5df1daa66dd956f716fdbd3d0bb1e29e10f29c10e2961cd2c495721021b8cf1dc9b6a42bf5d3bbfddcc6cb766736bb8baec464258e3fa4128c7434210e07a9c95857cb0438f03f2c15103c6b80f83403aa081311efc5a10991485f18c17d845b2c2cfdf37180209069b537d68665841b35cf0f0a9bb8b5480550059a60649a6862c89910b1260ad382d38ad372d37e458581429e60763ec22c5de9061a43046545ba4971724321312d98731b36dfaace7ff8e836be3d041ad368cb5d830d65ac31863a961ac958686113dac39e57c7b827ffb9604f763f7f4cfed5e57f39d9aef7cf3f77d67a7e63bbde62fa8fbfbdf0feb9af1a27f0fc002b672d7b97b808383838383837b7b7b7b7b7b7b7b7b7b737373737373737373736b6b6b6b6b6b6b6b6b6b636363636363636363635b6b5b6b5b6b5b6b5b6b5b6b5b6b5b6b5b6b5b6bc3800103060c183060c080616e6e6e6e6e6e6e6e6e4e4e4e4e4e4e4e4e4e4e2e2e2e2e2e2e2e2e2e2e0e0e0e0e0e0e0e0e0e0eeeedededededededededcdcdcdcdcdcdcdcdcdcdadadadadadadadadadad8d8d8d8d8d8d8d8d6d6d6d6d6d6d6d6d6d6d8d0d03060c183060c080010386b9b9b9b9b9b9b9b9b939393939393939393939b9b8b8b8b8b8b8b8b8b838383838383838383838b8b7b7b7b7b7b7b7b7b737373737373737373737b7b6b6b6b6b6b6b6b6b6363636363636363636b635360c737271706f6e6d6c7d6bfe9db3ec9e09e40016186375cd5a5077401523b75260e49619466e7561a416278cd48283915a433052cb89915a2030524b024616556164f1104616a56064f189914540ec2601a7e10817701c322b094666cdc0c8ac6618466681c0c82c328cc4ba849158713012eb0a4662e1c048ac1523b1e030120b8991f8d5050a18e33131073ebab67f5ae57d7af7c1ecd5d483a087b4e6a067fd6ea65dabbc4d6aaf5b6d97a66a34fda34abbb4e667f5df7669eeab0355dd42b3eb936b6eed5a3bbf33ebf7dfab545365f61b516ff5e7e66fdfa4eef983f74be63b2d338cb52a60ac45416b82de7d70cd2a3c9cd81334ccf390f787e7e7c130f02f30d083a1073e12797f7d8625ecfab257534d47a7f23d3730f3a0865413cebd1b58fdf7987c55f774bb99ad07575e481d5db938b64a91caf7343838438f971c5905beb13d37da1b1b61f2b8ca489954667c4f534b9534ab8057dd0f18c058ab8016012da8c89486de6737b79bdd9d7d5bcdfa0c4d723d8cb5f2cf79dec93724cf34e49fd55308bcebf3cf79104c6d66bbf977a35d377fba574196f70fd14b968ea5a6605cac44e94fb35c50337a4b4b4b537b6e9222310a51a422245c7ce408e36223466d6eaa73cdd0e4dfcb6452a7b92793edcedf99d764542b79523f7757932cff9f2b45013a8af263969696562189b33c04a6088504e3e2a1232827388c9a9a1a90232807bbbf866f71efe7abe0a71dd8d07573f79ed6e03f4f737f2be7dad80c7dd7eeaa9b6f27311880438cb1097cec8401e33409cc7637bf61d72e89016c38fd618ce9e8929d2f396d3174bd218c8b8508618c19609c05e6b8a9a96548f2330ac7263d52647871d1408397ef26e3a9c7121f0843cc64dcd18d686b2862230381850595b4840c898168901299a0be2806152e7f213aa09814837524820b0ea01843ae92861df87512a348d60ac3925362600032a22785d90f3e184aa002c438a82b00232de294339624030c35402fcb21a150bd488580508d2795e7859d025df0d4b130f762c8001ece4083aa5e98c872f7214c87a917117000829228c0d00528a78131abe6f85cb0c98364e302900f2e70142f65f1a823e40230d02638ba248c5c8c5df2e0e0948915899670682d38032123718c5cdc0b08ab4e74e1a3c7e1dc179ef88140861968853f12bba4b05af6f0e2461bbde529004dec8b6219122543113d75d18e804f4d8b14b6a886494719dfad46148005777e5298c24224f402ce0db18289f824801d65b8ac5d41646ea1331b1ca506b1ad931e075c7452415ded7578c3042505540fdd923a9c62888106a0443a79d336016a581c445b65c3855608f4f4a1e6e4c47394a20b547ec0f07ac699038945626ef3e4c1b4e4498f9fda7350904109291b7e3c937731bef0b93af46a2ab69a84e413ea3513c8951f74809c7706166da008cb7c4354310409308c81d7ebcf910c1f47c39774604c1b9d57e9ef028286a6414aa0fc971020123f31ecdbbd2d1c99d283fc063e912129c492c31f2a41a94b3450cb4f8984e6419e2fc8af4cde68f5411a3e6ed29a2014cf3f3ecb0f887f6168a0ff09e162932ab62e3d49bb878711523cba320e0053f04c19ea68ae7a9328a1045b9c7245e74b0c48a31675dade1e2522a2d422081a36ad889870b62851448dcd0e49d80282160973b0a32ef793e74dde8c0ce0ae319be05587cba07b23b929cfc90e3c17d015abe366869e143278252748feb05f7eb818181e5a4430d6f144e58033026c18e11e16d059703a7972ae49463cb98171d632d27200435586d45e718229c92a1a32da16210221b1622e69337061e086d685b4021c8ca1d383ce017690cb3a19fc6a5384d22576b9a5cb9ebbbf4d6c9b0342ba261ec2da2a0bc210546ae4d63c78d62243d83099cb3368556146459d20076aac14eac488335007e71c95d56705572d38e138779090c1d6e66ce314842a21558170cdc922d326ac391c400ab028f3e90d4dceca0016322c25f54c08f02cf6582037983b3d840776a4411f4d29798736972424047fe842ed8f290c1122e589dd414d242f5efcc2ec027398895f428c690ac0c4b1ea8d00279a531a4051bacad36b3a9b709b069cf13182a889b5a350e100e891e92701835059226c62226941e604a7321ecc4c2a481808210c31cfd8c0b0b4959763696a55124d2c9ecc2de92821796a2326d512088b57950c69085916eef083860ca003cb07850ea11262a04e7b6a00e7440cc4acd588692fb65499b40d1037b848e5e1a4fd1215f21bf4481df6e54d8427044fa5f4e654a4b803716a182426966e2211aa510fc8c0b532886a9eca750677caac1925551489b0c45d0757a99532881f9a30db52574800ce3038e6a47432023d58a21228dd6ad322a729d315a5206593070c5e59c28694da8ca52537f016255b5ea5a7d0a9326f07321e3abc3321290e65227ae0de708081985390ca13e81892c059d5391405f7bc612249d6e19b431d7e5ed274501873187885d047944052876502147ae893a693212647909c040c43199601b3f489ae0c05c3183faa84a9d8d40df5dc1ad14a6e5018ae80a447e20b53a18c155b0470415c63f4b3e26f91403b64746845871504a4b0022665a30a43330ae7ce8cbd2aa42cbda0a9006347058bba487a472ab12ade0a55e03e2e21113e9a820fa4314dc64014298ec8d06b63c7e8458adc26e508a0bdb51417fa50982aa6c884265987628e931082762e2541f1030d6f4d86023f5010a2787082d322e5160a342827d2782d907b1121c6891a6d27fa4e8051e16407298583894926c09840348137c74402b812894a5429c14b607096d0fa5428e847e1924885ea8cc2354255847742ac81c063c415211142144642097e5cd5c1254a402294db47bd70e440a20d891c819f1a1d8651213182d1a8af321f329118bcd428ac4d452ac52099bd2f128cd4813108631044d3c7190c7c12068d6c08a8c8e332107464ea55262a930a66f913f407043780f9014654fe2238204260a1b2c028813bf02d108fd89338c2889e1187ec080b28b0d06c917001580a500e600f434012800e96fc6dfa43f3f547e38fefcf6f929f97037ed03abf3bd50f411fa23e2a7d6ddfdc5f71000ea94af515fbd6b1cc44f80295bd575c270bf894f0a57c6a225289b0731431648b8813762222000a012a049b1038428c5890e04e0c9549883d4b7e6f4c8abd177b1ff6f8f41a45f5b064bd8e49afee8a1e002008818002a2490984db07571ff47c0800ec83e107334d1e487990e38147968741380f491f16ce2a6411c9d39477266f469e883c3c49785df09e78767807ee0edd51b963c78c50c118dfc9e93d33618cefb2a4182631c6767ffe68bb24886ed8a54641766937667440c5a2439d1d1d8474e860a243040ea0700afc00e3b72fdff62bdfa5565365c6a070242c08cbc162b09421780676f12106c41df01a4e622e8cb1029c0023c007b0015c0013c00330005800dec25ab888b33016c659cd2cc22c025945ac57184bd518c6585fea30c6ba8225859198ac7b9602035959ab4e1d17b1f23dad5d546991218cf1e1719119bc08c971d10e8cb11bfd8bd445270217f1618ccd92746749bab30a8bcc162593c99f932d7f7d66599e263fa26e3275cb57b365bbd1d42e243b4ffa65bfe4e7cc0ac9a44f46b5d3ec03dbdada9b026d6d6d6ded4d5121c96cf37bf800868ab343bac364fbfd5ef5ba9bd0e4873f0f52b6e460b3b6425469d8733335f43b6db9eaa38006871a1d0f150e2a0ac2b81808e322a03f18630278898f02c8c07c2fdb8db66c779bdbbb968631c66786f8066c3a16ea83d537b6b6b4b5343634ad76fddbbc3bfbb6dacd2c54b7ef6edf42793859a8023e4ca685061c32dbcd09637c98ec9aa97fbb09313a1222747818cdbde78c71310fc6453c1817ef600229f09110068c315c5a1b9a5ebff2f5581817dfa088f447ec18c341b4810d455d56d9bd7136d96afeb8b0616d302241a08c2f976a0cbaf2578aceb02c2e94867654776504d585364c30386ef0c9928105814bcc516ef60cc0bc75fadd6ed647b508410f827d430635713f71143409fb9ed6fcd9558bfec064f36737e90663ac45478771b14d2043c7401630c610fa11907bfc93081ffd7c7cecc308a7ad859929e0c709ef4520a224b6f6d08a0dae639d0c9dd049faa2cd2055a20fb0b9a801943b36dee4d9daa10748508e8b328b4c89891814c7b66ab1001878ba213173810aa921cb0a20a08a3d56021752203a5053c5b586c3d206134e5a37861e88391cfba1470c938629ad52a0c150d549ab6f39445207a49a567eeade1b57cac2382b81086860ae005a2502a473a51720a2b2f11131598ee5508e2a4324c9800ba4e4101afc61a1ce6beecf2cc5472c44a88299800b81362a57aa449d280d39dcd25529b03b98a244b3c00cc5d71c26ac0436046802840ba455e8c5264c9eaa40e68640736dfc0a0e715eba941204e9b1e3d4c6e1f048a94aaf56805a2048038d0e3b3ac02bfce993a13e7dc6be3aac31976cf278d24c4cfce0a2818a8b2e3fa3706cd2132594264992ea065bf9ed2173d1408397ef46d6a588b5aa4d5aa5ffa5c7121f08432a507293c56475244e990523dd88b68622367f7b9ae448b9bc0ab1a3ccc2824a5a4286c8d0820ad1d6239d87006c901299a0aee8159b432f5a6334394164e4f217a2030a494bf22550c5393b6044cb1c89e08203c81252342016c509822341c02a69d881df26579c0c90c4f70211974447b25618969c0a93460c00635592183f1d1918d193c2ec079f09312ba7d6089bc21a84410588715047209224c03ba0ca7644123b1671ca194b9261ac6f91dca810852e253600f4b21c120a95852fb3510c1e354064410c01a11a4f0acf8eae36152cc59001691059a00b9e3a16e6d645ac9269942cf9f0b806f070061a4c052accdad85974931b2796e5ee43980e537d96c4ea24a6e872e90d1207202889025658490322d091af4f9bf870390d8c593527024d2a011cb820c6f924c0581e241b1780780815c4d58e80bcbaf4c0ab78298b47dda0200680300881e8d5263fae409be0e8922c4a44a06e8c00d1253c0c8c5df2e0e0948915135eb51a9f2a94d262f4c2a1b5e00c440c1c167e34e3089e5aa3a54b2eee058455cb91a84b2e51902a0b2ef0d1e370ee4b0bc7939eb70a6242b21a7c11c83003add0471f5118ea0882b8d284af4b0aab650f2ffc108abc15ac375ed491bde529004de4eb71215002194800d0d2be8644c950444f5d06420e1bfc706dbd816344c0a7a645ca1aabbc57da3d3048554236e928e3bbd108608a122a7c92fe003801b6e0ce4f0a53587798bef855c1d4573de30b3837c40a66734d61275b5381504a59003bca7059bbf251c421533567510437b02d746683a3d20cd9d362f434410e50c7d8498f032e3aa93ad0a59123e505579b2c067b1dde304141b1e4d40f26f30c13912761744bea708a11068c61151c9c2070fa318e4aa493376d132488fd4121b4016188daa03888b6ca869b01051924a006119a31252cd0d3879a13137bbe2a20cac9c288c9d851d1052a3f6020a53cca9107e7d8d3f36a3007128bc4cc86492b2b2d98b86f8707624c4b9ef4f8a54d526d04f19a60c7cd0f29c8a084940d3e3201ee0dd0c038212db8ee627ce1737548d0ebd06776773804192bb69a84e4030a078e2bed97b8015a0c32902b3fe800392c068d9050b8826530810b8b3650845db659946584710ca84a9bc6aa18820418a6001e0b57fc6efc6132e4c5ebcf910c1f27c3a036697ad1377044d875604c1b9d576902b53ba40209e11b635c008286a6414aa0c010d3c0c61219a49f0d2f428048fcc43015b976e44690e795b8aeb78523537a8e36f04e74c5255aa189c8824f64480ab1e08042c8ec4c04202a971a5f25287589066ac948a0c34e599da607595d91d03cc8f305e92b18b618578399b0c898bcd1ea8333a84c29f5626a3588657f4d5a1384e2f9c70fd3292239543c3583463f20fe8561812b390cb34041e32480675908179b54b171a1e22e39c016014dcd1bb0760f0f23a476d018f5a5ca091b85785c19e3003005cf54a12666856726657989c256e6aa3789122a9008a3383cf69348aed080b9a2f3250684519649afa6381031a6571cdbde1e25229ac4e9cc923e331abcca2c3140c3a61511134e24093a5e9dce3c21c91056448dcd0e4962cba3b747858512f2581db54898831d750d8ea963c9e8a54cd09e069e3779339249478d4203ca22a999798a994df0aac345304a93460262f08932650799dc94e764c7dda5af3d287d5c3a8645ae311282606008c40091ce1207d2e42b8ad57133434f8a184926d8a1c2e7e5bd0c423a41f287fdeae3a541f10c012e744065e182e1a14504a3061e64b68a29249a72728c29079c1160c388021b8cf6643db07b2be0b580ce82d3c9b307a204c05ba637b3295c938c787203e382900367a2a8c831c481df022d07305465482240ec70c5aac00cb83d2f279892aca22183afac923d54b085295d210221b1622e9100aaf04bbbc08d44df022e0cdcd07a902a631226eddd61e089d2978331747ad0317006cc8d330008b2e42353979920701900713b3e9ea888b26884063664835f6d8a50ba0480073d47fc0ca1a1aaced7962e7beefe3661d092023db408ebc8363a20a46be21f18299609c0a887953758960561082a357282181a8c40a3a889941c606f0374c093ea4e23343259ee244eba44c838840d93b93c835e8442b186859e3338de32332aea04395023d73843f108c1a70c9ec0eac48833500737068807a5300312f6f83866f559c1550b2e12b57f1ff888960003168f5ac910c0c5508e981888c26aec6909232183adcdd946c61f13af8c895886721ca14a485520545bd202e927811eac27a10612e6e4c0028aaf82194fc7108a9a84020cdb84358703080136164a28e0c81773101c3fbda1c95901408f8348074a385a63e236b094d43321a02b0ae4275290343316fcc46e88154a740a4162a99bd1028ea91d1b901bcc9d1ec2035c375ce113af2e4869e31af4d1949277c83f5182f5fc3cb814642c0909c11f3a506a584d582e8a248728041253182244ca1368487034f09a71e62503979a485ebcf881e9f14890252e7e40f1d833368799f8258498476c7e1af25c8c3d13783171ac7a23c0091127acb42a1e016327515703284a57797a1af46cb472028d538cb440f3916b458700ad4321b5c3384a04b8796d1a70c6c708a242a60213636d91b44c75ed285438006af412d8bf801122f68408590206a1b244d0f40530cd8b103184286b7cb520738253d98e1e5852ad6550d2cfc075910a1206428842541aa0c951fb5261c8293236302c6de5e110c436e98f9817023371be5625d1c4e2899ca23935eabe7069a1421b95903cb51193ca05d69bab083b4e8c6150c622a272a8640cb55735cb31848c31333023180d0228e312009078481e8b054291684c90363b1480014e96187d361407b3240a520a19438c01c000000800001098a1997500bd28bac420ef847fa3f4f7f3c5b2f11ba37f12539efae84678a637c9c5ccf4bebac18e4925c8d80bac55c8dedd8ab31746b11c2dc2639c21e57ed8cb8c2ce42f58a24d617aaa91507028809b7b84044acaf08313db06c8840ac539d78241450df355a7a47c4bcdb5275078710f0b318816138e70a9099161445be560b15a0acef40f0a3d61e0eac47facf66fda3c20ca18233f43fc4f780ff5bafa1306fb3bd0fca690f7023b4ec140edb82dfad542a5ebfcf99dacf9612b2277283fde3af98e3e31e5ead2e409571ffa9b95a6a4fd76a2dcff6a0d6f9c972765f303dea1440dbfb1b3039d5f9538b6fb3cf55d9fa2671beb275f0efef6ffc7ad3ff6c5f8f269ba6e07998b5e66e60e9b661bd92bbe260c61272673373838edf7a03cae1fb5b2d9203a2fffc90bfd40a0669415a85eca1d382f650c9487b205b2453905f64d790275a48c81f251bec0bba4cc80a21b501c2c5bf970fe878b5d4f5258d19146788bd5f73be7056211079b8f4c0dafed0f1edd56ef7f419e9af13e2e6b6f242cff992b7d1df4bc09125335e073a34818ae2d21c8c0bb13f37f65911e650455d9e71cd4757581d8e4e1d27e59438ccb8090520d281dd6413a88e3345e771a6a3f65f85d78ff04ce6bc6bf20bd3f9f10c286a73cc3705ab009da452fcff76b6c9e3afb65c84b35efb69493f8b3ba4ae0092c6eade80df4c5ef7bdf49b4efa663dff70e2f4737254abc977dcd2f0adc55b7c2d279efc57728eeed493bdbbdbb49ef64971f1ffd222eb953503e77552d95aa41dc7566ef6ed252f097755d33406f1926265320344138933cd3d113a18f787ac3137af9bfe08998dd8f2464b974aedbee13fbb0fdb3f47234e091766619b9ab307c55c6e1d326cfaefa6f1dfbfff0c324c60ccea43ee98705da6f20f7cc9ae9c2739eeae5ec71950198878f0bf3fea0bdc0ac35fc091760c9f49a7fa7c92fdd76c319bd240974064718b260a1ceac4f3b1eda21153a75e92a7be583976bd3927b2b1e9b29ecf464677011fe15280d8cebdabab7457d92e363fd2681373bd3f05de37fbff20610208cd73f85db5099632762254f6f74e80604c085373842b1ecbde0fad6c5c70af909e2b1628d06cc30ed577272c59311ca8f05330ff2acc2d44eee72f4a7ce49fdbb901e411b8332f0f34c766691fd377d2d6eece4c14cfa3cff7e62e8bae174984fa89915b137b1ae407076ccff798a736479a143b46d0c14a45daa58d3a959fec0e561c083feffb7bb3d8c6de04abee6c443557799400f106b7113ee2c9c970db741db1b85940d3d1cfa797dc6ff5bfbe38c30036ddc868bbcdd30054ede3a95cfb8a6b98e870f4d174bf4efcf8329d67f7abc5b484d42505196a967fd77b32a292fbe6569465ec1ef1c60f7de6a2f53ed1f6b24f4c1f5ac84e797f1ad04f12083f7dd52dfc33468d5b765723bbf8f1594811207b4b106c325e8571103ced1efe98fffbfca7778460ea4b44058b28c0c15ab6467175aa6586fe1ca6a5a21ae22851a163da91b43bec514aef15f329e9c1d2b372a710681434e0ce1922c6c2dcdfcb94122c8f198667e5d9e42b7b0f919225d27008b2d45bc3af0b7a8001bb4ce417140086b3de0b5e3e939f313cf4fdbc652226854132ad65bc6fdb8ccc01cd5136288b88c6ce1bb2c594ceb128cd1dbde4742bfef437373adca1c770e99e5570e71b44b9e6feb3dd7fc8d0f7a2d4dc688f541a0f9b1f6ea6bb17abe7ed8da8b829314fbd5cff122e1051de21f05bea3bcb63d357af20ccda8549af60461b4c36d43408c3e378de59dcde38c27c806ed8b5cf87db8d08cfb0a66f2c17efa8f773493da3e968c3c0a4c7a4c608b6c3b28a423764ca12c5c767ff1e6710e6873e8d83cddfffbde78621a239f6c917ed31e42f6e54dbc3b23763c90555b0fbc5ec6df2849dc7ce15bf4441df3b8f5bd3004c06cf8c9317543191011da1df176c5de8f65620d905a165248ccccadcbd25a718336373313475fc3b7fbfef6e27a9cdf0b6d85cfaa2db58bcded75e2efdbfed5961e0db3fb23e9962a3e6833532f953e993e0caa02f2b0e9a90339608a6cbe42a55d72ee06bf97ef1fe776ffa25c22137cc4cb7449171b3ddc9121ad5c344605e1a70f92eb47154ff23618eb01fb0a8f750578bcc68676c8c53025ee598add56c7ed5d69adf166ba39ebad8332ff5476447b1119c9c70f5b0a7809a1b229aa6236a163b7df90040207ee716f67c91d309f1d10d8bb19b4d46f92c1489f62c7253bc77215cc805c5890f52487ffb1baca49843570358248cd3fa98d723c0c66f0f6dbbb22bce1748316b48a7750c905b48a654789ea061df7cda00c3883989b5a166be73918f1d90b0d87a2f611bac4f2028a147786cf9b38d4273824a003be3f27fde6363a7208c5dff2f8e5d4719b1e3df0a2f0ce841f40082ce6212804c84aee400e8899cb5037e1f74ed48eb231222ebfb641ac8dae95ca1d7999b38e351ad57f5dc13022b0caea90ecf6d285d031c2f88833ae3d53933e301d7f8b70b453d41b9ef11ee7b0dd4d176723c922c53a0f1043c1de9b58674bbe25f700920c3ad81b21e87c6f0b23af6835b81b1adb5d8d59d7ce46c2a2eed9cbb81ea4e55644b8ddbedc4a3d7e17e1a1a25341f9e36bbf7fb1477811270e1ee96296dde6d8dcfce4d746ac0708b2ae2ddbb913eb57ef9cc3d2584aabe5c1afc7c49d73548f77457e5dec2ad752ad9f045e575a8bc465b07ff703f88c29147555b5c9e84db11441356a57b09485962f6fd58ff9de451d40d99809061b221e7aafcae75dfc2d8a57814220fb04fe0d7b340310fd1a5606fdf5bd75fbbe6944601ae50b3a3cc7862e431e1eb67e57561b3547f8624b3930210377250639dc956888b88bb67c15dc190a43f9d344763692d6953c6e6317272364713cf8e635ca9cb4398933759c2c3eefe6a12fa5153b3ad59f77c5a9ae69725b6e00cacde0aeffedf8356e1f0ca3ed34e389959f1c32885dd9f8c14818b9bac411004bcfd2ad0ca7f582b767a2e408924eb49aaddb952690c0839f81a38d312f62c8695f77d6166a7866bdeba785fcc7eccbc80ea6207e88d5e258ce2314780f61c51a5026be00138197867b964875490edd027585831edee15030731032c621208508cf28fc2d2030b4b0dc1c3f6315ede8957b8e806f5d493afc82d36d62e464b0ec05a653e3b046791dac8ea77e1953fcce3f1c6b7d3f730c7af61b60e28fd31b9ca0c39fc16f0a423098a53f660e4cedeed41a63395486db9ec0f0be48c8bee8942f29c6acdfb4e233ff1a0f4a692466fb9718f67336bdf7387636578010a37d49dfa096b542f33b982d74400cd5c6cd15a6d25f9622bea63e750b18d4f73b7fef875b8fa8fec5e084e027b1bdcc95f709e702ecdf5d68d02e356477d2f8a0e9775c9730f69b0fe32c0ada450520a4c27bb06e6854288274f22306ee14f668661c7082a4674228658a7614b2470f709fdb06c38bc1adb078f106472c2de9265a741fd4b1a73d3c9468a10daa6fa44918c50251cca32052fec5cad30f9abd72e206aa935e1e9952bec60a3ee3180a4c73f5777d3a591dacbb04ddd6393f5447eddc560afca75dc881b752fa22065b91b4e2343e8b28fddb67ff5185b36b2009a48a64214423ca9d26a89da638208d8861d90c8306e1f718fb006e108df3a9acfc1840fc57d2b6316ba79f528774c43c809ed5f7d7eb7a5523ee550eccd3af2c0aa12d3299203344ed253454979012520ec5a1fc9773b25d5ad64d9ab81711ddc4063169260946f6e6823c724ba9f902e005ca90d7639c65d475c39ce76dce160860eeb4f10ee5a3f7a1ec9a686c2f5df90cf6b96393697dcb824bca8d37f9b9753a2f34e333ba531112a1d589705de900be045b54bef557ea919f806f434fcb2fb476d975aab3dded7d857445fd507effd3c4c4a40e45fa764b8e94bc0bad1160287c12c03f0dd236b908593f42e25a31ade3828098fcf8ed06e9488ff197d391aebfd66b56c37fe6a9ccbfe06c5d879edcb152312480669b92da0c07da4f5e9c35c5213a546d2d08a2fef4873fe95faa5330c8aeb5935f5fb9b1c6538af87c41ac670db9c119fc40210556e58840dee16d82a5428169c3a7df1b5ea49864f082adb6a319c92b70236159dd2931542098ef0701483db31a0999586ed7161afce8e2d07bc46b94d595dc568ebb8886676cec02bd15a9d58a17a8f63e44ec9791b832e5ad58bd790f3b78ebf302fdd2b83d887934413afb01a2a4623443d8ea85ee0c18c59e990861a429eae6409e9b036d89833853173ea1bb082f30747a95b4d26f6131ef1879ed84575651f6f9f69e6201d0bf7fe76048b8e056b1f533433506ab4d7353a0b496cee29200e000de190994a3545bfacbdb451b0bb8f0a832797cbed4c9bbed9d585d592297aaa8fac1f7c0195fd97fa1a2d6c8b52e1bf63e3ce887c76c45d1587775b0d88ea7d0ec5a4576d71a50abecbed633024a1f1147ac1d420a19e09b7a936c31a13ec8bbe38bf0a37814a6eac7a6b232a692c2e29c4c32bfa3658efaf9edd13316c91b3108340c5527422eab50099eb6fdf4a82489802e8f26ad9a3fd7c1c4e38c938dce58fb99c3af86dde89f1b7819b34b0adedf1b75f0b213be6e684a065f1d6ec6afe72f5167af01f1dce8cf9fa18b2d847c60b4b16663a8db344a6dd98839e19244a8b402dad55c85ce2f87bc81303438a1f92e0409980371b02db44ed4f78919a09d7ea7419d47244fc3973b7df740eb4f54d0e7841a744b2ed4771e5e51ce378a03ea283aaf4f9dcb881fafdd8eff15f6ab753e853cb97b0f12363dca5da68cf06bc1d1c15fa15a0f1db3e21541989dfb6e3ef738985e6aad1c6f0c70689c10826f99caa4943b0944b88baeca31a1c0a48ebac1d50f6a2e83fe14069cd60abf07334061dc492e8b870558de7c8a3363247015a663c6dba56f020708d30bc7a077a69bf5c074ea96e1dc9f986efa667b9c52df9b56fe46882c9a254fb88b05949899b27c77c8f5ea90e700bde57ecd654dc47c3c252d6d683544b9b8452910735f5b563e88b36507475da93151a6c0fdeae09fb526683c610ce0eab03b20dae7f87e5153d19b749ce8dc279e36a09817c9ae757a5d490f69f4be591029dec0fd0b904177d8b3a9145f8c5b49e37623cf19e4999670bbf342b9fd62e744b875923b07a14cb6b032d87e436600eed1ddab4375cc3ae4ef1ab39efc04cb76b9fe94baf8b03515dbbf8f572293b0f5f1564131222e7879f8ee2822eeb8409d662ab838d0d57b7da032999b1e6d1492e20ed06621256c6c54d17c964ca34a1cd4ef75f51233bcd322674b938142d19fd7f791c2916d5dd471468b0c09dc7ebf279699b22d323731192e0a797ddcbe9c0c6eadc61aa635b041cce7757751b1dc8b2e733211e1e2a0bc0e3d9782b48c8f1b4adbdcd5467fb6f5b089772dbe35c4aa39961a596b4ba07b4861b2b95f0ba1f692c663e97e6a94eaa66d4f20731a089a8bd78d80ca75d6ab036c4d0eea76ae360e56b4291e07a9aa5aa8a4d974da2413a7b41ce942d80fdc716978997dd1f6c0abacd141425379a773490efc0f6df07a3be0b5dac3b1e91409a429f244af09863261078cb24e0adf2f98eff876bb71756115c70d8bf444ed87c3907685357654f93e582b75e401b562a2e58bb841308fefe281bc8c701bd62ce25f6f8038ff765777efe6a9ec60422a994fe51f5b398742f73d2a7af773910a1ee31aee8d8af527e3d416ba274e095e0790bdb0d924e5f8137789eadaa0e30328a8b44d5cdc8672192cd2005fbc612e272c9ad8998594d2a076ab04490d6a8c7294f7c6290ec7ee3b93dc42ae5008c8414d4530a6c4884653ef3ee3ffe26177eb8faa407710f44d462e06a0f960a5b839a59f2408fc18278f1a6c0a5ef0264ee2f207cef81e68b49da6ff517d845fd0cba52b7d10676e381c9356a5ba006757b6319212d14e81be430024db8a54d574e56ae25e71c1563e17e511534fc81c632acce87c488ee772417e9bee504f941dd837603f2c1bc34a260c4803d0b9f35d9941a4d133b6bc2f4a3894c7d2bf19093243527d4e29f18de3a55ad7a0bc85da485cea924739c8c7968030e93404bd6f2fdcd87b5aa2fbef9ec6c6e8e44309732a9e9d4ecc2024284988be368c212bb48fd1e061c65a4533489c59829c43293953a822ef2904c06a5188ed1e3f33d1ad29d622ee1827c84f73c5aea2bf645cdc54175239e6eede58983108e36a7a06d4398ed36d79131dbad82e4e9ee8d7e45168d57d897c3d1e22299a65190306e8eddf04736d03050e8f44af7e01045d60dba449c4dddd581f5f4e4e6640e9b6e5ca09ef85b07c6077195260d17ce2a0c14d143ec37b233f00b4c0bd9bcac23ef3ae72311906e44400555518276a8d933ce939c729bcf2f91bf253149447cc3b34e631dd8bf597ed5c670b885e84391c2988bb36f0230aba2284aba621c4f7393257fef4165228f748a2bf3267e56fb284d64440513d0e5d59c6217955613dd64fef4be597b8f4668bfacdd95f2b9f8d35ee88f3a049a586e9638baf9fd396ad320e34a97e2fb9dc69b88eabbe735dc828991d0c665519f52a8d3acd901bdd677bf6415cf11c16ba0992dca74727035cd12d9a76f646da7eddccd48c553c6b21b3fa8175e30fdecde9c3662c93347f6a770df8bd9b8ad7687f70f474f200b615b164db4db33730e212ce946ff1bd8a2b02570cf68e0b524c721571e2d05d73782da6e024144e6544c4b57a9672c8a5ce35b27c6e98ac8f877ff1b99718397fd6715638f99a2b48d6d72f6e8272afdb4ba119c98834431641e2a423f2b7dc4c2c67c9aa18f60cb72c4cc27fdef8e8e466770ede9e6a96563a8a9a7b6178937b711751b1007170f71e1fd674fc30aa8fdc5aa977fb2c98b6104dc635d26dd91a565af61c8d921f2c225270b653dd2951ed7c1b46571ce0cce800b82dd95bc1a9e3cb81367fe56ead676082879d74ca43ac8a18403b9c2f7a6e18f6e89efb0f2da6c5ceb727353cf8d545c300731ace93800f0316ac320638decf307684c6d0850a9cff8556ab4f572642abf3983d743c823a082d702729f471ec3dddabda875fc297cf157f36d5aa7bdbae4cecd45fc24cca09dd80c3f42f20b9cb526a3fb69e4d75e93f541faa9f7dad625fe7a395fedd305bca7b5b2553579a31cc83db634b4bd5e159eb3ece772a48a13ff17f1e686a146f8811dadcb4f06c3a09af7f99730b1f9f0ee3c1934e934a326ade03e3e6397d5025b962769d3bac1d8e685fdc4742f9e36739d5b40e8e52f48124dd1f844674f25c27fe288ff3f6a5887e0ce3c50e69116fff0603835ab5117898e107dc544deb59fc4bb0c097ba9d4ac7aa618120ce0c7dfdcf86cb47c38486220ba48ddcd40ef5cc33e7df970d30c9106f25c341631c30530a34ae38a67088811e4467ea44f0ef0085ddd96dc079fb84e3cc9c8c88ea3c1810a979ba12820c82bbac5f1d7c7c36901408fe81c57dfa78536975e118831a8f40f721537fe9c98598414cb5e4ee65f935e1d1e49f08397a532211bd7244165391a8630baa84c95f234fdcfd2259de5eca2550aa7811b91530aa52a96158af5ac6ef263d73dc4cd4c46f5636a2d58d3e92bc611585e3a191389722277a9523b0cc513d9dc30aa1e3a3d2b9247522d03ac2247654523b6c2b77fc51effc153cc1563c7202c9a3a2e8e19e573d7e69e1f33f689f58a9f247e2124869d6401ca5829c5a06fdd4419184901c4a481f29c47a5ac81d31f419aba1b8f490848248218a881949e4b09ae890288a912a924d16e9eb229e849197cae88c340a5d6d24cde2483175c4451ef9af8ffe600552c822491a95a45e2631ac935c58283dab94e22695a4502be9279678514bce532e7d08a6c0544c62249954d24c2ca2c91daae9bbb22992759338c2496d94131fd2c957ed742c9ec24c3d49239fb4d64ffc0594bb29a83b122af635943811a5888ae25f19e5a0d151f74a2a30292597965211531cd5942772ea909e8a475089a4a8b490546c3495e319a26a09ea21af21c250d84427693b062157c9ae477ffc518fafac4c5220feba4fd2e61ef57618b7f63fc0fcc6e960bd45e9e90037ddbf1d6c230788eef78de782e4242ed7c630d0c2edf012a7b97428b63fcb45a73b3fcf8d20af9e439ee38a8d6b4b8c4099147863a5d700714cb842da7d45dd2333beda659fd5769d33bb96aecb02d105eccb7b6a7693319e391f26401375fa217b9e7a80d0ea32084ef9c167ef277095436a59f8db26f22870531d3ffcf407cfaa524d69bbea5e3ec43ef6a37e031031e9484223c58070fea39cb1f73f27f002b16c8425e8a1d79cd348659ec29ae2aa8d716e2192cde138a7b1a845f028dece6f420f6107410de51503c851aa10e4eced86f1ad7371c7f58fdf6744981fed2fc6eed85dcd3142daf06834acbd3e43af10721c9905db86f8b56458d8453eb7ae035e1a510970723fc2f6460704d6ec29217fefdfa3185b3c5d3ae27a33244cc86e3b7d46c5fd45b07eb2075a25dc341ee5076ca63adaa48ea58c233261457691f721191897ea91c084b7af1e0bbf3a784da704ec829235a7af07b48d751cbdbb0aee7fc1874f23d8e568d548458c0751c88f1ce91d70b72f95db603de11defba01b748d8514d2bda2f5aae35797aa1e9345aed328d43e756e7353f60c65031edd77c2a7b9ecc8965b69fca69838c00a8dfe2474d0000e38cd1acfd5fe81965b77d20a4458e9a776c2a9e3adab95a1361a0bca40a2d71dbc2de6fe75fda39096df8a7c1878d27fdac24d905acb8df7ce863f257b2e90b35915928177a0350db0b56fe74cbb10ce9e7720a327693235190dfcd278b40826ef462c55f533fd5ad68f27f856ffae9cda503ff151da5d75006537fef34426874856fb52de763d93baf4ce1eda54462ac25fc04ebe2a152215cd70b69008134886e016d0e3e8ddc69fc9e04ba4e6f1a38e359f19fb9aee00ddbe10e7d37a2e61f416ae9f7ee192db6effafcae2678c66f5383f35d5d05e9d0deb015eec9e86a93a925ea33af6a59d9fd185cd2f03fbff01d38393ac8ac44673335ae64c7c6d2c189b3f74e8128d7bcb525ed0b8abbe9a2e2f737c3c511b4154174a57e7bdfec85ee0e0a8afdc15dc7df05f359a7fa3b14cb216806dd9566600814957ca00be4c0e63332b4cefbe43d8e136f656c9be614d29423fc9dfdc7e9e57f97d02e41dfa5ed3f64ac72730ed41c8ac17012828256df8aa224f7196f0cb28140049f1299da1f0fd59086f6f4401340384e3c7fdea509d9a61528c0cd3289586563e5b2b6fc339701c51e2e79723016a5a67e68c21fac76b13568a13978dcc8116e57642ce702e3ffe579fd39d3f579d328f694a5a72fcf82386afc0629cb51b8c9864da0e5c3c8f907580bf7e2d307066e51402b3f02ca6a0d38922d79ddbaeba464cac5cf67ee22632afaba2d3f74d8e30497080900144c8c4f5ca4b8927d5084f2f00bcaea67fc3a137c1938f96af884bc9ac4d4b4d37b5bd5b5351e38391a1696cd63fa62a93dc884e4d53db09a2858cf05c25ba077bbb4440029dd1f64f2d3e963dee7d4c1b4cbb391efcc50f48d8a035e9e1cedca2ae094a97fe6d4c7ffc95c82728750eb06e5f126c79ec6c4d118976c2ec38852f07a4d88bfa389fceeb89b7acea75c579b0f6bdcb25b988832ec6a91b2e0ddd5cc7fd2ff047763c121bacefeef91c58495639a9f9dfecc38c9aabf32abfcdfb974c30c87463b44d6560db53b8d93456cc5367f36c3acff8aaa132bb439faeaf232eff50230df1d6f53c61ec918520db3a55c61f209d776f30f5832396e7db7d91af84ad4b0c06ec9572f51d8d958dfeefef2e33038b4d3f46c4ed141b2043e4c15e06f58ab86f35634fbb436cee6a94e8b710b1393e54f83d04dce6064b773d7f063b1e71263ecf3b795eb9d9649a92eb0a85e20680446f3748634def5deef6c66aa412d396ade80893cc8c80a59fe207b31025373d03df50df8e62399ad2b7730c78ce1c2b74bb6c5c6f384657fc91d575df9946d60ee067b1eb3e0a76f39afbb607c568ccf66e003596a73b1a7601e74cff6e6988d32a8691c59a547bdac3546b6f4922c88d966c06dcfc7f647c663b7371afb1f4c636a902cc34bbd5e8f43c5b08d8d0318dd08a336ddb553c39a3866de91f81ad8068e159d50d67cec757accf9d84fde8c0fb4215d86b9fa37c85d3a6ec46789145cf8ccbabc6b42b6fdb6f3dcdcf206b33cf3382e592c6c148f64746ecba86f2597990496b9db208bc48ef442ddc68fb8135db19388ec6e72442fd8b581c108f2d6c63b4503fc0864316b8432987a97ac60a35ed03b3ea08c8f0fd49e5266c015835ef5cd66caf53af1ad4d1bc48eabe98dfba66a4897e04262bb69c0bd85d5b8a08d2c63c888c5d3da101f6b9c8ff979c05acb9de6c7cabd174fbf73cc04e31ce250b451dae3bd31933bb1d20666aa47c5f9529e86b1876222ddf1f35397c552b0dc9acdaef2716bc3d59baff52e3c1d7c0c322743571e22ac419a615e10d6dea8c33dded4c41904971d577b4bfa630f7419b1328c2eb09e384b5b8435a22cd89df9e001184abb5ad4a71dd34ed6958431fc3cf11b6c718ab16657465cbbf800561671b3cc4f12bc51968af0447483c779fc2653c9a27393d88d70ee201a2a76b34f3005eef406e9f0947678199b786983d9cb4ea06c1134fba78caf5dcec142e3239fcd56a9e35a979b1e61b9e6bd0ae7293c52ab6ba54ee6dcf2ceb4313bc009639be37a3aa7db0a5f7e922d75dbcc8dd73ebd8f73c918318a5cd849c56374a1f6f6f8e7e780dcb761544764271f92a94358ea266671452f7bd8cf2933dc40d355d6393f06fe0c5712988b2f3188583e9997ec2baf8bbefb393d085c819eeadb50be956d03ac05134fa901dfe67831f16d92890f23b3be9156c4f497605447bcdaa7710898365e8bbe46cb7409d643f1c2ec4eae502cd9eda2bfc5c735e4159b2d4f938c3b4f8d3f84a3c606cc42f62226d3ee9dd300b2ec6de136483bf48872333feae6c447431435d3adcd465db35efb3c216bcd5be0eb6d2c39e31a8d498bfd73ad055b1522d9e13defedcb4f9e9e5c82515bdc3657178c610f7273c3d22799e7d56ded2dbb882d33a847367e296cbac24b5d841dafe9410d59c8f7b3155e4ca12ca096ebe6b7b3ad4d984c924df2d0f99eee79b39c663e9dbec7f929367a2a360631c64c3aa22e6309e69d62da53f914fbbae1fe9cad90d9dbca25d9fe71b746716895acb8bb77e645dcc11a2d7353f10e23279767d19d0e324016f4ed8d611d8d73de797fa02b11385447e7b619c9dcee353ea507b235e636023d358c468c9a577615ed4696f5cea067b633fac05bc6b8924d7bda46e046c6a6bb70abcc8efda79c658de8403dadd123df80a544a7ab1bd88515caa4731b7c8bd04b67e311da88295dd07955f7f4767e6e56239969c117335e6d2367907bba434d293748e6165724e286ab74a958a90636e46d9c57d60235bdb6769ec7eb580cb0c3fdf5c8c06c659e4527b4b6887677d03f7b0cfda7c53544d631c98caa98f90526ba0486d152bfd35963a06b961fe497993473619bed0a7bf5b92926ca2d31f075dce34fe81886cb8d0cd591cccd69a68c998dc04f44d5dc8163d1ecbaa73b6e287b89d92651d38fa7899dcd09615d590afc90ccf5bb9b6d63e24e63be0fcf4b9b7b641be4f44bda8fd730f67c3dbc3e796b1ab34643b385cfac0293f3b2ede2927193a7b0adf5a23bbb69efd7e9d358c7db54b903ff54d181de02ebe6a398d7f434395476bf6bdcb6c5189637f48664e5a361069fca67287bec5b80999e86b042333bef9ae3b2d0e6f8643fdd0e5dfe22d7781d9d9d812d2fca89a0f60b2ee4838ecc4506b125db307aa24b669ef6566589d59e1dfc903cc81450ebe8658d416110fa4d7537b543744a167e2906f508bd01037ae7ce6e049dda08ddf5c03da826eabe0b5ca94279cd1b7105d2867de35dc1f27dbc255b0a36199a01c2142fee17bde1251ac8a9d80b0ffc0ee29e3035722fa1f0bec6400b34db1db820ac3951461eb0b365ce1bcc9631867d4366505ff3f032d4513e90bf1e305ad3dec6578063f9a538d11c70080185bb1fc6e806ef1aaff93762d8bab1b81fd3813fc0c5b84cdf785f88cfdc535a816c1aa1f15df7ec8107cc8a38ee72382ba6cac4651068d1ec95ab4e8a8d2fb18199fa5e35fab548b21cb7d6c955fbae119e352bb99ab79105cff0d16946ee8df90672256175d0bde3cd6abc793af63aa2a39ebb2226e859b111507b578fb51d8fb86cdcb38df0237c22eeab1eb41935f1d1af89c3c886fd43ea60b7ae2833c725ce5a397c17b79be3a00297cab1f037bc5bc097dda4963919c3b52edc836ca01cd0001db093b4373d70d4eab50e98db01cbe412778f6bdcb6dab033870fb24a1a97e3d8d8e33c55732c6dc28ddee0acca8db7cad9dbf14b0fd6189da2690dcedc6a6f67b66387baebd8e20c3d3d97b672f3ddc6b436f837daf5a61b3f6eec92da2c68b099061363406f30471caa76b2ea6de82d2bf24183bfb0d1dea7d55eae2ceae6b39bcd6bb5702e7e105cb779842e0293f17958edaa1bade89283f8b216bbc616ec4c0db6a29a6eb793116623ef4623a6bd54c78a89f62013d425601c35642665281fdd4090d6cbb1c31bfd16d711b3b863a7ecd9afdded79806ff54a3cc63e767253603dc678340c3599311fee19c85ff3669ecbc822ea721ad83ec6970e30f77670c7cc96d284f281cdc45b7021f15532100ded899b17238e291ef762cdb571980cfa1cd1c2da03729fe62b7af941b0c83ce288eab12ef799ec38d6db72b57cf8c4b6cc40d3c8193d61d77e0dc7315c3424e21433b761fb1826bf656dafce7e8b8c3bdddfe028e34dbe9baac12ee7b6818f81bfd0721c010ea3cde28eeb4b6fcb1d70316fbf76ad813e2fb389ec18b6755da77b1d1b7f9e16d271104bc51fafbd2d60b23b82c984596583a96212ecb89fee78bfdface11addeacf2db77e1d10c335b7b3311f4bccb52d6ab8e55a0cf7fedc6d4c1e641c9e908f31c41be9b55ad30979c7283e400ddfb18d375cad5ce54d6b293794721a6eb1318b4e0d8ca276bd3de32ee7c8002d18d31f300cdab3d9a8d80e3c133ce04ffff5bc1213836bfc4be4e438324eed081c7eedd23943a7b2cd7bc50d3403c7ac7035dbf92c873b58a7c2eb23c94efb326fbb9b8ce298f02933bc91a5c2197f29467516eff2916f34830cb81d7b3a0bb63b59f5eb1a176bd96061772c63ab858539450db476f2ccabb0b6771d1a6b6857d12e46edd7cf2723d57da1b319cf386acca5e56df70d34d28ddab6e159b3bebe706e899eed5bd426249fecf8eacb4f024b2fd28e3278d861cbd352aab1c9c83a9df15e18663b1583fa826da8096365c84e3ff8347a7ac428f366bec2a6ba62fa41af5e7ede6819dc9147ce75604bc41dc3384e7c771631677813465b4fc09818225858bd864e04bf9c074ac7a25773535db6b93af2ec81d546970e71b9ae64b86369057e2c7c098bb89c5e092b333b7fe365dfc008733a69d46c7e543c2db44201f675e3ade7e9aa91d0e54d0b66cfa91c912a8bb9766ce35bef115bdd6d3a1c83411708df151b9a4b36ea9460fe9cc12bb1cbb4d99f62806a90df54c7ebdab5f07a2463c4d829b007718a6d5a89d19c898d3ae4b7f4c107b25563457316b15b58af6b38d612acc5036920b66a4f771eb9d137223befac3bc4ed5f4b0d6b3771b294419f1c755db5d5bee75a4e10ccbe139969e4c881c16f779b5ed57361e5f313dbc0c41be752879ed9fae3375362d7c685784a160adb0b03d1142f32642c5a66102764c97c8e3f72f11a1bd52cee18b6e632e6ed2df39e9379f106dc4696887ddcf3864c865a7760b49cc6b0cf6c83876f168a010e988dc9d217ed4068d6581ff46e1dbe826c3442033bcded37c60626dec1f5e64000170ae25b73657e13761e08c7d1d6e10e50abb5355b87a6b95e6c9ff107aece0c7b81f96e1887df83719fd258beb85433e6cb2e721d2ae83a0c344108ae4ddaeb3c268678e0f2155a4e01bbfeb26f32b38f023b11a38fb59596b8b3a48755aaf56bdf084c24a4ff67026b7ef4be3ac59c032d4fa0ae1e0f1f84de8e753d030f085fd43dbefa2343c401fc2dabc495e5dd60ac9665b1704fafe022fb97046f5fe4e69b2e794d6c5051ffa7fbc53c0597d15276328508f94d31d6f38e075379f1335254cac4902183ffd0966f324cc9abf076532b0b00417022e1b03c9ece3c1b63af78fe9262015ffe602b372cfe936c2344f0266d13fed721ef7a6066946fab3ff571c43b08c1a28d61a5b51be1629d909012106740bd0f8198d8faa55c14a198f489cbd27358ff40fb9e590117a25053cb27ef886c123f8e1b174e630890fe4c703af191e386ddfcf86cdd8dce7de0a5a7cddc42ef2288352583e50d99d1714960689206fd16071b4b21f67117e6ecb95196512e292bc5816811a5232ba16264456e11df47ff0f45b0f230ab4519db2ce9deb057b4021a65bd0e368392fe1b40cdbe1085ddc7e8793152ce799997113f0cd992ebd38887a9f48366ee735a00120853964027dee1c679e57f36d2f1371aa9300ed18b4e46beb1b3d1caf3e8bb94119381c1391fc920af83cd151567320fbc7414f25a8b40d50a14d3eb1b0ee09f061954bc03030ba5a8bb1fad3be8ea1c7c30b32704dbcbee69403ae7dc7c6e4ca47e562c9e1a28d0008b8e651da48024f7b2b51c6d0e484a110ddeddce65d928e4d1fb2cddbd8bc6448dd2aa966d29fd68e7a7f68df33e0cff7938d4f52ff4c1722d94f3ab7dab10e7a1181b2c9d3e40aa9e9e48306729cd3e0524f01436c069dc2d24f3b514b2364a7f35439f51da3644a79a260419dd69685c9b400b4db34ea62c1f5c345503c9c44e943f10c5b58fbf8d61c94a4de76b59ba317009f5a425f6bdd020c4e1f7f2899cb5a612f3b7aaf3110ab355facc0d011a1ffda12871f8dfd07af315b1306abe2dfef8ef4fe2797262eff1976b61508952bafde29f314d13e12587794017877b5e3cefb7aa75cbb98ddd2b2ad742b215a4320f0063f5235aedaeefa094e82283935e68dd9f7a414b93671dff27f0409fc86c2ffac8cfdf50e56ec6a51282ab8011d4f997b91504718a61affec7668bf3ce0018a363857a9bd827fc3c71b490d6482e700b1abe709a72dfdc8f7ff6f00c61daa2a65e733ba2eb0ffafef9b95f6f5e80401d21760e8cfed2fc8bf697c8db2688e087a6b3fabb156d26f5b9bae770903c9ba07ca14b27bec582f7921d3d73414483bed35977272bd6e88e332df7dc973acb31b6e206f7c37fd38bbe96a7e6bcfb0210fea48f725b3882cfa159ad7b9e35199e9c9d3549f7a91f7f458e2411f52a74baea0e4cd12561e3687cf158cee3798662ebfeea509ba434ab3df7528424e95723a8cb9959710e77ae2781de96f6ca5ffa2fd48571c697626d64beff6d4127ac590a6edb3821ffc735b6ed8355ffc7fab0a3517a343f144a25bfa1e31ec8bbd5a3884d09721612f5fdef17b476234ea45c08efdd447632812c8cc59647572d4e9c5268b0773293ec6569c07b83f9be8cfbcb859ce2566fc7f87a90118323ac4ed3d747efd8bef5ada6acf7a0b4219b86063615979e884d499f0d07aed49112120b238c9128114107e3de3c186d45c08af8f68cbcbd8bf8493801d6e44d2a88279cd4b80e97d6dee8901b4bfa2ff7dab977837871fae1a0530a362cc2bfc08452304ab19115672aff38d35bbd04eaf78fa13e9cd8a963e8e31faf5007a0fdb2fcb99240803ea542dc3c28a4277be9a73fe303c907a477c6f81cf0668e45e0b57c9e45b0917b189b9baaf9b95e3ac02d8e2db2c484edbcdf309744fe677e44dd89cd0d9e59834de7c0477622540165ccff9b7d01fd616a6adde3c26bfc822e1c25e9bc1194c070663c123cd0f6bfd3d87c1760ecf3b1eb8fde68cfc0707438b190bd6432cb79cb44bf8918d6db28b70d5921300739b350b83aedc4423ad1d8b47f791d6b91e76eceafa4a46f4b52f05dd3b81adcd72d172e8b007997ce4971b3b01bdd4d2793822b28e2d0570ad49dc092eb2447fd597d70fe19c91738fd94fe4cf40423a07f73a495f7e5b332b391f0d187b05508190a188ddc7ec0cc627225869e9ec99e271a4b41b82394a66ff671bb01928501844d7ee31799a9ccb905205760243078dc94f42152ee2a2384cb5d2b86a47329e7fd58b1694960fa5341c4427b3602e3ac14e55a0e9a8380724fc16d9294993b546f05c018ea855d2ba18a40472dd341f6e5a053383a37e7a658e19eb53fd531943fe20c0a7008ae685641699a967f2fb39dddca1288ff48669341d70daf140d4292143ecc6ff6b46b3fc238d747dfc91f73c2c4ba532eee03ecfe150a19742b04e12326c44f3f6b68b9b171ba7b138353fa012c977da889cc40fe9dc4af2f924953787f21c6f31a5a23ba224fbc89faa96f192ba8318c36f8612d31e849a7bd06b8a8d3bd4d98a0cf57a211d1bbd53f33aef421bf3bed4601f415a6be4cef5d15105fd708ad4b7ac69eb3313738868a2adf6eaa515f6684b7c6256c12d2ac7eab60f80b677df4863ef6904feeb56565f37253bc5c3f1675062a24819ee9bb5d170c637a65f3d2ef95b2f6f44868584246738b08420654829129c16aacad0093fa3a6285c1fdb62c0a507195e8d5cc0f24298efefdde77a85f0161be4c8cc2295daf63a9829bde72c0c180f7eddf36baae3934d761b1ddacc7e9c90c506c615f3e6666bc1fef97846f1d28b42f466b052a03ff1e71cce1b84d5ccb93b1c46d80616d42d15f3f5b258b0320c1071b033fe5ee45b048db60dee82e6c1d6496a0aaf8be495c37152d6ac0f79eebedc8a2ac4fbf9133e73cfeb6a980107dc832471a0d75e9f5d362f7b98ba121418ce3e3271afd67ccb28e0cff7645f13c90836ff31be276d9802bb4e9fdb68e0f4c8c5e9fc00c88649e383fbc1e25e01ea17d6a35328ae03ded8882d102c1096546293a5cb9bbb7d9ffff52739e359d292e985e66233e5f3285fdf024b40e59c0ab1c408c99e8055ea72facf52397a20ef67b35d25f2af8678936c079f1b689119085b3045d1ae500a12547cf44d42e6b09fe41960d015e0fec451d02c56503b11e2e15da13103cbad7002ee3f9033ca91375d6cd60af03eb7655e4b70985c4f0fac45ffb0370be4ba9e3160f5d34134c5013c08f2a3d9a6cd93b983c945f584580f9174d1a7f7cad9922f67ad3778e40301451d77ecde3a629d39409d23982585e98b1465cd2deaadae1a1e327a1942fa6d0325aa6745d84b54b7fc072e5d071458770ed6f35c09fc20abbc0ed82a89e39a5c4d3612b24a63a5c776b043f4c625c9a9b80b1396e83eba6b3523f451cd36c225bb96c622fcaedf0a45521d778af8c82364f4e90b0087035d798c850c172ee5308bf5cdfe82d3d813138096a9ae4b242cc642061419423daae0701750e13308fb7225f93d7071d59189defaa3b1baed434ac1d6df3c51c14028b11a9d9304dc541042d04b56be878160704b0155a26d76b1c3c0336a8f4311cd07d1e547f4805b655652b3480a4bb2d438e98e9cef6413572d8a698482252c861699ee8a16fed33745db7534179fa793be8f783983dd9ca9e7a080bb6d3f444307e8dcf56b301fe92b5e77f92a7d8208c7790611318357ef721bc0031a667d0cc4f4f187aaab3df3c9c22f212a26a666795ad52954fc1c6d8aba3165ac68b2a7310b99d245a17ae8dbb1b3b86a13e4c02f74dd324fae44c4d048b68d7211f8c5426648259e63a23f841e725b46fd51549a9a4cd1ec79d2e4e755a98d7d0b904257cd3db2e658cbbf59eafff2cf69f0ec8440648524ec1357ce356359bfe8d6ed4ce74c620501e95723117b6dbd66b39b6ca271c29c7a287012e83ed97f269aba9a4d900985d1b9bf1c6ea505c4e839eb35cdf9de52d05fefd0e7675a325c4ebd35cdd78d889c32bd438c2ef7cb22c95920c70be9e2f7379c5d42f17f08847ac6725d6649e7190802b187f585b2e793b7352ef81d26ca9e1d4829a02d992a984712ff93b8284a2dab17d515516368af6f0424763b2af8ee603b736209fec12daf6a0ad39422a9e1dc044b30e2e89e36dfc8edc976884a37e1d924059926a735dfee736e74a76e2a1942562999c59830dac77df6e7976cf08f58ec7325e7700e1b812949ad33c9059a625d0d76ee400975f54e77fec1c5a8ee0118f87f62a0e90258938b6e2525f2fe24a4d0e952b43ad62b071905065fcd0025792d36e0aaf0b0de1ee6ea60ca506710226f903c3f31f9a3a3c3bdbb2736ba9b383de3e13ee0445512be6be3ece59c412ea6f58035d7f642e1a0b46c578f3d2214c570f478bb78327317023dbc23f8a9ccb558c6584022919f162b702ff57c538ca51d3826fa031c73d0e780a3760357a8b29b8f2c8d9785890160e3ff72d67ee1f2560e1537fc28d2aace3576f8486f0e8a0571316b9a26cceae7ecf7281eb25cdf5015c873a86e18741259d1fbd86c60d4475a39e77b079b67297d24b7a699e273f9b69df025f89b2f33cede766a79f6e3082ca03fc9d8b3d8ad7fa8898655eba2aaa49423fe2303c866cff3ab5e6dc725c44237bc8a42c7a72635f84a74786712f8fd908221d7f74aee82aa59aa58a5afabaec027645c6ed10f11d39d05d4d4a13bdbde4e6b19a7dc5d33f1ff50cf1ecab6e6af4c4b84bb4dcbcec83b921deef9c2a612ecb9a5f82a72ecb0b5de249d4fe538891a52e94dd4c309aafe2f250f3a74fb8db252f457cbc3bc51a12ca599e80e496808ec6ca7418f92833d2d414cb39edb38cb1dec5c0b45772f9f819bb9397bb01e9d0853105106434cc1b3260ba9080dfc745256d8f9e7170f3fbd93f1df6dfb4836a90e5abe0555de0e947c2365d2e670a16ff27cc3accc1c0b6db08df3a1bfd646cd2b5a1724cc364ca1bea2a4922220506abf76da1dfc5f398d2bbc30beee8ea838e1cb7c75292a30eb42015c6a5d54cd48b13db9f4371df599fe2886663686921fb05c0552cbc6169bf572377bfeb4114a413c79c6d0c8879bc9ccf4a54cb63751df4770615430518ddab4a24301f626fe1d611bea097a52c22dfbce87230c57499e3d459cf591a1c28c2419eacc395a84b95df1fd4da6d3805bc56f9f4e82ede88d0eb926eee43a83d195fe4382d46dc6cdcc3bc78d07daab54e9a81a267149d334b4c73bea8118720c1a8f695ccd7cee2c482fe6c732abba2760097fef22a31ce46de8f62ce9cf00ec1f214910ce97c06e555cbec9195b8b885c8450b16436a21ac65b1d1fab15466d0ecbe4f2dc7169c5518fa8a5780bb5814528dbaefc2ddf217ad9dc98291d621bde90a7333aad993b0d54d7c06f95512f3609a2a953ed4bccd18460113100302480109b00dfd396e91b2b8c86a987a334275eae3e2932d3dc550c950d237b1de8644e185ad0a6b5196dca97bf1965e503ed9630f9538160b5f0896b667bd534c770ba4559b46f42410316e094fe0190fd9eb06e9584d0b30c9ea2dfcf1aa474b4f7c56e28fe6f6de0600beca20e7f6ab13776ad2a8a3e2386e2b797b87805df3646c489df83f64b0f8f9f31b711cbbe67c162cf3d0e8dfebbba7499538a43e15ff35d0f378cdec75b19f4338fc555b6ea9d671ce814ae4c626a03eca162a3c4212adb98eff79348bc8bec47203e726a2798e2af63916402fa96f2d01db72c17ef2c3ac68d15b9fdfe4843a68ab28f940366da316fbe0028efaf2e0cb9fe0d28756703256d4244b9b8330048b7027e5bfde3ea48612cd857d8906cff2fba8858a8e6c0c7fd5dadb12ff5ed4a1a6b624bd47258feec503133d503a3d0cecde8f2f8a0d7aa7787949a28a3913f2b471d7035e4686d6d7cfc24b7babaf817ef5f049275c272238f9ed0c399f1b301d208757600c3823bd8027f1c14cd13396acf1c8d38ebe9172a6eef03755cefe0d6002826fb0cfb6de351d4671680caba6f4bd1721e7c866bcbf7ec7b90b83ab82cf7cdf9a4c9affc85b2120bff827d221cc247d251a81a5cba60eaca8d1e09fb9757b5500975b4f04b188e3e12754aaef70add1ebb5ceccebf33645aa3d1a3ba0bd088caec61e6e3ecf616e179ba212dc186e0b3d48b94b2efbaf1a92cf5e4ef489c5524b468d1e3c2500a5eae245de07ef53ca60b3ff5141d7443885cb6a25dd57958357b7223b3f687467319e15240f53cea1bd1a6809d068f398e55a79db623785d00627fcc55984d852ee2c1f50ce9891a59f2735727bd5727729db6b435838437a13c1896e0ff0b9ed1817e6531eeb21aeefe54e75f5fb2162678d819ea6c41dacec793ec4826eee0faa5e85701bfa002647a16595d8ba6b6f587c26dfe3deb6416f93d812ffa2408f7fef610bcdf788451ab72eae25e5422b755a9abc6e12d23f2e0af671b2ee1b0815113c59984673adf513493f04dcb8fe6c795bc5ac5ce7bf882d8ce994b1ef7d29346b0afffd0f9a52b0f817bd62311096c8a31f6bc190fc4610f10264bc4198b2682680409a783bf62bdfb0deebaa0a0d6d6827a5a553772d27e3c11c43630289f8e9ebbba09f85fafe7c9c1f8a2566e4de7ddcefb0ef47c81aa88e1ec99d25e4bfdca7f218577c361fd3a93940f8d13379146464039aa01be4b295ca7fc812fc728f960d779dd764dc1488b2aa5cc6a88677f72d4822772c7ab70e0573bda0f5c1ce94c20432fc5ed78f640a3806e8a76e196dfaa715e2da15ad58a3528c956baa61ed7782875fcf2fc2bf1bb289c65a05fcdf0d0553b8417877edaa1f1c837a7ce836a39dc6f8763e0af58473d6b1641f31afd72e377c82d2201cdfd16151538057e1fa7c420c7e7954475711a8dac81f6d6e5706b0469ef06b7a7dc77d2ea4c2c18f2c21832132b44a79b42e2c95923187a846b852e8a9aa9b6ac39440a0dc80ca4330aa6eba272034d65f5dcace0acaef59db6d5d5774ef1d3947db8d5db611a560b323e6024e4784b4df4511f2388b22768cf8a28f196fe7e1e94aceb963602276f54c4a2ac51def36998625b2f9f2ee20da9472895efe7ffc0d0815eb31eb4da944a7ffb3c10648a452b7ca0b00191abe4f1409a8aec72bf3528012e59de2ed55d1210035a2079eb25de624fe4355d4462b07ffd328d106f397923a5d9c5de7fb2815d28ea5572e6aac045980ad30b7396adcba2c364c443e392ff95be0213b11acc5a0e676ce398af659bd5d7cadb089e39a444f50ba9ffec29937daf4bb5330f994b1ab3b5ae2538a77c0ffadfd13906745b9f175ebcd7bff9f46c21e39bfa073be6f488661eadae85adf6ceb2e283ee8ac2f3c6b578a5e8dbef80c91ce2f7162fa928127de0533fb75ff33d3deee4199edb7e9d0075379cd0e6fb7c2d93d6639afc6a888788ee61a9557243cdf245b3d7880eb12b3e4ff6c06aa31b963121ae72d563b2aa6eebe3b3b747baede9ab931387af650c9df9da8cfae3e93ea3f261299d1236f007080f71ba4ed175a71a8ef601ed1b82ac21d74e33478c619b873f8cfb9ee1d868c2b22cfab66214bd65cec76a9ef10b1fd6a915abad2fd73c92bbbabcfce7d84c0589e566b8b9793a09adbc22ee526565406b4d606ca3f9dc1470a91585770427b08f966e4f44892becc0e990f904d945010b8c00850b5848b44bf8758eeae7bf5128580db7ea029b8ade9bbcc2bbc4cab5f6a2215822337d15b77b7ff20d15f8a3162a7eca0e6e7af0b125ee5a196dde48459b4b9797b1f964194cb208215841fca863a66dbae824616346fc162a83dd5592ffb442425d3738d432c58c309a9ac5bce3468865a1a5bbca3a38c9c3fc7a36da77a3f976b06d89e90d5ac435fe942a7be18e51a54595f728d9ad7bf327944e60a45b37798b1e320d3ce003c2625c80cdc4d3f038e64e64607b8c9d0b1d156422fc8828ae5b99fc4c9c6e98bd10ef56b6a2b2d36dfb37dc3fcedf7ef7661621aef2b895964e08e77311a7b29488c03701ec8eac385ec35ac1e2f292fe6c1900e80d1f92149597720509c8f5cf5097c82a4228bd8a288afa4b3c485f6fc3cc08225ca2da31d246aaca0580376fe2a76fb4052544cc480255ce2b0288728e77f4edb7572e152d7044691fa78d6f0aeafbfae5bce4c5a5e5d8628d25f83b91c395097fad4f12f6529fb134b7d335a96a650512dedbb456af093f7ce351952319d43daa8572c5cf2bb199da165758bef0dcbd52b6066686582788fc988dc8452c371552ae13b7bf8491b7d0a9dd86a1fd44b169e85d6c701ddd0150753f893555dc0cabe618e76c8955dbe86a2140b339daa4774119f1571fd2f42ce46566837843d36f34f8644beb30e357908a24cf3d196f898d0a2dd72644c867ead1745c159c7ce52049e9cacdada092a3a6500dfad8f11791b1179af027cf4a8644dc69398a38a262e8ebdfc6f961529949b79b4c3d68ff1079def7cdca42d5a456c1645c4b4e0d7d33d3bc117bbdde04ca64a7fcec9702638604b9731946e6b70fc45e1b2a59b0b989bcd2cbb061268ba0081b473d5d4674eb027166989f6fd82cde425bbb3b9b2ff246b964ed119613769890836916dfb8d2ce9125965ae443cb71869b4a59aaba597a69f3a994bdd52acefaeba69862c9232ca169e119f1e100937c0f1c666a84ad48d69835371cc984dcfe404b71d2fd4af020ad12f1a3f0ab1c01eeac83f4e7d4deb4278afcb522aee08449b6ccfc998b91da74a1050e193f19884931d4ec81507c330b09f995a31c97edb9caf83108302944fe063b9869873682858ac2d52fdba71aabe1edd9ff34f27e31b952c565daefd36f42e02f1918ca06774f03b2627b589567f125ed9319ad02b19d62b06557f3dc34b02d7579b4d6158ad5caba705c5a522f85a03f41430e521166e894ba673e916b95c0e29f67c1acfc0b44f309fd9426d915a9bc63939d7cc1c18a33de355af713b16d9dbf2240489321c0bea05f5f36910cb2215e2969e314196a3f454c3a0e6a0313f9f3af3b9489822db4a5b2f77027d1152b3caf5b5a17600a3109076cf0c60e8ea2a07362c09ab2b8f972c20b1ef341ce10b808b37a0df7607457595113bbae00ca2000d39c4c0aad83db67252d925061ec67a721b0d5f1905b9b3af42d44f5f7cd862a3027a0e9e3b5ce7c571136f74fe302d7031dd0cfc80566a1c4dcaa63f9c371aec25306b406c245a29a943735169ffb0fb9be8b77ba5a50168be3cbbeac5bcc68a87b718ca5e1f11edbd6b8e8efecd1bf7f93754aa31878d1fe64fc1b7a8836dda05d336a7d299cc0d5eb66e137e6c14cf390f337a4282dc03ac63f5a076f70b84c04e31426f814bdf0bfd185aac69915e6ac13c1db5f71c39e30471abc48b1ccc18b2d276a1770db53d89e3b2c9f3fc3ee3d2451894f717decebf51c4a45d050536f10e6e6de58ef14d8af5e2ea9b524cde14b9f93a408b5c6bdf5bdbf37e964ec1a2effc8b72330d7ede3c7a836c1648155845284c745cc6829a4c9464be48db0328f236725c2ea623061ae22822630cca7f2ca9d92d97692f8b44f344fb573d42ef56baadce99ce26a4531d1f325864c193784524075f75aed40bdde6da4fade9716eafeaa004f6f7cafa26f42757f0be9a6a167598c7a9958417ba21b4c19845860fde349997d5989a394038d64d557e6921b0f8c3ce431494650c705a21fdfa8f30cd99a7687aafae5b0f664a9437d9d8836b35bdc0adc50de67ca9bf87ce506d62836559f102f19850e3ea9fc5ef6cb7732ee00be0e2f1ddfc42dc1c5f332e237fdfc9730489e9e8a28ef817651fc7c33c8270a053769b32cf8db225426e135e8637a350604c53b417b5f6ed209ff53c1d1887f0c536eec8e0333b8ad4573a2b6a15be3c1291a36233e7ecae79e311cdadb64197830536f1ce5b52d67fd3ef1df400e17719eb7d979eea86284bef34a2632ae5bba9cdfd31490775a0970566f6aea618a51fbe1688ed3304ada78cf9992fc69c2dc2c4367f9ee0722f1382bf45e4ec1f8585ba3f86aa9efa66267696b7cd2252aee2272470b64111a6eddcce64d28ad41cd83f4895251733ab7b16e96356018fa9d902c544221021bbc5a8872002a0e7da7fb2f15dcb42dee017fde32427aed959e8c54f1278829f0bb1b868beba7ca8a30adcc16464023b5747f38ed04715006f446d4b6aad0caf8dd04a3186baa8ed3d4ae735fe478a9bb56e055d5e4a575ef8897d6aca4932d32bc1a602f083d2a253107070c8a529a7a13e11968a613c14d4f59d82d497f9714751ed749ef2353c3cadacf55ecc9b5ac36171f709e7f26fabfbad4d57affb4b9a3e2eac8bf2f3ee9759fef763afd6156a2294f973ed81845f31fe698c2b5d20c7788dacc884215a18c48a90681fbf07b94bc92ce361ee8e5c5327488541280f21cdea5afc3b0a59fc05f5c18ed8f1a5ccb396d8b8b58b0fd762ea59578a0dbc029109ad2bbbe38b90d95f1bf02b5aecea69e223f58d4060be80a91ccbe21e09080af8ca13995cd779472bb73f8a146c027c85229516267f8657d7408f36eadbf8fb72f74802735407c17f80ab582202256f5989fc72ba986318f23f456fa2b36b39a5ec90b727aad90ea5b6cff9bcce06ba3ceafd46c87295ac390b41936eaaa1e432b6af157eae71fcf6ddee7ad53b63c43d637d0630f99393bd155279e7078156371af2c94f2c24176cab731f73a5f39d97ea5e4e4e146b464b650527b80827860e6b401ff1291024746550ec6c3d7e1a0d2cc10f141bad465c2b7e0c7abe84053a47b68d5d5caf15799e24bad1f6cea89b51168419b86990c4b52f1c95abbd97acdbc5c20f0a6e9047f99b92e2351964a08416ba809149a4af4dfe4adf4e83a79872846088134a05e8fa660e58e4a4f2b55dc6ce6702f0b794e47fcc10bf3ee52c2237a3bfa26959c3f3a209a7ebe2649155e1c8e461f8d5621c8da12f0ee29aea56e4a81b066c772aa41d5c02c2c36c397904e07b8011e4107b12a68462b605accbec13a4513bdcb08062fc2e22360186609356ec8af05ff16b665ba2390e33f50d35fca71dfa6f33420d0251a721c0355379add263ba5502ff966db23c2c4b3810202aad506aa1867a830ac902b838b6875f54a2ed60c926cd60c3df59eaf870aa953155ad526835c463777508ceb4374fe65e207dadfe43e341f1b3f0fd40847b44a7dd3a2efad167fb13c32fdb837731ebcdbbc47a458ecaf998d9a2fe81bce2ee19ff8ae617e872f85c2b01d7f41c1f5a8045ae9bc5488176c1c8b1414750a2f579cbf90da4f978ed82252279559f423a9589ed064a1112e56e42a02fee438e656d077974e9a19483d44aa52b1a27b73a0e2deeb3b7a77f1aae8f5fe796de110aae6cda31189a2cfda1ef293f07c7ba5d99948a6b5f0a0fd884259cb4a28024b2e5ed2c853915342926718b50bc355b650fc462566c6b4e8c8060e0b9f6aece0378f24ab2e6e14fa692c623ec94e1565440d0832b50bb2b464df3b4d2c1acab0cf7e4b60cb4fec359553246dfb805042805d44e49064cc100a64eb3f891cc65b220a59be90327b84874729a2baa28288afd398f1eed0f42d22e7267e5ef853850d7ac42341009306bedaa51abc109739bbe22a48be3dce6e503c95f3feff930f108de30dfc082fcd56c4b56486c412631e020235c58fb6fb0904db67c0d657689a6610381ba8d20c408d4546b3b91f69efb50ade24d85c85fa4d56ff8159373285c1e726cb11422d22738554cf8b48eb8faadc1471dc238a42ba5a67b581089e24fe9a94477d32ac0478c22d3fd761b89fb6d3917d3f1030d275f0f59f90d10b2b0bbd56d4aa21a7f68125415473bc31326457b46ce5786e7fd98e4bd911b177d6f115757c1b1ec19c6d0818760464108c19a975942b727c44f7964d9a191781421574e051d2346a83d5dd8661747f742cb5d7d23b3c4d83f13b77b22e1a255f8c8199d1af242fbc6e7c143df1a1d4b00dde8deff6714d5979c2e2844cd315105fbd7b252d6852746000a76876db76a216e04f08294cc16b7bc8ca5f5240a194cf978216fb5dd3a9d80dd0e3e950c25579438e55a7c1c1f7b8ee265bc9ffdd1734940c52482e8ebc6b736e05e7e8d96e79497a065d56157502c276d3de1bb96463971a672bf67cae246b185072ef29c2848787875835553cc7b142602f5e726af35c24d77014947bcdda2a706d5b62ea04eb994425f800fe0e4d8c3db3aef792ab3103f702dfc415dbb47bbf87f125bad5fa632c85b2feb68a21bef2903d5d6a2a42c104ac8c40f652c8231c2ce435ad9d8a0d836c6c01d3eb41f5929efe0146d379572c28e64777edf347658993035bd10c222584351559bf43a0ed278539156753fac9d59f3ba14489b57220da0f23ecd54f9ba9e97fe50561d4a6112edcc5181b80ed62153867598e564dfa8d858551d7a3eeeec37c696a78e43bb37cd6e895d23c7cedbb94904b5da043adc1d868980aeefe1a33803b58e1a5596e8f87d836fe2c3b6c8feff27897d78885a1c336151ebfdcbd37a1c68ddf07931200e7170cda294e56f908168e595a69b197536b5859f3163ebdf4e54288f831100aaab9ecf12a59ea6143648b4358e3a7ec92ecba01dfb5f7073f1811f67b46b364b70384c950bb4237868e99502f60cd7d86dad670b1612529298d5eb2a95d94a1ae4aa0ba26af15a4dfbd9b82275b267abaab0b4bfbe45cfa8bc2f1f06186039b3e55edf47db039dd6eb2dd4effa131cc1b049d41f8c7823416e50cfdb307c511a3dced393b448c4bde575dd8106dc1098117889c1b115ae49004cc54f3b7cb7a3893306975436ec30a410402500e264295e94045a8a58f491816d37ee5d477ee4bff661c3b812d0b62c3cbe31f4c0ba71658217745236152a820314e87015b8b5c1d7206ba6e8a56e457ff2da966d7ad7fe455e7eb62dcf41a0669ccc6580387c80dd08e0174c3bbd11f7bbc38810ea9efd3fad0088b15b75bdbe39e840bf44dcc803a8d0c8719fce300b9c1fd29a3ebdcd1963dd17ba4a8ea661bf5568952c61b2ccf49e51fe098dd5db2fafd08580294e407a4cd29f5698e49cdf86b687b1a604ba76caee4590b461f12a9ab85c46a0bf82003ae5cd5634c6c9d7a091025227f5e9045a1c9479b51927f0cd0f918d02f971430450afe2ba2fc68d848e0382d99a8a4068c826e34766ca9808668cc04882eae2c2b4842e6db94747057540bd29bd95cbba1d6548d1c8c4a7691413641d8a0f6cf9769ba6036a391b49161aac2c3a77260e436b6fe307886741ff7e5562d1bec181ef6bc178b6f7533c978b7564b9f8b0353370887cf53858bba5fc49eccb6b776fe45f139f9e8be1572257447b68b41833bb9b543a69c1fe2a0a0371d181d2e6cb5b4cead2c5ea8bde6bb81e93a5aca1261853d062260ae1e127c4906ca2b6c161b149baddc6e6292322a5cba95076db712745d97cda82766aba171dd514f2f1040979cb9ac0bb54d6e095e8e27d33b525d706a9fd077dbec35e14eb5c91ea60fc234e33cbf32c4f97f00af9d57988197984d171583765d79078a163596ac84877396f7fea9f3715e5b2ba3303b1484333ac0827c4416a4c02ad6e8196ad818d40693e8949d7325e0ec93be62a3581e585e0cf29036e40c5bc58e5d080087058fa94f21695340b1c24432c7aab9e2b84525f95c8470a4901a10aeede02f1f693f87bf9803ccaaf15ede001c17b78fd619e543edaa439655266f55d8d6f0fbc48490528a0b274eaa67a93efca34a82816e38d044198455f304204252f04adbd28821e26b33302fd24eed8df1e7e4d6d988c8a8757a3efb37c346f6e56b140a772719392fde3234ba9cfb09d753db360266bd2ba60c4d1b0b1af229cc5af01c85aac129417c88c042c77f25dd3d9e4e789826b002c5b852cc50b518121362ed7fe3898df7b3db054a3808d7fe53a4236bab0a873bcefea4d2f9dffc4fd9ef751ab504ca0670493f25d38083e13601a3865b26d244865a25d4e0a16deb0c529f0a905468283e8e614f2e54517f0190c8f1029348cfc8d016172fdb2114745812eeea41536317e6eacb45aa7308cb9a002c9307a6cb990640f9a2aa682c90491c60c620a1b4b5b77ef9c11e79ccbc223df6de3a45b6ad03c2d877cb79872714c8351b7618c7789112334785bebeb1d7c67ccd6c447096b2350333731707f484625f5cbb8b0308c7b8e02645c47e22f8bbd944744d6653ab08b12969d22c69fdb5f014268aa2b448db0fbe9d1cc6c0c1609d6cf53e56b0eccb8402a53f488a38f59b8686943b9cc3c469409ccfb40d4945884abbd0864bd693f59754398b4e8a53a52e2ded1d55b2216a20ce7df0de77b0898723e225c01569dc65259f1d4011cb59fc2329820f33ddcf1f132f4b62cec987bbeaf295b94ce698af182de68fe93eba11a4948025e3cf03b427f23d53a17c93d5a1cd690612a3871dd4d73d70cadad397a0ce7be90834cce935a41aab7d76cf4f016b70125580926b6c6d9beba1b9de6fabf9d4dcc12611f2b778e5f6835bb1dcfd6af1c968483fc76f97dc09aaba75f2a0eaf68f42c471f0beadd3ee3308211151d2b17c565719fd98d9c4c106ecb279bf5120fc5c8ca02d0d60b0da2cb192a0f348f0366c1149dbe3474b3f6be3ba8f110c32e45b13f2ef82cc161af4a1474014e9f6a1d9d12df8add3a41d4d2ce51175a67d897586947e7d678fd76cc71d844504c68bc07a77cdebee8eedfa87db17e0e584733cc86bdccfc86f97fca2ff7a3077e5c7135becaa5436dc51e7de48129135f457a08de6b3badd8c20593f6f9f00cdad3ff433bb012ed8cac348f84a80b0e26c319fb02153833203bb3925904168049578f412fe1743bf627a7681c1734f1987182595b552d98bbd14d1cd8d08a5801f57877493d0be262b7cdf3246f4d482f44f4566c36bec2f97c2ee7912b24f4e0833f889459989ea7a92a77ba1406a89eb4d32b69ad646bee412a26dd01aa27b101e0669e81982eb97edc1741539e89915d046ae27c013d51139f885b22a08a5f2d6842bd0029f1c65c7378f89e1ef08c47d62f4fd2c4ea0bee1dc5831fd5f578215be4cf158e3b23aad9058d759b06754b0d61fce9c281f22f807c29bd1bab9fbd051031e800740e25ebeded9461a547308e2cb1bc3bc97775d0e410e7a4e5e46d27c38f25f2b9e0a97862df8583091d0f29613071266a574f24c5b35d25b259a08aa38247ef383db17a7f4d63dcf57904c9fba7eba103d1f346ccbb47bf9978c970f075051b5bcb0dec9bc319a277f97df0546188e44d6ffc8e7c7f36864f948b43c4b682c2db9a2fe8089ffae2d2161c8a72f9fecdbede28f1d4d4b31b6d0ee6067ae720d9ff3b2246ee88d2d7d18d013667288d5c491007bbe29f87a5e1b06c4b5cf2fed48caaa97c5eec529714991fe3c2569765bfaef164e93efedb07e12e0e1b5d8596c134d20053adb464de4ca3a7c88a4aeb40f86ffd83b16b58b0f84dabb3c3134debc97cd93914bd1bd9bfd2e5ab00f142c1252253abdff619c4d9d36f8679637e776888086d24d4b0a82e3143a93b4033ef331a13198fc103ce996b6ba6b030dd8e08b3a7f65ea573f09f00803632f8e2a4fc579d477c757c24ead803ef8824aa91b407401c94c4776b744158caf9a00c7299a3f7f0704b4b787fe06673a6d7a136c9e6ae431d8df4a85b5a94b3fe99fbcc90f8923c83ff8864a83055e7a4fc2f2398221207505947f1bb9f5ecf2815b564404c467c2ea50d30ed003606316e87fc50f49dc04162f890cea0dca499632658be10d96cd2328b26c19feaa042989708d219bcc7e695d0806a4913ad95dd7d688a424acc330ced1e55d84abb8e3568f70f0cede3e3e886d652449e5e793d79be7fdabba0eb1289b7207b352eaf7820a6048d0ea1db9c2514494ad06189d5e9a0fcfd70fc14e0e9d28fdf695b62fd5c674b94078ef94df6989175da14a64fcbfbfa0ab82b0f6844b955a82d7cdda9f6b739921ac2f447c0e41f24eb622ea125442b57d6b939b11f361335de13921641bc13848adcb818efd14a6f0bdde7f1187c376690c58611280ca6070e11100d2f4110805695e1db36e92642c064b1b615a8aaaaaaaaaaaaaaaaaa5a96686408000360dbbd8e03a22ce0302000f3f07ddff753e6856892802392c47662dbb6ad255ba6982d8c2dcb2b0f215dbe00f821dead19f14c2addd9054f95c2adddf04c7a7069743ca788b833209eaa8d1babf349dc9d3fa529f19c627365543e397c7efcaf6e6d80e7d423972fa21ffa5d5a04cfa9db735a7d4d825b23e199a4e0b26fbe732e5f3efcd0075736c027a95c1a13cfa936cfd6222ff4c5b3bce2851478c224f9a1472e5d08bc909c6739e6855e972fa51fc2e039a59eb6e5d923bec91b53e493b3cbd6d40b957165c24f66f07c85ce7e782a369e73496bafe7d4d3a5ed9e53f349c8ad49f04c06f08c1dfd9313eeece8a9969e2f307ee8cdb3437c2379c684f8dc085796f554095c19119f1ce3d602798af19c92e1d6983ca71479c2d8f8a13d2e2d83e7d4015746c327a7b8a4e1ae1c0fae6ce993383c5fe3db7bc6e60f25af0ceba90a78c266f821302e6dcb73aac9b3a4fdce0477d6e4a9b03c5f52fc1008cf1803ff04844b171a3f647b965bbc5094a7d3ddf93da95ecf98ed9d287065749e0ae8ce5078aa2aee4e01cfa4245716c853fd7c353021794e559aef39e57a964bde46c195297db2862b7be1934e5c19139f5ce3d9387eee5c1a17cf29169e650d2fc4e419e3e173213cbbc68419f1436d3caff3bde0595af04caa3cdb436b563c9326dcda13cfe49be7bd7fceb37cf23633972eac1f4ae0ce644fd5bbb2003e09bc7c35f1434ab736c73339c4add9f04c22dd599f0acab3d4e3851c78f681ee049f54b1cbd7cf0fed9e312e3e57c673aa99f3c997355c5a9be7d4d1a56df19c5ae1cea2782aa84b97113f543e5d55fc50ec5936f142055cd99c4f665d19934f2ef09c5cbe55bbb50f9ec9056eede7d41d4f57103fd4a78be885e878ce1c4a23e23995e6caae9e8a80cbd69c17dae2d66acfa9a8e78c8caf32ba744d79a13d2e5f3b3f647467569e0a842bdbe29379dc5a966752805b1be499d4e2ced63c1511cfd94567003c15954b13e43995c5a56bf7424dcf17153fb4746b5e3c932e5cba2c78a13faecc824f2a5d99eb93454f9aedca41ba331e9e2a8d4bab7a4ec971d922f34222dc5ad63369c7f338b3ace28512b8b3e153d57065607c528f67ec827f52c5ad099f49d9b34ce06d6f9e31399f8b7ace375e26e6d2a63ca770b726c23339c17366c5570d5ddad5738a8ebba3c03359756b413c931f5cda22cfa931deb8b53f9ec92aee0e044f2adba5b9f19cf2e1ce86782a38ee8c89a7c2e3d99eaff1d215c80f053de7224f9be0d696782699ae4cf6c9039e65062f545e99244fb5e4592ef0b702eeec81a732f38cd1f039102e69095c3966dcd9064ff5c22d18013f14c4a5d9f19c32e2c61cf9a4ed72fabebce2d68a9e53903c678d7c150537c6c8276b97ad065ea8ccad4df24cbaf1ec0f5f472e4d86e7d406cf79c3b70cb92c9a6f1f9796f49cda5dda15cfa9139e313b3ec7c765d9c50b4979c680de4915cf7bfe382e2dcb734a804b5a942be78ce716fc8d5c99204fd5e7cc8cafca5dba98f821d7a5413da79cb834129e5314dc5ad53329c795a5f1c9406e6d826792c9b3e5e585966e4d86677283e74b8c1f82f39c167c6be7d67ccfa4ebc6684ff49525f149349e7de367edd208794e6d71676e9e4a892b03e4935877a6e4a9a65cd9fca4934bd33da7fa8455fd9024cf58977f32c573b6f1320d9eb1103e17e6d6da3c9347cf52cadb74b8b2dc272bb8b5a1e754239796c5732a853be3f254223c6170fc501fb726e6994ce0d686cfa4eeee103d938e3c9bc1b7e9198bf24fb2dcda22cfe418cfb9c5bf08b874055fe8e9ee487926b1aeac804f1e3ddfe10bc0952df14935ae8c834f8a70e92ae387782e6be6cbc79539f1c936ee0ecf33f9c79d45f054672e5b422fc4c1a54b8f1f5af28c397d0e92bb93c03349f58475f143729e4d63b69a7821216ebde4f8a114eeac82a71ae11963e173c667e7b4467b4ef5716b4d9e538bdc19eea91a784ea5b2043e99e559ae79219e5b9b7a26dfb83bc22795ecd62bf94335dc98205fc453c9f18c51f04f5eb86c81f1424e5cd2965c39c4e75395edf1c9a95b13e2993cf374e3d9265f34b76008fc9010cf18169f0be339f3e3abaa5cb6782f647cc660f89c993c9ebf50da99e714035716c127912e0dcb730a801b43e493b11b1be493f3ce2478aa36b7867b4e0572490bba728ab796c63399c37372f0ad2fcf73f82ab934109e53539ee5102fe4e4d982e485c6b8321f3ed9c5f332be526e8d8d679287e79c62cefaf8aa2997d6c873ca8ce7924c9792177ae3d6e43c9348dcda18cfe40c9716e739a5e5b204e3858c2e6d8ae7d408cf99c4b7ea3c630afc132c5736fb240297af187ec8f82c6778a125cf12f94256aeec8a4fdaf16cc579a1159e319f7722c4738e304b3e5ee88167a57cdb3ccb235e68804b13e23975e6d90af3424ccfb2e985187836f88d7367463cd51c77f6e5a94eb833354f25c4b37ce18502b88445f043483c5d2fbc509de77f7caf3c67687c95eeb21d7456c3538971770078260db9b5439e277cd288cbe25099d5533d79c296f8a1379e93909f89b9359fe7d4209785e4670297f6c373aaccad571f3fd4c225ade8ca21e3d6e6730a8f671df979c0952df9a4f0d2e43ca79078cea8970172e9da792136ee6c89a7927369569e533f77563e95944b437a4e193d5d3dbc50f2f2a5c10f517069833ca7b4b8b43a9e53455c59d3271f791efc0579b62c7821109ef3e965733c678a7cd504cff27c895706c3279fb87c2df9a1de73e2beb4726b383c93c3278c871f42e3cea23c5506cf560b2f14c3adb5f14cf670d9e3778f3beb792a076ec18c3f24c5ad95f04c72b933314fe5c2b384f236146ec1603f94c3f3c5c10f597065c44fa670655d7cd2e9d9bae1856cb8b2a24f56b9353c9ec9246e8de739f5c7a50bf6426a5c59ec93063ccb355ea8f79c4bdfc2e3ce664fe5bbb3119e0a8a2b0be3937b3ccb435ee8e8d6889e538e5c59119f24e31933e09f4c794e09bed53b53e2a9ee7876f61dde5ad43339c5b3bdef9b6719c50b1df05ce89773e982f24278dc5ad233b97bc65cf81c99e7ac90af92e0f235c00f016fbde8fcd00c57567ef2c98d497d72e7cac47c12ce95c1f9e40f97b6c0736a7665653ee9c2ad81f14cc2707724785205706b4c3c936d6e4d9167528c670c8dcfdd7169bfe7d4ebd6f67826a1b8b3a1a7d27263897c52766b42cf29a9e7297c6bcfd60c2f44c39585f0c91e9e33a7af6ae0f914df012ead89e7949b4b4be43915c6ad29794e21af2cce2781b82c8d2f24bb339ea7123e63b11fb27a2ef5ed726b679e49062e4d92e7541bcf56d10b8db934119e53135cd9d3272379c6c4f81c1b7726c553e97177187852d12eadf89cea3da7244f8be3d6087926b77896caf7884b979b175ae479f1b77a4e3d5e1675652d7c92894b2be139c5e5d6a8782649b8b535cf6404b760b61fdae1b29ce38582b7267b4eed7165853c55ebd62c7826899ec7f0ed79b6a4bc10f1d225c70ff93c2bc1f78ae7af5f8d4b83e33905c4650bf9425ddc5aec39a5c7b375e585d05c59964f96b9b5029e53923ccbf9b5376eed80e7d4d4a5453da7a6b83b4b9ec944aeac8d4f1672e962f3428adc58209ffc59255f33cfd6132f74c43396e59fac7067c4a7bae1ca14f8a496cbd67c210b6eccea934c9ea5232f94e5d612784e51ddd90a4f85c595493dd5ce738ef916067796c353997165757c92914b4bf39c72e0caccf8e41fb746e79944e3f275c30f9179b68678211e2e5db3175ae339949f8f5b4bf34c3a7067063c95974bbbe33975c4e50bcd0f8579f6e75be6d939e65f7c15b8b5359ec91d2e5b83bc101797f6c673ea873bf3e0a96878beb8fc5096e75a4a98d30f097265489f2ce11973e3734dcf18189f4be3caf2f8649dcb5e95d6c673aa872b6b7d72804b1a00574ed273e497824b333ea77cb7b67b26e7ad2df04cce2ecd88e794d213d6c40fc17169333ca738784ea1093be4871279c650f89c075786fb64eed9627a2114eeceee49356f8d80e7542497d6c073aa76652e7cb2893b3be0a9bedc5a19cf640d97167c4ec59e9de1abc865a9c30b05dd98249f04ba330a9e2acea5f5f19c92e2b9c73326c7e7a02e5fc21f82e01983e09fb4b9342acfa99e4b43e23905c2b32ce06d6d2ecdcb73eac933e6fb276aee6ce9a98c78963fbcd0d0b3077c979e93ca6c7df042235c19974fa679ce2b9d4df1547b3ccba7176ae0ca10f8a404cf92cb33997265597c52ce73d6f1b20eee2ce9a96cb8b31d9e0a8dcb57023fb4c09d85f0544bdc8281f04355dc199ca76ae2f235c80fbd7063b527eaa541f09cb25dbe68f8a1e33366f44f3cb8b52ecfa49367ac837f22c88d4df24925cf980d9f63f32c237826b3e76f7c7f57f6e5934bb756c53359c29575f9e49a2b43e393732ead8be7540bb7a6f44c42f08cb57e88ead9327ec29ef7f00de0b2f4f91d3f2e5b5abc1013cf991a5fb57bdef475e0eedc9ec939776782271593e7cb841f1a3e0fe2bbe45972f042ae5bd3f24c2e79cee2b7c63c63797cce8f4bd3e13945bc33364f75c48d657d52e8162ccd0f5171f95ae18788cf82f9e2f16c2dbdd009b7d6c33379bcb5289ec9385776c853c12ecd8fe7141597ad345e288a5beb794e0972778c9ec9ace71ce35f793ccb375ee8777766cf241f4fd70c2f04c99d15f0545daecc90a77a3d5b3d2f14e6ca609f2ce0caa8f8e41c97b0337e08ead2b4784ea9706b743c93443c6716ff2aba6cc55ec8cbad9df14cde7065833cd5bc7499f0428d5cc25cf8212f9e3bd29ddc93eacb8af00de4eef43c93825c599da752f29c405fb270697c3ca7a2b86c21f2425e3cfbc74f00ae8c844f0e7165519facbab43f9e53553ca76ec634f827739e4dcfd6082fc4c225ac3f74c3ad69f14caa7077809ec9422e0d8de7140e77c7ca93eae71923e273243c7fe02fe4f922e387e23c634cde891dcf97193f24c2f319be3ecfb9c4b71eb93b474f2aa0bb037c52c1ae4c8c4ff271e7edcb2acfd804ff84856719f542103c6793dc9b1477c7f7a4725dba6e2fd4c6ad019f49d895553d15d173eef02d45ae6c864f4a716bbd67b27567b7a762e0ce7c78aa35ae2c834f86f08c417d6e92cbd6182ff4c465ab8b176ae2d666782639789649bc50d1b3f3dbc19579f1c9a7bbe3f34c0e7267183cd50ab736c233b9e5d2cc784ed970e9e2e08504b932db27a55c76929f0a5c19199f8c7ace05be94e31286c40fb9f1ec183f5f57f6c327bdb833dd53717069639e5352eeec89a772bab3254f75f49c71bc6c833b73f2545b6ebdae7ec886e7497c852ecb1b5ea8c9b384fc5c72698e3ca7ceb8b4de73aaf53cccffc8b377fcbc3d637b7c6eeab9983f923b3b3e950ecf191b5fc57b966f5ea8e7ca763e09e5d906be233c7bc5cf9faff996972ea317cae3598279a1dedad53349c79d3c5f467065523e49c19559f924f1d235c90f19f09c517c6bea59eede96c433a6c3e7dc3c6fe3eb7b9ec637f79c67bc4c7777b03ca900b8342f9e532edc5a9c6752cb9d79792a139eb0457e6891670c837f62c573d67ef22e0dcc73aae8ce3e782a1f9e13fd783c5bfb263d6137fc901897ae342f84c8f385c50f315db69a5ee88a2b9b7aaa264f97d50f21f06cad7921132e6d8de7d40e9706e73995e5e9dae185a62e5bbf173a5e5a0ecfa9a45b6be099ac3dcb392f34e5d6f87826a3b8b5169ec93057f6e6933e3c4ba617badd8231f9211f6ebd22f9211aaeccc92723b8b2313ed9c79595f049222ecd90e7941797f6c573ea852b83e4a902b8b32a4fb5e6ee50f0a40a7abe967e48833bc3f2544cb760b41fd2e1ce9e3c1505cfe9c8d338b8b5139e490b9e338c7fa571f962faa10d2eed85e75406cfd9f43239eeccf554b94b1be139b5e539a79e26c71316c30f7d7169813ca7b0b83436cfa92acf86f1d375e9c2f2427d5cbafa42683ccb255e88804b5be13905e6caaaf8241d9796c273cacba52df19c62ba330e9e0a86a72b87179ae4ca783e19e5d92ae1855ab8843df24375eeac89a7cae3ca24f824995b73e099dcb93bae67d2e93963e8ccf6540bdc9dde93aa7569793ca794b8340a9e534197b6c1734a812bebe39354cfd6092fe4c23366c2e7c63c635a7c6e8ca7ebe98704b835db730a902b33fa2497e754a0ac5263857cb275d9e2e385b2b8b2de27bf5cda19cfa91b9e2d475e288c1ba3faa400cf32f836306eadca33e97365683e29c3dda9f2a4f2b9b4189e53626eed886772cdadc5f14c06f16c025f3657a6fba4054fd792178ae3d21e794ea97167c0a74a7acee09771dc99efa93c78ce395ec6c125ecea8726b935dd33d93b3bf3543fdcdaef997c3d19b934329e53345c19d32775b8351f9e4932b760587e48893b7bf3544c3ce70cdf2ae4f255e587aa3c6364fe0923cff28f178ae0d660cf29a84babe13935e6e93abe10f2ca7c3eb9bbb539cfe415cf19d4573d70638b7c927639fbe2aba23cfbfbc2b933089eea83cb3285175272f93afe10985bc3e29944e1d6829e538cdc9a9a67f2814b17d20b0d72eb65c70fa970e9cae28766b7d6c533d9c29531f254b5274d7681c4784ea2f205fba128b706c933b9c6ada179261b78965eca529217da72f93ae287d25c5a07cfa928b716c23389e5e96af242723c6179fc502057e6c1274db83b3fcf2421cf32cc0bcdcb2e7dffb8323f3e697567083c159867abc90b6df08c19f1b91d2e4dcd73ea8167f9c10bd16ecd83673277f9baf243569e0b32636c3ee7e5d215e685e65cba52782147aeacca27936e0d846772ca93b673e58cb9847df143783c4b372f84bbb53ea7e43c6122fc1016cfd28317825d9af039257b4e2b8d11f2c9f25996f142ba2b43e193475c19149f7ce3d29a9e534bdc99f1a972783691b275c50b2d7161757ea891274cea8718b935de33595eba5878213a57d6c897304b352fb473a7edcb29cfd923f716c4f3f215e5871878ce18f9aa2d77d6c153c570673f4f05c1a5a9f09cfa726b523c9322dc99134fd5746b609ec9a2e7342a5d14bc901fb7e6c23389c1f365c50fb1b9b39f4a812b9be0931f5c59984fb6b93b113ca96ecff9c47ce9dbba33219eea8d670bcd0b9170696e9e5357ee6c86a702e3cac67c9284e78c8aaf0aba341f9e53649ead1e5e2887274c8d1fd2e3ceba3c5509b7767b4ecd79968bbc1096674c8dcfc9797ec317e8d2f6784e41f12c1e3f7177678127d5ecce983cd595672b83176af3fc90ce4078aa222e8d85e7d405cf191d5f157cc67cf81c9c3b1bf354323c0779f6d2e9d235c20b493de72d58053fc4c49529f254b32bebe1935c3c67707c95efd95af242622e5b7ebc9016cff128610efcd0114fd8143f34c795013f19e6591ef03637cfd8d03f91dd9996a70ae1d6007926adb874d1f921026ec1743f44c4b34fad3df24caaf17cce84e3197bfd50d5a569f09c42e0d67878268d4f17213f44f47c15f043c1cb56d50b99f1dccdf775f942e18792ee0e02cf64f2ce1278aa0b6e8dea998ce3d28e9e53404fd7153f24bbb3d753e99e3331be4acaa595f19caae1cee65345b9b52b9ec913ae6c91a7a25d962cbc100057f6c853017067473c151d972ea91f2a7a4e00ca17ef871cb8b50d9e49059ebd4d180b3fd4c5254c8c1fcae3d952f342253ca7976ff13c6944578e189796c6732a87e7cc90afca7263559f5cf2a495f7678c6749f4360e9e31323ee7c6b315c50b21716bc16732f68c01ff89d2958df04921ae8ccd2773b83b539e54b8672b86179ae1ee64795209f03c8e2ff03943e4abb4dc9994a7d2e016cce78778b8b40a9e5343cf1800ef848e5b333e93bee755942dac173ae3d26e784e79f02c2578260cdc9a00cf29449e13ccb704b8b52fcfe40097b380394f9813cdb7d278b60a7821a4e76ce15b825c59d0271bb8b430cfa902ae4c804f06af4c864f46f16c6d7921a56799c60bf19e7bf0535d99964f9ab9b5d673aae9f995cae47cd2916779c60bed2e5f667ee8824b33f39c12de599ba74ae25942f24213dc9a1bcfa40f7756f454155c96afafc9710b36c10f2df16c1d2dd8971f6ae2ce04782a2b97303d7e68904be33da7ca67a3f85e3d5b09bcd0f0ee3cf0a4e2797e46693c3ca78c97d6c373ea7869603ca76078cedcf8aade8d35f2499e2bfbe09330dcd8d42701789641bc10d1659368cd89673284678c89cf55716b46cfa9ac2bc3e193573c63227c4ecc7366c757099fa5ec77b45cbac4f8a19d674ce9735c6eed8d67f287cb16d40b617167424f95e5d6a67896cf641e97f6e53935c0a5493da7cab83b023c93883cbfe3cbc0e52b881f3af38c11ff8922b7b6c533b9c2ad613d9375dc1a0acf64976753f8229f7f40ebfd11e3d22e784e11f02c85de86c1b37afcfc79b6a8bcd007cf12cd0bd59e5de02bc253ea49a35d0182cf19e65bc22b53f249df9dc1f0545d3c6355fe099c5b63e199bce0e9a2faa1039e31de3f49f32c736f23e2398768bdf2f8a1156e2dcc3359c07342f2b41d9e30383f74c5f3317ff21953e1734977a6c3539df1ace3ce909e6a865bfbe1992c7367404f15c1ad99f04c5670657a7c72925be3e29964e1ce5c78aa2d9e2f2f7e288467dbf8124a9b7a4ebd71691e3ca7729716c5732ace9d9df05454dc9d019ec947ae8ce89310dcdad333d9c4a595794e052f5d87fcd0933b1be0a9b85c1a0dcfa90e9e2d282f9474091bf343505c191e9fa473e9aae3877e9ee5182f94bbb2a44fa2f09c085476c027b15c599b4feef0fc886fd0ad95792683cf72e9856c7786c35395716b3b3c93c5674bc90b65f0845df14375dc99d153897996715ec8e7d915268c8e1ffae3d600784e1972672d3c9516cfd800ffa4e8d927be56b796c13379c0ad21f14c82f08cc1f95c0677b6e5a9e03c637d7c2ef99c893c4d824b3b3ea77ecfe7e84e9f4939cfd2ca33e179967fcd8de735cc3ea96c884f8a7167413c951b7706e6a95078ce0fbe85c6a599f09caae0ee507952f53cbb65768bca0879aaf2e9faa1b4079e533c77278067f29067d9c03379ddd9efa986b75e80fc900bcfd7ed87729756e73995c6b3e5e68552b8331a9e2a8c4b17173f44bbb1d90f5d5dda18cfa919ae0cf954aebb339f493c6ec17c3f54c465a97e42b934149e535d2e5b6fbcd014cf5a306315fc13189e311c3ec774f962e3874e7896b6dfe1e3e97af24275dc9d27cf649d4b83e23905e7d6247926db7896549e49edd2a8784e91f064e339a3f3555cae0c824f8eb96c0ddf452e4bacac894fb2f19c7166cc8fcf515d96492f14bbb2359fb4e1d66a7826c73ce9784e3c5e86746b539e49dca50df19c4273099bfa21489e30e40f21726bc767f277674c4f85c495f57c52f78495f1434f4f57d20b1172658e3c55cfdde13da9ca678c807f52c073327d2b8f3bcb3dd5067756c25349f18411f24387dc5a18cf640ccf99c6cb7acf394bd7971702e41296c20f6d7165407cf28b2b33e29365dc9a93e7149d5bebe39994e2f215c20f7570e9eae38798dc9ad43359c63306c4e7e2dc9ad333c9c4ad01f14c9a791ec617ca95e9f0c92c9eb1349fdbf22c9bbccdcb3386f44f06793e4267493c951db7e6e699bc7265537c528e6729f542123c5d452f64c71396c50fd9f19c49dfeae0d2b83ca7849e65222f74e5d6a43ca7b09ead1a5ea886e7907cc33c6180fc10f2cacc7cf2855b6be29974736b4bcf24953b43f35440dc82097fc888a71cb7f6e69994e0d95d673d3c951a4f581f3f44c8dda13d937d5cc248f8a12c2e5d4ffcd0ebca8e9f64e1ca3af8e4087707f74c06f26cbdf042323ce7a3b3009eaaca9dc9f0545f3c5b29bcd00b9756c8738a8bbbb3e549d5e4d210794e81f16c163ffb74b5f143b81b7be4933d9765a20533f343535c19224f15bb32de27bd3c5b40653c7c728b4ba3f39c42e3d60a7926b9b8b5d9738a8fe71997b00d7ee889e7aceae28c9ea5202f44e5d29e9e534ddc1a9967127865737c52915be3f24c0addd90d4f45c695c5f049289e8de05bc28df1bc93042e5f6ffc100a9716c473ea834bb37a4ecd71650f7cb283e7517c9fdc5a16cf640acf18099fe3e0d2c2784ec57065c14f62f09cc9f155bf2bc3f2c9e3a5bd794e49f09c7cbccc8f3b2b3e150e97c6c0738af634e439fff819efc6ae3ed9e4d692cf241c9776c273ca824bd79a174ae45916f1424fee6cca53a1b9b3099eeacd95d13e99c0654bce0b597165677c12905b0be03975c8add9f14c1af174155fa890272c911f52e4f9daf243123ccb275ec880e71cfc92dc9999a7eae1c6b03e197469383ca786b786e59904e019fbf24fa8784e31dfeaf29c4d5446c72717b9b23f397469553ca74ab832289f9ce0d2f0784e2571657c7c32796baee794d3b325f542605c9a0bcf290c6eec904fc2ae6cf8c913ee8cca53a5b935ab67728e674cf84fd63c5bf45d737708782623b9b23b3ed9c89d0294332ebe0acaf32bbe043c5d35bc50244fd8163f74c7731e51b6862f04278de7fca1322c3e79c7adddf14c1ef16c15f1423ddc98991f0081a72b801782e3d6187826697727ca337975e9d2f242517746c453c571e97ae085f4b8322d3e89c79ded9eaa832bcbe193585cdad2738acab324e485aa3cdbfa8eb9b3d653195dbe827ec877d9aae385aab8b4239e536b2e4b3b5e487865429f74e0d6bc3c934f9e738d9765f06c00736af9567967649e6a874bdacf9533bc3bad67b2e959e4ac50395b9579f9240877b6c05319efceeb997cbab42ecf2927b726c83399c5b3f5c30be970eb05f5432c5c593f49f47c8b2f02cfc6f04de4d99ae2859278c682fe49eb593ef04c6277d67b2aa45b4be199f472650d7c7283670bcc0bb1b935449e49309e31293eb7c59dd9f054635cda24cf29372e613efc501ab706c73309c42d58951f4ae2396ba84c8a4fc671e92ae085eeb82c4d7821a06759f43636b7b6f39cf2e3ca6e9f34ba330d9eaa855bcbe1994c7ace06f92a2ccfb28d17f23dfbea8e01cfe4245746f5544277c67baa3177e6f3540f3c67d19736dc1a94e794d5a5119f53bce76bcc0f75b96c99f142505cd9059f64bab52f9ec9172e4d82e754007746f454133ccfe27bc0a5613da7eab833119eea89cb17113f84e6b2557ca138cfd7013f24bc33069eeaf8ec95af15cfd6f989c0b3257c0579ce154a58eb8770b833284f85c19591f9640bcfd194f6c1736a81674b921722e3caae7cd2f82cf178a105ae0c8e4f1e7263c84fba9e37f125ba7ccd7e48ca3306c5e7b2b834209e53669e33886f49cd65b3e572bd5aae5787cddbd355b66ecbf59aaed70ce66a7973edb45c2f99ec962b2673b526cce59ae5abf3c0e6ab7cbddad6ab352b4fec75eb268bf5f25b9d00f0c8666cf684cd56ab31dbb2b65e65ac4967f91dd627ed393b97d5663ff6baaf12567bc564b198abbc1300db973bb4dbb6d9b342f317adb3e8d9a5102c00da54d2b66c6fc0adafdddaf9f5dc29e7ce38209edaad79ab8c7d2b5f414a5e3cf7f028d9c925bad57fee6ded9a560500d90db68bc9e504c462d37edd66c973edd6b97b004aa02cbbb25809746d5c00d37e80390141c78c76d1be577ee03e73b8e2b203e0f28e98714f3a00b7803ab9e79f5d2b31c005a0c7b811a0d43fb84d2ec70de53a6e0d3c96ffe71f9bccafbfffff4eb9f8ba9e47f1bf9311b9f5eca35978fe799482772ac320fce544bf66bbe5d7cf133ea087fc84a025e67f9e5a1545440bec1718e44be220b7a5805c4687ecd815b27248889ac810e44bba881ad68bea1c0490f50101b9ab0a422a881472a245a28e1582bc1c20ea268a406ef3450d830079501c519da30479f925ea28be4076ce0b398c1164e5bc80bcd4459d8bf2476d2920875989fa894290479105f2a026646d1644f5cc0ff91388208f05809c491dd9b04790bb8a40ee0202d93945c8615a909744e455424b8e9244be2480a8977c1175901ec8792438734eca9c9b0061fdc0915ed2903f6108f2d713f2a04490bb9c88fa8939c8ce99212f0b88bafc00b96b0de4371aa27e4121e77923ce2505513741761714c883e440564e0c51c3ea445dfaba6b07e46512222d43401e0409f2271a4156ce09515d0284ec5820c8637c205f6242d42f31c8a1d002f60b10e4305cd13c59ce748e1364f1a8821cab083911b29d03849c278e38334113f51239900300249b8915d9a551d43128c883ec886a0907399614e4af1fe4510e41ee422380e28a3c2644d44c1e91fd1a24ea12871c068cba49212d218c1cf28b0fe4310090f3c01167980451976690cbba202f8dc87340c40e2a04b9cb0ae431395195c302f2a03be2380a3490db5440ee5a22aae51be43127c8cb1bb24abc801d7b82bc0c206a2239c80ae080ec2687b47300401e1409f27281a84b17f2979727d71a800d3b426e0346dd8490fee222b48b89a86774c83a270064f1b0826c000bc03ac706e44bea40dec408c8cb600f92833ce607b23713642de3207bc687bce41275ac09d9393ec8f26045dd8491a88304418e45843c17446c011640d68702d9b13990334983bc54421e4b46fdc421c86341441d940772ac27c85f1b202fa5440dbb8a9a678233ed4944366c11e4a598a883d440d63641d4e59338de8943f6122ea27a889ca8ca990279933e90971c20521b0b516d0281ecd82051053a03765014b26314d15d3f202f2340eefaa2a781f090c79a206fad803ce80d6481cc90758e0b39ec81a88306415e6e80ec07747a0926aa7850415690469025649143266224ea7286dc164054cff490c7f6400e4594a85f872067b20679145a206ff214b5eb09e4650bd93937e4a5065125a411647d1a801da408721905b279e488d3393cc8a344823c8685bc49536fa247546f61c06eb1801c4625ead20af2a027e4651964e70c80ec9c16f2520db23d3ed93720ec256a441d9b425e0e1379091bc8ca39eaf1eb0bb23361779901c0512441fe52823c0a2f909740875c3689aa208b203bc700e42e1f90c31c410e6b0479c983ec214e51c54de4417490bba8f8e3a039c85f84202b870ae44fbc64bbbcc01d8b421e7384c93128e4390508b9dc414ec447cf25c0c74f3082bc4c20aa33086437f106f91230a25e4246d42f0ea2260a04f9eb82a883e848e4970ff25811c8cb2ec86384200f42225f4207f21700c861749003e007725816e44dd041b6e707bbc412752c0a7222457a0901f2202c6403d921e7f9c2cd5126419e8372c84bd040bec48ca84b0f1279c91cc8a3904456396bf4282257903f3108f29228eaa03590c7fa405e4a10751346202b0706e4416c208f89899a0908c8634420e7099239080a594118411efb01796c11e42f0b888e5921272aa49d53431e3302d95921c85d51441d3445c84bb240ce4409b9cf18646736e4313990133982ac6d0bf29720c883da40bec40df2b286bc0926e4650c79cc09b98b07e4ae23909707c4b1ec0bf2257144550e0a51bbb4d899e809799429e44db841ee0ae28f5d6300dd840fe4514c905dbb00ccaf3e9043f145d63941c8ce89218f314176a70839960151632910755914755023c8ce91218f2982dc8504f292177513222087e20cd92519e451ae90c786404ea340769021c86d02443500a1acb39da8229205f9120f649543a6956342d4415951973ec8da54883a2684bcc91e51f5610076ac00e4b01db27246887ad600acbd8a3aa60479ec8ea85d642839080ee4343a6447b145d4322c647708405e1e91bfd60c70c940d4e504c8975c81ec0790f4724c22379183bc89217d491bc8fa1c91c710893a281975d01ec89ed9218f19827cc908c89b0801794c92a84b2fc8ca51a3c74bbc883aa88ea7ced9415e0a450d5b00d9212ab2a3d4410e93425e8ebc4442649b0bc8da5640de041ce4511241cea407fba507b2362dc883f040568e0bc8a32091133512b54da847057840362c02e4510441ce440ad9a50d59394a7a1c250bd9d20d72ac01909759a22e09889a47cb99ce1942fe5204591e2990f521837c0900c84b2ac85f40c883d288ba740059dcc86359c89b5042fe0a40e8f282a80a5005f66b0eb2725a4016112bc8cb226f22487f2d213a0644d4b02b51c3aa206fe214752c08d9591039ec873ca641d4302cc8650dc08ed509ba4420ea181dc89ee1218f82853cf607b2737e90bf90a286e59097326479a6405e2a10f50b03e45102411e8b0379944690bb72405e4689ba4687ac3b0620e71192794908c87d3a4016c7931da510647dd0200f000a422e7f90f559421e0512e46550d4418e203ba7444e84472f99e871d9eb3103909d53e42e21a2266a426e3b2185cb5f8b3b43561c1bd8502810751445902f7903b96b0a647d9890c31a88ea214d51c720415ea641dee48fa86d22a4709441909dd383bc16668383184136101fb2738490132182ac1ca31e07f9811ca608f21855d4310490973dc861baa8992061c7f288fa26945d0e456d2321854b0b90134521bb5300b23f5760c5f5642f7983ac0d4bd42e36a2765d31eb9c913359421edb20aa87e011f5922d902f0923aa415460bb8640feea41ee72a3a71fd4e931a9a8a32c82dc1586cf4f3882dcd544d4a593387e19823cc80de4b531606319212f3b48649803c8fe8c411e450ad91f2ab25f37e4b63829ec83843c8647d44dfc88ba0920511349451d4b0059253aaf83aa909d21a37611819cd600ac87e411f512319003f0233bc64854e7d890ef3020bb8412f51389206bcb821c4a23c8ca59d2e327a0c86aab20aa730e40eeea21ea582351bb9488bac920ad120864f52983ec1c26c87346736ec211e46589ec4f1ab22272847ca701d900e021fb4541d05118411e2485bc0c13f58b91a8f26445cd240ad94fac216b9015d92e1ca20e420379ab0564837c901dc50a79d024841c7408b27370c85d5ddc8a8804c889f6e8651564e714803ca60772266a90c7e6208f3d216f9244fe1a83bc3c839c070c375d8245f62b0a79d01cc8daae44dd0409c8831e411e1b00391450a27a5b23bb4954d451e6208709a38ef208b2002820ab1c19a27681e1731466205fb246d4e5441eb323ea2886207f0582fc9501f2d79626c7004136901f725b1b519d33912f7103b98b0b5b05a902f96b1164e54881fc894590074972c8e5146403e9218f21a3667206d9db15596d12200f3a037969401cdb5a40568e08512f4903b96d2985fa98418e9540d44479745830ea72c92147a9428ee504b92b8bdab12ae44f1082accf07c86d23a4f06c0cd850e82067c206b94b0ae42f0d900bc040d451e8202f8f1c59e625ea204290bfc2445d0a1075998bfad5c5c9250c79133c90bbb0a05da241de5201790940d46511b96d05d94da690632110358c87ac9223af0a30477689017201847fec3202d94072c8122215553951206f220ef22563207b0343f6123890ef50203b1609f25705431341212b2785a84b25510705825c0a4664131d12751020c8ce69823c3684ec929eac724a882acf55d4301fb26774c889fa401e4b04799339c8b1a044f5903ba27ac853d4e51564e594e9361050b87c003951227dc91951ed4104561f36c84b608fe208f2720bb272ccb472d208b26b0b9e83fa400e404f764c01647dce200fe203791022c86563c0fac123ed991cf29800c8899c90970d44fd8414f2267720ebb306f9122c90b75c405e6670c83006a2be2d206b203a64670b44dd042a6a288e208b8b03d6a129b2a31082bc7c218f1d823c46843c8a3290bf70c86304206ba320ea28ae404ea447c73a20aa7368c89f1004796c12e4b147902fd10279144490cbc020e7594076d022c82ae942f66b0fe4af0998744e02c879c290bda40db28860418e4500f2a0aba89fa083ec1c22e4313a4d7e75807c091c647fd480fd1260e82790c86d4c2bbc4404e4b13a900739217789f1f31381200ffa0379501de45851a22a07043d864d415ea34076b98bfa094590b57189ba8936c85f8944ed4a4380061dc9c62a00b92b89a89b4082ec9c13b54b07e44477209f51203bc80a914b61d441547c1c850a79a90539cb7298acd5ba6ceb58ebd99ad35596afba62ad7296aeeedcde9a97afd9b0d8af56e9dab5cc157b5df6ca5a7bcdf9ea9cb5fb2a61af5fdf5eb15fafd695fdb4d1eeb72f7305f0ebcb69e39ebbbaec29afcb612bb9dc855b76f9edec00deb975f9edf57ace13ab3d6f496b2d9bf6acdb9eb76db300cad73fd7f699c99317f6c89ecb9eb776b97c25d0dde582833b3667fbbce5b7860dbbad0118379626033c5f6025e5f3f7221fdd33d0cefdcacf7ea1b5d2dfaa0fb3e85a71014b0926fc820c51c7b4c880c1310462908381bf45fc102e48c082044c4b9ba180ca0cec880820526344932372c009911a231adf0f0c2d072481642aacd03d1037d8f0612c80c085263f2e70f06a648ebf5265848f9271c7f869a1890a6fbc6b0197d01504b6607db8a649f12f267e56b0f2b382011397a6a00973dc272797dba030b8b829b7c984abc4c9ad137471656e53154e9edc1b029f85c2ed59ba4e6e5c9fa80bc5bb3d6edca726f747c9854a736d607c16ee39abe2ad9a3c6bb6b75ae3197be179445cca8ccf8be47276c15741727deab850045c1f3e2e94ee964a6fa4e4deb4b8b0289eb1369e97c56d91f0baa5fb53c28582ba38016e1306f73e3e088baa236e8fd47daa72950c6f1524f7f5e6778f3c6933bc150bcf09c117a9b92e177e67e596c4379a5ddc21d7e9776f3f5c980df71a3e88f73cf8a573b933c05f6970cb37de48e9f240f2581fdc1f182e9420b764e08bfeb8b7d9672545717b2eb84e515c1cd675fa727bc8b84f3d974a22b87522b8372f9f15c5250ce979be8b63e33ae1ee8f1c770e984b989ae7092f81cab8749e5c96fda22bae4ff24259708bf44438dc9edd7572e1964fbe888d6bfbe3b37e97b4dd5b8db93d2e5ca7a8db1a7aa3362e6e8adb14c925ccea79c527ade7adb8dc9f5ea80caead8ecf2ae0f664719f60f756c76da25d9b028f45c365cf782760ae0b91dfbd707d32b84f56dc9e45eed394672c86e715717dd8dca738ae8dcd6339dd5bd46d12e03efbb9b83248be4a86dbfae07548b72790fbf4bb3829b709cdbd0d3f0b8c272d87b77ae1ba62f81d96db0f9e68887b6bf3594db785c4eb80b83d51f7497779d678ac052e5f433ccf756f395c180cf747810b95c2a5cbc9ef9edc09f44ee05c253eb7ceed199be4796f5c4e39bea888e74bcaefccb8383aaed392672c91e7a9717f16b952b10b94e54acd706fc6cf22e3febc70a10279d69efe8a80cb5812cf73e2b6749f0b5ea0452e1d07aecf0af7a98f67ad8ebf72725fc6dfcdb93d62dca7dbc57d719d5ad80dd7078d0b15c03336e779665c7e559f35f7c98d612e9edf055ddb94c752e2e2dedca633eecf09176a8ffb6af33b479eafa2df85716d551e8b89fb83c895723d6d3e8f85c22dd3782333b795e67549b765c3eb56b8b8046e5399fbe2f91d15f796759b1ab840185ca9209e322fbeaa82fb63c59d13bc8de089906e477827b07b6bba4db88b43ba4d45dc95f8a02cd76789fb54757f8ab850935cc2b63c4f777b22b84e3c5c9cf1364531c7caf549739fd6b8b71e2e8c867b4bfaac30ae0f97fba4c4d3e6c463593d65aee7d9715fb2df3dc143ba3d7fdc27debd41dda69fcbd87cdef1966abcd10717f7c875aa72715e6ed30d5537dcdb1517e6c4fda1e0422d717bc65c272d6e19c91b0d716f325c180acf5a0b6f75757b8eae5310f7a78b3b07828bd3e236595dd6e99d6c707de0b85044d7a78a0be5bacbf920279e33e45b052fd007576a8b5b2e4f64c2edf9e03a997181c05c291fb6bae0fa48709f88b865086f24747d78b84f8b5c5b9cc78aba9c607cd10fd727ca7d4ae1fe34dd391ddc1f3717ca8e6bbbf2584f3c6bc6b7927abe82bf5be3f648719fbe3c673cd6effa08709fd45ccec67cd524cf99d45b35707d9cb84f5877193e28f79c7df1564e9eb0029e67801325973b1cfc159a5b66bd5113d7d6c667093d6773bc9502f727830b65c553c9ebd2f9e0b6dabcee83fbc374a1e4b83c553c16eefa9871a1945c02b971e91050180ef7e7893b47777b72b84e863c61903c6f786d8d7c1696eba3c07d3ae1493be1ad4cb836423eab812a022e79ba782c019e3443deea8a0ba4bb524a1717c77502e01236c0f30ab8dde08966b8dcc1fdd597eb83bc500edc1f29178a85dba3bb4e2ddcc9e49d8c707ffcb8738e17c8904b27787d62f7a983cb1d0bfeaaccfd29ba50223c5d69fc6ee9e278b70984cb39f545543c6b73fe2a81cbc3c66331f07c2df91d174f18d4f32eb84de29dfc6eeb84d7b5a98a737df6b850b95baef9bb25b74b9e08767d52b84f7bdcd7fc1d124f190f5fd54b25596e9d2a97078dc702de56f0442754bd79bac2f89d9a4b5af0ad3cb8b63c3e4b816b9bfa2c0b9a8ccf99edab98b8383fae93024fdacf5b557009d4c4a5b3e49684bc910c4fa0dc1695d7012f5d45bf1be0b68327aae1becafc6e90ebd3c17ddaa2ea850b24e64a2571b9a3e5af8eb735c2eb4078cec278ab271708e84a01efcf940bf5c3b353efe483fb63c79d93c1ed91e03a1151c5c7fd5173a1d8b83c5c3c560017e7c06d12614e95cb1d287fc5c1b591f05880dc1bedb388f7e6c385d570c9b3c15fa9c18b736f3a5c580cf72d3e6883cb9d37fe2a87db93bb4e2c5ccaca3c4f92db13c37502e4f2c8f15853ae4d85c72ae4be3cf85d1ff765c2efa86e0b88d7d57039d50fd55125c4bd8df9ac2e2e8b7c2749b744e08be6f80583e07952aa8cb84a18b87580d7467c2c362eee759baadc528637da5d1c0ab7298e5bba79a3a0fb93bb502eec723dfd6e84db23749dd83c67586f65e5599bfaabdf33567c1e0df787ce95dab9b7356e53ab9cdcb2ea8d9278d69cbc151d973c0efc1518cf5a157f85db313d6b55deea8ffba3c3855ae4b68a785d0ff786fb2c33cf5a107f05bbe499e1af92b75a54bf0be0f96af2bb2eeecf9a0be5c6f5a1e13e11722bc6c4f39e78d60678ab3aee6dce6d0aba47f0338bfb23c59dd3bbe5166fb4c1c555b94d285c1f2a2e54eb3963f25557dc93f820092e79c8fc951b556c2eae87db04c82df778a3139e35aabf025e96b02fcae2e2c8dca6296e4fec3a75706d478fe5c42dfb78a3149e32e1f30ab93d3ed7a9cc7d75f03b3dae6d90cf62e08efca0316e39f54646dc1e3eee93d1ed795da70d6e0f16f7e9752903e179c95b4af246443c6925bc55094f9a226f95c5fdc9e3cee1e07ec307f96e857827512ed0982be5c473b6c65b19704b21de88ca2df57823139e343ade4a89e72ce8abb078d6d6b3c275dae33eeced81cfeae1da58782c439e3227be0ae8f6a0719f945c1f25ee13d5f335c1efe6b865f18d68f7b6f459765c9f26f709847ba3f259415cdb1c9f557471716e531ab76788eb4487c78fcb1795dfc1717dbeb850b6db62e17571eeedf65965ae8fd38582727d08b84f71ee8de8b3dedc5b23b709cafd61e24261ddb290379ae1f61caf9318cf5a056f35c8edb1729d8078d278de6acb73e6f0452fdc9f072e140fcfbbd4678b0b457bc6f4789e18464e5489b9f502e3793bd726e5b16eb85472c1ad83858880dbf3c57ddab94a12b8750ab83f245ca8a78b0be13699717d58b84f51b72cbc93db254f047f15c6f591729f54e03171811cb974a65c9c98dbf4c3bd1df059275c9f30f7898a7babe1c256e0b9b92da2377283478b67ad84b74a12495025c865accdf376b838056e9399adc25c1b94c7aa61f7e5fa4471a1faa4c9792b262e6ee9367d717f04b8506bee6cf24e48b84058ae140c974a7ae970b915f344323c6b4cde2a8e6b13e1b1fc78d2d4bc959b5b5af92243ee4f7c500597b0ace7199fae287e87747bde5c273a2e90cf95f2dd7a25f2bc00ee8bccef04b994213def9167cf3cd113b7f57a232aae4d82c78ae2daeef8ac036e09fc223e9ec5e19d105dcab03c4f91dbe3c77ddaf1c4dc9b049f75c425d011974e001708924be7e856ebe977b6db83e63aa1716fbccf92e192e783bf72e4b6d8bcae4c910c97af1d9e57de9f30178a8afb1afe2eea2a71e0d611de56ed8db4b83e5a5ca8d925ed81b74a7a4e03fe89d1bd95f059825c5ced366579d28278ab199eb1169e37c4254f93bfaae2f63cdd2705ee4f0c176a907b131fb4e5599be1afbeae2c7e77bc3d4cd7098edb93c87d82e0691be0b16070327cc6d6781e16cf1a066f65c873b6c85b31707d86b84f8f3c6775bc1594fbf3c69dd3e5fad871a10ab8b6338f45c793b6c25ba1707d50b84f7a5c9f1eee13234f19f079845c1f412e94b06cdcf7f8202b9e3c588fe5e66988cb53c663f5ae0f10f7c991bb0f1fe4c0f5e97d0af30ae1fab8709ffeb84a84b78eefdeeeb84d3bb705e67559aa08799ee5e71e9730219e777429037aded37575f1bb0f2e6d463c56f272e7cc5f81707112dca6162ed0ec4a29707b1eb84e3adc1e18aed31fd776f55917dc1fa20b15c2bdc5f05952b7cc7d511e17c8e84aa5b92d105e77bc37153e0b79cb04be888efbdaf9dd14b745e6751c5c5c23d7e981fb99fa10dda736d72787fb84c8c5595d270b2e0f1c8f05c1650b8bdf95cfdad05bcd715b635ef7e5f2b8f1580e5c2003aed4f09671bc11089773d0e279e1afa6ae8feb3e89b9aeacdf15f13c809ec307012f816ab8747a2e5d1dfcee7729a33d0f8fdb1fde49d1c54d5da72dcf58d4f3c8b817f241625c9befb17cb82d0f5e77c1fd09e0422955a1b93d7ddca79c14666e8f9cfb54c02d7dbea88bdb03e63a41717b7ca21faa72b8e451e0afb4b89c137c11d3c5d96ed396aa0f2e8f208f85c1bdcdcf12737d94dca733cfda127f55bb3d255ca73c2e50ef4a2d5d5b0e8f05c92d795ff474818c57ca8afb3af33be4b376c65f2db9e5246f54c47d61f03b3c6e2b88d7e5707b64b84e73ae6bea773e3c6b7d2b322e7700f8ab0beecf16778e03b7c786eb44c80512ba52155c9c96dbf4c2f569e03ee1706f80dca626978791c74a7ad2cc78ab21ae2d90cf123e69596f35c64e84db2adec9d1b3f6c65f093d6769bc5501f736c385a57071775c27a1ebe3c68512baa5d41bf17009cbf2bcdcf559ba4f705cdc93dbe4c1b5f51eab87e7ac8cb72aba408c5c3a0f5c1f00ee139a5b2c4ff4e6d2b9b8f23645706f78dc26dba5922ab7ce946ba3fa2c2f976de39d84b9e401fe9517d726f55957aa80b837073e8b875b9e79a39e7b03e1b3eab83d41d769e94953f256163c6919bcd599fb83c69da3e5fa645da830cfd807cfbbe1def6b84d4a2ea7165fd4c3bdc93e6b787dbab8503b972f0a7e47c7b3b6c15b217271addb34e52a09ba7504b8b7003e6bcde54ce38b84b8adbed113b75e323caff7a78c3b07cbf5d9e13e29727ba05c27142e6b6bdeea91db83bb4ec75b72f046e5a5ebcdef20f8f5927a9e0017e82bd5e4f220f258632ecf1d8f25c16dd1f0ba142e8e87dbf4c7b366f557c1fb83c0853ae172b6c15749f2b4b51e0bceedd9e23ec9ee4fd69562f29419f155ae6b0be3b37a2e8e88db84bc3fc10b65c3f529de27312e6344cf5b73b963c35f91707dcedca734ae0b8cdfa579f2e4f1585a2e5d25fc6ecaed61e23a51dd9fda85225e5cec361ddd0af044afeb43c17d4ae2022972e934707f962e141cbc05ee567e76717f7017aacc9336e6add4549df0ac11f056765c5c14b7e9913b1f1f74c56d59799df05993fa2bddc5156f1314b7657c9d98db7af2467054215d5b97c7d2e27246e6aba82e6ec96dcae002f9ae14d3251014970e933b83de4909b7602a3cef817b0f1fd4c0f569dd270d2e0bc73bc1e0d2d5c0ef14b8ad37af4b734b39de68e992e782bf42e39ee6cbe465ab8ddfcd6e29c61b253d6753bc1593678d81b7e2e3491be4adaab8b70b3e8b8aeb23c37d12e4bed0fcce904b99d0f3f6b83d66ae531917f7c16dbae2fee070a112b93f298f218fc5c1ed51ba4e6b5c17d5ef80b8370a3e8b897b63e1b310b938186e131e4f5a156fd5c3fd79e44ad92e6e83db04c4bd9971615edc1e04ae530997878ac7badd5b9ddb6474712adca6399e32089e77c8a50d88c79ae47267f757636e8f98eb64c5f559e03ed97023b9b8a7eb54746f8edca628d78781fb74c325cfce5f41716d733e2b787b40b84e6d5c1f32f7a98c5b1edf68e7bae8fcee061e0997b42ede0a886b3b3ed61bb7bce0a1907059c6750df2bb14ee8dea3601ef4f03174a87a792d9a583e6debeb8302baa78b837252e2c88cb1d30fe4a86670debaf84f7478b3ba7812a1b6ecfd47da2e0fa405d2829973b75feea881d16b754f24560dc1b009fa5745b0dbc4e77499bf256c4674d8fbf2ae0dab4f82c5a1508f7b6c1676971818eae940bbb366ecbe78dc2b8823c63783cef8b5b16f146474f1e491e6bcd93167bab2ccfdaf0ad16b92f31bf73ba3d61dc27db555274eb10dd57f9bb246e8f21f7a9818b1b739b86b865932f32c30948ee2d8d0b0be396486ff47ad6746fe574ebc23be9b9adf2444cb7a7759d34b83e4ddca7ab8bcb729b5c78d65e782bac8bb3e2364ddd263dd10eb70ce08bc478d2e0782b246ee9c71badf0741df13b314fdb7cac37d7c78a0bf5ba3c623c96eebaeafc0e87db63c67dfab9e429fe551b97ae097e97bbe5116f84e539abe1ad6a9731299e17c56d65f03a0aeecff14291717f16b850385cd2ae782b1f6ecf0fd78991fbe3c69d63c12d054f54c2c51d709b8e4f5a98b7fae0bed8fc4eeaf20cf25819dc928c372a3e6784bc15f0f694d72983fb73c79da3c1254c80e70d706b96f0452adc1b019f45c2bd35f92ca6fba3e54221717dc6b8503e4fdaecadb4dc928f3742e1f650b94e3edc89f8a02af736c76d92ddd707bf2be49624bc51d105f272a578b83f3b17aaf88c4df0bc149eb535feaac99d7d276b9eb325de0a80a72c80e7413d6b3dfc95ebfa10ef5318cf89e58b962e2e779bd43c6743bc95cf7d89f03b49aecd85c742e4faccfb84c1fd69e242655dba7ebf43e0d265c0ef8aee8f1e77cef05ec7072d717f3ab8505bdcdbcf67a1b925e29de4ae4f9cfb24e7f6e4719f0eb84052ae149a8bb3e036d5706d193c5618773c3ee889eb53c8856ae0e28eb84d88dc62f14e24b805f3442c5cdc05b749875b3c2bfcd5244241dc1b98cf9ae2fecc71e75cf094193d6fceed31e23a415265c2f345f4bb2faecdcd63e9718126b852353c6b27bc15d5ad697b2721dc5b1eb7e9763973e0ab16b92d215ea7c3c535719b1ab9add91b61717f7e2ed4996bb3e2b360973b42fc150af7a7ca850ae216ecf73c28cfe9c017a5b93e775c2803aaaad8056bf33c06b2b8dc51f3574b9767ce635d707f7a2e949927cd8db73ae2de1eb94db9db73c3752ac4556bc16aafb684c1dad7f3e92a5b2e5b8fb5e67c8e6b5dd784b99e65ecf27f95b0d7ac35b3b55c6dcb36ebeaaed23a6b77c562a54cd67abada92c566c9e38af93c67f97a1536cbf2354d49ab371aedd63fe525cd45fbf5edf9b3d9df7a9fede05ecf723669ad363b7d6065135965d3359bed2a5b36da3c5f96df7a32733dabcdd76cdd1f5950cbf5c5cd9dda3200ca966d468483f1cc68ed6bf77cc25ab3b857eca755cec3533493a25a397bebc9b7ce5b321f5aac9c890f6cb6ecb6af125602edfa5956c6b6b5f6eb27f673ebe6aacf2572b5fe1680135c90c9b30004b8769be727c79e6798ec17930f609b8d5636067bb662bf5bfb02ae5dd92c95005dbe7a0a70d52682b98c8649d935eda0611ff07cdcabc9b592c0014fa0dc1a81930692bd6ac121db43025cb3a2645b5f9daf9fd7e56de7de5c2b317ac57aacbcc9b01fdaad097099acf673076eb5f69c31294047c0a5030fb0d036a20778905759fb23013f0fdb011a00c38629141df1cead8dde55f03c6984d96176b26d1a7888f31d5d1098f60d2d01760d0b6020ee13c3f096856eb0ed824179847976fdbb9ffba8955dc7de6819f717ec3366b86351fb25c1af01784b778359805f3b974f2da66f286d37ceea8d2f1fe3393fadfbf1e370ebcbc27f8dfbf06c35324fd1dcdeff800cb062b324850d9ad9c46f04036f849237c2f646b8be08ac2f22f91484f922c4782681c333099e2712aedf32f53ca28e2782cc6f61e4b7d4f1435cfd10114c21723f411d0f04d60391c603e1c40381c04fc0fb1f0479f2d0c5ff80c0ff409b3bf4f05ab278ee307b1fe6bc1637af45cd3387254f1c8ede8701be832cbe07353e0bf2494308cf83179f8587e74186a70c54bf03d0eb90fc1c46f81cf4f81ca03e87e0e3c0e4e98294c701f737d0f91b7678b2c0c893053c6a687ac6fe781a1c791ac6781ae478c65a4f138c9e86109e06263f031a3f8314cf119ef8192e78196238e2afb8f157ca78c2393edf54f13d31fe05aabfe2fb17fef817a09e6ec8fc0bc47fc1833f4afe0b7d1702f923a877a18577e1ccbb70400b82bc0b3cdfc20bdf0214df82996f818c057f74e6b9068fa712167f34fca30e9e4a509e85e2934d1ccf82925fe1915f81ce1f29f044e3f44c73e689e6f72a44bd0a73bc0a71bc0a5f5430e45520e35518e055a0e25520be0a41f34c039f02f25320e353d0e253c0e2bd68f12910f129c8f029e0f056cebc0d457c0a2dde104f9e0e4f325c3cc938f162b878141e782bb447a1fc13b2f82a4e4f1ed29fb0e6697ce24ff0e0691ce059e483ea4de0e1497ce04d88f34cdae3ab68f04cc2f24c3ae0abf4be84a32f613e096e7c953e09c427520b4fc23fc2203fc2214fe5881f018f2707273c15035e04385e84369e1b48f154e6c79112e3c45383113e4e051f47cb14a387a3c80cc3c6334c100f27cc334c011fc120ff668e375c9e60823e821e3e822b1f820c216819fa10849e5d78cf2e023c2da8f3b4e09108620f81d5d3022bcf0abe785680c4bbb93d9d1831b7e4f06d1e786ef97d9ba28780c933c5f14c2c3071f04c581ef8e2d930f10f4cf16c647836719e12fc3cb1f87e89875f3ae17925f64b162c1df0ac22c58330c4930af141387aba08f83554fc9a107e0d09bf46cb3bd0c3af61b246f84a6d3c21a8e109c1985752f36992cf07e4bc1a371ce0bd1a35afc6e8d3f49e0e087d1a163ecd9b4f63e6d34c49f3c0a751f268c83c1a3a1e8d19cf06c678345c9e0b503d8368cf0568f8336218f16796be012e7f06cc33e8c49f71f20d04f01ff4f1fc29f11f3cf1661279334abc192a9bf9f932435fa686672f4d192f4f86852743f50c6479f2d478325f9ecc9827e3a4ca1e7f02157f1ce22e3c7c971c7e01429e81180302fc31cb1fa7de38c833377b239a05d07c31842f12f1c512be68e61790f244289ec8c6179f3c15a0f344429ea8c213cb7c92d5131d782a507ba2059fd4f449587c52079fc4e4936e3f94e48f1f5af1c3257e98459c1f6e196ef9a104ef811bcf029a844d5eb8c07b80c223251f09ea91e878a4203e38c8239d79a4a4e7908fe7ee8b0fba81f4e453e81c40f763b09e4ae430a68b1fa3c58fb9e283233c9da8f0c1a41f63e6c738f0630ef80eae3e68f41d08f21d04f21d8cf01d9cf90ed43c8390df4195671021cf010dcfc1191e3c1085e7e0c9237d790e5ebf41d6934900bfc1231be8f11ba8f114e08d077e83d8ffacfe27f5628eaf811e2fc68017c3e43558e4a92491a792275e83a4d7e0c96bb0e435787d06937c06759e3f847c06537c06bacf60f718c8f019ec3c06903c06757c065261983e4c97c700000c94fc0569fc055afc0552fc0540acf01710ff822f7fc1edc148f260927830651e8c040f26f8608a7830afff42e7bf28f2a411ff0b20ff258e5fb3e6398362c27f21e1bfb0f92f62be64f9f2fa2faef742e5037a2f21bc97a5f772e5bd30f03d40de4bf95d2279e2004897299ecb1cdf6589efe2c3d335c177a1e1bb90f91e185d42f82ec5ef02e6bb50f07c2df0bd22de023b2c00e22d88f316ccaf408c0a3ce032e5b9049f8b03578e92921a35608401c695b304c21a2535195c39c62b07832b40ee0ae003e302597181beb83f645cf113c6fd01e3fa60e0f3c0f5c1b2bb38032e4ec78410f716c6bd01f12e33cc30038c77392582c821862009192490397f44f5c1c71e7a3c3935e581879c778df1c5bbeeb8638c31d240e35d88bccb8b77d579d716efdae38da8e18da8f22c218f2707773cbd64f908841f4100cf00114f9ed3f3002bcf03b408d1f303cd873ede87303d8cf039b8791c80f81b96de86a00c31c4c0c8c78015831c3198d1c2c330e55d80f32a34f22a787980840d7e0411b0f837737c08117c085ade0d24efa60f3743ef26e8db30f06d5ecf04c29215bfe4fb109c582324c7abe9e3d34c3903e7c98479324c9e0c00c6305ff4f2c5264f5ce3937a1ec47ec8e53dd0e391a87824333f668f1f83c76f70c08b497a0dc0bc06563e032d04f90b80f80b2ef042f55edcf82e277c97a3afe084ef52e6bb70f15c0af814027852917a0fdafc0f0cbc0f59dfc3203674f134c47e863c5e06e1cbd087a18787e1f62b6090029c4781913fc1f5262c7d0953be049908677c1c143e4e040fa7868743c3c3e9e0e1847938163c1c091e4e9687f3c0bfc1e1999efc92020f829a577af26a967c9a10be0c0b7fc4e18d82bcb1821fd6f9610d3f2cf31eecf01ca8790e8a9fc1139f41041fa6cc8779fd0544fc972eef8590f702c777a1e02d30e42ba8e27bf0e26ff8e389d8e28138e07fb0fa1f1af91baefe86e0c730f52df4bc0964fc0865bc0f611e8e0d0fc7f870c63c9c06de4d1ccf2681eff2c71f993e03317fc119df25821f93c7c320c3f740c1bfb0e56d60e08f5b7c1aa1df80851fe1e993f2f8314dbc17d87b59e055f83d97335e860c7e0d10bf74c68f90c38b40f471e478140ef934723c191fbe08237e080cbe07259e05aa67fae141b8e3c104bd9a30fe0b04df83208fc31cff4220ff821d6f029c6f23c833bd1e4d215fe68927c3e58f3ecf81f1c1d8de0b02df430b6500f8a1954792e16d48e0d324f21d08f01628f03dbcf92429df25cdff30e649b8fd9b33df46836792bd9a213ecd9427f3c89311e1bdc0f03d48f9e30b6f040d7fc308bf82d5bba1f220d46104e23980e037907a0bf8782e2dbc07817c0f5f3c0e5b3c9c2abe0219fe8700de0709be07493e87de9f70e54d08e299cc3c9b2cde8326df41186192cf859067d3e64778e0cf54f144111e29921f03c70b69fc9a371e0c19ef4589f702c0eba0e56f18e057907a1b0af813949e04481e4e1ccf04c38390e59506f862100f6ff8314c6f43d29f09e18f6dbc1721be883abe081f3e880f9e072a1e4e02af84f546367ecc22af019b0743e5bd44f238d8f131e0f12928fd08867c1914be0b1cdfc5cc73a9e2db189f78c4a750c827613d9a17de862bfe4c181f4314bf42960fa19fc198ffa2c693f0e48d6f1ecd1ccf65e791a0f80accf83755ff05806fb3e6d9b8f01ef4fc06863c17e1c7809564c7cb70c2f7d0e655a0f32494f1719a7c9739be4bd49f20c37bc1e37b98e04b003e0946af64fb347cfc073ecf41d267a0c7f730c9cf90c6bf89e39792fe84127e040f1ea9f51900f2313c7d1126bc0ead57018e27e1888763c2bf99e189a07a2194de87ddd3d0c8b7c0c1b7f0fa13fa6e0cf9a5db7f90c717d7782f7f7c172fbe8216fe863a7ec9ead37cf06400f83171fc0b438f821e9f04c7c3507c16e8782545be030bbe08a16f21ca2f31f933adffd2e6b9a4f040d0791c3ef8151c78367ffc9a3d3e0d93ff60fe0d36fc0b53ef4218df02046f020befc6cc0f25790d24f80b5c781ce4bc065d8c18e2bf14f25c76af46cecff0c6d710c71f21f91924b8aa43c791469ae8430fa7cf0581c7e782b0e38d36d640c30c32c400c30b2eb4c0c20a2aa420e28a086fda7c2e88a5cf05a154e6581c2275b08106185cf0a54b05147c0e88cf4d90801f9f937d2ec6c5165a6491030e248c2005144f24e1420b2aa420429c10dcb059323e293e7003143c19ee800447551a2060005aec498947d31d3be4f065f09ddcf00444e513cd7f0b28a080c20823e0383ada83722498608209264a36d040830d36d860030b0b0b08208088c55aaf213386cc1832e3cba3cb83175e78e185173f71d45b2f2a548a0516504001051c70c0e1830f3e78e18517473f3f474770c0c1061b6698f1170b28a080020e38e0f8924affa142850d36d860830d368c30c208238c30c20c33cc30c30c3380000208208000020c30c000030c2a547cf0c1071f7ca042c50b2fbc98071eb8e0820b1c70c0e1c7a73e3f58c0d1008e06707e8052d686499b0bda5c30400515944c983061528152054a152855a054814ca9023359cc6439321acb52261b6080d7ebf57abd5eafd7947280b294c97e4e91fd5386536ac297b3994ea71b608039616f3278f325ec72800160b1f6077013a6559651a2e4724545ff054c6098c030816102c3e60b9b2f6507180006d3e95aad010680c15ef365cbc6ca364a149d6e00186c800160b034147c19258a4ed76a6d41b3e5cc046726383381995c4e8a94586c800160b056eb78a5342097331e95adb21525ca6fa568a568a568a568c500dd4f25894a1295e1142a103cd0c002b81d9a0cf64c7ccfc4f537297f23e006299a4062881c40d0e26d27bc2d8bb7312dbd0d84b771f1b6083608d365cbdbbc781b95df01c48e387e678fdfc100cbef3cf03b57bee694c6531b5f73fa9a195f0bf3b52b4f8372e369653cad8da7413d2d87274f6be0694e9e86c103b4304fc3a240130094047fd6c0cf765624f1b2315e86c4cba0ca78991b460a3c71d204002540bcdccb1a78d90178a0e1a4e55f1748f02f30af2fff32600205b01850a535409501aa0c404114440e79402818402bd80a0008c04fc91ee50b2896bbfd94b4dab498aa041708f365cb982f18406b800096bc5a3e2da081f080830d33d800830d30bce0020b268000030a268c00c2071e7e2f70f8b233f0138cd267381ac06030585941cc0018ac562ba080020ac0b5b960c9cb9217a50a942a3093c54c16e391f168f894e153a6f440b651ce2683e50f660ef5e2800a942a383301192cc8368653a67cc1051794b15aad56abd5983061c2a40683b5bc8ccd7939cb030ea0d166b3d7abd52acbb25633a034e0b25633c080397b7959ab196040cb9634daebd56ad56a0618d0d66a68b6a0f9f2cc041f68c962268b992c66b298c95246023258c8602183850c16e391f1c878643c3a2a5ed66a30d89ced0107bc5ead169c364a6abe8ccd5ec666bf34e072162b67bf6c199bbd8c95b35f9697b12c5fc6662f0db89cc5cad9b2b22f3e097563867cf275f972e1878acf18ee9d0471e96abd501ab796c83319c6a549f19c12e11913e09dd4716b913c936c5cd9d0271f789653bc1002cf3280b755b96c3979a10e6e8d8c679286e72d7c799eb387d61c7926cfb833da53fd6e8dca33d9f38455f143745c99061f353fa1b19d7752c073aef9969ce765feac670c817f62e519b3e2735f3ca707dfe2e0ce4878aa282e8d8de7140fcf9ef1337665693e39c3ddb13d957826d95c19179f6cbab2e22755b82c4778a19fcb17f187be5c4ed84fdd95ed3ed9e5b9951f914b6be13915e6d9e2f2426b9e8d9af3cd2cc5bc50ebce829e4a8227cc8e1f02e41973f24f6acf79c7cbc6dc9a1fcf2415cf52cb3371e0d2a89e53715cba187821a82b43e293673c6353fec99ba7abcc0b1972e9f2bd90d39d85792a156ecdca33f9f37c41f1436bee0eec99847aba903fe4e4ca1cf8240797afdf0f3d7069913ca7d8783ec637caa589794e2570d91af3426f9e6599179a5dbe723fd4c0b3dcf99d2dcf96981772f37c882f934b737a4e31f12c5165bf4f5e7077849e49a94b1b3ea774cf2ef2b380a71f972e487ea880cb32eb852a789e3467587c550277e767f28e67c3942e0d5e28902b53e29369dc1af199e45d19984fb2b9b3324fc5c3f3375fd895bdf14943ee2c83a762e1cee23c9513cf19416b669e49e1b3acf3425a9e85f2657ac636f827815c59ed930adc5a1dcf6411cfd9c097733c6715ff7af29cedf155115c190b9f5ce23953285bae17ea726b433c93689e9d62f6852f22cfd83f3475694e3ca74278b68b5966f14250aecc8e4f4a5dd9244fc5e4ce624fc57bc2a2f82139ee0c8aa77a7ab68278a11deecc84a79ae2f2e5c10f79b93b073c93539786e639d5c095c5f149449e31193eb7e6d6387826a1dc19014f65c193f6e4ca19e3ee407926ad2e2d89e7d4d26569f5425c9e312cff048567ebf64217dc5a11cfa49a2bfbf924efd6c63c93529e31383ef7f48c21f1b9226eedc973aace730af2330d9e2f357e88844b979517e2e3a9c8737ef1af33ee2c85a7b2e2d272abf9cee72c335d4d3f14c0ad41f04cda9e651c2f047c36882f2497ae377ea8e739dd74d6c05391b93536cf649567abce0b8971e992e28760cf19a5b3e053112f0d83e7940197a6c6734a87e77a7cabdc1dd933b9c79d5d792a36cfd964c29ef8a1386e0d8d6717df049e3f7d1fb835359e491d9e13525ad673ca8e5b83e109c3734a83bb73e549a5e4d2e5f3426e3c5f0ffcd0945b33e03935c9ada9f04c7eb9b39da70a5ebafef8a126b75e88fc500c97b0273f04c4dda93d9351b726c63329c3b395c40bfd706b4b9e5389dc1ddd93aa9776c873ea8b270d7681c2b8840df1436b5c59944f6e79c61cf8274ccfadbe62ee0c89a7aae3ca22792a016e2c924ffe5c999a4fd2706bc8e7ef0fe4f2d5fa2128b7d6c13319e559c6f04202dcdacf738a904b53e43925c625cd802ba78c3b53e0a9c25cd80e3f54c67376757d26b8b2053ea9c1a53df19c7a7377869ec946ee2ccd5305f11ccc5fe7f9135f27b716e53975f59c45b426c73329c4738e67ebe78530b88405f243865cb620782133cf16f19dbab2269f64e0d26c784e215d1af039057bc2fcf8a1429e2d2b2f74e689c62dd8f087a2b8b3179e8a8b4babf29cf2b934a6e794952b5be393835c991b9f445e59059f54736b663c93365cbe2cf8212d9716c2730acb73523d6d8e2b73fa2424cfb2f736279e2f511a23cf29329eb0207e488da72b86177ae439b1be76c6ad55f04c0e3d63673e81a83b7d26ac861f0ae33993f3550bdc99d2530df1ec213f859e312f3e77c69541fdeb9304dc9a0ecf24f1d9d8d7835bb0363f64c5a54bce0f015d99109f0ce3d9aae28594b835a867d2895b23e2994cf37c71f1436eee0cf654bb4bd7103f349f33836f5970672c3c551677c78127d5ce8d7df00324706b0f3c933ccf96042f94e6ca8c9f5ce1f2f5e58726b8b4389e53413ce7213f1b7365007cf2f79cc89f6d706b709ec92c57b6c127e33c4fe45bc17316c9bd0d7165727c3291bb13f44c327269803ca7acb833e15315efcece33e9c7a569794e2d79ceb2aecf964bc3e23985c2ade5f14c2af174ddf042923ce7966fc16e0d81e754f2d6989e492bcf2ef99eb9b5179ec90c9e2ea1179ae3f285fc21189e73cecf7e77c7c93349e739cbe3ab18b8b3189eca8bcb97123fa4e6d9e27921305796c2279178c21cf921a9674c89cf4d7169649e53c05bc3e09934e0d21c784eeddc18249ff47996bfb74d7165147c12cd93e6ba3f653c5b01bc90069766c573ca84bb53f44c42726b113c93b767abf8665d59224f25bbb22d9f3c736b863c935e3c6139fcd018cf970e3f54e6ca40f8e40dcf98139fbbe2d6b63c934d2e5d5ffc50edd205e78518b9322b3e59c7b354f23b7f5cba14f9a101ae4c8d77f3b39fbb671fb760517e68885ba3e0990cba3bb167528f1ba3f3c9db256ce8877eb832363e49c8b3b5c30be1f06cd3cf9e67b9e599447069579e534aee2ccb53b9b9b2339f8ce1ca18f8a49867cc8ccfc1f18cb1f1b93c9e6d61c640f89c0557a6c22793b8b21d3ea9c533a6fb2749b75e8cfc900c5756c627fd785690ca8e3e59bcb53acf641a57967caaa02b23e09356eecc82a722e1d9ee34f0a4aa3d636d3e07e6ca0cf8e495e73f7c05b8b2283e09c773acef06b7d6f44c2ef19ce9ebba6cc9f142545cd9119f34e3592d731e2b7be2936e3c67817cd5952b33e193453c6be58bc4ade9f14c3e716b313c93622e4d8fe7d41397967c4ec1716b8d3c93663cf7f2d379dee4b7e3395f983de13bc81376c20f697165417c128c676b851782e1d290cfa92e9e30397ec88f4b9be039c5e4d68e9e49a0274cce0fcd79ceb4f82a052e5bb9172ade1d26cfa42257f6c727af9ee59567d2f36c1df0421edc9a02cfa9aa2bbbe19356dc58f29301dc8201f0433d3c3fa02c932f44c1ad95cfa93c9edde12b756b343c931d5cbe22f8212acfb2f5b5382e5d1fbcd02197f5e00b7565533ee9c1ddd1f2a45a726b493c934b972dda0b7d79bed0f8a1112e5f597ee8e8d238784e4179c2dcf8213e2ecd82e714d1ad213d9346cf57cf0fe92e6d87e754f1ee28792691972d365e488a67735f37b7c6c83349c6b37ccc59e7691d3c67c98b93f26c69792135cf79c5bf0678be48f8210fee4e9367729167ebcd0ba97069739e53575c9a1ccf29219eb38c972170652b7c52895b2b3e93bd2b7bf2c92997af303fb4e53979ad013da70ab9321b3e59c5b3ece185846e6d856712ccad41f14cc2b97cf587147856eaa7019736c7736a88676cf84f12b933159eea8a2b03fa64efce6a4f057cb63878a110aeac864f52f17cf1f043669e312a3ed7c53306c8e7b06e2d5799ef93609ead235ec8874b83e439b5c6d3b5c60fdd9ed7f15de07911df26cf19205f65e5b26dbe80dc8261f0434e5cba92f8a1d6ad69f04c22706b579e4925cf7a99ad255e0888e78b9456c4734acd9561f0493797a6f49c82e059e27e27eaf9d2e287da3c5b39bcd00dcff940696b9e5311dc99034f55e6d62e78260978463dfbc5cfd63366fc278b5c99089f0ce219a3e3737b5cb6842f44e6f215e7b74991eb8ae4773b3c5d77fc0ece7336f556532e61593c6f82276dbed595fbf3e442c1b96c13ef84818b63729b34b837486e93eefa4071a1beb7e067dd706f4a3e4bcd9dc1beaa88db5a7a1d99ebf3c885d272713db7a982a78de7b14a78c6f878de18972e097e67f4ac75792b426ecfbc4e185cdcf13649717d0cb84f235cf254f92b316e4f9aeb94c6e5ce187f35c3c505709bc2dc9e05ae930dcf1a046f15756fc8db34c0bd99f9ac352e8eea3a71b940205c2931eecd8fdbb4e4dab03c16141727749b3878d26e782b176ec1e23caf810b84c8a5b34015f1d95ae07359cfd9156f157471546ed30997404a5c3a02dc9fab2b25c0bd61f92c232e63bce7bdb93f005c2835d786c16381f1a43579ab2ff755fc1d20cf5ad35f153d5d80fcae84e7ac8bb722ba3e17dca7296e977827c1e752be6f5c1f2af7c987db5ae17570eecfd1851ae2be54f81dd65552c0adf3e49649bc519636a5b9addde7844fd822cfebe0f600afd30cf7e78c3b27cb7539f23b1bee8d880bdbe1960f7c5120b76787eb84c8f599e24295d766c163655185c13356c7f3bab836399f85c0fd81ba733c78c27171df2607ae8d82c7b2e2b6cb13a570d9577159d709ccb376c15b219f35376f15c95366c0f3fcb89c65f92a472e4e87db145585756f92dc26debd61f05956dc3de84f1f774ef139abf3560f3c6968bc1511973b61feea835b2f349e67bb3e695c2801aa5ab8b833aed3ecf638719dac9e405597ce0497f38d2f22e2e266b709cba52bf73b032e77d8f82b1b6e993c51ecf690709df078d66af8ab5e9fda7d1ade8a219f97c6f5b1e03e31710b16c6f328b84049576a8a0b14bc52215ccaac3c2f91db13c0754a737db2b850b27bc3ba4d0cdcdbefb36ab8b7063e6b87cb9da9bf4ae2e2a0ae13011727bc4d215c5b9dcfd2f26c20ef04e9fe7cb95051dc9e29ee536f4f94eb94c22dab78a30bae4f96fb54c46d956f14c5ed044f34c2c5395da701ae2de9b1d6b853f64e98ee0df65948973b72fe8a87fbd3e442b1b93839d769e8de8e3e8b88eb13c685c2dd1e2dee53ec72a7cc5fadb93e33dca741ae2deab37a97340ade8acc6dcdf03a142e2e799d28b8e4a9e0afccb82d0b5e77745f20fc6e91fb33bc505fdc5ff8202897402f5c3ab7ebb3e53e2171cb14de0881276d88b7a2e1fe4c72a57cee6d91db84c0b376c55ff55cce0ebe28cea54be9770ddc9e2ad7e987677b4ff4c0c50d6f5312cf9aecadd0b8dc99e1af4678da6a8f45c2eda1ba4f15dc9a357c910b1747c16d92e172c789bf62e1d2d6c36349726f3b5c980c9787ce631d6fcd14be6885cbd8cff3d0dc928e37627aba9ef8dd985f2f39cff3b93f712e541eb77cf3464317e8822bf5c393d6c25ba5f0bcf865d32dcf7823321788832bc5c4bd11729b9c5c968c77f2e54953e3ad8ab83831ae53ebf6a0709d9eee8fd39d33e6aec6077d70b9c3f4574c17c8832b05c5b3d5fb9c10b74cf3463fb714e18d9e5cb6c2f8ddebb6e61b41717f18b850395c1c20d729cafde1e142495d96ad2fc2e2590b7a2b399e53f0b38f8bf3b94d16dc1697d755b92f127e3775714dd7e9c97336c65b0d7071815c27a36bfbe0b1e678d672f8abf2de5c9fd5c15d880f9a727d9a2e94029740445c3a005cc270781e95db8ae075be3b0c1f14e5be987ed7c8b531f2594797878fc7e2726bbaf922146e8f17f7a976790a792c31bb600b3c2fcab3867b2b362e6e8feb64405512cf5702bf23e37ec70735f16cfd3e37c47535f2bb1a2e502497ce95672d8abfb2dd1ee47d62a02a8f5b12f146569eb31fde0a7781f6b87414f8f502e4794aee6d82cf52e2b67ede488c5b0af045635cbac4fcce77791e792c33f765fb5d15f7f6c167b1716d273c9620d787e93ec9716d907cd616de09973b3cfc95094eda5c168f7722e6b60c78a33a9e3304beca8fdb12e1756b6e8fed3a15ef557e7271e95ae17755aafab8b6473e6b828b0bba4d1b3c69c5b702e1b676781d0cb7e7e73a99a942e19635bc91ef9680bc510bcf5af95665dc9f142e541fd767920bc5e5fe1071a124b93e585ca8d8f521e13e355d1e421e4b830b34c8a5d3bb3e1edc272f9eb52efe0ae8b245c6ef60b7193c110cf736c6856d71cb3bde28847beb7dd60cd7d6e6b1a0ae0f1917eae7b6f8443d5cee44f1572d3c698cbc9516b7078efb24f4ac01f0566fdc1e36eed3928b43739bb2b82e417e87c22d1179a31b6e0f05d72989cb1806cf7be1e2a4b84d90dc35ee4dcb672571b9d3c25fc5b9c573c25f495295741fe38388f7a78a3b077839bb7c919b67ed88bf9add9e2ed7a989db0af3ba09ee0dcd67b97181a42e1d08ee6b7cd09beb3375a12ab840055c29a4a7cd80c792e1968677b264170c8fe781b93f53dc39be6ba3f2584bdcc3fc1ce4eec5078db93e805ca85f95154f9ea9c702e1e208b84dc4fb03e64249716bc43be1dd9e0cae1315d786c8674db94bf1415e9eb3a6b732bab6181e6b91cb57781b9c5b0b9e08857b73e336b56eb9c21b29708fb8404fae14069732129e777565617c5597fb63c18572e21216e579095cee68f057672ac7abcd2dd992e7e9718b078abfb2ba40015c292ad7a78d0b157439037e552255599775f34e5c973b4bfe2acc2d5b78a328f7c6f45979dc9b171756c5e51c7a27303c6b0abc151eb715e58d9a6ec176785e959d18f705e77790fc7a41f2bc25973b08fcd50617a876a5a25ca006aed408f705fb5d131787bc4ebd7b2be0b34ab88c01f0bc34b705e7756aae92076e9d056e4fd275f2e2ba26f95d0ff7a7800b45c2bd217d96175580dc72c917915115e672caf9a2236e09c61b79703fe383c83c654e9ec7c7c5b9b94d653c6b53deca8feb63bb4fc57babe0b39cb833255f25c5fd01e242d5b987f141c3cb599cb72a9f404b97cecf92f04e64cf1a217f05e5fe045d28a6cb9811cf6be20ec14f2c6e8f24f76982cb961dbfdbb93fba0bf5c2f549e4424db936413e6b815b2af1465a6e89c31b056f0f93ebb4e6ce02f82a2a6e0f18f789e7dabef8acdb53c6c457fd5c9f01eed39bfb5af33b452ed0cf95fadd9b22b7e9808b9b739da43c6b7cfc95011788e74a19dd9e2bee93eb960b7c911f973b497fa5e6593bf356746e0fd1756a739530b9750078d6e2f8aba14ba0a74b27815b2d4f24c2ed71e03ae5706f623e2b8bdbe27d6e81aa036e0f1df7e9c9fda172a180b87425f03b02ee4d84cf8aaa7ae2d225c2ef22681c1787c36de2e302295d29306e8f1df7a9e8e222b94e572eeeea3a79b92f33bf238428cb6df9f0ba19ee627c50d2c5eddc262db75f9e6885eb43e74249707f782e94f129d3e1abbe8c41f23c376e4faf5398278d8db732e2021172e9f8ae4f9bfb34c705eab952bdeb73bc4f645cf264f92b322e0ece6d42e3de26b94dbdcb5815cf93e2cedf89d22d1579a31c9eb3396fe5bb8401f23c0caeab90dfad5055fcf5dae37940f716c96ddadd9ee47de2727b945ca7334e40b836071ecb887b1c1f74c425d01d97ce01cfda1f7f85c0e54b82dfc971718c5ca706eecf11172af9a4f5f056313c638b3c6f8dfb23bb501edcd6ed8dbcb8e4d9f257655ca030570a88dbab7782e6493be2ad6c78be7e7e97c5f3f5fb9d1ad7e7e83e055145c47559fd4e886bb3e1b1e8dc9bcf679db93d5faed313f716c86d12ba8c293d2f875b2f3a9ed7736d323c1623bc31b795c2eb42b840ad2be5e47222f04f72d7278f0b95c065ab8adfcdcb961abf935dce45be088a4b9905cf93ba3dbcebf4c205e2e3d281f2a44df1563c5c025971e90455b9b93eb0fbc4c12d58eb7942771d3e28786f465c580ff7f5ef8cb83e3fdca7469e36041e6b864b1704bf83726f643e2b8ddbda79a32d2e790ef8ab2c2ed0bc5242f787850bf5c7e5ce037f95745b50de288f8b2be23619f2ac99792b476e79e58b10b9ad9e3702e302c9ae54021737c875d2dddb0a9f75c825accf0bba3d75dca701ee0ff24ab50a81fb03c59db3bb8c25f03ca68b5be33addee84f2451e5c1c98db94c3bd417161473c6357cf93e3f60c729f82b775f4ba052e65437c55ebde0ab94d4f6eabf83a0d2e77687fd5e5594be1adaaeecf07170a8debf3c27d02e4d225c3ef2cb8b7043e2b855b3abdd108cfd90d6fb573950c70eb0cdd1e9eeb44bc3f6f2e949ccb43c76355b9b6328f15c7f559e44255b93c583cd6cff5f9e03ea171e942e17754aeec8eaf22736d557cd6ebfa1872a11eb8ad00dec88ceb6375a1be5cc2889e47c05522c0ad03747bacee5397cb19096fd57ace66782bdabd79f0595f3c5fb9df9d71ab25c8ef7aaecd8dcf1abae5076f84bbad38af53ba3f40170acd05ea72a576b8b4e9f05891dc8df8a0a3a7ab88df69706d5e7c96edf6a4709da0aecf13f729ebf610709de2dc5ef14eb05c1f34f7498dfb979f739e34a8b77ae212a6e4794f2e63b4e799b96c9af268f1584a6e99f446b26b33f35872dc5b161706c5f5d9e03e65715b469ffbfe4c70a192b836a8cf32ba40875c3ac2eb63c37d423e676fbc1502cfa9e49dc479d628782b41ee4de8b3dc5c1c04b78984db63bc4e615cca1078de1fcf1aecadce78ca74cf0be4f9af6c024fd4bb949df03cac5b9af046055c9b078f95c6254c92e7255d9f2ef7e9897be3e0b3b6b83f855ca9ef8fd5950ae0fa10719f20b9addce77ef706f45969ee0f0d170a79ebf323e7ce117359a327025e250bdc3abf7b6be1b312b925952f2ae402d9ae54eef6d0ae9307b727bc13da25ec87e759b940115ca91476c16acf23ba9c7c7c911297319ee77df09c9df056aedbc3c3754ae4964c6fc4e4b252efe4787de8b85045b7ece18d1cb8ad2faf93e0b23abc1327d786f559616ecf00d7e9cdad992712e25983e4af7a170876a50cb807f14111dc9f3aee1c0c9eae337e07c2ed81e03aed707db4dc2723aad65c1cd06df27267942f1a5edc0bb749cec5e1719d88aa58b83d135ca7236eb9fb22a75b7af046b0fbd3c28502e4d72b89e7c1ee4de9b3e6b83e6eee131df74778a16eb83d71ae931dcf97d0efbc78d202792b2aae12086e1d062edbc53be972711ddc26216e8f02d7e9042792f7a7bc50629eb52f6f55c8eda9e13a0d724b336f74bb2d1c5ed7c2fdf1e142397227ef8bc83c6950de6a83cb189ae7e170c933fbab27ee4f990b65c6f539e13e41dd1f2717ea4dd51eb7639ee886fb211f54c6b5051f2b88fb7afd8e89ebc3c47db2aa32f37c59f0bb3a2e0e88db24c83336c2f37cb86c6df1bbd6e5c9e2b1809eb400deaa4b1f79c60479de19b795f43a0c2e5d667eb7c0e50ed65f2d71cba73722e1fe4072a56ef7078e3bc7cbbd357161445c4ee2178d707d20b95013dc570abfbbbab6308ff5c5b346e6ad1ad9052be1790e3c67516fc5bbf5f2442a3ccbf44ecafba374a1dab83eb2fb34e6e2a0b84d759e36268fc5c2fd99e14215e2c4231787c46d4ae4e230b84d3c3c6965de8acded29e33ef95cf2a4f92b3ad7d6f459506e0bf64655dc12cc4319e19675bc519bfb72fd6e89cb9d2f7f65e6492be4adacb81513f33c18ae0b8bdf19ef0f9b0b15c7bdc1f0598c3c1fe179a22e5f47bf8be3fee470a11479da6e8f65c22d677823debd817161575c9c92dbf4e5966ade08e8ba08f99d0ab751bc13076ec18e9e6774390be1ade633d6f43c30eead880be3e1bab6f85d997b3be2c27cb853f14160eecf960ba5c4258f97bf3ae356897702bcec9a77d2db73c87d72e0b273bc930cae0f04f769873b3be0aba29e40c94b47cb3d900ff2e256c178275eae8f09f7e9e972f6c157555d5b9ac7b2e3da748fa5c3f549e03e99706d1a3c961817a8572ae89683bc510cd746c567b92efbc73b19735b4edee88d6b7be1b112b92d9e37e2e23977f822186ebdc8781ecf7555f1bba46bebe3b378b7cce28d34b87475f9ddeebe967ec7c82d1979a31d2ecb9d2fdae2de14f82c156e157927c5678df7564fb728bc93da6db5de488afbc3c59df3c07de17e77c5b3f6c35fbd9e3541fe4a81672ce97933dc17d2eff8788ee5a71ef775c2efaaee8f21576a5e1ee4636df0a431bd159c302e67235f14c5bd6df9ac25eeadea36052f5dc4df05ef147a27265c5c1ed7c9c96d39f13a232e771af92b232e2e85db24c793e6e6ade2dc1e44eed303b7c787ebb448d5139e0a97678ec7a2f2742df13b0e6e0f11d7a9ceed09739da4b8b6373e8be849e3e0add254353d8bfb21acaa26aecf98fbc4c5f529739fccb83c873c56075786c34b81746f88dca602ae4c8caff2b20bc6c5f3b6fc82e59ea740559aebe3e53e41716d927c1697cb58ec7965eecfd39d8374fbc23bf1a91ae4b6bcbc0e4b951ed7c6e5b1aeb8a45979abe2edf1e03a71716f56b749787b64d769cc7571f13b33d7a6e6b1eeb8b8a4dbc4c4fd815da831770c7e12f254c273eb7cf9f2f23b3b9e716ef9c71bb1f0a4e5f1564d3c63393c0f89eb73c07d22e1dac63cd6194f25b75ba7b743bc13a35b1e7dd12177325f3e5dc68278de12b7acf346405c1f392ed493dbf3c475ba7ac6de3c8f877b5be0b372b8383bae5393e7ac8db73ae096ebf53b264fd71cbf0be1fa14719f22b93c8b3cd6f072b6c05729f2a43df2565d58715b4caf33738190aed413f7393e28895ba5f34ec8dc1f252e94d5f5e9dd27189e32069e87bc3e47dca7492e8fd463119faf087ef74621b836333e0b808beb729b6cf8059be2795a9eb320deaae759abf357bcdb7ae075bc2aa8dbb3c175bae2022970a5ca5c9b1e9f95bb9c6c7cd110f7f6749b7c7ec12a78deee9650bec88e6b83e3b39cecd8dc1b9ccfe2e38bfb13c59dc3bb3d5eae9313973b467fd5c1c595719d6217b7c46d62e4f658719f5ab7b5e48dd0b83e4eee939b672ceb79735cc28a78de955b76f1461d5ca0d89542e0faac71a1985c0ec90fa1719508dd3a4b2ed0962b65c3755dfd6e885b7af146636e8f0ad7498feb73c58582dddb9f95c1b315fc9c11b735c1eb2278d6846fb5c7c521729d80972d2f7ee7bab6358f85c7ed81e43e65b938116e931af746c26705727d84ee139b8bfbdd26a6bb211f44c633967c1e1cd755f5bb20ae6df8586a5c591a5fa5c1731e7d11085709945be7808babe036d15085c413a88f4b274a959aa21b6e49c31bf59eb427de4a877be3e2c2a6b83e57ee9310f775fc5d20f707ce85c2e3fe142f9418cf5ad45f1d705f49bff3e39618bc14232e67175fe4c3edd1729d8cb84a80b70eef9668dec8e739a77c91d2c545709b4ab83edfa70b2ecb2e9f809b5bc6f046ba2a039eb327de4a80db127a2336eecf2157aabc36a7cf8a724b1dde688127d008978eebe212b94ec16b43e3b304b86c3d316b92fc95effe6070a1aab8ad2baf63e0fa04dda7a50bc4e44a4d7067e28326b8b7309f55c505ba5d29dd6d21bd0ecce50e0c7f25c26d69791d0417e7bb4d6cae92e0add37bce0479abdffd81dec8b8302eee0f9a0ba5c62dd807cf0b5ea001ae9406972e2ebfd35d16ce3b79dd71f8a0dff519ba4f4cf796e5b390b8b6151e0b5935c0bdd9719b6ad736c16351c12be3e21cb94e115cceaa7c95d4ebcdb5597d16980b94e64a79f1ac3df1573cb70780eb84e6b688781d0f1788cea543e5b296e6adea5c9c99db64c56d497923a7db73bb4ec6e717f747840bd55435c32d1ea6bf7ae4fe8c71e71cdd9e32d7898c6b637aaca77bd30745715935dec9059740385c3a3ed7e7759f3678be02f8dd16cfe9fb22334f599f47c77dc5f95d24d747920b45c1b58df1593ff7c6f359c7cbf3c6633d7067ef8bca3c6b08bc959c5bb2f046509e40295c3ab1eba3bb4f2ddc1b049fe5c3d355c7efde5ceed4f1573b5cce22f82a469eb545fe2a775d5afc8ecc05c25da9ddbdbdf92ca8db93c0753281a7c1c57db94d38dc9b9bcf72ba3e11dc271eae0dcc6375f1a4c3b5d1f15904dcdbd06785704ba8372aa12a8aeb83bb4fc7fbcae077795c1ccf6d9ae0da36782c322e4f1c8f15c1b3ace01368734be68980b838a9ebe4c06dd013cd6e4f93eb04c2c5f9709be6dc9b9cdbc4739bc23bd9b904cae3d241e0e232b84d3d3c69c7b75ada0543e279586e09c21b05706f523eab85fb73c08532e156eb8fdfe1ae4ff03ed1707d22b9505b9c9cb9353ff8a212aecf1c176a800b44c195bae16ec6071dafcdca633571cb2fde08e9de9cb8b022ee6d7cd010bc126ecfd57df27219733d8fccae5721cf03e00291b952585cdb149fd57acef0782b29f767850be5c7bdd93e8bccbdb1719be6b336c75f113d5d6ffcaecd6d25f1ba209eb4376f25c2659f36001e4b855b3c6bfeaace258feeafb8b8b836b7898c7b8bf2592cf038b83f355c28432e679d2f92e2ba14f95d0c578907b7ea91cb9d25fe6a85cbd996afa273290be1795517f7c675fa09d2e22ae1e0d6e9727f7eb85074ee485fde71190b7a9ed2e54e0e7f55c2c529719b16b93c693c56f0dac4f82c9fdbeae0755eeecbcdefe85c1cec365979d25e782b15ee8fd1856ae1194be379575c2001ae1496db72e17523dc1e28ae13d6d3d5c4ef3ab83832ae136c170c86e745708bc113bd707126dca637eecf980bc5c5f5f9dd2719ae2d8ecf7a72bfe28330b83d6dae531cb7ece08d5a97b3e1574ddd66f14eb25c4e3fbe6889fb93bc5240b7a780eb24c2656b8edfd52ece86dba4c753767b5ed3e51ce48b9ab8e4b9f257635c1f21ee539ddba32772737f38b8505ab85cb85472e5d6a1726f675cd8179793cb17b5b9383dae53019713cc1785707b80aed307b7a4f3463fdca33e288b8b9be136395d20402e9ddc5389925bc7e7fe98b95067dc1e3dee53947b137e160ed7278e0be5e4568b8fdfddee3b7c90f0fe1072e7a4b93d155ca725ae0d8bcf925dee50f057642e2e86db94c79316c55bed70952870eb1870bbe58948b8a5156f1406a643d598fbe3e54241714b43dea886aa1e6e4d7827b32aa78b23dea6269eb1149e17c4b5293d969c6b033e1610d7478c0bd5735bc7d76d707d8aee139ce74c8fb7d23d67566f55e5ba92bffbe1de92b7e9776f1a7c1616f7233ee8cabd0df059225c2504dc3a4e2e4e86dbd47471bddbb4745fb3df4171e912fe2e813b2bbf2a885b6af046f3da5cee44f257485ca0e395bae2494be2ad6e78ca60cfbbe3faccb950c027accef33cb865d117b9f1a4d5de6a824bd7d1efa45471200507f7c7912b55bb4068ae5417570902b70e0117e8812b75c2e5ce097ff5e6026970a590b83e46f769859d1bcfda107f15bb949d79de24554f4f5af2adc0784ee0177d70e7e28338b8ae457e27c3bd6171614f5c1c15b749922ae42dd778a333d7f6f45952eecfef42d1f0e4717aac092ecf158fe5f3a439bd95134f9a076fa5f49429f1553ef7a6c66dfa2a5972eb28b93eb4fbe4c1c50ddda60eae8f08f7098ffb93c8957a5d5b0a8f4548d510972790c70a73cb32dec8f8042ae1d2793d6739bc154f950b4fda086f45c26d4d79ddeff6f4ae130cf766c58535715b03bc511cb704e28d22d805c33dcfc955b2c1ad63c1ad9624bf03e0594be3af98dc3288379a727b5ab84e7d5c2546b78e02b76cf3464d2e79c6fcd51ad716e5b16c78d29a78ab1c765d9eb52ffe4ac92d7f5fb4c7fdf9e2ce99f2a445f2565edc8235795ed1bd41f9ac158a6cd8b171cb02be088e5b1ef246365ca03a974e955b26dfe888fb93e642ad717b60d78983272d86b75ae1f6e756c58531715b14bc8eca5306c55729b9e5983772fd7201fd4ee8964b6f24c0b5a1f058835c1cd16d1a736baaf922136e83782750ee0f1e77ce06b734fa223c9e34346fd5e629e33d4f90aaa5db324f14c4bdd970612c5cdb0c8f2575393df82211ee8d91db94c06dddf03a166e697ca3daed39e33e01dd0e9f4887eb23c5859ad767779f5cb83d70ae531db72abc139ee715fc8cba404457cacbe51ce38b80b8d5c2fa9d00b7c427e2e192a7f7575d1ce341e2afaaae0ff03ecd707bd85ca737aeeb8bdfa179bacaf8dd9ae7ceebaf1cb8408f5c3a566e859e08777f20b8503d5c9f9efb44e6591be1ada6ae8f23170acbe5c4e08bde5ca0a22bb5c1a58ce97954cfd9d35be5ee5f7c10d2ed29e23a3d727b7ed74986cb1de35f29f15ab83c6a3c96f0da1a78ac22aeed8acf8a5da039978eeeca16f92a152e63333ccf88fb13c995c23d6b02bc151cb767cb7542e2b276f456805c2540b74ecf2df1782338f756c685757177e38388b83f642e54195736c33b31756d2d3cd621d7c6c16395716be589da5c1b209f05bc363a9f95e5dedeb84daeabe4805ba7e8fe0472e77cf08cadf03c21eeadf659c5db22de89eec963f5584c973b66fc150db7539e68cd6d85f03a34f7f6e5b3a4b8363e3e6b775b456f2447d5998b43e33aed5c1fe17daae19652bc91975b3af9a2356e4f23f7c9cab571f1593bb747789d6ab838236ed321f707ca8552e1f6b8b94e72dc1f0d2ed415b7c780eb34c22d83bee80cde0a973cc3bf62e3964dbc1105cf27f8c94793d2edd1e03a55718184570aceb5657d1606f7c6e5b39ab89cb577e2e6de8a3e6b840bb4c19552e2ca607827eadc9fdd858281b7c1c535b94d626e0ff13a7d717fd2b87326b83d58f7e9cb9307eab1b65c5beeb172b83e56ee13101727c975d2f2ac35f1573bf727880bf5c8b5f53cd609b71a3c510ccf5a127f457362cd05aae04ae570195b7a9e0ed767769f90ee597c900617e7c46d72e4b60a78233a2e6ede26086e8b87d7c570b943c55fb970d93ede490717c7c06d8273b943c25f85707da82e5497e7f479276f2e7790f82b156e8fef3ac5f094c99e27e73ec5077db9404dae1405d7e7900b05c1e508b485c1ebb6dc560baf13e129f3e1abe6e5ac82afaa735bc3d785b9057be3795d6e8fd1755ae1deaa7c9610d765c5ef88970790c702f3047273e9cccbd995af6ae4fec3073d705d81fcee84fb075fd6b93e18dca72a6ee9c41b71b93f155ca8266ecf08d749cec581709bbcb84a76b78e945b46f045833c65043c2feafa7cb94f51dc9e25d749e97ec2db6a1788812b25c225cdf75648cf9a237fb5bb3841ae53eebab07e47c4c55db94d2b5cdb028f35c46d6d79dd94fb9c0feae2e2a2dca633d7f6e5b1b8b83e30dca739f78776a1929eb6d8638970711cdca6202e77acfc55f1f6ccae13d27376c45b01dd32ce1b39b97481f91deff244f258676ecf01d78984cb43c663f1ae0f93fbb4e649cbbd5507f747860b45c8bdcc978e5c20de9502e17206c25b7dcb30de68f8dc997fc5c0e5f9e3b1bc5c2543b70e9327509c4ba77567e38384b805abe3795fae9227b78ed0c511729d769740685c3a035cee04ffca835bc2f046b95b2af045755cca80f8aaf22ae9dd3ab9db13c2759ae3966bde0880eb32e4772ddca9fcdce22a49ba5591dc57eb774adcf2843732e0963cbc510397b0e4f3884fdad25bbdb92e2a7e37bc47f1415dee747c9012f746c385a9707d9edca710ae0f08f7e98d4b181bcfb3a00a876b8be0b19278ce8a78ab9f4b1a96b7325e9b098f15c8b5a9f1594cee727c1012b7b5c4eb8678ca90f8aad72d074f44c3f551ba4f6d5cce28be4887676de7add4b8b7103eebe9e226b84d305cee40fd950ff715e677722e0e8ceb342f633e3c4f89272d85b73ae1e2bcb84edf1e13ae53d36d297923322e5bb6b0bcae81fb93c08542e1b60878a3392e50d095e2727da42ed4d1b391bc9332f7c775a138a8cae0395be1ad6055355c9e461eab787f46b8504ecf9792df69717b84b84e8e3c95d42e9d1bee4fd595020036c4c595709bdca8b2e0da0ef9ac089eb131cf8be1022570a5c85c5c1dd789c9b3b6c55ffd5cd2c6782b219eb6d9638d708b6784bf8ae4b67a789d0c973b417f85c1ed295e27306e4f13d7a96a888c67a33cd1ef0241b95266ee6dea36f99e343a6fc5c5ed29e43e2d70691be2b1a62ecb2d9f00d395b5f04d0cf1ac11df8a912bf3e3ab986e817827075c1f362e5493eb63bc4f633c677fbc55efe20ab84dc57b2b3f6b838bdbe136f9717b9cee5302b7a7ea3e59707bb85c27252e8f198fe5bbb62d8f55c52d1bf82240ee8fed421def501f44c5b5d9f15906dcb5f820314e96eeadcb6741715fb1df39716dbfc7fae159337aaba6a21a2e77627f65c1a5eccbf31ab93f465ca8a967acea79715cf2b4feaa89fb03c89d53e65246c1f318b93e08dca7126e4fcf7522737127dc26382e817cb874802e100157aa837b8b7d9607cf1ad05bb9717d62b84f815c5c18d7a9bc36363e2be8de889f25c6b525f25954ae92dcad03e5feccb973cc5c1f0deed315b767ca75eae1de22f8ac1f2e67d51755715b2fbc8e84eb3ae4772e3c5d43fc2e835b26de89f05236e579885c202e570a87aa36f78777a162b8ae177e77746d373c569d3b141f64c1fd41e242513d6d4c3c56d5b5f9f159beebf3739fccdc9bd167b9704b00be288ce7620a24c9a583e5da901e0b8dfb94f9dafd0e8ddb8abd9115b7acf245c88bfbe33a41b9b830b76987dbb3c4754a3e61733ccfcb2da778a32f4fa037974e5955e6defeb84d4ca4e8e096405ff4c5bd8b0feae0394be2ad945c5bf2b3ba5cc6963c4fcd3d3c36fc15d5f529ef5306d7d6c16399714b28dec882eb5375a1bc54897095106f9524d735c5ef3cb815abf23c13ae4f0bf7c98fdb52e27542dc9b1eb709e8de04b94d439740625c3a4e9e1d7a22009e402d5c3ab3dbfae175345c1f38f7e98eeb93749fc078ce76be6a896bf3f2585b5c1f3c2e1402d716c9675170b9d3c55fc5707f18b952b2dbe2e0755daa7eb83e66eed319b7628e3caf8de70c92b782e09662dea8973c3e7f25c5fdb95d2832f7c777a166b8a5166f24e63961ef84cdc5555da70a2ece8debe4f3a415f156355c9c1ad7c976b933c85f097181ac5ca916eeed890b33e2020daf9414cfda0d7f352f7718f8abe12d1f79a320ee4f1c77ce97fba3c2858aba045ae3d229ba9c577c110fd706e7b1fa78025d5d3a5b9eb343de4a78cb46dea8872688b83fbd0b25c3f5f1e342f52eae8bdb94757fa2ee1ce3ad16f2773e771e3e68814b17ef7707dc96ec8dae78d284de0accc5adb94d5ddc1f2b174a88ebf374a1a25cba967ee7c06dad799df1da0cf92c082ed0d095ea726751be6ace6d99f03aa6fbf3c79d43e67296c157457281ca5ca92c6e69f5464a5c5b098f35e7966cde68c905225e292a6ecf09d7c9e90215af5415f775c1efeeb88ce19e77e67efcd2e936cb13c5d961706f71dc26d8e54ed35ff570c923e5afb6b8554af009d4ae0b86df5db93c6d3c560377e6bea878b943f457193c6baeb732e3f6f4709d14b9a481f15641cca1726f1d7c1617973228cf03e472367d1112f7213e88ca2519d707e93e75717fb2b87318b8cd7a276a6ecda52f3ae1e2b8b84d5855055c2000ae1403b7c5c4eb88b82d33af1b7327e3838acf9dff6a81fb63c0852ae196be2fd2e3dee8b84db35b1af146579e34296fc5c1e52b8be7d12e81e8b8740cb89c667c5110f7e6749b7a6e0f9dfb74e5be42f85d9d27ade8ad3078bac6f89dd2f5a9e03e2d710bc013b52e6700ef44844b1907cfab736d6b7c56937b63f2596c2eeee836a9707fdab8732ab84053ae940ad72784fb54c7bd1d729b08b83f3d5ca8462e5d18fcae779534b97502b8b7259fb5747b80b84e52f7e6c26729f2dc91fe102f54181737c9759ae0e256b84d74dc3290377ae19665dec8766d353c9623f7478d3b67cb9387eab196ee6b84df4d727fe8b873c2dc1f2e178a89db23de49ef02bdae54d1c501709bc0dc1e16ae131fb78781eb74c33316e7793ddc1e32d7698c5b06bfa88f5b4af92239b7a5f4bae2bd65f05957dc9a71be288567ece979615c1c9bdb04c6fd695da80d9eb61f2b846b93e3b30620c2725b00bc5119f7678f3b27e90299b9525adc128d372a737f84b850905cdc0fb72990db53bb4ec367ad88bf925da02857ea83db13c77d1abab6101e6b8feb53e53efd7007e3833cb82d145ee7e6f6b4719f98dcb27927f3f6ec5ca7a4cb89c8173d714b47dec8870bd4c1956ae2e282b84d83dc12893792e0fadc709f0cb9b6111eeb8f8b53ba4d5c5ceeccf9ab20ae4f9d0b95e596037c511b5a54595d25636e159dcb89f54556dc579adf1d726f297c5621b735c4eb76b83ec3fbf445550acf5a9cb79ae4dad03c561d17d7c675eab97409fdcec9c541729dacdcd798dfed717f06b973d0dc56933752e3f658709d9878d23e782ba65bdaf046bf67ec84e701716f5d5c18151797e636697139e77c1113972e26bf23aaeae0fe74b9504f5c9f11ee531ed796e5b1a4782ed49e3bee1301cf191f6fb5ab0ae4f26cf15800dc5b9acf8ae3de78b8b0192eae8adb34c9b52df259562e656a9e377571855c27de2d0df8a2389eafdeefd2b8d54ae4773fcf581ccfdba26a8abb1e1f54c57366f92236972f239ef7bab6aacffaf2a42df156385cdc13b789ce13c88f4b47ca2d05792318aa8eb752bc93072e955470eb1c5dde6e8ceb92fa1d0d4f571cbf73735b1bbcce827b3bf3596ddc1f172ed49cdb8ae17526548df0a44df2565fdc9f3e688a678df65669dc1e35ee53004f9a1d6fb5c4f551e3422db925d51b21715bb637ea62172ce979bf8bab739da85cdc10b7a99027edf656145c5c0db709eaeec40751b0eb65c3f3e62d9778a309ae1229b74e02f7c7893b27777d02b950c1276d87b7826157cc8be755717b92b84e935ccab43c6f91aae1b545f159f34ea32f22de9f242e54d5252c8de755707f22b8503e5c9c19d749765b655ed7c1e5a4e28b76b8dc29e0afc4dc09f9a0307878b70790fbd4bbdc09e1afdadcd28c373ade1292371282a8816b03e1b1f2b837342e0c8cebf3c785f2dda9fb22e3ad96d4ef802e5d1efc0e78717d5c27049eb403de4a835b46f1465dee4d8c0bd3e2da0af92c076e3d78a21c6eb1de499a7ba3e2c29678029d70e9c0aa109923bc3ebefb14c3fdf1b9501f3c6771bc5502cf5a187f05c0ed41ba4e5b5c1f0eee93164f99ef79830c9d716b7c221feecfec420d9f3229be0a80fb83c285e2e3f644729fb43c5f6402655d3a14dcd605afd3f28c79f03c19ee6b83df3d5d5c0bb7c98eeb82e4773a5c9f412ed402f73c3e088a67accbf358b82ddc1b7d710924c3a583bb2d37af3b73ebc5c5f36a4f5a236fb5c525cf93bfbae2eec2dfac6e9d3c51d16d05bdd11a17c7c7753ae0e270b789cbbd45716148dc9befb368b8aa90fb93e5421d715b3abcee8567cdf75650f767880b15c9f559729f94ee0f01176a84db83c47592e4020572e9ec2e4f1d8f65e5fe0c70a1e2dc1b9ddb24e5de04f82c10ee687c5099a70b8fdfc5b93d6bae131bf7678d3b8782dbd3c075c2e1f670719f66b795e5750f5cc66c78de11f7b6f359c6fbd3e642d5717fccb87324b88c0df03c10ee8fd0856a737f24b850465ceef0fc9597db43c87d125e9c1cd729805bb6f146696e4fd67d027371735c27016e89e58b12a932e4e246b84d6cfc82edf1bc304d6bee0f1f770ef1deb6b83029aaae6e4f10d7a9912bb3fa2a1d9eb7b5e29d5cb93e3ef7a9cc93b6e6ad42b84a7eb7ceeec903c663e5aecf19170a80dbe3e43ab9b9382bb72985dbf3c87d92e0fa38709f72b840085ca9e2b388bc13e2c541b94d1f3c775a7fd540951317b7c56dbabaac1eef8483a7edf558712e6b6cde0a926bc3e1b11e79d690de4a91ebd3c885ba727f1ab952b45b36bd9108b7e7cc753ae3e268b84d4fb7678dfb24c0fd8d0f2ae2768a7702c16da179ddf072878fbffae10251b9522b5c1b99c782e3de50f82c426e4f487098cc2c0dd780d95254458a1426e2704d1a5dae8509c5519068d4e9927e43a3d0e72b027d66d4984142fa6d9942a5822d587ec7f9c4d1f108d4f57645dd2eb823e678b9a36f3aa12b0e7fc7638fa83bfa8c4d1cf17ebfdf5147cc158bc062afb71b32719424e4017dc06350771cf682bd9e6e89236312efd82bf68ec0e24fb7dbf58e65803a3468cc9c11f250984a1ce586c15ceec81bfe8841e12ee9a7dbc599491ce58e452350f8230e8fbe5f2f18ccb141e28818cc0187ba23914834260181c5e211473c61f0f74b12261d8f4561926f1734e26848d4098db9a22f17dc19733a1d9c59c4519270f70bea78c5a14fb7e3ed80c432401d4f8449c451f147fce578c09e30c8d30973bc37738823dfaf780cfa843a21ef5706a8d3ed94a6104749bb241f3129373c068b399d2eb80be2e8c8eb25f18c3c21af38e4e58845a16fc766027154d4f5927ac11eb1280c167f3b9e92d20f47c2dc8ff7f3118f3d5ef1d71b167965803ade0e84e9c351f13724fa843ae19108d405814134b38723ddae674cda1d75c71f0f481c0ad34c1e8e7a46a2b0573ce68c3be3af47cc19c9ec7094f44b0a1287c0e2ce374cf205733c36bf0fa60e4749395ef1c80b0293929276b9e210685c62a35406a83b337338fa019388c75d124f28d4e58cc79dae0c70d7d39d212aa149ea2d4d1c8e76c51dd0083cea7a3a1d4fc823faca0089c6a252156351898b30a76b60de7014d40d85baa36e38f4158d3d2151d7cb326d38fae982bc9fd107d4018dc420ef57ace128e9d8d3118947a250a733f682c1200d473ea24fa8db097dc0dd50981bfa8c40dd1d7386a3e0aed7dbe57c3a1e31373ce68c3e23d3b028458a0ea969ca70a4cbe58cc11e4f37cc019986c53517489192d3cd306338daed72ba1f9128e41d7b466331285c624a6ac326589c301ce588bca4dc3107cce974bfa3f0182cc3d3a96913d425cd178e8ec52271a7e3f174c59c6e9803f224f805b9884172335d3802fa7ebf5e52af28d4f188c6a12f8e19364b466351498d4e385b381a128949c59e4f07d409773ba290c8345c22b241c3b4a4c406090d9a611a363ade64e12898331e81493962522e09d833f28e65803a364841a62525363bdd5c2198ebf97e3e5e4ec8e3097b3a9eae07e4e97445e0709794e30979399d11785c42e2a9d1e99894dc9859e3db60aa70f40bfe8c475e30c9b8cbe97ec0a4229699c2516e984434f67ec09cef98dbe58440148e7c40dfef9734dcfd80436130b8fbcd138e7cc71f8f084c0a1e87c29f2e09c82b03d4f1b278c0e04e0714fe80bd5d52eff8d32d198b66dc282d59602c431c569122a5ca510ed86be550aca31f312908341e7bc02251e7f3ed7865803aa014295e8aaa1455a952f4a508cc96a2247a7544f41983c4e0af474cf2098bbfa1d0aaa71302733f234fc813ea8042a130a9d863a155bbdecf17cc0981be5c4ff8eb15893b1d91173cfa806580ba9f6f278114294560b6e8805447c36351b74bdafd8ec1e06e770ca2513a22c159620a1681c1529445ca95a22a455986144891b26698669783a1c9a3a1b078ec05893aa130c8cbe58452a404dd4891a2d321e9826e76c2a9a322f0a83b167dc6a45c6ec70b1681c12241193643098e4a4a4b16489122454a2fa8e3e9805cc42055aa931cf180bde390083c260977bb1cb177ac24473a616eb713ee7cbc635198148ce4c8c83bf69276c2232fc9d8eb018b49901cf574bc1def57ccf188c05d8f77d4f5911ff017d4f97440dd0e28e401733ce1eef733ee8849411d30f8f309854621b1758e74c0e150a81b26157fc6a42423d04886f7858ad1cc12182952a45c99a0a84a91044560b61451b1026749298d91022952928a48c29e16a57364240a8dc69c11c82bfa8a49c4dcd091a361aec70b167fc35e3008d41d83b932409d152952a448e12206494d1b3922f2883b602ea96704fe78c7df4f57a9239e91c7e3097fbede501714fe8c45468e80c35db027dcfd72461d4f674cd29501ea9498d464912265c9c864064989498d19304c59d2282d5d211ad77c912205c8d351d1458e80c65c2f670c0e9384bba051f82b0283458a14a2d18d0fc806cd9a34667c399d22473f1e3077fc0985c6a4e010d80b12bf5010dcf9d1448e7cbd9d0ee8fbf182c6df2ff7cb259501ead6c0619222458a14e31a38607a3a245d2245e44878040e89c71f709713f28ec2ddaf0c5087b4a4e3b1d0438e86bb5d7118d4f178c3a06f67e4f1ca0075c6e27089cc9a36c1146ac8d1ae4714f284bf609231e8cb0181c232401d8f84228f7e46a1b007ec119384c4249fd1082c03d42121097755a1851cf97ec7de30583c1275bc5cf097db1172a423127fc024a22fa9c81bf27243e220473c624f270c1a73b9e331a833ea74395d6e97c6c8c4060e1b1d4f9023dff1984b0aea883bddd1b83356692047459e30c947241a85bfe011c8fb0583693ae7c878d40975bedd4ef84b3aea8045e2990272d4ebf57cba5f2e98044c4adafd7e3c3443374845a39a1094908a4325343fdd1f47c5a4dc2fa9e70bee7abca24e7814fa7144e4118149c39dcf08cc057db9619271a90d1b253444a4e00ecd50cd92b10c71485cd306e98c92121ae0ae080c96a22e45558ab61481d95224d4fd928ac0a8239e90e7e31977c61f8f583cf28a8ac0364569cc946123c54d5112b04d9191a84609090d9a2131d73312d554a0e6ca4f4cfb38f2ed8a465d3027dcf57643e331082c03d4a9b9b20bb259a3a42b1f47c1e131c7eb198bb961af18fc018f7b1c0d893be051b8eb017bbc24a1f0a8c75170d8cb058d4062d2d097132625411d117b476392ef28041e7b43a1ef677c3af2ed8ac3245dcf58d4e986494947a7a35cf00724f28ac1e051683cea8a4d47c1e14e18240a77c49f8f281ce671d44beae988391f5127fcfd763d201e47ba24a090f83b1e7fbd5f5210a82bca39ca117b3f63d2ef2814127bc0dc2fe98ea3a49d71a8d30183c1df30f8230eed381afa72c32130980be67e435f52af8ea31e51d74b2aea8ebe5f50f71be684741ce5763e62b1d823128bbd24618f079ce38848f4198d3c5fd1083ce67a3ce14f8ea3a251f8db158139201018ecf19278711c25f58ac49e0f68e40979bf5f121c47c65c8ef7fbe974c4634f48dc15df384a2ae686c7235038cc0d87c41d6fe7c65170584c2aee7ec3e14f982b2615756d1c15853b1f300734fe86b9a4a4a0315706a82bc36426841e1ce304409d10cdd0c8d3b17124cce57249bca49e30283cf2726a8c6890ca3099090189687423d4f988c6a2121109090e9399a5e19aa06e8d239e3057040a8bb963d2af27041e87486ece047f7d548d23e0b1e7f3fd84c5e33078ece5784965803a3767908e43231aa49e6f4747d338daf58639de7148241e8db9a1d1e72b03d49561d3668db0080c96a50a8a463521541004ea7e95a4681cfd7a3c622f68cc0583bf1cafd7db9501ea90948ac13378c1638f5834ea78c0a1b00c50f733e3c86824f27a3b239127dc017d3a60ae0c5087666864628374064d9b249fae080c16305b8aaa90516346686c1947bce0af6814fe84bc233069184cf29171742c0e833d9eaf7814fa7cc1a463718c23203137dced74bc9d0e48ccf588c432405d9809d00cd5ec72a81847c75d90580c1a8549b9e06ff72bfeca007564d420e98c648a4743e3962a45618aaa144d5054a588a90d0552a42c1d8d5e8c45252e52a4188b4a6aa448912245829c6e672c2aa9e9198d4b4c493e9d6e0ed1308e7a493961716724fa72c0a4dece776c038671e7e309873dddae180c127dc0e01747bfa0b028e4fd7abc9d1038140a93cc7891d3f1823a5fb027d4e974bb1df0e70b1273bbddcef733fe82459eeed7eb75d02e8e803b5e4e973bee80bc9d3189084cda25958b23e03169270cfa84bfdeeec7231689be3569ccf086dde2a8c8331685c6a070670416793f1db00c50173c32b511b2092e31b1e1f1aac5d1d057e4f17cbbe1d0e71b1287405d19a00e898d999d2e8b20d09703ee7842a0b0c703fa84395d70f7ebf97a44234f27e40d873aa2cf88266928a4a0581cf57a3a9d6ff73bf272c7249ff1782c03d405bfa4515aba22450a1b2633c3252629529698d8fccaf48a23a38f38fc158b49bb1d2f9703ea92ca00753d9d19dd2e4d1b35b5e2c878e4250577bf602ea82bfa80456219a02eb74bd3464d1547c5e111380412753ce0aee8e3fd76bc1d1547475e501814068bb9a270270cfa8c45e31b33472425a6e01447c7249ed0a7fb0187bca16e28e41585521ce58e46a0f0c82be68cc61ed1772c2a31f9a238d21187451eb0986404ee7eb9e38fb8e68d118a23def1a83bf674c4a390684c12127740dd1347be9ecef80bf68cba1eb0e8fb018b68d820e974453546a6374624631a639935c325323edd90695844dae98016d489a362f1973b027949baddd0281412874c44e39a37c6268e7eb99eeed8db1187bb5eb0574c02ae51fa317194e41b068f459def37dc0d7d41e171d7258e72bc621290987414fa823a1e8f782c03d4f9744a1c25e57aba9e8fd72bfa8e4062afe82b03d4e574c25f9338f2f572bb1f1098ebed8a3c604f474412fa8ac4d130c74b2aee80bb220f580416753bdd12131bde1147c21daf284cf2f97ebc9df0274c4a12ea8c3802067b426230184cca2501d914d5b4514a2ae24808d4e58ac0e2f1e83b1e73c1e3b1a8c64c086ada28a9410aea16281147c0e24f18d4f172c62071f72bee923a4430f8ebf97cc05db0a7fbf17839a030f7031e7fc1241daf48d4e580c71fb0421c25f58abaa24fb723167dc39ff0582c03d4f9a03488a35ed2ef07ecf580c4e22f6957eced3c281047441d5198ebf580b9639118f4f98cbddf714c7f3812fa763b23d048ec1d938844df8f5806a80bc1a886b7642c435c1a1abf2c317dd1e9d42c199998866b804b4c6c7cf0f57a42a08ec70bfe74c09f6ff7f3ed8245a0ae583c1a75bcdc91f8dbe5b2b48723229097ebf188c5e0d0d81bea7e42354a6bde187fbcf27044f4f586ba245fafc7eb25198fc36319a06ec793a03b1ced84c0618f074c0a027b456091382c03d4a9514a93468988846698c60cd1584452624212ee98864a688ebeea70a4eb1581bae08f474c02e6843f1f131b335b84391c1d9386c763b1972b1681c65c6fb8cbe170d4e3ed78449d6ec713f67ab9a3cf6744b3a60d981c03bde168381c1e87c6a42351a7ebfd7ebadccf81da70c4f30973c0228f9823168149badeb00c50e76322b65962faa2d30599d21c6b38e2e576c21d2f1804261183c324e3d04c6938daf1883e214f07d40589bfa4de94e2d0022952d82c198b68ccb4490afeb8884122d3198e8c405d30d813e67a3ea2d0c8e309d70c974250d386a7fbed2ac35152d251971be67ac65c7097fb09896f8a59c8a8197a9122c54846cd70498a14366b9474402e62908a8d2189571c168bc16312d167d4e97abc223058a448515a32c651a3a4468a94346dd4f484c2b013fe78c21ff0a7cbe97a395fcf07ecf584c123b0784c020273c65cf0182cee7839377de1c8b7fb25e9863ee1ce373c0289bca21932668a4868daa4c9e91aa80b473d9e30e8e3ed8ac35fb09803ee826580ba5d084a6b90d0b44913ccf5deec7864cc5009fed2b451c36be168f8db157fbb23b048dc0577c263af0c50c70649a9d8d331a969a344f42d31b1e1fdcac291af181cfa928e3e9f7077ece58a39a5a39a2c44336a86164891a27454630609c8450cd2b12b1c0187bfa2ae18f42515773d239067449334646a9326c7ab0a47ba632fc917f4ed8ebedd7117cce9ca00754868d018893d6153380a0683b95f4fe7f3057fc29cae97f4e322068983a270744ce2f988bd2461ce9824450a1a3446e28f8b18240d7ac29130774c2a067f40628fb82316874724354634401d129c354a445f51d1d7a6084c5192af4d917067c25130482cea8449c79d4e181cee7e4965803a354a6698866894ccb8412a2e2921a951daed844c4b4a6c7a16947054d41d8b471f8fe8030e8741e0ef98c6b844c2d1f0f84bdaf97cc11d51c8eb057fbd32409dd292310e129c242437668a48bc1d131b354a44249d8e8c1a33c22d231c0189bba1eec7e31d8f44e35027f4ed2210e16838f4097b40e2ae9864f4057b46decf4a9ca363d20f98cbe98240a31138f4f18065803a354634453546a4dece67249a41c3c64705ce11af27dcf57e419d5167d4251d8bc232401d129c9e066f8e7841611118d40173401dd11814ee88c337c65f96108e7e441f31c8cb15938a493de0ce672c03d431ad5142626263645a522ac30649a86bc3c6cc12195f90682cee98d01c8169daa8d9e2e6282714f68cc1df6fd82bee72399faf97a5cdd10ea8cb1d733ca330c9a7db0d85c71d16a6a39f2fe9a823ee76439d4fe7eb0179608aae7829da6161730434f28c40623068fcf974ba2191c79360e9c8971bf68e461db017e4f5763b1f0847bc5e522fc82306773a637158fc2531b1395a73d4331287bb2423cf98644c021a8342344a4a4912281d1175432190c82bfe744261cf67d4f9d41ce574451eb14814f2928e49ba9defd84b73b43326ed8ebf9dcfa7db1189bf5e120fcd5170283c02794562f1084cd21979ba32401dd20ec988664d1b26a4a33347c7e24e0824ea8e3a5d2ef7fb1575badd940f8e7e419e4f97441c168739ddcfc8866969b806a96864422a0ed70875109839f2058b3d9e6fb83b02733ca3af385cc39352e648b7e3018b3d2030b7fbed7ec6a46019a06e7785cc518f27e4ed723b208fe8f31589466119a00ecd70898c91498d19a4e270cd4eb7a47445183c1e25f57abb9d2eb703fa823c9d50f8f37111839481f148084cca197d411def38cc117d446019a0ce5740f1e8e81b1e7749c2e06f67240e7bc6231ba08f8b18a430c4a3ddcfe8d3118d429f2ee8dbed82bf32401dd31b2312d26f695866c706b8342c43ec7d493a1af68c46a271c72b0a77c262d1987b639460c91882d29631c32360f1c823ee8444e19178e4e97e422a390e3c48121a8fc0de9147ece98cc75e2f5706a80bee746d9098d21c8358908e82c61d31294724127746e2cf97b40bee744863823b9f5097ebe584c1608f97d3f9743f9fce07e4ed7cc79d2f09e8e3f17abf364846364bc3353d1d970e8e74c45c30c998b4f3197fc2a4dc3077347a01fec8b4493216d77070f4cb11813b1d0f78e40173bf5f505806a8635a524a1a0eb041128fb84b12128f40e3afe703fa7459c4107fba6152cf08d4097dc41df097f4e3a2c16f27ccfd823ca26e07dce572c0e10419f492883be130670406773ce2f0679c008363b038d4fd7449b9a18f972b1293c21c058d3e210f7814ee7242ddb158245e7094e40bea74475e12efc8fb0d7f3aa23008e648183ce68cbedf3007241a814060eecb518eb7f3f57449bde170580c0e79c63245a505bc1c1d8f39634fd8131e7b46dfaf17dc158b4a5d8e7a3b616e08ec1581bdde51c7d31d733ba105473d9d30f8db25e57e44a06ef813eeca007568d6a43183c4d4260d921213523108ace08898a44bea197f40df5057d4e57c3b06b81c097f39e031182c0a8bc09f70e7dbed760c5070441c262505853c1f316908cced72ba326c80badf96a3e03068f41d937e40e270a8331a736a8c6e8c48699496d6e8760b4c90643ce672bedfaf573cf2927cc2a5a22e8b96df30a9b713fe78c31ff19833f6ca0075c636209851d306898d991f972c47493a9dd1f8db058fb95e11d8d3f9d2487014241a8bbade2fd733067bc76052521925269e0e8b8e982b260973c760d07724ee82423444a49d4ecb9523e010c8031e7f3e63d2ae282c168b6580ba20151f173148bba3242b3c2150b7cbfd82c05c4fb7db05753f212fd7f3197d3aa34e7804ea86c55ff15894dbb541b3268d999d2f4d1b353ca634c710aa1c058d47a04e28cc09854322f038249601ea9498d49841d2e97e411f9b242a47bbdd309823fa7a449f5028f4e5766580ba9f9169c8f4c5b79b725414e68843dd4e08fce58c3de2ce5706a85ba3a48406a9a75b525a13dce9b888417a2082a3dfee38040679ba22cf08ece978c432405dd0b8a45465691826894a4ff7e322060908c1d1700804f68443dd6ea84b3a16736dd6a831e32b0283e54817e4220669cd034749c79dd1f80326057f4160122f582c03d41991d2282d1d75ba223058cab0194a2045cacfb71b96612345ca19078e80c6612e97e3e9927640e0f168f49501ea823a6003b9200fb8130a75bd5eeef7db097b3c5d0f28dc25e98cbc5f90c8e31d85479d14294c6d94e00cd1489122fc71118324020347451ff097db018d49409eeed84b229601ea908a6a864a45dd6e0908664bd192518d9a2cb99d980582c09ed017fc117f419e30e938dc9501ea42505a228374d44d203c12fa74493c629038241a79416352b10c50b7e3058f8e40df8f5814fa783c2170d83b16cb007546354a4821282d91098630f422ac02ccf974416070d7cbed78c1dcefe7d301774163aee7d3118b3aa01098042c9601ea9088401dd1e8062984e112b0cbef4877fcf17ec0ded1b7f3f174c4a3ee785442623283660d1be116df51305814ea8041de5047ec0589c1a4a5de516ec813f28abb1d31580cf6743d5e19a00e89a7fb1581c15234a5a84a119822305b8a88c6303f1dd1e8260b0f69d7816e4beea8e7ebe5883a5eb157d40d85bf1ed1c794e69833fa158541602fa9a8cb0987399db10c50b7342c43cce9de48311e8143e3b1c84b020e8dc15c92af0c50b7c4c406c9c864662904a191c9cc120648411f1731486ea21c15813f5f70982bfa86be5cae07ec9501ea9076ba1f539a23cfc7450cd20f4a1a25252434c52524249902431dafa74b02167944a28ec72be6ca00754c698ebc3649462b6c8645b95d9ba225a5a5a523295298d21ca548312e29118bc06c2942e2f9b888413a934030b8fbed78c25e8ec7130a73bc1d50279492c60d1a34528e78c75daeb72326198bbb1eef37049601ea8ac06029fa22458a14294560b614198943346b90d498415aa3b454dcf59698d6f08c4b4ac41e173148460490d8b45942425a7240ef1764c01ba51098840af8f9764404ec744445486f86698606d02129a121a38446802769849c202d29310d9b1019302480d092a033c461121a344a093459c244688900023409c04c9a344a0700f04409132035c6356b864d7e887c7a744f70c123539ba09bce58060989c8c64c9a26b6a271c9585c524242329a59624a808787068d9933b91d3606d4764a4a4b07d07443e3d20133283226231903623a364c6cccbc312600dbbd3173342eadc9bd9686463444242426346d8647631101d76e80960e8980725734b26183645c421ab229128d48ba333fdf6e0dd1c80638e11ccd2c19dd2019919498d6c059222231bd310e501d921a25336c968c6e90de18792b00c801865c350d9b610852868836a619aa691d7088136d3dc4b862ca0f0a6ffe41618d7fab314618a101ab314610a101ab314688d3c09c363d26a62fd8bc2401082facb96211a506d414d3ccb0069acf48f0c10f8e98e99579c11132c1e39b39c658b1472c93b4c21dc31f0f469803298031c50e562084032b1b5820c6831f0d66c8e00d0cf4c0004a1834752e68c06a8c3feb07cc0872b49ca0418000005002f4e3d383bbd978765eae56396f16d695551555726a12492281e4913a741c291b29a51831e44fc091270e69c48ad40b8cbc71c8221628b243542256feac1b221efc59b74332f8b36e86acf9b36ec811feac5b210dfc59374296f8b36e83bcf167dd04b9e2cfba05f2c6ad8f03fe22d108a1ad8715734cf9d2a104800e7452825274518c71cc282d19e318e39861d38688cd90288329142cb080318e193666881c6880c818c74c90ca97461ec90723065efc700395f4650d181bc03043ee870c141c28f80bba68c37b323135273c98d8c789f37050f8a42f441897283768792e32e2059fc4c32bfd3c18e167e0c8cf00050954de4bd01baf409ae1bd50f046201e04277f247a2e4bc030c86bb0e4b9ccf0658edf00834f9a408b9c08bf162f56aa7cf9228195291878c901e57e16b30667300687f982c1fc652f77d9822b98cb14bce509d672962518cb573edaca55a672803008fcf97abc9d2e97d34141220c9f951f16a57f21a1269853a047650a6f676440135d5a48dfba00072d535a767ccb8a5606ad3dbe0918bec5a535c1b724685df9d6d1b7acb4a87c6b4a0b825603df62a0156cfdbee56bf1ca33daf8e2c97ca35ce34b35ce28e34b324a31be0ce38b2eb678022b9ff8d2892f9b58e24b259030e2cb22be24e2cb21be14e2cb207ee8e14b1ebedc6102bfba2a155039629942a580085d4066df34be68fc0f3f93f889c44f1b1e0a083f5ff8e9c44b41e66713f3849f25fc24e10212fb196786f0d34d0179fdace102e2faa9e69da8e102d27a4a3157f849e6e710538899f4b3f8138a9f637e66f04138fcc4602e21c5f1a7121388298700399e8023de890e4e7020603e11143f137f13444afc49e48278246765c8209f83820924aa3e57045a780458f84bf817c1830e3688e08106a020e0c48a962c1260b97264a50a95290b0883c09fafc7dbe9724652a240512001040e30e0827f19f072f22f2f5e7ea25704134d34d1041344e040030a3c809305a47470cef6399ecfed7cae96a37d6ef639dce76e9f03fadccf22203d91cf13fdfc5272007c4e0903b9f9b9fe50e6c03211948926f2f1b0082530fad7d101065831808a152d57b44081a285cad11194232d5ab268c992254b96a3232d50a064b142658a9605b21c09b5440166398a7204054a962329508e1298e05f0600b1e8743a5dafd7ebfd7e3e9f142950a04489d2ebf1780b2c20140281af085e11bc22d07264a5d7e3f114383a0283650d162950b060c17224250a142d5aae5cb972e5ca95a32c4756b25c39dae9ae1ced74578e76ba2b3b5d170b2ae042c1ab0b5116ddbfd6fc2bf72f2d5aa468d182050b162c57ae5cb972e54a162d598ea44481a225cb91957f55a952a54a152a54a850f997962c4752a64c596001a13008fcfdebc8f7af2329bc7f1d4581727414454b96230512f897962329ff3ae08003fea525cb5114287364b9e38d36dc90c0c9802c77c0c1861c6fb4e106157894e1244618586861051438fcf0420d33d0e0420b2aacd0c412704c8873420922bc407857d40a1cf22e3ffac8c325c8bbd294f0ae3346979b773131993133697829bc48017c270cf87ef91670c509a56f6df99616323c6110f8f30983c09fafc73316220c027fbe1e6fb7ee0950180cbeecc58235162899497a43060d1a260a263023019ca212992740452295344328fe33149c394d39869a3514840913260c9b720c35bb252c149cd93d01e2f1786976bb34c4d3a44183a61c030c160ace487086cc57aea0c142c104674e538ea1664d054b1ec986468c3ff26860c031ca1836ff05bc5d9a354ac32f78382cd3e3ed74c220f0e7ebf1d680b1068c3560ac01630d186bc05803c61a30d680f1e71e9963a588224028124820893c0288218f2862c71f4b30f287d5237dbc61a5c71a7ac841861e5620e2841a4a008188166f24b108106bd851471c8a7801c61b5e58716105175a2802061656562ca148238a044205228c58294245219fa3420e2aa478048844a450438a25e644f188951510563c01051a4b3cb28455128fe490f89c531256393bc0ccf05c08a1d3c8e718f9dc229f53e473897c0e91cf1df239e4e708f99c209f0be473733e07c8e7fef85c1f4e767cae8ecfd1f1b9393e27c7e7e2f8dc1b6b98f13932c0e0028bcf59f1392aa2f8dc139f6be2734b7c2e89df4a18324609c8c4c8c4c8c4c8c4c8c4c8c4c8c4c8c4c8c472c220f0e7eb9d1306813f0d9acce0869b4136f0e1bf30f05f1ef90e906788df05881f5c8fe690ffb2e6bb24f0379869037c387abc972d664002cc0e1999b1101ce4a0e9918971e9804bc9650735349c199c1732d7f4f5783b5d4e1804fe7c3dde4e041bc68cf01754f0080a8954808393e60b9819dca8a06e06d9e00866042e8378d9024e073320b10407cc0e9c07c8c8ca24602c0483211ce49039004d6fcd1932b12f53b874d0e50c97b24b1a2e3b84e9433603977ba4a8d16a3db89b8da74693c15eae728eebc1e170379c0dc783abe16838190e867be15c1f8041ee3540e0b9f4b908d066f75c68cf25ca73997d983e9e8becb9cc8043e271453c6e87c7e5f0381c1ef7c2e35878dc0a8fd3e071193c0e83c7d5791c9dc76d791c96c745f0b8dfe3768f337a9c94c745791c94c729f038041e67c0e30678dc93c7113d4ee871418f03e4717e3caec9e3f6781cd4e3f2785c1d8f9be3716f3cce8dc7013deee7711e3caecde3981ec7e6714b8f03e1716b1ea7f438358f4bf338348f3bf3b80f1e67e671651e47e671c7c7191f577c1cf171498f1b3eaee771b8c7dd1e677b1ccfe3761e577b1ced71b3c7c91ee7e471b1c7c11ef77adc92c771f1b82d1ea7c5e3b2781c168fbbe271aec7b51e573e8e8ac74df1b8f9b8281ed7736fc4f0b9173e17440e882f47ffc54aaec78823de88218cf0f24668217a237cde089e2fe2aa08395f041d5f841a17c144116d7e88425e08312f44032fc4ee85307a2108f82000f920f4f8209e7820a678208078207af81f00f91ffaf81f9ef81ffa3e3cf23e30f23ec8f13e98791f92de8731ef4397efe190ef018cefa18befa1c9f370c2f310c2e3200787361e872c7f030e6f031736085183144f83164fc39aa7a1f83310f133bc99e1cacf00fc187e18f080a1847f81f84204dfc2996f81824fe1cda7b0f429a8f914863e05269f42ed51b07a141e791412791406f91360f8134cf813787f42cf9f50fb136a420c6f429737e1f7234cfd086c3e4ef2e3c8f171981e4e190fe78b87b3c087f0c88720c887d0c7bb917a3766bc9b2ade0d09efc6cdb7c9e1dbc8f06d72dfe6806f33f46d027826e433453d1b189ecd0abf34c52f6df04b1afc12965f82e09718f8a5df2f25f06bce3c9adea3f97934b63fd3c79f69fa3372fc192dce24fd19a22773c09389fdd1913716f2c6396f04e38d4bbcd18737baf0c6de1b8d3e69824ffa7d52ec91d47824368f347c240b1ec9e791688f81e4c798f063e2fc98a5ef00eb3ba0f31d14f21df4f11c80f01ca8790e821cd87e83a9dfe084dfc0020d0c780d805e83d96b303f03451e83a10f03f55f06f92f6ffc972efe4b0affc5cc7fe1e0bf4cf92fc1ffb2e4bf00bd97e47b112292ef32e7bb48f15d7af82e297c9733dfc5f85d20782e613c97239ecb0ccf8585e7a2c17381e0b9fc9ecb014618f167e8f828c30f1688f39bddb00161a532c624311984f962654a1c386f4270d38689cd12086b94d4a44173e6033365c81c8d4562d2013b9d4ea7d3e9a4481106813f5f8fb7d3ed8441e0cfd7e3ed8441e0cfd743e0005d4e1804fef34979a240ae88c6cc1b3366dc98b1b2254b0659b284d1017d41dd4fd7e3f9743b222b3a21f009eecb23bed4e14b2da698c1e30e0a8eaa88a1600b98bf78e9c2054d961af0979180cc0d7f1b25e2157f95871bb1615a93e6ccd44069b84e8d03ffa427f8037b441038295d3fd14e7c44bb9fe8924774fb89fd50e913cd32fee1cc61de3067086a3261f84af15385f9e6679b9f4c3fd9cc35d3880f62e1679a9f447c90083f3f98c689f41f77e4e7053fb1e5001a66b6f8528b2002a6d20705cd1404c00729f909a6139cf9c9fce4e0e7063f35b8f8277dd0fcd9e5a7051878f92da880820924680d6fe5366e5120d09e0709209e478ae7c9e279e8789eddf350e0c6bf98985c29bccbf8a5123f7c99c697687c19c69767c498f8a26c79347998cd247e492b0f98c15e70260a3f45f809e70fca6c450d970ffe604b712b62d8cc31d07cb0967b13ad23222a5b43a29e23ae5324f7a6fb2c182e4e83dbe4c32de7782336557f3c5d3bfc0ecc9386c85b61717b5cd749cc3376c7f3bcb83e3cf789f88c95799e0d17c7c46d92ba3241be0acef3f5e477605c1723bf9be152a6c1f3e83c674dbc550097c791c732de9e3af709cbdd8a0f0af3a4bdde4a822a12ee807c1017d72788fb44e796586fc4c49356c35bb5705b2abceecdf521e44231708bc43bf1dd5bebb338b8ad1c5ee7c22d6f7823e0b336c55fdd9eb1379ea7c533767c5e0db75c7dfc8e84cb27c135709be25c9e3e1eab82eba3c78532ba1de49d7870a9c4cbad73e5fa5c5d283017a7e6366d717be4b84f44cf59e58bd65cdb058fe5052c8bcbd8ef7970ee0ff042d5705dc8dfb1707bae5c2721ee0f1b770e97cb9d1ffeea845b254f54de72ca1711f22b36c6f3acb82fdaefa2b8af127e97bc3f3b5c2846ae12dead6374810ab9747e1797c36deae3fa14709f44b8dc91e3af74b8405aae140d9727eab12cb837396e53ecf235c5f364b767ce7df2dd1e11aed31d97403b5c3a3fb7660b5fc4c26d55795df03ec307e92ecf188fb5bb38d76da2f28c39f0bc116ecf18f709778176576acded71e43e1d5dc676785e12d747910b45e5fe44b9502bdcf2e78bbcb83e50ee130acfa17cddb836a3c7c2e10295576ae8f6e8709d0e79d6caf82b019e343fde4a8aeb33e53ef570399df8a21cee8f2457aae7de92b830209e31289e07c5b541f058485cdb249f55c1c521709bc85cdc03b76984db12f3ba0aae0d8fcf4ae0da1e78ac232e10f2d2015e2ad972eb58b93d59ae5311170878a5dcdc9ea1ebc4745fc4dffd716f573eab885b42f045825ca00cae94101717759d12b8b62d3eabf6940df0bc3eeeadcc67a971c9b3c05f7df18c31f03c119eb344de6a81ab04e956d5b94a886e9d26d787910b65e5dac8f82ca0fb8374a1bab83d4fae53084f47dc12ce1b115ddccf6dea725b4dbcae88db16de09ee397be1ad64774af9a2a47b73f25970aeedcd63f1716f419fd5e6494be0adc43c6729bcd5ebdea4b8b0246e39c61b11ab76b83d185ca729ae4f0df7a990a7eb86df79b94a3ab8558e54d1702953e17959f767830b95c573d6c25bc5aecb8bdf9db99c4b7c110eb7d47d51d325edcb5b99b904e2e2d219bae5066f34ab94b8afdaefa4b825176fc4c1c561719ba86e09c71badb93d5ddc27da25d017970e1118f727840b75c7c56db94d315c2025574a787b04b84e6a9e359fb76ae392e7f657515cdc0db7698fcb9d2bfeea85ab4477eb44b92f0d7ed7f484b9f03c08eecfd495fab93f582e5411b70787eb847cba92f8dd069740415c3a4aaa88eecdca670d714b1fdee8815bad3abf537239915fe4c425ac86e74db9e4e95f31f1a4c579ab11ee2dce67f571c903fb2b276ebda89ec7e4de96b83021ee697c9099cb53e7b1cadc05f9a02fee407c1004cfda9bb792e4ba1ef95d0ed726c567954f1b018f15c36d69f03a2eb71ce18d0678d6eef8ab274fda1e6f05c5c5f5363d70ffe383b6b83fc60b35c6f541e44245706d507c562f777efe0acc6d29f04678dc8295f13c2e578901b7ce00cfd9196f45c00582e04aa1706575be0a860b34c9a523c1edd9e33e49b9ad1a5ea7c2a5920c6e1d092e8e84dbd4c6b396c55ff954295da0462e9d086e99c31b092f67245f34c5ed51e43e45706f6ddca6f299c7654d82b79a737d6ef7c9786bc6f0452d5ca0442e1d069eb1039ec7e6f6a8b94e6adc32ea8d54b87c613dafc9ed0def24e89664de88e79247c95f4d717b66b84e81dc9e34ee1300b7dac0136170817e57aacd2de7bc910bb77ce28d2ab8b6dd63ed705f28fcceeab275c6ef6217c8822ba5c395d5f155c6aa159e37133e6b90db224eda0f6f25c36dd1de288beb53c27d72baacec8718298ae139bbe3ada25c9bf1b1dcb83c563c56cf5349ebd2a9e0b680de688c5b32f9a23260393c81982e9d5e1c0bb7a98efba3c895825dca7a9ee7747bdeb84f41f706c76d7add928d3742736d4b8fd5745b47bcee875d2f1f9ed7ba3d8cdc272ab7ec7d11d4ed0cef44800bb4e44a65b99c31bd556f0f96eb34c42dc37c14a54b9a96b73a5ed2b478ab1f9e3440de6a8aeb53c68502ba85e19d005dee2cf25745dc5bf1b3c6b8b834ae13cf2d87be288de7147ed1997b43e2c27eb865226f84c37d71f03ba8cb1d2dfe0a865b2f389e87bb4067ae1417f776fcac322ed0972bd5c3a58bcaefa2dc52f14e22b83d2f5c273f2e10ed4a417162bcdbf041bd8b33e43af9ae0f12f769ea598be1adb2ae8f9c0b75c0c565719baaae8d86c76ae45216e7795617c7749bc4b8a5f244205ca09d2b25e5cae0f82a0f6e4f07d7298bdb1ade0993274d8eb74ae2dad2f8ac25973b20fc159bdba3c4759aba3f622e1416cf1794df95714b3bdec8cdb3d6c15b25726d153c16161748822b25c3258f007f45c5bda9f92c392ece85db74c76539fb222d2e8ecb6d9ae1b6cebcce035e08b74d9e48f6ec014fc47bd2aede4a8cdb33c97dda726dc5c76ae3faf8709fa4ae8f0ef72991fb201f04c6fda1e2cef9dd5605afb3720914c7a553c02f9806cfeb5d1b0c8fa5c8edc1e33e19707b82d78986cbdaf14e3478d60ef92b29f736e5b380b8b833b7098b4b5998e739727f985ca8a50be4ba524f9e36011e6b852b2be3ab2eb83f265c283d6e29fca2a8db49de89998b1be33ab99eb4d65b61b936473e4b8227edb73aba3f75ae14cfe5cb8ae7cdee8f17774e04b75a78fc8ee776857762bb36061e8b8827d00a978eecb25abc130baead8ccf5272b953c45fa570d992e277bd3838ae9392db3ae08decb82d235ee743d597674cea796cdc56026f24e70255b9522cdc928a3702736d677c5600d787779f5eb8380f6e9311cf9a036fd5c72daf78230c6e81da52f33ae22d9978a32d97e5ed8bb8b857f14117ec7ab550eea8f05770ae2b91dfc1706f167cd613b767789dbab82d01dee88c7bc3e1c25e78ce6278abd97326c95b45707d70b84f873c6d418fd5c2bdadf92c3a9e2ffdcbcd3376c1f35cb8b7dd67c5f09cedf056b65bb00b9ec7bb2e497ec7c3059aba74b25cc6b078de14b7d5e575572ed001572aa96a83674d83b73ae4fa04709fd25c9c93db845405c52d5813cf93e0fe605da925973bbebf42ba3f4b5ca8ab4b1a036f35bc4a7cb78eeef6145d273877eebee8786f3d9ff5c1b386fcab28172877a5d4dcb2843722e0bec40769b9afdbefaca82abaa50b6f24e5e9eae17717dc52ce1bbdb99c79f9aa47ae929e5b67de96eb8da678d2c4bc159a272de9add65c203557ea8b5b3af045736e29f44568dc9f2b178a887bcb7dd60bf7c7860b75c82d14efa4814b59d2f320b92d266f94c6e50ef2af86b83f672e541ab7e5c0eb76d707cc7d92e2c923e7b1b25cdc22d78981cbb3c76351707145b729e92a31deaaa97b33e436155dce3abec888cb1da4bf4a737bc113b570cb45de4887cb93c86321dd328397f2c8b386c75f0d706f57b7698127ad8fb78ae2d6802732baa51b6fa4e65933e0adee78c64a78de0fb725c1eb1cb83f52576a766f237cd69c676d85b7b29262cc2da37cd11df7223ec8cae54ed55f29716d597cd6ece216b84d6f6e4f15f7a97cd20a78ab0caeeb8adf156f8f20f7097879ec782c2cf747903be7cc055ae04ac579be56f85dd625d014974e93fb73c1859ae27207cb5f192f77fcf82b209e73882fa2e11246e679c0aa12aead8bcfe2b9406bae5418b73ce08be4b8bdf244215cee20f257443c6759bc95d0bd8c0f325e1f302ed4ed594be4af8cae4df85842dc9e28ae53d62db178a30c6e6d78274d9e32079e67c873a2b2454f54c02d89be488dfb43c69d73e52a8972eb20706d5a1e6b8a7bbbe1c25cb8383ad769caadd713cf8bdd1604afeb5d1fac0b75c17316c55b2d79ce7a78abdbbdf1719b00b83e5e5c289eebb3739f92ae0f1717aa760b167bded0b5f11e8b87dbc3c175c2e2b660785d09d747cc7dc2e2dac43cd61877a70f92e2f67c9d2eb82fa5df25727103dca6e1a52d89c7a2aa0ae1a92476e99cb94a7e6e1ddcfde8a71797b12d9e47c5fd5972a140b8dc99f257c4fbeaef8eb840825c3abc7b33e0b34cb8b73e6e5300771a3e6877c993c15fa5f13c5625c75b95bc3d52ae930ab755c2ebd8dc1bf0b36cb895e089e03c5f50cffbb937063e4b874b5794df15707ba0ee13947b7be1b316b93e2adc273e2e7986feca8a5beef0460c5c250ddc3ac14ba02c2e1da1db009ec875e93afe4e78950470ebfcdc1e1aae9320bc2c1747bb4d125cb6d672cd7ed7e4495bf2565e2eee769b28b82f35bf43e4025db9522f5c1f35f7898da70de8b152b8e50f6f04c17346f55654ee487c1096fbf3ba501ddc86792217ae4fee3eb170e942f33b062e67c5af4ade16026f74c7d336f4582e3c6b63fc5500cf99236fe5c0bd0d729b88ae4d91cfaa727f40b8506f3c6d4e1eeb85fbe3c085dae1928780bfc2e27216fd93281727c46d22a40accf5e9e342ed9eb509de2a904b5802cf43e09266c15b95b983f919c82d5578a3049e30419e97c1bdb1f92c3cee4fd28502e3199be3795c5c1f23ee9324d7e7cd7db2e3defa591a5c2027570acced69ba4f08dcdb93cf8ab30b737fa8ae94923b1c1f64c4fd19ba506eae0df9590fdc9ac72f22e139dbe2ad869eb536fe2ae89643bc51958bbbe236252f4e91eb246c9dfb13bb5048f7f6faac313c14ee0f1677ce02b71cbe51ecde12b94d06dc960caf3be18ec807997179fc78ac2e1707bc4d6dee4dea3629707d92b84fc95be6f146719e31429e87c6c5056f939b8bc3729b58b825015ff4c6e514f345709eb1029eb774cb17dec8a82a86fbe3c185f2e27214de16c253563eaf8eeb03749f3eb8f562e379b78b93dda62bcf5a94b7cae35903be951eb7bc7aa3252e5b70fc8e76b993c65fd51094c1e551e4b13cb83d54dca7f9841df0bc032e77e2fc959bfb73c38542e43919f8223497ae36bf7be059dbe1af5ad7b6c767e92e900357aa84aba478ab26b925f28d68b8b7292e4c895b3c6ffe0a92a78ce77979dcedf820268a66b8b3bfca877b0c1f24e549ab7aab30eecfbc501a5c4ea82f4ae2ce7c5f75c82d5178a303aecf1b176ae8e29208993fa8f42a98e9219683108662008661281453ca20536a0500f314608038201a0fc8246a1c2469d70314000d4d96da96309bd2520c858c3106104280310000000040404020006b8681a21b248473a4a0b7e247171ad57212a864049ef5783bb11980f8930fd81ba3e0aa1ff694b5c2bf8647f7696bc1602013df2d8798a0a57179be6fe0f1dee152da91fe8208fb0aadac8a09e453d315d03f147350c9d78942f5b90cf5e0217d3251a278b28da7a654740602a2bb04ee8df8db0db95414bf69dab2f56b6cf85c8a6d51e6954b2cda8fb6be2c62f5cb59f514d8ad4674f0388b2181ca81adc88cf834d1912fb11e4f2b12ec0897176121fb6a797e69b399131122e07f4bcb5b3676a60422628a698cc09129b0510b6049ecdf75019a4c6d81b02970b92b41b59d727364dc58c1910ce8574768d692a0a15eb60bcbe43c24566ab111ec96c165f6e5fad4bfd086e94d1b9258afb4efd3cb1a083edbb30240890df117f50ada2759d95011f58996343aee005dc82549650ea6ff67c005d31f99d0d854c3f85c3a620e3f04a9b32aa425a675893d0b042521615f10192dda53425b430af643dd6b4763bc28a15aaa11ee64b7ee762467c4a2e77b8639a32db9153d8aa276eecf911e6c9ca0407c00c93f1fae55d8ff306f0fe31b8d2959519e928333adb761e27dca664a81d122322141633c4bad8bf0606e68881cf6402fcb922ef658d32bbc981a9b05fd296cf55beceb078ab23ca855f046305f5ddb7eeadee0ea516b9e3829ace5b8338ca01f4e49b72e40ed23cb459330fe5222edb3357f3b806fd205fd8271f2e622697ea5f6505bff050475e2fffbc47eb65e6407f32bf9d02434f7eaa8af126039570dace33f2c63b295c075abe9cd43a260c49fc10f08b731d04fb36aaef7b14c11f4fa584d2c5d4f4d689745922503480c1dc93a9a85c26b158c125e4e8df20c024af22613ebe72b2192e92fc4df8940cc81e619b16f9d26d35d11901d41b9e1ebf21809e82241a2efba08aa34b95ce274c93e73f297f9cc636875b2724e0c805b0b2f52b4676e5e46ed62197476442f0459f038818de303d873715fc5a74449cba58249078698b3f04a197dd30172939f0ef40f8e9e220a5d4c7748eee0f8f9066fa175ba2a49889dc56ca00e421b691d9837993c85623fb3195cec6511f125e8472b99b57c55eb43e3e6887601e8f481eaaa5d9c9e951cfcf5c03ba465a6317ac1a89b60eb4c65a1c9c5828e5b19d27e041395730832e82e891c89dde6d0e435d5e37cc2e6f354e9d7fdbd577d68b2ef79f3c8f4221522c07060850ad26008811683886a0ca0158d4424bba3a98ed60e45b5a72bd6b18d1785901c9eca73bb57f5dc36147efee2ae2e987a02c999a8b7cabe16baa8a3fe651c2ae025407839b74c504284771ad9c1b01a71e7426fdbc40544f1df13971e45941b517454be0260f0dbe860c436568da41b01d713c56125210ee0a14e0a7ec2a8bb5fbd660d3ccfc685e77bff843864481be06108b68ae0710ffc60f7f605143b3466c85a5df62dd1a8a5c59203063c7481d19b1dbfcb10583056f4790c7ecc533582764481d9951c1a4633ccca8dcf57a10470703d981ab9854f7535200d295f347cfc0e00c22787cd982f4c8c8c9e50fc08d717fc32a9cf1d99da14f3f207c58b8d9bfcd24917dbeadce49f4e76b1c9913ce9bb1ba3dbbb58efeff61c1de66205dead32fa8c8b1d8ae54fe7ce39d0d5dac138aecfc01d0d2c3eb25d97b9d827cb8e301ae11d7f291e7e61d065660d39c6924a3e5015b8242882e4272fdd3426e0d59d5975e4f751a322f5e85061643fadcd7c5cb6dfdb473c2e584360db02afc9c01c3373931d7981b6a8bdbb98f6f40cf5f87ae717a565509341b69a30208aa68b0d2df08c91df12500fbe88002beb678dcc2e69a7194b293a3c7cd86d0eea912cee5038b28c3d194eb9ef04bcfbdf851cdc430efcf6959335758259728c34da24de073a028cadce2204e29dd95cddb18a88256a68caacdbcc2b32a40bd17b845e2a3a60b69b1d139e4a7339438f0dabba87113058967a21527713d5c30579d19b0010f582846bf1e81a57a5122475e23237e841bfa688bf2b714ef9f7818958bfa708b89f8742506d846638c37c8cf140d250f802322a6431a6a03b5e350e3569a8dd10d755f4b7e25cdda07419f3f4c7ee494d513e4f6b7e47ee4692ddf4f29b03f2bd3afaa59b59d0fe6885db7e0d5dff7f796012758e518c6cf92281c0c8db4ff84026aef5e704719f89dadc7f9a3c8e694ecb1d6f714d139c771e4ea3eda75e80d3a4b328d22aec077372070299fc0f07327bd0320959701e64f20bded634a504da8438403cac3b1c478447563701d9da670b3f0304991f3a97d02c7fa94238643af8913d6050c9ca3219241d371bbeefaaba1ecee00094614ca82f0026a13d63527647b8e92f70c4ed927fd8211e80baf241c93afc00d445a0f795a3bbd2529b265711c0d774c31481396132879c231a19c3d53bb94ca0113926b5c895c04428b50d8ad6ec4d0f79c427d8a5ad9a052164f2be5a001f3655a116ac738c60d1825da6e172d128438c3a7dc2b57d3e77b456392411f68ffbfca457127051a0b85b039c790e80baa165c0c94b3cc3aea0a7ecab5dca90b6689d2c96d6e8aa4163d6fe0d9fb833ef782601982dd47678943e11268e93ae3dce26d819994220c4168c99904c879cf24035daa7be4cc971fa3c4889c2067af4a5b46696028502013b8def26ebc0f2086d8e67209083f9b77a51143bf895e45a652552613660428897f351a08e59530227dc88570d3a5e9ead0f65e1ed13dc41b19b7617568455144c5833673c90038a2df9c83c044b09086cceb916b23656f6445d25c07679a6dcac3338428357ec929264cba0bce50a202a27f90e2453911650e0895dd8acf5351d9a02ee9ae588dd3a80e2da26e625663cc302e46a291896d45efc2f91f38d19b61175e0fbfe5891221dcbe06ea3d1c65a8ad7c3e9e24cc90bf8df91eff8cf7ea4ca18a65f9db490ae69d3895e65b5cdd5c1f8880bb63f629a223bb7365ea01f3580dc7d3406ea8f1646fac5831823b1b252f40a25fc71320f834fe0c3f03183fb28e242c35ca611863895c280568ebeb2a849905a764e32b33cab8c23b8ee974c424756b43ecd1db587e8d40b71f25f925bd87f292e16dedbcdf7e2efa68e28eff54dade456897fe420eba706e45166845d0cb6a5316459954a08efaf5ca246bde144aac41be222b0bd843881569a3033a2d9989c356e6ebb70a91e3855aacb42f66b4759fafbda97a0a5c4aec8697bf4ea17d2ba1cb5abb69378c14a4c2a8dfbde1f777e2763c15fb0d4b09d9e11f29d63062cbd1565e1e25416a0900d8e8469e089b2ee6cc21fc8d11ba3ee068a2b520c841a4ef967920b6c3d6044fcc7c17cee4288bb15e33dcb520a77d8bd894b42a5e665fcd3fe7f9b2e7382be70811ad960acf1c77e8b8d9c65ebdc93608643776f65c4bc02fc594c070d3a6e642bd068b87b973e9ae434fbdf85b7f6835d744e98532625ea5f84e5446213e3f6692afc80af4cd5dbe5148da0bcd65ec599670b1aac0cce394932e93109a9ea3eec9ab1bbc4c89cb12e2c2739b387d707810e481d8b60e2efafe609bad13b9ca3d67b72617bb4bfebf43879059e34fcef460e8b6b0b16f5a07676e0ffa13e47404cd414dfb5c77bf953eddb5aac62760174671af9da180ad684631652a86ef23a8eb642a8a1b1ae948d6a393e0d4b6ca7c8590ee5de5a2c9502a54fb1a8ccb1a7647cb579da9dc61ef04f37932cfe4b16ea6279a287a94840feb3e5be15257c3e48973e0c6c110f0b5373699270441259418cb10ec5df47e7f5da0da43f3dd3fd9c64cb504788e9d1c762b7b84ac7765a803d6b411cb89210695adb6dea1f2ecdbf1459be3ab35fd89f3ce0e3bb644d1b1c44065e4c5366b08c6da540caabb8e37ef268f966f624c3fdc43d0501756cdaf52c9eb84fbfe231d24361b2068bdbd354943226be9298f5dc08fce3505bf0f165ef653b90543e54a1eaa4054ea41aac629a844d22ebc6f1950567eb21e9cc30e1ae927bf93fedffe3a1c3c397b695387318321d80c8cedbe198ae8ca3e13e5b47b9d9a47161b792e15a60c0b3f03aa59c6ed2357bb9e7298bd0f19681716652df950beb945c2abc218356b77f1787197971a1fbc7af2d3a73d70d7a8f21184a8878b6e6f1c83b80f308a3e0b4bf0d0c317a675aba1683f4e4189b2079674c18820c4e2ac084b67ab8fe1884dfb5595ff454d0d96dbc458b0338d53e2194d5fd5ce5586051c8b6c58cc061640953ebc594ff8bc7e97f911939f26b2ff71cff52af3b6ecff7f7f4fe5f35fc59f796083582572a53157ef4237a20696f52fc3610f1e8c9909deb798c0a01a09a62707b1ad522f1ed5518507c33d8f27d1d0fd3dec563757de6f92c4ae67dc5fdd0b29ec929cfda2dc34886282dd1f6a9649aa690ad9c9f1d146e41d5a61504ea3c9379e4ba897eb6e248a3b6ccc82f697f5f99e357544869d408eb0a7d883098e98fc638ff3b33faad88c9122f1c9c3f346f347e1a4f588c811d9f127a61e36b3d53575272461252b9becbe04b1f7fa6e319d14dea55d1613c8b68caf8eb35ee9663ad28bbf628623d14bbf6289edc5c0032ff5293db401b915a656c61474fab61bdab4c44241d082ac054c8deeb863b0e6e6a8823a4e8d24d849a7b4ba9e38db9deeab6907aab183564daeffef0beb5e9cb4e90f908726620c9c35320978d02f922534b1e6535c9744eecee94eda269ddf656e9d963423b0150ee756050362044f6e324b10299c9179c4f4aba7a7476ec0c1b11a8f960d8a1a8c1c39764cd916b137cb4a7ae699a6560c4db855537b145a6063db3808a33dacaacc6db403e7031a7e6e6742634fb0bc3dcb1b8f98af36ff82ce36c5d4367b8eeac07dad7c3bfb5d1777dddd84a9730a4e740f6618ddef3585ad99f4570f2f8f6f6fe07af261229868703dbc94b223ad6c7fd36bccc315745706fdb85d815ce5774bedb5657e721a4fdf7af9dac39cf60f3bd38fbcfe06f6d79bff50e33799f04830bd8d0c493d2cc99d48f60761683490ef82026771379c34714b6c3d3c47ebd800313f3719bb8ded5cf04f4cc4ff04b5b13d0e4aa9eef7d7e684a9e95efbd1700a73794d4f9bcfebd7bfc318027fcc6dff861dcffe228bec4c4ca6adf0932aa4066b53fea81c97fab843ca4772e6d2875e08111ff5ef2a8d3463a8f77dabee519edf4f3d8d77cea537314db579db8de9726b8c3ffa4af3f909ee8c7c8fc3b533ae3c6ef8f4368b6f6d6d19e1ffc0ebdbf8390f7b060e9a377f2fd06f2ad7455e8d743d635a8f3cf206d51c666da915fd87b093cb9f7f6f12af5d971969abce786dfe0f7e7f0eb4590b8d7d5f8be9158f2356c43f42b3e66e86e996bd61f08c9a90377edad71a97df8cea2a7069ece5c2391ff8c29758dfba23dc4bdf6969925ded1b5c5f9ecbd4c3f0636d22bdbbf9fc1207bed8f2037304c9cd64df206eee7d9d38fa113777e4d1cf2df834af92818d7e2c0ba20fd5f8cee3f07c8b54e8217168986e625a11b4239f56159ce9142db12e3903ef01de99708af789a555e215c4fcc814e17a7fb9863e37ab64d5e9f73a4b526583722162ed9c29d71d73cddc001df2f240ebc0656ddcd2ea53c465167c365e9c7b0ed3cb84dd7521fa1e061c5b3c9687cee2d829c75c517eb76c6137f7cfa113f680d89f9bf3ceb956cfaa435a7f464491c7f12c45b18cf3eee65e4022e64ba58c562bcad3fa1d923ede95d22a0cf98c6df9773cce5e3c878ecec7e50cab35c741fff6ce0efec09e25fe7c9e60cd9416a4af6e7fa0b763b7b41e6c92a3a5eb1948fedf087bdcc7d18caf177ca9712a7436fd2510a3e1ce1903cb2f1716a415fd687ad2fa2b35527507a2c28fef996d67a9e900fb847b9e127cc7734d6e7e923a39c1a3efa5eb9be2d7af33c762efe17ff4406313ed8e194d6b3037117b6d6bb725d898f6858a7064702afd56ad31e36cfd974bb9fad5190b83443adf5851d6ff7fe4cfc63c8072adfb83f56b1264addc340e4743c330189b598222c37d8c7f2192d982ad247f9d23958ebe867753363fdcceaf3522efe6105dddcdc531b0e12e5223c095c1e8e3e17e4901cf8b81cc16f163127c29d2fee3cdf877757c046a6c4e2cfcfb2baaae3906b159408f366b95f8913f35bb8fc9c3b958955ad1ef97ad5dba6288e1295a754db26de6ffe76ebf22d0edc5fb123c74bb8bd64b54e7beb2eb51a50eb9d58ccc7bd7a0ff998d8027e355f24cdf86514ce49abfe6deb35a37539cf078e9ba91372a872bacc3622925d18fa0af36164f3038fb9ccbda3f581435f74175496e5e9a4b2aa1144e3dcc639a3e07c956949a36bbeb7ffc609b7f013bf3c679560b5cd111c20106e78d39c01f49eb0f4f0c6a07f0759e91f64df47a846a0b1bd84ec7f0aa1ff0b7535bd714dc7de333d9f6b3a2fd3eee126e49e46bf7f42cf9982c6ec56bb973fccb3bdb17fba978fdd9ed42875bafa86f1e31eb481ed8c0263448454f91e91c858e4c6d63ead295a39a9eecb91a5cb56f0af6794a7374febbabb92cff2bffa4c7af2dcc7ecbfe5f993f22d1b91b6017efeeb549093836b53f68826efc57c27c57fc963c60a705f06af4dc91bdc015b13b227975315afdaa603bedab28ce533711df071b18d97d92c82f4ca4c7bf26e9a999f86e7a970706cd29b6783f1b055f83f66f610dbc58648c2f7d4be3b65ff5ecb9930360383c773fbe0ee4f98ceaa357ae1c62d55f6df0a56cccaad24d6fb0ea3856ed142441f93b4dad7661467f0b8a337e8291ce050ef01ed1423bd9aee281dfd08bddbb1143b9a5e4f3922758786975a3c37ceb172bcf87c62e45f1993e10d20d15a91f1a207af95e22bf79d41fe5958533d21c3310959743463e1b33bc62827bed5fb5ea75f70bd08a48cb2e6c4a9bd64989ae0033b3e8b03fa7cd3ff3be7275975c9cdb621339c596eef6f02c0feec1a6b1ea3bce5118708a7f6662d5eb8f71d3f08f468b30d6ed9fab9fa930eac1f24b80480afbd6f4dcafc838ce79f44cb4b83abdbb5e37b275716dcf656a53ee2da9e766dce8cfbe9a96e46927be4e6da82b51e779eb7a9cac121a4f21887be70b8fc45cbbca27ae567be9058cffdecf7847f697c743c8b2fe3b62ef7171fdf9879a7644d001b1978501d342b60ff7510ba85702423991d8c05e4f94205e0c4dcdeecd11dfbceb64fde5c94f3e50ed6cca5b4b9a388641ef638b7cb367878a71eef9bdc421b0e68ec6c127b5ffdc4bf35e895df193549d62f09e5e6e852a58c2e746cc707f17056ef12ef96efb108139db2c7a836ed1b98f3f0f10f7fc7a63feab71a9cd5fd7dc701c30de78d75380e8bd07fe39aa97f43df66ef461b93df368ac573980d7320393392bca16df6de5ade7b3ec2601b6c591c2bb3ab3675d3196f338e231d4f717d439f2d191c1f716772f3f8e419feae8bc1bbed84373af2fbfe01de59ce60bba1ef917a8e5e5aaa3b7f374f2cf435374e5cf8b1b1f048f39928b7faddddf902a6bc4d121b9fc5cc05e25fcd5bef776afc62eee1394ffe69c12122931f620e46707b723d3cc0dae0f710be2c33ecbd2effecf2376471296b361ecd6efa11dd9771e2f81dc2ae1911c38971fb598e99cff377dcc3c9e9a7c1618dd9cd4f9e0c291d3bb51a024799191e1f11af0cef78aeccb3ffbdbdcbbc71c7f7b1dbe3b33f41e2439b33bb05a3e3342233bbfd090b3bf8cbc0c5de0d924dc7c06f2cdcf22878ef98580ae2cc9c0a6e3bc17845b31554692ce8f516e4ef8261599340f18fc114f732382d69432c3d233250d47b1beab9678f1bbb6b650f6aea7d38bef5feead1385cc856f8b63da17a2fa8e9b4625a2f1b37ea9a2f407a931afcd960e9338ee5c130b79f2d7cd1efcd84ffb9621ec5e978307a49b9ed0306e7da70427d0677ddf047dcb0e3d67ce856c330b1da6f92fe6478d4f4f4d955e8aff96777de4ebfcbf7d772f8f56390cb493f2f94c1b8398c6732eed9d81ec04d9adec61d79248a453c78ffa36e9ceaccf3a3e03455cfeeec73977295114be307cd689a91611e2ca8c6dc663233e0c5b22a4fd75096a11e59ac82895ab235cc53f5f38dd3e1b7978ff8409bf6bd0bf9b90472d78617698da6d2c9fc4aaa9145992d97c975791d50b0a6dccd302be08bb2291ff5ea84c8e35fb99dc76c7455bce860620e894d43793ab4771c0937717b700efe486b7cc4671a7c898f13d3f17e9836a59fda02b1c199439e6a24c0c02d4ab966ead5ac2c12955799d1926dc836da5a7d458d7b4f953e2baaac2a23333bd37487072866fc5915e30d1f30ebb281e5bec4e05ba5cfe0daf6b7d4903e17c4df1b01efa70dbdb19c19414fcf913e31fdc4ee2ad82aa567da8c326e4b4cc62fd29e5b6fdd82e9f644ed3899edaba3b434573b60c9c3548e7bc9ce5cf7cace42c7b96bbbcfcf1b7e31ad6c9e3163e7bbaf79990d071a77e7296426beadc48f53cae82103e56ea6af52e5d8a0373078bcb1a95e76cca55796788ae9d26e0d20f93ffb60ddf4cbe5113e6e91ac5dee7cc3407fad67c8e76e8f41a977f027bcaefe239ea7f1edd68a7c75c5f1975bef8a4ef079e2a5b37c12e0ffc8dbd71bdc3b5d31f18887e126b30319c8bc07daf8e40725ba0df8194d0a7268e097a13744a71f09bb50e46d3c68785e4cbf1b13e7553ce0bc2f0ef1feeb36ff7704b0e6820337fec0e9ddb789a7bbefb73dcfeab8e35eb34b0c3640e11fb6f827c47581a110665be8385479e9fe47fc6318ae61f312f617f8e64d071b687f8f4277e276cedc9d73cd0c5a46c9310d54afa6dce769c08e02b5f59d20a7aece23033a230558497a0f74436ea6c176ad3fa7b82fad78afc5b9e6eca6938e3d7cae141ffdab8764a2fe77d10dbbbd3c4f28ee4656d246d1d898ab12c047a642dd099d853d565b22f9595bc08db62d72d7f116388fbf19f5cb7ee3dc3a534e8bf7543bdafe7f5fe439bcdb6cf8e6953e7a7cb8d33e7f76fc861672d168c5fec56dacdea7a107c04c8f442eb5c6571c34c2cc58a5c62e72f65fb24c921b406fb90022f188bf903699f86c30707f1aab7fc5fef26fc924f26ff5e78d312c0de5535acf11c3515eb5dec584eeb7ffa8f59383c1ccaef2ccedbdc7a1ccfc4b6ec02619849fab56fbfa3bb08cf04ccfae47fed5de6f5cd09aaeca8ee4f77c34b18361a1f8bf6ef3126fb65d658ac26de465eaa0b0f01dd7c15b2d7fd0f367a28a935a3ec870ad0d0bd5900f0f7174a9ab8af02d58899a5e7b2080809c15a54bac9cab0eebab6fcd20a6f9aca756e6b6cc47f6a8d111d863146e9406d2692fc7f0663f7661db6ea3ee10155fba192c191986c474362d662f9f1d0e2bd52697a1de1939b7d61d7922f20996fa1973a24881f2c83a3a516c435f7acd223259ed69af49791deb8e58cd81e62e2f1399d8ca18af93ae5bc25c170c77eda8b4db5ea2d97240502c589b589b33c74d82ae1fc5161caddb7bdda07b5ba7d376a8943577c59a2bbcac1f9f617d66748b5d9f75ce7da830f31dfa3ae30a02795be99cacc458a04ede62254d32ab5dbf6a8bd43ffb22a9668b66a2ec4074441cca56a0b33cdfb9d6a1d42bc3bd1cd297b20ef13e9a04aab88a8cf6b487eab841737497d99451d12d6b3774cd89a35a32e8ed6961b09a35e29ad46629f7763b3df3096058bfe1c9b55dd77d3f4f695fbca52b5ac4d9b53ef69bbeba0b0b666a5ed4ed657c9a08d9dccbae57d22876ec8725592a4b25d75f56662c71429beac2a6ccb5d20e8ef22d49630339c26caddcd9214e478b14de906192490843cda1b16d0aea0a7a55ecbd7dd9a6a39e18d5ecc03cda984df1cd8cc80fb79e2d7a926fd655ffcd814b2ead1193d216bd2191af69f6584d2e43751f4e83a99b3b2eaeb093ccbe3b3af1323fe0216b61a519f641695db3f5d22dcadceb4bfe18b23866e31f52570dd4a2ab3254eab58889c2bac4311e0393ed27ebb528d09798fd84d66a59ad9824dae8328bdc691119cafba66738ee646bc9ac334acb7a490c17bd5e5d1ff156877e614659e06c90ba87da45d35c2685fed645d6d56f1b18ba6eb68ce30e7ba5682073861ae2aa35eae2be25b4b4fb1f9bae9ddf82d3cf1f22d79a66d4429fcddce0632265e42ec50cbab4d8182dd15a2d4a8b47225dd78f7922549b7e188a71fbe1ab4bcdc0f9f0d8c711f4dfd52d5ec5fb24a75e9eca5cd67ef510ec9d0237172e35ac7337df4d9b468596b0b430fc5675beff10ab0b19b545876ea9e2e74ae339efa33d9c23eb1b2fb2365781617d46ec038f1c16ff35c6a86e16f8de484cc91b35fe2dd30c8aee5ddbf9675a6ec4fe815d44a2480e12ad0e6cb7626630bb09b2ccddfecbc17b3def9aa89bfe80620eb261c31e92d11b7647231c9bd666524363ef2f014239c3287565b111e56a6487c0d53ff7dad7f8feeb9541fd81bac0a001d5054f767193146d68a3548dcb1beb98d00824af5503210b6ddf546cfa014e0838e7372367d8caace92d7c724136a96eb793aec8280cdca7516b88ae6fb6403c98bd33bad7e7a8698c4bcbccd949c4630c1a0b239a64528d7b3cb1ba7a209c2c9543b5462909575b2ca3762d1a48e8e9333d8d5707bde3158f141b73a6a2d30a2786737f500389c7fdf9cb2f8973d03cbea6c0d2f38cd89e1b3d1c85998a51e3ddebf8d6ccc2c11127b7096782159deb3d13b6d86b3f4d714e00dc20d92dfb81290a5bcef154ec0f77af40b1c246120dfe5aecc46ac5fa9dd1770cbe2e225373e86f6bd6b5a92564c1bf4667739f2bce7aab003872896b7f33111f8db1a03a71e4c2f0dac48ae9b2d88f25af344e9c3facd437fde79a77390d456ffc4c52288a20e02e2271595f2daf6d07469dc297a89fad063621c5a7395c0ed551273acb91efa7569c1950414e496b061feb50b57c81c3ceb6c87dcb9bc7cd7c960220a619e6241c8aa9d8fade929dafb5bb4b7de66b0e03b75b098b3779d6c243b92b94e1b60fc60fe9b3208b1280ab6601bfe88cf5bab1fd050190141702e1a07730726b291bbea55a22f1a6be54c670102ee968349694af1ac67359f96c0ecd63d6e451cd328e3cee2885db44d93f8ab8907800b7f5ca61ac04cc5488d2b54d94bd14a36bb871653a88d145f3ccd78cbee9bc03dfd8729b757227d9cfd7ad764010437b5cacc65b84a0ce818ed056fc40dddec1ebb156fc5af1d83d56ddb6b1b5b3d92ff1d20e670fea78588ced11cb9dc0f924d1ed3f1613bb7772be2cccbe3ab5edf4ffa16d3e33953a5ce703444c8eecb676cbb1deb84c6537e5036c0128af366eb6760967c3ae2323bab05589b50bccad46d6268b2b684ff8a3fdf2b06a8252f5020cd349140f13d667687384af1864ff3c725a301fe01f814dc64916f6c779be9fcdd74d3035e2aaae9752d426bfca89fd458c6d374e26aa946ff4320088edca68f76184e4d88e9052161c451dab42aa5f323188abf09beaa8218d42ca4552b13055ebee4248e286176b24b35b12b6f448cd433daa922c08bc89b1a5bc41d04ae9c007f775eaaa1e9dbb336c781797fcde05d859fc70cb7acdad3eeda8c6fca2f679fcfdaf11b399c42f95e51073ab683de634c552f1043dca8d6e1d90ef12f7e0e05354092b6698ef64cfcbf7f4e455bbe6599c6b3b64b82a93ec1d9a780522928bbc2392159da116e0e545d961fd5ec9eaa4240e1e83dc45aa14222d5332640572c981e1fa9046b4b2c7a7c1d3ad0c205f8acaaf2c4f1f04dbf99a62931575ee2e23db2b5d5be1dda39d2f21e98792158b63feb28c8792f752557a44cecb013d0c36b955e199b8080b59963df0c39eed71f09669620c8fdfb0c206ad549e787614197eaf70255f1ea2b0867fd5ab87a1e0b5e2f375bf61bf0b61bb0b6122c83843f27a327396c84d8360a466414f2c90a501fab8766250c09fa284d7f61e2e8eba3e0d3b56c4ad5b54b983c65e45f183f871915851c623bc45d33019d3de91b014d89945b580cc94231c9e70387cbc85823aafdf8294d8a54fb95dc2976b746fdebb84909b17a77d36a685a027735524aa38161fddc428c85629faf6f91f84a948052f88b9988a263a4d8d9bd513db7d045d401895fcfac62fbc391fe699688983631dc8a2bb4a0f7d912b92bfc52981c4a4202f0a314105fd841e12e9b080e155a5e58ac27871fa0899248ff6f7c3d076bb7fb0501fc639b1afabf0a8205ec9d7ebe5ecda36a0534c7e6bc62ec839722199a5723b70dd6da447c89f94e1f40beb9a96cfc26ec2b0e18c468dde7f2c64443c5e11a40da55865c2a1178530f03950ca02562525fe659b56970c6eebea8774086b3b218e80f87b9e18ac7d4778fd598e77a5980e2ca706165eef7a839c4b4a3d9372bb0be75cca351cf2c1cecd7c9437974a5858ca9502c49ee9bc18fd18b4b6d67882c2b7aacaaaab0c08bf8f109d4bd7fdfcaafc3e681ae632dfcf3ec1ca03a21555391b9f30e8ae31542e0d175e98bc21626e118a89d9bad87b38bcc5bdd75cc7a0b42fea714460f599e28aca5eebeb523b48c2d98ab28cfb6f6593767779c6a289fe4f56a3519bfc52d782ea6d42c40bd6f2c69831fb4dee793395bbfa243f1afeb1ab3696e016e9c86392ea0bd08d347ca77975930652642b502ce46e8a8c8824b02f53ea064a80fca2922043d1eca42466ff878178143f96a67d0217d746a4830979ceeb1282f72fa8020d88dffa0acedcfbc4d7413fe0223f1fca7b11e9d307ef7106a212c14eac575b03e6894a3721ec49d462fcd200d09185a20970047f8fd70b2981a4fd57caa3ff4f9f1a80af91442143e33211eb195072273055a1aa868078cef0af1e40f3217caad2ada40a44b5f0c0be0251787ec26b484b7a3061747a808f40ffd2bdd8df78c2526ec1b90b5f5251c7f21be7f9cdf960444ceebd11601d477af56fba1c18191cc557155e0708cadf3a13b73898b5ee7d5345a141b307415cddc8ce00b05f2c2584986af597f59c2d762782ceca8c7a81702e4ff29f89afe5c458c80dcbdba6f6630f9a7889e0535a45f9a308aff5dfbc27a2fc624cd7737e2861cd7147a7be96bf19e233d3af14545c275e9fb2ccd95f2fa313d5f68466fb0abf0e03b0da62696e505f2391603b36c49960f9bea73b01ee234c1c80b0af510682ebb9171290159fa57be8cbe0060d0f9e00d4ad21bc56703f14200ebf1bdb529acc5ec4364f94b3822a76fed97ffaf93bb4d187161d507d7126c115f65ca93c4693a0ecfb70754683beef2d550f37c71caee1f8dfb315aadec6fe3da30d350b6a09113a10b55c32f84bd0ca221070580a69c70f79eb0c06263feb854a5f347408f3bf50a10f7c2ffda86b13f6c250dc02ad69f1c84bb331fc510835a4a75f2334be114e2e31ed3b9e4fa13a8e85bc1700b3fb4fd3fbffbfd1084ff18da03381845a0a9e2b3cbfef499069f4b3a5e7160fa8a654c304d8ac5a15241486a837a5e639c19fe5f9a2c2ff2c47f47917faac0e8ee30ebea7da3386bbfd0501e6f0407a5af4fda01ac386fa74701cfec42d6ddf6cc1d5690f9931411e80b507e8b5aef3cf09fb37bcc4e7b7a71ed82732d0cf77def3376e05b48e5ac150e8732ea09027a37a562ba2f41802ed19fcc789035b1efee228d7c5f1d5aef31faa31643988ee64a8a3a040dfd671c6bca00b9947c84f559e6dfd1ab81b91e52f541839664cc098ebcc08ec8a56437a020274309d3b10d9a7e43a07567af7ea4017576d00bef44b63da10708710530bbd9b3dd7df1ca8e3dbfba266c0bc26d32244a9532ae7c2631dbcb62efde9e634c262ce3c253d2d1b775b75bae9570fda664bd9e33ac107b29309c1c394dff649d2396658dcb4f672b5d5eb030fa14e8fbd827c102c40b2d1e9b94f630f69519ca6d93d8756243d4b79c6694607739ee4f7129475e422b0eb836141fd61101b8a3bae51c8d0f23388bf3b68e96c98fba343bdd6f9322a1b725ed673d34ef3f9ca79682620e30cf4e5f2aca4cd99d35fe7da3d814bf059e715d7b121cc8c09c37ea1c21fd23c55dbfbe579f7ee6fde2f9bb5241ac51f7e9bee41337bceccdf4ad8ebfaa52353698bcf2db3ef097f6c4d687f0fa9006aeb0183e637fe89c788b5e6e6869237ad51feb99fcfc73b4e7a89f69937a4c1cc59fe0873796c0d4b1362b85ef47a69d397449bb39f2aaabb3e1edcf571eb164b3ff770f5056e1ff3ae89975b6c7afe16ebd11d5dd2406e7c1ea0b4c563ab05a3ecbfb076bfb0540e4ec4be96778674adb1585f26de53f8f92e6fb87fba75c3255f04f6e3069ff2e9bfc07ca725a119f6b87eb0f5f624fb824eb3c8303b8013e39717a505866387571dd71808fec545713f24fe5b0fe5ebebd367bc309fa36f568c7b783978f0a479557f54beb4381ccc1eb7d374e3e544391eccb0711b1adc91fe7693bb2d7cb8369831a58e8874f5aafd7c55beaff8c2866feb0078969a98a302cd18d86a9af1c9a4d711cd67704fb381f5817f742c7728e95e2bf6eabad2721ef70f5f36ac5fa2104a36e4d0ab34f27aaf1ba88b0f9e5d7befb03b01db2e48c6c46f67c1a7dacbd9c6c227f180e9d2e6ee7d671bbe11e14b1d641d59750ffab238242d28bc1a9b50cc1decf88857cf16375a3c5b6f5176dc0e5b1c6b3e9983c7987cbae9f1b624b28bc63580cdc46deaf99c79f54d999b7308d4f6033eefed2cf3126f07f02a9154e98ae290a103f5dc2b1af31b2d6b77ab579b507e05e818e04c38750bc06a598726356b2a928e700bc1bc6bcc919ba709bbbc0ab8f67717d95967745d8667b48f1b5ac6eb780fd198cadf2ea3a36765706e6cf668aef18cd9dc836e3f0c1c7c182c782838f890b37f1c1af31c59ec644ca479a498877ad90079a073c0f2c1b28103509949ccf849f140b2637ee8ceb279b5a33dd2cf259cddd6d83482d8ba8783f12a1f2b1797b047d8669c5ff7cc9a10785890d60e88c2e233331872cba5953b1d833ae1eb5e401758568edce9984837708b42e4f32fe323e4bda1a8941f748358a2ddb2abb96bb354aeb9d4f4bfed9fadbd433b7e1e5ad0d6075cb7adc5b177a5607c7c6f6e0a68f72ee6d9d2f5846a17896867fdfb86b6c3ebb99c85533c8c8f2aaca194216f3e7635852adf46710de8565b6fc86246c1e52df62eaedc3be7bbe4988d4e8c62fca35c911576366176c2b1b8610aa076c88dff5a2b65bf6f7c802aac80dc4723ee6f28a86e24cbb2fc37e47eb0b8b7921f90d748c8f54ec05e45500326a77bf530555aefbb40b648b15e319805c4bec368bc5adb0b8617558caf52dfcbe6e632703198b2236308991ded72111dca76bcdac1be872be603c09287ad9edfb7ad82f4dbda91586f5e607670b6fec90ef1ecb7343d6bf58dcbd8f9978d4dc81b8c250bef6eb30799eb113fd5a6bd0a3317f9a3cd7b9670bd87bd0a5adffa2164b5832f363af320db6d9898d8c3f93ebfd8e800ccfdf520751c952176eb76fc2398c659cc665d33c62ac6d1b2ac09dcbc13a70bff4eadebb3668f64f9dc665ef00a744e0b8b4412b8b328a3ba9d8bf6e067f31fff883c618fb32e8dbaf49ea1997f62d999710f0d6785e34e7950bc7ba266dcddca45fedabdd9b5e43c256b93a58d6309cbf09f4d4d0c3b4de65bfbd25678393abde7ba6cc58e0d0f7c06eb73fa819c7f7b980e757bb07fc63ffd77b982bed3c2f296c471ade6b1538bf7091a308c3f489c83e97656b6faa90c13f37e3c61b89ce5de7dc6338181be6769cb76c2ddb8dc1b9eb1afaf315165da23bde36088fdbacc3dcfa0c763ee6e3fb77eea3bdd7f8ba5108723c97b98a62eadab249d5bfe3e4ddc385861c10dbf0f451f7ac8309a0ce7339557d78417060f592ac6dcc59e18cd3ea05d51399428be71ba0749ac15dc98caedd79a6d3f5727b25cbe177e6a315a8c3178cbe1351c9ed6afb0075a2d85a66f844b7ab1263a773f7d36ce7c33af767f3f17fcad72c088a316de72abfb3a6a4c2bf0d997d2cd81db73c31bf1e89bef66961f366f70b3b088b85d67b7a676dc832bbf3a712b41dfa65be4d92f15cbbc4e3761d1cefe522cd375dda48b72964b6999affbe6fa7ac5b2e5f87945bcde8ccbec7009bf4df97020b450b08808f19bff39fadd7c35f54f68a540328bc7e8a2b1c6608e36b669c127e4cd112d58778f56c839405efd1779207dc8bc890eb1131cc7994773c1f34e9ddd53c65e9cd5fdce468f567a3b0cb8f26d3f8c770262e126b2e91738343e601e9ef8fc7c03a5d111faacdecb1be86da3bad4601da70705965edec371f4796add53f83b166a26ef3f230f9fda90d185bcaecab33442769de7d0005c2a6ece6cea53d29eb39f139b902bf3c16cc80a0167c82737e87dbf1bb03157032d6bc1369e45f65e16d8e87430f1c5ea192d0c07b93f752b67bfca151860bfe037635c51e5108d59ab09636456ab46dba5835701ad41e9dfa8f88353c142496c57e1aa714a7889fba3f3b41561135470d9fdb7254536deeef7153eb9ba36acce69cafdfa146091fde5647399c4be5bd50dfbba943f2ddddc61653ffc6d319d8a1ceed7fd043da94581fb71c76bedb18a7340dec0da4a7ab5eabb99ae1f0deb934d8e9acbfcb645eccec8c022d95f335f9c66b3db327ba01c9a663360e2f15417fc1b494ce3273796a3dfbe5b2d41845cdd6dcc72df864fe665735ccd553a09877a34c25fd0cb87fec77245bd1303e3ba4d62fae9f21ef080daa8a9c4b3b049aad45770fbbe3de371f8b359190d1b47fb237042698e3c6cc49a41f8fdf45e0c017a2e8618dbaa69836e6e3fe1f7f0d9dcd29d0fb2e7bfc51fd3d71de96fa03ee6e4a38939ba85aeeca8819bb6d7582eb7fbbc86d7fb9510c1f8b59ae71d1dff547ccc303aba6ec80305a740e85693f87a99f617317951572c1d8f069dfb49301ce7f98cbf19af2f446b3d3a53fdfbf913cadcaf9a6ef9ef8de7ad3ff90ac9a41fd810be8c49a2ecf16bf629a2e56e9052757cf1b94bd1ef6e3f617fcf3796fbdac4877515888738dff4785b4d3011eccf6010f1c01df0b6fb74806cd5043e8a67d76e1dc1cbff64533e630c83aabf5dbe73179b1b4dc46be7cdb8faf14c90548cc3da5393cbbf73dcf583573aee2f73e1e684c8d4e08e5ff6bccf0c67d3077dc5b4b42ae7fbff378dc344fdc8c61e74f213a33d275f2fa39a90fb32909e4753ed07c4ad33675bd0f22c272e56333731ad3b366f11f4866c6cfd67cbab4ec7ad95ebe75c5d819a9e8574e3f0de3e4dab70224a3b61298e18a615c64db9396e5ad9aae71f445de1fa346f9ff46572fdcd074660d13428fb68f7e2f9b3b66e7fa8b9e5f88b49c8fa9a241173d66ea99d9d17d7e78577121acbd8c904b2301c4743607ea223a17b1fc62436cebd6d597716e47e7a31e5ce558f9ce851a17f5a5c9f65086c9f960b7961e97b6eeb382bc8fcb8a97ac75d7caba0dcc516f095beeb547bdc4371584b9a3687d3a7d06d6bc98a722f51717178ac78190e9e39773a6733bfdd16b202f7076d96b90e9f94e808c7e03a1d82363f594779eb0c2f75b87baa7ebd555e6dc625b25980b24418fe917a4126bfbb8523be0692bcd4f6807fc301ff87b72e131919c3704fe934c32e2fc6b771df7e6d0c588eb77ac9857befe3bca4d1a6d2304d809be317df13b4b04e00f1f813b322a936e4c1431fce5197ac0eb6ecc8d9b79e37eafab7a9d6c7f5a2ba61b60e6b5f8dc680524102db5ae772afb134bf2108d753ed556ca41980669bec7caf45191e536088edfadfd5f67682bb5dfdf58e583407b6cf1e8f4ebef8de1e84f7c0b94a73ff5877431443b0da029aca6f6a2ebecd64dc137369ee937bcaef5acaf352ed2db102ffee81e63f414381cb794fb90e2d88d7f3278252ac7d606a968b6aedf49466c4671975b769592ebdcc8783825df5ae41ce3cf61c2851edaebd83732520550e35ef73633e8317a3b000b389893c01d646bd42af514d1f1ad013b7085c035783e30c4b52377e3395e47cdbe730e352f154cebebda3a955fcc5ef46c71a2370cec182b655de5e7acec85ef3f2c6a4bc21658ed42e4072afa4cc95aaf7fafcf5bab186ca851eca66538d4efbd0f708b89507df078017e64fac8edd443c3925b3d4bb890dfcc8d956be2d2396891984be09fb282961a8a6f4b3655ad16246c981ad313d9379fc39cd40c6f5ae2bc5ebf14f3e2b61bf52a7b5bc583ba4459b247171957e3bf56325f71bb35cfde32eebdd9aecc1f3678d1abed35f4ad0d2f37b8a8b3caf908c0df4667d3201b01c7c3eb59b12af4447cf2a946ae2cac92c232390b27766aa8bab7fcd76bfb43eae0eded311f6d914dc39a05a9632b38ffdaf5309c76946e674c6a786038473a992c39153f7a7baea2ecaf3dfab93b2cad20c9848eac31c458e01806c9300a7e65b94fd5624cfef25bb0611753b61cb9caaf9b59fda6b89f53dc2ecc7ecb3fabed06c0b0ec2d82ded997a7ad40ed61fe35503fd4b5b2aaa9421e288795cd779db7851f0c80fe28827eba6128163943aabeca22f2976f8a368ff88f4e0ebe5d57838da1066a0bad7d28893756b7bd52891bcf0dc50546a7510efada9d92a1ae32e32f5a6bf77c70cf26c0dea25b37cd2248b54df8606494962c6bd92f3285b8b3a27b9fed24bdc77fccb27218a0026666418754f32a8c60475b0822fe9b5dc6216d1c0b9919fc4bb5d1f2585dfebf702a6a28556e5d4734e67f5feb334ec411d35dff481fd2c681cff61335d31cf42e52126cfc2f1220994f88009446118a1a28884f6e78465cd084c625f01d79e80e7fd77846b08e1ade8c7497c37cac0bbca0d0a70a749a8058777d0c03c490cd3714462246c83f2a37cce0ce84c5e1ed6efec0d3f496369d71e16ad32ac8fb64da1629a4bba9975131cd65072a38b69c5f0459a6c9b76089ef0faac8adcb8f7e14e006e485f6be87984654bfafde16a2a5cd163711cdba4aa1616a12db8ff226fe5f4f7eed2c6300cf323607c642203886ae42e82644cfa795c0d8b6e18e3a0762ded23ea9cdb0a3e7f5f1aa720cbde48c49fdfa2250c0fdea78285aaabf1f84a3f0377eb313856ac4e40e34cc9b42f48f37659fc38030e9b82d66b30514557e063d0f0a7f14fdd3007e5bf0849827449b44d6721d04610aacacd5be917ee879067bf6482a45bf20a8c3dd6a5bc82c70b32b2c0e1815a01c49f26add5636fcff2a2296f1db4fdca213eeea539c4ad9fd094facdbcf15a83f12ec6d6ba0f619f9a71f78278d194d375ae3232d42491ffcd7f500cf46e01429f54dcb67ab9e275550f3077bd7ecbf7d383faff3d3f017f0d7895d28c46eacd4f2615c8d07ec992d5f3588714220dce7b17401c6c45a3bf9fb298f4bbfd1a6ed5b9dc3c2fb05b349341c4f1564b4e4b6d47efeca562d832fc9c13450e26817e1ca4ec105894ec388843ad3a0802bb28d6e7e305f85cbff422443445b5210133a4cf3d5c026af413f7dd40b4626a7fc61428a29e533867ea785292badab6cca8a9a71e0a9b1ee6688bd7b8c28f82eb1d9ba874d1e0766f3ecd8e078d208983aba0234fcae64213b1806b74cdc0dc7172f2f36b7c3768c42dc0186a943f6802365426ecf55effd1b6f617e21e4f2ddc74cddd928df3a94b039005ec9df0a2f675d89ad9bd076ea9c37c8eb38fd4abf3e2df7711b369257456ed987c30fb9de2ee07121320f8d5ac016b89dde34926d2947a9f0a1d63c59426a86dac6b48ff94f943c93bba47368fda7818bba2798dce256bd65e83ff2ab8d1e7c66294b422c1fad5458f1af3361eaa6aea6ac9ec7c17bf7b5fe8b465fd91f80a36a869d397624baf85559e3d1a1c2b04e47185b2f0d578f110be73a1f97fb16c73c636d28d4e3b2bb5124cac717a8949fad7c785b03117bddf4cc28884eb53c095b7073aef2bc109e3bef552781e045c39412ec60295391fb1dc2c278a1ac719df98e8cff75e6b71246e0b20e144fea1b7075971340336e252b315ddf1f501fb9e67334dfd3fa2656e748ee73d78429a3aeca37d645d33590ae5f1f8bb75eb110f09d0dc1d8c4e461cd16fcf1a3f9595acffbb1dc73247296d853e5c1d58710dd7eb8f878c049c7c3ba72b65939499e0d3b28b40ae8bd5d5752fe094f210a7c1ba205ba9c8c73720a43cdfd35df021f0e4b85df854f2184d58b0f99d4012d080ef9ff90ef15cc0e30b8a666d281b32b9d8bcda206153e89c14d2a919935fa1fe23eea07e6676e9edcfb94d25600b7c7f4b83f358e715cfac2bb37cbc3ebc5f12876ceb90c35c919ed2e6b17ed128c51cf026f84fc5dd8a9a13352078b9b8ef3f1556d7e163d8d573e4f1413d97c2713d443f426d19bb97ac48ca1f40ba53ef9c9ef8c3dff03fb3017a71a33d6af79011c4fc025ebe45cd0e3c60ae26e900c3300cc3300cc3300cc330bb91b1f6eded2bd436249394d30c64e4d85822726a4a29259952129c99f882d399894f6726cebbb791bc3f171f0f220ec70e5c7c244086153a4bd7952bb6422ec869f9b57cca56a9323fc81afa4037d86edcb091ca76e4c8a20b1c17f840387cc7052211195530bca9b20e796e27da944105d39d47b32cd1923105d3b9c98c4ab9726f2a52b0e282e54aeaa695b25ee5b9a13f668afa2f011951305ecc0b5a9573a8d82114cc61a34e52b164b93f51c6134c258d10ed1c4a9adc777c05229128c87082c1fdf5c4cfa3d47897c86882d1fe4d8f559dbc493a06c86082699414d936f1bae0f5e240c6120c6217eee67b4127e90e71900a44221ff6850c25982ec752793b87eb4992fc614930e51016a7242589a1afcbeb810c24986475127ac6cce43456828c239852e953fdd04b212bcb1f07041946307a484fa54aae4aa2a8777c059c20a308c6b00e7a2a49ab25c95c035a70f101228308c68baf13a5c49d92178e4422910f19433055ae5e12dea5df4207b2803e2118f7c7529f34bf3daa02b2c62167213c6c7861e3869e8c201854b4aeac5036104caa25a9a43ec55e77f307664f661f94b07213321b820c1f18ebcc6247fea3dcc9cae88141ef7aeda5cbf249e86c1103193c307c7992443e86695968063276601225fee2a72dbb1565c9d04151db42cb552cb8589866f6dd7eae20d44fab20dbe3866b20128944585d202307c6f0346a2b5decbc1c1c98e434d27277d02a662537306528a16289223e4f87326c609cfbd3f1439728251f3c7e471e1e17326a60faf7a046aba46c937290b51d393e787c17c5d8013268804e4a4c494196709e44c60cea3851b5ae78870c1918b3e6ff762e8a92a35e901103a3bfc7c8db7dfd5e2f12e90f2b26051930d0cd62d959998c978ac86e0537b5d1626f661264bcc0684a2aafd8be27d38427c8914524f221c305a6f750d94a273925bb6e85493d54e4e39a50f12d3158613839f75afa68b2ecd3315661b40f5667effbd9c74210f83055186caeedf394cee14a30c46f64418418a930ac2751cbb5c3fd49a22fbc02914824f2810aa376a536ddfb8a6232b4c37b3860c77fe11270418c53984f12ef5a25f79be183ac7d9c1b7ed872241c10e01192a3026a9c8118a6e84d2c5598318b96c2a5d092c249928e59e570aa0d442267828f482444b5e0e2e38b18a530ddc74b75d3f2f1eac72085b1c225eb60f2885117c7188529feb74aa9ace5b72986284ca6fde3cf35eb649f239150986cf553ac0e6ae34e3ea4070fe711899c101b5e44221f76426c785142703c0972f0500485418df0594f2eb2f42878906b8bc4f8842989aa51313244b63f9e30c71f258fd8be2456c7b44016c4e88441a67830bb4eeb75baa3c663470e1d3a7a84a071c2a8ee226ed28ace5b0c64ed8be4a3049fccbae379d82831366192a1377bc22be76c829a309eea9cb542fe993027115f26cae9dfec8d096387e5acf229dcb3242f81d7768d8c2515fb34f390ff114aeafc49cd124b18ccf2ae465ca83595af84c12e7b68515209036250c220c4824ae14992b6528c3109b3f8767b88f2b3be3c8624ccd94178b725931aa6132312c6cad1a34f783df94b180312a674f274f866cb28e989f10873e9b51242b8398148843fac2f86234c1fa65ff4c54f4a2c1ba31126a1e2692ba894b449d3c56084b124376db2f47de86c5984e9f52a760a192d56528a309a60fab642f782344f8c449852bbc29c85dc8eaa3110613025e992e4c54477862f887108d39b2779e54d46a6e90d6134313dab14c3c491b51086dfbe2455d9c8cfd26210c2a44b876bc5dc2d79840661989199622a95483d590b420c419873dacb3c8f779de44f204c524e427a788ccbd97640183d8a129772bc2df4fd07836c65f3934f9eb369ab0731fc60fa144a9ba8bdf7c1a0dcca3d6fdaff5a920f26619460f249a33bcf647b3048abf35492576af9510fe6cd90573b2bba56bf1879307ab5971cd4c959c9444b0c3c1864bb948bd795ec2b66dec164b631ca747c84f82aed608abb96f10aaf9637ae8349f8f0c1a356ac645a9499743065f1b6d2a6fee754b8ac10630ec613f14f3bd294604a50297230c96759bbc74afa1d2d250e863535b2441b5531662ca5c0c130e75143544b56fa74bec1943e4b0942c6a65aded20de64eff4949da3ce81343a50de67435a5ac434e3fc995c206f3e7bc56875aeb7f5bca1a0c4a09aeb27da35b7f296a309bfc297b38313d8d25d3609411bf1dccbb4c3498839e38a3f2f2372615e30cb8a5ec615ed1ba4e2ca7e68d5a52aac44f26df31cc6038d1828d76fcd821eb48a4ece30b1e5706d5f3e68556eb9c3abf43c733a0dc10830c26314c7b4e6396d3bd925b883106734e415e2a39cec7d329868518623068e792ab3b1e532146180c63f2a854efa4a4a4c46030efe55fcfb984da5bb19e10e30b0639b5fef7417c9435e9185e308cb0f5f1a4d473107eb1630731ba60ac92db83bff567e5919a06388b185c3057dffdc9f30c59bf5fc7175b20058821c6168cad61494e31fda3b5a35a70f1816268c1a0ba3da8242af64b45510e101e3f011b3a14c05e88910583ac67c7349bcb8a9947088e1c5928176260c19421abea930ebb9c5fcd046a26c02b1884e8122b7eff58de9b035630bd5a28b9e35920fb710b885185f73a57d8ec24c7983815b8b470aa96ed65b94eddac82ffbcdfe5243a8c31057366c9c12f27b9b2a58e2fb650420c29982ffd9e94b2bbaafa17a250881105f3c7c9c14f64c7978f2bf0058f0ffb82c7252106144cd182a5a8277c95fdf90493feaed4495059fc24158ce10483a86c22423b4bddc26103e4e3536023a43f8410a309065f4ff9a0b2a4bca004644d111083092655398e1c93c52bed1463098635fb924da758ea672598d25c9e94844a12cc6772a58b6ed9fe4d1a0906d56252f3748e4ab1c63882413bbea73eb92e097741d6d801318c606c4be1924ec252be528b6074b7f19c625ec72f8f08a624478b9fe36d67052dc6100ca7eec144ab9c361e435f818815bb420c21984e4eea77f35baab2280806f5b27d9f049d4f7f120806fd0f2f9635a4c9df3388f1035352f99d45b95c8790fac024d8485f93b561a6537a60924b8cea58955b922008947d6821060f8ca516dbd54dadfa2841f68b1d1588442291ce1d3c74c4d88139880ccd4abac7410c1d989230bf9582b5fdd92907e6b032e3514649f9390e4c59e747c7ca5d2242bf8179e374d014211fd4b581b9925c31abd3995cd2ad8141d7491d6ec467b7aad0c0f89b95b3081362329a81b1d54e8d8fce122586323049dfbf25e7e4cbc3873162604abad449b6a72559ea6160b8f015e7cced64a972e0f888f102f38add6c7d102572a2315c60181d4fa274b050d28d305a6112fdf9136592fd56b4470ea46f021b3cce8f20c78e0a640860b0c224d63ec996f28ece9041d67074612324c43f0e1b165b38e08b1b373c8b48240a3056613a1da12ea782e649eb7402305461d2c973a5a03de89c044bb11d9d0ad3c7e5d0f18392a29a2490b52e1ec76b910318a83065f71416c6f3b8c753d20303a730a5af583079b7a49a38c81a09ee043974a429ccfb29264ab4e24dad377a4c800901a314ca9778769c67bcd66dad25f12153a4c7297f87f72841c819294c5b26e50dd3c6c7078f1d2508b9488447611226defa43ba9b546388739e9e51519863ff5edc31754aec423cc1620b07fc8dc4b7f8105f40f9004628ccc994149dd749da8e90a389f0c061234705426ebc0d2d1e878d9414108920a480480484878d1d1e8984ac85dc7894c38b8fb353c00085f1452e973e25669fca85b8d9274a6e972e98665c685c2c1337cd5a2e08b5ea066078c2246bd5852929f5ff321b27f84810c0e884794466b95bccd8ec09b2c601189c30c8ee5c8c585393837a78f1f1c58ef72247173c70e4f0a2d807606cc214ecfff2936072a5f5206b498f10e4c31ea85b01189a30fbd88cce3987d5af5d471f06229148e4b0472472c36decf890dde137fc84e8875a268c36aa2441981c41d6423e5e8f8d47480e908fab010c4c18633b55921fee6a37be8451457b45bc8ad03aaa25cc1d2689a958a75ffaac84e9c4aee41c96eea53a25cc25daab2bc555d26e7e3c0fff62078f341893308d50e1f3d76225db7d3024617c4b9f54d755b0f92d12e6d7af1da15485d1ce4717386c84f4405348a4d57725b6c91727c47d80f188532c7b79d75d9be6664926557bbee3586c21012cf200c31126afafd80f75a59292fa681ca61166f30ba3cb4b859bec20596c990106238c7b5d4a523f19d2428d44cc3e6c510d301661d2aa94847a9247e73169715c80a1086307291e462eea8d25112625e7cf122d2caccd8b8854adecccbad66cb6e6b304f9fb59b5d3a73541028c4318a458e9e0f73b2608dd10e6913725e8a0af45695b08635a6e05cb516ad98795c3020c4298c4b9bdc5cce5adee0761ae7e93e3cf2caff42708538916fb9c4c287927e602188130ec49fb611efb54aadb5c04188030dae7b95f5075fab488c346173676502012e90fc373028c3f985364c5ade75a76be3f42fc60f84c592db1c43a4945b980d10793290fdb29ea2274ca07acc5e652d4b20a76f3d9a32add89a1ca72ae700fe6180fa2a2d9c90e27d783a9e7e26b8d92f53f878b6700230fe6f2bb3ce982dcffbcf1600a975f9424989c30a5045933c16e21012c4278e0701b3b5c036be8868d1e090360dcc124888d505667f75d4a7630570a9595242975285987518752fa0dfb50c93a118041078352b2bf5527a8f4299dc35eb315eccbd4a2b85867e88d39e9925c5268e5601274d4b59bca57be1b8c38982b4777e8c796a04789f37006602f01061c0c2656692849bc244bd437187e4d75bfe7d4e5a92c7383f1a48f7dba6b3ccf2b3c301089f487e569c1c507009200a30dc6fdad24fdc972e1c2dc03061b9267ecebb369e513ff02b0c8020b05605101096051940730d6d056bd5d6d451195b35953f6268cfcdb5e78aa06d3b59509af60492cd1d360fe4b4a8af19d92272da2c1781ff54ff65b3541c9e9c8b1830191482444478eee3314ceb2655d8a8baf57cdce496e6df8775fee3f0e5ae2c871238b48a4b5e0e2030055806106a3c812fd54eabc72a52d83f1e207b94ab96efdcc0a5ca00212c0028b2d24a0a8051864305d5eb8d7fdf9b97ce2280dc0228b0401141823c0188349a7a0e4fc925b1ecb9800430c66f5d6137e43bf938941d61ee5c081f6288716856184c15c577f427c4e9ece8372f0482b53000c30183f94362d5127ae98db3b14a076707c1152be50bc602ad9f9673516b4ff0764d512ec814524823605185d305f9dd66d4b9d56d302596336c0e082c12d9850bda735b74f0f5800034042fc0120210e927906185b308953b1ae947097441386168ca394abe82fdd9c9381ace1b0e15ef0483676a06207809105e3fe6f75b7e5a0049341d61a06168c273e09a1f3de9a782701e8d94e26cc29e7e748abd395dac18429644b2dbd3a97309738fb0b159fe43359c2304286cc31c943d75f09d35c90ab3627b898204a9867ac9258428b7cfe4cc27c792f66482d69c44712065372cfebac97ca7b240c9ab71e5dd6c4dd1b12c6ea4a6e55767a657e84e1bd724af73fee39eb08e358bc1a7df2567633698461dedae27d323d6a491861ac91af94adb4b28fc9220c96844a27a39412ea4b146110ff7b3a964b1261ea9c1ba7ef940ca58408e3b869cfbb24c54dff1046d335317555d97e6408b3bec9697a52eab250210c3a75672f25e484307f895db2760ae216f741184b12eb5354fd8a9d5d106657b1ac7aa29478b107c220f645875bb81b793a204c268fecaea0746596fe076389e97e7a5292924e713f18fd64dbae9ef176cffb602cfdb5d516733e982df9ad8e79bde8a43d187dc46951ad1f942ae9c17c9ed286c88a5d9ecf83e94a124ab54b3e49fdc683f1ee4b9616696572df7730e80e3fba2aade2446d07f38898a43a8eab099fae8331ff72aaa0ab4bfe93a683f9b264e5ace1ff3949cfc1248fd412223f769293b41ccc3d4a9f5221ba64973b0e0695a525e699ce59b40d07635ff2e8c12d8cd06ebfc16852b2bfca0a2a9fb61b4c95738afe97aa7694dd06c32739ca284f361b4c8225dfbf1ce40525d96b307ff8ca1f439ca5b1b41a0cfa8414e5165d948e9d0683d46b517d25f9bed768307a2925c99ea9bdbef519ccf239b467e724e54f3283b1e47d8e7faadf4a5f06b3e5fcd20c7d3aee93c1e81fda7b545610c21f83b97c2bbe28ffba14c46230ecb77cee51ead3e870180c32f2c97efe538687c160ea2d0b6da5cc92dce12f184d596cc66aec65097bc1143dc78f16cc6d33e72e984de76a11fabe5e3773c1741d26fe7249b97298b7600ad1eefb5a590b069343c88e39aef12a67c198a54f0c2d23848b92b160b2bcf4e4d1c584a9f0150c16768468974fed0b5bc19c2ae511f28452751e57c1ecad712d1fd7332ba682f1a44a9278b27fd4f53b0563c9597e45850d9573560ac6ce9f4d3cc97737cf46c19c2d7dea90256f4f928582499b8eab4fc9e4937c9f60529674b2b4db09064f2a69934c12ecb26e824992b9b21fff7d316682d12e9808759e25982441a9ebf8ed499da4124cd72e3a85d0fd279c49308f65f75be5143dcd4582314474cc513796abdc239882eafab45bb7a6746b0493ca7ed05bd626686f8b604eaa4a4c8a886072f9e8968329139e21985d4e10cf7f72dc122118c63e78a834ea84d982604ae2a50d5d5143476b8160ccf68ba6fe6eda493f30f898a0ecc2095d4a1f98e4ce499f60954f6e490f0ce3a792f60d65261e982c76cabcc87760924b92f4bfebc0244939caa8784265e9393066c97d49723cd929c78141ada49473aed45ef50dccc14288f818fa8312b681b92d8b4ed27c3cc1dd1a98f366842af7a89a2e0d8c15cc563ceb540aeb0ccca604594aca0e2596cac0b8f6fd96b4190373b6c921bd5f44c91718988349bea7bb5301bcc02494bc2394202b690d55002e30d8b597b857a9509ab6c26cf57db9d2efd35cca0aa35a5e972ead3cfbafc23817eda3ea49fb5f1526aba44599a05498e499101394945a57840ae39e38a32dec53baa05318bd43946ceaf226874c618a771a3a4aa7140669696c3fbd786e13529892603a875d1276497c1426750ba2d4299599bb284c32a27fa2dfb54aa1308768b6bd8505956e5018c7772cfd443fb1f4278c97a6f3c7a531796b4f18f32bc72cb1d776e74e18ac5b749a8e9d7c644e9884765f397dfbe3f1268c9676e45bb485725513a6caa104592f1354273361ca49ceaebc333161aa70b397eae9d5ccbc84495a4a6daa4efe4d9896308d9e0ae13b66254ceaa6d2a3aaf9df989430fd2549b2d05651f1e5244ce23bc9b564399dda5212461fa144c78f09775a46c270652589d793acdb4a4898c4156d4abd3ddb941e610eee6bead34a2b4f8e30a7124cf6984b9a6a6a8441eda73ce204d71e3d238c225a62728a4e117f11e6bcfb79f2f57fc9bd224c9278f97554be924b5022cc7f7955591ffe418408a38b8879b4136f54e810e6aad87039cb10060f6a7237d2bb4c924218648917b2fe3bc49e10c2a4e2b42949d2b9f24b0661927474d648b3a8ba0bc2a0a604cd912144aa1e0873e752d669fd04fb1c10e6b86aa9f3df4f12ef3f184d52e2414962c67cec07c347d176727a5d15ed83794e4cb6a4bb24c9493e182b558a6d41ab5c9a7b3048135e456a59f460909afa6effa54a8c250f26655792946ff184f90a1e0ca2b4749235bb47c5ca1d4c15cdbc044b955450153b18672f894b62af9760953a98947862e7a0273a984c7536b1552be3259c83c1825acad35b95043dcac1d4aba29dda5bf33dc6c19cd54e5b3f289f17211c0cbaa372b646a8a708df605032469b777ea677d00de6d7b1ea9383ae511d6c8351adca56ab529a2941361845e91ce425e9f3a104d7603093a4116a95aac704d560ea3d11e725edcfb49806f39b923dba49e96265110dc66e5bebdc3d29c5c53318ac52fa773f3f9d269ac1fcb7e69e716e7299580653ca4949a7ed178430910ce67415b52e8961b254700c066549ce509ec39f4941319845094ac57db0d41fc43098b4c96f8259147b11110cc6ccd310bf9ed6f6e1174c62de6ec3522a9d2cf482393d53c44413cd4aa90be67839be9a18af9a262e185ff6ec82b89ad6d2168cf1b9ef521269f7792d182b76f67f12d2bdfecb8251434d27513c4919a2c3824950f7f4ebd015cceba6f77efe46a89015cc974c4e3a4ecaa9ed5305c389142543eb93247ca8604ae1a345f9dcd53d9982499b78ca4cd44ee24ca46012fd4acdc9680fd3a36096b3d40f63fa25e55030ff9baaedeff0eff3130cea2e8b25f92cc3b34e30dcfebe456fcff36e13ccf2c14c5c13258d891e134c66df3143855c8feb2dc1a8d6574149a5c410ca53826147a9f43b295698ca25c154b1fe64eb4ba3267748305a6a93d3254165bbef8e604a7641bc7f581bf3ce088613ac4b7e72d519d515c1a44cd055827e5f78eb88600c2fb56c72ac1d791a82d16d546ce71196624908e67825da757f104cf2faf695bc4e933d100caa3a843cb94488e9fcc0144a4cef8e5d3af7f181d154766eb196f1ece98139d44e3c7a555532e181a944e372124ebe30fa0ecc9e83fa9cd35ab07cebc0acee27ef514be3ec00e4c0189674693dcf9fad0e000e8ceea57450232b8fce01b8816184c9516485505e73006c60fc60d96ff9b6d5b205a00626a5bae2df6fd69aac058006a60b7fe93c2b4bc5d702300393dc9d54d494588ed7022003b39e206fa163652fa9052006e69196c4c7b1a067570b000c4ca14ab345adfa62b500bcc02c9faddcc7a4b9162d002e30e90a712db9b379a1b5c2e497fe4e7492fcdd33569832ec3f889a55f4cb5661d09304cfa75f840a97a9c214d4e2e598969489a654183fcdea8230e129965061cea5a298db0825687f0a83cea74dfe1c599b23539857849e078b9f7246a530c8aed0d62a4b771f5218bd56a489f7a12e7a466154d327c998ab38da118549fc90b158e9e1a724148693843ba9cc0414e6d05f9793a5de31f5270cb6a12495a42c62b2f6844184ce17b5c2592a71274c17beaa62bb75729913069324b16c82c9e183bb09c37c4a4f7b2a437daa0953b9a82fcf177b2fcd84f94fd8df9cca9a254c98b3b7ed43b5764c2e6112bc043b1d6982492d610a66a529a3c4bb302b618c9363e6f6a484394dd8315dc242fb6512a6944ff623444918536d43a5ed64c9140973fe50525fb0fd2f3948188429b72c71d2499f4718e63b3ca51031ef3ac2dcb1947cf33c2267234c49c9592f96dcb28511c612849ed1257df6982dc2ec26caa72ca9528431de44356f556f174f4622cc2294929418a37310910c4498f44a12dbe328c91dc838843959e5d25b2a95126b640853c971d3928e5942b4955108d3c929c99e3b54b6d54408ece41c9d3fef9d1a8441c7688de8a45b53158461c4bfe9ac7549ef2b10a6ecdc93c4299d2d660c886d3dc6bb2cda6abc567cc99f7425e1f3ff97b44dc61f4c4ad52ceefd8b4a75d285f3c0407b715020c30f4693f2ff5bb68f8ba1c2620b07f008791e2e8195d107733255a32bc71ff1a606598b442291d6828b0f16c8e083494db074c289daadf2eec17492f0e4299e976441a607e3a8e7ce6f29d424b9e7c1602a551293d4fdb43f3c18c382fa850eb5965606594369cc1f27e30e2653e29892920ea5731f1212d253906107738cbcf7f9653b93269975309f18791f9477be924e24d891e3043c5e8b4c3a9883975ecea63a5665e1b1c32bd032e66052d29f3a4949f2cdd28c442211344124d21f236034c1591364c8c114d23c4cb6e742ce250e2631ac4efdd4cf699cc8808329d64ef7fb689e8afd87f89f8c3798aca2e5ad985ba754e406d377b949af2e5ad72c6d30c7a5def4ef94a48d4e24128968d1810c3698c4942497fc57423e08adc1240932d34e8d0aaf73cb5083a924b13ad6773fde0865a4c120f4a6aafd27b14a6d28781e3dbcf860fb1edfa304755a70f141011968308cf6fc2de3f12c372f8d0419673075c67687a94ea2e9bb0bbfd180fc810c33986266494948ef0eea4e70e408f1bf402412896448f2a123cb60782f314c5c2739f8bf64305f4c4f59c6e33b451c03b6952bc62ccdae5a2ccbf6989b98bbfb55ab188c2649727be479ccd1fe8b27c1ef28d7058e0b5c18cc2b57d29b0e26b99ae8bbc0f1376e38c849038329880a359363e77cdd01592bd600195f30e9d0a92b8ece6999d816653c74d8d871ca0a20c30ba6cd75d6a86775aac8052da382ec52d53f70d8e8e213e6330cf4273976f00851d5828b0f0bc8e882515f4d9fa9932184be29830ba6cb2666553ce1c42949646cc114e263ff28d1444c96bbc87183510b2e3e5086162e29a3041179137b6fa43c32b28074bfd8be666d72bac871035d06164c8f55b1145ad75637f2496ebdf42539449b43025d749143023a726811897491e3868c2b5869495c3cc4e4c25b8a3d536a1572b2d6d3206b3a3e98047ece9340b508ada531cf6d7d2755045375ca5013edbb0312e220667f209086011a443049ba43e58aea9c04250fc1a8aa55a5dccbbcd204595b21984e959b92c7e358183d24c446a2e72d6804c1d429a8f18ebe72222e3c70d8f00208264194eec5b9764f3941d670ece891b40668fcc06ce2892e392721f45ff44662638740c307e6fcbf646bd2fc746d206b1f38fc1c8ff33a427040a060f880460f4c97aa53fc78d1b3d32985e0f01d3cf879388f1e13d00cd0e08169d654326131bcd5723be04a1c932eb7f1e3321a165b3820128944eee3860e2f6ca0c1d8020d1d98574e4e1276a22d8e36075512b4ed874c8bab211c1928dbe1377a74f13bf66f2456010d1c1874f4147e7d96a3e5506781c60d4c9ebd445323f74f2a3130d0b08129d785d12e61974d379035ff30c1a38f37818d1d8fd89a03346a603c5ff94c9393ba94340dcc7d95fef336f354bb39508f10cb817a845c81c60c8cbb151eff47c7923f6c041a32581a313079e527ad1f22cf3b06b216c20387df0668c0c0346a9e83ae2da57342206b6aa9058d17984c6bdfc677ca2f21a6e102c39afd7d09fab7c1c3c65db6c2bc57a29d68971db9d500335861ea28faa4bf99f06422c81ac2612307088f1e0a40386c342012e9c28c55984d77123d7dab9b575615a61444051325d949e924322315e86823553eb8b74551e1a7f4c15b2c2fcc3885169e5454cc67e5a4067c64618629ec8e71b752f5b65ab5122bf19d37b43a099d97c21c7a6e623dae206b5e7cdcb8e11f49df0d05fc8e1ce9cd4861d26da6c4bc94fad2c94761923d744951fe2317735118fbb45d3ef59ad313b3c18c50183be712bc63c7e78f6fe16006280cb24428e94cf684d7ab0a333e61922f5d9e553f2170e3860b618627cc9e53cde53863f3fb75c2f83fe1e1a2aa050b333861d0a14bc92ad2c4c346880d1334165b3820070f1b213634f0f1118944222812f968cbc48c4d98e746df75102377b4af098368ee28151744c9c52b13a6b82294b0fbfc364b61c21c66bd4dca3627eb75f409332e614a59430869b27979de40d67e878e332cb67000166f09a399aafad1973ab71f78218a021389306146254cc153301da96bb29b30165b3820120921418e1c204609e3f9b989a36343295d4fc274b127679bd3d9b6ad240caa63c2c573b664ad72146644c2204e5525a1c5540e7a156106240ce2e3589af07ce18fe4e30e3d8e0c4c61c6238c6a26fd79f8d527153c24cc70842965f3ce57e5a4b49501614623cc39d4ac5a3e4bd2e9068a198c4058cb95e958a95a6288198b3076e8daf9b6a07ba305592b27f00bdc69c1c5070c17cc5084b962bfe7303154bdf638bab011a23998910883fc75db97502aa5f4951bcc4084495062c65aa4e8102625de5cabe3de095541d676fc8d1b7e3e4078d838810ddfb145243284198630f6259da54b1e7562e80b613a758b93bbb6adc604593325cc2084d92449fc8bcf36cb8d3b09330661d2bf5675aa61b18503426ee8f002021f3a42f4c8304310860df1c9745899a4c3fde0f15dbc0322911fcc0884299e1c437457ce31610159b3330d446ce8f0d4c20c4018ccec844fa596456715f82e763848167f70e4c031814824128984e0c861232412c9228b2cb0380af80247173630108960b18503b0d0c004221193814824b2e3913f4824726ca80676e4481f37cea3c8878e903ab5101b3abc10e00933fe60722bd59e4df6d4990e64cd2e0633fcf0c83039bd9c24447f206b3b6c7c986d32a30f6693622549593ee1f2fe203b830fc6feb2134a4f7ef4acf760764fb962566eeaa61e4c6269c993feba97a87930658f4ac2aac7b77d050f06b5579252f2e42c5abc8339d9c5ad91b5f5243b3b184c9dc92f27b4bf620359cb0de0e89143871739b298510773c9b2d8d6e7274b5db0a880040e0796bf98410783a90ca13c87ca2bcf41d642787c4888025090630191087a1494bd31630ef696aca52cfe206bba83870e14cc908326899d96e797e3aac32780814864c7876c8f1971284942289372c3c377120ec6ca1b61499267e2579004229148314124a2c9c58c3794d3bf43ad53504998c70c37fc7174c939b6e7d16302381a901798d106e38709a53dfc09f9a8b3c17015ee2bd726aa84c91a0c42eb04254c899d9c257a13893c8a441eedf890196ac0b2b668dca54adabea96fd2c712f3b70ecc488341f565afe479df9b0f1a4ca2e4c9ce7326c8757985196730ec456bb51cbd198c57d1dd2cc89b1c2c5706a3899e3d39d5f9f4c9608cf10d532d1d547efbc60dcf820a33b0d38b962cb784c460daebd8dd26e55ded78188ca52b27b94bb46030a62569f4577d3eecf205d3e69da4539277d6c2d2d5828b8f1d33bc6050ead77f65a2f8096aef076674c1ac7275764a5062d7aa8f195c30cda518edd1a1d4e9bc05c3af85c7fca084afa06ac1546926e8a0d232dbc41959308c9ae7b1f4dcce0a8f2f121e3d74e080402a6006164c16b76dc783e960e2e70a4651d7cb3997a9d7bb5630cf9f384a97a073d929ab603c93c684978965951c154cdb9e936c0ba7640faa19533088c929c6f4da981c2f29987594ac52334a5130058d3ff9454e758e13148c1dfb63bdce8f7a2c3dc16c2a8512cf9592af959c60baf4f9a0595513cca264ad5b959fca9e678251b343466a55f09cfc2598e2880893a49d9d6c7925186b47964515f9f9e327c1bcde26fd5e5ff0ac232498b3251d425ef797b8d1114c95e452f2749468bbc808e670315dd9df945f1615c15c2a493ad4e657720b2282e1cc4a3259a2996882ce104c6a7fb25b4afbad258910ccdfc173b69f83607a5172fecf259e7a4a020483eb869025ba7e899f1f18b3f3cc4baca865f281b94caa7d92391f75d203734ebfcd5262cad6e681b1243935a366926bbb03d3099df13a4154bc3a30df49de25c9d0a6a439309c641ec7ea54682f716032a1c53ac9b689aeddc0643ad77b423ccaa9dc0626b9733bb76a5f92700dcc1e4e454f42769a924203b3a553b5395b1f4cd733306dc50a5ec2c408ddcbc07c79e2e5b99a753906068b2579891fa2927860600a7e1e3c7f9424a8fc052649d0f28b6282255dcf708149466913d372e1b6c26495ecf2789c8faaac30882f354a1c934bcc5598a43273f7fc68fa52f6da326d23eb712a8c652a47d4bf56c76951611265c4bd9a243b8bc9294c4a9d070f42e7e8c93485b1933cd9a7925a0ad396f4f855f92485f1b4ea2f96c7bae7e4284c42b87ad0f1ef49a78b28ccd1f3dcccb2563d5c42612c556b49b494ba232da030a74a7b921e9de449c2f209935c96922c515c3b7cc51346917fa11fb54fc95ee984e1242b25cf2525446a8513a6646e62ac25996359d984b9f55487bd34ea721e3561fe8fa7da470966c2e0fda361a1443969829830fc6891d79973673b5ec21cb7c33a57958996444b18d634e5f474b0a0f3570983b68ab7f76f2be93e4a98a4fbcbe79d249970df244c5dd9a293f0e93cd49384d9f4f388086524ccf7299849f22861b51d240c3ae9ff27130fdfce230c3aadfb8892fa724c1c617efbd81dc3c3336d84d194143fe7fb52395c46982fc99ab1f254954d5b8449ceab272ab496be244598e4bd8d8a6ba3d2f289d893f0ce7f524e5d810873d4eaa8a25feaa43d1dc2249650c94c76106ff30d61303163f551941f66afe38b2d8a0160105714c2e4f2973c7b870a573b21cc1584964a2dcae24a4c1371c520ccc93cfc863899d61e0bc27c29ad83facaa74f12af088469457a1a55324cdc7c00612cc9d4292f49be49aae40fe6eaed2ce2ac74588e1f8c96c42c25acee7ac9bd0fa61f0b359f9fe6c493f96050499027c57d15937bf760be9cc5d2992c392a8c7a30075992c786eaf19c7479307fae52573d65e22c75051e8c722667cfbbfa1d4c5a4dac98ef4988fb0b08aeb08341a8f94b428c492728a983314bfd34fd83a7b9a9e90a3a1854f758d928b519e2640e663f9ddbd2d43d545b57c8c19ce65143776757ad7c1c0c37a2d3a7fed15272e0601cb592a627bbdf93fc06c3edac8eeefa6e309658d5ef9e1f7d63b7c194b2d54982acb0c1b426899dc22b286f4fafc16c2fb2a6543ea52587d46032bd8ebdb41a7249950693a44f8d7f978eddd74183292c7458f82cdabb04cf60127b5fafe63a873dcd60188bab5322adc485b70c26b96c77d6c449a2b42683793cc79c09e1b3ffd6184c8292f949bc7c9285c56230093ae7a4f4a3dcb7a361308adb688f6242bf0c067327b538b1d664bd8dbe603c95d6b62f84f6d0bd6012ed6269c99e704f72174c49122ba91b9d5fd45e2e185cd43b9727959474a32d98a475e91195236c4f540b067182569fbb8dcdc9b260f6d2d43f69e564c48905f357f6ca12bfaa42fc2bae60d8918b27b6886edbcb155630d668d5e6fece983e5d5105a3ddc750292f76c4285d41059324ee525227e9906da72ba660be5f915963ab2ee2748514cc15aec3c357e8ee9cae8882b18277c7d7473d49345d010593da1961a5827cb1325df104c39e8a5ede79841a957485130ca6b26ca57c41a568495734c1384a4ee79fd40963957405134cf2569a9650f91ebf74c5128c2a3a42a7281e75bc7485124c2a4bca2e79423cbc7445124c566264a4c51212cc29eba5e43693c37bfe0806dded6e82922b4cc7df08a6e09ec6929c3bb6c75f04c3488fb9f2f213b67d22984e52e22eaf2749d0d11f822589a526682a0473c593f583ca93e3413056556fa9307362974030fa5de519257498fe035352f23b74764e26161f18b6dec77ecd3234ec81d9db3c6b09aa4d2aef7960f0faf02d6f255b921d182e49514ab7b2cd9674608a9d264d29395fbc7360d0fe5993d7459ef871601072595249daf235ba81f1e4765f2831714a3630bcd7a59c743cf1646b608e2bba42dd07212a0d4c9e24252d444364766660122c3d259997e5a4b80cccd1754f67fb60fa1303c3be29d34963f7761898ae2eab9a2484ae788129456bf9bfd2ef29055de10283559e515393aa4d0eb5c224afe81cf79465ee87586138d35a1db95c95155a8531847853ba4d5a104aaa3049258fbcaf3b75a14f85412d65d126c7eb1c7d54185467087d39c9274ee714c679379b4b95f3e48e294ce3a342083129852985f14e91ef255d7e4861aa1d6d5ab29bb04fcf284c63b196843961649e4714a6927c6de5839cc679426134492c8f212f0579c9030aa3079573f410a2a197f309b3996ca73fa7389684c713e6d97e4fe1ba923cdde9846984a8d7bbd026d97738611e25e7c39589255cee6cc29c53c7bc374b166a3b9a30fa2571ec3c8e0178309e7b0a5fd96d722abf83e9ce749baea974a3543b98c4f7fd11a2b43a182d8fc9d7e1dea44e2a90357482d7f1c5163b5e870edfb18148e4e0f8220603a083a9e2b59d9b7ef45cd25b0c600ea6fda4a31c3cca06062007a349254f5ecac1b294201c363a0b121ce781811d0cd0fbc000e2605aff7b932daecae80315b3b7b1838701060007939cd25f32a9b5dfe4bdaa05171f2218c01b4cb253e4ee8d921b8c27b796a0f4e5596ba90dc6f6f4e94989d69794870de657b79416c4e2ee9eacc1e4a972ed2971abb5133598c32531a4cc577f7a93349873cea19ee97134184fd2b57b498ae71eef3398ea57d3bec48608bbcdd0dcb7ee65efcb50e5a6aa7dbcc992c1a4af779df3eae2e4760ca6d31f94bc68919b6b8ac19c4fce29b1a2439d5bc260fa601b4a3eb728295bc060361547ff5b89aed0f10b06cba646749a98242e7ac1fc16e42b8feccf7d5e170cb2a4a792c7afd4f45c30fb9e7482f0532a9df4160c27d4c5e4badcc9b25a308bcc654f79cde43c2d0ba69c42c5fd75d85cd3b060529f7dfda9a3c89eae602a39cc2595b38249bc247d0921f4eb8eaa6030c9e4123a9745ec1b00159242c996c38dccc8b1073005931c3b8a92933aade86c0052308b79e8cb4853314107d98f1d3c7494690e200ac693df72a69c129f4f080a862d9d53bc9221a48a4f30a7a024a9cdc7b35d2e7182c9f3fdabc91e3627f526182bbd2761214a9560612618e743783af9e49952b204837cae12549e2c9682aa04e3cfbaa69b7ffccb4930bb082d494a8d135e2b4830981c9683d24e825986473085395997c353fe5a33823929c9c40fa6670706500493ae55dfcaf324f9b84430d92779d9c372522649def0df6103d9487a02031882d15fde844a794cafc785600a52f4a7cadabb2d77104cddf72f100c1f5bfab3c9f10fccb615cd2d9514fbc0207b4aec2c7bd318931e98acd4d996380b964f9c07a69c5ae63e940525976469c00076600a953f7546f76e7b5a07e6e426e7f34787b6d202069003f3725c478fee1384120e922fca2cf6ffc916f71b68f97d4d2cbfcea5ab0d9695ad70295a83d3fcb49b7cbdf0a7a441fe29e5fc493350ccc45b3d399544adc8a00e7a2f4cde5ad4c518e429e6ece80b16d57330409c20bec7541e1db61730fac492f32b1b800bce6beabf1dcdaf82aa462b4ca1948d10a6bb7f920a2bcc266aa989ff255e52626829851aab3089a795adee6f257b09b28a871aaa3065ed9476fa63e3662f5da8910a835959e714de1f4eb041b6062a0ca3931464efb2b97dfe14a62eab9c2d9e09b71c99c2fcb7a162cc8d55e89352184f64b88e506369c38414a692ad135354c9cb568fc2dc27a5966acca230ffd95dd231bcf35a361426d99dec4f8cd9fa4a4161720b5a9e825754cb3f61b460821efbd913a670cae7dfeeff82969d30253dfa541279e184e932ecb54ac74d182cd79d3822ba82de4e1386536692f21b95de7d264c3a2b85d365ff9ee29830a8b7d793d3eaff49a91a9730b949ba97f3fe896cfd3b12115d50c31226e1972d962fa876fed40b352a61fe345274ea5f0b9e354a18e59356f667953a49576312c630fd58d2943cc89a22a08624ccd14ed2f1fe2f94e53b12267d61413ec70f97821212e68ff6497a4c5dd3336b3cc2a484ab74c184cad511a6e8b717f6fc4ad2ab7decf1e841810758266a34c2b02644fb8593e4beb618618eea399eea8fddb8d1232d6025506311a65d53134384f9052538fe83870d2f6cdcc853e38182474598527747ed7abb2d8f206b21396e1cbdbc15d44884e13d9e0e5d364aea199506b0d8420258e091a006224c829db4b2fd2fbeb6cda1c621cc31bf6b3ccfc5b41619c2dcf9e929cfbb9df0fa414e40012eeb3500c0a14621cc96437dc910a17da7a42750831026fb93a48a56eda7ad83ac7dc846a0c6200c4a093b6529bae8801a8230b89bf0df66daf37e88193453231026937b625216f1964e123bd4008441e445932b895126878e8e1e24c891e3465d8d3f184ecc180bbb59d1e1b5206af8c1b4257a15fe621d4f18e4781616881a7d30dedac829f95879bc54830fe65072b52b879a2433d6d883294d6de9243cc9fdf1c3c3bdb0c123e4edb0d8c20129b081812f6ee0a0c0a7c00606229188bda1861e0c7e4af6606ef2a78a150b35f2606a95efac7a2394e8c5ef156ae0c154b69e74d07bb1b27730bde853424def206b07a3e775ea9a3d7d17377c87c129d4a803d66319aa61419c20c7cfefc04b420d3ae0a1d74cdd9dc8b9581f6accc1601f4609a204255f12a72c6ac8c1e09ea428a56bbd03438d38984ad44f6a7cdb4f925370309cace141a469937e4d653c6abcc15cb226a9ffd0d1c412e6420d3718f49ee893748969fa758d3618c5242d6247cd897fef701dbf43c79ec02fc0f735d860b8513a855c25a9b1066309b2d4e7eaa5517602b27602dfc18d756a6a30e78cb338965f55eb83ac7df124c0a15aa89106d37f7a6eef701f94a03cb3420d3498db838c2f7d4b4a89a63398e2838a317a623f9b6e06e3f675f41d2db71cc232982e5405f529cf097fdd106a90c11cbea24e65fb3198cc726d9bded291fdd61083e994966e9fa4af1106d39732fda9ca573c4c3e0c7960c07ce80859000f14984884070a3e0a35c060929fbdbdf4f45f30a724b7d7dee9d710a7a2831a5e3055f0517b3b2509222219d4e88271f4a5f09f44f6a81971c120fab9c4dcdc4d7cb660d23d717d964abeac1ad4d082f1d74b5272aea4673fb360925f279d3cb1647e8405e38f74f35072a87555ae90a0996fa2a6e570ca628542d52ba96da95a2cfd2eea74ecff9c474915ccb194a9eaec9ebb2152c19c82f8afb8b5eff9bbbb418d299837bc4f750a7532239e410d2998db4d6fb7762e1289446250230a261d534328d9c5ac24512898c4b59dd97aca134c25f32b3e3b6343764e30e8582cd124534af8186a82e1945cea574fd892756182b1cf2c87c5d6e718d9124ca6f24c9452fa3fd2aaa104a3799eb892d2458f9b83ac9de0717ce0c85114900516f53774f4d8028b0a48008b16d44882499e0f6a3d5244c81382ac31124cd795eac4aaa56708877fd8f01da828e00bdfb143016be74dc0e74df0050e1b19a87104f38e9f20fa6faae55f24f21189740d2318ccc6e22e986f984741d67ad428827157548e3162fd24410e0edf91f0c891638717fe3b7860c0c60e1e65b5e0e2a304358860dc3293967fc264af538d21dcc155ab3cfc9f77821a42308d9fa51925e542502308a60bdaa45152accef7180435807056fde8ea61bf42fa03e3e91c6f2e9b5c25c831b7860fccfde757972a1e4b097781e36f7ce0b051b4460f923c8998360159f322078f901e47796090697b4a9c738bd1d3861759630726a8a10314d4c841e2257172a92555921489708624a905171f386ae0c0f06271728c35b1e54b3730deafcc7ea8127434d9c0641e773be324217daf81399630fe5942a99196a78141c687fc34dea924399a81e173ca6fdff1a28b922303b35c10a1aa46f64a6260ba3f494f4c47eb9361608af196725b496abcc098ad7aa28610f91f26355c60923fa9d6959b534bd2ad305dd75bf2dd1ab561b3c22047bda5945c4494205e85299a944ab8eb93e4dcab0a3c884e1ae2f5a6c2a0e3e85cdaa4b0258a1415e693ef84a7d4a1b39ca730c9d6b1d69ac294fb63a477054b61d03967c73ed391c220b4efeaca8be89f8cc628163d716ba22ffc9c70a028ec9c6dd4ccd3683d29a3110a838f36a1049344d597e80bdf408e136c516880c29c723ee1273efca76e41d62291cc0ed0f8c441ade492d25d6878e20a5e29fae7126e1ea5d109937cfae4e4943aa8ee39618a1d4d48fbdca94a841ad0c30b2c1a70012c22914824a46f40631305f15d2e7efe7b02011a9a30690d35e35da983484ba1910983b90925dada02593333770fa0810993b07a2f1e4bb0ddde8cc6250cffe7be629f2c942c59c220aa2b8e6f27952ef44a18e54d7dacf867a2589412061fb37092759de8dd933097748d592d4998285149984c924c50d70e2361504a3a29623e56d2ca6840c21426fab9ee1574f546e31106d525e85d0c5d72b5cf7fa0e108e3d567e7dc5797f4ed206b39d0122f7a6801011c14e037d06884f1549c149ef56584514db76c9a2433f662376e2cc2a4db94649a7bab97ba8ec339d0508441789630fac4da53070190061a893005a54d7c470e47c1231c10f022c78e06b4266820c294a2c8c5d67795d57f085349a652bd5d5798b91040c3108b8aa5738d95345a3ebad4828b8fa4510883a932514f7cac888a0961f0123d8e8ebc3ee19f92041a8330e99b8ef9a9a6210883cab1db4c3b3310e6681aea213c9cac5303c270497dfd49ebfc496e56a0f10783b8ca75ca47c70fe67295d9de0fd73957fb608a5e23f2eb7a2b8fa57c30daedec5d5e48b711efc168ef5e1dbfce29d0d083e1643561b2685a9bac3f0e138d3c98d32b439b7884f4401f5de0b0f19cd0c083f9734ff95c8ed9cf251f34ee60120b4f7d4189a1f4d5206b20213d743403f0fac34e0b2e3e3c40c30ea6a04ced57b8b351e2ebc2b738366e243d2a808148e4038b2d2480450e1e3abed891452412899cedefc871b640a30ec613ae72906172efc303058fa3580b2e3e7ad0a08349659c2407fda2d7953307930ca55faba411c85a8f8681041a7230c9148ba36bba8318330e26b15116e43bf6ed595c2012f91b899b60c787dc1707c7efd8d1e3c030020d3898ae2c54ac6879b577de1868bcc11c2eea97927962a852ea020d3718547949954af03bb194da60dcaf58a6be67835184f8b07adee1e4d01a8cfa26ac82923f772e490da6a4e2ff4972be37aa9406b3e6bfe5a4e64dba281a8c599fb39f4a2ea9df194c7732fc733829f97d6f0673502a76ceeb3d95d497c1a0cfcfedb285ccad10194c1da69f93cd08f79c3306737b9d758e7372257ec4600e3a292f415fc58ba23098db64cec4798a6d2660308bf8a092983af982d96eb7d3a991d52a265e30a8dd8a13cceb82b924adeca445875356c205934af2759c3429a272df8231d66f5e3de88cf95a30c93a27665aba743dea2c9844634b9acf3da524c182c1ee5f2cdc4cfe0aaf60929454a7af276a96c556308b89a7ddd3f572f65b059360a5bc3d27712e4b940a06e5a18450c976773f5330da6b87edfcea55594ac15c523a799475902de228183b5f52ae1d7fc9c2020583fc10fd2827a7cdf40453d01ef504e92658387182493ef1b3e3e7adb866134c2ab5859b240775bb21134cf254f69639a55ccf5b82392529d5eec7fe0f91124c6aa4ea9664e925894930895592872b4bbdcb0c09348e40c30846938d75a834ada543a240240c0603a15018100a276ebe02a31408002034208c06c32145d1b3a103140004404634502c2016241812101618120943a14018180a87c1804018100886c280c04070092b4c1d000a836cc8960acd7846621c8f5480dfb095a6f26690104c1ee29e83cd9a24d0cfc0e854421cb20099290935dc4093ae3c04fb733bd1a9f52e01cbb5b692235619f2e5e58748f5b0d7361725b7bc0e4690269ce133d882f2437c5844b82768038a6f0392dc7a43ae7d63babe5887953f5c3a30e2a0b4ca5d9a328b0f4a55a3e3bd5a81cb38d3011b0deb40d0fb5d227a39914406a211832cce7c29ca88f6526446891e29d53d095b892aa96c6898884e43cde53a2ee6d0bb2949ecfbffd65ce95be4fad9078fe4eabb513dbba111df7d700467d799c8fbbe37cfd43e3b01e0685814b46996a47cc849d8c9ce1f4a9470642ef49bf8fefcb53032a22a30e67a2369f2a6f1b6fbb3af8ae88e8c7a32a5ebef498a309027e3ad9fb17fa4ba2d890f3f8a325971fdb0ba5e357a8f6aa9eba383d917f78d5846c9b8f5c1370a0c4d0f1d053258aefbf951ba08b41fb3915d0c1d29c3fe986d2b40dc1ad561595ec00d8c89fa2b0a34ea7ca4a980fec60d6ec7d16e878ff968b8442ee4c0c4fa2f9f012723c7c21c7eb83af8d440a804bd8dc93ad963451a88251fc224ab74bf216020766440969b7990bdba9c8c059ef9c0211ad6b78654cd74e580079e2e14a56b9b0efb027ddf401f57d466fc8acf21ff75e5c52c9d59c9ef79a7e1a664c2f46a3a0e27a729a0fc1d37ec4dc679758ba506d84aa2639c5231b48f01b0d9d9f012230b67acfe6a54083ee0db64639849861587ba50f35cde5b25ab0ddb71a3dc0c314315f27779207c7952e44a32398a90cd1e6a6f1f45aa24ad3946702d7b55a3186d113a6c3c74539071d91039935bb268fef13df678fa49de0e11abd1040cb72f429de2d4ff5d486590d5189d73f4367d581280050af78f7841d4940aa30aed713e272a380a20cc2e2eab49857f50fdd6ad3ec84d3ffdc31ce2a7a579c4b93b19366478b48c7e438818e174e446fc622b269cd2fc36e6be3847dfd83e997bb57404a8847cb98056ecd457fe4d1c7bc9e9a9b4035a0e62c937987ce228cb0b6326b8c2d2a6dc0f1ff90cd02f1e805a9b22a36a61b5daf77ab96ffe55cf41e4d4df533d360da214c1ae4c6e6f04cd7a6ccf989b16e4aa7ffa2c37f1f9bddce0ad08b5f8118d92aaca10cd760e52c47fa4f27fdc915b9205f301e6a842ac96190486ebe5dc48b325edb3482e66b6a75a10ad2b2cd88f75644610b88d8781f73be0166209dd541e331a436a2c1b8d0969ff9d6c08b0230b9b78c427026669c653577317386fd90a342f17df9af04a40f38ee63cba60f1f84ff0880fc09aeb0a0bfa755d2d49d7b308ccec083563c6f2a758c0f8b5dca9c0388b4ab7783d50aae60bfa505c1caf11922817de06850d0c2cf068f98f88fc273dc51b75ac9e04c92c2cb2ed9b7a2519f42a5e0713fa262872811d9f27d9c927146d4cc0794d618780199598bf1a099c4e72ef6d94fa4fe1ff6fa889fd254f7b70ffea6d2ea9c77ed871276f45f11a07ef2d3a2a131432df97fd1c0c61d5055cc2f6408c1a4e7d9b781f206e40700714394e3e109064042e3540c5c2d22c45ed27017bd30b7947afeddf0435d0158c0264cb5a712e7fa6f0abf36d6eeb8a8c13c7e702b7bd023fab363e0c807e0f672037acd32db627b8c029df055ff232612089561553316053789195232c58b9aef83bb0478921c2a208c11d816397cd313d5096ba3968afe53ad2d8d968c108fa90c2178fe9edc12222708c07c2b08c657ee5ff7767eb7ef4a230ae4d5e5c884808d0071c3dc511f95197312c35ced5a3dea4622687acf36257f0c427124a495131cd424535ccfed0fef24596e2ccb56086c651e6b4e5f175d04942a7640525eb6323bf685c82874ee2a997213d180ed5c646cab45822a681afb5644fc56feed30f79e43703f6c9f24c9db391fc5ad059188209fa74a6b45e07a9f2f4ff279658941eb3205cc4c25b991a2421c35b32a61610ea5ad48c18e5c966b5d6f9e57cd1a2b096ccf5a7e2a7e623ce1d910102a25a572d88da9816a1ecb28b3146e4cb266421fdf7af734d01ebaae04432098fcaebb2e93ba746a5763af69e52c8c93a68f511c907d00b594da25154cce90d53440d2354bd613067da34201578b3a8d4fbb9f0b33ff2c5b48a41fd2d4905f884ee40b71f41c82aa34366f496e88d441eb0807fce725d1e9d9ddb007028d3f78bb30644325c14cf93cb0f49cc36eb223d281f8fca447e2501d3308f5c46cd9eb0a10bcb786d5e3ecd9fef7dee9f93a66d072f2b3164e1da1f82d11e2d639c7152c7647d64e2a8d1bf2e32794d9c49ed89d65233d91afd7e112a1ad184c3437c0a508b199da77675291ddf897b8991e589545b573b34e50388be0eeb067329d1887fa92c1bcd15d3099a617ed25a27486d308b3e7cdfcc8fc05eee44833c86a63a61dfcb4216c7a10e3077f88fb253153d94a012f46a940992baa800d68fe60c34600276423a717cc0aca0333a61901f34c7acaf42843c493d8781d49ed37a1940ced7b251fb490eab5559d160ebf277ea69305cc055f7245a3a06e17b57904469c4209296d6cbd75002fea671cd21c78724f15d75003e4f8f009f156c056d526b9d7f3c059920f3455f201be8056480139364640708e00d5958e39b26d8e0a50d6c0ec02a9beaed972117d807e6329fd0f1223b1e22b2b189f537c30ac4031e4129c8c25772dbf9a072fd15dceb4ff0f00ea2214585ad9f050012e0729a8d13598645ed4dd5add958d25fb609d262d68b32c0baed5e6e92892c9136ea5ab1a0d0d2c6541be1fbfb7fbe262a5dbebce2d13a006d427f99dd368bf33de53f1668078840aaebb5f982e616666be3c8ea6ea67b285ff3ad31e1f18325499d5e737021963016d018fae4526817f6786bbd10bfc331bc3a679948010bac8c0c12dc36ba471459c2ba6a90a475d73864bfe5b9af8391d75653d06a061b412389d34eb817f5a4e5999fe6e53f6e849bb8a15dc8490b7ebd08fd0284c5d6619ac57de2603eeea46bfc0dd3a6dccc9769db1ec593bd324e16f3b1c350e3f8e07c4e2bf0068f60d3a8badb66c7dc966cc84e289026ab7352f60d72c2dadb447271211a3b74781dbdc65bd6071305991b3c7b01a59d00e330081660d4958e35d03f0cf73ae967f93fe41c1ec3cec345506d421433aa8a82fa95f13825228bf0738ff10a3f15e4def37a0ebd6b1b6e5dfba8889d3c468998f51530d898a3becb0612f19e8fce398bdd5613409621ba43b2deb14f3bf6d077b6eb898f6121028410c2a8da412371840a43295c4c2bd7db3a7f2b9680d9639cfb2b5d685cc9f5c30d80e03a8b17726a17f366552e4296aeea1c57ab2a8e8219834583cf58d52cee4906f84d5e279f8cbdf0ed7302575789829ac262afe824dcf6ba5a59af71cda436bd548b369d36f2b2fc224e79f5c2f4b9938c2dedf2bdd0ecd625458ff3550ac4307ad445c60abad6083a96f679b7a5e21b6fad06aa2479824360cd2ca388ee7fe91595174e8c4adf41a1c7b23ddd9d640630dc96bfad6dd49eed06014355804bac532ace277d6b80fd565a3d56104fe94f2831fcf3c3289d0a631ede4b701faf601001e6661cec3c803ade8f8c427d21ee43a4cd5a25c76507245337227a7e0d79de0751dfbfce739a0102faf1951e8f6abdabfe27309ff98da3da08ebd0a0105acbb9c6da063556aeaf7d818748777179a627aacd53e02380ebb6eca8fc379edd2e7b6ec1beb5e400fc9285742e89f5d659114b4c85ecb588d8e08c22fe2e6e4132f9c8fb38492a6c054b4ae778808661fe840c6fbe24863427a0b0a694333318ce311da65665c06d7d0bb17519242d75a85a33436aacadff27dee9ea024b9511e4fb6b4a915b89606b99079a567a20b3a20169e10ce716378c5a3a03762b2d90adc8c069712f9881b2180906e2eb64c74718f6bc19ad717d63b9d591c257d32a6b73c3cbce5b6b18f70031fcdbeecc3db6abb9a3a11e2da4089c2ba39a966b53cf683c30639f48515b587aed13c971b36ae737cfc1f9e6b4266229a285f21b20805af85ab72fb0e983b8fa11fde3892dfa51c6abeb9c6d3076e073faa1f8c5ce00d6880227da91eb3765bc66745b6ca88562a7ab7568234b873deab6afb3607aa2f7bc82e22107b438234e00adbba1ad8172678719c50962059d68ab69d4a9dce88a024d2ffd9daa42cd6875ace29b681421f4b4fb4574fd4bb8d18b0f3500dc0b4b4a2f50313f4fe8058379acfa5cbb2f03313d51950d627d0eb97a02f95a8afbce6108d5ed99f13e67707511c2e55ccbddd9126a4c770cca1c7a024de3c984eab5c2243e2dbe83efd298af83ed723816d58f019e2a49e930ec84add2452f5b7e4bec0c0b9b07ec8a4a05f066eefcdb17d049dc8c0f5751142f9cc2df93942aa86482462b60cc9c82676347d6892c6c446802d79ccfe4d52d316f987dd1ef01f4da5ae600e9967962c70f8f579c3ed503a13e01edfbc632b18e04312b42eaa2937625867fd5b543129ca00efb9aec2de3e4668a92020990e601091701a4e05b3b356d5e4de572950ca7ee7cd878035c1d72a7bca9d4f1119deedabe2cf22a566473ac093f518765bce535eb1bf997f865a243fad86a02d81e2dbac14f04b0571fd0daceceaad00f54f4c233a200f692cf75297e429b7bec0e05af05133eabc96163c5715a63eaa365339531e7d0397950785d6b594e8ce612f47fd792a26fd406f16d2f05c0a31acaa96e087198aea8f3eee308c9ef27ab60bd45ad1f96314a8f6c2ba2148c14960c041bda1e898fc959bc48c0013d12a7c384dd87dab14803ec97bbe01b7819bc9de30809aca207e521c34e1f97fbf3d44abcb1b1bae72a17368df3a66b3b88a18e9170f6362ccb0bbe475e74aa2e3562f6a752f9b0734a45ede2e1e4d439eb321fb1f5ca697102775e39f647570a0b764394a465cd01362d08b416656c74cb89baab8a12f109d6e609b8a63f596a24d4039556fedde5638f97835831d4659915e243c6bdcdc9c89a2466bc38350aac54bd66a096faf002422b83145ddee3337edd406037ae9631a53217ae68f146db5f6c5ed6f1535ef7ce59b243dc9dec7e314e0739acbcf7890e208c520e6a66773504695b6018f596681b2abffaef5506f027e8aead7f3700a03edc443a7faf957e9438d5088b69594b6fdfaa26b543b9b973abcd8eb4f202fb644105810aa355b0694d1b761e435f8e283df854de68a5306c0d4d51566092e4ccdbad5c60f0a7f9b7614fdceacfaf63389ecb7c081fa1ed354b448218dbf266a5080d39e589ace79e998cfe32de121e4efa3c87b62c27957fb6e1e7c2ea4979c6c722a624a16d3c84b15f312e341c1869ee6d6c15947ffaf84b0329ddcd8397cfc47e9072d76a6993cdcb936e5cdb0b0138f648eb200cf39cae1a383f8e9bee5844f0d114901973d968ccece2330a3903839495ad29a73ad679953848ce2b5e343bc7792482d0989920702eaef6e061897fcdacc38bdf19ee3dc84ceb454a3134e3418cbaba5e924469984ec87b1c6df829eb8a28da9e91cb12d1810f5d113915b27e527f1989a92900746d9ea2eeb0e8722ef4f64c0ae9ff94d6fee2c6c31e8538d9b7ecf10d459a9886cb28924bb7db92227869afce4052bf604ade7d0e8dc763e4bf83050f89752245aac9c0c05d630bc21c1e8d3630c4c78b7c070f1d28a8d76affd728840e57cbbde078d3231c3d7af55535f09aa8292d29a386ff125546075892d3f3becc77a2238f0d69a705141bdfbe47dbc68553a9419d0a4f9b73d8657bf48f47e30c83a7c09347be996c9ccd2de3af712f1800caea5d5cd757304e04f2acca073c7ea0cdb7d28e9ad7841dc6e0dd75249c4beeca4d15786b6b4f432c3b0ff6e59a03f0168c4fec315f1d4852818ea4b3b4427db9ce795c1cfdd3e6f722920b3b456f33bbe04f59f7e973d065da6d1d6a9892adebca9fc8ec778b19c9d385112138c5368b8d20e5c15949e52a1fe99891eb1a64e76e0dfb66a0340c13f986ac43c9e6796e80af2d1561959b0d1158c3602ad8b30074b6fe13a9c37f6e6c0141397f3ef42f1c30dd601e1707779e606411bd4c11ba5711e5922619b6e584424e33a8882313cb7a3b4e6dbc344a1ffd29a264613b8c6e71ddecfdc4f13a63e575f211b9ed2850ab83c191208c44d821b0f918113d543ce6b72edaa1f8dbd08bb7c1bc757b916d749af2ce1f52a299f6bcc835804f87475864d609f12b3858d38bc36733a8b87e07b67249139b74aee3657fde874f781ea21a913d730d425182a4c6ba68f603ae7a66f5e9687f9971ef1cad825749c8294504999b00bd059d0f5ec3ddef09bd90561c8ba2506a13d227bd8810758b35048dc54191ada2b9f1a2c1ca435c09741dfc280bbeb410ae296011a3e58595c1ba74ed79256a39ab04a3215c126c3f208e59bdba89f6fe7fac36637789ab31429338a6c1c5085fd820d08014567b31a53459a83d5d82862b0080538654297864049669bc2929d1278580d18f604dc6c5ffdf6fdcc7efa06d4c93f3b4f5ecefdd890143aeeb631368ad3ea22cdfd0718516d1ad9fa5dacab86669f9db2755bfa3e0c98ed7da99f53853d6ba181a0dafc5408eb6281cd1f9869a57f50d020228e80bda43f5b2da09104700e7da623cdfe4529b752bed2f7d7213742b016001e9a5393f642a4a862b80b06328be9e88ea28ce5de4f7a686a10d390c2ed5e95f52ab83d712c19ca1444b54810cf710c0c5024dd14955ebb971c4102f31c0e7db5b5ea43cc6b88315fc518322b863043528c6610e6f63bc5aa64ec5f4acfa3db0291ba4ea2cd2bb1b558d14d99d06cda28ab4f28edf0285bc920eefdc41795fe082faf4bf466899c71b02d88e262b8996f0863e04e20a27d4c413f6b0442154732e21f45285051eb08b6b0ca08ac238424889d140598a5e27afedeb40ab745c26ee2065a8db64d3fdfd752a0bd2d6b18ff85f27722f4c8732ea8519c2cfe17c4ef7c9d4b659138c025d0a362ed5afb1465c4172a4a5784d5cde2889510c562bfdf6b178e95de2a1c2db3bce3bf42bc7ed3217e6bf95cda4fc8af2f2caca82721f7f41163227b4a210a56ac7d3a8824eb328b0629bd7811e8c1cf00962564da34cc74e73c0bd61c85b1d9083e4622d422c2cbd185085fa7707f46148af807bb7d03d01f558b0e703bc8f617d02f66cc15b02d46fe15e09e8b6867a0eb0370cde10b05e0b7b16e00d0bea15303d5bc8c18d00a3b742f1883c18e38ef430661df158cc38eb118c75c69be88c2929645194bc7447c98414f6c75993fed6bf37169cfd679877dace38d3a4e05bb780d8a0e0d4bf01aba002914a4bc74967da21d027ed2ad0f54ee069eec05d7997b72d640d9b6ce4250405492711da504c1a12a19d02a9f2271bec88b7b2dfe5fa5efbc49de6c65efbc44da771cad08093992b5f657916fe3bdccb2989db11d064a10d95d4412589494323942e340780ce2477557abf2e6f21294890768a9086c4b4a110ea2442da480131d6a5dd4e66ea209dc0a95902a773a3ae3721296890728a9086c47466b1d0215f4cade8401d521308351220150da92531d2910670c0fd2b3c7e60f4ab121cbcc77196f9d34e6152864850e056dc75dc214fee6c40255a24753585ac0ac85dd879e90485dbe84ac8dce1dcf17dbfc48b9cf229de22a4d1839bed8a25533e6772d7f14c40166bded2103f2ae9003ff205dcc8458979be284ada7c59cf75e34d9057aee8c6cf7266effae64e950c8b81518306892876170b13c36b57a00db2244c02ba52d3993dbb3b78951b3efb33b84bbcab9e90c7ee8d9ca59b9fc9d9dee9abbbc9d99ee9bb6ead3b18e4799726aa201311696a9303382f0a7906a89a4c74a0998cc8a0632ad1406632a283c66422039da988061993890e6b7ce6cf704756a789ac0e21588869f9361df7c5dde42cdff915bce50c9fdd0e349309597440251ac94a66ea72993d411ffc9ddcfa30eee7a24efe6ceef64adf265332d0b9e6311963cea632c3d8c6640e9a91850cd7e9cadeee0c9ee50edffe0cce24de55c9aa082d871ce5a33f829bdca133b713acc5e9f46e8b68dcfc341fa1d90e693c699dcb26a96bd6680e2f389c2a37a5b3e307b348a631380c42a4357f1ee4837b9a35ca09212656b000fceceb4492739f20545d8e8507dc18d79a9818906c2137c520cc15e7742434ba02a19076e4f58019192c7d399251ae233019be9b3aa2e5914e1c1db2664f550517414f57b6762737ab92d0688b44023640b06aebbb4b2062996472c14ca42d8d2e2955cb8ce7451b4a3d9368293ee228ac695f9d86cc10c5973b8ce30fa969c3c0b57e9ef440cae9aff9fc1d15146da359aa233b8fb82337f354585cb41a214181a62d5082cea06260cbb348433c2d532ad1a752df81304d2184dc26c9039be32df9a642a220198d6d2241c1cee79ec670553e9757702fd3ea1c454819a9b36a78aac3a6ade4461ea3ad85075d2ebcf6746b2afd09cc20b881d13d50cbb35236ed637f5ead7b2b4905549b620afc87c2068c675ffab073cb9b34e4b737df865612414988039398a2aa182f8525c6bc47474299adc6995009fabbc050e75db178377a3ca9f76280e80002a447342a777e065b5586951628d468efd61a68dcf67dd7247cc3b22788789ac412cb1bced283592609dba8593d41a9a724e0c134905125f11be279f04423bb94fd2b5c490d7dc29ef0ad1d70980d8818f13f8e40d3985d161f88da7572117da82febd79fb944a778142f28d8e24d3aec7356610d1b2753a42d20c26e72d51565cb8b8271d18ab8763e46f8114e155a45dc6cdb91cb5bf1f5e1f279b25e6fd6bede8a40f445e10f643ac60d1b3469e8d1cfac21119d9da8a13c3223a19a92bc2107a49c0edd4315c9c42c943dbfe69ee2d5f03f8f46dec179436ede637de7535c81ca4f90892c674a790687ada949e030c0491fbf0c27a3d6ae4518cf1e584e3eb97332eef4861251194c0b86e9ab49fc04d52e4d28ab79d24034fc50489592804238a5a96e8a3faac3cb4a8b22417c79b433593186eed6c69590835959157eea68360205854de82de7177273fc04475d7eebf11f4dd3c49626c5a700e890374abf6ce629b58f2841d748a829ce42a09220680887a3e640ca27a4edc866b1130f0377b6aadc3c007f89000cbb262d8d56ab0aeafdf4a64e3eca5aa0d99e37a2cb96b7ef26aaeb68f9f5f94803470eda6bb76455b0db72e4e5489240a32c784af46e7fa57d2534b3353465daef752d2a2b0b8272389b0b4710755b4256687ae1f1273b03af25838159a7ef18f5d5284058df41a6ffce688c73944b73a05befee820c414f3f958aa8c4cde481db1a58dc219af53d15734c910b66e1116b8d867bfd05e2b9af19f574efab944eacc0a171a93758597bc893402f26be309a7141ef342b274f4e48589a6ac5dee6af041a4bbebf87ee96833c59ea7408a0790000a18e16a13762e7781496184353e05624e798dbf0e4282b2f368431bbb1f1a597307358a7a22678ce64e5d5e2954be725e0c541f23c24c404b3e71938f61f48e0d033d480fecb12a4fc14270da21cd3531212df447344fd393517736bc53ab784b2ab622f21f732a66f6dab323f6509135bc72ca7c1af2474082ae68708974196722c719216cd700c54d559fc169f28befad0024edc43912f17794f549c95816946631a2d1825ce7c33f6b90ff2dceb3d1aced71c937385ee068c1f5653982ffb91bade57ff08702c72216628d2468d953e5f557a8d34619be9266d71fb8572b5f31f833dc6b118de4218d2c12f71d913da2e7f2c37176d2c1bcb1cb09ab3253ce30da3b0ac8d792bc30c309e40cf3cc58d9176720d8ebc2ba5454c1569953dec95cb4fdc86c38e820a056089374181379662906b06ecee0de81a32dc1df6ee173616008b60c8223024138c8078f029b0e584823aa184adf17000df0fc4695071c9866c33358574cb1052cf075094ed3f21f0bcae7c457a4984eff73677457e8eaa8a10bab2bc5c95c4dea434c1fa1ad8c9fa4bc9b28f5173d13b926b3ecdb8802785de77721b74d5ae9c79db32eba8c76b3e9c9b33ca4240d0f3a7f68e4240eb54261fbc7e7a020c3d2ec6fc3872112464fe0da1e85809a039c026ea9b67d89a88e19fbb3e3854a1e44e1c62a31c6c66e052a321e13781e6f2c128890a4d82d8443c8763cf3f2a36c777e27d376f31c7c3fc746f193d8c6a117df0012792eba2e0f0c3b1a935d0522bfa15b010103b8d3225b6a6eaf9dc079826e5184cd638c85a07213b3e4ae3e438d603e9527d2d028af2e6655f59c08195aeeb2dcdeee6c324203150cb61d36a3fbeabcc60b1ac0fbb789d605928b512a1af720448466910cac456edc90074ca31e01d727133d4182c4a09459339c4dadd52744059d431ef0a041444ced98b4e6a437f03287a255cbde5749be3b2d85ba3f0a52916d8127e0a067fa85f40b3638adaf30ad897d69b68df425bf46a1818e5f869e13ab743cf01b0c88b273ab0066270032eb467b1b636e1501e756375dc4e4224decf40374152d007d59ac7ecaa2b443a0e1150e060499a4508282d6a0bcdd74681451ab984fe4a72ef312181a68d5f448ad0244f69c7484714c4c34adbac2c0010a95aba58fa25c1e18675b0d694f1a83feb12136fd4b8505c6782c6a9905dbed6ac1ed0613920e2cb797e2d079d094eb3ad255752081096c8b7c9916879a3bf65e50e87384a011b6a71c81db95c760ea90a9e9467fedcfaab00ff00e5b1367635798daf6932aa77b4dc8f85c39b1a91bf1a468e8880d05ab2ba7351f2bb640d976cf79499cde39626beb8010cba4471b93aefb3098561723d9724ae8fc2243b54e15db737b92ba222a2ae1270ef31b500c80daa9d544f2543b6bb1922046ff8003f5f5cb64e2bfa2e36b98abd1b0f24bad71f8639162b9111a45a3f23fc2b8f0faaa11079f323af7e6fb85cb0a430151e65c707bd29234af3fc6566ed9f836368ddf77344c7cfa85eb1711e59356a4d662272d0f3a1793c0a69081cdde43930f420e86b5ebc3d1f60ca29705410cf21ed91abc7bfb2c664e9ab3b855f8d4a125faf40cb4325e0ab8c9d48cfed39c4519e65abb73073e5c9457419867a9594a103eca03242919fa12eae42bfa8111b66e5cd0cff222872038f8c458dd8fa093d82d6153e9151cf5ab5cd3e069700dd7c5851ba5a8d8c111cd24eba91fb9156163aaecef783eb07c45d460586050151f4c8d2acd3ac78d8275af7659ba2f65b9dc8aa97daa06141989bf2b363558669ab28e652232955ccd3ca4846e615632bede605814ef05f8ca1fde4fe7d93f7b44caf62fb215d64ee75e50440bdd97e103061f2d47d7ae84c56a885726dcb15a0f203df836d4153ca71a42d53cca444525fa67fb53e53c92c0781051d132d6835cb82f4a4e500c63d35aed22e37e06717974e6e09d1aeee9641440a29384770a8390dcc94d6563453b0994ec5bd15852f300b719ee46bbc332aec9c83ce8e6273b07a7916610cee880ce4ab38ea6e0e948f6b460a76d7a82c911db290d165da360b27b612f9a9a1b6e9177474f2395f19c2b75f5f26854d9ca318b18607b5ba23bbea21dd36b8c291bc8c458d397a72a5681501ce712bbd0a377e4f38fc7d3f2d8b7759fd9e1544a5eb2f312080706718a2b5bf77ca5fa9b7bce7ace0a2c199f4709f60965796bdc93f3dee55d998b62f781819a44c4ab800b6b401871f85d6e6a4e94a9c13aee331b2c03b56d826b27abef5effabb48038cd687e2e503b2f6c81dcd4e49373f5a4cb6685fe6fb5a16dda6770d03f0fee4abc61eba65cf7bfe54125a22f432b05dacfe6d6a4ba146a0d4d34c2c8f93cee87822f6c8957397a07ecf5ad0984f628c542730aa9f8561775218177c22b3e0bbea30d8c9eaa90c31aaaa3c05b0d7d41a6757519d35a2f3fdc9fa9ecbbf55d00c527989fee6f261a6f9e8e445dde20f92bb62ed25313c9b07b11eb44a3be9a4b890ab6a8b299576699eee3be54a73a7625ceb8e07da99092bbd04299d26786e77e25f389bf0545351327acceee54cc2d565dbe1452d66c89f830378d0bf50261c48e3d017ac712bb624aad415037f4016aed7a2bb90214e58b96b561c722349d2409192d95b4e05f5127b893317084dd508b6d792503a52e6d61182558140ddf7cbfd1f03d727d1e7bad52f0e2d7f8d333cc4cd128bca90e62863a85ac1993fcd406f9f2f1734eb877373020e647e9d268ff17b876f38c258f219221a27f55045df39ee55b93c91680fde0a6b0e445ecf7e57a3516c1fc4296def6e6727290ecc83cb26801a28b4336f7450436bc8e5b4c185a2ae5b9ff4db68147f101dc20b586b7855efadfddda7ad2cf2a378ee87526cd4ca4b224dae1863194a9237b490000b9cec6ffdbce29c2a0fd9094785d6f73f6cab85ae0f49d7f01dca96fbfb8d689a4ed7c0ff5bdd7cfc42e6352472ffe4ead6ebb8e584ac3a12604099602182f7a0d149cbb54af5c2929439a2da292900d801d09b82b144e7638898790d19a65f64413ddf0f4c44f7bf585dd297cc67a307ca4a42fa4aea4119a43159b7734166778b40e468d35c20904341bfb7793b9a1707080a2022cbc12c1272bea3c159ba0c6bd7f79a4b6e62da30f8f9583c5fec7e0748aef28f32e8670b3b096956be29851482970c0bddc4ce4e657dda992b4cfad70e1b885860a6a0ecb2021fb2e3a42c370ef36dc5e7578934413cf954195350549d7e3e30ce29e5ec18c764d7bc6377634bbc6e3e8b90d89cf867163efd7c3691ad0734fd0e15a65e47c10bf1098950f0f9bbcbe0bdc68f28f06d28c0f6d03f5fa605ea8a8b87034af17bd871631079364efe14c7bb2ea12c5b2a66685e2cc3735db02d9627db276c13bdd98ae8039be426dc76648e89772c5d10bbea03843c814fc8cdbc084edb240376f947c2c20b52a99df7919f95a7bd9e8fdd0a599dc11e098f4d6f755d6f127dae2293bdfe1fa4212f3bc26bec70b0ff3c5196ddf4c0d8ec706f01f41f02caf05fa03770f054723ccff33ccff33ccff33ccff37c8479ef8f65a31511c9eb113aafe33d6866ca9d9b442ac911d4488ee44866f55bb7f9e488e625e109420a160a66b5f9dd8dc92a4f7a144c172216ae7c53752328985e542e4a2f84d4e2138ce297ee225c886c95728251342b98fcc71563a9269843b75f9139896bdac10473a57b9c0b1133ef7396609a0ec9f9df03edf04c2518d24dc7a7aab861974c82b9dbc53a4caeeb9620124c9ea3e95eed3904cb1fc16c373965c4478d609090d3a3f26a76a45111cc26d3eaffe7b57e792298c523c793e821535f0fc1387bf1d244368560d20f75d9a2c58b4d3f08a6b2153df7e8b153e80082218b97655d13c9f1e7f88129b23c3ec7e71241253e3069aef999c5f868983d30c8df47e1912d0f4cd13d48138f33bb92b30363d9d74e5aa644f2301d984d2f050f42227a249e03d34f87ef1af93a9c7ce380120fd2d53b8cb3b88139e28bceb407cf7f4b163630aa85f0204290199b9890450d0ce241c418d7da724f6960b614c2f37cf43103f3a7fce9e413d5d3586460b208a72a51ef5e9f8d81794ea2c7922f9d050c4c22e369f139d6e32867f10243ac881f428acac1d49e850bccf18b887454eeb148a415e6ead4becb519ccadac10a73c78a3e69a973a54badc2fc196137162f7e6e5285b9a39c913df73879f8a6c290df53fc547d3ba5122a8c5e1d24455e8edffd9dc2d89bd7991212a53ec814c6e97c39d1ab97c23c1fce3adf4bbf7a278531626bb23b5946cc4761ac9f8b5ef51f5b6f89c260a3fb51fa38efa70787c2e4a19ec2c7e4c259060af3859ab4f393fc51487dc27c35f3eb16af27cc12522b9569db09f3e7b093ab12fa398ee484a12d8807ffb1dbc7dd4d982cb5e3c9b9947ee44313068b91be82f5ae7dd8910973b8c48e44a5720af3c084f12e4ab744b0f2387a2e610877ae21d93523e5b18471e3e3f83e35cc5afb4a98278f48c751e99430d59f840f753af609c94998a572f09b6fb7934224610e35d9ca21d4a7673d12e620eadfadcf871e390f0993440f4e44264887f41e61bc1cc4b22e91e838a48e306acda95e741ce7fc63234c3e15c9dbb5e27c0861846935528e2cb24598a3be9c430f6f73ab5684e12347a2589ee0c94312618c554bf520b8570ef28830f8cff6b45f586ecd0e61f458d6673b292adf6b08535d4a32294dec6eb910664952bbd517210c1d94b4d69ff6ac6410e614d73e2e2427a27a08c22817132d5db3c53e3210a6e491575ed2def01c018439374248083978cb1fc71f8c39a923f585bbe78e7e304ea4ce9ec53be5eafb604e1d25f3a032f3c110d299e7b0c6ff724aeec17c7d61f35e759fdcd6833165c53bbda7f360ccfa2877a4fdeb1dff78309be7be96f71c24e67607935cc833cb71303a5bdac11ce13bdef79cff091f75307ba7754709251d0cafb9f6e3fec1a6c539185fb3366a3b0c9df4cbc11c6e4739bfb3079d9d8b83e9e42d8d9a95af660f07835c96fac80a0b9f7e8339248beb131eabc7466e30c57bac4daf0e262d6d83493d72f638e5d0c147c40643e4ace5415b8ae8145a83a95e36423e259590623598fa222b27f4950673dd6ae4498bd0153b1a0cf39e537210d235619fc170d531c4424e9f3a4ecd60ce09da3bf739b649c132982d3ad7cec30e25e48b0c86f0c169c590d8d2af2613b01883e95af33b8e2689dd478bc15ce271a5ea1ce659c70983295b08d9c5838590ec0106938c5e6987967a68c7170c926a925fcecb8e72a4170cd1e55278c9d93bbf74c198e281b8e6ca5c30ea071db4e3e9f8f603b760e816b50e692d2d98a73f4ab17f46a2cf96058356eefabfacf2f8180bc6d1b9530fa26d2449af60a8b5afafbd76c9b16205c3575f841cb2a70aa6103572aeeca8e2c7492a182ea429cfd0ae09396a0ae61c29ddbcc3d50b714bc12cbebd67a59eece48b822152ef764facc7991c21600105c3f74c48f1e07ac62c4f30c75972b411392de5cee104f3668e67bbeeb485b909a6948d9ef34f71ad4626186b65fff4d3c7124c163cec3814177fb9cb19b05082397cf6524268a790104b02164930b4ea448796f6c17fae0b5820c13877eb39bfedc19b7c04534ec1242f4a8e85c508c676f1f0b142ac9426aa5ac0a20886289236f573902729342c604104636ba4fcdaa155ef42c810cc4118e9bc74a9100ce12cb77e78e1bc2c244130e5ddc8896fc94155428060d69193b8bd1cbd76ee07e6afbdce321db2bc7f8841c67831c4d82ec62058f8c09cf5e387b96e89fdb0a2aa0d60d10393548e53ee3855b8fdca860d1b368cb0e08139e5f3c383aa10967360b103a3955ce8785afe531e0b1d609103f358d08df4ab8fb4cc1b484751010b1c1c163730e4f824870ff452d84ee0c51721680216363024d1faf7403e6ea0186378310238605103b3cc7f7894fb30babc7741027402163430458ed84987207172dc0c8ce1956445f26cf7c7c909c2b00011b09081e1bfb6a673d4edc82558c4c01c26a7b9cbb77ad13a6e88b10af80216303064ad44d7fea9669667072c5e60ee94b2f55dfad8eee15c107103162e30987410c96346b8a0e4c315ad30871d84b98b1131c9498c830e57b0c2ac7329c7931e4b8ee8051820e8420c2e4ef01fe0c20f70d079f11f08238c107c71830157acc2142a694b3c8b20167e071c50b84215b66459597de49c194985b93e5ebef59c6b6c51e34910c68b41862b50619c4d4b1f8ee5befa6a6c51638b2d6a1400ad0c579ce28e5244891d87d241ca0e3ba8ba1ce58b96d6b080036ad8b06124302c5c610a43acc7dfd96a32c444245c510a93558e52ac89f01f2a4f0ad38d7dbc17631f84ca7370c5284cedeeb1f383290ac379bc1eeccaf275ec0985f17ddf239b74946a3b288c3a41fa438757e8fb9570c5270c9e72a656e5b497b055608c17c313e68da01133bc3e0eea25c153c0860d127c18d90943c48795332fe43fe570c2509e537aa2e5641dc89b304cce8ed2ef36d371900d1b10d800172bb8421306edf82ab775beac453750055764c2541b71dc527503e952700526ccf176cb3cca63c13630ba28819de08a4b18224e723b320b37122cc17ff14486708525ccaf76eeada2671b9204c2159530b707d18e42ee387b9c3e1f5c4109b368870d49d61eb9a39330e708965e23a4e794d237902461a8d4e1e5c548555bf806d28ff1c57aa0540fae8884f96e42b6d18b1f7d4e720109e37dca362529a683fc7570c5234c7dea7bafe31da7101be3c57084b1ff4222a794da0e3a47e1e08a461822d9c73be3f1a49c446f700523cc2f2178a4528f95155a84a795fac1aa3e4598cd4572c8962de590ff4418eab503e9dc915c7e50dae00a4498c35cd4efc0f643b50e61b6f011112c758a109234b8c2108689b2949c7ee79e6d21cc6d629667fca5edd326832b0861c8bd59c1ef5379580f3106614a23d1fe63d949f9713790046114cb2fa993e5f6f055175efc185ddc28011763bc070261fcca988a0895c2ae4f0810e6087ae6d942f66597fcc1a83b9a173e8c0e3bf57e304dba2cf6e14f7aee8baee883a9d347ca390a7b15ecbf8174900baee08339db47ee623adef7efc114b2d6a38adc0fe9e3e8c1a87b9d1fd654ff270f46ed2c39c871b77690e2030406931c65ea44f5f98bdd40f2c28bc50241170e00e3dd04325661f24e0fb276b7f77854615cffcb519ad6531d252315a6a4f6913f4a74b3d9322003156423659c826c7c21c31464c34a6188d6f2d2b14cf99ba430b4ef496ebd0a23df16440832466148d91df4659d7fc71635d2860d1b368880204314150ab2210314878c4f900d4f98c3e3402b4ad81620a3138674fdd0728a90ee3c9e13e634563ac9bd3bbbe89b304545c85126dc5422d7842164b4684487cedf3b138659b5f3d0e37414b3c684e92355434acae4277d09435754756fb598a35bc214fae921ca786c8f5c09e37bcef3fa78611f634a98ef4387d71ffa434a7812a68efe2e3c5efd258425615897c87e7ef1c15e1c0973be7b14dd23a92e624898438eee5b2bc8e494b38f30d7684a8a2fad234c97c2fbeaa7f81fd6d908b3dbbc87729dca5d4d4618b73fcaa1865afcecdf224c5f593dedc4a81cc5a708731c3f773afe4ffe798930f7a656b24e164498f73ee528e2f4740e2c8730e41c7988bcd6519a698630ac89eb471d3b3fec14c2b0ff71e5dde3a8e3342104e2dff6c147eb200caddb932cc27faa1484299fc713ad25dd6a8504c270dbaab37d9e254408200c97e5f93dd2d90895fa83f13a48955b55f9c1503d3f93f693791cda07e3e478653f8a79e58a64f0c114553da48bce7e313f64ecc13cb5923c947f946f3c64e8c1ec1326e490baed3c76c8c883e923c3a27b0e6be6918107534efe8bdbe1fd69f21b4885dcc11cddddea99c9b5e7cf0d242e46f02310c1174f481564d8c1709692aea6a76f2085f1639ce5a20ec60a0f268f854dd55c70f17c820581d2c1d0a659366fbed9fb71514a903107d34739f96b67ab4ea99220430ee6143d9ebd89de4ae83a071971308699bab8f9e5b85396830c38fc518cc7f5e1be6f305e9ce8b1c7fb606f4b37985646be5a6bd36c926d30590e7274ba90b342bf33830c369842fe78903fd696a9720d86ad0929c471ef0332d460f6f8d43a54f2ee30242c230da69af8ddc1752d8a9e9890810693779acb33d1f6fc2f19673047d1538ae997e3dfeb32cc6088dd0b9f83914a33393790f40832ca602813fdc8426224194c335d31a95722c5720f8cf109c8838c31182458fda78df197a81e04021be0020083902106534a513cef4f0729da85c1f471103d2a72bc83b26f201102be8880fe4106184c9363345ec2b9e720626990f10553f607cf392b8d4be60563d57fe49dd31d1132bd32c8e882213aa79c8f2cccc372704106174c2147313d4f587b8a9e23630ba67ca9ec43a647b9daa205b3f6f57f78163f8ea7bb81843b909105237c8e3817ff878b2e5390810593f4cf7858fb676df209c2f81188800632ae608ef1aaa5e76ef95bb981d405177f0214ac17571c906105838e8855647d105a1d30c8a882394894947bce6a850c2a9873c877eae421895a7e1532a6d0c64f082ed91553195230777ace1a1ea48bb66183a84821230ae6ac3ef72bd19d63f95030e48dc81f79db4f309ae5a062a4b34e30aae738f987af4716df26183a8ca5e0d629130c1f7e6c39858ea582049760de920f26a5f75d0b4125983f2a6ff8689d458f4b8241db2c87eeb821c190c348688434a9d21ec19483dea7dd930f6b2d23984d2fff7a4e043fab08664fdeb577edf91611110c5e51eedde3ca47e6100c2adbb1510d0b24400c13ec036a6c51e30463800102e35f74d1801a5bd438c137a0c61635c2f81b62ac026a6c51836408c11423071345e6263cf841307eea2064dbbd81609c0f725866ffcbf881a97294becee4a84a525c102141e91e64f8c0742527c983949f3fe981e1e24d79ab57b84847060fcaf1ef71a02b5e295c193b30cb4bad84378917242b327460f459091d786cd7b13222091939908103b3e57eb4c93367db08f8a01a1648401816f8220235b6a851630b5f408d2d6ad4b080036a887101cc47c8b881f94224427d509233721b98e3d749eae1d49faefa06193530254f1e454f2d12a45619343056597638f329ac72bc8174d41564ccc09cfa3cb8dcf0b9ae1429c890c1d6e569ddf3f2c2ef304610820a38002f212306faf8fbc54ca94423b844c880813987a129f5b177b11205192ff062ac5a8ae43977baf0020c30960b74830c1798a3a01eb332596ed112e30a91452b0c67fae5751d57ce7537908a14b2608549bf2bee74d53d7753c862155a5ccec92ae22bfb0da40f84200437b8782fbc0b2ebe084165a10af3e5a057f7e13064910a7377e78e438efe93df55811a5bd4b0809619b2408539bdd37a6c9992e5db82b238c52187f56e51c283843186185f74411e50638b1a5b1839c54116a630e4948bcb8d945a2f2e8521da476ce477becb9414c698ffd0952eef55f24761aa74cb1b15496d2e2f0ab3445f55d297cb71bfa130a7e0eda926697fb82e280c934ebe423cdc5b3b3f619c8923df6d9b27cc16da5196142365b1ab1366ef0b21eb6b0469f9e084e973768e4aef5352993751aaf873cf8ed2fb2097822c3461968b1feba17e0ab36f26cc1fcbbb637864d67d61c2d039ee28564fa4bc951642169730dd5fa7240f422764610953d24a11da546298442b610ec3fc7aefaf3ec50a75210b4a183afd8b618c7124008f90c524f4e852470739631e491c78c7e4c073f8ae23617c0c8f663a550c09b289da7e0ef982fa88536fae7b627927dd1168cb6cdae7e87536e238bcb156d1eae8b0c288b2e5bbaf9c1f7eb65a041d071d7cdc7badab2a22558f3e1f85ceebe5910872a53d48b29d3e7201c682a0c0d883c0581090304230c6ea09b2408421488c497c2cf65ed60d0c01a1304620c617a60e61f4fc97feb072eab8d42c0c6108c9bd2e913c07d31dad218b4218fb4ea62eba4f48168430448f2db23a780761b8f00e2a8764210853ded689142cfeadea241086b9e49583b433112a6401883f985bdc7290f02eb943dc0f86e8c9a6f2bc781cc7de0522b0c58d31bac880185e7851801a5bd4a8b1458d2d6a6c51a30224e8c20e2a50c8a20fc62ee9f22854cd69843e04e62a0b3e18a3b6ac3d18db529cd052a9c9fdc8420fa6d2cd8ccfee4a9e3e4a0959e4c13c973ebacbbbab49331e4c1df9e4b0b63e45ffe81d0cf1e2a72812fa3e4f720759d8c1e0514b9ef3d4f92aa47d90451d8cd6952b78c82bd1d7d2c1f4d9ce4672bfb4411673304978f7af0fbb1c0c963e8a1daeb974ac131e64110773449f78c9c625c4b8c0c110d2245c87b46215dc371827fc435535ff30567783f1fe2ae6c456ff8ecba20de6a0dfd7717fec51904b0eb26083c9437c46cce5b66cf50d242b618c0146166b307cec8a3cfe93d553450d465d5599d3cfb0ea2b0de61c2397dbe6a7ec154383f94bdb3d83d94f74c2757b18b29b19ccd117fe72eea7d8692983493edf7c1c6225ee6c32183e9e47a7472939b78dc1d02174cd87ebf87a2e6230cf99a70d0f56f2d11206d3c56b9605dbb78921180c75e6810793fa3eabfb0543f8e8c3c8e147ae7c1e5e30897e7d0ab13be5a8f25d30079d69e2e69fe3242e98e31c47ad221eebe3ba2d1892e9b64418cdd68e69c110cce3768e4424cbb62c98834f1e7c78ddf183f858306977f8c9abe3dc9a932b98e392a4cd07efb1d359c114214faec741ec3e4f553084f048d43f0e1d4b7450c15cef5b29448ec4cbe6291835b2879012cd33dfa41046c87b21dfb9341661ecb39d8a12f258ccaf08737ee8074b791333112fbac880076824c290239bbf4eef292cf68830c6793c1f0797dacbd38730e55f4b219697b6576c087307d21929c48747152d04f1c26e55a72c0961b4fa9e7ff397ee980dc2142c3ca7b987696bd30d3c41182d4bfd6e8e5fbfe76904020fe645cc3ae7a00188e3f27899109a1fb32d6cd8a0f107a3daccea76189ff871fc60ce27361e7c74f7c1b85f39f8c9ab7a9c663e1862bc8333533ff57cf7600e3bd8b6f5aceac1f05194f92c89b21cd33c9834a6d2257c100f869493a454faa1471729773085b8dc27ad6b372a7580861d0cb3a1a31626a6b4ee3a1842058b1c1eb359f1d2a083a9ff2d94a4c8e1f93f7330e767cf2152ba5a89090d39184e733cbfa974b07cd18883215aacfcf842cfbb5e5aa0010773764f977ed63372badc7044008d37982b3ef54ac67eb268042038880034dc600e2159db3b9d6d308590ae63cd5778888d0da6e8f92e64e450973ab20673677a645df7951021a32a3016048469a8a133c9714e1accd2518c4be1e1683049ce51a5b80e21fcd934ce8077a78bf4c0ce000d331842b078611117f6b7530673857c21f2ee64309b4afe927d9bc6184cebb16e5d5291e85fd11083d1276e072523a9f4ea3098e37ff865218f587a1c0cc68ea3ffc95b4a72e07d21fd0927f62929a2af6c010d2ff0a1911e773ad6f1edba5034b860be9c91e33857fc061610d0d842d9302d182c687c1caae38f46164a40030b4697b413ed627ebc3740e30ae6305a62a75b974916066858c1a86997626c5648b788512e40a30ac6bb4e8b6a392e34a84036684cc1102f44a28570412d461b36ca185f74a1802229d09082395cf52a7dce294b4e2a126844c1e4397a1cf8ee0405432aeb60d371789074a5f104ad67fde32e498b26818613cca95542b35752d424e908349a60dc3e999cf3b11b48c78e1cd06042d15882612556ba7924ad1eab7980aa03d05082d1b4fc32deb22ffc9f04537e6cb9e3b63c120c66d7922d44e8f6ef4730b484fb4072945348791bc154db29d5a3cf6ae1d245304998ed8ac729444b61221882b65bfb68bf9db84330975e7b66c867ebd80ac19c1d59a4f847b75e5f100cdd3329dcdd6472f001c15027e6f35174779eee07466b8b290fd337ce2c3e308c694cabb444fb20a50726f3a9aedfb2ef8a1b0fccf9ed29bae6faef5c3b30ed584a218711bda0950e8cdf925a72f4224948e5c0d4712a8bd1390ecc91a5bcb9e814bedb1b98830af9b1ce5c667b363074796a8ae9678f2b3530e4f02ffbe7f0175fa28131e53cb5cba9799c836660baeb4ac1e37842ca7164600a713d08b29155c53c68c4c01cef7c87912aa57e4f6160d0fecdbb8e6bcea3ed05a6378fda236277a45bd17081c9c3d653081f9f6e465574822f1ecc6885a9fd5362a36b3ebc42000617632c17fe6050ed0c56983ef98d4dc831b792640766acc2e416b4ee7390722cf22955187db5e31349a23bf901a38b0f88910a730edbe1ab5bba8e5ebc81f4817f300ec2c2c00c54982ee7afd2f372a730c9e58ddf3c914c61e8cafd61b66a844f9695c2e0111f4c924d5acb9f1a1670400d1b362a0a3348714dce7b48c7963c0a18fb0aa8b1458d4741171558099c61c6280cf239a59af3f1c456c6180118638c20020698210a73b8d7daebb03b5fbc4361fe3689d853ddf1d84bc00c509822e535543c48bacb0498f109b36a840e3c44ac572c9e3064a7dc7d9dd7c2544e270ce9cb54364b2478983961cef7f954ceb7d3c6b70943ad9a7c1cb4abc3a368c290f2a4d3babff49c639930b869ace4788f565c4c983cf22385766a39b30e0ad4d8a2c631811a1270400d2fc000411761fc175f7471543209332e614edd1e79e430d1c14b2c6196c97176d82557c2a46221968654a5850e250cd9425ce728052761b0eaf8230f4a26490ede40d201cc9084399aebfd8e933b87ff185dd07bb10025c38c48984ba693e4ae45f6f220618e9158bd1ea13f48fe11c6ce4df7d03ef071df1d61920a39fabb7ca61e0737c2e441cb6bbb64f67fc808e3e9eb071e85787cb9b3c28c4590443becc8425877686086220cf571855a0bddf14c1fd8312c11c69ebd60dbf157036a6c51831051cc7e5d310f3f8419c2b00f3bf8c8e1e328070bc1bd5d16eda8a42a8438caf51f5e9e1cf43d88737eb8756709628b36ebd032c7e32804c682e0a019813004f30f22a5741018fb802307cc00842157ee84ec39fe07837d4e89aa64d5b1e60763e4eff9c0b4d205c9cee883395b7e10f29ccb07b4fd721cc495d841760f86b49d3faad0e3b1f77a302749394dfcdc758c8bff22045479c0c286741cf5c58f276178002921b0012e2e30030f465389a615276fe87c3790fc0b10fc180b28807be1c582a002549871079357f768e5cb6e2011a51dcc15eb42b01c9f62de7fc28c3a18af42b80b1fa4b8161e3a98a3911413dc542ffe3207434c468a511d2b7230ad9ca7c75192e360bc0a13729ee7f83f7de060fc5c3fd12fe7cae7fa0d86fcc8573b6e428630c30d86383977c763151366b4c17cdd9223b31cf7925536186524bb4fe8e4eef1c3f82385196b30557dfa4fb58f472ba806a347aab38f9adb1129a52461461acc51a6d4dd65dea7f2d1608a965b0f165a6eaa9dc134b7163a4c4ffe51906cd82033cc608ef4b224e7d8a88b758131d60b04d8b0f1c58f31c67a718c6694c150f3eb1a95174b82b72e4010822f4ee0450824f08f802c04cc208321cbe3eec45fa63f06737eac9ce3245529f188c158aa26f791ec479ffe3018dbd67772f23418ccd6d1f653effd0543ea4bf1d2a7ff5039da0b064dcf3eb327a127ca5d3067fee7641d9a9d43980b865c39eaaecae6e191dd82417afeb3c6a35a305ceca81c771492efd62c1874f226e49c735830e7db7c8f2cc46ecfa15730a4ee5c0e5d54653dd40a86c873ba16caac429e5530458eb8fd4147a5f83815cc390e93dc762da246390543e5ccbcb60b8937a5608c903cab758982a9262287faa79c530605938b7bdc9de3c7bf049f609a8e573376a1134cf62ab3ff715744844d30c67aee9665cb91ef6382495e2676e7724b30e514aa563eb0b5cb5182f93b2de77039a4833109863a2fed50e9273e0a124c1269ce734276047320de2147bb2b131e2398535abc0e6ee616d5f605d4b080036a901a6614c19c9176a7a9b3155f11c11c3cced478b41f5c4a433065cb21e6a352857a2d0453b879fbed8cfdd0632e98110473fead6be40ab1b1901940c854c3a2a85448e5fe9c3ebdbd9a6504337e60ae73bd8e1f39a5fb1670800d1b337c60f61419fdf1c78c1ec898c103b3e565e458965a4c120231be0841135d290436c0050366ecc058f16bee36661d18472cf7e538b2dcc9590e0cd2718894f1ea916c8718360307e6baf9b7cea2f563aadec01ca57c858f3f56ae739860860d4cb94ebef6f2c757770dcc91bc7e3097c811622ec10c1a98afceb2e3a367600efe9e67c9b7e36371860c0c6ba713ab53fc5f5466c4c08d971fcb2a39c87b931a164800024ef00d98010393cfa7bd27950ab7b9175d64e08b192f304d9a3c5b9df258749be102534456742cd120b0012e5e462b8e1501753024cfb1c4879063217d7430b56fe57c1cb710ffccc17413c42207b3658a79103f8e3898375e27d8671f0e26151bf148ec7394b96f30c72f9bd1d21f4cd5ea06830519cbd408661faa3618772af56cccc50653e674d4921b9375c73598e5be26bff5538351f3a452eaed25d9320da69cd0c14f3cb173ffd060cad2f3d71bef35899cc1241f22a7f452663046278bd0089e1e3f2d833998cf16eb269364b2643045bcebb7b4a9165d1d83c13bfa94f09fe3bc290643fc8f50163fecc7bf309843b4742da7ae7335c160fad8642c725c295cc55f307eec9f1376ffdb342f186f2eb2eefb7a77f876c12cf2c93482b4fb7f447bb0a0d739fd6dc19c93e3a8c3a3a905530723f1c542c741763d0ba64e7121a84c1e0bc6aa2ab9a097bd8239fcb06392ec5630e7a87e5bfa91881f5f0543968e64b55be83c512a98034f1e67c7106f4bed140cbe9a1e8570f5cee8a560ce69d52c5eceed241f51307e79d4081a1f9f561f002898224bcf77a41d75d4ff04738ed292643f79492f4e30f84ffcdd7a7fb0916d826924bbc7a6b66b1979261824c5cd95ec5cb9944b30b77d7b8a1182fd875409c69a2c1245eda3a81693603a9b0f29497f3be810128c1223848931fd361a1fc16c152ddc4aca52026004937c787bd1938de420a7940014c1f4e755a175df4b265f841000229852b09cee6ec2c57a6508e6b80f3ffeac0ac11c77fc122126787a1c04534af3955fd53dd33e8060881fe2dcc7f9c93a487e60f4382ba7ddb35bf02825003e305d689f9891e308400fcc3ea14666a4c20343ee75103facff0901d881c1d6c3eb7dfae03c787560fab0233ad7f9633c9703b3e58fa17639c38121d64c749cf1f3207b035392df9e90c37dee686d600e69b23ada8646bc930901a88139f2e58f2d8807ddea03400353ccd4deb6a967608a8d946dad8fc5eb9581c957caffa388ded15125042006e69cd284761c84597d350a018081d1b6d73dc4f092e8fc05e618fbec732697722803e002a397ac758e1b65179113b46885f92c7bfef19c32397b56985c442784c0a0c52abea49792538731e1a30a3fce8a30b1efe6c3938a267e1cc748a697914705efd7f1a51099b4e99e020f92a6b073daaa1ce937b1ab14e710995deff1b091828f627cb64a4a368da20e3ef06822a78f28b8b1bd4bdbea930b0f7cd1c509fca052a1307a24b12335afa97c162b5a80c2b0366fe3db63c1430f085a7cc218f629dfca56c57ada80307e8c05100fb4f084715eb2e7529b8a9e3b61c871162b446c4d4b1d4e183466525dcc7b13e648277adb2d6bc22c96dc728a4755073513e6a0a24f9224398f33c58439c78fd3716b3dd9a59730cca77ca21f3e5e5fb4847127c865ffb29cb388953048ae746856fcfffb2861d4caf629aa9c84213aaaad07e98354d249c2349d1e7bdf63e7da23610e911c84d4f513333648987d7c46dc27a510fd23cca9d2d2e4fc1a51db11861c8711f9faab11a648de9e57aa6284a9bf3c5d45891ca4a74598537d7bf864531f5b11e61016b6dfb1a7dcedc844987fefc24ee4c882e6af05220ea7fe5397a2450e6197a99468e408969644d5a394424cac5c410b439873e94ee8a7458f258a000a1410685108d365cfeec1ff47ff7c3126684108936c7cdec91e663a920761d88df68d0ef6a25bec811682305f94f0f1915e9a873610e614ff8a97750784d9528e381da49e96f4f107c37d8af46fb93e8cddfd60a8f3e09278641deb651fcce6d9653e8a361f0cb9cb737967fbb2adecc1acff39769d547a3007794524f68510829f07b3c57ffa055ae0c16ca1b2654f9b96c643b4a0c51dcc3924e75817a3d70349075ad8c11c7df6b02664bdfa2d0c8216753095abd907f9b773b0590b3a18ba353bb8f43a767f72f109b061c3860d31ba085accc1e8dae975d66ee2455b4a0b391825e7085fa1a2e8557c030f52086c800b016811873cfaeef02b74e060ba50fde1fa6287f9df607a8f25da9e2f97c5e306a3e428224af4b4972ca70da6f538ff61a54abb1e870da6b42216b4625205f5d760fa1c4b16491f79fc385783495de643bf4acf4c9c067344558fa31c3fe5602f1a8c17fcd3728765e97f3d83492b5cbfd8cae59c543398eac3f29d859c36222c8359e7fb830fceca2e3c321872defd9b74bd464e1983d1cc23f1d891bea4b988214d51f574e795309872b47a3b42e4981f309852b382873b395f785f3047b2f7b1a945bc60b0249d3beebaba60c822293237d2852016174c9fa2e71ce94a5b30871d3dc78ddf4facb560ee9afd881fdc8b85b3608e3f3a8757791d72120bc6cfa1732348520fa32367a0c5154c15d2acbae50ff99f6305f355db87560749525a55305ac87af309be971ec70d24d5820a26c91e675e398eb52f9f8271d2c34fed714cca5b33400b2918db2ce7b230dbfd9c3e8116513046deb849c89fecc8a105144c59cc76e23d22b5659f60d0ad149276e8b2b838c1ac1ef5a96adddc363286164d305cc5bdb1d81d26986c65fae52453655f82e1e338f6fb599f8b1f7de4f51d55882d27c1a8a3a963c972a81e8804635bde4d0fb79489e9110ce1dc83f1c0f5fe7423183f165f2fb3e93884b208a68b27f2977d74ac430473e4b67276e7d146bc2198f5672bcdec84735d08a5c966294130a4fc502c471f08e6d5fc543662fdc0f4b322c952e6e44fe503934a7e4ba53d305ff20af9e5912b3c3c3045b8524fe9b37628a11d18bfb46226ab5bae551d983cac4811d39e723673606c13f5a0f56a9fc283055ae0c0943de4f85285d75b8db4b881d16522c807e115fdc70dc3d8b0b161bc185ad8c0fce15a23be45c929f562c3c611022d6a60e83055728494834dd0550b1a9872c797fa3fc79981e12ffd8594153bfcea65600e5fe2b785a7b5888139f0aaf6936c95dd560b1898aeafe338e6a7ecb7568b1718a2551eb1c9709d1cd5c205e6e84bae83f1bdedb4b6c2a43df18285ef67f5485698a36b0bbff27015e62e49d69fbe83aa045561b0ef97f91c4972cb49853947162a748c75906e5498b2ca07fbdbdd298c5216e2dcef875bc899c2503593dff196ff54570a7398aae768c4420ad3a507163f0ef633bb320af3587c5aca9ea25d554461bcad89a4b15296650b8539dacd55470f1feb1250183b2ad2b772a2590af904ea71ebe50953caa5881aefa86077c2a4e7d107f325151173c2386f951e153e5de5260c17ff41365c4aca221a9a30bd698ca554d2e651bc0b1a99d03de63ac5b2d0bb9fc2021a9830457b07fe51dc9bf8d60361ac05467009536e93fcd9372a479258c2bcf7a9fef7038f16f94a183dacfa751479e539a7c41de7d3db6de64918a48324661eb9f7579584b9836439f2f220e59f8b84b1e3d4269235ca7c8784a1b3cd7bbeeb38c8323dc214619d2c27f5bf8f2247184a3f9ed2f228e79bd908430e3c4bf228ce6449ac6930c21c87b2e90289c2a8e40de622c2481c0e0483016120140692f21e00631408001028248a8502b1501eecfb0e1480024d3420382a2a1e201c1410121612201204036130281c068401615020100a8301e240213c24a61f00a0a802051478ed45701f900e6441017b2721d4691095267b774b032fe43e099f3581622ea3fa1edce1650045ae503aa6872b940e0583a20205145e4671623a18c85b282a940b2ad7c19554030a1a0ad12894d40045a1a2c2f217d1d04482ea3be6843c54281a8c027a5241a30a3f0a7869e4e80b3c146df537625d925aa06450b7507941519e59427efccd70ace011cf1a868242d5433da0a450a55030541feade50c059b0f5211d6110933c6cc20bf79a14af66752adba084a3ee9da60197c52925a923d4234601ff6b48724374a166c99cc158ca226ebfdd906e6a7a1e91d4878a49fa852d29cb85a7741eb31b082fca776e87bc7aafc4cf37dcf86edee969904cc96e75d3b38d21c2d542d8252f5e9dc3ee1a25e111b9fcd386bce6f465a30f29a90e2f812da05b8cdc4292630a19eacba3d3e8b85a528930ed4746000dcbe1d42b0af23c2c749c0412aabb1116bad4c80c8d6e81ec563e0760c2dcad3f7d05a2c983efa11c8479bbde18280a3fbfaab38124e74a8743a8c25dd9866c0d2ad143df808e930641a6591c1e57587d0e8824a40f2867c3009a3c393ef1bc243cb4ef5021ad86b4fccc2d36274cf3ce3490703290eaf51f4d230d7b4680974f75eaca175f42a9f16931514c5cc63c255bca928cd97f608f81871149c25e9a4ed75faafec70ffd97ae087003614c0cf7265eae903a5ca36f39c2b9300d0a82a01b780427dd129c9da0de0529431046d92b4e850f6a421f8928144a8c2ea1ed28250a10a7390708ca42aa8a33c557ff94167d653154c647c6d55207f50490e34651e7a6cdaa0cea537eb7fc814c276bd3a5659fec951605f2a7e6aa8a9ff2573e6cf9f6ea74f30b6ef880d451cc2644c3f28a0877b4ce997a28893172e5b0e00ed088dba1f90ddc1ba5f614484451a64a9b78a497d39a453f90f5c65bdafebcf5e91c9f8f794b1b14514095056b83d0b4a64fc26de4e660c46699b8f18850f59ead36fd76da40e422103dc16eb03c8e8c15ad83f4c155b8462ea5d50c358c896a299baa8d13e6ca173370db5a2c905e194442543b8da4cca98d84f8a54090e7e432a509ceec02b927052c9ea09c3c6ec70bcddea9b93f4621b5e51c1995a4effcf451a74f584b1f8f96c205d973c2b2b9daa0b43e4c14d3bfb37ae623516c5e695709a9aefe7b1ecbfbe4aa533547cdd0c62882e647cfd03294210a4571a133941c3d53d4d320b61ab553d857134365e8ee9608d2442b6af86e4918eae64b4f002f085c3453c1ac835badab0ca05f2b0a582aaf8dde3dd8cfb2316874a40b66908063d6335a073cd91e363864133870f564bdbddd1adb6804142026559867b11839960ce79503a437b24620c68ef52fbf862501ca93aae7bc98248e75c44394d3249855beffc0f1228d86ee6b419c7e2505cc2ca904aa4eec9348cbd8311168ec3e93216a366b457c1a9e9dc96a3a67bd340267081a3b59aa1a0140eecf166a4130d13182e60493bd9e5630d46a400fc6c732446faf62edde9daaf61e439e9da311f4025709304d1c553cdc8f5d6f1095f39255a881df416de4c1084f157630b8b37a274c034a147cfa4238202a8277c103a7b89f56187c211567af8dd1f00c8cd595607d0d427da8c56702f242c9d86196d183d53d4c1b2a8dfa322fe57db9d3de0a9839818f71f95ff88ad537affa7e6bb455b2bca357e13e584301a74afab96938661ae3e05af0569c3ff918285d0705c46b564db3e5ef1dd876d174d0afaf67180ffef5acd5b024f4313d23bb82205246a6d69c13c37ab3c65feb82443bc42d2f02e12dd30a8e2f88d0f26cedc9b9ee9c7ba4f351bff47397ccb08d168dee41cd1ae35e105a8e2895f2c9afac4c58a435f18f7210133d9f0c319918a210e9177c16da1a4686d28732a05a513caf564ea64a29d60ce129290dc042cb60560439d276fc6ca8543c6e54a7c0285813bc2234ff2ea50cf0ed01e8fec5e52e886122edd67bdb2a29a8055111251332d3cf94cdc95fe65dee4e8d2fe27a759f6da8fffc46eac22bfb2e4c7139fda9c3241713b209bde01df2e3cfb154828b1e29e9fa001349e1e2184bf91d4cdb3119d9d87131ce34928902e5a24cc230ec449deec854b9d2660653a3ad213d187db68f9e50867c815bb0eda6aec3822e5a84cb0b2ba09a98cdc379cb7827a3dc03804c60440075e5d11af56b4f893a313262a99c101d813df199b4881ce3c5896a262f51db9a1641afffc22f0708926437fb72a20d98546e29094a89ebbc4b92e9dfab39902efbc502a54c2692e1f0adfed492a08cb65e7c37d1c44db04acc85a81b99c09ad1b4c571f14a7c873c7b754e5da9b5031cc8cbddd7c76959adeb8edc6dcdfc1914807ec68d59d301eb7678b6d5283bfda099de3be2198eba376dc4913700404a0de23b8993b7b2622038c2e26a1a9131397aed2afd900a45a7d49c4883c545ef5e8de9f50719f3fedbe85e85bd7f5b5510828faa65eb0452187bc85aa6d3fd958162521dd0c6af23b467aa82812f65d12dfb8295c4f7cc71605d5fc81025eb80581f61bd65453137820ba960c50d22cbc677e888ea1202305e737440af8c292fdc2a96e234ceba4c267a35dc73374e8d34d22240df4bd24b6e58e2101b987f7aa5b72cb282e4c74ebf2ae11d71ba900c3bd5fcd0f2537682f7860ab848f84986703b012f0dac92166d2c52e53ea54f71706f53f0e504664835739fe814103b8a786f83d7545017d749ca438ff495d6a81c959442d37605aa0b34a232ff3681021277b9d9de507b509fa8a330162d04b939b1dc7824fa747d9bf22bea8390f8534dc4ef1940fc76f4f1aaffbecfb507c894b9bdb82793bc3f8ec7b3a9a809097e61d37d7d5e198ff66ee0e4e5ac311bb920a3b49d4637c350f4d65a9136f33c6e289812189841463f1784bf4bb193c943c675b39a25fa9b5ca1831e4370b66e03c3f91614619a17c2b0e5f572e2870384050053ee5dd0e81e45940519c87215e395d7dd202655c11f1470425fd459ecf153dfa5e9baee2e1bbd92636f12d8f39365166c0e264972835001246e910da2f5a31d51aec9815b0ddec8a8a0a6c046f8dbf64fb970f120043f19ec4f9b71b42a7c968d75757bbd49641f3f4036dae2705bf47cb507c4504c286a14ac14b3579812444b24548af7fc5c815466bb4b269ac7aefe674207b1e3f22ec76bc29c8c1426f8d6bbd1a3e4a2b70fa53614f4d05cc3a9e3921e0d27dcee8a2ec0b2bf342711ae602a49a15ad51bf3e484b0cd010ad781fe5028bebf36392ab6394c5d862e5f17bb5d5ed085bdb329427fb5ca53d34ca4db46d82d594557e47dabb47e912499c90f5d0d41e6b37e25e57d90d7ec6782bb9acbc53e70a98d20a50fe155ff69188c62a48164551565fc5db219bfd035dc2c0e8cafc1f834e3a0ea430e39d47375608432ed263652a0cc512ad5b8b1537950de920487ca86550205490ff35c627229ce265ef02f28c1fad424cc542bd8a521decc502b536dc5164d2ea0fe3c66f4d7806fab86a0c31a2c3139bd3a34f0dc24186b47d165f38137ab2d84cb5f5ef9c222d53a072a0140b620c9993219b4e11e28408ceac594694e2067b0294f62d05e4b340b64300e659cf7e4d7d2f74d1004625744caae2b3891b39db0d366fc42bfc0d836c8dbb187f019ab01c6a48881df0c2ec780e08a3a5ceabbaffec53dd7460ea67fd44a1963022412dca7dcb9a4065e6d94203312f1ebd9966e729d974b366aa47893dce0827711ce18e1552070680a88a51829f56c0a731731d2b070d087beb0d6d61b575d822890b4a1a8a552618304dcfe32f42b04d6a24e1d1c27e9662056883c045693feb417eafdc1f3fe8340f17ce1b19d8f9c2824cd09eca08855352fedd97439af9f8be19c5d70b9b901626712bddf585e4ee8ee13e45e8b8248061810613de25d1d92aa3105ae8689fd286b7c89c67e89ae856106ec202af66cf53bcc469aa9fa7d97229a5f78c7a3f3dcbd52ef64fc2d78c4b36d92a7ccf1b60b72d4fdf02ecf8a37e06564b57fd33f553a7c94754a24a9422690a2e23d36171eac9207f598bfbf09ca3f9f12fe6b22bc46685bf00080b40ca9a7df012778b4da6bbf4059cdf55692159340b9ec800b6d41cfd80929ac576fb0eb65d2f827b9da83e45ffd08372dba850a288cb02c720593134f637a6b586e54750127315011fa00cffe0990b3623510c3bdf1efe8c6adf0c75481beda062ea817108835491dd39500a311b371aa97d833a928188ada652c2e59f0fa422d08e31e23e448a3c39af58e10a4c84da044164869b74314198df8cda0969b7ecc8a0f978add217b49126fe471bc0d1962ed314f4712b0451e8af4444531c2ebd07001c87305bd2319433089884a1ca399f44c7e1d9bb8b7f4ec2ebf0cbe8cb2941486100608afefad074db99a0ee19aeeb14fbdd030e94ada5d6ebcb3779144549526df204e68591bef793e34808aad896451f0d2ba36564bbb03d6be918147be1251e2f9a825b0d8d0eed27a19a56e6897ba1ddf46919e2df119189ea85479b25221f1018a5e0aab722281c06b734d785c2275b161bff33a6c80f48f3e377343287be0285e0515eda64926624a9585a158ce376ca751c211f0f3add859aa45503daaac675094b8c0dc8debda7599f1aca173059003bc27c3f19577a3b80da7a90aeeaa88d70983c5ef726085da908ac899a8f384d85b5f2679c529828aff2022d472aa625a7459ca1dd6ccad1dd8d1d0af783582e0faefaa45c89ab65ac43393757b453f72ce8c4a2dc622e518bb74d11854191e87167dd8fcdf517ae0390bdfd3fe69f8bc0aba0981caef535b317b182a1e4aa0974aab7ef6963114c55a81b4b042d2fd9aa3d05fac3ae9a2957e56724dabd8f4406e9d4529b2d8a87ae821d68a6ffbd6d53348d27d0d85b750a09e80f5225e0e9680b606e266714298f8920066b5e074d844cbaf9346944b3bc55f5106443ba20652b7260b4626f839554d90d4fc8dc320f4cb30d493aa2d69a9485afdf61518615fb67c7005a9426066a411a98a54198d18617a1c5844be272a26683b5486437803d0773ddbbe6966428701d30a665aa8c812fdf3512f4878bc0f1cca2c72f58e79d05893fd3930a4f6e4c9341f3207227e4261a237d7aaf63574b2442353715174257e3a3b17111debf576ad2a1f57a20290e6fe565626bbd66547b088e6f0062cf0aec293837f14e93885ab0708cccfc6c3047d2fdc82b3f8469350b8b515be93538a2b99df6bf40837366ca315e88815a34a8c3188072238bee5b9a99c26fd868c5cf4c56f3e2cc834f34aca7e6bab8ed7b42037fc3810a98fa2758ba8efd0805aebcd2461ccd186c3bdd4098a2b6e8b81b32725dadd5b431f03bb46755637d6b924ae1a8e03a223bbce04daaa0a1a554382da48f5306ffad94381294ea617b1f631c4752f14de0e511a410c846c821d63333fc59c74f778e1e45f779d81efa4168aae93720c71bac0ea8acc4db5a7731632c3cbb0613be44732d50d46caa4b64b3b8f85c3c35c886d53d74c03d385938fa27e3bb5784a446cae7ce602e19d0c81a7bbec4ea69b3b85ca20406861d244626d25b45d5a263244031628b15f9c45c9b1818403ed1d286da8fe1c476628bdbb1cd5dd439e7445b52353013f29d006a162b9ca453101752701fe8631e4957ffa48fdcbe82627d19adb7735dab0d139149ac6c8b1debdf2439d89d2fedb9139e8f9e23c4ef72b61ee64146833a494821e0bee27971fce313919629fd3b6b39a71a04a598503a1212afca6499d410b57a9b276b093c1efdbf32d9f807d2cb504e0d4abc63c4d93ecfadac7d205041d38a47c2200a46351b0774fae9ad4e04a64c029e2e8740e2d2d87b0c79b7e44fea5b567ba5576ff1b3dcea6e6524055bb4224397297fcc944f857cf24c0fd8bd1e0e100be3c96262d1387a23e55817eddad5f361e2a50b07b0eb5981d39baccb0bb9c8aed01c8751dba31ba4caefb9a240a3ccd6e17c24b673795f7f86e4278ebfd215b67a4138b828d23baf0659ceb62937d138901a8806c4e7865bc9e6c00f2902772628a92c1233d66a086609619791fd9ec78bca4539e0d8705e2eb3bc9a12afa75d4542a888a9588aec511753dd81140fa4a286383a06bfec22572ae0593292b42595f75f0c187c4e760dda8ea01b3f9c77c9da87383997cce57ea9cda6076499b5c0d53a4101843589f4df36e0b4ddc9ba3ad7f61ce4f4385a6c9dcadef1341804a0d45688d3eab6f0684dc405ef95fa6032d0ec521037ef818a97feb939f0fb120ce8128031c0bd7be0598767da5084e2849bf7a89ab6b470c9454df8cba8c03290a0bb7d50e0c49133edf25d3285f409bc949f5ac10d0629bc4f289fb2a7cba783fa754a2e937e643497782493ba046c2a4d8e58628bd44f61a94b5b1597fef8f4939c82d22161efbbeef2bf55991717b28d47a7902a12f46a082c690fced71a3eba5e619cce2f33860ccbb99669c01e5c8e924bac92837b7863c26a1fa911b34f2434c57f9d3364a4a6f8d838d92e2882cd2a61aec45bd25ef35c7d3921e036aa3d5570bf6fb0967500630837933e2abba54aec0c7c2485d76da4c2dd56ac8cc4fd2058db7f04971dec421eb5f421634cd830b422f81c90485144970e10cb7f0acfd9b976b0b21dcda246f5e08ac04df4adaf1aa38706a23e51e27392f1a68cd1763a3a5e2bca6971c2f8c192fb2a92969e8b900afeaf72681bf2d8bac41564b5d27b0c43bcf1cdeae6ceb340d88d67547ead9a5f9b74e5c1b7bf4e4e97ee2e1e884c1a30166b06c70b40c669c87a1c58f818a069b2bb7b59edec4f92083f5566102d894f9b566a99b61423e81615f7992322545b39cea67b0f15224174b6fd8c33113cc5d228b963e696fb62f5f0328f0e866f5e0331d88a9e4a72edbc867b46d7204bafd1641c2cada79be8c010ace0f44025c88def895581b052a7541002bb59bd4fc236c43f1c44f2d81fdc2ca1f2551330d8bb8f1c18d550c85582f7b687786ae6e2c9db93dd4040d0367f49a2ef016a381277af59b16483bc66d5f5393ba77b7a421d5ce10e1de008d44f6db81d2f42f8db2246d8c95451f73fc06b7fb6b8a4b92e4d4a21bda5b1195c7456000c9b0a9e2b1d3a048a8e1560a6dab091f99b521a2069023f0e241e4682064bda991bb0f77ed5f99d319504848d77ba2310aa691861ec106f11c858827639581f410475351e04f7081b800610aa29286b5a1b8506a2a5def0c776af28303c3b580af4b41d846bac9a2d3c540af9be660d208f9834ff4b1ac595f1097fb41e01f939fb7360e962da041330c92b8c32b2eae280784dd6af3f1bcc0693bc8192f15f7fe6ca05ec8973a4f6965ef9fb34b5db85c142c54e409bff0875ae03e7086ea8415753d1229d4cbbf716c40121bb033467df40a8b9e6aa1df784429164e485570241725bd8f85a74ea8af9304ac66cfa0a21749b1a2c3e0c458f54d7323ab6583743e8c62653172296cea19b751a9433ec87c1bd2fe51f2446831aaadc018b78b488d185bd8f4c9a57ff18cfb8a129573384b137f004e7945210d5e620b63fb453493913f597bb3df135287e9914fc287acecfd1f32bd62dcf701d0d4ef52714690680601d6c70232ffe623b4541aed4e8ff2d0b8314f4510bc932c975283c41f55f2d3828797dccf46c4cd85f4549f96c431baac3cbbc3a14efff159f6f703fe2c0e0e1eb918dc60ace3d52147f2b0c181e3d799b2b7bdb0a84c1a1669e5b7ef5b19a898d60302b50225e2a0849068d881bd785263a82b143bd40d85823a860a406187e241e1a09647414adda15a50d050042870a80f8a07b511aa0b8505058de262e28ca24bb540cda1cc50a1501e540fd406547e10752f06fe3c018a1f8a87aa4d40c571bebff180ca87b2404540e9507508ea9e89a570f5425d43bda09e43015f85fa1289c1dbbe0995935f7876273e762e4a47162fa6e75012a86d0805fcfafcd9fc14fd647972818a420950c150e741e9f8406d85ea2b2a21ae4de805288fa2f0525918941d39140d2a6651c052e3e3f71fa3144d4f8d0c09285ba8c728cace06ea01158342855a41996850a80ae700ab0f14695130a90ed41ea82c14050a1a0a5da06893f31a52195350297ca8099429a0fad8889ac529d94a3d2daed9a2e6521d2838c51d2aaf1a51c47214094de9686c795537d4114a200aa5230f699f8162871201d57700156f44f51fbef0fa0ef583c281ca855a150591da878a43b162104546a875216a2cd3ec21f48552825a439da00ca788222e94b5eb855c45fffe3982a67b6d13d6b60c63fcaac30c850a35427181da42e54a517d20ff4f61046a3aa168cf91b74f3c94078a06ea0b956628607bf7d7e00475cf032a07750df51beaf126eefe71b68444861f3a1f2a5dc7ef09ea01f586b28c5140487d37395040844683ba9622318010b4d79266e1475d9c48c825f8778940778aff5ab05fda1f6f9ac49961586cffe91d9697750941d1a9976d649e472f678f4b84957a9b82896d57e108f05343d0aa93072e0238fd264f65e46ae9418a6208af86cb2a2fd91d6454595e46b79505628b2899a36d2921a95eff60d85bbf102842fe34b34b1ce0928929b4ad7693299452f96537e327ae99ced74d378c09409acd35dc8e40f4bf04ba0aeea0ef707de8696be9a1b2747723f68c4e2fa95562ab932a0af82abf9ada5acd14508b52586524820fd858a9d58e77e2ceaa66bc02d7855f975ea138f33a50b59a7d9a0086502a589e6ce9313d98186c91ba4c013e0e9743de1278e9af606193ddc304b249769e81a4edca0668c939ee174e408c46e01f7b97f7f3861eb9b7ef96fdb332d7ac571adedf6569877a0b3feaab99ee6159d0d7bf205010ff8f601c0f4bbc2bf4c9d33941e15b9018b87f2aa897e633fe9a213e64a5672b3c96897f77fd7502113b27bc5e99633096c3b3bfc99faf7f026cfb96db0b70f91d62fe143d07d91fc552fa8365f301ff457af10c97ee7bd82ac23a17dacfb82ecc890ae0d3c78de83242e1c8ca5e53d69ef5427f8730acf22884d89fcfc150eb222c91d57bf934f279a765982ae158383aaf6aefcc3f9f1ce317747d881dc0336d9933ce6b987c0ab038a1895a5cc17d1930d47bf33ee9f2596c90f6c1bfdeaca7382cd93408bfe59436bb43fe2954b6004e255869b6e816edf594a94418ba2012b9425f60e8de90de2ff85c8bb3c7a05403f38b7e13d33649b0583a600818a822959c03232e4b5294c7cb76d4af4757b2814260cf5428f2a02b69dbeb02c7612dc175de7e193e761392bad70640d7263081c0efb9690de0cddcfdb678ac3cbd3f1eb59aaa29f1f14bf7c6ecf72ca749b57710aab8a2cb018f938d42811a5b632d7ef2ec59a5ed1617ec10754d0e75137694a7257717225ba586172c3e095e16e8779f01b6185de9efa075c54e390a0f62960882d1c317ad79a417172e2ba4123624e8a01c123aa9a1017c512509ccaad07031e3a9ed30e613ae36b836e4ada1f02d20d660796b365765b2e88c54d71c0b5a4e1372ad8723bd0a01053fe08e0f15fa6eb1b5c4cdd3018b196f8343e0d0eefe83e69f9b5b7af3b0db79cf4c39c545e2993b09ad506b2ff017e479a4b0b800780b38eb29147c65415caafd551509ad9cba0b8b8dfbe993baec586e8fb88c675e7ef8ec3bd0c4846a12887ef514734e29ba4a447cc4222567405c11908c2debc2bc2fb2a7ca6e33afe27111b3f31533d2a4d22a44e341edadc2073e1aa8ca1393da21127e803c04ba6e8339d6790ad1f857c8d0e9f8853f0e07c5ced64a2ce24082fd590475ec5fe1113f50286effd2da87f8454221e0f5a48d108a3861011e1155662ab3a172db82b7508958e7f2637d585028122cc590805074983b3221b8f6d4faf0bff275bbd7ae69f10cc615a382ce4b26bdc971a8c1ecd3e1335466022e24c4930a3760b26639ff000000000000000000d06c9488b54853324929e74650a18cfddc94924c29a5249c99f8e8e9cc046edb76c3ccc4a7339b51021e0a9c0adb0abc491273a5c4599a8782ec7a0b25e4538d2ef1a02955a5ba436133c4b25fcf9133b743b9423ef2fbadda895787a25ba607cf155284d6e8504a8f49ced65ba803c61ccad9f39384fc18b4f32f87525d6bc8d4d1c313e8408d13c08843f9740cb1c447bc7028b6e8da1233b53ffe9100c61b8ae739ff66caf451a27c6e301a5dc06883d1604341e9a8d0b96f65747c1bfb30d6500c21f8b58defd884b30d0c35145544a791596334f59886428a332dc9a36faa1d0c3494f2b344584999b97e232a80718642dee426f3f5c6b47dc2304331db8eba3491733c19170a3886518672273d39fba48e5c298e1ca9c68e2e6e7491fc23b0021678041a990cb62489c1226f0cc6180ae7e29666621743b174b6e887904fe792c350129a49f689f8a6f980a1ece9e6cd3f09612afe2f9425ccdbaf0619da23c80ba5fcd84d42881c74ca9c2e14247fbe2a353a17cadd1a9e435abb85f2c76d2ded202e7ea1164abf77afbee1e7ed3f0dc0c842314b58b9744f62a1a8293d29bff598d3d95728e6f8d1ad348f39e76e021da84101185628bc84d3389f92644e3a1a34180530aa50021854401853c01b30a45038f1faa5b3986044a10330a0501a9120c3336693a37e85f1847382d180d104a40102184c288c6bf8b8bdcf373d2da1a823f2b2d36492b59312ca49eaa60d9753128a29b5aef3269935730909259f5cb2fe51fbc4f54728260d5a6df544c81e232314726dd58fb236b31815a1383332b94bee640941442868bc9ae43bf135956808a5d4c97145844c61ea238462a56789be9a0f42f9365789f8246c42ea00a19cecfa77d7c5cd42e607255fcf0d370f31ab323e288c14df9c6127a4f7de8342f020c2b7ffe46cd779609dd62c42fedf41a9c524c7ff12b353eba0b879fa3b2e9b54b39d83b2dce74dd73e91356c1c1464aaf5dc5523a24e7c83424eda4e7a8dac9e866d500839fee910bd6a936e0d0a5a795da27ccdd75a1a943d7cc757c924c7a404c60c8aa7348e97dc198bbcc290417947fe4c883cbfe1652c8a22eb33ea8d6d27392c8a41abf72919a1e3e12b8a193cc7f1cdb9a268a9a1275bdf732d6c4559edd647a499f469b62c58513cdf78a52da2f6732a8b5594c3ca685a87bc2a0a62477c44648d888f5251bed6b66c3979d24a0715e51d21ee3a975e86ff9ca2d4d61a5314934e1d8d8ff3d1952945c9e3d5d8b9c60dfa71525c25223984cff5288a41e60ffbd9179a272c8a92d8bcb9769b5a4c23a1287c9e95565d89c1741414a5cc109e630cdfabf7270a5184cc264b989e28f65ca7d9a4c24e946b3ebb42e7f6905e3340169c28bfa8750e49e80c42fc4d14d3eb9ec97a79122fd244e9bfccfe6a53f5842613a512d371125cdde36931519e30fa93b80c71dec34b94737a9c488ca5254a9a23da7ac694da3366518962087a99bcde4409a3a193301a92b8cc2212472321514eaa376386679efb100b593ca298b6db3ee5d3e578fc2c0b4764d1887248fa2298ced942bd04af64c188e2bc68dbb4ab7532f22c165110f115329c0ef197794594738238b9699963049d4494ec3b843a1b53350b441444547864b7534f7a14421687287d6449273d87a4b199e6200b439435688ac86b6a7d924e21cab18377fed54f759005214aea5936aa758cabf70da21c25688fc7f72046ed82288f0c9e25c22410650d1a83682611219a0244b963d0b0758f5a5dea3f144e49191d459766bfcf0f4591a2b5456f63ceee9390451f0a427d967caade93930fe52c1384d89b4827f3288b3d943dc63eaa8d8e2bd7b3d043a98307fd2efaaf93b6180859e4a1986c24e64ea9de9e4d286481878248faab45043d9f9fdca110447ca38b90ed6034f40259d4a1b0317d5cd2e64a99946a167428e9cc93f3742a29f7df350cb298431672289e9f6e24a5ff42458d4341b4f58a8eaaf41a1b0ec5151946e30635318bec0de548117cf2e79f112b7143594cc635486a8de5216d28c920444b9ceaf0e1366c287889df660917df215d43292408992e9243d298aba1647245c4ca8fa7bd4a692826fd9b4ca5cbb798090d052577cf74f966c99c9f6111db9567aa4e6628c8e9904abb04891a94ca50ce93433ecfa5a6e57c32943654ef45cc6b2bd98fa1a4f3a68d31490cd378c550eecc49734eb2c2507e332bf1a9bdcc2c038692aa2c15753257a808fa4241097d4fa64afea9ec78a1987126ecbf886ab4ad0b258fd5892145c60f12e442612e3342f2286a79ed16ca153ad9de7c63f24cb5500e2743e690fa4f7c8c67a190f425a9512e39d6565828c88f5a428b9079e34d5728f9c71c926edf0a85319d519f3b56a1987a4fc6f2f5a850de1ccd36a64f5dcb3e85e28eb8d221b7494d6d49a198394c6a7fb8d4955c140a2ab943de9b8dfcd843a1d41d42769131aafced4f288a8ed2784f7273388d138a3e41db738636cb7c35a1fc22eb3fc9d0082e6226149450f7e76e72732991251443c7d072dab2676b29a1e8263248ccd7501d2649284bd07b2b3d17124a6a456c9fbed09e343a4239e4e8cc976f9935248d50cc744ded4c42c79a5884f255c69cb729e1d1448850d8b8fdd17a3b6ae8d8108a163361b55b4fe46c8450b2f0d3d9eefe84cc6a10caf1c35344497f9ff603a1b0f157d63b3f4eecfca0a4e4697ad81c7b9ba00fca1d35b96ecc7927badc8362ae493a44a4c49884cc83527796d24f2e1e3bce3b2869d2d4a17e5a62d76b63ad0959e8a05c9e2b7b649c503a623928556b4869ddb1daa68483d25d767e2a1d33ce3c3728e44e13ebcf446c50daab2ed5a0aca936fd6ae66dd5840685e0263d5c89998bfc3328b5fd9c6c0b6916322868a7c9254f9f1aeb3a1685bcf623940c2153d6091625c9a2ce474e664892bca224629ed730954152085d51ce5ef3281bfa37bd6e45c1d77dde5d74d0764256944cee4747521df57d15a5082262c99890cc25a58ac2b5c58ef6f0c9e429a5a23c216f509935ab8eca5051da9c43aeff4504dbcc298a29a9f632f727333d99a2fc12a2f87c90b949464b51106152e7954613f519294a573b2354cd9724111b4541ffe81227be24865914059d4f3a97fcc99fa60a4531ac761c892a395ee74051cce6b249f5e9d131689f286ca90c6ba3264f943f8912d2931a5d0b55270a5f56da971193e614e244794fbc73799ef81b929b289f9a50ff97254d1484b838f119b2c9ce7726ca32d741c6d8204c94b347f334959db4a6ed12059db5a4c7109b51b4b244216d7c99a86d53328695286e575a097925343d468962ba99301117a947ef93286d673ee5a13f9228291ff5d7a6ba3d794c248af1736a92ce6422368144e1346e5d98adaf9dfe11e511ba234a6a4a475bc94d23ca3bf955ff5b26e6931125a999e1b47e92a03db988629e7f1e15673288c915512c8dd5eaeb994daf29114509936f4e794c6f428b8842d0f812454e483ad37988b2b798c831e4a4b78a0d51127adb41e4d47da14b8528a7eb2cbe21fbdfb64688622e996e4e8812ea550ec290c3db1444495ca76b14f9a763300351d6b20fc936bec4681240147bc64f45b3d55b847f2868592ddbfffd5092a1c38f6bd57e76ba0fc5d8d1d7a35ac98752c9c875dccd6e2794f6508ea664e9646fa3a48a7a28b66dc91c54ee3902080b657b2f7d27f347a9a4af50d41b9df2ee4d55e268856204dd914a628e0732aa50ea68a5f93a04abcfee4006154a9731f6bcda484d1e7e3185724af3e07fa2661f495228e5c920665a5227cd4f144a923c888db5ad226203855279d5683c9321f5df36d64ef09a03194f38299d4445ef48eb7c1739b890e1848294bb4877ca356453ca6842d9e45e84f249a335694c28e48c915397a95fc4cf128af5d962af9fe2fa2e7270913b6428a1e0b6dbd7b175722f9384e2c8b1f60ba12e194828f8dfc4e4a7beeaba3d4231be22cc8aa88dcbdac69a110a616dbc5d4687a4b57e19452806d531a34a636ff42c110a3159e64b36e14908e5100a5afa7b261e620e0b6d044e08e53c9da29fe4534117a96504a114337fbf7b23201463149dd9fbd54ad62d64fc409b545a721c1d12761f94c4071755ef213abfef4171d6e366e76f25c051e38f97c08bcfb11590c1034308bbd33daae31d02193b2888f0702192449a48173528204307c41f7927d47f0c9d1cb83122f924a149c7d6c61a928183c258aa47cc29e36cc8868c1b9ce52149e8a727bd8f0d3a696ee9399e26b623a306056f3be951627e9798d0a018cc8485071152869c0864cca09ca3ad6ddef674b5be0c19946c73f2c9b7417752320862c4a25465f93bf294e7a4731bc74fc1a210d26812f2672c644caf28d649ec49df917e23c415253fb972ea1de2a64cb6a270c23efb236867693bbe3041b3a258d5326372e3ab285f4a38dd904ed64db4b1a6aa288d382ba5ef95ab2d4945e96436e547e43a881054143db3a751a2428431f9298a9333df670eda6935c914e593e9ff4f6af26ca32f45219ec753373a133e438a52c6a4f2a6b346c958e6c051231b106314a5fa17d9df41f3c4fecf700131449184798dbf9babdcc3afa78218a12824d90f134546e98018a0288611bf11649bae9604ff3a6adc781b3b6a5c8c4fa0262933410962780293325a5fa3a413450d9ae6262608ffcfa6e06f9c71a228e29e93e9d96cacd5c881a3460a3c87da26ca124d4a12125d3dc792043134516c1b21dff374f64ccf8dcff1e973383720ef0231325110d2d44ae874c244295d4daf5ef27dca2ac6250a961569e484740c4b947312eaa3cbd343bb3c41298177b1c30b139c4a14467b6fde87ff8ea31894288668e848e2f37a13639b4449575dff657a0f9b5312e5b534fd37969f35c8cce08201df450e2e341209b5937396dad4a5f718902867c94dbaafc37da7cf178f280975726b4db36dcbc811e594b65154dc89fe3160021c3b6eb88d0a34a2381e93ce8872d810232d3db5b1b688c286fd15cb24c75f551bbb2988a18892e5e66f889f1691451b6b2b78129ca17ff4c2253083188928e669e7188b74de4944442185fe9019646ac82e1e9a20c6210a3acde3f4fbea94766218a2b061155eaabd45e754888255cda98d19ee6e4b8428651031774cf5bc63fd204afe29a27a865c97a9114439c634914c67261005a55cad74abba66df8028ccdcb75e955758a7ff50d8b4d5fa4d6a1b72ed87827deefe303ee23768fb50b0d38ea2c583bc28113e94e7438e7b25267b284eccf6b0fe39e5f5aa07256d67d04927365675e8a88163871eab0e0c34f91c380e8a9187a208b1c137c4c4434925983c99ae73b0fe0ea54f325f8789663fb91d8a3539cb4a3e7d7ac2578772498fd3324a59f6c9e8b07ffa701b4fd99c72bc0eb4418c39944bf39b8c1b3b1a75399433d5f53f3d071349d9585b418e1d37fe0e10230ec5eb0922e8a0737ccc291c8af79ac5477be70dc5d9cb9fb4a7e3b33a3794553c7bcab61bef94b7a154424d4dd21ae6f3cd6c2877ad6fa6db19f5f81a4a3a5ae99470a64b925c0d253995bca756beeb256928ae4849a3f5263bc6d060effd0611a243bad5c65a0d1d6e8318fad75183068d4731ce509c1c9eb47a4b1b6b7fa643c79f8380133c06da0c05913b06cfce13fa4b6a630b10a30cc5dc13ae31cbdc5f572e2a108118644086ed4c11abc6b21863288fa46c913db235be46930231c450fca03cc9e43669b3c5f8851861301a376280a19875538b980ce79a598c2fe8fd39f7b61a236288e18542ce0d4a5eb44f7d73664388d1858208b25a973241a65e1a2813420c2e1426db8690848c636ca128c9f58450597923ae168a16314b2699fa6ee567a13c2288fad0113f9c90898185f2bfbc4f2edd1515150a625ca1b09f648a10913631c90ae54dea195572e7a4d7562106158a3f328acc632d7aa26e63f102b0831853284e2cf92047968bbcce31a45008563a9a8c613a8c56634461f920da39b27162f250c83ab87798f3f0fb13d07533bf9a4fb79a6a410c2714b28676fe992815e1d851c6f608c78d54408c26e03349e25e44b349dbd808b035006810830925d365bebe7fe2f1c55842396b9039da4448096553b15ba7caafe3ec2494df6373482dede82e120ac9f7e4863ccf755ec5384241a39b0e31fb4bbf270688618472262931d8b7c4f6d317a11cbaee733645d2483511ca9b95f619f37bfe1d630ca12841a6bb93e7997fea1a3abaa04183060dc2355875e878410c211464e7b1f68fa33bd544043182502a15d316b64167510db61840287a92c133a99097f5241b6b0681183f2896480d191bd3858ebfb143c7ef50d3b1e3757c0e2f6a7c20860fcaa5673f86ad6763adacc6dd0362f4a0583149bd395e076a0c1e14733e97a4252488bda8c60e37410e1c892dc7ebc001c4d841414f07d1efcb8fe6095e870e8ab7bba5d32a939f5ef8961839286690c9a34d37708cc0e0c0416163ec66ec8736d6748b1837b076fb74c861b3ae172bd8b15dc4b041319fd2522addf64444d4408c1a94d3876a95b8b341445f27108306456ffb9421878da9c8e5c07143045da40462cca0a4ce6b52efeda5e65d04377094802d104306c5511ff1a4f7e7b4e1894571fd33e7d3cb771980018bf2ad77850891bca2607ac7ac5435c8f52b877b9124a52b0a4adf6284e98e1ed367822ecea35694d364370d59ca6f4ec4c6da4100062b0a22d9e7f839264b3ae1f81a3970aca220fd3abe77fcd858c308c050455164aed8f89d23896ed4b8e147472a0c6e2efe5993f451176617b07b74305051fa91be159fc56b94b8c58c2d7805304e51b88ea16cf4887fce39a6280425731619bd96a290b77bfca4eaa4e06394361123c247cc618ca2e8a1333ea4cc31fee86088a2a03dc8f0ab1fe4861b8a620e412e736e444cf76172304081bb85d7665c9b5d842caa53a4494abd82874002140005189f2827ada39b753788d11c1b6b3780e189f28eb7c6b21746270a979bf944ecb01c1327d4fa1c61c2f9a69a0930365110df72da9b5f534ca489a2ade90be19bf1347c32519c24d6264ad660a258ffa52de3682e51ac7cf38eebce6e26b744e146a6bd9aaf1371e64a14d29989d226294c234c89f2c70cf1a9db2628a1348952e70857124513f1f6716277ac452251ce7bd993cf714d6220515cbd121f84e4cf74fa230aa3c53f269145e9fdd6c6c6008623b24f6bf12e21dd884ea49698194adeca086c3473f6349d2406631125191e3f6c5032c697204594c378ca20457f12510c4a83bc4a3d99736c44944fdcd8d698c89fb23d44b1e553d45f49b29c104394633a253f43ee879ab51005dff09adee266377884288415b1eb0927de4236886212d7f6b031e6fa93208a5723628eebe549ef42018c4014c4efb95b8f79a9520244612524159f793e7493fe50f24ca691f2ab2be6f74321473f91f373a89534ea43b97d7377b687f850fe9591f7e1a9b346cf1e4a11f4f6b2d3aef886f5509205ec87a067c1cde082016a042d965038d5baaad9f44f3e460b25a83741b79dfaa43a42c027b44802ca5ef34811b540c21de1adacd33e45455d36e951aa36beded512c42d687184c2891c5edaa565c3de21686184e25984a4f7361b6b29042d8a505897d4a6164428899c84579c690805997e12dddf94bbf942289b92d7a99e339de83e08e5eb5a370993eea4e900a1ec76124c65373f286f0cb934f1a635373e28c87cda6433c6f49c7b0fca1df7646afede9ce3e6813166fd13db58b5d841317ae8504daf6f1f550b1d14b3e93af3be8f98bcd32207c5eafcbb12454c8835c9a1050e4e8b1b944496e68c2ffa3636d3c206e59f139b1a1442ccdd13f2f593ea9d068510c457c8968c90443328ab8b7b8957a5869ab5904151f27bc6d071b102cf91c3d52316c5d6ffa02d5ef2b70550001648e315eae18a72794c3aba9e791e214a0778b4a25c9a62be37590991382b8ae9838dbe8f5651da7025b27b7f90a6af2a4afea75409a575b3d8968a82e9ec6f93e4fccce651519ea47744b8d96a49e25394752dd3e67df227774d51d0197fb54c644b518e297376cce1ae3179a428a9a912bb498daece3d46519acd6532fa9c9031aa1c3c44515ccd13f2a5a594b4cc161980227884a2bcd1f3d47d366da74636d64051d0b32fb279da2b3c3e5190232e25ebe8fe681a17681e9e28468d0f8dc95bf3fda613e58e1f324dc8acf3316d630de917330ef98b2d66cca8c00568d0a84004666c51810828c18313e7b18992ece93d7d0d414bdea01b2528018e1c3b6ee03880f0d044494e6e504da6bcaa461b5bc3231305ed31d96fd4b909cdd9583b868982521f264aec97288be7a89d37abbe5ea7ce12e5d276fb1d73dc7ecd4ae05189c255bd7a9c2c429feb9428468d159f597fafdb9e44f1c4cd9326cfa35466ec2189a2bee6897332ae492b12a5aa8f1eff93a7c63941a298912e4e8f8620db64f288b2698c66b5d5cecfb6230a22875cd5be1a9dcdf13b6e7c238a1db4268dd5bfa6abe4c18872081dd2444444519af65844f9b266b7d364918722caa7933788dce3790df4c123118538b2bfc93c6b723521a2e4aea7eb671a3d43f8100539d31f6915e9a4444be026f01c0ba041c33c0c51dca0b49d8c9a63bd29134043e7e0518852ca8810429e9c10051dc2455c714dcab4e83188d2cabc65c4706dd5bc0d1d5f7851a6a30551daac49134f468c7b3a1005c9a6af96c96ee35b53f0004421acc4e0264c646b2dfd81a433f56fa39d7e28072ddd11f6f37734a1471f4ab551e37aac63e88d657af0a1fc96ed3ea9bb4c8f3d981ecab249e6f9fe858ed1da584b54822f70d8c0000d1a51f0c883071e0a413f83a73253a2b3b32d78dca1d4a34798e89f18e48c76286649901233bca9ac93091e7528fdc40f26dfb36665448762eede71ab6c553de65052fa744edd105f82871c0adb22d2d274849c59138772fe84a441079d5623c3a1dc7299c464c43f177ddfa06e2875fab788695ad63e52f56883d1f06083aea178562e2aee7ef55eaf1aca41882ba17a7624dd260d85492be757276d2478a0a1f4df26449c1a91e52567288cb6199f6c72d649cc3ff030435184070f326fafbaac3294b48e121b6533965e0ac2830c85d3f17666e7226308a9c025d005d6c1630c25ffd898ec43cb4e88184af2eea5d4739ab4b2bbe01186f28695295991a39f3b184a223bd8969cca6c33d282c7178aa39d94b838950a1e5e288ff4db1ccee42e9447ca955e9e12170a9a5bcfedd23d56ad12f0d84249f7876852734e6212c35a28e609b24b4cfa10bef32911061e5928a709134cb743ace7cd081e58287836cf18827fd02b1e0a3cae50ccd8a983498cefb1fb17b9c3c30aa57c6bddf69c1f730e478d4e8547158a19325f37e9b2bad2d9584313c18d2e4870dec61633b6987176b804666c3163061711a0e10a0f2a94accf43b6beec24ae8effe2049fe32e87c7140a3133d6b5e677d5287b48a1d81e5ead2449da1c825128cdd658e97dcca489db58eb3d8107140a419f470c5a211b6b5f5e783ca118da84999cd1ae1d293c1d1e4e28bea88d2422487de0d18452aaa957e9f3df504a2614337a0ea2344b3a093922f058422167eaedb85022b8f736d6140c1e4a28a6e9ba3215b1a247bc4347e2173c9250526a4f5be78917a9791374e12538e7050f2490239426082d993f7e9e8d948711cea308c599347a5f92967e663c88801e43301aee2184244160208cc0e307e7e18382c8f13583b2eb2a6f5080068d1b387a503a21ebd48fd0216cbcc09143013468e8e082070f3c7690998abbd5a5676abaf6040f1d1474d94413795cec22bc1e392846d2259b748c26793a7ae020975be07183bce16103fc80470dce830609028f19947bedd497486ab664ef21839232a58334a9f1496f4e2c0af15469759e20af46038b82ba3211d66495f18a72d0d592faf92a167a571437c4df344eece821a815c56067a52fd4dad6ad793258518c9bb1df7f341d26381c58cb246315a5f1243336236ccba78ad2879c7e2382685ab570c84845d97645df7512a922fb892d200315a54d1b43d638aa84aed9c61a2209649ca2f0a7f936bad9bd7e96618a72965eab9ac9a5da2e4549fcee7c2c999e6c72a428668d2fd95fbd8db55114b34ec9929d4c9b5c43c78ed7a1c345a1872fd9214e0c45797545c5b2d53f54088a9267bb6b52bd938c4f944e4daa66c9caa4b4ee09a321a313c5ce26c4e9532fe7a3c38972eeeef89922ebfbe54d9464ae2ba5a125cedea78962b8c8d52e49467a0c2199284634f5516b3785f21c4c14e35be70d2345e289fe12a54f3a9834114a6bd5b484d1a84a9c0c4a94b755e36e273da22aeb4809c89844327acbbc35364914b3894e939d2a47c5b7912866bec7d4dff8749186c4c9784439baaf9c4c6a1c5ee8f81c8e288df875f11843cc71c24694ca94c584e841c817971145094a8448ea20c52a6c818c45180d13c85044e157eb7f84fe8f24fc2e769c2f3a1105eb7c9afff1076420a274a73e4a0e259aeff5808c43142657ea84cd31d8c6c91045b1eed0fde13986ae10c5091292ec943d218a699faab1d9454b35569063c76940c6208a2554bb36946ad5cbd858134459cc7a52bec9c6fb5c7121231025093a69aac7dc29f4cde1c50e1c3816f05ffc17165042c80084d190f187f26a8b694bef891a8240861fca21f663ada7c9c65a1a0764f4a1dcb71aea45547d436f636d6770c100bd3a3e94abcd3a7c3c899d473fd5309b848c3d94fbc647e418947a282439362a5329cfcd6f1e4a1e326a50fa4c7bcc1b1e0ad7b1219b38896145798752246942abd775104a6587828c58c27f44cc9219ae4339ac871af3accfe02202333a21830ee5e821d14499fa68d2cfa1ec1236bfe3477228891033ee4f5933b888c00c1971287dc7759a54f10abc04df9a900187429029d2fc4c5da3ee0d85d5f7f36042c2a43f2990e886a2e949d9c1c32aa30d2551624e248d314e6c52b798b185090e078e0a882164b0a11469628af3ecd958238f821b38bad0719632d650b6b8523236496b90a1063b8f4cd038a163b332d2503a9df89e99e2428f8865a0a1bce7d565e51f549f0ec080175fa057000d1ae8bdf8023dd2828c33a80c337c69ca940c165a837a5121a30c458fa69fefb236131c0e7c19198a41bc7cc39f79051963286a88fcd990c93677490cc5b08af15b93384979c2508ea7fe4244b88edc5a524106188adaab1512549e8cf47ee18a64e9692f17214950cf5c20c30be5f098180a7098e02fc0c5116474a13c3fea593d7f309d7c1b6b2c830be51824e8b8f9cfd25282822e4a70e331d0851738b8f82f4ed0c58d0cd8d8d1c50668d018828c2d943565e89dc811fbd6d742c17784d8c82a67a1bced2379342665f8804521c64a0dfa77938ac4a41b5de89f83001ce986037418e1e315259b2c26d63aa4f90ddec0b166666656d6bc448de0043a50430041f87045499efed2703a93898f5614945e9b709e4fbcc4a44a4b7cb0a23cab759621fb9d3c11243e5651f220b6d4c87eb24e6a121ccfc1050668d060c4872a2cf1f023547c37c7e7fb48c522f4c3072a8a31d29fe4ef13edf99cb1c51e3e4e51107983be975a26b50c3145d9278f18796bf24968d3f0518ad2f779ce1cc374ac7da428adcca9fc581a84241d4751ecb87e8b24e32db4268a52aa85e9ea1087a2bcd6260745416710719e5f9b4f5c9f28a713329b364fab54b9a30b1d1f9e28c4fdbe2a25b2a5e85127cae915bffbefab1ec25cf8e0443979383bd72bd5f0b109e684d8e40c1f9a287abab5a47ad1514e9289c25dbccecde8757fd70b1f982865bcedb80e0f3a87fd1f97280635df7ef9b9d0ad2d514e1efe22e75d4e5a6963cd8b1a3aba68f43abef00a182a3e2a513aad9d612abfcea48812e5c9dab21d71d45a4d466ef0318982124924d579bdccdc1b388839608b1b14d8517c48a2b061b486a0c3ccb751978bf8884451e62e7ed378fd6f0ac71d0ed0a0f1018962f87e76d5341e3c2c157c8d0e7c3ca210f38fd22de2c40419ae617b870f4794a38692a0da51d279da88b20961591a4d878f59c3000d1a3468106b46944c36e424f304cdee693c7c2ca298eeba337d045d4ac48a2887a44b076f092e32b28928efc8d8aafd183e9308220aaa6f3221ff5a664d8346e2243e0e5194286bd94147ee20794394a4e41043675613423f852066bde94d237cc70f421463470d91ae3e23c71b44b173dc50f5a59f93975bcca80accd862465160c616336a0233b698511298b1c58c8ac08c2d661404666c31a31e30a302119841870f41143ed427b1ec3e0251b68ebd1b47ab1ff80044c96f33c9bf139e35f23f9442cd579c8ddc973ed54037bcc68ef33972e03878372890010accd862060d1a1f7e288f9c70cf08610c7cf4a164d66726e5754e8ece87b279523e21886d112adad8b32f1af750fa4f4a378978be9b255eb80430d0f5a187825232adb3279ef79ef250d29d59c33bad3728f05e5060c61609a04183b7f08187e2ddc7907b21b6dd2259868f3b947428ffb0beff6116da5833a8860ed78180307cd8a1f4125285a8ee9ef05187a248d8bc1009a24331cdf77f64d9cca16032f1afaac23497520e050db24ddfddc3696de35092b1d4ac64cc44f5a48543e1b484b09622c435de50f06acfef644a6370d50d65f3ce3a1139c9a42d8ec2471b8a21ff87eb64b98fa61c6e82cf4114f0c18662f4705f93cbaff1b186d258eee9891bb136dc6a28092fd94fe99943888a031f6928780a4b8d19cf071a0a36ea375537547f9250e0e30ca58d7d8d13845fc6a0f4618662f8a07f23fcc4c61a8be0a30c2549a2f326b9c1b6737005381061c107194adda7c34aacc9d1da1c43c1d389e70b959a3bd41c5f811d1f62c08f3094b366747d7a3f19bb43c10718569d18e487904d6d44051f5f28fbc9ad4f6aca2697ea85d28953dbebd4a4692d4fc886fb39f2c607170a1e23b6c7ae1a4f63de78147c8d1dafe3736ca118d32799f05469fa8ed751820f2d1473734c63cee8230b05299ed9b428759b291bc107160a394993cfe923e85ee80a65f91cf44a18137aedb442b993ffa43e6121844e158aeb26ac740ab11e3752a1601b82688e58af0d6f0ae53cfa547c0e2a843e078e1d3af20220860f2914d3e8124f3bc283a63a078e1a7604f888423144d0b399f49efe708242c9b553ed22249d61351f7c3ca1a0f3348f0af121bfec74c3098514b2b341a7adbdce96195c4460c6058e091f4d2826137152ad7f8ef0c1844c7e0ce2b46b7cf9b184c29bbcd852d5fcb52325a88e8f24942edd3373428ae8a5b961761f482868f568391f1b1bbfd5b88f2394468706e1398c08a5d33770f061e1c308e51caf67a724092d6919e1a308255df2751a3e640e213d840f2294368abcb97f499a111e4239575494a75d0805e9b6baab5d06a1e07b2762789997bc4920944b46e3831a3b9ddbf483e28dd249ac97ec8382e7e01d3eed43fce84141d4da24d9ecc124695a081f3c28ab576a48a183c410bd1d943c66c73c956c78c950071f3a2868b2ce9fbad37ecfcf41615f630691cd4ab65ec40f1c94939daa12a5676a5343287cdca0d8a64d454e58c49f930d8ae136833eb12f9e2658838267fbd125c935f989113f685092a0749c41593be7bfc3ea7777f44306a5d15ee3c9e41e8b72a6534913cc65458605366f4ac8873e5f51b08f49e4f1d23a7ddb15e5b4a5744bae4cab39a71565eb33213b234b92e7ac2897ace6ff6bfeea6b5751acb26a097a661347a48ad27a944f239f6b438ba9287f48c87d276a34b88d8a62b69efb784f9da2fc4944cd1bb4653faba628445711ea5d6d434856298a3978ee9c2cc38faa9614653149666a6eaa1dff5114ebe49909e1c9dcda1345392965d23e84a4a3e52414c510474b5fab4af291014521e98e3a159e7d4ebe4f94aa37753cfff6f8163d513ebffd081e92fecd5f274ade2363f4fbd012838c13e5f694bf494db6f5ae9b28a40c5aef6e4687ec5213c5704244edc964a210264712f142041385bd4822c5cbcac6342f51d08acdb9ba334b14442ab91b46c754b9ac12e59c2eb463788f12857ca9d13d630841077b12c56c262b59ad9244d97f45e3c4903dbe6f244abb9ec1646c2051923199bde43812b7e34794b346335169ab238adea9d7b722d288c25e7ecc1d94850893644449889c203a434971ff1651d02a93abbf321f09514471b45e557bb529fb3b11e5984b5e8828b8cd9daceda7ac390f51ecfb98237e43befd18a2189f21448a4dae2a6121caf5227447d65d4fba2444c14f659ee6607d42bc4194b3d7451c139b31e51144e92c4ef828fddd285a20caa239c7d841ada7d90151ec0d3139bb6e4cbfed0fe5483aae4498f397acfba158ba3fb249c91145a9fa50f698a326e496f950dcdf7872abe3897b6a0f658fb8493c24481222d4434168eab0a7339f87d27dc69f796caf391d1e0ae27952a7d6a8a5ddb943594d5c4834d94cdab21d4adb1a3373c7881f295387928e41d37327e1bbf6a143495d93e6751ed9d8f91c8a177eaef56f1b5fa91c0a39c418c3774772962e0ea5307152d4247caa7f7028c58b48b32927d4b9f7866277958ec8759383ae6e28fce60f622d746c91671b4a651bec7d45d77785d850ceda21869329efa388d75018d16b9d9057437134644c4c235e94b6d250d219de3a57c4b7b10d1aca497ece20e44e3324a6cf501ed1417798cec1ef4e3394ae22c7647a75452775198a1ff393a91a9fdbd4c8505042f674c8eea6be640ce5a0428bf8f36b2d1bc550d8b862399b21f27285a17832b6a74d92038662aa0c6db3611ef5fc4221a7f8eb9cfd59829f5e2826d5fb1add35dd56ec42297f4c9d7a9a885a70a1709d940975de366262885a6ca11ca65eb649432d1484083295fc582a4245b2503a7dadd4dc2b416c140be57c96b6e919dffd3d57289e9a50abcb5135eab542b183cc9f317645d4a20a255ff5ced5daa7662e442da850d2eaea9b4ccd26b75d63c78d47c1db175a4ca1f04979b4f4de5d043f011a344cd0050d1a440b2914f26967ef912703b4884239cdc8a8595433e8b043a1386a999b4f3709711a2568f184c24bca10ab3d7a2a649c50dcb85113639e4836ea26146e9458c9bc2f6a25dc5a30a192418434310f8bb9a8040f46336124108642e16030100ac3a2bc47008313000000081c91c562b160349ce852f70114800359301e3e2e261e241a141418188885a25038180c05034140200c0c05828180403428c69358fe0a0e6bd954a7ce5417bfbeca42f5fb3d84469d6cea1e703eb4583edd96d4822b65dfaec839f06d360f09b8260b195605e452000f051bd3087f926965a015541550049e16b893a1634676c651372f086558d0d117c945671016e971bde5eb5c5c24f3353782fec51dd472a08013bdca0c8447d6c5639300343af29870a32c510338108aba5c0469f78b3da8b5c83bfaa27b083953b2c968be58d50c16563ed0268ac9f3aa85acbf0b5e1212b577914800e97b7f102a6a489b7e894d784d5b33b35de7f78c9c7e0415e65cd67895b28806d3d2e6f3c1c450da97a0b3123f30854ed67d94b06feaad0564413228b6eca0557ebc0022493c1ec110a381bcc833e1317cd74c51433a9917071fa0852643755218c21f7d513524e52736ed026b54e5058ded8481da8adcf8268a5b11942d605e71e591cb3fa071d0feacdd1b206bce45ead52b6ce35e9f24d0bbb59c42c9c24b12be5efd84742f98b2b882d1818d5d6de0f0be78f709553e9bdc8f7c575e48f63492a37e9febbf74a9965e06f052b1a33cbb5d167a4c1e9307043b46953ea5f8d126cc2717e529f40f096b0262f8f6736471ad1e8b702465510cfdd154656912216b473ad63c272bba29065a09607395b7b181a04001b8233a55dd8e88d3ea21241c83619bbb3eab939b5355519f2eb0d95254a34306d3f243fdd091f28b26f69c1a2dc9e626709712aea8ac129008c971888898f1ead2b7e1175013c41936045c331fe8688bd34508e5ac6f213f774cd9a5fb1111d4533fef6746060ca78df488970a81402915cc682b0033de8e8ee1e8f62e588156f124403894995c835e0829214f688e3c6865329cc591f58c9acbb9cfb97fa088ea428cd72b101797a2c19d709d69a3dceb447bb63c0061e6b46c48bf54bc7e47503858a27cf3188889d7ab06195d4f6d90ae82111ad6828efbd1a14971822fb57a67739c673fed00521a6f7748802cf6f8801b98835dc084401125d1eb28a382f47f08cf8f8536d1e3a8f4fbb2c04b712eadc492fe1e7aa34027ecdec9e7dbbc78e09c88f81897c72cb44d43fdf2da5b9103de1d5ef81f47c66995c92bb8045232b23a140f034650df1463d79db59d716423377a8e51535041cf4cf69444ff196015c773422d9177a72494c51ece7fba79e2371a37706e4e9bfe2acaf31339bf90eb41acc9e184767a24db627a7001a56124043c6151888f645b67ac3526288581e8fba8036a975ebf9bc2a39d6446ee5cf5610e23434e9545cf0e001cb747deb2f2eda805de58a088b98a4c63f1778b46cf27a88e91152eba1cb55914fb9ce5cf5325aba2bffca98259ac282870b7d47ba6820f37b15c9c15edc7b6b634a477108e5c405e2d2464773f42d3590fb09765f9deebdaaa7504c232c3db9a3ed62961d3d9f23d24dcf02f26630c8c471e55e59d63a37d63319e45ba1a43bd2a37a453f8f73d13bb58003563199afe85d3b77d8c7635b27d3a726e685c2a18d661d3fc288323a28a77551a72d50a315c03dc400082956fdf3ec4e0600b7ab75f26676fa4e3c809381abf09fd8a8888bd1321088cd24741228452b17016ee654f1276d0bc48137f6b06fa9bc0dc6931d0a0252617d3a6f3d9bbb0d60eca31602beb8de58ea330924339411b3be0844b84846b8469486b258b04267443ada8f67124511d5cd74d0057bae70780cb64135e6b8ca599984823807694ec804881ec41455b5d2f8e29e2669a9802370748b5883d65ca9d6850348b64346ce5c442cd5bdf7b553c94c2075a1b07786263a40d2213bdcd4523fa0982de86aaee6ccb65cd7fa742ed69ddf7ecb338cb18e2645d28c0e4d951d5dbffd134ae221600637aaaeeb94e54121cf067ac0262c51268b7129e8a1be3fa60847eb56667029a77672b738bd123a8f33c9cecaaef56b4cd82fcbb735cce28c3faa5ef4c7c20d25baa674139831323edf8acecabe5098f68ceba864f089b419dea5d0f3857f6782e71e299d0059cde6321cd91cefea50f7ad11ff722107bcdc7ef65fab871c1689bf1a11f94fbbaa6a9818b37342ec94833816e9f6e45e38185652b19ce031d4512337ef20fb19f9728db2ceaae3d681b7dc2d75e01617d76079a53c4a36747ce552bce6aee65e2306801da9286afa7ca665de69c2113378cd294cc3e74a2e5b3b2b726b5e5af7c676f405d6885572073f3f3aa06bcc97f0372ca02e015bbe2a0fcef98e0ef02b0bbe7f7390f1c127772e9001d836349146c5c484e183039515d200a6a0af9256b77ab4b8b58e94c2946dbad850b1d3b3d1ee33d947d15ae5baa57bd07dfc3c49c8f50a9520218c39ba96180db53f85f118bee1a29b055af205e94043bedde055baaf1edc4326cb806d8e9f459d8bfd30786a4091026972036e75222ea31c3f6c7c357c28c8a55c4d670e7e36de18bc990f0e6ec6bc594324581da7beedcdb30a79ceea15b25101fea5b589eeb95dd15af1d0fc85748ddf907b7e55329a7aaff840343c5f6e343a59ec55e71818b28e9d9e7144ea95c12449ce2bc7a5dfa46d1be4920691c6e1e0a8b894558b5cbafee23421e854c3d2ce32dd226be7fe73ac791cd356ea2189fabe9b798a8326825924aa5a74e4b06e2daeb400dd3124a1f1a161efc0d2a3d3d0159b6330f60514e7b910d52fa725c41494001411d838288fa41a1425d437951a062349a0c2d79a8165060a018a1ce00d0b8ae50d850eba0ec507ea04e82c24321813aaf2860a77c845c9692261995a99da0087e071aa77b05c81385f24aa3fa90a9ec5c02aa9b460103d8f78b405040f52beacb27a7031ab2c8863a43012d1c52232743595e600ab30b50ada520a230500f50d626ea16b8d4bed1ee03eb04c5d6a8a0525250fe50660240447aa8b68eda97ba0e4503c52fa2743039891d6f505ea1e077d4b4ace42ac301651cd4dc0008d42c283f941aa874a8a450c65010510328c8507b500a50ce1a940e8cdfd64441ca0c94ab5006011a436a1674cc19662a80ac0b4d2c4449e08a6fb1547b29a87d5011a034504aa18c21d4bd87e2056cf951a5523650891114f0d7681e017c790333710b95b946c2f1bfbf98451b0895875a02a50595b140015f81ed96202361b6f32d7f620b32e64778a4923aa9773ec7625a17bc23ae7c625b6cd1613939fd7aec29387afd9af88c88c6e5eb7683e0eb5c44885f45a753061803beaa2d451e96d00160963a344e0558afff29886b6566680cc6275cb59dfdece2dc65181a5e92c1ff4c2cc604c8ea100f842f103805ce400924bbe411a371292bf55ded7431e3241099151ef9153a9dab0bbc934109a80f8b0d6d0afbcfa39ffde84a01e71c0f398e4b15100d2c857b66a4d852faf24178a3fb82af6131e0507d9e06fd2cf676874924757560faccf5d124aca95534992f4d7ff1269d3d9ee1b8c5b7521baa557093217d23b384a811bb2eb1346fbc93338f8288450e7d8ff5a78a4632d769043b8b554c3ca172b413183d2c76854bd855d6178efce48f2446ff028dd1b0589cdb1216d9383fd07adc23cf723e4140328009709206d740ede8e23e05ce0bd7808304f84d95155ce853e2bfcca39442c281a461e686e85563561be79ea76bb0cfba3478d8899d62e7960580f1eb67e4e5ffdb50e98e863f7aea6c10e86ebff79acf54848f15d24f4d7b94e8cdbc75254d73b38a737eec348ff21498f09eb29aa7bc9e81ed15c9f791375e0f150330aa721393a17f440695605a03cd940c8c123a1f0e1b7af4aada06605551927a92b0e3c71bef85160520728f3b469b3b2823503ff683970b567857ad8a172c394dc645965ca1a50295317c661895917a9cbba47106a05326e8025450995f4e1dcac31774704a8f1fb29b7f0dbd0c4d0660df1b4624595fe888dbe495ef25726c83575dc5647019aec869b8e88e54c90e71cb89eb51536bf64c903e72fb341be2c419961d92a4af9a2ded8263a078263861009091e92e4536323ef669a4c1d2ff7e1a5f9a3ad37dd847e4242a6cb78139cc79bef8ceba461a78cbcf1205e45e7591809bf7c8df24de71c2c3e8bfaf26edd3d987e530070288a07ca432a9a3661908defba10a361c9191c87733fd28020615ba606fdf030da20dfa8e132eb13c6bbac7a5088ab66091f28bc7a1f509cdeec5f1930fd65283e09fd3497acb1011d3e76b2a5757f13e07445555c5f84a88e2ee3bf8a7ca2675313b76b5eef099d90a86e51c5e80e2d76eed4c7b9a09623c201437ea3fe9c06590e07ff5d4d31d133dc23ae75e5bb9353c57042da7b24b7f2ab172df8c3225218522f368a9a01203c3264cd0819cdcd1ee3e52fb78ea1d38e4e4c6ee5337ec8ecfb8d16ec670659c4928bfd77a5512a6ff826dfb9e2a0931e2ee71e110962ae7a7dfdf3bc514c99be1e199baa72455ce54422f4217b09c365060fb686501027cb6a7393322985059c5530d95675f637c75cf57ff8a542eaeb7250980ff60a9bf9e9ec99ee1ba175ad3a54a06753deebc9f5c90312cce96c11058f73e103eebc2ae9fed12199a3d8c837b0af8f09f52a7448441ecd0d6045af618d0afe28118ab11d913753c31ae2de644495c7a344adce28bdd844669896bf42763e9362989a0e435c99074a0acaaba3635afe0a0ab5b578d38d1727e4a84fb485d6f1a71dad4426ce2ad794b1135e5bc77dfaa10792006378af9d2702fa0453c2648849ad014605ccea5ec88644cf600b8ac869736c14bad69c92b6e52f50f02f4d015c54078c779e33ca92fb2ea01396255f1247c6d67b05efa64a65b6d1c75b1afdff02a95552612cdfa0f676ad2938a2e44a630b426916057d068ad012b5d2b0be4eeaeb51366f616414bb5629a7a7cb0a1061a0c937b88b137fc460aa4bcb901d0eead06d5153a678b1c7f7f1f48464c99410ed1ceae1fdc5e89c2c0e08ebae014afbbeea85e5c130df292597b97f32fb36b330150bdaeb7bff4d6c5bca42aa50bb2f7e41d6bb4474c5c602f9799c3662c5f08537b80622129e6b0c945d26c72522c0c75038ec359efaf705e994306d3d4024a2250ebf3470e64c5eabc134265c7253c44075c1bec4a4ba6c1a3334324655016efd55fd183a8c327139affc5e6bd8e8058c3d8fceaf62c10df9cb304e4ce68a91349da47e289c362ba3da24a44c8d4b90f7dcc757c3c62440ae80f3e42069148b48159421c6ff50c7346578e461e3ecd5e6f7952f58f0180ee010f066316dc089ad498c3fb3319744ad3f65d35dc1cff53bde42b338de8a2c97b778fc9341332b18d180df6b85bece3477ab33ad4760bc10f492aa13e690e96447327e9040f32f43a80880d6893ab5a809cb78750094b76f121d7cc796c80c04bc228543650c95d4b602779e73c495b7e7e1ca536d03a3f5c2b61502b0d68ce235ac095045f355027b9999e207e488410c9bc27c6cb4d969ab8758f82b777a07ef3cea46d7f3f74619c90eec8e5bb6138e04cfc60db301fa1e19d8d38ce9c71281621dafbacae048328bfe3b42fd929609d010886ca8e44448348a869921816d6c327902fe9ee47b77522e6d61055d68e47debaf8c49276ee7969b51657642c09ad2028437167bd12a7812cff9e84d4af42acf100269a9197f46876e2b60270eb08ac2cd2d4c9b5832b36bde7c8fdb84cbce153dcd8ccaa639cc1b827935110d8eeabb560e292f5f3fddd3a922262e036ee807c2a0ed551379be4572bbf2e1c366327c6f2a4bb1a718f093d1fe68a38199d80c5d86708167e744ddad475b4dcf8bc1abfa6b38732410413b33b1a127ca88443a2093651ede51877e5b1036d754389dd636138870837415f9e3144aa4a41673d10eb395a8c5a3dd4e5593e88552192737bf1431d9408b3eaafc24562481df46f3e28fdbcdbb260c447cd29cb41a11464b6cabd46c79a4af856311cb42c6579091744db858eed6f3dd92bf80ca91c89823bb6c9e0a69ccfbe05c1ce2abf3a7a20d9af28939c84da4717f267d44630315006a90df3eaab466d3ea3fc6af2aa3557990156851e27a84b81f5492d1b8c9473725ddc279389e4b3f3544af07f292b15a21848ecf071032caf0fda980d4c7452957be1b5ad1eead2accbf7af58fe78e8da4eec48d53443afa743a1a4e41931c83a8e943a358025445e7715358bfb1492d6126c699727abed14c1da2ffa2cb0be510aa749f3ffce16b987d3106e725a8985d1bd8117d42d462ee86bcc99f1f73221159027952c4ece39f16b20eb096a3ea63e9618a02ed4286f7648f70eb5d90dc3b1de37916d2eb1a9d37be25423d2bf7309a8486acff4cab00f8b8857935be19d50ad1ca6a45183a421a480322e75f9444ec1f91e8393fa414751c54a3ce317c6f6b35b7432889336f0c8eabe8429c0b710eaa81aa8a65378401ffa56223293212c6c92d14c4db184793b88f4d7aba1697446b812ddb363f1f2e6250c92026dd0413edfa6270e4af83b71ac130c0439139399f61c6deb279e03e5455ad5393b4b547a4ca17943e986da2ff6b9c48f25404596f8504dbb017cdd31a2e5b87119dd56bcc43576bf32c6cc146fb1bc167174bc9c8ba856e4aa34153148e6bc6ac81828899a0dc91144fe22003c5902dba024ae7d7c1604f42af4ea0c1e0eb7269cea720d9dccb4f5fa826a1d2d5e3b672540067db5d7ee85564cae115d5dc77755804e133a9debb60f04a5355594ec2987283f9689be5db77014c58b6192069faf1196240833d8724735d15752b84ff9e2e3781bf927670836f6c260c20f84dd13ce933585d15f38b884150030c453cb156b983123fd6119a510ac5f3dc7086313cee771e1bcfa66354277db48e7b79d06b160c8e367eb8c25a79d45b4e23ba2d702af64dce54a96da03f0df0d694cfb7983312746480ac2885dfbb418367620553b83b1a548edf72a3c4cc2165a7c5e42448fd8e8ad3823fa110f16eef554e2ad67bf34035c14b98697180eecb0509525c397cd943a9301925c30e01e312cd06b79ddbabf14f19212325e40359172e94a7dd4f741e848d4b51d7034dfab5c7afa5e5ea89e78a614fa49d6ea3c0eb765c8d0a4196fa4d0260dea12305ae88818664dfc664616b70d4fe4470fb75a1b6ed053ad6c812cea2849271fea2868f41bd8ab6bde0762a88ccd1995ba133a910c1a7265cec81fb5e1a470b86a2d4edf06a036f7ff7c033da7f5867042bedd6ab7287b12abcd45b2f365fc929579a857ccb063a813ebb848885d352a169ba1bd037d712c6cfd681d04d27e4c3bc772fab7cb163c627dbf4c5bb1f110334ed474e5c87530033ae6aa8403af5aa75969e17b0041d9b9499ef227380be2aca8787e9554af0febf0ad643f908fd937c89c67583972c7e4a6604c3df4cfd4ec70e59d6c40e4f0c462c4ab159771151cf9e7d4922f650d9f6e69ff5f3620f063de89fa3a8cfa8ba312d1a20ed89484303f42ae14281130d2b7e0b0dabd285d0cef4705e5d000d6d308cd7449aead3da19656444cfcc88a1f492a228310497d989aa8077155fc85254cb2eb5ef475991e8c9db0a1f17fe334143b6c3948a0ffbb22c0b5cab329e08dcd0e4e73929ef310eb3594222b812b32e68ed65d256453073391278df03e092c3dd7d8482ba109cc9828774ec42ce8e959cbf0d5d2b1a5c043d8a6181fe9feb454b37d75914d287549b610cc3b442f699f9b23fe0885e94db9152da204df2ccb95fffd39422aa2e0b371e2e5032becbaad22d464a7c6851749dd9a3c0afac02e61bdfb5b87984adda1f5e4b365c9596926e8c9843438b782792bc34840604c0d0e1bb4708d6280b4769b3b1bdcf1242829d2127a4f0f0663294f56a5fc17ca8b8dc9c2bd41983efbc8ebe7c384651a534689c0eae4462ff6a7c5cec033b8bb0c759d5aa36b7cd17f1aa80255832ff2a210b687ae35e56a5c5aea2e3ef7b438869b9b2f96183e1028541f84ee99891b1bb6f30623e0c27b71b54b06e2daa9d09996bd580b159200dfe1ba0ea76f8d529f9479c0a40babdb63c73a2bd93f57b1e871ba3d628fc6585340269b35741f5e453f3499fcd9eb3ed2da6ef5d8b8a527a2f915b5d93b4a3d8ab861c98482d30d2968066ff5f02045efef8fe493572c1305ec5032fb5d7ab516ef4e98b4bcafb7bda66afa812fd7f37dd82484b022dbdab027163dc896be1e4d5bba5b4685d6d45a1b9e826e06f9c0e68c9e3392c9835c84d95f747a060969c5c8e3fe1977e433be22d888f3c3e3f84c041db2062719cb88f28aeaca38f7eec24a0e6832c99d6293698f3909694d062771d4ad9696ed9bd7bff19f87e5ec27a8964334dba4147f861832dc4f163399797bece6657bf5cff648cc89c66549d9655c901d424d4732a646b9ec1f34c8fc09198c9e0b7798038fa53624e791476f1e04f697ec0b5369f9502030d6ac4e808b7a270840e5ed1dbe8126b3794dd0d011332ab2917ee0da7651001c8f349201e350921317b16a997afa2b20159be1351f6a871a06c70e03dd8a3e3552f030cea7d109f64147d540196fdca24923eac1097b1be5ea5320866f5c1d6af7b88736c5b2ef79b47634b0a3d8dd0c7a66e91648f27a9ea52ba3d1df118c8b91784aa439baf6b04e992ee9c53b694ecb98c59895344e92153453df42366dd06bf1dc4a81fcaa5970a9952b8007d828b3eb9d4f5c4a290922913077892a58eda569b817bc3083e20be56eea391311cb63786cd8e60ed35620ddae74a1f9d3898499b721256fbdc1c911716dbf8e8894024ca4acbf0bdd53a7100851b367176d9134212572f2b730808795f85dfff81edc43eedf73488eab888aa69b2c80fe09233ae611e8c98da9f4e5da05149f4235b1f0b9760a874dfa1c4dc845cafd58a124fed3ba6ee2056cd1e63e3bf1652da892f90c13cf6884b28824ed7c50993c7012a680c84f1678ca60207d266ce72e46d5777f6a3cbeea8e5929707cc9b374fa67763bea8cd5e2ea158db986f9b733abd0e0279d8d64a3e41ae6debc8276009e3d25451da7469838697610860d467de616f503eab8d1339cde5e9df1253ad9b39aedce5eacdd0c3f3748253f8fee99cf5cb8cbccabd2e4164287831acb53f47ca9fdce09d6f1132b661600d6ff664acc3059ff53dd7b108d3d35aa3dd12edb840b146b9fb3826957da4ea62f4a356a25b8a00b03bb300e4b9b3c240ce849a38484d59318f7fefa081e7cab8a7d49030bb9199d0be0a9689ca8dca67eb3acf5c78a29f4a89f41bb804b1ae6431fac5c38914242e27e2d1928c16a166771f702333626f586cd5c28dd586fe2893ac13a5dd3b39130f503f8b42e2f1b8f687a5203d529463a6ab24c11653b3758efcc1ca6ff9ec93298ac243e4c772e99e9a2ae9670d3eef1dd0508c2f7a57158da9cd2cd765882603aba304a56a897ecd3413f27651a6d6f031775f781b4e6b8a58443671455ae933ca6d366905a4132b74fd7281dece40a4f29ba023c425f63c66f1a9524ddf2a8d24c70000ae8046ebe14e8249781e09133459ff8d313de50f8c9bbe9d9461c6edc359b3ba5fca22311fca4f7b07e63cad75849b5d6f35441d87f65494ccb12b547d80eaa3d95a12c391103ab21878d4d3fb524d7c8b8319a04aa6a71083096aa28db68c389440c2446c79fef2574abdddf09fda5fb695aa089fa54e9fe543ae9205952235a8f43b943a9eb22ea5222fd412019e89dbe6f341b021f27b915b4c434097c2ad48e5800553df409fa70db6d68d7bdedb97f978181bde0f7eee4ff873f9d57b66366a35aabb866b0771142d45fa0b3d1a2084afd1cfd3e41407aa0b847a00357c8feb7753b18ccde19639815557fd855ba4e2f200c40029890e7c3bd4f583e1531d4b87ff1cf64d9dcbf95ecf4861cdf1f543e5dd332d7ecb571bc169a87f94ed5a2f2e6e64a7b3f0adfddbf4622c8552c1d064c2f8680839acb3c1dc14eec63ae274372f10833d012d0ecf3904695cc27a846360445e306f41efa13d54c97d643e58988b9afbfb473b37a34133109a5751aff0e61a6406bacce5948e14c939345fd73792305d10472f2f94c7500242183db8d351e883517febb3f87d1fdb62125b53ae78411da60d674a84eff693fb26da8a49343ca4e8430ee3f9735d960fd2024c316a8f8a0178c309b7ad2c46e1201522b80f755cff8990f7909e7686299bda0c0b9631fdc828c588e413228a6838a1b06af9d0e2ea8f3f0c1c41062c6150fc483c26503e4c9e37a85f2ef46c736b3e109b59e07819b4ecc2e04e98cdabb2e39c80743a23c9bc1a4725015538c3e4dc6c320d96731f260cac6ab7845793eeea877ceb25f6f048b81b07f72347e9ab4aab7f3b94ea9726206a116040ab589aea2e1004e6bb8924fcf0a23088441b36688e8e43532043192a808726a4ddd514b409f80a86486999d04c70497ca8cfaa240860d2268c57fcb4e1d4460252044708268916c4a2b5cda6940e46508b7a308dd9a611423d856dd358df5bc495d5c659f8d231359a162401f5ba821d73d2f3eca93f7925d5809204996276581d944b8227423049187885688612669a8f0645791bd579b58b82c0826a425214acb8744aaa9102be455484230edb00d8487211d86713b30548ea02330d690730514d929840ec846fc00d14a6a6b2f17b8e8d3471a4a46dbafb0747ef444fc38b29f06b8de2b5d6b5bcaa2f660d5d3c1bbadc760170570ee716a1b3d1aefe3e7ceab27d89c59b4a6879a062e75950cc32a040b2120b22d4a002ba240fe460fa9f893581077e4dd9a0e857f2c3aee4f758c6ce95f2a01e50740fcc210bac6bd2707beb5bc7ce992ea997b6102a0e6bca6090f45e64e8d1d0e94a8e68d144fd207a5c1121c89eabcf96e71d6d68b156b27c649c039390c64b54898cd9edb46723a9a99586bce57130ad7ef761da60a031d3c4712e54510a93a636b4d5763024bd7b9eb41da8ded64023211d0278c86286a6b3e7537a41ce2cc808d25ebec358e1a4a12720b995913a765803ab78c6a903e230416c919716977ed7c052be77943e036cb5a0ce87610510c30963c5cb03d1fcb967d671bd9e8de716c1aad22819d8846d909173177f489d220471e1f9651d0798c32e89a9e8254bc72c66280d2f06e7c950d34a25cc0ffe269f6da8c5676fa669c039349a303b1d7267bdc6d0c9a925e07ed36e7062922fdc6d766783649d023fa11b253d2c367365303d7b3d45d6fae0029193d0bd45f38d1f771ea5a3753c1fa1c85553b952f6a0061b7dd7873154c93d5d6aa510554b3327b669e159c6d39ec0b87e03bbd73c9d0c6356f8c4479a0e86e855805a5dbe4aa959497c9b2da443e5793e4fa216a82c94b5287c933bd2d71ba5fc12c6c1873d439ad388f93e32b548172907f5d9567faa074e87d691333a7aac150ecdec966861735ce3495a35295de2a1f0cc10fd0c2f21ba23e916ca574ef03d6750f481759fd8635229abc78a53e00512d6f8b19b4c14d52ff8a635771fefc9b1a5b4324a0197eea364c84ce80ee677d29b33c0b29c051a69a64c0159f4f6504e83642a944ca7cdf8b59b286d1b455216608e377001826b28809604c778ae67861c029b9ba29e3220b6fc933f8b61f2b42e609378be7c8881c634da6f0a94e4934e4f07c29103f210fbbfb0bf421bdef23aea2063bcac2130bb79aea770883ab59a31f5dc3853c46932e0a995008df33e442d412890bd7bb9f325e804bfee3f895316219b068bd2b9a98a48870571e8b280d559b98f65aa19dd3a420166da41dfa0919153b8083a1391a8420ecbc050e05be252d7969e234be3ef3b3d8bc656c9a491ea635ef145a3998bec370c6348a3557a5e77c90b158659f45e629eebb3c37cc04d53d9ba1b883ca432981d28232864a03150c250be507ca0c51b46f7eabc7045436940aa814a87c04aa6cc2ee286fa836503fa8aba170a048a16ea144403942a50d057caba0bea8030a502a7c1149c374433419477d234248bc6950e443f5a05440d941294285862a849208a505a50c95fc4089e0e31c39a04399827a830201c500b50be5024a071505a3eaa5cc0fa5230a1416d4978dca96a90231d40594c380020aaeb724d18ad281d5c23f016be3f8a1b0a138c628a007111df9131dd41128860418e1379494a284a4c287ea835285b28652840a0955084a9a516622d4025f00d597878291464d818ebc1929541dd4512818a7229260a29a9e00d53da8384e2f131631142f54e85039508a50d628d43d247d7917a09883aa4319a628604773f8a7d9035c010ad5e94363762baa27b51e2a1e5429943594289443a86441f5b1434d3f6a0a97180a83850d72b5a02650fea1ecc48a0aa30900090750e282d22121b8ad1005751e8a4ba174105cc63578bc8535fdacc5025eab84140c3540b0a6d0013c0c3c0c3c0c3c0cbcc6f05b5f6f81b59392dc721a74b7ed8a94924c292561435fe05d23ead62463f73bdf71c68110201e0cb80bfa0bc97fa79be1a42f8dc860b2d1ed2b5a86b3264fcd3e8b974d3e19ce369782eceacba7bf8fe16825e98d50fa6e449e2162485fa5bac6dd155b102161285abeddbfb9ad30184c269f5232af5f4857e5f6939b4d2c2f94c2dac4a86cf90d9d694248174ea28c1263922c29e9b3ee21840b0711bff2d2b6a5ba30846ce1a4ca72a689ae7922cf30d00d432b8468e1982989622af46757eb2c9c622c9d2566e577cd2a160ea2d4851c1367cd464342c81592a4512b292579ffd4e3878f045840040908e3478e629408b1c2419bb4194265fbabe6881052053042a87012b7d6d03e313aebc7a195f8248002092041c814701022853bf3a6eb9578400537524814ac98e5e11aab5b6367acaa58bd65a324f1fde4c7831028f8f019b48e1b31429e808010278084348101214c28214bf8e28ba680129210820405841ce1460e1e12100931828c9022d00821c248c8107ab80c7aecb0c1488810181012041f21401009f9418c101fdce8e121317ab18018bcf001126317213174418018b938400c5cc820d9f1052a408c5b0c20862d72f0904009ba07181250408c5a1820062d6ee4e02181ee0186040e10631647bbad5cb2ecd9fadeb238651a7162d63cc7e2a4d3c258c91963c0e26ce2a85dd4184e9e28478c579c644d759acee4d20a1d63b8e2787542dd89ca1373df569c362669c2ccce8a83ab858aab4c5ac531492ffdb9b157c53156bbc5743da7e22043f869bd1c15a7578d13b77c3fc5e954ac28fd2e99e26c2a66d5cde9371697e238278ec6f1b60b594a48716ead54423729e94aec519cf26aed9d8512511cd3e9cd1675d22aa81f8af3584a3e9e73725f30018541479a74820cb24f9c5d43a89a0df54c33f2c4e94c4c66269309a71a76e2284af40619cf2ac5b93971343516944c335dbbd9264ea997745965f7f14c6be2f8dfd597764f993856d6b5cc06cb73318c899345dd36179d26f478095b7dd3e86b7c2d718a2b61dab792124ee89538be89bdfb26b4a97f4b89633e5142c512f39b4ae7244ef96b848c3e394a968c248e966553d4204fda562812c7d3786d92ca42e2243e4e6fb691298ea53ce2204beeb5263994362d1d717eed50c285b71149e184d214e38411c79497517732b58893a4cedd546f58499122ce76f1aeab635252d29888630561ef279fb6681b1171d07dd2cd4f450f71b2cefd929f218e3164b0dc621b94743ec728c431c5ecaab132f680188438899c4b7e5d159a20c6208e594ae50ce5215fa617c4612f25bd52ea4b52720ac479fe0495c46c26409cbb7298505171d6c4f01fceae5942bacc96522aeb8793eed64c96d46aa850fb700c7629860fe750275b8a26e5d9d67b386e589c93517484c9a41e4e2a9c60a7c433d3da25461e0ee3ffa6577b6522d7dd430c3c1c4676952499b60b5dd522c61dce273f2ca9fba853326787834c52b42fc4a8c3319b6815aee45da9aabd10830ea7386aec92bc20da439ec3d1467583560995fa03a00d31e470de94ea2ee50bca9225d910230e470f93a127bb87c3c1a415a5b29a94ad4fe80da795bb64622ead777956371c4be3b987758f146dbf20461b4e3e2a53b37a5e75e513c460c3f92ae5acf39c57edae87d760c78d1d492d0931d6a08652afeff960359c8434fbfe531bb72b6ff0e805428c341c46259dddf9278ee3068f198081f40731d070fad4bb3452630c9bec1d5f84d13cce4a11629ce158f366c23365bc97fe7610c30c27db9069aa7b8e448c321cb3e799585f794cb8261331c8e0af066d0de78d1d694d8c319c046da2857a876edf3b0124b84002ce06001331c470b874b14b3ec19287186138552593a647e479bd976687186038c8d025fe626234a14e627ce128268516392969ceb9d010c30b87d33841975e4d196274e1a4498d88f4326f0b31b870d030cbce71530b31b67036f1f99aa6a74d430c2d9cd77c349f676d5a871e7018b48e8a91856312f57a7f2fc1c231a8a66416bee9fe5fe124948c68856ddbeb5638ca6617a159d7345d5c8573e5bf91bf6432c9cb5622061596cf12723f362b448c299c84675a9835bf4dfbc690c249849d9b1816339f92640d31a280d614ec4b5e06f1e9210614ce59ca4f58e62cd1fa7fc2498a124f480dd5a2bd13ceaa5d6b92b7dc750835e1a06dedb741c684e37d779fd85c9770aa24631096a4aa84532a1de369eac46f3c09a74db94db0cb7a75a341c229cca26d769897994738d655fef99fcd10151ae198d2a4cfb4d9d1722ac2d984d69215a32849c4478493a0ed94145eb5a4686c0827e95f2dc6f7c60c324238c85ef79d2649064d55108e1936be090b7217e3403826f1bd27c9f60f0e3265e46eeede9c148fe183ca5e3c77f9aab5799402082f4e729acc9736c9164ae9777152496ccdbfce33b3a78b5358d5b12433a836f15c1cd37b8392b44dd096797403082e8ebeb5e6961f2aa993bfc5f1f40926be456f8b539c3eb9c45a7e68cf6b71bc78d7bfdbfda3448816c71ca334bf8c7c9d209ac5512c9724bd085516272967d9f588b324f4589c32883349e7469fda0d8b63cdb5aae889d93df12b4e95f184678d1613d3bae2cef8d872d5d85a59aeebdbcfb7e76266405a710ad6fe75d29fb251a2ac48f6f3f452aeaee27cad954a529e497c9351c571dfa22649cc184f381527a1d552c508b159e3898ae3a7c656d2379792e814c73d395bcf32a3af5d60f030c5d9b64d10b17052c69e4a71de1df3d16eba72fa09290e2747baab998cc8dda338b8f56ce511252dc65f14c751e12767eaa138898cb14409c2773f934071526205b9a27c840e25c9270e5baeaf27d29497497be2604267d466131b4ddc4e1c542cc1cc349a8e4b27278ea3826a96d3d1e6336ee29c3199aa647a4426dd69e2681657b4786fbc90c9c461635cedef6224d02b8060e268635d7a4c1263b3e41b7a00b9c4f14b9c929672c5e8bdc401c41265032b71ae5831eebddbec2a6b6f00a1c4f1bff4d9aa6e8c26791287d98ebd91797d4b84247150666661b32363852f12270da122e52449302959402071be2f495e374994411e71d4f233a5e56a471cbe4756b0942985adb411892c0061c449459c57e650979466f0700a208b3865f0181d1126e8eeaf885338ad758d9a2d8971420790441c4d70f14b3f79a71d4a72f8d091a30706920d20883808bd5df3cb3fc8218e23e3c79a0ab3a7b3151e400c71f2db92e493d4840029c4d164fd7e67bc8a6b4a429c3745c989b831102083389790d77139d478be6b750710419c84be4aa6bd248daa012410a7d6d85751c28710d98100e2245f294d0d5d725394fc87a3b7a9abf7d869fcfc70f6df13ccf4d945bdf8dbbfe03e9c44598bfa7697f47c38add5c667d5c76b88f6700c6f7f299c708db1b21e0eea728c30f5b2b1b498879377f6cba6a9af4a1a1ece7beb279f92e3d3b45f209443c70d3602c81d8e16c25212f4891d0edb25a6bd82894100a9c3f976a3cd5e4c62c4bf7438892993924d9d26d994650ec7d4b5f946bc3a0091c3c9fe540a96a4f649d6e700240ec7932a645fc5b23fb9843f0081c3515bcb92e8f9fa746b3da0821b3b0079c3d94a3a719f11bae1381e2a6a87de32937d1b80b4e158e9424e1811b92909b2e114e37b9a6c25a625295c0390359cc4c57b11a7b34e124f7e80d10135eca6f2e4cb5269f20490341c4c8f9ed7bc98f66ddf00040d273942d8898ed228270d72066de3db9dc5cf2dd9042a903c691e2308c337a00410331c84c7bc8a9ecf3a35ef0029c349d29ab22973a9eb527fc1c3072064386e4c3d791ab3e5d77623b5f718c349dc52122b9e944426b5180eefa6ed301cedc4932d19e3fb26088663c9ebbba2fa4c73a69419902f7816fed6359cee85830927a5531f2ac48c8c43eb46fff8c25316902e5402e182816cc1d11b64cb584ab551440783065e81c44d50e660d0c0d38dc46bc03940b4808fda92b3842a1906c9c2a9e46bfca7664a25c6b1809fcc71a69604728593e0a519b4f2424eb641ac70709533cdbe79f4d703a9c2396d742931fa52695e3300a1c2397ee46e7f6f502a420399020552007b0091c2514de5a690a7329c64392880440141a060409e709c1f7dde279338e1e0a7cee6525ab7a46fc2b954fcf74dd9d43261c241b96b7d084f79d5d012cefb6f32bb353d474a3899b423df5a324cb2937012fdb891800e256e7884c349298949539794a5ad11ce76654289396611ce166efc4db60e8408e70d9592bbe8af30b54086704c5121b5dcd2a6280b88100e6f266727678bb4b08004e1e8699a4c5d5b5e250b0408e7329584d79eb3206f3b3380fce0a04d4fd8207bda47fa2b06101fd00001bd385c9cb8972c3de547f6e6b812451c5db64e5bf6e433f1449c479a8615257f051911228e3a5a53f3cc87f833ffbf8e3a0d71ec4bb12431d5f75bc58538989dce11258d96fb09713295cb729294549ecc200edf6ef194d8db4de30ae2941aff43a8a7e6d10371aa98de4369d025ff0c88739d74b751724913da3f68629d3a49bdaa7e2886d5a657cbf7e1342fdacbcce46eb10d1f8e2925e1f3196aa5fe3d1c4ea6c61e3131edaf87e3688ba25483998763cab76ab78887932475785aa6508289e21d4e62ee9e564ac45e06ed7012372aa8da8a52725f8753b95ad269321d0e9b64d51ce62735c8cce12493a4319f0e392da21c0ef795fd041f5d1c4e3197b525fe068753d41935694e2cf4b6371c2fb8c9b34135371cee82dbbc08f9734a6d386d0c1d42c492a706b1e1649ae7325ab3069364a574e58c51c349dc4ed1cda116734cc3418d0932a90d7ab91f1ace35b22f09d1a5ffac820118c875c6198ebba9f5ccb337c329df7f4b884a97e118c4f6f649db910dd7fc98418693fcc8df8653d2c8e833c6707093c41479f512934c22017f31430c872dd12fa47d29f35112868392849083e1706572c633e8d433f1174ef396c2d3d694a8fc7ae1bca2365b8c5d0b16db2e9cf2a9ab4b422c55902b170e6e972eff686c9b50b7704c8269ee93168e16fee40cbae6224a260bc7cb2dd6214c927a418985c3e593335e32c12b1c55533766fed878a1cdb0c2f14ec574976a6e4f4a8141034f4f727c318224c7173e24806346154e31fc44554aa3066650e1664c6186148e29cc25f78d8ac0868da4079a1185b20185b231e309c7a4abc12a2939e93489138e552233eda698269cc475be754be35e306fcc60c2d1334cf019bdafde2ce124376fc96a9e2be124eead28392b9b84d389595d7a47c324bd41c2d16445171944a7db664738da95cd6f9413e17de50c239ce4ff2e0dd7746bb22fc269fe74f8879c6710e1e4a7f263394ef8693386700adbd02a53ed3591cd10c2d12e2f6566bbc8309719cc08c229fb09af3cb9b30b6a201ced649254d0e6aba1a91b61f4f851b53ae3076dc6b4ada6cb4478337c702ea1deb4c9cef4b1931c327a71346da2867eff9c20c33090c18b4dc69e981e9f1dcf711785bf5cb770b151e2d01dc8d0c531a9660c2256ca44462e7420031746c62d8c0c5b9c8c5a60d2e250c62c5226e625597119b238858eee924d10df202316078bb2110519af281b26c31529a3152a831547d1cc71528f7e152725937057796230495a1507fdeda644349b12a9a7e27c52c51e2b99d4cf4942c5316d125de2c4985d3e39c5f1c24a4e97d027e90e99e2fc35325b55caa538dc76e90be51d26e94b8a9324f5c5dc33a32f532563146793929c3c5f2af5e4541447399dad8b86e2248c49298ee93f1d9501c5494f121372dffcc469c48d98913fca2fc913c7f0980b4254ca2515ecc4e9af644b5757d97a3a4e9cbe9454116d2a93691327155df45d4eae924913c7507af32f9864e2ac95ed7275895999990c4c1c6f446c3cc4c4a185a5f8031997389ed2ff291b543a51464947dff01e8dd22a09322c71d618476dc8d69b49171c5a3f7a64fde8b183c70d9c03322aa1051994388e4962853f2b41935c9ac4e13ff325f9e2a5d03a91c4a90495c932e990d192a0489ca410a1f1bf67489c9452578dbf5fbadf234ea2b22993a41819c7a28e38b59c1c952ac34daa1b71cc67a91b97648b6d9011e7101944a9ec4a614f16714a6542362e284910428a38bbfb693e593242454bc449989b5dea4d515d4abc06250719883806693adef24544e8ff10474db9e37653dc1087edaad07241fea6bf1027357fabe1cc4d893213e260a14b87c512467a9b83382541772788d3c66cd27f572aa36210901188a39ae41a535e5866d2017198f54cbf1a4b7f3805d9272a68f358bba81f4e524b6e58fff5ba12fb7050b294108bc9b23e3b3e1c4c12425cee893723af0bc8d8c3694399bc2a9333a3f87a38a696924fce8b537f277938a9db0a8def0c0fa75c67b6e57adfe1f8f67e9a5b254469db0ec7a89dfa9be2f11bd37538a79e5ce267291d4eb1194f12abbf94ace41c4e31eab59b698cf2262787a3ba995cb59652ba6d47461c50061c4e824ce71a9774df4b7ec331c9bea1728492a4d8991b8ed14c0a4a5e5fdb4b52340b32da8036d20532d870d25e524adab2bed69decf86204366cf8f8b1e30b1f3778a840c61a8eb92a251544e5f42c150e2d1d3b6ec860c70e1c377c80e14387fbd0b12399840c359cc258d9e6fd592979e330e8fa808c341cf44e973c272668a6ebd070ee102a46334b5972fc673848b14b16c2c424c9d99be164f28be9df6c327aef5886d36af2906b3227c359d6d45d478d9ed2f8184e6b1532ca26795f3a17c3e1f47fc99ff7cbc5a4301c4cbeec92d461d27d4e301ce47e59da52b96a495f38265db282ddafe8da0b276119e4a209b961f6eac241954aae29ed840b47b731e517cedec4bc6fe1985d56e4259d7a61624e0bc7d8f1dda64a948593943e7454a6b85cab61e17462f293929daf2961c62b9c7554efaebd6b676885f308cfd85eda648d96aec251535299d46bc68894a9709aeb6d0dba4e924ecf9bc231ae86d3545b0a27194fbf043db992dc71144e77e2d8bb26bde94350389a92b25a255fc2fbf484b359c8aea5efe7bc4e385aca373ac2849b707c53e97f847956d2960907a1922083bc25652ce1284226c164f4d2719570b2d4a7c418f1b414a449385ec7c9a1ff19241c63e6534a5ad125fc954738ee29691aee5b1d328c70eefd33496fbc5885b008a7169da765d46e8944388949afa4a45e1cc2e1e45d11b27df46a988470ac7539557f15a4ed1784b3bdfd2949e597328070ca3c61c359bff89bfce0985dfce4132a26c307a734aaa46f10ba92201bf6e27cb1f259acb14c6ae6c5c14cae6930fdaab6dd8d905d9c4cdc13970bca4bdcd2c5e1d4adeb069d35e9e72259d1543c0d42c4c541b6e4ac84b2d87ddfe2544adff908712d25a6cf16a754f2eacfb82585905a9ce40d268f494963c8b3d0c27fb714b6724284100ead1da97410328b624c28492a953be38d1da954b11c84c8e26c72536b8cdfb26bca589cc24bc95fe1f3f6bf344584c0e2244b4b98ed0a72ba24f942c82b4e552709524bace8ff12571cc65448d3d2f217c3a815c7cb52f2b76fcd0c61c549ccaa262adcc824a39219b28a739ec67f93bbcc4e5b9921aa38a9a0bee4528e35cd300e2d1dee63352415c7d438b732afd15031838a932555392ff3a8644a1e0b21a7b010539cd3b26bd29953d1245548294e5a5349766d416819178756a14059084248512a339527854771badabd51496b4f0811c5317ea549f1c2fbf7b9a1b044d5baecd5630801c5b93ceb6bf5d49229dd270edf77961574e589536c137599c5d489538956fc53279a6442ca89a3b6d8a7a816b5b0ea268e6a66caada47665dcef423471d0e7175a63a811777309219938fcc5322506952f5bd26500130725e88ba2366e54125726845ce2e4bb659ac184b3f091250e3289997c949efa7e4d48250edaaa4f977c499438569910359b439b0cf9240eb23646adcd9026b64512c79899652ec5ef955145e25871bd5a6b21deda42e2a84955cb44dd421e710ca7929f12375f100d0a71c4b124c9ad47a9346faf6821a411674b932e8e1e95c41425440823421671122a1a4b5b4af5214411e7adbc55d2b88969e14fc44169af495d51d25cf21171589d5359a93728fd0f714c3eeea5fcadb75437c4f9d72fe8bb90a352c60b7112e51973fc5eab4c27c4595d5390fdd1204ec2289d494f4a75b926419cc74d98d07da7c5bd241027252f3bf52f0810a79196ad77d2fd87530575b5184ed80fe7f76fb90f47113fa74bd2a499277c38c929b197f12fcfc5ba879358527cdd6e3e1199eae1142a2649de93bdd2a7260f271753a92441dbc53a251e8e9a19e3d5fdfb2ebb3b9c72494bc147ff0429dae1a44f87e9b2a4245183753886d99467635aef53293a9c32689d92db672b49a9399cfa4d89b09cdf9b22391cfbd2ef266d1ccead9a5469ca2428d7e070cedfd7b5b0142784bce1f45eba9b544c97e243881b4e82d62ec1d2a6d686639b924d5852ed26ad091b0e525499182fe9a438416b38e6d64d6ee2494a4cbeaae1dc2595c98c27e88cc6a4e1e0597ae9ef2fc96329888653aa39f155dbb476f567386d6712b62e0b6f05914e1ccd7c2e5c2c498416dd8df2c1893ac36c55860d63a94fb693566311d9c4b1d65489dcd17a38da0102535527a2895338b9cf8b31eef59c5420928993ca1c716d62161367d319d467cacb23c3e5126c65c8e68a19162aa7556dd2a6d9c9124b1cec4fce64492cc38445011b3652258e490ca7f72e99b0b8900825ce77b2fb42a725a5fb9fc431096a7179fdbf748924715216b56263fcd6201289a3cd997adb4a0b4a5e20711c79aa95d1ca45c99b720491471c6b2b5b9d6ec54ba3b76103dd487484e1a30f0f228e38c9ab5f3ae496cca87a3b0e09ecb840a98248238e61525ffeb9328f13144109b606960511469c2449d4bb933fa5164cb420b28873ec95d0d64c1763fc611051c4b1ba553657dcba1126151049c45934fbe64a7ea54e6d2c20828853e6492b49677583c8214e3b5ae285acf01c211331c429ddc9209377dc8de614d83d8814e2ec7be286d037a6760c941221c44950d56e620eed7731228338ba6f7f49aafb640c2311411c932513436a59d81f51200ed23de544c5cc934407045b61a52d17e3729689e3bdeba34933227f28beba6c6ce74609227e385da637adda76d5ab44fa70fa1e51e93474113e1cc6362bb28783e6d5a062f82da2877389de25624f588d9f2092877328b1fb6ab4365db33d7a8041011ffe238c0988e0e1a01f3bdfae5da20681c1e307181f10b983a3779390268678276207e35296c668bc752671574609e1e9e996481d2c5f20428783ae590927f3d75e12cde1f0a72797ef5ec92eaf1c4ee9cd3eb63e2e25492c0ea7144aca63a5750944e07078d7d217cbed1e3dfa46183c76f4f8612220f286b3ac25ed6f69f9d62ca9c70e1b140788b8e15c9782dedc7ba5b2323a7270253974ec9881ebe8c0f2f1c881acda70740b534acca14a7b8838b44c8e40840d27b1a0b2d724656b26470191351c4ee48fb89d520d65c3072269386c4a828db69ff9fb2893208286636c5812f742346f7f86a36c4c19d24d2c647d331c94fc6399f57b198e7a965f4e4e86835d9a25a9f263a804fdde8be1342336afcb9b84e118362c659aa4d45d9380e198ed929825e78299fe0b471116f3a88b5b5298c80b2739b3ef56564cf242d485834aaa7d440815e35c10170e72426feb890d426cbd8553ee8b15ed9fb5708ecfbea0794de63ecec2d9aa66849e60318ebd583846edbdd826c65ce1609b4f37c64726edad70d20e533209919ea7548593a024373bc9eead4ca870fa0ad2ef24dd14cef3169392243d37574ae19479a34bc5142d13330a0811c264b6781a14ce579ba54ef31282c8138efb654297249fcc31613078b4e330c3e1030c1ea82307094c9940c409e7abb85ad3133a4396dc40a40965224cc0945ce9c4af33e1b75b104496000511258824010e224838fe9fd4924da92247388cee0a7e3165a5988d88114e25ac6b30b12f7fa62852848368da28b123b3f97c224438e705a971d4ca6d6e26328493b97768e6b4cefb45847092826f0af55d99428804e15057494f4efd0008420408c778e9deae528ed0634283c80f4e7d79624c983b51fa7407111f9c2fa539d77eadec73eac579e3d2f6976cfe579817a77c9b2b31a8d40f25ba8b838a5bbf76caa28b735b66abf8a726f58cb93889fbee33b619cf81062eb8b1ed0d6255738b63d46b51aa81862d0e7b29f32fc5d5d6383b8d5a1c4c499746e74b7068dd585a1c3ca46d4cf174f6aa2da0318bea2efba6218ba357d4dca7a6624b5e38b46271382162297be9607110aa275d997768fe8d1568bce294d557d46778880d255a051aae385edfea6dedcce8f85a71d06f9a7e32bfc6c5c23458711619724f6d2fda896cd6061aab388bc56f984a17a67e0160113454714e3b29a7c40fcd82462a4ec25ecab198beed290a12408104742001141881bb0592177b050d541cfc2f883db595439da4536c592263929220ed030d539cddcdb2ff746fe8f94a021aa5389f9c10a1ae46a87b6b1240820b981d3448913446714cf37679936c553f12c5497e2d2fedf586109a509c36599bb809e5795296a7400314a79823456d18a9993727051a9f38988ccb6d6936186878e2d83f9e498456acd3249d38a9682253d85bbb40831327cd944569eeaf8fcb8b011a9b38888b266a1976f4d4a46968e22045f3b2ab48bbd584804626ce16ec357ebe9c65c5e0d0bac2b32a5ca08189537b49216b62c25bf6df40e312a796d5caa412c6173c78488002c8061a9638b6c91e27a54a57e224e8931694e53e49bb4f89938cde922e9dc885e627716ebf95134ca95152ee25718a17d7942c3312e790532657c6c40e1d1247dd5259a2660559a3479c5f56534cfe3b49aeee8836f4ac8d3849b99ac94b50aade74461c6553a9249c588289992fe278faaa64f4bbba542be23896ab25369a88c3d68ee90a27c7a69884889367300de2ea748893123b756fccf42d9718e2ec7f52bac8545288832cbdde259445d59c10671117b48d88d1208e29e98d26fbdb823849c2d41f88f3ceb82929693625981810c73f532658b75cdc10ffe19c7da546a858daf4cc7e38e6926485d50c2a73e63e1c4b7ddea859c698cc7c3889b9ca7f438c864e790fa7b393176652e4a48ef570122be97ca5c67938566fbe1326b53f2f8c87934cd9c43431ad294169ef70d81846ce6f543135ad1d8e9fc96383c595caded6e120e6fbdf3aefe29dd0e1a0324fb693678495c91c8ea74f2bb7e75653ad72389f2432c6cdbcff4d8dc3c94db69cbf673a9b53389c9409327359960a27fe86c3a95c255a7ed12dbb1b0eb24f86539994b03a9b361c2d9fec972c0d1bce27d67729d62788cbace1d89616a4c8d9aedba9869314c2ca7cdc4cc33157e85baabef954130d87cb7c0bd3d326b79867389768a5bc3a13d379698693a437c725e526a36f2cc339445a69855e0fad41329cb562700cc7f4dba1455cc9df11c5708ca94ea6129a477ec3309cd7acc25bdc92e4c9c170ca28aaa35d7fb6e9fbc261348fbe88fab9fbce0b4753539ea55a4b8eebba704c4b4a2b639c0ba7d85af146658c4187b670da12be722af246d5c229bdd7c9d4380b886860e12496b0f738d1a490cfae704c1f91f77bad70b014c49c9242e3e4b40ae7f4d1f535278fd09ba2c2490ca6d24f89214bdea7703051e28dcc5993cd2485479cb2a3700aa24d5226097a414942e16892dafab2f86592ece1d0da018d27200d279c4275c9d41099a5d484d39e74528ad6a299a92ffa3e408309878b9ee167f4a464a65fc239479dc6fd0be58d8086124e5a930aa116f65d2b1a8d241cd42951bb447b536a5fe3020d241c2c5c904189e9eb04a1be8e1c73a071847389d9d86cb5b3146ed6020d2324996e50f24b568453ae7759a04184f38fa818f51a76bff5211c4c4c9ac95e935a4cad10d0068d209c3fae2e05792a69c56c8170fe50fa951b542bd0f881d2f0c149da8d712566b393cbeec5b94c0c9769d3eb4c569b308317e7502264aab173d1a164176793bc33d98f9952717a60862e8efda5c47e66352317877d93999db79f3aef1a66e0e2f49bda5ee55b42cdab72c48c5b9cc346cd4e922ec9f861b80c6c711254903aa7c4511466d462062d8e63e9265bcc75799235631647ad9437c96ebf0e9d093364e15c8c6cd6120eadb2b21ecc88c541663e4197781345e76d8098018bb3c95ca7fd9cd8622f1e66bce2787d4a8e5e2ef7d363192566b8e214fd845dca26038719ad38e9d08b393356e9fb920c248002174800051260810450a0021478c20c569cff2bede688a8b8cbafe268977574989456c5b96413a5c22525726c4ec529c489913174892a4a507194135a6562b412ad36a738dca91242e7c446d7678af3baa612f35233e92dc549b2b02f49fc2e5e8c016106294ed2badfdf4be53f3ae5483a0831631495567c7a590575f50c519c4fd4ec19b494b878e342714cc29b985c33994a82491a6106284e1a4df6a4b45b25a22f333e714c27c3e74f9e24cf9e78624627ea8a99522ad36ce10c4e1c2d891964e9fd450e1c36b8f1c3878e1c37ca6c942dccd8c47184499eb39a63b356ca83199a385aba4c7237ce9fc8993896854a55c943835233073330712a154c87b849f266b57018332e713a61749ca0f5364949de12a7be38595182be12a7f9f250416cb08aef53e2705dea435bac5ca1fc499ce46613f67a2ce55b5e1227a5ff4f6b5bc921bf47e254635a6f846512cd3b24ce7d62ae53f1f48883561096b1b5e684d611c7943b679474a1bebed4889358512b31883e49c932e25832a69d1e8b27bfb988c3c60ce2a42447013aa8110903891a8fa8e188c25eab94891e9b84387402351ab1e6388b33370deba9d964c431541239eb954aec8b2fe2a8e1c5746525257fd28a38a5a4eee524f9c37abe2f78cc2003614cc0878f06810d2ed448c429255b8bb75063f3066a20e2f09a84598c252fd4fb30d091030630d8f1c5214e6b9e41cf48374bcfc018e2a0f4e57f5b8ec829698f472ec431c3e3445b4a4a053311e298417598ac27e4d4c63e06713c5139abfd7d622c95200e7add9a25ee2910e7ac122f4c8d0a204e23d45f57102daafcfcc329832e25dab3b6aec353c30fc753fa9af22e093a2add0794529b6b6b773e9cd4550af625842895d31e4e9f5682cc392a7a38961494247352b01274491e4ef79924d9f4d5783868bddc3a2a9ad224b61a77389efeb9f5971aed33d5b0c3e9448eb693bd7352ccd5e1bcf62ad294ae32d5930ea7249ca45345ba7bc8880835e6700c2d793cf3043b50430e0779c1355a8baa18f7c58ac3b954c6ec19aa44cbade4076ac0e19cfa1de24de57ee8d0408d371c84d05dd1dd844d36273e72cc0077e4b8c1030335dc7038d5b96f8d23ede42c478f1fd886f36d9a9cffd00fb31236e0277885ffafe7630de7927f67d46531498a26351c359f567d5e821a693845bf245c4c5b25a678a7a0061a8e194be56dc8523f6126418d331ccce392b658cba562d69ca086198ee75a294bc96a198e711ac4c9df6b5b4a8f43ab47fb17499699a006194e2966d324d6af75eb35c6703e7d73217bbaf25b16c331c6bfb56ee438b44c89a046184e15564a122e78a90618ce779abdf4b823c70d1e3ed2032ab801821a5f38a628623bb737c96abaf5c2a94d43af94d4e4f8c70c58c00233f0d136b06123e9ef6155a8d18544488d21d4a553971a5c782e54d43426a94a7276a8b1053559b07497d9ca9221ff4343877ea5530d2d1c43c52e196cc4accde80b307cb8087c18a146160e66da322c9c921884ae70cc642ba74fbacd096a2b1c7458dccec85d88ef6b54e11432bf84a967266da94185e39cf725b9f3d891f838b7408d29b0b9daa4134d504a56a570523297b8fe8e9d5c6314d4ae2cd364ea6261779498a5aa5ff4613250030ae74a917bc2ecfbfa48478d279c2c86975cb06c27aeafe184b3977052be3b599f6bd284b369686bb56e9b4929359870ccf4ffa6840b6b2ce16a28e120b7e54f9bdc24e1a4d64f161bd991df29120e62a6bf76d2f78db0d438c24196b4a6994dca1a646c831a463865c63a1993248643eb4672f7018218d428c2b13263be9ad0307aca1a4438e93a7994e8cd689f748d211cffcdbdc469b84abdd510c249d8c9d52f26dfc9bfd408c2c1fa4dad2ee636f92c37700cd8b0916ee038921a403809fb7fbdbf6992528fc47ddcc8a123c70c6edc483d1a470d6afce006357c70502d7ac562c6d0482fac8c2de39acd5466478dfa6db40d4ae4115e74994f124a54a423bb389b725162bd7d26d1c446747190b9bfbaa48faad3542e4eb2829f496b827071aa6849c96db5984349e98dbac539de2a67909a4749927810466c716eb9b4162c5932b14e72f428c1d5e2789a84ccacab7fa2295a9c2fc66432ba9f0a9e269b3e7cec8e1f3cc2e89198068ccce2b45956c5644daac0882cd6182dc7c6aa378391589c334e7c050dd9082c4e316e099a4eb538b432478f1294cad1038caa579cac34d9eeeae5bcdbb8e2a0aebd2faa7b53beba152729ec53b7b76585e29ab265cdaa99524ecb94e9346cd9ffebb58a9310ff71b97eaf52d8570923aa38e778e80c5b49c52e2537cac78f1d5ff8d84ac5794ebcc537e9c295d451c1c9dd33257fe736728aa39ad68abbdc7a82a6ba0718ad35628ae366d8c8f2ed36b1547230528a8352369b4de80b323525c569436a0893d447b855a3d862c5cc62148d37cb606521da5e3305b15814471531192cb67b7e9884e2f8a124415d2c061447dd3436f27e64d86a90235d0578ad183f711ab526260fb3dc0c9d27d26f72eeae67d490ed4827d4ba3cd912e5c499dadeb131a69cf15d83164d264ed8c84736714ce209b749d4ac49959a38e6b216e1d926ef043b13a78d35b599ef1dc1c44929cbe43dfa040b5f73a01e0e021b239738a65d32555e9a844d415718b1c4f975f358f0ebd3397b04366c8c54e2a464e89c49a6b561e30625ce26caa619319777b1f43b7ca055360e1fc61a870ff4800a6ed4c8244c121689b201896392fa43b79494c9c4fe11a7f7f11675c977c4c9b4dbc458b269f89f6f06a711672b49852e8d1bad4ab911461ce48b8aa24fccaf8a741147713f49ca206b192c56c4f9567448032389388eaedf37d9acec328f20c24af9ca6ef1221b634cedd0adb57164a608953074b4c131728893051d61a352d6cb340f43478f18e22cd723bc62ca264bda238538b768cda254ccbc1b5b11e29437459b2abf283b4aeee37464107c86d1bd0daffafadfc07108e2248b92f292db6fec132a550271b22db1b96d2ce6d2520c1027932fb4040d5979f7f8e14304366ca4f61e3946fe700a5a4d99c9bde8a54dd80fc9e8af9a2d89436bc70f5cc1481f8aa96aa16a1556bb1b2a5bc9ff3ffbefb821024f727c3102e6c36953baea06dbd125c5ab3d9cdaa424fe9a98a9e6be8e1d3e3430a2875392cdfdc48cb9551ac9c3c144c5a89ecccba067502af1605a4c51cdad552d7b575bf725717346eaf5c806237738889324597d64ae92e477c40ea7b0b14946ea706c9539b96f2a339a1aa143cda1e45036e2208211389cf2faee47936a1a69f2c81b0e6233c5d4e66e734ac9881b4e42a689325e623defde91c305236d38c9117132265fa8dced081b0e731a2633d69da9f846d66089c7a88a5bd81c27987e689e236a38a5d025dc995c295ede341c46be26d952759f5cd6172368388bde247b09736530728653cc5eb32284f64cf2d9c37d781a31c3c9ae6289936ee22921438691329c37998556c88993517becb0800c76ece080d508198e269b84c824063fe03a3e408291319c84af18bb4f3eb1af76311c4e49dd9ac4552b80e3478e138c84e1182764f6ba7c3bb629aea304291801c3a9e470d3263469991c8f7ce118b3f4578a51f6c2713479a8d19c49d5851918e9c2c92b99302ae54e3931c785c3ee2949d29a9298749243c70e1d9df4e8088c6ce158d285dd68add778a7fe1dc98d30d08d1b9e72d42047aaf3d1a33930a285938a3aa22fffa249311ac9c229c9af214b581b0ba7d24b1d939ff73d71327285837789a1ae376a4a36e6681e61f8066cd8f01186cbc0868d1eb1c2f9a45891d9a78ee67103373974e4e8c0e10346aa700c9f9a7e272f53ad04092354389924643441ac4952863f8563e91242090dd62b2a4ae17827f6eff72b460d8f44414bf175c4d658aa387e34086cd8c0f1a379d8b011c60f1c8c40e1a04f08b5b65fc92d4f607ce13d78e88e3ce16c41e84da2e491de297b8f1b36701da62630e28483a8a0dd4bdcedd3a282e347830071fc681e18c60fc391261c53f40ca5fddd8d1e3b6ee8e8e119d0eae1eb81914326ff8d7631b31d8c2ce12426493c49be8d49cfc71125246a262e1635662b7c653c39257f89b5ff175f309284b3851a71a54a5ff492e4c60c72f4f8b1653d7ef8b80d8c20e194a2ac4fa86cc24e8c39c2f94ff0ccfca67baf400d468c701ebdb5f2a5d265924dae44b0234538c9944ece247a34b72911e1d475992b5bd648bdecc8100e634a9652fd18bdee47847050f2b2a5f30c6f7e4123414063581655d1f6acdcf0da3713fa74be19f4d8618119f4d8f12340387a29d1cc92a969515312467e70b63c7ec9f29c2a9f99c7880f4ee326bc44f7b1d27f2f4e420525e58d954fca29e2c52934c5246227d6786fbb38e687aada12b6b1f53505862e0e234c5ed5cbba6d3bba1888e4e224e61273c927c5b3caf88b1d3b507e21828bb3c9957aae2bdfc4bb7c7cc16306396420728b73b9c5e88769bd92d2b638965231cba9b42eb9c95a2c321a6b31ba67ae4e1753154af01c4df79e312dce9bc4880b6a8e1b3f7c80d1e3868f1c5b3ebec8f1038c599cb28636a5629541e8b2389990dd68578ac5b9bcede4cf9ec0e274b289e59254c5a0ab7cc549255569d62c64bdd2ae3857b02053bd4b122686455a715a2d35355d41d85c4e841507213c4e1ad5da2a4e629960f199cec2e546441507d3dcd224369c8aa3659017d542c5296e8fb6cf5ed327faa7389bf5a87dcf9452c952c41407932b46b7694a2a6949a414274d1fba5a92201c5a689e0122a438c970f52ada2cc6ced53e7ce4f8a1a367b061fcf8e13d781830c2e871a338669149bcbd3349da538ae2944c5b8993d4289129138a32672e8b29dddb3b332e7a97d62f417112df6431b9c4a4cd3f0d887ce2e0dbb9d94b54ba49a727d47879934bda2216b534abbb942429098d93a8940f6552692c120ac3e1803810c5202173013313000000101a114662b150401ea8d2be03140003542c1e3e2c2e2a261e16181a1a89c6a17030140885036140280c0c03038160402c345192787e1fb88258bdb9d9d51e0788e5ac16b7da2acc537bd79ffbb4d115c78615e8ab53a37097904e53990a77a5f551d02cd219f5c7c397ee42c80c71a5b4e9f4b81acbe3d205306016d55ff3af84c6ac6894ac997b1b186cd0ab1600ef39a3a9301b3b422a33a72d2c20aa1790aea4671ec5d0fa9319e3738152888b4b8dbd01513c059d207a940ab256918e73644241f8e6d6e8d700e8860b718f9b7986b832b12d8308d72053846801a107a529cdf9190f2156595ba605532e75853742acbf9284c2c7a516ea1060225c16966a219d004be7c275229040125a332fbec6b1a9b2a1541d4327e165ef948d13d0b9162be87405be10d489a5e7efdb02b5afb1da35a68e731e86de11cad0721225e4e506e36661deb43c98966b2180ad47715db4d457364dd7f914f1b33ec53e70703afec8a628014555ea00f70607c727900cadb18639ea50554989097b0a463d01c2ea646e1650ed15dc969123d2810054f44838a58a6d85a8b57550acbe9b7c82ae5a819e4c90c6001e879bf5cfa874d163c5d32093c87bb17234ebe5eda2e9793732b51c5d8ca2d62aebc8231fe76ca8488368e537733be7d275cc429345674819a50a5ca082a970c17d85837ad2dec9dcefc218dea9c078eaf4ed3832903d2e2f9d0b499d9d0efbe44dc25cb2cdb517b68507fa2c0a9eca8e3b3310869df3eb0115a245c86285bb12f0fd48fc44d9532080d4e6557a2ac5c0f23e40cef93da0ea68b71cc1f27adfa3a059bd365f77c96dae245119d36987a1cde5a4c986ed8160280378536fe124a1b4b5894503d955885a4986835bd028cffae19646e6d72af0c04a4f4ebb6a0a532a5ba4881dcf40c2e6a6d58cc1bc8f27ebc32573e284c77f8231d91d2c8ca91b06e102f4b615b97460717a463cfa0da946a2015423bc263d8df7153808d2b535e2081502c3d9d0a87f150dabf5bd7903d524d26de03dda00797df10af4dd8ae761a377656b00ac9bc22f2ac1a467a47d03191f2476c5209980d23485b4b74d3967204ae469a55cbfb0e42ded4ea83c0fd18cc1d78d7c3ca63eab13c20afee096a4a0a80b71bc291b61192c4d6dcd3a63f758bef4c28eabce4e43bb3834e7d7cd07d148858739baeaebffa2a6dafc6c3e387d6a831c18416eae3e296034173441718a29808416bcd170bc2c267473c46177db2f205a99085e239666b8335553c1385bd67399515a30c527271b5b874f9e3b2b9e0d2050118545dd7a508dab10883d70216080717c6c141379cdf351e09a8cd5ac46c3295eec302cf2f1fe81c3ec1ec351eb754642e3de6bbfc8c9e92fbeb13a4c7d7919534c3fca013bf1177241f6b0c41680322b146481ec21b7063b25c1829bf906d67851bd3a9d70e835133dadd4fd4e62c1dcc0f20b3faff2d6e8c0e74e4259870f3af15d5ca21d7bc583acd37503e3efe1d2c688f5352a8c7330375279978372c611d076810e3f602405ba244a8153866d0236097c2f591dd325a30a8834a98328f01717c57af23284cfb0d0d7d86e524c0f93abb0e254ebfc78a95bbe16c38e597be489378ddb98467c24ab37b5fbac5ba5f779521755f10df615987643e9a204a7d4495cc471941d01615e81b93094b845f144965f55184a302f941c1cb57656920a33147ac9037b75ff53e2eb73e1fe2d1d68c1a661bbd8e81ed0e26f3ec07aee8bab02c54148c422f0528ac4e410affcae616052f918378ae424e1f5ee8c93ad12883a774179472a12cd11c5071cc44bc32053e5dbd5bc6d5b5613997b78a4e23d879a1a1e616768e197b705ed623ca6181ba98769d6627ed0f5e3e745147c0304bcf06e96098709c0cb05cba1656618f139308e48c21ac3c8565b2ff0517e9b104d4a10ff6452966d81b85e14e4996025489561a3498c3d8233d0f4a913b738321e48593857072d4f41775c0ab01be9dc2d1c5ad2a3b14cbb9b83fbffd51718d0ce419dab4d718b96a9c917d825199ce5dc29bbafb2251498182a287e45b2ee61aa2488f7bb97297beed00e091c6bcfa1d3fcf927f3085c13e06c4371db9b5e39a49c36dd92a23b502a5e6c291ff700d36ddaf1f1245a824307654817d15b3c7e8435bffc8386ba95099b8802c73622f0664b6cf946a56bc436d1115cab536e05d681e1f06c5d898f915dbdb4246e76df085bb7341907123df55fda13d1612343285ff48bd946acf458b2799225470d856dc63a2b1ee921cf273d5cd1a1702895e4dca292bd87071f0e2459892b59893392e4e325309b242a74f8e7fcb501f7dee3405a1b987dcc7101bb9eb0de138106f9bb35724ca56918b69f082de6dec9bf16b69b0cb8c6eb5e996824f251a4607771788ea2e60409dc8d0b29321f6f2e6a1597f776c3c981c6ac3fbfd9a20a94fd705c53f5109c1000b75b71ababfe400188d756b14aca320b5911492135419bd2dbef365e331047d20d64c1609f4e7a9f27fc388b69839a2881a73035d351f72ac70d7741f7c92a13297034beb044ab56180c9248deee58029995d428b5bbbe8d279bafcd331abb5c63c2076831836cdc310f059abc0f3c4bce1f85323f3da00b7dabcd2012307d71fdb40b97a996b867faef1ff375344b2d2db996468086b77d42122153e30a3576eb0de4af2c674aafab70b19c80aeb0cff6fec0e15d21cb2a8ff1b9737d3c9f098b78a0ff817e323531797e20370e629c370764dd10bd29a8fb3324872641170050270bbc00c29b9e94e2df334d20f0b5c31e342130d56c4a05f22868d41f54d0feff5a3b714f381578e3e02b60d48addc8306a83cd6ec0256e006d0812ab80e15c7179ae43a520e862c9acdf02829b68519c3a34d0c7a50b0a907d66e03ad5fa909611239b2711b5e76d24a1deff491a61c5ebfb209fb5cbf5f76730d6b4c423561e21c820477c78736687ad7c357f42eb5e5ebd52192e4e62f9d322cca060dc3cef84779a9d53d1f42631b45b83860cccc91f2f69c81d38a45f45182826fb80e00e17fdce3c4d4e79574bc7b3f25d9c4301a4e3ad208381126182bf568f06ad48fce90eb81790e0c07e2f5cb69c9b1980396f8774ad007bdb812434b880dea675b611344e1f67e3058364c98701d496c7b3dc8a267685e96dc1754c4597f3eaf91198b6fef5f404fc329468222d368b46a10b5110c0edf5b8cda082b93475c41eb14a416b060c199a592baffabed64697a84f2f70921284525a8f29582f175fad85878439e798fab6c621874c74de2965cb4b4e46077c7ed1957952eb7e6004e79cf011ee70bb4fb208f76bbd222cc44c0f8c1a9526766246403013a093589b321c36d436f4c3b92c524be9cf312d8d45313250130314b0c05299b0a69dc5c44cad3a19e8b50ae4d51a69f62a69e99f7fd2bf82d233a41427dd261722f8f049b1e9e6be2e9c246149202612ab31105a7183939f9de8bff4405173c8f507227af3454a8429333d4968a6093e51e62c83d8fb2db80160bef88db651f5e2b678dbd70b13c3cacde4851dfcef50c2ce4a617b1a3dafa949a650848c46d0d101aff21ba775eb51bbf7482ab0c3e9558a8dac958bfe8af96883cc3258160b30a7a29a6cfcd6797a6b1d86a7e4ae78561134042e314a9a1866ef91e0b80f0da8ea02a01be7ef68e005612c0397a09796f51795705e579bf5aee64e700da93125e1b92a23dd899476481981044116fda30c57a2fe2da9004a6c7f2f853e8ac688869104c295131cce11889dff4f6e1419acc41431cf8f2f3667a4ac8737407ac1428e7a39007ac10a9292f28e1d54d75adb6f46e934ade47f4f054e8fe48c2e2b2a01e90dcac7c18e158ba99b68f8c3c65b3ce544380e2a1a87b3473e4808da7d4249f53514b28e132a02d612f258f618144e22b74217c75bc05cb7980676d00e8b034dc21fcf102b3c6ef4f81afa023f0c29afadffc511f3fd4a0b186dd8cdf92fd6f323a7b26e4199f0b9c4e8bc71e91a62ec199d26f6fa1b26f3cc76fd373b4d60c20998a649b724b9bd4d23bf779455c86b8d085938de0af0a768879113ed7ce6039d2788c473b553f60fe6b125416196249666a946ac465ac779c15e0f8eaa2387c66a4f57ea8a1fed6a98c9b59f2b72f8716e88bde3671f86e2d5b18cc54ab66498b6db860f0c03adff7182ccfcb23b171ae94f9ff4def57bd3d8ad66c88f14078f4b9a6e6b1e4cd9c1e93ef644bad9a23dcd0389af7e52948f11f809000768a1aae3fce8274ea12bf0a99e93408918aa7d8847c8733e7d2e34f0a1151e434edb4662b37d86169b0f28d63a0367cff8e786c9562983afb726975ea176c6060979d3364ac2b68736cce99c600d637241cb3e31ef6766f3f718c3fd7998e16b946d5e885587d5992ec3198a209644f5133ec6e33353636e3b941ef99b62a49ade662dcd7eb67c478b3e39a2e0ee4a986fc208434eb0e208bb012e6b108df7a6191dee5a9a2c33b475bb3cb0a47c8e8c53e3bb00c74b4e2d433c7040bbb42fa687a35be4b2a6143819206597f3f35b2df7a7134c4abd50cea5908398b8136e80355ec804f3f301865375ec58eb32f5472e9ecdcc9854916396d324cbe73d18c96dc2b780c482fd861635006ada2907540d62634c954b5c8839e690a2a490359736c50adac4cad684e6c58b8b3d05a25ed85d2611edd4a8164aba8e6686291606dccc7c7b9c6100799ef6debd0e0dafb2828c75c1bbaca836404956c33c93b5378dc1192ad4da086d7ac0a04c4af2e3799dc08210ee508a8c9a36208fdcce3ed6d92ea0c715fc659197a88cd42019e37d249b19b8bf22c673fd53e316b62fd26cf0d33027220e100919ae32d4120888fde150183c7a580c88d701b790aa38a20277a0cf3807c24b5ed49bd5d01fdc9d18fb0adc291a759d8b5a1215166d2ed62b885113d645fc7ba15e0b74e1d0e3f200305055eecedd5d3eb2c96b86c5a90785cba4663a34e1cadc7cee76bafa9b0ecdbfe21cf1e0178a787072366d698ab34037815c474b9ab2422f371784e79755653817ba4be5b7e7d079952630f4eef1a19eb7ba446690cace5ae77515017d14b9997f8b233d748ba3232e6e19f116a72f03f288d80ab6315e40451a7831ce437cd3cb70d8dca3e8f00dce0f62e3685455c59404c757d2fb1b1ad79954601808a008d56ad252be78c8d49c00a48978e417a1310a36ce32d97d84619dcba3f4b8d20cbba9782ae9638415783c64b5540ab5cae068325726a86c5d2ec407852c6d5164e69893ea14ef0a10c4ba2a2f2112386dabada51b721ff360c128182f14e4ed670d6610ae1c309256166c978d80a155e0b1ec96a2ca7b2e251fa8c2e4922bfb7e6dacee54d2bc5ccc5bc9c005e31e1cc604abb0df40142509a96ce6114aa10eee9c7be54b781da8aab18ba35d248585b001f3416f21f1fc2ec5c76c74476f9171b476542292e26c1968af7bd1cdf5c7fb0c3cba9b6228304badf6b35e5ec2d78a0dfe10ec8320c67de160b98200e05e4e8c910acc82f48c1375195924181d955351323a4a6bd50c7dd3852d1a6191e8c0ee44ba24a8aadabb5edfdb9eb9f065b8c37b6d0c28a2f81f81177797caf3461682ff1148a51f4551de63b84aa264aa7c89292bee32f5a73a8341af9f10d58396b24c5da9a3372a9b976dfb01407d22bec6a5400a131562faf59079a5a72805c343a7e7c9e17c22b8fa5bdb1176c4ca4680445fce3b680e7540257d9dc4b46b5c3a6a53355a2721ea23608248914507f12fc3e51d3c1267d8425a630b59f1617202f759084d2abd94d04e58db5324b66166d6d48dd2201faca3dc32d940b4f822dcd8ae3571647b44d501e12da2c60b21569e4f76193d3a6b788032288635f5fd169a2823c6206325827b95eebec298ac95015a98c3a6bc4002770ebd5a080da986222d89995d87c0e9c2e2c714574913c9c53a20e298a3f494236ad47e1086085334a70809bb8c2dff9253703c832d193ae3a798ca219e09b81a36957fc27a2bbf5552c014c19a65736919de300d55a0ab580ed161e2befcd4223914bf0c99031842512f0fef4b8602c37f6095ead567ec763eff3f93460ce13978b17205464ef2ab58da49ead1f1bc1fbc3e32fd0737e8bdef60f93485f667d4a1653b98db5e11f970756788ab7889c948c55c2280995d01be04e504ba0bad7dcebd41526b8b4a5da013ec7a5a2442be1cf16d3d1db9cc466aab80e4b8c4dc677cbeb91492060e98444b697b4949187287d94ad95bc90674e6c100d3f696345bd55ea17575e95df110a06d16dd3c8ecc2e495c1a76dac732bf9877ad5d5abc549ff80836372a8f70f504050f1aa86a70afa7424c2491190bffcb556fa59f66f76f2501a8fdb25a2769b3dcf33941623c2a269419754a31a1cca8530ae7d8167eb7939b3a9d9d62a35be9ec5c6cecad78768aa56ecbce8ec68edf92e69b977e04ace529cfa1ef0e1681b72614940aad4aa7a05440ab4192002cfd020f64b0aa92aec64e8951d3157555d1bcfa3c155ebc2dcba3505f75ef28db93758d1f53c78d9281f8198d20d51623cbc09b7f4f137749bcf0d8b493e071691e6973855b6a59baeb9a2bfe0bdbd5f97730ff5b5795592ff119ea4e270122ad6307794b7389276334d9c2fd2931dfec7a59429c4858e0e1483428b9e8cff7fa1b79d5fc5e710b769d00812f1f906789c2f27c7022cbf71208947bac1b66e3bf8ece7adc9a4db7b13873970283204b38068a3328f97634a0985f262ca6c14ed54df2d62ba638a5322a39361493497f70f5649129b3534f637faff2e17326a81f54f4f0eaa8af2878b3db06c491eb289753a917ed840a184c72b7055271642c2b305212a931874fba7810b052769d214678033d66d0adb6259224d7b87db920a3ac658ec9caf6a99d6f310a187d86a29f21034e65381eb99b45f8b644d9104340e57087ca8d4791ff806558608ac44f7fac7dfbd2aee6b177bb0a7d2e15232ff015a30857d26368bcce4419edb66792fb7096788a7f7153ef81597add54a49a92a17c933be00ba64f485454bd00fe6afda9eb3db1baac71555bcc896a264c451d775f219c8b61eac757d1b85fdaa1d5f2530c6a9dbaf573726289b302b69bef240783c75035c086292674f4f888c3b1082f792c0807de4d56a0a83455259517bab2badfc816c10e774a78ef7657675d8957b4ba6ddcfe2cf665eb486fe0c6069a348cfdeb521bd28960f5c80d50925dfc3615d457766405268c41881291c7c21da0a28c7200e33dfd22310642c8de0064c941480bf2f46fd06ccfbb5ad0566aa062516cf1b9a9116326214bd6c8a25149165a89696de975aa9246eb9d760080dd394f9bdb4d4338485768c01def970671b68b2343489d547e56253505453b6b712389bf4c907d5caa0e0d9aef2e155eb1b6444ed90c88e179d06fc59f386f2ef229f5414ece899ccaf1ce87122b9e14e86329788a6918c990fd12f73dbe8b4c743dd99234c123288fe849905b0ad820041023275aa8d946afecb5fc35868f8000b6c175d00c40263b1fc6010d7bb238735112c1e107c0033684022c6e0a41c4d4ac3157a7e23e520cc9a2403e70bf3c681168b6913da63d7efd28a532a260769c8caf22f6b935238eda918b86305ef022ff6bf82e041d24e631494be65c21f65733467dada055e92daa5becc0f1769f3113f0181bc34f67830bc2d01f87f9e8abe2420d9c04601e9cfca3b217f57069e44d4ffff0906a1f719d7ed12cde40ea567f2418561f9c008fc0c59340473815203e8532dc5301197f80e7c9eed6892fb7700c828bda681b4c946fccea9a2d80e24051a200a5f0a0988e97e90f068d94da0d1c0c9e172b359830ac5a4c311067e9fb7211c938aa4c7fb6ac2c823398b020a911c3e0a985d68d3d1a48337df7f49bc554657d3aa7b0cbc66875591b46e8757a2d52e1354d9475d7050b064f654e05efcbd7307171028e066b53a166706afaaea686738ccbdde125274950eee34a1cc22a86792f60d69c77a67fb5cb3e696ae2f6a17f88a137e66f0682e9fb96abe4166252e2f55a63616420918cc93c7ddb9d9a4ed133143a069a29a82574aa41622e56ee0bba3a8377e5186937d4b376408d021704b61c809fe0707181aed36f63df5ac0ad052d3de44de8e83973b925c62620d887859b16eee6b74018801e002e801a609381586648cb89cf880de09400f7a5ddf7e509e0e5f39f311b39e58ae8bd8fdab3eb8b7b41e8fdc5924d1fc6fa43b57bad13f755047a01c9b45cdba15f85cb742d7afbad755ef9c9748c9118e8bcf95f68815be5529322f61058496090e07a42e4c55f93bbb97452f7b2cbed9cd5d433365781c3697d7e4fa34e16e48a10111f669a00a6d850b55879b229765af261973dc33454797449fd1463ee4dd2624124dba6a3a2da5a982bcf3d0857668dcdb63b6eaebaae534857413b3b5248f7346967ee4e559d6228b32704e7b7e7b5bef388b8387e073f05b64509b88e879d1ac85e3182b55ef980ef1c0ab8cc5af9b49d847d0bd2980446545ed6b369356a8c349c1dbbe819e88c009b01ba6b400f6af17ff19608d5b30216c50a5298fb737981232e98b1001b2173b34da66304a7546beb32faf0842ef01d3e8d0bcd1aa98fce695bd63b8254cf52159f56600d692282f081f97d676f542d361e62aa28b513266c6c126212b789eb37b79ca72d05301cf4dfb53c275fc31a35a89fc76c0f9f2f8050c9c369c4964696920ebe0f3ef7b32fabd1f8868a3ef341b41b401d7c13401bb65982e451588277f005ffcdc171edc027b5aafa6ad3f84b6f97a39dcc061427ca5e2d729b3f377e8f4c1b7a505b2570be194d9c67c58947bc4ed7d5bc5efca6f0f4875a36e9afc19e7b23cf32c4f929c3b4317efde2460fd097466e62096fe7b7076c3379749b038692c409042ffe857f00d50c524265eacb1a3f932a7abe3da99906879b041b126a204093432cc0e776279e7f0fcaf3d895ef3d5f26d367ab22275f2306db709b990db95fd9b62c50aafd5e95c72a246ab3871e3746030e27b7f327991f70ed12c21dcdd28d9a35c26e8e8603fe79814275743b92311c498f048b474494240d129838da49dc60f96cca0db72377cbfc975e044c7657ecc90892a14d0d7e96f77b09650b71ee0cbab49fe4b893c25015bab032fd222d02392a9eab5c918b6bb1e90a8e9091eca700f46e850b6d8a5caeab33846fdbcfdcd276c50be86f4bed6a4e9bd9cc70826d9f37ba1ce566d0b6af2d1de33373b68a0d41132ca79306f8649e60ccbfeced41d11e0ad1c2e62686ffe07dac37cca173284b617974f3d633eef760877d35650cd39431aca4b02abd3d3fb685bcb07fd0cb237d979f3198fe240517ee0515697931c75d4d32bddb19d3eb0efebf29ee4737462a194dd2025f4e23988cc55d984af13a2e044d661f3bcb924e4d861bb13b97055596aee2e2f32167df7968a6c2f94e92e1e93581024a8b48a7ccc73b87cc051e46bdc6e431ea008d1a98930787878c663f2b33d8c4795833cc7bb3abaff0b05f6d0d36870f7fbf7ce67707bd708c2426f0b471fba2c3fd00b84355768ce41e88076fe8e60691a38bb701ccceb8f7bc5388dfe69749f40992ddedecc0370da6d8d17372214b825b3b91253bae83fda9ccb8ae942b460ac9684feae16377061d029b7f91237d53e75d8b618bea8effb180656a9d3fbb8b83d18ac3e4c24e27ac705c5ae99250ceed90f8cd855ada03ad072cdcfdda3c5dcf8d2efae72d11393a809be035f2379bf53ce72e4132f89a972b066ce58188df87213e44b924a58fca0742b6543fcd760164ecaf7a3071cde6d64f8e7f8033c65aa03fd64859f2f7014d8fea11575189169a36a5b02345130d96c659eb1436b580ab9cf82c60481d831a766aa344bd8ddb90f8654e54f93a6f9de2d4696de815484984e954fdd7b1fd03204426a416d08cd34f540e9d7f32ace50580647f3500990d94ae590c2176a66e8014614589ae4061477e9446734f494a5b779881c588be52927715c034a47cd0624c6c23d89a4182e600543844faf83e81766b804271c51a1f2cd10710251362759c41c333fd01d70e38296a788fdbd94dfa71f1d42de145e2f4ae0f14848b5924beb88e8a9b88f65eb5a40b121645ac2e94c25bdb10c5173313216e415c8903eeca6ff31994bc6ebc00fee0f01085c47e489b6c1bec621d8a27a5ca61c22d340cd3fec85e101b6f72cd3601f767e5991819d74b0b42f7ae9d7f0335de0bc8064c5bddc9172b172aa0ec997e1fb4cb16fbfd19675189629cb62c0d4d9e381a25542fbe386712afbb24053c6501bf1661020a7c582106782eb0b29c21e7d4ed1bec170f00449fac6fe8ff3797a644801280bc3eb2f32f943b9a6fa2fc0b89a2fc37baf9177ae24c68f4ffbcc6374fb9c35b4932ebb700208a8f964fcf97e863c48f8c013601e0e6e3ecb71a012b413e9f0f020d081fc0a588ebdb56f020b432117c785878b4dea8e3d5292a8b077b35929de496476c02605c08bd358f0e39e27298c4106bcb4b4f975c3e004425f807272aa50c93c1fd98551823c305c9951ce3b78f10a62bdc1bdc261c5d1812cdce5031560864fa3be012b47e9a53cf565206316355c40839170efbc82f931d99558fc349b23f630de01bbbc689a769bd2fe36bb789cb9ce8dfc9ad3044131e9c334d0eae1483b5d5a495103414aafb0baf896517884063fd0c1568dea294ebc164601673ec462f34b6426953334e3a2ba0f929d156df86d916f8a61ea9dab7569999fd16ce51c63ec793f773f45a8c00c3c7a3bd50a6c68c25076f430afc0a4b27867ee4f89723753455963534b02fd9220ffb1a52c746b037a405aa17b527db39edd920aa088b7a7cac30f041f4bd4ece54b49fbc2af6b8302e7130577c15f4fb4f8c884a8755c33eeb64552cf1f069a1ca2dae4380d09c1cb2d7ed2baf751894b5c42d16f1ac8983ae9d4041bdff0f40ea2aada71f710b9305bb2e9252fcede8818930c731fe29c6802fb0cd208ba4f614bb6665daead705e5e0bb84944f8a9bf256b30908fc9f1edb42647142551c4c4f5eabd4d7403abffd046c8e6d803ed0b5726e46f699421cf10947bc3fb941ee4df9fca5903edddb7f7cc8a67b63153ad2b146eb15a83035224d1e97dd5548cf501fd12e429a4a2da7d2d40456ede5e260c5442779871affd6f116cfa029a8aa5dbb648c46baf8fd907e4bc9c6acb90452c7a46d5c75c8d6569ced0065ec97e7a0d7357acfd6946166e3468ed0ddc5c2bc6c14620a5cf02e9cc14c69451a5a5cbb943e5109df1d2d4ee6aa4dcfd5043a6201a0c111355495bb0e616dea16a20b79ad1da1d6f72cd49f65bb93507bd08a9deb4a579bf4a6408947bf89573bbc518faa3f02f8544fcf2b088658a26d5fcb509d214a5fe8be0ae3602857e85f2e3714d602e34ef35b08849dddd0559f102e4bd381ad46f994161500161f0850234ea9b98c003a23cf58eafa0015e181977f06a0632cc0e791061c38423aebb4dc8f213715e0799f0b1ac8d51c212c7e0e18a6a3f165f478350b4279c3a297ee2e1d8c430782363930acd2ddcbc4a3bc679ea9dc90692ee2363da52052dd18965386e26dfb7e33458286966c330002a0466fa7d546e8656a9f85781bea7ff952435a6d8d68b9c46100e0c090c7f300186c4cceaa04c049c9e434b482d047d679d9c9e464083fac846b111ab43a53e47fb3ed41cfdb85285d59f98f001de1bae20721992653bc2a74b5b01ae6dde94893d24306b593f35eb451a6bc35431431ee8baec4452875f6713c53f29f91b250db7078aa11ea352a2f1486585222116529e42e5579e177f3b9d17080cc1d40ffb0b411f560f2cf04ecb34499b4dbd768289faa66b15b160f2cf4654335f26b491290c56bc01a0dfa2b32e24183d3ddc28d7a795e7236ba7cdfd018be285c49c46d36bdbea79384c420a96d4212704385f875e1db7723e4af4a1c77070e7386256d6469948b46c6ae89940ed9d6253f5d78f30547da1dc5b2b729be3b322d1691ccde34d97496955dc1da0b98a78a7891199dd8ede30cfc63b0c3513bbf95f23e719d4c8542151c794d3500346131caadb323395582c40e616514ee97bc1cb2974468351d849c2254b314969c1c433055a25967d309341da899e0b85122d6e636d0a317b16cdda9fc57fb5d15977922f7680ce3b6e85f5ae5ce88b5d5c276ab0ec73400b2bf7663e905d979830ef67f426406798bfa348fbd1b3027ad1616f8ece455c53600f06b7a4310d9c5a1f0549a2df0a4bb79d2fa9efcc03d92cea4c7787b53986d03aea120cb43511ea5e836f1b573fff96afab0c61110fa54500fd7d6a48c8227a1e06e75faf982d2b9b92c3fcd4da8b6d6f44050470dee3103d04f4515de50d119b4a4c0add89f0256e65c4d63678244030e447493f17bff6e47c0900dff4b0d45874de15fa3ab3908912f4b27dab43809c0c8cc54220c23c22e4bd200c78a938d4fcd8aacda0b15aaeddabcce4071ac137e0b592482b01e6e93f384b7c98e6e2fbe029f8fc407ea83589fc029a04689f277b248daf3dcddc42e92b2c6e87fd4e008318c5ea18dd057c256487ebbec6f1e68a7e82b2cef181fe96b7bd581311bbc00e36f4f31bb0d785e94113300cadff0d4a08e83400d285f441ba6f50a010e96a1f701c7eb4f9ab5ff284fdde1a00dec5504a2e1c01ef42ba9f019a6686a5ce57cb7805032c4123c7886cb110b2875f4affa032111607a071bf1c22160e860c39cc5dd03e6c1a2943d779a330eb82310c241f911be787000c2780c387d7008c2b83ac0d384cd63a9f8669377f8e3e9cd3416e847ff6c008feb16907622d7aff9212336c53527008641e9d3965463c2860093b32f4c999eb5546cda654abd7df7102fea07e3b457442a9e40430c702568802e070d800de0480048e5802bf54313731f400e812e0018048021be80f98e556fb8d14e58e752e0f4a2576157a3568d0ca0638041103843801e0140054cda48002805c0c8bf38cee6d0b087ae320231ae501b49661040403c033117041010cf40cc050184113fc104a8769d3729dce48e5178c4676850bc2e799afc3c1223c8b121ce60f65b780886973ff22c9a00039f3d496d00864afb65b428c8587af6a037efc66be89573d46fb752d8cf36bb83d91c8317d12a4d75d35073df4a0607311a63a0f81ef4e2780e6b036b5d8882464f91670ad12928f03b52613da812bd7a202da5202201bd98f579182c4fef2ecd989cb8e560df0200a3ada656bd74e28dc021b408fb59e3051f445d934da36beb09eaf3141aa579efaf7b8f09e9c1086046a957330012c561451bcad7557ca635d799a0868e404fbd4ad724d2c0f5474653f3c9f86206bc6dd29e9fd4a07a4c5d801fc77ec8e23aeb551a3e850c6627251cf9445f9e3342b86afe3aa0b5a00c6b2288940878e7229da3a72f7945ad3d307035191282898035c973b337606e8cc81ce53b0560473452e6f0654a0e7a3d610208257873f8dcedf9fc1d4a9208d8265c7a3ebef3a96d4db2210e62dafda5b32f02e11a0b79f96c4419dfd2983d04c61a57858fee28b63733218937e909866657a95e0d42ea06b3869e8da44fc019e26822c196ef466041a18c438f740597967738a72ea1416cb7bb01171212bffd957f9a022d95b9fca2d6047f6cd03c91226cb94cf293bc1441e7b1aba171b9d65c0c5dd8fe3e2c8f88b40d32a4f47785bde47969f9928a2b51b962c861085af07b19ae65efc0529d04c9881ad2d3e0363a4b78ebf705177a61261b0770d305e369d1c3194be425debd92321373c3cebb9ef483507724404e9db0fe27b9c35ff71ff2fbb5aa837c354891d9a6b45782ea4d285abd89b7012d905881cc878d83b2fc798abb1acacf92c90a38b72183454262fa6c341992ffe55c12a687c9dfd6031116cb927c3fb8ac42bc9582eba699db631745ff99f52b81f192d0b0cf7f79ac9abca5c0aeaf4331c9e8b46fe4c3acaf6660a493f095e1731239dce510e197003fd96b8a171d02d9f06e16899f10044ec740c09511f82c092c09516c9744afb13627a146a7aceb8f309b97c836aacb830101b8654a24107486e7fd755b76161f023664ce359613bb25d4726310c39320b206b04c0a0e6e05ddd8b7a67d9d93b5cfc22ce93c25ee8f94762caa9369c0743983d564638b4ccdba2664d4ff3bebc63302d4ae7aa8f452d71307b13ce1365fea11717555593fc18ac8dbad8d7e0b8d7bd695406b4a14082b4fa85ef11e6a09b649b211cf873813d5135c7a46b61b7b2c05dc5ba7469e3a11446c82adac12bc6ebafed4adf3a35fd488cb954f74f1d107f8065c87f8fc12b43f65ec55e2bd9defe61f35a1f3e135e41abdf46a6040ce819d72fab6685308905527b4518e4d4a2ec94573bf7c981b3de215f8f0cd206aebe8a75bd2f32dead52a696d8945e8a8fee49b98eb0a5756a1eb02dbc18c64f00b775a35bcc001d94a9f44cc31a27cfb472fa943659502cec4042b0c3440b2a4d001ffffffffffffffff7fc136fb6d6b18d3be945b4a07a078418494524a29a5a43597904608620ccc39a37dce2bd00b25a50bfa0b040cae7ece890b9d9e22145faea4ea68f0ee3f885030957964ed663431c32897284109723d7e364f1825259dde991ea5e3c782710a2d61323b858051f0f4516e94857e517c35f154efc939b9c917c5ffdeb53c41bd28f8c669cdeaec3145bc2858c67913cfdecda15d14631237cc9a4717c55b3be1a1a6e4fc7f2e8aaf99d1344b65d2e2a25cbaed939ae616c5d8a5d24e90dba2247a9099c4b5a712f75a945fd469154d9aab755a1483c96baaa60fd1ed2c12c3f3c6666c135914630e75824ec295a4d52416a553ba7fceb246f699c0a274aede7dc2e7fd1126af28891621cd04197dfa4b5c51ea4fe26caaa91525e967639797bf65102b0a2a949c4cd62626f55945494cdbb7a1fb44f5af8ab2ae9fe6abb193f9a7a2b431658d6d8be6a44145e1344dd224f25314945f7f0cc22431bf3645d1639320e224753a5b8a4388870e3d3229ac30552709a35114b67edf3c9388131745f1f3861c9333acd31a8ab27679125e4b5014f379075b0d3f5192c49343680a3d51d4e89f4a9392fafd9d4e943daf3d7f3671a2249b3c97cda24d14e38a58b95875728f260af71eb29ae724f1f399287587babcfb13cb3798285d4972d2a1a3669ccd25caa2a964939e9127ba254a9d269e45875c8de14a943d35afc919bb049152a2183233573cac649f93288cb0d63d3125512c61f54dc8a0e368311225319e24fd707fa731489454d9a6939f3fcab47f444926e136861274d0d2de11e5749b324b8c8c275fdf8882fb7a69b5134614bbb3643b0dda4de61751d891a97afee43919a388c28b4c6a3637261105b57411ee49fd7d37114084ffa9551fbf3687288927d6b4b63686289649facda48f299b4d21ca273b6d0957b51b664388d2858816b9a127a46606515282dcbed09b7bf44910a51fd97b274281286f4ea5849970314f028892c7b70c42980e1b3c7f28683449bc137eaadaf74379eb3d79ba6789aaf7a1e8b3d72bae2e5e371f4a8252f7265a82c97e0f455122b45bc9f3a3b11e0a72fe6389f217d2cc43b94fc934f93141453c144c84501adb3c5fb67728687c870c919b134f3b9494f04d573a8b765f87c2e6f712741aa95d231d8a769fa9a7c4aa9d43a9646bfcb35db5d0c9a1b0af1b43d976f27c791c0a1f93ce256fe070d612aca49ede509453b6b5f1e386c28578edf4255b497f1b0abe6ef2a48d52253e1b8ae5a7d2d55e3f089935946ce3e8a4ee42fbc8a8a1749dedf6f4dd692897703ac9a96434e1ef4643692fc5d3fd27a1f7ee33147e4dbbca750e796f9ba1f45f8292132c4349ca52f2c894a66b3c3294e449a27e5cf7d9573d86e28ccff77c693a493194f4069d427a5da90e85a198af8312db6477dc040c25d39c935c66d278ecbf50cca632d4eae793a1f142d1c4b38ecb78174a25e59eefe75c2855a999cf65a7a4e75b28a997f85ef29cd2495a2875fcaf52326a5f67a16426c349912d3ee2c642d96554ef7ae3b6ea2b945389795636b6426935969e3631b44c5a8572f01c32079352a19c84c8a7faf6faa09c4239c618a355a3144abe397726d14e3a8c5128a7d3e549796fd20fa150ca4c25869e506e91711e5a5a4df69c50eacd1f563f74597b4d285d9f2e4dd741556e4c28f7c7134598c6727d0985d379e6fa4deed28b9550eef2bc1f6771124a6abf3f339ada60929050104a5072974c3a4249902faaccc7344b8c50d2d143e383677b0e15a1b0a20810a1946532dbee0fa3dc1a5b3f48f1dcef85511274e36f4e3d27c61e8c6274d538de2f27a40e8ca28aaa794cedb399ffa2a0ea992a4eed8be2e6b6dc0fdb309fee45e132aee5e72861c5e6454189f9440d254d1a39efa2985da47de9d3d9dfeaa254611fe46f43985e7351d48d26a74f2b3a4c5d5c9494d2af4f6ddea26cf2f4f368d319176d5112ea1d9fff32ccabad4539f409ab10329e7a132dcafe7e4a87886651127d593149125914a39eb81f64c9a2dd2416e5cfd5ab217aa3c4038bf2c9bbca68e71545d3d0a7d1d4aca8de1525e1644e25e3f38713d38a62a87d0d6e3d7a1f6745414ee74d7bfa2a4a52d4f3f4c9de24d9aa288f0c5f824cbafd94a4a29825d7e8383917f2a2a2ecfdc9ae2e3d453963eaff244b3545e93609a522a3b587504b518ea66faae3488a72aac6a07e4786dda8a3287d9b32df6ce663622a8a9256ff1b2557de6ba6a128089d3e568dc91f4e12280a5a1f3afe04317a4d9f28663cd9b44bf4de9678a2d83b4a3cfbfdd5753a51f20f3bfe6fa543f49c286efa586299b6109a6fe2b3d1cddaa6d644d9730ae1577225e69d89727aaacd9f6d333b268a7362960fb3e6254ab22f5ea277ddb99a96287749a5ca635889a2a6ca18d486f89b478972ef7a2c392725846812c58fd1941366fe799344414e29494cfb3673969128785413a693884962969028a75e68a7debcb22a1f51b61c1913d76df592234a2d1efb45c74f944e238a9bb9353bc49ff4184614f693a6986711c5ce49da5d2993f7ba224a8295ee2cff41a9998872942f15ab1411253109bb26fc7c88728c394e9a9976aa6c88c25bdf7ac8140b51cc2973ccdb14c2dd4442944ccc20e2f1348872feac5ea354c5354610c9e63cffaf4d20ca49b6d2bfbb79f4ef802806e5c9e433a56d72f80f0511bf0f4dddf420f543ba6b4ecd87f2fa5036c98450ffbc557a1e1f8a37d72a42e86e0fe58ea993e49a8493b4757a2866678a12e35cca5597876275c992d42455134d78286eed6b853e11254fee503ae1d3b3d6968c498e1d4a629edb0e2a6c4dfaeb707fa9eef4f8e950923fff556992aeaa9f4339ceb74b2a99fbbf1c4a4ace9d43c8c4a124c4866cf4f86f311cca15a2847ef78692ea29dd50bc92b1faa5b7a1f839e9934e926aa5936c28658cd66cea494ee2b786d2b9adfb67cdd4501c9d1f9ae4e0b39fb23494f4a8e930af0c0d85138498c8d9ce244cce503ae12d4268ff89ea98a1284ad0ce2e2a43f9cf3a95c7f394fbc950723b99c4cc0d293a632829ddb2af2363d2460ce554193f837ade98260cc51354cd5dedbed5602849299ab4449177925f2898075f7feb984ae55e286f9a6ad7b0bb509253ac6676b8508c91415b4c7ed1f12d944c3b68f9dc4937b95a288c92bfd7d4f4654916ea244f7a3c7162a16066fedd257685b2ed8799bdad508c7eef59568d518562bda5685262c9f9e88d418552d8894ff2c96fd62535865f1f43df791ac790c2b1bba57ae2791fcadb4796ccbb9549668c28146e7f3f8fdc33716d45c418502809aa9a1bd6a431e94df60031c6130a42c347c870a27410e7ae8e34da0f6338a16072f40f6d3244d8af6334a1b8ea25460d2a94204b6b3b8cc184627ca6cd261fd94fc7584223e366edd0e669f2a384dcec6e4e46e5c234dbd34fab6b93bca3df3192502cd9ca7664d049d0f9a0e00367a4b1038d0ee81cc6404239c9fea9923b7f8492ec268f764d9b318c50f6f598315bb224f104c728829b31c898e4c62042418cd09f32937cd3981f06a6577732af6fe661ecb3b3431788218c5228593e4af8241f3e16231885d1b97927da6456b5c40046312913fb4fdf6cb57bbf28b86be9bed8cd142bfaa2204ece1ff3a48df9a213a317a5ea146d3f31e6a0648d17799a90f93b9724bb2809ab29478aee4e79a318ba28bc881a9125753b44cf4531cb8ccc385d1a846c62e0a21c63ccace8e8d227aa18b728e7ce1a3657e9c5b0456973bed0fcd9df492b316a5196efa064537642c964a262d0a2945aa5a2b326691ec49845415353ca8791b5ee6b1e420c59147b4f4fd041a73263514c77f27bcf9534398d6a3ac034316051ac13a7348d5c17f7d22b4a928e313c9995c828b9a2783a26537247492bca269520de847d58511c93d2c7546a2ed3fe2aca1f2b963b26096b1b574539af7ae809b2a3f9c5a928852831f3ea2bf7a41715a56bfd8fa9169ea2b439ee9de6e839495213c314d6dd878c6d5857c88c67704d998493e2e00f94a26cf2ffa46ff949c2c3a428bd77c6d88e7514e5249fac760d1e3a410c51144cd8f5a7727fef1d3114e5dc415dd3a7f2cd61048a625f9998b37eb63e2914e313654fa3ddd47493b0fd8be189f269c7d57c27e62a3a6274a2f0267cd860b29a9a6b9813653fa1a9d3b8fc7b6f70b0f5264a6279697292732af9d5d344599464e6315f868387c1c1994874e4d8818e34182006260249f50c7a66285dc8365d27fd7ffc4d2c80942141c8504ed133a6a1b40f808ca19c43c98e1e93a869626700440cc5eed1edfb3d42689c71206128aadec71019f11f1a308080a124d77db0ebfd3c9d23902f14f4cc3f2f4449dd7ef242e1d204f139afe942a9636ede87d878e9692e9484da2a992506f3faec1612881692dd138f7bbd2b9b8dcf705392921de727a814240b6da33d670082859258af3b373ae68d1f02b942f9e418df26d6e3849e8058a198e4b8997766b426ad57a19cd6ff848f13fb72c36200a14241c8493131aecee8d354a7606ae93ea525b852285c988c1bc44d9dddc95007071285f2c99fe3714f780c966e1a205028776ae6787a5563c63ca1f0794449a59e64d43539a194d146bf9b74dba04b4d2828199f846d8f09456fcd6bdaea635e5b42293e64c932e19f9f4a4a28e9a4a64dbc125b9292500edf0cb2335575bca589312666fa47287d8ff44f5f1188118af14453ef29eb242544204528ca5668fb9fd19f33204428d9e6debe49f54df7304a26fd5c8b0c4aec9b85514c278fcc7d9e74cf9b04a360d5a3ed4c0a8cd249fef9f179923ab95f94357af96fceba92b4c41765d1b1d932e768f1a75e18d24d2b3ec92183170793417f654cb28b62664f277caa7e131f5d1433f67990497e729198249f18a4491f5c94f332e774ff27d9cae71625316e446eb4a4ae7b6c5114314a4eb1f89ceda416a5525b7d92d5afde4f8b72af9dcea437b328c8dc24fac6bd6fb52f8bc24921da4abad96dea63513e539954d6038b92be99d624293149dfbca2fc19d76ebd7145494cc949529ae4e691b6a224843c75a545c38a827ef5f8e6ab286d6c2fbd7b5d15a5729374c9723d15c513fe3a9dfe9fd039a828cdf9a78fc94665734e51ccce7d6adefd453ea6286996b5938da752f4a5289ebca7fe496dac639a14454b933b6f977632b91e45d9ced2e39eec64996a5194d43446e3a7181527a12899e6f40d5672507209280a26ee06a5315ffaeb9f2878b66ceb4bf9dcb4278a9ea304a53b63b293b9132553d116d3e09f4a8e39514e428cb4b6bcbfee781325b9c1c4a8d1f3e7c9d14431fc5e89a6f166a22407934b94c971fadfc544f1d3279d937eb89c732f5112563687a8712d51ead9f72c6eca3e2855a2a0840c5ab745747a13254ada5ff3e327e59cd0240abe49e9ad92244aa1f97a7c4c10a67d248a9b6bd2cda91512a5aa0fc2847bfa88f2c731bd4eb551944c1d51aed2d6ea4c52ff438d286e29951bbdc38892e9863a8b3ff7ec2ca26c5a4ebc29318a28099ee4eef0264dce49447946a991a33be293b97a88c256eff7f88d3279334431dfa82b9d7aa7c74214d35d6665d01b8f5512a2a47553bee56910a553512288927c4289794d8e3aa22410a5ac7b3d3973f58d0f88721acb0e4f6a2762ff434993e61c4366d10f2531cf3fcf98247ab6d88772926c63c9d43a2555e443494378debcfc74620af7500ca6e46ca684f450504a0999673d2e5f948772e6bccce6b95fdac143c9bbccf3ef42668fb903b27d9a311a3b94c53e3526c18338b1d7a13cebc14287f9c8a0431acac49025e86e0ec50f93d95a53aaf9490e25312ed3769049123a148792dc0e2b327dc8ce39702877e8c78d90d3a1f937144df6f0721f32a7d6b8a1a0a2ae31696a2d9db7a1f461f2eb24f127830da8d66a6d0d05ed64b11f4a534349bbbd29395c06b5494b4341e907e159c43df8070d050b252b4ddb4e09fe194ab15de2ae3d35fe3643e14ec4839e5d8682878c9b7744987b960c454d4da1a504ef5b750c85eff54dba166e9b1443d14f4c86a1a06c8492847752cae4c050923943e62c49ca185591f185821284be328f49bc5036cdb193d185d2c7ab78d498b90bf96f18195c284972c7e8ec2022630b25fff4252759e369734d8616ca49865b7daa491b75346464a1a0047d7234494ebe9e9a0c2c2494ec9dc488b014afa07e8e6c595beb9b143258e9dc1b4c4d86158a9e36759aad50158af5f183e82042849b1c2a94739cac9c75ed08644ca1644a50c2e9cc0ca5834a86140a3afadba3ea9d9ea024230a052926a849e2e2738c13144a99c1ccf49a99dc1922e309c5739153558209194e28cba8dc972083e93d6d1cc8684249838c98f69c3cfa6ac86042d9af34a4db9920648c93b18492b827b48971b7c4b44edab1038d33ae4086124a97d94b756f484612cad13289f6f3319824e814a273d8c83842ba5e72a76e2a73adeee5aeefa76272328c509c132bed6d32a80c3b838c222cdee9b5ba27e37a979b6df286fcf92c3149723c904184722cd3bdd32f0ea3d4a37e1fe3c99cec01ba51b723e9f84052460e1c1a70615c99fd4d495286f3abc74730d8d957ebb435d970153729738c3f4a50c2e3fe00463983be2fe1f3493b42f51fbf28c64fd6a2d7729531e7c317059dd7a6c4f5091d4ffde845c164f3574f62762979c2400acee0c18b921695f9109193e50477518e3d326cef248d1a7e5d14b3e4c40e4aed4e3aa98f5ce4339f235af77de0a2fc9b39cae9b79606257df8b845b1eb5d23b22e22d79bde4e5c8a0f5b1477844e72a1b31d1fb528cd9a7887f5feb8255ef7403cfc8316a5d2cbcb13a164165a8e6e6678559ee94e193dbf2b8b52db06d7bcd9feb2a1589473e8ab88091dbc44ce6b589446ab6aae6acfec662506f0e31545954f229ef33754e6c315e5ad1c7dab315bbf7a1fad28cca7cddee61de388fdc18a4ae45ab5424fb71d7cac223f54514eed7e7fa7df5494369924e389493c3953f8818a82979c04b99d1b36d09143870e73068f1b3c900d12742319541801157420cd151fa728b86c47afcf41679c9186294a42edfc2e6448dbdce060bb8106dac18e32eee0f0518a5255c9ddc9dc2608bd7f90a2a43575ffa8ce12e4d41fa328650c939adbbf4df03f4451fe92b78cc44728cad95cbeb39b32b7f6031425a5bb4bd01ead3196f0c7278ad984a713ec9150198a3e3c51daa4399efece9d28e96da74e652ef2943c278aafc9649b2869d33bd7a49310afb2264a375f32e6f7889ef59928fc7b8c8f11fb0726ca41942cb539a78a68f57189922466ff520bf51d77fcb04431a7f1f936ddb427c6dc4725caf55d82aa3b498925561f9428c5ec67ce1fa41f93b8eae3c60df7999a217c48a224c6c9f5e4159e8f486059eef5b266195af2e713cd7468201bd86047195c840f48a4fa447ab56f1e51141d7b83a9f8ac56c21f8ec8462423ccc6c7224ab25bac8493ddc418a1224ab2d3c7f3427b97bef79188d2cc79f4f01c5447e379f8404431e6567e77cca0192d1d3bd8a103073a56708882494a7cfc7bedc7fe6dd898c38721ca997c2e547709e9c8f1fb828f42143d4e4e8252e2ebbe5542145327a974e69609dd500b1f8328c92083eafc6964b2fccca491835282280715d9f9c14774f91f81289650fb1e7b3e65f88b02074439cb7bcd6ff43d031f7f28eef9c99b7495ecba267e30962c6a19ecfef3d18782d8347962494a8ac7071f8aa9943249a72d9de2cd1128e1630fa57e97d5b79acd97ae0e84831d2a401f7ce8a1249ade0d5be2a69a6641f8c843393e3bc9259ca7a8d195613ef050329d3c4db9dac9f369fee0e30ee51d759dfcc458081f76289e7ef713b404f3d8e2471dca25a819311de3e61e253a94849a94f1f53b3409f61cca9ac3576f8c9394fc971c14114bab73d3adbb0c4a36b96333a38f38e0071c0aaa4d79070fd2c0c71bf0c30d450ffbf91dcbd48d9216c1471bca191f93321db593fcfb830da5504a1237678a596ae8630dc53d25a23649825a9139143ed450f09394f42788d09ef6ff4843d935b39f32b571a3063fd050cadcdfa345d4f79916878f331443e57592a6e5ce4e6f8692cae7d6d3795286723655352d3a5a9f9c11e1830ce592d16cf5640ce5abd3196da5ec36490cc51069a2e75517f5416128b7271d4f3ef7943909180a635a3e94dce35f265f28891996413c49aa5fe385829877e7895d28f7596f36e13b97e65c287e1afb70a541b3df5b28d6c88ff6709e54af8572d691d9b09f313267a19894f0a60f2c947410fb5972093a969a3eae5012db842b7f3b1539d38715cadaeac9c393fad6317d54a170b2a9ede727ebf5d207150abad4cfe7d7b3da2d7d4ca164b2475e99dbaf6ee9430ae5dcf9bfbb26dc6d4b1f51287b3e39ecdf047957421f5028dfade692639227a47aa3f264d27102177ff22614fca487b0d94d32e34c28e7905d131b5a75d24b286b101ea4d94a28480b5de2a893e4ac3609a537134ec9248984b74f7d98343a4269ee5366759e5c198d50387a8f2828a9c3c42819838c498e3ba27025857727dbd49d34e8841a8d28e7b89fd15cbb7eb92f234c21dad396bc1697506311a5dbb041c8909b19548a351451eecdd0f922b38d6589351251f6d0b5cd498d106b20a2989a3de57d4eaa7108b351c3105ca31066a30621acc620ca310923d2fd4e83da591065d9924a560db24909268128b8968b0cb39edd7903a29c63efe74c42476ef487b29c70fba1f4d984eef43b69592509500835fa50cea0a183f0b039c886a883e34339e6533a62a34966f67b28e9ae67926a3d2625560d3d94cd3c6e77905e8d3c144778efc96db5dc20ab818792767756a59f35ee509261cdb4689126437fec504e27ffe6dea4a9434988870c3d275e830ec5fc7d6a32ebefcb8b35e650d29dd36ace58425f63460d39144bc70bcf3bcd554dc6a1dc9bb2833cf9aecbe4e050f04b0d9e74d8eb7cdd1bca2687d3f9e4706e329fdc503aa5e2a204e9b5a19c4f3ab32bb5c7a1061b8a49674972f00c4aeefa3594c4ea4ac65fac86e2a7bee71c4ead8c90a6a168fad2363da968286a79e6e47666ca3c67286e8a9f685956db2f9aa15852779e3939ba4bfe6a94a1e8415e3574fb35c8501a3542870ce2d47f4ca234a831867277f2164f1932e9f3f18c1a62285aa7122d733265525d612877b5c893a48a56d16e0d3094cb377d3c196c4f0a558d2f14c3a9941f7d6e59bba9e18592eaa0f4c4f0eee8a1e3035aa30b4535e9297e644ea6fb54830b051df4c88d1343851a5b2849632adbd3de28f15123d4d08266812de926a74b2c7c62ccfbf822f36848a8718592bcbb9b6512258dcce2e2a86185ab5185e27747cdf94c2dacc358830a3a853c5e37f4bb4d56c347864d72d6749ba5ac2185e209e28465b6129e7aac11856249aa3eda7c49f63d824249efc6b3b8ad8e2e264f28f75e95b0393e6f9de3848292fab2e474b23e86bf09c5f693a243bf6342d1c43e9dfe84934e69be8462c8c770a7d4cb4dac95502cd598e489e3778270935090614e6ee98d22a198ee35d4730ccbaaf50865cbcd7999e3462848b1ca8c1d7af35516a158e5354aef89f888b106110a6bf28cca4952cb4b378c72ea67a61399308abfe1e33d57158cf267ec20222ea4e650024631c824e6b0dd9b5f14ee4c78f649132332be28e7733feff092e4988efe4ce91f5e94a385f8afa88e490aeda2205bf2e5f249ba28bf956e894d7d2e0a7afc93ad6e705110a2c2eb469ca051bf452964a75ef524898f2fdba2981dcd2441f9f898a716c5d64d92ac21e26c54498bd2bce8ae0da349e809675138d1f4a7229f2c0a224eae93465ab1485814764f6aecb4d61d337e4549f254f2648618e28a92d4213cf4a986395385b4a25315cd9453b3bbf80e7547082b4c4356514cb3dcdecd1023ec244415c558f2ee8c8891d5212415a5dca0eddf4c0ccf6ab2a8280959924e9de3c163d939869053944bb987dc933acf3d892932b12f2d17424a6136485116f5bc16ea543a8aa27af0247cca754d9d2d4414e5d0adb19d69a497248784a2e4679275a715415128f965e1a6ff274afa293e947637c413e53631cdd3599e30f9a44748270aef27b3a9936328a54e219c287bf0b7536e7533af1bf208d944b9c7c3d7546a8e339b264ad2dd68b090692f8464a2b057a346650a3151d8e849c7a03636dbc9d920e4122549ace7c54a9628678e569e72bf368957a218dc44bce2f2debc4389d25dd6a6122b2fd96a12a58a79d394c10e9144496d656f67849062d2cc48944c73d467700f12c5cca3de3de85897cc8f28c84fe2d4ad04d111c8683039faed8a18d288d2a55ee972dfa4154218b1218b28ca86cdeb70a15644d1bce64fec51b5a24d9a89289b9b36318b344949821a11054d65a2fe77f5781c851ca27063d2677b99584ddf1005d9491263bcaccd9de790429464d0a67266fd1042147e940c2db23f832895507d8257ca853aa920caa1c4a44e5062ae6c3d0351ecd0fa2729199f3e8d802896aeee95946fa26285fc816de4fa61fb504683103e94dae4b0fee1bdd4da16b287528af26ce29a8e1e4a234d66caad38c9644e1e0a36ee79d448f95492f050f224269ad81c439eaa7728659de5a612b63a5b1b628792949b6d83c99689e3c93584d4a160d29cf49577aaf1e3cc103a94941cd578faabeac42c43e650d8a4a24ec8963243e45050f397f93732ba520d8983d9c810386035429aa4d4eacc9037146d830cf3582d0f296786b8a11c64a711fdd92afde2cc1047534508657227840d05116f5592673443d6503c39d693d020dd639542d4500e27e678a1944933240d85f7f5d7cc9c0c414349279d42bd833059107286522735fbacd03ca3b919cad9ed643439cff726bb0ce559f11ca6654b8ace642809a5cd2c444943c65036a9637692dca94bfa622889f22ee59a2a4d360f4331e6e7564f42d5e93ab3100286827dee244f5fb427cd7fa1789dd3c7122a1b84d678a1dc4156c8fc734ad021ec42f184fb9cde27174ab24919c254764908d94279e34369906d9eb14cf98490e43db5e98e3b2799215928c95e920c796257bab89921582857868f57a2c94e9af40cb942d93c6bce8a36618658a11843c3a7759a215528f8ae49b667d221542809a594b6cebeaada7ee61142a6503ccf3f2abb3dd609719221440a65d90c32677ad1cc49d45128898da546d7bcb4a7180ac51cba5517c254656ef60905b5551eeae97f9375ea84629244ffac6cd0a626994d2887f1d07042db661a13540d614249960a1d4cb4a41ab204b311a204b38137084942d962dd4fa7b1f7ac59f3400812428e50cace24eb949ce34e187f471936b8a123078e1e3976106204d7d246e64fef3baf0e8483471a67308e902294d7a4e9d123f385868729e3468f1c659cc1a33a104284f229f96ecbca3af38f86e97123073a084086c16e6a9b7d9b9efcb769b1dd1c932818250f264fccc1461e6b000146c98395da983b9e5d00f945e924695e4e0e1ea4efbe2895f8a69424de9ebd28e92e359749a90c0a20bc2888ce291df4677661360e48a7fddcde7a6cce454183de9d983be8a09964ab00828b8252f2b595529eb4457f05905b143f5eed654ec71645ef2d25378d12e3fcaf45c142c7f6e6cbdec974fb008416a5109a193687da244f5092a01e68ece8e3d103f1e081814501c82cca1ba2e35ab2094adc4c6119406451def23937a9c1ff7e332b80c4a2f4f5e1fa53355b3ba602082cca1d5efa233fc5e4d3500004905794a4fffead57ee9df538d8ce30e3a02b76d97ab3567577fb9231693c9eccdb9a896b2b4a1e636b9a4c726501082b0a3a326f0ef97d7269150535426852fafdb54f03a28ac2482f3117da54946414a579d2e3358b1215c513a949a509c23dc6e9144591d9a4c4f84d51b25102294531bf34e8553f35136b1052944ca7933b8b163d016414a5cf4d1bdff649b4b34144510ca5fc53e5abbec92f48288a26df2459d149f83c230828145126081566ab1dc8278ad9e12419cbe4c77916c413e50b79a639967990fd01e98496a37667329f7b279ae32555a8d1d124f76100c289d2c919f73c8b12e2497213aea94ce2a4e505a289528e9fd0ce9b72d97e104032514ed26389794f967525278060a2b449b69bafead1926c2d02c825caafa13505b14449109ea38e4942c7aa91412a51fc1b5d829d9e2fa11b1a4028a19328fdd9971432a6b5fe9f09209228f7ddea8bd4d0d1b3a40c241285d9a46d48944ccc9af13d06794479eb84921ae3db957c411c6136401ae18c0c257f3a99b971861a060c08238a3ffa37c9f6773a84691105af16f5b994a4a3788e224aa3bc4bb8ee74228a37efa34d12aaebf221a2781e27dbe9d7400ed15e8b5789567c696ba98e14298821b668005208274451fd4f923908b1415a9ba051823e01c8204ae959f3578569fd5e10853fd135f86810e5b1148882aac9536f4f1d5207447192bdfc98e80fc54d5a25aa88133f947f9464aaf59324c8fce943b9a349ea2ec52c4dea7c286f8556f150a2eda3bc87f2e9755dc6984d31d143e1646d4f33133e1a8379285fa8a613335484db878782ae2915a77430596cbb4371cde49c724d84a78aed50ce2ab249e609992a3cd5c1d91140ea5010ea318c4e2f49afa74a876212ed65cf4f060f640e2549a39ed0c1d472285db646ed1fbfb3ef701c4a155a258a3a3163e7d8c2a1ec1a273627ffa6bc92bea1a09e4cfc94f9fd91c9744341a8137574de8f206d28e778aa9a7cc2c3868d04614349f0f0994b08e51aca41f99bd226094f01440dc5a4a5ee6bd25db89e2c481acae7b5bda28411de49078286a27d9a28176935f624602681bc3a08e0ad51fcf21008940d6c1bf680d383471a681420003d780924480101e08146022ac023c719460101c8030002100009e0d0c1025c00001e3cde060104607a98c4460102607a9864071a360e0080013820013bceb88183637af0b8716301000840030e5010fb71e468d025868d71709fa571660642e3860e7224079dc1c3ad878e1c2718114571938c34993d2614e5183c4da88f260e3606457147c8ed2af133e9c9e0605b3390199f403ad8618619c88c038c78a224cda4ce0e1fc3c1e6da09ccd1a30c1c3ccc8d1b0918e1443187fa7c504a143fe9c2c1d665a461066ee20c338e267a201e99e8c119c70c648601463071898212aea32a33b544f94b9324af4b1669e5c071634719653d747c600395c01c3dd0c0c1a38c1b3712304209bc710301239328d6db49b5f00d6bdd273bcc904469fd84ee8f3efbb5eb20479246f6d0910379e8c8d1030d54468e48608e1e3ac891a471e34602462051aecedf24a4a813bfae2e63e411454f26d88afa1ceea2493970f0701e67ecb851238e28c8981426d96eb9efc776a41167d96e57b765ce88628bb8f895eed930b288f2c9f17272ffa699ca30370e8930c2c6a8edc14c27350e361d2389288931cbeaf7f008228aba272825ab453c4c1976839143942f46339eb03639d18c18a2183abed77f0ee741c773c348211e6d2f9f47ac84288e9c93841bef28a7321c6c653b460651780d3a4e08593f3dead86186193b0ea231228872f78919469894f518094441b6881256aab4c3cc8c1140944cdede8f2bd3ad494c051da8d48009441081835840021b3638808208a81da403df005862e40fa5d75442647f8b438729c30428d9a12371c40f3f18e9c3081f7aff986e996397f2d8d2601c18d4020ec2c81e0a232657f26192a8f78fe84175935d63ca70ba478941c3063a76b003072379d0abc1081e0a7fee59745231fac5be4369c47f8fb77eec50fe37e9c4658aeb9009be598dd0c1edb6da37af14f576bbad92af93321b994371b48961acecdb4b3047e450faf6be8e99b3f6a6b68b3012879256cd2bb7331ddaeac1c31c1d3bd8919c8560040e25efff7b3dc15d475638d86ef4008d1efe86a29ce4369f4978a0113794a4acd635755fd26a236d282729b3d527a17e8309d950aa974992e0a54d72f000e51646d660adae9e9da0c35e1851834169ce6c820c9e35c363073bca286387a7c193194d86678f2f49ba3082064bfb4a5977e7de868d3360b2768aab16997bd46766c0e41cc3e8a8a72e3b66a40ce718c4a90c259b74983378dc28325ce22721df942a0d2ea6913124eed774b4db6c9a0d46c47075c976eca58e5b7acc25ffded0b1a32e056568a0cf81c6e1f169f0283363240c25d3703dba46f8083fcf08184ada21d63bcdda7336c1c1da4307d2019ac0c8170adbe731d87b4c47bc50caf2f8d1357e8af0bad230d205576b55c462455e5e5e63833451539bdae6aa83112e944a109a45cbba5437bb71032f30b285622613733327fd068f1c376e9ced40e318336edc50632d980dcb4231889b206292a8694f4e8e112c94a53172858272cbced8a51c3218b1423159c9593bec7be760ca0f8c54a1286299afda2b73cb498562f6db4dbf2537328582897954de9cd81ca31f9142d15df4a4d52c7b3aae34a2501ceb53db490eb1f51e075b9fe580424949d245d9c8d1a296b163c79b05469e50b2cd2599aaad6938a160bf1dd36e3549db708e2614f65465e6183379d8130e361ba4f128b161c3066994615860c3c61a46985092c14efa24579e6e2468ec38088db21e3978e460812665a01c492b30b284b2898da89d514de7221c6c3cd8f13770704c19efa88c1e9f068f25010c6e848c28a13442e794b24ff2f97f70b0f5e08c1d68f460fb3478e848124a554295baf50d062348b8ca5dad43e4e2ceb6ecf73e892e8d1f43e88c1ca120d5fdad44ab5e3344c98d1ec80666c79881234900831b35468c50ceec2a294f8dfc541fc43152041f21425963d0f12445c9254e24328c729624e894cf27d95d2b228c9232a133948c76e25c2918c56ff32fa96411304a52d8f57d8af317256576a3f42631ed6cec8b62675f6a099aede43d7be1bce85da4882e188b919f482e58db38bdabab531797b53a59c4661ce1a26c5bda2b4b8c254c9cdca2741beaad1dba3227b14549e709bbf5934d68bb16c5e0a142890f3e2d4af7b5256f9a45665192c4f3c679ddc8a2fc9f6b357b1e759358943b281342f409aa9a6c5894ab4d3ce95f377642af2898542beff4415714a4f5061932bf3df6d88a6207ed6d1171a27b85ac28c996356adcf7b5c4d82a8abf1a553f345529495a44158513dfd87cbdeed17f9154149449fd2993a41d4f28828acc3336ddedec4d435c454d3e936f5553ee9bc829ca27d2f64b5a6dcd3b454c511243cf8849977aeea25294323c7e125f92dcd425a4289efcde229a2419c7ff5194ecc3a912449aa2289c4ccf183ec9dc9e178a92a91395f1ca0345c994d595d076c24de813659d0dd1dbdf16aa9527ae13c57cdf996962383371e684d90081c8264a9dbc836c921b370ff340e35108114da048264c04130575aa2d3be8cf254ab2471335d89aa80fb725ca39bd492683c922952849a6af666298641e9328204209b34104914994fb3dcb9957665125f58088240a278cb9659294474df2229128970a258fb4ca104a374288402269c226dfcba0a4ee118e4093d827a79caca912690423daaf3abd50b98f1751d04e57ebbb1eb69ed70810e280d161ccb881503a45944b5363ba27f17b53e260f5814822caae5f71b2f9838a7177947103a1614480831d8820a21ce3b74915993d44e12eb324fdddf100e95034508f0f201ba0814680876c800632a32c0622861029c49f738c5b9dc498ce149571461a3d140822841844dee13996795e9e25494f4d266464908ef333cc38d801114194f732c939e873d9201288c248fd9cad5774ccdf8b00e20fc97c8d1f3a496a3f942b93fe099be4932ec3227d28f5e433abaa7367d3443e94d3dbf5eceed3d89deca16082ed969c632ba28772b8af1efd26b56644f2606677acaaacbbab689e660e16231f37e549a9081eca66724f4cbeb7c81d4afa6134f48bc624f78ad8e110baefafb24dea603644e850ca7df37d134b9139d80444e450fa2c233fb24a72cb9238144ccef22c3f3a2f1081c31b6a4ff1b415b3b4ead2f604226e288e9055c25e286943316b90e5214c99989ef32288b0a1d09d6464da60d75983f14b52e23cce1251434912763477ec70a14f923414e363fca55cd506b9a2a1a8ff9b6433cd7b2e7486b2eee6aace9ff95436331474a4d9283fbb0ca5123b8f22642876101fdc2e4538d87a7018888006f4ce3066a0c81854d999fdb20ab3dc705d440ca54df5edff1232abe9110e2261289bf4da724df149980c86829ecf49693b25e6f4a8c81744bc50cc273f846cc99b84f8bb50de764d9a3c6b112e14e442f42b3b47640b9bee46db2b4992223b828816ca5d26c35cdc4f52b246240ba59419ff9c9a2ed491c30cfc8008164af1e193204b8ea140e40ac550755d95dd59e17826bbb1d39ec79c192255289db6cdb240840ae5bcf8baebdc9842d1a40df3ea61ace493d3d456202285627bd64ec29398bf0455d241240a05ffda98746e9127da9a2329638707747c80471744a050fe244788766f68a890b9c8134ec40907ed5311b18d211c6c0807460525d284c2697cbcd31cba27ed4c58af2a4c3cbcd43344968012e2d6aa3a09459450d2afa54ce5c47a934749a86b732dcecad54dce2b6f394d2bee8b20a15c9f993789072013224728aa9f0e971e967962c80825cd3e3d233cbf9f10294249dcdc29cbf3b87ba74588507cd33b3aaaac1ac32867bde718bdeb33412a8c52cf9c503ae63218c52a397f89e93260143779189da2d7f84579677b3fc6d5a4776a0d5f94e4dbb33f9ad45a329c418d5e94d37ace22aaa2a72cc48b921a69d29475f0f0cce1603436d5d845f94abedd73d0d864b32e4a4243a78ca14388c8b1462e8aa7757263d0f518fad7c045499079e71dd6c492ddad718bc2d949425ac986af996bd8a2209719c457eb667cab462d8abbe13fee9d125a9cd6984549f3fc56a7cfccb0230b6ac8826381068b621e398fed1cf48a729d76905182c815a5132d83b213cd248fa8150529e2cc359edc2debb0a2dc5184fcbced68bbfe2a0a72aa444f8cbd2a8a56163ad741c95bb5494541e6c409d6399b3841838a62be92459ef6f8298adf9ba6fd53b69dd9a6289a184fbbe39bd4746d290a4a8e32c9a429a9d75352944c924f99959cd913e5284a1b6b425c7fdc3da11345b9642e797e7a25de6ba12897924f942f751a5fb3366c20336cd87055d40045f15ad5a4103a7facd6f889f644f183ae3741e7db9863a946278ae139683b31a5833c8b13bd89d344494e7bea2c2d62b30665a258b29454ee254d34e660a218b2996925d6e494cd254a279494931b994f8a8d25ca594d38e15c359528f895dabcdb2f41c79812a519e9a3dddddb494c2751f83f59f489b14de9124914df327ed2b85b24cab1648c6cc786f8ca20513ef14566fa1a3d5a8bd0a834ace64282a250241689442271301450e83ca000331408001838240b0522c1601c89a2f2031480025724182e2c1a1e22160e0e181c14181286c3815020140885c16020281008048221b1863445f91eda66b3f484de0098263e7c8bf09a7afeccfe86a1b7716fa9ce8a9a86bfbf4ff54d8a3b1e0a9b441b0af32fa278a5fdf08dca78057dbb766ee425767565aeaf4417dbd3fac0217868de16bf20e7730a76c7512b7bc84e868d712509176113bc70a2d0ae4d4f162fd7d254e0ebbc9cf23dbe9c4bc58e63b1ed520529413f82a3602d656a2ecffacec3b23e2b3dc6c0f0e9487dfbeb67b97168336dedd99c2f7bbeddf429ce2efed2bed07dc486ff10cf9e364bb3837656b7b631b58f6c0aebd0aa9699ca2cb5fe9a79c6692c11c6ad82eb19f10db074594d5cbe354fe006c064eeb83a451a5c344f7e3f6934684daf72a71ea303e60242819f118c50c732b4f9277fce5bcc8e207a951eb07d72a0dc1c3d7132986b3e04f6dc8aec699ee861d0e23bdcf2be40a118ea180b61b55fc10deb063d611b8d38506958a89bf651440d206e1c8b08256201a922bf304c78a828c14dab4e6f5117167de8b351aee8ecd523b715d38438b7b26144d7299c73eb7445b73f238f030d837afd8036d4c1cc0945a5f9f41184b0ad71370e2556cceaeb6d68f4f9a1f27dbc9e4aeb2f88c00fdede338e3454ab01e9b6f2ac7b179ea3d2d30a08147c7e79c624eee4fd3dab184a77c635e99bd42aac4ccacd499f1047fd6c2b88630e62120398f8cbc3b9d54678f70b522279c19ea62549335a5c298cd533c799b6d192ce3b6195fb4c549a583789d43a3a722a1b4089eb72e87eb8043a51b18d6348602fe105ff0bbb889f5a4a31d199eb27ed2541be9530d1c2e241b38dd0c74c21831e10aef73cd8016ef429e6401ee0084a7c95e74d9ef662e38d98853f29c437384d7cf10069b7413f527f2876f165a5bac0e6c2a0d1dfc5828bd2396ac18cac794e7c52fe099d14609f127714368acdb9a157da2036ad397a3a930a3f9612f2611e8854f29788a376268168751349fe0c046b524e3abcac2df1a21ab2fb5a821e721382302b687a1d2c06e009cb306c49831e0dd8a13ba2631edf4d7c3db68ace502ae1562136d0b10396b2ccc3c27b4b89e14412fde4245c11c136b49fb40db0d5297b1a4b521fc710d29a57050b3dbcc2e0af1579ef0e6a3e40573b4260191af67d4d4353f5513bdb2beaee9ff5b91f5bfb7545f94af2e02f0bd47e966a62e063c141f80d427bf1277eb4b37b6bba495ff5f4cf01db34505faf33d63fa6b362d20aaa05269c87b54a33abf1b5a5c50f1db290bb8b2f6b49aa7b2a67b3366fd93a9d51d0475a977bff5bd397874fb79bff3f31a5781c165af27465ae44cd5f725f02f53559365b21e30413c64ab17b2374e81c527f9da31c4b3713fa1041e8f4ba3585092133a77ac7e9f6d29c8a46ffeef37c868428d7c1a5972c29d59670950a0bf2e9dc4cfe1b066f47620ecb8ac9de44dfd264f4c7f24bb160bc734008c45d080878c0312bc7486617e312cfdb371bf01216dcec16689947d5ee21bc2a04979623cb0c44ab4d04e3650c9ca9c900af2a2965e68ff216f75123b29655b59790edd341ba1ba26220a5ee3ab8c54f87424f4cb07b346ba520c4835cde49f3bd1b296addd25ec61a5ff350bf20ec277b2d00fed16eae39d7cded921ed22689cbfc75b88ade809d82b0f5957a0fcc907092bfcdc5df983d247c22b38b829f62ad087a03e6d6cd7753c28035c4783f2fb29c44e15e93745c29eff53e562c5328cb52d805202a9776763afb4abcbae38715d9496ebd9b9d9fa1b54bae8544fe1e25b9adb7800a4cc218ef5d642d9dd5f903c395975a0012aa4963f096afff98703df964dd562f363715c6be1049dd64a40470b932b7de6239577caf0e5dda32d64d9fb3269ed5304a794a44b5d0e83160f2cc086e939c008cde5d3453a622a29762510914adfddc0bed03ddf635c10d8e1b15a23ef0a6af1a2b0fbaffb84d3f54bdf44d8f9b7b67dc823e9215a4a17b9fe10e78d7eaf87afafa2405c540e1b816ba132ad04e5126dee44e7b6d1cfc2ef1043af4fc3a87b77122c03d518ed50e62072deb1af4a8c65c436222df12121d7f0dd661e7907dd9b94197b6d4756fc56e4793f83c47c2bbaf6a61817932b803a84aceac8ce8dd482de3498b74f00d95c50560980f975bd725f6203d4d280d2bbdcb7a3089f64d73e7c5b7266ee067247743ed5919d0b3120c8fff9a141b64a4a9e887defb30af78cc461c94aba44c4f8c25ed42ceb2634143a46b48de5a0355b49e372e0a6f17b49027792e845a98e9f931095cb4119171039ca1ee0a5744ac2ae74e9d9f4a1cdd4aa941955016e4a7c7fd78ed7b02d0639cbf4a5381ad3e1e0f6e391a9f366dda958958ed55b96991188756be2f39839741087f7c1007a49ea0f468447715399bfd12fdec82d9cfc448e86a48c9b7bccf832757b2c2ea5cf101bc10a4554c6b43d2e8015148fbe7c90bae2b46ab28e3bee1e6a170baba508101afd29f98a7ceb3b4ae02e9dc5e1db9491a6c700432dec427ff6051b350e676fccd965423e4b4b5eee1222a1e853fc4fb7ebecfe271a9ce85d501fd6b9f4433594a091f02a848869a19c0347c900ab2d4854c50b6218848c50cd255ac36798bb7e08987ba76576948e2b36f9e719992c3cd239dc3f3ac266dffbd661b9e11f7195076bda071e7643aaee41377e6e3e3289af78160c0cd6eead21db83a4641bd00f0c838ee92722ade10d948f798746155a133751aad3bf71660d04b34596739e604e1369ff29974348bfe398186dd0ec0b49bd436c01592d35fdf3dbdf896df9f90033527bc0899db68bcbb8060c12060c996b4b78b1d9d853bcd25b9d8e036a4b6b0df7b391f16635b49c298b0b5e0542914d75e3e5c44d13eebb2680af20d0ca47aab83624d151a30d68d56b4b3f922c4770ecb81c9c2c8fed0f0ac6f6276292fcfbb0ddac97bf351b0ee23a34370ee2bf41e8c84be324ebcf63d051f06c26f2d4659400aaec6f57582f569b4f99aac3dc69a5e456d827a6925843641bbb89c414f7d27769c60676e3726d51a0b6b30c5e46231abae90bb3ae07fdcec30ae460aa9300375dcef227682a9cd577de368d6f69fd65fa528ea86f347ea54e698b5b04f2cc254680d6d282560d434f1c0511bfae01a6c24363c809ff47f942b2ab20a962188accf87f09878c1b42df286642fa19678153c0b0fb2a9fdb9e46d4910662508f997a7e52d2130a59d5314af49410ba3e145454a627178a03b467193c607807c3e9be2f90bc7e81c0acd76a8ea29258a19e9ce971a6eb1dcc717c2b6a457eeeff6c93f9ce9e5fe7b19da705c16b3329edd0bdc5ce9c611ebaabb0dc2439c3b19b3362f5e3b403754b0e700a6d7281895f724b1771a9c845740853fac0f5f576927937825e6a4d3a375dedbd99efc2a7f36b86068bcc1e224065dc1ba713a15ea54aaca6bc3ae337514a4f5fa590df3b38caf469bca5557785aa921154c9b7880d06908b9426d77e33b2088405f176794c77afd49603c027be8c563d1dfa5db2dc9a5a4d023b1433fd2c5c16523d4e5864e45cc44622063657ea669a1a36582932a4e305d8cf564962c52485ed019f86add313a5b1fc0f6645dc14bf1d491ce941877a52039bf356a5249b62ab8f173a31d459baa35d60c1bc0154da33b9b0cb30b949f33f41a54488b3a66232949cf7d61545830af12e7208464ccd9a2e6b43972523214b1a5d0cc52673b07451f1d6d68af45b9e9e4930ca58a537512d930ff096164b6c2ca4cf3335c785395bef783222aa08c70a92484d1daa965ca0113aca0c45f1167cf04269fe3e2ebe839d732e88d3d2bdbf91a011b6b21bbbebd3c768cc0fd1084bb88a89fcfb43387608b771cceab9879cc49dea1b32720aa294bc80e4dd0d244fc0b877c016b27ee95c23d90c02ba70f4370c1ce5d2cde2a717606cdb86e9f570d69bf58926bfafa7446a9d207100bc8a19d61b28259e4d066728c4123558057cc91acde4275775c597b19f67be4cce00479ff20032ab77730ce3954941308414240a6c84f65ae6918fae8b159f6f10ff4103b94cd8d671f33b4850512c7565eb263319d30d6c1c02c952f60dbeb9baeec240f002fe6b636e993c96fe6b1188a1b8461d25682aacd8e32e2b476223f83926e3ac45a094e377c610968a66cc9b6c83b15649a0ed45ad428ecb1432efb4ef08002e8a97a8ed7745b82d4af3f473cb5168fb25d6f9c2c9ec70dd1f0eb599805e2763fb0f64352b7157b6a544f58a9cd6442af6624873ebcc165f30a210bb56be53621d543ddd091b1d096cf86230661d03c34e1cc8cc6ff8d84168207d301ee93ea7bae340913e82320749420438e1220dc53d24c6da52bf0e65d15035a97d0dd1e5ab6203f5d1aaed9fa50ced5153e60d8597a51bd76512fb3d4b3c29dc650c3901d1f3f7358c16c2c6d8ab2eadeabb419d512ec3e6aa9a94ac3868c7ac3a2614e2f2266d40ec7059a5048476cc2089d8880b0c4f8a920c259eb59a20843cecf6f45f99109f1992153a273a04e0a22bc4a9b39583d444c119411cb9e7a1884aa398700d589dea239fdf9b775000bee8e2702e9f0a1a99923b387cbb60bf16c8ba754aa6d34326bf081bae65aec155db417c3d83e30a0023436af4f51377aae46ceba71192f4dc9db89d626815ecf72592103045f2ccb2e487fd225a82e1b9443e8089d436822887e9a059b163324dd948d27fdfb28cb35fed1f8179e13e4c67f96b65999472e0ca902d0573a9b44624d7b0c094d6b66c942a058075a9d0ccda3d8a02e1199f4ca8d8c08d4453898e6ba988acf7294ca6c654bc26098aca10287983dcb7b6d35bac70fcae6bbe27e8fb32f562d0b4adad3a613217228e73b17459db7f8a781d43da8adbb408aa43179f2f8fc5e80a5853bae8a4cab3b68618428a2d309a26f169f7ba339e4280f3ea589582edad4a0c2893df47568ea5a1a0f3b53bb43da074753c79be0e8975917a3528372a1b72bab46a81086126d0f8e08ad7cf7f014500050260e37f6144d89373225f11c00f3e38970b1dda4369425e4a402b6e63284b3be6952819ffc249de3c383218de9f63447d0269c1682ed45b019a1a07395b363ec9a4a21bf4935c6bd6c199f53399f027c439e599cd4c6262a59c9d110542ed0426c980273994d9c0fbec9739ef4628cadb9e0191e9fc10b9f10175d2a40a0815bc20cc450e5bfc395a8f5fa2fc1b620d68945678c86cbb20a7c70d3b3a7fdc30d87e07cd9d71aa784176068a12128b0a751e7f0128c09eca83c564821906bb4a0bcaac36e29d7f590b74f9721e719acb997b9cb629cbfe2f1befa98d95958bb58636ee7bb92be5b915692c5f3de1125711347234867625c36235950ee95f630feec6eee04356cb8592e5e0933cc7aabc16b448d47236acdb33016aa403ba4366e5459c85dfa22722d98d45a2355dea45efb149084212f950740cf8faaf8c64c6add4d40feeaa5b8ddc0528ab37ba855b6422f5111ce218ec73b6d229d94479312b6875ad9520452f66194c2031753fad17f7ed4ff5dab061843d33331901cbe60e1cdb2f6924175be3c8d1fbf19f265a3e6814341ed5ce964cc676ca903cc70740fe2272fe4bf8e6715674036ae56d1334dd12cd1bd6b3eb5a6f5f57a553abf8dc14715a64890660642d0207034fa48e59b849fec3818b0ae900484581b951345ae28a338bf78a4b82ae33a7fb52965a0133d3432ced9147fd093839ed2cb134eb7a8667a6bddc680573775cb7f536ce8423d6ebb760a4f4d1e83d136520f2090e113b86f7b5c00040f43404ccf9e90fe7ae0ca830a9123903790f29903e3a527ab8dc52fe002b474ae207a0b5680fe1653f0f3205098211e8b862e1be2b93d0b4a780f15aa402acac992d97ebc1ce2924f59419ac5f23d714b3e4c90c05e2242a001fd781c6822998e5ca8daf02c56bfe3845555e1111fd7030d7a140b60998f468154cdd0049ae78ee1ad7d199b6dc6819aa58030e41bd72080ac4daae921595808f7410e1609cf06bafa2fd13a61f9267db54e35bab8c4cd10cadce8da75ae1558c1d7d470862b8700ecf8bb509badba8b027e3fe8555fa6da55f32d787d4b8dbc9f6f4af7e5cb2004e9ea3377cd847fc8d2147eeb1c6c01bbbaafbce946d00ad8689c8005a9ffdd98c9f722b9c92c9a149471efc2efe3b8ec07aecebe8a1e8fc0872da1fb9e672fca4cf44cc2361457cd364930bac98b35cc1f03f536a94b379af487df30b0450c3c47934deacaec37328ba62c2e1ba9da27111010bd77e82e2933356073727624e4b507462c12c1316690a4ef5a801fb6e135e1f3b6f03f8c11791ca405c71f90296c2429cce8793990b9ab30bbe1f01587a7b47ec2219196ebe4026e705589b10b8fe3689d04f6c17eb4123b1d5980eeba5c8d28d7484cfa3bbf90f435aa877995c824b05d33459a4b6300166bab88e53e9c32eaa6afdc7348de196cd6d06625d98d07e27c0bc5944b1b16bc7bb9ff3e65eaedde0541f947c10ce600d6c37455d09b2b17fc830193eeeabb3ae16483842de9b0495bae402c2d0611b10c92a8a06582a98635e7278ae22921fab13561bcb99bb2868f9f0d55a047815134da75592b78a045a39d63693790d394818a8a8ae645160b802f621726c473e8c1b57b733ed9a48420d877ebf1cd839965e2c70c8f02ad31ab3326b08614c8a619791f2e33fee51b32b4649383c3961658b264fb6b7290fd5976afa93d944bbaa77f674478a843a2dd414e1ba07c105bb2e99ea216990bc2300af638a6bbde1900b8271b629cd7ae33686fde7dcdb26c7dfd7b9b4801005ad6e6370e6a16fc37bb1d0da19dcbf4eba81377f7173b404181e42e40271d6e0e34df0cdb1265f2421235669b02a278fdd224d6a76a892b4e216390de8c3f738bc3d0dc84355d29d86f3dfbb049aa466e23908967d1c845e9972ed70978a299602bb5e99ea991105b63836fbdea156d883ef0432c28b97300d392abca8ae91de9154d3e779eb3fd78707122839a1b28df33df3b8a05dc61c5da3bd666879b206645db775efa1fb2e005e7bad26030c04c1d2a46ca81c41f15c1bba857581a819cd6bc23e6c4cbeffc022ccdd4b055581b92ada311b381e5e2bf7f401a28c734e5b737cdc69db59ba5794bb8119a000664f719d4abb8866d342bd4186d8eaf6f8b95a2d1480fd527461ce8efd1069404eef40bb4f89eedccdf8f845136ca3092889a67cbe4a49c175a20319632a413cc5aa9f333c2a2f18e2b0f1d49425a153bc32e7f2a202e972858e24515f8224205c5354b2acc20cae966b93e6791a24f68d1601afe969e10d4818a7ebd891151360a02d4b09ea995c6fa53bf9b8a87439d520bd640ec5fe6327bf540718d97b4a6812ffda5df99c0ee503bade46ac01a6184109c52e9b0dd5bdc463a09760d489ff15b6eb78eac3bf3e17e224af4870b3ace2228553910473ef381fe426bb44df863bc07fc33f7543fd7340610cc3cc5ef5ed398e4c580c405d295ae02826988235d7f8405e537362e14aad1a1d778d3035c905eafbf5ac72d4787bc7ffdf4e3f6b31a1ed2747fe07df8fee8b2628c2ed3474616eb34eca14c3a260201640f6f2fa8c7e53cbd1559554bf9da0b49803bcf76b5816a2685b3a8259f20af2830def0072c56c09b0ecace363532e380b8ee1820ffc5bb071d85917aef1a2ed0ebbf17febf7f95130235d09c690b44e6a07cda984ea3b4ca34070703f881f3415479c40ac6c89be52e3637104aaf9aa818e420e7912d37181e8adc520a2823e9ec4fe3458a48a842ec436b908e4b093afb85debec0af20e65b56d61991c4bacce23b01a4e0f6d6412e7890b548711abf29b85e9200cd1313214d3d9889591f31300c4df06daca5949b220cfc15562ea387fdb53d1099323b19137d92998761fa4e20256195f36cceb0539b75d66a509a264570571f60ffa66a11518ee2f1057d0a1bf27a906694a26e6c10ce4225ea34131c3bcde13d06c483823bc8933cb13d08437b1578b9333a0d9b1089144b150d6b5f8ca567d289edf4e156202e45b579cd9b398b200a1f35e4a498e2f10c158d853d7d9a2b70a45808b8485d881f23bd765c841e949eb168774e05f7666db5fb308ee4dd9fea2fdee11908cb550af9ab2260dd3e9e5052404e911dfbc779b67bfbfbc08ac60a61d21191eaa23efcabeb97e0fa4e74df5d8493e86d02bf709eb29475b532db2e2eea5c873b1dab1338d3bddb292f5f865491fb487e9d7390456466485d7a5ac85a6ca77186bb8fef25849d8fe4904f7c8bd813eb275a098d9a0ea629013b0fc121b47b36c3c0aacfbc1b982f2fa26a5b2485cca02c935e561118667d79e880908cbf230533c1becce6479090fc38894ddccb7468c374121f18e89833fbc20da847c38f9c01ff7a3e36c2475205baabd01ad4a8d97e3518629967ba054f5e5fc47c61e31ae5f4cb40545c692d2f54d0e5898fd59f0dd2493ecbd93e9c3d17903ed65b602b80aefc8d18dd4b2fc326534fb54c26aa53b6425c847545bb58ba6ccc82d102c9c092547c4ace0bbed51906b72ba636f2c254114681cc09ea6826b572a08d79e7efb21be7aef4d95c92ab2bbc99113cdd4a167e2996fa2d9cdf2ad2d15bf1ff7ffa3cb2b2131dbfc53612594813791144d82e37bef0abda99049aca086d874d0766554cf400491a16335e8afa085d720a3b8d94ee877667d6570374464d5fe5f025f6565e0beb888e38ca0af16e984b471534cbd9e6cfc20b3ecb63502a088162fe4f0b519175a140a4a3a32bd1deacab650e0099430d380f54f4e92e408a23224aa7816bb3e1b1e50f527b6abddb898bf441ceefa7bee9c14d260bce6eee5ab0b890f67a4472527a0b7202f9a058ee0e308288dca01d7121a479158c3a650acd60a09870561744900f3a9a97920572b85805de06f64573d9b31ae557b5cd32d89315175e8932b4ad1e16543ca3773fa6882514a940de24c3d01adfd8c43704a29642e7651dc605e79ad625910aa87282a155fb6e9af46a5376a1bf304ed53b4c25115039686c20460bbbcb65bb2359b802b61b84edc90cf76a342a2795103496cf035640742c2926a02f97bdb2be77fcbcb9b7950c47e6ae49289e5da3125bff54cf9604515b032457fd4e8f927b9d0ea2760bc8f7d2b6624615fc0a24cf83cb9ad85bae654e0b4799d3e60cdc7ae11d0a075be573e86201353438323f1c941b8d84e3c10881876c664e29628c631960583f3b84366d19c8820f3315a435fa105cce489962d52cb224babdba4dae7c3b9797de9d3569ad8071cc87a13434f4aae4cf36b736d0d5f8255355ef78c9c5f55aed25d72235d878d384125fa5af3b2c7d7e2438175a4768c3d066d742180be8bdb5f046c99eea9b9938ae0d01dc6a817b6b408e517819784905f41c606eac8007f61e8d1d9679893a358e55d10d77e0e580853571428aa3648204b03faa9c449879dc731a2e24aca0d19f9fab89d0a23e62cd5eb362434ce75eea5f0838a6cf8f37392f1901731a0f9d37d6e40116b40378e4a22a5afa17781d85b912256699c9a30b26c7acc98da480e38b7a4d1fbfad57adc907ae667dab1626e5e5bda5a95098b3526bc9c7cf6d0425613012ad17933cf176174474c9374a7de40de970b9883b0a73ca125d431deef291d8bc088e00512beb1fdb3620278da890eaa2d50bf3824690d95a1d90bc905e7038784e6d519f2a023c7b025e93f96e345d3e41887ae6b59680d377893d5c22d4aefed437129b03ca444380453ea82787f2809e14c94b9caba665cf2bc59015f15069026086b6f0370b783cef0d79b3092e27610d0cbfe3557afd690636de999b2934b2f5a10b1a9296d2c553bb844e202cc7ddf05b7b9a738e7c9372f58a1c011934ad13b7cd4c3a1350de7b1de1fa67c64a166652c8416eb939f4bbb246188ba13ef9f1bbebe42118a6734064746a48d215c068d8731bd8edc1c1d3f97b1d032be7f1391402c41321253d9d83da81bf89901e350239cc928eff04d1ec78517c87b68c20d34f63c646789c860c6cd332b907c38c948280abd5153535e10f51662a3c7f6c55142b436d2df54dc7bf42b424c924e952f2e615c91f70bb431724d89334992c1b2ac1d866e4091bf05702f01ec732027db2c5e7f15f1d040e4d2c8b3e85be21cadd6d42a08ab53680866c60377eb9e1ef4ad0941191e31d54189f1bc83b740dca4f55f4c77ba28ad0932f128a149cb7bbe6dbfc3bbefcf3b8a963cc993196e97296bc476e2e5321532436d83187649af339d2b1cd73a84636c21929ccd23658a740afe13ed2c31292f8c4667bc770ef66c7e345261d39a63cad3a05bbd1c9437c1f88a32334391106c268a791ba7bd394b702601990229f07e98f5c733dac2ca4e765f2221ca3c3220814e6f14dacc0a89c013097fb761fb9ab209c73c013bfc7cc0c329230549285f86421b547ad41432f13afe5d1cefc24deb70fcb63eb82e6d7674ca81415da996887a59786da9be03f19614a6bed8204a929f7b46e5dfe45c8772f25161bad145eda121a6b4e90bd87c85a3eae9b3f9a1b5ffdd538d399cd5aff699815fa35ddc068c8441a12d63aaab0991ffc7868913173103ea42a126032a74d66f63ab31c215782a95895dd7499e4998318d77457c770d3b5b5f0b434252e4fcef399f71ce4f8b7b35696521eca92014be54af0606d1b669641d25bf3911cf2f5af39da873fcf09c5c995e0e4b3da6136185453e41a8644b9743a8777398c9d828c151012aeb5e679c5735e8d5543c37c1d6f2e820df44f9a8fdd17a17b787d58bc3b1eb6605d2d0527ed28bcd63233f80f7aa96efd516cc4ae556414cef1f532abc3da6054f6d1289345532a90837beca7a3a89cd23aae8e182eea0e8e75a2c5e803e97d17577551e7704e7733bee88325c852a46fb3cba275537cf88b959a871d6fa2e1ff6a6e65f7e11990c5f59bb9b41be27d487399d07f862c3d8df994db431a7aba8b905f349902211b25cba7e7391b7388890acaf38cf7fc20cf76aa0f6b85d9db60ceb9612923b5a3d04a25b529d9e8d38c45f1d0ed32212da53235cbf6c9dda30d4bb8811cf3d2cde8302adee3262417c1a62307f1972717317455d044404111c41abce8dd4ded238cb04b175774c6ebb2c8cc0ea2e14cf1287a19bdca8b94a068a648f7ba0a576858c10bfbd1fbb58c2f8c6072d9d74c986a5464b8f746b35334ce41f6a7d095df610f4a39b7fb319de21771b53797a8e45685e5cb46b068353ee6df8ee937aa9026b87d225b9a9b23de92474e69c2992c31db074f04950718e712642d50e9591680c07bca6502a0bb3f6588cb25fa1095da5bf34e50465980c4e82c017e478cc98b2f57a5dd947b693ec4b43db9bce361a14958d2a1393b938eb06ed37dfba8573337bd2439a90bdbe86e9d0721eca1bed3dc454cf8458294da137dfbc0902709825aa11064255d594689046da9e73e822d0e2103b5a2fefb74010a2565a9a4990f6009d77c6e49fbb196753b7bed757a9380e34b4a77774db3a9067d938efa07dba3fd4975e53619cbb67630354f975817261c2b00826760094b9ee6a8637e9c6522c8bbe980be19ed91d32ea7d258bf212284740facc727657a83c36936cc418e00ff27f6c11cc97bc0d71b87f0a2f95786cfedf867e0ead226d4e3051bb7a40647f596b55d664d4dc19bc4db2b77b549c09b4b65a3fdce5596fb726b7db511c16df48b85c75e9e6c8ca5032227e1cbb4a887fbdd62d595058532e8edc4b5efcd21e388a2f6b6f792a30149bf6cbd562da195d037ed2433d29513e41cdeedf06d045a0b6f7300d6bde786ce9bf8eeec87effb6ce3658b00317528d9050a932dc3f0a9c61fd6fd09703e5c60561bf620af84af3777494ab1843c96890fc3cfa6a23f7e0fcabb46ec30f40757b0c5ddf3775ac0c9d483546d093e7dc19e9b9dada60ae7ad8bdc3b749162ab53b78af96d30e3eafba00db3f323d3b495b92b480ab041fb63559731bc208d25f0f5f2ea6155a054fdbeffc429ca7472e402854c811639320a7afd46ba8fdd7f63d57d0d143b593dc7285ff29440ac391e8a0590030153ee3f66b292bac72a00b755d06e0fd58600017856df7d1d4a6c3c4e86c19f1ffdaa3053638fabc2dca97661eb481f28a5039c30d0e07e957f67d103a40094c5f3b7c82a388c4be410bfac48c23909", + "patch": { + "balances": { + "balances": [ + [ + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + 1152921504606846976 + ], + [ + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + 1152921504606846976 + ], + [ + "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y", + 1152921504606846976 + ], + [ + "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy", + 1152921504606846976 + ], + [ + "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw", + 1152921504606846976 + ], + [ + "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL", + 1152921504606846976 + ], + [ + "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY", + 1152921504606846976 + ], + [ + "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc", + 1152921504606846976 + ], + [ + "5Ck5SLSHYac6WFt5UZRSsdJjwmpSZq85fd5TRNAdZQVzEAPT", + 1152921504606846976 + ], + [ + "5HKPmK9GYtE1PSLsS1qiYU9xQ9Si1NcEhdeCq9sw5bqu4ns8", + 1152921504606846976 + ], + [ + "5FCfAonRZgTFrTd9HREEyeJjDpT397KMzizE6T3DvebLFE7n", + 1152921504606846976 + ], + [ + "5CRmqmsiNFExV6VbdmPJViVxrWmkaXXvBrSX8oqBT8R9vmWk", + 1152921504606846976 + ] + ] + }, + "collatorSelection": { + "candidacyBond": 16000000000, + "invulnerables": [ + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" + ] + }, + "parachainInfo": { + "parachainId": 1000 + }, + "polkadotXcm": { + "safeXcmVersion": 5 + }, + "session": { + "keys": [ + [ + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + { + "aura": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" + } + ], + [ + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + { + "aura": "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" + } + ] + ] + }, + "sudo": { + "key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" + } + } + } + }, + "id": "custom", + "name": "Custom", + "para_id": 1000, + "properties": { + "tokenDecimals": 12, + "tokenSymbol": "UNIT" + }, + "protocolId": null, + "relay_chain": "rococo-local", + "telemetryEndpoints": null +} \ No newline at end of file diff --git a/templates/parachain/node/Cargo.toml b/templates/parachain/node/Cargo.toml index ba5f1212b79c47b5204dff0a8ba2da4a30af3e5a..ec4b13b184fcbe1db86a03f04bfee43a6e2bf81b 100644 --- a/templates/parachain/node/Cargo.toml +++ b/templates/parachain/node/Cargo.toml @@ -12,22 +12,22 @@ build = "build.rs" [dependencies] clap = { features = ["derive"], workspace = true } -log = { workspace = true, default-features = true } codec = { workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true, default-features = true } -jsonrpsee = { features = ["server"], workspace = true } +color-print = { workspace = true } +docify = { workspace = true } futures = { workspace = true } +jsonrpsee = { features = ["server"], 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 } -color-print = { workspace = true } polkadot-sdk = { workspace = true, features = ["node"] } parachain-template-runtime = { workspace = true } # Substrate -sc-tracing = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } +sc-tracing = { workspace = true, default-features = true } [build-dependencies] polkadot-sdk = { workspace = true, features = ["substrate-build-script-utils"] } diff --git a/templates/parachain/node/src/chain_spec.rs b/templates/parachain/node/src/chain_spec.rs index 7ae3c4900e42e394cc8b958b3d6dd3e9e31b705c..d4b3a41b89692aacd33e1ac6192edef33bf834a9 100644 --- a/templates/parachain/node/src/chain_spec.rs +++ b/templates/parachain/node/src/chain_spec.rs @@ -7,6 +7,8 @@ use serde::{Deserialize, Serialize}; /// Specialized `ChainSpec` for the normal parachain runtime. pub type ChainSpec = sc_service::GenericChainSpec<Extensions>; +/// The relay chain that you want to configure this parachain to connect to. +pub const RELAY_CHAIN: &str = "rococo-local"; /// The extensions for the [`ChainSpec`]. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension)] @@ -35,11 +37,7 @@ pub fn development_chain_spec() -> ChainSpec { ChainSpec::builder( 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: 1000, - }, + Extensions { relay_chain: RELAY_CHAIN.into(), para_id: runtime::PARACHAIN_ID }, ) .with_name("Development") .with_id("dev") @@ -59,11 +57,7 @@ pub fn local_chain_spec() -> ChainSpec { #[allow(deprecated)] ChainSpec::builder( 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: 1000, - }, + Extensions { relay_chain: RELAY_CHAIN.into(), para_id: runtime::PARACHAIN_ID }, ) .with_name("Local Testnet") .with_id("local_testnet") diff --git a/templates/parachain/pallets/template/src/mock.rs b/templates/parachain/pallets/template/src/mock.rs index b924428d4145c51ec7de253d59380cdb8dce57f6..3eeb9604f015334cd71c485f648b0dc796d33a9b 100644 --- a/templates/parachain/pallets/template/src/mock.rs +++ b/templates/parachain/pallets/template/src/mock.rs @@ -18,7 +18,8 @@ mod test_runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Test; diff --git a/templates/parachain/pallets/template/src/weights.rs b/templates/parachain/pallets/template/src/weights.rs index 9295492bc20bc919fa7f728260a25e9f0de31005..4d6dd5642a1ed3314e153412e1baf24cd0f23781 100644 --- a/templates/parachain/pallets/template/src/weights.rs +++ b/templates/parachain/pallets/template/src/weights.rs @@ -39,6 +39,12 @@ pub trait WeightInfo { } /// Weights for pallet_template using the Substrate node and recommended hardware. +#[cfg_attr( + not(feature = "std"), + deprecated( + note = "SubstrateWeight is auto-generated and should not be used in production. Replace it with runtime benchmarked weights." + ) +)] pub struct SubstrateWeight<T>(PhantomData<T>); impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { /// Storage: Template Something (r:0 w:1) diff --git a/templates/parachain/runtime/Cargo.toml b/templates/parachain/runtime/Cargo.toml index f1d33b4143e4440a5b993cfeb351da25ca9c3242..83d7bf4c9b72d60e37977411f0b5f8c6d5363c16 100644 --- a/templates/parachain/runtime/Cargo.toml +++ b/templates/parachain/runtime/Cargo.toml @@ -13,17 +13,17 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [build-dependencies] -substrate-wasm-builder = { optional = true, workspace = true, default-features = true } docify = { workspace = true } +substrate-wasm-builder = { optional = true, workspace = true, default-features = true } [dependencies] codec = { features = ["derive"], workspace = true } +docify = { workspace = true } hex-literal = { optional = true, workspace = true, default-features = true } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } -smallvec = { workspace = true, default-features = true } -docify = { workspace = true } serde_json = { workspace = true, default-features = false, features = ["alloc"] } +smallvec = { workspace = true, default-features = true } # Local pallet-parachain-template = { workspace = true } @@ -48,11 +48,11 @@ polkadot-sdk = { workspace = true, default-features = false, features = [ "cumulus-pallet-aura-ext", "cumulus-pallet-session-benchmarking", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-primitives-aura", "cumulus-primitives-core", - "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-utility", "pallet-collator-selection", "parachains-common", diff --git a/templates/parachain/runtime/src/apis.rs b/templates/parachain/runtime/src/apis.rs index 05a508ca655fb1fe6be4db53a839b76c8a9c1cec..d7da43b86af166e991371acb78806c46072cdfc0 100644 --- a/templates/parachain/runtime/src/apis.rs +++ b/templates/parachain/runtime/src/apis.rs @@ -114,6 +114,12 @@ impl_runtime_apis! { } } + impl frame_support::view_functions::runtime_api::RuntimeViewFunction<Block> for Runtime { + fn execute_view_function(id: frame_support::view_functions::ViewFunctionId, input: Vec<u8>) -> Result<Vec<u8>, frame_support::view_functions::ViewFunctionDispatchError> { + Runtime::execute_view_function(id, input) + } + } + impl sp_block_builder::BlockBuilder<Block> for Runtime { fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult { Executive::apply_extrinsic(extrinsic) diff --git a/templates/parachain/runtime/src/benchmarks.rs b/templates/parachain/runtime/src/benchmarks.rs index aae50e7258c0616248a5b11d3efab88e4258dba9..ca9d423bf856b12f8226e3f9a81ade4f059939f4 100644 --- a/templates/parachain/runtime/src/benchmarks.rs +++ b/templates/parachain/runtime/src/benchmarks.rs @@ -33,4 +33,5 @@ polkadot_sdk::frame_benchmarking::define_benchmarks!( [pallet_collator_selection, CollatorSelection] [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); diff --git a/templates/parachain/runtime/src/configs/mod.rs b/templates/parachain/runtime/src/configs/mod.rs index ba4c71c7f218705c4e5035d1fcac67fce10e2485..1e9155f59a57a8c9f5f34ef946fd3b39113e7a74 100644 --- a/templates/parachain/runtime/src/configs/mod.rs +++ b/templates/parachain/runtime/src/configs/mod.rs @@ -129,6 +129,11 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } +/// Configure the palelt weight reclaim tx. +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = (); +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; diff --git a/templates/parachain/runtime/src/genesis_config_presets.rs b/templates/parachain/runtime/src/genesis_config_presets.rs index aa1ff7895eb82e8a4d8ad20a0a77dca59e00ff1e..8cdadca5060ad00f3f97cebf61843c240d149ccd 100644 --- a/templates/parachain/runtime/src/genesis_config_presets.rs +++ b/templates/parachain/runtime/src/genesis_config_presets.rs @@ -16,8 +16,9 @@ use sp_keyring::Sr25519Keyring; /// The default XCM version to set in genesis config. const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; -/// Parachain id used for gensis config presets of parachain template. -const PARACHAIN_ID: u32 = 1000; +/// Parachain id used for genesis config presets of parachain template. +#[docify::export_content] +pub const PARACHAIN_ID: u32 = 1000; /// Generate the session keys from individual elements. /// diff --git a/templates/parachain/runtime/src/lib.rs b/templates/parachain/runtime/src/lib.rs index 43e76dba0591c5284358b18151bedc580d82d7b3..f312e9f80192fd767cdf93288c3331186aabf45a 100644 --- a/templates/parachain/runtime/src/lib.rs +++ b/templates/parachain/runtime/src/lib.rs @@ -33,6 +33,7 @@ use frame_support::weights::{ constants::WEIGHT_REF_TIME_PER_SECOND, Weight, WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial, }; +pub use genesis_config_presets::PARACHAIN_ID; pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; pub use sp_runtime::{MultiAddress, Perbill, Permill}; @@ -74,18 +75,20 @@ pub type BlockId = generic::BlockId<Block>; /// The extension to the basic transaction logic. #[docify::export(template_signed_extra)] -pub type TxExtension = ( - frame_system::CheckNonZeroSender<Runtime>, - frame_system::CheckSpecVersion<Runtime>, - frame_system::CheckTxVersion<Runtime>, - frame_system::CheckGenesis<Runtime>, - frame_system::CheckEra<Runtime>, - frame_system::CheckNonce<Runtime>, - frame_system::CheckWeight<Runtime>, - pallet_transaction_payment::ChargeTransactionPayment<Runtime>, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim<Runtime>, - frame_metadata_hash_extension::CheckMetadataHash<Runtime>, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender<Runtime>, + frame_system::CheckSpecVersion<Runtime>, + frame_system::CheckTxVersion<Runtime>, + frame_system::CheckGenesis<Runtime>, + frame_system::CheckEra<Runtime>, + frame_system::CheckNonce<Runtime>, + frame_system::CheckWeight<Runtime>, + pallet_transaction_payment::ChargeTransactionPayment<Runtime>, + frame_metadata_hash_extension::CheckMetadataHash<Runtime>, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -259,7 +262,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; @@ -271,6 +275,8 @@ mod runtime { pub type Timestamp = pallet_timestamp; #[runtime::pallet_index(3)] pub type ParachainInfo = parachain_info; + #[runtime::pallet_index(4)] + pub type WeightReclaim = cumulus_pallet_weight_reclaim; // Monetary stuff. #[runtime::pallet_index(10)] diff --git a/templates/parachain/src/lib.rs b/templates/parachain/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..d3c5b8ba3101a649dd9fdb73a07f97d40ab38064 --- /dev/null +++ b/templates/parachain/src/lib.rs @@ -0,0 +1,22 @@ +// 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 <http://www.gnu.org/licenses/>. + +// The parachain-template crate helps with keeping the README.md in sync +// with code sections across the components under the template: node, +// pallets & runtime, by using `docify`. + +#[cfg(feature = "generate-readme")] +docify::compile_markdown!("README.docify.md", "README.md"); diff --git a/templates/parachain/zombienet-omni-node.toml b/templates/parachain/zombienet-omni-node.toml index 29e99cfcd493113c3ee643c3cffb08e2fce498a9..2f263f157fcfa735101e12301f443277241df87d 100644 --- a/templates/parachain/zombienet-omni-node.toml +++ b/templates/parachain/zombienet-omni-node.toml @@ -14,7 +14,7 @@ ws_port = 9955 [[parachains]] id = 1000 -chain_spec_path = "<path/to/chain_spec.json>" +chain_spec_path = "./dev_chain_spec.json" [parachains.collator] name = "charlie" diff --git a/templates/solochain/node/Cargo.toml b/templates/solochain/node/Cargo.toml index 4c0ab31df95e2b95b0fd1d9fb548c98cf6862d90..90f576c88c23e894dbf8bf6bacd022deedeb39c5 100644 --- a/templates/solochain/node/Cargo.toml +++ b/templates/solochain/node/Cargo.toml @@ -17,41 +17,41 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] clap = { features = ["derive"], workspace = true } futures = { features = ["thread-pool"], workspace = true } -serde_json = { workspace = true, default-features = true } jsonrpsee = { features = ["server"], workspace = true } +serde_json = { workspace = true, default-features = true } # substrate client +sc-basic-authorship = { workspace = true, default-features = true } sc-cli = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } +sc-consensus = { workspace = true, default-features = true } +sc-consensus-aura = { workspace = true, default-features = true } +sc-consensus-grandpa = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } +sc-offchain = { 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-offchain = { workspace = true, default-features = true } -sc-consensus = { workspace = true, default-features = true } -sc-consensus-aura = { workspace = true, default-features = true } sp-consensus-aura = { workspace = true, default-features = true } -sc-consensus-grandpa = { workspace = true, default-features = true } sp-consensus-grandpa = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-genesis-builder = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sc-basic-authorship = { workspace = true, default-features = true } # substrate primitives -sp-runtime = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } -sp-timestamp = { workspace = true, default-features = true } -sp-inherents = { 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-blockchain = { workspace = true, default-features = true } +sp-inherents = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-timestamp = { workspace = true, default-features = true } # frame and pallets -frame-system = { workspace = true, default-features = true } frame-metadata-hash-extension = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } pallet-transaction-payment = { workspace = true, default-features = true } pallet-transaction-payment-rpc = { workspace = true, default-features = true } substrate-frame-rpc-system = { workspace = true, default-features = true } diff --git a/templates/solochain/node/src/benchmarking.rs b/templates/solochain/node/src/benchmarking.rs index 0d60230cd19c0347ca54bd0feae8a5ca7f2da66c..467cad4c0aaa50c3445e5370a2fbc12ba4482e97 100644 --- a/templates/solochain/node/src/benchmarking.rs +++ b/templates/solochain/node/src/benchmarking.rs @@ -122,6 +122,7 @@ pub fn create_benchmark_extrinsic( frame_system::CheckWeight::<runtime::Runtime>::new(), pallet_transaction_payment::ChargeTransactionPayment::<runtime::Runtime>::from(0), frame_metadata_hash_extension::CheckMetadataHash::<runtime::Runtime>::new(false), + frame_system::WeightReclaim::<runtime::Runtime>::new(), ); let raw_payload = runtime::SignedPayload::from_raw( @@ -137,6 +138,7 @@ pub fn create_benchmark_extrinsic( (), (), None, + (), ), ); let signature = raw_payload.using_encoded(|e| sender.sign(e)); diff --git a/templates/solochain/pallets/template/src/mock.rs b/templates/solochain/pallets/template/src/mock.rs index 1b86cd9b7709a43bf7abe45893569b53a614f0e3..44085bc3bff18e90d301c5275944c6e8aa41fd20 100644 --- a/templates/solochain/pallets/template/src/mock.rs +++ b/templates/solochain/pallets/template/src/mock.rs @@ -18,7 +18,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Test; diff --git a/templates/solochain/runtime/Cargo.toml b/templates/solochain/runtime/Cargo.toml index 837849e844b16f82a5b6eb6375ce00367a2dcd3c..1cff982fbf3c6db648b5c79e0f8f1d25dd462728 100644 --- a/templates/solochain/runtime/Cargo.toml +++ b/templates/solochain/runtime/Cargo.toml @@ -23,11 +23,11 @@ scale-info = { features = [ serde_json = { workspace = true, default-features = false, features = ["alloc"] } # frame +frame-executive = { workspace = true } +frame-metadata-hash-extension = { workspace = true } frame-support = { features = ["experimental"], workspace = true } frame-system = { workspace = true } frame-try-runtime = { optional = true, workspace = true } -frame-executive = { workspace = true } -frame-metadata-hash-extension = { workspace = true } # frame pallets pallet-aura = { workspace = true } @@ -46,11 +46,12 @@ sp-consensus-aura = { features = [ sp-consensus-grandpa = { features = [ "serde", ], workspace = true } -sp-keyring = { workspace = true } sp-core = { features = [ "serde", ], workspace = true } +sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } +sp-keyring = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { features = [ "serde", @@ -61,7 +62,6 @@ sp-transaction-pool = { workspace = true } sp-version = { features = [ "serde", ], workspace = true } -sp-genesis-builder = { workspace = true } # RPC related frame-system-rpc-runtime-api = { workspace = true } diff --git a/templates/solochain/runtime/src/apis.rs b/templates/solochain/runtime/src/apis.rs index 06c645fa0c53959b209fa4bcfe867eb6c34a5a6d..9dc588c43a2d5eaaa823802972a735dab40d49b0 100644 --- a/templates/solochain/runtime/src/apis.rs +++ b/templates/solochain/runtime/src/apis.rs @@ -75,6 +75,12 @@ impl_runtime_apis! { } } + impl frame_support::view_functions::runtime_api::RuntimeViewFunction<Block> for Runtime { + fn execute_view_function(id: frame_support::view_functions::ViewFunctionId, input: Vec<u8>) -> Result<Vec<u8>, frame_support::view_functions::ViewFunctionDispatchError> { + Runtime::execute_view_function(id, input) + } + } + impl sp_block_builder::BlockBuilder<Block> for Runtime { fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult { Executive::apply_extrinsic(extrinsic) diff --git a/templates/solochain/runtime/src/genesis_config_presets.rs b/templates/solochain/runtime/src/genesis_config_presets.rs index 049f4593451b951e55ba2106a5ddc85a88437a64..6af8dc9cd18a3d8d4c45d90cc3cbfe7e0dd29676 100644 --- a/templates/solochain/runtime/src/genesis_config_presets.rs +++ b/templates/solochain/runtime/src/genesis_config_presets.rs @@ -22,7 +22,7 @@ use serde_json::Value; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_consensus_grandpa::AuthorityId as GrandpaId; use sp_genesis_builder::{self, PresetId}; -use sp_keyring::AccountKeyring; +use sp_keyring::Sr25519Keyring; // Returns the genesis config presets populated with given parameters. fn testnet_genesis( @@ -56,12 +56,12 @@ pub fn development_config_genesis() -> Value { sp_keyring::Ed25519Keyring::Alice.public().into(), )], vec![ - AccountKeyring::Alice.to_account_id(), - AccountKeyring::Bob.to_account_id(), - AccountKeyring::AliceStash.to_account_id(), - AccountKeyring::BobStash.to_account_id(), + Sr25519Keyring::Alice.to_account_id(), + Sr25519Keyring::Bob.to_account_id(), + Sr25519Keyring::AliceStash.to_account_id(), + Sr25519Keyring::BobStash.to_account_id(), ], - sp_keyring::AccountKeyring::Alice.to_account_id(), + sp_keyring::Sr25519Keyring::Alice.to_account_id(), ) } @@ -78,11 +78,11 @@ pub fn local_config_genesis() -> Value { sp_keyring::Ed25519Keyring::Bob.public().into(), ), ], - AccountKeyring::iter() - .filter(|v| v != &AccountKeyring::One && v != &AccountKeyring::Two) + Sr25519Keyring::iter() + .filter(|v| v != &Sr25519Keyring::One && v != &Sr25519Keyring::Two) .map(|v| v.to_account_id()) .collect::<Vec<_>>(), - AccountKeyring::Alice.to_account_id(), + Sr25519Keyring::Alice.to_account_id(), ) } diff --git a/templates/solochain/runtime/src/lib.rs b/templates/solochain/runtime/src/lib.rs index ae0ea16ae42ef6122d1472677f5a23fe769e465d..f25b8413721ea2a2ede609fcfaf515f15d163468 100644 --- a/templates/solochain/runtime/src/lib.rs +++ b/templates/solochain/runtime/src/lib.rs @@ -157,6 +157,7 @@ pub type TxExtension = ( frame_system::CheckWeight<Runtime>, pallet_transaction_payment::ChargeTransactionPayment<Runtime>, frame_metadata_hash_extension::CheckMetadataHash<Runtime>, + frame_system::WeightReclaim<Runtime>, ); /// Unchecked extrinsic type as expected by this runtime. @@ -195,7 +196,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/templates/zombienet/Cargo.toml b/templates/zombienet/Cargo.toml index f29325dbe6a90ae45fbd39cec85a804ac753ee8b..805e4ddbcee28a33fd2b30f1e6e6082e87750059 100644 --- a/templates/zombienet/Cargo.toml +++ b/templates/zombienet/Cargo.toml @@ -10,10 +10,10 @@ edition.workspace = true publish = false [dependencies] +anyhow = { workspace = true } env_logger = { workspace = true } log = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread"] } -anyhow = { workspace = true } zombienet-sdk = { workspace = true } [features] diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 7f50658c4e160fc72fdc9a4dafb46aa4b1c52d31..fc0b2d5a140ed18419a3cece320f03f6fab8b711 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -29,6 +29,7 @@ std = [ "cumulus-pallet-parachain-system?/std", "cumulus-pallet-session-benchmarking?/std", "cumulus-pallet-solo-to-para?/std", + "cumulus-pallet-weight-reclaim?/std", "cumulus-pallet-xcm?/std", "cumulus-pallet-xcmp-queue?/std", "cumulus-ping?/std", @@ -56,6 +57,7 @@ std = [ "pallet-asset-conversion-tx-payment?/std", "pallet-asset-conversion?/std", "pallet-asset-rate?/std", + "pallet-asset-rewards?/std", "pallet-asset-tx-payment?/std", "pallet-assets-freezer?/std", "pallet-assets?/std", @@ -120,7 +122,6 @@ 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", @@ -240,6 +241,7 @@ runtime-benchmarks = [ "cumulus-pallet-dmp-queue?/runtime-benchmarks", "cumulus-pallet-parachain-system?/runtime-benchmarks", "cumulus-pallet-session-benchmarking?/runtime-benchmarks", + "cumulus-pallet-weight-reclaim?/runtime-benchmarks", "cumulus-pallet-xcmp-queue?/runtime-benchmarks", "cumulus-primitives-core?/runtime-benchmarks", "cumulus-primitives-utility?/runtime-benchmarks", @@ -255,6 +257,7 @@ runtime-benchmarks = [ "pallet-asset-conversion-tx-payment?/runtime-benchmarks", "pallet-asset-conversion?/runtime-benchmarks", "pallet-asset-rate?/runtime-benchmarks", + "pallet-asset-rewards?/runtime-benchmarks", "pallet-asset-tx-payment?/runtime-benchmarks", "pallet-assets-freezer?/runtime-benchmarks", "pallet-assets?/runtime-benchmarks", @@ -291,7 +294,6 @@ runtime-benchmarks = [ "pallet-membership?/runtime-benchmarks", "pallet-message-queue?/runtime-benchmarks", "pallet-migrations?/runtime-benchmarks", - "pallet-mixnet?/runtime-benchmarks", "pallet-mmr?/runtime-benchmarks", "pallet-multisig?/runtime-benchmarks", "pallet-nft-fractionalization?/runtime-benchmarks", @@ -363,6 +365,7 @@ runtime-benchmarks = [ "staging-node-inspect?/runtime-benchmarks", "staging-xcm-builder?/runtime-benchmarks", "staging-xcm-executor?/runtime-benchmarks", + "staging-xcm?/runtime-benchmarks", "xcm-runtime-apis?/runtime-benchmarks", ] try-runtime = [ @@ -370,6 +373,7 @@ try-runtime = [ "cumulus-pallet-dmp-queue?/try-runtime", "cumulus-pallet-parachain-system?/try-runtime", "cumulus-pallet-solo-to-para?/try-runtime", + "cumulus-pallet-weight-reclaim?/try-runtime", "cumulus-pallet-xcm?/try-runtime", "cumulus-pallet-xcmp-queue?/try-runtime", "cumulus-ping?/try-runtime", @@ -384,6 +388,7 @@ try-runtime = [ "pallet-asset-conversion-tx-payment?/try-runtime", "pallet-asset-conversion?/try-runtime", "pallet-asset-rate?/try-runtime", + "pallet-asset-rewards?/try-runtime", "pallet-asset-tx-payment?/try-runtime", "pallet-assets-freezer?/try-runtime", "pallet-assets?/try-runtime", @@ -541,7 +546,7 @@ 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-verify-signature", "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-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-weight-reclaim", "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-rewards", "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-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-verify-signature", "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 = [ "frame-benchmarking", "frame-benchmarking-pallet-pov", @@ -605,7 +610,7 @@ runtime = [ "sp-wasm-interface", "sp-weights", ] -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-eth-rpc", "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-approval-voting-parallel", "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-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-omni-node-lib", "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-eth-rpc", "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-approval-voting-parallel", "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-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-omni-node-lib", "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-runtime-utilities", "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", @@ -617,1885 +622,1901 @@ workspace = true [package.authors] workspace = true +[package.homepage] +workspace = true + +[package.repository] +workspace = true + [dependencies.assets-common] -path = "../cumulus/parachains/runtimes/assets/common" default-features = false optional = true +path = "../cumulus/parachains/runtimes/assets/common" [dependencies.binary-merkle-tree] -path = "../substrate/utils/binary-merkle-tree" default-features = false optional = true +path = "../substrate/utils/binary-merkle-tree" [dependencies.bp-header-chain] -path = "../bridges/primitives/header-chain" default-features = false optional = true +path = "../bridges/primitives/header-chain" [dependencies.bp-messages] -path = "../bridges/primitives/messages" default-features = false optional = true +path = "../bridges/primitives/messages" [dependencies.bp-parachains] -path = "../bridges/primitives/parachains" default-features = false optional = true +path = "../bridges/primitives/parachains" [dependencies.bp-polkadot] -path = "../bridges/chains/chain-polkadot" default-features = false optional = true +path = "../bridges/chains/chain-polkadot" [dependencies.bp-polkadot-core] -path = "../bridges/primitives/polkadot-core" default-features = false optional = true +path = "../bridges/primitives/polkadot-core" [dependencies.bp-relayers] -path = "../bridges/primitives/relayers" default-features = false optional = true +path = "../bridges/primitives/relayers" [dependencies.bp-runtime] -path = "../bridges/primitives/runtime" default-features = false optional = true +path = "../bridges/primitives/runtime" [dependencies.bp-test-utils] -path = "../bridges/primitives/test-utils" default-features = false optional = true +path = "../bridges/primitives/test-utils" [dependencies.bp-xcm-bridge-hub] -path = "../bridges/primitives/xcm-bridge-hub" default-features = false optional = true +path = "../bridges/primitives/xcm-bridge-hub" [dependencies.bp-xcm-bridge-hub-router] -path = "../bridges/primitives/xcm-bridge-hub-router" default-features = false optional = true +path = "../bridges/primitives/xcm-bridge-hub-router" [dependencies.bridge-hub-common] -path = "../cumulus/parachains/runtimes/bridge-hubs/common" default-features = false optional = true +path = "../cumulus/parachains/runtimes/bridge-hubs/common" [dependencies.bridge-runtime-common] -path = "../bridges/bin/runtime-common" default-features = false optional = true +path = "../bridges/bin/runtime-common" [dependencies.cumulus-pallet-aura-ext] -path = "../cumulus/pallets/aura-ext" default-features = false optional = true +path = "../cumulus/pallets/aura-ext" [dependencies.cumulus-pallet-dmp-queue] -path = "../cumulus/pallets/dmp-queue" default-features = false optional = true +path = "../cumulus/pallets/dmp-queue" [dependencies.cumulus-pallet-parachain-system] -path = "../cumulus/pallets/parachain-system" default-features = false optional = true +path = "../cumulus/pallets/parachain-system" [dependencies.cumulus-pallet-parachain-system-proc-macro] -path = "../cumulus/pallets/parachain-system/proc-macro" default-features = false optional = true +path = "../cumulus/pallets/parachain-system/proc-macro" [dependencies.cumulus-pallet-session-benchmarking] -path = "../cumulus/pallets/session-benchmarking" default-features = false optional = true +path = "../cumulus/pallets/session-benchmarking" [dependencies.cumulus-pallet-solo-to-para] +default-features = false +optional = true path = "../cumulus/pallets/solo-to-para" + +[dependencies.cumulus-pallet-weight-reclaim] default-features = false optional = true +path = "../cumulus/pallets/weight-reclaim" [dependencies.cumulus-pallet-xcm] -path = "../cumulus/pallets/xcm" default-features = false optional = true +path = "../cumulus/pallets/xcm" [dependencies.cumulus-pallet-xcmp-queue] -path = "../cumulus/pallets/xcmp-queue" default-features = false optional = true +path = "../cumulus/pallets/xcmp-queue" [dependencies.cumulus-ping] -path = "../cumulus/parachains/pallets/ping" default-features = false optional = true +path = "../cumulus/parachains/pallets/ping" [dependencies.cumulus-primitives-aura] -path = "../cumulus/primitives/aura" default-features = false optional = true +path = "../cumulus/primitives/aura" [dependencies.cumulus-primitives-core] -path = "../cumulus/primitives/core" default-features = false optional = true +path = "../cumulus/primitives/core" [dependencies.cumulus-primitives-parachain-inherent] -path = "../cumulus/primitives/parachain-inherent" default-features = false optional = true +path = "../cumulus/primitives/parachain-inherent" [dependencies.cumulus-primitives-proof-size-hostfunction] -path = "../cumulus/primitives/proof-size-hostfunction" default-features = false optional = true +path = "../cumulus/primitives/proof-size-hostfunction" [dependencies.cumulus-primitives-storage-weight-reclaim] -path = "../cumulus/primitives/storage-weight-reclaim" default-features = false optional = true +path = "../cumulus/primitives/storage-weight-reclaim" [dependencies.cumulus-primitives-timestamp] -path = "../cumulus/primitives/timestamp" default-features = false optional = true +path = "../cumulus/primitives/timestamp" [dependencies.cumulus-primitives-utility] -path = "../cumulus/primitives/utility" default-features = false optional = true +path = "../cumulus/primitives/utility" [dependencies.frame-benchmarking] -path = "../substrate/frame/benchmarking" default-features = false optional = true +path = "../substrate/frame/benchmarking" [dependencies.frame-benchmarking-pallet-pov] -path = "../substrate/frame/benchmarking/pov" default-features = false optional = true +path = "../substrate/frame/benchmarking/pov" [dependencies.frame-election-provider-solution-type] -path = "../substrate/frame/election-provider-support/solution-type" default-features = false optional = true +path = "../substrate/frame/election-provider-support/solution-type" [dependencies.frame-election-provider-support] -path = "../substrate/frame/election-provider-support" default-features = false optional = true +path = "../substrate/frame/election-provider-support" [dependencies.frame-executive] -path = "../substrate/frame/executive" default-features = false optional = true +path = "../substrate/frame/executive" [dependencies.frame-metadata-hash-extension] -path = "../substrate/frame/metadata-hash-extension" default-features = false optional = true +path = "../substrate/frame/metadata-hash-extension" [dependencies.frame-support] -path = "../substrate/frame/support" default-features = false optional = true +path = "../substrate/frame/support" [dependencies.frame-support-procedural] -path = "../substrate/frame/support/procedural" default-features = false optional = true +path = "../substrate/frame/support/procedural" [dependencies.frame-support-procedural-tools-derive] -path = "../substrate/frame/support/procedural/tools/derive" default-features = false optional = true +path = "../substrate/frame/support/procedural/tools/derive" [dependencies.frame-system] -path = "../substrate/frame/system" default-features = false optional = true +path = "../substrate/frame/system" [dependencies.frame-system-benchmarking] -path = "../substrate/frame/system/benchmarking" default-features = false optional = true +path = "../substrate/frame/system/benchmarking" [dependencies.frame-system-rpc-runtime-api] -path = "../substrate/frame/system/rpc/runtime-api" default-features = false optional = true +path = "../substrate/frame/system/rpc/runtime-api" [dependencies.frame-try-runtime] -path = "../substrate/frame/try-runtime" default-features = false optional = true +path = "../substrate/frame/try-runtime" [dependencies.pallet-alliance] -path = "../substrate/frame/alliance" default-features = false optional = true +path = "../substrate/frame/alliance" [dependencies.pallet-asset-conversion] -path = "../substrate/frame/asset-conversion" default-features = false optional = true +path = "../substrate/frame/asset-conversion" [dependencies.pallet-asset-conversion-ops] -path = "../substrate/frame/asset-conversion/ops" default-features = false optional = true +path = "../substrate/frame/asset-conversion/ops" [dependencies.pallet-asset-conversion-tx-payment] -path = "../substrate/frame/transaction-payment/asset-conversion-tx-payment" default-features = false optional = true +path = "../substrate/frame/transaction-payment/asset-conversion-tx-payment" [dependencies.pallet-asset-rate] +default-features = false +optional = true path = "../substrate/frame/asset-rate" + +[dependencies.pallet-asset-rewards] default-features = false optional = true +path = "../substrate/frame/asset-rewards" [dependencies.pallet-asset-tx-payment] -path = "../substrate/frame/transaction-payment/asset-tx-payment" default-features = false optional = true +path = "../substrate/frame/transaction-payment/asset-tx-payment" [dependencies.pallet-assets] -path = "../substrate/frame/assets" default-features = false optional = true +path = "../substrate/frame/assets" [dependencies.pallet-assets-freezer] -path = "../substrate/frame/assets-freezer" default-features = false optional = true +path = "../substrate/frame/assets-freezer" [dependencies.pallet-atomic-swap] -path = "../substrate/frame/atomic-swap" default-features = false optional = true +path = "../substrate/frame/atomic-swap" [dependencies.pallet-aura] -path = "../substrate/frame/aura" default-features = false optional = true +path = "../substrate/frame/aura" [dependencies.pallet-authority-discovery] -path = "../substrate/frame/authority-discovery" default-features = false optional = true +path = "../substrate/frame/authority-discovery" [dependencies.pallet-authorship] -path = "../substrate/frame/authorship" default-features = false optional = true +path = "../substrate/frame/authorship" [dependencies.pallet-babe] -path = "../substrate/frame/babe" default-features = false optional = true +path = "../substrate/frame/babe" [dependencies.pallet-bags-list] -path = "../substrate/frame/bags-list" default-features = false optional = true +path = "../substrate/frame/bags-list" [dependencies.pallet-balances] -path = "../substrate/frame/balances" default-features = false optional = true +path = "../substrate/frame/balances" [dependencies.pallet-beefy] -path = "../substrate/frame/beefy" default-features = false optional = true +path = "../substrate/frame/beefy" [dependencies.pallet-beefy-mmr] -path = "../substrate/frame/beefy-mmr" default-features = false optional = true +path = "../substrate/frame/beefy-mmr" [dependencies.pallet-bounties] -path = "../substrate/frame/bounties" default-features = false optional = true +path = "../substrate/frame/bounties" [dependencies.pallet-bridge-grandpa] -path = "../bridges/modules/grandpa" default-features = false optional = true +path = "../bridges/modules/grandpa" [dependencies.pallet-bridge-messages] -path = "../bridges/modules/messages" default-features = false optional = true +path = "../bridges/modules/messages" [dependencies.pallet-bridge-parachains] -path = "../bridges/modules/parachains" default-features = false optional = true +path = "../bridges/modules/parachains" [dependencies.pallet-bridge-relayers] -path = "../bridges/modules/relayers" default-features = false optional = true +path = "../bridges/modules/relayers" [dependencies.pallet-broker] -path = "../substrate/frame/broker" default-features = false optional = true +path = "../substrate/frame/broker" [dependencies.pallet-child-bounties] -path = "../substrate/frame/child-bounties" default-features = false optional = true +path = "../substrate/frame/child-bounties" [dependencies.pallet-collator-selection] -path = "../cumulus/pallets/collator-selection" default-features = false optional = true +path = "../cumulus/pallets/collator-selection" [dependencies.pallet-collective] -path = "../substrate/frame/collective" default-features = false optional = true +path = "../substrate/frame/collective" [dependencies.pallet-collective-content] -path = "../cumulus/parachains/pallets/collective-content" default-features = false optional = true +path = "../cumulus/parachains/pallets/collective-content" [dependencies.pallet-contracts] -path = "../substrate/frame/contracts" default-features = false optional = true +path = "../substrate/frame/contracts" [dependencies.pallet-contracts-proc-macro] -path = "../substrate/frame/contracts/proc-macro" default-features = false optional = true +path = "../substrate/frame/contracts/proc-macro" [dependencies.pallet-contracts-uapi] -path = "../substrate/frame/contracts/uapi" default-features = false optional = true +path = "../substrate/frame/contracts/uapi" [dependencies.pallet-conviction-voting] -path = "../substrate/frame/conviction-voting" default-features = false optional = true +path = "../substrate/frame/conviction-voting" [dependencies.pallet-core-fellowship] -path = "../substrate/frame/core-fellowship" default-features = false optional = true +path = "../substrate/frame/core-fellowship" [dependencies.pallet-delegated-staking] -path = "../substrate/frame/delegated-staking" default-features = false optional = true +path = "../substrate/frame/delegated-staking" [dependencies.pallet-democracy] -path = "../substrate/frame/democracy" default-features = false optional = true +path = "../substrate/frame/democracy" [dependencies.pallet-dev-mode] -path = "../substrate/frame/examples/dev-mode" default-features = false optional = true +path = "../substrate/frame/examples/dev-mode" [dependencies.pallet-election-provider-multi-phase] -path = "../substrate/frame/election-provider-multi-phase" default-features = false optional = true +path = "../substrate/frame/election-provider-multi-phase" [dependencies.pallet-election-provider-support-benchmarking] -path = "../substrate/frame/election-provider-support/benchmarking" default-features = false optional = true +path = "../substrate/frame/election-provider-support/benchmarking" [dependencies.pallet-elections-phragmen] -path = "../substrate/frame/elections-phragmen" default-features = false optional = true +path = "../substrate/frame/elections-phragmen" [dependencies.pallet-fast-unstake] -path = "../substrate/frame/fast-unstake" default-features = false optional = true +path = "../substrate/frame/fast-unstake" [dependencies.pallet-glutton] -path = "../substrate/frame/glutton" default-features = false optional = true +path = "../substrate/frame/glutton" [dependencies.pallet-grandpa] -path = "../substrate/frame/grandpa" default-features = false optional = true +path = "../substrate/frame/grandpa" [dependencies.pallet-identity] -path = "../substrate/frame/identity" default-features = false optional = true +path = "../substrate/frame/identity" [dependencies.pallet-im-online] -path = "../substrate/frame/im-online" default-features = false optional = true +path = "../substrate/frame/im-online" [dependencies.pallet-indices] -path = "../substrate/frame/indices" default-features = false optional = true +path = "../substrate/frame/indices" [dependencies.pallet-insecure-randomness-collective-flip] -path = "../substrate/frame/insecure-randomness-collective-flip" default-features = false optional = true +path = "../substrate/frame/insecure-randomness-collective-flip" [dependencies.pallet-lottery] -path = "../substrate/frame/lottery" default-features = false optional = true +path = "../substrate/frame/lottery" [dependencies.pallet-membership] -path = "../substrate/frame/membership" default-features = false optional = true +path = "../substrate/frame/membership" [dependencies.pallet-message-queue] -path = "../substrate/frame/message-queue" default-features = false optional = true +path = "../substrate/frame/message-queue" [dependencies.pallet-migrations] -path = "../substrate/frame/migrations" default-features = false optional = true +path = "../substrate/frame/migrations" [dependencies.pallet-mixnet] -path = "../substrate/frame/mixnet" default-features = false optional = true +path = "../substrate/frame/mixnet" [dependencies.pallet-mmr] -path = "../substrate/frame/merkle-mountain-range" default-features = false optional = true +path = "../substrate/frame/merkle-mountain-range" [dependencies.pallet-multisig] -path = "../substrate/frame/multisig" default-features = false optional = true +path = "../substrate/frame/multisig" [dependencies.pallet-nft-fractionalization] -path = "../substrate/frame/nft-fractionalization" default-features = false optional = true +path = "../substrate/frame/nft-fractionalization" [dependencies.pallet-nfts] -path = "../substrate/frame/nfts" default-features = false optional = true +path = "../substrate/frame/nfts" [dependencies.pallet-nfts-runtime-api] -path = "../substrate/frame/nfts/runtime-api" default-features = false optional = true +path = "../substrate/frame/nfts/runtime-api" [dependencies.pallet-nis] -path = "../substrate/frame/nis" default-features = false optional = true +path = "../substrate/frame/nis" [dependencies.pallet-node-authorization] -path = "../substrate/frame/node-authorization" default-features = false optional = true +path = "../substrate/frame/node-authorization" [dependencies.pallet-nomination-pools] -path = "../substrate/frame/nomination-pools" default-features = false optional = true +path = "../substrate/frame/nomination-pools" [dependencies.pallet-nomination-pools-benchmarking] -path = "../substrate/frame/nomination-pools/benchmarking" default-features = false optional = true +path = "../substrate/frame/nomination-pools/benchmarking" [dependencies.pallet-nomination-pools-runtime-api] -path = "../substrate/frame/nomination-pools/runtime-api" default-features = false optional = true +path = "../substrate/frame/nomination-pools/runtime-api" [dependencies.pallet-offences] -path = "../substrate/frame/offences" default-features = false optional = true +path = "../substrate/frame/offences" [dependencies.pallet-offences-benchmarking] -path = "../substrate/frame/offences/benchmarking" default-features = false optional = true +path = "../substrate/frame/offences/benchmarking" [dependencies.pallet-paged-list] -path = "../substrate/frame/paged-list" default-features = false optional = true +path = "../substrate/frame/paged-list" [dependencies.pallet-parameters] -path = "../substrate/frame/parameters" default-features = false optional = true +path = "../substrate/frame/parameters" [dependencies.pallet-preimage] -path = "../substrate/frame/preimage" default-features = false optional = true +path = "../substrate/frame/preimage" [dependencies.pallet-proxy] -path = "../substrate/frame/proxy" default-features = false optional = true +path = "../substrate/frame/proxy" [dependencies.pallet-ranked-collective] -path = "../substrate/frame/ranked-collective" default-features = false optional = true +path = "../substrate/frame/ranked-collective" [dependencies.pallet-recovery] -path = "../substrate/frame/recovery" default-features = false optional = true +path = "../substrate/frame/recovery" [dependencies.pallet-referenda] -path = "../substrate/frame/referenda" default-features = false optional = true +path = "../substrate/frame/referenda" [dependencies.pallet-remark] -path = "../substrate/frame/remark" default-features = false optional = true +path = "../substrate/frame/remark" [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 +path = "../substrate/frame/revive" [dependencies.pallet-revive-proc-macro] -path = "../substrate/frame/revive/proc-macro" default-features = false optional = true +path = "../substrate/frame/revive/proc-macro" [dependencies.pallet-revive-uapi] -path = "../substrate/frame/revive/uapi" default-features = false optional = true +path = "../substrate/frame/revive/uapi" [dependencies.pallet-root-offences] -path = "../substrate/frame/root-offences" default-features = false optional = true +path = "../substrate/frame/root-offences" [dependencies.pallet-root-testing] -path = "../substrate/frame/root-testing" default-features = false optional = true +path = "../substrate/frame/root-testing" [dependencies.pallet-safe-mode] -path = "../substrate/frame/safe-mode" default-features = false optional = true +path = "../substrate/frame/safe-mode" [dependencies.pallet-salary] -path = "../substrate/frame/salary" default-features = false optional = true +path = "../substrate/frame/salary" [dependencies.pallet-scheduler] -path = "../substrate/frame/scheduler" default-features = false optional = true +path = "../substrate/frame/scheduler" [dependencies.pallet-scored-pool] -path = "../substrate/frame/scored-pool" default-features = false optional = true +path = "../substrate/frame/scored-pool" [dependencies.pallet-session] -path = "../substrate/frame/session" default-features = false optional = true +path = "../substrate/frame/session" [dependencies.pallet-session-benchmarking] -path = "../substrate/frame/session/benchmarking" default-features = false optional = true +path = "../substrate/frame/session/benchmarking" [dependencies.pallet-skip-feeless-payment] -path = "../substrate/frame/transaction-payment/skip-feeless-payment" default-features = false optional = true +path = "../substrate/frame/transaction-payment/skip-feeless-payment" [dependencies.pallet-society] -path = "../substrate/frame/society" default-features = false optional = true +path = "../substrate/frame/society" [dependencies.pallet-staking] -path = "../substrate/frame/staking" default-features = false optional = true +path = "../substrate/frame/staking" [dependencies.pallet-staking-reward-curve] -path = "../substrate/frame/staking/reward-curve" default-features = false optional = true +path = "../substrate/frame/staking/reward-curve" [dependencies.pallet-staking-reward-fn] -path = "../substrate/frame/staking/reward-fn" default-features = false optional = true +path = "../substrate/frame/staking/reward-fn" [dependencies.pallet-staking-runtime-api] -path = "../substrate/frame/staking/runtime-api" default-features = false optional = true +path = "../substrate/frame/staking/runtime-api" [dependencies.pallet-state-trie-migration] -path = "../substrate/frame/state-trie-migration" default-features = false optional = true +path = "../substrate/frame/state-trie-migration" [dependencies.pallet-statement] -path = "../substrate/frame/statement" default-features = false optional = true +path = "../substrate/frame/statement" [dependencies.pallet-sudo] -path = "../substrate/frame/sudo" default-features = false optional = true +path = "../substrate/frame/sudo" [dependencies.pallet-timestamp] -path = "../substrate/frame/timestamp" default-features = false optional = true +path = "../substrate/frame/timestamp" [dependencies.pallet-tips] -path = "../substrate/frame/tips" default-features = false optional = true +path = "../substrate/frame/tips" [dependencies.pallet-transaction-payment] -path = "../substrate/frame/transaction-payment" default-features = false optional = true +path = "../substrate/frame/transaction-payment" [dependencies.pallet-transaction-payment-rpc-runtime-api] -path = "../substrate/frame/transaction-payment/rpc/runtime-api" default-features = false optional = true +path = "../substrate/frame/transaction-payment/rpc/runtime-api" [dependencies.pallet-transaction-storage] -path = "../substrate/frame/transaction-storage" default-features = false optional = true +path = "../substrate/frame/transaction-storage" [dependencies.pallet-treasury] -path = "../substrate/frame/treasury" default-features = false optional = true +path = "../substrate/frame/treasury" [dependencies.pallet-tx-pause] -path = "../substrate/frame/tx-pause" default-features = false optional = true +path = "../substrate/frame/tx-pause" [dependencies.pallet-uniques] -path = "../substrate/frame/uniques" default-features = false optional = true +path = "../substrate/frame/uniques" [dependencies.pallet-utility] -path = "../substrate/frame/utility" default-features = false optional = true +path = "../substrate/frame/utility" [dependencies.pallet-verify-signature] -path = "../substrate/frame/verify-signature" default-features = false optional = true +path = "../substrate/frame/verify-signature" [dependencies.pallet-vesting] -path = "../substrate/frame/vesting" default-features = false optional = true +path = "../substrate/frame/vesting" [dependencies.pallet-whitelist] -path = "../substrate/frame/whitelist" default-features = false optional = true +path = "../substrate/frame/whitelist" [dependencies.pallet-xcm] -path = "../polkadot/xcm/pallet-xcm" default-features = false optional = true +path = "../polkadot/xcm/pallet-xcm" [dependencies.pallet-xcm-benchmarks] -path = "../polkadot/xcm/pallet-xcm-benchmarks" default-features = false optional = true +path = "../polkadot/xcm/pallet-xcm-benchmarks" [dependencies.pallet-xcm-bridge-hub] -path = "../bridges/modules/xcm-bridge-hub" default-features = false optional = true +path = "../bridges/modules/xcm-bridge-hub" [dependencies.pallet-xcm-bridge-hub-router] -path = "../bridges/modules/xcm-bridge-hub-router" default-features = false optional = true +path = "../bridges/modules/xcm-bridge-hub-router" [dependencies.parachains-common] -path = "../cumulus/parachains/common" default-features = false optional = true +path = "../cumulus/parachains/common" [dependencies.polkadot-core-primitives] -path = "../polkadot/core-primitives" default-features = false optional = true +path = "../polkadot/core-primitives" [dependencies.polkadot-parachain-primitives] -path = "../polkadot/parachain" default-features = false optional = true +path = "../polkadot/parachain" [dependencies.polkadot-primitives] -path = "../polkadot/primitives" default-features = false optional = true +path = "../polkadot/primitives" [dependencies.polkadot-runtime-common] -path = "../polkadot/runtime/common" default-features = false optional = true +path = "../polkadot/runtime/common" [dependencies.polkadot-runtime-metrics] -path = "../polkadot/runtime/metrics" default-features = false optional = true +path = "../polkadot/runtime/metrics" [dependencies.polkadot-runtime-parachains] -path = "../polkadot/runtime/parachains" default-features = false optional = true +path = "../polkadot/runtime/parachains" [dependencies.polkadot-sdk-frame] -path = "../substrate/frame" default-features = false optional = true +path = "../substrate/frame" [dependencies.sc-chain-spec-derive] -path = "../substrate/client/chain-spec/derive" default-features = false optional = true +path = "../substrate/client/chain-spec/derive" [dependencies.sc-tracing-proc-macro] -path = "../substrate/client/tracing/proc-macro" default-features = false optional = true +path = "../substrate/client/tracing/proc-macro" [dependencies.slot-range-helper] -path = "../polkadot/runtime/common/slot_range_helper" default-features = false optional = true +path = "../polkadot/runtime/common/slot_range_helper" [dependencies.snowbridge-beacon-primitives] -path = "../bridges/snowbridge/primitives/beacon" default-features = false optional = true +path = "../bridges/snowbridge/primitives/beacon" [dependencies.snowbridge-core] -path = "../bridges/snowbridge/primitives/core" default-features = false optional = true +path = "../bridges/snowbridge/primitives/core" [dependencies.snowbridge-ethereum] -path = "../bridges/snowbridge/primitives/ethereum" default-features = false optional = true +path = "../bridges/snowbridge/primitives/ethereum" [dependencies.snowbridge-outbound-queue-merkle-tree] -path = "../bridges/snowbridge/pallets/outbound-queue/merkle-tree" default-features = false optional = true +path = "../bridges/snowbridge/pallets/outbound-queue/merkle-tree" [dependencies.snowbridge-outbound-queue-runtime-api] -path = "../bridges/snowbridge/pallets/outbound-queue/runtime-api" default-features = false optional = true +path = "../bridges/snowbridge/pallets/outbound-queue/runtime-api" [dependencies.snowbridge-pallet-ethereum-client] -path = "../bridges/snowbridge/pallets/ethereum-client" default-features = false optional = true +path = "../bridges/snowbridge/pallets/ethereum-client" [dependencies.snowbridge-pallet-ethereum-client-fixtures] -path = "../bridges/snowbridge/pallets/ethereum-client/fixtures" default-features = false optional = true +path = "../bridges/snowbridge/pallets/ethereum-client/fixtures" [dependencies.snowbridge-pallet-inbound-queue] -path = "../bridges/snowbridge/pallets/inbound-queue" default-features = false optional = true +path = "../bridges/snowbridge/pallets/inbound-queue" [dependencies.snowbridge-pallet-inbound-queue-fixtures] -path = "../bridges/snowbridge/pallets/inbound-queue/fixtures" default-features = false optional = true +path = "../bridges/snowbridge/pallets/inbound-queue/fixtures" [dependencies.snowbridge-pallet-outbound-queue] -path = "../bridges/snowbridge/pallets/outbound-queue" default-features = false optional = true +path = "../bridges/snowbridge/pallets/outbound-queue" [dependencies.snowbridge-pallet-system] -path = "../bridges/snowbridge/pallets/system" default-features = false optional = true +path = "../bridges/snowbridge/pallets/system" [dependencies.snowbridge-router-primitives] -path = "../bridges/snowbridge/primitives/router" default-features = false optional = true +path = "../bridges/snowbridge/primitives/router" [dependencies.snowbridge-runtime-common] -path = "../bridges/snowbridge/runtime/runtime-common" default-features = false optional = true +path = "../bridges/snowbridge/runtime/runtime-common" [dependencies.snowbridge-system-runtime-api] -path = "../bridges/snowbridge/pallets/system/runtime-api" default-features = false optional = true +path = "../bridges/snowbridge/pallets/system/runtime-api" [dependencies.sp-api] -path = "../substrate/primitives/api" default-features = false optional = true +path = "../substrate/primitives/api" [dependencies.sp-api-proc-macro] -path = "../substrate/primitives/api/proc-macro" default-features = false optional = true +path = "../substrate/primitives/api/proc-macro" [dependencies.sp-application-crypto] -path = "../substrate/primitives/application-crypto" default-features = false optional = true +path = "../substrate/primitives/application-crypto" [dependencies.sp-arithmetic] -path = "../substrate/primitives/arithmetic" default-features = false optional = true +path = "../substrate/primitives/arithmetic" [dependencies.sp-authority-discovery] -path = "../substrate/primitives/authority-discovery" default-features = false optional = true +path = "../substrate/primitives/authority-discovery" [dependencies.sp-block-builder] -path = "../substrate/primitives/block-builder" default-features = false optional = true +path = "../substrate/primitives/block-builder" [dependencies.sp-consensus-aura] -path = "../substrate/primitives/consensus/aura" default-features = false optional = true +path = "../substrate/primitives/consensus/aura" [dependencies.sp-consensus-babe] -path = "../substrate/primitives/consensus/babe" default-features = false optional = true +path = "../substrate/primitives/consensus/babe" [dependencies.sp-consensus-beefy] -path = "../substrate/primitives/consensus/beefy" default-features = false optional = true +path = "../substrate/primitives/consensus/beefy" [dependencies.sp-consensus-grandpa] -path = "../substrate/primitives/consensus/grandpa" default-features = false optional = true +path = "../substrate/primitives/consensus/grandpa" [dependencies.sp-consensus-pow] -path = "../substrate/primitives/consensus/pow" default-features = false optional = true +path = "../substrate/primitives/consensus/pow" [dependencies.sp-consensus-slots] -path = "../substrate/primitives/consensus/slots" default-features = false optional = true +path = "../substrate/primitives/consensus/slots" [dependencies.sp-core] -path = "../substrate/primitives/core" default-features = false optional = true +path = "../substrate/primitives/core" [dependencies.sp-crypto-ec-utils] -path = "../substrate/primitives/crypto/ec-utils" default-features = false optional = true +path = "../substrate/primitives/crypto/ec-utils" [dependencies.sp-crypto-hashing] -path = "../substrate/primitives/crypto/hashing" default-features = false optional = true +path = "../substrate/primitives/crypto/hashing" [dependencies.sp-crypto-hashing-proc-macro] -path = "../substrate/primitives/crypto/hashing/proc-macro" default-features = false optional = true +path = "../substrate/primitives/crypto/hashing/proc-macro" [dependencies.sp-debug-derive] -path = "../substrate/primitives/debug-derive" default-features = false optional = true +path = "../substrate/primitives/debug-derive" [dependencies.sp-externalities] -path = "../substrate/primitives/externalities" default-features = false optional = true +path = "../substrate/primitives/externalities" [dependencies.sp-genesis-builder] -path = "../substrate/primitives/genesis-builder" default-features = false optional = true +path = "../substrate/primitives/genesis-builder" [dependencies.sp-inherents] -path = "../substrate/primitives/inherents" default-features = false optional = true +path = "../substrate/primitives/inherents" [dependencies.sp-io] -path = "../substrate/primitives/io" default-features = false optional = true +path = "../substrate/primitives/io" [dependencies.sp-keyring] -path = "../substrate/primitives/keyring" default-features = false optional = true +path = "../substrate/primitives/keyring" [dependencies.sp-keystore] -path = "../substrate/primitives/keystore" default-features = false optional = true +path = "../substrate/primitives/keystore" [dependencies.sp-metadata-ir] -path = "../substrate/primitives/metadata-ir" default-features = false optional = true +path = "../substrate/primitives/metadata-ir" [dependencies.sp-mixnet] -path = "../substrate/primitives/mixnet" default-features = false optional = true +path = "../substrate/primitives/mixnet" [dependencies.sp-mmr-primitives] -path = "../substrate/primitives/merkle-mountain-range" default-features = false optional = true +path = "../substrate/primitives/merkle-mountain-range" [dependencies.sp-npos-elections] -path = "../substrate/primitives/npos-elections" default-features = false optional = true +path = "../substrate/primitives/npos-elections" [dependencies.sp-offchain] -path = "../substrate/primitives/offchain" default-features = false optional = true +path = "../substrate/primitives/offchain" [dependencies.sp-runtime] -path = "../substrate/primitives/runtime" default-features = false optional = true +path = "../substrate/primitives/runtime" [dependencies.sp-runtime-interface] -path = "../substrate/primitives/runtime-interface" default-features = false optional = true +path = "../substrate/primitives/runtime-interface" [dependencies.sp-runtime-interface-proc-macro] -path = "../substrate/primitives/runtime-interface/proc-macro" default-features = false optional = true +path = "../substrate/primitives/runtime-interface/proc-macro" [dependencies.sp-session] -path = "../substrate/primitives/session" default-features = false optional = true +path = "../substrate/primitives/session" [dependencies.sp-staking] -path = "../substrate/primitives/staking" default-features = false optional = true +path = "../substrate/primitives/staking" [dependencies.sp-state-machine] -path = "../substrate/primitives/state-machine" default-features = false optional = true +path = "../substrate/primitives/state-machine" [dependencies.sp-statement-store] -path = "../substrate/primitives/statement-store" default-features = false optional = true +path = "../substrate/primitives/statement-store" [dependencies.sp-std] -path = "../substrate/primitives/std" default-features = false optional = true +path = "../substrate/primitives/std" [dependencies.sp-storage] -path = "../substrate/primitives/storage" default-features = false optional = true +path = "../substrate/primitives/storage" [dependencies.sp-timestamp] -path = "../substrate/primitives/timestamp" default-features = false optional = true +path = "../substrate/primitives/timestamp" [dependencies.sp-tracing] -path = "../substrate/primitives/tracing" default-features = false optional = true +path = "../substrate/primitives/tracing" [dependencies.sp-transaction-pool] -path = "../substrate/primitives/transaction-pool" default-features = false optional = true +path = "../substrate/primitives/transaction-pool" [dependencies.sp-transaction-storage-proof] -path = "../substrate/primitives/transaction-storage-proof" default-features = false optional = true +path = "../substrate/primitives/transaction-storage-proof" [dependencies.sp-trie] -path = "../substrate/primitives/trie" default-features = false optional = true +path = "../substrate/primitives/trie" [dependencies.sp-version] -path = "../substrate/primitives/version" default-features = false optional = true +path = "../substrate/primitives/version" [dependencies.sp-version-proc-macro] -path = "../substrate/primitives/version/proc-macro" default-features = false optional = true +path = "../substrate/primitives/version/proc-macro" [dependencies.sp-wasm-interface] -path = "../substrate/primitives/wasm-interface" default-features = false optional = true +path = "../substrate/primitives/wasm-interface" [dependencies.sp-weights] -path = "../substrate/primitives/weights" default-features = false optional = true +path = "../substrate/primitives/weights" [dependencies.staging-parachain-info] -path = "../cumulus/parachains/pallets/parachain-info" default-features = false optional = true +path = "../cumulus/parachains/pallets/parachain-info" [dependencies.staging-xcm] -path = "../polkadot/xcm" default-features = false optional = true +path = "../polkadot/xcm" [dependencies.staging-xcm-builder] -path = "../polkadot/xcm/xcm-builder" default-features = false optional = true +path = "../polkadot/xcm/xcm-builder" [dependencies.staging-xcm-executor] -path = "../polkadot/xcm/xcm-executor" default-features = false optional = true +path = "../polkadot/xcm/xcm-executor" [dependencies.substrate-bip39] -path = "../substrate/utils/substrate-bip39" default-features = false optional = true +path = "../substrate/utils/substrate-bip39" [dependencies.testnet-parachains-constants] -path = "../cumulus/parachains/runtimes/constants" default-features = false optional = true +path = "../cumulus/parachains/runtimes/constants" [dependencies.tracing-gum-proc-macro] -path = "../polkadot/node/gum/proc-macro" default-features = false optional = true +path = "../polkadot/node/gum/proc-macro" [dependencies.xcm-procedural] -path = "../polkadot/xcm/procedural" default-features = false optional = true +path = "../polkadot/xcm/procedural" [dependencies.xcm-runtime-apis] -path = "../polkadot/xcm/xcm-runtime-apis" default-features = false optional = true +path = "../polkadot/xcm/xcm-runtime-apis" [dependencies.asset-test-utils] -path = "../cumulus/parachains/runtimes/assets/test-utils" default-features = false optional = true +path = "../cumulus/parachains/runtimes/assets/test-utils" [dependencies.bridge-hub-test-utils] -path = "../cumulus/parachains/runtimes/bridge-hubs/test-utils" default-features = false optional = true +path = "../cumulus/parachains/runtimes/bridge-hubs/test-utils" [dependencies.cumulus-client-cli] -path = "../cumulus/client/cli" default-features = false optional = true +path = "../cumulus/client/cli" [dependencies.cumulus-client-collator] -path = "../cumulus/client/collator" default-features = false optional = true +path = "../cumulus/client/collator" [dependencies.cumulus-client-consensus-aura] -path = "../cumulus/client/consensus/aura" default-features = false optional = true +path = "../cumulus/client/consensus/aura" [dependencies.cumulus-client-consensus-common] -path = "../cumulus/client/consensus/common" default-features = false optional = true +path = "../cumulus/client/consensus/common" [dependencies.cumulus-client-consensus-proposer] -path = "../cumulus/client/consensus/proposer" default-features = false optional = true +path = "../cumulus/client/consensus/proposer" [dependencies.cumulus-client-consensus-relay-chain] -path = "../cumulus/client/consensus/relay-chain" default-features = false optional = true +path = "../cumulus/client/consensus/relay-chain" [dependencies.cumulus-client-network] -path = "../cumulus/client/network" default-features = false optional = true +path = "../cumulus/client/network" [dependencies.cumulus-client-parachain-inherent] -path = "../cumulus/client/parachain-inherent" default-features = false optional = true +path = "../cumulus/client/parachain-inherent" [dependencies.cumulus-client-pov-recovery] -path = "../cumulus/client/pov-recovery" default-features = false optional = true +path = "../cumulus/client/pov-recovery" [dependencies.cumulus-client-service] -path = "../cumulus/client/service" default-features = false optional = true +path = "../cumulus/client/service" [dependencies.cumulus-relay-chain-inprocess-interface] -path = "../cumulus/client/relay-chain-inprocess-interface" default-features = false optional = true +path = "../cumulus/client/relay-chain-inprocess-interface" [dependencies.cumulus-relay-chain-interface] -path = "../cumulus/client/relay-chain-interface" default-features = false optional = true +path = "../cumulus/client/relay-chain-interface" [dependencies.cumulus-relay-chain-minimal-node] -path = "../cumulus/client/relay-chain-minimal-node" default-features = false optional = true +path = "../cumulus/client/relay-chain-minimal-node" [dependencies.cumulus-relay-chain-rpc-interface] -path = "../cumulus/client/relay-chain-rpc-interface" default-features = false optional = true +path = "../cumulus/client/relay-chain-rpc-interface" [dependencies.cumulus-test-relay-sproof-builder] -path = "../cumulus/test/relay-sproof-builder" default-features = false optional = true +path = "../cumulus/test/relay-sproof-builder" [dependencies.emulated-integration-tests-common] -path = "../cumulus/parachains/integration-tests/emulated/common" default-features = false optional = true +path = "../cumulus/parachains/integration-tests/emulated/common" [dependencies.fork-tree] -path = "../substrate/utils/fork-tree" default-features = false optional = true +path = "../substrate/utils/fork-tree" [dependencies.frame-benchmarking-cli] -path = "../substrate/utils/frame/benchmarking-cli" default-features = false optional = true +path = "../substrate/utils/frame/benchmarking-cli" [dependencies.frame-remote-externalities] -path = "../substrate/utils/frame/remote-externalities" default-features = false optional = true +path = "../substrate/utils/frame/remote-externalities" [dependencies.frame-support-procedural-tools] -path = "../substrate/frame/support/procedural/tools" default-features = false optional = true +path = "../substrate/frame/support/procedural/tools" [dependencies.generate-bags] -path = "../substrate/utils/frame/generate-bags" default-features = false optional = true +path = "../substrate/utils/frame/generate-bags" [dependencies.mmr-gadget] -path = "../substrate/client/merkle-mountain-range" default-features = false optional = true +path = "../substrate/client/merkle-mountain-range" [dependencies.mmr-rpc] -path = "../substrate/client/merkle-mountain-range/rpc" default-features = false optional = true +path = "../substrate/client/merkle-mountain-range/rpc" [dependencies.pallet-contracts-mock-network] -path = "../substrate/frame/contracts/mock-network" default-features = false optional = true +path = "../substrate/frame/contracts/mock-network" [dependencies.pallet-revive-eth-rpc] -path = "../substrate/frame/revive/rpc" default-features = false optional = true +path = "../substrate/frame/revive/rpc" [dependencies.pallet-revive-mock-network] -path = "../substrate/frame/revive/mock-network" default-features = false optional = true +path = "../substrate/frame/revive/mock-network" [dependencies.pallet-transaction-payment-rpc] -path = "../substrate/frame/transaction-payment/rpc" default-features = false optional = true +path = "../substrate/frame/transaction-payment/rpc" [dependencies.parachains-runtimes-test-utils] -path = "../cumulus/parachains/runtimes/test-utils" default-features = false optional = true +path = "../cumulus/parachains/runtimes/test-utils" [dependencies.polkadot-approval-distribution] -path = "../polkadot/node/network/approval-distribution" default-features = false optional = true +path = "../polkadot/node/network/approval-distribution" [dependencies.polkadot-availability-bitfield-distribution] -path = "../polkadot/node/network/bitfield-distribution" default-features = false optional = true +path = "../polkadot/node/network/bitfield-distribution" [dependencies.polkadot-availability-distribution] -path = "../polkadot/node/network/availability-distribution" default-features = false optional = true +path = "../polkadot/node/network/availability-distribution" [dependencies.polkadot-availability-recovery] -path = "../polkadot/node/network/availability-recovery" default-features = false optional = true +path = "../polkadot/node/network/availability-recovery" [dependencies.polkadot-cli] -path = "../polkadot/cli" default-features = false optional = true +path = "../polkadot/cli" [dependencies.polkadot-collator-protocol] -path = "../polkadot/node/network/collator-protocol" default-features = false optional = true +path = "../polkadot/node/network/collator-protocol" [dependencies.polkadot-dispute-distribution] -path = "../polkadot/node/network/dispute-distribution" default-features = false optional = true +path = "../polkadot/node/network/dispute-distribution" [dependencies.polkadot-erasure-coding] -path = "../polkadot/erasure-coding" default-features = false optional = true +path = "../polkadot/erasure-coding" [dependencies.polkadot-gossip-support] -path = "../polkadot/node/network/gossip-support" default-features = false optional = true +path = "../polkadot/node/network/gossip-support" [dependencies.polkadot-network-bridge] -path = "../polkadot/node/network/bridge" default-features = false optional = true +path = "../polkadot/node/network/bridge" [dependencies.polkadot-node-collation-generation] -path = "../polkadot/node/collation-generation" default-features = false optional = true +path = "../polkadot/node/collation-generation" [dependencies.polkadot-node-core-approval-voting] -path = "../polkadot/node/core/approval-voting" default-features = false optional = true +path = "../polkadot/node/core/approval-voting" [dependencies.polkadot-node-core-approval-voting-parallel] -path = "../polkadot/node/core/approval-voting-parallel" default-features = false optional = true +path = "../polkadot/node/core/approval-voting-parallel" [dependencies.polkadot-node-core-av-store] -path = "../polkadot/node/core/av-store" default-features = false optional = true +path = "../polkadot/node/core/av-store" [dependencies.polkadot-node-core-backing] -path = "../polkadot/node/core/backing" default-features = false optional = true +path = "../polkadot/node/core/backing" [dependencies.polkadot-node-core-bitfield-signing] -path = "../polkadot/node/core/bitfield-signing" default-features = false optional = true +path = "../polkadot/node/core/bitfield-signing" [dependencies.polkadot-node-core-candidate-validation] -path = "../polkadot/node/core/candidate-validation" default-features = false optional = true +path = "../polkadot/node/core/candidate-validation" [dependencies.polkadot-node-core-chain-api] -path = "../polkadot/node/core/chain-api" default-features = false optional = true +path = "../polkadot/node/core/chain-api" [dependencies.polkadot-node-core-chain-selection] -path = "../polkadot/node/core/chain-selection" default-features = false optional = true +path = "../polkadot/node/core/chain-selection" [dependencies.polkadot-node-core-dispute-coordinator] -path = "../polkadot/node/core/dispute-coordinator" default-features = false optional = true +path = "../polkadot/node/core/dispute-coordinator" [dependencies.polkadot-node-core-parachains-inherent] -path = "../polkadot/node/core/parachains-inherent" default-features = false optional = true +path = "../polkadot/node/core/parachains-inherent" [dependencies.polkadot-node-core-prospective-parachains] -path = "../polkadot/node/core/prospective-parachains" default-features = false optional = true +path = "../polkadot/node/core/prospective-parachains" [dependencies.polkadot-node-core-provisioner] -path = "../polkadot/node/core/provisioner" default-features = false optional = true +path = "../polkadot/node/core/provisioner" [dependencies.polkadot-node-core-pvf] -path = "../polkadot/node/core/pvf" default-features = false optional = true +path = "../polkadot/node/core/pvf" [dependencies.polkadot-node-core-pvf-checker] -path = "../polkadot/node/core/pvf-checker" default-features = false optional = true +path = "../polkadot/node/core/pvf-checker" [dependencies.polkadot-node-core-pvf-common] -path = "../polkadot/node/core/pvf/common" default-features = false optional = true +path = "../polkadot/node/core/pvf/common" [dependencies.polkadot-node-core-pvf-execute-worker] -path = "../polkadot/node/core/pvf/execute-worker" default-features = false optional = true +path = "../polkadot/node/core/pvf/execute-worker" [dependencies.polkadot-node-core-pvf-prepare-worker] -path = "../polkadot/node/core/pvf/prepare-worker" default-features = false optional = true +path = "../polkadot/node/core/pvf/prepare-worker" [dependencies.polkadot-node-core-runtime-api] -path = "../polkadot/node/core/runtime-api" default-features = false optional = true +path = "../polkadot/node/core/runtime-api" [dependencies.polkadot-node-metrics] -path = "../polkadot/node/metrics" default-features = false optional = true +path = "../polkadot/node/metrics" [dependencies.polkadot-node-network-protocol] -path = "../polkadot/node/network/protocol" default-features = false optional = true +path = "../polkadot/node/network/protocol" [dependencies.polkadot-node-primitives] -path = "../polkadot/node/primitives" default-features = false optional = true +path = "../polkadot/node/primitives" [dependencies.polkadot-node-subsystem] -path = "../polkadot/node/subsystem" default-features = false optional = true +path = "../polkadot/node/subsystem" [dependencies.polkadot-node-subsystem-types] -path = "../polkadot/node/subsystem-types" default-features = false optional = true +path = "../polkadot/node/subsystem-types" [dependencies.polkadot-node-subsystem-util] -path = "../polkadot/node/subsystem-util" default-features = false optional = true +path = "../polkadot/node/subsystem-util" [dependencies.polkadot-omni-node-lib] -path = "../cumulus/polkadot-omni-node/lib" default-features = false optional = true +path = "../cumulus/polkadot-omni-node/lib" [dependencies.polkadot-overseer] -path = "../polkadot/node/overseer" default-features = false optional = true +path = "../polkadot/node/overseer" [dependencies.polkadot-rpc] -path = "../polkadot/rpc" default-features = false optional = true +path = "../polkadot/rpc" [dependencies.polkadot-service] -path = "../polkadot/node/service" default-features = false optional = true +path = "../polkadot/node/service" [dependencies.polkadot-statement-distribution] -path = "../polkadot/node/network/statement-distribution" default-features = false optional = true +path = "../polkadot/node/network/statement-distribution" [dependencies.polkadot-statement-table] -path = "../polkadot/statement-table" default-features = false optional = true +path = "../polkadot/statement-table" [dependencies.sc-allocator] -path = "../substrate/client/allocator" default-features = false optional = true +path = "../substrate/client/allocator" [dependencies.sc-authority-discovery] -path = "../substrate/client/authority-discovery" default-features = false optional = true +path = "../substrate/client/authority-discovery" [dependencies.sc-basic-authorship] -path = "../substrate/client/basic-authorship" default-features = false optional = true +path = "../substrate/client/basic-authorship" [dependencies.sc-block-builder] -path = "../substrate/client/block-builder" default-features = false optional = true +path = "../substrate/client/block-builder" [dependencies.sc-chain-spec] -path = "../substrate/client/chain-spec" default-features = false optional = true +path = "../substrate/client/chain-spec" [dependencies.sc-cli] -path = "../substrate/client/cli" default-features = false optional = true +path = "../substrate/client/cli" [dependencies.sc-client-api] -path = "../substrate/client/api" default-features = false optional = true +path = "../substrate/client/api" [dependencies.sc-client-db] -path = "../substrate/client/db" default-features = false optional = true +path = "../substrate/client/db" [dependencies.sc-consensus] -path = "../substrate/client/consensus/common" default-features = false optional = true +path = "../substrate/client/consensus/common" [dependencies.sc-consensus-aura] -path = "../substrate/client/consensus/aura" default-features = false optional = true +path = "../substrate/client/consensus/aura" [dependencies.sc-consensus-babe] -path = "../substrate/client/consensus/babe" default-features = false optional = true +path = "../substrate/client/consensus/babe" [dependencies.sc-consensus-babe-rpc] -path = "../substrate/client/consensus/babe/rpc" default-features = false optional = true +path = "../substrate/client/consensus/babe/rpc" [dependencies.sc-consensus-beefy] -path = "../substrate/client/consensus/beefy" default-features = false optional = true +path = "../substrate/client/consensus/beefy" [dependencies.sc-consensus-beefy-rpc] -path = "../substrate/client/consensus/beefy/rpc" default-features = false optional = true +path = "../substrate/client/consensus/beefy/rpc" [dependencies.sc-consensus-epochs] -path = "../substrate/client/consensus/epochs" default-features = false optional = true +path = "../substrate/client/consensus/epochs" [dependencies.sc-consensus-grandpa] -path = "../substrate/client/consensus/grandpa" default-features = false optional = true +path = "../substrate/client/consensus/grandpa" [dependencies.sc-consensus-grandpa-rpc] -path = "../substrate/client/consensus/grandpa/rpc" default-features = false optional = true +path = "../substrate/client/consensus/grandpa/rpc" [dependencies.sc-consensus-manual-seal] -path = "../substrate/client/consensus/manual-seal" default-features = false optional = true +path = "../substrate/client/consensus/manual-seal" [dependencies.sc-consensus-pow] -path = "../substrate/client/consensus/pow" default-features = false optional = true +path = "../substrate/client/consensus/pow" [dependencies.sc-consensus-slots] -path = "../substrate/client/consensus/slots" default-features = false optional = true +path = "../substrate/client/consensus/slots" [dependencies.sc-executor] -path = "../substrate/client/executor" default-features = false optional = true +path = "../substrate/client/executor" [dependencies.sc-executor-common] -path = "../substrate/client/executor/common" default-features = false optional = true +path = "../substrate/client/executor/common" [dependencies.sc-executor-polkavm] -path = "../substrate/client/executor/polkavm" default-features = false optional = true +path = "../substrate/client/executor/polkavm" [dependencies.sc-executor-wasmtime] -path = "../substrate/client/executor/wasmtime" default-features = false optional = true +path = "../substrate/client/executor/wasmtime" [dependencies.sc-informant] -path = "../substrate/client/informant" default-features = false optional = true +path = "../substrate/client/informant" [dependencies.sc-keystore] -path = "../substrate/client/keystore" default-features = false optional = true +path = "../substrate/client/keystore" [dependencies.sc-mixnet] -path = "../substrate/client/mixnet" default-features = false optional = true +path = "../substrate/client/mixnet" [dependencies.sc-network] -path = "../substrate/client/network" default-features = false optional = true +path = "../substrate/client/network" [dependencies.sc-network-common] -path = "../substrate/client/network/common" default-features = false optional = true +path = "../substrate/client/network/common" [dependencies.sc-network-gossip] -path = "../substrate/client/network-gossip" default-features = false optional = true +path = "../substrate/client/network-gossip" [dependencies.sc-network-light] -path = "../substrate/client/network/light" default-features = false optional = true +path = "../substrate/client/network/light" [dependencies.sc-network-statement] -path = "../substrate/client/network/statement" default-features = false optional = true +path = "../substrate/client/network/statement" [dependencies.sc-network-sync] -path = "../substrate/client/network/sync" default-features = false optional = true +path = "../substrate/client/network/sync" [dependencies.sc-network-transactions] -path = "../substrate/client/network/transactions" default-features = false optional = true +path = "../substrate/client/network/transactions" [dependencies.sc-network-types] -path = "../substrate/client/network/types" default-features = false optional = true +path = "../substrate/client/network/types" [dependencies.sc-offchain] -path = "../substrate/client/offchain" default-features = false optional = true +path = "../substrate/client/offchain" [dependencies.sc-proposer-metrics] -path = "../substrate/client/proposer-metrics" default-features = false optional = true +path = "../substrate/client/proposer-metrics" [dependencies.sc-rpc] -path = "../substrate/client/rpc" default-features = false optional = true +path = "../substrate/client/rpc" [dependencies.sc-rpc-api] -path = "../substrate/client/rpc-api" default-features = false optional = true +path = "../substrate/client/rpc-api" [dependencies.sc-rpc-server] -path = "../substrate/client/rpc-servers" default-features = false optional = true +path = "../substrate/client/rpc-servers" [dependencies.sc-rpc-spec-v2] +default-features = false +optional = true path = "../substrate/client/rpc-spec-v2" + +[dependencies.sc-runtime-utilities] default-features = false optional = true +path = "../substrate/client/runtime-utilities" [dependencies.sc-service] -path = "../substrate/client/service" default-features = false optional = true +path = "../substrate/client/service" [dependencies.sc-state-db] -path = "../substrate/client/state-db" default-features = false optional = true +path = "../substrate/client/state-db" [dependencies.sc-statement-store] -path = "../substrate/client/statement-store" default-features = false optional = true +path = "../substrate/client/statement-store" [dependencies.sc-storage-monitor] -path = "../substrate/client/storage-monitor" default-features = false optional = true +path = "../substrate/client/storage-monitor" [dependencies.sc-sync-state-rpc] -path = "../substrate/client/sync-state-rpc" default-features = false optional = true +path = "../substrate/client/sync-state-rpc" [dependencies.sc-sysinfo] -path = "../substrate/client/sysinfo" default-features = false optional = true +path = "../substrate/client/sysinfo" [dependencies.sc-telemetry] -path = "../substrate/client/telemetry" default-features = false optional = true +path = "../substrate/client/telemetry" [dependencies.sc-tracing] -path = "../substrate/client/tracing" default-features = false optional = true +path = "../substrate/client/tracing" [dependencies.sc-transaction-pool] -path = "../substrate/client/transaction-pool" default-features = false optional = true +path = "../substrate/client/transaction-pool" [dependencies.sc-transaction-pool-api] -path = "../substrate/client/transaction-pool/api" default-features = false optional = true +path = "../substrate/client/transaction-pool/api" [dependencies.sc-utils] -path = "../substrate/client/utils" default-features = false optional = true +path = "../substrate/client/utils" [dependencies.snowbridge-runtime-test-common] -path = "../bridges/snowbridge/runtime/test-common" default-features = false optional = true +path = "../bridges/snowbridge/runtime/test-common" [dependencies.sp-blockchain] -path = "../substrate/primitives/blockchain" default-features = false optional = true +path = "../substrate/primitives/blockchain" [dependencies.sp-consensus] -path = "../substrate/primitives/consensus/common" default-features = false optional = true +path = "../substrate/primitives/consensus/common" [dependencies.sp-core-hashing] -path = "../substrate/deprecated/hashing" default-features = false optional = true +path = "../substrate/deprecated/hashing" [dependencies.sp-core-hashing-proc-macro] -path = "../substrate/deprecated/hashing/proc-macro" default-features = false optional = true +path = "../substrate/deprecated/hashing/proc-macro" [dependencies.sp-database] -path = "../substrate/primitives/database" default-features = false optional = true +path = "../substrate/primitives/database" [dependencies.sp-maybe-compressed-blob] -path = "../substrate/primitives/maybe-compressed-blob" default-features = false optional = true +path = "../substrate/primitives/maybe-compressed-blob" [dependencies.sp-panic-handler] -path = "../substrate/primitives/panic-handler" default-features = false optional = true +path = "../substrate/primitives/panic-handler" [dependencies.sp-rpc] -path = "../substrate/primitives/rpc" default-features = false optional = true +path = "../substrate/primitives/rpc" [dependencies.staging-chain-spec-builder] -path = "../substrate/bin/utils/chain-spec-builder" default-features = false optional = true +path = "../substrate/bin/utils/chain-spec-builder" [dependencies.staging-node-inspect] -path = "../substrate/bin/node/inspect" default-features = false optional = true +path = "../substrate/bin/node/inspect" [dependencies.staging-tracking-allocator] -path = "../polkadot/node/tracking-allocator" default-features = false optional = true +path = "../polkadot/node/tracking-allocator" [dependencies.subkey] -path = "../substrate/bin/utils/subkey" default-features = false optional = true +path = "../substrate/bin/utils/subkey" [dependencies.substrate-build-script-utils] -path = "../substrate/utils/build-script-utils" default-features = false optional = true +path = "../substrate/utils/build-script-utils" [dependencies.substrate-frame-rpc-support] -path = "../substrate/utils/frame/rpc/support" default-features = false optional = true +path = "../substrate/utils/frame/rpc/support" [dependencies.substrate-frame-rpc-system] -path = "../substrate/utils/frame/rpc/system" default-features = false optional = true +path = "../substrate/utils/frame/rpc/system" [dependencies.substrate-prometheus-endpoint] -path = "../substrate/utils/prometheus" default-features = false optional = true +path = "../substrate/utils/prometheus" [dependencies.substrate-rpc-client] -path = "../substrate/utils/frame/rpc/client" default-features = false optional = true +path = "../substrate/utils/frame/rpc/client" [dependencies.substrate-state-trie-migration-rpc] -path = "../substrate/utils/frame/rpc/state-trie-migration-rpc" default-features = false optional = true +path = "../substrate/utils/frame/rpc/state-trie-migration-rpc" [dependencies.substrate-wasm-builder] -path = "../substrate/utils/wasm-builder" default-features = false optional = true +path = "../substrate/utils/wasm-builder" [dependencies.tracing-gum] -path = "../polkadot/node/gum" default-features = false optional = true +path = "../polkadot/node/gum" [dependencies.xcm-emulator] -path = "../cumulus/xcm/xcm-emulator" default-features = false optional = true +path = "../cumulus/xcm/xcm-emulator" [dependencies.xcm-simulator] -path = "../polkadot/xcm/xcm-simulator" default-features = false optional = true +path = "../polkadot/xcm/xcm-simulator" [package.metadata.docs.rs] features = ["node", "runtime-full"] diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 2216864fad0f7bc304dbb34f5395bc08f05789ad..a132f16a2c33f617ee1a916deb585534d3e6f99c 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -141,6 +141,10 @@ pub use cumulus_pallet_session_benchmarking; #[cfg(feature = "cumulus-pallet-solo-to-para")] pub use cumulus_pallet_solo_to_para; +/// pallet and transaction extensions for accurate proof size reclaim. +#[cfg(feature = "cumulus-pallet-weight-reclaim")] +pub use cumulus_pallet_weight_reclaim; + /// Pallet for stuff specific to parachains' usage of XCM. #[cfg(feature = "cumulus-pallet-xcm")] pub use cumulus_pallet_xcm; @@ -308,6 +312,10 @@ pub use pallet_asset_conversion_tx_payment; #[cfg(feature = "pallet-asset-rate")] pub use pallet_asset_rate; +/// FRAME asset rewards pallet. +#[cfg(feature = "pallet-asset-rewards")] +pub use pallet_asset_rewards; + /// pallet to manage transaction payments in assets. #[cfg(feature = "pallet-asset-tx-payment")] pub use pallet_asset_tx_payment; @@ -584,10 +592,6 @@ pub use pallet_revive; #[cfg(feature = "pallet-revive-eth-rpc")] pub use pallet_revive_eth_rpc; -/// 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; @@ -1123,6 +1127,10 @@ pub use sc_rpc_server; #[cfg(feature = "sc-rpc-spec-v2")] pub use sc_rpc_spec_v2; +/// Substrate client utilities for frame runtime functions calls. +#[cfg(feature = "sc-runtime-utilities")] +pub use sc_runtime_utilities; + /// Substrate service. Starts a thread that spins up the network, client, and extrinsic pool. /// Manages communication between them. #[cfg(feature = "sc-service")]